>>+  0 signalling that both the body and the ramp have completed (i.e. the body
>>+  ran to completion before the ramp was re-entered).

>I had figured that the ramp would set the reference count to 1 initially, and 
>decrement it in a cleanup, and the actor would increment it after initial 
>await_resume, and decrement after final_suspend.
<snip>

As we then discussed off-list, the idea is then that the ramp has
a cleanup around the call to the body and the construction of the
return (where appropriate).  We then need the body to increment
the count when it has a use of the frame - but that use needs to be
released when an exception occurs before initial await_suspend. The
idea is to implement that with an eh-only cleanup around the
initial expressions.

When I came to implement this - it turned out to need a small tweak;
since we cannot sub-divide the await expression the cleanup needs
to be extended to encompass the initial await and the body.  In the
case that i_a_r_c is false we then expect the ramp to cleanup and
the body has released its use.

However, once i_a_r_c is passed, the body needs to reclaim its
use, since if unhanded_exception throws, the ramp must not clean
up - instead the user must do handle.destroy().  If u_h_e does not
throw then we continue with the final-await expression and the
body use is still relevant.  The solution to this is to increment
the use before calling promise.unhanded_exception().
(this requires the removal of the frame copy of the self-handle
 proposed in an earlier patch).
 tested on x86_64-darwin, powerpc64le-linux, OK for trunk now?
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."

Considering the coroutine to be made up of two components; a
'ramp' and a 'body' where the body represents the user's original
code and the ramp is responsible for setup of that and for
returning some object to the original caller.

Coroutine state, and responsibility for its release.

A coroutine has some state that persists across suspensions.

The state has two components:
  * State that is specified by the standard and persists for the entire
    life of the coroutine.
  * Local state that is constructed/destructed as scopes in the original
    function body are entered/exited.  The destruction of local state is
    always the responsibility of the body code.

The persistent state (and the overall storage for the state) must be
managed in two places:
  * The ramp function (which allocates and builds this - and can, in some
    cases, be responsible for destroying it)
  * The re-written function body which can destroy it when that body
    completes its final suspend - or when the handle.destroy () is called.

In all cases the ramp holds responsibility for constructing the standard-
mandated persistent state.

There are four ways in which the ramp might be re-entered after starting
the function body:
  A The body could suspend (one might expect that to be the 'normal' case
    for most coroutine).
  B The body might complete either synchronously or via continuations.
  C An exception might be thrown during the setup of the initial await
    expression, before the initial awaiter resumes.
  D An exception might be processed by promise.unhandled_exception () and
    that, in turn, might re-throw it (or throw something else).  In this
    case, the coroutine is considered suspended at the final suspension
    point.

Once the coroutine has passed initial suspend (i.e. the initial awaiter
await_resume() has been called) the body is considered to have a use of
the state.

Until the ramp return value has been constructed, the ramp is considered
to have a use of the state.

To manage these interacting conditions we allocate a reference counter
for the frame state.  This is initialised to 1 by the ramp as part of its
startup (note that failures/exceptions in the startup code are handled
locally to the ramp).

When the body returns (either normally, or by exception) the ramp releases
its use.

Once the rewritten coroutine body is started, the body is considered to
have a use of the frame.  This use (potentially) needs to be released if
an exception is thrown from the body.  We implement this using an eh-only
cleanup around the initial await and function body.  If we have the case
D above, then we re-establish a body use.

In case:

  A, typically the ramp would be re-entered with the body holding a use,
  and therefore the ramp should not destroy the state.

  B, both the body and ramp will have released their uses, and the ramp
  should destroy the state.

  C, we must arrange for the body to release its use, because we require
  the ramp to cleanup in this circumstance.

  D is an outlier, since the responsibility for destruction of the state
  now rests with the user's code (via a handle.destroy() call).  Since
  the exception will have removed the body's use, we must re-establish that
  before calling unhandled_exception().

  NOTE: In the case that the body has never suspended before such an
  exception occurs, the only reasonable way for the user code to obtain the
  necessary handle is if unhandled_exception() throws the handle or some
  object that contains the handle.  That is outside of the designs here -
  if the user code might need this corner-case, then such provision will
  have to be made.

In the ramp, we implement destruction for the persistent frame state by
means of cleanups.  These are run conditionally when the reference count
is 0 signalling that both the body and the ramp have completed.

In the body, once we pass the final suspend, then we test the use and
delete the state if the use is 0.

        PR c++/115908
        PR c++/118074
        PR c++/95615

gcc/cp/ChangeLog:

        * coroutines.cc (coro_frame_refcount_id): New.
        (coro_init_identifiers): Initialise coro_frame_refcount_id.
        (build_actor_fn): Set up initial_await_resume_called.  Handle
        decrementing of the frame reference count.  Return directly to
        the caller if that is non-zero.
        (cp_coroutine_transform::wrap_original_function_body): Increment
        the reference count before calling unhandled_exception.
        (cp_coroutine_transform::build_ramp_function): Implement the
        frame, promise and argument copy destruction via regular
        cleanups.  Decrement the reference count in a cleanup around
        the coroutine body.

gcc/testsuite/ChangeLog:

        * g++.dg/coroutines/pr115908.C: Move to...
        * g++.dg/coroutines/torture/pr115908.C: ...here.
        * g++.dg/coroutines/torture/pr95615-02.C: Move to...
        * g++.dg/coroutines/torture/pr95615-01-promise-ctor-throws.C: ...here.
        * g++.dg/coroutines/torture/pr95615-03.C: Move to...
        * g++.dg/coroutines/torture/pr95615-02-get-return-object-throws.C: 
...here.
        * g++.dg/coroutines/torture/pr95615-01.C: Move to...
        * g++.dg/coroutines/torture/pr95615-03-initial-suspend-throws.C: 
...here.
        * g++.dg/coroutines/torture/pr95615-04.C: Move to...
        * g++.dg/coroutines/torture/pr95615-04-initial-await-ready-throws.C: 
...here.
        * g++.dg/coroutines/torture/pr95615-05.C: Move to...
        * g++.dg/coroutines/torture/pr95615-05-initial-await-suspend-throws.C: 
...here.
        * g++.dg/coroutines/torture/pr95615.inc: Add more cases and ensure that 
the
        code completes properly when no exceptions are thrown.
        * g++.dg/coroutines/torture/pr95615-00-nothing-throws.C: New test.
        * g++.dg/coroutines/torture/pr95615-06-initial-await-resume-throws.C: 
New test.
        * g++.dg/coroutines/torture/pr95615-07-body-throws.C: New test.
        * 
g++.dg/coroutines/torture/pr95615-08-initial-suspend-throws-uhe-throws.C: New 
test.
        * g++.dg/coroutines/torture/pr95615-09-body-throws-uhe-throws.C: New 
test.

Signed-off-by: Iain Sandoe <i...@sandoe.co.uk>
---
 gcc/cp/coroutines.cc                          | 266 +++++++++++++-----
 .../coroutines/{ => torture}/pr115908.C       |   9 +-
 .../torture/pr95615-00-nothing-throws.C       |   5 +
 ...-02.C => pr95615-01-promise-ctor-throws.C} |   0
 ... => pr95615-02-get-return-object-throws.C} |   0
 ....C => pr95615-03-initial-suspend-throws.C} |   0
 ...> pr95615-04-initial-await-ready-throws.C} |   0
 ...pr95615-05-initial-await-suspend-throws.C} |   0
 .../pr95615-06-initial-await-resume-throws.C  |   7 +
 .../torture/pr95615-07-body-throws.C          |   7 +
 ...615-08-initial-suspend-throws-uhe-throws.C |   8 +
 .../pr95615-09-body-throws-uhe-throws.C       |  10 +
 .../g++.dg/coroutines/torture/pr95615.inc     | 196 +++++++++----
 13 files changed, 378 insertions(+), 130 deletions(-)
 rename gcc/testsuite/g++.dg/coroutines/{ => torture}/pr115908.C (88%)
 create mode 100644 
gcc/testsuite/g++.dg/coroutines/torture/pr95615-00-nothing-throws.C
 rename gcc/testsuite/g++.dg/coroutines/torture/{pr95615-02.C => 
pr95615-01-promise-ctor-throws.C} (100%)
 rename gcc/testsuite/g++.dg/coroutines/torture/{pr95615-03.C => 
pr95615-02-get-return-object-throws.C} (100%)
 rename gcc/testsuite/g++.dg/coroutines/torture/{pr95615-01.C => 
pr95615-03-initial-suspend-throws.C} (100%)
 rename gcc/testsuite/g++.dg/coroutines/torture/{pr95615-04.C => 
pr95615-04-initial-await-ready-throws.C} (100%)
 rename gcc/testsuite/g++.dg/coroutines/torture/{pr95615-05.C => 
pr95615-05-initial-await-suspend-throws.C} (100%)
 create mode 100644 
gcc/testsuite/g++.dg/coroutines/torture/pr95615-06-initial-await-resume-throws.C
 create mode 100644 
gcc/testsuite/g++.dg/coroutines/torture/pr95615-07-body-throws.C
 create mode 100644 
gcc/testsuite/g++.dg/coroutines/torture/pr95615-08-initial-suspend-throws-uhe-throws.C
 create mode 100644 
gcc/testsuite/g++.dg/coroutines/torture/pr95615-09-body-throws-uhe-throws.C

diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc
index 036fc7ba553..9275279f3db 100644
--- a/gcc/cp/coroutines.cc
+++ b/gcc/cp/coroutines.cc
@@ -191,7 +191,89 @@ static bool coro_promise_type_found_p (tree, location_t);
   just syntactic sugar for a co_await).
 
   We defer the analysis and transformation until template expansion is
-  complete so that we have complete types at that time.  */
+  complete so that we have complete types at that time.
+
+  ---------------------------------------------------------------------------
+
+  Coroutine state, and responsibility for its release.
+
+  As noted above, a coroutine has some state that persists across suspensions.
+
+  The state has two components:
+    * State that is specified by the standard and persists for the entire
+      life of the coroutine.
+    * Local state that is constructed/destructed as scopes in the original
+      function body are entered/exited.  The destruction of local state is
+      always the responsibility of the body code.
+
+  The persistent state (and the overall storage for the state) must be
+  managed in two places:
+    * The ramp function (which allocates and builds this - and can, in some
+      cases, be responsible for destroying it)
+    * The re-written function body which can destroy it when that body
+      completes its final suspend - or when the handle.destroy () is called.
+
+  In all cases the ramp holds responsibility for constructing the standard-
+  mandated persistent state.
+
+  There are four ways in which the ramp might be re-entered after starting
+  the function body:
+    A The body could suspend (one might expect that to be the 'normal' case
+      for most coroutine).
+    B The body might complete either synchronously or via continuations.
+    C An exception might be thrown during the setup of the initial await
+      expression, before the initial awaiter resumes.
+    D An exception might be processed by promise.unhandled_exception () and
+      that, in turn, might re-throw it (or throw something else).  In this
+      case, the coroutine is considered suspended at the final suspension
+      point.
+
+  Until the ramp return value has been constructed, the ramp is considered
+  to have a use of the state.
+
+  To manage these interacting conditions we allocate a reference counter
+  for the frame state.  This is initialised to 1 by the ramp as part of its
+  startup (note that failures/exceptions in the startup code are handled
+  locally to the ramp).
+
+  When the body returns (either normally, or by exception) the ramp releases
+  its use.
+
+  Once the rewritten coroutine body is started, the body is considered to
+  have a use of the frame.  This use (potentially) needs to be released if
+  an exception is thrown from the body.  We implement this using an eh-only
+  cleanup around the initial await and function body.  If we have the case
+  D above, then we re-establish a body use.
+
+  In case:
+
+    A, typically the ramp would be re-entered with the body holding a use,
+    and therefore the ramp should not destroy the state.
+
+    B, both the body and ramp will have released their uses, and the ramp
+    should destroy the state.
+
+    C, we must arrange for the body to release its use, because we require
+    the ramp to cleanup in this circumstance.
+
+    D is an outlier, since the responsibility for destruction of the state
+    now rests with the user's code (via a handle.destroy() call).  Since
+    the exception will have removed the body's use, we must re-establish that
+    before calling unhandled_exception().
+
+    NOTE: In the case that the body has never suspended before such an
+    exception occurs, the only reasonable way for the user code to obtain the
+    necessary handle is if unhandled_exception() throws the handle or some
+    object that contains the handle.  That is outside of the designs here -
+    if the user code might need this corner-case, then such provision will
+    have to be made.
+
+  In the ramp, we implement destruction for the persistent frame state by
+  means of cleanups.  These are run conditionally when the reference count
+  is 0 signalling that both the body and the ramp have completed.
+
+  In the body, once we pass the final suspend, then we test the use and
+  delete the state if the use is 0.  */
 
 
 /* The state that we collect during parsing (and template expansion) for
@@ -347,6 +429,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_frame_refcount_id;
 
 /* Create the identifiers used by the coroutines library interfaces and
    the implementation frame state.  */
@@ -384,6 +467,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_frame_refcount_id = get_identifier ("_Coro_frame_refcount");
 }
 
 /* Trees we only need to set up once.  */
@@ -2598,6 +2682,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);
+    }
+
   /* Now we know the real promise, and enough about the frame layout to
      decide where to put things.  */
 
@@ -2611,8 +2706,29 @@ 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);
 
-  /* Now do the tail of the function; first cleanups.  */
-  tree r = build_stmt (loc, LABEL_EXPR, del_promise_label);
+  /* We are done with the frame, but if the ramp still has a hold on it
+     we should not cleanup.  So decrement the refcount and then return to
+     the ramp if it is > 0.  */
+  tree coro_frame_refcount
+    = coro_build_frame_access_expr (actor_frame, coro_frame_refcount_id,
+                                   false, tf_warning_or_error);
+  tree released = build2_loc (loc, MINUS_EXPR, short_unsigned_type_node,
+                             coro_frame_refcount,
+                             build_int_cst (short_unsigned_type_node, 1));
+  tree r = cp_build_modify_expr (loc, coro_frame_refcount, NOP_EXPR, released,
+                                tf_warning_or_error);
+  finish_expr_stmt (r);
+  tree cond = build2_loc (loc, NE_EXPR, short_unsigned_type_node,
+                         coro_frame_refcount,
+                         build_int_cst (short_unsigned_type_node, 0));
+  tree ramp_cu_if = begin_if_stmt ();
+  finish_if_stmt_cond (cond, ramp_cu_if);
+  finish_return_stmt (NULL_TREE);
+  finish_then_clause (ramp_cu_if);
+  finish_if_stmt (ramp_cu_if);
+
+  /* Otherwise, do the tail of the function; first cleanups.  */
+  r = build_stmt (loc, LABEL_EXPR, del_promise_label);
   add_stmt (r);
 
   /* Destructors for the things we built explicitly.
@@ -2688,10 +2804,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,
                       del_promise_label, ret_label,
@@ -4457,6 +4569,14 @@ cp_coroutine_transform::wrap_original_function_body ()
   var_list = resume_idx_var;
   add_decl_expr (resume_idx_var);
 
+  tree coro_frame_refcount
+    = coro_build_artificial_var (loc, coro_frame_refcount_id,
+                                short_unsigned_type_node, orig_fn_decl,
+                                NULL_TREE);
+  DECL_CHAIN (coro_frame_refcount) = var_list;
+  var_list = coro_frame_refcount;
+  add_decl_expr (coro_frame_refcount);
+
   /* If the coroutine has a frame that needs to be freed, this will be set by
      the ramp.  */
   var = coro_build_artificial_var (loc, coro_frame_needs_free_id,
@@ -4465,6 +4585,14 @@ cp_coroutine_transform::wrap_original_function_body ()
   var_list = var;
   add_decl_expr (var);
 
+  /* We consider that the body has a use of the frame once we start to process
+     the initial suspend expression. (the use might be relinquished if we
+     encounter an exception before the body is finished).  */
+  tree body_use
+    = build2_loc (loc, PLUS_EXPR, short_unsigned_type_node, 
coro_frame_refcount,
+                 build_int_cst (short_unsigned_type_node, 1));
+  body_use = cp_build_modify_expr (loc, coro_frame_refcount, NOP_EXPR, 
body_use,
+                                tf_warning_or_error);
   if (flag_exceptions)
     {
       /* Build promise.unhandled_exception();  */
@@ -4485,10 +4613,25 @@ cp_coroutine_transform::wrap_original_function_body ()
       tree tcb = build_stmt (loc, TRY_BLOCK, NULL_TREE, NULL_TREE);
       add_stmt (tcb);
       TRY_STMTS (tcb) = push_stmt_list ();
+      finish_expr_stmt (body_use);
+      /* We need a new scope to handle the cleanup for the ramp use that is
+        needed for exceptions.  */
+      tree except_scope = begin_compound_stmt (0);
+      current_binding_level->artificial = 1;
+      tree release
+       = build2_loc (loc, MINUS_EXPR, short_unsigned_type_node,
+                     coro_frame_refcount, build_int_cst 
(short_unsigned_type_node, 1));
+       release = cp_build_modify_expr (loc, coro_frame_refcount, NOP_EXPR,
+                                      release, tf_warning_or_error);
+      push_cleanup (NULL_TREE, release, /*ehonly*/true);
       /* Add the initial await to the start of the user-authored function.  */
       finish_expr_stmt (initial_await);
+
       /* Append the original function body.  */
       add_stmt (coroutine_body);
+      /* End the scope that handles the remove of frame-use on exception.  */
+      finish_compound_stmt (except_scope);
+
       if (return_void)
        add_stmt (return_void);
       TRY_STMTS (tcb) = pop_stmt_list (TRY_STMTS (tcb));
@@ -4520,6 +4663,15 @@ cp_coroutine_transform::wrap_original_function_body ()
       r = build2 (MODIFY_EXPR, short_unsigned_type_node, resume_idx_var,
                  short_zero);
       finish_expr_stmt (r);
+      /* Now, if the the UEH throws, we still want a body use, but we gave
+        up our use when the EH cleanup fired.  */
+      tree re_use
+       = build2_loc (loc, PLUS_EXPR, short_unsigned_type_node,
+                     coro_frame_refcount,
+                     build_int_cst (short_unsigned_type_node, 1));
+      r = cp_build_modify_expr (loc, coro_frame_refcount, NOP_EXPR, re_use,
+                               tf_warning_or_error);
+      finish_expr_stmt (r);
       finish_expr_stmt (ueh);
       finish_handler (handler);
       TRY_HANDLERS (tcb) = pop_stmt_list (TRY_HANDLERS (tcb));
@@ -4539,6 +4691,7 @@ cp_coroutine_transform::wrap_original_function_body ()
                        coro_unhandled_exception_identifier,
                        get_coroutine_promise_type (orig_fn_decl));
        }
+      finish_expr_stmt (body_use);
       /* Else we don't check and don't care if the method is missing..
         just add the initial suspend, function and return.  */
       finish_expr_stmt (initial_await);
@@ -4933,31 +5086,17 @@ 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 coro_frame_refcount
+    = coro_build_and_push_artificial_var_with_dve (loc, coro_frame_refcount_id,
+                                                  short_unsigned_type_node,
+                                                  orig_fn_decl, NULL_TREE,
+                                                  deref_fp);
+  /* Cleanup if both the ramp and the body have finished.  */
+  tree cond
+    = build2_loc (loc, EQ_EXPR, short_unsigned_type_node, coro_frame_refcount,
+                 build_int_cst (short_unsigned_type_node, 0));
+  r = build3 (COND_EXPR, void_type_node, cond, delete_frame_call, void_node);
+  push_cleanup (coro_fp, r, /*eh_only*/false);
 
   /* Put the resumer and destroyer functions in.  */
 
@@ -5032,24 +5171,20 @@ 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,
+                 cond
+                   = build2_loc (loc, EQ_EXPR, short_unsigned_type_node,
+                                 coro_frame_refcount,
+                                 build_int_cst (short_unsigned_type_node, 0));
+                 r = build3_loc (loc, COND_EXPR, void_type_node, cond,
                                  parm.fr_copy_dtor, void_node);
-                 finish_expr_stmt (r);
-                 push_cleanup (fld_idx, pop_stmt_list (param_cleanup),
-                               /*eh_only*/true);
+                 push_cleanup (fld_idx, r, /*eh_only*/false);
                }
            }
        }
@@ -5094,22 +5229,15 @@ 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);
-       }
+      cond = build2_loc (loc, EQ_EXPR, short_unsigned_type_node,
+                        coro_frame_refcount,
+                        build_int_cst (short_unsigned_type_node, 0));
+      r = build3 (COND_EXPR, void_type_node, cond, promise_dtor, void_node);
+      push_cleanup (p, r, /*eh_only*/false);
     }
 
   tree get_ro
@@ -5174,16 +5302,22 @@ cp_coroutine_transform::build_ramp_function ()
        push_cleanup (coro_gro, coro_gro_cleanup, /*eh_only*/false);
     }
 
-  /* Start the coroutine body.  */
-  r = build_call_expr_loc (fn_start, resumer, 1, coro_fp);
+  /* Start the coroutine body, we now have a use of the frame...  */
+  r = cp_build_modify_expr (loc, coro_frame_refcount, NOP_EXPR,
+                           build_int_cst (short_unsigned_type_node, 1),
+                           tf_warning_or_error);
   finish_expr_stmt (r);
+  /* ... but when we finish we want to release that, and we want to do that
+     before any of the other cleanups run.  */
+  tree released
+    = build2_loc (loc, MINUS_EXPR, short_unsigned_type_node, 
coro_frame_refcount,
+                 build_int_cst (short_unsigned_type_node, 1));
+  released = cp_build_modify_expr (loc, coro_frame_refcount, NOP_EXPR, 
released,
+                                tf_warning_or_error);
+  push_cleanup (NULL_TREE, released, /*eh_only*/false);
 
-  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);
-    }
+  r = build_call_expr_loc (fn_start, resumer, 1, coro_fp);
+  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>
 
diff --git 
a/gcc/testsuite/g++.dg/coroutines/torture/pr95615-00-nothing-throws.C 
b/gcc/testsuite/g++.dg/coroutines/torture/pr95615-00-nothing-throws.C
new file mode 100644
index 00000000000..07558f591ed
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/torture/pr95615-00-nothing-throws.C
@@ -0,0 +1,5 @@
+//  { dg-do run }
+//  { dg-skip-if "requires hosted libstdc++ for cassert in pr95615.inc" { ! 
hostedlib } }
+
+// Check that this completes correctly if nothing throws.
+#include "pr95615.inc"
diff --git a/gcc/testsuite/g++.dg/coroutines/torture/pr95615-02.C 
b/gcc/testsuite/g++.dg/coroutines/torture/pr95615-01-promise-ctor-throws.C
similarity index 100%
rename from gcc/testsuite/g++.dg/coroutines/torture/pr95615-02.C
rename to 
gcc/testsuite/g++.dg/coroutines/torture/pr95615-01-promise-ctor-throws.C
diff --git a/gcc/testsuite/g++.dg/coroutines/torture/pr95615-03.C 
b/gcc/testsuite/g++.dg/coroutines/torture/pr95615-02-get-return-object-throws.C
similarity index 100%
rename from gcc/testsuite/g++.dg/coroutines/torture/pr95615-03.C
rename to 
gcc/testsuite/g++.dg/coroutines/torture/pr95615-02-get-return-object-throws.C
diff --git a/gcc/testsuite/g++.dg/coroutines/torture/pr95615-01.C 
b/gcc/testsuite/g++.dg/coroutines/torture/pr95615-03-initial-suspend-throws.C
similarity index 100%
rename from gcc/testsuite/g++.dg/coroutines/torture/pr95615-01.C
rename to 
gcc/testsuite/g++.dg/coroutines/torture/pr95615-03-initial-suspend-throws.C
diff --git a/gcc/testsuite/g++.dg/coroutines/torture/pr95615-04.C 
b/gcc/testsuite/g++.dg/coroutines/torture/pr95615-04-initial-await-ready-throws.C
similarity index 100%
rename from gcc/testsuite/g++.dg/coroutines/torture/pr95615-04.C
rename to 
gcc/testsuite/g++.dg/coroutines/torture/pr95615-04-initial-await-ready-throws.C
diff --git a/gcc/testsuite/g++.dg/coroutines/torture/pr95615-05.C 
b/gcc/testsuite/g++.dg/coroutines/torture/pr95615-05-initial-await-suspend-throws.C
similarity index 100%
rename from gcc/testsuite/g++.dg/coroutines/torture/pr95615-05.C
rename to 
gcc/testsuite/g++.dg/coroutines/torture/pr95615-05-initial-await-suspend-throws.C
diff --git 
a/gcc/testsuite/g++.dg/coroutines/torture/pr95615-06-initial-await-resume-throws.C
 
b/gcc/testsuite/g++.dg/coroutines/torture/pr95615-06-initial-await-resume-throws.C
new file mode 100644
index 00000000000..e57b4711b1d
--- /dev/null
+++ 
b/gcc/testsuite/g++.dg/coroutines/torture/pr95615-06-initial-await-resume-throws.C
@@ -0,0 +1,7 @@
+//  { dg-do run }
+//  { dg-skip-if "requires hosted libstdc++ for cassert in pr95615.inc" { ! 
hostedlib } }
+
+#define INITIAL_AWAIT_RESUME_THROWS 1
+// This should reach unhandled_exception
+#define SHOULD_CALL_UNHANDLED_EXCEPTION 1
+#include "pr95615.inc"
diff --git a/gcc/testsuite/g++.dg/coroutines/torture/pr95615-07-body-throws.C 
b/gcc/testsuite/g++.dg/coroutines/torture/pr95615-07-body-throws.C
new file mode 100644
index 00000000000..fb1059932f5
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/torture/pr95615-07-body-throws.C
@@ -0,0 +1,7 @@
+//  { dg-do run }
+//  { dg-skip-if "requires hosted libstdc++ for cassert in pr95615.inc" { ! 
hostedlib } }
+
+#define BODY_THROWS 1
+// This should reach unhandled_exception
+#define SHOULD_CALL_UNHANDLED_EXCEPTION 1
+#include "pr95615.inc"
diff --git 
a/gcc/testsuite/g++.dg/coroutines/torture/pr95615-08-initial-suspend-throws-uhe-throws.C
 
b/gcc/testsuite/g++.dg/coroutines/torture/pr95615-08-initial-suspend-throws-uhe-throws.C
new file mode 100644
index 00000000000..6dc47bf292c
--- /dev/null
+++ 
b/gcc/testsuite/g++.dg/coroutines/torture/pr95615-08-initial-suspend-throws-uhe-throws.C
@@ -0,0 +1,8 @@
+//  { dg-do run }
+//  { dg-skip-if "requires hosted libstdc++ for cassert in pr95615.inc" { ! 
hostedlib } }
+
+#define INITIAL_SUSPEND_THROWS 1
+// It should be irrelevant that unhandled_exception might throw, so we should
+// not call it.
+#define UNHANDLED_EXCEPTION_THROWS 1
+#include "pr95615.inc"
diff --git 
a/gcc/testsuite/g++.dg/coroutines/torture/pr95615-09-body-throws-uhe-throws.C 
b/gcc/testsuite/g++.dg/coroutines/torture/pr95615-09-body-throws-uhe-throws.C
new file mode 100644
index 00000000000..838b2bae2a3
--- /dev/null
+++ 
b/gcc/testsuite/g++.dg/coroutines/torture/pr95615-09-body-throws-uhe-throws.C
@@ -0,0 +1,10 @@
+//  { dg-do run }
+//  { dg-skip-if "requires hosted libstdc++ for cassert in pr95615.inc" { ! 
hostedlib } }
+
+#define BODY_THROWS 1
+// This should reach unhandled_exception...
+#define SHOULD_CALL_UNHANDLED_EXCEPTION 1
+// ... which will throw 
+#define UNHANDLED_EXCEPTION_THROWS 1
+// and we should cleanup by calling destroy.
+#include "pr95615.inc"
diff --git a/gcc/testsuite/g++.dg/coroutines/torture/pr95615.inc 
b/gcc/testsuite/g++.dg/coroutines/torture/pr95615.inc
index b6f78fb15b9..ab6731df0a6 100644
--- a/gcc/testsuite/g++.dg/coroutines/torture/pr95615.inc
+++ b/gcc/testsuite/g++.dg/coroutines/torture/pr95615.inc
@@ -1,127 +1,209 @@
-#if __has_include(<coroutine>)
 #include <coroutine>
-#else
-#include <experimental/coroutine>
-namespace std {
-    using namespace std::experimental;
-}
-#endif
-#include <cassert>
+#include <exception>
 #include <cstdio>
 #include <cstdlib>
 
-bool frame_live = false;
-bool promise_live = false;
-
-struct X {};
+#ifndef OUTPUT
+#  define PRINT(X)
+#  define PRINTF(...)
+#else
+//#include <iostream>
+#  define PRINT(X) puts(X)
+//#  define PRINT(X) std::cout << (X) << std::endl
+#  define PRINTF(...) printf(__VA_ARGS__)
+#endif
 
+int frame_live = 0;
+int promise_live = 0;
 int Y_live = 0;
 int task_live = 0;
+int unhandled_exception_called = 0;
+
+struct X {};
 
 struct Y
 {
-  Y () { std::puts("Y ()"); Y_live++; } 
-  Y (const Y&) { std::puts("Y (const Y&)"); Y_live++; } 
-  ~Y () { std::puts("~Y ()"); Y_live--; }
+  Y () {
+    PRINT("Y ()");
+    Y_live++;
+  } 
+  Y (const Y&) {
+    PRINT("Y (const Y&)");
+    Y_live++;
+  } 
+  ~Y () {
+    PRINT("~Y ()");
+    --Y_live;
+  }
 };
 
 struct task {
+    struct promise_type;
+    using coro_handle = std::coroutine_handle<promise_type>;
+    struct Z {
+      std::exception_ptr eptr;
+      coro_handle h;
+    };
     struct promise_type {
         void* operator new(size_t sz) {
-            std::puts("operator new()");
-            frame_live = true;
+            PRINT("operator new()");
+            frame_live++;
             return ::operator new(sz);
         }
 
-        void operator delete(void* p, size_t sz) {
-            std::puts("operator delete");
-            frame_live = false;
-            return ::operator delete(p, sz);
+        static void operator delete(void* p, size_t sz)  {
+            PRINT("operator delete");
+            --frame_live;
+            ::operator delete(p, sz);
         }
 
         promise_type() {
-            std::puts("promise_type()");
-#if PROMISE_CTOR_THROWS
+            PRINT("promise_type()");
+#ifdef PROMISE_CTOR_THROWS
              throw X{};
 #endif
             promise_live = true;
         }
 
         ~promise_type() {
-            std::puts("~promise_type()");
-            promise_live = false;
+            PRINT("~promise_type()");
+            --promise_live;
         }
 
         struct awaiter {
             bool await_ready() {
-#if INITIAL_AWAIT_READY_THROWS
+             PRINT("initial await_ready()");
+#ifdef INITIAL_AWAIT_READY_THROWS
                throw X{};
 #endif
+// If we are testing the suspend infra, then suspend, otherwise continue
+// so that we can test later paths (effectively, suspend_never).
+#ifdef INITIAL_AWAIT_SUSPEND_THROWS
                return false;
+#else
+               return true;
+#endif
             }
             void await_suspend(std::coroutine_handle<>) {
-#if INITIAL_AWAIT_SUSPEND_THROWS
+#ifdef INITIAL_AWAIT_SUSPEND_THROWS
                 throw X{};
 #endif
             }
             void await_resume() {
-#if INITIAL_AWAIT_RESUME_THROWS
-// this would be caught by unhandled_exception () which is tested
-// elsewhere.
+             PRINT("initial await_resume()");
+#ifdef INITIAL_AWAIT_RESUME_THROWS
+// This should be caught by unhandled_exception ()
                 throw X{};
 #endif
             }
         };
 
         awaiter initial_suspend() {
-#if INITIAL_SUSPEND_THROWS
+           PRINT("initial_suspend()");
+#ifdef INITIAL_SUSPEND_THROWS
             throw X{};
 #endif
             return {};
         }
 
         task get_return_object() {
-            std::puts("get_return_object()");
-#if GET_RETURN_OBJECT_THROWS
+            PRINT("get_return_object()");
+#ifdef GET_RETURN_OBJECT_THROWS
             throw X{};
 #endif
             return task{};
         }
 
-        std::suspend_never final_suspend() noexcept { return {}; }
-        void return_void() noexcept {}
-        void unhandled_exception() noexcept {
-            std::puts("unhandled_exception()");
+        std::suspend_never final_suspend() noexcept { 
+           PRINT("final_suspend()");
+           return {};
+        }
+        void return_void() {
+          PRINT("return_void()");
+        }
+        void unhandled_exception()  {
+            PRINT("unhandled_exception()");
+            unhandled_exception_called++;
+#ifdef UNHANDLED_EXCEPTION_THROWS
+           Z z{std::current_exception(),coro_handle::from_promise(*this)};
+           throw z;
+            //throw coro_handle::from_promise(*this);
+#endif
         }
     };
+   
+    task() noexcept {
+     PRINT("task()");
+     task_live++;
+    }
+    task(task&& t) noexcept {
+      PRINT("task(task&&)");
+      task_live++;
+    }
+    ~task() noexcept {
+      PRINT("~task()");
+      task_live--;
+    }
 
-    task() { std::puts("task()");  task_live++; }
-    ~task() { std::puts("~task()"); task_live--; }
-    task(task&&) { std::puts("task(task&&)"); task_live++;  }
 };
 
 task f(Y Val __attribute__((__unused__))) {
-    co_return;
+  PRINT("f()");
+#ifdef BODY_THROWS
+  throw X{};
+#endif
+  co_return;
 }
 
 int main() {
-    bool failed = false;
-    Y Val;
-    try {
-        f(Val);
-    } catch (X) {
-        std::puts("caught X");
-        if (task_live)
-          std::puts("task live"), failed = true;
-        if (promise_live)
-          std::puts("promise live"), failed = true;
-        if (frame_live)
-          std::puts("frame live"), failed = true;
-    }
-
+  bool failed = false;
+  Y Val;
+  try {
+    PRINT("try-block-s");
+    task tsk = f(Val);
+    PRINT("try-block-e");
+  } catch (X) {
+    PRINT("caught X");
+  } catch (task::Z& z) {
+    PRINTF("caught Z : %d\n",(bool)z.eptr);
+    if (z.h.done())
+      {
+        PRINT("done, cleaning up");
+        z.h.destroy ();
+      }
+    else
+      PRINT("not done?");
+  } catch (std::coroutine_handle<task::promise_type>& h) {
+    PRINT("caught handle");
+    if (h.done())
+      {
+        PRINT("done, cleaning up");
+        h.destroy ();
+      }
+    else
+      PRINT("not done?");
+  }
+
+  if (task_live)
+    failed = true, __builtin_printf("task still live\n") ;
+  if (promise_live)
+    failed = true, __builtin_printf("promise still live\n");
+  if (frame_live)
+    failed = true, __builtin_printf("frame still live : %d\n", frame_live);
   if (Y_live != 1)
-    std::printf("Y live %d\n", Y_live), failed = true;
+    failed = true, __builtin_printf("Y live count : %d\n", Y_live);
+#ifdef SHOULD_CALL_UNHANDLED_EXCEPTION
+  if (unhandled_exception_called != 1)
+    failed = true, __builtin_printf("unhandled exception : %d\n", 
unhandled_exception_called);
+#else
+  if (unhandled_exception_called != 0)
+    failed = true, __builtin_printf("unhandled exception : %d\n", 
unhandled_exception_called);
+#endif
+
+  __builtin_printf("checking result : %s \n",(failed?"bad":"good"));
+
+  fflush(stdout);
 
   if (failed)
-    abort() ;
+   __builtin_abort();
 }
-- 
2.39.2 (Apple Git-143)

Reply via email to