MLIR 24.0.0git
AlgebraicSimplification.cpp
Go to the documentation of this file.
1//===- AlgebraicSimplification.cpp - Simplify algebraic expressions -------===//
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 rewrites based on the basic rules of algebra
10// (Commutativity, associativity, etc...) and strength reductions for math
11// operations.
12//
13//===----------------------------------------------------------------------===//
14
20#include "mlir/IR/Builders.h"
21#include "mlir/IR/Matchers.h"
23#include <climits>
24
25using namespace mlir;
26
27//----------------------------------------------------------------------------//
28// PowFOp strength reduction.
29//----------------------------------------------------------------------------//
30
31namespace {
32struct PowFStrengthReduction : public OpRewritePattern<math::PowFOp> {
33public:
35
36 LogicalResult matchAndRewrite(math::PowFOp op,
37 PatternRewriter &rewriter) const final;
38};
39} // namespace
40
41LogicalResult
42PowFStrengthReduction::matchAndRewrite(math::PowFOp op,
43 PatternRewriter &rewriter) const {
44 Location loc = op.getLoc();
45 Value x = op.getLhs();
46 arith::FastMathFlags fmf = op.getFastmathAttr().getValue();
47
48 FloatAttr scalarExponent;
49 DenseFPElementsAttr vectorExponent;
50
51 bool isScalar = matchPattern(op.getRhs(), m_Constant(&scalarExponent));
52 bool isVector = matchPattern(op.getRhs(), m_Constant(&vectorExponent));
53
54 // Returns true if exponent is a constant equal to `value`.
55 auto isExponentValue = [&](double value) -> bool {
56 if (isScalar)
57 return scalarExponent.getValue().isExactlyValue(value);
58
59 if (isVector && vectorExponent.isSplat())
60 return vectorExponent.getSplatValue<FloatAttr>()
61 .getValue()
62 .isExactlyValue(value);
63
64 return false;
65 };
66
67 // Maybe broadcasts scalar value into vector type compatible with `op`.
68 auto bcast = [&](Value value) -> Value {
69 if (auto vec = dyn_cast<VectorType>(op.getType()))
70 return vector::BroadcastOp::create(rewriter, loc, vec, value);
71 return value;
72 };
73
74 // Replace `pow(x, 1.0)` with `x`.
75 if (isExponentValue(1.0)) {
76 rewriter.replaceOp(op, x);
77 return success();
78 }
79
80 // Replace `pow(x, 2.0)` with `x * x`.
81 if (isExponentValue(2.0)) {
82 rewriter.replaceOpWithNewOp<arith::MulFOp>(op, x, x, fmf);
83 return success();
84 }
85
86 // Replace `pow(x, 3.0)` with `x * x * x`.
87 if (isExponentValue(3.0)) {
88 Value square = arith::MulFOp::create(rewriter, loc, x, x, fmf);
89 rewriter.replaceOpWithNewOp<arith::MulFOp>(op, x, square, fmf);
90 return success();
91 }
92
93 // Replace `pow(x, -1.0)` with `1.0 / x`.
94 if (isExponentValue(-1.0)) {
95 Value one = arith::ConstantOp::create(
96 rewriter, loc,
97 rewriter.getFloatAttr(getElementTypeOrSelf(op.getType()), 1.0));
98 rewriter.replaceOpWithNewOp<arith::DivFOp>(op, bcast(one), x, fmf);
99 return success();
100 }
101
102 // Replace `pow(x, 0.5)` with `sqrt(x)`.
103 if (isExponentValue(0.5)) {
104 rewriter.replaceOpWithNewOp<math::SqrtOp>(op, x, fmf);
105 return success();
106 }
107
108 // Replace `pow(x, -0.5)` with `rsqrt(x)`.
109 if (isExponentValue(-0.5)) {
110 rewriter.replaceOpWithNewOp<math::RsqrtOp>(op, x, fmf);
111 return success();
112 }
113
114 // Replace `pow(x, 0.75)` with `sqrt(sqrt(x)) * sqrt(x)`.
115 if (isExponentValue(0.75)) {
116 Value powHalf = math::SqrtOp::create(rewriter, loc, x, fmf);
117 Value powQuarter = math::SqrtOp::create(rewriter, loc, powHalf, fmf);
118 rewriter.replaceOpWithNewOp<arith::MulFOp>(op, powHalf, powQuarter, fmf);
119 return success();
120 }
121
122 return failure();
123}
124
125//----------------------------------------------------------------------------//
126// FPowIOp/IPowIOp strength reduction.
127//----------------------------------------------------------------------------//
128
129namespace {
130template <typename PowIOpTy, typename DivOpTy, typename MulOpTy>
131struct PowIStrengthReduction : public OpRewritePattern<PowIOpTy> {
132
133 unsigned exponentThreshold;
134
135public:
136 PowIStrengthReduction(MLIRContext *context, unsigned exponentThreshold = 3,
137 PatternBenefit benefit = 1,
138 ArrayRef<StringRef> generatedNames = {})
139 : OpRewritePattern<PowIOpTy>(context, benefit, generatedNames),
140 exponentThreshold(exponentThreshold) {}
141
142 LogicalResult matchAndRewrite(PowIOpTy op,
143 PatternRewriter &rewriter) const final;
144};
145} // namespace
146
147template <typename PowIOpTy, typename DivOpTy, typename MulOpTy>
148LogicalResult
149PowIStrengthReduction<PowIOpTy, DivOpTy, MulOpTy>::matchAndRewrite(
150 PowIOpTy op, PatternRewriter &rewriter) const {
151 Location loc = op.getLoc();
152 Value base = op.getLhs();
153
154 IntegerAttr scalarExponent;
155 DenseIntElementsAttr vectorExponent;
156
157 bool isScalar = matchPattern(op.getRhs(), m_Constant(&scalarExponent));
158 bool isVector = matchPattern(op.getRhs(), m_Constant(&vectorExponent));
159
160 // Simplify cases with known exponent value.
161 int64_t exponentValue = 0;
162 if (isScalar)
163 exponentValue = scalarExponent.getInt();
164 else if (isVector && vectorExponent.isSplat())
165 exponentValue = vectorExponent.getSplatValue<IntegerAttr>().getInt();
166 else
167 return failure();
168
169 // Compute abs(exponent) and check the threshold before creating any IR,
170 // so that returning failure() here does not violate the pattern API contract.
171 bool exponentIsNegative = false;
172 if (exponentValue < 0) {
173 exponentIsNegative = true;
174 exponentValue *= -1;
175 }
176
177 // Bail out if `abs(exponent)` exceeds the threshold (exponent==0 is free).
178 if (exponentValue != 0 && exponentValue > exponentThreshold)
179 return failure();
180
181 // Maybe broadcasts scalar value into vector type compatible with `op`.
182 auto bcast = [&loc, &op, &rewriter](Value value) -> Value {
183 if (auto vec = dyn_cast<VectorType>(op.getType()))
184 return vector::BroadcastOp::create(rewriter, loc, vec, value);
185 return value;
186 };
187
188 Value one;
189 Type opType = getElementTypeOrSelf(op.getType());
190 if constexpr (std::is_same_v<PowIOpTy, math::FPowIOp>) {
191 one = arith::ConstantOp::create(rewriter, loc,
192 rewriter.getFloatAttr(opType, 1.0));
193 } else if constexpr (std::is_same_v<PowIOpTy, complex::PowiOp>) {
194 auto complexTy = cast<ComplexType>(opType);
195 Type elementType = complexTy.getElementType();
196 auto realPart = rewriter.getFloatAttr(elementType, 1.0);
197 auto imagPart = rewriter.getFloatAttr(elementType, 0.0);
198 one = complex::ConstantOp::create(
199 rewriter, loc, complexTy, rewriter.getArrayAttr({realPart, imagPart}));
200 } else {
201 one = arith::ConstantOp::create(rewriter, loc,
202 rewriter.getIntegerAttr(opType, 1));
203 }
204
205 // Replace `[fi]powi(x, 0)` with `1`.
206 if (exponentValue == 0) {
207 rewriter.replaceOp(op, bcast(one));
208 return success();
209 }
210
211 Value result = base;
212 // Transform to naive sequence of multiplications:
213 // * For positive exponent case replace:
214 // `[fi]powi(x, positive_exponent)`
215 // with:
216 // x * x * x * ...
217 // * For negative exponent case replace:
218 // `[fi]powi(x, negative_exponent)`
219 // with:
220 // (1 / x) * (1 / x) * (1 / x) * ...
221 auto buildMul = [&](Value lhs, Value rhs) {
222 if constexpr (std::is_same_v<PowIOpTy, complex::PowiOp>)
223 return MulOpTy::create(rewriter, loc, op.getType(), lhs, rhs,
224 op.getFastmathAttr());
225 else
226 return MulOpTy::create(rewriter, loc, lhs, rhs);
227 };
228 for (unsigned i = 1; i < exponentValue; ++i)
229 result = buildMul(result, base);
230
231 // Inverse the base for negative exponent, i.e. for
232 // `[fi]powi(x, negative_exponent)` set `x` to `1 / x`.
233 if (exponentIsNegative) {
234 if constexpr (std::is_same_v<PowIOpTy, complex::PowiOp>)
235 result = DivOpTy::create(rewriter, loc, op.getType(), bcast(one), result,
236 op.getFastmathAttr());
237 else
238 result = DivOpTy::create(rewriter, loc, bcast(one), result);
239 }
240
241 rewriter.replaceOp(op, result);
242 return success();
243}
244
245//----------------------------------------------------------------------------//
246// ExpOp/Exp2Op quotient strength reduction.
247//----------------------------------------------------------------------------//
248
249namespace {
250/// Replaces `exp(a) / exp(b)` with `exp(a - b)`, and likewise for `exp2`,
251/// trading a division and an exponential for a subtraction.
252template <typename ExpOpTy>
253struct ExpQuotientStrengthReduction : public OpRewritePattern<arith::DivFOp> {
254public:
256
257 LogicalResult matchAndRewrite(arith::DivFOp op,
258 PatternRewriter &rewriter) const final {
259 auto numerator = op.getLhs().getDefiningOp<ExpOpTy>();
260 auto denominator = op.getRhs().getDefiningOp<ExpOpTy>();
261 if (!numerator || !denominator)
262 return failure();
263
264 // The rewrite is only valid when the division may be turned into a
265 // reciprocal multiplication and then reassociated with the exponentials:
266 // exp(a) / exp(b) --> exp(a) * exp(-b) --> exp(a + -b)
267 // This mirrors LLVM's InstCombine, which reaches the same result with
268 // `arcp` for the first step and `reassoc` for the second. Note that the
269 // rewrite also changes the overflow behaviour: for a large `a == b` the
270 // original expression is `inf / inf`, i.e. NaN, while the folded one is
271 // `exp(0.0)`, i.e. 1.0.
272 arith::FastMathFlags fmf = op.getFastmath();
273 if (!bitEnumContainsAll(fmf, arith::FastMathFlags::arcp |
274 arith::FastMathFlags::reassoc))
275 return failure();
276
277 // The rewrite introduces a new exponential, so it is only profitable if at
278 // least one of the two it feeds on dies with the division; the exponential
279 // count then never grows while a division is traded for a subtraction.
280 // This is the fused equivalent of the `isOnlyUserOfAnyOperand()` check
281 // LLVM's InstCombine applies to `exp(X) * exp(Y) --> exp(X + Y)`.
282 Operation *divOp = op;
283 auto diesWithDivision = [divOp](Operation *exp) {
284 return llvm::all_of(exp->getUsers(),
285 [divOp](Operation *user) { return user == divOp; });
286 };
287 if (!diesWithDivision(numerator) && !diesWithDivision(denominator))
288 return failure();
289
290 // `nnan` and `ninf` are assumptions about the values an operation sees, and
291 // neither new operation sees the values its source did, so they cannot be
292 // carried over:
293 // - the subtraction consumes the exponents instead of the exponentials.
294 // `ninf` holds for `exp(-inf) / exp(0.0)`, i.e. `0.0 / 1.0`, while the
295 // subtraction `-inf - 0.0` is infinite.
296 // - the new exponential may overflow where neither of the old ones does.
297 // For f32 `exp(80.0)` and `exp(-80.0)` are both finite while
298 // `exp(80.0 - -80.0)` is not.
299 // All remaining flags only license *how* a value may be computed, so they
300 // stay. Derive them separately for the two new operations.
301 constexpr arith::FastMathFlags valueAssumptions =
302 arith::FastMathFlags::nnan | arith::FastMathFlags::ninf;
303
304 // The subtraction takes the place of the division.
305 arith::FastMathFlags subFmf = bitEnumClear(fmf, valueAssumptions);
306
307 // The new exponential may not be given a weaker accuracy contract than the
308 // ones it replaces, so it only keeps the flags common to both of them.
309 arith::FastMathFlags expFmf = bitEnumClear(
310 numerator.getFastmath() & denominator.getFastmath(), valueAssumptions);
311
312 Value exponent =
313 arith::SubFOp::create(rewriter, op.getLoc(), numerator.getOperand(),
314 denominator.getOperand(), subFmf);
315 rewriter.replaceOpWithNewOp<ExpOpTy>(op, exponent, expFmf);
316 return success();
317 }
318};
319} // namespace
320
321//----------------------------------------------------------------------------//
322
324 RewritePatternSet &patterns) {
325 patterns.add<
326 PowFStrengthReduction,
327 PowIStrengthReduction<math::IPowIOp, arith::DivSIOp, arith::MulIOp>,
328 PowIStrengthReduction<math::FPowIOp, arith::DivFOp, arith::MulFOp>,
329 PowIStrengthReduction<complex::PowiOp, complex::DivOp, complex::MulOp>>(
330 patterns.getContext(), /*exponentThreshold=*/8);
331 patterns.add<ExpQuotientStrengthReduction<math::ExpOp>,
332 ExpQuotientStrengthReduction<math::Exp2Op>>(
333 patterns.getContext());
334}
return success()
lhs
IntegerAttr getIntegerAttr(Type type, int64_t value)
Definition Builders.cpp:237
FloatAttr getFloatAttr(Type type, double value)
Definition Builders.cpp:263
ArrayAttr getArrayAttr(ArrayRef< Attribute > value)
Definition Builders.cpp:275
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
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...
OpTy replaceOpWithNewOp(Operation *op, Args &&...args)
Replace the results of the given (original) op with a new op that is created without verification (re...
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
void populateMathAlgebraicSimplificationPatterns(RewritePatternSet &patterns)
Type getElementTypeOrSelf(Type type)
Return the element type or return the type itself.
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...
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...