Re: [PATCH 1/2] [frange] Enable sub-ranges.

Andrea Pinski <[email protected]> Mon, 3 Aug 2026 17:01:10 -0700
Newsgroups gmane.comp.gcc.patches
Message-ID <CALvbMcA7pOqQLp4M41OitxuXKf01-onCrGEE0E6awgY9DZK4xQ@mail.gmail.com>
On Mon, Aug 3, 2026 at 12:16 AM Aldy Hernandez <[email protected]> wrote:
>
> Raise MAX_PAIRS from 1 to 2, enabling sub-ranges.  This will allow us
> to represent non-zero and other inequalities in a follow-up patch.

Thanks for implementing this.
I might take a look at some other related code generation now that we
can represent this.
The main thing I was looking at previously is the adding of 0.0 to a
frange without -0.0 (and NaN) can be optimized away.

Thanks,
Andrea


>
> I tested how many sub-ranges would give us the most bang for the buck,
> by allowing 10 sub-ranges, and seeing how many we created and used.
> Even though 1 sub-range caught 91.29% of what we encountered in real
> life (well, in a corpus of Fortran files from the LAPACK package), we
> couldn't represent non-zero, which caused DOM to get a bunch of cases
> we missed.  With 2 subranges, we caught 99.63% of ranges generated,
> and anything past this was useless, cause we either didn't generate
> them in real life, or there was no change in generated code by having
> more sub-ranges.  For instance, from 3631 files, going from 2 to 3
> sub-ranges caused code generation changes in 4 files (all rotmg*).
> Going past 3, produced zero effect.
>
> I also benchmarked LAPACK and the GSL package for jump threading
> changes as well as VRP constant propagation.  For LAPACK, we got 0.31%
> more jump threading opportunities, and for GSL 1.10%.  For VRP it was
> the opposite, LAPACK got 0.48% more constants propagated, whereas GSL
> got a mere 0.26% boost.
>
> This may not seem like much, but just being able to represent the
> inverse of a constant (e.g. nonzero) closes the gap with the DOM
> internal tables.  With this work, we get 100% of what DOM threading
> was getting with its internal tables for the LAPACK corpus.  We nuked
> 58 of the regressions, and I believe this will fix a few PRs we had
> open for nonzero folding, as well as some signed zero missed
> optimizations.
>
> Oh, and with this patch we cause code generation differences in 2.2%
> of LAPACK, and 5.48% of GSL files.  I didn't dig into any of these,
> but it seems other passes benefit as well.  Either way, we needed a
> way to represent != 0.0, which seemed like a big limitation in the
> initial frange implementation, and was causing me to lose sleep in my
> self-imposed break from hacking.
>
> Tested on ppc64le: regstrap, LAPACK, GSL.
>
> Pushed.
>
> gcc/ChangeLog:
>
>         * value-range.h (class frange): Raise MAX_PAIRS to 2.
>         * value-range.cc: Include value-range-storage.h.
>         (real_from_str, range_tests_sub_ranges, range_tests_sub_ranges_nan)
>         (range_tests_sub_ranges_zero, range_tests_sub_ranges_storage): New.
>         (range_tests_floats): Call them, and adjust the disjoint-union test.
>
> gcc/testsuite/ChangeLog:
>
>         * gcc.dg/tree-ssa/vrp-float-15.c: New test.
>         * gcc.dg/tree-ssa/vrp-float-16.c: New test.
>         * gcc.dg/tree-ssa/vrp-float-17.c: New test.
> ---
>  gcc/testsuite/gcc.dg/tree-ssa/vrp-float-15.c |  24 +++
>  gcc/testsuite/gcc.dg/tree-ssa/vrp-float-16.c |  30 ++++
>  gcc/testsuite/gcc.dg/tree-ssa/vrp-float-17.c |  26 +++
>  gcc/value-range.cc                           | 160 ++++++++++++++++++-
>  gcc/value-range.h                            |   6 +-
>  5 files changed, 239 insertions(+), 7 deletions(-)
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-15.c
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-16.c
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-17.c
>
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-15.c b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-15.c
> new file mode 100644
> index 00000000000..131fed09ea6
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-15.c
> @@ -0,0 +1,24 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -fno-thread-jumps -fdump-tree-evrp" } */
> +
> +extern void link_error (void);
> +
> +/* x is [-Inf, 2.0] U [4.0, +Inf]; 3.0 falls in the gap.  */
> +void
> +ineq_gap (double x)
> +{
> +  if (x <= 2.0 || x >= 4.0)
> +    if (x == 3.0)
> +      link_error ();
> +}
> +
> +/* x is [2.0, 2.0] U [4.0, 4.0]; 3.0 falls in the gap.  */
> +void
> +two_points (double x)
> +{
> +  if (x == 2.0 || x == 4.0)
> +    if (x == 3.0)
> +      link_error ();
> +}
> +
> +/* { dg-final { scan-tree-dump-not "link_error" "evrp" } } */
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-16.c b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-16.c
> new file mode 100644
> index 00000000000..841c6569a66
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-16.c
> @@ -0,0 +1,30 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -fno-thread-jumps -fdump-tree-evrp-details" } */
> +
> +/* Verify the pretty-printer shows two-piece ranges correctly.  */
> +
> +/* Two disjoint points: {2.0} U {4.0}.  */
> +double
> +pp_two_points (int c)
> +{
> +  double x;
> +  if (c)
> +    x = 2.0;
> +  else
> +    x = 4.0;
> +  return x;
> +}
> +
> +/* Two intervals from inequalities: [-Inf, 2.0] U [4.0, +Inf].  */
> +double
> +pp_ineq_gap (double x)
> +{
> +  if (x <= 2.0 || x >= 4.0)
> +    return x;
> +  return 0.0;
> +}
> +
> +/* {2.0, 2.0}{4.0, 4.0} -- two disjoint pieces on one line.  */
> +/* { dg-final { scan-tree-dump "2\\.0e\\+0\[^\r\n\]*2\\.0e\\+0\[^\r\n\]*4\\.0e\\+0\[^\r\n\]*4\\.0e\\+0" "evrp" } } */
> +/* [-Inf, 2.0][4.0, +Inf] -- inequality gap on one line.  */
> +/* { dg-final { scan-tree-dump "-Inf, 2\\.0e\\+0\[^\r\n\]*4\\.0e\\+0\[^\r\n\]*\\+Inf" "evrp" } } */
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-17.c b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-17.c
> new file mode 100644
> index 00000000000..4ae8ee1477c
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-17.c
> @@ -0,0 +1,26 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -fno-thread-jumps -fdump-tree-evrp" } */
> +
> +extern void link_error (void);
> +
> +/* [-Inf, 2.0] U [4.0, +Inf], meet [1.0, 5.0] -> [1.0, 2.0] U [4.0, 5.0].  */
> +void
> +meet_ineq (double x)
> +{
> +  if (x <= 2.0 || x >= 4.0)
> +    if (x >= 1.0 && x <= 5.0)
> +      if (x == 3.0)
> +       link_error ();
> +}
> +
> +/* [0.0, 5.0] U [10.0, 15.0], meet [4.0, 11.0] -> [4.0, 5.0] U [10.0, 11.0].  */
> +void
> +meet_intervals (double x)
> +{
> +  if ((x >= 0.0 && x <= 5.0) || (x >= 10.0 && x <= 15.0))
> +    if (x >= 4.0 && x <= 11.0)
> +      if (x == 7.0)
> +       link_error ();
> +}
> +
> +/* { dg-final { scan-tree-dump-not "link_error" "evrp" } } */
> diff --git a/gcc/value-range.cc b/gcc/value-range.cc
> index 18aa82733a4..2ce257f6ce6 100644
> --- a/gcc/value-range.cc
> +++ b/gcc/value-range.cc
> @@ -28,6 +28,7 @@ along with GCC; see the file COPYING3.  If not see
>  #include "ssa.h"
>  #include "tree-pretty-print.h"
>  #include "value-range-pretty-print.h"
> +#include "value-range-storage.h"
>  #include "fold-const.h"
>  #include "gimple-range.h"
>  #include "tree-dfa.h"
> @@ -3601,6 +3602,148 @@ frange_float (const char *lb, const char *ub, tree type = float_type_node)
>    return frange (type, min, max);
>  }
>
> +// Build the REAL_VALUE_TYPE for the string S.
> +
> +static REAL_VALUE_TYPE
> +real_from_str (const char *s)
> +{
> +  REAL_VALUE_TYPE r;
> +  gcc_assert (real_from_string (&r, s) == 0);
> +  return r;
> +}
> +
> +static void
> +range_tests_sub_ranges ()
> +{
> +  frange r0, r1;
> +
> +  // A union of two disjoint intervals keeps both.
> +  r0 = frange_float ("3", "5");
> +  r1 = frange_float ("10", "12");
> +  r0.union_ (r1);
> +  ASSERT_EQ (r0.num_pairs (), 2);
> +  ASSERT_TRUE (r0.contains_p (real_from_str ("4")));
> +  ASSERT_TRUE (r0.contains_p (real_from_str ("11")));
> +  ASSERT_FALSE (r0.contains_p (real_from_str ("7")));
> +
> +  REAL_VALUE_TYPE three = real_from_str ("3");
> +  REAL_VALUE_TYPE twelve = real_from_str ("12");
> +  ASSERT_TRUE (real_identical (&r0.lower_bound (), &three));
> +  ASSERT_TRUE (real_identical (&r0.upper_bound (), &twelve));
> +
> +  // Intersecting away one side leaves a single interval again.
> +  r1 = frange_float ("0", "6");
> +  r0.intersect (r1);
> +  ASSERT_EQ (r0.num_pairs (), 1);
> +  ASSERT_TRUE (r0.contains_p (real_from_str ("4")));
> +  ASSERT_FALSE (r0.contains_p (real_from_str ("11")));
> +
> +  // Overlapping intervals fuse rather than leave a gap.
> +  r0 = frange_float ("3", "8");
> +  r1 = frange_float ("5", "12");
> +  r0.union_ (r1);
> +  ASSERT_EQ (r0.num_pairs (), 1);
> +  ASSERT_TRUE (r0.contains_p (real_from_str ("7")));
> +
> +  if (frange::MAX_PAIRS == 2)
> +    {
> +      // When more pieces arrive than fit, the last slot swallows the tail:
> +      // [0,1] stays and [3,4], [100,101] merge into [3,101].
> +      r0 = frange_float ("0", "1");
> +      r1 = frange_float ("100", "101");
> +      r0.union_ (r1);
> +      r1 = frange_float ("3", "4");
> +      r0.union_ (r1);
> +      ASSERT_EQ (r0.num_pairs (), 2);
> +      ASSERT_TRUE (r0.contains_p (real_from_str ("50")));
> +      ASSERT_TRUE (r0.contains_p (real_from_str ("3.5")));
> +      ASSERT_TRUE (r0.contains_p (real_from_str ("100.5")));
> +    }
> +
> +  // Equality accounts for the sub-ranges.
> +  r0 = frange_float ("3", "5");
> +  r1 = frange_float ("10", "12");
> +  r0.union_ (r1);
> +  r1 = frange_float ("3", "12");
> +  ASSERT_NE (r0, r1);
> +
> +  // Intersecting every piece away, with the NAN cleared, leaves UNDEFINED.
> +  r0 = frange_float ("3", "5");
> +  r1 = frange_float ("10", "12");
> +  r0.union_ (r1);
> +  r0.clear_nan ();
> +  r1 = frange_float ("20", "25");
> +  r1.clear_nan ();
> +  r0.intersect (r1);
> +  ASSERT_TRUE (r0.undefined_p ());
> +}
> +
> +static void
> +range_tests_sub_ranges_zero ()
> +{
> +  frange r0, r1;
> +
> +  // -0.0 and +0.0 abut: nothing is representable between them, so the two
> +  // halves fuse into one interval rather than leaving a gap.
> +  r0 = frange_float ("-5", "-0.0");
> +  r0.clear_nan ();
> +  r1 = frange_float ("0.0", "5");
> +  r1.clear_nan ();
> +  r0.union_ (r1);
> +  ASSERT_EQ (r0.num_pairs (), 1);
> +  ASSERT_TRUE (r0.contains_p (dconst0));
> +  ASSERT_TRUE (r0.contains_p (dconstm0));
> +}
> +
> +// A cached frange must come back with every sub-range intact.
> +
> +static void
> +range_tests_sub_ranges_storage ()
> +{
> +  vrange_allocator alloc (false);
> +
> +  // A two-piece range comes back as two pieces, unchanged.
> +  frange r0 = frange_float ("3", "5");
> +  frange r1 = frange_float ("10", "12");
> +  r0.union_ (r1);
> +  ASSERT_EQ (r0.num_pairs (), 2);
> +
> +  vrange_storage *slot = alloc.clone (r0);
> +  frange r2;
> +  slot->get_vrange (r2, float_type_node);
> +  ASSERT_EQ (r2.num_pairs (), 2);
> +  ASSERT_EQ (r2, r0);
> +}
> +
> +// NANs and sub-ranges: unioning in a NAN keeps the intervals, while
> +// intersecting the intervals away collapses to a plain NAN with a single
> +// pair.
> +
> +static void
> +range_tests_sub_ranges_nan ()
> +{
> +  frange r0, r1;
> +
> +  // Union with a NAN keeps both intervals and gains the NAN.
> +  r0 = frange_float ("3", "5");
> +  r1 = frange_float ("10", "12");
> +  r0.union_ (r1);
> +  r0.clear_nan ();
> +  r1.set_nan (float_type_node);
> +  r0.union_ (r1);
> +  ASSERT_EQ (r0.num_pairs (), 2);
> +  ASSERT_TRUE (r0.maybe_isnan ());
> +
> +  // Intersecting the intervals away leaves just the NAN.
> +  r0 = frange_float ("3", "5");
> +  r1 = frange_float ("10", "12");
> +  r0.union_ (r1);
> +  r1 = frange_float ("20", "25");
> +  r0.intersect (r1);
> +  ASSERT_TRUE (r0.known_isnan ());
> +  ASSERT_EQ (r0.num_pairs (), 1);
> +}
> +
>  static void
>  range_tests_nan ()
>  {
> @@ -3901,12 +4044,20 @@ range_tests_floats ()
>    frange r0, r1;
>
>    if (HONOR_NANS (float_type_node))
> -    range_tests_nan ();
> +    {
> +      range_tests_nan ();
> +      range_tests_sub_ranges_nan ();
> +    }
>    range_tests_signbit ();
>    range_tests_flush_denormals ();
> +  range_tests_sub_ranges ();
> +  range_tests_sub_ranges_storage ();
>
>    if (HONOR_SIGNED_ZEROS (float_type_node))
> -    range_tests_signed_zeros ();
> +    {
> +      range_tests_signed_zeros ();
> +      range_tests_sub_ranges_zero ();
> +    }
>
>    // A range of [-INF,+INF] is actually VARYING if no other properties
>    // are set.
> @@ -3929,11 +4080,12 @@ range_tests_floats ()
>        ASSERT_NE (r0, r1);
>      }
>
> -  // [3,5] U [10,12] = [3,12].
> +  // [3,5] U [10,12] = [3,5][10,12]
>    r0 = frange_float ("3", "5");
>    r1 = frange_float ("10", "12");
>    r0.union_ (r1);
> -  ASSERT_EQ (r0, frange_float ("3", "12"));
> +  ASSERT_EQ (r0.num_pairs (), 2);
> +  ASSERT_NE (r0, frange_float ("3", "12"));
>
>    // [5,10] U [4,8] = [4,10]
>    r0 = frange_float ("5", "10");
> diff --git a/gcc/value-range.h b/gcc/value-range.h
> index 5e6ee37277f..dc80b2b4bf1 100644
> --- a/gcc/value-range.h
> +++ b/gcc/value-range.h
> @@ -594,8 +594,8 @@ struct frange_pair
>
>  // A subset of possible values for a floating point type.
>  //
> -// The representation is a single interval, unioned with a subset of
> -// { -NaN, +NaN }.
> +// The representation is a handful of disjoint intervals, unioned with a
> +// subset of { -NaN, +NaN }.
>
>  class frange final : public vrange
>  {
> @@ -666,7 +666,7 @@ public:
>    bool known_isdenormal_or_zero () const;
>    virtual void verify_range () const override;
>
> -  static const unsigned int MAX_PAIRS = 1;
> +  static const unsigned int MAX_PAIRS = 2;
>    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;
> --
> 2.47.3
>