[v2,2/4] tree-object-size: Fold PHI node offsets with constants [PR116556]
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
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
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
@@ -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 ();
}
@@ -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 ();
}
@@ -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))