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/Arithmetic/IR/Arithmetic.h" 10 #include "mlir/Dialect/Bufferization/IR/BufferizableOpInterface.h" 11 #include "mlir/Dialect/Bufferization/IR/Bufferization.h" 12 #include "mlir/Dialect/Func/IR/FuncOps.h" 13 #include "mlir/Dialect/MemRef/IR/MemRef.h" 14 #include "mlir/Dialect/MemRef/Utils/MemRefUtils.h" 15 #include "mlir/Dialect/SparseTensor/IR/SparseTensor.h" 16 #include "mlir/Dialect/Tensor/IR/Tensor.h" 17 #include "mlir/IR/Matchers.h" 18 19 using namespace mlir; 20 using namespace mlir::bufferization; 21 22 //===----------------------------------------------------------------------===// 23 // Helper functions 24 //===----------------------------------------------------------------------===// 25 26 FailureOr<Value> 27 mlir::bufferization::castOrReallocMemRefValue(OpBuilder &b, Value value, 28 MemRefType destType) { 29 auto srcType = value.getType().cast<MemRefType>(); 30 31 // Element type, rank and memory space must match. 32 if (srcType.getElementType() != destType.getElementType()) 33 return failure(); 34 if (srcType.getMemorySpaceAsInt() != destType.getMemorySpaceAsInt()) 35 return failure(); 36 if (srcType.getRank() != destType.getRank()) 37 return failure(); 38 39 // In case the affine maps are different, we may need to use a copy if we go 40 // from dynamic to static offset or stride (the canonicalization cannot know 41 // at this point that it is really cast compatible). 42 auto isGuaranteedCastCompatible = [](MemRefType source, MemRefType target) { 43 int64_t sourceOffset, targetOffset; 44 SmallVector<int64_t, 4> sourceStrides, targetStrides; 45 if (failed(getStridesAndOffset(source, sourceStrides, sourceOffset)) || 46 failed(getStridesAndOffset(target, targetStrides, targetOffset))) 47 return false; 48 auto dynamicToStatic = [](int64_t a, int64_t b) { 49 return a == MemRefType::getDynamicStrideOrOffset() && 50 b != MemRefType::getDynamicStrideOrOffset(); 51 }; 52 if (dynamicToStatic(sourceOffset, targetOffset)) 53 return false; 54 for (auto it : zip(sourceStrides, targetStrides)) 55 if (dynamicToStatic(std::get<0>(it), std::get<1>(it))) 56 return false; 57 return true; 58 }; 59 60 // Note: If `areCastCompatible`, a cast is valid, but may fail at runtime. To 61 // ensure that we only generate casts that always succeed at runtime, we check 62 // a fix extra conditions in `isGuaranteedCastCompatible`. 63 if (memref::CastOp::areCastCompatible(srcType, destType) && 64 isGuaranteedCastCompatible(srcType, destType)) { 65 Value casted = b.create<memref::CastOp>(value.getLoc(), destType, value); 66 return casted; 67 } 68 69 auto loc = value.getLoc(); 70 SmallVector<Value, 4> dynamicOperands; 71 for (int i = 0; i < destType.getRank(); ++i) { 72 if (destType.getShape()[i] != ShapedType::kDynamicSize) 73 continue; 74 auto index = b.createOrFold<arith::ConstantIndexOp>(loc, i); 75 Value size = b.create<memref::DimOp>(loc, value, index); 76 dynamicOperands.push_back(size); 77 } 78 // TODO: Use alloc/memcpy callback from BufferizationOptions if called via 79 // BufferizableOpInterface impl of ToMemrefOp. 80 Value copy = b.create<memref::AllocOp>(loc, destType, dynamicOperands); 81 b.create<memref::CopyOp>(loc, value, copy); 82 return copy; 83 } 84 85 /// Try to fold to_memref(to_tensor(x)). If x's type and the result type of the 86 /// to_memref op are different, a memref.cast is needed. 87 LogicalResult mlir::bufferization::foldToMemrefToTensorPair( 88 RewriterBase &rewriter, ToMemrefOp toMemref, bool allowSameType) { 89 auto memrefToTensor = toMemref.getTensor().getDefiningOp<ToTensorOp>(); 90 if (!memrefToTensor) 91 return failure(); 92 93 Type srcType = memrefToTensor.getMemref().getType(); 94 Type destType = toMemref.getType(); 95 96 // Directly rewrite if the type did not change. 97 if (srcType == destType) { 98 // Function can be configured to only handle cases where a cast is needed. 99 if (!allowSameType) 100 return failure(); 101 rewriter.replaceOp(toMemref, memrefToTensor.getMemref()); 102 return success(); 103 } 104 105 auto rankedSrcType = srcType.dyn_cast<MemRefType>(); 106 auto rankedDestType = destType.dyn_cast<MemRefType>(); 107 auto unrankedSrcType = srcType.dyn_cast<UnrankedMemRefType>(); 108 109 // Ranked memref -> Ranked memref cast. 110 if (rankedSrcType && rankedDestType) { 111 FailureOr<Value> replacement = castOrReallocMemRefValue( 112 rewriter, memrefToTensor.getMemref(), rankedDestType); 113 if (failed(replacement)) 114 return failure(); 115 116 rewriter.replaceOp(toMemref, *replacement); 117 return success(); 118 } 119 120 // Unranked memref -> Ranked memref cast: May require a copy. 121 // TODO: Not implemented at the moment. 122 if (unrankedSrcType && rankedDestType) 123 return failure(); 124 125 // Unranked memref -> unranked memref cast 126 // Ranked memref -> unranked memref cast: No copy needed. 127 assert(memref::CastOp::areCastCompatible(srcType, destType) && 128 "expected that types are cast compatible"); 129 rewriter.replaceOpWithNewOp<memref::CastOp>(toMemref, destType, 130 memrefToTensor.getMemref()); 131 return success(); 132 } 133 134 void mlir::bufferization::populateDynamicDimSizes( 135 OpBuilder &b, Location loc, Value shapedValue, 136 SmallVector<Value> &dynamicDims) { 137 auto shapedType = shapedValue.getType().cast<ShapedType>(); 138 for (int64_t i = 0; i < shapedType.getRank(); ++i) { 139 if (shapedType.isDynamicDim(i)) { 140 if (shapedType.isa<MemRefType>()) { 141 dynamicDims.push_back(b.create<memref::DimOp>(loc, shapedValue, i)); 142 } else { 143 assert(shapedType.isa<RankedTensorType>() && "expected tensor"); 144 dynamicDims.push_back(b.create<tensor::DimOp>(loc, shapedValue, i)); 145 } 146 } 147 } 148 } 149 150 //===----------------------------------------------------------------------===// 151 // AllocTensorOp 152 //===----------------------------------------------------------------------===// 153 154 LogicalResult AllocTensorOp::bufferize(RewriterBase &rewriter, 155 const BufferizationOptions &options) { 156 OpBuilder::InsertionGuard g(rewriter); 157 Operation *op = this->getOperation(); 158 Location loc = getLoc(); 159 160 // Nothing to do for dead AllocTensorOps. 161 if (getOperation()->getUses().empty()) { 162 rewriter.eraseOp(getOperation()); 163 return success(); 164 } 165 166 // Get "copy" buffer. 167 Value copyBuffer; 168 if (getCopy()) { 169 FailureOr<Value> maybeCopyBuffer = getBuffer(rewriter, getCopy(), options); 170 if (failed(maybeCopyBuffer)) 171 return failure(); 172 copyBuffer = *maybeCopyBuffer; 173 } 174 175 // Compute memory space of this allocation. 176 unsigned memorySpace; 177 if (getMemorySpace().hasValue()) { 178 memorySpace = *getMemorySpace(); 179 } else if (options.defaultMemorySpace.hasValue()) { 180 memorySpace = *options.defaultMemorySpace; 181 } else { 182 return op->emitError("could not infer memory space"); 183 } 184 185 // Create memory allocation. 186 auto allocType = 187 MemRefType::get(getType().getShape(), getType().getElementType(), 188 AffineMap(), memorySpace); 189 SmallVector<Value> dynamicDims = getDynamicSizes(); 190 if (getCopy()) { 191 assert(dynamicDims.empty() && "expected either `copy` or `dynamicDims`"); 192 populateDynamicDimSizes(rewriter, loc, copyBuffer, dynamicDims); 193 } 194 FailureOr<Value> alloc = 195 options.createAlloc(rewriter, loc, allocType, dynamicDims); 196 if (failed(alloc)) 197 return failure(); 198 199 // Create memory copy (if any). 200 if (getCopy()) { 201 if (failed(options.createMemCpy(rewriter, loc, copyBuffer, *alloc))) 202 return failure(); 203 } 204 205 // Should the buffer be deallocated? 206 AnalysisState analysisState(options); 207 bool dealloc; 208 if (op->hasAttr(BufferizationDialect::kEscapeAttrName)) { 209 // AllocTensorOp has one result. 210 ArrayAttr escapeAttr = 211 op->getAttr(BufferizationDialect::kEscapeAttrName).cast<ArrayAttr>(); 212 dealloc = !escapeAttr[0].cast<BoolAttr>().getValue(); 213 } else { 214 // No "escape" annotation found. 215 if (options.createDeallocs) { 216 // Perform an ad-hoc analysis. 217 dealloc = !analysisState.isTensorYielded(getResult()); 218 } else { 219 dealloc = false; 220 } 221 } 222 223 // Replace op. 224 replaceOpWithBufferizedValues(rewriter, getOperation(), *alloc); 225 226 // Create buffer deallocation (if requested). 227 if (!dealloc) 228 return success(); 229 230 rewriter.setInsertionPoint(rewriter.getInsertionBlock()->getTerminator()); 231 if (failed(options.createDealloc(rewriter, loc, *alloc))) 232 return failure(); 233 return success(); 234 } 235 236 bool AllocTensorOp::isMemoryWrite(OpResult opResult, 237 const AnalysisState &state) { 238 // AllocTensorOps do not write unless they have a `copy` value. 239 return static_cast<bool>(getCopy()); 240 } 241 242 bool AllocTensorOp::bufferizesToMemoryRead(OpOperand &opOperand, 243 const AnalysisState &state) { 244 assert(opOperand.getOperandNumber() == getNumOperands() - 1 && 245 "expected copy operand"); 246 return true; 247 } 248 249 bool AllocTensorOp::bufferizesToMemoryWrite(OpOperand &opOperand, 250 const AnalysisState &state) { 251 assert(opOperand.getOperandNumber() == getNumOperands() - 1 && 252 "expected copy operand"); 253 return false; 254 } 255 256 SmallVector<OpResult> 257 AllocTensorOp::getAliasingOpResult(OpOperand &opOperand, 258 const AnalysisState &state) { 259 // This is a new allocation. It does not alias with any other buffer. 260 return {}; 261 } 262 263 LogicalResult AllocTensorOp::verify() { 264 if (getCopy() && !getDynamicSizes().empty()) 265 return emitError("dynamic sizes not needed when copying a tensor"); 266 if (!getCopy() && getType().getNumDynamicDims() != 267 static_cast<int64_t>(getDynamicSizes().size())) 268 return emitError("expected ") 269 << getType().getNumDynamicDims() << " dynamic sizes"; 270 if (getCopy() && getCopy().getType() != getType()) 271 return emitError("expected that `copy` and return type match"); 272 273 // For sparse tensor allocation, we require that none of its 274 // uses escapes the function boundary directly. 275 if (sparse_tensor::getSparseTensorEncoding(getType())) { 276 for (auto &use : getOperation()->getUses()) 277 if (isa<func::ReturnOp, func::CallOp, func::CallIndirectOp>( 278 use.getOwner())) 279 return emitError("sparse tensor allocation should not escape function"); 280 } 281 282 return success(); 283 } 284 285 void AllocTensorOp::build(OpBuilder &builder, OperationState &result, 286 RankedTensorType type, ValueRange dynamicSizes) { 287 build(builder, result, type, dynamicSizes, /*copy=*/Value(), 288 /*memory_space=*/IntegerAttr()); 289 } 290 291 void AllocTensorOp::build(OpBuilder &builder, OperationState &result, 292 RankedTensorType type, ValueRange dynamicSizes, 293 Value copy) { 294 build(builder, result, type, dynamicSizes, copy, 295 /*memory_space=*/IntegerAttr()); 296 } 297 298 namespace { 299 /// Change the type of the result of a `bufferization.alloc_tensor` by making 300 /// the result type statically sized along dimension that in the original 301 /// operation where defined as dynamic, but the size was defined using a 302 /// `constant` op. For example: 303 /// 304 /// %c5 = arith.constant 5: index 305 /// %0 = bufferization.alloc_tensor(%arg0, %c5) : tensor<?x?xf32> 306 /// 307 /// to 308 /// 309 /// %0 = bufferization.alloc_tensor(%arg0) : tensor<?x5xf32> 310 struct ReplaceStaticShapeDims : OpRewritePattern<AllocTensorOp> { 311 using OpRewritePattern<AllocTensorOp>::OpRewritePattern; 312 313 LogicalResult matchAndRewrite(AllocTensorOp op, 314 PatternRewriter &rewriter) const override { 315 if (op.getCopy()) 316 return failure(); 317 SmallVector<int64_t> newShape = llvm::to_vector(op.getType().getShape()); 318 SmallVector<Value> newDynamicSizes; 319 unsigned int dynValCounter = 0; 320 for (int64_t i = 0; i < op.getType().getRank(); ++i) { 321 if (!op.isDynamicDim(i)) 322 continue; 323 Value value = op.getDynamicSizes()[dynValCounter++]; 324 APInt intVal; 325 if (matchPattern(value, m_ConstantInt(&intVal))) { 326 newShape[i] = intVal.getSExtValue(); 327 } else { 328 newDynamicSizes.push_back(value); 329 } 330 } 331 RankedTensorType newType = RankedTensorType::get( 332 newShape, op.getType().getElementType(), op.getType().getEncoding()); 333 if (newType == op.getType()) 334 return failure(); 335 auto newOp = rewriter.create<AllocTensorOp>( 336 op.getLoc(), newType, newDynamicSizes, /*copy=*/Value()); 337 rewriter.replaceOpWithNewOp<tensor::CastOp>(op, op.getType(), newOp); 338 return success(); 339 } 340 }; 341 342 struct FoldDimOfAllocTensorOp : public OpRewritePattern<tensor::DimOp> { 343 using OpRewritePattern<tensor::DimOp>::OpRewritePattern; 344 345 LogicalResult matchAndRewrite(tensor::DimOp dimOp, 346 PatternRewriter &rewriter) const override { 347 Optional<int64_t> maybeConstantIndex = dimOp.getConstantIndex(); 348 auto allocTensorOp = dimOp.source().getDefiningOp<AllocTensorOp>(); 349 if (!allocTensorOp || !maybeConstantIndex) 350 return failure(); 351 if (!allocTensorOp.getType().isDynamicDim(*maybeConstantIndex)) 352 return failure(); 353 rewriter.replaceOp( 354 dimOp, allocTensorOp.getDynamicSize(rewriter, *maybeConstantIndex)); 355 return success(); 356 } 357 }; 358 } // namespace 359 360 void AllocTensorOp::getCanonicalizationPatterns(RewritePatternSet &results, 361 MLIRContext *ctx) { 362 results.add<FoldDimOfAllocTensorOp, ReplaceStaticShapeDims>(ctx); 363 } 364 365 LogicalResult AllocTensorOp::reifyResultShapes( 366 OpBuilder &builder, ReifiedRankedShapedTypeDims &reifiedReturnShapes) { 367 auto shapes = llvm::to_vector<4>(llvm::map_range( 368 llvm::seq<int64_t>(0, getType().getRank()), [&](int64_t dim) -> Value { 369 if (isDynamicDim(dim)) 370 return getDynamicSize(builder, dim); 371 return builder.create<arith::ConstantIndexOp>(getLoc(), 372 getStaticSize(dim)); 373 })); 374 reifiedReturnShapes.emplace_back(std::move(shapes)); 375 return success(); 376 } 377 378 ParseResult AllocTensorOp::parse(OpAsmParser &parser, OperationState &result) { 379 SmallVector<OpAsmParser::UnresolvedOperand> dynamicSizesOperands; 380 if (parser.parseLParen() || parser.parseOperandList(dynamicSizesOperands) || 381 parser.parseRParen()) 382 return failure(); 383 ParseResult copyKeyword = parser.parseOptionalKeyword("copy"); 384 OpAsmParser::UnresolvedOperand copyOperand; 385 if (copyKeyword.succeeded()) 386 if (parser.parseLParen() || parser.parseOperand(copyOperand) || 387 parser.parseRParen()) 388 return failure(); 389 if (parser.parseOptionalAttrDict(result.attributes) || parser.parseColon()) 390 return failure(); 391 392 TensorType type; 393 if (parser.parseCustomTypeWithFallback(type)) 394 return failure(); 395 result.addTypes(type); 396 397 Type indexType = parser.getBuilder().getIndexType(); 398 if (parser.resolveOperands(dynamicSizesOperands, indexType, result.operands)) 399 return failure(); 400 if (copyKeyword.succeeded()) 401 if (parser.resolveOperand(copyOperand, type, result.operands)) 402 return failure(); 403 result.addAttribute(AllocTensorOp::getOperandSegmentSizeAttr(), 404 parser.getBuilder().getI32VectorAttr( 405 {static_cast<int32_t>(dynamicSizesOperands.size()), 406 static_cast<int32_t>(copyKeyword.succeeded())})); 407 return success(); 408 } 409 410 void AllocTensorOp::print(OpAsmPrinter &p) { 411 p << "(" << getDynamicSizes() << ")"; 412 if (getCopy()) 413 p << " copy(" << getCopy() << ")"; 414 p.printOptionalAttrDict((*this)->getAttrs(), /*elidedAttrs=*/{ 415 AllocTensorOp::getOperandSegmentSizeAttr()}); 416 p << " : "; 417 auto type = getResult().getType(); 418 if (auto validType = type.dyn_cast<::mlir::TensorType>()) 419 p.printStrippedAttrOrType(validType); 420 else 421 p << type; 422 } 423 424 Value AllocTensorOp::getDynamicSize(OpBuilder &b, unsigned idx) { 425 assert(isDynamicDim(idx) && "expected dynamic dim"); 426 if (getCopy()) 427 return b.create<tensor::DimOp>(getLoc(), getCopy(), idx); 428 return getOperand(getIndexOfDynamicSize(idx)); 429 } 430 431 //===----------------------------------------------------------------------===// 432 // CloneOp 433 //===----------------------------------------------------------------------===// 434 435 void CloneOp::getEffects( 436 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>> 437 &effects) { 438 effects.emplace_back(MemoryEffects::Read::get(), getInput(), 439 SideEffects::DefaultResource::get()); 440 effects.emplace_back(MemoryEffects::Write::get(), getOutput(), 441 SideEffects::DefaultResource::get()); 442 effects.emplace_back(MemoryEffects::Allocate::get(), getOutput(), 443 SideEffects::DefaultResource::get()); 444 } 445 446 OpFoldResult CloneOp::fold(ArrayRef<Attribute> operands) { 447 return succeeded(memref::foldMemRefCast(*this)) ? getResult() : Value(); 448 } 449 450 namespace { 451 452 /// Merge the clone and its source (by converting the clone to a cast) when 453 /// possible. 454 struct SimplifyClones : public OpRewritePattern<CloneOp> { 455 using OpRewritePattern<CloneOp>::OpRewritePattern; 456 457 LogicalResult matchAndRewrite(CloneOp cloneOp, 458 PatternRewriter &rewriter) const override { 459 if (cloneOp.use_empty()) { 460 rewriter.eraseOp(cloneOp); 461 return success(); 462 } 463 464 Value source = cloneOp.getInput(); 465 466 // This only finds dealloc operations for the immediate value. It should 467 // also consider aliases. That would also make the safety check below 468 // redundant. 469 llvm::Optional<Operation *> maybeCloneDeallocOp = 470 memref::findDealloc(cloneOp.getOutput()); 471 // Skip if either of them has > 1 deallocate operations. 472 if (!maybeCloneDeallocOp.hasValue()) 473 return failure(); 474 llvm::Optional<Operation *> maybeSourceDeallocOp = 475 memref::findDealloc(source); 476 if (!maybeSourceDeallocOp.hasValue()) 477 return failure(); 478 Operation *cloneDeallocOp = *maybeCloneDeallocOp; 479 Operation *sourceDeallocOp = *maybeSourceDeallocOp; 480 481 // If both are deallocated in the same block, their in-block lifetimes 482 // might not fully overlap, so we cannot decide which one to drop. 483 if (cloneDeallocOp && sourceDeallocOp && 484 cloneDeallocOp->getBlock() == sourceDeallocOp->getBlock()) 485 return failure(); 486 487 Block *currentBlock = cloneOp->getBlock(); 488 Operation *redundantDealloc = nullptr; 489 if (cloneDeallocOp && cloneDeallocOp->getBlock() == currentBlock) { 490 redundantDealloc = cloneDeallocOp; 491 } else if (sourceDeallocOp && sourceDeallocOp->getBlock() == currentBlock) { 492 redundantDealloc = sourceDeallocOp; 493 } 494 495 if (!redundantDealloc) 496 return failure(); 497 498 // Safety check that there are no other deallocations inbetween 499 // cloneOp and redundantDealloc, as otherwise we might deallocate an alias 500 // of source before the uses of the clone. With alias information, we could 501 // restrict this to only fail of the dealloc's operand is an alias 502 // of the source. 503 for (Operation *pos = cloneOp->getNextNode(); pos != redundantDealloc; 504 pos = pos->getNextNode()) { 505 auto effectInterface = dyn_cast<MemoryEffectOpInterface>(pos); 506 if (!effectInterface) 507 continue; 508 if (effectInterface.hasEffect<MemoryEffects::Free>()) 509 return failure(); 510 } 511 512 rewriter.replaceOpWithNewOp<memref::CastOp>(cloneOp, cloneOp.getType(), 513 source); 514 rewriter.eraseOp(redundantDealloc); 515 return success(); 516 } 517 }; 518 519 } // namespace 520 521 void CloneOp::getCanonicalizationPatterns(RewritePatternSet &results, 522 MLIRContext *context) { 523 results.add<SimplifyClones>(context); 524 } 525 526 //===----------------------------------------------------------------------===// 527 // ToTensorOp 528 //===----------------------------------------------------------------------===// 529 530 OpFoldResult ToTensorOp::fold(ArrayRef<Attribute>) { 531 if (auto toMemref = getMemref().getDefiningOp<ToMemrefOp>()) 532 // Approximate alias analysis by conservatively folding only when no there 533 // is no interleaved operation. 534 if (toMemref->getBlock() == this->getOperation()->getBlock() && 535 toMemref->getNextNode() == this->getOperation()) 536 return toMemref.getTensor(); 537 return {}; 538 } 539 540 namespace { 541 542 struct DimOfToTensorFolder : public OpRewritePattern<tensor::DimOp> { 543 using OpRewritePattern<tensor::DimOp>::OpRewritePattern; 544 545 LogicalResult matchAndRewrite(tensor::DimOp dimOp, 546 PatternRewriter &rewriter) const override { 547 auto memrefToTensorOp = dimOp.source().getDefiningOp<ToTensorOp>(); 548 if (!memrefToTensorOp) 549 return failure(); 550 551 rewriter.replaceOpWithNewOp<memref::DimOp>( 552 dimOp, memrefToTensorOp.getMemref(), dimOp.index()); 553 return success(); 554 } 555 }; 556 557 } // namespace 558 559 void ToTensorOp::getCanonicalizationPatterns(RewritePatternSet &results, 560 MLIRContext *context) { 561 results.add<DimOfToTensorFolder>(context); 562 } 563 564 //===----------------------------------------------------------------------===// 565 // ToMemrefOp 566 //===----------------------------------------------------------------------===// 567 568 OpFoldResult ToMemrefOp::fold(ArrayRef<Attribute>) { 569 if (auto memrefToTensor = getTensor().getDefiningOp<ToTensorOp>()) 570 if (memrefToTensor.getMemref().getType() == getType()) 571 return memrefToTensor.getMemref(); 572 return {}; 573 } 574 575 namespace { 576 577 /// Replace tensor.cast + to_memref by to_memref + memref.cast. 578 struct ToMemrefOfCast : public OpRewritePattern<ToMemrefOp> { 579 using OpRewritePattern<ToMemrefOp>::OpRewritePattern; 580 581 LogicalResult matchAndRewrite(ToMemrefOp toMemref, 582 PatternRewriter &rewriter) const final { 583 auto tensorCastOperand = 584 toMemref.getOperand().getDefiningOp<tensor::CastOp>(); 585 if (!tensorCastOperand) 586 return failure(); 587 auto srcTensorType = 588 tensorCastOperand.getOperand().getType().dyn_cast<RankedTensorType>(); 589 if (!srcTensorType) 590 return failure(); 591 auto memrefType = MemRefType::get(srcTensorType.getShape(), 592 srcTensorType.getElementType()); 593 Value memref = rewriter.create<ToMemrefOp>(toMemref.getLoc(), memrefType, 594 tensorCastOperand.getOperand()); 595 rewriter.replaceOpWithNewOp<memref::CastOp>(toMemref, toMemref.getType(), 596 memref); 597 return success(); 598 } 599 }; 600 601 /// Canonicalize bufferization.to_tensor + bufferization.to_memref to 602 /// memref.cast when type mismatches prevent `ToMemrefOp::fold` to kick in. 603 struct TensorLoadToMemref : public OpRewritePattern<ToMemrefOp> { 604 using OpRewritePattern<ToMemrefOp>::OpRewritePattern; 605 606 LogicalResult matchAndRewrite(ToMemrefOp toMemref, 607 PatternRewriter &rewriter) const final { 608 // Only handle cases where a cast is needed. The other case is handled by 609 // the folder. 610 return foldToMemrefToTensorPair(rewriter, toMemref, 611 /*allowSameType=*/false); 612 } 613 }; 614 615 /// Fold a load on a to_memref operation into an tensor.extract on the 616 /// corresponding tensor. 617 struct LoadOfToMemref : public OpRewritePattern<memref::LoadOp> { 618 using OpRewritePattern<memref::LoadOp>::OpRewritePattern; 619 620 LogicalResult matchAndRewrite(memref::LoadOp load, 621 PatternRewriter &rewriter) const override { 622 auto toMemref = load.memref().getDefiningOp<ToMemrefOp>(); 623 if (!toMemref) 624 return failure(); 625 626 rewriter.replaceOpWithNewOp<tensor::ExtractOp>(load, toMemref.getTensor(), 627 load.indices()); 628 return success(); 629 } 630 }; 631 632 /// Fold dim of a to_memref into the dim of the tensor. 633 struct DimOfCastOp : public OpRewritePattern<memref::DimOp> { 634 using OpRewritePattern<memref::DimOp>::OpRewritePattern; 635 636 LogicalResult matchAndRewrite(memref::DimOp dimOp, 637 PatternRewriter &rewriter) const override { 638 auto castOp = dimOp.source().getDefiningOp<ToMemrefOp>(); 639 if (!castOp) 640 return failure(); 641 Value newSource = castOp.getOperand(); 642 rewriter.replaceOpWithNewOp<tensor::DimOp>(dimOp, newSource, dimOp.index()); 643 return success(); 644 } 645 }; 646 647 } // namespace 648 649 void ToMemrefOp::getCanonicalizationPatterns(RewritePatternSet &results, 650 MLIRContext *context) { 651 results.add<DimOfCastOp, LoadOfToMemref, ToMemrefOfCast, TensorLoadToMemref>( 652 context); 653 } 654 655 LogicalResult ToMemrefOp::bufferize(RewriterBase &rewriter, 656 const BufferizationOptions &options) { 657 // Fold to_memref(to_tensor(x)) to x. Insert a cast if necessary. 658 (void)foldToMemrefToTensorPair(rewriter, *this); 659 // Note: The return value of `bufferize` indicates whether there was an error 660 // or not. (And not whether the pattern matched or not.) 661 return success(); 662 } 663 664 Optional<Operation *> CloneOp::buildDealloc(OpBuilder &builder, Value alloc) { 665 return builder.create<memref::DeallocOp>(alloc.getLoc(), alloc) 666 .getOperation(); 667 } 668 669 Optional<Value> CloneOp::buildClone(OpBuilder &builder, Value alloc) { 670 return builder.create<CloneOp>(alloc.getLoc(), alloc).getResult(); 671 } 672 673 //===----------------------------------------------------------------------===// 674 // TableGen'd op method definitions 675 //===----------------------------------------------------------------------===// 676 677 #define GET_OP_CLASSES 678 #include "mlir/Dialect/Bufferization/IR/BufferizationOps.cpp.inc" 679