aboutsummaryrefslogtreecommitdiff
path: root/gcc/cp/coroutines.cc
diff options
context:
space:
mode:
Diffstat (limited to 'gcc/cp/coroutines.cc')
-rw-r--r--gcc/cp/coroutines.cc342
1 files changed, 243 insertions, 99 deletions
diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc
index 17d0ee9..52cc186 100644
--- a/gcc/cp/coroutines.cc
+++ b/gcc/cp/coroutines.cc
@@ -191,8 +191,87 @@ static bool coro_promise_type_found_p (tree, location_t);
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. */
+ complete so that we have complete types at that time.
+ ---------------------------------------------------------------------------
+
+ Coroutine state, and responsibility for its release.
+
+ As noted above, a coroutine has some state that persists across suspensions.
+
+ The state has two components:
+ * State that is specified by the standard and persists for the entire
+ life of the coroutine.
+ * Local state that is constructed/destructed as scopes in the original
+ function body are entered/exited. The destruction of local state is
+ always the responsibility of the body code.
+
+ The persistent state (and the overall storage for the state) must be
+ managed in two places:
+ * The ramp function (which allocates and builds this - and can, in some
+ cases, be responsible for destroying it)
+ * The re-written function body which can destroy it when that body
+ completes its final suspend - or when the handle.destroy () is called.
+
+ In all cases the ramp holds responsibility for constructing the standard-
+ mandated persistent state.
+
+ There are four ways in which the ramp might be re-entered after starting
+ the function body:
+ A The body could suspend (one might expect that to be the 'normal' case
+ for most coroutines).
+ B The body might complete either synchronously or via continuations.
+ C An exception might be thrown during the setup of the initial await
+ expression, before the initial awaiter resumes.
+ D An exception might be processed by promise.unhandled_exception () and
+ that, in turn, might re-throw it (or throw something else). In this
+ case, the coroutine is considered suspended at the final suspension
+ point.
+
+ Until the ramp return value has been constructed, the ramp is considered
+ to have a use of the state.
+
+ To manage these interacting conditions we allocate a reference counter
+ for the frame state. This is initialised to 1 by the ramp as part of its
+ startup (note that failures/exceptions in the startup code are handled
+ locally to the ramp).
+
+ When the body returns (either normally, or by exception) the ramp releases
+ its use.
+
+ Once the rewritten coroutine body is started, the body is considered to
+ have a use of the frame. This use (potentially) needs to be released if
+ an exception is thrown from the body. We implement this using an eh-only
+ cleanup around the initial await and function body. If we have the case
+ D above, then we do not release the use.
+
+ In case:
+
+ A, typically the ramp would be re-entered with the body holding a use,
+ and therefore the ramp should not destroy the state.
+
+ B, both the body and ramp will have released their uses, and the ramp
+ should destroy the state.
+
+ C, we must arrange for the body to release its use, because we require
+ the ramp to cleanup in this circumstance.
+
+ D is an outlier, since the responsibility for destruction of the state
+ now rests with the user's code (via a handle.destroy() call).
+
+ NOTE: In the case that the body has never suspended before such an
+ exception occurs, the only reasonable way for the user code to obtain the
+ necessary handle is if unhandled_exception() throws the handle or some
+ object that contains the handle. That is outside of the designs here -
+ if the user code might need this corner-case, then such provision will
+ have to be made.
+
+ In the ramp, we implement destruction for the persistent frame state by
+ means of cleanups. These are run conditionally when the reference count
+ is 0 signalling that both the body and the ramp have completed.
+
+ In the body, once we pass the final suspend, then we test the use and
+ delete the state if the use is 0. */
/* The state that we collect during parsing (and template expansion) for
a coroutine. */
@@ -206,11 +285,10 @@ struct GTY((for_user)) coroutine_info
tree traits_type; /* The cached traits type for this function. */
tree handle_type; /* The cached coroutine handle for this function. */
tree self_h_proxy; /* A handle instance that is used as the proxy for the
- one that will eventually be allocated in the coroutine
- frame. */
+ one that will eventually be built in lowering. */
tree promise_proxy; /* Likewise, a proxy promise instance. */
- tree from_address; /* handle_type from_address function. */
- tree return_void; /* The expression for p.return_void() if it exists. */
+ tree from_address; /* handle_type from_address() function. */
+ tree return_void; /* The expression for p.return_void(), if it exists. */
location_t first_coro_keyword; /* The location of the keyword that made this
function into a coroutine. */
@@ -348,6 +426,7 @@ static GTY(()) tree coro_resume_index_id;
static GTY(()) tree coro_self_handle_id;
static GTY(()) tree coro_actor_continue_id;
static GTY(()) tree coro_frame_i_a_r_c_id;
+static GTY(()) tree coro_frame_refcount_id;
/* Create the identifiers used by the coroutines library interfaces and
the implementation frame state. */
@@ -385,6 +464,7 @@ coro_init_identifiers ()
coro_resume_index_id = get_identifier ("_Coro_resume_index");
coro_self_handle_id = get_identifier ("_Coro_self_handle");
coro_actor_continue_id = get_identifier ("_Coro_actor_continue");
+ coro_frame_refcount_id = get_identifier ("_Coro_frame_refcount");
}
/* Trees we only need to set up once. */
@@ -1467,6 +1547,12 @@ finish_co_await_expr (location_t kw, tree expr)
if (!expr || error_operand_p (expr))
return error_mark_node;
+ if (cp_unevaluated_operand)
+ {
+ error_at (kw, "%qs cannot be used in an unevaluated context","co_await");
+ return error_mark_node;
+ }
+
if (!coro_common_keyword_context_valid_p (current_function_decl, kw,
"co_await"))
return error_mark_node;
@@ -1547,6 +1633,12 @@ finish_co_yield_expr (location_t kw, tree expr)
if (!expr || error_operand_p (expr))
return error_mark_node;
+ if (cp_unevaluated_operand)
+ {
+ error_at (kw, "%qs cannot be used in an unevaluated context","co_yield");
+ return error_mark_node;
+ }
+
/* Check the general requirements and simple syntax errors. */
if (!coro_common_keyword_context_valid_p (current_function_decl, kw,
"co_yield"))
@@ -1970,12 +2062,13 @@ struct coro_aw_data
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 dispatch; /* This is where we go if we restart the dispatch. */
tree conthand; /* This is the handle for a continuation. */
+ tree handle_type; /* Handle type for this coroutine... */
+ tree hfa_m; /* ... and handle.from_address() for this. */
unsigned index; /* This is our current resume index. */
};
@@ -2093,6 +2186,18 @@ expand_one_await_expression (tree *expr, tree *await_expr, void *d)
tree suspend = TREE_VEC_ELT (awaiter_calls, 1); /* await_suspend(). */
tree susp_type = TREE_TYPE (suspend);
+ tree susp_call = suspend;
+ if (TREE_CODE (suspend) == TARGET_EXPR)
+ susp_call = TARGET_EXPR_INITIAL (suspend);
+ gcc_checking_assert (TREE_CODE (susp_call) == CALL_EXPR);
+ tree dummy_ch = build_dummy_object (data->handle_type);
+ r = fold_convert (build_pointer_type (void_type_node), data->coro_fp);
+ vec<tree, va_gc> *args = make_tree_vector_single (r);
+ tree hfa = cp_fold_rvalue (
+ build_new_method_call (dummy_ch, data->hfa_m, &args, NULL_TREE,
+ LOOKUP_NORMAL, NULL, tf_warning_or_error));
+ release_tree_vector (args);
+ CALL_EXPR_ARG (susp_call, call_expr_nargs (susp_call) - 1) = hfa;
bool is_cont = false;
/* NOTE: final suspend can't resume; the "resume" label in that case
@@ -2574,21 +2679,16 @@ build_actor_fn (location_t loc, tree coro_frame_type, tree actor, tree fnbody,
/* Now we start building the rewritten function body. */
add_stmt (build_stmt (loc, LABEL_EXPR, actor_begin_label));
- /* actor's coroutine 'self handle'. */
- tree ash = coro_build_frame_access_expr (actor_frame, coro_self_handle_id,
- false, tf_warning_or_error);
- /* So construct the self-handle from the frame address. */
- tree hfa_m = get_coroutine_from_address (orig);
- /* Should have been set earlier by coro_promise_type_found_p. */
- gcc_assert (hfa_m);
-
- tree 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 = cp_build_init_expr (ash, hfa);
- finish_expr_stmt (r);
- release_tree_vector (args);
+ tree i_a_r_c = NULL_TREE;
+ if (flag_exceptions)
+ {
+ i_a_r_c
+ = coro_build_frame_access_expr (actor_frame, coro_frame_i_a_r_c_id,
+ false, tf_warning_or_error);
+ tree m = cp_build_modify_expr (loc, i_a_r_c, NOP_EXPR,
+ boolean_false_node, tf_warning_or_error);
+ finish_expr_stmt (m);
+ }
/* Now we know the real promise, and enough about the frame layout to
decide where to put things. */
@@ -2603,7 +2703,28 @@ build_actor_fn (location_t loc, tree coro_frame_type, tree actor, tree fnbody,
/* Add in our function body with the co_returns rewritten to final form. */
add_stmt (fnbody);
- /* Now do the tail of the function; first cleanups. */
+ /* We are done with the frame, but if the ramp still has a hold on it
+ we should not cleanup. So decrement the refcount and then return to
+ the ramp if it is > 0. */
+ tree coro_frame_refcount
+ = coro_build_frame_access_expr (actor_frame, coro_frame_refcount_id,
+ false, tf_warning_or_error);
+ tree released = build2_loc (loc, MINUS_EXPR, short_unsigned_type_node,
+ coro_frame_refcount,
+ build_int_cst (short_unsigned_type_node, 1));
+ tree r = cp_build_modify_expr (loc, coro_frame_refcount, NOP_EXPR, released,
+ tf_warning_or_error);
+ finish_expr_stmt (r);
+ tree cond = build2_loc (loc, NE_EXPR, short_unsigned_type_node,
+ coro_frame_refcount,
+ build_int_cst (short_unsigned_type_node, 0));
+ tree ramp_cu_if = begin_if_stmt ();
+ finish_if_stmt_cond (cond, ramp_cu_if);
+ finish_return_stmt (NULL_TREE);
+ finish_then_clause (ramp_cu_if);
+ finish_if_stmt (ramp_cu_if);
+
+ /* Otherwise, do the tail of the function; first cleanups. */
r = build_stmt (loc, LABEL_EXPR, del_promise_label);
add_stmt (r);
@@ -2672,16 +2793,19 @@ build_actor_fn (location_t loc, tree coro_frame_type, tree actor, tree fnbody,
gcc_checking_assert (maybe_cleanup_point_expr_void (r) == r);
add_stmt (r);
+ /* How to construct the handle for this coroutine from the frame address. */
+ tree hfa_m = get_coroutine_from_address (orig);
+ /* Should have been set earlier by coro_promise_type_found_p. */
+ gcc_assert (hfa_m);
+ tree handle_type = TREE_TYPE (get_coroutine_self_handle_proxy (orig));
+
/* We've now rewritten the tree and added the initial and final
co_awaits. Now pass over the tree and expand the co_awaits. */
- tree i_a_r_c = NULL_TREE;
- if (flag_exceptions)
- i_a_r_c = coro_build_frame_access_expr (actor_frame, coro_frame_i_a_r_c_id,
- false, tf_warning_or_error);
coro_aw_data data = {actor, actor_fp, resume_idx_var, i_a_r_c,
- ash, del_promise_label, ret_label,
- continue_label, restart_dispatch_label, continuation, 2};
+ del_promise_label, ret_label,
+ continue_label, restart_dispatch_label, continuation,
+ handle_type, hfa_m, 2};
cp_walk_tree (&actor_body, await_statement_expander, &data, NULL);
BIND_EXPR_BODY (actor_bind) = pop_stmt_list (actor_body);
@@ -3467,6 +3591,19 @@ analyze_expression_awaits (tree *stmt, int *do_subtree, void *d)
}
*do_subtree = 0;
}
+ else if (!fn && CALL_EXPR_IFN (*stmt) == IFN_ASSUME)
+ {
+ tree expr = CALL_EXPR_ARG (*stmt, 0);
+ if (TREE_SIDE_EFFECTS (expr))
+ {
+ location_t loc_e = cp_expr_location (expr);
+ location_t loc_s = cp_expr_location (*stmt);
+ location_t loc_n = make_location (loc_e, loc_s, loc_s);
+ warning_at (loc_n, OPT_Wattributes,"assumption ignored"
+ " because it contains an await-expression");
+ *stmt = build_empty_stmt (loc_n);
+ }
+ }
}
break;
case CO_YIELD_EXPR:
@@ -4399,16 +4536,6 @@ cp_coroutine_transform::wrap_original_function_body ()
var_list = promise;
add_decl_expr (promise);
- /* We need a handle to this coroutine, which is passed to every
- await_suspend(). This was created on demand when parsing we now link it
- into our scope. */
- var = get_coroutine_self_handle_proxy (orig_fn_decl);
- DECL_CONTEXT (var) = orig_fn_decl;
- DECL_SOURCE_LOCATION (var) = loc;
- DECL_CHAIN (var) = var_list;
- var_list = var;
- add_decl_expr (var);
-
/* If we have function parms, then these will be copied to the coroutine
frame as per [dcl.fct.def.coroutine] / 13.
Here, we create a local (proxy) variable for each parm, since the original
@@ -4452,6 +4579,14 @@ cp_coroutine_transform::wrap_original_function_body ()
var_list = resume_idx_var;
add_decl_expr (resume_idx_var);
+ tree coro_frame_refcount
+ = coro_build_artificial_var (loc, coro_frame_refcount_id,
+ short_unsigned_type_node, orig_fn_decl,
+ NULL_TREE);
+ DECL_CHAIN (coro_frame_refcount) = var_list;
+ var_list = coro_frame_refcount;
+ add_decl_expr (coro_frame_refcount);
+
/* If the coroutine has a frame that needs to be freed, this will be set by
the ramp. */
var = coro_build_artificial_var (loc, coro_frame_needs_free_id,
@@ -4460,6 +4595,15 @@ cp_coroutine_transform::wrap_original_function_body ()
var_list = var;
add_decl_expr (var);
+ /* We consider that the body has a use of the frame once we start to process
+ the initial suspend expression. (the use might be relinquished if we
+ encounter an exception before the body is finished). */
+ tree body_use
+ = build2_loc (loc, PLUS_EXPR, short_unsigned_type_node, coro_frame_refcount,
+ build_int_cst (short_unsigned_type_node, 1));
+ body_use = cp_build_modify_expr (loc, coro_frame_refcount, NOP_EXPR, body_use,
+ tf_warning_or_error);
+ finish_expr_stmt (body_use);
if (flag_exceptions)
{
/* Build promise.unhandled_exception(); */
@@ -4480,10 +4624,28 @@ cp_coroutine_transform::wrap_original_function_body ()
tree tcb = build_stmt (loc, TRY_BLOCK, NULL_TREE, NULL_TREE);
add_stmt (tcb);
TRY_STMTS (tcb) = push_stmt_list ();
+ /* We need a new scope to handle the cleanup for the ramp use that is
+ needed for exceptions. */
+ tree except_scope = begin_compound_stmt (0);
+ current_binding_level->artificial = 1;
+ tree release
+ = build2_loc (loc, MINUS_EXPR, short_unsigned_type_node,
+ coro_frame_refcount, build_int_cst (short_unsigned_type_node, 1));
+ release = cp_build_modify_expr (loc, coro_frame_refcount, NOP_EXPR,
+ release, tf_warning_or_error);
+ /* Once we pass the initial await resume, the cleanup rules on exception
+ change so that the responsibility lies with the caller. */
+ release = build3 (COND_EXPR, void_type_node, i_a_r_c,
+ build_empty_stmt (loc), release);
+ push_cleanup (NULL_TREE, release, /*ehonly*/true);
/* Add the initial await to the start of the user-authored function. */
finish_expr_stmt (initial_await);
+ /* End the scope that handles the remove of frame-use on exception. */
+ finish_compound_stmt (except_scope);
+
/* Append the original function body. */
add_stmt (coroutine_body);
+
if (return_void)
add_stmt (return_void);
TRY_STMTS (tcb) = pop_stmt_list (TRY_STMTS (tcb));
@@ -4928,31 +5090,18 @@ cp_coroutine_transform::build_ramp_function ()
just set it true. */
TREE_USED (frame_needs_free) = true;
- tree iarc_x = NULL_TREE;
- tree coro_before_return = NULL_TREE;
- if (flag_exceptions)
- {
- coro_before_return
- = coro_build_and_push_artificial_var (loc, "_Coro_before_return",
- boolean_type_node, orig_fn_decl,
- boolean_true_node);
- iarc_x
- = coro_build_and_push_artificial_var_with_dve (loc,
- coro_frame_i_a_r_c_id,
- boolean_type_node,
- orig_fn_decl,
- boolean_false_node,
- deref_fp);
- tree frame_cleanup = push_stmt_list ();
- tree do_fr_cleanup
- = build1_loc (loc, TRUTH_NOT_EXPR, boolean_type_node, iarc_x);
- do_fr_cleanup = build2_loc (loc, TRUTH_ANDIF_EXPR, boolean_type_node,
- coro_before_return, do_fr_cleanup);
- r = build3 (COND_EXPR, void_type_node, do_fr_cleanup,
- delete_frame_call, void_node);
- finish_expr_stmt (r);
- push_cleanup (coro_fp, pop_stmt_list (frame_cleanup), /*eh_only*/true);
- }
+ tree coro_frame_refcount
+ = coro_build_and_push_artificial_var_with_dve (loc, coro_frame_refcount_id,
+ short_unsigned_type_node,
+ orig_fn_decl, NULL_TREE,
+ deref_fp);
+ /* Cleanup if both the ramp and the body have finished. */
+ tree cond
+ = build2_loc (loc, EQ_EXPR, short_unsigned_type_node, coro_frame_refcount,
+ build_int_cst (short_unsigned_type_node, 0));
+ r = build3 (COND_EXPR, void_type_node, cond, delete_frame_call,
+ build_empty_stmt (loc));
+ push_cleanup (coro_fp, r, /*eh_only*/false);
/* Put the resumer and destroyer functions in. */
@@ -5027,24 +5176,20 @@ cp_coroutine_transform::build_ramp_function ()
/* Arrange for parm copies to be cleaned up when an exception is
thrown before initial await resume. */
- if (flag_exceptions && !parm.trivial_dtor)
+ if (!parm.trivial_dtor)
{
parm.fr_copy_dtor
= cxx_maybe_build_cleanup (fld_idx, tf_warning_or_error);
if (parm.fr_copy_dtor && parm.fr_copy_dtor != error_mark_node)
{
param_dtor_list.safe_push (parm.field_id);
- tree param_cleanup = push_stmt_list ();
- tree do_cleanup
- = build1_loc (loc, TRUTH_NOT_EXPR, boolean_type_node, iarc_x);
- do_cleanup
- = build2_loc (loc, TRUTH_ANDIF_EXPR, boolean_type_node,
- coro_before_return, do_cleanup);
- r = build3_loc (loc, COND_EXPR, void_type_node, do_cleanup,
- parm.fr_copy_dtor, void_node);
- finish_expr_stmt (r);
- push_cleanup (fld_idx, pop_stmt_list (param_cleanup),
- /*eh_only*/true);
+ cond
+ = build2_loc (loc, EQ_EXPR, short_unsigned_type_node,
+ coro_frame_refcount,
+ build_int_cst (short_unsigned_type_node, 0));
+ r = build3_loc (loc, COND_EXPR, void_type_node, cond,
+ parm.fr_copy_dtor, build_empty_stmt (loc));
+ push_cleanup (fld_idx, r, /*eh_only*/false);
}
}
}
@@ -5089,22 +5234,16 @@ cp_coroutine_transform::build_ramp_function ()
finish_expr_stmt (r);
}
- if (flag_exceptions)
+ tree promise_dtor = cxx_maybe_build_cleanup (p, tf_warning_or_error);
+ /* If the promise is live, then run its dtor if that's available. */
+ if (promise_dtor && promise_dtor != error_mark_node)
{
- tree promise_dtor = cxx_maybe_build_cleanup (p, tf_warning_or_error);
- /* If the promise is live, then run its dtor if that's available. */
- if (promise_dtor && promise_dtor != error_mark_node)
- {
- tree promise_cleanup = push_stmt_list ();
- tree do_cleanup
- = build1_loc (loc, TRUTH_NOT_EXPR, boolean_type_node, iarc_x);
- do_cleanup = build2_loc (loc, TRUTH_ANDIF_EXPR, boolean_type_node,
- coro_before_return, do_cleanup);
- r = build3 (COND_EXPR, void_type_node, do_cleanup,
- promise_dtor, void_node);
- finish_expr_stmt (r);
- push_cleanup (p, pop_stmt_list (promise_cleanup), /*eh_only*/true);
- }
+ cond = build2_loc (loc, EQ_EXPR, short_unsigned_type_node,
+ coro_frame_refcount,
+ build_int_cst (short_unsigned_type_node, 0));
+ r = build3 (COND_EXPR, void_type_node, cond, promise_dtor,
+ build_empty_stmt (loc));
+ push_cleanup (p, r, /*eh_only*/false);
}
tree get_ro
@@ -5169,16 +5308,22 @@ cp_coroutine_transform::build_ramp_function ()
push_cleanup (coro_gro, coro_gro_cleanup, /*eh_only*/false);
}
- /* Start the coroutine body. */
- r = build_call_expr_loc (fn_start, resumer, 1, coro_fp);
+ /* Start the coroutine body, we now have a use of the frame... */
+ r = cp_build_modify_expr (loc, coro_frame_refcount, NOP_EXPR,
+ build_int_cst (short_unsigned_type_node, 1),
+ tf_warning_or_error);
finish_expr_stmt (r);
+ /* ... but when we finish we want to release that, and we want to do that
+ before any of the other cleanups run. */
+ tree released
+ = build2_loc (loc, MINUS_EXPR, short_unsigned_type_node, coro_frame_refcount,
+ build_int_cst (short_unsigned_type_node, 1));
+ released = cp_build_modify_expr (loc, coro_frame_refcount, NOP_EXPR, released,
+ tf_warning_or_error);
+ push_cleanup (NULL_TREE, released, /*eh_only*/false);
- if (flag_exceptions)
- {
- r = cp_build_modify_expr (input_location, coro_before_return, NOP_EXPR,
- boolean_false_node, tf_warning_or_error);
- finish_expr_stmt (r);
- }
+ r = build_call_expr_loc (fn_start, resumer, 1, coro_fp);
+ finish_expr_stmt (r);
/* The ramp is done, we just need the return statement, which we build from
the return object we constructed before we called the actor. */
@@ -5260,7 +5405,6 @@ cp_coroutine_transform::~cp_coroutine_transform ()
bool _Coro_frame_needs_free; free the coro frame mem if set.
bool _Coro_i_a_r_c; [dcl.fct.def.coroutine] / 5.3
short _Coro_resume_index;
- handle_type _Coro_self_handle;
parameter copies (were required).
local variables saved (including awaitables)
(maybe) trailing space.