https://gcc.gnu.org/g:a8072f31bc90000b3b8a2c0c7819710cf924727a
commit r17-2113-ga8072f31bc90000b3b8a2c0c7819710cf924727a Author: Naveen <[email protected]> Date: Fri Jul 3 05:24:04 2026 -0700 tree-optimization: fold memset with length in [0, 1] to conditional store [PR102202] 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. gcc/ChangeLog: PR tree-optimization/102202 * tree-call-cdce.cc: Include "tree-ssanames.h", "gimple-fold.h". (len_has_boolean_range_p): New function. (can_shrink_wrap_len_p): New function. (gen_zero_len_conditions): New function. (shrink_wrap_len_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: PR tree-optimization/102202 * gcc.dg/pr102202-1.c: New test. * gcc.dg/pr102202.c: New test. * gcc.target/aarch64/pr100518.c: Modify to handle the warning. Signed-off-by: Naveen <[email protected]> Diff: --- gcc/testsuite/gcc.dg/pr102202-1.c | 16 ++++ gcc/testsuite/gcc.dg/pr102202.c | 19 +++++ gcc/testsuite/gcc.target/aarch64/pr100518.c | 2 +- gcc/tree-call-cdce.cc | 110 ++++++++++++++++++++++++++-- 4 files changed, 141 insertions(+), 6 deletions(-) diff --git a/gcc/testsuite/gcc.dg/pr102202-1.c b/gcc/testsuite/gcc.dg/pr102202-1.c new file mode 100644 index 000000000000..480bf749ef09 --- /dev/null +++ b/gcc/testsuite/gcc.dg/pr102202-1.c @@ -0,0 +1,16 @@ +/* PR tree-optimization/102202 */ +/* { dg-do compile } */ +/* { dg-options "-O2 -fdump-tree-optimized" } */ + +/* A [0, 2] length is not a boolean range, so the call must be left alone. */ + +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 000000000000..3aca9815d830 --- /dev/null +++ b/gcc/testsuite/gcc.dg/pr102202.c @@ -0,0 +1,19 @@ +/* PR tree-optimization/102202 */ +/* { dg-do compile } */ +/* { dg-options "-O2 -fdump-tree-cdce-details" } */ + +void +g1 (unsigned int n, int c, char *d) +{ + unsigned int len = n & 1; + __builtin_memset (d, c, len); +} + +char * +g2 (unsigned int n, int c, char *d) +{ + unsigned int len = n & 1; + return __builtin_memset (d, c, len); +} + +/* { dg-final { scan-tree-dump-times "function call is shrink-wrapped into error conditions" 2 "cdce" } } */ diff --git a/gcc/testsuite/gcc.target/aarch64/pr100518.c b/gcc/testsuite/gcc.target/aarch64/pr100518.c index 177991cfb228..eb8d947185a5 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 2be891a7222a..144f485909cf 100644 --- a/gcc/tree-call-cdce.cc +++ b/gcc/tree-call-cdce.cc @@ -37,7 +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" + /* This pass serves two closely-related purposes: @@ -1255,6 +1257,97 @@ use_internal_fn (gcall *call) is_arg_conds ? new_call : NULL); } +/* Return true if LEN is an SSA_NAME known to have a boolean range, i.e. its + value is provably in [0, 1]. */ + +static bool +len_has_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 supported length-taking builtin whose length + argument is an SSA name known to have a boolean range. On success, + set LEN_ARG to the argument index of the length. */ + +static bool +can_shrink_wrap_len_p (gcall *call, unsigned *len_arg) +{ + if (!gimple_call_builtin_p (call, BUILT_IN_MEMSET) + || !gimple_vdef (call)) + return false; + + constexpr unsigned memset_len_arg = 2; + if (gimple_call_num_args (call) <= memset_len_arg) + return false; + + tree len = gimple_call_arg (call, memset_len_arg); + if (!len_has_boolean_range_p (len, call)) + return false; + + *len_arg = memset_len_arg; + return true; +} + +/* Generate the condition vector that guards a call whose LEN is known to be + in [0, 1]: skip the call when LEN is zero. */ + +static void +gen_zero_len_conditions (tree len, vec<gimple *> &conds, unsigned *nconds) +{ + tree zero = build_zero_cst (TREE_TYPE (len)); + + gcc_assert (nconds); + conds.quick_push (gimple_build_cond (EQ_EXPR, len, zero, + NULL_TREE, NULL_TREE)); + *nconds = 1; +} + +/* Shrink-wrap CALL whose LEN_ARG argument is known to be in [0, 1]. + ZERO_LEN_RESULT is the value of the call result when its length is zero. + On the guarded path the length is one, so pin it to a constant for + subsequent folding. */ + +static void +shrink_wrap_len_call (gcall *call, unsigned len_arg, tree zero_len_result) +{ + tree lhs = gimple_call_lhs (call); + + /* Define the result on both the guarded and bypass paths before wrapping. */ + if (lhs) + { + gcc_assert (zero_len_result); + location_t loc = gimple_location (call); + gimple_stmt_iterator gsi = gsi_for_stmt (call); + + zero_len_result = gimple_convert (&gsi, true, GSI_SAME_STMT, loc, + TREE_TYPE (lhs), zero_len_result); + gassign *stmt = gimple_build_assign (lhs, zero_len_result); + 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; + update_stmt (call); + } + + tree len = gimple_call_arg (call, len_arg); + unsigned nconds = 0; + auto_vec<gimple *, 1> conds; + gen_zero_len_conditions (len, conds, &nconds); + gcc_assert (nconds != 0); + + shrink_wrap_one_built_in_call_with_conds (call, conds, nconds); + + /* On the guarded path the length is one. */ + gimple_call_set_arg (call, len_arg, build_one_cst (TREE_TYPE (len))); + update_stmt (call); + gimple_stmt_iterator gsi = gsi_for_stmt (call); + fold_stmt (&gsi); +} + /* The top level function for conditional dead code shrink wrapping transformation. */ @@ -1267,7 +1360,12 @@ 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)) + unsigned len_arg; + + /* memset returns its destination pointer on the zero-length path. */ + if (can_shrink_wrap_len_p (bi_call, &len_arg)) + shrink_wrap_len_call (bi_call, len_arg, gimple_call_arg (bi_call, 0)); + else if (gimple_call_lhs (bi_call)) use_internal_fn (bi_call); else shrink_wrap_one_built_in_call (bi_call); @@ -1326,11 +1424,13 @@ pass_call_cdce::execute (function *fun) for (i = gsi_start_bb (bb); !gsi_end_p (i); gsi_next (&i)) { gcall *stmt = dyn_cast <gcall *> (gsi_stmt (i)); + unsigned len_arg; if (stmt && gimple_call_builtin_p (stmt, BUILT_IN_NORMAL) - && (gimple_call_lhs (stmt) - ? can_use_internal_fn (stmt) - : can_test_argument_range (stmt)) + && (can_shrink_wrap_len_p (stmt, &len_arg) + || (gimple_call_lhs (stmt) + ? can_use_internal_fn (stmt) + : can_test_argument_range (stmt))) && can_guard_call_p (stmt)) { if (dump_file && (dump_flags & TDF_DETAILS))
