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