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 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, map.getNumSymbols(), 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 auto attr = linalgOp.template getAttrOfType<IntegerAttr>("symbol_source"); 169 auto allIvsPlusDims = SmallVector<Value, 4>(allIvs.begin(), allIvs.end()); 170 if (attr) { 171 auto operand = linalgOp.getOperand(attr.getInt()); 172 auto shapedType = operand.getType().template cast<ShapedType>(); 173 allIvsPlusDims.reserve(allIvs.size() + shapedType.getRank()); 174 for (unsigned idx = 0, e = shapedType.getRank(); idx < e; ++idx) 175 allIvsPlusDims.push_back(b.create<DimOp>(loc, operand, idx)); 176 } 177 178 // TODO: Avoid the loads if the corresponding argument of the 179 // region has no uses. 180 // 1.a. Emit load from input views. 181 for (unsigned i = 0; i < nInputs; ++i) { 182 auto indexing = makeCanonicalAffineApplies( 183 b, loc, linalgOp.getInputIndexingMap(i), allIvsPlusDims); 184 // Passing through IndexedValueType emits the proper load operation. 185 indexedValues.push_back(IndexedValueType(linalgOp.getInput(i))(indexing)); 186 } 187 // 1.b. Emit load from output views. 188 for (unsigned i = 0; i < nOutputs; ++i) { 189 auto indexing = makeCanonicalAffineApplies( 190 b, loc, linalgOp.getOutputIndexingMap(i), allIvsPlusDims); 191 // Passing through IndexedValueType emits the proper load operation. 192 indexedValues.push_back( 193 IndexedValueType(linalgOp.getOutputBuffer(i))(indexing)); 194 } 195 196 // TODO: When a region inliner exists, use it. 197 // 2. Inline region, currently only works for a single basic block. 198 // 3. Emit store. 199 SmallVector<SmallVector<Value, 8>, 8> indexing; 200 SmallVector<Value, 8> outputBuffers; 201 for (unsigned i = 0; i < nOutputs; ++i) { 202 indexing.push_back(makeCanonicalAffineApplies( 203 b, loc, linalgOp.getOutputIndexingMap(i), allIvsPlusDims)); 204 outputBuffers.push_back(linalgOp.getOutputBuffer(i)); 205 } 206 inlineRegionAndEmitStore<IndexedValueType>(linalgOp, indexedValues, indexing, 207 outputBuffers); 208 } 209 210 template <typename IndexedValueType> 211 void emitScalarImplementation(ArrayRef<Value> allIvs, CopyOp copyOp) { 212 assert(copyOp.hasBufferSemantics() && 213 "expected linalg op with buffer semantics"); 214 auto nPar = copyOp.getNumParallelLoops(); 215 assert(nPar == allIvs.size()); 216 auto inputIvs = 217 permuteIvs(allIvs.take_front(nPar), copyOp.inputPermutation()); 218 auto outputIvs = 219 permuteIvs(allIvs.take_front(nPar), copyOp.outputPermutation()); 220 SmallVector<Value, 8> iivs(inputIvs.begin(), inputIvs.end()); 221 SmallVector<Value, 8> oivs(outputIvs.begin(), outputIvs.end()); 222 IndexedValueType O(copyOp.getOutputBuffer(0)), I(copyOp.getInput(0)); 223 // Emit the proper scalar assignment, whether we are dealing with a 0-D or 224 // an n-D loop nest; with or without permutations. 225 // clang-format off 226 nPar > 0 ? O(oivs) = I(iivs) : 227 O() = I(); 228 // clang-format on 229 } 230 231 template <typename IndexedValueType> 232 void emitScalarImplementation(ArrayRef<Value> allIvs, FillOp fillOp) { 233 assert(fillOp.hasBufferSemantics() && 234 "expected linalg op with buffer semantics"); 235 auto nPar = fillOp.getNumParallelLoops(); 236 assert(nPar == allIvs.size()); 237 auto ivs = SmallVector<Value, 4>(allIvs.begin(), allIvs.begin() + nPar); 238 IndexedValueType O(fillOp.getOutputBuffer(0)); 239 // Emit the proper scalar assignment, whether we are dealing with a 0-D or 240 // an n-D loop nest; with or without permutations. 241 nPar > 0 ? O(ivs) = fillOp.value() : O() = fillOp.value(); 242 } 243 244 template <typename IndexedValueType> 245 void emitScalarImplementation(ArrayRef<Value> allIvs, DotOp dotOp) { 246 assert(dotOp.hasBufferSemantics() && 247 "expected linalg op with buffer semantics"); 248 assert(allIvs.size() == 1); 249 Value r_i(allIvs[0]); 250 IndexedValueType A(dotOp.getInput(0)), B(dotOp.getInput(1)), 251 C(dotOp.getOutputBuffer(0)); 252 // Emit scalar form. 253 C() = C() + A(r_i) * B(r_i); 254 } 255 256 template <typename IndexedValueType> 257 Value getConvOpInput(ConvOp convOp, StdIndexedValue im, 258 MutableArrayRef<Value> imIdx) { 259 // TODO: add a level of indirection to linalg.generic. 260 if (!convOp.padding()) 261 return im(imIdx); 262 263 auto *context = ScopedContext::getContext(); 264 Value zeroIndex = std_constant_index(0); 265 SmallVector<Value, 8> conds; 266 SmallVector<Value, 8> clampedImIdx; 267 for (auto iter : llvm::enumerate(imIdx)) { 268 int idx = iter.index(); 269 auto dim = iter.value(); 270 // Only need to iterate over the window dimensions. 271 if (idx == 0 || idx == static_cast<int>(imIdx.size()) - 1) { 272 clampedImIdx.push_back(dim); 273 continue; 274 } 275 276 using edsc::op::sge; 277 using edsc::op::slt; 278 using edsc::op::operator||; 279 Value leftOutOfBound = slt(dim, zeroIndex); 280 if (conds.empty()) 281 conds.push_back(leftOutOfBound); 282 else 283 conds.push_back(conds.back() || leftOutOfBound); 284 Value rightBound = std_dim(convOp.input(), idx); 285 conds.push_back(conds.back() || (sge(dim, rightBound))); 286 287 // When padding is involved, the indices will only be shifted to negative, 288 // so having a max op is enough. 289 auto maxMap = AffineMap::get(/*dimCount=*/1, 0, 290 {getAffineDimExpr(/*position=*/0, context), 291 getAffineConstantExpr(0, context)}, 292 context); 293 clampedImIdx.push_back(affine_max(dim.getType(), maxMap, ValueRange{dim})); 294 } 295 296 auto &b = ScopedContext::getBuilderRef(); 297 Type type = convOp.input().getType().cast<MemRefType>().getElementType(); 298 Value zero = std_constant(type, b.getZeroAttr(type)); 299 Value readInput = im(clampedImIdx); 300 return conds.empty() ? readInput 301 : (Value)std_select(conds.back(), zero, readInput); 302 } 303 304 /// Returns true is `convOp` has a non-zero padding. 305 static bool hasPadding(ConvOp convOp) { 306 for (unsigned i = 0, e = convOp.getNumSpatialDimensions(); i < e; ++i) { 307 if (convOp.getLowPad(i) > 0 || convOp.getHighPad(i) > 0) 308 return true; 309 } 310 return false; 311 } 312 313 template <typename IndexedValueType> 314 static void emitScalarImplementation(ArrayRef<Value> allIvs, ConvOp convOp) { 315 assert(convOp.hasBufferSemantics() && 316 "expected linalg op with buffer semantics"); 317 auto &b = ScopedContext::getBuilderRef(); 318 auto loc = ScopedContext::getLocation(); 319 auto mapsRange = convOp.indexing_maps().getAsRange<AffineMapAttr>(); 320 auto maps = llvm::to_vector<8>( 321 llvm::map_range(mapsRange, [](AffineMapAttr a) { return a.getValue(); })); 322 SmallVector<Value, 8> fIdx( 323 makeCanonicalAffineApplies(b, loc, maps[0], allIvs)); 324 SmallVector<Value, 8> imIdx( 325 makeCanonicalAffineApplies(b, loc, maps[1], allIvs)); 326 SmallVector<Value, 8> oIdx( 327 makeCanonicalAffineApplies(b, loc, maps[2], allIvs)); 328 329 IndexedValueType F(convOp.filter()), O(convOp.output()); 330 331 // Emit scalar form. Padded conv involves an affine.max in the memory access 332 // which is not allowed by affine.load. Override to use an StdIndexedValue 333 // when there is non-zero padding. 334 if (hasPadding(convOp)) { 335 StdIndexedValue I(convOp.input()); 336 Value paddedInput = getConvOpInput<IndexedValueType>(convOp, I, imIdx); 337 O(oIdx) += F(fIdx) * paddedInput; 338 } else { 339 IndexedValueType I(convOp.input()); 340 O(oIdx) += F(fIdx) * I(imIdx); 341 } 342 } 343 344 template <typename IndexedValueType> 345 void emitScalarImplementation(ArrayRef<Value> allIvs, PoolingMaxOp op) { 346 InputAndOutputIndices indices = getInputAndOutputIndices(allIvs, op); 347 // Emit scalar form. 348 IndexedValueType output(op.output()); 349 IndexedValueType input(op.input()); 350 Value lhs = output(indices.outputs); 351 Value rhs = input(indices.inputs); 352 using edsc::op::sgt; 353 Value maxValue = std_select(sgt(lhs, rhs), lhs, rhs); 354 output(indices.outputs) = maxValue; 355 } 356 357 template <typename IndexedValueType> 358 void emitScalarImplementation(ArrayRef<Value> allIvs, PoolingMinOp op) { 359 InputAndOutputIndices indices = getInputAndOutputIndices(allIvs, op); 360 // Emit scalar form. 361 IndexedValueType output(op.output()); 362 IndexedValueType input(op.input()); 363 Value lhs = output(indices.outputs); 364 Value rhs = input(indices.inputs); 365 using edsc::op::slt; 366 Value minValue = std_select(slt(lhs, rhs), lhs, rhs); 367 output(indices.outputs) = minValue; 368 } 369 template <typename IndexedValueType> 370 void emitScalarImplementation(ArrayRef<Value> allIvs, PoolingSumOp op) { 371 auto indices = getInputAndOutputIndices(allIvs, op); 372 IndexedValueType input(op.input()), output(op.output()); 373 374 // Emit scalar form. 375 output(indices.outputs) += input(indices.inputs); 376 } 377 /// Emits the MLIR for the scalar part of the indexed generic op by: 378 /// 1. Emitting load ops for each input and output view in order. This is 379 /// achieved by applying the appropriate input or output map to the 380 /// enclosing induction variables. 381 /// 2. Emitting a call to `op.fun()` that takes as arguments the induction 382 /// variables and the scalars from point 1. above. 383 /// 3. Emitting store ops to store the results of 2. to the output views. 384 /// 385 /// An example output may resemble: 386 /// 387 /// ``` 388 /// scf.for %i = %c0 to %0 step %c1 { 389 /// scf.for %j = %c0 to %1 step %c1 { 390 /// scf.for %k = %c0 to %4 step %c1 { 391 /// %11 = load %arg0[%i, %j] : 392 /// memref<?x?xf32, stride_specification> 393 /// %12 = load %arg1[%i, %j, %k] : 394 /// memref<?x?x?xf32, stride_specification> 395 /// %13 = load %arg2[%i, %k, %j] : 396 /// memref<?x?x?xf32, stride_specification> 397 /// %14:2 = call @foo(%i, %j, %k, %11, %12, %13) : 398 /// (index, index, index, f32, f32, f32) -> (f32, f32) 399 /// store %14#0, %arg1[%i, %j, %k] : 400 /// memref<?x?x?Xf32, stride_specification> 401 /// store %14#1, %arg2[%i, %k, %j] : 402 /// memref<?x?x?Xf32, stride_specification> 403 /// } 404 /// } 405 /// } 406 /// ``` 407 template <typename IndexedValueType> 408 static void emitScalarImplementation(ArrayRef<Value> allIvs, 409 IndexedGenericOp indexedGenericOp) { 410 assert(indexedGenericOp.hasBufferSemantics() && 411 "expected linalg op with buffer semantics"); 412 auto &b = ScopedContext::getBuilderRef(); 413 auto loc = ScopedContext::getLocation(); 414 unsigned nInputs = indexedGenericOp.getNumInputs(); 415 unsigned nOutputs = indexedGenericOp.getNumOutputs(); 416 unsigned nLoops = allIvs.size(); 417 SmallVector<Value, 4> indexedValues; 418 indexedValues.reserve(nLoops + nInputs + nOutputs); 419 for (unsigned i = 0; i < nLoops; ++i) 420 indexedValues.push_back(allIvs[i]); 421 422 // TODO: Avoid the loads if the corresponding argument of the 423 // region has no uses. 424 // 1.a. Emit load from input views. 425 for (unsigned i = 0; i < nInputs; ++i) { 426 auto indexing = makeCanonicalAffineApplies( 427 b, loc, indexedGenericOp.getInputIndexingMap(i), allIvs); 428 // Pass input i through IndexedValueType emits the proper load operation. 429 indexedValues.push_back( 430 IndexedValueType(indexedGenericOp.getInput(i))(indexing)); 431 } 432 // 1.b. Emit load from output views. 433 for (unsigned i = 0; i < nOutputs; ++i) { 434 auto indexing = makeCanonicalAffineApplies( 435 b, loc, indexedGenericOp.getOutputIndexingMap(i), allIvs); 436 // Pass output i through IndexedValueType emits the proper load operation. 437 indexedValues.push_back( 438 IndexedValueType(indexedGenericOp.getOutputBuffer(i))(indexing)); 439 } 440 441 // TODO: When a region inliner exists, use it. 442 // 2. Inline region, currently only works for a single basic block. 443 // 3. Emit store. 444 SmallVector<SmallVector<Value, 8>, 8> indexing; 445 SmallVector<Value, 8> outputBuffers; 446 for (unsigned i = 0; i < nOutputs; ++i) { 447 indexing.push_back(makeCanonicalAffineApplies( 448 b, loc, indexedGenericOp.getOutputIndexingMap(i), allIvs)); 449 outputBuffers.push_back(indexedGenericOp.getOutputBuffer(i)); 450 } 451 inlineRegionAndEmitStore<IndexedValueType>(indexedGenericOp, indexedValues, 452 indexing, outputBuffers); 453 } 454 455 template <typename LoopTy, typename ConcreteOpTy> 456 Optional<LinalgLoops> linalgOpToLoopsImpl(Operation *op, OpBuilder &builder) { 457 using IndexedValueTy = typename GenerateLoopNest<LoopTy>::IndexedValueTy; 458 459 ScopedContext scope(builder, op->getLoc()); 460 461 // The flattened loopToOperandRangesMaps is expected to be an invertible 462 // permutation map (which is asserted in the inverse calculation). 463 auto linalgOp = cast<ConcreteOpTy>(op); 464 assert(linalgOp.hasBufferSemantics() && 465 "expected linalg op with buffer semantics"); 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 SmallVector<Value, 8> sizes = getViewSizes(builder, linalgOp); 471 AffineMap map = concatAffineMaps(maps); 472 if (map.getNumSymbols()) { 473 // Ignore symbols for now as they are not supported by inversePermutation. 474 unsigned dims = map.getNumDims(); 475 SmallVector<AffineExpr, 8> zeros( 476 map.getNumSymbols(), getAffineConstantExpr(0, map.getContext())); 477 SmallVector<AffineExpr, 8> res; 478 for (auto result : map.getResults()) 479 res.push_back(result.replaceDimsAndSymbols({}, zeros)); 480 481 map = AffineMap::get(dims, 0, res, map.getContext()); 482 483 // Cut off values that would have been applied to symbols 484 sizes.resize(res.size()); 485 } 486 487 AffineMap invertedMap = inversePermutation(map); 488 if (!invertedMap) 489 return {}; 490 if (invertedMap.isEmpty()) { 491 emitScalarImplementation<IndexedValueTy>({}, linalgOp); 492 return LinalgLoops(); 493 } 494 495 SmallVector<Value, 4> allIvs; 496 auto loopRanges = emitLoopRanges(scope.getBuilderRef(), scope.getLocation(), 497 invertedMap, sizes); 498 GenerateLoopNest<LoopTy>::doit( 499 loopRanges, linalgOp.iterator_types().getValue(), [&](ValueRange ivs) { 500 allIvs.append(ivs.begin(), ivs.end()); 501 emitScalarImplementation<IndexedValueTy>(allIvs, linalgOp); 502 }); 503 // Number of loop ops might be different from the number of ivs since some 504 // loops like affine.parallel and scf.parallel have multiple ivs. 505 llvm::SetVector<Operation *> loopSet; 506 for (Value iv : allIvs) { 507 if (!iv) 508 return {}; 509 // The induction variable is a block argument of the entry block of the 510 // loop operation. 511 BlockArgument ivVal = iv.dyn_cast<BlockArgument>(); 512 if (!ivVal) 513 return {}; 514 loopSet.insert(ivVal.getOwner()->getParentOp()); 515 } 516 LinalgLoops loops(loopSet.begin(), loopSet.end()); 517 return loops; 518 } 519 520 template <typename LoopType, typename ConcreteOp> 521 class LinalgRewritePattern : public RewritePattern { 522 public: 523 explicit LinalgRewritePattern(MLIRContext *context) 524 : RewritePattern(ConcreteOp::getOperationName(), 1, context) {} 525 526 LogicalResult matchAndRewrite(Operation *op, 527 PatternRewriter &rewriter) const override { 528 if (!linalgOpToLoopsImpl<LoopType, ConcreteOp>(op, rewriter)) 529 return failure(); 530 rewriter.eraseOp(op); 531 return success(); 532 } 533 }; 534 535 template <typename LoopType, typename ConcreteOp> 536 void insertOnePattern(OwningRewritePatternList &patterns, MLIRContext *ctx) { 537 patterns.insert<LinalgRewritePattern<LoopType, ConcreteOp>>(ctx); 538 } 539 540 template <typename LoopType, typename... Args> 541 void insertPatterns(OwningRewritePatternList &patterns, MLIRContext *ctx) { 542 (void)std::initializer_list<int>{ 543 0, (insertOnePattern<LoopType, Args>(patterns, ctx), 0)...}; 544 } 545 546 /// Local folding pattern for AffineApplyOp that we can apply greedily. 547 /// This replaces AffineApplyOp by the proper value in cases where the 548 /// associated map is trivial. 549 /// A trivial map here is defined as a map with a single result and either: 550 /// 1. Zero operand + returns a single AffineConstantExpr 551 /// 2. One operand + returns a single AffineDimExpr 552 /// 3. One operand + returns a single AffineSymbolExpr 553 // 554 /// In the first case, the AffineApplyOp is replaced by a new constant. In the 555 /// other cases, it is replaced by its unique operand. 556 struct FoldAffineOp : public RewritePattern { 557 FoldAffineOp(MLIRContext *context) 558 : RewritePattern(AffineApplyOp::getOperationName(), 0, context) {} 559 560 LogicalResult matchAndRewrite(Operation *op, 561 PatternRewriter &rewriter) const override { 562 AffineApplyOp affineApplyOp = cast<AffineApplyOp>(op); 563 auto map = affineApplyOp.getAffineMap(); 564 if (map.getNumResults() != 1 || map.getNumInputs() > 1) 565 return failure(); 566 567 AffineExpr expr = map.getResult(0); 568 if (map.getNumInputs() == 0) { 569 if (auto val = expr.dyn_cast<AffineConstantExpr>()) { 570 rewriter.replaceOpWithNewOp<ConstantIndexOp>(op, val.getValue()); 571 return success(); 572 } 573 return failure(); 574 } 575 if (expr.dyn_cast<AffineDimExpr>() || expr.dyn_cast<AffineSymbolExpr>()) { 576 rewriter.replaceOp(op, op->getOperand(0)); 577 return success(); 578 } 579 return failure(); 580 } 581 }; 582 } // namespace 583 584 template <typename LoopType> 585 static void lowerLinalgToLoopsImpl(FuncOp funcOp, MLIRContext *context) { 586 OwningRewritePatternList patterns; 587 // Canonicalization and folding patterns applied greedily allow cleaning up 588 // the emitted IR on the fly. 589 // TODO: fold view and subview ops? 590 insertPatterns<LoopType, 591 #define GET_OP_LIST 592 #include "mlir/Dialect/Linalg/IR/LinalgStructuredOps.cpp.inc" 593 >(patterns, context); 594 595 DimOp::getCanonicalizationPatterns(patterns, context); 596 AffineApplyOp::getCanonicalizationPatterns(patterns, context); 597 patterns.insert<FoldAffineOp>(context); 598 // Just apply the patterns greedily. 599 applyPatternsAndFoldGreedily(funcOp, patterns); 600 } 601 602 namespace { 603 struct LowerToAffineLoops 604 : public LinalgLowerToAffineLoopsBase<LowerToAffineLoops> { 605 void runOnFunction() override { 606 lowerLinalgToLoopsImpl<AffineForOp>(getFunction(), &getContext()); 607 } 608 }; 609 struct LowerToLoops : public LinalgLowerToLoopsBase<LowerToLoops> { 610 void runOnFunction() override { 611 lowerLinalgToLoopsImpl<scf::ForOp>(getFunction(), &getContext()); 612 } 613 }; 614 struct LowerToParallelLoops 615 : public LinalgLowerToParallelLoopsBase<LowerToParallelLoops> { 616 void runOnFunction() override { 617 lowerLinalgToLoopsImpl<scf::ParallelOp>(getFunction(), &getContext()); 618 } 619 }; 620 } // namespace 621 622 std::unique_ptr<OperationPass<FuncOp>> mlir::createConvertLinalgToLoopsPass() { 623 return std::make_unique<LowerToLoops>(); 624 } 625 626 std::unique_ptr<OperationPass<FuncOp>> 627 mlir::createConvertLinalgToParallelLoopsPass() { 628 return std::make_unique<LowerToParallelLoops>(); 629 } 630 631 std::unique_ptr<OperationPass<FuncOp>> 632 mlir::createConvertLinalgToAffineLoopsPass() { 633 return std::make_unique<LowerToAffineLoops>(); 634 } 635 636 // TODO: gradually remove this layer as more ops become "named". 637 template <typename LoopTy> 638 static Optional<LinalgLoops> linalgOpToLoopsImplSwitch(Operation *op, 639 OpBuilder &builder) { 640 assert(isa<LinalgOp>(op) && "LinalgOp expected"); 641 if (isa<CopyOp>(op)) 642 return linalgOpToLoopsImpl<LoopTy, CopyOp>(op, builder); 643 if (isa<FillOp>(op)) 644 return linalgOpToLoopsImpl<LoopTy, FillOp>(op, builder); 645 if (isa<DotOp>(op)) 646 return linalgOpToLoopsImpl<LoopTy, DotOp>(op, builder); 647 if (isa<ConvOp>(op)) 648 return linalgOpToLoopsImpl<LoopTy, ConvOp>(op, builder); 649 if (isa<PoolingMaxOp>(op)) 650 return linalgOpToLoopsImpl<LoopTy, PoolingMaxOp>(op, builder); 651 if (isa<PoolingMinOp>(op)) 652 return linalgOpToLoopsImpl<LoopTy, PoolingMinOp>(op, builder); 653 if (isa<PoolingSumOp>(op)) 654 return linalgOpToLoopsImpl<LoopTy, PoolingSumOp>(op, builder); 655 if (isa<IndexedGenericOp>(op)) 656 return linalgOpToLoopsImpl<LoopTy, IndexedGenericOp>(op, builder); 657 658 // TODO: Cases below are generic and need a LinalgStructuredOpInterface. 659 if (isa<GenericOp>(op)) 660 return linalgOpToLoopsImpl<LoopTy, GenericOp>(op, builder); 661 if (isa<MatmulOp>(op)) 662 return linalgOpToLoopsImpl<LoopTy, MatmulOp>(op, builder); 663 if (isa<MatvecOp>(op)) 664 return linalgOpToLoopsImpl<LoopTy, MatvecOp>(op, builder); 665 if (isa<BatchMatmulOp>(op)) 666 return linalgOpToLoopsImpl<LoopTy, BatchMatmulOp>(op, builder); 667 llvm_unreachable("Unexpected op in linalgOpToLoopsImpl"); 668 } 669 670 /// Emits a loop nest with the proper body for `op`. 671 template <typename LoopTy> 672 Optional<LinalgLoops> mlir::linalg::linalgLowerOpToLoops(OpBuilder &builder, 673 Operation *op) { 674 return linalgOpToLoopsImplSwitch<LoopTy>(op, builder); 675 } 676 677 template Optional<LinalgLoops> 678 mlir::linalg::linalgLowerOpToLoops<AffineForOp>(OpBuilder &builder, 679 Operation *op); 680 template Optional<LinalgLoops> 681 mlir::linalg::linalgLowerOpToLoops<scf::ForOp>(OpBuilder &builder, 682 Operation *op); 683 template Optional<LinalgLoops> 684 mlir::linalg::linalgLowerOpToLoops<scf::ParallelOp>(OpBuilder &builder, 685 Operation *op); 686 687 /// Emits a loop nest of `affine.for` with the proper body for `op`. 688 LogicalResult mlir::linalg::linalgOpToAffineLoops(OpBuilder &builder, 689 Operation *op) { 690 Optional<LinalgLoops> loops = linalgLowerOpToLoops<AffineForOp>(builder, op); 691 return loops ? success() : failure(); 692 } 693 694 /// Emits a loop nest of `scf.for` with the proper body for `op`. 695 LogicalResult mlir::linalg::linalgOpToLoops(OpBuilder &builder, Operation *op) { 696 Optional<LinalgLoops> loops = linalgLowerOpToLoops<scf::ForOp>(builder, op); 697 return loops ? success() : failure(); 698 } 699 700 /// Emits a loop nest of `scf.parallel` with the proper body for `op`. 701 LogicalResult mlir::linalg::linalgOpToParallelLoops(OpBuilder &builder, 702 Operation *op) { 703 Optional<LinalgLoops> loops = 704 linalgLowerOpToLoops<scf::ParallelOp>(builder, op); 705 return loops ? success() : failure(); 706 } 707