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/StandardOps/IR/Ops.h" 12 #include "mlir/Dialect/Tensor/IR/Tensor.h" 13 #include "mlir/Dialect/Traits.h" 14 #include "mlir/IR/Builders.h" 15 #include "mlir/IR/BuiltinTypes.h" 16 #include "mlir/IR/DialectImplementation.h" 17 #include "mlir/IR/PatternMatch.h" 18 #include "mlir/Transforms/InliningUtils.h" 19 #include "llvm/ADT/SmallString.h" 20 #include "llvm/ADT/TypeSwitch.h" 21 #include "llvm/Support/raw_ostream.h" 22 23 using namespace mlir; 24 using namespace mlir::shape; 25 26 namespace { 27 #include "ShapeCanonicalization.inc" 28 } 29 30 RankedTensorType shape::getExtentTensorType(MLIRContext *ctx) { 31 return RankedTensorType::get({ShapedType::kDynamicSize}, IndexType::get(ctx)); 32 } 33 34 static bool isErrorPropagationPossible(TypeRange operandTypes) { 35 return llvm::any_of(operandTypes, [](Type ty) { 36 return ty.isa<SizeType, ShapeType, ValueShapeType>(); 37 }); 38 } 39 40 static LogicalResult verifySizeOrIndexOp(Operation *op) { 41 assert(op != nullptr && op->getNumResults() == 1); 42 Type resultTy = op->getResultTypes().front(); 43 if (isErrorPropagationPossible(op->getOperandTypes())) { 44 if (!resultTy.isa<SizeType>()) 45 return op->emitOpError() 46 << "if at least one of the operands can hold error values then " 47 "the result must be of type `size` to propagate them"; 48 } 49 return success(); 50 } 51 52 static LogicalResult verifyShapeOrExtentTensorOp(Operation *op) { 53 assert(op != nullptr && op->getNumResults() == 1); 54 Type resultTy = op->getResultTypes().front(); 55 if (isErrorPropagationPossible(op->getOperandTypes())) { 56 if (!resultTy.isa<ShapeType>()) 57 return op->emitOpError() 58 << "if at least one of the operands can hold error values then " 59 "the result must be of type `shape` to propagate them"; 60 } 61 return success(); 62 } 63 64 //===----------------------------------------------------------------------===// 65 // InlinerInterface 66 //===----------------------------------------------------------------------===// 67 68 namespace { 69 /// This class defines the interface for inlining shape dialect ops. 70 struct ShapeInlinerInterface : public DialectInlinerInterface { 71 using DialectInlinerInterface::DialectInlinerInterface; 72 73 // Returns true if the given region 'src' can be inlined into the region 74 // 'dest' that is attached to an operation registered to the current dialect. 75 bool isLegalToInline(Region *dest, Region *src, bool wouldBeCloned, 76 BlockAndValueMapping &) const final { 77 return true; 78 } 79 80 // Returns true if the given operation 'op', that is registered to this 81 // dialect, can be inlined into the region 'dest' that is attached to an 82 // operation registered to the current dialect. 83 bool isLegalToInline(Operation *op, Region *dest, bool wouldBeCloned, 84 BlockAndValueMapping &) const final { 85 return true; 86 } 87 }; 88 } // namespace 89 90 void ShapeDialect::initialize() { 91 addOperations< 92 #define GET_OP_LIST 93 #include "mlir/Dialect/Shape/IR/ShapeOps.cpp.inc" 94 >(); 95 addTypes<ShapeType, SizeType, ValueShapeType, WitnessType>(); 96 addInterfaces<ShapeInlinerInterface>(); 97 // Allow unknown operations during prototyping and testing. As the dialect is 98 // still evolving it makes it simple to start with an unregistered ops and 99 // try different variants before actually defining the op. 100 allowUnknownOperations(); 101 } 102 103 Operation *ShapeDialect::materializeConstant(OpBuilder &builder, 104 Attribute value, Type type, 105 Location loc) { 106 if (type.isa<ShapeType>() || 107 type == getExtentTensorType(builder.getContext())) 108 return builder.create<ConstShapeOp>(loc, type, 109 value.cast<DenseIntElementsAttr>()); 110 if (type.isa<SizeType>()) 111 return builder.create<ConstSizeOp>(loc, type, value.cast<IntegerAttr>()); 112 if (type.isa<WitnessType>()) 113 return builder.create<ConstWitnessOp>(loc, type, value.cast<BoolAttr>()); 114 if (ConstantOp::isBuildableWith(value, type)) 115 return builder.create<ConstantOp>(loc, type, value); 116 return nullptr; 117 } 118 119 /// Parse a type registered to this dialect. 120 Type ShapeDialect::parseType(DialectAsmParser &parser) const { 121 StringRef keyword; 122 if (parser.parseKeyword(&keyword)) 123 return Type(); 124 125 if (keyword == "shape") 126 return ShapeType::get(getContext()); 127 if (keyword == "size") 128 return SizeType::get(getContext()); 129 if (keyword == "value_shape") 130 return ValueShapeType::get(getContext()); 131 if (keyword == "witness") 132 return WitnessType::get(getContext()); 133 134 parser.emitError(parser.getNameLoc(), "unknown shape type: ") << keyword; 135 return Type(); 136 } 137 138 /// Print a type registered to this dialect. 139 void ShapeDialect::printType(Type type, DialectAsmPrinter &os) const { 140 TypeSwitch<Type>(type) 141 .Case<ShapeType>([&](Type) { os << "shape"; }) 142 .Case<SizeType>([&](Type) { os << "size"; }) 143 .Case<ValueShapeType>([&](Type) { os << "value_shape"; }) 144 .Case<WitnessType>([&](Type) { os << "witness"; }) 145 .Default([](Type) { llvm_unreachable("unexpected 'shape' type kind"); }); 146 } 147 148 LogicalResult ShapeDialect::verifyOperationAttribute(Operation *op, 149 NamedAttribute attribute) { 150 // Verify shape.lib attribute. 151 if (attribute.first == "shape.lib") { 152 if (!op->hasTrait<OpTrait::SymbolTable>()) 153 return op->emitError( 154 "shape.lib attribute may only be on op implementing SymbolTable"); 155 156 if (auto symbolRef = attribute.second.dyn_cast<SymbolRefAttr>()) { 157 auto *symbol = SymbolTable::lookupSymbolIn(op, symbolRef); 158 if (!symbol) 159 return op->emitError("shape function library ") 160 << symbolRef << " not found"; 161 return isa<shape::FunctionLibraryOp>(symbol) 162 ? success() 163 : op->emitError() 164 << symbolRef << " required to be shape function library"; 165 } 166 167 if (auto arr = attribute.second.dyn_cast<ArrayAttr>()) { 168 // Verify all entries are function libraries and mappings in libraries 169 // refer to unique ops. 170 DenseSet<Identifier> key; 171 for (auto it : arr) { 172 if (!it.isa<SymbolRefAttr>()) 173 return op->emitError( 174 "only SymbolRefAttr allowed in shape.lib attribute array"); 175 176 auto shapeFnLib = dyn_cast<shape::FunctionLibraryOp>( 177 SymbolTable::lookupSymbolIn(op, it.cast<SymbolRefAttr>())); 178 if (!shapeFnLib) 179 return op->emitError() 180 << it << " does not refer to FunctionLibraryOp"; 181 for (auto mapping : shapeFnLib.mapping()) { 182 if (!key.insert(mapping.first).second) { 183 return op->emitError("only one op to shape mapping allowed, found " 184 "multiple for `") 185 << mapping.first << "`"; 186 } 187 } 188 } 189 return success(); 190 } 191 192 return op->emitError("only SymbolRefAttr or array of SymbolRefAttrs " 193 "allowed as shape.lib attribute"); 194 } 195 return success(); 196 } 197 198 //===----------------------------------------------------------------------===// 199 // AnyOp 200 //===----------------------------------------------------------------------===// 201 202 // TODO: Canonicalization should be implemented for shapes that can be 203 // determined through mixtures of the known dimensions of the inputs. 204 OpFoldResult AnyOp::fold(ArrayRef<Attribute> operands) { 205 // Only the last operand is checked because AnyOp is commutative. 206 if (operands.back()) 207 return operands.back(); 208 209 return nullptr; 210 } 211 212 //===----------------------------------------------------------------------===// 213 // AssumingOp 214 //===----------------------------------------------------------------------===// 215 216 static ParseResult parseAssumingOp(OpAsmParser &parser, 217 OperationState &result) { 218 result.regions.reserve(1); 219 Region *doRegion = result.addRegion(); 220 221 auto &builder = parser.getBuilder(); 222 OpAsmParser::OperandType cond; 223 if (parser.parseOperand(cond) || 224 parser.resolveOperand(cond, builder.getType<WitnessType>(), 225 result.operands)) 226 return failure(); 227 228 // Parse optional results type list. 229 if (parser.parseOptionalArrowTypeList(result.types)) 230 return failure(); 231 232 // Parse the region and add a terminator if elided. 233 if (parser.parseRegion(*doRegion, /*arguments=*/{}, /*argTypes=*/{})) 234 return failure(); 235 AssumingOp::ensureTerminator(*doRegion, parser.getBuilder(), result.location); 236 237 // Parse the optional attribute list. 238 if (parser.parseOptionalAttrDict(result.attributes)) 239 return failure(); 240 return success(); 241 } 242 243 static void print(OpAsmPrinter &p, AssumingOp op) { 244 bool yieldsResults = !op.results().empty(); 245 246 p << AssumingOp::getOperationName() << " " << op.witness(); 247 if (yieldsResults) { 248 p << " -> (" << op.getResultTypes() << ")"; 249 } 250 p.printRegion(op.doRegion(), 251 /*printEntryBlockArgs=*/false, 252 /*printBlockTerminators=*/yieldsResults); 253 p.printOptionalAttrDict(op->getAttrs()); 254 } 255 256 namespace { 257 // Removes AssumingOp with a passing witness and inlines the region. 258 struct AssumingWithTrue : public OpRewritePattern<AssumingOp> { 259 using OpRewritePattern<AssumingOp>::OpRewritePattern; 260 261 LogicalResult matchAndRewrite(AssumingOp op, 262 PatternRewriter &rewriter) const override { 263 auto witness = op.witness().getDefiningOp<ConstWitnessOp>(); 264 if (!witness || !witness.passingAttr()) 265 return failure(); 266 267 AssumingOp::inlineRegionIntoParent(op, rewriter); 268 return success(); 269 } 270 }; 271 } // namespace 272 273 void AssumingOp::getCanonicalizationPatterns(RewritePatternSet &patterns, 274 MLIRContext *context) { 275 // If taking a passing witness, inline region. 276 patterns.add<AssumingWithTrue>(context); 277 } 278 279 // See RegionBranchOpInterface in Interfaces/ControlFlowInterfaces.td 280 void AssumingOp::getSuccessorRegions( 281 Optional<unsigned> index, ArrayRef<Attribute> operands, 282 SmallVectorImpl<RegionSuccessor> ®ions) { 283 // AssumingOp has unconditional control flow into the region and back to the 284 // parent, so return the correct RegionSuccessor purely based on the index 285 // being None or 0. 286 if (index.hasValue()) { 287 regions.push_back(RegionSuccessor(getResults())); 288 return; 289 } 290 291 regions.push_back(RegionSuccessor(&doRegion())); 292 } 293 294 void AssumingOp::inlineRegionIntoParent(AssumingOp &op, 295 PatternRewriter &rewriter) { 296 auto *blockBeforeAssuming = rewriter.getInsertionBlock(); 297 auto *assumingBlock = op.getBody(); 298 auto initPosition = rewriter.getInsertionPoint(); 299 auto *blockAfterAssuming = 300 rewriter.splitBlock(blockBeforeAssuming, initPosition); 301 302 // Remove the AssumingOp and AssumingYieldOp. 303 auto &yieldOp = assumingBlock->back(); 304 rewriter.inlineRegionBefore(op.doRegion(), blockAfterAssuming); 305 rewriter.replaceOp(op, yieldOp.getOperands()); 306 rewriter.eraseOp(&yieldOp); 307 308 // Merge blocks together as there was no branching behavior from the 309 // AssumingOp. 310 rewriter.mergeBlocks(assumingBlock, blockBeforeAssuming); 311 rewriter.mergeBlocks(blockAfterAssuming, blockBeforeAssuming); 312 } 313 314 void AssumingOp::build( 315 OpBuilder &builder, OperationState &result, Value witness, 316 function_ref<SmallVector<Value, 2>(OpBuilder &, Location)> bodyBuilder) { 317 318 result.addOperands(witness); 319 Region *bodyRegion = result.addRegion(); 320 bodyRegion->push_back(new Block); 321 Block &bodyBlock = bodyRegion->front(); 322 323 // Build body. 324 OpBuilder::InsertionGuard guard(builder); 325 builder.setInsertionPointToStart(&bodyBlock); 326 SmallVector<Value, 2> yieldValues = bodyBuilder(builder, result.location); 327 builder.create<AssumingYieldOp>(result.location, yieldValues); 328 329 SmallVector<Type, 2> assumingTypes; 330 for (Value v : yieldValues) 331 assumingTypes.push_back(v.getType()); 332 result.addTypes(assumingTypes); 333 } 334 335 //===----------------------------------------------------------------------===// 336 // AssumingAllOp 337 //===----------------------------------------------------------------------===// 338 339 void AssumingAllOp::getCanonicalizationPatterns(RewritePatternSet &patterns, 340 MLIRContext *context) { 341 patterns.add<AssumingAllOneOp>(context); 342 } 343 344 OpFoldResult AssumingAllOp::fold(ArrayRef<Attribute> operands) { 345 // Iterate in reverse to first handle all constant operands. They are 346 // guaranteed to be the tail of the inputs because this is commutative. 347 for (int idx = operands.size() - 1; idx >= 0; idx--) { 348 Attribute a = operands[idx]; 349 // Cannot fold if any inputs are not constant; 350 if (!a) 351 return nullptr; 352 353 // We do not need to keep statically known values after handling them in 354 // this method. 355 getOperation()->eraseOperand(idx); 356 357 // Always false if any input is statically known false 358 if (!a.cast<BoolAttr>().getValue()) 359 return a; 360 } 361 // If this is reached, all inputs were statically known passing. 362 return BoolAttr::get(getContext(), true); 363 } 364 365 static LogicalResult verify(AssumingAllOp op) { 366 // Ensure that AssumingAllOp contains at least one operand 367 if (op.getNumOperands() == 0) 368 return op.emitOpError("no operands specified"); 369 370 return success(); 371 } 372 373 //===----------------------------------------------------------------------===// 374 // BroadcastOp 375 //===----------------------------------------------------------------------===// 376 377 OpFoldResult BroadcastOp::fold(ArrayRef<Attribute> operands) { 378 if (operands.size() == 1) 379 return shapes().front(); 380 381 // TODO: Support folding with more than 2 input shapes 382 if (shapes().size() > 2) 383 return nullptr; 384 385 if (!operands[1]) 386 return nullptr; 387 388 auto rhsShape = llvm::to_vector<6>( 389 operands[1].cast<DenseIntElementsAttr>().getValues<int64_t>()); 390 if (rhsShape.empty()) 391 return shapes()[0]; 392 393 if (!operands[0]) 394 return nullptr; 395 396 auto lhsShape = llvm::to_vector<6>( 397 operands[0].cast<DenseIntElementsAttr>().getValues<int64_t>()); 398 if (lhsShape.empty()) 399 return shapes()[1]; 400 401 SmallVector<int64_t, 6> resultShape; 402 // If the shapes are not compatible, we can't fold it. 403 // TODO: Fold to an "error". 404 if (!OpTrait::util::getBroadcastedShape(lhsShape, rhsShape, resultShape)) 405 return nullptr; 406 Builder builder(getContext()); 407 return builder.getIndexTensorAttr(resultShape); 408 } 409 410 static LogicalResult verify(BroadcastOp op) { 411 return verifyShapeOrExtentTensorOp(op); 412 } 413 414 namespace { 415 template <typename OpTy> 416 struct RemoveDuplicateOperandsPattern : public OpRewritePattern<OpTy> { 417 using OpRewritePattern<OpTy>::OpRewritePattern; 418 419 LogicalResult matchAndRewrite(OpTy op, 420 PatternRewriter &rewriter) const override { 421 // Find unique operands. 422 SmallVector<Value, 2> unique; 423 for (Value v : op.getOperands()) { 424 if (!llvm::is_contained(unique, v)) 425 unique.push_back(v); 426 } 427 428 // Reduce op to equivalent with unique operands. 429 if (unique.size() < op.getNumOperands()) { 430 rewriter.replaceOpWithNewOp<OpTy>(op, op->getResultTypes(), unique, 431 op->getAttrs()); 432 return success(); 433 } 434 435 return failure(); 436 } 437 }; 438 439 struct BroadcastForwardSingleOperandPattern 440 : public OpRewritePattern<BroadcastOp> { 441 using OpRewritePattern<BroadcastOp>::OpRewritePattern; 442 443 LogicalResult matchAndRewrite(BroadcastOp op, 444 PatternRewriter &rewriter) const override { 445 if (op.getNumOperands() == 1) { 446 rewriter.replaceOp(op, op.shapes().front()); 447 return success(); 448 } 449 return failure(); 450 } 451 }; 452 } // namespace 453 454 void BroadcastOp::getCanonicalizationPatterns(RewritePatternSet &patterns, 455 MLIRContext *context) { 456 patterns.add<BroadcastForwardSingleOperandPattern, 457 RemoveDuplicateOperandsPattern<BroadcastOp>>(context); 458 } 459 460 //===----------------------------------------------------------------------===// 461 // ConcatOp 462 //===----------------------------------------------------------------------===// 463 464 OpFoldResult ConcatOp::fold(ArrayRef<Attribute> operands) { 465 if (!operands[0] || !operands[1]) 466 return nullptr; 467 auto lhsShape = llvm::to_vector<6>( 468 operands[0].cast<DenseIntElementsAttr>().getValues<int64_t>()); 469 auto rhsShape = llvm::to_vector<6>( 470 operands[1].cast<DenseIntElementsAttr>().getValues<int64_t>()); 471 SmallVector<int64_t, 6> resultShape; 472 resultShape.append(lhsShape.begin(), lhsShape.end()); 473 resultShape.append(rhsShape.begin(), rhsShape.end()); 474 Builder builder(getContext()); 475 return builder.getIndexTensorAttr(resultShape); 476 } 477 478 //===----------------------------------------------------------------------===// 479 // ConstShapeOp 480 //===----------------------------------------------------------------------===// 481 482 static void print(OpAsmPrinter &p, ConstShapeOp &op) { 483 p << "shape.const_shape "; 484 p.printOptionalAttrDict(op->getAttrs(), /*elidedAttrs=*/{"shape"}); 485 p << "["; 486 interleaveComma(op.shape().getValues<int64_t>(), p, 487 [&](int64_t i) { p << i; }); 488 p << "] : "; 489 p.printType(op.getType()); 490 } 491 492 static ParseResult parseConstShapeOp(OpAsmParser &parser, 493 OperationState &result) { 494 if (parser.parseOptionalAttrDict(result.attributes)) 495 return failure(); 496 // We piggy-back on ArrayAttr parsing, though we don't internally store the 497 // shape as an ArrayAttr. 498 // TODO: Implement custom parser and maybe make syntax a bit more concise. 499 Attribute extentsRaw; 500 NamedAttrList dummy; 501 if (parser.parseAttribute(extentsRaw, "dummy", dummy)) 502 return failure(); 503 auto extentsArray = extentsRaw.dyn_cast<ArrayAttr>(); 504 if (!extentsArray) 505 return failure(); 506 SmallVector<int64_t, 6> ints; 507 for (Attribute extent : extentsArray) { 508 IntegerAttr attr = extent.dyn_cast<IntegerAttr>(); 509 if (!attr) 510 return failure(); 511 ints.push_back(attr.getInt()); 512 } 513 Builder &builder = parser.getBuilder(); 514 result.addAttribute("shape", builder.getIndexTensorAttr(ints)); 515 Type resultTy; 516 if (parser.parseColonType(resultTy)) 517 return failure(); 518 result.types.push_back(resultTy); 519 return success(); 520 } 521 522 OpFoldResult ConstShapeOp::fold(ArrayRef<Attribute>) { return shapeAttr(); } 523 524 void ConstShapeOp::getCanonicalizationPatterns(RewritePatternSet &patterns, 525 MLIRContext *context) { 526 patterns.add<TensorCastConstShape>(context); 527 } 528 529 //===----------------------------------------------------------------------===// 530 // CstrBroadcastableOp 531 //===----------------------------------------------------------------------===// 532 533 namespace { 534 // Given an input shape Value, try to obtain the shape's values. 535 LogicalResult getShapeVec(Value input, SmallVectorImpl<int64_t> &shapeValues) { 536 if (auto inputOp = input.getDefiningOp<ShapeOfOp>()) { 537 auto type = inputOp.arg().getType().dyn_cast<ShapedType>(); 538 if (!type.hasRank()) 539 return failure(); 540 shapeValues = llvm::to_vector<6>(type.getShape()); 541 return success(); 542 } else if (auto inputOp = input.getDefiningOp<ConstShapeOp>()) { 543 shapeValues = llvm::to_vector<6>(inputOp.shape().getValues<int64_t>()); 544 return success(); 545 } else { 546 return failure(); 547 } 548 } 549 } // namespace 550 551 void CstrBroadcastableOp::getCanonicalizationPatterns( 552 RewritePatternSet &patterns, MLIRContext *context) { 553 // Canonicalization patterns have overlap with the considerations during 554 // folding in case additional shape information is inferred at some point that 555 // does not result in folding. 556 patterns.add<CstrBroadcastableEqOps, 557 RemoveDuplicateOperandsPattern<CstrBroadcastableOp>>(context); 558 } 559 560 // Return true if there is exactly one attribute not representing a scalar 561 // broadcast. 562 static bool hasAtMostSingleNonScalar(ArrayRef<Attribute> attributes) { 563 bool nonScalarSeen = false; 564 for (Attribute a : attributes) { 565 if (!a || a.cast<DenseIntElementsAttr>().getNumElements() != 0) { 566 if (nonScalarSeen) 567 return false; 568 nonScalarSeen = true; 569 } 570 } 571 return true; 572 } 573 574 OpFoldResult CstrBroadcastableOp::fold(ArrayRef<Attribute> operands) { 575 // No broadcasting is needed if all operands but one are scalar. 576 if (hasAtMostSingleNonScalar(operands)) 577 return BoolAttr::get(getContext(), true); 578 579 if ([&] { 580 SmallVector<SmallVector<int64_t, 6>, 6> extents; 581 for (const auto &operand : operands) { 582 if (!operand) 583 return false; 584 extents.push_back(llvm::to_vector<6>( 585 operand.cast<DenseIntElementsAttr>().getValues<int64_t>())); 586 } 587 return OpTrait::util::staticallyKnownBroadcastable(extents); 588 }()) 589 return BoolAttr::get(getContext(), true); 590 591 // Lastly, see if folding can be completed based on what constraints are known 592 // on the input shapes. 593 if ([&] { 594 SmallVector<SmallVector<int64_t, 6>, 6> extents; 595 for (auto shapeValue : shapes()) { 596 extents.emplace_back(); 597 if (failed(getShapeVec(shapeValue, extents.back()))) 598 return false; 599 } 600 return OpTrait::util::staticallyKnownBroadcastable(extents); 601 }()) 602 return BoolAttr::get(getContext(), true); 603 604 // Because a failing witness result here represents an eventual assertion 605 // failure, we do not replace it with a constant witness. 606 return nullptr; 607 } 608 609 static LogicalResult verify(CstrBroadcastableOp op) { 610 // Ensure that AssumingAllOp contains at least one operand 611 if (op.getNumOperands() < 2) 612 return op.emitOpError("required at least 2 input shapes"); 613 return success(); 614 } 615 616 //===----------------------------------------------------------------------===// 617 // CstrEqOp 618 //===----------------------------------------------------------------------===// 619 620 void CstrEqOp::getCanonicalizationPatterns(RewritePatternSet &patterns, 621 MLIRContext *context) { 622 // If inputs are equal, return passing witness 623 patterns.add<CstrEqEqOps>(context); 624 } 625 626 OpFoldResult CstrEqOp::fold(ArrayRef<Attribute> operands) { 627 if (llvm::all_of(operands, 628 [&](Attribute a) { return a && a == operands[0]; })) 629 return BoolAttr::get(getContext(), true); 630 631 // Because a failing witness result here represents an eventual assertion 632 // failure, we do not try to replace it with a constant witness. Similarly, we 633 // cannot if there are any non-const inputs. 634 return nullptr; 635 } 636 637 //===----------------------------------------------------------------------===// 638 // ConstSizeOp 639 //===----------------------------------------------------------------------===// 640 641 void ConstSizeOp::build(OpBuilder &builder, OperationState &result, 642 int64_t value) { 643 build(builder, result, builder.getIndexAttr(value)); 644 } 645 646 OpFoldResult ConstSizeOp::fold(ArrayRef<Attribute>) { return valueAttr(); } 647 648 void ConstSizeOp::getAsmResultNames( 649 llvm::function_ref<void(Value, StringRef)> setNameFn) { 650 SmallString<4> buffer; 651 llvm::raw_svector_ostream os(buffer); 652 os << "c" << value(); 653 setNameFn(getResult(), os.str()); 654 } 655 656 //===----------------------------------------------------------------------===// 657 // ConstWitnessOp 658 //===----------------------------------------------------------------------===// 659 660 OpFoldResult ConstWitnessOp::fold(ArrayRef<Attribute>) { return passingAttr(); } 661 662 //===----------------------------------------------------------------------===// 663 // CstrRequireOp 664 //===----------------------------------------------------------------------===// 665 666 OpFoldResult CstrRequireOp::fold(ArrayRef<Attribute> operands) { 667 return operands[0]; 668 } 669 670 //===----------------------------------------------------------------------===// 671 // DivOp 672 //===----------------------------------------------------------------------===// 673 674 OpFoldResult DivOp::fold(ArrayRef<Attribute> operands) { 675 auto lhs = operands[0].dyn_cast_or_null<IntegerAttr>(); 676 if (!lhs) 677 return nullptr; 678 auto rhs = operands[1].dyn_cast_or_null<IntegerAttr>(); 679 if (!rhs) 680 return nullptr; 681 682 // Division in APInt does not follow floor(lhs, rhs) when the result is 683 // negative. Rather, APInt rounds toward zero. 684 APInt quotient, remainder; 685 APInt::sdivrem(lhs.getValue(), rhs.getValue(), quotient, remainder); 686 if (quotient.isNegative() && !remainder.isNullValue()) { 687 quotient -= 1; 688 } 689 690 Type indexTy = IndexType::get(getContext()); 691 return IntegerAttr::get(indexTy, quotient); 692 } 693 694 //===----------------------------------------------------------------------===// 695 // ShapeEqOp 696 //===----------------------------------------------------------------------===// 697 698 OpFoldResult ShapeEqOp::fold(ArrayRef<Attribute> operands) { 699 bool allSame = true; 700 if (!operands.empty() && !operands[0]) 701 return {}; 702 for (Attribute operand : operands.drop_front(1)) { 703 if (!operand) 704 return {}; 705 allSame = allSame && operand == operands[0]; 706 } 707 return BoolAttr::get(getContext(), allSame); 708 } 709 710 //===----------------------------------------------------------------------===// 711 // IndexToSizeOp 712 //===----------------------------------------------------------------------===// 713 714 OpFoldResult IndexToSizeOp::fold(ArrayRef<Attribute> operands) { 715 // Constant values of both types, `shape.size` and `index`, are represented as 716 // `IntegerAttr`s which makes constant folding simple. 717 if (Attribute arg = operands[0]) 718 return arg; 719 return {}; 720 } 721 722 void IndexToSizeOp::getCanonicalizationPatterns(RewritePatternSet &patterns, 723 MLIRContext *context) { 724 patterns.add<SizeToIndexToSizeCanonicalization>(context); 725 } 726 727 //===----------------------------------------------------------------------===// 728 // FromExtentsOp 729 //===----------------------------------------------------------------------===// 730 731 OpFoldResult FromExtentsOp::fold(ArrayRef<Attribute> operands) { 732 if (llvm::any_of(operands, [](Attribute a) { return !a; })) 733 return nullptr; 734 SmallVector<int64_t, 6> extents; 735 for (auto attr : operands) 736 extents.push_back(attr.cast<IntegerAttr>().getInt()); 737 Builder builder(getContext()); 738 return builder.getIndexTensorAttr(extents); 739 } 740 741 //===----------------------------------------------------------------------===// 742 // FunctionLibraryOp 743 //===----------------------------------------------------------------------===// 744 745 void FunctionLibraryOp::build(OpBuilder &builder, OperationState &result, 746 StringRef name) { 747 result.attributes.push_back(builder.getNamedAttr( 748 ::mlir::SymbolTable::getSymbolAttrName(), builder.getStringAttr(name))); 749 } 750 751 FuncOp FunctionLibraryOp::getShapeFunction(Operation *op) { 752 auto attr = mapping() 753 .get(op->getName().getIdentifier()) 754 .dyn_cast_or_null<FlatSymbolRefAttr>(); 755 if (!attr) 756 return nullptr; 757 return lookupSymbol<FuncOp>(attr); 758 } 759 760 ParseResult parseFunctionLibraryOp(OpAsmParser &parser, 761 OperationState &result) { 762 // Parse the op name. 763 StringAttr nameAttr; 764 if (parser.parseSymbolName(nameAttr, ::mlir::SymbolTable::getSymbolAttrName(), 765 result.attributes)) 766 return failure(); 767 768 if (parser.parseOptionalAttrDictWithKeyword(result.attributes)) 769 return failure(); 770 771 auto *bodyRegion = result.addRegion(); 772 if (parser.parseRegion(*bodyRegion)) 773 return failure(); 774 775 if (parser.parseKeyword("mapping")) 776 return failure(); 777 778 DictionaryAttr mappingAttr; 779 if (parser.parseAttribute(mappingAttr, 780 parser.getBuilder().getType<NoneType>(), "mapping", 781 result.attributes)) 782 return failure(); 783 return success(); 784 } 785 786 void print(OpAsmPrinter &p, FunctionLibraryOp op) { 787 p << op.getOperationName() << ' '; 788 p.printSymbolName(op.getName()); 789 p.printOptionalAttrDictWithKeyword( 790 op->getAttrs(), {SymbolTable::getSymbolAttrName(), "mapping"}); 791 p.printRegion(op.getOperation()->getRegion(0), /*printEntryBlockArgs=*/false, 792 /*printBlockTerminators=*/false); 793 p << " mapping "; 794 p.printAttributeWithoutType(op.mappingAttr()); 795 } 796 797 //===----------------------------------------------------------------------===// 798 // GetExtentOp 799 //===----------------------------------------------------------------------===// 800 801 Optional<int64_t> GetExtentOp::getConstantDim() { 802 if (auto constSizeOp = dim().getDefiningOp<ConstSizeOp>()) 803 return constSizeOp.value().getLimitedValue(); 804 if (auto constantOp = dim().getDefiningOp<ConstantOp>()) 805 return constantOp.value().cast<IntegerAttr>().getInt(); 806 return llvm::None; 807 } 808 809 OpFoldResult GetExtentOp::fold(ArrayRef<Attribute> operands) { 810 auto elements = operands[0].dyn_cast_or_null<DenseIntElementsAttr>(); 811 if (!elements) 812 return nullptr; 813 Optional<int64_t> dim = getConstantDim(); 814 if (!dim.hasValue()) 815 return nullptr; 816 if (dim.getValue() >= elements.getNumElements()) 817 return nullptr; 818 return elements.getValue({(uint64_t)dim.getValue()}); 819 } 820 821 void GetExtentOp::build(OpBuilder &builder, OperationState &result, Value shape, 822 int64_t dim) { 823 auto loc = result.location; 824 auto dimAttr = builder.getIndexAttr(dim); 825 if (shape.getType().isa<ShapeType>()) { 826 Value dim = builder.create<ConstSizeOp>(loc, dimAttr); 827 build(builder, result, builder.getType<SizeType>(), shape, dim); 828 } else { 829 Value dim = 830 builder.create<ConstantOp>(loc, builder.getIndexType(), dimAttr); 831 build(builder, result, builder.getIndexType(), shape, dim); 832 } 833 } 834 835 //===----------------------------------------------------------------------===// 836 // IsBroadcastableOp 837 //===----------------------------------------------------------------------===// 838 839 void IsBroadcastableOp::getCanonicalizationPatterns(RewritePatternSet &patterns, 840 MLIRContext *context) { 841 patterns.add<RemoveDuplicateOperandsPattern<IsBroadcastableOp>>(context); 842 } 843 844 OpFoldResult IsBroadcastableOp::fold(ArrayRef<Attribute> operands) { 845 // Can always broadcast fewer than two shapes. 846 if (operands.size() < 2) { 847 return BoolAttr::get(getContext(), true); 848 } 849 850 return nullptr; 851 } 852 853 //===----------------------------------------------------------------------===// 854 // RankOp 855 //===----------------------------------------------------------------------===// 856 857 OpFoldResult shape::RankOp::fold(ArrayRef<Attribute> operands) { 858 auto shape = operands[0].dyn_cast_or_null<DenseIntElementsAttr>(); 859 if (!shape) 860 return {}; 861 int64_t rank = shape.getNumElements(); 862 Builder builder(getContext()); 863 return builder.getIndexAttr(rank); 864 } 865 866 /// Evaluate the `rank` operation for shapes of ranked tensors at compile time. 867 /// Constant folding fails in cases where only the rank is constant, not the 868 /// shape itself. 869 /// This canonicalization matches `shape.rank(shape.shape_of(%ranked_tensor))`. 870 /// 871 /// Example: 872 /// 873 /// %shape = shape.shape_of %ranked_tensor : tensor<1x2x?xf32> 874 /// %rank = shape.rank %shape 875 /// 876 /// becomes 877 /// 878 /// %rank = shape.const_size 3 879 880 namespace { 881 struct RankShapeOfCanonicalizationPattern 882 : public OpRewritePattern<shape::RankOp> { 883 using OpRewritePattern<shape::RankOp>::OpRewritePattern; 884 885 LogicalResult matchAndRewrite(shape::RankOp op, 886 PatternRewriter &rewriter) const override { 887 auto shapeOfOp = op.shape().getDefiningOp<ShapeOfOp>(); 888 if (!shapeOfOp) 889 return failure(); 890 auto rankedTensorType = 891 shapeOfOp.arg().getType().dyn_cast<RankedTensorType>(); 892 if (!rankedTensorType) 893 return failure(); 894 int64_t rank = rankedTensorType.getRank(); 895 if (op.getType().isa<IndexType>()) { 896 rewriter.replaceOpWithNewOp<ConstantIndexOp>(op.getOperation(), rank); 897 } else if (op.getType().isa<shape::SizeType>()) { 898 rewriter.replaceOpWithNewOp<shape::ConstSizeOp>(op.getOperation(), rank); 899 } else { 900 return failure(); 901 } 902 return success(); 903 } 904 }; 905 } // namespace 906 907 void shape::RankOp::getCanonicalizationPatterns(RewritePatternSet &patterns, 908 MLIRContext *context) { 909 patterns.add<RankShapeOfCanonicalizationPattern>(context); 910 } 911 912 //===----------------------------------------------------------------------===// 913 // NumElementsOp 914 //===----------------------------------------------------------------------===// 915 916 OpFoldResult NumElementsOp::fold(ArrayRef<Attribute> operands) { 917 918 // Fold only when argument constant. 919 Attribute shape = operands[0]; 920 if (!shape) 921 return {}; 922 923 APInt product(64, 1); 924 for (auto value : shape.cast<DenseIntElementsAttr>()) 925 product *= value; 926 Builder builder(getContext()); 927 return builder.getIndexAttr(product.getLimitedValue()); 928 } 929 930 void NumElementsOp::build(OpBuilder &builder, OperationState &result, 931 Value shape) { 932 if (shape.getType().isa<ShapedType>()) { 933 auto type = builder.getIndexType(); 934 return build(builder, result, type, shape); 935 } 936 auto type = SizeType::get(builder.getContext()); 937 return build(builder, result, type, shape); 938 } 939 940 //===----------------------------------------------------------------------===// 941 // MulOp 942 //===----------------------------------------------------------------------===// 943 944 OpFoldResult MulOp::fold(ArrayRef<Attribute> operands) { 945 auto lhs = operands[0].dyn_cast_or_null<IntegerAttr>(); 946 if (!lhs) 947 return nullptr; 948 auto rhs = operands[1].dyn_cast_or_null<IntegerAttr>(); 949 if (!rhs) 950 return nullptr; 951 APInt folded = lhs.getValue() * rhs.getValue(); 952 Type indexTy = IndexType::get(getContext()); 953 return IntegerAttr::get(indexTy, folded); 954 } 955 956 //===----------------------------------------------------------------------===// 957 // ShapeOfOp 958 //===----------------------------------------------------------------------===// 959 960 OpFoldResult ShapeOfOp::fold(ArrayRef<Attribute>) { 961 auto type = getOperand().getType().dyn_cast<ShapedType>(); 962 if (!type || !type.hasStaticShape()) 963 return nullptr; 964 Builder builder(getContext()); 965 return builder.getIndexTensorAttr(type.getShape()); 966 } 967 968 void ShapeOfOp::build(OpBuilder &builder, OperationState &result, Value arg) { 969 Type type = arg.getType().isa<ShapedType>() 970 ? (Type)getExtentTensorType(builder.getContext()) 971 : (Type)builder.getType<ShapeType>(); 972 return ShapeOfOp::build(builder, result, type, arg); 973 } 974 975 namespace { 976 struct ShapeOfWithTensor : public OpRewritePattern<shape::ShapeOfOp> { 977 using OpRewritePattern<shape::ShapeOfOp>::OpRewritePattern; 978 979 LogicalResult matchAndRewrite(shape::ShapeOfOp op, 980 PatternRewriter &rewriter) const override { 981 if (!op.arg().getType().isa<ShapedType>()) 982 return failure(); 983 if (op.getType().isa<ShapedType>()) 984 return failure(); 985 986 rewriter.replaceOpWithNewOp<shape::ShapeOfOp>(op.getOperation(), op.arg()); 987 return success(); 988 } 989 }; 990 991 // Canonicalize 992 // ``` 993 // %0 = shape.shape_of %arg : tensor<?x?x?xf32> -> tensor<3xindex> 994 // %1 = tensor.cast %0 : tensor<3xindex> to tensor<?xindex> 995 // ``` 996 // to 997 // ``` 998 // %1 = shape.shape_of %arg : tensor<?x?x?xf32> -> tensor<?xindex> 999 // ``` 1000 struct ShapeOfCastedExtentTensor : public OpRewritePattern<tensor::CastOp> { 1001 using OpRewritePattern<tensor::CastOp>::OpRewritePattern; 1002 1003 LogicalResult matchAndRewrite(tensor::CastOp op, 1004 PatternRewriter &rewriter) const override { 1005 auto ty = op.getType().dyn_cast<RankedTensorType>(); 1006 if (!ty || ty.getRank() != 1) 1007 return failure(); 1008 1009 auto shapeOfOp = op.source().getDefiningOp<ShapeOfOp>(); 1010 if (!shapeOfOp) 1011 return failure(); 1012 1013 // Argument type must be ranked and must not conflict. 1014 auto argTy = shapeOfOp.arg().getType().dyn_cast<RankedTensorType>(); 1015 if (!argTy || (!ty.isDynamicDim(0) && ty.getDimSize(0) != argTy.getRank())) 1016 return failure(); 1017 1018 rewriter.replaceOpWithNewOp<ShapeOfOp>(op, ty, shapeOfOp.arg()); 1019 return success(); 1020 } 1021 }; 1022 } // namespace 1023 1024 void ShapeOfOp::getCanonicalizationPatterns(RewritePatternSet &patterns, 1025 MLIRContext *context) { 1026 patterns.add<ShapeOfCastedExtentTensor, ShapeOfWithTensor>(context); 1027 } 1028 1029 //===----------------------------------------------------------------------===// 1030 // SizeToIndexOp 1031 //===----------------------------------------------------------------------===// 1032 1033 OpFoldResult SizeToIndexOp::fold(ArrayRef<Attribute> operands) { 1034 // Constant values of both types, `shape.size` and `index`, are represented as 1035 // `IntegerAttr`s which makes constant folding simple. 1036 if (Attribute arg = operands[0]) 1037 return arg; 1038 return impl::foldCastOp(*this); 1039 } 1040 1041 void SizeToIndexOp::getCanonicalizationPatterns(RewritePatternSet &patterns, 1042 MLIRContext *context) { 1043 patterns.add<IndexToSizeToIndexCanonicalization>(context); 1044 } 1045 1046 //===----------------------------------------------------------------------===// 1047 // YieldOp 1048 //===----------------------------------------------------------------------===// 1049 1050 static LogicalResult verify(shape::YieldOp op) { 1051 auto *parentOp = op->getParentOp(); 1052 auto results = parentOp->getResults(); 1053 auto operands = op.getOperands(); 1054 1055 if (parentOp->getNumResults() != op.getNumOperands()) 1056 return op.emitOpError() << "number of operands does not match number of " 1057 "results of its parent"; 1058 for (auto e : llvm::zip(results, operands)) 1059 if (std::get<0>(e).getType() != std::get<1>(e).getType()) 1060 return op.emitOpError() 1061 << "types mismatch between yield op and its parent"; 1062 1063 return success(); 1064 } 1065 1066 //===----------------------------------------------------------------------===// 1067 // SplitAtOp 1068 //===----------------------------------------------------------------------===// 1069 1070 LogicalResult SplitAtOp::fold(ArrayRef<Attribute> operands, 1071 SmallVectorImpl<OpFoldResult> &results) { 1072 if (!operands[0] || !operands[1]) 1073 return failure(); 1074 auto shapeVec = llvm::to_vector<6>( 1075 operands[0].cast<DenseIntElementsAttr>().getValues<int64_t>()); 1076 auto shape = llvm::makeArrayRef(shapeVec); 1077 auto splitPoint = operands[1].cast<IntegerAttr>().getInt(); 1078 // Verify that the split point is in the correct range. 1079 // TODO: Constant fold to an "error". 1080 int64_t rank = shape.size(); 1081 if (!(-rank <= splitPoint && splitPoint <= rank)) 1082 return failure(); 1083 if (splitPoint < 0) 1084 splitPoint += shape.size(); 1085 Builder builder(operands[0].getContext()); 1086 results.push_back(builder.getIndexTensorAttr(shape.take_front(splitPoint))); 1087 results.push_back(builder.getIndexTensorAttr(shape.drop_front(splitPoint))); 1088 return success(); 1089 } 1090 1091 //===----------------------------------------------------------------------===// 1092 // ToExtentTensorOp 1093 //===----------------------------------------------------------------------===// 1094 1095 OpFoldResult ToExtentTensorOp::fold(ArrayRef<Attribute> operands) { 1096 if (!operands[0]) 1097 return impl::foldCastOp(*this); 1098 Builder builder(getContext()); 1099 auto shape = llvm::to_vector<6>( 1100 operands[0].cast<DenseIntElementsAttr>().getValues<int64_t>()); 1101 auto type = RankedTensorType::get({static_cast<int64_t>(shape.size())}, 1102 builder.getIndexType()); 1103 return DenseIntElementsAttr::get(type, shape); 1104 } 1105 1106 //===----------------------------------------------------------------------===// 1107 // ReduceOp 1108 //===----------------------------------------------------------------------===// 1109 1110 void ReduceOp::build(OpBuilder &builder, OperationState &result, Value shape, 1111 ValueRange initVals) { 1112 result.addOperands(shape); 1113 result.addOperands(initVals); 1114 1115 Region *bodyRegion = result.addRegion(); 1116 bodyRegion->push_back(new Block); 1117 Block &bodyBlock = bodyRegion->front(); 1118 bodyBlock.addArgument(builder.getIndexType()); 1119 1120 Type elementType; 1121 if (auto tensorType = shape.getType().dyn_cast<TensorType>()) 1122 elementType = tensorType.getElementType(); 1123 else 1124 elementType = SizeType::get(builder.getContext()); 1125 bodyBlock.addArgument(elementType); 1126 1127 for (Type initValType : initVals.getTypes()) { 1128 bodyBlock.addArgument(initValType); 1129 result.addTypes(initValType); 1130 } 1131 } 1132 1133 static LogicalResult verify(ReduceOp op) { 1134 // Verify block arg types. 1135 Block &block = op.region().front(); 1136 1137 // The block takes index, extent, and aggregated values as arguments. 1138 auto blockArgsCount = op.initVals().size() + 2; 1139 if (block.getNumArguments() != blockArgsCount) 1140 return op.emitOpError() << "ReduceOp body is expected to have " 1141 << blockArgsCount << " arguments"; 1142 1143 // The first block argument is the index and must always be of type `index`. 1144 if (!block.getArgument(0).getType().isa<IndexType>()) 1145 return op.emitOpError( 1146 "argument 0 of ReduceOp body is expected to be of IndexType"); 1147 1148 // The second block argument is the extent and must be of type `size` or 1149 // `index`, depending on whether the reduce operation is applied to a shape or 1150 // to an extent tensor. 1151 Type extentTy = block.getArgument(1).getType(); 1152 if (op.shape().getType().isa<ShapeType>()) { 1153 if (!extentTy.isa<SizeType>()) 1154 return op.emitOpError("argument 1 of ReduceOp body is expected to be of " 1155 "SizeType if the ReduceOp operates on a ShapeType"); 1156 } else { 1157 if (!extentTy.isa<IndexType>()) 1158 return op.emitOpError( 1159 "argument 1 of ReduceOp body is expected to be of IndexType if the " 1160 "ReduceOp operates on an extent tensor"); 1161 } 1162 1163 for (auto type : llvm::enumerate(op.initVals())) 1164 if (block.getArgument(type.index() + 2).getType() != type.value().getType()) 1165 return op.emitOpError() 1166 << "type mismatch between argument " << type.index() + 2 1167 << " of ReduceOp body and initial value " << type.index(); 1168 return success(); 1169 } 1170 1171 static ParseResult parseReduceOp(OpAsmParser &parser, OperationState &result) { 1172 // Parse operands. 1173 SmallVector<OpAsmParser::OperandType, 3> operands; 1174 Type shapeOrExtentTensorType; 1175 if (parser.parseOperandList(operands, /*requiredOperandCount=*/-1, 1176 OpAsmParser::Delimiter::Paren) || 1177 parser.parseColonType(shapeOrExtentTensorType) || 1178 parser.parseOptionalArrowTypeList(result.types)) 1179 return failure(); 1180 1181 // Resolve operands. 1182 auto initVals = llvm::makeArrayRef(operands).drop_front(); 1183 if (parser.resolveOperand(operands.front(), shapeOrExtentTensorType, 1184 result.operands) || 1185 parser.resolveOperands(initVals, result.types, parser.getNameLoc(), 1186 result.operands)) 1187 return failure(); 1188 1189 // Parse the body. 1190 Region *body = result.addRegion(); 1191 if (parser.parseRegion(*body, /*args=*/{}, /*argTypes=*/{})) 1192 return failure(); 1193 1194 // Parse attributes. 1195 if (parser.parseOptionalAttrDict(result.attributes)) 1196 return failure(); 1197 1198 return success(); 1199 } 1200 1201 static void print(OpAsmPrinter &p, ReduceOp op) { 1202 p << op.getOperationName() << '(' << op.shape() << ", " << op.initVals() 1203 << ") : " << op.shape().getType(); 1204 p.printOptionalArrowTypeList(op.getResultTypes()); 1205 p.printRegion(op.region()); 1206 p.printOptionalAttrDict(op->getAttrs()); 1207 } 1208 1209 #define GET_OP_CLASSES 1210 #include "mlir/Dialect/Shape/IR/ShapeOps.cpp.inc" 1211