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;
+}