[gcc r17-2806] [frange] Add set_pairs to install a set of sub-ranges.

Aldy Hernandez via Gcc-cvs <[email protected]> Thu, 30 Jul 2026 06:10:19 +0000 (GMT)
Newsgroups gmane.comp.gcc.cvs
Message-ID <[email protected]>
https://gcc.gnu.org/g:b04da41463346ab5d87e43496c2446115adee3ac

commit r17-2806-gb04da41463346ab5d87e43496c2446115adee3ac
Author: Aldy Hernandez <[email protected]>
Date:   Tue Jul 28 11:22:49 2026 +0000

    [frange] Add set_pairs to install a set of sub-ranges.
    
    Implement frange::set_pairs(), a function that takes an array of
    sub-ranges, sorts them, fuses those that overlap, while capping the
    result to MAX_PAIRS, and then installs these as the frange's
    sub-ranges.
    
    Preliminary stats show that 2 subranges cover 99.6% of all ranges
    needed, so we don't bend over backwards to this super efficiently like
    we do for irange.  We're unlikely to ever need more than 2 subranges.
    
    MAX_PAIRS is still 1, so set_pairs only ever installs a single
    interval for now.
    
    No changes to functionality until we flip the switch.
    
    Tested on ppc64le: regstrap, LAPACK, no assembly accross a corpus of
    Fortran files, etc.
    
    gcc/ChangeLog:
    
            * value-range.h (class frange): Declare set_pairs.  Document the
            sub-range representation and that the no-argument bounds are the
            convex hull.
            * value-range.cc (frange_fusible_p): New.
            (frange::set_pairs): New.
            (frange::flush_denormals_to_zero): Reinstall the endpoints through
            set_pairs.
            (frange::normalize_kind): Note a range with a gap is never varying.
            (frange::verify_range): Check the sub-range invariants.

Diff:
---
 gcc/value-range.cc | 101 +++++++++++++++++++++++++++++++++++++++++++++++------
 gcc/value-range.h  |   1 +
 2 files changed, 92 insertions(+), 10 deletions(-)

diff --git a/gcc/value-range.cc b/gcc/value-range.cc
index 09381e626026..ac8096746e37 100644
--- a/gcc/value-range.cc
+++ b/gcc/value-range.cc
@@ -1013,6 +1013,22 @@ frange_cmp (const REAL_VALUE_TYPE &a, const REAL_VALUE_TYPE &b)
   return 0;
 }
 
+// Return TRUE if [..., A_MAX] and [B_MIN, ...] can be fused into one interval,
+// either because they overlap or because no representable value exists between
+// them.  The latter is how -0.0 and +0.0 abut: there is nothing in between, so
+// [x, -0.0] U [+0.0, y] is really [x, y].
+
+static bool
+frange_fusible_p (machine_mode mode, const REAL_VALUE_TYPE &a_max,
+		  const REAL_VALUE_TYPE &b_min)
+{
+  if (frange_cmp (b_min, a_max) <= 0)
+    return true;
+  REAL_VALUE_TYPE next = a_max;
+  frange_nextafter (mode, next, dconstinf);
+  return frange_cmp (b_min, next) <= 0;
+}
+
 // Flush denormal endpoints to the appropriate 0.0.
 
 void
@@ -1022,23 +1038,29 @@ frange::flush_denormals_to_zero ()
     return;
 
   machine_mode mode = TYPE_MODE (type ());
+  frange_pair pairs[MAX_PAIRS];
+  unsigned n = m_num_ranges;
 
-  // FIXME: Rewrite for sub-ranges.
   // Flush a denormal endpoint to a zero of the same sign: a +denormal lower
-  // bound to +0.0, and a -denormal upper bound to -0.0.  Then call
-  // canonicalize_zeros to rewrite the sign to whatever the flags make
+  // bound to +0.0, and a -denormal upper bound to -0.0.  Then set_pairs, via
+  // canonicalize_zeros, rewrites the sign to whatever the flags make
   // canonical.  For example, under !HONOR_SIGNED_ZEROS (-fno-signed-zeros) a
   // range reaching zero must hold both signs of it, so:
   //
   //     [ +DENORMAL, 5.0 ]  flushes to  [ -0.0, 5.0 ]
   //
-  // keeping contains_p (-0.0) true; under HONOR_SIGNED_ZEROS the sign stands and
-  // it stays [ +0.0, 5.0 ].
-  if (real_isdenormal (&m_pairs[0].max, mode) && real_isneg (&m_pairs[0].max))
-    m_pairs[0].max = dconstm0;
-  if (real_isdenormal (&m_pairs[0].min, mode) && !real_isneg (&m_pairs[0].min))
-    m_pairs[0].min = dconst0;
-  canonicalize_zeros (m_pairs[0]);
+  // keeping contains_p (-0.0) true; under HONOR_SIGNED_ZEROS the sign stands
+  // and it stays [ +0.0, 5.0 ].
+  for (unsigned i = 0; i < n; ++i)
+    {
+      pairs[i] = m_pairs[i];
+      if (real_isdenormal (&pairs[i].max, mode) && real_isneg (&pairs[i].max))
+	pairs[i].max = dconstm0;
+      if (real_isdenormal (&pairs[i].min, mode) && !real_isneg (&pairs[i].min))
+	pairs[i].min = dconst0;
+    }
+
+  set_pairs (pairs, n);
 }
 
 // Canonicalize the signed zeros of a sub-range according with what the target
@@ -1070,6 +1092,58 @@ frange::canonicalize_zeros (frange_pair &p)
     }
 }
 
+// Sort, fuse and install the N intervals in PAIRS as this range's sub-ranges.
+//
+// Fusing merges intervals that overlap or abut.  If more than MAX_PAIRS still
+// survive, the last slot swallows the surplus.
+
+void
+frange::set_pairs (frange_pair *pairs, unsigned n)
+{
+  gcc_checking_assert (n > 0);
+  machine_mode mode = TYPE_MODE (m_type);
+
+  // Sort by lower bound.  N is tiny (at most 2 * MAX_PAIRS).
+  for (unsigned i = 0; i + 1 < n; ++i)
+    for (unsigned j = i + 1; j < n; ++j)
+      if (frange_cmp (pairs[j].min, pairs[i].min) < 0)
+	std::swap (pairs[i], pairs[j]);
+
+  // Fuse overlapping and abutting intervals.
+  unsigned k = 0;
+  for (unsigned i = 1; i < n; ++i)
+    {
+      if (frange_fusible_p (mode, pairs[k].max, pairs[i].min))
+	{
+	  if (frange_cmp (pairs[i].max, pairs[k].max) > 0)
+	    pairs[k].max = pairs[i].max;
+	}
+      else
+	pairs[++k] = pairs[i];
+    }
+  n = k + 1;
+
+  // Only MAX_PAIRS fit.  Like irange, keep the first pieces and let the last
+  // slot swallow the rest.
+  if (n > MAX_PAIRS)
+    {
+      pairs[MAX_PAIRS - 1].max = pairs[n - 1].max;
+      n = MAX_PAIRS;
+    }
+
+  m_kind = VR_RANGE;
+  m_num_ranges = n;
+  for (unsigned i = 0; i < n; ++i)
+    {
+      m_pairs[i] = pairs[i];
+      canonicalize_zeros (m_pairs[i]);
+    }
+
+  normalize_kind ();
+  if (flag_checking)
+    verify_range ();
+}
+
 // Setter for franges.
 
 void
@@ -1510,6 +1584,7 @@ frange::verify_range () const
       return;
     case VR_RANGE:
       gcc_checking_assert (m_type);
+      gcc_checking_assert (m_num_ranges >= 1 && m_num_ranges <= MAX_PAIRS);
       break;
     case VR_NAN:
       gcc_checking_assert (m_type);
@@ -1540,6 +1615,12 @@ frange::verify_range () const
 			     && !real_iszero (&m_pairs[i].max, 1));
     }
 
+  // Sub-ranges are sorted and separated by at least one representable value.
+  for (unsigned i = 1; i < m_num_ranges; ++i)
+    gcc_checking_assert (!frange_fusible_p (TYPE_MODE (m_type),
+					    m_pairs[i - 1].max,
+					    m_pairs[i].min));
+
   // If all the properties are clear, we better not span the entire
   // domain, because that would make us varying.
   if (m_num_ranges == 1 && m_pos_nan && m_neg_nan)
diff --git a/gcc/value-range.h b/gcc/value-range.h
index 2ea0006ba9fd..5e6ee37277ff 100644
--- a/gcc/value-range.h
+++ b/gcc/value-range.h
@@ -679,6 +679,7 @@ private:
   bool normalize_kind ();
   bool union_nans (const frange &);
   bool intersect_nans (const frange &);
+  void set_pairs (frange_pair *, unsigned);
   void canonicalize_zeros (frange_pair &);
 
   tree m_type;