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