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