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)