[v2,2/4] tree-object-size: Fold PHI node offsets with constants [PR116556]

Message ID 20240920164029.63843-3-siddhesh@gotplt.org
State New
Headers
Series tree-object-size: Improved offset handling |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gcc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_gcc_check--master-aarch64 success Test passed
linaro-tcwg-bot/tcwg_gcc_check--master-arm success Test passed

Commit Message

Siddhesh Poyarekar Sept. 20, 2024, 4:40 p.m. UTC
  In PTR + OFFSET cases, try harder to see if the target offset could
result in a constant.  Specifically, if the offset is a PHI node with
all constant branches, return the minimum (or maximum for OST_MINIMUM)
of the possible values.

gcc/ChangeLog:

	PR tree-optimization/116556
	* tree-object-size.cc (try_collapsing_offset): New function.
	(plus_stmt_object_size): Use it.
	* gcc/testsuite/gcc.dg/builtin-object-size-1.c (test12, test13):
	New functions.
	(main): Call them.
	* gcc/testsuite/gcc.dg/builtin-object-size-3.c (test12, test13):
	New functions.
	(main): Call them.

Signed-off-by: Siddhesh Poyarekar <siddhesh@gotplt.org>
---
 gcc/testsuite/gcc.dg/builtin-object-size-1.c | 63 ++++++++++++++++++++
 gcc/testsuite/gcc.dg/builtin-object-size-3.c | 63 ++++++++++++++++++++
 gcc/tree-object-size.cc                      | 60 +++++++++++++++++++
 3 files changed, 186 insertions(+)
  

Comments

Jakub Jelinek Sept. 27, 2024, 11:10 a.m. UTC | #1
On Fri, Sep 20, 2024 at 12:40:27PM -0400, Siddhesh Poyarekar wrote:
> --- a/gcc/tree-object-size.cc
> +++ b/gcc/tree-object-size.cc
> @@ -1468,6 +1468,63 @@ merge_object_sizes (struct object_size_info *osi, tree dest, tree orig)
>    return bitmap_bit_p (osi->reexamine, SSA_NAME_VERSION (orig));
>  }
>  
> +/* For constant sizes, try collapsing a non-constant offset to a constant if
> +   possible.  The case handled at the moment is when the offset is a PHI node
> +   with all of its targets are constants.  */
> +
> +static tree
> +try_collapsing_offset (tree op, int object_size_type)
> +{
> +  gcc_assert (!(object_size_type & OST_DYNAMIC));
> +
> +  if (TREE_CODE (op) != SSA_NAME)
> +    return op;
> +
> +  gimple *stmt = SSA_NAME_DEF_STMT (op);
> +
> +  switch (gimple_code (stmt))
> +    {
> +    case GIMPLE_ASSIGN:
> +      /* Peek through casts.  */
> +      if (gimple_assign_rhs_code (stmt) == NOP_EXPR)

Do you really want to handle all casts?  That could be widening, narrowing,
from non-integral types, ...
If only same mode, then gimple_assign_unary_nop_p probably should be used
instead, if any from integral types (same precision, widening, narrowing),
then perhaps CONVERT_EXPR_CODE_P (gimple_assign_rhs_code (stmt))
but verify that INTEGRAL_TYPE_P (TREE_TYPE (gimple_assign_rhs1 (stmt)))
before actually recursing.
Note, I think narrowing or widening casts to sizetype are fine, but when
you recurse through, other casts might not be anymore.
Consider
  short int _1;
  unsigned int _2;
  size_t _3;
...
  # _1 = PHI <-10(7), 12(8), 4(9), -42(10)>
  _2 = (unsigned int) _1;
  _3 = (size_t) _2;
If the recursion finds minimum or maximum from the signed short int _1
values (cast to ptrdiff_type_node), pretending that is the minimum or
maximum for _3 is just wrong, as the cast from signed to unsigned will
turn negative values to something larger than the smaller positive values.

Similarly, consider casts from unsigned __int128 -> unsigned short -> size_t
(or signed short in between), what is minimum/maximum in 128-bits (the code
for PHIs actually ignores the upper bits and looks only for signed 64-bits,
but if there is unfolded cast from INTEGER_CST you actually could have even
large value) isn't necessarily minimum after cast to 16-bit (unsigned or
signed).

So, unless you want to get all the possibilities into account, perhaps only
recurse through casts from integer types to integer types with precision
of sizetype?

And perhaps also look for INTEGER_CST type returned from the recursive
call and if it doesn't have sizetype precision, either convert it to
sizetype or ignore.

> +	{
> +	  tree ret = try_collapsing_offset (gimple_assign_rhs1 (stmt),
> +					    object_size_type);
> +	  if (TREE_CODE (ret) == INTEGER_CST)
> +	    return ret;
> +	}
> +      break;
> +    case GIMPLE_PHI:
> +	{
> +	  tree off = ((object_size_type & OST_MINIMUM)
> +		      ? TYPE_MIN_VALUE (ptrdiff_type_node)
> +		      : TYPE_MAX_VALUE (ptrdiff_type_node));

Because you only process constants, I wonder whether using
wide_int off and performing the min/max on wide_int wouldn't be
better, no need to create INTEGER_CSTs for all the intermediate
results.
That would be
      unsigned int prec = TYPE_PRECISION (ptrdiff_type_node);
      wide_int off = ((object_size_type & OST_MINIMUM)
		      ? wi::to_wide (TYPE_MIN_VALUE (ptrdiff_type_node))
		      : wi::to_wide (TYPE_MAX_VALUE (ptrdiff_type_node)));

> +
> +	  for (unsigned i = 0; i < gimple_phi_num_args (stmt); i++)
> +	    {
> +	      tree rhs = gimple_phi_arg_def (stmt, i);
> +

I wonder if it wouldn't be useful to recurse here,
	      rhs = try_collapsing_offset (rhs, object_size_type);
but guess with some extra counter argument and only allow some small
constant levels of nesting (but also do that for the cast cases).

> +	      if (TREE_CODE (rhs) != INTEGER_CST)
> +		return op;
> +
> +	      /* Note that this is the *opposite* of what we usually do with
> +		 sizes, because the maximum offset estimate here will give us a
> +		 minimum size estimate and vice versa.  */
> +	      enum tree_code code = (object_size_type & OST_MINIMUM
> +				     ? MAX_EXPR : MIN_EXPR);
> +
> +	      off = fold_build2 (code, ptrdiff_type_node, off,
> +				 fold_convert (ptrdiff_type_node, rhs));

And this could be

	      wide_int w = wi::to_wide (rhs, prec);
	      if (object_size_type & OST_MINIMUM)
		off = wi::smax (off, w);
	      else
		off = wi::smin (off, w);

> +	    }
> +	  return fold_convert (sizetype, off);

	  return wide_int_to_tree (sizetype, off);

> +	}
> +    default:
> +      break;
> +    }
> +
> +  /* Nothing worked, so return OP untouched.  */
> +  return op;
> +}
>  
>  /* Compute object_sizes for VAR, defined to the result of an assignment
>     with operator POINTER_PLUS_EXPR.  Return true if the object size might
> @@ -1500,6 +1557,9 @@ plus_stmt_object_size (struct object_size_info *osi, tree var, gimple *stmt)
>    if (object_sizes_unknown_p (object_size_type, varno))
>      return false;
>  
> +  if (!(object_size_type & OST_DYNAMIC) && TREE_CODE (op1) != INTEGER_CST)
> +    op1 = try_collapsing_offset (op1, object_size_type);
> +
>    /* Handle PTR + OFFSET here.  */
>    if (size_valid_p (op1, object_size_type)
>        && (TREE_CODE (op0) == SSA_NAME || TREE_CODE (op0) == ADDR_EXPR))
> -- 
> 2.45.1

	Jakub
  

Patch

diff --git a/gcc/testsuite/gcc.dg/builtin-object-size-1.c b/gcc/testsuite/gcc.dg/builtin-object-size-1.c
index d6d13c5ef7a..1f5cd5d99a1 100644
--- a/gcc/testsuite/gcc.dg/builtin-object-size-1.c
+++ b/gcc/testsuite/gcc.dg/builtin-object-size-1.c
@@ -712,6 +712,65 @@  test11 (void)
 }
 #endif
 
+void
+__attribute__ ((noinline))
+test12 (unsigned cond)
+{
+  char *buf2 = malloc (10);
+  char *p;
+  size_t t;
+
+  if (cond)
+    t = 8;
+  else
+    t = 4;
+
+  p = &buf2[t];
+
+#ifdef __builtin_object_size
+  if (__builtin_object_size (p, 0) != (cond ? 2 : 6))
+    FAIL ();
+#else
+  if (__builtin_object_size (p, 0) != 6)
+    FAIL ();
+#endif
+}
+
+void
+__attribute__ ((noinline))
+test13 (unsigned cond)
+{
+  char *buf2 = malloc (10);
+  char *p = &buf2[4];
+  int t;
+
+  if (cond)
+    t = -1;
+  else
+    t = -2;
+
+#ifdef __builtin_object_size
+  if (__builtin_object_size (&p[t], 0) != (cond ? 7 : 8))
+    FAIL ();
+#else
+  if (__builtin_object_size (&p[t], 0) != 8)
+    FAIL ();
+#endif
+
+  if (cond)
+    t = 1;
+  else
+    t = -3;
+
+#ifdef __builtin_object_size
+  if (__builtin_object_size (&p[t], 0) != (cond ? 5 : 9))
+    FAIL ();
+#else
+  if (__builtin_object_size (&p[t], 0) != 9)
+    FAIL ();
+#endif
+}
+
 int
 main (void)
 {
@@ -729,6 +788,10 @@  main (void)
   test10 ();
 #ifndef SKIP_STRNDUP
   test11 ();
+  test12 (1);
+  test12 (0);
+  test13 (1);
+  test13 (0);
 #endif
   DONE ();
 }
diff --git a/gcc/testsuite/gcc.dg/builtin-object-size-3.c b/gcc/testsuite/gcc.dg/builtin-object-size-3.c
index ec2c62c9640..7fad106fc27 100644
--- a/gcc/testsuite/gcc.dg/builtin-object-size-3.c
+++ b/gcc/testsuite/gcc.dg/builtin-object-size-3.c
@@ -720,6 +720,65 @@  test11 (void)
 }
 #endif
 
+void
+__attribute__ ((noinline))
+test12 (unsigned cond)
+{
+  char *buf2 = malloc (10);
+  char *p;
+  size_t t;
+
+  if (cond)
+    t = 8;
+  else
+    t = 4;
+
+  p = &buf2[t];
+
+#ifdef __builtin_object_size
+  if (__builtin_object_size (p, 2) != (cond ? 2 : 6))
+    FAIL ();
+#else
+  if (__builtin_object_size (p, 2) != 2)
+    FAIL ();
+#endif
+}
+
+void
+__attribute__ ((noinline))
+test13 (unsigned cond)
+{
+  char *buf2 = malloc (10);
+  char *p = &buf2[4];
+  size_t t;
+
+  if (cond)
+    t = -1;
+  else
+    t = -2;
+
+#ifdef __builtin_object_size
+  if (__builtin_object_size (&p[t], 2) != (cond ? 7 : 8))
+    FAIL ();
+#else
+  if (__builtin_object_size (&p[t], 2) != 7)
+    FAIL ();
+#endif
+
+  if (cond)
+    t = 1;
+  else
+    t = -3;
+
+#ifdef __builtin_object_size
+  if (__builtin_object_size (&p[t], 2) != (cond ? 5 : 9))
+    FAIL ();
+#else
+  if (__builtin_object_size (&p[t], 2) != 5)
+    FAIL ();
+#endif
+}
+
 int
 main (void)
 {
@@ -738,5 +797,9 @@  main (void)
 #ifndef SKIP_STRNDUP
   test11 ();
 #endif
+  test12 (1);
+  test12 (0);
+  test13 (1);
+  test13 (0);
   DONE ();
 }
diff --git a/gcc/tree-object-size.cc b/gcc/tree-object-size.cc
index f8fae0cbc82..2dfc28407ab 100644
--- a/gcc/tree-object-size.cc
+++ b/gcc/tree-object-size.cc
@@ -1468,6 +1468,63 @@  merge_object_sizes (struct object_size_info *osi, tree dest, tree orig)
   return bitmap_bit_p (osi->reexamine, SSA_NAME_VERSION (orig));
 }
 
+/* For constant sizes, try collapsing a non-constant offset to a constant if
+   possible.  The case handled at the moment is when the offset is a PHI node
+   with all of its targets are constants.  */
+
+static tree
+try_collapsing_offset (tree op, int object_size_type)
+{
+  gcc_assert (!(object_size_type & OST_DYNAMIC));
+
+  if (TREE_CODE (op) != SSA_NAME)
+    return op;
+
+  gimple *stmt = SSA_NAME_DEF_STMT (op);
+
+  switch (gimple_code (stmt))
+    {
+    case GIMPLE_ASSIGN:
+      /* Peek through casts.  */
+      if (gimple_assign_rhs_code (stmt) == NOP_EXPR)
+	{
+	  tree ret = try_collapsing_offset (gimple_assign_rhs1 (stmt),
+					    object_size_type);
+	  if (TREE_CODE (ret) == INTEGER_CST)
+	    return ret;
+	}
+      break;
+    case GIMPLE_PHI:
+	{
+	  tree off = ((object_size_type & OST_MINIMUM)
+		      ? TYPE_MIN_VALUE (ptrdiff_type_node)
+		      : TYPE_MAX_VALUE (ptrdiff_type_node));
+
+	  for (unsigned i = 0; i < gimple_phi_num_args (stmt); i++)
+	    {
+	      tree rhs = gimple_phi_arg_def (stmt, i);
+
+	      if (TREE_CODE (rhs) != INTEGER_CST)
+		return op;
+
+	      /* Note that this is the *opposite* of what we usually do with
+		 sizes, because the maximum offset estimate here will give us a
+		 minimum size estimate and vice versa.  */
+	      enum tree_code code = (object_size_type & OST_MINIMUM
+				     ? MAX_EXPR : MIN_EXPR);
+
+	      off = fold_build2 (code, ptrdiff_type_node, off,
+				 fold_convert (ptrdiff_type_node, rhs));
+	    }
+	  return fold_convert (sizetype, off);
+	}
+    default:
+      break;
+    }
+
+  /* Nothing worked, so return OP untouched.  */
+  return op;
+}
 
 /* Compute object_sizes for VAR, defined to the result of an assignment
    with operator POINTER_PLUS_EXPR.  Return true if the object size might
@@ -1500,6 +1557,9 @@  plus_stmt_object_size (struct object_size_info *osi, tree var, gimple *stmt)
   if (object_sizes_unknown_p (object_size_type, varno))
     return false;
 
+  if (!(object_size_type & OST_DYNAMIC) && TREE_CODE (op1) != INTEGER_CST)
+    op1 = try_collapsing_offset (op1, object_size_type);
+
   /* Handle PTR + OFFSET here.  */
   if (size_valid_p (op1, object_size_type)
       && (TREE_CODE (op0) == SSA_NAME || TREE_CODE (op0) == ADDR_EXPR))