| /* coroutine-specific state, expansions and tests. |
| |
| Copyright (C) 2018-2025 Free Software Foundation, Inc. |
| |
| Contributed by Iain Sandoe <iain@sandoe.co.uk> under contract to Facebook. |
| |
| This file is part of GCC. |
| |
| GCC is free software; you can redistribute it and/or modify it under |
| the terms of the GNU General Public License as published by the Free |
| Software Foundation; either version 3, or (at your option) any later |
| version. |
| |
| GCC is distributed in the hope that it will be useful, but WITHOUT ANY |
| WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GCC; see the file COPYING3. If not see |
| <http://www.gnu.org/licenses/>. */ |
| |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| #include "target.h" |
| #include "cp-tree.h" |
| #include "stringpool.h" |
| #include "stmt.h" |
| #include "stor-layout.h" |
| #include "tree-iterator.h" |
| #include "tree.h" |
| #include "gcc-rich-location.h" |
| #include "hash-map.h" |
| #include "coroutines.h" |
| |
| /* ================= Debug. ================= */ |
| |
| #include "langhooks.h" |
| #include "cxx-pretty-print.h" |
| |
| /* Walk through the fields of the type TYP and print them to the pretty printer |
| PP. */ |
| |
| static void |
| dump_record_fields (cxx_pretty_printer *pp, tree typ) |
| { |
| pp->type_id (typ); |
| pp_newline_and_indent (pp, 2); |
| pp_left_brace (pp); |
| pp_indentation (pp) += 2; |
| |
| /* We'll be on a new line, we don't need padding. */ |
| pp->set_padding (pp_none); |
| |
| for (tree tmp = TYPE_FIELDS (typ); tmp; tmp = DECL_CHAIN (tmp)) |
| { |
| pp_newline_and_indent (pp, 0); |
| pp->declaration (tmp); |
| } |
| |
| pp_newline_and_indent (pp, -2); |
| pp_right_brace (pp); |
| pp_newline_and_indent (pp, -2); |
| } |
| |
| /* The lang-coro stream. */ |
| static FILE *dmp_str = NULL; |
| |
| /* ID of the lang-coro dump. */ |
| int coro_dump_id; |
| |
| /* Flags passed to the lang-coro dump. */ |
| static dump_flags_t coro_dump_flags; |
| |
| /* Pretty print the function FNDECL, which ought to be a coroutine before |
| co_await expansion, into the lang-coro dump, if it is enabled. */ |
| |
| static void |
| coro_maybe_dump_initial_function (tree fndecl) |
| { |
| if (!dmp_str) |
| return; |
| |
| bool lambda_p = LAMBDA_TYPE_P (DECL_CONTEXT (fndecl)); |
| fprintf (dmp_str, "%s %s original:\n", |
| (lambda_p ? "Lambda" : "Function"), |
| lang_hooks.decl_printable_name (fndecl, 2)); |
| |
| cxx_pretty_printer pp; |
| pp.set_output_stream (dmp_str); |
| pp.flags = coro_dump_flags; |
| pp.declaration (fndecl); |
| pp_newline_and_flush (&pp); |
| } |
| |
| /* Pretty print the RAMP function to the lang-coro dump, if it is enabled. */ |
| |
| static void |
| coro_maybe_dump_ramp (tree ramp) |
| { |
| if (!dmp_str) |
| return; |
| |
| cxx_pretty_printer pp; |
| pp.set_output_stream (dmp_str); |
| pp.flags = coro_dump_flags; |
| |
| pp_string (&pp, "Ramp function:"); |
| pp_newline_and_indent (&pp, 0); |
| pp.declaration (ramp); |
| pp_newline_and_flush (&pp); |
| } |
| |
| /* For a given ACTOR and DESTROY, if lang-coro dumping is enabled, pretty-print |
| their contents to the lang-coro dump. */ |
| |
| static void |
| coro_maybe_dump_transformed_functions (tree actor, tree destroy) |
| { |
| if (!dmp_str) |
| return; |
| |
| cxx_pretty_printer pp; |
| pp.set_output_stream (dmp_str); |
| pp.flags = coro_dump_flags; |
| |
| if (!actor || actor == error_mark_node) |
| { |
| pp_string (&pp, "Transform failed"); |
| pp_newline_and_flush (&pp); |
| return; |
| } |
| |
| tree frame = TREE_TYPE (TREE_TYPE (DECL_ARGUMENTS (actor))); |
| pp_string (&pp, "Frame type:"); |
| pp_newline (&pp); |
| dump_record_fields (&pp, frame); |
| pp_newline_and_flush (&pp); |
| |
| pp_string (&pp, "Actor/resumer:"); |
| pp_newline (&pp); |
| pp.declaration (actor); |
| pp_newline_and_flush (&pp); |
| |
| pp_string (&pp, "Destroyer:"); |
| pp_newline (&pp); |
| pp.declaration (destroy); |
| pp_newline_and_flush (&pp); |
| } |
| |
| /* ================= END Debug. ================= */ |
| |
| static bool coro_promise_type_found_p (tree, location_t); |
| |
| /* GCC C++ coroutines implementation. |
| |
| The user authors a function that becomes a coroutine (lazily) by |
| making use of any of the co_await, co_yield or co_return keywords. |
| |
| Unlike a regular function, where the activation record is placed on the |
| stack, and is destroyed on function exit, a coroutine has some state that |
| persists between calls - the coroutine frame (analogous to a stack frame). |
| |
| We transform the user's function into three pieces: |
| 1. A so-called ramp function, that establishes the coroutine frame and |
| begins execution of the coroutine. |
| 2. An actor function that contains the state machine corresponding to the |
| user's suspend/resume structure. |
| 3. A stub function that calls the actor function in 'destroy' mode. |
| |
| The actor function is executed: |
| * from "resume point 0" by the ramp. |
| * from resume point N ( > 0 ) for handle.resume() calls. |
| * from the destroy stub for destroy point N for handle.destroy() calls. |
| |
| The functions in this file carry out the necessary analysis of, and |
| transforms to, the AST to perform this. |
| |
| The C++ coroutine design makes use of some helper functions that are |
| authored in a so-called "promise" class provided by the user. |
| |
| At parse time (or post substitution) the type of the coroutine promise |
| will be determined. At that point, we can look up the required promise |
| class methods and issue diagnostics if they are missing or incorrect. To |
| avoid repeating these actions at code-gen time, we make use of temporary |
| 'proxy' variables for the coroutine handle and the promise - which will |
| eventually be instantiated in the coroutine frame. |
| |
| Each of the keywords will expand to a code sequence (although co_yield is |
| just syntactic sugar for a co_await). |
| |
| We defer the analysis and transformation until template expansion is |
| complete so that we have complete types at that time. |
| |
| --------------------------------------------------------------------------- |
| |
| Coroutine state, and responsibility for its release. |
| |
| As noted above, a coroutine has some state that persists across suspensions. |
| |
| The state has two components: |
| * State that is specified by the standard and persists for the entire |
| life of the coroutine. |
| * Local state that is constructed/destructed as scopes in the original |
| function body are entered/exited. The destruction of local state is |
| always the responsibility of the body code. |
| |
| The persistent state (and the overall storage for the state) must be |
| managed in two places: |
| * The ramp function (which allocates and builds this - and can, in some |
| cases, be responsible for destroying it) |
| * The re-written function body which can destroy it when that body |
| completes its final suspend - or when the handle.destroy () is called. |
| |
| In all cases the ramp holds responsibility for constructing the standard- |
| mandated persistent state. |
| |
| There are four ways in which the ramp might be re-entered after starting |
| the function body: |
| A The body could suspend (one might expect that to be the 'normal' case |
| for most coroutines). |
| B The body might complete either synchronously or via continuations. |
| C An exception might be thrown during the setup of the initial await |
| expression, before the initial awaiter resumes. |
| D An exception might be processed by promise.unhandled_exception () and |
| that, in turn, might re-throw it (or throw something else). In this |
| case, the coroutine is considered suspended at the final suspension |
| point. |
| |
| Until the ramp return value has been constructed, the ramp is considered |
| to have a use of the state. |
| |
| To manage these interacting conditions we allocate a reference counter |
| for the frame state. This is initialised to 1 by the ramp as part of its |
| startup (note that failures/exceptions in the startup code are handled |
| locally to the ramp). |
| |
| When the body returns (either normally, or by exception) the ramp releases |
| its use. |
| |
| Once the rewritten coroutine body is started, the body is considered to |
| have a use of the frame. This use (potentially) needs to be released if |
| an exception is thrown from the body. We implement this using an eh-only |
| cleanup around the initial await and function body. If we have the case |
| D above, then we do not release the use. |
| |
| In case: |
| |
| A, typically the ramp would be re-entered with the body holding a use, |
| and therefore the ramp should not destroy the state. |
| |
| B, both the body and ramp will have released their uses, and the ramp |
| should destroy the state. |
| |
| C, we must arrange for the body to release its use, because we require |
| the ramp to cleanup in this circumstance. |
| |
| D is an outlier, since the responsibility for destruction of the state |
| now rests with the user's code (via a handle.destroy() call). |
| |
| NOTE: In the case that the body has never suspended before such an |
| exception occurs, the only reasonable way for the user code to obtain the |
| necessary handle is if unhandled_exception() throws the handle or some |
| object that contains the handle. That is outside of the designs here - |
| if the user code might need this corner-case, then such provision will |
| have to be made. |
| |
| In the ramp, we implement destruction for the persistent frame state by |
| means of cleanups. These are run conditionally when the reference count |
| is 0 signalling that both the body and the ramp have completed. |
| |
| In the body, once we pass the final suspend, then we test the use and |
| delete the state if the use is 0. */ |
| |
| /* The state that we collect during parsing (and template expansion) for |
| a coroutine. */ |
| |
| struct GTY((for_user)) coroutine_info |
| { |
| tree function_decl; /* The original function decl. */ |
| 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 built in lowering. */ |
| tree promise_proxy; /* Likewise, a proxy promise instance. */ |
| tree from_address; /* handle_type from_address() function. */ |
| tree return_void; /* The expression for p.return_void(), if it exists. */ |
| location_t first_coro_keyword; /* The location of the keyword that made this |
| function into a coroutine. */ |
| |
| /* Temporary variable number assigned by get_awaitable_var. */ |
| int awaitable_number = 0; |
| |
| /* Flags to avoid repeated errors for per-function issues. */ |
| bool coro_ret_type_error_emitted; |
| bool coro_promise_error_emitted; |
| bool coro_co_return_error_emitted; |
| }; |
| |
| struct coroutine_info_hasher : ggc_ptr_hash<coroutine_info> |
| { |
| typedef tree compare_type; /* We only compare the function decl. */ |
| static inline hashval_t hash (coroutine_info *); |
| static inline hashval_t hash (const compare_type &); |
| static inline bool equal (coroutine_info *, coroutine_info *); |
| static inline bool equal (coroutine_info *, const compare_type &); |
| }; |
| |
| /* This table holds all the collected coroutine state for coroutines in |
| the current translation unit. */ |
| |
| static GTY (()) hash_table<coroutine_info_hasher> *coroutine_info_table; |
| |
| /* We will initialize state lazily. */ |
| static bool coro_initialized = false; |
| |
| /* Return a hash value for the entry pointed to by INFO. |
| The compare type is a tree, but the only trees we are going use are |
| function decls. We use the DECL_UID as the hash value since that is |
| stable across PCH. */ |
| |
| hashval_t |
| coroutine_info_hasher::hash (coroutine_info *info) |
| { |
| return DECL_UID (info->function_decl); |
| } |
| |
| /* Return a hash value for the compare value COMP. */ |
| |
| hashval_t |
| coroutine_info_hasher::hash (const compare_type& comp) |
| { |
| return DECL_UID (comp); |
| } |
| |
| /* Return true if the entries pointed to by LHS and RHS are for the |
| same coroutine. */ |
| |
| bool |
| coroutine_info_hasher::equal (coroutine_info *lhs, coroutine_info *rhs) |
| { |
| return lhs->function_decl == rhs->function_decl; |
| } |
| |
| bool |
| coroutine_info_hasher::equal (coroutine_info *lhs, const compare_type& rhs) |
| { |
| return lhs->function_decl == rhs; |
| } |
| |
| /* Get the existing coroutine_info for FN_DECL, or insert a new one if the |
| entry does not yet exist. */ |
| |
| coroutine_info * |
| get_or_insert_coroutine_info (tree fn_decl) |
| { |
| gcc_checking_assert (coroutine_info_table != NULL); |
| |
| coroutine_info **slot = coroutine_info_table->find_slot_with_hash |
| (fn_decl, coroutine_info_hasher::hash (fn_decl), INSERT); |
| |
| if (*slot == NULL) |
| { |
| *slot = new (ggc_cleared_alloc<coroutine_info> ()) coroutine_info (); |
| (*slot)->function_decl = fn_decl; |
| } |
| |
| return *slot; |
| } |
| |
| /* Get the existing coroutine_info for FN_DECL, fail if it doesn't exist. */ |
| |
| coroutine_info * |
| get_coroutine_info (tree fn_decl) |
| { |
| if (coroutine_info_table == NULL) |
| return NULL; |
| |
| coroutine_info **slot = coroutine_info_table->find_slot_with_hash |
| (fn_decl, coroutine_info_hasher::hash (fn_decl), NO_INSERT); |
| if (slot) |
| return *slot; |
| return NULL; |
| } |
| |
| /* We will lazily create all the identifiers that are used by coroutines |
| on the first attempt to lookup the traits. */ |
| |
| /* Identifiers that are used by all coroutines. */ |
| |
| static GTY(()) tree coro_traits_identifier; |
| static GTY(()) tree coro_handle_identifier; |
| static GTY(()) tree coro_promise_type_identifier; |
| |
| /* Required promise method name identifiers. */ |
| |
| static GTY(()) tree coro_await_transform_identifier; |
| static GTY(()) tree coro_initial_suspend_identifier; |
| static GTY(()) tree coro_final_suspend_identifier; |
| static GTY(()) tree coro_return_void_identifier; |
| static GTY(()) tree coro_return_value_identifier; |
| static GTY(()) tree coro_yield_value_identifier; |
| static GTY(()) tree coro_address_identifier; |
| static GTY(()) tree coro_from_address_identifier; |
| static GTY(()) tree coro_get_return_object_identifier; |
| static GTY(()) tree coro_gro_on_allocation_fail_identifier; |
| static GTY(()) tree coro_unhandled_exception_identifier; |
| |
| /* Awaitable methods. */ |
| |
| static GTY(()) tree coro_await_ready_identifier; |
| static GTY(()) tree coro_await_suspend_identifier; |
| static GTY(()) tree coro_await_resume_identifier; |
| |
| /* Accessors for the coroutine frame state used by the implementation. */ |
| |
| static GTY(()) tree coro_resume_fn_id; |
| static GTY(()) tree coro_destroy_fn_id; |
| static GTY(()) tree coro_promise_id; |
| static GTY(()) tree coro_frame_needs_free_id; |
| static GTY(()) tree coro_resume_index_id; |
| static GTY(()) tree coro_self_handle_id; |
| static GTY(()) tree coro_actor_continue_id; |
| static GTY(()) tree coro_frame_i_a_r_c_id; |
| static GTY(()) tree coro_frame_refcount_id; |
| |
| /* Create the identifiers used by the coroutines library interfaces and |
| the implementation frame state. */ |
| |
| static void |
| coro_init_identifiers () |
| { |
| coro_traits_identifier = get_identifier ("coroutine_traits"); |
| coro_handle_identifier = get_identifier ("coroutine_handle"); |
| coro_promise_type_identifier = get_identifier ("promise_type"); |
| |
| coro_await_transform_identifier = get_identifier ("await_transform"); |
| coro_initial_suspend_identifier = get_identifier ("initial_suspend"); |
| coro_final_suspend_identifier = get_identifier ("final_suspend"); |
| coro_return_void_identifier = get_identifier ("return_void"); |
| coro_return_value_identifier = get_identifier ("return_value"); |
| coro_yield_value_identifier = get_identifier ("yield_value"); |
| coro_address_identifier = get_identifier ("address"); |
| coro_from_address_identifier = get_identifier ("from_address"); |
| coro_get_return_object_identifier = get_identifier ("get_return_object"); |
| coro_gro_on_allocation_fail_identifier = |
| get_identifier ("get_return_object_on_allocation_failure"); |
| coro_unhandled_exception_identifier = get_identifier ("unhandled_exception"); |
| |
| coro_await_ready_identifier = get_identifier ("await_ready"); |
| coro_await_suspend_identifier = get_identifier ("await_suspend"); |
| coro_await_resume_identifier = get_identifier ("await_resume"); |
| |
| /* Coroutine state frame field accessors. */ |
| coro_resume_fn_id = get_identifier ("_Coro_resume_fn"); |
| coro_destroy_fn_id = get_identifier ("_Coro_destroy_fn"); |
| coro_promise_id = get_identifier ("_Coro_promise"); |
| coro_frame_needs_free_id = get_identifier ("_Coro_frame_needs_free"); |
| coro_frame_i_a_r_c_id = get_identifier ("_Coro_initial_await_resume_called"); |
| coro_resume_index_id = get_identifier ("_Coro_resume_index"); |
| coro_self_handle_id = get_identifier ("_Coro_self_handle"); |
| coro_actor_continue_id = get_identifier ("_Coro_actor_continue"); |
| coro_frame_refcount_id = get_identifier ("_Coro_frame_refcount"); |
| } |
| |
| /* Trees we only need to set up once. */ |
| |
| static GTY(()) tree coro_traits_templ; |
| static GTY(()) tree coro_handle_templ; |
| static GTY(()) tree void_coro_handle_type; |
| static GTY(()) tree void_coro_handle_address; |
| |
| /* ================= Parse, Semantics and Type checking ================= */ |
| |
| /* This initial set of routines are helper for the parsing and template |
| expansion phases. |
| |
| At the completion of this, we will have completed trees for each of the |
| keywords, but making use of proxy variables for the self-handle and the |
| promise class instance. */ |
| |
| /* [coroutine.traits] |
| Lookup the coroutine_traits template decl. */ |
| |
| static tree |
| find_coro_traits_template_decl (location_t kw) |
| { |
| /* If we are missing fundamental information, such as the traits, (or the |
| declaration found is not a type template), then don't emit an error for |
| every keyword in a TU, just do it once. */ |
| static bool traits_error_emitted = false; |
| |
| tree traits_decl = lookup_qualified_name (std_node, coro_traits_identifier, |
| LOOK_want::NORMAL, |
| /*complain=*/!traits_error_emitted); |
| if (traits_decl == error_mark_node |
| || !DECL_TYPE_TEMPLATE_P (traits_decl)) |
| { |
| if (!traits_error_emitted) |
| { |
| auto_diagnostic_group d; |
| gcc_rich_location richloc (kw); |
| error_at (&richloc, "coroutines require a traits template; cannot" |
| " find %<%E::%E%>", std_node, coro_traits_identifier); |
| inform (&richloc, "perhaps %<#include <coroutine>%> is missing"); |
| traits_error_emitted = true; |
| } |
| return NULL_TREE; |
| } |
| else |
| return traits_decl; |
| } |
| |
| /* Instantiate Coroutine traits for the function signature. */ |
| |
| static tree |
| instantiate_coro_traits (tree fndecl, location_t kw) |
| { |
| /* [coroutine.traits.primary] |
| So now build up a type list for the template <typename _R, typename...>. |
| The types are the function's arg types and _R is the function return |
| type. */ |
| |
| tree functyp = TREE_TYPE (fndecl); |
| tree arg = DECL_ARGUMENTS (fndecl); |
| tree arg_node = TYPE_ARG_TYPES (functyp); |
| tree argtypes = make_tree_vec (list_length (arg_node)-1); |
| unsigned p = 0; |
| |
| while (arg_node != NULL_TREE && !VOID_TYPE_P (TREE_VALUE (arg_node))) |
| { |
| if (is_this_parameter (arg) |
| || DECL_NAME (arg) == closure_identifier) |
| { |
| /* We pass a reference to *this to the param preview. */ |
| tree ct = TREE_TYPE (TREE_TYPE (arg)); |
| TREE_VEC_ELT (argtypes, p++) = cp_build_reference_type (ct, false); |
| } |
| else |
| TREE_VEC_ELT (argtypes, p++) = TREE_VALUE (arg_node); |
| |
| arg_node = TREE_CHAIN (arg_node); |
| arg = DECL_CHAIN (arg); |
| } |
| |
| tree argtypepack = cxx_make_type (TYPE_ARGUMENT_PACK); |
| ARGUMENT_PACK_ARGS (argtypepack) = argtypes; |
| |
| tree targ = make_tree_vec (2); |
| TREE_VEC_ELT (targ, 0) = TREE_TYPE (functyp); |
| TREE_VEC_ELT (targ, 1) = argtypepack; |
| |
| tree traits_class |
| = lookup_template_class (coro_traits_templ, targ, |
| /*in_decl=*/NULL_TREE, /*context=*/NULL_TREE, |
| tf_warning_or_error); |
| |
| if (traits_class == error_mark_node) |
| { |
| error_at (kw, "cannot instantiate %<coroutine traits%>"); |
| return NULL_TREE; |
| } |
| |
| return traits_class; |
| } |
| |
| /* [coroutine.handle] */ |
| |
| static tree |
| find_coro_handle_template_decl (location_t kw) |
| { |
| /* As for the coroutine traits, this error is per TU, so only emit |
| it once. */ |
| static bool coro_handle_error_emitted = false; |
| tree handle_decl = lookup_qualified_name (std_node, coro_handle_identifier, |
| LOOK_want::NORMAL, |
| !coro_handle_error_emitted); |
| if (handle_decl == error_mark_node |
| || !DECL_CLASS_TEMPLATE_P (handle_decl)) |
| { |
| if (!coro_handle_error_emitted) |
| error_at (kw, "coroutines require a handle class template;" |
| " cannot find %<%E::%E%>", std_node, coro_handle_identifier); |
| coro_handle_error_emitted = true; |
| return NULL_TREE; |
| } |
| else |
| return handle_decl; |
| } |
| |
| /* Get and validate HANDLE_TYPE::address. The resulting function, if any, will |
| be a non-overloaded member function that takes no arguments and returns |
| void*. If that is not the case, signals an error and returns NULL_TREE. */ |
| |
| static tree |
| get_handle_type_address (location_t kw, tree handle_type) |
| { |
| tree addr_getter = lookup_member (handle_type, coro_address_identifier, 1, |
| 0, tf_warning_or_error); |
| if (!addr_getter || addr_getter == error_mark_node) |
| { |
| qualified_name_lookup_error (handle_type, coro_address_identifier, |
| error_mark_node, kw); |
| return NULL_TREE; |
| } |
| |
| if (!BASELINK_P (addr_getter) |
| || TREE_CODE (TREE_TYPE (addr_getter)) != METHOD_TYPE) |
| { |
| error_at (kw, "%qE must be a non-overloaded method", addr_getter); |
| return NULL_TREE; |
| } |
| |
| tree fn_t = TREE_TYPE (addr_getter); |
| tree arg = TYPE_ARG_TYPES (fn_t); |
| |
| /* Skip the 'this' pointer. */ |
| arg = TREE_CHAIN (arg); |
| |
| /* Check that from_addr has the argument list (). */ |
| if (arg != void_list_node) |
| { |
| error_at (kw, "%qE must take no arguments", addr_getter); |
| return NULL_TREE; |
| } |
| |
| tree ret_t = TREE_TYPE (fn_t); |
| if (!same_type_p (ret_t, ptr_type_node)) |
| { |
| error_at (kw, "%qE must return %qT, not %qT", |
| addr_getter, ptr_type_node, ret_t); |
| return NULL_TREE; |
| } |
| |
| return addr_getter; |
| } |
| |
| /* Get and validate HANDLE_TYPE::from_address. The resulting function, if |
| any, will be a non-overloaded static function that takes a single void* and |
| returns HANDLE_TYPE. If that is not the case, signals an error and returns |
| NULL_TREE. */ |
| |
| static tree |
| get_handle_type_from_address (location_t kw, tree handle_type) |
| { |
| tree from_addr = lookup_member (handle_type, coro_from_address_identifier, 1, |
| 0, tf_warning_or_error); |
| if (!from_addr || from_addr == error_mark_node) |
| { |
| qualified_name_lookup_error (handle_type, coro_from_address_identifier, |
| error_mark_node, kw); |
| return NULL_TREE; |
| } |
| if (!BASELINK_P (from_addr) |
| || TREE_CODE (TREE_TYPE (from_addr)) != FUNCTION_TYPE) |
| { |
| error_at (kw, "%qE must be a non-overloaded static function", from_addr); |
| return NULL_TREE; |
| } |
| |
| tree fn_t = TREE_TYPE (from_addr); |
| tree arg = TYPE_ARG_TYPES (fn_t); |
| /* Check that from_addr has the argument list (void*). */ |
| if (!arg |
| || !same_type_p (TREE_VALUE (arg), ptr_type_node) |
| || TREE_CHAIN (arg) != void_list_node) |
| { |
| error_at (kw, "%qE must take a single %qT", from_addr, ptr_type_node); |
| return NULL_TREE; |
| } |
| |
| tree ret_t = TREE_TYPE (fn_t); |
| if (!same_type_p (ret_t, handle_type)) |
| { |
| error_at (kw, "%qE must return %qT, not %qT", |
| from_addr, handle_type, ret_t); |
| return NULL_TREE; |
| } |
| |
| return from_addr; |
| } |
| |
| static tree |
| instantiate_coro_handle_for_promise_type (location_t kw, tree promise_type) |
| { |
| /* So now build up a type list for the template, one entry, the promise. */ |
| tree targ = make_tree_vec (1); |
| TREE_VEC_ELT (targ, 0) = promise_type; |
| tree handle_type |
| = lookup_template_class (coro_handle_identifier, targ, |
| /* in_decl=*/NULL_TREE, |
| /* context=*/std_node, |
| tf_warning_or_error); |
| |
| if (handle_type == error_mark_node) |
| { |
| error_at (kw, "cannot instantiate a %<coroutine handle%> for" |
| " promise type %qT", promise_type); |
| return NULL_TREE; |
| } |
| |
| return handle_type; |
| } |
| |
| /* Look for the promise_type in the instantiated traits. */ |
| |
| static tree |
| find_promise_type (tree traits_class) |
| { |
| tree promise_type |
| = lookup_member (traits_class, coro_promise_type_identifier, |
| /* protect=*/1, /*want_type=*/true, tf_warning_or_error); |
| |
| if (promise_type) |
| promise_type |
| = complete_type_or_else (TREE_TYPE (promise_type), promise_type); |
| |
| /* NULL_TREE on fail. */ |
| return promise_type; |
| } |
| |
| /* Perform initialization of the coroutine processor state, if not done |
| before. */ |
| |
| static bool |
| ensure_coro_initialized (location_t loc) |
| { |
| if (!coro_initialized) |
| { |
| /* Trees we only need to create once. |
| Set up the identifiers we will use. */ |
| coro_init_identifiers (); |
| |
| /* Coroutine traits template. */ |
| coro_traits_templ = find_coro_traits_template_decl (loc); |
| if (coro_traits_templ == NULL_TREE) |
| return false; |
| |
| /* coroutine_handle<> template. */ |
| coro_handle_templ = find_coro_handle_template_decl (loc); |
| if (coro_handle_templ == NULL_TREE) |
| return false; |
| |
| /* We can also instantiate the void coroutine_handle<> */ |
| void_coro_handle_type |
| = instantiate_coro_handle_for_promise_type (loc, void_type_node); |
| if (void_coro_handle_type == NULL_TREE) |
| return false; |
| |
| void_coro_handle_address |
| = get_handle_type_address (loc, void_coro_handle_type); |
| 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); |
| |
| if (coroutine_info_table == NULL) |
| return false; |
| |
| 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. */ |
| |
| coroutine_info *coro_info = get_or_insert_coroutine_info (fndecl); |
| /* Without this, we cannot really proceed. */ |
| gcc_checking_assert (coro_info); |
| |
| /* If we don't already have a current promise type, try to look it up. */ |
| if (coro_info->promise_type == NULL_TREE) |
| { |
| /* Get the coroutine traits template class instance for the function |
| signature we have - coroutine_traits <R, ...> */ |
| |
| tree templ_class = coro_get_traits_class (fndecl, loc); |
| |
| /* Find the promise type for that. */ |
| coro_info->promise_type = find_promise_type (templ_class); |
| |
| /* If we don't find it, punt on the rest. */ |
| if (coro_info->promise_type == NULL_TREE) |
| { |
| if (!coro_info->coro_promise_error_emitted) |
| error_at (loc, "unable to find the promise type for" |
| " this coroutine"); |
| coro_info->coro_promise_error_emitted = true; |
| return false; |
| } |
| |
| /* Test for errors in the promise type that can be determined now. */ |
| tree has_ret_void = lookup_member (coro_info->promise_type, |
| coro_return_void_identifier, |
| /*protect=*/1, /*want_type=*/0, |
| tf_none); |
| tree has_ret_val = lookup_member (coro_info->promise_type, |
| coro_return_value_identifier, |
| /*protect=*/1, /*want_type=*/0, |
| tf_none); |
| if (has_ret_void && has_ret_val) |
| { |
| auto_diagnostic_group d; |
| location_t ploc = DECL_SOURCE_LOCATION (fndecl); |
| if (!coro_info->coro_co_return_error_emitted) |
| error_at (ploc, "the coroutine promise type %qT declares both" |
| " %<return_value%> and %<return_void%>", |
| coro_info->promise_type); |
| inform (DECL_SOURCE_LOCATION (BASELINK_FUNCTIONS (has_ret_void)), |
| "%<return_void%> declared here"); |
| has_ret_val = BASELINK_FUNCTIONS (has_ret_val); |
| const char *message = "%<return_value%> declared here"; |
| if (TREE_CODE (has_ret_val) == OVERLOAD) |
| { |
| has_ret_val = OVL_FIRST (has_ret_val); |
| message = "%<return_value%> first declared here"; |
| } |
| inform (DECL_SOURCE_LOCATION (has_ret_val), message); |
| coro_info->coro_co_return_error_emitted = true; |
| return false; |
| } |
| |
| /* Try to find the handle type for the promise. */ |
| tree handle_type |
| = instantiate_coro_handle_for_promise_type (loc, coro_info->promise_type); |
| if (handle_type == NULL_TREE) |
| return false; |
| tree from_address = get_handle_type_from_address (loc, handle_type); |
| if (from_address == NULL_TREE) |
| return false; |
| |
| /* Complete this, we're going to use it. */ |
| coro_info->handle_type = complete_type_or_else (handle_type, fndecl); |
| coro_info->from_address = from_address; |
| |
| /* Diagnostic would be emitted by complete_type_or_else. */ |
| if (!coro_info->handle_type) |
| return false; |
| |
| /* Build a proxy for a handle to "self" as the param to |
| await_suspend() calls. */ |
| coro_info->self_h_proxy |
| = build_lang_decl (VAR_DECL, coro_self_handle_id, |
| coro_info->handle_type); |
| |
| /* 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); |
| |
| /* Note where we first saw a coroutine keyword. */ |
| coro_info->first_coro_keyword = loc; |
| } |
| |
| return true; |
| } |
| |
| /* Map from actor or destroyer to ramp. */ |
| static GTY(()) hash_map<tree, tree> *to_ramp; |
| |
| /* Given a tree that is an actor or destroy, find the ramp function. */ |
| |
| tree |
| coro_get_ramp_function (tree decl) |
| { |
| if (!to_ramp) |
| return NULL_TREE; |
| tree *p = to_ramp->get (decl); |
| if (p) |
| return *p; |
| return NULL_TREE; |
| } |
| |
| /* Given the DECL for a ramp function (the user's original declaration) return |
| the actor function if it has been defined. */ |
| |
| tree |
| coro_get_actor_function (tree decl) |
| { |
| if (coroutine_info *info = get_coroutine_info (decl)) |
| return info->actor_decl; |
| |
| return NULL_TREE; |
| } |
| |
| /* Given the DECL for a ramp function (the user's original declaration) return |
| the destroy function if it has been defined. */ |
| |
| tree |
| coro_get_destroy_function (tree decl) |
| { |
| if (coroutine_info *info = get_coroutine_info (decl)) |
| return info->destroy_decl; |
| |
| return NULL_TREE; |
| } |
| |
| /* Given a CO_AWAIT_EXPR AWAIT_EXPR, return its resume call. */ |
| |
| tree |
| co_await_get_resume_call (tree await_expr) |
| { |
| gcc_checking_assert (TREE_CODE (await_expr) == CO_AWAIT_EXPR); |
| tree vec = TREE_OPERAND (await_expr, 3); |
| if (!vec) |
| return nullptr; |
| return TREE_VEC_ELT (vec, 2); |
| } |
| |
| |
| /* These functions assumes that the caller has verified that the state for |
| the decl has been initialized, we try to minimize work here. */ |
| |
| static tree |
| get_coroutine_promise_type (tree decl) |
| { |
| if (coroutine_info *info = get_coroutine_info (decl)) |
| return info->promise_type; |
| |
| return NULL_TREE; |
| } |
| |
| static tree |
| get_coroutine_handle_type (tree decl) |
| { |
| if (coroutine_info *info = get_coroutine_info (decl)) |
| return info->handle_type; |
| |
| return NULL_TREE; |
| } |
| |
| static tree |
| get_coroutine_self_handle_proxy (tree decl) |
| { |
| if (coroutine_info *info = get_coroutine_info (decl)) |
| return info->self_h_proxy; |
| |
| return NULL_TREE; |
| } |
| |
| static tree |
| get_coroutine_promise_proxy (tree decl) |
| { |
| if (coroutine_info *info = get_coroutine_info (decl)) |
| return info->promise_proxy; |
| |
| return NULL_TREE; |
| } |
| |
| static tree |
| get_coroutine_from_address (tree decl) |
| { |
| if (coroutine_info *info = get_coroutine_info (decl)) |
| return info->from_address; |
| |
| return NULL_TREE; |
| } |
| |
| static tree |
| lookup_promise_method (tree fndecl, tree member_id, location_t loc, |
| bool musthave) |
| { |
| tree promise = get_coroutine_promise_type (fndecl); |
| tree pm_memb |
| = lookup_member (promise, member_id, |
| /*protect=*/1, /*want_type=*/0, tf_warning_or_error); |
| if (musthave && pm_memb == NULL_TREE) |
| { |
| error_at (loc, "no member named %qE in %qT", member_id, promise); |
| return error_mark_node; |
| } |
| return pm_memb; |
| } |
| |
| /* Build an expression of the form p.method (args) where the p is a promise |
| object for the current coroutine. |
| OBJECT is the promise object instance to use, it may be NULL, in which case |
| we will use the promise_proxy instance for this coroutine. |
| ARGS may be NULL, for empty parm lists. */ |
| |
| static tree |
| coro_build_promise_expression (tree fn, tree promise_obj, tree member_id, |
| location_t loc, vec<tree, va_gc> **args, |
| bool musthave) |
| { |
| tree meth = lookup_promise_method (fn, member_id, loc, musthave); |
| if (meth == error_mark_node) |
| return error_mark_node; |
| |
| /* If we don't find it, and it isn't needed, an empty return is OK. */ |
| if (!meth) |
| return NULL_TREE; |
| |
| tree promise |
| = promise_obj ? promise_obj |
| : get_coroutine_promise_proxy (current_function_decl); |
| tree expr; |
| if (BASELINK_P (meth)) |
| expr = build_new_method_call (promise, meth, args, NULL_TREE, |
| LOOKUP_NORMAL, NULL, tf_warning_or_error); |
| else |
| { |
| expr = build_class_member_access_expr (promise, meth, NULL_TREE, |
| true, tf_warning_or_error); |
| vec<tree, va_gc> *real_args; |
| if (!args) |
| real_args = make_tree_vector (); |
| else |
| real_args = *args; |
| expr = build_op_call (expr, &real_args, tf_warning_or_error); |
| } |
| return expr; |
| } |
| |
| /* Caching get for the expression p.return_void (). */ |
| |
| static tree |
| get_coroutine_return_void_expr (tree decl, location_t loc, bool musthave) |
| { |
| if (coroutine_info *info = get_coroutine_info (decl)) |
| { |
| /* If we don't have it try to build it. */ |
| if (!info->return_void) |
| info->return_void |
| = coro_build_promise_expression (current_function_decl, NULL, |
| coro_return_void_identifier, |
| loc, NULL, musthave); |
| /* Don't return an error if it's an optional call. */ |
| if (!musthave && info->return_void == error_mark_node) |
| return NULL_TREE; |
| return info->return_void; |
| } |
| return musthave ? error_mark_node : NULL_TREE; |
| } |
| |
| /* Lookup an Awaitable member, which should be await_ready, await_suspend |
| or await_resume. */ |
| |
| static tree |
| lookup_awaitable_member (tree await_type, tree member_id, location_t loc) |
| { |
| tree aw_memb |
| = lookup_member (await_type, member_id, |
| /*protect=*/1, /*want_type=*/0, tf_warning_or_error); |
| if (aw_memb == NULL_TREE) |
| { |
| error_at (loc, "no member named %qE in %qT", member_id, await_type); |
| return error_mark_node; |
| } |
| return aw_memb; |
| } |
| |
| /* Here we check the constraints that are common to all keywords (since the |
| presence of a coroutine keyword makes the function into a coroutine). */ |
| |
| static bool |
| coro_common_keyword_context_valid_p (tree fndecl, location_t kw_loc, |
| const char *kw_name) |
| { |
| if (fndecl == NULL_TREE) |
| { |
| error_at (kw_loc, "%qs cannot be used outside a function", kw_name); |
| return false; |
| } |
| |
| /* This is arranged in order of prohibitions in the std. */ |
| if (DECL_MAIN_P (fndecl)) |
| { |
| /* [basic.start.main] 3. The function main shall not be a coroutine. */ |
| error_at (kw_loc, "%qs cannot be used in the %<main%> function", |
| kw_name); |
| return false; |
| } |
| |
| if (DECL_DECLARED_CONSTEXPR_P (fndecl)) |
| { |
| cp_function_chain->invalid_constexpr = true; |
| if (!is_instantiation_of_constexpr (fndecl)) |
| { |
| /* [dcl.constexpr] 3.3 it shall not be a coroutine. */ |
| error_at (kw_loc, "%qs cannot be used in a %<constexpr%> function", |
| kw_name); |
| return false; |
| } |
| } |
| |
| if (FNDECL_USED_AUTO (fndecl)) |
| { |
| /* [dcl.spec.auto] 15. A function declared with a return type that uses |
| a placeholder type shall not be a coroutine. */ |
| error_at (kw_loc, |
| "%qs cannot be used in a function with a deduced return type", |
| kw_name); |
| return false; |
| } |
| |
| if (varargs_function_p (fndecl)) |
| { |
| /* [dcl.fct.def.coroutine] The parameter-declaration-clause of the |
| coroutine shall not terminate with an ellipsis that is not part |
| of a parameter-declaration. */ |
| error_at (kw_loc, |
| "%qs cannot be used in a varargs function", kw_name); |
| return false; |
| } |
| |
| if (DECL_CONSTRUCTOR_P (fndecl)) |
| { |
| /* [class.ctor] 7. a constructor shall not be a coroutine. */ |
| error_at (kw_loc, "%qs cannot be used in a constructor", kw_name); |
| return false; |
| } |
| |
| if (DECL_DESTRUCTOR_P (fndecl)) |
| { |
| /* [class.dtor] 21. a destructor shall not be a coroutine. */ |
| error_at (kw_loc, "%qs cannot be used in a destructor", kw_name); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* Here we check the constraints that are not per keyword. */ |
| |
| static bool |
| coro_function_valid_p (tree fndecl) |
| { |
| location_t f_loc = DECL_SOURCE_LOCATION (fndecl); |
| |
| /* For cases where fundamental information cannot be found, e.g. the |
| coroutine traits are missing, we need to punt early. */ |
| if (!coro_promise_type_found_p (fndecl, f_loc)) |
| return false; |
| |
| /* Since we think the function is a coroutine, that implies we parsed |
| a keyword that triggered this. Keywords check promise validity for |
| their context and thus the promise type should be known at this point. */ |
| if (get_coroutine_handle_type (fndecl) == NULL_TREE |
| || get_coroutine_promise_type (fndecl) == NULL_TREE) |
| return false; |
| |
| if (current_function_returns_value || current_function_returns_null) |
| { |
| /* TODO: record or extract positions of returns (and the first coro |
| keyword) so that we can add notes to the diagnostic about where |
| the bad keyword is and what made the function into a coro. */ |
| error_at (f_loc, "a %<return%> statement is not allowed in coroutine;" |
| " did you mean %<co_return%>?"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| enum suspend_point_kind { |
| CO_AWAIT_SUSPEND_POINT = 0, |
| CO_YIELD_SUSPEND_POINT, |
| INITIAL_SUSPEND_POINT, |
| FINAL_SUSPEND_POINT |
| }; |
| |
| /* Helper function to build a named variable for the temps we use for each |
| await point. The root of the name is determined by SUSPEND_KIND, and |
| the variable is of type V_TYPE. The awaitable number is reset each time |
| we encounter a final suspend. */ |
| |
| static tree |
| get_awaitable_var (suspend_point_kind suspend_kind, tree v_type) |
| { |
| auto cinfo = get_coroutine_info (current_function_decl); |
| gcc_checking_assert (cinfo); |
| char *buf; |
| switch (suspend_kind) |
| { |
| default: buf = xasprintf ("Aw%d", cinfo->awaitable_number++); break; |
| case CO_YIELD_SUSPEND_POINT: |
| buf = xasprintf ("Yd%d", cinfo->awaitable_number++); |
| break; |
| case INITIAL_SUSPEND_POINT: buf = xasprintf ("Is"); break; |
| case FINAL_SUSPEND_POINT: buf = xasprintf ("Fs"); break; |
| } |
| tree ret = get_identifier (buf); |
| free (buf); |
| ret = build_lang_decl (VAR_DECL, ret, v_type); |
| DECL_ARTIFICIAL (ret) = true; |
| return ret; |
| } |
| |
| /* Helpers to diagnose missing noexcept on final await expressions. */ |
| |
| static bool |
| coro_diagnose_throwing_fn (tree fndecl) |
| { |
| if (!TYPE_NOTHROW_P (TREE_TYPE (fndecl))) |
| { |
| auto_diagnostic_group d; |
| location_t f_loc = cp_expr_loc_or_loc (fndecl, |
| DECL_SOURCE_LOCATION (fndecl)); |
| error_at (f_loc, "the expression %qE is required to be non-throwing", |
| fndecl); |
| inform (f_loc, "must be declared with %<noexcept(true)%>"); |
| return true; |
| } |
| return false; |
| } |
| |
| static bool |
| coro_diagnose_throwing_final_aw_expr (tree expr) |
| { |
| if (TREE_CODE (expr) == TARGET_EXPR) |
| expr = TARGET_EXPR_INITIAL (expr); |
| tree fn = NULL_TREE; |
| if (TREE_CODE (expr) == CALL_EXPR) |
| fn = CALL_EXPR_FN (expr); |
| else if (TREE_CODE (expr) == AGGR_INIT_EXPR) |
| fn = AGGR_INIT_EXPR_FN (expr); |
| else if (TREE_CODE (expr) == CONSTRUCTOR) |
| return false; |
| else |
| { |
| gcc_checking_assert (0 && "unhandled expression type"); |
| return false; |
| } |
| fn = TREE_OPERAND (fn, 0); |
| 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; |
| } |
| |
| /* Is EXPR an lvalue that will refer to the same object after a resume? |
| |
| This is close to asking tree_invariant_p of its address, but that doesn't |
| distinguish temporaries from other variables. */ |
| |
| static bool |
| is_stable_lvalue (tree expr) |
| { |
| if (TREE_SIDE_EFFECTS (expr)) |
| return false; |
| |
| for (; handled_component_p (expr); |
| expr = TREE_OPERAND (expr, 0)) |
| { |
| if (TREE_CODE (expr) == ARRAY_REF |
| && !TREE_CONSTANT (TREE_OPERAND (expr, 1))) |
| return false; |
| } |
| |
| return (TREE_CODE (expr) == PARM_DECL |
| || (VAR_P (expr) && !is_local_temp (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. |
| |
| 'a', 'o' and 'e' are used as per the description in the section noted. |
| |
| 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. |
| |
| 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, |
| 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))) |
| { |
| o = build_new_op (loc, CO_AWAIT_EXPR, LOOKUP_NORMAL, a, NULL_TREE, |
| NULL_TREE, NULL_TREE, NULL, tf_warning_or_error); |
| /* If no viable functions are found, o is a. */ |
| if (!o || o == error_mark_node) |
| o = a; |
| else if (flag_exceptions && suspend_kind == FINAL_SUSPEND_POINT) |
| { |
| /* We found an overload for co_await(), diagnose throwing cases. */ |
| if (TREE_CODE (o) == TARGET_EXPR |
| && coro_diagnose_throwing_final_aw_expr (o)) |
| return error_mark_node; |
| |
| /* We now know that the final suspend object is distinct from the |
| final awaiter, so check for a non-throwing DTOR where needed. */ |
| if (tree dummy = cxx_maybe_build_cleanup (a, tf_none)) |
| { |
| if (CONVERT_EXPR_P (dummy)) |
| dummy = TREE_OPERAND (dummy, 0); |
| dummy = TREE_OPERAND (CALL_EXPR_FN (dummy), 0); |
| if (coro_diagnose_throwing_fn (dummy)) |
| return error_mark_node; |
| } |
| } |
| } |
| else |
| o = a; /* This is most likely about to fail anyway. */ |
| |
| tree o_type = TREE_TYPE (o); |
| if (o_type && !VOID_TYPE_P (o_type)) |
| o_type = complete_type_or_else (o_type, o); |
| |
| if (!o_type || o_type == error_mark_node) |
| return error_mark_node; |
| |
| if (TREE_CODE (o_type) != RECORD_TYPE) |
| { |
| if (suspend_kind == FINAL_SUSPEND_POINT) |
| error_at (loc, "%qs awaitable type %qT is not a structure", |
| "final_suspend()", o_type); |
| else if (suspend_kind == INITIAL_SUSPEND_POINT) |
| error_at (loc, "%qs awaitable type %qT is not a structure", |
| "initial_suspend()", o_type); |
| else |
| error_at (loc, "awaitable type %qT is not a structure", o_type); |
| return error_mark_node; |
| } |
| |
| /* Check for required awaitable members and their types. */ |
| tree awrd_meth |
| = lookup_awaitable_member (o_type, coro_await_ready_identifier, loc); |
| if (!awrd_meth || awrd_meth == error_mark_node) |
| return error_mark_node; |
| tree awsp_meth |
| = lookup_awaitable_member (o_type, coro_await_suspend_identifier, loc); |
| if (!awsp_meth || awsp_meth == error_mark_node) |
| return error_mark_node; |
| |
| /* The type of the co_await is the return type of the awaitable's |
| await_resume, so we need to look that up. */ |
| tree awrs_meth |
| = lookup_awaitable_member (o_type, coro_await_resume_identifier, loc); |
| if (!awrs_meth || awrs_meth == error_mark_node) |
| return error_mark_node; |
| |
| /* [expr.await]/3.3 If o would be a prvalue, the temporary |
| materialization conversion ([conv.rval]) is applied. */ |
| if (!glvalue_p (o)) |
| o = get_target_expr (o, tf_warning_or_error); |
| |
| /* [expr.await]/3.4 e is an lvalue referring to the result of evaluating the |
| (possibly-converted) o. |
| |
| So, either reuse an existing stable lvalue such as a variable or |
| COMPONENT_REF thereof, or create a new a coroutine state frame variable |
| for the awaiter, since it must persist across suspension. */ |
| tree e_var = NULL_TREE; |
| tree e_proxy = o; |
| if (is_stable_lvalue (o)) |
| o = NULL_TREE; /* Use the existing entity. */ |
| else /* We need a non-temp var. */ |
| { |
| tree p_type = TREE_TYPE (o); |
| tree o_a = o; |
| if (glvalue_p (o)) |
| { |
| /* Build a reference variable for a non-stable lvalue o. */ |
| p_type = cp_build_reference_type (p_type, xvalue_p (o)); |
| o_a = build_address (o); |
| o_a = cp_fold_convert (p_type, o_a); |
| } |
| e_var = get_awaitable_var (suspend_kind, p_type); |
| o = cp_build_init_expr (loc, e_var, o_a); |
| e_proxy = convert_from_reference (e_var); |
| } |
| |
| /* I suppose we could check that this is contextually convertible to bool. */ |
| tree awrd_func = NULL_TREE; |
| tree awrd_call |
| = build_new_method_call (e_proxy, awrd_meth, NULL, NULL_TREE, LOOKUP_NORMAL, |
| &awrd_func, tf_warning_or_error); |
| |
| if (!awrd_func || !awrd_call || awrd_call == error_mark_node) |
| return error_mark_node; |
| |
| /* The suspend method may return one of three types: |
| 1. void (no special action needed). |
| 2. bool (if true, we don't need to suspend). |
| 3. a coroutine handle, we execute the handle.resume() call. */ |
| tree awsp_func = NULL_TREE; |
| tree h_proxy = get_coroutine_self_handle_proxy (current_function_decl); |
| vec<tree, va_gc> *args = make_tree_vector_single (h_proxy); |
| tree awsp_call |
| = build_new_method_call (e_proxy, awsp_meth, &args, NULL_TREE, |
| LOOKUP_NORMAL, &awsp_func, tf_warning_or_error); |
| |
| release_tree_vector (args); |
| if (!awsp_func || !awsp_call || awsp_call == error_mark_node) |
| return error_mark_node; |
| |
| bool ok = false; |
| tree susp_return_type = TREE_TYPE (TREE_TYPE (awsp_func)); |
| if (same_type_p (susp_return_type, void_type_node)) |
| ok = true; |
| else if (same_type_p (susp_return_type, boolean_type_node)) |
| ok = true; |
| else if (TREE_CODE (susp_return_type) == RECORD_TYPE |
| && CLASS_TYPE_P (susp_return_type) |
| && CLASSTYPE_TEMPLATE_INFO (susp_return_type)) |
| { |
| tree tt = CLASSTYPE_TI_TEMPLATE (susp_return_type); |
| if (tt == coro_handle_templ) |
| ok = true; |
| } |
| |
| if (!ok) |
| { |
| error_at (loc, "%<await_suspend%> must return %<void%>, %<bool%> or" |
| " a coroutine handle"); |
| return error_mark_node; |
| } |
| |
| /* Finally, the type of e.await_resume() is the co_await's type. */ |
| tree awrs_func = NULL_TREE; |
| tree awrs_call |
| = build_new_method_call (e_proxy, awrs_meth, NULL, NULL_TREE, LOOKUP_NORMAL, |
| &awrs_func, tf_warning_or_error); |
| |
| if (!awrs_func || !awrs_call || awrs_call == error_mark_node) |
| return error_mark_node; |
| |
| if (flag_exceptions && suspend_kind == FINAL_SUSPEND_POINT) |
| { |
| if (coro_diagnose_throwing_fn (awrd_func)) |
| return error_mark_node; |
| if (coro_diagnose_throwing_fn (awsp_func)) |
| return error_mark_node; |
| if (coro_diagnose_throwing_fn (awrs_func)) |
| return error_mark_node; |
| if (tree dummy = cxx_maybe_build_cleanup (e_var, tf_none)) |
| { |
| if (CONVERT_EXPR_P (dummy)) |
| dummy = TREE_OPERAND (dummy, 0); |
| dummy = TREE_OPERAND (CALL_EXPR_FN (dummy), 0); |
| if (coro_diagnose_throwing_fn (dummy)) |
| return error_mark_node; |
| } |
| } |
| |
| /* We now have three call expressions, in terms of the promise, handle and |
| 'e' proxy expression. Save them in the await expression for later |
| expansion. */ |
| |
| tree awaiter_calls = make_tree_vec (3); |
| TREE_VEC_ELT (awaiter_calls, 0) = awrd_call; /* await_ready(). */ |
| TREE_VEC_ELT (awaiter_calls, 1) = awsp_call; /* await_suspend(). */ |
| tree te = NULL_TREE; |
| if (TREE_CODE (awrs_call) == TARGET_EXPR) |
| { |
| te = awrs_call; |
| awrs_call = TARGET_EXPR_INITIAL (awrs_call); |
| } |
| TREE_VEC_ELT (awaiter_calls, 2) = awrs_call; /* await_resume(). */ |
| |
| if (e_var) |
| e_proxy = e_var; |
| |
| 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, |
| awrs_type, |
| a, e_proxy, o, awaiter_calls, |
| suspend_kind_cst); |
| TREE_SIDE_EFFECTS (await_expr) = true; |
| if (te) |
| { |
| TREE_OPERAND (te, 1) = await_expr; |
| TREE_SIDE_EFFECTS (te) = true; |
| await_expr = te; |
| } |
| SET_EXPR_LOCATION (await_expr, loc); |
| |
| 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 |
| finish_co_await_expr (location_t kw, tree expr) |
| { |
| if (!expr || error_operand_p (expr)) |
| return error_mark_node; |
| |
| if (cp_unevaluated_operand) |
| { |
| error_at (kw, "%qs cannot be used in an unevaluated context","co_await"); |
| return error_mark_node; |
| } |
| |
| if (!coro_common_keyword_context_valid_p (current_function_decl, kw, |
| "co_await")) |
| return error_mark_node; |
| |
| /* The current function has now become a coroutine, if it wasn't already. */ |
| DECL_COROUTINE_P (current_function_decl) = 1; |
| |
| /* This function will appear to have no return statement, even if it |
| is declared to return non-void (most likely). This is correct - we |
| synthesize the return for the ramp in the compiler. So suppress any |
| 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); |
| |
| /* 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. */ |
| if (!coro_promise_type_found_p (current_function_decl, kw)) |
| return error_mark_node; |
| |
| /* [expr.await] 3.2 |
| The incoming cast expression might be transformed by a promise |
| 'await_transform()'. */ |
| tree at_meth |
| = lookup_promise_method (current_function_decl, |
| coro_await_transform_identifier, kw, |
| /*musthave=*/false); |
| if (at_meth == error_mark_node) |
| return error_mark_node; |
| |
| tree a = expr; |
| if (at_meth) |
| { |
| /* try to build a = p.await_transform (e). */ |
| vec<tree, va_gc> *args = make_tree_vector_single (expr); |
| a = build_new_method_call (get_coroutine_promise_proxy ( |
| current_function_decl), |
| at_meth, &args, NULL_TREE, LOOKUP_NORMAL, |
| NULL, tf_warning_or_error); |
| |
| /* As I read the section. |
| We saw an await_transform method, so it's mandatory that we replace |
| expr with p.await_transform (expr), therefore if the method call fails |
| (presumably, we don't have suitable arguments) then this part of the |
| process fails. */ |
| if (a == error_mark_node) |
| return error_mark_node; |
| } |
| |
| /* Now we want to build co_await a. */ |
| return build_co_await (kw, a, CO_AWAIT_SUSPEND_POINT, expr); |
| } |
| |
| /* Take the EXPR given and attempt to build: |
| co_await p.yield_value (expr); |
| per [expr.yield] para 1. */ |
| |
| tree |
| finish_co_yield_expr (location_t kw, tree expr) |
| { |
| if (!expr || error_operand_p (expr)) |
| return error_mark_node; |
| |
| if (cp_unevaluated_operand) |
| { |
| error_at (kw, "%qs cannot be used in an unevaluated context","co_yield"); |
| return error_mark_node; |
| } |
| |
| /* Check the general requirements and simple syntax errors. */ |
| if (!coro_common_keyword_context_valid_p (current_function_decl, kw, |
| "co_yield")) |
| return error_mark_node; |
| |
| /* The current function has now become a coroutine, if it wasn't already. */ |
| DECL_COROUTINE_P (current_function_decl) = 1; |
| |
| /* This function will appear to have no return statement, even if it |
| is declared to return non-void (most likely). This is correct - we |
| synthesize the return for the ramp in the compiler. So suppress any |
| 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); |
| |
| /* 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 |
| the promise type, and obtain its return type. */ |
| return error_mark_node; |
| |
| /* [expr.yield] / 1 |
| Let e be the operand of the yield-expression and p be an lvalue naming |
| the promise object of the enclosing coroutine, then the yield-expression |
| is equivalent to the expression co_await p.yield_value(e). |
| build p.yield_value(e): */ |
| vec<tree, va_gc> *args = make_tree_vector_single (expr); |
| tree yield_call |
| = coro_build_promise_expression (current_function_decl, NULL, |
| coro_yield_value_identifier, kw, |
| &args, /*musthave=*/true); |
| release_tree_vector (args); |
| |
| /* Now build co_await p.yield_value (e). |
| Noting that for co_yield, there is no evaluation of any potential |
| promise transform_await(), so we call build_co_await directly. */ |
| |
| tree op = build_co_await (kw, yield_call, CO_YIELD_SUSPEND_POINT); |
| if (op != error_mark_node) |
| { |
| if (REFERENCE_REF_P (op)) |
| op = TREE_OPERAND (op, 0); |
| /* If the await expression is wrapped in a TARGET_EXPR, then transfer |
| that wrapper to the CO_YIELD_EXPR, since this is just a proxy for |
| its contained await. Otherwise, just build the CO_YIELD_EXPR. */ |
| if (TREE_CODE (op) == TARGET_EXPR) |
| { |
| tree t = TARGET_EXPR_INITIAL (op); |
| t = build2_loc (kw, CO_YIELD_EXPR, TREE_TYPE (t), expr, t); |
| TARGET_EXPR_INITIAL (op) = t; |
| } |
| else |
| op = build2_loc (kw, CO_YIELD_EXPR, TREE_TYPE (op), expr, op); |
| TREE_SIDE_EFFECTS (op) = 1; |
| op = convert_from_reference (op); |
| } |
| |
| return op; |
| } |
| |
| /* Check and build a co_return statement. |
| First that it's valid to have a co_return keyword here. |
| If it is, then check and build the p.return_{void(),value(expr)}. |
| These are built against a proxy for the promise, which will be filled |
| in with the actual frame version when the function is transformed. */ |
| |
| tree |
| finish_co_return_stmt (location_t kw, tree expr) |
| { |
| if (expr) |
| STRIP_ANY_LOCATION_WRAPPER (expr); |
| |
| if (error_operand_p (expr)) |
| return error_mark_node; |
| |
| /* If it fails the following test, the function is not permitted to be a |
| coroutine, so the co_return statement is erroneous. */ |
| if (!coro_common_keyword_context_valid_p (current_function_decl, kw, |
| "co_return")) |
| return error_mark_node; |
| |
| /* The current function has now become a coroutine, if it wasn't |
| already. */ |
| DECL_COROUTINE_P (current_function_decl) = 1; |
| |
| /* This function will appear to have no return statement, even if it |
| is declared to return non-void (most likely). This is correct - we |
| synthesize the return for the ramp in the compiler. So suppress any |
| 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 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. */ |
| expr = build2_loc (kw, CO_RETURN_EXPR, void_type_node, |
| expr, NULL_TREE); |
| expr = maybe_cleanup_point_expr_void (expr); |
| return add_stmt (expr); |
| } |
| |
| if (!coro_promise_type_found_p (current_function_decl, kw)) |
| return error_mark_node; |
| |
| /* Suppress -Wreturn-type for co_return, we need to check indirectly |
| whether the promise type has a suitable return_void/return_value. */ |
| suppress_warning (current_function_decl, OPT_Wreturn_type); |
| |
| if (!processing_template_decl && warn_sequence_point) |
| verify_sequence_points (expr); |
| |
| if (expr) |
| { |
| /* If we had an id-expression obfuscated by force_paren_expr, we need |
| to undo it so we can try to treat it as an rvalue below. */ |
| expr = maybe_undo_parenthesized_ref (expr); |
| |
| if (error_operand_p (expr)) |
| return error_mark_node; |
| } |
| |
| /* If the promise object doesn't have the correct return call then |
| there's a mis-match between the co_return <expr> and this. */ |
| tree co_ret_call = error_mark_node; |
| if (expr == NULL_TREE || VOID_TYPE_P (TREE_TYPE (expr))) |
| co_ret_call |
| = get_coroutine_return_void_expr (current_function_decl, kw, true); |
| else |
| { |
| /* [class.copy.elision] / 3. |
| An implicitly movable entity is a variable of automatic storage |
| duration that is either a non-volatile object or an rvalue reference |
| to a non-volatile object type. For such objects in the context of |
| the co_return, the overload resolution should be carried out first |
| treating the object as an rvalue, if that fails, then we fall back |
| to regular overload resolution. */ |
| |
| tree arg = expr; |
| if (tree moved = treat_lvalue_as_rvalue_p (expr, /*return*/true)) |
| arg = moved; |
| |
| releasing_vec args = make_tree_vector_single (arg); |
| co_ret_call |
| = coro_build_promise_expression (current_function_decl, NULL, |
| coro_return_value_identifier, kw, |
| &args, /*musthave=*/true); |
| } |
| |
| /* Makes no sense for a co-routine really. */ |
| if (TREE_THIS_VOLATILE (current_function_decl)) |
| warning_at (kw, 0, |
| "function declared %<noreturn%> has a" |
| " %<co_return%> statement"); |
| |
| expr = build2_loc (kw, CO_RETURN_EXPR, void_type_node, expr, co_ret_call); |
| expr = maybe_cleanup_point_expr_void (expr); |
| return add_stmt (expr); |
| } |
| |
| /* We need to validate the arguments to __builtin_coro_promise, since the |
| second two must be constant, and the builtins machinery doesn't seem to |
| deal with that properly. */ |
| |
| tree |
| coro_validate_builtin_call (tree call, tsubst_flags_t) |
| { |
| tree fn = TREE_OPERAND (CALL_EXPR_FN (call), 0); |
| |
| gcc_checking_assert (DECL_BUILT_IN_CLASS (fn) == BUILT_IN_NORMAL); |
| switch (DECL_FUNCTION_CODE (fn)) |
| { |
| default: |
| return call; |
| |
| case BUILT_IN_CORO_PROMISE: |
| { |
| /* Argument 0 is already checked by the normal built-in machinery |
| Argument 1 must be a constant of size type. It probably makes |
| little sense if it's not a power of 2, but that isn't specified |
| formally. */ |
| tree arg = CALL_EXPR_ARG (call, 1); |
| location_t loc = EXPR_LOCATION (arg); |
| |
| /* We expect alignof expressions in templates. */ |
| if (TREE_CODE (arg) == ALIGNOF_EXPR) |
| ; |
| else if (!TREE_CONSTANT (arg)) |
| { |
| error_at (loc, "the align argument to %<__builtin_coro_promise%>" |
| " must be a constant"); |
| return error_mark_node; |
| } |
| /* Argument 2 is the direction - to / from handle address to promise |
| address. */ |
| arg = CALL_EXPR_ARG (call, 2); |
| loc = EXPR_LOCATION (arg); |
| if (!TREE_CONSTANT (arg)) |
| { |
| error_at (loc, "the direction argument to" |
| " %<__builtin_coro_promise%> must be a constant"); |
| return error_mark_node; |
| } |
| return call; |
| break; |
| } |
| } |
| } |
| |
| /* ================= Morph and Expand. ================= |
| |
| The entry point here is morph_fn_to_coro () which is called from |
| finish_function () when we have completed any template expansion. |
| |
| This is preceded by helper functions that implement the phases below. |
| |
| The process proceeds in four phases. |
| |
| A Initial framing. |
| The user's function body is wrapped in the initial and final suspend |
| points and we begin building the coroutine frame. |
| We build empty decls for the actor and destroyer functions at this |
| time too. |
| When exceptions are enabled, the user's function body will also be |
| wrapped in a try-catch block with the catch invoking the promise |
| class 'unhandled_exception' method. |
| |
| B Analysis. |
| The user's function body is analyzed to determine the suspend points, |
| if any, and to capture local variables that might persist across such |
| suspensions. In most cases, it is not necessary to capture compiler |
| temporaries, since the tree-lowering nests the suspensions correctly. |
| However, in the case of a captured reference, there is a lifetime |
| extension to the end of the full expression - which can mean across a |
| suspend point in which case it must be promoted to a frame variable. |
| |
| At the conclusion of analysis, we have a conservative frame layout and |
| maps of the local variables to their frame entry points. |
| |
| C Build the ramp function. |
| Carry out the allocation for the coroutine frame (NOTE; the actual size |
| computation is deferred until late in the middle end to allow for future |
| optimizations that will be allowed to elide unused frame entries). |
| We build the return object. |
| |
| D Build and expand the actor and destroyer function bodies. |
| The destroyer is a trivial shim that sets a bit to indicate that the |
| destroy dispatcher should be used and then calls into the actor. |
| |
| The actor function is the implementation of the user's state machine. |
| The current suspend point is noted in an index. |
| Each suspend point is encoded as a pair of internal functions, one in |
| the relevant dispatcher, and one representing the suspend point. |
| |
| During this process, the user's local variables and the proxies for the |
| self-handle and the promise class instance are re-written to their |
| coroutine frame equivalents. |
| |
| The complete bodies for the ramp, actor and destroy function are passed |
| back to finish_function for folding and gimplification. */ |
| |
| /* Helper to build a coroutine state frame access expression |
| CORO_FP is a frame pointer |
| MEMBER_ID is an identifier for a frame field |
| PRESERVE_REF is true, the expression returned will have REFERENCE_TYPE if |
| the MEMBER does. |
| COMPLAIN is passed to the underlying functions. */ |
| |
| static tree |
| coro_build_frame_access_expr (tree coro_ref, tree member_id, bool preserve_ref, |
| tsubst_flags_t complain) |
| { |
| gcc_checking_assert (INDIRECT_REF_P (coro_ref)); |
| tree fr_type = TREE_TYPE (coro_ref); |
| tree mb = lookup_member (fr_type, member_id, /*protect=*/1, /*want_type=*/0, |
| complain); |
| if (!mb || mb == error_mark_node) |
| return error_mark_node; |
| tree expr |
| = build_class_member_access_expr (coro_ref, mb, NULL_TREE, |
| preserve_ref, complain); |
| return expr; |
| } |
| |
| /* Helpers to build an artificial var, with location LOC, NAME and TYPE, in |
| CTX, and with initializer INIT. */ |
| |
| static tree |
| coro_build_artificial_var (location_t loc, tree name, tree type, tree ctx, |
| tree init) |
| { |
| tree res = build_lang_decl (VAR_DECL, name, type); |
| DECL_SOURCE_LOCATION (res) = loc; |
| DECL_CONTEXT (res) = ctx; |
| DECL_ARTIFICIAL (res) = true; |
| DECL_INITIAL (res) = init; |
| return res; |
| } |
| |
| /* As above, except allowing the name to be a string. */ |
| |
| static tree |
| coro_build_artificial_var (location_t loc, const char *name, tree type, |
| tree ctx, tree init) |
| { |
| return coro_build_artificial_var (loc, get_identifier (name), |
| type, ctx, init); |
| } |
| |
| /* As for coro_build_artificial_var, except that we push this decl into |
| the current binding scope and add the decl_expr. */ |
| |
| static tree |
| coro_build_and_push_artificial_var (location_t loc, const char *name, |
| tree type, tree ctx, tree init) |
| { |
| tree v = coro_build_artificial_var (loc, name, type, ctx, init); |
| v = pushdecl (v); |
| add_decl_expr (v); |
| return v; |
| } |
| |
| /* Build a var NAME of TYPE in CTX and with INIT; add a DECL_VALUE_EXPR that |
| refers to BASE.FIELD. */ |
| |
| static tree |
| coro_build_artificial_var_with_dve (location_t loc, tree name, tree type, |
| tree ctx, tree init, tree base, |
| tree field = NULL_TREE) |
| { |
| tree v = coro_build_artificial_var (loc, name, type, ctx, init); |
| if (field == NULL_TREE) |
| field = name; |
| tree dve = coro_build_frame_access_expr (base, field, true, |
| tf_warning_or_error); |
| SET_DECL_VALUE_EXPR (v, dve); |
| DECL_HAS_VALUE_EXPR_P (v) = true; |
| return v; |
| } |
| |
| /* As for coro_build_artificial_var_with_dve, but push into the current binding |
| scope and add the decl_expr. */ |
| |
| static tree |
| coro_build_and_push_artificial_var_with_dve (location_t loc, tree name, |
| tree type, tree ctx, tree init, |
| tree base, tree field = NULL_TREE) |
| { |
| tree v = coro_build_artificial_var_with_dve (loc, name, type, ctx, init, |
| base, field); |
| v = pushdecl (v); |
| add_decl_expr (v); |
| return v; |
| } |
| |
| /* 2. Create a named label in the specified context. */ |
| |
| static tree |
| create_named_label_with_ctx (location_t loc, const char *name, tree ctx) |
| { |
| tree lab_id = get_identifier (name); |
| tree lab = define_label (loc, lab_id); |
| DECL_CONTEXT (lab) = ctx; |
| DECL_ARTIFICIAL (lab) = true; |
| TREE_USED (lab) = true; |
| return lab; |
| } |
| |
| struct proxy_replace |
| { |
| tree from, to; |
| }; |
| |
| static tree |
| replace_proxy (tree *here, int *do_subtree, void *d) |
| { |
| proxy_replace *data = (proxy_replace *) d; |
| |
| if (*here == data->from) |
| { |
| *here = data->to; |
| *do_subtree = 0; |
| } |
| else |
| *do_subtree = 1; |
| return NULL_TREE; |
| } |
| |
| /* Support for expansion of co_await statements. */ |
| |
| struct coro_aw_data |
| { |
| tree actor_fn; /* Decl for context. */ |
| tree coro_fp; /* Frame pointer var. */ |
| tree resume_idx; /* This is the index var in the frame. */ |
| tree i_a_r_c; /* initial suspend await_resume() was called if true. */ |
| tree cleanup; /* This is where to go once we complete local destroy. */ |
| tree cororet; /* This is where to go if we suspend. */ |
| tree corocont; /* This is where to go if we continue. */ |
| tree dispatch; /* This is where we go if we restart the dispatch. */ |
| tree conthand; /* This is the handle for a continuation. */ |
| tree handle_type; /* Handle type for this coroutine... */ |
| tree hfa_m; /* ... and handle.from_address() for this. */ |
| unsigned index; /* This is our current resume index. */ |
| }; |
| |
| /* Lightweight search for the first await expression in tree-walk order. |
| returns: |
| The first await expression found in STMT. |
| NULL_TREE if there are none. |
| So can be used to determine if the statement needs to be processed for |
| awaits. */ |
| |
| static tree |
| co_await_find_in_subtree (tree *stmt, int *, void *d) |
| { |
| tree **p = (tree **) d; |
| if (TREE_CODE (*stmt) == CO_AWAIT_EXPR) |
| { |
| *p = stmt; |
| return *stmt; |
| } |
| return NULL_TREE; |
| } |
| |
| /* Starting with a statement: |
| |
| stmt => some tree containing one or more await expressions. |
| |
| We replace the statement with: |
| <STATEMENT_LIST> { |
| initialize awaitable |
| if (!ready) |
| { |
| suspension context. |
| } |
| resume: |
| revised statement with one await expression rewritten to its |
| await_resume() return value. |
| } |
| |
| We then recurse into the initializer and the revised statement |
| repeating this replacement until there are no more await expressions |
| in either. */ |
| |
| static tree * |
| expand_one_await_expression (tree *expr, tree *await_expr, void *d) |
| { |
| coro_aw_data *data = (coro_aw_data *) d; |
| |
| tree saved_statement = *expr; |
| tree saved_co_await = *await_expr; |
| |
| tree actor = data->actor_fn; |
| location_t loc = EXPR_LOCATION (*expr); |
| tree var = TREE_OPERAND (saved_co_await, 1); /* frame slot. */ |
| tree init_expr = TREE_OPERAND (saved_co_await, 2); /* initializer. */ |
| tree awaiter_calls = TREE_OPERAND (saved_co_await, 3); |
| |
| tree source = TREE_OPERAND (saved_co_await, 4); |
| bool is_final |
| = (source && TREE_INT_CST_LOW (source) == (int) FINAL_SUSPEND_POINT); |
| bool is_initial |
| = (source && TREE_INT_CST_LOW (source) == (int) INITIAL_SUSPEND_POINT); |
| |
| /* Build labels for the destinations of the control flow when we are resuming |
| or destroying. */ |
| int resume_point = data->index; |
| char *buf = xasprintf ("destroy.%d", resume_point); |
| tree destroy_label = create_named_label_with_ctx (loc, buf, actor); |
| free (buf); |
| buf = xasprintf ("resume.%d", resume_point); |
| tree resume_label = create_named_label_with_ctx (loc, buf, actor); |
| free (buf); |
| |
| /* This will contain our expanded expression and replace the original |
| expression. */ |
| tree stmt_list = push_stmt_list (); |
| tree *await_init = NULL; |
| |
| bool needs_dtor = TYPE_HAS_NONTRIVIAL_DESTRUCTOR (TREE_TYPE (var)); |
| if (!init_expr) |
| needs_dtor = false; /* No need, the var's lifetime is managed elsewhere. */ |
| else |
| { |
| finish_expr_stmt (init_expr); |
| /* We have an initializer, which might itself contain await exprs. */ |
| await_init = tsi_stmt_ptr (tsi_last (stmt_list)); |
| } |
| |
| /* Use the await_ready() call to test if we need to suspend. */ |
| tree ready_cond = TREE_VEC_ELT (awaiter_calls, 0); /* await_ready(). */ |
| |
| /* We will resume (or continue) at the following index. */ |
| tree resume_idx = build_int_cst (short_unsigned_type_node, data->index); |
| tree r = cp_build_init_expr (data->resume_idx, resume_idx); |
| finish_expr_stmt (r); |
| |
| /* Convert to bool, if necessary. */ |
| if (TREE_CODE (TREE_TYPE (ready_cond)) != BOOLEAN_TYPE) |
| ready_cond = cp_convert (boolean_type_node, ready_cond, |
| tf_warning_or_error); |
| tree susp_if = begin_if_stmt (); |
| /* Be aggressive in folding here, since there are a significant number of |
| cases where the ready condition is constant. */ |
| ready_cond = invert_truthvalue_loc (loc, ready_cond); |
| finish_if_stmt_cond (ready_cond, susp_if); |
| |
| /* Find out what we have to do with the awaiter's suspend method. |
| [expr.await] |
| (5.1) If the result of await-ready is false, the coroutine is considered |
| suspended. Then: |
| (5.1.1) If the type of await-suspend is std::coroutine_handle<Z>, |
| await-suspend.resume() is evaluated. |
| (5.1.2) if the type of await-suspend is bool, await-suspend is evaluated, |
| and the coroutine is resumed if the result is false. |
| (5.1.3) Otherwise, await-suspend is evaluated. */ |
| |
| tree suspend = TREE_VEC_ELT (awaiter_calls, 1); /* await_suspend(). */ |
| tree susp_type = TREE_TYPE (suspend); |
| tree susp_call = suspend; |
| if (TREE_CODE (suspend) == TARGET_EXPR) |
| susp_call = TARGET_EXPR_INITIAL (suspend); |
| gcc_checking_assert (TREE_CODE (susp_call) == CALL_EXPR); |
| tree dummy_ch = build_dummy_object (data->handle_type); |
| r = fold_convert (build_pointer_type (void_type_node), data->coro_fp); |
| vec<tree, va_gc> *args = make_tree_vector_single (r); |
| tree hfa = cp_fold_rvalue ( |
| build_new_method_call (dummy_ch, data->hfa_m, &args, NULL_TREE, |
| LOOKUP_NORMAL, NULL, tf_warning_or_error)); |
| release_tree_vector (args); |
| CALL_EXPR_ARG (susp_call, call_expr_nargs (susp_call) - 1) = hfa; |
| |
| bool is_cont = false; |
| /* NOTE: final suspend can't resume; the "resume" label in that case |
| corresponds to implicit destruction. */ |
| if (VOID_TYPE_P (susp_type)) |
| /* Void return - just proceed to suspend. */ |
| finish_expr_stmt (suspend); |
| else if (TREE_CODE (susp_type) == BOOLEAN_TYPE) |
| { |
| /* Boolean return, "continue" if the call returns false. */ |
| tree restart_if = begin_if_stmt (); |
| suspend = invert_truthvalue_loc (loc, suspend); |
| finish_if_stmt_cond (suspend, restart_if); |
| /* Resume - so restart the dispatcher, since we do not know if this |
| coroutine was already resumed from within await_suspend. We must |
| exit this scope without cleanups. */ |
| r = build_call_expr_internal_loc (loc, IFN_CO_SUSPN, void_type_node, 1, |
| build_address (data->dispatch)); |
| /* This will eventually expand to 'goto coro.restart.dispatch'. */ |
| finish_expr_stmt (r); |
| finish_then_clause (restart_if); |
| finish_if_stmt (restart_if); |
| } |
| else |
| { |
| /* Handle return, save it to the continuation. */ |
| r = suspend; |
| if (!same_type_ignoring_top_level_qualifiers_p (susp_type, |
| void_coro_handle_type)) |
| r = build1_loc (loc, VIEW_CONVERT_EXPR, void_coro_handle_type, r); |
| r = cp_build_init_expr (loc, data->conthand, r); |
| finish_expr_stmt (r); |
| is_cont = true; |
| } |
| |
| tree d_l = build_address (destroy_label); |
| tree r_l = build_address (resume_label); |
| tree susp = build_address (data->cororet); |
| tree cont = build_address (data->corocont); |
| tree final_susp = build_int_cst (integer_type_node, is_final ? 1 : 0); |
| |
| resume_idx = build_int_cst (integer_type_node, data->index); |
| |
| tree sw = begin_switch_stmt (); |
| |
| r = build_call_expr_internal_loc (loc, IFN_CO_YIELD, integer_type_node, 5, |
| resume_idx, final_susp, r_l, d_l, |
| data->coro_fp); |
| finish_switch_cond (r, sw); |
| finish_case_label (loc, integer_zero_node, NULL_TREE); /* case 0: */ |
| /* Implement the suspend, a scope exit without clean ups. */ |
| r = build_call_expr_internal_loc (loc, IFN_CO_SUSPN, void_type_node, 1, |
| is_cont ? cont : susp); |
| finish_expr_stmt (r); /* This will eventually expand to 'goto return'. */ |
| finish_case_label (loc, integer_one_node, NULL_TREE); /* case 1: */ |
| add_stmt (build_stmt (loc, GOTO_EXPR, resume_label)); /* goto resume; */ |
| finish_case_label (loc, NULL_TREE, NULL_TREE); /* default:; */ |
| add_stmt (build_stmt (loc, GOTO_EXPR, destroy_label)); /* goto destroy; */ |
| |
| finish_switch_stmt (sw); |
| add_stmt (build_stmt (loc, LABEL_EXPR, destroy_label)); |
| if (needs_dtor) |
| finish_expr_stmt (build_cleanup (var)); |
| add_stmt (build_stmt (loc, GOTO_EXPR, data->cleanup)); |
| |
| finish_then_clause (susp_if); |
| finish_if_stmt (susp_if); |
| |
| /* Resume point. */ |
| add_stmt (build_stmt (loc, LABEL_EXPR, resume_label)); |
| |
| if (is_initial && data->i_a_r_c) |
| { |
| r = cp_build_modify_expr (loc, data->i_a_r_c, NOP_EXPR, boolean_true_node, |
| tf_warning_or_error); |
| finish_expr_stmt (r); |
| } |
| |
| /* This will produce the value (if one is provided) from the co_await |
| expression. */ |
| tree resume_call = TREE_VEC_ELT (awaiter_calls, 2); /* await_resume(). */ |
| if (REFERENCE_REF_P (resume_call)) |
| /* Sink to await_resume call_expr. */ |
| resume_call = TREE_OPERAND (resume_call, 0); |
| |
| *await_expr = resume_call; /* Replace the co_await expr with its result. */ |
| append_to_statement_list_force (saved_statement, &stmt_list); |
| /* Get a pointer to the revised statement. */ |
| tree *revised = tsi_stmt_ptr (tsi_last (stmt_list)); |
| if (needs_dtor) |
| finish_expr_stmt (build_cleanup (var)); |
| data->index += 2; |
| |
| /* Replace the original expression with the expansion. */ |
| *expr = pop_stmt_list (stmt_list); |
| |
| /* Now, if the awaitable had an initializer, expand any awaits that might |
| be embedded in it. */ |
| tree *aw_expr_ptr; |
| if (await_init && |
| cp_walk_tree (await_init, co_await_find_in_subtree, &aw_expr_ptr, NULL)) |
| expand_one_await_expression (await_init, aw_expr_ptr, d); |
| |
| /* Expand any more await expressions in the original statement. */ |
| if (cp_walk_tree (revised, co_await_find_in_subtree, &aw_expr_ptr, NULL)) |
| expand_one_await_expression (revised, aw_expr_ptr, d); |
| |
| return NULL; |
| } |
| |
| /* Check to see if a statement contains at least one await expression, if |
| so, then process that. */ |
| |
| static tree |
| process_one_statement (tree *stmt, void *d) |
| { |
| tree *aw_expr_ptr; |
| if (cp_walk_tree (stmt, co_await_find_in_subtree, &aw_expr_ptr, NULL)) |
| expand_one_await_expression (stmt, aw_expr_ptr, d); |
| return NULL_TREE; |
| } |
| |
| static tree |
| await_statement_expander (tree *stmt, int *do_subtree, void *d) |
| { |
| tree res = NULL_TREE; |
| |
| /* Process a statement at a time. */ |
| if (STATEMENT_CLASS_P (*stmt) |
| || TREE_CODE (*stmt) == BIND_EXPR |
| || TREE_CODE (*stmt) == CLEANUP_POINT_EXPR) |
| return NULL_TREE; /* Just process the sub-trees. */ |
| else if (TREE_CODE (*stmt) == STATEMENT_LIST) |
| { |
| for (tree &s : tsi_range (*stmt)) |
| { |
| res = cp_walk_tree (&s, await_statement_expander, |
| d, NULL); |
| if (res) |
| return res; |
| } |
| *do_subtree = 0; /* Done subtrees. */ |
| } |
| else if (EXPR_P (*stmt)) |
| { |
| process_one_statement (stmt, d); |
| *do_subtree = 0; /* Done subtrees. */ |
| } |
| |
| /* Continue statement walk, where required. */ |
| return res; |
| } |
| |
| struct await_xform_data |
| { |
| tree actor_fn; /* Decl for context. */ |
| tree actor_frame; |
| hash_map<tree, suspend_point_info> *suspend_points; |
| }; |
| |
| /* When we built the await expressions, we didn't know the coro frame |
| layout, therefore no idea where to find the promise or where to put |
| the awaitables. Now we know these things, fill them in. */ |
| |
| static tree |
| transform_await_expr (tree await_expr, await_xform_data *xform) |
| { |
| suspend_point_info *si = xform->suspend_points->get (await_expr); |
| location_t loc = EXPR_LOCATION (await_expr); |
| if (!si) |
| { |
| error_at (loc, "no suspend point info for %qD", await_expr); |
| return error_mark_node; |
| } |
| |
| /* So, on entry, we have: |
| in : CO_AWAIT_EXPR (a, e_proxy, o, awr_call_vector, mode) |
| We no longer need a [it had diagnostic value, maybe?] |
| We need to replace the e_proxy in the awr_call. */ |
| |
| /* If we have a frame var for the awaitable, get a reference to it. */ |
| proxy_replace data; |
| if (si->await_field_id) |
| { |
| tree as |
| = coro_build_frame_access_expr (xform->actor_frame, si->await_field_id, |
| true, tf_warning_or_error); |
| /* Replace references to the instance proxy with the frame entry now |
| computed. */ |
| data.from = TREE_OPERAND (await_expr, 1); |
| data.to = as; |
| cp_walk_tree (&await_expr, replace_proxy, &data, NULL); |
| |
| /* .. and replace. */ |
| TREE_OPERAND (await_expr, 1) = as; |
| } |
| |
| return await_expr; |
| } |
| |
| /* A wrapper for the transform_await_expr function so that it can be a |
| callback from cp_walk_tree. */ |
| |
| static tree |
| transform_await_wrapper (tree *stmt, int *do_subtree, void *d) |
| { |
| /* Set actor function as new DECL_CONTEXT of label_decl. */ |
| struct await_xform_data *xform = (struct await_xform_data *) d; |
| if (TREE_CODE (*stmt) == LABEL_DECL |
| && DECL_CONTEXT (*stmt) != xform->actor_fn) |
| DECL_CONTEXT (*stmt) = xform->actor_fn; |
| |
| /* We should have already lowered co_yields to their co_await. */ |
| gcc_checking_assert (TREE_CODE (*stmt) != CO_YIELD_EXPR); |
| if (TREE_CODE (*stmt) != CO_AWAIT_EXPR) |
| return NULL_TREE; |
| |
| tree await_expr = *stmt; |
| *stmt = transform_await_expr (await_expr, xform); |
| if (*stmt == error_mark_node) |
| *do_subtree = 0; |
| return NULL_TREE; |
| } |
| |
| /* For figuring out what local variable usage we have. */ |
| struct local_vars_transform |
| { |
| tree context; |
| tree actor_frame; |
| tree coro_frame_type; |
| location_t loc; |
| hash_map<tree, local_var_info> *local_var_uses; |
| }; |
| |
| static tree |
| transform_local_var_uses (tree *stmt, int *do_subtree, void *d) |
| { |
| local_vars_transform *lvd = (local_vars_transform *) d; |
| |
| /* For each var in this bind expr (that has a frame id, which means it was |
| accessed), build a frame reference and add it as the DECL_VALUE_EXPR. */ |
| |
| if (TREE_CODE (*stmt) == BIND_EXPR) |
| { |
| tree lvar; |
| for (lvar = BIND_EXPR_VARS (*stmt); lvar != NULL; |
| lvar = DECL_CHAIN (lvar)) |
| { |
| bool existed; |
| local_var_info &local_var |
| = lvd->local_var_uses->get_or_insert (lvar, &existed); |
| gcc_checking_assert (existed); |
| |
| /* Re-write the variable's context to be in the actor func. */ |
| DECL_CONTEXT (lvar) = lvd->context; |
| |
| /* For capture proxies, this could include the decl value expr. */ |
| if (local_var.is_lambda_capture || local_var.has_value_expr_p) |
| continue; /* No frame entry for this. */ |
| |
| /* TODO: implement selective generation of fields when vars are |
| known not-used. */ |
| if (local_var.field_id == NULL_TREE) |
| continue; /* Wasn't used. */ |
| tree fld_idx |
| = coro_build_frame_access_expr (lvd->actor_frame, |
| local_var.field_id, true, |
| tf_warning_or_error); |
| local_var.field_idx = fld_idx; |
| SET_DECL_VALUE_EXPR (lvar, fld_idx); |
| DECL_HAS_VALUE_EXPR_P (lvar) = true; |
| } |
| cp_walk_tree (&BIND_EXPR_BODY (*stmt), transform_local_var_uses, d, NULL); |
| *do_subtree = 0; /* We've done the body already. */ |
| return NULL_TREE; |
| } |
| return NULL_TREE; |
| } |
| |
| /* A helper to build the frame DTOR. |
| [dcl.fct.def.coroutine] / 12 |
| The deallocation function’s name is looked up in the scope of the promise |
| type. If this lookup fails, the deallocation function’s name is looked up |
| in the global scope. If deallocation function lookup finds both a usual |
| deallocation function with only a pointer parameter and a usual |
| deallocation function with both a pointer parameter and a size parameter, |
| then the selected deallocation function shall be the one with two |
| parameters. Otherwise, the selected deallocation function shall be the |
| function with one parameter. If no usual deallocation function is found |
| the program is ill-formed. The selected deallocation function shall be |
| called with the address of the block of storage to be reclaimed as its |
| first argument. If a deallocation function with a parameter of type |
| std::size_t is used, the size of the block is passed as the corresponding |
| argument. */ |
| |
| static tree |
| build_coroutine_frame_delete_expr (tree, tree, tree, location_t); |
| |
| /* The actor transform. */ |
| |
| static void |
| build_actor_fn (location_t loc, tree coro_frame_type, tree actor, tree fnbody, |
| tree orig, hash_map<tree, local_var_info> *local_var_uses, |
| hash_map<tree, suspend_point_info> *suspend_points, |
| vec<tree> *param_dtor_list, |
| tree resume_idx_var, unsigned body_count, tree frame_size, |
| bool inline_p) |
| { |
| verify_stmt_tree (fnbody); |
| /* Some things we inherit from the original function. */ |
| tree promise_type = get_coroutine_promise_type (orig); |
| tree promise_proxy = get_coroutine_promise_proxy (orig); |
| |
| /* One param, the coro frame pointer. */ |
| tree actor_fp = DECL_ARGUMENTS (actor); |
| |
| bool spf = start_preparsed_function (actor, NULL_TREE, SF_PRE_PARSED); |
| gcc_checking_assert (spf); |
| gcc_checking_assert (cfun && current_function_decl && TREE_STATIC (actor)); |
| if (flag_exceptions) |
| /* We, unconditionally, add a try/catch and rethrow. |
| TODO: Determine if the combination of initial suspend and the original |
| body cannot throw, and elide these additions. */ |
| cp_function_chain->can_throw = true; |
| tree stmt = begin_function_body (); |
| |
| tree actor_bind = build3 (BIND_EXPR, void_type_node, NULL, NULL, NULL); |
| tree top_block = make_node (BLOCK); |
| BIND_EXPR_BLOCK (actor_bind) = top_block; |
| |
| tree continuation = coro_build_artificial_var (loc, coro_actor_continue_id, |
| void_coro_handle_type, actor, |
| NULL_TREE); |
| |
| BIND_EXPR_VARS (actor_bind) = continuation; |
| BLOCK_VARS (top_block) = BIND_EXPR_VARS (actor_bind) ; |
| |
| /* Link in the block associated with the outer scope of the re-written |
| function body. */ |
| tree first = expr_first (fnbody); |
| gcc_checking_assert (first && TREE_CODE (first) == BIND_EXPR); |
| tree block = BIND_EXPR_BLOCK (first); |
| gcc_checking_assert (BLOCK_SUPERCONTEXT (block) == NULL_TREE); |
| gcc_checking_assert (BLOCK_CHAIN (block) == NULL_TREE); |
| BLOCK_SUPERCONTEXT (block) = top_block; |
| BLOCK_SUBBLOCKS (top_block) = block; |
| |
| add_stmt (actor_bind); |
| tree actor_body = push_stmt_list (); |
| |
| /* The entry point for the actor code from the ramp. */ |
| tree actor_begin_label |
| = create_named_label_with_ctx (loc, "actor.begin", actor); |
| tree actor_frame = build1_loc (loc, INDIRECT_REF, coro_frame_type, actor_fp); |
| |
| /* Declare the continuation handle. */ |
| add_decl_expr (continuation); |
| |
| /* Re-write local vars, similarly. */ |
| local_vars_transform xform_vars_data |
| = {actor, actor_frame, coro_frame_type, loc, local_var_uses}; |
| cp_walk_tree (&fnbody, transform_local_var_uses, &xform_vars_data, NULL); |
| tree rat = coro_build_frame_access_expr (actor_frame, coro_resume_index_id, |
| false, tf_warning_or_error); |
| tree ret_label |
| = create_named_label_with_ctx (loc, "actor.suspend.ret", actor); |
| |
| tree continue_label |
| = create_named_label_with_ctx (loc, "actor.continue.ret", actor); |
| |
| /* Build the dispatcher; for each await expression there is an odd entry |
| corresponding to the destruction action and an even entry for the resume |
| one: |
| if (resume index is odd) |
| { |
| switch (resume index) |
| case 1: |
| goto cleanup. |
| case ... odd suspension point number |
| .CO_ACTOR (... odd suspension point number) |
| break; |
| default: |
| break; |
| } |
| else |
| { |
| coro.restart.dispatch: |
| case 0: |
| goto start. |
| case ... even suspension point number |
| .CO_ACTOR (... even suspension point number) |
| break; |
| default: |
| break; |
| } |
| we should not get here unless something is broken badly. |
| __builtin_trap (); |
| */ |
| tree lsb_if = begin_if_stmt (); |
| tree chkb0 = build2 (BIT_AND_EXPR, short_unsigned_type_node, rat, |
| build_int_cst (short_unsigned_type_node, 1)); |
| chkb0 = build2 (NE_EXPR, short_unsigned_type_node, chkb0, |
| build_int_cst (short_unsigned_type_node, 0)); |
| finish_if_stmt_cond (chkb0, lsb_if); |
| |
| tree destroy_dispatcher = begin_switch_stmt (); |
| finish_switch_cond (rat, destroy_dispatcher); |
| |
| /* The destroy point numbered #1 is special, in that it is reached from a |
| coroutine that is suspended after re-throwing from unhandled_exception(). |
| This label just invokes the cleanup of promise, param copies and the |
| frame itself. */ |
| tree del_promise_label |
| = create_named_label_with_ctx (loc, "coro.delete.promise", actor); |
| finish_case_label (loc, build_int_cst (short_unsigned_type_node, 1), |
| NULL_TREE); |
| add_stmt (build_stmt (loc, GOTO_EXPR, del_promise_label)); |
| |
| short unsigned lab_num = 3; |
| for (unsigned destr_pt = 0; destr_pt < body_count; destr_pt++) |
| { |
| tree l_num = build_int_cst (short_unsigned_type_node, lab_num); |
| finish_case_label (loc, l_num, NULL_TREE); |
| tree c = build_call_expr_internal_loc (loc, IFN_CO_ACTOR, void_type_node, |
| 1, l_num); |
| finish_expr_stmt (c); |
| finish_break_stmt (); |
| lab_num += 2; |
| } |
| finish_case_label (loc, NULL_TREE, NULL_TREE); |
| finish_break_stmt (); |
| |
| /* Finish the destroy dispatcher. */ |
| finish_switch_stmt (destroy_dispatcher); |
| |
| finish_then_clause (lsb_if); |
| begin_else_clause (lsb_if); |
| |
| /* For the case of a boolean await_resume () that returns 'true' we should |
| restart the dispatch, since we cannot know if additional resumes were |
| executed from within the await_suspend function. */ |
| tree restart_dispatch_label |
| = create_named_label_with_ctx (loc, "coro.restart.dispatch", actor); |
| add_stmt (build_stmt (loc, LABEL_EXPR, restart_dispatch_label)); |
| |
| tree dispatcher = begin_switch_stmt (); |
| finish_switch_cond (rat, dispatcher); |
| finish_case_label (loc, build_int_cst (short_unsigned_type_node, 0), |
| NULL_TREE); |
| add_stmt (build_stmt (loc, GOTO_EXPR, actor_begin_label)); |
| |
| lab_num = 2; |
| /* The final resume should be made to hit the default (trap, UB) entry |
| although it will be unreachable via the normal entry point, since that |
| is set to NULL on reaching final suspend. */ |
| for (unsigned resu_pt = 0; resu_pt < body_count; resu_pt++) |
| { |
| tree l_num = build_int_cst (short_unsigned_type_node, lab_num); |
| finish_case_label (loc, l_num, NULL_TREE); |
| tree c = build_call_expr_internal_loc (loc, IFN_CO_ACTOR, void_type_node, |
| 1, l_num); |
| finish_expr_stmt (c); |
| finish_break_stmt (); |
| lab_num += 2; |
| } |
| finish_case_label (loc, NULL_TREE, NULL_TREE); |
| finish_break_stmt (); |
| |
| /* Finish the resume dispatcher. */ |
| finish_switch_stmt (dispatcher); |
| |
| finish_else_clause (lsb_if); |
| finish_if_stmt (lsb_if); |
| |
| /* If we reach here then we've hit UB. */ |
| tree t = build_call_expr_loc (loc, builtin_decl_explicit (BUILT_IN_TRAP), 0); |
| finish_expr_stmt (t); |
| |
| /* Now we start building the rewritten function body. */ |
| add_stmt (build_stmt (loc, LABEL_EXPR, actor_begin_label)); |
| |
| tree i_a_r_c = NULL_TREE; |
| if (flag_exceptions) |
| { |
| i_a_r_c |
| = coro_build_frame_access_expr (actor_frame, coro_frame_i_a_r_c_id, |
| false, tf_warning_or_error); |
| tree m = cp_build_modify_expr (loc, i_a_r_c, NOP_EXPR, |
| boolean_false_node, tf_warning_or_error); |
| finish_expr_stmt (m); |
| } |
| |
| /* Now we know the real promise, and enough about the frame layout to |
| decide where to put things. */ |
| |
| await_xform_data xform = {actor, actor_frame, suspend_points}; |
| |
| /* Transform the await expressions in the function body. Only do each |
| await tree once! */ |
| hash_set<tree> pset; |
| cp_walk_tree (&fnbody, transform_await_wrapper, &xform, &pset); |
| |
| /* Add in our function body with the co_returns rewritten to final form. */ |
| add_stmt (fnbody); |
| |
| /* We are done with the frame, but if the ramp still has a hold on it |
| we should not cleanup. So decrement the refcount and then return to |
| the ramp if it is > 0. */ |
| tree coro_frame_refcount |
| = coro_build_frame_access_expr (actor_frame, coro_frame_refcount_id, |
| false, tf_warning_or_error); |
| tree released = build2_loc (loc, MINUS_EXPR, short_unsigned_type_node, |
| coro_frame_refcount, |
| build_int_cst (short_unsigned_type_node, 1)); |
| tree r = cp_build_modify_expr (loc, coro_frame_refcount, NOP_EXPR, released, |
| tf_warning_or_error); |
| finish_expr_stmt (r); |
| tree cond = build2_loc (loc, NE_EXPR, short_unsigned_type_node, |
| coro_frame_refcount, |
| build_int_cst (short_unsigned_type_node, 0)); |
| tree ramp_cu_if = begin_if_stmt (); |
| finish_if_stmt_cond (cond, ramp_cu_if); |
| finish_return_stmt (NULL_TREE); |
| finish_then_clause (ramp_cu_if); |
| finish_if_stmt (ramp_cu_if); |
| |
| /* Otherwise, do the tail of the function; first cleanups. */ |
| r = build_stmt (loc, LABEL_EXPR, del_promise_label); |
| add_stmt (r); |
| |
| /* Destructors for the things we built explicitly. |
| promise... */ |
| if (tree c = cxx_maybe_build_cleanup (promise_proxy, tf_warning_or_error)) |
| finish_expr_stmt (c); |
| |
| /* Argument copies ... */ |
| while (!param_dtor_list->is_empty ()) |
| { |
| tree parm_id = param_dtor_list->pop (); |
| tree a = coro_build_frame_access_expr (actor_frame, parm_id, false, |
| tf_warning_or_error); |
| if (tree dtor = cxx_maybe_build_cleanup (a, tf_warning_or_error)) |
| finish_expr_stmt (dtor); |
| } |
| |
| /* Here deallocate the frame (if we allocated it), which we will have at |
| present. */ |
| tree fnf2_x |
| = coro_build_frame_access_expr (actor_frame, coro_frame_needs_free_id, |
| false, tf_warning_or_error); |
| tree need_free_if = begin_if_stmt (); |
| finish_if_stmt_cond (fnf2_x, need_free_if); |
| |
| /* Build the frame DTOR. */ |
| tree del_coro_fr |
| = build_coroutine_frame_delete_expr (actor_fp, frame_size, |
| promise_type, loc); |
| finish_expr_stmt (del_coro_fr); |
| finish_then_clause (need_free_if); |
| finish_if_stmt (need_free_if); |
| |
| /* Done. */ |
| finish_return_stmt (NULL_TREE); |
| |
| /* This is the suspend return point. */ |
| add_stmt (build_stmt (loc, LABEL_EXPR, ret_label)); |
| |
| finish_return_stmt (NULL_TREE); |
| |
| /* This is the 'continuation' return point. For such a case we have a coro |
| handle (from the await_suspend() call) and we want handle.resume() to |
| execute as a tailcall allowing arbitrary chaining of coroutines. */ |
| add_stmt (build_stmt (loc, LABEL_EXPR, continue_label)); |
| |
| /* Should have been set earlier by the coro_initialized code. */ |
| gcc_assert (void_coro_handle_address); |
| |
| /* We want to force a tail-call even for O0/1, so this expands the resume |
| call into its underlying implementation. */ |
| tree addr = build_new_method_call (continuation, void_coro_handle_address, |
| NULL, NULL_TREE, LOOKUP_NORMAL, NULL, |
| tf_warning_or_error); |
| tree resume = build_call_expr_loc |
| (loc, builtin_decl_explicit (BUILT_IN_CORO_RESUME), 1, addr); |
| |
| /* In order to support an arbitrary number of coroutine continuations, |
| we must tail call them. However, some targets do not support indirect |
| tail calls to arbitrary callees. See PR94359. */ |
| CALL_EXPR_TAILCALL (resume) = true; |
| finish_expr_stmt (resume); |
| |
| r = build_stmt (loc, RETURN_EXPR, NULL); |
| gcc_checking_assert (maybe_cleanup_point_expr_void (r) == r); |
| add_stmt (r); |
| |
| /* How to construct the handle for this coroutine from the frame address. */ |
| tree hfa_m = get_coroutine_from_address (orig); |
| /* Should have been set earlier by coro_promise_type_found_p. */ |
| gcc_assert (hfa_m); |
| tree handle_type = TREE_TYPE (get_coroutine_self_handle_proxy (orig)); |
| |
| /* We've now rewritten the tree and added the initial and final |
| co_awaits. Now pass over the tree and expand the co_awaits. */ |
| |
| coro_aw_data data = {actor, actor_fp, resume_idx_var, i_a_r_c, |
| del_promise_label, ret_label, |
| continue_label, restart_dispatch_label, continuation, |
| handle_type, hfa_m, 2}; |
| cp_walk_tree (&actor_body, await_statement_expander, &data, NULL); |
| |
| BIND_EXPR_BODY (actor_bind) = pop_stmt_list (actor_body); |
| TREE_SIDE_EFFECTS (actor_bind) = true; |
| |
| cfun->coroutine_component = 1; |
| finish_function_body (stmt); |
| finish_function (inline_p); |
| } |
| |
| /* The prototype 'destroy' function : |
| frame->__Coro_resume_index |= 1; |
| actor (frame); */ |
| |
| static void |
| build_destroy_fn (location_t loc, tree coro_frame_type, tree destroy, |
| tree actor, bool inline_p) |
| { |
| /* One param, the coro frame pointer. */ |
| tree destr_fp = DECL_ARGUMENTS (destroy); |
| gcc_checking_assert (POINTER_TYPE_P (TREE_TYPE (destr_fp)) |
| && same_type_p (coro_frame_type, |
| TREE_TYPE (TREE_TYPE (destr_fp)))); |
| |
| bool spf = start_preparsed_function (destroy, NULL_TREE, SF_PRE_PARSED); |
| gcc_checking_assert (spf); |
| tree dstr_stmt = begin_function_body (); |
| |
| tree destr_frame |
| = cp_build_indirect_ref (loc, destr_fp, RO_UNARY_STAR, |
| tf_warning_or_error); |
| |
| tree rat = coro_build_frame_access_expr (destr_frame, coro_resume_index_id, |
| false, tf_warning_or_error); |
| |
| /* _resume_at |= 1 */ |
| tree dstr_idx |
| = build2_loc (loc, BIT_IOR_EXPR, short_unsigned_type_node, rat, |
| build_int_cst (short_unsigned_type_node, 1)); |
| tree r = cp_build_modify_expr (loc, rat, NOP_EXPR, dstr_idx, |
| tf_warning_or_error); |
| finish_expr_stmt (r); |
| |
| /* So .. call the actor .. */ |
| finish_expr_stmt (build_call_expr_loc (loc, actor, 1, destr_fp)); |
| |
| /* done. */ |
| finish_return_stmt (NULL_TREE); |
| |
| gcc_checking_assert (cfun && current_function_decl); |
| cfun->coroutine_component = 1; |
| finish_function_body (dstr_stmt); |
| finish_function (inline_p); |
| } |
| |
| /* Helper that returns an identifier for an appended extension to the |
| current un-mangled function name. */ |
| |
| static tree |
| get_fn_local_identifier (tree orig, const char *append) |
| { |
| /* Figure out the bits we need to generate names for the outlined things |
| For consistency, this needs to behave the same way as |
| ASM_FORMAT_PRIVATE_NAME does. */ |
| tree nm = DECL_NAME (orig); |
| const char *sep, *pfx = ""; |
| #ifndef NO_DOT_IN_LABEL |
| sep = "."; |
| #else |
| #ifndef NO_DOLLAR_IN_LABEL |
| sep = "$"; |
| #else |
| sep = "_"; |
| pfx = "__"; |
| #endif |
| #endif |
| |
| char *an; |
| if (DECL_ASSEMBLER_NAME (orig)) |
| an = ACONCAT ((IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (orig)), sep, append, |
| (char *) 0)); |
| else if (DECL_USE_TEMPLATE (orig) && DECL_TEMPLATE_INFO (orig) |
| && DECL_TI_ARGS (orig)) |
| { |
| tree tpl_args = DECL_TI_ARGS (orig); |
| an = ACONCAT ((pfx, IDENTIFIER_POINTER (nm), (char *) 0)); |
| for (int i = 0; i < TREE_VEC_LENGTH (tpl_args); ++i) |
| { |
| tree typ = DECL_NAME (TYPE_NAME (TREE_VEC_ELT (tpl_args, i))); |
| an = ACONCAT ((an, sep, IDENTIFIER_POINTER (typ), (char *) 0)); |
| } |
| an = ACONCAT ((an, sep, append, (char *) 0)); |
| } |
| else |
| an = ACONCAT ((pfx, IDENTIFIER_POINTER (nm), sep, append, (char *) 0)); |
| |
| return get_identifier (an); |
| } |
| |
| /* Build an initial or final await initialized from the promise |
| initial_suspend or final_suspend expression. */ |
| |
| static tree |
| build_init_or_final_await (location_t loc, bool is_final) |
| { |
| tree suspend_alt = is_final ? coro_final_suspend_identifier |
| : coro_initial_suspend_identifier; |
| |
| tree setup_call |
| = coro_build_promise_expression (current_function_decl, NULL, suspend_alt, |
| loc, NULL, /*musthave=*/true); |
| |
| /* Check for noexcept on the final_suspend call. */ |
| if (flag_exceptions && is_final && setup_call != error_mark_node |
| && coro_diagnose_throwing_final_aw_expr (setup_call)) |
| return error_mark_node; |
| |
| /* So build the co_await for this */ |
| /* For initial/final suspends the call is "a" per [expr.await] 3.2. */ |
| return build_co_await (loc, setup_call, (is_final ? FINAL_SUSPEND_POINT |
| : INITIAL_SUSPEND_POINT)); |
| } |
| |
| /* Callback to record the essential data for each await point found in the |
| function. */ |
| |
| static bool |
| register_await_info (tree await_expr, tree aw_type, tree aw_nam, |
| hash_map<tree, suspend_point_info> *suspend_points) |
| { |
| bool seen; |
| suspend_point_info &s |
| = suspend_points->get_or_insert (await_expr, &seen); |
| if (seen) |
| { |
| warning_at (EXPR_LOCATION (await_expr), 0, "duplicate info for %qE", |
| await_expr); |
| return false; |
| } |
| s.awaitable_type = aw_type; |
| s.await_field_id = aw_nam; |
| return true; |
| } |
| |
| /* If this is an await expression, then count it (both uniquely within the |
| function and locally within a single statement). */ |
| |
| static tree |
| register_awaits (tree *stmt, int *, void *d) |
| { |
| tree aw_expr = *stmt; |
| |
| /* We should have already lowered co_yields to their co_await. */ |
| gcc_checking_assert (TREE_CODE (aw_expr) != CO_YIELD_EXPR); |
| |
| if (TREE_CODE (aw_expr) != CO_AWAIT_EXPR) |
| return NULL_TREE; |
| |
| /* Count how many awaits the current expression contains. */ |
| susp_frame_data *data = (susp_frame_data *) d; |
| data->saw_awaits++; |
| /* Each await suspend context is unique, this is a function-wide value. */ |
| data->await_number++; |
| |
| /* Awaitables should either be user-locals or promoted to coroutine frame |
| entries at this point, and their initializers should have been broken |
| out. */ |
| tree aw = TREE_OPERAND (aw_expr, 1); |
| gcc_checking_assert (!TREE_OPERAND (aw_expr, 2)); |
| |
| tree aw_field_type = TREE_TYPE (aw); |
| tree aw_field_nam = NULL_TREE; |
| register_await_info (aw_expr, aw_field_type, aw_field_nam, data->suspend_points); |
| |
| /* Rewrite target expressions on the await_suspend () to remove extraneous |
| cleanups for the awaitables, which are now promoted to frame vars and |
| managed via that. */ |
| tree v = TREE_OPERAND (aw_expr, 3); |
| tree o = TREE_VEC_ELT (v, 1); |
| if (TREE_CODE (o) == TARGET_EXPR) |
| TREE_VEC_ELT (v, 1) = get_target_expr (TARGET_EXPR_INITIAL (o)); |
| return NULL_TREE; |
| } |
| |
| /* There are cases where any await expression is relevant. */ |
| static tree |
| find_any_await (tree *stmt, int *dosub, void *d) |
| { |
| if (TREE_CODE (*stmt) == CO_AWAIT_EXPR) |
| { |
| *dosub = 0; /* We don't need to consider this any further. */ |
| if (d) |
| *(tree **)d = stmt; |
| return *stmt; |
| } |
| return NULL_TREE; |
| } |
| |
| static bool |
| tmp_target_expr_p (tree t) |
| { |
| if (TREE_CODE (t) != TARGET_EXPR) |
| return false; |
| tree v = TARGET_EXPR_SLOT (t); |
| if (!DECL_ARTIFICIAL (v)) |
| return false; |
| if (DECL_NAME (v)) |
| return false; |
| return true; |
| } |
| |
| /* Structure to record sub-expressions that need to be handled by the |
| statement flattener. */ |
| |
| struct coro_interesting_subtree |
| { |
| tree* entry; |
| hash_set<tree> *temps_used; |
| }; |
| |
| /* tree-walk callback that returns the first encountered sub-expression of |
| a kind that needs to be handled specifically by the statement flattener. */ |
| |
| static tree |
| find_interesting_subtree (tree *expr_p, int *dosub, void *d) |
| { |
| tree expr = *expr_p; |
| coro_interesting_subtree *p = (coro_interesting_subtree *)d; |
| if (TREE_CODE (expr) == CO_AWAIT_EXPR) |
| { |
| *dosub = 0; /* We don't need to consider this any further. */ |
| if (TREE_OPERAND (expr, 2)) |
| { |
| p->entry = expr_p; |
| return expr; |
| } |
| } |
| else if (tmp_target_expr_p (expr) |
| && !TARGET_EXPR_ELIDING_P (expr) |
| && !p->temps_used->contains (expr)) |
| { |
| p->entry = expr_p; |
| return expr; |
| } |
| |
| return NULL_TREE; |
| } |
| |
| /* Node for a doubly-linked list of promoted variables and their |
| initializers. When the initializer is a conditional expression |
| the 'then' and 'else' clauses are represented by a linked list |
| attached to then_cl and else_cl respectively. */ |
| |
| struct var_nest_node |
| { |
| var_nest_node () = default; |
| var_nest_node (tree v, tree i, var_nest_node *p, var_nest_node *n) |
| : var(v), init(i), prev(p), next(n), then_cl (NULL), else_cl (NULL) |
| { |
| if (p) |
| p->next = this; |
| if (n) |
| n->prev = this; |
| } |
| tree var; |
| tree init; |
| var_nest_node *prev; |
| var_nest_node *next; |
| var_nest_node *then_cl; |
| var_nest_node *else_cl; |
| }; |
| |
| /* This is called for single statements from the co-await statement walker. |
| It checks to see if the statement contains any initializers for awaitables |
| and if any of these capture items by reference. */ |
| |
| static void |
| flatten_await_stmt (var_nest_node *n, hash_set<tree> *promoted, |
| hash_set<tree> *temps_used, tree *replace_in) |
| { |
| bool init_expr = false; |
| switch (TREE_CODE (n->init)) |
| { |
| default: break; |
| /* Compound expressions must be flattened specifically. */ |
| case COMPOUND_EXPR: |
| { |
| tree first = TREE_OPERAND (n->init, 0); |
| n->init = TREE_OPERAND (n->init, 1); |
| var_nest_node *ins |
| = new var_nest_node(NULL_TREE, first, n->prev, n); |
| /* The compiler (but not the user) can generate temporaries with |
| uses in the second arm of a compound expr. */ |
| flatten_await_stmt (ins, promoted, temps_used, &n->init); |
| flatten_await_stmt (n, promoted, temps_used, NULL); |
| /* The two arms have been processed separately. */ |
| return; |
| } |
| break; |
| /* Handle conditional expressions. */ |
| case INIT_EXPR: |
| init_expr = true; |
| /* FALLTHROUGH */ |
| case MODIFY_EXPR: |
| { |
| tree old_expr = TREE_OPERAND (n->init, 1); |
| if (TREE_CODE (old_expr) == COMPOUND_EXPR) |
| { |
| tree first = TREE_OPERAND (old_expr, 0); |
| TREE_OPERAND (n->init, 1) = TREE_OPERAND (old_expr, 1); |
| var_nest_node *ins |
| = new var_nest_node(NULL_TREE, first, n->prev, n); |
| flatten_await_stmt (ins, promoted, temps_used, |
| &TREE_OPERAND (n->init, 1)); |
| flatten_await_stmt (n, promoted, temps_used, NULL); |
| return; |
| } |
| if (TREE_CODE (old_expr) != COND_EXPR) |
| break; |
| /* Reconstruct x = t ? y : z; |
| as (void) t ? x = y : x = z; */ |
| tree var = TREE_OPERAND (n->init, 0); |
| tree var_type = TREE_TYPE (var); |
| tree cond = COND_EXPR_COND (old_expr); |
| /* We are allowed a void type throw in one or both of the cond |
| expr arms. */ |
| tree then_cl = COND_EXPR_THEN (old_expr); |
| if (!VOID_TYPE_P (TREE_TYPE (then_cl))) |
| { |
| gcc_checking_assert (TREE_CODE (then_cl) != STATEMENT_LIST); |
| if (init_expr) |
| then_cl = cp_build_init_expr (var, then_cl); |
| else |
| then_cl = build2 (MODIFY_EXPR, var_type, var, then_cl); |
| } |
| tree else_cl = COND_EXPR_ELSE (old_expr); |
| if (!VOID_TYPE_P (TREE_TYPE (else_cl))) |
| { |
| gcc_checking_assert (TREE_CODE (else_cl) != STATEMENT_LIST); |
| if (init_expr) |
| else_cl = cp_build_init_expr (var, else_cl); |
| else |
| else_cl = build2 (MODIFY_EXPR, var_type, var, else_cl); |
| } |
| n->init = build3 (COND_EXPR, var_type, cond, then_cl, else_cl); |
| } |
| /* FALLTHROUGH */ |
| case COND_EXPR: |
| { |
| tree *found; |
| tree cond = COND_EXPR_COND (n->init); |
| /* If the condition contains an await expression, then we need to |
| set that first and use a separate var. */ |
| if (cp_walk_tree (&cond, find_any_await, &found, NULL)) |
| { |
| tree cond_type = TREE_TYPE (cond); |
| tree cond_var = build_lang_decl (VAR_DECL, NULL_TREE, cond_type); |
| DECL_ARTIFICIAL (cond_var) = true; |
| layout_decl (cond_var, 0); |
| gcc_checking_assert (!TYPE_NEEDS_CONSTRUCTING (cond_type)); |
| cond = cp_build_init_expr (cond_var, cond); |
| var_nest_node *ins |
| = new var_nest_node (cond_var, cond, n->prev, n); |
| COND_EXPR_COND (n->init) = cond_var; |
| flatten_await_stmt (ins, promoted, temps_used, NULL); |
| } |
| |
| n->then_cl |
| = new var_nest_node (n->var, COND_EXPR_THEN (n->init), NULL, NULL); |
| n->else_cl |
| = new var_nest_node (n->var, COND_EXPR_ELSE (n->init), NULL, NULL); |
| flatten_await_stmt (n->then_cl, promoted, temps_used, NULL); |
| /* Point to the start of the flattened code. */ |
| while (n->then_cl->prev) |
| n->then_cl = n->then_cl->prev; |
| flatten_await_stmt (n->else_cl, promoted, temps_used, NULL); |
| while (n->else_cl->prev) |
| n->else_cl = n->else_cl->prev; |
| return; |
| } |
| break; |
| } |
| coro_interesting_subtree v = { NULL, temps_used }; |
| tree t = cp_walk_tree (&n->init, find_interesting_subtree, (void *)&v, NULL); |
| if (!t) |
| return; |
| switch (TREE_CODE (t)) |
| { |
| default: break; |
| case CO_AWAIT_EXPR: |
| { |
| /* Await expressions with initializers have a compiler-temporary |
| as the awaitable. 'promote' this. */ |
| tree var = TREE_OPERAND (t, 1); |
| bool already_present = promoted->add (var); |
| gcc_checking_assert (!already_present); |
| tree init = TREE_OPERAND (t, 2); |
| switch (TREE_CODE (init)) |
| { |
| default: break; |
| case INIT_EXPR: |
| case MODIFY_EXPR: |
| { |
| tree inner = TREE_OPERAND (init, 1); |
| /* We can have non-lvalue-expressions here, but when we see |
| a target expression, mark it as already used. */ |
| if (TREE_CODE (inner) == TARGET_EXPR) |
| { |
| temps_used->add (inner); |
| gcc_checking_assert |
| (TREE_CODE (TARGET_EXPR_INITIAL (inner)) != COND_EXPR); |
| } |
| } |
| break; |
| case CALL_EXPR: |
| /* If this is a call and not a CTOR, then we didn't expect it. */ |
| gcc_checking_assert |
| (DECL_CONSTRUCTOR_P (TREE_OPERAND (CALL_EXPR_FN (init), 0))); |
| break; |
| } |
| var_nest_node *ins = new var_nest_node (var, init, n->prev, n); |
| TREE_OPERAND (t, 2) = NULL_TREE; |
| flatten_await_stmt (ins, promoted, temps_used, NULL); |
| flatten_await_stmt (n, promoted, temps_used, NULL); |
| return; |
| } |
| break; |
| case TARGET_EXPR: |
| { |
| /* We have a temporary; promote it, but allow for the idiom in code |
| generated by the compiler like |
| a = (target_expr produces temp, op uses temp). */ |
| tree init = t; |
| temps_used->add (init); |
| tree var_type = TREE_TYPE (init); |
| char *buf = xasprintf ("T%03u", (unsigned) temps_used->elements ()); |
| tree var = build_lang_decl (VAR_DECL, get_identifier (buf), var_type); |
| DECL_ARTIFICIAL (var) = true; |
| free (buf); |
| bool already_present = promoted->add (var); |
| gcc_checking_assert (!already_present); |
| tree inner = TARGET_EXPR_INITIAL (init); |
| gcc_checking_assert |
| (TREE_CODE (inner) != COND_EXPR |
| || !cp_walk_tree (&inner, find_any_await, nullptr, nullptr)); |
| init = cp_build_modify_expr (input_location, var, INIT_EXPR, init, |
| tf_warning_or_error); |
| /* Simplify for the case that we have an init containing the temp |
| alone. */ |
| if (t == n->init && n->var == NULL_TREE) |
| { |
| n->var = var; |
| proxy_replace pr = {TARGET_EXPR_SLOT (t), var}; |
| cp_walk_tree (&init, replace_proxy, &pr, NULL); |
| n->init = init; |
| if (replace_in) |
| cp_walk_tree (replace_in, replace_proxy, &pr, NULL); |
| flatten_await_stmt (n, promoted, temps_used, NULL); |
| } |
| else |
| { |
| var_nest_node *ins |
| = new var_nest_node (var, init, n->prev, n); |
| /* We have to replace the target expr... */ |
| *v.entry = var; |
| /* ... and any uses of its var. */ |
| proxy_replace pr = {TARGET_EXPR_SLOT (t), var}; |
| cp_walk_tree (&n->init, replace_proxy, &pr, NULL); |
| /* Compiler-generated temporaries can also have uses in |
| following arms of compound expressions, which will be listed |
| in 'replace_in' if present. */ |
| if (replace_in) |
| cp_walk_tree (replace_in, replace_proxy, &pr, NULL); |
| flatten_await_stmt (ins, promoted, temps_used, NULL); |
| flatten_await_stmt (n, promoted, temps_used, NULL); |
| } |
| return; |
| } |
| break; |
| } |
| } |
| |
| /* Helper for 'process_conditional' that handles recursion into nested |
| conditionals. */ |
| |
| static void |
| handle_nested_conditionals (var_nest_node *n, vec<tree>& list, |
| hash_map<tree, tree>& map) |
| { |
| do |
| { |
| if (n->var && DECL_NAME (n->var)) |
| { |
| list.safe_push (n->var); |
| if (TYPE_HAS_NONTRIVIAL_DESTRUCTOR (TREE_TYPE (n->var))) |
| { |
| bool existed; |
| tree& flag = map.get_or_insert (n->var, &existed); |
| if (!existed) |
| { |
| /* We didn't see this var before and it needs a DTOR, so |
| build a guard variable for it. */ |
| char *nam |
| = xasprintf ("%s_guard", |
| IDENTIFIER_POINTER (DECL_NAME (n->var))); |
| flag = build_lang_decl (VAR_DECL, get_identifier (nam), |
| boolean_type_node); |
| free (nam); |
| DECL_ARTIFICIAL (flag) = true; |
| } |
| |
| /* The initializer for this variable is replaced by a compound |
| expression that performs the init and then records that the |
| variable is live (and the DTOR should be run at the scope |
| exit. */ |
| tree set_flag = cp_build_init_expr (flag, boolean_true_node); |
| n->init |
| = build2 (COMPOUND_EXPR, boolean_type_node, n->init, set_flag); |
| } |
| } |
| if (TREE_CODE (n->init) == COND_EXPR) |
| { |
| tree new_then = push_stmt_list (); |
| handle_nested_conditionals (n->then_cl, list, map); |
| new_then = pop_stmt_list (new_then); |
| tree new_else = push_stmt_list (); |
| handle_nested_conditionals (n->else_cl, list, map); |
| new_else = pop_stmt_list (new_else); |
| tree new_if |
| = build4 (IF_STMT, void_type_node, COND_EXPR_COND (n->init), |
| new_then, new_else, NULL_TREE); |
| add_stmt (new_if); |
| } |
| else |
| finish_expr_stmt (n->init); |
| n = n->next; |
| } while (n); |
| } |
| |
| /* helper for 'maybe_promote_temps'. |
| |
| When we have a conditional expression which might embed await expressions |
| and/or promoted variables, we need to handle it appropriately. |
| |
| The linked lists for the 'then' and 'else' clauses in a conditional node |
| identify the promoted variables (but these cannot be wrapped in a regular |
| cleanup). |
| |
| So recurse through the lists and build up a composite list of captured vars. |
| Declare these and any guard variables needed to decide if a DTOR should be |
| run. Then embed the conditional into a try-finally expression that handles |
| running each DTOR conditionally on its guard variable. */ |
| |
| static void |
| process_conditional (var_nest_node *n, tree& vlist) |
| { |
| tree init = n->init; |
| hash_map<tree, tree> var_flags; |
| auto_vec<tree> var_list; |
| tree new_then = push_stmt_list (); |
| handle_nested_conditionals (n->then_cl, var_list, var_flags); |
| new_then = pop_stmt_list (new_then); |
| tree new_else = push_stmt_list (); |
| handle_nested_conditionals (n->else_cl, var_list, var_flags); |
| new_else = pop_stmt_list (new_else); |
| /* Declare the vars. There are two loops so that the boolean flags are |
| grouped in the frame. */ |
| for (unsigned i = 0; i < var_list.length(); i++) |
| { |
| tree var = var_list[i]; |
| DECL_CHAIN (var) = vlist; |
| vlist = var; |
| add_decl_expr (var); |
| } |
| /* Define the guard flags for variables that need a DTOR. */ |
| for (unsigned i = 0; i < var_list.length(); i++) |
| { |
| tree *flag = var_flags.get (var_list[i]); |
| if (flag) |
| { |
| DECL_INITIAL (*flag) = boolean_false_node; |
| DECL_CHAIN (*flag) = vlist; |
| vlist = *flag; |
| add_decl_expr (*flag); |
| } |
| } |
| tree new_if |
| = build4 (IF_STMT, void_type_node, COND_EXPR_COND (init), |
| new_then, new_else, NULL_TREE); |
| /* Build a set of conditional DTORs. */ |
| tree final_actions = push_stmt_list (); |
| while (!var_list.is_empty()) |
| { |
| tree var = var_list.pop (); |
| tree *flag = var_flags.get (var); |
| if (!flag) |
| continue; |
| if (tree cleanup = cxx_maybe_build_cleanup (var, tf_warning_or_error)) |
| { |
| tree cond_cleanup = begin_if_stmt (); |
| finish_if_stmt_cond (*flag, cond_cleanup); |
| finish_expr_stmt (cleanup); |
| finish_then_clause (cond_cleanup); |
| finish_if_stmt (cond_cleanup); |
| } |
| } |
| final_actions = pop_stmt_list (final_actions); |
| tree try_finally |
| = build2 (TRY_FINALLY_EXPR, void_type_node, new_if, final_actions); |
| add_stmt (try_finally); |
| } |
| |
| /* Given *STMT, that contains at least one await expression. |
| |
| The full expression represented in the original source code will contain |
| suspension points, but it is still required that the lifetime of temporary |
| values extends to the end of the expression. |
| |
| We already have a mechanism to 'promote' user-authored local variables |
| to a coroutine frame counterpart (which allows explicit management of the |
| lifetime across suspensions). The transform here re-writes STMT into |
| a bind expression, promotes temporary values into local variables in that |
| and flattens the statement into a series of cleanups. |
| |
| Conditional expressions are re-written to regular 'if' statements. |
| The cleanups for variables initialized inside a conditional (including |
| nested cases) are wrapped in a try-finally clause, with guard variables |
| to determine which DTORs need to be run. */ |
| |
| static tree |
| maybe_promote_temps (tree *stmt, void *d) |
| { |
| susp_frame_data *awpts = (susp_frame_data *) d; |
| |
| location_t sloc = EXPR_LOCATION (*stmt); |
| tree expr = *stmt; |
| /* Strip off uninteresting wrappers. */ |
| if (TREE_CODE (expr) == CLEANUP_POINT_EXPR) |
| expr = TREE_OPERAND (expr, 0); |
| if (TREE_CODE (expr) == EXPR_STMT) |
| expr = EXPR_STMT_EXPR (expr); |
| if (TREE_CODE (expr) == CONVERT_EXPR |
| && VOID_TYPE_P (TREE_TYPE (expr))) |
| expr = TREE_OPERAND (expr, 0); |
| STRIP_NOPS (expr); |
| |
| /* We walk the statement trees, flattening it into an ordered list of |
| variables with initializers and fragments corresponding to compound |
| expressions, truth or/and if and ternary conditionals. Conditional |
| expressions carry a nested list of fragments for the then and else |
| clauses. We anchor to the 'bottom' of the fragment list; we will write |
| a cleanup nest with one shell for each variable initialized. */ |
| var_nest_node *root = new var_nest_node (NULL_TREE, expr, NULL, NULL); |
| /* Check to see we didn't promote one twice. */ |
| hash_set<tree> promoted_vars; |
| hash_set<tree> used_temps; |
| flatten_await_stmt (root, &promoted_vars, &used_temps, NULL); |
| |
| gcc_checking_assert (root->next == NULL); |
| tree vlist = NULL_TREE; |
| var_nest_node *t = root; |
| /* We build the bind scope expression from the bottom-up. |
| EXPR_LIST holds the inner expression nest at the current cleanup |
| level (becoming the final expression list when we've exhausted the |
| number of sub-expression fragments). */ |
| tree expr_list = NULL_TREE; |
| do |
| { |
| tree new_list = push_stmt_list (); |
| /* When we have a promoted variable, then add that to the bind scope |
| and initialize it. When there's no promoted variable, we just need |
| to run the initializer. |
| If the initializer is a conditional expression, we need to collect |
| and declare any promoted variables nested within it. DTORs for such |
| variables must be run conditionally too. |
| |
| Since here we're synthetically processing code here, we've already |
| emitted any Wunused-result warnings. Below, however, we call |
| finish_expr_stmt, which will convert its operand to void, and could |
| result in such a diagnostic being emitted. To avoid that, convert to |
| void ahead of time. */ |
| if (t->var) |
| { |
| tree var = t->var; |
| DECL_CHAIN (var) = vlist; |
| vlist = var; |
| add_decl_expr (var); |
| if (TREE_CODE (t->init) == COND_EXPR) |
| process_conditional (t, vlist); |
| else |
| finish_expr_stmt (convert_to_void (t->init, ICV_STATEMENT, tf_none)); |
| if (tree cleanup = cxx_maybe_build_cleanup (var, tf_warning_or_error)) |
| { |
| tree cl = build_stmt (sloc, CLEANUP_STMT, expr_list, cleanup, var); |
| add_stmt (cl); /* push this onto the level above. */ |
| } |
| else if (expr_list) |
| { |
| if (TREE_CODE (expr_list) != STATEMENT_LIST) |
| add_stmt (expr_list); |
| else if (!tsi_end_p (tsi_start (expr_list))) |
| add_stmt (expr_list); |
| } |
| } |
| else |
| { |
| if (TREE_CODE (t->init) == COND_EXPR) |
| process_conditional (t, vlist); |
| else |
| finish_expr_stmt (convert_to_void (t->init, ICV_STATEMENT, tf_none)); |
| if (expr_list) |
| { |
| if (TREE_CODE (expr_list) != STATEMENT_LIST) |
| add_stmt (expr_list); |
| else if (!tsi_end_p (tsi_start (expr_list))) |
| add_stmt (expr_list); |
| } |
| } |
| expr_list = pop_stmt_list (new_list); |
| var_nest_node *old = t; |
| t = t->prev; |
| delete old; |
| } while (t); |
| |
| /* Now produce the bind expression containing the 'promoted' temporaries |
| as its variable list, and the cleanup nest as the statement. */ |
| tree await_bind = build3_loc (sloc, BIND_EXPR, void_type_node, |
| NULL, NULL, NULL); |
| BIND_EXPR_BODY (await_bind) = expr_list; |
| BIND_EXPR_VARS (await_bind) = nreverse (vlist); |
| tree b_block = make_node (BLOCK); |
| if (!awpts->block_stack->is_empty ()) |
| { |
| tree s_block = awpts->block_stack->last (); |
| if (s_block) |
| { |
| BLOCK_SUPERCONTEXT (b_block) = s_block; |
| BLOCK_CHAIN (b_block) = BLOCK_SUBBLOCKS (s_block); |
| BLOCK_SUBBLOCKS (s_block) = b_block; |
| } |
| } |
| BLOCK_VARS (b_block) = BIND_EXPR_VARS (await_bind) ; |
| BIND_EXPR_BLOCK (await_bind) = b_block; |
| TREE_SIDE_EFFECTS (await_bind) = TREE_SIDE_EFFECTS (BIND_EXPR_BODY (await_bind)); |
| *stmt = await_bind; |
| hash_set<tree> visited; |
| return cp_walk_tree (stmt, register_awaits, d, &visited); |
| } |
| |
| /* Relatively lightweight callback to do initial assessment: |
| 0) Rewrite some await expressions. |
| 1) If the statement/expression contains any await expressions. |
| 2) If the statement/expression potentially requires a re-write to handle |
| TRUTH_{AND,OR}IF_EXPRs since, in most cases, they will need expansion |
| so that the await expressions are not processed in the case of the |
| short-circuit arm. |
| |
| CO_YIELD expressions are re-written to their underlying co_await. */ |
| |
| static tree |
| analyze_expression_awaits (tree *stmt, int *do_subtree, void *d) |
| { |
| susp_frame_data *awpts = (susp_frame_data *) d; |
| |
| switch (TREE_CODE (*stmt)) |
| { |
| default: return NULL_TREE; |
| case CALL_EXPR: |
| { |
| tree fn = cp_get_callee_fndecl_nofold (*stmt); |
| /* Special-cases where we want to re-write await expressions to some |
| other value before they are otherwise processed. */ |
| if (fn && DECL_IS_BUILTIN_CONSTANT_P (fn)) |
| { |
| gcc_checking_assert (call_expr_nargs (*stmt) == 1); |
| tree expr = CALL_EXPR_ARG (*stmt, 0); |
| if (cp_walk_tree (&expr, find_any_await, nullptr, NULL)) |
| { |
| if (TREE_CONSTANT (maybe_constant_value (expr))) |
| *stmt = integer_one_node; |
| else |
| *stmt = integer_zero_node; |
| } |
| *do_subtree = 0; |
| } |
| else if (!fn && CALL_EXPR_IFN (*stmt) == IFN_ASSUME) |
| { |
| tree expr = CALL_EXPR_ARG (*stmt, 0); |
| if (TREE_SIDE_EFFECTS (expr)) |
| { |
| location_t loc_e = cp_expr_location (expr); |
| location_t loc_s = cp_expr_location (*stmt); |
| location_t loc_n = make_location (loc_e, loc_s, loc_s); |
| warning_at (loc_n, OPT_Wattributes,"assumption ignored" |
| " because it contains an await-expression"); |
| *stmt = build_empty_stmt (loc_n); |
| } |
| } |
| } |
| break; |
| case CO_YIELD_EXPR: |
| /* co_yield is syntactic sugar, re-write it to co_await. */ |
| *stmt = TREE_OPERAND (*stmt, 1); |
| /* FALLTHROUGH */ |
| case CO_AWAIT_EXPR: |
| awpts->saw_awaits++; |
| /* A non-null initializer for the awaiter means we need to expand. */ |
| if (TREE_OPERAND (*stmt, 2)) |
| awpts->has_awaiter_init = true; |
| break; |
| case TRUTH_ANDIF_EXPR: |
| case TRUTH_ORIF_EXPR: |
| { |
| /* We don't need special action for awaits in the always-executed |
| arm of a TRUTH_IF. */ |
| if (tree res = cp_walk_tree (&TREE_OPERAND (*stmt, 0), |
| analyze_expression_awaits, d, NULL)) |
| return res; |
| /* However, if there are await expressions on the conditionally |
| executed branch, we must expand the TRUTH_IF to ensure that the |
| expanded await expression control-flow is fully contained in the |
| conditionally executed code. */ |
| unsigned aw_count = awpts->saw_awaits; |
| if (tree res = cp_walk_tree (&TREE_OPERAND (*stmt, 1), |
| analyze_expression_awaits, d, NULL)) |
| return res; |
| if (awpts->saw_awaits > aw_count) |
| { |
| awpts->truth_aoif_to_expand->add (*stmt); |
| awpts->needs_truth_if_exp = true; |
| } |
| /* We've done the sub-trees here. */ |
| *do_subtree = 0; |
| } |
| break; |
| } |
| |
| return NULL_TREE; /* Recurse until done. */ |
| } |
| |
| /* Given *EXPR |
| If EXPR contains a TRUTH_{AND,OR}IF_EXPR, TAOIE with an await expr on |
| the conditionally executed branch, change this in a ternary operator. |
| |
| bool not_expr = TAOIE == TRUTH_ORIF_EXPR ? NOT : NOP; |
| not_expr (always-exec expr) ? conditionally-exec expr : not_expr; |
| |
| Apply this recursively to the condition and the conditionally-exec |
| branch. */ |
| |
| struct truth_if_transform { |
| tree *orig_stmt; |
| tree scratch_var; |
| hash_set<tree> *truth_aoif_to_expand; |
| }; |
| |
| static tree |
| expand_one_truth_if (tree *expr, int *do_subtree, void *d) |
| { |
| truth_if_transform *xform = (truth_if_transform *) d; |
| |
| bool needs_not = false; |
| switch (TREE_CODE (*expr)) |
| { |
| default: break; |
| case TRUTH_ORIF_EXPR: |
| needs_not = true; |
| /* FALLTHROUGH */ |
| case TRUTH_ANDIF_EXPR: |
| { |
| if (!xform->truth_aoif_to_expand->contains (*expr)) |
| break; |
| |
| location_t sloc = EXPR_LOCATION (*expr); |
| /* Transform truth expression into a cond expression with |
| * the always-executed arm as the condition. |
| * the conditionally-executed arm as the then clause. |
| * the 'else' clause is fixed: 'true' for ||,'false' for &&. */ |
| tree cond = TREE_OPERAND (*expr, 0); |
| tree test1 = TREE_OPERAND (*expr, 1); |
| tree fixed = needs_not ? boolean_true_node : boolean_false_node; |
| if (needs_not) |
| cond = build1 (TRUTH_NOT_EXPR, boolean_type_node, cond); |
| tree cond_expr |
| = build3_loc (sloc, COND_EXPR, boolean_type_node, |
| cond, test1, fixed); |
| *expr = cond_expr; |
| if (tree res = cp_walk_tree (&COND_EXPR_COND (*expr), |
| expand_one_truth_if, d, NULL)) |
| return res; |
| if (tree res = cp_walk_tree (&COND_EXPR_THEN (*expr), |
| expand_one_truth_if, d, NULL)) |
| return res; |
| /* We've manually processed necessary sub-trees here. */ |
| *do_subtree = 0; |
| } |
| break; |
| } |
| return NULL_TREE; |
| } |
| |
| /* Helper that adds a new variable of VAR_TYPE to a bind scope BIND, the |
| name is made up from NAM_ROOT, NAM_VERS. */ |
| |
| static tree |
| add_var_to_bind (tree& bind, tree var_type, |
| const char *nam_root, unsigned nam_vers) |
| { |
| tree b_vars = BIND_EXPR_VARS (bind); |
| /* Build a variable to hold the condition, this will be included in the |
| frame as a local var. */ |
| char *nam = xasprintf ("__%s_%d", nam_root, nam_vers); |
| tree newvar = build_lang_decl (VAR_DECL, get_identifier (nam), var_type); |
| free (nam); |
| DECL_CHAIN (newvar) = b_vars; |
| BIND_EXPR_VARS (bind) = newvar; |
| return newvar; |
| } |
| |
| /* Helper to build and add if (!cond) break; */ |
| |
| static void |
| coro_build_add_if_not_cond_break (tree cond) |
| { |
| tree if_stmt = begin_if_stmt (); |
| tree invert = build1 (TRUTH_NOT_EXPR, boolean_type_node, cond); |
| finish_if_stmt_cond (invert, if_stmt); |
| finish_break_stmt (); |
| finish_then_clause (if_stmt); |
| finish_if_stmt (if_stmt); |
| } |
| |
| /* Tree walk callback to replace continue statements with goto label. */ |
| static tree |
| replace_continue (tree *stmt, int *do_subtree, void *d) |
| { |
| tree expr = *stmt; |
| if (TREE_CODE (expr) == CLEANUP_POINT_EXPR) |
| expr = TREE_OPERAND (expr, 0); |
| if (CONVERT_EXPR_P (expr) && VOID_TYPE_P (TREE_TYPE (expr))) |
| expr = TREE_OPERAND (expr, 0); |
| STRIP_NOPS (expr); |
| if (!STATEMENT_CLASS_P (expr)) |
| return NULL_TREE; |
| |
| switch (TREE_CODE (expr)) |
| { |
| /* Unless it's a special case, just walk the subtrees as usual. */ |
| default: return NULL_TREE; |
| |
| case CONTINUE_STMT: |
| { |
| tree *label = (tree *)d; |
| location_t loc = EXPR_LOCATION (expr); |
| /* re-write a continue to goto label. */ |
| *stmt = build_stmt (loc, GOTO_EXPR, *label); |
| *do_subtree = 0; |
| return NULL_TREE; |
| } |
| |
| /* Statements that do not require recursion. */ |
| case DECL_EXPR: |
| case BREAK_STMT: |
| case GOTO_EXPR: |
| case LABEL_EXPR: |
| case CASE_LABEL_EXPR: |
| case ASM_EXPR: |
| /* These must break recursion. */ |
| case FOR_STMT: |
| case WHILE_STMT: |
| case DO_STMT: |
| *do_subtree = 0; |
| return NULL_TREE; |
| } |
| } |
| |
| /* Tree walk callback to analyze, register and pre-process statements that |
| contain await expressions. */ |
| |
| static tree |
| await_statement_walker (tree *stmt, int *do_subtree, void *d) |
| { |
| tree res = NULL_TREE; |
| susp_frame_data *awpts = (susp_frame_data *) d; |
| |
| /* Process a statement at a time. */ |
| if (TREE_CODE (*stmt) == BIND_EXPR) |
| { |
| /* For conditional expressions, we might wish to add an artificial var |
| to their containing bind expr. */ |
| vec_safe_push (awpts->bind_stack, *stmt); |
| /* We might need to insert a new bind expression, and want to link it |
| into the correct scope, so keep a note of the current block scope. */ |
| tree blk = BIND_EXPR_BLOCK (*stmt); |
| vec_safe_push (awpts->block_stack, blk); |
| res = cp_walk_tree (&BIND_EXPR_BODY (*stmt), await_statement_walker, |
| d, NULL); |
| awpts->block_stack->pop (); |
| awpts->bind_stack->pop (); |
| *do_subtree = 0; /* Done subtrees. */ |
| return res; |
| } |
| else if (TREE_CODE (*stmt) == STATEMENT_LIST) |
| { |
| for (tree &s : tsi_range (*stmt)) |
| { |
| res = cp_walk_tree (&s, await_statement_walker, |
| d, NULL); |
| if (res) |
| return res; |
| } |
| *do_subtree = 0; /* Done subtrees. */ |
| return NULL_TREE; |
| } |
| |
| /* We have something to be handled as a single statement. We have to handle |
| a few statements specially where await statements have to be moved out of |
| constructs. */ |
| tree expr = *stmt; |
| if (TREE_CODE (*stmt) == CLEANUP_POINT_EXPR) |
| expr = TREE_OPERAND (expr, 0); |
| STRIP_NOPS (expr); |
| |
| if (STATEMENT_CLASS_P (expr)) |
| switch (TREE_CODE (expr)) |
| { |
| /* Unless it's a special case, just walk the subtrees as usual. */ |
| default: return NULL_TREE; |
| |
| /* When we have a conditional expression, which contains one or more |
| await expressions, we have to break the condition out into a |
| regular statement so that the control flow introduced by the await |
| transforms can be implemented. */ |
| case IF_STMT: |
| { |
| tree *await_ptr; |
| hash_set<tree> visited; |
| /* Transform 'if (cond with awaits) then stmt1 else stmt2' into |
| bool cond = cond with awaits. |
| if (cond) then stmt1 else stmt2. */ |
| tree if_stmt = *stmt; |
| /* We treat the condition as if it was a stand-alone statement, |
| to see if there are any await expressions which will be analyzed |
| and registered. */ |
| if (!(cp_walk_tree (&IF_COND (if_stmt), |
| find_any_await, &await_ptr, &visited))) |
| return NULL_TREE; /* Nothing special to do here. */ |
| |
| gcc_checking_assert (!awpts->bind_stack->is_empty()); |
| tree& bind_expr = awpts->bind_stack->last (); |
| tree newvar = add_var_to_bind (bind_expr, boolean_type_node, |
| "ifcd", awpts->cond_number++); |
| tree insert_list = push_stmt_list (); |
| tree cond_inner = IF_COND (if_stmt); |
| if (TREE_CODE (cond_inner) == CLEANUP_POINT_EXPR) |
| cond_inner = TREE_OPERAND (cond_inner, 0); |
| add_decl_expr (newvar); |
| location_t sloc = EXPR_LOCATION (IF_COND (if_stmt)); |
| /* We want to initialize the new variable with the expression |
| that contains the await(s) and potentially also needs to |
| have truth_if expressions expanded. */ |
| tree new_s = cp_build_init_expr (sloc, newvar, cond_inner); |
| finish_expr_stmt (new_s); |
| IF_COND (if_stmt) = newvar; |
| add_stmt (if_stmt); |
| *stmt = pop_stmt_list (insert_list); |
| /* So now walk the new statement list. */ |
| res = cp_walk_tree (stmt, await_statement_walker, d, NULL); |
| *do_subtree = 0; /* Done subtrees. */ |
| return res; |
| } |
| break; |
| case FOR_STMT: |
| { |
| tree *await_ptr; |
| hash_set<tree> visited; |
| /* for loops only need special treatment if the condition or the |
| iteration expression contain a co_await. */ |
| tree for_stmt = *stmt; |
| /* At present, the FE always generates a separate initializer for |
| the FOR_INIT_STMT, when the expression has an await. Check that |
| this assumption holds in the future. */ |
| gcc_checking_assert |
| (!(cp_walk_tree (&FOR_INIT_STMT (for_stmt), find_any_await, |
| &await_ptr, &visited))); |
| |
| visited.empty (); |
| bool for_cond_await |
| = cp_walk_tree (&FOR_COND (for_stmt), find_any_await, |
| &await_ptr, &visited); |
| |
| visited.empty (); |
| bool for_expr_await |
| = cp_walk_tree (&FOR_EXPR (for_stmt), find_any_await, |
| &await_ptr, &visited); |
| |
| /* If the condition has an await, then we will need to rewrite the |
| loop as |
| for (init expression;true;iteration expression) { |
| condition = await expression; |
| if (condition) |
| break; |
| ... |
| } |
| */ |
| if (for_cond_await) |
| { |
| tree insert_list = push_stmt_list (); |
| /* This will be expanded when the revised body is handled. */ |
| coro_build_add_if_not_cond_break (FOR_COND (for_stmt)); |
| /* .. add the original for body. */ |
| add_stmt (FOR_BODY (for_stmt)); |
| /* To make the new for body. */ |
| FOR_BODY (for_stmt) = pop_stmt_list (insert_list); |
| FOR_COND (for_stmt) = boolean_true_node; |
| } |
| /* If the iteration expression has an await, it's a bit more |
| tricky. |
| for (init expression;condition;) { |
| ... |
| iteration_expr_label: |
| iteration expression with await; |
| } |
| but, then we will need to re-write any continue statements into |
| 'goto iteration_expr_label:'. |
| */ |
| if (for_expr_await) |
| { |
| location_t sloc = EXPR_LOCATION (FOR_EXPR (for_stmt)); |
| tree insert_list = push_stmt_list (); |
| /* The original for body. */ |
| add_stmt (FOR_BODY (for_stmt)); |
| char *buf = xasprintf ("for.iter.expr.%u", awpts->cond_number++); |
| tree it_expr_label |
| = create_named_label_with_ctx (sloc, buf, NULL_TREE); |
| free (buf); |
| add_stmt (build_stmt (sloc, LABEL_EXPR, it_expr_label)); |
| tree for_expr = FOR_EXPR (for_stmt); |
| /* Present the iteration expression as a statement. */ |
| if (TREE_CODE (for_expr) == CLEANUP_POINT_EXPR) |
| for_expr = TREE_OPERAND (for_expr, 0); |
| STRIP_NOPS (for_expr); |
| finish_expr_stmt (for_expr); |
| FOR_EXPR (for_stmt) = NULL_TREE; |
| FOR_BODY (for_stmt) = pop_stmt_list (insert_list); |
| /* rewrite continue statements to goto label. */ |
| hash_set<tree> visited_continue; |
| if ((res = cp_walk_tree (&FOR_BODY (for_stmt), |
| replace_continue, &it_expr_label, &visited_continue))) |
| return res; |
| } |
| |
| /* So now walk the body statement (list), if there were no await |
| expressions, then this handles the original body - and either |
| way we will have finished with this statement. */ |
| res = cp_walk_tree (&FOR_BODY (for_stmt), |
| await_statement_walker, d, NULL); |
| *do_subtree = 0; /* Done subtrees. */ |
| return res; |
| } |
| break; |
| case WHILE_STMT: |
| { |
| /* We turn 'while (cond with awaits) stmt' into |
| while (true) { |
| if (!(cond with awaits)) |
| break; |
| stmt.. |
| } */ |
| tree *await_ptr; |
| hash_set<tree> visited; |
| tree while_stmt = *stmt; |
| if (!(cp_walk_tree (&WHILE_COND (while_stmt), |
| find_any_await, &await_ptr, &visited))) |
| return NULL_TREE; /* Nothing special to do here. */ |
| |
| tree insert_list = push_stmt_list (); |
| coro_build_add_if_not_cond_break (WHILE_COND (while_stmt)); |
| /* The original while body. */ |
| add_stmt (WHILE_BODY (while_stmt)); |
| /* The new while body. */ |
| WHILE_BODY (while_stmt) = pop_stmt_list (insert_list); |
| WHILE_COND (while_stmt) = boolean_true_node; |
| /* So now walk the new statement list. */ |
| res = cp_walk_tree (&WHILE_BODY (while_stmt), |
| await_statement_walker, d, NULL); |
| *do_subtree = 0; /* Done subtrees. */ |
| return res; |
| } |
| break; |
| case DO_STMT: |
| { |
| /* We turn do stmt while (cond with awaits) into: |
| do { |
| stmt.. |
| if (!(cond with awaits)) |
| break; |
| } while (true); */ |
| tree do_stmt = *stmt; |
| tree *await_ptr; |
| hash_set<tree> visited; |
| if (!(cp_walk_tree (&DO_COND (do_stmt), |
| find_any_await, &await_ptr, &visited))) |
| return NULL_TREE; /* Nothing special to do here. */ |
| |
| tree insert_list = push_stmt_list (); |
| /* The original do stmt body. */ |
| add_stmt (DO_BODY (do_stmt)); |
| coro_build_add_if_not_cond_break (DO_COND (do_stmt)); |
| /* The new while body. */ |
| DO_BODY (do_stmt) = pop_stmt_list (insert_list); |
| DO_COND (do_stmt) = boolean_true_node; |
| /* So now walk the new statement list. */ |
| res = cp_walk_tree (&DO_BODY (do_stmt), await_statement_walker, |
| d, NULL); |
| *do_subtree = 0; /* Done subtrees. */ |
| return res; |
| } |
| break; |
| case SWITCH_STMT: |
| { |
| /* We turn 'switch (cond with awaits) stmt' into |
| switch_type cond = cond with awaits |
| switch (cond) stmt. */ |
| tree sw_stmt = *stmt; |
| tree *await_ptr; |
| hash_set<tree> visited; |
| if (!(cp_walk_tree (&SWITCH_STMT_COND (sw_stmt), |
| find_any_await, &await_ptr, &visited))) |
| return NULL_TREE; /* Nothing special to do here. */ |
| |
| gcc_checking_assert (!awpts->bind_stack->is_empty()); |
| /* Build a variable to hold the condition, this will be |
| included in the frame as a local var. */ |
| tree& bind_expr = awpts->bind_stack->last (); |
| tree sw_type = SWITCH_STMT_TYPE (sw_stmt); |
| tree newvar = add_var_to_bind (bind_expr, sw_type, "swch", |
| awpts->cond_number++); |
| tree insert_list = push_stmt_list (); |
| add_decl_expr (newvar); |
| |
| tree cond_inner = SWITCH_STMT_COND (sw_stmt); |
| if (TREE_CODE (cond_inner) == CLEANUP_POINT_EXPR) |
| cond_inner = TREE_OPERAND (cond_inner, 0); |
| location_t sloc = EXPR_LOCATION (SWITCH_STMT_COND (sw_stmt)); |
| tree new_s = cp_build_init_expr (sloc, newvar, |
| cond_inner); |
| finish_expr_stmt (new_s); |
| SWITCH_STMT_COND (sw_stmt) = newvar; |
| /* Now add the switch statement with the condition re- |
| written to use the local var. */ |
| add_stmt (sw_stmt); |
| *stmt = pop_stmt_list (insert_list); |
| /* Process the expanded list. */ |
| res = cp_walk_tree (stmt, await_statement_walker, |
| d, NULL); |
| *do_subtree = 0; /* Done subtrees. */ |
| return res; |
| } |
| break; |
| case CO_RETURN_EXPR: |
| { |
| /* Expand the co_return as per [stmt.return.coroutine] |
| - for co_return; |
| { p.return_void (); goto final_suspend; } |
| - for co_return [void expr]; |
| { expr; p.return_void(); goto final_suspend;} |
| - for co_return [non void expr]; |
| { p.return_value(expr); goto final_suspend; } */ |
| location_t loc = EXPR_LOCATION (expr); |
| tree call = TREE_OPERAND (expr, 1); |
| expr = TREE_OPERAND (expr, 0); |
| tree ret_list = push_stmt_list (); |
| /* [stmt.return.coroutine], 2.2 |
| If expr is present and void, it is placed immediately before |
| the call for return_void; */ |
| if (expr && VOID_TYPE_P (TREE_TYPE (expr))) |
| finish_expr_stmt (expr); |
| /* Insert p.return_{void,value(expr)}. */ |
| finish_expr_stmt (call); |
| TREE_USED (awpts->fs_label) = 1; |
| add_stmt (build_stmt (loc, GOTO_EXPR, awpts->fs_label)); |
| *stmt = pop_stmt_list (ret_list); |
| res = cp_walk_tree (stmt, await_statement_walker, d, NULL); |
| /* Once this is complete, we will have processed subtrees. */ |
| *do_subtree = 0; |
| return res; |
| } |
| break; |
| case HANDLER: |
| { |
| /* [expr.await] An await-expression shall appear only in a |
| potentially-evaluated expression within the compound-statement |
| of a function-body outside of a handler. */ |
| tree *await_ptr; |
| hash_set<tree> visited; |
| if (!(cp_walk_tree (&HANDLER_BODY (expr), find_any_await, |
| &await_ptr, &visited))) |
| return NULL_TREE; /* All OK. */ |
| location_t loc = EXPR_LOCATION (*await_ptr); |
| error_at (loc, "await expressions are not permitted in handlers"); |
| return NULL_TREE; /* This is going to fail later anyway. */ |
| } |
| break; |
| } |
| else if (EXPR_P (expr)) |
| { |
| hash_set<tree> visited; |
| tree *await_ptr; |
| if (!(cp_walk_tree (stmt, find_any_await, &await_ptr, &visited))) |
| return NULL_TREE; /* Nothing special to do here. */ |
| |
| visited.empty (); |
| awpts->saw_awaits = 0; |
| hash_set<tree> truth_aoif_to_expand; |
| awpts->truth_aoif_to_expand = &truth_aoif_to_expand; |
| awpts->needs_truth_if_exp = false; |
| awpts->has_awaiter_init = false; |
| if ((res = cp_walk_tree (stmt, analyze_expression_awaits, d, &visited))) |
| return res; |
| *do_subtree = 0; /* Done subtrees. */ |
| if (!awpts->saw_awaits) |
| return NULL_TREE; /* Nothing special to do here. */ |
| |
| if (awpts->needs_truth_if_exp) |
| { |
| /* If a truth-and/or-if expression has an await expression in the |
| conditionally-taken branch, then it must be rewritten into a |
| regular conditional. */ |
| truth_if_transform xf = {stmt, NULL_TREE, &truth_aoif_to_expand}; |
| if ((res = cp_walk_tree (stmt, expand_one_truth_if, &xf, NULL))) |
| return res; |
| } |
| /* Process this statement, which contains at least one await expression |
| to 'promote' temporary values to a coroutine frame slot. */ |
| return maybe_promote_temps (stmt, d); |
| } |
| /* Continue recursion, if needed. */ |
| return res; |
| } |
| |
| /* For figuring out what param usage we have. */ |
| |
| struct param_frame_data |
| { |
| tree *field_list; |
| hash_map<tree, param_info> *param_uses; |
| hash_set<tree *> *visited; |
| location_t loc; |
| bool param_seen; |
| }; |
| |
| /* A tree walk callback that rewrites each parm use to the local variable |
| that represents its copy in the frame. */ |
| |
| static tree |
| rewrite_param_uses (tree *stmt, int *do_subtree ATTRIBUTE_UNUSED, void *d) |
| { |
| param_frame_data *data = (param_frame_data *) d; |
| |
| /* For lambda closure content, we have to look specifically. */ |
| if (VAR_P (*stmt) && DECL_HAS_VALUE_EXPR_P (*stmt)) |
| { |
| tree t = DECL_VALUE_EXPR (*stmt); |
| return cp_walk_tree (&t, rewrite_param_uses, d, NULL); |
| } |
| |
| if (unevaluated_p (TREE_CODE (*stmt))) |
| { |
| /* No odr-uses in unevaluated operands. */ |
| *do_subtree = 0; |
| return NULL_TREE; |
| } |
| |
| if (TREE_CODE (*stmt) != PARM_DECL) |
| return NULL_TREE; |
| |
| /* If we already saw the containing expression, then we're done. */ |
| if (data->visited->add (stmt)) |
| return NULL_TREE; |
| |
| bool existed; |
| param_info &parm = data->param_uses->get_or_insert (*stmt, &existed); |
| gcc_checking_assert (existed); |
| |
| *stmt = parm.copy_var; |
| return NULL_TREE; |
| } |
| |
| /* Build up a set of info that determines how each param copy will be |
| handled. We store this in a hash map so that we can access it from |
| a tree walk callback that re-writes the original parameters to their |
| copies. */ |
| |
| void |
| cp_coroutine_transform::analyze_fn_parms () |
| { |
| if (!DECL_ARGUMENTS (orig_fn_decl)) |
| return; |
| |
| /* Build a hash map with an entry for each param. |
| The key is the param tree. |
| Then we have an entry for the frame field name. |
| Then a cache for the field ref when we come to use it. |
| Then a tree list of the uses. |
| The second two entries start out empty - and only get populated |
| when we see uses. */ |
| bool lambda_p = LAMBDA_FUNCTION_P (orig_fn_decl); |
| |
| /* Count the param copies from 1 as per the std. */ |
| unsigned parm_num = 1; |
| for (tree arg = DECL_ARGUMENTS (orig_fn_decl); arg != NULL; |
| ++parm_num, arg = DECL_CHAIN (arg)) |
| { |
| bool existed; |
| param_info &parm = param_uses.get_or_insert (arg, &existed); |
| gcc_checking_assert (!existed); |
| parm.body_uses = NULL; |
| tree actual_type = TREE_TYPE (arg); |
| actual_type = complete_type_or_else (actual_type, orig_fn_decl); |
| if (actual_type == NULL_TREE) |
| actual_type = error_mark_node; |
| parm.orig_type = actual_type; |
| parm.by_ref = parm.pt_ref = parm.rv_ref = false; |
| if (TREE_CODE (actual_type) == REFERENCE_TYPE) |
| { |
| /* If the user passes by reference, then we will save the |
| pointer to the original. As noted in |
| [dcl.fct.def.coroutine] / 13, if the lifetime of the |
| referenced item ends and then the coroutine is resumed, |
| we have UB; well, the user asked for it. */ |
| if (TYPE_REF_IS_RVALUE (actual_type)) |
| parm.rv_ref = true; |
| else |
| parm.pt_ref = true; |
| } |
| else if (TYPE_REF_P (DECL_ARG_TYPE (arg))) |
| parm.by_ref = true; |
| |
| parm.frame_type = actual_type; |
| |
| parm.this_ptr = is_this_parameter (arg); |
| parm.lambda_cobj = lambda_p && DECL_NAME (arg) == closure_identifier; |
| |
| tree name = DECL_NAME (arg); |
| if (!name) |
| { |
| char *buf = xasprintf ("_Coro_q%u___unnamed", parm_num); |
| name = get_identifier (buf); |
| free (buf); |
| } |
| parm.field_id = name; |
| if (TYPE_HAS_NONTRIVIAL_DESTRUCTOR (parm.frame_type)) |
| parm.trivial_dtor = false; |
| else |
| parm.trivial_dtor = true; |
| } |
| } |
| |
| /* Small helper for the repetitive task of adding a new field to the coro |
| frame type. */ |
| |
| static tree |
| coro_make_frame_entry (tree *field_list, const char *name, tree fld_type, |
| location_t loc) |
| { |
| tree id = get_identifier (name); |
| tree decl = build_decl (loc, FIELD_DECL, id, fld_type); |
| DECL_CHAIN (decl) = *field_list; |
| *field_list = decl; |
| return id; |
| } |
| |
| /* A tree-walk callback that processes one bind expression noting local |
| variables, and making a coroutine frame slot available for those that |
| need it, so that they can be 'promoted' across suspension points. */ |
| |
| static tree |
| register_local_var_uses (tree *stmt, int *do_subtree, void *d) |
| { |
| if (TREE_CODE (*stmt) != BIND_EXPR) |
| return NULL_TREE; |
| |
| local_vars_frame_data *lvd = (local_vars_frame_data *) d; |
| |
| /* As we enter a bind expression - record the vars there and then recurse. |
| As we exit drop the nest depth. |
| The bind index is a growing count of how many bind indices we've seen. |
| We build a space in the frame for each local var. */ |
| |
| tree lvar; |
| unsigned serial = 0; |
| for (lvar = BIND_EXPR_VARS (*stmt); lvar != NULL; lvar = DECL_CHAIN (lvar)) |
| { |
| bool existed; |
| local_var_info &local_var |
| = lvd->local_var_uses->get_or_insert (lvar, &existed); |
| gcc_checking_assert (!existed); |
| local_var.def_loc = DECL_SOURCE_LOCATION (lvar); |
| tree lvtype = TREE_TYPE (lvar); |
| local_var.frame_type = lvtype; |
| local_var.field_idx = local_var.field_id = NULL_TREE; |
| |
| /* Make sure that we only present vars to the tests below. */ |
| if (TREE_CODE (lvar) != PARM_DECL |
| && TREE_CODE (lvar) != VAR_DECL) |
| continue; |
| |
| /* We don't move static vars into the frame. */ |
| local_var.is_static = TREE_STATIC (lvar); |
| if (local_var.is_static) |
| continue; |
| |
| poly_uint64 size; |
| if (TREE_CODE (lvtype) == ARRAY_TYPE |
| && !poly_int_tree_p (DECL_SIZE_UNIT (lvar), &size)) |
| { |
| sorry_at (local_var.def_loc, "variable length arrays are not" |
| " yet supported in coroutines"); |
| /* Ignore it, this is broken anyway. */ |
| continue; |
| } |
| |
| lvd->local_var_seen = true; |
| /* If this var is a lambda capture proxy, we want to leave it alone, |
| and later rewrite the DECL_VALUE_EXPR to indirect through the |
| frame copy of the pointer to the lambda closure object. */ |
| local_var.is_lambda_capture = is_capture_proxy (lvar); |
| if (local_var.is_lambda_capture) |
| continue; |
| |
| /* If a variable has a value expression, then that's what needs |
| to be processed. */ |
| local_var.has_value_expr_p = DECL_HAS_VALUE_EXPR_P (lvar); |
| if (local_var.has_value_expr_p) |
| continue; |
| |
| /* Make names depth+index unique, so that we can support nested |
| scopes with identically named locals and still be able to |
| identify them in the coroutine frame. */ |
| tree lvname = DECL_NAME (lvar); |
| char *buf = NULL; |
| |
| /* The outermost bind scope contains the artificial variables that |
| we inject to implement the coro state machine. We want to be able |
| to inspect these in debugging. */ |
| if (lvname != NULL_TREE && lvd->nest_depth == 0) |
| buf = xasprintf ("%s", IDENTIFIER_POINTER (lvname)); |
| else if (lvname != NULL_TREE) |
| buf = xasprintf ("%s_%u_%u", IDENTIFIER_POINTER (lvname), |
| lvd->nest_depth, lvd->bind_indx); |
| else |
| buf = xasprintf ("_D%u_%u_%u", lvd->nest_depth, lvd->bind_indx, |
| serial++); |
| |
| /* TODO: Figure out if we should build a local type that has any |
| excess alignment or size from the original decl. */ |
| local_var.field_id = coro_make_frame_entry (lvd->field_list, buf, |
| lvtype, lvd->loc); |
| free (buf); |
| /* We don't walk any of the local var sub-trees, they won't contain |
| any bind exprs. */ |
| } |
| lvd->bind_indx++; |
| lvd->nest_depth++; |
| /* Ensure we only visit each expression once. */ |
| cp_walk_tree_without_duplicates (&BIND_EXPR_BODY (*stmt), |
| register_local_var_uses, d); |
| *do_subtree = 0; /* We've done this. */ |
| lvd->nest_depth--; |
| return NULL_TREE; |
| } |
| |
| /* Build, return FUNCTION_DECL node based on ORIG with a type FN_TYPE which has |
| a single argument of type CORO_FRAME_PTR. Build the actor function if |
| ACTOR_P is true, otherwise the destroy. */ |
| |
| static tree |
| coro_build_actor_or_destroy_function (tree orig, tree fn_type, |
| tree coro_frame_ptr, bool actor_p) |
| { |
| location_t loc = DECL_SOURCE_LOCATION (orig); |
| tree fn |
| = 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); |
| |
| DECL_CONTEXT (fn) = DECL_CONTEXT (orig); |
| DECL_SOURCE_LOCATION (fn) = loc; |
| DECL_ARTIFICIAL (fn) = true; |
| DECL_INITIAL (fn) = error_mark_node; |
| |
| tree id = get_identifier ("frame_ptr"); |
| tree fp = build_lang_decl (PARM_DECL, id, coro_frame_ptr); |
| DECL_ARTIFICIAL (fp) = true; |
| DECL_CONTEXT (fp) = fn; |
| DECL_ARG_TYPE (fp) = type_passed_as (coro_frame_ptr); |
| DECL_ARGUMENTS (fn) = fp; |
| |
| /* Copy selected attributes from the original function. */ |
| TREE_USED (fn) = TREE_USED (orig); |
| if (DECL_SECTION_NAME (orig)) |
| set_decl_section_name (fn, orig); |
| /* Copy any alignment that the FE added. */ |
| if (DECL_ALIGN (orig)) |
| SET_DECL_ALIGN (fn, DECL_ALIGN (orig)); |
| /* Copy any alignment the user added. */ |
| DECL_USER_ALIGN (fn) = DECL_USER_ALIGN (orig); |
| /* Apply attributes from the original fn. */ |
| DECL_ATTRIBUTES (fn) = copy_list (DECL_ATTRIBUTES (orig)); |
| /* but we do not want ones for contracts. */ |
| remove_contract_attributes (fn); |
| |
| /* A void return. */ |
| tree resdecl = build_decl (loc, RESULT_DECL, 0, void_type_node); |
| DECL_CONTEXT (resdecl) = fn; |
| DECL_ARTIFICIAL (resdecl) = 1; |
| DECL_IGNORED_P (resdecl) = 1; |
| DECL_RESULT (fn) = resdecl; |
| |
| /* Set up a means to find out if a decl is one of the helpers and, if so, |
| which one. */ |
| if (coroutine_info *info = get_coroutine_info (orig)) |
| { |
| gcc_checking_assert ((actor_p && info->actor_decl == NULL_TREE) |
| || info->destroy_decl == NULL_TREE); |
| if (actor_p) |
| info->actor_decl = fn; |
| else |
| info->destroy_decl = fn; |
| } |
| return fn; |
| } |
| |
| /* Re-write the body as per [dcl.fct.def.coroutine] / 5. */ |
| |
| void |
| cp_coroutine_transform::wrap_original_function_body () |
| { |
| /* Avoid the code here attaching a location that makes the debugger jump. */ |
| iloc_sentinel stable_input_loc (fn_start); |
| location_t loc = fn_start; |
| |
| /* This will be our new outer scope. */ |
| tree update_body |
| = build3_loc (loc, BIND_EXPR, void_type_node, NULL, NULL, NULL); |
| tree top_block = make_node (BLOCK); |
| BIND_EXPR_BLOCK (update_body) = top_block; |
| BIND_EXPR_BODY (update_body) = push_stmt_list (); |
| |
| /* If the function has a top level bind expression, then connect that |
| after first making sure we give it a new block. */ |
| tree first = expr_first (coroutine_body); |
| if (first && TREE_CODE (first) == BIND_EXPR) |
| { |
| tree block = BIND_EXPR_BLOCK (first); |
| gcc_checking_assert (block); |
| gcc_checking_assert (BLOCK_SUPERCONTEXT (block) == NULL_TREE); |
| gcc_checking_assert (BLOCK_CHAIN (block) == NULL_TREE); |
| /* Replace the top block to avoid issues with locations for args |
| appearing to be in a non-existent place. */ |
| tree replace_blk = make_node (BLOCK); |
| BLOCK_VARS (replace_blk) = BLOCK_VARS (block); |
| BLOCK_SUBBLOCKS (replace_blk) = BLOCK_SUBBLOCKS (block); |
| for (tree b = BLOCK_SUBBLOCKS (replace_blk); b; b = BLOCK_CHAIN (b)) |
| BLOCK_SUPERCONTEXT (b) = replace_blk; |
| BIND_EXPR_BLOCK (first) = replace_blk; |
| /* The top block has one child, so far, and we have now got a |
| superblock. */ |
| BLOCK_SUPERCONTEXT (replace_blk) = top_block; |
| BLOCK_SUBBLOCKS (top_block) = replace_blk; |
| } |
| else |
| { |
| /* We are missing a top level BIND_EXPR. We need one to ensure that we |
| don't shuffle around the coroutine frame and corrupt it. */ |
| tree bind_wrap = build3_loc (loc, BIND_EXPR, void_type_node, |
| NULL, NULL, NULL); |
| BIND_EXPR_BODY (bind_wrap) = coroutine_body; |
| /* Ensure we have a block to connect up the scopes. */ |
| tree new_blk = make_node (BLOCK); |
| BIND_EXPR_BLOCK (bind_wrap) = new_blk; |
| BLOCK_SUBBLOCKS (top_block) = new_blk; |
| coroutine_body = bind_wrap; |
| } |
| |
| /* Wrap the function body in a try {} catch (...) {} block, if exceptions |
| are enabled. */ |
| tree var_list = NULL_TREE; |
| |
| /* [stmt.return.coroutine] / 3 |
| If p.return_void() is a valid expression, flowing off the end of a |
| coroutine is equivalent to a co_return with no operand; otherwise |
| flowing off the end of a coroutine results in undefined behavior. */ |
| tree return_void |
| = get_coroutine_return_void_expr (orig_fn_decl, loc, false); |
| |
| /* The pointer to the resume function. */ |
| tree resume_fn_ptr |
| = coro_build_artificial_var (loc, coro_resume_fn_id, |
| act_des_fn_ptr_type, orig_fn_decl, NULL_TREE); |
| DECL_CHAIN (resume_fn_ptr) = var_list; |
| var_list = resume_fn_ptr; |
| add_decl_expr (resume_fn_ptr); |
| |
| /* We will need to be able to set the resume function pointer to nullptr |
| to signal that the coroutine is 'done'. */ |
| tree zero_resume |
| = build1 (CONVERT_EXPR, act_des_fn_ptr_type, nullptr_node); |
| |
| /* The pointer to the destroy function. */ |
| tree var |
| = coro_build_artificial_var (loc, coro_destroy_fn_id, |
| act_des_fn_ptr_type, orig_fn_decl, NULL_TREE); |
| DECL_CHAIN (var) = var_list; |
| var_list = var; |
| add_decl_expr (var); |
| |
| /* The promise was created on demand when parsing we now link it into |
| our scope. */ |
| tree promise = get_coroutine_promise_proxy (orig_fn_decl); |
| DECL_CONTEXT (promise) = orig_fn_decl; |
| DECL_SOURCE_LOCATION (promise) = loc; |
| DECL_CHAIN (promise) = var_list; |
| var_list = promise; |
| add_decl_expr (promise); |
| |
| /* If we have function parms, then these will be copied to the coroutine |
| frame as per [dcl.fct.def.coroutine] / 13. |
| Here, we create a local (proxy) variable for each parm, since the original |
| parms will be out of scope once the ramp has finished. The proxy vars will |
| get DECL_VALUE_EXPRs pointing to the frame copies, so that we can interact |
| with them in the debugger. */ |
| if (DECL_ARGUMENTS (orig_fn_decl)) |
| { |
| /* Add a local var for each parm. */ |
| for (tree arg = DECL_ARGUMENTS (orig_fn_decl); arg != NULL; |
| arg = DECL_CHAIN (arg)) |
| { |
| param_info *parm_i = param_uses.get (arg); |
| gcc_checking_assert (parm_i); |
| parm_i->copy_var |
| = build_lang_decl (VAR_DECL, parm_i->field_id, TREE_TYPE (arg)); |
| DECL_SOURCE_LOCATION (parm_i->copy_var) = DECL_SOURCE_LOCATION (arg); |
| DECL_CONTEXT (parm_i->copy_var) = orig_fn_decl; |
| DECL_ARTIFICIAL (parm_i->copy_var) = true; |
| DECL_CHAIN (parm_i->copy_var) = var_list; |
| var_list = parm_i->copy_var; |
| add_decl_expr (parm_i->copy_var); |
| } |
| |
| /* Now replace all uses of the parms in the function body with the proxy |
| vars. We want to this to apply to every instance of param's use, so |
| don't include a 'visited' hash_set on the tree walk, however we will |
| arrange to visit each containing expression only once. */ |
| hash_set<tree *> visited; |
| param_frame_data param_data = {NULL, ¶m_uses, |
| &visited, loc, false}; |
| cp_walk_tree (&coroutine_body, rewrite_param_uses, ¶m_data, NULL); |
| } |
| |
| /* We create a resume index, this is initialized in the ramp. */ |
| resume_idx_var |
| = coro_build_artificial_var (loc, coro_resume_index_id, |
| short_unsigned_type_node, orig_fn_decl, |
| NULL_TREE); |
| DECL_CHAIN (resume_idx_var) = var_list; |
| var_list = resume_idx_var; |
| add_decl_expr (resume_idx_var); |
| |
| tree coro_frame_refcount |
| = coro_build_artificial_var (loc, coro_frame_refcount_id, |
| short_unsigned_type_node, orig_fn_decl, |
| NULL_TREE); |
| DECL_CHAIN (coro_frame_refcount) = var_list; |
| var_list = coro_frame_refcount; |
| add_decl_expr (coro_frame_refcount); |
| |
| /* If the coroutine has a frame that needs to be freed, this will be set by |
| the ramp. */ |
| var = coro_build_artificial_var (loc, coro_frame_needs_free_id, |
| boolean_type_node, orig_fn_decl, NULL_TREE); |
| DECL_CHAIN (var) = var_list; |
| var_list = var; |
| add_decl_expr (var); |
| |
| /* We consider that the body has a use of the frame once we start to process |
| the initial suspend expression. (the use might be relinquished if we |
| encounter an exception before the body is finished). */ |
| tree body_use |
| = build2_loc (loc, PLUS_EXPR, short_unsigned_type_node, coro_frame_refcount, |
| build_int_cst (short_unsigned_type_node, 1)); |
| body_use = cp_build_modify_expr (loc, coro_frame_refcount, NOP_EXPR, body_use, |
| tf_warning_or_error); |
| finish_expr_stmt (body_use); |
| if (flag_exceptions) |
| { |
| /* Build promise.unhandled_exception(); */ |
| tree ueh |
| = coro_build_promise_expression (orig_fn_decl, promise, |
| coro_unhandled_exception_identifier, |
| loc, NULL, /*musthave=*/true); |
| /* Create and initialize the initial-await-resume-called variable per |
| [dcl.fct.def.coroutine] / 5.3. */ |
| tree i_a_r_c |
| = coro_build_artificial_var (loc, coro_frame_i_a_r_c_id, |
| boolean_type_node, orig_fn_decl, |
| NULL_TREE); |
| DECL_CHAIN (i_a_r_c) = var_list; |
| var_list = i_a_r_c; |
| add_decl_expr (i_a_r_c); |
| /* Start the try-catch. */ |
| tree tcb = build_stmt (loc, TRY_BLOCK, NULL_TREE, NULL_TREE); |
| add_stmt (tcb); |
| TRY_STMTS (tcb) = push_stmt_list (); |
| /* We need a new scope to handle the cleanup for the ramp use that is |
| needed for exceptions. */ |
| tree except_scope = begin_compound_stmt (0); |
| current_binding_level->artificial = 1; |
| tree release |
| = build2_loc (loc, MINUS_EXPR, short_unsigned_type_node, |
| coro_frame_refcount, build_int_cst (short_unsigned_type_node, 1)); |
| release = cp_build_modify_expr (loc, coro_frame_refcount, NOP_EXPR, |
| release, tf_warning_or_error); |
| /* Once we pass the initial await resume, the cleanup rules on exception |
| change so that the responsibility lies with the caller. */ |
| release = build3 (COND_EXPR, void_type_node, i_a_r_c, |
| build_empty_stmt (loc), release); |
| push_cleanup (NULL_TREE, release, /*ehonly*/true); |
| /* Add the initial await to the start of the user-authored function. */ |
| finish_expr_stmt (initial_await); |
| /* End the scope that handles the remove of frame-use on exception. */ |
| finish_compound_stmt (except_scope); |
| |
| /* Append the original function body. */ |
| add_stmt (coroutine_body); |
| |
| if (return_void) |
| add_stmt (return_void); |
| TRY_STMTS (tcb) = pop_stmt_list (TRY_STMTS (tcb)); |
| TRY_HANDLERS (tcb) = push_stmt_list (); |
| /* Mimic what the parser does for the catch. */ |
| tree handler = begin_handler (); |
| finish_handler_parms (NULL_TREE, handler); /* catch (...) */ |
| |
| /* Get the initial await resume called value. */ |
| tree not_iarc_if = begin_if_stmt (); |
| tree not_iarc = build1_loc (loc, TRUTH_NOT_EXPR, |
| boolean_type_node, i_a_r_c); |
| finish_if_stmt_cond (not_iarc, not_iarc_if); |
| /* If the initial await resume called value is false, rethrow... */ |
| tree rethrow = build_throw (loc, NULL_TREE, tf_warning_or_error); |
| suppress_warning (rethrow); |
| finish_expr_stmt (rethrow); |
| finish_then_clause (not_iarc_if); |
| finish_if_stmt (not_iarc_if); |
| /* ... else call the promise unhandled exception method |
| but first we set done = true and the resume index to 0. |
| If the unhandled exception method returns, then we continue |
| to the final await expression (which duplicates the clearing of |
| the field). */ |
| tree r = build2_loc (loc, MODIFY_EXPR, act_des_fn_ptr_type, resume_fn_ptr, |
| zero_resume); |
| finish_expr_stmt (r); |
| tree short_zero = build_int_cst (short_unsigned_type_node, 0); |
| r = build2 (MODIFY_EXPR, short_unsigned_type_node, resume_idx_var, |
| short_zero); |
| finish_expr_stmt (r); |
| finish_expr_stmt (ueh); |
| finish_handler (handler); |
| TRY_HANDLERS (tcb) = pop_stmt_list (TRY_HANDLERS (tcb)); |
| } |
| else |
| { |
| if (pedantic) |
| { |
| /* We still try to look for the promise method and warn if it's not |
| present. */ |
| tree ueh_meth |
| = lookup_promise_method (orig_fn_decl, |
| coro_unhandled_exception_identifier, |
| loc, /*musthave=*/false); |
| if (!ueh_meth || ueh_meth == error_mark_node) |
| warning_at (loc, 0, "no member named %qE in %qT", |
| coro_unhandled_exception_identifier, |
| get_coroutine_promise_type (orig_fn_decl)); |
| } |
| /* Else we don't check and don't care if the method is missing.. |
| just add the initial suspend, function and return. */ |
| finish_expr_stmt (initial_await); |
| /* Append the original function body. */ |
| add_stmt (coroutine_body); |
| if (return_void) |
| add_stmt (return_void); |
| } |
| |
| /* We are now doing actions associated with the end of the function, so |
| point to the closing brace. */ |
| input_location = loc = fn_end; |
| |
| /* co_return branches to the final_suspend label, so declare that now. */ |
| fs_label |
| = create_named_label_with_ctx (loc, "final.suspend", NULL_TREE); |
| add_stmt (build_stmt (loc, LABEL_EXPR, fs_label)); |
| |
| /* Before entering the final suspend point, we signal that this point has |
| been reached by setting the resume function pointer to zero (this is |
| what the 'done()' builtin tests) as per the current ABI. */ |
| zero_resume = build2_loc (loc, MODIFY_EXPR, act_des_fn_ptr_type, |
| resume_fn_ptr, zero_resume); |
| finish_expr_stmt (zero_resume); |
| finish_expr_stmt (final_await); |
| |
| BIND_EXPR_BODY (update_body) = pop_stmt_list (BIND_EXPR_BODY (update_body)); |
| BIND_EXPR_VARS (update_body) = nreverse (var_list); |
| BLOCK_VARS (top_block) = BIND_EXPR_VARS (update_body); |
| |
| coroutine_body = update_body; |
| } |
| |
| /* Extract the body of the function we are going to outline, leaving |
| to original function decl ready to build the ramp. */ |
| |
| static tree |
| split_coroutine_body_from_ramp (tree fndecl) |
| { |
| /* Sanity-check and punt if we have a nonsense tree because of earlier |
| parse errors, perhaps. */ |
| if (!current_binding_level |
| || current_binding_level->kind != sk_function_parms) |
| return NULL_TREE; |
| |
| /* Once we've tied off the original user-authored body in fn_body. |
| Start the replacement synthesized ramp body. */ |
| |
| tree body; |
| if (use_eh_spec_block (fndecl)) |
| { |
| body = pop_stmt_list (TREE_OPERAND (current_eh_spec_block, 0)); |
| TREE_OPERAND (current_eh_spec_block, 0) = push_stmt_list (); |
| } |
| else |
| { |
| body = pop_stmt_list (DECL_SAVED_TREE (fndecl)); |
| DECL_SAVED_TREE (fndecl) = push_stmt_list (); |
| } |
| |
| /* We can't validly get here with an empty statement list, since there's no |
| way for the FE to decide it's a coroutine in the absence of any code. */ |
| gcc_checking_assert (body != NULL_TREE); |
| |
| /* If we have an empty or erroneous function body, do not try to transform it |
| since that would potentially wrap errors. */ |
| tree body_start = expr_first (body); |
| if (body_start == NULL_TREE || body_start == error_mark_node) |
| { |
| /* Restore the original state. */ |
| add_stmt (body); |
| return NULL_TREE; |
| } |
| return body; |
| } |
| |
| /* Build the expression to allocate the coroutine frame according to the |
| rules of [dcl.fct.def.coroutine] / 9. */ |
| |
| static tree |
| build_coroutine_frame_alloc_expr (tree promise_type, tree orig_fn_decl, |
| location_t fn_start, tree grooaf, |
| hash_map<tree, param_info> *param_uses, |
| tree frame_size) |
| { |
| /* Allocate the frame, this has several possibilities: |
| [dcl.fct.def.coroutine] / 9 (part 1) |
| The allocation function’s name is looked up in the scope of the promise |
| type. It is not a failure for it to be absent see part 4, below. */ |
| |
| tree nwname = ovl_op_identifier (false, NEW_EXPR); |
| tree new_fn_call = NULL_TREE; |
| tree dummy_promise |
| = build_dummy_object (get_coroutine_promise_type (orig_fn_decl)); |
| |
| if (TYPE_HAS_NEW_OPERATOR (promise_type)) |
| { |
| tree fns = lookup_promise_method (orig_fn_decl, nwname, fn_start, |
| /*musthave=*/true); |
| /* [dcl.fct.def.coroutine] / 9 (part 2) |
| If the lookup finds an allocation function in the scope of the promise |
| type, overload resolution is performed on a function call created by |
| assembling an argument list. The first argument is the amount of space |
| requested, and has type std::size_t. The lvalues p1...pn are the |
| succeeding arguments.. */ |
| vec<tree, va_gc> *args = make_tree_vector (); |
| vec_safe_push (args, frame_size); /* Space needed. */ |
| |
| for (tree arg = DECL_ARGUMENTS (orig_fn_decl); arg != NULL; |
| arg = DECL_CHAIN (arg)) |
| { |
| param_info *parm_i = param_uses->get (arg); |
| gcc_checking_assert (parm_i); |
| if (parm_i->this_ptr || parm_i->lambda_cobj) |
| { |
| /* We pass a reference to *this to the allocator lookup. */ |
| /* It's unsafe to use the cp_ version here since current_class_ref |
| might've gotten clobbered earlier during rewrite_param_uses. */ |
| tree this_ref = build_fold_indirect_ref (arg); |
| vec_safe_push (args, this_ref); |
| } |
| else |
| vec_safe_push (args, convert_from_reference (arg)); |
| } |
| |
| /* Note the function selected; we test to see if it's NOTHROW. */ |
| tree func; |
| /* Failure is not an error for this attempt. */ |
| new_fn_call = build_new_method_call (dummy_promise, fns, &args, NULL, |
| LOOKUP_NORMAL, &func, tf_none); |
| release_tree_vector (args); |
| |
| if (new_fn_call == error_mark_node) |
| { |
| /* [dcl.fct.def.coroutine] / 9 (part 3) |
| If no viable function is found, overload resolution is performed |
| again on a function call created by passing just the amount of |
| space required as an argument of type std::size_t. */ |
| args = make_tree_vector_single (frame_size); /* Space needed. */ |
| new_fn_call = build_new_method_call (dummy_promise, fns, &args, |
| NULL_TREE, LOOKUP_NORMAL, &func, |
| tf_none); |
| release_tree_vector (args); |
| } |
| |
| /* However, if the promise provides an operator new, then one of these |
| two options must be available. */ |
| if (new_fn_call == error_mark_node) |
| { |
| error_at (fn_start, "%qE is provided by %qT but is not usable with" |
| " the function signature %qD", nwname, promise_type, |
| orig_fn_decl); |
| return error_mark_node; |
| } |
| else if (grooaf && !TYPE_NOTHROW_P (TREE_TYPE (func))) |
| { |
| error_at (fn_start, "%qE is provided by %qT but %qE is not marked" |
| " %<throw()%> or %<noexcept%>", grooaf, promise_type, nwname); |
| return error_mark_node; |
| } |
| else if (!grooaf && TYPE_NOTHROW_P (TREE_TYPE (func))) |
| warning_at (fn_start, 0, "%qE is marked %<throw()%> or %<noexcept%> but" |
| " no usable %<get_return_object_on_allocation_failure%>" |
| " is provided by %qT", nwname, promise_type); |
| } |
| else /* No operator new in the promise. */ |
| { |
| /* [dcl.fct.def.coroutine] / 9 (part 4) |
| If this lookup fails, the allocation function’s name is looked up in |
| the global scope. */ |
| |
| vec<tree, va_gc> *args; |
| /* build_operator_new_call () will insert size needed as element 0 of |
| this, and we might need to append the std::nothrow constant. */ |
| vec_alloc (args, 2); |
| if (grooaf) |
| { |
| /* [dcl.fct.def.coroutine] / 10 (part 2) |
| If any declarations (of the get return on allocation fail) are |
| found, then the result of a call to an allocation function used |
| to obtain storage for the coroutine state is assumed to return |
| nullptr if it fails to obtain storage and, if a global allocation |
| function is selected, the ::operator new(size_t, nothrow_t) form |
| is used. The allocation function used in this case shall have a |
| non-throwing noexcept-specification. So we need std::nothrow. */ |
| tree std_nt = lookup_qualified_name (std_node, |
| get_identifier ("nothrow"), |
| LOOK_want::NORMAL, |
| /*complain=*/true); |
| if (!std_nt || std_nt == error_mark_node) |
| { |
| /* Something is seriously wrong, punt. */ |
| error_at (fn_start, "%qE is provided by %qT but %<std::nothrow%>" |
| " cannot be found", grooaf, promise_type); |
| return error_mark_node; |
| } |
| vec_safe_push (args, std_nt); |
| } |
| |
| /* If we get to this point, we must succeed in looking up the global |
| operator new for the params provided. Since we are not setting |
| size_check or cookie, we expect frame_size to be unaltered. */ |
| tree cookie = NULL; |
| new_fn_call = build_operator_new_call (nwname, &args, &frame_size, |
| &cookie, /*align_arg=*/NULL, |
| /*size_check=*/NULL, /*fn=*/NULL, |
| tf_warning_or_error); |
| release_tree_vector (args); |
| } |
| return new_fn_call; |
| } |
| |
| /* Build an expression to delete the coroutine state frame. */ |
| |
| static tree |
| build_coroutine_frame_delete_expr (tree coro_fp, tree frame_size, |
| tree promise_type, location_t loc) |
| { |
| /* Cast the frame pointer to a pointer to promise so that the build op |
| delete call will search the promise. */ |
| tree pptr_type = build_pointer_type (promise_type); |
| tree frame_arg = build1_loc (loc, CONVERT_EXPR, pptr_type, coro_fp); |
| /* [dcl.fct.def.coroutine] / 12 sentence 3: |
| If both a usual deallocation function with only a pointer parameter and |
| a usual deallocation function with both a pointer parameter and a size |
| parameter are found, then the selected deallocation function shall be the |
| one with two parameters. */ |
| tree del_coro_fr |
| = build_coroutine_op_delete_call (DELETE_EXPR, frame_arg, frame_size, |
| /*global_p=*/false, /*placement=*/NULL, |
| /*alloc_fn=*/NULL, tf_warning_or_error); |
| if (!del_coro_fr || del_coro_fr == error_mark_node) |
| return error_mark_node; |
| return del_coro_fr; |
| } |
| |
| /* Build the ramp function. |
| Here we take the original function definition which has now had its body |
| removed, and use it as the declaration of the ramp which both replaces the |
| user's written function at call sites, and is responsible for starting |
| the coroutine it defined. |
| returns false on error. |
| |
| We should arrive here with the state of the compiler as if we had just |
| executed start_preparsed_function(). */ |
| |
| bool |
| cp_coroutine_transform::build_ramp_function () |
| { |
| gcc_checking_assert (current_binding_level |
| && current_binding_level->kind == sk_function_parms); |
| |
| /* This is completely synthetic code, if we find an issue then we have not |
| much chance to point at the most useful place in the user's code. In |
| lieu of this use the function start - so at least the diagnostic relates |
| to something that the user can inspect. */ |
| iloc_sentinel saved_position (fn_start); |
| location_t loc = fn_start; |
| |
| tree promise_type = get_coroutine_promise_type (orig_fn_decl); |
| tree fn_return_type = TREE_TYPE (TREE_TYPE (orig_fn_decl)); |
| bool void_ramp_p = VOID_TYPE_P (fn_return_type); |
| /* We know there was no return statement, that is intentional. */ |
| suppress_warning (orig_fn_decl, OPT_Wreturn_type); |
| |
| /* [dcl.fct.def.coroutine] / 10 (part1) |
| The unqualified-id get_return_object_on_allocation_failure is looked up |
| in the scope of the promise type by class member access lookup. */ |
| |
| /* We don't require this, but, if the lookup succeeds, then the function |
| must be usable, punt if it is not. */ |
| tree grooaf_meth |
| = lookup_promise_method (orig_fn_decl, |
| coro_gro_on_allocation_fail_identifier, loc, |
| /*musthave*/ false); |
| tree grooaf = NULL_TREE; |
| tree dummy_promise |
| = build_dummy_object (get_coroutine_promise_type (orig_fn_decl)); |
| if (grooaf_meth && grooaf_meth != error_mark_node) |
| { |
| grooaf |
| = coro_build_promise_expression (orig_fn_decl, dummy_promise, |
| coro_gro_on_allocation_fail_identifier, |
| fn_start, NULL, /*musthave=*/false); |
| |
| /* That should succeed. */ |
| if (!grooaf || grooaf == error_mark_node) |
| { |
| error_at (fn_start, "%qE is provided by %qT but is not usable with" |
| " the function %qD", coro_gro_on_allocation_fail_identifier, |
| promise_type, orig_fn_decl); |
| return false; |
| } |
| } |
| |
| /* Check early for usable allocator/deallocator, without which we cannot |
| build a useful ramp; early exit if they are not available or usable. */ |
| |
| frame_size = TYPE_SIZE_UNIT (frame_type); |
| |
| /* Make a var to represent the frame pointer early. */ |
| tree coro_fp = coro_build_artificial_var (loc, "_Coro_frameptr", |
| frame_ptr_type, orig_fn_decl, |
| NULL_TREE); |
| |
| tree new_fn_call |
| = build_coroutine_frame_alloc_expr (promise_type, orig_fn_decl, fn_start, |
| grooaf, ¶m_uses, frame_size); |
| |
| /* We must have a useable allocator to proceed. */ |
| if (!new_fn_call || new_fn_call == error_mark_node) |
| return false; |
| |
| /* Likewise, we need the DTOR to delete the frame. */ |
| tree delete_frame_call |
| = build_coroutine_frame_delete_expr (coro_fp, frame_size, promise_type, |
| fn_start); |
| if (!delete_frame_call || delete_frame_call == error_mark_node) |
| return false; |
| |
| /* At least verify we can lookup the get return object method. */ |
| tree get_ro_meth |
| = lookup_promise_method (orig_fn_decl, |
| coro_get_return_object_identifier, loc, |
| /*musthave*/ true); |
| if (!get_ro_meth || get_ro_meth == error_mark_node) |
| return false; |
| |
| /* So now construct the Ramp: */ |
| |
| tree ramp_fnbody = begin_compound_stmt (BCS_FN_BODY); |
| coro_fp = pushdecl (coro_fp); |
| add_decl_expr (coro_fp); |
| |
| /* Build the frame. */ |
| |
| /* The CO_FRAME internal function is a mechanism to allow the middle end |
| to adjust the allocation in response to optimizations. We provide the |
| current conservative estimate of the frame size (as per the current) |
| computed layout. */ |
| |
| tree resizeable |
| = build_call_expr_internal_loc (loc, IFN_CO_FRAME, size_type_node, 2, |
| frame_size, |
| build_zero_cst (frame_ptr_type)); |
| CALL_EXPR_ARG (new_fn_call, 0) = resizeable; |
| tree allocated = build1 (CONVERT_EXPR, frame_ptr_type, new_fn_call); |
| tree r = cp_build_init_expr (coro_fp, allocated); |
| finish_expr_stmt (r); |
| |
| /* If the user provided a method to return an object on alloc fail, then |
| check the returned pointer and call the func if it's null. |
| Otherwise, no check, and we fail for noexcept/fno-exceptions cases. */ |
| |
| tree grooaf_if_stmt = NULL_TREE; |
| tree alloc_ok_scope = NULL_TREE; |
| if (grooaf) |
| { |
| /* [dcl.fct.def.coroutine] / 10 (part 3) |
| If the allocation function returns nullptr,the coroutine returns |
| control to the caller of the coroutine and the return value is |
| obtained by a call to T::get_return_object_on_allocation_failure(), |
| where T is the promise type. */ |
| tree cond = build1 (CONVERT_EXPR, frame_ptr_type, nullptr_node); |
| cond = build2 (NE_EXPR, boolean_type_node, coro_fp, cond); |
| grooaf_if_stmt = begin_if_stmt (); |
| finish_if_stmt_cond (cond, grooaf_if_stmt); |
| alloc_ok_scope = begin_compound_stmt (BCS_NORMAL); |
| } |
| |
| /* Dereference the frame pointer, to use in member access code. */ |
| tree deref_fp |
| = cp_build_indirect_ref (loc, coro_fp, RO_UNARY_STAR, tf_warning_or_error); |
| |
| /* For now, once allocation has succeeded we always assume that this needs |
| destruction, there's no impl. for frame allocation elision. */ |
| tree frame_needs_free |
| = coro_build_and_push_artificial_var_with_dve (loc, |
| coro_frame_needs_free_id, |
| boolean_type_node, |
| orig_fn_decl, |
| boolean_true_node, |
| deref_fp); |
| /* Although it appears to be unused here the frame entry is needed and we |
| just set it true. */ |
| TREE_USED (frame_needs_free) = true; |
| |
| tree coro_frame_refcount |
| = coro_build_and_push_artificial_var_with_dve (loc, coro_frame_refcount_id, |
| short_unsigned_type_node, |
| orig_fn_decl, NULL_TREE, |
| deref_fp); |
| /* Cleanup if both the ramp and the body have finished. */ |
| tree cond |
| = build2_loc (loc, EQ_EXPR, short_unsigned_type_node, coro_frame_refcount, |
| build_int_cst (short_unsigned_type_node, 0)); |
| r = build3 (COND_EXPR, void_type_node, cond, delete_frame_call, |
| build_empty_stmt (loc)); |
| push_cleanup (coro_fp, r, /*eh_only*/false); |
| |
| /* Put the resumer and destroyer functions in. */ |
| |
| tree actor_addr = build1 (ADDR_EXPR, act_des_fn_ptr_type, resumer); |
| coro_build_and_push_artificial_var_with_dve (loc, coro_resume_fn_id, |
| act_des_fn_ptr_type, |
| orig_fn_decl, |
| actor_addr, deref_fp); |
| |
| tree destroy_addr = build1 (ADDR_EXPR, act_des_fn_ptr_type, destroyer); |
| coro_build_and_push_artificial_var_with_dve (loc, coro_destroy_fn_id, |
| act_des_fn_ptr_type, |
| orig_fn_decl, |
| destroy_addr, deref_fp); |
| |
| /* [dcl.fct.def.coroutine] /13 |
| When a coroutine is invoked, a copy is created for each coroutine |
| parameter. Each such copy is an object with automatic storage duration |
| that is direct-initialized from an lvalue referring to the corresponding |
| parameter if the parameter is an lvalue reference, and from an xvalue |
| referring to it otherwise. A reference to a parameter in the function- |
| body of the coroutine and in the call to the coroutine promise |
| constructor is replaced by a reference to its copy. */ |
| |
| vec<tree, va_gc> *promise_args = NULL; /* So that we can adjust refs. */ |
| |
| /* The initialization and destruction of each parameter copy occurs in the |
| context of the called coroutine. Initializations of parameter copies are |
| sequenced before the call to the coroutine promise constructor and |
| indeterminately sequenced with respect to each other. The lifetime of |
| parameter copies ends immediately after the lifetime of the coroutine |
| promise object ends. */ |
| |
| if (DECL_ARGUMENTS (orig_fn_decl)) |
| { |
| promise_args = make_tree_vector (); |
| for (tree arg = DECL_ARGUMENTS (orig_fn_decl); arg != NULL; |
| arg = DECL_CHAIN (arg)) |
| { |
| bool existed; |
| param_info &parm = param_uses.get_or_insert (arg, &existed); |
| tree fld_idx |
| = coro_build_frame_access_expr (deref_fp, parm.field_id, |
| false, tf_warning_or_error); |
| |
| /* Add this to the promise CTOR arguments list, accounting for |
| refs and special handling for method this ptr. */ |
| if (parm.this_ptr || parm.lambda_cobj) |
| { |
| /* We pass a reference to *this to the param preview. */ |
| /* It's unsafe to use the cp_ version here since current_class_ref |
| might've gotten clobbered earlier during rewrite_param_uses. */ |
| tree this_ref = build_fold_indirect_ref (arg); |
| vec_safe_push (promise_args, this_ref); |
| } |
| else if (parm.rv_ref) |
| vec_safe_push (promise_args, move (fld_idx)); |
| else |
| vec_safe_push (promise_args, fld_idx); |
| |
| if (parm.rv_ref || parm.pt_ref) |
| /* Initialise the frame reference field directly. */ |
| r = build2 (INIT_EXPR, TREE_TYPE (arg), |
| TREE_OPERAND (fld_idx, 0), arg); |
| else |
| { |
| r = forward_parm (arg); |
| r = cp_build_modify_expr (loc, fld_idx, INIT_EXPR, r, |
| tf_warning_or_error); |
| } |
| finish_expr_stmt (r); |
| |
| /* Arrange for parm copies to be cleaned up when an exception is |
| thrown before initial await resume. */ |
| if (!parm.trivial_dtor) |
| { |
| parm.fr_copy_dtor |
| = cxx_maybe_build_cleanup (fld_idx, tf_warning_or_error); |
| if (parm.fr_copy_dtor && parm.fr_copy_dtor != error_mark_node) |
| { |
| param_dtor_list.safe_push (parm.field_id); |
| cond |
| = build2_loc (loc, EQ_EXPR, short_unsigned_type_node, |
| coro_frame_refcount, |
| build_int_cst (short_unsigned_type_node, 0)); |
| r = build3_loc (loc, COND_EXPR, void_type_node, cond, |
| parm.fr_copy_dtor, build_empty_stmt (loc)); |
| push_cleanup (fld_idx, r, /*eh_only*/false); |
| } |
| } |
| } |
| } |
| |
| /* Set up the promise. */ |
| tree p |
| = coro_build_and_push_artificial_var_with_dve (loc, coro_promise_id, |
| promise_type, orig_fn_decl, |
| NULL_TREE, deref_fp); |
| |
| if (type_build_ctor_call (promise_type)) |
| { |
| /* Construct the promise object [dcl.fct.def.coroutine] / 5.7. |
| |
| First try to find a constructor with an argument list comprised of |
| the parameter copies. */ |
| |
| if (DECL_ARGUMENTS (orig_fn_decl)) |
| { |
| r = build_special_member_call (p, complete_ctor_identifier, |
| &promise_args, promise_type, |
| LOOKUP_NORMAL, tf_none); |
| release_tree_vector (promise_args); |
| } |
| else |
| r = NULL_TREE; |
| |
| /* If that fails then the promise constructor argument list is empty. */ |
| if (r == NULL_TREE || r == error_mark_node) |
| r = build_special_member_call (p, complete_ctor_identifier, NULL, |
| promise_type, LOOKUP_NORMAL, |
| tf_warning_or_error); |
| |
| /* If type_build_ctor_call() encounters deprecated implicit CTORs it will |
| return true, and therefore we will execute this code path. However, |
| we might well not actually require a CTOR and under those conditions |
| the build call above will not return a call expression, but the |
| original instance object. Do not attempt to add the statement unless |
| it has side-effects. */ |
| if (r && r != error_mark_node && TREE_SIDE_EFFECTS (r)) |
| finish_expr_stmt (r); |
| } |
| |
| tree promise_dtor = cxx_maybe_build_cleanup (p, tf_warning_or_error); |
| /* If the promise is live, then run its dtor if that's available. */ |
| if (promise_dtor && promise_dtor != error_mark_node) |
| { |
| cond = build2_loc (loc, EQ_EXPR, short_unsigned_type_node, |
| coro_frame_refcount, |
| build_int_cst (short_unsigned_type_node, 0)); |
| r = build3 (COND_EXPR, void_type_node, cond, promise_dtor, |
| build_empty_stmt (loc)); |
| push_cleanup (p, r, /*eh_only*/false); |
| } |
| |
| tree get_ro |
| = coro_build_promise_expression (orig_fn_decl, p, |
| coro_get_return_object_identifier, |
| fn_start, NULL, /*musthave=*/true); |
| |
| /* Without a return object we haven't got much clue what's going on. */ |
| if (!get_ro || get_ro == error_mark_node) |
| return false; |
| |
| /* Check for a bad get return object type. |
| [dcl.fct.def.coroutine] / 7 requires: |
| The expression promise.get_return_object() is used to initialize the |
| returned reference or prvalue result object ... |
| When we use a local to hold this, it is decltype(auto). */ |
| tree gro_type |
| = finish_decltype_type (get_ro, /*id_expression_or_member_access_p*/false, |
| tf_warning_or_error); |
| if (VOID_TYPE_P (gro_type) && !void_ramp_p) |
| { |
| error_at (fn_start, "no viable conversion from %<void%> provided by" |
| " %<get_return_object%> to return type %qT", fn_return_type); |
| return false; |
| } |
| |
| /* Initialize the resume_idx_var to 0, meaning "not started". */ |
| coro_build_and_push_artificial_var_with_dve |
| (loc, coro_resume_index_id, short_unsigned_type_node, orig_fn_decl, |
| build_zero_cst (short_unsigned_type_node), deref_fp); |
| |
| /* We must manage the cleanups ourselves, with the exception of the g_r_o, |
| because the responsibility for them changes after the initial suspend. |
| However, any use of cxx_maybe_build_cleanup () in preceding code can |
| set the throwing_cleanup flag. */ |
| cp_function_chain->throwing_cleanup = false; |
| |
| /* [dcl.fct.def.coroutine] / 7 |
| The expression promise.get_return_object() is used to initialize the |
| glvalue result or prvalue result object of a call to a coroutine. */ |
| |
| tree coro_gro = NULL_TREE; |
| if (void_ramp_p) |
| /* We still want to call the method, even if the result is unused. */ |
| finish_expr_stmt (get_ro); |
| else |
| { |
| /* Per CWG2563, we keep the result of promise.get_return_object () in |
| a temp which is then used to intialize the return object, including |
| NVRO. */ |
| |
| coro_gro |
| = coro_build_and_push_artificial_var (loc, "_Coro_gro", gro_type, |
| orig_fn_decl, NULL_TREE); |
| |
| r = cp_build_init_expr (coro_gro, STRIP_REFERENCE_REF (get_ro)); |
| finish_expr_stmt (r); |
| tree coro_gro_cleanup |
| = cxx_maybe_build_cleanup (coro_gro, tf_warning_or_error); |
| if (coro_gro_cleanup) |
| push_cleanup (coro_gro, coro_gro_cleanup, /*eh_only*/false); |
| } |
| |
| /* Start the coroutine body, we now have a use of the frame... */ |
| r = cp_build_modify_expr (loc, coro_frame_refcount, NOP_EXPR, |
| build_int_cst (short_unsigned_type_node, 1), |
| tf_warning_or_error); |
| finish_expr_stmt (r); |
| /* ... but when we finish we want to release that, and we want to do that |
| before any of the other cleanups run. */ |
| tree released |
| = build2_loc (loc, MINUS_EXPR, short_unsigned_type_node, coro_frame_refcount, |
| build_int_cst (short_unsigned_type_node, 1)); |
| released = cp_build_modify_expr (loc, coro_frame_refcount, NOP_EXPR, released, |
| tf_warning_or_error); |
| push_cleanup (NULL_TREE, released, /*eh_only*/false); |
| |
| r = build_call_expr_loc (fn_start, resumer, 1, coro_fp); |
| finish_expr_stmt (r); |
| |
| /* The ramp is done, we just need the return statement, which we build from |
| the return object we constructed before we called the actor. */ |
| |
| /* This is our 'normal' exit. */ |
| r = void_ramp_p ? NULL_TREE : convert_from_reference (coro_gro); |
| finish_return_stmt (r); |
| |
| if (grooaf) |
| { |
| finish_compound_stmt (alloc_ok_scope); |
| finish_then_clause (grooaf_if_stmt); |
| |
| begin_else_clause (grooaf_if_stmt); |
| /* We come here if the frame allocation failed. */ |
| r = NULL_TREE; |
| if (void_ramp_p) |
| /* Execute the get-return-object-on-alloc-fail call... */ |
| finish_expr_stmt (grooaf); |
| else |
| /* Get the fallback return object. */ |
| r = grooaf; |
| finish_return_stmt (r); |
| finish_if_stmt (grooaf_if_stmt); |
| } |
| |
| finish_compound_stmt (ramp_fnbody); |
| return true; |
| } |
| |
| /* ------- Encapsulate analysis of the couroutine -------- */ |
| |
| |
| cp_coroutine_transform::cp_coroutine_transform (tree _orig_fn, bool _inl) |
| : orig_fn_decl (_orig_fn), inline_p (_inl) |
| { |
| /* We don't expect to be called with missing decl or e_m_n. */ |
| gcc_checking_assert (orig_fn_decl |
| && TREE_CODE (orig_fn_decl) == FUNCTION_DECL); |
| if (!coro_function_valid_p (orig_fn_decl)) |
| { |
| /* For early errors, we do not want a diagnostic about the missing |
| ramp return value, since the user cannot fix this - a 'return' is |
| not allowed in a coroutine. */ |
| suppress_warning (orig_fn_decl, OPT_Wreturn_type); |
| /* Discard the body, we can't process it further... */ |
| pop_stmt_list (DECL_SAVED_TREE (orig_fn_decl)); |
| /* ... and make an empty fn. */ |
| DECL_SAVED_TREE (orig_fn_decl) = push_stmt_list (); |
| /* Match the expected nesting when an eh block is in use. */ |
| if (use_eh_spec_block (orig_fn_decl)) |
| current_eh_spec_block = begin_eh_spec_block (); |
| valid_coroutine = false; |
| } |
| |
| /* We don't have the locus of the opening brace - it's filled in later (and |
| there doesn't really seem to be any easy way to get at it). */ |
| fn_start = DECL_SOURCE_LOCATION (orig_fn_decl); |
| /* The closing brace is assumed to be input_location. */ |
| fn_end = input_location; |
| |
| /* Build types we need. */ |
| tree fr_name = get_fn_local_identifier (orig_fn_decl, "Frame"); |
| frame_type = xref_tag (record_type, fr_name); |
| DECL_CONTEXT (TYPE_NAME (frame_type)) = DECL_CONTEXT (orig_fn_decl); |
| frame_ptr_type = build_pointer_type (frame_type); |
| act_des_fn_type |
| = build_function_type_list (void_type_node, frame_ptr_type, NULL_TREE); |
| act_des_fn_ptr_type = build_pointer_type (act_des_fn_type); |
| valid_coroutine = true; |
| } |
| |
| cp_coroutine_transform::~cp_coroutine_transform () |
| { |
| } |
| |
| /* Here we: |
| a) Check that the function and promise type are valid for a |
| coroutine. |
| b) Carry out the initial morph to create the skeleton of the |
| coroutine ramp function and the rewritten body. |
| |
| Assumptions. |
| |
| 1. We only hit this code once all dependencies are resolved. |
| 2. The function body will be either a bind expr or a statement list |
| 3. That cfun and current_function_decl are valid for the case we're |
| expanding. |
| 4. 'input_location' will be of the final brace for the function. |
| |
| We do something like this: |
| declare a dummy coro frame. |
| struct _R_frame { |
| using handle_type = coro::coroutine_handle<coro1::promise_type>; |
| void (*_Coro_resume_fn)(_R_frame *); |
| void (*_Coro_destroy_fn)(_R_frame *); |
| coro1::promise_type _Coro_promise; |
| bool _Coro_frame_needs_free; free the coro frame mem if set. |
| bool _Coro_i_a_r_c; [dcl.fct.def.coroutine] / 5.3 |
| short _Coro_resume_index; |
| parameter copies (were required). |
| local variables saved (including awaitables) |
| (maybe) trailing space. |
| }; */ |
| |
| void |
| cp_coroutine_transform::apply_transforms () |
| { |
| if (dmp_str == NULL) |
| dmp_str = dump_begin (coro_dump_id, &coro_dump_flags); |
| |
| coro_maybe_dump_initial_function (orig_fn_decl); |
| |
| coroutine_body |
| = split_coroutine_body_from_ramp (orig_fn_decl); |
| if (!coroutine_body) |
| { |
| valid_coroutine = false; |
| return; |
| } |
| /* Keep the original function block tree to one side and reset. */ |
| body_blocks = current_binding_level->blocks; |
| current_binding_level->blocks = NULL_TREE; |
| |
| /* Collect information on the original function params and their use in the |
| function body. */ |
| analyze_fn_parms (); |
| |
| /* Declare the actor and destroyer functions, the following code needs to |
| see these. */ |
| resumer |
| = coro_build_actor_or_destroy_function (orig_fn_decl, act_des_fn_type, |
| frame_ptr_type, true); |
| destroyer |
| = coro_build_actor_or_destroy_function (orig_fn_decl, act_des_fn_type, |
| frame_ptr_type, false); |
| |
| /* Avoid repeating diagnostics about promise or awaiter fails. */ |
| if (!seen_error ()) |
| { |
| iloc_sentinel stable_input_loc (fn_start); |
| initial_await = build_init_or_final_await (fn_start, false); |
| input_location = fn_end; |
| if (initial_await && initial_await != error_mark_node) |
| final_await = build_init_or_final_await (fn_end, true); |
| } |
| |
| /* Transform the function body as per [dcl.fct.def.coroutine] / 5. */ |
| wrap_original_function_body (); |
| |
| /* Analyze the body await expressions. */ |
| susp_frame_data body_aw_points (fs_label, &suspend_points); |
| cp_walk_tree (&coroutine_body, await_statement_walker, &body_aw_points, NULL); |
| await_count = body_aw_points.await_number; |
| |
| /* Determine the fields for the coroutine state. */ |
| tree field_list = NULL_TREE; |
| local_vars_frame_data local_vars_data (&field_list, &local_var_uses); |
| cp_walk_tree_without_duplicates (&coroutine_body, register_local_var_uses, |
| &local_vars_data); |
| |
| /* Conservative computation of the coroutine frame content. */ |
| frame_type = begin_class_definition (frame_type); |
| TYPE_FIELDS (frame_type) = field_list; |
| TYPE_BINFO (frame_type) = make_tree_binfo (0); |
| BINFO_OFFSET (TYPE_BINFO (frame_type)) = size_zero_node; |
| BINFO_TYPE (TYPE_BINFO (frame_type)) = frame_type; |
| frame_type = finish_struct (frame_type, NULL_TREE); |
| |
| valid_coroutine = build_ramp_function (); |
| coro_maybe_dump_ramp (orig_fn_decl); |
| } |
| |
| /* Having analysed and collected the necessary data we are now in a position |
| to build the outlined coroutine body and the destroyer shim. */ |
| |
| void |
| cp_coroutine_transform::finish_transforms () |
| { |
| if (!valid_coroutine) |
| return; |
| |
| current_function_decl = resumer; |
| build_actor_fn (fn_start, frame_type, resumer, coroutine_body, orig_fn_decl, |
| &local_var_uses, &suspend_points, ¶m_dtor_list, |
| resume_idx_var, await_count, frame_size, inline_p); |
| |
| current_function_decl = destroyer; |
| build_destroy_fn (fn_start, frame_type, destroyer, resumer, inline_p); |
| |
| coro_maybe_dump_transformed_functions (resumer, destroyer); |
| } |
| |
| #include "gt-cp-coroutines.h" |
| |