On Mon, 3 Aug 2020, Jason Merrill wrote: > On 8/3/20 2:45 PM, Patrick Palka wrote: > > On Mon, 3 Aug 2020, Jason Merrill wrote: > > > > > On 8/3/20 8:53 AM, Patrick Palka wrote: > > > > On Mon, 3 Aug 2020, Patrick Palka wrote: > > > > > > > > > In the first testcase below, expand_aggr_init_1 sets up t's default > > > > > constructor such that the ctor first zero-initializes the entire base > > > > > b, > > > > > followed by calling b's default constructor, the latter of which just > > > > > default-initializes the array member b::m via a VEC_INIT_EXPR. > > > > > > > > > > So upon constexpr evaluation of this latter VEC_INIT_EXPR, ctx->ctor > > > > > is > > > > > nonempty due to the prior zero-initialization, and we proceed in > > > > > cxx_eval_vec_init to append new constructor_elts to the end of > > > > > ctx->ctor > > > > > without first checking if a matching constructor_elt already exists. > > > > > This leads to ctx->ctor having two matching constructor_elts for each > > > > > index. > > > > > > > > > > This patch partially fixes this issue by making the RANGE_EXPR > > > > > optimization in cxx_eval_vec_init truncate ctx->ctor before adding the > > > > > single RANGE_EXPR constructor_elt. This isn't a complete fix because > > > > > the RANGE_EXPR optimization applies only when the constant initializer > > > > > is relocatable, so whenever it's not relocatable we can still build up > > > > > an invalid CONSTRUCTOR, e.g. if in the first testcase we add an NSDMI > > > > > such as 'e *p = this;' to struct e, then the ICE still occurs even > > > > > with > > > > > this patch. > > > > > > > > A complete but more risky one-line fix would be to always truncate > > > > ctx->ctor beforehand, not just when the RANGE_EXPR optimization applies. > > > > If it's true that the initializer of a VEC_INIT_EXPR can't observe the > > > > previous elements of the target array, then it should be safe to always > > > > truncate I think? > > > > > > What if default-initialization of the array element type doesn't fully > > > initialize the elements, e.g. if 'e' had another member without a default > > > initializer? Does truncation first mean we lose the zero-initialization > > > of > > > such a member? > > > > Hmm, it looks like we would lose the zero-initialization of such a > > member with or without truncation first (so with any one of the three > > proposed fixes). I think it's because the evaluation loop in > > cxx_eval_vec_init disregards each element's prior (zero-initialized) > > state. > > > > > > > > We could probably still do the truncation, but clear the > > > CONSTRUCTOR_NO_CLEARING flag on the element initializer. > > > > Ah, this seems to work well. Like this? > > > > -- >8 -- > > > > Subject: [PATCH] c++: cxx_eval_vec_init after zero initialization [PR96282] > > > > In the first testcase below, expand_aggr_init_1 sets up t's default > > constructor such that the ctor first zero-initializes the entire base b, > > followed by calling b's default constructor, the latter of which just > > default-initializes the array member b::m via a VEC_INIT_EXPR. > > > > So upon constexpr evaluation of this latter VEC_INIT_EXPR, ctx->ctor is > > nonempty due to the prior zero-initialization, and we proceed in > > cxx_eval_vec_init to append new constructor_elts to the end of ctx->ctor > > without first checking if a matching constructor_elt already exists. > > This leads to ctx->ctor having two matching constructor_elts for each > > index. > > > > This patch fixes this issue by truncating a zero-initialized array > > object in cxx_eval_vec_init_1 before we begin appending default-initialized > > array elements to it. Since default-initialization may leave parts of > > the element type unitialized, we also preserve the array's prior > > zero-initialized state by clearing CONSTRUCTOR_NO_CLEARING on each > > appended element initializers. > > > > gcc/cp/ChangeLog: > > > > PR c++/96282 > > * constexpr.c (cxx_eval_vec_init_1): Truncate ctx->ctor and > > then clear CONSTRUCTOR_NO_CLEARING on each appended element > > initializer if we're default-initializing a previously > > zero-initialized array object. > > > > gcc/testsuite/ChangeLog: > > > > PR c++/96282 > > * g++.dg/cpp0x/constexpr-array26.C: New test. > > * g++.dg/cpp0x/constexpr-array27.C: New test. > > * g++.dg/cpp2a/constexpr-init18.C: New test. > > --- > > gcc/cp/constexpr.c | 17 ++++++++++++++++- > > gcc/testsuite/g++.dg/cpp0x/constexpr-array26.C | 13 +++++++++++++ > > gcc/testsuite/g++.dg/cpp0x/constexpr-array27.C | 13 +++++++++++++ > > gcc/testsuite/g++.dg/cpp2a/constexpr-init18.C | 16 ++++++++++++++++ > > 4 files changed, 58 insertions(+), 1 deletion(-) > > create mode 100644 gcc/testsuite/g++.dg/cpp0x/constexpr-array26.C > > create mode 100644 gcc/testsuite/g++.dg/cpp0x/constexpr-array27.C > > create mode 100644 gcc/testsuite/g++.dg/cpp2a/constexpr-init18.C > > > > diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c > > index b1c1d249c6e..706bef323b2 100644 > > --- a/gcc/cp/constexpr.c > > +++ b/gcc/cp/constexpr.c > > @@ -4171,6 +4171,17 @@ cxx_eval_vec_init_1 (const constexpr_ctx *ctx, tree > > atype, tree init, > > pre_init = true; > > } > > + bool zero_initialized_p = false; > > + if ((pre_init || value_init || !init) && initializer_zerop (ctx->ctor)) > > Does initializer_zerop capture the difference between a default-initialized or > value-initialized containing object? I would expect it to be true for both. > Maybe check CONSTRUCTOR_NO_CLEARING (ctx->ctor) instead?
D'oh, indeed it looks like initializer_zerop is not what we want here. > > This all seems parallel to the no_zero_init code in cxx_eval_store_expression. I am testing the following, which inspects CONSTRUCTOR_NO_CLEARING and also beefs up the tests to verify that we handle the multidimensional array case correctly. -- >8 -- Subject: [PATCH] c++: cxx_eval_vec_init after zero initialization [PR96282] In the first testcase below, expand_aggr_init_1 sets up t's default constructor such that the ctor first zero-initializes the entire base b, followed by calling b's default constructor, the latter of which just default-initializes the array member b::m via a VEC_INIT_EXPR. So upon constexpr evaluation of this latter VEC_INIT_EXPR, ctx->ctor is nonempty due to the prior zero-initialization, and we proceed in cxx_eval_vec_init to append new constructor_elts to the end of ctx->ctor without first checking if a matching constructor_elt already exists. This leads to ctx->ctor having two matching constructor_elts for each index. This patch fixes this issue by truncating a zeroed out array CONSTRUCTOR in cxx_eval_vec_init_1 before we begin appending array elements to it. We preserve its zeroed out state during evaluation by clearing CONSTRUCTOR_NO_CLEARING on each new appended element. gcc/cp/ChangeLog: PR c++/96282 * constexpr.c (cxx_eval_vec_init_1): Truncate ctx->ctor and then clear CONSTRUCTOR_NO_CLEARING on each appended element initializer if we're initializing a previously zeroed out array object. gcc/testsuite/ChangeLog: PR c++/96282 * g++.dg/cpp0x/constexpr-array26.C: New test. * g++.dg/cpp0x/constexpr-array27.C: New test. * g++.dg/cpp2a/constexpr-init18.C: New test. --- gcc/cp/constexpr.c | 18 +++++++++++++++++- gcc/testsuite/g++.dg/cpp0x/constexpr-array26.C | 13 +++++++++++++ gcc/testsuite/g++.dg/cpp0x/constexpr-array27.C | 13 +++++++++++++ gcc/testsuite/g++.dg/cpp2a/constexpr-init18.C | 16 ++++++++++++++++ 4 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 gcc/testsuite/g++.dg/cpp0x/constexpr-array26.C create mode 100644 gcc/testsuite/g++.dg/cpp0x/constexpr-array27.C create mode 100644 gcc/testsuite/g++.dg/cpp2a/constexpr-init18.C diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c index b1c1d249c6e..364630f6333 100644 --- a/gcc/cp/constexpr.c +++ b/gcc/cp/constexpr.c @@ -4171,6 +4171,18 @@ cxx_eval_vec_init_1 (const constexpr_ctx *ctx, tree atype, tree init, pre_init = true; } + bool zeroed_out = false; + if ((pre_init || value_init || !init) + && !CONSTRUCTOR_NO_CLEARING (ctx->ctor)) + { + /* We're initializing an array object that had been zeroed out + earlier. Truncate ctx->ctor and preserve its prior state by + propagating CONSTRUCTOR_NO_CLEARING to each of the element + initializers we append to it. */ + zeroed_out = true; + vec_safe_truncate (*p, 0); + } + tree nelts = get_array_or_vector_nelts (ctx, atype, non_constant_p, overflow_p); unsigned HOST_WIDE_INT max = tree_to_uhwi (nelts); @@ -4182,7 +4194,11 @@ cxx_eval_vec_init_1 (const constexpr_ctx *ctx, tree atype, tree init, constexpr_ctx new_ctx; init_subob_ctx (ctx, new_ctx, idx, pre_init ? init : elttype); if (new_ctx.ctor != ctx->ctor) - CONSTRUCTOR_APPEND_ELT (*p, idx, new_ctx.ctor); + { + if (zeroed_out) + CONSTRUCTOR_NO_CLEARING (new_ctx.ctor) = false; + CONSTRUCTOR_APPEND_ELT (*p, idx, new_ctx.ctor); + } if (TREE_CODE (elttype) == ARRAY_TYPE) { /* A multidimensional array; recurse. */ diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-array26.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-array26.C new file mode 100644 index 00000000000..274f55a88bf --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-array26.C @@ -0,0 +1,13 @@ +// PR c++/96282 +// { dg-do compile { target c++11 } } + +struct e { bool v = true; }; + +template<int N> +struct b { e m[N]; }; + +template<int N> +struct t : b<N> { constexpr t() : b<N>() {} }; + +constexpr t<1> h1; +constexpr t<42> h2; diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-array27.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-array27.C new file mode 100644 index 00000000000..1234caef31d --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-array27.C @@ -0,0 +1,13 @@ +// PR c++/96282 +// { dg-do compile { target c++11 } } + +struct e { bool v = true; e *p = this; }; + +template<int N> +struct b { e m[N][N]; }; + +template<int N> +struct t : b<N> { constexpr t() : b<N>() {} }; + +constexpr t<1> h1; +constexpr t<42> h2; diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-init18.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-init18.C new file mode 100644 index 00000000000..47ad11f2290 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-init18.C @@ -0,0 +1,16 @@ +// PR c++/96282 +// { dg-do compile { target c++20 } } + +struct e { bool v = true; bool w; }; + +template<int N> +struct b { e m[N][N]; }; + +template<int N> +struct t : b<N> { constexpr t() : b<N>() {} }; + +constexpr t<1> h1; +static_assert(h1.m[0][0].w == false); + +constexpr t<42> h2; +static_assert(h2.m[17][17].w == false); -- 2.28.0.89.g85b4e0a6dc