On Wed, May 6, 2026 at 3:32 AM Naveen
<[email protected]> wrote:
>
> PR tree-optimization/102202
>
> gcc/ChangeLog:
>
>         PR tree-optimization/102202
>         * tree-call-cdce.cc: Include "tree-ssanames.h", "gimple-fold.h"
>         and "gimplify-me.h".
>         (memset_len_zero_or_one_p): New function.
>         (can_shrink_wrap_memset_p): New function.
>         (gen_memset_conditions): New function.
>         (replace_memset_call_with_store): New function.
>         (shrink_wrap_one_memset_call): New function.
>         (shrink_wrap_conditional_dead_built_in_calls): Dispatch to
>         shrink_wrap_one_memset_call for memset calls eligible for the [0, 1]
>         length transform, ahead of the generic LHS and range-test paths.
>         (pass_call_cdce::execute): Collect memset calls satisfying
>         can_shrink_wrap_memset_p as shrink-wrap candidates.
>
> gcc/testsuite/ChangeLog:
>
>         * gcc.dg/pr102202.c: New test.
>
> Signed-off-by: Naveen <[email protected]>
> ---
>  gcc/testsuite/gcc.dg/pr102202.c |  31 +++++++
>  gcc/tree-call-cdce.cc           | 138 +++++++++++++++++++++++++++++++-
>  2 files changed, 165 insertions(+), 4 deletions(-)
>  create mode 100644 gcc/testsuite/gcc.dg/pr102202.c
>
> diff --git a/gcc/testsuite/gcc.dg/pr102202.c b/gcc/testsuite/gcc.dg/pr102202.c
> new file mode 100644
> index 00000000000..6d3306370c0
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/pr102202.c
> @@ -0,0 +1,31 @@
> +/* PR tree-optimization/102202 */
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -fdump-tree-optimized" } */
> +
> +void
> +g1 (int a, char *d)
> +{
> +  if (a < 0 || a > 1)
> +    __builtin_unreachable ();
> +  __builtin_memset (d, 0, a);
> +}
> +
> +char *
> +g2 (unsigned a, char *d)
> +{
> +  return __builtin_memset (d, 1, a & 1);
> +}
> +
> +void
> +g3 (int a, int c, char *d)
> +{
> +  if (a < 0 || a > 1)
> +    __builtin_unreachable ();
> +  __builtin_memset (d, c, a);
> +}
> +
> +/* { dg-final { scan-tree-dump-times {MEM[^;\n]*= 0;} 1 "optimized" } } */
> +/* { dg-final { scan-tree-dump-times {MEM[^;\n]*= 1;} 1 "optimized" } } */
> +/* { dg-final { scan-tree-dump-times {return d_[0-9]} 1 "optimized" } } */
> +/* { dg-final { scan-tree-dump-times {MEM[^;\n]*=.*c_[0-9]+\(D\)} 1 
> "optimized" } } */
> +/* { dg-final { scan-tree-dump-not "MEMSET" "optimized" } } */


Maybe add a few more testcases. Like:
```
void
g4 (int a, int c, char *d)
{
  if (a < 0 || a > 1)
     return;
 __builtin_memset (d, c, a);
}
```
And ones which are not shrink wrap, e.g. the range is [0,2].


> diff --git a/gcc/tree-call-cdce.cc b/gcc/tree-call-cdce.cc
> index 2be891a7222..6e4ebd00765 100644
> --- a/gcc/tree-call-cdce.cc
> +++ b/gcc/tree-call-cdce.cc
> @@ -37,6 +37,9 @@ along with GCC; see the file COPYING3.  If not see
>  #include "internal-fn.h"
>  #include "tree-dfa.h"
>  #include "tree-eh.h"
> +#include "tree-ssanames.h"
> +#include "gimple-fold.h"
> +#include "gimplify-me.h"
>
>
>  /* This pass serves two closely-related purposes:
> @@ -1255,9 +1258,130 @@ use_internal_fn (gcall *call)
>                                             is_arg_conds ? new_call : NULL);
>  }
>
> +/* Return true if LEN is a constant zero or one or an SSA_NAME known to have
> +   a boolean range.  */
> +
> +static bool
> +memset_len_zero_or_one_p (tree len, gimple *stmt)
> +{
> +  if (integer_zerop (len) || integer_onep (len))
> +    return true;

We can remove the constant cases here. 0 will be folded away. 1 should
have been simplified to just a store but it looks like it is not.
Handle that case first in gimple-fold.cc and then remove the constant
check here.

> +
> +  if (TREE_CODE (len) != SSA_NAME || !INTEGRAL_TYPE_P (TREE_TYPE (len)))
> +    return false;
> +  return ssa_name_has_boolean_range (len, stmt);
> +}
> +
> +/* Return true if CALL is a memset that may be shrink-wrapped based on LEN
> +   being known to be zero or one.  */
> +
> +static bool
> +can_shrink_wrap_memset_p (gcall *call)
> +{
> +  tree fndecl = gimple_call_fndecl (call);
> +
> +  if (!fndecl
> +      || !fndecl_built_in_p (fndecl, BUILT_IN_NORMAL)
> +      || DECL_FUNCTION_CODE (fndecl) != BUILT_IN_MEMSET)
This can be simply:
if (!fndecl || !fndecl_built_in_p (fndecl, BUILT_IN_MEMSET))


> +    return false;
> +
> +  /* The replacement store needs to preserve the original memory VDEF.  */

Maybe better is "memset should not declared as pure/const.  ".
You should also have a check for `gimple_call_num_args (call) == 3` here.

> +  if (!gimple_vdef (call))
> +    return false;
> +
> +  return memset_len_zero_or_one_p (gimple_call_arg (call, 2), call);
> +}
> +
> +/* Generate the condition vector used to guard a memset call whose length is
> +   known to be in [0, 1].  */
> +
> +static void
> +gen_memset_conditions (gcall *call, vec<gimple *> &conds, unsigned *nconds)
> +{
> +  tree len = gimple_call_arg (call, 2);
> +  tree zero = build_zero_cst (TREE_TYPE (len));
> +
> +  gcc_assert (nconds);
> +  *nconds = 0;
> +
> +  conds.quick_push (gimple_build_cond (EQ_EXPR, len, zero,
> +                                      NULL_TREE, NULL_TREE));
> +  *nconds = 1;
> +}
> +
> +/* Replace CALL known to be a length-one memset with a single-byte store.
> +   The destination is converted to unsigned char * and the fill value to the
> +   destination byte type using gimple_convert.  */
> +
> +static void
> +replace_memset_call_with_store (gcall *call)
> +{
> +  tree dest = gimple_call_arg (call, 0);
> +  tree c = gimple_call_arg (call, 1);
> +  location_t loc = gimple_location (call);
> +
> +  tree byte_type = unsigned_char_type_node;
> +  tree byte_ptr_type = build_pointer_type (byte_type);
> +
> +  gimple_stmt_iterator gsi = gsi_for_stmt (call);
> +  dest = gimple_convert (&gsi, true, GSI_SAME_STMT, loc, byte_ptr_type, 
> dest);
> +  dest = force_gimple_operand_gsi_1 (&gsi, dest, is_gimple_mem_ref_addr,
> +                                    NULL_TREE, true, GSI_SAME_STMT);
> +
> +  tree mem = build2 (MEM_REF, byte_type, dest,
> +                    build_int_cst (ptr_type_node, 0));
> +
> +  /* Memset writes the low byte of the fill value to each destination type.  
> */
> +  tree rhs = gimple_convert (&gsi, true, GSI_SAME_STMT, loc, byte_type, c);
> +
> +  gassign *store = gimple_build_assign (mem, rhs);
> +  gimple_move_vops (store, call);
> +  gimple_set_location (store, loc);
> +
> +  copy_warning (store, call);
> +  suppress_warning (store, OPT_Wstringop_overflow_);
I am curious why you added the suppression of -Wstringop-overflow here?
Not saying what you did is wrong, just curious the reason.

Thanks,
Andrea

> +
> +  gsi = gsi_for_stmt (call);
> +  gsi_replace (&gsi, store, false);
> +}
> +
> +/* Shrink wrap a memset call whose length is known to be in [0, 1].
> +   If the call has an LHS, preserve the builtin return value by assigning the
> +   destination pointer to the LHS before the call transfer.  */
> +
> +static void
> +shrink_wrap_one_memset_call (gcall *call)
> +{
> +  tree lhs = gimple_call_lhs (call);
> +
> +  if (lhs)
> +    {
> +      tree dest = gimple_call_arg (call, 0);
> +      location_t loc = gimple_location (call);
> +      gimple_stmt_iterator gsi = gsi_for_stmt (call);
> +
> +      dest = gimple_convert (&gsi, true, GSI_SAME_STMT, loc,
> +                            TREE_TYPE (lhs), dest);
> +      gassign *stmt = gimple_build_assign (lhs, dest);
> +      gimple_set_location (stmt, loc);
> +
> +      gsi_insert_before (&gsi, stmt, GSI_SAME_STMT);
> +
> +      gimple_call_set_lhs (call, NULL_TREE);
> +      SSA_NAME_DEF_STMT (lhs) = stmt;
> +    }
> +
> +  unsigned nconds = 0;
> +  auto_vec<gimple *, 1> conds;
> +  gen_memset_conditions (call, conds, &nconds);
> +  gcc_assert (nconds != 0);
> +
> +  shrink_wrap_one_built_in_call_with_conds (call, conds, nconds);
> +  replace_memset_call_with_store (call);
> +}
> +
>  /* The top level function for conditional dead code shrink
>     wrapping transformation.  */
> -
>  static void
>  shrink_wrap_conditional_dead_built_in_calls (const vec<gcall *> &calls)
>  {
> @@ -1267,8 +1391,13 @@ shrink_wrap_conditional_dead_built_in_calls (const 
> vec<gcall *> &calls)
>    for (; i < n ; i++)
>      {
>        gcall *bi_call = calls[i];
> -      if (gimple_call_lhs (bi_call))
> +
> +      /* Use the memset specific transform for the [0, 1] length case.  */
> +      if (can_shrink_wrap_memset_p (bi_call))
> +       shrink_wrap_one_memset_call (bi_call);
> +      else if (gimple_call_lhs (bi_call))
>         use_internal_fn (bi_call);
> +      /* Other eligible calls are shrink wrapped by the generic path.  */
>        else
>         shrink_wrap_one_built_in_call (bi_call);
>      }
> @@ -1328,9 +1457,10 @@ pass_call_cdce::execute (function *fun)
>           gcall *stmt = dyn_cast <gcall *> (gsi_stmt (i));
>            if (stmt
>               && gimple_call_builtin_p (stmt, BUILT_IN_NORMAL)
> -             && (gimple_call_lhs (stmt)
> +             && (can_shrink_wrap_memset_p (stmt)
> +             || (gimple_call_lhs (stmt)
>                   ? can_use_internal_fn (stmt)
> -                 : can_test_argument_range (stmt))
> +                 : can_test_argument_range (stmt)))
>               && can_guard_call_p (stmt))
>              {
>                if (dump_file && (dump_flags & TDF_DETAILS))
> --
> 2.34.1
>

Reply via email to