From 3dbc772128e944819b09e21021d4fcd5dc17f2d4 Mon Sep 17 00:00:00 2001 From: Iain Sandoe Date: Thu, 23 Apr 2020 16:59:00 +0100 Subject: [PATCH] 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 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 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 | 17 + gcc/cp/coroutines.cc | 433 +++++++++++++++++- gcc/testsuite/ChangeLog | 10 + .../coroutines/torture/co-await-18-if-cond.C | 85 ++++ .../torture/co-await-19-while-cond.C | 68 +++ .../torture/co-await-20-do-while-cond.C | 68 +++ .../torture/co-await-21-switch-value.C | 63 +++ .../torture/co-await-22-truth-and-of-if.C | 81 ++++ .../torture/co-ret-16-simple-control-flow.C | 49 ++ 9 files changed, 864 insertions(+), 10 deletions(-) create mode 100644 gcc/testsuite/g++.dg/coroutines/torture/co-await-18-if-cond.C create mode 100644 gcc/testsuite/g++.dg/coroutines/torture/co-await-19-while-cond.C create mode 100644 gcc/testsuite/g++.dg/coroutines/torture/co-await-20-do-while-cond.C create mode 100644 gcc/testsuite/g++.dg/coroutines/torture/co-await-21-switch-value.C create mode 100644 gcc/testsuite/g++.dg/coroutines/torture/co-await-22-truth-and-of-if.C create mode 100644 gcc/testsuite/g++.dg/coroutines/torture/co-ret-16-simple-control-flow.C diff --git a/gcc/cp/ChangeLog b/gcc/cp/ChangeLog index a197f00f63c..fa150e3462f 100644 --- a/gcc/cp/ChangeLog +++ b/gcc/cp/ChangeLog @@ -1,3 +1,20 @@ +2020-04-23 Iain Sandoe + + 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 PR translation/94698 diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc index b1d91f84cae..bec165b6ec6 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 *block_stack; /* Track block scopes. */ vec *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 captured_temps; /* The suspend captured these temps. */ vec *to_replace; /* The VAR decls to replace. */ + hash_set *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 *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 visited; + awpts->saw_awaits = 0; + hash_set 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 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 (), NULL, 0, false}; + hash_set (), 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 b287e12588f..249a1520581 100644 --- a/gcc/testsuite/ChangeLog +++ b/gcc/testsuite/ChangeLog @@ -1,3 +1,13 @@ +2020-04-23 Iain Sandoe + + 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 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 00000000000..6b05cfb44ac --- /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 00000000000..2cd37616aaa --- /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 00000000000..bb1e97a6ef0 --- /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 00000000000..b5e1bf38050 --- /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 00000000000..54659741cbe --- /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 00000000000..6d4196d64f0 --- /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 -- 2.43.5