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/MemRef/IR/MemRef.h" 10 #include "mlir/Dialect/MemRef/Utils/MemRefUtils.h" 11 #include "mlir/Dialect/StandardOps/IR/Ops.h" 12 #include "mlir/Dialect/StandardOps/Utils/Utils.h" 13 #include "mlir/Dialect/Tensor/IR/Tensor.h" 14 #include "mlir/Dialect/Utils/StaticValueUtils.h" 15 #include "mlir/IR/AffineMap.h" 16 #include "mlir/IR/Builders.h" 17 #include "mlir/IR/BuiltinTypes.h" 18 #include "mlir/IR/Matchers.h" 19 #include "mlir/IR/PatternMatch.h" 20 #include "mlir/IR/TypeUtilities.h" 21 #include "mlir/Interfaces/InferTypeOpInterface.h" 22 #include "mlir/Interfaces/ViewLikeInterface.h" 23 #include "llvm/ADT/STLExtras.h" 24 25 using namespace mlir; 26 using namespace mlir::memref; 27 28 /// Materialize a single constant operation from a given attribute value with 29 /// the desired resultant type. 30 Operation *MemRefDialect::materializeConstant(OpBuilder &builder, 31 Attribute value, Type type, 32 Location loc) { 33 return builder.create<mlir::ConstantOp>(loc, type, value); 34 } 35 36 //===----------------------------------------------------------------------===// 37 // Common canonicalization pattern support logic 38 //===----------------------------------------------------------------------===// 39 40 /// This is a common class used for patterns of the form 41 /// "someop(memrefcast) -> someop". It folds the source of any memref.cast 42 /// into the root operation directly. 43 static LogicalResult foldMemRefCast(Operation *op, Value inner = nullptr) { 44 bool folded = false; 45 for (OpOperand &operand : op->getOpOperands()) { 46 auto cast = operand.get().getDefiningOp<CastOp>(); 47 if (cast && operand.get() != inner && 48 !cast.getOperand().getType().isa<UnrankedMemRefType>()) { 49 operand.set(cast.getOperand()); 50 folded = true; 51 } 52 } 53 return success(folded); 54 } 55 56 //===----------------------------------------------------------------------===// 57 // Helpers for GlobalOp 58 //===----------------------------------------------------------------------===// 59 60 static Type getTensorTypeFromMemRefType(Type type) { 61 if (auto memref = type.dyn_cast<MemRefType>()) 62 return RankedTensorType::get(memref.getShape(), memref.getElementType()); 63 if (auto memref = type.dyn_cast<UnrankedMemRefType>()) 64 return UnrankedTensorType::get(memref.getElementType()); 65 return NoneType::get(type.getContext()); 66 } 67 68 //===----------------------------------------------------------------------===// 69 // AllocOp / AllocaOp 70 //===----------------------------------------------------------------------===// 71 72 template <typename AllocLikeOp> 73 static LogicalResult verifyAllocLikeOp(AllocLikeOp op) { 74 static_assert(llvm::is_one_of<AllocLikeOp, AllocOp, AllocaOp>::value, 75 "applies to only alloc or alloca"); 76 auto memRefType = op.getResult().getType().template dyn_cast<MemRefType>(); 77 if (!memRefType) 78 return op.emitOpError("result must be a memref"); 79 80 if (static_cast<int64_t>(op.dynamicSizes().size()) != 81 memRefType.getNumDynamicDims()) 82 return op.emitOpError("dimension operand count does not equal memref " 83 "dynamic dimension count"); 84 85 unsigned numSymbols = 0; 86 if (!memRefType.getAffineMaps().empty()) 87 numSymbols = memRefType.getAffineMaps().front().getNumSymbols(); 88 if (op.symbolOperands().size() != numSymbols) 89 return op.emitOpError("symbol operand count does not equal memref symbol " 90 "count: expected ") 91 << numSymbols << ", got " << op.symbolOperands().size(); 92 93 return success(); 94 } 95 96 static LogicalResult verify(AllocOp op) { return verifyAllocLikeOp(op); } 97 98 static LogicalResult verify(AllocaOp op) { 99 // An alloca op needs to have an ancestor with an allocation scope trait. 100 if (!op->getParentWithTrait<OpTrait::AutomaticAllocationScope>()) 101 return op.emitOpError( 102 "requires an ancestor op with AutomaticAllocationScope trait"); 103 104 return verifyAllocLikeOp(op); 105 } 106 107 namespace { 108 /// Fold constant dimensions into an alloc like operation. 109 template <typename AllocLikeOp> 110 struct SimplifyAllocConst : public OpRewritePattern<AllocLikeOp> { 111 using OpRewritePattern<AllocLikeOp>::OpRewritePattern; 112 113 LogicalResult matchAndRewrite(AllocLikeOp alloc, 114 PatternRewriter &rewriter) const override { 115 // Check to see if any dimensions operands are constants. If so, we can 116 // substitute and drop them. 117 if (llvm::none_of(alloc.dynamicSizes(), [](Value operand) { 118 return matchPattern(operand, matchConstantIndex()); 119 })) 120 return failure(); 121 122 auto memrefType = alloc.getType(); 123 124 // Ok, we have one or more constant operands. Collect the non-constant ones 125 // and keep track of the resultant memref type to build. 126 SmallVector<int64_t, 4> newShapeConstants; 127 newShapeConstants.reserve(memrefType.getRank()); 128 SmallVector<Value, 4> dynamicSizes; 129 130 unsigned dynamicDimPos = 0; 131 for (unsigned dim = 0, e = memrefType.getRank(); dim < e; ++dim) { 132 int64_t dimSize = memrefType.getDimSize(dim); 133 // If this is already static dimension, keep it. 134 if (dimSize != -1) { 135 newShapeConstants.push_back(dimSize); 136 continue; 137 } 138 auto dynamicSize = alloc.dynamicSizes()[dynamicDimPos]; 139 auto *defOp = dynamicSize.getDefiningOp(); 140 if (auto constantIndexOp = dyn_cast_or_null<ConstantIndexOp>(defOp)) { 141 // Dynamic shape dimension will be folded. 142 newShapeConstants.push_back(constantIndexOp.getValue()); 143 } else { 144 // Dynamic shape dimension not folded; copy dynamicSize from old memref. 145 newShapeConstants.push_back(-1); 146 dynamicSizes.push_back(dynamicSize); 147 } 148 dynamicDimPos++; 149 } 150 151 // Create new memref type (which will have fewer dynamic dimensions). 152 MemRefType newMemRefType = 153 MemRefType::Builder(memrefType).setShape(newShapeConstants); 154 assert(static_cast<int64_t>(dynamicSizes.size()) == 155 newMemRefType.getNumDynamicDims()); 156 157 // Create and insert the alloc op for the new memref. 158 auto newAlloc = rewriter.create<AllocLikeOp>( 159 alloc.getLoc(), newMemRefType, dynamicSizes, alloc.symbolOperands(), 160 alloc.alignmentAttr()); 161 // Insert a cast so we have the same type as the old alloc. 162 auto resultCast = 163 rewriter.create<CastOp>(alloc.getLoc(), newAlloc, alloc.getType()); 164 165 rewriter.replaceOp(alloc, {resultCast}); 166 return success(); 167 } 168 }; 169 170 /// Fold alloc operations with no users or only store and dealloc uses. 171 template <typename T> 172 struct SimplifyDeadAlloc : public OpRewritePattern<T> { 173 using OpRewritePattern<T>::OpRewritePattern; 174 175 LogicalResult matchAndRewrite(T alloc, 176 PatternRewriter &rewriter) const override { 177 if (llvm::any_of(alloc->getUsers(), [&](Operation *op) { 178 if (auto storeOp = dyn_cast<StoreOp>(op)) 179 return storeOp.value() == alloc; 180 return !isa<DeallocOp>(op); 181 })) 182 return failure(); 183 184 for (Operation *user : llvm::make_early_inc_range(alloc->getUsers())) 185 rewriter.eraseOp(user); 186 187 rewriter.eraseOp(alloc); 188 return success(); 189 } 190 }; 191 } // end anonymous namespace. 192 193 void AllocOp::getCanonicalizationPatterns(RewritePatternSet &results, 194 MLIRContext *context) { 195 results.add<SimplifyAllocConst<AllocOp>, SimplifyDeadAlloc<AllocOp>>(context); 196 } 197 198 void AllocaOp::getCanonicalizationPatterns(RewritePatternSet &results, 199 MLIRContext *context) { 200 results.add<SimplifyAllocConst<AllocaOp>, SimplifyDeadAlloc<AllocaOp>>( 201 context); 202 } 203 204 //===----------------------------------------------------------------------===// 205 // AllocaScopeOp 206 //===----------------------------------------------------------------------===// 207 208 static void print(OpAsmPrinter &p, AllocaScopeOp &op) { 209 bool printBlockTerminators = false; 210 211 p << " "; 212 if (!op.results().empty()) { 213 p << " -> (" << op.getResultTypes() << ")"; 214 printBlockTerminators = true; 215 } 216 p.printRegion(op.bodyRegion(), 217 /*printEntryBlockArgs=*/false, 218 /*printBlockTerminators=*/printBlockTerminators); 219 p.printOptionalAttrDict(op->getAttrs()); 220 } 221 222 static ParseResult parseAllocaScopeOp(OpAsmParser &parser, 223 OperationState &result) { 224 // Create a region for the body. 225 result.regions.reserve(1); 226 Region *bodyRegion = result.addRegion(); 227 228 // Parse optional results type list. 229 if (parser.parseOptionalArrowTypeList(result.types)) 230 return failure(); 231 232 // Parse the body region. 233 if (parser.parseRegion(*bodyRegion, /*arguments=*/{}, /*argTypes=*/{})) 234 return failure(); 235 AllocaScopeOp::ensureTerminator(*bodyRegion, parser.getBuilder(), 236 result.location); 237 238 // Parse the optional attribute list. 239 if (parser.parseOptionalAttrDict(result.attributes)) 240 return failure(); 241 242 return success(); 243 } 244 245 static LogicalResult verify(AllocaScopeOp op) { 246 if (failed(RegionBranchOpInterface::verifyTypes(op))) 247 return failure(); 248 249 return success(); 250 } 251 252 void AllocaScopeOp::getSuccessorRegions( 253 Optional<unsigned> index, ArrayRef<Attribute> operands, 254 SmallVectorImpl<RegionSuccessor> ®ions) { 255 if (index.hasValue()) { 256 regions.push_back(RegionSuccessor(getResults())); 257 return; 258 } 259 260 regions.push_back(RegionSuccessor(&bodyRegion())); 261 } 262 263 //===----------------------------------------------------------------------===// 264 // AssumeAlignmentOp 265 //===----------------------------------------------------------------------===// 266 267 static LogicalResult verify(AssumeAlignmentOp op) { 268 unsigned alignment = op.alignment(); 269 if (!llvm::isPowerOf2_32(alignment)) 270 return op.emitOpError("alignment must be power of 2"); 271 return success(); 272 } 273 274 //===----------------------------------------------------------------------===// 275 // BufferCastOp 276 //===----------------------------------------------------------------------===// 277 278 OpFoldResult BufferCastOp::fold(ArrayRef<Attribute>) { 279 if (auto tensorLoad = tensor().getDefiningOp<TensorLoadOp>()) 280 if (tensorLoad.memref().getType() == getType()) 281 return tensorLoad.memref(); 282 return {}; 283 } 284 285 namespace { 286 /// Replace tensor_cast + buffer_cast by buffer_cast + memref_cast. 287 struct BufferCast : public OpRewritePattern<BufferCastOp> { 288 using OpRewritePattern<BufferCastOp>::OpRewritePattern; 289 290 LogicalResult matchAndRewrite(BufferCastOp bufferCast, 291 PatternRewriter &rewriter) const final { 292 auto tensorCastOperand = 293 bufferCast.getOperand().getDefiningOp<tensor::CastOp>(); 294 if (!tensorCastOperand) 295 return failure(); 296 auto srcTensorType = 297 tensorCastOperand.getOperand().getType().dyn_cast<RankedTensorType>(); 298 if (!srcTensorType) 299 return failure(); 300 auto memrefType = MemRefType::get(srcTensorType.getShape(), 301 srcTensorType.getElementType()); 302 Value memref = rewriter.create<BufferCastOp>( 303 bufferCast.getLoc(), memrefType, tensorCastOperand.getOperand()); 304 rewriter.replaceOpWithNewOp<CastOp>(bufferCast, bufferCast.getType(), 305 memref); 306 return success(); 307 } 308 }; 309 310 /// Canonicalize memref.tensor_load + memref.buffer_cast to memref.cast when 311 /// type mismatches prevent `BufferCastOp::fold` to kick in. 312 struct TensorLoadToMemRef : public OpRewritePattern<BufferCastOp> { 313 using OpRewritePattern<BufferCastOp>::OpRewritePattern; 314 315 LogicalResult matchAndRewrite(BufferCastOp bufferCast, 316 PatternRewriter &rewriter) const final { 317 auto tensorLoad = bufferCast.tensor().getDefiningOp<TensorLoadOp>(); 318 // Bail unless we have a tensor_load + memref.buffer_cast with different 319 // types. `BufferCastOp::fold` handles the same type case. 320 if (!tensorLoad || tensorLoad.memref().getType() == bufferCast.getType()) 321 return failure(); 322 // If types are definitely not cast-compatible, bail. 323 if (!CastOp::areCastCompatible(tensorLoad.memref().getType(), 324 bufferCast.getType())) 325 return failure(); 326 327 // We already know that the types are potentially cast-compatible. However 328 // in case the affine maps are different, we may need to use a copy if we go 329 // from dynamic to static offset or stride (the canonicalization cannot know 330 // at this point that it is really cast compatible). 331 auto isGuaranteedCastCompatible = [](MemRefType source, MemRefType target) { 332 int64_t sourceOffset, targetOffset; 333 SmallVector<int64_t, 4> sourceStrides, targetStrides; 334 if (failed(getStridesAndOffset(source, sourceStrides, sourceOffset)) || 335 failed(getStridesAndOffset(target, targetStrides, targetOffset))) 336 return false; 337 auto dynamicToStatic = [](int64_t a, int64_t b) { 338 return a == MemRefType::getDynamicStrideOrOffset() && 339 b != MemRefType::getDynamicStrideOrOffset(); 340 }; 341 if (dynamicToStatic(sourceOffset, targetOffset)) 342 return false; 343 for (auto it : zip(sourceStrides, targetStrides)) 344 if (dynamicToStatic(std::get<0>(it), std::get<1>(it))) 345 return false; 346 return true; 347 }; 348 349 auto tensorLoadType = tensorLoad.memref().getType().dyn_cast<MemRefType>(); 350 auto bufferCastType = bufferCast.getType().dyn_cast<MemRefType>(); 351 if (tensorLoadType && bufferCastType && 352 !isGuaranteedCastCompatible(tensorLoadType, bufferCastType)) { 353 MemRefType resultType = bufferCastType; 354 auto loc = bufferCast.getLoc(); 355 SmallVector<Value, 4> dynamicOperands; 356 for (int i = 0; i < resultType.getRank(); ++i) { 357 if (resultType.getShape()[i] != ShapedType::kDynamicSize) 358 continue; 359 auto index = rewriter.createOrFold<ConstantIndexOp>(loc, i); 360 Value size = rewriter.create<tensor::DimOp>(loc, tensorLoad, index); 361 dynamicOperands.push_back(size); 362 } 363 auto copy = 364 rewriter.create<memref::AllocOp>(loc, resultType, dynamicOperands); 365 rewriter.create<CopyOp>(loc, tensorLoad.memref(), copy); 366 rewriter.replaceOp(bufferCast, {copy}); 367 } else 368 rewriter.replaceOpWithNewOp<CastOp>(bufferCast, bufferCast.getType(), 369 tensorLoad.memref()); 370 return success(); 371 } 372 }; 373 374 } // namespace 375 376 void BufferCastOp::getCanonicalizationPatterns(RewritePatternSet &results, 377 MLIRContext *context) { 378 results.add<BufferCast, TensorLoadToMemRef>(context); 379 } 380 381 //===----------------------------------------------------------------------===// 382 // CastOp 383 //===----------------------------------------------------------------------===// 384 385 /// Determines whether MemRef_CastOp casts to a more dynamic version of the 386 /// source memref. This is useful to to fold a memref.cast into a consuming op 387 /// and implement canonicalization patterns for ops in different dialects that 388 /// may consume the results of memref.cast operations. Such foldable memref.cast 389 /// operations are typically inserted as `view` and `subview` ops are 390 /// canonicalized, to preserve the type compatibility of their uses. 391 /// 392 /// Returns true when all conditions are met: 393 /// 1. source and result are ranked memrefs with strided semantics and same 394 /// element type and rank. 395 /// 2. each of the source's size, offset or stride has more static information 396 /// than the corresponding result's size, offset or stride. 397 /// 398 /// Example 1: 399 /// ```mlir 400 /// %1 = memref.cast %0 : memref<8x16xf32> to memref<?x?xf32> 401 /// %2 = consumer %1 ... : memref<?x?xf32> ... 402 /// ``` 403 /// 404 /// may fold into: 405 /// 406 /// ```mlir 407 /// %2 = consumer %0 ... : memref<8x16xf32> ... 408 /// ``` 409 /// 410 /// Example 2: 411 /// ``` 412 /// %1 = memref.cast %0 : memref<?x16xf32, affine_map<(i, j)->(16 * i + j)>> 413 /// to memref<?x?xf32> 414 /// consumer %1 : memref<?x?xf32> ... 415 /// ``` 416 /// 417 /// may fold into: 418 /// 419 /// ``` 420 /// consumer %0 ... : memref<?x16xf32, affine_map<(i, j)->(16 * i + j)>> 421 /// ``` 422 bool CastOp::canFoldIntoConsumerOp(CastOp castOp) { 423 MemRefType sourceType = castOp.source().getType().dyn_cast<MemRefType>(); 424 MemRefType resultType = castOp.getType().dyn_cast<MemRefType>(); 425 426 // Requires ranked MemRefType. 427 if (!sourceType || !resultType) 428 return false; 429 430 // Requires same elemental type. 431 if (sourceType.getElementType() != resultType.getElementType()) 432 return false; 433 434 // Requires same rank. 435 if (sourceType.getRank() != resultType.getRank()) 436 return false; 437 438 // Only fold casts between strided memref forms. 439 int64_t sourceOffset, resultOffset; 440 SmallVector<int64_t, 4> sourceStrides, resultStrides; 441 if (failed(getStridesAndOffset(sourceType, sourceStrides, sourceOffset)) || 442 failed(getStridesAndOffset(resultType, resultStrides, resultOffset))) 443 return false; 444 445 // If cast is towards more static sizes along any dimension, don't fold. 446 for (auto it : llvm::zip(sourceType.getShape(), resultType.getShape())) { 447 auto ss = std::get<0>(it), st = std::get<1>(it); 448 if (ss != st) 449 if (MemRefType::isDynamic(ss) && !MemRefType::isDynamic(st)) 450 return false; 451 } 452 453 // If cast is towards more static offset along any dimension, don't fold. 454 if (sourceOffset != resultOffset) 455 if (MemRefType::isDynamicStrideOrOffset(sourceOffset) && 456 !MemRefType::isDynamicStrideOrOffset(resultOffset)) 457 return false; 458 459 // If cast is towards more static strides along any dimension, don't fold. 460 for (auto it : llvm::zip(sourceStrides, resultStrides)) { 461 auto ss = std::get<0>(it), st = std::get<1>(it); 462 if (ss != st) 463 if (MemRefType::isDynamicStrideOrOffset(ss) && 464 !MemRefType::isDynamicStrideOrOffset(st)) 465 return false; 466 } 467 468 return true; 469 } 470 471 bool CastOp::areCastCompatible(TypeRange inputs, TypeRange outputs) { 472 if (inputs.size() != 1 || outputs.size() != 1) 473 return false; 474 Type a = inputs.front(), b = outputs.front(); 475 auto aT = a.dyn_cast<MemRefType>(); 476 auto bT = b.dyn_cast<MemRefType>(); 477 478 auto uaT = a.dyn_cast<UnrankedMemRefType>(); 479 auto ubT = b.dyn_cast<UnrankedMemRefType>(); 480 481 if (aT && bT) { 482 if (aT.getElementType() != bT.getElementType()) 483 return false; 484 if (aT.getAffineMaps() != bT.getAffineMaps()) { 485 int64_t aOffset, bOffset; 486 SmallVector<int64_t, 4> aStrides, bStrides; 487 if (failed(getStridesAndOffset(aT, aStrides, aOffset)) || 488 failed(getStridesAndOffset(bT, bStrides, bOffset)) || 489 aStrides.size() != bStrides.size()) 490 return false; 491 492 // Strides along a dimension/offset are compatible if the value in the 493 // source memref is static and the value in the target memref is the 494 // same. They are also compatible if either one is dynamic (see 495 // description of MemRefCastOp for details). 496 auto checkCompatible = [](int64_t a, int64_t b) { 497 return (a == MemRefType::getDynamicStrideOrOffset() || 498 b == MemRefType::getDynamicStrideOrOffset() || a == b); 499 }; 500 if (!checkCompatible(aOffset, bOffset)) 501 return false; 502 for (auto aStride : enumerate(aStrides)) 503 if (!checkCompatible(aStride.value(), bStrides[aStride.index()])) 504 return false; 505 } 506 if (aT.getMemorySpace() != bT.getMemorySpace()) 507 return false; 508 509 // They must have the same rank, and any specified dimensions must match. 510 if (aT.getRank() != bT.getRank()) 511 return false; 512 513 for (unsigned i = 0, e = aT.getRank(); i != e; ++i) { 514 int64_t aDim = aT.getDimSize(i), bDim = bT.getDimSize(i); 515 if (aDim != -1 && bDim != -1 && aDim != bDim) 516 return false; 517 } 518 return true; 519 } else { 520 if (!aT && !uaT) 521 return false; 522 if (!bT && !ubT) 523 return false; 524 // Unranked to unranked casting is unsupported 525 if (uaT && ubT) 526 return false; 527 528 auto aEltType = (aT) ? aT.getElementType() : uaT.getElementType(); 529 auto bEltType = (bT) ? bT.getElementType() : ubT.getElementType(); 530 if (aEltType != bEltType) 531 return false; 532 533 auto aMemSpace = (aT) ? aT.getMemorySpace() : uaT.getMemorySpace(); 534 auto bMemSpace = (bT) ? bT.getMemorySpace() : ubT.getMemorySpace(); 535 if (aMemSpace != bMemSpace) 536 return false; 537 538 return true; 539 } 540 541 return false; 542 } 543 544 OpFoldResult CastOp::fold(ArrayRef<Attribute> operands) { 545 return succeeded(foldMemRefCast(*this)) ? getResult() : Value(); 546 } 547 548 //===----------------------------------------------------------------------===// 549 // CloneOp 550 //===----------------------------------------------------------------------===// 551 552 void CloneOp::getEffects( 553 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>> 554 &effects) { 555 effects.emplace_back(MemoryEffects::Read::get(), input(), 556 SideEffects::DefaultResource::get()); 557 effects.emplace_back(MemoryEffects::Write::get(), output(), 558 SideEffects::DefaultResource::get()); 559 effects.emplace_back(MemoryEffects::Allocate::get(), output(), 560 SideEffects::DefaultResource::get()); 561 } 562 563 namespace { 564 /// Merge the clone and its source (by converting the clone to a cast) when 565 /// possible. 566 struct SimplifyClones : public OpRewritePattern<CloneOp> { 567 using OpRewritePattern<CloneOp>::OpRewritePattern; 568 569 LogicalResult matchAndRewrite(CloneOp cloneOp, 570 PatternRewriter &rewriter) const override { 571 if (cloneOp.use_empty()) { 572 rewriter.eraseOp(cloneOp); 573 return success(); 574 } 575 576 Value source = cloneOp.input(); 577 578 // This only finds dealloc operations for the immediate value. It should 579 // also consider aliases. That would also make the safety check below 580 // redundant. 581 llvm::Optional<Operation *> maybeCloneDeallocOp = 582 findDealloc(cloneOp.output()); 583 // Skip if either of them has > 1 deallocate operations. 584 if (!maybeCloneDeallocOp.hasValue()) 585 return failure(); 586 llvm::Optional<Operation *> maybeSourceDeallocOp = findDealloc(source); 587 if (!maybeSourceDeallocOp.hasValue()) 588 return failure(); 589 Operation *cloneDeallocOp = *maybeCloneDeallocOp; 590 Operation *sourceDeallocOp = *maybeSourceDeallocOp; 591 592 // If both are deallocated in the same block, their in-block lifetimes 593 // might not fully overlap, so we cannot decide which one to drop. 594 if (cloneDeallocOp && sourceDeallocOp && 595 cloneDeallocOp->getBlock() == sourceDeallocOp->getBlock()) 596 return failure(); 597 598 Block *currentBlock = cloneOp->getBlock(); 599 Operation *redundantDealloc = nullptr; 600 if (cloneDeallocOp && cloneDeallocOp->getBlock() == currentBlock) { 601 redundantDealloc = cloneDeallocOp; 602 } else if (sourceDeallocOp && sourceDeallocOp->getBlock() == currentBlock) { 603 redundantDealloc = sourceDeallocOp; 604 } 605 606 if (!redundantDealloc) 607 return failure(); 608 609 // Safety check that there are no other deallocations inbetween 610 // cloneOp and redundantDealloc, as otherwise we might deallocate an alias 611 // of source before the uses of the clone. With alias information, we could 612 // restrict this to only fail of the dealloc's operand is an alias 613 // of the source. 614 for (Operation *pos = cloneOp->getNextNode(); pos != redundantDealloc; 615 pos = pos->getNextNode()) { 616 auto effectInterface = dyn_cast<MemoryEffectOpInterface>(pos); 617 if (!effectInterface) 618 continue; 619 if (effectInterface.hasEffect<MemoryEffects::Free>()) 620 return failure(); 621 } 622 623 rewriter.replaceOpWithNewOp<memref::CastOp>(cloneOp, cloneOp.getType(), 624 source); 625 rewriter.eraseOp(redundantDealloc); 626 return success(); 627 } 628 }; 629 630 } // end anonymous namespace. 631 632 void CloneOp::getCanonicalizationPatterns(OwningRewritePatternList &results, 633 MLIRContext *context) { 634 results.insert<SimplifyClones>(context); 635 } 636 637 OpFoldResult CloneOp::fold(ArrayRef<Attribute> operands) { 638 return succeeded(foldMemRefCast(*this)) ? getResult() : Value(); 639 } 640 641 //===----------------------------------------------------------------------===// 642 // DeallocOp 643 //===----------------------------------------------------------------------===// 644 645 LogicalResult DeallocOp::fold(ArrayRef<Attribute> cstOperands, 646 SmallVectorImpl<OpFoldResult> &results) { 647 /// dealloc(memrefcast) -> dealloc 648 return foldMemRefCast(*this); 649 } 650 651 //===----------------------------------------------------------------------===// 652 // DimOp 653 //===----------------------------------------------------------------------===// 654 655 void DimOp::build(OpBuilder &builder, OperationState &result, Value source, 656 int64_t index) { 657 auto loc = result.location; 658 Value indexValue = builder.create<ConstantIndexOp>(loc, index); 659 build(builder, result, source, indexValue); 660 } 661 662 void DimOp::build(OpBuilder &builder, OperationState &result, Value source, 663 Value index) { 664 auto indexTy = builder.getIndexType(); 665 build(builder, result, indexTy, source, index); 666 } 667 668 Optional<int64_t> DimOp::getConstantIndex() { 669 if (auto constantOp = index().getDefiningOp<ConstantOp>()) 670 return constantOp.getValue().cast<IntegerAttr>().getInt(); 671 return {}; 672 } 673 674 static LogicalResult verify(DimOp op) { 675 // Assume unknown index to be in range. 676 Optional<int64_t> index = op.getConstantIndex(); 677 if (!index.hasValue()) 678 return success(); 679 680 // Check that constant index is not knowingly out of range. 681 auto type = op.source().getType(); 682 if (auto memrefType = type.dyn_cast<MemRefType>()) { 683 if (index.getValue() >= memrefType.getRank()) 684 return op.emitOpError("index is out of range"); 685 } else if (type.isa<UnrankedMemRefType>()) { 686 // Assume index to be in range. 687 } else { 688 llvm_unreachable("expected operand with memref type"); 689 } 690 return success(); 691 } 692 693 /// Return a map with key being elements in `vals` and data being number of 694 /// occurences of it. Use std::map, since the `vals` here are strides and the 695 /// dynamic stride value is the same as the tombstone value for 696 /// `DenseMap<int64_t>`. 697 static std::map<int64_t, unsigned> getNumOccurences(ArrayRef<int64_t> vals) { 698 std::map<int64_t, unsigned> numOccurences; 699 for (auto val : vals) 700 numOccurences[val]++; 701 return numOccurences; 702 } 703 704 /// Given the type of the un-rank reduced subview result type and the 705 /// rank-reduced result type, computes the dropped dimensions. This accounts for 706 /// cases where there are multiple unit-dims, but only a subset of those are 707 /// dropped. For MemRefTypes these can be disambiguated using the strides. If a 708 /// dimension is dropped the stride must be dropped too. 709 static llvm::Optional<llvm::SmallDenseSet<unsigned>> 710 computeMemRefRankReductionMask(MemRefType originalType, MemRefType reducedType, 711 ArrayAttr staticSizes) { 712 llvm::SmallDenseSet<unsigned> unusedDims; 713 if (originalType.getRank() == reducedType.getRank()) 714 return unusedDims; 715 716 for (auto dim : llvm::enumerate(staticSizes)) 717 if (dim.value().cast<IntegerAttr>().getInt() == 1) 718 unusedDims.insert(dim.index()); 719 SmallVector<int64_t> originalStrides, candidateStrides; 720 int64_t originalOffset, candidateOffset; 721 if (failed( 722 getStridesAndOffset(originalType, originalStrides, originalOffset)) || 723 failed( 724 getStridesAndOffset(reducedType, candidateStrides, candidateOffset))) 725 return llvm::None; 726 727 // For memrefs, a dimension is truly dropped if its corresponding stride is 728 // also dropped. This is particularly important when more than one of the dims 729 // is 1. Track the number of occurences of the strides in the original type 730 // and the candidate type. For each unused dim that stride should not be 731 // present in the candidate type. Note that there could be multiple dimensions 732 // that have the same size. We dont need to exactly figure out which dim 733 // corresponds to which stride, we just need to verify that the number of 734 // reptitions of a stride in the original + number of unused dims with that 735 // stride == number of repititions of a stride in the candidate. 736 std::map<int64_t, unsigned> currUnaccountedStrides = 737 getNumOccurences(originalStrides); 738 std::map<int64_t, unsigned> candidateStridesNumOccurences = 739 getNumOccurences(candidateStrides); 740 llvm::SmallDenseSet<unsigned> prunedUnusedDims; 741 for (unsigned dim : unusedDims) { 742 int64_t originalStride = originalStrides[dim]; 743 if (currUnaccountedStrides[originalStride] > 744 candidateStridesNumOccurences[originalStride]) { 745 // This dim can be treated as dropped. 746 currUnaccountedStrides[originalStride]--; 747 continue; 748 } 749 if (currUnaccountedStrides[originalStride] == 750 candidateStridesNumOccurences[originalStride]) { 751 // The stride for this is not dropped. Keep as is. 752 prunedUnusedDims.insert(dim); 753 continue; 754 } 755 if (currUnaccountedStrides[originalStride] < 756 candidateStridesNumOccurences[originalStride]) { 757 // This should never happen. Cant have a stride in the reduced rank type 758 // that wasnt in the original one. 759 return llvm::None; 760 } 761 } 762 763 for (auto prunedDim : prunedUnusedDims) 764 unusedDims.erase(prunedDim); 765 if (unusedDims.size() + reducedType.getRank() != originalType.getRank()) 766 return llvm::None; 767 return unusedDims; 768 } 769 770 llvm::SmallDenseSet<unsigned> SubViewOp::getDroppedDims() { 771 MemRefType sourceType = getSourceType(); 772 MemRefType resultType = getType(); 773 llvm::Optional<llvm::SmallDenseSet<unsigned>> unusedDims = 774 computeMemRefRankReductionMask(sourceType, resultType, static_sizes()); 775 assert(unusedDims && "unable to find unused dims of subview"); 776 return *unusedDims; 777 } 778 779 OpFoldResult DimOp::fold(ArrayRef<Attribute> operands) { 780 // All forms of folding require a known index. 781 auto index = operands[1].dyn_cast_or_null<IntegerAttr>(); 782 if (!index) 783 return {}; 784 785 // Folding for unranked types (UnrankedMemRefType) is not supported. 786 auto memrefType = source().getType().dyn_cast<MemRefType>(); 787 if (!memrefType) 788 return {}; 789 790 // Fold if the shape extent along the given index is known. 791 if (!memrefType.isDynamicDim(index.getInt())) { 792 Builder builder(getContext()); 793 return builder.getIndexAttr(memrefType.getShape()[index.getInt()]); 794 } 795 796 // The size at the given index is now known to be a dynamic size. 797 unsigned unsignedIndex = index.getValue().getZExtValue(); 798 799 // Fold dim to the size argument for an `AllocOp`, `ViewOp`, or `SubViewOp`. 800 Operation *definingOp = source().getDefiningOp(); 801 802 if (auto alloc = dyn_cast_or_null<AllocOp>(definingOp)) 803 return *(alloc.getDynamicSizes().begin() + 804 memrefType.getDynamicDimIndex(unsignedIndex)); 805 806 if (auto alloca = dyn_cast_or_null<AllocaOp>(definingOp)) 807 return *(alloca.getDynamicSizes().begin() + 808 memrefType.getDynamicDimIndex(unsignedIndex)); 809 810 if (auto view = dyn_cast_or_null<ViewOp>(definingOp)) 811 return *(view.getDynamicSizes().begin() + 812 memrefType.getDynamicDimIndex(unsignedIndex)); 813 814 if (auto subview = dyn_cast_or_null<SubViewOp>(definingOp)) { 815 llvm::SmallDenseSet<unsigned> unusedDims = subview.getDroppedDims(); 816 unsigned resultIndex = 0; 817 unsigned sourceRank = subview.getSourceType().getRank(); 818 unsigned sourceIndex = 0; 819 for (auto i : llvm::seq<unsigned>(0, sourceRank)) { 820 if (unusedDims.count(i)) 821 continue; 822 if (resultIndex == unsignedIndex) { 823 sourceIndex = i; 824 break; 825 } 826 resultIndex++; 827 } 828 assert(subview.isDynamicSize(sourceIndex) && 829 "expected dynamic subview size"); 830 return subview.getDynamicSize(sourceIndex); 831 } 832 833 if (auto sizeInterface = 834 dyn_cast_or_null<OffsetSizeAndStrideOpInterface>(definingOp)) { 835 assert(sizeInterface.isDynamicSize(unsignedIndex) && 836 "Expected dynamic subview size"); 837 return sizeInterface.getDynamicSize(unsignedIndex); 838 } 839 840 // dim(memrefcast) -> dim 841 if (succeeded(foldMemRefCast(*this))) 842 return getResult(); 843 844 return {}; 845 } 846 847 namespace { 848 /// Fold dim of a memref reshape operation to a load into the reshape's shape 849 /// operand. 850 struct DimOfMemRefReshape : public OpRewritePattern<DimOp> { 851 using OpRewritePattern<DimOp>::OpRewritePattern; 852 853 LogicalResult matchAndRewrite(DimOp dim, 854 PatternRewriter &rewriter) const override { 855 auto reshape = dim.source().getDefiningOp<ReshapeOp>(); 856 857 if (!reshape) 858 return failure(); 859 860 // Place the load directly after the reshape to ensure that the shape memref 861 // was not mutated. 862 rewriter.setInsertionPointAfter(reshape); 863 Location loc = dim.getLoc(); 864 Value load = rewriter.create<LoadOp>(loc, reshape.shape(), dim.index()); 865 if (load.getType() != dim.getType()) 866 load = rewriter.create<IndexCastOp>(loc, dim.getType(), load); 867 rewriter.replaceOp(dim, load); 868 return success(); 869 } 870 }; 871 872 /// Fold dim of a cast into the dim of the source of the memref cast. 873 struct DimOfCastOp : public OpRewritePattern<DimOp> { 874 using OpRewritePattern<DimOp>::OpRewritePattern; 875 876 LogicalResult matchAndRewrite(DimOp dimOp, 877 PatternRewriter &rewriter) const override { 878 auto castOp = dimOp.source().getDefiningOp<BufferCastOp>(); 879 if (!castOp) 880 return failure(); 881 Value newSource = castOp.getOperand(); 882 rewriter.replaceOpWithNewOp<tensor::DimOp>(dimOp, newSource, dimOp.index()); 883 return success(); 884 } 885 }; 886 } // end anonymous namespace. 887 888 void DimOp::getCanonicalizationPatterns(RewritePatternSet &results, 889 MLIRContext *context) { 890 results.add<DimOfMemRefReshape, DimOfCastOp>(context); 891 } 892 893 // --------------------------------------------------------------------------- 894 // DmaStartOp 895 // --------------------------------------------------------------------------- 896 897 void DmaStartOp::build(OpBuilder &builder, OperationState &result, 898 Value srcMemRef, ValueRange srcIndices, Value destMemRef, 899 ValueRange destIndices, Value numElements, 900 Value tagMemRef, ValueRange tagIndices, Value stride, 901 Value elementsPerStride) { 902 result.addOperands(srcMemRef); 903 result.addOperands(srcIndices); 904 result.addOperands(destMemRef); 905 result.addOperands(destIndices); 906 result.addOperands({numElements, tagMemRef}); 907 result.addOperands(tagIndices); 908 if (stride) 909 result.addOperands({stride, elementsPerStride}); 910 } 911 912 void DmaStartOp::print(OpAsmPrinter &p) { 913 p << " " << getSrcMemRef() << '[' << getSrcIndices() << "], " 914 << getDstMemRef() << '[' << getDstIndices() << "], " << getNumElements() 915 << ", " << getTagMemRef() << '[' << getTagIndices() << ']'; 916 if (isStrided()) 917 p << ", " << getStride() << ", " << getNumElementsPerStride(); 918 919 p.printOptionalAttrDict((*this)->getAttrs()); 920 p << " : " << getSrcMemRef().getType() << ", " << getDstMemRef().getType() 921 << ", " << getTagMemRef().getType(); 922 } 923 924 // Parse DmaStartOp. 925 // Ex: 926 // %dma_id = dma_start %src[%i, %j], %dst[%k, %l], %size, 927 // %tag[%index], %stride, %num_elt_per_stride : 928 // : memref<3076 x f32, 0>, 929 // memref<1024 x f32, 2>, 930 // memref<1 x i32> 931 // 932 ParseResult DmaStartOp::parse(OpAsmParser &parser, OperationState &result) { 933 OpAsmParser::OperandType srcMemRefInfo; 934 SmallVector<OpAsmParser::OperandType, 4> srcIndexInfos; 935 OpAsmParser::OperandType dstMemRefInfo; 936 SmallVector<OpAsmParser::OperandType, 4> dstIndexInfos; 937 OpAsmParser::OperandType numElementsInfo; 938 OpAsmParser::OperandType tagMemrefInfo; 939 SmallVector<OpAsmParser::OperandType, 4> tagIndexInfos; 940 SmallVector<OpAsmParser::OperandType, 2> strideInfo; 941 942 SmallVector<Type, 3> types; 943 auto indexType = parser.getBuilder().getIndexType(); 944 945 // Parse and resolve the following list of operands: 946 // *) source memref followed by its indices (in square brackets). 947 // *) destination memref followed by its indices (in square brackets). 948 // *) dma size in KiB. 949 if (parser.parseOperand(srcMemRefInfo) || 950 parser.parseOperandList(srcIndexInfos, OpAsmParser::Delimiter::Square) || 951 parser.parseComma() || parser.parseOperand(dstMemRefInfo) || 952 parser.parseOperandList(dstIndexInfos, OpAsmParser::Delimiter::Square) || 953 parser.parseComma() || parser.parseOperand(numElementsInfo) || 954 parser.parseComma() || parser.parseOperand(tagMemrefInfo) || 955 parser.parseOperandList(tagIndexInfos, OpAsmParser::Delimiter::Square)) 956 return failure(); 957 958 // Parse optional stride and elements per stride. 959 if (parser.parseTrailingOperandList(strideInfo)) 960 return failure(); 961 962 bool isStrided = strideInfo.size() == 2; 963 if (!strideInfo.empty() && !isStrided) { 964 return parser.emitError(parser.getNameLoc(), 965 "expected two stride related operands"); 966 } 967 968 if (parser.parseColonTypeList(types)) 969 return failure(); 970 if (types.size() != 3) 971 return parser.emitError(parser.getNameLoc(), "fewer/more types expected"); 972 973 if (parser.resolveOperand(srcMemRefInfo, types[0], result.operands) || 974 parser.resolveOperands(srcIndexInfos, indexType, result.operands) || 975 parser.resolveOperand(dstMemRefInfo, types[1], result.operands) || 976 parser.resolveOperands(dstIndexInfos, indexType, result.operands) || 977 // size should be an index. 978 parser.resolveOperand(numElementsInfo, indexType, result.operands) || 979 parser.resolveOperand(tagMemrefInfo, types[2], result.operands) || 980 // tag indices should be index. 981 parser.resolveOperands(tagIndexInfos, indexType, result.operands)) 982 return failure(); 983 984 if (isStrided) { 985 if (parser.resolveOperands(strideInfo, indexType, result.operands)) 986 return failure(); 987 } 988 989 return success(); 990 } 991 992 LogicalResult DmaStartOp::verify() { 993 unsigned numOperands = getNumOperands(); 994 995 // Mandatory non-variadic operands are: src memref, dst memref, tag memref and 996 // the number of elements. 997 if (numOperands < 4) 998 return emitOpError("expected at least 4 operands"); 999 1000 // Check types of operands. The order of these calls is important: the later 1001 // calls rely on some type properties to compute the operand position. 1002 // 1. Source memref. 1003 if (!getSrcMemRef().getType().isa<MemRefType>()) 1004 return emitOpError("expected source to be of memref type"); 1005 if (numOperands < getSrcMemRefRank() + 4) 1006 return emitOpError() << "expected at least " << getSrcMemRefRank() + 4 1007 << " operands"; 1008 if (!getSrcIndices().empty() && 1009 !llvm::all_of(getSrcIndices().getTypes(), 1010 [](Type t) { return t.isIndex(); })) 1011 return emitOpError("expected source indices to be of index type"); 1012 1013 // 2. Destination memref. 1014 if (!getDstMemRef().getType().isa<MemRefType>()) 1015 return emitOpError("expected destination to be of memref type"); 1016 unsigned numExpectedOperands = getSrcMemRefRank() + getDstMemRefRank() + 4; 1017 if (numOperands < numExpectedOperands) 1018 return emitOpError() << "expected at least " << numExpectedOperands 1019 << " operands"; 1020 if (!getDstIndices().empty() && 1021 !llvm::all_of(getDstIndices().getTypes(), 1022 [](Type t) { return t.isIndex(); })) 1023 return emitOpError("expected destination indices to be of index type"); 1024 1025 // 3. Number of elements. 1026 if (!getNumElements().getType().isIndex()) 1027 return emitOpError("expected num elements to be of index type"); 1028 1029 // 4. Tag memref. 1030 if (!getTagMemRef().getType().isa<MemRefType>()) 1031 return emitOpError("expected tag to be of memref type"); 1032 numExpectedOperands += getTagMemRefRank(); 1033 if (numOperands < numExpectedOperands) 1034 return emitOpError() << "expected at least " << numExpectedOperands 1035 << " operands"; 1036 if (!getTagIndices().empty() && 1037 !llvm::all_of(getTagIndices().getTypes(), 1038 [](Type t) { return t.isIndex(); })) 1039 return emitOpError("expected tag indices to be of index type"); 1040 1041 // Optional stride-related operands must be either both present or both 1042 // absent. 1043 if (numOperands != numExpectedOperands && 1044 numOperands != numExpectedOperands + 2) 1045 return emitOpError("incorrect number of operands"); 1046 1047 // 5. Strides. 1048 if (isStrided()) { 1049 if (!getStride().getType().isIndex() || 1050 !getNumElementsPerStride().getType().isIndex()) 1051 return emitOpError( 1052 "expected stride and num elements per stride to be of type index"); 1053 } 1054 1055 return success(); 1056 } 1057 1058 LogicalResult DmaStartOp::fold(ArrayRef<Attribute> cstOperands, 1059 SmallVectorImpl<OpFoldResult> &results) { 1060 /// dma_start(memrefcast) -> dma_start 1061 return foldMemRefCast(*this); 1062 } 1063 1064 // --------------------------------------------------------------------------- 1065 // DmaWaitOp 1066 // --------------------------------------------------------------------------- 1067 1068 void DmaWaitOp::build(OpBuilder &builder, OperationState &result, 1069 Value tagMemRef, ValueRange tagIndices, 1070 Value numElements) { 1071 result.addOperands(tagMemRef); 1072 result.addOperands(tagIndices); 1073 result.addOperands(numElements); 1074 } 1075 1076 void DmaWaitOp::print(OpAsmPrinter &p) { 1077 p << " " << getTagMemRef() << '[' << getTagIndices() << "], " 1078 << getNumElements(); 1079 p.printOptionalAttrDict((*this)->getAttrs()); 1080 p << " : " << getTagMemRef().getType(); 1081 } 1082 1083 // Parse DmaWaitOp. 1084 // Eg: 1085 // dma_wait %tag[%index], %num_elements : memref<1 x i32, (d0) -> (d0), 4> 1086 // 1087 ParseResult DmaWaitOp::parse(OpAsmParser &parser, OperationState &result) { 1088 OpAsmParser::OperandType tagMemrefInfo; 1089 SmallVector<OpAsmParser::OperandType, 2> tagIndexInfos; 1090 Type type; 1091 auto indexType = parser.getBuilder().getIndexType(); 1092 OpAsmParser::OperandType numElementsInfo; 1093 1094 // Parse tag memref, its indices, and dma size. 1095 if (parser.parseOperand(tagMemrefInfo) || 1096 parser.parseOperandList(tagIndexInfos, OpAsmParser::Delimiter::Square) || 1097 parser.parseComma() || parser.parseOperand(numElementsInfo) || 1098 parser.parseColonType(type) || 1099 parser.resolveOperand(tagMemrefInfo, type, result.operands) || 1100 parser.resolveOperands(tagIndexInfos, indexType, result.operands) || 1101 parser.resolveOperand(numElementsInfo, indexType, result.operands)) 1102 return failure(); 1103 1104 return success(); 1105 } 1106 1107 LogicalResult DmaWaitOp::fold(ArrayRef<Attribute> cstOperands, 1108 SmallVectorImpl<OpFoldResult> &results) { 1109 /// dma_wait(memrefcast) -> dma_wait 1110 return foldMemRefCast(*this); 1111 } 1112 1113 LogicalResult DmaWaitOp::verify() { 1114 // Mandatory non-variadic operands are tag and the number of elements. 1115 if (getNumOperands() < 2) 1116 return emitOpError() << "expected at least 2 operands"; 1117 1118 // Check types of operands. The order of these calls is important: the later 1119 // calls rely on some type properties to compute the operand position. 1120 if (!getTagMemRef().getType().isa<MemRefType>()) 1121 return emitOpError() << "expected tag to be of memref type"; 1122 1123 if (getNumOperands() != 2 + getTagMemRefRank()) 1124 return emitOpError() << "expected " << 2 + getTagMemRefRank() 1125 << " operands"; 1126 1127 if (!getTagIndices().empty() && 1128 !llvm::all_of(getTagIndices().getTypes(), 1129 [](Type t) { return t.isIndex(); })) 1130 return emitOpError() << "expected tag indices to be of index type"; 1131 1132 if (!getNumElements().getType().isIndex()) 1133 return emitOpError() 1134 << "expected the number of elements to be of index type"; 1135 1136 return success(); 1137 } 1138 1139 //===----------------------------------------------------------------------===// 1140 // GlobalOp 1141 //===----------------------------------------------------------------------===// 1142 1143 static void printGlobalMemrefOpTypeAndInitialValue(OpAsmPrinter &p, GlobalOp op, 1144 TypeAttr type, 1145 Attribute initialValue) { 1146 p << type; 1147 if (!op.isExternal()) { 1148 p << " = "; 1149 if (op.isUninitialized()) 1150 p << "uninitialized"; 1151 else 1152 p.printAttributeWithoutType(initialValue); 1153 } 1154 } 1155 1156 static ParseResult 1157 parseGlobalMemrefOpTypeAndInitialValue(OpAsmParser &parser, TypeAttr &typeAttr, 1158 Attribute &initialValue) { 1159 Type type; 1160 if (parser.parseType(type)) 1161 return failure(); 1162 1163 auto memrefType = type.dyn_cast<MemRefType>(); 1164 if (!memrefType || !memrefType.hasStaticShape()) 1165 return parser.emitError(parser.getNameLoc()) 1166 << "type should be static shaped memref, but got " << type; 1167 typeAttr = TypeAttr::get(type); 1168 1169 if (parser.parseOptionalEqual()) 1170 return success(); 1171 1172 if (succeeded(parser.parseOptionalKeyword("uninitialized"))) { 1173 initialValue = UnitAttr::get(parser.getBuilder().getContext()); 1174 return success(); 1175 } 1176 1177 Type tensorType = getTensorTypeFromMemRefType(memrefType); 1178 if (parser.parseAttribute(initialValue, tensorType)) 1179 return failure(); 1180 if (!initialValue.isa<ElementsAttr>()) 1181 return parser.emitError(parser.getNameLoc()) 1182 << "initial value should be a unit or elements attribute"; 1183 return success(); 1184 } 1185 1186 static LogicalResult verify(GlobalOp op) { 1187 auto memrefType = op.type().dyn_cast<MemRefType>(); 1188 if (!memrefType || !memrefType.hasStaticShape()) 1189 return op.emitOpError("type should be static shaped memref, but got ") 1190 << op.type(); 1191 1192 // Verify that the initial value, if present, is either a unit attribute or 1193 // an elements attribute. 1194 if (op.initial_value().hasValue()) { 1195 Attribute initValue = op.initial_value().getValue(); 1196 if (!initValue.isa<UnitAttr>() && !initValue.isa<ElementsAttr>()) 1197 return op.emitOpError("initial value should be a unit or elements " 1198 "attribute, but got ") 1199 << initValue; 1200 1201 // Check that the type of the initial value is compatible with the type of 1202 // the global variable. 1203 if (initValue.isa<ElementsAttr>()) { 1204 Type initType = initValue.getType(); 1205 Type tensorType = getTensorTypeFromMemRefType(memrefType); 1206 if (initType != tensorType) 1207 return op.emitOpError("initial value expected to be of type ") 1208 << tensorType << ", but was of type " << initType; 1209 } 1210 } 1211 1212 // TODO: verify visibility for declarations. 1213 return success(); 1214 } 1215 1216 //===----------------------------------------------------------------------===// 1217 // GetGlobalOp 1218 //===----------------------------------------------------------------------===// 1219 1220 LogicalResult 1221 GetGlobalOp::verifySymbolUses(SymbolTableCollection &symbolTable) { 1222 // Verify that the result type is same as the type of the referenced 1223 // memref.global op. 1224 auto global = 1225 symbolTable.lookupNearestSymbolFrom<GlobalOp>(*this, nameAttr()); 1226 if (!global) 1227 return emitOpError("'") 1228 << name() << "' does not reference a valid global memref"; 1229 1230 Type resultType = result().getType(); 1231 if (global.type() != resultType) 1232 return emitOpError("result type ") 1233 << resultType << " does not match type " << global.type() 1234 << " of the global memref @" << name(); 1235 return success(); 1236 } 1237 1238 //===----------------------------------------------------------------------===// 1239 // LoadOp 1240 //===----------------------------------------------------------------------===// 1241 1242 static LogicalResult verify(LoadOp op) { 1243 if (op.getNumOperands() != 1 + op.getMemRefType().getRank()) 1244 return op.emitOpError("incorrect number of indices for load"); 1245 return success(); 1246 } 1247 1248 OpFoldResult LoadOp::fold(ArrayRef<Attribute> cstOperands) { 1249 /// load(memrefcast) -> load 1250 if (succeeded(foldMemRefCast(*this))) 1251 return getResult(); 1252 return OpFoldResult(); 1253 } 1254 1255 namespace { 1256 /// Fold a load on a buffer_cast operation into an tensor.extract on the 1257 /// corresponding tensor. 1258 struct LoadOfBufferCast : public OpRewritePattern<LoadOp> { 1259 using OpRewritePattern<LoadOp>::OpRewritePattern; 1260 1261 LogicalResult matchAndRewrite(LoadOp load, 1262 PatternRewriter &rewriter) const override { 1263 auto buffercast = load.memref().getDefiningOp<BufferCastOp>(); 1264 if (!buffercast) 1265 return failure(); 1266 1267 rewriter.replaceOpWithNewOp<tensor::ExtractOp>(load, buffercast.tensor(), 1268 load.indices()); 1269 return success(); 1270 } 1271 }; 1272 } // end anonymous namespace. 1273 1274 void LoadOp::getCanonicalizationPatterns(RewritePatternSet &results, 1275 MLIRContext *context) { 1276 results.add<LoadOfBufferCast>(context); 1277 } 1278 1279 //===----------------------------------------------------------------------===// 1280 // PrefetchOp 1281 //===----------------------------------------------------------------------===// 1282 1283 static void print(OpAsmPrinter &p, PrefetchOp op) { 1284 p << " " << op.memref() << '['; 1285 p.printOperands(op.indices()); 1286 p << ']' << ", " << (op.isWrite() ? "write" : "read"); 1287 p << ", locality<" << op.localityHint(); 1288 p << ">, " << (op.isDataCache() ? "data" : "instr"); 1289 p.printOptionalAttrDict( 1290 op->getAttrs(), 1291 /*elidedAttrs=*/{"localityHint", "isWrite", "isDataCache"}); 1292 p << " : " << op.getMemRefType(); 1293 } 1294 1295 static ParseResult parsePrefetchOp(OpAsmParser &parser, 1296 OperationState &result) { 1297 OpAsmParser::OperandType memrefInfo; 1298 SmallVector<OpAsmParser::OperandType, 4> indexInfo; 1299 IntegerAttr localityHint; 1300 MemRefType type; 1301 StringRef readOrWrite, cacheType; 1302 1303 auto indexTy = parser.getBuilder().getIndexType(); 1304 auto i32Type = parser.getBuilder().getIntegerType(32); 1305 if (parser.parseOperand(memrefInfo) || 1306 parser.parseOperandList(indexInfo, OpAsmParser::Delimiter::Square) || 1307 parser.parseComma() || parser.parseKeyword(&readOrWrite) || 1308 parser.parseComma() || parser.parseKeyword("locality") || 1309 parser.parseLess() || 1310 parser.parseAttribute(localityHint, i32Type, "localityHint", 1311 result.attributes) || 1312 parser.parseGreater() || parser.parseComma() || 1313 parser.parseKeyword(&cacheType) || parser.parseColonType(type) || 1314 parser.resolveOperand(memrefInfo, type, result.operands) || 1315 parser.resolveOperands(indexInfo, indexTy, result.operands)) 1316 return failure(); 1317 1318 if (!readOrWrite.equals("read") && !readOrWrite.equals("write")) 1319 return parser.emitError(parser.getNameLoc(), 1320 "rw specifier has to be 'read' or 'write'"); 1321 result.addAttribute( 1322 PrefetchOp::getIsWriteAttrName(), 1323 parser.getBuilder().getBoolAttr(readOrWrite.equals("write"))); 1324 1325 if (!cacheType.equals("data") && !cacheType.equals("instr")) 1326 return parser.emitError(parser.getNameLoc(), 1327 "cache type has to be 'data' or 'instr'"); 1328 1329 result.addAttribute( 1330 PrefetchOp::getIsDataCacheAttrName(), 1331 parser.getBuilder().getBoolAttr(cacheType.equals("data"))); 1332 1333 return success(); 1334 } 1335 1336 static LogicalResult verify(PrefetchOp op) { 1337 if (op.getNumOperands() != 1 + op.getMemRefType().getRank()) 1338 return op.emitOpError("too few indices"); 1339 1340 return success(); 1341 } 1342 1343 LogicalResult PrefetchOp::fold(ArrayRef<Attribute> cstOperands, 1344 SmallVectorImpl<OpFoldResult> &results) { 1345 // prefetch(memrefcast) -> prefetch 1346 return foldMemRefCast(*this); 1347 } 1348 1349 //===----------------------------------------------------------------------===// 1350 // ReinterpretCastOp 1351 //===----------------------------------------------------------------------===// 1352 1353 /// Build a ReinterpretCastOp with all dynamic entries: `staticOffsets`, 1354 /// `staticSizes` and `staticStrides` are automatically filled with 1355 /// source-memref-rank sentinel values that encode dynamic entries. 1356 void ReinterpretCastOp::build(OpBuilder &b, OperationState &result, 1357 MemRefType resultType, Value source, 1358 OpFoldResult offset, ArrayRef<OpFoldResult> sizes, 1359 ArrayRef<OpFoldResult> strides, 1360 ArrayRef<NamedAttribute> attrs) { 1361 SmallVector<int64_t> staticOffsets, staticSizes, staticStrides; 1362 SmallVector<Value> dynamicOffsets, dynamicSizes, dynamicStrides; 1363 dispatchIndexOpFoldResults(offset, dynamicOffsets, staticOffsets, 1364 ShapedType::kDynamicStrideOrOffset); 1365 dispatchIndexOpFoldResults(sizes, dynamicSizes, staticSizes, 1366 ShapedType::kDynamicSize); 1367 dispatchIndexOpFoldResults(strides, dynamicStrides, staticStrides, 1368 ShapedType::kDynamicStrideOrOffset); 1369 build(b, result, resultType, source, dynamicOffsets, dynamicSizes, 1370 dynamicStrides, b.getI64ArrayAttr(staticOffsets), 1371 b.getI64ArrayAttr(staticSizes), b.getI64ArrayAttr(staticStrides)); 1372 result.addAttributes(attrs); 1373 } 1374 1375 void ReinterpretCastOp::build(OpBuilder &b, OperationState &result, 1376 MemRefType resultType, Value source, 1377 int64_t offset, ArrayRef<int64_t> sizes, 1378 ArrayRef<int64_t> strides, 1379 ArrayRef<NamedAttribute> attrs) { 1380 SmallVector<OpFoldResult> sizeValues = 1381 llvm::to_vector<4>(llvm::map_range(sizes, [&](int64_t v) -> OpFoldResult { 1382 return b.getI64IntegerAttr(v); 1383 })); 1384 SmallVector<OpFoldResult> strideValues = llvm::to_vector<4>( 1385 llvm::map_range(strides, [&](int64_t v) -> OpFoldResult { 1386 return b.getI64IntegerAttr(v); 1387 })); 1388 build(b, result, resultType, source, b.getI64IntegerAttr(offset), sizeValues, 1389 strideValues, attrs); 1390 } 1391 1392 void ReinterpretCastOp::build(OpBuilder &b, OperationState &result, 1393 MemRefType resultType, Value source, Value offset, 1394 ValueRange sizes, ValueRange strides, 1395 ArrayRef<NamedAttribute> attrs) { 1396 SmallVector<OpFoldResult> sizeValues = llvm::to_vector<4>( 1397 llvm::map_range(sizes, [](Value v) -> OpFoldResult { return v; })); 1398 SmallVector<OpFoldResult> strideValues = llvm::to_vector<4>( 1399 llvm::map_range(strides, [](Value v) -> OpFoldResult { return v; })); 1400 build(b, result, resultType, source, offset, sizeValues, strideValues, attrs); 1401 } 1402 1403 // TODO: ponder whether we want to allow missing trailing sizes/strides that are 1404 // completed automatically, like we have for subview and extract_slice. 1405 static LogicalResult verify(ReinterpretCastOp op) { 1406 // The source and result memrefs should be in the same memory space. 1407 auto srcType = op.source().getType().cast<BaseMemRefType>(); 1408 auto resultType = op.getType().cast<MemRefType>(); 1409 if (srcType.getMemorySpace() != resultType.getMemorySpace()) 1410 return op.emitError("different memory spaces specified for source type ") 1411 << srcType << " and result memref type " << resultType; 1412 if (srcType.getElementType() != resultType.getElementType()) 1413 return op.emitError("different element types specified for source type ") 1414 << srcType << " and result memref type " << resultType; 1415 1416 // Match sizes in result memref type and in static_sizes attribute. 1417 for (auto &en : 1418 llvm::enumerate(llvm::zip(resultType.getShape(), 1419 extractFromI64ArrayAttr(op.static_sizes())))) { 1420 int64_t resultSize = std::get<0>(en.value()); 1421 int64_t expectedSize = std::get<1>(en.value()); 1422 if (resultSize != expectedSize) 1423 return op.emitError("expected result type with size = ") 1424 << expectedSize << " instead of " << resultSize 1425 << " in dim = " << en.index(); 1426 } 1427 1428 // Match offset and strides in static_offset and static_strides attributes if 1429 // result memref type has an affine map specified. 1430 if (!resultType.getAffineMaps().empty()) { 1431 int64_t resultOffset; 1432 SmallVector<int64_t, 4> resultStrides; 1433 if (failed(getStridesAndOffset(resultType, resultStrides, resultOffset))) 1434 return failure(); 1435 1436 // Match offset in result memref type and in static_offsets attribute. 1437 int64_t expectedOffset = 1438 extractFromI64ArrayAttr(op.static_offsets()).front(); 1439 if (resultOffset != expectedOffset) 1440 return op.emitError("expected result type with offset = ") 1441 << resultOffset << " instead of " << expectedOffset; 1442 1443 // Match strides in result memref type and in static_strides attribute. 1444 for (auto &en : llvm::enumerate(llvm::zip( 1445 resultStrides, extractFromI64ArrayAttr(op.static_strides())))) { 1446 int64_t resultStride = std::get<0>(en.value()); 1447 int64_t expectedStride = std::get<1>(en.value()); 1448 if (resultStride != expectedStride) 1449 return op.emitError("expected result type with stride = ") 1450 << expectedStride << " instead of " << resultStride 1451 << " in dim = " << en.index(); 1452 } 1453 } 1454 return success(); 1455 } 1456 1457 //===----------------------------------------------------------------------===// 1458 // Reassociative reshape ops 1459 //===----------------------------------------------------------------------===// 1460 1461 SmallVector<AffineMap, 4> CollapseShapeOp::getReassociationMaps() { 1462 return getSymbolLessAffineMaps(getReassociationExprs()); 1463 } 1464 SmallVector<ReassociationExprs, 4> CollapseShapeOp::getReassociationExprs() { 1465 return convertReassociationIndicesToExprs(getContext(), 1466 getReassociationIndices()); 1467 } 1468 1469 SmallVector<AffineMap, 4> ExpandShapeOp::getReassociationMaps() { 1470 return getSymbolLessAffineMaps(getReassociationExprs()); 1471 } 1472 SmallVector<ReassociationExprs, 4> ExpandShapeOp::getReassociationExprs() { 1473 return convertReassociationIndicesToExprs(getContext(), 1474 getReassociationIndices()); 1475 } 1476 1477 static void print(OpAsmPrinter &p, ExpandShapeOp op) { 1478 ::mlir::printReshapeOp<ExpandShapeOp>(p, op); 1479 } 1480 1481 static void print(OpAsmPrinter &p, CollapseShapeOp op) { 1482 ::mlir::printReshapeOp<CollapseShapeOp>(p, op); 1483 } 1484 1485 /// Detect whether memref dims [dim, dim + extent) can be reshaped without 1486 /// copies. 1487 static bool isReshapableDimBand(unsigned dim, unsigned extent, 1488 ArrayRef<int64_t> sizes, 1489 ArrayRef<AffineExpr> strides) { 1490 assert(sizes.size() == strides.size() && "mismatched ranks"); 1491 // off by 1 indexing to avoid out of bounds 1492 // V 1493 for (auto idx = dim, e = dim + extent; idx + 1 < e; ++idx) { 1494 // Only bands of static shapes are reshapable. This is due to the fact that 1495 // there is no relation between dynamic sizes and dynamic strides: we do not 1496 // have enough information to know whether a "-1" size corresponds to the 1497 // proper symbol in the AffineExpr of a stride. 1498 if (ShapedType::isDynamic(sizes[dim + 1])) 1499 return false; 1500 // TODO: Refine this by passing the proper nDims and nSymbols so we can 1501 // simplify on the fly and catch more reshapable cases. 1502 if (strides[idx] != strides[idx + 1] * sizes[idx + 1]) 1503 return false; 1504 } 1505 return true; 1506 } 1507 1508 /// Compute the MemRefType obtained by applying the `reassociation` (which is 1509 /// expected to be valid) to `type`. 1510 /// If `type` is Contiguous MemRefType, this always produce a contiguous 1511 /// MemRefType. 1512 static MemRefType 1513 computeReshapeCollapsedType(MemRefType type, 1514 ArrayRef<AffineMap> reassociation) { 1515 auto sizes = type.getShape(); 1516 AffineExpr offset; 1517 SmallVector<AffineExpr, 4> strides; 1518 auto status = getStridesAndOffset(type, strides, offset); 1519 (void)status; 1520 assert(succeeded(status) && "expected strided memref"); 1521 1522 SmallVector<int64_t, 4> newSizes; 1523 newSizes.reserve(reassociation.size()); 1524 SmallVector<AffineExpr, 4> newStrides; 1525 newStrides.reserve(reassociation.size()); 1526 1527 // Use the fact that reassociation is valid to simplify the logic: only use 1528 // each map's rank. 1529 assert(isReassociationValid(reassociation) && "invalid reassociation"); 1530 unsigned currentDim = 0; 1531 for (AffineMap m : reassociation) { 1532 unsigned dim = m.getNumResults(); 1533 int64_t size = 1; 1534 AffineExpr stride = strides[currentDim + dim - 1]; 1535 if (!isReshapableDimBand(currentDim, dim, sizes, strides)) { 1536 size = ShapedType::kDynamicSize; 1537 stride = AffineExpr(); 1538 } else { 1539 for (unsigned d = 0; d < dim; ++d) 1540 size *= sizes[currentDim + d]; 1541 } 1542 newSizes.push_back(size); 1543 newStrides.push_back(stride); 1544 currentDim += dim; 1545 } 1546 1547 // Early-exit: if `type` is contiguous, the result must be contiguous. 1548 if (canonicalizeStridedLayout(type).getAffineMaps().empty()) 1549 return MemRefType::Builder(type).setShape(newSizes).setAffineMaps({}); 1550 1551 // Convert back to int64_t because we don't have enough information to create 1552 // new strided layouts from AffineExpr only. This corresponds to a case where 1553 // copies may be necessary. 1554 int64_t intOffset = ShapedType::kDynamicStrideOrOffset; 1555 if (auto o = offset.dyn_cast<AffineConstantExpr>()) 1556 intOffset = o.getValue(); 1557 SmallVector<int64_t, 4> intStrides; 1558 intStrides.reserve(strides.size()); 1559 for (auto stride : newStrides) { 1560 if (auto cst = stride.dyn_cast_or_null<AffineConstantExpr>()) 1561 intStrides.push_back(cst.getValue()); 1562 else 1563 intStrides.push_back(ShapedType::kDynamicStrideOrOffset); 1564 } 1565 auto layout = 1566 makeStridedLinearLayoutMap(intStrides, intOffset, type.getContext()); 1567 return canonicalizeStridedLayout( 1568 MemRefType::Builder(type).setShape(newSizes).setAffineMaps({layout})); 1569 } 1570 1571 void ExpandShapeOp::build(OpBuilder &b, OperationState &result, Value src, 1572 ArrayRef<ReassociationIndices> reassociation, 1573 ArrayRef<NamedAttribute> attrs) { 1574 auto memRefType = src.getType().cast<MemRefType>(); 1575 auto resultType = computeReshapeCollapsedType( 1576 memRefType, getSymbolLessAffineMaps(convertReassociationIndicesToExprs( 1577 b.getContext(), reassociation))); 1578 build(b, result, resultType, src, attrs); 1579 result.addAttribute(getReassociationAttrName(), 1580 getReassociationIndicesAttribute(b, reassociation)); 1581 } 1582 1583 void CollapseShapeOp::build(OpBuilder &b, OperationState &result, Value src, 1584 ArrayRef<ReassociationIndices> reassociation, 1585 ArrayRef<NamedAttribute> attrs) { 1586 auto memRefType = src.getType().cast<MemRefType>(); 1587 auto resultType = computeReshapeCollapsedType( 1588 memRefType, getSymbolLessAffineMaps(convertReassociationIndicesToExprs( 1589 b.getContext(), reassociation))); 1590 build(b, result, resultType, src, attrs); 1591 result.addAttribute(getReassociationAttrName(), 1592 getReassociationIndicesAttribute(b, reassociation)); 1593 } 1594 1595 template <typename ReshapeOp, 1596 bool isExpansion = std::is_same<ReshapeOp, ExpandShapeOp>::value> 1597 static LogicalResult verifyReshapeOp(ReshapeOp op, MemRefType expandedType, 1598 MemRefType collapsedType) { 1599 if (failed( 1600 verifyReshapeLikeTypes(op, expandedType, collapsedType, isExpansion))) 1601 return failure(); 1602 auto maps = op.getReassociationMaps(); 1603 MemRefType expectedType = computeReshapeCollapsedType(expandedType, maps); 1604 if (collapsedType != expectedType) 1605 return op.emitOpError("expected collapsed type to be ") 1606 << expectedType << ", but got " << collapsedType; 1607 return success(); 1608 } 1609 1610 static LogicalResult verify(ExpandShapeOp op) { 1611 return verifyReshapeOp(op, op.getResultType(), op.getSrcType()); 1612 } 1613 1614 void ExpandShapeOp::getCanonicalizationPatterns(RewritePatternSet &results, 1615 MLIRContext *context) { 1616 results.add<CollapseReshapeOps<ExpandShapeOp>, 1617 CollapseMixedReshapeOps<ExpandShapeOp, CollapseShapeOp>>(context); 1618 } 1619 1620 static LogicalResult verify(CollapseShapeOp op) { 1621 return verifyReshapeOp(op, op.getSrcType(), op.getResultType()); 1622 } 1623 1624 struct CollapseShapeOpMemRefCastFolder 1625 : public OpRewritePattern<CollapseShapeOp> { 1626 public: 1627 using OpRewritePattern<CollapseShapeOp>::OpRewritePattern; 1628 1629 LogicalResult matchAndRewrite(CollapseShapeOp op, 1630 PatternRewriter &rewriter) const override { 1631 auto cast = op.getOperand().getDefiningOp<CastOp>(); 1632 if (!cast) 1633 return failure(); 1634 1635 if (!CastOp::canFoldIntoConsumerOp(cast)) 1636 return failure(); 1637 1638 Type newResultType = computeReshapeCollapsedType( 1639 cast.getOperand().getType().cast<MemRefType>(), 1640 op.getReassociationMaps()); 1641 1642 if (newResultType == op.getResultType()) { 1643 rewriter.updateRootInPlace( 1644 op, [&]() { op.srcMutable().assign(cast.source()); }); 1645 } else { 1646 Value newOp = rewriter.create<CollapseShapeOp>( 1647 op->getLoc(), cast.source(), op.getReassociationIndices()); 1648 rewriter.replaceOpWithNewOp<CastOp>(op, op.getType(), newOp); 1649 } 1650 return success(); 1651 } 1652 }; 1653 1654 void CollapseShapeOp::getCanonicalizationPatterns(RewritePatternSet &results, 1655 MLIRContext *context) { 1656 results.add<CollapseReshapeOps<CollapseShapeOp>, 1657 CollapseMixedReshapeOps<CollapseShapeOp, ExpandShapeOp>, 1658 CollapseShapeOpMemRefCastFolder>(context); 1659 } 1660 OpFoldResult ExpandShapeOp::fold(ArrayRef<Attribute> operands) { 1661 if (succeeded(foldMemRefCast(*this))) 1662 return getResult(); 1663 return foldReshapeOp<ExpandShapeOp, CollapseShapeOp>(*this, operands); 1664 } 1665 OpFoldResult CollapseShapeOp::fold(ArrayRef<Attribute> operands) { 1666 return foldReshapeOp<CollapseShapeOp, ExpandShapeOp>(*this, operands); 1667 } 1668 1669 //===----------------------------------------------------------------------===// 1670 // ReshapeOp 1671 //===----------------------------------------------------------------------===// 1672 1673 static LogicalResult verify(ReshapeOp op) { 1674 Type operandType = op.source().getType(); 1675 Type resultType = op.result().getType(); 1676 1677 Type operandElementType = operandType.cast<ShapedType>().getElementType(); 1678 Type resultElementType = resultType.cast<ShapedType>().getElementType(); 1679 if (operandElementType != resultElementType) 1680 return op.emitOpError("element types of source and destination memref " 1681 "types should be the same"); 1682 1683 if (auto operandMemRefType = operandType.dyn_cast<MemRefType>()) 1684 if (!operandMemRefType.getAffineMaps().empty()) 1685 return op.emitOpError( 1686 "source memref type should have identity affine map"); 1687 1688 int64_t shapeSize = op.shape().getType().cast<MemRefType>().getDimSize(0); 1689 auto resultMemRefType = resultType.dyn_cast<MemRefType>(); 1690 if (resultMemRefType) { 1691 if (!resultMemRefType.getAffineMaps().empty()) 1692 return op.emitOpError( 1693 "result memref type should have identity affine map"); 1694 if (shapeSize == ShapedType::kDynamicSize) 1695 return op.emitOpError("cannot use shape operand with dynamic length to " 1696 "reshape to statically-ranked memref type"); 1697 if (shapeSize != resultMemRefType.getRank()) 1698 return op.emitOpError( 1699 "length of shape operand differs from the result's memref rank"); 1700 } 1701 return success(); 1702 } 1703 1704 //===----------------------------------------------------------------------===// 1705 // StoreOp 1706 //===----------------------------------------------------------------------===// 1707 1708 static LogicalResult verify(StoreOp op) { 1709 if (op.getNumOperands() != 2 + op.getMemRefType().getRank()) 1710 return op.emitOpError("store index operand count not equal to memref rank"); 1711 1712 return success(); 1713 } 1714 1715 LogicalResult StoreOp::fold(ArrayRef<Attribute> cstOperands, 1716 SmallVectorImpl<OpFoldResult> &results) { 1717 /// store(memrefcast) -> store 1718 return foldMemRefCast(*this, getValueToStore()); 1719 } 1720 1721 //===----------------------------------------------------------------------===// 1722 // SubViewOp 1723 //===----------------------------------------------------------------------===// 1724 1725 namespace { 1726 /// Helpers to write more idiomatic operations. 1727 namespace saturated_arith { 1728 struct Wrapper { 1729 explicit Wrapper(int64_t v) : v(v) {} 1730 operator int64_t() { return v; } 1731 int64_t v; 1732 }; 1733 Wrapper operator+(Wrapper a, int64_t b) { 1734 if (ShapedType::isDynamicStrideOrOffset(a) || 1735 ShapedType::isDynamicStrideOrOffset(b)) 1736 return Wrapper(ShapedType::kDynamicStrideOrOffset); 1737 return Wrapper(a.v + b); 1738 } 1739 Wrapper operator*(Wrapper a, int64_t b) { 1740 if (ShapedType::isDynamicStrideOrOffset(a) || 1741 ShapedType::isDynamicStrideOrOffset(b)) 1742 return Wrapper(ShapedType::kDynamicStrideOrOffset); 1743 return Wrapper(a.v * b); 1744 } 1745 } // end namespace saturated_arith 1746 } // end namespace 1747 1748 /// A subview result type can be fully inferred from the source type and the 1749 /// static representation of offsets, sizes and strides. Special sentinels 1750 /// encode the dynamic case. 1751 Type SubViewOp::inferResultType(MemRefType sourceMemRefType, 1752 ArrayRef<int64_t> leadingStaticOffsets, 1753 ArrayRef<int64_t> leadingStaticSizes, 1754 ArrayRef<int64_t> leadingStaticStrides) { 1755 // A subview may specify only a leading subset of offset/sizes/strides in 1756 // which case we complete with offset=0, sizes from memref type and strides=1. 1757 unsigned rank = sourceMemRefType.getRank(); 1758 assert(leadingStaticOffsets.size() <= rank && 1759 "unexpected leadingStaticOffsets overflow"); 1760 assert(leadingStaticSizes.size() <= rank && 1761 "unexpected leadingStaticSizes overflow"); 1762 assert(leadingStaticStrides.size() <= rank && 1763 "unexpected leadingStaticStrides overflow"); 1764 auto staticOffsets = llvm::to_vector<4>(leadingStaticOffsets); 1765 auto staticSizes = llvm::to_vector<4>(leadingStaticSizes); 1766 auto staticStrides = llvm::to_vector<4>(leadingStaticStrides); 1767 unsigned numTrailingOffsets = rank - staticOffsets.size(); 1768 unsigned numTrailingSizes = rank - staticSizes.size(); 1769 unsigned numTrailingStrides = rank - staticStrides.size(); 1770 staticOffsets.append(numTrailingOffsets, 0); 1771 llvm::append_range(staticSizes, 1772 sourceMemRefType.getShape().take_back(numTrailingSizes)); 1773 staticStrides.append(numTrailingStrides, 1); 1774 1775 // Extract source offset and strides. 1776 int64_t sourceOffset; 1777 SmallVector<int64_t, 4> sourceStrides; 1778 auto res = getStridesAndOffset(sourceMemRefType, sourceStrides, sourceOffset); 1779 assert(succeeded(res) && "SubViewOp expected strided memref type"); 1780 (void)res; 1781 1782 // Compute target offset whose value is: 1783 // `sourceOffset + sum_i(staticOffset_i * sourceStrides_i)`. 1784 int64_t targetOffset = sourceOffset; 1785 for (auto it : llvm::zip(staticOffsets, sourceStrides)) { 1786 auto staticOffset = std::get<0>(it), targetStride = std::get<1>(it); 1787 using namespace saturated_arith; 1788 targetOffset = Wrapper(targetOffset) + Wrapper(staticOffset) * targetStride; 1789 } 1790 1791 // Compute target stride whose value is: 1792 // `sourceStrides_i * staticStrides_i`. 1793 SmallVector<int64_t, 4> targetStrides; 1794 targetStrides.reserve(staticOffsets.size()); 1795 for (auto it : llvm::zip(sourceStrides, staticStrides)) { 1796 auto sourceStride = std::get<0>(it), staticStride = std::get<1>(it); 1797 using namespace saturated_arith; 1798 targetStrides.push_back(Wrapper(sourceStride) * staticStride); 1799 } 1800 1801 // The type is now known. 1802 return MemRefType::get( 1803 staticSizes, sourceMemRefType.getElementType(), 1804 makeStridedLinearLayoutMap(targetStrides, targetOffset, 1805 sourceMemRefType.getContext()), 1806 sourceMemRefType.getMemorySpace()); 1807 } 1808 1809 Type SubViewOp::inferResultType(MemRefType sourceMemRefType, 1810 ArrayRef<OpFoldResult> leadingStaticOffsets, 1811 ArrayRef<OpFoldResult> leadingStaticSizes, 1812 ArrayRef<OpFoldResult> leadingStaticStrides) { 1813 SmallVector<int64_t> staticOffsets, staticSizes, staticStrides; 1814 SmallVector<Value> dynamicOffsets, dynamicSizes, dynamicStrides; 1815 dispatchIndexOpFoldResults(leadingStaticOffsets, dynamicOffsets, 1816 staticOffsets, ShapedType::kDynamicStrideOrOffset); 1817 dispatchIndexOpFoldResults(leadingStaticSizes, dynamicSizes, staticSizes, 1818 ShapedType::kDynamicSize); 1819 dispatchIndexOpFoldResults(leadingStaticStrides, dynamicStrides, 1820 staticStrides, ShapedType::kDynamicStrideOrOffset); 1821 return SubViewOp::inferResultType(sourceMemRefType, staticOffsets, 1822 staticSizes, staticStrides) 1823 .cast<MemRefType>(); 1824 } 1825 1826 Type SubViewOp::inferRankReducedResultType( 1827 unsigned resultRank, MemRefType sourceRankedTensorType, 1828 ArrayRef<int64_t> leadingStaticOffsets, 1829 ArrayRef<int64_t> leadingStaticSizes, 1830 ArrayRef<int64_t> leadingStaticStrides) { 1831 auto inferredType = 1832 inferResultType(sourceRankedTensorType, leadingStaticOffsets, 1833 leadingStaticSizes, leadingStaticStrides) 1834 .cast<MemRefType>(); 1835 assert(inferredType.getRank() >= resultRank && "expected "); 1836 int rankDiff = inferredType.getRank() - resultRank; 1837 if (rankDiff > 0) { 1838 auto shape = inferredType.getShape(); 1839 llvm::SmallDenseSet<unsigned> dimsToProject; 1840 mlir::getPositionsOfShapeOne(rankDiff, shape, dimsToProject); 1841 SmallVector<int64_t> projectedShape; 1842 for (unsigned pos = 0, e = shape.size(); pos < e; ++pos) 1843 if (!dimsToProject.contains(pos)) 1844 projectedShape.push_back(shape[pos]); 1845 1846 AffineMap map; 1847 auto maps = inferredType.getAffineMaps(); 1848 if (!maps.empty() && maps.front()) 1849 map = getProjectedMap(maps.front(), dimsToProject); 1850 inferredType = 1851 MemRefType::get(projectedShape, inferredType.getElementType(), map, 1852 inferredType.getMemorySpace()); 1853 } 1854 return inferredType; 1855 } 1856 1857 Type SubViewOp::inferRankReducedResultType( 1858 unsigned resultRank, MemRefType sourceRankedTensorType, 1859 ArrayRef<OpFoldResult> leadingStaticOffsets, 1860 ArrayRef<OpFoldResult> leadingStaticSizes, 1861 ArrayRef<OpFoldResult> leadingStaticStrides) { 1862 SmallVector<int64_t> staticOffsets, staticSizes, staticStrides; 1863 SmallVector<Value> dynamicOffsets, dynamicSizes, dynamicStrides; 1864 dispatchIndexOpFoldResults(leadingStaticOffsets, dynamicOffsets, 1865 staticOffsets, ShapedType::kDynamicStrideOrOffset); 1866 dispatchIndexOpFoldResults(leadingStaticSizes, dynamicSizes, staticSizes, 1867 ShapedType::kDynamicSize); 1868 dispatchIndexOpFoldResults(leadingStaticStrides, dynamicStrides, 1869 staticStrides, ShapedType::kDynamicStrideOrOffset); 1870 return SubViewOp::inferRankReducedResultType( 1871 resultRank, sourceRankedTensorType, staticOffsets, staticSizes, 1872 staticStrides); 1873 } 1874 // Build a SubViewOp with mixed static and dynamic entries and custom result 1875 // type. If the type passed is nullptr, it is inferred. 1876 void SubViewOp::build(OpBuilder &b, OperationState &result, 1877 MemRefType resultType, Value source, 1878 ArrayRef<OpFoldResult> offsets, 1879 ArrayRef<OpFoldResult> sizes, 1880 ArrayRef<OpFoldResult> strides, 1881 ArrayRef<NamedAttribute> attrs) { 1882 SmallVector<int64_t> staticOffsets, staticSizes, staticStrides; 1883 SmallVector<Value> dynamicOffsets, dynamicSizes, dynamicStrides; 1884 dispatchIndexOpFoldResults(offsets, dynamicOffsets, staticOffsets, 1885 ShapedType::kDynamicStrideOrOffset); 1886 dispatchIndexOpFoldResults(sizes, dynamicSizes, staticSizes, 1887 ShapedType::kDynamicSize); 1888 dispatchIndexOpFoldResults(strides, dynamicStrides, staticStrides, 1889 ShapedType::kDynamicStrideOrOffset); 1890 auto sourceMemRefType = source.getType().cast<MemRefType>(); 1891 // Structuring implementation this way avoids duplication between builders. 1892 if (!resultType) { 1893 resultType = SubViewOp::inferResultType(sourceMemRefType, staticOffsets, 1894 staticSizes, staticStrides) 1895 .cast<MemRefType>(); 1896 } 1897 build(b, result, resultType, source, dynamicOffsets, dynamicSizes, 1898 dynamicStrides, b.getI64ArrayAttr(staticOffsets), 1899 b.getI64ArrayAttr(staticSizes), b.getI64ArrayAttr(staticStrides)); 1900 result.addAttributes(attrs); 1901 } 1902 1903 // Build a SubViewOp with mixed static and dynamic entries and inferred result 1904 // type. 1905 void SubViewOp::build(OpBuilder &b, OperationState &result, Value source, 1906 ArrayRef<OpFoldResult> offsets, 1907 ArrayRef<OpFoldResult> sizes, 1908 ArrayRef<OpFoldResult> strides, 1909 ArrayRef<NamedAttribute> attrs) { 1910 build(b, result, MemRefType(), source, offsets, sizes, strides, attrs); 1911 } 1912 1913 // Build a SubViewOp with static entries and inferred result type. 1914 void SubViewOp::build(OpBuilder &b, OperationState &result, Value source, 1915 ArrayRef<int64_t> offsets, ArrayRef<int64_t> sizes, 1916 ArrayRef<int64_t> strides, 1917 ArrayRef<NamedAttribute> attrs) { 1918 SmallVector<OpFoldResult> offsetValues = llvm::to_vector<4>( 1919 llvm::map_range(offsets, [&](int64_t v) -> OpFoldResult { 1920 return b.getI64IntegerAttr(v); 1921 })); 1922 SmallVector<OpFoldResult> sizeValues = 1923 llvm::to_vector<4>(llvm::map_range(sizes, [&](int64_t v) -> OpFoldResult { 1924 return b.getI64IntegerAttr(v); 1925 })); 1926 SmallVector<OpFoldResult> strideValues = llvm::to_vector<4>( 1927 llvm::map_range(strides, [&](int64_t v) -> OpFoldResult { 1928 return b.getI64IntegerAttr(v); 1929 })); 1930 build(b, result, source, offsetValues, sizeValues, strideValues, attrs); 1931 } 1932 1933 // Build a SubViewOp with dynamic entries and custom result type. If the 1934 // type passed is nullptr, it is inferred. 1935 void SubViewOp::build(OpBuilder &b, OperationState &result, 1936 MemRefType resultType, Value source, 1937 ArrayRef<int64_t> offsets, ArrayRef<int64_t> sizes, 1938 ArrayRef<int64_t> strides, 1939 ArrayRef<NamedAttribute> attrs) { 1940 SmallVector<OpFoldResult> offsetValues = llvm::to_vector<4>( 1941 llvm::map_range(offsets, [&](int64_t v) -> OpFoldResult { 1942 return b.getI64IntegerAttr(v); 1943 })); 1944 SmallVector<OpFoldResult> sizeValues = 1945 llvm::to_vector<4>(llvm::map_range(sizes, [&](int64_t v) -> OpFoldResult { 1946 return b.getI64IntegerAttr(v); 1947 })); 1948 SmallVector<OpFoldResult> strideValues = llvm::to_vector<4>( 1949 llvm::map_range(strides, [&](int64_t v) -> OpFoldResult { 1950 return b.getI64IntegerAttr(v); 1951 })); 1952 build(b, result, resultType, source, offsetValues, sizeValues, strideValues, 1953 attrs); 1954 } 1955 1956 // Build a SubViewOp with dynamic entries and custom result type. If the type 1957 // passed is nullptr, it is inferred. 1958 void SubViewOp::build(OpBuilder &b, OperationState &result, 1959 MemRefType resultType, Value source, ValueRange offsets, 1960 ValueRange sizes, ValueRange strides, 1961 ArrayRef<NamedAttribute> attrs) { 1962 SmallVector<OpFoldResult> offsetValues = llvm::to_vector<4>( 1963 llvm::map_range(offsets, [](Value v) -> OpFoldResult { return v; })); 1964 SmallVector<OpFoldResult> sizeValues = llvm::to_vector<4>( 1965 llvm::map_range(sizes, [](Value v) -> OpFoldResult { return v; })); 1966 SmallVector<OpFoldResult> strideValues = llvm::to_vector<4>( 1967 llvm::map_range(strides, [](Value v) -> OpFoldResult { return v; })); 1968 build(b, result, resultType, source, offsetValues, sizeValues, strideValues); 1969 } 1970 1971 // Build a SubViewOp with dynamic entries and inferred result type. 1972 void SubViewOp::build(OpBuilder &b, OperationState &result, Value source, 1973 ValueRange offsets, ValueRange sizes, ValueRange strides, 1974 ArrayRef<NamedAttribute> attrs) { 1975 build(b, result, MemRefType(), source, offsets, sizes, strides, attrs); 1976 } 1977 1978 /// For ViewLikeOpInterface. 1979 Value SubViewOp::getViewSource() { return source(); } 1980 1981 enum SubViewVerificationResult { 1982 Success, 1983 RankTooLarge, 1984 SizeMismatch, 1985 ElemTypeMismatch, 1986 MemSpaceMismatch, 1987 AffineMapMismatch 1988 }; 1989 1990 /// Checks if `original` Type type can be rank reduced to `reduced` type. 1991 /// This function is slight variant of `is subsequence` algorithm where 1992 /// not matching dimension must be 1. 1993 static SubViewVerificationResult 1994 isRankReducedType(Type originalType, Type candidateReducedType, 1995 ArrayAttr staticSizes, std::string *errMsg = nullptr) { 1996 if (originalType == candidateReducedType) 1997 return SubViewVerificationResult::Success; 1998 if (!originalType.isa<MemRefType>()) 1999 return SubViewVerificationResult::Success; 2000 if (originalType.isa<MemRefType>() && !candidateReducedType.isa<MemRefType>()) 2001 return SubViewVerificationResult::Success; 2002 2003 ShapedType originalShapedType = originalType.cast<ShapedType>(); 2004 ShapedType candidateReducedShapedType = 2005 candidateReducedType.cast<ShapedType>(); 2006 2007 // Rank and size logic is valid for all ShapedTypes. 2008 ArrayRef<int64_t> originalShape = originalShapedType.getShape(); 2009 ArrayRef<int64_t> candidateReducedShape = 2010 candidateReducedShapedType.getShape(); 2011 unsigned originalRank = originalShape.size(), 2012 candidateReducedRank = candidateReducedShape.size(); 2013 if (candidateReducedRank > originalRank) 2014 return SubViewVerificationResult::RankTooLarge; 2015 2016 MemRefType original = originalType.cast<MemRefType>(); 2017 MemRefType candidateReduced = candidateReducedType.cast<MemRefType>(); 2018 2019 auto optionalUnusedDimsMask = 2020 computeMemRefRankReductionMask(original, candidateReduced, staticSizes); 2021 2022 // Sizes cannot be matched in case empty vector is returned. 2023 if (!optionalUnusedDimsMask.hasValue()) 2024 return SubViewVerificationResult::SizeMismatch; 2025 2026 if (originalShapedType.getElementType() != 2027 candidateReducedShapedType.getElementType()) 2028 return SubViewVerificationResult::ElemTypeMismatch; 2029 2030 // Strided layout logic is relevant for MemRefType only. 2031 if (original.getMemorySpace() != candidateReduced.getMemorySpace()) 2032 return SubViewVerificationResult::MemSpaceMismatch; 2033 return SubViewVerificationResult::Success; 2034 } 2035 2036 template <typename OpTy> 2037 static LogicalResult produceSubViewErrorMsg(SubViewVerificationResult result, 2038 OpTy op, Type expectedType, 2039 StringRef errMsg = "") { 2040 auto memrefType = expectedType.cast<ShapedType>(); 2041 switch (result) { 2042 case SubViewVerificationResult::Success: 2043 return success(); 2044 case SubViewVerificationResult::RankTooLarge: 2045 return op.emitError("expected result rank to be smaller or equal to ") 2046 << "the source rank. " << errMsg; 2047 case SubViewVerificationResult::SizeMismatch: 2048 return op.emitError("expected result type to be ") 2049 << expectedType 2050 << " or a rank-reduced version. (mismatch of result sizes) " 2051 << errMsg; 2052 case SubViewVerificationResult::ElemTypeMismatch: 2053 return op.emitError("expected result element type to be ") 2054 << memrefType.getElementType() << errMsg; 2055 case SubViewVerificationResult::MemSpaceMismatch: 2056 return op.emitError("expected result and source memory spaces to match.") 2057 << errMsg; 2058 case SubViewVerificationResult::AffineMapMismatch: 2059 return op.emitError("expected result type to be ") 2060 << expectedType 2061 << " or a rank-reduced version. (mismatch of result affine map) " 2062 << errMsg; 2063 } 2064 llvm_unreachable("unexpected subview verification result"); 2065 } 2066 2067 /// Verifier for SubViewOp. 2068 static LogicalResult verify(SubViewOp op) { 2069 MemRefType baseType = op.getSourceType(); 2070 MemRefType subViewType = op.getType(); 2071 2072 // The base memref and the view memref should be in the same memory space. 2073 if (baseType.getMemorySpace() != subViewType.getMemorySpace()) 2074 return op.emitError("different memory spaces specified for base memref " 2075 "type ") 2076 << baseType << " and subview memref type " << subViewType; 2077 2078 // Verify that the base memref type has a strided layout map. 2079 if (!isStrided(baseType)) 2080 return op.emitError("base type ") << baseType << " is not strided"; 2081 2082 // Verify result type against inferred type. 2083 auto expectedType = SubViewOp::inferResultType( 2084 baseType, extractFromI64ArrayAttr(op.static_offsets()), 2085 extractFromI64ArrayAttr(op.static_sizes()), 2086 extractFromI64ArrayAttr(op.static_strides())); 2087 2088 std::string errMsg; 2089 auto result = 2090 isRankReducedType(expectedType, subViewType, op.static_sizes(), &errMsg); 2091 return produceSubViewErrorMsg(result, op, expectedType, errMsg); 2092 } 2093 2094 raw_ostream &mlir::operator<<(raw_ostream &os, Range &range) { 2095 return os << "range " << range.offset << ":" << range.size << ":" 2096 << range.stride; 2097 } 2098 2099 /// Return the list of Range (i.e. offset, size, stride). Each Range 2100 /// entry contains either the dynamic value or a ConstantIndexOp constructed 2101 /// with `b` at location `loc`. 2102 SmallVector<Range, 8> mlir::getOrCreateRanges(OffsetSizeAndStrideOpInterface op, 2103 OpBuilder &b, Location loc) { 2104 std::array<unsigned, 3> ranks = op.getArrayAttrMaxRanks(); 2105 assert(ranks[0] == ranks[1] && "expected offset and sizes of equal ranks"); 2106 assert(ranks[1] == ranks[2] && "expected sizes and strides of equal ranks"); 2107 SmallVector<Range, 8> res; 2108 unsigned rank = ranks[0]; 2109 res.reserve(rank); 2110 for (unsigned idx = 0; idx < rank; ++idx) { 2111 Value offset = 2112 op.isDynamicOffset(idx) 2113 ? op.getDynamicOffset(idx) 2114 : b.create<ConstantIndexOp>(loc, op.getStaticOffset(idx)); 2115 Value size = op.isDynamicSize(idx) 2116 ? op.getDynamicSize(idx) 2117 : b.create<ConstantIndexOp>(loc, op.getStaticSize(idx)); 2118 Value stride = 2119 op.isDynamicStride(idx) 2120 ? op.getDynamicStride(idx) 2121 : b.create<ConstantIndexOp>(loc, op.getStaticStride(idx)); 2122 res.emplace_back(Range{offset, size, stride}); 2123 } 2124 return res; 2125 } 2126 2127 /// Infer the canonical type of the result of a subview operation. Returns a 2128 /// type with rank `resultRank` that is either the rank of the rank-reduced 2129 /// type, or the non-rank-reduced type. 2130 static MemRefType 2131 getCanonicalSubViewResultType(unsigned resultRank, MemRefType sourceType, 2132 ArrayRef<OpFoldResult> mixedOffsets, 2133 ArrayRef<OpFoldResult> mixedSizes, 2134 ArrayRef<OpFoldResult> mixedStrides) { 2135 auto resultType = 2136 SubViewOp::inferRankReducedResultType( 2137 resultRank, sourceType, mixedOffsets, mixedSizes, mixedStrides) 2138 .cast<MemRefType>(); 2139 if (resultType.getRank() != resultRank) { 2140 resultType = SubViewOp::inferResultType(sourceType, mixedOffsets, 2141 mixedSizes, mixedStrides) 2142 .cast<MemRefType>(); 2143 } 2144 return resultType; 2145 } 2146 2147 namespace { 2148 /// Pattern to rewrite a subview op with MemRefCast arguments. 2149 /// This essentially pushes memref.cast past its consuming subview when 2150 /// `canFoldIntoConsumerOp` is true. 2151 /// 2152 /// Example: 2153 /// ``` 2154 /// %0 = memref.cast %V : memref<16x16xf32> to memref<?x?xf32> 2155 /// %1 = memref.subview %0[0, 0][3, 4][1, 1] : 2156 /// memref<?x?xf32> to memref<3x4xf32, offset:?, strides:[?, 1]> 2157 /// ``` 2158 /// is rewritten into: 2159 /// ``` 2160 /// %0 = memref.subview %V: memref<16x16xf32> to memref<3x4xf32, #[[map0]]> 2161 /// %1 = memref.cast %0: memref<3x4xf32, offset:0, strides:[16, 1]> to 2162 /// memref<3x4xf32, offset:?, strides:[?, 1]> 2163 /// ``` 2164 class SubViewOpMemRefCastFolder final : public OpRewritePattern<SubViewOp> { 2165 public: 2166 using OpRewritePattern<SubViewOp>::OpRewritePattern; 2167 2168 LogicalResult matchAndRewrite(SubViewOp subViewOp, 2169 PatternRewriter &rewriter) const override { 2170 // Any constant operand, just return to let SubViewOpConstantFolder kick in. 2171 if (llvm::any_of(subViewOp.getOperands(), [](Value operand) { 2172 return matchPattern(operand, matchConstantIndex()); 2173 })) 2174 return failure(); 2175 2176 auto castOp = subViewOp.source().getDefiningOp<CastOp>(); 2177 if (!castOp) 2178 return failure(); 2179 2180 if (!CastOp::canFoldIntoConsumerOp(castOp)) 2181 return failure(); 2182 2183 /// Deduce the resultType of the SubViewOp using `inferSubViewResultType` on 2184 /// the cast source operand type and the SubViewOp static information. This 2185 /// is the resulting type if the MemRefCastOp were folded. 2186 auto resultType = getCanonicalSubViewResultType( 2187 subViewOp.getType().getRank(), 2188 castOp.source().getType().cast<MemRefType>(), 2189 subViewOp.getMixedOffsets(), subViewOp.getMixedSizes(), 2190 subViewOp.getMixedStrides()); 2191 Value newSubView = rewriter.create<SubViewOp>( 2192 subViewOp.getLoc(), resultType, castOp.source(), subViewOp.offsets(), 2193 subViewOp.sizes(), subViewOp.strides(), subViewOp.static_offsets(), 2194 subViewOp.static_sizes(), subViewOp.static_strides()); 2195 rewriter.replaceOpWithNewOp<CastOp>(subViewOp, subViewOp.getType(), 2196 newSubView); 2197 return success(); 2198 } 2199 }; 2200 } // namespace 2201 2202 /// Return the canonical type of the result of a subview. 2203 struct SubViewReturnTypeCanonicalizer { 2204 MemRefType operator()(SubViewOp op, ArrayRef<OpFoldResult> mixedOffsets, 2205 ArrayRef<OpFoldResult> mixedSizes, 2206 ArrayRef<OpFoldResult> mixedStrides) { 2207 return getCanonicalSubViewResultType(op.getType().getRank(), 2208 op.getSourceType(), mixedOffsets, 2209 mixedSizes, mixedStrides); 2210 } 2211 }; 2212 2213 /// A canonicalizer wrapper to replace SubViewOps. 2214 struct SubViewCanonicalizer { 2215 void operator()(PatternRewriter &rewriter, SubViewOp op, SubViewOp newOp) { 2216 rewriter.replaceOpWithNewOp<CastOp>(op, newOp, op.getType()); 2217 } 2218 }; 2219 2220 void SubViewOp::getCanonicalizationPatterns(RewritePatternSet &results, 2221 MLIRContext *context) { 2222 results 2223 .add<OpWithOffsetSizesAndStridesConstantArgumentFolder< 2224 SubViewOp, SubViewReturnTypeCanonicalizer, SubViewCanonicalizer>, 2225 SubViewOpMemRefCastFolder>(context); 2226 } 2227 2228 OpFoldResult SubViewOp::fold(ArrayRef<Attribute> operands) { 2229 auto resultShapedType = getResult().getType().cast<ShapedType>(); 2230 auto sourceShapedType = source().getType().cast<ShapedType>(); 2231 2232 if (resultShapedType.hasStaticShape() && 2233 resultShapedType == sourceShapedType) { 2234 return getViewSource(); 2235 } 2236 2237 return {}; 2238 } 2239 2240 //===----------------------------------------------------------------------===// 2241 // TensorLoadOp 2242 //===----------------------------------------------------------------------===// 2243 2244 OpFoldResult TensorLoadOp::fold(ArrayRef<Attribute>) { 2245 if (auto bufferCast = memref().getDefiningOp<BufferCastOp>()) 2246 // Approximate alias analysis by conservatively folding only when no there 2247 // is no interleaved operation. 2248 if (bufferCast->getBlock() == this->getOperation()->getBlock() && 2249 bufferCast->getNextNode() == this->getOperation()) 2250 return bufferCast.tensor(); 2251 return {}; 2252 } 2253 2254 namespace { 2255 struct DimOfTensorLoadFolder : public OpRewritePattern<tensor::DimOp> { 2256 using OpRewritePattern<tensor::DimOp>::OpRewritePattern; 2257 2258 LogicalResult matchAndRewrite(tensor::DimOp dimOp, 2259 PatternRewriter &rewriter) const override { 2260 auto tensorLoadOp = dimOp.source().getDefiningOp<TensorLoadOp>(); 2261 if (!tensorLoadOp) 2262 return failure(); 2263 2264 rewriter.replaceOpWithNewOp<DimOp>(dimOp, tensorLoadOp.memref(), 2265 dimOp.index()); 2266 return success(); 2267 } 2268 }; 2269 } // namespace 2270 2271 void TensorLoadOp::getCanonicalizationPatterns(RewritePatternSet &results, 2272 MLIRContext *context) { 2273 results.add<DimOfTensorLoadFolder>(context); 2274 } 2275 2276 //===----------------------------------------------------------------------===// 2277 // TransposeOp 2278 //===----------------------------------------------------------------------===// 2279 2280 /// Build a strided memref type by applying `permutationMap` tp `memRefType`. 2281 static MemRefType inferTransposeResultType(MemRefType memRefType, 2282 AffineMap permutationMap) { 2283 auto rank = memRefType.getRank(); 2284 auto originalSizes = memRefType.getShape(); 2285 // Compute permuted sizes. 2286 SmallVector<int64_t, 4> sizes(rank, 0); 2287 for (auto en : llvm::enumerate(permutationMap.getResults())) 2288 sizes[en.index()] = 2289 originalSizes[en.value().cast<AffineDimExpr>().getPosition()]; 2290 2291 // Compute permuted strides. 2292 int64_t offset; 2293 SmallVector<int64_t, 4> strides; 2294 auto res = getStridesAndOffset(memRefType, strides, offset); 2295 assert(succeeded(res) && strides.size() == static_cast<unsigned>(rank)); 2296 (void)res; 2297 auto map = 2298 makeStridedLinearLayoutMap(strides, offset, memRefType.getContext()); 2299 map = permutationMap ? map.compose(permutationMap) : map; 2300 return MemRefType::Builder(memRefType).setShape(sizes).setAffineMaps(map); 2301 } 2302 2303 void TransposeOp::build(OpBuilder &b, OperationState &result, Value in, 2304 AffineMapAttr permutation, 2305 ArrayRef<NamedAttribute> attrs) { 2306 auto permutationMap = permutation.getValue(); 2307 assert(permutationMap); 2308 2309 auto memRefType = in.getType().cast<MemRefType>(); 2310 // Compute result type. 2311 MemRefType resultType = inferTransposeResultType(memRefType, permutationMap); 2312 2313 build(b, result, resultType, in, attrs); 2314 result.addAttribute(TransposeOp::getPermutationAttrName(), permutation); 2315 } 2316 2317 // transpose $in $permutation attr-dict : type($in) `to` type(results) 2318 static void print(OpAsmPrinter &p, TransposeOp op) { 2319 p << " " << op.in() << " " << op.permutation(); 2320 p.printOptionalAttrDict(op->getAttrs(), 2321 {TransposeOp::getPermutationAttrName()}); 2322 p << " : " << op.in().getType() << " to " << op.getType(); 2323 } 2324 2325 static ParseResult parseTransposeOp(OpAsmParser &parser, 2326 OperationState &result) { 2327 OpAsmParser::OperandType in; 2328 AffineMap permutation; 2329 MemRefType srcType, dstType; 2330 if (parser.parseOperand(in) || parser.parseAffineMap(permutation) || 2331 parser.parseOptionalAttrDict(result.attributes) || 2332 parser.parseColonType(srcType) || 2333 parser.resolveOperand(in, srcType, result.operands) || 2334 parser.parseKeywordType("to", dstType) || 2335 parser.addTypeToList(dstType, result.types)) 2336 return failure(); 2337 2338 result.addAttribute(TransposeOp::getPermutationAttrName(), 2339 AffineMapAttr::get(permutation)); 2340 return success(); 2341 } 2342 2343 static LogicalResult verify(TransposeOp op) { 2344 if (!op.permutation().isPermutation()) 2345 return op.emitOpError("expected a permutation map"); 2346 if (op.permutation().getNumDims() != op.getShapedType().getRank()) 2347 return op.emitOpError( 2348 "expected a permutation map of same rank as the input"); 2349 2350 auto srcType = op.in().getType().cast<MemRefType>(); 2351 auto dstType = op.getType().cast<MemRefType>(); 2352 auto transposedType = inferTransposeResultType(srcType, op.permutation()); 2353 if (dstType != transposedType) 2354 return op.emitOpError("output type ") 2355 << dstType << " does not match transposed input type " << srcType 2356 << ", " << transposedType; 2357 return success(); 2358 } 2359 2360 OpFoldResult TransposeOp::fold(ArrayRef<Attribute>) { 2361 if (succeeded(foldMemRefCast(*this))) 2362 return getResult(); 2363 return {}; 2364 } 2365 2366 //===----------------------------------------------------------------------===// 2367 // ViewOp 2368 //===----------------------------------------------------------------------===// 2369 2370 static ParseResult parseViewOp(OpAsmParser &parser, OperationState &result) { 2371 OpAsmParser::OperandType srcInfo; 2372 SmallVector<OpAsmParser::OperandType, 1> offsetInfo; 2373 SmallVector<OpAsmParser::OperandType, 4> sizesInfo; 2374 auto indexType = parser.getBuilder().getIndexType(); 2375 Type srcType, dstType; 2376 llvm::SMLoc offsetLoc; 2377 if (parser.parseOperand(srcInfo) || parser.getCurrentLocation(&offsetLoc) || 2378 parser.parseOperandList(offsetInfo, OpAsmParser::Delimiter::Square)) 2379 return failure(); 2380 2381 if (offsetInfo.size() != 1) 2382 return parser.emitError(offsetLoc) << "expects 1 offset operand"; 2383 2384 return failure( 2385 parser.parseOperandList(sizesInfo, OpAsmParser::Delimiter::Square) || 2386 parser.parseOptionalAttrDict(result.attributes) || 2387 parser.parseColonType(srcType) || 2388 parser.resolveOperand(srcInfo, srcType, result.operands) || 2389 parser.resolveOperands(offsetInfo, indexType, result.operands) || 2390 parser.resolveOperands(sizesInfo, indexType, result.operands) || 2391 parser.parseKeywordType("to", dstType) || 2392 parser.addTypeToList(dstType, result.types)); 2393 } 2394 2395 static void print(OpAsmPrinter &p, ViewOp op) { 2396 p << ' ' << op.getOperand(0) << '['; 2397 p.printOperand(op.byte_shift()); 2398 p << "][" << op.sizes() << ']'; 2399 p.printOptionalAttrDict(op->getAttrs()); 2400 p << " : " << op.getOperand(0).getType() << " to " << op.getType(); 2401 } 2402 2403 static LogicalResult verify(ViewOp op) { 2404 auto baseType = op.getOperand(0).getType().cast<MemRefType>(); 2405 auto viewType = op.getType(); 2406 2407 // The base memref should have identity layout map (or none). 2408 if (baseType.getAffineMaps().size() > 1 || 2409 (baseType.getAffineMaps().size() == 1 && 2410 !baseType.getAffineMaps()[0].isIdentity())) 2411 return op.emitError("unsupported map for base memref type ") << baseType; 2412 2413 // The result memref should have identity layout map (or none). 2414 if (viewType.getAffineMaps().size() > 1 || 2415 (viewType.getAffineMaps().size() == 1 && 2416 !viewType.getAffineMaps()[0].isIdentity())) 2417 return op.emitError("unsupported map for result memref type ") << viewType; 2418 2419 // The base memref and the view memref should be in the same memory space. 2420 if (baseType.getMemorySpace() != viewType.getMemorySpace()) 2421 return op.emitError("different memory spaces specified for base memref " 2422 "type ") 2423 << baseType << " and view memref type " << viewType; 2424 2425 // Verify that we have the correct number of sizes for the result type. 2426 unsigned numDynamicDims = viewType.getNumDynamicDims(); 2427 if (op.sizes().size() != numDynamicDims) 2428 return op.emitError("incorrect number of size operands for type ") 2429 << viewType; 2430 2431 return success(); 2432 } 2433 2434 Value ViewOp::getViewSource() { return source(); } 2435 2436 namespace { 2437 2438 struct ViewOpShapeFolder : public OpRewritePattern<ViewOp> { 2439 using OpRewritePattern<ViewOp>::OpRewritePattern; 2440 2441 LogicalResult matchAndRewrite(ViewOp viewOp, 2442 PatternRewriter &rewriter) const override { 2443 // Return if none of the operands are constants. 2444 if (llvm::none_of(viewOp.getOperands(), [](Value operand) { 2445 return matchPattern(operand, matchConstantIndex()); 2446 })) 2447 return failure(); 2448 2449 // Get result memref type. 2450 auto memrefType = viewOp.getType(); 2451 2452 // Get offset from old memref view type 'memRefType'. 2453 int64_t oldOffset; 2454 SmallVector<int64_t, 4> oldStrides; 2455 if (failed(getStridesAndOffset(memrefType, oldStrides, oldOffset))) 2456 return failure(); 2457 assert(oldOffset == 0 && "Expected 0 offset"); 2458 2459 SmallVector<Value, 4> newOperands; 2460 2461 // Offset cannot be folded into result type. 2462 2463 // Fold any dynamic dim operands which are produced by a constant. 2464 SmallVector<int64_t, 4> newShapeConstants; 2465 newShapeConstants.reserve(memrefType.getRank()); 2466 2467 unsigned dynamicDimPos = 0; 2468 unsigned rank = memrefType.getRank(); 2469 for (unsigned dim = 0, e = rank; dim < e; ++dim) { 2470 int64_t dimSize = memrefType.getDimSize(dim); 2471 // If this is already static dimension, keep it. 2472 if (!ShapedType::isDynamic(dimSize)) { 2473 newShapeConstants.push_back(dimSize); 2474 continue; 2475 } 2476 auto *defOp = viewOp.sizes()[dynamicDimPos].getDefiningOp(); 2477 if (auto constantIndexOp = dyn_cast_or_null<ConstantIndexOp>(defOp)) { 2478 // Dynamic shape dimension will be folded. 2479 newShapeConstants.push_back(constantIndexOp.getValue()); 2480 } else { 2481 // Dynamic shape dimension not folded; copy operand from old memref. 2482 newShapeConstants.push_back(dimSize); 2483 newOperands.push_back(viewOp.sizes()[dynamicDimPos]); 2484 } 2485 dynamicDimPos++; 2486 } 2487 2488 // Create new memref type with constant folded dims. 2489 MemRefType newMemRefType = 2490 MemRefType::Builder(memrefType).setShape(newShapeConstants); 2491 // Nothing new, don't fold. 2492 if (newMemRefType == memrefType) 2493 return failure(); 2494 2495 // Create new ViewOp. 2496 auto newViewOp = rewriter.create<ViewOp>(viewOp.getLoc(), newMemRefType, 2497 viewOp.getOperand(0), 2498 viewOp.byte_shift(), newOperands); 2499 // Insert a cast so we have the same type as the old memref type. 2500 rewriter.replaceOpWithNewOp<CastOp>(viewOp, newViewOp, viewOp.getType()); 2501 return success(); 2502 } 2503 }; 2504 2505 struct ViewOpMemrefCastFolder : public OpRewritePattern<ViewOp> { 2506 using OpRewritePattern<ViewOp>::OpRewritePattern; 2507 2508 LogicalResult matchAndRewrite(ViewOp viewOp, 2509 PatternRewriter &rewriter) const override { 2510 Value memrefOperand = viewOp.getOperand(0); 2511 CastOp memrefCastOp = memrefOperand.getDefiningOp<CastOp>(); 2512 if (!memrefCastOp) 2513 return failure(); 2514 Value allocOperand = memrefCastOp.getOperand(); 2515 AllocOp allocOp = allocOperand.getDefiningOp<AllocOp>(); 2516 if (!allocOp) 2517 return failure(); 2518 rewriter.replaceOpWithNewOp<ViewOp>(viewOp, viewOp.getType(), allocOperand, 2519 viewOp.byte_shift(), viewOp.sizes()); 2520 return success(); 2521 } 2522 }; 2523 2524 } // end anonymous namespace 2525 2526 void ViewOp::getCanonicalizationPatterns(RewritePatternSet &results, 2527 MLIRContext *context) { 2528 results.add<ViewOpShapeFolder, ViewOpMemrefCastFolder>(context); 2529 } 2530 2531 //===----------------------------------------------------------------------===// 2532 // TableGen'd op method definitions 2533 //===----------------------------------------------------------------------===// 2534 2535 #define GET_OP_CLASSES 2536 #include "mlir/Dialect/MemRef/IR/MemRefOps.cpp.inc" 2537