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