1 //===- LinalgOps.cpp - Implementation of the linalg operations ------------===//
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 // This file implements the Linalg operations.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "mlir/Dialect/Linalg/IR/LinalgOps.h"
14 
15 #include "mlir/Dialect/Affine/IR/AffineOps.h"
16 #include "mlir/Dialect/Linalg/IR/LinalgTypes.h"
17 #include "mlir/Dialect/MemRef/IR/MemRef.h"
18 #include "mlir/Dialect/SCF/SCF.h"
19 #include "mlir/Dialect/StandardOps/IR/Ops.h"
20 #include "mlir/Dialect/Tensor/IR/Tensor.h"
21 #include "mlir/Dialect/Utils/ReshapeOpsUtils.h"
22 #include "mlir/Dialect/Utils/StaticValueUtils.h"
23 #include "mlir/IR/AffineExprVisitor.h"
24 #include "mlir/IR/Matchers.h"
25 #include "mlir/IR/OpImplementation.h"
26 #include "mlir/IR/PatternMatch.h"
27 #include "mlir/Interfaces/InferTypeOpInterface.h"
28 #include "mlir/Parser.h"
29 
30 #include "llvm/ADT/DenseMap.h"
31 #include "llvm/ADT/SetVector.h"
32 #include "llvm/ADT/SmallSet.h"
33 #include "llvm/ADT/StringSet.h"
34 #include "llvm/ADT/TypeSwitch.h"
35 #include "llvm/Support/FormatVariadic.h"
36 #include "llvm/Support/MathExtras.h"
37 #include "llvm/Support/raw_ostream.h"
38 
39 using namespace mlir;
40 using namespace mlir::linalg;
41 
42 #include "mlir/Dialect/Linalg/IR/LinalgOpsDialect.cpp.inc"
43 
44 /// Forward declarations.
45 
46 /// Generic entry point to create the block for the region of a LinalgOp.
47 /// This is used by both named structured ops created by ods-gen and by manually
48 /// defined C++ ops.
49 /// This is used by both builders and parsers.
50 /// This function creates the block in the region with arguments corresponding
51 /// to the elemental types of `inputTypes` and `outputTypes`. The latter are
52 /// asserted to be of ShapedType.
53 template <typename NamedStructuredOpType>
54 static void fillStructuredOpRegion(
55     OpBuilder &opBuilder, Region &region, TypeRange inputTypes,
56     TypeRange outputTypes,
57     std::function<void(unsigned, unsigned)> errorHandler = nullptr);
58 
59 /// Generic entry point to create both the region and the block of a LinalgOp.
60 template <typename NamedStructuredOpType>
61 static void
62 createAndFillStructuredOpRegion(OpBuilder &opBuilder, OperationState &result,
63                                 TypeRange inputTypes, TypeRange outputTypes);
64 
65 /// Common parsing and printing used for both named structured ops created by
66 /// ods-gen and by manually defined C++ ops. Does not handle regions.
67 static ParseResult
68 parseCommonStructuredOpParts(OpAsmParser &parser, OperationState &result,
69                              SmallVectorImpl<Type> &inputTypes,
70                              SmallVectorImpl<Type> &outputTypes);
71 template <typename NamedStructuredOpType>
72 static void printCommonStructuredOpParts(OpAsmPrinter &p,
73                                          NamedStructuredOpType op);
74 
75 /// Specific parsing and printing for named structured ops created by ods-gen.
76 template <typename NamedStructuredOpType>
77 static ParseResult
78 parseNamedStructuredOpRegion(OpAsmParser &parser, Region &region,
79                              TypeRange inputTypes, TypeRange outputTypes);
80 
81 static ParseResult
82 parseNamedStructuredOpResults(OpAsmParser &parser,
83                               SmallVectorImpl<Type> &resultTypes);
84 
85 template <typename NamedStructuredOpType>
86 static ParseResult parseNamedStructuredOp(OpAsmParser &parser,
87                                           OperationState &result);
88 
89 static void printNamedStructuredOpResults(OpAsmPrinter &p,
90                                           TypeRange resultTypes);
91 
92 template <typename NamedStructuredOpType>
93 static void printNamedStructuredOp(OpAsmPrinter &p, NamedStructuredOpType op);
94 
95 /// Helper function to convert a vector of `OpFoldResult`s into a vector of
96 /// `Value`s.
97 static SmallVector<Value> getAsValues(OpBuilder &b, Location loc,
98                                       ArrayRef<OpFoldResult> valueOrAttrVec) {
99   return llvm::to_vector<4>(
100       llvm::map_range(valueOrAttrVec, [&](OpFoldResult value) -> Value {
101         if (auto attr = value.dyn_cast<Attribute>())
102           return b.create<ConstantIndexOp>(loc,
103                                            attr.cast<IntegerAttr>().getInt());
104         return value.get<Value>();
105       }));
106 }
107 
108 /// This is a common class used for patterns of the form
109 /// ```
110 ///    someop(memrefcast(%src)) -> someop(%src)
111 /// ```
112 /// It folds the source of the memref.cast into the root operation directly.
113 static LogicalResult foldMemRefCast(Operation *op) {
114   bool folded = false;
115   for (OpOperand &operand : op->getOpOperands()) {
116     auto castOp = operand.get().getDefiningOp<memref::CastOp>();
117     if (castOp && memref::CastOp::canFoldIntoConsumerOp(castOp)) {
118       operand.set(castOp.getOperand());
119       folded = true;
120     }
121   }
122   return success(folded);
123 }
124 
125 /// This is a specialization of `foldMemRefCast` used for patterns of the form
126 /// ```
127 ///    tiled_loop(memrefcast(%src)) -> tiled_loop(%src)
128 /// ```
129 /// It folds the source of the memref.cast into the root operation directly.
130 static LogicalResult foldMemRefCastInTiledLoopOp(TiledLoopOp op) {
131   bool folded = false;
132   Location loc = op->getLoc();
133 
134   Block *body = op.getBody();
135   OpBuilder b = OpBuilder::atBlockBegin(body);
136 
137   // Update `input` and `output` operands and block arguments if necessary.
138   // Operands list: [lbs, ubs, steps, inputs, outputs].
139   // Block args list: [ivs, inputs, outputs].
140   for (size_t operandIndex = op.getNumControlOperands(),
141               bbArgIndex = op.getNumLoops(), e = op.getNumOperands();
142        operandIndex < e; ++operandIndex, ++bbArgIndex) {
143     OpOperand &operand = op->getOpOperand(operandIndex);
144 
145     auto castOp = operand.get().getDefiningOp<memref::CastOp>();
146     if (castOp && memref::CastOp::canFoldIntoConsumerOp(castOp)) {
147       operand.set(castOp.getOperand());
148       BlockArgument newBbArg =
149           body->insertArgument(bbArgIndex, castOp.getOperand().getType());
150       BlockArgument oldBbArg = body->getArgument(newBbArg.getArgNumber() + 1);
151 
152       // Insert memref.cast back to the original type.
153       oldBbArg.replaceAllUsesWith(
154           b.create<memref::CastOp>(loc, oldBbArg.getType(), newBbArg));
155       body->eraseArgument(oldBbArg.getArgNumber());
156 
157       folded = true;
158     }
159   }
160   return success(folded);
161 }
162 
163 //===----------------------------------------------------------------------===//
164 // Region builder helper.
165 // TODO: Move this to a utility library.
166 // The public methods on this class are referenced directly from generated code
167 // and bind by name to math functions in the DSL as:
168 //   `applyfn__{fnName}`
169 // Examples:
170 //   `applyfn__add`
171 //   `applyfn__mul`
172 // The naming convention is intentional in order to match snake-cased DSL names.
173 // See mlir-linalg-ods-yaml-gen.cpp for the code that mates to this class.
174 //
175 // Implementations of the math functions must be polymorphic over numeric types,
176 // internally performing necessary casts. If the function application makes no
177 // sense, then the only recourse is to assert and return nullptr. This can be
178 // extended later if it becomes possible to fail construction of the region. The
179 // invariant should be enforced at a higher level.
180 //
181 // TODO: These helpers are currently type polymorphic over the class of integer
182 // and floating point types, but they will not internally cast within bit
183 // widths of a class (mixed precision such as i8->i32) or across classes
184 // (i.e. mixed float and integer). Many such combinations are ambiguous or need
185 // to be handled with care and work is being considered to extend the op
186 // language to make such cases explicit. In the mean-time, violating this will
187 // fail verification, which is deemed acceptable.
188 //===----------------------------------------------------------------------===//
189 
190 namespace {
191 
192 class RegionBuilderHelper {
193 public:
194   RegionBuilderHelper(MLIRContext *context, Block &block)
195       : context(context), block(block) {}
196 
197   // Generates operations to cast the given operand to a specified type.
198   // If the cast cannot be performed, a warning will be issued and the
199   // operand returned as-is (which will presumably yield a verification
200   // issue downstream).
201   Value cast(Type toType, Value operand) {
202     OpBuilder builder = getBuilder();
203     auto loc = operand.getLoc();
204 
205     if (operand.getType() == toType)
206       return operand;
207     if (auto toIntType = toType.dyn_cast<IntegerType>()) {
208       // If operand is floating point, cast directly to the int type.
209       if (operand.getType().isa<FloatType>())
210         return builder.create<FPToSIOp>(loc, toType, operand);
211       // Cast index operands directly to the int type.
212       if (operand.getType().isIndex())
213         return builder.create<IndexCastOp>(loc, toType, operand);
214       if (auto fromIntType = operand.getType().dyn_cast<IntegerType>()) {
215         // Either sign extend or truncate.
216         if (toIntType.getWidth() > fromIntType.getWidth())
217           return builder.create<SignExtendIOp>(loc, toType, operand);
218         if (toIntType.getWidth() < fromIntType.getWidth())
219           return builder.create<TruncateIOp>(loc, toType, operand);
220       }
221     } else if (auto toFloatType = toType.dyn_cast<FloatType>()) {
222       // If operand is integer, cast directly to the float type.
223       // Note that it is unclear how to cast from BF16<->FP16.
224       if (operand.getType().isa<IntegerType>())
225         return builder.create<SIToFPOp>(loc, toFloatType, operand);
226       if (auto fromFloatType = operand.getType().dyn_cast<FloatType>()) {
227         if (toFloatType.getWidth() > fromFloatType.getWidth())
228           return builder.create<FPExtOp>(loc, toFloatType, operand);
229         if (toFloatType.getWidth() < fromFloatType.getWidth())
230           return builder.create<FPTruncOp>(loc, toFloatType, operand);
231       }
232     }
233 
234     emitWarning(operand.getLoc()) << "could not cast operand of type "
235                                   << operand.getType() << " to " << toType;
236     return operand;
237   }
238 
239   Value applyfn__add(Value lhs, Value rhs) {
240     OpBuilder builder = getBuilder();
241     if (isFloatingPoint(lhs))
242       return builder.create<AddFOp>(lhs.getLoc(), lhs, rhs);
243     if (isInteger(lhs))
244       return builder.create<AddIOp>(lhs.getLoc(), lhs, rhs);
245     llvm_unreachable("unsupported non numeric type");
246   }
247 
248   Value applyfn__exp(Value x) {
249     OpBuilder builder = getBuilder();
250     if (isFloatingPoint(x))
251       return builder.create<math::ExpOp>(x.getLoc(), x);
252     llvm_unreachable("unsupported non numeric type");
253   }
254 
255   Value applyfn__log(Value x) {
256     OpBuilder builder = getBuilder();
257     if (isFloatingPoint(x))
258       return builder.create<math::LogOp>(x.getLoc(), x);
259     llvm_unreachable("unsupported non numeric type");
260   }
261 
262   Value applyfn__sub(Value lhs, Value rhs) {
263     OpBuilder builder = getBuilder();
264     if (isFloatingPoint(lhs))
265       return builder.create<SubFOp>(lhs.getLoc(), lhs, rhs);
266     if (isInteger(lhs))
267       return builder.create<SubIOp>(lhs.getLoc(), lhs, rhs);
268     llvm_unreachable("unsupported non numeric type");
269   }
270 
271   Value applyfn__mul(Value lhs, Value rhs) {
272     OpBuilder builder = getBuilder();
273     if (isFloatingPoint(lhs))
274       return builder.create<MulFOp>(lhs.getLoc(), lhs, rhs);
275     if (isInteger(lhs))
276       return builder.create<MulIOp>(lhs.getLoc(), lhs, rhs);
277     llvm_unreachable("unsupported non numeric type");
278   }
279 
280   Value applyfn__max(Value lhs, Value rhs) {
281     if (isFloatingPoint(lhs))
282       return emitCmpFAndSelect(lhs, rhs, CmpFPredicate::OGT);
283     if (isInteger(lhs))
284       return emitCmpIAndSelect(lhs, rhs, CmpIPredicate::sgt);
285     llvm_unreachable("unsupported non numeric type");
286   }
287 
288   Value applyfn__min(Value lhs, Value rhs) {
289     if (isFloatingPoint(lhs))
290       return emitCmpFAndSelect(lhs, rhs, CmpFPredicate::OLT);
291     if (isInteger(lhs))
292       return emitCmpIAndSelect(lhs, rhs, CmpIPredicate::slt);
293     llvm_unreachable("unsupported non numeric type");
294   }
295 
296   void yieldOutputs(ValueRange values) {
297     assert(!values.empty() && "linalg ops must yield outputs");
298     if (values.empty())
299       return;
300     Value first = values.front();
301     OpBuilder builder = getBuilder();
302     builder.create<YieldOp>(first.getLoc(), values);
303   }
304 
305   Value constant(std::string value) {
306     OpBuilder builder = getBuilder();
307     Location loc = builder.getUnknownLoc();
308     Attribute valueAttr = parseAttribute(value, builder.getContext());
309     return builder.create<ConstantOp>(loc, valueAttr.getType(), valueAttr);
310   }
311 
312   Value index(int64_t dim) {
313     OpBuilder builder = getBuilder();
314     return builder.create<IndexOp>(builder.getUnknownLoc(), dim);
315   }
316 
317   Type getIntegerType(unsigned width) {
318     return IntegerType::get(context, width);
319   }
320 
321   Type getFloat32Type() { return Float32Type::get(context); }
322 
323   Type getFloat64Type() { return Float64Type::get(context); }
324 
325 private:
326   MLIRContext *context;
327   Block &block;
328 
329   Value emitCmpFAndSelect(Value lhs, Value rhs, CmpFPredicate predicate) {
330     OpBuilder builder = getBuilder();
331     Value condition = builder.create<CmpFOp>(lhs.getLoc(), predicate, lhs, rhs);
332     return builder.create<SelectOp>(lhs.getLoc(), condition, lhs, rhs);
333   }
334   Value emitCmpIAndSelect(Value lhs, Value rhs, CmpIPredicate predicate) {
335     OpBuilder builder = getBuilder();
336     Value condition = builder.create<CmpIOp>(lhs.getLoc(), predicate, lhs, rhs);
337     return builder.create<SelectOp>(lhs.getLoc(), condition, lhs, rhs);
338   }
339 
340   bool isFloatingPoint(Value value) { return value.getType().isa<FloatType>(); }
341   bool isInteger(Value value) { return value.getType().isa<IntegerType>(); }
342 
343   OpBuilder getBuilder() {
344     OpBuilder builder(context);
345     builder.setInsertionPointToEnd(&block);
346     return builder;
347   }
348 };
349 
350 } // namespace
351 
352 //===----------------------------------------------------------------------===//
353 // CopyOp
354 //===----------------------------------------------------------------------===//
355 void CopyOp::regionBuilder(ImplicitLocOpBuilder &b, Block &block) {
356   assert(block.getNumArguments() == 2 && "CopyOp regionBuilder expects 2 args");
357   b.create<linalg::YieldOp>(block.getArgument(0));
358 }
359 
360 void CopyOp::build(OpBuilder &builder, OperationState &result, Value input,
361                    Value output, AffineMap inputPermutation,
362                    AffineMap outputPermutation,
363                    ArrayRef<NamedAttribute> namedAttrs) {
364   result.addOperands({input, output});
365   result.addAttributes(namedAttrs);
366   if (inputPermutation)
367     result.addAttribute("inputPermutation",
368                         AffineMapAttr::get(inputPermutation));
369   if (outputPermutation)
370     result.addAttribute("outputPermutation",
371                         AffineMapAttr::get(outputPermutation));
372   result.addRegion();
373   fillStructuredOpRegion<CopyOp>(builder, *result.regions.front(),
374                                  TypeRange{input.getType()},
375                                  TypeRange{output.getType()});
376 }
377 
378 ParseResult parseCopyOpRegion(OpAsmParser &parser, Region &r, Type inputType,
379                               Type outputType) {
380   OpBuilder opBuilder(parser.getBuilder().getContext());
381   fillStructuredOpRegion<CopyOp>(opBuilder, r, TypeRange{inputType},
382                                  TypeRange{outputType});
383   return success();
384 }
385 
386 /// CopyOp region is elided when printing.
387 void printCopyOpRegion(OpAsmPrinter &, Operation *, Region &, Type, Type) {}
388 
389 static LogicalResult verify(CopyOp op) {
390   OpOperand *output = op.getOutputOperand(0);
391   OpOperand *input = op.getInputOperand(0);
392   if (getElementTypeOrSelf(input->get()) != getElementTypeOrSelf(output->get()))
393     return op.emitOpError("expects views of the same type");
394   if (op.getRank(input) != op.getRank(output))
395     return op.emitOpError("expects views of the same rank");
396   auto rank = op.getNumParallelLoops();
397   auto inputPermutationMap = op.inputPermutation();
398   if (inputPermutationMap) {
399     if (inputPermutationMap->getNumInputs() != rank)
400       return op.emitOpError("expects optional input_permutation map of rank ")
401              << rank;
402     if (!inputPermutationMap->isPermutation())
403       return op.emitOpError(
404           "expects optional input_permutation map to be a permutation");
405   }
406   auto outputPermutationMap = op.outputPermutation();
407   if (outputPermutationMap) {
408     if (outputPermutationMap->getNumInputs() != rank)
409       return op.emitOpError("expects optional output_permutation map of rank ")
410              << rank;
411     if (!outputPermutationMap->isPermutation())
412       return op.emitOpError(
413           "expects optional output_permutation map to be a permutation");
414   }
415   if (rank == 0 && inputPermutationMap)
416     return op.emitOpError("expected no input permutation when rank == 0");
417   if (rank == 0 && outputPermutationMap)
418     return op.emitOpError("expected no output permutation when rank == 0");
419   return success();
420 }
421 
422 void CopyOp::getEffects(
423     SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
424         &effects) {
425   effects.emplace_back(MemoryEffects::Read::get(), input(),
426                        SideEffects::DefaultResource::get());
427   effects.emplace_back(MemoryEffects::Write::get(), output(),
428                        SideEffects::DefaultResource::get());
429 }
430 
431 namespace {
432 /// Remove copy operations that copy data inplace. Requirements are:
433 /// 1) The input and output values are identical.
434 /// 2) The input and output permutation maps are identical.
435 struct EraseIdentityCopyOp : public OpRewritePattern<CopyOp> {
436   using OpRewritePattern<CopyOp>::OpRewritePattern;
437 
438   LogicalResult matchAndRewrite(CopyOp copyOp,
439                                 PatternRewriter &rewriter) const override {
440     assert(copyOp.hasBufferSemantics());
441     if (copyOp.input() == copyOp.output() &&
442         copyOp.inputPermutation() == copyOp.outputPermutation()) {
443       rewriter.eraseOp(copyOp);
444       return success();
445     }
446     return failure();
447   }
448 };
449 } // namespace
450 
451 void CopyOp::getCanonicalizationPatterns(RewritePatternSet &results,
452                                          MLIRContext *context) {
453   results.add<EraseIdentityCopyOp>(context);
454 }
455 
456 //===----------------------------------------------------------------------===//
457 // FillOp
458 //===----------------------------------------------------------------------===//
459 void FillOp::regionBuilder(ImplicitLocOpBuilder &b, Block &block) {
460   assert(block.getNumArguments() == 2 && "FillOp regionBuilder expects 2 args");
461   b.create<linalg::YieldOp>(block.getArgument(0));
462 }
463 
464 void FillOp::build(OpBuilder &builder, OperationState &result, Value value,
465                    Value output) {
466   build(builder, result, output.getType().dyn_cast<RankedTensorType>(), value,
467         output);
468   fillStructuredOpRegion<FillOp>(builder, *result.regions.front(),
469                                  TypeRange{value.getType()},
470                                  TypeRange{output.getType()}, {});
471 }
472 
473 ParseResult parseFillOpRegion(OpAsmParser &parser, Region &r, Type valueType,
474                               Type outputType) {
475   OpBuilder opBuilder(parser.getBuilder().getContext());
476   fillStructuredOpRegion<FillOp>(opBuilder, r, TypeRange{valueType},
477                                  TypeRange{outputType});
478   return success();
479 }
480 
481 /// FillOp region is elided when printing.
482 void printFillOpRegion(OpAsmPrinter &, Operation *, Region &, Type, Type) {}
483 
484 static LogicalResult verify(FillOp op) {
485   OpOperand *output = op.getOutputOperand(0);
486   Type fillType = op.value().getType();
487   if (getElementTypeOrSelf(output->get()) != fillType)
488     return op.emitOpError("expects fill type to match view elemental type");
489   return success();
490 }
491 
492 void FillOp::getEffects(
493     SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
494         &effects) {
495   if (output().getType().isa<MemRefType>())
496     effects.emplace_back(MemoryEffects::Write::get(), output(),
497                          SideEffects::DefaultResource::get());
498 }
499 
500 //===----------------------------------------------------------------------===//
501 // GenericOps
502 //===----------------------------------------------------------------------===//
503 void GenericOp::build(
504     OpBuilder &builder, OperationState &result, TypeRange resultTensorTypes,
505     ValueRange inputs, ValueRange outputs, ArrayRef<AffineMap> indexingMaps,
506     ArrayRef<StringRef> iteratorTypes, StringRef doc, StringRef libraryCall,
507     function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild,
508     ArrayRef<NamedAttribute> attributes) {
509   build(builder, result, resultTensorTypes, inputs, outputs,
510         builder.getAffineMapArrayAttr(indexingMaps),
511         builder.getStrArrayAttr(iteratorTypes),
512         doc.empty() ? StringAttr() : builder.getStringAttr(doc),
513         libraryCall.empty() ? StringAttr()
514                             : builder.getStringAttr(libraryCall));
515   result.addAttributes(attributes);
516   if (!bodyBuild)
517     return;
518 
519   SmallVector<Type, 4> blockArgTypes;
520   for (ValueRange container : {inputs, outputs})
521     for (Value v : container)
522       blockArgTypes.push_back(getElementTypeOrSelf(v));
523 
524   OpBuilder::InsertionGuard guard(builder);
525   auto &region = *result.regions.front();
526   Block *bodyBlock = builder.createBlock(&region, region.end(), blockArgTypes);
527   bodyBuild(builder, result.location, bodyBlock->getArguments());
528 }
529 
530 void GenericOp::build(
531     OpBuilder &builder, OperationState &result, ValueRange inputs,
532     ValueRange outputs, ArrayRef<AffineMap> indexingMaps,
533     ArrayRef<StringRef> iteratorTypes, StringRef doc, StringRef libraryCall,
534     function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild,
535     ArrayRef<NamedAttribute> attributes) {
536   build(builder, result, TypeRange{}, inputs, outputs, indexingMaps,
537         iteratorTypes, doc, libraryCall, bodyBuild, attributes);
538 }
539 
540 void GenericOp::build(
541     OpBuilder &builder, OperationState &result, ValueRange inputs,
542     ValueRange outputs, ArrayRef<AffineMap> indexingMaps,
543     ArrayRef<StringRef> iteratorTypes,
544     function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild,
545     ArrayRef<NamedAttribute> attributes) {
546   build(builder, result, inputs, outputs, indexingMaps, iteratorTypes,
547         /*doc=*/"",
548         /*libraryCall=*/"", bodyBuild, attributes);
549 }
550 
551 void GenericOp::build(
552     OpBuilder &builder, OperationState &result, TypeRange resultTensorTypes,
553     ValueRange inputs, ValueRange outputs, ArrayRef<AffineMap> indexingMaps,
554     ArrayRef<StringRef> iteratorTypes,
555     function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild,
556     ArrayRef<NamedAttribute> attributes) {
557   build(builder, result, resultTensorTypes, inputs, outputs, indexingMaps,
558         iteratorTypes,
559         /*doc=*/"",
560         /*libraryCall=*/"", bodyBuild, attributes);
561 }
562 
563 static void print(OpAsmPrinter &p, GenericOp op) {
564   p << " ";
565 
566   // Print extra attributes.
567   auto genericAttrNames = op.linalgTraitAttrNames();
568 
569   llvm::StringSet<> genericAttrNamesSet;
570   genericAttrNamesSet.insert(genericAttrNames.begin(), genericAttrNames.end());
571   SmallVector<NamedAttribute, 8> genericAttrs;
572   for (auto attr : op->getAttrs())
573     if (genericAttrNamesSet.count(attr.first.strref()) > 0)
574       genericAttrs.push_back(attr);
575   if (!genericAttrs.empty()) {
576     auto genericDictAttr = DictionaryAttr::get(op.getContext(), genericAttrs);
577     p << genericDictAttr;
578   }
579 
580   // Printing is shared with named ops, except for the region and attributes
581   printCommonStructuredOpParts(p, op);
582 
583   genericAttrNames.push_back("operand_segment_sizes");
584   genericAttrNamesSet.insert(genericAttrNames.back());
585 
586   bool hasExtraAttrs = false;
587   for (NamedAttribute n : op->getAttrs()) {
588     if ((hasExtraAttrs = !genericAttrNamesSet.contains(n.first.strref())))
589       break;
590   }
591   if (hasExtraAttrs) {
592     p << " attrs = ";
593     p.printOptionalAttrDict(op->getAttrs(), /*elidedAttrs=*/genericAttrNames);
594   }
595 
596   // Print region.
597   if (!op.region().empty())
598     p.printRegion(op.region());
599 
600   // Print results.
601   printNamedStructuredOpResults(p, op.result_tensors().getTypes());
602 }
603 
604 static ParseResult parseGenericOp(OpAsmParser &parser, OperationState &result) {
605   DictionaryAttr dictAttr;
606   // Parse the core linalg traits that must check into a dictAttr.
607   // The name is unimportant as we will overwrite result.attributes.
608   // The core linalg traits must contain the information necessary to pass the
609   // verifier.
610   if (parser.parseAttribute(dictAttr, "_", result.attributes))
611     return failure();
612   result.attributes.assign(dictAttr.getValue().begin(),
613                            dictAttr.getValue().end());
614 
615   // Parsing is shared with named ops, except for the region.
616   SmallVector<Type, 1> inputTypes, outputTypes;
617   if (parseCommonStructuredOpParts(parser, result, inputTypes, outputTypes))
618     return failure();
619 
620   // Optional attributes may be added.
621   if (succeeded(parser.parseOptionalKeyword("attrs")))
622     if (failed(parser.parseEqual()) ||
623         failed(parser.parseOptionalAttrDict(result.attributes)))
624       return failure();
625 
626   SmallVector<OpAsmParser::OperandType, 8> regionOperands;
627   std::unique_ptr<Region> region = std::make_unique<Region>();
628   SmallVector<Type, 8> operandTypes, regionTypes;
629   if (parser.parseRegion(*region, regionOperands, regionTypes))
630     return failure();
631   result.addRegion(std::move(region));
632 
633   // Generic ops may specify that a subset of its outputs are tensors. Such
634   // outputs are specified in the result type.
635   // TODO: may need to move output parsing before region parsing.
636   // Need to wait for declarative assembly resolution to decide.
637   SmallVector<Type, 1> outputTensorsTypes;
638   if (parseNamedStructuredOpResults(parser, outputTensorsTypes))
639     return failure();
640   result.addTypes(outputTensorsTypes);
641 
642   return success();
643 }
644 
645 static void getGenericEffectsImpl(
646     SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
647         &effects,
648     ValueRange results, ValueRange inputBuffers, ValueRange outputs) {
649   for (Value value : results) {
650     effects.emplace_back(MemoryEffects::Allocate::get(), value,
651                          SideEffects::DefaultResource::get());
652   }
653   for (Value value : inputBuffers) {
654     effects.emplace_back(MemoryEffects::Read::get(), value,
655                          SideEffects::DefaultResource::get());
656   }
657   for (Value value : outputs) {
658     effects.emplace_back(MemoryEffects::Read::get(), value,
659                          SideEffects::DefaultResource::get());
660     effects.emplace_back(MemoryEffects::Write::get(), value,
661                          SideEffects::DefaultResource::get());
662   }
663 }
664 
665 void GenericOp::getEffects(
666     SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
667         &effects) {
668   SmallVector<Value> inputBuffers = getInputBufferOperands();
669   SmallVector<Value> outputBuffers = getOutputBufferOperands();
670   getGenericEffectsImpl(effects, getOperation()->getResults(), inputBuffers,
671                         outputBuffers);
672 }
673 
674 template <typename GenericOpType>
675 static LogicalResult verifyGenericOp(GenericOpType op) {
676   return success();
677 }
678 
679 static LogicalResult verify(GenericOp op) { return verifyGenericOp(op); }
680 
681 namespace {
682 // Deduplicate redundant args of a linalg generic op.
683 // An arg is redundant if it has the same Value and indexing map as another.
684 struct DeduplicateGenericOpInputs : public OpRewritePattern<GenericOp> {
685   using OpRewritePattern<GenericOp>::OpRewritePattern;
686 
687   LogicalResult matchAndRewrite(GenericOp genericOp,
688                                 PatternRewriter &rewriter) const override {
689     // Associate each input to an equivalent "canonical" input that has the same
690     // Value and indexing map.
691     //
692     // In the non-duplicate case, input `i` will have canonical input `i`. But
693     // in the case of duplicated inputs, the canonical input could be some other
694     // input `< i`. That is, a later input will have some earlier input as its
695     // canonical input.
696     llvm::SmallDenseMap<std::pair<Value, AffineMap>, unsigned> canonicalInput;
697     // For later remapping tasks like deduplicating payload block arguments,
698     // having a simple "inputIndex -> canonicalInputIndex" integer mapping is
699     // convenient.
700     SmallVector<unsigned> canonicalInputIndices;
701     for (OpOperand *opOperand : genericOp.getInputOperands()) {
702       AffineMap indexingMap = genericOp.getTiedIndexingMap(opOperand);
703       // STL-like maps have a convenient behavior for our use case here. In the
704       // case of duplicate keys, the insertion is rejected, and the returned
705       // iterator gives access to the value already in the map.
706       auto pair = canonicalInput.insert(
707           {{opOperand->get(), indexingMap}, opOperand->getOperandNumber()});
708       canonicalInputIndices.push_back(pair.first->second);
709     }
710 
711     // If there are no duplicate args, then bail out.
712     if (canonicalInput.size() == genericOp.getNumInputs())
713       return failure();
714 
715     // The operands for the newly canonicalized op.
716     SmallVector<Value> newInputOperands;
717     for (OpOperand *opOperand : genericOp.getInputOperands())
718       if (canonicalInputIndices[opOperand->getOperandNumber()] ==
719           opOperand->getOperandNumber())
720         newInputOperands.push_back(opOperand->get());
721 
722     // Repair the indexing maps by filtering out the ones that have been
723     // eliminated.
724     SmallVector<AffineMap> newIndexingMaps;
725     for (OpOperand *opOperand : genericOp.getInputOperands())
726       if (canonicalInputIndices[opOperand->getOperandNumber()] ==
727           opOperand->getOperandNumber())
728         newIndexingMaps.push_back(genericOp.getTiedIndexingMap(opOperand));
729     for (OpOperand *opOperand : genericOp.getOutputOperands())
730       newIndexingMaps.push_back(genericOp.getTiedIndexingMap(opOperand));
731 
732     // Clone the old op with new operands.
733     SmallVector<Value> outputOperands = genericOp.getOutputOperands();
734     auto newOp = rewriter.create<GenericOp>(
735         genericOp.getLoc(), genericOp->getResultTypes(), newInputOperands,
736         outputOperands, rewriter.getAffineMapArrayAttr(newIndexingMaps),
737         genericOp.iterator_types(), genericOp.docAttr(),
738         genericOp.library_callAttr());
739 
740     // Copy over unknown attributes. They might be load bearing for some flow.
741     ArrayRef<StringRef> odsAttrs = genericOp.getAttributeNames();
742     for (NamedAttribute kv : genericOp->getAttrs()) {
743       if (!llvm::is_contained(odsAttrs, kv.first.c_str())) {
744         newOp->setAttr(kv.first, kv.second);
745       }
746     }
747 
748     rewriter.inlineRegionBefore(genericOp.region(), newOp.region(),
749                                 newOp.region().begin());
750 
751     // Repair the payload entry block by RAUW'ing redundant arguments and
752     // erasing them.
753     Block &payload = newOp.region().front();
754     SmallVector<OpOperand *> inputOperands = genericOp.getInputOperands();
755     for (OpOperand *opOperand : llvm::reverse(inputOperands)) {
756       // Iterate in reverse, so that we erase later args first, preventing the
757       // argument list from shifting unexpectedly and invalidating all our
758       // indices.
759       unsigned operandNumber = opOperand->getOperandNumber();
760       if (canonicalInputIndices[operandNumber] == operandNumber)
761         continue;
762       payload.getArgument(operandNumber)
763           .replaceAllUsesWith(
764               payload.getArgument(canonicalInputIndices[operandNumber]));
765       payload.eraseArgument(operandNumber);
766     }
767 
768     rewriter.replaceOp(genericOp, newOp->getResults());
769     return success();
770   }
771 };
772 
773 /// Remove generic operations (on tensors) that are just copying
774 /// the values from inputs to the results. Requirements are
775 /// 1) All iterator types are parallel
776 /// 2) The body contains just a yield operation with the yielded values being
777 ///    the arguments corresponding to the operands.
778 struct EraseIdentityGenericOp : public OpRewritePattern<GenericOp> {
779   using OpRewritePattern<GenericOp>::OpRewritePattern;
780 
781   LogicalResult matchAndRewrite(GenericOp genericOp,
782                                 PatternRewriter &rewriter) const override {
783     if (!genericOp.hasTensorSemantics())
784       return failure();
785     // Check all indexing maps are identity.
786     if (llvm::any_of(genericOp.getIndexingMaps(),
787                      [](AffineMap map) { return !map.isIdentity(); }))
788       return failure();
789 
790     // Check that the body of the linalg operation is just a linalg.yield
791     // operation.
792     Block &body = genericOp.region().front();
793     if (!llvm::hasSingleElement(body))
794       return failure();
795     auto yieldOp = dyn_cast<linalg::YieldOp>(body.getTerminator());
796     if (!yieldOp)
797       return failure();
798 
799     // Get the argument number of the returned values. That is the operand
800     // number to use for replacing uses of this operation.
801     SmallVector<Value> returnedArgs;
802     for (Value yieldVal : yieldOp.values()) {
803       auto yieldArg = yieldVal.dyn_cast<BlockArgument>();
804       if (!yieldArg || yieldArg.getOwner() != &body)
805         return failure();
806       unsigned argumentNumber = yieldArg.getArgNumber();
807       returnedArgs.push_back(genericOp->getOperand(argumentNumber));
808     }
809     if (returnedArgs.size() != genericOp->getNumResults())
810       return failure();
811     rewriter.replaceOp(genericOp, returnedArgs);
812     return success();
813   }
814 };
815 } // namespace
816 
817 void GenericOp::getCanonicalizationPatterns(RewritePatternSet &results,
818                                             MLIRContext *context) {
819   results.add<DeduplicateGenericOpInputs, EraseIdentityGenericOp>(context);
820 }
821 
822 //===----------------------------------------------------------------------===//
823 // InitTensorOp
824 //===----------------------------------------------------------------------===//
825 void InitTensorOp::build(OpBuilder &b, OperationState &result,
826                          ArrayRef<OpFoldResult> sizes, Type elementType,
827                          ArrayRef<NamedAttribute> attrs) {
828   unsigned rank = sizes.size();
829   SmallVector<Value, 4> dynamicSizes;
830   SmallVector<int64_t, 4> staticSizes;
831   for (unsigned i = 0; i < rank; ++i) {
832     dispatchIndexOpFoldResult(sizes[i], dynamicSizes, staticSizes,
833                               ShapedType::kDynamicSize);
834   }
835   auto resultType = RankedTensorType ::get(staticSizes, elementType);
836   build(b, result, resultType, dynamicSizes, b.getI64ArrayAttr(staticSizes));
837   result.addAttributes(attrs);
838 }
839 
840 static LogicalResult verify(InitTensorOp op) {
841   RankedTensorType resultType = op.getType();
842   SmallVector<int64_t, 4> staticSizes = llvm::to_vector<4>(llvm::map_range(
843       op.static_sizes().cast<ArrayAttr>(),
844       [](Attribute a) -> int64_t { return a.cast<IntegerAttr>().getInt(); }));
845 
846   if (failed(verifyListOfOperandsOrIntegers(op, "sizes", resultType.getRank(),
847                                             op.static_sizes(), op.sizes(),
848                                             ShapedType::isDynamic)))
849     return failure();
850 
851   if (op.static_sizes().size() != static_cast<unsigned>(resultType.getRank()))
852     return op->emitError("expected ")
853            << resultType.getRank() << " sizes values";
854 
855   Type expectedType =
856       InitTensorOp::inferResultType(staticSizes, resultType.getElementType());
857   if (resultType != expectedType) {
858     return op.emitError("specified type ")
859            << resultType << " does not match the inferred type "
860            << expectedType;
861   }
862   return success();
863 }
864 
865 Type InitTensorOp::inferResultType(ArrayRef<int64_t> staticSizes,
866                                    Type elementType) {
867   return RankedTensorType::get(staticSizes, elementType);
868 }
869 
870 namespace {
871 /// Change the type of the result of a `linalg.init_tensor` by making the result
872 /// type statically sized along dimension that in the original operation where
873 /// defined as dynamic, but the size was defined using a `constant` op. For
874 /// example
875 ///
876 ///  %c5 = constant 5: index
877 ///  %0 = linalg.init_tensor [%arg0, %c5] : tensor<?x?xf32>
878 ///
879 ///  to
880 ///
881 ///  %0 = linalg.init_tensor [%arg0, 5] : tensor<?x5xf32>
882 struct ReplaceStaticShapeDims : OpRewritePattern<InitTensorOp> {
883   using OpRewritePattern<InitTensorOp>::OpRewritePattern;
884 
885   LogicalResult matchAndRewrite(InitTensorOp op,
886                                 PatternRewriter &rewriter) const override {
887     SmallVector<Value, 4> dynamicSizes;
888     SmallVector<int64_t, 4> staticSizes;
889     for (unsigned i = 0, e = op.getType().getRank(); i != e; ++i) {
890       // If the size is already static, nothing to do.
891       if (!op.isDynamicSize(i)) {
892         staticSizes.push_back(op.getStaticSize(i));
893         continue;
894       }
895 
896       // If the size is dynamic but defined using a `constant` op, get the
897       // constant value to find the static size to use.
898       unsigned operandNum = op.getIndexOfDynamicSize(i);
899       Value sizeOperand = op.getOperand(operandNum);
900       if (auto constantIndexOp = sizeOperand.getDefiningOp<ConstantIndexOp>()) {
901         staticSizes.push_back(constantIndexOp.getValue());
902         continue;
903       }
904 
905       // Fallback case. Keep the size dynamic.
906       dynamicSizes.push_back(sizeOperand);
907       staticSizes.push_back(ShapedType::kDynamicSize);
908     }
909     RankedTensorType newType =
910         RankedTensorType::get(staticSizes, op.getType().getElementType());
911     if (newType == op.getType())
912       return failure();
913     auto newOp =
914         rewriter.create<InitTensorOp>(op.getLoc(), newType, dynamicSizes,
915                                       rewriter.getI64ArrayAttr(staticSizes));
916     rewriter.replaceOpWithNewOp<tensor::CastOp>(op, op.getType(), newOp);
917     return success();
918   }
919 };
920 } // namespace
921 
922 namespace {
923 /// Since `init_tensor` operation creates a tensor needed only for its shape, a
924 /// slice of this is also needed only for its shape. The result can be
925 /// replaced by a new init_tensor operation of the same size as the extract
926 /// slice op.
927 struct FoldInitTensorWithExtractSliceOp
928     : public OpRewritePattern<tensor::ExtractSliceOp> {
929   using OpRewritePattern<tensor::ExtractSliceOp>::OpRewritePattern;
930 
931   LogicalResult matchAndRewrite(tensor::ExtractSliceOp sliceOp,
932                                 PatternRewriter &rewriter) const override {
933     if (!sliceOp.source().getDefiningOp<linalg::InitTensorOp>())
934       return failure();
935     // ExtractSliceOp may be rank-reducing; its dynamic sizes must be preserved
936     // as well as its result type.
937     rewriter.replaceOpWithNewOp<linalg::InitTensorOp>(
938         sliceOp, sliceOp.sizes(),
939         sliceOp.result().getType().cast<RankedTensorType>().getShape(),
940         sliceOp.getSourceType().getElementType());
941     return success();
942   }
943 };
944 
945 template <typename TensorReshapeOp>
946 struct FoldInitTensorWithTensorReshapeOp
947     : public OpRewritePattern<TensorReshapeOp> {
948   using OpRewritePattern<TensorReshapeOp>::OpRewritePattern;
949 
950   LogicalResult matchAndRewrite(TensorReshapeOp reshapeOp,
951                                 PatternRewriter &rewriter) const override {
952     if (!reshapeOp.src().template getDefiningOp<InitTensorOp>())
953       return failure();
954     Location loc = reshapeOp.getLoc();
955     ReifiedRankedShapedTypeDims resultShapes;
956     if (failed(reshapeOp.reifyResultShapes(rewriter, resultShapes)) ||
957         !llvm::hasSingleElement(resultShapes))
958       return failure();
959     Value initTensor = rewriter.create<InitTensorOp>(
960         loc, getAsOpFoldResult(resultShapes[0]),
961         reshapeOp.getResultType().getElementType());
962     if (initTensor.getType() != reshapeOp.getResultType()) {
963       rewriter.replaceOpWithNewOp<tensor::CastOp>(
964           reshapeOp, reshapeOp.getResultType(), initTensor);
965     } else {
966       rewriter.replaceOp(reshapeOp, initTensor);
967     }
968     return success();
969   }
970 };
971 
972 struct FoldInitTensorWithDimOp : public OpRewritePattern<tensor::DimOp> {
973   using OpRewritePattern<tensor::DimOp>::OpRewritePattern;
974 
975   LogicalResult matchAndRewrite(tensor::DimOp dimOp,
976                                 PatternRewriter &rewriter) const override {
977     Optional<int64_t> maybeConstantIndex = dimOp.getConstantIndex();
978     auto initTensorOp = dimOp.source().getDefiningOp<linalg::InitTensorOp>();
979     if (!initTensorOp || !maybeConstantIndex)
980       return failure();
981     if (!initTensorOp.isDynamicSize(*maybeConstantIndex))
982       return failure();
983     rewriter.replaceOp(dimOp, initTensorOp.getDynamicSize(*maybeConstantIndex));
984     return success();
985   }
986 };
987 } // namespace
988 
989 void InitTensorOp::getCanonicalizationPatterns(RewritePatternSet &results,
990                                                MLIRContext *context) {
991   results.add<FoldInitTensorWithDimOp, FoldInitTensorWithExtractSliceOp,
992               FoldInitTensorWithTensorReshapeOp<TensorExpandShapeOp>,
993               FoldInitTensorWithTensorReshapeOp<TensorCollapseShapeOp>,
994               ReplaceStaticShapeDims>(context);
995 }
996 
997 LogicalResult InitTensorOp::reifyResultShapes(
998     OpBuilder &builder, ReifiedRankedShapedTypeDims &reifiedReturnShapes) {
999   auto shapes = llvm::to_vector<4>(llvm::map_range(
1000       llvm::seq<int64_t>(0, getType().getRank()), [&](int64_t dim) -> Value {
1001         if (isDynamicSize(dim))
1002           return getDynamicSize(dim);
1003         return builder.create<ConstantIndexOp>(getLoc(), getStaticSize(dim));
1004       }));
1005   reifiedReturnShapes.emplace_back(std::move(shapes));
1006   return success();
1007 }
1008 
1009 //===----------------------------------------------------------------------===//
1010 // PadTensorOp
1011 //===----------------------------------------------------------------------===//
1012 
1013 // TODO: Replace custom<InferType> directive with AllTypesMatch as soon as it
1014 // supports optional types.
1015 void printInferType(OpAsmPrinter &printer, Operation *op, Value optOperand,
1016                     Type typeToInfer, Type typeToInferFrom) {}
1017 
1018 ParseResult parseInferType(OpAsmParser &parser,
1019                            Optional<OpAsmParser::OperandType> optOperand,
1020                            Type &typeToInfer, Type typeToInferFrom) {
1021   if (optOperand)
1022     typeToInfer = typeToInferFrom;
1023   return success();
1024 }
1025 
1026 static LogicalResult verify(PadTensorOp op) {
1027   auto sourceType = op.source().getType().cast<RankedTensorType>();
1028   auto resultType = op.result().getType().cast<RankedTensorType>();
1029   auto expectedType = PadTensorOp::inferResultType(
1030       sourceType, extractFromI64ArrayAttr(op.static_low()),
1031       extractFromI64ArrayAttr(op.static_high()));
1032   for (int i = 0, e = sourceType.getRank(); i < e; ++i) {
1033     if (resultType.getDimSize(i) == expectedType.getDimSize(i))
1034       continue;
1035     if (expectedType.isDynamicDim(i))
1036       continue;
1037     return op.emitError("specified type ")
1038            << resultType << " does not match the inferred type "
1039            << expectedType;
1040   }
1041 
1042   auto &region = op.region();
1043   unsigned rank = resultType.getRank();
1044   Block &block = region.front();
1045   if (block.getNumArguments() != rank)
1046     return op.emitError("expected the block to have ") << rank << " arguments";
1047 
1048   // Note: the number and type of yield values are checked in the YieldOp.
1049   for (auto en : llvm::enumerate(block.getArgumentTypes())) {
1050     if (!en.value().isIndex())
1051       return op.emitOpError("expected block argument ")
1052              << (en.index() + 1) << " to be an index";
1053   }
1054 
1055   return success();
1056 }
1057 
1058 RankedTensorType PadTensorOp::inferResultType(RankedTensorType sourceType,
1059                                               ArrayRef<int64_t> staticLow,
1060                                               ArrayRef<int64_t> staticHigh) {
1061   unsigned rank = sourceType.getRank();
1062   assert(staticLow.size() == rank && "unexpected staticLow size mismatch");
1063   assert(staticHigh.size() == rank && "unexpected staticHigh size mismatch");
1064 
1065   SmallVector<int64_t, 4> resultShape;
1066   for (auto i : llvm::seq<unsigned>(0, rank)) {
1067     if (sourceType.isDynamicDim(i) ||
1068         staticLow[i] == ShapedType::kDynamicSize ||
1069         staticHigh[i] == ShapedType::kDynamicSize) {
1070       resultShape.push_back(ShapedType::kDynamicSize);
1071     } else {
1072       int64_t size = sourceType.getDimSize(i) + staticLow[i] + staticHigh[i];
1073       resultShape.push_back(size);
1074     }
1075   }
1076 
1077   return RankedTensorType::get(resultShape, sourceType.getElementType());
1078 }
1079 
1080 void PadTensorOp::build(OpBuilder &b, OperationState &result, Value source,
1081                         ArrayRef<int64_t> staticLow,
1082                         ArrayRef<int64_t> staticHigh, ValueRange low,
1083                         ValueRange high, ArrayRef<NamedAttribute> attrs) {
1084   auto sourceType = source.getType().cast<RankedTensorType>();
1085   auto resultType = inferResultType(sourceType, staticLow, staticHigh);
1086   build(b, result, resultType, source, low, high, b.getI64ArrayAttr(staticLow),
1087         b.getI64ArrayAttr(staticHigh));
1088   result.addAttributes(attrs);
1089 }
1090 
1091 void PadTensorOp::build(OpBuilder &b, OperationState &result, Value source,
1092                         ValueRange low, ValueRange high,
1093                         ArrayRef<NamedAttribute> attrs) {
1094   auto sourceType = source.getType().cast<RankedTensorType>();
1095   unsigned rank = sourceType.getRank();
1096   SmallVector<int64_t, 4> staticVector(rank, ShapedType::kDynamicSize);
1097   build(b, result, source, staticVector, staticVector, low, high, attrs);
1098 }
1099 
1100 void PadTensorOp::build(OpBuilder &b, OperationState &result, Type resultType,
1101                         Value source, ArrayRef<OpFoldResult> low,
1102                         ArrayRef<OpFoldResult> high,
1103                         ArrayRef<NamedAttribute> attrs) {
1104   assert(resultType.isa<RankedTensorType>());
1105   auto sourceType = source.getType().cast<RankedTensorType>();
1106   unsigned rank = sourceType.getRank();
1107   SmallVector<Value, 4> dynamicLow, dynamicHigh;
1108   SmallVector<int64_t, 4> staticLow, staticHigh;
1109   for (unsigned i = 0; i < rank; ++i) {
1110     // staticLow and staticHigh have full information of the padding config.
1111     // This will grow staticLow and staticHigh with 1 value. If the config is
1112     // dynamic (ie not a constant), dynamicLow and dynamicHigh will grow with 1
1113     // value as well.
1114     dispatchIndexOpFoldResult(low[i], dynamicLow, staticLow,
1115                               ShapedType::kDynamicSize);
1116     dispatchIndexOpFoldResult(high[i], dynamicHigh, staticHigh,
1117                               ShapedType::kDynamicSize);
1118   }
1119   if (!resultType) {
1120     resultType =
1121         PadTensorOp::inferResultType(sourceType, staticLow, staticHigh);
1122   }
1123   build(b, result, resultType, source, dynamicLow, dynamicHigh,
1124         b.getI64ArrayAttr(staticLow), b.getI64ArrayAttr(staticHigh));
1125 }
1126 
1127 PadTensorOp PadTensorOp::createPadScalarOp(Type type, Value source, Value pad,
1128                                            ArrayRef<OpFoldResult> low,
1129                                            ArrayRef<OpFoldResult> high,
1130                                            Location loc, OpBuilder &builder) {
1131   auto padTensorOp =
1132       builder.create<linalg::PadTensorOp>(loc, type, source, low, high);
1133   int rank = padTensorOp.getResultType().getRank();
1134   SmallVector<Type, 4> blockArgTypes;
1135   blockArgTypes.assign(rank, builder.getIndexType());
1136   auto &region = padTensorOp.region();
1137   // `builder.createBlock` changes the insertion point within the block. Create
1138   // a guard to reset the insertion point of the builder after it is destroyed.
1139   OpBuilder::InsertionGuard guard(builder);
1140   builder.createBlock(&region, region.end(), blockArgTypes);
1141   builder.create<linalg::YieldOp>(loc, pad);
1142   return padTensorOp;
1143 }
1144 
1145 PadTensorOp PadTensorOp::createPadHighOp(Type type, Value source, Value pad,
1146                                          Location loc, OpBuilder &builder) {
1147   SmallVector<OpFoldResult, 4> low, high;
1148   auto rankedTensorType = type.cast<RankedTensorType>();
1149   assert(rankedTensorType.hasStaticShape());
1150   int rank = rankedTensorType.getRank();
1151   for (int i = 0; i < rank; ++i) {
1152     auto dimOp = builder.createOrFold<tensor::DimOp>(loc, source, i);
1153     auto resultDimSize = builder.createOrFold<ConstantIndexOp>(
1154         loc, rankedTensorType.getDimSize(i));
1155     auto highValue = builder.createOrFold<SubIOp>(loc, resultDimSize, dimOp);
1156     high.push_back(highValue);
1157     low.push_back(builder.createOrFold<ConstantIndexOp>(loc, 0));
1158   }
1159   return PadTensorOp::createPadScalarOp(type, source, pad, low, high, loc,
1160                                         builder);
1161 }
1162 
1163 LogicalResult PadTensorOp::reifyResultShapes(
1164     OpBuilder &b, ReifiedRankedShapedTypeDims &reifiedReturnShapes) {
1165   Location loc = getLoc();
1166   auto lowPad = getMixedLowPad();
1167   auto highPad = getMixedHighPad();
1168   SmallVector<Value> shapes;
1169   for (auto dim : llvm::seq<int64_t>(0, getSourceType().getRank())) {
1170     // Shape along each dimension is source dim + low pad + high pad.
1171     SmallVector<Value> mapOperands;
1172     mapOperands.push_back(b.createOrFold<tensor::DimOp>(loc, source(), dim));
1173     AffineExpr expr = b.getAffineDimExpr(0);
1174     unsigned numSymbols = 0;
1175     auto addOpFoldResult = [&](OpFoldResult valueOrAttr) {
1176       if (Value v = valueOrAttr.dyn_cast<Value>()) {
1177         expr = expr + b.getAffineSymbolExpr(numSymbols++);
1178         mapOperands.push_back(v);
1179         return;
1180       }
1181       int64_t staticValue =
1182           valueOrAttr.get<Attribute>().cast<IntegerAttr>().getInt();
1183       expr = expr + staticValue;
1184     };
1185     addOpFoldResult(lowPad[dim]);
1186     addOpFoldResult(highPad[dim]);
1187     shapes.push_back(applyMapToValues(
1188         b, loc, AffineMap::get(1, numSymbols, expr), mapOperands)[0]);
1189   }
1190   reifiedReturnShapes.emplace_back(std::move(shapes));
1191   return success();
1192 }
1193 
1194 //===----------------------------------------------------------------------===//
1195 // Methods related to PadTensor tiling.
1196 //===----------------------------------------------------------------------===//
1197 
1198 /// Given an OpFoldResult, return a Value. If the OpFoldResult is an Attribute,
1199 /// it must be of type Integer.
1200 static Value getAsValue(OpBuilder &builder, Location loc, OpFoldResult ofr) {
1201   if (auto val = ofr.dyn_cast<Value>())
1202     return val;
1203   auto intVal = getConstantIntValue(ofr);
1204   assert(intVal && "expected Value or IntegerAttr");
1205   return builder.create<ConstantIndexOp>(loc, *intVal);
1206 }
1207 
1208 SmallVector<Value> PadTensorOp::getDestinationOperands(OpBuilder &b) {
1209   ReifiedRankedShapedTypeDims reifiedShapes;
1210   (void)reifyResultShapes(b, reifiedShapes);
1211   SmallVector<OpFoldResult> mixedSizes = getAsOpFoldResult(reifiedShapes[0]);
1212   Value initTensor = b.create<InitTensorOp>(getLoc(), mixedSizes,
1213                                             getResultType().getElementType());
1214   return {initTensor};
1215 }
1216 
1217 SmallVector<StringRef> PadTensorOp::getLoopIteratorTypes() {
1218   SmallVector<StringRef> iteratorTypes(getResultType().getRank(),
1219                                        getParallelIteratorTypeName());
1220   return iteratorTypes;
1221 }
1222 
1223 SmallVector<Range> PadTensorOp::getLoopBounds(OpBuilder &b) {
1224   ReifiedRankedShapedTypeDims reifiedShapes;
1225   (void)reifyResultShapes(b, reifiedShapes);
1226   Value zero = b.create<ConstantIndexOp>(getLoc(), 0);
1227   Value one = b.create<ConstantIndexOp>(getLoc(), 1);
1228   // Initialize all the ranges to {zero, one, one}. All the `ub`s are
1229   // overwritten.
1230   SmallVector<Range> loopRanges(reifiedShapes[0].size(), {zero, one, one});
1231   for (auto ub : enumerate(reifiedShapes[0]))
1232     loopRanges[ub.index()].size = ub.value();
1233   return loopRanges;
1234 }
1235 
1236 Operation *PadTensorOp::getTiledImplementation(OpBuilder &b, ValueRange dest,
1237                                                ArrayRef<OpFoldResult> offsets,
1238                                                ArrayRef<OpFoldResult> sizes) {
1239   // Only constant padding value supported.
1240   Value padValue = getConstantPaddingValue();
1241   if (!padValue)
1242     return nullptr;
1243 
1244   // Helper variables and functions for various arithmetic operations. These are
1245   // used extensively for computing new offset/length and padding values.
1246   Location loc = getLoc();
1247   AffineExpr dim0, dim1;
1248   bindDims(b.getContext(), dim0, dim1);
1249   // Add two integers.
1250   auto addMap = AffineMap::get(2, 0, {dim0 + dim1});
1251   auto add = [&](Value v1, Value v2) {
1252     return b.createOrFold<AffineApplyOp>(loc, addMap, ValueRange{v1, v2});
1253   };
1254   // Subtract two integers.
1255   auto subMap = AffineMap::get(2, 0, {dim0 - dim1});
1256   auto sub = [&](Value v1, Value v2) {
1257     return b.createOrFold<AffineApplyOp>(loc, subMap, ValueRange{v1, v2});
1258   };
1259   // Take the minimum of two integers.
1260   auto idMap = AffineMap::getMultiDimIdentityMap(2, b.getContext());
1261   auto min = [&](Value v1, Value v2) {
1262     return b.createOrFold<AffineMinOp>(loc, idMap, ValueRange{v1, v2});
1263   };
1264   // Take the maximum of two integers.
1265   auto max = [&](Value v1, Value v2) {
1266     return b.createOrFold<AffineMaxOp>(loc, idMap, ValueRange{v1, v2});
1267   };
1268   // Zero index-typed integer.
1269   auto zero = b.create<ConstantIndexOp>(loc, 0);
1270 
1271   // Helper function for filling static/dynamic low/high padding indices vectors
1272   // of PadTensorOp.
1273   auto appendIndex = [&](Value val, SmallVector<Value> &dynIndices,
1274                          SmallVector<int64_t> &staticIndices) {
1275     if (auto constInt = getConstantIntValue(val)) {
1276       staticIndices.push_back(*constInt);
1277     } else {
1278       staticIndices.push_back(ShapedType::kDynamicSize);
1279       dynIndices.push_back(val);
1280     }
1281   };
1282 
1283   // Compute new offsets, lengths, low padding, high padding.
1284   SmallVector<OpFoldResult> newOffsets, newLengths, newStrides;
1285   SmallVector<Value> newLows, newHighs;
1286   SmallVector<int64_t> staticNewLows, staticNewHighs;
1287   // Set to true if the original data source is not read at all.
1288   bool hasZeroLen = false;
1289   // Same as hasZeroLen, but for dynamic dimension sizes. This condition
1290   // is true if the original data source turns out to be unused at runtime.
1291   Value dynHasZeroLenCond;
1292 
1293   int64_t rank = getSourceType().getRank();
1294   for (unsigned dim = 0; dim < rank; ++dim) {
1295     auto low = getAsValue(b, loc, getMixedLowPad()[dim]);
1296     bool hasLowPad = getConstantIntValue(low) != static_cast<int64_t>(0);
1297     auto high = getAsValue(b, loc, getMixedHighPad()[dim]);
1298     bool hasHighPad = getConstantIntValue(high) != static_cast<int64_t>(0);
1299     auto offset = getAsValue(b, loc, offsets[dim]);
1300     auto length = getAsValue(b, loc, sizes[dim]);
1301     auto srcSize = b.createOrFold<tensor::DimOp>(loc, source(), dim);
1302 
1303     // The new amount of low padding is `low - offset`. Except for the case
1304     // where none of the low padding is read. In that case, the new amount of
1305     // low padding is zero.
1306     //
1307     // Optimization: If low = 0, then newLow = 0.
1308     Value newLow = hasLowPad ? max(zero, sub(low, offset)) : zero;
1309     appendIndex(newLow, newLows, staticNewLows);
1310 
1311     // Start reading the data from position `offset - low`. Since the original
1312     // read may have started in the low padding zone, this value could be
1313     // negative. Therefore, start reading from:
1314     //
1315     // max(offset - low, 0)
1316     //
1317     // The original read could also have started in the high padding zone.
1318     // In that case, set the offset to the end of source tensor. The new
1319     // ExtractSliceOp length will be zero in that case. (Effectively reading no
1320     // data from the source.)
1321     //
1322     // Optimization: If low = 0, then the formula can be simplified.
1323     Value newOffset = hasLowPad ? min(max(sub(offset, low), zero), srcSize)
1324                                 : min(offset, srcSize);
1325     newOffsets.push_back(getAsOpFoldResult(newOffset));
1326 
1327     // The original ExtractSliceOp was reading until position `offset + length`.
1328     // Therefore, the corresponding position within the source tensor is:
1329     //
1330     // offset + length - low
1331     //
1332     // In case the original ExtractSliceOp stopped reading within the low
1333     // padding zone, this value can be negative. In that case, the end position
1334     // of the read should be zero. (Similar to newOffset.)
1335     //
1336     // The original read could also have stopped in the high padding zone.
1337     // In that case, set the end positition of the read should be the end of the
1338     // source tensor. (Similar to newOffset.)
1339     //
1340     // endLoc = min(max(offset - low + length, 0), srcSize)
1341     //
1342     // The new ExtractSliceOp length is `endLoc - newOffset`.
1343     //
1344     // Optimization: If low = 0, then the formula can be simplified.
1345     Value endLoc = hasLowPad
1346                        ? min(max(add(sub(offset, low), length), zero), srcSize)
1347                        : min(add(offset, length), srcSize);
1348     Value newLength = sub(endLoc, newOffset);
1349     newLengths.push_back(getAsOpFoldResult(newLength));
1350 
1351     // Check if newLength is zero. In that case, no SubTensorOp should be
1352     // executed.
1353     if (auto newLengthInt = getConstantIntValue(newLength)) {
1354       hasZeroLen |= *newLengthInt == 0;
1355     } else {
1356       Value check = b.create<CmpIOp>(loc, CmpIPredicate::eq, newLength, zero);
1357       dynHasZeroLenCond = dynHasZeroLenCond
1358                               ? b.create<OrOp>(loc, check, dynHasZeroLenCond)
1359                               : check;
1360     }
1361 
1362     // The amount of high padding is simply the number of elements remaining,
1363     // so that the result has the same length as the original ExtractSliceOp.
1364     // As an optimization, if the original high padding is zero, then the new
1365     // high padding must also be zero.
1366     Value newHigh = hasHighPad ? sub(sub(length, newLength), newLow) : zero;
1367     appendIndex(newHigh, newHighs, staticNewHighs);
1368 
1369     // Only unit stride supported.
1370     newStrides.push_back(b.getIndexAttr(1));
1371   }
1372 
1373   // The shape of the result can be obtained from the sizes passed in.
1374   SmallVector<Value> dynDims;
1375   SmallVector<int64_t> shape;
1376   dispatchIndexOpFoldResults(sizes, dynDims, shape, ShapedType::kDynamicSize);
1377   RankedTensorType resultType =
1378       RankedTensorType::get(shape, getResultType().getElementType());
1379 
1380   // Insert cast to ensure that types match. (May be folded away.)
1381   auto castResult = [&](Value val) -> Operation * {
1382     auto castOp = b.create<tensor::CastOp>(loc, resultType, val);
1383     return castOp;
1384   };
1385 
1386   // In cases where the original data source is unused: Emit a GenerateOp and
1387   // do not generate a SliceOp. (The result shape of the SliceOp would
1388   // have a dimension of size 0, the semantics of which is unclear.)
1389   auto createGenerateOp = [&]() {
1390     // Create GenerateOp.
1391     auto generateOp = b.create<tensor::GenerateOp>(
1392         loc, resultType, dynDims,
1393         [&](OpBuilder &builder, Location gLoc, ValueRange indices) {
1394           builder.create<tensor::YieldOp>(gLoc, padValue);
1395         });
1396     return castResult(generateOp);
1397   };
1398 
1399   // Emit a SliceOp and a PadTensorOp. Should not be used in cases where
1400   // the result shape of the new SliceOp has a zero dimension.
1401   auto createPadTensorOfSubTensor = [&]() {
1402     // Create pad_tensor(subtensor(x)).
1403     auto newSliceOp = b.create<tensor::ExtractSliceOp>(
1404         loc, source(), newOffsets, newLengths, newStrides);
1405     auto newPadTensorOp = b.create<PadTensorOp>(
1406         loc, newSliceOp, staticNewLows, staticNewHighs, newLows, newHighs);
1407 
1408     // Copy region to new PadTensorOp.
1409     BlockAndValueMapping bvm;
1410     region().cloneInto(&newPadTensorOp.getRegion(), bvm);
1411 
1412     // Cast result and return.
1413     return castResult(newPadTensorOp);
1414   };
1415 
1416   // Rewrite subtensor(pad_tensor(x)) into a GenerateOp it is statically known
1417   // that the original data source x is not used.
1418   if (hasZeroLen) {
1419     return createGenerateOp();
1420   }
1421 
1422   // If there are dynamic dimensions: Generate an scf.if check to avoid creating
1423   // SliceOps with result dimensions of size 0 at runtime.
1424   if (dynHasZeroLenCond) {
1425     auto result = b.create<scf::IfOp>(
1426         loc, resultType, dynHasZeroLenCond,
1427         /*thenBuilder=*/
1428         [&](OpBuilder &b, Location loc) {
1429           b.create<scf::YieldOp>(loc, createGenerateOp()->getResult(0));
1430         },
1431         /*elseBuilder=*/
1432         [&](OpBuilder &b, Location loc) {
1433           b.create<scf::YieldOp>(loc,
1434                                  createPadTensorOfSubTensor()->getResult(0));
1435         });
1436     return result;
1437   }
1438   return createPadTensorOfSubTensor();
1439 }
1440 
1441 namespace {
1442 // Folds linalg.pad_tensor when padding is static zeros.
1443 struct FoldStaticZeroPadding : public OpRewritePattern<PadTensorOp> {
1444   using OpRewritePattern<PadTensorOp>::OpRewritePattern;
1445 
1446   LogicalResult matchAndRewrite(PadTensorOp padTensorOp,
1447                                 PatternRewriter &rewriter) const override {
1448     if (!padTensorOp.hasZeroLowPad() || !padTensorOp.hasZeroHighPad())
1449       return failure();
1450     rewriter.replaceOpWithNewOp<tensor::CastOp>(
1451         padTensorOp, padTensorOp.result().getType(), padTensorOp.source());
1452     return success();
1453   }
1454 };
1455 
1456 // Fold CastOp into PadTensorOp when adding static information.
1457 struct FoldSourceTensorCast : public OpRewritePattern<PadTensorOp> {
1458   using OpRewritePattern<PadTensorOp>::OpRewritePattern;
1459 
1460   LogicalResult matchAndRewrite(PadTensorOp padTensorOp,
1461                                 PatternRewriter &rewriter) const override {
1462     auto castOp = padTensorOp.source().getDefiningOp<tensor::CastOp>();
1463     if (!tensor::canFoldIntoConsumerOp(castOp))
1464       return failure();
1465 
1466     auto newResultType = PadTensorOp::inferResultType(
1467         castOp.source().getType().cast<RankedTensorType>(),
1468         extractFromI64ArrayAttr(padTensorOp.static_low()),
1469         extractFromI64ArrayAttr(padTensorOp.static_high()));
1470 
1471     if (newResultType == padTensorOp.getResultType()) {
1472       rewriter.updateRootInPlace(padTensorOp, [&]() {
1473         padTensorOp.sourceMutable().assign(castOp.source());
1474       });
1475     } else {
1476       auto newOp = rewriter.create<PadTensorOp>(
1477           padTensorOp->getLoc(), newResultType, padTensorOp.source(),
1478           padTensorOp.low(), padTensorOp.high(), padTensorOp.static_low(),
1479           padTensorOp.static_high());
1480       BlockAndValueMapping mapper;
1481       padTensorOp.getRegion().cloneInto(&newOp.getRegion(), mapper);
1482 
1483       rewriter.replaceOpWithNewOp<tensor::CastOp>(
1484           padTensorOp, padTensorOp.getResultType(), newOp);
1485     }
1486     return success();
1487   }
1488 };
1489 } // namespace
1490 
1491 void PadTensorOp::getCanonicalizationPatterns(RewritePatternSet &results,
1492                                               MLIRContext *context) {
1493   results.add<FoldStaticZeroPadding, FoldSourceTensorCast>(context);
1494 }
1495 
1496 /// Return the padding value of the PadTensorOp if it constant. In this context,
1497 /// "constant" means an actual constant or "defined outside of the block".
1498 ///
1499 /// Values are considered constant in three cases:
1500 ///  - A ConstantLike value.
1501 ///  - A basic block argument from a different block.
1502 ///  - A value defined outside of the block.
1503 ///
1504 /// If the padding value is not constant, an empty Value is returned.
1505 Value PadTensorOp::getConstantPaddingValue() {
1506   auto yieldOp = dyn_cast<YieldOp>(getRegion().front().getTerminator());
1507   if (!yieldOp || yieldOp.values().size() != 1)
1508     return {};
1509   Value padValue = yieldOp.values().front();
1510   // Check if yield value is a constant.
1511   if (matchPattern(padValue, m_Constant()))
1512     return padValue;
1513   // Check if yield value is defined inside the PadTensorOp block.
1514   if (padValue.getParentBlock() == &getRegion().front())
1515     return {};
1516   // Else: Yield value defined outside of the PadTensorOp block.
1517   return padValue;
1518 }
1519 
1520 OpFoldResult PadTensorOp::fold(ArrayRef<Attribute>) {
1521   if (getResultType().hasStaticShape() && getResultType() == getSourceType())
1522     return source();
1523   return {};
1524 }
1525 
1526 //===----------------------------------------------------------------------===//
1527 // ReshapeOp
1528 //===----------------------------------------------------------------------===//
1529 
1530 static void print(OpAsmPrinter &p, linalg::TensorExpandShapeOp op) {
1531   ::mlir::printReshapeOp<linalg::TensorExpandShapeOp>(p, op);
1532 }
1533 
1534 static void print(OpAsmPrinter &p, linalg::TensorCollapseShapeOp op) {
1535   ::mlir::printReshapeOp<linalg::TensorCollapseShapeOp>(p, op);
1536 }
1537 
1538 template <typename AffineExprTy>
1539 unsigned getMaxPosOfType(ArrayRef<ReassociationExprs> exprArrays) {
1540   unsigned pos = 0;
1541   for (const auto &exprs : exprArrays) {
1542     for (auto expr : exprs) {
1543       expr.walk([&pos](AffineExpr e) {
1544         if (auto d = e.dyn_cast<AffineExprTy>())
1545           pos = std::max(pos, d.getPosition());
1546       });
1547     }
1548   }
1549   return pos;
1550 }
1551 
1552 SmallVector<AffineMap, 4> TensorCollapseShapeOp::getReassociationMaps() {
1553   return getSymbolLessAffineMaps(getReassociationExprs());
1554 }
1555 SmallVector<ReassociationExprs, 4>
1556 TensorCollapseShapeOp::getReassociationExprs() {
1557   return convertReassociationIndicesToExprs(getContext(),
1558                                             getReassociationIndices());
1559 }
1560 SmallVector<AffineMap, 4> TensorExpandShapeOp::getReassociationMaps() {
1561   return getSymbolLessAffineMaps(getReassociationExprs());
1562 }
1563 SmallVector<ReassociationExprs, 4>
1564 TensorExpandShapeOp::getReassociationExprs() {
1565   return convertReassociationIndicesToExprs(getContext(),
1566                                             getReassociationIndices());
1567 }
1568 
1569 /// For reshape op compute the shape at dimension `dimIndex` of the output in
1570 /// terms of shape of the `src`, when the reshape op is a collapsing
1571 /// operation. It is the product of the shape of the collapsed dimensions of the
1572 /// `src`.
1573 static OpFoldResult
1574 getCollapsedOutputDimFromInputShape(OpBuilder &builder, Location loc,
1575                                     int64_t dimIndex, Value src,
1576                                     ArrayRef<AffineMap> reassociationMap) {
1577   AffineMap map = reassociationMap[dimIndex];
1578   unsigned startPos =
1579       map.getResults().front().cast<AffineDimExpr>().getPosition();
1580   unsigned endPos = map.getResults().back().cast<AffineDimExpr>().getPosition();
1581   AffineExpr expr;
1582   SmallVector<Value, 2> dynamicDims;
1583   for (auto dim : llvm::seq_inclusive(startPos, endPos)) {
1584     dynamicDims.push_back(builder.createOrFold<tensor::DimOp>(loc, src, dim));
1585     AffineExpr currExpr = builder.getAffineSymbolExpr(dim - startPos);
1586     expr = (expr ? expr * currExpr : currExpr);
1587   }
1588   return applyMapToValues(builder, loc,
1589                           AffineMap::get(0, endPos - startPos + 1, expr),
1590                           dynamicDims)[0];
1591 }
1592 
1593 /// Given the `src` of a collapsing reshape op and its reassociation maps,
1594 /// compute the shape of the result of the reshape.
1595 static SmallVector<OpFoldResult, 4> getCollapsedOutputShapeFromInputShape(
1596     OpBuilder &builder, Location loc, Value src,
1597     ArrayRef<int64_t> dstStaticShape, ArrayRef<AffineMap> reassociation) {
1598   return llvm::to_vector<4>(llvm::map_range(
1599       llvm::seq<int64_t>(0, dstStaticShape.size()), [&](int64_t dim) {
1600         return getCollapsedOutputDimFromInputShape(builder, loc, dim, src,
1601                                                    reassociation);
1602       }));
1603 }
1604 
1605 /// Compute a map that for a given dimension of the expanded type gives the
1606 /// dimension in the collapsed type it maps to. Essentially its the inverse of
1607 /// the `reassocation` maps.
1608 static llvm::DenseMap<int64_t, int64_t>
1609 getExpandedDimToCollapsedDimMap(ArrayRef<AffineMap> reassociation) {
1610   llvm::DenseMap<int64_t, int64_t> expandedDimToCollapsedDim;
1611   for (auto map : enumerate(reassociation)) {
1612     unsigned startPos =
1613         map.value().getResults().front().cast<AffineDimExpr>().getPosition();
1614     unsigned endPos =
1615         map.value().getResults().back().cast<AffineDimExpr>().getPosition();
1616     for (auto dim : llvm::seq_inclusive(startPos, endPos)) {
1617       expandedDimToCollapsedDim[dim] = map.index();
1618     }
1619   }
1620   return expandedDimToCollapsedDim;
1621 }
1622 
1623 /// For an expanding reshape op, compute the value for a dimension of the output
1624 /// from the shape of the input.
1625 static OpFoldResult getExpandedOutputDimFromInputShape(
1626     OpBuilder &builder, Location loc, int64_t dimIndex, Value src,
1627     ArrayRef<int64_t> dstStaticShape, ArrayRef<AffineMap> reassociation,
1628     llvm::DenseMap<int64_t, int64_t> &expandedDimToCollapsedDim) {
1629   if (!ShapedType::isDynamic(dstStaticShape[dimIndex])) {
1630     return builder.getI64IntegerAttr(dstStaticShape[dimIndex]);
1631   }
1632   unsigned sourceDimPos = expandedDimToCollapsedDim[dimIndex];
1633   unsigned startPos = reassociation[sourceDimPos]
1634                           .getResults()
1635                           .front()
1636                           .cast<AffineDimExpr>()
1637                           .getPosition();
1638   unsigned endPos = reassociation[sourceDimPos]
1639                         .getResults()
1640                         .back()
1641                         .cast<AffineDimExpr>()
1642                         .getPosition();
1643   int64_t linearizedStaticDim = 1;
1644   for (auto d :
1645        llvm::enumerate(dstStaticShape.slice(startPos, endPos - startPos + 1))) {
1646     if (d.index() + startPos == static_cast<unsigned>(dimIndex))
1647       continue;
1648     assert(!ShapedType::isDynamic(d.value()) &&
1649            "single dimension cannot be expanded into multiple dynamic "
1650            "dimensions");
1651     linearizedStaticDim *= d.value();
1652   }
1653   Value sourceDim = builder.create<tensor::DimOp>(loc, src, sourceDimPos);
1654   return applyMapToValues(
1655       builder, loc,
1656       AffineMap::get(
1657           0, 1, builder.getAffineSymbolExpr(0).floorDiv(linearizedStaticDim)),
1658       sourceDim)[0];
1659 }
1660 
1661 /// Given the `src` of an expanding reshape op, the reassociation maps and the
1662 /// result type, compute the shape of the result of the reshape.
1663 static SmallVector<OpFoldResult, 4> getExpandedOutputShapeFromInputShape(
1664     OpBuilder &builder, Location loc, Value src,
1665     ArrayRef<int64_t> dstStaticShape, ArrayRef<AffineMap> reassociation) {
1666   llvm::DenseMap<int64_t, int64_t> expandedDimToCollapsedDim =
1667       getExpandedDimToCollapsedDimMap(reassociation);
1668   return llvm::to_vector<4>(llvm::map_range(
1669       llvm::seq<int64_t>(0, dstStaticShape.size()), [&](int64_t dim) {
1670         return getExpandedOutputDimFromInputShape(builder, loc, dim, src,
1671                                                   dstStaticShape, reassociation,
1672                                                   expandedDimToCollapsedDim);
1673       }));
1674 }
1675 
1676 static SmallVector<OpFoldResult, 4>
1677 getReshapeOutputShapeFromInputShape(OpBuilder &builder, Location loc, Value src,
1678                                     ArrayRef<int64_t> dstStaticShape,
1679                                     ArrayRef<AffineMap> reassocation) {
1680   return dstStaticShape.size() >
1681                  static_cast<size_t>(src.getType().cast<ShapedType>().getRank())
1682              ? getExpandedOutputShapeFromInputShape(
1683                    builder, loc, src, dstStaticShape, reassocation)
1684              : getCollapsedOutputShapeFromInputShape(
1685                    builder, loc, src, dstStaticShape, reassocation);
1686 }
1687 
1688 //===----------------------------------------------------------------------===//
1689 // TensorReshapeOp
1690 //===----------------------------------------------------------------------===//
1691 
1692 /// Compute the RankedTensorType obtained by applying `reassociation` to `type`.
1693 static RankedTensorType
1694 computeTensorReshapeCollapsedType(RankedTensorType type,
1695                                   ArrayRef<AffineMap> reassociation) {
1696   auto shape = type.getShape();
1697   SmallVector<int64_t, 4> newShape;
1698   newShape.reserve(reassociation.size());
1699 
1700   // Use the fact that reassociation is valid to simplify the logic: only use
1701   // each map's rank.
1702   assert(isReassociationValid(reassociation) && "invalid reassociation");
1703   unsigned currentDim = 0;
1704   for (AffineMap m : reassociation) {
1705     unsigned dim = m.getNumResults();
1706     auto band = shape.slice(currentDim, dim);
1707     int64_t size = 1;
1708     if (llvm::is_contained(band, ShapedType::kDynamicSize))
1709       size = ShapedType::kDynamicSize;
1710     else
1711       for (unsigned d = 0; d < dim; ++d)
1712         size *= shape[currentDim + d];
1713     newShape.push_back(size);
1714     currentDim += dim;
1715   }
1716 
1717   return RankedTensorType::get(newShape, type.getElementType());
1718 }
1719 
1720 void mlir::linalg::TensorCollapseShapeOp::build(
1721     OpBuilder &b, OperationState &result, Value src,
1722     ArrayRef<ReassociationIndices> reassociation,
1723     ArrayRef<NamedAttribute> attrs) {
1724   auto resultType = computeTensorReshapeCollapsedType(
1725       src.getType().cast<RankedTensorType>(),
1726       getSymbolLessAffineMaps(
1727           convertReassociationIndicesToExprs(b.getContext(), reassociation)));
1728   build(b, result, resultType, src, attrs);
1729   result.addAttribute(getReassociationAttrName(),
1730                       getReassociationIndicesAttribute(b, reassociation));
1731 }
1732 
1733 void mlir::linalg::TensorExpandShapeOp::build(
1734     OpBuilder &b, OperationState &result, Value src,
1735     ArrayRef<ReassociationIndices> reassociation,
1736     ArrayRef<NamedAttribute> attrs) {
1737   auto resultType = computeTensorReshapeCollapsedType(
1738       src.getType().cast<RankedTensorType>(),
1739       getSymbolLessAffineMaps(
1740           convertReassociationIndicesToExprs(b.getContext(), reassociation)));
1741   build(b, result, resultType, src, attrs);
1742   result.addAttribute(getReassociationAttrName(),
1743                       getReassociationIndicesAttribute(b, reassociation));
1744 }
1745 
1746 template <typename TensorReshapeOp,
1747           bool isExpansion =
1748               std::is_same<TensorReshapeOp, TensorExpandShapeOp>::value>
1749 static LogicalResult verifyTensorReshapeOp(TensorReshapeOp op,
1750                                            RankedTensorType expandedType,
1751                                            RankedTensorType collapsedType) {
1752   if (failed(
1753           verifyReshapeLikeTypes(op, expandedType, collapsedType, isExpansion)))
1754     return failure();
1755 
1756   auto maps = op.getReassociationMaps();
1757   RankedTensorType expectedType =
1758       computeTensorReshapeCollapsedType(expandedType, maps);
1759   if (collapsedType != expectedType)
1760     return op.emitOpError("expected collapsed type to be ")
1761            << expectedType << ", but got " << collapsedType;
1762   return success();
1763 }
1764 
1765 static LogicalResult verify(TensorExpandShapeOp op) {
1766   return verifyTensorReshapeOp(op, op.getResultType(), op.getSrcType());
1767 }
1768 
1769 static LogicalResult verify(TensorCollapseShapeOp op) {
1770   return verifyTensorReshapeOp(op, op.getSrcType(), op.getResultType());
1771 }
1772 
1773 namespace {
1774 /// Reshape of a splat constant can be replaced with a constant of the result
1775 /// type.
1776 template <typename TensorReshapeOp>
1777 struct FoldReshapeWithConstant : OpRewritePattern<TensorReshapeOp> {
1778   using OpRewritePattern<TensorReshapeOp>::OpRewritePattern;
1779   LogicalResult matchAndRewrite(TensorReshapeOp reshapeOp,
1780                                 PatternRewriter &rewriter) const override {
1781     DenseElementsAttr attr;
1782     if (!matchPattern(reshapeOp.src(), m_Constant(&attr)))
1783       return failure();
1784     if (!attr || !attr.isSplat())
1785       return failure();
1786     DenseElementsAttr newAttr = DenseElementsAttr::getFromRawBuffer(
1787         reshapeOp.getResultType(), attr.getRawData(), true);
1788     rewriter.replaceOpWithNewOp<ConstantOp>(reshapeOp, newAttr);
1789     return success();
1790   }
1791 };
1792 
1793 /// Fold linalg.fill -> linalg.tensor_reshape chain.
1794 ///
1795 /// For such op chains, we can create new linalg.fill ops with the result
1796 /// type of the linalg.tensor_reshape op.
1797 template <typename TensorReshapeOp>
1798 struct FoldFillWithTensorReshape : OpRewritePattern<TensorReshapeOp> {
1799   using OpRewritePattern<TensorReshapeOp>::OpRewritePattern;
1800   LogicalResult matchAndRewrite(TensorReshapeOp reshapeOp,
1801                                 PatternRewriter &rewriter) const override {
1802     auto oldFill = reshapeOp.src().template getDefiningOp<FillOp>();
1803     if (!oldFill)
1804       return failure();
1805 
1806     Location loc = oldFill.getLoc();
1807     auto newInit = rewriter.create<TensorReshapeOp>(
1808         loc, reshapeOp.getResultType(), oldFill.output(),
1809         reshapeOp.reassociation());
1810     rewriter.replaceOpWithNewOp<FillOp>(reshapeOp, oldFill.value(), newInit);
1811 
1812     return success();
1813   }
1814 };
1815 } // namespace
1816 
1817 void TensorExpandShapeOp::getCanonicalizationPatterns(
1818     RewritePatternSet &results, MLIRContext *context) {
1819   results
1820       .add<CollapseReshapeOps<TensorExpandShapeOp>,
1821            CollapseMixedReshapeOps<TensorExpandShapeOp, TensorCollapseShapeOp>,
1822            FoldFillWithTensorReshape<TensorExpandShapeOp>,
1823            FoldInitTensorWithTensorReshapeOp<TensorExpandShapeOp>,
1824            FoldReshapeWithConstant<TensorExpandShapeOp>>(context);
1825 }
1826 
1827 void TensorCollapseShapeOp::getCanonicalizationPatterns(
1828     RewritePatternSet &results, MLIRContext *context) {
1829   results
1830       .add<CollapseReshapeOps<TensorCollapseShapeOp>,
1831            CollapseMixedReshapeOps<TensorCollapseShapeOp, TensorExpandShapeOp>,
1832            FoldFillWithTensorReshape<TensorCollapseShapeOp>,
1833            FoldInitTensorWithTensorReshapeOp<TensorCollapseShapeOp>,
1834            FoldReshapeWithConstant<TensorCollapseShapeOp>>(context);
1835 }
1836 
1837 LogicalResult TensorExpandShapeOp::reifyResultShapes(
1838     OpBuilder &b, ReifiedRankedShapedTypeDims &reifiedReturnShapes) {
1839   auto resultShape =
1840       getAsValues(b, getLoc(),
1841                   getReshapeOutputShapeFromInputShape(
1842                       b, getLoc(), src(), getResultType().getShape(),
1843                       getReassociationMaps()));
1844   reifiedReturnShapes.emplace_back(std::move(resultShape));
1845   return success();
1846 }
1847 
1848 LogicalResult TensorCollapseShapeOp::reifyResultShapes(
1849     OpBuilder &b, ReifiedRankedShapedTypeDims &reifiedReturnShapes) {
1850   auto resultShape =
1851       getAsValues(b, getLoc(),
1852                   getReshapeOutputShapeFromInputShape(
1853                       b, getLoc(), src(), getResultType().getShape(),
1854                       getReassociationMaps()));
1855   reifiedReturnShapes.emplace_back(std::move(resultShape));
1856   return success();
1857 }
1858 
1859 //===----------------------------------------------------------------------===//
1860 // YieldOp
1861 //===----------------------------------------------------------------------===//
1862 
1863 static void print(OpAsmPrinter &p, linalg::YieldOp op) {
1864   if (op.getNumOperands() > 0)
1865     p << ' ' << op.getOperands();
1866   p.printOptionalAttrDict(op->getAttrs());
1867   if (op.getNumOperands() > 0)
1868     p << " : " << op.getOperandTypes();
1869 }
1870 
1871 static ParseResult parseYieldOp(OpAsmParser &parser, OperationState &result) {
1872   SmallVector<OpAsmParser::OperandType, 2> opInfo;
1873   SmallVector<Type, 2> types;
1874   llvm::SMLoc loc = parser.getCurrentLocation();
1875   return failure(parser.parseOperandList(opInfo) ||
1876                  parser.parseOptionalAttrDict(result.attributes) ||
1877                  (!opInfo.empty() && parser.parseColonTypeList(types)) ||
1878                  parser.resolveOperands(opInfo, types, loc, result.operands));
1879 }
1880 
1881 // Check the operand number and types must match the element types of the
1882 // LinalgOp interface's shaped operands.
1883 static LogicalResult verifyYield(linalg::YieldOp op, LinalgOp linalgOp) {
1884   if (op.getNumOperands() != linalgOp.getNumOutputs())
1885     return op.emitOpError("expected number of yield values (")
1886            << linalgOp.getNumOutputs()
1887            << ") to match the number of operands of the enclosing "
1888            << "LinalgOp (" << op.getNumOperands() << ")";
1889 
1890   for (OpOperand &opOperand : op->getOpOperands()) {
1891     OpOperand *outputOperand =
1892         linalgOp.getOutputOperand(opOperand.getOperandNumber());
1893     Type elementType = getElementTypeOrSelf(outputOperand->get().getType());
1894     if (opOperand.get().getType() != elementType)
1895       return op.emitOpError("type of yield operand ")
1896              << (opOperand.getOperandNumber() + 1) << " ("
1897              << opOperand.get().getType() << ") doesn't match "
1898              << "the element type of the enclosing linalg.generic op ("
1899              << elementType << ")";
1900   }
1901   return success();
1902 }
1903 
1904 static LogicalResult verify(linalg::YieldOp op) {
1905   auto *parentOp = op->getParentOp();
1906   if (parentOp->getNumRegions() != 1 || parentOp->getRegion(0).empty())
1907     return op.emitOpError("expected single non-empty parent region");
1908 
1909   if (auto linalgOp = dyn_cast<LinalgOp>(parentOp))
1910     return verifyYield(op, cast<LinalgOp>(parentOp));
1911 
1912   if (auto padTensorOp = dyn_cast<linalg::PadTensorOp>(parentOp)) {
1913     if (op.getNumOperands() != 1)
1914       return op.emitOpError("expected single yield operand (got ")
1915              << op->getNumOperands() << ")";
1916     if (op.getOperand(0).getType() !=
1917         padTensorOp.getType().cast<ShapedType>().getElementType())
1918       return op.emitOpError("expected yield type to match shape element type");
1919     return success();
1920   }
1921 
1922   if (auto tiledLoopOp = dyn_cast<linalg::TiledLoopOp>(parentOp)) {
1923     // Check if output args with tensor types match results types.
1924     SmallVector<Value, 2> tensorOuts;
1925     llvm::copy_if(
1926         tiledLoopOp.outputs(), std::back_inserter(tensorOuts),
1927         [&](Value out) { return out.getType().isa<RankedTensorType>(); });
1928     if (tensorOuts.size() != op.values().size())
1929       return op.emitOpError("expected number of tensor output args = ")
1930              << tensorOuts.size() << " to match the number of yield operands = "
1931              << op.values().size();
1932 
1933     TypeRange tensorTypes(llvm::makeArrayRef(tensorOuts));
1934     for (auto &item :
1935          llvm::enumerate(llvm::zip(tensorTypes, op.getOperandTypes()))) {
1936       Type outType, resultType;
1937       unsigned index = item.index();
1938       std::tie(outType, resultType) = item.value();
1939       if (outType != resultType)
1940         return op.emitOpError("expected yield operand ")
1941                << index << " with type = " << resultType
1942                << " to match output arg type = " << outType;
1943     }
1944     return success();
1945   }
1946   return op.emitOpError("expected parent op with LinalgOp interface");
1947 }
1948 
1949 //===----------------------------------------------------------------------===//
1950 // TiledLoopOp
1951 //===----------------------------------------------------------------------===//
1952 
1953 void TiledLoopOp::build(OpBuilder &builder, OperationState &result,
1954                         ValueRange lowerBounds, ValueRange upperBounds,
1955                         ValueRange steps, ValueRange inputs, ValueRange outputs,
1956                         ArrayAttr iteratorTypes,
1957                         function_ref<void(OpBuilder &, Location, ValueRange,
1958                                           ValueRange, ValueRange)>
1959                             bodyBuilderFn) {
1960   build(builder, result, lowerBounds, upperBounds, steps, inputs, outputs,
1961         iteratorTypes, llvm::None, bodyBuilderFn);
1962 }
1963 
1964 void TiledLoopOp::build(OpBuilder &builder, OperationState &result,
1965                         ValueRange lowerBounds, ValueRange upperBounds,
1966                         ValueRange steps, ValueRange inputs, ValueRange outputs,
1967                         ArrayAttr iteratorTypes,
1968                         Optional<ArrayAttr> distributionTypes,
1969                         function_ref<void(OpBuilder &, Location, ValueRange,
1970                                           ValueRange, ValueRange)>
1971                             bodyBuilderFn) {
1972   result.addOperands(lowerBounds);
1973   result.addOperands(upperBounds);
1974   result.addOperands(steps);
1975   result.addOperands(inputs);
1976   result.addOperands(outputs);
1977   result.addAttribute(
1978       TiledLoopOp::getOperandSegmentSizeAttr(),
1979       builder.getI32VectorAttr({static_cast<int32_t>(lowerBounds.size()),
1980                                 static_cast<int32_t>(upperBounds.size()),
1981                                 static_cast<int32_t>(steps.size()),
1982                                 static_cast<int32_t>(inputs.size()),
1983                                 static_cast<int32_t>(outputs.size())}));
1984   result.addAttribute(getIteratorTypesAttrName(), iteratorTypes);
1985 
1986   if (distributionTypes.hasValue())
1987     result.addAttribute(getDistributionTypesAttrName(),
1988                         distributionTypes.getValue());
1989 
1990   // Add output types for `RankedTensorType` output arguments.
1991   for (Value output : outputs) {
1992     Type outputType = output.getType();
1993     if (outputType.isa<RankedTensorType>())
1994       result.addTypes(outputType);
1995   }
1996 
1997   OpBuilder::InsertionGuard guard(builder);
1998   unsigned numIVs = steps.size();
1999   SmallVector<Type, 8> argTypes(numIVs, builder.getIndexType());
2000   for (Type type : TypeRange(inputs))
2001     argTypes.push_back(type);
2002   for (Type type : TypeRange(outputs))
2003     argTypes.push_back(type);
2004   Region *bodyRegion = result.addRegion();
2005   Block *bodyBlock = builder.createBlock(bodyRegion, {}, argTypes);
2006 
2007   if (bodyBuilderFn) {
2008     builder.setInsertionPointToStart(bodyBlock);
2009     bodyBuilderFn(builder, result.location,
2010                   bodyBlock->getArguments().take_front(numIVs),
2011                   bodyBlock->getArguments().slice(numIVs, inputs.size()),
2012                   bodyBlock->getArguments().take_back(outputs.size()));
2013     TiledLoopOp::ensureTerminator(*bodyRegion, builder, result.location);
2014   }
2015 }
2016 
2017 static void print(OpAsmPrinter &p, TiledLoopOp op) {
2018   p << " (" << op.getInductionVars() << ") = (" << op.lowerBound() << ") to ("
2019     << op.upperBound() << ") step (" << op.step() << ")";
2020 
2021   if (!op.inputs().empty()) {
2022     p << " ins (";
2023     llvm::interleaveComma(llvm::zip(op.getRegionInputArgs(), op.inputs()), p,
2024                           [&](auto it) {
2025                             p << std::get<0>(it) << " = " << std::get<1>(it)
2026                               << ": " << std::get<1>(it).getType();
2027                           });
2028     p << ")";
2029   }
2030   if (!op.outputs().empty()) {
2031     p << " outs (";
2032     llvm::interleaveComma(llvm::zip(op.getRegionOutputArgs(), op.outputs()), p,
2033                           [&](auto it) {
2034                             p << std::get<0>(it) << " = " << std::get<1>(it)
2035                               << ": " << std::get<1>(it).getType();
2036                           });
2037     p << ")";
2038   }
2039 
2040   if (llvm::any_of(op.iterator_types(), [](Attribute attr) {
2041         return attr.cast<StringAttr>().getValue() !=
2042                getParallelIteratorTypeName();
2043       }))
2044     p << " iterators" << op.iterator_types() << "";
2045 
2046   if (op.distribution_types().hasValue())
2047     p << " distribution" << op.distribution_types().getValue() << "";
2048 
2049   p.printRegion(op.region(), /*printEntryBlockArgs=*/false);
2050   p.printOptionalAttrDict(
2051       op->getAttrs(), /*elidedAttrs=*/{TiledLoopOp::getOperandSegmentSizeAttr(),
2052                                        getIteratorTypesAttrName(),
2053                                        getDistributionTypesAttrName()});
2054 }
2055 
2056 static ParseResult parseTiledLoopOp(OpAsmParser &parser,
2057                                     OperationState &result) {
2058   auto &builder = parser.getBuilder();
2059   // Parse an opening `(` followed by induction variables followed by `)`
2060   SmallVector<OpAsmParser::OperandType, 4> ivs;
2061   if (parser.parseRegionArgumentList(ivs, /*requiredOperandCount=*/-1,
2062                                      OpAsmParser::Delimiter::Paren))
2063     return failure();
2064 
2065   // Parse loop bounds.
2066   SmallVector<OpAsmParser::OperandType, 4> lower;
2067   if (parser.parseEqual() ||
2068       parser.parseOperandList(lower, ivs.size(),
2069                               OpAsmParser::Delimiter::Paren) ||
2070       parser.resolveOperands(lower, builder.getIndexType(), result.operands))
2071     return failure();
2072 
2073   SmallVector<OpAsmParser::OperandType, 4> upper;
2074   if (parser.parseKeyword("to") ||
2075       parser.parseOperandList(upper, ivs.size(),
2076                               OpAsmParser::Delimiter::Paren) ||
2077       parser.resolveOperands(upper, builder.getIndexType(), result.operands))
2078     return failure();
2079 
2080   // Parse step values.
2081   SmallVector<OpAsmParser::OperandType, 4> steps;
2082   if (parser.parseKeyword("step") ||
2083       parser.parseOperandList(steps, ivs.size(),
2084                               OpAsmParser::Delimiter::Paren) ||
2085       parser.resolveOperands(steps, builder.getIndexType(), result.operands))
2086     return failure();
2087 
2088   // Parse input tensors.
2089   SmallVector<OpAsmParser::OperandType, 4> inputs, input_region_args;
2090   SmallVector<Type, 4> inputTypes;
2091   if (succeeded(parser.parseOptionalKeyword("ins"))) {
2092     llvm::SMLoc inputsOperandsLoc = parser.getCurrentLocation();
2093 
2094     if (parser.parseAssignmentListWithTypes(input_region_args, inputs,
2095                                             inputTypes))
2096       return failure();
2097 
2098     if (parser.resolveOperands(inputs, inputTypes, inputsOperandsLoc,
2099                                result.operands))
2100       return failure();
2101   }
2102 
2103   // Parse output tensors.
2104   SmallVector<OpAsmParser::OperandType, 4> outputs, output_region_args;
2105   SmallVector<Type, 4> outputTypes;
2106   if (succeeded(parser.parseOptionalKeyword("outs"))) {
2107     llvm::SMLoc outputsOperandsLoc = parser.getCurrentLocation();
2108 
2109     if (parser.parseAssignmentListWithTypes(output_region_args, outputs,
2110                                             outputTypes))
2111       return failure();
2112 
2113     if (parser.resolveOperands(outputs, outputTypes, outputsOperandsLoc,
2114                                result.operands))
2115       return failure();
2116     for (Type outputType : outputTypes)
2117       if (outputType.isa<RankedTensorType>())
2118         result.addTypes(outputType);
2119   }
2120 
2121   // Parse attributes.
2122   SmallVector<Attribute, 4> iterTypes, distributionTypes;
2123   auto parseAttr = [&](StringRef keyword, SmallVector<Attribute, 4> *attrs) {
2124     if (succeeded(parser.parseOptionalKeyword(keyword))) {
2125       StringAttr attr;
2126 
2127       if (parser.parseLSquare() || parser.parseAttribute(attr))
2128         return failure();
2129       attrs->push_back(attr);
2130       for (int i = 1, e = ivs.size(); i < e; ++i) {
2131         if (parser.parseComma() || parser.parseAttribute(attr))
2132           return failure();
2133         attrs->push_back(attr);
2134       }
2135       if (parser.parseRSquare())
2136         return failure();
2137     }
2138     return success();
2139   };
2140   if (failed(parseAttr("iterators", &iterTypes)) ||
2141       failed(parseAttr("distribution", &distributionTypes)))
2142     return failure();
2143 
2144   // Set all loop iterator types to "parallel" if they are not printed in IR.
2145   if (iterTypes.empty()) {
2146     auto parallelIter = builder.getStringAttr(getParallelIteratorTypeName());
2147     iterTypes = SmallVector<Attribute, 4>(ivs.size(), parallelIter);
2148   }
2149   result.addAttribute(getIteratorTypesAttrName(),
2150                       builder.getArrayAttr(iterTypes));
2151   if (!distributionTypes.empty())
2152     result.addAttribute(getDistributionTypesAttrName(),
2153                         builder.getArrayAttr(distributionTypes));
2154   result.addAttribute(
2155       TiledLoopOp::getOperandSegmentSizeAttr(),
2156       builder.getI32VectorAttr({static_cast<int32_t>(lower.size()),
2157                                 static_cast<int32_t>(upper.size()),
2158                                 static_cast<int32_t>(steps.size()),
2159                                 static_cast<int32_t>(inputs.size()),
2160                                 static_cast<int32_t>(outputs.size())}));
2161 
2162   // Parse the body.
2163   Region *body = result.addRegion();
2164 
2165   SmallVector<Type, 4> region_types(ivs.size(), builder.getIndexType());
2166   region_types.append(inputTypes);
2167   region_types.append(outputTypes);
2168 
2169   SmallVector<OpAsmParser::OperandType, 4> region_args(ivs);
2170   region_args.append(input_region_args);
2171   region_args.append(output_region_args);
2172 
2173   if (parser.parseRegion(*body, region_args, region_types))
2174     return failure();
2175 
2176   // Parse optional attributes.
2177   parser.parseOptionalAttrDict(result.attributes);
2178 
2179   return success();
2180 }
2181 
2182 Region &TiledLoopOp::getLoopBody() { return region(); }
2183 
2184 LogicalResult TiledLoopOp::moveOutOfLoop(ArrayRef<Operation *> ops) {
2185   for (auto *op : ops)
2186     op->moveBefore(*this);
2187   return success();
2188 }
2189 
2190 bool TiledLoopOp::isDefinedOutsideOfLoop(Value value) {
2191   return !region().isAncestor(value.getParentRegion());
2192 }
2193 
2194 static LogicalResult verify(TiledLoopOp op) {
2195   // Check if iterator types are provided for every loop dimension.
2196   if (op.iterator_types().size() != op.getNumLoops())
2197     return op.emitOpError("expected iterator types array attribute size = ")
2198            << op.iterator_types().size()
2199            << " to match the number of loops = " << op.getNumLoops();
2200 
2201   // Check if types of input arguments match region args types.
2202   for (auto &item :
2203        llvm::enumerate(llvm::zip(op.inputs(), op.getRegionInputArgs()))) {
2204     Value input, inputRegionArg;
2205     unsigned index = item.index();
2206     std::tie(input, inputRegionArg) = item.value();
2207     if (input.getType() != inputRegionArg.getType())
2208       return op.emitOpError("expected input arg ")
2209              << index << " with type = " << input.getType()
2210              << " to match region arg " << index + op.getNumLoops()
2211              << " type = " << inputRegionArg.getType();
2212   }
2213 
2214   // Check if types of input arguments match region args types.
2215   for (auto &item :
2216        llvm::enumerate(llvm::zip(op.outputs(), op.getRegionOutputArgs()))) {
2217     Value output, outputRegionArg;
2218     unsigned index = item.index();
2219     std::tie(output, outputRegionArg) = item.value();
2220     if (output.getType() != outputRegionArg.getType())
2221       return op.emitOpError("expected output arg ")
2222              << index << " with type = " << output.getType()
2223              << " to match region arg "
2224              << index + op.getNumLoops() + op.inputs().size()
2225              << " type = " << outputRegionArg.getType();
2226   }
2227   return success();
2228 }
2229 
2230 namespace {
2231 
2232 static constexpr int64_t kNoMatch = -1;
2233 
2234 // Folds away TiledLoopOp inputs if they have no uses within the body.
2235 //
2236 // Example:
2237 //
2238 // %0 = linalg.tiled_loop ...  ins (%in_ = %in: tensor<...>,
2239 //                                  %in_buf_ = %in_buf: memref<...>) {...}
2240 // Becomes
2241 //
2242 // linalg.tiled_loop ...  ins (%in_buf_ = %in_buf: memref<...>) {...}
2243 struct TiledLoopInputsFolder : public OpRewritePattern<linalg::TiledLoopOp> {
2244   using OpRewritePattern<linalg::TiledLoopOp>::OpRewritePattern;
2245 
2246   LogicalResult matchAndRewrite(linalg::TiledLoopOp tiledLoop,
2247                                 PatternRewriter &rewriter) const final {
2248     SmallVector<Value, 2> newInputs, regionInputTensorArgs;
2249     // Store ids of the corresponding old and new input operands.
2250     SmallVector<int64_t, 2> oldInputIdToNew(tiledLoop.inputs().size(),
2251                                             kNoMatch);
2252     for (auto en : llvm::enumerate(
2253              llvm::zip(tiledLoop.inputs(), tiledLoop.getRegionInputArgs()))) {
2254       Value in, bbArg;
2255       size_t index = en.index();
2256       std::tie(in, bbArg) = en.value();
2257       if (!bbArg.use_empty()) {
2258         oldInputIdToNew[index] = newInputs.size();
2259         newInputs.push_back(in);
2260       }
2261     }
2262     if (newInputs.size() == tiledLoop.inputs().size())
2263       return failure();
2264     Location loc = tiledLoop.getLoc();
2265     auto newTiledLoop = rewriter.create<TiledLoopOp>(
2266         loc, tiledLoop.lowerBound(), tiledLoop.upperBound(), tiledLoop.step(),
2267         newInputs, tiledLoop.outputs(), tiledLoop.iterator_types(),
2268         tiledLoop.distribution_types());
2269 
2270     // Clone the region.
2271     BlockAndValueMapping bvm;
2272     bvm.map(tiledLoop.getInductionVars(), newTiledLoop.getInductionVars());
2273     bvm.map(tiledLoop.getRegionOutputArgs(),
2274             newTiledLoop.getRegionOutputArgs());
2275     for (const auto &en : llvm::enumerate(oldInputIdToNew))
2276       if (en.value() != kNoMatch)
2277         bvm.map(tiledLoop.getRegionInputArgs()[en.index()],
2278                 newTiledLoop.getRegionInputArgs()[en.value()]);
2279     OpBuilder innerBuilder =
2280         OpBuilder::atBlockEnd(newTiledLoop.getBody(), rewriter.getListener());
2281     for (auto &op : *tiledLoop.getBody())
2282       innerBuilder.clone(op, bvm);
2283     rewriter.replaceOp(tiledLoop, newTiledLoop.getResults());
2284 
2285     return success();
2286   }
2287 };
2288 
2289 } // namespace
2290 
2291 /// A simple, conservative analysis to determine if the loop is shape
2292 /// conserving. I.e., the type of the arg-th yielded value is the same as the
2293 /// type of the corresponding basic block argument of the loop.
2294 /// Note: This function handles only simple cases. Expand as needed.
2295 static bool isShapePreserving(TiledLoopOp loopOp, int64_t arg) {
2296   auto yieldOp = cast<YieldOp>(loopOp.getLoopBody().front().getTerminator());
2297   if (yieldOp.values().empty())
2298     // Tiled loop either has no outputs or is a "memref-based version". In
2299     // either case, the loop is shape conserving.
2300     return true;
2301   assert(arg < static_cast<int64_t>(yieldOp.values().size()) &&
2302          "arg is out of bounds");
2303   Value value = yieldOp.values()[arg];
2304   while (value) {
2305     if (value == loopOp.getRegionOutputArgs()[arg])
2306       return true;
2307     OpResult opResult = value.dyn_cast<OpResult>();
2308     if (!opResult)
2309       return false;
2310 
2311     using tensor::InsertSliceOp;
2312     value = llvm::TypeSwitch<Operation *, Value>(opResult.getOwner())
2313                 .template Case<InsertSliceOp>(
2314                     [&](InsertSliceOp op) { return op.dest(); })
2315                 .template Case<TiledLoopOp>([&](TiledLoopOp loopOp) {
2316                   return isShapePreserving(loopOp, opResult.getResultNumber())
2317                              ? loopOp.outputs()[opResult.getResultNumber()]
2318                              : Value();
2319                 })
2320                 .Default([&](auto op) { return Value(); });
2321   }
2322   return false;
2323 }
2324 
2325 namespace {
2326 
2327 /// Fold dim(x) where `x` is an input/output argument of a TiledLoopOp block
2328 /// to dim(y) where `y` is the initial input/output value of the argument.
2329 ///
2330 /// E.g.:
2331 /// %y = ... : tensor<...>
2332 /// linalg.tiled_loop ... ins(%x = %y : tensor<...>) {
2333 ///   tensor.dim %x, %c0 : tensor<...>
2334 /// }
2335 ///
2336 /// is folded to:
2337 /// %y = ... : tensor<...>
2338 /// linalg.tiled_loop ... ins(%x = %y : tensor<...>) {
2339 ///   tensor.dim %y, %c0 : tensor<...>
2340 /// }
2341 ///
2342 /// Note: Dim ops are folded only if it can be proven that the runtime type of
2343 /// the yielded value (in case of outputs) does not change with loop iterations.
2344 template <typename OpTy>
2345 struct DimOfTiledLoopInsOutsFolder : public OpRewritePattern<OpTy> {
2346   using OpRewritePattern<OpTy>::OpRewritePattern;
2347 
2348   LogicalResult matchAndRewrite(OpTy dimOp,
2349                                 PatternRewriter &rewriter) const final {
2350     auto src = dimOp.source().template dyn_cast<BlockArgument>();
2351     if (!src)
2352       return failure();
2353     auto loopOp =
2354         dyn_cast<TiledLoopOp>(src.getOwner()->getParent()->getParentOp());
2355     if (!loopOp)
2356       return failure();
2357     unsigned numLoops = loopOp.getNumLoops();
2358     unsigned numInputArgs = loopOp.getRegionInputArgs().size();
2359     if (src.getArgNumber() >= numInputArgs + numLoops &&
2360         !isShapePreserving(loopOp,
2361                            src.getArgNumber() - numInputArgs - numLoops))
2362       return failure();
2363 
2364     auto inputArgs = loopOp.getRegionInputArgs();
2365     auto it1 = llvm::find(inputArgs, src);
2366     if (it1 != inputArgs.end()) {
2367       rewriter.updateRootInPlace(dimOp, [&] {
2368         dimOp.sourceMutable().assign(loopOp.inputs()[it1 - inputArgs.begin()]);
2369       });
2370       return success();
2371     }
2372 
2373     auto outputArgs = loopOp.getRegionOutputArgs();
2374     auto it2 = llvm::find(outputArgs, src);
2375     if (it2 != outputArgs.end()) {
2376       rewriter.updateRootInPlace(dimOp, [&] {
2377         dimOp.sourceMutable().assign(
2378             loopOp.outputs()[it2 - outputArgs.begin()]);
2379       });
2380       return success();
2381     }
2382 
2383     return failure();
2384   }
2385 };
2386 
2387 /// Fold dim(r) where `r` is the result of a TiledLoopOp to dim(y) where `y`
2388 /// is the initial output value of the loop.
2389 ///
2390 /// E.g.:
2391 /// %y = ... : tensor<...>
2392 /// %r = linalg.tiled_loop ... outs(%i = %y : tensor<...>) {
2393 ///   ...
2394 /// }
2395 /// %0 = tensor.dim %r, %c0 : tensor<...>
2396 ///
2397 /// is folded to:
2398 /// %y = ... : tensor<...>
2399 /// linalg.tiled_loop ... outs(%i = %y : tensor<...>) {
2400 ///   ...
2401 /// }
2402 /// %0 = tensor.dim %y, %c0 : tensor<...>
2403 ///
2404 /// Note: Dim ops are folded only if it can be proven that the runtime type of
2405 /// the yielded value (in case of outputs) does not change with loop iterations.
2406 template <typename OpTy>
2407 struct DimOfTiledLoopResultFolder : public OpRewritePattern<OpTy> {
2408   using OpRewritePattern<OpTy>::OpRewritePattern;
2409 
2410   LogicalResult matchAndRewrite(OpTy dimOp,
2411                                 PatternRewriter &rewriter) const final {
2412     auto loopOp = dimOp.source().template getDefiningOp<TiledLoopOp>();
2413     if (!loopOp)
2414       return failure();
2415     auto opResult = dimOp.source().template cast<OpResult>();
2416     unsigned resultNumber = opResult.getResultNumber();
2417     if (!isShapePreserving(loopOp, resultNumber))
2418       return failure();
2419     rewriter.updateRootInPlace(dimOp, [&]() {
2420       dimOp.sourceMutable().assign(loopOp.outputs()[resultNumber]);
2421     });
2422     return success();
2423   }
2424 };
2425 
2426 // Folds away TiledLoopOp output tensors when the following conditions are met:
2427 // * result of `linalg.tiled_loop` has no uses
2428 // * output tensor is the argument of `linalg.yield`
2429 //
2430 // Example:
2431 //
2432 // %0 = linalg.tiled_loop ...  outs (%o_ = %out: tensor<...>,
2433 //                                   %obuf_ = %out_buf: memref<...>) {
2434 //   ...
2435 //   linalg.yield %o_ : tensor ...
2436 // }
2437 //
2438 // Becomes
2439 //
2440 // linalg.tiled_loop ...  outs (%obuf_ = %out_buf: memref<...>) {
2441 //   ...
2442 //   linalg.yield
2443 // }
2444 struct TiledLoopResultsFolder : public OpRewritePattern<linalg::TiledLoopOp> {
2445   using OpRewritePattern<linalg::TiledLoopOp>::OpRewritePattern;
2446 
2447   LogicalResult matchAndRewrite(linalg::TiledLoopOp tiledLoop,
2448                                 PatternRewriter &rewriter) const final {
2449     if (tiledLoop.getNumResults() == 0)
2450       return failure();
2451 
2452     Block *block = tiledLoop.getBody();
2453     auto yieldOp = cast<linalg::YieldOp>(block->getTerminator());
2454 
2455     // Match the pattern and collect output buffers that will replace the output
2456     // tensors and also the ops that will be ignored when cloning the body.
2457     SmallVector<Value, 2> newOutputOperands, newYieldArgs;
2458     int resultId = 0;
2459     // Store ids of the corresponding old and new output operands.
2460     SmallVector<int64_t, 2> oldOutputIdToNew(tiledLoop.outputs().size(),
2461                                              kNoMatch);
2462     // Store ids of the corresponding old and new results.
2463     SmallVector<int64_t, 2> oldResultIdToNew(tiledLoop.getNumResults(),
2464                                              kNoMatch);
2465     SmallVector<Value, 2> resultReplacement(tiledLoop.getNumResults());
2466     for (auto en : llvm::enumerate(
2467              llvm::zip(tiledLoop.outputs(), tiledLoop.getRegionOutputArgs()))) {
2468       size_t index = en.index();
2469       Value out = std::get<0>(en.value());
2470       Value outRegionArg = std::get<1>(en.value());
2471 
2472       if (!out.getType().isa<RankedTensorType>()) {
2473         oldOutputIdToNew[index] = newOutputOperands.size();
2474         newOutputOperands.push_back(out);
2475         continue;
2476       }
2477       Value result = tiledLoop.getResult(resultId);
2478       Value yieldArg = yieldOp.getOperand(resultId);
2479       if (yieldArg != outRegionArg || !result.use_empty()) {
2480         oldOutputIdToNew[index] = newOutputOperands.size();
2481         oldResultIdToNew[resultId] = newYieldArgs.size();
2482         resultReplacement[resultId] = out;
2483         newOutputOperands.push_back(out);
2484         newYieldArgs.push_back(yieldArg);
2485       }
2486       ++resultId;
2487     }
2488     if (newOutputOperands.size() == tiledLoop.outputs().size())
2489       return failure();
2490 
2491     Location loc = tiledLoop.getLoc();
2492     auto newTiledLoop = rewriter.create<TiledLoopOp>(
2493         loc, tiledLoop.lowerBound(), tiledLoop.upperBound(), tiledLoop.step(),
2494         tiledLoop.inputs(), newOutputOperands, tiledLoop.iterator_types(),
2495         tiledLoop.distribution_types());
2496 
2497     // Clone the region.
2498     BlockAndValueMapping bvm;
2499     bvm.map(tiledLoop.getInductionVars(), newTiledLoop.getInductionVars());
2500     bvm.map(tiledLoop.getRegionInputArgs(), newTiledLoop.getRegionInputArgs());
2501     for (const auto &en : llvm::enumerate(oldOutputIdToNew)) {
2502       if (en.value() != kNoMatch)
2503         bvm.map(tiledLoop.getRegionOutputArgs()[en.index()],
2504                 newTiledLoop.getRegionOutputArgs()[en.value()]);
2505       else
2506         bvm.map(tiledLoop.getRegionOutputArgs()[en.index()],
2507                 tiledLoop.outputs()[en.index()]);
2508     }
2509     OpBuilder innerBuilder =
2510         OpBuilder::atBlockEnd(newTiledLoop.getBody(), rewriter.getListener());
2511     for (auto &op : tiledLoop.getBody()->without_terminator())
2512       innerBuilder.clone(op, bvm);
2513     innerBuilder.create<linalg::YieldOp>(
2514         loc, llvm::to_vector<2>(llvm::map_range(
2515                  newYieldArgs, [&](Value arg) { return bvm.lookup(arg); })));
2516 
2517     for (const auto &en : llvm::enumerate(oldResultIdToNew))
2518       if (en.value() != kNoMatch)
2519         resultReplacement[en.index()] = newTiledLoop.getResult(en.value());
2520     rewriter.replaceOp(tiledLoop, resultReplacement);
2521 
2522     return success();
2523   }
2524 };
2525 } // namespace
2526 
2527 void TiledLoopOp::getCanonicalizationPatterns(OwningRewritePatternList &results,
2528                                               MLIRContext *context) {
2529   results.insert<TiledLoopInputsFolder, TiledLoopResultsFolder,
2530                  DimOfTiledLoopInsOutsFolder<tensor::DimOp>,
2531                  DimOfTiledLoopInsOutsFolder<memref::DimOp>,
2532                  DimOfTiledLoopResultFolder<tensor::DimOp>,
2533                  DimOfTiledLoopResultFolder<memref::DimOp>>(context);
2534 }
2535 
2536 LogicalResult TiledLoopOp::fold(ArrayRef<Attribute>,
2537                                 SmallVectorImpl<OpFoldResult> &) {
2538   return foldMemRefCastInTiledLoopOp(*this);
2539 }
2540 
2541 //===----------------------------------------------------------------------===//
2542 // IndexOp
2543 //===----------------------------------------------------------------------===//
2544 
2545 static LogicalResult verify(IndexOp op) {
2546   auto linalgOp = dyn_cast<LinalgOp>(op->getParentOp());
2547   if (!linalgOp)
2548     return op.emitOpError("expected parent op with LinalgOp interface");
2549   if (linalgOp.getNumLoops() <= op.dim())
2550     return op.emitOpError("expected dim (")
2551            << op.dim() << ") to be lower than the number of loops ("
2552            << linalgOp.getNumLoops() << ") of the enclosing LinalgOp";
2553   return success();
2554 }
2555 
2556 /////// Operations corresponding to library calls defined with Tablegen ////////
2557 
2558 template <typename LinalgPoolingOp>
2559 static LogicalResult verifyStrideOrDilation(LinalgPoolingOp op,
2560                                             ArrayRef<Attribute> attrs,
2561                                             bool isStride) {
2562   auto strideOrDilation = isStride ? "stride" : "dilation";
2563   if (attrs.size() != op.getNumWindowLoops())
2564     return op.emitOpError("expects num ")
2565            << strideOrDilation
2566            << "s equal to number of window dimensions: " << attrs.size()
2567            << " vs " << op.getNumWindowLoops();
2568   return success();
2569 }
2570 
2571 void ConvOp::getEffects(
2572     SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
2573         &effects) {
2574   effects.emplace_back(MemoryEffects::Read::get(), input(),
2575                        SideEffects::DefaultResource::get());
2576   effects.emplace_back(MemoryEffects::Read::get(), filter(),
2577                        SideEffects::DefaultResource::get());
2578   effects.emplace_back(MemoryEffects::Write::get(), output(),
2579                        SideEffects::DefaultResource::get());
2580 }
2581 
2582 static LogicalResult verify(ConvOp op) {
2583   auto oType = op.output().getType().cast<MemRefType>();
2584   auto fType = op.filter().getType().cast<MemRefType>();
2585   auto iType = op.input().getType().cast<MemRefType>();
2586   if (oType.getElementType() != iType.getElementType() ||
2587       oType.getElementType() != fType.getElementType())
2588     return op.emitOpError("expects memref elemental types to match");
2589   if (oType.getRank() != iType.getRank() || oType.getRank() != fType.getRank())
2590     return op.emitOpError("expects memref ranks to match");
2591   if (auto strides = op.strides()) {
2592     if (failed(verifyStrideOrDilation(op, strides->getValue(),
2593                                       /*isStride=*/true)))
2594       return failure();
2595   }
2596   if (auto dilations = op.dilations()) {
2597     if (failed(verifyStrideOrDilation(op, dilations->getValue(),
2598                                       /*isStride=*/false)))
2599       return failure();
2600   }
2601   return success();
2602 }
2603 
2604 template <typename PoolingOp>
2605 static LogicalResult verifySingleInputPoolingOp(PoolingOp op) {
2606   auto inputType = op.input().getType().template cast<MemRefType>();
2607   auto outputType = op.output().getType().template cast<MemRefType>();
2608   if (outputType.getElementType() != inputType.getElementType())
2609     return op.emitOpError("expects memref elemental types to match");
2610 
2611   auto windowDimsType = op.windowDims().getType().template cast<MemRefType>();
2612   if (outputType.getRank() != inputType.getRank() ||
2613       outputType.getRank() != windowDimsType.getRank())
2614     return op.emitOpError("expects memref ranks to match");
2615 
2616   if (auto strides = op.strides()) {
2617     if (failed(verifyStrideOrDilation(op, strides->getValue(),
2618                                       /*isStride=*/true)))
2619       return failure();
2620   }
2621   if (auto dilations = op.dilations()) {
2622     if (failed(verifyStrideOrDilation(op, dilations->getValue(),
2623                                       /*isStride=*/false)))
2624       return failure();
2625   }
2626   return success();
2627 }
2628 
2629 #define DEFINE_POOLING_OP_GET_EFFECTS(OP_NAME)                                 \
2630   void OP_NAME::getEffects(                                                    \
2631       SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>      \
2632           &effects) {                                                          \
2633     effects.emplace_back(MemoryEffects::Read::get(), input(),                  \
2634                          SideEffects::DefaultResource::get());                 \
2635     effects.emplace_back(MemoryEffects::Write::get(), output(),                \
2636                          SideEffects::DefaultResource::get());                 \
2637   }
2638 
2639 static LogicalResult verify(PoolingMaxOp op) {
2640   return verifySingleInputPoolingOp(op);
2641 }
2642 static LogicalResult verify(PoolingMinOp op) {
2643   return verifySingleInputPoolingOp(op);
2644 }
2645 static LogicalResult verify(PoolingSumOp op) {
2646   return verifySingleInputPoolingOp(op);
2647 }
2648 
2649 DEFINE_POOLING_OP_GET_EFFECTS(PoolingMaxOp)
2650 DEFINE_POOLING_OP_GET_EFFECTS(PoolingMinOp)
2651 DEFINE_POOLING_OP_GET_EFFECTS(PoolingSumOp)
2652 
2653 #include "mlir/Dialect/Linalg/IR/LinalgNamedStructuredOps.tcgen.cpp.inc"
2654 #include "mlir/Dialect/Linalg/IR/LinalgNamedStructuredOps.yamlgen.cpp.inc"
2655 
2656 #define GET_OP_CLASSES
2657 #include "mlir/Dialect/Linalg/IR/LinalgOps.cpp.inc"
2658 
2659 #define GET_OP_CLASSES
2660 #include "mlir/Dialect/Linalg/IR/LinalgStructuredOps.cpp.inc"
2661 
2662 /// Return the dims that are `iteratorTypeName` loops in the LinalgOp `op`.
2663 /// Assumes `op` is a LinalgOp.
2664 void mlir::linalg::getDimsOfType(Operation *op, StringRef iteratorTypeName,
2665                                  SmallVectorImpl<AffineExpr> &res) {
2666   if (!cast<LinalgOp>(op).iterator_types())
2667     return;
2668 
2669   unsigned dim = 0;
2670   MLIRContext *ctx = op->getContext();
2671   for (auto tn :
2672        cast<LinalgOp>(op).iterator_types().getAsValueRange<StringAttr>()) {
2673     if (tn == iteratorTypeName)
2674       res.push_back(getAffineDimExpr(dim, ctx));
2675     ++dim;
2676   }
2677 }
2678 
2679 AffineMap mlir::linalg::extractOrIdentityMap(Optional<AffineMap> maybeMap,
2680                                              unsigned rank,
2681                                              MLIRContext *context) {
2682   if (maybeMap)
2683     return maybeMap.getValue();
2684   if (rank == 0)
2685     return AffineMap::get(context);
2686   return AffineMap::getMultiDimIdentityMap(rank, context);
2687 }
2688 
2689 SmallVector<AffineExpr, 4>
2690 mlir::linalg::makeAffineDimExprs(unsigned num, unsigned &startIdx,
2691                                  MLIRContext *context) {
2692   SmallVector<AffineExpr, 4> res;
2693   res.reserve(num);
2694   for (unsigned i = 0; i < num; ++i)
2695     res.push_back(getAffineDimExpr(startIdx++, context));
2696   return res;
2697 }
2698 
2699 template <typename PoolingOp>
2700 SmallVector<AffineExpr, 4>
2701 mlir::linalg::weightedPoolingInputIndex(PoolingOp op,
2702                                         ArrayRef<AffineExpr> outputDims,
2703                                         ArrayRef<AffineExpr> windowDims) {
2704   assert(outputDims.size() == windowDims.size());
2705   SmallVector<AffineExpr, 4> res;
2706   res.reserve(outputDims.size());
2707   for (unsigned i = 0, e = outputDims.size(); i < e; ++i) {
2708     // TODO: add a level of indirection to linalg.generic.
2709     auto expr = op.getStride(i) * outputDims[i] +
2710                 op.getDilation(i) * windowDims[i] - op.getLowPad(i);
2711     res.push_back(expr);
2712   }
2713   return res;
2714 }
2715 
2716 #define INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(OP_TYPE)                      \
2717   template SmallVector<AffineExpr, 4>                                          \
2718   mlir::linalg::weightedPoolingInputIndex<OP_TYPE>(                            \
2719       OP_TYPE op, ArrayRef<AffineExpr> outputDims,                             \
2720       ArrayRef<AffineExpr> windowDims);
2721 
2722 INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(ConvOp)
2723 INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(PoolingMaxOp)
2724 INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(PoolingMinOp)
2725 INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(PoolingSumOp)
2726 
2727 SmallVector<AffineExpr, 4> mlir::linalg::concat(ArrayRef<AffineExpr> a,
2728                                                 ArrayRef<AffineExpr> b) {
2729   auto rangeA = llvm::make_range(a.begin(), a.end());
2730   auto rangeB = llvm::make_range(b.begin(), b.end());
2731   auto concatRanges = llvm::concat<const AffineExpr>(rangeA, rangeB);
2732   return llvm::to_vector<4>(concatRanges);
2733 }
2734 
2735 static void appendMangledType(llvm::raw_string_ostream &ss, Type t) {
2736   if (auto memref = t.dyn_cast<MemRefType>()) {
2737     ss << "view";
2738     for (auto size : memref.getShape())
2739       if (size < 0)
2740         ss << "sx";
2741       else
2742         ss << size << "x";
2743     appendMangledType(ss, memref.getElementType());
2744   } else if (auto vec = t.dyn_cast<VectorType>()) {
2745     ss << "vector";
2746     llvm::interleave(
2747         vec.getShape(), [&](int64_t i) { ss << i; }, [&]() { ss << "x"; });
2748     appendMangledType(ss, vec.getElementType());
2749   } else if (t.isSignlessIntOrIndexOrFloat()) {
2750     ss << t;
2751   } else {
2752     llvm_unreachable("Invalid type for linalg library name mangling");
2753   }
2754 }
2755 
2756 std::string mlir::linalg::generateLibraryCallName(Operation *op) {
2757   assert(isa<LinalgOp>(op));
2758   std::string name(op->getName().getStringRef().str());
2759   name.reserve(128);
2760   std::replace(name.begin(), name.end(), '.', '_');
2761   llvm::raw_string_ostream ss(name);
2762   ss << "_";
2763   auto types = op->getOperandTypes();
2764   llvm::interleave(
2765       types.begin(), types.end(), [&](Type t) { appendMangledType(ss, t); },
2766       [&]() { ss << "_"; });
2767   return ss.str();
2768 }
2769 
2770 // TODO: Consider making all this boilerplate easy to autogenerate
2771 // with Tablegen. This seems a desirable property in the context of
2772 // OpInterfaces where a Linalg "named" op **isa** LinalgOp.
2773 OpFoldResult TensorExpandShapeOp::fold(ArrayRef<Attribute> operands) {
2774   return foldReshapeOp<TensorExpandShapeOp, TensorCollapseShapeOp>(*this,
2775                                                                    operands);
2776 }
2777 OpFoldResult TensorCollapseShapeOp::fold(ArrayRef<Attribute> operands) {
2778   return foldReshapeOp<TensorCollapseShapeOp, TensorExpandShapeOp>(*this,
2779                                                                    operands);
2780 }
2781 
2782 //===----------------------------------------------------------------------===//
2783 // Support for named Linalg ops defined in ods-gen.
2784 //===----------------------------------------------------------------------===//
2785 
2786 /// Generic entry point to create the block for the region of a LinalgOp.
2787 /// This is used by both named structured ops created by ods-gen and by manually
2788 /// defined C++ ops.
2789 /// This is used by both builders and parsers.
2790 /// This function creates the block in the region with arguments corresponding
2791 /// to the elemental types of `inputTypes` and `outputTypes`, which are asserted
2792 /// to be ShapedType.
2793 template <typename NamedStructuredOpType>
2794 static void
2795 fillStructuredOpRegion(OpBuilder &opBuilder, Region &region,
2796                        TypeRange inputTypes, TypeRange outputTypes,
2797                        std::function<void(unsigned, unsigned)> errorHandler) {
2798   assert(llvm::all_of(outputTypes, [](Type t) { return t.isa<ShapedType>(); }));
2799 
2800   // TODO: atm all operands go through getElementTypeOrSelf,
2801   // reconsider when we have evidence we need to.
2802   SmallVector<Type, 8> argTypes;
2803   for (auto containers : {inputTypes, outputTypes})
2804     for (auto t : containers)
2805       argTypes.push_back(getElementTypeOrSelf(t));
2806 
2807   // RAII.
2808   OpBuilder::InsertionGuard guard(opBuilder);
2809   Block *body = opBuilder.createBlock(&region, /*insertPt=*/{}, argTypes);
2810   unsigned actual = body->getNumArguments();
2811   unsigned expected = NamedStructuredOpType::getNumRegionArgs();
2812   if (expected != actual) {
2813     if (errorHandler)
2814       errorHandler(expected, actual);
2815     return;
2816   }
2817 
2818   opBuilder.setInsertionPointToStart(body);
2819   ImplicitLocOpBuilder b(opBuilder.getUnknownLoc(), opBuilder);
2820   NamedStructuredOpType::regionBuilder(b, *body);
2821 
2822   // indexing_maps is an auto-generated method.
2823 
2824   // iterator_types is an auto-generated method.
2825 }
2826 
2827 /// Generic entry point to create both the region and the block of a LinalgOp.
2828 template <typename NamedStructuredOpType>
2829 void createAndFillStructuredOpRegion(OpBuilder &opBuilder,
2830                                      OperationState &result,
2831                                      TypeRange inputTypes,
2832                                      TypeRange outputTypes) {
2833   Region &region = *result.addRegion();
2834   fillStructuredOpRegion<NamedStructuredOpType>(
2835       opBuilder, region, inputTypes, outputTypes,
2836       [&](unsigned expected, unsigned actual) {
2837         assert(expected != actual && "incorrect number of arguments");
2838       });
2839 }
2840 
2841 /// Common parsing used for both named structured ops created by ods-gen and by
2842 /// manually defined C++ ops. Does not handle regions.
2843 static ParseResult
2844 parseCommonStructuredOpParts(OpAsmParser &parser, OperationState &result,
2845                              SmallVectorImpl<Type> &inputTypes,
2846                              SmallVectorImpl<Type> &outputTypes) {
2847   llvm::SMLoc inputsOperandsLoc, outputsOperandsLoc;
2848   SmallVector<OpAsmParser::OperandType, 4> inputsOperands, outputsOperands;
2849 
2850   parser.parseOptionalAttrDict(result.attributes);
2851 
2852   if (succeeded(parser.parseOptionalKeyword("ins"))) {
2853     if (parser.parseLParen())
2854       return failure();
2855 
2856     inputsOperandsLoc = parser.getCurrentLocation();
2857     if (parser.parseOperandList(inputsOperands) ||
2858         parser.parseColonTypeList(inputTypes) || parser.parseRParen())
2859       return failure();
2860   }
2861 
2862   if (succeeded(parser.parseOptionalKeyword("outs"))) {
2863     outputsOperandsLoc = parser.getCurrentLocation();
2864     if (parser.parseLParen() || parser.parseOperandList(outputsOperands) ||
2865         parser.parseColonTypeList(outputTypes) || parser.parseRParen())
2866       return failure();
2867   }
2868 
2869   if (parser.resolveOperands(inputsOperands, inputTypes, inputsOperandsLoc,
2870                              result.operands) ||
2871       parser.resolveOperands(outputsOperands, outputTypes, outputsOperandsLoc,
2872                              result.operands))
2873     return failure();
2874 
2875   result.addAttribute("operand_segment_sizes",
2876                       parser.getBuilder().getI32VectorAttr(
2877                           {static_cast<int32_t>(inputsOperands.size()),
2878                            static_cast<int32_t>(outputsOperands.size())}));
2879   return success();
2880 }
2881 
2882 template <typename NamedStructuredOpType>
2883 static void printCommonStructuredOpParts(OpAsmPrinter &p,
2884                                          NamedStructuredOpType op) {
2885   if (!op.inputs().empty())
2886     p << " ins(" << op.inputs() << " : " << op.inputs().getTypes() << ")";
2887   if (!op.outputs().empty())
2888     p << " outs(" << op.outputs() << " : " << op.outputs().getTypes() << ")";
2889 }
2890 
2891 //===----------------------------------------------------------------------===//
2892 // Specific parsing and printing for named structured ops created by ods-gen.
2893 //===----------------------------------------------------------------------===//
2894 
2895 template <typename NamedStructuredOpType>
2896 static ParseResult
2897 parseNamedStructuredOpRegion(OpAsmParser &parser, Region &region,
2898                              TypeRange inputTypes, TypeRange outputTypes) {
2899   ParseResult res = success();
2900   OpBuilder opBuilder(parser.getBuilder().getContext());
2901   // Resolve `captures` into `capturedValues` at parse time so we can build the
2902   // region with captures.
2903   SmallVector<Value> capturedValues;
2904   fillStructuredOpRegion<NamedStructuredOpType>(
2905       opBuilder, region, inputTypes, outputTypes,
2906       [&](unsigned expected, unsigned actual) {
2907         res = parser.emitError(
2908             parser.getCurrentLocation(),
2909             llvm::formatv("[parseNamedStructuredOpRegion] ods-gen generated "
2910                           "region expects {0} args, got {1}",
2911                           expected, actual));
2912         region.front().dump();
2913       });
2914   return res;
2915 }
2916 
2917 static ParseResult
2918 parseNamedStructuredOpResults(OpAsmParser &parser,
2919                               SmallVectorImpl<Type> &resultTypes) {
2920   if (parser.parseOptionalArrowTypeList(resultTypes))
2921     return failure();
2922   return success();
2923 }
2924 
2925 template <typename NamedStructuredOpType>
2926 static ParseResult parseNamedStructuredOp(OpAsmParser &parser,
2927                                           OperationState &result) {
2928   // TODO: Enable when ods-gen supports captures.
2929   SmallVector<Type, 1> inputTypes, outputTypes;
2930   if (parseCommonStructuredOpParts(parser, result, inputTypes, outputTypes))
2931     return failure();
2932 
2933   // TODO: consider merging results parsing into region parsing.
2934   // Need to wait for declarative assembly resolution to decide.
2935   SmallVector<Type, 1> outputTensorsTypes;
2936   if (parseNamedStructuredOpResults(parser, outputTensorsTypes))
2937     return failure();
2938   result.addTypes(outputTensorsTypes);
2939 
2940   std::unique_ptr<Region> region = std::make_unique<Region>();
2941   if (parseNamedStructuredOpRegion<NamedStructuredOpType>(
2942           parser, *region, inputTypes, outputTypes))
2943     return failure();
2944   result.addRegion(std::move(region));
2945 
2946   return success();
2947 }
2948 
2949 static void printNamedStructuredOpResults(OpAsmPrinter &p,
2950                                           TypeRange resultTypes) {
2951   if (resultTypes.empty())
2952     return;
2953   p.printOptionalArrowTypeList(resultTypes);
2954 }
2955 
2956 template <typename NamedStructuredOpType>
2957 static void printNamedStructuredOp(OpAsmPrinter &p, NamedStructuredOpType op) {
2958   p.printOptionalAttrDict(
2959       op->getAttrs(),
2960       /*elidedAttrs=*/{"operand_segment_sizes",
2961                        // See generated code in mlir-linalg-yaml-gen.cpp
2962                        "linalg.memoized_indexing_maps"});
2963 
2964   // Printing is shared with generic ops, except for the region and
2965   // attributes.
2966   printCommonStructuredOpParts(p, op);
2967 
2968   // Results printing.
2969   printNamedStructuredOpResults(p, op.result_tensors().getTypes());
2970 
2971   // Region is elided.
2972 }
2973 
2974 template <typename NamedStructuredOpType>
2975 static LogicalResult verifyNamedStructuredOp(NamedStructuredOpType op) {
2976   return verifyGenericOp<NamedStructuredOpType>(op);
2977 }
2978 
2979 //===----------------------------------------------------------------------===//
2980 // Canonicalizers and Folders.
2981 //===----------------------------------------------------------------------===//
2982 
2983 namespace {
2984 struct EraseDeadLinalgOp : public OpInterfaceRewritePattern<LinalgOp> {
2985   using OpInterfaceRewritePattern<LinalgOp>::OpInterfaceRewritePattern;
2986 
2987   LogicalResult matchAndRewrite(LinalgOp op,
2988                                 PatternRewriter &rewriter) const override {
2989     for (OpOperand *opOperand : op.getInputAndOutputOperands()) {
2990       // Linalg "inputs" may be either tensor or memref type.
2991       // tensor<0xelt_type> is a convention that may not always mean
2992       // "0 iterations". Only erase in cases we see memref<...x0x...>.
2993       auto mt = opOperand->get().getType().dyn_cast<MemRefType>();
2994       if (!mt)
2995         continue;
2996       if (llvm::is_contained(op.getShape(opOperand), 0)) {
2997         rewriter.eraseOp(op);
2998         return success();
2999       }
3000     }
3001     return failure();
3002   }
3003 };
3004 
3005 struct FoldTensorCastOp : public OpInterfaceRewritePattern<LinalgOp> {
3006   using OpInterfaceRewritePattern<LinalgOp>::OpInterfaceRewritePattern;
3007 
3008   LogicalResult matchAndRewrite(LinalgOp op,
3009                                 PatternRewriter &rewriter) const override {
3010     // If no operand comes from a tensor::CastOp and can be folded then fail.
3011     bool hasTensorCastOperand =
3012         llvm::any_of(op.getInputAndOutputOperands(), [&](OpOperand *opOperand) {
3013           if (opOperand->get().isa<BlockArgument>())
3014             return false;
3015           auto castOp = opOperand->get().getDefiningOp<tensor::CastOp>();
3016           return castOp && canFoldIntoConsumerOp(castOp);
3017         });
3018     if (!hasTensorCastOperand)
3019       return failure();
3020 
3021     SmallVector<Type, 4> newResultTypes;
3022     newResultTypes.reserve(op->getNumResults());
3023     SmallVector<Value, 4> newOperands;
3024     newOperands.reserve(op->getNumOperands());
3025     // Inputs may fold.
3026     for (OpOperand *opOperand : op.getInputOperands()) {
3027       auto tensorCastOp = opOperand->get().getDefiningOp<tensor::CastOp>();
3028       newOperands.push_back(canFoldIntoConsumerOp(tensorCastOp)
3029                                 ? tensorCastOp.source()
3030                                 : opOperand->get());
3031     }
3032     // Init tensors may fold, in which case the resultType must also change.
3033     for (OpOperand *opOperand : op.getOutputOperands()) {
3034       auto tensorCastOp = opOperand->get().getDefiningOp<tensor::CastOp>();
3035       bool fold = canFoldIntoConsumerOp(tensorCastOp);
3036       newOperands.push_back(fold ? tensorCastOp.getOperand()
3037                                  : opOperand->get());
3038       newResultTypes.push_back(newOperands.back().getType());
3039     }
3040     // Clone op.
3041     Operation *newOp =
3042         op.clone(rewriter, op->getLoc(), newResultTypes, newOperands);
3043     SmallVector<Value, 4> replacements;
3044     replacements.reserve(newOp->getNumResults());
3045     for (auto result : llvm::zip(op->getResults(), newOp->getResults())) {
3046       Value oldResult = std::get<0>(result);
3047       Value newResult = std::get<1>(result);
3048       if (newResult.getType() != oldResult.getType()) {
3049         replacements.push_back(rewriter.create<tensor::CastOp>(
3050             op->getLoc(), oldResult.getType(), newResult));
3051       } else {
3052         replacements.push_back(newResult);
3053       }
3054     }
3055     rewriter.replaceOp(op, replacements);
3056 
3057     return success();
3058   }
3059 };
3060 } // namespace
3061 
3062 #define LINALGOP_FOLDERS(XXX)                                                  \
3063   LogicalResult XXX::fold(ArrayRef<Attribute>,                                 \
3064                           SmallVectorImpl<OpFoldResult> &) {                   \
3065     return foldMemRefCast(*this);                                              \
3066   }
3067 
3068 LINALGOP_FOLDERS(ConvOp)
3069 LINALGOP_FOLDERS(PoolingMaxOp)
3070 LINALGOP_FOLDERS(PoolingMinOp)
3071 LINALGOP_FOLDERS(PoolingSumOp)
3072 LINALGOP_FOLDERS(CopyOp)
3073 LINALGOP_FOLDERS(FillOp)
3074 LINALGOP_FOLDERS(GenericOp)
3075 
3076 // All named ops canonicalizers and folders are auto-generated in the
3077 // .cpp.inc.
3078 
3079 //===----------------------------------------------------------------------===//
3080 // LinalgDialect
3081 //===----------------------------------------------------------------------===//
3082 
3083 void LinalgDialect::getCanonicalizationPatterns(
3084     RewritePatternSet &results) const {
3085   results.add<EraseDeadLinalgOp, FoldTensorCastOp>(getContext());
3086 }
3087