https://gcc.gnu.org/g:bb6660183276d4b5b450beb2303e8b6e6aaa2f08
commit r17-4822-gbb6660183276d4b5b450beb2303e8b6e6aaa2f08 Author: Aldy Hernandez <[email protected]> Date: Wed Sep 30 09:27:20 2026 +0000 ranger: walk a constant array's initializer by index A load from a constant array at an unknown index unions every element of its initializer, and adds zero if the initializer has fewer elements than the array. We currently do this by counting, ignoring position. My next patch will need to know where the missing elements are, so this patch changes the approach to a walk instead of a count. We cap initializers at 32 elements by default (--param vrp-cstload-limit), so there's no penalty for doing the walk. For that matter, the whole patch series is a wash in compile time performance. Tested on ppc64le Linux, including Ada for the swapped ranges test. No assembly changes over a corpus of .ii files from a stage1 bootstrap. gcc/ChangeLog: * gimple-range-fold.cc (ctor_ref_range::accumulate_range_from_array_ref): Walk the initializer by index to find the elements it leaves out. gcc/testsuite/ChangeLog: * g++.dg/tree-ssa/vrp-from-cst-agg-1.C: New test. * gcc.dg/tree-ssa/vrp-from-cst-agg-9.c: New test. * gnat.dg/opt111.adb: New test. Diff: --- gcc/gimple-range-fold.cc | 57 +++++++++++++++++++--- gcc/testsuite/g++.dg/tree-ssa/vrp-from-cst-agg-1.C | 22 +++++++++ gcc/testsuite/gcc.dg/tree-ssa/vrp-from-cst-agg-9.c | 27 ++++++++++ gcc/testsuite/gnat.dg/opt111.adb | 28 +++++++++++ 4 files changed, 126 insertions(+), 8 deletions(-) diff --git a/gcc/gimple-range-fold.cc b/gcc/gimple-range-fold.cc index f567df891c89..554bb4e122c2 100644 --- a/gcc/gimple-range-fold.cc +++ b/gcc/gimple-range-fold.cc @@ -1124,7 +1124,9 @@ ctor_ref_range::accumulate_range_from_component_ref (tree init, unsigned i, // static const int t[2] = { 4, 5 }; // // INIT is { 4, 5 } and I is 1, the end of the path. For t[1] the range is -// [5, 5] and for t[n] it is [4, 5]. +// [5, 5] and for t[n] it is [4, 5]. When every element is loaded, whatever +// the initializer leaves out reads as zero: take each element's indices out +// of the domain to see if anything is left. bool ctor_ref_range::accumulate_range_from_array_ref (tree init, unsigned i, @@ -1154,16 +1156,53 @@ ctor_ref_range::accumulate_range_from_array_ref (tree init, unsigned i, || !tree_fits_uhwi_p (TYPE_MIN_VALUE (domain)) || !tree_fits_uhwi_p (TYPE_MAX_VALUE (domain))) return false; - unsigned HOST_WIDE_INT needed_count - = (tree_to_uhwi (TYPE_MAX_VALUE (domain)) - - tree_to_uhwi (TYPE_MIN_VALUE (domain)) + 1); - if (CONSTRUCTOR_NELTS (init) < needed_count) - accumulate_range_from_missing_constructor_elt (); + // Unsigned bounds that look reversed are signed, as they are for + // get_array_ctor_element_at_index: Ada's -1 .. 5 has a lower bound of + // SIZE_MAX. + tree type = TREE_TYPE (TYPE_MIN_VALUE (domain)); + if (TYPE_UNSIGNED (type) + && tree_int_cst_lt (TYPE_MAX_VALUE (domain), TYPE_MIN_VALUE (domain))) + type = signed_type_for (type); + wide_int min = wi::to_wide (TYPE_MIN_VALUE (domain)); + wide_int max = wi::to_wide (TYPE_MAX_VALUE (domain)); + + // Ada represents an empty array with reversed bounds. + if (wi::gt_p (min, max, TYPE_SIGN (type))) + return true; + + unsigned prec = TYPE_PRECISION (type); + int_range_max missing (type, min, max); + wide_int next = missing.lower_bound (); unsigned ix; - tree val; - FOR_EACH_CONSTRUCTOR_VALUE (CONSTRUCTOR_ELTS (init), ix, val) + tree index, val; + + // Build a range with the entire domain, and take out any elements which are + // initialized. If there's anything left in that range, it means there are + // missing elements and we should add a [0, 0] to the final range. + FOR_EACH_CONSTRUCTOR_ELT (CONSTRUCTOR_ELTS (init), ix, index, val) { + wide_int lo, hi; + + // A null index means the element follows the previous one. + if (!index) + lo = hi = next; + else if (TREE_CODE (index) == RANGE_EXPR) + { + lo = wi::to_wide (TREE_OPERAND (index, 0), prec); + hi = wi::to_wide (TREE_OPERAND (index, 1), prec); + } + else + { + gcc_checking_assert (TREE_CODE (index) == INTEGER_CST); + lo = hi = wi::to_wide (index, prec); + } + // A RAW_DATA_CST holds one element per byte. + if (TREE_CODE (val) == RAW_DATA_CST) + hi = wi::add (hi, RAW_DATA_LENGTH (val) - 1); + missing.intersect (int_range<2> (type, lo, hi, VR_ANTI_RANGE)); + next = wi::add (hi, 1); + /* TODO: If the array index in the expr is an SSA_NAME with a known range, we could use just values loaded from the corresponding array elements. */ @@ -1171,6 +1210,8 @@ ctor_ref_range::accumulate_range_from_array_ref (tree init, unsigned i, return false; } + if (!missing.undefined_p ()) + accumulate_range_from_missing_constructor_elt (); return true; } diff --git a/gcc/testsuite/g++.dg/tree-ssa/vrp-from-cst-agg-1.C b/gcc/testsuite/g++.dg/tree-ssa/vrp-from-cst-agg-1.C new file mode 100644 index 000000000000..07d2618871dc --- /dev/null +++ b/gcc/testsuite/g++.dg/tree-ssa/vrp-from-cst-agg-1.C @@ -0,0 +1,22 @@ +// { dg-do compile { target c++14 } } +// { dg-options "-O2 -fdump-tree-optimized" } + +// The elements of t after the first share one initializer, { 0, 3 }, under +// a RANGE_EXPR: the two elements of its constructor cover all eight of the +// array, so no load reads a zero from an element left out. + +struct S { int a; int b = 3; }; +static const S t[8] = { { 1, 2 } }; + +void link_error (); + +int +foo (unsigned i) +{ + int v = t[i].b; + if (v != 2 && v != 3) + link_error (); + return v; +} + +// { dg-final { scan-tree-dump-not "link_error" "optimized" } } diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-from-cst-agg-9.c b/gcc/testsuite/gcc.dg/tree-ssa/vrp-from-cst-agg-9.c new file mode 100644 index 000000000000..c06a12a80f36 --- /dev/null +++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-from-cst-agg-9.c @@ -0,0 +1,27 @@ +/* { dg-do run } */ +/* { dg-options "-O2" } */ + +/* The initializer leaves out the elements at 0, 1, 3 and 4, and those + from 6 on: all read as zero, so whatever the index the load is 0, 5 + or 9. */ + +void link_error (void); + +static const int tbl[8] = { [2] = 5, [5] = 9 }; + +[[gnu::noipa]] int +foo (int i) +{ + int v = tbl[i]; + if (v != 0 && v != 5 && v != 9) + link_error (); + return v == 0; +} + +int +main (void) +{ + if (!foo (0) || foo (2) || foo (5) || !foo (7)) + __builtin_abort (); + return 0; +} diff --git a/gcc/testsuite/gnat.dg/opt111.adb b/gcc/testsuite/gnat.dg/opt111.adb new file mode 100644 index 000000000000..8a4da47fd4db --- /dev/null +++ b/gcc/testsuite/gnat.dg/opt111.adb @@ -0,0 +1,28 @@ +-- { dg-do run } +-- { dg-options "-O2" } + +-- The domain of an array with a negative lower bound has unsigned bounds +-- that look reversed. Loads from it are not loads from an empty array. + +procedure Opt111 is + + type Arr is array (-1 .. 5) of Integer; + T : constant Arr := (11, 12, 13, 14, 15, 16, 17); + + function G (I : Integer; C : Boolean) return Integer; + pragma No_Inline (G); + + function G (I : Integer; C : Boolean) return Integer is + R : Integer := 0; + begin + if C then + R := T (I); + end if; + return R; + end; + +begin + if G (-1, True) /= 11 or else G (5, True) /= 17 then + raise Program_Error; + end if; +end;
