Skip to content

Conversation

dtcxzyw
Copy link
Member

@dtcxzyw dtcxzyw commented Oct 11, 2025

This patch adds support for fadd/fsub operations. I only tested this patch with some special ranges because the exhaustive check is too expensive.

@dtcxzyw dtcxzyw added the floating-point Floating-point math label Oct 11, 2025
@dtcxzyw dtcxzyw marked this pull request as ready for review October 11, 2025 18:00
@llvmbot
Copy link
Member

llvmbot commented Oct 11, 2025

@llvm/pr-subscribers-llvm-ir

Author: Yingwei Zheng (dtcxzyw)

Changes

This 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:

  • (modified) llvm/include/llvm/IR/ConstantFPRange.h (+8)
  • (modified) llvm/lib/IR/ConstantFPRange.cpp (+66-4)
  • (modified) llvm/unittests/IR/ConstantFPRangeTest.cpp (+163-23)
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

Labels

floating-point Floating-point math llvm:ir

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants