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/EDSC/Intrinsics.h"
17 #include "mlir/Dialect/Linalg/IR/LinalgTypes.h"
18 #include "mlir/Dialect/StandardOps/IR/Ops.h"
19 #include "mlir/IR/AffineExprVisitor.h"
20 #include "mlir/IR/Matchers.h"
21 #include "mlir/IR/OpImplementation.h"
22 #include "mlir/IR/PatternMatch.h"
23 
24 #include "llvm/ADT/DenseMap.h"
25 #include "llvm/ADT/SetVector.h"
26 #include "llvm/ADT/SmallSet.h"
27 #include "llvm/ADT/StringSet.h"
28 #include "llvm/Support/FormatVariadic.h"
29 #include "llvm/Support/MathExtras.h"
30 #include "llvm/Support/raw_ostream.h"
31 
32 using namespace mlir;
33 using namespace mlir::linalg;
34 
35 /// Forward declarations.
36 template <typename NamedStructuredOpType>
37 static void buildNamedStructuredOpRegionAndAttributes(OpBuilder &opBuilder,
38                                                       OperationState &result,
39                                                       TypeRange inputTypes,
40                                                       TypeRange outputTypes);
41 
42 static ParseResult
43 parseCommonStructuredOpParts(OpAsmParser &parser, OperationState &result,
44                              SmallVectorImpl<Type> &inputTypes,
45                              SmallVectorImpl<Type> &outputTypes);
46 
47 template <typename NamedStructuredOpType>
48 static ParseResult
49 parseNamedStructuredOpRegion(OpAsmParser &parser, Region &region,
50                              TypeRange inputTypes, TypeRange outputTypes);
51 static ParseResult
52 parseNamedStructuredOpResults(OpAsmParser &parser,
53                               SmallVectorImpl<Type> &resultTypes);
54 
55 template <typename NamedStructuredOpType>
56 static ParseResult parseNamedStructuredOp(OpAsmParser &parser,
57                                           OperationState &result);
58 
59 template <typename NamedStructuredOpType>
60 static void printCommonStructuredOpParts(OpAsmPrinter &p,
61                                          NamedStructuredOpType op);
62 
63 static void printNamedStructuredOpResults(OpAsmPrinter &p,
64                                           TypeRange resultTypes);
65 
66 template <typename NamedStructuredOpType>
67 static void printNamedStructuredOp(OpAsmPrinter &p, NamedStructuredOpType op);
68 
69 /// This is a common class used for patterns of the form
70 /// ```
71 ///    someop(memrefcast) -> someop
72 /// ```
73 /// It folds the source of the memref_cast into the root operation directly.
74 static LogicalResult foldMemRefCast(Operation *op) {
75   bool folded = false;
76   for (OpOperand &operand : op->getOpOperands()) {
77     auto castOp = operand.get().getDefiningOp<MemRefCastOp>();
78     if (castOp && canFoldIntoConsumerOp(castOp)) {
79       operand.set(castOp.getOperand());
80       folded = true;
81     }
82   }
83   return success(folded);
84 }
85 
86 //===----------------------------------------------------------------------===//
87 // FillOp
88 //===----------------------------------------------------------------------===//
89 
90 void FillOp::build(OpBuilder &builder, OperationState &result, Value output,
91                    Value value) {
92   build(builder, result, output.getType().dyn_cast<RankedTensorType>(), output,
93         value);
94 }
95 
96 //===----------------------------------------------------------------------===//
97 // GenericOps
98 //===----------------------------------------------------------------------===//
99 void GenericOp::build(
100     OpBuilder &builder, OperationState &result, TypeRange resultTensorTypes,
101     ValueRange inputs, ValueRange outputs, ArrayRef<AffineMap> indexingMaps,
102     ArrayRef<StringRef> iteratorTypes, StringRef doc, StringRef libraryCall,
103     function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild) {
104   build(builder, result, resultTensorTypes, inputs, outputs,
105         builder.getAffineMapArrayAttr(indexingMaps),
106         builder.getStrArrayAttr(iteratorTypes),
107         doc.empty() ? StringAttr() : builder.getStringAttr(doc),
108         libraryCall.empty() ? StringAttr() : builder.getStringAttr(libraryCall),
109         ArrayAttr());
110   if (!bodyBuild)
111     return;
112 
113   SmallVector<Type, 4> blockArgTypes;
114   for (ValueRange container : {inputs, outputs})
115     for (Value v : container)
116       blockArgTypes.push_back(v.getType().cast<ShapedType>().getElementType());
117 
118   OpBuilder::InsertionGuard guard(builder);
119   auto &region = *result.regions.front();
120   Block *bodyBlock = builder.createBlock(&region, region.end(), blockArgTypes);
121   bodyBuild(builder, result.location, bodyBlock->getArguments());
122 }
123 
124 void GenericOp::build(
125     OpBuilder &builder, OperationState &result, ValueRange inputs,
126     ValueRange outputs, ArrayRef<AffineMap> indexingMaps,
127     ArrayRef<StringRef> iteratorTypes, StringRef doc, StringRef libraryCall,
128     function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild) {
129   build(builder, result, TypeRange{}, inputs, outputs, indexingMaps,
130         iteratorTypes, doc, libraryCall, bodyBuild);
131 }
132 
133 void GenericOp::build(
134     OpBuilder &builder, OperationState &result, ValueRange inputs,
135     ValueRange outputs, ArrayRef<AffineMap> indexingMaps,
136     ArrayRef<StringRef> iteratorTypes,
137     function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild) {
138   build(builder, result, inputs, outputs, indexingMaps, iteratorTypes,
139         /*doc=*/"",
140         /*libraryCall=*/"", bodyBuild);
141 }
142 
143 void GenericOp::build(
144     OpBuilder &builder, OperationState &result, TypeRange resultTensorTypes,
145     ValueRange inputs, ValueRange outputs, ArrayRef<AffineMap> indexingMaps,
146     ArrayRef<StringRef> iteratorTypes,
147     function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild) {
148   build(builder, result, resultTensorTypes, inputs, outputs, indexingMaps,
149         iteratorTypes,
150         /*doc=*/"",
151         /*libraryCall=*/"", bodyBuild);
152 }
153 void IndexedGenericOp::build(
154     OpBuilder &builder, OperationState &result, TypeRange resultTensorTypes,
155     ValueRange inputs, ValueRange outputs, ArrayRef<AffineMap> indexingMaps,
156     ArrayRef<StringRef> iteratorTypes, StringRef doc, StringRef libraryCall,
157     function_ref<void(OpBuilder &, Location, ValueRange, ValueRange)>
158         bodyBuild) {
159   build(builder, result, resultTensorTypes, inputs, outputs,
160         builder.getAffineMapArrayAttr(indexingMaps),
161         builder.getStrArrayAttr(iteratorTypes),
162         doc.empty() ? StringAttr() : builder.getStringAttr(doc),
163         libraryCall.empty() ? StringAttr() : builder.getStringAttr(libraryCall),
164         ArrayAttr());
165   if (!bodyBuild)
166     return;
167 
168   unsigned nLoops = iteratorTypes.size();
169   SmallVector<Type, 4> blockArgTypes(nLoops, builder.getIndexType());
170   for (ValueRange container : {inputs, outputs})
171     for (Value v : container)
172       blockArgTypes.push_back(v.getType().cast<ShapedType>().getElementType());
173 
174   OpBuilder::InsertionGuard guard(builder);
175   auto &region = *result.regions.front();
176   Block *bodyBlock = builder.createBlock(&region, region.end(), blockArgTypes);
177   bodyBuild(builder, result.location,
178             bodyBlock->getArguments().take_front(nLoops),
179             bodyBlock->getArguments().drop_front(nLoops));
180 }
181 
182 void IndexedGenericOp::build(
183     OpBuilder &builder, OperationState &result, ValueRange inputs,
184     ValueRange outputs, ArrayRef<AffineMap> indexingMaps,
185     ArrayRef<StringRef> iteratorTypes, StringRef doc, StringRef libraryCall,
186     function_ref<void(OpBuilder &, Location, ValueRange, ValueRange)>
187         bodyBuild) {
188   build(builder, result, TypeRange{}, inputs, outputs, indexingMaps,
189         iteratorTypes, doc, libraryCall, bodyBuild);
190 }
191 
192 void IndexedGenericOp::build(
193     OpBuilder &builder, OperationState &result, ValueRange inputs,
194     ValueRange outputs, ArrayRef<AffineMap> indexingMaps,
195     ArrayRef<StringRef> iteratorTypes,
196     function_ref<void(OpBuilder &, Location, ValueRange, ValueRange)>
197         bodyBuild) {
198   build(builder, result, inputs, outputs, indexingMaps, iteratorTypes,
199         /*doc=*/"", /*libraryCall=*/"", bodyBuild);
200 }
201 
202 void IndexedGenericOp::build(
203     OpBuilder &builder, OperationState &result, TypeRange resultTensorTypes,
204     ValueRange inputs, ValueRange outputs, ArrayRef<AffineMap> indexingMaps,
205     ArrayRef<StringRef> iteratorTypes,
206     function_ref<void(OpBuilder &, Location, ValueRange, ValueRange)>
207         bodyBuild) {
208   build(builder, result, resultTensorTypes, inputs, outputs, indexingMaps,
209         iteratorTypes,
210         /*doc=*/"",
211         /*libraryCall=*/"", bodyBuild);
212 }
213 
214 template <typename GenericOpType>
215 static void printGenericOp(OpAsmPrinter &p, GenericOpType op) {
216   p << op.getOperationName() << " ";
217 
218   // Print extra attributes.
219   auto genericAttrNames = op.linalgTraitAttrNames();
220 
221   llvm::StringSet<> genericAttrNamesSet;
222   genericAttrNamesSet.insert(genericAttrNames.begin(), genericAttrNames.end());
223   SmallVector<NamedAttribute, 8> genericAttrs;
224   for (auto attr : op.getAttrs())
225     if (genericAttrNamesSet.count(attr.first.strref()) > 0)
226       genericAttrs.push_back(attr);
227   if (!genericAttrs.empty()) {
228     auto genericDictAttr = DictionaryAttr::get(op.getContext(), genericAttrs);
229     p << genericDictAttr;
230   }
231 
232   // Printing is shared with named ops, except for the region and attributes
233   printCommonStructuredOpParts(p, op);
234 
235   genericAttrNames.push_back("operand_segment_sizes");
236   genericAttrNamesSet.insert(genericAttrNames.back());
237 
238   bool hasExtraAttrs = false;
239   for (NamedAttribute n : op.getAttrs()) {
240     if ((hasExtraAttrs = !genericAttrNamesSet.contains(n.first.strref())))
241       break;
242   }
243   if (hasExtraAttrs) {
244     p << " attrs = ";
245     p.printOptionalAttrDict(op.getAttrs(), /*elidedAttrs=*/genericAttrNames);
246   }
247 
248   // Print region.
249   if (!op.region().empty())
250     p.printRegion(op.region());
251 
252   // Print results.
253   printNamedStructuredOpResults(p, op.result_tensors().getTypes());
254 }
255 
256 static void print(OpAsmPrinter &p, GenericOp op) { printGenericOp(p, op); }
257 
258 static void print(OpAsmPrinter &p, IndexedGenericOp op) {
259   printGenericOp(p, op);
260 }
261 
262 static ParseResult parseGenericOp(OpAsmParser &parser, OperationState &result) {
263   DictionaryAttr dictAttr;
264   // Parse the core linalg traits that must check into a dictAttr.
265   // The name is unimportant as we will overwrite result.attributes.
266   // The core linalg traits must contain the information necessary to pass the
267   // verifier.
268   if (parser.parseAttribute(dictAttr, "_", result.attributes))
269     return failure();
270   result.attributes.assign(dictAttr.getValue().begin(),
271                            dictAttr.getValue().end());
272 
273   // Parsing is shared with named ops, except for the region.
274   SmallVector<Type, 1> inputTypes, outputTypes;
275   if (parseCommonStructuredOpParts(parser, result, inputTypes, outputTypes))
276     return failure();
277 
278   // Optional attributes may be added.
279   if (succeeded(parser.parseOptionalKeyword("attrs")))
280     if (failed(parser.parseEqual()) ||
281         failed(parser.parseOptionalAttrDict(result.attributes)))
282       return failure();
283 
284   SmallVector<OpAsmParser::OperandType, 8> regionOperands;
285   std::unique_ptr<Region> region = std::make_unique<Region>();
286   SmallVector<Type, 8> operandTypes, regionTypes;
287   if (parser.parseRegion(*region, regionOperands, regionTypes))
288     return failure();
289   result.addRegion(std::move(region));
290 
291   // Generic ops may specify that a subset of its outputs are tensors. Such
292   // outputs are specified in the result type.
293   // TODO: may need to move output parsing before region parsing.
294   // Need to wait for declarative assembly resolution to decide.
295   SmallVector<Type, 1> outputTensorsTypes;
296   if (parseNamedStructuredOpResults(parser, outputTensorsTypes))
297     return failure();
298   result.addTypes(outputTensorsTypes);
299 
300   return success();
301 }
302 
303 static void getGenericEffectsImpl(
304     SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
305         &effects,
306     ValueRange results, ValueRange inputBuffers, ValueRange outputs) {
307   for (Value value : results) {
308     effects.emplace_back(MemoryEffects::Allocate::get(), value,
309                          SideEffects::DefaultResource::get());
310   }
311   for (Value value : inputBuffers) {
312     effects.emplace_back(MemoryEffects::Read::get(), value,
313                          SideEffects::DefaultResource::get());
314   }
315   for (Value value : outputs) {
316     effects.emplace_back(MemoryEffects::Read::get(), value,
317                          SideEffects::DefaultResource::get());
318     effects.emplace_back(MemoryEffects::Write::get(), value,
319                          SideEffects::DefaultResource::get());
320   }
321 }
322 
323 void GenericOp::getEffects(
324     SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
325         &effects) {
326   getGenericEffectsImpl(effects, getOperation()->getResults(),
327                         getInputBuffers(), getOutputBuffers());
328 }
329 
330 void IndexedGenericOp::getEffects(
331     SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
332         &effects) {
333   getGenericEffectsImpl(effects, getOperation()->getResults(),
334                         getInputBuffers(), getOutputBuffers());
335 }
336 
337 namespace {
338 
339 template <typename GenericOpType>
340 struct AnnotationsVerifier {
341   static LogicalResult verify(GenericOpType op) { return success(); }
342 };
343 
344 template <>
345 LogicalResult AnnotationsVerifier<GenericOp>::verify(GenericOp op) {
346   ArrayAttr sparseAttr = op.sparseAttr();
347   if (!sparseAttr)
348     return success();
349   // Verify consistency of sparse annotations.
350   if (!op.hasTensorSemantics())
351     return op.emitOpError("expected sparse annotations on tensors only");
352   if (op.getNumOutputs() != 1)
353     return op.emitOpError("expected single output tensor");
354   unsigned numTensors = op.getNumShapedOperands();
355   if (sparseAttr.size() != numTensors)
356     return op.emitOpError("expected one sparse annotation for each tensor");
357   for (unsigned t = 0; t < numTensors; t++) {
358     auto dimAttr = sparseAttr[t].dyn_cast_or_null<ArrayAttr>();
359     if (!dimAttr)
360       return op.emitOpError("expected sparse annotation array for tensor ")
361              << t;
362     unsigned rank = op.getShapedType(t).getRank();
363     if (dimAttr.size() != rank)
364       return op.emitOpError("expected sparse annotation with rank ")
365              << rank << " for tensor " << t;
366     // Per-dimension annotations for each tensor consist of only "D" or "S".
367     for (unsigned d = 0; d < rank; d++) {
368       if (isDenseDim(dimAttr[d])) {
369         continue;
370       } else if (isSparseDim(dimAttr[d])) {
371         if (t == numTensors - 1)
372           return op.emitOpError("sparse output tensors not supported (yet)");
373         continue;
374       }
375       return op.emitOpError("expected sparse annotation at position ")
376              << d << " for tensor " << t;
377     }
378   }
379   return success();
380 }
381 
382 } // namespace
383 
384 template <typename GenericOpType>
385 static LogicalResult verifyGenericOp(GenericOpType op) {
386   if (failed(AnnotationsVerifier<GenericOpType>::verify(op)))
387     return failure();
388 
389   return success();
390 }
391 
392 static LogicalResult verify(GenericOp op) { return verifyGenericOp(op); }
393 
394 static LogicalResult verify(IndexedGenericOp op) { return verifyGenericOp(op); }
395 
396 //===----------------------------------------------------------------------===//
397 // InitTensorOp
398 //===----------------------------------------------------------------------===//
399 
400 
401 static LogicalResult verify(InitTensorOp op) {
402   RankedTensorType resultType = op.getType();
403   SmallVector<int64_t, 4> staticSizes = llvm::to_vector<4>(llvm::map_range(
404       op.static_sizes().cast<ArrayAttr>(),
405       [](Attribute a) -> int64_t { return a.cast<IntegerAttr>().getInt(); }));
406 
407   if (failed(verifyListOfOperandsOrIntegers(op, "sizes", resultType.getRank(),
408                                             op.static_sizes(), op.sizes(),
409                                             ShapedType::isDynamic)))
410     return failure();
411 
412   if (op.static_sizes().size() != static_cast<unsigned>(resultType.getRank()))
413     return op->emitError("expected ")
414            << resultType.getRank() << " sizes values";
415 
416   Type expectedType =
417       InitTensorOp::inferResultType(staticSizes, resultType.getElementType());
418   if (resultType != expectedType) {
419     return op.emitError("specified type ")
420            << resultType << " does not match the inferred type "
421            << expectedType;
422   }
423   return success();
424 }
425 
426 Type InitTensorOp::inferResultType(ArrayRef<int64_t> staticSizes,
427                                    Type elementType) {
428   return RankedTensorType::get(staticSizes, elementType);
429 }
430 
431 namespace {
432 /// Change the type of the result of a `linalg.init_tensor` by making the result
433 /// type statically sized along dimension that in the original operation where
434 /// defined as dynamic, but the size was defined using a `constant` op. For
435 /// example
436 ///
437 ///  %c5 = constant 5: index
438 ///  %0 = linalg.init_tensor [%arg0, %c5] : tensor<?x?xf32>
439 ///
440 ///  to
441 ///
442 ///  %0 = linalg.init_tensor [%arg0, 5] : tensor<?x5xf32>
443 struct ReplaceStaticShapeDims : OpRewritePattern<InitTensorOp> {
444   using OpRewritePattern<InitTensorOp>::OpRewritePattern;
445 
446   LogicalResult matchAndRewrite(InitTensorOp op,
447                                 PatternRewriter &rewriter) const override {
448     SmallVector<Value, 4> dynamicSizes;
449     SmallVector<int64_t, 4> staticSizes;
450     for (unsigned i = 0, e = op.getType().getRank(); i != e; ++i) {
451       // If the size is already static, nothing to do.
452       if (!op.isDynamicSize(i)) {
453         staticSizes.push_back(op.getStaticSize(i));
454         continue;
455       }
456 
457       // If the size is dynamic but defined using a `constant` op, get the
458       // constant value to find the static size to use.
459       unsigned operandNum = op.getIndexOfDynamicSize(i);
460       Value sizeOperand = op.getOperand(operandNum);
461       if (auto constantIndexOp = sizeOperand.getDefiningOp<ConstantIndexOp>()) {
462         staticSizes.push_back(constantIndexOp.getValue());
463         continue;
464       }
465 
466       // Fallback case. Keep the size dynamic.
467       dynamicSizes.push_back(sizeOperand);
468       staticSizes.push_back(ShapedType::kDynamicSize);
469     }
470     RankedTensorType newType =
471         RankedTensorType::get(staticSizes, op.getType().getElementType());
472     if (newType == op.getType())
473       return failure();
474     auto newOp =
475         rewriter.create<InitTensorOp>(op.getLoc(), newType, dynamicSizes,
476                                       rewriter.getI64ArrayAttr(staticSizes));
477     rewriter.replaceOpWithNewOp<tensor::CastOp>(op, op.getType(), newOp);
478     return success();
479   }
480 };
481 
482 /// Canonicalize a `linalg.init_tensor` -> `dim` pattern by replacing the `dim`
483 /// with
484 /// - A constant value if the size is static along the dimension.
485 /// - The dynamic value that defines the size of the result of
486 ///   `linalg.init_tensor` op.
487 struct ReplaceDimOfInitTensorOp : public OpRewritePattern<DimOp> {
488   using OpRewritePattern<DimOp>::OpRewritePattern;
489 
490   LogicalResult matchAndRewrite(DimOp dimOp,
491                                 PatternRewriter &rewriter) const override {
492     auto initTensorOp = dimOp.memrefOrTensor().getDefiningOp<InitTensorOp>();
493     if (!initTensorOp)
494       return failure();
495     auto dimIndex = dimOp.index().getDefiningOp<ConstantIndexOp>();
496     if (!dimIndex)
497       return failure();
498     int64_t index = dimIndex.getValue();
499     if (!initTensorOp.isDynamicSize(index)) {
500       rewriter.replaceOpWithNewOp<ConstantIndexOp>(
501           dimOp, initTensorOp.getStaticSize(index));
502     } else {
503       rewriter.replaceOp(dimOp, initTensorOp.getDynamicSize(index));
504     }
505     return success();
506   }
507 };
508 } // namespace
509 
510 static Value getCollapsedInitTensor(OpBuilder &builder,
511                                     TensorReshapeOp reshapeOp) {
512   Location loc = reshapeOp.getLoc();
513   SmallVector<Value, 4> dynamicShapes;
514   SmallVector<int64_t, 4> staticShapes;
515   auto reassociation = reshapeOp.getReassociationMaps();
516   Value src = reshapeOp.src();
517   RankedTensorType srcType = reshapeOp.getSrcType();
518   ArrayRef<int64_t> srcShape = srcType.getShape();
519   for (auto map : reassociation) {
520     Value linearizedDynamicDim = nullptr;
521     int64_t linearizedStaticDim = 1;
522     for (unsigned i : llvm::map_range(map.getResults(), [](AffineExpr e) {
523            return e.cast<AffineDimExpr>().getPosition();
524          })) {
525       if (ShapedType::isDynamic(srcShape[i])) {
526         Value shapeVal = builder.create<DimOp>(loc, src, i);
527         if (linearizedDynamicDim) {
528           linearizedDynamicDim =
529               builder.create<MulIOp>(loc, linearizedDynamicDim, shapeVal);
530         } else {
531           linearizedDynamicDim = shapeVal;
532         }
533       } else {
534         linearizedStaticDim *= srcShape[i];
535       }
536     }
537     if (linearizedDynamicDim) {
538       if (linearizedStaticDim != 1) {
539         linearizedDynamicDim = builder.create<MulIOp>(
540             loc, linearizedDynamicDim,
541             builder.create<ConstantIndexOp>(loc, linearizedStaticDim));
542       }
543       dynamicShapes.push_back(linearizedDynamicDim);
544       staticShapes.push_back(ShapedType::kDynamicSize);
545     } else {
546       staticShapes.push_back(linearizedStaticDim);
547     }
548   }
549   return builder.create<InitTensorOp>(loc, dynamicShapes, staticShapes,
550                                       srcType.getElementType());
551 }
552 
553 static Value getExpandedInitTensor(OpBuilder &builder,
554                                    TensorReshapeOp reshapeOp) {
555   SmallVector<Value, 4> dynamicShapes;
556   SmallVector<int64_t, 4> staticShapes;
557   auto reassociation = reshapeOp.getReassociationMaps();
558   Value src = reshapeOp.src();
559   RankedTensorType srcType = reshapeOp.getSrcType();
560   ArrayRef<int64_t> srcShape = srcType.getShape();
561   ArrayRef<int64_t> dstShape = reshapeOp.getResultType().getShape();
562   Location loc = reshapeOp.getLoc();
563   for (auto map : enumerate(reassociation)) {
564     int64_t linearizedStaticDim = 1;
565     bool hasDynamic = false;
566     for (unsigned i :
567          llvm::map_range(map.value().getResults(), [](AffineExpr e) {
568            return e.cast<AffineDimExpr>().getPosition();
569          })) {
570       if (ShapedType::isDynamic(dstShape[i])) {
571         // Only one of the dimensions of the expanded shape should be dynamic.
572         if (hasDynamic)
573           return nullptr;
574         hasDynamic = true;
575         staticShapes.push_back(ShapedType::kDynamicSize);
576         continue;
577       }
578       staticShapes.push_back(dstShape[i]);
579       linearizedStaticDim *= dstShape[i];
580     }
581     if (hasDynamic) {
582       // If the expanded dimensions has a dynamic shape, the src shape must be
583       // dynamic as well.
584       if (!ShapedType::isDynamic(srcShape[map.index()]))
585         return nullptr;
586       Value dynamicDim = builder.create<DimOp>(loc, src, map.index());
587       if (linearizedStaticDim != 1) {
588         dynamicDim = builder.create<UnsignedDivIOp>(
589             loc, dynamicDim,
590             builder.create<ConstantIndexOp>(loc, linearizedStaticDim));
591       }
592       dynamicShapes.push_back(dynamicDim);
593     }
594   }
595   return builder.create<InitTensorOp>(loc, dynamicShapes, staticShapes,
596                                       srcType.getElementType());
597 }
598 
599 namespace {
600 /// Since `init_tensor` operation creates a tensor needed only for its shape, a
601 /// subtensor of this is also needed only for its shape. The result can be
602 /// replaced by a new init_tensor operation of the same size as the subtensor
603 /// op.
604 struct FoldInitTensorWithSubTensorOp : public OpRewritePattern<SubTensorOp> {
605   using OpRewritePattern<SubTensorOp>::OpRewritePattern;
606 
607   LogicalResult matchAndRewrite(SubTensorOp subtensorOp,
608                                 PatternRewriter &rewriter) const override {
609     if (!subtensorOp.source().getDefiningOp<linalg::InitTensorOp>())
610       return failure();
611     rewriter.replaceOpWithNewOp<linalg::InitTensorOp>(
612         subtensorOp, subtensorOp.sizes(),
613         llvm::to_vector<4>(llvm::map_range(
614             subtensorOp.static_sizes(),
615             [](Attribute attr) { return attr.cast<IntegerAttr>().getInt(); })),
616         subtensorOp.getSourceType().getElementType());
617     return success();
618   }
619 };
620 
621 struct FoldInitTensorWithTensorReshapeOp
622     : public OpRewritePattern<TensorReshapeOp> {
623   using OpRewritePattern<TensorReshapeOp>::OpRewritePattern;
624 
625   LogicalResult matchAndRewrite(TensorReshapeOp reshapeOp,
626                                 PatternRewriter &rewriter) const override {
627     if (!reshapeOp.src().getDefiningOp<InitTensorOp>())
628       return failure();
629     RankedTensorType collapsedType = reshapeOp.getSrcType();
630     RankedTensorType expandedType = reshapeOp.getResultType();
631     bool isCollapsed = expandedType.getRank() < collapsedType.getRank();
632     if (isCollapsed)
633       std::swap(collapsedType, expandedType);
634     Value initTensorOp = isCollapsed
635                              ? getCollapsedInitTensor(rewriter, reshapeOp)
636                              : getExpandedInitTensor(rewriter, reshapeOp);
637     if (!initTensorOp)
638       return failure();
639     rewriter.replaceOp(reshapeOp, initTensorOp);
640     return success();
641   }
642 };
643 } // namespace
644 
645 void InitTensorOp::getCanonicalizationPatterns(
646     OwningRewritePatternList &results, MLIRContext *context) {
647   results
648       .insert<FoldInitTensorWithSubTensorOp, FoldInitTensorWithTensorReshapeOp,
649               ReplaceDimOfInitTensorOp, ReplaceStaticShapeDims>(context);
650 }
651 
652 //===----------------------------------------------------------------------===//
653 // PadTensorOp
654 //===----------------------------------------------------------------------===//
655 
656 /// Extract int64_t values from the assumed ArrayAttr of IntegerAttr.
657 static SmallVector<int64_t, 4> extractFromI64ArrayAttr(Attribute attr) {
658   return llvm::to_vector<4>(
659       llvm::map_range(attr.cast<ArrayAttr>(), [](Attribute a) -> int64_t {
660         return a.cast<IntegerAttr>().getInt();
661       }));
662 }
663 
664 static LogicalResult verify(PadTensorOp op) {
665   auto sourceType = op.source().getType().cast<RankedTensorType>();
666   auto resultType = op.result().getType().cast<RankedTensorType>();
667   auto expectedType = PadTensorOp::inferResultType(
668       sourceType, extractFromI64ArrayAttr(op.static_low()),
669       extractFromI64ArrayAttr(op.static_high()));
670   for (int i = 0, e = sourceType.getRank(); i < e; ++i) {
671     if (resultType.getDimSize(i) == expectedType.getDimSize(i))
672       continue;
673     if (expectedType.isDynamicDim(i))
674       continue;
675     return op.emitError("specified type ")
676            << resultType << " does not match the inferred type "
677            << expectedType;
678   }
679 
680   auto &region = op.region();
681   unsigned rank = resultType.getRank();
682   Block &block = region.front();
683   if (block.getNumArguments() != rank)
684     return op.emitError("expected the block to have ") << rank << " arguments";
685 
686   // Note: the number and type of yield values are checked in the YieldOp.
687   for (auto en : llvm::enumerate(block.getArgumentTypes())) {
688     if (!en.value().isIndex())
689       return op.emitOpError("expected block argument ")
690              << (en.index() + 1) << " to be an index";
691   }
692 
693   return success();
694 }
695 
696 RankedTensorType PadTensorOp::inferResultType(RankedTensorType sourceType,
697                                               ArrayRef<int64_t> staticLow,
698                                               ArrayRef<int64_t> staticHigh) {
699   unsigned rank = sourceType.getRank();
700   assert(staticLow.size() == rank && "unexpected staticLow size mismatch");
701   assert(staticHigh.size() == rank && "unexpected staticHigh size mismatch");
702 
703   SmallVector<int64_t, 4> resultShape;
704   for (auto i : llvm::seq<unsigned>(0, rank)) {
705     if (sourceType.isDynamicDim(i) ||
706         staticLow[i] == ShapedType::kDynamicSize ||
707         staticHigh[i] == ShapedType::kDynamicSize) {
708       resultShape.push_back(ShapedType::kDynamicSize);
709     } else {
710       int64_t size = sourceType.getDimSize(i) + staticLow[i] + staticHigh[i];
711       resultShape.push_back(size);
712     }
713   }
714 
715   return RankedTensorType::get(resultShape, sourceType.getElementType());
716 }
717 
718 /// Helper function to dispatch an OpFoldResult into either the `dynamicVec` if
719 /// it is a Value or into `staticVec` if it is an IntegerAttr.
720 /// In the case of a Value, a copy of the `sentinel` value is also pushed to
721 /// `staticVec`. This is useful to extract mixed static and dynamic entries that
722 /// come from an AttrSizedOperandSegments trait.
723 static void dispatchIndexOpFoldResult(OpFoldResult ofr,
724                                       SmallVectorImpl<Value> &dynamicVec,
725                                       SmallVectorImpl<int64_t> &staticVec,
726                                       int64_t sentinel) {
727   if (auto v = ofr.dyn_cast<Value>()) {
728     dynamicVec.push_back(v);
729     staticVec.push_back(sentinel);
730     return;
731   }
732   APInt apInt = ofr.dyn_cast<Attribute>().cast<IntegerAttr>().getValue();
733   staticVec.push_back(apInt.getSExtValue());
734 }
735 
736 void PadTensorOp::build(OpBuilder &b, OperationState &result, Value source,
737                         ArrayRef<int64_t> staticLow,
738                         ArrayRef<int64_t> staticHigh, ValueRange low,
739                         ValueRange high, ArrayRef<NamedAttribute> attrs) {
740   auto sourceType = source.getType().cast<RankedTensorType>();
741   auto resultType = inferResultType(sourceType, staticLow, staticHigh);
742   build(b, result, resultType, source, low, high, b.getI64ArrayAttr(staticLow),
743         b.getI64ArrayAttr(staticHigh));
744   result.addAttributes(attrs);
745 }
746 
747 void PadTensorOp::build(OpBuilder &b, OperationState &result, Value source,
748                         ValueRange low, ValueRange high,
749                         ArrayRef<NamedAttribute> attrs) {
750   auto sourceType = source.getType().cast<RankedTensorType>();
751   unsigned rank = sourceType.getRank();
752   SmallVector<int64_t, 4> staticVector(ShapedType::kDynamicSize, rank);
753   build(b, result, source, staticVector, staticVector, low, high, attrs);
754 }
755 
756 void PadTensorOp::build(OpBuilder &b, OperationState &result, Type resultType,
757                         Value source, ArrayRef<OpFoldResult> low,
758                         ArrayRef<OpFoldResult> high,
759                         ArrayRef<NamedAttribute> attrs) {
760   assert(resultType.isa<RankedTensorType>());
761   auto sourceType = source.getType().cast<RankedTensorType>();
762   unsigned rank = sourceType.getRank();
763   SmallVector<Value, 4> dynamicLow, dynamicHigh;
764   SmallVector<int64_t, 4> staticLow, staticHigh;
765   for (unsigned i = 0; i < rank; ++i) {
766     // staticLow and staticHigh have full information of the padding config.
767     // This will grow staticLow and staticHigh with 1 value. If the config is
768     // dynamic (ie not a constant), dynamicLow and dynamicHigh will grow with 1
769     // value as well.
770     dispatchIndexOpFoldResult(low[i], dynamicLow, staticLow,
771                               ShapedType::kDynamicSize);
772     dispatchIndexOpFoldResult(high[i], dynamicHigh, staticHigh,
773                               ShapedType::kDynamicSize);
774   }
775   if (!resultType) {
776     resultType =
777         PadTensorOp::inferResultType(sourceType, staticLow, staticHigh);
778   }
779   build(b, result, resultType, source, dynamicLow, dynamicHigh,
780         b.getI64ArrayAttr(staticLow), b.getI64ArrayAttr(staticHigh));
781 }
782 
783 PadTensorOp PadTensorOp::createPadScalarOp(Type type, Value source, Value pad,
784                                            ArrayRef<OpFoldResult> low,
785                                            ArrayRef<OpFoldResult> high,
786                                            Location loc, OpBuilder &builder) {
787   auto padTensorOp =
788       builder.create<linalg::PadTensorOp>(loc, type, source, low, high);
789   int rank = padTensorOp.getResultType().getRank();
790   SmallVector<Type, 4> blockArgTypes;
791   blockArgTypes.assign(rank, builder.getIndexType());
792   auto &region = padTensorOp.region();
793   // `builder.createBlock` changes the insertion point within the block. Create
794   // a guard to reset the insertion point of the builder after it is destroyed.
795   OpBuilder::InsertionGuard guard(builder);
796   builder.createBlock(&region, region.end(), blockArgTypes);
797   builder.create<linalg::YieldOp>(loc, pad);
798   return padTensorOp;
799 }
800 
801 PadTensorOp PadTensorOp::createPadHighOp(Type type, Value source, Value pad,
802                                          Location loc, OpBuilder &builder) {
803   SmallVector<OpFoldResult, 4> low, high;
804   auto rankedTensorType = type.cast<RankedTensorType>();
805   assert(rankedTensorType.hasStaticShape());
806   int rank = rankedTensorType.getRank();
807   for (int i = 0; i < rank; ++i) {
808     auto dimOp = builder.createOrFold<DimOp>(loc, source, i);
809     auto resultDimSize = builder.createOrFold<ConstantIndexOp>(
810         loc, rankedTensorType.getDimSize(i));
811     auto highValue = builder.createOrFold<SubIOp>(loc, resultDimSize, dimOp);
812     high.push_back(highValue);
813     low.push_back(builder.createOrFold<ConstantIndexOp>(loc, 0));
814   }
815   return PadTensorOp::createPadScalarOp(type, source, pad, low, high, loc,
816                                         builder);
817 }
818 
819 //===----------------------------------------------------------------------===//
820 // ReshapeOp
821 //===----------------------------------------------------------------------===//
822 
823 /// Collapse reassociation maps that are used in pair of reshape ops where one
824 /// is a producer and other is the consumer. Only valid to use this method when
825 /// both the producer and consumer are collapsing dimensions or both are
826 /// expanding dimensions.
827 ///
828 /// For example,
829 ///   mapsProducer = [affine_map<(d0, d1, d2, d3, d4) -> (d0, d1)>,
830 ///                   affine_map<(d0, d1, d2, d3, d4) -> (d2)>,
831 ///                   affine_map<(d0, d1, d2, d3, d4) -> (d3, d4)>]
832 ///   mapsConsumer = [affine_map<(d0, d1, d2) -> (d0, d1)>,
833 ///                   affine_map<(d0, d1, d2) -> (d2)>]
834 ///
835 /// is folded into
836 ///
837 ///   result = [affine_map<(d0, d1, d2, d3, d4) -> (d0, d1, d2)>,
838 ///             affine_map<(d0, d1, d2, d3, d4) -> (d3, d4)>]
839 static ArrayAttr collapseReassociationMaps(ArrayRef<AffineMap> mapsProducer,
840                                            ArrayRef<AffineMap> mapsConsumer,
841                                            MLIRContext *context) {
842   // Handle the corner case of the result being a rank 0 shaped type. Return an
843   // emtpy ArrayAttr.
844   if (mapsConsumer.empty() && !mapsProducer.empty())
845     return ArrayAttr::get(context, ArrayRef<Attribute>());
846   if (mapsProducer.empty() || mapsConsumer.empty() ||
847       mapsProducer[0].getNumDims() < mapsConsumer[0].getNumDims() ||
848       mapsProducer.size() != mapsConsumer[0].getNumDims())
849     return nullptr;
850   unsigned numLhsDims = mapsProducer[0].getNumDims();
851   unsigned currDim = 0;
852   SmallVector<AffineExpr, 4> reassociations;
853   SmallVector<Attribute, 4> reassociationMaps;
854   for (AffineMap rhs : mapsConsumer) {
855     for (AffineExpr rhsExpr : rhs.getResults()) {
856       AffineDimExpr dimExpr = rhsExpr.cast<AffineDimExpr>();
857       for (int i = 0, e = mapsProducer[dimExpr.getPosition()].getNumResults();
858            i < e; ++i) {
859         reassociations.push_back(getAffineDimExpr(currDim++, context));
860       }
861     }
862     reassociationMaps.push_back(AffineMapAttr::get(AffineMap::get(
863         numLhsDims, /*numSymbols =*/0, reassociations, context)));
864     reassociations.clear();
865   }
866   return ArrayAttr::get(context, reassociationMaps);
867 }
868 
869 namespace {
870 /// Pattern to collapse producer/consumer reshape ops that are both collapsing
871 /// dimensions or are both expanding dimensions.
872 template <typename ReshapeOpTy>
873 struct CollapseReshapeOps : public OpRewritePattern<ReshapeOpTy> {
874   using OpRewritePattern<ReshapeOpTy>::OpRewritePattern;
875   LogicalResult matchAndRewrite(ReshapeOpTy reshapeOp,
876                                 PatternRewriter &rewriter) const override {
877     auto srcReshapeOp = reshapeOp.src().template getDefiningOp<ReshapeOpTy>();
878     if (!srcReshapeOp)
879       return failure();
880 
881     auto areReshapeOpsFoldable = [](ShapedType largerType,
882                                     ShapedType intermediateType,
883                                     ShapedType smallerType) -> bool {
884       return largerType.getRank() > intermediateType.getRank() &&
885              intermediateType.getRank() > smallerType.getRank();
886     };
887     // Check if producer and consumer are both expanding dims.
888     if (areReshapeOpsFoldable(reshapeOp.getResultType(), reshapeOp.getSrcType(),
889                               srcReshapeOp.getSrcType())) {
890       rewriter.replaceOpWithNewOp<ReshapeOpTy>(
891           reshapeOp, reshapeOp.getResultType(), srcReshapeOp.src(),
892           collapseReassociationMaps(reshapeOp.getReassociationMaps(),
893                                     srcReshapeOp.getReassociationMaps(),
894                                     rewriter.getContext()));
895       return success();
896     }
897     // Check if producer and consumer are both collapsing dims.
898     if (areReshapeOpsFoldable(srcReshapeOp.getSrcType(), reshapeOp.getSrcType(),
899                               reshapeOp.getResultType())) {
900       rewriter.replaceOpWithNewOp<ReshapeOpTy>(
901           reshapeOp, reshapeOp.getResultType(), srcReshapeOp.src(),
902           collapseReassociationMaps(srcReshapeOp.getReassociationMaps(),
903                                     reshapeOp.getReassociationMaps(),
904                                     rewriter.getContext()));
905       return success();
906     }
907     return failure();
908   }
909 };
910 } // namespace
911 
912 template <typename ReshapeOpTy>
913 static OpFoldResult foldReshapeOp(ReshapeOpTy reshapeOp,
914                                   ArrayRef<Attribute> operands) {
915   // Fold producer-consumer reshape ops that where the operand type of the
916   // producer is same as the return type of the consumer.
917   ReshapeOpTy reshapeSrcOp =
918       reshapeOp.src().template getDefiningOp<ReshapeOpTy>();
919   if (reshapeSrcOp && reshapeSrcOp.getSrcType() == reshapeOp.getResultType())
920     return reshapeSrcOp.src();
921   // Reshape of a constant can be replaced with a new constant.
922   if (auto elements = operands.front().dyn_cast_or_null<DenseElementsAttr>()) {
923     return elements.reshape(
924         reshapeOp.getResult().getType().template cast<ShapedType>());
925   }
926   return nullptr;
927 }
928 
929 /// Return true if the reassociation specification is valid, false otherwise.
930 /// When false, the `invalidIndex` integer pointer is optionally filled with the
931 /// index of the offending reassociation map.
932 static bool isReassociationValid(ArrayRef<AffineMap> reassociation,
933                                  int *invalidIndex = nullptr) {
934   if (reassociation.empty())
935     return true;
936   unsigned nDims = reassociation[0].getNumDims();
937   unsigned nextExpectedDim = 0;
938   for (auto it : llvm::enumerate(reassociation)) {
939     auto m = it.value();
940     if (m.getNumDims() != nDims || m.getNumSymbols() != 0) {
941       if (invalidIndex)
942         *invalidIndex = it.index();
943       return false;
944     }
945     for (auto e : m.getResults()) {
946       auto d = e.dyn_cast<AffineDimExpr>();
947       if (!d || d.getPosition() != nextExpectedDim++) {
948         if (invalidIndex)
949           *invalidIndex = it.index();
950         return false;
951       }
952     }
953   }
954   if (nextExpectedDim != nDims) {
955     if (invalidIndex)
956       *invalidIndex = reassociation.size() - 1;
957     return false;
958   }
959   return true;
960 }
961 
962 /// Detect whether memref dims [dim, dim + extent) can be reshaped without
963 /// copies.
964 static bool isReshapableDimBand(unsigned dim, unsigned extent,
965                                 ArrayRef<int64_t> sizes,
966                                 ArrayRef<AffineExpr> strides) {
967   assert(sizes.size() == strides.size() && "mismatched ranks");
968   // off by 1 indexing to avoid out of bounds
969   //                       V
970   for (auto idx = dim, e = dim + extent; idx + 1 < e; ++idx) {
971     // Only bands of static shapes are reshapable. This is due to the fact that
972     // there is no relation between dynamic sizes and dynamic strides: we do not
973     // have enough information to know whether a "-1" size corresponds to the
974     // proper symbol in the AffineExpr of a stride.
975     if (ShapedType::isDynamic(sizes[dim + 1]))
976       return false;
977     // TODO: Refine this by passing the proper nDims and nSymbols so we can
978     // simplify on the fly and catch more reshapable cases.
979     if (strides[idx] != strides[idx + 1] * sizes[idx + 1])
980       return false;
981   }
982   return true;
983 }
984 
985 /// Compute the MemRefType obtained by applying the `reassociation` (which is
986 /// expected to be valid) to `type`.
987 /// If `type` is Contiguous MemRefType, this always produce a contiguous
988 /// MemRefType.
989 static MemRefType
990 computeReshapeCollapsedType(MemRefType type,
991                             ArrayRef<AffineMap> reassociation) {
992   auto sizes = type.getShape();
993   AffineExpr offset;
994   SmallVector<AffineExpr, 4> strides;
995   auto status = getStridesAndOffset(type, strides, offset);
996   (void)status;
997   assert(succeeded(status) && "expected strided memref");
998 
999   SmallVector<int64_t, 4> newSizes;
1000   newSizes.reserve(reassociation.size());
1001   SmallVector<AffineExpr, 4> newStrides;
1002   newStrides.reserve(reassociation.size());
1003 
1004   // Use the fact that reassociation is valid to simplify the logic: only use
1005   // each map's rank.
1006   assert(isReassociationValid(reassociation) && "invalid reassociation");
1007   unsigned currentDim = 0;
1008   for (AffineMap m : reassociation) {
1009     unsigned dim = m.getNumResults();
1010     int64_t size = 1;
1011     AffineExpr stride = strides[currentDim + dim - 1];
1012     if (!isReshapableDimBand(currentDim, dim, sizes, strides)) {
1013       size = ShapedType::kDynamicSize;
1014       stride = AffineExpr();
1015     } else {
1016       for (unsigned d = 0; d < dim; ++d)
1017         size *= sizes[currentDim + d];
1018     }
1019     newSizes.push_back(size);
1020     newStrides.push_back(stride);
1021     currentDim += dim;
1022   }
1023 
1024   // Early-exit: if `type` is contiguous, the result must be contiguous.
1025   if (canonicalizeStridedLayout(type).getAffineMaps().empty())
1026     return MemRefType::Builder(type).setShape(newSizes).setAffineMaps({});
1027 
1028   // Convert back to int64_t because we don't have enough information to create
1029   // new strided layouts from AffineExpr only. This corresponds to a case where
1030   // copies may be necessary.
1031   int64_t intOffset = ShapedType::kDynamicStrideOrOffset;
1032   if (auto o = offset.dyn_cast<AffineConstantExpr>())
1033     intOffset = o.getValue();
1034   SmallVector<int64_t, 4> intStrides;
1035   intStrides.reserve(strides.size());
1036   for (auto stride : newStrides) {
1037     if (auto cst = stride.dyn_cast_or_null<AffineConstantExpr>())
1038       intStrides.push_back(cst.getValue());
1039     else
1040       intStrides.push_back(ShapedType::kDynamicStrideOrOffset);
1041   }
1042   auto layout =
1043       makeStridedLinearLayoutMap(intStrides, intOffset, type.getContext());
1044   return canonicalizeStridedLayout(
1045       MemRefType::Builder(type).setShape(newSizes).setAffineMaps({layout}));
1046 }
1047 
1048 /// Helper functions assert Attribute of the proper type in attr and returns the
1049 /// corresponding vector.
1050 /// TODO: this should be evolved into a generic
1051 /// `getRangeOfType<AffineMap>(ArrayAttr attrs)` that does not copy.
1052 static SmallVector<AffineMap, 4> getAffineMaps(ArrayAttr attrs) {
1053   return llvm::to_vector<8>(llvm::map_range(
1054       attrs, [](Attribute a) { return a.cast<AffineMapAttr>().getValue(); }));
1055 }
1056 
1057 template <typename AffineExprTy>
1058 unsigned getMaxPosOfType(ArrayRef<ReassociationExprs> exprArrays) {
1059   unsigned pos = 0;
1060   for (const auto &exprs : exprArrays) {
1061     for (auto expr : exprs) {
1062       expr.walk([&pos](AffineExpr e) {
1063         if (auto d = e.dyn_cast<AffineExprTy>())
1064           pos = std::max(pos, d.getPosition());
1065       });
1066     }
1067   }
1068   return pos;
1069 }
1070 
1071 static SmallVector<AffineMap, 4>
1072 getSymbolLessAffineMaps(ArrayRef<ReassociationExprs> reassociation) {
1073   unsigned maxDim = getMaxPosOfType<AffineDimExpr>(reassociation);
1074   assert(getMaxPosOfType<AffineSymbolExpr>(reassociation) == 0 &&
1075          "Expected symbol-less expressions");
1076   SmallVector<AffineMap, 4> maps;
1077   maps.reserve(reassociation.size());
1078   for (const auto &exprs : reassociation) {
1079     assert(!exprs.empty());
1080     maps.push_back(AffineMap::get(maxDim + 1, 0, exprs, exprs[0].getContext()));
1081   }
1082   return maps;
1083 }
1084 
1085 static SmallVector<SmallVector<AffineExpr, 2>, 2>
1086 convertReassociationIndicesToMaps(
1087     OpBuilder &b, ArrayRef<ReassociationIndices> reassociationIndices) {
1088   SmallVector<SmallVector<AffineExpr, 2>, 2> reassociationMaps;
1089   for (const auto &indices : reassociationIndices) {
1090     SmallVector<AffineExpr, 2> reassociationMap;
1091     reassociationMap.reserve(indices.size());
1092     for (int64_t index : indices)
1093       reassociationMap.push_back(b.getAffineDimExpr(index));
1094     reassociationMaps.push_back(std::move(reassociationMap));
1095   }
1096   return reassociationMaps;
1097 }
1098 
1099 void mlir::linalg::ReshapeOp::build(OpBuilder &b, OperationState &result,
1100                                     Value src,
1101                                     ArrayRef<ReassociationExprs> reassociation,
1102                                     ArrayRef<NamedAttribute> attrs) {
1103   auto maps = getSymbolLessAffineMaps(reassociation);
1104   auto memRefType = src.getType().cast<MemRefType>();
1105   auto resultType = computeReshapeCollapsedType(memRefType, maps);
1106   build(b, result, resultType, src, attrs);
1107   result.addAttribute(ReshapeOp::getReassociationAttrName(),
1108                       b.getAffineMapArrayAttr(maps));
1109 }
1110 
1111 void mlir::linalg::ReshapeOp::build(OpBuilder &b, OperationState &result,
1112                                     Type resultType, Value src,
1113                                     ArrayRef<ReassociationExprs> reassociation,
1114                                     ArrayRef<NamedAttribute> attrs) {
1115   auto maps = getSymbolLessAffineMaps(reassociation);
1116   build(b, result, resultType, src, attrs);
1117   result.addAttribute(ReshapeOp::getReassociationAttrName(),
1118                       b.getAffineMapArrayAttr(maps));
1119 }
1120 
1121 Value mlir::linalg::ReshapeOp::getViewSource() { return src(); }
1122 
1123 /// Verify that shapes of the reshaped types using following rules
1124 /// 1) if a dimension in the collapsed type is static, then the corresponding
1125 ///    dimensions in the expanded shape should be
1126 ///    a) static
1127 ///    b) the product should be same as the collaped shape.
1128 /// 2) if a dimension in the collaped type is dynamic, one and only one of the
1129 ///    corresponding dimensions in the expanded type should be dynamic. This
1130 ///    rule is only needed with reshape operations that are expanding.
1131 template <typename OpTy>
1132 static LogicalResult verifyReshapeLikeShapes(OpTy op, ShapedType collapsedType,
1133                                              ShapedType expandedType,
1134                                              bool isExpandingReshape) {
1135   ArrayRef<int64_t> collapsedShape = collapsedType.getShape();
1136   ArrayRef<int64_t> expandedShape = expandedType.getShape();
1137   unsigned expandedDimStart = 0;
1138   for (auto map : llvm::enumerate(op.getReassociationMaps())) {
1139     Optional<int64_t> dynamicShape;
1140     int64_t linearizedStaticShape = 1;
1141     for (auto dim : llvm::enumerate(expandedShape.slice(
1142              expandedDimStart, map.value().getNumResults()))) {
1143       if (ShapedType::isDynamic(dim.value())) {
1144         if (isExpandingReshape && dynamicShape) {
1145           return op->emitOpError("invalid to have a single dimension (")
1146                  << map.index() << ") expanded into multiple dynamic dims ("
1147                  << expandedDimStart + dynamicShape.getValue() << ","
1148                  << expandedDimStart + dim.index() << ")";
1149         }
1150         dynamicShape = dim.index();
1151       } else {
1152         linearizedStaticShape *= dim.value();
1153       }
1154     }
1155     if (dynamicShape) {
1156       if (!ShapedType::isDynamic(collapsedShape[map.index()])) {
1157         return op->emitOpError("expected dimension ")
1158                << map.index()
1159                << " of collapsed type to be dynamic since one or more of the "
1160                   "corresponding dimensions in the expanded type is dynamic";
1161       }
1162     } else {
1163       if (collapsedShape[map.index()] != linearizedStaticShape) {
1164         return op->emitOpError("expected dimension ")
1165                << map.index() << " of collapsed type to be static value of "
1166                << linearizedStaticShape << " ";
1167       }
1168     }
1169     expandedDimStart += map.value().getNumResults();
1170   }
1171   return success();
1172 }
1173 
1174 // Common verifier for reshape-like types. Fills `expandedType` and
1175 // `collapsedType` with the proper `src` or `result` type.
1176 template <typename Op, typename T>
1177 static LogicalResult verifyReshapeLikeTypes(Op op, T &expandedType,
1178                                             T &collapsedType) {
1179   expandedType = op.getSrcType();
1180   collapsedType = op.getResultType();
1181   unsigned expandedRank = expandedType.getRank();
1182   unsigned collapsedRank = collapsedType.getRank();
1183   bool isCollapse = expandedRank > collapsedRank;
1184   if (!isCollapse) {
1185     std::swap(expandedRank, collapsedRank);
1186     std::swap(expandedType, collapsedType);
1187   }
1188   if (expandedRank == 0)
1189     return op.emitOpError("expected non-zero memref ranks");
1190   if (expandedRank == collapsedRank)
1191     return op.emitOpError("expected to collapse or expand dims");
1192 
1193   if (collapsedRank == 0) {
1194     // If collapsed rank is 0, then expanded type must be static shaped and of
1195     // sizes 1.
1196     if (llvm::any_of(expandedType.getShape(),
1197                      [](int64_t dim) -> bool { return dim != 1; }))
1198       return op.emitOpError(
1199           "invalid to reshape tensor/memref with non-unit extent dimensions to "
1200           "zero-rank tensor/memref");
1201     return success();
1202   }
1203   if (collapsedRank != op.reassociation().size())
1204     return op.emitOpError("expected rank of the collapsed type(")
1205            << collapsedRank << ") to be the number of reassociation maps("
1206            << op.reassociation().size() << ")";
1207   auto maps = getAffineMaps(op.reassociation());
1208   for (auto it : llvm::enumerate(maps))
1209     if (it.value().getNumDims() != expandedRank)
1210       return op.emitOpError("expected reassociation map #")
1211              << it.index() << " of same rank as expanded memref("
1212              << expandedRank << "), but got " << it.value().getNumDims();
1213   int invalidIdx = 0;
1214   if (!isReassociationValid(maps, &invalidIdx))
1215     return op.emitOpError("expected reassociation map #")
1216            << invalidIdx << " to be valid and contiguous";
1217   return verifyReshapeLikeShapes(op, collapsedType, expandedType, !isCollapse);
1218 }
1219 
1220 static LogicalResult verify(ReshapeOp op) {
1221   MemRefType expandedType, collapsedType;
1222   if (failed(verifyReshapeLikeTypes(op, expandedType, collapsedType)))
1223     return failure();
1224   auto maps = getAffineMaps(op.reassociation());
1225   MemRefType expectedType = computeReshapeCollapsedType(expandedType, maps);
1226   if (collapsedType != expectedType)
1227     return op.emitOpError("expected collapsed type to be ")
1228            << expectedType << ", but got " << collapsedType;
1229   return success();
1230 }
1231 
1232 void ReshapeOp::getCanonicalizationPatterns(OwningRewritePatternList &results,
1233                                             MLIRContext *context) {
1234   results.insert<CollapseReshapeOps<ReshapeOp>>(context);
1235 }
1236 
1237 //===----------------------------------------------------------------------===//
1238 // TensorReshapeOp
1239 //===----------------------------------------------------------------------===//
1240 
1241 /// Compute the RankedTensorType obtained by applying `reassociation` to `type`.
1242 static RankedTensorType
1243 computeTensorReshapeCollapsedType(RankedTensorType type,
1244                                   ArrayRef<AffineMap> reassociation) {
1245   auto shape = type.getShape();
1246   SmallVector<int64_t, 4> newShape;
1247   newShape.reserve(reassociation.size());
1248 
1249   // Use the fact that reassociation is valid to simplify the logic: only use
1250   // each map's rank.
1251   assert(isReassociationValid(reassociation) && "invalid reassociation");
1252   unsigned currentDim = 0;
1253   for (AffineMap m : reassociation) {
1254     unsigned dim = m.getNumResults();
1255     auto band = shape.slice(currentDim, dim);
1256     int64_t size = 1;
1257     if (llvm::is_contained(band, ShapedType::kDynamicSize))
1258       size = ShapedType::kDynamicSize;
1259     else
1260       for (unsigned d = 0; d < dim; ++d)
1261         size *= shape[currentDim + d];
1262     newShape.push_back(size);
1263     currentDim += dim;
1264   }
1265 
1266   return RankedTensorType::get(newShape, type.getElementType());
1267 }
1268 
1269 void mlir::linalg::TensorReshapeOp::build(
1270     OpBuilder &b, OperationState &result, Value src,
1271     ArrayRef<ReassociationExprs> reassociation,
1272     ArrayRef<NamedAttribute> attrs) {
1273   auto maps = getSymbolLessAffineMaps(reassociation);
1274   auto resultType = computeTensorReshapeCollapsedType(
1275       src.getType().cast<RankedTensorType>(), maps);
1276   build(b, result, resultType, src, attrs);
1277   result.addAttribute(TensorReshapeOp::getReassociationAttrName(),
1278                       b.getAffineMapArrayAttr(maps));
1279 }
1280 
1281 void mlir::linalg::TensorReshapeOp::build(
1282     OpBuilder &b, OperationState &result, Type resultType, Value src,
1283     ArrayRef<ReassociationExprs> reassociation,
1284     ArrayRef<NamedAttribute> attrs) {
1285   auto maps = getSymbolLessAffineMaps(reassociation);
1286   build(b, result, resultType, src, attrs);
1287   result.addAttribute(TensorReshapeOp::getReassociationAttrName(),
1288                       b.getAffineMapArrayAttr(maps));
1289 }
1290 
1291 static LogicalResult verify(TensorReshapeOp op) {
1292   RankedTensorType expandedType, collapsedType;
1293   if (failed(verifyReshapeLikeTypes(op, expandedType, collapsedType)))
1294     return failure();
1295   auto maps = getAffineMaps(op.reassociation());
1296   RankedTensorType expectedType =
1297       computeTensorReshapeCollapsedType(expandedType, maps);
1298   if (collapsedType != expectedType)
1299     return op.emitOpError("expected collapsed type to be ")
1300            << expectedType << ", but got " << collapsedType;
1301   return success();
1302 }
1303 
1304 namespace {
1305 /// Reshape of a splat constant can be replaced with a constant of the result
1306 /// type.
1307 struct FoldReshapeWithConstant : OpRewritePattern<TensorReshapeOp> {
1308   using OpRewritePattern<TensorReshapeOp>::OpRewritePattern;
1309   LogicalResult matchAndRewrite(TensorReshapeOp reshapeOp,
1310                                 PatternRewriter &rewriter) const override {
1311     DenseElementsAttr attr;
1312     if (!matchPattern(reshapeOp.src(), m_Constant(&attr)))
1313       return failure();
1314     if (!attr || !attr.isSplat())
1315       return failure();
1316     DenseElementsAttr newAttr = DenseElementsAttr::getFromRawBuffer(
1317         reshapeOp.getResultType(), attr.getRawData(), true);
1318     rewriter.replaceOpWithNewOp<ConstantOp>(reshapeOp, newAttr);
1319     return success();
1320   }
1321 };
1322 } // namespace
1323 
1324 void TensorReshapeOp::getCanonicalizationPatterns(
1325     OwningRewritePatternList &results, MLIRContext *context) {
1326   results.insert<CollapseReshapeOps<TensorReshapeOp>, FoldReshapeWithConstant>(
1327       context);
1328 }
1329 
1330 //===----------------------------------------------------------------------===//
1331 // YieldOp
1332 //===----------------------------------------------------------------------===//
1333 
1334 static void print(OpAsmPrinter &p, linalg::YieldOp op) {
1335   p << op.getOperationName();
1336   if (op.getNumOperands() > 0)
1337     p << ' ' << op.getOperands();
1338   p.printOptionalAttrDict(op.getAttrs());
1339   if (op.getNumOperands() > 0)
1340     p << " : " << op.getOperandTypes();
1341 }
1342 
1343 static ParseResult parseYieldOp(OpAsmParser &parser, OperationState &result) {
1344   SmallVector<OpAsmParser::OperandType, 2> opInfo;
1345   SmallVector<Type, 2> types;
1346   llvm::SMLoc loc = parser.getCurrentLocation();
1347   return failure(parser.parseOperandList(opInfo) ||
1348                  parser.parseOptionalAttrDict(result.attributes) ||
1349                  (!opInfo.empty() && parser.parseColonTypeList(types)) ||
1350                  parser.resolveOperands(opInfo, types, loc, result.operands));
1351 }
1352 
1353 // Check the operand number and types must match the element types of the
1354 // LinalgOp interface's shaped operands.
1355 static LogicalResult verifyYield(linalg::YieldOp op,
1356                                  LinalgOp linalgOpInterface) {
1357   auto nOutputs = linalgOpInterface.getNumOutputs();
1358   if (op.getNumOperands() != nOutputs)
1359     return op.emitOpError("expected number of yield values (")
1360            << nOutputs << ") to match the number of operands of the enclosing "
1361            << "LinalgOp (" << op.getNumOperands() << ")";
1362 
1363   for (unsigned i = 0; i != nOutputs; ++i) {
1364     auto elementType =
1365         linalgOpInterface.getOutputShapedType(i).getElementType();
1366     if (op.getOperand(i).getType() != elementType)
1367       return op.emitOpError("type of yield operand ")
1368              << (i + 1) << " (" << op.getOperand(i).getType()
1369              << ") doesn't match "
1370              << "the element type of the enclosing linalg.generic op ("
1371              << elementType << ")";
1372   }
1373   return success();
1374 }
1375 
1376 static LogicalResult verify(linalg::YieldOp op) {
1377   auto *parentOp = op->getParentOp();
1378   if (parentOp->getNumRegions() != 1 || parentOp->getRegion(0).empty())
1379     return op.emitOpError("expected single non-empty parent region");
1380 
1381   if (auto linalgOp = dyn_cast<LinalgOp>(parentOp))
1382     return verifyYield(op, cast<LinalgOp>(parentOp));
1383 
1384   if (auto padTensorOp = dyn_cast<linalg::PadTensorOp>(parentOp)) {
1385     if (op.getNumOperands() != 1)
1386       return op.emitOpError("expected single yield operand (got ")
1387              << op->getNumOperands() << ")";
1388     if (op.getOperand(0).getType() !=
1389         padTensorOp.getType().cast<ShapedType>().getElementType())
1390       return op.emitOpError("expected yield type to match shape element type");
1391     return success();
1392   }
1393 
1394   return op.emitOpError("expected parent op with LinalgOp interface");
1395 }
1396 
1397 /////// Operations corresponding to library calls defined with Tablegen ////////
1398 
1399 void FillOp::getEffects(
1400     SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
1401         &effects) {
1402   if (output().getType().isa<MemRefType>())
1403     effects.emplace_back(MemoryEffects::Write::get(), output(),
1404                          SideEffects::DefaultResource::get());
1405 }
1406 
1407 static LogicalResult verify(FillOp op) {
1408   auto viewType = op.getOutputShapedType(0);
1409   auto fillType = op.value().getType();
1410   if (viewType.getElementType() != fillType)
1411     return op.emitOpError("expects fill type to match view elemental type");
1412   if (!op.getNumResults() && !viewType.isa<MemRefType>()) {
1413     return op.emitOpError(
1414         "expected fill op with no result value to use memref type");
1415   }
1416   return success();
1417 }
1418 
1419 void CopyOp::getEffects(
1420     SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
1421         &effects) {
1422   effects.emplace_back(MemoryEffects::Read::get(), input(),
1423                        SideEffects::DefaultResource::get());
1424   effects.emplace_back(MemoryEffects::Write::get(), output(),
1425                        SideEffects::DefaultResource::get());
1426 }
1427 
1428 static LogicalResult verify(CopyOp op) {
1429   auto outputViewType = op.getOutputShapedType(0);
1430   auto inputViewType = op.getInputShapedType(0);
1431   if (inputViewType.getElementType() != outputViewType.getElementType())
1432     return op.emitOpError("expects views of the same type");
1433   if (inputViewType.getRank() != outputViewType.getRank())
1434     return op.emitOpError("expects views of the same rank");
1435   auto rank = op.getNumParallelLoops();
1436   auto inputPermutationMap = op.inputPermutation();
1437   if (inputPermutationMap) {
1438     if (inputPermutationMap->getNumInputs() != rank)
1439       return op.emitOpError("expects optional input_permutation map of rank ")
1440              << rank;
1441     if (!inputPermutationMap->isPermutation())
1442       return op.emitOpError(
1443           "expects optional input_permutation map to be a permutation");
1444   }
1445   auto outputPermutationMap = op.outputPermutation();
1446   if (outputPermutationMap) {
1447     if (outputPermutationMap->getNumInputs() != rank)
1448       return op.emitOpError("expects optional output_permutation map of rank ")
1449              << rank;
1450     if (!outputPermutationMap->isPermutation())
1451       return op.emitOpError(
1452           "expects optional output_permutation map to be a permutation");
1453   }
1454   if (rank == 0 && inputPermutationMap)
1455     return op.emitOpError("expected no input permutation when rank == 0");
1456   if (rank == 0 && outputPermutationMap)
1457     return op.emitOpError("expected no output permutation when rank == 0");
1458   return success();
1459 }
1460 
1461 template <typename LinalgPoolingOp>
1462 static LogicalResult verifyStrideOrDilation(LinalgPoolingOp op,
1463                                             ArrayRef<Attribute> attrs,
1464                                             bool isStride) {
1465   auto strideOrDilation = isStride ? "stride" : "dilation";
1466   if (attrs.size() != op.getNumWindowLoops())
1467     return op.emitOpError("expects num ")
1468            << strideOrDilation
1469            << "s equal to number of window dimensions: " << attrs.size()
1470            << " vs " << op.getNumWindowLoops();
1471   return success();
1472 }
1473 
1474 void ConvOp::getEffects(
1475     SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
1476         &effects) {
1477   effects.emplace_back(MemoryEffects::Read::get(), input(),
1478                        SideEffects::DefaultResource::get());
1479   effects.emplace_back(MemoryEffects::Read::get(), filter(),
1480                        SideEffects::DefaultResource::get());
1481   effects.emplace_back(MemoryEffects::Write::get(), output(),
1482                        SideEffects::DefaultResource::get());
1483 }
1484 
1485 static LogicalResult verify(ConvOp op) {
1486   auto oType = op.output().getType().cast<MemRefType>();
1487   auto fType = op.filter().getType().cast<MemRefType>();
1488   auto iType = op.input().getType().cast<MemRefType>();
1489   if (oType.getElementType() != iType.getElementType() ||
1490       oType.getElementType() != fType.getElementType())
1491     return op.emitOpError("expects memref elemental types to match");
1492   if (oType.getRank() != iType.getRank() || oType.getRank() != fType.getRank())
1493     return op.emitOpError("expects memref ranks to match");
1494   if (auto strides = op.strides()) {
1495     if (failed(
1496             verifyStrideOrDilation(op, strides->getValue(), /*isStride=*/true)))
1497       return failure();
1498   }
1499   if (auto dilations = op.dilations()) {
1500     if (failed(verifyStrideOrDilation(op, dilations->getValue(),
1501                                       /*isStride=*/false)))
1502       return failure();
1503   }
1504   return success();
1505 }
1506 
1507 template <typename PoolingOp>
1508 static LogicalResult verifySingleInputPoolingOp(PoolingOp op) {
1509   auto inputType = op.input().getType().template cast<MemRefType>();
1510   auto outputType = op.output().getType().template cast<MemRefType>();
1511   if (outputType.getElementType() != inputType.getElementType())
1512     return op.emitOpError("expects memref elemental types to match");
1513 
1514   auto windowDimsType = op.windowDims().getType().template cast<MemRefType>();
1515   if (outputType.getRank() != inputType.getRank() ||
1516       outputType.getRank() != windowDimsType.getRank())
1517     return op.emitOpError("expects memref ranks to match");
1518 
1519   if (auto strides = op.strides()) {
1520     if (failed(
1521             verifyStrideOrDilation(op, strides->getValue(), /*isStride=*/true)))
1522       return failure();
1523   }
1524   if (auto dilations = op.dilations()) {
1525     if (failed(verifyStrideOrDilation(op, dilations->getValue(),
1526                                       /*isStride=*/false)))
1527       return failure();
1528   }
1529   return success();
1530 }
1531 
1532 #define DEFINE_POOLING_OP_GET_EFFECTS(OP_NAME)                                 \
1533   void OP_NAME::getEffects(                                                    \
1534       SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>      \
1535           &effects) {                                                          \
1536     effects.emplace_back(MemoryEffects::Read::get(), input(),                  \
1537                          SideEffects::DefaultResource::get());                 \
1538     effects.emplace_back(MemoryEffects::Write::get(), output(),                \
1539                          SideEffects::DefaultResource::get());                 \
1540   }
1541 
1542 static LogicalResult verify(PoolingMaxOp op) {
1543   return verifySingleInputPoolingOp(op);
1544 }
1545 static LogicalResult verify(PoolingMinOp op) {
1546   return verifySingleInputPoolingOp(op);
1547 }
1548 static LogicalResult verify(PoolingSumOp op) {
1549   return verifySingleInputPoolingOp(op);
1550 }
1551 
1552 DEFINE_POOLING_OP_GET_EFFECTS(PoolingMaxOp)
1553 DEFINE_POOLING_OP_GET_EFFECTS(PoolingMinOp)
1554 DEFINE_POOLING_OP_GET_EFFECTS(PoolingSumOp)
1555 
1556 namespace {
1557 struct EraseDeadLinalgOp;
1558 struct FoldTensorCastOp;
1559 } // namespace
1560 
1561 #include "mlir/Dialect/Linalg/IR/LinalgNamedStructuredOps.cpp.inc"
1562 
1563 #define GET_OP_CLASSES
1564 #include "mlir/Dialect/Linalg/IR/LinalgOps.cpp.inc"
1565 
1566 #define GET_OP_CLASSES
1567 #include "mlir/Dialect/Linalg/IR/LinalgStructuredOps.cpp.inc"
1568 
1569 /// Return the dims that are `iteratorTypeName` loops in the LinalgOp `op`.
1570 /// Assumes `op` is a LinalgOp.
1571 void mlir::linalg::getDimsOfType(Operation *op, StringRef iteratorTypeName,
1572                                  SmallVectorImpl<AffineExpr> &res) {
1573   if (!cast<LinalgOp>(op).iterator_types())
1574     return;
1575 
1576   unsigned dim = 0;
1577   MLIRContext *ctx = op->getContext();
1578   for (auto tn :
1579        cast<LinalgOp>(op).iterator_types().getAsValueRange<StringAttr>()) {
1580     if (tn == iteratorTypeName)
1581       res.push_back(getAffineDimExpr(dim, ctx));
1582     ++dim;
1583   }
1584 }
1585 
1586 AffineMap mlir::linalg::extractOrIdentityMap(Optional<AffineMap> maybeMap,
1587                                              unsigned rank,
1588                                              MLIRContext *context) {
1589   if (maybeMap)
1590     return maybeMap.getValue();
1591   if (rank == 0)
1592     return AffineMap::get(context);
1593   return AffineMap::getMultiDimIdentityMap(rank, context);
1594 }
1595 
1596 SmallVector<AffineExpr, 4>
1597 mlir::linalg::makeAffineDimExprs(unsigned num, unsigned &startIdx,
1598                                  MLIRContext *context) {
1599   SmallVector<AffineExpr, 4> res;
1600   res.reserve(num);
1601   for (unsigned i = 0; i < num; ++i)
1602     res.push_back(getAffineDimExpr(startIdx++, context));
1603   return res;
1604 }
1605 
1606 template <typename PoolingOp>
1607 SmallVector<AffineExpr, 4>
1608 mlir::linalg::weightedPoolingInputIndex(PoolingOp op,
1609                                         ArrayRef<AffineExpr> outputDims,
1610                                         ArrayRef<AffineExpr> windowDims) {
1611   assert(outputDims.size() == windowDims.size());
1612   SmallVector<AffineExpr, 4> res;
1613   res.reserve(outputDims.size());
1614   for (unsigned i = 0, e = outputDims.size(); i < e; ++i) {
1615     // TODO: add a level of indirection to linalg.generic.
1616     auto expr = op.getStride(i) * outputDims[i] +
1617                 op.getDilation(i) * windowDims[i] - op.getLowPad(i);
1618     res.push_back(expr);
1619   }
1620   return res;
1621 }
1622 
1623 #define INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(OP_TYPE)                      \
1624   template SmallVector<AffineExpr, 4>                                          \
1625   mlir::linalg::weightedPoolingInputIndex<OP_TYPE>(                            \
1626       OP_TYPE op, ArrayRef<AffineExpr> outputDims,                             \
1627       ArrayRef<AffineExpr> windowDims);
1628 
1629 INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(ConvOp)
1630 INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(PoolingMaxOp)
1631 INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(PoolingMinOp)
1632 INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(PoolingSumOp)
1633 
1634 SmallVector<AffineExpr, 4> mlir::linalg::concat(ArrayRef<AffineExpr> a,
1635                                                 ArrayRef<AffineExpr> b) {
1636   auto rangeA = llvm::make_range(a.begin(), a.end());
1637   auto rangeB = llvm::make_range(b.begin(), b.end());
1638   auto concatRanges = llvm::concat<const AffineExpr>(rangeA, rangeB);
1639   return llvm::to_vector<4>(concatRanges);
1640 }
1641 
1642 static void appendMangledType(llvm::raw_string_ostream &ss, Type t) {
1643   if (auto memref = t.dyn_cast<MemRefType>()) {
1644     ss << "view";
1645     for (auto size : memref.getShape())
1646       if (size < 0)
1647         ss << "sx";
1648       else
1649         ss << size << "x";
1650     appendMangledType(ss, memref.getElementType());
1651   } else if (auto vec = t.dyn_cast<VectorType>()) {
1652     ss << "vector";
1653     llvm::interleave(
1654         vec.getShape(), [&](int64_t i) { ss << i; }, [&]() { ss << "x"; });
1655     appendMangledType(ss, vec.getElementType());
1656   } else if (t.isSignlessIntOrIndexOrFloat()) {
1657     ss << t;
1658   } else {
1659     llvm_unreachable("Invalid type for linalg library name mangling");
1660   }
1661 }
1662 
1663 std::string mlir::linalg::generateLibraryCallName(Operation *op) {
1664   assert(isa<LinalgOp>(op));
1665   std::string name(op->getName().getStringRef().str());
1666   name.reserve(128);
1667   std::replace(name.begin(), name.end(), '.', '_');
1668   llvm::raw_string_ostream ss(name);
1669   ss << "_";
1670   auto types = op->getOperandTypes();
1671   llvm::interleave(
1672       types.begin(), types.end(), [&](Type t) { appendMangledType(ss, t); },
1673       [&]() { ss << "_"; });
1674   return ss.str();
1675 }
1676 
1677 // TODO: Consider making all this boilerplate easy to autogenerate
1678 // with Tablegen. This seems a desirable property in the context of
1679 // OpInterfaces where a Linalg "named" op **isa** LinalgOp.
1680 OpFoldResult ReshapeOp::fold(ArrayRef<Attribute> operands) {
1681   if (succeeded(foldMemRefCast(*this)))
1682     return getResult();
1683   return foldReshapeOp(*this, operands);
1684 }
1685 OpFoldResult TensorReshapeOp::fold(ArrayRef<Attribute> operands) {
1686   return foldReshapeOp(*this, operands);
1687 }
1688 
1689 //===----------------------------------------------------------------------===//
1690 // Auto-generated Linalg named ops.
1691 //===----------------------------------------------------------------------===//
1692 
1693 template <typename NamedStructuredOpType>
1694 static void buildNamedStructuredOpRegionAndAttributesImpl(
1695     OpBuilder &opBuilder, Region &region, TypeRange inputTypes,
1696     TypeRange outputTypes,
1697     std::function<void(unsigned, unsigned)> errorHandler) {
1698   // TODO: atm all operands go through getElementTypeOrSelf,
1699   // reconsider when we have evidence we need to.
1700   SmallVector<Type, 8> argTypes;
1701   for (auto containers : {inputTypes, outputTypes})
1702     for (auto t : containers)
1703       argTypes.push_back(getElementTypeOrSelf(t));
1704 
1705   // RAII.
1706   OpBuilder::InsertionGuard guard(opBuilder);
1707   Block *body = opBuilder.createBlock(&region, {}, argTypes);
1708   unsigned actual = body->getNumArguments();
1709   unsigned expected = NamedStructuredOpType::getNumRegionArgs();
1710   if (expected != actual)
1711     return errorHandler(expected, actual);
1712 
1713   opBuilder.setInsertionPointToStart(body);
1714   mlir::edsc::ScopedContext scope(opBuilder, opBuilder.getUnknownLoc());
1715   NamedStructuredOpType::regionBuilder(*body);
1716 
1717   // indexing_maps is an auto-generated method.
1718 
1719   // iterator_types is an auto-generated method.
1720 }
1721 
1722 template <typename NamedStructuredOpType>
1723 void buildNamedStructuredOpRegionAndAttributes(OpBuilder &opBuilder,
1724                                                OperationState &result,
1725                                                TypeRange inputTypes,
1726                                                TypeRange outputTypes) {
1727   Region &region = *result.addRegion();
1728   buildNamedStructuredOpRegionAndAttributesImpl<NamedStructuredOpType>(
1729       opBuilder, region, inputTypes, outputTypes,
1730       [&](unsigned expected, unsigned actual) {
1731         llvm::errs() << "region expects " << expected << " args, got "
1732                      << actual;
1733         assert(expected != actual && "incorrect number of arguments");
1734       });
1735 }
1736 
1737 template <typename NamedStructuredOpType>
1738 static ParseResult
1739 parseNamedStructuredOpRegion(OpAsmParser &parser, Region &region,
1740                              TypeRange inputTypes, TypeRange outputTypes) {
1741   ParseResult res = success();
1742   OpBuilder opBuilder(parser.getBuilder().getContext());
1743   buildNamedStructuredOpRegionAndAttributesImpl<NamedStructuredOpType>(
1744       opBuilder, region, inputTypes, outputTypes,
1745       [&](unsigned expected, unsigned actual) {
1746         res = parser.emitError(parser.getCurrentLocation(),
1747                                llvm::formatv("region expects {0} args, got {1}",
1748                                              expected, actual));
1749       });
1750   return res;
1751 }
1752 
1753 static ParseResult
1754 parseNamedStructuredOpResults(OpAsmParser &parser,
1755                               SmallVectorImpl<Type> &resultTypes) {
1756   if (succeeded(parser.parseOptionalArrow()))
1757     if (parser.parseTypeList(resultTypes))
1758       return failure();
1759   return success();
1760 }
1761 
1762 static ParseResult
1763 parseCommonStructuredOpParts(OpAsmParser &parser, OperationState &result,
1764                              SmallVectorImpl<Type> &inputTypes,
1765                              SmallVectorImpl<Type> &outputTypes) {
1766   llvm::SMLoc inputsOperandsLoc, outputsOperandsLoc;
1767   SmallVector<OpAsmParser::OperandType, 4> inputsOperands, outputsOperands;
1768 
1769   parser.parseOptionalAttrDict(result.attributes);
1770 
1771   if (succeeded(parser.parseOptionalKeyword("ins"))) {
1772     if (parser.parseLParen())
1773       return failure();
1774 
1775     inputsOperandsLoc = parser.getCurrentLocation();
1776     if (parser.parseOperandList(inputsOperands) ||
1777         parser.parseColonTypeList(inputTypes) || parser.parseRParen())
1778       return failure();
1779   }
1780 
1781   if (succeeded(parser.parseOptionalKeyword("outs"))) {
1782     outputsOperandsLoc = parser.getCurrentLocation();
1783     if (parser.parseLParen() || parser.parseOperandList(outputsOperands) ||
1784         parser.parseColonTypeList(outputTypes) || parser.parseRParen())
1785       return failure();
1786   }
1787 
1788   if (parser.resolveOperands(inputsOperands, inputTypes, inputsOperandsLoc,
1789                              result.operands) ||
1790       parser.resolveOperands(outputsOperands, outputTypes, outputsOperandsLoc,
1791                              result.operands))
1792     return failure();
1793 
1794   result.addAttribute("operand_segment_sizes",
1795                       parser.getBuilder().getI32VectorAttr(
1796                           {static_cast<int32_t>(inputsOperands.size()),
1797                            static_cast<int32_t>(outputsOperands.size())}));
1798   return success();
1799 }
1800 
1801 template <typename NamedStructuredOpType>
1802 static ParseResult parseNamedStructuredOp(OpAsmParser &parser,
1803                                           OperationState &result) {
1804   SmallVector<Type, 1> inputTypes, outputTypes;
1805   if (parseCommonStructuredOpParts(parser, result, inputTypes, outputTypes))
1806     return failure();
1807 
1808   // TODO: consider merging results parsing into region parsing.
1809   // Need to wait for declarative assembly resolution to decide.
1810   SmallVector<Type, 1> outputTensorsTypes;
1811   if (parseNamedStructuredOpResults(parser, outputTensorsTypes))
1812     return failure();
1813   result.addTypes(outputTensorsTypes);
1814 
1815   std::unique_ptr<Region> region = std::make_unique<Region>();
1816   if (parseNamedStructuredOpRegion<NamedStructuredOpType>(
1817           parser, *region, inputTypes, outputTypes))
1818     return failure();
1819   result.addRegion(std::move(region));
1820 
1821   return success();
1822 }
1823 
1824 static void printNamedStructuredOpResults(OpAsmPrinter &p,
1825                                           TypeRange resultTypes) {
1826   if (resultTypes.empty())
1827     return;
1828   p.printOptionalArrowTypeList(resultTypes);
1829 }
1830 
1831 template <typename NamedStructuredOpType>
1832 static void printCommonStructuredOpParts(OpAsmPrinter &p,
1833                                          NamedStructuredOpType op) {
1834   if (!op.inputs().empty())
1835     p << " ins(" << op.inputs() << " : " << op.inputs().getTypes() << ")";
1836   if (!op.outputs().empty())
1837     p << " outs(" << op.outputs() << " : " << op.outputs().getTypes() << ")";
1838 }
1839 
1840 template <typename NamedStructuredOpType>
1841 static void printNamedStructuredOp(OpAsmPrinter &p, NamedStructuredOpType op) {
1842   p << op.getOperationName();
1843   p.printOptionalAttrDict(op.getAttrs(),
1844                           /*elidedAttrs=*/{"operand_segment_sizes"});
1845 
1846   // Printing is shared with generic ops, except for the region and
1847   // attributes.
1848   printCommonStructuredOpParts(p, op);
1849 
1850   // Results printing.
1851   printNamedStructuredOpResults(p, op.result_tensors().getTypes());
1852 
1853   // Region is elided.
1854 }
1855 
1856 template <typename NamedStructuredOpType>
1857 static LogicalResult verifyNamedStructuredOp(NamedStructuredOpType op) {
1858   return verifyGenericOp<NamedStructuredOpType>(op);
1859 }
1860 
1861 namespace {
1862 struct EraseDeadLinalgOp : public RewritePattern {
1863   EraseDeadLinalgOp(PatternBenefit benefit = 1)
1864       : RewritePattern(benefit, MatchAnyOpTypeTag()) {}
1865 
1866   LogicalResult matchAndRewrite(Operation *op,
1867                                 PatternRewriter &rewriter) const override {
1868     auto linalgOp = dyn_cast<LinalgOp>(op);
1869     if (!linalgOp)
1870       return failure();
1871     for (Value v : linalgOp.getShapedOperands()) {
1872       // Linalg "inputs" may be either tensor or memref type.
1873       // tensor<0xelt_type> is a convention that may not always mean
1874       // "0 iterations". Only erase in cases we see memref<...x0x...>.
1875       auto mt = v.getType().dyn_cast<MemRefType>();
1876       if (!mt)
1877         continue;
1878       if (llvm::is_contained(mt.getShape(), 0)) {
1879         rewriter.eraseOp(linalgOp);
1880         return success();
1881       }
1882     }
1883     return failure();
1884   }
1885 };
1886 
1887 struct FoldTensorCastOp : public RewritePattern {
1888   FoldTensorCastOp(PatternBenefit benefit = 1)
1889       : RewritePattern(benefit, MatchAnyOpTypeTag()) {}
1890 
1891   LogicalResult matchAndRewrite(Operation *op,
1892                                 PatternRewriter &rewriter) const override {
1893     auto linalgOp = dyn_cast<LinalgOp>(op);
1894     if (!linalgOp)
1895       return failure();
1896 
1897     // If no operand comes from a tensor::CastOp and can be folded then fail.
1898     bool hasTensorCastOperand =
1899         llvm::any_of(linalgOp.getShapedOperands(), [&](Value v) {
1900           if (v.isa<BlockArgument>())
1901             return false;
1902           auto castOp = v.getDefiningOp<tensor::CastOp>();
1903           return castOp && canFoldIntoConsumerOp(castOp);
1904         });
1905     if (!hasTensorCastOperand)
1906       return failure();
1907 
1908     SmallVector<Type, 4> newResultTypes;
1909     newResultTypes.reserve(op->getNumResults());
1910     SmallVector<Value, 4> newOperands;
1911     newOperands.reserve(op->getNumOperands());
1912     // Inputs may fold.
1913     for (Value v : linalgOp.getInputs()) {
1914       auto tensorCastOp = v.getDefiningOp<tensor::CastOp>();
1915       newOperands.push_back(
1916           canFoldIntoConsumerOp(tensorCastOp) ? tensorCastOp.source() : v);
1917     }
1918     // Init tensors may fold, in which case the resultType must also change.
1919     for (Value v : linalgOp.getOutputs()) {
1920       auto tensorCastOp = v.getDefiningOp<tensor::CastOp>();
1921       bool fold = canFoldIntoConsumerOp(tensorCastOp);
1922       newOperands.push_back(fold ? tensorCastOp.getOperand() : v);
1923       newResultTypes.push_back(newOperands.back().getType());
1924     }
1925     auto extraOperands = linalgOp.getAssumedNonShapedOperands();
1926     newOperands.append(extraOperands.begin(), extraOperands.end());
1927     // Clone op.
1928     Operation *newOp =
1929         linalgOp.clone(rewriter, op->getLoc(), newResultTypes, newOperands);
1930     rewriter.replaceOp(op, newOp->getResults());
1931 
1932     return success();
1933   }
1934 };
1935 
1936 /// Replaces std.dim operations that use the result of a LinalgOp (on tensors)
1937 /// with std.dim operations that use one of the arguments. For example,
1938 ///
1939 /// %0 = linalg.matmul ins(%arg0, %arg1, ...)
1940 /// %1 = dim %0, %c0
1941 ///
1942 /// with
1943 ///
1944 /// %1 = dim %arg0, %c0
1945 ///
1946 /// where possible. With this the result of the `linalg.matmul` is not used in
1947 /// dim operations. If the value produced is replaced with another value (say by
1948 /// tiling `linalg.matmul`) will make the `linalg.matmul` truly dead instead of
1949 /// used in a dim op that would prevent the DCE of this op.
1950 struct ReplaceDimOfLinalgOpResult : public OpRewritePattern<DimOp> {
1951   using OpRewritePattern<DimOp>::OpRewritePattern;
1952 
1953   LogicalResult matchAndRewrite(DimOp dimOp,
1954                                 PatternRewriter &rewriter) const override {
1955     Value dimValue = dimOp.memrefOrTensor();
1956     Optional<int64_t> dimIndex = dimOp.getConstantIndex();
1957     if (!dimIndex)
1958       return failure();
1959     auto linalgOp = dimValue.getDefiningOp<LinalgOp>();
1960     if (!linalgOp)
1961       return failure();
1962 
1963     unsigned resultIndex = dimValue.cast<OpResult>().getResultNumber();
1964     Optional<Value> operandDimValue = linalgOp.inferResultDimFromInputShapes(
1965         rewriter, dimOp.getLoc(), resultIndex,
1966         static_cast<unsigned>(*dimIndex));
1967     if (!operandDimValue) {
1968       // Its always possible to replace using the corresponding `outs`
1969       // parameter.
1970       operandDimValue = rewriter.create<DimOp>(
1971           dimOp.getLoc(), linalgOp.getOutput(resultIndex), *dimIndex);
1972     }
1973     rewriter.replaceOp(dimOp, *operandDimValue);
1974     return success();
1975   }
1976 };
1977 
1978 } // namespace
1979 
1980 namespace {
1981 // Deduplicate redundant args of a linalg op.
1982 // An arg is redundant if it has the same Value and indexing map as another.
1983 struct DeduplicateInputs : public RewritePattern {
1984   DeduplicateInputs(PatternBenefit benefit = 1)
1985       : RewritePattern(benefit, MatchAnyOpTypeTag()) {}
1986 
1987   LogicalResult matchAndRewrite(Operation *op,
1988                                 PatternRewriter &rewriter) const override {
1989     // This pattern reduces the number of arguments of an op, which breaks
1990     // the invariants of semantically charged named ops.
1991     if (!isa<GenericOp, IndexedGenericOp>(op))
1992       return failure();
1993     auto linalgOp = cast<LinalgOp>(op);
1994 
1995     // Associate each input to an equivalent "canonical" input that has the same
1996     // Value and indexing map.
1997     //
1998     // In the non-duplicate case, input `i` will have canonical input `i`. But
1999     // in the case of duplicated inputs, the canonical input could be some other
2000     // input `< i`. That is, a later input will have some earlier input as its
2001     // canonical input.
2002     llvm::SmallDenseMap<std::pair<Value, AffineMap>, int> canonicalInput;
2003     // For later remapping tasks like deduplicating payload block arguments,
2004     // having a simple "inputIndex -> canonicalInputIndex" integer mapping is
2005     // convenient.
2006     SmallVector<int, 6> canonicalInputIndices;
2007     for (int i = 0, e = linalgOp.getNumInputs(); i != e; i++) {
2008       Value input = linalgOp.getInput(i);
2009       AffineMap indexingMap = linalgOp.getInputIndexingMap(i);
2010       // STL-like maps have a convenient behavior for our use case here. In the
2011       // case of duplicate keys, the insertion is rejected, and the returned
2012       // iterator gives access to the value already in the map.
2013       auto pair = canonicalInput.insert({{input, indexingMap}, i});
2014       canonicalInputIndices.push_back(pair.first->second);
2015     }
2016 
2017     // If there are no duplicate args, then bail out.
2018     if (canonicalInput.size() == linalgOp.getNumInputs())
2019       return failure();
2020 
2021     // The operands for the newly canonicalized op.
2022     SmallVector<Value, 6> newOperands;
2023     for (auto v : llvm::enumerate(linalgOp.getInputs()))
2024       if (canonicalInputIndices[v.index()] == static_cast<int>(v.index()))
2025         newOperands.push_back(v.value());
2026     llvm::append_range(newOperands, linalgOp.getOutputs());
2027     llvm::append_range(newOperands, linalgOp.getAssumedNonShapedOperands());
2028 
2029     // Clone the old op with new operands.
2030     Operation *newOp = linalgOp.clone(rewriter, op->getLoc(),
2031                                       op->getResultTypes(), newOperands);
2032     auto newLinalgOp = cast<LinalgOp>(newOp);
2033 
2034     // Repair the indexing maps by filtering out the ones that have been
2035     // eliminated.
2036     SmallVector<AffineMap, 6> newIndexingMaps;
2037     for (int i = 0, e = newLinalgOp.getNumInputs(); i != e; i++)
2038       if (canonicalInputIndices[i] == i)
2039         newIndexingMaps.push_back(newLinalgOp.getIndexingMap(i));
2040     for (int i = 0, e = newLinalgOp.getNumOutputs(); i != e; i++)
2041       newIndexingMaps.push_back(newLinalgOp.getOutputIndexingMap(i));
2042     newOp->setAttr("indexing_maps",
2043                    rewriter.getAffineMapArrayAttr(newIndexingMaps));
2044 
2045     // Set the number of inputs to the new value. The `clone` call above kept
2046     // the value from the original op.
2047     newLinalgOp.setNumInputs(canonicalInput.size());
2048 
2049     // linalg.indexed_generic payloads have additional arguments prepended to
2050     // the block arg list.
2051     int bbArgBaseOffset = newLinalgOp.getNumPayloadInductionVariables();
2052 
2053     // Repair the payload entry block by RAUW'ing redundant arguments and
2054     // erasing them.
2055     Block &payload = newOp->getRegion(0).front();
2056     for (int i = 0, e = linalgOp.getNumInputs(); i < e; i++) {
2057       // Iterate in reverse, so that we erase later args first, preventing the
2058       // argument list from shifting unexpectedly and invalidating all our
2059       // indices.
2060       int reversed = e - i - 1;
2061       int canonicalIndex = canonicalInputIndices[reversed];
2062       if (canonicalInputIndices[reversed] == reversed)
2063         continue;
2064       payload.getArgument(bbArgBaseOffset + reversed)
2065           .replaceAllUsesWith(
2066               payload.getArgument(bbArgBaseOffset + canonicalIndex));
2067       payload.eraseArgument(bbArgBaseOffset + reversed);
2068     }
2069 
2070     rewriter.replaceOp(op, newOp->getResults());
2071     return success();
2072   }
2073 };
2074 
2075 /// Remove generic/indexed_generic operations (on tensors) that are just copying
2076 /// the values from inputs to the results. Requirements are
2077 /// 1) All iterator types are parallel
2078 /// 2) The body contains just a yield operation with the yielded values being
2079 ///    the arguments corresponding to the operands.
2080 struct RemoveIdentityLinalgOps : public RewritePattern {
2081   RemoveIdentityLinalgOps(PatternBenefit benefit = 1)
2082       : RewritePattern(benefit, MatchAnyOpTypeTag()) {}
2083 
2084   LogicalResult matchAndRewrite(Operation *op,
2085                                 PatternRewriter &rewriter) const override {
2086     if (!isa<GenericOp, IndexedGenericOp>(op))
2087       return failure();
2088     LinalgOp genericOp = cast<LinalgOp>(op);
2089     if (!genericOp.hasTensorSemantics())
2090       return failure();
2091     // Check all indexing maps are identity.
2092     if (llvm::any_of(genericOp.getIndexingMaps(),
2093                      [](AffineMap map) { return !map.isIdentity(); }))
2094       return failure();
2095 
2096     // Check that the body of the linalg operation is just a linalg.yield
2097     // operation.
2098     Block &body = op->getRegion(0).front();
2099     if (!llvm::hasSingleElement(body))
2100       return failure();
2101     auto yieldOp = dyn_cast<linalg::YieldOp>(body.getTerminator());
2102     if (!yieldOp)
2103       return failure();
2104 
2105     // Get the argument number of the returned values. That is the operand
2106     // number to use for replacing uses of this operation.
2107     unsigned numIndexArgs = genericOp.getNumPayloadInductionVariables();
2108     SmallVector<Value, 4> returnedArgs;
2109     for (Value yieldVal : yieldOp.values()) {
2110       auto yieldArg = yieldVal.dyn_cast<BlockArgument>();
2111       if (!yieldArg || yieldArg.getOwner() != &body)
2112         return failure();
2113       unsigned argumentNumber = yieldArg.getArgNumber();
2114       if (argumentNumber < numIndexArgs)
2115         return failure();
2116       returnedArgs.push_back(op->getOperand(argumentNumber - numIndexArgs));
2117     }
2118     if (returnedArgs.size() != genericOp.getOperation()->getNumResults())
2119       return failure();
2120     rewriter.replaceOp(genericOp, returnedArgs);
2121     return success();
2122   }
2123 };
2124 } // namespace
2125 
2126 #define CANONICALIZERS_AND_FOLDERS(XXX)                                        \
2127   void XXX::getCanonicalizationPatterns(OwningRewritePatternList &results,     \
2128                                         MLIRContext *context) {                \
2129     results.insert<DeduplicateInputs, EraseDeadLinalgOp, FoldTensorCastOp,     \
2130                    RemoveIdentityLinalgOps>();                                 \
2131     results.insert<ReplaceDimOfLinalgOpResult>(context);                       \
2132   }                                                                            \
2133                                                                                \
2134   LogicalResult XXX::fold(ArrayRef<Attribute>,                                 \
2135                           SmallVectorImpl<OpFoldResult> &) {                   \
2136     return foldMemRefCast(*this);                                              \
2137   }
2138 
2139 CANONICALIZERS_AND_FOLDERS(ConvOp)
2140 CANONICALIZERS_AND_FOLDERS(PoolingMaxOp)
2141 CANONICALIZERS_AND_FOLDERS(PoolingMinOp)
2142 CANONICALIZERS_AND_FOLDERS(PoolingSumOp)
2143 CANONICALIZERS_AND_FOLDERS(CopyOp)
2144 CANONICALIZERS_AND_FOLDERS(FillOp)
2145 CANONICALIZERS_AND_FOLDERS(GenericOp)
2146 CANONICALIZERS_AND_FOLDERS(IndexedGenericOp)
2147 
2148 // All named ops canonicalizers and folders are auto-generated in the
2149 // .cpp.inc.
2150