tree-optimization: fold memset with length in [0, 1] to conditional store [PR102202]

Message ID 20260506041649.1357118-1-naveen.siddegowda@oss.qualcomm.com
State New
Headers
Series tree-optimization: fold memset with length in [0, 1] to conditional store [PR102202] |

Commit Message

Naveen SiddeGowda May 6, 2026, 4:16 a.m. UTC
  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 <naveen.siddegowda@oss.qualcomm.com>
---
 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
  

Patch

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" } } */
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;
+
+  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)
+    return false;
+
+  /* The replacement store needs to preserve the original memory VDEF.  */
+  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_);
+
+  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))