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