On Mon, 9 Dec 2024, Jakub Jelinek wrote:

> Hi!
> 
> On Thu, Dec 05, 2024 at 11:37:45AM +0100, Richard Biener wrote:
> > VN again is the culprit for exploiting address equivalences before
> > __builtin_object_size got the chance to do its job.  This time
> > it isn't about union members but adjacent structure fields where
> > an address to one after the last element of an array field can
> > spill over to the next field.
> > 
> > The following protects all out-of-bound accesses on the upper bound
> > side (singling out TYPE_MAX_VALUE + 1 is more expensive).  It
> > ignores other out-of-bound addresses that would invoke UB.
> > 
> > Zero-sized arrays are a bit awkward because the C++ represents them
> > with a -1U upper bound.
> > 
> > There's a similar issue for zero-sized components whose address can
> > be the same as the adjacent field in C.
> 
> So, as I wrote earlier, I'd suggest dropping gcc.dg/torture/pr117912-3.c
> from the patch and replacing it with the attached one (now tweaked
> to use [[no_unique_address]] and be C++ compatible).
> 
> Generally, I'm really surprised all these routines only look at one
> reference, don't compare two references (one earlier seen, another one
> compared against that), which is where something like tree-object-size.cc
> (addr_object_size) like checking could be done, but guess that just
> shows how little I know about SCCVN.
> 
> The 3 hunks seem correct in what they attempt to do, zero sized components
> are indeed a problem because they result in the same offset for perhaps
> __bos POV incompatible access.  And similarly the out of bounds (invalid)
> plus the boundary case (valid to have address taken, invalid to dereference
> it).  And to my surprise (haven't studied how it actually works), if I
> modify the testcases to have
>   bar (&p->b[24]);
>   bar (&p->b[24]);
>   bar (&p->b[24]);
> (in -1.c) or
>   bar (&p->b);
>   bar (&p->b);
>   bar (&p->b);
> (in -3.c), then fre1 still optimizes all the &p->b[24] to just one SSA_NAME
> (or all the &p->b).

yes, the ->off member is to allow non-structurally same refs to 
value-number the same.  With ->off == -1 (unknown) structurally
same refs are still value-numbered the same.

> > --- a/gcc/tree-ssa-sccvn.cc
> > +++ b/gcc/tree-ssa-sccvn.cc
> > @@ -987,9 +987,12 @@ copy_reference_ops_from_ref (tree ref, 
> > vec<vn_reference_op_s> *result)
> >                      + (wi::to_offset (bit_offset) >> LOG2_BITS_PER_UNIT));
> >                 /* Probibit value-numbering zero offset components
> 
> s/Probibit/Prohibit/

Fixed everywhere.

> >                    of addresses the same before the pass folding
> > -                  __builtin_object_size had a chance to run.  */
> > +                  __builtin_object_size had a chance to run.  Likewise
> > +                  for components of zero size at arbitrary offset.  */
> >                 if (TREE_CODE (orig) != ADDR_EXPR
> > -                   || maybe_ne (off, 0)
> > +                   || (TYPE_SIZE (temp.type)
> > +                       && !integer_zerop (TYPE_SIZE (temp.type))
> 
> Maybe && integer_nonzerop (TYPE_SIZE (temp.type)) instead?
> I mean, if we have a VL structure
> struct T { int b[n]; };
> struct S { int a; struct T b; int c; };
> then b could have zero size (if n == 0) or non-zero one.  Perhaps
> it is punted on elsewhere and __builtin_object_size (&s->b, 1) and
> __builtin_object_size (&s->c, 1) could be different.

Good point.  Changed.

> 
> > +                       && maybe_ne (off, 0))
> >                     || (cfun->curr_properties & PROP_objsz))
> >                   off.to_shwi (&temp.off);
> >               }
> > @@ -1010,9 +1013,30 @@ copy_reference_ops_from_ref (tree ref, 
> > vec<vn_reference_op_s> *result)
> >         if (! temp.op2)
> >           temp.op2 = size_binop (EXACT_DIV_EXPR, TYPE_SIZE_UNIT (eltype),
> >                                  size_int (TYPE_ALIGN_UNIT (eltype)));
> > +       /* Probibit value-numbering addresses of out-of-bound ARRAY_REFs
> 
> s/Probibit/Prohibit/ again
> 
> Plus am not sure if out-of-bound is the right term to use, in the test
> b[24] is not out of bounds, it is one past the last element in the array
> in C/C++ terms, which is out of bounds only for dereferences.
> Perhaps calling the var oob is fine if the comment makes it clear that
> it is about real out of bounds accesses as well as taking address of the
> one past the last element in an array.

I've changed it to 'addresses of one-after-the-last element ARRAY_REFs'

> > +          the same as addresses of other components before the pass
> > +          folding __builtin_object_size had a chance to run.  */
> > +       bool avoid_oob = true;
> > +       if (TREE_CODE (orig) != ADDR_EXPR
> > +           || cfun->curr_properties & PROP_objsz)
> > +         avoid_oob = false;
> > +       else if (poly_int_tree_p (temp.op0))
> > +         {
> > +           tree ub = array_ref_up_bound (ref);
> > +           if (ub
> > +               && poly_int_tree_p (ub)
> > +               /* ???  The C frontend for T[0] uses [0:] and the
> > +                  C++ frontend [0:-1U].  See layout_type for how
> > +                  awkward this is.  */
> > +               && !integer_minus_onep (ub)
> > +               && known_le (wi::to_poly_offset (temp.op0),
> > +                            wi::to_poly_offset (ub)))
> > +             avoid_oob = false;
> 
> I wonder if we shouldn't punt (aka have avoid_oob true) also on temp.op0 
> smaller
> than array_ref_low_bound (ref).

Those would be UB, I've tried to keep the check conservative but fast.

> > +         }
> >         if (poly_int_tree_p (temp.op0)
> >             && poly_int_tree_p (temp.op1)
> > -           && TREE_CODE (temp.op2) == INTEGER_CST)
> > +           && TREE_CODE (temp.op2) == INTEGER_CST
> > +           && !avoid_oob)
> >           {
> >             poly_offset_int off = ((wi::to_poly_offset (temp.op0)
> >                                     - wi::to_poly_offset (temp.op1))
> > @@ -1754,6 +1778,23 @@ re_valueize:
> >            && poly_int_tree_p (vro->op1)
> >            && TREE_CODE (vro->op2) == INTEGER_CST)
> >     {
> > +     /* Probibit value-numbering addresses of out-of-bound ARRAY_REFs
> 
> Again Prohibit
> Same concern about naming.
> 
> > +        the same as addresses of other components before the pass
> > +        folding __builtin_object_size had a chance to run.  */
> > +     if (!(cfun->curr_properties & PROP_objsz)
> > +         && (*orig)[0].opcode == ADDR_EXPR)
> > +       {
> > +         tree dom = TYPE_DOMAIN ((*orig)[i + 1].type);
> > +         if (!dom
> > +             || !TYPE_MAX_VALUE (dom)
> > +             || !poly_int_tree_p (TYPE_MAX_VALUE (dom))
> > +             || integer_minus_onep (TYPE_MAX_VALUE (dom)))
> > +           continue;
> > +         if (!known_le (wi::to_poly_offset (vro->op0),
> > +                        wi::to_poly_offset (TYPE_MAX_VALUE (dom))))
> > +           continue;
> 
> And again wonder about low bound.
> 
> > +       }
> > +
> >       poly_offset_int off = ((wi::to_poly_offset (vro->op0)
> >                               - wi::to_poly_offset (vro->op1))
> >                              * wi::to_offset (vro->op2)
> 
> Otherwise LGTM.

I'll retest, repost and push.

Thanks,
Richard.

> --- gcc/testsuite/c-c++-common/torture/pr117912-3.c.jj        2024-12-09 
> 12:04:07.892204122 +0100
> +++ gcc/testsuite/c-c++-common/torture/pr117912-3.c   2024-12-09 
> 12:10:25.309936271 +0100
> @@ -0,0 +1,61 @@
> +/* { dg-do run } */
> +/* { dg-additional-options "-std=gnu++20" { target c++ } } */
> +
> +struct B {};
> +struct A { int a;
> +#ifdef __cplusplus
> +        [[no_unique_address]]
> +#endif
> +        struct B b;
> +        char c[]; };
> +volatile void *p;
> +
> +void __attribute__((noipa))
> +bar (void *q)
> +{
> +  p = q;
> +}
> +
> +__SIZE_TYPE__ __attribute__((noipa))
> +foo (struct A *p)
> +{
> +  bar (&p->b);
> +  bar (&p->c);
> +  return __builtin_object_size (&p->c, 1);
> +}
> +
> +__SIZE_TYPE__ __attribute__((noipa))
> +baz (void)
> +{
> +  struct A *p = (struct A *) __builtin_malloc (__builtin_offsetof (struct A, 
> c) + 64);
> +  bar (&p->b);
> +  bar (&p->c);
> +  return __builtin_object_size (&p->c, 1);
> +}
> +
> +__SIZE_TYPE__ __attribute__((noipa))
> +qux (struct A *p)
> +{
> +  bar (&p->b);
> +  bar (&p->c);
> +  return __builtin_object_size (&p->c, 3);
> +}
> +
> +__SIZE_TYPE__ __attribute__((noipa))
> +boo (void)
> +{
> +  struct A *p = (struct A *) __builtin_malloc (__builtin_offsetof (struct A, 
> c) + 64);
> +  bar (&p->b);
> +  bar (&p->c);
> +  return __builtin_object_size (&p->c, 3);
> +}
> +
> +int
> +main ()
> +{
> +  static struct A a = { .a = 1, .b = {}, .c = { 1, 2, 3, 4, 0 } };
> +  if (foo (&a) < 5)
> +    __builtin_abort ();
> +  if (baz () < 64)
> +    __builtin_abort ();
> +}
> 
> 
>       Jakub
> 
> 

-- 
Richard Biener <rguent...@suse.de>
SUSE Software Solutions Germany GmbH,
Frankenstrasse 146, 90461 Nuernberg, Germany;
GF: Ivo Totev, Andrew McDonald, Werner Knoblich; (HRB 36809, AG Nuernberg)

Reply via email to