On 6/3/25 10:02 AM, Iain Sandoe wrote:


On 3 Jun 2025, at 02:36, Jason Merrill <ja...@redhat.com> wrote:

On 6/2/25 5:27 PM, Iain Sandoe wrote:
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.

So it is true that a continued coroutine has suspended .. but, unfortunately, 
it can be prevented from returning to the ramp before it's destroyed…

Then the patch is OK, thanks.

A feature of coroutines is that there are many ways to customise them - so…  
with some creative thinking  …

If we invent an awaiter with an await_suspend always causes us to continue 
ourself - then we can defeat this….

https://godbolt.org/z/KxYcWqP9Y

The question is, where to draw the line.  it is always possible to write code 
that is wrong .. the question is, is this an innocent circumstance that we 
should try to cater for - or?

I think it is, the user shouldn't need to worry about how their suspend behavior affects lifetime.

(I’m fine either way ..  but I suspect we will want a state enum rather than 
adding more booleans).

Returning to my earlier suggestion:

An approach to fixing this (in a later patch) might be to replace the new before_return with a flag in the frame such that, if set, the actor avoids destroying the state and instead clears the flag to indicate to the ramp that it still needs to destroy the state? Then the ramp cleanup would no longer be EH-only or controlled by IARC, just controlled by this flag. Thoughts?

I guess this is a refcount; we want to destroy the state when both actor and ramp are done. So for clarity it could actually be a {0,1,2} counter rather than a boolean.

I guess I also need to update the drafting again.

Jason

Reply via email to