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