Re: [C++ coroutines 3/6] Front end parsing and transforms.

2019-11-19 Thread Nathan Sidwell

On 11/17/19 5:25 AM, Iain Sandoe wrote:


+++ b/gcc/cp/coroutines.cc



+/* = Morph and Expand. =



+/* Helpers for label creation.  */
+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_ARTIFICIAL (lab) = 1;
+  DECL_IGNORED_P (lab) = 1;


We can use true for such boolean flags now, your call.



+/* We mark our named labels as used, because we want to keep them in place
+   during development.  FIXME: Remove this before integration.  */


FIXME?


+struct __proxy_replace
+{
+  tree from, to;
+};


std::pair ?



+/* If this is a coreturn statement (or one wrapped in a cleanup) then
+   return the list of statements to replace it.  */
+static tree


space between comment and function -- here and elsewhere.


+  /* p.return_void and p.return_value are probably void, but it's not
+ clear if that's intended to be a guarantee.  CHECKME.  */


CHECKME?


+  /* We might have a single co_return statement, in which case, we do
+have to replace it with a list.  */


'do have to' reads weirdly -> 'have to' or 'do not have to' ?



+static tree
+co_await_expander (tree *stmt, int * /*do_subtree*/, void *d)
+{



+  r = build2_loc (loc, INIT_EXPR, TREE_TYPE (sv_handle), sv_handle,
+ suspend);
+  append_to_statement_list (r, _list);
+  tree resume
+   = lookup_member (TREE_TYPE (sv_handle), get_identifier ("resume"), 1, 0,
+tf_warning_or_error);
+  resume = build_new_method_call (sv_handle, resume, NULL, NULL_TREE,
+ LOOKUP_NORMAL, NULL, tf_warning_or_error);
+  resume = coro_build_cvt_void_expr_stmt (resume, loc);


Do we have a way of forcing this to be a tail call?  Should comment 
and/or TODO:



+  append_to_statement_list (resume, _list);
+}


// comments approaching ...


+  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, 
susp);
+  r = coro_build_cvt_void_expr_stmt (r, loc);


// danger zone over :)  [there are others scattered around though]



+/* 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, struct __await_xform_data *xform)
+{
+  struct 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);


that looks implementory speak -- is it an ICE?



+  /* FIXME: determine if it's better to walk the co_await several times with
+ a quick test, or once with a more complex test.  */


Probably can simply be an 'i'm not sure, I went with ... ' comment?



+  /* Update the block associated with the outer scope of the orig fn.  */
+  tree first = expr_first (fnbody);
+  if (first && TREE_CODE (first) == BIND_EXPR)
+{
+  /* We will discard this, since it's connected to the original scope
+nest... ??? CHECKME, this might be overly cautious.  */

?


+  tree block = BIND_EXPR_BLOCK (first);
+  if (block) // For this to be missing is probably a bug.


gcc_assert?


+   {
+ gcc_assert (BLOCK_SUPERCONTEXT (block) == NULL_TREE);
+ gcc_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 ();
+
+  /* FIXME: this is development marker, remove later.  */


FIXME



+  tree return_void = NULL_TREE;
+  tree rvm
+= lookup_promise_member (orig, "return_void", loc, false /*musthave*/);

  /*musthave=*/false  I think there are other similar cases



+  /* Get a reference to the final suspend var in the frame.  */
+  transform_await_expr (final_await, );
+  r = coro_build_expr_stmt (final_await, loc);
+  add_stmt (r);
+
+  /* now do the tail of the function.  */

now->Now


+  /* Here deallocate the frame (if we allocated it), which we will have at
+ present.  */


sentence is confusing.  reword?


+/* 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. */


pity we don't have a generic helper already.


+  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) && 

Re: [C++ coroutines 3/6] Front end parsing and transforms.

2019-11-18 Thread Nathan Sidwell

On 11/17/19 5:25 AM, Iain Sandoe wrote:



+++ b/gcc/cp/coroutines.cc



+/* FIXME: minimise headers.. */

FIXED?


+/* DEBUG remove me.  */
+extern void debug_tree (tree);

?



+static tree find_coro_traits_template_decl (location_t);
+static tree find_coro_handle_type (location_t, tree);
+static tree find_promise_type (tree);
+static tree
+lookup_promise_member (tree, const char *, location_t, bool);


bad line break?



+/* Lookup std::experimental.  */
+static tree
+find_std_experimental (location_t loc)


Would this be better caching into a global_tree?

+{
+  /* we want std::experimental::coroutine_traits class template decl.  */
... or is the template_decl the thing to cache? (Oh I see this comment 
is incomplete as there's promise_type too).



+/* Lookup the coroutine_traits template decl.
+   Instantiate that for the function signature.  */
+
+static tree
+find_coro_traits_template_decl (location_t kw)
+{



+
+  tree traits_name = get_identifier ("coroutine_traits");
+  tree traits_decl
+= lookup_template_class (traits_name, targ,
+/* in_decl */ NULL_TREE,
+/* context */ exp_ns,
+/* entering scope */ false, tf_none);


You should be able to pass the TEMPLATE_DECL into lookup_template_class. 
So, yes cache the TEMPLATE_DECL std::experimental::coroutine_traits on 
first lookup into a global tree.



+
+  if (traits_decl == error_mark_node)
+{
+  error_at (kw, "couldn't instantiate coroutine_traits");


couldn't -> cannot (or something else non-apostrophey)


+static tree
+find_coro_handle_type (location_t kw, tree promise_type)
+{
+  tree exp_ns = find_std_experimental (kw);
+  if (!exp_ns)
+return NULL_TREE;
+
+  /* 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_name = get_identifier ("coroutine_handle");
+  tree handle_type
+= lookup_template_class (handle_name, targ,


As with the traits, cache the TEMPLATE_DECL.


+/* Look for the promise_type in the instantiated.  */
+
+static tree
+find_promise_type (tree handle_type)
+{
+  tree promise_name = get_identifier ("promise_type");


could you add these identifiers to cp_global_trees? (use-once 
identifiers fine not to, but there;s no point continually rehashing 
multi-use ones).



+/* The state that we collect during parsing (and template expansion) for
+   a coroutine.  */
+typedef struct coroutine_info
+{
+  tree promise_type;
+  tree handle_type;
+  tree self_h_proxy;
+  tree promise_proxy;
+  location_t first_coro_keyword;
+} coroutine_info_t;


typedef struct X{} X_t; is C-like.  Please comment the fields though.
Doesn't this need GTYing?  What keeps those trees alive across GC 
otherwise?  (try running with gc-always and see what happens?)



+/* These function assumes that the caller has verified that the state for


function->functions


+   the decl has been initialised, we try to minimise work here.  */


IIUC american/oxford-english IZED?


+static tree
+get_coroutine_promise_type (tree decl)
+{
+  gcc_checking_assert (fn_to_coro_info);
+
+  coroutine_info_t *info = fn_to_coro_info->get (decl);
+  if (!info)
+return NULL_TREE;
+  return info->promise_type;


idiomatic C++ would be

if (coroutine_info_t *info = ...)
  return info->promis_type;
return NULL_TREE;

your call.


+/* Here we check the constraints that are common to all keywords (since the
+   presence of a coroutine keyword makes the function into a coroutine).  */
+



+  /* This is arranged in order of prohibitions in the std.  */


could you add a [clause.subname] reference maybe?


+  if (DECL_MAIN_P (fndecl))




+/* Here we will check the constraints that are not per keyword.  */


will-> ""


+
+static bool
+coro_function_valid_p (tree fndecl)
+{
+  location_t f_loc = DECL_SOURCE_LOCATION (fndecl);
+
+  /* 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.
+  */

unfortunate line break?



+  gcc_assert (get_coroutine_handle_type (fndecl) != NULL_TREE
+ && get_coroutine_promise_type (fndecl) != NULL_TREE);
+
+  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, "return statement not allowed in coroutine;"

<%return%> not allowed ...?



+/*  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 is the original await expr.
+MODE:
+  0 = regular function body co_await
+  1 = await from a co_yield
+  2 = 

[C++ coroutines 3/6] Front end parsing and transforms.

2019-11-17 Thread Iain Sandoe


As described in the covering note, there are two parts to this.

1. Parsing, template instantiation and diagnostics for the standard-
   mandated class entries.

  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 transformatin until template expansion is
  complete so that we have complete types at that time.

2. AST analysis and transformation which performs the code-gen for the
   outlined state machine.

   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 analysed 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
 optimisations 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 instanceare 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.

gcc/cp/ChangeLog:

2019-11-17  Iain Sandoe  

* Make-lang.in: Add coroutines.o.
* call.c (add_builtin_candidates): Handle CO_AWAIT_EXPR.
(op_error): Likewise.
(build_new_op_1): Likewise.
* constexpr.c (potential_constant_expression_1): Handle
CO_AWAIT_EXPR, CO_YIELD_EXPR.
* coroutines.cc: New file.
* cp-objcp-common.c (cp_common_init_ts): Add CO_AWAIT_EXPR,