The PROP_warn_access_done guard inside gimple_fold_builtin_memset is dropped. The multi-byte arbitrary-pointer fold is now gated on a new fold_multi_byte parameter that is only passed true from gimple_isel_builtin_call. check_bounds_or_overlap now returns false for any violation without the OPT_Wrestrict exception.
The tree-cfgcleanup.cc sweep and PROP_warn_access_done property are dropped entirely. gimple_isel_builtin_call now handles BUILT_IN_MEMSET by calling gimple_fold_builtin_memset. The pass ordering in passes.def places ISEL right after the final access-warning pass. -Wstringop-overflow diagnostics are issued before the fold preserving the regression fixes. NEXT_PASS (pass_warn_access, /*early=*/false, /*check_dangling=*/false); NEXT_PASS (pass_gimple_isel); <-- fold fires here The existing guard at the top of gimple_isel_builtin_call covers the -O0 concern with no new code. No extra IL walk is needed; ISEL already iterates every statement. if (!optimize || optimize_debug) return; Bootstrapped and regtested on x86_64 and aarch64. No new regressions. 2026-09-21 Naveen <[email protected]> gcc/ChangeLog: PR tree-optimization/102202 * gimple-fold.cc (gimple_fold_builtin_memset): Add fold_multi_byte parameter (default false). Gate multi-byte arbitrary-pointer fold on fold_multi_byte and PROP_objsz; exclude ADDR_EXPR destinations from the multi-byte path to preserve the existing typed-store fold below. Return false for any check_bounds_or_overlap violation. * gimple-fold.h (gimple_fold_builtin_memset): Declare new fold_multi_byte parameter with default false. * gimple-isel.cc (gimple_isel_builtin_call): Call gimple_fold_builtin_memset with fold_multi_byte true for BUILT_IN_MEMSET so the fold fires after pass_warn_access. gcc/testsuite/ChangeLog: PR tree-optimization/102202 * gcc.dg/pr102202-fold-warn.c: New test. * gcc.dg/pr102202-fold-zero-n.c: New test. * gcc.dg/pr86010-2.c: Accept an inline scalar store. * gcc.dg/pr86010.c: Likewise. * gcc.dg/tree-ssa/calloc-5.c: Accept an inline scalar store. * gcc.target/aarch64/pr102202-fold-no-mode.c: New test. Signed-off-by: Naveen <[email protected]> --- gcc/gimple-fold.cc | 181 +++++++++++++----- gcc/gimple-fold.h | 2 + gcc/gimple-isel.cc | 11 +- gcc/passes.def | 2 +- gcc/testsuite/gcc.dg/pr102202-fold-warn.c | 13 ++ gcc/testsuite/gcc.dg/pr102202-fold-zero-n.c | 55 ++++++ gcc/testsuite/gcc.dg/pr86010-2.c | 4 +- gcc/testsuite/gcc.dg/pr86010.c | 4 +- gcc/testsuite/gcc.dg/tree-ssa/calloc-5.c | 8 +- .../aarch64/pr102202-fold-no-mode.c | 12 ++ 10 files changed, 230 insertions(+), 62 deletions(-) create mode 100644 gcc/testsuite/gcc.dg/pr102202-fold-warn.c create mode 100644 gcc/testsuite/gcc.dg/pr102202-fold-zero-n.c create mode 100644 gcc/testsuite/gcc.target/aarch64/pr102202-fold-no-mode.c diff --git a/gcc/gimple-fold.cc b/gcc/gimple-fold.cc index 3f4c979793b..c2f8a15ebbe 100644 --- a/gcc/gimple-fold.cc +++ b/gcc/gimple-fold.cc @@ -1458,10 +1458,12 @@ gimple_fold_builtin_bzero (gimple_stmt_iterator *gsi) /* Fold function call to builtin memset or bzero at *GSI setting the memory of size LEN to VAL. Return whether a simplification was made. */ -static bool -gimple_fold_builtin_memset (gimple_stmt_iterator *gsi, tree c, tree len) +bool +gimple_fold_builtin_memset (gimple_stmt_iterator *gsi, bool fold_multi_byte) { gimple *stmt = gsi_stmt (*gsi); + tree c = gimple_call_arg (stmt, 1); + tree len = gimple_call_arg (stmt, 2); tree etype; unsigned HOST_WIDE_INT length, cval; @@ -1481,62 +1483,139 @@ gimple_fold_builtin_memset (gimple_stmt_iterator *gsi, tree c, tree len) length = tree_to_uhwi (len); tree dest = gimple_call_arg (stmt, 0); - if (length == 1 - && POINTER_TYPE_P (TREE_TYPE (dest))) + if (POINTER_TYPE_P (TREE_TYPE (dest))) { - /* Keep the original call until object-size analysis has inspected it. */ - if (!(cfun->curr_properties & PROP_objsz)) + /* Keep length-one calls until object-size analysis has inspected them. + Multi-byte arbitrary-pointer folds are enabled explicitly by ISEL, + after the final access-warning pass. The existing ADDR_EXPR fold + below remains available throughout. */ + if (length == 1 && !(cfun->curr_properties & PROP_objsz)) return false; - /* Detect out-of-bounds accesses without issuing warnings. - Avoid folding out-of-bounds accesses but to avoid false - positives for unreachable code defer warning until after - DCE has worked its magic. - -Wrestrict is still diagnosed. */ - if (int warning = check_bounds_or_overlap (as_a <gcall *>(stmt), - dest, NULL_TREE, len, - NULL_TREE, false, false)) - if (warning != OPT_Wrestrict) - return false; + if (cfun->curr_properties & PROP_objsz) + { + unsigned int dest_align = get_pointer_alignment (dest); + machine_mode mode = VOIDmode; + etype = NULL_TREE; + + /* Preserve the established length-one timing after object-size + analysis. Only the new wider fold needs delaying for late + range-based diagnostics. */ + if (length == 1) + etype = unsigned_char_type_node; + else if (fold_multi_byte + && TREE_CODE (dest) != ADDR_EXPR + && length <= MOVE_MAX + && length <= HOST_BITS_PER_WIDE_INT / BITS_PER_UNIT) + { + unsigned HOST_WIDE_INT bits = length * BITS_PER_UNIT; + if (int_mode_for_size (bits, 0).exists () + && bitwise_mode_for_size (bits).exists (&mode) + && known_eq (GET_MODE_BITSIZE (mode), bits) + /* If DEST is not naturally aligned, require an efficient + unaligned store or a movmisalign pattern. */ + && (dest_align >= GET_MODE_ALIGNMENT (mode) + || !targetm.slow_unaligned_access (mode, dest_align) + || (optab_handler (movmisalign_optab, mode) + != CODE_FOR_nothing))) + { + etype = bitwise_type_for_mode (mode); + if (etype && !INTEGRAL_TYPE_P (etype)) + etype = NULL_TREE; + if (etype && dest_align < GET_MODE_ALIGNMENT (mode)) + etype = build_aligned_type (etype, dest_align); + } + } - etype = unsigned_char_type_node; - tree ptype = TREE_TYPE (TREE_TYPE (dest)); - if (TYPE_VOLATILE (ptype)) - etype = build_qualified_type (etype, TYPE_QUAL_VOLATILE); + if (etype) + { + /* Leave accesses rejected by the bounds checker for the warning + pass. Remaining calls are folded in ISEL after pass_warn_access + has inspected the original call. */ + if (check_bounds_or_overlap (as_a <gcall *>(stmt), + dest, NULL_TREE, len, + NULL_TREE, false, false)) + return false; - location_t loc = gimple_location (stmt); - tree cval_tree; - if (TREE_CODE (c) == INTEGER_CST) - cval_tree = fold_convert (etype, c); - else - cval_tree = gimple_convert (gsi, true, GSI_SAME_STMT, loc, etype, c); + tree ptype = TREE_TYPE (TREE_TYPE (dest)); + if (TYPE_VOLATILE (ptype)) + etype = build_qualified_type (etype, TYPE_QUAL_VOLATILE); - /* Build accesses at offset zero with a ref-all character type. */ - tree off0 - = build_int_cst (build_pointer_type_for_mode (char_type_node, - ptr_mode, true), 0); - tree var = fold_build2_loc (loc, MEM_REF, etype, dest, off0); - gimple *store = gimple_build_assign (var, cval_tree); - gimple_move_vops (store, stmt); - gimple_set_location (store, loc); - copy_warning (store, stmt); + location_t loc = gimple_location (stmt); + tree cval_tree; + if (length == 1) + { + if (TREE_CODE (c) == INTEGER_CST) + cval_tree = fold_convert (etype, c); + else + cval_tree = gimple_convert (gsi, true, GSI_SAME_STMT, loc, + etype, c); + } + else + { + tree value_type = TYPE_MAIN_VARIANT (etype); + tree byte; + if (TREE_CODE (c) == INTEGER_CST) + byte = fold_convert (unsigned_char_type_node, c); + else + byte = gimple_convert (gsi, true, GSI_SAME_STMT, loc, + unsigned_char_type_node, c); - tree lhs = gimple_call_lhs (stmt); - if (!lhs) - { - gsi_replace (gsi, store, false); - return true; - } + tree byte_value; + if (TREE_CODE (byte) == INTEGER_CST) + byte_value = fold_convert (value_type, byte); + else + byte_value = gimple_convert (gsi, true, GSI_SAME_STMT, loc, + value_type, byte); + + unsigned HOST_WIDE_INT multiplier = 1; + for (unsigned HOST_WIDE_INT i = 1; i < length; ++i) + multiplier = (multiplier << BITS_PER_UNIT) | 1; + tree multiplier_tree + = build_int_cst_type (value_type, multiplier); + if (TREE_CODE (byte_value) == INTEGER_CST) + cval_tree = fold_build2_loc (loc, MULT_EXPR, value_type, + byte_value, multiplier_tree); + else + cval_tree = gimple_build (gsi, true, GSI_SAME_STMT, loc, + MULT_EXPR, value_type, byte_value, + multiplier_tree); + if (!useless_type_conversion_p (etype, + TREE_TYPE (cval_tree))) + cval_tree = gimple_convert (gsi, true, GSI_SAME_STMT, loc, + etype, cval_tree); + } - gsi_insert_before (gsi, store, GSI_SAME_STMT); - tree ret = dest; - if (!useless_type_conversion_p (TREE_TYPE (lhs), TREE_TYPE (dest))) - ret = gimple_convert (gsi, true, GSI_SAME_STMT, loc, - TREE_TYPE (lhs), dest); - gimple *asgn = gimple_build_assign (lhs, ret); - gsi_replace (gsi, asgn, false); + /* Build a store at offset zero with the selected scalar type. */ + tree off0 + = build_int_cst + (build_pointer_type_for_mode (char_type_node, + ptr_mode, true), 0); + tree var = fold_build2_loc (loc, MEM_REF, etype, dest, off0); + gimple *store = gimple_build_assign (var, cval_tree); + gimple_move_vops (store, stmt); + gimple_set_location (store, loc); + copy_warning (store, stmt); - return true; + tree lhs = gimple_call_lhs (stmt); + if (!lhs) + { + gsi_replace (gsi, store, false); + return true; + } + + gsi_insert_before (gsi, store, GSI_SAME_STMT); + tree ret = dest; + if (!useless_type_conversion_p (TREE_TYPE (lhs), + TREE_TYPE (dest))) + ret = gimple_convert (gsi, true, GSI_SAME_STMT, loc, + TREE_TYPE (lhs), dest); + gimple *asgn = gimple_build_assign (lhs, ret); + gsi_replace (gsi, asgn, false); + + return true; + } + } } if (TREE_CODE (c) != INTEGER_CST) @@ -5567,9 +5646,7 @@ gimple_fold_builtin (gimple_stmt_iterator *gsi) return gimple_fold_builtin_bzero (gsi); case BUILT_IN_MEMSET: - return gimple_fold_builtin_memset (gsi, - gimple_call_arg (stmt, 1), - gimple_call_arg (stmt, 2)); + return gimple_fold_builtin_memset (gsi); case BUILT_IN_MEMPCPY: if (gimple_fold_builtin_memory_op (gsi, gimple_call_arg (stmt, 0), gimple_call_arg (stmt, 1), fcode)) diff --git a/gcc/gimple-fold.h b/gcc/gimple-fold.h index febc15330c8..b0873f748cf 100644 --- a/gcc/gimple-fold.h +++ b/gcc/gimple-fold.h @@ -35,6 +35,8 @@ extern tree follow_single_use_edges (tree); extern tree follow_all_ssa_edges (tree); extern bool fold_stmt (gimple_stmt_iterator *, bitmap = nullptr); extern bool fold_stmt (gimple_stmt_iterator *, tree (*) (tree), bitmap = nullptr); +extern bool gimple_fold_builtin_memset (gimple_stmt_iterator *, + bool = false); extern bool fold_stmt_inplace (gimple_stmt_iterator *, tree (*) (tree) = no_follow_ssa_edges); extern tree maybe_fold_and_comparisons (tree, enum tree_code, tree, tree, enum tree_code, tree, tree, diff --git a/gcc/gimple-isel.cc b/gcc/gimple-isel.cc index 1d346ee124f..f103c78eb00 100644 --- a/gcc/gimple-isel.cc +++ b/gcc/gimple-isel.cc @@ -1276,7 +1276,7 @@ optimize_atomic_op_fetch_cmp_0 (gimple_stmt_iterator *gsip, } /* Process builtin CALL located at GSI. - Currently it is only fgr atomic functions optimizations from above. */ + Currently it is only for atomic functions optimizations from above. */ static void gimple_isel_builtin_call (gcall *call, gimple_stmt_iterator *gsi) { @@ -1288,7 +1288,7 @@ gimple_isel_builtin_call (gcall *call, gimple_stmt_iterator *gsi) return; tree callee = gimple_call_fndecl (call); - + switch (DECL_FUNCTION_CODE (callee)) { #define CASE_ATOMIC(NAME) \ @@ -1346,6 +1346,12 @@ gimple_isel_builtin_call (gcall *call, gimple_stmt_iterator *gsi) false); break; + case BUILT_IN_MEMSET: + /* Fold constant-length memset calls after pass_warn_access has + inspected the original call. */ + gimple_fold_builtin_memset (gsi, true); + break; + default:; } } @@ -1411,4 +1417,3 @@ make_pass_gimple_isel (gcc::context *ctxt) { return new pass_gimple_isel (ctxt); } - diff --git a/gcc/passes.def b/gcc/passes.def index 9095c134f49..dc7f9569bac 100644 --- a/gcc/passes.def +++ b/gcc/passes.def @@ -449,10 +449,10 @@ along with GCC; see the file COPYING3. If not see NEXT_PASS (pass_cleanup_eh); NEXT_PASS (pass_lower_resx); NEXT_PASS (pass_nrv); + NEXT_PASS (pass_warn_access, /*early=*/false, /*check_dangling=*/false); NEXT_PASS (pass_gimple_isel); NEXT_PASS (pass_harden_conditional_branches); NEXT_PASS (pass_harden_compares); - NEXT_PASS (pass_warn_access, /*early=*/false, /*check_dangling=*/false); NEXT_PASS (pass_cleanup_cfg_post_optimizing); NEXT_PASS (pass_warn_function_noreturn); diff --git a/gcc/testsuite/gcc.dg/pr102202-fold-warn.c b/gcc/testsuite/gcc.dg/pr102202-fold-warn.c new file mode 100644 index 00000000000..9bfe566c9f0 --- /dev/null +++ b/gcc/testsuite/gcc.dg/pr102202-fold-warn.c @@ -0,0 +1,13 @@ +/* PR tree-optimization/102202 */ +/* { dg-do compile } */ +/* { dg-options "-O2 -Wstringop-overflow=2" } */ + +extern void sink (void *); + +void +f (int c) +{ + void *p = __builtin_malloc (1); + __builtin_memset (p, c, 2); /* { dg-warning "writing 2 bytes into a region of size 1" } */ + sink (p); +} diff --git a/gcc/testsuite/gcc.dg/pr102202-fold-zero-n.c b/gcc/testsuite/gcc.dg/pr102202-fold-zero-n.c new file mode 100644 index 00000000000..275cc0bd034 --- /dev/null +++ b/gcc/testsuite/gcc.dg/pr102202-fold-zero-n.c @@ -0,0 +1,55 @@ +/* PR tree-optimization/102202 */ +/* { dg-do compile } */ +/* { dg-require-effective-target int32plus } */ +/* { dg-options "-O2 -fdump-tree-cdce-details -fdump-tree-optimized" } */ + +/* CDCE turns the exact range {0, 2} into a zero-length bypass and a call + with constant length two. The latter should then fold to one scalar + store even though the destination is an arbitrary pointer. */ + +void +g1 (unsigned int n, int c, unsigned short *d) +{ + __SIZE_TYPE__ len = (n & 1) ? 2 : 0; + __builtin_memset (d, c, len); +} + +void * +g2 (unsigned int n, int c, unsigned short *d) +{ + __SIZE_TYPE__ len = (n & 1) ? 2 : 0; + return __builtin_memset (d, c, len); +} + +void +g3 (unsigned int n, unsigned short *d) +{ + __SIZE_TYPE__ len = (n & 1) ? 2 : 0; + __builtin_memset (d, 7, len); +} + +/* Ordinary constant-length calls use the same fold. */ + +void +g4 (int c, unsigned short *d) +{ + __builtin_memset (d, c, 2); +} + +void * +g5 (int c, unsigned short *d) +{ + return __builtin_memset (d, c, 2); +} + +void +g6 (unsigned short *d) +{ + __builtin_memset (d, 7, 2); +} + +/* Only the first three calls have exact {0, 2} lengths. */ +/* { dg-final { scan-tree-dump-times "function call is shrink-wrapped into error conditions" 3 "cdce" } } */ + +/* Both CDCE-produced and ordinary constant-length calls are scalarized. */ +/* { dg-final { scan-tree-dump-not "__builtin_memset" "optimized" } } */ diff --git a/gcc/testsuite/gcc.dg/pr86010-2.c b/gcc/testsuite/gcc.dg/pr86010-2.c index 4c82e65aeb5..6adb8638c1e 100644 --- a/gcc/testsuite/gcc.dg/pr86010-2.c +++ b/gcc/testsuite/gcc.dg/pr86010-2.c @@ -19,4 +19,6 @@ void h (char *a) f (a); } -/* { dg-final { scan-tree-dump-times "__builtin_memset" 2 "optimized" } } */ +/* Each function retains one memory write, represented by either a memset call + or an inline scalar store. */ +/* { dg-final { scan-tree-dump-times {__builtin_memset|MEM[^;\n\r]*=} 2 "optimized" } } */ diff --git a/gcc/testsuite/gcc.dg/pr86010.c b/gcc/testsuite/gcc.dg/pr86010.c index ac279893268..5b656ba0339 100644 --- a/gcc/testsuite/gcc.dg/pr86010.c +++ b/gcc/testsuite/gcc.dg/pr86010.c @@ -21,4 +21,6 @@ void h (void) f (a); } -/* { dg-final { scan-tree-dump-times "__builtin_memset" 2 "optimized" } } */ +/* Each function retains one memory write, represented by either a memset call + or an inline scalar store. */ +/* { dg-final { scan-tree-dump-times {__builtin_memset|MEM[^;\n\r]*=} 2 "optimized" } } */ diff --git a/gcc/testsuite/gcc.dg/tree-ssa/calloc-5.c b/gcc/testsuite/gcc.dg/tree-ssa/calloc-5.c index 3d3e5a14542..9c4dbe9d4db 100644 --- a/gcc/testsuite/gcc.dg/tree-ssa/calloc-5.c +++ b/gcc/testsuite/gcc.dg/tree-ssa/calloc-5.c @@ -1,8 +1,8 @@ /* PR tree-optimization/83821 - local aggregate initialization defeats strlen optimization - Verify that with DSE disabled, a memset() call to zero out a subregion - of memory allocated by calloc() is not eliminated after a non-zero byte - is written into it using memset() in between the two calls. + Verify that with DSE disabled, a write that zeroes a subregion of memory + allocated by calloc() is not eliminated after a nonzero value is written + into it between the two writes. { dg-do compile } { dg-options "-O2 -fno-tree-dse -fdump-tree-optimized" } */ @@ -19,4 +19,4 @@ char* keep_memset_calls (void) /* { dg-final { scan-tree-dump-not "malloc" "optimized" } } { dg-final { scan-tree-dump-times "_calloc \\\(" 1 "optimized" } } - { dg-final { scan-tree-dump-times "_memset \\\(" 2 "optimized" } } */ + { dg-final { scan-tree-dump-times {__builtin_memset|MEM[^;\n\r]*=} 2 "optimized" } } */ diff --git a/gcc/testsuite/gcc.target/aarch64/pr102202-fold-no-mode.c b/gcc/testsuite/gcc.target/aarch64/pr102202-fold-no-mode.c new file mode 100644 index 00000000000..ad66c66cabc --- /dev/null +++ b/gcc/testsuite/gcc.target/aarch64/pr102202-fold-no-mode.c @@ -0,0 +1,12 @@ +/* PR tree-optimization/102202 */ +/* { dg-do compile } */ +/* { dg-options "-O2 -fdump-tree-optimized" } */ + +void +f (int c, unsigned char *d) +{ + __builtin_memset (d, c, 3); +} + +/* AArch64 has no three-byte integer mode, so keep the call. */ +/* { dg-final { scan-tree-dump-times "__builtin_memset" 1 "optimized" } } */ -- 2.34.1
