Re: [PATCH] [frange] Implement set_nonzero and nonzero_p.

Andrew MacLeod <[email protected]> Tue, 4 Aug 2026 09:11:24 -0400
Newsgroups gmane.comp.gcc.patches
Message-ID <[email protected]>
On 8/3/26 09:09, Aldy Hernandez wrote:
> [Jakub, Richi, Andrew, etc: Does anyone remember the reason for this
> craziness?  Should we revisit what vrange::nonzero_p() does, or will
> this mess up the ranger's cache or something I'm not aware of?]
>
> 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.


It was historical.. probably related to pointers.

I have no issues with changing it, it certainly can be confusing... It 
would require changing existing uses, and then finding all the places 
where we use the !contains_p(zero) version.

Perhaps part of the problem is that it is ambiguous anyway.. perhaps it 
would be much clearer to simply introduce a contains_zero_p ()  method.  
thats seems better to me.

We could also audit the existing nonzero_p () uses and see if there 
might be  a better name, or even a real need for it..

Andrew

>
> 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.
>
> Pushed.
>
> p.s. This concludes the multi-range frange work, sans the
> range-op-float.cc tidbit Jakub pointed out.
>
> gcc/ChangeLog:
>
> 	* value-range.cc (frange::set_nonzero): Implement.
> 	(frange::nonzero_p): Implement.
> 	(range_tests_excluding): Test set_nonzero and nonzero_p.
> ---
>   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 b4c097343d9..69d89f34227 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")));