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