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