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