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

Reply via email to