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
>

Reply via email to