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/MemRef/IR/MemRef.h" 19 #include "mlir/Dialect/StandardOps/IR/Ops.h" 20 #include "mlir/IR/AffineExprVisitor.h" 21 #include "mlir/IR/Matchers.h" 22 #include "mlir/IR/OpImplementation.h" 23 #include "mlir/IR/PatternMatch.h" 24 #include "mlir/Parser.h" 25 26 #include "llvm/ADT/DenseMap.h" 27 #include "llvm/ADT/SetVector.h" 28 #include "llvm/ADT/SmallSet.h" 29 #include "llvm/ADT/StringSet.h" 30 #include "llvm/Support/FormatVariadic.h" 31 #include "llvm/Support/MathExtras.h" 32 #include "llvm/Support/raw_ostream.h" 33 34 using namespace mlir; 35 using namespace mlir::linalg; 36 37 /// Forward declarations. 38 39 /// Generic entry point to create the block for the region of a LinalgOp. 40 /// This is used by both named structured ops created by ods-gen and by manually 41 /// defined C++ ops. 42 /// This is used by both builders and parsers. 43 /// This function creates the block in the region with arguments corresponding 44 /// to the elemental types of `inputTypes` and `outputTypes`, which are asserted 45 /// to be ShapedType. 46 template <typename NamedStructuredOpType> 47 static void fillStructuredOpRegion( 48 OpBuilder &opBuilder, Region ®ion, TypeRange inputTypes, 49 TypeRange outputTypes, ValueRange captures = {}, 50 std::function<void(unsigned, unsigned)> errorHandler = nullptr); 51 52 /// Generic entry point to create both the region and the block of a LinalgOp. 53 template <typename NamedStructuredOpType> 54 static void 55 createAndFillStructuredOpRegion(OpBuilder &opBuilder, OperationState &result, 56 TypeRange inputTypes, TypeRange outputTypes, 57 ValueRange captures = {}); 58 59 /// Common parsing and printing used for both named structured ops created by 60 /// ods-gen and by manually defined C++ ops. Does not handle regions. 61 static ParseResult 62 parseCommonStructuredOpParts(OpAsmParser &parser, OperationState &result, 63 SmallVectorImpl<Type> &inputTypes, 64 SmallVectorImpl<Type> &outputTypes); 65 template <typename NamedStructuredOpType> 66 static void printCommonStructuredOpParts(OpAsmPrinter &p, 67 NamedStructuredOpType op); 68 69 /// Specific parsing and printing for named structured ops created by ods-gen. 70 template <typename NamedStructuredOpType> 71 static ParseResult 72 parseNamedStructuredOpRegion(OpAsmParser &parser, Region ®ion, 73 TypeRange inputTypes, TypeRange outputTypes, 74 ArrayRef<OpAsmParser::OperandType> captures = {}); 75 76 static ParseResult 77 parseNamedStructuredOpResults(OpAsmParser &parser, 78 SmallVectorImpl<Type> &resultTypes); 79 80 template <typename NamedStructuredOpType> 81 static ParseResult 82 parseNamedStructuredOp(OpAsmParser &parser, OperationState &result, 83 ArrayRef<OpAsmParser::OperandType> captures = {}); 84 85 static void printNamedStructuredOpResults(OpAsmPrinter &p, 86 TypeRange resultTypes); 87 88 template <typename NamedStructuredOpType> 89 static void printNamedStructuredOp(OpAsmPrinter &p, NamedStructuredOpType op); 90 91 /// Helper function to dispatch an OpFoldResult into either the `dynamicVec` if 92 /// it is a Value or into `staticVec` if it is an IntegerAttr. 93 /// In the case of a Value, a copy of the `sentinel` value is also pushed to 94 /// `staticVec`. This is useful to extract mixed static and dynamic entries that 95 /// come from an AttrSizedOperandSegments trait. 96 static void dispatchIndexOpFoldResult(OpFoldResult ofr, 97 SmallVectorImpl<Value> &dynamicVec, 98 SmallVectorImpl<int64_t> &staticVec, 99 int64_t sentinel) { 100 if (auto v = ofr.dyn_cast<Value>()) { 101 dynamicVec.push_back(v); 102 staticVec.push_back(sentinel); 103 return; 104 } 105 APInt apInt = ofr.dyn_cast<Attribute>().cast<IntegerAttr>().getValue(); 106 staticVec.push_back(apInt.getSExtValue()); 107 } 108 109 /// This is a common class used for patterns of the form 110 /// ``` 111 /// someop(memrefcast) -> someop 112 /// ``` 113 /// It folds the source of the memref.cast into the root operation directly. 114 static LogicalResult foldMemRefCast(Operation *op) { 115 bool folded = false; 116 for (OpOperand &operand : op->getOpOperands()) { 117 auto castOp = operand.get().getDefiningOp<memref::CastOp>(); 118 if (castOp && memref::CastOp::canFoldIntoConsumerOp(castOp)) { 119 operand.set(castOp.getOperand()); 120 folded = true; 121 } 122 } 123 return success(folded); 124 } 125 126 //===----------------------------------------------------------------------===// 127 // Region builder helper. 128 // TODO: Move this to a utility library. 129 // The public methods on this class are referenced directly from generated code 130 // and bind by name to math functions in the DSL as: 131 // `applyfn__{fnName}` 132 // Examples: 133 // `applyfn__add` 134 // `applyfn__mul` 135 // The naming convention is intentional in order to match snake-cased DSL names. 136 // See mlir-linalg-ods-yaml-gen.cpp for the code that mates to this class. 137 // 138 // Implementations of the math functions must be polymorphic over numeric types, 139 // internally performing necessary casts. If the function application makes no 140 // sense, then the only recourse is to assert and return nullptr. This can be 141 // extended later if it becomes possible to fail construction of the region. The 142 // invariant should be enforced at a higher level. 143 // 144 // TODO: These helpers are currently type polymorphic over the class of integer 145 // and floating point types, but they will not internally cast within bit 146 // widths of a class (mixed precision such as i8->i32) or across classes 147 // (i.e. mixed float and integer). Many such combinations are ambiguous or need 148 // to be handled with care and work is being considered to extend the op 149 // language to make such cases explicit. In the mean-time, violating this will 150 // fail verification, which is deemed acceptable. 151 //===----------------------------------------------------------------------===// 152 153 namespace { 154 155 class RegionBuilderHelper { 156 public: 157 RegionBuilderHelper(Block &block) : block(block) {} 158 159 // Generates operations to cast the given operand to a specified type. 160 // If the cast cannot be performed, a warning will be issued and the 161 // operand returned as-is (which will presumably yield a verification 162 // issue downstream). 163 Value cast(Type toType, Value operand) { 164 OpBuilder builder = getBuilder(operand); 165 auto loc = operand.getLoc(); 166 167 if (operand.getType() == toType) 168 return operand; 169 if (auto toIntType = toType.dyn_cast<IntegerType>()) { 170 // If operand is floating point, cast directly to the int type. 171 if (operand.getType().isa<FloatType>()) 172 return builder.create<FPToSIOp>(loc, toType, operand); 173 if (auto fromIntType = operand.getType().dyn_cast<IntegerType>()) { 174 // Either sign extend or truncate. 175 if (toIntType.getWidth() > fromIntType.getWidth()) 176 return builder.create<SignExtendIOp>(loc, toType, operand); 177 else if (toIntType.getWidth() < fromIntType.getWidth()) 178 return builder.create<TruncateIOp>(loc, toType, operand); 179 } 180 } else if (auto toFloatType = toType.dyn_cast<FloatType>()) { 181 // If operand is integer, cast directly to the float type. 182 // Note that it is unclear how to cast from BF16<->FP16. 183 if (operand.getType().isa<IntegerType>()) 184 return builder.create<SIToFPOp>(loc, toFloatType, operand); 185 if (auto fromFloatType = operand.getType().dyn_cast<FloatType>()) { 186 if (toFloatType.getWidth() > fromFloatType.getWidth()) 187 return builder.create<FPExtOp>(loc, toFloatType, operand); 188 else if (toFloatType.getWidth() < fromFloatType.getWidth()) 189 return builder.create<FPTruncOp>(loc, toFloatType, operand); 190 } 191 } 192 193 emitWarning(operand.getLoc()) << "could not cast operand of type " 194 << operand.getType() << " to " << toType; 195 return operand; 196 } 197 198 Value applyfn__add(Value lhs, Value rhs) { 199 OpBuilder builder = getBuilder(lhs); 200 if (isFloatingPoint(lhs)) 201 return builder.create<AddFOp>(lhs.getLoc(), lhs, rhs); 202 else if (isInteger(lhs)) 203 return builder.create<AddIOp>(lhs.getLoc(), lhs, rhs); 204 llvm_unreachable("unsupported non numeric type"); 205 } 206 207 Value applyfn__mul(Value lhs, Value rhs) { 208 OpBuilder builder = getBuilder(lhs); 209 if (isFloatingPoint(lhs)) 210 return builder.create<MulFOp>(lhs.getLoc(), lhs, rhs); 211 else if (isInteger(lhs)) 212 return builder.create<MulIOp>(lhs.getLoc(), lhs, rhs); 213 llvm_unreachable("unsupported non numeric type"); 214 } 215 216 void yieldOutputs(ValueRange values) { 217 assert(!values.empty() && "linalg ops must yield outputs"); 218 if (values.empty()) 219 return; 220 Value first = values.front(); 221 OpBuilder builder = getBuilder(first); 222 builder.create<YieldOp>(first.getLoc(), values); 223 } 224 225 private: 226 Block █ 227 228 bool isFloatingPoint(Value value) { return value.getType().isa<FloatType>(); } 229 bool isInteger(Value value) { return value.getType().isa<IntegerType>(); } 230 231 OpBuilder getBuilder(Value value) { 232 OpBuilder builder(value.getContext()); 233 builder.setInsertionPointToEnd(&block); 234 return builder; 235 } 236 }; 237 238 } // namespace 239 240 //===----------------------------------------------------------------------===// 241 // CopyOp 242 //===----------------------------------------------------------------------===// 243 void CopyOp::regionBuilder(Block &block, ValueRange captures) { 244 using namespace edsc::intrinsics; 245 assert(block.getNumArguments() == 2 && "CopyOp regionBuilder expects 2 args"); 246 (linalg_yield(block.getArgument(0))); 247 } 248 249 void CopyOp::build(OpBuilder &builder, OperationState &result, Value input, 250 Value output, AffineMap inputPermutation, 251 AffineMap outputPermutation, 252 ArrayRef<NamedAttribute> namedAttrs) { 253 result.addOperands({input, output}); 254 result.addAttributes(namedAttrs); 255 if (inputPermutation) 256 result.addAttribute("inputPermutation", 257 AffineMapAttr::get(inputPermutation)); 258 if (outputPermutation) 259 result.addAttribute("outputPermutation", 260 AffineMapAttr::get(outputPermutation)); 261 result.addRegion(); 262 fillStructuredOpRegion<CopyOp>(builder, *result.regions.front(), 263 TypeRange{input.getType()}, 264 TypeRange{output.getType()}); 265 } 266 267 ParseResult parseCopyOpRegion(OpAsmParser &parser, Region &r, Type inputType, 268 Type outputType) { 269 OpBuilder opBuilder(parser.getBuilder().getContext()); 270 fillStructuredOpRegion<CopyOp>(opBuilder, r, TypeRange{inputType}, 271 TypeRange{outputType}); 272 return success(); 273 } 274 275 /// CopyOp region is elided when printing. 276 void printCopyOpRegion(OpAsmPrinter &, Operation *, Region &, Type, Type) {} 277 278 static LogicalResult verify(CopyOp op) { 279 auto outputViewType = op.getOutputShapedType(0); 280 auto inputViewType = op.getInputShapedType(0); 281 if (inputViewType.getElementType() != outputViewType.getElementType()) 282 return op.emitOpError("expects views of the same type"); 283 if (inputViewType.getRank() != outputViewType.getRank()) 284 return op.emitOpError("expects views of the same rank"); 285 auto rank = op.getNumParallelLoops(); 286 auto inputPermutationMap = op.inputPermutation(); 287 if (inputPermutationMap) { 288 if (inputPermutationMap->getNumInputs() != rank) 289 return op.emitOpError("expects optional input_permutation map of rank ") 290 << rank; 291 if (!inputPermutationMap->isPermutation()) 292 return op.emitOpError( 293 "expects optional input_permutation map to be a permutation"); 294 } 295 auto outputPermutationMap = op.outputPermutation(); 296 if (outputPermutationMap) { 297 if (outputPermutationMap->getNumInputs() != rank) 298 return op.emitOpError("expects optional output_permutation map of rank ") 299 << rank; 300 if (!outputPermutationMap->isPermutation()) 301 return op.emitOpError( 302 "expects optional output_permutation map to be a permutation"); 303 } 304 if (rank == 0 && inputPermutationMap) 305 return op.emitOpError("expected no input permutation when rank == 0"); 306 if (rank == 0 && outputPermutationMap) 307 return op.emitOpError("expected no output permutation when rank == 0"); 308 return success(); 309 } 310 311 void CopyOp::getEffects( 312 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>> 313 &effects) { 314 effects.emplace_back(MemoryEffects::Read::get(), input(), 315 SideEffects::DefaultResource::get()); 316 effects.emplace_back(MemoryEffects::Write::get(), output(), 317 SideEffects::DefaultResource::get()); 318 } 319 320 //===----------------------------------------------------------------------===// 321 // FillOp 322 //===----------------------------------------------------------------------===// 323 void FillOp::regionBuilder(Block &block, ValueRange captures) { 324 using namespace edsc::intrinsics; 325 assert(captures.size() == 1 && "FillOp regionBuilder expects 1 capture"); 326 (linalg_yield(captures)); 327 } 328 329 void FillOp::build(OpBuilder &builder, OperationState &result, Value output, 330 Value value) { 331 build(builder, result, output.getType().dyn_cast<RankedTensorType>(), output, 332 value); 333 fillStructuredOpRegion<FillOp>(builder, *result.regions.front(), TypeRange{}, 334 TypeRange{output.getType()}, value); 335 } 336 337 ParseResult parseFillOpRegion(OpAsmParser &parser, Region &r, Type outputType, 338 OpAsmParser::OperandType valueRef) { 339 OpBuilder opBuilder(parser.getBuilder().getContext()); 340 // Resolve `valueRef` into `value` at parse time so we can build the region 341 // with captures. 342 SmallVector<Value> value; 343 parser.resolveOperand(valueRef, getElementTypeOrSelf(outputType), value); 344 fillStructuredOpRegion<FillOp>(opBuilder, r, TypeRange{}, 345 TypeRange{outputType}, value); 346 return success(); 347 } 348 349 /// FillOp region is elided when printing. 350 void printFillOpRegion(OpAsmPrinter &, Operation *, Region &, Type, Value) {} 351 352 static LogicalResult verify(FillOp op) { 353 auto viewType = op.getOutputShapedType(0); 354 auto fillType = op.value().getType(); 355 if (viewType.getElementType() != fillType) 356 return op.emitOpError("expects fill type to match view elemental type"); 357 if (!op.getNumResults() && !viewType.isa<MemRefType>()) { 358 return op.emitOpError( 359 "expected fill op with no result value to use memref type"); 360 } 361 return success(); 362 } 363 364 void FillOp::getEffects( 365 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>> 366 &effects) { 367 if (output().getType().isa<MemRefType>()) 368 effects.emplace_back(MemoryEffects::Write::get(), output(), 369 SideEffects::DefaultResource::get()); 370 } 371 372 //===----------------------------------------------------------------------===// 373 // GenericOps 374 //===----------------------------------------------------------------------===// 375 void GenericOp::build( 376 OpBuilder &builder, OperationState &result, TypeRange resultTensorTypes, 377 ValueRange inputs, ValueRange outputs, ArrayRef<AffineMap> indexingMaps, 378 ArrayRef<StringRef> iteratorTypes, StringRef doc, StringRef libraryCall, 379 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild) { 380 build(builder, result, resultTensorTypes, inputs, outputs, 381 builder.getAffineMapArrayAttr(indexingMaps), 382 builder.getStrArrayAttr(iteratorTypes), 383 doc.empty() ? StringAttr() : builder.getStringAttr(doc), 384 libraryCall.empty() ? StringAttr() : builder.getStringAttr(libraryCall), 385 ArrayAttr()); 386 if (!bodyBuild) 387 return; 388 389 SmallVector<Type, 4> blockArgTypes; 390 for (ValueRange container : {inputs, outputs}) 391 for (Value v : container) 392 blockArgTypes.push_back(v.getType().cast<ShapedType>().getElementType()); 393 394 OpBuilder::InsertionGuard guard(builder); 395 auto ®ion = *result.regions.front(); 396 Block *bodyBlock = builder.createBlock(®ion, region.end(), blockArgTypes); 397 bodyBuild(builder, result.location, bodyBlock->getArguments()); 398 } 399 400 void GenericOp::build( 401 OpBuilder &builder, OperationState &result, ValueRange inputs, 402 ValueRange outputs, ArrayRef<AffineMap> indexingMaps, 403 ArrayRef<StringRef> iteratorTypes, StringRef doc, StringRef libraryCall, 404 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild) { 405 build(builder, result, TypeRange{}, inputs, outputs, indexingMaps, 406 iteratorTypes, doc, libraryCall, bodyBuild); 407 } 408 409 void GenericOp::build( 410 OpBuilder &builder, OperationState &result, ValueRange inputs, 411 ValueRange outputs, ArrayRef<AffineMap> indexingMaps, 412 ArrayRef<StringRef> iteratorTypes, 413 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild) { 414 build(builder, result, inputs, outputs, indexingMaps, iteratorTypes, 415 /*doc=*/"", 416 /*libraryCall=*/"", bodyBuild); 417 } 418 419 void GenericOp::build( 420 OpBuilder &builder, OperationState &result, TypeRange resultTensorTypes, 421 ValueRange inputs, ValueRange outputs, ArrayRef<AffineMap> indexingMaps, 422 ArrayRef<StringRef> iteratorTypes, 423 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild) { 424 build(builder, result, resultTensorTypes, inputs, outputs, indexingMaps, 425 iteratorTypes, 426 /*doc=*/"", 427 /*libraryCall=*/"", bodyBuild); 428 } 429 void IndexedGenericOp::build( 430 OpBuilder &builder, OperationState &result, TypeRange resultTensorTypes, 431 ValueRange inputs, ValueRange outputs, ArrayRef<AffineMap> indexingMaps, 432 ArrayRef<StringRef> iteratorTypes, StringRef doc, StringRef libraryCall, 433 function_ref<void(OpBuilder &, Location, ValueRange, ValueRange)> 434 bodyBuild) { 435 build(builder, result, resultTensorTypes, inputs, outputs, 436 builder.getAffineMapArrayAttr(indexingMaps), 437 builder.getStrArrayAttr(iteratorTypes), 438 doc.empty() ? StringAttr() : builder.getStringAttr(doc), 439 libraryCall.empty() ? StringAttr() : builder.getStringAttr(libraryCall), 440 ArrayAttr()); 441 if (!bodyBuild) 442 return; 443 444 unsigned nLoops = iteratorTypes.size(); 445 SmallVector<Type, 4> blockArgTypes(nLoops, builder.getIndexType()); 446 for (ValueRange container : {inputs, outputs}) 447 for (Value v : container) 448 blockArgTypes.push_back(v.getType().cast<ShapedType>().getElementType()); 449 450 OpBuilder::InsertionGuard guard(builder); 451 auto ®ion = *result.regions.front(); 452 Block *bodyBlock = builder.createBlock(®ion, region.end(), blockArgTypes); 453 bodyBuild(builder, result.location, 454 bodyBlock->getArguments().take_front(nLoops), 455 bodyBlock->getArguments().drop_front(nLoops)); 456 } 457 458 void IndexedGenericOp::build( 459 OpBuilder &builder, OperationState &result, ValueRange inputs, 460 ValueRange outputs, ArrayRef<AffineMap> indexingMaps, 461 ArrayRef<StringRef> iteratorTypes, StringRef doc, StringRef libraryCall, 462 function_ref<void(OpBuilder &, Location, ValueRange, ValueRange)> 463 bodyBuild) { 464 build(builder, result, TypeRange{}, inputs, outputs, indexingMaps, 465 iteratorTypes, doc, libraryCall, bodyBuild); 466 } 467 468 void IndexedGenericOp::build( 469 OpBuilder &builder, OperationState &result, ValueRange inputs, 470 ValueRange outputs, ArrayRef<AffineMap> indexingMaps, 471 ArrayRef<StringRef> iteratorTypes, 472 function_ref<void(OpBuilder &, Location, ValueRange, ValueRange)> 473 bodyBuild) { 474 build(builder, result, inputs, outputs, indexingMaps, iteratorTypes, 475 /*doc=*/"", /*libraryCall=*/"", bodyBuild); 476 } 477 478 void IndexedGenericOp::build( 479 OpBuilder &builder, OperationState &result, TypeRange resultTensorTypes, 480 ValueRange inputs, ValueRange outputs, ArrayRef<AffineMap> indexingMaps, 481 ArrayRef<StringRef> iteratorTypes, 482 function_ref<void(OpBuilder &, Location, ValueRange, ValueRange)> 483 bodyBuild) { 484 build(builder, result, resultTensorTypes, inputs, outputs, indexingMaps, 485 iteratorTypes, 486 /*doc=*/"", 487 /*libraryCall=*/"", bodyBuild); 488 } 489 490 template <typename GenericOpType> 491 static void printGenericOp(OpAsmPrinter &p, GenericOpType op) { 492 p << op.getOperationName() << " "; 493 494 // Print extra attributes. 495 auto genericAttrNames = op.linalgTraitAttrNames(); 496 497 llvm::StringSet<> genericAttrNamesSet; 498 genericAttrNamesSet.insert(genericAttrNames.begin(), genericAttrNames.end()); 499 SmallVector<NamedAttribute, 8> genericAttrs; 500 for (auto attr : op->getAttrs()) 501 if (genericAttrNamesSet.count(attr.first.strref()) > 0) 502 genericAttrs.push_back(attr); 503 if (!genericAttrs.empty()) { 504 auto genericDictAttr = DictionaryAttr::get(op.getContext(), genericAttrs); 505 p << genericDictAttr; 506 } 507 508 // Printing is shared with named ops, except for the region and attributes 509 printCommonStructuredOpParts(p, op); 510 511 genericAttrNames.push_back("operand_segment_sizes"); 512 genericAttrNamesSet.insert(genericAttrNames.back()); 513 514 bool hasExtraAttrs = false; 515 for (NamedAttribute n : op->getAttrs()) { 516 if ((hasExtraAttrs = !genericAttrNamesSet.contains(n.first.strref()))) 517 break; 518 } 519 if (hasExtraAttrs) { 520 p << " attrs = "; 521 p.printOptionalAttrDict(op->getAttrs(), /*elidedAttrs=*/genericAttrNames); 522 } 523 524 // Print region. 525 if (!op.region().empty()) 526 p.printRegion(op.region()); 527 528 // Print results. 529 printNamedStructuredOpResults(p, op.result_tensors().getTypes()); 530 } 531 532 static void print(OpAsmPrinter &p, GenericOp op) { printGenericOp(p, op); } 533 534 static void print(OpAsmPrinter &p, IndexedGenericOp op) { 535 printGenericOp(p, op); 536 } 537 538 static ParseResult parseGenericOp(OpAsmParser &parser, OperationState &result) { 539 DictionaryAttr dictAttr; 540 // Parse the core linalg traits that must check into a dictAttr. 541 // The name is unimportant as we will overwrite result.attributes. 542 // The core linalg traits must contain the information necessary to pass the 543 // verifier. 544 if (parser.parseAttribute(dictAttr, "_", result.attributes)) 545 return failure(); 546 result.attributes.assign(dictAttr.getValue().begin(), 547 dictAttr.getValue().end()); 548 549 // Parsing is shared with named ops, except for the region. 550 SmallVector<Type, 1> inputTypes, outputTypes; 551 if (parseCommonStructuredOpParts(parser, result, inputTypes, outputTypes)) 552 return failure(); 553 554 // Optional attributes may be added. 555 if (succeeded(parser.parseOptionalKeyword("attrs"))) 556 if (failed(parser.parseEqual()) || 557 failed(parser.parseOptionalAttrDict(result.attributes))) 558 return failure(); 559 560 SmallVector<OpAsmParser::OperandType, 8> regionOperands; 561 std::unique_ptr<Region> region = std::make_unique<Region>(); 562 SmallVector<Type, 8> operandTypes, regionTypes; 563 if (parser.parseRegion(*region, regionOperands, regionTypes)) 564 return failure(); 565 result.addRegion(std::move(region)); 566 567 // Generic ops may specify that a subset of its outputs are tensors. Such 568 // outputs are specified in the result type. 569 // TODO: may need to move output parsing before region parsing. 570 // Need to wait for declarative assembly resolution to decide. 571 SmallVector<Type, 1> outputTensorsTypes; 572 if (parseNamedStructuredOpResults(parser, outputTensorsTypes)) 573 return failure(); 574 result.addTypes(outputTensorsTypes); 575 576 return success(); 577 } 578 579 static void getGenericEffectsImpl( 580 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>> 581 &effects, 582 ValueRange results, ValueRange inputBuffers, ValueRange outputs) { 583 for (Value value : results) { 584 effects.emplace_back(MemoryEffects::Allocate::get(), value, 585 SideEffects::DefaultResource::get()); 586 } 587 for (Value value : inputBuffers) { 588 effects.emplace_back(MemoryEffects::Read::get(), value, 589 SideEffects::DefaultResource::get()); 590 } 591 for (Value value : outputs) { 592 effects.emplace_back(MemoryEffects::Read::get(), value, 593 SideEffects::DefaultResource::get()); 594 effects.emplace_back(MemoryEffects::Write::get(), value, 595 SideEffects::DefaultResource::get()); 596 } 597 } 598 599 void GenericOp::getEffects( 600 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>> 601 &effects) { 602 getGenericEffectsImpl(effects, getOperation()->getResults(), 603 getInputBuffers(), getOutputBuffers()); 604 } 605 606 void IndexedGenericOp::getEffects( 607 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>> 608 &effects) { 609 getGenericEffectsImpl(effects, getOperation()->getResults(), 610 getInputBuffers(), getOutputBuffers()); 611 } 612 613 namespace { 614 615 template <typename GenericOpType> 616 struct AnnotationsVerifier { 617 static LogicalResult verify(GenericOpType op) { return success(); } 618 }; 619 620 template <> 621 LogicalResult AnnotationsVerifier<GenericOp>::verify(GenericOp op) { 622 ArrayAttr sparseAttr = op.sparseAttr(); 623 if (!sparseAttr) 624 return success(); 625 // Verify consistency of sparse annotations. 626 if (!op.hasTensorSemantics()) 627 return op.emitOpError("expected sparse annotations on tensors only"); 628 if (op.getNumOutputs() != 1) 629 return op.emitOpError("expected single output tensor"); 630 unsigned numTensors = op.getNumShapedOperands(); 631 if (sparseAttr.size() != numTensors) 632 return op.emitOpError("expected one sparse annotation for each tensor"); 633 for (unsigned t = 0; t < numTensors; t++) { 634 auto dimAttr = sparseAttr[t].dyn_cast_or_null<ArrayAttr>(); 635 if (!dimAttr) 636 return op.emitOpError("expected sparse annotation array for tensor ") 637 << t; 638 unsigned rank = op.getShapedType(t).getRank(); 639 if (dimAttr.size() != rank) 640 return op.emitOpError("expected sparse annotation with rank ") 641 << rank << " for tensor " << t; 642 // Per-dimension annotations for each tensor consist of only "D" or "S". 643 for (unsigned d = 0; d < rank; d++) { 644 if (isDenseDim(dimAttr[d])) { 645 continue; 646 } else if (isSparseDim(dimAttr[d])) { 647 if (t == numTensors - 1) 648 return op.emitOpError("sparse output tensors not supported (yet)"); 649 continue; 650 } 651 return op.emitOpError("expected sparse annotation at position ") 652 << d << " for tensor " << t; 653 } 654 } 655 return success(); 656 } 657 658 } // namespace 659 660 template <typename GenericOpType> 661 static LogicalResult verifyGenericOp(GenericOpType op) { 662 if (failed(AnnotationsVerifier<GenericOpType>::verify(op))) 663 return failure(); 664 665 return success(); 666 } 667 668 static LogicalResult verify(GenericOp op) { return verifyGenericOp(op); } 669 670 static LogicalResult verify(IndexedGenericOp op) { return verifyGenericOp(op); } 671 672 //===----------------------------------------------------------------------===// 673 // InitTensorOp 674 //===----------------------------------------------------------------------===// 675 void InitTensorOp::build(OpBuilder &b, OperationState &result, 676 ArrayRef<OpFoldResult> sizes, Type elementType, 677 ArrayRef<NamedAttribute> attrs) { 678 unsigned rank = sizes.size(); 679 SmallVector<Value, 4> dynamicSizes; 680 SmallVector<int64_t, 4> staticSizes; 681 for (unsigned i = 0; i < rank; ++i) { 682 // staticLow and staticHigh have full information of the padding config. 683 // This will grow staticLow and staticHigh with 1 value. If the config is 684 // dynamic (ie not a constant), dynamicLow and dynamicHigh will grow with 1 685 // value as well. 686 dispatchIndexOpFoldResult(sizes[i], dynamicSizes, staticSizes, 687 ShapedType::kDynamicSize); 688 } 689 auto resultType = RankedTensorType ::get(staticSizes, elementType); 690 build(b, result, resultType, dynamicSizes, b.getI64ArrayAttr(staticSizes)); 691 result.addAttributes(attrs); 692 } 693 694 static LogicalResult verify(InitTensorOp op) { 695 RankedTensorType resultType = op.getType(); 696 SmallVector<int64_t, 4> staticSizes = llvm::to_vector<4>(llvm::map_range( 697 op.static_sizes().cast<ArrayAttr>(), 698 [](Attribute a) -> int64_t { return a.cast<IntegerAttr>().getInt(); })); 699 700 if (failed(verifyListOfOperandsOrIntegers(op, "sizes", resultType.getRank(), 701 op.static_sizes(), op.sizes(), 702 ShapedType::isDynamic))) 703 return failure(); 704 705 if (op.static_sizes().size() != static_cast<unsigned>(resultType.getRank())) 706 return op->emitError("expected ") 707 << resultType.getRank() << " sizes values"; 708 709 Type expectedType = 710 InitTensorOp::inferResultType(staticSizes, resultType.getElementType()); 711 if (resultType != expectedType) { 712 return op.emitError("specified type ") 713 << resultType << " does not match the inferred type " 714 << expectedType; 715 } 716 return success(); 717 } 718 719 Type InitTensorOp::inferResultType(ArrayRef<int64_t> staticSizes, 720 Type elementType) { 721 return RankedTensorType::get(staticSizes, elementType); 722 } 723 724 namespace { 725 /// Change the type of the result of a `linalg.init_tensor` by making the result 726 /// type statically sized along dimension that in the original operation where 727 /// defined as dynamic, but the size was defined using a `constant` op. For 728 /// example 729 /// 730 /// %c5 = constant 5: index 731 /// %0 = linalg.init_tensor [%arg0, %c5] : tensor<?x?xf32> 732 /// 733 /// to 734 /// 735 /// %0 = linalg.init_tensor [%arg0, 5] : tensor<?x5xf32> 736 struct ReplaceStaticShapeDims : OpRewritePattern<InitTensorOp> { 737 using OpRewritePattern<InitTensorOp>::OpRewritePattern; 738 739 LogicalResult matchAndRewrite(InitTensorOp op, 740 PatternRewriter &rewriter) const override { 741 SmallVector<Value, 4> dynamicSizes; 742 SmallVector<int64_t, 4> staticSizes; 743 for (unsigned i = 0, e = op.getType().getRank(); i != e; ++i) { 744 // If the size is already static, nothing to do. 745 if (!op.isDynamicSize(i)) { 746 staticSizes.push_back(op.getStaticSize(i)); 747 continue; 748 } 749 750 // If the size is dynamic but defined using a `constant` op, get the 751 // constant value to find the static size to use. 752 unsigned operandNum = op.getIndexOfDynamicSize(i); 753 Value sizeOperand = op.getOperand(operandNum); 754 if (auto constantIndexOp = sizeOperand.getDefiningOp<ConstantIndexOp>()) { 755 staticSizes.push_back(constantIndexOp.getValue()); 756 continue; 757 } 758 759 // Fallback case. Keep the size dynamic. 760 dynamicSizes.push_back(sizeOperand); 761 staticSizes.push_back(ShapedType::kDynamicSize); 762 } 763 RankedTensorType newType = 764 RankedTensorType::get(staticSizes, op.getType().getElementType()); 765 if (newType == op.getType()) 766 return failure(); 767 auto newOp = 768 rewriter.create<InitTensorOp>(op.getLoc(), newType, dynamicSizes, 769 rewriter.getI64ArrayAttr(staticSizes)); 770 rewriter.replaceOpWithNewOp<tensor::CastOp>(op, op.getType(), newOp); 771 return success(); 772 } 773 }; 774 775 /// Canonicalize a `linalg.init_tensor` -> `dim` pattern by replacing the `dim` 776 /// with 777 /// - A constant value if the size is static along the dimension. 778 /// - The dynamic value that defines the size of the result of 779 /// `linalg.init_tensor` op. 780 struct ReplaceDimOfInitTensorOp : public OpRewritePattern<memref::DimOp> { 781 using OpRewritePattern<memref::DimOp>::OpRewritePattern; 782 783 LogicalResult matchAndRewrite(memref::DimOp dimOp, 784 PatternRewriter &rewriter) const override { 785 auto initTensorOp = dimOp.memrefOrTensor().getDefiningOp<InitTensorOp>(); 786 if (!initTensorOp) 787 return failure(); 788 auto dimIndex = dimOp.index().getDefiningOp<ConstantIndexOp>(); 789 if (!dimIndex) 790 return failure(); 791 int64_t index = dimIndex.getValue(); 792 if (!initTensorOp.isDynamicSize(index)) { 793 rewriter.replaceOpWithNewOp<ConstantIndexOp>( 794 dimOp, initTensorOp.getStaticSize(index)); 795 } else { 796 rewriter.replaceOp(dimOp, initTensorOp.getDynamicSize(index)); 797 } 798 return success(); 799 } 800 }; 801 } // namespace 802 803 namespace { 804 /// Since `init_tensor` operation creates a tensor needed only for its shape, a 805 /// subtensor of this is also needed only for its shape. The result can be 806 /// replaced by a new init_tensor operation of the same size as the subtensor 807 /// op. 808 struct FoldInitTensorWithSubTensorOp : public OpRewritePattern<SubTensorOp> { 809 using OpRewritePattern<SubTensorOp>::OpRewritePattern; 810 811 LogicalResult matchAndRewrite(SubTensorOp subtensorOp, 812 PatternRewriter &rewriter) const override { 813 if (!subtensorOp.source().getDefiningOp<linalg::InitTensorOp>()) 814 return failure(); 815 rewriter.replaceOpWithNewOp<linalg::InitTensorOp>( 816 subtensorOp, subtensorOp.sizes(), 817 llvm::to_vector<4>(llvm::map_range( 818 subtensorOp.static_sizes(), 819 [](Attribute attr) { return attr.cast<IntegerAttr>().getInt(); })), 820 subtensorOp.getSourceType().getElementType()); 821 return success(); 822 } 823 }; 824 825 struct FoldInitTensorWithTensorReshapeOp 826 : public OpRewritePattern<TensorReshapeOp> { 827 using OpRewritePattern<TensorReshapeOp>::OpRewritePattern; 828 829 LogicalResult matchAndRewrite(TensorReshapeOp reshapeOp, 830 PatternRewriter &rewriter) const override { 831 if (!reshapeOp.src().getDefiningOp<InitTensorOp>()) 832 return failure(); 833 Location loc = reshapeOp.getLoc(); 834 SmallVector<Value, 4> resultShapeValues = 835 reshapeOp.getOutputShape(rewriter, loc); 836 Value initTensor = rewriter.create<InitTensorOp>( 837 loc, resultShapeValues, reshapeOp.getResultType().getElementType()); 838 rewriter.replaceOpWithNewOp<tensor::CastOp>( 839 reshapeOp, reshapeOp.getResultType(), initTensor); 840 return success(); 841 } 842 }; 843 } // namespace 844 845 void InitTensorOp::getCanonicalizationPatterns(RewritePatternSet &results, 846 MLIRContext *context) { 847 results.add<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<memref::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<memref::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<memref::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(RewritePatternSet &results, 1549 MLIRContext *context) { 1550 results.add<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<memref::DimOp> { 1641 using OpRewritePattern<memref::DimOp>::OpRewritePattern; 1642 LogicalResult matchAndRewrite(memref::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(RewritePatternSet &results, 1664 MLIRContext *context) { 1665 results.add<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, ArrayAttr 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(), iteratorTypes); 1761 1762 // Add output types for `RankedTensorType` output arguments. 1763 for (Value output : outputs) { 1764 Type outputType = output.getType(); 1765 if (outputType.isa<RankedTensorType>()) 1766 result.addTypes(outputType); 1767 } 1768 1769 OpBuilder::InsertionGuard guard(builder); 1770 unsigned numIVs = steps.size(); 1771 SmallVector<Type, 8> argTypes(numIVs, builder.getIndexType()); 1772 Region *bodyRegion = result.addRegion(); 1773 Block *bodyBlock = builder.createBlock(bodyRegion, {}, argTypes); 1774 1775 if (bodyBuilderFn) { 1776 builder.setInsertionPointToStart(bodyBlock); 1777 bodyBuilderFn(builder, result.location, bodyBlock->getArguments()); 1778 TiledLoopOp::ensureTerminator(*bodyRegion, builder, result.location); 1779 } 1780 } 1781 1782 static void print(OpAsmPrinter &p, TiledLoopOp op) { 1783 p << op.getOperationName() << " (" << op.getBody()->getArguments() << ") = (" 1784 << op.lowerBound() << ") to (" << op.upperBound() << ") step (" << op.step() 1785 << ")"; 1786 1787 if (!op.inputs().empty()) 1788 p << " ins (" << op.inputs() << ": " << TypeRange(op.inputs()) << ")"; 1789 if (!op.outputs().empty()) 1790 p << " outs (" << op.outputs() << ":" << TypeRange(op.outputs()) << ")"; 1791 1792 if (llvm::any_of(op.iterator_types(), [](Attribute attr) { 1793 return attr.cast<StringAttr>().getValue() != 1794 getParallelIteratorTypeName(); 1795 })) { 1796 p << " iterators" << op.iterator_types() << ""; 1797 } 1798 1799 p.printRegion(op.region(), /*printEntryBlockArgs=*/false); 1800 p.printOptionalAttrDict( 1801 op->getAttrs(), /*elidedAttrs=*/{TiledLoopOp::getOperandSegmentSizeAttr(), 1802 getIteratorTypesAttrName()}); 1803 } 1804 1805 static ParseResult parseTiledLoopOp(OpAsmParser &parser, 1806 OperationState &result) { 1807 auto &builder = parser.getBuilder(); 1808 // Parse an opening `(` followed by induction variables followed by `)` 1809 SmallVector<OpAsmParser::OperandType, 4> ivs; 1810 if (parser.parseRegionArgumentList(ivs, /*requiredOperandCount=*/-1, 1811 OpAsmParser::Delimiter::Paren)) 1812 return failure(); 1813 1814 // Parse loop bounds. 1815 SmallVector<OpAsmParser::OperandType, 4> lower; 1816 if (parser.parseEqual() || 1817 parser.parseOperandList(lower, ivs.size(), 1818 OpAsmParser::Delimiter::Paren) || 1819 parser.resolveOperands(lower, builder.getIndexType(), result.operands)) 1820 return failure(); 1821 1822 SmallVector<OpAsmParser::OperandType, 4> upper; 1823 if (parser.parseKeyword("to") || 1824 parser.parseOperandList(upper, ivs.size(), 1825 OpAsmParser::Delimiter::Paren) || 1826 parser.resolveOperands(upper, builder.getIndexType(), result.operands)) 1827 return failure(); 1828 1829 // Parse step values. 1830 SmallVector<OpAsmParser::OperandType, 4> steps; 1831 if (parser.parseKeyword("step") || 1832 parser.parseOperandList(steps, ivs.size(), 1833 OpAsmParser::Delimiter::Paren) || 1834 parser.resolveOperands(steps, builder.getIndexType(), result.operands)) 1835 return failure(); 1836 1837 // Parse input tensors. 1838 SmallVector<OpAsmParser::OperandType, 4> inputs; 1839 if (succeeded(parser.parseOptionalKeyword("ins"))) { 1840 SmallVector<Type, 4> inputTypes; 1841 llvm::SMLoc inputsOperandsLoc = parser.getCurrentLocation(); 1842 1843 if (parser.parseLParen() || parser.parseOperandList(inputs) || 1844 parser.parseColonTypeList(inputTypes) || parser.parseRParen()) 1845 return failure(); 1846 1847 if (parser.resolveOperands(inputs, inputTypes, inputsOperandsLoc, 1848 result.operands)) 1849 return failure(); 1850 } 1851 1852 // Parse output tensors. 1853 SmallVector<OpAsmParser::OperandType, 4> outputs; 1854 if (succeeded(parser.parseOptionalKeyword("outs"))) { 1855 SmallVector<Type, 4> outputTypes; 1856 llvm::SMLoc outputsOperandsLoc = parser.getCurrentLocation(); 1857 1858 if (parser.parseLParen() || parser.parseOperandList(outputs) || 1859 parser.parseColonTypeList(outputTypes) || parser.parseRParen()) 1860 return failure(); 1861 1862 if (parser.resolveOperands(outputs, outputTypes, outputsOperandsLoc, 1863 result.operands)) 1864 return failure(); 1865 for (Type outputType : outputTypes) 1866 if (outputType.isa<RankedTensorType>()) 1867 result.addTypes(outputType); 1868 } 1869 1870 // Parse attributes. 1871 SmallVector<Attribute, 4> iterTypes; 1872 if (succeeded(parser.parseOptionalKeyword("iterators"))) { 1873 StringAttr iterType; 1874 1875 if (parser.parseLSquare() || parser.parseAttribute(iterType)) 1876 return failure(); 1877 iterTypes.push_back(iterType); 1878 for (int i = 1, e = ivs.size(); i < e; ++i) { 1879 if (parser.parseComma() || parser.parseAttribute(iterType)) 1880 return failure(); 1881 iterTypes.push_back(iterType); 1882 } 1883 if (parser.parseRSquare()) 1884 return failure(); 1885 } else { 1886 auto parallelIter = builder.getStringAttr(getParallelIteratorTypeName()); 1887 iterTypes = SmallVector<Attribute, 4>(ivs.size(), parallelIter); 1888 } 1889 result.addAttribute(getIteratorTypesAttrName(), 1890 builder.getArrayAttr(iterTypes)); 1891 result.addAttribute( 1892 TiledLoopOp::getOperandSegmentSizeAttr(), 1893 builder.getI32VectorAttr({static_cast<int32_t>(lower.size()), 1894 static_cast<int32_t>(upper.size()), 1895 static_cast<int32_t>(steps.size()), 1896 static_cast<int32_t>(inputs.size()), 1897 static_cast<int32_t>(outputs.size())})); 1898 1899 // Parse the body. 1900 Region *body = result.addRegion(); 1901 SmallVector<Type, 4> types(ivs.size(), builder.getIndexType()); 1902 if (parser.parseRegion(*body, ivs, types)) 1903 return failure(); 1904 1905 // Parse optional attributes. 1906 parser.parseOptionalAttrDict(result.attributes); 1907 1908 return success(); 1909 } 1910 1911 Region &TiledLoopOp::getLoopBody() { return region(); } 1912 1913 LogicalResult TiledLoopOp::moveOutOfLoop(ArrayRef<Operation *> ops) { 1914 for (auto *op : ops) 1915 op->moveBefore(*this); 1916 return success(); 1917 } 1918 1919 bool TiledLoopOp::isDefinedOutsideOfLoop(Value value) { 1920 return !region().isAncestor(value.getParentRegion()); 1921 } 1922 1923 static LogicalResult verify(TiledLoopOp op) { return success(); } 1924 1925 /////// Operations corresponding to library calls defined with Tablegen //////// 1926 1927 template <typename LinalgPoolingOp> 1928 static LogicalResult verifyStrideOrDilation(LinalgPoolingOp op, 1929 ArrayRef<Attribute> attrs, 1930 bool isStride) { 1931 auto strideOrDilation = isStride ? "stride" : "dilation"; 1932 if (attrs.size() != op.getNumWindowLoops()) 1933 return op.emitOpError("expects num ") 1934 << strideOrDilation 1935 << "s equal to number of window dimensions: " << attrs.size() 1936 << " vs " << op.getNumWindowLoops(); 1937 return success(); 1938 } 1939 1940 void ConvOp::getEffects( 1941 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>> 1942 &effects) { 1943 effects.emplace_back(MemoryEffects::Read::get(), input(), 1944 SideEffects::DefaultResource::get()); 1945 effects.emplace_back(MemoryEffects::Read::get(), filter(), 1946 SideEffects::DefaultResource::get()); 1947 effects.emplace_back(MemoryEffects::Write::get(), output(), 1948 SideEffects::DefaultResource::get()); 1949 } 1950 1951 static LogicalResult verify(ConvOp op) { 1952 auto oType = op.output().getType().cast<MemRefType>(); 1953 auto fType = op.filter().getType().cast<MemRefType>(); 1954 auto iType = op.input().getType().cast<MemRefType>(); 1955 if (oType.getElementType() != iType.getElementType() || 1956 oType.getElementType() != fType.getElementType()) 1957 return op.emitOpError("expects memref elemental types to match"); 1958 if (oType.getRank() != iType.getRank() || oType.getRank() != fType.getRank()) 1959 return op.emitOpError("expects memref ranks to match"); 1960 if (auto strides = op.strides()) { 1961 if (failed( 1962 verifyStrideOrDilation(op, strides->getValue(), /*isStride=*/true))) 1963 return failure(); 1964 } 1965 if (auto dilations = op.dilations()) { 1966 if (failed(verifyStrideOrDilation(op, dilations->getValue(), 1967 /*isStride=*/false))) 1968 return failure(); 1969 } 1970 return success(); 1971 } 1972 1973 template <typename PoolingOp> 1974 static LogicalResult verifySingleInputPoolingOp(PoolingOp op) { 1975 auto inputType = op.input().getType().template cast<MemRefType>(); 1976 auto outputType = op.output().getType().template cast<MemRefType>(); 1977 if (outputType.getElementType() != inputType.getElementType()) 1978 return op.emitOpError("expects memref elemental types to match"); 1979 1980 auto windowDimsType = op.windowDims().getType().template cast<MemRefType>(); 1981 if (outputType.getRank() != inputType.getRank() || 1982 outputType.getRank() != windowDimsType.getRank()) 1983 return op.emitOpError("expects memref ranks to match"); 1984 1985 if (auto strides = op.strides()) { 1986 if (failed( 1987 verifyStrideOrDilation(op, strides->getValue(), /*isStride=*/true))) 1988 return failure(); 1989 } 1990 if (auto dilations = op.dilations()) { 1991 if (failed(verifyStrideOrDilation(op, dilations->getValue(), 1992 /*isStride=*/false))) 1993 return failure(); 1994 } 1995 return success(); 1996 } 1997 1998 #define DEFINE_POOLING_OP_GET_EFFECTS(OP_NAME) \ 1999 void OP_NAME::getEffects( \ 2000 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>> \ 2001 &effects) { \ 2002 effects.emplace_back(MemoryEffects::Read::get(), input(), \ 2003 SideEffects::DefaultResource::get()); \ 2004 effects.emplace_back(MemoryEffects::Write::get(), output(), \ 2005 SideEffects::DefaultResource::get()); \ 2006 } 2007 2008 static LogicalResult verify(PoolingMaxOp op) { 2009 return verifySingleInputPoolingOp(op); 2010 } 2011 static LogicalResult verify(PoolingMinOp op) { 2012 return verifySingleInputPoolingOp(op); 2013 } 2014 static LogicalResult verify(PoolingSumOp op) { 2015 return verifySingleInputPoolingOp(op); 2016 } 2017 2018 DEFINE_POOLING_OP_GET_EFFECTS(PoolingMaxOp) 2019 DEFINE_POOLING_OP_GET_EFFECTS(PoolingMinOp) 2020 DEFINE_POOLING_OP_GET_EFFECTS(PoolingSumOp) 2021 2022 namespace { 2023 struct EraseDeadLinalgOp; 2024 struct FoldTensorCastOp; 2025 } // namespace 2026 2027 #include "mlir/Dialect/Linalg/IR/LinalgNamedStructuredOps.tcgen.cpp.inc" 2028 #include "mlir/Dialect/Linalg/IR/LinalgNamedStructuredOps.yamlgen.cpp.inc" 2029 2030 #define GET_OP_CLASSES 2031 #include "mlir/Dialect/Linalg/IR/LinalgOps.cpp.inc" 2032 2033 #define GET_OP_CLASSES 2034 #include "mlir/Dialect/Linalg/IR/LinalgStructuredOps.cpp.inc" 2035 2036 #define GET_OP_CLASSES 2037 #include "mlir/Dialect/Linalg/IR/LinalgSparseOps.cpp.inc" 2038 2039 /// Return the dims that are `iteratorTypeName` loops in the LinalgOp `op`. 2040 /// Assumes `op` is a LinalgOp. 2041 void mlir::linalg::getDimsOfType(Operation *op, StringRef iteratorTypeName, 2042 SmallVectorImpl<AffineExpr> &res) { 2043 if (!cast<LinalgOp>(op).iterator_types()) 2044 return; 2045 2046 unsigned dim = 0; 2047 MLIRContext *ctx = op->getContext(); 2048 for (auto tn : 2049 cast<LinalgOp>(op).iterator_types().getAsValueRange<StringAttr>()) { 2050 if (tn == iteratorTypeName) 2051 res.push_back(getAffineDimExpr(dim, ctx)); 2052 ++dim; 2053 } 2054 } 2055 2056 AffineMap mlir::linalg::extractOrIdentityMap(Optional<AffineMap> maybeMap, 2057 unsigned rank, 2058 MLIRContext *context) { 2059 if (maybeMap) 2060 return maybeMap.getValue(); 2061 if (rank == 0) 2062 return AffineMap::get(context); 2063 return AffineMap::getMultiDimIdentityMap(rank, context); 2064 } 2065 2066 SmallVector<AffineExpr, 4> 2067 mlir::linalg::makeAffineDimExprs(unsigned num, unsigned &startIdx, 2068 MLIRContext *context) { 2069 SmallVector<AffineExpr, 4> res; 2070 res.reserve(num); 2071 for (unsigned i = 0; i < num; ++i) 2072 res.push_back(getAffineDimExpr(startIdx++, context)); 2073 return res; 2074 } 2075 2076 template <typename PoolingOp> 2077 SmallVector<AffineExpr, 4> 2078 mlir::linalg::weightedPoolingInputIndex(PoolingOp op, 2079 ArrayRef<AffineExpr> outputDims, 2080 ArrayRef<AffineExpr> windowDims) { 2081 assert(outputDims.size() == windowDims.size()); 2082 SmallVector<AffineExpr, 4> res; 2083 res.reserve(outputDims.size()); 2084 for (unsigned i = 0, e = outputDims.size(); i < e; ++i) { 2085 // TODO: add a level of indirection to linalg.generic. 2086 auto expr = op.getStride(i) * outputDims[i] + 2087 op.getDilation(i) * windowDims[i] - op.getLowPad(i); 2088 res.push_back(expr); 2089 } 2090 return res; 2091 } 2092 2093 #define INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(OP_TYPE) \ 2094 template SmallVector<AffineExpr, 4> \ 2095 mlir::linalg::weightedPoolingInputIndex<OP_TYPE>( \ 2096 OP_TYPE op, ArrayRef<AffineExpr> outputDims, \ 2097 ArrayRef<AffineExpr> windowDims); 2098 2099 INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(ConvOp) 2100 INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(PoolingMaxOp) 2101 INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(PoolingMinOp) 2102 INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(PoolingSumOp) 2103 2104 SmallVector<AffineExpr, 4> mlir::linalg::concat(ArrayRef<AffineExpr> a, 2105 ArrayRef<AffineExpr> b) { 2106 auto rangeA = llvm::make_range(a.begin(), a.end()); 2107 auto rangeB = llvm::make_range(b.begin(), b.end()); 2108 auto concatRanges = llvm::concat<const AffineExpr>(rangeA, rangeB); 2109 return llvm::to_vector<4>(concatRanges); 2110 } 2111 2112 static void appendMangledType(llvm::raw_string_ostream &ss, Type t) { 2113 if (auto memref = t.dyn_cast<MemRefType>()) { 2114 ss << "view"; 2115 for (auto size : memref.getShape()) 2116 if (size < 0) 2117 ss << "sx"; 2118 else 2119 ss << size << "x"; 2120 appendMangledType(ss, memref.getElementType()); 2121 } else if (auto vec = t.dyn_cast<VectorType>()) { 2122 ss << "vector"; 2123 llvm::interleave( 2124 vec.getShape(), [&](int64_t i) { ss << i; }, [&]() { ss << "x"; }); 2125 appendMangledType(ss, vec.getElementType()); 2126 } else if (t.isSignlessIntOrIndexOrFloat()) { 2127 ss << t; 2128 } else { 2129 llvm_unreachable("Invalid type for linalg library name mangling"); 2130 } 2131 } 2132 2133 std::string mlir::linalg::generateLibraryCallName(Operation *op) { 2134 assert(isa<LinalgOp>(op)); 2135 std::string name(op->getName().getStringRef().str()); 2136 name.reserve(128); 2137 std::replace(name.begin(), name.end(), '.', '_'); 2138 llvm::raw_string_ostream ss(name); 2139 ss << "_"; 2140 auto types = op->getOperandTypes(); 2141 llvm::interleave( 2142 types.begin(), types.end(), [&](Type t) { appendMangledType(ss, t); }, 2143 [&]() { ss << "_"; }); 2144 return ss.str(); 2145 } 2146 2147 // TODO: Consider making all this boilerplate easy to autogenerate 2148 // with Tablegen. This seems a desirable property in the context of 2149 // OpInterfaces where a Linalg "named" op **isa** LinalgOp. 2150 OpFoldResult ReshapeOp::fold(ArrayRef<Attribute> operands) { 2151 if (succeeded(foldMemRefCast(*this))) 2152 return getResult(); 2153 return foldReshapeOp(*this, operands); 2154 } 2155 OpFoldResult TensorReshapeOp::fold(ArrayRef<Attribute> operands) { 2156 return foldReshapeOp(*this, operands); 2157 } 2158 2159 //===----------------------------------------------------------------------===// 2160 // Support for named Linalg ops defined in ods-gen. 2161 //===----------------------------------------------------------------------===// 2162 2163 /// Generic entry point to create the block for the region of a LinalgOp. 2164 /// This is used by both named structured ops created by ods-gen and by manually 2165 /// defined C++ ops. 2166 /// This is used by both builders and parsers. 2167 /// This function creates the block in the region with arguments corresponding 2168 /// to the elemental types of `inputTypes` and `outputTypes`, which are asserted 2169 /// to be ShapedType. 2170 template <typename NamedStructuredOpType> 2171 static void 2172 fillStructuredOpRegion(OpBuilder &opBuilder, Region ®ion, 2173 TypeRange inputTypes, TypeRange outputTypes, 2174 ValueRange captures, 2175 std::function<void(unsigned, unsigned)> errorHandler) { 2176 assert(llvm::all_of(inputTypes, [](Type t) { return t.isa<ShapedType>(); })); 2177 assert(llvm::all_of(outputTypes, [](Type t) { return t.isa<ShapedType>(); })); 2178 2179 // TODO: atm all operands go through getElementTypeOrSelf, 2180 // reconsider when we have evidence we need to. 2181 SmallVector<Type, 8> argTypes; 2182 for (auto containers : {inputTypes, outputTypes}) 2183 for (auto t : containers) 2184 argTypes.push_back(getElementTypeOrSelf(t)); 2185 2186 // RAII. 2187 OpBuilder::InsertionGuard guard(opBuilder); 2188 Block *body = opBuilder.createBlock(®ion, /*insertPt=*/{}, argTypes); 2189 unsigned actual = body->getNumArguments(); 2190 unsigned expected = NamedStructuredOpType::getNumRegionArgs(); 2191 if (expected != actual) { 2192 if (errorHandler) 2193 errorHandler(expected, actual); 2194 return; 2195 } 2196 2197 opBuilder.setInsertionPointToStart(body); 2198 mlir::edsc::ScopedContext scope(opBuilder, opBuilder.getUnknownLoc()); 2199 NamedStructuredOpType::regionBuilder(*body, captures); 2200 2201 // indexing_maps is an auto-generated method. 2202 2203 // iterator_types is an auto-generated method. 2204 } 2205 2206 /// Generic entry point to create both the region and the block of a LinalgOp. 2207 template <typename NamedStructuredOpType> 2208 void createAndFillStructuredOpRegion(OpBuilder &opBuilder, 2209 OperationState &result, 2210 TypeRange inputTypes, 2211 TypeRange outputTypes, 2212 ValueRange captures) { 2213 Region ®ion = *result.addRegion(); 2214 fillStructuredOpRegion<NamedStructuredOpType>( 2215 opBuilder, region, inputTypes, outputTypes, captures, 2216 [&](unsigned expected, unsigned actual) { 2217 assert(expected != actual && "incorrect number of arguments"); 2218 }); 2219 } 2220 2221 /// Common parsing used for both named structured ops created by ods-gen and by 2222 /// manually defined C++ ops. Does not handle regions. 2223 static ParseResult 2224 parseCommonStructuredOpParts(OpAsmParser &parser, OperationState &result, 2225 SmallVectorImpl<Type> &inputTypes, 2226 SmallVectorImpl<Type> &outputTypes) { 2227 llvm::SMLoc inputsOperandsLoc, outputsOperandsLoc; 2228 SmallVector<OpAsmParser::OperandType, 4> inputsOperands, outputsOperands; 2229 2230 parser.parseOptionalAttrDict(result.attributes); 2231 2232 if (succeeded(parser.parseOptionalKeyword("ins"))) { 2233 if (parser.parseLParen()) 2234 return failure(); 2235 2236 inputsOperandsLoc = parser.getCurrentLocation(); 2237 if (parser.parseOperandList(inputsOperands) || 2238 parser.parseColonTypeList(inputTypes) || parser.parseRParen()) 2239 return failure(); 2240 } 2241 2242 if (succeeded(parser.parseOptionalKeyword("outs"))) { 2243 outputsOperandsLoc = parser.getCurrentLocation(); 2244 if (parser.parseLParen() || parser.parseOperandList(outputsOperands) || 2245 parser.parseColonTypeList(outputTypes) || parser.parseRParen()) 2246 return failure(); 2247 } 2248 2249 if (parser.resolveOperands(inputsOperands, inputTypes, inputsOperandsLoc, 2250 result.operands) || 2251 parser.resolveOperands(outputsOperands, outputTypes, outputsOperandsLoc, 2252 result.operands)) 2253 return failure(); 2254 2255 result.addAttribute("operand_segment_sizes", 2256 parser.getBuilder().getI32VectorAttr( 2257 {static_cast<int32_t>(inputsOperands.size()), 2258 static_cast<int32_t>(outputsOperands.size())})); 2259 return success(); 2260 } 2261 2262 template <typename NamedStructuredOpType> 2263 static void printCommonStructuredOpParts(OpAsmPrinter &p, 2264 NamedStructuredOpType op) { 2265 if (!op.inputs().empty()) 2266 p << " ins(" << op.inputs() << " : " << op.inputs().getTypes() << ")"; 2267 if (!op.outputs().empty()) 2268 p << " outs(" << op.outputs() << " : " << op.outputs().getTypes() << ")"; 2269 } 2270 2271 //===----------------------------------------------------------------------===// 2272 // Specific parsing and printing for named structured ops created by ods-gen. 2273 //===----------------------------------------------------------------------===// 2274 2275 template <typename NamedStructuredOpType> 2276 static ParseResult 2277 parseNamedStructuredOpRegion(OpAsmParser &parser, Region ®ion, 2278 TypeRange inputTypes, TypeRange outputTypes, 2279 ArrayRef<OpAsmParser::OperandType> captures) { 2280 ParseResult res = success(); 2281 OpBuilder opBuilder(parser.getBuilder().getContext()); 2282 // Resolve `captures` into `capturedValues` at parse time so we can build the 2283 // region with captures. 2284 SmallVector<Value> capturedValues; 2285 fillStructuredOpRegion<NamedStructuredOpType>( 2286 opBuilder, region, inputTypes, outputTypes, capturedValues, 2287 [&](unsigned expected, unsigned actual) { 2288 res = parser.emitError( 2289 parser.getCurrentLocation(), 2290 llvm::formatv("[parseNamedStructuredOpRegion] ods-gen generated " 2291 "region expects {0} args, got {1}", 2292 expected, actual)); 2293 region.front().dump(); 2294 }); 2295 return res; 2296 } 2297 2298 static ParseResult 2299 parseNamedStructuredOpResults(OpAsmParser &parser, 2300 SmallVectorImpl<Type> &resultTypes) { 2301 if (succeeded(parser.parseOptionalArrow())) 2302 if (parser.parseTypeList(resultTypes)) 2303 return failure(); 2304 return success(); 2305 } 2306 2307 template <typename NamedStructuredOpType> 2308 static ParseResult 2309 parseNamedStructuredOp(OpAsmParser &parser, OperationState &result, 2310 ArrayRef<OpAsmParser::OperandType> captures) { 2311 // TODO: Enable when ods-gen supports captures. 2312 assert(captures.empty() && "unexpected captures for named structured ops"); 2313 SmallVector<Type, 1> inputTypes, outputTypes; 2314 if (parseCommonStructuredOpParts(parser, result, inputTypes, outputTypes)) 2315 return failure(); 2316 2317 // TODO: consider merging results parsing into region parsing. 2318 // Need to wait for declarative assembly resolution to decide. 2319 SmallVector<Type, 1> outputTensorsTypes; 2320 if (parseNamedStructuredOpResults(parser, outputTensorsTypes)) 2321 return failure(); 2322 result.addTypes(outputTensorsTypes); 2323 2324 std::unique_ptr<Region> region = std::make_unique<Region>(); 2325 if (parseNamedStructuredOpRegion<NamedStructuredOpType>( 2326 parser, *region, inputTypes, outputTypes, captures)) 2327 return failure(); 2328 result.addRegion(std::move(region)); 2329 2330 return success(); 2331 } 2332 2333 static void printNamedStructuredOpResults(OpAsmPrinter &p, 2334 TypeRange resultTypes) { 2335 if (resultTypes.empty()) 2336 return; 2337 p.printOptionalArrowTypeList(resultTypes); 2338 } 2339 2340 template <typename NamedStructuredOpType> 2341 static void printNamedStructuredOp(OpAsmPrinter &p, NamedStructuredOpType op) { 2342 p << op.getOperationName(); 2343 p.printOptionalAttrDict( 2344 op->getAttrs(), 2345 /*elidedAttrs=*/{"operand_segment_sizes", 2346 // See generated code in mlir-linalg-yaml-gen.cpp 2347 "linalg.memoized_indexing_maps"}); 2348 2349 // Printing is shared with generic ops, except for the region and 2350 // attributes. 2351 printCommonStructuredOpParts(p, op); 2352 2353 // Results printing. 2354 printNamedStructuredOpResults(p, op.result_tensors().getTypes()); 2355 2356 // Region is elided. 2357 } 2358 2359 template <typename NamedStructuredOpType> 2360 static LogicalResult verifyNamedStructuredOp(NamedStructuredOpType op) { 2361 return verifyGenericOp<NamedStructuredOpType>(op); 2362 } 2363 2364 //===----------------------------------------------------------------------===// 2365 // Canonicalizers and Folders. 2366 //===----------------------------------------------------------------------===// 2367 2368 namespace { 2369 struct EraseDeadLinalgOp : public RewritePattern { 2370 EraseDeadLinalgOp(PatternBenefit benefit = 1) 2371 : RewritePattern(benefit, MatchAnyOpTypeTag()) {} 2372 2373 LogicalResult matchAndRewrite(Operation *op, 2374 PatternRewriter &rewriter) const override { 2375 auto linalgOp = dyn_cast<LinalgOp>(op); 2376 if (!linalgOp) 2377 return failure(); 2378 for (Value v : linalgOp.getShapedOperands()) { 2379 // Linalg "inputs" may be either tensor or memref type. 2380 // tensor<0xelt_type> is a convention that may not always mean 2381 // "0 iterations". Only erase in cases we see memref<...x0x...>. 2382 auto mt = v.getType().dyn_cast<MemRefType>(); 2383 if (!mt) 2384 continue; 2385 if (llvm::is_contained(mt.getShape(), 0)) { 2386 rewriter.eraseOp(linalgOp); 2387 return success(); 2388 } 2389 } 2390 return failure(); 2391 } 2392 }; 2393 2394 struct FoldTensorCastOp : public RewritePattern { 2395 FoldTensorCastOp(PatternBenefit benefit = 1) 2396 : RewritePattern(benefit, MatchAnyOpTypeTag()) {} 2397 2398 LogicalResult matchAndRewrite(Operation *op, 2399 PatternRewriter &rewriter) const override { 2400 auto linalgOp = dyn_cast<LinalgOp>(op); 2401 if (!linalgOp) 2402 return failure(); 2403 2404 // If no operand comes from a tensor::CastOp and can be folded then fail. 2405 bool hasTensorCastOperand = 2406 llvm::any_of(linalgOp.getShapedOperands(), [&](Value v) { 2407 if (v.isa<BlockArgument>()) 2408 return false; 2409 auto castOp = v.getDefiningOp<tensor::CastOp>(); 2410 return castOp && canFoldIntoConsumerOp(castOp); 2411 }); 2412 if (!hasTensorCastOperand) 2413 return failure(); 2414 2415 SmallVector<Type, 4> newResultTypes; 2416 newResultTypes.reserve(op->getNumResults()); 2417 SmallVector<Value, 4> newOperands; 2418 newOperands.reserve(op->getNumOperands()); 2419 // Inputs may fold. 2420 for (Value v : linalgOp.getInputs()) { 2421 auto tensorCastOp = v.getDefiningOp<tensor::CastOp>(); 2422 newOperands.push_back( 2423 canFoldIntoConsumerOp(tensorCastOp) ? tensorCastOp.source() : v); 2424 } 2425 // Init tensors may fold, in which case the resultType must also change. 2426 for (Value v : linalgOp.getOutputs()) { 2427 auto tensorCastOp = v.getDefiningOp<tensor::CastOp>(); 2428 bool fold = canFoldIntoConsumerOp(tensorCastOp); 2429 newOperands.push_back(fold ? tensorCastOp.getOperand() : v); 2430 newResultTypes.push_back(newOperands.back().getType()); 2431 } 2432 auto extraOperands = linalgOp.getAssumedNonShapedOperands(); 2433 newOperands.append(extraOperands.begin(), extraOperands.end()); 2434 // Clone op. 2435 Operation *newOp = 2436 linalgOp.clone(rewriter, op->getLoc(), newResultTypes, newOperands); 2437 SmallVector<Value, 4> replacements; 2438 replacements.reserve(newOp->getNumResults()); 2439 for (auto result : llvm::zip(op->getResults(), newOp->getResults())) { 2440 Value oldResult = std::get<0>(result); 2441 Value newResult = std::get<1>(result); 2442 if (newResult.getType() != oldResult.getType()) { 2443 replacements.push_back(rewriter.create<tensor::CastOp>( 2444 op->getLoc(), oldResult.getType(), newResult)); 2445 } else { 2446 replacements.push_back(newResult); 2447 } 2448 } 2449 rewriter.replaceOp(op, replacements); 2450 2451 return success(); 2452 } 2453 }; 2454 2455 /// Replaces memref.dim operations that use the result of a LinalgOp (on 2456 /// tensors) with memref.dim operations that use one of the arguments. For 2457 /// example, 2458 /// 2459 /// %0 = linalg.matmul ins(%arg0, %arg1, ...) 2460 /// %1 = memref.dim %0, %c0 2461 /// 2462 /// with 2463 /// 2464 /// %1 = memref.dim %arg0, %c0 2465 /// 2466 /// where possible. With this the result of the `linalg.matmul` is not used in 2467 /// dim operations. If the value produced is replaced with another value (say by 2468 /// tiling `linalg.matmul`) will make the `linalg.matmul` truly dead instead of 2469 /// used in a dim op that would prevent the DCE of this op. 2470 struct ReplaceDimOfLinalgOpResult : public OpRewritePattern<memref::DimOp> { 2471 using OpRewritePattern<memref::DimOp>::OpRewritePattern; 2472 2473 LogicalResult matchAndRewrite(memref::DimOp dimOp, 2474 PatternRewriter &rewriter) const override { 2475 Value dimValue = dimOp.memrefOrTensor(); 2476 Optional<int64_t> dimIndex = dimOp.getConstantIndex(); 2477 if (!dimIndex) 2478 return failure(); 2479 auto linalgOp = dimValue.getDefiningOp<LinalgOp>(); 2480 if (!linalgOp) 2481 return failure(); 2482 2483 unsigned resultIndex = dimValue.cast<OpResult>().getResultNumber(); 2484 Optional<Value> operandDimValue = linalgOp.inferResultDimFromInputShapes( 2485 rewriter, dimOp.getLoc(), resultIndex, 2486 static_cast<unsigned>(*dimIndex)); 2487 if (!operandDimValue) { 2488 // Its always possible to replace using the corresponding `outs` 2489 // parameter. 2490 operandDimValue = rewriter.create<memref::DimOp>( 2491 dimOp.getLoc(), linalgOp.getOutput(resultIndex), *dimIndex); 2492 } 2493 rewriter.replaceOp(dimOp, *operandDimValue); 2494 return success(); 2495 } 2496 }; 2497 2498 } // namespace 2499 2500 namespace { 2501 // Deduplicate redundant args of a linalg op. 2502 // An arg is redundant if it has the same Value and indexing map as another. 2503 struct DeduplicateInputs : public RewritePattern { 2504 DeduplicateInputs(PatternBenefit benefit = 1) 2505 : RewritePattern(benefit, MatchAnyOpTypeTag()) {} 2506 2507 LogicalResult matchAndRewrite(Operation *op, 2508 PatternRewriter &rewriter) const override { 2509 // This pattern reduces the number of arguments of an op, which breaks 2510 // the invariants of semantically charged named ops. 2511 if (!isa<GenericOp, IndexedGenericOp>(op)) 2512 return failure(); 2513 auto linalgOp = cast<LinalgOp>(op); 2514 2515 // Associate each input to an equivalent "canonical" input that has the same 2516 // Value and indexing map. 2517 // 2518 // In the non-duplicate case, input `i` will have canonical input `i`. But 2519 // in the case of duplicated inputs, the canonical input could be some other 2520 // input `< i`. That is, a later input will have some earlier input as its 2521 // canonical input. 2522 llvm::SmallDenseMap<std::pair<Value, AffineMap>, int> canonicalInput; 2523 // For later remapping tasks like deduplicating payload block arguments, 2524 // having a simple "inputIndex -> canonicalInputIndex" integer mapping is 2525 // convenient. 2526 SmallVector<int, 6> canonicalInputIndices; 2527 for (int i = 0, e = linalgOp.getNumInputs(); i != e; i++) { 2528 Value input = linalgOp.getInput(i); 2529 AffineMap indexingMap = linalgOp.getInputIndexingMap(i); 2530 // STL-like maps have a convenient behavior for our use case here. In the 2531 // case of duplicate keys, the insertion is rejected, and the returned 2532 // iterator gives access to the value already in the map. 2533 auto pair = canonicalInput.insert({{input, indexingMap}, i}); 2534 canonicalInputIndices.push_back(pair.first->second); 2535 } 2536 2537 // If there are no duplicate args, then bail out. 2538 if (canonicalInput.size() == linalgOp.getNumInputs()) 2539 return failure(); 2540 2541 // The operands for the newly canonicalized op. 2542 SmallVector<Value, 6> newOperands; 2543 for (auto v : llvm::enumerate(linalgOp.getInputs())) 2544 if (canonicalInputIndices[v.index()] == static_cast<int>(v.index())) 2545 newOperands.push_back(v.value()); 2546 llvm::append_range(newOperands, linalgOp.getOutputs()); 2547 llvm::append_range(newOperands, linalgOp.getAssumedNonShapedOperands()); 2548 2549 // Clone the old op with new operands. 2550 Operation *newOp = linalgOp.clone(rewriter, op->getLoc(), 2551 op->getResultTypes(), newOperands); 2552 auto newLinalgOp = cast<LinalgOp>(newOp); 2553 2554 // Repair the indexing maps by filtering out the ones that have been 2555 // eliminated. 2556 SmallVector<AffineMap, 6> newIndexingMaps; 2557 for (int i = 0, e = newLinalgOp.getNumInputs(); i != e; i++) 2558 if (canonicalInputIndices[i] == i) 2559 newIndexingMaps.push_back(newLinalgOp.getIndexingMap(i)); 2560 for (int i = 0, e = newLinalgOp.getNumOutputs(); i != e; i++) 2561 newIndexingMaps.push_back(newLinalgOp.getOutputIndexingMap(i)); 2562 newOp->setAttr("indexing_maps", 2563 rewriter.getAffineMapArrayAttr(newIndexingMaps)); 2564 2565 // Set the number of inputs to the new value. The `clone` call above kept 2566 // the value from the original op. 2567 newLinalgOp.setNumInputs(canonicalInput.size()); 2568 2569 // linalg.indexed_generic payloads have additional arguments prepended to 2570 // the block arg list. 2571 int bbArgBaseOffset = newLinalgOp.getNumPayloadInductionVariables(); 2572 2573 // Repair the payload entry block by RAUW'ing redundant arguments and 2574 // erasing them. 2575 Block &payload = newOp->getRegion(0).front(); 2576 for (int i = 0, e = linalgOp.getNumInputs(); i < e; i++) { 2577 // Iterate in reverse, so that we erase later args first, preventing the 2578 // argument list from shifting unexpectedly and invalidating all our 2579 // indices. 2580 int reversed = e - i - 1; 2581 int canonicalIndex = canonicalInputIndices[reversed]; 2582 if (canonicalInputIndices[reversed] == reversed) 2583 continue; 2584 payload.getArgument(bbArgBaseOffset + reversed) 2585 .replaceAllUsesWith( 2586 payload.getArgument(bbArgBaseOffset + canonicalIndex)); 2587 payload.eraseArgument(bbArgBaseOffset + reversed); 2588 } 2589 2590 rewriter.replaceOp(op, newOp->getResults()); 2591 return success(); 2592 } 2593 }; 2594 2595 /// Remove generic/indexed_generic operations (on tensors) that are just copying 2596 /// the values from inputs to the results. Requirements are 2597 /// 1) All iterator types are parallel 2598 /// 2) The body contains just a yield operation with the yielded values being 2599 /// the arguments corresponding to the operands. 2600 struct RemoveIdentityLinalgOps : public RewritePattern { 2601 RemoveIdentityLinalgOps(PatternBenefit benefit = 1) 2602 : RewritePattern(benefit, MatchAnyOpTypeTag()) {} 2603 2604 LogicalResult matchAndRewrite(Operation *op, 2605 PatternRewriter &rewriter) const override { 2606 if (auto copyOp = dyn_cast<CopyOp>(op)) { 2607 assert(copyOp.hasBufferSemantics()); 2608 if (copyOp.input() == copyOp.output() && 2609 copyOp.inputPermutation() == copyOp.outputPermutation()) { 2610 rewriter.eraseOp(op); 2611 return success(); 2612 } 2613 } 2614 2615 if (!isa<GenericOp, IndexedGenericOp>(op)) 2616 return failure(); 2617 LinalgOp genericOp = cast<LinalgOp>(op); 2618 if (!genericOp.hasTensorSemantics()) 2619 return failure(); 2620 // Check all indexing maps are identity. 2621 if (llvm::any_of(genericOp.getIndexingMaps(), 2622 [](AffineMap map) { return !map.isIdentity(); })) 2623 return failure(); 2624 2625 // Check that the body of the linalg operation is just a linalg.yield 2626 // operation. 2627 Block &body = op->getRegion(0).front(); 2628 if (!llvm::hasSingleElement(body)) 2629 return failure(); 2630 auto yieldOp = dyn_cast<linalg::YieldOp>(body.getTerminator()); 2631 if (!yieldOp) 2632 return failure(); 2633 2634 // Get the argument number of the returned values. That is the operand 2635 // number to use for replacing uses of this operation. 2636 unsigned numIndexArgs = genericOp.getNumPayloadInductionVariables(); 2637 SmallVector<Value, 4> returnedArgs; 2638 for (Value yieldVal : yieldOp.values()) { 2639 auto yieldArg = yieldVal.dyn_cast<BlockArgument>(); 2640 if (!yieldArg || yieldArg.getOwner() != &body) 2641 return failure(); 2642 unsigned argumentNumber = yieldArg.getArgNumber(); 2643 if (argumentNumber < numIndexArgs) 2644 return failure(); 2645 returnedArgs.push_back(op->getOperand(argumentNumber - numIndexArgs)); 2646 } 2647 if (returnedArgs.size() != genericOp.getOperation()->getNumResults()) 2648 return failure(); 2649 rewriter.replaceOp(genericOp, returnedArgs); 2650 return success(); 2651 } 2652 }; 2653 } // namespace 2654 2655 #define CANONICALIZERS_AND_FOLDERS(XXX) \ 2656 void XXX::getCanonicalizationPatterns(RewritePatternSet &results, \ 2657 MLIRContext *context) { \ 2658 results.add<DeduplicateInputs, EraseDeadLinalgOp, FoldTensorCastOp, \ 2659 RemoveIdentityLinalgOps>(); \ 2660 results.add<ReplaceDimOfLinalgOpResult>(context); \ 2661 } \ 2662 \ 2663 LogicalResult XXX::fold(ArrayRef<Attribute>, \ 2664 SmallVectorImpl<OpFoldResult> &) { \ 2665 return foldMemRefCast(*this); \ 2666 } 2667 2668 CANONICALIZERS_AND_FOLDERS(ConvOp) 2669 CANONICALIZERS_AND_FOLDERS(PoolingMaxOp) 2670 CANONICALIZERS_AND_FOLDERS(PoolingMinOp) 2671 CANONICALIZERS_AND_FOLDERS(PoolingSumOp) 2672 CANONICALIZERS_AND_FOLDERS(CopyOp) 2673 CANONICALIZERS_AND_FOLDERS(FillOp) 2674 CANONICALIZERS_AND_FOLDERS(GenericOp) 2675 CANONICALIZERS_AND_FOLDERS(IndexedGenericOp) 2676 2677 // All named ops canonicalizers and folders are auto-generated in the 2678 // .cpp.inc. 2679