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?

Another complete fix would be to replace the uses of CONSTRUCTOR_APPEND_ELT
with get_or_insert_ctor_field, which does the right thing when a
matching constructor_elt already exists.  But get_or_insert_ctor_field
was introduced only in GCC 10, .

I'm not sure which fix we should go with in light of this being an
8/9/10/11 regression..

> 
> A side benefit of the approach taken by this patch is that constexpr
> evaluation of a simple VEC_INIT_EXPR for a high-dimensional array no
> longer scales exponentially with the number of dimensions.  This is
> because after the RANGE_EXPR optimization, the CONSTRUCTOR for each
> array dimension now consists of a single constructor_elt (with index
> 0...max-1) instead of two constructor_elts (with indexes 0 and 1...max-1
> respectively).  This is verified by the second testcase below.
> 
> Bootstrapped and regtested on x86_64-pc-linux-gnu.  Does this look OK
> for trunk and perhaps for backports?
> 
> gcc/cp/ChangeLog:
> 
>       PR c++/96282
>       * constexpr.c (cxx_eval_vec_init_1): Move the i == 0 test to the
>       if statement that guards the RANGE_EXPR optimization. Consider the
>       RANGE_EXPR optimization before we append the first element.
>       Truncate ctx->ctor when performing the RANGE_EXPR optimization.
>       Make the built RANGE_EXPR start at index 0 instead of 1.  Don't
>       call unshare_constructor.
> 
> gcc/testsuite/ChangeLog:
> 
>       PR c++/96282
>       * g++.dg/cpp0x/constexpr-array26.C: New test.
>       * g++.dg/cpp0x/constexpr-array27.C: New test.
> ---
>  gcc/cp/constexpr.c                            | 36 ++++++++++---------
>  .../g++.dg/cpp0x/constexpr-array26.C          | 13 +++++++
>  .../g++.dg/cpp0x/constexpr-array27.C          | 18 ++++++++++
>  3 files changed, 51 insertions(+), 16 deletions(-)
>  create mode 100644 gcc/testsuite/g++.dg/cpp0x/constexpr-array26.C
>  create mode 100644 gcc/testsuite/g++.dg/cpp0x/constexpr-array27.C
> 
> diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c
> index b1c1d249c6e..3808a0713ba 100644
> --- a/gcc/cp/constexpr.c
> +++ b/gcc/cp/constexpr.c
> @@ -4189,7 +4189,7 @@ cxx_eval_vec_init_1 (const constexpr_ctx *ctx, tree 
> atype, tree init,
>         if (value_init || init == NULL_TREE)
>           {
>             eltinit = NULL_TREE;
> -           reuse = i == 0;
> +           reuse = true;
>           }
>         else
>           eltinit = cp_build_array_ref (input_location, init, idx, complain);
> @@ -4206,7 +4206,7 @@ cxx_eval_vec_init_1 (const constexpr_ctx *ctx, tree 
> atype, tree init,
>           return ctx->ctor;
>         eltinit = cxx_eval_constant_expression (&new_ctx, init, lval,
>                                                 non_constant_p, overflow_p);
> -       reuse = i == 0;
> +       reuse = true;
>       }
>        else
>       {
> @@ -4222,33 +4222,37 @@ cxx_eval_vec_init_1 (const constexpr_ctx *ctx, tree 
> atype, tree init,
>       }
>        if (*non_constant_p && !ctx->quiet)
>       break;
> -      if (new_ctx.ctor != ctx->ctor)
> -     {
> -       /* We appended this element above; update the value.  */
> -       gcc_assert ((*p)->last().index == idx);
> -       (*p)->last().value = eltinit;
> -     }
> -      else
> -     CONSTRUCTOR_APPEND_ELT (*p, idx, eltinit);
> +
>        /* Reuse the result of cxx_eval_constant_expression call
>        from the first iteration to all others if it is a constant
>        initializer that doesn't require relocations.  */
> -      if (reuse
> -       && max > 1
> +      if (i == 0
> +       && reuse
>         && (eltinit == NULL_TREE
>             || (initializer_constant_valid_p (eltinit, TREE_TYPE (eltinit))
>                 == null_pointer_node)))
>       {
>         if (new_ctx.ctor != ctx->ctor)
>           eltinit = new_ctx.ctor;
> -       tree range = build2 (RANGE_EXPR, size_type_node,
> -                            build_int_cst (size_type_node, 1),
> -                            build_int_cst (size_type_node, max - 1));
> -       CONSTRUCTOR_APPEND_ELT (*p, range, unshare_constructor (eltinit));
> +       vec_safe_truncate (*p, 0);
> +       if (max > 1)
> +         idx = build2 (RANGE_EXPR, size_type_node,
> +                       build_int_cst (size_type_node, 0),
> +                       build_int_cst (size_type_node, max - 1));
> +       CONSTRUCTOR_APPEND_ELT (*p, idx, eltinit);
>         break;
>       }
>        else if (i == 0)
>       vec_safe_reserve (*p, max);
> +
> +      if (new_ctx.ctor != ctx->ctor)
> +     {
> +       /* We appended this element above; update the value.  */
> +       gcc_assert ((*p)->last().index == idx);
> +       (*p)->last().value = eltinit;
> +     }
> +      else
> +     CONSTRUCTOR_APPEND_ELT (*p, idx, eltinit);
>      }
>  
>    if (!*non_constant_p)
> 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..a5ce3f7be08
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-array27.C
> @@ -0,0 +1,18 @@
> +// Verify that default initialization an array of aggregates within an 
> aggregate
> +// does not scale exponentially with the number of dimensions.
> +
> +// { dg-do compile { target c++11 } }
> +// Pass -fsyntax-only to stress only the performance of the frontend.
> +// { dg-additional-options "-fsyntax-only" }
> +
> +struct A
> +{
> +  int a = 42;
> +};
> +
> +struct B
> +{
> +  A 
> b[2][2][2][2][2][2][2][2][2][2][2][2][2][2][2][2][2][2][2][2][2][2][2][2];
> +};
> +
> +constexpr B c;
> -- 
> 2.28.0.89.g85b4e0a6dc
> 
> 

Reply via email to