[v2] tree-optimization: Extend CDCE memset shrink-wrapping to {0, N} ranges [PR102202]

Message ID 20260707063917.3162084-1-naveen.siddegowda@oss.qualcomm.com
State Committed
Commit 54dd6575fa5ba7554396039820bee88d4dddab6d
Headers
Series [v2] tree-optimization: Extend CDCE memset shrink-wrapping to {0, N} ranges [PR102202] |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gcc_build--master-arm fail Patch failed to apply
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 fail Patch failed to apply

Commit Message

Naveen July 7, 2026, 6:39 a.m. UTC
  The patch extends the CDCE shrink-wrapping support added for PR102202.

The existing transform handles memset calls whose length is known to be
in [0, 1].  It bypasses the call for length zero and specializes the
nonzero path to a constant length of one.

Extend this to exact two-value length ranges {0, N}, where N is a
nonzero integer constant.  The zero-length path is still bypassed and
the nonzero path is specialized to the constant N.  In particular, the
transform does not accept an ordinary interval [0, N] since such an
interval can contain values other than zero and N.

The range query now returns the nonzero member of either a [0, 1] range
or an exact {0, N} range.  The existing [0, 1] behavior is preserved.

gcc/ChangeLog:
	PR tree-optimization/102202
	* tree-call-cdce.cc: Include "value-query.h" and "value-range.h".
	(len_has_boolean_range_p): Remove.
	(get_len_nonzero_value): New function. Return the nonzero member of
	LEN's value range when it is a boolean range [0, 1] or an exact
	two-value set {0, N}.
	(can_shrink_wrap_len_p): Replace len_has_boolean_range_p call with
	get_len_nonzero_value. Add NONZERO_LEN output parameter and set it
	to the returned nonzero value on success.
	(shrink_wrap_len_call): Use it to pin the length argument on the
	guarded call path instead of build_one_cst.
	(shrink_wrap_conditional_dead_built_in_calls): Declare local
	nonzero_len and pass it to can_shrink_wrap_len_p and forward it to
	shrink_wrap_len_call.
	(pass_call_cdce::execute): Pass nullptr as NONZERO_LEN to
	can_shrink_wrap_len_p.

gcc/testsuite/ChangeLog:
	PR tree-optimization/102202
	* gcc.dg/pr102202-3.c: New test.
	* gcc.dg/pr102202-4.c: New test.

Signed-off-by: Naveen <naveen.siddegowda@oss.qualcomm.com>
---
 gcc/testsuite/gcc.dg/pr102202-3.c | 21 ++++++++++
 gcc/testsuite/gcc.dg/pr102202-4.c | 16 ++++++++
 gcc/tree-call-cdce.cc             | 65 +++++++++++++++++++++----------
 3 files changed, 82 insertions(+), 20 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/pr102202-3.c
 create mode 100644 gcc/testsuite/gcc.dg/pr102202-4.c
  

Comments

Richard Biener July 7, 2026, 8:37 a.m. UTC | #1
On Tue, Jul 7, 2026 at 8:39 AM Naveen
<naveen.siddegowda@oss.qualcomm.com> wrote:
>
> The patch extends the CDCE shrink-wrapping support added for PR102202.
>
> The existing transform handles memset calls whose length is known to be
> in [0, 1].  It bypasses the call for length zero and specializes the
> nonzero path to a constant length of one.
>
> Extend this to exact two-value length ranges {0, N}, where N is a
> nonzero integer constant.  The zero-length path is still bypassed and
> the nonzero path is specialized to the constant N.  In particular, the
> transform does not accept an ordinary interval [0, N] since such an
> interval can contain values other than zero and N.
>
> The range query now returns the nonzero member of either a [0, 1] range
> or an exact {0, N} range.  The existing [0, 1] behavior is preserved.

OK.

Thanks,
Richard.

> gcc/ChangeLog:
>         PR tree-optimization/102202
>         * tree-call-cdce.cc: Include "value-query.h" and "value-range.h".
>         (len_has_boolean_range_p): Remove.
>         (get_len_nonzero_value): New function. Return the nonzero member of
>         LEN's value range when it is a boolean range [0, 1] or an exact
>         two-value set {0, N}.
>         (can_shrink_wrap_len_p): Replace len_has_boolean_range_p call with
>         get_len_nonzero_value. Add NONZERO_LEN output parameter and set it
>         to the returned nonzero value on success.
>         (shrink_wrap_len_call): Use it to pin the length argument on the
>         guarded call path instead of build_one_cst.
>         (shrink_wrap_conditional_dead_built_in_calls): Declare local
>         nonzero_len and pass it to can_shrink_wrap_len_p and forward it to
>         shrink_wrap_len_call.
>         (pass_call_cdce::execute): Pass nullptr as NONZERO_LEN to
>         can_shrink_wrap_len_p.
>
> gcc/testsuite/ChangeLog:
>         PR tree-optimization/102202
>         * gcc.dg/pr102202-3.c: New test.
>         * gcc.dg/pr102202-4.c: New test.
>
> Signed-off-by: Naveen <naveen.siddegowda@oss.qualcomm.com>
> ---
>  gcc/testsuite/gcc.dg/pr102202-3.c | 21 ++++++++++
>  gcc/testsuite/gcc.dg/pr102202-4.c | 16 ++++++++
>  gcc/tree-call-cdce.cc             | 65 +++++++++++++++++++++----------
>  3 files changed, 82 insertions(+), 20 deletions(-)
>  create mode 100644 gcc/testsuite/gcc.dg/pr102202-3.c
>  create mode 100644 gcc/testsuite/gcc.dg/pr102202-4.c
>
> diff --git a/gcc/testsuite/gcc.dg/pr102202-3.c b/gcc/testsuite/gcc.dg/pr102202-3.c
> new file mode 100644
> index 00000000000..1904e309fee
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/pr102202-3.c
> @@ -0,0 +1,21 @@
> +/* PR tree-optimization/102202 */
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -fdump-tree-cdce-details" } */
> +
> +/* An exact two-value range {0, 2}.  Use a PHI so that Ranger retains
> +   the two singleton values at the call.  */
> +
> +void
> +g1 (unsigned int x, int c, char *d)
> +{
> +  __SIZE_TYPE__ len;
> +
> +  if (x)
> +    len = 2;
> +  else
> +    len = 0;
> +
> +  __builtin_memset (d, c, len);
> +}
> +
> +/* { dg-final { scan-tree-dump-times "function call is shrink-wrapped into error conditions" 1 "cdce" } } */
> diff --git a/gcc/testsuite/gcc.dg/pr102202-4.c b/gcc/testsuite/gcc.dg/pr102202-4.c
> new file mode 100644
> index 00000000000..908256b5b2d
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/pr102202-4.c
> @@ -0,0 +1,16 @@
> +/* PR tree-optimization/102202 */
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -fdump-tree-cdce-details" } */
> +
> +/* The interval [0, 2] is not the exact two-value set {0, 2}.  */
> +
> +void
> +g1 (__SIZE_TYPE__ len, int c, char *d)
> +{
> +  if (len > 2)
> +    __builtin_unreachable ();
> +
> +  __builtin_memset (d, c, len);
> +}
> +
> +/* { dg-final { scan-tree-dump-not "function call is shrink-wrapped into error conditions" "cdce" } } */
> diff --git a/gcc/tree-call-cdce.cc b/gcc/tree-call-cdce.cc
> index 144f485909c..289504fc927 100644
> --- a/gcc/tree-call-cdce.cc
> +++ b/gcc/tree-call-cdce.cc
> @@ -39,6 +39,8 @@ along with GCC; see the file COPYING3.  If not see
>  #include "tree-eh.h"
>  #include "tree-ssanames.h"
>  #include "gimple-fold.h"
> +#include "value-query.h"
> +#include "value-range.h"
>
>
>  /* This pass serves two closely-related purposes:
> @@ -1257,23 +1259,40 @@ 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].  */
> +/* Return the nonzero member of LEN's range if LEN is an SSA name with range
> +   [0, 1] or the exact two-value set {0, N}.  Return NULL_TREE otherwise.  */
>
> -static bool
> -len_has_boolean_range_p (tree len, gimple *stmt)
> +static tree
> +get_len_nonzero_value (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 NULL_TREE;
> +
> +  if (ssa_name_has_boolean_range (len, stmt))
> +    return build_one_cst (TREE_TYPE (len));
> +
> +  int_range<2> range (TREE_TYPE (len));
> +  if (!get_range_query (cfun)->range_of_expr (range, len, stmt)
> +      || range.num_pairs () != 2)
> +    return NULL_TREE;
> +
> +  if (!wi::eq_p (range.lower_bound (0), 0)
> +      || !wi::eq_p (range.upper_bound (0), 0))
> +    return NULL_TREE;
> +
> +  if (!wi::eq_p (range.lower_bound (1), range.upper_bound (1)))
> +    return NULL_TREE;
> +
> +  return wide_int_to_tree (TREE_TYPE (len), range.lower_bound (1));
>  }
>
>  /* 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.  */
> +   argument is an SSA name known to have a suitable range.  On success,
> +   set LEN_ARG to the argument index of the length and, if NONZERO_LEN is
> +   nonnull, set it to the nonzero member of the range.  */
>
>  static bool
> -can_shrink_wrap_len_p (gcall *call, unsigned *len_arg)
> +can_shrink_wrap_len_p (gcall *call, unsigned *len_arg, tree *nonzero_len)
>  {
>    if (!gimple_call_builtin_p (call, BUILT_IN_MEMSET)
>        || !gimple_vdef (call))
> @@ -1284,10 +1303,13 @@ can_shrink_wrap_len_p (gcall *call, unsigned *len_arg)
>      return false;
>
>    tree len = gimple_call_arg (call, memset_len_arg);
> -  if (!len_has_boolean_range_p (len, call))
> +  tree value = get_len_nonzero_value (len, call);
> +  if (!value)
>      return false;
>
>    *len_arg = memset_len_arg;
> +  if (nonzero_len)
> +    *nonzero_len = value;
>    return true;
>  }
>
> @@ -1305,13 +1327,14 @@ gen_zero_len_conditions (tree len, vec<gimple *> &conds, unsigned *nconds)
>    *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.  */
> +/* Shrink-wrap CALL whose LEN_ARG argument is known to be in [0, 1] or
> +   {0, N}.  ZERO_LEN_RESULT is the value of the call result when its length
> +   is zero.  On the guarded path the length is NONZERO_LEN, so pin it to that
> +   constant for subsequent folding.  */
>
>  static void
> -shrink_wrap_len_call (gcall *call, unsigned len_arg, tree zero_len_result)
> +shrink_wrap_len_call (gcall *call, unsigned len_arg, tree zero_len_result,
> +                     tree nonzero_len)
>  {
>    tree lhs = gimple_call_lhs (call);
>
> @@ -1341,8 +1364,8 @@ shrink_wrap_len_call (gcall *call, unsigned len_arg, tree zero_len_result)
>
>    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)));
> +  /* On the guarded path the length is NONZERO_LEN.  */
> +  gimple_call_set_arg (call, len_arg, nonzero_len);
>    update_stmt (call);
>    gimple_stmt_iterator gsi = gsi_for_stmt (call);
>    fold_stmt (&gsi);
> @@ -1363,8 +1386,10 @@ shrink_wrap_conditional_dead_built_in_calls (const vec<gcall *> &calls)
>        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));
> +      tree nonzero_len;
> +      if (can_shrink_wrap_len_p (bi_call, &len_arg, &nonzero_len))
> +       shrink_wrap_len_call (bi_call, len_arg, gimple_call_arg (bi_call, 0),
> +                             nonzero_len);
>        else if (gimple_call_lhs (bi_call))
>         use_internal_fn (bi_call);
>        else
> @@ -1427,7 +1452,7 @@ pass_call_cdce::execute (function *fun)
>           unsigned len_arg;
>            if (stmt
>               && gimple_call_builtin_p (stmt, BUILT_IN_NORMAL)
> -             && (can_shrink_wrap_len_p (stmt, &len_arg)
> +             && (can_shrink_wrap_len_p (stmt, &len_arg, nullptr)
>                   || (gimple_call_lhs (stmt)
>                       ? can_use_internal_fn (stmt)
>                       : can_test_argument_range (stmt)))
> --
> 2.34.1
>
  

Patch

diff --git a/gcc/testsuite/gcc.dg/pr102202-3.c b/gcc/testsuite/gcc.dg/pr102202-3.c
new file mode 100644
index 00000000000..1904e309fee
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/pr102202-3.c
@@ -0,0 +1,21 @@ 
+/* PR tree-optimization/102202 */
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-cdce-details" } */
+
+/* An exact two-value range {0, 2}.  Use a PHI so that Ranger retains
+   the two singleton values at the call.  */
+
+void
+g1 (unsigned int x, int c, char *d)
+{
+  __SIZE_TYPE__ len;
+
+  if (x)
+    len = 2;
+  else
+    len = 0;
+
+  __builtin_memset (d, c, len);
+}
+
+/* { dg-final { scan-tree-dump-times "function call is shrink-wrapped into error conditions" 1 "cdce" } } */
diff --git a/gcc/testsuite/gcc.dg/pr102202-4.c b/gcc/testsuite/gcc.dg/pr102202-4.c
new file mode 100644
index 00000000000..908256b5b2d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/pr102202-4.c
@@ -0,0 +1,16 @@ 
+/* PR tree-optimization/102202 */
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-cdce-details" } */
+
+/* The interval [0, 2] is not the exact two-value set {0, 2}.  */
+
+void
+g1 (__SIZE_TYPE__ len, int c, char *d)
+{
+  if (len > 2)
+    __builtin_unreachable ();
+
+  __builtin_memset (d, c, len);
+}
+
+/* { dg-final { scan-tree-dump-not "function call is shrink-wrapped into error conditions" "cdce" } } */
diff --git a/gcc/tree-call-cdce.cc b/gcc/tree-call-cdce.cc
index 144f485909c..289504fc927 100644
--- a/gcc/tree-call-cdce.cc
+++ b/gcc/tree-call-cdce.cc
@@ -39,6 +39,8 @@  along with GCC; see the file COPYING3.  If not see
 #include "tree-eh.h"
 #include "tree-ssanames.h"
 #include "gimple-fold.h"
+#include "value-query.h"
+#include "value-range.h"
 
 
 /* This pass serves two closely-related purposes:
@@ -1257,23 +1259,40 @@  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].  */
+/* Return the nonzero member of LEN's range if LEN is an SSA name with range
+   [0, 1] or the exact two-value set {0, N}.  Return NULL_TREE otherwise.  */
 
-static bool
-len_has_boolean_range_p (tree len, gimple *stmt)
+static tree
+get_len_nonzero_value (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 NULL_TREE;
+
+  if (ssa_name_has_boolean_range (len, stmt))
+    return build_one_cst (TREE_TYPE (len));
+
+  int_range<2> range (TREE_TYPE (len));
+  if (!get_range_query (cfun)->range_of_expr (range, len, stmt)
+      || range.num_pairs () != 2)
+    return NULL_TREE;
+
+  if (!wi::eq_p (range.lower_bound (0), 0)
+      || !wi::eq_p (range.upper_bound (0), 0))
+    return NULL_TREE;
+
+  if (!wi::eq_p (range.lower_bound (1), range.upper_bound (1)))
+    return NULL_TREE;
+
+  return wide_int_to_tree (TREE_TYPE (len), range.lower_bound (1));  
 }
 
 /* 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.  */
+   argument is an SSA name known to have a suitable range.  On success,
+   set LEN_ARG to the argument index of the length and, if NONZERO_LEN is
+   nonnull, set it to the nonzero member of the range.  */
 
 static bool
-can_shrink_wrap_len_p (gcall *call, unsigned *len_arg)
+can_shrink_wrap_len_p (gcall *call, unsigned *len_arg, tree *nonzero_len)
 {
   if (!gimple_call_builtin_p (call, BUILT_IN_MEMSET)
       || !gimple_vdef (call))
@@ -1284,10 +1303,13 @@  can_shrink_wrap_len_p (gcall *call, unsigned *len_arg)
     return false;
 
   tree len = gimple_call_arg (call, memset_len_arg);
-  if (!len_has_boolean_range_p (len, call))
+  tree value = get_len_nonzero_value (len, call);
+  if (!value)
     return false;
 
   *len_arg = memset_len_arg;
+  if (nonzero_len)
+    *nonzero_len = value;
   return true;
 }
 
@@ -1305,13 +1327,14 @@  gen_zero_len_conditions (tree len, vec<gimple *> &conds, unsigned *nconds)
   *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.  */
+/* Shrink-wrap CALL whose LEN_ARG argument is known to be in [0, 1] or
+   {0, N}.  ZERO_LEN_RESULT is the value of the call result when its length
+   is zero.  On the guarded path the length is NONZERO_LEN, so pin it to that
+   constant for subsequent folding.  */
 
 static void
-shrink_wrap_len_call (gcall *call, unsigned len_arg, tree zero_len_result)
+shrink_wrap_len_call (gcall *call, unsigned len_arg, tree zero_len_result,
+		      tree nonzero_len)
 {
   tree lhs = gimple_call_lhs (call);
 
@@ -1341,8 +1364,8 @@  shrink_wrap_len_call (gcall *call, unsigned len_arg, tree zero_len_result)
 
   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)));
+  /* On the guarded path the length is NONZERO_LEN.  */
+  gimple_call_set_arg (call, len_arg, nonzero_len);
   update_stmt (call);
   gimple_stmt_iterator gsi = gsi_for_stmt (call);
   fold_stmt (&gsi);
@@ -1363,8 +1386,10 @@  shrink_wrap_conditional_dead_built_in_calls (const vec<gcall *> &calls)
       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));
+      tree nonzero_len;
+      if (can_shrink_wrap_len_p (bi_call, &len_arg, &nonzero_len))
+	shrink_wrap_len_call (bi_call, len_arg, gimple_call_arg (bi_call, 0),
+			      nonzero_len);
       else if (gimple_call_lhs (bi_call))
 	use_internal_fn (bi_call);
       else
@@ -1427,7 +1452,7 @@  pass_call_cdce::execute (function *fun)
 	  unsigned len_arg;
           if (stmt
 	      && gimple_call_builtin_p (stmt, BUILT_IN_NORMAL)
-	      && (can_shrink_wrap_len_p (stmt, &len_arg)
+	      && (can_shrink_wrap_len_p (stmt, &len_arg, nullptr)
 		  || (gimple_call_lhs (stmt)
 		      ? can_use_internal_fn (stmt)
 		      : can_test_argument_range (stmt)))