Message ID | 86y15hjpx1.fsf@aarsen.me |
---|---|
State | New |
Headers | show |
Series | [v2] c++/coroutines: only defer expanding co_{await,return,yield} if dependent [PR112341] | expand |
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; > +}
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; +}