gcc/cp/ChangeLog:
PR c++/95519
* coroutines.cc (struct coroutine_info):Add a field
to hold computed p.return_void expressions.
(coro_build_promise_expression): New.
(get_coroutine_return_void_expr): New.
(finish_co_yield_expr): Build the promise expression
using coro_build_promise_expression.
(finish_co_return_stmt): Likewise.
(build_init_or_final_await): Likewise.
(morph_fn_to_coro): Likewise, for several cases.
gcc/testsuite/ChangeLog:
PR c++/95519
* g++.dg/coroutines/torture/pr95519-00-return_void.C: New test.
* g++.dg/coroutines/torture/pr95519-01-initial-suspend.C: New test.
* g++.dg/coroutines/torture/pr95519-02-final_suspend.C: New test.
* g++.dg/coroutines/torture/pr95519-03-return-value.C: New test.
* g++.dg/coroutines/torture/pr95519-04-yield-value.C: New test.
* g++.dg/coroutines/torture/pr95519-05-gro.C: New test.
* g++.dg/coroutines/torture/pr95519-06-grooaf.C: New test.
* g++.dg/coroutines/torture/pr95519-07-unhandled-exception.C: New test.
---
gcc/cp/coroutines.cc | 195 ++++++++++--------
.../torture/pr95519-00-return_void.C | 63 ++++++
.../torture/pr95519-01-initial-suspend.C | 69 +++++++
.../torture/pr95519-02-final_suspend.C | 69 +++++++
.../torture/pr95519-03-return-value.C | 80 +++++++
.../torture/pr95519-04-yield-value.C | 84 ++++++++
.../coroutines/torture/pr95519-05-gro.C | 64 ++++++
.../coroutines/torture/pr95519-06-grooaf.C | 49 +++++
.../torture/pr95519-07-unhandled-exception.C | 69 +++++++
9 files changed, 656 insertions(+), 86 deletions(-)
create mode 100644
gcc/testsuite/g++.dg/coroutines/torture/pr95519-00-return_void.C
create mode 100644
gcc/testsuite/g++.dg/coroutines/torture/pr95519-01-initial-suspend.C
create mode 100644
gcc/testsuite/g++.dg/coroutines/torture/pr95519-02-final_suspend.C
create mode 100644
gcc/testsuite/g++.dg/coroutines/torture/pr95519-03-return-value.C
create mode 100644
gcc/testsuite/g++.dg/coroutines/torture/pr95519-04-yield-value.C
create mode 100644 gcc/testsuite/g++.dg/coroutines/torture/pr95519-05-gro.C
create mode 100644 gcc/testsuite/g++.dg/coroutines/torture/pr95519-06-grooaf.C
create mode 100644
gcc/testsuite/g++.dg/coroutines/torture/pr95519-07-unhandled-exception.C
diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc
index ba88b992a83..8b8d00e8e0c 100644
--- a/gcc/cp/coroutines.cc
+++ b/gcc/cp/coroutines.cc
@@ -88,6 +88,7 @@ struct GTY((for_user)) coroutine_info
one that will eventually be allocated in the coroutine
frame. */
tree promise_proxy; /* Likewise, a proxy promise instance. */
+ tree return_void; /* The expression for p.return_void() if it exists. */
location_t first_coro_keyword; /* The location of the keyword that made this
function into a coroutine. */
/* Flags to avoid repeated errors for per-function issues. */
@@ -554,6 +555,67 @@ lookup_promise_method (tree fndecl, tree member_id,
location_t loc,
return pm_memb;
}
+/* Build an expression of the form p.method (args) where the p is a promise
+ object for the current coroutine.
+ OBJECT is the promise object instance to use, it may be NULL, in which case
+ we will use the promise_proxy instance for this coroutine.
+ ARGS may be NULL, for empty parm lists. */
+
+static tree
+coro_build_promise_expression (tree fn, tree promise_obj, tree member_id,
+ location_t loc, vec<tree, va_gc> **args,
+ bool musthave)
+{
+ tree meth = lookup_promise_method (fn, member_id, loc, musthave);
+ if (meth == error_mark_node)
+ return error_mark_node;
+
+ /* If we don't find it, and it isn't needed, an empty return is OK. */
+ if (!meth)
+ return NULL_TREE;
+
+ tree promise
+ = promise_obj ? promise_obj
+ : get_coroutine_promise_proxy (current_function_decl);
+ tree expr;
+ if (BASELINK_P (meth))
+ expr = build_new_method_call (promise, meth, args, NULL_TREE,
+ LOOKUP_NORMAL, NULL, tf_warning_or_error);
+ else
+ {
+ expr = build_class_member_access_expr (promise, meth, NULL_TREE,
+ true, tf_warning_or_error);
+ vec<tree, va_gc> *real_args;
+ if (!args)
+ real_args = make_tree_vector ();
+ else
+ real_args = *args;
+ expr = build_op_call (expr, &real_args, tf_warning_or_error);
+ }
+ return expr;
+}
+
+/* Caching get for the expression p.return_void (). */
+
+static tree
+get_coroutine_return_void_expr (tree decl, location_t loc, bool musthave)
+{
+ if (coroutine_info *info = get_coroutine_info (decl))
+ {
+ /* If we don't have it try to build it. */
+ if (!info->return_void)
+ info->return_void
+ = coro_build_promise_expression (current_function_decl, NULL,
+ coro_return_void_identifier,
+ loc, NULL, musthave);
+ /* Don't return an error if it's an optional call. */
+ if (!musthave && info->return_void == error_mark_node)
+ return NULL_TREE;
+ return info->return_void;
+ }
+ return musthave ? error_mark_node : NULL_TREE;
+}
+
/* Lookup an Awaitable member, which should be await_ready, await_suspend
or await_resume. */
@@ -943,23 +1005,17 @@ finish_co_yield_expr (location_t kw, tree expr)
the promise type, and obtain its return type. */
return error_mark_node;
- /* The incoming expr is "e" per [expr.yield] para 1, lookup and build a
- call for p.yield_value(e). */
- tree y_meth = lookup_promise_method (current_function_decl,
- coro_yield_value_identifier, kw,
- /*musthave=*/true);
- if (!y_meth || y_meth == error_mark_node)
- return error_mark_node;
-
/* [expr.yield] / 1
Let e be the operand of the yield-expression and p be an lvalue naming
the promise object of the enclosing coroutine, then the yield-expression
is equivalent to the expression co_await p.yield_value(e).
build p.yield_value(e): */
vec<tree, va_gc> *args = make_tree_vector_single (expr);
- tree yield_call = build_new_method_call
- (get_coroutine_promise_proxy (current_function_decl), y_meth, &args,
- NULL_TREE, LOOKUP_NORMAL, NULL, tf_warning_or_error);
+ tree yield_call
+ = coro_build_promise_expression (current_function_decl, NULL,
+ coro_yield_value_identifier, kw,
+ &args, /*musthave=*/true);
+ release_tree_vector (args);
/* Now build co_await p.yield_value (e).
Noting that for co_yield, there is no evaluation of any potential
@@ -1063,27 +1119,10 @@ finish_co_return_stmt (location_t kw, tree expr)
there's a mis-match between the co_return <expr> and this. */
tree co_ret_call = error_mark_node;
if (expr == NULL_TREE || VOID_TYPE_P (TREE_TYPE (expr)))
- {
- tree crv_meth
- = lookup_promise_method (current_function_decl,
- coro_return_void_identifier, kw,
- /*musthave=*/true);
- if (crv_meth == error_mark_node)
- return error_mark_node;
-
- co_ret_call = build_new_method_call (
- get_coroutine_promise_proxy (current_function_decl), crv_meth, NULL,
- NULL_TREE, LOOKUP_NORMAL, NULL, tf_warning_or_error);
- }
+ co_ret_call
+ = get_coroutine_return_void_expr (current_function_decl, kw, true);
else
{
- tree crv_meth
- = lookup_promise_method (current_function_decl,
- coro_return_value_identifier, kw,
- /*musthave=*/true);
- if (crv_meth == error_mark_node)
- return error_mark_node;
-
/* [class.copy.elision] / 3.
An implicitly movable entity is a variable of automatic storage
duration that is either a non-volatile object or an rvalue reference
@@ -1096,21 +1135,24 @@ finish_co_return_stmt (location_t kw, tree expr)
&& CLASS_TYPE_P (TREE_TYPE (expr))
&& !TYPE_VOLATILE (TREE_TYPE (expr)))
{
- vec<tree, va_gc> *args = make_tree_vector_single (move (expr));
/* It's OK if this fails... */
- co_ret_call = build_new_method_call
- (get_coroutine_promise_proxy (current_function_decl), crv_meth,
- &args, NULL_TREE, LOOKUP_NORMAL|LOOKUP_PREFER_RVALUE,
- NULL, tf_none);
+ vec<tree, va_gc> *args = make_tree_vector_single (move (expr));
+ co_ret_call
+ = coro_build_promise_expression (current_function_decl, NULL,
+ coro_return_value_identifier, kw,
+ &args, /*musthave=*/false);
+ release_tree_vector (args);
}
- if (co_ret_call == error_mark_node)
+ if (!co_ret_call || co_ret_call == error_mark_node)
{
- vec<tree, va_gc> *args = make_tree_vector_single (expr);
/* ... but this must succeed if we didn't get the move variant. */
- co_ret_call = build_new_method_call
- (get_coroutine_promise_proxy (current_function_decl), crv_meth,
- &args, NULL_TREE, LOOKUP_NORMAL, NULL, tf_warning_or_error);
+ vec<tree, va_gc> *args = make_tree_vector_single (expr);
+ co_ret_call
+ = coro_build_promise_expression (current_function_decl, NULL,
+ coro_return_value_identifier, kw,
+ &args, /*musthave=*/true);
+ release_tree_vector (args);
}
}
@@ -2585,23 +2627,18 @@ get_fn_local_identifier (tree orig, const char *append)
return get_identifier (an);
}
+/* Build an initial or final await initialized from the promise
+ initial_suspend or final_suspend expression. */
+
static tree
build_init_or_final_await (location_t loc, bool is_final)
{
tree suspend_alt = is_final ? coro_final_suspend_identifier
: coro_initial_suspend_identifier;
- tree setup_meth = lookup_promise_method (current_function_decl, suspend_alt,
- loc, /*musthave=*/true);
- if (!setup_meth || setup_meth == error_mark_node)
- return error_mark_node;
- tree s_fn = NULL_TREE;
- tree setup_call = build_new_method_call (
- get_coroutine_promise_proxy (current_function_decl), setup_meth, NULL,
- NULL_TREE, LOOKUP_NORMAL, &s_fn, tf_warning_or_error);
-
- if (!s_fn || setup_call == error_mark_node)
- return error_mark_node;
+ tree setup_call
+ = coro_build_promise_expression (current_function_decl, NULL, suspend_alt,
+ loc, NULL, /*musthave=*/true);
/* So build the co_await for this */
/* For initial/final suspends the call is "a" per [expr.await] 3.2. */
@@ -3918,22 +3955,17 @@ morph_fn_to_coro (tree orig, tree *resumer, tree
*destroyer)
The unqualified-id get_return_object_on_allocation_failure is looked up
in the scope of the promise type by class member access lookup. */
- tree grooaf_meth
- = lookup_promise_method (orig, coro_gro_on_allocation_fail_identifier,
- fn_start, /*musthave=*/false);
-
- tree grooaf = NULL_TREE;
- tree dummy_promise = build_dummy_object (get_coroutine_promise_type (orig));
-
- /* We don't require this, so lookup_promise_method can return NULL,
+ /* We don't require this, so coro_build_promise_expression can return NULL,
but, if the lookup succeeds, then the function must be usable. */
- if (grooaf_meth && BASELINK_P (grooaf_meth))
- grooaf = build_new_method_call (dummy_promise, grooaf_meth, NULL,
- NULL_TREE, LOOKUP_NORMAL, NULL,
- tf_warning_or_error);
-
- /* ... but if that fails, returning an error, the later stages can't handle
- the erroneous expression, so we reset the call as if it was absent. */
+ tree dummy_promise = build_dummy_object (get_coroutine_promise_type (orig));
+ tree grooaf
+ = coro_build_promise_expression (orig, dummy_promise,
+ coro_gro_on_allocation_fail_identifier,
+ fn_start, NULL, /*musthave=*/false);
+
+ /* however, should that fail, returning an error, the later stages can't
+ handle the erroneous expression, so we reset the call as if it was
+ absent. */
if (grooaf == error_mark_node)
grooaf = NULL_TREE;
@@ -3948,7 +3980,7 @@ morph_fn_to_coro (tree orig, tree *resumer, tree *destroyer)
if (TYPE_HAS_NEW_OPERATOR (promise_type))
{
tree fns = lookup_promise_method (orig, nwname, fn_start,
- /*musthave=*/true);
+ /*musthave=*/true);
/* [dcl.fct.def.coroutine] / 9 (part 2)
If the lookup finds an allocation function in the scope of the promise
type, overload resolution is performed on a function call created by
@@ -4278,12 +4310,10 @@ morph_fn_to_coro (tree orig, tree *resumer, tree
*destroyer)
BIND_EXPR_BLOCK (gro_context_bind) = gro_block;
add_stmt (gro_context_bind);
- tree gro_meth = lookup_promise_method (orig,
- coro_get_return_object_identifier,
- fn_start, /*musthave=*/true );
tree get_ro
- = build_new_method_call (p, gro_meth, NULL, NULL_TREE, LOOKUP_NORMAL, NULL,
- tf_warning_or_error);
+ = coro_build_promise_expression (orig, p,
+ coro_get_return_object_identifier,
+ fn_start, NULL, /*musthave=*/true);
/* Without a return object we haven't got much clue what's going on. */
if (get_ro == error_mark_node)
{
@@ -4495,14 +4525,10 @@ morph_fn_to_coro (tree orig, tree *resumer, tree
*destroyer)
Calls to return_value () will have to be checked and created as
required. */
- tree return_void = NULL_TREE;
- tree rvm
- = lookup_promise_method (orig, coro_return_void_identifier, fn_start,
- /*musthave=*/false);
- if (rvm && rvm != error_mark_node)
- return_void
- = build_new_method_call (ap, rvm, NULL, NULL_TREE, LOOKUP_NORMAL, NULL,
- tf_warning_or_error);
+ tree return_void
+ = coro_build_promise_expression (current_function_decl, ap,
+ coro_return_void_identifier,
+ fn_start, NULL, /*musthave=*/false);
/* [stmt.return.coroutine] (2.2 : 3) if p.return_void() is a valid
expression, flowing off the end of a coroutine is equivalent to
@@ -4520,13 +4546,10 @@ morph_fn_to_coro (tree orig, tree *resumer, tree
*destroyer)
if (flag_exceptions)
{
- tree ueh_meth
- = lookup_promise_method (orig, coro_unhandled_exception_identifier,
- fn_start, /*musthave=*/true);
- /* Build promise.unhandled_exception(); */
tree ueh
- = build_new_method_call (ap, ueh_meth, NULL, NULL_TREE, LOOKUP_NORMAL,
- NULL, tf_warning_or_error);
+ = coro_build_promise_expression (current_function_decl, ap,
+ coro_unhandled_exception_identifier,
+ fn_start, NULL, /*musthave=*/true);
/* The try block is just the original function, there's no real
need to call any function to do this. */
diff --git a/gcc/testsuite/g++.dg/coroutines/torture/pr95519-00-return_void.C
b/gcc/testsuite/g++.dg/coroutines/torture/pr95519-00-return_void.C
new file mode 100644
index 00000000000..2952d011674
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/torture/pr95519-00-return_void.C
@@ -0,0 +1,63 @@
+// { dg-do run }
+
+#include "../coro.h"
+
+struct pt_b
+{
+ std::suspend_never initial_suspend() const noexcept { return {}; }
+ std::suspend_never final_suspend() const noexcept { return {}; }
+ void unhandled_exception() const noexcept {}
+};
+
+int called_rv_op = 0;
+struct rv
+{
+ void operator ()(){
+ PRINT("call to operator ");
+ called_rv_op++;
+ }
+};
+
+struct pt_c : pt_b
+{
+ using handle_t = std::coroutine_handle<pt_c>;
+ auto get_return_object() noexcept { return handle_t::from_promise(*this); }
+ rv return_void;
+};
+
+int called_lambda = 0;
+
+struct pt_d : pt_b
+{
+ using handle_t = std::coroutine_handle<pt_d>;
+ auto get_return_object() noexcept { return handle_t::from_promise(*this); }
+ static constexpr auto return_void
+ = []{ PRINT("call to lambda "); called_lambda++; };
+};
+
+template <> struct std::coroutine_traits<pt_c::handle_t>
+ { using promise_type = pt_c; };
+
+static pt_c::handle_t foo ()
+{
+ co_return;
+}
+
+template <> struct std::coroutine_traits<pt_d::handle_t>
+ { using promise_type = pt_d; };
+
+static pt_d::handle_t bar ()
+{
+ co_return;
+}
+
+int main ()
+{
+ foo ();
+ bar ();
+ if (called_rv_op != 1 || called_lambda != 1)
+ {
+ PRINT ("Failed to call one of the return_void cases");
+ abort ();
+ }
+}
diff --git
a/gcc/testsuite/g++.dg/coroutines/torture/pr95519-01-initial-suspend.C
b/gcc/testsuite/g++.dg/coroutines/torture/pr95519-01-initial-suspend.C
new file mode 100644
index 00000000000..346c20dbd8f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/torture/pr95519-01-initial-suspend.C
@@ -0,0 +1,69 @@
+// { dg-do run }
+
+#include "../coro.h"
+
+struct pt_b
+{
+ std::suspend_never final_suspend() const noexcept { return {}; }
+ constexpr void return_void () noexcept {};
+ void unhandled_exception() const noexcept {}
+};
+
+int called_is_op = 0;
+struct is
+{
+ std::suspend_never operator ()() noexcept {
+ PRINT("call to operator IS");
+ called_is_op++;
+ return {};
+ }
+};
+
+struct pt_c : pt_b
+{
+ using handle_t = std::coroutine_handle<pt_c>;
+ auto get_return_object() noexcept { return handle_t::from_promise(*this); }
+ is initial_suspend;
+};
+
+int called_lambda = 0;
+struct pt_d : pt_b
+{
+ using handle_t = std::coroutine_handle<pt_d>;
+ auto get_return_object() noexcept { return handle_t::from_promise(*this); }
+ static constexpr auto initial_suspend
+ = []() noexcept {
+ PRINT("call to lambda IS");
+ called_lambda++;
+ return std::suspend_never{};
+ };
+};
+
+template <>
+struct std::coroutine_traits<pt_c::handle_t>
+{ using promise_type = pt_c; };
+
+static pt_c::handle_t foo ()
+{
+ co_return;
+}
+
+template <>
+struct std::coroutine_traits<pt_d::handle_t>
+{ using promise_type = pt_d; };
+
+static pt_d::handle_t bar ()
+{
+ co_return;
+}
+
+int main ()
+{
+ foo ();
+ bar ();
+ if (called_is_op != 1 || called_lambda != 1)
+ {
+ PRINT ("Failed to call one of the initial_suspend cases");
+ abort ();
+ }
+}
diff --git a/gcc/testsuite/g++.dg/coroutines/torture/pr95519-02-final_suspend.C
b/gcc/testsuite/g++.dg/coroutines/torture/pr95519-02-final_suspend.C
new file mode 100644
index 00000000000..8e7ba11c723
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/torture/pr95519-02-final_suspend.C
@@ -0,0 +1,69 @@
+// { dg-do run }
+
+#include "../coro.h"
+
+struct pt_b
+{
+ std::suspend_never initial_suspend() const noexcept { return {}; }
+ constexpr void return_void () noexcept {};
+ void unhandled_exception() const noexcept {}
+};
+
+int called_fs_op = 0;
+struct fs
+{
+ auto operator ()() noexcept {
+ PRINT("call to operator FS");
+ called_fs_op++;
+ return std::suspend_never{};
+ }
+};
+
+struct pt_c : pt_b
+{
+ using handle_t = std::coroutine_handle<pt_c>;
+ auto get_return_object() noexcept { return handle_t::from_promise(*this); }
+ fs final_suspend;
+};
+
+int called_lambda = 0;
+struct pt_d : pt_b
+{
+ using handle_t = std::coroutine_handle<pt_d>;
+ auto get_return_object() noexcept { return handle_t::from_promise(*this); }
+ constexpr static auto final_suspend
+ = []() noexcept {
+ PRINT("call to lambda FS");
+ called_lambda++;
+ return std::suspend_never{};
+ };
+};
+
+template <>
+struct std::coroutine_traits<pt_c::handle_t>
+{ using promise_type = pt_c; };
+
+static pt_c::handle_t foo ()
+{
+ co_return;
+}
+
+template <>
+struct std::coroutine_traits<pt_d::handle_t>
+{ using promise_type = pt_d; };
+
+static pt_d::handle_t bar ()
+{
+ co_return;
+}
+
+int main ()
+{
+ foo ();
+ bar ();
+ if (called_fs_op != 1 || called_lambda != 1)
+ {
+ PRINT ("Failed to call one of the initial_suspend cases");
+ abort ();
+ }
+}
diff --git a/gcc/testsuite/g++.dg/coroutines/torture/pr95519-03-return-value.C
b/gcc/testsuite/g++.dg/coroutines/torture/pr95519-03-return-value.C
new file mode 100644
index 00000000000..7d01509001c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/torture/pr95519-03-return-value.C
@@ -0,0 +1,80 @@
+// { dg-do run }
+
+#include "../coro.h"
+
+struct pt_b
+{
+ std::suspend_never initial_suspend() const noexcept { return {}; }
+ std::suspend_never final_suspend() const noexcept { return {}; }
+ void unhandled_exception() const noexcept {}
+};
+
+int called_rv_op = 0;
+int v;
+
+struct pt_c : pt_b
+{
+ struct rv
+ {
+ void operator ()(int x){
+ PRINTF("call to operator ret val with %d\n", x);
+ called_rv_op++;
+ v = x;
+// int val () { return x; }
+ }
+ };
+ using handle_t = std::coroutine_handle<pt_c>;
+ auto get_return_object() noexcept { return handle_t::from_promise(*this); }
+ rv return_value;
+};
+
+int called_lambda = 0;
+
+struct pt_d : pt_b
+{
+ using handle_t = std::coroutine_handle<pt_d>;
+ auto get_return_object() noexcept { return handle_t::from_promise(*this); }
+ static constexpr auto return_value
+ = [] (int x) { PRINTF("call to lambda ret val %d\n", x); called_lambda++;
v = x;};
+};
+
+template <> struct std::coroutine_traits<pt_c::handle_t>
+ { using promise_type = pt_c; };
+
+static pt_c::handle_t foo ()
+{
+ co_return 5;
+}
+
+template <> struct std::coroutine_traits<pt_d::handle_t>
+ { using promise_type = pt_d; };
+
+static pt_d::handle_t bar ()
+{
+ co_return 3;
+}
+
+int main ()
+{
+ /* These 'coroutines' run to completion imediately, like a regular fn. */
+ foo ();
+ if (v != 5)
+ {
+ PRINT ("foo failed to set v");
+ abort ();
+ }
+
+ bar ();
+ if (v != 3)
+ {
+ PRINT ("bar failed to set v");
+ abort ();
+ }
+
+
+ if (called_rv_op != 1 || called_lambda != 1)
+ {
+ PRINT ("Failed to call one of the return_void cases");
+ abort ();
+ }
+}
diff --git a/gcc/testsuite/g++.dg/coroutines/torture/pr95519-04-yield-value.C
b/gcc/testsuite/g++.dg/coroutines/torture/pr95519-04-yield-value.C
new file mode 100644
index 00000000000..d09e5ba1075
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/torture/pr95519-04-yield-value.C
@@ -0,0 +1,84 @@
+// { dg-do run }
+
+#include "../coro.h"
+
+struct pt_b
+{
+ std::suspend_never initial_suspend() const noexcept { return {}; }
+ std::suspend_never final_suspend() const noexcept { return {}; }
+ void unhandled_exception() const noexcept {}
+ constexpr static void return_void () noexcept {}
+};
+
+int called_yv_op = 0;
+int v;
+
+struct pt_c : pt_b
+{
+ struct yv
+ {
+ auto operator ()(int x){
+ PRINTF("call to operator yield val with %d\n", x);
+ called_yv_op++;
+ v = x;
+ return std::suspend_never{};
+ }
+ };
+ using handle_t = std::coroutine_handle<pt_c>;
+ auto get_return_object() noexcept { return handle_t::from_promise(*this); }
+ yv yield_value;
+};
+
+int called_lambda = 0;
+struct pt_d : pt_b
+{
+ using handle_t = std::coroutine_handle<pt_d>;
+ auto get_return_object() noexcept { return handle_t::from_promise(*this); }
+ static constexpr auto yield_value = [] (int x) -> std::suspend_never
+ {
+ PRINTF("call to lambda yield val %d\n", x);
+ called_lambda++;
+ v = x;
+ return {};
+ };
+};
+
+template <> struct std::coroutine_traits<pt_c::handle_t>
+ { using promise_type = pt_c; };
+
+static pt_c::handle_t foo ()
+{
+ co_yield 5;
+}
+
+template <> struct std::coroutine_traits<pt_d::handle_t>
+ { using promise_type = pt_d; };
+
+static pt_d::handle_t bar ()
+{
+ co_yield 3;
+}
+
+int main ()
+{
+ /* These 'coroutines' run to completion imediately, like a regular fn. */
+ foo ();
+ if (v != 5)
+ {
+ PRINT ("foo failed to yield v");
+ abort ();
+ }
+
+ bar ();
+ if (v != 3)
+ {
+ PRINT ("bar failed to yield v");
+ abort ();
+ }
+
+ if (called_yv_op != 1 || called_lambda != 1)
+ {
+ PRINT ("Failed to call one of the return_void cases");
+ abort ();
+ }
+}
diff --git a/gcc/testsuite/g++.dg/coroutines/torture/pr95519-05-gro.C
b/gcc/testsuite/g++.dg/coroutines/torture/pr95519-05-gro.C
new file mode 100644
index 00000000000..ba0a1e3dc60
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/torture/pr95519-05-gro.C
@@ -0,0 +1,64 @@
+// { dg-do run }
+
+#include "../coro.h"
+
+struct pt_b
+{
+ std::suspend_always initial_suspend() const noexcept { return {}; }
+ std::suspend_never final_suspend() const noexcept { return {}; }
+ constexpr void return_void () noexcept {};
+ constexpr void unhandled_exception() const noexcept {}
+};
+
+int called_gro_op = 0;
+
+template<typename R, typename HandleRef, typename ...T>
+struct std::coroutine_traits<R, HandleRef, T...> {
+ struct pt_c;
+ using promise_type = pt_c;
+ struct pt_c : pt_b {
+ //using handle_t = std::coroutine_handle<pt_c>;
+ pt_c (HandleRef h, T ...args)
+ { h = std::coroutine_handle<pt_c>::from_promise (*this);
+ PRINT ("Created Promise");
+ //g_promise = 1;
+ }
+ struct gro
+ {
+ auto operator ()() {
+ PRINT("call to operator ");
+ called_gro_op++;
+ }
+ };
+ gro get_return_object;
+ };
+};
+
+static void
+foo (std::coroutine_handle<>& h)
+{
+ co_return;
+}
+
+int main ()
+{
+ std::coroutine_handle<> f;
+ foo (f);
+ if (f.done())
+ {
+ PRINT ("unexptected finished f coro");
+ abort ();
+ }
+ f.resume();
+ if (!f.done())
+ {
+ PRINT ("expected f to be finished");
+ abort ();
+ }
+
+ if (called_gro_op != 1)
+ {
+ PRINT ("Failed to call gro op");
+ abort ();
+ }
+}
diff --git a/gcc/testsuite/g++.dg/coroutines/torture/pr95519-06-grooaf.C
b/gcc/testsuite/g++.dg/coroutines/torture/pr95519-06-grooaf.C
new file mode 100644
index 00000000000..b6b6bd73c9a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/torture/pr95519-06-grooaf.C
@@ -0,0 +1,49 @@
+// { dg-do run }
+
+#include "../coro.h"
+
+struct pt_b
+{
+ constexpr std::suspend_never initial_suspend() noexcept { return {}; }
+ constexpr std::suspend_never final_suspend() noexcept { return {}; }
+ constexpr void return_void () noexcept {}
+ constexpr void unhandled_exception() noexcept {}
+};
+
+int called_grooaf = 0;
+
+struct pt_c : pt_b
+{
+ using handle_t = std::coroutine_handle<pt_c>;
+ auto get_return_object() noexcept { return handle_t::from_promise(*this); }
+
+ static constexpr auto get_return_object_on_allocation_failure
+ = []{ PRINT("call to lambda grooaf");
+ called_grooaf++; return std::coroutine_handle<pt_c>{};
+ };
+
+ /* Provide an operator new, that always fails. */
+ void *operator new (std::size_t sz) noexcept {
+ PRINT ("promise_type: used failing op new");
+ return nullptr;
+ }
+};
+
+template <> struct std::coroutine_traits<pt_c::handle_t>
+ { using promise_type = pt_c; };
+
+static pt_c::handle_t
+foo ()
+{
+ co_return;
+}
+
+int main ()
+{
+ foo ();
+ if (called_grooaf != 1)
+ {
+ PRINT ("Failed to call grooaf");
+ abort ();
+ }
+}
diff --git
a/gcc/testsuite/g++.dg/coroutines/torture/pr95519-07-unhandled-exception.C
b/gcc/testsuite/g++.dg/coroutines/torture/pr95519-07-unhandled-exception.C
new file mode 100644
index 00000000000..be2a928ea00
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/torture/pr95519-07-unhandled-exception.C
@@ -0,0 +1,69 @@
+// { dg-do run }
+
+#include "../coro.h"
+
+struct pt_b
+{
+ constexpr std::suspend_never initial_suspend() const noexcept { return {}; }
+ constexpr std::suspend_never final_suspend() const noexcept { return {}; }
+ constexpr void return_void () {};
+};
+
+int called_ueh_op = 0;
+struct ueh
+{
+ void operator ()() noexcept {
+ PRINT("call to operator UEH");
+ called_ueh_op++;
+ }
+};
+
+struct pt_c : pt_b
+{
+ using handle_t = std::coroutine_handle<pt_c>;
+ auto get_return_object() noexcept { return handle_t::from_promise(*this); }
+ ueh unhandled_exception;
+};
+
+int lambda_ueh = 0;
+
+struct pt_d : pt_b
+{
+ using handle_t = std::coroutine_handle<pt_d>;
+ auto get_return_object() noexcept { return handle_t::from_promise(*this); }
+ static constexpr auto unhandled_exception
+ = [] () noexcept { PRINT("call to lambda UEH"); lambda_ueh++; };
+};
+
+template <>
+struct std::coroutine_traits<pt_c::handle_t>
+ { using promise_type = pt_c; };
+
+static pt_c::handle_t
+foo ()
+{
+ throw ("foo");
+ co_return;
+}
+
+template <>
+struct std::coroutine_traits<pt_d::handle_t>
+ { using promise_type = pt_d; };
+
+static pt_d::handle_t
+bar ()
+{
+ throw ("bar");
+ co_return;
+}
+
+int main ()
+{
+ foo ();
+ bar ();
+ if (called_ueh_op != 1 || lambda_ueh != 1)
+ {
+ PRINT ("Failed to call one of the UEH cases");
+ abort ();
+ }
+}