blob: eecb40b2689b15fd514097a8659d188f0acac7cb [file]
//===- 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());
}