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