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