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