[v2] c++/coroutines: only defer expanding co_{await,return,yield} if dependent [PR112341]
Checks
Context |
Check |
Description |
linaro-tcwg-bot/tcwg_gcc_build--master-arm |
success
|
Build passed
|
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 |
success
|
Build passed
|
Commit Message
Okay, I've reworked it, and it built and passed coroutine tests.
Regstrapping overnight. Is the following OK with you?
---------- >8 ----------
By doing so, we can get diagnostics in template decls when we know we
can. For instance, in the following:
awaitable g();
template<typename>
task f()
{
co_await g();
co_yield 1;
co_return "foo";
}
... the coroutine promise type in each statement is always
std::coroutine_handle<task>::promise_type, and all of the operands are
not type-dependent, so we can always compute the resulting types (and
expected types) of these expressions and statements.
Also, when we do not know the type of the CO_AWAIT_EXPR or
CO_YIELD_EXPR, we now return NULL_TREE as the type rather than
unknown_type_node. This is more correct, since the type is not unknown,
it just isn't determined yet. This also means we can remove the
CO_AWAIT_EXPR and CO_YIELD_EXPR special-cases from
type_dependent_expression_p.
PR c++/112341 - error: insufficient contextual information to determine type on co_await result in function template
gcc/cp/ChangeLog:
PR c++/112341
* coroutines.cc (struct coroutine_info): Also cache the
traits type.
(ensure_coro_initialized): New function. Makes sure we have
initialized the coroutine state successfully, or informs the
caller should it fail to do so. Extracted from
coro_promise_type_found_p.
(coro_get_traits_class): New function. Gets the (cached)
coroutine traits type for a given coroutine. Extracted from
coro_promise_type_found_p and refactored to cache the result.
(coro_promise_type_found_p): Use the two functions above.
(build_template_co_await_expr): New function. Builds a
CO_AWAIT_EXPR representing a CO_AWAIT_EXPR in a template
declaration.
(build_co_await): Use the above if processing_template_decl, and
give it a proper type.
(coro_dependent_p): New function. Returns true iff its
argument is a type-dependent expression OR the current functions
traits class is type dependent.
(finish_co_await_expr): Defer expansion only in the case
coro_dependent_p returns true.
(finish_co_yield_expr): Ditto.
(finish_co_return_stmt): Ditto.
* pt.cc (type_dependent_expression_p): Do not treat
CO_AWAIT/CO_YIELD specially.
gcc/testsuite/ChangeLog:
PR c++/112341
* g++.dg/coroutines/pr112341-2.C: New test.
* g++.dg/coroutines/pr112341-3.C: New test.
* g++.dg/coroutines/torture/co-yield-03-tmpl-nondependent.C: New
test.
* g++.dg/coroutines/pr112341.C: New test.
---
gcc/cp/coroutines.cc | 157 ++++++++++++++----
gcc/cp/pt.cc | 5 -
gcc/testsuite/g++.dg/coroutines/pr112341-2.C | 25 +++
gcc/testsuite/g++.dg/coroutines/pr112341-3.C | 65 ++++++++
gcc/testsuite/g++.dg/coroutines/pr112341.C | 21 +++
.../torture/co-yield-03-tmpl-nondependent.C | 140 ++++++++++++++++
6 files changed, 376 insertions(+), 37 deletions(-)
create mode 100644 gcc/testsuite/g++.dg/coroutines/pr112341-2.C
create mode 100644 gcc/testsuite/g++.dg/coroutines/pr112341-3.C
create mode 100644 gcc/testsuite/g++.dg/coroutines/pr112341.C
create mode 100644 gcc/testsuite/g++.dg/coroutines/torture/co-yield-03-tmpl-nondependent.C
Comments
On 7/31/24 3:56 PM, Arsen Arsenović wrote:
> Okay, I've reworked it, and it built and passed coroutine tests.
> Regstrapping overnight. Is the following OK with you?
OK.
> ---------- >8 ----------
> By doing so, we can get diagnostics in template decls when we know we
> can. For instance, in the following:
>
> awaitable g();
> template<typename>
> task f()
> {
> co_await g();
> co_yield 1;
> co_return "foo";
> }
>
> ... the coroutine promise type in each statement is always
> std::coroutine_handle<task>::promise_type, and all of the operands are
> not type-dependent, so we can always compute the resulting types (and
> expected types) of these expressions and statements.
>
> Also, when we do not know the type of the CO_AWAIT_EXPR or
> CO_YIELD_EXPR, we now return NULL_TREE as the type rather than
> unknown_type_node. This is more correct, since the type is not unknown,
> it just isn't determined yet. This also means we can remove the
> CO_AWAIT_EXPR and CO_YIELD_EXPR special-cases from
> type_dependent_expression_p.
>
> PR c++/112341 - error: insufficient contextual information to determine type on co_await result in function template
>
> gcc/cp/ChangeLog:
>
> PR c++/112341
> * coroutines.cc (struct coroutine_info): Also cache the
> traits type.
> (ensure_coro_initialized): New function. Makes sure we have
> initialized the coroutine state successfully, or informs the
> caller should it fail to do so. Extracted from
> coro_promise_type_found_p.
> (coro_get_traits_class): New function. Gets the (cached)
> coroutine traits type for a given coroutine. Extracted from
> coro_promise_type_found_p and refactored to cache the result.
> (coro_promise_type_found_p): Use the two functions above.
> (build_template_co_await_expr): New function. Builds a
> CO_AWAIT_EXPR representing a CO_AWAIT_EXPR in a template
> declaration.
> (build_co_await): Use the above if processing_template_decl, and
> give it a proper type.
> (coro_dependent_p): New function. Returns true iff its
> argument is a type-dependent expression OR the current functions
> traits class is type dependent.
> (finish_co_await_expr): Defer expansion only in the case
> coro_dependent_p returns true.
> (finish_co_yield_expr): Ditto.
> (finish_co_return_stmt): Ditto.
> * pt.cc (type_dependent_expression_p): Do not treat
> CO_AWAIT/CO_YIELD specially.
>
> gcc/testsuite/ChangeLog:
>
> PR c++/112341
> * g++.dg/coroutines/pr112341-2.C: New test.
> * g++.dg/coroutines/pr112341-3.C: New test.
> * g++.dg/coroutines/torture/co-yield-03-tmpl-nondependent.C: New
> test.
> * g++.dg/coroutines/pr112341.C: New test.
> ---
> gcc/cp/coroutines.cc | 157 ++++++++++++++----
> gcc/cp/pt.cc | 5 -
> gcc/testsuite/g++.dg/coroutines/pr112341-2.C | 25 +++
> gcc/testsuite/g++.dg/coroutines/pr112341-3.C | 65 ++++++++
> gcc/testsuite/g++.dg/coroutines/pr112341.C | 21 +++
> .../torture/co-yield-03-tmpl-nondependent.C | 140 ++++++++++++++++
> 6 files changed, 376 insertions(+), 37 deletions(-)
> create mode 100644 gcc/testsuite/g++.dg/coroutines/pr112341-2.C
> create mode 100644 gcc/testsuite/g++.dg/coroutines/pr112341-3.C
> create mode 100644 gcc/testsuite/g++.dg/coroutines/pr112341.C
> create mode 100644 gcc/testsuite/g++.dg/coroutines/torture/co-yield-03-tmpl-nondependent.C
>
> diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc
> index 08a610afc82b..b535519b56d1 100644
> --- a/gcc/cp/coroutines.cc
> +++ b/gcc/cp/coroutines.cc
> @@ -85,6 +85,7 @@ struct GTY((for_user)) coroutine_info
> tree actor_decl; /* The synthesized actor function. */
> tree destroy_decl; /* The synthesized destroy function. */
> tree promise_type; /* The cached promise type for this function. */
> + tree traits_type; /* The cached traits type for this function. */
> tree handle_type; /* The cached coroutine handle for this function. */
> tree self_h_proxy; /* A handle instance that is used as the proxy for the
> one that will eventually be allocated in the coroutine
> @@ -527,11 +528,12 @@ find_promise_type (tree traits_class)
> return promise_type;
> }
>
> +/* Perform initialization of the coroutine processor state, if not done
> + before. */
> +
> static bool
> -coro_promise_type_found_p (tree fndecl, location_t loc)
> +ensure_coro_initialized (location_t loc)
> {
> - gcc_assert (fndecl != NULL_TREE);
> -
> if (!coro_initialized)
> {
> /* Trees we only need to create once.
> @@ -569,6 +571,30 @@ coro_promise_type_found_p (tree fndecl, location_t loc)
>
> coro_initialized = true;
> }
> + return true;
> +}
> +
> +/* Try to get the coroutine traits class. */
> +static tree
> +coro_get_traits_class (tree fndecl, location_t loc)
> +{
> + gcc_assert (fndecl != NULL_TREE);
> + gcc_assert (coro_initialized);
> +
> + coroutine_info *coro_info = get_or_insert_coroutine_info (fndecl);
> + auto& traits_type = coro_info->traits_type;
> + if (!traits_type)
> + traits_type = instantiate_coro_traits (fndecl, loc);
> + return traits_type;
> +}
> +
> +static bool
> +coro_promise_type_found_p (tree fndecl, location_t loc)
> +{
> + gcc_assert (fndecl != NULL_TREE);
> +
> + if (!ensure_coro_initialized (loc))
> + return false;
>
> /* Save the coroutine data on the side to avoid the overhead on every
> function decl tree. */
> @@ -583,7 +609,7 @@ coro_promise_type_found_p (tree fndecl, location_t loc)
> /* Get the coroutine traits template class instance for the function
> signature we have - coroutine_traits <R, ...> */
>
> - tree templ_class = instantiate_coro_traits (fndecl, loc);
> + tree templ_class = coro_get_traits_class (fndecl, loc);
>
> /* Find the promise type for that. */
> coro_info->promise_type = find_promise_type (templ_class);
> @@ -1030,6 +1056,19 @@ coro_diagnose_throwing_final_aw_expr (tree expr)
> return coro_diagnose_throwing_fn (fn);
> }
>
> +/* Build a co_await suitable for later expansion. */
> +
> +tree
> +build_template_co_await_expr (location_t kw, tree type, tree expr, tree kind)
> +{
> + tree aw_expr = build5_loc (kw, CO_AWAIT_EXPR, type, expr,
> + NULL_TREE, NULL_TREE, NULL_TREE,
> + kind);
> + TREE_SIDE_EFFECTS (aw_expr) = true;
> + return aw_expr;
> +}
> +
> +
> /* This performs [expr.await] bullet 3.3 and validates the interface obtained.
> It is also used to build the initial and final suspend points.
>
> @@ -1038,11 +1077,24 @@ coro_diagnose_throwing_final_aw_expr (tree expr)
> A, the original yield/await expr, is found at source location LOC.
>
> We will be constructing a CO_AWAIT_EXPR for a suspend point of one of
> - the four suspend_point_kind kinds. This is indicated by SUSPEND_KIND. */
> + the four suspend_point_kind kinds. This is indicated by SUSPEND_KIND.
> +
> + In the case that we're processing a template declaration, we can't save
> + actual awaiter expressions as the frame type will differ between
> + instantiations, but we can generate them to type-check them and compute the
> + resulting expression type. In those cases, we will return a
> + template-appropriate CO_AWAIT_EXPR and throw away the rest of the results.
> + Such an expression requires the original co_await operand unaltered. Pass
> + it as ORIG_OPERAND. If it is the same as 'a', you can pass NULL_TREE (the
> + default) to use 'a' as the value. */
>
> static tree
> -build_co_await (location_t loc, tree a, suspend_point_kind suspend_kind)
> +build_co_await (location_t loc, tree a, suspend_point_kind suspend_kind,
> + tree orig_operand = NULL_TREE)
> {
> + if (orig_operand == NULL_TREE)
> + orig_operand = a;
> +
> /* Try and overload of operator co_await, .... */
> tree o;
> if (MAYBE_CLASS_TYPE_P (TREE_TYPE (a)))
> @@ -1247,11 +1299,13 @@ build_co_await (location_t loc, tree a, suspend_point_kind suspend_kind)
> if (REFERENCE_REF_P (e_proxy))
> e_proxy = TREE_OPERAND (e_proxy, 0);
>
> + tree awrs_type = TREE_TYPE (TREE_TYPE (awrs_func));
> + tree suspend_kind_cst = build_int_cst (integer_type_node,
> + (int) suspend_kind);
> tree await_expr = build5_loc (loc, CO_AWAIT_EXPR,
> - TREE_TYPE (TREE_TYPE (awrs_func)),
> + awrs_type,
> a, e_proxy, o, awaiter_calls,
> - build_int_cst (integer_type_node,
> - (int) suspend_kind));
> + suspend_kind_cst);
> TREE_SIDE_EFFECTS (await_expr) = true;
> if (te)
> {
> @@ -1260,7 +1314,23 @@ build_co_await (location_t loc, tree a, suspend_point_kind suspend_kind)
> await_expr = te;
> }
> SET_EXPR_LOCATION (await_expr, loc);
> - return convert_from_reference (await_expr);
> +
> + if (processing_template_decl)
> + return build_template_co_await_expr (loc, awrs_type, orig_operand,
> + suspend_kind_cst);
> + return convert_from_reference (await_expr);
> +}
> +
> +/* Returns true iff EXPR or the TRAITS_CLASS, which should be a
> + coroutine_traits instance, are dependent. In those cases, we can't decide
> + what the types of our co_{await,yield,return} expressions are, so we defer
> + expansion entirely. */
> +
> +static bool
> +coro_dependent_p (tree expr, tree traits_class)
> +{
> + return type_dependent_expression_p (expr)
> + || dependent_type_p (traits_class);
> }
>
> tree
> @@ -1282,20 +1352,24 @@ finish_co_await_expr (location_t kw, tree expr)
> extraneous warnings during substitution. */
> suppress_warning (current_function_decl, OPT_Wreturn_type);
>
> - /* Defer expansion when we are processing a template.
> - FIXME: If the coroutine function's type is not dependent, and the operand
> - is not dependent, we should determine the type of the co_await expression
> - using the DEPENDENT_EXPR wrapper machinery. That allows us to determine
> - the subexpression type, but leave its operand unchanged and then
> - instantiate it later. */
> - if (processing_template_decl)
> - {
> - tree aw_expr = build5_loc (kw, CO_AWAIT_EXPR, unknown_type_node, expr,
> - NULL_TREE, NULL_TREE, NULL_TREE,
> - integer_zero_node);
> - TREE_SIDE_EFFECTS (aw_expr) = true;
> - return aw_expr;
> - }
> + /* Prepare for coroutine transformations. */
> + if (!ensure_coro_initialized (kw))
> + return error_mark_node;
> +
> + /* Get our traits class. */
> + tree traits_class = coro_get_traits_class (current_function_decl, kw);
> +
> + /* Defer expansion when we are processing a template, unless the traits type
> + and expression would not be dependent. In the case that the types are
> + not dependent but we are processing a template declaration, we will do
> + most of the computation but throw away the results (except for the
> + await_resume type). Otherwise, if our co_await is type-dependent
> + (i.e. the promise type or operand type is dependent), we can't do much,
> + and just return early with a NULL_TREE type (indicating that we cannot
> + compute the type yet). */
> + if (coro_dependent_p (expr, traits_class))
> + return build_template_co_await_expr (kw, NULL_TREE, expr,
> + integer_zero_node);
>
> /* We must be able to look up the "await_transform" method in the scope of
> the promise type, and obtain its return type. */
> @@ -1332,7 +1406,7 @@ finish_co_await_expr (location_t kw, tree expr)
> }
>
> /* Now we want to build co_await a. */
> - return build_co_await (kw, a, CO_AWAIT_SUSPEND_POINT);
> + return build_co_await (kw, a, CO_AWAIT_SUSPEND_POINT, expr);
> }
>
> /* Take the EXPR given and attempt to build:
> @@ -1359,10 +1433,22 @@ finish_co_yield_expr (location_t kw, tree expr)
> extraneous warnings during substitution. */
> suppress_warning (current_function_decl, OPT_Wreturn_type);
>
> - /* Defer expansion when we are processing a template; see FIXME in the
> - co_await code. */
> - if (processing_template_decl)
> - return build2_loc (kw, CO_YIELD_EXPR, unknown_type_node, expr, NULL_TREE);
> + /* Prepare for coroutine transformations. */
> + if (!ensure_coro_initialized (kw))
> + return error_mark_node;
> +
> + /* Get our traits class. */
> + tree traits_class = coro_get_traits_class (current_function_decl, kw);
> +
> + /* Defer expansion when we are processing a template; see note in
> + finish_co_await_expr. Also note that a yield is equivalent to
> +
> + co_await p.yield_value(EXPR)
> +
> + If either p or EXPR are type-dependent, then the whole expression is
> + certainly type-dependent, and we can't proceed. */
> + if (coro_dependent_p (expr, traits_class))
> + return build2_loc (kw, CO_YIELD_EXPR, NULL_TREE, expr, NULL_TREE);
>
> if (!coro_promise_type_found_p (current_function_decl, kw))
> /* We must be able to look up the "yield_value" method in the scope of
> @@ -1439,13 +1525,20 @@ finish_co_return_stmt (location_t kw, tree expr)
> extraneous warnings during substitution. */
> suppress_warning (current_function_decl, OPT_Wreturn_type);
>
> + /* Prepare for coroutine transformations. */
> + if (!ensure_coro_initialized (kw))
> + return error_mark_node;
> +
> + /* Get our traits class. */
> + tree traits_class = coro_get_traits_class (current_function_decl, kw);
> +
> if (processing_template_decl
> && check_for_bare_parameter_packs (expr))
> return error_mark_node;
>
> - /* Defer expansion when we are processing a template; see FIXME in the
> - co_await code. */
> - if (processing_template_decl)
> + /* Defer expansion when we must and are processing a template; see note in
> + finish_co_await_expr. */
> + if (coro_dependent_p (expr, traits_class))
> {
> /* co_return expressions are always void type, regardless of the
> expression type. */
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index 39f7e8a4e688..77fa5907c3d3 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -28651,11 +28651,6 @@ type_dependent_expression_p (tree expression)
> if (TREE_CODE (expression) == SCOPE_REF)
> return false;
>
> - /* CO_AWAIT/YIELD_EXPR with unknown type is always dependent. */
> - if (TREE_CODE (expression) == CO_AWAIT_EXPR
> - || TREE_CODE (expression) == CO_YIELD_EXPR)
> - return true;
> -
> if (BASELINK_P (expression))
> {
> if (BASELINK_OPTYPE (expression)
> diff --git a/gcc/testsuite/g++.dg/coroutines/pr112341-2.C b/gcc/testsuite/g++.dg/coroutines/pr112341-2.C
> new file mode 100644
> index 000000000000..54c7d851ae8d
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/coroutines/pr112341-2.C
> @@ -0,0 +1,25 @@
> +// https://gcc.gnu.org/PR112341
> +#include <coroutine>
> +struct A { int j; };
> +struct B {
> + bool await_ready();
> + bool await_suspend(std::coroutine_handle<>);
> + A await_resume();
> +};
> +struct C {
> + struct promise_type {
> + std::suspend_always initial_suspend();
> + std::suspend_always final_suspend() noexcept;
> + B yield_value(auto);
> + void unhandled_exception();
> + C get_return_object();
> + void return_value(A);
> + };
> +};
> +template<typename>
> +C f() {
> + // Make sure we verify types we can still.
> + (co_await B()).i; // { dg-error "'struct A' has no member named 'i'" }
> + (co_yield 123).i; // { dg-error "'struct A' has no member named 'i'" }
> + co_return 123; // { dg-error "cannot convert 'int' to 'A'" }
> +}
> diff --git a/gcc/testsuite/g++.dg/coroutines/pr112341-3.C b/gcc/testsuite/g++.dg/coroutines/pr112341-3.C
> new file mode 100644
> index 000000000000..79beab88416a
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/coroutines/pr112341-3.C
> @@ -0,0 +1,65 @@
> +// https://gcc.gnu.org/PR112341
> +#include <coroutine>
> +struct A { int j; };
> +struct B {
> + bool await_ready();
> + bool await_suspend(std::coroutine_handle<>);
> + A await_resume();
> +};
> +struct C {
> + struct promise_type {
> + std::suspend_always initial_suspend();
> + std::suspend_always final_suspend() noexcept;
> + void unhandled_exception();
> + C get_return_object();
> + void return_void();
> + std::suspend_never yield_value(int x);
> + };
> +};
> +
> +// Similar to the above, but with a template yield_value and return_value.
> +struct D {
> + struct promise_type {
> + std::suspend_always initial_suspend();
> + std::suspend_always final_suspend() noexcept;
> + void unhandled_exception();
> + D get_return_object();
> + void return_value(auto);
> + std::suspend_never yield_value(auto);
> + };
> +};
> +
> +template<typename>
> +C f() {
> + co_return;
> +}
> +
> +template<typename>
> +C g()
> +{
> + co_yield 123;
> +}
> +
> +template<typename>
> +D f1() {
> + co_return 123;
> +}
> +
> +template<typename>
> +D g1()
> +{
> + co_yield 123;
> +}
> +
> +void
> +g() {
> + f<int>();
> + f<bool>();
> + g<int>();
> + g<bool>();
> +
> + f1<int>();
> + f1<bool>();
> + g1<int>();
> + g1<bool>();
> +}
> diff --git a/gcc/testsuite/g++.dg/coroutines/pr112341.C b/gcc/testsuite/g++.dg/coroutines/pr112341.C
> new file mode 100644
> index 000000000000..ffb1fec87c02
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/coroutines/pr112341.C
> @@ -0,0 +1,21 @@
> +// https://gcc.gnu.org/PR112341
> +#include <coroutine>
> +struct A { int j; };
> +struct B {
> + bool await_ready();
> + bool await_suspend(std::coroutine_handle<>);
> + A await_resume();
> +};
> +struct C {
> + struct promise_type {
> + std::suspend_always initial_suspend();
> + std::suspend_always final_suspend() noexcept;
> + void unhandled_exception();
> + C get_return_object();
> + void return_void();
> + B yield_value(auto) { return {}; }
> + };
> +};
> +C f1(auto) { (co_await B()).j; }
> +C f2(auto) { (co_yield 0).j; }
> +void g() { f1(0); f2(0); }
> diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-yield-03-tmpl-nondependent.C b/gcc/testsuite/g++.dg/coroutines/torture/co-yield-03-tmpl-nondependent.C
> new file mode 100644
> index 000000000000..8e91d9557d2f
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-yield-03-tmpl-nondependent.C
> @@ -0,0 +1,140 @@
> +// { dg-do run }
> +
> +// Test co_yield in templated code where the promise type is not dependent.
> +
> +#include "../coro.h"
> +
> +template <typename T>
> +struct looper {
> +
> + struct promise_type {
> + T value;
> + promise_type() { PRINT ("Created Promise"); }
> + ~promise_type() { PRINT ("Destroyed Promise"); }
> +
> + auto get_return_object () {
> + PRINT ("get_return_object: handle from promise");
> + return handle_type::from_promise (*this);
> + }
> +
> + auto initial_suspend () {
> + PRINT ("get initial_suspend (always)");
> + return suspend_always_prt{};
> + }
> +
> + auto final_suspend () noexcept {
> + PRINT ("get final_suspend (always)");
> + return suspend_always_prt{};
> + }
> +
> + void return_value (T v) {
> + PRINTF ("return_value () %lf\n", (double)v);
> + value = v;
> + }
> +
> + auto yield_value (T v) {
> + PRINTF ("yield_value () %lf and suspend always\n", (double)v);
> + value = v;
> + return suspend_always_prt{};
> + }
> +
> + T get_value (void) { return value; }
> +
> + void unhandled_exception() { PRINT ("** unhandled exception"); }
> + };
> +
> + using handle_type = coro::coroutine_handle<looper::promise_type>;
> + handle_type handle;
> +
> + looper () : handle(0) {}
> + looper (handle_type _handle)
> + : handle(_handle) {
> + PRINT("Created coro1 object from handle");
> + }
> + looper (const looper &) = delete; // no copying
> + looper (looper &&s) : handle(s.handle) {
> + s.handle = nullptr;
> + PRINT("looper mv ctor ");
> + }
> + looper &operator = (looper &&s) {
> + handle = s.handle;
> + s.handle = nullptr;
> + PRINT("looper op= ");
> + return *this;
> + }
> + ~looper() {
> + PRINT("Destroyed coro1");
> + if ( handle )
> + handle.destroy();
> + }
> +
> + struct suspend_never_prt {
> + bool await_ready() const noexcept { return true; }
> + void await_suspend(handle_type) const noexcept { PRINT ("susp-never-susp"); }
> + void await_resume() const noexcept { PRINT ("susp-never-resume");}
> + };
> +
> + /* NOTE: this has a DTOR to test that pathway. */
> + struct suspend_always_prt {
> + bool await_ready() const noexcept { return false; }
> + void await_suspend(handle_type) const noexcept { PRINT ("susp-always-susp"); }
> + void await_resume() const noexcept { PRINT ("susp-always-resume"); }
> + ~suspend_always_prt() { PRINT ("susp-always-DTOR"); }
> + };
> +
> +};
> +
> +// Contrived to avoid non-scalar state across the yield.
> +template <typename>
> +looper<int> f () noexcept
> +{
> + for (int i = 5; i < 10 ; ++i)
> + {
> + PRINTF ("f: about to yield %d\n", i);
> + co_yield (int) i;
> + }
> +
> + PRINT ("f: about to return 6174");
> + co_return 6174;
> +}
> +
> +// contrived, only going to work for an int.
> +int main ()
> +{
> + PRINT ("main: create int looper");
> + auto f_coro = f<int> ();
> +
> + if (f_coro.handle.done())
> + {
> + PRINT ("main: said we were done, but we hadn't started!");
> + abort();
> + }
> +
> + PRINT ("main: OK -- looping");
> + int y, test = 5;
> + do {
> + f_coro.handle.resume();
> + if (f_coro.handle.done())
> + break;
> + y = f_coro.handle.promise().get_value();
> + if (y != test)
> + {
> + PRINTF ("main: failed for test %d, got %d\n", test, y);
> + abort();
> + }
> + test++;
> + } while (test < 20);
> +
> + y = f_coro.handle.promise().get_value();
> + if ( y != 6174 )
> + abort ();
> +
> + PRINT ("main: apparently got 6174");
> + if (!f_coro.handle.done())
> + {
> + PRINT ("main: apparently not done...");
> + abort ();
> + }
> + PRINT ("main: returning");
> + return 0;
> +}
@@ -85,6 +85,7 @@ struct GTY((for_user)) coroutine_info
tree actor_decl; /* The synthesized actor function. */
tree destroy_decl; /* The synthesized destroy function. */
tree promise_type; /* The cached promise type for this function. */
+ tree traits_type; /* The cached traits type for this function. */
tree handle_type; /* The cached coroutine handle for this function. */
tree self_h_proxy; /* A handle instance that is used as the proxy for the
one that will eventually be allocated in the coroutine
@@ -527,11 +528,12 @@ find_promise_type (tree traits_class)
return promise_type;
}
+/* Perform initialization of the coroutine processor state, if not done
+ before. */
+
static bool
-coro_promise_type_found_p (tree fndecl, location_t loc)
+ensure_coro_initialized (location_t loc)
{
- gcc_assert (fndecl != NULL_TREE);
-
if (!coro_initialized)
{
/* Trees we only need to create once.
@@ -569,6 +571,30 @@ coro_promise_type_found_p (tree fndecl, location_t loc)
coro_initialized = true;
}
+ return true;
+}
+
+/* Try to get the coroutine traits class. */
+static tree
+coro_get_traits_class (tree fndecl, location_t loc)
+{
+ gcc_assert (fndecl != NULL_TREE);
+ gcc_assert (coro_initialized);
+
+ coroutine_info *coro_info = get_or_insert_coroutine_info (fndecl);
+ auto& traits_type = coro_info->traits_type;
+ if (!traits_type)
+ traits_type = instantiate_coro_traits (fndecl, loc);
+ return traits_type;
+}
+
+static bool
+coro_promise_type_found_p (tree fndecl, location_t loc)
+{
+ gcc_assert (fndecl != NULL_TREE);
+
+ if (!ensure_coro_initialized (loc))
+ return false;
/* Save the coroutine data on the side to avoid the overhead on every
function decl tree. */
@@ -583,7 +609,7 @@ coro_promise_type_found_p (tree fndecl, location_t loc)
/* Get the coroutine traits template class instance for the function
signature we have - coroutine_traits <R, ...> */
- tree templ_class = instantiate_coro_traits (fndecl, loc);
+ tree templ_class = coro_get_traits_class (fndecl, loc);
/* Find the promise type for that. */
coro_info->promise_type = find_promise_type (templ_class);
@@ -1030,6 +1056,19 @@ coro_diagnose_throwing_final_aw_expr (tree expr)
return coro_diagnose_throwing_fn (fn);
}
+/* Build a co_await suitable for later expansion. */
+
+tree
+build_template_co_await_expr (location_t kw, tree type, tree expr, tree kind)
+{
+ tree aw_expr = build5_loc (kw, CO_AWAIT_EXPR, type, expr,
+ NULL_TREE, NULL_TREE, NULL_TREE,
+ kind);
+ TREE_SIDE_EFFECTS (aw_expr) = true;
+ return aw_expr;
+}
+
+
/* This performs [expr.await] bullet 3.3 and validates the interface obtained.
It is also used to build the initial and final suspend points.
@@ -1038,11 +1077,24 @@ coro_diagnose_throwing_final_aw_expr (tree expr)
A, the original yield/await expr, is found at source location LOC.
We will be constructing a CO_AWAIT_EXPR for a suspend point of one of
- the four suspend_point_kind kinds. This is indicated by SUSPEND_KIND. */
+ the four suspend_point_kind kinds. This is indicated by SUSPEND_KIND.
+
+ In the case that we're processing a template declaration, we can't save
+ actual awaiter expressions as the frame type will differ between
+ instantiations, but we can generate them to type-check them and compute the
+ resulting expression type. In those cases, we will return a
+ template-appropriate CO_AWAIT_EXPR and throw away the rest of the results.
+ Such an expression requires the original co_await operand unaltered. Pass
+ it as ORIG_OPERAND. If it is the same as 'a', you can pass NULL_TREE (the
+ default) to use 'a' as the value. */
static tree
-build_co_await (location_t loc, tree a, suspend_point_kind suspend_kind)
+build_co_await (location_t loc, tree a, suspend_point_kind suspend_kind,
+ tree orig_operand = NULL_TREE)
{
+ if (orig_operand == NULL_TREE)
+ orig_operand = a;
+
/* Try and overload of operator co_await, .... */
tree o;
if (MAYBE_CLASS_TYPE_P (TREE_TYPE (a)))
@@ -1247,11 +1299,13 @@ build_co_await (location_t loc, tree a, suspend_point_kind suspend_kind)
if (REFERENCE_REF_P (e_proxy))
e_proxy = TREE_OPERAND (e_proxy, 0);
+ tree awrs_type = TREE_TYPE (TREE_TYPE (awrs_func));
+ tree suspend_kind_cst = build_int_cst (integer_type_node,
+ (int) suspend_kind);
tree await_expr = build5_loc (loc, CO_AWAIT_EXPR,
- TREE_TYPE (TREE_TYPE (awrs_func)),
+ awrs_type,
a, e_proxy, o, awaiter_calls,
- build_int_cst (integer_type_node,
- (int) suspend_kind));
+ suspend_kind_cst);
TREE_SIDE_EFFECTS (await_expr) = true;
if (te)
{
@@ -1260,7 +1314,23 @@ build_co_await (location_t loc, tree a, suspend_point_kind suspend_kind)
await_expr = te;
}
SET_EXPR_LOCATION (await_expr, loc);
- return convert_from_reference (await_expr);
+
+ if (processing_template_decl)
+ return build_template_co_await_expr (loc, awrs_type, orig_operand,
+ suspend_kind_cst);
+ return convert_from_reference (await_expr);
+}
+
+/* Returns true iff EXPR or the TRAITS_CLASS, which should be a
+ coroutine_traits instance, are dependent. In those cases, we can't decide
+ what the types of our co_{await,yield,return} expressions are, so we defer
+ expansion entirely. */
+
+static bool
+coro_dependent_p (tree expr, tree traits_class)
+{
+ return type_dependent_expression_p (expr)
+ || dependent_type_p (traits_class);
}
tree
@@ -1282,20 +1352,24 @@ finish_co_await_expr (location_t kw, tree expr)
extraneous warnings during substitution. */
suppress_warning (current_function_decl, OPT_Wreturn_type);
- /* Defer expansion when we are processing a template.
- FIXME: If the coroutine function's type is not dependent, and the operand
- is not dependent, we should determine the type of the co_await expression
- using the DEPENDENT_EXPR wrapper machinery. That allows us to determine
- the subexpression type, but leave its operand unchanged and then
- instantiate it later. */
- if (processing_template_decl)
- {
- tree aw_expr = build5_loc (kw, CO_AWAIT_EXPR, unknown_type_node, expr,
- NULL_TREE, NULL_TREE, NULL_TREE,
- integer_zero_node);
- TREE_SIDE_EFFECTS (aw_expr) = true;
- return aw_expr;
- }
+ /* Prepare for coroutine transformations. */
+ if (!ensure_coro_initialized (kw))
+ return error_mark_node;
+
+ /* Get our traits class. */
+ tree traits_class = coro_get_traits_class (current_function_decl, kw);
+
+ /* Defer expansion when we are processing a template, unless the traits type
+ and expression would not be dependent. In the case that the types are
+ not dependent but we are processing a template declaration, we will do
+ most of the computation but throw away the results (except for the
+ await_resume type). Otherwise, if our co_await is type-dependent
+ (i.e. the promise type or operand type is dependent), we can't do much,
+ and just return early with a NULL_TREE type (indicating that we cannot
+ compute the type yet). */
+ if (coro_dependent_p (expr, traits_class))
+ return build_template_co_await_expr (kw, NULL_TREE, expr,
+ integer_zero_node);
/* We must be able to look up the "await_transform" method in the scope of
the promise type, and obtain its return type. */
@@ -1332,7 +1406,7 @@ finish_co_await_expr (location_t kw, tree expr)
}
/* Now we want to build co_await a. */
- return build_co_await (kw, a, CO_AWAIT_SUSPEND_POINT);
+ return build_co_await (kw, a, CO_AWAIT_SUSPEND_POINT, expr);
}
/* Take the EXPR given and attempt to build:
@@ -1359,10 +1433,22 @@ finish_co_yield_expr (location_t kw, tree expr)
extraneous warnings during substitution. */
suppress_warning (current_function_decl, OPT_Wreturn_type);
- /* Defer expansion when we are processing a template; see FIXME in the
- co_await code. */
- if (processing_template_decl)
- return build2_loc (kw, CO_YIELD_EXPR, unknown_type_node, expr, NULL_TREE);
+ /* Prepare for coroutine transformations. */
+ if (!ensure_coro_initialized (kw))
+ return error_mark_node;
+
+ /* Get our traits class. */
+ tree traits_class = coro_get_traits_class (current_function_decl, kw);
+
+ /* Defer expansion when we are processing a template; see note in
+ finish_co_await_expr. Also note that a yield is equivalent to
+
+ co_await p.yield_value(EXPR)
+
+ If either p or EXPR are type-dependent, then the whole expression is
+ certainly type-dependent, and we can't proceed. */
+ if (coro_dependent_p (expr, traits_class))
+ return build2_loc (kw, CO_YIELD_EXPR, NULL_TREE, expr, NULL_TREE);
if (!coro_promise_type_found_p (current_function_decl, kw))
/* We must be able to look up the "yield_value" method in the scope of
@@ -1439,13 +1525,20 @@ finish_co_return_stmt (location_t kw, tree expr)
extraneous warnings during substitution. */
suppress_warning (current_function_decl, OPT_Wreturn_type);
+ /* Prepare for coroutine transformations. */
+ if (!ensure_coro_initialized (kw))
+ return error_mark_node;
+
+ /* Get our traits class. */
+ tree traits_class = coro_get_traits_class (current_function_decl, kw);
+
if (processing_template_decl
&& check_for_bare_parameter_packs (expr))
return error_mark_node;
- /* Defer expansion when we are processing a template; see FIXME in the
- co_await code. */
- if (processing_template_decl)
+ /* Defer expansion when we must and are processing a template; see note in
+ finish_co_await_expr. */
+ if (coro_dependent_p (expr, traits_class))
{
/* co_return expressions are always void type, regardless of the
expression type. */
@@ -28651,11 +28651,6 @@ type_dependent_expression_p (tree expression)
if (TREE_CODE (expression) == SCOPE_REF)
return false;
- /* CO_AWAIT/YIELD_EXPR with unknown type is always dependent. */
- if (TREE_CODE (expression) == CO_AWAIT_EXPR
- || TREE_CODE (expression) == CO_YIELD_EXPR)
- return true;
-
if (BASELINK_P (expression))
{
if (BASELINK_OPTYPE (expression)
new file mode 100644
@@ -0,0 +1,25 @@
+// https://gcc.gnu.org/PR112341
+#include <coroutine>
+struct A { int j; };
+struct B {
+ bool await_ready();
+ bool await_suspend(std::coroutine_handle<>);
+ A await_resume();
+};
+struct C {
+ struct promise_type {
+ std::suspend_always initial_suspend();
+ std::suspend_always final_suspend() noexcept;
+ B yield_value(auto);
+ void unhandled_exception();
+ C get_return_object();
+ void return_value(A);
+ };
+};
+template<typename>
+C f() {
+ // Make sure we verify types we can still.
+ (co_await B()).i; // { dg-error "'struct A' has no member named 'i'" }
+ (co_yield 123).i; // { dg-error "'struct A' has no member named 'i'" }
+ co_return 123; // { dg-error "cannot convert 'int' to 'A'" }
+}
new file mode 100644
@@ -0,0 +1,65 @@
+// https://gcc.gnu.org/PR112341
+#include <coroutine>
+struct A { int j; };
+struct B {
+ bool await_ready();
+ bool await_suspend(std::coroutine_handle<>);
+ A await_resume();
+};
+struct C {
+ struct promise_type {
+ std::suspend_always initial_suspend();
+ std::suspend_always final_suspend() noexcept;
+ void unhandled_exception();
+ C get_return_object();
+ void return_void();
+ std::suspend_never yield_value(int x);
+ };
+};
+
+// Similar to the above, but with a template yield_value and return_value.
+struct D {
+ struct promise_type {
+ std::suspend_always initial_suspend();
+ std::suspend_always final_suspend() noexcept;
+ void unhandled_exception();
+ D get_return_object();
+ void return_value(auto);
+ std::suspend_never yield_value(auto);
+ };
+};
+
+template<typename>
+C f() {
+ co_return;
+}
+
+template<typename>
+C g()
+{
+ co_yield 123;
+}
+
+template<typename>
+D f1() {
+ co_return 123;
+}
+
+template<typename>
+D g1()
+{
+ co_yield 123;
+}
+
+void
+g() {
+ f<int>();
+ f<bool>();
+ g<int>();
+ g<bool>();
+
+ f1<int>();
+ f1<bool>();
+ g1<int>();
+ g1<bool>();
+}
new file mode 100644
@@ -0,0 +1,21 @@
+// https://gcc.gnu.org/PR112341
+#include <coroutine>
+struct A { int j; };
+struct B {
+ bool await_ready();
+ bool await_suspend(std::coroutine_handle<>);
+ A await_resume();
+};
+struct C {
+ struct promise_type {
+ std::suspend_always initial_suspend();
+ std::suspend_always final_suspend() noexcept;
+ void unhandled_exception();
+ C get_return_object();
+ void return_void();
+ B yield_value(auto) { return {}; }
+ };
+};
+C f1(auto) { (co_await B()).j; }
+C f2(auto) { (co_yield 0).j; }
+void g() { f1(0); f2(0); }
new file mode 100644
@@ -0,0 +1,140 @@
+// { dg-do run }
+
+// Test co_yield in templated code where the promise type is not dependent.
+
+#include "../coro.h"
+
+template <typename T>
+struct looper {
+
+ struct promise_type {
+ T value;
+ promise_type() { PRINT ("Created Promise"); }
+ ~promise_type() { PRINT ("Destroyed Promise"); }
+
+ auto get_return_object () {
+ PRINT ("get_return_object: handle from promise");
+ return handle_type::from_promise (*this);
+ }
+
+ auto initial_suspend () {
+ PRINT ("get initial_suspend (always)");
+ return suspend_always_prt{};
+ }
+
+ auto final_suspend () noexcept {
+ PRINT ("get final_suspend (always)");
+ return suspend_always_prt{};
+ }
+
+ void return_value (T v) {
+ PRINTF ("return_value () %lf\n", (double)v);
+ value = v;
+ }
+
+ auto yield_value (T v) {
+ PRINTF ("yield_value () %lf and suspend always\n", (double)v);
+ value = v;
+ return suspend_always_prt{};
+ }
+
+ T get_value (void) { return value; }
+
+ void unhandled_exception() { PRINT ("** unhandled exception"); }
+ };
+
+ using handle_type = coro::coroutine_handle<looper::promise_type>;
+ handle_type handle;
+
+ looper () : handle(0) {}
+ looper (handle_type _handle)
+ : handle(_handle) {
+ PRINT("Created coro1 object from handle");
+ }
+ looper (const looper &) = delete; // no copying
+ looper (looper &&s) : handle(s.handle) {
+ s.handle = nullptr;
+ PRINT("looper mv ctor ");
+ }
+ looper &operator = (looper &&s) {
+ handle = s.handle;
+ s.handle = nullptr;
+ PRINT("looper op= ");
+ return *this;
+ }
+ ~looper() {
+ PRINT("Destroyed coro1");
+ if ( handle )
+ handle.destroy();
+ }
+
+ struct suspend_never_prt {
+ bool await_ready() const noexcept { return true; }
+ void await_suspend(handle_type) const noexcept { PRINT ("susp-never-susp"); }
+ void await_resume() const noexcept { PRINT ("susp-never-resume");}
+ };
+
+ /* NOTE: this has a DTOR to test that pathway. */
+ struct suspend_always_prt {
+ bool await_ready() const noexcept { return false; }
+ void await_suspend(handle_type) const noexcept { PRINT ("susp-always-susp"); }
+ void await_resume() const noexcept { PRINT ("susp-always-resume"); }
+ ~suspend_always_prt() { PRINT ("susp-always-DTOR"); }
+ };
+
+};
+
+// Contrived to avoid non-scalar state across the yield.
+template <typename>
+looper<int> f () noexcept
+{
+ for (int i = 5; i < 10 ; ++i)
+ {
+ PRINTF ("f: about to yield %d\n", i);
+ co_yield (int) i;
+ }
+
+ PRINT ("f: about to return 6174");
+ co_return 6174;
+}
+
+// contrived, only going to work for an int.
+int main ()
+{
+ PRINT ("main: create int looper");
+ auto f_coro = f<int> ();
+
+ if (f_coro.handle.done())
+ {
+ PRINT ("main: said we were done, but we hadn't started!");
+ abort();
+ }
+
+ PRINT ("main: OK -- looping");
+ int y, test = 5;
+ do {
+ f_coro.handle.resume();
+ if (f_coro.handle.done())
+ break;
+ y = f_coro.handle.promise().get_value();
+ if (y != test)
+ {
+ PRINTF ("main: failed for test %d, got %d\n", test, y);
+ abort();
+ }
+ test++;
+ } while (test < 20);
+
+ y = f_coro.handle.promise().get_value();
+ if ( y != 6174 )
+ abort ();
+
+ PRINT ("main: apparently got 6174");
+ if (!f_coro.handle.done())
+ {
+ PRINT ("main: apparently not done...");
+ abort ();
+ }
+ PRINT ("main: returning");
+ return 0;
+}