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/Linalg/IR/LinalgOps.h" 11 #include "mlir/Dialect/Linalg/IR/LinalgTypes.h" 12 #include "mlir/Dialect/Linalg/Passes.h" 13 #include "mlir/Dialect/Linalg/Transforms/Transforms.h" 14 #include "mlir/Dialect/Linalg/Utils/Utils.h" 15 #include "mlir/IR/AffineExpr.h" 16 #include "mlir/IR/AffineMap.h" 17 #include "mlir/IR/BlockAndValueMapping.h" 18 #include "mlir/Support/LLVM.h" 19 #include "mlir/Transforms/DialectConversion.h" 20 #include "mlir/Transforms/FoldUtils.h" 21 #include "mlir/Transforms/GreedyPatternRewriteDriver.h" 22 #include "llvm/ADT/TypeSwitch.h" 23 24 using namespace mlir; 25 using namespace mlir::linalg; 26 27 namespace { 28 /// Helper struct to build simple arithmetic quantities with minimal type 29 /// inference support. 30 struct ArithBuilder { 31 ArithBuilder(OpBuilder &b, Location loc) : b(b), loc(loc) {} 32 33 Value select(Value cmp, Value lhs, Value rhs) { 34 return b.create<SelectOp>(loc, cmp, lhs, rhs); 35 } 36 Value slt(Value lhs, Value rhs) { 37 if (lhs.getType().isa<IntegerType>()) 38 return b.create<CmpIOp>(loc, CmpIPredicate::slt, lhs, rhs); 39 return b.create<CmpFOp>(loc, CmpFPredicate::OLT, lhs, rhs); 40 } 41 Value sgt(Value lhs, Value rhs) { 42 if (lhs.getType().isa<IntegerType>()) 43 return b.create<CmpIOp>(loc, CmpIPredicate::sgt, lhs, rhs); 44 return b.create<CmpFOp>(loc, CmpFPredicate::OGT, lhs, rhs); 45 } 46 Value add(Value lhs, Value rhs) { 47 if (lhs.getType().isa<IntegerType>()) 48 return b.create<AddIOp>(loc, lhs, rhs); 49 return b.create<AddFOp>(loc, lhs, rhs); 50 } 51 Value mul(Value lhs, Value rhs) { 52 if (lhs.getType().isa<IntegerType>()) 53 return b.create<MulIOp>(loc, lhs, rhs); 54 return b.create<MulFOp>(loc, lhs, rhs); 55 } 56 57 OpBuilder &b; 58 Location loc; 59 }; 60 } // namespace 61 62 static SmallVector<Value> makeCanonicalAffineApplies(OpBuilder &b, Location loc, 63 AffineMap map, 64 ArrayRef<Value> vals) { 65 if (map.isEmpty()) 66 return {}; 67 68 assert(map.getNumInputs() == vals.size()); 69 SmallVector<Value> res; 70 res.reserve(map.getNumResults()); 71 auto dims = map.getNumDims(); 72 for (auto e : map.getResults()) { 73 auto exprMap = AffineMap::get(dims, map.getNumSymbols(), e); 74 SmallVector<Value> operands(vals.begin(), vals.end()); 75 canonicalizeMapAndOperands(&exprMap, &operands); 76 res.push_back(b.create<AffineApplyOp>(loc, exprMap, operands)); 77 } 78 return res; 79 } 80 81 template <typename LoadOpTy, typename StoreOpTy, typename OpType> 82 static void inlineRegionAndEmitStore(OpBuilder &b, Location loc, OpType op, 83 ArrayRef<Value> indexedValues, 84 ArrayRef<SmallVector<Value>> indexing, 85 ArrayRef<Value> outputBuffers) { 86 auto &block = op->getRegion(0).front(); 87 BlockAndValueMapping map; 88 map.map(block.getArguments(), indexedValues); 89 for (auto &op : block.without_terminator()) { 90 auto *newOp = b.clone(op, map); 91 map.map(op.getResults(), newOp->getResults()); 92 } 93 94 Operation *terminator = block.getTerminator(); 95 for (OpOperand &operand : terminator->getOpOperands()) { 96 Value toStore = map.lookupOrDefault(operand.get()); 97 b.create<StoreOpTy>(loc, toStore, outputBuffers[operand.getOperandNumber()], 98 indexing[operand.getOperandNumber()]); 99 } 100 } 101 102 // Returns a pair that contains input indices and output indices of a 103 // SingleInputPoolingOp `op`. 104 struct InputAndOutputIndices { 105 SmallVector<Value> inputs; 106 SmallVector<Value> outputs; 107 }; 108 template <typename SingleInputPoolingOp> 109 static InputAndOutputIndices 110 getInputAndOutputIndices(OpBuilder &b, Location loc, ArrayRef<Value> allIvs, 111 SingleInputPoolingOp op) { 112 auto mapsRange = op.indexing_maps().template getAsRange<AffineMapAttr>(); 113 auto maps = llvm::to_vector<8>( 114 llvm::map_range(mapsRange, [](AffineMapAttr a) { return a.getValue(); })); 115 return InputAndOutputIndices{ 116 makeCanonicalAffineApplies(b, loc, maps[0], allIvs), 117 makeCanonicalAffineApplies(b, loc, maps[2], allIvs)}; 118 } 119 120 /// Emits the MLIR for the scalar part of the generic op by: 121 /// 1. Emitting load ops for each input and output view in order. This is 122 /// achieved by applying the appropriate input or output map to the 123 /// enclosing induction variables. 124 /// 2. Emitting a call to `op.fun()` that takes as arguments the scalars 125 /// from point 1. above. 126 /// 3. Emitting store ops to store the results of 2. to the output 127 /// views. 128 /// 129 /// An example output may resemble: 130 /// 131 /// ``` 132 /// scf.for %i = %c0 to %0 step %c1 { 133 /// scf.for %j = %c0 to %1 step %c1 { 134 /// scf.for %k = %c0 to %4 step %c1 { 135 /// %11 = load %arg0[%i, %j] : 136 /// memref<?x?xf32, stride_specification> 137 /// %12 = load %arg1[%i, %j, %k] : 138 /// memref<?x?x?xf32, stride_specification> 139 /// %13 = load %arg2[%i, %k, %j] : 140 /// memref<?x?x?xf32, stride_specification> 141 /// %14:2 = call @foo(%11, %12, %13) : (f32, f32, f32) -> (f32, f32) 142 /// store %14#0, %arg1[%i, %j, %k] : 143 /// memref<?x?x?Xf32, stride_specification> 144 /// store %14#1, %arg2[%i, %k, %j] : 145 /// memref<?x?x?Xf32, stride_specification> 146 /// } 147 /// } 148 /// } 149 /// ``` 150 template <typename LoadOpTy, typename StoreOpTy> 151 static void emitScalarImplementation(OpBuilder &b, Location loc, 152 ArrayRef<Value> allIvs, 153 LinalgOp linalgOp) { 154 assert(linalgOp.hasBufferSemantics() && 155 "expected linalg op with buffer semantics"); 156 unsigned nInputs = linalgOp.getNumInputs(); 157 unsigned nOutputs = linalgOp.getNumOutputs(); 158 SmallVector<Value> indexedValues; 159 indexedValues.reserve(nInputs + nOutputs); 160 161 auto allIvsPlusDims = SmallVector<Value>(allIvs.begin(), allIvs.end()); 162 163 // TODO: Avoid the loads if the corresponding argument of the 164 // region has no uses. 165 // 1.a. Emit load from input views. 166 for (unsigned i = 0; i < nInputs; ++i) { 167 auto indexing = makeCanonicalAffineApplies( 168 b, loc, linalgOp.getInputIndexingMap(i), allIvsPlusDims); 169 indexedValues.push_back( 170 b.create<LoadOpTy>(loc, linalgOp.getInput(i), indexing)); 171 } 172 // 1.b. Emit load from output views. 173 for (unsigned i = 0; i < nOutputs; ++i) { 174 auto indexing = makeCanonicalAffineApplies( 175 b, loc, linalgOp.getOutputIndexingMap(i), allIvsPlusDims); 176 indexedValues.push_back( 177 b.create<LoadOpTy>(loc, linalgOp.getOutputBuffer(i), indexing)); 178 } 179 180 // TODO: When a region inliner exists, use it. 181 // 2. Inline region, currently only works for a single basic block. 182 // 3. Emit store. 183 SmallVector<SmallVector<Value>, 8> indexing; 184 SmallVector<Value> outputBuffers; 185 for (unsigned i = 0; i < nOutputs; ++i) { 186 indexing.push_back(makeCanonicalAffineApplies( 187 b, loc, linalgOp.getOutputIndexingMap(i), allIvsPlusDims)); 188 outputBuffers.push_back(linalgOp.getOutputBuffer(i)); 189 } 190 inlineRegionAndEmitStore<LoadOpTy, StoreOpTy>(b, loc, linalgOp, indexedValues, 191 indexing, outputBuffers); 192 } 193 194 // Create a padded view into the given `input` tensor using the 'indices' 195 // to access the tensor. `skipPadding` lists the dimensions for which no padding 196 // is needed e.g. the non-spatial dimensions for convolutions. 197 Value getPaddedInput(OpBuilder &b, Location loc, Value input, 198 ArrayRef<Value> indices, ArrayRef<int> skipPadding, 199 Value padValue) { 200 Value zeroIndex = b.create<ConstantIndexOp>(loc, 0); 201 SmallVector<Value> conds; 202 SmallVector<Value> clampedImIdx; 203 for (auto iter : llvm::enumerate(indices)) { 204 int idx = iter.index(); 205 auto dim = iter.value(); 206 if (is_contained(skipPadding, idx)) { 207 clampedImIdx.push_back(dim); 208 continue; 209 } 210 211 Value leftOutOfBound = 212 b.create<CmpIOp>(loc, CmpIPredicate::slt, dim, zeroIndex); 213 if (conds.empty()) 214 conds.push_back(leftOutOfBound); 215 else 216 conds.push_back(b.create<OrOp>(loc, conds.back(), leftOutOfBound)); 217 Value rightBound = b.create<memref::DimOp>(loc, input, idx); 218 Value rightOutOfBound = 219 b.create<CmpIOp>(loc, CmpIPredicate::sge, dim, rightBound); 220 conds.push_back(b.create<OrOp>(loc, conds.back(), rightOutOfBound)); 221 222 // When padding is involved, the indices will only be shifted to negative, 223 // so having a max op is enough. 224 MLIRContext *ctx = input.getContext(); 225 AffineExpr m = getAffineDimExpr(/*position=*/0, ctx), 226 zero = getAffineConstantExpr(0, ctx); 227 AffineMap maxMap = 228 AffineMap::inferFromExprList(ArrayRef<ArrayRef<AffineExpr>>{{m, zero}}) 229 .front(); 230 clampedImIdx.push_back(b.create<AffineMaxOp>(loc, maxMap, ValueRange{dim})); 231 } 232 233 Value readInput = b.create<memref::LoadOp>(loc, input, clampedImIdx); 234 if (conds.empty()) 235 return readInput; 236 237 return b.create<SelectOp>(loc, conds.back(), padValue, readInput); 238 } 239 240 namespace { 241 242 /// The padding value for a given Op depends on the semantics of the Op. 243 /// The identity value for ConvOp and PoolingSumOp is 0, for PoolingMaxOp is 244 /// -inf or minInt and for PoolingMinOp is inf or maxInt. 245 template <typename OpType> Attribute getPadValueAttr(Type type) { 246 llvm_unreachable("Unexpected op type for getPadValueAttr"); 247 return {}; 248 } 249 250 template <> Attribute getPadValueAttr<PoolingMaxOp>(Type type) { 251 if (auto floatType = type.dyn_cast<FloatType>()) { 252 return OpBuilder(type.getContext()) 253 .getFloatAttr(floatType, APFloat::getInf(floatType.getFloatSemantics(), 254 /*Negative*/ true)); 255 } 256 if (auto intType = type.dyn_cast<IntegerType>()) { 257 unsigned width = intType.getWidth(); 258 // The select instruction used to lower the PoolingMin uses a signed 259 // comparison, use a signed constant irrespective of the signedness of the 260 // integer type. 261 return OpBuilder(type.getContext()) 262 .getIntegerAttr(intType, APInt::getSignedMinValue(width)); 263 } 264 llvm_unreachable("Unsupported data type for PoolingMaxOp"); 265 return {}; 266 } 267 268 template <> Attribute getPadValueAttr<PoolingMinOp>(Type type) { 269 if (auto floatType = type.dyn_cast<FloatType>()) { 270 return OpBuilder(type.getContext()) 271 .getFloatAttr(floatType, 272 APFloat::getInf(floatType.getFloatSemantics())); 273 } 274 if (auto intType = type.dyn_cast<IntegerType>()) { 275 unsigned width = intType.getWidth(); 276 // The select instruction used to lower the PoolingMin uses a signed 277 // comparison, use a signed constant irrespective of the signedness of the 278 // integer type. 279 return OpBuilder(type.getContext()) 280 .getIntegerAttr(intType, APInt::getSignedMaxValue(width)); 281 } 282 llvm_unreachable("Unsupported data type for PoolingMinOp"); 283 return {}; 284 } 285 286 template <> Attribute getPadValueAttr<PoolingSumOp>(Type type) { 287 return OpBuilder(type.getContext()).getZeroAttr(type); 288 } 289 290 template <> Attribute getPadValueAttr<ConvOp>(Type type) { 291 return OpBuilder(type.getContext()).getZeroAttr(type); 292 } 293 294 } // namespace 295 296 /// Returns true is `convOp` has a non-zero padding. 297 static bool hasPadding(ConvOp convOp) { 298 for (unsigned i = 0, e = convOp.getNumSpatialDimensions(); i < e; ++i) { 299 if (convOp.getLowPad(i) > 0 || convOp.getHighPad(i) > 0) 300 return true; 301 } 302 return false; 303 } 304 305 template <typename LoadOpTy, typename StoreOpTy> 306 static void emitScalarImplementation(OpBuilder &b, Location loc, 307 ArrayRef<Value> allIvs, ConvOp convOp) { 308 assert(convOp.hasBufferSemantics() && 309 "expected linalg op with buffer semantics"); 310 auto mapsRange = convOp.indexing_maps().getAsRange<AffineMapAttr>(); 311 auto maps = llvm::to_vector<8>( 312 llvm::map_range(mapsRange, [](AffineMapAttr a) { return a.getValue(); })); 313 SmallVector<Value> fIdx(makeCanonicalAffineApplies(b, loc, maps[0], allIvs)); 314 SmallVector<Value> imIdx(makeCanonicalAffineApplies(b, loc, maps[1], allIvs)); 315 SmallVector<Value> oIdx(makeCanonicalAffineApplies(b, loc, maps[2], allIvs)); 316 317 Value filter = convOp.filter(), output = convOp.output(); 318 319 // Emit scalar form. Padded conv involves an affine.max in the memory access 320 // which is not allowed by affine.load. Override to use an MemRefIndexedValue 321 // when there is non-zero padding. 322 if (hasPadding(convOp)) { 323 Type type = convOp.input().getType().cast<MemRefType>().getElementType(); 324 Value padValue = 325 b.create<ConstantOp>(loc, type, getPadValueAttr<ConvOp>(type)); 326 Value paddedInput = 327 getPaddedInput(b, loc, convOp.input(), imIdx, 328 /* Only need to pad the window dimensions */ 329 {0, static_cast<int>(imIdx.size()) - 1}, padValue); 330 Value filterVal = b.create<LoadOpTy>(loc, filter, fIdx); 331 Value mulVal = ArithBuilder(b, loc).mul(filterVal, paddedInput); 332 Value outputVal = b.create<LoadOpTy>(loc, output, oIdx); 333 Value addVal = ArithBuilder(b, loc).add(mulVal, outputVal); 334 b.create<StoreOpTy>(loc, addVal, output, oIdx); 335 } else { 336 Value inputVal = b.create<LoadOpTy>(loc, convOp.input(), imIdx); 337 Value filterVal = b.create<LoadOpTy>(loc, filter, fIdx); 338 Value mulVal = ArithBuilder(b, loc).mul(filterVal, inputVal); 339 Value outputVal = b.create<LoadOpTy>(loc, output, oIdx); 340 Value addVal = ArithBuilder(b, loc).add(mulVal, outputVal); 341 b.create<StoreOpTy>(loc, addVal, output, oIdx); 342 } 343 } 344 345 template <typename PoolingOp> static bool hasPadding(PoolingOp poolingOp) { 346 for (unsigned i = 0, e = poolingOp.getNumWindowLoops(); i < e; ++i) { 347 if (poolingOp.getLowPad(i) > 0 || poolingOp.getHighPad(i) > 0) 348 return true; 349 } 350 return false; 351 } 352 353 template <typename LoadOpTy, typename StoreOpTy, typename PoolingOp> 354 static Value getPoolingInput(OpBuilder &b, Location loc, PoolingOp op, 355 ArrayRef<Value> inputIndices) { 356 if (hasPadding(op)) { 357 Type type = 358 op.input().getType().template cast<MemRefType>().getElementType(); 359 Value padValue = 360 b.create<ConstantOp>(loc, type, getPadValueAttr<PoolingOp>(type)); 361 return getPaddedInput(b, loc, op.input(), inputIndices, 362 /*Pad every dimension*/ {}, padValue); 363 } 364 return b.create<LoadOpTy>(loc, op.input(), inputIndices); 365 } 366 367 template <typename LoadOpTy, typename StoreOpTy, typename OpType> 368 void emitPoolingMinMaxScalarImplementation(OpBuilder &b, Location loc, 369 ArrayRef<Value> allIvs, OpType op) { 370 InputAndOutputIndices indices = getInputAndOutputIndices(b, loc, allIvs, op); 371 Value lhs = b.create<LoadOpTy>(loc, op.output(), indices.outputs); 372 Value rhs = getPoolingInput<LoadOpTy, StoreOpTy>(b, loc, op, indices.inputs); 373 Value value = llvm::TypeSwitch<Operation *, Value>(op) 374 .Case([&](PoolingMinOp poolingOp) { 375 return ArithBuilder(b, loc).select( 376 ArithBuilder(b, loc).slt(lhs, rhs), lhs, rhs); 377 }) 378 .Case([&](PoolingMaxOp poolingOp) { 379 return ArithBuilder(b, loc).select( 380 ArithBuilder(b, loc).sgt(lhs, rhs), lhs, rhs); 381 }) 382 .Default([&](auto) { return Value(); }); 383 b.create<StoreOpTy>(loc, value, op.output(), indices.outputs); 384 } 385 386 template <typename LoadOpTy, typename StoreOpTy> 387 static void emitScalarImplementation(OpBuilder &b, Location loc, 388 ArrayRef<Value> allIvs, PoolingMaxOp op) { 389 emitPoolingMinMaxScalarImplementation<LoadOpTy, StoreOpTy, PoolingMaxOp>( 390 b, loc, allIvs, op); 391 } 392 393 template <typename LoadOpTy, typename StoreOpTy> 394 static void emitScalarImplementation(OpBuilder &b, Location loc, 395 ArrayRef<Value> allIvs, PoolingMinOp op) { 396 emitPoolingMinMaxScalarImplementation<LoadOpTy, StoreOpTy, PoolingMinOp>( 397 b, loc, allIvs, op); 398 } 399 400 template <typename LoadOpTy, typename StoreOpTy> 401 static void emitScalarImplementation(OpBuilder &b, Location loc, 402 ArrayRef<Value> allIvs, PoolingSumOp op) { 403 auto indices = getInputAndOutputIndices(b, loc, allIvs, op); 404 Value inputVal = 405 getPoolingInput<LoadOpTy, StoreOpTy>(b, loc, op, indices.inputs); 406 Value outputVal = b.create<LoadOpTy>(loc, op.output(), indices.outputs); 407 Value added = ArithBuilder(b, loc).add(outputVal, inputVal); 408 b.create<StoreOpTy>(loc, added, op.output(), indices.outputs); 409 } 410 411 /// Replace the index operations in the body of the loop nest by the matching 412 /// induction variables. 413 static void replaceIndexOpsByInductionVariables(LinalgOp linalgOp, 414 PatternRewriter &rewriter, 415 ArrayRef<Operation *> loopOps) { 416 // Extract the induction variables of the loop nest from outer to inner. 417 SmallVector<Value> allIvs; 418 for (Operation *loopOp : loopOps) { 419 llvm::TypeSwitch<Operation *>(loopOp) 420 .Case([&](scf::ParallelOp parallelOp) { 421 allIvs.append(parallelOp.getInductionVars().begin(), 422 parallelOp.getInductionVars().end()); 423 }) 424 .Case([&](scf::ForOp forOp) { 425 allIvs.push_back(forOp.getInductionVar()); 426 }) 427 .Case([&](AffineForOp affineForOp) { 428 allIvs.push_back(affineForOp.getInductionVar()); 429 }) 430 .Default([&](Operation *op) { assert(false && "unexpected op"); }); 431 } 432 assert(linalgOp.getNumLoops() == allIvs.size() && 433 "expected the number of loops and induction variables to match"); 434 // Replace the index operations in the body of the innermost loop op. 435 if (!loopOps.empty()) { 436 LoopLikeOpInterface loopOp = loopOps.back(); 437 for (IndexOp indexOp : 438 llvm::make_early_inc_range(loopOp.getLoopBody().getOps<IndexOp>())) 439 rewriter.replaceOp(indexOp, allIvs[indexOp.dim()]); 440 } 441 } 442 443 template <typename LoopTy> 444 static Optional<LinalgLoops> linalgOpToLoopsImpl(PatternRewriter &rewriter, 445 LinalgOp linalgOp) { 446 using LoadOpTy = 447 typename std::conditional<std::is_same<LoopTy, AffineForOp>::value, 448 AffineLoadOp, memref::LoadOp>::type; 449 using StoreOpTy = 450 typename std::conditional<std::is_same<LoopTy, AffineForOp>::value, 451 AffineStoreOp, memref::StoreOp>::type; 452 453 // Canonicalize indexed_generic operations before lowering them to loops. 454 if (isa<IndexedGenericOp>(linalgOp)) 455 return llvm::None; 456 457 // The flattened loopToOperandRangesMaps is expected to be an invertible 458 // permutation map (which is asserted in the inverse calculation). 459 assert(linalgOp.hasBufferSemantics() && 460 "expected linalg op with buffer semantics"); 461 462 auto loopRanges = linalgOp.createLoopRanges(rewriter, linalgOp.getLoc()); 463 auto iteratorTypes = llvm::to_vector<4>(linalgOp.iterator_types().getValue()); 464 465 SmallVector<Value> allIvs; 466 GenerateLoopNest<LoopTy>::doit( 467 rewriter, linalgOp.getLoc(), loopRanges, linalgOp, iteratorTypes, 468 [&](OpBuilder &b, Location loc, ValueRange ivs, 469 ValueRange iterArgs) -> scf::ValueVector { 470 assert(iterArgs.empty() && "unexpected iterArgs"); 471 allIvs.append(ivs.begin(), ivs.end()); 472 llvm::TypeSwitch<Operation *>(linalgOp) 473 .Case<ConvOp, PoolingMaxOp, PoolingMinOp, PoolingSumOp, LinalgOp>( 474 [&](auto op) { 475 emitScalarImplementation<LoadOpTy, StoreOpTy>(b, loc, allIvs, 476 op); 477 }) 478 .Default([&](Operation *op) { assert(false && "unexpected op"); }); 479 return scf::ValueVector{}; 480 }); 481 // Number of loop ops might be different from the number of ivs since some 482 // loops like affine.parallel and scf.parallel have multiple ivs. 483 SetVector<Operation *> loopSet; 484 for (Value iv : allIvs) { 485 if (!iv) 486 return {}; 487 // The induction variable is a block argument of the entry block of the 488 // loop operation. 489 BlockArgument ivVal = iv.dyn_cast<BlockArgument>(); 490 if (!ivVal) 491 return {}; 492 loopSet.insert(ivVal.getOwner()->getParentOp()); 493 } 494 LinalgLoops loops(loopSet.begin(), loopSet.end()); 495 // Replace all index operations in the loop body. 496 replaceIndexOpsByInductionVariables(linalgOp, rewriter, loops); 497 return loops; 498 } 499 500 namespace { 501 template <typename LoopType> 502 class LinalgRewritePattern : public RewritePattern { 503 public: 504 LinalgRewritePattern(MLIRContext *context) 505 : RewritePattern(MatchAnyOpTypeTag(), /*benefit=*/1, context) {} 506 507 LogicalResult matchAndRewrite(Operation *op, 508 PatternRewriter &rewriter) const override { 509 auto linalgOp = dyn_cast<LinalgOp>(op); 510 if (!isa<LinalgOp>(op)) 511 return failure(); 512 if (!linalgOpToLoopsImpl<LoopType>(rewriter, linalgOp)) 513 return failure(); 514 rewriter.eraseOp(op); 515 return success(); 516 } 517 }; 518 519 struct TiledLoopToSCFPattern : public OpRewritePattern<TiledLoopOp> { 520 using OpRewritePattern<TiledLoopOp>::OpRewritePattern; 521 522 LogicalResult matchAndRewrite(TiledLoopOp tiledLoop, 523 PatternRewriter &rewriter) const override { 524 Location loc = tiledLoop.getLoc(); 525 526 // Fail conversion if the `tiled_loop` has not been bufferized. 527 if (!llvm::all_of(tiledLoop.outputs(), [&](Value arg) { 528 return arg.getType().isa<MemRefType>(); 529 })) 530 return failure(); 531 532 // TODO: Build loop nest with `scf.for` and `scf.parallel` depending on the 533 // iterator type. 534 scf::buildLoopNest(rewriter, loc, tiledLoop.lowerBound(), 535 tiledLoop.upperBound(), tiledLoop.step(), 536 [&](OpBuilder &builder, Location loc, ValueRange ivs) { 537 // Move body without its terminator. 538 SmallVector<Value> newBlockArgs; 539 newBlockArgs.append(ivs.begin(), ivs.end()); 540 newBlockArgs.append(tiledLoop.inputs().begin(), 541 tiledLoop.inputs().end()); 542 newBlockArgs.append(tiledLoop.outputs().begin(), 543 tiledLoop.outputs().end()); 544 Block *newBody = rewriter.getInsertionBlock(); 545 rewriter.mergeBlocks(tiledLoop.getBody(), newBody, 546 newBlockArgs); 547 rewriter.eraseOp(newBody->getTerminator()); 548 }); 549 rewriter.eraseOp(tiledLoop); 550 return success(); 551 } 552 }; 553 554 /// Local folding pattern for AffineApplyOp that we can apply greedily. 555 /// This replaces AffineApplyOp by the proper value in cases where the 556 /// associated map is trivial. 557 /// A trivial map here is defined as a map with a single result and either: 558 /// 1. Zero operand + returns a single AffineConstantExpr 559 /// 2. One operand + returns a single AffineDimExpr 560 /// 3. One operand + returns a single AffineSymbolExpr 561 // 562 /// In the first case, the AffineApplyOp is replaced by a new constant. In the 563 /// other cases, it is replaced by its unique operand. 564 struct FoldAffineOp : public RewritePattern { 565 FoldAffineOp(MLIRContext *context) 566 : RewritePattern(AffineApplyOp::getOperationName(), 0, context) {} 567 568 LogicalResult matchAndRewrite(Operation *op, 569 PatternRewriter &rewriter) const override { 570 AffineApplyOp affineApplyOp = cast<AffineApplyOp>(op); 571 auto map = affineApplyOp.getAffineMap(); 572 if (map.getNumResults() != 1 || map.getNumInputs() > 1) 573 return failure(); 574 575 AffineExpr expr = map.getResult(0); 576 if (map.getNumInputs() == 0) { 577 if (auto val = expr.dyn_cast<AffineConstantExpr>()) { 578 rewriter.replaceOpWithNewOp<ConstantIndexOp>(op, val.getValue()); 579 return success(); 580 } 581 return failure(); 582 } 583 if (expr.dyn_cast<AffineDimExpr>() || expr.dyn_cast<AffineSymbolExpr>()) { 584 rewriter.replaceOp(op, op->getOperand(0)); 585 return success(); 586 } 587 return failure(); 588 } 589 }; 590 591 template <typename LoopType> 592 static void lowerLinalgToLoopsImpl(FuncOp funcOp) { 593 MLIRContext *context = funcOp.getContext(); 594 RewritePatternSet patterns(context); 595 patterns.add<LinalgRewritePattern<LoopType>>(context); 596 memref::DimOp::getCanonicalizationPatterns(patterns, context); 597 AffineApplyOp::getCanonicalizationPatterns(patterns, context); 598 patterns.add<FoldAffineOp>(context); 599 // Just apply the patterns greedily. 600 (void)applyPatternsAndFoldGreedily(funcOp, std::move(patterns)); 601 } 602 603 struct LowerToAffineLoops 604 : public LinalgLowerToAffineLoopsBase<LowerToAffineLoops> { 605 void getDependentDialects(DialectRegistry ®istry) const override { 606 registry.insert<memref::MemRefDialect>(); 607 } 608 void runOnFunction() override { 609 lowerLinalgToLoopsImpl<AffineForOp>(getFunction()); 610 } 611 }; 612 613 struct LowerToLoops : public LinalgLowerToLoopsBase<LowerToLoops> { 614 void getDependentDialects(DialectRegistry ®istry) const override { 615 registry.insert<memref::MemRefDialect, scf::SCFDialect>(); 616 } 617 void runOnFunction() override { 618 lowerLinalgToLoopsImpl<scf::ForOp>(getFunction()); 619 } 620 }; 621 622 struct LowerToParallelLoops 623 : public LinalgLowerToParallelLoopsBase<LowerToParallelLoops> { 624 void runOnFunction() override { 625 lowerLinalgToLoopsImpl<scf::ParallelOp>(getFunction()); 626 } 627 }; 628 629 struct LowerTiledLoopsToSCF 630 : public LinalgLowerTiledLoopsToSCFBase<LowerTiledLoopsToSCF> { 631 void runOnFunction() override { 632 MLIRContext *context = &getContext(); 633 RewritePatternSet patterns(context); 634 patterns.add<TiledLoopToSCFPattern>(context); 635 (void)applyPatternsAndFoldGreedily(getFunction(), std::move(patterns)); 636 } 637 }; 638 } // namespace 639 640 std::unique_ptr<OperationPass<FuncOp>> 641 mlir::createConvertLinalgTiledLoopsToSCFPass() { 642 return std::make_unique<LowerTiledLoopsToSCF>(); 643 } 644 645 std::unique_ptr<OperationPass<FuncOp>> mlir::createConvertLinalgToLoopsPass() { 646 return std::make_unique<LowerToLoops>(); 647 } 648 649 std::unique_ptr<OperationPass<FuncOp>> 650 mlir::createConvertLinalgToParallelLoopsPass() { 651 return std::make_unique<LowerToParallelLoops>(); 652 } 653 654 std::unique_ptr<OperationPass<FuncOp>> 655 mlir::createConvertLinalgToAffineLoopsPass() { 656 return std::make_unique<LowerToAffineLoops>(); 657 } 658 659 /// Emits a loop nest of `affine.for` with the proper body for `linalgOp`. 660 Optional<LinalgLoops> 661 mlir::linalg::linalgOpToAffineLoops(PatternRewriter &rewriter, 662 LinalgOp linalgOp) { 663 return linalgOpToLoopsImpl<AffineForOp>(rewriter, linalgOp); 664 } 665 666 /// Emits a loop nest of `scf.for` with the proper body for `linalgOp`. 667 Optional<LinalgLoops> mlir::linalg::linalgOpToLoops(PatternRewriter &rewriter, 668 LinalgOp linalgOp) { 669 return linalgOpToLoopsImpl<scf::ForOp>(rewriter, linalgOp); 670 } 671 672 /// Emits a loop nest of `scf.parallel` with the proper body for `linalgOp`. 673 Optional<LinalgLoops> 674 mlir::linalg::linalgOpToParallelLoops(PatternRewriter &rewriter, 675 LinalgOp linalgOp) { 676 return linalgOpToLoopsImpl<scf::ParallelOp>(rewriter, linalgOp); 677 } 678