On Wed, 18 Mar 2020, Patrick Palka wrote: > On Tue, 17 Mar 2020, Jason Merrill wrote: > > > On 3/16/20 1:39 PM, Patrick Palka wrote: > > > In this PR, we are performing constexpr evaluation of a CONSTRUCTOR of > > > type > > > union U which looks like > > > > > > {.a=foo (&<PLACEHOLDER_EXPR union U>)}. > > > > > > Since the function foo takes a reference to the CONSTRUCTOR we're > > > building, > > > it > > > could potentially modify the CONSTRUCTOR from under us. In particular > > > since > > > U > > > is a union, the evaluation of a's initializer could change the active > > > member > > > from a to another member -- something which cxx_eval_bare_aggregate > > > doesn't > > > expect to happen. > > > > > > Upon further investigation, it turns out this issue is not limited to > > > constructors of UNION_TYPE and not limited to cxx_eval_bare_aggregate > > > either. > > > For example, within cxx_eval_store_expression we may be evaluating an > > > assignment > > > such as (this comes from the test pr94066-2.C): > > > > > > ((union U *) this)->a = TARGET_EXPR <D.2163, foo ((union U *) this)>; > > > > I assume this is actually an INIT_EXPR, or we would have preevaluated and > > not > > had this problem. > > Yes exactly, I should have specified that the above is an INIT_EXPR and > not a MODIFY_EXPR. > > > > > > where evaluation of foo could change the active member of *this, which was > > > set > > > earlier in cxx_eval_store_expression to 'a'. And if U is a RECORD_TYPE, > > > then > > > evaluation of foo could add new fields to *this, thereby making stale the > > > 'valp' > > > pointer to the target constructor_elt through which we're later assigning. > > > > > > So in short, it seems that both cxx_eval_bare_aggregate and > > > cxx_eval_store_expression do not anticipate that a constructor_elt's > > > initializer > > > could modify the underlying CONSTRUCTOR as a side-effect. > > > > Oof. If this is well-formed, it's because initialization of a doesn't > > actually start until the return statement of foo, so we're probably wrong to > > create a CONSTRUCTOR to hold the value of 'a' before evaluating foo. > > Perhaps > > init_subob_ctx shouldn't preemptively create a CONSTRUCTOR, and similarly > > for > > the cxx_eval_store_expression !preeval code. > > Hmm, I think I see what you mean. I'll look into this.
In cpp0x/constexpr-array12.C we have struct A { int ar[3]; }; constexpr A a1 = { 0, a1.ar[0] }; the initializer for a1 is a CONSTRUCTOR with the form {.ar={0, (int) VIEW_CONVERT_EXPR<const struct A>(a1).ar[0]}} If we don't preemptively create a CONSTRUCTOR in cxx_eval_bare_aggregate to hold the value of 'ar' before evaluating its initializer, then we won't be able to resolve the 'a1.ar[0]' later on, and we will reject this otherwise valid test case with an "accessing an uninitialized array element" diagnostic. So it seems we need to continue creating a CONSTRUCTOR in cxx_eval_bare_aggregate before evaluating the initializer of an aggregate sub-object to handle self-referential CONSTRUCTORs like the one above. Then again, clang is going with rejecting the original testcase with the following justification: https://bugs.llvm.org/show_bug.cgi?id=45133#c1 Should we follow suit? > > > > > > To fix this problem, this patch makes cxx_eval_bare_aggregate and > > > cxx_eval_store_expression recompute the pointer to the constructor_elt > > > through > > > which we're assigning, after the initializer has been evaluated. > > > > > > I am worried that the use of get_or_insert_ctor_field in > > > cxx_eval_bare_aggregate > > > might introduce quadratic behavior where there wasn't before. I wonder if > > > there's a cheap heuristic we can use in cxx_eval_bare_aggregate to > > > determine > > > whether "self-modification" took place, and in which case we could avoid > > > calling > > > get_or_insert_ctor_field and do the fast thing we that we were doing > > > before > > > the > > > patch. > > > > We could remember the (integer) index of the constructor element and verify > > that our sub-ctors are still at those indices before doing a new search. > > Good idea, this should essentially eliminate the overhead of the second > set of calls to get_or_insert_ctor_fields in cxx_eval_store_expression > in the common case where the initializer doesn't mutate the underlying > CONSTRUCTORs. I added this optimization to v2 of the patch, through a > new 'pos_hint' parameter of get_or_insert_ctor_fields. > > As another optimization to get_or_insert_ctor_fields, we can first check > whether the bit_position of INDEX is greater than the bit_position of > the last constructor element of CTOR. In his case we can immediately > append a new constructor element to CTOR, and avoid iterating over the > whole TYPE_FIELDS chain and over CTOR. I think this optimization > eliminates the potential quadratic behavior in cxx_eval_bare_aggregate > in the common case. > > With these two optimizations, the added overhead of the recomputations > added by this patch should be negligible. > > -- >8 -- > > gcc/cp/ChangeLog: > > PR c++/94066 > * constexpr.c (get_or_insert_ctor_field): Split out (while adding > handling for VECTOR_TYPEs, and common-case optimizations) from ... > (cxx_eval_store_expression): ... here. Record the sequence of indexes > into INDEXES that yields the suboject we're assigning to. Record the > integer offsets of the constructor indexes we're assigning through into > INDEX_POSITIONS. After evaluating the initializer of the store > expression, recompute valp using INDEXES and using INDEX_POSITIONS as > hints. > (cxx_eval_bare_aggregate): Use get_or_insert_ctor_field to recompute the > pointer to the constructor_elt we're assigning through after evaluating > each initializer. > > gcc/testsuite/ChangeLog: > > PR c++/94066 > * g++.dg/cpp1y/pr94066.C: New test. > * g++.dg/cpp1y/pr94066-2.C: New test. > * g++.dg/cpp1y/pr94066-3.C: New test. > * g++.dg/cpp1y/pr94066-4.C: New test. > * g++.dg/cpp1y/pr94066-5.C: New test. > --- > gcc/cp/constexpr.c | 219 ++++++++++++++++--------- > gcc/testsuite/g++.dg/cpp1y/pr94066-2.C | 21 +++ > gcc/testsuite/g++.dg/cpp1y/pr94066-3.C | 21 +++ > gcc/testsuite/g++.dg/cpp1y/pr94066-4.C | 22 +++ > gcc/testsuite/g++.dg/cpp1y/pr94066-5.C | 18 ++ > gcc/testsuite/g++.dg/cpp1y/pr94066.C | 20 +++ > 6 files changed, 242 insertions(+), 79 deletions(-) > create mode 100644 gcc/testsuite/g++.dg/cpp1y/pr94066-2.C > create mode 100644 gcc/testsuite/g++.dg/cpp1y/pr94066-3.C > create mode 100644 gcc/testsuite/g++.dg/cpp1y/pr94066-4.C > create mode 100644 gcc/testsuite/g++.dg/cpp1y/pr94066-5.C > create mode 100644 gcc/testsuite/g++.dg/cpp1y/pr94066.C > > diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c > index 192face9a3a..9ea64467661 100644 > --- a/gcc/cp/constexpr.c > +++ b/gcc/cp/constexpr.c > @@ -3144,6 +3144,107 @@ find_array_ctor_elt (tree ary, tree dindex, bool > insert) > return -1; > } > > +/* Return a pointer to the constructor_elt of CTOR which matches INDEX. If > no > + matching constructor_elt exists, then add one to CTOR. Emit an > appropriate > + diagnostic if CTOR constructs a UNION_TYPE and adding INDEX would change > the > + active member of the union, unless QUIET is true. As an optimization, > + if POS_HINT is non-negative then it is used as a guess for the (integer) > + index of the matching constructor_elt within CTOR. */ > + > +static constructor_elt * > +get_or_insert_ctor_field (tree ctor, tree index, int pos_hint, > + bool quiet, bool *non_constant_p) > +{ > + tree type = TREE_TYPE (ctor); > + if (TREE_CODE (type) == VECTOR_TYPE && index == NULL_TREE) > + { > + CONSTRUCTOR_APPEND_ELT (CONSTRUCTOR_ELTS (ctor), index, NULL_TREE); > + return &CONSTRUCTOR_ELTS (ctor)->last(); > + } > + else if (TREE_CODE (type) == ARRAY_TYPE || TREE_CODE (type) == VECTOR_TYPE) > + { > + HOST_WIDE_INT i = find_array_ctor_elt (ctor, index, /*insert*/true); > + gcc_assert (i >= 0); > + constructor_elt *cep = CONSTRUCTOR_ELT (ctor, i); > + gcc_assert (cep->index == NULL_TREE > + || TREE_CODE (cep->index) != RANGE_EXPR); > + return cep; > + } > + else > + { > + gcc_assert (TREE_CODE (index) == FIELD_DECL); > + > + /* We must keep the CONSTRUCTOR's ELTS in FIELD order. > + Usually we meet initializers in that order, but it is > + possible for base types to be placed not in program > + order. */ > + tree fields = TYPE_FIELDS (DECL_CONTEXT (index)); > + unsigned HOST_WIDE_INT idx = 0; > + constructor_elt *cep = NULL; > + > + if (TREE_CODE (type) == UNION_TYPE && CONSTRUCTOR_NELTS (ctor) > + && CONSTRUCTOR_ELT (ctor, 0)->index != index) > + { > + if (cxx_dialect < cxx2a) > + { > + if (!quiet) > + error ("change of the active member of a union from %qD to %qD", > + CONSTRUCTOR_ELT (ctor, 0)->index, > + index); > + *non_constant_p = true; > + } > + /* Changing active member. */ > + vec_safe_truncate (CONSTRUCTOR_ELTS (ctor), 0); > + } > + /* Check the hint. */ > + else if (pos_hint >= 0 && (unsigned)pos_hint < CONSTRUCTOR_NELTS (ctor) > + && CONSTRUCTOR_ELT (ctor, pos_hint)->index == index) > + { > + cep = CONSTRUCTOR_ELT (ctor, pos_hint); > + goto found; > + } > + /* If the bit offset of INDEX is at larger than that of the endmost > + constructor_elt, then just immediately append a new constructor_elt to > + the end of CTOR. */ > + else if (TREE_CODE (type) == RECORD_TYPE && CONSTRUCTOR_NELTS (ctor) > + && tree_int_cst_compare (bit_position (index), > + bit_position (CONSTRUCTOR_ELTS (ctor) > + ->last().index)) > 0) > + { > + idx = CONSTRUCTOR_NELTS (ctor); > + goto insert; > + } > + > + for (; vec_safe_iterate (CONSTRUCTOR_ELTS (ctor), idx, &cep); > + idx++, fields = DECL_CHAIN (fields)) > + { > + if (index == cep->index) > + goto found; > + > + /* The field we're initializing must be on the field > + list. Look to see if it is present before the > + field the current ELT initializes. */ > + for (; fields != cep->index; fields = DECL_CHAIN (fields)) > + if (index == fields) > + goto insert; > + } > + > + /* We fell off the end of the CONSTRUCTOR, so insert a new > + entry at the end. */ > + insert: > + { > + constructor_elt ce = { index, NULL_TREE }; > + > + vec_safe_insert (CONSTRUCTOR_ELTS (ctor), idx, ce); > + cep = CONSTRUCTOR_ELT (ctor, idx); > + } > + found:; > + > + return cep; > + } > +} > + > + > /* Under the control of CTX, issue a detailed diagnostic for > an out-of-bounds subscript INDEX into the expression ARRAY. */ > > @@ -3747,10 +3848,14 @@ cxx_eval_bare_aggregate (const constexpr_ctx *ctx, > tree t, > { > tree orig_value = value; > init_subob_ctx (ctx, new_ctx, index, value); > + int pos_hint = -1; > if (new_ctx.ctor != ctx->ctor) > - /* If we built a new CONSTRUCTOR, attach it now so that other > - initializers can refer to it. */ > - CONSTRUCTOR_APPEND_ELT (*p, index, new_ctx.ctor); > + { > + /* If we built a new CONSTRUCTOR, attach it now so that other > + initializers can refer to it. */ > + CONSTRUCTOR_APPEND_ELT (*p, index, new_ctx.ctor); > + pos_hint = vec_safe_length (*p) - 1; > + } > tree elt = cxx_eval_constant_expression (&new_ctx, value, > lval, > non_constant_p, overflow_p); > @@ -3784,14 +3889,15 @@ cxx_eval_bare_aggregate (const constexpr_ctx *ctx, > tree t, > } > else > { > - if (new_ctx.ctor != ctx->ctor) > - { > - /* We appended this element above; update the value. */ > - gcc_assert ((*p)->last().index == index); > - (*p)->last().value = elt; > - } > - else > - CONSTRUCTOR_APPEND_ELT (*p, index, elt); > + /* The underlying CONSTRUCTOR might have been mutated by the > + initializer, so recompute the location of the target > + constructor_elt. */ > + iloc_sentinel ils (cp_expr_location (t)); > + constructor_elt *cep > + = get_or_insert_ctor_field (ctx->ctor, index, pos_hint, > + ctx->quiet, non_constant_p); > + cep->value = elt; > + > /* Adding or replacing an element might change the ctor's flags. */ > TREE_CONSTANT (ctx->ctor) = constant_p; > TREE_SIDE_EFFECTS (ctx->ctor) = side_effects_p; > @@ -4566,7 +4672,8 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, > tree t, > type = TREE_TYPE (object); > bool no_zero_init = true; > > - releasing_vec ctors; > + releasing_vec ctors, indexes; > + auto_vec<int> index_positions; > while (!refs->is_empty ()) > { > if (*valp == NULL_TREE) > @@ -4607,77 +4714,23 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, > tree t, > subobjects will also be zero-initialized. */ > no_zero_init = CONSTRUCTOR_NO_CLEARING (*valp); > > - vec_safe_push (ctors, *valp); > - > enum tree_code code = TREE_CODE (type); > type = refs->pop(); > tree index = refs->pop(); > > - constructor_elt *cep = NULL; > - if (code == ARRAY_TYPE) > - { > - HOST_WIDE_INT i > - = find_array_ctor_elt (*valp, index, /*insert*/true); > - gcc_assert (i >= 0); > - cep = CONSTRUCTOR_ELT (*valp, i); > - gcc_assert (cep->index == NULL_TREE > - || TREE_CODE (cep->index) != RANGE_EXPR); > - } > - else > - { > - gcc_assert (TREE_CODE (index) == FIELD_DECL); > - > - /* We must keep the CONSTRUCTOR's ELTS in FIELD order. > - Usually we meet initializers in that order, but it is > - possible for base types to be placed not in program > - order. */ > - tree fields = TYPE_FIELDS (DECL_CONTEXT (index)); > - unsigned HOST_WIDE_INT idx; > - > - if (code == UNION_TYPE && CONSTRUCTOR_NELTS (*valp) > - && CONSTRUCTOR_ELT (*valp, 0)->index != index) > - { > - if (cxx_dialect < cxx2a) > - { > - if (!ctx->quiet) > - error_at (cp_expr_loc_or_input_loc (t), > - "change of the active member of a union " > - "from %qD to %qD", > - CONSTRUCTOR_ELT (*valp, 0)->index, > - index); > - *non_constant_p = true; > - } > - /* Changing active member. */ > - vec_safe_truncate (CONSTRUCTOR_ELTS (*valp), 0); > - no_zero_init = true; > - } > + vec_safe_push (ctors, *valp); > + vec_safe_push (indexes, index); > > - for (idx = 0; > - vec_safe_iterate (CONSTRUCTOR_ELTS (*valp), idx, &cep); > - idx++, fields = DECL_CHAIN (fields)) > - { > - if (index == cep->index) > - goto found; > - > - /* The field we're initializing must be on the field > - list. Look to see if it is present before the > - field the current ELT initializes. */ > - for (; fields != cep->index; fields = DECL_CHAIN (fields)) > - if (index == fields) > - goto insert; > - } > + if (code == UNION_TYPE && CONSTRUCTOR_NELTS (*valp) > + && CONSTRUCTOR_ELT (*valp, 0)->index != index) > + no_zero_init = true; > > - /* We fell off the end of the CONSTRUCTOR, so insert a new > - entry at the end. */ > - insert: > - { > - constructor_elt ce = { index, NULL_TREE }; > + iloc_sentinel ils (cp_expr_location (t)); > + constructor_elt *cep > + = get_or_insert_ctor_field (*valp, index, /*pos_hint=*/-1, > + ctx->quiet, non_constant_p); > + index_positions.safe_push (cep - CONSTRUCTOR_ELTS (*valp)->address ()); > > - vec_safe_insert (CONSTRUCTOR_ELTS (*valp), idx, ce); > - cep = CONSTRUCTOR_ELT (*valp, idx); > - } > - found:; > - } > valp = &cep->value; > } > > @@ -4758,9 +4811,17 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, > tree t, > init = tinit; > init = cxx_eval_constant_expression (&new_ctx, init, false, > non_constant_p, overflow_p); > - if (ctors->is_empty()) > - /* The hash table might have moved since the get earlier. */ > - valp = ctx->global->values.get (object); > + /* The hash table might have moved since the get earlier. */ > + valp = ctx->global->values.get (object); > + /* And the underlying CONSTRUCTORs might have been mutated by the > + initializer, so we must recompute VALP. */ > + for (unsigned i = 0; i < vec_safe_length (indexes); i++) > + { > + constructor_elt *cep > + = get_or_insert_ctor_field (*valp, indexes[i], index_positions[i], > + /*quiet=*/true, non_constant_p); > + valp = &cep->value; > + } > } > > /* Don't share a CONSTRUCTOR that might be changed later. */ > diff --git a/gcc/testsuite/g++.dg/cpp1y/pr94066-2.C > b/gcc/testsuite/g++.dg/cpp1y/pr94066-2.C > new file mode 100644 > index 00000000000..ec8210a9d73 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/cpp1y/pr94066-2.C > @@ -0,0 +1,21 @@ > +// PR c++/94066 > +// { dg-do compile { target c++14 } } > + > +struct A { long x; }; > + > +union U; > +constexpr A foo(U *up); > + > +union U { > + U() = default; > + A a = foo(this); int y; > +}; > + > +constexpr A foo(U *up) { > + up->y = 11; // { dg-error ".U::a. to .U::y." "" { target c++17_down } } > + return {42}; > +} > + > +extern constexpr U u = {}; // { dg-error ".U::y. to .U::a." "" { target > c++17_down } } > + > +static_assert(u.a.x == 42, ""); // { dg-error "non-constant" "" { target > c++17_down } } > diff --git a/gcc/testsuite/g++.dg/cpp1y/pr94066-3.C > b/gcc/testsuite/g++.dg/cpp1y/pr94066-3.C > new file mode 100644 > index 00000000000..b68c975d569 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/cpp1y/pr94066-3.C > @@ -0,0 +1,21 @@ > +// PR c++/94066 > +// { dg-do compile { target c++14 } } > + > +struct A { long x; }; > + > +struct U; > +constexpr A foo(U *up); > + > +struct U { > + A a = foo(this); int y; > +}; > + > +constexpr A foo(U *up) { > + up->y = 11; > + return {42}; > +} > + > +extern constexpr U u = {}; > + > +static_assert(u.y == 11, ""); > +static_assert(u.a.x == 42, ""); > diff --git a/gcc/testsuite/g++.dg/cpp1y/pr94066-4.C > b/gcc/testsuite/g++.dg/cpp1y/pr94066-4.C > new file mode 100644 > index 00000000000..0362c939faf > --- /dev/null > +++ b/gcc/testsuite/g++.dg/cpp1y/pr94066-4.C > @@ -0,0 +1,22 @@ > +// PR c++/94066 > +// { dg-do compile { target c++14 } } > + > +struct A { long x; }; > + > +struct U; > +constexpr A foo(U *up); > + > +struct U { > + U() = default; > + int y; A a = foo(this); > +}; > + > +constexpr A foo(U *up) { > + up->y = 11; > + return {42}; > +} > + > +extern constexpr U u = {}; > + > +static_assert(u.y == 11, ""); > +static_assert(u.a.x == 42, ""); > diff --git a/gcc/testsuite/g++.dg/cpp1y/pr94066-5.C > b/gcc/testsuite/g++.dg/cpp1y/pr94066-5.C > new file mode 100644 > index 00000000000..63069f3ea5f > --- /dev/null > +++ b/gcc/testsuite/g++.dg/cpp1y/pr94066-5.C > @@ -0,0 +1,18 @@ > +// PR c++/94066 > +// { dg-do compile { target c++14 } } > + > +union U; > +constexpr int foo(U *up); > + > +union U { > + int a = foo(this); int y; > +}; > + > +constexpr int foo(U *up) { > + up->y = 11; > + return 42; > +} > + > +extern constexpr U u = {}; // { dg-error ".U::y. to .U::a." "" { target > c++17_down } } > + > +static_assert(u.a == 42, ""); // { dg-error "non-constant" "" { target > c++17_down } } > diff --git a/gcc/testsuite/g++.dg/cpp1y/pr94066.C > b/gcc/testsuite/g++.dg/cpp1y/pr94066.C > new file mode 100644 > index 00000000000..11912199406 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/cpp1y/pr94066.C > @@ -0,0 +1,20 @@ > +// PR c++/94066 > +// { dg-do compile { target c++14 } } > + > +struct A { long x; }; > + > +union U; > +constexpr A foo(U *up); > + > +union U { > + A a = foo(this); int y; > +}; > + > +constexpr A foo(U *up) { > + up->y = 11; // { dg-error ".U::a. to .U::y." "" { target c++17_down } } > + return {42}; > +} > + > +extern constexpr U u = {}; // { dg-error ".U::y. to .U::a." "" { target > c++17_down } } > + > +static_assert(u.a.x == 42, ""); // { dg-error "non-constant" "" { target > c++17_down } } > -- > 2.26.0.rc1.11.g30e9940356 > >