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