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/SCF/SCF.h" 19 #include "mlir/Dialect/StandardOps/IR/Ops.h" 20 #include "mlir/Dialect/StandardOps/Utils/Utils.h" 21 #include "mlir/Dialect/Tensor/IR/Tensor.h" 22 #include "mlir/Dialect/Utils/ReshapeOpsUtils.h" 23 #include "mlir/Dialect/Utils/StaticValueUtils.h" 24 #include "mlir/IR/AffineExprVisitor.h" 25 #include "mlir/IR/Matchers.h" 26 #include "mlir/IR/OpImplementation.h" 27 #include "mlir/IR/PatternMatch.h" 28 #include "mlir/Interfaces/InferTypeOpInterface.h" 29 #include "mlir/Parser.h" 30 31 #include "llvm/ADT/DenseMap.h" 32 #include "llvm/ADT/SetVector.h" 33 #include "llvm/ADT/SmallSet.h" 34 #include "llvm/ADT/StringSet.h" 35 #include "llvm/ADT/TypeSwitch.h" 36 #include "llvm/Support/FormatVariadic.h" 37 #include "llvm/Support/MathExtras.h" 38 #include "llvm/Support/raw_ostream.h" 39 40 using namespace mlir; 41 using namespace mlir::linalg; 42 43 #include "mlir/Dialect/Linalg/IR/LinalgOpsDialect.cpp.inc" 44 45 /// Forward declarations. 46 47 /// Generic entry point to create the block for the region of a LinalgOp. 48 /// This is used by both named structured ops created by ods-gen and by manually 49 /// defined C++ ops. 50 /// This is used by both builders and parsers. 51 /// This function creates the block in the region with arguments corresponding 52 /// to the elemental types of `inputTypes` and `outputTypes`. The latter are 53 /// asserted to be of ShapedType. 54 template <typename NamedStructuredOpType> 55 static void fillStructuredOpRegion( 56 OpBuilder &opBuilder, Region ®ion, TypeRange inputTypes, 57 TypeRange outputTypes, 58 std::function<void(unsigned, unsigned)> errorHandler = nullptr); 59 60 /// Generic entry point to create both the region and the block of a LinalgOp. 61 template <typename NamedStructuredOpType> 62 static void 63 createAndFillStructuredOpRegion(OpBuilder &opBuilder, OperationState &result, 64 TypeRange inputTypes, TypeRange outputTypes); 65 66 /// Common parsing and printing used for both named structured ops created by 67 /// ods-gen and by manually defined C++ ops. Does not handle regions. 68 static ParseResult 69 parseCommonStructuredOpParts(OpAsmParser &parser, OperationState &result, 70 SmallVectorImpl<Type> &inputTypes, 71 SmallVectorImpl<Type> &outputTypes); 72 template <typename NamedStructuredOpType> 73 static void printCommonStructuredOpParts(OpAsmPrinter &p, 74 NamedStructuredOpType op); 75 76 /// Specific parsing and printing for named structured ops created by ods-gen. 77 template <typename NamedStructuredOpType> 78 static ParseResult 79 parseNamedStructuredOpRegion(OpAsmParser &parser, Region ®ion, 80 TypeRange inputTypes, TypeRange outputTypes); 81 82 static ParseResult 83 parseNamedStructuredOpResults(OpAsmParser &parser, 84 SmallVectorImpl<Type> &resultTypes); 85 86 template <typename NamedStructuredOpType> 87 static ParseResult parseNamedStructuredOp(OpAsmParser &parser, 88 OperationState &result); 89 90 static void printNamedStructuredOpResults(OpAsmPrinter &p, 91 TypeRange resultTypes); 92 93 template <typename NamedStructuredOpType> 94 static void printNamedStructuredOp(OpAsmPrinter &p, NamedStructuredOpType op); 95 96 /// Helper function to convert a vector of `OpFoldResult`s into a vector of 97 /// `Value`s. 98 static SmallVector<Value> getAsValues(OpBuilder &b, Location loc, 99 ArrayRef<OpFoldResult> valueOrAttrVec) { 100 return llvm::to_vector<4>( 101 llvm::map_range(valueOrAttrVec, [&](OpFoldResult value) -> Value { 102 return getValueOrCreateConstantIndexOp(b, loc, 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 ArrayRef<NamedAttribute> attributes) { 507 build(builder, result, resultTensorTypes, inputs, outputs, 508 builder.getAffineMapArrayAttr(indexingMaps), 509 builder.getStrArrayAttr(iteratorTypes), 510 doc.empty() ? StringAttr() : builder.getStringAttr(doc), 511 libraryCall.empty() ? StringAttr() 512 : builder.getStringAttr(libraryCall)); 513 result.addAttributes(attributes); 514 if (!bodyBuild) 515 return; 516 517 SmallVector<Type, 4> blockArgTypes; 518 for (ValueRange container : {inputs, outputs}) 519 for (Value v : container) 520 blockArgTypes.push_back(getElementTypeOrSelf(v)); 521 522 OpBuilder::InsertionGuard guard(builder); 523 auto ®ion = *result.regions.front(); 524 Block *bodyBlock = builder.createBlock(®ion, region.end(), blockArgTypes); 525 bodyBuild(builder, result.location, bodyBlock->getArguments()); 526 } 527 528 void GenericOp::build( 529 OpBuilder &builder, OperationState &result, ValueRange inputs, 530 ValueRange outputs, ArrayRef<AffineMap> indexingMaps, 531 ArrayRef<StringRef> iteratorTypes, StringRef doc, StringRef libraryCall, 532 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild, 533 ArrayRef<NamedAttribute> attributes) { 534 build(builder, result, TypeRange{}, inputs, outputs, indexingMaps, 535 iteratorTypes, doc, libraryCall, bodyBuild, attributes); 536 } 537 538 void GenericOp::build( 539 OpBuilder &builder, OperationState &result, ValueRange inputs, 540 ValueRange outputs, ArrayRef<AffineMap> indexingMaps, 541 ArrayRef<StringRef> iteratorTypes, 542 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild, 543 ArrayRef<NamedAttribute> attributes) { 544 build(builder, result, inputs, outputs, indexingMaps, iteratorTypes, 545 /*doc=*/"", 546 /*libraryCall=*/"", bodyBuild, attributes); 547 } 548 549 void GenericOp::build( 550 OpBuilder &builder, OperationState &result, TypeRange resultTensorTypes, 551 ValueRange inputs, ValueRange outputs, ArrayRef<AffineMap> indexingMaps, 552 ArrayRef<StringRef> iteratorTypes, 553 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild, 554 ArrayRef<NamedAttribute> attributes) { 555 build(builder, result, resultTensorTypes, inputs, outputs, indexingMaps, 556 iteratorTypes, 557 /*doc=*/"", 558 /*libraryCall=*/"", bodyBuild, attributes); 559 } 560 561 static void print(OpAsmPrinter &p, GenericOp op) { 562 p << " "; 563 564 // Print extra attributes. 565 auto genericAttrNames = op.linalgTraitAttrNames(); 566 567 llvm::StringSet<> genericAttrNamesSet; 568 genericAttrNamesSet.insert(genericAttrNames.begin(), genericAttrNames.end()); 569 SmallVector<NamedAttribute, 8> genericAttrs; 570 for (auto attr : op->getAttrs()) 571 if (genericAttrNamesSet.count(attr.first.strref()) > 0) 572 genericAttrs.push_back(attr); 573 if (!genericAttrs.empty()) { 574 auto genericDictAttr = DictionaryAttr::get(op.getContext(), genericAttrs); 575 p << genericDictAttr; 576 } 577 578 // Printing is shared with named ops, except for the region and attributes 579 printCommonStructuredOpParts(p, op); 580 581 genericAttrNames.push_back("operand_segment_sizes"); 582 genericAttrNamesSet.insert(genericAttrNames.back()); 583 584 bool hasExtraAttrs = false; 585 for (NamedAttribute n : op->getAttrs()) { 586 if ((hasExtraAttrs = !genericAttrNamesSet.contains(n.first.strref()))) 587 break; 588 } 589 if (hasExtraAttrs) { 590 p << " attrs = "; 591 p.printOptionalAttrDict(op->getAttrs(), /*elidedAttrs=*/genericAttrNames); 592 } 593 594 // Print region. 595 if (!op.region().empty()) 596 p.printRegion(op.region()); 597 598 // Print results. 599 printNamedStructuredOpResults(p, op.result_tensors().getTypes()); 600 } 601 602 static ParseResult parseGenericOp(OpAsmParser &parser, OperationState &result) { 603 DictionaryAttr dictAttr; 604 // Parse the core linalg traits that must check into a dictAttr. 605 // The name is unimportant as we will overwrite result.attributes. 606 // The core linalg traits must contain the information necessary to pass the 607 // verifier. 608 if (parser.parseAttribute(dictAttr, "_", result.attributes)) 609 return failure(); 610 result.attributes.assign(dictAttr.getValue().begin(), 611 dictAttr.getValue().end()); 612 613 // Parsing is shared with named ops, except for the region. 614 SmallVector<Type, 1> inputTypes, outputTypes; 615 if (parseCommonStructuredOpParts(parser, result, inputTypes, outputTypes)) 616 return failure(); 617 618 // Optional attributes may be added. 619 if (succeeded(parser.parseOptionalKeyword("attrs"))) 620 if (failed(parser.parseEqual()) || 621 failed(parser.parseOptionalAttrDict(result.attributes))) 622 return failure(); 623 624 SmallVector<OpAsmParser::OperandType, 8> regionOperands; 625 std::unique_ptr<Region> region = std::make_unique<Region>(); 626 SmallVector<Type, 8> operandTypes, regionTypes; 627 if (parser.parseRegion(*region, regionOperands, regionTypes)) 628 return failure(); 629 result.addRegion(std::move(region)); 630 631 // Generic ops may specify that a subset of its outputs are tensors. Such 632 // outputs are specified in the result type. 633 // TODO: may need to move output parsing before region parsing. 634 // Need to wait for declarative assembly resolution to decide. 635 SmallVector<Type, 1> outputTensorsTypes; 636 if (parseNamedStructuredOpResults(parser, outputTensorsTypes)) 637 return failure(); 638 result.addTypes(outputTensorsTypes); 639 640 return success(); 641 } 642 643 static void getGenericEffectsImpl( 644 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>> 645 &effects, 646 ValueRange results, ValueRange inputBuffers, ValueRange outputs) { 647 for (Value value : results) { 648 effects.emplace_back(MemoryEffects::Allocate::get(), value, 649 SideEffects::DefaultResource::get()); 650 } 651 for (Value value : inputBuffers) { 652 effects.emplace_back(MemoryEffects::Read::get(), value, 653 SideEffects::DefaultResource::get()); 654 } 655 for (Value value : outputs) { 656 effects.emplace_back(MemoryEffects::Read::get(), value, 657 SideEffects::DefaultResource::get()); 658 effects.emplace_back(MemoryEffects::Write::get(), value, 659 SideEffects::DefaultResource::get()); 660 } 661 } 662 663 void GenericOp::getEffects( 664 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>> 665 &effects) { 666 SmallVector<Value> inputBuffers = getInputBufferOperands(); 667 SmallVector<Value> outputBuffers = getOutputBufferOperands(); 668 getGenericEffectsImpl(effects, getOperation()->getResults(), inputBuffers, 669 outputBuffers); 670 } 671 672 template <typename GenericOpType> 673 static LogicalResult verifyGenericOp(GenericOpType op) { 674 return success(); 675 } 676 677 static LogicalResult verify(GenericOp op) { return verifyGenericOp(op); } 678 679 namespace { 680 // Deduplicate redundant args of a linalg generic op. 681 // An arg is redundant if it has the same Value and indexing map as another. 682 struct DeduplicateGenericOpInputs : public OpRewritePattern<GenericOp> { 683 using OpRewritePattern<GenericOp>::OpRewritePattern; 684 685 LogicalResult matchAndRewrite(GenericOp genericOp, 686 PatternRewriter &rewriter) const override { 687 // Associate each input to an equivalent "canonical" input that has the same 688 // Value and indexing map. 689 // 690 // In the non-duplicate case, input `i` will have canonical input `i`. But 691 // in the case of duplicated inputs, the canonical input could be some other 692 // input `< i`. That is, a later input will have some earlier input as its 693 // canonical input. 694 llvm::SmallDenseMap<std::pair<Value, AffineMap>, unsigned> canonicalInput; 695 // For later remapping tasks like deduplicating payload block arguments, 696 // having a simple "inputIndex -> canonicalInputIndex" integer mapping is 697 // convenient. 698 SmallVector<unsigned> canonicalInputIndices; 699 for (OpOperand *opOperand : genericOp.getInputOperands()) { 700 AffineMap indexingMap = genericOp.getTiedIndexingMap(opOperand); 701 // STL-like maps have a convenient behavior for our use case here. In the 702 // case of duplicate keys, the insertion is rejected, and the returned 703 // iterator gives access to the value already in the map. 704 auto pair = canonicalInput.insert( 705 {{opOperand->get(), indexingMap}, opOperand->getOperandNumber()}); 706 canonicalInputIndices.push_back(pair.first->second); 707 } 708 709 // If there are no duplicate args, then bail out. 710 if (canonicalInput.size() == genericOp.getNumInputs()) 711 return failure(); 712 713 // The operands for the newly canonicalized op. 714 SmallVector<Value> newInputOperands; 715 for (OpOperand *opOperand : genericOp.getInputOperands()) 716 if (canonicalInputIndices[opOperand->getOperandNumber()] == 717 opOperand->getOperandNumber()) 718 newInputOperands.push_back(opOperand->get()); 719 720 // Repair the indexing maps by filtering out the ones that have been 721 // eliminated. 722 SmallVector<AffineMap> newIndexingMaps; 723 for (OpOperand *opOperand : genericOp.getInputOperands()) 724 if (canonicalInputIndices[opOperand->getOperandNumber()] == 725 opOperand->getOperandNumber()) 726 newIndexingMaps.push_back(genericOp.getTiedIndexingMap(opOperand)); 727 for (OpOperand *opOperand : genericOp.getOutputOperands()) 728 newIndexingMaps.push_back(genericOp.getTiedIndexingMap(opOperand)); 729 730 // Clone the old op with new operands. 731 SmallVector<Value> outputOperands = genericOp.getOutputOperands(); 732 auto newOp = rewriter.create<GenericOp>( 733 genericOp.getLoc(), genericOp->getResultTypes(), newInputOperands, 734 outputOperands, rewriter.getAffineMapArrayAttr(newIndexingMaps), 735 genericOp.iterator_types(), genericOp.docAttr(), 736 genericOp.library_callAttr()); 737 738 // Copy over unknown attributes. They might be load bearing for some flow. 739 ArrayRef<StringRef> odsAttrs = genericOp.getAttributeNames(); 740 for (NamedAttribute kv : genericOp->getAttrs()) { 741 if (!llvm::is_contained(odsAttrs, kv.first.c_str())) { 742 newOp->setAttr(kv.first, kv.second); 743 } 744 } 745 746 rewriter.inlineRegionBefore(genericOp.region(), newOp.region(), 747 newOp.region().begin()); 748 749 // Repair the payload entry block by RAUW'ing redundant arguments and 750 // erasing them. 751 Block &payload = newOp.region().front(); 752 SmallVector<OpOperand *> inputOperands = genericOp.getInputOperands(); 753 for (OpOperand *opOperand : llvm::reverse(inputOperands)) { 754 // Iterate in reverse, so that we erase later args first, preventing the 755 // argument list from shifting unexpectedly and invalidating all our 756 // indices. 757 unsigned operandNumber = opOperand->getOperandNumber(); 758 if (canonicalInputIndices[operandNumber] == operandNumber) 759 continue; 760 payload.getArgument(operandNumber) 761 .replaceAllUsesWith( 762 payload.getArgument(canonicalInputIndices[operandNumber])); 763 payload.eraseArgument(operandNumber); 764 } 765 766 rewriter.replaceOp(genericOp, newOp->getResults()); 767 return success(); 768 } 769 }; 770 771 /// Remove generic operations (on tensors) that are just copying 772 /// the values from inputs to the results. Requirements are 773 /// 1) All iterator types are parallel 774 /// 2) The body contains just a yield operation with the yielded values being 775 /// the arguments corresponding to the operands. 776 struct EraseIdentityGenericOp : public OpRewritePattern<GenericOp> { 777 using OpRewritePattern<GenericOp>::OpRewritePattern; 778 779 LogicalResult matchAndRewrite(GenericOp genericOp, 780 PatternRewriter &rewriter) const override { 781 if (!genericOp.hasTensorSemantics()) 782 return failure(); 783 // Check all indexing maps are identity. 784 if (llvm::any_of(genericOp.getIndexingMaps(), 785 [](AffineMap map) { return !map.isIdentity(); })) 786 return failure(); 787 788 // Check that the body of the linalg operation is just a linalg.yield 789 // operation. 790 Block &body = genericOp.region().front(); 791 if (!llvm::hasSingleElement(body)) 792 return failure(); 793 auto yieldOp = dyn_cast<linalg::YieldOp>(body.getTerminator()); 794 if (!yieldOp) 795 return failure(); 796 797 // Get the argument number of the returned values. That is the operand 798 // number to use for replacing uses of this operation. 799 SmallVector<Value> returnedArgs; 800 for (Value yieldVal : yieldOp.values()) { 801 auto yieldArg = yieldVal.dyn_cast<BlockArgument>(); 802 if (!yieldArg || yieldArg.getOwner() != &body) 803 return failure(); 804 unsigned argumentNumber = yieldArg.getArgNumber(); 805 returnedArgs.push_back(genericOp->getOperand(argumentNumber)); 806 } 807 if (returnedArgs.size() != genericOp->getNumResults()) 808 return failure(); 809 rewriter.replaceOp(genericOp, returnedArgs); 810 return success(); 811 } 812 }; 813 } // namespace 814 815 void GenericOp::getCanonicalizationPatterns(RewritePatternSet &results, 816 MLIRContext *context) { 817 results.add<DeduplicateGenericOpInputs, EraseIdentityGenericOp>(context); 818 } 819 820 //===----------------------------------------------------------------------===// 821 // InitTensorOp 822 //===----------------------------------------------------------------------===// 823 void InitTensorOp::build(OpBuilder &b, OperationState &result, 824 ArrayRef<OpFoldResult> sizes, Type elementType, 825 ArrayRef<NamedAttribute> attrs) { 826 unsigned rank = sizes.size(); 827 SmallVector<Value, 4> dynamicSizes; 828 SmallVector<int64_t, 4> staticSizes; 829 for (unsigned i = 0; i < rank; ++i) { 830 dispatchIndexOpFoldResult(sizes[i], dynamicSizes, staticSizes, 831 ShapedType::kDynamicSize); 832 } 833 auto resultType = RankedTensorType ::get(staticSizes, elementType); 834 build(b, result, resultType, dynamicSizes, b.getI64ArrayAttr(staticSizes)); 835 result.addAttributes(attrs); 836 } 837 838 static LogicalResult verify(InitTensorOp op) { 839 RankedTensorType resultType = op.getType(); 840 SmallVector<int64_t, 4> staticSizes = llvm::to_vector<4>(llvm::map_range( 841 op.static_sizes().cast<ArrayAttr>(), 842 [](Attribute a) -> int64_t { return a.cast<IntegerAttr>().getInt(); })); 843 844 if (failed(verifyListOfOperandsOrIntegers(op, "sizes", resultType.getRank(), 845 op.static_sizes(), op.sizes(), 846 ShapedType::isDynamic))) 847 return failure(); 848 849 if (op.static_sizes().size() != static_cast<unsigned>(resultType.getRank())) 850 return op->emitError("expected ") 851 << resultType.getRank() << " sizes values"; 852 853 Type expectedType = 854 InitTensorOp::inferResultType(staticSizes, resultType.getElementType()); 855 if (resultType != expectedType) { 856 return op.emitError("specified type ") 857 << resultType << " does not match the inferred type " 858 << expectedType; 859 } 860 return success(); 861 } 862 863 Type InitTensorOp::inferResultType(ArrayRef<int64_t> staticSizes, 864 Type elementType) { 865 return RankedTensorType::get(staticSizes, elementType); 866 } 867 868 namespace { 869 /// Change the type of the result of a `linalg.init_tensor` by making the result 870 /// type statically sized along dimension that in the original operation where 871 /// defined as dynamic, but the size was defined using a `constant` op. For 872 /// example 873 /// 874 /// %c5 = constant 5: index 875 /// %0 = linalg.init_tensor [%arg0, %c5] : tensor<?x?xf32> 876 /// 877 /// to 878 /// 879 /// %0 = linalg.init_tensor [%arg0, 5] : tensor<?x5xf32> 880 struct ReplaceStaticShapeDims : OpRewritePattern<InitTensorOp> { 881 using OpRewritePattern<InitTensorOp>::OpRewritePattern; 882 883 LogicalResult matchAndRewrite(InitTensorOp op, 884 PatternRewriter &rewriter) const override { 885 SmallVector<Value, 4> dynamicSizes; 886 SmallVector<int64_t, 4> staticSizes; 887 for (unsigned i = 0, e = op.getType().getRank(); i != e; ++i) { 888 // If the size is already static, nothing to do. 889 if (!op.isDynamicSize(i)) { 890 staticSizes.push_back(op.getStaticSize(i)); 891 continue; 892 } 893 894 // If the size is dynamic but defined using a `constant` op, get the 895 // constant value to find the static size to use. 896 unsigned operandNum = op.getIndexOfDynamicSize(i); 897 Value sizeOperand = op.getOperand(operandNum); 898 if (auto constantIndexOp = sizeOperand.getDefiningOp<ConstantIndexOp>()) { 899 staticSizes.push_back(constantIndexOp.getValue()); 900 continue; 901 } 902 903 // Fallback case. Keep the size dynamic. 904 dynamicSizes.push_back(sizeOperand); 905 staticSizes.push_back(ShapedType::kDynamicSize); 906 } 907 RankedTensorType newType = 908 RankedTensorType::get(staticSizes, op.getType().getElementType()); 909 if (newType == op.getType()) 910 return failure(); 911 auto newOp = 912 rewriter.create<InitTensorOp>(op.getLoc(), newType, dynamicSizes, 913 rewriter.getI64ArrayAttr(staticSizes)); 914 rewriter.replaceOpWithNewOp<tensor::CastOp>(op, op.getType(), newOp); 915 return success(); 916 } 917 }; 918 } // namespace 919 920 namespace { 921 /// Since `init_tensor` operation creates a tensor needed only for its shape, a 922 /// slice of this is also needed only for its shape. The result can be 923 /// replaced by a new init_tensor operation of the same size as the extract 924 /// slice op. 925 struct FoldInitTensorWithExtractSliceOp 926 : public OpRewritePattern<tensor::ExtractSliceOp> { 927 using OpRewritePattern<tensor::ExtractSliceOp>::OpRewritePattern; 928 929 LogicalResult matchAndRewrite(tensor::ExtractSliceOp sliceOp, 930 PatternRewriter &rewriter) const override { 931 if (!sliceOp.source().getDefiningOp<linalg::InitTensorOp>()) 932 return failure(); 933 // ExtractSliceOp may be rank-reducing; its dynamic sizes must be preserved 934 // as well as its result type. 935 rewriter.replaceOpWithNewOp<linalg::InitTensorOp>( 936 sliceOp, sliceOp.sizes(), 937 sliceOp.result().getType().cast<RankedTensorType>().getShape(), 938 sliceOp.getSourceType().getElementType()); 939 return success(); 940 } 941 }; 942 943 template <typename TensorReshapeOp> 944 struct FoldInitTensorWithTensorReshapeOp 945 : public OpRewritePattern<TensorReshapeOp> { 946 using OpRewritePattern<TensorReshapeOp>::OpRewritePattern; 947 948 LogicalResult matchAndRewrite(TensorReshapeOp reshapeOp, 949 PatternRewriter &rewriter) const override { 950 if (!reshapeOp.src().template getDefiningOp<InitTensorOp>()) 951 return failure(); 952 Location loc = reshapeOp.getLoc(); 953 ReifiedRankedShapedTypeDims resultShapes; 954 if (failed(reshapeOp.reifyResultShapes(rewriter, resultShapes)) || 955 !llvm::hasSingleElement(resultShapes)) 956 return failure(); 957 Value initTensor = rewriter.create<InitTensorOp>( 958 loc, getAsOpFoldResult(resultShapes[0]), 959 reshapeOp.getResultType().getElementType()); 960 if (initTensor.getType() != reshapeOp.getResultType()) { 961 rewriter.replaceOpWithNewOp<tensor::CastOp>( 962 reshapeOp, reshapeOp.getResultType(), initTensor); 963 } else { 964 rewriter.replaceOp(reshapeOp, initTensor); 965 } 966 return success(); 967 } 968 }; 969 970 struct FoldInitTensorWithDimOp : public OpRewritePattern<tensor::DimOp> { 971 using OpRewritePattern<tensor::DimOp>::OpRewritePattern; 972 973 LogicalResult matchAndRewrite(tensor::DimOp dimOp, 974 PatternRewriter &rewriter) const override { 975 Optional<int64_t> maybeConstantIndex = dimOp.getConstantIndex(); 976 auto initTensorOp = dimOp.source().getDefiningOp<linalg::InitTensorOp>(); 977 if (!initTensorOp || !maybeConstantIndex) 978 return failure(); 979 if (!initTensorOp.isDynamicSize(*maybeConstantIndex)) 980 return failure(); 981 rewriter.replaceOp(dimOp, initTensorOp.getDynamicSize(*maybeConstantIndex)); 982 return success(); 983 } 984 }; 985 } // namespace 986 987 void InitTensorOp::getCanonicalizationPatterns(RewritePatternSet &results, 988 MLIRContext *context) { 989 results.add<FoldInitTensorWithDimOp, FoldInitTensorWithExtractSliceOp, 990 FoldInitTensorWithTensorReshapeOp<TensorExpandShapeOp>, 991 FoldInitTensorWithTensorReshapeOp<TensorCollapseShapeOp>, 992 ReplaceStaticShapeDims>(context); 993 } 994 995 LogicalResult InitTensorOp::reifyResultShapes( 996 OpBuilder &builder, ReifiedRankedShapedTypeDims &reifiedReturnShapes) { 997 auto shapes = llvm::to_vector<4>(llvm::map_range( 998 llvm::seq<int64_t>(0, getType().getRank()), [&](int64_t dim) -> Value { 999 if (isDynamicSize(dim)) 1000 return getDynamicSize(dim); 1001 return builder.create<ConstantIndexOp>(getLoc(), getStaticSize(dim)); 1002 })); 1003 reifiedReturnShapes.emplace_back(std::move(shapes)); 1004 return success(); 1005 } 1006 1007 //===----------------------------------------------------------------------===// 1008 // PadTensorOp 1009 //===----------------------------------------------------------------------===// 1010 1011 // TODO: Replace custom<InferType> directive with AllTypesMatch as soon as it 1012 // supports optional types. 1013 void printInferType(OpAsmPrinter &printer, Operation *op, Value optOperand, 1014 Type typeToInfer, Type typeToInferFrom) {} 1015 1016 ParseResult parseInferType(OpAsmParser &parser, 1017 Optional<OpAsmParser::OperandType> optOperand, 1018 Type &typeToInfer, Type typeToInferFrom) { 1019 if (optOperand) 1020 typeToInfer = typeToInferFrom; 1021 return success(); 1022 } 1023 1024 static LogicalResult verify(PadTensorOp op) { 1025 auto sourceType = op.source().getType().cast<RankedTensorType>(); 1026 auto resultType = op.result().getType().cast<RankedTensorType>(); 1027 auto expectedType = PadTensorOp::inferResultType( 1028 sourceType, extractFromI64ArrayAttr(op.static_low()), 1029 extractFromI64ArrayAttr(op.static_high())); 1030 for (int i = 0, e = sourceType.getRank(); i < e; ++i) { 1031 if (resultType.getDimSize(i) == expectedType.getDimSize(i)) 1032 continue; 1033 if (expectedType.isDynamicDim(i)) 1034 continue; 1035 return op.emitError("specified type ") 1036 << resultType << " does not match the inferred type " 1037 << expectedType; 1038 } 1039 1040 auto ®ion = op.region(); 1041 unsigned rank = resultType.getRank(); 1042 Block &block = region.front(); 1043 if (block.getNumArguments() != rank) 1044 return op.emitError("expected the block to have ") << rank << " arguments"; 1045 1046 // Note: the number and type of yield values are checked in the YieldOp. 1047 for (auto en : llvm::enumerate(block.getArgumentTypes())) { 1048 if (!en.value().isIndex()) 1049 return op.emitOpError("expected block argument ") 1050 << (en.index() + 1) << " to be an index"; 1051 } 1052 1053 return success(); 1054 } 1055 1056 RankedTensorType PadTensorOp::inferResultType(RankedTensorType sourceType, 1057 ArrayRef<int64_t> staticLow, 1058 ArrayRef<int64_t> staticHigh) { 1059 unsigned rank = sourceType.getRank(); 1060 assert(staticLow.size() == rank && "unexpected staticLow size mismatch"); 1061 assert(staticHigh.size() == rank && "unexpected staticHigh size mismatch"); 1062 1063 SmallVector<int64_t, 4> resultShape; 1064 for (auto i : llvm::seq<unsigned>(0, rank)) { 1065 if (sourceType.isDynamicDim(i) || 1066 staticLow[i] == ShapedType::kDynamicSize || 1067 staticHigh[i] == ShapedType::kDynamicSize) { 1068 resultShape.push_back(ShapedType::kDynamicSize); 1069 } else { 1070 int64_t size = sourceType.getDimSize(i) + staticLow[i] + staticHigh[i]; 1071 resultShape.push_back(size); 1072 } 1073 } 1074 1075 return RankedTensorType::get(resultShape, sourceType.getElementType()); 1076 } 1077 1078 void PadTensorOp::build(OpBuilder &b, OperationState &result, Value source, 1079 ArrayRef<int64_t> staticLow, 1080 ArrayRef<int64_t> staticHigh, ValueRange low, 1081 ValueRange high, ArrayRef<NamedAttribute> attrs) { 1082 auto sourceType = source.getType().cast<RankedTensorType>(); 1083 auto resultType = inferResultType(sourceType, staticLow, staticHigh); 1084 build(b, result, resultType, source, low, high, b.getI64ArrayAttr(staticLow), 1085 b.getI64ArrayAttr(staticHigh)); 1086 result.addAttributes(attrs); 1087 } 1088 1089 void PadTensorOp::build(OpBuilder &b, OperationState &result, Value source, 1090 ValueRange low, ValueRange high, 1091 ArrayRef<NamedAttribute> attrs) { 1092 auto sourceType = source.getType().cast<RankedTensorType>(); 1093 unsigned rank = sourceType.getRank(); 1094 SmallVector<int64_t, 4> staticVector(rank, ShapedType::kDynamicSize); 1095 build(b, result, source, staticVector, staticVector, low, high, attrs); 1096 } 1097 1098 void PadTensorOp::build(OpBuilder &b, OperationState &result, Type resultType, 1099 Value source, ArrayRef<OpFoldResult> low, 1100 ArrayRef<OpFoldResult> high, 1101 ArrayRef<NamedAttribute> attrs) { 1102 assert(resultType.isa<RankedTensorType>()); 1103 auto sourceType = source.getType().cast<RankedTensorType>(); 1104 unsigned rank = sourceType.getRank(); 1105 SmallVector<Value, 4> dynamicLow, dynamicHigh; 1106 SmallVector<int64_t, 4> staticLow, staticHigh; 1107 for (unsigned i = 0; i < rank; ++i) { 1108 // staticLow and staticHigh have full information of the padding config. 1109 // This will grow staticLow and staticHigh with 1 value. If the config is 1110 // dynamic (ie not a constant), dynamicLow and dynamicHigh will grow with 1 1111 // value as well. 1112 dispatchIndexOpFoldResult(low[i], dynamicLow, staticLow, 1113 ShapedType::kDynamicSize); 1114 dispatchIndexOpFoldResult(high[i], dynamicHigh, staticHigh, 1115 ShapedType::kDynamicSize); 1116 } 1117 if (!resultType) { 1118 resultType = 1119 PadTensorOp::inferResultType(sourceType, staticLow, staticHigh); 1120 } 1121 build(b, result, resultType, source, dynamicLow, dynamicHigh, 1122 b.getI64ArrayAttr(staticLow), b.getI64ArrayAttr(staticHigh)); 1123 } 1124 1125 PadTensorOp PadTensorOp::createPadScalarOp(Type type, Value source, Value pad, 1126 ArrayRef<OpFoldResult> low, 1127 ArrayRef<OpFoldResult> high, 1128 Location loc, OpBuilder &builder) { 1129 auto padTensorOp = 1130 builder.create<linalg::PadTensorOp>(loc, type, source, low, high); 1131 int rank = padTensorOp.getResultType().getRank(); 1132 SmallVector<Type, 4> blockArgTypes; 1133 blockArgTypes.assign(rank, builder.getIndexType()); 1134 auto ®ion = padTensorOp.region(); 1135 // `builder.createBlock` changes the insertion point within the block. Create 1136 // a guard to reset the insertion point of the builder after it is destroyed. 1137 OpBuilder::InsertionGuard guard(builder); 1138 builder.createBlock(®ion, region.end(), blockArgTypes); 1139 builder.create<linalg::YieldOp>(loc, pad); 1140 return padTensorOp; 1141 } 1142 1143 PadTensorOp PadTensorOp::createPadHighOp(Type type, Value source, Value pad, 1144 Location loc, OpBuilder &builder) { 1145 SmallVector<OpFoldResult, 4> low, high; 1146 auto rankedTensorType = type.cast<RankedTensorType>(); 1147 assert(rankedTensorType.hasStaticShape()); 1148 int rank = rankedTensorType.getRank(); 1149 for (int i = 0; i < rank; ++i) { 1150 auto dimOp = builder.createOrFold<tensor::DimOp>(loc, source, i); 1151 auto resultDimSize = builder.createOrFold<ConstantIndexOp>( 1152 loc, rankedTensorType.getDimSize(i)); 1153 auto highValue = builder.createOrFold<SubIOp>(loc, resultDimSize, dimOp); 1154 high.push_back(highValue); 1155 low.push_back(builder.createOrFold<ConstantIndexOp>(loc, 0)); 1156 } 1157 return PadTensorOp::createPadScalarOp(type, source, pad, low, high, loc, 1158 builder); 1159 } 1160 1161 LogicalResult PadTensorOp::reifyResultShapes( 1162 OpBuilder &b, ReifiedRankedShapedTypeDims &reifiedReturnShapes) { 1163 Location loc = getLoc(); 1164 auto lowPad = getMixedLowPad(); 1165 auto highPad = getMixedHighPad(); 1166 SmallVector<Value> shapes; 1167 for (auto dim : llvm::seq<int64_t>(0, getSourceType().getRank())) { 1168 // Shape along each dimension is source dim + low pad + high pad. 1169 SmallVector<Value> mapOperands; 1170 mapOperands.push_back(b.createOrFold<tensor::DimOp>(loc, source(), dim)); 1171 AffineExpr expr = b.getAffineDimExpr(0); 1172 unsigned numSymbols = 0; 1173 auto addOpFoldResult = [&](OpFoldResult valueOrAttr) { 1174 if (Value v = valueOrAttr.dyn_cast<Value>()) { 1175 expr = expr + b.getAffineSymbolExpr(numSymbols++); 1176 mapOperands.push_back(v); 1177 return; 1178 } 1179 int64_t staticValue = 1180 valueOrAttr.get<Attribute>().cast<IntegerAttr>().getInt(); 1181 expr = expr + staticValue; 1182 }; 1183 addOpFoldResult(lowPad[dim]); 1184 addOpFoldResult(highPad[dim]); 1185 shapes.push_back(applyMapToValues( 1186 b, loc, AffineMap::get(1, numSymbols, expr), mapOperands)[0]); 1187 } 1188 reifiedReturnShapes.emplace_back(std::move(shapes)); 1189 return success(); 1190 } 1191 1192 //===----------------------------------------------------------------------===// 1193 // Methods related to PadTensor tiling. 1194 //===----------------------------------------------------------------------===// 1195 1196 SmallVector<Value> PadTensorOp::getDestinationOperands(OpBuilder &b) { 1197 ReifiedRankedShapedTypeDims reifiedShapes; 1198 (void)reifyResultShapes(b, reifiedShapes); 1199 SmallVector<OpFoldResult> mixedSizes = getAsOpFoldResult(reifiedShapes[0]); 1200 Value initTensor = b.create<InitTensorOp>(getLoc(), mixedSizes, 1201 getResultType().getElementType()); 1202 return {initTensor}; 1203 } 1204 1205 SmallVector<StringRef> PadTensorOp::getLoopIteratorTypes() { 1206 SmallVector<StringRef> iteratorTypes(getResultType().getRank(), 1207 getParallelIteratorTypeName()); 1208 return iteratorTypes; 1209 } 1210 1211 SmallVector<Range> PadTensorOp::getLoopBounds(OpBuilder &b) { 1212 ReifiedRankedShapedTypeDims reifiedShapes; 1213 (void)reifyResultShapes(b, reifiedShapes); 1214 Value zero = b.create<ConstantIndexOp>(getLoc(), 0); 1215 Value one = b.create<ConstantIndexOp>(getLoc(), 1); 1216 // Initialize all the ranges to {zero, one, one}. All the `ub`s are 1217 // overwritten. 1218 SmallVector<Range> loopRanges(reifiedShapes[0].size(), {zero, one, one}); 1219 for (auto ub : enumerate(reifiedShapes[0])) 1220 loopRanges[ub.index()].size = ub.value(); 1221 return loopRanges; 1222 } 1223 1224 Operation *PadTensorOp::getTiledImplementation(OpBuilder &b, ValueRange dest, 1225 ArrayRef<OpFoldResult> offsets, 1226 ArrayRef<OpFoldResult> sizes) { 1227 // Only constant padding value supported. 1228 Value padValue = getConstantPaddingValue(); 1229 if (!padValue) 1230 return nullptr; 1231 1232 // Helper variables and functions for various arithmetic operations. These are 1233 // used extensively for computing new offset/length and padding values. 1234 Location loc = getLoc(); 1235 AffineExpr dim0, dim1; 1236 bindDims(b.getContext(), dim0, dim1); 1237 // Add two integers. 1238 auto addMap = AffineMap::get(2, 0, {dim0 + dim1}); 1239 auto add = [&](Value v1, Value v2) { 1240 return b.createOrFold<AffineApplyOp>(loc, addMap, ValueRange{v1, v2}); 1241 }; 1242 // Subtract two integers. 1243 auto subMap = AffineMap::get(2, 0, {dim0 - dim1}); 1244 auto sub = [&](Value v1, Value v2) { 1245 return b.createOrFold<AffineApplyOp>(loc, subMap, ValueRange{v1, v2}); 1246 }; 1247 // Take the minimum of two integers. 1248 auto idMap = AffineMap::getMultiDimIdentityMap(2, b.getContext()); 1249 auto min = [&](Value v1, Value v2) { 1250 return b.createOrFold<AffineMinOp>(loc, idMap, ValueRange{v1, v2}); 1251 }; 1252 // Take the maximum of two integers. 1253 auto max = [&](Value v1, Value v2) { 1254 return b.createOrFold<AffineMaxOp>(loc, idMap, ValueRange{v1, v2}); 1255 }; 1256 // Zero index-typed integer. 1257 auto zero = b.create<ConstantIndexOp>(loc, 0); 1258 1259 // Helper function for filling static/dynamic low/high padding indices vectors 1260 // of PadTensorOp. 1261 auto appendIndex = [&](Value val, SmallVector<Value> &dynIndices, 1262 SmallVector<int64_t> &staticIndices) { 1263 if (auto constInt = getConstantIntValue(val)) { 1264 staticIndices.push_back(*constInt); 1265 } else { 1266 staticIndices.push_back(ShapedType::kDynamicSize); 1267 dynIndices.push_back(val); 1268 } 1269 }; 1270 1271 // Compute new offsets, lengths, low padding, high padding. 1272 SmallVector<OpFoldResult> newOffsets, newLengths, newStrides; 1273 SmallVector<Value> newLows, newHighs; 1274 SmallVector<int64_t> staticNewLows, staticNewHighs; 1275 // Set to true if the original data source is not read at all. 1276 bool hasZeroLen = false; 1277 // Same as hasZeroLen, but for dynamic dimension sizes. This condition 1278 // is true if the original data source turns out to be unused at runtime. 1279 Value dynHasZeroLenCond; 1280 1281 int64_t rank = getSourceType().getRank(); 1282 for (unsigned dim = 0; dim < rank; ++dim) { 1283 auto low = getValueOrCreateConstantIndexOp(b, loc, getMixedLowPad()[dim]); 1284 bool hasLowPad = getConstantIntValue(low) != static_cast<int64_t>(0); 1285 auto high = getValueOrCreateConstantIndexOp(b, loc, getMixedHighPad()[dim]); 1286 bool hasHighPad = getConstantIntValue(high) != static_cast<int64_t>(0); 1287 auto offset = getValueOrCreateConstantIndexOp(b, loc, offsets[dim]); 1288 auto length = getValueOrCreateConstantIndexOp(b, loc, sizes[dim]); 1289 auto srcSize = b.createOrFold<tensor::DimOp>(loc, source(), dim); 1290 1291 // The new amount of low padding is `low - offset`. Except for the case 1292 // where none of the low padding is read. In that case, the new amount of 1293 // low padding is zero. 1294 // 1295 // Optimization: If low = 0, then newLow = 0. 1296 Value newLow = hasLowPad ? max(zero, sub(low, offset)) : zero; 1297 appendIndex(newLow, newLows, staticNewLows); 1298 1299 // Start reading the data from position `offset - low`. Since the original 1300 // read may have started in the low padding zone, this value could be 1301 // negative. Therefore, start reading from: 1302 // 1303 // max(offset - low, 0) 1304 // 1305 // The original read could also have started in the high padding zone. 1306 // In that case, set the offset to the end of source tensor. The new 1307 // ExtractSliceOp length will be zero in that case. (Effectively reading no 1308 // data from the source.) 1309 // 1310 // Optimization: If low = 0, then the formula can be simplified. 1311 Value newOffset = hasLowPad ? min(max(sub(offset, low), zero), srcSize) 1312 : min(offset, srcSize); 1313 newOffsets.push_back(getAsOpFoldResult(newOffset)); 1314 1315 // The original ExtractSliceOp was reading until position `offset + length`. 1316 // Therefore, the corresponding position within the source tensor is: 1317 // 1318 // offset + length - low 1319 // 1320 // In case the original ExtractSliceOp stopped reading within the low 1321 // padding zone, this value can be negative. In that case, the end position 1322 // of the read should be zero. (Similar to newOffset.) 1323 // 1324 // The original read could also have stopped in the high padding zone. 1325 // In that case, set the end positition of the read should be the end of the 1326 // source tensor. (Similar to newOffset.) 1327 // 1328 // endLoc = min(max(offset - low + length, 0), srcSize) 1329 // 1330 // The new ExtractSliceOp length is `endLoc - newOffset`. 1331 // 1332 // Optimization: If low = 0, then the formula can be simplified. 1333 Value endLoc = hasLowPad 1334 ? min(max(add(sub(offset, low), length), zero), srcSize) 1335 : min(add(offset, length), srcSize); 1336 Value newLength = sub(endLoc, newOffset); 1337 newLengths.push_back(getAsOpFoldResult(newLength)); 1338 1339 // Check if newLength is zero. In that case, no SubTensorOp should be 1340 // executed. 1341 if (auto newLengthInt = getConstantIntValue(newLength)) { 1342 hasZeroLen |= *newLengthInt == 0; 1343 } else { 1344 Value check = b.create<CmpIOp>(loc, CmpIPredicate::eq, newLength, zero); 1345 dynHasZeroLenCond = dynHasZeroLenCond 1346 ? b.create<OrOp>(loc, check, dynHasZeroLenCond) 1347 : check; 1348 } 1349 1350 // The amount of high padding is simply the number of elements remaining, 1351 // so that the result has the same length as the original ExtractSliceOp. 1352 // As an optimization, if the original high padding is zero, then the new 1353 // high padding must also be zero. 1354 Value newHigh = hasHighPad ? sub(sub(length, newLength), newLow) : zero; 1355 appendIndex(newHigh, newHighs, staticNewHighs); 1356 1357 // Only unit stride supported. 1358 newStrides.push_back(b.getIndexAttr(1)); 1359 } 1360 1361 // The shape of the result can be obtained from the sizes passed in. 1362 SmallVector<Value> dynDims; 1363 SmallVector<int64_t> shape; 1364 dispatchIndexOpFoldResults(sizes, dynDims, shape, ShapedType::kDynamicSize); 1365 RankedTensorType resultType = 1366 RankedTensorType::get(shape, getResultType().getElementType()); 1367 1368 // Insert cast to ensure that types match. (May be folded away.) 1369 auto castResult = [&](Value val) -> Operation * { 1370 auto castOp = b.create<tensor::CastOp>(loc, resultType, val); 1371 return castOp; 1372 }; 1373 1374 // In cases where the original data source is unused: Emit a GenerateOp and 1375 // do not generate a SliceOp. (The result shape of the SliceOp would 1376 // have a dimension of size 0, the semantics of which is unclear.) 1377 auto createGenerateOp = [&]() { 1378 // Create GenerateOp. 1379 auto generateOp = b.create<tensor::GenerateOp>( 1380 loc, resultType, dynDims, 1381 [&](OpBuilder &builder, Location gLoc, ValueRange indices) { 1382 builder.create<tensor::YieldOp>(gLoc, padValue); 1383 }); 1384 return castResult(generateOp); 1385 }; 1386 1387 // Emit a SliceOp and a PadTensorOp. Should not be used in cases where 1388 // the result shape of the new SliceOp has a zero dimension. 1389 auto createPadTensorOfSubTensor = [&]() { 1390 // Create pad_tensor(subtensor(x)). 1391 auto newSliceOp = b.create<tensor::ExtractSliceOp>( 1392 loc, source(), newOffsets, newLengths, newStrides); 1393 auto newPadTensorOp = b.create<PadTensorOp>( 1394 loc, newSliceOp, staticNewLows, staticNewHighs, newLows, newHighs); 1395 1396 // Copy region to new PadTensorOp. 1397 BlockAndValueMapping bvm; 1398 region().cloneInto(&newPadTensorOp.getRegion(), bvm); 1399 1400 // Cast result and return. 1401 return castResult(newPadTensorOp); 1402 }; 1403 1404 // Rewrite subtensor(pad_tensor(x)) into a GenerateOp it is statically known 1405 // that the original data source x is not used. 1406 if (hasZeroLen) { 1407 return createGenerateOp(); 1408 } 1409 1410 // If there are dynamic dimensions: Generate an scf.if check to avoid creating 1411 // SliceOps with result dimensions of size 0 at runtime. 1412 if (dynHasZeroLenCond) { 1413 auto result = b.create<scf::IfOp>( 1414 loc, resultType, dynHasZeroLenCond, 1415 /*thenBuilder=*/ 1416 [&](OpBuilder &b, Location loc) { 1417 b.create<scf::YieldOp>(loc, createGenerateOp()->getResult(0)); 1418 }, 1419 /*elseBuilder=*/ 1420 [&](OpBuilder &b, Location loc) { 1421 b.create<scf::YieldOp>(loc, 1422 createPadTensorOfSubTensor()->getResult(0)); 1423 }); 1424 return result; 1425 } 1426 return createPadTensorOfSubTensor(); 1427 } 1428 1429 namespace { 1430 // Folds linalg.pad_tensor when padding is static zeros. 1431 struct FoldStaticZeroPadding : public OpRewritePattern<PadTensorOp> { 1432 using OpRewritePattern<PadTensorOp>::OpRewritePattern; 1433 1434 LogicalResult matchAndRewrite(PadTensorOp padTensorOp, 1435 PatternRewriter &rewriter) const override { 1436 if (!padTensorOp.hasZeroLowPad() || !padTensorOp.hasZeroHighPad()) 1437 return failure(); 1438 rewriter.replaceOpWithNewOp<tensor::CastOp>( 1439 padTensorOp, padTensorOp.result().getType(), padTensorOp.source()); 1440 return success(); 1441 } 1442 }; 1443 1444 // Fold CastOp into PadTensorOp when adding static information. 1445 struct FoldSourceTensorCast : public OpRewritePattern<PadTensorOp> { 1446 using OpRewritePattern<PadTensorOp>::OpRewritePattern; 1447 1448 LogicalResult matchAndRewrite(PadTensorOp padTensorOp, 1449 PatternRewriter &rewriter) const override { 1450 auto castOp = padTensorOp.source().getDefiningOp<tensor::CastOp>(); 1451 if (!tensor::canFoldIntoConsumerOp(castOp)) 1452 return failure(); 1453 1454 auto newResultType = PadTensorOp::inferResultType( 1455 castOp.source().getType().cast<RankedTensorType>(), 1456 extractFromI64ArrayAttr(padTensorOp.static_low()), 1457 extractFromI64ArrayAttr(padTensorOp.static_high())); 1458 1459 if (newResultType == padTensorOp.getResultType()) { 1460 rewriter.updateRootInPlace(padTensorOp, [&]() { 1461 padTensorOp.sourceMutable().assign(castOp.source()); 1462 }); 1463 } else { 1464 auto newOp = rewriter.create<PadTensorOp>( 1465 padTensorOp->getLoc(), newResultType, padTensorOp.source(), 1466 padTensorOp.low(), padTensorOp.high(), padTensorOp.static_low(), 1467 padTensorOp.static_high()); 1468 BlockAndValueMapping mapper; 1469 padTensorOp.getRegion().cloneInto(&newOp.getRegion(), mapper); 1470 1471 rewriter.replaceOpWithNewOp<tensor::CastOp>( 1472 padTensorOp, padTensorOp.getResultType(), newOp); 1473 } 1474 return success(); 1475 } 1476 }; 1477 } // namespace 1478 1479 void PadTensorOp::getCanonicalizationPatterns(RewritePatternSet &results, 1480 MLIRContext *context) { 1481 results.add<FoldStaticZeroPadding, FoldSourceTensorCast>(context); 1482 } 1483 1484 /// Return the padding value of the PadTensorOp if it constant. In this context, 1485 /// "constant" means an actual constant or "defined outside of the block". 1486 /// 1487 /// Values are considered constant in three cases: 1488 /// - A ConstantLike value. 1489 /// - A basic block argument from a different block. 1490 /// - A value defined outside of the block. 1491 /// 1492 /// If the padding value is not constant, an empty Value is returned. 1493 Value PadTensorOp::getConstantPaddingValue() { 1494 auto yieldOp = dyn_cast<YieldOp>(getRegion().front().getTerminator()); 1495 if (!yieldOp || yieldOp.values().size() != 1) 1496 return {}; 1497 Value padValue = yieldOp.values().front(); 1498 // Check if yield value is a constant. 1499 if (matchPattern(padValue, m_Constant())) 1500 return padValue; 1501 // Check if yield value is defined inside the PadTensorOp block. 1502 if (padValue.getParentBlock() == &getRegion().front()) 1503 return {}; 1504 // Else: Yield value defined outside of the PadTensorOp block. 1505 return padValue; 1506 } 1507 1508 OpFoldResult PadTensorOp::fold(ArrayRef<Attribute>) { 1509 if (getResultType().hasStaticShape() && getResultType() == getSourceType()) 1510 return source(); 1511 return {}; 1512 } 1513 1514 //===----------------------------------------------------------------------===// 1515 // ReshapeOp 1516 //===----------------------------------------------------------------------===// 1517 1518 static void print(OpAsmPrinter &p, linalg::TensorExpandShapeOp op) { 1519 ::mlir::printReshapeOp<linalg::TensorExpandShapeOp>(p, op); 1520 } 1521 1522 static void print(OpAsmPrinter &p, linalg::TensorCollapseShapeOp op) { 1523 ::mlir::printReshapeOp<linalg::TensorCollapseShapeOp>(p, op); 1524 } 1525 1526 template <typename AffineExprTy> 1527 unsigned getMaxPosOfType(ArrayRef<ReassociationExprs> exprArrays) { 1528 unsigned pos = 0; 1529 for (const auto &exprs : exprArrays) { 1530 for (auto expr : exprs) { 1531 expr.walk([&pos](AffineExpr e) { 1532 if (auto d = e.dyn_cast<AffineExprTy>()) 1533 pos = std::max(pos, d.getPosition()); 1534 }); 1535 } 1536 } 1537 return pos; 1538 } 1539 1540 SmallVector<AffineMap, 4> TensorCollapseShapeOp::getReassociationMaps() { 1541 return getSymbolLessAffineMaps(getReassociationExprs()); 1542 } 1543 SmallVector<ReassociationExprs, 4> 1544 TensorCollapseShapeOp::getReassociationExprs() { 1545 return convertReassociationIndicesToExprs(getContext(), 1546 getReassociationIndices()); 1547 } 1548 SmallVector<AffineMap, 4> TensorExpandShapeOp::getReassociationMaps() { 1549 return getSymbolLessAffineMaps(getReassociationExprs()); 1550 } 1551 SmallVector<ReassociationExprs, 4> 1552 TensorExpandShapeOp::getReassociationExprs() { 1553 return convertReassociationIndicesToExprs(getContext(), 1554 getReassociationIndices()); 1555 } 1556 1557 /// For reshape op compute the shape at dimension `dimIndex` of the output in 1558 /// terms of shape of the `src`, when the reshape op is a collapsing 1559 /// operation. It is the product of the shape of the collapsed dimensions of the 1560 /// `src`. 1561 static OpFoldResult 1562 getCollapsedOutputDimFromInputShape(OpBuilder &builder, Location loc, 1563 int64_t dimIndex, Value src, 1564 ArrayRef<AffineMap> reassociationMap) { 1565 AffineMap map = reassociationMap[dimIndex]; 1566 unsigned startPos = 1567 map.getResults().front().cast<AffineDimExpr>().getPosition(); 1568 unsigned endPos = map.getResults().back().cast<AffineDimExpr>().getPosition(); 1569 AffineExpr expr; 1570 SmallVector<Value, 2> dynamicDims; 1571 for (auto dim : llvm::seq_inclusive(startPos, endPos)) { 1572 dynamicDims.push_back(builder.createOrFold<tensor::DimOp>(loc, src, dim)); 1573 AffineExpr currExpr = builder.getAffineSymbolExpr(dim - startPos); 1574 expr = (expr ? expr * currExpr : currExpr); 1575 } 1576 return applyMapToValues(builder, loc, 1577 AffineMap::get(0, endPos - startPos + 1, expr), 1578 dynamicDims)[0]; 1579 } 1580 1581 /// Given the `src` of a collapsing reshape op and its reassociation maps, 1582 /// compute the shape of the result of the reshape. 1583 static SmallVector<OpFoldResult, 4> getCollapsedOutputShapeFromInputShape( 1584 OpBuilder &builder, Location loc, Value src, 1585 ArrayRef<int64_t> dstStaticShape, ArrayRef<AffineMap> reassociation) { 1586 return llvm::to_vector<4>(llvm::map_range( 1587 llvm::seq<int64_t>(0, dstStaticShape.size()), [&](int64_t dim) { 1588 return getCollapsedOutputDimFromInputShape(builder, loc, dim, src, 1589 reassociation); 1590 })); 1591 } 1592 1593 /// Compute a map that for a given dimension of the expanded type gives the 1594 /// dimension in the collapsed type it maps to. Essentially its the inverse of 1595 /// the `reassocation` maps. 1596 static llvm::DenseMap<int64_t, int64_t> 1597 getExpandedDimToCollapsedDimMap(ArrayRef<AffineMap> reassociation) { 1598 llvm::DenseMap<int64_t, int64_t> expandedDimToCollapsedDim; 1599 for (auto map : enumerate(reassociation)) { 1600 unsigned startPos = 1601 map.value().getResults().front().cast<AffineDimExpr>().getPosition(); 1602 unsigned endPos = 1603 map.value().getResults().back().cast<AffineDimExpr>().getPosition(); 1604 for (auto dim : llvm::seq_inclusive(startPos, endPos)) { 1605 expandedDimToCollapsedDim[dim] = map.index(); 1606 } 1607 } 1608 return expandedDimToCollapsedDim; 1609 } 1610 1611 /// For an expanding reshape op, compute the value for a dimension of the output 1612 /// from the shape of the input. 1613 static OpFoldResult getExpandedOutputDimFromInputShape( 1614 OpBuilder &builder, Location loc, int64_t dimIndex, Value src, 1615 ArrayRef<int64_t> dstStaticShape, ArrayRef<AffineMap> reassociation, 1616 llvm::DenseMap<int64_t, int64_t> &expandedDimToCollapsedDim) { 1617 if (!ShapedType::isDynamic(dstStaticShape[dimIndex])) { 1618 return builder.getI64IntegerAttr(dstStaticShape[dimIndex]); 1619 } 1620 unsigned sourceDimPos = expandedDimToCollapsedDim[dimIndex]; 1621 unsigned startPos = reassociation[sourceDimPos] 1622 .getResults() 1623 .front() 1624 .cast<AffineDimExpr>() 1625 .getPosition(); 1626 unsigned endPos = reassociation[sourceDimPos] 1627 .getResults() 1628 .back() 1629 .cast<AffineDimExpr>() 1630 .getPosition(); 1631 int64_t linearizedStaticDim = 1; 1632 for (auto d : 1633 llvm::enumerate(dstStaticShape.slice(startPos, endPos - startPos + 1))) { 1634 if (d.index() + startPos == static_cast<unsigned>(dimIndex)) 1635 continue; 1636 assert(!ShapedType::isDynamic(d.value()) && 1637 "single dimension cannot be expanded into multiple dynamic " 1638 "dimensions"); 1639 linearizedStaticDim *= d.value(); 1640 } 1641 Value sourceDim = builder.create<tensor::DimOp>(loc, src, sourceDimPos); 1642 return applyMapToValues( 1643 builder, loc, 1644 AffineMap::get( 1645 0, 1, builder.getAffineSymbolExpr(0).floorDiv(linearizedStaticDim)), 1646 sourceDim)[0]; 1647 } 1648 1649 /// Given the `src` of an expanding reshape op, the reassociation maps and the 1650 /// result type, compute the shape of the result of the reshape. 1651 static SmallVector<OpFoldResult, 4> getExpandedOutputShapeFromInputShape( 1652 OpBuilder &builder, Location loc, Value src, 1653 ArrayRef<int64_t> dstStaticShape, ArrayRef<AffineMap> reassociation) { 1654 llvm::DenseMap<int64_t, int64_t> expandedDimToCollapsedDim = 1655 getExpandedDimToCollapsedDimMap(reassociation); 1656 return llvm::to_vector<4>(llvm::map_range( 1657 llvm::seq<int64_t>(0, dstStaticShape.size()), [&](int64_t dim) { 1658 return getExpandedOutputDimFromInputShape(builder, loc, dim, src, 1659 dstStaticShape, reassociation, 1660 expandedDimToCollapsedDim); 1661 })); 1662 } 1663 1664 static SmallVector<OpFoldResult, 4> 1665 getReshapeOutputShapeFromInputShape(OpBuilder &builder, Location loc, Value src, 1666 ArrayRef<int64_t> dstStaticShape, 1667 ArrayRef<AffineMap> reassocation) { 1668 return dstStaticShape.size() > 1669 static_cast<size_t>(src.getType().cast<ShapedType>().getRank()) 1670 ? getExpandedOutputShapeFromInputShape( 1671 builder, loc, src, dstStaticShape, reassocation) 1672 : getCollapsedOutputShapeFromInputShape( 1673 builder, loc, src, dstStaticShape, reassocation); 1674 } 1675 1676 //===----------------------------------------------------------------------===// 1677 // TensorReshapeOp 1678 //===----------------------------------------------------------------------===// 1679 1680 /// Compute the RankedTensorType obtained by applying `reassociation` to `type`. 1681 static RankedTensorType 1682 computeTensorReshapeCollapsedType(RankedTensorType type, 1683 ArrayRef<AffineMap> reassociation) { 1684 auto shape = type.getShape(); 1685 SmallVector<int64_t, 4> newShape; 1686 newShape.reserve(reassociation.size()); 1687 1688 // Use the fact that reassociation is valid to simplify the logic: only use 1689 // each map's rank. 1690 assert(isReassociationValid(reassociation) && "invalid reassociation"); 1691 unsigned currentDim = 0; 1692 for (AffineMap m : reassociation) { 1693 unsigned dim = m.getNumResults(); 1694 auto band = shape.slice(currentDim, dim); 1695 int64_t size = 1; 1696 if (llvm::is_contained(band, ShapedType::kDynamicSize)) 1697 size = ShapedType::kDynamicSize; 1698 else 1699 for (unsigned d = 0; d < dim; ++d) 1700 size *= shape[currentDim + d]; 1701 newShape.push_back(size); 1702 currentDim += dim; 1703 } 1704 1705 return RankedTensorType::get(newShape, type.getElementType()); 1706 } 1707 1708 void mlir::linalg::TensorCollapseShapeOp::build( 1709 OpBuilder &b, OperationState &result, Value src, 1710 ArrayRef<ReassociationIndices> reassociation, 1711 ArrayRef<NamedAttribute> attrs) { 1712 auto resultType = computeTensorReshapeCollapsedType( 1713 src.getType().cast<RankedTensorType>(), 1714 getSymbolLessAffineMaps( 1715 convertReassociationIndicesToExprs(b.getContext(), reassociation))); 1716 build(b, result, resultType, src, attrs); 1717 result.addAttribute(getReassociationAttrName(), 1718 getReassociationIndicesAttribute(b, reassociation)); 1719 } 1720 1721 void mlir::linalg::TensorExpandShapeOp::build( 1722 OpBuilder &b, OperationState &result, Value src, 1723 ArrayRef<ReassociationIndices> reassociation, 1724 ArrayRef<NamedAttribute> attrs) { 1725 auto resultType = computeTensorReshapeCollapsedType( 1726 src.getType().cast<RankedTensorType>(), 1727 getSymbolLessAffineMaps( 1728 convertReassociationIndicesToExprs(b.getContext(), reassociation))); 1729 build(b, result, resultType, src, attrs); 1730 result.addAttribute(getReassociationAttrName(), 1731 getReassociationIndicesAttribute(b, reassociation)); 1732 } 1733 1734 template <typename TensorReshapeOp, 1735 bool isExpansion = 1736 std::is_same<TensorReshapeOp, TensorExpandShapeOp>::value> 1737 static LogicalResult verifyTensorReshapeOp(TensorReshapeOp op, 1738 RankedTensorType expandedType, 1739 RankedTensorType collapsedType) { 1740 if (failed( 1741 verifyReshapeLikeTypes(op, expandedType, collapsedType, isExpansion))) 1742 return failure(); 1743 1744 auto maps = op.getReassociationMaps(); 1745 RankedTensorType expectedType = 1746 computeTensorReshapeCollapsedType(expandedType, maps); 1747 if (collapsedType != expectedType) 1748 return op.emitOpError("expected collapsed type to be ") 1749 << expectedType << ", but got " << collapsedType; 1750 return success(); 1751 } 1752 1753 static LogicalResult verify(TensorExpandShapeOp op) { 1754 return verifyTensorReshapeOp(op, op.getResultType(), op.getSrcType()); 1755 } 1756 1757 static LogicalResult verify(TensorCollapseShapeOp op) { 1758 return verifyTensorReshapeOp(op, op.getSrcType(), op.getResultType()); 1759 } 1760 1761 namespace { 1762 /// Reshape of a splat constant can be replaced with a constant of the result 1763 /// type. 1764 template <typename TensorReshapeOp> 1765 struct FoldReshapeWithConstant : OpRewritePattern<TensorReshapeOp> { 1766 using OpRewritePattern<TensorReshapeOp>::OpRewritePattern; 1767 LogicalResult matchAndRewrite(TensorReshapeOp reshapeOp, 1768 PatternRewriter &rewriter) const override { 1769 DenseElementsAttr attr; 1770 if (!matchPattern(reshapeOp.src(), m_Constant(&attr))) 1771 return failure(); 1772 if (!attr || !attr.isSplat()) 1773 return failure(); 1774 DenseElementsAttr newAttr = DenseElementsAttr::getFromRawBuffer( 1775 reshapeOp.getResultType(), attr.getRawData(), true); 1776 rewriter.replaceOpWithNewOp<ConstantOp>(reshapeOp, newAttr); 1777 return success(); 1778 } 1779 }; 1780 1781 /// Fold linalg.fill -> linalg.tensor_reshape chain. 1782 /// 1783 /// For such op chains, we can create new linalg.fill ops with the result 1784 /// type of the linalg.tensor_reshape op. 1785 template <typename TensorReshapeOp> 1786 struct FoldFillWithTensorReshape : OpRewritePattern<TensorReshapeOp> { 1787 using OpRewritePattern<TensorReshapeOp>::OpRewritePattern; 1788 LogicalResult matchAndRewrite(TensorReshapeOp reshapeOp, 1789 PatternRewriter &rewriter) const override { 1790 auto oldFill = reshapeOp.src().template getDefiningOp<FillOp>(); 1791 if (!oldFill) 1792 return failure(); 1793 1794 Location loc = oldFill.getLoc(); 1795 auto newInit = rewriter.create<TensorReshapeOp>( 1796 loc, reshapeOp.getResultType(), oldFill.output(), 1797 reshapeOp.reassociation()); 1798 rewriter.replaceOpWithNewOp<FillOp>(reshapeOp, oldFill.value(), newInit); 1799 1800 return success(); 1801 } 1802 }; 1803 } // namespace 1804 1805 void TensorExpandShapeOp::getCanonicalizationPatterns( 1806 RewritePatternSet &results, MLIRContext *context) { 1807 results 1808 .add<CollapseReshapeOps<TensorExpandShapeOp>, 1809 CollapseMixedReshapeOps<TensorExpandShapeOp, TensorCollapseShapeOp>, 1810 FoldFillWithTensorReshape<TensorExpandShapeOp>, 1811 FoldInitTensorWithTensorReshapeOp<TensorExpandShapeOp>, 1812 FoldReshapeWithConstant<TensorExpandShapeOp>>(context); 1813 } 1814 1815 void TensorCollapseShapeOp::getCanonicalizationPatterns( 1816 RewritePatternSet &results, MLIRContext *context) { 1817 results 1818 .add<CollapseReshapeOps<TensorCollapseShapeOp>, 1819 CollapseMixedReshapeOps<TensorCollapseShapeOp, TensorExpandShapeOp>, 1820 FoldFillWithTensorReshape<TensorCollapseShapeOp>, 1821 FoldInitTensorWithTensorReshapeOp<TensorCollapseShapeOp>, 1822 FoldReshapeWithConstant<TensorCollapseShapeOp>>(context); 1823 } 1824 1825 LogicalResult TensorExpandShapeOp::reifyResultShapes( 1826 OpBuilder &b, ReifiedRankedShapedTypeDims &reifiedReturnShapes) { 1827 auto resultShape = 1828 getAsValues(b, getLoc(), 1829 getReshapeOutputShapeFromInputShape( 1830 b, getLoc(), src(), getResultType().getShape(), 1831 getReassociationMaps())); 1832 reifiedReturnShapes.emplace_back(std::move(resultShape)); 1833 return success(); 1834 } 1835 1836 LogicalResult TensorCollapseShapeOp::reifyResultShapes( 1837 OpBuilder &b, ReifiedRankedShapedTypeDims &reifiedReturnShapes) { 1838 auto resultShape = 1839 getAsValues(b, getLoc(), 1840 getReshapeOutputShapeFromInputShape( 1841 b, getLoc(), src(), getResultType().getShape(), 1842 getReassociationMaps())); 1843 reifiedReturnShapes.emplace_back(std::move(resultShape)); 1844 return success(); 1845 } 1846 1847 //===----------------------------------------------------------------------===// 1848 // YieldOp 1849 //===----------------------------------------------------------------------===// 1850 1851 static void print(OpAsmPrinter &p, linalg::YieldOp op) { 1852 if (op.getNumOperands() > 0) 1853 p << ' ' << op.getOperands(); 1854 p.printOptionalAttrDict(op->getAttrs()); 1855 if (op.getNumOperands() > 0) 1856 p << " : " << op.getOperandTypes(); 1857 } 1858 1859 static ParseResult parseYieldOp(OpAsmParser &parser, OperationState &result) { 1860 SmallVector<OpAsmParser::OperandType, 2> opInfo; 1861 SmallVector<Type, 2> types; 1862 llvm::SMLoc loc = parser.getCurrentLocation(); 1863 return failure(parser.parseOperandList(opInfo) || 1864 parser.parseOptionalAttrDict(result.attributes) || 1865 (!opInfo.empty() && parser.parseColonTypeList(types)) || 1866 parser.resolveOperands(opInfo, types, loc, result.operands)); 1867 } 1868 1869 // Check the operand number and types must match the element types of the 1870 // LinalgOp interface's shaped operands. 1871 static LogicalResult verifyYield(linalg::YieldOp op, LinalgOp linalgOp) { 1872 if (op.getNumOperands() != linalgOp.getNumOutputs()) 1873 return op.emitOpError("expected number of yield values (") 1874 << linalgOp.getNumOutputs() 1875 << ") to match the number of operands of the enclosing " 1876 << "LinalgOp (" << op.getNumOperands() << ")"; 1877 1878 for (OpOperand &opOperand : op->getOpOperands()) { 1879 OpOperand *outputOperand = 1880 linalgOp.getOutputOperand(opOperand.getOperandNumber()); 1881 Type elementType = getElementTypeOrSelf(outputOperand->get().getType()); 1882 if (opOperand.get().getType() != elementType) 1883 return op.emitOpError("type of yield operand ") 1884 << (opOperand.getOperandNumber() + 1) << " (" 1885 << opOperand.get().getType() << ") doesn't match " 1886 << "the element type of the enclosing linalg.generic op (" 1887 << elementType << ")"; 1888 } 1889 return success(); 1890 } 1891 1892 static LogicalResult verify(linalg::YieldOp op) { 1893 auto *parentOp = op->getParentOp(); 1894 if (parentOp->getNumRegions() != 1 || parentOp->getRegion(0).empty()) 1895 return op.emitOpError("expected single non-empty parent region"); 1896 1897 if (auto linalgOp = dyn_cast<LinalgOp>(parentOp)) 1898 return verifyYield(op, cast<LinalgOp>(parentOp)); 1899 1900 if (auto padTensorOp = dyn_cast<linalg::PadTensorOp>(parentOp)) { 1901 if (op.getNumOperands() != 1) 1902 return op.emitOpError("expected single yield operand (got ") 1903 << op->getNumOperands() << ")"; 1904 if (op.getOperand(0).getType() != 1905 padTensorOp.getType().cast<ShapedType>().getElementType()) 1906 return op.emitOpError("expected yield type to match shape element type"); 1907 return success(); 1908 } 1909 1910 if (auto tiledLoopOp = dyn_cast<linalg::TiledLoopOp>(parentOp)) { 1911 // Check if output args with tensor types match results types. 1912 SmallVector<Value, 2> tensorOuts; 1913 llvm::copy_if( 1914 tiledLoopOp.outputs(), std::back_inserter(tensorOuts), 1915 [&](Value out) { return out.getType().isa<RankedTensorType>(); }); 1916 if (tensorOuts.size() != op.values().size()) 1917 return op.emitOpError("expected number of tensor output args = ") 1918 << tensorOuts.size() << " to match the number of yield operands = " 1919 << op.values().size(); 1920 1921 TypeRange tensorTypes(llvm::makeArrayRef(tensorOuts)); 1922 for (auto &item : 1923 llvm::enumerate(llvm::zip(tensorTypes, op.getOperandTypes()))) { 1924 Type outType, resultType; 1925 unsigned index = item.index(); 1926 std::tie(outType, resultType) = item.value(); 1927 if (outType != resultType) 1928 return op.emitOpError("expected yield operand ") 1929 << index << " with type = " << resultType 1930 << " to match output arg type = " << outType; 1931 } 1932 return success(); 1933 } 1934 return op.emitOpError("expected parent op with LinalgOp interface"); 1935 } 1936 1937 //===----------------------------------------------------------------------===// 1938 // TiledLoopOp 1939 //===----------------------------------------------------------------------===// 1940 1941 void TiledLoopOp::build(OpBuilder &builder, OperationState &result, 1942 ValueRange lowerBounds, ValueRange upperBounds, 1943 ValueRange steps, ValueRange inputs, ValueRange outputs, 1944 ArrayAttr iteratorTypes, 1945 function_ref<void(OpBuilder &, Location, ValueRange, 1946 ValueRange, ValueRange)> 1947 bodyBuilderFn) { 1948 build(builder, result, lowerBounds, upperBounds, steps, inputs, outputs, 1949 iteratorTypes, llvm::None, bodyBuilderFn); 1950 } 1951 1952 void TiledLoopOp::build(OpBuilder &builder, OperationState &result, 1953 ValueRange lowerBounds, ValueRange upperBounds, 1954 ValueRange steps, ValueRange inputs, ValueRange outputs, 1955 ArrayAttr iteratorTypes, 1956 Optional<ArrayAttr> distributionTypes, 1957 function_ref<void(OpBuilder &, Location, ValueRange, 1958 ValueRange, ValueRange)> 1959 bodyBuilderFn) { 1960 result.addOperands(lowerBounds); 1961 result.addOperands(upperBounds); 1962 result.addOperands(steps); 1963 result.addOperands(inputs); 1964 result.addOperands(outputs); 1965 result.addAttribute( 1966 TiledLoopOp::getOperandSegmentSizeAttr(), 1967 builder.getI32VectorAttr({static_cast<int32_t>(lowerBounds.size()), 1968 static_cast<int32_t>(upperBounds.size()), 1969 static_cast<int32_t>(steps.size()), 1970 static_cast<int32_t>(inputs.size()), 1971 static_cast<int32_t>(outputs.size())})); 1972 result.addAttribute(getIteratorTypesAttrName(), iteratorTypes); 1973 1974 if (distributionTypes.hasValue()) 1975 result.addAttribute(getDistributionTypesAttrName(), 1976 distributionTypes.getValue()); 1977 1978 // Add output types for `RankedTensorType` output arguments. 1979 for (Value output : outputs) { 1980 Type outputType = output.getType(); 1981 if (outputType.isa<RankedTensorType>()) 1982 result.addTypes(outputType); 1983 } 1984 1985 OpBuilder::InsertionGuard guard(builder); 1986 unsigned numIVs = steps.size(); 1987 SmallVector<Type, 8> argTypes(numIVs, builder.getIndexType()); 1988 for (Type type : TypeRange(inputs)) 1989 argTypes.push_back(type); 1990 for (Type type : TypeRange(outputs)) 1991 argTypes.push_back(type); 1992 Region *bodyRegion = result.addRegion(); 1993 Block *bodyBlock = builder.createBlock(bodyRegion, {}, argTypes); 1994 1995 if (bodyBuilderFn) { 1996 builder.setInsertionPointToStart(bodyBlock); 1997 bodyBuilderFn(builder, result.location, 1998 bodyBlock->getArguments().take_front(numIVs), 1999 bodyBlock->getArguments().slice(numIVs, inputs.size()), 2000 bodyBlock->getArguments().take_back(outputs.size())); 2001 TiledLoopOp::ensureTerminator(*bodyRegion, builder, result.location); 2002 } 2003 } 2004 2005 static void print(OpAsmPrinter &p, TiledLoopOp op) { 2006 p << " (" << op.getInductionVars() << ") = (" << op.lowerBound() << ") to (" 2007 << op.upperBound() << ") step (" << op.step() << ")"; 2008 2009 if (!op.inputs().empty()) { 2010 p << " ins ("; 2011 llvm::interleaveComma(llvm::zip(op.getRegionInputArgs(), op.inputs()), p, 2012 [&](auto it) { 2013 p << std::get<0>(it) << " = " << std::get<1>(it) 2014 << ": " << std::get<1>(it).getType(); 2015 }); 2016 p << ")"; 2017 } 2018 if (!op.outputs().empty()) { 2019 p << " outs ("; 2020 llvm::interleaveComma(llvm::zip(op.getRegionOutputArgs(), op.outputs()), p, 2021 [&](auto it) { 2022 p << std::get<0>(it) << " = " << std::get<1>(it) 2023 << ": " << std::get<1>(it).getType(); 2024 }); 2025 p << ")"; 2026 } 2027 2028 if (llvm::any_of(op.iterator_types(), [](Attribute attr) { 2029 return attr.cast<StringAttr>().getValue() != 2030 getParallelIteratorTypeName(); 2031 })) 2032 p << " iterators" << op.iterator_types() << ""; 2033 2034 if (op.distribution_types().hasValue()) 2035 p << " distribution" << op.distribution_types().getValue() << ""; 2036 2037 p.printRegion(op.region(), /*printEntryBlockArgs=*/false); 2038 p.printOptionalAttrDict( 2039 op->getAttrs(), /*elidedAttrs=*/{TiledLoopOp::getOperandSegmentSizeAttr(), 2040 getIteratorTypesAttrName(), 2041 getDistributionTypesAttrName()}); 2042 } 2043 2044 static ParseResult parseTiledLoopOp(OpAsmParser &parser, 2045 OperationState &result) { 2046 auto &builder = parser.getBuilder(); 2047 // Parse an opening `(` followed by induction variables followed by `)` 2048 SmallVector<OpAsmParser::OperandType, 4> ivs; 2049 if (parser.parseRegionArgumentList(ivs, /*requiredOperandCount=*/-1, 2050 OpAsmParser::Delimiter::Paren)) 2051 return failure(); 2052 2053 // Parse loop bounds. 2054 SmallVector<OpAsmParser::OperandType, 4> lower; 2055 if (parser.parseEqual() || 2056 parser.parseOperandList(lower, ivs.size(), 2057 OpAsmParser::Delimiter::Paren) || 2058 parser.resolveOperands(lower, builder.getIndexType(), result.operands)) 2059 return failure(); 2060 2061 SmallVector<OpAsmParser::OperandType, 4> upper; 2062 if (parser.parseKeyword("to") || 2063 parser.parseOperandList(upper, ivs.size(), 2064 OpAsmParser::Delimiter::Paren) || 2065 parser.resolveOperands(upper, builder.getIndexType(), result.operands)) 2066 return failure(); 2067 2068 // Parse step values. 2069 SmallVector<OpAsmParser::OperandType, 4> steps; 2070 if (parser.parseKeyword("step") || 2071 parser.parseOperandList(steps, ivs.size(), 2072 OpAsmParser::Delimiter::Paren) || 2073 parser.resolveOperands(steps, builder.getIndexType(), result.operands)) 2074 return failure(); 2075 2076 // Parse input tensors. 2077 SmallVector<OpAsmParser::OperandType, 4> inputs, input_region_args; 2078 SmallVector<Type, 4> inputTypes; 2079 if (succeeded(parser.parseOptionalKeyword("ins"))) { 2080 llvm::SMLoc inputsOperandsLoc = parser.getCurrentLocation(); 2081 2082 if (parser.parseAssignmentListWithTypes(input_region_args, inputs, 2083 inputTypes)) 2084 return failure(); 2085 2086 if (parser.resolveOperands(inputs, inputTypes, inputsOperandsLoc, 2087 result.operands)) 2088 return failure(); 2089 } 2090 2091 // Parse output tensors. 2092 SmallVector<OpAsmParser::OperandType, 4> outputs, output_region_args; 2093 SmallVector<Type, 4> outputTypes; 2094 if (succeeded(parser.parseOptionalKeyword("outs"))) { 2095 llvm::SMLoc outputsOperandsLoc = parser.getCurrentLocation(); 2096 2097 if (parser.parseAssignmentListWithTypes(output_region_args, outputs, 2098 outputTypes)) 2099 return failure(); 2100 2101 if (parser.resolveOperands(outputs, outputTypes, outputsOperandsLoc, 2102 result.operands)) 2103 return failure(); 2104 for (Type outputType : outputTypes) 2105 if (outputType.isa<RankedTensorType>()) 2106 result.addTypes(outputType); 2107 } 2108 2109 // Parse attributes. 2110 SmallVector<Attribute, 4> iterTypes, distributionTypes; 2111 auto parseAttr = [&](StringRef keyword, SmallVector<Attribute, 4> *attrs) { 2112 if (succeeded(parser.parseOptionalKeyword(keyword))) { 2113 StringAttr attr; 2114 2115 if (parser.parseLSquare() || parser.parseAttribute(attr)) 2116 return failure(); 2117 attrs->push_back(attr); 2118 for (int i = 1, e = ivs.size(); i < e; ++i) { 2119 if (parser.parseComma() || parser.parseAttribute(attr)) 2120 return failure(); 2121 attrs->push_back(attr); 2122 } 2123 if (parser.parseRSquare()) 2124 return failure(); 2125 } 2126 return success(); 2127 }; 2128 if (failed(parseAttr("iterators", &iterTypes)) || 2129 failed(parseAttr("distribution", &distributionTypes))) 2130 return failure(); 2131 2132 // Set all loop iterator types to "parallel" if they are not printed in IR. 2133 if (iterTypes.empty()) { 2134 auto parallelIter = builder.getStringAttr(getParallelIteratorTypeName()); 2135 iterTypes = SmallVector<Attribute, 4>(ivs.size(), parallelIter); 2136 } 2137 result.addAttribute(getIteratorTypesAttrName(), 2138 builder.getArrayAttr(iterTypes)); 2139 if (!distributionTypes.empty()) 2140 result.addAttribute(getDistributionTypesAttrName(), 2141 builder.getArrayAttr(distributionTypes)); 2142 result.addAttribute( 2143 TiledLoopOp::getOperandSegmentSizeAttr(), 2144 builder.getI32VectorAttr({static_cast<int32_t>(lower.size()), 2145 static_cast<int32_t>(upper.size()), 2146 static_cast<int32_t>(steps.size()), 2147 static_cast<int32_t>(inputs.size()), 2148 static_cast<int32_t>(outputs.size())})); 2149 2150 // Parse the body. 2151 Region *body = result.addRegion(); 2152 2153 SmallVector<Type, 4> region_types(ivs.size(), builder.getIndexType()); 2154 region_types.append(inputTypes); 2155 region_types.append(outputTypes); 2156 2157 SmallVector<OpAsmParser::OperandType, 4> region_args(ivs); 2158 region_args.append(input_region_args); 2159 region_args.append(output_region_args); 2160 2161 if (parser.parseRegion(*body, region_args, region_types)) 2162 return failure(); 2163 2164 // Parse optional attributes. 2165 parser.parseOptionalAttrDict(result.attributes); 2166 2167 return success(); 2168 } 2169 2170 Region &TiledLoopOp::getLoopBody() { return region(); } 2171 2172 LogicalResult TiledLoopOp::moveOutOfLoop(ArrayRef<Operation *> ops) { 2173 for (auto *op : ops) 2174 op->moveBefore(*this); 2175 return success(); 2176 } 2177 2178 bool TiledLoopOp::isDefinedOutsideOfLoop(Value value) { 2179 return !region().isAncestor(value.getParentRegion()); 2180 } 2181 2182 static LogicalResult verify(TiledLoopOp op) { 2183 // Check if iterator types are provided for every loop dimension. 2184 if (op.iterator_types().size() != op.getNumLoops()) 2185 return op.emitOpError("expected iterator types array attribute size = ") 2186 << op.iterator_types().size() 2187 << " to match the number of loops = " << op.getNumLoops(); 2188 2189 // Check if types of input arguments match region args types. 2190 for (auto &item : 2191 llvm::enumerate(llvm::zip(op.inputs(), op.getRegionInputArgs()))) { 2192 Value input, inputRegionArg; 2193 unsigned index = item.index(); 2194 std::tie(input, inputRegionArg) = item.value(); 2195 if (input.getType() != inputRegionArg.getType()) 2196 return op.emitOpError("expected input arg ") 2197 << index << " with type = " << input.getType() 2198 << " to match region arg " << index + op.getNumLoops() 2199 << " type = " << inputRegionArg.getType(); 2200 } 2201 2202 // Check if types of input arguments match region args types. 2203 for (auto &item : 2204 llvm::enumerate(llvm::zip(op.outputs(), op.getRegionOutputArgs()))) { 2205 Value output, outputRegionArg; 2206 unsigned index = item.index(); 2207 std::tie(output, outputRegionArg) = item.value(); 2208 if (output.getType() != outputRegionArg.getType()) 2209 return op.emitOpError("expected output arg ") 2210 << index << " with type = " << output.getType() 2211 << " to match region arg " 2212 << index + op.getNumLoops() + op.inputs().size() 2213 << " type = " << outputRegionArg.getType(); 2214 } 2215 return success(); 2216 } 2217 2218 namespace { 2219 2220 static constexpr int64_t kNoMatch = -1; 2221 2222 // Folds away TiledLoopOp inputs if they have no uses within the body. 2223 // 2224 // Example: 2225 // 2226 // %0 = linalg.tiled_loop ... ins (%in_ = %in: tensor<...>, 2227 // %in_buf_ = %in_buf: memref<...>) {...} 2228 // Becomes 2229 // 2230 // linalg.tiled_loop ... ins (%in_buf_ = %in_buf: memref<...>) {...} 2231 struct TiledLoopInputsFolder : public OpRewritePattern<linalg::TiledLoopOp> { 2232 using OpRewritePattern<linalg::TiledLoopOp>::OpRewritePattern; 2233 2234 LogicalResult matchAndRewrite(linalg::TiledLoopOp tiledLoop, 2235 PatternRewriter &rewriter) const final { 2236 SmallVector<Value, 2> newInputs, regionInputTensorArgs; 2237 // Store ids of the corresponding old and new input operands. 2238 SmallVector<int64_t, 2> oldInputIdToNew(tiledLoop.inputs().size(), 2239 kNoMatch); 2240 for (auto en : llvm::enumerate( 2241 llvm::zip(tiledLoop.inputs(), tiledLoop.getRegionInputArgs()))) { 2242 Value in, bbArg; 2243 size_t index = en.index(); 2244 std::tie(in, bbArg) = en.value(); 2245 if (!bbArg.use_empty()) { 2246 oldInputIdToNew[index] = newInputs.size(); 2247 newInputs.push_back(in); 2248 } 2249 } 2250 if (newInputs.size() == tiledLoop.inputs().size()) 2251 return failure(); 2252 Location loc = tiledLoop.getLoc(); 2253 auto newTiledLoop = rewriter.create<TiledLoopOp>( 2254 loc, tiledLoop.lowerBound(), tiledLoop.upperBound(), tiledLoop.step(), 2255 newInputs, tiledLoop.outputs(), tiledLoop.iterator_types(), 2256 tiledLoop.distribution_types()); 2257 2258 // Clone the region. 2259 BlockAndValueMapping bvm; 2260 bvm.map(tiledLoop.getInductionVars(), newTiledLoop.getInductionVars()); 2261 bvm.map(tiledLoop.getRegionOutputArgs(), 2262 newTiledLoop.getRegionOutputArgs()); 2263 for (const auto &en : llvm::enumerate(oldInputIdToNew)) 2264 if (en.value() != kNoMatch) 2265 bvm.map(tiledLoop.getRegionInputArgs()[en.index()], 2266 newTiledLoop.getRegionInputArgs()[en.value()]); 2267 OpBuilder innerBuilder = 2268 OpBuilder::atBlockEnd(newTiledLoop.getBody(), rewriter.getListener()); 2269 for (auto &op : *tiledLoop.getBody()) 2270 innerBuilder.clone(op, bvm); 2271 rewriter.replaceOp(tiledLoop, newTiledLoop.getResults()); 2272 2273 return success(); 2274 } 2275 }; 2276 2277 } // namespace 2278 2279 /// A simple, conservative analysis to determine if the loop is shape 2280 /// conserving. I.e., the type of the arg-th yielded value is the same as the 2281 /// type of the corresponding basic block argument of the loop. 2282 /// Note: This function handles only simple cases. Expand as needed. 2283 static bool isShapePreserving(TiledLoopOp loopOp, int64_t arg) { 2284 auto yieldOp = cast<YieldOp>(loopOp.getLoopBody().front().getTerminator()); 2285 if (yieldOp.values().empty()) 2286 // Tiled loop either has no outputs or is a "memref-based version". In 2287 // either case, the loop is shape conserving. 2288 return true; 2289 assert(arg < static_cast<int64_t>(yieldOp.values().size()) && 2290 "arg is out of bounds"); 2291 Value value = yieldOp.values()[arg]; 2292 while (value) { 2293 if (value == loopOp.getRegionOutputArgs()[arg]) 2294 return true; 2295 OpResult opResult = value.dyn_cast<OpResult>(); 2296 if (!opResult) 2297 return false; 2298 2299 using tensor::InsertSliceOp; 2300 value = llvm::TypeSwitch<Operation *, Value>(opResult.getOwner()) 2301 .template Case<InsertSliceOp>( 2302 [&](InsertSliceOp op) { return op.dest(); }) 2303 .template Case<TiledLoopOp>([&](TiledLoopOp loopOp) { 2304 return isShapePreserving(loopOp, opResult.getResultNumber()) 2305 ? loopOp.outputs()[opResult.getResultNumber()] 2306 : Value(); 2307 }) 2308 .Default([&](auto op) { return Value(); }); 2309 } 2310 return false; 2311 } 2312 2313 namespace { 2314 2315 /// Fold dim(x) where `x` is an input/output argument of a TiledLoopOp block 2316 /// to dim(y) where `y` is the initial input/output value of the argument. 2317 /// 2318 /// E.g.: 2319 /// %y = ... : tensor<...> 2320 /// linalg.tiled_loop ... ins(%x = %y : tensor<...>) { 2321 /// tensor.dim %x, %c0 : tensor<...> 2322 /// } 2323 /// 2324 /// is folded to: 2325 /// %y = ... : tensor<...> 2326 /// linalg.tiled_loop ... ins(%x = %y : tensor<...>) { 2327 /// tensor.dim %y, %c0 : tensor<...> 2328 /// } 2329 /// 2330 /// Note: Dim ops are folded only if it can be proven that the runtime type of 2331 /// the yielded value (in case of outputs) does not change with loop iterations. 2332 template <typename OpTy> 2333 struct DimOfTiledLoopInsOutsFolder : public OpRewritePattern<OpTy> { 2334 using OpRewritePattern<OpTy>::OpRewritePattern; 2335 2336 LogicalResult matchAndRewrite(OpTy dimOp, 2337 PatternRewriter &rewriter) const final { 2338 auto src = dimOp.source().template dyn_cast<BlockArgument>(); 2339 if (!src) 2340 return failure(); 2341 auto loopOp = 2342 dyn_cast<TiledLoopOp>(src.getOwner()->getParent()->getParentOp()); 2343 if (!loopOp) 2344 return failure(); 2345 unsigned numLoops = loopOp.getNumLoops(); 2346 unsigned numInputArgs = loopOp.getRegionInputArgs().size(); 2347 if (src.getArgNumber() >= numInputArgs + numLoops && 2348 !isShapePreserving(loopOp, 2349 src.getArgNumber() - numInputArgs - numLoops)) 2350 return failure(); 2351 2352 auto inputArgs = loopOp.getRegionInputArgs(); 2353 auto it1 = llvm::find(inputArgs, src); 2354 if (it1 != inputArgs.end()) { 2355 rewriter.updateRootInPlace(dimOp, [&] { 2356 dimOp.sourceMutable().assign(loopOp.inputs()[it1 - inputArgs.begin()]); 2357 }); 2358 return success(); 2359 } 2360 2361 auto outputArgs = loopOp.getRegionOutputArgs(); 2362 auto it2 = llvm::find(outputArgs, src); 2363 if (it2 != outputArgs.end()) { 2364 rewriter.updateRootInPlace(dimOp, [&] { 2365 dimOp.sourceMutable().assign( 2366 loopOp.outputs()[it2 - outputArgs.begin()]); 2367 }); 2368 return success(); 2369 } 2370 2371 return failure(); 2372 } 2373 }; 2374 2375 /// Fold dim(r) where `r` is the result of a TiledLoopOp to dim(y) where `y` 2376 /// is the initial output value of the loop. 2377 /// 2378 /// E.g.: 2379 /// %y = ... : tensor<...> 2380 /// %r = linalg.tiled_loop ... outs(%i = %y : tensor<...>) { 2381 /// ... 2382 /// } 2383 /// %0 = tensor.dim %r, %c0 : tensor<...> 2384 /// 2385 /// is folded to: 2386 /// %y = ... : tensor<...> 2387 /// linalg.tiled_loop ... outs(%i = %y : tensor<...>) { 2388 /// ... 2389 /// } 2390 /// %0 = tensor.dim %y, %c0 : tensor<...> 2391 /// 2392 /// Note: Dim ops are folded only if it can be proven that the runtime type of 2393 /// the yielded value (in case of outputs) does not change with loop iterations. 2394 template <typename OpTy> 2395 struct DimOfTiledLoopResultFolder : public OpRewritePattern<OpTy> { 2396 using OpRewritePattern<OpTy>::OpRewritePattern; 2397 2398 LogicalResult matchAndRewrite(OpTy dimOp, 2399 PatternRewriter &rewriter) const final { 2400 auto loopOp = dimOp.source().template getDefiningOp<TiledLoopOp>(); 2401 if (!loopOp) 2402 return failure(); 2403 auto opResult = dimOp.source().template cast<OpResult>(); 2404 unsigned resultNumber = opResult.getResultNumber(); 2405 if (!isShapePreserving(loopOp, resultNumber)) 2406 return failure(); 2407 rewriter.updateRootInPlace(dimOp, [&]() { 2408 dimOp.sourceMutable().assign(loopOp.outputs()[resultNumber]); 2409 }); 2410 return success(); 2411 } 2412 }; 2413 2414 // Folds away TiledLoopOp output tensors when the following conditions are met: 2415 // * result of `linalg.tiled_loop` has no uses 2416 // * output tensor is the argument of `linalg.yield` 2417 // 2418 // Example: 2419 // 2420 // %0 = linalg.tiled_loop ... outs (%o_ = %out: tensor<...>, 2421 // %obuf_ = %out_buf: memref<...>) { 2422 // ... 2423 // linalg.yield %o_ : tensor ... 2424 // } 2425 // 2426 // Becomes 2427 // 2428 // linalg.tiled_loop ... outs (%obuf_ = %out_buf: memref<...>) { 2429 // ... 2430 // linalg.yield 2431 // } 2432 struct TiledLoopResultsFolder : public OpRewritePattern<linalg::TiledLoopOp> { 2433 using OpRewritePattern<linalg::TiledLoopOp>::OpRewritePattern; 2434 2435 LogicalResult matchAndRewrite(linalg::TiledLoopOp tiledLoop, 2436 PatternRewriter &rewriter) const final { 2437 if (tiledLoop.getNumResults() == 0) 2438 return failure(); 2439 2440 Block *block = tiledLoop.getBody(); 2441 auto yieldOp = cast<linalg::YieldOp>(block->getTerminator()); 2442 2443 // Match the pattern and collect output buffers that will replace the output 2444 // tensors and also the ops that will be ignored when cloning the body. 2445 SmallVector<Value, 2> newOutputOperands, newYieldArgs; 2446 int resultId = 0; 2447 // Store ids of the corresponding old and new output operands. 2448 SmallVector<int64_t, 2> oldOutputIdToNew(tiledLoop.outputs().size(), 2449 kNoMatch); 2450 // Store ids of the corresponding old and new results. 2451 SmallVector<int64_t, 2> oldResultIdToNew(tiledLoop.getNumResults(), 2452 kNoMatch); 2453 SmallVector<Value, 2> resultReplacement(tiledLoop.getNumResults()); 2454 for (auto en : llvm::enumerate( 2455 llvm::zip(tiledLoop.outputs(), tiledLoop.getRegionOutputArgs()))) { 2456 size_t index = en.index(); 2457 Value out = std::get<0>(en.value()); 2458 Value outRegionArg = std::get<1>(en.value()); 2459 2460 if (!out.getType().isa<RankedTensorType>()) { 2461 oldOutputIdToNew[index] = newOutputOperands.size(); 2462 newOutputOperands.push_back(out); 2463 continue; 2464 } 2465 Value result = tiledLoop.getResult(resultId); 2466 Value yieldArg = yieldOp.getOperand(resultId); 2467 if (yieldArg != outRegionArg || !result.use_empty()) { 2468 oldOutputIdToNew[index] = newOutputOperands.size(); 2469 oldResultIdToNew[resultId] = newYieldArgs.size(); 2470 resultReplacement[resultId] = out; 2471 newOutputOperands.push_back(out); 2472 newYieldArgs.push_back(yieldArg); 2473 } 2474 ++resultId; 2475 } 2476 if (newOutputOperands.size() == tiledLoop.outputs().size()) 2477 return failure(); 2478 2479 Location loc = tiledLoop.getLoc(); 2480 auto newTiledLoop = rewriter.create<TiledLoopOp>( 2481 loc, tiledLoop.lowerBound(), tiledLoop.upperBound(), tiledLoop.step(), 2482 tiledLoop.inputs(), newOutputOperands, tiledLoop.iterator_types(), 2483 tiledLoop.distribution_types()); 2484 2485 // Clone the region. 2486 BlockAndValueMapping bvm; 2487 bvm.map(tiledLoop.getInductionVars(), newTiledLoop.getInductionVars()); 2488 bvm.map(tiledLoop.getRegionInputArgs(), newTiledLoop.getRegionInputArgs()); 2489 for (const auto &en : llvm::enumerate(oldOutputIdToNew)) { 2490 if (en.value() != kNoMatch) 2491 bvm.map(tiledLoop.getRegionOutputArgs()[en.index()], 2492 newTiledLoop.getRegionOutputArgs()[en.value()]); 2493 else 2494 bvm.map(tiledLoop.getRegionOutputArgs()[en.index()], 2495 tiledLoop.outputs()[en.index()]); 2496 } 2497 OpBuilder innerBuilder = 2498 OpBuilder::atBlockEnd(newTiledLoop.getBody(), rewriter.getListener()); 2499 for (auto &op : tiledLoop.getBody()->without_terminator()) 2500 innerBuilder.clone(op, bvm); 2501 innerBuilder.create<linalg::YieldOp>( 2502 loc, llvm::to_vector<2>(llvm::map_range( 2503 newYieldArgs, [&](Value arg) { return bvm.lookup(arg); }))); 2504 2505 for (const auto &en : llvm::enumerate(oldResultIdToNew)) 2506 if (en.value() != kNoMatch) 2507 resultReplacement[en.index()] = newTiledLoop.getResult(en.value()); 2508 rewriter.replaceOp(tiledLoop, resultReplacement); 2509 2510 return success(); 2511 } 2512 }; 2513 } // namespace 2514 2515 void TiledLoopOp::getCanonicalizationPatterns(OwningRewritePatternList &results, 2516 MLIRContext *context) { 2517 results.insert<TiledLoopInputsFolder, TiledLoopResultsFolder, 2518 DimOfTiledLoopInsOutsFolder<tensor::DimOp>, 2519 DimOfTiledLoopInsOutsFolder<memref::DimOp>, 2520 DimOfTiledLoopResultFolder<tensor::DimOp>, 2521 DimOfTiledLoopResultFolder<memref::DimOp>>(context); 2522 } 2523 2524 LogicalResult TiledLoopOp::fold(ArrayRef<Attribute>, 2525 SmallVectorImpl<OpFoldResult> &) { 2526 return foldMemRefCastInTiledLoopOp(*this); 2527 } 2528 2529 //===----------------------------------------------------------------------===// 2530 // IndexOp 2531 //===----------------------------------------------------------------------===// 2532 2533 static LogicalResult verify(IndexOp op) { 2534 auto linalgOp = dyn_cast<LinalgOp>(op->getParentOp()); 2535 if (!linalgOp) 2536 return op.emitOpError("expected parent op with LinalgOp interface"); 2537 if (linalgOp.getNumLoops() <= op.dim()) 2538 return op.emitOpError("expected dim (") 2539 << op.dim() << ") to be lower than the number of loops (" 2540 << linalgOp.getNumLoops() << ") of the enclosing LinalgOp"; 2541 return success(); 2542 } 2543 2544 /////// Operations corresponding to library calls defined with Tablegen //////// 2545 2546 template <typename LinalgPoolingOp> 2547 static LogicalResult verifyStrideOrDilation(LinalgPoolingOp op, 2548 ArrayRef<Attribute> attrs, 2549 bool isStride) { 2550 auto strideOrDilation = isStride ? "stride" : "dilation"; 2551 if (attrs.size() != op.getNumWindowLoops()) 2552 return op.emitOpError("expects num ") 2553 << strideOrDilation 2554 << "s equal to number of window dimensions: " << attrs.size() 2555 << " vs " << op.getNumWindowLoops(); 2556 return success(); 2557 } 2558 2559 void ConvOp::getEffects( 2560 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>> 2561 &effects) { 2562 effects.emplace_back(MemoryEffects::Read::get(), input(), 2563 SideEffects::DefaultResource::get()); 2564 effects.emplace_back(MemoryEffects::Read::get(), filter(), 2565 SideEffects::DefaultResource::get()); 2566 effects.emplace_back(MemoryEffects::Write::get(), output(), 2567 SideEffects::DefaultResource::get()); 2568 } 2569 2570 static LogicalResult verify(ConvOp op) { 2571 auto oType = op.output().getType().cast<MemRefType>(); 2572 auto fType = op.filter().getType().cast<MemRefType>(); 2573 auto iType = op.input().getType().cast<MemRefType>(); 2574 if (oType.getElementType() != iType.getElementType() || 2575 oType.getElementType() != fType.getElementType()) 2576 return op.emitOpError("expects memref elemental types to match"); 2577 if (oType.getRank() != iType.getRank() || oType.getRank() != fType.getRank()) 2578 return op.emitOpError("expects memref ranks to match"); 2579 if (auto strides = op.strides()) { 2580 if (failed(verifyStrideOrDilation(op, strides->getValue(), 2581 /*isStride=*/true))) 2582 return failure(); 2583 } 2584 if (auto dilations = op.dilations()) { 2585 if (failed(verifyStrideOrDilation(op, dilations->getValue(), 2586 /*isStride=*/false))) 2587 return failure(); 2588 } 2589 return success(); 2590 } 2591 2592 template <typename PoolingOp> 2593 static LogicalResult verifySingleInputPoolingOp(PoolingOp op) { 2594 auto inputType = op.input().getType().template cast<MemRefType>(); 2595 auto outputType = op.output().getType().template cast<MemRefType>(); 2596 if (outputType.getElementType() != inputType.getElementType()) 2597 return op.emitOpError("expects memref elemental types to match"); 2598 2599 auto windowDimsType = op.windowDims().getType().template cast<MemRefType>(); 2600 if (outputType.getRank() != inputType.getRank() || 2601 outputType.getRank() != windowDimsType.getRank()) 2602 return op.emitOpError("expects memref ranks to match"); 2603 2604 if (auto strides = op.strides()) { 2605 if (failed(verifyStrideOrDilation(op, strides->getValue(), 2606 /*isStride=*/true))) 2607 return failure(); 2608 } 2609 if (auto dilations = op.dilations()) { 2610 if (failed(verifyStrideOrDilation(op, dilations->getValue(), 2611 /*isStride=*/false))) 2612 return failure(); 2613 } 2614 return success(); 2615 } 2616 2617 #define DEFINE_POOLING_OP_GET_EFFECTS(OP_NAME) \ 2618 void OP_NAME::getEffects( \ 2619 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>> \ 2620 &effects) { \ 2621 effects.emplace_back(MemoryEffects::Read::get(), input(), \ 2622 SideEffects::DefaultResource::get()); \ 2623 effects.emplace_back(MemoryEffects::Write::get(), output(), \ 2624 SideEffects::DefaultResource::get()); \ 2625 } 2626 2627 static LogicalResult verify(PoolingMaxOp op) { 2628 return verifySingleInputPoolingOp(op); 2629 } 2630 static LogicalResult verify(PoolingMinOp op) { 2631 return verifySingleInputPoolingOp(op); 2632 } 2633 static LogicalResult verify(PoolingSumOp op) { 2634 return verifySingleInputPoolingOp(op); 2635 } 2636 2637 DEFINE_POOLING_OP_GET_EFFECTS(PoolingMaxOp) 2638 DEFINE_POOLING_OP_GET_EFFECTS(PoolingMinOp) 2639 DEFINE_POOLING_OP_GET_EFFECTS(PoolingSumOp) 2640 2641 #include "mlir/Dialect/Linalg/IR/LinalgNamedStructuredOps.tcgen.cpp.inc" 2642 #include "mlir/Dialect/Linalg/IR/LinalgNamedStructuredOps.yamlgen.cpp.inc" 2643 2644 #define GET_OP_CLASSES 2645 #include "mlir/Dialect/Linalg/IR/LinalgOps.cpp.inc" 2646 2647 #define GET_OP_CLASSES 2648 #include "mlir/Dialect/Linalg/IR/LinalgStructuredOps.cpp.inc" 2649 2650 /// Return the dims that are `iteratorTypeName` loops in the LinalgOp `op`. 2651 /// Assumes `op` is a LinalgOp. 2652 void mlir::linalg::getDimsOfType(Operation *op, StringRef iteratorTypeName, 2653 SmallVectorImpl<AffineExpr> &res) { 2654 if (!cast<LinalgOp>(op).iterator_types()) 2655 return; 2656 2657 unsigned dim = 0; 2658 MLIRContext *ctx = op->getContext(); 2659 for (auto tn : 2660 cast<LinalgOp>(op).iterator_types().getAsValueRange<StringAttr>()) { 2661 if (tn == iteratorTypeName) 2662 res.push_back(getAffineDimExpr(dim, ctx)); 2663 ++dim; 2664 } 2665 } 2666 2667 AffineMap mlir::linalg::extractOrIdentityMap(Optional<AffineMap> maybeMap, 2668 unsigned rank, 2669 MLIRContext *context) { 2670 if (maybeMap) 2671 return maybeMap.getValue(); 2672 if (rank == 0) 2673 return AffineMap::get(context); 2674 return AffineMap::getMultiDimIdentityMap(rank, context); 2675 } 2676 2677 SmallVector<AffineExpr, 4> 2678 mlir::linalg::makeAffineDimExprs(unsigned num, unsigned &startIdx, 2679 MLIRContext *context) { 2680 SmallVector<AffineExpr, 4> res; 2681 res.reserve(num); 2682 for (unsigned i = 0; i < num; ++i) 2683 res.push_back(getAffineDimExpr(startIdx++, context)); 2684 return res; 2685 } 2686 2687 template <typename PoolingOp> 2688 SmallVector<AffineExpr, 4> 2689 mlir::linalg::weightedPoolingInputIndex(PoolingOp op, 2690 ArrayRef<AffineExpr> outputDims, 2691 ArrayRef<AffineExpr> windowDims) { 2692 assert(outputDims.size() == windowDims.size()); 2693 SmallVector<AffineExpr, 4> res; 2694 res.reserve(outputDims.size()); 2695 for (unsigned i = 0, e = outputDims.size(); i < e; ++i) { 2696 // TODO: add a level of indirection to linalg.generic. 2697 auto expr = op.getStride(i) * outputDims[i] + 2698 op.getDilation(i) * windowDims[i] - op.getLowPad(i); 2699 res.push_back(expr); 2700 } 2701 return res; 2702 } 2703 2704 #define INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(OP_TYPE) \ 2705 template SmallVector<AffineExpr, 4> \ 2706 mlir::linalg::weightedPoolingInputIndex<OP_TYPE>( \ 2707 OP_TYPE op, ArrayRef<AffineExpr> outputDims, \ 2708 ArrayRef<AffineExpr> windowDims); 2709 2710 INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(ConvOp) 2711 INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(PoolingMaxOp) 2712 INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(PoolingMinOp) 2713 INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(PoolingSumOp) 2714 2715 SmallVector<AffineExpr, 4> mlir::linalg::concat(ArrayRef<AffineExpr> a, 2716 ArrayRef<AffineExpr> b) { 2717 auto rangeA = llvm::make_range(a.begin(), a.end()); 2718 auto rangeB = llvm::make_range(b.begin(), b.end()); 2719 auto concatRanges = llvm::concat<const AffineExpr>(rangeA, rangeB); 2720 return llvm::to_vector<4>(concatRanges); 2721 } 2722 2723 static void appendMangledType(llvm::raw_string_ostream &ss, Type t) { 2724 if (auto memref = t.dyn_cast<MemRefType>()) { 2725 ss << "view"; 2726 for (auto size : memref.getShape()) 2727 if (size < 0) 2728 ss << "sx"; 2729 else 2730 ss << size << "x"; 2731 appendMangledType(ss, memref.getElementType()); 2732 } else if (auto vec = t.dyn_cast<VectorType>()) { 2733 ss << "vector"; 2734 llvm::interleave( 2735 vec.getShape(), [&](int64_t i) { ss << i; }, [&]() { ss << "x"; }); 2736 appendMangledType(ss, vec.getElementType()); 2737 } else if (t.isSignlessIntOrIndexOrFloat()) { 2738 ss << t; 2739 } else { 2740 llvm_unreachable("Invalid type for linalg library name mangling"); 2741 } 2742 } 2743 2744 std::string mlir::linalg::generateLibraryCallName(Operation *op) { 2745 assert(isa<LinalgOp>(op)); 2746 std::string name(op->getName().getStringRef().str()); 2747 name.reserve(128); 2748 std::replace(name.begin(), name.end(), '.', '_'); 2749 llvm::raw_string_ostream ss(name); 2750 ss << "_"; 2751 auto types = op->getOperandTypes(); 2752 llvm::interleave( 2753 types.begin(), types.end(), [&](Type t) { appendMangledType(ss, t); }, 2754 [&]() { ss << "_"; }); 2755 return ss.str(); 2756 } 2757 2758 // TODO: Consider making all this boilerplate easy to autogenerate 2759 // with Tablegen. This seems a desirable property in the context of 2760 // OpInterfaces where a Linalg "named" op **isa** LinalgOp. 2761 OpFoldResult TensorExpandShapeOp::fold(ArrayRef<Attribute> operands) { 2762 return foldReshapeOp<TensorExpandShapeOp, TensorCollapseShapeOp>(*this, 2763 operands); 2764 } 2765 OpFoldResult TensorCollapseShapeOp::fold(ArrayRef<Attribute> operands) { 2766 return foldReshapeOp<TensorCollapseShapeOp, TensorExpandShapeOp>(*this, 2767 operands); 2768 } 2769 2770 //===----------------------------------------------------------------------===// 2771 // Support for named Linalg ops defined in ods-gen. 2772 //===----------------------------------------------------------------------===// 2773 2774 /// Generic entry point to create the block for the region of a LinalgOp. 2775 /// This is used by both named structured ops created by ods-gen and by manually 2776 /// defined C++ ops. 2777 /// This is used by both builders and parsers. 2778 /// This function creates the block in the region with arguments corresponding 2779 /// to the elemental types of `inputTypes` and `outputTypes`, which are asserted 2780 /// to be ShapedType. 2781 template <typename NamedStructuredOpType> 2782 static void 2783 fillStructuredOpRegion(OpBuilder &opBuilder, Region ®ion, 2784 TypeRange inputTypes, TypeRange outputTypes, 2785 std::function<void(unsigned, unsigned)> errorHandler) { 2786 assert(llvm::all_of(outputTypes, [](Type t) { return t.isa<ShapedType>(); })); 2787 2788 // TODO: atm all operands go through getElementTypeOrSelf, 2789 // reconsider when we have evidence we need to. 2790 SmallVector<Type, 8> argTypes; 2791 for (auto containers : {inputTypes, outputTypes}) 2792 for (auto t : containers) 2793 argTypes.push_back(getElementTypeOrSelf(t)); 2794 2795 // RAII. 2796 OpBuilder::InsertionGuard guard(opBuilder); 2797 Block *body = opBuilder.createBlock(®ion, /*insertPt=*/{}, argTypes); 2798 unsigned actual = body->getNumArguments(); 2799 unsigned expected = NamedStructuredOpType::getNumRegionArgs(); 2800 if (expected != actual) { 2801 if (errorHandler) 2802 errorHandler(expected, actual); 2803 return; 2804 } 2805 2806 opBuilder.setInsertionPointToStart(body); 2807 ImplicitLocOpBuilder b(opBuilder.getUnknownLoc(), opBuilder); 2808 NamedStructuredOpType::regionBuilder(b, *body); 2809 2810 // indexing_maps is an auto-generated method. 2811 2812 // iterator_types is an auto-generated method. 2813 } 2814 2815 /// Generic entry point to create both the region and the block of a LinalgOp. 2816 template <typename NamedStructuredOpType> 2817 void createAndFillStructuredOpRegion(OpBuilder &opBuilder, 2818 OperationState &result, 2819 TypeRange inputTypes, 2820 TypeRange outputTypes) { 2821 Region ®ion = *result.addRegion(); 2822 fillStructuredOpRegion<NamedStructuredOpType>( 2823 opBuilder, region, inputTypes, outputTypes, 2824 [&](unsigned expected, unsigned actual) { 2825 assert(expected != actual && "incorrect number of arguments"); 2826 }); 2827 } 2828 2829 /// Common parsing used for both named structured ops created by ods-gen and by 2830 /// manually defined C++ ops. Does not handle regions. 2831 static ParseResult 2832 parseCommonStructuredOpParts(OpAsmParser &parser, OperationState &result, 2833 SmallVectorImpl<Type> &inputTypes, 2834 SmallVectorImpl<Type> &outputTypes) { 2835 llvm::SMLoc inputsOperandsLoc, outputsOperandsLoc; 2836 SmallVector<OpAsmParser::OperandType, 4> inputsOperands, outputsOperands; 2837 2838 parser.parseOptionalAttrDict(result.attributes); 2839 2840 if (succeeded(parser.parseOptionalKeyword("ins"))) { 2841 if (parser.parseLParen()) 2842 return failure(); 2843 2844 inputsOperandsLoc = parser.getCurrentLocation(); 2845 if (parser.parseOperandList(inputsOperands) || 2846 parser.parseColonTypeList(inputTypes) || parser.parseRParen()) 2847 return failure(); 2848 } 2849 2850 if (succeeded(parser.parseOptionalKeyword("outs"))) { 2851 outputsOperandsLoc = parser.getCurrentLocation(); 2852 if (parser.parseLParen() || parser.parseOperandList(outputsOperands) || 2853 parser.parseColonTypeList(outputTypes) || parser.parseRParen()) 2854 return failure(); 2855 } 2856 2857 if (parser.resolveOperands(inputsOperands, inputTypes, inputsOperandsLoc, 2858 result.operands) || 2859 parser.resolveOperands(outputsOperands, outputTypes, outputsOperandsLoc, 2860 result.operands)) 2861 return failure(); 2862 2863 result.addAttribute("operand_segment_sizes", 2864 parser.getBuilder().getI32VectorAttr( 2865 {static_cast<int32_t>(inputsOperands.size()), 2866 static_cast<int32_t>(outputsOperands.size())})); 2867 return success(); 2868 } 2869 2870 template <typename NamedStructuredOpType> 2871 static void printCommonStructuredOpParts(OpAsmPrinter &p, 2872 NamedStructuredOpType op) { 2873 if (!op.inputs().empty()) 2874 p << " ins(" << op.inputs() << " : " << op.inputs().getTypes() << ")"; 2875 if (!op.outputs().empty()) 2876 p << " outs(" << op.outputs() << " : " << op.outputs().getTypes() << ")"; 2877 } 2878 2879 //===----------------------------------------------------------------------===// 2880 // Specific parsing and printing for named structured ops created by ods-gen. 2881 //===----------------------------------------------------------------------===// 2882 2883 template <typename NamedStructuredOpType> 2884 static ParseResult 2885 parseNamedStructuredOpRegion(OpAsmParser &parser, Region ®ion, 2886 TypeRange inputTypes, TypeRange outputTypes) { 2887 ParseResult res = success(); 2888 OpBuilder opBuilder(parser.getBuilder().getContext()); 2889 // Resolve `captures` into `capturedValues` at parse time so we can build the 2890 // region with captures. 2891 SmallVector<Value> capturedValues; 2892 fillStructuredOpRegion<NamedStructuredOpType>( 2893 opBuilder, region, inputTypes, outputTypes, 2894 [&](unsigned expected, unsigned actual) { 2895 res = parser.emitError( 2896 parser.getCurrentLocation(), 2897 llvm::formatv("[parseNamedStructuredOpRegion] ods-gen generated " 2898 "region expects {0} args, got {1}", 2899 expected, actual)); 2900 region.front().dump(); 2901 }); 2902 return res; 2903 } 2904 2905 static ParseResult 2906 parseNamedStructuredOpResults(OpAsmParser &parser, 2907 SmallVectorImpl<Type> &resultTypes) { 2908 if (parser.parseOptionalArrowTypeList(resultTypes)) 2909 return failure(); 2910 return success(); 2911 } 2912 2913 template <typename NamedStructuredOpType> 2914 static ParseResult parseNamedStructuredOp(OpAsmParser &parser, 2915 OperationState &result) { 2916 // TODO: Enable when ods-gen supports captures. 2917 SmallVector<Type, 1> inputTypes, outputTypes; 2918 if (parseCommonStructuredOpParts(parser, result, inputTypes, outputTypes)) 2919 return failure(); 2920 2921 // TODO: consider merging results parsing into region parsing. 2922 // Need to wait for declarative assembly resolution to decide. 2923 SmallVector<Type, 1> outputTensorsTypes; 2924 if (parseNamedStructuredOpResults(parser, outputTensorsTypes)) 2925 return failure(); 2926 result.addTypes(outputTensorsTypes); 2927 2928 std::unique_ptr<Region> region = std::make_unique<Region>(); 2929 if (parseNamedStructuredOpRegion<NamedStructuredOpType>( 2930 parser, *region, inputTypes, outputTypes)) 2931 return failure(); 2932 result.addRegion(std::move(region)); 2933 2934 return success(); 2935 } 2936 2937 static void printNamedStructuredOpResults(OpAsmPrinter &p, 2938 TypeRange resultTypes) { 2939 if (resultTypes.empty()) 2940 return; 2941 p.printOptionalArrowTypeList(resultTypes); 2942 } 2943 2944 template <typename NamedStructuredOpType> 2945 static void printNamedStructuredOp(OpAsmPrinter &p, NamedStructuredOpType op) { 2946 p.printOptionalAttrDict( 2947 op->getAttrs(), 2948 /*elidedAttrs=*/{"operand_segment_sizes", 2949 // See generated code in mlir-linalg-yaml-gen.cpp 2950 "linalg.memoized_indexing_maps"}); 2951 2952 // Printing is shared with generic ops, except for the region and 2953 // attributes. 2954 printCommonStructuredOpParts(p, op); 2955 2956 // Results printing. 2957 printNamedStructuredOpResults(p, op.result_tensors().getTypes()); 2958 2959 // Region is elided. 2960 } 2961 2962 template <typename NamedStructuredOpType> 2963 static LogicalResult verifyNamedStructuredOp(NamedStructuredOpType op) { 2964 return verifyGenericOp<NamedStructuredOpType>(op); 2965 } 2966 2967 //===----------------------------------------------------------------------===// 2968 // Canonicalizers and Folders. 2969 //===----------------------------------------------------------------------===// 2970 2971 namespace { 2972 struct EraseDeadLinalgOp : public OpInterfaceRewritePattern<LinalgOp> { 2973 using OpInterfaceRewritePattern<LinalgOp>::OpInterfaceRewritePattern; 2974 2975 LogicalResult matchAndRewrite(LinalgOp op, 2976 PatternRewriter &rewriter) const override { 2977 for (OpOperand *opOperand : op.getInputAndOutputOperands()) { 2978 // Linalg "inputs" may be either tensor or memref type. 2979 // tensor<0xelt_type> is a convention that may not always mean 2980 // "0 iterations". Only erase in cases we see memref<...x0x...>. 2981 auto mt = opOperand->get().getType().dyn_cast<MemRefType>(); 2982 if (!mt) 2983 continue; 2984 if (llvm::is_contained(op.getShape(opOperand), 0)) { 2985 rewriter.eraseOp(op); 2986 return success(); 2987 } 2988 } 2989 return failure(); 2990 } 2991 }; 2992 2993 struct FoldTensorCastOp : public OpInterfaceRewritePattern<LinalgOp> { 2994 using OpInterfaceRewritePattern<LinalgOp>::OpInterfaceRewritePattern; 2995 2996 LogicalResult matchAndRewrite(LinalgOp op, 2997 PatternRewriter &rewriter) const override { 2998 // If no operand comes from a tensor::CastOp and can be folded then fail. 2999 bool hasTensorCastOperand = 3000 llvm::any_of(op.getInputAndOutputOperands(), [&](OpOperand *opOperand) { 3001 if (opOperand->get().isa<BlockArgument>()) 3002 return false; 3003 auto castOp = opOperand->get().getDefiningOp<tensor::CastOp>(); 3004 return castOp && canFoldIntoConsumerOp(castOp); 3005 }); 3006 if (!hasTensorCastOperand) 3007 return failure(); 3008 3009 SmallVector<Type, 4> newResultTypes; 3010 newResultTypes.reserve(op->getNumResults()); 3011 SmallVector<Value, 4> newOperands; 3012 newOperands.reserve(op->getNumOperands()); 3013 // Inputs may fold. 3014 for (OpOperand *opOperand : op.getInputOperands()) { 3015 auto tensorCastOp = opOperand->get().getDefiningOp<tensor::CastOp>(); 3016 newOperands.push_back(canFoldIntoConsumerOp(tensorCastOp) 3017 ? tensorCastOp.source() 3018 : opOperand->get()); 3019 } 3020 // Init tensors may fold, in which case the resultType must also change. 3021 for (OpOperand *opOperand : op.getOutputOperands()) { 3022 auto tensorCastOp = opOperand->get().getDefiningOp<tensor::CastOp>(); 3023 bool fold = canFoldIntoConsumerOp(tensorCastOp); 3024 newOperands.push_back(fold ? tensorCastOp.getOperand() 3025 : opOperand->get()); 3026 newResultTypes.push_back(newOperands.back().getType()); 3027 } 3028 // Clone op. 3029 Operation *newOp = 3030 op.clone(rewriter, op->getLoc(), newResultTypes, newOperands); 3031 SmallVector<Value, 4> replacements; 3032 replacements.reserve(newOp->getNumResults()); 3033 for (auto result : llvm::zip(op->getResults(), newOp->getResults())) { 3034 Value oldResult = std::get<0>(result); 3035 Value newResult = std::get<1>(result); 3036 if (newResult.getType() != oldResult.getType()) { 3037 replacements.push_back(rewriter.create<tensor::CastOp>( 3038 op->getLoc(), oldResult.getType(), newResult)); 3039 } else { 3040 replacements.push_back(newResult); 3041 } 3042 } 3043 rewriter.replaceOp(op, replacements); 3044 3045 return success(); 3046 } 3047 }; 3048 3049 static llvm::SmallVector<int64_t> getIndicesVector(int start, int end) { 3050 return llvm::to_vector<2>(llvm::seq<int64_t>(start, end)); 3051 } 3052 3053 LogicalResult matchAndReplaceDepthwiseConv(Operation *operation, Value input, 3054 Value kernel, Value iZp, Value kZp, 3055 Value init, Attribute stride, 3056 Attribute dilation, 3057 PatternRewriter &rewriter) { 3058 Location loc = operation->getLoc(); 3059 auto linalgOp = dyn_cast<LinalgOp>(operation); 3060 // Exit out on the memref version of this operation. 3061 if (!linalgOp || !linalgOp.hasTensorSemantics()) 3062 return failure(); 3063 3064 auto result = operation->getResult(0); 3065 3066 auto kernelTy = kernel.getType().dyn_cast<RankedTensorType>(); 3067 auto initTy = init.getType().dyn_cast<RankedTensorType>(); 3068 auto resultTy = result.getType().template dyn_cast<RankedTensorType>(); 3069 if (!kernelTy || !initTy || !resultTy) 3070 return failure(); 3071 3072 if (kernelTy.getDimSize(3) != 1) 3073 return failure(); 3074 3075 // Collapse kernel dims. 3076 SmallVector<ReassociationIndices, 4> collapsedKernelDims = { 3077 getIndicesVector(0, 1), getIndicesVector(1, 2), getIndicesVector(2, 4)}; 3078 auto newKernelTy = RankedTensorType::get( 3079 {kernelTy.getDimSize(0), kernelTy.getDimSize(1), kernelTy.getDimSize(2)}, 3080 kernelTy.getElementType()); 3081 auto collapsedKernel = rewriter.create<linalg::TensorCollapseShapeOp>( 3082 loc, newKernelTy, kernel, collapsedKernelDims); 3083 3084 // Collapse init dims. 3085 SmallVector<ReassociationIndices, 4> collapsedInitDims = { 3086 getIndicesVector(0, 1), getIndicesVector(1, 2), getIndicesVector(2, 3), 3087 getIndicesVector(3, 5)}; 3088 auto newInitTy = 3089 RankedTensorType::get({initTy.getDimSize(0), initTy.getDimSize(1), 3090 initTy.getDimSize(2), initTy.getDimSize(3)}, 3091 initTy.getElementType()); 3092 auto collapsedInit = rewriter.create<linalg::TensorCollapseShapeOp>( 3093 loc, newInitTy, init, collapsedInitDims); 3094 3095 Value newConv; 3096 if (isa<DepthwiseConv2DNhwcOp>(operation)) { 3097 newConv = rewriter 3098 .create<DepthwiseConv2DNhwOp>( 3099 loc, newInitTy, ValueRange{input, collapsedKernel}, 3100 ValueRange{collapsedInit}, stride, dilation) 3101 .getResult(0); 3102 } else if (isa<DepthwiseConv2DNhwcQOp>(operation)) { 3103 newConv = 3104 rewriter 3105 .create<DepthwiseConv2DNhwQOp>( 3106 loc, newInitTy, ValueRange{input, collapsedKernel, iZp, kZp}, 3107 ValueRange{collapsedInit}, stride, dilation) 3108 .getResult(0); 3109 } 3110 3111 if (!newConv) 3112 return failure(); 3113 3114 // Expand dimensions back out to 3115 rewriter.replaceOpWithNewOp<linalg::TensorExpandShapeOp>( 3116 operation, resultTy, newConv, collapsedInitDims); 3117 return success(); 3118 } 3119 3120 struct SimplifyDepthwiseConvOp 3121 : public OpRewritePattern<DepthwiseConv2DNhwcOp> { 3122 using OpRewritePattern<DepthwiseConv2DNhwcOp>::OpRewritePattern; 3123 3124 LogicalResult matchAndRewrite(DepthwiseConv2DNhwcOp op, 3125 PatternRewriter &rewriter) const override { 3126 Operation *operation = op.getOperation(); 3127 Value input = op.getInputOperand(0)->get(); 3128 Value kernel = op.getInputOperand(1)->get(); 3129 Value init = op.getOutputOperand(0)->get(); 3130 3131 auto stride = op.strides(); 3132 auto dilation = op.dilations(); 3133 3134 return matchAndReplaceDepthwiseConv(operation, input, kernel, nullptr, 3135 nullptr, init, stride, dilation, 3136 rewriter); 3137 } 3138 }; 3139 3140 struct SimplifyDepthwiseConvQOp 3141 : public OpRewritePattern<DepthwiseConv2DNhwcQOp> { 3142 using OpRewritePattern<DepthwiseConv2DNhwcQOp>::OpRewritePattern; 3143 3144 LogicalResult matchAndRewrite(DepthwiseConv2DNhwcQOp op, 3145 PatternRewriter &rewriter) const override { 3146 Operation *operation = op.getOperation(); 3147 Value input = op.getInputOperand(0)->get(); 3148 Value kernel = op.getInputOperand(1)->get(); 3149 Value iZp = op.getInputOperand(2)->get(); 3150 Value kZp = op.getInputOperand(3)->get(); 3151 Value init = op.getOutputOperand(0)->get(); 3152 3153 auto stride = op.strides(); 3154 auto dilation = op.dilations(); 3155 3156 return matchAndReplaceDepthwiseConv(operation, input, kernel, iZp, kZp, 3157 init, stride, dilation, rewriter); 3158 } 3159 }; 3160 3161 } // namespace 3162 3163 #define LINALGOP_FOLDERS(XXX) \ 3164 LogicalResult XXX::fold(ArrayRef<Attribute>, \ 3165 SmallVectorImpl<OpFoldResult> &) { \ 3166 return foldMemRefCast(*this); \ 3167 } 3168 3169 LINALGOP_FOLDERS(ConvOp) 3170 LINALGOP_FOLDERS(PoolingMaxOp) 3171 LINALGOP_FOLDERS(PoolingMinOp) 3172 LINALGOP_FOLDERS(PoolingSumOp) 3173 LINALGOP_FOLDERS(CopyOp) 3174 LINALGOP_FOLDERS(FillOp) 3175 LINALGOP_FOLDERS(GenericOp) 3176 3177 // All named ops canonicalizers and folders are auto-generated in the 3178 // .cpp.inc. 3179 3180 //===----------------------------------------------------------------------===// 3181 // LinalgDialect 3182 //===----------------------------------------------------------------------===// 3183 3184 void LinalgDialect::getCanonicalizationPatterns( 3185 RewritePatternSet &results) const { 3186 results.add<EraseDeadLinalgOp, FoldTensorCastOp, SimplifyDepthwiseConvOp, 3187 SimplifyDepthwiseConvQOp>(getContext()); 3188 } 3189