-
Notifications
You must be signed in to change notification settings - Fork 15.2k
[ConstantFPRange] Add support for add/sub #162962
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
dtcxzyw
wants to merge
4
commits into
llvm:main
Choose a base branch
from
dtcxzyw:cfr-addsub
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@llvm/pr-subscribers-llvm-ir Author: Yingwei Zheng (dtcxzyw) ChangesThis patch adds support for fadd/fsub operations. I only tested this patch with some special ranges because the exhaustive check is too expensive. Full diff: https://github.com/llvm/llvm-project/pull/162962.diff 3 Files Affected:
diff --git a/llvm/include/llvm/IR/ConstantFPRange.h b/llvm/include/llvm/IR/ConstantFPRange.h
index d47f6c02c883d..39dc7c100d6f7 100644
--- a/llvm/include/llvm/IR/ConstantFPRange.h
+++ b/llvm/include/llvm/IR/ConstantFPRange.h
@@ -222,6 +222,14 @@ class [[nodiscard]] ConstantFPRange {
LLVM_ABI ConstantFPRange
cast(const fltSemantics &DstSem,
APFloat::roundingMode RM = APFloat::rmNearestTiesToEven) const;
+
+ /// Return a new range representing the possible values resulting
+ /// from an addition of a value in this range and a value in \p Other.
+ LLVM_ABI ConstantFPRange add(const ConstantFPRange &Other) const;
+
+ /// Return a new range representing the possible values resulting
+ /// from a subtraction of a value in this range and a value in \p Other.
+ LLVM_ABI ConstantFPRange sub(const ConstantFPRange &Other) const;
};
inline raw_ostream &operator<<(raw_ostream &OS, const ConstantFPRange &CR) {
diff --git a/llvm/lib/IR/ConstantFPRange.cpp b/llvm/lib/IR/ConstantFPRange.cpp
index 070e833f4d1c0..51d2e215c980c 100644
--- a/llvm/lib/IR/ConstantFPRange.cpp
+++ b/llvm/lib/IR/ConstantFPRange.cpp
@@ -414,15 +414,31 @@ ConstantFPRange ConstantFPRange::negate() const {
return ConstantFPRange(-Upper, -Lower, MayBeQNaN, MayBeSNaN);
}
+/// Return true if the finite part is not empty after removing infinities.
+static bool removeInf(APFloat &Lower, APFloat &Upper, bool &HasPosInf,
+ bool &HasNegInf) {
+ assert(strictCompare(Lower, Upper) != APFloat::cmpGreaterThan &&
+ "Non-NaN part is empty.");
+ auto &Sem = Lower.getSemantics();
+ if (Lower.isNegInfinity()) {
+ Lower = APFloat::getLargest(Sem, /*Negative=*/true);
+ HasNegInf = true;
+ }
+ if (Upper.isPosInfinity()) {
+ Upper = APFloat::getLargest(Sem, /*Negative=*/false);
+ HasPosInf = true;
+ }
+ return strictCompare(Lower, Upper) != APFloat::cmpGreaterThan;
+}
+
ConstantFPRange ConstantFPRange::getWithoutInf() const {
if (isNaNOnly())
return *this;
APFloat NewLower = Lower;
APFloat NewUpper = Upper;
- if (Lower.isNegInfinity())
- NewLower = APFloat::getLargest(getSemantics(), /*Negative=*/true);
- if (Upper.isPosInfinity())
- NewUpper = APFloat::getLargest(getSemantics(), /*Negative=*/false);
+ bool UnusedFlag;
+ removeInf(NewLower, NewUpper, /*HasPosInf=*/UnusedFlag,
+ /*HasNegInf=*/UnusedFlag);
canonicalizeRange(NewLower, NewUpper);
return ConstantFPRange(std::move(NewLower), std::move(NewUpper), MayBeQNaN,
MayBeSNaN);
@@ -444,3 +460,49 @@ ConstantFPRange ConstantFPRange::cast(const fltSemantics &DstSem,
/*MayBeQNaNVal=*/MayBeQNaN || MayBeSNaN,
/*MayBeSNaNVal=*/false);
}
+
+ConstantFPRange ConstantFPRange::add(const ConstantFPRange &Other) const {
+ bool ResMayBeQNaN = ((MayBeQNaN || MayBeSNaN) && !Other.isEmptySet()) ||
+ ((Other.MayBeQNaN || Other.MayBeSNaN) && !isEmptySet());
+ if (isNaNOnly() || Other.isNaNOnly())
+ return getNaNOnly(getSemantics(), /*MayBeQNaN=*/ResMayBeQNaN,
+ /*MayBeSNaN=*/false);
+ bool LHSHasNegInf = false, LHSHasPosInf = false;
+ APFloat LHSLower = Lower, LHSUpper = Upper;
+ bool LHSFiniteIsNonEmpty =
+ removeInf(LHSLower, LHSUpper, LHSHasPosInf, LHSHasNegInf);
+ bool RHSHasNegInf = false, RHSHasPosInf = false;
+ APFloat RHSLower = Other.Lower, RHSUpper = Other.Upper;
+ bool RHSFiniteIsNonEmpty =
+ removeInf(RHSLower, RHSUpper, RHSHasPosInf, RHSHasNegInf);
+ // -inf + +inf = QNaN
+ ResMayBeQNaN |=
+ (LHSHasNegInf && RHSHasPosInf) || (LHSHasPosInf && RHSHasNegInf);
+ // +inf + finite/+inf = +inf, -inf + finite/-inf = -inf
+ bool HasNegInf = (LHSHasNegInf && (RHSFiniteIsNonEmpty || RHSHasNegInf)) ||
+ (RHSHasNegInf && (LHSFiniteIsNonEmpty || LHSHasNegInf));
+ bool HasPosInf = (LHSHasPosInf && (RHSFiniteIsNonEmpty || RHSHasPosInf)) ||
+ (RHSHasPosInf && (LHSFiniteIsNonEmpty || LHSHasPosInf));
+ if (LHSFiniteIsNonEmpty && RHSFiniteIsNonEmpty) {
+ APFloat NewLower =
+ HasNegInf ? APFloat::getInf(LHSLower.getSemantics(), /*Negative=*/true)
+ : LHSLower + RHSLower;
+ APFloat NewUpper =
+ HasPosInf ? APFloat::getInf(LHSUpper.getSemantics(), /*Negative=*/false)
+ : LHSUpper + RHSUpper;
+ return ConstantFPRange(NewLower, NewUpper, ResMayBeQNaN,
+ /*MayBeSNaN=*/false);
+ }
+ // If both HasNegInf and HasPosInf are false, the non-NaN part is empty.
+ // We just return the canonical form [+inf, -inf] for the empty non-NaN set.
+ return ConstantFPRange(
+ APFloat::getInf(Lower.getSemantics(), /*Negative=*/HasNegInf),
+ APFloat::getInf(Upper.getSemantics(), /*Negative=*/!HasPosInf),
+ ResMayBeQNaN,
+ /*MayBeSNaN=*/false);
+}
+
+ConstantFPRange ConstantFPRange::sub(const ConstantFPRange &Other) const {
+ // fsub X, Y = fadd X, (fneg Y)
+ return add(Other.negate());
+}
diff --git a/llvm/unittests/IR/ConstantFPRangeTest.cpp b/llvm/unittests/IR/ConstantFPRangeTest.cpp
index 58a65b9a96ab8..cf9b31cf88480 100644
--- a/llvm/unittests/IR/ConstantFPRangeTest.cpp
+++ b/llvm/unittests/IR/ConstantFPRangeTest.cpp
@@ -22,6 +22,7 @@ class ConstantFPRangeTest : public ::testing::Test {
static ConstantFPRange Full;
static ConstantFPRange Empty;
static ConstantFPRange Finite;
+ static ConstantFPRange NonNaN;
static ConstantFPRange One;
static ConstantFPRange PosZero;
static ConstantFPRange NegZero;
@@ -44,6 +45,8 @@ ConstantFPRange ConstantFPRangeTest::Empty =
ConstantFPRange::getEmpty(APFloat::IEEEdouble());
ConstantFPRange ConstantFPRangeTest::Finite =
ConstantFPRange::getFinite(APFloat::IEEEdouble());
+ConstantFPRange ConstantFPRangeTest::NonNaN =
+ ConstantFPRange::getNonNaN(APFloat::IEEEdouble());
ConstantFPRange ConstantFPRangeTest::One = ConstantFPRange(APFloat(1.0));
ConstantFPRange ConstantFPRangeTest::PosZero = ConstantFPRange(
APFloat::getZero(APFloat::IEEEdouble(), /*Negative=*/false));
@@ -79,15 +82,21 @@ static void strictNext(APFloat &V) {
V.next(/*nextDown=*/false);
}
+enum class SparseLevel {
+ Dense,
+ SpecialValuesWithAllPowerOfTwos,
+ SpecialValuesOnly,
+};
+
template <typename Fn>
-static void EnumerateConstantFPRangesImpl(Fn TestFn, bool Exhaustive,
+static void EnumerateConstantFPRangesImpl(Fn TestFn, SparseLevel Level,
bool MayBeQNaN, bool MayBeSNaN) {
const fltSemantics &Sem = APFloat::Float8E4M3();
APFloat PosInf = APFloat::getInf(Sem, /*Negative=*/false);
APFloat NegInf = APFloat::getInf(Sem, /*Negative=*/true);
TestFn(ConstantFPRange(PosInf, NegInf, MayBeQNaN, MayBeSNaN));
- if (!Exhaustive) {
+ if (Level != SparseLevel::Dense) {
SmallVector<APFloat, 36> Values;
Values.push_back(APFloat::getInf(Sem, /*Negative=*/true));
Values.push_back(APFloat::getLargest(Sem, /*Negative=*/true));
@@ -95,10 +104,13 @@ static void EnumerateConstantFPRangesImpl(Fn TestFn, bool Exhaustive,
unsigned Exponents = APFloat::semanticsMaxExponent(Sem) -
APFloat::semanticsMinExponent(Sem) + 3;
unsigned MantissaBits = APFloat::semanticsPrecision(Sem) - 1;
- // Add -2^(max exponent), -2^(max exponent-1), ..., -2^(min exponent)
- for (unsigned M = Exponents - 2; M != 0; --M)
- Values.push_back(
- APFloat(Sem, APInt(BitWidth, (M + Exponents) << MantissaBits)));
+ if (Level == SparseLevel::SpecialValuesWithAllPowerOfTwos) {
+ // Add -2^(max exponent), -2^(max exponent-1), ..., -2^(min exponent)
+ for (unsigned M = Exponents - 2; M != 0; --M)
+ Values.push_back(
+ APFloat(Sem, APInt(BitWidth, (M + Exponents) << MantissaBits)));
+ }
+ Values.push_back(APFloat::getSmallestNormalized(Sem, /*Negative=*/true));
Values.push_back(APFloat::getSmallest(Sem, /*Negative=*/true));
Values.push_back(APFloat::getZero(Sem, /*Negative=*/true));
size_t E = Values.size();
@@ -127,26 +139,30 @@ static void EnumerateConstantFPRangesImpl(Fn TestFn, bool Exhaustive,
}
template <typename Fn>
-static void EnumerateConstantFPRanges(Fn TestFn, bool Exhaustive) {
- EnumerateConstantFPRangesImpl(TestFn, Exhaustive, /*MayBeQNaN=*/false,
+static void EnumerateConstantFPRanges(Fn TestFn, SparseLevel Level,
+ bool IgnoreSNaNs = false) {
+ EnumerateConstantFPRangesImpl(TestFn, Level, /*MayBeQNaN=*/false,
/*MayBeSNaN=*/false);
- EnumerateConstantFPRangesImpl(TestFn, Exhaustive, /*MayBeQNaN=*/false,
- /*MayBeSNaN=*/true);
- EnumerateConstantFPRangesImpl(TestFn, Exhaustive, /*MayBeQNaN=*/true,
+ EnumerateConstantFPRangesImpl(TestFn, Level, /*MayBeQNaN=*/true,
/*MayBeSNaN=*/false);
- EnumerateConstantFPRangesImpl(TestFn, Exhaustive, /*MayBeQNaN=*/true,
+ if (IgnoreSNaNs)
+ return;
+ EnumerateConstantFPRangesImpl(TestFn, Level, /*MayBeQNaN=*/false,
+ /*MayBeSNaN=*/true);
+ EnumerateConstantFPRangesImpl(TestFn, Level, /*MayBeQNaN=*/true,
/*MayBeSNaN=*/true);
}
template <typename Fn>
static void EnumerateTwoInterestingConstantFPRanges(Fn TestFn,
- bool Exhaustive) {
+ SparseLevel Level) {
EnumerateConstantFPRanges(
[&](const ConstantFPRange &CR1) {
EnumerateConstantFPRanges(
- [&](const ConstantFPRange &CR2) { TestFn(CR1, CR2); }, Exhaustive);
+ [&](const ConstantFPRange &CR2) { TestFn(CR1, CR2); }, Level,
+ /*IgnoreSNaNs=*/true);
},
- Exhaustive);
+ Level, /*IgnoreSNaNs=*/true);
}
template <typename Fn>
@@ -348,16 +364,25 @@ TEST_F(ConstantFPRangeTest, ExhaustivelyEnumerate) {
constexpr unsigned Expected = 4 * ((NNaNValues + 1) * NNaNValues / 2 + 1);
unsigned Count = 0;
EnumerateConstantFPRanges([&](const ConstantFPRange &) { ++Count; },
- /*Exhaustive=*/true);
+ SparseLevel::Dense);
EXPECT_EQ(Expected, Count);
}
TEST_F(ConstantFPRangeTest, Enumerate) {
- constexpr unsigned NNaNValues = 2 * ((1 << 4) - 2 + 4);
+ constexpr unsigned NNaNValues = 2 * ((1 << 4) - 2 + 5);
constexpr unsigned Expected = 4 * ((NNaNValues + 1) * NNaNValues / 2 + 1);
unsigned Count = 0;
EnumerateConstantFPRanges([&](const ConstantFPRange &) { ++Count; },
- /*Exhaustive=*/false);
+ SparseLevel::SpecialValuesWithAllPowerOfTwos);
+ EXPECT_EQ(Expected, Count);
+}
+
+TEST_F(ConstantFPRangeTest, EnumerateWithSpecialValuesOnly) {
+ constexpr unsigned NNaNValues = 2 * 5;
+ constexpr unsigned Expected = 4 * ((NNaNValues + 1) * NNaNValues / 2 + 1);
+ unsigned Count = 0;
+ EnumerateConstantFPRanges([&](const ConstantFPRange &) { ++Count; },
+ SparseLevel::SpecialValuesOnly);
EXPECT_EQ(Expected, Count);
}
@@ -459,7 +484,7 @@ TEST_F(ConstantFPRangeTest, FPClassify) {
EXPECT_EQ(SignBit, CR.getSignBit()) << CR;
EXPECT_EQ(Mask, CR.classify()) << CR;
},
- /*Exhaustive=*/true);
+ SparseLevel::Dense);
#endif
}
@@ -560,7 +585,7 @@ TEST_F(ConstantFPRangeTest, makeAllowedFCmpRegion) {
<< "Suboptimal result for makeAllowedFCmpRegion(" << Pred << ", "
<< CR << ")";
},
- /*Exhaustive=*/false);
+ SparseLevel::SpecialValuesWithAllPowerOfTwos);
}
#endif
}
@@ -671,7 +696,7 @@ TEST_F(ConstantFPRangeTest, makeSatisfyingFCmpRegion) {
<< ", " << CR << ")";
}
},
- /*Exhaustive=*/false);
+ SparseLevel::SpecialValuesWithAllPowerOfTwos);
}
#endif
}
@@ -804,13 +829,13 @@ TEST_F(ConstantFPRangeTest, negate) {
}
TEST_F(ConstantFPRangeTest, getWithout) {
- EXPECT_EQ(Full.getWithoutNaN(), ConstantFPRange::getNonNaN(Sem));
+ EXPECT_EQ(Full.getWithoutNaN(), NonNaN);
EXPECT_EQ(NaN.getWithoutNaN(), Empty);
EXPECT_EQ(NaN.getWithoutInf(), NaN);
EXPECT_EQ(PosInf.getWithoutInf(), Empty);
EXPECT_EQ(NegInf.getWithoutInf(), Empty);
- EXPECT_EQ(ConstantFPRange::getNonNaN(Sem).getWithoutInf(), Finite);
+ EXPECT_EQ(NonNaN.getWithoutInf(), Finite);
EXPECT_EQ(Zero.getWithoutInf(), Zero);
EXPECT_EQ(ConstantFPRange::getNonNaN(APFloat::getInf(Sem, /*Negative=*/true),
APFloat(3.0))
@@ -925,4 +950,119 @@ TEST_F(ConstantFPRangeTest, cast) {
/*IgnoreNaNPayload=*/true);
}
+TEST_F(ConstantFPRangeTest, add) {
+ EXPECT_EQ(Full.add(Full), NonNaN.unionWith(QNaN));
+ EXPECT_EQ(Full.add(Empty), Empty);
+ EXPECT_EQ(Empty.add(Full), Empty);
+ EXPECT_EQ(Empty.add(Empty), Empty);
+ EXPECT_EQ(One.add(One), ConstantFPRange(APFloat(2.0)));
+ EXPECT_EQ(Some.add(Some),
+ ConstantFPRange::getNonNaN(APFloat(-6.0), APFloat(6.0)));
+ EXPECT_EQ(SomePos.add(SomeNeg),
+ ConstantFPRange::getNonNaN(APFloat(-3.0), APFloat(3.0)));
+ EXPECT_EQ(PosInf.add(PosInf), PosInf);
+ EXPECT_EQ(NegInf.add(NegInf), NegInf);
+ EXPECT_EQ(PosInf.add(Finite.unionWith(PosInf)), PosInf);
+ EXPECT_EQ(NegInf.add(Finite.unionWith(NegInf)), NegInf);
+ EXPECT_EQ(PosInf.add(Finite.unionWith(NegInf)), PosInf.unionWith(QNaN));
+ EXPECT_EQ(NegInf.add(Finite.unionWith(PosInf)), NegInf.unionWith(QNaN));
+ EXPECT_EQ(PosInf.add(NegInf), QNaN);
+ EXPECT_EQ(NegInf.add(PosInf), QNaN);
+ EXPECT_EQ(PosZero.add(NegZero), PosZero);
+ EXPECT_EQ(PosZero.add(Zero), PosZero);
+ EXPECT_EQ(NegZero.add(NegZero), NegZero);
+ EXPECT_EQ(NegZero.add(Zero), Zero);
+ EXPECT_EQ(NaN.add(NaN), QNaN);
+ EXPECT_EQ(NaN.add(Finite), QNaN);
+ EXPECT_EQ(NonNaN.unionWith(NaN).add(NonNaN), NonNaN.unionWith(QNaN));
+ EXPECT_EQ(PosInf.unionWith(QNaN).add(PosInf), PosInf.unionWith(QNaN));
+ EXPECT_EQ(PosInf.unionWith(NaN).add(ConstantFPRange(APFloat(24.0))),
+ PosInf.unionWith(QNaN));
+
+#if defined(EXPENSIVE_CHECKS)
+ EnumerateTwoInterestingConstantFPRanges(
+ [](const ConstantFPRange &LHS, const ConstantFPRange &RHS) {
+ ConstantFPRange Res = LHS.add(RHS);
+ ConstantFPRange Expected =
+ ConstantFPRange::getEmpty(LHS.getSemantics());
+ EnumerateValuesInConstantFPRange(
+ LHS,
+ [&](const APFloat &LHSC) {
+ EnumerateValuesInConstantFPRange(
+ RHS,
+ [&](const APFloat &RHSC) {
+ APFloat Sum = LHSC + RHSC;
+ EXPECT_TRUE(Res.contains(Sum))
+ << "Wrong result for " << LHS << " + " << RHS
+ << ". The result " << Res << " should contain " << Sum;
+ if (!Expected.contains(Sum))
+ Expected = Expected.unionWith(ConstantFPRange(Sum));
+ },
+ /*IgnoreNaNPayload=*/true);
+ },
+ /*IgnoreNaNPayload=*/true);
+ EXPECT_EQ(Res, Expected)
+ << "Suboptimal result for " << LHS << " + " << RHS << ". Expected "
+ << Expected << ", but got " << Res;
+ },
+ SparseLevel::SpecialValuesOnly);
+#endif
+}
+
+TEST_F(ConstantFPRangeTest, sub) {
+ EXPECT_EQ(Full.sub(Full), NonNaN.unionWith(QNaN));
+ EXPECT_EQ(Full.sub(Empty), Empty);
+ EXPECT_EQ(Empty.sub(Full), Empty);
+ EXPECT_EQ(Empty.sub(Empty), Empty);
+ EXPECT_EQ(One.sub(One), ConstantFPRange(APFloat(0.0)));
+ EXPECT_EQ(Some.sub(Some),
+ ConstantFPRange::getNonNaN(APFloat(-6.0), APFloat(6.0)));
+ EXPECT_EQ(SomePos.sub(SomeNeg),
+ ConstantFPRange::getNonNaN(APFloat(0.0), APFloat(6.0)));
+ EXPECT_EQ(PosInf.sub(NegInf), PosInf);
+ EXPECT_EQ(NegInf.sub(PosInf), NegInf);
+ EXPECT_EQ(PosInf.sub(Finite.unionWith(NegInf)), PosInf);
+ EXPECT_EQ(NegInf.sub(Finite.unionWith(PosInf)), NegInf);
+ EXPECT_EQ(PosInf.sub(Finite.unionWith(PosInf)), PosInf.unionWith(QNaN));
+ EXPECT_EQ(NegInf.sub(Finite.unionWith(NegInf)), NegInf.unionWith(QNaN));
+ EXPECT_EQ(PosInf.sub(PosInf), QNaN);
+ EXPECT_EQ(NegInf.sub(NegInf), QNaN);
+ EXPECT_EQ(PosZero.sub(NegZero), PosZero);
+ EXPECT_EQ(PosZero.sub(Zero), PosZero);
+ EXPECT_EQ(NegZero.sub(NegZero), PosZero);
+ EXPECT_EQ(NegZero.sub(PosZero), NegZero);
+ EXPECT_EQ(NegZero.sub(Zero), Zero);
+ EXPECT_EQ(NaN.sub(NaN), QNaN);
+ EXPECT_EQ(NaN.add(Finite), QNaN);
+
+#if defined(EXPENSIVE_CHECKS)
+ EnumerateTwoInterestingConstantFPRanges(
+ [](const ConstantFPRange &LHS, const ConstantFPRange &RHS) {
+ ConstantFPRange Res = LHS.sub(RHS);
+ ConstantFPRange Expected =
+ ConstantFPRange::getEmpty(LHS.getSemantics());
+ EnumerateValuesInConstantFPRange(
+ LHS,
+ [&](const APFloat &LHSC) {
+ EnumerateValuesInConstantFPRange(
+ RHS,
+ [&](const APFloat &RHSC) {
+ APFloat Diff = LHSC - RHSC;
+ EXPECT_TRUE(Res.contains(Diff))
+ << "Wrong result for " << LHS << " - " << RHS
+ << ". The result " << Res << " should contain " << Diff;
+ if (!Expected.contains(Diff))
+ Expected = Expected.unionWith(ConstantFPRange(Diff));
+ },
+ /*IgnoreNaNPayload=*/true);
+ },
+ /*IgnoreNaNPayload=*/true);
+ EXPECT_EQ(Res, Expected)
+ << "Suboptimal result for " << LHS << " - " << RHS << ". Expected "
+ << Expected << ", but got " << Res;
+ },
+ SparseLevel::SpecialValuesOnly);
+#endif
+}
+
} // anonymous namespace
|
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This patch adds support for fadd/fsub operations. I only tested this patch with some special ranges because the exhaustive check is too expensive.