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. Thanks, Drea > > --- > 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. > > 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 | 81 ++++++++++++++++++++++++++++++++++ > gcc/tree-ssa-forwprop.cc | 30 +++++++++++++ > 2 files changed, 111 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..380c72619cf > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/pr92408.c > @@ -0,0 +1,81 @@ > +/* { dg-do compile } */ > + > +/* { dg-options "-O2 -Wno-error=incompatible-pointer-types > -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" 4 "forwprop2" } > } */ > + > +/* { dg-final { scan-tree-dump-times "\\*s" 1 "optimized" } } */ > +/* { dg-final { scan-tree-dump-times "void \\*\\)s" 2 "optimized" } } */ > +/* { dg-final { scan-tree-dump-times "char.*?void \\*\\)&s" 1 > "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 (); > +} > + > +int main (void) { > + volatile char s[] = "\0\1\1\1"; > + > + // This ought to dereference as a char * pointer. > + __SIZE_TYPE__ n = __builtin_strlen ((int *) s); /* { dg-warning > "incompatible pointer type" } */ > + > + /* > + If the strlen call above is turned into ((int *) s)*, then it will > see the > + four-byte string "\0\1\1\1" and read it as 0x01010100. This is > undesirable > + given that the original strlen call would have just produced a 0. > + > + So, this tests to ensure that the value is 0, as we would expect. > + */ > + if (n) > + return 1; > + else > + return 0; > +} > \ No newline at end of file > diff --git a/gcc/tree-ssa-forwprop.cc b/gcc/tree-ssa-forwprop.cc > index 9bb001d2f63..c372ff9d66e 100644 > --- a/gcc/tree-ssa-forwprop.cc > +++ b/gcc/tree-ssa-forwprop.cc > @@ -2454,6 +2454,34 @@ optimize_stack_restore (gimple_stmt_iterator > *gsi, gimple *call) > return true; > } > > +/* 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); > + 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 = fold_build2 (MEM_REF, char_type_node, ptr, > + build_zero_cst (ptr_type_node)); > + > + /* Perform a type conversion. */ > + 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 +2513,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
