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?

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