Message ID | ZfNro5HtgOJTEE4R@redhat.com |
---|---|
State | New |
Headers | show |
Series | [v3] c++: ICE with temporary of class type in array DMI [PR109966] | expand |
On Thu, Mar 14, 2024 at 05:26:59PM -0400, Marek Polacek wrote: > @@ -1441,11 +1406,13 @@ static tree > replace_placeholders_for_class_temp_r (tree *tp, int *, void *data) > { > tree t = *tp; > - tree full_expr = *static_cast<tree *>(data); > + auto pset = static_cast<hash_set<tree> *>(data); > > /* We're looking for a TARGET_EXPR nested in the whole expression. */ > if (TREE_CODE (t) == TARGET_EXPR > - && !potential_prvalue_result_of (t, full_expr)) > + /* That serves as temporary materialization, not an initializer. */ > + && !TARGET_EXPR_ELIDING_P (t) > + && !pset->add (t)) > { > tree init = TARGET_EXPR_INITIAL (t); > while (TREE_CODE (init) == COMPOUND_EXPR) > @@ -1460,6 +1427,16 @@ replace_placeholders_for_class_temp_r (tree *tp, int *, void *data) > gcc_checking_assert (!find_placeholders (init)); > } > } > + /* TARGET_EXPRs initializing function arguments are not marked as eliding, > + even though gimplify_arg drops them on the floor. Don't go replacing > + placeholders in them. */ > + else if (TREE_CODE (t) == CALL_EXPR || TREE_CODE (t) == AGGR_INIT_EXPR) > + for (int i = 0; i < call_expr_nargs (t); ++i) > + { > + tree arg = get_nth_callarg (t, i); > + if (TREE_CODE (arg) == TARGET_EXPR) I just realized this could also check !TARGET_EXPR_ELIDING_P; there's no point to adding an eliding TARGET_EXPR into the pset. > + pset->add (arg); > + } Marek
On 3/14/24 17:26, Marek Polacek wrote: > > In the following patch, I'm taking a different tack. I believe > we ought to use TARGET_EXPR_ELIDING_P. The gimplify_arg bit I'm > talking about below is this: > > /* Also strip a TARGET_EXPR that would force an extra copy. */ > if (TREE_CODE (*arg_p) == TARGET_EXPR) > { > tree init = TARGET_EXPR_INITIAL (*arg_p); > if (init > && !VOID_TYPE_P (TREE_TYPE (init))) > *arg_p = init; > } > > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk/13? > > -- >8 -- > This ICE started with the fairly complicated r13-765. We crash in > gimplify_var_or_parm_decl because a stray VAR_DECL leaked there. > The problem is ultimately that potential_prvalue_result_of wasn't > correctly handling arrays and replace_placeholders_for_class_temp_r > replaced a PLACEHOLDER_EXPR in a TARGET_EXPR which is used in the > context of copy elision. If I have > > M m[2] = { M{""}, M{""} }; > > then we don't invoke the M(const M&) copy-ctor. > > One part of the fix is to use TARGET_EXPR_ELIDING_P rather than > potential_prvalue_result_of. That unfortunately doesn't handle the > case like > > struct N { N(M); }; > N arr[2] = { M{""}, M{""} }; > > because TARGET_EXPRs that initialize a function argument are not > marked TARGET_EXPR_ELIDING_P even though gimplify_arg drops such > TARGET_EXPRs on the floor. We can use a pset to avoid replacing > placeholders in them. > > I made an attempt to use set_target_expr_eliding in > convert_for_arg_passing but that regressed constexpr-diag1.C, and does > not seem like a prudent change in stage 4 anyway. I tried the same thing to see what you mean, and that doesn't look like a regression to me, just a different (and more accurate) diagnostic. But you're right that this patch is safer, and the other approach can wait for stage 1. Will you queue that up? In the mean time, this patch is OK. > I just realized this could also check !TARGET_EXPR_ELIDING_P; there's no point > to adding an eliding TARGET_EXPR into the pset. ...with this change. Jason
On Fri, Apr 12, 2024 at 04:15:45PM -0400, Jason Merrill wrote: > On 3/14/24 17:26, Marek Polacek wrote: > > > > In the following patch, I'm taking a different tack. I believe > > we ought to use TARGET_EXPR_ELIDING_P. The gimplify_arg bit I'm > > talking about below is this: > > > > /* Also strip a TARGET_EXPR that would force an extra copy. */ > > if (TREE_CODE (*arg_p) == TARGET_EXPR) > > { > > tree init = TARGET_EXPR_INITIAL (*arg_p); > > if (init > > && !VOID_TYPE_P (TREE_TYPE (init))) > > *arg_p = init; > > } > > > > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk/13? > > > > -- >8 -- > > This ICE started with the fairly complicated r13-765. We crash in > > gimplify_var_or_parm_decl because a stray VAR_DECL leaked there. > > The problem is ultimately that potential_prvalue_result_of wasn't > > correctly handling arrays and replace_placeholders_for_class_temp_r > > replaced a PLACEHOLDER_EXPR in a TARGET_EXPR which is used in the > > context of copy elision. If I have > > > > M m[2] = { M{""}, M{""} }; > > > > then we don't invoke the M(const M&) copy-ctor. > > > > One part of the fix is to use TARGET_EXPR_ELIDING_P rather than > > potential_prvalue_result_of. That unfortunately doesn't handle the > > case like > > > > struct N { N(M); }; > > N arr[2] = { M{""}, M{""} }; > > > > because TARGET_EXPRs that initialize a function argument are not > > marked TARGET_EXPR_ELIDING_P even though gimplify_arg drops such > > TARGET_EXPRs on the floor. We can use a pset to avoid replacing > > placeholders in them. > > > > I made an attempt to use set_target_expr_eliding in > > convert_for_arg_passing but that regressed constexpr-diag1.C, and does > > not seem like a prudent change in stage 4 anyway. > > I tried the same thing to see what you mean, and that doesn't look like a > regression to me, just a different (and more accurate) diagnostic. > > But you're right that this patch is safer, and the other approach can wait > for stage 1. Will you queue that up? In the mean time, this patch is OK. Yeah, happy to; I've opened 114707 to remember. > > I just realized this could also check !TARGET_EXPR_ELIDING_P; there's no point > > to adding an eliding TARGET_EXPR into the pset. > > ...with this change. Thanks. Marek
diff --git a/gcc/cp/typeck2.cc b/gcc/cp/typeck2.cc index 31198b2f9f5..4bf3201eedc 100644 --- a/gcc/cp/typeck2.cc +++ b/gcc/cp/typeck2.cc @@ -1399,41 +1399,6 @@ digest_init_flags (tree type, tree init, int flags, tsubst_flags_t complain) return digest_init_r (type, init, 0, flags, complain); } -/* Return true if SUBOB initializes the same object as FULL_EXPR. - For instance: - - A a = A{}; // initializer - A a = (A{}); // initializer - A a = (1, A{}); // initializer - A a = true ? A{} : A{}; // initializer - auto x = A{}.x; // temporary materialization - auto x = foo(A{}); // temporary materialization - - FULL_EXPR is the whole expression, SUBOB is its TARGET_EXPR subobject. */ - -static bool -potential_prvalue_result_of (tree subob, tree full_expr) -{ - if (subob == full_expr) - return true; - else if (TREE_CODE (full_expr) == TARGET_EXPR) - { - tree init = TARGET_EXPR_INITIAL (full_expr); - if (TREE_CODE (init) == COND_EXPR) - return (potential_prvalue_result_of (subob, TREE_OPERAND (init, 1)) - || potential_prvalue_result_of (subob, TREE_OPERAND (init, 2))); - else if (TREE_CODE (init) == COMPOUND_EXPR) - return potential_prvalue_result_of (subob, TREE_OPERAND (init, 1)); - /* ??? I don't know if this can be hit. */ - else if (TREE_CODE (init) == PAREN_EXPR) - { - gcc_checking_assert (false); - return potential_prvalue_result_of (subob, TREE_OPERAND (init, 0)); - } - } - return false; -} - /* Callback to replace PLACEHOLDER_EXPRs in a TARGET_EXPR (which isn't used in the context of guaranteed copy elision). */ @@ -1441,11 +1406,13 @@ static tree replace_placeholders_for_class_temp_r (tree *tp, int *, void *data) { tree t = *tp; - tree full_expr = *static_cast<tree *>(data); + auto pset = static_cast<hash_set<tree> *>(data); /* We're looking for a TARGET_EXPR nested in the whole expression. */ if (TREE_CODE (t) == TARGET_EXPR - && !potential_prvalue_result_of (t, full_expr)) + /* That serves as temporary materialization, not an initializer. */ + && !TARGET_EXPR_ELIDING_P (t) + && !pset->add (t)) { tree init = TARGET_EXPR_INITIAL (t); while (TREE_CODE (init) == COMPOUND_EXPR) @@ -1460,6 +1427,16 @@ replace_placeholders_for_class_temp_r (tree *tp, int *, void *data) gcc_checking_assert (!find_placeholders (init)); } } + /* TARGET_EXPRs initializing function arguments are not marked as eliding, + even though gimplify_arg drops them on the floor. Don't go replacing + placeholders in them. */ + else if (TREE_CODE (t) == CALL_EXPR || TREE_CODE (t) == AGGR_INIT_EXPR) + for (int i = 0; i < call_expr_nargs (t); ++i) + { + tree arg = get_nth_callarg (t, i); + if (TREE_CODE (arg) == TARGET_EXPR) + pset->add (arg); + } return NULL_TREE; } @@ -1507,8 +1484,8 @@ digest_nsdmi_init (tree decl, tree init, tsubst_flags_t complain) temporary materialization does not occur when initializing an object from a prvalue of the same type, therefore we must not replace the placeholder with a temporary object so that it can be elided. */ - cp_walk_tree (&init, replace_placeholders_for_class_temp_r, &init, - nullptr); + hash_set<tree> pset; + cp_walk_tree (&init, replace_placeholders_for_class_temp_r, &pset, nullptr); return init; } diff --git a/gcc/testsuite/g++.dg/cpp1y/nsdmi-aggr20.C b/gcc/testsuite/g++.dg/cpp1y/nsdmi-aggr20.C new file mode 100644 index 00000000000..4796d861e83 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/nsdmi-aggr20.C @@ -0,0 +1,17 @@ +// PR c++/109966 +// { dg-do compile { target c++14 } } + +#define SA(X) static_assert ((X),#X) + +struct A { + int a; + int b = a; +}; + +struct B { + int x = 0; + int y[1]{A{x}.b}; +}; + +constexpr B b = { }; +SA(b.y[0] == 0); diff --git a/gcc/testsuite/g++.dg/cpp1y/nsdmi-aggr21.C b/gcc/testsuite/g++.dg/cpp1y/nsdmi-aggr21.C new file mode 100644 index 00000000000..efec45bc1a8 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/nsdmi-aggr21.C @@ -0,0 +1,59 @@ +// PR c++/109966 +// { dg-do compile { target c++14 } } + +struct k { + k(const char *); +}; +struct M { + k name; + int j = 42; + int i = j; +}; + +M m = M{""}; + +struct S { + M arr1[1]{M{""}}; + M a1[1] = { (M{""}) }; + M a2[1] = { (true, M{""}) }; + M a3[1] = { true ? M{""} : M{""} }; + M arr2[2]{M{""}, M{""}}; + M arr3[3]{M{""}, M{""}, M{""}}; + + M arr1e[1] = {M{""}}; + M arr2e[2] = {M{""}, M{""}}; + M arr3e[3] = {M{""}, M{""}, M{""}}; + + M arr1l[1] = { m }; + M arr2l[2] = { m, m }; + M arr3l[3] = { m, m, m }; + + M m1 = M{""}; + M m2 = m; + M m3{M{""}}; + M m4 = {M{""}}; +} o; + +struct N { + N(M); +}; + +struct Z { + N arr1[1]{ M{""} }; + N arr2[2]{ M{""}, M{""} }; + N arr1e[1] = { M{""} }; + N arr2e[2] = { M{""}, M{""} }; +} z; + +struct Y { + k name; + int j = 42; + int i = j; + operator M(); +}; + +struct W { + M arr1[1]{ Y{""} }; + M arr2[2]{ Y{""}, Y{""} }; + M arr3[3]{ Y{""}, Y{""}, Y{""} }; +} w;