On Fri, Jan 09, 2026 at 10:01:49AM +0000, Iain Sandoe wrote: > > > > On 9 Jan 2026, at 08:37, Nathaniel Shead <[email protected]> wrote: > > > > On Fri, Jan 09, 2026 at 08:19:33AM +0000, Iain Sandoe wrote: > >> Hi Nathaniel. > >> > >> thanks for looking at this (both language features were in flux at the same > >> time and I don’t think the interaction was especially well-considered). > >> > >>> On 9 Jan 2026, at 04:39, Nathaniel Shead <[email protected]> > >>> wrote: > >>> > >>> Bootstrapped and regtested on x86_64-pc-linux-gnu, OK for trunk? > >>> > >>> -- >8 -- > >>> > >>> While working on another issue I found that currently modules do not > >>> work with coroutines at all. This patch fixes a number of issues in > >>> both the coroutines logic and modules logic to ensure that they play > >>> well together. To summarize: > >>> > >>> - The coroutine proxy objects did not have a DECL_CONTEXT set (required > >>> for modules to merge declarations). > >>> > >>> - The coroutine transformation functions were always considered > >>> non-inline, even for an inline ramp function, which meant that modules > >>> didn't realise it needed to stream a definition. > >> > >> I am somewhat concerned about the proposed change here (it looks like > >> an ABI change - albeit an addition so, presumably, not breaking). > >> > >> The principle is that the three functions are all considered to be part of > >> the > >> same entity, where the user-visible interface is only the ramp and the > >> coroutine > >> handle. > >> > >> That is, I don’t think that this is an ‘exposure’ in the sense of P1815 > >> since > >> the split into ramp/actor/destroyer is an internal detail invisible to the > >> end > >> user. > >> > > > > I think perhaps my wording wasn't clear; this wasn't so much about > > internal linkage (though that of course is also related) but about vague > > linkage. Consider the following TU: > > > > #include <coroutine> > > struct simple_promise; > > struct simple_coroutine : std::coroutine_handle<simple_promise> { > > using promise_type = ::simple_promise; > > }; > > struct simple_promise { > > simple_coroutine get_return_object() { return { > > simple_coroutine::from_promise(*this) }; } > > std::suspend_always initial_suspend() noexcept { return {}; } > > std::suspend_always final_suspend() noexcept { return {}; } > > void return_void() {} > > void unhandled_exception() {} > > }; > > inline simple_coroutine foo() { > > co_return; > > } > > > > We do not emit foo as it is unused, but we do emit the transform > > functions (e.g. _Z3fooP13_Z3foov.Frame.actor). > > I think that is https://gcc.gnu.org/bugzilla/show_bug.cgi?id=102528 > > > These functions are > > also not marked as '.globl' (they are not TREE_PUBLIC and so are > > considered internal to the TU), so a different TU calling a > > forward-declared 'foo' would get undefined references to the actor > > function etc. > > I’m not sure what you mean by ‘forward-declared’ here; for foo() to be > usable there must be TU(s) in which foo() is emitted - that/those TU(s) > need to contain the helpers. > > > My assumption was that the intention is that they are all meant to come > > along for the ride with each other wrt linkage; that is, in this case > > foo has vague linkage, so foo.actor and foo.destroy should be as well, > > and will be emitted iff foo is. Rather than every TU having copies of > > the transform functions. > > I agree that this seems more space-efficient (at the expense of emitting more > vague-linkage symbols). I think it would, however break the assumptions > made if, for example, foo () in one TU was able to link against the helpers in > another - since the details of the lowering are compiler-specific. >
Ah, I didn't think about this aspect of compatibility. > Although, perhaps ‘between-compilers’ is a moot point, since we cannot > share .BMIs. > > >> Would it be possible to make the actor and destroyer fns dependencies > >> of the ramp (they are) and have them streamed and restored in that way? > > > > That said, it should be possible to special-case the actor and destroyer > > functions and always stream their bodies in a modules context if we're > > streaming the body of the ramp function, which would maintain the > > current ABI, if that is indeed intentional. > > Let’s continue to think it through - and collect Jason’s input too. > Iain > Thanks. From reading through the bug report you linked and comparing what Clang does as well I think I now have a better understanding what's going on, and I don't think what I've done here is correct anymore. At some point tomorrow I'll try to make a v2 which just overrides modules streaming to force emission from that end. > > > > Nathaniel > > > >> > >> Iain > >> > >>> - In an importing TU we had lost the connection between the ramp > >>> functions and the transform functions, as they were kept in a pair > >>> of global maps. > >>> > >>> - Modules streaming couldn't discriminate between the actor or destroy > >>> functions when merging. > >>> > >>> - Modules streaming wasn't setting the cfun->coroutine_component flag, > >>> needed to activate the middle-end coroutine lowering pass. > >>> > >>> This patch also separates the coroutine_info_table initialization from > >>> the ensure_coro_initialized function. If the first time we see a > >>> coroutine is from a module import, we need to register the > >>> transformation functions now but calling ensure_coro_initialized would > >>> lookup e.g. std::coroutine_traits, which may only be visible from this > >>> module that we're currently reading, causing a recursive load. > >>> Separating the concerns allows this to work correctly. > >>> > >>> gcc/cp/ChangeLog: > >>> > >>> * coroutines.cc (create_coroutine_info_table): New function. > >>> (get_or_insert_coroutine_info): Mark static. > >>> (ensure_coro_initialized): Likewise; use > >>> create_coroutine_info_table. > >>> (coro_promise_type_found_p): Set DECL_CONTEXT of proxies. > >>> (coro_set_ramp_function): New function. > >>> (coro_set_transform_functions): New function. > >>> (coro_build_actor_or_destroy_function): Use > >>> coro_set_ramp_function; copy linkage from original function. > >>> * cp-tree.h (coro_set_transform_functions): Declare. > >>> (coro_set_ramp_function): Declare. > >>> * decl2.cc (mark_used): Mark transform functions as used if we > >>> use the ramp function. > >>> * module.cc (struct merge_key): New field coro_disc. > >>> (struct post_process_data): New field coroutine_component. > >>> (get_coroutine_discriminator): New function. > >>> (trees_out::key_mergeable): Write coroutine discriminator. > >>> (check_mergeable_decl): Adjust comment, check for matching > >>> coroutine discriminator. > >>> (trees_in::key_mergeable): Read coroutine discriminator. > >>> (trees_out::write_function_def): Write coroutine_component and > >>> ramp/actor/destroy functions for coroutines. > >>> (trees_in::read_function_def): Read them. > >>> (module_state::read_cluster): Set cfun->coroutine_component. > >>> > >>> gcc/testsuite/ChangeLog: > >>> > >>> * g++.dg/modules/coro-1_a.C: New test. > >>> * g++.dg/modules/coro-1_b.C: New test. > >>> > >>> Signed-off-by: Nathaniel Shead <[email protected]> > >>> --- > >>> gcc/cp/coroutines.cc | 64 ++++++++++++++++---- > >>> gcc/cp/cp-tree.h | 4 ++ > >>> gcc/cp/decl2.cc | 13 +++++ > >>> gcc/cp/module.cc | 78 ++++++++++++++++++++++--- > >>> gcc/testsuite/g++.dg/modules/coro-1_a.C | 28 +++++++++ > >>> gcc/testsuite/g++.dg/modules/coro-1_b.C | 19 ++++++ > >>> 6 files changed, 189 insertions(+), 17 deletions(-) > >>> create mode 100644 gcc/testsuite/g++.dg/modules/coro-1_a.C > >>> create mode 100644 gcc/testsuite/g++.dg/modules/coro-1_b.C > >>> > >>> diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc > >>> index f0485a95073..930d453dde2 100644 > >>> --- a/gcc/cp/coroutines.cc > >>> +++ b/gcc/cp/coroutines.cc > >>> @@ -353,10 +353,20 @@ coroutine_info_hasher::equal (coroutine_info *lhs, > >>> const compare_type& rhs) > >>> return lhs->function_decl == rhs; > >>> } > >>> > >>> +/* Initialize the coroutine info table, to hold state per coroutine decl, > >>> + if not already created. */ > >>> + > >>> +static void > >>> +create_coroutine_info_table () > >>> +{ > >>> + if (!coroutine_info_table) > >>> + coroutine_info_table = hash_table<coroutine_info_hasher>::create_ggc > >>> (11); > >>> +} > >>> + > >>> /* Get the existing coroutine_info for FN_DECL, or insert a new one if the > >>> entry does not yet exist. */ > >>> > >>> -coroutine_info * > >>> +static coroutine_info * > >>> get_or_insert_coroutine_info (tree fn_decl) > >>> { > >>> gcc_checking_assert (coroutine_info_table != NULL); > >>> @@ -375,7 +385,7 @@ get_or_insert_coroutine_info (tree fn_decl) > >>> > >>> /* Get the existing coroutine_info for FN_DECL, fail if it doesn't exist. > >>> */ > >>> > >>> -coroutine_info * > >>> +static coroutine_info * > >>> get_coroutine_info (tree fn_decl) > >>> { > >>> if (coroutine_info_table == NULL) > >>> @@ -757,11 +767,7 @@ ensure_coro_initialized (location_t loc) > >>> 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 = > >>> - hash_table<coroutine_info_hasher>::create_ggc (11); > >>> - > >>> + create_coroutine_info_table (); > >>> if (coroutine_info_table == NULL) > >>> return false; > >>> > >>> @@ -873,11 +879,13 @@ coro_promise_type_found_p (tree fndecl, location_t > >>> loc) > >>> coro_info->self_h_proxy > >>> = build_lang_decl (VAR_DECL, coro_self_handle_id, > >>> coro_info->handle_type); > >>> + DECL_CONTEXT (coro_info->self_h_proxy) = fndecl; > >>> > >>> /* Build a proxy for the promise so that we can perform lookups. */ > >>> coro_info->promise_proxy > >>> = build_lang_decl (VAR_DECL, coro_promise_id, > >>> coro_info->promise_type); > >>> + DECL_CONTEXT (coro_info->promise_proxy) = fndecl; > >>> > >>> /* Note where we first saw a coroutine keyword. */ > >>> coro_info->first_coro_keyword = loc; > >>> @@ -902,6 +910,17 @@ coro_get_ramp_function (tree decl) > >>> return NULL_TREE; > >>> } > >>> > >>> +/* Given a DECL, an actor or destroyer, build a link from that to the > >>> ramp > >>> + function. Used by modules streaming. */ > >>> + > >>> +void > >>> +coro_set_ramp_function (tree decl, tree ramp) > >>> +{ > >>> + if (!to_ramp) > >>> + to_ramp = hash_map<tree, tree>::create_ggc (10); > >>> + to_ramp->put (decl, ramp); > >>> +} > >>> + > >>> /* Given the DECL for a ramp function (the user's original declaration) > >>> return > >>> the actor function if it has been defined. */ > >>> > >>> @@ -926,6 +945,27 @@ coro_get_destroy_function (tree decl) > >>> return NULL_TREE; > >>> } > >>> > >>> +/* For a given ramp function DECL, set the actor and destroy functions. > >>> + This is only used by modules streaming. */ > >>> + > >>> +void > >>> +coro_set_transform_functions (tree decl, tree actor, tree destroy) > >>> +{ > >>> + /* Only relevant with modules. */ > >>> + gcc_assert (modules_p ()); > >>> + > >>> + /* This should only be called for newly streamed declarations. */ > >>> + gcc_assert (!get_coroutine_info (decl)); > >>> + > >>> + /* This might be the first use of coroutine info in the TU, so > >>> + create the coroutine info table if needed. */ > >>> + create_coroutine_info_table (); > >>> + > >>> + coroutine_info *coroutine = get_or_insert_coroutine_info (decl); > >>> + coroutine->actor_decl = actor; > >>> + coroutine->destroy_decl = destroy; > >>> +} > >>> + > >>> /* Given a CO_AWAIT_EXPR AWAIT_EXPR, return its resume call. */ > >>> > >>> tree > >>> @@ -4393,15 +4433,19 @@ coro_build_actor_or_destroy_function (tree orig, > >>> tree fn_type, > >>> = build_lang_decl (FUNCTION_DECL, copy_node (DECL_NAME (orig)), > >>> fn_type); > >>> > >>> /* Allow for locating the ramp (original) function from this one. */ > >>> - if (!to_ramp) > >>> - to_ramp = hash_map<tree, tree>::create_ggc (10); > >>> - to_ramp->put (fn, orig); > >>> + coro_set_ramp_function (fn, orig); > >>> > >>> DECL_CONTEXT (fn) = DECL_CONTEXT (orig); > >>> DECL_SOURCE_LOCATION (fn) = loc; > >>> DECL_ARTIFICIAL (fn) = true; > >>> DECL_INITIAL (fn) = error_mark_node; > >>> > >>> + /* Copy linkage from the original function. */ > >>> + TREE_PUBLIC (fn) = TREE_PUBLIC (orig); > >>> + DECL_DECLARED_INLINE_P (fn) = DECL_DECLARED_INLINE_P (orig); > >>> + DECL_NOT_REALLY_EXTERN (fn) = DECL_NOT_REALLY_EXTERN (orig); > >>> + DECL_INTERFACE_KNOWN (fn) = DECL_INTERFACE_KNOWN (orig); > >>> + > >>> tree id = get_identifier ("frame_ptr"); > >>> tree fp = build_lang_decl (PARM_DECL, id, coro_frame_ptr); > >>> DECL_ARTIFICIAL (fp) = true; > >>> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h > >>> index b8470fc256c..1d40b387d6e 100644 > >>> --- a/gcc/cp/cp-tree.h > >>> +++ b/gcc/cp/cp-tree.h > >>> @@ -9144,6 +9144,10 @@ extern tree coro_get_ramp_function (tree); > >>> > >>> extern tree co_await_get_resume_call (tree await_expr); > >>> > >>> +/* Only for use by modules. */ > >>> +extern void coro_set_transform_functions (tree, tree, tree); > >>> +extern void coro_set_ramp_function (tree, tree); > >>> + > >>> /* Inline bodies. */ > >>> > >>> inline tree > >>> diff --git a/gcc/cp/decl2.cc b/gcc/cp/decl2.cc > >>> index e807eab1b8a..d50864b8f75 100644 > >>> --- a/gcc/cp/decl2.cc > >>> +++ b/gcc/cp/decl2.cc > >>> @@ -6480,6 +6480,19 @@ mark_used (tree decl, tsubst_flags_t complain /* = > >>> tf_warning_or_error */) > >>> return false; > >>> } > >>> > >>> + /* For coroutines, we need to mark the transform functions as used, > >>> + if they exist yet. */ > >>> + if (flag_coroutines > >>> + && TREE_CODE (decl) == FUNCTION_DECL > >>> + && DECL_COROUTINE_P (decl) > >>> + && DECL_RAMP_P (decl)) > >>> + { > >>> + if (tree actor = DECL_ACTOR_FN (decl)) > >>> + mark_used (actor); > >>> + if (tree destroy = DECL_DESTROY_FN (decl)) > >>> + mark_used (destroy); > >>> + } > >>> + > >>> /* If DECL has a deduced return type, we need to instantiate it now to > >>> find out its type. For OpenMP user defined reductions, we need them > >>> instantiated for reduction clauses which inline them by hand directly. > >>> diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc > >>> index af0730ba974..67fb1e4a22d 100644 > >>> --- a/gcc/cp/module.cc > >>> +++ b/gcc/cp/module.cc > >>> @@ -2969,6 +2969,7 @@ static char const *const merge_kind_name[MK_hwm] = > >>> /* Mergeable entity location data. */ > >>> struct merge_key { > >>> cp_ref_qualifier ref_q : 2; > >>> + unsigned coro_disc : 2; /* Discriminator for coroutine transforms. */ > >>> unsigned index; > >>> > >>> tree ret; /* Return type, if appropriate. */ > >>> @@ -2977,7 +2978,7 @@ struct merge_key { > >>> tree constraints; /* Constraints. */ > >>> > >>> merge_key () > >>> - :ref_q (REF_QUAL_NONE), index (0), > >>> + :ref_q (REF_QUAL_NONE), coro_disc (0), index (0), > >>> ret (NULL_TREE), args (NULL_TREE), > >>> constraints (NULL_TREE) > >>> { > >>> @@ -2999,6 +3000,7 @@ struct post_process_data { > >>> bool returns_null; > >>> bool returns_abnormally; > >>> bool infinite_loop; > >>> + bool coroutine_component; > >>> }; > >>> > >>> /* Tree stream reader. Note that reading a stream doesn't mark the > >>> @@ -11696,6 +11698,24 @@ trees_in::decl_container () > >>> return container; > >>> } > >>> > >>> +/* Gets a 2-bit discriminator to distinguish coroutine actor or destroy > >>> + functions from a normal function. */ > >>> + > >>> +static int > >>> +get_coroutine_discriminator (tree inner) > >>> +{ > >>> + if (tree ramp = DECL_RAMP_FN (inner)) > >>> + { > >>> + if (DECL_ACTOR_FN (ramp) == inner) > >>> + return 1; > >>> + else if (DECL_DESTROY_FN (ramp) == inner) > >>> + return 2; > >>> + else > >>> + gcc_unreachable (); > >>> + } > >>> + return 0; > >>> +} > >>> + > >>> /* Write out key information about a mergeable DEP. Does not write > >>> the contents of DEP itself. The context has already been > >>> written. The container has already been streamed. */ > >>> @@ -11787,6 +11807,7 @@ trees_out::key_mergeable (int tag, merge_kind mk, > >>> tree decl, tree inner, > >>> tree fn_type = TREE_TYPE (inner); > >>> > >>> key.ref_q = type_memfn_rqual (fn_type); > >>> + key.coro_disc = get_coroutine_discriminator (inner); > >>> key.args = TYPE_ARG_TYPES (fn_type); > >>> > >>> if (tree reqs = get_constraints (inner)) > >>> @@ -11923,7 +11944,12 @@ trees_out::key_mergeable (int tag, merge_kind > >>> mk, tree decl, tree inner, > >>> tree_node (name); > >>> if (streaming_p ()) > >>> { > >>> - unsigned code = (key.ref_q << 0) | (key.index << 2); > >>> + /* Check we have enough bits for the index. */ > >>> + gcc_checking_assert (key.index < (1u << (sizeof (unsigned) * 8 - 4))); > >>> + > >>> + unsigned code = ((key.ref_q << 0) > >>> + | (key.coro_disc << 2) > >>> + | (key.index << 4)); > >>> u (code); > >>> } > >>> > >>> @@ -11947,8 +11973,8 @@ trees_out::key_mergeable (int tag, merge_kind mk, > >>> tree decl, tree inner, > >>> } > >>> } > >>> > >>> -/* DECL is a new declaration that may be duplicated in OVL. Use RET & > >>> - ARGS to find its clone, or NULL. If DECL's DECL_NAME is NULL, this > >>> +/* DECL is a new declaration that may be duplicated in OVL. Use KEY > >>> + to find its clone, or NULL. If DECL's DECL_NAME is NULL, this > >>> has been found by a proxy. It will be an enum type located by its > >>> first member. > >>> > >>> @@ -12008,6 +12034,9 @@ check_mergeable_decl (merge_kind mk, tree decl, > >>> tree ovl, merge_key const &key) > >>> && (!DECL_IS_UNDECLARED_BUILTIN (m_inner) > >>> || !DECL_EXTERN_C_P (m_inner) > >>> || DECL_EXTERN_C_P (d_inner)) > >>> + /* Reject if we're not the same kind of coroutine function. */ > >>> + && (!flag_coroutines > >>> + || key.coro_disc == get_coroutine_discriminator (m_inner)) > >>> /* Reject if one is a different member of a > >>> guarded/pre/post fn set. */ > >>> && (!flag_contracts > >>> @@ -12125,7 +12154,8 @@ trees_in::key_mergeable (int tag, merge_kind mk, > >>> tree decl, tree inner, > >>> merge_key key; > >>> unsigned code = u (); > >>> key.ref_q = cp_ref_qualifier ((code >> 0) & 3); > >>> - key.index = code >> 2; > >>> + key.coro_disc = (code >> 2) & 3; > >>> + key.index = code >> 4; > >>> > >>> if (mk == MK_enum) > >>> key.ret = tree_node (); > >>> @@ -13031,6 +13061,8 @@ trees_out::write_function_def (tree decl) > >>> flags |= 8 * f->language->returns_null; > >>> flags |= 16 * f->language->returns_abnormally; > >>> flags |= 32 * f->language->infinite_loop; > >>> + /* Set for coroutines. */ > >>> + flags |= 64 * f->coroutine_component; > >>> } > >>> > >>> u (flags); > >>> @@ -13041,6 +13073,17 @@ trees_out::write_function_def (tree decl) > >>> state->write_location (*this, f->function_start_locus); > >>> state->write_location (*this, f->function_end_locus); > >>> } > >>> + > >>> + if (f && f->coroutine_component) > >>> + { > >>> + tree ramp = DECL_RAMP_FN (decl); > >>> + tree_node (ramp); > >>> + if (!ramp) > >>> + { > >>> + tree_node (DECL_ACTOR_FN (decl)); > >>> + tree_node (DECL_DESTROY_FN (decl)); > >>> + } > >>> + } > >>> } > >>> > >>> void > >>> @@ -13056,13 +13099,13 @@ trees_in::read_function_def (tree decl, tree > >>> maybe_template) > >>> tree initial = tree_node (); > >>> tree saved = tree_node (); > >>> tree context = tree_node (); > >>> - constexpr_fundef cexpr; > >>> post_process_data pdata {}; > >>> pdata.decl = maybe_template; > >>> > >>> tree maybe_dup = odr_duplicate (maybe_template, DECL_SAVED_TREE (decl)); > >>> bool installing = maybe_dup && !DECL_SAVED_TREE (decl); > >>> > >>> + constexpr_fundef cexpr; > >>> if (u ()) > >>> { > >>> cexpr.parms = chained_decls (); > >>> @@ -13074,7 +13117,6 @@ trees_in::read_function_def (tree decl, tree > >>> maybe_template) > >>> cexpr.decl = NULL_TREE; > >>> > >>> unsigned flags = u (); > >>> - > >>> if (flags & 2) > >>> { > >>> pdata.start_locus = state->read_location (*this); > >>> @@ -13083,6 +13125,22 @@ trees_in::read_function_def (tree decl, tree > >>> maybe_template) > >>> pdata.returns_null = flags & 8; > >>> pdata.returns_abnormally = flags & 16; > >>> pdata.infinite_loop = flags & 32; > >>> + pdata.coroutine_component = flags & 64; > >>> + } > >>> + > >>> + tree coro_actor = NULL_TREE; > >>> + tree coro_destroy = NULL_TREE; > >>> + tree coro_ramp = NULL_TREE; > >>> + if (pdata.coroutine_component) > >>> + { > >>> + coro_ramp = tree_node (); > >>> + if (!coro_ramp) > >>> + { > >>> + coro_actor = tree_node (); > >>> + coro_destroy = tree_node (); > >>> + if ((coro_actor == NULL_TREE) != (coro_destroy == NULL_TREE)) > >>> + set_overrun (); > >>> + } > >>> } > >>> > >>> if (get_overrun ()) > >>> @@ -13100,6 +13158,11 @@ trees_in::read_function_def (tree decl, tree > >>> maybe_template) > >>> if (cexpr.decl) > >>> register_constexpr_fundef (cexpr); > >>> > >>> + if (coro_ramp) > >>> + coro_set_ramp_function (decl, coro_ramp); > >>> + else if (coro_actor && coro_destroy) > >>> + coro_set_transform_functions (decl, coro_actor, coro_destroy); > >>> + > >>> if (DECL_LOCAL_DECL_P (decl)) > >>> /* Block-scope OMP UDRs aren't real functions, and don't need a > >>> function structure to be allocated or to be expanded. */ > >>> @@ -17556,6 +17619,7 @@ module_state::read_cluster (unsigned snum) > >>> cfun->language->returns_null = pdata.returns_null; > >>> cfun->language->returns_abnormally = pdata.returns_abnormally; > >>> cfun->language->infinite_loop = pdata.infinite_loop; > >>> + cfun->coroutine_component = pdata.coroutine_component; > >>> > >>> /* Make sure we emit explicit instantiations. > >>> FIXME do we want to do this in expand_or_defer_fn instead? */ > >>> diff --git a/gcc/testsuite/g++.dg/modules/coro-1_a.C > >>> b/gcc/testsuite/g++.dg/modules/coro-1_a.C > >>> new file mode 100644 > >>> index 00000000000..ec2c8300a80 > >>> --- /dev/null > >>> +++ b/gcc/testsuite/g++.dg/modules/coro-1_a.C > >>> @@ -0,0 +1,28 @@ > >>> +// { dg-do compile { target c++20 } } > >>> +// { dg-additional-options "-fmodules" } > >>> +// { dg-module-cmi M } > >>> + > >>> +module; > >>> +#include <coroutine> > >>> +export module M; > >>> + > >>> +struct simple_promise; > >>> +struct simple_coroutine : std::coroutine_handle<simple_promise> { > >>> + using promise_type = ::simple_promise; > >>> +}; > >>> +struct simple_promise { > >>> + simple_coroutine get_return_object() { return { > >>> simple_coroutine::from_promise(*this) }; } > >>> + std::suspend_always initial_suspend() noexcept { return {}; } > >>> + std::suspend_always final_suspend() noexcept { return {}; } > >>> + void return_void() {} > >>> + void unhandled_exception() {} > >>> +}; > >>> +export simple_coroutine coroutine() { > >>> + co_return; > >>> +} > >>> +export inline simple_coroutine inline_coroutine() { > >>> + co_return; > >>> +} > >>> +export template <typename T> simple_coroutine template_coroutine() { > >>> + co_return; > >>> +} > >>> diff --git a/gcc/testsuite/g++.dg/modules/coro-1_b.C > >>> b/gcc/testsuite/g++.dg/modules/coro-1_b.C > >>> new file mode 100644 > >>> index 00000000000..e1384a7d639 > >>> --- /dev/null > >>> +++ b/gcc/testsuite/g++.dg/modules/coro-1_b.C > >>> @@ -0,0 +1,19 @@ > >>> +// { dg-module-do run { target c++20 } } > >>> +// { dg-additional-options "-fmodules" } > >>> + > >>> +#include <coroutine> > >>> +import M; > >>> + > >>> +int main() { > >>> + auto a = coroutine(); > >>> + a.resume(); > >>> + a.destroy(); > >>> + > >>> + auto b = inline_coroutine(); > >>> + b.resume(); > >>> + b.destroy(); > >>> + > >>> + auto c = template_coroutine<int>(); > >>> + c.resume(); > >>> + c.destroy(); > >>> +} > >>> -- > >>> 2.51.0 > >
