blob: 551ddc9cc4139110cdc11072ac03c54e556f84d6 [file] [log] [blame]
/* coroutine-specific state, expansions and tests.
Copyright (C) 2018-2022 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"
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. */
/* 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 handle_type; /* The cached coroutine handle for this function. */
tree self_h_proxy; /* A handle instance that is used as the proxy for the
one that will eventually be allocated in the coroutine
frame. */
tree promise_proxy; /* Likewise, a proxy promise instance. */
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. */
/* 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_resume_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;
/* 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_resume_identifier = get_identifier ("resume");
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");
}
/* 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;
/* ================= 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)
{
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);
SET_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,
/*entering scope=*/false, 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;
}
/* Instantiate the handle template for a given promise type. */
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,
/* entering scope=*/false, 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;
}
static bool
coro_promise_type_found_p (tree fndecl, location_t loc)
{
gcc_assert (fndecl != NULL_TREE);
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, NULL_TREE);
if (void_coro_handle_type == NULL_TREE)
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;
}
/* 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 = instantiate_coro_traits (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)
{
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;
/* Complete this, we're going to use it. */
coro_info->handle_type = complete_type_or_else (handle_type, fndecl);
/* 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;
}
/* 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
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)
{
static int awn = 0;
char *buf;
switch (suspend_kind)
{
default: buf = xasprintf ("Aw%d", awn++); break;
case CO_YIELD_SUSPEND_POINT: buf = xasprintf ("Yd%d", awn++); break;
case INITIAL_SUSPEND_POINT: buf = xasprintf ("Is"); break;
case FINAL_SUSPEND_POINT: buf = xasprintf ("Fs"); awn = 0; 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)))
{
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);
}
/* 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. */
static tree
build_co_await (location_t loc, tree a, suspend_point_kind suspend_kind)
{
/* 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. */
tree a_type = TREE_TYPE (a);
if (TYPE_HAS_NONTRIVIAL_DESTRUCTOR (a_type))
if (tree dummy
= build_special_member_call (a, complete_dtor_identifier,
NULL, a_type, LOOKUP_NORMAL,
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)
return error_mark_node;
if (TREE_CODE (o_type) != RECORD_TYPE)
{
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;
/* To complete the lookups, we need an instance of 'e' which is built from
'o' according to [expr.await] 3.4.
If we need to materialize this as a temporary, then that will have to be
'promoted' to a coroutine frame var. However, if the awaitable is a
user variable, parameter or comes from a scope outside this function,
then we must use it directly - or we will see unnecessary copies.
If o is a variable, find the underlying var. */
tree e_proxy = STRIP_NOPS (o);
if (INDIRECT_REF_P (e_proxy))
e_proxy = TREE_OPERAND (e_proxy, 0);
while (TREE_CODE (e_proxy) == COMPONENT_REF)
{
e_proxy = TREE_OPERAND (e_proxy, 0);
if (INDIRECT_REF_P (e_proxy))
e_proxy = TREE_OPERAND (e_proxy, 0);
if (TREE_CODE (e_proxy) == CALL_EXPR)
{
/* We could have operator-> here too. */
tree op = TREE_OPERAND (CALL_EXPR_FN (e_proxy), 0);
if (DECL_OVERLOADED_OPERATOR_P (op)
&& DECL_OVERLOADED_OPERATOR_IS (op, COMPONENT_REF))
{
e_proxy = CALL_EXPR_ARG (e_proxy, 0);
STRIP_NOPS (e_proxy);
gcc_checking_assert (TREE_CODE (e_proxy) == ADDR_EXPR);
e_proxy = TREE_OPERAND (e_proxy, 0);
}
}
STRIP_NOPS (e_proxy);
}
/* Only build a temporary if we need it. */
STRIP_NOPS (e_proxy);
if (TREE_CODE (e_proxy) == PARM_DECL
|| (VAR_P (e_proxy) && !is_local_temp (e_proxy)))
{
e_proxy = o;
o = NULL_TREE; /* The var is already present. */
}
else
{
e_proxy = get_awaitable_var (suspend_kind, o_type);
o = cp_build_modify_expr (loc, e_proxy, INIT_EXPR, o,
tf_warning_or_error);
}
/* 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 (TYPE_HAS_NONTRIVIAL_DESTRUCTOR (o_type))
if (tree dummy
= build_special_member_call (e_proxy, complete_dtor_identifier,
NULL, o_type, LOOKUP_NORMAL,
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' proxies. 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 = TREE_OPERAND (awrs_call, 1);
}
TREE_VEC_ELT (awaiter_calls, 2) = awrs_call; /* await_resume(). */
tree await_expr = build5_loc (loc, CO_AWAIT_EXPR,
TREE_TYPE (TREE_TYPE (awrs_func)),
a, e_proxy, o, awaiter_calls,
build_int_cst (integer_type_node,
(int) suspend_kind));
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);
return convert_from_reference (await_expr);
}
tree
finish_co_await_expr (location_t kw, tree expr)
{
if (!expr || error_operand_p (expr))
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);
/* Defer expansion when we are processing a template.
FIXME: If the coroutine function's type is not dependent, and the operand
is not dependent, we should determine the type of the co_await expression
using the DEPENDENT_EXPR wrapper machinery. That allows us to determine
the subexpression type, but leave its operand unchanged and then
instantiate it later. */
if (processing_template_decl)
{
tree aw_expr = build5_loc (kw, CO_AWAIT_EXPR, unknown_type_node, expr,
NULL_TREE, NULL_TREE, NULL_TREE,
integer_zero_node);
TREE_SIDE_EFFECTS (aw_expr) = true;
return aw_expr;
}
/* 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);
}
/* 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;
/* 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);
/* Defer expansion when we are processing a template; see FIXME in the
co_await code. */
if (processing_template_decl)
return build2_loc (kw, CO_YIELD_EXPR, unknown_type_node, expr, NULL_TREE);
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 = TREE_OPERAND (op, 1);
t = build2_loc (kw, CO_YIELD_EXPR, TREE_TYPE (t), expr, t);
TREE_OPERAND (op, 1) = 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);
if (processing_template_decl
&& check_for_bare_parameter_packs (expr))
return error_mark_node;
/* Defer expansion when we are processing a template; see FIXME in the
co_await code. */
if (processing_template_decl)
{
/* 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 (processing_template_decl)
expr = build_non_dependent_expr (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) == NON_DEPENDENT_EXPR
&& TREE_CODE (TREE_OPERAND (arg, 0)) == 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. */
/* Helpers to build EXPR_STMT and void-cast EXPR_STMT, common ops. */
static tree
coro_build_expr_stmt (tree expr, location_t loc)
{
return maybe_cleanup_point_expr_void (build_stmt (loc, EXPR_STMT, expr));
}
static tree
coro_build_cvt_void_expr_stmt (tree expr, location_t loc)
{
tree t = build1 (CONVERT_EXPR, void_type_node, expr);
return coro_build_expr_stmt (t, loc);
}
/* 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;
}
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);
}
/* Helpers for label creation:
1. Create a named label in the specified context. */
static tree
create_anon_label_with_ctx (location_t loc, tree ctx)
{
tree lab = build_decl (loc, LABEL_DECL, NULL_TREE, void_type_node);
DECL_CONTEXT (lab) = ctx;
DECL_ARTIFICIAL (lab) = true;
DECL_IGNORED_P (lab) = true;
TREE_USED (lab) = true;
return lab;
}
/* 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 self_h; /* This is a handle to the current coro (frame var). */
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 conthand; /* This is the handle for a continuation. */
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 *stmt, tree *await_expr, void *d)
{
coro_aw_data *data = (coro_aw_data *) d;
tree saved_statement = *stmt;
tree saved_co_await = *await_expr;
tree actor = data->actor_fn;
location_t loc = EXPR_LOCATION (*stmt);
tree var = TREE_OPERAND (saved_co_await, 1); /* frame slot. */
tree 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 needs_dtor = TYPE_HAS_NONTRIVIAL_DESTRUCTOR (TREE_TYPE (var));
int resume_point = data->index;
size_t bufsize = sizeof ("destroy.") + 10;
char *buf = (char *) alloca (bufsize);
snprintf (buf, bufsize, "destroy.%d", resume_point);
tree destroy_label = create_named_label_with_ctx (loc, buf, actor);
snprintf (buf, bufsize, "resume.%d", resume_point);
tree resume_label = create_named_label_with_ctx (loc, buf, actor);
tree empty_list = build_empty_stmt (loc);
tree await_type = TREE_TYPE (var);
tree stmt_list = NULL;
tree r;
tree *await_init = NULL;
if (!expr)
needs_dtor = false; /* No need, the var's lifetime is managed elsewhere. */
else
{
r = coro_build_cvt_void_expr_stmt (expr, loc);
append_to_statement_list_force (r, &stmt_list);
/* 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(). */
/* 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);
/* 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);
ready_cond
= build1_loc (loc, CLEANUP_POINT_EXPR, boolean_type_node, ready_cond);
tree body_list = NULL;
tree susp_idx = build_int_cst (short_unsigned_type_node, data->index);
r = build2_loc (loc, MODIFY_EXPR, short_unsigned_type_node, data->resume_idx,
susp_idx);
r = coro_build_cvt_void_expr_stmt (r, loc);
append_to_statement_list (r, &body_list);
/* 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);
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))
{
/* We just call await_suspend() and hit the yield. */
suspend = coro_build_cvt_void_expr_stmt (suspend, loc);
append_to_statement_list (suspend, &body_list);
}
else if (TREE_CODE (susp_type) == BOOLEAN_TYPE)
{
/* Boolean return, continue if the call returns false. */
suspend = build1_loc (loc, TRUTH_NOT_EXPR, boolean_type_node, suspend);
suspend
= build1_loc (loc, CLEANUP_POINT_EXPR, boolean_type_node, suspend);
tree go_on = build1_loc (loc, GOTO_EXPR, void_type_node, resume_label);
r = build3_loc (loc, COND_EXPR, void_type_node, suspend, go_on,
empty_list);
append_to_statement_list (r, &body_list);
}
else
{
r = build1_loc (loc, CONVERT_EXPR, void_coro_handle_type, suspend);
r = build2_loc (loc, INIT_EXPR, void_coro_handle_type, data->conthand, r);
r = build1 (CONVERT_EXPR, void_type_node, r);
append_to_statement_list (r, &body_list);
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);
susp_idx = build_int_cst (integer_type_node, data->index);
tree sw = begin_switch_stmt ();
tree cond = build_decl (loc, VAR_DECL, NULL_TREE, integer_type_node);
DECL_ARTIFICIAL (cond) = 1;
DECL_IGNORED_P (cond) = 1;
layout_decl (cond, 0);
r = build_call_expr_internal_loc (loc, IFN_CO_YIELD, integer_type_node, 5,
susp_idx, final_susp, r_l, d_l,
data->coro_fp);
r = build2 (INIT_EXPR, integer_type_node, cond, r);
finish_switch_cond (r, sw);
r = build_case_label (build_int_cst (integer_type_node, 0), NULL_TREE,
create_anon_label_with_ctx (loc, actor));
add_stmt (r); /* 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);
r = coro_build_cvt_void_expr_stmt (r, loc);
add_stmt (r); /* goto ret; */
r = build_case_label (build_int_cst (integer_type_node, 1), NULL_TREE,
create_anon_label_with_ctx (loc, actor));
add_stmt (r); /* case 1: */
r = build1_loc (loc, GOTO_EXPR, void_type_node, resume_label);
add_stmt (r); /* goto resume; */
r = build_case_label (NULL_TREE, NULL_TREE,
create_anon_label_with_ctx (loc, actor));
add_stmt (r); /* default:; */
r = build1_loc (loc, GOTO_EXPR, void_type_node, destroy_label);
add_stmt (r); /* goto destroy; */
/* part of finish switch. */
SWITCH_STMT_BODY (sw) = pop_stmt_list (SWITCH_STMT_BODY (sw));
pop_switch ();
tree scope = SWITCH_STMT_SCOPE (sw);
SWITCH_STMT_SCOPE (sw) = NULL;
r = do_poplevel (scope);
append_to_statement_list (r, &body_list);
destroy_label = build_stmt (loc, LABEL_EXPR, destroy_label);
append_to_statement_list (destroy_label, &body_list);
if (needs_dtor)
{
tree dtor = build_special_member_call (var, complete_dtor_identifier,
NULL, await_type, LOOKUP_NORMAL,
tf_warning_or_error);
append_to_statement_list (dtor, &body_list);
}
r = build1_loc (loc, GOTO_EXPR, void_type_node, data->cleanup);
append_to_statement_list (r, &body_list);
r = build3_loc (loc, COND_EXPR, void_type_node, ready_cond, body_list,
empty_list);
append_to_statement_list (r, &stmt_list);
/* Resume point. */
resume_label = build_stmt (loc, LABEL_EXPR, resume_label);
append_to_statement_list (resume_label, &stmt_list);
/* 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)
{
tree dtor = build_special_member_call (var, complete_dtor_identifier,
NULL, await_type, LOOKUP_NORMAL,
tf_warning_or_error);
append_to_statement_list (dtor, &stmt_list);
}
data->index += 2;
/* Replace the original statement with the expansion. */
*stmt = 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)
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;
}
/* Suspend point hash_map. */
struct suspend_point_info
{
/* coro frame field type. */
tree awaitable_type;
/* coro frame field name. */
tree await_field_id;
};
static hash_map<tree, suspend_point_info> *suspend_points;
struct await_xform_data
{
tree actor_fn; /* Decl for context. */
tree actor_frame;
};
/* 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 = 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. */
tree coro_frame_type = TREE_TYPE (xform->actor_frame);
/* If we have a frame var for the awaitable, get a reference to it. */
proxy_replace data;
if (si->await_field_id)
{
tree as_m
= lookup_member (coro_frame_type, si->await_field_id,
/*protect=*/1, /*want_type=*/0, tf_warning_or_error);
tree as = build_class_member_access_expr (xform->actor_frame, as_m,
NULL_TREE, 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;
}
/* This caches information that we determine about function params,
their uses and copies in the coroutine frame. */
struct param_info
{
tree field_id; /* The name of the copy in the coroutine frame. */
tree copy_var; /* The local var proxy for the frame copy. */
vec<tree *> *body_uses; /* Worklist of uses, void if there are none. */
tree frame_type; /* The type used to represent this parm in the frame. */
tree orig_type; /* The original type of the parm (not as passed). */
tree guard_var; /* If we need a DTOR on exception, this bool guards it. */
tree fr_copy_dtor; /* If we need a DTOR on exception, this is it. */
bool by_ref; /* Was passed by reference. */
bool pt_ref; /* Was a pointer to object. */
bool rv_ref; /* Was an rvalue ref. */
bool trivial_dtor; /* The frame type has a trivial DTOR. */
bool this_ptr; /* Is 'this' */
bool lambda_cobj; /* Lambda capture object */
};
struct local_var_info
{
tree field_id;
tree field_idx;
tree frame_type;
bool is_lambda_capture;
bool is_static;
bool has_value_expr_p;
location_t def_loc;
};
/* 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_ref
= lookup_member (lvd->coro_frame_type, local_var.field_id,
/*protect=*/1, /*want_type=*/0,
tf_warning_or_error);
tree fld_idx = build3_loc (lvd->loc, COMPONENT_REF, TREE_TYPE (lvar),
lvd->actor_frame, fld_ref, NULL_TREE);
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
coro_get_frame_dtor (tree coro_fp, tree orig, tree frame_size,
tree promise_type, location_t loc)
{
tree del_coro_fr = NULL_TREE;
tree frame_arg = build1 (CONVERT_EXPR, ptr_type_node, coro_fp);
tree delname = ovl_op_identifier (false, DELETE_EXPR);
tree fns = lookup_promise_method (orig, delname, loc,
/*musthave=*/false);
if (fns && BASELINK_P (fns))
{
/* Look for sized version first, since this takes precedence. */
vec<tree, va_gc> *args = make_tree_vector ();
vec_safe_push (args, frame_arg);
vec_safe_push (args, frame_size);
tree dummy_promise = build_dummy_object (promise_type);
/* It's OK to fail for this one... */
del_coro_fr = build_new_method_call (dummy_promise, fns, &args,
NULL_TREE, LOOKUP_NORMAL, NULL,
tf_none);
if (!del_coro_fr || del_coro_fr == error_mark_node)
{
release_tree_vector (args);
args = make_tree_vector_single (frame_arg);
del_coro_fr = build_new_method_call (dummy_promise, fns, &args,
NULL_TREE, LOOKUP_NORMAL, NULL,
tf_none);
}
/* But one of them must succeed, or the program is ill-formed. */
if (!del_coro_fr || del_coro_fr == error_mark_node)
{
error_at (loc, "%qE is provided by %qT but is not usable with"
" the function signature %qD", delname, promise_type, orig);
del_coro_fr = error_mark_node;
}
}
else
{
del_coro_fr = build_op_delete_call (DELETE_EXPR, frame_arg, frame_size,
/*global_p=*/true, /*placement=*/NULL,
/*alloc_fn=*/NULL,
tf_warning_or_error);
if (!del_coro_fr || del_coro_fr == error_mark_node)
del_coro_fr = error_mark_node;
}
return del_coro_fr;
}
/* 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,
vec<tree, va_gc> *param_dtor_list,
tree resume_idx_var, unsigned body_count, tree frame_size)
{
verify_stmt_tree (fnbody);
/* Some things we inherit from the original function. */
tree handle_type = get_coroutine_handle_type (orig);
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);
/* We have a definition here. */
TREE_STATIC (actor) = 1;
tree actor_outer = push_stmt_list ();
current_stmt_tree ()->stmts_are_full_exprs_p = 1;
tree stmt = begin_compound_stmt (BCS_FN_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_field = lookup_member (coro_frame_type, coro_resume_index_id,
1, 0, tf_warning_or_error);
tree rat = build3 (COMPONENT_REF, short_unsigned_type_node, actor_frame,
rat_field, NULL_TREE);
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);
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);
tree ddeflab = build_case_label (NULL_TREE, NULL_TREE,
create_anon_label_with_ctx (loc, actor));
add_stmt (ddeflab);
tree b = build_call_expr_loc (loc, builtin_decl_explicit (BUILT_IN_TRAP), 0);
b = coro_build_cvt_void_expr_stmt (b, loc);
add_stmt (b);
/* 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);
b = build_case_label (build_int_cst (short_unsigned_type_node, 1), NULL_TREE,
create_anon_label_with_ctx (loc, actor));
add_stmt (b);
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);
b = build_case_label (l_num, NULL_TREE,
create_anon_label_with_ctx (loc, actor));
add_stmt (b);
b = build_call_expr_internal_loc (loc, IFN_CO_ACTOR, void_type_node, 1,
l_num);
b = coro_build_cvt_void_expr_stmt (b, loc);
add_stmt (b);
b = build1 (GOTO_EXPR, void_type_node, CASE_LABEL (ddeflab));
add_stmt (b);
lab_num += 2;
}
/* Insert the prototype dispatcher. */
finish_switch_stmt (destroy_dispatcher);
finish_then_clause (lsb_if);
begin_else_clause (lsb_if);
tree dispatcher = begin_switch_stmt ();
finish_switch_cond (rat, dispatcher);
b = build_case_label (build_int_cst (short_unsigned_type_node, 0), NULL_TREE,
create_anon_label_with_ctx (loc, actor));
add_stmt (b);
b = build1 (GOTO_EXPR, void_type_node, actor_begin_label);
add_stmt (b);
tree rdeflab = build_case_label (NULL_TREE, NULL_TREE,
create_anon_label_with_ctx (loc, actor));
add_stmt (rdeflab);
b = build_call_expr_loc (loc, builtin_decl_explicit (BUILT_IN_TRAP), 0);
b = coro_build_cvt_void_expr_stmt (b, loc);
add_stmt (b);
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);
b = build_case_label (l_num, NULL_TREE,
create_anon_label_with_ctx (loc, actor));
add_stmt (b);
b = build_call_expr_internal_loc (loc, IFN_CO_ACTOR, void_type_node, 1,
l_num);
b = coro_build_cvt_void_expr_stmt (b, loc);
add_stmt (b);
b = build1 (GOTO_EXPR, void_type_node, CASE_LABEL (rdeflab));
add_stmt (b);
lab_num += 2;
}
/* Insert the prototype dispatcher. */
finish_switch_stmt (dispatcher);
finish_else_clause (lsb_if);
finish_if_stmt (lsb_if);
tree r = build_stmt (loc, LABEL_EXPR, actor_begin_label);
add_stmt (r);
/* actor's coroutine 'self handle'. */
tree ash_m = lookup_member (coro_frame_type, coro_self_handle_id, 1,
0, tf_warning_or_error);
tree ash = build_class_member_access_expr (actor_frame, ash_m, NULL_TREE,
false, tf_warning_or_error);
/* So construct the self-handle from the frame address. */
tree hfa_m = lookup_member (handle_type, coro_from_address_identifier, 1,
0, tf_warning_or_error);
r = build1 (CONVERT_EXPR, build_pointer_type (void_type_node), actor_fp);
vec<tree, va_gc> *args = make_tree_vector_single (r);
tree hfa = build_new_method_call (ash, hfa_m, &args, NULL_TREE, LOOKUP_NORMAL,
NULL, tf_warning_or_error);
r = build2 (INIT_EXPR, handle_type, ash, hfa);
r = coro_build_cvt_void_expr_stmt (r, loc);
add_stmt (r);
release_tree_vector (args);
/* 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};
/* 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);
/* now do the tail of the function. */
r = build_stmt (loc, LABEL_EXPR, del_promise_label);
add_stmt (r);
/* Destructors for the things we built explicitly. */
r = build_special_member_call (promise_proxy, complete_dtor_identifier, NULL,
promise_type, LOOKUP_NORMAL,
tf_warning_or_error);
add_stmt (r);
tree del_frame_label
= create_named_label_with_ctx (loc, "coro.delete.frame", actor);
r = build_stmt (loc, LABEL_EXPR, del_frame_label);
add_stmt (r);
/* Here deallocate the frame (if we allocated it), which we will have at
present. */
tree fnf_m
= lookup_member (coro_frame_type, coro_frame_needs_free_id, 1,
0, tf_warning_or_error);
tree fnf2_x = build_class_member_access_expr (actor_frame, fnf_m, NULL_TREE,
false, tf_warning_or_error);
tree need_free_if = begin_if_stmt ();
fnf2_x = build1 (CONVERT_EXPR, integer_type_node, fnf2_x);
tree cmp = build2 (NE_EXPR, integer_type_node, fnf2_x, integer_zero_node);
finish_if_stmt_cond (cmp, need_free_if);
if (param_dtor_list != NULL)
{
int i;
tree pid;
FOR_EACH_VEC_ELT (*param_dtor_list, i, pid)
{
tree m
= lookup_member (coro_frame_type, pid, 1, 0, tf_warning_or_error);
tree a = build_class_member_access_expr (actor_frame, m, NULL_TREE,
false, tf_warning_or_error);
tree t = TREE_TYPE (a);
tree dtor;
dtor
= build_special_member_call (a, complete_dtor_identifier, NULL, t,
LOOKUP_NORMAL, tf_warning_or_error);
add_stmt (dtor);
}
}
/* Build the frame DTOR. */
tree del_coro_fr = coro_get_frame_dtor (actor_fp, orig, frame_size,
promise_type, loc);
finish_expr_stmt (del_coro_fr);
finish_then_clause (need_free_if);
tree scope = IF_SCOPE (need_free_if);
IF_SCOPE (need_free_if) = NULL;
r = do_poplevel (scope);
add_stmt (r);
/* done. */
r = build_stmt (loc, RETURN_EXPR, NULL);
suppress_warning (r); /* We don't want a warning about this. */
r = maybe_cleanup_point_expr_void (r);
add_stmt (r);
/* This is the suspend return point. */
r = build_stmt (loc, LABEL_EXPR, ret_label);
add_stmt (r);
r = build_stmt (loc, RETURN_EXPR, NULL);
suppress_warning (r); /* We don't want a warning about this. */
r = maybe_cleanup_point_expr_void (r);
add_stmt (r);
/* 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. */
r = build_stmt (loc, LABEL_EXPR, continue_label);
add_stmt (r);
/* We want to force a tail-call even for O0/1, so this expands the resume
call into its underlying implementation. */
tree addr = lookup_member (void_coro_handle_type, coro_address_identifier,
1, 0, tf_warning_or_error);
addr = build_new_method_call (continuation, addr, 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;
resume = coro_build_cvt_void_expr_stmt (resume, loc);
add_stmt (resume);
r = build_stmt (loc, RETURN_EXPR, NULL);
gcc_checking_assert (maybe_cleanup_point_expr_void (r) == r);
add_stmt (r);
/* 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, NULL_TREE,
ash, del_promise_label, ret_label,
continue_label, continuation, 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;
finish_compound_stmt (stmt);
DECL_SAVED_TREE (actor) = pop_stmt_list (actor_outer);
verify_stmt_tree (DECL_SAVED_TREE (actor));
}
/* 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)
{
/* One param, the coro frame pointer. */
tree destr_fp = DECL_ARGUMENTS (destroy);
/* We have a definition here. */
TREE_STATIC (destroy) = 1;
tree destr_outer = push_stmt_list ();
current_stmt_tree ()->stmts_are_full_exprs_p = 1;
tree dstr_stmt = begin_compound_stmt (BCS_FN_BODY);
tree destr_frame = build1 (INDIRECT_REF, coro_frame_type, destr_fp);
tree rat_field = lookup_member (coro_frame_type, coro_resume_index_id,
1, 0, tf_warning_or_error);
tree rat = build3 (COMPONENT_REF, short_unsigned_type_node,
destr_frame, rat_field, NULL_TREE);
/* _resume_at |= 1 */
tree dstr_idx = build2 (BIT_IOR_EXPR, short_unsigned_type_node, rat,
build_int_cst (short_unsigned_type_node, 1));
tree r = build2 (MODIFY_EXPR, short_unsigned_type_node, rat, dstr_idx);
r = coro_build_cvt_void_expr_stmt (r, loc);
add_stmt (r);
/* So .. call the actor .. */
r = build_call_expr_loc (loc, actor, 1, destr_fp);
r = coro_build_cvt_void_expr_stmt (r, loc);
add_stmt (r);
/* done. */
r = build_stmt (loc, RETURN_EXPR, NULL);
r = maybe_cleanup_point_expr_void (r);
add_stmt (r);
finish_compound_stmt (dstr_stmt);
DECL_SAVED_TREE (destroy) = pop_stmt_list (destr_outer);
}
/* 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)
{
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;
}
/* This data set is used when analyzing statements for await expressions. */
struct susp_frame_data
{
/* Function-wide. */
tree *field_list; /* The current coroutine frame field list. */
tree handle_type; /* The self-handle type for this coroutine. */
tree fs_label; /* The destination for co_returns. */
vec<tree, va_gc> *block_stack; /* Track block scopes. */
vec<tree, va_gc> *bind_stack; /* Track current bind expr. */
unsigned await_number; /* Which await in the function. */
unsigned cond_number; /* Which replaced condition in the fn. */
/* Temporary values for one statement or expression being analyzed. */
hash_set<tree> captured_temps; /* The suspend captured these temps. */
vec<tree, va_gc> *to_replace; /* The VAR decls to replace. */
hash_set<tree> *truth_aoif_to_expand; /* The set of TRUTH exprs to expand. */
unsigned saw_awaits; /* Count of awaits in this statement */
bool captures_temporary; /* This expr captures temps by ref. */
bool needs_truth_if_exp; /* We must expand a truth_if expression. */
bool has_awaiter_init; /* We must handle initializing an awaiter. */
};
/* 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);
/* 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 (TREE_OPERAND (o, 1));
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. */
tree **p = (tree **) d;
*p = stmt;
return *stmt;
}
return NULL_TREE;
}
static bool
tmp_target_expr_p (tree t)
{
if (TREE_CODE (t) != TARGET_EXPR)
return false;
tree v = TREE_OPERAND (t, 0);
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)
&& !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);
then_cl
= build2 (init_expr ? INIT_EXPR : 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);
else_cl
= build2 (init_expr ? INIT_EXPR : 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 = build2 (INIT_EXPR, cond_type, 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 (TREE_OPERAND (inner, 1)) != 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 ("D.%d", DECL_UID (TREE_OPERAND (init, 0)));
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 = TREE_OPERAND (init, 1);
gcc_checking_assert (TREE_CODE (inner) != COND_EXPR);
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 = {TREE_OPERAND (t, 0), 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 = {TREE_OPERAND (t, 0), 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 = build2 (INIT_EXPR, boolean_type_node,
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;
tree var_type = TREE_TYPE (var);
tree cleanup
= build_special_member_call (var, complete_dtor_identifier,
NULL, var_type, LOOKUP_NORMAL,
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. */
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 (t->init);
tree var_type = TREE_TYPE (var);
if (TYPE_HAS_NONTRIVIAL_DESTRUCTOR (var_type))
{
tree cleanup
= build_special_member_call (var, complete_dtor_identifier,
NULL, var_type, LOOKUP_NORMAL,
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 (t->init);
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)