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() 451 : builder.getStringAttr(libraryCall)); 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() 506 : builder.getStringAttr(libraryCall)); 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 template <typename GenericOpType> 680 static LogicalResult verifyGenericOp(GenericOpType op) { 681 return success(); 682 } 683 684 static LogicalResult verify(GenericOp op) { return verifyGenericOp(op); } 685 686 static LogicalResult verify(IndexedGenericOp op) { return verifyGenericOp(op); } 687 688 namespace { 689 690 /// Replace indexed_generic ops by generic ops that access the iteration indices 691 /// using index operation calls. 692 struct ConvertIndexedToGenericOp : OpRewritePattern<IndexedGenericOp> { 693 using OpRewritePattern<IndexedGenericOp>::OpRewritePattern; 694 LogicalResult matchAndRewrite(IndexedGenericOp indexedOp, 695 PatternRewriter &rewriter) const override { 696 // Replace all uses of the index block arguments. 697 BlockAndValueMapping bvm; 698 if (Block *body = indexedOp.getBody()) { 699 rewriter.setInsertionPointToStart(body); 700 for (const auto &en : llvm::enumerate( 701 body->getArguments().take_front(indexedOp.getNumLoops()))) { 702 Value index = rewriter.create<IndexOp>(indexedOp.getLoc(), en.index()); 703 bvm.map(en.value(), index); 704 } 705 } 706 707 // Create a generic replacement operation and clone the body. 708 rewriter.setInsertionPointAfter(indexedOp); 709 SmallVector<StringRef> iterators = llvm::to_vector<4>( 710 indexedOp.iterator_types().getAsValueRange<StringAttr>()); 711 GenericOp genericOp = rewriter.create<GenericOp>( 712 indexedOp.getLoc(), indexedOp->getResultTypes(), indexedOp.getInputs(), 713 indexedOp.getOutputs(), indexedOp.getIndexingMaps(), iterators); 714 Region &genericRegion = genericOp.region(); 715 Region &indexedRegion = indexedOp.region(); 716 rewriter.cloneRegionBefore(indexedRegion, genericRegion, 717 genericRegion.begin(), bvm); 718 719 rewriter.replaceOp(indexedOp, genericOp->getResults()); 720 return success(); 721 } 722 }; 723 } // namespace 724 725 void IndexedGenericOp::getCanonicalizationPatterns(RewritePatternSet &results, 726 MLIRContext *context) { 727 results.add<ConvertIndexedToGenericOp>(context); 728 } 729 730 //===----------------------------------------------------------------------===// 731 // InitTensorOp 732 //===----------------------------------------------------------------------===// 733 void InitTensorOp::build(OpBuilder &b, OperationState &result, 734 ArrayRef<OpFoldResult> sizes, Type elementType, 735 ArrayRef<NamedAttribute> attrs) { 736 unsigned rank = sizes.size(); 737 SmallVector<Value, 4> dynamicSizes; 738 SmallVector<int64_t, 4> staticSizes; 739 for (unsigned i = 0; i < rank; ++i) { 740 dispatchIndexOpFoldResult(sizes[i], dynamicSizes, staticSizes, 741 ShapedType::kDynamicSize); 742 } 743 auto resultType = RankedTensorType ::get(staticSizes, elementType); 744 build(b, result, resultType, dynamicSizes, b.getI64ArrayAttr(staticSizes)); 745 result.addAttributes(attrs); 746 } 747 748 static LogicalResult verify(InitTensorOp op) { 749 RankedTensorType resultType = op.getType(); 750 SmallVector<int64_t, 4> staticSizes = llvm::to_vector<4>(llvm::map_range( 751 op.static_sizes().cast<ArrayAttr>(), 752 [](Attribute a) -> int64_t { return a.cast<IntegerAttr>().getInt(); })); 753 754 if (failed(verifyListOfOperandsOrIntegers(op, "sizes", resultType.getRank(), 755 op.static_sizes(), op.sizes(), 756 ShapedType::isDynamic))) 757 return failure(); 758 759 if (op.static_sizes().size() != static_cast<unsigned>(resultType.getRank())) 760 return op->emitError("expected ") 761 << resultType.getRank() << " sizes values"; 762 763 Type expectedType = 764 InitTensorOp::inferResultType(staticSizes, resultType.getElementType()); 765 if (resultType != expectedType) { 766 return op.emitError("specified type ") 767 << resultType << " does not match the inferred type " 768 << expectedType; 769 } 770 return success(); 771 } 772 773 Type InitTensorOp::inferResultType(ArrayRef<int64_t> staticSizes, 774 Type elementType) { 775 return RankedTensorType::get(staticSizes, elementType); 776 } 777 778 namespace { 779 /// Change the type of the result of a `linalg.init_tensor` by making the result 780 /// type statically sized along dimension that in the original operation where 781 /// defined as dynamic, but the size was defined using a `constant` op. For 782 /// example 783 /// 784 /// %c5 = constant 5: index 785 /// %0 = linalg.init_tensor [%arg0, %c5] : tensor<?x?xf32> 786 /// 787 /// to 788 /// 789 /// %0 = linalg.init_tensor [%arg0, 5] : tensor<?x5xf32> 790 struct ReplaceStaticShapeDims : OpRewritePattern<InitTensorOp> { 791 using OpRewritePattern<InitTensorOp>::OpRewritePattern; 792 793 LogicalResult matchAndRewrite(InitTensorOp op, 794 PatternRewriter &rewriter) const override { 795 SmallVector<Value, 4> dynamicSizes; 796 SmallVector<int64_t, 4> staticSizes; 797 for (unsigned i = 0, e = op.getType().getRank(); i != e; ++i) { 798 // If the size is already static, nothing to do. 799 if (!op.isDynamicSize(i)) { 800 staticSizes.push_back(op.getStaticSize(i)); 801 continue; 802 } 803 804 // If the size is dynamic but defined using a `constant` op, get the 805 // constant value to find the static size to use. 806 unsigned operandNum = op.getIndexOfDynamicSize(i); 807 Value sizeOperand = op.getOperand(operandNum); 808 if (auto constantIndexOp = sizeOperand.getDefiningOp<ConstantIndexOp>()) { 809 staticSizes.push_back(constantIndexOp.getValue()); 810 continue; 811 } 812 813 // Fallback case. Keep the size dynamic. 814 dynamicSizes.push_back(sizeOperand); 815 staticSizes.push_back(ShapedType::kDynamicSize); 816 } 817 RankedTensorType newType = 818 RankedTensorType::get(staticSizes, op.getType().getElementType()); 819 if (newType == op.getType()) 820 return failure(); 821 auto newOp = 822 rewriter.create<InitTensorOp>(op.getLoc(), newType, dynamicSizes, 823 rewriter.getI64ArrayAttr(staticSizes)); 824 rewriter.replaceOpWithNewOp<tensor::CastOp>(op, op.getType(), newOp); 825 return success(); 826 } 827 }; 828 } // namespace 829 830 namespace { 831 /// Since `init_tensor` operation creates a tensor needed only for its shape, a 832 /// subtensor of this is also needed only for its shape. The result can be 833 /// replaced by a new init_tensor operation of the same size as the subtensor 834 /// op. 835 struct FoldInitTensorWithSubTensorOp : public OpRewritePattern<SubTensorOp> { 836 using OpRewritePattern<SubTensorOp>::OpRewritePattern; 837 838 LogicalResult matchAndRewrite(SubTensorOp subtensorOp, 839 PatternRewriter &rewriter) const override { 840 if (!subtensorOp.source().getDefiningOp<linalg::InitTensorOp>()) 841 return failure(); 842 rewriter.replaceOpWithNewOp<linalg::InitTensorOp>( 843 subtensorOp, subtensorOp.sizes(), 844 llvm::to_vector<4>(llvm::map_range( 845 subtensorOp.static_sizes(), 846 [](Attribute attr) { return attr.cast<IntegerAttr>().getInt(); })), 847 subtensorOp.getSourceType().getElementType()); 848 return success(); 849 } 850 }; 851 852 struct FoldInitTensorWithTensorReshapeOp 853 : public OpRewritePattern<TensorReshapeOp> { 854 using OpRewritePattern<TensorReshapeOp>::OpRewritePattern; 855 856 LogicalResult matchAndRewrite(TensorReshapeOp reshapeOp, 857 PatternRewriter &rewriter) const override { 858 if (!reshapeOp.src().getDefiningOp<InitTensorOp>()) 859 return failure(); 860 Location loc = reshapeOp.getLoc(); 861 SmallVector<SmallVector<Value>, 4> resultShapes; 862 if (failed(reshapeOp.reifyReturnTypeShapesPerResultDim(rewriter, 863 resultShapes)) || 864 !llvm::hasSingleElement(resultShapes)) 865 return failure(); 866 Value initTensor = rewriter.create<InitTensorOp>( 867 loc, getAsOpFoldResult(resultShapes[0]), 868 reshapeOp.getResultType().getElementType()); 869 if (initTensor.getType() != reshapeOp.getResultType()) { 870 rewriter.replaceOpWithNewOp<tensor::CastOp>( 871 reshapeOp, reshapeOp.getResultType(), initTensor); 872 } else { 873 rewriter.replaceOp(reshapeOp, initTensor); 874 } 875 return success(); 876 } 877 }; 878 } // namespace 879 880 void InitTensorOp::getCanonicalizationPatterns(RewritePatternSet &results, 881 MLIRContext *context) { 882 results.add<FoldInitTensorWithSubTensorOp, FoldInitTensorWithTensorReshapeOp, 883 ReplaceStaticShapeDims>(context); 884 } 885 886 LogicalResult InitTensorOp::reifyReturnTypeShapesPerResultDim( 887 OpBuilder &builder, 888 SmallVectorImpl<SmallVector<Value>> &reifiedReturnShapes) { 889 auto shapes = llvm::to_vector<4>(llvm::map_range( 890 llvm::seq<int64_t>(0, getType().getRank()), [&](int64_t dim) -> Value { 891 if (isDynamicSize(dim)) 892 return getDynamicSize(dim); 893 return builder.create<ConstantIndexOp>(getLoc(), getStaticSize(dim)); 894 })); 895 reifiedReturnShapes.emplace_back(std::move(shapes)); 896 return success(); 897 } 898 899 //===----------------------------------------------------------------------===// 900 // PadTensorOp 901 //===----------------------------------------------------------------------===// 902 903 /// Extract int64_t values from the assumed ArrayAttr of IntegerAttr. 904 static SmallVector<int64_t, 4> extractFromI64ArrayAttr(Attribute attr) { 905 return llvm::to_vector<4>( 906 llvm::map_range(attr.cast<ArrayAttr>(), [](Attribute a) -> int64_t { 907 return a.cast<IntegerAttr>().getInt(); 908 })); 909 } 910 911 static LogicalResult verify(PadTensorOp op) { 912 auto sourceType = op.source().getType().cast<RankedTensorType>(); 913 auto resultType = op.result().getType().cast<RankedTensorType>(); 914 auto expectedType = PadTensorOp::inferResultType( 915 sourceType, extractFromI64ArrayAttr(op.static_low()), 916 extractFromI64ArrayAttr(op.static_high())); 917 for (int i = 0, e = sourceType.getRank(); i < e; ++i) { 918 if (resultType.getDimSize(i) == expectedType.getDimSize(i)) 919 continue; 920 if (expectedType.isDynamicDim(i)) 921 continue; 922 return op.emitError("specified type ") 923 << resultType << " does not match the inferred type " 924 << expectedType; 925 } 926 927 auto ®ion = op.region(); 928 unsigned rank = resultType.getRank(); 929 Block &block = region.front(); 930 if (block.getNumArguments() != rank) 931 return op.emitError("expected the block to have ") << rank << " arguments"; 932 933 // Note: the number and type of yield values are checked in the YieldOp. 934 for (auto en : llvm::enumerate(block.getArgumentTypes())) { 935 if (!en.value().isIndex()) 936 return op.emitOpError("expected block argument ") 937 << (en.index() + 1) << " to be an index"; 938 } 939 940 return success(); 941 } 942 943 RankedTensorType PadTensorOp::inferResultType(RankedTensorType sourceType, 944 ArrayRef<int64_t> staticLow, 945 ArrayRef<int64_t> staticHigh) { 946 unsigned rank = sourceType.getRank(); 947 assert(staticLow.size() == rank && "unexpected staticLow size mismatch"); 948 assert(staticHigh.size() == rank && "unexpected staticHigh size mismatch"); 949 950 SmallVector<int64_t, 4> resultShape; 951 for (auto i : llvm::seq<unsigned>(0, rank)) { 952 if (sourceType.isDynamicDim(i) || 953 staticLow[i] == ShapedType::kDynamicSize || 954 staticHigh[i] == ShapedType::kDynamicSize) { 955 resultShape.push_back(ShapedType::kDynamicSize); 956 } else { 957 int64_t size = sourceType.getDimSize(i) + staticLow[i] + staticHigh[i]; 958 resultShape.push_back(size); 959 } 960 } 961 962 return RankedTensorType::get(resultShape, sourceType.getElementType()); 963 } 964 965 void PadTensorOp::build(OpBuilder &b, OperationState &result, Value source, 966 ArrayRef<int64_t> staticLow, 967 ArrayRef<int64_t> staticHigh, ValueRange low, 968 ValueRange high, ArrayRef<NamedAttribute> attrs) { 969 auto sourceType = source.getType().cast<RankedTensorType>(); 970 auto resultType = inferResultType(sourceType, staticLow, staticHigh); 971 build(b, result, resultType, source, low, high, b.getI64ArrayAttr(staticLow), 972 b.getI64ArrayAttr(staticHigh)); 973 result.addAttributes(attrs); 974 } 975 976 void PadTensorOp::build(OpBuilder &b, OperationState &result, Value source, 977 ValueRange low, ValueRange high, 978 ArrayRef<NamedAttribute> attrs) { 979 auto sourceType = source.getType().cast<RankedTensorType>(); 980 unsigned rank = sourceType.getRank(); 981 SmallVector<int64_t, 4> staticVector(ShapedType::kDynamicSize, rank); 982 build(b, result, source, staticVector, staticVector, low, high, attrs); 983 } 984 985 void PadTensorOp::build(OpBuilder &b, OperationState &result, Type resultType, 986 Value source, ArrayRef<OpFoldResult> low, 987 ArrayRef<OpFoldResult> high, 988 ArrayRef<NamedAttribute> attrs) { 989 assert(resultType.isa<RankedTensorType>()); 990 auto sourceType = source.getType().cast<RankedTensorType>(); 991 unsigned rank = sourceType.getRank(); 992 SmallVector<Value, 4> dynamicLow, dynamicHigh; 993 SmallVector<int64_t, 4> staticLow, staticHigh; 994 for (unsigned i = 0; i < rank; ++i) { 995 // staticLow and staticHigh have full information of the padding config. 996 // This will grow staticLow and staticHigh with 1 value. If the config is 997 // dynamic (ie not a constant), dynamicLow and dynamicHigh will grow with 1 998 // value as well. 999 dispatchIndexOpFoldResult(low[i], dynamicLow, staticLow, 1000 ShapedType::kDynamicSize); 1001 dispatchIndexOpFoldResult(high[i], dynamicHigh, staticHigh, 1002 ShapedType::kDynamicSize); 1003 } 1004 if (!resultType) { 1005 resultType = 1006 PadTensorOp::inferResultType(sourceType, staticLow, staticHigh); 1007 } 1008 build(b, result, resultType, source, dynamicLow, dynamicHigh, 1009 b.getI64ArrayAttr(staticLow), b.getI64ArrayAttr(staticHigh)); 1010 } 1011 1012 PadTensorOp PadTensorOp::createPadScalarOp(Type type, Value source, Value pad, 1013 ArrayRef<OpFoldResult> low, 1014 ArrayRef<OpFoldResult> high, 1015 Location loc, OpBuilder &builder) { 1016 auto padTensorOp = 1017 builder.create<linalg::PadTensorOp>(loc, type, source, low, high); 1018 int rank = padTensorOp.getResultType().getRank(); 1019 SmallVector<Type, 4> blockArgTypes; 1020 blockArgTypes.assign(rank, builder.getIndexType()); 1021 auto ®ion = padTensorOp.region(); 1022 // `builder.createBlock` changes the insertion point within the block. Create 1023 // a guard to reset the insertion point of the builder after it is destroyed. 1024 OpBuilder::InsertionGuard guard(builder); 1025 builder.createBlock(®ion, region.end(), blockArgTypes); 1026 builder.create<linalg::YieldOp>(loc, pad); 1027 return padTensorOp; 1028 } 1029 1030 PadTensorOp PadTensorOp::createPadHighOp(Type type, Value source, Value pad, 1031 Location loc, OpBuilder &builder) { 1032 SmallVector<OpFoldResult, 4> low, high; 1033 auto rankedTensorType = type.cast<RankedTensorType>(); 1034 assert(rankedTensorType.hasStaticShape()); 1035 int rank = rankedTensorType.getRank(); 1036 for (int i = 0; i < rank; ++i) { 1037 auto dimOp = builder.createOrFold<memref::DimOp>(loc, source, i); 1038 auto resultDimSize = builder.createOrFold<ConstantIndexOp>( 1039 loc, rankedTensorType.getDimSize(i)); 1040 auto highValue = builder.createOrFold<SubIOp>(loc, resultDimSize, dimOp); 1041 high.push_back(highValue); 1042 low.push_back(builder.createOrFold<ConstantIndexOp>(loc, 0)); 1043 } 1044 return PadTensorOp::createPadScalarOp(type, source, pad, low, high, loc, 1045 builder); 1046 } 1047 1048 LogicalResult PadTensorOp::reifyReturnTypeShapesPerResultDim( 1049 OpBuilder &b, SmallVectorImpl<SmallVector<Value>> &reifiedReturnShapes) { 1050 Location loc = getLoc(); 1051 auto lowPad = getMixedLowPad(); 1052 auto highPad = getMixedHighPad(); 1053 SmallVector<Value> shapes; 1054 for (auto dim : llvm::seq<int64_t>(0, getSourceType().getRank())) { 1055 // Shape along each dimension is source dim + low pad + high pad. 1056 SmallVector<Value> mapOperands; 1057 mapOperands.push_back(b.createOrFold<memref::DimOp>(loc, source(), dim)); 1058 AffineExpr expr = b.getAffineDimExpr(0); 1059 unsigned numSymbols = 0; 1060 auto addOpFoldResult = [&](OpFoldResult valueOrAttr) { 1061 if (Value v = valueOrAttr.dyn_cast<Value>()) { 1062 expr = expr + b.getAffineSymbolExpr(numSymbols++); 1063 mapOperands.push_back(v); 1064 return; 1065 } 1066 int64_t staticValue = 1067 valueOrAttr.get<Attribute>().cast<IntegerAttr>().getInt(); 1068 expr = expr + staticValue; 1069 }; 1070 addOpFoldResult(lowPad[dim]); 1071 addOpFoldResult(highPad[dim]); 1072 shapes.push_back(applyMapToValues( 1073 b, loc, AffineMap::get(1, numSymbols, expr), mapOperands)[0]); 1074 } 1075 reifiedReturnShapes.emplace_back(std::move(shapes)); 1076 return success(); 1077 } 1078 1079 //===----------------------------------------------------------------------===// 1080 // ReshapeOp 1081 //===----------------------------------------------------------------------===// 1082 1083 Optional<SmallVector<ReassociationIndices>> 1084 mlir::linalg::getReassociationIndicesForReshape(ShapedType sourceType, 1085 ShapedType targetType) { 1086 // Make the sourceType greater rank than the targetType. If they are same 1087 // rank, then its an unsupported reshape op. 1088 if (sourceType.getRank() == targetType.getRank()) 1089 return llvm::None; 1090 if (sourceType.getRank() < targetType.getRank()) 1091 std::swap(sourceType, targetType); 1092 1093 ArrayRef<int64_t> sourceShape = sourceType.getShape(); 1094 ArrayRef<int64_t> targetShape = targetType.getShape(); 1095 unsigned sourceDim = 0; 1096 SmallVector<ReassociationIndices> reassociationMap; 1097 reassociationMap.reserve(targetType.getRank()); 1098 1099 ReassociationIndices currIndices; 1100 int64_t prodOfCollapsedDims = 1; 1101 while (sourceDim < sourceShape.size()) { 1102 unsigned targetDim = reassociationMap.size(); 1103 1104 // If all the dimensions of the targetShape are exhausted, then the 1105 // remaining dims in the source shape must be all 1s. So for such cases, set 1106 // 1 as the target shape. The actual reassociation indices will be handled 1107 // later. 1108 int64_t currTargetShape = 1109 (targetDim < targetType.getRank() ? targetShape[targetDim] : 1); 1110 while (sourceShape[sourceDim] != ShapedType::kDynamicSize && 1111 prodOfCollapsedDims * sourceShape[sourceDim] < currTargetShape && 1112 sourceDim < sourceShape.size()) { 1113 prodOfCollapsedDims *= sourceShape[sourceDim]; 1114 currIndices.push_back(sourceDim++); 1115 } 1116 1117 // If the current expanded dimension is dynamic, then the collapsed 1118 // dimensions should also be dynamic and product of all previous unprocessed 1119 // dimensions of the expanded shape should be 1. 1120 if (sourceShape[sourceDim] == ShapedType::kDynamicSize && 1121 (currTargetShape != ShapedType::kDynamicSize || 1122 prodOfCollapsedDims != 1)) 1123 return llvm::None; 1124 1125 // If the collapsed dim is dynamic, the current expanded dim should also 1126 // be dynamic. 1127 if (currTargetShape == ShapedType::kDynamicSize && 1128 sourceShape[sourceDim] != ShapedType::kDynamicSize) 1129 return llvm::None; 1130 1131 // For static shapes, if the product of dimensions of the expanded shape 1132 // should match the collapsed dimension shape. 1133 if (prodOfCollapsedDims * sourceShape[sourceDim] != currTargetShape) 1134 return llvm::None; 1135 1136 currIndices.push_back(sourceDim++); 1137 // If the reassociation is empty but the currIndices is not, this by 1138 // definition is folding unit-dimensions with the result being scalar type. 1139 // So only append the `currIndices` if reassociation map is not empty. 1140 if (targetDim == targetShape.size()) { 1141 if (!reassociationMap.empty() && !currIndices.empty()) 1142 reassociationMap.back().append(currIndices.begin(), currIndices.end()); 1143 // Break out of the loops. We should be done here. 1144 break; 1145 } 1146 reassociationMap.emplace_back(ReassociationIndices{}); 1147 std::swap(reassociationMap.back(), currIndices); 1148 prodOfCollapsedDims = 1; 1149 } 1150 // All the dimensions in the two shapes must have been processed. 1151 if (reassociationMap.size() != targetShape.size() || 1152 sourceDim != sourceShape.size()) 1153 return llvm::None; 1154 return reassociationMap; 1155 } 1156 1157 template <typename ReshapeLikeOp> 1158 static void print(OpAsmPrinter &p, ReshapeLikeOp op) { 1159 p << op.getOperationName() << ' ' << op.src() << " ["; 1160 1161 llvm::interleaveComma(op.reassociation(), p, [&](const Attribute &attr) { 1162 p << '['; 1163 auto arrayAttr = attr.template cast<ArrayAttr>(); 1164 llvm::interleaveComma(arrayAttr, p, [&](const Attribute &attr) { 1165 p << attr.cast<IntegerAttr>().getInt(); 1166 }); 1167 p << ']'; 1168 }); 1169 1170 p << "] "; 1171 p.printOptionalAttrDict(op->getAttrs(), 1172 /*elidedAttrs=*/{op.getReassociationAttrName()}); 1173 p << ": " << op.src().getType() << " into " << op.getType(); 1174 } 1175 1176 static void print(OpAsmPrinter &p, linalg::ReshapeOp op) { 1177 print<linalg::ReshapeOp>(p, op); 1178 } 1179 1180 static void print(OpAsmPrinter &p, linalg::TensorReshapeOp op) { 1181 print<linalg::TensorReshapeOp>(p, op); 1182 } 1183 1184 static ParseResult parseReshapeLikeOp(OpAsmParser &parser, 1185 OperationState &result) { 1186 // Parse the operand. 1187 OpAsmParser::OperandType src; 1188 if (parser.parseOperand(src)) 1189 return failure(); 1190 1191 // Parse reassociation indices. 1192 Builder &b = parser.getBuilder(); 1193 SmallVector<Attribute, 4> reassociation; 1194 if (parser.parseLSquare()) 1195 return failure(); 1196 1197 while (true) { 1198 if (succeeded(parser.parseOptionalRSquare())) 1199 break; 1200 if (parser.parseLSquare()) 1201 return failure(); 1202 SmallVector<int64_t> indices; 1203 while (true) { 1204 int64_t index; 1205 if (parser.parseInteger(index)) 1206 return failure(); 1207 indices.push_back(index); 1208 1209 if (succeeded(parser.parseOptionalComma())) 1210 continue; 1211 if (failed(parser.parseRSquare())) 1212 return failure(); 1213 break; 1214 } 1215 reassociation.push_back(b.getI64ArrayAttr(indices)); 1216 if (succeeded(parser.parseOptionalComma())) 1217 continue; 1218 if (failed(parser.parseRSquare())) 1219 return failure(); 1220 break; 1221 } 1222 1223 result.addAttribute(ReshapeOp::getReassociationAttrName(), 1224 b.getArrayAttr(reassociation)); 1225 1226 // Parse optional attributes. 1227 parser.parseOptionalAttrDict(result.attributes); 1228 1229 // Parse types. 1230 Type srcType; 1231 Type resultType; 1232 if (parser.parseColon() || parser.parseType(srcType) || 1233 parser.resolveOperand(src, srcType, result.operands) || 1234 parser.parseKeyword("into") || parser.parseType(resultType)) 1235 return failure(); 1236 result.addTypes(resultType); 1237 return success(); 1238 } 1239 1240 /// Collapse reassociation maps that are used in pair of reshape ops where one 1241 /// is a producer and other is the consumer. Only valid to use this method when 1242 /// both the producer and consumer are collapsing dimensions or both are 1243 /// expanding dimensions. 1244 /// 1245 /// For example, 1246 /// mapsProducer = [affine_map<(d0, d1, d2, d3, d4) -> (d0, d1)>, 1247 /// affine_map<(d0, d1, d2, d3, d4) -> (d2)>, 1248 /// affine_map<(d0, d1, d2, d3, d4) -> (d3, d4)>] 1249 /// mapsConsumer = [affine_map<(d0, d1, d2) -> (d0, d1)>, 1250 /// affine_map<(d0, d1, d2) -> (d2)>] 1251 /// 1252 /// is folded into 1253 /// 1254 /// result = [affine_map<(d0, d1, d2, d3, d4) -> (d0, d1, d2)>, 1255 /// affine_map<(d0, d1, d2, d3, d4) -> (d3, d4)>] 1256 static Optional<SmallVector<ReassociationIndices>> 1257 collapseReassociationIndices(ArrayRef<AffineMap> mapsProducer, 1258 ArrayRef<AffineMap> mapsConsumer, 1259 MLIRContext *context) { 1260 // Make the producer the larger sized vector. If they are of same size, the 1261 // resulting reshape is not a supported reshape op. 1262 if (mapsProducer.size() == mapsConsumer.size()) 1263 return llvm::None; 1264 if (mapsProducer.size() < mapsConsumer.size()) 1265 std::swap(mapsProducer, mapsConsumer); 1266 1267 // Handle the corner case of the result being a rank 0 shaped type. Return an 1268 // empty reassociation. 1269 if (mapsConsumer.empty()) 1270 return SmallVector<ReassociationIndices>{}; 1271 if (mapsProducer.size() != mapsConsumer[0].getNumDims()) 1272 return llvm::None; 1273 1274 unsigned currDim = 0; 1275 SmallVector<ReassociationIndices> reassociationMaps; 1276 for (AffineMap rhs : mapsConsumer) { 1277 ReassociationIndices reassociations; 1278 for (AffineExpr rhsExpr : rhs.getResults()) { 1279 AffineDimExpr dimExpr = rhsExpr.cast<AffineDimExpr>(); 1280 for (int i = 0, e = mapsProducer[dimExpr.getPosition()].getNumResults(); 1281 i < e; ++i) 1282 reassociations.push_back(currDim++); 1283 } 1284 reassociationMaps.push_back(std::move(reassociations)); 1285 } 1286 return reassociationMaps; 1287 } 1288 1289 namespace { 1290 /// Pattern to collapse producer/consumer reshape ops that are both collapsing 1291 /// dimensions or are both expanding dimensions. 1292 template <typename ReshapeOpTy> 1293 struct CollapseReshapeOps : public OpRewritePattern<ReshapeOpTy> { 1294 using OpRewritePattern<ReshapeOpTy>::OpRewritePattern; 1295 LogicalResult matchAndRewrite(ReshapeOpTy reshapeOp, 1296 PatternRewriter &rewriter) const override { 1297 auto srcReshapeOp = reshapeOp.src().template getDefiningOp<ReshapeOpTy>(); 1298 if (!srcReshapeOp) 1299 return failure(); 1300 1301 ShapedType srcReshapeSrcType = srcReshapeOp.getSrcType(); 1302 ShapedType intermediateType = reshapeOp.getSrcType(); 1303 ShapedType resultType = reshapeOp.getResultType(); 1304 1305 auto areReshapeOpsFoldable = [](ShapedType largerType, 1306 ShapedType intermediateType, 1307 ShapedType smallerType) -> bool { 1308 return largerType.getRank() > intermediateType.getRank() && 1309 intermediateType.getRank() > smallerType.getRank(); 1310 }; 1311 Optional<SmallVector<ReassociationIndices>> reassociationIndices = 1312 llvm::None; 1313 // Check if producer and consumer are both expanding dims or both collapsing 1314 // dims. In this case, try to compose the affine maps. This works for 1315 // dynamic shapes too. 1316 if (areReshapeOpsFoldable(resultType, intermediateType, 1317 srcReshapeSrcType) || 1318 areReshapeOpsFoldable(srcReshapeSrcType, intermediateType, 1319 resultType)) { 1320 reassociationIndices = collapseReassociationIndices( 1321 srcReshapeOp.getReassociationMaps(), reshapeOp.getReassociationMaps(), 1322 rewriter.getContext()); 1323 } 1324 if (!reassociationIndices) { 1325 // If the source reshape can be collapsed/expanded into the target reshape 1326 // they can still be folded. This can only be reasoned about statically 1327 // for cases where 1328 // - either all shapes are static, or 1329 // - The number of dynamic dimensions matches in the source of source and 1330 // result with all other dimensions being 1. 1331 reassociationIndices = 1332 getReassociationIndicesForReshape(srcReshapeSrcType, resultType); 1333 } 1334 if (!reassociationIndices) 1335 return failure(); 1336 rewriter.replaceOpWithNewOp<ReshapeOpTy>( 1337 reshapeOp, resultType, srcReshapeOp.src(), *reassociationIndices); 1338 return success(); 1339 } 1340 }; 1341 } // namespace 1342 1343 template <typename ReshapeOpTy> 1344 static OpFoldResult foldReshapeOp(ReshapeOpTy reshapeOp, 1345 ArrayRef<Attribute> operands) { 1346 // Fold producer-consumer reshape ops that where the operand type of the 1347 // producer is same as the return type of the consumer. 1348 ReshapeOpTy reshapeSrcOp = 1349 reshapeOp.src().template getDefiningOp<ReshapeOpTy>(); 1350 if (reshapeSrcOp && reshapeSrcOp.getSrcType() == reshapeOp.getResultType()) 1351 return reshapeSrcOp.src(); 1352 // Reshape of a constant can be replaced with a new constant. 1353 if (auto elements = operands.front().dyn_cast_or_null<DenseElementsAttr>()) { 1354 return elements.reshape( 1355 reshapeOp.getResult().getType().template cast<ShapedType>()); 1356 } 1357 return nullptr; 1358 } 1359 1360 /// Return true if the reassociation specification is valid, false otherwise. 1361 /// When false, the `invalidIndex` integer pointer is optionally filled with the 1362 /// index of the offending reassociation map. 1363 static bool isReassociationValid(ArrayRef<AffineMap> reassociation, 1364 int *invalidIndex = nullptr) { 1365 if (reassociation.empty()) 1366 return true; 1367 unsigned nDims = reassociation[0].getNumDims(); 1368 unsigned nextExpectedDim = 0; 1369 for (auto it : llvm::enumerate(reassociation)) { 1370 auto m = it.value(); 1371 if (m.getNumDims() != nDims || m.getNumSymbols() != 0) { 1372 if (invalidIndex) 1373 *invalidIndex = it.index(); 1374 return false; 1375 } 1376 for (auto e : m.getResults()) { 1377 auto d = e.dyn_cast<AffineDimExpr>(); 1378 if (!d || d.getPosition() != nextExpectedDim++) { 1379 if (invalidIndex) 1380 *invalidIndex = it.index(); 1381 return false; 1382 } 1383 } 1384 } 1385 if (nextExpectedDim != nDims) { 1386 if (invalidIndex) 1387 *invalidIndex = reassociation.size() - 1; 1388 return false; 1389 } 1390 return true; 1391 } 1392 1393 /// Detect whether memref dims [dim, dim + extent) can be reshaped without 1394 /// copies. 1395 static bool isReshapableDimBand(unsigned dim, unsigned extent, 1396 ArrayRef<int64_t> sizes, 1397 ArrayRef<AffineExpr> strides) { 1398 assert(sizes.size() == strides.size() && "mismatched ranks"); 1399 // off by 1 indexing to avoid out of bounds 1400 // V 1401 for (auto idx = dim, e = dim + extent; idx + 1 < e; ++idx) { 1402 // Only bands of static shapes are reshapable. This is due to the fact that 1403 // there is no relation between dynamic sizes and dynamic strides: we do not 1404 // have enough information to know whether a "-1" size corresponds to the 1405 // proper symbol in the AffineExpr of a stride. 1406 if (ShapedType::isDynamic(sizes[dim + 1])) 1407 return false; 1408 // TODO: Refine this by passing the proper nDims and nSymbols so we can 1409 // simplify on the fly and catch more reshapable cases. 1410 if (strides[idx] != strides[idx + 1] * sizes[idx + 1]) 1411 return false; 1412 } 1413 return true; 1414 } 1415 1416 /// Compute the MemRefType obtained by applying the `reassociation` (which is 1417 /// expected to be valid) to `type`. 1418 /// If `type` is Contiguous MemRefType, this always produce a contiguous 1419 /// MemRefType. 1420 static MemRefType 1421 computeReshapeCollapsedType(MemRefType type, 1422 ArrayRef<AffineMap> reassociation) { 1423 auto sizes = type.getShape(); 1424 AffineExpr offset; 1425 SmallVector<AffineExpr, 4> strides; 1426 auto status = getStridesAndOffset(type, strides, offset); 1427 (void)status; 1428 assert(succeeded(status) && "expected strided memref"); 1429 1430 SmallVector<int64_t, 4> newSizes; 1431 newSizes.reserve(reassociation.size()); 1432 SmallVector<AffineExpr, 4> newStrides; 1433 newStrides.reserve(reassociation.size()); 1434 1435 // Use the fact that reassociation is valid to simplify the logic: only use 1436 // each map's rank. 1437 assert(isReassociationValid(reassociation) && "invalid reassociation"); 1438 unsigned currentDim = 0; 1439 for (AffineMap m : reassociation) { 1440 unsigned dim = m.getNumResults(); 1441 int64_t size = 1; 1442 AffineExpr stride = strides[currentDim + dim - 1]; 1443 if (!isReshapableDimBand(currentDim, dim, sizes, strides)) { 1444 size = ShapedType::kDynamicSize; 1445 stride = AffineExpr(); 1446 } else { 1447 for (unsigned d = 0; d < dim; ++d) 1448 size *= sizes[currentDim + d]; 1449 } 1450 newSizes.push_back(size); 1451 newStrides.push_back(stride); 1452 currentDim += dim; 1453 } 1454 1455 // Early-exit: if `type` is contiguous, the result must be contiguous. 1456 if (canonicalizeStridedLayout(type).getAffineMaps().empty()) 1457 return MemRefType::Builder(type).setShape(newSizes).setAffineMaps({}); 1458 1459 // Convert back to int64_t because we don't have enough information to create 1460 // new strided layouts from AffineExpr only. This corresponds to a case where 1461 // copies may be necessary. 1462 int64_t intOffset = ShapedType::kDynamicStrideOrOffset; 1463 if (auto o = offset.dyn_cast<AffineConstantExpr>()) 1464 intOffset = o.getValue(); 1465 SmallVector<int64_t, 4> intStrides; 1466 intStrides.reserve(strides.size()); 1467 for (auto stride : newStrides) { 1468 if (auto cst = stride.dyn_cast_or_null<AffineConstantExpr>()) 1469 intStrides.push_back(cst.getValue()); 1470 else 1471 intStrides.push_back(ShapedType::kDynamicStrideOrOffset); 1472 } 1473 auto layout = 1474 makeStridedLinearLayoutMap(intStrides, intOffset, type.getContext()); 1475 return canonicalizeStridedLayout( 1476 MemRefType::Builder(type).setShape(newSizes).setAffineMaps({layout})); 1477 } 1478 1479 1480 template <typename AffineExprTy> 1481 unsigned getMaxPosOfType(ArrayRef<ReassociationExprs> exprArrays) { 1482 unsigned pos = 0; 1483 for (const auto &exprs : exprArrays) { 1484 for (auto expr : exprs) { 1485 expr.walk([&pos](AffineExpr e) { 1486 if (auto d = e.dyn_cast<AffineExprTy>()) 1487 pos = std::max(pos, d.getPosition()); 1488 }); 1489 } 1490 } 1491 return pos; 1492 } 1493 1494 static SmallVector<AffineMap, 4> 1495 getSymbolLessAffineMaps(ArrayRef<ReassociationExprs> reassociation) { 1496 unsigned maxDim = getMaxPosOfType<AffineDimExpr>(reassociation); 1497 assert(getMaxPosOfType<AffineSymbolExpr>(reassociation) == 0 && 1498 "Expected symbol-less expressions"); 1499 SmallVector<AffineMap, 4> maps; 1500 maps.reserve(reassociation.size()); 1501 for (const auto &exprs : reassociation) { 1502 assert(!exprs.empty()); 1503 maps.push_back(AffineMap::get(maxDim + 1, 0, exprs, exprs[0].getContext())); 1504 } 1505 return maps; 1506 } 1507 1508 static SmallVector<ReassociationIndices, 2> convertReassociationMapsToIndices( 1509 OpBuilder &b, ArrayRef<ReassociationExprs> reassociationExprs) { 1510 SmallVector<ReassociationIndices, 2> reassociationIndices; 1511 for (const auto &exprs : reassociationExprs) { 1512 ReassociationIndices indices; 1513 indices.reserve(exprs.size()); 1514 for (const auto &expr : exprs) 1515 indices.push_back(expr.cast<AffineDimExpr>().getPosition()); 1516 reassociationIndices.push_back(indices); 1517 } 1518 return reassociationIndices; 1519 } 1520 1521 static SmallVector<SmallVector<AffineExpr, 2>, 2> 1522 convertReassociationIndicesToExprs( 1523 OpBuilder &b, ArrayRef<ReassociationIndices> reassociationIndices) { 1524 SmallVector<SmallVector<AffineExpr, 2>, 2> reassociationMaps; 1525 for (const auto &indices : reassociationIndices) { 1526 SmallVector<AffineExpr, 2> reassociationMap; 1527 reassociationMap.reserve(indices.size()); 1528 for (int64_t index : indices) 1529 reassociationMap.push_back(b.getAffineDimExpr(index)); 1530 reassociationMaps.push_back(std::move(reassociationMap)); 1531 } 1532 return reassociationMaps; 1533 } 1534 1535 SmallVector<AffineMap, 4> ReshapeOp::getReassociationMaps() { 1536 return getSymbolLessAffineMaps(getReassociationExprs()); 1537 } 1538 SmallVector<ReassociationExprs, 4> ReshapeOp::getReassociationExprs() { 1539 OpBuilder b(this->getContext()); 1540 return convertReassociationIndicesToExprs(b, getReassociationIndices()); 1541 } 1542 SmallVector<AffineMap, 4> TensorReshapeOp::getReassociationMaps() { 1543 return getSymbolLessAffineMaps(getReassociationExprs()); 1544 } 1545 SmallVector<ReassociationExprs, 4> TensorReshapeOp::getReassociationExprs() { 1546 OpBuilder b(this->getContext()); 1547 return convertReassociationIndicesToExprs(b, getReassociationIndices()); 1548 } 1549 /// For reshape op compute the shape at dimension `dimIndex` of the output in 1550 /// terms of shape of the `src`, when the reshape op is a collapsing 1551 /// operation. It is the product of the shape of the collapsed dimensions of the 1552 /// `src`. 1553 static OpFoldResult 1554 getCollapsedOutputDimFromInputShape(OpBuilder &builder, Location loc, 1555 int64_t dimIndex, Value src, 1556 ArrayRef<AffineMap> reassociationMap) { 1557 AffineMap map = reassociationMap[dimIndex]; 1558 unsigned startPos = 1559 map.getResults().front().cast<AffineDimExpr>().getPosition(); 1560 unsigned endPos = map.getResults().back().cast<AffineDimExpr>().getPosition(); 1561 AffineExpr expr; 1562 SmallVector<Value, 2> dynamicDims; 1563 for (auto dim : llvm::seq(startPos, endPos + 1)) { 1564 dynamicDims.push_back(builder.createOrFold<memref::DimOp>(loc, src, dim)); 1565 AffineExpr currExpr = builder.getAffineSymbolExpr(dim - startPos); 1566 expr = (expr ? expr * currExpr : currExpr); 1567 } 1568 return applyMapToValues(builder, loc, 1569 AffineMap::get(0, endPos - startPos + 1, expr), 1570 dynamicDims)[0]; 1571 } 1572 1573 /// Given the `src` of a collapsing reshape op and its reassociation maps, 1574 /// compute the shape of the result of the reshape. 1575 static SmallVector<OpFoldResult, 4> getCollapsedOutputShapeFromInputShape( 1576 OpBuilder &builder, Location loc, Value src, 1577 ArrayRef<int64_t> dstStaticShape, ArrayRef<AffineMap> reassociation) { 1578 return llvm::to_vector<4>(llvm::map_range( 1579 llvm::seq<int64_t>(0, dstStaticShape.size()), [&](int64_t dim) { 1580 return getCollapsedOutputDimFromInputShape(builder, loc, dim, src, 1581 reassociation); 1582 })); 1583 } 1584 1585 /// Compute a map that for a given dimension of the expanded type gives the 1586 /// dimension in the collapsed type it maps to. Essentially its the inverse of 1587 /// the `reassocation` maps. 1588 static llvm::DenseMap<int64_t, int64_t> 1589 getExpandedDimToCollapsedDimMap(ArrayRef<AffineMap> reassociation) { 1590 llvm::DenseMap<int64_t, int64_t> expandedDimToCollapsedDim; 1591 for (auto map : enumerate(reassociation)) { 1592 unsigned startPos = 1593 map.value().getResults().front().cast<AffineDimExpr>().getPosition(); 1594 unsigned endPos = 1595 map.value().getResults().back().cast<AffineDimExpr>().getPosition(); 1596 for (auto dim : llvm::seq(startPos, endPos + 1)) { 1597 expandedDimToCollapsedDim[dim] = map.index(); 1598 } 1599 } 1600 return expandedDimToCollapsedDim; 1601 } 1602 1603 /// For an expanding reshape op, compute the value for a dimension of the output 1604 /// from the shape of the input. 1605 static OpFoldResult getExpandedOutputDimFromInputShape( 1606 OpBuilder &builder, Location loc, int64_t dimIndex, Value src, 1607 ArrayRef<int64_t> dstStaticShape, ArrayRef<AffineMap> reassociation, 1608 llvm::DenseMap<int64_t, int64_t> &expandedDimToCollapsedDim) { 1609 if (!ShapedType::isDynamic(dstStaticShape[dimIndex])) { 1610 return builder.getI64IntegerAttr(dstStaticShape[dimIndex]); 1611 } 1612 unsigned sourceDimPos = expandedDimToCollapsedDim[dimIndex]; 1613 unsigned startPos = reassociation[sourceDimPos] 1614 .getResults() 1615 .front() 1616 .cast<AffineDimExpr>() 1617 .getPosition(); 1618 unsigned endPos = reassociation[sourceDimPos] 1619 .getResults() 1620 .back() 1621 .cast<AffineDimExpr>() 1622 .getPosition(); 1623 int64_t linearizedStaticDim = 1; 1624 for (auto d : 1625 llvm::enumerate(dstStaticShape.slice(startPos, endPos - startPos + 1))) { 1626 if (d.index() + startPos == static_cast<unsigned>(dimIndex)) 1627 continue; 1628 assert(!ShapedType::isDynamic(d.value()) && 1629 "single dimension cannot be expanded into multiple dynamic " 1630 "dimensions"); 1631 linearizedStaticDim *= d.value(); 1632 } 1633 Value sourceDim = builder.create<memref::DimOp>(loc, src, sourceDimPos); 1634 return applyMapToValues( 1635 builder, loc, 1636 AffineMap::get( 1637 0, 1, builder.getAffineSymbolExpr(0).floorDiv(linearizedStaticDim)), 1638 sourceDim)[0]; 1639 } 1640 1641 /// Given the `src` of an expanding reshape op, the reassociation maps and the 1642 /// result type, compute the shape of the result of the reshape. 1643 static SmallVector<OpFoldResult, 4> getExpandedOutputShapeFromInputShape( 1644 OpBuilder &builder, Location loc, Value src, 1645 ArrayRef<int64_t> dstStaticShape, ArrayRef<AffineMap> reassociation) { 1646 llvm::DenseMap<int64_t, int64_t> expandedDimToCollapsedDim = 1647 getExpandedDimToCollapsedDimMap(reassociation); 1648 return llvm::to_vector<4>(llvm::map_range( 1649 llvm::seq<int64_t>(0, dstStaticShape.size()), [&](int64_t dim) { 1650 return getExpandedOutputDimFromInputShape(builder, loc, dim, src, 1651 dstStaticShape, reassociation, 1652 expandedDimToCollapsedDim); 1653 })); 1654 } 1655 1656 static SmallVector<OpFoldResult, 4> 1657 getReshapeOutputShapeFromInputShape(OpBuilder &builder, Location loc, Value src, 1658 ArrayRef<int64_t> dstStaticShape, 1659 ArrayRef<AffineMap> reassocation) { 1660 return dstStaticShape.size() > 1661 static_cast<size_t>(src.getType().cast<ShapedType>().getRank()) 1662 ? getExpandedOutputShapeFromInputShape( 1663 builder, loc, src, dstStaticShape, reassocation) 1664 : getCollapsedOutputShapeFromInputShape( 1665 builder, loc, src, dstStaticShape, reassocation); 1666 } 1667 1668 static ArrayAttr 1669 getReassociationIndicesAttribute(OpBuilder &b, 1670 ArrayRef<ReassociationIndices> reassociation) { 1671 SmallVector<Attribute, 4> reassociationAttr = 1672 llvm::to_vector<4>(llvm::map_range( 1673 reassociation, [&](ReassociationIndices indices) -> Attribute { 1674 return b.getI64ArrayAttr(indices).cast<Attribute>(); 1675 })); 1676 return b.getArrayAttr(reassociationAttr); 1677 } 1678 1679 void mlir::linalg::ReshapeOp::build( 1680 OpBuilder &b, OperationState &result, Value src, 1681 ArrayRef<ReassociationIndices> reassociation, 1682 ArrayRef<NamedAttribute> attrs) { 1683 auto memRefType = src.getType().cast<MemRefType>(); 1684 auto resultType = computeReshapeCollapsedType( 1685 memRefType, getSymbolLessAffineMaps( 1686 convertReassociationIndicesToExprs(b, reassociation))); 1687 build(b, result, resultType, src, attrs); 1688 result.addAttribute(ReshapeOp::getReassociationAttrName(), 1689 getReassociationIndicesAttribute(b, reassociation)); 1690 } 1691 1692 void mlir::linalg::ReshapeOp::build( 1693 OpBuilder &b, OperationState &result, Type resultType, Value src, 1694 ArrayRef<ReassociationIndices> reassociation, 1695 ArrayRef<NamedAttribute> attrs) { 1696 build(b, result, resultType, src, attrs); 1697 result.addAttribute(ReshapeOp::getReassociationAttrName(), 1698 getReassociationIndicesAttribute(b, reassociation)); 1699 } 1700 1701 Value mlir::linalg::ReshapeOp::getViewSource() { return src(); } 1702 1703 /// Verify that shapes of the reshaped types using following rules 1704 /// 1) if a dimension in the collapsed type is static, then the corresponding 1705 /// dimensions in the expanded shape should be 1706 /// a) static 1707 /// b) the product should be same as the collaped shape. 1708 /// 2) if a dimension in the collaped type is dynamic, one and only one of the 1709 /// corresponding dimensions in the expanded type should be dynamic. This 1710 /// rule is only needed with reshape operations that are expanding. 1711 template <typename OpTy> 1712 static LogicalResult verifyReshapeLikeShapes(OpTy op, ShapedType collapsedType, 1713 ShapedType expandedType, 1714 bool isExpandingReshape) { 1715 ArrayRef<int64_t> collapsedShape = collapsedType.getShape(); 1716 ArrayRef<int64_t> expandedShape = expandedType.getShape(); 1717 unsigned expandedDimStart = 0; 1718 for (auto map : llvm::enumerate(op.getReassociationMaps())) { 1719 Optional<int64_t> dynamicShape; 1720 int64_t linearizedStaticShape = 1; 1721 for (auto dim : llvm::enumerate(expandedShape.slice( 1722 expandedDimStart, map.value().getNumResults()))) { 1723 if (ShapedType::isDynamic(dim.value())) { 1724 if (isExpandingReshape && dynamicShape) { 1725 return op->emitOpError("invalid to have a single dimension (") 1726 << map.index() << ") expanded into multiple dynamic dims (" 1727 << expandedDimStart + dynamicShape.getValue() << "," 1728 << expandedDimStart + dim.index() << ")"; 1729 } 1730 dynamicShape = dim.index(); 1731 } else { 1732 linearizedStaticShape *= dim.value(); 1733 } 1734 } 1735 if (dynamicShape) { 1736 if (!ShapedType::isDynamic(collapsedShape[map.index()])) { 1737 return op->emitOpError("expected dimension ") 1738 << map.index() 1739 << " of collapsed type to be dynamic since one or more of the " 1740 "corresponding dimensions in the expanded type is dynamic"; 1741 } 1742 } else { 1743 if (collapsedShape[map.index()] != linearizedStaticShape) { 1744 return op->emitOpError("expected dimension ") 1745 << map.index() << " of collapsed type to be static value of " 1746 << linearizedStaticShape << " "; 1747 } 1748 } 1749 expandedDimStart += map.value().getNumResults(); 1750 } 1751 return success(); 1752 } 1753 1754 // Common verifier for reshape-like types. Fills `expandedType` and 1755 // `collapsedType` with the proper `src` or `result` type. 1756 template <typename Op, typename T> 1757 static LogicalResult verifyReshapeLikeTypes(Op op, T &expandedType, 1758 T &collapsedType) { 1759 expandedType = op.getSrcType(); 1760 collapsedType = op.getResultType(); 1761 unsigned expandedRank = expandedType.getRank(); 1762 unsigned collapsedRank = collapsedType.getRank(); 1763 bool isCollapse = expandedRank > collapsedRank; 1764 if (!isCollapse) { 1765 std::swap(expandedRank, collapsedRank); 1766 std::swap(expandedType, collapsedType); 1767 } 1768 if (expandedRank == 0) 1769 return op.emitOpError("expected non-zero memref ranks"); 1770 if (expandedRank == collapsedRank) 1771 return op.emitOpError("expected to collapse or expand dims"); 1772 1773 if (collapsedRank == 0) { 1774 // If collapsed rank is 0, then expanded type must be static shaped and of 1775 // sizes 1. 1776 if (llvm::any_of(expandedType.getShape(), 1777 [](int64_t dim) -> bool { return dim != 1; })) 1778 return op.emitOpError("invalid to reshape tensor/memref with non-unit " 1779 "extent dimensions to zero-rank tensor/memref"); 1780 return success(); 1781 } 1782 if (collapsedRank != op.reassociation().size()) 1783 return op.emitOpError("expected rank of the collapsed type(") 1784 << collapsedRank << ") to be the number of reassociation maps(" 1785 << op.reassociation().size() << ")"; 1786 auto maps = op.getReassociationMaps(); 1787 for (auto it : llvm::enumerate(maps)) 1788 if (it.value().getNumDims() != expandedRank) 1789 return op.emitOpError("expected reassociation map #") 1790 << it.index() << " of same rank as expanded memref(" 1791 << expandedRank << "), but got " << it.value().getNumDims(); 1792 int invalidIdx = 0; 1793 if (!isReassociationValid(maps, &invalidIdx)) 1794 return op.emitOpError("expected reassociation map #") 1795 << invalidIdx << " to be valid and contiguous"; 1796 return verifyReshapeLikeShapes(op, collapsedType, expandedType, !isCollapse); 1797 } 1798 1799 static LogicalResult verify(ReshapeOp op) { 1800 MemRefType expandedType, collapsedType; 1801 if (failed(verifyReshapeLikeTypes(op, expandedType, collapsedType))) 1802 return failure(); 1803 auto maps = op.getReassociationMaps(); 1804 MemRefType expectedType = computeReshapeCollapsedType(expandedType, maps); 1805 if (collapsedType != expectedType) 1806 return op.emitOpError("expected collapsed type to be ") 1807 << expectedType << ", but got " << collapsedType; 1808 return success(); 1809 } 1810 1811 void ReshapeOp::getCanonicalizationPatterns(RewritePatternSet &results, 1812 MLIRContext *context) { 1813 results.add<CollapseReshapeOps<ReshapeOp>>(context); 1814 } 1815 1816 //===----------------------------------------------------------------------===// 1817 // TensorReshapeOp 1818 //===----------------------------------------------------------------------===// 1819 1820 /// Compute the RankedTensorType obtained by applying `reassociation` to `type`. 1821 static RankedTensorType 1822 computeTensorReshapeCollapsedType(RankedTensorType type, 1823 ArrayRef<AffineMap> reassociation) { 1824 auto shape = type.getShape(); 1825 SmallVector<int64_t, 4> newShape; 1826 newShape.reserve(reassociation.size()); 1827 1828 // Use the fact that reassociation is valid to simplify the logic: only use 1829 // each map's rank. 1830 assert(isReassociationValid(reassociation) && "invalid reassociation"); 1831 unsigned currentDim = 0; 1832 for (AffineMap m : reassociation) { 1833 unsigned dim = m.getNumResults(); 1834 auto band = shape.slice(currentDim, dim); 1835 int64_t size = 1; 1836 if (llvm::is_contained(band, ShapedType::kDynamicSize)) 1837 size = ShapedType::kDynamicSize; 1838 else 1839 for (unsigned d = 0; d < dim; ++d) 1840 size *= shape[currentDim + d]; 1841 newShape.push_back(size); 1842 currentDim += dim; 1843 } 1844 1845 return RankedTensorType::get(newShape, type.getElementType()); 1846 } 1847 1848 void mlir::linalg::TensorReshapeOp::build( 1849 OpBuilder &b, OperationState &result, Value src, 1850 ArrayRef<ReassociationIndices> reassociation, 1851 ArrayRef<NamedAttribute> attrs) { 1852 auto resultType = computeTensorReshapeCollapsedType( 1853 src.getType().cast<RankedTensorType>(), 1854 getSymbolLessAffineMaps( 1855 convertReassociationIndicesToExprs(b, reassociation))); 1856 build(b, result, resultType, src, attrs); 1857 result.addAttribute(ReshapeOp::getReassociationAttrName(), 1858 getReassociationIndicesAttribute(b, reassociation)); 1859 } 1860 1861 void mlir::linalg::TensorReshapeOp::build( 1862 OpBuilder &b, OperationState &result, Type resultType, Value src, 1863 ArrayRef<ReassociationIndices> reassociation, 1864 ArrayRef<NamedAttribute> attrs) { 1865 build(b, result, resultType, src, attrs); 1866 result.addAttribute(ReshapeOp::getReassociationAttrName(), 1867 getReassociationIndicesAttribute(b, reassociation)); 1868 } 1869 1870 static LogicalResult verify(TensorReshapeOp op) { 1871 RankedTensorType expandedType, collapsedType; 1872 if (failed(verifyReshapeLikeTypes(op, expandedType, collapsedType))) 1873 return failure(); 1874 1875 auto maps = op.getReassociationMaps(); 1876 RankedTensorType expectedType = 1877 computeTensorReshapeCollapsedType(expandedType, maps); 1878 if (collapsedType != expectedType) 1879 return op.emitOpError("expected collapsed type to be ") 1880 << expectedType << ", but got " << collapsedType; 1881 return success(); 1882 } 1883 1884 namespace { 1885 /// Reshape of a splat constant can be replaced with a constant of the result 1886 /// type. 1887 struct FoldReshapeWithConstant : OpRewritePattern<TensorReshapeOp> { 1888 using OpRewritePattern<TensorReshapeOp>::OpRewritePattern; 1889 LogicalResult matchAndRewrite(TensorReshapeOp reshapeOp, 1890 PatternRewriter &rewriter) const override { 1891 DenseElementsAttr attr; 1892 if (!matchPattern(reshapeOp.src(), m_Constant(&attr))) 1893 return failure(); 1894 if (!attr || !attr.isSplat()) 1895 return failure(); 1896 DenseElementsAttr newAttr = DenseElementsAttr::getFromRawBuffer( 1897 reshapeOp.getResultType(), attr.getRawData(), true); 1898 rewriter.replaceOpWithNewOp<ConstantOp>(reshapeOp, newAttr); 1899 return success(); 1900 } 1901 }; 1902 1903 /// Fold linalg.fill -> linalg.tensor_reshape chain. 1904 /// 1905 /// For such op chains, we can create new linalg.fill ops with the result 1906 /// type of the linalg.tensor_reshape op. 1907 struct FoldFillWithTensorReshape : OpRewritePattern<TensorReshapeOp> { 1908 using OpRewritePattern<TensorReshapeOp>::OpRewritePattern; 1909 LogicalResult matchAndRewrite(TensorReshapeOp reshapeOp, 1910 PatternRewriter &rewriter) const override { 1911 auto oldFill = reshapeOp.src().getDefiningOp<FillOp>(); 1912 if (!oldFill) 1913 return failure(); 1914 1915 Location loc = oldFill.getLoc(); 1916 auto newInit = rewriter.create<TensorReshapeOp>( 1917 loc, reshapeOp.getResultType(), oldFill.output(), 1918 reshapeOp.reassociation()); 1919 rewriter.replaceOpWithNewOp<FillOp>(reshapeOp, newInit, oldFill.value()); 1920 1921 return success(); 1922 } 1923 }; 1924 } // namespace 1925 1926 void TensorReshapeOp::getCanonicalizationPatterns(RewritePatternSet &results, 1927 MLIRContext *context) { 1928 results.add<CollapseReshapeOps<TensorReshapeOp>, FoldFillWithTensorReshape, 1929 FoldInitTensorWithTensorReshapeOp, FoldReshapeWithConstant>( 1930 context); 1931 } 1932 1933 LogicalResult TensorReshapeOp::reifyReturnTypeShapesPerResultDim( 1934 OpBuilder &b, SmallVectorImpl<SmallVector<Value>> &reifiedReturnShapes) { 1935 auto resultShape = 1936 getAsValues(b, getLoc(), 1937 getReshapeOutputShapeFromInputShape( 1938 b, getLoc(), src(), getResultType().getShape(), 1939 getReassociationMaps())); 1940 reifiedReturnShapes.emplace_back(std::move(resultShape)); 1941 return success(); 1942 } 1943 1944 //===----------------------------------------------------------------------===// 1945 // YieldOp 1946 //===----------------------------------------------------------------------===// 1947 1948 static void print(OpAsmPrinter &p, linalg::YieldOp op) { 1949 p << op.getOperationName(); 1950 if (op.getNumOperands() > 0) 1951 p << ' ' << op.getOperands(); 1952 p.printOptionalAttrDict(op->getAttrs()); 1953 if (op.getNumOperands() > 0) 1954 p << " : " << op.getOperandTypes(); 1955 } 1956 1957 static ParseResult parseYieldOp(OpAsmParser &parser, OperationState &result) { 1958 SmallVector<OpAsmParser::OperandType, 2> opInfo; 1959 SmallVector<Type, 2> types; 1960 llvm::SMLoc loc = parser.getCurrentLocation(); 1961 return failure(parser.parseOperandList(opInfo) || 1962 parser.parseOptionalAttrDict(result.attributes) || 1963 (!opInfo.empty() && parser.parseColonTypeList(types)) || 1964 parser.resolveOperands(opInfo, types, loc, result.operands)); 1965 } 1966 1967 // Check the operand number and types must match the element types of the 1968 // LinalgOp interface's shaped operands. 1969 static LogicalResult verifyYield(linalg::YieldOp op, 1970 LinalgOp linalgOpInterface) { 1971 auto nOutputs = linalgOpInterface.getNumOutputs(); 1972 if (op.getNumOperands() != nOutputs) 1973 return op.emitOpError("expected number of yield values (") 1974 << nOutputs << ") to match the number of operands of the enclosing " 1975 << "LinalgOp (" << op.getNumOperands() << ")"; 1976 1977 for (unsigned i = 0; i != nOutputs; ++i) { 1978 auto elementType = 1979 linalgOpInterface.getOutputShapedType(i).getElementType(); 1980 if (op.getOperand(i).getType() != elementType) 1981 return op.emitOpError("type of yield operand ") 1982 << (i + 1) << " (" << op.getOperand(i).getType() 1983 << ") doesn't match " 1984 << "the element type of the enclosing linalg.generic op (" 1985 << elementType << ")"; 1986 } 1987 return success(); 1988 } 1989 1990 static LogicalResult verify(linalg::YieldOp op) { 1991 auto *parentOp = op->getParentOp(); 1992 if (parentOp->getNumRegions() != 1 || parentOp->getRegion(0).empty()) 1993 return op.emitOpError("expected single non-empty parent region"); 1994 1995 if (auto linalgOp = dyn_cast<LinalgOp>(parentOp)) 1996 return verifyYield(op, cast<LinalgOp>(parentOp)); 1997 1998 if (auto padTensorOp = dyn_cast<linalg::PadTensorOp>(parentOp)) { 1999 if (op.getNumOperands() != 1) 2000 return op.emitOpError("expected single yield operand (got ") 2001 << op->getNumOperands() << ")"; 2002 if (op.getOperand(0).getType() != 2003 padTensorOp.getType().cast<ShapedType>().getElementType()) 2004 return op.emitOpError("expected yield type to match shape element type"); 2005 return success(); 2006 } 2007 2008 if (auto tiledLoopOp = dyn_cast<linalg::TiledLoopOp>(parentOp)) { 2009 // Check if output args with tensor types match results types. 2010 SmallVector<Value, 2> tensorOuts; 2011 llvm::copy_if( 2012 tiledLoopOp.outputs(), std::back_inserter(tensorOuts), 2013 [&](Value out) { return out.getType().isa<RankedTensorType>(); }); 2014 if (tensorOuts.size() != op.values().size()) 2015 return op.emitOpError("expected number of tensor output args = ") 2016 << tensorOuts.size() << " to match the number of yield operands = " 2017 << op.values().size(); 2018 2019 TypeRange tensorTypes(llvm::makeArrayRef(tensorOuts)); 2020 for (auto &item : 2021 llvm::enumerate(llvm::zip(tensorTypes, op.getOperandTypes()))) { 2022 Type outType, resultType; 2023 unsigned index = item.index(); 2024 std::tie(outType, resultType) = item.value(); 2025 if (outType != resultType) 2026 return op.emitOpError("expected yield operand ") 2027 << index << " with type = " << resultType 2028 << " to match output arg type = " << outType; 2029 } 2030 return success(); 2031 } 2032 return op.emitOpError("expected parent op with LinalgOp interface"); 2033 } 2034 2035 //===----------------------------------------------------------------------===// 2036 // TiledLoopOp 2037 //===----------------------------------------------------------------------===// 2038 2039 void TiledLoopOp::build(OpBuilder &builder, OperationState &result, 2040 ValueRange lowerBounds, ValueRange upperBounds, 2041 ValueRange steps, ValueRange inputs, ValueRange outputs, 2042 ArrayAttr iteratorTypes, 2043 function_ref<void(OpBuilder &, Location, ValueRange, 2044 ValueRange, ValueRange)> 2045 bodyBuilderFn) { 2046 result.addOperands(lowerBounds); 2047 result.addOperands(upperBounds); 2048 result.addOperands(steps); 2049 result.addOperands(inputs); 2050 result.addOperands(outputs); 2051 result.addAttribute( 2052 TiledLoopOp::getOperandSegmentSizeAttr(), 2053 builder.getI32VectorAttr({static_cast<int32_t>(lowerBounds.size()), 2054 static_cast<int32_t>(upperBounds.size()), 2055 static_cast<int32_t>(steps.size()), 2056 static_cast<int32_t>(inputs.size()), 2057 static_cast<int32_t>(outputs.size())})); 2058 result.addAttribute(getIteratorTypesAttrName(), iteratorTypes); 2059 2060 // Add output types for `RankedTensorType` output arguments. 2061 for (Value output : outputs) { 2062 Type outputType = output.getType(); 2063 if (outputType.isa<RankedTensorType>()) 2064 result.addTypes(outputType); 2065 } 2066 2067 OpBuilder::InsertionGuard guard(builder); 2068 unsigned numIVs = steps.size(); 2069 SmallVector<Type, 8> argTypes(numIVs, builder.getIndexType()); 2070 for (Type type : TypeRange(inputs)) 2071 argTypes.push_back(type); 2072 for (Type type : TypeRange(outputs)) 2073 argTypes.push_back(type); 2074 Region *bodyRegion = result.addRegion(); 2075 Block *bodyBlock = builder.createBlock(bodyRegion, {}, argTypes); 2076 2077 if (bodyBuilderFn) { 2078 builder.setInsertionPointToStart(bodyBlock); 2079 bodyBuilderFn(builder, result.location, 2080 bodyBlock->getArguments().take_front(numIVs), 2081 bodyBlock->getArguments().slice(numIVs, inputs.size()), 2082 bodyBlock->getArguments().take_back(outputs.size())); 2083 TiledLoopOp::ensureTerminator(*bodyRegion, builder, result.location); 2084 } 2085 } 2086 2087 static void print(OpAsmPrinter &p, TiledLoopOp op) { 2088 p << op.getOperationName() << " (" << op.getInductionVars() << ") = (" 2089 << op.lowerBound() << ") to (" << op.upperBound() << ") step (" << op.step() 2090 << ")"; 2091 2092 if (!op.inputs().empty()) { 2093 p << " ins ("; 2094 llvm::interleaveComma(llvm::zip(op.getRegionInputArgs(), op.inputs()), p, 2095 [&](auto it) { 2096 p << std::get<0>(it) << " = " << std::get<1>(it) 2097 << ": " << std::get<1>(it).getType(); 2098 }); 2099 p << ")"; 2100 } 2101 if (!op.outputs().empty()) { 2102 p << " outs ("; 2103 llvm::interleaveComma(llvm::zip(op.getRegionOutputArgs(), op.outputs()), p, 2104 [&](auto it) { 2105 p << std::get<0>(it) << " = " << std::get<1>(it) 2106 << ": " << std::get<1>(it).getType(); 2107 }); 2108 p << ")"; 2109 } 2110 2111 if (llvm::any_of(op.iterator_types(), [](Attribute attr) { 2112 return attr.cast<StringAttr>().getValue() != 2113 getParallelIteratorTypeName(); 2114 })) { 2115 p << " iterators" << op.iterator_types() << ""; 2116 } 2117 2118 p.printRegion(op.region(), /*printEntryBlockArgs=*/false); 2119 p.printOptionalAttrDict( 2120 op->getAttrs(), /*elidedAttrs=*/{TiledLoopOp::getOperandSegmentSizeAttr(), 2121 getIteratorTypesAttrName()}); 2122 } 2123 2124 static ParseResult parseTiledLoopOp(OpAsmParser &parser, 2125 OperationState &result) { 2126 auto &builder = parser.getBuilder(); 2127 // Parse an opening `(` followed by induction variables followed by `)` 2128 SmallVector<OpAsmParser::OperandType, 4> ivs; 2129 if (parser.parseRegionArgumentList(ivs, /*requiredOperandCount=*/-1, 2130 OpAsmParser::Delimiter::Paren)) 2131 return failure(); 2132 2133 // Parse loop bounds. 2134 SmallVector<OpAsmParser::OperandType, 4> lower; 2135 if (parser.parseEqual() || 2136 parser.parseOperandList(lower, ivs.size(), 2137 OpAsmParser::Delimiter::Paren) || 2138 parser.resolveOperands(lower, builder.getIndexType(), result.operands)) 2139 return failure(); 2140 2141 SmallVector<OpAsmParser::OperandType, 4> upper; 2142 if (parser.parseKeyword("to") || 2143 parser.parseOperandList(upper, ivs.size(), 2144 OpAsmParser::Delimiter::Paren) || 2145 parser.resolveOperands(upper, builder.getIndexType(), result.operands)) 2146 return failure(); 2147 2148 // Parse step values. 2149 SmallVector<OpAsmParser::OperandType, 4> steps; 2150 if (parser.parseKeyword("step") || 2151 parser.parseOperandList(steps, ivs.size(), 2152 OpAsmParser::Delimiter::Paren) || 2153 parser.resolveOperands(steps, builder.getIndexType(), result.operands)) 2154 return failure(); 2155 2156 // Parse input tensors. 2157 SmallVector<OpAsmParser::OperandType, 4> inputs, input_region_args; 2158 SmallVector<Type, 4> inputTypes; 2159 if (succeeded(parser.parseOptionalKeyword("ins"))) { 2160 llvm::SMLoc inputsOperandsLoc = parser.getCurrentLocation(); 2161 2162 if (parser.parseAssignmentListWithTypes(input_region_args, inputs, 2163 inputTypes)) 2164 return failure(); 2165 2166 if (parser.resolveOperands(inputs, inputTypes, inputsOperandsLoc, 2167 result.operands)) 2168 return failure(); 2169 } 2170 2171 // Parse output tensors. 2172 SmallVector<OpAsmParser::OperandType, 4> outputs, output_region_args; 2173 SmallVector<Type, 4> outputTypes; 2174 if (succeeded(parser.parseOptionalKeyword("outs"))) { 2175 llvm::SMLoc outputsOperandsLoc = parser.getCurrentLocation(); 2176 2177 if (parser.parseAssignmentListWithTypes(output_region_args, outputs, 2178 outputTypes)) 2179 return failure(); 2180 2181 if (parser.resolveOperands(outputs, outputTypes, outputsOperandsLoc, 2182 result.operands)) 2183 return failure(); 2184 for (Type outputType : outputTypes) 2185 if (outputType.isa<RankedTensorType>()) 2186 result.addTypes(outputType); 2187 } 2188 2189 // Parse attributes. 2190 SmallVector<Attribute, 4> iterTypes; 2191 if (succeeded(parser.parseOptionalKeyword("iterators"))) { 2192 StringAttr iterType; 2193 2194 if (parser.parseLSquare() || parser.parseAttribute(iterType)) 2195 return failure(); 2196 iterTypes.push_back(iterType); 2197 for (int i = 1, e = ivs.size(); i < e; ++i) { 2198 if (parser.parseComma() || parser.parseAttribute(iterType)) 2199 return failure(); 2200 iterTypes.push_back(iterType); 2201 } 2202 if (parser.parseRSquare()) 2203 return failure(); 2204 } else { 2205 auto parallelIter = builder.getStringAttr(getParallelIteratorTypeName()); 2206 iterTypes = SmallVector<Attribute, 4>(ivs.size(), parallelIter); 2207 } 2208 result.addAttribute(getIteratorTypesAttrName(), 2209 builder.getArrayAttr(iterTypes)); 2210 result.addAttribute( 2211 TiledLoopOp::getOperandSegmentSizeAttr(), 2212 builder.getI32VectorAttr({static_cast<int32_t>(lower.size()), 2213 static_cast<int32_t>(upper.size()), 2214 static_cast<int32_t>(steps.size()), 2215 static_cast<int32_t>(inputs.size()), 2216 static_cast<int32_t>(outputs.size())})); 2217 2218 // Parse the body. 2219 Region *body = result.addRegion(); 2220 2221 SmallVector<Type, 4> region_types(ivs.size(), builder.getIndexType()); 2222 region_types.append(inputTypes); 2223 region_types.append(outputTypes); 2224 2225 SmallVector<OpAsmParser::OperandType, 4> region_args(ivs); 2226 region_args.append(input_region_args); 2227 region_args.append(output_region_args); 2228 2229 if (parser.parseRegion(*body, region_args, region_types)) 2230 return failure(); 2231 2232 // Parse optional attributes. 2233 parser.parseOptionalAttrDict(result.attributes); 2234 2235 return success(); 2236 } 2237 2238 Region &TiledLoopOp::getLoopBody() { return region(); } 2239 2240 LogicalResult TiledLoopOp::moveOutOfLoop(ArrayRef<Operation *> ops) { 2241 for (auto *op : ops) 2242 op->moveBefore(*this); 2243 return success(); 2244 } 2245 2246 bool TiledLoopOp::isDefinedOutsideOfLoop(Value value) { 2247 return !region().isAncestor(value.getParentRegion()); 2248 } 2249 2250 static LogicalResult verify(TiledLoopOp op) { 2251 // Check if iterator types are provided for every loop dimension. 2252 if (op.iterator_types().size() != op.getNumLoops()) 2253 return op.emitOpError("expected iterator types array attribute size = ") 2254 << op.iterator_types().size() 2255 << " to match the number of loops = " << op.getNumLoops(); 2256 2257 // Check if types of input arguments match region args types. 2258 for (auto &item : 2259 llvm::enumerate(llvm::zip(op.inputs(), op.getRegionInputArgs()))) { 2260 Value input, inputRegionArg; 2261 unsigned index = item.index(); 2262 std::tie(input, inputRegionArg) = item.value(); 2263 if (input.getType() != inputRegionArg.getType()) 2264 return op.emitOpError("expected input arg ") 2265 << index << " with type = " << input.getType() 2266 << " to match region arg " << index + op.getNumLoops() 2267 << " type = " << inputRegionArg.getType(); 2268 } 2269 2270 // Check if types of input arguments match region args types. 2271 for (auto &item : 2272 llvm::enumerate(llvm::zip(op.outputs(), op.getRegionOutputArgs()))) { 2273 Value output, outputRegionArg; 2274 unsigned index = item.index(); 2275 std::tie(output, outputRegionArg) = item.value(); 2276 if (output.getType() != outputRegionArg.getType()) 2277 return op.emitOpError("expected output arg ") 2278 << index << " with type = " << output.getType() 2279 << " to match region arg " 2280 << index + op.getNumLoops() + op.inputs().size() 2281 << " type = " << outputRegionArg.getType(); 2282 } 2283 return success(); 2284 } 2285 2286 namespace { 2287 2288 static constexpr int64_t kNoMatch = -1; 2289 2290 // Folds away TiledLoopOp inputs if they have no uses within the body. 2291 // 2292 // Example: 2293 // 2294 // %0 = linalg.tiled_loop ... ins (%in_ = %in: tensor<...>, 2295 // %in_buf_ = %in_buf: memref<...>) {...} 2296 // Becomes 2297 // 2298 // linalg.tiled_loop ... ins (%in_buf_ = %in_buf: memref<...>) {...} 2299 struct TiledLoopInputsFolder : public OpRewritePattern<linalg::TiledLoopOp> { 2300 using OpRewritePattern<linalg::TiledLoopOp>::OpRewritePattern; 2301 2302 LogicalResult matchAndRewrite(linalg::TiledLoopOp tiledLoop, 2303 PatternRewriter &rewriter) const final { 2304 SmallVector<Value, 2> newInputs, regionInputTensorArgs; 2305 // Store ids of the corresponding old and new input operands. 2306 SmallVector<int64_t, 2> oldInputIdToNew(tiledLoop.inputs().size(), 2307 kNoMatch); 2308 for (auto en : llvm::enumerate( 2309 llvm::zip(tiledLoop.inputs(), tiledLoop.getRegionInputArgs()))) { 2310 Value in, bbArg; 2311 size_t index = en.index(); 2312 std::tie(in, bbArg) = en.value(); 2313 if (!bbArg.use_empty()) { 2314 oldInputIdToNew[index] = newInputs.size(); 2315 newInputs.push_back(in); 2316 } 2317 } 2318 if (newInputs.size() == tiledLoop.inputs().size()) 2319 return failure(); 2320 Location loc = tiledLoop.getLoc(); 2321 auto newTiledLoop = rewriter.create<TiledLoopOp>( 2322 loc, tiledLoop.lowerBound(), tiledLoop.upperBound(), tiledLoop.step(), 2323 newInputs, tiledLoop.outputs(), tiledLoop.iterator_types()); 2324 2325 // Clone the region. 2326 BlockAndValueMapping bvm; 2327 bvm.map(tiledLoop.getInductionVars(), newTiledLoop.getInductionVars()); 2328 bvm.map(tiledLoop.getRegionOutputArgs(), 2329 newTiledLoop.getRegionOutputArgs()); 2330 for (const auto &en : llvm::enumerate(oldInputIdToNew)) 2331 if (en.value() != kNoMatch) 2332 bvm.map(tiledLoop.getRegionInputArgs()[en.index()], 2333 newTiledLoop.getRegionInputArgs()[en.value()]); 2334 OpBuilder innerBuilder = 2335 OpBuilder::atBlockEnd(newTiledLoop.getBody(), rewriter.getListener()); 2336 for (auto &op : *tiledLoop.getBody()) 2337 innerBuilder.clone(op, bvm); 2338 rewriter.replaceOp(tiledLoop, newTiledLoop.getResults()); 2339 2340 return success(); 2341 } 2342 }; 2343 2344 // Folds away TiledLoopOp output tensors when the following conditions are met: 2345 // * result of `linalg.tiled_loop` has no uses 2346 // * output tensor is the argument of `linalg.yield` 2347 // 2348 // Example: 2349 // 2350 // %0 = linalg.tiled_loop ... outs (%o_ = %out: tensor<...>, 2351 // %obuf_ = %out_buf: memref<...>) { 2352 // ... 2353 // linalg.yield %o_ : tensor ... 2354 // } 2355 // 2356 // Becomes 2357 // 2358 // linalg.tiled_loop ... outs (%obuf_ = %out_buf: memref<...>) { 2359 // ... 2360 // linalg.yield 2361 // } 2362 struct TiledLoopResultsFolder : public OpRewritePattern<linalg::TiledLoopOp> { 2363 using OpRewritePattern<linalg::TiledLoopOp>::OpRewritePattern; 2364 2365 LogicalResult matchAndRewrite(linalg::TiledLoopOp tiledLoop, 2366 PatternRewriter &rewriter) const final { 2367 if (tiledLoop.getNumResults() == 0) 2368 return failure(); 2369 2370 Block *block = tiledLoop.getBody(); 2371 auto yieldOp = cast<linalg::YieldOp>(block->getTerminator()); 2372 2373 // Match the pattern and collect output buffers that will replace the output 2374 // tensors and also the ops that will be ignored when cloning the body. 2375 SmallVector<Value, 2> newOutputOperands, newYieldArgs; 2376 int resultId = 0; 2377 // Store ids of the corresponding old and new output operands. 2378 SmallVector<int64_t, 2> oldOutputIdToNew(tiledLoop.outputs().size(), 2379 kNoMatch); 2380 // Store ids of the corresponding old and new results. 2381 SmallVector<int64_t, 2> oldResultIdToNew(tiledLoop.getNumResults(), 2382 kNoMatch); 2383 SmallVector<Value, 2> resultReplacement(tiledLoop.getNumResults()); 2384 for (auto en : llvm::enumerate( 2385 llvm::zip(tiledLoop.outputs(), tiledLoop.getRegionOutputArgs()))) { 2386 size_t index = en.index(); 2387 Value out = std::get<0>(en.value()); 2388 Value outRegionArg = std::get<1>(en.value()); 2389 2390 if (!out.getType().isa<RankedTensorType>()) { 2391 oldOutputIdToNew[index] = newOutputOperands.size(); 2392 newOutputOperands.push_back(out); 2393 continue; 2394 } 2395 Value result = tiledLoop.getResult(resultId); 2396 Value yieldArg = yieldOp.getOperand(resultId); 2397 if (yieldArg != outRegionArg || !result.use_empty()) { 2398 oldOutputIdToNew[index] = newOutputOperands.size(); 2399 oldResultIdToNew[resultId] = newYieldArgs.size(); 2400 resultReplacement[resultId] = out; 2401 newOutputOperands.push_back(out); 2402 newYieldArgs.push_back(yieldArg); 2403 } 2404 ++resultId; 2405 } 2406 if (newOutputOperands.size() == tiledLoop.outputs().size()) 2407 return failure(); 2408 2409 Location loc = tiledLoop.getLoc(); 2410 auto newTiledLoop = rewriter.create<TiledLoopOp>( 2411 loc, tiledLoop.lowerBound(), tiledLoop.upperBound(), tiledLoop.step(), 2412 tiledLoop.inputs(), newOutputOperands, tiledLoop.iterator_types()); 2413 2414 // Clone the region. 2415 BlockAndValueMapping bvm; 2416 bvm.map(tiledLoop.getInductionVars(), newTiledLoop.getInductionVars()); 2417 bvm.map(tiledLoop.getRegionInputArgs(), newTiledLoop.getRegionInputArgs()); 2418 for (const auto &en : llvm::enumerate(oldOutputIdToNew)) { 2419 if (en.value() != kNoMatch) 2420 bvm.map(tiledLoop.getRegionOutputArgs()[en.index()], 2421 newTiledLoop.getRegionOutputArgs()[en.value()]); 2422 else 2423 bvm.map(tiledLoop.getRegionOutputArgs()[en.index()], 2424 tiledLoop.outputs()[en.index()]); 2425 } 2426 OpBuilder innerBuilder = 2427 OpBuilder::atBlockEnd(newTiledLoop.getBody(), rewriter.getListener()); 2428 for (auto &op : tiledLoop.getBody()->without_terminator()) 2429 innerBuilder.clone(op, bvm); 2430 innerBuilder.create<linalg::YieldOp>( 2431 loc, llvm::to_vector<2>(llvm::map_range( 2432 newYieldArgs, [&](Value arg) { return bvm.lookup(arg); }))); 2433 2434 for (const auto &en : llvm::enumerate(oldResultIdToNew)) 2435 if (en.value() != kNoMatch) 2436 resultReplacement[en.index()] = newTiledLoop.getResult(en.value()); 2437 rewriter.replaceOp(tiledLoop, resultReplacement); 2438 2439 return success(); 2440 } 2441 }; 2442 } // namespace 2443 2444 void TiledLoopOp::getCanonicalizationPatterns(OwningRewritePatternList &results, 2445 MLIRContext *context) { 2446 results.insert<TiledLoopInputsFolder, TiledLoopResultsFolder>(context); 2447 } 2448 2449 LogicalResult TiledLoopOp::fold(ArrayRef<Attribute>, 2450 SmallVectorImpl<OpFoldResult> &) { 2451 return foldMemRefCastInTiledLoopOp(*this); 2452 } 2453 2454 //===----------------------------------------------------------------------===// 2455 // IndexOp 2456 //===----------------------------------------------------------------------===// 2457 2458 static LogicalResult verify(IndexOp op) { 2459 auto linalgOp = dyn_cast<LinalgOp>(op->getParentOp()); 2460 if (!linalgOp) 2461 return op.emitOpError("expected parent op with LinalgOp interface"); 2462 if (linalgOp.getNumLoops() <= op.dim()) 2463 return op.emitOpError("expected dim (") 2464 << op.dim() << ") to be lower than the number of loops (" 2465 << linalgOp.getNumLoops() << ") of the enclosing LinalgOp"; 2466 return success(); 2467 } 2468 2469 /////// Operations corresponding to library calls defined with Tablegen //////// 2470 2471 template <typename LinalgPoolingOp> 2472 static LogicalResult verifyStrideOrDilation(LinalgPoolingOp op, 2473 ArrayRef<Attribute> attrs, 2474 bool isStride) { 2475 auto strideOrDilation = isStride ? "stride" : "dilation"; 2476 if (attrs.size() != op.getNumWindowLoops()) 2477 return op.emitOpError("expects num ") 2478 << strideOrDilation 2479 << "s equal to number of window dimensions: " << attrs.size() 2480 << " vs " << op.getNumWindowLoops(); 2481 return success(); 2482 } 2483 2484 void ConvOp::getEffects( 2485 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>> 2486 &effects) { 2487 effects.emplace_back(MemoryEffects::Read::get(), input(), 2488 SideEffects::DefaultResource::get()); 2489 effects.emplace_back(MemoryEffects::Read::get(), filter(), 2490 SideEffects::DefaultResource::get()); 2491 effects.emplace_back(MemoryEffects::Write::get(), output(), 2492 SideEffects::DefaultResource::get()); 2493 } 2494 2495 static LogicalResult verify(ConvOp op) { 2496 auto oType = op.output().getType().cast<MemRefType>(); 2497 auto fType = op.filter().getType().cast<MemRefType>(); 2498 auto iType = op.input().getType().cast<MemRefType>(); 2499 if (oType.getElementType() != iType.getElementType() || 2500 oType.getElementType() != fType.getElementType()) 2501 return op.emitOpError("expects memref elemental types to match"); 2502 if (oType.getRank() != iType.getRank() || oType.getRank() != fType.getRank()) 2503 return op.emitOpError("expects memref ranks to match"); 2504 if (auto strides = op.strides()) { 2505 if (failed(verifyStrideOrDilation(op, strides->getValue(), 2506 /*isStride=*/true))) 2507 return failure(); 2508 } 2509 if (auto dilations = op.dilations()) { 2510 if (failed(verifyStrideOrDilation(op, dilations->getValue(), 2511 /*isStride=*/false))) 2512 return failure(); 2513 } 2514 return success(); 2515 } 2516 2517 template <typename PoolingOp> 2518 static LogicalResult verifySingleInputPoolingOp(PoolingOp op) { 2519 auto inputType = op.input().getType().template cast<MemRefType>(); 2520 auto outputType = op.output().getType().template cast<MemRefType>(); 2521 if (outputType.getElementType() != inputType.getElementType()) 2522 return op.emitOpError("expects memref elemental types to match"); 2523 2524 auto windowDimsType = op.windowDims().getType().template cast<MemRefType>(); 2525 if (outputType.getRank() != inputType.getRank() || 2526 outputType.getRank() != windowDimsType.getRank()) 2527 return op.emitOpError("expects memref ranks to match"); 2528 2529 if (auto strides = op.strides()) { 2530 if (failed(verifyStrideOrDilation(op, strides->getValue(), 2531 /*isStride=*/true))) 2532 return failure(); 2533 } 2534 if (auto dilations = op.dilations()) { 2535 if (failed(verifyStrideOrDilation(op, dilations->getValue(), 2536 /*isStride=*/false))) 2537 return failure(); 2538 } 2539 return success(); 2540 } 2541 2542 #define DEFINE_POOLING_OP_GET_EFFECTS(OP_NAME) \ 2543 void OP_NAME::getEffects( \ 2544 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>> \ 2545 &effects) { \ 2546 effects.emplace_back(MemoryEffects::Read::get(), input(), \ 2547 SideEffects::DefaultResource::get()); \ 2548 effects.emplace_back(MemoryEffects::Write::get(), output(), \ 2549 SideEffects::DefaultResource::get()); \ 2550 } 2551 2552 static LogicalResult verify(PoolingMaxOp op) { 2553 return verifySingleInputPoolingOp(op); 2554 } 2555 static LogicalResult verify(PoolingMinOp op) { 2556 return verifySingleInputPoolingOp(op); 2557 } 2558 static LogicalResult verify(PoolingSumOp op) { 2559 return verifySingleInputPoolingOp(op); 2560 } 2561 2562 DEFINE_POOLING_OP_GET_EFFECTS(PoolingMaxOp) 2563 DEFINE_POOLING_OP_GET_EFFECTS(PoolingMinOp) 2564 DEFINE_POOLING_OP_GET_EFFECTS(PoolingSumOp) 2565 2566 namespace { 2567 struct EraseDeadLinalgOp; 2568 struct FoldTensorCastOp; 2569 } // namespace 2570 2571 #include "mlir/Dialect/Linalg/IR/LinalgNamedStructuredOps.tcgen.cpp.inc" 2572 #include "mlir/Dialect/Linalg/IR/LinalgNamedStructuredOps.yamlgen.cpp.inc" 2573 2574 #define GET_OP_CLASSES 2575 #include "mlir/Dialect/Linalg/IR/LinalgOps.cpp.inc" 2576 2577 #define GET_OP_CLASSES 2578 #include "mlir/Dialect/Linalg/IR/LinalgStructuredOps.cpp.inc" 2579 2580 /// Return the dims that are `iteratorTypeName` loops in the LinalgOp `op`. 2581 /// Assumes `op` is a LinalgOp. 2582 void mlir::linalg::getDimsOfType(Operation *op, StringRef iteratorTypeName, 2583 SmallVectorImpl<AffineExpr> &res) { 2584 if (!cast<LinalgOp>(op).iterator_types()) 2585 return; 2586 2587 unsigned dim = 0; 2588 MLIRContext *ctx = op->getContext(); 2589 for (auto tn : 2590 cast<LinalgOp>(op).iterator_types().getAsValueRange<StringAttr>()) { 2591 if (tn == iteratorTypeName) 2592 res.push_back(getAffineDimExpr(dim, ctx)); 2593 ++dim; 2594 } 2595 } 2596 2597 AffineMap mlir::linalg::extractOrIdentityMap(Optional<AffineMap> maybeMap, 2598 unsigned rank, 2599 MLIRContext *context) { 2600 if (maybeMap) 2601 return maybeMap.getValue(); 2602 if (rank == 0) 2603 return AffineMap::get(context); 2604 return AffineMap::getMultiDimIdentityMap(rank, context); 2605 } 2606 2607 SmallVector<AffineExpr, 4> 2608 mlir::linalg::makeAffineDimExprs(unsigned num, unsigned &startIdx, 2609 MLIRContext *context) { 2610 SmallVector<AffineExpr, 4> res; 2611 res.reserve(num); 2612 for (unsigned i = 0; i < num; ++i) 2613 res.push_back(getAffineDimExpr(startIdx++, context)); 2614 return res; 2615 } 2616 2617 template <typename PoolingOp> 2618 SmallVector<AffineExpr, 4> 2619 mlir::linalg::weightedPoolingInputIndex(PoolingOp op, 2620 ArrayRef<AffineExpr> outputDims, 2621 ArrayRef<AffineExpr> windowDims) { 2622 assert(outputDims.size() == windowDims.size()); 2623 SmallVector<AffineExpr, 4> res; 2624 res.reserve(outputDims.size()); 2625 for (unsigned i = 0, e = outputDims.size(); i < e; ++i) { 2626 // TODO: add a level of indirection to linalg.generic. 2627 auto expr = op.getStride(i) * outputDims[i] + 2628 op.getDilation(i) * windowDims[i] - op.getLowPad(i); 2629 res.push_back(expr); 2630 } 2631 return res; 2632 } 2633 2634 #define INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(OP_TYPE) \ 2635 template SmallVector<AffineExpr, 4> \ 2636 mlir::linalg::weightedPoolingInputIndex<OP_TYPE>( \ 2637 OP_TYPE op, ArrayRef<AffineExpr> outputDims, \ 2638 ArrayRef<AffineExpr> windowDims); 2639 2640 INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(ConvOp) 2641 INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(PoolingMaxOp) 2642 INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(PoolingMinOp) 2643 INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(PoolingSumOp) 2644 2645 SmallVector<AffineExpr, 4> mlir::linalg::concat(ArrayRef<AffineExpr> a, 2646 ArrayRef<AffineExpr> b) { 2647 auto rangeA = llvm::make_range(a.begin(), a.end()); 2648 auto rangeB = llvm::make_range(b.begin(), b.end()); 2649 auto concatRanges = llvm::concat<const AffineExpr>(rangeA, rangeB); 2650 return llvm::to_vector<4>(concatRanges); 2651 } 2652 2653 static void appendMangledType(llvm::raw_string_ostream &ss, Type t) { 2654 if (auto memref = t.dyn_cast<MemRefType>()) { 2655 ss << "view"; 2656 for (auto size : memref.getShape()) 2657 if (size < 0) 2658 ss << "sx"; 2659 else 2660 ss << size << "x"; 2661 appendMangledType(ss, memref.getElementType()); 2662 } else if (auto vec = t.dyn_cast<VectorType>()) { 2663 ss << "vector"; 2664 llvm::interleave( 2665 vec.getShape(), [&](int64_t i) { ss << i; }, [&]() { ss << "x"; }); 2666 appendMangledType(ss, vec.getElementType()); 2667 } else if (t.isSignlessIntOrIndexOrFloat()) { 2668 ss << t; 2669 } else { 2670 llvm_unreachable("Invalid type for linalg library name mangling"); 2671 } 2672 } 2673 2674 std::string mlir::linalg::generateLibraryCallName(Operation *op) { 2675 assert(isa<LinalgOp>(op)); 2676 std::string name(op->getName().getStringRef().str()); 2677 name.reserve(128); 2678 std::replace(name.begin(), name.end(), '.', '_'); 2679 llvm::raw_string_ostream ss(name); 2680 ss << "_"; 2681 auto types = op->getOperandTypes(); 2682 llvm::interleave( 2683 types.begin(), types.end(), [&](Type t) { appendMangledType(ss, t); }, 2684 [&]() { ss << "_"; }); 2685 return ss.str(); 2686 } 2687 2688 // TODO: Consider making all this boilerplate easy to autogenerate 2689 // with Tablegen. This seems a desirable property in the context of 2690 // OpInterfaces where a Linalg "named" op **isa** LinalgOp. 2691 OpFoldResult ReshapeOp::fold(ArrayRef<Attribute> operands) { 2692 if (succeeded(foldMemRefCast(*this))) 2693 return getResult(); 2694 return foldReshapeOp(*this, operands); 2695 } 2696 OpFoldResult TensorReshapeOp::fold(ArrayRef<Attribute> operands) { 2697 return foldReshapeOp(*this, operands); 2698 } 2699 2700 //===----------------------------------------------------------------------===// 2701 // Support for named Linalg ops defined in ods-gen. 2702 //===----------------------------------------------------------------------===// 2703 2704 /// Generic entry point to create the block for the region of a LinalgOp. 2705 /// This is used by both named structured ops created by ods-gen and by manually 2706 /// defined C++ ops. 2707 /// This is used by both builders and parsers. 2708 /// This function creates the block in the region with arguments corresponding 2709 /// to the elemental types of `inputTypes` and `outputTypes`, which are asserted 2710 /// to be ShapedType. 2711 template <typename NamedStructuredOpType> 2712 static void 2713 fillStructuredOpRegion(OpBuilder &opBuilder, Region ®ion, 2714 TypeRange inputTypes, TypeRange outputTypes, 2715 ValueRange captures, 2716 std::function<void(unsigned, unsigned)> errorHandler) { 2717 assert(llvm::all_of(inputTypes, [](Type t) { return t.isa<ShapedType>(); })); 2718 assert(llvm::all_of(outputTypes, [](Type t) { return t.isa<ShapedType>(); })); 2719 2720 // TODO: atm all operands go through getElementTypeOrSelf, 2721 // reconsider when we have evidence we need to. 2722 SmallVector<Type, 8> argTypes; 2723 for (auto containers : {inputTypes, outputTypes}) 2724 for (auto t : containers) 2725 argTypes.push_back(getElementTypeOrSelf(t)); 2726 2727 // RAII. 2728 OpBuilder::InsertionGuard guard(opBuilder); 2729 Block *body = opBuilder.createBlock(®ion, /*insertPt=*/{}, argTypes); 2730 unsigned actual = body->getNumArguments(); 2731 unsigned expected = NamedStructuredOpType::getNumRegionArgs(); 2732 if (expected != actual) { 2733 if (errorHandler) 2734 errorHandler(expected, actual); 2735 return; 2736 } 2737 2738 opBuilder.setInsertionPointToStart(body); 2739 mlir::edsc::ScopedContext scope(opBuilder, opBuilder.getUnknownLoc()); 2740 NamedStructuredOpType::regionBuilder(*body, captures); 2741 2742 // indexing_maps is an auto-generated method. 2743 2744 // iterator_types is an auto-generated method. 2745 } 2746 2747 /// Generic entry point to create both the region and the block of a LinalgOp. 2748 template <typename NamedStructuredOpType> 2749 void createAndFillStructuredOpRegion(OpBuilder &opBuilder, 2750 OperationState &result, 2751 TypeRange inputTypes, 2752 TypeRange outputTypes, 2753 ValueRange captures) { 2754 Region ®ion = *result.addRegion(); 2755 fillStructuredOpRegion<NamedStructuredOpType>( 2756 opBuilder, region, inputTypes, outputTypes, captures, 2757 [&](unsigned expected, unsigned actual) { 2758 assert(expected != actual && "incorrect number of arguments"); 2759 }); 2760 } 2761 2762 /// Common parsing used for both named structured ops created by ods-gen and by 2763 /// manually defined C++ ops. Does not handle regions. 2764 static ParseResult 2765 parseCommonStructuredOpParts(OpAsmParser &parser, OperationState &result, 2766 SmallVectorImpl<Type> &inputTypes, 2767 SmallVectorImpl<Type> &outputTypes) { 2768 llvm::SMLoc inputsOperandsLoc, outputsOperandsLoc; 2769 SmallVector<OpAsmParser::OperandType, 4> inputsOperands, outputsOperands; 2770 2771 parser.parseOptionalAttrDict(result.attributes); 2772 2773 if (succeeded(parser.parseOptionalKeyword("ins"))) { 2774 if (parser.parseLParen()) 2775 return failure(); 2776 2777 inputsOperandsLoc = parser.getCurrentLocation(); 2778 if (parser.parseOperandList(inputsOperands) || 2779 parser.parseColonTypeList(inputTypes) || parser.parseRParen()) 2780 return failure(); 2781 } 2782 2783 if (succeeded(parser.parseOptionalKeyword("outs"))) { 2784 outputsOperandsLoc = parser.getCurrentLocation(); 2785 if (parser.parseLParen() || parser.parseOperandList(outputsOperands) || 2786 parser.parseColonTypeList(outputTypes) || parser.parseRParen()) 2787 return failure(); 2788 } 2789 2790 if (parser.resolveOperands(inputsOperands, inputTypes, inputsOperandsLoc, 2791 result.operands) || 2792 parser.resolveOperands(outputsOperands, outputTypes, outputsOperandsLoc, 2793 result.operands)) 2794 return failure(); 2795 2796 result.addAttribute("operand_segment_sizes", 2797 parser.getBuilder().getI32VectorAttr( 2798 {static_cast<int32_t>(inputsOperands.size()), 2799 static_cast<int32_t>(outputsOperands.size())})); 2800 return success(); 2801 } 2802 2803 template <typename NamedStructuredOpType> 2804 static void printCommonStructuredOpParts(OpAsmPrinter &p, 2805 NamedStructuredOpType op) { 2806 if (!op.inputs().empty()) 2807 p << " ins(" << op.inputs() << " : " << op.inputs().getTypes() << ")"; 2808 if (!op.outputs().empty()) 2809 p << " outs(" << op.outputs() << " : " << op.outputs().getTypes() << ")"; 2810 } 2811 2812 //===----------------------------------------------------------------------===// 2813 // Specific parsing and printing for named structured ops created by ods-gen. 2814 //===----------------------------------------------------------------------===// 2815 2816 template <typename NamedStructuredOpType> 2817 static ParseResult 2818 parseNamedStructuredOpRegion(OpAsmParser &parser, Region ®ion, 2819 TypeRange inputTypes, TypeRange outputTypes, 2820 ArrayRef<OpAsmParser::OperandType> captures) { 2821 ParseResult res = success(); 2822 OpBuilder opBuilder(parser.getBuilder().getContext()); 2823 // Resolve `captures` into `capturedValues` at parse time so we can build the 2824 // region with captures. 2825 SmallVector<Value> capturedValues; 2826 fillStructuredOpRegion<NamedStructuredOpType>( 2827 opBuilder, region, inputTypes, outputTypes, capturedValues, 2828 [&](unsigned expected, unsigned actual) { 2829 res = parser.emitError( 2830 parser.getCurrentLocation(), 2831 llvm::formatv("[parseNamedStructuredOpRegion] ods-gen generated " 2832 "region expects {0} args, got {1}", 2833 expected, actual)); 2834 region.front().dump(); 2835 }); 2836 return res; 2837 } 2838 2839 static ParseResult 2840 parseNamedStructuredOpResults(OpAsmParser &parser, 2841 SmallVectorImpl<Type> &resultTypes) { 2842 if (succeeded(parser.parseOptionalArrow())) 2843 if (parser.parseTypeList(resultTypes)) 2844 return failure(); 2845 return success(); 2846 } 2847 2848 template <typename NamedStructuredOpType> 2849 static ParseResult 2850 parseNamedStructuredOp(OpAsmParser &parser, OperationState &result, 2851 ArrayRef<OpAsmParser::OperandType> captures) { 2852 // TODO: Enable when ods-gen supports captures. 2853 assert(captures.empty() && "unexpected captures for named structured ops"); 2854 SmallVector<Type, 1> inputTypes, outputTypes; 2855 if (parseCommonStructuredOpParts(parser, result, inputTypes, outputTypes)) 2856 return failure(); 2857 2858 // TODO: consider merging results parsing into region parsing. 2859 // Need to wait for declarative assembly resolution to decide. 2860 SmallVector<Type, 1> outputTensorsTypes; 2861 if (parseNamedStructuredOpResults(parser, outputTensorsTypes)) 2862 return failure(); 2863 result.addTypes(outputTensorsTypes); 2864 2865 std::unique_ptr<Region> region = std::make_unique<Region>(); 2866 if (parseNamedStructuredOpRegion<NamedStructuredOpType>( 2867 parser, *region, inputTypes, outputTypes, captures)) 2868 return failure(); 2869 result.addRegion(std::move(region)); 2870 2871 return success(); 2872 } 2873 2874 static void printNamedStructuredOpResults(OpAsmPrinter &p, 2875 TypeRange resultTypes) { 2876 if (resultTypes.empty()) 2877 return; 2878 p.printOptionalArrowTypeList(resultTypes); 2879 } 2880 2881 template <typename NamedStructuredOpType> 2882 static void printNamedStructuredOp(OpAsmPrinter &p, NamedStructuredOpType op) { 2883 p << op.getOperationName(); 2884 p.printOptionalAttrDict( 2885 op->getAttrs(), 2886 /*elidedAttrs=*/{"operand_segment_sizes", 2887 // See generated code in mlir-linalg-yaml-gen.cpp 2888 "linalg.memoized_indexing_maps"}); 2889 2890 // Printing is shared with generic ops, except for the region and 2891 // attributes. 2892 printCommonStructuredOpParts(p, op); 2893 2894 // Results printing. 2895 printNamedStructuredOpResults(p, op.result_tensors().getTypes()); 2896 2897 // Region is elided. 2898 } 2899 2900 template <typename NamedStructuredOpType> 2901 static LogicalResult verifyNamedStructuredOp(NamedStructuredOpType op) { 2902 return verifyGenericOp<NamedStructuredOpType>(op); 2903 } 2904 2905 //===----------------------------------------------------------------------===// 2906 // Canonicalizers and Folders. 2907 //===----------------------------------------------------------------------===// 2908 2909 namespace { 2910 struct EraseDeadLinalgOp : public OpInterfaceRewritePattern<LinalgOp> { 2911 using OpInterfaceRewritePattern<LinalgOp>::OpInterfaceRewritePattern; 2912 2913 LogicalResult matchAndRewrite(LinalgOp op, 2914 PatternRewriter &rewriter) const override { 2915 for (Value v : op.getShapedOperands()) { 2916 // Linalg "inputs" may be either tensor or memref type. 2917 // tensor<0xelt_type> is a convention that may not always mean 2918 // "0 iterations". Only erase in cases we see memref<...x0x...>. 2919 auto mt = v.getType().dyn_cast<MemRefType>(); 2920 if (!mt) 2921 continue; 2922 if (llvm::is_contained(mt.getShape(), 0)) { 2923 rewriter.eraseOp(op); 2924 return success(); 2925 } 2926 } 2927 return failure(); 2928 } 2929 }; 2930 2931 struct FoldTensorCastOp : public OpInterfaceRewritePattern<LinalgOp> { 2932 using OpInterfaceRewritePattern<LinalgOp>::OpInterfaceRewritePattern; 2933 2934 LogicalResult matchAndRewrite(LinalgOp op, 2935 PatternRewriter &rewriter) const override { 2936 // If no operand comes from a tensor::CastOp and can be folded then fail. 2937 bool hasTensorCastOperand = 2938 llvm::any_of(op.getShapedOperands(), [&](Value v) { 2939 if (v.isa<BlockArgument>()) 2940 return false; 2941 auto castOp = v.getDefiningOp<tensor::CastOp>(); 2942 return castOp && canFoldIntoConsumerOp(castOp); 2943 }); 2944 if (!hasTensorCastOperand) 2945 return failure(); 2946 2947 SmallVector<Type, 4> newResultTypes; 2948 newResultTypes.reserve(op->getNumResults()); 2949 SmallVector<Value, 4> newOperands; 2950 newOperands.reserve(op->getNumOperands()); 2951 // Inputs may fold. 2952 for (Value v : op.getInputs()) { 2953 auto tensorCastOp = v.getDefiningOp<tensor::CastOp>(); 2954 newOperands.push_back( 2955 canFoldIntoConsumerOp(tensorCastOp) ? tensorCastOp.source() : v); 2956 } 2957 // Init tensors may fold, in which case the resultType must also change. 2958 for (Value v : op.getOutputs()) { 2959 auto tensorCastOp = v.getDefiningOp<tensor::CastOp>(); 2960 bool fold = canFoldIntoConsumerOp(tensorCastOp); 2961 newOperands.push_back(fold ? tensorCastOp.getOperand() : v); 2962 newResultTypes.push_back(newOperands.back().getType()); 2963 } 2964 auto extraOperands = op.getAssumedNonShapedOperands(); 2965 newOperands.append(extraOperands.begin(), extraOperands.end()); 2966 // Clone op. 2967 Operation *newOp = 2968 op.clone(rewriter, op->getLoc(), newResultTypes, newOperands); 2969 SmallVector<Value, 4> replacements; 2970 replacements.reserve(newOp->getNumResults()); 2971 for (auto result : llvm::zip(op->getResults(), newOp->getResults())) { 2972 Value oldResult = std::get<0>(result); 2973 Value newResult = std::get<1>(result); 2974 if (newResult.getType() != oldResult.getType()) { 2975 replacements.push_back(rewriter.create<tensor::CastOp>( 2976 op->getLoc(), oldResult.getType(), newResult)); 2977 } else { 2978 replacements.push_back(newResult); 2979 } 2980 } 2981 rewriter.replaceOp(op, replacements); 2982 2983 return success(); 2984 } 2985 }; 2986 } // namespace 2987 2988 namespace { 2989 // Deduplicate redundant args of a linalg op. 2990 // An arg is redundant if it has the same Value and indexing map as another. 2991 struct DeduplicateInputs : public OpInterfaceRewritePattern<LinalgOp> { 2992 using OpInterfaceRewritePattern<LinalgOp>::OpInterfaceRewritePattern; 2993 2994 LogicalResult matchAndRewrite(LinalgOp op, 2995 PatternRewriter &rewriter) const override { 2996 // This pattern reduces the number of arguments of an op, which breaks 2997 // the invariants of semantically charged named ops. 2998 if (!isa<GenericOp, IndexedGenericOp>(op)) 2999 return failure(); 3000 3001 // Associate each input to an equivalent "canonical" input that has the same 3002 // Value and indexing map. 3003 // 3004 // In the non-duplicate case, input `i` will have canonical input `i`. But 3005 // in the case of duplicated inputs, the canonical input could be some other 3006 // input `< i`. That is, a later input will have some earlier input as its 3007 // canonical input. 3008 llvm::SmallDenseMap<std::pair<Value, AffineMap>, int> canonicalInput; 3009 // For later remapping tasks like deduplicating payload block arguments, 3010 // having a simple "inputIndex -> canonicalInputIndex" integer mapping is 3011 // convenient. 3012 SmallVector<int, 6> canonicalInputIndices; 3013 for (int i = 0, e = op.getNumInputs(); i != e; i++) { 3014 Value input = op.getInput(i); 3015 AffineMap indexingMap = op.getInputIndexingMap(i); 3016 // STL-like maps have a convenient behavior for our use case here. In the 3017 // case of duplicate keys, the insertion is rejected, and the returned 3018 // iterator gives access to the value already in the map. 3019 auto pair = canonicalInput.insert({{input, indexingMap}, i}); 3020 canonicalInputIndices.push_back(pair.first->second); 3021 } 3022 3023 // If there are no duplicate args, then bail out. 3024 if (canonicalInput.size() == op.getNumInputs()) 3025 return failure(); 3026 3027 // The operands for the newly canonicalized op. 3028 SmallVector<Value, 6> newOperands; 3029 for (auto v : llvm::enumerate(op.getInputs())) 3030 if (canonicalInputIndices[v.index()] == static_cast<int>(v.index())) 3031 newOperands.push_back(v.value()); 3032 llvm::append_range(newOperands, op.getOutputs()); 3033 llvm::append_range(newOperands, op.getAssumedNonShapedOperands()); 3034 3035 // Clone the old op with new operands. 3036 Operation *newOp = 3037 op.clone(rewriter, op->getLoc(), op->getResultTypes(), newOperands); 3038 auto newLinalgOp = cast<LinalgOp>(newOp); 3039 3040 // Repair the indexing maps by filtering out the ones that have been 3041 // eliminated. 3042 SmallVector<AffineMap, 6> newIndexingMaps; 3043 for (int i = 0, e = newLinalgOp.getNumInputs(); i != e; i++) 3044 if (canonicalInputIndices[i] == i) 3045 newIndexingMaps.push_back(newLinalgOp.getIndexingMap(i)); 3046 for (int i = 0, e = newLinalgOp.getNumOutputs(); i != e; i++) 3047 newIndexingMaps.push_back(newLinalgOp.getOutputIndexingMap(i)); 3048 newOp->setAttr("indexing_maps", 3049 rewriter.getAffineMapArrayAttr(newIndexingMaps)); 3050 3051 // Set the number of inputs to the new value. The `clone` call above kept 3052 // the value from the original op. 3053 newLinalgOp.setNumInputs(canonicalInput.size()); 3054 3055 // linalg.indexed_generic payloads have additional arguments prepended to 3056 // the block arg list. 3057 int bbArgBaseOffset = newLinalgOp.getNumPayloadInductionVariables(); 3058 3059 // Repair the payload entry block by RAUW'ing redundant arguments and 3060 // erasing them. 3061 Block &payload = newOp->getRegion(0).front(); 3062 for (int i = 0, e = op.getNumInputs(); i < e; i++) { 3063 // Iterate in reverse, so that we erase later args first, preventing the 3064 // argument list from shifting unexpectedly and invalidating all our 3065 // indices. 3066 int reversed = e - i - 1; 3067 int canonicalIndex = canonicalInputIndices[reversed]; 3068 if (canonicalInputIndices[reversed] == reversed) 3069 continue; 3070 payload.getArgument(bbArgBaseOffset + reversed) 3071 .replaceAllUsesWith( 3072 payload.getArgument(bbArgBaseOffset + canonicalIndex)); 3073 payload.eraseArgument(bbArgBaseOffset + reversed); 3074 } 3075 3076 rewriter.replaceOp(op, newOp->getResults()); 3077 return success(); 3078 } 3079 }; 3080 3081 /// Remove generic/indexed_generic operations (on tensors) that are just copying 3082 /// the values from inputs to the results. Requirements are 3083 /// 1) All iterator types are parallel 3084 /// 2) The body contains just a yield operation with the yielded values being 3085 /// the arguments corresponding to the operands. 3086 struct RemoveIdentityLinalgOps : public OpInterfaceRewritePattern<LinalgOp> { 3087 using OpInterfaceRewritePattern<LinalgOp>::OpInterfaceRewritePattern; 3088 3089 LogicalResult matchAndRewrite(LinalgOp op, 3090 PatternRewriter &rewriter) const override { 3091 if (auto copyOp = dyn_cast<CopyOp>(*op)) { 3092 assert(copyOp.hasBufferSemantics()); 3093 if (copyOp.input() == copyOp.output() && 3094 copyOp.inputPermutation() == copyOp.outputPermutation()) { 3095 rewriter.eraseOp(op); 3096 return success(); 3097 } 3098 } 3099 3100 if (!isa<GenericOp, IndexedGenericOp>(op)) 3101 return failure(); 3102 if (!op.hasTensorSemantics()) 3103 return failure(); 3104 // Check all indexing maps are identity. 3105 if (llvm::any_of(op.getIndexingMaps(), 3106 [](AffineMap map) { return !map.isIdentity(); })) 3107 return failure(); 3108 3109 // Check that the body of the linalg operation is just a linalg.yield 3110 // operation. 3111 Block &body = op->getRegion(0).front(); 3112 if (!llvm::hasSingleElement(body)) 3113 return failure(); 3114 auto yieldOp = dyn_cast<linalg::YieldOp>(body.getTerminator()); 3115 if (!yieldOp) 3116 return failure(); 3117 3118 // Get the argument number of the returned values. That is the operand 3119 // number to use for replacing uses of this operation. 3120 unsigned numIndexArgs = op.getNumPayloadInductionVariables(); 3121 SmallVector<Value, 4> returnedArgs; 3122 for (Value yieldVal : yieldOp.values()) { 3123 auto yieldArg = yieldVal.dyn_cast<BlockArgument>(); 3124 if (!yieldArg || yieldArg.getOwner() != &body) 3125 return failure(); 3126 unsigned argumentNumber = yieldArg.getArgNumber(); 3127 if (argumentNumber < numIndexArgs) 3128 return failure(); 3129 returnedArgs.push_back(op->getOperand(argumentNumber - numIndexArgs)); 3130 } 3131 if (returnedArgs.size() != op.getOperation()->getNumResults()) 3132 return failure(); 3133 rewriter.replaceOp(op, returnedArgs); 3134 return success(); 3135 } 3136 }; 3137 } // namespace 3138 3139 #define CANONICALIZERS_AND_FOLDERS(XXX) \ 3140 void XXX::getCanonicalizationPatterns(RewritePatternSet &results, \ 3141 MLIRContext *context) { \ 3142 results.add<DeduplicateInputs, EraseDeadLinalgOp, FoldTensorCastOp, \ 3143 RemoveIdentityLinalgOps>(context); \ 3144 } \ 3145 \ 3146 LogicalResult XXX::fold(ArrayRef<Attribute>, \ 3147 SmallVectorImpl<OpFoldResult> &) { \ 3148 return foldMemRefCast(*this); \ 3149 } 3150 3151 CANONICALIZERS_AND_FOLDERS(ConvOp) 3152 CANONICALIZERS_AND_FOLDERS(PoolingMaxOp) 3153 CANONICALIZERS_AND_FOLDERS(PoolingMinOp) 3154 CANONICALIZERS_AND_FOLDERS(PoolingSumOp) 3155 CANONICALIZERS_AND_FOLDERS(CopyOp) 3156 CANONICALIZERS_AND_FOLDERS(FillOp) 3157 CANONICALIZERS_AND_FOLDERS(GenericOp) 3158 3159 // All named ops canonicalizers and folders are auto-generated in the 3160 // .cpp.inc. 3161