aboutsummaryrefslogtreecommitdiff
path: root/gcc
diff options
context:
space:
mode:
authorIain Sandoe <iain@sandoe.co.uk>2020-04-23 16:59:00 +0100
committerIain Sandoe <iain@sandoe.co.uk>2020-04-23 20:34:57 +0100
commit3dbc772128e944819b09e21021d4fcd5dc17f2d4 (patch)
tree389001201cb096a63933c1499409b7da23303331 /gcc
parent7291b2edf6f87fba839b0d10c04b2562a5f6bd60 (diff)
downloadgcc-3dbc772128e944819b09e21021d4fcd5dc17f2d4.zip
gcc-3dbc772128e944819b09e21021d4fcd5dc17f2d4.tar.gz
gcc-3dbc772128e944819b09e21021d4fcd5dc17f2d4.tar.bz2
coroutines: Fix handling of conditional statements [PR94288]
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.
Diffstat (limited to 'gcc')
-rw-r--r--gcc/cp/ChangeLog17
-rw-r--r--gcc/cp/coroutines.cc433
-rw-r--r--gcc/testsuite/ChangeLog10
-rw-r--r--gcc/testsuite/g++.dg/coroutines/torture/co-await-18-if-cond.C85
-rw-r--r--gcc/testsuite/g++.dg/coroutines/torture/co-await-19-while-cond.C68
-rw-r--r--gcc/testsuite/g++.dg/coroutines/torture/co-await-20-do-while-cond.C68
-rw-r--r--gcc/testsuite/g++.dg/coroutines/torture/co-await-21-switch-value.C63
-rw-r--r--gcc/testsuite/g++.dg/coroutines/torture/co-await-22-truth-and-of-if.C81
-rw-r--r--gcc/testsuite/g++.dg/coroutines/torture/co-ret-16-simple-control-flow.C49
9 files changed, 864 insertions, 10 deletions
diff --git a/gcc/cp/ChangeLog b/gcc/cp/ChangeLog
index a197f00..fa150e3 100644
--- a/gcc/cp/ChangeLog
+++ b/gcc/cp/ChangeLog
@@ -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
diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc
index b1d91f8..bec165b 100644
--- a/gcc/cp/coroutines.cc
+++ b/gcc/cp/coroutines.cc
@@ -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 ();
diff --git a/gcc/testsuite/ChangeLog b/gcc/testsuite/ChangeLog
index b287e12..249a152 100644
--- a/gcc/testsuite/ChangeLog
+++ b/gcc/testsuite/ChangeLog
@@ -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
index 0000000..6b05cfb
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/torture/co-await-18-if-cond.C
@@ -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
index 0000000..2cd3761
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/torture/co-await-19-while-cond.C
@@ -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
index 0000000..bb1e97a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/torture/co-await-20-do-while-cond.C
@@ -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
index 0000000..b5e1bf3
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/torture/co-await-21-switch-value.C
@@ -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
index 0000000..5465974
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/torture/co-await-22-truth-and-of-if.C
@@ -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
index 0000000..6d4196d
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/torture/co-ret-16-simple-control-flow.C
@@ -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