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