Message ID | ZqlUMpbuMc50YXYB@redhat.com |
---|---|
State | New |
Headers | show |
Series | [v2] c++: array new with value-initialization, again [PR115645] | expand |
On 7/30/24 4:59 PM, Marek Polacek wrote: > On Mon, Jul 29, 2024 at 06:34:40PM -0400, Jason Merrill wrote: >> On 7/29/24 4:18 PM, Marek Polacek wrote: >>> On Tue, Jul 23, 2024 at 05:18:52PM -0400, Jason Merrill wrote: >>>> On 7/17/24 5:33 PM, Marek Polacek wrote: >>>>> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk? >>>> >>>> Hmm, I thought I had replied to this already. >>>> >>>>> -- >8 -- >>>>> Unfortunately, my r15-1946 fix broke the attached testcase. In it, >>>>> we no longer go into the >>>>> /* P1009: Array size deduction in new-expressions. */ >>>>> block, and instead generate an operator new [] call along with a loop >>>>> in build_new_1, which we can't constexpr-evaluate. So this patch >>>>> reverts r15-1946 and uses CONSTRUCTOR_IS_PAREN_INIT to distinguish >>>>> between () and {} to fix the original testcase (anew7.C). >>>>> >>>>> PR c++/115645 >>>>> >>>>> gcc/cp/ChangeLog: >>>>> >>>>> * call.cc (convert_like_internal) <case ck_user>: Don't report errors >>>>> about calling an explicit constructor when the constructor was marked >>>>> CONSTRUCTOR_IS_PAREN_INIT. >>>>> * init.cc (build_new): Revert r15-1946. Set CONSTRUCTOR_IS_PAREN_INIT. >>>>> (build_vec_init): Maybe set CONSTRUCTOR_IS_PAREN_INIT. >>>>> >>>>> gcc/testsuite/ChangeLog: >>>>> >>>>> * g++.dg/cpp2a/constexpr-new23.C: New test. >>>>> --- >>>>> gcc/cp/call.cc | 2 ++ >>>>> gcc/cp/init.cc | 17 ++++----- >>>>> gcc/testsuite/g++.dg/cpp2a/constexpr-new23.C | 38 ++++++++++++++++++++ >>>>> 3 files changed, 49 insertions(+), 8 deletions(-) >>>>> create mode 100644 gcc/testsuite/g++.dg/cpp2a/constexpr-new23.C >>>>> >>>>> diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc >>>>> index a5d3426b70c..2d94d5e0d07 100644 >>>>> --- a/gcc/cp/call.cc >>>>> +++ b/gcc/cp/call.cc >>>>> @@ -8592,6 +8592,8 @@ convert_like_internal (conversion *convs, tree expr, tree fn, int argnum, >>>>> && BRACE_ENCLOSED_INITIALIZER_P (expr) >>>>> /* Unless this is for direct-list-initialization. */ >>>>> && (!CONSTRUCTOR_IS_DIRECT_INIT (expr) || convs->need_temporary_p) >>>>> + /* And it wasn't a ()-init. */ >>>>> + && !CONSTRUCTOR_IS_PAREN_INIT (expr) >>>>> /* And in C++98 a default constructor can't be explicit. */ >>>>> && cxx_dialect >= cxx11) >>>>> { >>>>> diff --git a/gcc/cp/init.cc b/gcc/cp/init.cc >>>>> index e9561c146d7..4138a6077dd 100644 >>>>> --- a/gcc/cp/init.cc >>>>> +++ b/gcc/cp/init.cc >>>>> @@ -4005,20 +4005,17 @@ build_new (location_t loc, vec<tree, va_gc> **placement, tree type, >>>>> /* P1009: Array size deduction in new-expressions. */ >>>>> const bool array_p = TREE_CODE (type) == ARRAY_TYPE; >>>>> if (*init >>>>> - /* If the array didn't specify its bound, we have to deduce it. */ >>>>> - && ((array_p && !TYPE_DOMAIN (type)) >>>>> - /* For C++20 array with parenthesized-init, we have to process >>>>> - the parenthesized-list. But don't do it for (), which is >>>>> - value-initialization, and INIT should stay empty. */ >>>>> - || (cxx_dialect >= cxx20 >>>>> - && (array_p || nelts) >>>>> - && !(*init)->is_empty ()))) >>>>> + /* If ARRAY_P, we have to deduce the array bound. For C++20 paren-init, >>>>> + we have to process the parenthesized-list. But don't do it for (), >>>>> + which is value-initialization, and INIT should stay empty. */ >>>>> + && (array_p || (cxx_dialect >= cxx20 && nelts && !(*init)->is_empty ()))) >>>>> { >>>>> /* This means we have 'new T[]()'. */ >>>>> if ((*init)->is_empty ()) >>>>> { >>>>> tree ctor = build_constructor (init_list_type_node, NULL); >>>>> CONSTRUCTOR_IS_DIRECT_INIT (ctor) = true; >>>>> + CONSTRUCTOR_IS_PAREN_INIT (ctor) = true; >>>>> vec_safe_push (*init, ctor); >>>>> } >>>>> tree &elt = (**init)[0]; >>>>> @@ -4735,6 +4732,9 @@ build_vec_init (tree base, tree maxindex, tree init, >>>>> bool do_static_init = (DECL_P (obase) && TREE_STATIC (obase)); >>>>> bool empty_list = false; >>>>> + const bool paren_init_p = (init >>>>> + && TREE_CODE (init) == CONSTRUCTOR >>>>> + && CONSTRUCTOR_IS_PAREN_INIT (init)); >>>> >>>> I think rather than recognizing paren-init in general, we want to recognize >>>> () specifically, and set explicit_value_init_p... >>>> >>>>> if (init && BRACE_ENCLOSED_INITIALIZER_P (init) >>>>> && CONSTRUCTOR_NELTS (init) == 0) >>>>> /* Skip over the handling of non-empty init lists. */ >>>>> @@ -4927,6 +4927,7 @@ build_vec_init (tree base, tree maxindex, tree init, >>>>> || TREE_CODE (type) == ARRAY_TYPE)) >>>>> { >>>>> init = build_constructor (init_list_type_node, NULL); >>>>> + CONSTRUCTOR_IS_PAREN_INIT (init) = paren_init_p; >>>>> } >>>>> else >>>>> { >>>> >>>> ...by taking the else branch here. Then we shouldn't need the convert_like >>>> change. >>> >>> Unfortunately that then breaks Jon's test (constexpr-new23.C which this >>> patch is adding). The problem is that if we do *not* create a new {}, and >>> do explicit_value_init_p, we end up with >>> >>> int[1] * D.2643; >>> <<< Unknown tree: expr_stmt >>> (void) (D.2643 = (int[1] *) D.2642) >>>; >>> int[1] * D.2644; >>> <<< Unknown tree: expr_stmt >>> (void) (D.2644 = D.2643) >>>; >>> TARGET_EXPR <D.2645, 0>; >>> <<< Unknown tree: for_stmt >>> D.2645 > -1 >>> <<cleanup_point <<< Unknown tree: expr_stmt >>> *(int[1] &) int * D.2646; >>> <<< Unknown tree: expr_stmt >>> (void) (D.2646 = (int *) D.2644) >>>; >>> int * D.2647; >>> <<< Unknown tree: expr_stmt >>> (void) (D.2647 = D.2646) >>>; >>> TARGET_EXPR <D.2648, 0>; >>> <<< Unknown tree: for_stmt >>> >>> D.2648 > -1 >>> >>> <<cleanup_point <<< Unknown tree: expr_stmt >>> *D.2647 = 0, --D.2648 >>>>>; >>> <<< Unknown tree: expr_stmt >>> (void) ++D.2647 >>>; >>> >>>; >>> D.2646, --D.2645 >>>>>; >>> <<< Unknown tree: expr_stmt >>> (void) ++D.2644 >>>; >>> >>>; >>> D.2643 >>> >>> rather than: >>> >>> int[1] * D.2643; >>> <<< Unknown tree: expr_stmt >>> (void) (D.2643 = (int[1] *) D.2642) >>>; >>> int[1] * D.2644; >>> <<< Unknown tree: expr_stmt >>> (void) (D.2644 = D.2643) >>>; >>> TARGET_EXPR <D.2645, 0>; >>> <<< Unknown tree: for_stmt >>> D.2645 > -1 >>> <<cleanup_point <<< Unknown tree: expr_stmt >>> *D.2644 = {}, --D.2645 >>>>>; >>> <<< Unknown tree: expr_stmt >>> (void) ++D.2644 >>>; >>> >>>; >>> D.2643 >>> >>> In the former, the "*D.2647 = 0" assignment is what breaks constexpr, >>> which then complains: >>> >>> constexpr-new23.C:16:16: error: accessing 'test_array()::U::arr' member instead of initialized 'test_array()::U::x' member in constant expression >>> 16 | return ::new((void*)p) T[1](); >>> | ^~~~~~~~~~~~~~~~~~~~~~ >>> constexpr-new23.C:31:9: note: initializing 'test_array()::U::arr' requires a member access expression as the left operand of the assignment >>> 31 | int arr[4]; >>> >>> >>> If there is no bug in constexpr, then it looks like we need to >>> initialize using a {} rather than a loop that assigns 0 to each >>> element. >> >> Ah, thanks. >> >> It looks like the first bug is that build_vec_init wrongly leaves try_const >> false for this case (without your patch) because int doesn't have a >> constexpr default constructor, failing to consider that value-initialization >> of scalars is constexpr. > > Oh wow, I should have noticed that. > >> Then, once we're into the looping initialization, we aren't expressing it in >> a way that will satisfy the strict checking in constexpr evaluation; it >> needs to initialize the array, not just its elements. >> >> I expect we could fix that with something like >> >>> /* Start array lifetime before accessing elements. */ >>> if (TREE_CODE (atype) == ARRAY_TYPE) >>> { >>> elt_init = build_constructor (atype, nullptr); >>> CONSTRUCTOR_NO_CLEARING (elt_init) = true; >>> for_stmt = build2 (INIT_EXPR, atype, obase, elt_init); >>> finish_expr_stmt (for_stmt); >>> } >> >> but if we're only concerned about constexpr, fixing the first bug ought to >> be enough; in constant evaluation if we don't get a constant initializer >> we're failing anyway. > > This patch fixes the first bug. Thanks! > > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk? > > -- >8 -- > Unfortunately, my r15-1946 fix broke the attached testcase; the > constexpr evaluation reported an error about not being able to > evaluate the code emitted by build_vec_init. Jason figured out > it's because we were wrongly setting try_const to false, where > in fact it should have been true. Value-initialization of scalars > is constexpr, so we should check that alongside of > type_has_constexpr_default_constructor. > > PR c++/115645 > > gcc/cp/ChangeLog: > > * init.cc (build_vec_init): When initializing a scalar type, try to > create a constant initializer. > > gcc/testsuite/ChangeLog: > > * g++.dg/cpp2a/constexpr-new23.C: New test. > --- > gcc/cp/init.cc | 4 ++- > gcc/testsuite/g++.dg/cpp2a/constexpr-new23.C | 38 ++++++++++++++++++++ > 2 files changed, 41 insertions(+), 1 deletion(-) > create mode 100644 gcc/testsuite/g++.dg/cpp2a/constexpr-new23.C > > diff --git a/gcc/cp/init.cc b/gcc/cp/init.cc > index e9561c146d7..a3a97e2c128 100644 > --- a/gcc/cp/init.cc > +++ b/gcc/cp/init.cc > @@ -4724,7 +4724,9 @@ build_vec_init (tree base, tree maxindex, tree init, > && TREE_CONSTANT (maxindex) > && (init ? TREE_CODE (init) == CONSTRUCTOR > : (type_has_constexpr_default_constructor > - (inner_elt_type))) > + (inner_elt_type) > + /* Value-initialization of scalars is constexpr. */ > + || SCALAR_TYPE_P (inner_elt_type))) I think we also want to check explicit_value_init_p, since default-init of scalars is not constexpr. I don't think we'd actually get here in that case, as callers like build_new_1 avoid calling build_vec_init at all, but I'd still like to be correct. Jason
diff --git a/gcc/cp/init.cc b/gcc/cp/init.cc index e9561c146d7..a3a97e2c128 100644 --- a/gcc/cp/init.cc +++ b/gcc/cp/init.cc @@ -4724,7 +4724,9 @@ build_vec_init (tree base, tree maxindex, tree init, && TREE_CONSTANT (maxindex) && (init ? TREE_CODE (init) == CONSTRUCTOR : (type_has_constexpr_default_constructor - (inner_elt_type))) + (inner_elt_type) + /* Value-initialization of scalars is constexpr. */ + || SCALAR_TYPE_P (inner_elt_type))) && (literal_type_p (inner_elt_type) || TYPE_HAS_CONSTEXPR_CTOR (inner_elt_type))); vec<constructor_elt, va_gc> *const_vec = NULL; diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-new23.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-new23.C new file mode 100644 index 00000000000..1abbef18fae --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-new23.C @@ -0,0 +1,38 @@ +// PR c++/115645 +// { dg-do compile { target c++20 } } + +using size_t = decltype(sizeof(0)); + +void* operator new(size_t, void* p) { return p; } +void* operator new[](size_t, void* p) { return p; } + +#define VERIFY(C) if (!(C)) throw + +namespace std { + template<typename T> + constexpr T* construct_at(T* p) + { + if constexpr (__is_array(T)) + return ::new((void*)p) T[1](); + else + return ::new((void*)p) T(); + } +} + +constexpr void +test_array() +{ + int arr[1] { 99 }; + std::construct_at(&arr); + VERIFY( arr[0] == 0 ); + + union U { + long long x = -1; + int arr[4]; + } u; + + auto p = std::construct_at(&u.arr); + VERIFY( (*p)[0] == 0 ); +} + +static_assert( [] { test_array(); return true; }() );