MLIR 24.0.0git
SCFToAffine.cpp
Go to the documentation of this file.
1//===- SCFToAffine.cpp - SCF to Affine conversion -------------------------===//
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// This file implements a pass to raise scf ops to affine ops.
10//
11//===----------------------------------------------------------------------===//
12
17#include "mlir/IR/AffineExpr.h"
18#include "mlir/IR/AffineMap.h"
19#include "mlir/IR/Value.h"
21#include "mlir/Pass/Pass.h"
22#include "mlir/Support/LLVM.h"
24#include "llvm/ADT/SmallVector.h"
25
26namespace mlir {
27#define GEN_PASS_DEF_RAISESCFTOAFFINEPASS
28#include "mlir/Conversion/Passes.h.inc"
29} // namespace mlir
30
31using namespace mlir;
32
33namespace {
34
35//===----------------------------------------------------------------------===//
36// SCFToAffinePass
37//===----------------------------------------------------------------------===//
38
39struct SCFToAffinePass
40 : public impl::RaiseSCFToAffinePassBase<SCFToAffinePass> {
41 void runOnOperation() override;
42};
43
44//===----------------------------------------------------------------------===//
45// ForOpRewrite
46//===----------------------------------------------------------------------===//
47
48/// Raise an `scf.for` to an equivalent `affine.for` if lb, ub and step satisfy
49/// certain constraints making this possible.
50struct ForOpRewrite : public OpRewritePattern<scf::ForOp> {
51 using OpRewritePattern<scf::ForOp>::OpRewritePattern;
52
53 LogicalResult matchAndRewrite(scf::ForOp op,
54 PatternRewriter &rewriter) const override;
55
56private:
57 /// Definitively decide whether we are going to raise or not.
58 ///
59 /// An `scf.for` can trivially be raised if lb, ub are dimensions and step is
60 /// a constant. With some more work one can raise under relaxed constraints as
61 /// expressed by this function.
62 bool canRaiseToAffine(scf::ForOp op) const;
63
64 /// Cast lb, ub, step and the induction variable of an integer-typed `op` to
65 /// `index`, in place. The bound and step casts are placed at the top level of
66 /// the affine scope so they are valid affine symbols; the induction variable
67 /// is cast back to its original type at the start of the body so the body is
68 /// left unchanged. Assumes `canRaiseToAffine(op) == true`.
69 void castBoundsToIndex(scf::ForOp op, PatternRewriter &rewriter) const;
70
71 /// Returns an equivalent `affine.for` skeleton and the *old* induction
72 /// variable for use by the body that is inlined later. The affine loop body
73 /// is left empty except for an operation computing the old induction variable
74 /// from the new one *iff* it differs from the new one.
75 ///
76 /// Assumes `canRaiseToAffine(op) == true` and index casts were performed (if
77 /// necessary).
78 ///
79 /// There are two cases:
80 ///
81 /// 1. step is constant
82 /// 2. step is dynamic (not constant)
83 ///
84 /// In case (1) and if lb, ub are (valid) dimensions `scf.for` is trivially
85 /// raised (leaving lb, ub, iv as is). If lb is an `affine.max` we "inline" it
86 /// into the loop's lower bound map. Similarly if ub is an `affine.min`.
87 ///
88 /// In case (2) we *normalize* the loop to run from 0 with step 1: the new
89 /// upper bound is `ceil((ub - lb) / step)` and the original induction
90 /// variable is recovered in the body as `lb + step * new_iv`. Here we require
91 /// lb to be a dimension; ub may still be an `affine.min`, which is rescaled
92 /// accordingly.
93 std::pair<affine::AffineForOp, Value>
94 createAffineFor(scf::ForOp op, PatternRewriter &rewriter) const;
95
96 std::pair<affine::AffineForOp, Value>
97 createAffineForWithConstantStep(scf::ForOp op, int64_t step,
98 PatternRewriter &rewriter) const;
99
100 std::pair<affine::AffineForOp, Value>
101 createAffineForWithDynamicStep(scf::ForOp op,
102 PatternRewriter &rewriter) const;
103};
104
105bool indexBoundsRaisable(scf::ForOp op) {
106 Value lb = op.getLowerBound();
107 Value ub = op.getUpperBound();
108 IntegerAttr constAttr;
109
110 // The asymmetry between lb and ub comes from the fact that the step
111 // normalization (for non-constant (dynamic) steps) does not work with
112 // multiple *lower* bounds (max).
113 bool lbOK = affine::isValidDim(lb) ||
114 (isa_and_present<affine::AffineMaxOp>(lb.getDefiningOp()) &&
115 matchPattern(op.getStep(), m_Constant(&constAttr)));
116 bool ubOK = affine::isValidDim(ub) ||
117 isa_and_present<affine::AffineMinOp>(ub.getDefiningOp());
118 bool stepOK = affine::isValidSymbol(op.getStep());
119
120 return lbOK && ubOK && stepOK;
121}
122
123/// Decide whether an integer-typed loop can be raised by first casting its
124/// bounds (lb, ub, step) to `index`. Requires the cast to be lossless under
125/// affine's *signed* `index` interpretation, and every bound to be available at
126/// the top level of the affine scope (so the inserted casts are valid symbols).
127bool intBoundsRaisable(scf::ForOp op, IntegerType intType) {
128 uint64_t indexWidth = DataLayout::closest(op)
129 .getTypeSizeInBits(IndexType::get(op.getContext()))
130 .getFixedValue();
131 // Lossless under signed index: sign-extend needs width <= indexWidth;
132 // zero-extend (unsigned) needs a spare sign bit, i.e. width < indexWidth.
133 uint64_t requiredWidth = intType.getWidth() + (op.getUnsignedCmp() ? 1 : 0);
134 if (requiredWidth > indexWidth)
135 return false;
136
137 Region *scope = affine::getAffineScope(op);
138 if (!scope)
139 return false;
140
141 // Being top-level implies the value is a symbol once it is casted to index.
142 return affine::isTopLevelValue(op.getLowerBound(), scope) &&
143 affine::isTopLevelValue(op.getUpperBound(), scope) &&
144 affine::isTopLevelValue(op.getStep(), scope);
145}
146
147bool ForOpRewrite::canRaiseToAffine(scf::ForOp op) const {
148 Type type = op.getInductionVar().getType();
149 if (isa<IndexType>(type))
150 return indexBoundsRaisable(op);
151 if (auto intType = dyn_cast<IntegerType>(type))
152 return intBoundsRaisable(op, intType);
153 return false;
154}
155
156LogicalResult ForOpRewrite::matchAndRewrite(scf::ForOp op,
157 PatternRewriter &rewriter) const {
158 if (!canRaiseToAffine(op)) {
159 return rewriter.notifyMatchFailure(op, "cannot raise scf op to affine");
160 }
161
162 if (!isa<IndexType>(op.getInductionVar().getType()))
163 castBoundsToIndex(op, rewriter);
164
165 auto [affineFor, oldIV] = createAffineFor(op, rewriter);
166 Block *affineBody = affineFor.getBody();
167
168 if (affineBody->mightHaveTerminator()) {
169 // No unregistered ops in the body, so this is definitive.
170 Operation *terminator = affineBody->getTerminator();
171 assert(isa<affine::AffineYieldOp>(terminator) &&
172 "expected affine.yield if there *might* be terminator");
173 rewriter.eraseOp(terminator);
174 }
175
176 SmallVector<Value> argValues;
177 argValues.push_back(oldIV);
178 llvm::append_range(argValues, affineFor.getRegionIterArgs());
179 rewriter.inlineBlockBefore(op.getBody(), affineBody, affineBody->end(),
180 argValues);
181
182 auto scfYieldOp = cast<scf::YieldOp>(affineBody->getTerminator());
183 rewriter.setInsertionPointToEnd(affineBody);
184 rewriter.replaceOpWithNewOp<affine::AffineYieldOp>(scfYieldOp,
185 scfYieldOp->getOperands());
186
187 rewriter.replaceOp(op, affineFor);
188 return success();
189}
190
191std::pair<affine::AffineForOp, Value>
192ForOpRewrite::createAffineFor(scf::ForOp op, PatternRewriter &rewriter) const {
193 IntegerAttr constAttr;
194 if (matchPattern(op.getStep(), m_Constant(&constAttr))) {
195 int64_t step = constAttr.getInt();
196 assert(step > 0 && "scf.for has positive step");
197 return createAffineForWithConstantStep(op, step, rewriter);
198 }
199 return createAffineForWithDynamicStep(op, rewriter);
200}
201
202std::pair<affine::AffineForOp, Value>
203ForOpRewrite::createAffineForWithConstantStep(scf::ForOp op, int64_t step,
204 PatternRewriter &rewriter) const {
205 Value lb = op.getLowerBound();
206 Value ub = op.getUpperBound();
207
208 auto lbOperands = ValueRange(lb);
209 auto ubOperands = ValueRange(ub);
210
211 auto lbMap = AffineMap::getMultiDimIdentityMap(1, rewriter.getContext());
212 auto ubMap = AffineMap::getMultiDimIdentityMap(1, rewriter.getContext());
213
214 if (auto ubMinOp = ub.getDefiningOp<affine::AffineMinOp>()) {
215 ubOperands = ubMinOp->getOperands();
216 ubMap = ubMinOp.getAffineMap();
217 }
218
219 if (auto lbMaxOp = lb.getDefiningOp<affine::AffineMaxOp>()) {
220 lbOperands = lbMaxOp->getOperands();
221 lbMap = lbMaxOp.getAffineMap();
222 }
223
224 auto affineFor =
225 affine::AffineForOp::create(rewriter, op.getLoc(), lbOperands, lbMap,
226 ubOperands, ubMap, step, op.getInits());
227
228 return std::make_pair(affineFor, affineFor.getInductionVar());
229}
230
231std::pair<affine::AffineForOp, Value>
232ForOpRewrite::createAffineForWithDynamicStep(scf::ForOp op,
233 PatternRewriter &rewriter) const {
234 Value lb = op.getLowerBound();
235 Value ub = op.getUpperBound();
236 Value step = op.getStep();
237
238 assert(affine::isValidDim(lb) &&
239 "dynamic-step lower bound must be a valid affine dim");
240
241 AffineExpr d0 = rewriter.getAffineDimExpr(0);
242 AffineExpr d1 = rewriter.getAffineDimExpr(1);
243 AffineExpr s0 = rewriter.getAffineSymbolExpr(0);
244 AffineMap zeroMap = rewriter.getConstantAffineMap(0);
245
246 llvm::SmallVector<Value, 3> ubOperands = {lb, ub, step};
247
248 // ub is transformed with (x - lb + step - 1) floorDiv step where x ranges
249 // over all ub_i. lb is transformed to zero.
250
251 AffineMap ubMap = AffineMap::get(2, 1, (d1 - d0 + s0 - 1).floorDiv(s0));
252
253 if (auto ubMinOp = ub.getDefiningOp<affine::AffineMinOp>()) {
254 AffineMap origUbMap = ubMinOp.getAffineMap();
255 unsigned ubDims = origUbMap.getNumDims();
256 unsigned ubSyms = origUbMap.getNumSymbols();
257
258 AffineExpr lbDim = rewriter.getAffineDimExpr(ubDims);
259 AffineExpr stepSym = rewriter.getAffineSymbolExpr(ubSyms);
260
261 SmallVector<AffineExpr> ubExprs;
262 ubExprs.reserve(origUbMap.getNumResults());
263 for (AffineExpr ubI : origUbMap.getResults()) {
264 ubExprs.push_back((ubI - lbDim + stepSym - 1).floorDiv(stepSym));
265 }
266
267 // Combined space: dims = [ub dims, lb]
268 // syms = [ub syms, step]
269 ubMap =
270 AffineMap::get(ubDims + 1, ubSyms + 1, ubExprs, rewriter.getContext());
271
272 // Operand order consistent with "combined space" above:
273 ValueRange ubOps = ubMinOp->getOperands();
274 SmallVector<Value> combined;
275 combined.append(ubOps.begin(), ubOps.begin() + ubDims); // ub dims
276 combined.push_back(lb); // lb (single dim)
277 combined.append(ubOps.begin() + ubDims, ubOps.end()); // ub syms
278 combined.push_back(op.getStep()); // step (single sym)
279 ubOperands = std::move(combined);
280 }
281
282 auto affineFor = affine::AffineForOp::create(
283 rewriter, op.getLoc(), {}, zeroMap, ubOperands, ubMap, 1, op.getInits());
284
285 // old_iv = old_lb + new_iv * step
286 AffineMap ivMap = AffineMap::get(2, 1, d0 + d1 * s0);
287
288 llvm::SmallVector<Value, 3> ivOperands = {lb, affineFor.getInductionVar(),
289 step};
290
291 rewriter.setInsertionPointToStart(affineFor.getBody());
292 auto oldIV =
293 affine::AffineApplyOp::create(rewriter, op.getLoc(), ivMap, ivOperands);
294
295 return std::make_pair(affineFor, oldIV);
296}
297
298void ForOpRewrite::castBoundsToIndex(scf::ForOp loop,
299 PatternRewriter &rewriter) const {
300 OpBuilder::InsertionGuard guard(rewriter);
301
302 Value lb = loop.getLowerBound();
303 Value ub = loop.getUpperBound();
304 Value step = loop.getStep();
305 Type originalType = step.getType();
306
307 assert(lb.getType() == originalType && ub.getType() == originalType &&
308 "expected lb, ub, and step to have the same type");
309
310 auto createIndexCast = [&](Type out, Value in) -> Value {
311 Location loc = loop.getLoc();
312 if (loop.getUnsignedCmp())
313 return arith::IndexCastUIOp::create(rewriter, loc, out, in);
314 return arith::IndexCastOp::create(rewriter, loc, out, in);
315 };
316
317 // We place the bound casts at the top level of the affine scope so that they
318 // are identified as valid affine symbols.
319
320 Region *scope = affine::getAffineScope(loop);
321 Operation *anchor = loop;
322 while (anchor->getParentRegion() != scope)
323 anchor = anchor->getParentOp();
324 rewriter.setInsertionPoint(anchor);
325
326 Value newLb = createIndexCast(rewriter.getIndexType(), lb);
327 Value newUb = createIndexCast(rewriter.getIndexType(), ub);
328 Value newStep = createIndexCast(rewriter.getIndexType(), step);
329
330 rewriter.modifyOpInPlace(loop, [&] {
331 loop.setLowerBound(newLb);
332 loop.setUpperBound(newUb);
333 loop.setStep(newStep);
334
335 Value originalIV = loop.getInductionVar();
336 Value iv = loop.getBody()->insertArgument(
337 (unsigned)0, rewriter.getIndexType(), loop.getLoc());
338
339 rewriter.setInsertionPointToStart(loop.getBody());
340 Value castIV = createIndexCast(originalType, iv);
341 rewriter.replaceAllUsesWith(originalIV, castIV);
342
343 // Original induction var is now at index 1.
344 loop.getBody()->eraseArgument(1);
345 });
346}
347
348//===----------------------------------------------------------------------===//
349// Pass implementation
350//===----------------------------------------------------------------------===//
351
352void SCFToAffinePass::runOnOperation() {
353 MLIRContext &ctx = getContext();
354 RewritePatternSet patterns(&ctx);
356
357 (void)applyPatternsGreedily(getOperation(), std::move(patterns));
358}
359
360} // namespace
361
362//===----------------------------------------------------------------------===//
363// API
364//===----------------------------------------------------------------------===//
365
367 patterns.add<ForOpRewrite>(patterns.getContext());
368}
return success()
b getContext())
static AffineMap getMultiDimIdentityMap(unsigned numDims, MLIRContext *context)
Returns an AffineMap with 'numDims' identity result dim exprs.
static AffineMap get(MLIRContext *context)
Returns a zero result affine map with no dimensions or symbols: () -> ().
unsigned getNumSymbols() const
unsigned getNumDims() const
ArrayRef< AffineExpr > getResults() const
unsigned getNumResults() const
Operation * getTerminator()
Get the terminator operation of this block.
Definition Block.cpp:249
bool mightHaveTerminator()
Return "true" if this block might have a terminator.
Definition Block.cpp:255
iterator end()
Definition Block.h:168
AffineExpr getAffineSymbolExpr(unsigned position)
Definition Builders.cpp:373
AffineExpr getAffineDimExpr(unsigned position)
Definition Builders.cpp:369
AffineMap getConstantAffineMap(int64_t val)
Returns a single constant result affine map with 0 dimensions and 0 symbols.
Definition Builders.cpp:383
MLIRContext * getContext() const
Definition Builders.h:56
IndexType getIndexType()
Definition Builders.cpp:55
static DataLayout closest(Operation *op)
Returns the layout of the closest parent operation carrying layout info.
void setInsertionPointToStart(Block *block)
Sets the insertion point to the start of the specified block.
Definition Builders.h:433
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Definition Builders.h:400
void setInsertionPointToEnd(Block *block)
Sets the insertion point to the end of the specified block.
Definition Builders.h:438
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
Definition Operation.h:251
Region * getParentRegion()
Returns the region to which the instruction belongs.
Definition Operation.h:247
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Definition Region.h:26
MLIRContext * getContext() const
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
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.
virtual void inlineBlockBefore(Block *source, Block *dest, Block::iterator before, ValueRange argValues={})
Inline the operations of block 'source' into block 'dest' before the given position.
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.
virtual void replaceAllUsesWith(Value from, Value to)
Find uses of from and replace them with to.
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 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
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
bool isValidDim(Value value)
Returns true if the given Value can be used as a dimension id in the region of the closest surroundin...
bool isTopLevelValue(Value value)
A utility function to check if a value is defined at the top level of an op with trait AffineScope or...
bool isValidSymbol(Value value)
Returns true if the given value can be used as a symbol in the region of the closest surrounding op t...
Region * getAffineScope(Operation *op)
Returns the closest region enclosing op that is held by an operation with trait AffineScope; nullptr ...
Include the generated interface declarations.
bool matchPattern(Value value, const Pattern &pattern)
Entry point for matching a pattern over a Value.
Definition Matchers.h:490
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 populateSCFToAffineConversionPatterns(RewritePatternSet &patterns)
Collect a set of patterns to convert SCF operations to Affine operations.
detail::constant_op_matcher m_Constant()
Matches a constant foldable operation.
Definition Matchers.h:369
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...