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
105static bool areValidAffineMapOperands(AffineMap map, ValueRange operands,
106 Region *scope) {
107 assert(map.getNumInputs() == operands.size() &&
108 "expected one operand per affine map input");
109 return llvm::all_of(
110 operands.take_front(map.getNumDims()),
111 [&](Value value) { return affine::isValidDim(value, scope); }) &&
112 llvm::all_of(operands.drop_front(map.getNumDims()), [&](Value value) {
113 return affine::isValidSymbol(value, scope);
114 });
115}
116
117bool indexBoundsRaisable(scf::ForOp op) {
118 Value lb = op.getLowerBound();
119 Value ub = op.getUpperBound();
120 IntegerAttr constAttr;
121 Region *scope = affine::getAffineScope(op);
122 if (!scope)
123 return false;
124
125 // The asymmetry between lb and ub comes from the fact that the step
126 // normalization (for non-constant (dynamic) steps) does not work with
127 // multiple *lower* bounds (max).
128 auto lbMaxOp = lb.getDefiningOp<affine::AffineMaxOp>();
129 bool lbOK = affine::isValidDim(lb, scope) ||
130 (lbMaxOp && matchPattern(op.getStep(), m_Constant(&constAttr)) &&
131 areValidAffineMapOperands(lbMaxOp.getAffineMap(),
132 lbMaxOp->getOperands(), scope));
133 auto ubMinOp = ub.getDefiningOp<affine::AffineMinOp>();
134 bool ubOK =
135 affine::isValidDim(ub, scope) ||
136 (ubMinOp && areValidAffineMapOperands(ubMinOp.getAffineMap(),
137 ubMinOp->getOperands(), scope));
138 bool stepOK = affine::isValidSymbol(op.getStep(), scope);
139
140 return lbOK && ubOK && stepOK;
141}
142
143/// Decide whether an integer-typed loop can be raised by first casting its
144/// bounds (lb, ub, step) to `index`. Requires the cast to be lossless under
145/// affine's *signed* `index` interpretation, and every bound to be available at
146/// the top level of the affine scope (so the inserted casts are valid symbols).
147bool intBoundsRaisable(scf::ForOp op, IntegerType intType) {
148 uint64_t indexWidth = DataLayout::closest(op)
149 .getTypeSizeInBits(IndexType::get(op.getContext()))
150 .getFixedValue();
151 // Lossless under signed index: sign-extend needs width <= indexWidth;
152 // zero-extend (unsigned) needs a spare sign bit, i.e. width < indexWidth.
153 uint64_t requiredWidth = intType.getWidth() + (op.getUnsignedCmp() ? 1 : 0);
154 if (requiredWidth > indexWidth)
155 return false;
156
157 Region *scope = affine::getAffineScope(op);
158 if (!scope)
159 return false;
160
161 // Being top-level implies the value is a symbol once it is casted to index.
162 return affine::isTopLevelValue(op.getLowerBound(), scope) &&
163 affine::isTopLevelValue(op.getUpperBound(), scope) &&
164 affine::isTopLevelValue(op.getStep(), scope);
165}
166
167bool ForOpRewrite::canRaiseToAffine(scf::ForOp op) const {
168 Type type = op.getInductionVar().getType();
169 if (isa<IndexType>(type))
170 return indexBoundsRaisable(op);
171 if (auto intType = dyn_cast<IntegerType>(type))
172 return intBoundsRaisable(op, intType);
173 return false;
174}
175
176LogicalResult ForOpRewrite::matchAndRewrite(scf::ForOp op,
177 PatternRewriter &rewriter) const {
178 if (!canRaiseToAffine(op)) {
179 return rewriter.notifyMatchFailure(op, "cannot raise scf op to affine");
180 }
181
182 if (!isa<IndexType>(op.getInductionVar().getType()))
183 castBoundsToIndex(op, rewriter);
184
185 auto [affineFor, oldIV] = createAffineFor(op, rewriter);
186 Block *affineBody = affineFor.getBody();
187
188 if (affineBody->mightHaveTerminator()) {
189 // No unregistered ops in the body, so this is definitive.
190 Operation *terminator = affineBody->getTerminator();
191 assert(isa<affine::AffineYieldOp>(terminator) &&
192 "expected affine.yield if there *might* be terminator");
193 rewriter.eraseOp(terminator);
194 }
195
196 SmallVector<Value> argValues;
197 argValues.push_back(oldIV);
198 llvm::append_range(argValues, affineFor.getRegionIterArgs());
199 rewriter.inlineBlockBefore(op.getBody(), affineBody, affineBody->end(),
200 argValues);
201
202 auto scfYieldOp = cast<scf::YieldOp>(affineBody->getTerminator());
203 rewriter.setInsertionPointToEnd(affineBody);
204 rewriter.replaceOpWithNewOp<affine::AffineYieldOp>(scfYieldOp,
205 scfYieldOp->getOperands());
206
207 rewriter.replaceOp(op, affineFor);
208 return success();
209}
210
211std::pair<affine::AffineForOp, Value>
212ForOpRewrite::createAffineFor(scf::ForOp op, PatternRewriter &rewriter) const {
213 IntegerAttr constAttr;
214 if (matchPattern(op.getStep(), m_Constant(&constAttr))) {
215 int64_t step = constAttr.getInt();
216 assert(step > 0 && "scf.for has positive step");
217 return createAffineForWithConstantStep(op, step, rewriter);
218 }
219 return createAffineForWithDynamicStep(op, rewriter);
220}
221
222std::pair<affine::AffineForOp, Value>
223ForOpRewrite::createAffineForWithConstantStep(scf::ForOp op, int64_t step,
224 PatternRewriter &rewriter) const {
225 Value lb = op.getLowerBound();
226 Value ub = op.getUpperBound();
227
228 auto lbOperands = ValueRange(lb);
229 auto ubOperands = ValueRange(ub);
230
231 auto lbMap = AffineMap::getMultiDimIdentityMap(1, rewriter.getContext());
232 auto ubMap = AffineMap::getMultiDimIdentityMap(1, rewriter.getContext());
233
234 if (auto ubMinOp = ub.getDefiningOp<affine::AffineMinOp>()) {
235 ubOperands = ubMinOp->getOperands();
236 ubMap = ubMinOp.getAffineMap();
237 }
238
239 if (auto lbMaxOp = lb.getDefiningOp<affine::AffineMaxOp>()) {
240 lbOperands = lbMaxOp->getOperands();
241 lbMap = lbMaxOp.getAffineMap();
242 }
243
244 auto affineFor =
245 affine::AffineForOp::create(rewriter, op.getLoc(), lbOperands, lbMap,
246 ubOperands, ubMap, step, op.getInits());
247
248 return std::make_pair(affineFor, affineFor.getInductionVar());
249}
250
251std::pair<affine::AffineForOp, Value>
252ForOpRewrite::createAffineForWithDynamicStep(scf::ForOp op,
253 PatternRewriter &rewriter) const {
254 Value lb = op.getLowerBound();
255 Value ub = op.getUpperBound();
256 Value step = op.getStep();
257
258 assert(affine::isValidDim(lb) &&
259 "dynamic-step lower bound must be a valid affine dim");
260
261 AffineExpr d0 = rewriter.getAffineDimExpr(0);
262 AffineExpr d1 = rewriter.getAffineDimExpr(1);
263 AffineExpr s0 = rewriter.getAffineSymbolExpr(0);
264 AffineMap zeroMap = rewriter.getConstantAffineMap(0);
265
266 llvm::SmallVector<Value, 3> ubOperands = {lb, ub, step};
267
268 // ub is transformed with (x - lb + step - 1) floorDiv step where x ranges
269 // over all ub_i. lb is transformed to zero.
270
271 AffineMap ubMap = AffineMap::get(2, 1, (d1 - d0 + s0 - 1).floorDiv(s0));
272
273 if (auto ubMinOp = ub.getDefiningOp<affine::AffineMinOp>()) {
274 AffineMap origUbMap = ubMinOp.getAffineMap();
275 unsigned ubDims = origUbMap.getNumDims();
276 unsigned ubSyms = origUbMap.getNumSymbols();
277
278 AffineExpr lbDim = rewriter.getAffineDimExpr(ubDims);
279 AffineExpr stepSym = rewriter.getAffineSymbolExpr(ubSyms);
280
281 SmallVector<AffineExpr> ubExprs;
282 ubExprs.reserve(origUbMap.getNumResults());
283 for (AffineExpr ubI : origUbMap.getResults()) {
284 ubExprs.push_back((ubI - lbDim + stepSym - 1).floorDiv(stepSym));
285 }
286
287 // Combined space: dims = [ub dims, lb]
288 // syms = [ub syms, step]
289 ubMap =
290 AffineMap::get(ubDims + 1, ubSyms + 1, ubExprs, rewriter.getContext());
291
292 // Operand order consistent with "combined space" above:
293 ValueRange ubOps = ubMinOp->getOperands();
294 SmallVector<Value> combined;
295 combined.append(ubOps.begin(), ubOps.begin() + ubDims); // ub dims
296 combined.push_back(lb); // lb (single dim)
297 combined.append(ubOps.begin() + ubDims, ubOps.end()); // ub syms
298 combined.push_back(op.getStep()); // step (single sym)
299 ubOperands = std::move(combined);
300 }
301
302 auto affineFor = affine::AffineForOp::create(
303 rewriter, op.getLoc(), {}, zeroMap, ubOperands, ubMap, 1, op.getInits());
304
305 // old_iv = old_lb + new_iv * step
306 AffineMap ivMap = AffineMap::get(2, 1, d0 + d1 * s0);
307
308 llvm::SmallVector<Value, 3> ivOperands = {lb, affineFor.getInductionVar(),
309 step};
310
311 rewriter.setInsertionPointToStart(affineFor.getBody());
312 auto oldIV =
313 affine::AffineApplyOp::create(rewriter, op.getLoc(), ivMap, ivOperands);
314
315 return std::make_pair(affineFor, oldIV);
316}
317
318void ForOpRewrite::castBoundsToIndex(scf::ForOp loop,
319 PatternRewriter &rewriter) const {
320 OpBuilder::InsertionGuard guard(rewriter);
321
322 Value lb = loop.getLowerBound();
323 Value ub = loop.getUpperBound();
324 Value step = loop.getStep();
325 Type originalType = step.getType();
326
327 assert(lb.getType() == originalType && ub.getType() == originalType &&
328 "expected lb, ub, and step to have the same type");
329
330 auto createIndexCast = [&](Type out, Value in) -> Value {
331 Location loc = loop.getLoc();
332 if (loop.getUnsignedCmp())
333 return arith::IndexCastUIOp::create(rewriter, loc, out, in);
334 return arith::IndexCastOp::create(rewriter, loc, out, in);
335 };
336
337 // We place the bound casts at the top level of the affine scope so that they
338 // are identified as valid affine symbols.
339
340 Region *scope = affine::getAffineScope(loop);
341 Operation *anchor = loop;
342 while (anchor->getParentRegion() != scope)
343 anchor = anchor->getParentOp();
344 rewriter.setInsertionPoint(anchor);
345
346 Value newLb = createIndexCast(rewriter.getIndexType(), lb);
347 Value newUb = createIndexCast(rewriter.getIndexType(), ub);
348 Value newStep = createIndexCast(rewriter.getIndexType(), step);
349
350 rewriter.modifyOpInPlace(loop, [&] {
351 loop.setLowerBound(newLb);
352 loop.setUpperBound(newUb);
353 loop.setStep(newStep);
354
355 Value originalIV = loop.getInductionVar();
356 Value iv = loop.getBody()->insertArgument(
357 (unsigned)0, rewriter.getIndexType(), loop.getLoc());
358
359 rewriter.setInsertionPointToStart(loop.getBody());
360 Value castIV = createIndexCast(originalType, iv);
361 rewriter.replaceAllUsesWith(originalIV, castIV);
362
363 // Original induction var is now at index 1.
364 loop.getBody()->eraseArgument(1);
365 });
366}
367
368//===----------------------------------------------------------------------===//
369// Pass implementation
370//===----------------------------------------------------------------------===//
371
372void SCFToAffinePass::runOnOperation() {
373 MLIRContext &ctx = getContext();
374 RewritePatternSet patterns(&ctx);
376
377 (void)applyPatternsGreedily(getOperation(), std::move(patterns));
378}
379
380} // namespace
381
382//===----------------------------------------------------------------------===//
383// API
384//===----------------------------------------------------------------------===//
385
387 patterns.add<ForOpRewrite>(patterns.getContext());
388}
return success()
b getContext())
A multi-dimensional affine map Affine map's are immutable like Type's, and they are uniqued.
Definition AffineMap.h:46
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
unsigned getNumInputs() 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:377
AffineExpr getAffineDimExpr(unsigned position)
Definition Builders.cpp:373
AffineMap getConstantAffineMap(int64_t val)
Returns a single constant result affine map with 0 dimensions and 0 symbols.
Definition Builders.cpp:387
MLIRContext * getContext() const
Definition Builders.h:56
IndexType getIndexType()
Definition Builders.cpp:59
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:434
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Definition Builders.h:401
void setInsertionPointToEnd(Block *block)
Sets the insertion point to the end of the specified block.
Definition Builders.h:439
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 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
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...