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