[PATCH] c++: cxx_eval_vec_init after zero initialization [PR96282]

Jason Merrill jason@redhat.com
Mon Aug 3 15:35:07 GMT 2020


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?

We could probably still do the truncation, but clear the 
CONSTRUCTOR_NO_CLEARING flag on the element initializer.

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



More information about the Gcc-patches mailing list