On 8/21/24 3:10 PM, Iain Sandoe wrote:
This splits out the building of the allocation and deallocation expressions
and runs them early in the ramp build, so that we can exit if they are not
usable, before we start building the ramp body.
Likewise move checks for other required resources to the begining of the
ramp builder.
This is preparation for work needed to update the allocation/destruction
in cases where we have excess alignment of the promise or other saved frame
state.
gcc/cp/ChangeLog:
* coroutines.cc (coro_get_frame_dtor): Rename...
(build_coroutine_frame_delete_expr):... to this.
(build_actor_fn): Use revised frame delete function.
(build_coroutine_frame_alloc_expr): New.
(cp_coroutine_transform::complete_ramp_function): Rename...
(cp_coroutine_transform::build_ramp_function): ... to this.
Reorder code to carry out checks for prerequisites before the
codegen. Split out the allocation/delete code.
(cp_coroutine_transform::apply_transforms): Use revised name.
* coroutines.h: Rename function.
gcc/testsuite/ChangeLog:
* g++.dg/coroutines/coro-bad-gro-00-class-gro-scalar-return.C:
Use revised diagnostics.
* g++.dg/coroutines/coro-bad-gro-01-void-gro-non-class-coro.C:
Likewise.
* g++.dg/coroutines/coro-bad-grooaf-00-static.C: Likewise.
* g++.dg/coroutines/ramp-return-b.C: Likewise.
Signed-off-by: Iain Sandoe <i...@sandoe.co.uk>
+static tree
+build_coroutine_frame_alloc_expr (tree promise_type, tree orig_fn_decl,
+ location_t fn_start, tree grooaf,
+ hash_map<tree, param_info> *param_uses,
+ tree frame_size)
{
/* Allocate the frame, this has several possibilities:
[dcl.fct.def.coroutine] / 9 (part 1)
The allocation function’s name is looked up in the scope of the promise
+ type. It is not a failure for it to be absent see part 4, below. */
tree nwname = ovl_op_identifier (false, NEW_EXPR);
+ tree new_fn_call = NULL_TREE;
+ tree dummy_promise
+ = build_dummy_object (get_coroutine_promise_type (orig_fn_decl));
if (TYPE_HAS_NEW_OPERATOR (promise_type))
{
@@ -4611,7 +4454,7 @@ cp_coroutine_transform::complete_ramp_function ()
requested, and has type std::size_t. The lvalues p1...pn are the
succeeding arguments.. */
vec<tree, va_gc> *args = make_tree_vector ();
+ vec_safe_push (args, frame_size); /* Space needed. */
for (tree arg = DECL_ARGUMENTS (orig_fn_decl); arg != NULL;
arg = DECL_CHAIN (arg))
@@ -4633,18 +4476,18 @@ cp_coroutine_transform::complete_ramp_function ()
/* Note the function selected; we test to see if it's NOTHROW. */
tree func;
/* Failure is not an error for this attempt. */
+ new_fn_call = build_new_method_call (dummy_promise, fns, &args, NULL,
LOOKUP_NORMAL, &func, tf_none);
release_tree_vector (args);
- if (new_fn == error_mark_node)
+ if (new_fn_call == error_mark_node)
{
/* [dcl.fct.def.coroutine] / 9 (part 3)
If no viable function is found, overload resolution is performed
again on a function call created by passing just the amount of
space required as an argument of type std::size_t. */
+ args = make_tree_vector_single (frame_size); /* Space needed. */
+ new_fn_call = build_new_method_call (dummy_promise, fns, &args,
NULL_TREE, LOOKUP_NORMAL, &func,
tf_none);
release_tree_vector (args);
@@ -4652,21 +4495,18 @@ cp_coroutine_transform::complete_ramp_function ()
/* However, if the promise provides an operator new, then one of these
two options must be available. */
+ if (new_fn_call == error_mark_node)
{
error_at (fn_start, "%qE is provided by %qT but is not usable with"
" the function signature %qD", nwname, promise_type,
orig_fn_decl);
+ return error_mark_node;
}
else if (grooaf && !TYPE_NOTHROW_P (TREE_TYPE (func)))
{
error_at (fn_start, "%qE is provided by %qT but %qE is not marked"
" %<throw()%> or %<noexcept%>", grooaf, promise_type, nwname);
+ return error_mark_node;
}
else if (!grooaf && TYPE_NOTHROW_P (TREE_TYPE (func)))
warning_at (fn_start, 0, "%qE is marked %<throw()%> or %<noexcept%> but"
@@ -4698,31 +4538,264 @@ cp_coroutine_transform::complete_ramp_function ()
LOOK_want::NORMAL,
/*complain=*/true);
if (!std_nt || std_nt == error_mark_node)
+ {
+ /* Something is seriously wrong, punt. */
+ error_at (fn_start, "%qE is provided by %qT but %<std::nothrow%>"
+ " cannot be found", grooaf, promise_type);
+ return error_mark_node;
+ }
vec_safe_push (args, std_nt);
}
/* If we get to this point, we must succeed in looking up the global
operator new for the params provided. Extract a simplified version
+ of the machinery from build_operator_new_call.
This seems like a call to that function, not a simplified version of it?
It would be nice to factor more of the code for choosing a member or
global operator new out of build_new_1 to use here as well, but not
necessary.
+ NOTE: This can update the frame size so we need to account for that
+ when building the IFN_CO_FRAME later. */
I don't think it can when cookie and size_check are both NULL, as they
are here.
tree cookie = NULL;
+ new_fn_call = build_operator_new_call (nwname, &args, &frame_size,
+ &cookie, /*align_arg=*/NULL,
+ /*size_check=*/NULL, /*fn=*/NULL,
+ tf_warning_or_error);
release_tree_vector (args);
}
+ return new_fn_call;
+}
- tree allocated = build1 (CONVERT_EXPR, frame_ptr_type, new_fn);
+static tree
+build_coroutine_frame_delete_expr (tree coro_fp, tree orig, tree frame_size,
+ tree promise_type, location_t loc)
+{
Here it seems like you could already use build_op_delete_call for all of
this, just by converting coro_fp to pointer-to-promise_type instead of
to ptr_type_node?
+ 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;
+}
+
+/* Build the ramp function.
+ Here we take the original function definition which has now had its body
+ removed, and use it as the declaration of the ramp which both replaces the
+ user's written function at call sites, and is responsible for starting
+ the coroutine it defined.
+ returns NULL_TREE on error or an expression for the frame size.
+
+ We should arrive here with the state of the compiler as if we had just
+ executed start_preparsed_function(). */
+
+void
+cp_coroutine_transform::build_ramp_function ()
+{
+ gcc_checking_assert (current_binding_level
+ && current_binding_level->kind == sk_function_parms);
+
+ /* This is completely synthetic code, if we find an issue then we have not
+ much chance to point at the most useful place in the user's code. In
+ lieu of this use the function start - so at least the diagnostic relates
+ to something that the user can inspect. */
+
+ location_t save_input_loc = input_location;
+ location_t loc = fn_start;
+ input_location = loc;
Here using iloc_sentinel is simple.
+
+ tree promise_type = get_coroutine_promise_type (orig_fn_decl);
+ tree fn_return_type = TREE_TYPE (TREE_TYPE (orig_fn_decl));
+
+ /* [dcl.fct.def.coroutine] / 10 (part1)
+ The unqualified-id get_return_object_on_allocation_failure is looked up
+ in the scope of the promise type by class member access lookup. */
+
+ /* We don't require this, but, if the lookup succeeds, then the function
+ must be usable, punt if it is not. */
+ tree grooaf_meth
+ = lookup_promise_method (orig_fn_decl,
+ coro_gro_on_allocation_fail_identifier, loc,
+ /*musthave*/ false);
+ tree grooaf = NULL_TREE;
+ tree dummy_promise
+ = build_dummy_object (get_coroutine_promise_type (orig_fn_decl));
+ if (grooaf_meth && grooaf_meth != error_mark_node)
+ {
+ grooaf
+ = coro_build_promise_expression (orig_fn_decl, dummy_promise,
+ coro_gro_on_allocation_fail_identifier,
+ fn_start, NULL, /*musthave=*/false);
+
+ /* That should succeed. */
+ if (!grooaf || grooaf == error_mark_node)
+ {
+ error_at (fn_start, "%qE is provided by %qT but is not usable with"
+ " the function %qD", coro_gro_on_allocation_fail_identifier,
+ promise_type, orig_fn_decl);
+ valid_coroutine = false;
+ input_location = save_input_loc;
+ return;
+ }
+ }
+
+ /* Check early for usable allocator/deallocator, without which we cannot
+ build a useful ramp; early exit if they are not available or usable. */
+
+ frame_size = TYPE_SIZE_UNIT (frame_type);
+
+ /* Make a var to represent the frame pointer early. */
+ tree zeroinit = build1_loc (loc, CONVERT_EXPR,
+ frame_ptr_type, nullptr_node);
+ tree coro_fp = coro_build_artificial_var (loc, "_Coro_frameptr",
+ frame_ptr_type, orig_fn_decl,
+ zeroinit);
+
+ tree new_fn_call
+ = build_coroutine_frame_alloc_expr (promise_type, orig_fn_decl, fn_start,
+ grooaf, param_uses, frame_size);
+
+ /* We must have a useable allocator to proceed. */
+ if (!new_fn_call || new_fn_call == error_mark_node)
+ {
+ valid_coroutine = false;
+ input_location = save_input_loc;
+ return;
+ }
+
+ /* Likewise, we need the DTOR to delete the frame. */
+ tree delete_frame_call
+ = build_coroutine_frame_delete_expr (coro_fp, orig_fn_decl, frame_size,
+ promise_type, fn_start);
+ if (!delete_frame_call || delete_frame_call == error_mark_node)
+ {
+ valid_coroutine = false;
+ input_location = save_input_loc;
+ return;
+ }
+
+ /* At least verify we can lookup the get return object method. */
+ tree get_ro_meth
+ = lookup_promise_method (orig_fn_decl,
+ coro_get_return_object_identifier, loc,
+ /*musthave*/ true);
+ if (!get_ro_meth || get_ro_meth == error_mark_node)
+ {
+ valid_coroutine = false;
+ input_location = save_input_loc;
+ return;
+ }
+
+ /* So now construct the Ramp: */
+ tree stmt = begin_function_body ();
+ /* Now build the ramp function pieces. */
+ tree ramp_bind = build3 (BIND_EXPR, void_type_node, NULL, NULL, NULL);
+ add_stmt (ramp_bind);
+ tree ramp_body = push_stmt_list ();
+ tree varlist = coro_fp;
+
+ /* To signal that we need to cleanup copied function args. */
+ if (flag_exceptions && DECL_ARGUMENTS (orig_fn_decl))
+ for (tree arg = DECL_ARGUMENTS (orig_fn_decl); arg != NULL;
+ arg = DECL_CHAIN (arg))
+ {
+ param_info *parm_i = param_uses->get (arg);
+ gcc_checking_assert (parm_i);
+ if (parm_i->trivial_dtor)
+ continue;
+ DECL_CHAIN (parm_i->guard_var) = varlist;
+ varlist = parm_i->guard_var;
+ }
+
+ /* Signal that we need to clean up the promise object on exception. */
+ tree coro_promise_live
+ = coro_build_artificial_var (loc, "_Coro_promise_live", boolean_type_node,
+ orig_fn_decl, boolean_false_node);
+ DECL_CHAIN (coro_promise_live) = varlist;
+ varlist = coro_promise_live;
+
+ /* When the get-return-object is in the RETURN slot, we need to arrange for
+ cleanup on exception. */
+ tree coro_gro_live
+ = coro_build_artificial_var (loc, "_Coro_gro_live", boolean_type_node,
+ orig_fn_decl, boolean_false_node);
+
+ DECL_CHAIN (coro_gro_live) = varlist;
+ varlist = coro_gro_live;
+
+ /* Collected the scope vars we need ... only one for now. */
+ BIND_EXPR_VARS (ramp_bind) = nreverse (varlist);
+
+ /* We're now going to create a new top level scope block for the ramp
+ function. */
+ tree top_block = make_node (BLOCK);
+
+ BIND_EXPR_BLOCK (ramp_bind) = top_block;
+ BLOCK_VARS (top_block) = BIND_EXPR_VARS (ramp_bind);
+ BLOCK_SUBBLOCKS (top_block) = NULL_TREE;
+ current_binding_level->blocks = top_block;
+
+ /* The decl_expr for the coro frame pointer, initialize to zero so that we
+ can pass it to the IFN_CO_FRAME (since there's no way to pass a type,
+ directly apparently). This avoids a "used uninitialized" warning. */
You could pass build_zero_cst (frame_ptr_type) instead of the variable?
Jason