MLIR 24.0.0git
LoopSpecialization.cpp
Go to the documentation of this file.
1//===- LoopSpecialization.cpp - scf.parallel/SCR.for specialization -------===//
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// Specializes parallel loops and for loops for easier unrolling and
10// vectorization.
11//
12//===----------------------------------------------------------------------===//
13
15
23#include "mlir/IR/AffineExpr.h"
24#include "mlir/IR/IRMapping.h"
27
28namespace mlir {
29#define GEN_PASS_DEF_SCFFORLOOPPEELING
30#define GEN_PASS_DEF_SCFFORLOOPSPECIALIZATION
31#define GEN_PASS_DEF_SCFPARALLELLOOPSPECIALIZATION
32#include "mlir/Dialect/SCF/Transforms/Passes.h.inc"
33} // namespace mlir
34
35using namespace mlir;
36using namespace mlir::affine;
37using scf::ForOp;
38using scf::ParallelOp;
39
40/// Rewrite a parallel loop with bounds defined by an affine.min with a constant
41/// into 2 loops after checking if the bounds are equal to that constant. This
42/// is beneficial if the loop will almost always have the constant bound and
43/// that version can be fully unrolled and vectorized.
44static void specializeParallelLoopForUnrolling(ParallelOp op) {
45 SmallVector<int64_t, 2> constantIndices;
46 constantIndices.reserve(op.getUpperBound().size());
47 for (auto bound : op.getUpperBound()) {
48 auto minOp = bound.getDefiningOp<AffineMinOp>();
49 if (!minOp)
50 return;
51 int64_t minConstant = std::numeric_limits<int64_t>::max();
52 for (AffineExpr expr : minOp.getMap().getResults()) {
53 if (auto constantIndex = dyn_cast<AffineConstantExpr>(expr))
54 minConstant = std::min(minConstant, constantIndex.getValue());
55 }
56 if (minConstant == std::numeric_limits<int64_t>::max())
57 return;
58 constantIndices.push_back(minConstant);
59 }
60
61 OpBuilder b(op);
62 IRMapping map;
63 Value cond;
64 for (auto bound : llvm::zip(op.getUpperBound(), constantIndices)) {
65 Value constant =
66 arith::ConstantIndexOp::create(b, op.getLoc(), std::get<1>(bound));
67 Value cmp = arith::CmpIOp::create(b, op.getLoc(), arith::CmpIPredicate::eq,
68 std::get<0>(bound), constant);
69 cond = cond ? arith::AndIOp::create(b, op.getLoc(), cond, cmp) : cmp;
70 map.map(std::get<0>(bound), constant);
71 }
72 auto ifOp = scf::IfOp::create(b, op.getLoc(), cond, /*withElseRegion=*/true);
73 ifOp.getThenBodyBuilder().clone(*op.getOperation(), map);
74 ifOp.getElseBodyBuilder().clone(*op.getOperation());
75 op.erase();
76}
77
78/// Rewrite a for loop with bounds defined by an affine.min with a constant into
79/// 2 loops after checking if the bounds are equal to that constant. This is
80/// beneficial if the loop will almost always have the constant bound and that
81/// version can be fully unrolled and vectorized.
82static void specializeForLoopForUnrolling(ForOp op) {
83 auto bound = op.getUpperBound();
84 auto minOp = bound.getDefiningOp<AffineMinOp>();
85 if (!minOp)
86 return;
87 int64_t minConstant = std::numeric_limits<int64_t>::max();
88 for (AffineExpr expr : minOp.getMap().getResults()) {
89 if (auto constantIndex = dyn_cast<AffineConstantExpr>(expr))
90 minConstant = std::min(minConstant, constantIndex.getValue());
91 }
92 if (minConstant == std::numeric_limits<int64_t>::max())
93 return;
94
95 OpBuilder b(op);
96 IRMapping map;
97 Value constant = arith::ConstantOp::create(
98 b, op.getLoc(),
99 IntegerAttr::get(op.getUpperBound().getType(), minConstant));
100 Value cond = arith::CmpIOp::create(b, op.getLoc(), arith::CmpIPredicate::eq,
101 bound, constant);
102 map.map(bound, constant);
103 auto ifOp = scf::IfOp::create(b, op.getLoc(), cond, /*withElseRegion=*/true);
104 ifOp.getThenBodyBuilder().clone(*op.getOperation(), map);
105 ifOp.getElseBodyBuilder().clone(*op.getOperation());
106 op.erase();
107}
108
109/// Rewrite a for loop with bounds/step that potentially do not divide evenly
110/// into a for loop where the step divides the iteration space evenly, followed
111/// by an scf.if for the last (partial) iteration (if any).
112///
113/// This function rewrites the given scf.for loop in-place and creates a new
114/// scf.if operation for the last iteration. It replaces all uses of the
115/// unpeeled loop with the results of the newly generated scf.if.
116///
117/// The newly generated scf.if operation is returned via `ifOp`. The boundary
118/// at which the loop is split (new upper bound) is returned via `splitBound`.
119/// The return value indicates whether the loop was rewritten or not.
120///
121/// Note: Loops with a step size of 0 cannot be peeled. Applying this function
122/// to such a loop may result in IR with undefined behavior.
123static LogicalResult peelForLoop(RewriterBase &b, ForOp forOp,
124 ForOp &partialIteration, Value &splitBound) {
126 auto lbInt = getConstantIntValue(forOp.getLowerBound());
127 auto ubInt = getConstantIntValue(forOp.getUpperBound());
128 auto stepInt = getConstantIntValue(forOp.getStep());
130 // No specialization necessary if step size is 1. Also bail out in case of an
131 // invalid zero or negative step which might have happened during folding.
132 if (stepInt && *stepInt <= 1)
133 return failure();
134
135 // No specialization necessary if step already divides upper bound evenly.
136 // Fast path: lb, ub and step are constants.
137 if (lbInt && ubInt && stepInt && (*ubInt - *lbInt) % *stepInt == 0)
138 return failure();
139
140 // Only the dynamic path computes the peeling bound with affine.apply, which
141 // accepts only index operands.
142 if ((!lbInt || !ubInt || !stepInt) &&
143 !forOp.getInductionVar().getType().isIndex())
144 return failure();
146 // Slow path: Examine the ops that define lb, ub and step.
147 AffineExpr sym0, sym1, sym2;
148 bindSymbols(b.getContext(), sym0, sym1, sym2);
149 SmallVector<Value> operands{forOp.getLowerBound(), forOp.getUpperBound(),
150 forOp.getStep()};
151 AffineMap map = AffineMap::get(0, 3, {(sym1 - sym0) % sym2});
153 if (auto constExpr = dyn_cast<AffineConstantExpr>(map.getResult(0)))
154 if (constExpr.getValue() == 0)
155 return failure();
156
157 // New upper bound: max(%lb, %ub - (%ub - %lb) mod %step). Affine `mod` is
158 // non-negative, so without the clamp an empty loop (%lb >= %ub) gets a split
159 // bound below %lb and its partial iteration executes.
160 auto modMap = AffineMap::get(0, 3, {sym1 - ((sym1 - sym0) % sym2), sym0},
161 b.getContext());
162 b.setInsertionPoint(forOp);
163 auto loc = forOp.getLoc();
164 splitBound = b.createOrFold<AffineMaxOp>(loc, modMap,
165 ValueRange{forOp.getLowerBound(),
166 forOp.getUpperBound(),
167 forOp.getStep()});
168 if (splitBound.getType() != forOp.getLowerBound().getType())
169 splitBound = b.createOrFold<arith::IndexCastOp>(
170 loc, forOp.getLowerBound().getType(), splitBound);
171
172 // Create ForOp for partial iteration.
173 b.setInsertionPointAfter(forOp);
174 partialIteration = cast<ForOp>(b.clone(*forOp.getOperation()));
175 partialIteration.getLowerBoundMutable().assign(splitBound);
176 b.replaceAllUsesWith(forOp.getResults(), partialIteration->getResults());
177 partialIteration.getInitArgsMutable().assign(forOp->getResults());
178
179 // Set new upper loop bound.
180 b.modifyOpInPlace(forOp,
181 [&]() { forOp.getUpperBoundMutable().assign(splitBound); });
182
183 return success();
184}
185
186static void rewriteAffineOpAfterPeeling(RewriterBase &rewriter, ForOp forOp,
187 ForOp partialIteration,
188 Value previousUb) {
189 Value mainIv = forOp.getInductionVar();
190 Value partialIv = partialIteration.getInductionVar();
191 assert(forOp.getStep() == partialIteration.getStep() &&
192 "expected same step in main and partial loop");
193 Value step = forOp.getStep();
194
195 forOp.walk([&](Operation *affineOp) {
196 if (!isa<AffineMinOp, AffineMaxOp>(affineOp))
197 return WalkResult::advance();
198 (void)scf::rewritePeeledMinMaxOp(rewriter, affineOp, mainIv, previousUb,
199 step,
200 /*insideLoop=*/true);
201 return WalkResult::advance();
202 });
203 partialIteration.walk([&](Operation *affineOp) {
204 if (!isa<AffineMinOp, AffineMaxOp>(affineOp))
205 return WalkResult::advance();
206 (void)scf::rewritePeeledMinMaxOp(rewriter, affineOp, partialIv, previousUb,
207 step, /*insideLoop=*/false);
208 return WalkResult::advance();
209 });
210}
211
213 ForOp forOp,
214 ForOp &partialIteration) {
215 Value previousUb = forOp.getUpperBound();
216 Value splitBound;
217 if (failed(peelForLoop(rewriter, forOp, partialIteration, splitBound)))
218 return failure();
219
220 // Rewrite affine.min and affine.max ops.
221 rewriteAffineOpAfterPeeling(rewriter, forOp, partialIteration, previousUb);
222
223 return success();
224}
225
226/// Rewrites the original scf::ForOp as two scf::ForOp Ops, the first
227/// scf::ForOp corresponds to the first iteration of the loop which can be
228/// canonicalized away in the following optimizations. The second loop Op
229/// contains the remaining iterations, with a lower bound updated as the
230/// original lower bound plus the step (i.e. skips the first iteration).
231LogicalResult mlir::scf::peelForLoopFirstIteration(RewriterBase &b, ForOp forOp,
232 ForOp &firstIteration) {
234 auto lbInt = getConstantIntValue(forOp.getLowerBound());
235 auto ubInt = getConstantIntValue(forOp.getUpperBound());
236 auto stepInt = getConstantIntValue(forOp.getStep());
237
238 // Peeling is not needed if there is one or less iteration.
239 if (lbInt && ubInt && stepInt && ceil(float(*ubInt - *lbInt) / *stepInt) <= 1)
240 return failure();
241
242 // The peeling bound (%lb + %step) is computed with affine.apply, which
243 // accepts only index operands. %ub does not feed into this bound, so only
244 // %lb and %step need to be constant to guarantee the affine.apply (see below)
245 // folds away before its (non-index) operand types matter.
246 if ((!lbInt || !stepInt) && !forOp.getInductionVar().getType().isIndex())
247 return failure();
248
249 AffineExpr lbSymbol, stepSymbol;
250 bindSymbols(b.getContext(), lbSymbol, stepSymbol);
251
252 // New lower bound for main loop: min(%lb + %step, %ub). Without the minimum
253 // the peeled first iteration spans [%lb, %lb + %step) and executes even when
254 // the source loop is empty (%lb >= %ub).
255 AffineExpr ubSymbol;
256 bindSymbols(b.getContext(), lbSymbol, stepSymbol, ubSymbol);
257 auto ubMap =
258 AffineMap::get(0, 3, {lbSymbol + stepSymbol, ubSymbol}, b.getContext());
259 b.setInsertionPoint(forOp);
260 auto loc = forOp.getLoc();
261 Value splitBound = b.createOrFold<AffineMinOp>(
262 loc, ubMap,
263 ValueRange{forOp.getLowerBound(), forOp.getStep(),
264 forOp.getUpperBound()});
265 if (splitBound.getType() != forOp.getUpperBound().getType())
266 splitBound = b.createOrFold<arith::IndexCastOp>(
267 loc, forOp.getUpperBound().getType(), splitBound);
268
269 // Peel the first iteration.
270 firstIteration = cast<ForOp>(b.clone(*forOp.getOperation()));
271 b.modifyOpInPlace(firstIteration, [&]() {
272 firstIteration.getUpperBoundMutable().assign(splitBound);
273 });
274 // Update main loop with new lower bound.
275 b.modifyOpInPlace(forOp, [&]() {
276 forOp.getInitArgsMutable().assign(firstIteration->getResults());
277 forOp.getLowerBoundMutable().assign(splitBound);
278 });
279
280 return success();
282
283static constexpr char kPeeledLoopLabel[] = "__peeled_loop__";
284static constexpr char kPartialIterationLabel[] = "__partial_iteration__";
285
286namespace {
287struct ForLoopPeelingPattern : public OpRewritePattern<ForOp> {
288 ForLoopPeelingPattern(MLIRContext *ctx, bool peelFront, bool skipPartial)
289 : OpRewritePattern<ForOp>(ctx), peelFront(peelFront),
290 skipPartial(skipPartial) {}
292 LogicalResult matchAndRewrite(ForOp forOp,
293 PatternRewriter &rewriter) const override {
294 if (forOp.getUnsignedCmp())
295 return rewriter.notifyMatchFailure(forOp,
296 "unsigned loops are not supported");
297
298 // Do not peel already peeled loops.
299 if (forOp->hasDiscardableAttr(kPeeledLoopLabel))
300 return failure();
301
302 scf::ForOp partialIteration;
303 // The case for peeling the first iteration of the loop.
304 if (peelFront) {
305 if (failed(
306 peelForLoopFirstIteration(rewriter, forOp, partialIteration))) {
307 return failure();
308 }
309 } else {
310 if (skipPartial) {
311 // No peeling of loops inside the partial iteration of another peeled
312 // loop.
313 Operation *op = forOp.getOperation();
314 while ((op = op->getParentOfType<scf::ForOp>())) {
316 return failure();
317 }
318 }
319 // Apply loop peeling.
320 if (failed(
321 peelForLoopAndSimplifyBounds(rewriter, forOp, partialIteration)))
322 return failure();
323 }
324
325 // Apply label, so that the same loop is not rewritten a second time.
326 rewriter.modifyOpInPlace(partialIteration, [&]() {
327 partialIteration->setDiscardableAttr(kPeeledLoopLabel,
328 rewriter.getUnitAttr());
329 partialIteration->setDiscardableAttr(kPartialIterationLabel,
330 rewriter.getUnitAttr());
331 });
332 rewriter.modifyOpInPlace(forOp, [&]() {
333 forOp->setDiscardableAttr(kPeeledLoopLabel, rewriter.getUnitAttr());
334 });
335 return success();
336 }
337
338 // If set to true, the first iteration of the loop will be peeled. Otherwise,
339 // the unevenly divisible loop will be peeled at the end.
340 bool peelFront;
341
342 /// If set to true, loops inside partial iterations of another peeled loop
343 /// are not peeled. This reduces the size of the generated code. Partial
344 /// iterations are not usually performance critical.
345 /// Note: Takes into account the entire chain of parent operations, not just
346 /// the direct parent.
347 bool skipPartial;
348};
349} // namespace
350
351namespace {
352struct ParallelLoopSpecialization
354 ParallelLoopSpecialization> {
355 void runOnOperation() override {
356 getOperation()->walk(
357 [](ParallelOp op) { specializeParallelLoopForUnrolling(op); });
358 }
359};
360
361struct ForLoopSpecialization
362 : public impl::SCFForLoopSpecializationBase<ForLoopSpecialization> {
363 void runOnOperation() override {
364 getOperation()->walk([](ForOp op) { specializeForLoopForUnrolling(op); });
365 }
366};
367
368struct ForLoopPeeling : public impl::SCFForLoopPeelingBase<ForLoopPeeling> {
369 using impl::SCFForLoopPeelingBase<ForLoopPeeling>::SCFForLoopPeelingBase;
370
371 void runOnOperation() override {
372 auto *parentOp = getOperation();
373 MLIRContext *ctx = parentOp->getContext();
374 RewritePatternSet patterns(ctx);
375 patterns.add<ForLoopPeelingPattern>(ctx, peelFront, skipPartial);
376 (void)applyPatternsGreedily(parentOp, std::move(patterns));
377
378 // Drop the markers.
379 parentOp->walk([](Operation *op) {
382 });
383 }
384};
385} // namespace
386
388 return std::make_unique<ParallelLoopSpecialization>();
389}
390
392 return std::make_unique<ForLoopSpecialization>();
393}
return success()
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
static void specializeForLoopForUnrolling(ForOp op)
Rewrite a for loop with bounds defined by an affine.min with a constant into 2 loops after checking i...
static void specializeParallelLoopForUnrolling(ParallelOp op)
Rewrite a parallel loop with bounds defined by an affine.min with a constant into 2 loops after check...
static constexpr char kPeeledLoopLabel[]
static void rewriteAffineOpAfterPeeling(RewriterBase &rewriter, ForOp forOp, ForOp partialIteration, Value previousUb)
static LogicalResult peelForLoop(RewriterBase &b, ForOp forOp, ForOp &partialIteration, Value &splitBound)
Rewrite a for loop with bounds/step that potentially do not divide evenly into a for loop where the s...
static constexpr char kPartialIterationLabel[]
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: () -> ().
AffineExpr getResult(unsigned idx) const
UnitAttr getUnitAttr()
Definition Builders.cpp:106
This is a utility class for mapping one set of IR entities to another.
Definition IRMapping.h:26
void map(Value from, Value to)
Inserts a new mapping for 'from' to 'to'.
Definition IRMapping.h:30
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
RAII guard to reset the insertion point of the builder when destroyed.
Definition Builders.h:351
This class helps build Operations.
Definition Builders.h:210
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
OpTy getParentOfType()
Return the closest surrounding parent operation that is of type 'OpTy'.
Definition Operation.h:255
bool hasDiscardableAttr(StringRef name)
Return true if this operation has a discardable attribute with the provided name.
Definition Operation.h:503
Attribute removeDiscardableAttr(StringAttr name)
Remove the discardable attribute with the specified name if it exists.
Definition Operation.h:524
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
This class coordinates the application of a rewrite on a set of IR, providing a way for clients to tr...
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,...
void modifyOpInPlace(Operation *root, CallableT &&callable)
This method is a utility wrapper around an in-place modification of an operation.
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
Type getType() const
Return the type of this value.
Definition Value.h:105
static WalkResult advance()
Definition WalkResult.h:47
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
Definition ArithOps.cpp:398
void fullyComposeAffineMapAndOperands(AffineMap *map, SmallVectorImpl< Value > *operands, bool composeAffineMin=false)
Given an affine map map and its input operands, this method composes into map, maps of AffineApplyOps...
DynamicAPInt ceil(const Fraction &f)
Definition Fraction.h:79
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:734
LogicalResult peelForLoopAndSimplifyBounds(RewriterBase &rewriter, ForOp forOp, scf::ForOp &partialIteration)
Rewrite a for loop with bounds/step that potentially do not divide evenly into a for loop where the s...
LogicalResult peelForLoopFirstIteration(RewriterBase &rewriter, ForOp forOp, scf::ForOp &partialIteration)
Peel the first iteration out of the scf.for loop.
LogicalResult rewritePeeledMinMaxOp(RewriterBase &rewriter, Operation *op, Value iv, Value ub, Value step, bool insideLoop)
Try to simplify the given affine.min/max operation op after loop peeling.
Include the generated interface declarations.
std::unique_ptr< Pass > createParallelLoopSpecializationPass()
Creates a pass that specializes parallel loop for unrolling and vectorization.
std::unique_ptr< Pass > createForLoopSpecializationPass()
Creates a pass that specializes for loop for unrolling and vectorization.
std::optional< int64_t > getConstantIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer.
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
Definition Utils.cpp:311
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...
void bindSymbols(MLIRContext *ctx, AffineExprTy &...exprs)
Bind a list of AffineExpr references to SymbolExpr at positions: [0 .
Definition AffineExpr.h:325
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...