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