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