Hi

the ability of ranger to infer ranges from loads from contants and
even string literals has gratly expanded recently but currently it
still cannot say anything about _5 in:

   _4 = "test" + _3;
   _5 = *_4;

This patch pattern matches this sequence and when detected (and the
string length is within the limit), builds an artificial ARRAY_REF
that is then sent down the existing machinery.  Alternatively, we
could define that having an integer SSA_NAME in component_path means
that it should be treated like an ARRAY_REF, but that also feels a bit
ugly.

Bootstrapped on x86_64, the testsuite is still running.  OK if it
passes?

Thanks,

Martin


gcc/ChangeLog:

2026-11-13  Martin Jambor  <[email protected]>

        * gimple-range-fold.cc
        (fold_using_range::range_from_readonly_var): Pattern match offseted
        load from a STRING_CST spread over two gimple statements.

gcc/testsuite/ChangeLog:

2026-11-13  Martin Jambor  <[email protected]>

        * gcc.dg/tree-ssa/vrp-from-cst-agg-20.c: New test.
---
 gcc/gimple-range-fold.cc                      | 68 +++++++++++++++----
 .../gcc.dg/tree-ssa/vrp-from-cst-agg-20.c     | 33 +++++++++
 2 files changed, 86 insertions(+), 15 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-from-cst-agg-20.c

diff --git a/gcc/gimple-range-fold.cc b/gcc/gimple-range-fold.cc
index 72a74a737cb..cb5d472d4d6 100644
--- a/gcc/gimple-range-fold.cc
+++ b/gcc/gimple-range-fold.cc
@@ -1387,26 +1387,64 @@ fold_using_range::range_from_readonly_var (vrange &r, 
gimple *stmt,
 
   // The component path of the reference, from the aggregate out.
   auto_vec <tree, 4> component_path;
-  while (TREE_CODE (t) == ARRAY_REF
-        || TREE_CODE (t) == COMPONENT_REF)
-    {
-      component_path.safe_push (t);
-      t = TREE_OPERAND (t, 0);
-    }
-  component_path.reverse ();
-  if (component_path.is_empty ()
-      || (TREE_CODE (t) != VAR_DECL
-         && TREE_CODE (t) != CONST_DECL))
-    return false;
+  tree ctor = NULL_TREE;
+  bool fake = false;
 
-  if (!tree_fits_uhwi_p (DECL_SIZE_UNIT (t))
-      || tree_to_uhwi (DECL_SIZE_UNIT (t)) > limit)
-    return false;
+  // The code below only handles accesses through handled_components but a 
common
+  // way of looking at a character of string looks like this:
+  //   _4 = "test" + _3;
+  //   _5 = *_4;
+  // When looking for a value range of _5, construct an artificial ARRAY_REF
+  // into the string constant.
+  if (TREE_CODE (t) == MEM_REF
+      && TREE_CODE (TREE_OPERAND (t, 0)) == SSA_NAME
+      && !SSA_NAME_IS_DEFAULT_DEF (TREE_OPERAND (t, 0))
+      && integer_zerop (TREE_OPERAND (t, 1)))
+    {
+      gimple *def = SSA_NAME_DEF_STMT (TREE_OPERAND (t, 0));
+      if (gimple_code (def) == GIMPLE_ASSIGN
+         && gimple_assign_rhs_code (def) == POINTER_PLUS_EXPR)
+       {
+         tree def_op_rhs1 = gimple_assign_rhs1 (def);
+         if (TREE_CODE (def_op_rhs1) == ADDR_EXPR
+             && TREE_CODE (TREE_OPERAND (def_op_rhs1, 0)) == STRING_CST
+             && ((unsigned) TREE_STRING_LENGTH (TREE_OPERAND (def_op_rhs1, 0))
+                 <= limit))
+           {
+             ctor = TREE_OPERAND (def_op_rhs1, 0);
+             tree ft = build4 (ARRAY_REF, type,
+                               TREE_OPERAND (def_op_rhs1, 0),
+                               gimple_assign_rhs2 (def),
+                               NULL_TREE, NULL_TREE);
+             component_path.safe_push (ft);
+             fake = true;
+           }
+       }
+    }
+
+  if (!fake)
+    {
+      while (TREE_CODE (t) == ARRAY_REF
+            || TREE_CODE (t) == COMPONENT_REF)
+       {
+         component_path.safe_push (t);
+         t = TREE_OPERAND (t, 0);
+       }
+      component_path.reverse ();
+      if (component_path.is_empty ()
+         || (TREE_CODE (t) != VAR_DECL
+             && TREE_CODE (t) != CONST_DECL))
+       return false;
+
+      if (!tree_fits_uhwi_p (DECL_SIZE_UNIT (t))
+         || tree_to_uhwi (DECL_SIZE_UNIT (t)) > limit)
+       return false;
+      ctor = ctor_for_folding (t);
+    }
 
   /* TODO: We perhaps should try to handle at least some cases when the
      declaration is wrapped in a MEM_REF, but we need to be careful to look at
      the right part of the constructor then.  */
-  tree ctor = ctor_for_folding (t);
   if (!ctor
       || (TREE_CODE (ctor) != CONSTRUCTOR
          && TREE_CODE (ctor) != STRING_CST))
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-from-cst-agg-20.c 
b/gcc/testsuite/gcc.dg/tree-ssa/vrp-from-cst-agg-20.c
new file mode 100644
index 00000000000..ef38023506f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-from-cst-agg-20.c
@@ -0,0 +1,33 @@
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+
+int idx;
+void unwanted_call (void);
+
+#if 0
+int
+#else
+inline int __attribute__((always_inline))
+#endif
+foo (char *s)
+{
+  if (s[idx] == '%')
+    {
+      unwanted_call ();
+      unwanted_call ();
+      unwanted_call ();
+      unwanted_call ();
+      unwanted_call ();
+      unwanted_call ();
+      unwanted_call ();
+      return 1;
+    }
+  return 0;
+}
+
+int bar (void)
+{
+  return foo ("test");
+}
+
+/* { dg-final { scan-assembler-not "unwanted_call" } } */
-- 
2.56.0

Reply via email to