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