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)