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