| Message ID | aWCGcCrm_-iGdgQ3@Thaum.localdomain |
|---|---|
| State | New |
| Headers |
Return-Path: <gcc-patches-bounces~patchwork=sourceware.org@gcc.gnu.org> X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [127.0.0.1]) by sourceware.org (Postfix) with ESMTP id 8DB0B4BA2E1D for <patchwork@sourceware.org>; Fri, 9 Jan 2026 04:40:04 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 8DB0B4BA2E1D Authentication-Results: sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20230601 header.b=Ea5OEPlu X-Original-To: gcc-patches@gcc.gnu.org Delivered-To: gcc-patches@gcc.gnu.org Received: from mail-pj1-f53.google.com (mail-pj1-f53.google.com [209.85.216.53]) by sourceware.org (Postfix) with ESMTPS id DE9AF4BA2E07 for <gcc-patches@gcc.gnu.org>; Fri, 9 Jan 2026 04:39:18 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org DE9AF4BA2E07 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=gmail.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org DE9AF4BA2E07 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=209.85.216.53 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1767933559; cv=none; b=x7gTLHtRgvGBuRV4ha/g9EIZwcCdAArAXTXsnncTGz4kJgHIo/Xen96mdbSvI9I6HzPrpB/ATxBsD3Hn19NQRcJEZdxRlD060QzTo6eYUiaZj7z0Q2hVuiYDK0wVRpIEsZ7qogMav6JCIdKcoV8JqnOSSOB7Nrcqg13SWEzjl7w= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1767933559; c=relaxed/simple; bh=h+w/XAyjY3veY/ReZQ71EqHheEhvhjyDeMD/BPOqb0A=; h=DKIM-Signature:Date:From:To:Subject:Message-ID:MIME-Version; b=d8h9HPf1zNSIrtG39Bd5/rAspVmN1z/COk1Dg5Olav0FCKwgMlJQig7DiUoOsjJ4oGGkWRRbMwWoj93HiC6pX7iw7XKylfVlzkdMBZd0rk49VgU2I6T622ku9tTQ2YNXcB/qqswXsh1Td7IuddXbmLtnHYzLwnV0BXWVGJry13Y= ARC-Authentication-Results: i=1; server2.sourceware.org Received: by mail-pj1-f53.google.com with SMTP id 98e67ed59e1d1-34eeffdb197so597178a91.1 for <gcc-patches@gcc.gnu.org>; Thu, 08 Jan 2026 20:39:18 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1767933558; x=1768538358; darn=gcc.gnu.org; h=content-disposition:mime-version:message-id:subject:cc:to:from:date :from:to:cc:subject:date:message-id:reply-to; bh=Cv7CrmyPeBSh8INzpfNd/pjQvEwlRG0SfrqaefRrzrY=; b=Ea5OEPlu4zja8Z+IhVX26AHT1mFzsyBqv2B7DcJ5Fqktvxfnhd4mLvdTXnvQo2gXSn 6DsFvYowv5HfKrNJp4xh7a4RibN/ilGVr+u+WyZHuNBh++9OZwLbBZxwqTz7IJu/sdS/ t01AFjYYcyoTKoF3Pu/5+76NhPVO0t20yJVd+fL+piXOeVvSplBLshWoHn+T2h8ZJLbb coyrwRaPP6yVYVTVhOryozmTrp3zskSUFECIgWzWmDKRNOJ41fO3JaHbZw3WZCrbyvQj 6ZYxuqpcoisqmTSvu5pNofQ1kW24lM1eEYe9s98/qYy0uV+901jCFbTjvtA8LZ7Yw2YS fm/g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1767933558; x=1768538358; h=content-disposition:mime-version:message-id:subject:cc:to:from:date :x-gm-gg:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=Cv7CrmyPeBSh8INzpfNd/pjQvEwlRG0SfrqaefRrzrY=; b=t7DD+Cehk9Xzx1Rhnq7cfxWsKI8EfdzkcIPme7iKbeZ6Ai8ceRv8zVtpun1GGx7twr rJxbb//Ie4UM4MvQGFBTNhwTossvBo3/yzvqA7my7aVGhldMhOcs5U4IrzXG0LfRmJPm rFjxxu7joKLSp2NiRAJCGqCzabxhSKAsHBPdhXJMR2ShqepkBYkuWVmjDkkLvLuYMq6R Jmw1NCX0o/bciKqbUKFYm1Ws5Bck2sUDqVerKcDyA4p2x584B1A7+WcoUpz8FxJaYVUE JZoP1GI0sU6VGkOKpexI9VYjnCYcwmm5ltWOn3bZTcQ5ceKElLZD8XvqDOtnw0YQsMKW X0NQ== X-Gm-Message-State: AOJu0Yyq/uiJajb0sm/+YIuIMax+NGN7LrN42X7BrMUQkNCFPAUvQF2s s7FylxS2f2ueHfap46xvnitNlD8Q7OZXi/dMdumiLTUjZi/wWp+vxL4C/poCMQ== X-Gm-Gg: AY/fxX7jsW8B1QcrULymF1W6xpcILBcwcFreI9rPDtyM57ZC0Fo8/7EicpODettVgLJ y442MUcxNhgLkd2qH9ON+ssXfux3VG2V+6eVbmsQ1w8SILF2sbSQ3Z7PC2EnJcQ1AtaWa4fTCqt mJ0XshuYxNKWGEh1oFVc4qjzGKXstXXiECThXhYlyH7cYpP30fKehKNwXCdXgILrutXjMUmqXpG zxCgvPHeOhtmOD44QWlWy+/hvykcAKHacjMSHZrtN0sD2wvvJbhSquK7KT6RWy7LVlDkf1WMJ2k Vt3r8bV9tam29zGY+Jaep8WC1SEsDiul2Yn2T/ZF3LXw5DAPpRhdpbWTFZOHkPIWyPrcebup/Fv eUb1nSoEuuVq62U6+wjYUj2wDWkLqAy+xPDTwWzEOaw5we5CpY7h3j7gLuK1OXyLZP4ZD3z6bYE WqdyVP4LiQyloGQIAOiKQiRraq2IqtTJrnPQAVNVpDC+o= X-Google-Smtp-Source: AGHT+IHDS0SPLSz7fagwXZRRCKj5gwd1zS5b3Tyw+uG5T3ZtQGH09fMi+g1Wqf8MxA8JBE7HgBn3OA== X-Received: by 2002:a17:903:1ca:b0:2a0:d662:7283 with SMTP id d9443c01a7336-2a3ee436196mr60410385ad.3.1767933557485; Thu, 08 Jan 2026 20:39:17 -0800 (PST) Received: from Thaum.localdomain ([163.53.146.3]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2a3e3c48dc1sm90988185ad.40.2026.01.08.20.39.15 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 08 Jan 2026 20:39:17 -0800 (PST) Date: Fri, 9 Jan 2026 15:39:12 +1100 From: Nathaniel Shead <nathanieloshead@gmail.com> To: gcc-patches@gcc.gnu.org Cc: Jason Merrill <jason@redhat.com>, Iain Sandoe <iain@sandoe.co.uk> Subject: [PATCH] c++: modules and coroutines Message-ID: <aWCGcCrm_-iGdgQ3@Thaum.localdomain> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline X-Spam-Status: No, score=-11.8 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, FREEMAIL_FROM, GIT_PATCH_0, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED, SPF_HELO_NONE, SPF_PASS, TXREP, URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gcc-patches mailing list <gcc-patches.gcc.gnu.org> List-Unsubscribe: <https://gcc.gnu.org/mailman/options/gcc-patches>, <mailto:gcc-patches-request@gcc.gnu.org?subject=unsubscribe> List-Archive: <https://gcc.gnu.org/pipermail/gcc-patches/> List-Post: <mailto:gcc-patches@gcc.gnu.org> List-Help: <mailto:gcc-patches-request@gcc.gnu.org?subject=help> List-Subscribe: <https://gcc.gnu.org/mailman/listinfo/gcc-patches>, <mailto:gcc-patches-request@gcc.gnu.org?subject=subscribe> Errors-To: gcc-patches-bounces~patchwork=sourceware.org@gcc.gnu.org |
| Series |
c++: modules and coroutines
|
|
Commit Message
Nathaniel Shead
Jan. 9, 2026, 4:39 a.m. UTC
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.
- 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 <nathanieloshead@gmail.com>
---
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
Comments
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 <nathanieloshead@gmail.com> 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. 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? 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 <nathanieloshead@gmail.com> > --- > 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 >
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 <nathanieloshead@gmail.com> 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). 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. 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. > 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. 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 <nathanieloshead@gmail.com> > > --- > > 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 > > >
> On 9 Jan 2026, at 08:37, Nathaniel Shead <nathanieloshead@gmail.com> 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 <nathanieloshead@gmail.com> 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 <nathanieloshead@gmail.com> >>> --- >>> 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
On Fri, Jan 09, 2026 at 10:01:49AM +0000, Iain Sandoe wrote: > > > > On 9 Jan 2026, at 08:37, Nathaniel Shead <nathanieloshead@gmail.com> 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 <nathanieloshead@gmail.com> 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 <nathanieloshead@gmail.com> > >>> --- > >>> 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 > >
On 1/9/26 6:01 PM, Iain Sandoe wrote: > > >> On 9 Jan 2026, at 08:37, Nathaniel Shead <nathanieloshead@gmail.com> 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 <nathanieloshead@gmail.com> 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. We can avoid that by putting them all in the same comdat group. Jason
> On 13 Jan 2026, at 11:02, Jason Merrill <jason@redhat.com> wrote: > > On 1/9/26 6:01 PM, Iain Sandoe wrote: >>> On 9 Jan 2026, at 08:37, Nathaniel Shead <nathanieloshead@gmail.com> 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 <nathanieloshead@gmail.com> 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. > > We can avoid that by putting them all in the same comdat group. I’ve drafted a patch on my coroutines WIP branch that does this, which should work for ELF and PE-coff. However, we still need to deal with platforms that do not support comdat (or only support it in a limited way). Iain > > Jason
On 1/14/26 3:58 AM, Iain Sandoe wrote: >> On 13 Jan 2026, at 11:02, Jason Merrill <jason@redhat.com> wrote: >> >> On 1/9/26 6:01 PM, Iain Sandoe wrote: >>>> On 9 Jan 2026, at 08:37, Nathaniel Shead <nathanieloshead@gmail.com> 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 <nathanieloshead@gmail.com> 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. >> >> We can avoid that by putting them all in the same comdat group. > > I’ve drafted a patch on my coroutines WIP branch that does this, which should > work for ELF and PE-coff. I'd like to get this in 16, since it affects ABI. > However, we still need to deal with platforms that do not support comdat > (or only support it in a limited way). For such platforms I'd still be inclined to use vague linkage, as a linker choosing one symbol from one TU and one symbol from another seems unlikely, but I don't feel strongly about it. Jason
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(); +}