1 //===----------------------------------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "mlir/Dialect/StandardOps/Utils/Utils.h"
10 #include "mlir/Dialect/Tensor/IR/Tensor.h"
11 #include "mlir/Dialect/Utils/StaticValueUtils.h"
12 #include "mlir/IR/BlockAndValueMapping.h"
13 #include "mlir/IR/Builders.h"
14 #include "mlir/IR/Matchers.h"
15 #include "mlir/IR/PatternMatch.h"
16 #include "mlir/IR/TypeUtilities.h"
17 #include "llvm/ADT/STLExtras.h"
18 
19 using namespace mlir;
20 using namespace mlir::tensor;
21 
22 /// Materialize a single constant operation from a given attribute value with
23 /// the desired resultant type.
24 Operation *TensorDialect::materializeConstant(OpBuilder &builder,
25                                               Attribute value, Type type,
26                                               Location loc) {
27   return builder.create<mlir::ConstantOp>(loc, type, value);
28 }
29 
30 //===----------------------------------------------------------------------===//
31 // CastOp
32 //===----------------------------------------------------------------------===//
33 
34 /// Determines whether tensor::CastOp casts to a more dynamic version of the
35 /// source tensor. This is useful to fold a tensor.cast into a consuming op and
36 /// implement canonicalization patterns for ops in different dialects that may
37 /// consume the results of tensor.cast operations. Such foldable tensor.cast
38 /// operations are typically inserted as `slice` ops and are canonicalized,
39 /// to preserve the type compatibility of their uses.
40 ///
41 /// Returns true when all conditions are met:
42 /// 1. source and result are ranked tensors with same element type and rank.
43 /// 2. the tensor type has more static information than the result
44 ///
45 /// Example:
46 /// ```mlir
47 ///   %1 = tensor.cast %0 : tensor<8x16xf32> to tensor<?x?xf32>
48 ///   %2 = consumer %1 ... : tensor<?x?xf32> ...
49 /// ```
50 ///
51 /// folds into:
52 ///
53 /// ```mlir
54 ///   %2 = consumer %0 ... : tensor<8x16xf32> ...
55 /// ```
56 bool mlir::tensor::canFoldIntoConsumerOp(CastOp castOp) {
57   if (!castOp)
58     return false;
59 
60   RankedTensorType sourceType =
61       castOp.source().getType().dyn_cast<RankedTensorType>();
62   RankedTensorType resultType = castOp.getType().dyn_cast<RankedTensorType>();
63 
64   // Requires RankedTensorType.
65   if (!sourceType || !resultType)
66     return false;
67 
68   // Requires same elemental type.
69   if (sourceType.getElementType() != resultType.getElementType())
70     return false;
71 
72   // Requires same rank.
73   if (sourceType.getRank() != resultType.getRank())
74     return false;
75 
76   // If cast is towards more static sizes along any dimension, don't fold.
77   for (auto t : llvm::zip(sourceType.getShape(), resultType.getShape())) {
78     if (ShapedType::isDynamic(std::get<0>(t)) &&
79         !ShapedType::isDynamic(std::get<1>(t)))
80       return false;
81   }
82 
83   return true;
84 }
85 
86 /// Performs folding of any operand of `op` if it comes from a tensor::CastOp
87 /// that can be folded.
88 LogicalResult mlir::tensor::foldTensorCast(Operation *op) {
89   bool folded = false;
90   for (OpOperand &operand : op->getOpOperands()) {
91     auto castOp = operand.get().getDefiningOp<tensor::CastOp>();
92     if (castOp && tensor::canFoldIntoConsumerOp(castOp)) {
93       operand.set(castOp.getOperand());
94       folded = true;
95     }
96   }
97   return success(folded);
98 }
99 
100 bool CastOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
101   if (inputs.size() != 1 || outputs.size() != 1)
102     return false;
103   Type a = inputs.front(), b = outputs.front();
104   auto aT = a.dyn_cast<TensorType>();
105   auto bT = b.dyn_cast<TensorType>();
106   if (!aT || !bT)
107     return false;
108 
109   if (aT.getElementType() != bT.getElementType())
110     return false;
111 
112   return succeeded(verifyCompatibleShape(aT, bT));
113 }
114 
115 /// Compute a TensorType that has the joined shape knowledge of the two
116 /// given TensorTypes. The element types need to match.
117 static TensorType joinShapes(TensorType one, TensorType two) {
118   assert(one.getElementType() == two.getElementType());
119 
120   if (!one.hasRank())
121     return two;
122   if (!two.hasRank())
123     return one;
124 
125   int64_t rank = one.getRank();
126   if (rank != two.getRank())
127     return {};
128 
129   SmallVector<int64_t, 4> join;
130   join.reserve(rank);
131   for (int64_t i = 0; i < rank; ++i) {
132     if (one.isDynamicDim(i)) {
133       join.push_back(two.getDimSize(i));
134       continue;
135     }
136     if (two.isDynamicDim(i)) {
137       join.push_back(one.getDimSize(i));
138       continue;
139     }
140     if (one.getDimSize(i) != two.getDimSize(i))
141       return {};
142     join.push_back(one.getDimSize(i));
143   }
144   return RankedTensorType::get(join, one.getElementType());
145 }
146 
147 namespace {
148 
149 /// Replaces chains of two tensor.cast operations by a single tensor.cast
150 /// operation if doing so does not remove runtime constraints.
151 struct ChainedTensorCast : public OpRewritePattern<CastOp> {
152   using OpRewritePattern<CastOp>::OpRewritePattern;
153 
154   LogicalResult matchAndRewrite(CastOp tensorCast,
155                                 PatternRewriter &rewriter) const final {
156     auto tensorCastOperand = tensorCast.getOperand().getDefiningOp<CastOp>();
157 
158     if (!tensorCastOperand)
159       return failure();
160 
161     auto sourceType =
162         tensorCastOperand.getOperand().getType().cast<TensorType>();
163     auto intermediateType = tensorCastOperand.getType().cast<TensorType>();
164     auto resultType = tensorCast.getType().cast<TensorType>();
165 
166     // We can remove the intermediate cast if joining all three produces the
167     // same result as just joining the source and result shapes.
168     auto firstJoin =
169         joinShapes(joinShapes(sourceType, intermediateType), resultType);
170 
171     // The join might not exist if the cast sequence would fail at runtime.
172     if (!firstJoin)
173       return failure();
174 
175     // The newJoin always exists if the above join exists, it might just contain
176     // less information. If so, we cannot drop the intermediate cast, as doing
177     // so would remove runtime checks.
178     auto newJoin = joinShapes(sourceType, resultType);
179     if (firstJoin != newJoin)
180       return failure();
181 
182     rewriter.replaceOpWithNewOp<CastOp>(tensorCast, resultType,
183                                         tensorCastOperand.getOperand());
184     return success();
185   }
186 };
187 
188 } // namespace
189 
190 void CastOp::getCanonicalizationPatterns(RewritePatternSet &results,
191                                          MLIRContext *context) {
192   results.add<ChainedTensorCast>(context);
193 }
194 
195 //===----------------------------------------------------------------------===//
196 // DimOp
197 //===----------------------------------------------------------------------===//
198 
199 void DimOp::build(OpBuilder &builder, OperationState &result, Value source,
200                   int64_t index) {
201   auto loc = result.location;
202   Value indexValue = builder.create<ConstantIndexOp>(loc, index);
203   build(builder, result, source, indexValue);
204 }
205 
206 void DimOp::build(OpBuilder &builder, OperationState &result, Value source,
207                   Value index) {
208   auto indexTy = builder.getIndexType();
209   build(builder, result, indexTy, source, index);
210 }
211 
212 Optional<int64_t> DimOp::getConstantIndex() {
213   if (auto constantOp = index().getDefiningOp<ConstantOp>())
214     return constantOp.getValue().cast<IntegerAttr>().getInt();
215   return {};
216 }
217 
218 static LogicalResult verify(DimOp op) {
219   // Assume unknown index to be in range.
220   Optional<int64_t> index = op.getConstantIndex();
221   if (!index.hasValue())
222     return success();
223 
224   // Check that constant index is not knowingly out of range.
225   auto type = op.source().getType();
226   if (auto tensorType = type.dyn_cast<RankedTensorType>()) {
227     if (index.getValue() >= tensorType.getRank())
228       return op.emitOpError("index is out of range");
229   } else if (type.isa<UnrankedTensorType>()) {
230     // Assume index to be in range.
231   } else {
232     llvm_unreachable("expected operand with tensor type");
233   }
234   return success();
235 }
236 
237 OpFoldResult DimOp::fold(ArrayRef<Attribute> operands) {
238   // All forms of folding require a known index.
239   auto index = operands[1].dyn_cast_or_null<IntegerAttr>();
240   if (!index)
241     return {};
242 
243   // Folding for unranked types (UnrankedTensorType) is not supported.
244   auto tensorType = source().getType().dyn_cast<RankedTensorType>();
245   if (!tensorType)
246     return {};
247 
248   // Fold if the shape extent along the given index is known.
249   if (!tensorType.isDynamicDim(index.getInt())) {
250     Builder builder(getContext());
251     return builder.getIndexAttr(tensorType.getShape()[index.getInt()]);
252   }
253 
254   Operation *definingOp = source().getDefiningOp();
255 
256   // Fold dim to the operand of tensor.generate.
257   if (auto fromElements = dyn_cast_or_null<tensor::GenerateOp>(definingOp)) {
258     auto resultType =
259         fromElements.getResult().getType().cast<RankedTensorType>();
260     // The case where the type encodes the size of the dimension is handled
261     // above.
262     assert(resultType.getShape()[index.getInt()] ==
263            RankedTensorType::kDynamicSize);
264 
265     // Find the operand of the fromElements that corresponds to this index.
266     auto dynExtents = fromElements.dynamicExtents().begin();
267     for (auto dim : resultType.getShape().take_front(index.getInt()))
268       if (dim == RankedTensorType::kDynamicSize)
269         dynExtents++;
270 
271     return Value{*dynExtents};
272   }
273 
274   // dim(insert_slice.result()) -> dim(insert_slice.dest())
275   if (auto insertSliceOp =
276           dyn_cast_or_null<tensor::InsertSliceOp>(definingOp)) {
277     this->sourceMutable().assign(insertSliceOp.dest());
278     return getResult();
279   }
280 
281   // The size at the given index is now known to be a dynamic size.
282   unsigned unsignedIndex = index.getValue().getZExtValue();
283 
284   if (auto sizeInterface =
285           dyn_cast_or_null<OffsetSizeAndStrideOpInterface>(definingOp)) {
286     int64_t nthDynamicIndex =
287         tensorType.getRelativeIndexOfDynamicDim(unsignedIndex);
288     return sizeInterface.sizes()[nthDynamicIndex];
289   }
290 
291   // dim(cast) -> dim
292   if (succeeded(foldTensorCast(*this)))
293     return getResult();
294 
295   return {};
296 }
297 
298 namespace {
299 /// Fold dim of a cast into the dim of the source of the tensor cast.
300 struct DimOfCastOp : public OpRewritePattern<DimOp> {
301   using OpRewritePattern<DimOp>::OpRewritePattern;
302 
303   LogicalResult matchAndRewrite(DimOp dimOp,
304                                 PatternRewriter &rewriter) const override {
305     auto castOp = dimOp.source().getDefiningOp<CastOp>();
306     if (!castOp)
307       return failure();
308     Value newSource = castOp.getOperand();
309     rewriter.replaceOpWithNewOp<DimOp>(dimOp, newSource, dimOp.index());
310     return success();
311   }
312 };
313 } // end anonymous namespace.
314 
315 void DimOp::getCanonicalizationPatterns(RewritePatternSet &results,
316                                         MLIRContext *context) {
317   results.add<DimOfCastOp>(context);
318 }
319 
320 //===----------------------------------------------------------------------===//
321 // ExtractOp
322 //===----------------------------------------------------------------------===//
323 
324 static LogicalResult verify(ExtractOp op) {
325   // Verify the # indices match if we have a ranked type.
326   if (auto tensorType = op.tensor().getType().dyn_cast<RankedTensorType>())
327     if (tensorType.getRank() != static_cast<int64_t>(op.indices().size()))
328       return op.emitOpError("incorrect number of indices for extract_element");
329 
330   return success();
331 }
332 
333 OpFoldResult ExtractOp::fold(ArrayRef<Attribute> operands) {
334   // The tensor operand must be a known constant.
335   Attribute tensor = operands.front();
336   if (!tensor)
337     return {};
338   // If this is a splat elements attribute, simply return the value. All of the
339   // elements of a splat attribute are the same.
340   if (auto splatTensor = tensor.dyn_cast<SplatElementsAttr>())
341     return splatTensor.getSplatValue();
342 
343   // Otherwise, collect the constant indices into the tensor.
344   SmallVector<uint64_t, 8> indices;
345   for (Attribute indice : llvm::drop_begin(operands, 1)) {
346     if (!indice || !indice.isa<IntegerAttr>())
347       return {};
348     indices.push_back(indice.cast<IntegerAttr>().getInt());
349   }
350 
351   // If this is an elements attribute, query the value at the given indices.
352   auto elementsAttr = tensor.dyn_cast<ElementsAttr>();
353   if (elementsAttr && elementsAttr.isValidIndex(indices))
354     return elementsAttr.getValue(indices);
355   return {};
356 }
357 
358 //===----------------------------------------------------------------------===//
359 // FromElementsOp
360 //===----------------------------------------------------------------------===//
361 
362 void FromElementsOp::build(OpBuilder &builder, OperationState &result,
363                            Type elementType, ValueRange elements) {
364   Type resultTy = RankedTensorType::get({static_cast<int64_t>(elements.size())},
365                                         elementType);
366   result.addOperands(elements);
367   result.addTypes(resultTy);
368 }
369 
370 void FromElementsOp::build(OpBuilder &builder, OperationState &result,
371                            ValueRange elements) {
372   assert(!elements.empty() && "expected at least one element");
373   build(builder, result, elements.front().getType(), elements);
374 }
375 
376 OpFoldResult FromElementsOp::fold(ArrayRef<Attribute> operands) {
377   if (!llvm::is_contained(operands, nullptr))
378     return DenseElementsAttr::get(getType(), operands);
379   return {};
380 }
381 
382 namespace {
383 
384 // Canonicalizes the pattern of the form
385 //
386 // %tensor = tensor.from_elements(%element) : (i32) -> tensor<1xi32>
387 // %extracted_element = tensor.extract %tensor[%c0] : tensor<1xi32>
388 //
389 // to just %element.
390 struct ExtractElementFromTensorFromElements
391     : public OpRewritePattern<tensor::ExtractOp> {
392   using OpRewritePattern<tensor::ExtractOp>::OpRewritePattern;
393 
394   LogicalResult matchAndRewrite(tensor::ExtractOp extract,
395                                 PatternRewriter &rewriter) const final {
396     if (extract.indices().size() != 1)
397       return failure();
398 
399     auto tensorFromElements = extract.tensor().getDefiningOp<FromElementsOp>();
400     if (tensorFromElements == nullptr)
401       return failure();
402 
403     APInt index;
404     if (!matchPattern(*extract.indices().begin(), m_ConstantInt(&index)))
405       return failure();
406     // Prevent out of bounds accesses. This can happen in invalid code that will
407     // never execute.
408     if (tensorFromElements->getNumOperands() <= index.getZExtValue() ||
409         index.getSExtValue() < 0)
410       return failure();
411     rewriter.replaceOp(extract,
412                        tensorFromElements.getOperand(index.getZExtValue()));
413     return success();
414   }
415 };
416 
417 } // namespace
418 
419 void FromElementsOp::getCanonicalizationPatterns(RewritePatternSet &results,
420                                                  MLIRContext *context) {
421   results.add<ExtractElementFromTensorFromElements>(context);
422 }
423 
424 //===----------------------------------------------------------------------===//
425 // InsertOp
426 //===----------------------------------------------------------------------===//
427 
428 static LogicalResult verify(InsertOp op) {
429   // Verify the # indices match if we have a ranked type.
430   if (auto destType = op.dest().getType().dyn_cast<RankedTensorType>())
431     if (destType.getRank() != static_cast<int64_t>(op.indices().size()))
432       return op.emitOpError("incorrect number of indices");
433   return success();
434 }
435 
436 OpFoldResult InsertOp::fold(ArrayRef<Attribute> operands) {
437   Attribute scalar = operands[0];
438   Attribute dest = operands[1];
439   if (scalar && dest)
440     if (auto splatDest = dest.dyn_cast<SplatElementsAttr>())
441       if (scalar == splatDest.getSplatValue())
442         return dest;
443   return {};
444 }
445 
446 //===----------------------------------------------------------------------===//
447 // GenerateOp
448 //===----------------------------------------------------------------------===//
449 
450 static LogicalResult verify(GenerateOp op) {
451   // Ensure that the tensor type has as many dynamic dimensions as are specified
452   // by the operands.
453   RankedTensorType resultTy = op.getType().cast<RankedTensorType>();
454   if (op.getNumOperands() != resultTy.getNumDynamicDims())
455     return op.emitError("must have as many index operands as dynamic extents "
456                         "in the result type");
457 
458   // Ensure that region arguments span the index space.
459   if (!llvm::all_of(op.body().getArgumentTypes(),
460                     [](Type ty) { return ty.isIndex(); }))
461     return op.emitError("all body arguments must be index");
462   if (op.body().getNumArguments() != resultTy.getRank())
463     return op.emitError("must have one body argument per input dimension");
464 
465   // Ensure that the region yields an element of the right type.
466   auto yieldOp =
467       llvm::cast<YieldOp>(op.body().getBlocks().front().getTerminator());
468   if (yieldOp.value().getType() != resultTy.getElementType())
469     return op.emitOpError(
470         "body must be terminated with a `yield` operation of the tensor "
471         "element type");
472 
473   return success();
474 }
475 
476 void GenerateOp::build(
477     OpBuilder &b, OperationState &result, Type resultTy,
478     ValueRange dynamicExtents,
479     function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuilder) {
480   build(b, result, resultTy, dynamicExtents);
481 
482   // Build and populate body.
483   OpBuilder::InsertionGuard guard(b);
484   Region *bodyRegion = result.regions.front().get();
485   auto rank = resultTy.cast<RankedTensorType>().getRank();
486   SmallVector<Type, 2> argumentTypes(rank, b.getIndexType());
487   Block *bodyBlock =
488       b.createBlock(bodyRegion, bodyRegion->end(), argumentTypes);
489   bodyBuilder(b, result.location, bodyBlock->getArguments());
490 }
491 
492 namespace {
493 
494 /// Canonicalizes tensor.generate operations with a constant
495 /// operand into the equivalent operation with the operand expressed in the
496 /// result type, instead. We also insert a type cast to make sure that the
497 /// resulting IR is still well-typed.
498 struct StaticTensorGenerate : public OpRewritePattern<GenerateOp> {
499   using OpRewritePattern<GenerateOp>::OpRewritePattern;
500 
501   LogicalResult matchAndRewrite(GenerateOp tensorFromElements,
502                                 PatternRewriter &rewriter) const final {
503     auto resultType =
504         tensorFromElements.getResult().getType().cast<RankedTensorType>();
505 
506     if (resultType.hasStaticShape())
507       return failure();
508 
509     SmallVector<Value, 4> newOperands;
510     SmallVector<int64_t, 4> newShape;
511     auto operandsIt = tensorFromElements.dynamicExtents().begin();
512 
513     for (int64_t dim : resultType.getShape()) {
514       if (dim != RankedTensorType::kDynamicSize) {
515         newShape.push_back(dim);
516         continue;
517       }
518       APInt index;
519       if (!matchPattern(*operandsIt, m_ConstantInt(&index))) {
520         newShape.push_back(RankedTensorType::kDynamicSize);
521         newOperands.push_back(*operandsIt++);
522         continue;
523       }
524       newShape.push_back(index.getSExtValue());
525       operandsIt++;
526     }
527 
528     if (newOperands.size() == tensorFromElements.dynamicExtents().size())
529       return failure();
530 
531     auto loc = tensorFromElements.getLoc();
532     auto newOp = rewriter.create<GenerateOp>(
533         loc, RankedTensorType::get(newShape, resultType.getElementType()),
534         newOperands);
535     rewriter.inlineRegionBefore(tensorFromElements.body(), newOp.body(),
536                                 newOp.body().begin());
537     rewriter.replaceOpWithNewOp<tensor::CastOp>(tensorFromElements, resultType,
538                                                 newOp);
539     return success();
540   }
541 };
542 
543 /// Canonicalizes the pattern of the form
544 ///
545 /// %tensor = tensor.generate %x {
546 ///   ^bb0(%arg0: index):  // no predecessors
547 ///   <computation>
548 ///   yield %1 : index
549 /// } : tensor<?xindex>
550 /// %extracted_element = tensor.extract %tensor[%c0] : tensor<?xi32>
551 ///
552 /// to just <computation> with %arg0 replaced by %c0. We only do this if the
553 /// tensor.generate operation has no side-effects.
554 struct ExtractFromTensorGenerate : public OpRewritePattern<tensor::ExtractOp> {
555   using OpRewritePattern<tensor::ExtractOp>::OpRewritePattern;
556 
557   LogicalResult matchAndRewrite(tensor::ExtractOp extract,
558                                 PatternRewriter &rewriter) const final {
559     auto tensorFromElements = extract.tensor().getDefiningOp<GenerateOp>();
560     if (!tensorFromElements || !wouldOpBeTriviallyDead(tensorFromElements))
561       return failure();
562 
563     BlockAndValueMapping mapping;
564     Block *body = tensorFromElements.getBody();
565     mapping.map(body->getArguments(), extract.indices());
566     for (auto &op : body->without_terminator())
567       rewriter.clone(op, mapping);
568 
569     auto yield = cast<YieldOp>(body->getTerminator());
570 
571     rewriter.replaceOp(extract, mapping.lookupOrDefault(yield.value()));
572     return success();
573   }
574 };
575 
576 /// Canonicalizes the pattern of the form
577 ///
578 /// %val = tensor.cast %source : : tensor<?xi32> to tensor<2xi32>
579 /// %extracted_element = tensor.extract %val[%c0] : tensor<2xi32>
580 ///
581 /// to
582 ///
583 /// %extracted_element = tensor.extract %source[%c0] : tensor<?xi32>
584 struct ExtractFromTensorCast : public OpRewritePattern<tensor::ExtractOp> {
585   using OpRewritePattern<tensor::ExtractOp>::OpRewritePattern;
586 
587   LogicalResult matchAndRewrite(tensor::ExtractOp extract,
588                                 PatternRewriter &rewriter) const final {
589     auto tensorCast = extract.tensor().getDefiningOp<tensor::CastOp>();
590     if (!tensorCast)
591       return failure();
592 
593     rewriter.replaceOpWithNewOp<tensor::ExtractOp>(extract, tensorCast.source(),
594                                                    extract.indices());
595     return success();
596   }
597 };
598 
599 } // namespace
600 
601 void GenerateOp::getCanonicalizationPatterns(RewritePatternSet &results,
602                                              MLIRContext *context) {
603   // TODO: Move extract patterns to tensor::ExtractOp.
604   results.add<ExtractFromTensorGenerate, ExtractFromTensorCast,
605               StaticTensorGenerate>(context);
606 }
607 
608 //===----------------------------------------------------------------------===//
609 // ReshapeOp
610 //===----------------------------------------------------------------------===//
611 
612 static int64_t GetNumElements(ShapedType type) {
613   int64_t numElements = 1;
614   for (auto dim : type.getShape())
615     numElements *= dim;
616   return numElements;
617 }
618 
619 static LogicalResult verify(ReshapeOp op) {
620   TensorType operandType = op.source().getType().cast<TensorType>();
621   TensorType resultType = op.result().getType().cast<TensorType>();
622 
623   if (operandType.getElementType() != resultType.getElementType())
624     return op.emitOpError("element types of source and destination tensor "
625                           "types should be the same");
626 
627   int64_t shapeSize =
628       op.shape().getType().cast<RankedTensorType>().getDimSize(0);
629   auto resultRankedType = resultType.dyn_cast<RankedTensorType>();
630   auto operandRankedType = operandType.dyn_cast<RankedTensorType>();
631 
632   if (resultRankedType) {
633     if (operandRankedType && resultRankedType.hasStaticShape() &&
634         operandRankedType.hasStaticShape()) {
635       if (GetNumElements(operandRankedType) != GetNumElements(resultRankedType))
636         return op.emitOpError("source and destination tensor should have the "
637                               "same number of elements");
638     }
639     if (shapeSize == TensorType::kDynamicSize)
640       return op.emitOpError("cannot use shape operand with dynamic length to "
641                             "reshape to statically-ranked tensor type");
642     if (shapeSize != resultRankedType.getRank())
643       return op.emitOpError(
644           "length of shape operand differs from the result's tensor rank");
645   }
646   return success();
647 }
648 
649 //===----------------------------------------------------------------------===//
650 // ExtractSliceOp
651 //===----------------------------------------------------------------------===//
652 
653 /// An extract_slice op result type can be fully inferred from the source type
654 /// and the static representation of offsets, sizes and strides. Special
655 /// sentinels encode the dynamic case.
656 Type ExtractSliceOp::inferResultType(RankedTensorType sourceRankedTensorType,
657                                      ArrayRef<int64_t> leadingStaticOffsets,
658                                      ArrayRef<int64_t> leadingStaticSizes,
659                                      ArrayRef<int64_t> leadingStaticStrides) {
660   // An extract_slice op may specify only a leading subset of offset/sizes/
661   // strides in which case we complete with offset=0, sizes from memref type and
662   // strides=1.
663   unsigned rank = sourceRankedTensorType.getRank();
664   assert(leadingStaticSizes.size() <= rank &&
665          "unexpected leadingStaticSizes overflow");
666   auto staticSizes = llvm::to_vector<4>(leadingStaticSizes);
667   unsigned numTrailingSizes = rank - staticSizes.size();
668   llvm::append_range(staticSizes, sourceRankedTensorType.getShape().take_back(
669                                       numTrailingSizes));
670   return RankedTensorType::get(staticSizes,
671                                sourceRankedTensorType.getElementType());
672 }
673 
674 Type ExtractSliceOp::inferResultType(
675     RankedTensorType sourceRankedTensorType,
676     ArrayRef<OpFoldResult> leadingStaticOffsets,
677     ArrayRef<OpFoldResult> leadingStaticSizes,
678     ArrayRef<OpFoldResult> leadingStaticStrides) {
679   SmallVector<int64_t> staticOffsets, staticSizes, staticStrides;
680   SmallVector<Value> dynamicOffsets, dynamicSizes, dynamicStrides;
681   dispatchIndexOpFoldResults(leadingStaticOffsets, dynamicOffsets,
682                              staticOffsets, ShapedType::kDynamicStrideOrOffset);
683   dispatchIndexOpFoldResults(leadingStaticSizes, dynamicSizes, staticSizes,
684                              ShapedType::kDynamicSize);
685   dispatchIndexOpFoldResults(leadingStaticStrides, dynamicStrides,
686                              staticStrides, ShapedType::kDynamicStrideOrOffset);
687   return ExtractSliceOp::inferResultType(sourceRankedTensorType, staticOffsets,
688                                          staticSizes, staticStrides);
689 }
690 
691 /// An extract_slice op result type can be fully inferred from the source type
692 /// and the static representation of offsets, sizes and strides. Special
693 /// sentinels encode the dynamic case.
694 Type ExtractSliceOp::inferRankReducedResultType(
695     unsigned resultRank, RankedTensorType sourceRankedTensorType,
696     ArrayRef<int64_t> leadingStaticOffsets,
697     ArrayRef<int64_t> leadingStaticSizes,
698     ArrayRef<int64_t> leadingStaticStrides) {
699   auto inferredType =
700       inferResultType(sourceRankedTensorType, leadingStaticOffsets,
701                       leadingStaticSizes, leadingStaticStrides)
702           .cast<RankedTensorType>();
703   int rankDiff = inferredType.getRank() - resultRank;
704   if (rankDiff > 0) {
705     auto shape = inferredType.getShape();
706     llvm::SmallDenseSet<unsigned> dimsToProject;
707     mlir::getPositionsOfShapeOne(rankDiff, shape, dimsToProject);
708     SmallVector<int64_t> projectedShape;
709     for (unsigned pos = 0, e = shape.size(); pos < e; ++pos)
710       if (!dimsToProject.contains(pos))
711         projectedShape.push_back(shape[pos]);
712     inferredType =
713         RankedTensorType::get(projectedShape, inferredType.getElementType());
714   }
715   return inferredType;
716 }
717 
718 Type ExtractSliceOp::inferRankReducedResultType(
719     unsigned resultRank, RankedTensorType sourceRankedTensorType,
720     ArrayRef<OpFoldResult> leadingStaticOffsets,
721     ArrayRef<OpFoldResult> leadingStaticSizes,
722     ArrayRef<OpFoldResult> leadingStaticStrides) {
723   SmallVector<int64_t> staticOffsets, staticSizes, staticStrides;
724   SmallVector<Value> dynamicOffsets, dynamicSizes, dynamicStrides;
725   dispatchIndexOpFoldResults(leadingStaticOffsets, dynamicOffsets,
726                              staticOffsets, ShapedType::kDynamicStrideOrOffset);
727   dispatchIndexOpFoldResults(leadingStaticSizes, dynamicSizes, staticSizes,
728                              ShapedType::kDynamicSize);
729   dispatchIndexOpFoldResults(leadingStaticStrides, dynamicStrides,
730                              staticStrides, ShapedType::kDynamicStrideOrOffset);
731   return ExtractSliceOp::inferRankReducedResultType(
732       resultRank, sourceRankedTensorType, staticOffsets, staticSizes,
733       staticStrides);
734 }
735 
736 /// Build an ExtractSliceOp with mixed static and dynamic entries and custom
737 /// result type. If the type passed is nullptr, it is inferred.
738 void ExtractSliceOp::build(OpBuilder &b, OperationState &result,
739                            RankedTensorType resultType, Value source,
740                            ArrayRef<OpFoldResult> offsets,
741                            ArrayRef<OpFoldResult> sizes,
742                            ArrayRef<OpFoldResult> strides,
743                            ArrayRef<NamedAttribute> attrs) {
744   SmallVector<int64_t> staticOffsets, staticSizes, staticStrides;
745   SmallVector<Value> dynamicOffsets, dynamicSizes, dynamicStrides;
746   dispatchIndexOpFoldResults(offsets, dynamicOffsets, staticOffsets,
747                              ShapedType::kDynamicStrideOrOffset);
748   dispatchIndexOpFoldResults(sizes, dynamicSizes, staticSizes,
749                              ShapedType::kDynamicSize);
750   dispatchIndexOpFoldResults(strides, dynamicStrides, staticStrides,
751                              ShapedType::kDynamicStrideOrOffset);
752   auto sourceRankedTensorType = source.getType().cast<RankedTensorType>();
753   // Structuring implementation this way avoids duplication between builders.
754   if (!resultType) {
755     resultType =
756         ExtractSliceOp::inferResultType(sourceRankedTensorType, staticOffsets,
757                                         staticSizes, staticStrides)
758             .cast<RankedTensorType>();
759   }
760   build(b, result, resultType, source, dynamicOffsets, dynamicSizes,
761         dynamicStrides, b.getI64ArrayAttr(staticOffsets),
762         b.getI64ArrayAttr(staticSizes), b.getI64ArrayAttr(staticStrides));
763   result.addAttributes(attrs);
764 }
765 
766 /// Build an ExtractSliceOp with mixed static and dynamic entries and inferred
767 /// result type.
768 void ExtractSliceOp::build(OpBuilder &b, OperationState &result, Value source,
769                            ArrayRef<OpFoldResult> offsets,
770                            ArrayRef<OpFoldResult> sizes,
771                            ArrayRef<OpFoldResult> strides,
772                            ArrayRef<NamedAttribute> attrs) {
773   build(b, result, RankedTensorType(), source, offsets, sizes, strides, attrs);
774 }
775 
776 /// Build an ExtractSliceOp with dynamic entries and custom result type. If the
777 /// type passed is nullptr, it is inferred.
778 void ExtractSliceOp::build(OpBuilder &b, OperationState &result,
779                            RankedTensorType resultType, Value source,
780                            ValueRange offsets, ValueRange sizes,
781                            ValueRange strides, ArrayRef<NamedAttribute> attrs) {
782   SmallVector<OpFoldResult> offsetValues = llvm::to_vector<4>(
783       llvm::map_range(offsets, [](Value v) -> OpFoldResult { return v; }));
784   SmallVector<OpFoldResult> sizeValues = llvm::to_vector<4>(
785       llvm::map_range(sizes, [](Value v) -> OpFoldResult { return v; }));
786   SmallVector<OpFoldResult> strideValues = llvm::to_vector<4>(
787       llvm::map_range(strides, [](Value v) -> OpFoldResult { return v; }));
788   build(b, result, resultType, source, offsetValues, sizeValues, strideValues);
789 }
790 
791 /// Build an ExtractSliceOp with dynamic entries and inferred result type.
792 void ExtractSliceOp::build(OpBuilder &b, OperationState &result, Value source,
793                            ValueRange offsets, ValueRange sizes,
794                            ValueRange strides, ArrayRef<NamedAttribute> attrs) {
795   build(b, result, RankedTensorType(), source, offsets, sizes, strides, attrs);
796 }
797 
798 enum SliceVerificationResult {
799   Success,
800   RankTooLarge,
801   SizeMismatch,
802   ElemTypeMismatch,
803 };
804 
805 /// Checks if `original` Type type can be rank reduced to `reduced` type.
806 /// This function is slight variant of `is subsequence` algorithm where
807 /// not matching dimension must be 1.
808 static SliceVerificationResult
809 isRankReducedType(Type originalType, Type candidateReducedType,
810                   std::string *errMsg = nullptr) {
811   if (originalType == candidateReducedType)
812     return SliceVerificationResult::Success;
813   if (!originalType.isa<RankedTensorType>())
814     return SliceVerificationResult::Success;
815   if (originalType.isa<RankedTensorType>() &&
816       !candidateReducedType.isa<RankedTensorType>())
817     return SliceVerificationResult::Success;
818 
819   ShapedType originalShapedType = originalType.cast<ShapedType>();
820   ShapedType candidateReducedShapedType =
821       candidateReducedType.cast<ShapedType>();
822 
823   // Rank and size logic is valid for all ShapedTypes.
824   ArrayRef<int64_t> originalShape = originalShapedType.getShape();
825   ArrayRef<int64_t> candidateReducedShape =
826       candidateReducedShapedType.getShape();
827   unsigned originalRank = originalShape.size(),
828            candidateReducedRank = candidateReducedShape.size();
829   if (candidateReducedRank > originalRank)
830     return SliceVerificationResult::RankTooLarge;
831 
832   auto optionalUnusedDimsMask =
833       computeRankReductionMask(originalShape, candidateReducedShape);
834 
835   // Sizes cannot be matched in case empty vector is returned.
836   if (!optionalUnusedDimsMask.hasValue())
837     return SliceVerificationResult::SizeMismatch;
838 
839   if (originalShapedType.getElementType() !=
840       candidateReducedShapedType.getElementType())
841     return SliceVerificationResult::ElemTypeMismatch;
842 
843   // We are done for the tensor case.
844   if (originalType.isa<RankedTensorType>())
845     return SliceVerificationResult::Success;
846 
847   return SliceVerificationResult::Success;
848 }
849 
850 template <typename OpTy>
851 static LogicalResult produceSliceErrorMsg(SliceVerificationResult result,
852                                           OpTy op, Type expectedType,
853                                           StringRef errMsg = "") {
854   auto memrefType = expectedType.cast<ShapedType>();
855   switch (result) {
856   case SliceVerificationResult::Success:
857     return success();
858   case SliceVerificationResult::RankTooLarge:
859     return op.emitError("expected result rank to be smaller or equal to ")
860            << "the source rank. " << errMsg;
861   case SliceVerificationResult::SizeMismatch:
862     return op.emitError("expected result type to be ")
863            << expectedType
864            << " or a rank-reduced version. (mismatch of result sizes) "
865            << errMsg;
866   case SliceVerificationResult::ElemTypeMismatch:
867     return op.emitError("expected result element type to be ")
868            << memrefType.getElementType() << errMsg;
869   }
870   llvm_unreachable("unexpected extract_slice op verification result");
871 }
872 
873 /// Verifier for ExtractSliceOp.
874 static LogicalResult verify(ExtractSliceOp op) {
875   // Verify result type against inferred type.
876   auto expectedType = ExtractSliceOp::inferResultType(
877       op.getSourceType(), extractFromI64ArrayAttr(op.static_offsets()),
878       extractFromI64ArrayAttr(op.static_sizes()),
879       extractFromI64ArrayAttr(op.static_strides()));
880   auto result = isRankReducedType(expectedType, op.getType());
881   return produceSliceErrorMsg(result, op, expectedType);
882 }
883 
884 /// Infer the canonical type of the result of an extract_slice op. Returns a
885 /// type with rank `resultRank` that is either the rank of the rank-reduced
886 /// type, or the non-rank-reduced type.
887 static RankedTensorType
888 getCanonicalSliceResultType(unsigned resultRank, RankedTensorType sourceType,
889                             ArrayRef<OpFoldResult> mixedOffsets,
890                             ArrayRef<OpFoldResult> mixedSizes,
891                             ArrayRef<OpFoldResult> mixedStrides) {
892   auto resultType =
893       ExtractSliceOp::inferRankReducedResultType(
894           resultRank, sourceType, mixedOffsets, mixedSizes, mixedStrides)
895           .cast<RankedTensorType>();
896   if (resultType.getRank() != resultRank) {
897     resultType = ExtractSliceOp::inferResultType(sourceType, mixedOffsets,
898                                                  mixedSizes, mixedStrides)
899                      .cast<RankedTensorType>();
900   }
901   return resultType;
902 }
903 
904 namespace {
905 /// Pattern to rewrite an extract_slice op with tensor::Cast arguments.
906 /// This essentially pushes memref_cast past its consuming slice when
907 /// `canFoldIntoConsumerOp` is true.
908 ///
909 /// Example:
910 /// ```
911 ///   %0 = tensor.cast %V : tensor<16x16xf32> to tensor<?x?xf32>
912 ///   %1 = tensor.extract_slice %0[0, 0][3, 4][1, 1] : tensor<?x?xf32> to
913 ///   tensor<3x4xf32>
914 /// ```
915 /// is rewritten into:
916 /// ```
917 ///   %0 = tensor.extract_slice %V[0, 0][3, 4][1, 1] : tensor<16x16xf32> to
918 ///   tensor<3x4xf32> %1 = tensor.cast %0: tensor<3x4xf32> to tensor<3x4xf32>
919 /// ```
920 class ExtractSliceOpCastFolder final : public OpRewritePattern<ExtractSliceOp> {
921 public:
922   using OpRewritePattern<ExtractSliceOp>::OpRewritePattern;
923 
924   LogicalResult matchAndRewrite(ExtractSliceOp sliceOp,
925                                 PatternRewriter &rewriter) const override {
926     // Any constant operand, just return to let SubViewOpConstantFolder kick in.
927     if (llvm::any_of(sliceOp.getOperands(), [](Value operand) {
928           return matchPattern(operand, matchConstantIndex());
929         }))
930       return failure();
931 
932     auto castOp = sliceOp.source().getDefiningOp<tensor::CastOp>();
933     if (!castOp)
934       return failure();
935 
936     if (!canFoldIntoConsumerOp(castOp))
937       return failure();
938 
939     /// Deduce the type of the result to use for the canonicalized operation.
940     RankedTensorType resultType = getCanonicalSliceResultType(
941         sliceOp.getType().getRank(), sliceOp.getSourceType(),
942         sliceOp.getMixedOffsets(), sliceOp.getMixedSizes(),
943         sliceOp.getMixedStrides());
944     Value newSlice = rewriter.create<ExtractSliceOp>(
945         sliceOp.getLoc(), resultType, castOp.source(), sliceOp.offsets(),
946         sliceOp.sizes(), sliceOp.strides(), sliceOp.static_offsets(),
947         sliceOp.static_sizes(), sliceOp.static_strides());
948     rewriter.replaceOpWithNewOp<tensor::CastOp>(sliceOp, sliceOp.getType(),
949                                                 newSlice);
950     return success();
951   }
952 };
953 } // namespace
954 
955 /// Return the canonical type of the result of an extract_slice op.
956 struct SliceReturnTypeCanonicalizer {
957   RankedTensorType operator()(ExtractSliceOp op,
958                               ArrayRef<OpFoldResult> mixedOffsets,
959                               ArrayRef<OpFoldResult> mixedSizes,
960                               ArrayRef<OpFoldResult> mixedStrides) {
961     return getCanonicalSliceResultType(op.getType().getRank(),
962                                        op.getSourceType(), mixedOffsets,
963                                        mixedSizes, mixedStrides);
964   }
965 };
966 
967 /// A canonicalizer wrapper to replace ExtractSliceOps.
968 struct SliceCanonicalizer {
969   void operator()(PatternRewriter &rewriter, ExtractSliceOp op,
970                   ExtractSliceOp newOp) {
971     Value replacement = newOp.getResult();
972     if (replacement.getType() != op.getType())
973       replacement = rewriter.create<tensor::CastOp>(op.getLoc(), op.getType(),
974                                                     replacement);
975     rewriter.replaceOp(op, replacement);
976   }
977 };
978 
979 void ExtractSliceOp::getCanonicalizationPatterns(RewritePatternSet &results,
980                                                  MLIRContext *context) {
981   results.add<
982       OpWithOffsetSizesAndStridesConstantArgumentFolder<
983           ExtractSliceOp, SliceReturnTypeCanonicalizer, SliceCanonicalizer>,
984       ExtractSliceOpCastFolder>(context);
985 }
986 
987 //
988 static LogicalResult
989 foldIdentityOffsetSizeAndStrideOpInterface(OffsetSizeAndStrideOpInterface op,
990                                            ShapedType shapedType) {
991   OpBuilder b(op.getContext());
992   for (OpFoldResult ofr : op.getMixedOffsets())
993     if (getConstantIntValue(ofr) != static_cast<int64_t>(0))
994       return failure();
995   // Rank-reducing noops only need to inspect the leading dimensions: llvm::zip
996   // is appropriate.
997   auto shape = shapedType.getShape();
998   for (auto it : llvm::zip(op.getMixedSizes(), shape))
999     if (getConstantIntValue(std::get<0>(it)) != std::get<1>(it))
1000       return failure();
1001   for (OpFoldResult ofr : op.getMixedStrides())
1002     if (getConstantIntValue(ofr) != static_cast<int64_t>(1))
1003       return failure();
1004   return success();
1005 }
1006 
1007 OpFoldResult ExtractSliceOp::fold(ArrayRef<Attribute>) {
1008   if (getSourceType() == getType() &&
1009       succeeded(foldIdentityOffsetSizeAndStrideOpInterface(*this, getType())))
1010     return this->source();
1011   return OpFoldResult();
1012 }
1013 
1014 //===----------------------------------------------------------------------===//
1015 // InsertSliceOp
1016 //===----------------------------------------------------------------------===//
1017 
1018 // Build a InsertSliceOp with mixed static and dynamic entries.
1019 void InsertSliceOp::build(OpBuilder &b, OperationState &result, Value source,
1020                           Value dest, ArrayRef<OpFoldResult> offsets,
1021                           ArrayRef<OpFoldResult> sizes,
1022                           ArrayRef<OpFoldResult> strides,
1023                           ArrayRef<NamedAttribute> attrs) {
1024   SmallVector<int64_t> staticOffsets, staticSizes, staticStrides;
1025   SmallVector<Value> dynamicOffsets, dynamicSizes, dynamicStrides;
1026   dispatchIndexOpFoldResults(offsets, dynamicOffsets, staticOffsets,
1027                              ShapedType::kDynamicStrideOrOffset);
1028   dispatchIndexOpFoldResults(sizes, dynamicSizes, staticSizes,
1029                              ShapedType::kDynamicSize);
1030   dispatchIndexOpFoldResults(strides, dynamicStrides, staticStrides,
1031                              ShapedType::kDynamicStrideOrOffset);
1032   build(b, result, dest.getType(), source, dest, dynamicOffsets, dynamicSizes,
1033         dynamicStrides, b.getI64ArrayAttr(staticOffsets),
1034         b.getI64ArrayAttr(staticSizes), b.getI64ArrayAttr(staticStrides));
1035   result.addAttributes(attrs);
1036 }
1037 
1038 // Build a InsertSliceOp with dynamic entries.
1039 void InsertSliceOp::build(OpBuilder &b, OperationState &result, Value source,
1040                           Value dest, ValueRange offsets, ValueRange sizes,
1041                           ValueRange strides, ArrayRef<NamedAttribute> attrs) {
1042   SmallVector<OpFoldResult> offsetValues = llvm::to_vector<4>(
1043       llvm::map_range(offsets, [](Value v) -> OpFoldResult { return v; }));
1044   SmallVector<OpFoldResult> sizeValues = llvm::to_vector<4>(
1045       llvm::map_range(sizes, [](Value v) -> OpFoldResult { return v; }));
1046   SmallVector<OpFoldResult> strideValues = llvm::to_vector<4>(
1047       llvm::map_range(strides, [](Value v) -> OpFoldResult { return v; }));
1048   build(b, result, source, dest, offsetValues, sizeValues, strideValues);
1049 }
1050 
1051 OpFoldResult InsertSliceOp::fold(ArrayRef<Attribute>) {
1052   if (getSourceType().hasStaticShape() && getType().hasStaticShape() &&
1053       getSourceType() == getType() &&
1054       succeeded(foldIdentityOffsetSizeAndStrideOpInterface(*this, getType())))
1055     return this->source();
1056   return OpFoldResult();
1057 }
1058 
1059 namespace {
1060 /// Pattern to rewrite a insert_slice op with constant arguments.
1061 class InsertSliceOpConstantArgumentFolder final
1062     : public OpRewritePattern<InsertSliceOp> {
1063 public:
1064   using OpRewritePattern<InsertSliceOp>::OpRewritePattern;
1065 
1066   LogicalResult matchAndRewrite(InsertSliceOp insertSliceOp,
1067                                 PatternRewriter &rewriter) const override {
1068     // No constant operand, just return.
1069     if (llvm::none_of(insertSliceOp.getOperands(), [](Value operand) {
1070           return matchPattern(operand, matchConstantIndex());
1071         }))
1072       return failure();
1073 
1074     // At least one of offsets/sizes/strides is a new constant.
1075     // Form the new list of operands and constant attributes from the
1076     // existing.
1077     SmallVector<OpFoldResult> mixedOffsets(insertSliceOp.getMixedOffsets());
1078     SmallVector<OpFoldResult> mixedSizes(insertSliceOp.getMixedSizes());
1079     SmallVector<OpFoldResult> mixedStrides(insertSliceOp.getMixedStrides());
1080     canonicalizeSubViewPart(mixedOffsets, ShapedType::isDynamicStrideOrOffset);
1081     canonicalizeSubViewPart(mixedSizes, ShapedType::isDynamic);
1082     canonicalizeSubViewPart(mixedStrides, ShapedType::isDynamicStrideOrOffset);
1083 
1084     // Create the new op in canonical form.
1085     rewriter.replaceOpWithNewOp<InsertSliceOp>(
1086         insertSliceOp, insertSliceOp.source(), insertSliceOp.dest(),
1087         mixedOffsets, mixedSizes, mixedStrides);
1088     return success();
1089   }
1090 };
1091 
1092 /// Fold tensor_casts with insert_slice operations.
1093 struct InsertSliceOpCastFolder final : public OpRewritePattern<InsertSliceOp> {
1094   using OpRewritePattern<InsertSliceOp>::OpRewritePattern;
1095 
1096   LogicalResult matchAndRewrite(InsertSliceOp insertSliceOp,
1097                                 PatternRewriter &rewriter) const override {
1098     if (llvm::any_of(insertSliceOp.getOperands(), [](Value operand) {
1099           return matchPattern(operand, matchConstantIndex());
1100         }))
1101       return failure();
1102 
1103     auto getSourceOfCastOp = [](Value v) -> Optional<Value> {
1104       auto castOp = v.getDefiningOp<tensor::CastOp>();
1105       if (!castOp || !canFoldIntoConsumerOp(castOp))
1106         return llvm::None;
1107       return castOp.source();
1108     };
1109     Optional<Value> sourceCastSource =
1110         getSourceOfCastOp(insertSliceOp.source());
1111     Optional<Value> destCastSource = getSourceOfCastOp(insertSliceOp.dest());
1112     if (!sourceCastSource && !destCastSource)
1113       return failure();
1114 
1115     Value replacement = rewriter.create<InsertSliceOp>(
1116         insertSliceOp.getLoc(),
1117         (sourceCastSource ? *sourceCastSource : insertSliceOp.source()),
1118         (destCastSource ? *destCastSource : insertSliceOp.dest()),
1119         insertSliceOp.getMixedOffsets(), insertSliceOp.getMixedSizes(),
1120         insertSliceOp.getMixedStrides());
1121 
1122     if (replacement.getType() != insertSliceOp.getType()) {
1123       replacement = rewriter.create<tensor::CastOp>(
1124           insertSliceOp.getLoc(), insertSliceOp.getType(), replacement);
1125     }
1126     rewriter.replaceOp(insertSliceOp, replacement);
1127     return success();
1128   }
1129 };
1130 } // namespace
1131 
1132 void InsertSliceOp::getCanonicalizationPatterns(RewritePatternSet &results,
1133                                                 MLIRContext *context) {
1134   results.add<InsertSliceOpConstantArgumentFolder, InsertSliceOpCastFolder>(
1135       context);
1136 }
1137 
1138 //===----------------------------------------------------------------------===//
1139 // TableGen'd op method definitions
1140 //===----------------------------------------------------------------------===//
1141 
1142 #define GET_OP_CLASSES
1143 #include "mlir/Dialect/Tensor/IR/TensorOps.cpp.inc"
1144