Message ID | YrZXHg4qzuzp3anZ@redhat.com |
---|---|
State | New |
Headers | show |
Series | [v2] c++: fix broken copy elision with nested TARGET_EXPRs [PR105550] | expand |
On 6/24/22 20:30, Marek Polacek wrote: > On Thu, Jun 02, 2022 at 05:08:54PM -0400, Jason Merrill wrote: >> On 5/26/22 11:01, Marek Polacek wrote: >>> In this problem, we are failing to properly perform copy elision with >>> a conditional operator, so this: >>> >>> constexpr A a = true ? A{} : A{}; >>> >>> fails with: >>> >>> error: 'A{((const A*)(&<anonymous>))}' is not a constant expression >>> >>> The whole initializer is >>> >>> TARGET_EXPR <D.2395, 1 ? TARGET_EXPR <D.2393, {.p=(const struct A *) &<PLACEHOLDER_EXPR struct A>}> : TARGET_EXPR <D.2394, {.p=(const struct A *) &<PLACEHOLDER_EXPR struct A>}>> >>> >>> where the outermost TARGET_EXPR is elided, but not the nested ones. >>> Then we end up replacing the PLACEHOLDER_EXPRs with the temporaries the >>> TARGET_EXPRs represent, which is precisely what should *not* happen with >>> copy elision. >>> >>> I've tried the approach of tweaking ctx->object, but I ran into gazillion >>> problems with that. I thought that I would let cxx_eval_constant_expression >>> /TARGET_EXPR create a new object only when ctx->object was null, then >>> adjust setting of ctx->object in places like cxx_bind_parameters_in_call >>> and cxx_eval_component_reference but that failed completely. Sometimes >>> ctx->object has to be reset, sometimes it cannot be reset, 'this' needed >>> special handling, etc. I gave up. >>>> But now that we have potential_prvalue_result_of, a simple but less >>> elegant solution is the following. I thought about setting a flag on >>> a TARGET_EXPR to avoid adding ctx.full_expr, but a new flag would be >>> overkill and using TARGET_EXPR_DIRECT_INIT_P broke things. > > Sorry it's taken me so long to get back to this. > >> This doesn't seem like a general solution; the same issue would also apply >> to ?: of TARGET_EXPR when it's a subexpression rather than the full >> expression, like f(true ? A{} : B{}). > > You're right. > >> Another simple approach, but more general, would be to routinely strip >> TARGET_EXPR from the operands of ?: like we do in various other places in >> constexpr.c. > > How about this, then? > > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk? OK. > -- >8 -- > In this problem, we are failing to properly perform copy elision with > a conditional operator, so this: > > constexpr A a = true ? A{} : A{}; > > fails with: > > error: 'A{((const A*)(&<anonymous>))}' is not a constant expression > > The whole initializer is > > TARGET_EXPR <D.2395, 1 ? TARGET_EXPR <D.2393, {.p=(const struct A *) &<PLACEHOLDER_EXPR struct A>}> : TARGET_EXPR <D.2394, {.p=(const struct A *) &<PLACEHOLDER_EXPR struct A>}>> > > where the outermost TARGET_EXPR is elided, but not the nested ones. > Then we end up replacing the PLACEHOLDER_EXPRs with the temporaries the > TARGET_EXPRs represent, which is precisely what should *not* happen with > copy elision. > > I've tried the approach of tweaking ctx->object, but I ran into gazillion > problems with that. I thought that I would let cxx_eval_constant_expression > /TARGET_EXPR create a new object only when ctx->object was null, then > adjust setting of ctx->object in places like cxx_bind_parameters_in_call > and cxx_eval_component_reference but that failed completely. Sometimes > ctx->object has to be reset, sometimes it cannot be reset, 'this' needed > special handling, etc. I gave up. > > Instead, this patch strips TARGET_EXPRs from the operands of ?: like > we do in various other places in constexpr.c. > > PR c++/105550 > > gcc/cp/ChangeLog: > > * constexpr.cc (cxx_eval_conditional_expression): Strip TARGET_EXPRs. > > gcc/testsuite/ChangeLog: > > * g++.dg/cpp1y/nsdmi-aggr16.C: Remove FIXME. > * g++.dg/cpp1y/nsdmi-aggr17.C: Remove FIXME. > * g++.dg/cpp0x/constexpr-elision1.C: New test. > * g++.dg/cpp1y/constexpr-elision1.C: New test. > --- > gcc/cp/constexpr.cc | 7 +++ > .../g++.dg/cpp0x/constexpr-elision1.C | 16 ++++++ > .../g++.dg/cpp1y/constexpr-elision1.C | 53 +++++++++++++++++++ > gcc/testsuite/g++.dg/cpp1y/nsdmi-aggr16.C | 5 +- > gcc/testsuite/g++.dg/cpp1y/nsdmi-aggr17.C | 5 +- > 5 files changed, 80 insertions(+), 6 deletions(-) > create mode 100644 gcc/testsuite/g++.dg/cpp0x/constexpr-elision1.C > create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-elision1.C > > diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc > index 0dc94d9445d..5f7fc6f8f0c 100644 > --- a/gcc/cp/constexpr.cc > +++ b/gcc/cp/constexpr.cc > @@ -3507,6 +3507,13 @@ cxx_eval_conditional_expression (const constexpr_ctx *ctx, tree t, > val = TREE_OPERAND (t, 1); > if (TREE_CODE (t) == IF_STMT && !val) > val = void_node; > + /* A TARGET_EXPR may be nested inside another TARGET_EXPR, but still > + serve as the initializer for the same object as the outer TARGET_EXPR, > + as in > + A a = true ? A{} : A{}; > + so strip the inner TARGET_EXPR so we don't materialize a temporary. */ > + if (TREE_CODE (val) == TARGET_EXPR) > + val = TARGET_EXPR_INITIAL (val); > return cxx_eval_constant_expression (ctx, val, lval, non_constant_p, > overflow_p, jump_target); > } > diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-elision1.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-elision1.C > new file mode 100644 > index 00000000000..9e7b9ec3405 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-elision1.C > @@ -0,0 +1,16 @@ > +// PR c++/105550 > +// { dg-do compile { target c++11 } } > + > +template <typename, typename> struct pair { > + constexpr pair(int, int) {} > +}; > +template <typename _Tp, typename _Compare> > +pair<const _Tp &, const _Tp &> minmax(const _Tp &__a, const _Tp &__b, > + _Compare) { > + return 0 ? pair<const _Tp &, const _Tp &>(__b, __a) > + : pair<const _Tp &, const _Tp &>(__a, __b); > +} > +typedef int value_type; > +typedef int compare_type; > +template pair<const value_type &, const value_type &> > +minmax(const value_type &, const value_type &, compare_type); > diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-elision1.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-elision1.C > new file mode 100644 > index 00000000000..b225ae29cde > --- /dev/null > +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-elision1.C > @@ -0,0 +1,53 @@ > +// PR c++/105550 > +// { dg-do compile { target c++14 } } > + > +struct A { > + const A *p = this; > +}; > + > +struct B { > + const B *p = this; > + constexpr operator A() const { return {}; } > +}; > + > +constexpr A > +bar (A) > +{ > + return {}; > +} > + > +constexpr A baz() { return {}; } > + > +struct E { > + A a1 = true ? A{} : A{}; > + A a2 = true ? A{} : B{}; > + A a3 = false ? A{} : B{}; > + A a4 = false ? B{} : B{}; > + A a5 = A{}; > + A a6 = B{}; > + A a7 = false ? B{} : (true ? A{} : A{}); > + A a8 = false ? (true ? A{} : B{}) : (true ? A{} : A{}); > + A a9 = (A{}); > + A a10 = (true, A{}); > + A a11 = bar (A{}); > + A a12 = baz (); > + A a13 = (A{}, A{}); > +}; > + > +constexpr E e{}; > + > +constexpr A a1 = true ? A{} : A{}; > +constexpr A a2 = true ? A{} : B{}; > +constexpr A a3 = false ? A{} : B{}; > +constexpr A a4 = false ? B{} : B{}; > +constexpr A a5 = A{}; > +constexpr A a6 = B{}; > +constexpr A a7 = false ? B{} : (true ? A{} : A{}); > +constexpr A a8 = false ? (true ? A{} : B{}) : (true ? A{} : A{}); > +constexpr A a9 = (A{}); > +constexpr A a10 = (true, A{}); > +constexpr A a11 = bar (A{}); > +//static_assert(a10.p == &a10, ""); // bug, 105619 > +constexpr A a12 = baz (); > +//static_assert(a11.p == &a11, ""); // bug, 105619 > +constexpr A a13 = (A{}, A{}); > diff --git a/gcc/testsuite/g++.dg/cpp1y/nsdmi-aggr16.C b/gcc/testsuite/g++.dg/cpp1y/nsdmi-aggr16.C > index dc6492c1b0b..5e66bdd2370 100644 > --- a/gcc/testsuite/g++.dg/cpp1y/nsdmi-aggr16.C > +++ b/gcc/testsuite/g++.dg/cpp1y/nsdmi-aggr16.C > @@ -40,9 +40,8 @@ struct E { > A d = true ? (false ? A{} : A{}) : (false ? A{} : A{}); > }; > > -// FIXME: When fixing this, also fix nsdmi-aggr17.C. > -constexpr E e; // { dg-bogus "" "PR105550" { xfail *-*-* } } > -SA (e.a.p == &e.a); // { dg-bogus "" "PR105550" { xfail *-*-* } } > +constexpr E e; > +SA (e.a.p == &e.a); > > E e1 = { }; > > diff --git a/gcc/testsuite/g++.dg/cpp1y/nsdmi-aggr17.C b/gcc/testsuite/g++.dg/cpp1y/nsdmi-aggr17.C > index fc27a2cdac7..ca9637b37eb 100644 > --- a/gcc/testsuite/g++.dg/cpp1y/nsdmi-aggr17.C > +++ b/gcc/testsuite/g++.dg/cpp1y/nsdmi-aggr17.C > @@ -56,9 +56,8 @@ struct F { > A a = true ? A{x} : A{x}; > }; > > -// FIXME: Doesn't work due to PR105550. > -//constexpr F f; > -//SA (f.a.p == &f.a); > +constexpr F f; > +SA (f.a.p == &f.a); > SA (e.x == 42); > F f2 = { }; > F f3 = { 42 }; > > base-commit: 113844d68e94f4e9c0e946db351ba7d3d4a1335a
diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc index 0dc94d9445d..5f7fc6f8f0c 100644 --- a/gcc/cp/constexpr.cc +++ b/gcc/cp/constexpr.cc @@ -3507,6 +3507,13 @@ cxx_eval_conditional_expression (const constexpr_ctx *ctx, tree t, val = TREE_OPERAND (t, 1); if (TREE_CODE (t) == IF_STMT && !val) val = void_node; + /* A TARGET_EXPR may be nested inside another TARGET_EXPR, but still + serve as the initializer for the same object as the outer TARGET_EXPR, + as in + A a = true ? A{} : A{}; + so strip the inner TARGET_EXPR so we don't materialize a temporary. */ + if (TREE_CODE (val) == TARGET_EXPR) + val = TARGET_EXPR_INITIAL (val); return cxx_eval_constant_expression (ctx, val, lval, non_constant_p, overflow_p, jump_target); } diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-elision1.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-elision1.C new file mode 100644 index 00000000000..9e7b9ec3405 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-elision1.C @@ -0,0 +1,16 @@ +// PR c++/105550 +// { dg-do compile { target c++11 } } + +template <typename, typename> struct pair { + constexpr pair(int, int) {} +}; +template <typename _Tp, typename _Compare> +pair<const _Tp &, const _Tp &> minmax(const _Tp &__a, const _Tp &__b, + _Compare) { + return 0 ? pair<const _Tp &, const _Tp &>(__b, __a) + : pair<const _Tp &, const _Tp &>(__a, __b); +} +typedef int value_type; +typedef int compare_type; +template pair<const value_type &, const value_type &> +minmax(const value_type &, const value_type &, compare_type); diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-elision1.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-elision1.C new file mode 100644 index 00000000000..b225ae29cde --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-elision1.C @@ -0,0 +1,53 @@ +// PR c++/105550 +// { dg-do compile { target c++14 } } + +struct A { + const A *p = this; +}; + +struct B { + const B *p = this; + constexpr operator A() const { return {}; } +}; + +constexpr A +bar (A) +{ + return {}; +} + +constexpr A baz() { return {}; } + +struct E { + A a1 = true ? A{} : A{}; + A a2 = true ? A{} : B{}; + A a3 = false ? A{} : B{}; + A a4 = false ? B{} : B{}; + A a5 = A{}; + A a6 = B{}; + A a7 = false ? B{} : (true ? A{} : A{}); + A a8 = false ? (true ? A{} : B{}) : (true ? A{} : A{}); + A a9 = (A{}); + A a10 = (true, A{}); + A a11 = bar (A{}); + A a12 = baz (); + A a13 = (A{}, A{}); +}; + +constexpr E e{}; + +constexpr A a1 = true ? A{} : A{}; +constexpr A a2 = true ? A{} : B{}; +constexpr A a3 = false ? A{} : B{}; +constexpr A a4 = false ? B{} : B{}; +constexpr A a5 = A{}; +constexpr A a6 = B{}; +constexpr A a7 = false ? B{} : (true ? A{} : A{}); +constexpr A a8 = false ? (true ? A{} : B{}) : (true ? A{} : A{}); +constexpr A a9 = (A{}); +constexpr A a10 = (true, A{}); +constexpr A a11 = bar (A{}); +//static_assert(a10.p == &a10, ""); // bug, 105619 +constexpr A a12 = baz (); +//static_assert(a11.p == &a11, ""); // bug, 105619 +constexpr A a13 = (A{}, A{}); diff --git a/gcc/testsuite/g++.dg/cpp1y/nsdmi-aggr16.C b/gcc/testsuite/g++.dg/cpp1y/nsdmi-aggr16.C index dc6492c1b0b..5e66bdd2370 100644 --- a/gcc/testsuite/g++.dg/cpp1y/nsdmi-aggr16.C +++ b/gcc/testsuite/g++.dg/cpp1y/nsdmi-aggr16.C @@ -40,9 +40,8 @@ struct E { A d = true ? (false ? A{} : A{}) : (false ? A{} : A{}); }; -// FIXME: When fixing this, also fix nsdmi-aggr17.C. -constexpr E e; // { dg-bogus "" "PR105550" { xfail *-*-* } } -SA (e.a.p == &e.a); // { dg-bogus "" "PR105550" { xfail *-*-* } } +constexpr E e; +SA (e.a.p == &e.a); E e1 = { }; diff --git a/gcc/testsuite/g++.dg/cpp1y/nsdmi-aggr17.C b/gcc/testsuite/g++.dg/cpp1y/nsdmi-aggr17.C index fc27a2cdac7..ca9637b37eb 100644 --- a/gcc/testsuite/g++.dg/cpp1y/nsdmi-aggr17.C +++ b/gcc/testsuite/g++.dg/cpp1y/nsdmi-aggr17.C @@ -56,9 +56,8 @@ struct F { A a = true ? A{x} : A{x}; }; -// FIXME: Doesn't work due to PR105550. -//constexpr F f; -//SA (f.a.p == &f.a); +constexpr F f; +SA (f.a.p == &f.a); SA (e.x == 42); F f2 = { }; F f3 = { 42 };