https://gcc.gnu.org/g:255207b2ce6168cd9620980d448dc23e2488de7f

commit r17-3056-g255207b2ce6168cd9620980d448dc23e2488de7f
Author: Jakub Jelinek <[email protected]>
Date:   Fri Aug 7 09:45:04 2026 +0200

    range-op-float, value-range: Fix up float_widen_lhs_range [PR126641]
    
    The recent change to make float_widen_lhs_range work on multiple
    subrange pairs broke e.g. the following testcase or various other
    things, set_significand is often called with SIGNIFICANT_BITS
    as the second argument and overflows the storage (in my case 3 elt
    array, if 192 is passed that stores [3]).
    
    The culprit is in the PR109008 optimization, float_widen_lhs_range
    produces something that isn't really a valid range for the corresponding
    type, it is the passed in valid range slightly extended if needed.
    The extension can be +-1ulp from the largest representable finite values
    (negative/positive) or +-0.5ulp in between some representable finite values,
    or in some cases +-1ulp.
    
    The reason for this hack is that extending by full representable 1ulp
    in all cases is simply too much, especially if that extension means going
    from the largest representable finite value to +-infinity.  The function
    is used in various frange reverse ops and the intent is that this adjusted
    range doesn't really survive after the reverse operation handler, we should
    call some frange_arithmetic etc. call and that should handle even the not
    exactly representable in type (but representable in the gcc internal format,
    160 bit precision, wide range of exponents) values, we do range_arithmetic
    which produces something in the gcc internal format and then round it to
    the mode of the desired type.
    
    Now, the r17-2929 change broke this, because it uses union_ ->
    frange_fusible_p -> frange_nextafter on those not exactly representable
    values and frange_nextafter -> real_nextafter quite understandably doesn't
    work properly on those values, to find the next after value it assumes the
    input is representable.
    
    The following patch fixes this by changing float_widen_lhs_range into
    a frange method, where users instead of doing
    wlhs = float_widen_lhs_range (type, lhs);
    do
    wlhs = lhs; wlhs.widen (type);
    and the new method widens the range directly on m_pairs elements, doesn't
    use set/union_.
    Because *this should be a canonicalized range, I think the widening 
shouldn't
    change the range canonicalization properties (like m_kind etc.), the
    extension is always just a tiny bit, shouldn't jump from finite to infinite
    or to NaN etc.  The only thing that can happen is that e.g. with
    -frounding-math if the original range has 1ulp hole in betweenn pairs (say
    ~[0.5, 0.5] range that the widening of 0.5-1ulp could result in 0.5 and
    0.5+1ulp on the other bound also to 0.5 could result in the same value,
    so the code just merges pairs in such rare cases.
    And we need to avoid copying the range, e.g. operator= invokes range
    verification which again calls frange_fusible_p -> frange_nextafter.
    
    2026-08-07  Jakub Jelinek  <[email protected]>
    
            PR tree-optimization/126641
            * value-range.h (class frange): Declare widen method.
            * range-op-float.cc (float_widen_bound): Move to value-range.cc.
            (float_widen_lhs_range): Remove.
            (operator_plus::op1_range, operator_minus::op1_range,
            operator_minus::op2_range, operator_mult::op1_range,
            foperator_div::op1_range, foperator_div::op2_range,
            operator_cast::op1_range, range_op_float_tests): Replace
            X = float_widen_lhs_range (T, R) with X = R; X.widen (T).
            * value-range.cc (float_widen_bound): New, moved from
            range-op-float.cc.
            (frange::widen): New method, partially based on
            float_widen_lhs_range, but replace lhs with *this and
            avoid using set/union_ to construct range, instead modify
            m_pair and m_num_ranges directly.
            * real.cc (set_significand_bit, clear_significand_bit,
            test_significand_bit): Add
            gcc_checking_assert (n < SIGNIFICAND_BITS).
            (clear_significand_below): Add
            gcc_checking_assert (n <= SIGNIFICAND_BITS).
    
            * gcc.dg/pr126641.c: New test.
    
    Reviewed-by: Richard Biener <[email protected]>
    Reviewed-by: Aldy Hernandez <[email protected]>

Diff:
---
 gcc/range-op-float.cc           | 95 ++++++++---------------------------------
 gcc/real.cc                     |  4 ++
 gcc/testsuite/gcc.dg/pr126641.c | 12 ++++++
 gcc/value-range.cc              | 86 +++++++++++++++++++++++++++++++++++++
 gcc/value-range.h               |  2 +
 5 files changed, 121 insertions(+), 78 deletions(-)

diff --git a/gcc/range-op-float.cc b/gcc/range-op-float.cc
index b03f1e97449c..14810bd858e7 100644
--- a/gcc/range-op-float.cc
+++ b/gcc/range-op-float.cc
@@ -2366,75 +2366,6 @@ zero_to_inf_range (REAL_VALUE_TYPE &lb, REAL_VALUE_TYPE 
&ub, int signbit_known)
     }
 }
 
-/* Widen a single bound of a sub-range by 1ulp (or 0.5ulp) in the direction of
-   DIR.  */
-
-static REAL_VALUE_TYPE
-float_widen_bound (tree type, const REAL_VALUE_TYPE &bound,
-                  const REAL_VALUE_TYPE &dir)
-{
-  REAL_VALUE_TYPE res = bound;
-  if (!real_isfinite (&bound) && real_isneg (&bound) == real_isneg (&dir))
-    return res;
-  frange_nextafter (TYPE_MODE (type), res, dir);
-  if (real_isinf (&res))
-    {
-      /* For +-DBL_MAX, instead of +-Inf use nexttoward (+-DBL_MAX, +-LDBL_MAX)
-        in a hypothetical wider type with the same mantissa precision but
-        larger exponent range; it is outside of range of double values, but
-        makes it clear it is just one ulp larger rather than infinite amount
-        larger.  */
-      res = real_isneg (&dir) ? dconstm1 : dconst1;
-      SET_REAL_EXP (&res, FLOAT_MODE_FORMAT (TYPE_MODE (type))->emax + 1);
-    }
-  if (!flag_rounding_math
-      && !MODE_COMPOSITE_P (TYPE_MODE (type))
-      && real_isfinite (&bound))
-    {
-      /* If not -frounding-math nor IBM double double, actually widen
-        just by 0.5ulp rather than 1ulp.  */
-      REAL_VALUE_TYPE tem;
-      real_arithmetic (&tem, PLUS_EXPR, &bound, &res);
-      real_arithmetic (&res, RDIV_EXPR, &tem, &dconst2);
-    }
-  return res;
-}
-
-/* Extend the LHS range by 1ulp in each direction.  For op1_range
-   or op2_range of binary operations just computing the inverse
-   operation on ranges isn't sufficient.  Consider e.g.
-   [1., 1.] = op1 + [1., 1.].  op1's range is not [0., 0.], but
-   [-0x1.0p-54, 0x1.0p-53] (when not -frounding-math), any value for
-   which adding 1. to it results in 1. after rounding to nearest.
-   So, for op1_range/op2_range extend the lhs range by 1ulp (or 0.5ulp)
-   in each direction.  See PR109008 for more details.  */
-
-static frange
-float_widen_lhs_range (tree type, const frange &lhs)
-{
-  frange ret = lhs;
-  if (lhs.known_isnan ())
-    return ret;
-  /* Temporarily disable -ffinite-math-only, so that frange::set doesn't
-     reduce the range back to real_min_representable (type) as lower bound
-     or real_max_representable (type) as upper bound.  */
-  bool save_flag_finite_math_only = flag_finite_math_only;
-  flag_finite_math_only = false;
-  ret.set_undefined ();
-  for (unsigned i = 0; i < lhs.num_pairs (); ++i)
-    {
-      REAL_VALUE_TYPE lb = float_widen_bound (type, lhs.lower_bound (i),
-                                             dconstninf);
-      REAL_VALUE_TYPE ub = float_widen_bound (type, lhs.upper_bound (i),
-                                             dconstinf);
-      frange tmp;
-      tmp.set (type, lb, ub, lhs.get_nan_state ());
-      ret.union_ (tmp);
-    }
-  flag_finite_math_only = save_flag_finite_math_only;
-  return ret;
-}
-
 bool
 operator_plus::op1_range (frange &r, tree type, const frange &lhs,
                          const frange &op2, relation_trio) const
@@ -2444,7 +2375,8 @@ operator_plus::op1_range (frange &r, tree type, const 
frange &lhs,
   range_op_handler minus (MINUS_EXPR);
   if (!minus)
     return false;
-  frange wlhs = float_widen_lhs_range (type, lhs);
+  frange wlhs = lhs;
+  wlhs.widen (type);
   return float_binary_op_range_finish (minus.fold_range (r, type, wlhs, op2),
                                       r, type, wlhs);
 }
@@ -2502,7 +2434,8 @@ operator_minus::op1_range (frange &r, tree type,
 {
   if (lhs.undefined_p ())
     return false;
-  frange wlhs = float_widen_lhs_range (type, lhs);
+  frange wlhs = lhs;
+  wlhs.widen (type);
   return float_binary_op_range_finish (
              range_op_handler (PLUS_EXPR).fold_range (r, type, wlhs, op2),
              r, type, wlhs);
@@ -2515,7 +2448,8 @@ operator_minus::op2_range (frange &r, tree type,
 {
   if (lhs.undefined_p ())
     return false;
-  frange wlhs = float_widen_lhs_range (type, lhs);
+  frange wlhs = lhs;
+  wlhs.widen (type);
   return float_binary_op_range_finish (fold_range (r, type, op1, wlhs),
                                       r, type, wlhs);
 }
@@ -2591,7 +2525,8 @@ operator_mult::op1_range (frange &r, tree type,
   range_op_handler rdiv (RDIV_EXPR);
   if (!rdiv)
     return false;
-  frange wlhs = float_widen_lhs_range (type, lhs);
+  frange wlhs = lhs;
+  wlhs.widen (type);
   bool ret = rdiv.fold_range (r, type, wlhs, op2);
   if (ret == false)
     return false;
@@ -2756,7 +2691,8 @@ public:
   {
     if (lhs.undefined_p ())
       return false;
-    frange wlhs = float_widen_lhs_range (type, lhs);
+    frange wlhs = lhs;
+    wlhs.widen (type);
     bool ret = range_op_handler (MULT_EXPR).fold_range (r, type, wlhs, op2);
     if (!ret)
       return ret;
@@ -2788,7 +2724,8 @@ public:
   {
     if (lhs.undefined_p ())
       return false;
-    frange wlhs = float_widen_lhs_range (type, lhs);
+    frange wlhs = lhs;
+    wlhs.widen (type);
     bool ret = fold_range (r, type, op1, wlhs);
     if (!ret)
       return ret;
@@ -3025,7 +2962,8 @@ operator_cast::op1_range (frange &r, tree type, const 
frange &lhs,
   else
     {
       rm = true;
-      wlhs = float_widen_lhs_range (lhs_type, lhs);
+      wlhs = lhs;
+      wlhs.widen (lhs_type);
     }
   auto save_flag_rounding_math = flag_rounding_math;
   flag_rounding_math = rm;
@@ -3273,14 +3211,15 @@ range_op_float_tests ()
   if (HONOR_NANS (float_type_node))
     ASSERT_TRUE (r.maybe_isnan ());
 
-  // float_widen_lhs_range widens each sub-range and keeps the gap between
+  // r.widen widens each sub-range and keeps the gap between
   // them.
   r0 = frange_float ("1.0", "2.0");
   r1 = frange_float ("10.0", "11.0");
   r0.union_ (r1);
   r0.clear_nan ();
   ASSERT_EQ (r0.num_pairs (), 2);
-  r = float_widen_lhs_range (float_type_node, r0);
+  r = r0;
+  r.widen (float_type_node);
   ASSERT_EQ (r.num_pairs (), 2);
   REAL_VALUE_TYPE five;
   real_from_string (&five, "5.0");
diff --git a/gcc/real.cc b/gcc/real.cc
index 29cb6a1edd2b..1dadd2aa6cd5 100644
--- a/gcc/real.cc
+++ b/gcc/real.cc
@@ -399,6 +399,7 @@ cmp_significand_0 (const REAL_VALUE_TYPE *a)
 static inline void
 set_significand_bit (REAL_VALUE_TYPE *r, unsigned int n)
 {
+  gcc_checking_assert (n < SIGNIFICAND_BITS);
   r->sig[n / HOST_BITS_PER_LONG]
     |= (unsigned long)1 << (n % HOST_BITS_PER_LONG);
 }
@@ -408,6 +409,7 @@ set_significand_bit (REAL_VALUE_TYPE *r, unsigned int n)
 static inline void
 clear_significand_bit (REAL_VALUE_TYPE *r, unsigned int n)
 {
+  gcc_checking_assert (n < SIGNIFICAND_BITS);
   r->sig[n / HOST_BITS_PER_LONG]
     &= ~((unsigned long)1 << (n % HOST_BITS_PER_LONG));
 }
@@ -420,6 +422,7 @@ test_significand_bit (REAL_VALUE_TYPE *r, unsigned int n)
   /* ??? Compiler bug here if we return this expression directly.
      The conversion to bool strips the "&1" and we wind up testing
      e.g. 2 != 0 -> true.  Seen in gcc version 3.2 20020520.  */
+  gcc_checking_assert (n < SIGNIFICAND_BITS);
   int t = (r->sig[n / HOST_BITS_PER_LONG] >> (n % HOST_BITS_PER_LONG)) & 1;
   return t;
 }
@@ -431,6 +434,7 @@ clear_significand_below (REAL_VALUE_TYPE *r, unsigned int n)
 {
   int i, w = n / HOST_BITS_PER_LONG;
 
+  gcc_checking_assert (n <= SIGNIFICAND_BITS);
   for (i = 0; i < w; ++i)
     r->sig[i] = 0;
 
diff --git a/gcc/testsuite/gcc.dg/pr126641.c b/gcc/testsuite/gcc.dg/pr126641.c
new file mode 100644
index 000000000000..588dc66e22cc
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/pr126641.c
@@ -0,0 +1,12 @@
+/* PR tree-optimization/126641 */
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+
+extern double x;
+extern int n;
+
+int
+foo ()
+{
+  return n - x && x * 0;
+}
diff --git a/gcc/value-range.cc b/gcc/value-range.cc
index 3da1609f8303..93a956796743 100644
--- a/gcc/value-range.cc
+++ b/gcc/value-range.cc
@@ -1750,6 +1750,92 @@ frange::ubound () const
   return build_real (type (), upper_bound ());
 }
 
+/* Widen a single bound of a sub-range by 1ulp (or 0.5ulp) in the direction of
+   DIR.  */
+
+static REAL_VALUE_TYPE
+float_widen_bound (tree type, const REAL_VALUE_TYPE &bound,
+                  const REAL_VALUE_TYPE &dir)
+{
+  REAL_VALUE_TYPE res = bound;
+  if (!real_isfinite (&bound) && real_isneg (&bound) == real_isneg (&dir))
+    return res;
+  frange_nextafter (TYPE_MODE (type), res, dir);
+  if (real_isinf (&res))
+    {
+      /* For +-DBL_MAX, instead of +-Inf use nexttoward (+-DBL_MAX, +-LDBL_MAX)
+        in a hypothetical wider type with the same mantissa precision but
+        larger exponent range; it is outside of range of double values, but
+        makes it clear it is just one ulp larger rather than infinite amount
+        larger.  */
+      res = real_isneg (&dir) ? dconstm1 : dconst1;
+      SET_REAL_EXP (&res, FLOAT_MODE_FORMAT (TYPE_MODE (type))->emax + 1);
+    }
+  if (!flag_rounding_math
+      && !MODE_COMPOSITE_P (TYPE_MODE (type))
+      && real_isfinite (&bound))
+    {
+      /* If not -frounding-math nor IBM double double, actually widen
+        just by 0.5ulp rather than 1ulp.  */
+      REAL_VALUE_TYPE tem;
+      real_arithmetic (&tem, PLUS_EXPR, &bound, &res);
+      real_arithmetic (&res, RDIV_EXPR, &tem, &dconst2);
+    }
+  return res;
+}
+
+/* Extend the *this range by 1ulp in each direction.  For op1_range
+   or op2_range of binary operations just computing the inverse
+   operation on ranges isn't sufficient.  Consider e.g.
+   [1., 1.] = op1 + [1., 1.].  op1's range is not [0., 0.], but
+   [-0x1.0p-54, 0x1.0p-53] (when not -frounding-math), any value for
+   which adding 1. to it results in 1. after rounding to nearest.
+   So, for op1_range/op2_range extend the lhs range by 1ulp (or 0.5ulp)
+   in each direction.  See PR109008 for more details.  */
+
+void
+frange::widen (tree type)
+{
+  if (known_isnan ())
+    return;
+  /* Temporarily disable -ffinite-math-only, so that frange::set doesn't
+     reduce the range back to real_min_representable (type) as lower bound
+     or real_max_representable (type) as upper bound.  */
+  bool save_flag_finite_math_only = flag_finite_math_only;
+  flag_finite_math_only = false;
+  unsigned j = 0;
+  for (unsigned i = 0; i < num_pairs (); ++i)
+    {
+      REAL_VALUE_TYPE lb = float_widen_bound (type, lower_bound (i),
+                                             dconstninf);
+      REAL_VALUE_TYPE ub = float_widen_bound (type, upper_bound (i),
+                                             dconstinf);
+      /* The result of float_widen_bound is often not representable in
+        type (could be smaller by 1ulp from representable finite minimum,
+        0.5ulp from some representable finite value or 1ulp larger than
+        representable finite maximum).  On such values calling e.g.
+        frange_nextafter doesn't work properly, so avoid merging the
+        pairs with union_ because that calls frange_fusible_p etc.
+        This range is often just something that should have the
+        real values passed to frange_arithmetic etc. and have the result
+        of that converted to something actually representable in the
+        type.  See PR126641 and PR109008.  As lhs should have been
+        canonicalized before, the slightly adjusted range should have
+        similar properties, just merge pairs where max would be >= than
+        min of the next pair.  */
+      if (j && !real_less (&m_pairs[j - 1].max, &lb))
+       m_pairs[j - 1].max = ub;
+      else
+       {
+         m_pairs[j].min = lb;
+         m_pairs[j].max = ub;
+         ++j;
+       }
+    }
+  m_num_ranges = j;
+  flag_finite_math_only = save_flag_finite_math_only;
+}
+
 // Here we copy between any two irange's.
 
 irange &
diff --git a/gcc/value-range.h b/gcc/value-range.h
index ba7d1ee7d71c..d7c426fd2f74 100644
--- a/gcc/value-range.h
+++ b/gcc/value-range.h
@@ -672,6 +672,8 @@ public:
   unsigned num_pairs () const { return m_num_ranges; }
   const REAL_VALUE_TYPE &lower_bound (unsigned pair) const;
   const REAL_VALUE_TYPE &upper_bound (unsigned pair) const;
+
+  void widen (tree);
 protected:
   virtual bool contains_p (tree cst) const override;
   virtual void set (tree, tree, value_range_kind = VR_RANGE) override;

Reply via email to