[gcc r17-2898] [frange] Implement set_nonzero and nonzero_p.

Aldy Hernandez via Gcc-cvs <[email protected]> Mon, 3 Aug 2026 13:08:31 +0000 (GMT)
Newsgroups gmane.comp.gcc.cvs
Message-ID <[email protected]>
https://gcc.gnu.org/g:3db020600d47180dc097f405d1dd7cfcafe24d43

commit r17-2898-g3db020600d47180dc097f405d1dd7cfcafe24d43
Author: Aldy Hernandez <[email protected]>
Date:   Tue Jul 14 17:27:25 2026 +0000

    [frange] Implement set_nonzero and nonzero_p.
    
    Excluding an interval is now expressible, and excluding zero is just
    the [-0.0, +0.0] case of it, so say so.
    
    For some stupid historical reason which I can't remember, the irange
    and prange nonzero_p() predicates returns true only for ~[0,0], so
    even [5,5] returns false.  When we want to test whether a range
    contains a zero, we usually use the contains_p() idiom.  I think this
    is idotic, but perhaps there is a reason for it.
    
    I've implemented the frange version the same way, with the wrinkle
    that the constructor for ~[-0.0, +0.0] includes the possibility of
    +-NAN, which means that nonzero_p() must ignore the NAN bits,
    otherwise anything but a strict ~[-0.0, +0.0] +-NAN would return
    false.  For example, this:
    
            x = frange(0.0, VR_ANTI_RANGE);
            x.clear_nan();
            x.nonzero_p();  <-- would return false
    
    Tested on ppc64le Linux: regstrap and LAPACK.  Surprisingly there are
    no changes to generated output in my Fortran files, presumably because
    intersect/union are enough to fold inequalities away, and also because
    there are no callers to nonzero_p() for frange.  Every nonzero_p()
    call is guarded by prange or irange checks, but it's nice to
    implement these since they are pure virtuals from the base vrange
    class.
    
    gcc/ChangeLog:
    
            * value-range.cc (frange::set_nonzero): Implement.
            (frange::nonzero_p): Implement.
            (range_tests_excluding): Test set_nonzero and nonzero_p.

Diff:
---
 gcc/value-range.cc | 41 +++++++++++++++++++++++++++++++++++++----
 1 file changed, 37 insertions(+), 4 deletions(-)

diff --git a/gcc/value-range.cc b/gcc/value-range.cc
index b4c097343d93..69d89f342272 100644
--- a/gcc/value-range.cc
+++ b/gcc/value-range.cc
@@ -1685,18 +1685,35 @@ frange::verify_range () const
 			 || !frange_val_is_max (m_pairs[0].max, m_type));
 }
 
-// We can't do much with nonzeros yet.
 void
 frange::set_nonzero (tree type)
 {
-  set_varying (type);
+  set (type, dconstm0, dconst0, VR_ANTI_RANGE);
 }
 
-// We can't do much with nonzeros yet.
+// Return TRUE when this range is exactly the "everything but zero" set that
+// set_nonzero builds, mirroring irange::nonzero_p.  Callers wanting "does not
+// contain zero" should use the !contains_p (0) idiom.
+//
+// A NAN is not a zero, so nonzero-ness depends only on the intervals, not on
+// whether the range may also be a NAN.  We therefore recognize the nonzero
+// range by comparing intervals against set_nonzero's with the NAN state
+// ignored.  A strict *this == set_nonzero () would be wrong: set_nonzero
+// leaves the NAN able to be either sign, so a range that is otherwise exactly
+// nonzero but whose NAN has been cleared would compare unequal.
+
 bool
 frange::nonzero_p () const
 {
-  return false;
+  if (undefined_p () || known_isnan ())
+    return false;
+
+  frange nz;
+  nz.set_nonzero (type ());
+  nz.clear_nan ();
+  frange tmp = *this;
+  tmp.clear_nan ();
+  return tmp == nz;
 }
 
 // Set range to [+0.0, +0.0] if honoring signed zeros, or [0.0, 0.0]
@@ -3826,11 +3843,27 @@ range_tests_sub_ranges_zero ()
   ASSERT_TRUE (r0.contains_p (real_from_str ("-1.0")));
 
   // Excluding zero from [-0.0, 5.0] eats the lower end entirely.
+  r0.set_nonzero (float_type_node);
+  ASSERT_TRUE (r0.nonzero_p ());
+  ASSERT_FALSE (r0.contains_p (dconst0));
+  ASSERT_FALSE (r0.contains_p (dconstm0));
+
+  // A NAN is not a zero, so clearing the NAN leaves a nonzero range nonzero.
+  r0.clear_nan ();
+  ASSERT_TRUE (r0.nonzero_p ());
+
+  // A range that merely avoids zero is not the nonzero range.
+  r0 = frange_float ("1.0", "10.0");
+  ASSERT_FALSE (r0.nonzero_p ());
+
+  // Excluding zero from [-0.0, 5.0] leaves (0, 5]: it avoids zero but is not
+  // the whole nonzero range.
   r0 = frange_float ("-0.0", "5.0");
   r0.clear_nan ();
   r1 = frange_float_excluding ("0.0");
   r0.intersect (r1);
   ASSERT_EQ (r0.num_pairs (), 1);
+  ASSERT_FALSE (r0.nonzero_p ());
   ASSERT_FALSE (r0.contains_p (dconst0));
   ASSERT_FALSE (r0.contains_p (dconstm0));
   ASSERT_TRUE (r0.contains_p (real_from_str ("5.0")));