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;
}
*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);
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
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)
{
/* 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);
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)
{
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. */
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.
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. */
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 ();