1 //===- Shape.cpp - MLIR Shape Operations ----------------------------------===// 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/Shape/IR/Shape.h" 10 11 #include "mlir/Dialect/Traits.h" 12 #include "mlir/IR/Builders.h" 13 #include "mlir/IR/DialectImplementation.h" 14 #include "mlir/IR/PatternMatch.h" 15 #include "mlir/IR/StandardTypes.h" 16 #include "llvm/ADT/SmallString.h" 17 #include "llvm/Support/raw_ostream.h" 18 19 using namespace mlir; 20 using namespace mlir::shape; 21 22 namespace { 23 #include "ShapeCanonicalization.inc" 24 } 25 26 ShapeDialect::ShapeDialect(MLIRContext *context) 27 : Dialect(getDialectNamespace(), context) { 28 addOperations< 29 #define GET_OP_LIST 30 #include "mlir/Dialect/Shape/IR/ShapeOps.cpp.inc" 31 >(); 32 addTypes<ComponentType, ElementType, ShapeType, SizeType, ValueShapeType, 33 WitnessType>(); 34 // Allow unknown operations during prototyping and testing. As the dialect is 35 // still evolving it makes it simple to start with an unregistered ops and 36 // try different variants before actually defining the op. 37 allowUnknownOperations(); 38 } 39 40 Operation *ShapeDialect::materializeConstant(OpBuilder &builder, 41 Attribute value, Type type, 42 Location loc) { 43 if (auto shapeType = type.dyn_cast<ShapeType>()) 44 return builder.create<ConstShapeOp>(loc, type, 45 value.cast<DenseIntElementsAttr>()); 46 if (auto sizeType = type.dyn_cast<SizeType>()) 47 return builder.create<ConstSizeOp>(loc, type, value.cast<IntegerAttr>()); 48 if (auto witnessType = type.dyn_cast<WitnessType>()) 49 return builder.create<ConstWitnessOp>(loc, type, value.cast<BoolAttr>()); 50 return nullptr; 51 } 52 53 /// Parse a type registered to this dialect. 54 Type ShapeDialect::parseType(DialectAsmParser &parser) const { 55 StringRef keyword; 56 if (parser.parseKeyword(&keyword)) 57 return Type(); 58 59 if (keyword == "component") 60 return ComponentType::get(getContext()); 61 if (keyword == "element") 62 return ElementType::get(getContext()); 63 if (keyword == "shape") 64 return ShapeType::get(getContext()); 65 if (keyword == "size") 66 return SizeType::get(getContext()); 67 if (keyword == "value_shape") 68 return ValueShapeType::get(getContext()); 69 if (keyword == "witness") 70 return WitnessType::get(getContext()); 71 72 parser.emitError(parser.getNameLoc(), "unknown shape type: ") << keyword; 73 return Type(); 74 } 75 76 /// Print a type registered to this dialect. 77 void ShapeDialect::printType(Type type, DialectAsmPrinter &os) const { 78 switch (type.getKind()) { 79 case ShapeTypes::Component: 80 os << "component"; 81 return; 82 case ShapeTypes::Element: 83 os << "element"; 84 return; 85 case ShapeTypes::Size: 86 os << "size"; 87 return; 88 case ShapeTypes::Shape: 89 os << "shape"; 90 return; 91 case ShapeTypes::ValueShape: 92 os << "value_shape"; 93 return; 94 case ShapeTypes::Witness: 95 os << "witness"; 96 return; 97 default: 98 llvm_unreachable("unexpected 'shape' type kind"); 99 } 100 } 101 102 //===----------------------------------------------------------------------===// 103 // AnyOp 104 //===----------------------------------------------------------------------===// 105 106 // TODO: Canonicalization should be implemented for shapes that can be 107 // determined through mixtures of the known dimensions of the inputs. 108 OpFoldResult AnyOp::fold(ArrayRef<Attribute> operands) { 109 // Only the last operand is checked because AnyOp is commutative. 110 if (operands.back()) 111 return operands.back(); 112 113 return nullptr; 114 } 115 116 //===----------------------------------------------------------------------===// 117 // AssumingOp 118 //===----------------------------------------------------------------------===// 119 120 static ParseResult parseAssumingOp(OpAsmParser &parser, 121 OperationState &result) { 122 result.regions.reserve(1); 123 Region *doRegion = result.addRegion(); 124 125 auto &builder = parser.getBuilder(); 126 OpAsmParser::OperandType cond; 127 if (parser.parseOperand(cond) || 128 parser.resolveOperand(cond, builder.getType<WitnessType>(), 129 result.operands)) 130 return failure(); 131 132 // Parse optional results type list. 133 if (parser.parseOptionalArrowTypeList(result.types)) 134 return failure(); 135 136 // Parse the region and add a terminator if elided. 137 if (parser.parseRegion(*doRegion, /*arguments=*/{}, /*argTypes=*/{})) 138 return failure(); 139 AssumingOp::ensureTerminator(*doRegion, parser.getBuilder(), result.location); 140 141 // Parse the optional attribute list. 142 if (parser.parseOptionalAttrDict(result.attributes)) 143 return failure(); 144 return success(); 145 } 146 147 static void print(OpAsmPrinter &p, AssumingOp op) { 148 bool yieldsResults = !op.results().empty(); 149 150 p << AssumingOp::getOperationName() << " " << op.witness(); 151 if (yieldsResults) { 152 p << " -> (" << op.getResultTypes() << ")"; 153 } 154 p.printRegion(op.doRegion(), 155 /*printEntryBlockArgs=*/false, 156 /*printBlockTerminators=*/yieldsResults); 157 p.printOptionalAttrDict(op.getAttrs()); 158 } 159 160 namespace { 161 // Removes AssumingOp with a passing witness and inlines the region. 162 struct AssumingWithTrue : public OpRewritePattern<AssumingOp> { 163 using OpRewritePattern<AssumingOp>::OpRewritePattern; 164 165 LogicalResult matchAndRewrite(AssumingOp op, 166 PatternRewriter &rewriter) const override { 167 auto witness = op.witness().getDefiningOp<ConstWitnessOp>(); 168 if (!witness || !witness.passingAttr()) 169 return failure(); 170 171 AssumingOp::inlineRegionIntoParent(op, rewriter); 172 return success(); 173 } 174 }; 175 } // namespace 176 177 void AssumingOp::getCanonicalizationPatterns(OwningRewritePatternList &patterns, 178 MLIRContext *context) { 179 // If taking a passing witness, inline region. 180 patterns.insert<AssumingWithTrue>(context); 181 } 182 183 void AssumingOp::inlineRegionIntoParent(AssumingOp &op, 184 PatternRewriter &rewriter) { 185 auto *blockBeforeAssuming = rewriter.getInsertionBlock(); 186 auto *assumingBlock = op.getBody(); 187 auto initPosition = rewriter.getInsertionPoint(); 188 auto *blockAfterAssuming = 189 rewriter.splitBlock(blockBeforeAssuming, initPosition); 190 191 // Remove the AssumingOp and AssumingYieldOp. 192 auto &yieldOp = assumingBlock->back(); 193 rewriter.inlineRegionBefore(op.doRegion(), blockAfterAssuming); 194 rewriter.replaceOp(op, yieldOp.getOperands()); 195 rewriter.eraseOp(&yieldOp); 196 197 // Merge blocks together as there was no branching behavior from the 198 // AssumingOp. 199 rewriter.mergeBlocks(assumingBlock, blockBeforeAssuming); 200 rewriter.mergeBlocks(blockAfterAssuming, blockBeforeAssuming); 201 } 202 203 //===----------------------------------------------------------------------===// 204 // AssumingAllOp 205 //===----------------------------------------------------------------------===// 206 OpFoldResult AssumingAllOp::fold(ArrayRef<Attribute> operands) { 207 // Iterate in reverse to first handle all constant operands. They are 208 // guaranteed to be the tail of the inputs because this is commutative. 209 for (int idx = operands.size() - 1; idx >= 0; idx--) { 210 Attribute a = operands[idx]; 211 // Cannot fold if any inputs are not constant; 212 if (!a) 213 return nullptr; 214 215 // We do not need to keep statically known values after handling them in 216 // this method. 217 getOperation()->eraseOperand(idx); 218 219 // Always false if any input is statically known false 220 if (!a.cast<BoolAttr>().getValue()) 221 return a; 222 } 223 // If this is reached, all inputs were statically known passing. 224 return BoolAttr::get(true, getContext()); 225 } 226 227 static LogicalResult verify(AssumingAllOp op) { 228 // Ensure that AssumingAllOp contains at least one operand 229 if (op.getNumOperands() == 0) 230 return op.emitOpError("no operands specified"); 231 232 return success(); 233 } 234 235 //===----------------------------------------------------------------------===// 236 // BroadcastOp 237 //===----------------------------------------------------------------------===// 238 239 OpFoldResult BroadcastOp::fold(ArrayRef<Attribute> operands) { 240 if (!operands[1]) 241 return nullptr; 242 243 auto rhsShape = llvm::to_vector<6>( 244 operands[1].cast<DenseIntElementsAttr>().getValues<int64_t>()); 245 if (rhsShape.empty()) 246 return lhs(); 247 248 if (!operands[0]) 249 return nullptr; 250 251 auto lhsShape = llvm::to_vector<6>( 252 operands[0].cast<DenseIntElementsAttr>().getValues<int64_t>()); 253 if (lhsShape.empty()) 254 return rhs(); 255 256 SmallVector<int64_t, 6> resultShape; 257 // If the shapes are not compatible, we can't fold it. 258 // TODO: Fold to an "error". 259 if (!OpTrait::util::getBroadcastedShape(lhsShape, rhsShape, resultShape)) 260 return nullptr; 261 Builder builder(getContext()); 262 return builder.getIndexTensorAttr(resultShape); 263 } 264 265 //===----------------------------------------------------------------------===// 266 // ConcatOp 267 //===----------------------------------------------------------------------===// 268 269 OpFoldResult ConcatOp::fold(ArrayRef<Attribute> operands) { 270 if (!operands[0] || !operands[1]) 271 return nullptr; 272 auto lhsShape = llvm::to_vector<6>( 273 operands[0].cast<DenseIntElementsAttr>().getValues<int64_t>()); 274 auto rhsShape = llvm::to_vector<6>( 275 operands[1].cast<DenseIntElementsAttr>().getValues<int64_t>()); 276 SmallVector<int64_t, 6> resultShape; 277 resultShape.append(lhsShape.begin(), lhsShape.end()); 278 resultShape.append(rhsShape.begin(), rhsShape.end()); 279 Builder builder(getContext()); 280 return builder.getIndexTensorAttr(resultShape); 281 } 282 283 //===----------------------------------------------------------------------===// 284 // ConstShapeOp 285 //===----------------------------------------------------------------------===// 286 287 static void print(OpAsmPrinter &p, ConstShapeOp &op) { 288 p << "shape.const_shape "; 289 p.printOptionalAttrDict(op.getAttrs(), /*elidedAttrs=*/{"shape"}); 290 p << "["; 291 interleaveComma(op.shape().getValues<int64_t>(), p, 292 [&](int64_t i) { p << i; }); 293 p << "]"; 294 } 295 296 static ParseResult parseConstShapeOp(OpAsmParser &parser, 297 OperationState &result) { 298 if (parser.parseOptionalAttrDict(result.attributes)) 299 return failure(); 300 // We piggy-back on ArrayAttr parsing, though we don't internally store the 301 // shape as an ArrayAttr. 302 // TODO: Implement custom parser and maybe make syntax a bit more concise. 303 Attribute extentsRaw; 304 NamedAttrList dummy; 305 if (parser.parseAttribute(extentsRaw, "dummy", dummy)) 306 return failure(); 307 auto extentsArray = extentsRaw.dyn_cast<ArrayAttr>(); 308 if (!extentsArray) 309 return failure(); 310 SmallVector<int64_t, 6> ints; 311 for (Attribute extent : extentsArray) { 312 IntegerAttr attr = extent.dyn_cast<IntegerAttr>(); 313 if (!attr) 314 return failure(); 315 ints.push_back(attr.getInt()); 316 } 317 Builder &builder = parser.getBuilder(); 318 result.addAttribute("shape", builder.getIndexTensorAttr(ints)); 319 320 result.types.push_back(ShapeType::get(builder.getContext())); 321 return success(); 322 } 323 324 OpFoldResult ConstShapeOp::fold(ArrayRef<Attribute>) { return shapeAttr(); } 325 326 //===----------------------------------------------------------------------===// 327 // CstrBroadcastableOp 328 //===----------------------------------------------------------------------===// 329 330 namespace { 331 // Given an input shape Value, try to obtain the shape's values. 332 LogicalResult getShapeVec(Value input, SmallVectorImpl<int64_t> &shapeValues) { 333 if (auto inputOp = input.getDefiningOp<ShapeOfOp>()) { 334 auto type = inputOp.arg().getType().dyn_cast<ShapedType>(); 335 if (!type.hasRank()) 336 return failure(); 337 shapeValues = llvm::to_vector<6>(type.getShape()); 338 return success(); 339 } else if (auto inputOp = input.getDefiningOp<ConstShapeOp>()) { 340 shapeValues = llvm::to_vector<6>(inputOp.shape().getValues<int64_t>()); 341 return success(); 342 } else { 343 return failure(); 344 } 345 } 346 347 // For shapes that were created by some operations, we can obtain partial 348 // information on the shapes and sometimes determine if they will be 349 // broadcastable with that. 350 struct CstrBroadcastablePartialInfo 351 : public OpRewritePattern<CstrBroadcastableOp> { 352 using OpRewritePattern<CstrBroadcastableOp>::OpRewritePattern; 353 354 LogicalResult matchAndRewrite(CstrBroadcastableOp op, 355 PatternRewriter &rewriter) const override { 356 SmallVector<int64_t, 6> lhsShape, rhsShape; 357 if (failed(getShapeVec(op.lhs(), lhsShape))) 358 return failure(); 359 if (failed(getShapeVec(op.rhs(), rhsShape))) 360 return failure(); 361 if (!OpTrait::util::staticallyKnownBroadcastable(lhsShape, rhsShape)) 362 return failure(); 363 364 rewriter.replaceOpWithNewOp<ConstWitnessOp>(op.getOperation(), true); 365 return success(); 366 } 367 }; 368 369 // Scalars are always broadcastable. 370 struct CstrBroadcastableScalar : public OpRewritePattern<CstrBroadcastableOp> { 371 using OpRewritePattern<CstrBroadcastableOp>::OpRewritePattern; 372 373 LogicalResult matchAndRewrite(CstrBroadcastableOp op, 374 PatternRewriter &rewriter) const override { 375 SmallVector<int64_t, 6> shape; 376 if (failed(getShapeVec(op.lhs(), shape)) || shape.size() > 0) 377 return failure(); 378 if (failed(getShapeVec(op.rhs(), shape)) || shape.size() > 0) 379 return failure(); 380 381 rewriter.replaceOpWithNewOp<ConstWitnessOp>(op.getOperation(), true); 382 return success(); 383 } 384 }; 385 386 } // namespace 387 388 void CstrBroadcastableOp::getCanonicalizationPatterns( 389 OwningRewritePatternList &patterns, MLIRContext *context) { 390 // Canonicalization patterns have overlap with the considerations during 391 // folding in case additional shape information is inferred at some point that 392 // does not result in folding. 393 patterns.insert<CstrBroadcastableEqOps, CstrBroadcastablePartialInfo, 394 CstrBroadcastableScalar>(context); 395 } 396 397 OpFoldResult CstrBroadcastableOp::fold(ArrayRef<Attribute> operands) { 398 // Both operands are not needed if one is a scalar. 399 if (operands[0] && 400 operands[0].cast<DenseIntElementsAttr>().getNumElements() == 0) 401 return BoolAttr::get(true, getContext()); 402 if (operands[1] && 403 operands[1].cast<DenseIntElementsAttr>().getNumElements() == 0) 404 return BoolAttr::get(true, getContext()); 405 406 if (operands[0] && operands[1]) { 407 auto lhsShape = llvm::to_vector<6>( 408 operands[0].cast<DenseIntElementsAttr>().getValues<int64_t>()); 409 auto rhsShape = llvm::to_vector<6>( 410 operands[1].cast<DenseIntElementsAttr>().getValues<int64_t>()); 411 SmallVector<int64_t, 6> resultShape; 412 if (OpTrait::util::staticallyKnownBroadcastable(lhsShape, rhsShape)) 413 return BoolAttr::get(true, getContext()); 414 } 415 416 // Lastly, see if folding can be completed based on what constraints are known 417 // on the input shapes. 418 SmallVector<int64_t, 6> lhsShape, rhsShape; 419 if (failed(getShapeVec(lhs(), lhsShape))) 420 return nullptr; 421 if (failed(getShapeVec(rhs(), rhsShape))) 422 return nullptr; 423 424 if (OpTrait::util::staticallyKnownBroadcastable(lhsShape, rhsShape)) 425 return BoolAttr::get(true, getContext()); 426 427 // Because a failing witness result here represents an eventual assertion 428 // failure, we do not replace it with a constant witness. 429 return nullptr; 430 } 431 432 //===----------------------------------------------------------------------===// 433 // CstrEqOp 434 //===----------------------------------------------------------------------===// 435 436 void CstrEqOp::getCanonicalizationPatterns(OwningRewritePatternList &patterns, 437 MLIRContext *context) { 438 // If inputs are equal, return passing witness 439 patterns.insert<CstrEqEqOps>(context); 440 } 441 442 OpFoldResult CstrEqOp::fold(ArrayRef<Attribute> operands) { 443 if (llvm::all_of(operands, 444 [&](Attribute a) { return a && a == operands[0]; })) 445 return BoolAttr::get(true, getContext()); 446 447 // Because a failing witness result here represents an eventual assertion 448 // failure, we do not try to replace it with a constant witness. Similarly, we 449 // cannot if there are any non-const inputs. 450 return nullptr; 451 } 452 453 //===----------------------------------------------------------------------===// 454 // ConstSizeOp 455 //===----------------------------------------------------------------------===// 456 457 void ConstSizeOp::build(OpBuilder &builder, OperationState &result, 458 int64_t value) { 459 build(builder, result, builder.getIndexAttr(value)); 460 } 461 462 OpFoldResult ConstSizeOp::fold(ArrayRef<Attribute>) { return valueAttr(); } 463 464 void ConstSizeOp::getAsmResultNames( 465 llvm::function_ref<void(Value, StringRef)> setNameFn) { 466 SmallString<4> buffer; 467 llvm::raw_svector_ostream os(buffer); 468 os << "c" << value(); 469 setNameFn(getResult(), os.str()); 470 } 471 472 //===----------------------------------------------------------------------===// 473 // ConstWitnessOp 474 //===----------------------------------------------------------------------===// 475 476 OpFoldResult ConstWitnessOp::fold(ArrayRef<Attribute>) { return passingAttr(); } 477 478 //===----------------------------------------------------------------------===// 479 // ShapeEqOp 480 //===----------------------------------------------------------------------===// 481 482 OpFoldResult ShapeEqOp::fold(ArrayRef<Attribute> operands) { 483 auto lhs = operands[0].dyn_cast_or_null<DenseIntElementsAttr>(); 484 if (lhs == nullptr) 485 return {}; 486 auto rhs = operands[1].dyn_cast_or_null<DenseIntElementsAttr>(); 487 if (rhs == nullptr) 488 return {}; 489 return BoolAttr::get(lhs == rhs, getContext()); 490 } 491 492 //===----------------------------------------------------------------------===// 493 // IndexToSizeOp 494 //===----------------------------------------------------------------------===// 495 496 OpFoldResult IndexToSizeOp::fold(ArrayRef<Attribute> operands) { 497 // Constant values of both types, `shape.size` and `index`, are represented as 498 // `IntegerAttr`s which makes constant folding simple. 499 if (Attribute arg = operands[0]) 500 return arg; 501 return {}; 502 } 503 504 void IndexToSizeOp::getCanonicalizationPatterns( 505 OwningRewritePatternList &patterns, MLIRContext *context) { 506 patterns.insert<SizeToIndexToSizeCanonicalization>(context); 507 } 508 509 //===----------------------------------------------------------------------===// 510 // FromExtentsOp 511 //===----------------------------------------------------------------------===// 512 513 OpFoldResult FromExtentsOp::fold(ArrayRef<Attribute> operands) { 514 if (llvm::any_of(operands, [](Attribute a) { return !a; })) 515 return nullptr; 516 SmallVector<int64_t, 6> extents; 517 for (auto attr : operands) 518 extents.push_back(attr.cast<IntegerAttr>().getInt()); 519 Builder builder(getContext()); 520 return builder.getIndexTensorAttr(extents); 521 } 522 523 //===----------------------------------------------------------------------===// 524 // GetExtentOp 525 //===----------------------------------------------------------------------===// 526 527 Optional<int64_t> GetExtentOp::getConstantDim() { 528 if (auto constSizeOp = dim().getDefiningOp<ConstSizeOp>()) { 529 return constSizeOp.value().getLimitedValue(); 530 } 531 return llvm::None; 532 } 533 534 OpFoldResult GetExtentOp::fold(ArrayRef<Attribute> operands) { 535 auto elements = operands[0].dyn_cast_or_null<DenseIntElementsAttr>(); 536 if (!elements) 537 return nullptr; 538 Optional<int64_t> dim = getConstantDim(); 539 if (!dim.hasValue()) 540 return nullptr; 541 if (dim.getValue() >= elements.getNumElements()) 542 return nullptr; 543 return elements.getValue({(uint64_t)dim.getValue()}); 544 } 545 546 void GetExtentOp::build(OpBuilder &builder, OperationState &result, Value shape, 547 int64_t dim) { 548 auto loc = result.location; 549 auto dimAttr = builder.getIndexAttr(dim); 550 Value dimValue = builder.create<ConstSizeOp>(loc, dimAttr); 551 build(builder, result, shape, dimValue); 552 } 553 554 //===----------------------------------------------------------------------===// 555 // RankOp 556 //===----------------------------------------------------------------------===// 557 558 OpFoldResult RankOp::fold(ArrayRef<Attribute> operands) { 559 auto shape = operands[0].dyn_cast_or_null<DenseIntElementsAttr>(); 560 if (!shape) 561 return {}; 562 int64_t rank = shape.getNumElements(); 563 Builder builder(getContext()); 564 return builder.getIndexAttr(rank); 565 } 566 567 /// Evaluate the `rank` operation for shapes of ranked tensors at compile time. 568 /// Constant folding fails in cases where only the rank is constant, not the 569 /// shape itself. 570 /// This canonicalization matches `shape.rank(shape.shape_of(%ranked_tensor))`. 571 /// 572 /// Example: 573 /// 574 /// %shape = shape.shape_of %ranked_tensor : tensor<1x2x?xf32> 575 /// %rank = shape.rank %shape 576 /// 577 /// becomes 578 /// 579 /// %rank = shape.const_size 3 580 581 namespace { 582 struct RankShapeOfCanonicalizationPattern : public OpRewritePattern<RankOp> { 583 using OpRewritePattern<RankOp>::OpRewritePattern; 584 585 LogicalResult matchAndRewrite(RankOp op, 586 PatternRewriter &rewriter) const override { 587 auto shapeOfOp = op.shape().getDefiningOp<ShapeOfOp>(); 588 if (!shapeOfOp) 589 return failure(); 590 auto rankedTensorType = 591 shapeOfOp.arg().getType().dyn_cast<RankedTensorType>(); 592 if (!rankedTensorType) 593 return failure(); 594 int64_t rank = rankedTensorType.getRank(); 595 rewriter.replaceOpWithNewOp<ConstSizeOp>(op.getOperation(), rank); 596 return success(); 597 } 598 }; 599 } // namespace 600 601 void RankOp::getCanonicalizationPatterns(OwningRewritePatternList &patterns, 602 MLIRContext *context) { 603 patterns.insert<RankShapeOfCanonicalizationPattern>(context); 604 } 605 606 //===----------------------------------------------------------------------===// 607 // NumElementsOp 608 //===----------------------------------------------------------------------===// 609 610 OpFoldResult NumElementsOp::fold(ArrayRef<Attribute> operands) { 611 612 // Fold only when argument constant. 613 Attribute shape = operands[0]; 614 if (!shape) 615 return {}; 616 617 APInt product(64, 1); 618 for (auto value : shape.cast<DenseIntElementsAttr>()) 619 product *= value; 620 Builder builder(getContext()); 621 return builder.getIndexAttr(product.getLimitedValue()); 622 } 623 624 //===----------------------------------------------------------------------===// 625 // ShapeOfOp 626 //===----------------------------------------------------------------------===// 627 628 OpFoldResult ShapeOfOp::fold(ArrayRef<Attribute>) { 629 auto type = getOperand().getType().dyn_cast<ShapedType>(); 630 if (!type || !type.hasStaticShape()) 631 return nullptr; 632 Builder builder(getContext()); 633 return builder.getIndexTensorAttr(type.getShape()); 634 } 635 636 //===----------------------------------------------------------------------===// 637 // SizeToIndexOp 638 //===----------------------------------------------------------------------===// 639 640 OpFoldResult SizeToIndexOp::fold(ArrayRef<Attribute> operands) { 641 // Constant values of both types, `shape.size` and `index`, are represented as 642 // `IntegerAttr`s which makes constant folding simple. 643 if (Attribute arg = operands[0]) 644 return arg; 645 return {}; 646 } 647 648 void SizeToIndexOp::getCanonicalizationPatterns( 649 OwningRewritePatternList &patterns, MLIRContext *context) { 650 patterns.insert<IndexToSizeToIndexCanonicalization>(context); 651 } 652 653 //===----------------------------------------------------------------------===// 654 // YieldOp 655 //===----------------------------------------------------------------------===// 656 657 static LogicalResult verify(YieldOp op) { 658 auto *parentOp = op.getParentOp(); 659 auto results = parentOp->getResults(); 660 auto operands = op.getOperands(); 661 662 if (parentOp->getNumResults() != op.getNumOperands()) 663 return op.emitOpError() << "number of operands does not match number of " 664 "results of its parent"; 665 for (auto e : llvm::zip(results, operands)) 666 if (std::get<0>(e).getType() != std::get<1>(e).getType()) 667 return op.emitOpError() 668 << "types mismatch between yield op and its parent"; 669 670 return success(); 671 } 672 673 //===----------------------------------------------------------------------===// 674 // SplitAtOp 675 //===----------------------------------------------------------------------===// 676 677 LogicalResult SplitAtOp::fold(ArrayRef<Attribute> operands, 678 SmallVectorImpl<OpFoldResult> &results) { 679 if (!operands[0] || !operands[1]) 680 return failure(); 681 auto shapeVec = llvm::to_vector<6>( 682 operands[0].cast<DenseIntElementsAttr>().getValues<int64_t>()); 683 auto shape = llvm::makeArrayRef(shapeVec); 684 auto splitPoint = operands[1].cast<IntegerAttr>().getInt(); 685 // Verify that the split point is in the correct range. 686 // TODO: Constant fold to an "error". 687 int64_t rank = shape.size(); 688 if (!(-rank <= splitPoint && splitPoint <= rank)) 689 return failure(); 690 if (splitPoint < 0) 691 splitPoint += shape.size(); 692 Builder builder(operands[0].getContext()); 693 results.push_back(builder.getIndexTensorAttr(shape.take_front(splitPoint))); 694 results.push_back(builder.getIndexTensorAttr(shape.drop_front(splitPoint))); 695 return success(); 696 } 697 698 //===----------------------------------------------------------------------===// 699 // ToExtentTensorOp 700 //===----------------------------------------------------------------------===// 701 702 OpFoldResult ToExtentTensorOp::fold(ArrayRef<Attribute> operands) { 703 if (!operands[0]) 704 return nullptr; 705 Builder builder(getContext()); 706 auto shape = llvm::to_vector<6>( 707 operands[0].cast<DenseIntElementsAttr>().getValues<int64_t>()); 708 auto type = RankedTensorType::get({static_cast<int64_t>(shape.size())}, 709 builder.getIndexType()); 710 return DenseIntElementsAttr::get(type, shape); 711 } 712 713 //===----------------------------------------------------------------------===// 714 // ReduceOp 715 //===----------------------------------------------------------------------===// 716 717 void ReduceOp::build(OpBuilder &builder, OperationState &result, Value shape, 718 ValueRange initVals) { 719 result.addOperands(shape); 720 result.addOperands(initVals); 721 722 Region *bodyRegion = result.addRegion(); 723 bodyRegion->push_back(new Block); 724 Block &bodyBlock = bodyRegion->front(); 725 bodyBlock.addArgument(builder.getIndexType()); 726 bodyBlock.addArgument(SizeType::get(builder.getContext())); 727 728 for (Type initValType : initVals.getTypes()) { 729 bodyBlock.addArgument(initValType); 730 result.addTypes(initValType); 731 } 732 } 733 734 static LogicalResult verify(ReduceOp op) { 735 // Verify block arg types. 736 Block &block = op.region().front(); 737 738 // The block takes index, extent, and aggregated values as arguments. 739 auto blockArgsCount = op.initVals().size() + 2; 740 if (block.getNumArguments() != blockArgsCount) 741 return op.emitOpError() << "ReduceOp body is expected to have " 742 << blockArgsCount << " arguments"; 743 744 // The first block argument is the index and must always be of type `index`. 745 if (!block.getArgument(0).getType().isa<IndexType>()) 746 return op.emitOpError( 747 "argument 0 of ReduceOp body is expected to be of IndexType"); 748 749 // The second block argument is the extent and must be of type `size` or 750 // `index`, depending on whether the reduce operation is applied to a shape or 751 // to an extent tensor. 752 Type extentTy = block.getArgument(1).getType(); 753 if (op.shape().getType().isa<ShapeType>()) { 754 if (!extentTy.isa<SizeType>()) 755 return op.emitOpError("argument 1 of ReduceOp body is expected to be of " 756 "SizeType if the ReduceOp operates on a ShapeType"); 757 } else { 758 if (!extentTy.isa<IndexType>()) 759 return op.emitOpError( 760 "argument 1 of ReduceOp body is expected to be of IndexType if the " 761 "ReduceOp operates on an extent tensor"); 762 } 763 764 for (auto type : llvm::enumerate(op.initVals())) 765 if (block.getArgument(type.index() + 2).getType() != type.value().getType()) 766 return op.emitOpError() 767 << "type mismatch between argument " << type.index() + 2 768 << " of ReduceOp body and initial value " << type.index(); 769 return success(); 770 } 771 772 static ParseResult parseReduceOp(OpAsmParser &parser, OperationState &result) { 773 // Parse operands. 774 SmallVector<OpAsmParser::OperandType, 3> operands; 775 Type shapeOrExtentTensorType; 776 if (parser.parseOperandList(operands, /*requiredOperandCount=*/-1, 777 OpAsmParser::Delimiter::Paren) || 778 parser.parseColonType(shapeOrExtentTensorType) || 779 parser.parseOptionalArrowTypeList(result.types)) 780 return failure(); 781 782 // Resolve operands. 783 auto initVals = llvm::makeArrayRef(operands).drop_front(); 784 if (parser.resolveOperand(operands.front(), shapeOrExtentTensorType, 785 result.operands) || 786 parser.resolveOperands(initVals, result.types, parser.getNameLoc(), 787 result.operands)) 788 return failure(); 789 790 // Parse the body. 791 Region *body = result.addRegion(); 792 if (parser.parseRegion(*body, /*args=*/{}, /*argTypes=*/{})) 793 return failure(); 794 795 // Parse attributes. 796 if (parser.parseOptionalAttrDict(result.attributes)) 797 return failure(); 798 799 return success(); 800 } 801 802 static void print(OpAsmPrinter &p, ReduceOp op) { 803 p << op.getOperationName() << '(' << op.shape() << ", " << op.initVals() 804 << ") : " << op.shape().getType(); 805 p.printOptionalArrowTypeList(op.getResultTypes()); 806 p.printRegion(op.region()); 807 p.printOptionalAttrDict(op.getAttrs()); 808 } 809 810 namespace mlir { 811 namespace shape { 812 813 #define GET_OP_CLASSES 814 #include "mlir/Dialect/Shape/IR/ShapeOps.cpp.inc" 815 816 } // namespace shape 817 } // namespace mlir 818