[llvm] [ConstantFPRange] Add support for add/sub (PR #162962)
Yingwei Zheng via llvm-commits
llvm-commits at lists.llvm.org
Fri Oct 10 20:18:20 PDT 2025
https://github.com/dtcxzyw created https://github.com/llvm/llvm-project/pull/162962
None
>From aa26a6df28763f8b736ae62b9f561c99eb4b5745 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Sat, 11 Oct 2025 10:54:15 +0800
Subject: [PATCH] [ConstantFPRange] Add support for add/sub
---
llvm/include/llvm/IR/ConstantFPRange.h | 8 +
llvm/lib/IR/ConstantFPRange.cpp | 66 ++++++++-
llvm/unittests/IR/ConstantFPRangeTest.cpp | 171 +++++++++++++++++++---
3 files changed, 221 insertions(+), 24 deletions(-)
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..a62b0d36b93a8 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,45 @@ ConstantFPRange ConstantFPRange::cast(const fltSemantics &DstSem,
/*MayBeQNaNVal=*/MayBeQNaN || MayBeSNaN,
/*MayBeSNaNVal=*/false);
}
+
+ConstantFPRange ConstantFPRange::add(const ConstantFPRange &Other) const {
+ bool MayBeQNaN =
+ (MayBeQNaN || MayBeSNaN) && (Other.MayBeQNaN || Other.MayBeSNaN);
+ if (isNaNOnly() || Other.isNaNOnly())
+ return getNaNOnly(getSemantics(), /*MayBeQNaN=*/MayBeQNaN,
+ /*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
+ MayBeQNaN |= (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, MayBeQNaN, /*MayBeSNaN=*/false);
+ }
+ // If both HasNegInf and HasPosInf are true, the non-NaN part is empty.
+ return ConstantFPRange(
+ APFloat::getInf(Lower.getSemantics(), /*Negative=*/HasNegInf),
+ APFloat::getInf(Upper.getSemantics(), /*Negative=*/!HasPosInf), MayBeQNaN,
+ /*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..4eb97564af0e3 100644
--- a/llvm/unittests/IR/ConstantFPRangeTest.cpp
+++ b/llvm/unittests/IR/ConstantFPRangeTest.cpp
@@ -79,15 +79,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 +101,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 +136,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 IgnoreNaNs = false) {
+ EnumerateConstantFPRangesImpl(TestFn, Level, /*MayBeQNaN=*/false,
/*MayBeSNaN=*/false);
- EnumerateConstantFPRangesImpl(TestFn, Exhaustive, /*MayBeQNaN=*/false,
+ if (IgnoreNaNs)
+ return;
+ EnumerateConstantFPRangesImpl(TestFn, Level, /*MayBeQNaN=*/false,
/*MayBeSNaN=*/true);
- EnumerateConstantFPRangesImpl(TestFn, Exhaustive, /*MayBeQNaN=*/true,
+ EnumerateConstantFPRangesImpl(TestFn, Level, /*MayBeQNaN=*/true,
/*MayBeSNaN=*/false);
- EnumerateConstantFPRangesImpl(TestFn, Exhaustive, /*MayBeQNaN=*/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,
+ /*IgnoreNaNs=*/true);
},
- Exhaustive);
+ Level, /*IgnoreNaNs=*/true);
}
template <typename Fn>
@@ -348,16 +361,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; },
+ 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; },
- /*Exhaustive=*/false);
+ SparseLevel::SpecialValuesOnly);
EXPECT_EQ(Expected, Count);
}
@@ -459,7 +481,7 @@ TEST_F(ConstantFPRangeTest, FPClassify) {
EXPECT_EQ(SignBit, CR.getSignBit()) << CR;
EXPECT_EQ(Mask, CR.classify()) << CR;
},
- /*Exhaustive=*/true);
+ SparseLevel::Dense);
#endif
}
@@ -560,7 +582,7 @@ TEST_F(ConstantFPRangeTest, makeAllowedFCmpRegion) {
<< "Suboptimal result for makeAllowedFCmpRegion(" << Pred << ", "
<< CR << ")";
},
- /*Exhaustive=*/false);
+ SparseLevel::SpecialValuesWithAllPowerOfTwos);
}
#endif
}
@@ -671,7 +693,7 @@ TEST_F(ConstantFPRangeTest, makeSatisfyingFCmpRegion) {
<< ", " << CR << ")";
}
},
- /*Exhaustive=*/false);
+ SparseLevel::SpecialValuesWithAllPowerOfTwos);
}
#endif
}
@@ -925,4 +947,113 @@ TEST_F(ConstantFPRangeTest, cast) {
/*IgnoreNaNPayload=*/true);
}
+TEST_F(ConstantFPRangeTest, add) {
+ EXPECT_EQ(Full.add(Full), ConstantFPRange::getNonNaN(Sem).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);
+
+#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), ConstantFPRange::getNonNaN(Sem).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);
+
+#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
More information about the llvm-commits
mailing list