coroutines: Fix handling of conditional statements [PR94288]
authorIain Sandoe <iain@sandoe.co.uk>
Thu, 23 Apr 2020 15:59:00 +0000 (16:59 +0100)
committerIain Sandoe <iain@sandoe.co.uk>
Thu, 23 Apr 2020 19:34:57 +0000 (20:34 +0100)
Normally, when we find a statement containing an await expression
this will be expanded to a statement list implementing the control
flow implied.  The expansion process successively replaces each
await expression in a statement with the result of its await_resume().

In the case of conditional statements (if, while, do, switch) the
expansion of the condition (or expression in the case of do-while)
cannot take place 'inline', leading to the PR.

The solution is to evaluate the expression separately, and to
transform while and do-while loops into endless loops with a break
on the required condition.

In fixing this, I realised that I'd also made a thinko in the case
of expanding truth-and/or-if expressions, where one arm of the
expression might need to be short-circuited.  The mechanism for
expanding via the tree walk will not work correctly in this case and
we need to pre-expand any truth-and/or-if with an await expression
on its conditionally-taken arm.  This applies to any statement with
truth-and/or-if expressions, so can be handled generically.

gcc/cp/ChangeLog:

2020-04-23  Iain Sandoe  <iain@sandoe.co.uk>

PR c++/94288
* coroutines.cc (await_statement_expander): Simplify cases.
(struct susp_frame_data): Add fields for truth and/or if
cases, rename one field.
(analyze_expression_awaits): New.
(expand_one_truth_if): New.
(add_var_to_bind): New helper.
(coro_build_add_if_not_cond_break): New helper.
(await_statement_walker): Handle conditional expressions,
handle expansion of truth-and/or-if cases.
(bind_expr_find_in_subtree): New, checking-only.
(coro_body_contains_bind_expr_p): New, checking-only.
(morph_fn_to_coro): Ensure that we have a top level bind
expression.

gcc/testsuite/ChangeLog:

2020-04-23  Iain Sandoe  <iain@sandoe.co.uk>

PR c++/94288
* g++.dg/coroutines/torture/co-await-18-if-cond.C: New test.
* g++.dg/coroutines/torture/co-await-19-while-cond.C: New test.
* g++.dg/coroutines/torture/co-await-20-do-while-cond.C: New test.
* g++.dg/coroutines/torture/co-await-21-switch-value.C: New test.
* g++.dg/coroutines/torture/co-await-22-truth-and-of-if.C: New test.
* g++.dg/coroutines/torture/co-ret-16-simple-control-flow.C: New test.

gcc/cp/ChangeLog
gcc/cp/coroutines.cc
gcc/testsuite/ChangeLog
gcc/testsuite/g++.dg/coroutines/torture/co-await-18-if-cond.C [new file with mode: 0644]
gcc/testsuite/g++.dg/coroutines/torture/co-await-19-while-cond.C [new file with mode: 0644]
gcc/testsuite/g++.dg/coroutines/torture/co-await-20-do-while-cond.C [new file with mode: 0644]
gcc/testsuite/g++.dg/coroutines/torture/co-await-21-switch-value.C [new file with mode: 0644]
gcc/testsuite/g++.dg/coroutines/torture/co-await-22-truth-and-of-if.C [new file with mode: 0644]
gcc/testsuite/g++.dg/coroutines/torture/co-ret-16-simple-control-flow.C [new file with mode: 0644]

index a197f00f63ce22a177d5cce94c7f1bd86d4b7ad7..fa150e3462fc29f064c6fc1dd3f092fb56e23633 100644 (file)
@@ -1,3 +1,20 @@
+2020-04-23 Iain Sandoe <iain@sandoe.co.uk>
+
+       PR c++/94288
+       * coroutines.cc (await_statement_expander): Simplify cases.
+       (struct susp_frame_data): Add fields for truth and/or if
+       cases, rename one field.
+       (analyze_expression_awaits): New.
+       (expand_one_truth_if): New.
+       (add_var_to_bind): New helper.
+       (coro_build_add_if_not_cond_break): New helper.
+       (await_statement_walker): Handle conditional expressions,
+       handle expansion of truth-and/or-if cases.
+       (bind_expr_find_in_subtree): New, checking-only.
+       (coro_body_contains_bind_expr_p): New, checking-only.
+       (morph_fn_to_coro): Ensure that we have a top level bind
+       expression.
+
 2020-04-22  Jonathan Wakely  <jwakely@redhat.com>
 
        PR translation/94698
index b1d91f84cae042f8c8695596458133d7f69cf28f..bec165b6ec6b204196488b645c79e437aad305e7 100644 (file)
@@ -1627,9 +1627,8 @@ await_statement_expander (tree *stmt, int *do_subtree, void *d)
   tree res = NULL_TREE;
 
   /* Process a statement at a time.  */
-  if (TREE_CODE (*stmt) == BIND_EXPR)
-    res = cp_walk_tree (&BIND_EXPR_BODY (*stmt), await_statement_expander,
-                       d, NULL);
+  if (STATEMENT_CLASS_P (*stmt) || TREE_CODE (*stmt) == BIND_EXPR)
+    return NULL_TREE; /* Just process the sub-trees.  */
   else if (TREE_CODE (*stmt) == STATEMENT_LIST)
     {
       tree_stmt_iterator i;
@@ -1642,8 +1641,6 @@ await_statement_expander (tree *stmt, int *do_subtree, void *d)
        }
       *do_subtree = 0; /* Done subtrees.  */
     }
-  else if (STATEMENT_CLASS_P (*stmt))
-    return NULL_TREE; /* Process the sub-trees.  */
   else if (EXPR_P (*stmt))
     {
       process_one_statement (stmt, d);
@@ -2587,12 +2584,14 @@ struct susp_frame_data
   vec<tree, va_gc> *block_stack; /* Track block scopes.  */
   vec<tree, va_gc> *bind_stack;  /* Track current bind expr.  */
   unsigned await_number;        /* Which await in the function.  */
-  unsigned condition_number;    /* Which replaced condition in the fn.  */
+  unsigned cond_number;                 /* Which replaced condition in the fn.  */
   /* Temporary values for one statement or expression being analyzed.  */
   hash_set<tree> captured_temps; /* The suspend captured these temps.  */
   vec<tree, va_gc> *to_replace;  /* The VAR decls to replace.  */
+  hash_set<tree> *truth_aoif_to_expand; /* The set of TRUTH exprs to expand.  */
   unsigned saw_awaits;          /* Count of awaits in this statement  */
   bool captures_temporary;      /* This expr captures temps by ref.  */
+  bool needs_truth_if_exp;      /* We must expand a truth_if expression.  */
 };
 
 /* Walk the sub-tree looking for call expressions that both capture
@@ -2896,6 +2895,178 @@ maybe_promote_captured_temps (tree *stmt, void *d)
   return NULL_TREE;
 }
 
+/* Lightweight callback to determine two key factors:
+   1) If the statement/expression contains any await expressions.
+   2) If the statement/expression potentially requires a re-write to handle
+      TRUTH_{AND,OR}IF_EXPRs since, in most cases, they will need expansion
+      so that the await expressions are not processed in the case of the
+      short-circuit arm.
+   CO_YIELD expressions are re-written to their underlying co_await.  */
+
+static tree
+analyze_expression_awaits (tree *stmt, int *do_subtree, void *d)
+{
+  susp_frame_data *awpts = (susp_frame_data *) d;
+
+  switch (TREE_CODE (*stmt))
+    {
+      default: return NULL_TREE;
+      case CO_YIELD_EXPR:
+       /* co_yield is syntactic sugar, re-write it to co_await.  */
+       *stmt = TREE_OPERAND (*stmt, 1);
+       /* FALLTHROUGH */
+      case CO_AWAIT_EXPR:
+       awpts->saw_awaits++;
+       break;
+      case TRUTH_ANDIF_EXPR:
+      case TRUTH_ORIF_EXPR:
+       {
+         /* We don't need special action for awaits in the always-executed
+            arm of a TRUTH_IF.  */
+         if (tree res = cp_walk_tree (&TREE_OPERAND (*stmt, 0),
+                                      analyze_expression_awaits, d, NULL))
+           return res;
+         /* However, if there are await expressions on the conditionally
+            executed branch, we must expand the TRUTH_IF to ensure that the
+            expanded await expression control-flow is fully contained in the
+            conditionally executed code.  */
+         unsigned aw_count = awpts->saw_awaits;
+         if (tree res = cp_walk_tree (&TREE_OPERAND (*stmt, 1),
+                                      analyze_expression_awaits, d, NULL))
+           return res;
+         if (awpts->saw_awaits > aw_count)
+           {
+             awpts->truth_aoif_to_expand->add (*stmt);
+             awpts->needs_truth_if_exp = true;
+           }
+         /* We've done the sub-trees here.  */
+         *do_subtree = 0;
+       }
+       break;
+    }
+
+  return NULL_TREE; /* Recurse until done.  */
+}
+
+/* Given *EXPR
+   If EXPR contains a TRUTH_{AND,OR}IF_EXPR, TAOIE with an await expr on
+   the conditional branch expand this to:
+
+   bool not_expr = TAOIE == TRUTH_ORIF_EXPR ? NOT : NOP;
+   A) bool t = always exec expr
+      if (not_expr (t))
+   B)   t = conditionally exec expr
+   c) EXPR' = EXPR with  TAOIE replaced by t.
+
+   Then repeat this for A, B and C.  */
+
+struct truth_if_transform {
+  tree *orig_stmt;
+  tree scratch_var;
+  hash_set<tree> *truth_aoif_to_expand;
+};
+
+static tree
+expand_one_truth_if (tree *expr, int *do_subtree, void *d)
+{
+  truth_if_transform *xform = (truth_if_transform *) d;
+
+  bool needs_not = false;
+  switch (TREE_CODE (*expr))
+    {
+      default: break;
+      case TRUTH_ORIF_EXPR:
+       needs_not = true;
+       /* FALLTHROUGH */
+      case TRUTH_ANDIF_EXPR:
+       {
+         if (!xform->truth_aoif_to_expand->contains (*expr))
+           break;
+
+         location_t sloc = EXPR_LOCATION (*expr);
+         tree type = TREE_TYPE (xform->scratch_var);
+         gcc_checking_assert (TREE_CODE (type) == BOOLEAN_TYPE);
+         tree new_list = push_stmt_list ();
+         /* Init our scratch with the unconditionally-evaluated expr.  */
+         tree new_s = build2_loc (sloc, INIT_EXPR, boolean_type_node,
+                                  xform->scratch_var,
+                                  TREE_OPERAND (*expr, 0));
+         finish_expr_stmt (new_s);
+         tree *pre = tsi_stmt_ptr (tsi_last (new_list));
+         tree if_cond = xform->scratch_var;
+         if (needs_not)
+           if_cond = build1 (TRUTH_NOT_EXPR, boolean_type_node, if_cond);
+         tree if_stmt = begin_if_stmt ();
+         finish_if_stmt_cond (if_cond, if_stmt);
+         /* If we take the if branch, then overwrite scratch with the cond
+            executed branch.  */
+         new_s = build2 (INIT_EXPR, boolean_type_node,
+                         xform->scratch_var, TREE_OPERAND (*expr, 1));
+         finish_expr_stmt (new_s);
+         finish_then_clause (if_stmt);
+         finish_if_stmt (if_stmt);
+         *expr = xform->scratch_var; /* now contains the result.  */
+         /* So now we've got a statement list expanding one TAOIe.  */
+         add_stmt (*xform->orig_stmt);
+         tree *post = tsi_stmt_ptr (tsi_last (new_list));
+         *xform->orig_stmt = pop_stmt_list (new_list);
+         /* Now recurse into the pre, if and post parts.  */
+         truth_if_transform sub_data = {pre, xform->scratch_var,
+                                        xform->truth_aoif_to_expand};
+         if (tree res = cp_walk_tree (pre, expand_one_truth_if, &sub_data,
+                                      NULL))
+           return res;
+         sub_data.orig_stmt = &THEN_CLAUSE (if_stmt);
+         if (tree res = cp_walk_tree (&THEN_CLAUSE (if_stmt),
+                                      expand_one_truth_if, &sub_data, NULL))
+           return res;
+         sub_data.orig_stmt = post;
+         if (tree res = cp_walk_tree (post, expand_one_truth_if, &sub_data,
+                                      NULL))
+           return res;
+         /* We've done the sub-trees here.  */
+         *do_subtree = 0;
+       }
+       break;
+    }
+  return NULL_TREE;
+}
+
+/* Helper that adds a new variable of VAR_TYPE to a bind scope BIND, the
+   name is made up from NAM_ROOT, NAM_VERS.  */
+
+static tree
+add_var_to_bind (tree& bind, tree var_type,
+                const char *nam_root, unsigned nam_vers)
+{
+
+  tree b_vars = BIND_EXPR_VARS (bind);
+  /* Build a variable to hold the condition, this will be included in the
+     frame as a local var.  */
+  char *nam = xasprintf ("%s.%d", nam_root, nam_vers);
+  tree newvar = build_lang_decl (VAR_DECL, get_identifier (nam), var_type);
+  free (nam);
+  DECL_CHAIN (newvar) = b_vars;
+  BIND_EXPR_VARS (bind) = newvar;
+  return newvar;
+}
+
+/* Helper to build and add if (!cond) break;  */
+
+static void
+coro_build_add_if_not_cond_break (tree cond)
+{
+  tree if_stmt = begin_if_stmt ();
+  tree invert = build1 (TRUTH_NOT_EXPR, boolean_type_node, cond);
+  finish_if_stmt_cond (invert, if_stmt);
+  finish_break_stmt ();
+  finish_then_clause (if_stmt);
+  finish_if_stmt (if_stmt);
+}
+
+/* Tree walk callback to analyze, register and pre-process statements that
+   contain await expressions.  */
+
 static tree
 await_statement_walker (tree *stmt, int *do_subtree, void *d)
 {
@@ -2905,6 +3076,9 @@ await_statement_walker (tree *stmt, int *do_subtree, void *d)
   /* Process a statement at a time.  */
   if (TREE_CODE (*stmt) == BIND_EXPR)
     {
+      /* For conditional expressions, we might wish to add an artificial var
+        to their containing bind expr.  */
+      vec_safe_push (awpts->bind_stack, *stmt);
       /* We might need to insert a new bind expression, and want to link it
         into the correct scope, so keep a note of the current block scope.  */
       tree blk = BIND_EXPR_BLOCK (*stmt);
@@ -2912,7 +3086,9 @@ await_statement_walker (tree *stmt, int *do_subtree, void *d)
       res = cp_walk_tree (&BIND_EXPR_BODY (*stmt), await_statement_walker,
                          d, NULL);
       awpts->block_stack->pop ();
+      awpts->bind_stack->pop ();
       *do_subtree = 0; /* Done subtrees.  */
+      return res;
     }
   else if (TREE_CODE (*stmt) == STATEMENT_LIST)
     {
@@ -2925,13 +3101,205 @@ await_statement_walker (tree *stmt, int *do_subtree, void *d)
            return res;
        }
       *do_subtree = 0; /* Done subtrees.  */
+      return NULL_TREE;
     }
-  else if (STATEMENT_CLASS_P (*stmt))
-    return NULL_TREE; /* Process the subtrees.  */
+
+  /* We have something to be handled as a single statement.  */
+  hash_set<tree> visited;
+  awpts->saw_awaits = 0;
+  hash_set<tree> truth_aoif_to_expand;
+  awpts->truth_aoif_to_expand = &truth_aoif_to_expand;
+  awpts->needs_truth_if_exp = false;
+
+  if (STATEMENT_CLASS_P (*stmt))
+    switch (TREE_CODE (*stmt))
+      {
+       /* Unless it's a special case, just walk the subtrees as usual.  */
+       default: return NULL_TREE;
+
+       /* When we have a conditional expression, which contains one or more
+          await expressions, we have to break the condition out into a
+          regular statement so that the control flow introduced by the await
+          transforms can be implemented.  */
+       case IF_STMT:
+         {
+           /* Transform 'if (cond with awaits) then stmt1 else stmt2' into
+              bool cond = cond with awaits.
+              if (cond) then stmt1 else stmt2.  */
+           tree if_stmt = *stmt;
+           /* We treat the condition as if it was a stand-alone statement,
+              to see if there are any await expressions which will be analysed
+              and registered.  */
+           if ((res = cp_walk_tree (&IF_COND (if_stmt),
+               analyze_expression_awaits, d, &visited)))
+             return res;
+           if (!awpts->saw_awaits)
+             return NULL_TREE; /* Nothing special to do here.  */
+
+           gcc_checking_assert (!awpts->bind_stack->is_empty());
+           tree& bind_expr = awpts->bind_stack->last ();
+           tree newvar = add_var_to_bind (bind_expr, boolean_type_node,
+                                          "ifcd", awpts->cond_number++);
+           tree insert_list = push_stmt_list ();
+           tree cond_inner = IF_COND (if_stmt);
+           if (TREE_CODE (cond_inner) == CLEANUP_POINT_EXPR)
+             cond_inner = TREE_OPERAND (cond_inner, 0);
+           add_decl_expr (newvar);
+           location_t sloc = EXPR_LOCATION (IF_COND (if_stmt));
+           /* We want to initialize the new variable with the expression
+              that contains the await(s) and potentially also needs to
+              have truth_if expressions expanded.  */
+           tree new_s = build2_loc (sloc, MODIFY_EXPR, boolean_type_node,
+                                    newvar, cond_inner);
+           finish_expr_stmt (new_s);
+           if (awpts->needs_truth_if_exp)
+             {
+               tree *sp = tsi_stmt_ptr (tsi_last (insert_list));
+               truth_if_transform xf = {sp, newvar, &truth_aoif_to_expand};
+               if ((res = cp_walk_tree (sp, expand_one_truth_if, &xf, NULL)))
+                 return res;
+             }
+           IF_COND (if_stmt) = newvar;
+           add_stmt (if_stmt);
+           *stmt = pop_stmt_list (insert_list);
+           /* So now walk the new statement list.  */
+           res = cp_walk_tree (stmt, await_statement_walker, d, NULL);
+           *do_subtree = 0; /* Done subtrees.  */
+           return res;
+         }
+         break;
+       case WHILE_STMT:
+         {
+           /* We turn 'while (cond with awaits) stmt' into
+              while (true) {
+                 if (!(cond with awaits))
+                   break;
+                 stmt..
+               } */
+           tree while_stmt = *stmt;
+           if ((res = cp_walk_tree (&WHILE_COND (while_stmt),
+               analyze_expression_awaits, d, &visited)))
+             return res;
+           if (!awpts->saw_awaits)
+             return NULL_TREE; /* Nothing special to do here.  */
+
+           tree insert_list = push_stmt_list ();
+           coro_build_add_if_not_cond_break (WHILE_COND (while_stmt));
+           /* The original while body.  */
+           add_stmt (WHILE_BODY (while_stmt));
+           /* The new while body.  */
+           WHILE_BODY (while_stmt) = pop_stmt_list (insert_list);
+           WHILE_COND (while_stmt) = boolean_true_node;
+           /* So now walk the new statement list.  */
+           res = cp_walk_tree (&WHILE_BODY (while_stmt),
+                               await_statement_walker, d, NULL);
+           *do_subtree = 0; /* Done subtrees.  */
+           return res;
+         }
+         break;
+       case DO_STMT:
+         {
+           /* We turn do stmt while (cond with awaits) into:
+              do {
+                 stmt..
+                 if (!(cond with awaits))
+                   break;
+              } while (true); */
+           tree do_stmt = *stmt;
+           if ((res = cp_walk_tree (&DO_COND (do_stmt),
+               analyze_expression_awaits, d, &visited)))
+             return res;
+           if (!awpts->saw_awaits)
+             return NULL_TREE; /* Nothing special to do here.  */
+
+           tree insert_list = push_stmt_list ();
+           /* The original do stmt body.  */
+           add_stmt (DO_BODY (do_stmt));
+           coro_build_add_if_not_cond_break (DO_COND (do_stmt));
+           /* The new while body.  */
+           DO_BODY (do_stmt) = pop_stmt_list (insert_list);
+           DO_COND (do_stmt) = boolean_true_node;
+           /* So now walk the new statement list.  */
+           res = cp_walk_tree (&DO_BODY (do_stmt), await_statement_walker,
+                               d, NULL);
+           *do_subtree = 0; /* Done subtrees.  */
+           return res;
+
+         }
+         break;
+       case SWITCH_STMT:
+         {
+           /* We turn 'switch (cond with awaits) stmt' into
+              switch_type cond = cond with awaits
+              switch (cond) stmt.  */
+           tree sw_stmt = *stmt;
+           if ((res = cp_walk_tree (&SWITCH_STMT_COND (sw_stmt),
+               analyze_expression_awaits, d, &visited)))
+             return res;
+           if (!awpts->saw_awaits)
+             return NULL_TREE; /* Nothing special to do here.  */
+
+           gcc_checking_assert (!awpts->bind_stack->is_empty());
+           /* Build a variable to hold the condition, this will be
+                  included in the frame as a local var.  */
+           tree& bind_expr = awpts->bind_stack->last ();
+           tree sw_type = SWITCH_STMT_TYPE (sw_stmt);
+           tree newvar = add_var_to_bind (bind_expr, sw_type, "swch",
+                                          awpts->cond_number++);
+           tree insert_list = push_stmt_list ();
+           add_decl_expr (newvar);
+
+           tree cond_inner = SWITCH_STMT_COND (sw_stmt);
+           if (TREE_CODE (cond_inner) == CLEANUP_POINT_EXPR)
+             cond_inner = TREE_OPERAND (cond_inner, 0);
+           location_t sloc = EXPR_LOCATION (SWITCH_STMT_COND (sw_stmt));
+           tree new_s = build2_loc (sloc, INIT_EXPR, sw_type, newvar,
+                                    cond_inner);
+           finish_expr_stmt (new_s);
+           SWITCH_STMT_COND (sw_stmt) = newvar;
+           /* Now add the switch statement with the condition re-
+                  written to use the local var.  */
+           add_stmt (sw_stmt);
+           *stmt = pop_stmt_list (insert_list);
+           /* Process the expanded list.  */
+           res = cp_walk_tree (stmt, await_statement_walker,
+                               d, NULL);
+           *do_subtree = 0; /* Done subtrees.  */
+           return res;
+         }
+         break;
+      }
   else if (EXPR_P (*stmt))
     {
-      res = maybe_promote_captured_temps (stmt, d);
+      if ((res = cp_walk_tree (stmt, analyze_expression_awaits, d, &visited)))
+       return res;
       *do_subtree = 0; /* Done subtrees.  */
+      if (!awpts->saw_awaits)
+       return NULL_TREE; /* Nothing special to do here.  */
+
+      /* Unless we need to expand any truth-and/or-if expressions, then the
+        remaining action is to check for temporaries to await expressions
+        captured by refence.  */
+      if (!awpts->needs_truth_if_exp)
+       return maybe_promote_captured_temps (stmt, d);
+
+      gcc_checking_assert (!awpts->bind_stack->is_empty());
+      tree& bind_expr = awpts->bind_stack->last ();
+      /* Build a variable to hold the condition, this will be
+      included in the frame as a local var.  */
+      tree newvar = add_var_to_bind (bind_expr, boolean_type_node,
+                                    "taoi", awpts->cond_number++);
+      tree insert_list = push_stmt_list ();
+      add_decl_expr (newvar);
+      add_stmt (*stmt);
+      tree *sp = tsi_stmt_ptr (tsi_last (insert_list));
+      *stmt = pop_stmt_list (insert_list);
+
+      truth_if_transform xf = {sp, newvar, &truth_aoif_to_expand};
+      if ((res = cp_walk_tree (sp, expand_one_truth_if, &xf, NULL)))
+       return res;
+      /* Process the expanded trees.  */
+      return cp_walk_tree (stmt, await_statement_walker, d, NULL);
     }
  
   /* Continue recursion, if needed.  */
@@ -3073,6 +3441,27 @@ act_des_fn (tree orig, tree fn_type, tree coro_frame_ptr, const char* name)
   return fn;
 }
 
+#if CHECKING_P
+/* Return a bind expression if we see one, else NULL_TREE.  */
+static tree
+bind_expr_find_in_subtree (tree *stmt, int *, void *)
+{
+  if (TREE_CODE (*stmt) == BIND_EXPR)
+    return *stmt;
+  return NULL_TREE;
+}
+
+/* Return the first bind expression that the sub-tree given by STMT
+   contains.  */
+
+static tree
+coro_body_contains_bind_expr_p (tree *stmt)
+{
+  hash_set<tree> visited;
+  return cp_walk_tree (stmt, bind_expr_find_in_subtree, NULL, &visited);
+}
+#endif
+
 /* Here we:
    a) Check that the function and promise type are valid for a
       coroutine.
@@ -3160,6 +3549,30 @@ morph_fn_to_coro (tree orig, tree *resumer, tree *destroyer)
       TREE_OPERAND (body_start, 0) = push_stmt_list ();
     }
 
+  /* We can be presented with a function that currently has no outer bind
+     expression.  We will insert bind scopes in expanding await expressions,
+     and therefore need a top level to the tree, so synthesize an outer bind
+     expression and scope.  */
+  tree check_bind = expr_first (fnbody);
+  if (check_bind && TREE_CODE (check_bind) != BIND_EXPR)
+    {
+      tree update_body = build3 (BIND_EXPR, void_type_node, NULL, NULL, NULL);
+      tree blk = make_node (BLOCK);
+      gcc_checking_assert (!coro_body_contains_bind_expr_p (&fnbody));
+      BIND_EXPR_BLOCK (update_body) = blk;
+      if (TREE_CODE (fnbody) == STATEMENT_LIST)
+       BIND_EXPR_BODY (update_body) = fnbody;
+      else
+       {
+         tree tlist = NULL_TREE;
+         append_to_statement_list_force (fnbody, &tlist);
+         BIND_EXPR_BODY (update_body) = tlist;
+       }
+      tree new_body_list = NULL_TREE;
+      append_to_statement_list_force (update_body, &new_body_list);
+      fnbody = new_body_list;
+    }
+
   /* Create the coro frame type, as far as it can be known at this stage.
      1. Types we already know.  */
 
@@ -3309,7 +3722,7 @@ morph_fn_to_coro (tree orig, tree *resumer, tree *destroyer)
      vars) they will get added to the coro frame along with other locals.  */
   susp_frame_data body_aw_points
     = {&field_list, handle_type, NULL, NULL, 0, 0,
-       hash_set<tree> (), NULL, 0, false};
+       hash_set<tree> (), NULL, NULL, 0, false, false};
   body_aw_points.block_stack = make_tree_vector ();
   body_aw_points.bind_stack = make_tree_vector ();
   body_aw_points.to_replace = make_tree_vector ();
index b287e12588f2760c4be6c0ed06f53143ddf14693..249a1520581f134dff85fccc6f67e9d77f764b08 100644 (file)
@@ -1,3 +1,13 @@
+2020-04-23 Iain Sandoe <iain@sandoe.co.uk>
+
+       PR c++/94288
+       * g++.dg/coroutines/torture/co-await-18-if-cond.C: New test.
+       * g++.dg/coroutines/torture/co-await-19-while-cond.C: New test.
+       * g++.dg/coroutines/torture/co-await-20-do-while-cond.C: New test.
+       * g++.dg/coroutines/torture/co-await-21-switch-value.C: New test.
+       * g++.dg/coroutines/torture/co-await-22-truth-and-of-if.C: New test.
+       * g++.dg/coroutines/torture/co-ret-16-simple-control-flow.C: New test.
+
 2020-04-23  Marek Polacek  <polacek@redhat.com>
 
        PR c++/94733
diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-await-18-if-cond.C b/gcc/testsuite/g++.dg/coroutines/torture/co-await-18-if-cond.C
new file mode 100644 (file)
index 0000000..6b05cfb
--- /dev/null
@@ -0,0 +1,85 @@
+// { dg-do run }
+
+// Test co-await in if condition.
+
+#include "../coro.h"
+
+// boiler-plate for tests of codegen
+#include "../coro1-ret-int-yield-int.h"
+
+/* An awaiter that suspends always and returns a boolean as the
+   await_resume output.  */
+struct BoolAwaiter {
+  bool v;
+  BoolAwaiter (bool _v) : v(_v) {}
+  bool await_ready () { return false; }
+  void await_suspend (coro::coroutine_handle<>) {}
+  bool await_resume () { return v; }
+};
+
+/* An awaiter that suspends always and returns an int as the
+   await_resume output.  */
+struct IntAwaiter {
+  int v;
+  IntAwaiter (int _v) : v(_v) {}
+  bool await_ready () { return false; }
+  void await_suspend (coro::coroutine_handle<>) {}
+  int await_resume () { return v; }
+};
+
+//extern int tt ();
+  int three = 3;
+  int two = 2;
+
+struct coro1
+my_coro (bool t) noexcept
+{
+//  if (int two = 2;tt () || co_await BoolAwaiter (t) && t && co_await IntAwaiter (5) == co_await IntAwaiter (7))
+  if (co_await BoolAwaiter (t) && co_await IntAwaiter (5) == 5)
+    {
+      int five = three + two;
+      co_return 6169 + five;
+    }
+  else
+    {
+      int five = three + two;
+      co_return 37 + five;
+    }
+}
+
+int main ()
+{
+  PRINT ("main: create coro");
+  struct coro1 x = my_coro (true);
+
+  if (x.handle.done())
+    {
+      PRINT ("main: apparently done when we should not be...");
+      abort ();
+    }
+
+  PRINT ("main: resume initial suspend");
+  x.handle.resume();
+
+  PRINT ("main: if condition 1 - true");
+  x.handle.resume();
+
+  PRINT ("main: if condition 2 - true");
+  x.handle.resume();
+
+  PRINT ("main: after resume");
+  int y = x.handle.promise().get_value();
+  if ( y != 6174 )
+    {
+      PRINTF ("main: apparently wrong value : %d\n", y);
+      abort ();
+    }
+
+  if (!x.handle.done())
+    {
+      PRINT ("main: apparently not done...");
+      abort ();
+    }
+  PRINT ("main: returning");
+  return 0;
+}
diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-await-19-while-cond.C b/gcc/testsuite/g++.dg/coroutines/torture/co-await-19-while-cond.C
new file mode 100644 (file)
index 0000000..2cd3761
--- /dev/null
@@ -0,0 +1,68 @@
+// { dg-do run }
+
+// Test co-await in while condition.
+
+#include "../coro.h"
+
+// boiler-plate for tests of codegen
+#include "../coro1-ret-int-yield-int.h"
+
+/* An awaiter that suspends always and returns a boolean as the
+   await_resume output.  */
+struct BoolAwaiter {
+  bool v;
+  BoolAwaiter (bool _v) : v(_v) {}
+  bool await_ready () { return false; }
+  void await_suspend (coro::coroutine_handle<>) {}
+  bool await_resume () { return v; }
+};
+
+//extern bool tt(void);
+int three = 3;
+struct coro1
+my_coro (bool t)
+{
+  //int three = 3;
+  while (co_await BoolAwaiter (t) && t)
+    {
+      int five = three + 2;
+      co_yield 6169 + five;
+    }
+
+  co_return 42;
+}
+
+int main ()
+{
+  PRINT ("main: create coro");
+  struct coro1 x = my_coro (false);
+
+  if (x.handle.done())
+    {
+      PRINT ("main: apparently done when we shouldn't be...");
+      abort ();
+    }
+
+  PRINT ("main: resume initial suspend");
+  x.handle.resume();
+
+  // will be false - so no yield expected.
+  PRINT ("main: while condition");
+  x.handle.resume();
+
+  int y = x.handle.promise().get_value();
+  if ( y != 42 )
+    {
+      PRINTF ("main: apparently wrong value : %d\n", y);
+      abort ();
+    }
+
+  if (!x.handle.done())
+    {
+      PRINT ("main: apparently not done...");
+      abort ();
+    }
+
+  PRINT ("main: returning");
+  return 0;
+}
diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-await-20-do-while-cond.C b/gcc/testsuite/g++.dg/coroutines/torture/co-await-20-do-while-cond.C
new file mode 100644 (file)
index 0000000..bb1e97a
--- /dev/null
@@ -0,0 +1,68 @@
+// { dg-do run }
+
+// Test co-await in do-while conditional
+
+#include "../coro.h"
+
+// boiler-plate for tests of codegen
+#include "../coro1-ret-int-yield-int.h"
+
+/* An awaiter that suspends always and returns a boolean as the
+   await_resume output.  The boolean toggles on each call.  */
+struct BoolAwaiter {
+  bool v;
+  BoolAwaiter (bool _v) : v(_v) {}
+  bool await_ready () { return false; }
+  void await_suspend (coro::coroutine_handle<>) {}
+  bool await_resume () { v = !v; return v; }
+};
+
+int v = 32;
+
+struct coro1
+my_coro (bool t)
+{
+  auto aw = BoolAwaiter (t);
+  do {
+    int five = 5;
+    v += five;
+  } while (co_await aw && !t);
+
+  co_return v;
+}
+
+int main ()
+{
+  PRINT ("main: create coro");
+  struct coro1 x = my_coro (false);
+
+  if (x.handle.done())
+    {
+      PRINT ("main: apparently done when we should not be...");
+      abort ();
+    }
+
+  PRINT ("main: resume initial suspend");
+  x.handle.resume();
+
+  PRINT ("main: resume while test, succeed first time");
+  x.handle.resume();
+
+  PRINT ("main: resume while test, fail second");
+  x.handle.resume();
+
+  int y = x.handle.promise().get_value();
+  if ( y != 42 )
+    {
+      PRINTF ("main: apparently wrong value : %d\n", y);
+      abort ();
+    }
+
+  if (!x.handle.done())
+    {
+      PRINT ("main: apparently not done...");
+      abort ();
+    }
+  PRINT ("main: returning");
+  return 0;
+}
diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-await-21-switch-value.C b/gcc/testsuite/g++.dg/coroutines/torture/co-await-21-switch-value.C
new file mode 100644 (file)
index 0000000..b5e1bf3
--- /dev/null
@@ -0,0 +1,63 @@
+// { dg-do run }
+
+// Test co-await in while condition.
+
+#include "../coro.h"
+
+// boiler-plate for tests of codegen
+#include "../coro1-ret-int-yield-int.h"
+
+/* An awaiter that suspends always and returns an int as the
+   await_resume output.  */
+struct IntAwaiter {
+  int v;
+  IntAwaiter (int _v) : v(_v) {}
+  bool await_ready () { return false; }
+  void await_suspend (coro::coroutine_handle<>) {}
+  int await_resume () { return v; }
+};
+
+struct coro1
+my_coro (int t)
+{
+  int v = 6174;
+  switch (co_await IntAwaiter (t) + 5)
+  {
+    default: break;
+    case 22: co_return v;
+  }
+  co_return 42;
+}
+
+int main ()
+{
+  PRINT ("main: create coro");
+  struct coro1 x = my_coro (17);
+
+  if (x.handle.done())
+    {
+      PRINT ("main: apparently done when we should not be...");
+      abort ();
+    }
+
+  PRINT ("main: resume initial suspend");
+  x.handle.resume();
+
+  PRINT ("main: resume switch condition");
+  x.handle.resume();
+
+  int y = x.handle.promise().get_value();
+  if ( y != 6174 )
+    {
+      PRINTF ("main: apparently wrong value : %d\n", y);
+      abort ();
+    }
+
+  if (!x.handle.done())
+    {
+      PRINT ("main: apparently not done...");
+      abort ();
+    }
+  PRINT ("main: returning");
+  return 0;
+}
diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-await-22-truth-and-of-if.C b/gcc/testsuite/g++.dg/coroutines/torture/co-await-22-truth-and-of-if.C
new file mode 100644 (file)
index 0000000..5465974
--- /dev/null
@@ -0,0 +1,81 @@
+// { dg-do run }
+
+// Test co-await in while condition.
+
+#include "../coro.h"
+
+// boiler-plate for tests of codegen
+#include "../coro1-ret-int-yield-int.h"
+
+/* An awaiter that suspends always and returns an int as the
+   await_resume output.  */
+
+struct IntAwaiter {
+  int v;
+  IntAwaiter (int _v) : v(_v) {}
+  bool await_ready () { return false; }
+  void await_suspend (coro::coroutine_handle<>) {}
+  int await_resume () { return v; }
+};
+
+/* An awaiter that suspends always and returns a boolean as the
+   await_resume output.  The boolean toggles on each call.  */
+
+struct BoolAwaiter {
+  bool v;
+  BoolAwaiter (bool _v) : v(_v) {}
+  bool await_ready () { return false; }
+  void await_suspend (coro::coroutine_handle<>) {}
+  bool await_resume () { v = !v; return v; }
+};
+
+/* We will be able to establish that the second part of the conditional
+   expression is not evaluated (if it was, then we'd need an additional
+   resume to complete the coroutine).  */
+
+struct coro1
+my_coro (int t)
+{
+
+  bool x = co_await IntAwaiter (t) == 5 || co_await BoolAwaiter (false);
+
+  if (x)
+    co_return 6174;
+  co_return 42;
+}
+
+int main ()
+{
+  PRINT ("main: create coro");
+  struct coro1 x = my_coro (5);
+
+  if (x.handle.done())
+    {
+      PRINT ("main: apparently done when we should not be...");
+      abort ();
+    }
+
+  PRINT ("main: resume initial suspend");
+  x.handle.resume();
+
+  PRINT ("main: resume IntAwaiter");
+  x.handle.resume();
+
+  // The evaluation of 'co_await IntAwaiter (t) == 5' should be true, thus
+  // the second co_await in the expression will be unexecuted. 
+  
+  int y = x.handle.promise().get_value();
+  if ( y != 6174 )
+    {
+      PRINTF ("main: apparently wrong value : %d\n", y);
+      abort ();
+    }
+
+  if (!x.handle.done())
+    {
+      PRINT ("main: apparently not done...");
+      abort ();
+    }
+  PRINT ("main: returning");
+  return 0;
+}
diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-ret-16-simple-control-flow.C b/gcc/testsuite/g++.dg/coroutines/torture/co-ret-16-simple-control-flow.C
new file mode 100644 (file)
index 0000000..6d4196d
--- /dev/null
@@ -0,0 +1,49 @@
+#if 0
+//  { d g-do run }
+
+// Test returning an int.
+// We will use the promise to contain this to avoid having to include
+// additional C++ headers.
+
+#include "../coro.h"
+
+// boiler-plate for tests of codegen
+#include "../coro1-ret-int-yield-int.h"
+
+struct coro1
+f (int v)
+{
+  if (co_await coro1::suspend_always_intprt{v} == 10)
+    co_return 6174;
+  else
+    {
+      int v = 42;
+      PRINT ("coro1: about to return");
+      co_return v;
+    }
+}
+
+int main ()
+{
+  PRINT ("main: create coro1");
+  struct coro1 x = f (10);
+  PRINT ("main: got coro1 - resuming");
+  if (x.handle.done())
+    abort();
+  // initial susp
+  x.handle.resume();
+  PRINT ("main: after resume");
+  // if condition
+  x.handle.resume();
+  int y = x.handle.promise().get_value();
+  if ( y != 6174 )
+    abort ();
+  if (!x.handle.done())
+    {
+      PRINT ("main: apparently not done...");
+      abort ();
+    }
+  PRINT ("main: returning");
+  return 0;
+}
+#endif