This could not be done as a cherry-pick from the trunk resolution.
Tested on x86_64-darwin, powerpc64le linux sparc9 solaris,
OK for 14.3 ?
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.

        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..58e2da3201d 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, gate,
+                    coro_before_return);
+      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;
+}
-- 
2.39.2 (Apple Git-143)

Reply via email to