1 //===- LinalgInterfaces.cpp - Linalg interfaces implementation ------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "mlir/Dialect/Linalg/IR/LinalgInterfaces.h"
10 
11 #include "mlir/Dialect/Affine/IR/AffineOps.h"
12 #include "mlir/Dialect/Arithmetic/IR/Arithmetic.h"
13 #include "mlir/Dialect/MemRef/IR/MemRef.h"
14 #include "mlir/Dialect/Tensor/IR/Tensor.h"
15 #include "mlir/IR/AffineExprVisitor.h"
16 #include "mlir/IR/AffineMap.h"
17 #include "mlir/IR/TypeUtilities.h"
18 #include "llvm/ADT/SmallBitVector.h"
19 
20 using namespace mlir;
21 using namespace mlir::linalg;
22 
23 /// Include the definitions of the copy operation interface.
24 #include "mlir/Dialect/Linalg/IR/LinalgInterfaces.cpp.inc"
25 
26 //===----------------------------------------------------------------------===//
27 // ContractionOpInterface implementation
28 //===----------------------------------------------------------------------===//
29 
30 /// Return true if the use-def chain from `v` to `from` consists of 0 or more
31 /// unary single-operand operations.
32 // TODO: relax to multi-operands with constants, which are technically unary ops
33 // as needed (e.g. add5).
34 static bool isChainOfUnaryOpsFrom(Value v, Value from) {
35   while (true) {
36     if (v == from)
37       return true;
38     Operation *op = v.getDefiningOp();
39     if (!op || op->getNumOperands() != 1)
40       return false;
41     v = op->getOperand(0);
42   };
43 }
44 
45 /// Return the unique instance of OpType in `block` if it is indeed unique.
46 /// Return null if none or more than 1 instances exist.
47 template <typename OpType>
48 static OpType getSingleOpOfType(Block &block) {
49   OpType res = nullptr;
50   block.walk([&](OpType op) {
51     if (res) {
52       res = nullptr;
53       return WalkResult::interrupt();
54     }
55     res = op;
56     return WalkResult::advance();
57   });
58   return res;
59 }
60 
61 /// Detect whether res is any permutation of `u5(u1(c) + u2(u3(a) * u4(b)))`
62 /// on the field (AddOpType, MulOpType), where u1, u2, u3, u4 and u5 represent
63 /// unary operations that may change the type.
64 template <typename AddOpType, typename MulOpType>
65 static bool isAddMul(Block &block) {
66   if (block.getNumArguments() != 3)
67     return false;
68   Operation *yieldOp = block.getTerminator();
69   if (yieldOp->getNumOperands() != 1)
70     return false;
71 
72   AddOpType addOp = getSingleOpOfType<AddOpType>(block);
73   MulOpType mulOp = getSingleOpOfType<MulOpType>(block);
74   if (!addOp || !mulOp)
75     return false;
76 
77   Value argA = block.getArgument(0), argB = block.getArgument(1);
78   Value a = mulOp->getOperand(0), b = mulOp->getOperand(1);
79   Value mul = mulOp->getResult(0);
80   Value argC = block.getArgument(2);
81   Value c1 = addOp->getOperand(0), c2 = addOp->getOperand(1);
82   Value add = addOp->getResult(0);
83   Value res = yieldOp->getOperand(0);
84   // Result traces back to add.
85   auto un = isChainOfUnaryOpsFrom;
86   bool success = un(res, add);
87   // One of the operands of add traces back to argC, the other to the mul.
88   success |= (un(c1, argC) && un(c2, mul)) || ((un(c1, mul)) && un(c2, argC));
89   // One of the operands of mul traces back to argA, the other to argB.
90   success |= (un(a, argA) && un(b, argB)) || ((un(a, argB)) && un(b, argA));
91   return success;
92 }
93 
94 enum class MatchContractionResult {
95   Success = 0,
96   NotLinalgOp,
97   WrongNumOperands,
98   NoReduction,
99   NotProjectedPermutations,
100   NotAddMul
101 };
102 static MatchContractionResult isContractionInterfaceImpl(Operation *op) {
103   auto linalgOp = dyn_cast<linalg::LinalgOp>(op);
104   if (!linalgOp)
105     return MatchContractionResult::NotLinalgOp;
106   if (linalgOp.getNumInputs() != 2 || linalgOp.getNumOutputs() != 1)
107     return MatchContractionResult::WrongNumOperands;
108   auto mapRange = linalgOp.indexing_maps().getAsValueRange<AffineMapAttr>();
109   if (linalgOp.getNumReductionLoops() == 0)
110     return MatchContractionResult::NoReduction;
111   if (llvm::any_of(mapRange,
112                    [](AffineMap m) { return !m.isProjectedPermutation(); }))
113     return MatchContractionResult::NotProjectedPermutations;
114   // TODO: more fields than add/mul.
115   if (!isAddMul<arith::AddFOp, arith::MulFOp>(linalgOp->getRegion(0).front()) &&
116       !isAddMul<arith::AddIOp, arith::MulIOp>(linalgOp->getRegion(0).front()))
117     return MatchContractionResult::NotAddMul;
118   return MatchContractionResult::Success;
119 }
120 
121 bool mlir::linalg::isaContractionOpInterface(LinalgOp linalgOp) {
122   if (!linalgOp)
123     return false;
124   Operation *op = linalgOp.getOperation();
125   return isa<ContractionOpInterface>(op) ||
126          (isContractionInterfaceImpl(op) == MatchContractionResult::Success);
127 }
128 
129 /// Verify that a LinalgOp `op` is a contraction.
130 /// A Linalg contraction is defined in general terms:
131 ///   1. Has 2 input and 1 output shapes.
132 ///   2. Has at least one reduction dimension.
133 ///   3. Has only projected permutation indexing maps.
134 ///   4. its body computes `u5(u1(c) + u2(u3(a) * u4(b)))` on some field
135 ///   (AddOpType, MulOpType), where u1, u2, u3, u4 and u5 represent scalar unary
136 ///   operations that may change the type (e.g. for mixed-precision).
137 /// As a consequence, when vectorization of such an op occurs, the only special
138 /// behavior is that the (unique) MulOpType is vectorized into a
139 /// `vector.contract`. All other ops are handled in a generic fashion.
140 /// In the future, we may wish to allow more input arguments and elementwise and
141 /// constant operations that do not involve the reduction dimension(s).
142 LogicalResult mlir::linalg::detail::verifyContractionInterface(Operation *op) {
143   auto res = isContractionInterfaceImpl(op);
144   if (res == MatchContractionResult::NotLinalgOp)
145     return op->emitError("expected a LinalgOp");
146   if (res == MatchContractionResult::WrongNumOperands)
147     return op->emitError("expected op with 2 inputs and 1 outputs");
148   if (res == MatchContractionResult::NoReduction)
149     return op->emitError("expected at least a reduction loop");
150   if (res == MatchContractionResult::NotProjectedPermutations)
151     return op->emitError("expected all indexings to be projected permutations");
152   if (res == MatchContractionResult::NotAddMul)
153     return op->emitError("(add, mul) operations not found");
154   return success();
155 }
156 
157 //===----------------------------------------------------------------------===//
158 // ConvolutionOpInterface implementation
159 //===----------------------------------------------------------------------===//
160 
161 /// Of the given two expressions returns one that is of type T (`lhs` gets
162 /// preference over `rhs`)
163 template <typename T>
164 static T getAffineExprOfType(AffineExpr lhs, AffineExpr rhs) {
165   return lhs.isa<T>() ? lhs.cast<T>()
166                       : (rhs.isa<T>() ? rhs.cast<T>() : nullptr);
167 }
168 
169 namespace {
170 /// Walk the indexing expressions for input of a convolution operation to verify
171 /// its of the right form, either
172 /// - AffineDimExpr
173 /// - AffineDimExpr (`*` (AffineSymbolExpr | AffineConstantExpr))?
174 ///      (`+` AffineDimExpr (`*` (AffineSymbolExpr | AffineConstantExpr))?)*
175 ///
176 /// classifies the AffineDimExpr as convolved dimensions or unconvolved
177 /// dimensions and verifies each dimension occurs only once.
178 struct ConvAccessExprWalker
179     : public AffineExprVisitor<ConvAccessExprWalker, LogicalResult> {
180   llvm::SmallDenseSet<unsigned> convolvedDims;
181   llvm::SmallDenseSet<unsigned> unConvolvedDims;
182 
183   LogicalResult visitDimExpr(AffineDimExpr dimExpr) {
184     unsigned position = dimExpr.getPosition();
185     if (unConvolvedDims.count(position) || convolvedDims.count(position)) {
186       return failure();
187     }
188     unConvolvedDims.insert(position);
189     return success();
190   }
191 
192   LogicalResult visitSymbolExpr(AffineSymbolExpr expr) { return failure(); }
193 
194   LogicalResult visitConstantExpr(AffineConstantExpr expr) { return failure(); }
195 
196   LogicalResult visitAffineBinaryOpExpr(AffineBinaryOpExpr binaryExpr) {
197     // In pre-order visit, top level op has to be an add op.
198     if (binaryExpr.getKind() != AffineExprKind::Add)
199       return failure();
200     return success(succeeded(isDimExprOrMulExpr(binaryExpr.getLHS())) &&
201                    succeeded(isDimExprOrMulExpr(binaryExpr.getRHS())));
202   }
203 
204   LogicalResult isDimExprOrMulExpr(AffineExpr expr) {
205     if (auto dimExpr = expr.dyn_cast<AffineDimExpr>()) {
206       unsigned dim = dimExpr.getPosition();
207       if (convolvedDims.count(dim) || unConvolvedDims.count(dim))
208         return failure();
209       convolvedDims.insert(dim);
210       return success();
211     }
212     if (auto symbolMulExpr = expr.dyn_cast<AffineBinaryOpExpr>()) {
213       if (symbolMulExpr.getKind() != AffineExprKind::Mul)
214         return failure();
215       auto lhsExpr = symbolMulExpr.getLHS();
216       auto rhsExpr = symbolMulExpr.getRHS();
217       // Check for symbol expression.
218       AffineExpr mulExpr =
219           getAffineExprOfType<AffineSymbolExpr>(lhsExpr, rhsExpr);
220       // If there was no symbol expr, check for constant expression.
221       if (!mulExpr) {
222         mulExpr = getAffineExprOfType<AffineConstantExpr>(lhsExpr, rhsExpr);
223       }
224       auto dimExpr = getAffineExprOfType<AffineDimExpr>(lhsExpr, rhsExpr);
225       if (!mulExpr || !dimExpr)
226         return failure();
227       unsigned dim = dimExpr.getPosition();
228       if (convolvedDims.count(dim) || unConvolvedDims.count(dim))
229         return failure();
230       convolvedDims.insert(dim);
231       return success();
232     }
233     return failure();
234   }
235 };
236 } // namespace
237 
238 static llvm::SmallDenseSet<unsigned> getPreservedDims(AffineMap map) {
239   assert(map.isProjectedPermutation() &&
240          "expected map to have projected permutations");
241   llvm::SmallDenseSet<unsigned> preservedDims;
242   for (auto expr : map.getResults())
243     preservedDims.insert(expr.cast<AffineDimExpr>().getPosition());
244   return preservedDims;
245 }
246 
247 enum class MatchConvolutionResult {
248   Success = 0,
249   NotLinalgOp,
250   WrongNumOperands,
251   WrongInputIndexingMap,
252   NotProjectedPermutations,
253   NonConvolutionLoop,
254   OutputDimsNotParallel,
255   NonOutputDimNotReduction
256 };
257 
258 static MatchConvolutionResult isConvolutionInterfaceImpl(Operation *op) {
259   auto linalgOp = dyn_cast<linalg::LinalgOp>(op);
260   if (!linalgOp)
261     return MatchConvolutionResult::NotLinalgOp;
262   if (linalgOp.getNumInputs() < 2 || linalgOp.getNumOutputs() != 1)
263     return MatchConvolutionResult::WrongNumOperands;
264 
265   auto indexingMaps = linalgOp.getIndexingMaps();
266 
267   // Check the input indexing map has the right form.
268   ConvAccessExprWalker inputExprWalker;
269   if (llvm::any_of(indexingMaps[0].getResults(),
270                    [&inputExprWalker](AffineExpr expr) {
271                      return failed(inputExprWalker.visit(expr));
272                    })) {
273     return MatchConvolutionResult::WrongInputIndexingMap;
274   }
275 
276   // Filter and output maps must be projected permutation.
277   if (!indexingMaps[1].isProjectedPermutation() ||
278       !indexingMaps.back().isProjectedPermutation())
279     return MatchConvolutionResult::NotProjectedPermutations;
280 
281   auto iteratorTypesRange =
282       linalgOp.iterator_types().getAsValueRange<StringAttr>();
283 
284   llvm::SmallDenseSet<unsigned> outputDims =
285       getPreservedDims(indexingMaps.back());
286   llvm::SmallDenseSet<unsigned> filterDims = getPreservedDims(indexingMaps[1]);
287   // Make sure all loops are charecterized as one of:
288   // - Batch loop : present in output, as non-convolved in input, not present in
289   //   filter.
290   // - Output image dimension : present in output, convolved dims in input, not
291   //   present in filter.
292   // - Output channel dimension : present in output, not present in input,
293   //   present in filter.
294   // - Filter loop dimension : present in filter, convolved in input, not
295   //   present in output.
296   // - Input channel dimension : unconvolved in input, not present in output,
297   //   present in filter.
298   // - Depth multiplier : unconvolved in input, present in output, present in
299   //   filter.
300   llvm::SmallDenseSet<unsigned> allLoopDims;
301   for (auto outputExpr : indexingMaps.back().getResults()) {
302     unsigned outputDim = outputExpr.cast<AffineDimExpr>().getPosition();
303     if (inputExprWalker.unConvolvedDims.count(outputDim) &&
304         !filterDims.count(outputDim)) {
305       // Batch dimension.
306       if (*std::next(iteratorTypesRange.begin(), outputDim) !=
307           getParallelIteratorTypeName())
308         return MatchConvolutionResult::OutputDimsNotParallel;
309       allLoopDims.insert(outputDim);
310       continue;
311     }
312     if (inputExprWalker.convolvedDims.count(outputDim) &&
313         !filterDims.count(outputDim)) {
314       // Output image Loop dimension.
315       if (*std::next(iteratorTypesRange.begin(), outputDim) !=
316           getParallelIteratorTypeName())
317         return MatchConvolutionResult::OutputDimsNotParallel;
318       allLoopDims.insert(outputDim);
319       continue;
320     }
321     if (!inputExprWalker.convolvedDims.count(outputDim) &&
322         !inputExprWalker.unConvolvedDims.count(outputDim) &&
323         filterDims.count(outputDim)) {
324       // Output channel dimension.
325       if (*std::next(iteratorTypesRange.begin(), outputDim) !=
326           getParallelIteratorTypeName())
327         return MatchConvolutionResult::OutputDimsNotParallel;
328       allLoopDims.insert(outputDim);
329       continue;
330     }
331     if (inputExprWalker.unConvolvedDims.count(outputDim) &&
332         filterDims.count(outputDim)) {
333       // Depth multiplier.
334       if (*std::next(iteratorTypesRange.begin(), outputDim) !=
335           getParallelIteratorTypeName())
336         return MatchConvolutionResult::OutputDimsNotParallel;
337       allLoopDims.insert(outputDim);
338       continue;
339     }
340     return MatchConvolutionResult::NonConvolutionLoop;
341   }
342   for (auto filterExpr : indexingMaps[1].getResults()) {
343     unsigned filterDim = filterExpr.cast<AffineDimExpr>().getPosition();
344     if (outputDims.count(filterDim) &&
345         !inputExprWalker.unConvolvedDims.count(filterDim) &&
346         !inputExprWalker.convolvedDims.count(filterDim)) {
347       // Output channel dimension. THis is already seen, continue;
348       continue;
349     }
350     if (inputExprWalker.convolvedDims.count(filterDim) &&
351         !outputDims.count(filterDim)) {
352       // Filter loop dimension.
353       if (*std::next(iteratorTypesRange.begin(), filterDim) !=
354           getReductionIteratorTypeName())
355         return MatchConvolutionResult::NonOutputDimNotReduction;
356       if (allLoopDims.count(filterDim))
357         return MatchConvolutionResult::NonConvolutionLoop;
358       allLoopDims.insert(filterDim);
359       continue;
360     }
361     if (inputExprWalker.unConvolvedDims.count(filterDim) &&
362         !outputDims.count(filterDim)) {
363       // Input channel dimension.
364       if (*std::next(iteratorTypesRange.begin(), filterDim) !=
365           getReductionIteratorTypeName())
366         return MatchConvolutionResult::NonOutputDimNotReduction;
367       if (allLoopDims.count(filterDim))
368         return MatchConvolutionResult::NonConvolutionLoop;
369       allLoopDims.insert(filterDim);
370       continue;
371     }
372     if (inputExprWalker.unConvolvedDims.count(filterDim) &&
373         outputDims.count(filterDim)) {
374       // Depthwise loop. Already seen.
375       continue;
376     }
377     return MatchConvolutionResult::NonConvolutionLoop;
378   }
379   // All loops must be covered now.
380   if (allLoopDims.size() != linalgOp.getNumLoops())
381     return MatchConvolutionResult::NonConvolutionLoop;
382 
383   return MatchConvolutionResult::Success;
384 }
385 
386 LogicalResult mlir::linalg::detail::verifyConvolutionInterface(Operation *op) {
387   auto res = isConvolutionInterfaceImpl(op);
388   if (res == MatchConvolutionResult::NotLinalgOp)
389     return op->emitError("expected a LinalgOp");
390   if (res == MatchConvolutionResult::WrongNumOperands)
391     return op->emitError("expected op with 2 inputs and 1 output");
392   if (res == MatchConvolutionResult::WrongInputIndexingMap)
393     return op->emitError("unexpected input index map for convolutions");
394   if (res == MatchConvolutionResult::NotProjectedPermutations) {
395     return op->emitError(
396         "expected output/filter indexing maps to be projected permutations");
397   }
398   if (res == MatchConvolutionResult::NonConvolutionLoop) {
399     return op->emitError("unexpected loop dimension for convolution op");
400   }
401   if (res == MatchConvolutionResult::OutputDimsNotParallel) {
402     return op->emitError(
403         "expected all iterators used to access outputs to be parallel");
404   }
405   if (res == MatchConvolutionResult::NonOutputDimNotReduction) {
406     return op->emitError(
407         "expected all iterators not used to access outputs to be reduction");
408   }
409   return success();
410 }
411 
412 //===----------------------------------------------------------------------===//
413 // FillOpInterface implementation
414 //===----------------------------------------------------------------------===//
415 
416 enum class MatchFillResult {
417   Success = 0,
418   NotLinalgOp,
419   WrongNumOperands,
420   NotScalarInput
421 };
422 
423 static MatchFillResult isFillInterfaceImpl(Operation *op) {
424   auto linalgOp = dyn_cast<linalg::LinalgOp>(op);
425   if (!linalgOp)
426     return MatchFillResult::NotLinalgOp;
427   if (linalgOp.getNumInputs() != 1 || linalgOp.getNumOutputs() != 1)
428     return MatchFillResult::WrongNumOperands;
429 
430   OpOperand *value = linalgOp.getInputOperand(0);
431   if (!linalgOp.isScalar(value))
432     return MatchFillResult::NotScalarInput;
433 
434   return MatchFillResult::Success;
435 }
436 
437 LogicalResult mlir::linalg::detail::verifyFillInterface(Operation *op) {
438   auto res = isFillInterfaceImpl(op);
439   if (res == MatchFillResult::NotLinalgOp)
440     return op->emitError("expected a LinalgOp");
441   if (res == MatchFillResult::WrongNumOperands)
442     return op->emitError("expected op with 1 input and 1 output");
443   if (res == MatchFillResult::NotScalarInput)
444     return op->emitError("expected op with scalar input");
445 
446   return success();
447 }
448 
449 //===----------------------------------------------------------------------===//
450 // StructuredOpInterface implementation
451 //===----------------------------------------------------------------------===//
452 
453 OpOperandVector::operator SmallVector<Value>() {
454   SmallVector<Value> result;
455   result.reserve(this->size());
456   llvm::transform(*this, std::back_inserter(result),
457                   [](OpOperand *opOperand) { return opOperand->get(); });
458   return result;
459 }
460 
461 /// Helper function that creates a memref::DimOp or tensor::DimOp depending on
462 /// the type of `source`.
463 static Value createOrFoldDimOp(OpBuilder &b, Location loc, Value source,
464                                int64_t dim) {
465   if (source.getType().isa<UnrankedMemRefType, MemRefType>())
466     return b.createOrFold<memref::DimOp>(loc, source, dim);
467   if (source.getType().isa<UnrankedTensorType, RankedTensorType>())
468     return b.createOrFold<tensor::DimOp>(loc, source, dim);
469   llvm_unreachable("Expected MemRefType or TensorType");
470 }
471 
472 SmallVector<Value, 4> LinalgOp::createFlatListOfOperandDims(OpBuilder &b,
473                                                             Location loc) {
474   SmallVector<Value, 4> res;
475   for (OpOperand *opOperand : getInputAndOutputOperands()) {
476     for (int64_t i = 0, e = getRank(opOperand); i < e; ++i)
477       res.push_back(createOrFoldDimOp(b, loc, opOperand->get(), i));
478   }
479   return res;
480 }
481 
482 SmallVector<int64_t, 4> LinalgOp::createFlatListOfOperandStaticDims() {
483   SmallVector<int64_t, 4> res;
484   assert(!hasDynamicShape() && "expected operands to have static shapes");
485   for (OpOperand *opOperand : getInputAndOutputOperands())
486     llvm::append_range(res, getShape(opOperand));
487   return res;
488 }
489 
490 SmallVector<Range, 4> LinalgOp::createLoopRanges(OpBuilder &b, Location loc) {
491   AffineMap map = getLoopsToShapesMap();
492   unsigned numDims = map.getNumDims(), numRes = map.getNumResults();
493   auto viewSizes = createFlatListOfOperandDims(b, loc);
494   SmallVector<Range, 4> res(numDims);
495   Value zeroVal = b.create<arith::ConstantIndexOp>(loc, 0);
496   Value oneVal = b.create<arith::ConstantIndexOp>(loc, 1);
497   for (unsigned idx = 0; idx < numRes; ++idx) {
498     auto result = map.getResult(idx);
499     if (auto d = result.dyn_cast<AffineDimExpr>()) {
500       if (res[d.getPosition()].offset)
501         continue;
502       res[d.getPosition()] = Range{zeroVal, viewSizes[idx], oneVal};
503     }
504   }
505   return res;
506 }
507 
508 SmallVector<int64_t, 4> LinalgOp::computeStaticLoopSizes() {
509   AffineMap map = getLoopsToShapesMap();
510   unsigned numDims = map.getNumDims(), numRes = map.getNumResults();
511   SmallVector<int64_t, 4> allShapeSizes = createFlatListOfOperandStaticDims();
512   SmallVector<int64_t, 4> res(numDims, 0);
513   for (unsigned idx = 0; idx < numRes; ++idx) {
514     auto result = map.getResult(idx);
515     if (auto d = result.dyn_cast<AffineDimExpr>())
516       res[d.getPosition()] = allShapeSizes[idx];
517   }
518   return res;
519 }
520 
521 /// Visitor to check if any of the given set of positions from AffineDimExprs
522 /// are used within an AffineExpr.
523 struct HasAffineDimExprVisitor
524     : public AffineExprVisitor<HasAffineDimExprVisitor, bool> {
525   HasAffineDimExprVisitor(llvm::SmallBitVector positions)
526       : positions(std::move(positions)) {}
527 
528   bool visitAffineBinaryOpExpr(AffineBinaryOpExpr binaryOpExpr) {
529     return visit(binaryOpExpr.getLHS()) || visit(binaryOpExpr.getRHS());
530   }
531 
532   bool visitDimExpr(AffineDimExpr dimExpr) {
533     return positions.test(dimExpr.getPosition());
534   }
535 
536   bool visitConstantExpr(AffineConstantExpr constExpr) { return false; }
537 
538   bool visitSymbolExpr(AffineSymbolExpr symbolExpr) { return false; }
539 
540 private:
541   llvm::SmallBitVector positions;
542 };
543 
544 LogicalResult
545 LinalgOp::reifyResultShapes(OpBuilder &b,
546                             ReifiedRankedShapedTypeDims &reifiedReturnShapes) {
547   // An example that helps understand the logic below.
548   // Consider the following expression O(i+j, j) += A(i,k) * B(k, j)
549   // We want to express the shape of dim 0 of O in terms of shape of the inputs.
550   // This is achieved as follows.
551   //   loopsToShapesMap = (d0, d1, d2) -> (d0, d2, d2, d1, d0 + d1, d1)
552   //   subMapOfResultShapes = (d0, d1, d2) -> (d0 + d1, d1)
553   //   shapesToLoopsMap = (d0, d2, d2, d3, d4, d5) -> (d0, d3, d2)
554   //   resultShapesFromInputShapes = subMapOfResultDim.compose(shapesToLoopMap)
555   //     = (d0, d1, d2, d3, d4, d5) -> (d0 + d1, d1)
556   AffineMap loopsToShapesMap = getLoopsToShapesMap();
557 
558   // Find the position in the above map that represents the shape of the
559   // result:dim being inferred.
560   auto resultShapesSubMapPos = getResultsPositionInLoopsToShapeMap();
561 
562   /// From loopsToShapesMap extract the submap that represents the shape of the
563   /// (resultIdx, dim) needed.
564   AffineMap loopToResultsShapeMap = loopsToShapesMap.getSliceMap(
565       resultShapesSubMapPos.first,
566       resultShapesSubMapPos.second - resultShapesSubMapPos.first);
567   AffineMap resultShapesFromInputShapesMap =
568       loopToResultsShapeMap.compose(getShapesToLoopsMap());
569 
570   // Check that the result dim map does not contain the positions corresponding
571   // to the outputs.
572   llvm::SmallBitVector outputDims(resultShapesFromInputShapesMap.getNumDims());
573   outputDims.set(resultShapesSubMapPos.first, resultShapesSubMapPos.second);
574   HasAffineDimExprVisitor checkDimExpr(std::move(outputDims));
575   Location loc = getOperation()->getLoc();
576   auto allResultDimValues =
577       applyMapToValues(b, loc, resultShapesFromInputShapesMap,
578                        createFlatListOfOperandDims(b, loc));
579   int64_t pos = 0;
580   ArrayRef<AffineExpr> shapeExprs = resultShapesFromInputShapesMap.getResults();
581   for (OpOperand *opOperand : getOutputOperands()) {
582     SmallVector<Value> shapes;
583     for (int64_t dim : llvm::seq<int64_t>(0, getRank(opOperand))) {
584       if (checkDimExpr.visit(shapeExprs[pos]))
585         shapes.push_back(createOrFoldDimOp(b, loc, opOperand->get(), dim));
586       else
587         shapes.push_back(allResultDimValues[pos]);
588       pos++;
589     }
590     reifiedReturnShapes.emplace_back(std::move(shapes));
591   }
592   return success();
593 }
594 
595 LogicalResult mlir::linalg::detail::verifyStructuredOpInterface(Operation *op) {
596   LinalgOp linalgOp = cast<LinalgOp>(op);
597   // Expect at least one output operand.
598   // This means an op that constructs a tensor out of indices cannot be a
599   // LinalgOp at the moment. For now this will have to be a special op until we
600   // have output shape operands that are not tensors.
601   int64_t numInputs = linalgOp.getNumInputs();
602   int64_t numOutputs = linalgOp.getNumOutputs();
603   if (numOutputs == 0)
604     return op->emitOpError("expected at least one output operand");
605   if (failed(OpTrait::impl::verifyNOperands(op, numInputs + numOutputs)))
606     return failure();
607   // Verify the number of results matches the number of output tensors.
608   if (op->getNumResults() != linalgOp.getOutputTensorOperands().size())
609     return op->emitOpError("expected the number of results (")
610            << op->getNumResults()
611            << ") to be equal to the number of output tensors ("
612            << linalgOp.getOutputTensorOperands().size() << ")";
613 
614   // Check all iterator types are known.
615   auto iteratorTypesRange =
616       linalgOp.iterator_types().getAsValueRange<StringAttr>();
617   for (StringRef iteratorType : iteratorTypesRange) {
618     if (!llvm::is_contained(getAllIteratorTypeNames(), iteratorType))
619       return op->emitOpError("unexpected iterator_type (")
620              << iteratorType << ")";
621   }
622 
623   // Before checking indexing maps, we need to make sure the attributes
624   // referenced by it are valid.
625   if (linalgOp.hasDynamicIndexingMaps())
626     if (failed(linalgOp.verifyIndexingMapRequiredAttributes()))
627       return failure();
628 
629   // All input/output operands must be indexed.
630   if (static_cast<int64_t>(linalgOp.indexing_maps().size()) !=
631       linalgOp.getNumInputsAndOutputs())
632     return op->emitOpError("expected the number of indexing_map (")
633            << linalgOp.indexing_maps().size()
634            << ") to be equal to the number of input/output operands ("
635            << linalgOp.getNumInputsAndOutputs() << ")";
636 
637   for (OpOperand *opOperand : linalgOp.getInputAndOutputOperands()) {
638     AffineMap indexingMap = linalgOp.getTiedIndexingMap(opOperand);
639 
640     // Symbols disallowed.
641     if (indexingMap.getNumSymbols() != 0)
642       return op->emitOpError("unexpected symbols in indexing_map #")
643              << opOperand->getOperandNumber();
644 
645     // Domain must be consistent.
646     unsigned numLoops = linalgOp.getNumLoops();
647     if (indexingMap.getNumDims() != numLoops)
648       return op->emitOpError("expected indexing_map #")
649              << opOperand->getOperandNumber() << " to have " << numLoops
650              << " dim(s) to match the number of loops";
651 
652     int64_t rank = linalgOp.getRank(opOperand);
653     if (indexingMap.getNumResults() != rank)
654       return op->emitOpError("expected operand rank (")
655              << rank << ") to match the result rank of indexing_map #"
656              << opOperand->getOperandNumber() << " ("
657              << indexingMap.getNumResults() << ")";
658   }
659 
660   SmallVector<unsigned> redDims;
661   linalgOp.getReductionDims(redDims);
662 
663   // Simplifying assumption: either full tensor or full buffer mode.
664   // This allows simpler verification of output operands vs result types
665   // without premature tracking of which operand is what in mixed-mode.
666   // TODO: relax when mixed-mode needs to pass verification.
667   if (!linalgOp.getOutputBufferOperands().empty() &&
668       !linalgOp.getOutputTensorOperands().empty())
669     return op->emitOpError(
670         "expected output operands to all have tensor type or "
671         "all have buffer type");
672 
673   for (OpOperand *opOperand : linalgOp.getOutputTensorOperands()) {
674     OpResult result = linalgOp.getTiedOpResult(opOperand);
675     if (result.getType() != opOperand->get().getType())
676       return op->emitOpError("expected type of operand #")
677              << opOperand->getOperandNumber() << " ("
678              << opOperand->get().getType() << ")"
679              << " to match type of corresponding result (" << result.getType()
680              << ")";
681   }
682 
683   // Output tensor indexing map may not depend on reduction indices.
684   for (OpOperand *opOperand : linalgOp.getOutputOperands()) {
685     AffineMap indexingMap = linalgOp.getTiedIndexingMap(opOperand);
686     for (AffineExpr expr : indexingMap.getResults()) {
687       for (unsigned pos : redDims) {
688         if (expr.isFunctionOfDim(pos)) {
689           std::string exprStr;
690           {
691             llvm::raw_string_ostream os(exprStr);
692             os << expr;
693           }
694           return op->emitOpError(
695                      "unexpected output tensor expression in indexing map #")
696                  << (opOperand->getOperandNumber() - linalgOp.getNumInputs())
697                  << " a.k.a '" << exprStr
698                  << "' is function of reduction iterator 'd" << pos << "'";
699         }
700       }
701     }
702   }
703 
704   // Check the region has exactly one block.
705   if (linalgOp->getNumRegions() != 1 ||
706       !llvm::hasSingleElement(linalgOp->getRegion(0)))
707     return op->emitOpError("expects to have 1 region with 1 block");
708 
709   if (!linalgOp.getShapesToLoopsMap())
710     return op->emitOpError("expected the shape-to-loops map to be non-null");
711 
712   // Simplifying assumption: bbargs match 1-1 with shape operands elemental
713   // types.
714   // TODO: once ranked shape types are plugged in, we may want to drop the
715   // corresponding bbargs, that can never be read from. This will be subject to
716   // consistency discussions (i.e. what to do with output tensors whose bbarg is
717   // not used).
718   Block &block = linalgOp->getRegion(0).front();
719 
720   if (linalgOp.getNumInputsAndOutputs() != block.getNumArguments())
721     return op->emitOpError("expected as many non-induction variable region "
722                            "arguments as the number of input/output operands");
723 
724   for (OpOperand *opOperand : linalgOp.getInputAndOutputOperands()) {
725     Type elementType = getElementTypeOrSelf(opOperand->get());
726     Type argType = block.getArgument(opOperand->getOperandNumber()).getType();
727     if (elementType != argType)
728       return op->emitOpError("expected type of bb argument #")
729              << opOperand->getOperandNumber() << " (" << argType << ")"
730              << " to match element or self type of the corresponding operand ("
731              << elementType << ")";
732   }
733 
734   // Check if given shapes match to inferred shapes.
735   Optional<SmallVector<int64_t, 4>> endLoopRangeValues =
736       linalgOp.getStaticLoopRanges();
737   if (!endLoopRangeValues)
738     return op->emitOpError("unable to find loop range for operation");
739   SmallVector<int64_t, 4> startLoopRangeValues((*endLoopRangeValues).size(), 0);
740 
741   // Verify only static cases since we can't get exact dimension sizes and loop
742   // ranges for dynamic cases in this stage.
743   if (llvm::none_of(*endLoopRangeValues, ShapedType::isDynamic)) {
744     for (int64_t &range : *endLoopRangeValues)
745       range -= 1;
746     for (OpOperand *opOperand : linalgOp.getInputAndOutputOperands()) {
747       AffineMap indexingMap = linalgOp.getTiedIndexingMap(opOperand);
748       SmallVector<int64_t, 4> startIndices =
749           indexingMap.compose(startLoopRangeValues);
750       SmallVector<int64_t, 4> endIndices =
751           indexingMap.compose(*endLoopRangeValues);
752       ArrayRef<int64_t> shape = linalgOp.getShape(opOperand);
753       for (auto dim : llvm::seq<int64_t>(0, shape.size())) {
754         // Ignore dynamic dimension or the case that the dimension size is 0
755         if (ShapedType::isDynamic(shape[dim]) || shape[dim] == 0)
756           continue;
757 
758         // The first index or last index should be the maximum or the minimum in
759         // the inferred index ranges since the range is increasing or
760         // decreasing. The size of dimensions of input/output operands and the
761         // maximum value + 1 in the inferred range should be the same. But, for
762         // now we check if the inferred ranges are in boundary of input/output
763         // operands' size or not in case that Affine Expressions are complicated
764         // such as d0 * 3
765         // + d1 since it is not easy to handle the issues.
766         // Found the case that this solution can't check, for example, (d0, d1)
767         // -> (d1 - d0)
768         int64_t inferredDimSize =
769             std::max(startIndices[dim], endIndices[dim]) + 1;
770         if (std::min(startIndices[dim], endIndices[dim]) < 0) {
771           std::string mapStr;
772           {
773             llvm::raw_string_ostream os(mapStr);
774             os << indexingMap;
775           }
776           return op->emitOpError(
777                      "unexpected result less than 0 at expression #")
778                  << dim << " in " << mapStr;
779         }
780         if (indexingMap.getResult(dim).dyn_cast<AffineDimExpr>()) {
781           if (inferredDimSize != shape[dim]) {
782             return op->emitOpError("inferred input/output operand #")
783                    << opOperand->getOperandNumber()
784                    << " has shape's dimension #" << dim << " to be "
785                    << inferredDimSize << ", but found " << shape[dim];
786           }
787         } else {
788           if (inferredDimSize > shape[dim]) {
789             return op->emitOpError("inferred input/output operand #")
790                    << opOperand->getOperandNumber()
791                    << " has shape's dimension #" << dim
792                    << " to be greater than or equal to " << inferredDimSize
793                    << ", but found " << shape[dim];
794           }
795         }
796       }
797     }
798   }
799 
800   return success();
801 }
802