MLIR 24.0.0git
Loops.cpp
Go to the documentation of this file.
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
10
19#include "mlir/IR/AffineExpr.h"
20#include "mlir/IR/AffineMap.h"
21#include "mlir/IR/IRMapping.h"
22#include "mlir/Support/LLVM.h"
26#include "llvm/ADT/TypeSwitch.h"
27
28namespace mlir {
29#define GEN_PASS_DEF_CONVERTLINALGTOAFFINELOOPSPASS
30#define GEN_PASS_DEF_CONVERTLINALGTOLOOPSPASS
31#define GEN_PASS_DEF_CONVERTLINALGTOPARALLELLOOPSPASS
32#include "mlir/Dialect/Linalg/Passes.h.inc"
33} // namespace mlir
34
35using namespace mlir;
36using namespace mlir::linalg;
37
39 AffineMap map,
40 ArrayRef<Value> vals) {
41 if (map.isEmpty())
42 return {};
43
44 assert(map.getNumInputs() == vals.size());
46 res.reserve(map.getNumResults());
47 auto dims = map.getNumDims();
48 for (auto e : map.getResults()) {
49 auto exprMap = AffineMap::get(dims, map.getNumSymbols(), e);
50 SmallVector<Value> operands(vals);
51 affine::canonicalizeMapAndOperands(&exprMap, &operands);
52 res.push_back(affine::AffineApplyOp::create(b, loc, exprMap, operands));
53 }
54 return res;
55}
56
57template <typename LoadOpTy, typename StoreOpTy, typename OpType>
58static void inlineRegionAndEmitStore(OpBuilder &b, Location loc, OpType op,
59 ArrayRef<Value> indexedValues,
61 ArrayRef<Value> outputBuffers) {
62 auto &block = op->getRegion(0).front();
63 IRMapping map;
64 map.map(block.getArguments(), indexedValues);
65 for (auto &op : block.without_terminator()) {
66 auto *newOp = b.clone(op, map);
67 map.map(op.getResults(), newOp->getResults());
68 }
69
70 Operation *terminator = block.getTerminator();
71 for (OpOperand &operand : terminator->getOpOperands()) {
72 Value toStore = map.lookupOrDefault(operand.get());
73 StoreOpTy::create(b, loc, toStore,
74 outputBuffers[operand.getOperandNumber()],
75 indexing[operand.getOperandNumber()]);
76 }
77}
78
79/// Emits the MLIR for the scalar part of the generic op by:
80/// 1. Emitting load ops for each input and output view in order. This is
81/// achieved by applying the appropriate input or output map to the
82/// enclosing induction variables.
83/// 2. Emitting a call to `op.fun()` that takes as arguments the scalars
84/// from point 1. above.
85/// 3. Emitting store ops to store the results of 2. to the output
86/// views.
87///
88/// An example output may resemble:
89///
90/// ```
91/// scf.for %i = %c0 to %0 step %c1 {
92/// scf.for %j = %c0 to %1 step %c1 {
93/// scf.for %k = %c0 to %4 step %c1 {
94/// %11 = load %arg0[%i, %j] :
95/// memref<?x?xf32, stride_specification>
96/// %12 = load %arg1[%i, %j, %k] :
97/// memref<?x?x?xf32, stride_specification>
98/// %13 = load %arg2[%i, %k, %j] :
99/// memref<?x?x?xf32, stride_specification>
100/// %14:2 = call @foo(%11, %12, %13) : (f32, f32, f32) -> (f32, f32)
101/// store %14#0, %arg1[%i, %j, %k] :
102/// memref<?x?x?Xf32, stride_specification>
103/// store %14#1, %arg2[%i, %k, %j] :
104/// memref<?x?x?Xf32, stride_specification>
105/// }
106/// }
107/// }
108/// ```
109template <typename LoadOpTy, typename StoreOpTy>
111 ArrayRef<Value> allIvs,
112 LinalgOp linalgOp) {
113 assert(linalgOp.hasPureBufferSemantics() &&
114 "expected linalg op with buffer semantics");
115 SmallVector<Value> indexedValues;
116 indexedValues.reserve(linalgOp->getNumOperands());
117
118 auto allIvsPlusDims = SmallVector<Value>(allIvs);
120 // TODO: Avoid the loads if the corresponding argument of the
121 // region has no uses.
122 // 1.a. Emit load from input operand or for scalars access the operand itself.
123 for (OpOperand *inputOperand : linalgOp.getDpsInputOperands()) {
124 if (linalgOp.isScalar(inputOperand)) {
125 indexedValues.push_back(inputOperand->get());
126 continue;
127 }
128 auto indexing = makeCanonicalAffineApplies(
129 b, loc, linalgOp.getMatchingIndexingMap(inputOperand), allIvsPlusDims);
130 indexedValues.push_back(
131 LoadOpTy::create(b, loc, inputOperand->get(), indexing));
132 }
133 // 1.b. Emit load from output views.
134 for (OpOperand &outputOperand : linalgOp.getDpsInitsMutable()) {
136 b, loc, linalgOp.getMatchingIndexingMap(&outputOperand),
137 allIvsPlusDims);
138 indexedValues.push_back(
139 LoadOpTy::create(b, loc, outputOperand.get(), indexing));
141
142 // TODO: When a region inliner exists, use it.
143 // 2. Inline region, currently only works for a single basic block.
144 // 3. Emit store.
146 SmallVector<Value> outputBuffers;
147 for (OpOperand &outputOperand : linalgOp.getDpsInitsMutable()) {
148 if (!isa<MemRefType>(outputOperand.get().getType()))
149 continue;
150 indexing.push_back(makeCanonicalAffineApplies(
151 b, loc, linalgOp.getMatchingIndexingMap(&outputOperand),
152 allIvsPlusDims));
153 outputBuffers.push_back(outputOperand.get());
154 }
155 inlineRegionAndEmitStore<LoadOpTy, StoreOpTy>(b, loc, linalgOp, indexedValues,
156 indexing, outputBuffers);
157}
158
159/// Replace the index operations in the body of the loop nest by the matching
160/// induction variables.
162 LinalgOp linalgOp,
164 // Extract the induction variables of the loop nest from outer to inner.
165 SmallVector<Value> allIvs;
166 for (Operation *loopOp : loopOps) {
168 .Case([&](scf::ParallelOp parallelOp) {
169 allIvs.append(parallelOp.getInductionVars());
170 })
171 .Case([&](scf::ForOp forOp) {
172 allIvs.push_back(forOp.getInductionVar());
173 })
174 .Case([&](affine::AffineForOp affineForOp) {
175 allIvs.push_back(affineForOp.getInductionVar());
176 })
177 .DefaultUnreachable("unexpected op");
178 }
179 assert(linalgOp.getNumLoops() == allIvs.size() &&
180 "expected the number of loops and induction variables to match");
181 // Replace the index operations in the body of the innermost loop op.
182 if (!loopOps.empty()) {
183 auto loopOp = cast<LoopLikeOpInterface>(loopOps.back());
184 for (Region *r : loopOp.getLoopRegions())
185 for (IndexOp indexOp : llvm::make_early_inc_range(r->getOps<IndexOp>()))
186 rewriter.replaceOp(indexOp, allIvs[indexOp.getDim()]);
187 }
188}
189
190template <typename LoopTy>
191static FailureOr<LinalgLoops> linalgOpToLoopsImpl(RewriterBase &rewriter,
192 LinalgOp linalgOp) {
193 using LoadOpTy =
194 std::conditional_t<std::is_same<LoopTy, affine::AffineForOp>::value,
195 affine::AffineLoadOp, memref::LoadOp>;
196 using StoreOpTy =
197 std::conditional_t<std::is_same<LoopTy, affine::AffineForOp>::value,
198 affine::AffineStoreOp, memref::StoreOp>;
200 // The flattened loopToOperandRangesMaps is expected to be an invertible
201 // permutation map (which is asserted in the inverse calculation).
202 assert(linalgOp.hasPureBufferSemantics() &&
203 "expected linalg op with buffer semantics");
204
205 auto loopRanges = linalgOp.createLoopRanges(rewriter, linalgOp.getLoc());
206 auto iteratorTypes = linalgOp.getIteratorTypesArray();
208 SmallVector<Value> allIvs;
210 rewriter, linalgOp.getLoc(), loopRanges, linalgOp, iteratorTypes,
211 [&](OpBuilder &b, Location loc, ValueRange ivs,
212 ValueRange operandValuesToUse) -> scf::ValueVector {
213 assert(operandValuesToUse == linalgOp->getOperands() &&
214 "expect operands are captured and not passed by loop argument");
215 allIvs.append(ivs.begin(), ivs.end());
216 emitScalarImplementation<LoadOpTy, StoreOpTy>(b, loc, allIvs, linalgOp);
217 return scf::ValueVector{};
218 });
219 // Number of loop ops might be different from the number of ivs since some
220 // loops like affine.parallel and scf.parallel have multiple ivs.
222 for (Value iv : allIvs) {
223 if (!iv)
224 return failure();
225 // The induction variable is a block argument of the entry block of the
226 // loop operation.
227 BlockArgument ivVal = dyn_cast<BlockArgument>(iv);
228 if (!ivVal)
229 return failure();
230 loopSet.insert(ivVal.getOwner()->getParentOp());
231 }
232 LinalgLoops loops(loopSet.begin(), loopSet.end());
233 // Replace all index operations in the loop body.
234 replaceIndexOpsByInductionVariables(rewriter, linalgOp, loops);
235 return loops;
236}
237
238namespace {
239template <typename LoopType>
240class LinalgRewritePattern : public RewritePattern {
241public:
242 LinalgRewritePattern(MLIRContext *context)
243 : RewritePattern(MatchAnyOpTypeTag(), /*benefit=*/1, context) {}
244
245 LogicalResult matchAndRewrite(Operation *op,
246 PatternRewriter &rewriter) const override {
247 auto linalgOp = dyn_cast<LinalgOp>(op);
248 if (!isa<LinalgOp>(op) || !linalgOp.hasPureBufferSemantics()) {
249 return rewriter.notifyMatchFailure(
250 op, "expected linalg op with buffer semantics");
251 }
252 if (failed(linalgOpToLoopsImpl<LoopType>(rewriter, linalgOp)))
253 return failure();
254 rewriter.eraseOp(op);
255 return success();
256 }
257};
258
259/// Local folding pattern for AffineApplyOp that we can apply greedily.
260/// This replaces AffineApplyOp by the proper value in cases where the
261/// associated map is trivial.
262/// A trivial map here is defined as a map with a single result and either:
263/// 1. Zero operand + returns a single AffineConstantExpr
264/// 2. One operand + returns a single AffineDimExpr
265/// 3. One operand + returns a single AffineSymbolExpr
267/// In the first case, the AffineApplyOp is replaced by a new constant. In the
268/// other cases, it is replaced by its unique operand.
269struct FoldAffineOp : public RewritePattern {
270 FoldAffineOp(MLIRContext *context)
271 : RewritePattern(affine::AffineApplyOp::getOperationName(), 0, context) {}
273 LogicalResult matchAndRewrite(Operation *op,
274 PatternRewriter &rewriter) const override {
275 auto affineApplyOp = cast<affine::AffineApplyOp>(op);
276 auto map = affineApplyOp.getAffineMap();
277 if (map.getNumResults() != 1 || map.getNumInputs() > 1)
278 return failure();
279
280 AffineExpr expr = map.getResult(0);
281 if (map.getNumInputs() == 0) {
282 if (auto val = dyn_cast<AffineConstantExpr>(expr)) {
283 rewriter.replaceOpWithNewOp<arith::ConstantIndexOp>(op, val.getValue());
284 return success();
285 }
286 return failure();
287 }
288 if (isa<AffineDimExpr, AffineSymbolExpr>(expr)) {
289 rewriter.replaceOp(op, op->getOperand(0));
290 return success();
291 }
292 return failure();
293 }
294};
295
296template <typename LoopType>
297static void lowerLinalgToLoopsImpl(Operation *enclosingOp) {
298 MLIRContext *context = enclosingOp->getContext();
299 RewritePatternSet patterns(context);
300 patterns.add<LinalgRewritePattern<LoopType>>(context);
301 memref::DimOp::getCanonicalizationPatterns(patterns, context);
302 tensor::DimOp::getCanonicalizationPatterns(patterns, context);
303 affine::AffineApplyOp::getCanonicalizationPatterns(patterns, context);
304 patterns.add<FoldAffineOp>(context);
305 // Just apply the patterns greedily.
306 (void)applyPatternsGreedily(enclosingOp, std::move(patterns));
307}
308
309struct LowerToAffineLoops
310 : public impl::ConvertLinalgToAffineLoopsPassBase<LowerToAffineLoops> {
312 LowerToAffineLoops>::ConvertLinalgToAffineLoopsPassBase;
313 void getDependentDialects(DialectRegistry &registry) const override {
314 registry.insert<memref::MemRefDialect>();
315 }
316 void runOnOperation() override {
317 lowerLinalgToLoopsImpl<affine::AffineForOp>(getOperation());
318 }
319};
320
321struct LowerToLoops : public impl::ConvertLinalgToLoopsPassBase<LowerToLoops> {
323 LowerToLoops>::ConvertLinalgToLoopsPassBase;
324 void getDependentDialects(DialectRegistry &registry) const override {
325 registry.insert<memref::MemRefDialect, scf::SCFDialect>();
326 }
327 void runOnOperation() override {
328 lowerLinalgToLoopsImpl<scf::ForOp>(getOperation());
329 }
330};
331
332struct LowerToParallelLoops
333 : public impl::ConvertLinalgToParallelLoopsPassBase<LowerToParallelLoops> {
334 using impl::ConvertLinalgToParallelLoopsPassBase<
335 LowerToParallelLoops>::ConvertLinalgToParallelLoopsPassBase;
336 void runOnOperation() override {
337 lowerLinalgToLoopsImpl<scf::ParallelOp>(getOperation());
338 }
339};
340
341} // namespace
342
343/// Emits a loop nest of `affine.for` with the proper body for `linalgOp`.
344FailureOr<LinalgLoops>
346 return linalgOpToLoopsImpl<affine::AffineForOp>(rewriter, linalgOp);
347}
348
349/// Emits a loop nest of `scf.for` with the proper body for `linalgOp`.
350FailureOr<LinalgLoops> mlir::linalg::linalgOpToLoops(RewriterBase &rewriter,
351 LinalgOp linalgOp) {
352 return linalgOpToLoopsImpl<scf::ForOp>(rewriter, linalgOp);
353}
354
355/// Emits a loop nest of `scf.parallel` with the proper body for `linalgOp`.
356FailureOr<LinalgLoops>
358 LinalgOp linalgOp) {
359 return linalgOpToLoopsImpl<scf::ParallelOp>(rewriter, linalgOp);
360}
return success()
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
static SmallVector< Value > makeCanonicalAffineApplies(OpBuilder &b, Location loc, AffineMap map, ArrayRef< Value > vals)
Definition Loops.cpp:38
static void replaceIndexOpsByInductionVariables(RewriterBase &rewriter, LinalgOp linalgOp, ArrayRef< Operation * > loopOps)
Replace the index operations in the body of the loop nest by the matching induction variables.
Definition Loops.cpp:161
static void inlineRegionAndEmitStore(OpBuilder &b, Location loc, OpType op, ArrayRef< Value > indexedValues, ArrayRef< SmallVector< Value > > indexing, ArrayRef< Value > outputBuffers)
Definition Loops.cpp:58
static FailureOr< LinalgLoops > linalgOpToLoopsImpl(RewriterBase &rewriter, LinalgOp linalgOp)
Definition Loops.cpp:191
static void emitScalarImplementation(OpBuilder &b, Location loc, ArrayRef< Value > allIvs, LinalgOp linalgOp)
Emits the MLIR for the scalar part of the generic op by:
Definition Loops.cpp:110
Base type for affine expression.
Definition AffineExpr.h:68
A multi-dimensional affine map Affine map's are immutable like Type's, and they are uniqued.
Definition AffineMap.h:46
static AffineMap get(MLIRContext *context)
Returns a zero result affine map with no dimensions or symbols: () -> ().
bool isEmpty() const
Returns true if this affine map is an empty map, i.e., () -> ().
unsigned getNumSymbols() const
unsigned getNumDims() const
ArrayRef< AffineExpr > getResults() const
unsigned getNumResults() const
unsigned getNumInputs() const
This class represents an argument of a Block.
Definition Value.h:306
Block * getOwner() const
Returns the block that owns this argument.
Definition Value.h:315
Operation * getParentOp()
Returns the closest surrounding operation that contains this block.
Definition Block.cpp:31
The DialectRegistry maps a dialect namespace to a constructor for the matching dialect.
This is a utility class for mapping one set of IR entities to another.
Definition IRMapping.h:26
auto lookupOrDefault(T from) const
Lookup a mapped value within the map.
Definition IRMapping.h:65
void map(Value from, Value to)
Inserts a new mapping for 'from' to 'to'.
Definition IRMapping.h:30
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:64
This class helps build Operations.
Definition Builders.h:210
This class represents an operand of an operation.
Definition Value.h:254
OpT getOperation()
Return the current operation being transformed.
Definition Pass.h:389
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Value getOperand(unsigned idx)
Definition Operation.h:375
MutableArrayRef< OpOperand > getOpOperands()
Definition Operation.h:408
MLIRContext * getContext()
Return the context this operation is associated with.
Definition Operation.h:233
virtual void runOnOperation()=0
The polymorphic API that runs the pass over the currently held operation.
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Definition Region.h:26
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
RewritePattern is the common base class for all DAG to DAG replacements.
This class coordinates the application of a rewrite on a set of IR, providing a way for clients to tr...
virtual void replaceOp(Operation *op, ValueRange newValues)
Replace the results of the given (original) operation with the specified list of values (replacements...
virtual void eraseOp(Operation *op)
This method erases an operation that is known to have no uses.
std::enable_if_t<!std::is_convertible< CallbackT, Twine >::value, LogicalResult > notifyMatchFailure(Location loc, CallbackT &&reasonCallback)
Used to notify the listener that the IR failed to be rewritten because of a match failure,...
OpTy replaceOpWithNewOp(Operation *op, Args &&...args)
Replace the results of the given (original) op with a new op that is created without verification (re...
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Specialization of arith.constant op that returns an integer of index type.
Definition Arith.h:93
void canonicalizeMapAndOperands(AffineMap *map, SmallVectorImpl< Value > *operands)
Modifies both map and operands in-place so as to:
SmallVector< Operation *, 4 > LinalgLoops
Definition Transforms.h:517
FailureOr< LinalgLoops > linalgOpToLoops(RewriterBase &rewriter, LinalgOp linalgOp)
Emit a loop nest of scf.for with the proper body for linalgOp.
Definition Loops.cpp:350
FailureOr< LinalgLoops > linalgOpToAffineLoops(RewriterBase &rewriter, LinalgOp linalgOp)
Emit a loop nest of affine.for with the proper body for linalgOp.
Definition Loops.cpp:345
FailureOr< LinalgLoops > linalgOpToParallelLoops(RewriterBase &rewriter, LinalgOp linalgOp)
Emit a loop nest of scf.parallel with the proper body for linalgOp.
Definition Loops.cpp:357
SmallVector< Value > ValueVector
An owning vector of values, handy to return from functions.
Definition SCF.h:66
Include the generated interface declarations.
LogicalResult applyPatternsGreedily(Region &region, const FrozenRewritePatternSet &patterns, GreedyRewriteConfig config=GreedyRewriteConfig(), bool *changed=nullptr)
Rewrite ops in the given region, which must be isolated from above, by repeatedly applying the highes...
llvm::SetVector< T, Vector, Set, N > SetVector
Definition LLVM.h:128
static void doit(OpBuilder &b, Location loc, ArrayRef< Range > loopRanges, LinalgOp linalgOp, ArrayRef< utils::IteratorType > iteratorTypes, function_ref< scf::ValueVector(OpBuilder &, Location, ValueRange, ValueRange)> bodyBuilderFn, ArrayRef< linalg::ProcInfo > procInfo={})