[PATCH Coroutines]Access promise via actor function's frame pointer argument

Iain Sandoe iain@sandoe.co.uk
Mon Jan 20 15:08:00 GMT 2020


Hi Bin,

bin.cheng <bin.cheng@linux.alibaba.com> wrote:

> By standard, coroutine body should be encapsulated in try-catch block
> as following:
>  try {
>    // coroutine body
>  } catch(...) {
>    promise.unhandled_exception();
>  }
> Given above try-catch block is implemented in the coroutine actor
> function called by coroutine ramp function, so the promise should
> be accessed via actor function's coroutine frame pointer argument,
> rather than the ramp function's coroutine frame variable.

thanks, good catch!
it has not triggered for me (including on some more complex test-cases that are not useable
in the testsuite).

> This patch also refactored code to make the fix easy, unfortunately,

see below,

> I failed to reduce a test case from cpproro.

it would be good if we could try to find a reproducer.  I’m happy to try and throw
creduce at a preprocessed file, if you have one.

> gcc/cp
> 2020-01-20  Bin Cheng  <bin.linux@linux.alibaba.com>
> 
>        * coroutines.cc (act_des_fn, wrap_coroutine_body): New.
>        (morph_fn_to_coro): Call act_des_fn to build actor/destroy decls, as
>        well as access promise via actor function's frame pointer argument.
>        Refactor code into above functions.
>        (build_actor_fn, build_destroy_fn): Use frame pointer argument

diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc
index b04baae..1b0338c5 100644
--- a/gcc/cp/coroutines.cc
+++ b/gcc/cp/coroutines.cc
@@ -1827,11 +1827,7 @@ build_actor_fn (location_t loc, tree coro_frame_type, tree actor, tree fnbody,
   tree act_des_fn_ptr = build_pointer_type (act_des_fn_type);
 
   /* One param, the coro frame pointer.  */
-  tree actor_fp
-    = build_lang_decl (PARM_DECL, get_identifier ("frame_ptr"), coro_frame_ptr);
-  DECL_CONTEXT (actor_fp) = actor;
-  DECL_ARG_TYPE (actor_fp) = type_passed_as (coro_frame_ptr);
-  DECL_ARGUMENTS (actor) = actor_fp;
+  tree actor_fp = DECL_ARGUMENTS (actor);
 
   /* A void return.  */
   tree resdecl = build_decl (loc, RESULT_DECL, 0, void_type_node);
@@ -2218,12 +2214,7 @@ build_destroy_fn (location_t loc, tree coro_frame_type, tree destroy,
 		  tree actor)
 {
   /* One param, the coro frame pointer.  */
-  tree coro_frame_ptr = build_pointer_type (coro_frame_type);
-  tree destr_fp
-    = build_lang_decl (PARM_DECL, get_identifier ("frame_ptr"), coro_frame_ptr);
-  DECL_CONTEXT (destr_fp) = destroy;
-  DECL_ARG_TYPE (destr_fp) = type_passed_as (coro_frame_ptr);
-  DECL_ARGUMENTS (destroy) = destr_fp;
+  tree destr_fp = DECL_ARGUMENTS (destroy);
 
   /* A void return.  */
   tree resdecl = build_decl (loc, RESULT_DECL, 0, void_type_node);
@@ -2861,6 +2852,111 @@ register_local_var_uses (tree *stmt, int *do_subtree, void *d)
   return NULL_TREE;
 }
 
+/* Build, return FUNCTION_DECL node with its coroutine frame pointer argument
+   for either actor or destroy functions.  */
+
+static tree
+act_des_fn (tree orig, tree fn_type, tree coro_frame_ptr, const char* name)
+{
+  tree fn_name = get_fn_local_identifier (orig, name);
+  tree fn = build_lang_decl (FUNCTION_DECL, fn_name, fn_type);
+  DECL_CONTEXT (fn) = DECL_CONTEXT (orig);
+  DECL_INITIAL (fn) = error_mark_node;
+  tree id = get_identifier ("frame_ptr");
+  tree fp = build_lang_decl (PARM_DECL, id, coro_frame_ptr);
+  DECL_CONTEXT (fp) = fn;
+  DECL_ARG_TYPE (fp) = type_passed_as (coro_frame_ptr);
+  DECL_ARGUMENTS (fn) = fp;
+  return fn;
+}

^^^ these (and the hunk in morph_fn_to_coro) or an equivalent are prequisite for the actual
fix and a nice tidy-up, thanks.

+/* Wrap and return the function body in a try {} catch (...) {} block if
+   exceptions are needed.  */
+
+static tree
+wrap_coroutine_body (tree coro_frame_type, tree actor, tree fnbody, tree orig)
+{
+  /* First make a new block for the body - that will be embedded in the
+     re-written function.  */
+  tree first = expr_first (fnbody);
+  bool orig_fn_has_outer_bind = false;
+  tree replace_blk = NULL_TREE;
+  if (first && TREE_CODE (first) == BIND_EXPR)
+    {
+      orig_fn_has_outer_bind = true;
+      tree block = BIND_EXPR_BLOCK (first);
+      replace_blk = make_node (BLOCK);
+      if (block) // missing block is probably an error.
+	{
+	  gcc_assert (BLOCK_SUPERCONTEXT (block) == NULL_TREE);
+	  gcc_assert (BLOCK_CHAIN (block) == NULL_TREE);
+	  BLOCK_VARS (replace_blk) = BLOCK_VARS (block);
+	  BLOCK_SUBBLOCKS (replace_blk) = BLOCK_SUBBLOCKS (block);
+	  for (tree b = BLOCK_SUBBLOCKS (replace_blk); b; b = BLOCK_CHAIN (b))
+	    BLOCK_SUPERCONTEXT (b) = replace_blk;
+	}
+      BIND_EXPR_BLOCK (first) = replace_blk;
+    }
+
+  location_t loc = DECL_SOURCE_LOCATION (orig);
+  if (flag_exceptions)
+    {
+      tree ueh_meth
+	= lookup_promise_method (orig, coro_unhandled_exception_identifier,
+				 loc, /*musthave=*/ true);
+      /* actor's version of the promise.  */
+      tree actor_frame = build1_loc (loc, INDIRECT_REF, coro_frame_type,
+				     DECL_ARGUMENTS (actor));
+      tree ap_m = lookup_member (coro_frame_type, get_identifier ("__p"), 1, 0,
+				 tf_warning_or_error);
+      tree ap = build_class_member_access_expr (actor_frame, ap_m, NULL_TREE,
+						false, tf_warning_or_error);
+      /* Build promise.unhandled_exception();  */
+      tree ueh = build_new_method_call (ap, ueh_meth, NULL, NULL_TREE,
+					LOOKUP_NORMAL, NULL,
+					tf_warning_or_error);
+
+      /* The try block is just the original function, there's no real
+	 need to call any function to do this.  */
+      fnbody = build_stmt (loc, TRY_BLOCK, fnbody, NULL_TREE);
+      TRY_HANDLERS (fnbody) = push_stmt_list ();
+      /* Mimic what the parser does for the catch.  */
+      tree handler = begin_handler ();
+      finish_handler_parms (NULL_TREE, handler); /* catch (...) */
+      ueh = maybe_cleanup_point_expr_void (ueh);
+      add_stmt (ueh);
+      finish_handler (handler);
+      TRY_HANDLERS (fnbody) = pop_stmt_list (TRY_HANDLERS (fnbody));
+      /* If the function starts with a BIND_EXPR, then we need to create
+	 one here to contain the try-catch and to link up the scopes.  */
+      if (orig_fn_has_outer_bind)
+	{
+	  fnbody = build3 (BIND_EXPR, void_type_node, NULL, fnbody, NULL);
+	  /* Make and connect the scope blocks.  */
+	  tree tcb_block = make_node (BLOCK);
+	  /* .. and connect it here.  */
+	  BLOCK_SUPERCONTEXT (replace_blk) = tcb_block;
+	  BLOCK_SUBBLOCKS (tcb_block) = replace_blk;
+	  BIND_EXPR_BLOCK (fnbody) = tcb_block;
+	}
+    }
+  else if (pedantic)
+    {
+      /* We still try to look for the promise method and warn if it's not
+	 present.  */
+      tree ueh_meth
+	= lookup_promise_method (orig, coro_unhandled_exception_identifier,
+				 loc, /*musthave=*/ false);
+      if (!ueh_meth || ueh_meth == error_mark_node)
+	warning_at (loc, 0, "no member named %qE in %qT",
+		    coro_unhandled_exception_identifier,
+		    get_coroutine_promise_type (orig));
+    }
+  /* Else we don't check and don't care if the method is missing.  */
+
+  return fnbody;
+}

IMO this ^^^ obfuscates the fix, and I don’t think it should be done at the same time.

I don’t (personally) think it has to be oulined, since the functionality is only used once
(but other maintainers might take a different view).

 /* Here we:
    a) Check that the function and promise type are valid for a
       coroutine.
@@ -2988,17 +3084,9 @@ morph_fn_to_coro (tree orig, tree *resumer, tree *destroyer)
     = build_function_type_list (void_type_node, coro_frame_ptr, NULL_TREE);
   tree act_des_fn_ptr = build_pointer_type (act_des_fn_type);
 
-  /* Declare the actor function.  */
-  tree actor_name = get_fn_local_identifier (orig, "actor");
-  tree actor = build_lang_decl (FUNCTION_DECL, actor_name, act_des_fn_type);
-  DECL_CONTEXT (actor) = DECL_CONTEXT (orig);
-  DECL_INITIAL (actor) = error_mark_node;
-
-  /* Declare the destroyer function.  */
-  tree destr_name = get_fn_local_identifier (orig, "destroy");
-  tree destroy = build_lang_decl (FUNCTION_DECL, destr_name, act_des_fn_type);
-  DECL_CONTEXT (destroy) = DECL_CONTEXT (orig);
-  DECL_INITIAL (destroy) = error_mark_node;
+  /* Declare the actor and destroyer function.  */
+  tree actor = act_des_fn (orig, act_des_fn_type, coro_frame_ptr, "actor");
+  tree destroy = act_des_fn (orig, act_des_fn_type, coro_frame_ptr, "destroy");
 
^^^^ but this is needed (or the equivalent) to enable the fix.

thanks
Iain



More information about the Gcc-patches mailing list