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