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

Reply via email to