On 8/1/24 12:34 PM, Arsen Arsenović wrote:
> Tested on x86_64-pc-linux-gnu.
>
> OK for trunk?
>
> TIA, have a lovely day.
> ---------- >8 ----------
> Currently, it is possible to ICE GCC by giving it sufficiently broken
> code, where sufficiently broken means a std::coroutine_handle missing a
> default on the promise_type template argument, and missing members.
> As the code generator relies on lookups in the coroutine_handle never
> failing (and has no way to signal that error), lets do it ahead of time,
> save the result, and use that. This saves us some lookups and allows us
> to propagate an error.
>
> PR c++/105475 - coroutines: ICE in coerce_template_parms, at cp/pt.cc:9183
>
> gcc/cp/ChangeLog:
>
> PR c++/105475
> * coroutines.cc (struct coroutine_info): Add from_address_bl.
> Carries the from_address member we looked up earlier.
> (coro_resume_identifier): Remove. Unused.
> (coro_init_identifiers): Do not initialize the above.
> (void_coro_handle_address): New variable. Contains the baselink
> for the std::coroutine_handle<void>::address() instance method.
> (get_handle_type_address_bl): New function. Looks up and
> validates handle_type::address in a given handle_type.
> (get_handle_type_from_address_bl): New function. Looks up and
> validates handle_type::from_address in a given handle_type.
> (ensure_coro_initialized):Remove reliance on
> coroutine_handle<> defaulting the promise type to void.
> Initialize void_coro_handle_address with
> get_handle_type_from_address_bl.
> (coro_promise_type_found_p): Initialize
> coro_info->from_address_bl with the result of
> get_handle_type_from_address_bl.
> (get_coroutine_from_address_baselink): New helper. Gets the
> handle_type::from_address BASELINK from a coroutine_info.
> (build_actor_fn): Use the get_coroutine_from_address_baselink
> helper and void_coro_handle_address.
I don't think these names need to mention "baselink", but I'm not
strongly against it.
A few other tweaks below:
> @@ -90,6 +90,7 @@ struct GTY((for_user)) coroutine_info
> tree self_h_proxy; /* A handle instance that is used as the proxy for the
> one that will eventually be allocated in the coroutine
> frame. */
> + tree from_address_bl; /* handle_type from_address function (BASELINK). */
Inserting this here breaks the "Likewise" reference in the next line.
> 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
> @@ -389,7 +389,105 @@ find_coro_handle_template_decl (location_t kw)
> return handle_decl;
> }
>
> -/* Instantiate the handle template for a given promise type. */
> +/* Get and validate HANDLE_TYPE#address. The resulting function, if any, will
:: instead of #
> + be a non-overloaded member function that takes no arguments and returns
> + void*. If that is not the case, signals an error and returns NULL_TREE. */
> +
> +static tree
> +get_handle_type_address_bl (location_t kw, tree handle_type)
> +{
> + tree addr_getter = lookup_member (handle_type, coro_address_identifier, 1,
> + 0, tf_warning_or_error);
> + if (!addr_getter || addr_getter == error_mark_node)
> + {
> + error_at (kw, "could not find %qE in %qT",
> + coro_address_identifier, handle_type);
How about using qualified_name_lookup_error?
> + gcc_assert (TREE_CODE (addr_getter) == BASELINK);
This looks like it will ICE on an invalid handle type (e.g. where
"address" is a type or data member) instead of giving an error.
> + tree fn_t = TREE_TYPE (addr_getter);
> + if (TREE_CODE (fn_t) != METHOD_TYPE)
> + {
> + error_at (kw, "%qE must be a non-overloaded method", addr_getter);
> + return NULL_TREE;
> + }
> +
> + tree arg = TYPE_ARG_TYPES (fn_t);
> + /* Given that this is a method, we have an argument to skip (the this
> + pointer). */
> + gcc_checking_assert (TREE_CHAIN (arg));
This assert seems redundant with the following checks.
> + arg = TREE_CHAIN (arg);
> +
> + /* Check that from_addr has the argument list (). */
> + if (!arg
> + || !same_type_p (TREE_VALUE (arg), void_type_node)
> + || TREE_CHAIN (arg))
> + {
> + error_at (kw, "%qE must take no arguments", addr_getter);
> + return NULL_TREE;
> + }
I think this could just be
if (arg != void_list_node)
as we share the terminal void between all non-varargs function types.
> + tree ret_t = TREE_TYPE (fn_t);
> + if (!same_type_p (ret_t, ptr_type_node))
> + {
> + error_at (kw, "%qE must return %qT, not %qT",
> + addr_getter, ptr_type_node, ret_t);
> + return NULL_TREE;
> + }
Do you also want to check that it has public access?
> @@ -454,10 +552,15 @@ ensure_coro_initialized (location_t loc)
>
> /* We can also instantiate the void coroutine_handle<> */
> void_coro_handle_type =
> - instantiate_coro_handle_for_promise_type (loc, NULL_TREE);
> + instantiate_coro_handle_for_promise_type (loc, void_type_node);
> if (void_coro_handle_type == NULL_TREE)
> return false;
>
> + void_coro_handle_address =
> + get_handle_type_address_bl (loc, void_coro_handle_type);
The = should be at the beginning of the second line for both variables.
> @@ -2365,8 +2480,9 @@ build_actor_fn (location_t loc, tree coro_frame_type, tree actor, tree fnbody,
> tree ash = build_class_member_access_expr (actor_frame, ash_m, NULL_TREE,
> false, tf_warning_or_error);
> /* So construct the self-handle from the frame address. */
> - tree hfa_m = lookup_member (handle_type, coro_from_address_identifier, 1,
> - 0, tf_warning_or_error);
> + tree hfa_m = get_coroutine_from_address_baselink (orig);
> + /* Should have been set earlier by coro_promise_type_found_p. */
> + gcc_assert (hfa_m);
Maybe move this assert into the new get function?
Jason
@@ -90,6 +90,7 @@ struct GTY((for_user)) coroutine_info
tree self_h_proxy; /* A handle instance that is used as the proxy for the
one that will eventually be allocated in the coroutine
frame. */
+ tree from_address_bl; /* handle_type from_address function (BASELINK). */
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
@@ -203,7 +204,6 @@ static GTY(()) tree coro_final_suspend_identifier;
static GTY(()) tree coro_return_void_identifier;
static GTY(()) tree coro_return_value_identifier;
static GTY(()) tree coro_yield_value_identifier;
-static GTY(()) tree coro_resume_identifier;
static GTY(()) tree coro_address_identifier;
static GTY(()) tree coro_from_address_identifier;
static GTY(()) tree coro_get_return_object_identifier;
@@ -243,7 +243,6 @@ coro_init_identifiers ()
coro_return_void_identifier = get_identifier ("return_void");
coro_return_value_identifier = get_identifier ("return_value");
coro_yield_value_identifier = get_identifier ("yield_value");
- coro_resume_identifier = get_identifier ("resume");
coro_address_identifier = get_identifier ("address");
coro_from_address_identifier = get_identifier ("from_address");
coro_get_return_object_identifier = get_identifier ("get_return_object");
@@ -271,6 +270,7 @@ coro_init_identifiers ()
static GTY(()) tree coro_traits_templ;
static GTY(()) tree coro_handle_templ;
static GTY(()) tree void_coro_handle_type;
+static GTY(()) tree void_coro_handle_address;
/* ================= Parse, Semantics and Type checking ================= */
@@ -389,7 +389,105 @@ find_coro_handle_template_decl (location_t kw)
return handle_decl;
}
-/* Instantiate the handle template for a given promise type. */
+/* Get and validate HANDLE_TYPE#address. The resulting function, if any, will
+ be a non-overloaded member function that takes no arguments and returns
+ void*. If that is not the case, signals an error and returns NULL_TREE. */
+
+static tree
+get_handle_type_address_bl (location_t kw, tree handle_type)
+{
+ tree addr_getter = lookup_member (handle_type, coro_address_identifier, 1,
+ 0, tf_warning_or_error);
+ if (!addr_getter || addr_getter == error_mark_node)
+ {
+ error_at (kw, "could not find %qE in %qT",
+ coro_address_identifier, handle_type);
+ return NULL_TREE;
+ }
+
+ gcc_assert (TREE_CODE (addr_getter) == BASELINK);
+
+ tree fn_t = TREE_TYPE (addr_getter);
+ if (TREE_CODE (fn_t) != METHOD_TYPE)
+ {
+ error_at (kw, "%qE must be a non-overloaded method", addr_getter);
+ return NULL_TREE;
+ }
+
+ tree arg = TYPE_ARG_TYPES (fn_t);
+ /* Given that this is a method, we have an argument to skip (the this
+ pointer). */
+ gcc_checking_assert (TREE_CHAIN (arg));
+ arg = TREE_CHAIN (arg);
+
+ /* Check that from_addr has the argument list (). */
+ if (!arg
+ || !same_type_p (TREE_VALUE (arg), void_type_node)
+ || TREE_CHAIN (arg))
+ {
+ error_at (kw, "%qE must take no arguments", addr_getter);
+ return NULL_TREE;
+ }
+
+ tree ret_t = TREE_TYPE (fn_t);
+ if (!same_type_p (ret_t, ptr_type_node))
+ {
+ error_at (kw, "%qE must return %qT, not %qT",
+ addr_getter, ptr_type_node, ret_t);
+ return NULL_TREE;
+ }
+
+ return addr_getter;
+}
+
+/* Get and validate HANDLE_TYPE::from_address. The resulting function, if
+ any, will be a non-overloaded static function that takes a single void* and
+ returns HANDLE_TYPE. If that is not the case, signals an error and returns
+ NULL_TREE. */
+
+static tree
+get_handle_type_from_address_bl (location_t kw, tree handle_type)
+{
+ tree from_addr = lookup_member (handle_type, coro_from_address_identifier, 1,
+ 0, tf_warning_or_error);
+ if (!from_addr || from_addr == error_mark_node)
+ {
+ error_at (kw, "could not find %qE in %qT",
+ coro_from_address_identifier, handle_type);
+ return NULL_TREE;
+ }
+
+ gcc_assert (TREE_CODE (from_addr) == BASELINK);
+
+ tree fn_t = TREE_TYPE (from_addr);
+ if (TREE_CODE (fn_t) != FUNCTION_TYPE)
+ {
+ error_at (kw, "%qE must be a non-overloaded static function", from_addr);
+ return NULL_TREE;
+ }
+
+ tree arg = TYPE_ARG_TYPES (fn_t);
+ /* Check that from_addr has the argument list (void*). */
+ if (!arg
+ || !same_type_p (TREE_VALUE (arg), ptr_type_node)
+ || (arg = TREE_CHAIN (arg), !arg)
+ || !same_type_p (TREE_VALUE (arg), void_type_node)
+ || TREE_CHAIN (arg))
+ {
+ error_at (kw, "%qE must take a single %qT", from_addr, ptr_type_node);
+ return NULL_TREE;
+ }
+
+ tree ret_t = TREE_TYPE (fn_t);
+ if (!same_type_p (ret_t, handle_type))
+ {
+ error_at (kw, "%qE must return %qT, not %qT",
+ from_addr, handle_type, ret_t);
+ return NULL_TREE;
+ }
+
+ return from_addr;
+}
static tree
instantiate_coro_handle_for_promise_type (location_t kw, tree promise_type)
@@ -454,10 +552,15 @@ ensure_coro_initialized (location_t loc)
/* We can also instantiate the void coroutine_handle<> */
void_coro_handle_type =
- instantiate_coro_handle_for_promise_type (loc, NULL_TREE);
+ instantiate_coro_handle_for_promise_type (loc, void_type_node);
if (void_coro_handle_type == NULL_TREE)
return false;
+ void_coro_handle_address =
+ get_handle_type_address_bl (loc, void_coro_handle_type);
+ if (!void_coro_handle_address)
+ return false;
+
/* A table to hold the state, per coroutine decl. */
gcc_checking_assert (coroutine_info_table == NULL);
coroutine_info_table =
@@ -556,9 +659,13 @@ coro_promise_type_found_p (tree fndecl, location_t loc)
instantiate_coro_handle_for_promise_type (loc, coro_info->promise_type);
if (handle_type == NULL_TREE)
return false;
+ tree from_address = get_handle_type_from_address_bl (loc, handle_type);
+ if (from_address == NULL_TREE)
+ return false;
/* Complete this, we're going to use it. */
coro_info->handle_type = complete_type_or_else (handle_type, fndecl);
+ coro_info->from_address_bl = from_address;
/* Diagnostic would be emitted by complete_type_or_else. */
if (!coro_info->handle_type)
@@ -674,6 +781,15 @@ get_coroutine_promise_proxy (tree decl)
return NULL_TREE;
}
+static tree
+get_coroutine_from_address_baselink (tree decl)
+{
+ if (coroutine_info *info = get_coroutine_info (decl))
+ return info->from_address_bl;
+
+ return NULL_TREE;
+}
+
static tree
lookup_promise_method (tree fndecl, tree member_id, location_t loc,
bool musthave)
@@ -2212,7 +2328,6 @@ build_actor_fn (location_t loc, tree coro_frame_type, tree actor, tree fnbody,
{
verify_stmt_tree (fnbody);
/* Some things we inherit from the original function. */
- tree handle_type = get_coroutine_handle_type (orig);
tree promise_type = get_coroutine_promise_type (orig);
tree promise_proxy = get_coroutine_promise_proxy (orig);
@@ -2365,8 +2480,9 @@ build_actor_fn (location_t loc, tree coro_frame_type, tree actor, tree fnbody,
tree ash = build_class_member_access_expr (actor_frame, ash_m, NULL_TREE,
false, tf_warning_or_error);
/* So construct the self-handle from the frame address. */
- tree hfa_m = lookup_member (handle_type, coro_from_address_identifier, 1,
- 0, tf_warning_or_error);
+ tree hfa_m = get_coroutine_from_address_baselink (orig);
+ /* Should have been set earlier by coro_promise_type_found_p. */
+ gcc_assert (hfa_m);
r = build1 (CONVERT_EXPR, build_pointer_type (void_type_node), actor_fp);
vec<tree, va_gc> *args = make_tree_vector_single (r);
@@ -2461,12 +2577,14 @@ build_actor_fn (location_t loc, tree coro_frame_type, tree actor, tree fnbody,
r = build_stmt (loc, LABEL_EXPR, continue_label);
add_stmt (r);
+ /* Should have been set earlier by the coro_initialized code. */
+ gcc_assert (void_coro_handle_address);
+
/* We want to force a tail-call even for O0/1, so this expands the resume
call into its underlying implementation. */
- tree addr = lookup_member (void_coro_handle_type, coro_address_identifier,
- 1, 0, tf_warning_or_error);
- addr = build_new_method_call (continuation, addr, NULL, NULL_TREE,
- LOOKUP_NORMAL, NULL, tf_warning_or_error);
+ tree addr = build_new_method_call (continuation, void_coro_handle_address,
+ NULL, NULL_TREE, LOOKUP_NORMAL, NULL,
+ tf_warning_or_error);
tree resume = build_call_expr_loc
(loc, builtin_decl_explicit (BUILT_IN_CORO_RESUME), 1, addr);
@@ -79,6 +79,8 @@ namespace std {
template <typename...> struct coroutine_traits;
template <typename = void> struct coroutine_handle {
operator coroutine_handle<>();
+ static coroutine_handle from_address(void*);
+ void* address();
};
struct suspend_always {
bool await_ready();
@@ -4,6 +4,8 @@ namespace std {
template <typename _Result> struct coroutine_traits : _Result {};
template <typename = void> struct coroutine_handle {
operator coroutine_handle<>();
+ static coroutine_handle from_address(void*);
+ void* address();
};
}
struct coro1 {
@@ -29,7 +29,10 @@ struct suspend_always {
namespace std {
template <class PromiseType = void>
-struct coroutine_handle {};
+struct coroutine_handle {
+ static coroutine_handle from_address(void*);
+ void* address();
+};
}
struct bad_promise_6 {
new file mode 100644
@@ -0,0 +1,27 @@
+// https://gcc.gnu.org/PR105475
+// Test the case where we have everything we need for coroutine_handle<void>,
+// but not for coroutine_handle<promise>.
+namespace std {
+struct awaitable {
+ bool await_ready() noexcept { return false; }
+ void await_suspend(auto) noexcept {}
+ bool await_resume() noexcept { return true; }
+};
+
+template <typename T> struct coroutine_handle {
+ static coroutine_handle from_address(void *address);
+};
+
+template <typename T = void> struct coroutine_traits {
+ struct promise_type {
+ awaitable initial_suspend() { return {}; }
+ awaitable final_suspend() noexcept { return {}; }
+ void return_void() {}
+ T get_return_object() { return T(); }
+ void unhandled_exception() {}
+ };
+};
+} // namespace std
+
+void foo() { co_return; }
+// { dg-error "could not find 'address' in 'std::coroutine_handle<void>'" "" { target *-*-* } {.-1} }
new file mode 100644
@@ -0,0 +1,29 @@
+// { dg-do compile }
+// https://gcc.gnu.org/PR105475
+// Test the case where we lack 'from_address' (i.e. the member required from
+// coroutine_handle<void>), but not address()
+namespace std {
+
+struct awaitable {
+ bool await_ready() noexcept { return false; }
+ void await_suspend(auto) noexcept {}
+ bool await_resume() noexcept { return true; }
+};
+
+template <typename T> struct coroutine_handle {
+ void* address();
+};
+
+template <typename T = void> struct coroutine_traits {
+ struct promise_type {
+ awaitable initial_suspend() { return {}; }
+ awaitable final_suspend() noexcept { return {}; }
+ void return_void() {}
+ T get_return_object() { return T(); }
+ void unhandled_exception() {}
+ };
+};
+} // namespace std
+
+void foo() { co_return; }
+// { dg-error "could not find 'from_address' in 'std::coroutine_handle<std::coroutine_traits<void>::promise_type>'" "" { target *-*-* } {.-1} }
new file mode 100644
@@ -0,0 +1,29 @@
+// https://gcc.gnu.org/PR105475
+// Test the case where we create a non-static from_address.
+#include <coroutine>
+
+struct promise;
+
+struct task
+{ using promise_type = promise; };
+
+struct promise
+{
+ void return_void () {}
+ std::suspend_never final_suspend() noexcept { return {}; }
+ std::suspend_never initial_suspend() noexcept { return {}; }
+ void unhandled_exception () {}
+ task get_return_object() { return {}; }
+};
+
+/* Invalid. */
+namespace std
+{
+ template<>
+ struct coroutine_handle<promise>
+ { coroutine_handle from_address(void*); };
+};
+
+task foo()
+{ co_return; }
+// { dg-error "std::__n4861::coroutine_handle<promise>::from_address' must be a non-overloaded static function" "" { target *-*-* } {.-1} }
new file mode 100644
@@ -0,0 +1,33 @@
+// https://gcc.gnu.org/PR105475
+// Test the case where we create a static address.
+#include <coroutine>
+
+struct promise;
+
+struct task
+{ using promise_type = promise; };
+
+struct promise
+{
+ void return_void () {}
+ std::suspend_never final_suspend() noexcept { return {}; }
+ std::suspend_never initial_suspend() noexcept { return {}; }
+ void unhandled_exception () {}
+ task get_return_object() { return {}; }
+};
+
+/* Invalid. */
+namespace std
+{
+ template<>
+ struct coroutine_handle<promise>
+ {
+ static coroutine_handle from_address(void*);
+
+ static void* address();
+ };
+};
+
+task foo()
+{ co_return; }
+// { dg-error "std::__n4861::coroutine_handle<void>::address' must be a non-overloaded member function" "" { target *-*-* } {.-1} }
new file mode 100644
@@ -0,0 +1,33 @@
+// https://gcc.gnu.org/PR105475
+// Test the case where we specialize coroutine_handle<void> and remove
+// 'address'.
+namespace std {
+
+struct awaitable {
+ bool await_ready() noexcept { return false; }
+ void await_suspend(auto) noexcept {}
+ bool await_resume() noexcept { return true; }
+};
+
+template <typename T> struct coroutine_handle {
+ static coroutine_handle from_address(void *address);
+ void* address();
+};
+
+template <> struct coroutine_handle<void> {
+ static coroutine_handle from_address(void *address);
+};
+
+template <typename T = void> struct coroutine_traits {
+ struct promise_type {
+ awaitable initial_suspend() { return {}; }
+ awaitable final_suspend() noexcept { return {}; }
+ void return_void() {}
+ T get_return_object() { return T(); }
+ void unhandled_exception() {}
+ };
+};
+} // namespace std
+
+void foo() { co_return; }
+// { dg-error "could not find 'address' in 'std::coroutine_handle<void>'" "" { target *-*-* } {.-1} }
new file mode 100644
@@ -0,0 +1,29 @@
+// https://gcc.gnu.org/PR105475
+// Test the case where we specialize coroutine_handle and break it.
+#include <coroutine>
+
+struct promise;
+
+struct task
+{ using promise_type = promise; };
+
+struct promise
+{
+ void return_void () {}
+ std::suspend_never final_suspend() noexcept { return {}; }
+ std::suspend_never initial_suspend() noexcept { return {}; }
+ void unhandled_exception () {}
+ task get_return_object() { return {}; }
+};
+
+/* Invalid. */
+namespace std
+{
+ template<>
+ struct coroutine_handle<promise>
+ {};
+};
+
+task foo()
+{ co_return; }
+// { dg-error "could not find 'from_address' in 'std::__n4861::coroutine_handle<promise>'" "" { target *-*-* } {.-1} }
new file mode 100644
@@ -0,0 +1,28 @@
+// https://gcc.gnu.org/PR105475
+namespace std {
+
+struct handle {};
+
+struct awaitable {
+ bool await_ready() noexcept { return false; }
+ void await_suspend(handle) noexcept {}
+ bool await_resume() noexcept { return true; }
+};
+
+template <typename T> struct coroutine_handle {
+ static handle from_address(void *address) noexcept { return {}; }
+};
+
+template <typename T = void> struct coroutine_traits {
+ struct promise_type {
+ awaitable initial_suspend() { return {}; }
+ awaitable final_suspend() noexcept { return {}; }
+ void return_void() {}
+ T get_return_object() { return T(); }
+ void unhandled_exception() {}
+ };
+};
+} // namespace std
+
+void foo() { co_return; }
+// { dg-error "could not find 'address' in 'std::coroutine_handle<void>'" "" { target *-*-* } {.-1} }
@@ -5,8 +5,15 @@ template <typename _Result, typename> struct coroutine_traits {
using promise_type = _Result::promise_type;
};
template <typename = void> struct coroutine_handle;
-template <> struct coroutine_handle<> { public: };
-template <typename> struct coroutine_handle : coroutine_handle<> {};
+template <> struct coroutine_handle<> {
+public:
+ static coroutine_handle from_address(void*);
+ void* address();
+};
+template <typename> struct coroutine_handle : coroutine_handle<> {
+ static coroutine_handle from_address(void*);
+ void* address();
+};
struct suspend_always {
bool await_ready();
void await_suspend(coroutine_handle<>);
@@ -5,8 +5,14 @@ template <typename a> a b(a &&);
template <typename c> struct d { c e; };
template <typename f, typename> struct coroutine_traits : f {};
template <typename = void> struct coroutine_handle;
-template <> struct coroutine_handle<> {};
-template <typename> struct coroutine_handle : coroutine_handle<> {};
+template <> struct coroutine_handle<> {
+ static coroutine_handle from_address(void*);
+ void* address();
+};
+template <typename> struct coroutine_handle : coroutine_handle<> {
+ static coroutine_handle from_address(void*);
+ void* address();
+};
struct g {};
} // namespace std
@@ -2,8 +2,14 @@
namespace std {
template <typename a, typename...> struct coroutine_traits : a {};
template <typename = void> struct coroutine_handle;
-template <> struct coroutine_handle<> {};
-template <typename> struct coroutine_handle : coroutine_handle<> {};
+template <> struct coroutine_handle<> {
+ static coroutine_handle from_address(void*);
+ void* address();
+};
+template <typename> struct coroutine_handle : coroutine_handle<> {
+ static coroutine_handle from_address(void*);
+ void* address();
+};
struct b {
bool await_ready();
void await_suspend(coroutine_handle<>);
@@ -2,8 +2,14 @@ namespace std {
inline namespace __n4861 {
template <typename _Result, typename> struct coroutine_traits : _Result {};
template <typename = void> struct coroutine_handle;
-template <> struct coroutine_handle<> {};
-template <typename> struct coroutine_handle : coroutine_handle<> {};
+template <> struct coroutine_handle<> {
+ static coroutine_handle from_address(void*);
+ void* address();
+};
+template <typename> struct coroutine_handle : coroutine_handle<> {
+ static coroutine_handle from_address(void*);
+ void* address();
+};
struct suspend_never {
bool await_ready() noexcept;
void await_suspend(coroutine_handle<>) noexcept;