[gcc r17-2891] [frange] Enable sub-ranges.
Aldy Hernandez via Gcc-cvs <[email protected]> Mon, 3 Aug 2026 07:16:28 +0000 (GMT)
| Newsgroups | gmane.comp.gcc.cvs |
|---|---|
| Message-ID | <[email protected]> |
https://gcc.gnu.org/g:9e391103a9fcc140584193ca7f75e5e64181d632 commit r17-2891-g9e391103a9fcc140584193ca7f75e5e64181d632 Author: Aldy Hernandez <[email protected]> Date: Thu Jul 30 08:55:15 2026 +0000 [frange] Enable sub-ranges. 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. 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. 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. Diff: --- 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(-) 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 000000000000..131fed09ea69 --- /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 000000000000..841c6569a66e --- /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 000000000000..4ae8ee1477c1 --- /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 18aa82733a4b..2ce257f6ce60 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 5e6ee37277ff..dc80b2b4bf10 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;