> On 2 Jun 2025, at 21:17, Jason Merrill <ja...@redhat.com> wrote:
>
> On 6/1/25 3:30 AM, Iain Sandoe wrote:
>> Updated. I realised we no longer need to refer to
>> initial_await_resume_called in the ramp at all, v2 removes the setting
>> of the variable from there and puts it into the start of the actor.
>> Tested on x86_64-darwin and powerp64le-linux.
>> Confirmed that the sanitizer test in PR 118074 is fixed but we already
>> have a suitable testcase in PR 115908 which has now been moved to the
>> 'torture' sub-directory to get wider code-gen coverage.
>> OK for trunk?
>> thanks
>> Iain
>> --- 8< ---
>> This implements the final piece of the revised CWG2563 wording;
>> "It exits the scope of promise only if the coroutine completed
>> without suspending."
>> The change removes the need to track whether we have reached the ramp
>> return. We can now assume that the coroutine frame is valid when
>> constructing the ramp return value. The ramp function no longer needs
>> to check initial_await_resume_called, and the references to that can
>> be made local to the resumer.
>> The cleanups for promise, arg copies and frame are now adjusted to
>> depend only on whether a suspend has occurred.
>> PR c++/115908
>> PR c++/118074
>> gcc/cp/ChangeLog:
>> * coroutines.cc: New GTY coro_ramp_cleanup_id.
>> (coro_init_identifiers): Initialize coro_ramp_cleanup_id.
>> (build_actor_fn): Set coro_ramp_cleanup_id false when we
>> exit via a suspend or continuation return. Do not clean
>> up if we pass the final await expression without suspending.
>> (cp_coroutine_transform::wrap_original_function_body):
>> Declare _Coro_ramp_cleanup.
>> (cp_coroutine_transform::build_ramp_function): Amend
>> cleanups to use coro_ramp_cleanup and set that to be true
>> on init. Remove the use of _Coro_befoe_return.
>> gcc/testsuite/ChangeLog:
>> * g++.dg/coroutines/pr115908.C: Move to...
>> * g++.dg/coroutines/torture/pr115908.C: ...here.
>> Signed-off-by: Iain Sandoe <i...@sandoe.co.uk>
>> ---
>> gcc/cp/coroutines.cc | 121 +++++++++---------
>> .../coroutines/{ => torture}/pr115908.C | 9 +-
>> 2 files changed, 60 insertions(+), 70 deletions(-)
>> rename gcc/testsuite/g++.dg/coroutines/{ => torture}/pr115908.C (88%)
>> diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc
>> index a2b6d53805a..42f719ddde1 100644
>> --- a/gcc/cp/coroutines.cc
>> +++ b/gcc/cp/coroutines.cc
>> @@ -348,6 +348,7 @@ static GTY(()) tree coro_resume_index_id;
>> static GTY(()) tree coro_self_handle_id;
>> static GTY(()) tree coro_actor_continue_id;
>> static GTY(()) tree coro_frame_i_a_r_c_id;
>> +static GTY(()) tree coro_ramp_cleanup_id;
>> /* Create the identifiers used by the coroutines library interfaces and
>> the implementation frame state. */
>> @@ -385,6 +386,7 @@ coro_init_identifiers ()
>> coro_resume_index_id = get_identifier ("_Coro_resume_index");
>> coro_self_handle_id = get_identifier ("_Coro_self_handle");
>> coro_actor_continue_id = get_identifier ("_Coro_actor_continue");
>> + coro_ramp_cleanup_id = get_identifier ("_Coro_ramp_should_cleanup");
>> }
>> /* Trees we only need to set up once. */
>> @@ -2568,6 +2570,17 @@ build_actor_fn (location_t loc, tree coro_frame_type,
>> tree actor, tree fnbody,
>> /* Now we start building the rewritten function body. */
>> add_stmt (build_stmt (loc, LABEL_EXPR, actor_begin_label));
>> + tree i_a_r_c = NULL_TREE;
>> + if (flag_exceptions)
>> + {
>> + i_a_r_c
>> + = coro_build_frame_access_expr (actor_frame, coro_frame_i_a_r_c_id,
>> + false, tf_warning_or_error);
>> + tree m = cp_build_modify_expr (loc, i_a_r_c, NOP_EXPR,
>> + boolean_false_node, tf_warning_or_error);
>> + finish_expr_stmt (m);
>> + }
>> +
>> /* actor's coroutine 'self handle'. */
>> tree ash = coro_build_frame_access_expr (actor_frame, coro_self_handle_id,
>> false, tf_warning_or_error);
>> @@ -2597,6 +2610,16 @@ build_actor_fn (location_t loc, tree coro_frame_type,
>> tree actor, tree fnbody,
>> /* Add in our function body with the co_returns rewritten to final form.
>> */
>> add_stmt (fnbody);
>> + /* If we reach here without suspending, then the ramp should cleanup. */
>> + tree ramp_should_cleanup
>> + = coro_build_frame_access_expr (actor_frame, coro_ramp_cleanup_id,
>> + false, tf_warning_or_error);
>> + tree ramp_cu_if = begin_if_stmt ();
>> + finish_if_stmt_cond (ramp_should_cleanup, ramp_cu_if);
>> + finish_return_stmt (NULL_TREE);
>> + finish_then_clause (ramp_cu_if);
>> + finish_if_stmt (ramp_cu_if);
>> +
>> /* Now do the tail of the function; first cleanups. */
>> r = build_stmt (loc, LABEL_EXPR, del_promise_label);
>> add_stmt (r);
>> @@ -2638,6 +2661,9 @@ build_actor_fn (location_t loc, tree coro_frame_type,
>> tree actor, tree fnbody,
>> /* This is the suspend return point. */
>> add_stmt (build_stmt (loc, LABEL_EXPR, ret_label));
>> + r = cp_build_modify_expr (loc, ramp_should_cleanup, NOP_EXPR,
>> + boolean_false_node, tf_warning_or_error);
>> + finish_expr_stmt (r);
>> finish_return_stmt (NULL_TREE);
>> /* This is the 'continuation' return point. For such a case we have a
>> coro
>> @@ -2645,6 +2671,10 @@ build_actor_fn (location_t loc, tree coro_frame_type,
>> tree actor, tree fnbody,
>> execute as a tailcall allowing arbitrary chaining of coroutines. */
>> add_stmt (build_stmt (loc, LABEL_EXPR, continue_label));
>> + r = cp_build_modify_expr (loc, ramp_should_cleanup, NOP_EXPR,
>> + boolean_false_node, tf_warning_or_error);
>> + finish_expr_stmt (r);
>
> Hmm, if this tail-call ends up returning to the ramp, can we get the same
> lifetime problem?
I think this is correct, because if the tailcall returns to the ramp it means
that the coroutine has suspended.
(I actually do have local tests that check continuations - we cannot commit
them because of the issue with platforms that cannot do indirect tailcalls - PR
94794 … )
>
>> /* Should have been set earlier by the coro_initialized code. */
>> gcc_assert (void_coro_handle_address);
>> @@ -2668,10 +2698,6 @@ build_actor_fn (location_t loc, tree
>> coro_frame_type, tree actor, tree fnbody,
>> /* We've now rewritten the tree and added the initial and final
>> co_awaits. Now pass over the tree and expand the co_awaits. */
>> - tree i_a_r_c = NULL_TREE;
>> - if (flag_exceptions)
>> - i_a_r_c = coro_build_frame_access_expr (actor_frame,
>> coro_frame_i_a_r_c_id,
>> - false, tf_warning_or_error);
>> coro_aw_data data = {actor, actor_fp, resume_idx_var, i_a_r_c,
>> ash, del_promise_label, ret_label,
>> @@ -4434,6 +4460,13 @@ cp_coroutine_transform::wrap_original_function_body ()
>> var_list = var;
>> add_decl_expr (var);
>> + tree ramp_does_cleanup
>> + = coro_build_artificial_var (loc, coro_ramp_cleanup_id,
>> boolean_type_node,
>> + orig_fn_decl, NULL_TREE);
>> + DECL_CHAIN (ramp_does_cleanup) = var_list;
>> + var_list = ramp_does_cleanup;
>> + add_decl_expr (ramp_does_cleanup);
>> +
>> if (flag_exceptions)
>> {
>> /* Build promise.unhandled_exception(); */
>> @@ -4898,31 +4931,16 @@ cp_coroutine_transform::build_ramp_function ()
>> just set it true. */
>> TREE_USED (frame_needs_free) = true;
>> - tree iarc_x = NULL_TREE;
>> - tree coro_before_return = NULL_TREE;
>> - if (flag_exceptions)
>> - {
>> - coro_before_return
>> - = coro_build_and_push_artificial_var (loc, "_Coro_before_return",
>> - boolean_type_node, orig_fn_decl,
>> - boolean_true_node);
>> - iarc_x
>> - = coro_build_and_push_artificial_var_with_dve (loc,
>> - coro_frame_i_a_r_c_id,
>> - boolean_type_node,
>> - orig_fn_decl,
>> - boolean_false_node,
>> - deref_fp);
>> - tree frame_cleanup = push_stmt_list ();
>> - tree do_fr_cleanup
>> - = build1_loc (loc, TRUTH_NOT_EXPR, boolean_type_node, iarc_x);
>> - do_fr_cleanup = build2_loc (loc, TRUTH_ANDIF_EXPR, boolean_type_node,
>> - coro_before_return, do_fr_cleanup);
>> - r = build3 (COND_EXPR, void_type_node, do_fr_cleanup,
>> - delete_frame_call, void_node);
>> - finish_expr_stmt (r);
>> - push_cleanup (coro_fp, pop_stmt_list (frame_cleanup),
>> /*eh_only*/true);
>> - }
>> + tree ramp_should_cleanup
>> + = coro_build_and_push_artificial_var_with_dve (loc,
>> coro_ramp_cleanup_id,
>> + boolean_type_node,
>> + orig_fn_decl,
>> + boolean_true_node,
>> + deref_fp);
>> +
>> + r = build3 (COND_EXPR, void_type_node, ramp_should_cleanup,
>> + delete_frame_call, void_node);
>> + push_cleanup (coro_fp, r, /*eh_only*/false);
>> /* Put the resumer and destroyer functions in. */
>> @@ -4997,24 +5015,17 @@ cp_coroutine_transform::build_ramp_function ()
>> /* Arrange for parm copies to be cleaned up when an exception is
>> thrown before initial await resume. */
>> - if (flag_exceptions && !parm.trivial_dtor)
>> + if (!parm.trivial_dtor)
>> {
>> parm.fr_copy_dtor
>> = cxx_maybe_build_cleanup (fld_idx, tf_warning_or_error);
>> if (parm.fr_copy_dtor && parm.fr_copy_dtor != error_mark_node)
>> {
>> param_dtor_list.safe_push (parm.field_id);
>> - tree param_cleanup = push_stmt_list ();
>> - tree do_cleanup
>> - = build1_loc (loc, TRUTH_NOT_EXPR, boolean_type_node,
>> iarc_x);
>> - do_cleanup
>> - = build2_loc (loc, TRUTH_ANDIF_EXPR, boolean_type_node,
>> - coro_before_return, do_cleanup);
>> - r = build3_loc (loc, COND_EXPR, void_type_node, do_cleanup,
>> - parm.fr_copy_dtor, void_node);
>> - finish_expr_stmt (r);
>> - push_cleanup (fld_idx, pop_stmt_list (param_cleanup),
>> - /*eh_only*/true);
>> + r = build3_loc (loc, COND_EXPR, void_type_node,
>> + ramp_should_cleanup, parm.fr_copy_dtor,
>> + void_node);
>> + push_cleanup (fld_idx, r, /*eh_only*/false);
>> }
>> }
>> }
>> @@ -5059,22 +5070,13 @@ cp_coroutine_transform::build_ramp_function ()
>> finish_expr_stmt (r);
>> }
>> - if (flag_exceptions)
>> + tree promise_dtor = cxx_maybe_build_cleanup (p, tf_warning_or_error);
>> + /* If the promise is live, then run its dtor if that's available. */
>> + if (promise_dtor && promise_dtor != error_mark_node)
>> {
>> - tree promise_dtor = cxx_maybe_build_cleanup (p, tf_warning_or_error);
>> - /* If the promise is live, then run its dtor if that's available. */
>> - if (promise_dtor && promise_dtor != error_mark_node)
>> - {
>> - tree promise_cleanup = push_stmt_list ();
>> - tree do_cleanup
>> - = build1_loc (loc, TRUTH_NOT_EXPR, boolean_type_node, iarc_x);
>> - do_cleanup = build2_loc (loc, TRUTH_ANDIF_EXPR, boolean_type_node,
>> - coro_before_return, do_cleanup);
>> - r = build3 (COND_EXPR, void_type_node, do_cleanup,
>> - promise_dtor, void_node);
>> - finish_expr_stmt (r);
>> - push_cleanup (p, pop_stmt_list (promise_cleanup), /*eh_only*/true);
>> - }
>> + r = build3 (COND_EXPR, void_type_node, ramp_should_cleanup,
>> + promise_dtor, void_node);
>> + push_cleanup (p, r, /*eh_only*/false);
>> }
>> tree get_ro
>> @@ -5143,13 +5145,6 @@ cp_coroutine_transform::build_ramp_function ()
>> r = build_call_expr_loc (fn_start, resumer, 1, coro_fp);
>> finish_expr_stmt (r);
>> - if (flag_exceptions)
>> - {
>> - r = cp_build_modify_expr (input_location, coro_before_return,
>> NOP_EXPR,
>> - boolean_false_node, tf_warning_or_error);
>> - finish_expr_stmt (r);
>> - }
>> -
>> /* The ramp is done, we just need the return statement, which we build
>> from
>> the return object we constructed before we called the actor. */
>> diff --git a/gcc/testsuite/g++.dg/coroutines/pr115908.C
>> b/gcc/testsuite/g++.dg/coroutines/torture/pr115908.C
>> similarity index 88%
>> rename from gcc/testsuite/g++.dg/coroutines/pr115908.C
>> rename to gcc/testsuite/g++.dg/coroutines/torture/pr115908.C
>> index a40cece1143..ff562ef44d0 100644
>> --- a/gcc/testsuite/g++.dg/coroutines/pr115908.C
>> +++ b/gcc/testsuite/g++.dg/coroutines/torture/pr115908.C
>> @@ -3,13 +3,8 @@
>> // With the changes to deal with CWG2563 (and PR119916) we now use the
>> // referenced promise in the return expression. It is quite reasonable
>> // for a body implementation to complete before control is returned to
>> -// the ramp - and in that case we would be creating the ramp return object
>> -// from an already-deleted promise object.
>> -// This is recognised to be a poor situation and resolution via a core
>> -// issue is planned.
>> -
>> -// In this test we explicitly trigger the circumstance mentioned above.
>> -// { dg-xfail-run-if "" { *-*-* } }
>> +// the ramp - so we need to ensure that the promise lifetime is extended
>> +// in that case, tested here.
>> #include <coroutine>