1 //===- Loops.cpp - conversion from Linalg named and generic ops to loops --===// 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 "PassDetail.h" 10 #include "mlir/Dialect/Affine/EDSC/Intrinsics.h" 11 #include "mlir/Dialect/Linalg/EDSC/FoldedIntrinsics.h" 12 #include "mlir/Dialect/Linalg/IR/LinalgOps.h" 13 #include "mlir/Dialect/Linalg/IR/LinalgTypes.h" 14 #include "mlir/Dialect/Linalg/Passes.h" 15 #include "mlir/Dialect/Linalg/Transforms/Transforms.h" 16 #include "mlir/Dialect/Linalg/Utils/Utils.h" 17 #include "mlir/Dialect/SCF/EDSC/Builders.h" 18 #include "mlir/Dialect/StandardOps/EDSC/Intrinsics.h" 19 #include "mlir/IR/AffineExpr.h" 20 #include "mlir/IR/AffineMap.h" 21 #include "mlir/IR/BlockAndValueMapping.h" 22 #include "mlir/Support/LLVM.h" 23 #include "mlir/Transforms/DialectConversion.h" 24 #include "mlir/Transforms/FoldUtils.h" 25 26 using namespace mlir; 27 using namespace mlir::edsc; 28 using namespace mlir::edsc::intrinsics; 29 using namespace mlir::linalg; 30 31 using edsc::op::operator+; 32 33 static SmallVector<Value, 8> makeCanonicalAffineApplies(OpBuilder &b, 34 Location loc, 35 AffineMap map, 36 ArrayRef<Value> vals) { 37 if (map.isEmpty()) 38 return {}; 39 assert(map.getNumSymbols() == 0); 40 assert(map.getNumInputs() == vals.size()); 41 SmallVector<Value, 8> res; 42 res.reserve(map.getNumResults()); 43 auto dims = map.getNumDims(); 44 for (auto e : map.getResults()) { 45 auto exprMap = AffineMap::get(dims, 0, e); 46 SmallVector<Value, 4> operands(vals.begin(), vals.end()); 47 canonicalizeMapAndOperands(&exprMap, &operands); 48 res.push_back(affine_apply(exprMap, operands)); 49 } 50 return res; 51 } 52 53 static SmallVector<Value, 4> permuteIvs(ArrayRef<Value> ivs, 54 Optional<AffineMap> permutation) { 55 return permutation ? applyMapToValues(ScopedContext::getBuilderRef(), 56 ScopedContext::getLocation(), 57 permutation.getValue(), ivs) 58 : SmallVector<Value, 4>(ivs.begin(), ivs.end()); 59 } 60 61 // Creates a number of ranges equal to the number of results in `map`. 62 // The returned ranges correspond to the loop ranges, in the proper order, for 63 // which new loops will be created. 64 static SmallVector<SubViewOp::Range, 4> 65 emitLoopRanges(OpBuilder &b, Location loc, AffineMap map, 66 ArrayRef<Value> allViewSizes) { 67 // Apply `map` to get view sizes in loop order. 68 auto sizes = applyMapToValues(b, loc, map, allViewSizes); 69 // Create a new range with the applied tile sizes. 70 ScopedContext scope(b, loc); 71 SmallVector<SubViewOp::Range, 4> res; 72 for (unsigned idx = 0, e = map.getNumResults(); idx < e; ++idx) { 73 res.push_back(SubViewOp::Range{std_constant_index(0), sizes[idx], 74 std_constant_index(1)}); 75 } 76 return res; 77 } 78 79 template <typename IndexedValueType, typename OpType> 80 static void inlineRegionAndEmitStore(OpType op, ArrayRef<Value> indexedValues, 81 ArrayRef<SmallVector<Value, 8>> indexing, 82 ArrayRef<Value> outputBuffers) { 83 assert(op.getOperation()->getNumRegions() == 1 && 84 "Expected single region op"); 85 auto &b = ScopedContext::getBuilderRef(); 86 auto &block = op.region().front(); 87 BlockAndValueMapping map; 88 map.map(block.getArguments(), indexedValues); 89 for (auto &op : block.without_terminator()) { 90 assert(op.getNumRegions() == 0 && "expected a non-nested region"); 91 auto *newOp = b.clone(op, map); 92 map.map(op.getResults(), newOp->getResults()); 93 } 94 95 Operation &terminator = block.back(); 96 assert(isa<YieldOp>(terminator) && 97 "expected a yield op in the end of the region"); 98 for (unsigned i = 0, e = terminator.getNumOperands(); i < e; ++i) { 99 IndexedValueType O(outputBuffers[i]); 100 O(indexing[i]) = map.lookupOrDefault(terminator.getOperand(i)); 101 } 102 } 103 104 // Returns a pair that contains input indices and output indices of a 105 // SingleInputPoolingOp `op`. 106 struct InputAndOutputIndices { 107 SmallVector<Value, 8> inputs; 108 SmallVector<Value, 8> outputs; 109 }; 110 template <typename SingleInputPoolingOp> 111 static InputAndOutputIndices getInputAndOutputIndices(ArrayRef<Value> allIvs, 112 SingleInputPoolingOp op) { 113 auto &b = ScopedContext::getBuilderRef(); 114 auto loc = ScopedContext::getLocation(); 115 auto mapsRange = op.indexing_maps().template getAsRange<AffineMapAttr>(); 116 auto maps = llvm::to_vector<8>( 117 llvm::map_range(mapsRange, [](AffineMapAttr a) { return a.getValue(); })); 118 return InputAndOutputIndices{ 119 makeCanonicalAffineApplies(b, loc, maps[0], allIvs), 120 makeCanonicalAffineApplies(b, loc, maps[2], allIvs)}; 121 } 122 123 namespace { 124 125 /// Emits the MLIR for the scalar part of the generic op by: 126 /// 1. Emitting load ops for each input and output view in order. This is 127 /// achieved by applying the appropriate input or output map to the 128 /// enclosing induction variables. 129 /// 2. Emitting a call to `op.fun()` that takes as arguments the scalars 130 /// from point 1. above. 131 /// 3. Emitting store ops to store the results of 2. to the output 132 /// views. 133 /// 134 /// An example output may resemble: 135 /// 136 /// ``` 137 /// scf.for %i = %c0 to %0 step %c1 { 138 /// scf.for %j = %c0 to %1 step %c1 { 139 /// scf.for %k = %c0 to %4 step %c1 { 140 /// %11 = load %arg0[%i, %j] : 141 /// memref<?x?xf32, stride_specification> 142 /// %12 = load %arg1[%i, %j, %k] : 143 /// memref<?x?x?xf32, stride_specification> 144 /// %13 = load %arg2[%i, %k, %j] : 145 /// memref<?x?x?xf32, stride_specification> 146 /// %14:2 = call @foo(%11, %12, %13) : (f32, f32, f32) -> (f32, f32) 147 /// store %14#0, %arg1[%i, %j, %k] : 148 /// memref<?x?x?Xf32, stride_specification> 149 /// store %14#1, %arg2[%i, %k, %j] : 150 /// memref<?x?x?Xf32, stride_specification> 151 /// } 152 /// } 153 /// } 154 /// ``` 155 // TODO: need a LinalgStructuredOpInterface. 156 template <typename IndexedValueType, typename LinalgStructuredOpType> 157 void emitScalarImplementation(ArrayRef<Value> allIvs, 158 LinalgStructuredOpType linalgOp) { 159 assert(linalgOp.hasBufferSemantics() && 160 "expected linalg op with buffer semantics"); 161 auto &b = ScopedContext::getBuilderRef(); 162 auto loc = ScopedContext::getLocation(); 163 unsigned nInputs = linalgOp.getNumInputs(); 164 unsigned nOutputs = linalgOp.getNumOutputs(); 165 SmallVector<Value, 4> indexedValues; 166 indexedValues.reserve(nInputs + nOutputs); 167 168 // TODO(mravishankar): Avoid the loads if the corresponding argument of the 169 // region has no uses. 170 // 1.a. Emit load from input views. 171 for (unsigned i = 0; i < nInputs; ++i) { 172 auto indexing = makeCanonicalAffineApplies( 173 b, loc, linalgOp.getInputIndexingMap(i), allIvs); 174 // Passing through IndexedValueType emits the proper load operation. 175 indexedValues.push_back(IndexedValueType(linalgOp.getInput(i))(indexing)); 176 } 177 // 1.b. Emit load from output views. 178 for (unsigned i = 0; i < nOutputs; ++i) { 179 auto indexing = makeCanonicalAffineApplies( 180 b, loc, linalgOp.getOutputIndexingMap(i), allIvs); 181 // Passing through IndexedValueType emits the proper load operation. 182 indexedValues.push_back( 183 IndexedValueType(linalgOp.getOutputBuffer(i))(indexing)); 184 } 185 186 // TODO(ntv): When a region inliner exists, use it. 187 // 2. Inline region, currently only works for a single basic block. 188 // 3. Emit store. 189 SmallVector<SmallVector<Value, 8>, 8> indexing; 190 SmallVector<Value, 8> outputBuffers; 191 for (unsigned i = 0; i < nOutputs; ++i) { 192 indexing.push_back(makeCanonicalAffineApplies( 193 b, loc, linalgOp.getOutputIndexingMap(i), allIvs)); 194 outputBuffers.push_back(linalgOp.getOutputBuffer(i)); 195 } 196 inlineRegionAndEmitStore<IndexedValueType>(linalgOp, indexedValues, indexing, 197 outputBuffers); 198 } 199 200 template <typename IndexedValueType> 201 void emitScalarImplementation(ArrayRef<Value> allIvs, CopyOp copyOp) { 202 assert(copyOp.hasBufferSemantics() && 203 "expected linalg op with buffer semantics"); 204 auto nPar = copyOp.getNumParallelLoops(); 205 assert(nPar == allIvs.size()); 206 auto inputIvs = 207 permuteIvs(allIvs.take_front(nPar), copyOp.inputPermutation()); 208 auto outputIvs = 209 permuteIvs(allIvs.take_front(nPar), copyOp.outputPermutation()); 210 SmallVector<Value, 8> iivs(inputIvs.begin(), inputIvs.end()); 211 SmallVector<Value, 8> oivs(outputIvs.begin(), outputIvs.end()); 212 IndexedValueType O(copyOp.getOutputBuffer(0)), I(copyOp.getInput(0)); 213 // Emit the proper scalar assignment, whether we are dealing with a 0-D or 214 // an n-D loop nest; with or without permutations. 215 // clang-format off 216 nPar > 0 ? O(oivs) = I(iivs) : 217 O() = I(); 218 // clang-format on 219 } 220 221 template <typename IndexedValueType> 222 void emitScalarImplementation(ArrayRef<Value> allIvs, FillOp fillOp) { 223 assert(fillOp.hasBufferSemantics() && 224 "expected linalg op with buffer semantics"); 225 auto nPar = fillOp.getNumParallelLoops(); 226 assert(nPar == allIvs.size()); 227 auto ivs = SmallVector<Value, 4>(allIvs.begin(), allIvs.begin() + nPar); 228 IndexedValueType O(fillOp.getOutputBuffer(0)); 229 // Emit the proper scalar assignment, whether we are dealing with a 0-D or 230 // an n-D loop nest; with or without permutations. 231 nPar > 0 ? O(ivs) = fillOp.value() : O() = fillOp.value(); 232 } 233 234 template <typename IndexedValueType> 235 void emitScalarImplementation(ArrayRef<Value> allIvs, DotOp dotOp) { 236 assert(dotOp.hasBufferSemantics() && 237 "expected linalg op with buffer semantics"); 238 assert(allIvs.size() == 1); 239 Value r_i(allIvs[0]); 240 IndexedValueType A(dotOp.getInput(0)), B(dotOp.getInput(1)), 241 C(dotOp.getOutputBuffer(0)); 242 // Emit scalar form. 243 C() = C() + A(r_i) * B(r_i); 244 } 245 template <typename IndexedValueType> 246 void emitScalarImplementation(ArrayRef<Value> allIvs, MatvecOp matvecOp) { 247 assert(matvecOp.hasBufferSemantics() && 248 "expected linalg op with buffer semantics"); 249 assert(allIvs.size() == 2); 250 Value i(allIvs[0]), r_j(allIvs[1]); 251 IndexedValueType A(matvecOp.getInput(0)), B(matvecOp.getInput(1)), 252 C(matvecOp.getOutputBuffer(0)); 253 // Emit scalar form. 254 C(i) = C(i) + A(i, r_j) * B(r_j); 255 } 256 257 template <typename IndexedValueType> 258 Value getConvOpInput(ConvOp convOp, StdIndexedValue im, 259 MutableArrayRef<Value> imIdx) { 260 // TODO(ntv): add a level of indirection to linalg.generic. 261 if (!convOp.padding()) 262 return im(imIdx); 263 264 auto *context = ScopedContext::getContext(); 265 Value zeroIndex = std_constant_index(0); 266 SmallVector<Value, 8> conds; 267 SmallVector<Value, 8> clampedImIdx; 268 for (auto iter : llvm::enumerate(imIdx)) { 269 int idx = iter.index(); 270 auto dim = iter.value(); 271 // Only need to iterate over the window dimensions. 272 if (idx == 0 || idx == static_cast<int>(imIdx.size()) - 1) { 273 clampedImIdx.push_back(dim); 274 continue; 275 } 276 277 using edsc::op::operator<; 278 using edsc::op::operator>=; 279 using edsc::op::operator||; 280 Value leftOutOfBound = dim < zeroIndex; 281 if (conds.empty()) 282 conds.push_back(leftOutOfBound); 283 else 284 conds.push_back(conds.back() || leftOutOfBound); 285 Value rightBound = std_dim(convOp.input(), idx); 286 conds.push_back(conds.back() || (dim >= rightBound)); 287 288 // When padding is involved, the indices will only be shifted to negative, 289 // so having a max op is enough. 290 auto maxMap = AffineMap::get(/*dimCount=*/1, 0, 291 {getAffineDimExpr(/*position=*/0, context), 292 getAffineConstantExpr(0, context)}, 293 context); 294 clampedImIdx.push_back(affine_max(dim.getType(), maxMap, ValueRange{dim})); 295 } 296 297 auto &b = ScopedContext::getBuilderRef(); 298 Type type = convOp.input().getType().cast<MemRefType>().getElementType(); 299 Value zero = std_constant(type, b.getZeroAttr(type)); 300 Value readInput = im(clampedImIdx); 301 return conds.empty() ? readInput 302 : (Value)std_select(conds.back(), zero, readInput); 303 } 304 305 /// Returns true is `convOp` has a non-zero padding. 306 static bool hasPadding(ConvOp convOp) { 307 for (unsigned i = 0, e = convOp.getNumSpatialDimensions(); i < e; ++i) { 308 if (convOp.getLowPad(i) > 0 || convOp.getHighPad(i) > 0) 309 return true; 310 } 311 return false; 312 } 313 314 template <typename IndexedValueType> 315 static void emitScalarImplementation(ArrayRef<Value> allIvs, ConvOp convOp) { 316 assert(convOp.hasBufferSemantics() && 317 "expected linalg op with buffer semantics"); 318 auto &b = ScopedContext::getBuilderRef(); 319 auto loc = ScopedContext::getLocation(); 320 auto mapsRange = convOp.indexing_maps().getAsRange<AffineMapAttr>(); 321 auto maps = llvm::to_vector<8>( 322 llvm::map_range(mapsRange, [](AffineMapAttr a) { return a.getValue(); })); 323 SmallVector<Value, 8> fIdx( 324 makeCanonicalAffineApplies(b, loc, maps[0], allIvs)); 325 SmallVector<Value, 8> imIdx( 326 makeCanonicalAffineApplies(b, loc, maps[1], allIvs)); 327 SmallVector<Value, 8> oIdx( 328 makeCanonicalAffineApplies(b, loc, maps[2], allIvs)); 329 330 IndexedValueType F(convOp.filter()), O(convOp.output()); 331 332 // Emit scalar form. Padded conv involves an affine.max in the memory access 333 // which is not allowed by affine.load. Override to use an StdIndexedValue 334 // when there is non-zero padding. 335 if (hasPadding(convOp)) { 336 StdIndexedValue I(convOp.input()); 337 Value paddedInput = getConvOpInput<IndexedValueType>(convOp, I, imIdx); 338 O(oIdx) += F(fIdx) * paddedInput; 339 } else { 340 IndexedValueType I(convOp.input()); 341 O(oIdx) += F(fIdx) * I(imIdx); 342 } 343 } 344 345 template <typename IndexedValueType> 346 void emitScalarImplementation(ArrayRef<Value> allIvs, PoolingMaxOp op) { 347 auto indices = getInputAndOutputIndices(allIvs, op); 348 // Emit scalar form. 349 Value lhs = std_load(op.output(), indices.outputs); 350 Value rhs = std_load(op.input(), indices.inputs); 351 using edsc::op::operator>; 352 Value maxValue = std_select(lhs > rhs, lhs, rhs); 353 std_store(maxValue, op.output(), indices.outputs); 354 } 355 template <typename IndexedValueType> 356 void emitScalarImplementation(ArrayRef<Value> allIvs, PoolingMinOp op) { 357 auto indices = getInputAndOutputIndices(allIvs, op); 358 // Emit scalar form. 359 Value lhs = std_load(op.output(), indices.outputs); 360 Value rhs = std_load(op.input(), indices.inputs); 361 using edsc::op::operator<; 362 Value minValue = std_select(lhs < rhs, lhs, rhs); 363 std_store(minValue, op.output(), indices.outputs); 364 } 365 template <typename IndexedValueType> 366 void emitScalarImplementation(ArrayRef<Value> allIvs, PoolingSumOp op) { 367 auto indices = getInputAndOutputIndices(allIvs, op); 368 IndexedValueType input(op.input()), output(op.output()); 369 370 // Emit scalar form. 371 output(indices.outputs) += input(indices.inputs); 372 } 373 /// Emits the MLIR for the scalar part of the indexed generic op by: 374 /// 1. Emitting load ops for each input and output view in order. This is 375 /// achieved by applying the appropriate input or output map to the 376 /// enclosing induction variables. 377 /// 2. Emitting a call to `op.fun()` that takes as arguments the induction 378 /// variables and the scalars from point 1. above. 379 /// 3. Emitting store ops to store the results of 2. to the output views. 380 /// 381 /// An example output may resemble: 382 /// 383 /// ``` 384 /// scf.for %i = %c0 to %0 step %c1 { 385 /// scf.for %j = %c0 to %1 step %c1 { 386 /// scf.for %k = %c0 to %4 step %c1 { 387 /// %11 = load %arg0[%i, %j] : 388 /// memref<?x?xf32, stride_specification> 389 /// %12 = load %arg1[%i, %j, %k] : 390 /// memref<?x?x?xf32, stride_specification> 391 /// %13 = load %arg2[%i, %k, %j] : 392 /// memref<?x?x?xf32, stride_specification> 393 /// %14:2 = call @foo(%i, %j, %k, %11, %12, %13) : 394 /// (index, index, index, f32, f32, f32) -> (f32, f32) 395 /// store %14#0, %arg1[%i, %j, %k] : 396 /// memref<?x?x?Xf32, stride_specification> 397 /// store %14#1, %arg2[%i, %k, %j] : 398 /// memref<?x?x?Xf32, stride_specification> 399 /// } 400 /// } 401 /// } 402 /// ``` 403 template <typename IndexedValueType> 404 static void emitScalarImplementation(ArrayRef<Value> allIvs, 405 IndexedGenericOp indexedGenericOp) { 406 assert(indexedGenericOp.hasBufferSemantics() && 407 "expected linalg op with buffer semantics"); 408 auto &b = ScopedContext::getBuilderRef(); 409 auto loc = ScopedContext::getLocation(); 410 unsigned nInputs = indexedGenericOp.getNumInputs(); 411 unsigned nOutputs = indexedGenericOp.getNumOutputs(); 412 unsigned nLoops = allIvs.size(); 413 SmallVector<Value, 4> indexedValues; 414 indexedValues.reserve(nLoops + nInputs + nOutputs); 415 for (unsigned i = 0; i < nLoops; ++i) 416 indexedValues.push_back(allIvs[i]); 417 418 // TODO(mravishankar): Avoid the loads if the corresponding argument of the 419 // region has no uses. 420 // 1.a. Emit load from input views. 421 for (unsigned i = 0; i < nInputs; ++i) { 422 auto indexing = makeCanonicalAffineApplies( 423 b, loc, indexedGenericOp.getInputIndexingMap(i), allIvs); 424 // Pass input i through IndexedValueType emits the proper load operation. 425 indexedValues.push_back( 426 IndexedValueType(indexedGenericOp.getInput(i))(indexing)); 427 } 428 // 1.b. Emit load from output views. 429 for (unsigned i = 0; i < nOutputs; ++i) { 430 auto indexing = makeCanonicalAffineApplies( 431 b, loc, indexedGenericOp.getOutputIndexingMap(i), allIvs); 432 // Pass output i through IndexedValueType emits the proper load operation. 433 indexedValues.push_back( 434 IndexedValueType(indexedGenericOp.getOutputBuffer(i))(indexing)); 435 } 436 437 // TODO(ntv): When a region inliner exists, use it. 438 // 2. Inline region, currently only works for a single basic block. 439 // 3. Emit store. 440 SmallVector<SmallVector<Value, 8>, 8> indexing; 441 SmallVector<Value, 8> outputBuffers; 442 for (unsigned i = 0; i < nOutputs; ++i) { 443 indexing.push_back(makeCanonicalAffineApplies( 444 b, loc, indexedGenericOp.getOutputIndexingMap(i), allIvs)); 445 outputBuffers.push_back(indexedGenericOp.getOutputBuffer(i)); 446 } 447 inlineRegionAndEmitStore<IndexedValueType>(indexedGenericOp, indexedValues, 448 indexing, outputBuffers); 449 } 450 451 template <typename LoopTy, typename ConcreteOpTy> 452 Optional<LinalgLoops> linalgOpToLoopsImpl(Operation *op, OpBuilder &builder) { 453 using IndexedValueTy = typename GenerateLoopNest<LoopTy>::IndexedValueTy; 454 455 ScopedContext scope(builder, op->getLoc()); 456 457 // The flattened loopToOperandRangesMaps is expected to be an invertible 458 // permutation map (which is asserted in the inverse calculation). 459 auto linalgOp = cast<ConcreteOpTy>(op); 460 assert(linalgOp.hasBufferSemantics() && 461 "expected linalg op with buffer semantics"); 462 auto nPar = linalgOp.getNumParallelLoops(); 463 auto nRed = linalgOp.getNumReductionLoops(); 464 auto nWin = linalgOp.getNumWindowLoops(); 465 auto nLoops = nPar + nRed + nWin; 466 auto mapsRange = 467 linalgOp.indexing_maps().template getAsRange<AffineMapAttr>(); 468 auto maps = llvm::to_vector<8>( 469 llvm::map_range(mapsRange, [](AffineMapAttr a) { return a.getValue(); })); 470 AffineMap invertedMap = inversePermutation(concatAffineMaps(maps)); 471 if (!invertedMap) 472 return {}; 473 if (invertedMap.isEmpty()) { 474 emitScalarImplementation<IndexedValueTy>({}, linalgOp); 475 return LinalgLoops(); 476 } 477 478 SmallVector<Value, 4> allIvs(nLoops); 479 auto loopRanges = 480 emitLoopRanges(scope.getBuilderRef(), scope.getLocation(), invertedMap, 481 getViewSizes(builder, linalgOp)); 482 assert(loopRanges.size() == allIvs.size()); 483 GenerateLoopNest<LoopTy>::doit( 484 allIvs, loopRanges, linalgOp.iterator_types().getValue(), [&] { 485 SmallVector<Value, 4> allIvValues(allIvs.begin(), allIvs.end()); 486 emitScalarImplementation<IndexedValueTy>(allIvValues, linalgOp); 487 }); 488 // Number of loop ops might be different from the number of ivs since some 489 // loops like affine.parallel and scf.parallel have multiple ivs. 490 llvm::SetVector<Operation *> loopSet; 491 for (Value iv : allIvs) { 492 if (!iv) 493 return {}; 494 // The induction variable is a block argument of the entry block of the 495 // loop operation. 496 BlockArgument ivVal = iv.dyn_cast<BlockArgument>(); 497 if (!ivVal) 498 return {}; 499 loopSet.insert(ivVal.getOwner()->getParentOp()); 500 } 501 LinalgLoops loops(loopSet.begin(), loopSet.end()); 502 return loops; 503 } 504 505 template <typename LoopType, typename ConcreteOp> 506 class LinalgRewritePattern : public RewritePattern { 507 public: 508 explicit LinalgRewritePattern(MLIRContext *context) 509 : RewritePattern(ConcreteOp::getOperationName(), 1, context) {} 510 511 LogicalResult matchAndRewrite(Operation *op, 512 PatternRewriter &rewriter) const override { 513 if (!linalgOpToLoopsImpl<LoopType, ConcreteOp>(op, rewriter)) 514 return failure(); 515 rewriter.eraseOp(op); 516 return success(); 517 } 518 }; 519 520 template <typename LoopType, typename ConcreteOp> 521 void insertOnePattern(OwningRewritePatternList &patterns, MLIRContext *ctx) { 522 patterns.insert<LinalgRewritePattern<LoopType, ConcreteOp>>(ctx); 523 } 524 525 template <typename LoopType, typename... Args> 526 void insertPatterns(OwningRewritePatternList &patterns, MLIRContext *ctx) { 527 (void)std::initializer_list<int>{ 528 0, (insertOnePattern<LoopType, Args>(patterns, ctx), 0)...}; 529 } 530 531 /// Local folding pattern for AffineApplyOp that we can apply greedily. 532 /// This replaces AffineApplyOp by the proper value in cases where the 533 /// associated map is trivial. 534 /// A trivial map here is defined as a map with a single result and either: 535 /// 1. Zero operand + returns a single AffineConstantExpr 536 /// 2. One operand + returns a single AffineDimExpr 537 /// 3. One operand + returns a single AffineSymbolExpr 538 // 539 /// In the first case, the AffineApplyOp is replaced by a new constant. In the 540 /// other cases, it is replaced by its unique operand. 541 struct FoldAffineOp : public RewritePattern { 542 FoldAffineOp(MLIRContext *context) 543 : RewritePattern(AffineApplyOp::getOperationName(), 0, context) {} 544 545 LogicalResult matchAndRewrite(Operation *op, 546 PatternRewriter &rewriter) const override { 547 AffineApplyOp affineApplyOp = cast<AffineApplyOp>(op); 548 auto map = affineApplyOp.getAffineMap(); 549 if (map.getNumResults() != 1 || map.getNumInputs() > 1) 550 return failure(); 551 552 AffineExpr expr = map.getResult(0); 553 if (map.getNumInputs() == 0) { 554 if (auto val = expr.dyn_cast<AffineConstantExpr>()) { 555 rewriter.replaceOpWithNewOp<ConstantIndexOp>(op, val.getValue()); 556 return success(); 557 } 558 return failure(); 559 } 560 if (expr.dyn_cast<AffineDimExpr>() || expr.dyn_cast<AffineSymbolExpr>()) { 561 rewriter.replaceOp(op, op->getOperand(0)); 562 return success(); 563 } 564 return failure(); 565 } 566 }; 567 } // namespace 568 569 template <typename LoopType> 570 static void lowerLinalgToLoopsImpl(FuncOp funcOp, MLIRContext *context) { 571 OwningRewritePatternList patterns; 572 // Canonicalization and folding patterns applied greedily allow cleaning up 573 // the emitted IR on the fly. 574 // TODO(ntv) fold view and subview ops? 575 insertPatterns<LoopType, 576 #define GET_OP_LIST 577 #include "mlir/Dialect/Linalg/IR/LinalgStructuredOps.cpp.inc" 578 >(patterns, context); 579 580 DimOp::getCanonicalizationPatterns(patterns, context); 581 AffineApplyOp::getCanonicalizationPatterns(patterns, context); 582 patterns.insert<FoldAffineOp>(context); 583 // Just apply the patterns greedily. 584 applyPatternsAndFoldGreedily(funcOp, patterns); 585 } 586 587 namespace { 588 struct LowerToAffineLoops 589 : public LinalgLowerToAffineLoopsBase<LowerToAffineLoops> { 590 void runOnFunction() override { 591 lowerLinalgToLoopsImpl<AffineForOp>(getFunction(), &getContext()); 592 } 593 }; 594 struct LowerToLoops : public LinalgLowerToLoopsBase<LowerToLoops> { 595 void runOnFunction() override { 596 lowerLinalgToLoopsImpl<scf::ForOp>(getFunction(), &getContext()); 597 } 598 }; 599 struct LowerToParallelLoops 600 : public LinalgLowerToParallelLoopsBase<LowerToParallelLoops> { 601 void runOnFunction() override { 602 lowerLinalgToLoopsImpl<scf::ParallelOp>(getFunction(), &getContext()); 603 } 604 }; 605 } // namespace 606 607 std::unique_ptr<OperationPass<FuncOp>> mlir::createConvertLinalgToLoopsPass() { 608 return std::make_unique<LowerToLoops>(); 609 } 610 611 std::unique_ptr<OperationPass<FuncOp>> 612 mlir::createConvertLinalgToParallelLoopsPass() { 613 return std::make_unique<LowerToParallelLoops>(); 614 } 615 616 std::unique_ptr<OperationPass<FuncOp>> 617 mlir::createConvertLinalgToAffineLoopsPass() { 618 return std::make_unique<LowerToAffineLoops>(); 619 } 620 621 // TODO: gradually remove this layer as more ops become "named". 622 template <typename LoopTy> 623 Optional<LinalgLoops> linalgOpToLoopsImplSwitch(Operation *op, 624 OpBuilder &builder) { 625 assert(isa<LinalgOp>(op) && "LinalgOp expected"); 626 if (isa<CopyOp>(op)) 627 return linalgOpToLoopsImpl<LoopTy, CopyOp>(op, builder); 628 if (isa<FillOp>(op)) 629 return linalgOpToLoopsImpl<LoopTy, FillOp>(op, builder); 630 if (isa<DotOp>(op)) 631 return linalgOpToLoopsImpl<LoopTy, DotOp>(op, builder); 632 if (isa<MatvecOp>(op)) 633 return linalgOpToLoopsImpl<LoopTy, MatvecOp>(op, builder); 634 if (isa<ConvOp>(op)) 635 return linalgOpToLoopsImpl<LoopTy, ConvOp>(op, builder); 636 if (isa<PoolingMaxOp>(op)) 637 return linalgOpToLoopsImpl<LoopTy, PoolingMaxOp>(op, builder); 638 if (isa<PoolingMinOp>(op)) 639 return linalgOpToLoopsImpl<LoopTy, PoolingMinOp>(op, builder); 640 if (isa<PoolingSumOp>(op)) 641 return linalgOpToLoopsImpl<LoopTy, PoolingSumOp>(op, builder); 642 if (isa<IndexedGenericOp>(op)) 643 return linalgOpToLoopsImpl<LoopTy, IndexedGenericOp>(op, builder); 644 645 // TODO: Cases below are generic and need a LinalgStructuredOpInterface. 646 if (isa<GenericOp>(op)) 647 return linalgOpToLoopsImpl<LoopTy, GenericOp>(op, builder); 648 if (isa<MatmulOp>(op)) 649 return linalgOpToLoopsImpl<LoopTy, MatmulOp>(op, builder); 650 if (isa<BatchMatmulOp>(op)) 651 return linalgOpToLoopsImpl<LoopTy, BatchMatmulOp>(op, builder); 652 llvm_unreachable("Unexpected op in linalgOpToLoopsImpl"); 653 } 654 655 /// Emits a loop nest with the proper body for `op`. 656 template <typename LoopTy> 657 Optional<LinalgLoops> mlir::linalg::linalgLowerOpToLoops(OpBuilder &builder, 658 Operation *op) { 659 return linalgOpToLoopsImplSwitch<LoopTy>(op, builder); 660 } 661 662 template Optional<LinalgLoops> 663 mlir::linalg::linalgLowerOpToLoops<AffineForOp>(OpBuilder &builder, 664 Operation *op); 665 template Optional<LinalgLoops> 666 mlir::linalg::linalgLowerOpToLoops<scf::ForOp>(OpBuilder &builder, 667 Operation *op); 668 template Optional<LinalgLoops> 669 mlir::linalg::linalgLowerOpToLoops<scf::ParallelOp>(OpBuilder &builder, 670 Operation *op); 671 672 /// Emits a loop nest of `affine.for` with the proper body for `op`. 673 LogicalResult mlir::linalg::linalgOpToAffineLoops(OpBuilder &builder, 674 Operation *op) { 675 Optional<LinalgLoops> loops = linalgLowerOpToLoops<AffineForOp>(builder, op); 676 return loops ? success() : failure(); 677 } 678 679 /// Emits a loop nest of `scf.for` with the proper body for `op`. 680 LogicalResult mlir::linalg::linalgOpToLoops(OpBuilder &builder, Operation *op) { 681 Optional<LinalgLoops> loops = linalgLowerOpToLoops<scf::ForOp>(builder, op); 682 return loops ? success() : failure(); 683 } 684 685 /// Emits a loop nest of `scf.parallel` with the proper body for `op`. 686 LogicalResult mlir::linalg::linalgOpToParallelLoops(OpBuilder &builder, 687 Operation *op) { 688 Optional<LinalgLoops> loops = 689 linalgLowerOpToLoops<scf::ParallelOp>(builder, op); 690 return loops ? success() : failure(); 691 } 692