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());
129
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();
145
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: %ub - (%ub - %lb) mod %step
158 auto modMap = AffineMap::get(0, 3, {sym1 - ((sym1 - sym0) % sym2)});
159 b.setInsertionPoint(forOp);
160 auto loc = forOp.getLoc();
161 splitBound = b.createOrFold<AffineApplyOp>(loc, modMap,
162 ValueRange{forOp.getLowerBound(),
163 forOp.getUpperBound(),
164 forOp.getStep()});
165 if (splitBound.getType() != forOp.getLowerBound().getType())
166 splitBound = b.createOrFold<arith::IndexCastOp>(
167 loc, forOp.getLowerBound().getType(), splitBound);
168
169 // Create ForOp for partial iteration.
170 b.setInsertionPointAfter(forOp);
171 partialIteration = cast<ForOp>(b.clone(*forOp.getOperation()));
172 partialIteration.getLowerBoundMutable().assign(splitBound);
173 b.replaceAllUsesWith(forOp.getResults(), partialIteration->getResults());
174 partialIteration.getInitArgsMutable().assign(forOp->getResults());
175
176 // Set new upper loop bound.
177 b.modifyOpInPlace(forOp,
178 [&]() { forOp.getUpperBoundMutable().assign(splitBound); });
179
180 return success();
181}
182
183static void rewriteAffineOpAfterPeeling(RewriterBase &rewriter, ForOp forOp,
184 ForOp partialIteration,
185 Value previousUb) {
186 Value mainIv = forOp.getInductionVar();
187 Value partialIv = partialIteration.getInductionVar();
188 assert(forOp.getStep() == partialIteration.getStep() &&
189 "expected same step in main and partial loop");
190 Value step = forOp.getStep();
191
192 forOp.walk([&](Operation *affineOp) {
193 if (!isa<AffineMinOp, AffineMaxOp>(affineOp))
194 return WalkResult::advance();
195 (void)scf::rewritePeeledMinMaxOp(rewriter, affineOp, mainIv, previousUb,
196 step,
197 /*insideLoop=*/true);
198 return WalkResult::advance();
199 });
200 partialIteration.walk([&](Operation *affineOp) {
201 if (!isa<AffineMinOp, AffineMaxOp>(affineOp))
202 return WalkResult::advance();
203 (void)scf::rewritePeeledMinMaxOp(rewriter, affineOp, partialIv, previousUb,
204 step, /*insideLoop=*/false);
205 return WalkResult::advance();
206 });
207}
208
210 ForOp forOp,
211 ForOp &partialIteration) {
212 Value previousUb = forOp.getUpperBound();
213 Value splitBound;
214 if (failed(peelForLoop(rewriter, forOp, partialIteration, splitBound)))
215 return failure();
216
217 // Rewrite affine.min and affine.max ops.
218 rewriteAffineOpAfterPeeling(rewriter, forOp, partialIteration, previousUb);
219
220 return success();
221}
222
223/// Rewrites the original scf::ForOp as two scf::ForOp Ops, the first
224/// scf::ForOp corresponds to the first iteration of the loop which can be
225/// canonicalized away in the following optimizations. The second loop Op
226/// contains the remaining iterations, with a lower bound updated as the
227/// original lower bound plus the step (i.e. skips the first iteration).
228LogicalResult mlir::scf::peelForLoopFirstIteration(RewriterBase &b, ForOp forOp,
229 ForOp &firstIteration) {
231 auto lbInt = getConstantIntValue(forOp.getLowerBound());
232 auto ubInt = getConstantIntValue(forOp.getUpperBound());
233 auto stepInt = getConstantIntValue(forOp.getStep());
234
235 // Peeling is not needed if there is one or less iteration.
236 if (lbInt && ubInt && stepInt && ceil(float(*ubInt - *lbInt) / *stepInt) <= 1)
237 return failure();
238
239 // The peeling bound (%lb + %step) is computed with affine.apply, which
240 // accepts only index operands. %ub does not feed into this bound, so only
241 // %lb and %step need to be constant to guarantee the affine.apply (see below)
242 // folds away before its (non-index) operand types matter.
243 if ((!lbInt || !stepInt) && !forOp.getInductionVar().getType().isIndex())
244 return failure();
245
246 AffineExpr lbSymbol, stepSymbol;
247 bindSymbols(b.getContext(), lbSymbol, stepSymbol);
248
249 // New lower bound for main loop: %lb + %step
250 auto ubMap = AffineMap::get(0, 2, {lbSymbol + stepSymbol});
251 b.setInsertionPoint(forOp);
252 auto loc = forOp.getLoc();
253 Value splitBound = b.createOrFold<AffineApplyOp>(
254 loc, ubMap, ValueRange{forOp.getLowerBound(), forOp.getStep()});
255 if (splitBound.getType() != forOp.getUpperBound().getType())
256 splitBound = b.createOrFold<arith::IndexCastOp>(
257 loc, forOp.getUpperBound().getType(), splitBound);
258
259 // Peel the first iteration.
260 firstIteration = cast<ForOp>(b.clone(*forOp.getOperation()));
261 b.modifyOpInPlace(firstIteration, [&]() {
262 firstIteration.getUpperBoundMutable().assign(splitBound);
263 });
264 // Update main loop with new lower bound.
265 b.modifyOpInPlace(forOp, [&]() {
266 forOp.getInitArgsMutable().assign(firstIteration->getResults());
267 forOp.getLowerBoundMutable().assign(splitBound);
268 });
269
270 return success();
271}
272
273static constexpr char kPeeledLoopLabel[] = "__peeled_loop__";
274static constexpr char kPartialIterationLabel[] = "__partial_iteration__";
275
276namespace {
277struct ForLoopPeelingPattern : public OpRewritePattern<ForOp> {
278 ForLoopPeelingPattern(MLIRContext *ctx, bool peelFront, bool skipPartial)
279 : OpRewritePattern<ForOp>(ctx), peelFront(peelFront),
280 skipPartial(skipPartial) {}
281
282 LogicalResult matchAndRewrite(ForOp forOp,
283 PatternRewriter &rewriter) const override {
284 if (forOp.getUnsignedCmp())
285 return rewriter.notifyMatchFailure(forOp,
286 "unsigned loops are not supported");
287
288 // Do not peel already peeled loops.
289 if (forOp->hasDiscardableAttr(kPeeledLoopLabel))
290 return failure();
291
292 scf::ForOp partialIteration;
293 // The case for peeling the first iteration of the loop.
294 if (peelFront) {
295 if (failed(
296 peelForLoopFirstIteration(rewriter, forOp, partialIteration))) {
297 return failure();
298 }
299 } else {
300 if (skipPartial) {
301 // No peeling of loops inside the partial iteration of another peeled
302 // loop.
303 Operation *op = forOp.getOperation();
304 while ((op = op->getParentOfType<scf::ForOp>())) {
306 return failure();
307 }
308 }
309 // Apply loop peeling.
310 if (failed(
311 peelForLoopAndSimplifyBounds(rewriter, forOp, partialIteration)))
312 return failure();
313 }
314
315 // Apply label, so that the same loop is not rewritten a second time.
316 rewriter.modifyOpInPlace(partialIteration, [&]() {
317 partialIteration->setDiscardableAttr(kPeeledLoopLabel,
318 rewriter.getUnitAttr());
319 partialIteration->setDiscardableAttr(kPartialIterationLabel,
320 rewriter.getUnitAttr());
321 });
322 rewriter.modifyOpInPlace(forOp, [&]() {
323 forOp->setDiscardableAttr(kPeeledLoopLabel, rewriter.getUnitAttr());
324 });
325 return success();
326 }
327
328 // If set to true, the first iteration of the loop will be peeled. Otherwise,
329 // the unevenly divisible loop will be peeled at the end.
330 bool peelFront;
331
332 /// If set to true, loops inside partial iterations of another peeled loop
333 /// are not peeled. This reduces the size of the generated code. Partial
334 /// iterations are not usually performance critical.
335 /// Note: Takes into account the entire chain of parent operations, not just
336 /// the direct parent.
337 bool skipPartial;
338};
339} // namespace
340
341namespace {
342struct ParallelLoopSpecialization
343 : public impl::SCFParallelLoopSpecializationBase<
344 ParallelLoopSpecialization> {
345 void runOnOperation() override {
346 getOperation()->walk(
347 [](ParallelOp op) { specializeParallelLoopForUnrolling(op); });
348 }
349};
350
351struct ForLoopSpecialization
352 : public impl::SCFForLoopSpecializationBase<ForLoopSpecialization> {
353 void runOnOperation() override {
354 getOperation()->walk([](ForOp op) { specializeForLoopForUnrolling(op); });
355 }
356};
357
358struct ForLoopPeeling : public impl::SCFForLoopPeelingBase<ForLoopPeeling> {
359 using impl::SCFForLoopPeelingBase<ForLoopPeeling>::SCFForLoopPeelingBase;
360
361 void runOnOperation() override {
362 auto *parentOp = getOperation();
363 MLIRContext *ctx = parentOp->getContext();
364 RewritePatternSet patterns(ctx);
365 patterns.add<ForLoopPeelingPattern>(ctx, peelFront, skipPartial);
366 (void)applyPatternsGreedily(parentOp, std::move(patterns));
367
368 // Drop the markers.
369 parentOp->walk([](Operation *op) {
372 });
373 }
374};
375} // namespace
376
378 return std::make_unique<ParallelLoopSpecialization>();
379}
380
382 return std::make_unique<ForLoopSpecialization>();
383}
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:732
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:307
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...