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