1 //===- LinalgOps.cpp - Implementation of the linalg 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 // This file implements the Linalg operations. 10 // 11 //===----------------------------------------------------------------------===// 12 13 #include "mlir/Dialect/Linalg/IR/Linalg.h" 14 15 #include "mlir/Dialect/SCF/SCF.h" 16 #include "mlir/Dialect/StandardOps/Utils/Utils.h" 17 #include "mlir/Dialect/Utils/ReshapeOpsUtils.h" 18 #include "mlir/Dialect/Utils/StaticValueUtils.h" 19 #include "mlir/IR/AffineExprVisitor.h" 20 #include "mlir/IR/Matchers.h" 21 #include "mlir/IR/OpImplementation.h" 22 #include "mlir/IR/PatternMatch.h" 23 #include "mlir/Interfaces/InferTypeOpInterface.h" 24 #include "mlir/Parser.h" 25 26 #include "llvm/ADT/DenseMap.h" 27 #include "llvm/ADT/SetVector.h" 28 #include "llvm/ADT/SmallSet.h" 29 #include "llvm/ADT/StringSet.h" 30 #include "llvm/ADT/TypeSwitch.h" 31 #include "llvm/Support/FormatVariadic.h" 32 #include "llvm/Support/MathExtras.h" 33 #include "llvm/Support/raw_ostream.h" 34 35 using namespace mlir; 36 using namespace mlir::linalg; 37 38 #include "mlir/Dialect/Linalg/IR/LinalgOpsDialect.cpp.inc" 39 40 /// Forward declarations. 41 42 /// Generic entry point to create the block for the region of a LinalgOp. 43 /// This is used by both named structured ops created by ods-gen and by manually 44 /// defined C++ ops. 45 /// This is used by both builders and parsers. 46 /// This function creates the block in the region with arguments corresponding 47 /// to the elemental types of `inputTypes` and `outputTypes`. The latter are 48 /// asserted to be of ShapedType. 49 template <typename NamedStructuredOpType> 50 static void fillStructuredOpRegion( 51 OpBuilder &opBuilder, Region ®ion, TypeRange inputTypes, 52 TypeRange outputTypes, 53 llvm::function_ref<void(unsigned, unsigned)> errorHandler = nullptr); 54 55 /// Generic entry point to create both the region and the block of a LinalgOp. 56 template <typename NamedStructuredOpType> 57 static void 58 createAndFillStructuredOpRegion(OpBuilder &opBuilder, OperationState &result, 59 TypeRange inputTypes, TypeRange outputTypes); 60 61 /// Common parsing and printing used for both named structured ops created by 62 /// ods-gen and by manually defined C++ ops. Does not handle regions. 63 static ParseResult 64 parseCommonStructuredOpParts(OpAsmParser &parser, OperationState &result, 65 SmallVectorImpl<Type> &inputTypes, 66 SmallVectorImpl<Type> &outputTypes); 67 template <typename NamedStructuredOpType> 68 static void printCommonStructuredOpParts(OpAsmPrinter &p, 69 NamedStructuredOpType op); 70 71 /// Specific parsing and printing for named structured ops created by ods-gen. 72 template <typename NamedStructuredOpType> 73 static ParseResult 74 parseNamedStructuredOpRegion(OpAsmParser &parser, Region ®ion, 75 TypeRange inputTypes, TypeRange outputTypes); 76 77 static ParseResult 78 parseNamedStructuredOpResults(OpAsmParser &parser, 79 SmallVectorImpl<Type> &resultTypes); 80 81 template <typename NamedStructuredOpType> 82 static ParseResult parseNamedStructuredOp(OpAsmParser &parser, 83 OperationState &result); 84 85 static void printNamedStructuredOpResults(OpAsmPrinter &p, 86 TypeRange resultTypes); 87 88 template <typename NamedStructuredOpType> 89 static void printNamedStructuredOp(OpAsmPrinter &p, NamedStructuredOpType op); 90 91 /// This is a common class used for patterns of the form 92 /// ``` 93 /// someop(memrefcast(%src)) -> someop(%src) 94 /// ``` 95 /// It folds the source of the memref.cast into the root operation directly. 96 static LogicalResult foldMemRefCast(Operation *op) { 97 bool folded = false; 98 for (OpOperand &operand : op->getOpOperands()) { 99 auto castOp = operand.get().getDefiningOp<memref::CastOp>(); 100 if (castOp && memref::CastOp::canFoldIntoConsumerOp(castOp)) { 101 operand.set(castOp.getOperand()); 102 folded = true; 103 } 104 } 105 return success(folded); 106 } 107 108 /// This is a specialization of `foldMemRefCast` used for patterns of the form 109 /// ``` 110 /// tiled_loop(memrefcast(%src)) -> tiled_loop(%src) 111 /// ``` 112 /// It folds the source of the memref.cast into the root operation directly. 113 static LogicalResult foldMemRefCastInTiledLoopOp(TiledLoopOp op) { 114 bool folded = false; 115 Location loc = op->getLoc(); 116 117 Block *body = op.getBody(); 118 OpBuilder b = OpBuilder::atBlockBegin(body); 119 120 // Update `input` and `output` operands and block arguments if necessary. 121 // Operands list: [lbs, ubs, steps, inputs, outputs]. 122 // Block args list: [ivs, inputs, outputs]. 123 for (size_t operandIndex = op.getNumControlOperands(), 124 bbArgIndex = op.getNumLoops(), e = op.getNumOperands(); 125 operandIndex < e; ++operandIndex, ++bbArgIndex) { 126 OpOperand &operand = op->getOpOperand(operandIndex); 127 128 auto castOp = operand.get().getDefiningOp<memref::CastOp>(); 129 if (castOp && memref::CastOp::canFoldIntoConsumerOp(castOp)) { 130 operand.set(castOp.getOperand()); 131 BlockArgument newBbArg = body->insertArgument( 132 bbArgIndex, castOp.getOperand().getType(), op.getLoc()); 133 BlockArgument oldBbArg = body->getArgument(newBbArg.getArgNumber() + 1); 134 135 // Insert memref.cast back to the original type. 136 oldBbArg.replaceAllUsesWith( 137 b.create<memref::CastOp>(loc, oldBbArg.getType(), newBbArg)); 138 body->eraseArgument(oldBbArg.getArgNumber()); 139 140 folded = true; 141 } 142 } 143 return success(folded); 144 } 145 146 //===----------------------------------------------------------------------===// 147 // Region builder helper. 148 // TODO: Move this to a utility library. 149 // The public methods on this class are referenced directly from generated code 150 // and bind by name to math and type conversion functions in the DSL as: 151 // `arithfn__{fnName}` 152 // `typefn__{fnName}` 153 // Examples: 154 // `arithfn__add` 155 // `arithfn__mul` 156 // `typefn__cast` 157 // The naming convention is intentional in order to match snake-cased DSL names. 158 // See mlir-linalg-ods-yaml-gen.cpp for the code that mates to this class. 159 // 160 // Implementations of the math functions must be polymorphic over numeric types, 161 // internally performing necessary casts. If the function application makes no 162 // sense, then the only recourse is to assert and return nullptr. This can be 163 // extended later if it becomes possible to fail construction of the region. The 164 // invariant should be enforced at a higher level. 165 // 166 // TODO: These helpers are currently type polymorphic over the class of integer 167 // and floating point types, but they will not internally cast within bit 168 // widths of a class (mixed precision such as i8->i32) or across classes 169 // (i.e. mixed float and integer). Many such combinations are ambiguous or need 170 // to be handled with care and work is being considered to extend the op 171 // language to make such cases explicit. In the mean-time, violating this will 172 // fail verification, which is deemed acceptable. 173 //===----------------------------------------------------------------------===// 174 175 namespace { 176 177 class RegionBuilderHelper { 178 public: 179 RegionBuilderHelper(MLIRContext *context, Block &block) 180 : context(context), block(block) {} 181 182 // Generates operations to cast the given operand to a specified type. 183 // If the cast cannot be performed, a warning will be issued and the 184 // operand returned as-is (which will presumably yield a verification 185 // issue downstream). 186 Value cast(Type toType, Value operand, bool isUnsignedCast) { 187 OpBuilder builder = getBuilder(); 188 auto loc = operand.getLoc(); 189 190 if (operand.getType() == toType) 191 return operand; 192 if (auto toIntType = toType.dyn_cast<IntegerType>()) { 193 // If operand is floating point, cast directly to the int type. 194 if (operand.getType().isa<FloatType>()) { 195 if (isUnsignedCast) 196 return builder.create<arith::FPToUIOp>(loc, toType, operand); 197 return builder.create<arith::FPToSIOp>(loc, toType, operand); 198 } 199 // Cast index operands directly to the int type. 200 if (operand.getType().isIndex()) 201 return builder.create<arith::IndexCastOp>(loc, toType, operand); 202 if (auto fromIntType = operand.getType().dyn_cast<IntegerType>()) { 203 // Either extend or truncate. 204 if (toIntType.getWidth() > fromIntType.getWidth()) { 205 if (isUnsignedCast) 206 return builder.create<arith::ExtUIOp>(loc, toType, operand); 207 return builder.create<arith::ExtSIOp>(loc, toType, operand); 208 } 209 if (toIntType.getWidth() < fromIntType.getWidth()) 210 return builder.create<arith::TruncIOp>(loc, toType, operand); 211 } 212 } else if (auto toFloatType = toType.dyn_cast<FloatType>()) { 213 // If operand is integer, cast directly to the float type. 214 // Note that it is unclear how to cast from BF16<->FP16. 215 if (operand.getType().isa<IntegerType>()) { 216 if (isUnsignedCast) 217 return builder.create<arith::UIToFPOp>(loc, toFloatType, operand); 218 return builder.create<arith::SIToFPOp>(loc, toFloatType, operand); 219 } 220 if (auto fromFloatType = operand.getType().dyn_cast<FloatType>()) { 221 if (toFloatType.getWidth() > fromFloatType.getWidth()) 222 return builder.create<arith::ExtFOp>(loc, toFloatType, operand); 223 if (toFloatType.getWidth() < fromFloatType.getWidth()) 224 return builder.create<arith::TruncFOp>(loc, toFloatType, operand); 225 } 226 } 227 228 emitWarning(operand.getLoc()) << "could not cast operand of type " 229 << operand.getType() << " to " << toType; 230 return operand; 231 } 232 233 // NOLINTNEXTLINE(*-identifier-naming): externally called. 234 Value typefn__cast(Type toType, Value operand) { 235 return cast(toType, operand, false); 236 } 237 238 // NOLINTNEXTLINE(*-identifier-naming): externally called. 239 Value typefn__cast_unsigned(Type toType, Value operand) { 240 return cast(toType, operand, true); 241 } 242 243 // NOLINTNEXTLINE(*-identifier-naming): externally called. 244 Value arithfn__add(Value lhs, Value rhs) { 245 OpBuilder builder = getBuilder(); 246 if (isFloatingPoint(lhs)) 247 return builder.create<arith::AddFOp>(lhs.getLoc(), lhs, rhs); 248 if (isInteger(lhs)) 249 return builder.create<arith::AddIOp>(lhs.getLoc(), lhs, rhs); 250 llvm_unreachable("unsupported non numeric type"); 251 } 252 253 // NOLINTNEXTLINE(*-identifier-naming): externally called. 254 Value arithfn__exp(Value x) { 255 OpBuilder builder = getBuilder(); 256 if (isFloatingPoint(x)) 257 return builder.create<math::ExpOp>(x.getLoc(), x); 258 llvm_unreachable("unsupported non numeric type"); 259 } 260 261 // NOLINTNEXTLINE(*-identifier-naming): externally called. 262 Value arithfn__log(Value x) { 263 OpBuilder builder = getBuilder(); 264 if (isFloatingPoint(x)) 265 return builder.create<math::LogOp>(x.getLoc(), x); 266 llvm_unreachable("unsupported non numeric type"); 267 } 268 269 // NOLINTNEXTLINE(*-identifier-naming): externally called. 270 Value arithfn__sub(Value lhs, Value rhs) { 271 OpBuilder builder = getBuilder(); 272 if (isFloatingPoint(lhs)) 273 return builder.create<arith::SubFOp>(lhs.getLoc(), lhs, rhs); 274 if (isInteger(lhs)) 275 return builder.create<arith::SubIOp>(lhs.getLoc(), lhs, rhs); 276 llvm_unreachable("unsupported non numeric type"); 277 } 278 279 // NOLINTNEXTLINE(*-identifier-naming): externally called. 280 Value arithfn__mul(Value lhs, Value rhs) { 281 OpBuilder builder = getBuilder(); 282 if (isFloatingPoint(lhs)) 283 return builder.create<arith::MulFOp>(lhs.getLoc(), lhs, rhs); 284 if (isInteger(lhs)) 285 return builder.create<arith::MulIOp>(lhs.getLoc(), lhs, rhs); 286 llvm_unreachable("unsupported non numeric type"); 287 } 288 289 // NOLINTNEXTLINE(*-identifier-naming): externally called. 290 Value arithfn__max(Value lhs, Value rhs) { 291 OpBuilder builder = getBuilder(); 292 if (isFloatingPoint(lhs)) 293 return builder.create<arith::MaxFOp>(lhs.getLoc(), lhs, rhs); 294 if (isInteger(lhs)) 295 return builder.create<arith::MaxSIOp>(lhs.getLoc(), lhs, rhs); 296 llvm_unreachable("unsupported non numeric type"); 297 } 298 299 // NOLINTNEXTLINE(*-identifier-naming): externally called. 300 Value arithfn__max_unsigned(Value lhs, Value rhs) { 301 OpBuilder builder = getBuilder(); 302 if (isFloatingPoint(lhs)) 303 return builder.create<arith::MaxFOp>(lhs.getLoc(), lhs, rhs); 304 if (isInteger(lhs)) 305 return builder.create<arith::MaxUIOp>(lhs.getLoc(), lhs, rhs); 306 llvm_unreachable("unsupported non numeric type"); 307 } 308 309 // NOLINTNEXTLINE(*-identifier-naming): externally called. 310 Value arithfn__min(Value lhs, Value rhs) { 311 OpBuilder builder = getBuilder(); 312 if (isFloatingPoint(lhs)) 313 return builder.create<arith::MinFOp>(lhs.getLoc(), lhs, rhs); 314 if (isInteger(lhs)) 315 return builder.create<arith::MinSIOp>(lhs.getLoc(), lhs, rhs); 316 llvm_unreachable("unsupported non numeric type"); 317 } 318 319 // NOLINTNEXTLINE(*-identifier-naming): externally called. 320 Value arithfn__min_unsigned(Value lhs, Value rhs) { 321 OpBuilder builder = getBuilder(); 322 if (isFloatingPoint(lhs)) 323 return builder.create<arith::MinFOp>(lhs.getLoc(), lhs, rhs); 324 if (isInteger(lhs)) 325 return builder.create<arith::MinUIOp>(lhs.getLoc(), lhs, rhs); 326 llvm_unreachable("unsupported non numeric type"); 327 } 328 329 void yieldOutputs(ValueRange values) { 330 assert(!values.empty() && "linalg ops must yield outputs"); 331 if (values.empty()) 332 return; 333 Value first = values.front(); 334 OpBuilder builder = getBuilder(); 335 builder.create<YieldOp>(first.getLoc(), values); 336 } 337 338 Value constant(const std::string &value) { 339 OpBuilder builder = getBuilder(); 340 Location loc = builder.getUnknownLoc(); 341 Attribute valueAttr = parseAttribute(value, builder.getContext()); 342 return builder.create<arith::ConstantOp>(loc, valueAttr.getType(), 343 valueAttr); 344 } 345 346 Value index(int64_t dim) { 347 OpBuilder builder = getBuilder(); 348 return builder.create<IndexOp>(builder.getUnknownLoc(), dim); 349 } 350 351 Type getIntegerType(unsigned width) { 352 return IntegerType::get(context, width); 353 } 354 355 Type getFloat32Type() { return Float32Type::get(context); } 356 357 Type getFloat64Type() { return Float64Type::get(context); } 358 359 private: 360 MLIRContext *context; 361 Block █ 362 363 bool isFloatingPoint(Value value) { return value.getType().isa<FloatType>(); } 364 bool isInteger(Value value) { return value.getType().isa<IntegerType>(); } 365 366 OpBuilder getBuilder() { 367 OpBuilder builder(context); 368 builder.setInsertionPointToEnd(&block); 369 return builder; 370 } 371 }; 372 373 } // namespace 374 375 //===----------------------------------------------------------------------===// 376 // CopyOp 377 //===----------------------------------------------------------------------===// 378 void CopyOp::regionBuilder(ImplicitLocOpBuilder &b, Block &block) { 379 assert(block.getNumArguments() == 2 && "CopyOp regionBuilder expects 2 args"); 380 b.create<linalg::YieldOp>(block.getArgument(0)); 381 } 382 383 void CopyOp::build(OpBuilder &builder, OperationState &result, Value input, 384 Value output, AffineMap inputPermutation, 385 AffineMap outputPermutation, 386 ArrayRef<NamedAttribute> namedAttrs) { 387 result.addOperands({input, output}); 388 result.addAttributes(namedAttrs); 389 if (inputPermutation) 390 result.addAttribute("inputPermutation", 391 AffineMapAttr::get(inputPermutation)); 392 if (outputPermutation) 393 result.addAttribute("outputPermutation", 394 AffineMapAttr::get(outputPermutation)); 395 result.addRegion(); 396 fillStructuredOpRegion<CopyOp>(builder, *result.regions.front(), 397 TypeRange{input.getType()}, 398 TypeRange{output.getType()}); 399 } 400 401 ParseResult parseCopyOpRegion(OpAsmParser &parser, Region &r, Type inputType, 402 Type outputType) { 403 OpBuilder opBuilder(parser.getContext()); 404 fillStructuredOpRegion<CopyOp>(opBuilder, r, TypeRange{inputType}, 405 TypeRange{outputType}); 406 return success(); 407 } 408 409 /// CopyOp region is elided when printing. 410 void printCopyOpRegion(OpAsmPrinter &, Operation *, Region &, Type, Type) {} 411 412 static LogicalResult verify(CopyOp op) { 413 OpOperand *output = op.getOutputOperand(0); 414 OpOperand *input = op.getInputOperand(0); 415 if (getElementTypeOrSelf(input->get()) != getElementTypeOrSelf(output->get())) 416 return op.emitOpError("expects views of the same type"); 417 if (op.getRank(input) != op.getRank(output)) 418 return op.emitOpError("expects views of the same rank"); 419 auto rank = op.getNumParallelLoops(); 420 auto inputPermutationMap = op.inputPermutation(); 421 if (inputPermutationMap) { 422 if (inputPermutationMap->getNumInputs() != rank) 423 return op.emitOpError("expects optional input_permutation map of rank ") 424 << rank; 425 if (!inputPermutationMap->isPermutation()) 426 return op.emitOpError( 427 "expects optional input_permutation map to be a permutation"); 428 } 429 auto outputPermutationMap = op.outputPermutation(); 430 if (outputPermutationMap) { 431 if (outputPermutationMap->getNumInputs() != rank) 432 return op.emitOpError("expects optional output_permutation map of rank ") 433 << rank; 434 if (!outputPermutationMap->isPermutation()) 435 return op.emitOpError( 436 "expects optional output_permutation map to be a permutation"); 437 } 438 if (rank == 0 && inputPermutationMap) 439 return op.emitOpError("expected no input permutation when rank == 0"); 440 if (rank == 0 && outputPermutationMap) 441 return op.emitOpError("expected no output permutation when rank == 0"); 442 return success(); 443 } 444 445 void CopyOp::getEffects( 446 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>> 447 &effects) { 448 effects.emplace_back(MemoryEffects::Read::get(), input(), 449 SideEffects::DefaultResource::get()); 450 effects.emplace_back(MemoryEffects::Write::get(), output(), 451 SideEffects::DefaultResource::get()); 452 } 453 454 namespace { 455 /// Remove copy operations that copy data inplace. Requirements are: 456 /// 1) The input and output values are identical. 457 /// 2) The input and output permutation maps are identical. 458 struct EraseIdentityCopyOp : public OpRewritePattern<CopyOp> { 459 using OpRewritePattern<CopyOp>::OpRewritePattern; 460 461 LogicalResult matchAndRewrite(CopyOp copyOp, 462 PatternRewriter &rewriter) const override { 463 assert(copyOp.hasBufferSemantics()); 464 if (copyOp.input() == copyOp.output() && 465 copyOp.inputPermutation() == copyOp.outputPermutation()) { 466 rewriter.eraseOp(copyOp); 467 return success(); 468 } 469 return failure(); 470 } 471 }; 472 } // namespace 473 474 void CopyOp::getCanonicalizationPatterns(RewritePatternSet &results, 475 MLIRContext *context) { 476 results.add<EraseIdentityCopyOp>(context); 477 } 478 479 //===----------------------------------------------------------------------===// 480 // FillOp 481 //===----------------------------------------------------------------------===// 482 void FillOp::regionBuilder(ImplicitLocOpBuilder &b, Block &block) { 483 assert(block.getNumArguments() == 2 && "FillOp regionBuilder expects 2 args"); 484 b.create<linalg::YieldOp>(block.getArgument(0)); 485 } 486 487 void FillOp::build(OpBuilder &builder, OperationState &result, Value value, 488 Value output) { 489 build(builder, result, output.getType().dyn_cast<RankedTensorType>(), value, 490 output); 491 fillStructuredOpRegion<FillOp>(builder, *result.regions.front(), 492 TypeRange{value.getType()}, 493 TypeRange{output.getType()}, {}); 494 } 495 496 ParseResult parseFillOpRegion(OpAsmParser &parser, Region &r, Type valueType, 497 Type outputType) { 498 OpBuilder opBuilder(parser.getContext()); 499 fillStructuredOpRegion<FillOp>(opBuilder, r, TypeRange{valueType}, 500 TypeRange{outputType}); 501 return success(); 502 } 503 504 /// FillOp region is elided when printing. 505 void printFillOpRegion(OpAsmPrinter &, Operation *, Region &, Type, Type) {} 506 507 static LogicalResult verify(FillOp op) { 508 OpOperand *output = op.getOutputOperand(0); 509 Type fillType = op.value().getType(); 510 if (getElementTypeOrSelf(output->get()) != fillType) 511 return op.emitOpError("expects fill type to match view elemental type"); 512 return success(); 513 } 514 515 void FillOp::getEffects( 516 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>> 517 &effects) { 518 if (output().getType().isa<MemRefType>()) 519 effects.emplace_back(MemoryEffects::Write::get(), output(), 520 SideEffects::DefaultResource::get()); 521 } 522 523 namespace { 524 525 /// Fold linalg.fill -> tensor.expand/collapse_shape chain. 526 /// 527 /// For such op chains, we can create new linalg.fill ops with the result 528 /// type of the tensor.expand/collapse_shape op. 529 template <typename TensorReshapeOp> 530 struct FoldFillWithTensorReshape : OpRewritePattern<TensorReshapeOp> { 531 using OpRewritePattern<TensorReshapeOp>::OpRewritePattern; 532 LogicalResult matchAndRewrite(TensorReshapeOp reshapeOp, 533 PatternRewriter &rewriter) const override { 534 auto oldFill = reshapeOp.src().template getDefiningOp<FillOp>(); 535 if (!oldFill) 536 return failure(); 537 538 Location loc = oldFill.getLoc(); 539 auto newInit = rewriter.create<TensorReshapeOp>( 540 loc, reshapeOp.getResultType(), oldFill.output(), 541 reshapeOp.reassociation()); 542 rewriter.replaceOpWithNewOp<FillOp>(reshapeOp, oldFill.value(), newInit); 543 544 return success(); 545 } 546 }; 547 548 } // namespace 549 550 void FillOp::getCanonicalizationPatterns(RewritePatternSet &results, 551 MLIRContext *context) { 552 results.add<FoldFillWithTensorReshape<tensor::CollapseShapeOp>, 553 FoldFillWithTensorReshape<tensor::ExpandShapeOp>>(context); 554 } 555 556 //===----------------------------------------------------------------------===// 557 // GenericOps 558 //===----------------------------------------------------------------------===// 559 void GenericOp::build( 560 OpBuilder &builder, OperationState &result, TypeRange resultTensorTypes, 561 ValueRange inputs, ValueRange outputs, ArrayRef<AffineMap> indexingMaps, 562 ArrayRef<StringRef> iteratorTypes, StringRef doc, StringRef libraryCall, 563 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild, 564 ArrayRef<NamedAttribute> attributes) { 565 build(builder, result, resultTensorTypes, inputs, outputs, 566 builder.getAffineMapArrayAttr(indexingMaps), 567 builder.getStrArrayAttr(iteratorTypes), 568 doc.empty() ? StringAttr() : builder.getStringAttr(doc), 569 libraryCall.empty() ? StringAttr() 570 : builder.getStringAttr(libraryCall)); 571 result.addAttributes(attributes); 572 if (!bodyBuild) 573 return; 574 575 SmallVector<Type, 4> blockArgTypes; 576 SmallVector<Location, 4> blockArgLocs; 577 for (ValueRange container : {inputs, outputs}) { 578 for (Value v : container) { 579 blockArgTypes.push_back(getElementTypeOrSelf(v)); 580 blockArgLocs.push_back(v.getLoc()); 581 } 582 } 583 584 OpBuilder::InsertionGuard guard(builder); 585 auto ®ion = *result.regions.front(); 586 Block *bodyBlock = 587 builder.createBlock(®ion, region.end(), blockArgTypes, blockArgLocs); 588 bodyBuild(builder, result.location, bodyBlock->getArguments()); 589 } 590 591 void GenericOp::build( 592 OpBuilder &builder, OperationState &result, ValueRange inputs, 593 ValueRange outputs, ArrayRef<AffineMap> indexingMaps, 594 ArrayRef<StringRef> iteratorTypes, StringRef doc, StringRef libraryCall, 595 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild, 596 ArrayRef<NamedAttribute> attributes) { 597 build(builder, result, TypeRange{}, inputs, outputs, indexingMaps, 598 iteratorTypes, doc, libraryCall, bodyBuild, attributes); 599 } 600 601 void GenericOp::build( 602 OpBuilder &builder, OperationState &result, ValueRange inputs, 603 ValueRange outputs, ArrayRef<AffineMap> indexingMaps, 604 ArrayRef<StringRef> iteratorTypes, 605 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild, 606 ArrayRef<NamedAttribute> attributes) { 607 build(builder, result, inputs, outputs, indexingMaps, iteratorTypes, 608 /*doc=*/"", 609 /*libraryCall=*/"", bodyBuild, attributes); 610 } 611 612 void GenericOp::build( 613 OpBuilder &builder, OperationState &result, TypeRange resultTensorTypes, 614 ValueRange inputs, ValueRange outputs, ArrayRef<AffineMap> indexingMaps, 615 ArrayRef<StringRef> iteratorTypes, 616 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild, 617 ArrayRef<NamedAttribute> attributes) { 618 build(builder, result, resultTensorTypes, inputs, outputs, indexingMaps, 619 iteratorTypes, 620 /*doc=*/"", 621 /*libraryCall=*/"", bodyBuild, attributes); 622 } 623 624 static void print(OpAsmPrinter &p, GenericOp op) { 625 p << " "; 626 627 // Print extra attributes. 628 auto genericAttrNames = op.linalgTraitAttrNames(); 629 630 llvm::StringSet<> genericAttrNamesSet; 631 genericAttrNamesSet.insert(genericAttrNames.begin(), genericAttrNames.end()); 632 SmallVector<NamedAttribute, 8> genericAttrs; 633 for (auto attr : op->getAttrs()) 634 if (genericAttrNamesSet.count(attr.getName().strref()) > 0) 635 genericAttrs.push_back(attr); 636 if (!genericAttrs.empty()) { 637 auto genericDictAttr = DictionaryAttr::get(op.getContext(), genericAttrs); 638 p << genericDictAttr; 639 } 640 641 // Printing is shared with named ops, except for the region and attributes 642 printCommonStructuredOpParts(p, op); 643 644 genericAttrNames.push_back("operand_segment_sizes"); 645 genericAttrNamesSet.insert(genericAttrNames.back()); 646 647 bool hasExtraAttrs = false; 648 for (NamedAttribute n : op->getAttrs()) { 649 if ((hasExtraAttrs = !genericAttrNamesSet.contains(n.getName().strref()))) 650 break; 651 } 652 if (hasExtraAttrs) { 653 p << " attrs = "; 654 p.printOptionalAttrDict(op->getAttrs(), /*elidedAttrs=*/genericAttrNames); 655 } 656 657 // Print region. 658 if (!op.region().empty()) { 659 p << ' '; 660 p.printRegion(op.region()); 661 } 662 663 // Print results. 664 printNamedStructuredOpResults(p, op.result_tensors().getTypes()); 665 } 666 667 static ParseResult parseGenericOp(OpAsmParser &parser, OperationState &result) { 668 DictionaryAttr dictAttr; 669 // Parse the core linalg traits that must check into a dictAttr. 670 // The name is unimportant as we will overwrite result.attributes. 671 // The core linalg traits must contain the information necessary to pass the 672 // verifier. 673 if (parser.parseAttribute(dictAttr, "_", result.attributes)) 674 return failure(); 675 result.attributes.assign(dictAttr.getValue().begin(), 676 dictAttr.getValue().end()); 677 678 // Parsing is shared with named ops, except for the region. 679 SmallVector<Type, 1> inputTypes, outputTypes; 680 if (parseCommonStructuredOpParts(parser, result, inputTypes, outputTypes)) 681 return failure(); 682 683 // Optional attributes may be added. 684 if (succeeded(parser.parseOptionalKeyword("attrs"))) 685 if (failed(parser.parseEqual()) || 686 failed(parser.parseOptionalAttrDict(result.attributes))) 687 return failure(); 688 689 SmallVector<OpAsmParser::OperandType, 8> regionOperands; 690 std::unique_ptr<Region> region = std::make_unique<Region>(); 691 SmallVector<Type, 8> operandTypes, regionTypes; 692 if (parser.parseRegion(*region, regionOperands, regionTypes)) 693 return failure(); 694 result.addRegion(std::move(region)); 695 696 // Generic ops may specify that a subset of its outputs are tensors. Such 697 // outputs are specified in the result type. 698 // TODO: may need to move output parsing before region parsing. 699 // Need to wait for declarative assembly resolution to decide. 700 SmallVector<Type, 1> outputTensorsTypes; 701 if (parseNamedStructuredOpResults(parser, outputTensorsTypes)) 702 return failure(); 703 result.addTypes(outputTensorsTypes); 704 705 return success(); 706 } 707 708 static void getGenericEffectsImpl( 709 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>> 710 &effects, 711 ValueRange results, ValueRange inputBuffers, ValueRange outputs) { 712 for (Value value : results) { 713 effects.emplace_back(MemoryEffects::Allocate::get(), value, 714 SideEffects::DefaultResource::get()); 715 } 716 for (Value value : inputBuffers) { 717 effects.emplace_back(MemoryEffects::Read::get(), value, 718 SideEffects::DefaultResource::get()); 719 } 720 for (Value value : outputs) { 721 effects.emplace_back(MemoryEffects::Read::get(), value, 722 SideEffects::DefaultResource::get()); 723 effects.emplace_back(MemoryEffects::Write::get(), value, 724 SideEffects::DefaultResource::get()); 725 } 726 } 727 728 void GenericOp::getEffects( 729 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>> 730 &effects) { 731 SmallVector<Value> inputBuffers = getInputBufferOperands(); 732 SmallVector<Value> outputBuffers = getOutputBufferOperands(); 733 getGenericEffectsImpl(effects, getOperation()->getResults(), inputBuffers, 734 outputBuffers); 735 } 736 737 template <typename GenericOpType> 738 static LogicalResult verifyGenericOp(GenericOpType op) { 739 return success(); 740 } 741 742 static LogicalResult verify(GenericOp op) { return verifyGenericOp(op); } 743 744 namespace { 745 // Deduplicate redundant args of a linalg generic op. 746 // An arg is redundant if it has the same Value and indexing map as another. 747 struct DeduplicateGenericOpInputs : public OpRewritePattern<GenericOp> { 748 using OpRewritePattern<GenericOp>::OpRewritePattern; 749 750 LogicalResult matchAndRewrite(GenericOp genericOp, 751 PatternRewriter &rewriter) const override { 752 // Associate each input to an equivalent "canonical" input that has the same 753 // Value and indexing map. 754 // 755 // In the non-duplicate case, input `i` will have canonical input `i`. But 756 // in the case of duplicated inputs, the canonical input could be some other 757 // input `< i`. That is, a later input will have some earlier input as its 758 // canonical input. 759 llvm::SmallDenseMap<std::pair<Value, AffineMap>, unsigned> canonicalInput; 760 // For later remapping tasks like deduplicating payload block arguments, 761 // having a simple "inputIndex -> canonicalInputIndex" integer mapping is 762 // convenient. 763 SmallVector<unsigned> canonicalInputIndices; 764 for (OpOperand *opOperand : genericOp.getInputOperands()) { 765 AffineMap indexingMap = genericOp.getTiedIndexingMap(opOperand); 766 // STL-like maps have a convenient behavior for our use case here. In the 767 // case of duplicate keys, the insertion is rejected, and the returned 768 // iterator gives access to the value already in the map. 769 auto pair = canonicalInput.insert( 770 {{opOperand->get(), indexingMap}, opOperand->getOperandNumber()}); 771 canonicalInputIndices.push_back(pair.first->second); 772 } 773 774 // If there are no duplicate args, then bail out. 775 if (canonicalInput.size() == genericOp.getNumInputs()) 776 return failure(); 777 778 // The operands for the newly canonicalized op. 779 SmallVector<Value> newInputOperands; 780 for (OpOperand *opOperand : genericOp.getInputOperands()) 781 if (canonicalInputIndices[opOperand->getOperandNumber()] == 782 opOperand->getOperandNumber()) 783 newInputOperands.push_back(opOperand->get()); 784 785 // Repair the indexing maps by filtering out the ones that have been 786 // eliminated. 787 SmallVector<AffineMap> newIndexingMaps; 788 for (OpOperand *opOperand : genericOp.getInputOperands()) 789 if (canonicalInputIndices[opOperand->getOperandNumber()] == 790 opOperand->getOperandNumber()) 791 newIndexingMaps.push_back(genericOp.getTiedIndexingMap(opOperand)); 792 for (OpOperand *opOperand : genericOp.getOutputOperands()) 793 newIndexingMaps.push_back(genericOp.getTiedIndexingMap(opOperand)); 794 795 // Clone the old op with new operands. 796 SmallVector<Value> outputOperands = genericOp.getOutputOperands(); 797 auto newOp = rewriter.create<GenericOp>( 798 genericOp.getLoc(), genericOp->getResultTypes(), newInputOperands, 799 outputOperands, rewriter.getAffineMapArrayAttr(newIndexingMaps), 800 genericOp.iterator_types(), genericOp.docAttr(), 801 genericOp.library_callAttr()); 802 803 // Copy over unknown attributes. They might be load bearing for some flow. 804 ArrayRef<StringRef> odsAttrs = genericOp.getAttributeNames(); 805 for (NamedAttribute kv : genericOp->getAttrs()) { 806 if (!llvm::is_contained(odsAttrs, kv.getName().getValue())) { 807 newOp->setAttr(kv.getName(), kv.getValue()); 808 } 809 } 810 811 rewriter.inlineRegionBefore(genericOp.region(), newOp.region(), 812 newOp.region().begin()); 813 814 // Repair the payload entry block by RAUW'ing redundant arguments and 815 // erasing them. 816 Block &payload = newOp.region().front(); 817 SmallVector<OpOperand *> inputOperands = genericOp.getInputOperands(); 818 for (OpOperand *opOperand : llvm::reverse(inputOperands)) { 819 // Iterate in reverse, so that we erase later args first, preventing the 820 // argument list from shifting unexpectedly and invalidating all our 821 // indices. 822 unsigned operandNumber = opOperand->getOperandNumber(); 823 if (canonicalInputIndices[operandNumber] == operandNumber) 824 continue; 825 payload.getArgument(operandNumber) 826 .replaceAllUsesWith( 827 payload.getArgument(canonicalInputIndices[operandNumber])); 828 payload.eraseArgument(operandNumber); 829 } 830 831 rewriter.replaceOp(genericOp, newOp->getResults()); 832 return success(); 833 } 834 }; 835 836 /// Remove generic operations (on tensors) that are just copying 837 /// the values from inputs to the results. Requirements are 838 /// 1) All iterator types are parallel 839 /// 2) The body contains just a yield operation with the yielded values being 840 /// the arguments corresponding to the operands. 841 struct EraseIdentityGenericOp : public OpRewritePattern<GenericOp> { 842 using OpRewritePattern<GenericOp>::OpRewritePattern; 843 844 LogicalResult matchAndRewrite(GenericOp genericOp, 845 PatternRewriter &rewriter) const override { 846 if (!genericOp.hasTensorSemantics()) 847 return failure(); 848 // Check all indexing maps are identity. 849 if (llvm::any_of(genericOp.getIndexingMaps(), 850 [](AffineMap map) { return !map.isIdentity(); })) 851 return failure(); 852 853 // Check that the body of the linalg operation is just a linalg.yield 854 // operation. 855 Block &body = genericOp.region().front(); 856 if (!llvm::hasSingleElement(body)) 857 return failure(); 858 auto yieldOp = dyn_cast<linalg::YieldOp>(body.getTerminator()); 859 if (!yieldOp) 860 return failure(); 861 862 // Get the argument number of the returned values. That is the operand 863 // number to use for replacing uses of this operation. 864 SmallVector<Value> returnedArgs; 865 for (const auto &yieldVal : llvm::enumerate(yieldOp.values())) { 866 auto yieldArg = yieldVal.value().dyn_cast<BlockArgument>(); 867 if (!yieldArg || yieldArg.getOwner() != &body) 868 return failure(); 869 unsigned argumentNumber = yieldArg.getArgNumber(); 870 Value returnedArg = genericOp->getOperand(argumentNumber); 871 Type resultType = genericOp->getResult(yieldVal.index()).getType(); 872 // The input can have a different type than the result, e.g. a dynamic 873 // input dimension can be turned into a static output dimension. 874 if (returnedArg.getType() != resultType) 875 returnedArg = rewriter.create<tensor::CastOp>(genericOp.getLoc(), 876 resultType, returnedArg); 877 returnedArgs.push_back(returnedArg); 878 } 879 if (returnedArgs.size() != genericOp->getNumResults()) 880 return failure(); 881 rewriter.replaceOp(genericOp, returnedArgs); 882 return success(); 883 } 884 }; 885 } // namespace 886 887 void GenericOp::getCanonicalizationPatterns(RewritePatternSet &results, 888 MLIRContext *context) { 889 results.add<DeduplicateGenericOpInputs, EraseIdentityGenericOp>(context); 890 } 891 892 //===----------------------------------------------------------------------===// 893 // InitTensorOp 894 //===----------------------------------------------------------------------===// 895 896 void InitTensorOp::build(OpBuilder &b, OperationState &result, 897 ArrayRef<OpFoldResult> sizes, Type elementType, 898 ArrayRef<NamedAttribute> attrs) { 899 SmallVector<Value, 4> dynamicSizes; 900 SmallVector<int64_t, 4> staticSizes; 901 dispatchIndexOpFoldResults(sizes, dynamicSizes, staticSizes, 902 ShapedType::kDynamicSize); 903 auto resultType = RankedTensorType ::get(staticSizes, elementType); 904 build(b, result, resultType, dynamicSizes, b.getI64ArrayAttr(staticSizes)); 905 result.addAttributes(attrs); 906 } 907 908 static LogicalResult verify(InitTensorOp op) { 909 RankedTensorType resultType = op.getType(); 910 SmallVector<int64_t, 4> staticSizes = llvm::to_vector<4>(llvm::map_range( 911 op.static_sizes().cast<ArrayAttr>(), 912 [](Attribute a) -> int64_t { return a.cast<IntegerAttr>().getInt(); })); 913 914 if (failed(verifyListOfOperandsOrIntegers(op, "sizes", resultType.getRank(), 915 op.static_sizes(), op.sizes(), 916 ShapedType::isDynamic))) 917 return failure(); 918 919 if (op.static_sizes().size() != static_cast<unsigned>(resultType.getRank())) 920 return op->emitError("expected ") 921 << resultType.getRank() << " sizes values"; 922 923 Type expectedType = InitTensorOp::inferResultType( 924 staticSizes, resultType.getElementType(), resultType.getEncoding()); 925 if (resultType != expectedType) { 926 return op.emitError("specified type ") 927 << resultType << " does not match the inferred type " 928 << expectedType; 929 } 930 return success(); 931 } 932 933 Type InitTensorOp::inferResultType(ArrayRef<int64_t> staticSizes, 934 Type elementType, Attribute encoding) { 935 return RankedTensorType::get(staticSizes, elementType, encoding); 936 } 937 938 namespace { 939 /// Change the type of the result of a `linalg.init_tensor` by making the result 940 /// type statically sized along dimension that in the original operation where 941 /// defined as dynamic, but the size was defined using a `constant` op. For 942 /// example 943 /// 944 /// %c5 = arith.constant 5: index 945 /// %0 = linalg.init_tensor [%arg0, %c5] : tensor<?x?xf32> 946 /// 947 /// to 948 /// 949 /// %0 = linalg.init_tensor [%arg0, 5] : tensor<?x5xf32> 950 struct ReplaceStaticShapeDims : OpRewritePattern<InitTensorOp> { 951 using OpRewritePattern<InitTensorOp>::OpRewritePattern; 952 953 LogicalResult matchAndRewrite(InitTensorOp op, 954 PatternRewriter &rewriter) const override { 955 SmallVector<Value, 4> dynamicSizes; 956 SmallVector<int64_t, 4> staticSizes; 957 for (unsigned i = 0, e = op.getType().getRank(); i != e; ++i) { 958 // If the size is already static, nothing to do. 959 if (!op.isDynamicSize(i)) { 960 staticSizes.push_back(op.getStaticSize(i)); 961 continue; 962 } 963 964 // If the size is dynamic but defined using a `constant` op, get the 965 // constant value to find the static size to use. 966 unsigned operandNum = op.getIndexOfDynamicSize(i); 967 Value sizeOperand = op.getOperand(operandNum); 968 if (auto constantIndexOp = 969 sizeOperand.getDefiningOp<arith::ConstantIndexOp>()) { 970 staticSizes.push_back(constantIndexOp.value()); 971 continue; 972 } 973 974 // Fallback case. Keep the size dynamic. 975 dynamicSizes.push_back(sizeOperand); 976 staticSizes.push_back(ShapedType::kDynamicSize); 977 } 978 RankedTensorType newType = 979 RankedTensorType::get(staticSizes, op.getType().getElementType()); 980 if (newType == op.getType()) 981 return failure(); 982 auto newOp = 983 rewriter.create<InitTensorOp>(op.getLoc(), newType, dynamicSizes, 984 rewriter.getI64ArrayAttr(staticSizes)); 985 rewriter.replaceOpWithNewOp<tensor::CastOp>(op, op.getType(), newOp); 986 return success(); 987 } 988 }; 989 } // namespace 990 991 namespace { 992 /// Since `init_tensor` operation creates a tensor needed only for its shape, a 993 /// slice of this is also needed only for its shape. The result can be 994 /// replaced by a new init_tensor operation of the same size as the extract 995 /// slice op. 996 struct FoldInitTensorWithExtractSliceOp 997 : public OpRewritePattern<tensor::ExtractSliceOp> { 998 using OpRewritePattern<tensor::ExtractSliceOp>::OpRewritePattern; 999 1000 LogicalResult matchAndRewrite(tensor::ExtractSliceOp sliceOp, 1001 PatternRewriter &rewriter) const override { 1002 if (!sliceOp.source().getDefiningOp<linalg::InitTensorOp>()) 1003 return failure(); 1004 // ExtractSliceOp may be rank-reducing; its dynamic sizes must be preserved 1005 // as well as its result type. 1006 rewriter.replaceOpWithNewOp<linalg::InitTensorOp>( 1007 sliceOp, sliceOp.sizes(), 1008 sliceOp.result().getType().cast<RankedTensorType>().getShape(), 1009 sliceOp.getSourceType().getElementType()); 1010 return success(); 1011 } 1012 }; 1013 1014 template <typename TensorReshapeOp> 1015 struct FoldInitTensorWithTensorReshapeOp 1016 : public OpRewritePattern<TensorReshapeOp> { 1017 using OpRewritePattern<TensorReshapeOp>::OpRewritePattern; 1018 1019 LogicalResult matchAndRewrite(TensorReshapeOp reshapeOp, 1020 PatternRewriter &rewriter) const override { 1021 if (!reshapeOp.src().template getDefiningOp<InitTensorOp>()) 1022 return failure(); 1023 Location loc = reshapeOp.getLoc(); 1024 ReifiedRankedShapedTypeDims resultShapes; 1025 ReifyRankedShapedTypeOpInterface reifyShapedTypeInterface = 1026 cast<ReifyRankedShapedTypeOpInterface>(reshapeOp.getOperation()); 1027 if (failed(reifyShapedTypeInterface.reifyResultShapes(rewriter, 1028 resultShapes)) || 1029 !llvm::hasSingleElement(resultShapes)) 1030 return failure(); 1031 Value initTensor = rewriter.create<InitTensorOp>( 1032 loc, getAsOpFoldResult(resultShapes[0]), 1033 reshapeOp.getResultType().getElementType()); 1034 if (initTensor.getType() != reshapeOp.getResultType()) { 1035 rewriter.replaceOpWithNewOp<tensor::CastOp>( 1036 reshapeOp, reshapeOp.getResultType(), initTensor); 1037 } else { 1038 rewriter.replaceOp(reshapeOp, initTensor); 1039 } 1040 return success(); 1041 } 1042 }; 1043 1044 struct FoldInitTensorWithDimOp : public OpRewritePattern<tensor::DimOp> { 1045 using OpRewritePattern<tensor::DimOp>::OpRewritePattern; 1046 1047 LogicalResult matchAndRewrite(tensor::DimOp dimOp, 1048 PatternRewriter &rewriter) const override { 1049 Optional<int64_t> maybeConstantIndex = dimOp.getConstantIndex(); 1050 auto initTensorOp = dimOp.source().getDefiningOp<linalg::InitTensorOp>(); 1051 if (!initTensorOp || !maybeConstantIndex) 1052 return failure(); 1053 if (!initTensorOp.isDynamicSize(*maybeConstantIndex)) 1054 return failure(); 1055 rewriter.replaceOp(dimOp, initTensorOp.getDynamicSize(*maybeConstantIndex)); 1056 return success(); 1057 } 1058 }; 1059 } // namespace 1060 1061 void InitTensorOp::getCanonicalizationPatterns(RewritePatternSet &results, 1062 MLIRContext *context) { 1063 results.add<FoldInitTensorWithDimOp, FoldInitTensorWithExtractSliceOp, 1064 FoldInitTensorWithTensorReshapeOp<tensor::ExpandShapeOp>, 1065 FoldInitTensorWithTensorReshapeOp<tensor::CollapseShapeOp>, 1066 ReplaceStaticShapeDims>(context); 1067 } 1068 1069 LogicalResult InitTensorOp::reifyResultShapes( 1070 OpBuilder &builder, ReifiedRankedShapedTypeDims &reifiedReturnShapes) { 1071 auto shapes = llvm::to_vector<4>(llvm::map_range( 1072 llvm::seq<int64_t>(0, getType().getRank()), [&](int64_t dim) -> Value { 1073 if (isDynamicSize(dim)) 1074 return getDynamicSize(dim); 1075 return builder.create<arith::ConstantIndexOp>(getLoc(), 1076 getStaticSize(dim)); 1077 })); 1078 reifiedReturnShapes.emplace_back(std::move(shapes)); 1079 return success(); 1080 } 1081 1082 //===----------------------------------------------------------------------===// 1083 // PadTensorOp 1084 //===----------------------------------------------------------------------===// 1085 1086 // TODO: Replace custom<InferType> directive with AllTypesMatch as soon as it 1087 // supports optional types. 1088 void printInferType(OpAsmPrinter &printer, Operation *op, Value optOperand, 1089 Type typeToInfer, Type typeToInferFrom) {} 1090 1091 ParseResult parseInferType(OpAsmParser &parser, 1092 Optional<OpAsmParser::OperandType> optOperand, 1093 Type &typeToInfer, Type typeToInferFrom) { 1094 if (optOperand) 1095 typeToInfer = typeToInferFrom; 1096 return success(); 1097 } 1098 1099 static LogicalResult verify(PadTensorOp op) { 1100 auto sourceType = op.source().getType().cast<RankedTensorType>(); 1101 auto resultType = op.result().getType().cast<RankedTensorType>(); 1102 auto expectedType = PadTensorOp::inferResultType( 1103 sourceType, extractFromI64ArrayAttr(op.static_low()), 1104 extractFromI64ArrayAttr(op.static_high())); 1105 for (int i = 0, e = sourceType.getRank(); i < e; ++i) { 1106 if (resultType.getDimSize(i) == expectedType.getDimSize(i)) 1107 continue; 1108 if (expectedType.isDynamicDim(i)) 1109 continue; 1110 return op.emitError("specified type ") 1111 << resultType << " does not match the inferred type " 1112 << expectedType; 1113 } 1114 1115 auto ®ion = op.region(); 1116 unsigned rank = resultType.getRank(); 1117 Block &block = region.front(); 1118 if (block.getNumArguments() != rank) 1119 return op.emitError("expected the block to have ") << rank << " arguments"; 1120 1121 // Note: the number and type of yield values are checked in the YieldOp. 1122 for (const auto &en : llvm::enumerate(block.getArgumentTypes())) { 1123 if (!en.value().isIndex()) 1124 return op.emitOpError("expected block argument ") 1125 << (en.index() + 1) << " to be an index"; 1126 } 1127 1128 return success(); 1129 } 1130 1131 RankedTensorType PadTensorOp::inferResultType(RankedTensorType sourceType, 1132 ArrayRef<int64_t> staticLow, 1133 ArrayRef<int64_t> staticHigh, 1134 ArrayRef<int64_t> resultShape) { 1135 unsigned rank = sourceType.getRank(); 1136 assert(staticLow.size() == rank && "unexpected staticLow size mismatch"); 1137 assert(staticHigh.size() == rank && "unexpected staticHigh size mismatch"); 1138 assert((resultShape.empty() || resultShape.size() == rank) && 1139 "unexpected resultShape size mismatch"); 1140 1141 SmallVector<int64_t, 4> inferredShape; 1142 for (auto i : llvm::seq<unsigned>(0, rank)) { 1143 if (sourceType.isDynamicDim(i) || 1144 staticLow[i] == ShapedType::kDynamicSize || 1145 staticHigh[i] == ShapedType::kDynamicSize) { 1146 inferredShape.push_back(resultShape.empty() ? ShapedType::kDynamicSize 1147 : resultShape[i]); 1148 } else { 1149 int64_t size = sourceType.getDimSize(i) + staticLow[i] + staticHigh[i]; 1150 assert((resultShape.empty() || size == resultShape[i] || 1151 resultShape[i] == ShapedType::kDynamicSize) && 1152 "mismatch between inferred shape and result shape"); 1153 inferredShape.push_back(size); 1154 } 1155 } 1156 1157 return RankedTensorType::get(inferredShape, sourceType.getElementType()); 1158 } 1159 1160 void PadTensorOp::build(OpBuilder &b, OperationState &result, Value source, 1161 ArrayRef<int64_t> staticLow, 1162 ArrayRef<int64_t> staticHigh, ValueRange low, 1163 ValueRange high, bool nofold, 1164 ArrayRef<NamedAttribute> attrs) { 1165 auto sourceType = source.getType().cast<RankedTensorType>(); 1166 auto resultType = inferResultType(sourceType, staticLow, staticHigh); 1167 build(b, result, resultType, source, low, high, b.getI64ArrayAttr(staticLow), 1168 b.getI64ArrayAttr(staticHigh), nofold ? b.getUnitAttr() : UnitAttr()); 1169 result.addAttributes(attrs); 1170 } 1171 1172 void PadTensorOp::build(OpBuilder &b, OperationState &result, Value source, 1173 ValueRange low, ValueRange high, bool nofold, 1174 ArrayRef<NamedAttribute> attrs) { 1175 auto sourceType = source.getType().cast<RankedTensorType>(); 1176 unsigned rank = sourceType.getRank(); 1177 SmallVector<int64_t, 4> staticVector(rank, ShapedType::kDynamicSize); 1178 build(b, result, source, staticVector, staticVector, low, high, nofold, 1179 attrs); 1180 } 1181 1182 void PadTensorOp::build(OpBuilder &b, OperationState &result, Type resultType, 1183 Value source, ArrayRef<OpFoldResult> low, 1184 ArrayRef<OpFoldResult> high, bool nofold, 1185 ArrayRef<NamedAttribute> attrs) { 1186 assert(resultType.isa<RankedTensorType>()); 1187 auto sourceType = source.getType().cast<RankedTensorType>(); 1188 SmallVector<Value, 4> dynamicLow, dynamicHigh; 1189 SmallVector<int64_t, 4> staticLow, staticHigh; 1190 // staticLow and staticHigh have full information of the padding config. 1191 // This will grow staticLow and staticHigh with 1 value. If the config is 1192 // dynamic (ie not a constant), dynamicLow and dynamicHigh will grow with 1 1193 // value as well. 1194 dispatchIndexOpFoldResults(low, dynamicLow, staticLow, 1195 ShapedType::kDynamicSize); 1196 dispatchIndexOpFoldResults(high, dynamicHigh, staticHigh, 1197 ShapedType::kDynamicSize); 1198 if (!resultType) { 1199 resultType = 1200 PadTensorOp::inferResultType(sourceType, staticLow, staticHigh); 1201 } 1202 build(b, result, resultType, source, dynamicLow, dynamicHigh, 1203 b.getI64ArrayAttr(staticLow), b.getI64ArrayAttr(staticHigh), 1204 nofold ? b.getUnitAttr() : UnitAttr()); 1205 result.addAttributes(attrs); 1206 } 1207 1208 PadTensorOp PadTensorOp::createPadScalarOp(Type type, Value source, Value pad, 1209 ArrayRef<OpFoldResult> low, 1210 ArrayRef<OpFoldResult> high, 1211 bool nofold, Location loc, 1212 OpBuilder &builder) { 1213 auto padTensorOp = 1214 builder.create<linalg::PadTensorOp>(loc, type, source, low, high, nofold); 1215 int rank = padTensorOp.getResultType().getRank(); 1216 SmallVector<Type, 4> blockArgTypes(rank, builder.getIndexType()); 1217 SmallVector<Location, 4> blockArgLocs(rank, loc); 1218 auto ®ion = padTensorOp.region(); 1219 // `builder.createBlock` changes the insertion point within the block. Create 1220 // a guard to reset the insertion point of the builder after it is destroyed. 1221 OpBuilder::InsertionGuard guard(builder); 1222 builder.createBlock(®ion, region.end(), blockArgTypes, blockArgLocs); 1223 builder.create<linalg::YieldOp>(loc, pad); 1224 return padTensorOp; 1225 } 1226 1227 PadTensorOp PadTensorOp::createPadHighOp(Type type, Value source, Value pad, 1228 bool nofold, Location loc, 1229 OpBuilder &b) { 1230 SmallVector<OpFoldResult, 4> low, high; 1231 auto rankedTensorType = type.cast<RankedTensorType>(); 1232 assert(rankedTensorType.hasStaticShape()); 1233 for (const auto &en : enumerate(rankedTensorType.getShape())) { 1234 AffineExpr d0; 1235 bindDims(b.getContext(), d0); 1236 auto dimOp = b.createOrFold<tensor::DimOp>(loc, source, en.index()); 1237 Value paddingWidth = 1238 makeComposedAffineApply(b, loc, en.value() - d0, {dimOp}); 1239 high.push_back(paddingWidth); 1240 low.push_back(b.createOrFold<arith::ConstantIndexOp>(loc, 0)); 1241 } 1242 return PadTensorOp::createPadScalarOp(type, source, pad, low, high, nofold, 1243 loc, b); 1244 } 1245 1246 LogicalResult PadTensorOp::reifyResultShapes( 1247 OpBuilder &b, ReifiedRankedShapedTypeDims &reifiedReturnShapes) { 1248 Location loc = getLoc(); 1249 auto lowPad = getMixedLowPad(); 1250 auto highPad = getMixedHighPad(); 1251 SmallVector<Value> shapes; 1252 for (auto dim : llvm::seq<int64_t>(0, getSourceType().getRank())) { 1253 // Shape along each dimension is source dim + low pad + high pad. 1254 SmallVector<Value> mapOperands; 1255 mapOperands.push_back(b.createOrFold<tensor::DimOp>(loc, source(), dim)); 1256 AffineExpr expr = b.getAffineDimExpr(0); 1257 unsigned numSymbols = 0; 1258 auto addOpFoldResult = [&](OpFoldResult valueOrAttr) { 1259 if (Value v = valueOrAttr.dyn_cast<Value>()) { 1260 expr = expr + b.getAffineSymbolExpr(numSymbols++); 1261 mapOperands.push_back(v); 1262 return; 1263 } 1264 int64_t staticValue = 1265 valueOrAttr.get<Attribute>().cast<IntegerAttr>().getInt(); 1266 expr = expr + staticValue; 1267 }; 1268 addOpFoldResult(lowPad[dim]); 1269 addOpFoldResult(highPad[dim]); 1270 shapes.push_back(applyMapToValues( 1271 b, loc, AffineMap::get(1, numSymbols, expr), mapOperands)[0]); 1272 } 1273 reifiedReturnShapes.emplace_back(std::move(shapes)); 1274 return success(); 1275 } 1276 1277 //===----------------------------------------------------------------------===// 1278 // Methods related to PadTensor tiling. 1279 //===----------------------------------------------------------------------===// 1280 1281 SmallVector<Value> PadTensorOp::getDestinationOperands(OpBuilder &b) { 1282 ReifiedRankedShapedTypeDims reifiedShapes; 1283 (void)reifyResultShapes(b, reifiedShapes); 1284 SmallVector<OpFoldResult> mixedSizes = getAsOpFoldResult(reifiedShapes[0]); 1285 Value initTensor = b.create<InitTensorOp>(getLoc(), mixedSizes, 1286 getResultType().getElementType()); 1287 return {initTensor}; 1288 } 1289 1290 SmallVector<StringRef> PadTensorOp::getLoopIteratorTypes() { 1291 SmallVector<StringRef> iteratorTypes(getResultType().getRank(), 1292 getParallelIteratorTypeName()); 1293 return iteratorTypes; 1294 } 1295 1296 SmallVector<Range> PadTensorOp::getIterationDomain(OpBuilder &b) { 1297 ReifiedRankedShapedTypeDims reifiedShapes; 1298 (void)reifyResultShapes(b, reifiedShapes); 1299 Value zero = b.create<arith::ConstantIndexOp>(getLoc(), 0); 1300 Value one = b.create<arith::ConstantIndexOp>(getLoc(), 1); 1301 // Initialize all the ranges to {zero, one, one}. All the `ub`s are 1302 // overwritten. 1303 SmallVector<Range> loopRanges(reifiedShapes[0].size(), {zero, one, one}); 1304 for (const auto &ub : enumerate(reifiedShapes[0])) 1305 loopRanges[ub.index()].size = ub.value(); 1306 return loopRanges; 1307 } 1308 1309 SmallVector<Operation *> PadTensorOp::getTiledImplementation( 1310 OpBuilder &b, ValueRange dest, ArrayRef<OpFoldResult> offsets, 1311 ArrayRef<OpFoldResult> sizes, bool /*tileDestOperands*/) { 1312 // Only constant padding value supported. 1313 Value padValue = getConstantPaddingValue(); 1314 if (!padValue) 1315 return {}; 1316 1317 // Helper variables and functions for various arithmetic operations. These are 1318 // used extensively for computing new offset/length and padding values. 1319 Location loc = getLoc(); 1320 AffineExpr dim0, dim1; 1321 bindDims(b.getContext(), dim0, dim1); 1322 // Add two integers. 1323 auto addMap = AffineMap::get(2, 0, {dim0 + dim1}); 1324 auto add = [&](Value v1, Value v2) { 1325 return b.createOrFold<AffineApplyOp>(loc, addMap, ValueRange{v1, v2}); 1326 }; 1327 // Subtract two integers. 1328 auto subMap = AffineMap::get(2, 0, {dim0 - dim1}); 1329 auto sub = [&](Value v1, Value v2) { 1330 return b.createOrFold<AffineApplyOp>(loc, subMap, ValueRange{v1, v2}); 1331 }; 1332 // Take the minimum of two integers. 1333 auto idMap = AffineMap::getMultiDimIdentityMap(2, b.getContext()); 1334 auto min = [&](Value v1, Value v2) { 1335 return b.createOrFold<AffineMinOp>(loc, idMap, ValueRange{v1, v2}); 1336 }; 1337 // Take the maximum of two integers. 1338 auto max = [&](Value v1, Value v2) { 1339 return b.createOrFold<AffineMaxOp>(loc, idMap, ValueRange{v1, v2}); 1340 }; 1341 // Zero index-typed integer. 1342 auto zero = b.create<arith::ConstantIndexOp>(loc, 0); 1343 1344 // Helper function for filling static/dynamic low/high padding indices vectors 1345 // of PadTensorOp. 1346 auto appendIndex = [&](Value val, SmallVector<Value> &dynIndices, 1347 SmallVector<int64_t> &staticIndices) { 1348 if (auto constInt = getConstantIntValue(val)) { 1349 staticIndices.push_back(*constInt); 1350 } else { 1351 staticIndices.push_back(ShapedType::kDynamicSize); 1352 dynIndices.push_back(val); 1353 } 1354 }; 1355 1356 // Compute new offsets, lengths, low padding, high padding. 1357 SmallVector<OpFoldResult> newOffsets, newLengths, newStrides; 1358 SmallVector<Value> newLows, newHighs; 1359 SmallVector<int64_t> staticNewLows, staticNewHighs; 1360 // Set to true if the original data source is not read at all. 1361 bool hasZeroLen = false; 1362 // Same as hasZeroLen, but for dynamic dimension sizes. This condition 1363 // is true if the original data source turns out to be unused at runtime. 1364 Value dynHasZeroLenCond; 1365 1366 int64_t rank = getSourceType().getRank(); 1367 for (unsigned dim = 0; dim < rank; ++dim) { 1368 auto low = getValueOrCreateConstantIndexOp(b, loc, getMixedLowPad()[dim]); 1369 bool hasLowPad = getConstantIntValue(low) != static_cast<int64_t>(0); 1370 auto high = getValueOrCreateConstantIndexOp(b, loc, getMixedHighPad()[dim]); 1371 bool hasHighPad = getConstantIntValue(high) != static_cast<int64_t>(0); 1372 auto offset = getValueOrCreateConstantIndexOp(b, loc, offsets[dim]); 1373 auto length = getValueOrCreateConstantIndexOp(b, loc, sizes[dim]); 1374 auto srcSize = b.createOrFold<tensor::DimOp>(loc, source(), dim); 1375 1376 // The new amount of low padding is `low - offset`. Except for the case 1377 // where none of the low padding is read. In that case, the new amount of 1378 // low padding is zero. 1379 // 1380 // Optimization: If low = 0, then newLow = 0. 1381 Value newLow = hasLowPad ? max(zero, sub(low, offset)) : zero; 1382 appendIndex(newLow, newLows, staticNewLows); 1383 1384 // Start reading the data from position `offset - low`. Since the original 1385 // read may have started in the low padding zone, this value could be 1386 // negative. Therefore, start reading from: 1387 // 1388 // max(offset - low, 0) 1389 // 1390 // The original read could also have started in the high padding zone. 1391 // In that case, set the offset to the end of source tensor. The new 1392 // ExtractSliceOp length will be zero in that case. (Effectively reading no 1393 // data from the source.) 1394 // 1395 // Optimization: If low = 0, then the formula can be simplified. 1396 Value newOffset = hasLowPad ? min(max(sub(offset, low), zero), srcSize) 1397 : min(offset, srcSize); 1398 newOffsets.push_back(getAsOpFoldResult(newOffset)); 1399 1400 // The original ExtractSliceOp was reading until position `offset + length`. 1401 // Therefore, the corresponding position within the source tensor is: 1402 // 1403 // offset + length - low 1404 // 1405 // In case the original ExtractSliceOp stopped reading within the low 1406 // padding zone, this value can be negative. In that case, the end position 1407 // of the read should be zero. (Similar to newOffset.) 1408 // 1409 // The original read could also have stopped in the high padding zone. 1410 // In that case, set the end positition of the read should be the end of the 1411 // source tensor. (Similar to newOffset.) 1412 // 1413 // endLoc = min(max(offset - low + length, 0), srcSize) 1414 // 1415 // The new ExtractSliceOp length is `endLoc - newOffset`. 1416 // 1417 // Optimization: If low = 0, then the formula can be simplified. 1418 Value endLoc = hasLowPad 1419 ? min(max(add(sub(offset, low), length), zero), srcSize) 1420 : min(add(offset, length), srcSize); 1421 Value newLength = sub(endLoc, newOffset); 1422 newLengths.push_back(getAsOpFoldResult(newLength)); 1423 1424 // Check if newLength is zero. In that case, no SubTensorOp should be 1425 // executed. 1426 if (auto newLengthInt = getConstantIntValue(newLength)) { 1427 hasZeroLen |= *newLengthInt == 0; 1428 } else { 1429 Value check = b.create<arith::CmpIOp>(loc, arith::CmpIPredicate::eq, 1430 newLength, zero); 1431 dynHasZeroLenCond = 1432 dynHasZeroLenCond 1433 ? b.create<arith::OrIOp>(loc, check, dynHasZeroLenCond) 1434 : check; 1435 } 1436 1437 // The amount of high padding is simply the number of elements remaining, 1438 // so that the result has the same length as the original ExtractSliceOp. 1439 // As an optimization, if the original high padding is zero, then the new 1440 // high padding must also be zero. 1441 Value newHigh = hasHighPad ? sub(sub(length, newLength), newLow) : zero; 1442 appendIndex(newHigh, newHighs, staticNewHighs); 1443 1444 // Only unit stride supported. 1445 newStrides.push_back(b.getIndexAttr(1)); 1446 } 1447 1448 // The shape of the result can be obtained from the sizes passed in. 1449 SmallVector<Value> dynDims; 1450 SmallVector<int64_t> shape; 1451 dispatchIndexOpFoldResults(sizes, dynDims, shape, ShapedType::kDynamicSize); 1452 RankedTensorType resultType = 1453 RankedTensorType::get(shape, getResultType().getElementType()); 1454 1455 // Insert cast to ensure that types match. (May be folded away.) 1456 auto castResult = [&](Value val) -> Operation * { 1457 auto castOp = b.create<tensor::CastOp>(loc, resultType, val); 1458 return castOp; 1459 }; 1460 1461 // In cases where the original data source is unused: Emit a GenerateOp and 1462 // do not generate a SliceOp. (The result shape of the SliceOp would 1463 // have a dimension of size 0, the semantics of which is unclear.) 1464 auto createGenerateOp = [&]() { 1465 // Create GenerateOp. 1466 auto generateOp = b.create<tensor::GenerateOp>( 1467 loc, resultType, dynDims, 1468 [&](OpBuilder &builder, Location gLoc, ValueRange indices) { 1469 builder.create<tensor::YieldOp>(gLoc, padValue); 1470 }); 1471 return castResult(generateOp); 1472 }; 1473 1474 // Emit a SliceOp and a PadTensorOp. Should not be used in cases where 1475 // the result shape of the new SliceOp has a zero dimension. 1476 auto createPadTensorOfSubTensor = [&]() { 1477 // Create pad_tensor(subtensor(x)). 1478 auto newSliceOp = b.create<tensor::ExtractSliceOp>( 1479 loc, source(), newOffsets, newLengths, newStrides); 1480 auto newPadTensorOp = b.create<PadTensorOp>( 1481 loc, newSliceOp, staticNewLows, staticNewHighs, newLows, newHighs); 1482 1483 // Copy region to new PadTensorOp. 1484 BlockAndValueMapping bvm; 1485 region().cloneInto(&newPadTensorOp.getRegion(), bvm); 1486 1487 // Cast result and return. 1488 return castResult(newPadTensorOp); 1489 }; 1490 1491 // Rewrite subtensor(pad_tensor(x)) into a GenerateOp it is statically known 1492 // that the original data source x is not used. 1493 if (hasZeroLen) { 1494 return {createGenerateOp()}; 1495 } 1496 1497 // If there are dynamic dimensions: Generate an scf.if check to avoid creating 1498 // SliceOps with result dimensions of size 0 at runtime. 1499 if (dynHasZeroLenCond) { 1500 auto result = b.create<scf::IfOp>( 1501 loc, resultType, dynHasZeroLenCond, 1502 /*thenBuilder=*/ 1503 [&](OpBuilder &b, Location loc) { 1504 b.create<scf::YieldOp>(loc, createGenerateOp()->getResult(0)); 1505 }, 1506 /*elseBuilder=*/ 1507 [&](OpBuilder &b, Location loc) { 1508 b.create<scf::YieldOp>(loc, 1509 createPadTensorOfSubTensor()->getResult(0)); 1510 }); 1511 return {result}; 1512 } 1513 return {createPadTensorOfSubTensor()}; 1514 } 1515 1516 namespace { 1517 // Folds linalg.pad_tensor when padding is static zeros and the attribute 1518 // doesn't request otherwise. 1519 struct FoldStaticZeroPadding : public OpRewritePattern<PadTensorOp> { 1520 using OpRewritePattern<PadTensorOp>::OpRewritePattern; 1521 1522 LogicalResult matchAndRewrite(PadTensorOp padTensorOp, 1523 PatternRewriter &rewriter) const override { 1524 if (!padTensorOp.hasZeroLowPad() || !padTensorOp.hasZeroHighPad()) 1525 return failure(); 1526 if (padTensorOp.nofold()) 1527 return failure(); 1528 rewriter.replaceOpWithNewOp<tensor::CastOp>( 1529 padTensorOp, padTensorOp.result().getType(), padTensorOp.source()); 1530 return success(); 1531 } 1532 }; 1533 1534 // Fold CastOp into PadTensorOp when adding static information. 1535 struct FoldSourceTensorCast : public OpRewritePattern<PadTensorOp> { 1536 using OpRewritePattern<PadTensorOp>::OpRewritePattern; 1537 1538 LogicalResult matchAndRewrite(PadTensorOp padTensorOp, 1539 PatternRewriter &rewriter) const override { 1540 auto castOp = padTensorOp.source().getDefiningOp<tensor::CastOp>(); 1541 if (!tensor::canFoldIntoConsumerOp(castOp)) 1542 return failure(); 1543 1544 auto newResultType = PadTensorOp::inferResultType( 1545 castOp.source().getType().cast<RankedTensorType>(), 1546 extractFromI64ArrayAttr(padTensorOp.static_low()), 1547 extractFromI64ArrayAttr(padTensorOp.static_high()), 1548 padTensorOp.getResultType().getShape()); 1549 1550 if (newResultType == padTensorOp.getResultType()) { 1551 rewriter.updateRootInPlace(padTensorOp, [&]() { 1552 padTensorOp.sourceMutable().assign(castOp.source()); 1553 }); 1554 } else { 1555 auto newOp = rewriter.create<PadTensorOp>( 1556 padTensorOp->getLoc(), newResultType, padTensorOp.source(), 1557 padTensorOp.low(), padTensorOp.high(), padTensorOp.static_low(), 1558 padTensorOp.static_high(), padTensorOp.nofold()); 1559 BlockAndValueMapping mapper; 1560 padTensorOp.getRegion().cloneInto(&newOp.getRegion(), mapper); 1561 1562 rewriter.replaceOpWithNewOp<tensor::CastOp>( 1563 padTensorOp, padTensorOp.getResultType(), newOp); 1564 } 1565 return success(); 1566 } 1567 }; 1568 1569 // Fold CastOp using the result of PadTensorOp back into the latter if it adds 1570 // static information. 1571 struct FoldTargetTensorCast : public OpRewritePattern<PadTensorOp> { 1572 using OpRewritePattern<PadTensorOp>::OpRewritePattern; 1573 1574 LogicalResult matchAndRewrite(PadTensorOp padTensorOp, 1575 PatternRewriter &rewriter) const override { 1576 if (!padTensorOp.result().hasOneUse()) 1577 return failure(); 1578 auto tensorCastOp = 1579 dyn_cast<tensor::CastOp>(*padTensorOp->getUsers().begin()); 1580 if (!tensorCastOp) 1581 return failure(); 1582 if (!tensor::preservesStaticInformation(padTensorOp.result().getType(), 1583 tensorCastOp.dest().getType())) 1584 return failure(); 1585 1586 auto replacementOp = rewriter.create<PadTensorOp>( 1587 padTensorOp.getLoc(), tensorCastOp.dest().getType(), 1588 padTensorOp.source(), padTensorOp.low(), padTensorOp.high(), 1589 padTensorOp.static_low(), padTensorOp.static_high(), 1590 padTensorOp.nofold()); 1591 replacementOp.region().takeBody(padTensorOp.region()); 1592 1593 rewriter.replaceOp(padTensorOp, replacementOp.result()); 1594 rewriter.replaceOp(tensorCastOp, replacementOp.result()); 1595 return success(); 1596 } 1597 }; 1598 } // namespace 1599 1600 void PadTensorOp::getCanonicalizationPatterns(RewritePatternSet &results, 1601 MLIRContext *context) { 1602 results.add<FoldStaticZeroPadding, FoldSourceTensorCast>(context); 1603 results.add<FoldTargetTensorCast>(context); 1604 } 1605 1606 /// Return the padding value of the PadTensorOp if it constant. In this context, 1607 /// "constant" means an actual constant or "defined outside of the block". 1608 /// 1609 /// Values are considered constant in three cases: 1610 /// - A ConstantLike value. 1611 /// - A basic block argument from a different block. 1612 /// - A value defined outside of the block. 1613 /// 1614 /// If the padding value is not constant, an empty Value is returned. 1615 Value PadTensorOp::getConstantPaddingValue() { 1616 auto yieldOp = dyn_cast<YieldOp>(getRegion().front().getTerminator()); 1617 if (!yieldOp || yieldOp.values().size() != 1) 1618 return {}; 1619 Value padValue = yieldOp.values().front(); 1620 // Check if yield value is a constant. 1621 if (matchPattern(padValue, m_Constant())) 1622 return padValue; 1623 // Check if yield value is defined inside the PadTensorOp block. 1624 if (padValue.getParentBlock() == &getRegion().front()) 1625 return {}; 1626 // Else: Yield value defined outside of the PadTensorOp block. 1627 return padValue; 1628 } 1629 1630 OpFoldResult PadTensorOp::fold(ArrayRef<Attribute>) { 1631 if (getResultType().hasStaticShape() && getResultType() == getSourceType() && 1632 !nofold()) 1633 return source(); 1634 return {}; 1635 } 1636 1637 //===----------------------------------------------------------------------===// 1638 // YieldOp 1639 //===----------------------------------------------------------------------===// 1640 1641 static void print(OpAsmPrinter &p, linalg::YieldOp op) { 1642 if (op.getNumOperands() > 0) 1643 p << ' ' << op.getOperands(); 1644 p.printOptionalAttrDict(op->getAttrs()); 1645 if (op.getNumOperands() > 0) 1646 p << " : " << op.getOperandTypes(); 1647 } 1648 1649 static ParseResult parseYieldOp(OpAsmParser &parser, OperationState &result) { 1650 SmallVector<OpAsmParser::OperandType, 2> opInfo; 1651 SmallVector<Type, 2> types; 1652 llvm::SMLoc loc = parser.getCurrentLocation(); 1653 return failure(parser.parseOperandList(opInfo) || 1654 parser.parseOptionalAttrDict(result.attributes) || 1655 (!opInfo.empty() && parser.parseColonTypeList(types)) || 1656 parser.resolveOperands(opInfo, types, loc, result.operands)); 1657 } 1658 1659 // Check the operand number and types must match the element types of the 1660 // LinalgOp interface's shaped operands. 1661 static LogicalResult verifyYield(linalg::YieldOp op, LinalgOp linalgOp) { 1662 if (op.getNumOperands() != linalgOp.getNumOutputs()) 1663 return op.emitOpError("expected number of yield values (") 1664 << linalgOp.getNumOutputs() 1665 << ") to match the number of operands of the enclosing " 1666 << "LinalgOp (" << op.getNumOperands() << ")"; 1667 1668 for (OpOperand &opOperand : op->getOpOperands()) { 1669 OpOperand *outputOperand = 1670 linalgOp.getOutputOperand(opOperand.getOperandNumber()); 1671 Type elementType = getElementTypeOrSelf(outputOperand->get().getType()); 1672 if (opOperand.get().getType() != elementType) 1673 return op.emitOpError("type of yield operand ") 1674 << (opOperand.getOperandNumber() + 1) << " (" 1675 << opOperand.get().getType() << ") doesn't match " 1676 << "the element type of the enclosing linalg.generic op (" 1677 << elementType << ")"; 1678 } 1679 return success(); 1680 } 1681 1682 static LogicalResult verify(linalg::YieldOp op) { 1683 auto *parentOp = op->getParentOp(); 1684 if (parentOp->getNumRegions() != 1 || parentOp->getRegion(0).empty()) 1685 return op.emitOpError("expected single non-empty parent region"); 1686 1687 if (auto linalgOp = dyn_cast<LinalgOp>(parentOp)) 1688 return verifyYield(op, cast<LinalgOp>(parentOp)); 1689 1690 if (auto padTensorOp = dyn_cast<linalg::PadTensorOp>(parentOp)) { 1691 if (op.getNumOperands() != 1) 1692 return op.emitOpError("expected single yield operand (got ") 1693 << op->getNumOperands() << ")"; 1694 if (op.getOperand(0).getType() != 1695 padTensorOp.getType().cast<ShapedType>().getElementType()) 1696 return op.emitOpError("expected yield type to match shape element type"); 1697 return success(); 1698 } 1699 1700 if (auto tiledLoopOp = dyn_cast<linalg::TiledLoopOp>(parentOp)) { 1701 // Check if output args with tensor types match results types. 1702 SmallVector<Value, 2> tensorOuts; 1703 llvm::copy_if( 1704 tiledLoopOp.outputs(), std::back_inserter(tensorOuts), 1705 [&](Value out) { return out.getType().isa<RankedTensorType>(); }); 1706 if (tensorOuts.size() != op.values().size()) 1707 return op.emitOpError("expected number of tensor output args = ") 1708 << tensorOuts.size() << " to match the number of yield operands = " 1709 << op.values().size(); 1710 1711 TypeRange tensorTypes(llvm::makeArrayRef(tensorOuts)); 1712 for (auto &item : 1713 llvm::enumerate(llvm::zip(tensorTypes, op.getOperandTypes()))) { 1714 Type outType, resultType; 1715 unsigned index = item.index(); 1716 std::tie(outType, resultType) = item.value(); 1717 if (outType != resultType) 1718 return op.emitOpError("expected yield operand ") 1719 << index << " with type = " << resultType 1720 << " to match output arg type = " << outType; 1721 } 1722 return success(); 1723 } 1724 return op.emitOpError("expected parent op with LinalgOp interface"); 1725 } 1726 1727 //===----------------------------------------------------------------------===// 1728 // TiledLoopOp 1729 //===----------------------------------------------------------------------===// 1730 1731 void TiledLoopOp::build(OpBuilder &builder, OperationState &result, 1732 ValueRange lowerBounds, ValueRange upperBounds, 1733 ValueRange steps, ValueRange inputs, ValueRange outputs, 1734 ArrayAttr iteratorTypes, 1735 function_ref<void(OpBuilder &, Location, ValueRange, 1736 ValueRange, ValueRange)> 1737 bodyBuilderFn) { 1738 build(builder, result, lowerBounds, upperBounds, steps, inputs, outputs, 1739 iteratorTypes, llvm::None, bodyBuilderFn); 1740 } 1741 1742 void TiledLoopOp::build(OpBuilder &builder, OperationState &result, 1743 ValueRange lowerBounds, ValueRange upperBounds, 1744 ValueRange steps, ValueRange inputs, ValueRange outputs, 1745 ArrayAttr iteratorTypes, 1746 Optional<ArrayAttr> distributionTypes, 1747 function_ref<void(OpBuilder &, Location, ValueRange, 1748 ValueRange, ValueRange)> 1749 bodyBuilderFn) { 1750 result.addOperands(lowerBounds); 1751 result.addOperands(upperBounds); 1752 result.addOperands(steps); 1753 result.addOperands(inputs); 1754 result.addOperands(outputs); 1755 result.addAttribute( 1756 TiledLoopOp::getOperandSegmentSizeAttr(), 1757 builder.getI32VectorAttr({static_cast<int32_t>(lowerBounds.size()), 1758 static_cast<int32_t>(upperBounds.size()), 1759 static_cast<int32_t>(steps.size()), 1760 static_cast<int32_t>(inputs.size()), 1761 static_cast<int32_t>(outputs.size())})); 1762 result.addAttribute(getIteratorTypesAttrName(), iteratorTypes); 1763 1764 if (distributionTypes.hasValue()) 1765 result.addAttribute(getDistributionTypesAttrName(), 1766 distributionTypes.getValue()); 1767 1768 // Add output types for `RankedTensorType` output arguments. 1769 for (Value output : outputs) { 1770 Type outputType = output.getType(); 1771 if (outputType.isa<RankedTensorType>()) 1772 result.addTypes(outputType); 1773 } 1774 1775 OpBuilder::InsertionGuard guard(builder); 1776 unsigned numIVs = steps.size(); 1777 SmallVector<Type, 8> argTypes(numIVs, builder.getIndexType()); 1778 SmallVector<Location, 8> argLocs(numIVs, result.location); 1779 for (Value input : inputs) { 1780 argTypes.push_back(input.getType()); 1781 argLocs.push_back(input.getLoc()); 1782 } 1783 for (Value output : outputs) { 1784 argTypes.push_back(output.getType()); 1785 argLocs.push_back(output.getLoc()); 1786 } 1787 Region *bodyRegion = result.addRegion(); 1788 Block *bodyBlock = builder.createBlock(bodyRegion, {}, argTypes, argLocs); 1789 1790 if (bodyBuilderFn) { 1791 builder.setInsertionPointToStart(bodyBlock); 1792 bodyBuilderFn(builder, result.location, 1793 bodyBlock->getArguments().take_front(numIVs), 1794 bodyBlock->getArguments().slice(numIVs, inputs.size()), 1795 bodyBlock->getArguments().take_back(outputs.size())); 1796 TiledLoopOp::ensureTerminator(*bodyRegion, builder, result.location); 1797 } 1798 } 1799 1800 static void print(OpAsmPrinter &p, TiledLoopOp op) { 1801 p << " (" << op.getInductionVars() << ") = (" << op.lowerBound() << ") to (" 1802 << op.upperBound() << ") step (" << op.step() << ")"; 1803 1804 if (!op.inputs().empty()) { 1805 p << " ins ("; 1806 llvm::interleaveComma(llvm::zip(op.getRegionInputArgs(), op.inputs()), p, 1807 [&](auto it) { 1808 p << std::get<0>(it) << " = " << std::get<1>(it) 1809 << ": " << std::get<1>(it).getType(); 1810 }); 1811 p << ")"; 1812 } 1813 if (!op.outputs().empty()) { 1814 p << " outs ("; 1815 llvm::interleaveComma(llvm::zip(op.getRegionOutputArgs(), op.outputs()), p, 1816 [&](auto it) { 1817 p << std::get<0>(it) << " = " << std::get<1>(it) 1818 << ": " << std::get<1>(it).getType(); 1819 }); 1820 p << ")"; 1821 } 1822 1823 if (llvm::any_of(op.iterator_types(), [](Attribute attr) { 1824 return attr.cast<StringAttr>().getValue() != 1825 getParallelIteratorTypeName(); 1826 })) 1827 p << " iterators" << op.iterator_types(); 1828 1829 if (op.distribution_types().hasValue()) 1830 p << " distribution" << op.distribution_types().getValue(); 1831 1832 p << ' '; 1833 p.printRegion(op.region(), /*printEntryBlockArgs=*/false); 1834 p.printOptionalAttrDict( 1835 op->getAttrs(), /*elidedAttrs=*/{TiledLoopOp::getOperandSegmentSizeAttr(), 1836 getIteratorTypesAttrName(), 1837 getDistributionTypesAttrName()}); 1838 } 1839 1840 static ParseResult parseTiledLoopOp(OpAsmParser &parser, 1841 OperationState &result) { 1842 auto &builder = parser.getBuilder(); 1843 // Parse an opening `(` followed by induction variables followed by `)` 1844 SmallVector<OpAsmParser::OperandType, 4> ivs; 1845 if (parser.parseRegionArgumentList(ivs, /*requiredOperandCount=*/-1, 1846 OpAsmParser::Delimiter::Paren)) 1847 return failure(); 1848 1849 // Parse loop bounds. 1850 SmallVector<OpAsmParser::OperandType, 4> lower; 1851 if (parser.parseEqual() || 1852 parser.parseOperandList(lower, ivs.size(), 1853 OpAsmParser::Delimiter::Paren) || 1854 parser.resolveOperands(lower, builder.getIndexType(), result.operands)) 1855 return failure(); 1856 1857 SmallVector<OpAsmParser::OperandType, 4> upper; 1858 if (parser.parseKeyword("to") || 1859 parser.parseOperandList(upper, ivs.size(), 1860 OpAsmParser::Delimiter::Paren) || 1861 parser.resolveOperands(upper, builder.getIndexType(), result.operands)) 1862 return failure(); 1863 1864 // Parse step values. 1865 SmallVector<OpAsmParser::OperandType, 4> steps; 1866 if (parser.parseKeyword("step") || 1867 parser.parseOperandList(steps, ivs.size(), 1868 OpAsmParser::Delimiter::Paren) || 1869 parser.resolveOperands(steps, builder.getIndexType(), result.operands)) 1870 return failure(); 1871 1872 // Parse input tensors. 1873 SmallVector<OpAsmParser::OperandType, 4> inputs, inputRegionArgs; 1874 SmallVector<Type, 4> inputTypes; 1875 if (succeeded(parser.parseOptionalKeyword("ins"))) { 1876 llvm::SMLoc inputsOperandsLoc = parser.getCurrentLocation(); 1877 1878 if (parser.parseAssignmentListWithTypes(inputRegionArgs, inputs, 1879 inputTypes)) 1880 return failure(); 1881 1882 if (parser.resolveOperands(inputs, inputTypes, inputsOperandsLoc, 1883 result.operands)) 1884 return failure(); 1885 } 1886 1887 // Parse output tensors. 1888 SmallVector<OpAsmParser::OperandType, 4> outputs, outputRegionArgs; 1889 SmallVector<Type, 4> outputTypes; 1890 if (succeeded(parser.parseOptionalKeyword("outs"))) { 1891 llvm::SMLoc outputsOperandsLoc = parser.getCurrentLocation(); 1892 1893 if (parser.parseAssignmentListWithTypes(outputRegionArgs, outputs, 1894 outputTypes)) 1895 return failure(); 1896 1897 if (parser.resolveOperands(outputs, outputTypes, outputsOperandsLoc, 1898 result.operands)) 1899 return failure(); 1900 for (Type outputType : outputTypes) 1901 if (outputType.isa<RankedTensorType>()) 1902 result.addTypes(outputType); 1903 } 1904 1905 // Parse attributes. 1906 SmallVector<Attribute, 4> iterTypes, distributionTypes; 1907 auto parseAttr = [&](StringRef keyword, SmallVector<Attribute, 4> *attrs) { 1908 if (succeeded(parser.parseOptionalKeyword(keyword))) { 1909 StringAttr attr; 1910 1911 if (parser.parseLSquare() || parser.parseAttribute(attr)) 1912 return failure(); 1913 attrs->push_back(attr); 1914 for (int i = 1, e = ivs.size(); i < e; ++i) { 1915 if (parser.parseComma() || parser.parseAttribute(attr)) 1916 return failure(); 1917 attrs->push_back(attr); 1918 } 1919 if (parser.parseRSquare()) 1920 return failure(); 1921 } 1922 return success(); 1923 }; 1924 if (failed(parseAttr("iterators", &iterTypes)) || 1925 failed(parseAttr("distribution", &distributionTypes))) 1926 return failure(); 1927 1928 // Set all loop iterator types to "parallel" if they are not printed in IR. 1929 if (iterTypes.empty()) { 1930 auto parallelIter = builder.getStringAttr(getParallelIteratorTypeName()); 1931 iterTypes = SmallVector<Attribute, 4>(ivs.size(), parallelIter); 1932 } 1933 result.addAttribute(getIteratorTypesAttrName(), 1934 builder.getArrayAttr(iterTypes)); 1935 if (!distributionTypes.empty()) 1936 result.addAttribute(getDistributionTypesAttrName(), 1937 builder.getArrayAttr(distributionTypes)); 1938 result.addAttribute( 1939 TiledLoopOp::getOperandSegmentSizeAttr(), 1940 builder.getI32VectorAttr({static_cast<int32_t>(lower.size()), 1941 static_cast<int32_t>(upper.size()), 1942 static_cast<int32_t>(steps.size()), 1943 static_cast<int32_t>(inputs.size()), 1944 static_cast<int32_t>(outputs.size())})); 1945 1946 // Parse the body. 1947 Region *body = result.addRegion(); 1948 1949 SmallVector<Type, 4> regionTypes(ivs.size(), builder.getIndexType()); 1950 regionTypes.append(inputTypes); 1951 regionTypes.append(outputTypes); 1952 1953 SmallVector<OpAsmParser::OperandType, 4> regionArgs(ivs); 1954 regionArgs.append(inputRegionArgs); 1955 regionArgs.append(outputRegionArgs); 1956 1957 if (parser.parseRegion(*body, regionArgs, regionTypes)) 1958 return failure(); 1959 1960 // Parse optional attributes. 1961 parser.parseOptionalAttrDict(result.attributes); 1962 1963 return success(); 1964 } 1965 1966 Region &TiledLoopOp::getLoopBody() { return region(); } 1967 1968 LogicalResult TiledLoopOp::moveOutOfLoop(ArrayRef<Operation *> ops) { 1969 for (auto *op : ops) 1970 op->moveBefore(*this); 1971 return success(); 1972 } 1973 1974 bool TiledLoopOp::isDefinedOutsideOfLoop(Value value) { 1975 return !region().isAncestor(value.getParentRegion()); 1976 } 1977 1978 static LogicalResult verify(TiledLoopOp op) { 1979 // Check if iterator types are provided for every loop dimension. 1980 if (op.iterator_types().size() != op.getNumLoops()) 1981 return op.emitOpError("expected iterator types array attribute size = ") 1982 << op.iterator_types().size() 1983 << " to match the number of loops = " << op.getNumLoops(); 1984 1985 // Check if types of input arguments match region args types. 1986 for (auto &item : 1987 llvm::enumerate(llvm::zip(op.inputs(), op.getRegionInputArgs()))) { 1988 Value input, inputRegionArg; 1989 unsigned index = item.index(); 1990 std::tie(input, inputRegionArg) = item.value(); 1991 if (input.getType() != inputRegionArg.getType()) 1992 return op.emitOpError("expected input arg ") 1993 << index << " with type = " << input.getType() 1994 << " to match region arg " << index + op.getNumLoops() 1995 << " type = " << inputRegionArg.getType(); 1996 } 1997 1998 // Check if types of input arguments match region args types. 1999 for (auto &item : 2000 llvm::enumerate(llvm::zip(op.outputs(), op.getRegionOutputArgs()))) { 2001 Value output, outputRegionArg; 2002 unsigned index = item.index(); 2003 std::tie(output, outputRegionArg) = item.value(); 2004 if (output.getType() != outputRegionArg.getType()) 2005 return op.emitOpError("expected output arg ") 2006 << index << " with type = " << output.getType() 2007 << " to match region arg " 2008 << index + op.getNumLoops() + op.inputs().size() 2009 << " type = " << outputRegionArg.getType(); 2010 } 2011 return success(); 2012 } 2013 2014 namespace { 2015 2016 static constexpr int64_t kNoMatch = -1; 2017 2018 // Folds away TiledLoopOp inputs if they have no uses within the body. 2019 // 2020 // Example: 2021 // 2022 // %0 = linalg.tiled_loop ... ins (%in_ = %in: tensor<...>, 2023 // %in_buf_ = %in_buf: memref<...>) {...} 2024 // Becomes 2025 // 2026 // linalg.tiled_loop ... ins (%in_buf_ = %in_buf: memref<...>) {...} 2027 struct TiledLoopInputsFolder : public OpRewritePattern<linalg::TiledLoopOp> { 2028 using OpRewritePattern<linalg::TiledLoopOp>::OpRewritePattern; 2029 2030 LogicalResult matchAndRewrite(linalg::TiledLoopOp tiledLoop, 2031 PatternRewriter &rewriter) const final { 2032 SmallVector<Value, 2> newInputs, regionInputTensorArgs; 2033 // Store ids of the corresponding old and new input operands. 2034 SmallVector<int64_t, 2> oldInputIdToNew(tiledLoop.inputs().size(), 2035 kNoMatch); 2036 for (const auto &en : llvm::enumerate( 2037 llvm::zip(tiledLoop.inputs(), tiledLoop.getRegionInputArgs()))) { 2038 Value in, bbArg; 2039 size_t index = en.index(); 2040 std::tie(in, bbArg) = en.value(); 2041 if (!bbArg.use_empty()) { 2042 oldInputIdToNew[index] = newInputs.size(); 2043 newInputs.push_back(in); 2044 } 2045 } 2046 if (newInputs.size() == tiledLoop.inputs().size()) 2047 return failure(); 2048 Location loc = tiledLoop.getLoc(); 2049 auto newTiledLoop = rewriter.create<TiledLoopOp>( 2050 loc, tiledLoop.lowerBound(), tiledLoop.upperBound(), tiledLoop.step(), 2051 newInputs, tiledLoop.outputs(), tiledLoop.iterator_types(), 2052 tiledLoop.distribution_types()); 2053 2054 // Clone the region. 2055 BlockAndValueMapping bvm; 2056 bvm.map(tiledLoop.getInductionVars(), newTiledLoop.getInductionVars()); 2057 bvm.map(tiledLoop.getRegionOutputArgs(), 2058 newTiledLoop.getRegionOutputArgs()); 2059 for (const auto &en : llvm::enumerate(oldInputIdToNew)) 2060 if (en.value() != kNoMatch) 2061 bvm.map(tiledLoop.getRegionInputArgs()[en.index()], 2062 newTiledLoop.getRegionInputArgs()[en.value()]); 2063 OpBuilder innerBuilder = 2064 OpBuilder::atBlockEnd(newTiledLoop.getBody(), rewriter.getListener()); 2065 for (auto &op : *tiledLoop.getBody()) 2066 innerBuilder.clone(op, bvm); 2067 rewriter.replaceOp(tiledLoop, newTiledLoop.getResults()); 2068 2069 return success(); 2070 } 2071 }; 2072 2073 } // namespace 2074 2075 /// A simple, conservative analysis to determine if the loop is shape 2076 /// conserving. I.e., the type of the arg-th yielded value is the same as the 2077 /// type of the corresponding basic block argument of the loop. 2078 /// Note: This function handles only simple cases. Expand as needed. 2079 static bool isShapePreserving(TiledLoopOp loopOp, int64_t arg) { 2080 auto yieldOp = cast<YieldOp>(loopOp.getLoopBody().front().getTerminator()); 2081 if (yieldOp.values().empty()) 2082 // Tiled loop either has no outputs or is a "memref-based version". In 2083 // either case, the loop is shape conserving. 2084 return true; 2085 assert(arg < static_cast<int64_t>(yieldOp.values().size()) && 2086 "arg is out of bounds"); 2087 Value value = yieldOp.values()[arg]; 2088 while (value) { 2089 if (value == loopOp.getRegionOutputArgs()[arg]) 2090 return true; 2091 OpResult opResult = value.dyn_cast<OpResult>(); 2092 if (!opResult) 2093 return false; 2094 2095 using tensor::InsertSliceOp; 2096 value = llvm::TypeSwitch<Operation *, Value>(opResult.getOwner()) 2097 .template Case<InsertSliceOp>( 2098 [&](InsertSliceOp op) { return op.dest(); }) 2099 .template Case<TiledLoopOp>([&](TiledLoopOp loopOp) { 2100 return isShapePreserving(loopOp, opResult.getResultNumber()) 2101 ? loopOp.outputs()[opResult.getResultNumber()] 2102 : Value(); 2103 }) 2104 .Default([&](auto op) { return Value(); }); 2105 } 2106 return false; 2107 } 2108 2109 namespace { 2110 2111 /// Fold dim(x) where `x` is an input/output argument of a TiledLoopOp block 2112 /// to dim(y) where `y` is the initial input/output value of the argument. 2113 /// 2114 /// E.g.: 2115 /// %y = ... : tensor<...> 2116 /// linalg.tiled_loop ... ins(%x = %y : tensor<...>) { 2117 /// tensor.dim %x, %c0 : tensor<...> 2118 /// } 2119 /// 2120 /// is folded to: 2121 /// %y = ... : tensor<...> 2122 /// linalg.tiled_loop ... ins(%x = %y : tensor<...>) { 2123 /// tensor.dim %y, %c0 : tensor<...> 2124 /// } 2125 /// 2126 /// Note: Dim ops are folded only if it can be proven that the runtime type of 2127 /// the yielded value (in case of outputs) does not change with loop iterations. 2128 template <typename OpTy> 2129 struct DimOfTiledLoopInsOutsFolder : public OpRewritePattern<OpTy> { 2130 using OpRewritePattern<OpTy>::OpRewritePattern; 2131 2132 LogicalResult matchAndRewrite(OpTy dimOp, 2133 PatternRewriter &rewriter) const final { 2134 auto src = dimOp.source().template dyn_cast<BlockArgument>(); 2135 if (!src) 2136 return failure(); 2137 auto loopOp = 2138 dyn_cast<TiledLoopOp>(src.getOwner()->getParent()->getParentOp()); 2139 if (!loopOp) 2140 return failure(); 2141 unsigned numLoops = loopOp.getNumLoops(); 2142 unsigned numInputArgs = loopOp.getRegionInputArgs().size(); 2143 if (src.getArgNumber() >= numInputArgs + numLoops && 2144 !isShapePreserving(loopOp, 2145 src.getArgNumber() - numInputArgs - numLoops)) 2146 return failure(); 2147 2148 auto inputArgs = loopOp.getRegionInputArgs(); 2149 auto it1 = llvm::find(inputArgs, src); 2150 if (it1 != inputArgs.end()) { 2151 rewriter.updateRootInPlace(dimOp, [&] { 2152 dimOp.sourceMutable().assign(loopOp.inputs()[it1 - inputArgs.begin()]); 2153 }); 2154 return success(); 2155 } 2156 2157 auto outputArgs = loopOp.getRegionOutputArgs(); 2158 auto it2 = llvm::find(outputArgs, src); 2159 if (it2 != outputArgs.end()) { 2160 rewriter.updateRootInPlace(dimOp, [&] { 2161 dimOp.sourceMutable().assign( 2162 loopOp.outputs()[it2 - outputArgs.begin()]); 2163 }); 2164 return success(); 2165 } 2166 2167 return failure(); 2168 } 2169 }; 2170 2171 /// Fold dim(r) where `r` is the result of a TiledLoopOp to dim(y) where `y` 2172 /// is the initial output value of the loop. 2173 /// 2174 /// E.g.: 2175 /// %y = ... : tensor<...> 2176 /// %r = linalg.tiled_loop ... outs(%i = %y : tensor<...>) { 2177 /// ... 2178 /// } 2179 /// %0 = tensor.dim %r, %c0 : tensor<...> 2180 /// 2181 /// is folded to: 2182 /// %y = ... : tensor<...> 2183 /// linalg.tiled_loop ... outs(%i = %y : tensor<...>) { 2184 /// ... 2185 /// } 2186 /// %0 = tensor.dim %y, %c0 : tensor<...> 2187 /// 2188 /// Note: Dim ops are folded only if it can be proven that the runtime type of 2189 /// the yielded value (in case of outputs) does not change with loop iterations. 2190 template <typename OpTy> 2191 struct DimOfTiledLoopResultFolder : public OpRewritePattern<OpTy> { 2192 using OpRewritePattern<OpTy>::OpRewritePattern; 2193 2194 LogicalResult matchAndRewrite(OpTy dimOp, 2195 PatternRewriter &rewriter) const final { 2196 auto loopOp = dimOp.source().template getDefiningOp<TiledLoopOp>(); 2197 if (!loopOp) 2198 return failure(); 2199 auto opResult = dimOp.source().template cast<OpResult>(); 2200 unsigned resultNumber = opResult.getResultNumber(); 2201 if (!isShapePreserving(loopOp, resultNumber)) 2202 return failure(); 2203 rewriter.updateRootInPlace(dimOp, [&]() { 2204 dimOp.sourceMutable().assign(loopOp.outputs()[resultNumber]); 2205 }); 2206 return success(); 2207 } 2208 }; 2209 2210 // Folds away TiledLoopOp output tensors when the following conditions are met: 2211 // * result of `linalg.tiled_loop` has no uses 2212 // * output tensor is the argument of `linalg.yield` 2213 // 2214 // Example: 2215 // 2216 // %0 = linalg.tiled_loop ... outs (%o_ = %out: tensor<...>, 2217 // %obuf_ = %out_buf: memref<...>) { 2218 // ... 2219 // linalg.yield %o_ : tensor ... 2220 // } 2221 // 2222 // Becomes 2223 // 2224 // linalg.tiled_loop ... outs (%obuf_ = %out_buf: memref<...>) { 2225 // ... 2226 // linalg.yield 2227 // } 2228 struct TiledLoopResultsFolder : public OpRewritePattern<linalg::TiledLoopOp> { 2229 using OpRewritePattern<linalg::TiledLoopOp>::OpRewritePattern; 2230 2231 LogicalResult matchAndRewrite(linalg::TiledLoopOp tiledLoop, 2232 PatternRewriter &rewriter) const final { 2233 if (tiledLoop.getNumResults() == 0) 2234 return failure(); 2235 2236 Block *block = tiledLoop.getBody(); 2237 auto yieldOp = cast<linalg::YieldOp>(block->getTerminator()); 2238 2239 // Match the pattern and collect output buffers that will replace the output 2240 // tensors and also the ops that will be ignored when cloning the body. 2241 SmallVector<Value, 2> newOutputOperands, newYieldArgs; 2242 int resultId = 0; 2243 // Store ids of the corresponding old and new output operands. 2244 SmallVector<int64_t, 2> oldOutputIdToNew(tiledLoop.outputs().size(), 2245 kNoMatch); 2246 // Store ids of the corresponding old and new results. 2247 SmallVector<int64_t, 2> oldResultIdToNew(tiledLoop.getNumResults(), 2248 kNoMatch); 2249 SmallVector<Value, 2> resultReplacement(tiledLoop.getNumResults()); 2250 for (const auto &en : llvm::enumerate( 2251 llvm::zip(tiledLoop.outputs(), tiledLoop.getRegionOutputArgs()))) { 2252 size_t index = en.index(); 2253 Value out = std::get<0>(en.value()); 2254 Value outRegionArg = std::get<1>(en.value()); 2255 2256 if (!out.getType().isa<RankedTensorType>()) { 2257 oldOutputIdToNew[index] = newOutputOperands.size(); 2258 newOutputOperands.push_back(out); 2259 continue; 2260 } 2261 Value result = tiledLoop.getResult(resultId); 2262 Value yieldArg = yieldOp.getOperand(resultId); 2263 if (yieldArg != outRegionArg || !result.use_empty()) { 2264 oldOutputIdToNew[index] = newOutputOperands.size(); 2265 oldResultIdToNew[resultId] = newYieldArgs.size(); 2266 resultReplacement[resultId] = out; 2267 newOutputOperands.push_back(out); 2268 newYieldArgs.push_back(yieldArg); 2269 } 2270 ++resultId; 2271 } 2272 if (newOutputOperands.size() == tiledLoop.outputs().size()) 2273 return failure(); 2274 2275 Location loc = tiledLoop.getLoc(); 2276 auto newTiledLoop = rewriter.create<TiledLoopOp>( 2277 loc, tiledLoop.lowerBound(), tiledLoop.upperBound(), tiledLoop.step(), 2278 tiledLoop.inputs(), newOutputOperands, tiledLoop.iterator_types(), 2279 tiledLoop.distribution_types()); 2280 2281 // Clone the region. 2282 BlockAndValueMapping bvm; 2283 bvm.map(tiledLoop.getInductionVars(), newTiledLoop.getInductionVars()); 2284 bvm.map(tiledLoop.getRegionInputArgs(), newTiledLoop.getRegionInputArgs()); 2285 for (const auto &en : llvm::enumerate(oldOutputIdToNew)) { 2286 if (en.value() != kNoMatch) 2287 bvm.map(tiledLoop.getRegionOutputArgs()[en.index()], 2288 newTiledLoop.getRegionOutputArgs()[en.value()]); 2289 else 2290 bvm.map(tiledLoop.getRegionOutputArgs()[en.index()], 2291 tiledLoop.outputs()[en.index()]); 2292 } 2293 OpBuilder innerBuilder = 2294 OpBuilder::atBlockEnd(newTiledLoop.getBody(), rewriter.getListener()); 2295 for (auto &op : tiledLoop.getBody()->without_terminator()) 2296 innerBuilder.clone(op, bvm); 2297 innerBuilder.create<linalg::YieldOp>( 2298 loc, llvm::to_vector<2>(llvm::map_range( 2299 newYieldArgs, [&](Value arg) { return bvm.lookup(arg); }))); 2300 2301 for (const auto &en : llvm::enumerate(oldResultIdToNew)) 2302 if (en.value() != kNoMatch) 2303 resultReplacement[en.index()] = newTiledLoop.getResult(en.value()); 2304 rewriter.replaceOp(tiledLoop, resultReplacement); 2305 2306 return success(); 2307 } 2308 }; 2309 } // namespace 2310 2311 void TiledLoopOp::getCanonicalizationPatterns(OwningRewritePatternList &results, 2312 MLIRContext *context) { 2313 results.insert<TiledLoopInputsFolder, TiledLoopResultsFolder, 2314 DimOfTiledLoopInsOutsFolder<tensor::DimOp>, 2315 DimOfTiledLoopInsOutsFolder<memref::DimOp>, 2316 DimOfTiledLoopResultFolder<tensor::DimOp>, 2317 DimOfTiledLoopResultFolder<memref::DimOp>>(context); 2318 } 2319 2320 LogicalResult TiledLoopOp::fold(ArrayRef<Attribute>, 2321 SmallVectorImpl<OpFoldResult> &) { 2322 return foldMemRefCastInTiledLoopOp(*this); 2323 } 2324 2325 //===----------------------------------------------------------------------===// 2326 // IndexOp 2327 //===----------------------------------------------------------------------===// 2328 2329 static LogicalResult verify(IndexOp op) { 2330 auto linalgOp = dyn_cast<LinalgOp>(op->getParentOp()); 2331 if (!linalgOp) 2332 return op.emitOpError("expected parent op with LinalgOp interface"); 2333 if (linalgOp.getNumLoops() <= op.dim()) 2334 return op.emitOpError("expected dim (") 2335 << op.dim() << ") to be lower than the number of loops (" 2336 << linalgOp.getNumLoops() << ") of the enclosing LinalgOp"; 2337 return success(); 2338 } 2339 2340 /////// Operations corresponding to library calls defined with Tablegen //////// 2341 2342 #include "mlir/Dialect/Linalg/IR/LinalgNamedStructuredOps.yamlgen.cpp.inc" 2343 2344 #define GET_OP_CLASSES 2345 #include "mlir/Dialect/Linalg/IR/LinalgOps.cpp.inc" 2346 2347 #define GET_OP_CLASSES 2348 #include "mlir/Dialect/Linalg/IR/LinalgStructuredOps.cpp.inc" 2349 2350 /// Return the dims that are `iteratorTypeName` loops in the LinalgOp `op`. 2351 /// Assumes `op` is a LinalgOp. 2352 void mlir::linalg::getDimsOfType(Operation *op, StringRef iteratorTypeName, 2353 SmallVectorImpl<unsigned> &res) { 2354 if (!cast<LinalgOp>(op).iterator_types()) 2355 return; 2356 2357 unsigned dim = 0; 2358 for (auto tn : 2359 cast<LinalgOp>(op).iterator_types().getAsValueRange<StringAttr>()) { 2360 if (tn == iteratorTypeName) 2361 res.push_back(dim); 2362 ++dim; 2363 } 2364 } 2365 2366 AffineMap mlir::linalg::extractOrIdentityMap(Optional<AffineMap> maybeMap, 2367 unsigned rank, 2368 MLIRContext *context) { 2369 if (maybeMap) 2370 return maybeMap.getValue(); 2371 if (rank == 0) 2372 return AffineMap::get(context); 2373 return AffineMap::getMultiDimIdentityMap(rank, context); 2374 } 2375 2376 SmallVector<AffineExpr, 4> 2377 mlir::linalg::makeAffineDimExprs(unsigned num, unsigned &startIdx, 2378 MLIRContext *context) { 2379 SmallVector<AffineExpr, 4> res; 2380 res.reserve(num); 2381 for (unsigned i = 0; i < num; ++i) 2382 res.push_back(getAffineDimExpr(startIdx++, context)); 2383 return res; 2384 } 2385 2386 SmallVector<AffineExpr, 4> mlir::linalg::concat(ArrayRef<AffineExpr> a, 2387 ArrayRef<AffineExpr> b) { 2388 auto rangeA = llvm::make_range(a.begin(), a.end()); 2389 auto rangeB = llvm::make_range(b.begin(), b.end()); 2390 auto concatRanges = llvm::concat<const AffineExpr>(rangeA, rangeB); 2391 return llvm::to_vector<4>(concatRanges); 2392 } 2393 2394 static void appendMangledType(llvm::raw_string_ostream &ss, Type t) { 2395 if (auto memref = t.dyn_cast<MemRefType>()) { 2396 ss << "view"; 2397 for (auto size : memref.getShape()) 2398 if (size < 0) 2399 ss << "sx"; 2400 else 2401 ss << size << "x"; 2402 appendMangledType(ss, memref.getElementType()); 2403 } else if (auto vec = t.dyn_cast<VectorType>()) { 2404 ss << "vector"; 2405 llvm::interleave( 2406 vec.getShape(), [&](int64_t i) { ss << i; }, [&]() { ss << "x"; }); 2407 appendMangledType(ss, vec.getElementType()); 2408 } else if (t.isSignlessIntOrIndexOrFloat()) { 2409 ss << t; 2410 } else { 2411 llvm_unreachable("Invalid type for linalg library name mangling"); 2412 } 2413 } 2414 2415 std::string mlir::linalg::generateLibraryCallName(Operation *op) { 2416 assert(isa<LinalgOp>(op)); 2417 std::string name(op->getName().getStringRef().str()); 2418 name.reserve(128); 2419 std::replace(name.begin(), name.end(), '.', '_'); 2420 llvm::raw_string_ostream ss(name); 2421 ss << "_"; 2422 auto types = op->getOperandTypes(); 2423 llvm::interleave( 2424 types.begin(), types.end(), [&](Type t) { appendMangledType(ss, t); }, 2425 [&]() { ss << "_"; }); 2426 return ss.str(); 2427 } 2428 2429 //===----------------------------------------------------------------------===// 2430 // Support for named Linalg ops defined in ods-gen. 2431 //===----------------------------------------------------------------------===// 2432 2433 /// Generic entry point to create the block for the region of a LinalgOp. 2434 /// This is used by both named structured ops created by ods-gen and by manually 2435 /// defined C++ ops. 2436 /// This is used by both builders and parsers. 2437 /// This function creates the block in the region with arguments corresponding 2438 /// to the elemental types of `inputTypes` and `outputTypes`, which are asserted 2439 /// to be ShapedType. 2440 template <typename NamedStructuredOpType> 2441 static void fillStructuredOpRegion( 2442 OpBuilder &opBuilder, Region ®ion, TypeRange inputTypes, 2443 TypeRange outputTypes, 2444 llvm::function_ref<void(unsigned, unsigned)> errorHandler) { 2445 assert(llvm::all_of(outputTypes, [](Type t) { return t.isa<ShapedType>(); })); 2446 2447 // TODO: atm all operands go through getElementTypeOrSelf, 2448 // reconsider when we have evidence we need to. 2449 SmallVector<Type, 8> argTypes; 2450 SmallVector<Location, 8> argLocs; 2451 for (auto containers : {inputTypes, outputTypes}) { 2452 for (auto t : containers) { 2453 argTypes.push_back(getElementTypeOrSelf(t)); 2454 2455 // TODO: Pass in a proper location here. 2456 argLocs.push_back(opBuilder.getUnknownLoc()); 2457 } 2458 } 2459 2460 // RAII. 2461 OpBuilder::InsertionGuard guard(opBuilder); 2462 Block *body = 2463 opBuilder.createBlock(®ion, /*insertPt=*/{}, argTypes, argLocs); 2464 unsigned actual = body->getNumArguments(); 2465 unsigned expected = NamedStructuredOpType::getNumRegionArgs(); 2466 if (expected != actual) { 2467 if (errorHandler) 2468 errorHandler(expected, actual); 2469 return; 2470 } 2471 2472 opBuilder.setInsertionPointToStart(body); 2473 ImplicitLocOpBuilder b(opBuilder.getUnknownLoc(), opBuilder); 2474 NamedStructuredOpType::regionBuilder(b, *body); 2475 2476 // indexing_maps is an auto-generated method. 2477 2478 // iterator_types is an auto-generated method. 2479 } 2480 2481 /// Generic entry point to create both the region and the block of a LinalgOp. 2482 template <typename NamedStructuredOpType> 2483 void createAndFillStructuredOpRegion(OpBuilder &opBuilder, 2484 OperationState &result, 2485 TypeRange inputTypes, 2486 TypeRange outputTypes) { 2487 Region ®ion = *result.addRegion(); 2488 fillStructuredOpRegion<NamedStructuredOpType>( 2489 opBuilder, region, inputTypes, outputTypes, 2490 [&](unsigned expected, unsigned actual) { 2491 assert(expected != actual && "incorrect number of arguments"); 2492 }); 2493 } 2494 2495 /// Common parsing used for both named structured ops created by ods-gen and by 2496 /// manually defined C++ ops. Does not handle regions. 2497 static ParseResult 2498 parseCommonStructuredOpParts(OpAsmParser &parser, OperationState &result, 2499 SmallVectorImpl<Type> &inputTypes, 2500 SmallVectorImpl<Type> &outputTypes) { 2501 llvm::SMLoc inputsOperandsLoc, outputsOperandsLoc; 2502 SmallVector<OpAsmParser::OperandType, 4> inputsOperands, outputsOperands; 2503 2504 parser.parseOptionalAttrDict(result.attributes); 2505 2506 if (succeeded(parser.parseOptionalKeyword("ins"))) { 2507 if (parser.parseLParen()) 2508 return failure(); 2509 2510 inputsOperandsLoc = parser.getCurrentLocation(); 2511 if (parser.parseOperandList(inputsOperands) || 2512 parser.parseColonTypeList(inputTypes) || parser.parseRParen()) 2513 return failure(); 2514 } 2515 2516 if (succeeded(parser.parseOptionalKeyword("outs"))) { 2517 outputsOperandsLoc = parser.getCurrentLocation(); 2518 if (parser.parseLParen() || parser.parseOperandList(outputsOperands) || 2519 parser.parseColonTypeList(outputTypes) || parser.parseRParen()) 2520 return failure(); 2521 } 2522 2523 if (parser.resolveOperands(inputsOperands, inputTypes, inputsOperandsLoc, 2524 result.operands) || 2525 parser.resolveOperands(outputsOperands, outputTypes, outputsOperandsLoc, 2526 result.operands)) 2527 return failure(); 2528 2529 result.addAttribute("operand_segment_sizes", 2530 parser.getBuilder().getI32VectorAttr( 2531 {static_cast<int32_t>(inputsOperands.size()), 2532 static_cast<int32_t>(outputsOperands.size())})); 2533 return success(); 2534 } 2535 2536 template <typename NamedStructuredOpType> 2537 static void printCommonStructuredOpParts(OpAsmPrinter &p, 2538 NamedStructuredOpType op) { 2539 if (!op.inputs().empty()) 2540 p << " ins(" << op.inputs() << " : " << op.inputs().getTypes() << ")"; 2541 if (!op.outputs().empty()) 2542 p << " outs(" << op.outputs() << " : " << op.outputs().getTypes() << ")"; 2543 } 2544 2545 //===----------------------------------------------------------------------===// 2546 // Specific parsing and printing for named structured ops created by ods-gen. 2547 //===----------------------------------------------------------------------===// 2548 2549 template <typename NamedStructuredOpType> 2550 static ParseResult 2551 parseNamedStructuredOpRegion(OpAsmParser &parser, Region ®ion, 2552 TypeRange inputTypes, TypeRange outputTypes) { 2553 ParseResult res = success(); 2554 OpBuilder opBuilder(parser.getContext()); 2555 // Resolve `captures` into `capturedValues` at parse time so we can build the 2556 // region with captures. 2557 SmallVector<Value> capturedValues; 2558 fillStructuredOpRegion<NamedStructuredOpType>( 2559 opBuilder, region, inputTypes, outputTypes, 2560 [&](unsigned expected, unsigned actual) { 2561 res = parser.emitError( 2562 parser.getCurrentLocation(), 2563 llvm::formatv("[parseNamedStructuredOpRegion] ods-gen generated " 2564 "region expects {0} args, got {1}", 2565 expected, actual)); 2566 region.front().dump(); 2567 }); 2568 return res; 2569 } 2570 2571 static ParseResult 2572 parseNamedStructuredOpResults(OpAsmParser &parser, 2573 SmallVectorImpl<Type> &resultTypes) { 2574 if (parser.parseOptionalArrowTypeList(resultTypes)) 2575 return failure(); 2576 return success(); 2577 } 2578 2579 template <typename NamedStructuredOpType> 2580 static ParseResult parseNamedStructuredOp(OpAsmParser &parser, 2581 OperationState &result) { 2582 // TODO: Enable when ods-gen supports captures. 2583 SmallVector<Type, 1> inputTypes, outputTypes; 2584 if (parseCommonStructuredOpParts(parser, result, inputTypes, outputTypes)) 2585 return failure(); 2586 2587 // TODO: consider merging results parsing into region parsing. 2588 // Need to wait for declarative assembly resolution to decide. 2589 SmallVector<Type, 1> outputTensorsTypes; 2590 if (parseNamedStructuredOpResults(parser, outputTensorsTypes)) 2591 return failure(); 2592 result.addTypes(outputTensorsTypes); 2593 2594 std::unique_ptr<Region> region = std::make_unique<Region>(); 2595 if (parseNamedStructuredOpRegion<NamedStructuredOpType>( 2596 parser, *region, inputTypes, outputTypes)) 2597 return failure(); 2598 result.addRegion(std::move(region)); 2599 2600 return success(); 2601 } 2602 2603 static void printNamedStructuredOpResults(OpAsmPrinter &p, 2604 TypeRange resultTypes) { 2605 if (resultTypes.empty()) 2606 return; 2607 p.printOptionalArrowTypeList(resultTypes); 2608 } 2609 2610 template <typename NamedStructuredOpType> 2611 static void printNamedStructuredOp(OpAsmPrinter &p, NamedStructuredOpType op) { 2612 p.printOptionalAttrDict( 2613 op->getAttrs(), 2614 /*elidedAttrs=*/{"operand_segment_sizes", 2615 // See generated code in mlir-linalg-yaml-gen.cpp 2616 "linalg.memoized_indexing_maps"}); 2617 2618 // Printing is shared with generic ops, except for the region and 2619 // attributes. 2620 printCommonStructuredOpParts(p, op); 2621 2622 // Results printing. 2623 printNamedStructuredOpResults(p, op.result_tensors().getTypes()); 2624 2625 // Region is elided. 2626 } 2627 2628 template <typename NamedStructuredOpType> 2629 static LogicalResult verifyNamedStructuredOp(NamedStructuredOpType op) { 2630 return verifyGenericOp<NamedStructuredOpType>(op); 2631 } 2632 2633 //===----------------------------------------------------------------------===// 2634 // Canonicalizers and Folders. 2635 //===----------------------------------------------------------------------===// 2636 2637 namespace { 2638 struct EraseDeadLinalgOp : public OpInterfaceRewritePattern<LinalgOp> { 2639 using OpInterfaceRewritePattern<LinalgOp>::OpInterfaceRewritePattern; 2640 2641 LogicalResult matchAndRewrite(LinalgOp op, 2642 PatternRewriter &rewriter) const override { 2643 for (OpOperand *opOperand : op.getInputAndOutputOperands()) { 2644 // Linalg "inputs" may be either tensor or memref type. 2645 // tensor<0xelt_type> is a convention that may not always mean 2646 // "0 iterations". Only erase in cases we see memref<...x0x...>. 2647 auto mt = opOperand->get().getType().dyn_cast<MemRefType>(); 2648 if (!mt) 2649 continue; 2650 if (llvm::is_contained(op.getShape(opOperand), 0)) { 2651 rewriter.eraseOp(op); 2652 return success(); 2653 } 2654 } 2655 return failure(); 2656 } 2657 }; 2658 2659 struct FoldTensorCastOp : public OpInterfaceRewritePattern<LinalgOp> { 2660 using OpInterfaceRewritePattern<LinalgOp>::OpInterfaceRewritePattern; 2661 2662 LogicalResult matchAndRewrite(LinalgOp op, 2663 PatternRewriter &rewriter) const override { 2664 // If no operand comes from a tensor::CastOp and can be folded then fail. 2665 bool hasTensorCastOperand = 2666 llvm::any_of(op.getInputAndOutputOperands(), [&](OpOperand *opOperand) { 2667 if (opOperand->get().isa<BlockArgument>()) 2668 return false; 2669 auto castOp = opOperand->get().getDefiningOp<tensor::CastOp>(); 2670 return castOp && canFoldIntoConsumerOp(castOp); 2671 }); 2672 if (!hasTensorCastOperand) 2673 return failure(); 2674 2675 SmallVector<Type, 4> newResultTypes; 2676 newResultTypes.reserve(op->getNumResults()); 2677 SmallVector<Value, 4> newOperands; 2678 newOperands.reserve(op->getNumOperands()); 2679 // Inputs may fold. 2680 for (OpOperand *opOperand : op.getInputOperands()) { 2681 auto tensorCastOp = opOperand->get().getDefiningOp<tensor::CastOp>(); 2682 newOperands.push_back(canFoldIntoConsumerOp(tensorCastOp) 2683 ? tensorCastOp.source() 2684 : opOperand->get()); 2685 } 2686 // Init tensors may fold, in which case the resultType must also change. 2687 for (OpOperand *opOperand : op.getOutputOperands()) { 2688 auto tensorCastOp = opOperand->get().getDefiningOp<tensor::CastOp>(); 2689 bool fold = canFoldIntoConsumerOp(tensorCastOp); 2690 newOperands.push_back(fold ? tensorCastOp.getOperand() 2691 : opOperand->get()); 2692 newResultTypes.push_back(newOperands.back().getType()); 2693 } 2694 // Clone op. 2695 Operation *newOp = 2696 op.clone(rewriter, op->getLoc(), newResultTypes, newOperands); 2697 SmallVector<Value, 4> replacements; 2698 replacements.reserve(newOp->getNumResults()); 2699 for (auto result : llvm::zip(op->getResults(), newOp->getResults())) { 2700 Value oldResult = std::get<0>(result); 2701 Value newResult = std::get<1>(result); 2702 if (newResult.getType() != oldResult.getType()) { 2703 replacements.push_back(rewriter.create<tensor::CastOp>( 2704 op->getLoc(), oldResult.getType(), newResult)); 2705 } else { 2706 replacements.push_back(newResult); 2707 } 2708 } 2709 rewriter.replaceOp(op, replacements); 2710 2711 return success(); 2712 } 2713 }; 2714 2715 } // namespace 2716 2717 #define LINALGOP_FOLDERS(XXX) \ 2718 LogicalResult XXX::fold(ArrayRef<Attribute>, \ 2719 SmallVectorImpl<OpFoldResult> &) { \ 2720 return foldMemRefCast(*this); \ 2721 } 2722 2723 LINALGOP_FOLDERS(CopyOp) 2724 LINALGOP_FOLDERS(FillOp) 2725 LINALGOP_FOLDERS(GenericOp) 2726 2727 // All named ops canonicalizers and folders are auto-generated in the 2728 // .cpp.inc. 2729 2730 //===----------------------------------------------------------------------===// 2731 // LinalgDialect 2732 //===----------------------------------------------------------------------===// 2733 2734 void LinalgDialect::getCanonicalizationPatterns( 2735 RewritePatternSet &results) const { 2736 results.add<EraseDeadLinalgOp, FoldTensorCastOp>(getContext()); 2737 } 2738 2739 Operation *LinalgDialect::materializeConstant(OpBuilder &builder, 2740 Attribute value, Type type, 2741 Location loc) { 2742 return builder.create<arith::ConstantOp>(loc, type, value); 2743 } 2744