| //===- AlgebraicSimplification.cpp - Simplify algebraic expressions -------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // This file implements rewrites based on the basic rules of algebra | |
| // (Commutativity, associativity, etc...) and strength reductions for math | |
| // operations. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "mlir/Dialect/Arith/IR/Arith.h" | |
| #include "mlir/Dialect/Complex/IR/Complex.h" | |
| #include "mlir/Dialect/Math/IR/Math.h" | |
| #include "mlir/Dialect/Math/Transforms/Passes.h" | |
| #include "mlir/Dialect/Vector/IR/VectorOps.h" | |
| #include "mlir/IR/Builders.h" | |
| #include "mlir/IR/Matchers.h" | |
| #include "mlir/IR/TypeUtilities.h" | |
| #include <climits> | |
| #include <limits> | |
| using namespace mlir; | |
| //----------------------------------------------------------------------------// | |
| // PowFOp strength reduction. | |
| //----------------------------------------------------------------------------// | |
| namespace { | |
| struct PowFStrengthReduction : public OpRewritePattern<math::PowFOp> { | |
| public: | |
| using Base::Base; | |
| LogicalResult matchAndRewrite(math::PowFOp op, | |
| PatternRewriter &rewriter) const final; | |
| }; | |
| } // namespace | |
| LogicalResult | |
| PowFStrengthReduction::matchAndRewrite(math::PowFOp op, | |
| PatternRewriter &rewriter) const { | |
| Location loc = op.getLoc(); | |
| Value x = op.getLhs(); | |
| arith::FastMathFlags fmf = op.getFastmathAttr().getValue(); | |
| FloatAttr scalarExponent; | |
| DenseFPElementsAttr vectorExponent; | |
| bool isScalar = matchPattern(op.getRhs(), m_Constant(&scalarExponent)); | |
| bool isVector = matchPattern(op.getRhs(), m_Constant(&vectorExponent)); | |
| // Returns true if exponent is a constant equal to `value`. | |
| auto isExponentValue = [&](double value) -> bool { | |
| if (isScalar) | |
| return scalarExponent.getValue().isExactlyValue(value); | |
| if (isVector && vectorExponent.isSplat()) | |
| return vectorExponent.getSplatValue<FloatAttr>() | |
| .getValue() | |
| .isExactlyValue(value); | |
| return false; | |
| }; | |
| // Maybe broadcasts scalar value into vector type compatible with `op`. | |
| auto bcast = [&](Value value) -> Value { | |
| if (auto vec = dyn_cast<VectorType>(op.getType())) | |
| return vector::BroadcastOp::create(rewriter, loc, vec, value); | |
| return value; | |
| }; | |
| // Replace `pow(x, 1.0)` with `x`. | |
| if (isExponentValue(1.0)) { | |
| rewriter.replaceOp(op, x); | |
| return success(); | |
| } | |
| // Replace `pow(x, 2.0)` with `x * x`. | |
| if (isExponentValue(2.0)) { | |
| rewriter.replaceOpWithNewOp<arith::MulFOp>(op, x, x, fmf); | |
| return success(); | |
| } | |
| // Replace `pow(x, 3.0)` with `x * x * x`. | |
| if (isExponentValue(3.0)) { | |
| Value square = arith::MulFOp::create(rewriter, loc, x, x, fmf); | |
| rewriter.replaceOpWithNewOp<arith::MulFOp>(op, x, square, fmf); | |
| return success(); | |
| } | |
| // Replace `pow(x, -1.0)` with `1.0 / x`. | |
| if (isExponentValue(-1.0)) { | |
| Value one = arith::ConstantOp::create( | |
| rewriter, loc, | |
| rewriter.getFloatAttr(getElementTypeOrSelf(op.getType()), 1.0)); | |
| rewriter.replaceOpWithNewOp<arith::DivFOp>(op, bcast(one), x, fmf); | |
| return success(); | |
| } | |
| // Replace `pow(x, 0.5)` with `sqrt(x)`. | |
| if (isExponentValue(0.5)) { | |
| rewriter.replaceOpWithNewOp<math::SqrtOp>(op, x, fmf); | |
| return success(); | |
| } | |
| // Replace `pow(x, -0.5)` with `rsqrt(x)`. | |
| if (isExponentValue(-0.5)) { | |
| rewriter.replaceOpWithNewOp<math::RsqrtOp>(op, x, fmf); | |
| return success(); | |
| } | |
| // Replace `pow(x, 0.75)` with `sqrt(sqrt(x)) * sqrt(x)`. | |
| if (isExponentValue(0.75)) { | |
| Value powHalf = math::SqrtOp::create(rewriter, loc, x, fmf); | |
| Value powQuarter = math::SqrtOp::create(rewriter, loc, powHalf, fmf); | |
| rewriter.replaceOpWithNewOp<arith::MulFOp>(op, powHalf, powQuarter, fmf); | |
| return success(); | |
| } | |
| return failure(); | |
| } | |
| //----------------------------------------------------------------------------// | |
| // FPowIOp/IPowIOp strength reduction. | |
| //----------------------------------------------------------------------------// | |
| namespace { | |
| template <typename PowIOpTy, typename DivOpTy, typename MulOpTy> | |
| struct PowIStrengthReduction : public OpRewritePattern<PowIOpTy> { | |
| unsigned exponentThreshold; | |
| public: | |
| PowIStrengthReduction(MLIRContext *context, unsigned exponentThreshold = 3, | |
| PatternBenefit benefit = 1, | |
| ArrayRef<StringRef> generatedNames = {}) | |
| : OpRewritePattern<PowIOpTy>(context, benefit, generatedNames), | |
| exponentThreshold(exponentThreshold) {} | |
| LogicalResult matchAndRewrite(PowIOpTy op, | |
| PatternRewriter &rewriter) const final; | |
| }; | |
| } // namespace | |
| template <typename PowIOpTy, typename DivOpTy, typename MulOpTy> | |
| LogicalResult | |
| PowIStrengthReduction<PowIOpTy, DivOpTy, MulOpTy>::matchAndRewrite( | |
| PowIOpTy op, PatternRewriter &rewriter) const { | |
| Location loc = op.getLoc(); | |
| Value base = op.getLhs(); | |
| IntegerAttr scalarExponent; | |
| DenseIntElementsAttr vectorExponent; | |
| bool isScalar = matchPattern(op.getRhs(), m_Constant(&scalarExponent)); | |
| bool isVector = matchPattern(op.getRhs(), m_Constant(&vectorExponent)); | |
| // Simplify cases with known exponent value. | |
| int64_t exponentValue = 0; | |
| if (isScalar) | |
| exponentValue = scalarExponent.getInt(); | |
| else if (isVector && vectorExponent.isSplat()) | |
| exponentValue = vectorExponent.getSplatValue<IntegerAttr>().getInt(); | |
| else | |
| return failure(); | |
| // Compute abs(exponent) and check the threshold before creating any IR, | |
| // so that returning failure() here does not violate the pattern API contract. | |
| bool exponentIsNegative = false; | |
| if (exponentValue < 0) { | |
| exponentIsNegative = true; | |
| // Integer division by zero is UB (unlike float/complex, where the | |
| // division is defined and yields Inf). base isn't necessarily constant | |
| // here, so defer negative-exponent IPowIOp to MathToFuncs's | |
| // createElementIPowIFunc, which returns 0 for base == 0 without | |
| // dividing. MathToFuncs does not lower index, so index keeps the | |
| // strength reduction below. | |
| if constexpr (std::is_same_v<PowIOpTy, math::IPowIOp>) | |
| if (isa<IntegerType>(getElementTypeOrSelf(op.getType()))) | |
| return failure(); | |
| // INT64_MIN has no positive representation in int64_t, so negating it | |
| // below would overflow. Bail out rather than negate. This is reached by | |
| // FPowIOp, complex::PowiOp and index-typed IPowIOp. | |
| if (exponentValue == std::numeric_limits<int64_t>::min()) | |
| return failure(); | |
| exponentValue *= -1; | |
| } | |
| // Bail out if `abs(exponent)` exceeds the threshold (exponent==0 is free). | |
| if (exponentValue != 0 && exponentValue > exponentThreshold) | |
| return failure(); | |
| // Maybe broadcasts scalar value into vector type compatible with `op`. | |
| auto bcast = [&loc, &op, &rewriter](Value value) -> Value { | |
| if (auto vec = dyn_cast<VectorType>(op.getType())) | |
| return vector::BroadcastOp::create(rewriter, loc, vec, value); | |
| return value; | |
| }; | |
| Value one; | |
| Type opType = getElementTypeOrSelf(op.getType()); | |
| if constexpr (std::is_same_v<PowIOpTy, math::FPowIOp>) { | |
| one = arith::ConstantOp::create(rewriter, loc, | |
| rewriter.getFloatAttr(opType, 1.0)); | |
| } else if constexpr (std::is_same_v<PowIOpTy, complex::PowiOp>) { | |
| auto complexTy = cast<ComplexType>(opType); | |
| Type elementType = complexTy.getElementType(); | |
| auto realPart = rewriter.getFloatAttr(elementType, 1.0); | |
| auto imagPart = rewriter.getFloatAttr(elementType, 0.0); | |
| one = complex::ConstantOp::create( | |
| rewriter, loc, complexTy, rewriter.getArrayAttr({realPart, imagPart})); | |
| } else { | |
| one = arith::ConstantOp::create(rewriter, loc, | |
| rewriter.getIntegerAttr(opType, 1)); | |
| } | |
| // Replace `[fi]powi(x, 0)` with `1`. | |
| if (exponentValue == 0) { | |
| rewriter.replaceOp(op, bcast(one)); | |
| return success(); | |
| } | |
| Value result = base; | |
| // Transform to naive sequence of multiplications: | |
| // * For positive exponent case replace: | |
| // `[fi]powi(x, positive_exponent)` | |
| // with: | |
| // x * x * x * ... | |
| // * For negative exponent case replace: | |
| // `[fi]powi(x, negative_exponent)` | |
| // with: | |
| // (1 / x) * (1 / x) * (1 / x) * ... | |
| auto buildMul = [&](Value lhs, Value rhs) { | |
| if constexpr (std::is_same_v<PowIOpTy, complex::PowiOp>) | |
| return MulOpTy::create(rewriter, loc, op.getType(), lhs, rhs, | |
| op.getFastmathAttr()); | |
| else | |
| return MulOpTy::create(rewriter, loc, lhs, rhs); | |
| }; | |
| for (unsigned i = 1; i < exponentValue; ++i) | |
| result = buildMul(result, base); | |
| // Inverse the base for negative exponent, i.e. for | |
| // `[fi]powi(x, negative_exponent)` set `x` to `1 / x`. | |
| if (exponentIsNegative) { | |
| if constexpr (std::is_same_v<PowIOpTy, complex::PowiOp>) | |
| result = DivOpTy::create(rewriter, loc, op.getType(), bcast(one), result, | |
| op.getFastmathAttr()); | |
| else | |
| result = DivOpTy::create(rewriter, loc, bcast(one), result); | |
| } | |
| rewriter.replaceOp(op, result); | |
| return success(); | |
| } | |
| //----------------------------------------------------------------------------// | |
| // ExpOp/Exp2Op quotient strength reduction. | |
| //----------------------------------------------------------------------------// | |
| namespace { | |
| /// Replaces `exp(a) / exp(b)` with `exp(a - b)`, and likewise for `exp2`, | |
| /// trading a division and an exponential for a subtraction. | |
| template <typename ExpOpTy> | |
| struct ExpQuotientStrengthReduction : public OpRewritePattern<arith::DivFOp> { | |
| public: | |
| using Base::Base; | |
| LogicalResult matchAndRewrite(arith::DivFOp op, | |
| PatternRewriter &rewriter) const final { | |
| auto numerator = op.getLhs().getDefiningOp<ExpOpTy>(); | |
| auto denominator = op.getRhs().getDefiningOp<ExpOpTy>(); | |
| if (!numerator || !denominator) | |
| return failure(); | |
| // The rewrite is only valid when the division may be turned into a | |
| // reciprocal multiplication and then reassociated with the exponentials: | |
| // exp(a) / exp(b) --> exp(a) * exp(-b) --> exp(a + -b) | |
| // This mirrors LLVM's InstCombine, which reaches the same result with | |
| // `arcp` for the first step and `reassoc` for the second. Note that the | |
| // rewrite also changes the overflow behaviour: for a large `a == b` the | |
| // original expression is `inf / inf`, i.e. NaN, while the folded one is | |
| // `exp(0.0)`, i.e. 1.0. | |
| arith::FastMathFlags fmf = op.getFastmath(); | |
| if (!bitEnumContainsAll(fmf, arith::FastMathFlags::arcp | | |
| arith::FastMathFlags::reassoc)) | |
| return failure(); | |
| // The rewrite introduces a new exponential, so it is only profitable if at | |
| // least one of the two it feeds on dies with the division; the exponential | |
| // count then never grows while a division is traded for a subtraction. | |
| // This is the fused equivalent of the `isOnlyUserOfAnyOperand()` check | |
| // LLVM's InstCombine applies to `exp(X) * exp(Y) --> exp(X + Y)`. | |
| Operation *divOp = op; | |
| auto diesWithDivision = [divOp](Operation *exp) { | |
| return llvm::all_of(exp->getUsers(), | |
| [divOp](Operation *user) { return user == divOp; }); | |
| }; | |
| if (!diesWithDivision(numerator) && !diesWithDivision(denominator)) | |
| return failure(); | |
| // `nnan` and `ninf` are assumptions about the values an operation sees, and | |
| // neither new operation sees the values its source did, so they cannot be | |
| // carried over: | |
| // - the subtraction consumes the exponents instead of the exponentials. | |
| // `ninf` holds for `exp(-inf) / exp(0.0)`, i.e. `0.0 / 1.0`, while the | |
| // subtraction `-inf - 0.0` is infinite. | |
| // - the new exponential may overflow where neither of the old ones does. | |
| // For f32 `exp(80.0)` and `exp(-80.0)` are both finite while | |
| // `exp(80.0 - -80.0)` is not. | |
| // All remaining flags only license *how* a value may be computed, so they | |
| // stay. Derive them separately for the two new operations. | |
| constexpr arith::FastMathFlags valueAssumptions = | |
| arith::FastMathFlags::nnan | arith::FastMathFlags::ninf; | |
| // The subtraction takes the place of the division. | |
| arith::FastMathFlags subFmf = bitEnumClear(fmf, valueAssumptions); | |
| // The new exponential may not be given a weaker accuracy contract than the | |
| // ones it replaces, so it only keeps the flags common to both of them. | |
| arith::FastMathFlags expFmf = bitEnumClear( | |
| numerator.getFastmath() & denominator.getFastmath(), valueAssumptions); | |
| Value exponent = | |
| arith::SubFOp::create(rewriter, op.getLoc(), numerator.getOperand(), | |
| denominator.getOperand(), subFmf); | |
| rewriter.replaceOpWithNewOp<ExpOpTy>(op, exponent, expFmf); | |
| return success(); | |
| } | |
| }; | |
| } // namespace | |
| //----------------------------------------------------------------------------// | |
| void mlir::populateMathAlgebraicSimplificationPatterns( | |
| RewritePatternSet &patterns) { | |
| patterns.add< | |
| PowFStrengthReduction, | |
| PowIStrengthReduction<math::IPowIOp, arith::DivSIOp, arith::MulIOp>, | |
| PowIStrengthReduction<math::FPowIOp, arith::DivFOp, arith::MulFOp>, | |
| PowIStrengthReduction<complex::PowiOp, complex::DivOp, complex::MulOp>>( | |
| patterns.getContext(), /*exponentThreshold=*/8); | |
| patterns.add<ExpQuotientStrengthReduction<math::ExpOp>, | |
| ExpQuotientStrengthReduction<math::Exp2Op>>( | |
| patterns.getContext()); | |
| } |