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::UnresolvedOperand, 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 if (!tensor::CastOp::areCastCompatible(returnedArg.getType(), 841 resultType)) 842 return failure(); 843 returnedArg = rewriter.create<tensor::CastOp>( 844 genericOp.getLoc(), resultType, returnedArg); 845 } 846 } 847 returnedArgs.push_back(returnedArg); 848 } 849 850 if (returnedArgs.size() != genericOp->getNumResults()) 851 return failure(); 852 rewriter.replaceOp(genericOp, returnedArgs); 853 return success(); 854 } 855 }; 856 857 /// Drop dead args of a linalg generic op. 858 /// An arg is dead if it has zero uses in the op region. 859 struct DeadArgsGenericOpInputs : public OpRewritePattern<GenericOp> { 860 using OpRewritePattern<GenericOp>::OpRewritePattern; 861 LogicalResult matchAndRewrite(GenericOp genericOp, 862 PatternRewriter &rewriter) const override { 863 SmallVector<AffineMap> oldIndexingMaps = genericOp.getIndexingMaps(); 864 // Maps must be projected permutations. 865 if (llvm::any_of(genericOp.getIndexingMaps(), [](AffineMap map) { 866 return !map.isProjectedPermutation(); 867 })) 868 return failure(); 869 Block &payload = genericOp.region().front(); 870 SmallVector<Value> newInputOperands; 871 SmallVector<AffineMap> newIndexingMaps; 872 bool deadArgFound = false; 873 int inputSize = genericOp.getInputOperands().size(); 874 for (int i = inputSize - 1; i >= 0; i--) { 875 OpOperand *opOperand = genericOp.getInputOperand(i); 876 // Iterate in reverse, so that we erase later args first, preventing the 877 // argument list from shifting unexpectedly and invalidating all our 878 // indices. 879 if (payload.getArgument(i).use_empty() && 880 !hasaUniqueDim(oldIndexingMaps, i)) { 881 payload.eraseArgument(i); 882 deadArgFound = true; 883 // remove this indexing map out of consideration for hasaUniqueDim check 884 oldIndexingMaps.erase(oldIndexingMaps.begin() + i); 885 } else { 886 newInputOperands.insert(newInputOperands.begin(), opOperand->get()); 887 newIndexingMaps.insert(newIndexingMaps.begin(), 888 genericOp.getTiedIndexingMap(opOperand)); 889 } 890 } 891 // Bail out if there are no dead args. 892 if (!deadArgFound) 893 return failure(); 894 for (OpOperand *opOperand : genericOp.getOutputOperands()) 895 newIndexingMaps.push_back(genericOp.getTiedIndexingMap(opOperand)); 896 SmallVector<Value> outputOperands = genericOp.getOutputOperands(); 897 898 auto newOp = rewriter.create<GenericOp>( 899 genericOp.getLoc(), genericOp->getResultTypes(), newInputOperands, 900 outputOperands, rewriter.getAffineMapArrayAttr(newIndexingMaps), 901 genericOp.iterator_types(), genericOp.docAttr(), 902 genericOp.library_callAttr()); 903 // Copy over unknown attributes. They might be load bearing for some flow. 904 ArrayRef<StringRef> odsAttrs = genericOp.getAttributeNames(); 905 for (NamedAttribute kv : genericOp->getAttrs()) { 906 if (!llvm::is_contained(odsAttrs, kv.getName().getValue())) { 907 newOp->setAttr(kv.getName(), kv.getValue()); 908 } 909 } 910 rewriter.inlineRegionBefore(genericOp.region(), newOp.region(), 911 newOp.region().begin()); 912 rewriter.replaceOp(genericOp, newOp->getResults()); 913 return success(); 914 } 915 }; 916 } // namespace 917 918 void GenericOp::getCanonicalizationPatterns(RewritePatternSet &results, 919 MLIRContext *context) { 920 results.add<DeduplicateGenericOpInputs, EraseIdentityGenericOp, 921 DeadArgsGenericOpInputs>(context); 922 } 923 924 LogicalResult GenericOp::fold(ArrayRef<Attribute>, 925 SmallVectorImpl<OpFoldResult> &) { 926 return foldMemRefCast(*this); 927 } 928 929 //===----------------------------------------------------------------------===// 930 // InitTensorOp 931 //===----------------------------------------------------------------------===// 932 933 void InitTensorOp::build(OpBuilder &b, OperationState &result, 934 ArrayRef<OpFoldResult> sizes, Type elementType, 935 ArrayRef<NamedAttribute> attrs) { 936 SmallVector<Value, 4> dynamicSizes; 937 SmallVector<int64_t, 4> staticSizes; 938 dispatchIndexOpFoldResults(sizes, dynamicSizes, staticSizes, 939 ShapedType::kDynamicSize); 940 auto resultType = RankedTensorType ::get(staticSizes, elementType); 941 build(b, result, resultType, dynamicSizes, b.getI64ArrayAttr(staticSizes)); 942 result.addAttributes(attrs); 943 } 944 945 LogicalResult InitTensorOp::verify() { 946 RankedTensorType resultType = getType(); 947 SmallVector<int64_t, 4> staticSizes = llvm::to_vector<4>(llvm::map_range( 948 static_sizes().cast<ArrayAttr>(), 949 [](Attribute a) -> int64_t { return a.cast<IntegerAttr>().getInt(); })); 950 951 if (failed(verifyListOfOperandsOrIntegers( 952 *this, "sizes", resultType.getRank(), static_sizes(), sizes(), 953 ShapedType::isDynamic))) 954 return failure(); 955 956 if (static_sizes().size() != static_cast<unsigned>(resultType.getRank())) 957 return emitError("expected ") << resultType.getRank() << " sizes values"; 958 959 Type expectedType = InitTensorOp::inferResultType( 960 staticSizes, resultType.getElementType(), resultType.getEncoding()); 961 if (resultType != expectedType) { 962 return emitError("specified type ") 963 << resultType << " does not match the inferred type " 964 << expectedType; 965 } 966 return success(); 967 } 968 969 Type InitTensorOp::inferResultType(ArrayRef<int64_t> staticSizes, 970 Type elementType, Attribute encoding) { 971 return RankedTensorType::get(staticSizes, elementType, encoding); 972 } 973 974 SmallVector<OpFoldResult> InitTensorOp::getMixedSizes() { 975 SmallVector<OpFoldResult> mixedSizes; 976 mixedSizes.reserve(getType().getRank()); 977 unsigned dynamicValIndex = 0; 978 for (Attribute attr : static_sizes()) { 979 auto intAttr = attr.cast<IntegerAttr>(); 980 if (!ShapedType::isDynamic(intAttr.getInt())) { 981 mixedSizes.push_back(intAttr); 982 continue; 983 } 984 mixedSizes.push_back(sizes()[dynamicValIndex++]); 985 } 986 return mixedSizes; 987 } 988 989 namespace { 990 /// Change the type of the result of a `linalg.init_tensor` by making the result 991 /// type statically sized along dimension that in the original operation where 992 /// defined as dynamic, but the size was defined using a `constant` op. For 993 /// example 994 /// 995 /// %c5 = arith.constant 5: index 996 /// %0 = linalg.init_tensor [%arg0, %c5] : tensor<?x?xf32> 997 /// 998 /// to 999 /// 1000 /// %0 = linalg.init_tensor [%arg0, 5] : tensor<?x5xf32> 1001 struct ReplaceStaticShapeDims : OpRewritePattern<InitTensorOp> { 1002 using OpRewritePattern<InitTensorOp>::OpRewritePattern; 1003 1004 LogicalResult matchAndRewrite(InitTensorOp op, 1005 PatternRewriter &rewriter) const override { 1006 SmallVector<Value, 4> dynamicSizes; 1007 SmallVector<int64_t, 4> staticSizes; 1008 for (unsigned i = 0, e = op.getType().getRank(); i != e; ++i) { 1009 // If the size is already static, nothing to do. 1010 if (!op.isDynamicSize(i)) { 1011 staticSizes.push_back(op.getStaticSize(i)); 1012 continue; 1013 } 1014 1015 // If the size is dynamic but defined using a `constant` op, get the 1016 // constant value to find the static size to use. 1017 unsigned operandNum = op.getIndexOfDynamicSize(i); 1018 Value sizeOperand = op.getOperand(operandNum); 1019 if (auto constantIndexOp = 1020 sizeOperand.getDefiningOp<arith::ConstantIndexOp>()) { 1021 staticSizes.push_back(constantIndexOp.value()); 1022 continue; 1023 } 1024 1025 // Fallback case. Keep the size dynamic. 1026 dynamicSizes.push_back(sizeOperand); 1027 staticSizes.push_back(ShapedType::kDynamicSize); 1028 } 1029 RankedTensorType newType = 1030 RankedTensorType::get(staticSizes, op.getType().getElementType()); 1031 if (newType == op.getType()) 1032 return failure(); 1033 auto newOp = 1034 rewriter.create<InitTensorOp>(op.getLoc(), newType, dynamicSizes, 1035 rewriter.getI64ArrayAttr(staticSizes)); 1036 rewriter.replaceOpWithNewOp<tensor::CastOp>(op, op.getType(), newOp); 1037 return success(); 1038 } 1039 }; 1040 } // namespace 1041 1042 namespace { 1043 /// Since `init_tensor` operation creates a tensor needed only for its shape, a 1044 /// slice of this is also needed only for its shape. The result can be 1045 /// replaced by a new init_tensor operation of the same size as the extract 1046 /// slice op. 1047 struct FoldInitTensorWithExtractSliceOp 1048 : public OpRewritePattern<tensor::ExtractSliceOp> { 1049 using OpRewritePattern<tensor::ExtractSliceOp>::OpRewritePattern; 1050 1051 LogicalResult matchAndRewrite(tensor::ExtractSliceOp sliceOp, 1052 PatternRewriter &rewriter) const override { 1053 if (!sliceOp.source().getDefiningOp<linalg::InitTensorOp>()) 1054 return failure(); 1055 // ExtractSliceOp may be rank-reducing; its dynamic sizes must be preserved 1056 // as well as its result type. 1057 rewriter.replaceOpWithNewOp<linalg::InitTensorOp>( 1058 sliceOp, sliceOp.sizes(), 1059 sliceOp.result().getType().cast<RankedTensorType>().getShape(), 1060 sliceOp.getSourceType().getElementType()); 1061 return success(); 1062 } 1063 }; 1064 1065 template <typename TensorReshapeOp> 1066 struct FoldInitTensorWithTensorReshapeOp 1067 : public OpRewritePattern<TensorReshapeOp> { 1068 using OpRewritePattern<TensorReshapeOp>::OpRewritePattern; 1069 1070 LogicalResult matchAndRewrite(TensorReshapeOp reshapeOp, 1071 PatternRewriter &rewriter) const override { 1072 if (!reshapeOp.src().template getDefiningOp<InitTensorOp>()) 1073 return failure(); 1074 Location loc = reshapeOp.getLoc(); 1075 ReifiedRankedShapedTypeDims resultShapes; 1076 ReifyRankedShapedTypeOpInterface reifyShapedTypeInterface = 1077 cast<ReifyRankedShapedTypeOpInterface>(reshapeOp.getOperation()); 1078 if (failed(reifyShapedTypeInterface.reifyResultShapes(rewriter, 1079 resultShapes)) || 1080 !llvm::hasSingleElement(resultShapes)) 1081 return failure(); 1082 Value initTensor = rewriter.create<InitTensorOp>( 1083 loc, getAsOpFoldResult(resultShapes[0]), 1084 reshapeOp.getResultType().getElementType()); 1085 if (initTensor.getType() != reshapeOp.getResultType()) { 1086 rewriter.replaceOpWithNewOp<tensor::CastOp>( 1087 reshapeOp, reshapeOp.getResultType(), initTensor); 1088 } else { 1089 rewriter.replaceOp(reshapeOp, initTensor); 1090 } 1091 return success(); 1092 } 1093 }; 1094 1095 struct FoldInitTensorWithDimOp : public OpRewritePattern<tensor::DimOp> { 1096 using OpRewritePattern<tensor::DimOp>::OpRewritePattern; 1097 1098 LogicalResult matchAndRewrite(tensor::DimOp dimOp, 1099 PatternRewriter &rewriter) const override { 1100 Optional<int64_t> maybeConstantIndex = dimOp.getConstantIndex(); 1101 auto initTensorOp = dimOp.source().getDefiningOp<linalg::InitTensorOp>(); 1102 if (!initTensorOp || !maybeConstantIndex) 1103 return failure(); 1104 if (!initTensorOp.isDynamicSize(*maybeConstantIndex)) 1105 return failure(); 1106 rewriter.replaceOp(dimOp, initTensorOp.getDynamicSize(*maybeConstantIndex)); 1107 return success(); 1108 } 1109 }; 1110 1111 /// Canonicalize 1112 /// 1113 /// ```mlir 1114 /// %0 = linalg.init_tensor [%d0, %d1] : tensor<?x?xf32> 1115 /// %1 = tensor.cast %0 : tensor<?x?xf32> to tensor<4x?xf32> 1116 /// ``` 1117 /// 1118 /// into 1119 /// 1120 /// ```mlir 1121 /// %0 = linalg.init_tensor [4, %d1] : tensor<4x?xf32> 1122 /// ``` 1123 /// 1124 /// This assumes the input program is correct in terms of its shape. So it 1125 /// is safe to assume that `%d0` is in fact 4. If that was not the case, the 1126 /// input program is wrong to begin with, so its undefined behavior anyway (i.e. 1127 /// this optimization can still triggering without violating program semantics). 1128 struct FoldInitTensorWithTensorCastOp 1129 : public OpRewritePattern<tensor::CastOp> { 1130 using OpRewritePattern<tensor::CastOp>::OpRewritePattern; 1131 1132 LogicalResult matchAndRewrite(tensor::CastOp castOp, 1133 PatternRewriter &rewriter) const override { 1134 if (!canFoldIntoProducerOp(castOp)) 1135 return failure(); 1136 auto producer = castOp.source().getDefiningOp<InitTensorOp>(); 1137 if (!producer) 1138 return failure(); 1139 1140 auto resultType = castOp->getResult(0).getType().cast<RankedTensorType>(); 1141 ArrayRef<int64_t> resultShape = resultType.getShape(); 1142 SmallVector<OpFoldResult> currMixedSizes = producer.getMixedSizes(); 1143 SmallVector<OpFoldResult> newMixedSizes; 1144 newMixedSizes.reserve(currMixedSizes.size()); 1145 assert(resultShape.size() == currMixedSizes.size() && 1146 "mismatch in result shape and sizes of init_tensor op"); 1147 for (auto it : llvm::zip(resultShape, currMixedSizes)) { 1148 int64_t newDim = std::get<0>(it); 1149 OpFoldResult currDim = std::get<1>(it); 1150 // Case 1: The init tensor dim is static. Check that the tensor cast 1151 // result dim matches. 1152 if (auto attr = currDim.dyn_cast<Attribute>()) { 1153 if (ShapedType::isDynamic(newDim) || 1154 newDim != attr.cast<IntegerAttr>().getInt()) { 1155 // Something is off, the cast result shape cannot be more dynamic than 1156 // the init tensor result shape (enforced by `canFoldIntoProducer`). 1157 // Abort for now. 1158 return rewriter.notifyMatchFailure( 1159 producer, "mismatch in static value of shape of init " 1160 "tensor result and cast result"); 1161 } 1162 newMixedSizes.push_back(attr); 1163 continue; 1164 } 1165 1166 // Case 2 : The tensor cast shape is static, but init tensor result shape 1167 // is dynamic. 1168 if (!ShapedType::isDynamic(newDim)) { 1169 newMixedSizes.push_back(rewriter.getIndexAttr(newDim)); 1170 continue; 1171 } 1172 1173 // Case 3 : The tensor cast shape is dynamic and init tensor result shape 1174 // is dynamic. Use the dynamic value from the init tensor op. 1175 newMixedSizes.push_back(currDim); 1176 } 1177 1178 rewriter.replaceOpWithNewOp<InitTensorOp>(castOp, newMixedSizes, 1179 resultType.getElementType()); 1180 return success(); 1181 } 1182 }; 1183 1184 } // namespace 1185 1186 void InitTensorOp::getCanonicalizationPatterns(RewritePatternSet &results, 1187 MLIRContext *context) { 1188 results.add<FoldInitTensorWithTensorCastOp, FoldInitTensorWithDimOp, 1189 FoldInitTensorWithExtractSliceOp, 1190 FoldInitTensorWithTensorReshapeOp<tensor::ExpandShapeOp>, 1191 FoldInitTensorWithTensorReshapeOp<tensor::CollapseShapeOp>, 1192 ReplaceStaticShapeDims>(context); 1193 } 1194 1195 LogicalResult InitTensorOp::reifyResultShapes( 1196 OpBuilder &builder, ReifiedRankedShapedTypeDims &reifiedReturnShapes) { 1197 auto shapes = llvm::to_vector<4>(llvm::map_range( 1198 llvm::seq<int64_t>(0, getType().getRank()), [&](int64_t dim) -> Value { 1199 if (isDynamicSize(dim)) 1200 return getDynamicSize(dim); 1201 return builder.create<arith::ConstantIndexOp>(getLoc(), 1202 getStaticSize(dim)); 1203 })); 1204 reifiedReturnShapes.emplace_back(std::move(shapes)); 1205 return success(); 1206 } 1207 1208 //===----------------------------------------------------------------------===// 1209 // YieldOp 1210 //===----------------------------------------------------------------------===// 1211 1212 void linalg::YieldOp::print(OpAsmPrinter &p) { 1213 if (getNumOperands() > 0) 1214 p << ' ' << getOperands(); 1215 p.printOptionalAttrDict((*this)->getAttrs()); 1216 if (getNumOperands() > 0) 1217 p << " : " << getOperandTypes(); 1218 } 1219 1220 ParseResult YieldOp::parse(OpAsmParser &parser, OperationState &result) { 1221 SmallVector<OpAsmParser::UnresolvedOperand, 2> opInfo; 1222 SmallVector<Type, 2> types; 1223 SMLoc loc = parser.getCurrentLocation(); 1224 return failure(parser.parseOperandList(opInfo) || 1225 parser.parseOptionalAttrDict(result.attributes) || 1226 (!opInfo.empty() && parser.parseColonTypeList(types)) || 1227 parser.resolveOperands(opInfo, types, loc, result.operands)); 1228 } 1229 1230 // Check the operand number and types must match the element types of the 1231 // LinalgOp interface's shaped operands. 1232 static LogicalResult verifyYield(linalg::YieldOp op, LinalgOp linalgOp) { 1233 if (op.getNumOperands() != linalgOp.getNumOutputs()) 1234 return op.emitOpError("expected number of yield values (") 1235 << linalgOp.getNumOutputs() 1236 << ") to match the number of operands of the enclosing " 1237 << "LinalgOp (" << op.getNumOperands() << ")"; 1238 1239 for (OpOperand &opOperand : op->getOpOperands()) { 1240 OpOperand *outputOperand = 1241 linalgOp.getOutputOperand(opOperand.getOperandNumber()); 1242 Type elementType = getElementTypeOrSelf(outputOperand->get().getType()); 1243 if (opOperand.get().getType() != elementType) 1244 return op.emitOpError("type of yield operand ") 1245 << (opOperand.getOperandNumber() + 1) << " (" 1246 << opOperand.get().getType() << ") doesn't match " 1247 << "the element type of the enclosing linalg.generic op (" 1248 << elementType << ")"; 1249 } 1250 return success(); 1251 } 1252 1253 LogicalResult linalg::YieldOp::verify() { 1254 auto *parentOp = (*this)->getParentOp(); 1255 if (parentOp->getNumRegions() != 1 || parentOp->getRegion(0).empty()) 1256 return emitOpError("expected single non-empty parent region"); 1257 1258 if (auto linalgOp = dyn_cast<LinalgOp>(parentOp)) 1259 return verifyYield(*this, linalgOp); 1260 1261 return emitOpError("expected parent op with LinalgOp interface"); 1262 } 1263 1264 //===----------------------------------------------------------------------===// 1265 // IndexOp 1266 //===----------------------------------------------------------------------===// 1267 1268 LogicalResult IndexOp::verify() { 1269 auto linalgOp = dyn_cast<LinalgOp>((*this)->getParentOp()); 1270 if (!linalgOp) 1271 return emitOpError("expected parent op with LinalgOp interface"); 1272 if (linalgOp.getNumLoops() <= dim()) 1273 return emitOpError("expected dim (") 1274 << dim() << ") to be lower than the number of loops (" 1275 << linalgOp.getNumLoops() << ") of the enclosing LinalgOp"; 1276 return success(); 1277 } 1278 1279 /////// Operations corresponding to library calls defined with Tablegen //////// 1280 1281 #include "mlir/Dialect/Linalg/IR/LinalgNamedStructuredOps.yamlgen.cpp.inc" 1282 1283 #define GET_OP_CLASSES 1284 #include "mlir/Dialect/Linalg/IR/LinalgOps.cpp.inc" 1285 1286 #define GET_OP_CLASSES 1287 #include "mlir/Dialect/Linalg/IR/LinalgStructuredOps.cpp.inc" 1288 1289 /// Return the dims that are `iteratorTypeName` loops in the LinalgOp `op`. 1290 /// Assumes `op` is a LinalgOp. 1291 void mlir::linalg::getDimsOfType(Operation *op, StringRef iteratorTypeName, 1292 SmallVectorImpl<unsigned> &res) { 1293 if (!cast<LinalgOp>(op).iterator_types()) 1294 return; 1295 1296 unsigned dim = 0; 1297 for (auto tn : 1298 cast<LinalgOp>(op).iterator_types().getAsValueRange<StringAttr>()) { 1299 if (tn == iteratorTypeName) 1300 res.push_back(dim); 1301 ++dim; 1302 } 1303 } 1304 1305 AffineMap mlir::linalg::extractOrIdentityMap(Optional<AffineMap> maybeMap, 1306 unsigned rank, 1307 MLIRContext *context) { 1308 if (maybeMap) 1309 return maybeMap.getValue(); 1310 if (rank == 0) 1311 return AffineMap::get(context); 1312 return AffineMap::getMultiDimIdentityMap(rank, context); 1313 } 1314 1315 SmallVector<AffineExpr, 4> 1316 mlir::linalg::makeAffineDimExprs(unsigned num, unsigned &startIdx, 1317 MLIRContext *context) { 1318 SmallVector<AffineExpr, 4> res; 1319 res.reserve(num); 1320 for (unsigned i = 0; i < num; ++i) 1321 res.push_back(getAffineDimExpr(startIdx++, context)); 1322 return res; 1323 } 1324 1325 SmallVector<AffineExpr, 4> mlir::linalg::concat(ArrayRef<AffineExpr> a, 1326 ArrayRef<AffineExpr> b) { 1327 auto rangeA = llvm::make_range(a.begin(), a.end()); 1328 auto rangeB = llvm::make_range(b.begin(), b.end()); 1329 auto concatRanges = llvm::concat<const AffineExpr>(rangeA, rangeB); 1330 return llvm::to_vector<4>(concatRanges); 1331 } 1332 1333 static void appendMangledType(llvm::raw_string_ostream &ss, Type t) { 1334 if (auto memref = t.dyn_cast<MemRefType>()) { 1335 ss << "view"; 1336 for (auto size : memref.getShape()) 1337 if (size < 0) 1338 ss << "sx"; 1339 else 1340 ss << size << "x"; 1341 appendMangledType(ss, memref.getElementType()); 1342 } else if (auto vec = t.dyn_cast<VectorType>()) { 1343 ss << "vector"; 1344 llvm::interleave( 1345 vec.getShape(), [&](int64_t i) { ss << i; }, [&]() { ss << "x"; }); 1346 appendMangledType(ss, vec.getElementType()); 1347 } else if (t.isSignlessIntOrIndexOrFloat()) { 1348 ss << t; 1349 } else { 1350 llvm_unreachable("Invalid type for linalg library name mangling"); 1351 } 1352 } 1353 1354 std::string mlir::linalg::generateLibraryCallName(Operation *op) { 1355 assert(isa<LinalgOp>(op)); 1356 std::string name(op->getName().getStringRef().str()); 1357 name.reserve(128); 1358 std::replace(name.begin(), name.end(), '.', '_'); 1359 llvm::raw_string_ostream ss(name); 1360 ss << "_"; 1361 auto types = op->getOperandTypes(); 1362 llvm::interleave( 1363 types.begin(), types.end(), [&](Type t) { appendMangledType(ss, t); }, 1364 [&]() { ss << "_"; }); 1365 return ss.str(); 1366 } 1367 1368 //===----------------------------------------------------------------------===// 1369 // Support for named Linalg ops defined in ods-gen. 1370 //===----------------------------------------------------------------------===// 1371 1372 /// Generic entry point to create the block for the region of a LinalgOp. 1373 /// This is used by both named structured ops created by ods-gen and by manually 1374 /// defined C++ ops. 1375 /// This is used by both builders and parsers. 1376 /// This function creates the block in the region with arguments corresponding 1377 /// to the elemental types of `inputTypes` and `outputTypes`, which are asserted 1378 /// to be ShapedType. 1379 template <typename NamedStructuredOpType> 1380 static void fillStructuredOpRegion( 1381 OpBuilder &opBuilder, Region ®ion, TypeRange inputTypes, 1382 TypeRange outputTypes, ArrayRef<NamedAttribute> attrs, 1383 llvm::function_ref<void(unsigned, unsigned)> errorHandler) { 1384 assert(llvm::all_of(outputTypes, [](Type t) { return t.isa<ShapedType>(); })); 1385 1386 // TODO: atm all operands go through getElementTypeOrSelf, 1387 // reconsider when we have evidence we need to. 1388 SmallVector<Type, 8> argTypes; 1389 SmallVector<Location, 8> argLocs; 1390 for (auto containers : {inputTypes, outputTypes}) { 1391 for (auto t : containers) { 1392 argTypes.push_back(getElementTypeOrSelf(t)); 1393 1394 // TODO: Pass in a proper location here. 1395 argLocs.push_back(opBuilder.getUnknownLoc()); 1396 } 1397 } 1398 1399 // RAII. 1400 OpBuilder::InsertionGuard guard(opBuilder); 1401 Block *body = 1402 opBuilder.createBlock(®ion, /*insertPt=*/{}, argTypes, argLocs); 1403 unsigned actual = body->getNumArguments(); 1404 unsigned expected = NamedStructuredOpType::getNumRegionArgs(); 1405 if (expected != actual) { 1406 if (errorHandler) 1407 errorHandler(expected, actual); 1408 return; 1409 } 1410 1411 opBuilder.setInsertionPointToStart(body); 1412 ImplicitLocOpBuilder b(opBuilder.getUnknownLoc(), opBuilder); 1413 NamedStructuredOpType::regionBuilder(b, *body, attrs); 1414 1415 // indexing_maps is an auto-generated method. 1416 1417 // iterator_types is an auto-generated method. 1418 } 1419 1420 /// Generic entry point to create both the region and the block of a LinalgOp. 1421 template <typename NamedStructuredOpType> 1422 void createAndFillStructuredOpRegion(OpBuilder &opBuilder, 1423 OperationState &result, 1424 TypeRange inputTypes, 1425 TypeRange outputTypes) { 1426 Region ®ion = *result.addRegion(); 1427 fillStructuredOpRegion<NamedStructuredOpType>( 1428 opBuilder, region, inputTypes, outputTypes, result.attributes.getAttrs(), 1429 [&](unsigned expected, unsigned actual) { 1430 assert(expected != actual && "incorrect number of arguments"); 1431 }); 1432 } 1433 1434 /// Common parsing used for both named structured ops created by ods-gen and by 1435 /// manually defined C++ ops. Does not handle regions. 1436 static ParseResult 1437 parseCommonStructuredOpParts(OpAsmParser &parser, OperationState &result, 1438 SmallVectorImpl<Type> &inputTypes, 1439 SmallVectorImpl<Type> &outputTypes) { 1440 SMLoc inputsOperandsLoc, outputsOperandsLoc; 1441 SmallVector<OpAsmParser::UnresolvedOperand, 4> inputsOperands, 1442 outputsOperands; 1443 1444 parser.parseOptionalAttrDict(result.attributes); 1445 1446 if (succeeded(parser.parseOptionalKeyword("ins"))) { 1447 if (parser.parseLParen()) 1448 return failure(); 1449 1450 inputsOperandsLoc = parser.getCurrentLocation(); 1451 if (parser.parseOperandList(inputsOperands) || 1452 parser.parseColonTypeList(inputTypes) || parser.parseRParen()) 1453 return failure(); 1454 } 1455 1456 if (succeeded(parser.parseOptionalKeyword("outs"))) { 1457 outputsOperandsLoc = parser.getCurrentLocation(); 1458 if (parser.parseLParen() || parser.parseOperandList(outputsOperands) || 1459 parser.parseColonTypeList(outputTypes) || parser.parseRParen()) 1460 return failure(); 1461 } 1462 1463 if (parser.resolveOperands(inputsOperands, inputTypes, inputsOperandsLoc, 1464 result.operands) || 1465 parser.resolveOperands(outputsOperands, outputTypes, outputsOperandsLoc, 1466 result.operands)) 1467 return failure(); 1468 1469 result.addAttribute("operand_segment_sizes", 1470 parser.getBuilder().getI32VectorAttr( 1471 {static_cast<int32_t>(inputsOperands.size()), 1472 static_cast<int32_t>(outputsOperands.size())})); 1473 return success(); 1474 } 1475 1476 template <typename NamedStructuredOpType> 1477 static void printCommonStructuredOpParts(OpAsmPrinter &p, 1478 NamedStructuredOpType op) { 1479 if (!op.inputs().empty()) 1480 p << " ins(" << op.inputs() << " : " << op.inputs().getTypes() << ")"; 1481 if (!op.outputs().empty()) 1482 p << " outs(" << op.outputs() << " : " << op.outputs().getTypes() << ")"; 1483 } 1484 1485 //===----------------------------------------------------------------------===// 1486 // Specific parsing and printing for named structured ops created by ods-gen. 1487 //===----------------------------------------------------------------------===// 1488 1489 template <typename NamedStructuredOpType> 1490 static ParseResult 1491 parseNamedStructuredOpRegion(OpAsmParser &parser, Region ®ion, 1492 TypeRange inputTypes, TypeRange outputTypes, 1493 ArrayRef<NamedAttribute> attrs) { 1494 ParseResult res = success(); 1495 OpBuilder opBuilder(parser.getContext()); 1496 // Resolve `captures` into `capturedValues` at parse time so we can build the 1497 // region with captures. 1498 SmallVector<Value> capturedValues; 1499 fillStructuredOpRegion<NamedStructuredOpType>( 1500 opBuilder, region, inputTypes, outputTypes, attrs, 1501 [&](unsigned expected, unsigned actual) { 1502 res = parser.emitError( 1503 parser.getCurrentLocation(), 1504 llvm::formatv("[parseNamedStructuredOpRegion] ods-gen generated " 1505 "region expects {0} args, got {1}", 1506 expected, actual)); 1507 region.front().dump(); 1508 }); 1509 return res; 1510 } 1511 1512 static ParseResult 1513 parseNamedStructuredOpResults(OpAsmParser &parser, 1514 SmallVectorImpl<Type> &resultTypes) { 1515 if (parser.parseOptionalArrowTypeList(resultTypes)) 1516 return failure(); 1517 return success(); 1518 } 1519 1520 template <typename NamedStructuredOpType> 1521 static ParseResult parseNamedStructuredOp(OpAsmParser &parser, 1522 OperationState &result) { 1523 // TODO: Enable when ods-gen supports captures. 1524 SmallVector<Type, 1> inputTypes, outputTypes; 1525 if (parseCommonStructuredOpParts(parser, result, inputTypes, outputTypes)) 1526 return failure(); 1527 1528 // TODO: consider merging results parsing into region parsing. 1529 // Need to wait for declarative assembly resolution to decide. 1530 SmallVector<Type, 1> outputTensorsTypes; 1531 if (parseNamedStructuredOpResults(parser, outputTensorsTypes)) 1532 return failure(); 1533 result.addTypes(outputTensorsTypes); 1534 1535 std::unique_ptr<Region> region = std::make_unique<Region>(); 1536 if (parseNamedStructuredOpRegion<NamedStructuredOpType>( 1537 parser, *region, inputTypes, outputTypes, 1538 result.attributes.getAttrs())) 1539 return failure(); 1540 result.addRegion(std::move(region)); 1541 1542 return success(); 1543 } 1544 1545 static void printNamedStructuredOpResults(OpAsmPrinter &p, 1546 TypeRange resultTypes) { 1547 if (resultTypes.empty()) 1548 return; 1549 p.printOptionalArrowTypeList(resultTypes); 1550 } 1551 1552 template <typename NamedStructuredOpType> 1553 static void printNamedStructuredOp(OpAsmPrinter &p, NamedStructuredOpType op) { 1554 p.printOptionalAttrDict( 1555 op->getAttrs(), 1556 /*elidedAttrs=*/{"operand_segment_sizes", 1557 // See generated code in mlir-linalg-yaml-gen.cpp 1558 "linalg.memoized_indexing_maps"}); 1559 1560 // Printing is shared with generic ops, except for the region and 1561 // attributes. 1562 printCommonStructuredOpParts(p, op); 1563 1564 // Results printing. 1565 printNamedStructuredOpResults(p, op.result_tensors().getTypes()); 1566 1567 // Region is elided. 1568 } 1569 1570 template <typename NamedStructuredOpType> 1571 static LogicalResult verifyNamedStructuredOp(NamedStructuredOpType op) { 1572 return verifyGenericOp<NamedStructuredOpType>(op); 1573 } 1574 1575 //===----------------------------------------------------------------------===// 1576 // Canonicalizers and Folders. 1577 //===----------------------------------------------------------------------===// 1578 1579 namespace { 1580 struct EraseDeadLinalgOp : public OpInterfaceRewritePattern<LinalgOp> { 1581 using OpInterfaceRewritePattern<LinalgOp>::OpInterfaceRewritePattern; 1582 1583 LogicalResult matchAndRewrite(LinalgOp op, 1584 PatternRewriter &rewriter) const override { 1585 for (OpOperand *opOperand : op.getInputAndOutputOperands()) { 1586 // Linalg "inputs" may be either tensor or memref type. 1587 // tensor<0xelt_type> is a convention that may not always mean 1588 // "0 iterations". Only erase in cases we see memref<...x0x...>. 1589 auto mt = opOperand->get().getType().dyn_cast<MemRefType>(); 1590 if (!mt) 1591 continue; 1592 if (llvm::is_contained(op.getShape(opOperand), 0)) { 1593 rewriter.eraseOp(op); 1594 return success(); 1595 } 1596 } 1597 return failure(); 1598 } 1599 }; 1600 1601 struct FoldTensorCastProducerOp : public OpInterfaceRewritePattern<LinalgOp> { 1602 using OpInterfaceRewritePattern<LinalgOp>::OpInterfaceRewritePattern; 1603 1604 LogicalResult matchAndRewrite(LinalgOp op, 1605 PatternRewriter &rewriter) const override { 1606 // If no operand comes from a tensor::CastOp and can be folded then fail. 1607 bool hasTensorCastOperand = 1608 llvm::any_of(op.getInputAndOutputOperands(), [&](OpOperand *opOperand) { 1609 if (opOperand->get().isa<BlockArgument>()) 1610 return false; 1611 auto castOp = opOperand->get().getDefiningOp<tensor::CastOp>(); 1612 return castOp && canFoldIntoConsumerOp(castOp); 1613 }); 1614 if (!hasTensorCastOperand) 1615 return failure(); 1616 1617 SmallVector<Type, 4> newResultTypes; 1618 newResultTypes.reserve(op->getNumResults()); 1619 SmallVector<Value, 4> newOperands; 1620 newOperands.reserve(op->getNumOperands()); 1621 // Inputs may fold. 1622 for (OpOperand *opOperand : op.getInputOperands()) { 1623 auto tensorCastOp = opOperand->get().getDefiningOp<tensor::CastOp>(); 1624 newOperands.push_back(canFoldIntoConsumerOp(tensorCastOp) 1625 ? tensorCastOp.source() 1626 : opOperand->get()); 1627 } 1628 // Init tensors may fold, in which case the resultType must also change. 1629 for (OpOperand *opOperand : op.getOutputOperands()) { 1630 auto tensorCastOp = opOperand->get().getDefiningOp<tensor::CastOp>(); 1631 bool fold = canFoldIntoConsumerOp(tensorCastOp); 1632 newOperands.push_back(fold ? tensorCastOp.getOperand() 1633 : opOperand->get()); 1634 newResultTypes.push_back(newOperands.back().getType()); 1635 } 1636 // Clone op. 1637 Operation *newOp = 1638 op.clone(rewriter, op->getLoc(), newResultTypes, newOperands); 1639 SmallVector<Value, 4> replacements; 1640 replacements.reserve(newOp->getNumResults()); 1641 for (auto result : llvm::zip(op->getResults(), newOp->getResults())) { 1642 Value oldResult = std::get<0>(result); 1643 Value newResult = std::get<1>(result); 1644 if (newResult.getType() != oldResult.getType()) { 1645 replacements.push_back(rewriter.create<tensor::CastOp>( 1646 op->getLoc(), oldResult.getType(), newResult)); 1647 } else { 1648 replacements.push_back(newResult); 1649 } 1650 } 1651 rewriter.replaceOp(op, replacements); 1652 1653 return success(); 1654 } 1655 }; 1656 1657 /// Fold LinalgOps with `tensor.cast` consumer if the `tensor.cast` has 1658 /// result that is more static than the linalg op. 1659 struct FoldTensorCastConsumerOp : public OpRewritePattern<tensor::CastOp> { 1660 using OpRewritePattern<tensor::CastOp>::OpRewritePattern; 1661 1662 LogicalResult matchAndRewrite(tensor::CastOp castOp, 1663 PatternRewriter &rewriter) const override { 1664 if (!tensor::canFoldIntoProducerOp(castOp)) 1665 return failure(); 1666 auto linalgOp = castOp.source().getDefiningOp<LinalgOp>(); 1667 if (!linalgOp) 1668 return failure(); 1669 1670 OpBuilder::InsertionGuard guard(rewriter); 1671 rewriter.setInsertionPoint(linalgOp); 1672 1673 Location loc = linalgOp.getLoc(); 1674 OpResult resultValue = castOp.source().cast<OpResult>(); 1675 unsigned resultNumber = resultValue.getResultNumber(); 1676 auto resultType = castOp->getResult(0).getType().cast<RankedTensorType>(); 1677 // Replace the `outs` for the result with a `tensor.cast`. This cast is now 1678 // going from a more dynamic shape to a less dynamic shape. If the producer 1679 // for this cast, i.e. producer of the out operand, is also an operation 1680 // that folds with tensor.cast consumer (like this pattern), the cast will 1681 // continue to propagate as far up the stack as it can go. 1682 OpOperand *outOperand = linalgOp.getOutputOperand(resultNumber); 1683 Value newOperand = 1684 rewriter.create<tensor::CastOp>(loc, resultType, outOperand->get()); 1685 SmallVector<Value> newOperands = linalgOp.getInputOperands(); 1686 SmallVector<Value> outputOperands = linalgOp.getOutputOperands(); 1687 outputOperands[resultNumber] = newOperand; 1688 newOperands.append(outputOperands.begin(), outputOperands.end()); 1689 1690 SmallVector<Type> resultTypes(linalgOp->result_type_begin(), 1691 linalgOp->result_type_end()); 1692 resultTypes[resultNumber] = resultType; 1693 Operation *newOp = linalgOp.clone(rewriter, loc, resultTypes, newOperands); 1694 1695 // Create a tensor.cast operation back to the original type. 1696 Value castBack = rewriter.create<tensor::CastOp>( 1697 loc, resultValue.getType(), newOp->getResult(resultNumber)); 1698 1699 SmallVector<Value> results(newOp->result_begin(), newOp->result_end()); 1700 results[resultNumber] = castBack; 1701 rewriter.replaceOp(linalgOp, results); 1702 rewriter.replaceOp(castOp, newOp->getResult(resultNumber)); 1703 return success(); 1704 } 1705 }; 1706 1707 /// For each of the operand in `operands` this function maps the static sizes of 1708 /// dimensions to their affine dim expressions. 1709 static void populateMap(LinalgOp linalgOp, ArrayRef<OpOperand *> operands, 1710 llvm::DenseMap<AffineExpr, int64_t> &affineExprToSize) { 1711 for (OpOperand *opOperand : operands) { 1712 if (linalgOp.isScalar(opOperand)) 1713 continue; 1714 Value src = opOperand->get(); 1715 auto sourceType = src.getType().cast<RankedTensorType>(); 1716 auto sourceMap = linalgOp.getTiedIndexingMap(opOperand); 1717 1718 // Get the `sourceShape` of the `sourceType`. If the operand is a result of 1719 // `tensor.cast` operation and source of the cast operation has a static 1720 // shape, then assign it to the `sourceShape`. 1721 auto parentOp = src.getDefiningOp(); 1722 ArrayRef<int64_t> sourceShape = sourceType.getShape(); 1723 if (parentOp) { 1724 if (auto castOp = dyn_cast<tensor::CastOp>(parentOp)) { 1725 Value castSource = castOp.source(); 1726 auto castSourceType = castSource.getType().cast<RankedTensorType>(); 1727 if (castSourceType.hasStaticShape()) 1728 sourceShape = castSourceType.getShape(); 1729 } 1730 } 1731 1732 // If the source shape's dimension has a static shape, map the affine dim 1733 // expression to the known static size. 1734 for (unsigned i = 0; i < sourceShape.size(); i++) { 1735 if (sourceType.isDynamicDim(i)) 1736 continue; 1737 if (auto affineDimExpr = sourceMap.getResult(i).dyn_cast<AffineDimExpr>()) 1738 affineExprToSize.try_emplace(affineDimExpr, sourceShape[i]); 1739 } 1740 } 1741 } 1742 1743 /// Creates new operand w.r.t 'opOperand' of `linalgOp` with static sizes 1744 /// mapped in `affineExprToSize`. New operands are created in `newOperands` and 1745 /// their result types is stored in `resultTypes`. If `opOperand` requires no 1746 /// change then `changeNeeded` is false and same operand is added in the 1747 /// `newOperands` list. 1748 static void createNewOperandWithStaticSizes( 1749 Location loc, PatternRewriter &rewriter, OpOperand *opOperand, 1750 llvm::DenseMap<AffineExpr, int64_t> &affineExprToSize, LinalgOp linalgOp, 1751 SmallVector<Value> &newOperands, SmallVector<Type> &resultTypes, 1752 bool &changeNeeded) { 1753 Value src = opOperand->get(); 1754 newOperands.push_back(src); 1755 if (linalgOp.isScalar(opOperand)) 1756 return; 1757 auto sourceType = src.getType().cast<RankedTensorType>(); 1758 Type resultType = sourceType; 1759 if (sourceType.hasStaticShape() && linalgOp.isOutputTensor(opOperand)) { 1760 resultTypes.push_back(resultType); 1761 return; 1762 } 1763 ArrayRef<int64_t> sourceShape = sourceType.getShape(); 1764 AffineMap sourceMap = linalgOp.getTiedIndexingMap(opOperand); 1765 SmallVector<int64_t> newShape; 1766 // If operand is updated with new shape, `newOperandNeeded` will be 1767 // true. 1768 bool newOperandNeeded = false; 1769 for (unsigned i = 0; i < sourceShape.size(); i++) { 1770 int64_t dimShape = sourceShape[i]; 1771 AffineExpr dimExpr = sourceMap.getResult(i); 1772 if (affineExprToSize.find(dimExpr) == affineExprToSize.end() || 1773 !sourceType.isDynamicDim(i)) { 1774 newShape.push_back(dimShape); 1775 continue; 1776 } 1777 // Dimension has a dynamic shape and corresponding affine dim 1778 // expression is present in the map. So assign the size for the 1779 // given affine dim expression to the dimension. 1780 newShape.push_back(affineExprToSize[dimExpr]); 1781 newOperandNeeded = true; 1782 } 1783 resultType = RankedTensorType::get(newShape, sourceType.getElementType()); 1784 if (newOperandNeeded) { 1785 changeNeeded = true; 1786 // Get the new operand value given its size and element type by 1787 // casting it. 1788 Value newOperand = rewriter.create<tensor::CastOp>(loc, resultType, src); 1789 unsigned index = opOperand->getOperandNumber(); 1790 newOperands[index] = newOperand; 1791 } 1792 if (linalgOp.isOutputTensor(opOperand)) 1793 resultTypes.push_back(resultType); 1794 } 1795 1796 /// Static shapes for the operands can be inferred if any one of the operands 1797 /// have a static shape. This can be done by referring to the affine dim 1798 /// expressions for the operand. 1799 struct InferStaticShapeOfOperands : public OpInterfaceRewritePattern<LinalgOp> { 1800 using OpInterfaceRewritePattern<LinalgOp>::OpInterfaceRewritePattern; 1801 1802 LogicalResult matchAndRewrite(LinalgOp linalgOp, 1803 PatternRewriter &rewriter) const override { 1804 if (!linalgOp.hasTensorSemantics()) 1805 return failure(); 1806 1807 // Maps must be projected permutations. 1808 if (llvm::any_of(linalgOp.getIndexingMaps(), [](AffineMap map) { 1809 return !map.isProjectedPermutation(); 1810 })) 1811 return failure(); 1812 1813 // Maps affine dim expressions to the static size of that dimension. 1814 llvm::DenseMap<AffineExpr, int64_t> affineExprToSize; 1815 Location loc = linalgOp.getLoc(); 1816 1817 // For each of the affine dim expression, check if the size is known. If 1818 // known add that in the map. 1819 populateMap(linalgOp, linalgOp.getInputAndOutputOperands(), 1820 affineExprToSize); 1821 1822 SmallVector<Value> newOperands; 1823 SmallVector<Type> resultTypes; 1824 1825 // `changeNeeded` is `false` if the operands of `linalgOp` require no 1826 // change in their types. 1827 bool changeNeeded = false; 1828 newOperands.reserve(linalgOp.getNumInputsAndOutputs()); 1829 resultTypes.reserve(linalgOp.getNumOutputs()); 1830 1831 // Iterate over all the operands and update the static sizes. 1832 for (OpOperand *opOperand : linalgOp.getInputAndOutputOperands()) { 1833 createNewOperandWithStaticSizes(loc, rewriter, opOperand, 1834 affineExprToSize, linalgOp, newOperands, 1835 resultTypes, changeNeeded); 1836 } 1837 1838 // If the generic op has all the required static information, no 1839 // canonicalization needed. 1840 if (!changeNeeded) 1841 return failure(); 1842 1843 // Clone op. 1844 Operation *newOp = 1845 linalgOp.clone(rewriter, linalgOp->getLoc(), resultTypes, newOperands); 1846 SmallVector<Value> replacements; 1847 replacements.reserve(newOp->getNumResults()); 1848 for (auto it : llvm::zip(linalgOp->getResults(), newOp->getResults())) { 1849 Value newResult = std::get<1>(it); 1850 Value oldResult = std::get<0>(it); 1851 Type newType = newResult.getType(); 1852 Type oldType = oldResult.getType(); 1853 replacements.push_back( 1854 (newType != oldType) 1855 ? rewriter.create<tensor::CastOp>(loc, oldType, newResult) 1856 : newResult); 1857 } 1858 rewriter.replaceOp(linalgOp, replacements); 1859 return success(); 1860 } 1861 }; 1862 1863 } // namespace 1864 1865 // All named ops canonicalizers and folders are auto-generated in the 1866 // .cpp.inc. 1867 1868 //===----------------------------------------------------------------------===// 1869 // LinalgDialect 1870 //===----------------------------------------------------------------------===// 1871 1872 void LinalgDialect::getCanonicalizationPatterns( 1873 RewritePatternSet &results) const { 1874 results.add<EraseDeadLinalgOp, FoldTensorCastConsumerOp, 1875 FoldTensorCastProducerOp, InferStaticShapeOfOperands>( 1876 getContext()); 1877 } 1878 1879 Operation *LinalgDialect::materializeConstant(OpBuilder &builder, 1880 Attribute value, Type type, 1881 Location loc) { 1882 return builder.create<arith::ConstantOp>(loc, type, value); 1883 } 1884