Hi Jason, gentle ping for this one (sorry I forgot to ping earlier and then WG21 …)
> On 31 Oct 2024, at 08:40, Iain Sandoe <iains....@gmail.com> wrote: > > This version tested on x86_64-darwin,linux, powerpc64-linux, on folly > and by Sam on wider codebases, > >>>> Why don't you need a variable to preserve o across suspensions if it's a >>>> call returning lvalue reference? >>> We always need a space for the awaiter, unless it is already a >>> variable/parameter (or part of one). >>>> I suspect that the simple case is not lvalue_p, but !TREE_SIDE_EFFECTS. >>> That is likely where I’m going wrong - we must not generate a variable for >>> any case that already has one (or a parm), but we must for any case that is >>> a temporary. >>> So, I should adjust the logic to use !TREE_SIDE_EFFECTS. > >> Or perhaps DECL_P. The difference would be for compound lvalues like *p or >> a[n]; if the value of p or a or n could change across suspension, the same >> side-effect-free lvalue expression could refer to a different object. > > Right, part of the code that was elided catered for the compound values by > making a reference to the original entity and placing that in the frame. We > restore that behaviour here. > > Note that there is no point in making a reference to an xvalue (we'd only > have to save the expiring value in the frame anyway), so we just go ahead > and build that var directly. There is one small additional optimisation, > in that building a reference to a non-pointer component ref wastes frame > space if the underlying entity is a variable or parameter. > > Thanks for the help in working through the different cases here, OK for > trunk now? > thanks > Iain. > > --- 8< --- > > Awaiters always need to have a coroutine state frame copy since > they persist across potential supensions. It simplifies the later > analysis considerably to assign these early which we do when > building co_await expressions. > > The cleanups in r15-3146-g47dbd69b1, unfortunately elided some of > processing used to cater for cases where the var created from an > xvalue, or is a pointer/reference type. > > Corrected thus. > > PR c++/116506 > PR c++/116880 > > gcc/cp/ChangeLog: > > * coroutines.cc (build_co_await): Ensure that xvalues are > materialised. Handle references/pointer values in awaiter > access expressions. > > gcc/testsuite/ChangeLog: > > * g++.dg/coroutines/pr116506.C: New test. > * g++.dg/coroutines/pr116880.C: New test. > > Signed-off-by: Iain Sandoe <i...@sandoe.co.uk> > --- > gcc/cp/coroutines.cc | 82 +++++++++++++++++----- > gcc/testsuite/g++.dg/coroutines/pr116506.C | 53 ++++++++++++++ > gcc/testsuite/g++.dg/coroutines/pr116880.C | 36 ++++++++++ > 3 files changed, 154 insertions(+), 17 deletions(-) > create mode 100644 gcc/testsuite/g++.dg/coroutines/pr116506.C > create mode 100644 gcc/testsuite/g++.dg/coroutines/pr116880.C > > diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc > index ba326bcd627..dde8ba4e614 100644 > --- a/gcc/cp/coroutines.cc > +++ b/gcc/cp/coroutines.cc > @@ -1072,6 +1072,30 @@ build_template_co_await_expr (location_t kw, tree > type, tree expr, tree kind) > return aw_expr; > } > > +/* For a component ref that is not a pointer type, decide if we can use > + this directly. */ > +static bool > +usable_component_ref (tree comp_ref) > +{ > + if (TREE_CODE (comp_ref) != COMPONENT_REF > + || TREE_SIDE_EFFECTS (comp_ref)) > + return false; > + > + while (TREE_CODE (comp_ref) == COMPONENT_REF) > + { > + comp_ref = TREE_OPERAND (comp_ref, 0); > + STRIP_NOPS (comp_ref); > + /* x-> */ > + if (INDIRECT_REF_P (comp_ref)) > + return false; > + /* operator-> */ > + if (TREE_CODE (comp_ref) == CALL_EXPR) > + return false; > + STRIP_NOPS (comp_ref); > + } > + gcc_checking_assert (VAR_P (comp_ref) || TREE_CODE (comp_ref) == > PARM_DECL); > + return true; > +} > > /* This performs [expr.await] bullet 3.3 and validates the interface > obtained. > It is also used to build the initial and final suspend points. > @@ -1134,13 +1158,12 @@ build_co_await (location_t loc, tree a, > suspend_point_kind suspend_kind, > if (o_type && !VOID_TYPE_P (o_type)) > o_type = complete_type_or_else (o_type, o); > > - if (!o_type) > + if (!o_type || o_type == error_mark_node) > return error_mark_node; > > if (TREE_CODE (o_type) != RECORD_TYPE) > { > - error_at (loc, "awaitable type %qT is not a structure", > - o_type); > + error_at (loc, "awaitable type %qT is not a structure", o_type); > return error_mark_node; > } > > @@ -1166,20 +1189,47 @@ build_co_await (location_t loc, tree a, > suspend_point_kind suspend_kind, > if (!glvalue_p (o)) > o = get_target_expr (o, tf_warning_or_error); > > - tree e_proxy = o; > - if (glvalue_p (o)) > + /* We know that we need a coroutine state frame variable for the awaiter, > + since it must persist across suspension; so where one is needed, build > + it now. */ > + tree e_proxy = STRIP_NOPS (o); > + if (INDIRECT_REF_P (e_proxy)) > + e_proxy = TREE_OPERAND (e_proxy, 0); > + o_type = TREE_TYPE (e_proxy); > + bool is_ptr = TYPE_PTR_P (o_type); > + if (!is_ptr > + && (TREE_CODE (e_proxy) == PARM_DECL > + || usable_component_ref (e_proxy) > + || (VAR_P (e_proxy) && !is_local_temp (e_proxy)))) > o = NULL_TREE; /* Use the existing entity. */ > - else /* We need to materialise it. */ > + else /* We need a non-temp var. */ > { > - e_proxy = get_awaitable_var (suspend_kind, o_type); > - o = cp_build_init_expr (loc, e_proxy, o); > - e_proxy = convert_from_reference (e_proxy); > + tree p_type = o_type; > + tree o_a = o; > + if (lvalue_p (o) && !TREE_SIDE_EFFECTS (o)) > + { > + if (is_ptr) > + p_type = TREE_TYPE (p_type); > + p_type = cp_build_reference_type (p_type, false); > + o_a = build_address (o); > + } > + if (INDIRECT_REF_P (o_a)) > + o_a = TREE_OPERAND (o_a, 0); > + e_proxy = get_awaitable_var (suspend_kind, p_type); > + o = cp_build_init_expr (loc, e_proxy, o_a); > } > > + /* Build an expression for the object that will be used to call the > awaitable > + methods. */ > + tree e_object = convert_from_reference (e_proxy); > + if (TYPE_PTR_P (TREE_TYPE (e_object))) > + e_object = cp_build_indirect_ref (input_location, e_object, > RO_UNARY_STAR, > + tf_warning_or_error); > + > /* I suppose we could check that this is contextually convertible to bool. > */ > tree awrd_func = NULL_TREE; > tree awrd_call > - = build_new_method_call (e_proxy, awrd_meth, NULL, NULL_TREE, > LOOKUP_NORMAL, > + = build_new_method_call (e_object, awrd_meth, NULL, NULL_TREE, > LOOKUP_NORMAL, > &awrd_func, tf_warning_or_error); > > if (!awrd_func || !awrd_call || awrd_call == error_mark_node) > @@ -1193,7 +1243,7 @@ build_co_await (location_t loc, tree a, > suspend_point_kind suspend_kind, > tree h_proxy = get_coroutine_self_handle_proxy (current_function_decl); > vec<tree, va_gc> *args = make_tree_vector_single (h_proxy); > tree awsp_call > - = build_new_method_call (e_proxy, awsp_meth, &args, NULL_TREE, > + = build_new_method_call (e_object, awsp_meth, &args, NULL_TREE, > LOOKUP_NORMAL, &awsp_func, tf_warning_or_error); > > release_tree_vector (args); > @@ -1225,7 +1275,7 @@ build_co_await (location_t loc, tree a, > suspend_point_kind suspend_kind, > /* Finally, the type of e.await_resume() is the co_await's type. */ > tree awrs_func = NULL_TREE; > tree awrs_call > - = build_new_method_call (e_proxy, awrs_meth, NULL, NULL_TREE, > LOOKUP_NORMAL, > + = build_new_method_call (e_object, awrs_meth, NULL, NULL_TREE, > LOOKUP_NORMAL, > &awrs_func, tf_warning_or_error); > > if (!awrs_func || !awrs_call || awrs_call == error_mark_node) > @@ -1239,7 +1289,7 @@ build_co_await (location_t loc, tree a, > suspend_point_kind suspend_kind, > return error_mark_node; > if (coro_diagnose_throwing_fn (awrs_func)) > return error_mark_node; > - if (tree dummy = cxx_maybe_build_cleanup (e_proxy, tf_none)) > + if (tree dummy = cxx_maybe_build_cleanup (e_object, tf_none)) > { > if (CONVERT_EXPR_P (dummy)) > dummy = TREE_OPERAND (dummy, 0); > @@ -1250,7 +1300,8 @@ build_co_await (location_t loc, tree a, > suspend_point_kind suspend_kind, > } > > /* We now have three call expressions, in terms of the promise, handle and > - 'e' proxies. Save them in the await expression for later expansion. */ > + 'e' proxy expression. Save them in the await expression for later > + expansion. */ > > tree awaiter_calls = make_tree_vec (3); > TREE_VEC_ELT (awaiter_calls, 0) = awrd_call; /* await_ready(). */ > @@ -1263,9 +1314,6 @@ build_co_await (location_t loc, tree a, > suspend_point_kind suspend_kind, > } > TREE_VEC_ELT (awaiter_calls, 2) = awrs_call; /* await_resume(). */ > > - if (REFERENCE_REF_P (e_proxy)) > - e_proxy = TREE_OPERAND (e_proxy, 0); > - > tree awrs_type = TREE_TYPE (TREE_TYPE (awrs_func)); > tree suspend_kind_cst = build_int_cst (integer_type_node, > (int) suspend_kind); > diff --git a/gcc/testsuite/g++.dg/coroutines/pr116506.C > b/gcc/testsuite/g++.dg/coroutines/pr116506.C > new file mode 100644 > index 00000000000..57a6e360566 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/coroutines/pr116506.C > @@ -0,0 +1,53 @@ > +// { dg-do run } > +// { dg-additional-options "-fno-exceptions" } > + > +#include <coroutine> > + > +bool g_too_early = true; > +std::coroutine_handle<> g_handle; > + > +struct Awaiter { > + Awaiter() {} > + ~Awaiter() { > + if (g_too_early) { > + __builtin_abort (); > + } > + } > + > + bool await_ready() { return false; } > + void await_suspend(std::coroutine_handle<> handle) { > + g_handle = handle; > + } > + > + void await_resume() {} > +}; > + > +struct SuspendNever { > + bool await_ready() noexcept { return true; } > + void await_suspend(std::coroutine_handle<>) noexcept {} > + void await_resume() noexcept {} > +}; > + > +struct Coroutine { > + struct promise_type { > + Coroutine get_return_object() { return {}; } > + SuspendNever initial_suspend() { return {}; } > + SuspendNever final_suspend() noexcept { return {}; } > + void return_void() {} > + void unhandled_exception() {} > + > + Awaiter&& await_transform(Awaiter&& u) { > + return static_cast<Awaiter&&>(u); > + } > + }; > +}; > + > +Coroutine foo() { > + co_await Awaiter{}; > +} > + > +int main() { > + foo(); > + g_too_early = false; > + g_handle.destroy(); > +} > diff --git a/gcc/testsuite/g++.dg/coroutines/pr116880.C > b/gcc/testsuite/g++.dg/coroutines/pr116880.C > new file mode 100644 > index 00000000000..f0db6a26044 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/coroutines/pr116880.C > @@ -0,0 +1,36 @@ > +#include <coroutine> > + > +struct promise_type; > +using handle_type = std::coroutine_handle<promise_type>; > + > +struct Co { > + handle_type handle; > + using promise_type = ::promise_type; > + > + explicit Co(handle_type handle) : handle(handle) {} > + > + bool await_ready() { return false; } > + std::coroutine_handle<> await_suspend(handle_type handle); > + void await_resume() {} > +}; > + > +struct Done {}; > + > +struct promise_type { > + Co get_return_object(); > + > + std::suspend_always initial_suspend() { return {}; }; > + std::suspend_always final_suspend() noexcept { return {}; }; > + void return_value(Done) {} > + void return_value(Co&&); > + void unhandled_exception() { throw; }; > + Co&& await_transform(Co&& co) { return static_cast<Co&&>(co); } > +}; > + > +Co tryToRun(); > + > +Co init() > +{ > + co_await tryToRun(); > + co_return Done{}; > +} > -- > 2.39.2 (Apple Git-143) >