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