1 //===- LinalgTransformOps.cpp - Implementation of Linalg transform ops ----===// 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 #include "mlir/Dialect/Linalg/TransformOps/LinalgTransformOps.h" 10 11 #include "mlir/Dialect/Linalg/IR/Linalg.h" 12 #include "mlir/Dialect/Linalg/Transforms/Transforms.h" 13 #include "mlir/Dialect/PDL/IR/PDL.h" 14 #include "mlir/Dialect/PDL/IR/PDLTypes.h" 15 #include "mlir/Dialect/Transform/IR/TransformDialect.h" 16 #include "mlir/Interfaces/SideEffectInterfaces.h" 17 #include "mlir/Parser/Parser.h" 18 #include "mlir/Transforms/GreedyPatternRewriteDriver.h" 19 #include "llvm/Support/FormatVariadic.h" 20 21 using namespace mlir; 22 using namespace mlir::linalg; 23 using namespace mlir::transform; 24 25 /// Extracts a vector of int64_t from an array attribute. Asserts if the 26 /// attribute contains values other than integers. 27 static SmallVector<int64_t> extractI64Array(ArrayAttr attr) { 28 SmallVector<int64_t> result; 29 result.reserve(attr.size()); 30 for (APInt value : attr.getAsValueRange<IntegerAttr>()) 31 result.push_back(value.getSExtValue()); 32 return result; 33 } 34 35 /// Extracts a vector of unsigned from an array attribute. Asserts if the 36 /// attribute contains values other than intergers. May truncate. 37 static SmallVector<unsigned> extractUIntArray(ArrayAttr attr) { 38 SmallVector<unsigned> result; 39 result.reserve(attr.size()); 40 for (APInt value : attr.getAsValueRange<IntegerAttr>()) 41 result.push_back(value.getZExtValue()); 42 return result; 43 } 44 45 namespace { 46 /// A simple pattern rewriter that implements no special logic. 47 class SimpleRewriter : public PatternRewriter { 48 public: 49 SimpleRewriter(MLIRContext *context) : PatternRewriter(context) {} 50 }; 51 } // namespace 52 53 /// Attempts to apply the pattern specified as template argument to the given 54 /// operation. The pattern is expected to have a `returningMatchAndRewrite` 55 /// function that returns the "main" result or failure. Returns failure if the 56 /// pattern failed to apply. Extra arguments are forwarded to the pattern 57 /// constructor. 58 template <typename PatternTy, typename... Args> 59 static FailureOr<LinalgOp> tryApply(Operation *operation, Args &&...args) { 60 // Check if the given operation has the type expected by the pattern. 61 using OpTy = typename llvm::function_traits< 62 decltype(&PatternTy::returningMatchAndRewrite)>::template arg_t<0>; 63 auto op = dyn_cast<OpTy>(operation); 64 if (!op) 65 return failure(); 66 67 // Apply the pattern directly to the op. 68 PatternTy pattern(operation->getContext(), std::forward<Args>(args)...); 69 SimpleRewriter rewriter(operation->getContext()); 70 rewriter.setInsertionPoint(operation); 71 auto result = pattern.returningMatchAndRewrite(op, rewriter); 72 if (failed(result)) 73 return failure(); 74 return cast<LinalgOp>(result->getOperation()); 75 } 76 77 //===----------------------------------------------------------------------===// 78 // DecomposeOp 79 //===----------------------------------------------------------------------===// 80 81 FailureOr<LinalgOp> transform::DecomposeOp::applyToOne(LinalgOp target) { 82 FailureOr<LinalgOp> windowed = 83 tryApply<DownscaleSizeOneWindowed2DConvolution>(target); 84 if (succeeded(windowed)) 85 return windowed; 86 87 FailureOr<LinalgOp> depthwise = 88 tryApply<DownscaleDepthwiseConv2DNhwcHwcOp>(target); 89 if (succeeded(depthwise)) 90 return depthwise; 91 92 return reportUnknownTransformError(target); 93 } 94 95 //===----------------------------------------------------------------------===// 96 // GeneralizeOp 97 //===----------------------------------------------------------------------===// 98 99 FailureOr<LinalgOp> transform::GeneralizeOp::applyToOne(LinalgOp target) { 100 // Exit early if no transformation is needed. 101 if (isa<GenericOp>(target)) 102 return target; 103 104 FailureOr<LinalgOp> generic = tryApply<LinalgGeneralizationPattern>(target); 105 if (succeeded(generic)) 106 return generic; 107 108 return reportUnknownTransformError(target); 109 } 110 111 //===----------------------------------------------------------------------===// 112 // InterchangeOp 113 //===----------------------------------------------------------------------===// 114 115 FailureOr<LinalgOp> transform::InterchangeOp::applyToOne(LinalgOp target) { 116 SmallVector<unsigned> interchangeVector = 117 extractUIntArray(getIteratorInterchange()); 118 // Exit early if no transformation is needed. 119 if (interchangeVector.empty()) 120 return target; 121 122 auto genericTarget = dyn_cast<GenericOp>(target.getOperation()); 123 if (!genericTarget) { 124 InFlightDiagnostic diag = emitOpError() 125 << "applies to " << GenericOp::getOperationName() 126 << " ops"; 127 diag.attachNote(target.getLoc()) << "attempted to apply to this op"; 128 return diag; 129 } 130 131 return tryApply<GenericOpInterchangePattern>(target, interchangeVector); 132 } 133 134 LogicalResult transform::InterchangeOp::verify() { 135 SmallVector<unsigned> permutation = 136 extractUIntArray(getIteratorInterchange()); 137 auto sequence = llvm::to_vector(llvm::seq<unsigned>(0, permutation.size())); 138 if (!std::is_permutation(sequence.begin(), sequence.end(), 139 permutation.begin(), permutation.end())) { 140 return emitOpError() 141 << "expects iterator_interchange to be a permutation, found " 142 << getIteratorInterchange(); 143 } 144 return success(); 145 } 146 147 //===---------------------------------------------------------------------===// 148 // PadOp 149 //===---------------------------------------------------------------------===// 150 151 FailureOr<LinalgOp> transform::PadOp::applyToOne(LinalgOp target) { 152 // Convert the integer packing flags to booleans. 153 SmallVector<bool> packPaddings; 154 for (int64_t packPadding : extractI64Array(getPackPaddings())) 155 packPaddings.push_back(static_cast<bool>(packPadding)); 156 157 // Convert the padding values to attributes. 158 SmallVector<Attribute> paddingValues; 159 for (auto const &it : 160 llvm::zip(getPaddingValues(), target->getOperandTypes())) { 161 Attribute attr = std::get<0>(it); 162 Type elementType = getElementTypeOrSelf(std::get<1>(it)); 163 // Try to parse string attributes to obtain an attribute of element type. 164 if (auto stringAttr = attr.dyn_cast<StringAttr>()) { 165 paddingValues.push_back( 166 parseAttribute(attr.cast<StringAttr>(), elementType)); 167 if (!paddingValues.back()) { 168 InFlightDiagnostic diag = emitOpError() 169 << "expects a padding value that parses to " 170 << elementType << ", got " << std::get<0>(it); 171 diag.attachNote(target.getLoc()) << "when applied to this op"; 172 return diag; 173 } 174 continue; 175 } 176 // Otherwise, add the attribute directly. 177 if (attr.getType() != elementType) { 178 InFlightDiagnostic diag = emitOpError() 179 << "expects a padding value of type " 180 << elementType << ", got " << attr; 181 diag.attachNote(target.getLoc()) << "when applied to this op"; 182 return diag; 183 } 184 paddingValues.push_back(attr); 185 } 186 187 // Extract the transpose vectors. 188 SmallVector<SmallVector<int64_t>> transposePaddings; 189 for (Attribute transposeVector : getTransposePaddings().cast<ArrayAttr>()) 190 transposePaddings.push_back( 191 extractI64Array(transposeVector.cast<ArrayAttr>())); 192 193 LinalgPaddingOptions paddingOptions; 194 paddingOptions.setPaddingValues(paddingValues); 195 paddingOptions.setPaddingDimensions(extractI64Array(getPaddingDimensions())); 196 paddingOptions.setPackPaddings(packPaddings); 197 paddingOptions.setHoistPaddings(extractI64Array(getHoistPaddings())); 198 paddingOptions.setTransposePaddings(transposePaddings); 199 200 FailureOr<LinalgOp> result = 201 tryApply<LinalgPaddingPattern>(target, paddingOptions); 202 if (succeeded(result)) 203 return result; 204 205 InFlightDiagnostic diag = emitError() 206 << "failed to apply pattern to target op"; 207 diag.attachNote(target.getLoc()) << "target op"; 208 return diag; 209 } 210 211 LogicalResult transform::PadOp::verify() { 212 SmallVector<int64_t> packPaddings = extractI64Array(getPackPaddings()); 213 if (any_of(packPaddings, [](int64_t packPadding) { 214 return packPadding != 0 && packPadding != 1; 215 })) { 216 return emitOpError() 217 << "expects pack_paddings to contain booleans (0/1), found " 218 << getPackPaddings(); 219 } 220 221 SmallVector<int64_t> paddingDimensions = 222 extractI64Array(getPaddingDimensions()); 223 if (any_of(paddingDimensions, 224 [](int64_t paddingDimension) { return paddingDimension < 0; })) { 225 return emitOpError() 226 << "expects padding_dimensions to contain positive integers, found " 227 << getPaddingDimensions(); 228 } 229 230 SmallVector<int64_t> hoistPaddings = extractI64Array(getHoistPaddings()); 231 if (any_of(hoistPaddings, 232 [](int64_t hoistPadding) { return hoistPadding < 0; })) { 233 return emitOpError() 234 << "expects hoist_paddings to contain positive integers, found " 235 << getHoistPaddings(); 236 } 237 238 ArrayAttr transposes = getTransposePaddings(); 239 for (Attribute attr : transposes) { 240 SmallVector<int64_t> transpose = extractFromI64ArrayAttr(attr); 241 auto sequence = llvm::to_vector(llvm::seq<int64_t>(0, transpose.size())); 242 if (!std::is_permutation(sequence.begin(), sequence.end(), 243 transpose.begin(), transpose.end())) { 244 return emitOpError() 245 << "expects transpose_paddings to be a permutation, found " 246 << attr; 247 } 248 } 249 return success(); 250 } 251 252 //===----------------------------------------------------------------------===// 253 // ScalarizeOp 254 //===----------------------------------------------------------------------===// 255 256 FailureOr<LinalgOp> transform::ScalarizeOp::applyToOne(LinalgOp target) { 257 LinalgTilingOptions tilingOptions; 258 tilingOptions.scalarizeDynamicDims(); 259 // Tiling with "scalarize_dyn_dims" actually sets the same lambda as the tile 260 // sizes and asserts that it is not already set. 261 SmallVector<int64_t> emptyTileSizes; 262 LinalgTilingPattern pattern(getContext(), tilingOptions); 263 SimpleRewriter rewriter(getContext()); 264 rewriter.setInsertionPoint(target); 265 FailureOr<TiledLinalgOp> result = 266 pattern.returningMatchAndRewrite(target, rewriter); 267 if (failed(result)) 268 return failure(); 269 270 return result->op; 271 } 272 273 //===----------------------------------------------------------------------===// 274 // TileOp 275 //===----------------------------------------------------------------------===// 276 277 /// Apply a tiling transformation to all payload ops and store both the 278 /// tiled operation as well as the created tile loops. 279 static LogicalResult 280 applyTilingToAll(Operation *transformOp, Value target, 281 ArrayRef<int64_t> tileSizes, 282 transform::TransformResults &transformResults, 283 transform::TransformState &state, 284 function_ref<FailureOr<TiledLinalgOp>(LinalgOp)> applyFn) { 285 // Number of loops: Number of tiles sizes that are not zero. 286 size_t numLoops = tileSizes.size() - llvm::count(tileSizes, 0); 287 // All payload ops. These should all be LinalgOps for now. 288 ArrayRef<Operation *> payloadOps = state.getPayloadOps(target); 289 290 SmallVector<Operation *> tiledLinalgOps; 291 SmallVector<SmallVector<Operation *>> loopOps(numLoops); 292 for (unsigned int i = 0; i < numLoops; ++i) 293 loopOps[i].reserve(payloadOps.size()); 294 295 for (Operation *target : payloadOps) { 296 auto linalgOp = dyn_cast<linalg::LinalgOp>(target); 297 if (!linalgOp) 298 return transformOp->emitError("only LinalgOps are supported"); 299 300 FailureOr<TiledLinalgOp> tiled = applyFn(linalgOp); 301 if (failed(tiled)) 302 return failure(); 303 304 tiledLinalgOps.push_back(tiled->op); 305 if (tiled->loops.size() != numLoops) 306 // Not enough loops were generated. This usually means that the input size 307 // was smaller than the tiling size. 308 // TODO: LinalgTilingPattern should return failure(). 309 return failure(); 310 for (unsigned int i = 0; i < numLoops; ++i) 311 loopOps[i].push_back(tiled->loops[i]); 312 } 313 314 transformResults.set(transformOp->getOpResult(0), tiledLinalgOps); 315 for (unsigned int i = 0; i < numLoops; ++i) 316 transformResults.set(transformOp->getOpResult(i + 1), loopOps[i]); 317 return success(); 318 } 319 320 LogicalResult transform::TileOp::apply(TransformResults &transformResults, 321 TransformState &state) { 322 LinalgTilingOptions tilingOptions; 323 SmallVector<int64_t> tileSizes = extractI64Array(getSizes()); 324 325 if (!tileSizes.empty()) 326 tilingOptions.setTileSizes(tileSizes); 327 tilingOptions.setInterchange(extractUIntArray(getInterchange())); 328 LinalgTilingPattern pattern(getContext(), tilingOptions); 329 330 return applyTilingToAll(getOperation(), getTarget(), tileSizes, 331 transformResults, state, [&](LinalgOp linalgOp) { 332 SimpleRewriter rewriter(linalgOp.getContext()); 333 return pattern.returningMatchAndRewrite(linalgOp, 334 rewriter); 335 }); 336 } 337 338 ParseResult transform::TileOp::parse(OpAsmParser &parser, 339 OperationState &result) { 340 StringRef sizesAttrName = TileOp::getSizesAttrName(result.name).getValue(); 341 OpAsmParser::UnresolvedOperand targetOperand; 342 SMLoc opLoc = parser.getCurrentLocation(); 343 if (parser.parseOperand(targetOperand) || 344 parser.parseOptionalAttrDict(result.attributes)) 345 return failure(); 346 Attribute sizesAttr = result.attributes.get(sizesAttrName); 347 if (!sizesAttr) 348 return parser.emitError(opLoc) 349 << "expected '" << sizesAttrName << "' attribute"; 350 auto sizesArrayAttr = sizesAttr.dyn_cast<ArrayAttr>(); 351 if (!sizesArrayAttr) 352 return parser.emitError(opLoc) 353 << "'" << sizesAttrName << "' attribute must be an array"; 354 Type pdlOpType = parser.getBuilder().getType<pdl::OperationType>(); 355 size_t numExpectedLoops = 356 sizesArrayAttr.size() - llvm::count(extractI64Array(sizesArrayAttr), 0); 357 result.addTypes(SmallVector<Type>(numExpectedLoops + 1, pdlOpType)); 358 if (parser.resolveOperand(targetOperand, pdlOpType, result.operands)) 359 return failure(); 360 return success(); 361 } 362 363 void TileOp::print(OpAsmPrinter &p) { 364 p << ' '; 365 p << getTarget(); 366 p.printOptionalAttrDict((*this)->getAttrs()); 367 } 368 369 void TileOp::getEffects( 370 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>> 371 &effects) { 372 // `target` arg is consumed and can no longer be used. 373 effects.emplace_back(MemoryEffects::Read::get(), getTarget(), 374 TransformMappingResource::get()); 375 effects.emplace_back(MemoryEffects::Free::get(), getTarget(), 376 TransformMappingResource::get()); 377 378 for (Value r : getResults()) { 379 effects.emplace_back(MemoryEffects::Write::get(), r, 380 TransformMappingResource::get()); 381 effects.emplace_back(MemoryEffects::Allocate::get(), r, 382 TransformMappingResource::get()); 383 } 384 385 effects.emplace_back(MemoryEffects::Read::get(), PayloadIRResource::get()); 386 effects.emplace_back(MemoryEffects::Write::get(), PayloadIRResource::get()); 387 } 388 389 //===----------------------------------------------------------------------===// 390 // VectorizeOp 391 //===----------------------------------------------------------------------===// 392 393 FailureOr<Operation *> VectorizeOp::applyToOne(Operation *target) { 394 if (!target->hasTrait<OpTrait::IsIsolatedFromAbove>()) { 395 InFlightDiagnostic diag = emitOpError() 396 << "applies only to isolated-from-above targets"; 397 diag.attachNote(target->getLoc()) << "non-isolated target"; 398 return diag; 399 } 400 401 MLIRContext *ctx = getContext(); 402 RewritePatternSet patterns(ctx); 403 patterns.add<LinalgVectorizationPattern>(ctx); 404 405 vector::populateVectorTransferPermutationMapLoweringPatterns(patterns); 406 vector::populateVectorReductionToContractPatterns(patterns); 407 patterns.add<linalg::LinalgCopyVTRForwardingPattern, 408 linalg::LinalgCopyVTWForwardingPattern>(ctx, 409 /*benefit=*/2); 410 vector::TransferReadOp::getCanonicalizationPatterns(patterns, ctx); 411 vector::TransferWriteOp::getCanonicalizationPatterns(patterns, ctx); 412 if (getVectorizePadding()) 413 linalg::populatePadOpVectorizationPatterns(patterns); 414 415 if (failed(applyPatternsAndFoldGreedily(target, std::move(patterns)))) 416 return reportUnknownTransformError(target); 417 return target; 418 } 419 420 //===----------------------------------------------------------------------===// 421 // Transform op registration 422 //===----------------------------------------------------------------------===// 423 424 namespace { 425 /// Registers new ops and declares PDL as dependent dialect since the additional 426 /// ops are using PDL types for operands and results. 427 class LinalgTransformDialectExtension 428 : public transform::TransformDialectExtension< 429 LinalgTransformDialectExtension> { 430 public: 431 LinalgTransformDialectExtension() { 432 declareDependentDialect<pdl::PDLDialect>(); 433 declareDependentDialect<scf::SCFDialect>(); 434 declareDependentDialect<vector::VectorDialect>(); 435 registerTransformOps< 436 #define GET_OP_LIST 437 #include "mlir/Dialect/Linalg/TransformOps/LinalgTransformOps.cpp.inc" 438 >(); 439 } 440 }; 441 } // namespace 442 443 #define GET_OP_CLASSES 444 #include "mlir/Dialect/Linalg/TransformOps/LinalgTransformOps.cpp.inc" 445 446 void mlir::linalg::registerTransformDialectExtension( 447 DialectRegistry ®istry) { 448 registry.addExtensions<LinalgTransformDialectExtension>(); 449 } 450