On Wed, May 27, 2026 at 7:59 AM Naveen SiddeGowda <[email protected]> wrote: > > The patch improves memset optimization when the length is known to be 0 or 1. > It uses Ranger information to recognize such cases, shrink-wraps the call on > the zero-length case and replaces the one-byte case with a direct byte store. > It also extends gimple_fold_builtin_memset to handle Ranger-proven singleton > lengths not just integer constants.
I wonder why this special-cases 1 and not zero or power-of-two lengths (bound by MOVE_MAX as memcpy inlining does or in the way memset folding already does when the length is constant)? In fact I wonder why gimple_fold_builtin_memset_len_one is needed at all? A little guarding should allow non-constant value for len == 1 to flow with the present folding code? Where does shrink-wrapping of memset help in practice? Can the shrink-wrapping part be split out from the folding part? > PR tree-optimization/102202 > > gcc/ChangeLog: > > PR tree-optimization/102202 > * gimple-fold.cc (gimple_fold_builtin_memset_len_one): New function. > (gimple_fold_builtin_memset): Call the function when length is 1. > * tree-call-cdce.cc: Include "tree-ssanames.h", "gimple-fold.h" > and "gimplify-me.h". > (memset_len_boolean_range_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. > * gcc.dg/pr102202.c: New test. > * gcc.target/aarch64/pr100518.c: Modify to handle the warning. > > Signed-off-by: Naveen SiddeGowda <[email protected]> > --- > gcc/gimple-fold.cc | 68 +++++++++- > gcc/testsuite/gcc.dg/pr102202-1.c | 14 +++ > gcc/testsuite/gcc.dg/pr102202.c | 54 ++++++++ > gcc/testsuite/gcc.target/aarch64/pr100518.c | 2 +- > gcc/tree-call-cdce.cc | 133 +++++++++++++++++++- > 5 files changed, 266 insertions(+), 5 deletions(-) > create mode 100644 gcc/testsuite/gcc.dg/pr102202-1.c > create mode 100644 gcc/testsuite/gcc.dg/pr102202.c > > diff --git a/gcc/gimple-fold.cc b/gcc/gimple-fold.cc > index a2f9de53855..64c637befd6 100644 > --- a/gcc/gimple-fold.cc > +++ b/gcc/gimple-fold.cc > @@ -1444,6 +1444,69 @@ gimple_fold_builtin_bzero (gimple_stmt_iterator *gsi) > return true; > } > > +static bool > +gimple_fold_builtin_memset_len_one (gimple_stmt_iterator *gsi, > + tree dest, tree c) > +{ > + gimple *stmt = gsi_stmt (*gsi); > + location_t loc = gimple_location (stmt); > + gimple_stmt_iterator ins_gsi = *gsi; > + > + if (!POINTER_TYPE_P (TREE_TYPE (dest))) > + return false; > + > + tree byte_type = unsigned_char_type_node; > + tree store_type = byte_type; > + > + /* Propagate qualifiers, e.g. volatile, from the destination's pointee > + type so the replacement store does not silently drop them. */ > + tree dest_ptype = TREE_TYPE (TREE_TYPE (dest)); > + if (dest_ptype) > + store_type = build_qualified_type (byte_type, TYPE_QUALS (dest_ptype)); > + > + tree byte_ptr_type = build_pointer_type_for_mode (store_type, ptr_mode, > true); > + > + /* Cast the destination pointer to a byte pointer for the MEM_REF. */ > + tree store_dest = gimple_convert (&ins_gsi, true, GSI_SAME_STMT, loc, > + byte_ptr_type, dest); > + > + store_dest = force_gimple_operand_gsi_1 (&ins_gsi, store_dest, > + is_gimple_mem_ref_addr, > + NULL_TREE, true, GSI_SAME_STMT); > + > + /* Build a byte MEM_REF at offset zero. */ > + tree mem = fold_build2_loc (loc, MEM_REF, store_type, store_dest, > + build_int_cst (byte_ptr_type, 0)); > + > + /* Convert the fill value to the byte value stored by memset. */ > + tree rhs = gimple_convert (&ins_gsi, true, GSI_SAME_STMT, loc, > + byte_type, c); > + > + gassign *store = gimple_build_assign (mem, rhs); > + gimple_move_vops (store, stmt); > + gimple_set_location (store, loc); > + copy_warning (store, stmt); > + > + tree lhs = gimple_call_lhs (stmt); > + if (!lhs) > + { > + gsi_replace (gsi, store, false); > + return true; > + } > + > + tree ret = gimple_convert (&ins_gsi, true, GSI_SAME_STMT, loc, > + TREE_TYPE (lhs), dest); > + gassign *ret_stmt = gimple_build_assign (lhs, ret); > + gimple_set_location (ret_stmt, loc); > + > + gimple_seq seq = NULL; > + gimple_seq_add_stmt (&seq, store); > + gimple_seq_add_stmt (&seq, ret_stmt); > + > + gsi_replace_with_seq (gsi, seq, false); > + return true; > +} > + > /* Fold function call to builtin memset or bzero at *GSI setting the > memory of size LEN to VAL. Return whether a simplification was made. */ > > @@ -1453,6 +1516,7 @@ gimple_fold_builtin_memset (gimple_stmt_iterator *gsi, > tree c, tree len) > gimple *stmt = gsi_stmt (*gsi); > tree etype; > unsigned HOST_WIDE_INT length, cval; > + tree dest = gimple_call_arg (stmt, 0); > > /* If the LEN parameter is zero, return DEST. */ > if (integer_zerop (len)) > @@ -1464,13 +1528,15 @@ gimple_fold_builtin_memset (gimple_stmt_iterator > *gsi, tree c, tree len) > if (!gimple_vdef (stmt) && gimple_in_ssa_p (cfun)) > return false; > > + if (integer_onep (len)) > + return gimple_fold_builtin_memset_len_one (gsi, dest, c); > + > if (! tree_fits_uhwi_p (len)) > return false; > > if (TREE_CODE (c) != INTEGER_CST) > return false; > > - tree dest = gimple_call_arg (stmt, 0); > tree var = dest; > if (TREE_CODE (var) != ADDR_EXPR) > return false; > diff --git a/gcc/testsuite/gcc.dg/pr102202-1.c > b/gcc/testsuite/gcc.dg/pr102202-1.c > new file mode 100644 > index 00000000000..a512d7bd555 > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/pr102202-1.c > @@ -0,0 +1,14 @@ > +/* PR tree-optimization/102202 */ > +/* { dg-do compile } */ > +/* { dg-options "-O2 -fdump-tree-optimized" } */ > + > +void > +g1 (int a, int c, char *d) > +{ > + if (a < 0 || a > 2) > + __builtin_unreachable (); > + > + __builtin_memset (d, c, a); > +} > + > +/* { dg-final { scan-tree-dump "__builtin_memset" "optimized" } } */ > diff --git a/gcc/testsuite/gcc.dg/pr102202.c b/gcc/testsuite/gcc.dg/pr102202.c > new file mode 100644 > index 00000000000..7e6673e168d > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/pr102202.c > @@ -0,0 +1,54 @@ > +/* 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); > +} > + > +void > +g4 (int a, int c, char *d) > +{ > + if (a < 0 || a > 1) > + return; > + > + __builtin_memset (d, c, a); > +} > + > +void * > +g5 (char *d, int c) > +{ > + return __builtin_memset (d, c, 1); > +} > + > +void > +g6 (char *d, int c) > +{ > + __builtin_memset (d, c, 1); > +} > + > +/* { dg-final { scan-tree-dump-not "__builtin_memset" "optimized" } } */ > +/* { dg-final { scan-tree-dump-times {MEM[^;\n\r]*= 0;} 1 "optimized" } } */ > +/* { dg-final { scan-tree-dump-times {MEM[^;\n\r]*= 1;} 1 "optimized" } } */ > +/* { dg-final { scan-tree-dump-times {\(unsigned char\) c_[0-9]+\(D\)} 4 > "optimized" } } */ > +/* { dg-final { scan-tree-dump-times {MEM[^;\n\r]*= _[0-9]+;} 4 "optimized" > } } */ > diff --git a/gcc/testsuite/gcc.target/aarch64/pr100518.c > b/gcc/testsuite/gcc.target/aarch64/pr100518.c > index 177991cfb22..eb8d947185a 100644 > --- a/gcc/testsuite/gcc.target/aarch64/pr100518.c > +++ b/gcc/testsuite/gcc.target/aarch64/pr100518.c > @@ -5,5 +5,5 @@ int unsigned_range_min, unsigned_range_max, a11___trans_tmp_1; > > void a11() { > a11___trans_tmp_1 = unsigned_range_max < unsigned_range_min; > - __builtin_memset((char *)1, 0, a11___trans_tmp_1); > + __builtin_memset((char *)1, 0, a11___trans_tmp_1); /* { dg-warning > "writing 1 byte into a region of size 0" } */ > } > diff --git a/gcc/tree-call-cdce.cc b/gcc/tree-call-cdce.cc > index 2be891a7222..5a4ced585f3 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,6 +1258,124 @@ use_internal_fn (gcall *call) > is_arg_conds ? new_call : NULL); > } > > +/* Return true if LEN is a SSA_NAME known to have a boolean range. */ > + > +static bool > +memset_len_boolean_range_p (tree len, gimple *stmt) > +{ > + 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 a SSA_NAME known to have a boolean range. */ > + > +static bool > +can_shrink_wrap_memset_p (gcall *call) > +{ > + tree fndecl = gimple_call_fndecl (call); > + > + if (!fndecl || !fndecl_built_in_p (fndecl, BUILT_IN_MEMSET)) > + return false; > + > + if (gimple_call_num_args (call) != 3) > + return false; > + > + /* memset should not be declared as pure/const. */ > + if (!gimple_vdef (call)) > + return false; > + > + return memset_len_boolean_range_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); > + > + 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. */ > > @@ -1267,8 +1388,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 +1454,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 >
