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