On 8/23/24 9:41 AM, Iain Sandoe wrote:
http://eel.is/c++draft/dcl.fct.def.coroutine#12 (sentence 2) says " If both a 
usual deallocation function with only a pointer parameter and a usual deallocation 
function with both a pointer parameter and a size parameter are found, then the 
selected deallocation function shall be the one with two parameters.”
however, if my promise provides both - the one with a single param is always 
chosen.
It is not that the other overload is invalid - if I do not include the single 
param version, the two param one is happily selected.

Ah, that's backwards from https://eel.is/c++draft/expr.delete#9.4 "If the 
deallocation functions belong to a class scope, the one without a parameter of type 
std​::​size_t is selected."

This is implemented as

          /* -- If the deallocation functions have class scope, the one without 
a parameter of type std::size_t is selected.  */
          bool want_size;
          if (DECL_CLASS_SCOPE_P (fn))
            want_size = false;

I guess we need some way for build_op_delete_call to know that we want the 
other preference in this case.

Adding a defaulted param to the existing call seems to be messy since it would 
interrupt the complain being last parm theme..

… I suppose we could add an overload with an additional bool specifying 
priority to the two argument case?

if that seems reasonable, I can take that on - as part of this patch (or 
separately).

That patch might look as below.

I have addressed the other comments here;

If this does not look like a good direction, then perhaps we could proceed
with the original version and address improvements as a follow-on along with
the other changes we need to make to support over-aligned frame objects?

thanks,
Iain

--- 8< ---

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:

        * call.cc (build_op_delete_call_1): Renamed and added a param
        to allow the caller to prioritize two argument usual deleters.
        (build_op_delete_call): Add an overload to expose the option
        to prioritize two argument deleters.
        * coroutines.cc (coro_get_frame_dtor): Rename...
        (build_coroutine_frame_delete_expr):... to this; simplify to
        use build_op_delete_call for all cases.
        (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.
        * cp-tree.h (build_op_delete_call): Add an overload for the version
        that allows two operand deleters.

gcc/testsuite/ChangeLog:

        * g++.dg/coroutines/coro-bad-alloc-01-bad-op-del.C: Use revised
        diagnostics.
        * g++.dg/coroutines/coro-bad-gro-00-class-gro-scalar-return.C:
        Likewise.
        * 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>
---
  gcc/cp/call.cc                                |  36 +-
  gcc/cp/coroutines.cc                          | 462 ++++++++++--------
  gcc/cp/coroutines.h                           |   2 +-
  gcc/cp/cp-tree.h                              |   3 +
  .../coroutines/coro-bad-alloc-01-bad-op-del.C |   2 +-
  .../coro-bad-gro-00-class-gro-scalar-return.C |   4 +-
  .../coro-bad-gro-01-void-gro-non-class-coro.C |   4 +-
  .../coroutines/coro-bad-grooaf-00-static.C    |   6 +-
  .../g++.dg/coroutines/ramp-return-b.C         |   8 +-
  9 files changed, 292 insertions(+), 235 deletions(-)

diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index 0fe679aae9f..377882a1c61 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -7851,6 +7851,8 @@ usual_deallocation_fn_p (tree fn)
     SIZE is the size of the memory block to be deleted.
     GLOBAL_P is true if the delete-expression should not consider
     class-specific delete operators.
+   TWO_ARGS_PRIORITY_P is true if a two argument usual deallocation should be
+   chosen in preference to the single argument version in a class context.
     PLACEMENT is the corresponding placement new call, or NULL_TREE.
If this call to "operator delete" is being generated as part to
@@ -7859,10 +7861,10 @@ usual_deallocation_fn_p (tree fn)
     we call a deallocation function), then ALLOC_FN is the allocation
     function.  */
-tree
-build_op_delete_call (enum tree_code code, tree addr, tree size,
-                     bool global_p, tree placement,
-                     tree alloc_fn, tsubst_flags_t complain)
+static tree
+build_op_delete_call_1 (enum tree_code code, tree addr, tree size,
+                       bool global_p, bool two_args_priority_p, tree placement,
+                       tree alloc_fn, tsubst_flags_t complain)
  {
    tree fn = NULL_TREE;
    tree fns, fnname, type, t;
@@ -8041,7 +8043,7 @@ build_op_delete_call (enum tree_code code, tree addr, 
tree size,
            /* -- If the deallocation functions have class scope, the one
               without a parameter of type std::size_t is selected.  */
            bool want_size;
-           if (DECL_CLASS_SCOPE_P (fn))
+           if (DECL_CLASS_SCOPE_P (fn) && !two_args_priority_p)
              want_size = false;
/* -- If the type is complete and if, for the second alternative
@@ -8179,6 +8181,30 @@ build_op_delete_call (enum tree_code code, tree addr, 
tree size,
    return error_mark_node;
  }
+/* Arguments as per build_op_delete_call_1 (). */
+
+tree
+build_op_delete_call (enum tree_code code, tree addr, tree size,
+                     bool global_p, bool two_args_priority_p, tree placement,
+                     tree alloc_fn, tsubst_flags_t complain)
+{
+  return build_op_delete_call_1 (code, addr, size, global_p,
+                               two_args_priority_p, placement, alloc_fn,
+                               complain);
+}

Rather than having a new overload with the same name and a bool parameter that will only be used to pass "true", let's give it a different name with no new parameter that passes "true" to _1. Perhaps build_op_delete_call_coro, and call the new parameter to _1 coro_p instead of two_args_priority_p?

...
+/* 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().  */
+
+bool
+cp_coroutine_transform::build_ramp_function ()
+{
+  gcc_checking_assert (current_binding_level
+                      && current_binding_level->kind == sk_function_parms);
+
+  /* This is completely synthetic code, if we find an issue then we have not
+     much chance to point at the most useful place in the user's code.  In
+     lieu of this use the function start - so at least the diagnostic relates
+     to something that the user can inspect.  */
+  iloc_sentinel saved_position (fn_start);
+  location_t loc = fn_start;
+
+  tree promise_type = get_coroutine_promise_type (orig_fn_decl);
+  tree fn_return_type = TREE_TYPE (TREE_TYPE (orig_fn_decl));
+
+  /* [dcl.fct.def.coroutine] / 10 (part1)
+    The unqualified-id get_return_object_on_allocation_failure is looked up
+    in the scope of the promise type by class member access lookup.  */
+
+  /* We don't require this,  but, if the lookup succeeds, then the function
+     must be usable, punt if it is not.  */
+  tree grooaf_meth
+    = lookup_promise_method (orig_fn_decl,
+                            coro_gro_on_allocation_fail_identifier, loc,
+                            /*musthave*/ false);
+  tree grooaf = NULL_TREE;
+  tree dummy_promise
+    = build_dummy_object (get_coroutine_promise_type (orig_fn_decl));
+  if (grooaf_meth && grooaf_meth != error_mark_node)
+    {
+      grooaf
+       = coro_build_promise_expression (orig_fn_decl, dummy_promise,
+                                        coro_gro_on_allocation_fail_identifier,
+                                        fn_start, NULL, /*musthave=*/false);
+
+      /* That should succeed.  */
+      if (!grooaf || grooaf == error_mark_node)
+       {
+         error_at (fn_start, "%qE is provided by %qT but is not usable with"
+                   " the function %qD", coro_gro_on_allocation_fail_identifier,
+                   promise_type, orig_fn_decl);
+         return false;
+       }
+    }
+
+  /* Check early for usable allocator/deallocator, without which we cannot
+     build a useful ramp; early exit if they are not available or usable.  */
+
+  frame_size = TYPE_SIZE_UNIT (frame_type);
+
+  /* Make a var to represent the frame pointer early.  Initialize to zero so
+     that we can pass it to the IFN_CO_FRAME (to give that access to the frame
+     type).  */
+  tree coro_fp = coro_build_artificial_var (loc, "_Coro_frameptr",
+                                           frame_ptr_type, orig_fn_decl,
+                                           NULL_TREE);
+
+  tree new_fn_call
+    = build_coroutine_frame_alloc_expr (promise_type, orig_fn_decl, fn_start,
+                                       grooaf, &param_uses, frame_size);
+
+  /* We must have a useable allocator to proceed.  */
+  if (!new_fn_call || new_fn_call == error_mark_node)
+    return false;
+
+  /* Likewise, we need the DTOR to delete the frame.  */
+  tree delete_frame_call
+    = build_coroutine_frame_delete_expr (coro_fp, orig_fn_decl, frame_size,
+                                        promise_type, fn_start);
+  if (!delete_frame_call || delete_frame_call == error_mark_node)
+    return false;
+
+  /* At least verify we can lookup the get return object method.  */
+  tree get_ro_meth
+    = lookup_promise_method (orig_fn_decl,
+                            coro_get_return_object_identifier, loc,
+                            /*musthave*/ true);
+  if (!get_ro_meth || get_ro_meth == error_mark_node)
+    return false;
+
+  /* So now construct the Ramp: */
+
+  /* In generated code, for the most part, we need to set the location to
+     'unknown' to avoid the debugger jumping around.  */

I don't think that applies to an entirely artificial function like the ramp? Where else would the debugger jump to?

+  input_location = loc = UNKNOWN_LOCATION;
+
+  tree ramp_fnbody = 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_outer_bind = 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;
+
+  add_decl_expr (coro_fp);
+  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);
+       if (parm_i->trivial_dtor)
+         continue;
+       add_decl_expr (parm_i->guard_var);
+      }
+  add_decl_expr (coro_promise_live);
+  add_decl_expr (coro_gro_live);
+
+  /* Build the frame.  */
+
+  /* The CO_FRAME internal function is a mechanism to allow the middle end
+     to adjust the allocation in response to optimizations.  We provide the
+     current conservative estimate of the frame size (as per the current)
+     computed layout.  */
+
+  tree resizeable
+    = build_call_expr_internal_loc (loc, IFN_CO_FRAME, size_type_node, 2,
+                                   frame_size,
+                                   build_zero_cst (frame_ptr_type));
+  CALL_EXPR_ARG (new_fn_call, 0) = resizeable;
+  tree allocated = build1 (CONVERT_EXPR, frame_ptr_type, new_fn_call);
    tree r = cp_build_init_expr (coro_fp, allocated);
    finish_expr_stmt (r);
@@ -4943,8 +4976,7 @@ cp_coroutine_transform::complete_ramp_function ()
        BIND_EXPR_BODY (ramp_bind) = pop_stmt_list (ramp_outer_bind);
        /* Suppress warnings about the missing return value.  */
        suppress_warning (orig_fn_decl, OPT_Wreturn_type);
-      valid_coroutine = false;
-      return;
+      return false;
      }
tree gro_context_body = push_stmt_list ();
@@ -5029,9 +5061,6 @@ cp_coroutine_transform::complete_ramp_function ()
    r = build_call_expr_loc (fn_start, resumer, 1, coro_fp);
    finish_expr_stmt (r);
- /* Switch to using 'input_location' as the loc, since we're now more
-     logically doing things related to the end of the function.  */
-
    /* The ramp is done, we just need the return value.
       [dcl.fct.def.coroutine] / 7
       The expression promise.get_return_object() is used to initialize the
@@ -5061,14 +5090,17 @@ cp_coroutine_transform::complete_ramp_function ()
    else
      {
        /* We can't initialize a non-class return value from void.  */
-      error_at (input_location, "cannot initialize a return object of type"
+      error_at (fn_start, "cannot initialize a return object of type"
                " %qT with an rvalue of type %<void%>", fn_return_type);
-      r = error_mark_node;
-      valid_coroutine = false;
-      return;
+      return false;
      }
+ /* Without a relevant location, bad conversions in finish_return_stmt result
+     in unusable diagnostics, since there is not even a mention of the
+     relevant function.  */

...and if you don't switch to UNKNOWN_LOCATION above, you don't need to mess with input_location here.

Jason

Reply via email to