MLIR 24.0.0git
AffineExpandIndexOps.cpp
Go to the documentation of this file.
1//===- AffineExpandIndexOps.cpp - Affine expand index ops pass ------------===//
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 expand affine index ops into one or more more
10// fundamental operations.
11//===----------------------------------------------------------------------===//
12
15
21
22namespace mlir {
23namespace affine {
24#define GEN_PASS_DEF_AFFINEEXPANDINDEXOPS
25#include "mlir/Dialect/Affine/Transforms/Passes.h.inc"
26} // namespace affine
27} // namespace mlir
28
29using namespace mlir;
30using namespace mlir::affine;
31
32/// Given a basis (in static and dynamic components), return the sequence of
33/// suffix products of the basis, including the product of the entire basis,
34/// which must **not** contain an outer bound.
35///
36/// If excess dynamic values are provided, the values at the beginning
37/// will be ignored. This allows for dropping the outer bound without
38/// needing to manipulate the dynamic value array. `knownPositive`
39/// indicases that the values being used to compute the strides are known
40/// to be non-negative.
42 ValueRange dynamicBasis,
43 ArrayRef<int64_t> staticBasis,
44 bool knownNonNegative) {
45 if (staticBasis.empty())
46 return {};
47
49 result.reserve(staticBasis.size());
50 size_t dynamicIndex = dynamicBasis.size();
51 Value dynamicPart = nullptr;
52 int64_t staticPart = 1;
53 // The products of the strides can't have overflow by definition of
54 // affine.*_index.
55 arith::IntegerOverflowFlags ovflags = arith::IntegerOverflowFlags::nsw;
56 if (knownNonNegative)
57 ovflags = ovflags | arith::IntegerOverflowFlags::nuw;
58 for (int64_t elem : llvm::reverse(staticBasis)) {
59 if (ShapedType::isDynamic(elem)) {
60 // Note: basis elements and their products are, definitionally,
61 // non-negative, so `nuw` is justified.
62 if (dynamicPart)
63 dynamicPart =
64 arith::MulIOp::create(rewriter, loc, dynamicPart,
65 dynamicBasis[dynamicIndex - 1], ovflags);
66 else
67 dynamicPart = dynamicBasis[dynamicIndex - 1];
68 --dynamicIndex;
69 } else {
70 staticPart *= elem;
71 }
72
73 if (dynamicPart && staticPart == 1) {
74 result.push_back(dynamicPart);
75 } else {
76 Value stride =
77 rewriter.createOrFold<arith::ConstantIndexOp>(loc, staticPart);
78 if (dynamicPart)
79 stride =
80 arith::MulIOp::create(rewriter, loc, dynamicPart, stride, ovflags);
81 result.push_back(stride);
82 }
83 }
84 std::reverse(result.begin(), result.end());
85 return result;
86}
87
88/// Broadcast a scalar value to match the given type. If the type is already
89/// scalar, returns the value as-is. For vector types, uses vector.broadcast.
91 Value value, Type targetType) {
92 if (value.getType() == targetType)
93 return value;
94 return vector::BroadcastOp::create(rewriter, loc, targetType, value);
95}
96
97LogicalResult
99 AffineDelinearizeIndexOp op) {
100 Location loc = op.getLoc();
101 Value linearIdx = op.getLinearIndex();
102 unsigned numResults = op.getNumResults();
103 ArrayRef<int64_t> staticBasis = op.getStaticBasis();
104 if (numResults == staticBasis.size())
105 staticBasis = staticBasis.drop_front();
106
107 if (numResults == 1) {
108 rewriter.replaceOp(op, linearIdx);
109 return success();
110 }
111
112 SmallVector<Value> results;
113 results.reserve(numResults);
114 SmallVector<Value> strides =
115 computeStrides(loc, rewriter, op.getDynamicBasis(), staticBasis,
116 /*knownNonNegative=*/true);
117
118 // Broadcast strides and zero to match the linear index type (needed for
119 // vector types where the strides are scalar but the index is a vector).
120 Type indexType = linearIdx.getType();
121 for (Value &stride : strides)
122 stride = broadcastToMatchType(rewriter, loc, stride, indexType);
123
124 Value zero =
125 arith::ConstantOp::create(rewriter, loc, rewriter.getZeroAttr(indexType));
126
127 Value initialPart =
128 arith::FloorDivSIOp::create(rewriter, loc, linearIdx, strides.front());
129 results.push_back(initialPart);
130
131 auto emitModTerm = [&](Value stride) -> Value {
132 Value remainder = arith::RemSIOp::create(rewriter, loc, linearIdx, stride);
133 Value remainderNegative = arith::CmpIOp::create(
134 rewriter, loc, arith::CmpIPredicate::slt, remainder, zero);
135 // If the correction is relevant, this term is <= stride, which is known
136 // to be positive in `index`. Otherwise, while 2 * stride might overflow,
137 // this branch won't be taken, so the risk of `poison` is fine.
138 Value corrected = arith::AddIOp::create(rewriter, loc, remainder, stride,
139 arith::IntegerOverflowFlags::nsw);
140 Value mod = arith::SelectOp::create(rewriter, loc, remainderNegative,
141 corrected, remainder);
142 return mod;
143 };
144
145 // Generate all the intermediate parts
146 for (size_t i = 0, e = strides.size() - 1; i < e; ++i) {
147 Value thisStride = strides[i];
148 Value nextStride = strides[i + 1];
149 Value modulus = emitModTerm(thisStride);
150 // We know both inputs are positive, so floorDiv == div.
151 // This could potentially be a divui, but it's not clear if that would
152 // cause issues.
153 Value divided = arith::DivSIOp::create(rewriter, loc, modulus, nextStride);
154 results.push_back(divided);
155 }
156
157 results.push_back(emitModTerm(strides.back()));
158
159 rewriter.replaceOp(op, results);
160 return success();
161}
162
164 AffineLinearizeIndexOp op) {
165 // Should be folded away, included here for safety.
166 if (op.getMultiIndex().empty()) {
167 rewriter.replaceOpWithNewOp<arith::ConstantOp>(
168 op, rewriter.getZeroAttr(op.getLinearIndex().getType()));
169 return success();
170 }
171
172 Location loc = op.getLoc();
173 ValueRange multiIndex = op.getMultiIndex();
174 Type indexType = op.getLinearIndex().getType();
175 size_t numIndexes = multiIndex.size();
176 ArrayRef<int64_t> staticBasis = op.getStaticBasis();
177 if (numIndexes == staticBasis.size())
178 staticBasis = staticBasis.drop_front();
179
180 SmallVector<Value> strides =
181 computeStrides(loc, rewriter, op.getDynamicBasis(), staticBasis,
182 /*knownNonNegative=*/op.getDisjoint());
183
184 // Broadcast strides to match the index type (needed for vector types).
185 for (Value &stride : strides)
186 stride = broadcastToMatchType(rewriter, loc, stride, indexType);
187
189 scaledValues.reserve(numIndexes);
190
191 // Note: strides doesn't contain a value for the final element (stride 1)
192 // and everything else lines up. We use the "mutable" accessor so we can get
193 // our hands on an `OpOperand&` for the loop invariant counting function.
194 for (auto [stride, idxOp] :
195 llvm::zip_equal(strides, llvm::drop_end(op.getMultiIndexMutable()))) {
196 Value scaledIdx = arith::MulIOp::create(rewriter, loc, idxOp.get(), stride,
197 arith::IntegerOverflowFlags::nsw);
198 int64_t numHoistableLoops = numEnclosingInvariantLoops(idxOp);
199 scaledValues.emplace_back(scaledIdx, numHoistableLoops);
200 }
201 scaledValues.emplace_back(
202 multiIndex.back(),
203 numEnclosingInvariantLoops(op.getMultiIndexMutable()[numIndexes - 1]));
204
205 // Sort by how many enclosing loops there are, ties implicitly broken by
206 // size of the stride.
207 llvm::stable_sort(scaledValues,
208 [&](auto l, auto r) { return l.second > r.second; });
209
210 Value result = scaledValues.front().first;
211 for (auto [scaledValue, numHoistableLoops] : llvm::drop_begin(scaledValues)) {
212 std::ignore = numHoistableLoops;
213 result = arith::AddIOp::create(rewriter, loc, result, scaledValue,
214 arith::IntegerOverflowFlags::nsw);
215 }
216 rewriter.replaceOp(op, result);
217 return success();
218}
219
220namespace {
221struct LowerDelinearizeIndexOps
222 : public OpRewritePattern<AffineDelinearizeIndexOp> {
223 using OpRewritePattern<AffineDelinearizeIndexOp>::OpRewritePattern;
224 LogicalResult matchAndRewrite(AffineDelinearizeIndexOp op,
225 PatternRewriter &rewriter) const override {
226 return affine::lowerAffineDelinearizeIndexOp(rewriter, op);
227 }
228};
229
230struct LowerLinearizeIndexOps final : OpRewritePattern<AffineLinearizeIndexOp> {
232 LogicalResult matchAndRewrite(AffineLinearizeIndexOp op,
233 PatternRewriter &rewriter) const override {
234 return affine::lowerAffineLinearizeIndexOp(rewriter, op);
235 }
236};
237
238class ExpandAffineIndexOpsPass
239 : public affine::impl::AffineExpandIndexOpsBase<ExpandAffineIndexOpsPass> {
240public:
241 ExpandAffineIndexOpsPass() = default;
242
243 void runOnOperation() override {
244 MLIRContext *context = &getContext();
245 RewritePatternSet patterns(context);
247 if (failed(applyPatternsGreedily(getOperation(), std::move(patterns))))
248 return signalPassFailure();
249 }
250};
251
252} // namespace
253
255 RewritePatternSet &patterns) {
256 patterns.insert<LowerDelinearizeIndexOps, LowerLinearizeIndexOps>(
257 patterns.getContext());
258}
259
261 return std::make_unique<ExpandAffineIndexOpsPass>();
262}
return success()
static Value broadcastToMatchType(RewriterBase &rewriter, Location loc, Value value, Type targetType)
Broadcast a scalar value to match the given type.
b getContext())
TypedAttr getZeroAttr(Type type)
Definition Builders.cpp:333
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
void createOrFold(SmallVectorImpl< Value > &results, Location location, Args &&...args)
Create an operation of specific op type at the current insertion point, and immediately try to fold i...
Definition Builders.h:528
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
RewritePatternSet & insert(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
MLIRContext * getContext() const
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...
OpTy replaceOpWithNewOp(Operation *op, Args &&...args)
Replace the results of the given (original) op with a new op that is created without verification (re...
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
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
Specialization of arith.constant op that returns an integer of index type.
Definition Arith.h:93
LogicalResult lowerAffineDelinearizeIndexOp(RewriterBase &rewriter, AffineDelinearizeIndexOp op)
Lowers affine.delinearize_index into a sequence of division and remainder operations.
LogicalResult lowerAffineLinearizeIndexOp(RewriterBase &rewriter, AffineLinearizeIndexOp op)
Lowers affine.linearize_index into a sequence of multiplications and additions.
std::unique_ptr< Pass > createAffineExpandIndexOpsPass()
Creates a pass to expand affine index operations into more fundamental operations (not necessarily re...
int64_t numEnclosingInvariantLoops(OpOperand &operand)
Performs explicit copying for the contiguous sequence of operations in the block iterator range [‘beg...
void populateAffineExpandIndexOpsPatterns(RewritePatternSet &patterns)
Populate patterns that expand affine index operations into more fundamental operations (not necessari...
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:732
Include the generated interface declarations.
SmallVector< int64_t > computeStrides(ArrayRef< int64_t > sizes)
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...
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...
OpRewritePattern(MLIRContext *context, PatternBenefit benefit=1, ArrayRef< StringRef > generatedNames={})
Patterns must specify the root operation name they match against, and can also specify the benefit of...