> 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.

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

> 
> 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


Reply via email to