On Fri, Oct 31, 2025 at 2:47 PM Martin Jambor <[email protected]> wrote: > > Hi, > > this patch adds the ability to infer ranges from loads from global > constant static aggregates which have static initializers. Even when > the load has an ARRAY_REF with an unknown index and thus we do not know > the particular constant that is being loaded, we can traverse the > corresponding elements of the initializer and see if we know in what > range(s) the loaded value must fall - or for pointers we can sometimes > infer that the value cannot be NULL. > > I thought this was similar to fold_using_range::range_of_address and > so I decided to put my implementation alongside of it. I hope that is > the correct place. > > Bootstrapped, LTO-bootstrapped and tested on x86_64-linux, bootstrap > on aarch64-linux is underway. OK for master if it passes?
I am 99% sure there are a few bug reports which this will fix. PR 102981 for one. > > Thanks, > > Martin > > > gcc/ChangeLog: > > 2025-10-31 Martin Jambor <[email protected]> > > * gimple-range-fold.h (class fold_using_range): New member > function range_from_readonly_var. > * gimple-range-fold.cc (fold_using_range::fold_stmt): Call > range_from_readonly_var on assignments. > (add_loaded_invariant_to_range): New function. > (range_from_readonly_load): Likewise. > (fold_using_range::range_from_readonly_var): Likewise. > * params.opt (param_vrp_cstload_limit): New. > * doc/invoke.texi (vrp-cstload-limit): Likewise. > > gcc/testsuite/ChangeLog: > > 2025-10-31 Martin Jambor <[email protected]> > > * gcc.dg/tree-ssa/vrp-from-cst-agg-1.c: New test. > * gcc.dg/tree-ssa/vrp-from-cst-agg-2.c: Likewise. > * gcc.dg/tree-ssa/vrp-from-cst-agg-3.c: Likewise. > * gcc.dg/tree-ssa/vrp-from-cst-agg-4.c: Likewise. > * gcc.dg/ipa/vrp-from-cst-agg-5.c: Likewise. > --- > gcc/doc/invoke.texi | 3 + > gcc/gimple-range-fold.cc | 214 +++++++++++++++++- > gcc/gimple-range-fold.h | 1 + > gcc/params.opt | 4 + > gcc/testsuite/gcc.dg/ipa/vrp-from-cst-agg-5.c | 33 +++ > .../gcc.dg/tree-ssa/vrp-from-cst-agg-1.c | 36 +++ > .../gcc.dg/tree-ssa/vrp-from-cst-agg-2.c | 57 +++++ > .../gcc.dg/tree-ssa/vrp-from-cst-agg-3.c | 34 +++ > .../gcc.dg/tree-ssa/vrp-from-cst-agg-4.c | 34 +++ > 9 files changed, 412 insertions(+), 4 deletions(-) > create mode 100644 gcc/testsuite/gcc.dg/ipa/vrp-from-cst-agg-5.c > create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-from-cst-agg-1.c > create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-from-cst-agg-2.c > create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-from-cst-agg-3.c > create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-from-cst-agg-4.c > > diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi > index b40fc892fa0..c2542f8b882 100644 > --- a/gcc/doc/invoke.texi > +++ b/gcc/doc/invoke.texi > @@ -17632,6 +17632,9 @@ Increasing the cost multiplier will make vector loops > more profitable. > @item vrp-block-limit > Maximum number of basic blocks before VRP switches to a lower memory > algorithm. > > +@item vrp-cstload-limit > +Maximum number of VRP intervals inferred from a load from a constant > aggregate. > + > @item vrp-sparse-threshold > Maximum number of basic blocks before VRP uses a sparse bitmap cache. > > diff --git a/gcc/gimple-range-fold.cc b/gcc/gimple-range-fold.cc > index 06c645f3d08..b455589428e 100644 > --- a/gcc/gimple-range-fold.cc > +++ b/gcc/gimple-range-fold.cc > @@ -639,10 +639,14 @@ fold_using_range::fold_stmt (vrange &r, gimple *s, > fur_source &src, tree name) > if (!name) > name = gimple_get_lhs (s); > > - // Process addresses. > - if (gimple_code (s) == GIMPLE_ASSIGN > - && gimple_assign_rhs_code (s) == ADDR_EXPR) > - return range_of_address (as_a <prange> (r), s, src); > + // Process addresses and loads from static constructors. > + if (gimple_code (s) == GIMPLE_ASSIGN) > + { > + if (gimple_assign_rhs_code (s) == ADDR_EXPR) > + return range_of_address (as_a <prange> (r), s, src); > + if (range_from_readonly_var (r, s)) > + return true; > + } > > gimple_range_op_handler handler (s); > if (handler) > @@ -891,6 +895,208 @@ fold_using_range::range_of_address (prange &r, gimple > *stmt, fur_source &src) > return true; > } > > +// VAL must be an interprocedural invariant. If VAL is a pointer which > cannot > +// be zero, then set it to nonzero if it is undefined and return true. If > VAL > +// is a pointer which can be zero return false. If VAL is of another type, > add > +// the constant to the set of ranges to R and return true. > + > +static bool > +add_loaded_invariant_to_range (vrange &r, tree val) > +{ > + if (POINTER_TYPE_P (TREE_TYPE (val))) > + { > + bool strict_overflow_p; > + if (tree_single_nonzero_warnv_p (val, &strict_overflow_p)) > + { > + if (r.undefined_p ()) > + r.set_nonzero (TREE_TYPE (val)); > + return true; > + } > + else > + return false; > + } > + else > + { > + if (TREE_CODE (val) == REAL_CST > + && real_isnan (TREE_REAL_CST_PTR (val))) > + return false; > + > + if (is_a <irange> (r)) > + { > + irange &ir = as_a <irange> (r); > + if (!r.contains_p (val) > + && ir.num_pairs () >= (unsigned) param_vrp_cstload_limit) > + return false; > + } > + > + value_range tmp (val, val, VR_RANGE); > + if (r.undefined_p ()) > + r = tmp; > + else > + r.union_ (tmp); > + return true; > + } > +} > + > +// One step of fold_using_range::range_from_readonly_var. Process > expressions > +// in comps from index I to 0 according to the corresponding static > initializer > +// in CONSTUCTOR, adding all possible loaded constants (which must be > storable > +// to TYPE) to R. Return true if the all necessary values were extracted and > +// added to R successfully. > + > +static bool > +range_from_readonly_load (vrange &r, tree type, tree constructor, > + const vec <tree> &comps, int i) > +{ > + unsigned ix; > + tree index, val; > + tree expr = comps[i]; > + > + gcc_assert (TREE_CODE (expr) == COMPONENT_REF > + || TREE_CODE (expr) == ARRAY_REF); > + tree op1 = TREE_OPERAND (expr, 1); > + if (TREE_CODE (expr) == COMPONENT_REF > + || TREE_CODE (op1) == INTEGER_CST) > + { > + FOR_EACH_CONSTRUCTOR_ELT (CONSTRUCTOR_ELTS (constructor), ix, index, > val) > + { > + if (TREE_CODE (index) == FIELD_DECL) > + { > + if (index != op1) > + continue; > + } > + else if (TREE_CODE (index) == INTEGER_CST > + && TREE_CODE (op1) == INTEGER_CST) > + { > + if (!tree_int_cst_equal (index, op1)) > + continue; > + } > + else > + gcc_unreachable (); > + > + if (TREE_CODE (val) == CONSTRUCTOR) > + { > + if (i > 0) > + return range_from_readonly_load (r, type, val, comps, i - 1); > + else > + return false; > + } > + if (is_gimple_ip_invariant (val)) > + { > + if (i != 0 > + || (!useless_type_conversion_p (type, TREE_TYPE (val)))) > + return false; > + > + return add_loaded_invariant_to_range (r, val); > + } > + return false; > + } > + return false; > + } > + > + tree arr_type = TREE_TYPE (constructor); > + tree domain = TYPE_DOMAIN (arr_type); > + if (!TYPE_MIN_VALUE (domain) > + || !TYPE_MAX_VALUE (domain) > + || !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); > + unsigned HOST_WIDE_INT count = 0; > + > + FOR_EACH_CONSTRUCTOR_ELT (CONSTRUCTOR_ELTS (constructor), ix, index, val) > + { > + /* 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. */ > + count++; > + if (TREE_CODE (val) == CONSTRUCTOR) > + { > + if (i <= 0 > + || !range_from_readonly_load (r, type, val, comps, i - 1)) > + return false; > + } > + else if (is_gimple_ip_invariant (val)) > + { > + if (i != 0 > + || (!useless_type_conversion_p (type, TREE_TYPE (val)))) > + return false; > + > + if (!add_loaded_invariant_to_range (r, val)) > + return false; > + } > + else > + return false; > + } > + > + if (count < needed_count) > + { > + if (AGGREGATE_TYPE_P (type) > + || !irange::supports_p (type)) > + return false; > + if (!add_loaded_invariant_to_range (r, build_zero_cst (type))) > + return false; > + return true; > + } > + else > + { > + gcc_assert (count == needed_count); > + return true; > + } > +} > + > +// Attempt to calculate the range of value loaded by STMT (which must be an > +// assignment) if it is a load from a read-only aggregate variable. If > +// successful, return true and set the discovered range in R. Otherwise > return > +// false and leave R untouched. > + > +bool > +fold_using_range::range_from_readonly_var (vrange &r, gimple *stmt) > +{ > + gcc_checking_assert (gimple_code (stmt) == GIMPLE_ASSIGN); > + > + tree t = gimple_assign_rhs1 (stmt); > + if (TREE_CODE (t) != ARRAY_REF > + && TREE_CODE (t) != COMPONENT_REF) > + return false; > + int count = 0; > + while (TREE_CODE (t) == ARRAY_REF > + || TREE_CODE (t) == COMPONENT_REF) > + { > + count++; > + t = TREE_OPERAND (t, 0); > + } > + if (!VAR_P (t) > + || !is_global_var (t) > + || !TREE_READONLY (t) > + || !DECL_INITIAL (t) > + || TREE_CODE (DECL_INITIAL (t)) != CONSTRUCTOR) > + return false; > + > + t = gimple_assign_rhs1 (stmt); > + auto_vec <tree, 4> comps; > + comps.safe_grow (count, true); > + int i = 0; > + while (TREE_CODE (t) == ARRAY_REF > + || TREE_CODE (t) == COMPONENT_REF) > + { > + comps[i] = t; > + t = TREE_OPERAND (t, 0); > + i++; > + } > + > + tree type = TREE_TYPE (gimple_assign_lhs (stmt)); > + gcc_checking_assert (value_range::supports_type_p (type)); > + value_range tmp (type); > + bool res = range_from_readonly_load (tmp, type, DECL_INITIAL (t), comps, > + count - 1); > + if (res) > + r = tmp; > + return res; > +} > + > // Calculate a range for phi statement S and return it in R. > // If a range cannot be calculated, return false. > > diff --git a/gcc/gimple-range-fold.h b/gcc/gimple-range-fold.h > index 826a10fd9e8..bc5c6686a65 100644 > --- a/gcc/gimple-range-fold.h > +++ b/gcc/gimple-range-fold.h > @@ -180,6 +180,7 @@ protected: > bool range_of_call (vrange &r, gcall *call, fur_source &src); > bool range_of_cond_expr (vrange &r, gassign* cond, fur_source &src); > bool range_of_address (prange &r, gimple *s, fur_source &src); > + bool range_from_readonly_var (vrange &r, gimple *stmt); > bool range_of_phi (vrange &r, gphi *phi, fur_source &src); > void range_of_ssa_name_with_loop_info (vrange &, tree, class loop *, gphi > *, > fur_source &src); > diff --git a/gcc/params.opt b/gcc/params.opt > index f8884e976e7..85c93b82963 100644 > --- a/gcc/params.opt > +++ b/gcc/params.opt > @@ -1273,6 +1273,10 @@ The scaling multiplier as a percentage to apply to all > scalar loop costing when > Common Joined UInteger Var(param_vrp_block_limit) Init(150000) Optimization > Param > Maximum number of basic blocks before VRP switches to a fast model with less > memory requirements. > > +-param=vrp-cstload-limit= > +Common Joined UInteger Var(param_vrp_cstload_limit) Init(4) IntegerRange(0, > 255) Optimization Param > +Maximum number of VRP intervals inferred from a load from a constant > aggregate. > + > -param=vrp-sparse-threshold= > Common Joined UInteger Var(param_vrp_sparse_threshold) Init(3000) > Optimization Param > Maximum number of basic blocks before VRP uses a sparse bitmap cache. > diff --git a/gcc/testsuite/gcc.dg/ipa/vrp-from-cst-agg-5.c > b/gcc/testsuite/gcc.dg/ipa/vrp-from-cst-agg-5.c > new file mode 100644 > index 00000000000..cd71d329487 > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/ipa/vrp-from-cst-agg-5.c > @@ -0,0 +1,33 @@ > +/* { dg-do compile } */ > +/* { dg-options "-O3 -fdump-ipa-cp-details" } */ > + > +static const struct { > + int w; > + int h; > +} sizes[7] = { > + { 16, 16 }, > + { 16, 8 }, > + { 8, 16 }, > + { 8, 8 }, > + { 8, 4 }, > + { 4, 8 }, > + { 4, 4 } > +}; > + > +int baz(int, int); > + > +[[gnu::noinline]] void bar(int w, int h) > +{ > + for (int i = 0; i < w; i++) > + for (int j = 0; i < h; j++) > + baz (i, j); > +} > + > +void foo (int index) > +{ > + int w = sizes[index].w; > + int h = sizes[index].h; > + > + bar (w, h); > +} > +/* { dg-final { scan-ipa-dump-times "irange" 2 "cp" } } */ > diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-from-cst-agg-1.c > b/gcc/testsuite/gcc.dg/tree-ssa/vrp-from-cst-agg-1.c > new file mode 100644 > index 00000000000..51b1fe6329c > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-from-cst-agg-1.c > @@ -0,0 +1,36 @@ > +/* { dg-do link } */ > +/* { dg-options "-O2" } */ > + > +int ga = 1; > +int gb = 2; > +int gc = 3; > +volatile int gi; > + > +static const int *vars[3] = {&ga, &gb, &gc}; > + > +void link_error (void); > + > +[[gnu::noinline]] int foo (int index) > +{ > + for (int i = 0; i < 4; ++i) > + { > + const int *p = vars[index]; > + if (!p) > + link_error (); > + else > + gi = *p; > + } > + return 0; > +} > + > +[[gnu::noipa]] int > +get_index (void) > +{ > + return 1; > +} > + > +int main (int argc, char **argv) > +{ > + foo (get_index ()); > + return 0; > +} > diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-from-cst-agg-2.c > b/gcc/testsuite/gcc.dg/tree-ssa/vrp-from-cst-agg-2.c > new file mode 100644 > index 00000000000..0f7677d90b8 > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-from-cst-agg-2.c > @@ -0,0 +1,57 @@ > +/* { dg-do run } */ > +/* { dg-options "-O2" } */ > + > +enum comp_cat_tag > +{ > + cc_partial_ordering, > + cc_weak_ordering, > + cc_strong_ordering, > + cc_last > +}; > + > +struct comp_cat_info_t > +{ > + const char *name; > + const char *members[4]; > +}; > + > +static const struct comp_cat_info_t comp_cat_info[cc_last] > += { > + { "partial_ordering", { "equivalent", "greater", "less", "unordered" } }, > + { "weak_ordering", { "equivalent", "greater", "less" } }, > + { "strong_ordering", { "equal", "greater", "less" } } > +}; > + > +volatile const char *gp; > + > +[[gnu::noipa]] static void > +check (const char *p) > +{ > + if (!p) > + __builtin_abort (); > + gp = p; > +} > + > +[[gnu::noinline]] int foo (enum comp_cat_tag tag) > +{ > + for (int i = 0; i < 4; ++i) > + { > + const char *p = comp_cat_info[tag].members[i]; > + if (!p) > + continue;; > + check (p); > + } > + return 0; > +} > + > +[[gnu::noipa]] enum comp_cat_tag > +get_index (void) > +{ > + return cc_strong_ordering; > +} > + > +int main (int argc, char **argv) > +{ > + foo (get_index ()); > + return 0; > +} > diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-from-cst-agg-3.c > b/gcc/testsuite/gcc.dg/tree-ssa/vrp-from-cst-agg-3.c > new file mode 100644 > index 00000000000..484099ca5a1 > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-from-cst-agg-3.c > @@ -0,0 +1,34 @@ > +/* { dg-do link } */ > +/* { dg-options "-O2" } */ > + > +volatile int gi; > + > +static const int values[3] = {5, 7, 11}; > + > +void link_error (void); > + > +[[gnu::noinline]] int foo (int index) > +{ > + for (int i = 0; i < 3; ++i) > + { > + const int v = values[index]; > + if (v <= 2 > + || v > 666) > + link_error (); > + else > + gi = v; > + } > + return 0; > +} > + > +[[gnu::noipa]] int > +get_index (void) > +{ > + return 1; > +} > + > +int main (int argc, char **argv) > +{ > + foo (get_index ()); > + return 0; > +} > diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-from-cst-agg-4.c > b/gcc/testsuite/gcc.dg/tree-ssa/vrp-from-cst-agg-4.c > new file mode 100644 > index 00000000000..7999ee1753a > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-from-cst-agg-4.c > @@ -0,0 +1,34 @@ > +/* { dg-do link } */ > +/* { dg-options "-O2" } */ > + > +volatile int gi; > + > +static const int values[25] = {5, 7, 11}; > + > +void link_error (void); > + > +[[gnu::noinline]] int foo (int index) > +{ > + for (int i = 0; i < 3; ++i) > + { > + const int v = values[index]; > + if (v <= -2 > + || v > 666) > + link_error (); > + else > + gi = v; > + } > + return 0; > +} > + > +[[gnu::noipa]] int > +get_index (void) > +{ > + return 1; > +} > + > +int main (int argc, char **argv) > +{ > + foo (get_index ()); > + return 0; > +} > -- > 2.51.1 >
