On Mon, 3 Aug 2020, Patrick Palka wrote:

> 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.

Hmm, might we need to check both?  !CONSTRUCTOR_NO_CLEARING tells us
about the omitted initializers, but it seems we'd still need to test if
the explicit initializers are zero.

> 
> > 
> > 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
> 
> 

Reply via email to