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/Linalg.h" 14 15 #include "mlir/Dialect/Arithmetic/Utils/Utils.h" 16 #include "mlir/Dialect/SCF/SCF.h" 17 #include "mlir/Dialect/SparseTensor/IR/SparseTensor.h" 18 #include "mlir/Dialect/Utils/ReshapeOpsUtils.h" 19 #include "mlir/Dialect/Utils/StaticValueUtils.h" 20 #include "mlir/IR/AffineExprVisitor.h" 21 #include "mlir/IR/Matchers.h" 22 #include "mlir/IR/OpImplementation.h" 23 #include "mlir/IR/PatternMatch.h" 24 #include "mlir/Interfaces/InferTypeOpInterface.h" 25 #include "mlir/Parser/Parser.h" 26 27 #include "llvm/ADT/DenseMap.h" 28 #include "llvm/ADT/SetVector.h" 29 #include "llvm/ADT/SmallSet.h" 30 #include "llvm/ADT/StringSet.h" 31 #include "llvm/ADT/TypeSwitch.h" 32 #include "llvm/Support/FormatVariadic.h" 33 #include "llvm/Support/MathExtras.h" 34 #include "llvm/Support/raw_ostream.h" 35 36 using namespace mlir; 37 using namespace mlir::linalg; 38 39 /// Forward declarations. 40 41 /// Generic entry point to create the block for the region of a LinalgOp. 42 /// This is used by both named structured ops created by ods-gen and by manually 43 /// defined C++ ops. 44 /// This is used by both builders and parsers. 45 /// This function creates the block in the region with arguments corresponding 46 /// to the elemental types of `inputTypes` and `outputTypes`. The latter are 47 /// asserted to be of ShapedType. 48 template <typename NamedStructuredOpType> 49 static void fillStructuredOpRegion( 50 OpBuilder &opBuilder, Region ®ion, TypeRange inputTypes, 51 TypeRange outputTypes, ArrayRef<NamedAttribute> attrs, 52 llvm::function_ref<void(unsigned, unsigned)> errorHandler = nullptr); 53 54 /// Generic entry point to create both the region and the block of a LinalgOp. 55 template <typename NamedStructuredOpType> 56 static void 57 createAndFillStructuredOpRegion(OpBuilder &opBuilder, OperationState &result, 58 TypeRange inputTypes, TypeRange outputTypes); 59 60 /// Common parsing and printing used for both named structured ops created by 61 /// ods-gen and by manually defined C++ ops. Does not handle regions. 62 static ParseResult 63 parseCommonStructuredOpParts(OpAsmParser &parser, OperationState &result, 64 SmallVectorImpl<Type> &inputTypes, 65 SmallVectorImpl<Type> &outputTypes); 66 template <typename NamedStructuredOpType> 67 static void printCommonStructuredOpParts(OpAsmPrinter &p, 68 NamedStructuredOpType op); 69 70 /// Specific parsing and printing for named structured ops created by ods-gen. 71 template <typename NamedStructuredOpType> 72 static ParseResult 73 parseNamedStructuredOpRegion(OpAsmParser &parser, Region ®ion, 74 TypeRange inputTypes, TypeRange outputTypes, 75 ArrayRef<NamedAttribute> attrs); 76 77 static ParseResult 78 parseNamedStructuredOpResults(OpAsmParser &parser, 79 SmallVectorImpl<Type> &resultTypes); 80 81 template <typename NamedStructuredOpType> 82 static ParseResult parseNamedStructuredOp(OpAsmParser &parser, 83 OperationState &result); 84 85 static void printNamedStructuredOpResults(OpAsmPrinter &p, 86 TypeRange resultTypes); 87 88 template <typename NamedStructuredOpType> 89 static void printNamedStructuredOp(OpAsmPrinter &p, NamedStructuredOpType op); 90 91 /// This is a common class used for patterns of the form 92 /// ``` 93 /// someop(memrefcast(%src)) -> someop(%src) 94 /// ``` 95 /// It folds the source of the memref.cast into the root operation directly. 96 static LogicalResult foldMemRefCast(Operation *op) { 97 bool folded = false; 98 for (OpOperand &operand : op->getOpOperands()) { 99 auto castOp = operand.get().getDefiningOp<memref::CastOp>(); 100 if (castOp && memref::CastOp::canFoldIntoConsumerOp(castOp)) { 101 operand.set(castOp.getOperand()); 102 folded = true; 103 } 104 } 105 return success(folded); 106 } 107 108 /// Helper function to find if there is atleast one dimension in an AffineMap 109 /// testMap that is contained in `testMapLocation` of `maps` but not in any 110 /// other locations 111 static bool hasaUniqueDim(ArrayRef<AffineMap> maps, unsigned testMapLocation) { 112 AffineMap testMap = maps[testMapLocation]; 113 llvm::SmallDenseSet<unsigned> dimsToCheck; 114 for (auto result : testMap.getResults()) { 115 auto expr = result.dyn_cast<AffineDimExpr>(); 116 if (expr != nullptr) 117 dimsToCheck.insert(expr.getPosition()); 118 } 119 for (auto It : llvm::enumerate(maps)) { 120 if (It.index() == testMapLocation) 121 continue; 122 auto map = It.value(); 123 for (auto result : map.getResults()) { 124 auto expr = result.dyn_cast<AffineDimExpr>(); 125 if (expr != nullptr) { 126 dimsToCheck.erase(expr.getPosition()); 127 } 128 if (dimsToCheck.empty()) 129 return false; 130 } 131 } 132 return true; 133 } 134 135 //===----------------------------------------------------------------------===// 136 // Region builder helper. 137 // TODO: Move this to a utility library. 138 // The public methods on this class are referenced directly from generated code. 139 // Helper build the unary, binary, and type conversion functions defined by the 140 // DSL. See mlir-linalg-ods-yaml-gen.cpp for the code that uses this class. 141 // 142 // Implementations of the math functions must be polymorphic over numeric types, 143 // internally performing necessary casts. If the function application makes no 144 // sense, then the only recourse is to assert and return nullptr. This can be 145 // extended later if it becomes possible to fail construction of the region. The 146 // invariant should be enforced at a higher level. 147 // 148 // TODO: These helpers are currently type polymorphic over the class of integer 149 // and floating point types, but they will not internally cast within bit 150 // widths of a class (mixed precision such as i8->i32) or across classes 151 // (i.e. mixed float and integer). Many such combinations are ambiguous or need 152 // to be handled with care and work is being considered to extend the op 153 // language to make such cases explicit. In the mean-time, violating this will 154 // fail verification, which is deemed acceptable. 155 //===----------------------------------------------------------------------===// 156 157 namespace { 158 159 class RegionBuilderHelper { 160 public: 161 RegionBuilderHelper(MLIRContext *context, Block &block) 162 : context(context), block(block) {} 163 164 // Build the unary functions defined by OpDSL. 165 Value buildUnaryFn(UnaryFn unaryFn, Value arg) { 166 if (!isFloatingPoint(arg)) 167 llvm_unreachable("unsupported non numeric type"); 168 OpBuilder builder = getBuilder(); 169 switch (unaryFn) { 170 case UnaryFn::exp: 171 return builder.create<math::ExpOp>(arg.getLoc(), arg); 172 case UnaryFn::log: 173 return builder.create<math::LogOp>(arg.getLoc(), arg); 174 case UnaryFn::abs: 175 return builder.create<math::AbsOp>(arg.getLoc(), arg); 176 case UnaryFn::ceil: 177 return builder.create<math::CeilOp>(arg.getLoc(), arg); 178 case UnaryFn::floor: 179 return builder.create<math::FloorOp>(arg.getLoc(), arg); 180 case UnaryFn::negf: 181 return builder.create<arith::NegFOp>(arg.getLoc(), arg); 182 } 183 llvm_unreachable("unsupported unary function"); 184 } 185 186 // Build the binary functions defined by OpDSL. 187 Value buildBinaryFn(BinaryFn binaryFn, Value arg0, Value arg1) { 188 bool allFloatingPoint = isFloatingPoint(arg0) && isFloatingPoint(arg1); 189 bool allInteger = isInteger(arg0) && isInteger(arg1); 190 if (!allFloatingPoint && !allInteger) 191 llvm_unreachable("unsupported non numeric type"); 192 OpBuilder builder = getBuilder(); 193 switch (binaryFn) { 194 case BinaryFn::add: 195 if (allFloatingPoint) 196 return builder.create<arith::AddFOp>(arg0.getLoc(), arg0, arg1); 197 return builder.create<arith::AddIOp>(arg0.getLoc(), arg0, arg1); 198 case BinaryFn::sub: 199 if (allFloatingPoint) 200 return builder.create<arith::SubFOp>(arg0.getLoc(), arg0, arg1); 201 return builder.create<arith::SubIOp>(arg0.getLoc(), arg0, arg1); 202 case BinaryFn::mul: 203 if (allFloatingPoint) 204 return builder.create<arith::MulFOp>(arg0.getLoc(), arg0, arg1); 205 return builder.create<arith::MulIOp>(arg0.getLoc(), arg0, arg1); 206 case BinaryFn::max_signed: 207 if (allFloatingPoint) 208 return builder.create<arith::MaxFOp>(arg0.getLoc(), arg0, arg1); 209 return builder.create<arith::MaxSIOp>(arg0.getLoc(), arg0, arg1); 210 case BinaryFn::min_signed: 211 if (allFloatingPoint) 212 return builder.create<arith::MinFOp>(arg0.getLoc(), arg0, arg1); 213 return builder.create<arith::MinSIOp>(arg0.getLoc(), arg0, arg1); 214 case BinaryFn::max_unsigned: 215 if (allFloatingPoint) 216 return builder.create<arith::MaxFOp>(arg0.getLoc(), arg0, arg1); 217 return builder.create<arith::MaxUIOp>(arg0.getLoc(), arg0, arg1); 218 case BinaryFn::min_unsigned: 219 if (allFloatingPoint) 220 return builder.create<arith::MinFOp>(arg0.getLoc(), arg0, arg1); 221 return builder.create<arith::MinUIOp>(arg0.getLoc(), arg0, arg1); 222 } 223 llvm_unreachable("unsupported binary function"); 224 } 225 226 // Build the type functions defined by OpDSL. 227 Value buildTypeFn(TypeFn typeFn, Type toType, Value operand) { 228 switch (typeFn) { 229 case TypeFn::cast_signed: 230 return cast(toType, operand, false); 231 case TypeFn::cast_unsigned: 232 return cast(toType, operand, true); 233 } 234 llvm_unreachable("unsupported type conversion function"); 235 } 236 237 void yieldOutputs(ValueRange values) { 238 OpBuilder builder = getBuilder(); 239 Location loc = builder.getUnknownLoc(); 240 builder.create<YieldOp>(loc, values); 241 } 242 243 Value constant(const std::string &value) { 244 OpBuilder builder = getBuilder(); 245 Location loc = builder.getUnknownLoc(); 246 Attribute valueAttr = parseAttribute(value, builder.getContext()); 247 return builder.create<arith::ConstantOp>(loc, valueAttr.getType(), 248 valueAttr); 249 } 250 251 Value index(int64_t dim) { 252 OpBuilder builder = getBuilder(); 253 return builder.create<IndexOp>(builder.getUnknownLoc(), dim); 254 } 255 256 Type getIntegerType(unsigned width) { 257 return IntegerType::get(context, width); 258 } 259 260 Type getFloat32Type() { return Float32Type::get(context); } 261 Type getFloat64Type() { return Float64Type::get(context); } 262 263 private: 264 // Generates operations to cast the given operand to a specified type. 265 // If the cast cannot be performed, a warning will be issued and the 266 // operand returned as-is (which will presumably yield a verification 267 // issue downstream). 268 Value cast(Type toType, Value operand, bool isUnsignedCast) { 269 OpBuilder builder = getBuilder(); 270 auto loc = operand.getLoc(); 271 272 if (operand.getType() == toType) 273 return operand; 274 if (auto toIntType = toType.dyn_cast<IntegerType>()) { 275 // If operand is floating point, cast directly to the int type. 276 if (operand.getType().isa<FloatType>()) { 277 if (isUnsignedCast) 278 return builder.create<arith::FPToUIOp>(loc, toType, operand); 279 return builder.create<arith::FPToSIOp>(loc, toType, operand); 280 } 281 // Cast index operands directly to the int type. 282 if (operand.getType().isIndex()) 283 return builder.create<arith::IndexCastOp>(loc, toType, operand); 284 if (auto fromIntType = operand.getType().dyn_cast<IntegerType>()) { 285 // Either extend or truncate. 286 if (toIntType.getWidth() > fromIntType.getWidth()) { 287 if (isUnsignedCast) 288 return builder.create<arith::ExtUIOp>(loc, toType, operand); 289 return builder.create<arith::ExtSIOp>(loc, toType, operand); 290 } 291 if (toIntType.getWidth() < fromIntType.getWidth()) 292 return builder.create<arith::TruncIOp>(loc, toType, operand); 293 } 294 } else if (auto toFloatType = toType.dyn_cast<FloatType>()) { 295 // If operand is integer, cast directly to the float type. 296 // Note that it is unclear how to cast from BF16<->FP16. 297 if (operand.getType().isa<IntegerType>()) { 298 if (isUnsignedCast) 299 return builder.create<arith::UIToFPOp>(loc, toFloatType, operand); 300 return builder.create<arith::SIToFPOp>(loc, toFloatType, operand); 301 } 302 if (auto fromFloatType = operand.getType().dyn_cast<FloatType>()) { 303 if (toFloatType.getWidth() > fromFloatType.getWidth()) 304 return builder.create<arith::ExtFOp>(loc, toFloatType, operand); 305 if (toFloatType.getWidth() < fromFloatType.getWidth()) 306 return builder.create<arith::TruncFOp>(loc, toFloatType, operand); 307 } 308 } 309 310 emitWarning(operand.getLoc()) << "could not cast operand of type " 311 << operand.getType() << " to " << toType; 312 return operand; 313 } 314 315 bool isFloatingPoint(Value value) { return value.getType().isa<FloatType>(); } 316 bool isInteger(Value value) { return value.getType().isa<IntegerType>(); } 317 318 OpBuilder getBuilder() { 319 OpBuilder builder(context); 320 builder.setInsertionPointToEnd(&block); 321 return builder; 322 } 323 324 MLIRContext *context; 325 Block █ 326 }; 327 328 } // namespace 329 330 //===----------------------------------------------------------------------===// 331 // FillOp 332 //===----------------------------------------------------------------------===// 333 334 namespace { 335 336 /// Fold linalg.fill -> tensor.expand/collapse_shape chain. 337 /// 338 /// For such op chains, we can create new linalg.fill ops with the result 339 /// type of the tensor.expand/collapse_shape op. 340 template <typename TensorReshapeOp> 341 struct FoldFillWithTensorReshape : OpRewritePattern<TensorReshapeOp> { 342 using OpRewritePattern<TensorReshapeOp>::OpRewritePattern; 343 LogicalResult matchAndRewrite(TensorReshapeOp reshapeOp, 344 PatternRewriter &rewriter) const override { 345 auto oldFill = reshapeOp.src().template getDefiningOp<FillOp>(); 346 if (!oldFill) 347 return failure(); 348 349 Location loc = oldFill.getLoc(); 350 auto newInit = rewriter.create<TensorReshapeOp>( 351 loc, reshapeOp.getResultType(), oldFill.output(), 352 reshapeOp.reassociation()); 353 rewriter.replaceOpWithNewOp<FillOp>(reshapeOp, ValueRange{oldFill.value()}, 354 ValueRange{newInit}); 355 356 return success(); 357 } 358 }; 359 360 /// Fold tensor.pad(linalg.fill) into linalg.fill if the padding value and the 361 /// filling value are the same. 362 struct FoldFillWithPad final : public OpRewritePattern<tensor::PadOp> { 363 using OpRewritePattern::OpRewritePattern; 364 365 LogicalResult matchAndRewrite(tensor::PadOp padOp, 366 PatternRewriter &rewriter) const override { 367 auto fillOp = padOp.source().getDefiningOp<linalg::FillOp>(); 368 if (!fillOp) 369 return failure(); 370 371 // We can only fold if the padding value is the same as the original 372 // filling value. 373 Value padValue = padOp.getConstantPaddingValue(); 374 if (!padValue || fillOp.value() != padValue) 375 return failure(); 376 377 ReifiedRankedShapedTypeDims reifiedShape; 378 ReifyRankedShapedTypeOpInterface interface = 379 cast<ReifyRankedShapedTypeOpInterface>(padOp.getOperation()); 380 if (failed(interface.reifyResultShapes(rewriter, reifiedShape))) 381 return rewriter.notifyMatchFailure( 382 padOp, "failed to reify tensor.pad op result shape"); 383 384 auto oldResultType = padOp.getResultType(); 385 SmallVector<int64_t, 4> staticShape(oldResultType.getRank(), 386 ShapedType::kDynamicSize); 387 auto newInitOp = rewriter.create<InitTensorOp>( 388 padOp.getLoc(), reifiedShape.front(), staticShape, 389 oldResultType.getElementType()); 390 auto newFillOp = rewriter.create<FillOp>( 391 fillOp.getLoc(), ValueRange{padValue}, ValueRange{newInitOp}); 392 rewriter.replaceOpWithNewOp<tensor::CastOp>(padOp, oldResultType, 393 newFillOp.result()); 394 395 return success(); 396 } 397 }; 398 399 /// Fold tensor.insert_slice(tensor.pad(<input>), linalg.fill) into 400 /// tensor.insert_slice(<input>, linalg.fill) if the padding value and the 401 /// filling value are the same. 402 struct FoldInsertPadIntoFill : public OpRewritePattern<tensor::InsertSliceOp> { 403 using OpRewritePattern::OpRewritePattern; 404 405 LogicalResult matchAndRewrite(tensor::InsertSliceOp insertOp, 406 PatternRewriter &rewriter) const override { 407 auto srcPadOp = insertOp.source().getDefiningOp<tensor::PadOp>(); 408 if (!srcPadOp) 409 return failure(); 410 411 if (insertOp.getType().getRank() != insertOp.getSourceType().getRank()) 412 return failure(); 413 414 // Walk back the tensor.insert_slice chain and find the first destination 415 // value at the start of the chain. 416 Value firstDest = insertOp.dest(); 417 while (auto prevOp = firstDest.getDefiningOp<tensor::InsertSliceOp>()) { 418 if (prevOp.getType().getRank() != prevOp.getSourceType().getRank()) 419 return failure(); 420 421 // Make sure the range of values accessed are disjoint. Without this, we 422 // cannot fold tensor.pad away. 423 bool disjoint = false; 424 for (int i = 0, e = prevOp.getType().getRank(); i < e; ++i) { 425 // If the dimension has dynamic offset/size, we cannot guarantee 426 // disjoint. So just skip it. 427 if (insertOp.isDynamicOffset(i) || insertOp.isDynamicSize(i) || 428 insertOp.isDynamicStride(i) || prevOp.isDynamicOffset(i) || 429 prevOp.isDynamicSize(i) || prevOp.isDynamicStride(i)) 430 continue; 431 432 // Get the range start and end, inclusively for both. 433 int64_t prevStart = prevOp.getStaticOffset(i); 434 int64_t prevEnd = prevStart + (prevOp.getStaticSize(i) - 1) * 435 prevOp.getStaticStride(i); 436 int64_t nextStart = insertOp.getStaticOffset(i); 437 int64_t nextEnd = nextStart + (insertOp.getStaticSize(i) - 1) * 438 insertOp.getStaticStride(i); 439 if (prevEnd < nextStart || nextEnd < prevStart) { 440 disjoint = true; 441 break; 442 } 443 } 444 445 if (!disjoint) 446 break; 447 firstDest = prevOp.dest(); 448 } 449 450 // Check whether the first destination is a fill op. For overlapped cases, 451 // this also cannot be true. 452 auto dstFillOp = firstDest.getDefiningOp<linalg::FillOp>(); 453 if (!dstFillOp) 454 return failure(); 455 456 // We can only fold if the padding value is the same as the original 457 // filling value. 458 Value padValue = srcPadOp.getConstantPaddingValue(); 459 if (!padValue || dstFillOp.value() != padValue) 460 return failure(); 461 462 SmallVector<OpFoldResult> lowPads = srcPadOp.getMixedLowPad(); 463 SmallVector<OpFoldResult> oldOffsets = insertOp.getMixedOffsets(); 464 465 Location loc = insertOp.getLoc(); 466 MLIRContext *context = getContext(); 467 468 AffineExpr sym0, sym1; 469 bindSymbols(context, sym0, sym1); 470 auto addMap = AffineMap::get(0, 2, {sym0 + sym1}, context); 471 472 // Calculate the new offsets for the insert. It should be the old offsets 473 // plus low padding sizes. 474 SmallVector<OpFoldResult, 4> newOffsets; 475 for (const auto &p : llvm::zip(lowPads, oldOffsets)) { 476 Value padValue = getValueOrCreateConstantIndexOp( 477 rewriter, srcPadOp.getLoc(), std::get<0>(p)); 478 Value offsetValue = getValueOrCreateConstantIndexOp( 479 rewriter, insertOp.getLoc(), std::get<1>(p)); 480 newOffsets.push_back( 481 applyMapToValues(rewriter, loc, addMap, {offsetValue, padValue})[0]); 482 } 483 484 SmallVector<OpFoldResult, 4> newSizes; 485 for (int i = 0, e = srcPadOp.getSourceType().getRank(); i < e; ++i) { 486 newSizes.push_back( 487 rewriter.create<tensor::DimOp>(loc, srcPadOp.source(), i).result()); 488 } 489 490 rewriter.replaceOpWithNewOp<tensor::InsertSliceOp>( 491 insertOp, srcPadOp.source(), insertOp.dest(), newOffsets, newSizes, 492 insertOp.getMixedStrides()); 493 return success(); 494 } 495 }; 496 497 } // namespace 498 499 void FillOp::getCanonicalizationPatterns(RewritePatternSet &results, 500 MLIRContext *context) { 501 results 502 .add<FoldFillWithPad, FoldFillWithTensorReshape<tensor::CollapseShapeOp>, 503 FoldFillWithTensorReshape<tensor::ExpandShapeOp>, 504 FoldInsertPadIntoFill>(context); 505 } 506 507 //===----------------------------------------------------------------------===// 508 // GenericOps 509 //===----------------------------------------------------------------------===// 510 void GenericOp::build( 511 OpBuilder &builder, OperationState &result, TypeRange resultTensorTypes, 512 ValueRange inputs, ValueRange outputs, ArrayRef<AffineMap> indexingMaps, 513 ArrayRef<StringRef> iteratorTypes, StringRef doc, StringRef libraryCall, 514 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild, 515 ArrayRef<NamedAttribute> attributes) { 516 build(builder, result, resultTensorTypes, inputs, outputs, 517 builder.getAffineMapArrayAttr(indexingMaps), 518 builder.getStrArrayAttr(iteratorTypes), 519 doc.empty() ? StringAttr() : builder.getStringAttr(doc), 520 libraryCall.empty() ? StringAttr() 521 : builder.getStringAttr(libraryCall)); 522 result.addAttributes(attributes); 523 if (!bodyBuild) 524 return; 525 526 SmallVector<Type, 4> blockArgTypes; 527 SmallVector<Location, 4> blockArgLocs; 528 for (ValueRange container : {inputs, outputs}) { 529 for (Value v : container) { 530 blockArgTypes.push_back(getElementTypeOrSelf(v)); 531 blockArgLocs.push_back(v.getLoc()); 532 } 533 } 534 535 OpBuilder::InsertionGuard guard(builder); 536 auto ®ion = *result.regions.front(); 537 Block *bodyBlock = 538 builder.createBlock(®ion, region.end(), blockArgTypes, blockArgLocs); 539 bodyBuild(builder, result.location, bodyBlock->getArguments()); 540 } 541 542 void GenericOp::build( 543 OpBuilder &builder, OperationState &result, ValueRange inputs, 544 ValueRange outputs, ArrayRef<AffineMap> indexingMaps, 545 ArrayRef<StringRef> iteratorTypes, StringRef doc, StringRef libraryCall, 546 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild, 547 ArrayRef<NamedAttribute> attributes) { 548 build(builder, result, TypeRange{}, inputs, outputs, indexingMaps, 549 iteratorTypes, doc, libraryCall, bodyBuild, attributes); 550 } 551 552 void GenericOp::build( 553 OpBuilder &builder, OperationState &result, ValueRange inputs, 554 ValueRange outputs, ArrayRef<AffineMap> indexingMaps, 555 ArrayRef<StringRef> iteratorTypes, 556 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild, 557 ArrayRef<NamedAttribute> attributes) { 558 build(builder, result, inputs, outputs, indexingMaps, iteratorTypes, 559 /*doc=*/"", 560 /*libraryCall=*/"", bodyBuild, attributes); 561 } 562 563 void GenericOp::build( 564 OpBuilder &builder, OperationState &result, TypeRange resultTensorTypes, 565 ValueRange inputs, ValueRange outputs, ArrayRef<AffineMap> indexingMaps, 566 ArrayRef<StringRef> iteratorTypes, 567 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild, 568 ArrayRef<NamedAttribute> attributes) { 569 build(builder, result, resultTensorTypes, inputs, outputs, indexingMaps, 570 iteratorTypes, 571 /*doc=*/"", 572 /*libraryCall=*/"", bodyBuild, attributes); 573 } 574 575 void GenericOp::print(OpAsmPrinter &p) { 576 p << " "; 577 578 // Print extra attributes. 579 auto genericAttrNames = linalgTraitAttrNames(); 580 581 llvm::StringSet<> genericAttrNamesSet; 582 genericAttrNamesSet.insert(genericAttrNames.begin(), genericAttrNames.end()); 583 SmallVector<NamedAttribute, 8> genericAttrs; 584 for (auto attr : (*this)->getAttrs()) 585 if (genericAttrNamesSet.count(attr.getName().strref()) > 0) 586 genericAttrs.push_back(attr); 587 if (!genericAttrs.empty()) { 588 auto genericDictAttr = DictionaryAttr::get(getContext(), genericAttrs); 589 p << genericDictAttr; 590 } 591 592 // Printing is shared with named ops, except for the region and attributes 593 printCommonStructuredOpParts(p, *this); 594 595 genericAttrNames.push_back("operand_segment_sizes"); 596 genericAttrNamesSet.insert(genericAttrNames.back()); 597 598 bool hasExtraAttrs = false; 599 for (NamedAttribute n : (*this)->getAttrs()) { 600 if ((hasExtraAttrs = !genericAttrNamesSet.contains(n.getName().strref()))) 601 break; 602 } 603 if (hasExtraAttrs) { 604 p << " attrs = "; 605 p.printOptionalAttrDict((*this)->getAttrs(), 606 /*elidedAttrs=*/genericAttrNames); 607 } 608 609 // Print region. 610 if (!region().empty()) { 611 p << ' '; 612 p.printRegion(region()); 613 } 614 615 // Print results. 616 printNamedStructuredOpResults(p, result_tensors().getTypes()); 617 } 618 619 ParseResult GenericOp::parse(OpAsmParser &parser, OperationState &result) { 620 DictionaryAttr dictAttr; 621 // Parse the core linalg traits that must check into a dictAttr. 622 // The name is unimportant as we will overwrite result.attributes. 623 // The core linalg traits must contain the information necessary to pass the 624 // verifier. 625 if (parser.parseAttribute(dictAttr, "_", result.attributes)) 626 return failure(); 627 result.attributes.assign(dictAttr.getValue().begin(), 628 dictAttr.getValue().end()); 629 630 // Parsing is shared with named ops, except for the region. 631 SmallVector<Type, 1> inputTypes, outputTypes; 632 if (parseCommonStructuredOpParts(parser, result, inputTypes, outputTypes)) 633 return failure(); 634 635 // Optional attributes may be added. 636 if (succeeded(parser.parseOptionalKeyword("attrs"))) 637 if (failed(parser.parseEqual()) || 638 failed(parser.parseOptionalAttrDict(result.attributes))) 639 return failure(); 640 641 SmallVector<OpAsmParser::OperandType, 8> regionOperands; 642 std::unique_ptr<Region> region = std::make_unique<Region>(); 643 SmallVector<Type, 8> operandTypes, regionTypes; 644 if (parser.parseRegion(*region, regionOperands, regionTypes)) 645 return failure(); 646 result.addRegion(std::move(region)); 647 648 // Generic ops may specify that a subset of its outputs are tensors. Such 649 // outputs are specified in the result type. 650 // TODO: may need to move output parsing before region parsing. 651 // Need to wait for declarative assembly resolution to decide. 652 SmallVector<Type, 1> outputTensorsTypes; 653 if (parseNamedStructuredOpResults(parser, outputTensorsTypes)) 654 return failure(); 655 result.addTypes(outputTensorsTypes); 656 657 return success(); 658 } 659 660 static void getGenericEffectsImpl( 661 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>> 662 &effects, 663 ValueRange results, ValueRange inputBuffers, ValueRange outputs) { 664 for (Value value : inputBuffers) { 665 effects.emplace_back(MemoryEffects::Read::get(), value, 666 SideEffects::DefaultResource::get()); 667 } 668 for (Value value : outputs) { 669 effects.emplace_back(MemoryEffects::Read::get(), value, 670 SideEffects::DefaultResource::get()); 671 effects.emplace_back(MemoryEffects::Write::get(), value, 672 SideEffects::DefaultResource::get()); 673 } 674 } 675 676 void GenericOp::getEffects( 677 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>> 678 &effects) { 679 SmallVector<Value> inputBuffers = getInputBufferOperands(); 680 SmallVector<Value> outputBuffers = getOutputBufferOperands(); 681 getGenericEffectsImpl(effects, getOperation()->getResults(), inputBuffers, 682 outputBuffers); 683 } 684 685 template <typename GenericOpType> 686 static LogicalResult verifyGenericOp(GenericOpType op) { 687 return success(); 688 } 689 690 LogicalResult GenericOp::verify() { return verifyGenericOp(*this); } 691 692 namespace { 693 // Deduplicate redundant args of a linalg generic op. 694 // An arg is redundant if it has the same Value and indexing map as another. 695 struct DeduplicateGenericOpInputs : public OpRewritePattern<GenericOp> { 696 using OpRewritePattern<GenericOp>::OpRewritePattern; 697 698 LogicalResult matchAndRewrite(GenericOp genericOp, 699 PatternRewriter &rewriter) const override { 700 // Associate each input to an equivalent "canonical" input that has the same 701 // Value and indexing map. 702 // 703 // In the non-duplicate case, input `i` will have canonical input `i`. But 704 // in the case of duplicated inputs, the canonical input could be some other 705 // input `< i`. That is, a later input will have some earlier input as its 706 // canonical input. 707 llvm::SmallDenseMap<std::pair<Value, AffineMap>, unsigned> canonicalInput; 708 // For later remapping tasks like deduplicating payload block arguments, 709 // having a simple "inputIndex -> canonicalInputIndex" integer mapping is 710 // convenient. 711 SmallVector<unsigned> canonicalInputIndices; 712 for (OpOperand *opOperand : genericOp.getInputOperands()) { 713 AffineMap indexingMap = genericOp.getTiedIndexingMap(opOperand); 714 // STL-like maps have a convenient behavior for our use case here. In the 715 // case of duplicate keys, the insertion is rejected, and the returned 716 // iterator gives access to the value already in the map. 717 auto pair = canonicalInput.insert( 718 {{opOperand->get(), indexingMap}, opOperand->getOperandNumber()}); 719 canonicalInputIndices.push_back(pair.first->second); 720 } 721 722 // If there are no duplicate args, then bail out. 723 if (canonicalInput.size() == genericOp.getNumInputs()) 724 return failure(); 725 726 // The operands for the newly canonicalized op. 727 SmallVector<Value> newInputOperands; 728 for (OpOperand *opOperand : genericOp.getInputOperands()) 729 if (canonicalInputIndices[opOperand->getOperandNumber()] == 730 opOperand->getOperandNumber()) 731 newInputOperands.push_back(opOperand->get()); 732 733 // Repair the indexing maps by filtering out the ones that have been 734 // eliminated. 735 SmallVector<AffineMap> newIndexingMaps; 736 for (OpOperand *opOperand : genericOp.getInputOperands()) 737 if (canonicalInputIndices[opOperand->getOperandNumber()] == 738 opOperand->getOperandNumber()) 739 newIndexingMaps.push_back(genericOp.getTiedIndexingMap(opOperand)); 740 for (OpOperand *opOperand : genericOp.getOutputOperands()) 741 newIndexingMaps.push_back(genericOp.getTiedIndexingMap(opOperand)); 742 743 // Clone the old op with new operands. 744 SmallVector<Value> outputOperands = genericOp.getOutputOperands(); 745 auto newOp = rewriter.create<GenericOp>( 746 genericOp.getLoc(), genericOp->getResultTypes(), newInputOperands, 747 outputOperands, rewriter.getAffineMapArrayAttr(newIndexingMaps), 748 genericOp.iterator_types(), genericOp.docAttr(), 749 genericOp.library_callAttr()); 750 751 // Copy over unknown attributes. They might be load bearing for some flow. 752 ArrayRef<StringRef> odsAttrs = genericOp.getAttributeNames(); 753 for (NamedAttribute kv : genericOp->getAttrs()) { 754 if (!llvm::is_contained(odsAttrs, kv.getName().getValue())) { 755 newOp->setAttr(kv.getName(), kv.getValue()); 756 } 757 } 758 759 rewriter.inlineRegionBefore(genericOp.region(), newOp.region(), 760 newOp.region().begin()); 761 762 // Repair the payload entry block by RAUW'ing redundant arguments and 763 // erasing them. 764 Block &payload = newOp.region().front(); 765 SmallVector<OpOperand *> inputOperands = genericOp.getInputOperands(); 766 for (OpOperand *opOperand : llvm::reverse(inputOperands)) { 767 // Iterate in reverse, so that we erase later args first, preventing the 768 // argument list from shifting unexpectedly and invalidating all our 769 // indices. 770 unsigned operandNumber = opOperand->getOperandNumber(); 771 if (canonicalInputIndices[operandNumber] == operandNumber) 772 continue; 773 payload.getArgument(operandNumber) 774 .replaceAllUsesWith( 775 payload.getArgument(canonicalInputIndices[operandNumber])); 776 payload.eraseArgument(operandNumber); 777 } 778 779 rewriter.replaceOp(genericOp, newOp->getResults()); 780 return success(); 781 } 782 }; 783 784 /// Remove generic operations (on tensors) that are just copying 785 /// the values from inputs to the results. Requirements are 786 /// 1) All iterator types are parallel 787 /// 2) The body contains just a yield operation with the yielded values being 788 /// the arguments corresponding to the operands. 789 struct EraseIdentityGenericOp : public OpRewritePattern<GenericOp> { 790 using OpRewritePattern<GenericOp>::OpRewritePattern; 791 792 LogicalResult matchAndRewrite(GenericOp genericOp, 793 PatternRewriter &rewriter) const override { 794 // Check all indexing maps are identity. 795 if (llvm::any_of(genericOp.getIndexingMaps(), 796 [](AffineMap map) { return !map.isIdentity(); })) 797 return failure(); 798 799 // Check that the body of the linalg operation is just a linalg.yield 800 // operation. 801 Block &body = genericOp.region().front(); 802 if (!llvm::hasSingleElement(body)) 803 return failure(); 804 auto yieldOp = dyn_cast<linalg::YieldOp>(body.getTerminator()); 805 if (!yieldOp) 806 return failure(); 807 808 // In the buffer case, we need to check exact buffer equality. 809 if (genericOp.hasBufferSemantics()) { 810 if (genericOp.getNumInputs() == 1 && genericOp.getNumOutputs() == 1 && 811 genericOp.getInputOperand(0)->get() == 812 genericOp.getOutputOperand(0)->get()) { 813 rewriter.eraseOp(genericOp); 814 return success(); 815 } 816 return failure(); 817 } 818 819 // Get the argument number of the returned values. That is the operand 820 // number to use for replacing uses of this operation. 821 SmallVector<Value> returnedArgs; 822 for (const auto &yieldVal : llvm::enumerate(yieldOp.values())) { 823 auto yieldArg = yieldVal.value().dyn_cast<BlockArgument>(); 824 if (!yieldArg || yieldArg.getOwner() != &body) 825 return failure(); 826 unsigned argumentNumber = yieldArg.getArgNumber(); 827 Value returnedArg = genericOp->getOperand(argumentNumber); 828 Type resultType = genericOp->getResult(yieldVal.index()).getType(); 829 // The input can have a different type than the result, e.g. a dynamic 830 // input dimension can be turned into a static output dimension. 831 Type returnType = returnedArg.getType(); 832 if (returnType != resultType) { 833 // Distinguish between sparse conversion or dense tensor casting. 834 // TODO: unify the two ops? 835 if (sparse_tensor::getSparseTensorEncoding(returnType) || 836 sparse_tensor::getSparseTensorEncoding(resultType)) 837 returnedArg = rewriter.create<sparse_tensor::ConvertOp>( 838 genericOp.getLoc(), resultType, returnedArg); 839 else 840 returnedArg = rewriter.create<tensor::CastOp>( 841 genericOp.getLoc(), resultType, returnedArg); 842 } 843 returnedArgs.push_back(returnedArg); 844 } 845 846 if (returnedArgs.size() != genericOp->getNumResults()) 847 return failure(); 848 rewriter.replaceOp(genericOp, returnedArgs); 849 return success(); 850 } 851 }; 852 853 /// Drop dead args of a linalg generic op. 854 /// An arg is dead if it has zero uses in the op region. 855 struct DeadArgsGenericOpInputs : public OpRewritePattern<GenericOp> { 856 using OpRewritePattern<GenericOp>::OpRewritePattern; 857 LogicalResult matchAndRewrite(GenericOp genericOp, 858 PatternRewriter &rewriter) const override { 859 SmallVector<AffineMap> oldIndexingMaps = genericOp.getIndexingMaps(); 860 // Maps must be projected permutations. 861 if (llvm::any_of(genericOp.getIndexingMaps(), [](AffineMap map) { 862 return !map.isProjectedPermutation(); 863 })) 864 return failure(); 865 Block &payload = genericOp.region().front(); 866 SmallVector<Value> newInputOperands; 867 SmallVector<AffineMap> newIndexingMaps; 868 bool deadArgFound = false; 869 int inputSize = genericOp.getInputOperands().size(); 870 for (int i = inputSize - 1; i >= 0; i--) { 871 OpOperand *opOperand = genericOp.getInputOperand(i); 872 // Iterate in reverse, so that we erase later args first, preventing the 873 // argument list from shifting unexpectedly and invalidating all our 874 // indices. 875 if (payload.getArgument(i).use_empty() && 876 !hasaUniqueDim(oldIndexingMaps, i)) { 877 payload.eraseArgument(i); 878 deadArgFound = true; 879 // remove this indexing map out of consideration for hasaUniqueDim check 880 oldIndexingMaps.erase(oldIndexingMaps.begin() + i); 881 } else { 882 newInputOperands.insert(newInputOperands.begin(), opOperand->get()); 883 newIndexingMaps.insert(newIndexingMaps.begin(), 884 genericOp.getTiedIndexingMap(opOperand)); 885 } 886 } 887 // Bail out if there are no dead args. 888 if (!deadArgFound) 889 return failure(); 890 for (OpOperand *opOperand : genericOp.getOutputOperands()) 891 newIndexingMaps.push_back(genericOp.getTiedIndexingMap(opOperand)); 892 SmallVector<Value> outputOperands = genericOp.getOutputOperands(); 893 894 auto newOp = rewriter.create<GenericOp>( 895 genericOp.getLoc(), genericOp->getResultTypes(), newInputOperands, 896 outputOperands, rewriter.getAffineMapArrayAttr(newIndexingMaps), 897 genericOp.iterator_types(), genericOp.docAttr(), 898 genericOp.library_callAttr()); 899 // Copy over unknown attributes. They might be load bearing for some flow. 900 ArrayRef<StringRef> odsAttrs = genericOp.getAttributeNames(); 901 for (NamedAttribute kv : genericOp->getAttrs()) { 902 if (!llvm::is_contained(odsAttrs, kv.getName().getValue())) { 903 newOp->setAttr(kv.getName(), kv.getValue()); 904 } 905 } 906 rewriter.inlineRegionBefore(genericOp.region(), newOp.region(), 907 newOp.region().begin()); 908 rewriter.replaceOp(genericOp, newOp->getResults()); 909 return success(); 910 } 911 }; 912 913 /// Fold linalg.fill into linalg.generic 914 struct FoldFillWithGenericOp : public OpRewritePattern<GenericOp> { 915 using OpRewritePattern<GenericOp>::OpRewritePattern; 916 917 LogicalResult matchAndRewrite(GenericOp genericOp, 918 PatternRewriter &rewriter) const override { 919 if (!genericOp.hasTensorSemantics()) 920 return failure(); 921 bool fillFound = false; 922 Block &payload = genericOp.region().front(); 923 for (OpOperand *opOperand : genericOp.getInputOperands()) { 924 FillOp fillOp = opOperand->get().getDefiningOp<FillOp>(); 925 if (fillOp) { 926 fillFound = true; 927 payload.getArgument(opOperand->getOperandNumber()) 928 .replaceAllUsesWith(fillOp.value()); 929 } 930 } 931 // fail if there are no FillOps to fold. 932 return success(fillFound); 933 } 934 }; 935 } // namespace 936 937 void GenericOp::getCanonicalizationPatterns(RewritePatternSet &results, 938 MLIRContext *context) { 939 results.add<DeduplicateGenericOpInputs, EraseIdentityGenericOp, 940 DeadArgsGenericOpInputs, FoldFillWithGenericOp>(context); 941 } 942 943 LogicalResult GenericOp::fold(ArrayRef<Attribute>, 944 SmallVectorImpl<OpFoldResult> &) { 945 return foldMemRefCast(*this); 946 } 947 948 //===----------------------------------------------------------------------===// 949 // InitTensorOp 950 //===----------------------------------------------------------------------===// 951 952 void InitTensorOp::build(OpBuilder &b, OperationState &result, 953 ArrayRef<OpFoldResult> sizes, Type elementType, 954 ArrayRef<NamedAttribute> attrs) { 955 SmallVector<Value, 4> dynamicSizes; 956 SmallVector<int64_t, 4> staticSizes; 957 dispatchIndexOpFoldResults(sizes, dynamicSizes, staticSizes, 958 ShapedType::kDynamicSize); 959 auto resultType = RankedTensorType ::get(staticSizes, elementType); 960 build(b, result, resultType, dynamicSizes, b.getI64ArrayAttr(staticSizes)); 961 result.addAttributes(attrs); 962 } 963 964 LogicalResult InitTensorOp::verify() { 965 RankedTensorType resultType = getType(); 966 SmallVector<int64_t, 4> staticSizes = llvm::to_vector<4>(llvm::map_range( 967 static_sizes().cast<ArrayAttr>(), 968 [](Attribute a) -> int64_t { return a.cast<IntegerAttr>().getInt(); })); 969 970 if (failed(verifyListOfOperandsOrIntegers( 971 *this, "sizes", resultType.getRank(), static_sizes(), sizes(), 972 ShapedType::isDynamic))) 973 return failure(); 974 975 if (static_sizes().size() != static_cast<unsigned>(resultType.getRank())) 976 return emitError("expected ") << resultType.getRank() << " sizes values"; 977 978 Type expectedType = InitTensorOp::inferResultType( 979 staticSizes, resultType.getElementType(), resultType.getEncoding()); 980 if (resultType != expectedType) { 981 return emitError("specified type ") 982 << resultType << " does not match the inferred type " 983 << expectedType; 984 } 985 return success(); 986 } 987 988 Type InitTensorOp::inferResultType(ArrayRef<int64_t> staticSizes, 989 Type elementType, Attribute encoding) { 990 return RankedTensorType::get(staticSizes, elementType, encoding); 991 } 992 993 SmallVector<OpFoldResult> InitTensorOp::getMixedSizes() { 994 SmallVector<OpFoldResult> mixedSizes; 995 mixedSizes.reserve(getType().getRank()); 996 unsigned dynamicValIndex = 0; 997 for (Attribute attr : static_sizes()) { 998 auto intAttr = attr.cast<IntegerAttr>(); 999 if (!ShapedType::isDynamic(intAttr.getInt())) { 1000 mixedSizes.push_back(intAttr); 1001 continue; 1002 } 1003 mixedSizes.push_back(sizes()[dynamicValIndex++]); 1004 } 1005 return mixedSizes; 1006 } 1007 1008 namespace { 1009 /// Change the type of the result of a `linalg.init_tensor` by making the result 1010 /// type statically sized along dimension that in the original operation where 1011 /// defined as dynamic, but the size was defined using a `constant` op. For 1012 /// example 1013 /// 1014 /// %c5 = arith.constant 5: index 1015 /// %0 = linalg.init_tensor [%arg0, %c5] : tensor<?x?xf32> 1016 /// 1017 /// to 1018 /// 1019 /// %0 = linalg.init_tensor [%arg0, 5] : tensor<?x5xf32> 1020 struct ReplaceStaticShapeDims : OpRewritePattern<InitTensorOp> { 1021 using OpRewritePattern<InitTensorOp>::OpRewritePattern; 1022 1023 LogicalResult matchAndRewrite(InitTensorOp op, 1024 PatternRewriter &rewriter) const override { 1025 SmallVector<Value, 4> dynamicSizes; 1026 SmallVector<int64_t, 4> staticSizes; 1027 for (unsigned i = 0, e = op.getType().getRank(); i != e; ++i) { 1028 // If the size is already static, nothing to do. 1029 if (!op.isDynamicSize(i)) { 1030 staticSizes.push_back(op.getStaticSize(i)); 1031 continue; 1032 } 1033 1034 // If the size is dynamic but defined using a `constant` op, get the 1035 // constant value to find the static size to use. 1036 unsigned operandNum = op.getIndexOfDynamicSize(i); 1037 Value sizeOperand = op.getOperand(operandNum); 1038 if (auto constantIndexOp = 1039 sizeOperand.getDefiningOp<arith::ConstantIndexOp>()) { 1040 staticSizes.push_back(constantIndexOp.value()); 1041 continue; 1042 } 1043 1044 // Fallback case. Keep the size dynamic. 1045 dynamicSizes.push_back(sizeOperand); 1046 staticSizes.push_back(ShapedType::kDynamicSize); 1047 } 1048 RankedTensorType newType = 1049 RankedTensorType::get(staticSizes, op.getType().getElementType()); 1050 if (newType == op.getType()) 1051 return failure(); 1052 auto newOp = 1053 rewriter.create<InitTensorOp>(op.getLoc(), newType, dynamicSizes, 1054 rewriter.getI64ArrayAttr(staticSizes)); 1055 rewriter.replaceOpWithNewOp<tensor::CastOp>(op, op.getType(), newOp); 1056 return success(); 1057 } 1058 }; 1059 } // namespace 1060 1061 namespace { 1062 /// Since `init_tensor` operation creates a tensor needed only for its shape, a 1063 /// slice of this is also needed only for its shape. The result can be 1064 /// replaced by a new init_tensor operation of the same size as the extract 1065 /// slice op. 1066 struct FoldInitTensorWithExtractSliceOp 1067 : public OpRewritePattern<tensor::ExtractSliceOp> { 1068 using OpRewritePattern<tensor::ExtractSliceOp>::OpRewritePattern; 1069 1070 LogicalResult matchAndRewrite(tensor::ExtractSliceOp sliceOp, 1071 PatternRewriter &rewriter) const override { 1072 if (!sliceOp.source().getDefiningOp<linalg::InitTensorOp>()) 1073 return failure(); 1074 // ExtractSliceOp may be rank-reducing; its dynamic sizes must be preserved 1075 // as well as its result type. 1076 rewriter.replaceOpWithNewOp<linalg::InitTensorOp>( 1077 sliceOp, sliceOp.sizes(), 1078 sliceOp.result().getType().cast<RankedTensorType>().getShape(), 1079 sliceOp.getSourceType().getElementType()); 1080 return success(); 1081 } 1082 }; 1083 1084 template <typename TensorReshapeOp> 1085 struct FoldInitTensorWithTensorReshapeOp 1086 : public OpRewritePattern<TensorReshapeOp> { 1087 using OpRewritePattern<TensorReshapeOp>::OpRewritePattern; 1088 1089 LogicalResult matchAndRewrite(TensorReshapeOp reshapeOp, 1090 PatternRewriter &rewriter) const override { 1091 if (!reshapeOp.src().template getDefiningOp<InitTensorOp>()) 1092 return failure(); 1093 Location loc = reshapeOp.getLoc(); 1094 ReifiedRankedShapedTypeDims resultShapes; 1095 ReifyRankedShapedTypeOpInterface reifyShapedTypeInterface = 1096 cast<ReifyRankedShapedTypeOpInterface>(reshapeOp.getOperation()); 1097 if (failed(reifyShapedTypeInterface.reifyResultShapes(rewriter, 1098 resultShapes)) || 1099 !llvm::hasSingleElement(resultShapes)) 1100 return failure(); 1101 Value initTensor = rewriter.create<InitTensorOp>( 1102 loc, getAsOpFoldResult(resultShapes[0]), 1103 reshapeOp.getResultType().getElementType()); 1104 if (initTensor.getType() != reshapeOp.getResultType()) { 1105 rewriter.replaceOpWithNewOp<tensor::CastOp>( 1106 reshapeOp, reshapeOp.getResultType(), initTensor); 1107 } else { 1108 rewriter.replaceOp(reshapeOp, initTensor); 1109 } 1110 return success(); 1111 } 1112 }; 1113 1114 struct FoldInitTensorWithDimOp : public OpRewritePattern<tensor::DimOp> { 1115 using OpRewritePattern<tensor::DimOp>::OpRewritePattern; 1116 1117 LogicalResult matchAndRewrite(tensor::DimOp dimOp, 1118 PatternRewriter &rewriter) const override { 1119 Optional<int64_t> maybeConstantIndex = dimOp.getConstantIndex(); 1120 auto initTensorOp = dimOp.source().getDefiningOp<linalg::InitTensorOp>(); 1121 if (!initTensorOp || !maybeConstantIndex) 1122 return failure(); 1123 if (!initTensorOp.isDynamicSize(*maybeConstantIndex)) 1124 return failure(); 1125 rewriter.replaceOp(dimOp, initTensorOp.getDynamicSize(*maybeConstantIndex)); 1126 return success(); 1127 } 1128 }; 1129 1130 /// Canonicalize 1131 /// 1132 /// ```mlir 1133 /// %0 = linalg.init_tensor [%d0, %d1] : tensor<?x?xf32> 1134 /// %1 = tensor.cast %0 : tensor<?x?xf32> to tensor<4x?xf32> 1135 /// ``` 1136 /// 1137 /// into 1138 /// 1139 /// ```mlir 1140 /// %0 = linalg.init_tensor [4, %d1] : tensor<4x?xf32> 1141 /// ``` 1142 /// 1143 /// This assumes the input program is correct in terms of its shape. So it 1144 /// is safe to assume that `%d0` is in fact 4. If that was not the case, the 1145 /// input program is wrong to begin with, so its undefined behavior anyway (i.e. 1146 /// this optimization can still triggering without violating program semantics). 1147 struct FoldInitTensorWithTensorCastOp 1148 : public OpRewritePattern<tensor::CastOp> { 1149 using OpRewritePattern<tensor::CastOp>::OpRewritePattern; 1150 1151 LogicalResult matchAndRewrite(tensor::CastOp castOp, 1152 PatternRewriter &rewriter) const override { 1153 if (!canFoldIntoProducerOp(castOp)) 1154 return failure(); 1155 auto producer = castOp.source().getDefiningOp<InitTensorOp>(); 1156 if (!producer) 1157 return failure(); 1158 1159 auto resultType = castOp->getResult(0).getType().cast<RankedTensorType>(); 1160 ArrayRef<int64_t> resultShape = resultType.getShape(); 1161 SmallVector<OpFoldResult> currMixedSizes = producer.getMixedSizes(); 1162 SmallVector<OpFoldResult> newMixedSizes; 1163 newMixedSizes.reserve(currMixedSizes.size()); 1164 assert(resultShape.size() == currMixedSizes.size() && 1165 "mismatch in result shape and sizes of init_tensor op"); 1166 for (auto it : llvm::zip(resultShape, currMixedSizes)) { 1167 int64_t newDim = std::get<0>(it); 1168 OpFoldResult currDim = std::get<1>(it); 1169 // Case 1: The init tensor dim is static. Check that the tensor cast 1170 // result dim matches. 1171 if (auto attr = currDim.dyn_cast<Attribute>()) { 1172 if (ShapedType::isDynamic(newDim) || 1173 newDim != attr.cast<IntegerAttr>().getInt()) { 1174 // Something is off, the cast result shape cannot be more dynamic than 1175 // the init tensor result shape (enforced by `canFoldIntoProducer`). 1176 // Abort for now. 1177 return rewriter.notifyMatchFailure( 1178 producer, "mismatch in static value of shape of init " 1179 "tensor result and cast result"); 1180 } 1181 newMixedSizes.push_back(attr); 1182 continue; 1183 } 1184 1185 // Case 2 : The tensor cast shape is static, but init tensor result shape 1186 // is dynamic. 1187 if (!ShapedType::isDynamic(newDim)) { 1188 newMixedSizes.push_back(rewriter.getIndexAttr(newDim)); 1189 continue; 1190 } 1191 1192 // Case 3 : The tensor cast shape is dynamic and init tensor result shape 1193 // is dynamic. Use the dynamic value from the init tensor op. 1194 newMixedSizes.push_back(currDim); 1195 } 1196 1197 rewriter.replaceOpWithNewOp<InitTensorOp>(castOp, newMixedSizes, 1198 resultType.getElementType()); 1199 return success(); 1200 } 1201 }; 1202 1203 } // namespace 1204 1205 void InitTensorOp::getCanonicalizationPatterns(RewritePatternSet &results, 1206 MLIRContext *context) { 1207 results.add<FoldInitTensorWithTensorCastOp, FoldInitTensorWithDimOp, 1208 FoldInitTensorWithExtractSliceOp, 1209 FoldInitTensorWithTensorReshapeOp<tensor::ExpandShapeOp>, 1210 FoldInitTensorWithTensorReshapeOp<tensor::CollapseShapeOp>, 1211 ReplaceStaticShapeDims>(context); 1212 } 1213 1214 LogicalResult InitTensorOp::reifyResultShapes( 1215 OpBuilder &builder, ReifiedRankedShapedTypeDims &reifiedReturnShapes) { 1216 auto shapes = llvm::to_vector<4>(llvm::map_range( 1217 llvm::seq<int64_t>(0, getType().getRank()), [&](int64_t dim) -> Value { 1218 if (isDynamicSize(dim)) 1219 return getDynamicSize(dim); 1220 return builder.create<arith::ConstantIndexOp>(getLoc(), 1221 getStaticSize(dim)); 1222 })); 1223 reifiedReturnShapes.emplace_back(std::move(shapes)); 1224 return success(); 1225 } 1226 1227 //===----------------------------------------------------------------------===// 1228 // YieldOp 1229 //===----------------------------------------------------------------------===// 1230 1231 void linalg::YieldOp::print(OpAsmPrinter &p) { 1232 if (getNumOperands() > 0) 1233 p << ' ' << getOperands(); 1234 p.printOptionalAttrDict((*this)->getAttrs()); 1235 if (getNumOperands() > 0) 1236 p << " : " << getOperandTypes(); 1237 } 1238 1239 ParseResult YieldOp::parse(OpAsmParser &parser, OperationState &result) { 1240 SmallVector<OpAsmParser::OperandType, 2> opInfo; 1241 SmallVector<Type, 2> types; 1242 SMLoc loc = parser.getCurrentLocation(); 1243 return failure(parser.parseOperandList(opInfo) || 1244 parser.parseOptionalAttrDict(result.attributes) || 1245 (!opInfo.empty() && parser.parseColonTypeList(types)) || 1246 parser.resolveOperands(opInfo, types, loc, result.operands)); 1247 } 1248 1249 // Check the operand number and types must match the element types of the 1250 // LinalgOp interface's shaped operands. 1251 static LogicalResult verifyYield(linalg::YieldOp op, LinalgOp linalgOp) { 1252 if (op.getNumOperands() != linalgOp.getNumOutputs()) 1253 return op.emitOpError("expected number of yield values (") 1254 << linalgOp.getNumOutputs() 1255 << ") to match the number of operands of the enclosing " 1256 << "LinalgOp (" << op.getNumOperands() << ")"; 1257 1258 for (OpOperand &opOperand : op->getOpOperands()) { 1259 OpOperand *outputOperand = 1260 linalgOp.getOutputOperand(opOperand.getOperandNumber()); 1261 Type elementType = getElementTypeOrSelf(outputOperand->get().getType()); 1262 if (opOperand.get().getType() != elementType) 1263 return op.emitOpError("type of yield operand ") 1264 << (opOperand.getOperandNumber() + 1) << " (" 1265 << opOperand.get().getType() << ") doesn't match " 1266 << "the element type of the enclosing linalg.generic op (" 1267 << elementType << ")"; 1268 } 1269 return success(); 1270 } 1271 1272 LogicalResult linalg::YieldOp::verify() { 1273 auto *parentOp = (*this)->getParentOp(); 1274 if (parentOp->getNumRegions() != 1 || parentOp->getRegion(0).empty()) 1275 return emitOpError("expected single non-empty parent region"); 1276 1277 if (auto linalgOp = dyn_cast<LinalgOp>(parentOp)) 1278 return verifyYield(*this, linalgOp); 1279 1280 return emitOpError("expected parent op with LinalgOp interface"); 1281 } 1282 1283 //===----------------------------------------------------------------------===// 1284 // IndexOp 1285 //===----------------------------------------------------------------------===// 1286 1287 LogicalResult IndexOp::verify() { 1288 auto linalgOp = dyn_cast<LinalgOp>((*this)->getParentOp()); 1289 if (!linalgOp) 1290 return emitOpError("expected parent op with LinalgOp interface"); 1291 if (linalgOp.getNumLoops() <= dim()) 1292 return emitOpError("expected dim (") 1293 << dim() << ") to be lower than the number of loops (" 1294 << linalgOp.getNumLoops() << ") of the enclosing LinalgOp"; 1295 return success(); 1296 } 1297 1298 /////// Operations corresponding to library calls defined with Tablegen //////// 1299 1300 #include "mlir/Dialect/Linalg/IR/LinalgNamedStructuredOps.yamlgen.cpp.inc" 1301 1302 #define GET_OP_CLASSES 1303 #include "mlir/Dialect/Linalg/IR/LinalgOps.cpp.inc" 1304 1305 #define GET_OP_CLASSES 1306 #include "mlir/Dialect/Linalg/IR/LinalgStructuredOps.cpp.inc" 1307 1308 /// Return the dims that are `iteratorTypeName` loops in the LinalgOp `op`. 1309 /// Assumes `op` is a LinalgOp. 1310 void mlir::linalg::getDimsOfType(Operation *op, StringRef iteratorTypeName, 1311 SmallVectorImpl<unsigned> &res) { 1312 if (!cast<LinalgOp>(op).iterator_types()) 1313 return; 1314 1315 unsigned dim = 0; 1316 for (auto tn : 1317 cast<LinalgOp>(op).iterator_types().getAsValueRange<StringAttr>()) { 1318 if (tn == iteratorTypeName) 1319 res.push_back(dim); 1320 ++dim; 1321 } 1322 } 1323 1324 AffineMap mlir::linalg::extractOrIdentityMap(Optional<AffineMap> maybeMap, 1325 unsigned rank, 1326 MLIRContext *context) { 1327 if (maybeMap) 1328 return maybeMap.getValue(); 1329 if (rank == 0) 1330 return AffineMap::get(context); 1331 return AffineMap::getMultiDimIdentityMap(rank, context); 1332 } 1333 1334 SmallVector<AffineExpr, 4> 1335 mlir::linalg::makeAffineDimExprs(unsigned num, unsigned &startIdx, 1336 MLIRContext *context) { 1337 SmallVector<AffineExpr, 4> res; 1338 res.reserve(num); 1339 for (unsigned i = 0; i < num; ++i) 1340 res.push_back(getAffineDimExpr(startIdx++, context)); 1341 return res; 1342 } 1343 1344 SmallVector<AffineExpr, 4> mlir::linalg::concat(ArrayRef<AffineExpr> a, 1345 ArrayRef<AffineExpr> b) { 1346 auto rangeA = llvm::make_range(a.begin(), a.end()); 1347 auto rangeB = llvm::make_range(b.begin(), b.end()); 1348 auto concatRanges = llvm::concat<const AffineExpr>(rangeA, rangeB); 1349 return llvm::to_vector<4>(concatRanges); 1350 } 1351 1352 static void appendMangledType(llvm::raw_string_ostream &ss, Type t) { 1353 if (auto memref = t.dyn_cast<MemRefType>()) { 1354 ss << "view"; 1355 for (auto size : memref.getShape()) 1356 if (size < 0) 1357 ss << "sx"; 1358 else 1359 ss << size << "x"; 1360 appendMangledType(ss, memref.getElementType()); 1361 } else if (auto vec = t.dyn_cast<VectorType>()) { 1362 ss << "vector"; 1363 llvm::interleave( 1364 vec.getShape(), [&](int64_t i) { ss << i; }, [&]() { ss << "x"; }); 1365 appendMangledType(ss, vec.getElementType()); 1366 } else if (t.isSignlessIntOrIndexOrFloat()) { 1367 ss << t; 1368 } else { 1369 llvm_unreachable("Invalid type for linalg library name mangling"); 1370 } 1371 } 1372 1373 std::string mlir::linalg::generateLibraryCallName(Operation *op) { 1374 assert(isa<LinalgOp>(op)); 1375 std::string name(op->getName().getStringRef().str()); 1376 name.reserve(128); 1377 std::replace(name.begin(), name.end(), '.', '_'); 1378 llvm::raw_string_ostream ss(name); 1379 ss << "_"; 1380 auto types = op->getOperandTypes(); 1381 llvm::interleave( 1382 types.begin(), types.end(), [&](Type t) { appendMangledType(ss, t); }, 1383 [&]() { ss << "_"; }); 1384 return ss.str(); 1385 } 1386 1387 //===----------------------------------------------------------------------===// 1388 // Support for named Linalg ops defined in ods-gen. 1389 //===----------------------------------------------------------------------===// 1390 1391 /// Generic entry point to create the block for the region of a LinalgOp. 1392 /// This is used by both named structured ops created by ods-gen and by manually 1393 /// defined C++ ops. 1394 /// This is used by both builders and parsers. 1395 /// This function creates the block in the region with arguments corresponding 1396 /// to the elemental types of `inputTypes` and `outputTypes`, which are asserted 1397 /// to be ShapedType. 1398 template <typename NamedStructuredOpType> 1399 static void fillStructuredOpRegion( 1400 OpBuilder &opBuilder, Region ®ion, TypeRange inputTypes, 1401 TypeRange outputTypes, ArrayRef<NamedAttribute> attrs, 1402 llvm::function_ref<void(unsigned, unsigned)> errorHandler) { 1403 assert(llvm::all_of(outputTypes, [](Type t) { return t.isa<ShapedType>(); })); 1404 1405 // TODO: atm all operands go through getElementTypeOrSelf, 1406 // reconsider when we have evidence we need to. 1407 SmallVector<Type, 8> argTypes; 1408 SmallVector<Location, 8> argLocs; 1409 for (auto containers : {inputTypes, outputTypes}) { 1410 for (auto t : containers) { 1411 argTypes.push_back(getElementTypeOrSelf(t)); 1412 1413 // TODO: Pass in a proper location here. 1414 argLocs.push_back(opBuilder.getUnknownLoc()); 1415 } 1416 } 1417 1418 // RAII. 1419 OpBuilder::InsertionGuard guard(opBuilder); 1420 Block *body = 1421 opBuilder.createBlock(®ion, /*insertPt=*/{}, argTypes, argLocs); 1422 unsigned actual = body->getNumArguments(); 1423 unsigned expected = NamedStructuredOpType::getNumRegionArgs(); 1424 if (expected != actual) { 1425 if (errorHandler) 1426 errorHandler(expected, actual); 1427 return; 1428 } 1429 1430 opBuilder.setInsertionPointToStart(body); 1431 ImplicitLocOpBuilder b(opBuilder.getUnknownLoc(), opBuilder); 1432 NamedStructuredOpType::regionBuilder(b, *body, attrs); 1433 1434 // indexing_maps is an auto-generated method. 1435 1436 // iterator_types is an auto-generated method. 1437 } 1438 1439 /// Generic entry point to create both the region and the block of a LinalgOp. 1440 template <typename NamedStructuredOpType> 1441 void createAndFillStructuredOpRegion(OpBuilder &opBuilder, 1442 OperationState &result, 1443 TypeRange inputTypes, 1444 TypeRange outputTypes) { 1445 Region ®ion = *result.addRegion(); 1446 fillStructuredOpRegion<NamedStructuredOpType>( 1447 opBuilder, region, inputTypes, outputTypes, result.attributes.getAttrs(), 1448 [&](unsigned expected, unsigned actual) { 1449 assert(expected != actual && "incorrect number of arguments"); 1450 }); 1451 } 1452 1453 /// Common parsing used for both named structured ops created by ods-gen and by 1454 /// manually defined C++ ops. Does not handle regions. 1455 static ParseResult 1456 parseCommonStructuredOpParts(OpAsmParser &parser, OperationState &result, 1457 SmallVectorImpl<Type> &inputTypes, 1458 SmallVectorImpl<Type> &outputTypes) { 1459 SMLoc inputsOperandsLoc, outputsOperandsLoc; 1460 SmallVector<OpAsmParser::OperandType, 4> inputsOperands, outputsOperands; 1461 1462 parser.parseOptionalAttrDict(result.attributes); 1463 1464 if (succeeded(parser.parseOptionalKeyword("ins"))) { 1465 if (parser.parseLParen()) 1466 return failure(); 1467 1468 inputsOperandsLoc = parser.getCurrentLocation(); 1469 if (parser.parseOperandList(inputsOperands) || 1470 parser.parseColonTypeList(inputTypes) || parser.parseRParen()) 1471 return failure(); 1472 } 1473 1474 if (succeeded(parser.parseOptionalKeyword("outs"))) { 1475 outputsOperandsLoc = parser.getCurrentLocation(); 1476 if (parser.parseLParen() || parser.parseOperandList(outputsOperands) || 1477 parser.parseColonTypeList(outputTypes) || parser.parseRParen()) 1478 return failure(); 1479 } 1480 1481 if (parser.resolveOperands(inputsOperands, inputTypes, inputsOperandsLoc, 1482 result.operands) || 1483 parser.resolveOperands(outputsOperands, outputTypes, outputsOperandsLoc, 1484 result.operands)) 1485 return failure(); 1486 1487 result.addAttribute("operand_segment_sizes", 1488 parser.getBuilder().getI32VectorAttr( 1489 {static_cast<int32_t>(inputsOperands.size()), 1490 static_cast<int32_t>(outputsOperands.size())})); 1491 return success(); 1492 } 1493 1494 template <typename NamedStructuredOpType> 1495 static void printCommonStructuredOpParts(OpAsmPrinter &p, 1496 NamedStructuredOpType op) { 1497 if (!op.inputs().empty()) 1498 p << " ins(" << op.inputs() << " : " << op.inputs().getTypes() << ")"; 1499 if (!op.outputs().empty()) 1500 p << " outs(" << op.outputs() << " : " << op.outputs().getTypes() << ")"; 1501 } 1502 1503 //===----------------------------------------------------------------------===// 1504 // Specific parsing and printing for named structured ops created by ods-gen. 1505 //===----------------------------------------------------------------------===// 1506 1507 template <typename NamedStructuredOpType> 1508 static ParseResult 1509 parseNamedStructuredOpRegion(OpAsmParser &parser, Region ®ion, 1510 TypeRange inputTypes, TypeRange outputTypes, 1511 ArrayRef<NamedAttribute> attrs) { 1512 ParseResult res = success(); 1513 OpBuilder opBuilder(parser.getContext()); 1514 // Resolve `captures` into `capturedValues` at parse time so we can build the 1515 // region with captures. 1516 SmallVector<Value> capturedValues; 1517 fillStructuredOpRegion<NamedStructuredOpType>( 1518 opBuilder, region, inputTypes, outputTypes, attrs, 1519 [&](unsigned expected, unsigned actual) { 1520 res = parser.emitError( 1521 parser.getCurrentLocation(), 1522 llvm::formatv("[parseNamedStructuredOpRegion] ods-gen generated " 1523 "region expects {0} args, got {1}", 1524 expected, actual)); 1525 region.front().dump(); 1526 }); 1527 return res; 1528 } 1529 1530 static ParseResult 1531 parseNamedStructuredOpResults(OpAsmParser &parser, 1532 SmallVectorImpl<Type> &resultTypes) { 1533 if (parser.parseOptionalArrowTypeList(resultTypes)) 1534 return failure(); 1535 return success(); 1536 } 1537 1538 template <typename NamedStructuredOpType> 1539 static ParseResult parseNamedStructuredOp(OpAsmParser &parser, 1540 OperationState &result) { 1541 // TODO: Enable when ods-gen supports captures. 1542 SmallVector<Type, 1> inputTypes, outputTypes; 1543 if (parseCommonStructuredOpParts(parser, result, inputTypes, outputTypes)) 1544 return failure(); 1545 1546 // TODO: consider merging results parsing into region parsing. 1547 // Need to wait for declarative assembly resolution to decide. 1548 SmallVector<Type, 1> outputTensorsTypes; 1549 if (parseNamedStructuredOpResults(parser, outputTensorsTypes)) 1550 return failure(); 1551 result.addTypes(outputTensorsTypes); 1552 1553 std::unique_ptr<Region> region = std::make_unique<Region>(); 1554 if (parseNamedStructuredOpRegion<NamedStructuredOpType>( 1555 parser, *region, inputTypes, outputTypes, 1556 result.attributes.getAttrs())) 1557 return failure(); 1558 result.addRegion(std::move(region)); 1559 1560 return success(); 1561 } 1562 1563 static void printNamedStructuredOpResults(OpAsmPrinter &p, 1564 TypeRange resultTypes) { 1565 if (resultTypes.empty()) 1566 return; 1567 p.printOptionalArrowTypeList(resultTypes); 1568 } 1569 1570 template <typename NamedStructuredOpType> 1571 static void printNamedStructuredOp(OpAsmPrinter &p, NamedStructuredOpType op) { 1572 p.printOptionalAttrDict( 1573 op->getAttrs(), 1574 /*elidedAttrs=*/{"operand_segment_sizes", 1575 // See generated code in mlir-linalg-yaml-gen.cpp 1576 "linalg.memoized_indexing_maps"}); 1577 1578 // Printing is shared with generic ops, except for the region and 1579 // attributes. 1580 printCommonStructuredOpParts(p, op); 1581 1582 // Results printing. 1583 printNamedStructuredOpResults(p, op.result_tensors().getTypes()); 1584 1585 // Region is elided. 1586 } 1587 1588 template <typename NamedStructuredOpType> 1589 static LogicalResult verifyNamedStructuredOp(NamedStructuredOpType op) { 1590 return verifyGenericOp<NamedStructuredOpType>(op); 1591 } 1592 1593 //===----------------------------------------------------------------------===// 1594 // Canonicalizers and Folders. 1595 //===----------------------------------------------------------------------===// 1596 1597 namespace { 1598 struct EraseDeadLinalgOp : public OpInterfaceRewritePattern<LinalgOp> { 1599 using OpInterfaceRewritePattern<LinalgOp>::OpInterfaceRewritePattern; 1600 1601 LogicalResult matchAndRewrite(LinalgOp op, 1602 PatternRewriter &rewriter) const override { 1603 for (OpOperand *opOperand : op.getInputAndOutputOperands()) { 1604 // Linalg "inputs" may be either tensor or memref type. 1605 // tensor<0xelt_type> is a convention that may not always mean 1606 // "0 iterations". Only erase in cases we see memref<...x0x...>. 1607 auto mt = opOperand->get().getType().dyn_cast<MemRefType>(); 1608 if (!mt) 1609 continue; 1610 if (llvm::is_contained(op.getShape(opOperand), 0)) { 1611 rewriter.eraseOp(op); 1612 return success(); 1613 } 1614 } 1615 return failure(); 1616 } 1617 }; 1618 1619 struct FoldTensorCastProducerOp : public OpInterfaceRewritePattern<LinalgOp> { 1620 using OpInterfaceRewritePattern<LinalgOp>::OpInterfaceRewritePattern; 1621 1622 LogicalResult matchAndRewrite(LinalgOp op, 1623 PatternRewriter &rewriter) const override { 1624 // If no operand comes from a tensor::CastOp and can be folded then fail. 1625 bool hasTensorCastOperand = 1626 llvm::any_of(op.getInputAndOutputOperands(), [&](OpOperand *opOperand) { 1627 if (opOperand->get().isa<BlockArgument>()) 1628 return false; 1629 auto castOp = opOperand->get().getDefiningOp<tensor::CastOp>(); 1630 return castOp && canFoldIntoConsumerOp(castOp); 1631 }); 1632 if (!hasTensorCastOperand) 1633 return failure(); 1634 1635 SmallVector<Type, 4> newResultTypes; 1636 newResultTypes.reserve(op->getNumResults()); 1637 SmallVector<Value, 4> newOperands; 1638 newOperands.reserve(op->getNumOperands()); 1639 // Inputs may fold. 1640 for (OpOperand *opOperand : op.getInputOperands()) { 1641 auto tensorCastOp = opOperand->get().getDefiningOp<tensor::CastOp>(); 1642 newOperands.push_back(canFoldIntoConsumerOp(tensorCastOp) 1643 ? tensorCastOp.source() 1644 : opOperand->get()); 1645 } 1646 // Init tensors may fold, in which case the resultType must also change. 1647 for (OpOperand *opOperand : op.getOutputOperands()) { 1648 auto tensorCastOp = opOperand->get().getDefiningOp<tensor::CastOp>(); 1649 bool fold = canFoldIntoConsumerOp(tensorCastOp); 1650 newOperands.push_back(fold ? tensorCastOp.getOperand() 1651 : opOperand->get()); 1652 newResultTypes.push_back(newOperands.back().getType()); 1653 } 1654 // Clone op. 1655 Operation *newOp = 1656 op.clone(rewriter, op->getLoc(), newResultTypes, newOperands); 1657 SmallVector<Value, 4> replacements; 1658 replacements.reserve(newOp->getNumResults()); 1659 for (auto result : llvm::zip(op->getResults(), newOp->getResults())) { 1660 Value oldResult = std::get<0>(result); 1661 Value newResult = std::get<1>(result); 1662 if (newResult.getType() != oldResult.getType()) { 1663 replacements.push_back(rewriter.create<tensor::CastOp>( 1664 op->getLoc(), oldResult.getType(), newResult)); 1665 } else { 1666 replacements.push_back(newResult); 1667 } 1668 } 1669 rewriter.replaceOp(op, replacements); 1670 1671 return success(); 1672 } 1673 }; 1674 1675 /// Fold LinalgOps with `tensor.cast` consumer if the `tensor.cast` has 1676 /// result that is more static than the linalg op. 1677 struct FoldTensorCastConsumerOp : public OpRewritePattern<tensor::CastOp> { 1678 using OpRewritePattern<tensor::CastOp>::OpRewritePattern; 1679 1680 LogicalResult matchAndRewrite(tensor::CastOp castOp, 1681 PatternRewriter &rewriter) const override { 1682 if (!tensor::canFoldIntoProducerOp(castOp)) 1683 return failure(); 1684 auto linalgOp = castOp.source().getDefiningOp<LinalgOp>(); 1685 if (!linalgOp) 1686 return failure(); 1687 1688 OpBuilder::InsertionGuard guard(rewriter); 1689 rewriter.setInsertionPoint(linalgOp); 1690 1691 Location loc = linalgOp.getLoc(); 1692 OpResult resultValue = castOp.source().cast<OpResult>(); 1693 unsigned resultNumber = resultValue.getResultNumber(); 1694 auto resultType = castOp->getResult(0).getType().cast<RankedTensorType>(); 1695 // Replace the `outs` for the result with a `tensor.cast`. This cast is now 1696 // going from a more dynamic shape to a less dynamic shape. If the producer 1697 // for this cast, i.e. producer of the out operand, is also an operation 1698 // that folds with tensor.cast consumer (like this pattern), the cast will 1699 // continue to propagate as far up the stack as it can go. 1700 OpOperand *outOperand = linalgOp.getOutputOperand(resultNumber); 1701 Value newOperand = 1702 rewriter.create<tensor::CastOp>(loc, resultType, outOperand->get()); 1703 SmallVector<Value> newOperands = linalgOp.getInputOperands(); 1704 SmallVector<Value> outputOperands = linalgOp.getOutputOperands(); 1705 outputOperands[resultNumber] = newOperand; 1706 newOperands.append(outputOperands.begin(), outputOperands.end()); 1707 1708 SmallVector<Type> resultTypes(linalgOp->result_type_begin(), 1709 linalgOp->result_type_end()); 1710 resultTypes[resultNumber] = resultType; 1711 Operation *newOp = linalgOp.clone(rewriter, loc, resultTypes, newOperands); 1712 1713 // Create a tensor.cast operation back to the original type. 1714 Value castBack = rewriter.create<tensor::CastOp>( 1715 loc, resultValue.getType(), newOp->getResult(resultNumber)); 1716 1717 SmallVector<Value> results(newOp->result_begin(), newOp->result_end()); 1718 results[resultNumber] = castBack; 1719 rewriter.replaceOp(linalgOp, results); 1720 rewriter.replaceOp(castOp, newOp->getResult(resultNumber)); 1721 return success(); 1722 } 1723 }; 1724 1725 /// For each of the operand in `operands` this function maps the static sizes of 1726 /// dimensions to their affine dim expressions. 1727 static void populateMap(LinalgOp linalgOp, ArrayRef<OpOperand *> operands, 1728 llvm::DenseMap<AffineExpr, int64_t> &affineExprToSize) { 1729 for (OpOperand *opOperand : operands) { 1730 if (linalgOp.isScalar(opOperand)) 1731 continue; 1732 Value src = opOperand->get(); 1733 auto sourceType = src.getType().cast<RankedTensorType>(); 1734 auto sourceMap = linalgOp.getTiedIndexingMap(opOperand); 1735 1736 // Get the `sourceShape` of the `sourceType`. If the operand is a result of 1737 // `tensor.cast` operation and source of the cast operation has a static 1738 // shape, then assign it to the `sourceShape`. 1739 auto parentOp = src.getDefiningOp(); 1740 ArrayRef<int64_t> sourceShape = sourceType.getShape(); 1741 if (parentOp) { 1742 if (auto castOp = dyn_cast<tensor::CastOp>(parentOp)) { 1743 Value castSource = castOp.source(); 1744 auto castSourceType = castSource.getType().cast<RankedTensorType>(); 1745 if (castSourceType.hasStaticShape()) 1746 sourceShape = castSourceType.getShape(); 1747 } 1748 } 1749 1750 // If the source shape's dimension has a static shape, map the affine dim 1751 // expression to the known static size. 1752 for (unsigned i = 0; i < sourceShape.size(); i++) { 1753 if (sourceType.isDynamicDim(i)) 1754 continue; 1755 if (auto affineDimExpr = sourceMap.getResult(i).dyn_cast<AffineDimExpr>()) 1756 affineExprToSize.try_emplace(affineDimExpr, sourceShape[i]); 1757 } 1758 } 1759 } 1760 1761 /// Creates new operand w.r.t 'opOperand' of `linalgOp` with static sizes 1762 /// mapped in `affineExprToSize`. New operands are created in `newOperands` and 1763 /// their result types is stored in `resultTypes`. If `opOperand` requires no 1764 /// change then `changeNeeded` is false and same operand is added in the 1765 /// `newOperands` list. 1766 static void createNewOperandWithStaticSizes( 1767 Location loc, PatternRewriter &rewriter, OpOperand *opOperand, 1768 llvm::DenseMap<AffineExpr, int64_t> &affineExprToSize, LinalgOp linalgOp, 1769 SmallVector<Value> &newOperands, SmallVector<Type> &resultTypes, 1770 bool &changeNeeded) { 1771 Value src = opOperand->get(); 1772 newOperands.push_back(src); 1773 if (linalgOp.isScalar(opOperand)) 1774 return; 1775 auto sourceType = src.getType().cast<RankedTensorType>(); 1776 Type resultType = sourceType; 1777 if (sourceType.hasStaticShape() && linalgOp.isOutputTensor(opOperand)) { 1778 resultTypes.push_back(resultType); 1779 return; 1780 } 1781 ArrayRef<int64_t> sourceShape = sourceType.getShape(); 1782 AffineMap sourceMap = linalgOp.getTiedIndexingMap(opOperand); 1783 SmallVector<int64_t> newShape; 1784 // If operand is updated with new shape, `newOperandNeeded` will be 1785 // true. 1786 bool newOperandNeeded = false; 1787 for (unsigned i = 0; i < sourceShape.size(); i++) { 1788 int64_t dimShape = sourceShape[i]; 1789 AffineExpr dimExpr = sourceMap.getResult(i); 1790 if (affineExprToSize.find(dimExpr) == affineExprToSize.end() || 1791 !sourceType.isDynamicDim(i)) { 1792 newShape.push_back(dimShape); 1793 continue; 1794 } 1795 // Dimension has a dynamic shape and corresponding affine dim 1796 // expression is present in the map. So assign the size for the 1797 // given affine dim expression to the dimension. 1798 newShape.push_back(affineExprToSize[dimExpr]); 1799 newOperandNeeded = true; 1800 } 1801 resultType = RankedTensorType::get(newShape, sourceType.getElementType()); 1802 if (newOperandNeeded) { 1803 changeNeeded = true; 1804 // Get the new operand value given its size and element type by 1805 // casting it. 1806 Value newOperand = rewriter.create<tensor::CastOp>(loc, resultType, src); 1807 unsigned index = opOperand->getOperandNumber(); 1808 newOperands[index] = newOperand; 1809 } 1810 if (linalgOp.isOutputTensor(opOperand)) 1811 resultTypes.push_back(resultType); 1812 } 1813 1814 /// Static shapes for the operands can be inferred if any one of the operands 1815 /// have a static shape. This can be done by referring to the affine dim 1816 /// expressions for the operand. 1817 struct InferStaticShapeOfOperands : public OpInterfaceRewritePattern<LinalgOp> { 1818 using OpInterfaceRewritePattern<LinalgOp>::OpInterfaceRewritePattern; 1819 1820 LogicalResult matchAndRewrite(LinalgOp linalgOp, 1821 PatternRewriter &rewriter) const override { 1822 if (!linalgOp.hasTensorSemantics()) 1823 return failure(); 1824 1825 // Maps must be projected permutations. 1826 if (llvm::any_of(linalgOp.getIndexingMaps(), [](AffineMap map) { 1827 return !map.isProjectedPermutation(); 1828 })) 1829 return failure(); 1830 1831 // Maps affine dim expressions to the static size of that dimension. 1832 llvm::DenseMap<AffineExpr, int64_t> affineExprToSize; 1833 Location loc = linalgOp.getLoc(); 1834 1835 // For each of the affine dim expression, check if the size is known. If 1836 // known add that in the map. 1837 populateMap(linalgOp, linalgOp.getInputAndOutputOperands(), 1838 affineExprToSize); 1839 1840 SmallVector<Value> newOperands; 1841 SmallVector<Type> resultTypes; 1842 1843 // `changeNeeded` is `false` if the operands of `linalgOp` require no 1844 // change in their types. 1845 bool changeNeeded = false; 1846 newOperands.reserve(linalgOp.getNumInputsAndOutputs()); 1847 resultTypes.reserve(linalgOp.getNumOutputs()); 1848 1849 // Iterate over all the operands and update the static sizes. 1850 for (OpOperand *opOperand : linalgOp.getInputAndOutputOperands()) { 1851 createNewOperandWithStaticSizes(loc, rewriter, opOperand, 1852 affineExprToSize, linalgOp, newOperands, 1853 resultTypes, changeNeeded); 1854 } 1855 1856 // If the generic op has all the required static information, no 1857 // canonicalization needed. 1858 if (!changeNeeded) 1859 return failure(); 1860 1861 // Clone op. 1862 Operation *newOp = 1863 linalgOp.clone(rewriter, linalgOp->getLoc(), resultTypes, newOperands); 1864 SmallVector<Value> replacements; 1865 replacements.reserve(newOp->getNumResults()); 1866 for (auto it : llvm::zip(linalgOp->getResults(), newOp->getResults())) { 1867 Value newResult = std::get<1>(it); 1868 Value oldResult = std::get<0>(it); 1869 Type newType = newResult.getType(); 1870 Type oldType = oldResult.getType(); 1871 replacements.push_back( 1872 (newType != oldType) 1873 ? rewriter.create<tensor::CastOp>(loc, oldType, newResult) 1874 : newResult); 1875 } 1876 rewriter.replaceOp(linalgOp, replacements); 1877 return success(); 1878 } 1879 }; 1880 1881 } // namespace 1882 1883 // All named ops canonicalizers and folders are auto-generated in the 1884 // .cpp.inc. 1885 1886 //===----------------------------------------------------------------------===// 1887 // LinalgDialect 1888 //===----------------------------------------------------------------------===// 1889 1890 void LinalgDialect::getCanonicalizationPatterns( 1891 RewritePatternSet &results) const { 1892 results.add<EraseDeadLinalgOp, FoldTensorCastConsumerOp, 1893 FoldTensorCastProducerOp, InferStaticShapeOfOperands>( 1894 getContext()); 1895 } 1896 1897 Operation *LinalgDialect::materializeConstant(OpBuilder &builder, 1898 Attribute value, Type type, 1899 Location loc) { 1900 return builder.create<arith::ConstantOp>(loc, type, value); 1901 } 1902