On 5/14/25 2:10 PM, Iain Sandoe wrote:
        that indicates we have not yet reached the ramp return.

This flag was not part of the fix on trunk, and could use more rationale.

The original fix was OK on trunk because exceptions thrown from the
return expression would happen before the initial suspend.  Having fixed
BZ199916 (which restores the state as per GCC-14) then such throws would
become indistinguishable.  Unfortunately, on many OSs that the frame is
destroyed does not become immediately obvious and testcases pass.  This
is why I included Sparc9 Solaris in the testing - there the frame
destruction does cause a fail.  So, for an implementation where the
return expr. can throw after the frame is destroyed the addition flag
is needed (I amended the patch desciption with an abbreviated comment).

+      gate = build2 (TRUTH_AND_EXPR, boolean_type_node, gate,
+                    coro_before_return);

Doesn't the order of operands to the && need to be the other way around,
to avoid checking iarc_x after the coro state has been destroyed?

Thanks for catching that, I need to check this on the trunk patch too.

OK now (after a retest)?

OK.

thanks
Iain

--- 8< ---

This is a GCC-14 version of the same strategy as used on trunk, but
with the more wide-ranging code cleanups elided.  Since the return
expression could throw, but after the frame is destroyed, we must
also account for this, in addition to whether initial await_resume
has been called.

        PR c++/113773

gcc/cp/ChangeLog:

        * coroutines.cc (coro_rewrite_function_body): Do not set
        initial_await_resume_called here.
        (morph_fn_to_coro): Set it here, and introduce a new flag
        that indicates we have not yet reached the ramp return.
        Gate the EH cleanups on both of these flags).

gcc/testsuite/ChangeLog:

        * g++.dg/coroutines/torture/pr113773.C: New test.

Signed-off-by: Iain Sandoe <i...@sandoe.co.uk>
---
  gcc/cp/coroutines.cc                          | 45 ++++++++++---
  .../g++.dg/coroutines/torture/pr113773.C      | 66 +++++++++++++++++++
  2 files changed, 102 insertions(+), 9 deletions(-)
  create mode 100644 gcc/testsuite/g++.dg/coroutines/torture/pr113773.C

diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc
index 8811d249c02..d96176973ec 100644
--- a/gcc/cp/coroutines.cc
+++ b/gcc/cp/coroutines.cc
@@ -4460,7 +4460,7 @@ coro_rewrite_function_body (location_t fn_start, tree 
fnbody, tree orig,
        tree i_a_r_c
        = coro_build_artificial_var (fn_start, coro_frame_i_a_r_c_id,
                                     boolean_type_node, orig,
-                                    boolean_false_node);
+                                    NULL_TREE);
        DECL_CHAIN (i_a_r_c) = var_list;
        var_list = i_a_r_c;
        add_decl_expr (i_a_r_c);
@@ -4779,10 +4779,15 @@ morph_fn_to_coro (tree orig, tree *resumer, tree 
*destroyer)
    tree coro_gro_live
      = coro_build_artificial_var (fn_start, "_Coro_gro_live",
                                 boolean_type_node, orig, boolean_false_node);
-
    DECL_CHAIN (coro_gro_live) = varlist;
    varlist = coro_gro_live;
+ tree coro_before_return
+    = coro_build_artificial_var (fn_start, "_Coro_before_return",
+                                boolean_type_node, orig, boolean_true_node);
+  DECL_CHAIN (coro_before_return) = varlist;
+  varlist = coro_before_return;
+
    /* Collected the scope vars we need ... only one for now. */
    BIND_EXPR_VARS (ramp_bind) = nreverse (varlist);
@@ -4811,6 +4816,7 @@ morph_fn_to_coro (tree orig, tree *resumer, tree *destroyer)
        }
    add_decl_expr (coro_promise_live);
    add_decl_expr (coro_gro_live);
+  add_decl_expr (coro_before_return);
/* The CO_FRAME internal function is a mechanism to allow the middle end
       to adjust the allocation in response to optimizations.  We provide the
@@ -4964,8 +4970,10 @@ morph_fn_to_coro (tree orig, tree *resumer, tree 
*destroyer)
tree allocated = build1 (CONVERT_EXPR, coro_frame_ptr, new_fn);
    tree r = cp_build_init_expr (coro_fp, allocated);
-  r = coro_build_cvt_void_expr_stmt (r, fn_start);
-  add_stmt (r);
+  finish_expr_stmt (r);
+
+  /* deref the frame pointer, to use in member access code.  */
+  tree deref_fp = build_x_arrow (fn_start, coro_fp, tf_warning_or_error);
/* If the user provided a method to return an object on alloc fail, then
       check the returned pointer and call the func if it's null.
@@ -5001,16 +5009,22 @@ morph_fn_to_coro (tree orig, tree *resumer, tree 
*destroyer)
       destruction in the case that promise or g.r.o setup fails or an exception
       is thrown from the initial suspend expression.  */
    tree ramp_cleanup = NULL_TREE;
+  tree iarc_x = NULL_TREE;
    if (flag_exceptions)
      {
+      iarc_x = lookup_member (coro_frame_type, coro_frame_i_a_r_c_id,
+                    /*protect=*/1, /*want_type=*/0, tf_warning_or_error);
+      iarc_x
+       = build_class_member_access_expr (deref_fp, iarc_x, NULL_TREE, false,
+                                         tf_warning_or_error);
+      r = cp_build_init_expr (iarc_x, boolean_false_node);
+      finish_expr_stmt (r);
+
        ramp_cleanup = build_stmt (fn_start, TRY_BLOCK, NULL, NULL);
        add_stmt (ramp_cleanup);
        TRY_STMTS (ramp_cleanup) = push_stmt_list ();
      }
- /* deref the frame pointer, to use in member access code. */
-  tree deref_fp = build_x_arrow (fn_start, coro_fp, tf_warning_or_error);
-
    /* For now, once allocation has succeeded we always assume that this needs
       destruction, there's no impl. for frame allocation elision.  */
    tree fnf_m = lookup_member (coro_frame_type, coro_frame_needs_free_id,
@@ -5018,8 +5032,7 @@ morph_fn_to_coro (tree orig, tree *resumer, tree 
*destroyer)
    tree fnf_x = build_class_member_access_expr (deref_fp, fnf_m, NULL_TREE,
                                               false, tf_warning_or_error);
    r = cp_build_init_expr (fnf_x, boolean_true_node);
-  r = coro_build_cvt_void_expr_stmt (r, fn_start);
-  add_stmt (r);
+  finish_expr_stmt (r);
/* Put the resumer and destroyer functions in. */ @@ -5305,6 +5318,11 @@ morph_fn_to_coro (tree orig, tree *resumer, tree *destroyer)
       either as the return value (if it's the same type) or to the CTOR
       for an object of the return type.  */
+ r = build_modify_expr (fn_start, coro_before_return, boolean_type_node,
+                        INIT_EXPR, fn_start, boolean_false_node,
+                        boolean_type_node);
+  finish_expr_stmt (r);
+
    /* We must manage the cleanups ourselves, because the responsibility for
       them changes after the initial suspend.  However, any use of
       cxx_maybe_build_cleanup () can set the throwing_cleanup flag.  */
@@ -5351,6 +5369,12 @@ morph_fn_to_coro (tree orig, tree *resumer, tree 
*destroyer)
          add_stmt (gro_d_if);
        }
+ tree gating_if = begin_if_stmt ();
+      tree gate = build1 (TRUTH_NOT_EXPR, boolean_type_node, iarc_x);
+      gate = build2 (TRUTH_AND_EXPR, boolean_type_node, coro_before_return,
+                    gate);
+      finish_if_stmt_cond (gate, gating_if);
+
        /* If the promise is live, then run its dtor if that's available.  */
        if (promise_dtor && promise_dtor != error_mark_node)
        {
@@ -5389,6 +5413,9 @@ morph_fn_to_coro (tree orig, tree *resumer, tree 
*destroyer)
        tree del_coro_fr = coro_get_frame_dtor (coro_fp, orig, frame_size,
                                              promise_type, fn_start);
        finish_expr_stmt (del_coro_fr);
+      finish_then_clause (gating_if);
+      finish_if_stmt (gating_if);
+
        tree rethrow = build_throw (fn_start, NULL_TREE, tf_warning_or_error);
        suppress_warning (rethrow);
        finish_expr_stmt (rethrow);
diff --git a/gcc/testsuite/g++.dg/coroutines/torture/pr113773.C 
b/gcc/testsuite/g++.dg/coroutines/torture/pr113773.C
new file mode 100644
index 00000000000..b048b0d63f4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/torture/pr113773.C
@@ -0,0 +1,66 @@
+//  { dg-do run }
+#include <coroutine>
+#ifdef OUTPUT
+#include <iostream>
+#endif
+
+struct result {
+  operator int() {
+    throw 42;
+  }
+};
+
+static int p_dtor_count = 0;
+class promise {
+public:
+  result get_return_object() { return {}; }
+  std::suspend_never initial_suspend() {
+#ifdef OUTPUT
+    std::cout << "initial suspend" << std::endl;
+#endif
+    return {};
+  }
+  void unhandled_exception() {
+#ifdef OUTPUT
+    std::cout << "unhandled exception" << std::endl;
+#endif
+  }
+  std::suspend_never final_suspend() noexcept {
+#ifdef OUTPUT
+    std::cout << "final suspend" << std::endl;
+#endif
+    return {};
+  }
+  void return_void() {}
+  ~promise() {
+    p_dtor_count++;
+#ifdef OUTPUT
+   std::cout << "~promise()" << std::endl;
+#endif
+  }
+};
+
+template <class... Args>
+struct std::coroutine_traits<int, Args...> {
+  using promise_type = promise;
+};
+
+int f() {
+  co_return;
+}
+
+int main() {
+  try {
+    f();
+  }
+  catch (int i) {
+    if (i != 42)
+      __builtin_abort ();
+#ifdef OUTPUT
+    std::cout << "caught 42" << std::endl;
+#endif
+  }
+  if (p_dtor_count != 1)
+    __builtin_abort ();
+  return 0;
+}

Reply via email to