On Monday, 13.07.2026 at 23:22, Jeffrey Law wrote:
On 7/10/2026 6:55 PM, Andrea Pinski wrote:
> On Fri, Jul 10, 2026 at 5:24 AM Ashley Chekhova <[email protected]> 
wrote:
On Monday, 06.07.2026 at 15:33, Richard Biener wrote:
On Mon, Jun 29, 2026 at 1:06 PM Ashley Chekhova <[email protected]> wrote:
Checks for strlen(s) == 0 could be rewritten as *s == 0 in the simple
case, but in complex cases (such as those involving variable
assignment), the optimization wouldn't be implemented. Fix this
by moving it over to forwprop from fold-const. Although, since
this currently only runs when PROP_last_full_fold is set, the
original code is kept in as well.
Can you tell why it's restricted to the last full fold?

Bootstrapped and tested on x86_64-pc-linux-gnu

           PR tree-optimization/92408

gcc/ChangeLog:
           * tree-ssa-forwprop.cc (optimize_strlen_comp): Rewrite
strlen(s) == 0 as *s == 0 and strlen(s) != 0 as *s != 0.
           * tree-ssa-forwprop.cc (simplify_builtin_call): Added call to
optimize_strlen_comp.

gcc/testsuite/ChangeLog:
           + gcc.dg/pr92408.c: New test.

Signed-off-by: Ashley Chekhova <[email protected]>
---
    gcc/testsuite/gcc.dg/pr92408.c | 60 ++++++++++++++++++++++++++++++++++
    gcc/tree-ssa-forwprop.cc       | 46 ++++++++++++++++++++++++++
    2 files changed, 106 insertions(+)
    create mode 100644 gcc/testsuite/gcc.dg/pr92408.c

diff --git a/gcc/testsuite/gcc.dg/pr92408.c b/gcc/testsuite/gcc.dg/pr92408.c
new file mode 100644
index 00000000000..f7adc7153cc
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/pr92408.c
@@ -0,0 +1,60 @@
+/* { dg-do compile } */
+
+/* { dg-options "-O2 -fdump-tree-forwprop2 -fdump-tree-optimized" } */
+
+/*
+  Two different checks are used here to ensure that this optimization
+  doesn't occur before PROP_last_full_fold is set.
+*/
+
+/* { dg-final { scan-tree-dump-times "\\*s" 1 "forwprop2" } } */
+/* { dg-final { scan-tree-dump-times "__builtin_strlen" 3 "forwprop2" }
} */
+
+/* { dg-final { scan-tree-dump-times "\\*s" 3 "optimized" } } */
+/* { dg-final { scan-tree-dump-times "__builtin_strlen" 1 "optimized" }
} */
+
+typedef __SIZE_TYPE__ size_t;
+void a (void);
+void b (void);
+
+void modified1 (const char *s)
+{
+  if (__builtin_strlen (s))   // folded to if (*s)
+    a ();
+}
+
+void modified2 (const char *s)
+{
+  /*
+    folded to
+    __SIZE_TYPE__ n = (*s);
+  */
+  __SIZE_TYPE__ n = __builtin_strlen (s);
+  if (n)
+    a ();
+}
+
+void unaffected1 (const char *s)
+{
+  /*
+    this shouldn't be folded.
+  */
+  __SIZE_TYPE__ n = __builtin_strlen (s);
+  if (n)
+    a ();
+  if (n > 5)
+    b ();
+}
+
+void modified3 (const char *s)
+{
+  /*
+    folded to
+    __SIZE_TYPE__ n = (*s);
+  */
+  __SIZE_TYPE__ n = __builtin_strlen (s);
+  if (n)
+    a ();
+  if (!n)
+    b ();
+}
\ No newline at end of file
diff --git a/gcc/tree-ssa-forwprop.cc b/gcc/tree-ssa-forwprop.cc
index 9bb001d2f63..1db6bf8ccd8 100644
--- a/gcc/tree-ssa-forwprop.cc
+++ b/gcc/tree-ssa-forwprop.cc
@@ -2454,6 +2454,50 @@ optimize_stack_restore (gimple_stmt_iterator
*gsi, gimple *call)
      return true;
    }

+
+/* Optimize
+   (strlen(s) == 0)
+   into
+   (*s == 0)
+
+   and do the same with
+   (strlen(s) != 0)
+   into
+   (*s != 0)
Please compat the comment, like to

    /* Optimizes strlen (s) ==/!= 0 to *s ==/!= 0.  */

+*/
+
+
+static bool
+optimize_strlen_comp (gimple_stmt_iterator *gsi, gimple *call)
+{
+  if (!fold_before_rtl_expansion_p ())
+    return false;
+
+  tree lhs = gimple_call_lhs (call);
+
less vertical space

+  if (lhs == NULL_TREE || use_in_zero_equality (lhs, true) == NULL)
+    return false;
+
+  /* The string passed to strlen. */
+  tree ptr = gimple_call_arg (call, 0);
+
+  /* Dereference the string. */
+  tree deref = build_simple_mem_ref (ptr);
This doesn't work reliably since pointer types are all equal in GIMPLE
so you could end up with a int * pointer here for which build_simple_mem_ref
would create an 'int' access.

You need to use sth like

      tree deref = fold_build2 (MEM_REF, char_type_node, ptr,
build_zero_cst (ptr_type_node));

+
+  /* Perform a type conversion if necessary. */
+  if (!useless_type_conversion_p (TREE_TYPE (lhs), TREE_TYPE (deref)))
And this should always be required.

+  {
+    deref = fold_convert_loc (gimple_location (call),
+                              TREE_TYPE (lhs),
+                              deref);
+  }
+
+  /* Replace the original call to strlen with the dereference we just
built. */
+  gimplify_and_update_call_from_tree (gsi, deref);
+
+  return true;
+}
+
    /* *GSI_P is a GIMPLE_CALL to a builtin function.
       Optimize
       memcpy (p, "abcd", 4);
@@ -2485,6 +2529,8 @@ simplify_builtin_call (gimple_stmt_iterator
*gsi_p, tree callee2, bool full_walk

      switch (DECL_FUNCTION_CODE (callee2))
        {
+    case BUILT_IN_STRLEN:
+      return optimize_strlen_comp (gsi_p, as_a<gcall*>(stmt2));
        case BUILT_IN_STACK_RESTORE:
          return optimize_stack_restore (gsi_p, as_a<gcall*>(stmt2));
        case BUILT_IN_MEMCMP:
--
2.53.0

Changes in v2:
   - Type conversion after dereferencing is now always required.
   - Fixed the issue regarding strlen being called on pointer-types other
than char *.
   - Added a test-case for that issue.

Can you tell why it's restricted to the last full fold?
Restriction to the last full fold comes at the recommendation of Drea
Pinski in comment 6 on the PR
(https://gcc.gnu.org/bugzilla/show_bug.cgi?id=92408#c6). Perhaps this
doesn't warrant inclusion in the test case? I'm impartial regardless.
> Yes, my suggestion was just in case the folding gets in the way of the
> strlen pass. I didn't know if the strlen pass would like the checking
> done like `*a == 0` or `strlen(a) == 0`. And I didn't want to
> introduce a regression due to that.
So given it was just one test affected (on every target), do we want to
try and be more aggressive by enabling the transformation earlier and
fix that one test?  Or would you prefer more conservatism here?

jeff
This test failure does seem to at-least be substantive, given that it represents a missed optimization.

When the transformation is applied early, the call to strlen is removed before the strlen pass can see it, and thus information about the string can no longer be gathered. Ordinarily for this test case, the compiler will see strlen (s) == 0 and use that to turn strcpy into memcpy, but since the strlen call is removed, it can no longer do so. In an ideal world, I imagine the compiler should be able to discern the string is empty given *s == 0, just as with strlen (s) == 0. Moreover, perhaps it should remove the strcpy of an empty string altogether. Regardless, without that information, some optimizations will be missed.

Would an "if *s == 0, the string is empty" (or perhaps "if s[i] == 0, the length of the string is less than or equal to i") analysis in strlen be feasible to implement? Is it even true? I think whether or not the last full fold restriction can be lifted comes down to these things.

Reply via email to