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

Reply via email to