Hi Apparently, I had a brainstorm when posting patches to cover the cases with await expressions in do {} while; and while {} ; and omitted the for loop case.
Fixed thus. tested on x86_64-darwin, x86_64-linux-gnu and with cppcoro and folly/coroutines. OK for master / 10.x? thanks Iain gcc/cp/ChangeLog: * coroutines.cc (replace_continue): Rewrite continue into 'goto label'. (await_statement_walker): Handle await expressions in the initializer, condition and iteration expressions of for loops. gcc/testsuite/ChangeLog: * g++.dg/coroutines/pr98480.C: New test. * g++.dg/coroutines/torture/co-await-24-for-init.C: New test. * g++.dg/coroutines/torture/co-await-25-for-condition.C: New test. * g++.dg/coroutines/torture/co-await-26-for-iteration-expr.C: New test. --- gcc/cp/coroutines.cc | 126 ++++++++++++++++++ gcc/testsuite/g++.dg/coroutines/pr98480.C | 20 +++ .../coroutines/torture/co-await-24-for-init.C | 101 ++++++++++++++ .../torture/co-await-25-for-condition.C | 94 +++++++++++++ .../torture/co-await-26-for-iteration-expr.C | 87 ++++++++++++ 5 files changed, 428 insertions(+) create mode 100644 gcc/testsuite/g++.dg/coroutines/pr98480.C create mode 100644 gcc/testsuite/g++.dg/coroutines/torture/co-await-24-for-init.C create mode 100644 gcc/testsuite/g++.dg/coroutines/torture/co-await-25-for-condition.C create mode 100644 gcc/testsuite/g++.dg/coroutines/torture/co-await-26-for-iteration-expr.C diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc index 712431583d5..438538a0b4f 100644 --- a/gcc/cp/coroutines.cc +++ b/gcc/cp/coroutines.cc @@ -3431,6 +3431,50 @@ coro_build_add_if_not_cond_break (tree cond) finish_if_stmt (if_stmt); } +/* Tree walk callback to replace continue statements with goto label. */ +static tree +replace_continue (tree *stmt, int *do_subtree, void *d) +{ + tree expr = *stmt; + if (TREE_CODE (expr) == CLEANUP_POINT_EXPR) + expr = TREE_OPERAND (expr, 0); + if (CONVERT_EXPR_P (expr) && VOID_TYPE_P (expr)) + expr = TREE_OPERAND (expr, 0); + STRIP_NOPS (expr); + if (!STATEMENT_CLASS_P (expr)) + return NULL_TREE; + + switch (TREE_CODE (expr)) + { + /* Unless it's a special case, just walk the subtrees as usual. */ + default: return NULL_TREE; + + case CONTINUE_STMT: + { + tree *label = (tree *)d; + location_t loc = EXPR_LOCATION (expr); + /* re-write a continue to goto label. */ + *stmt = build_stmt (loc, GOTO_EXPR, *label); + *do_subtree = 0; + return NULL_TREE; + } + + /* Statements that do not require recursion. */ + case DECL_EXPR: + case BREAK_STMT: + case GOTO_EXPR: + case LABEL_EXPR: + case CASE_LABEL_EXPR: + case ASM_EXPR: + /* These must break recursion. */ + case FOR_STMT: + case WHILE_STMT: + case DO_STMT: + *do_subtree = 0; + return NULL_TREE; + } +} + /* Tree walk callback to analyze, register and pre-process statements that contain await expressions. */ @@ -3534,6 +3578,88 @@ await_statement_walker (tree *stmt, int *do_subtree, void *d) return res; } break; + case FOR_STMT: + { + /* for loops only need special treatment if the condition or the + iteration expression contain a co_await. */ + tree for_stmt = *stmt; + /* Sanity check. */ + if ((res = cp_walk_tree (&FOR_INIT_STMT (for_stmt), + analyze_expression_awaits, d, &visited))) + return res; + gcc_checking_assert (!awpts->saw_awaits); + + if ((res = cp_walk_tree (&FOR_COND (for_stmt), + analyze_expression_awaits, d, &visited))) + return res; + bool for_cond_await = awpts->saw_awaits != 0; + unsigned save_awaits = awpts->saw_awaits; + + if ((res = cp_walk_tree (&FOR_EXPR (for_stmt), + analyze_expression_awaits, d, &visited))) + return res; + bool for_expr_await = awpts->saw_awaits > save_awaits; + + /* If the condition has an await, then we will need to rewrite the + loop as + for (init expression;true;iteration expression) { + condition = await expression; + if (condition) + break; + ... + } + */ + if (for_cond_await) + { + tree insert_list = push_stmt_list (); + /* This will be expanded when the revised body is handled. */ + coro_build_add_if_not_cond_break (FOR_COND (for_stmt)); + /* .. add the original for body. */ + add_stmt (FOR_BODY (for_stmt)); + /* To make the new for body. */ + FOR_BODY (for_stmt) = pop_stmt_list (insert_list); + FOR_COND (for_stmt) = boolean_true_node; + } + /* If the iteration expression has an await, it's a bit more + tricky. + for (init expression;condition;) { + ... + iteration_expr_label: + iteration expression with await; + } + but, then we will need to re-write any continue statements into + 'goto iteration_expr_label:'. + */ + if (for_expr_await) + { + location_t sloc = EXPR_LOCATION (FOR_EXPR (for_stmt)); + tree insert_list = push_stmt_list (); + /* The original for body. */ + add_stmt (FOR_BODY (for_stmt)); + char *buf = xasprintf ("for.iter.expr.%u", awpts->cond_number++); + tree it_expr_label + = create_named_label_with_ctx (sloc, buf, NULL_TREE); + free (buf); + add_stmt (build_stmt (sloc, LABEL_EXPR, it_expr_label)); + add_stmt (FOR_EXPR (for_stmt)); + FOR_EXPR (for_stmt) = NULL_TREE; + FOR_BODY (for_stmt) = pop_stmt_list (insert_list); + /* rewrite continue statements to goto label. */ + hash_set<tree> visited_continue; + if ((res = cp_walk_tree (&FOR_BODY (for_stmt), + replace_continue, &it_expr_label, &visited_continue))) + return res; + } + + /* So now walk the body statement (list), if there were no await + expressions, then this handles the original body - and either + way we will have finished with this statement. */ + res = cp_walk_tree (&FOR_BODY (for_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 diff --git a/gcc/testsuite/g++.dg/coroutines/pr98480.C b/gcc/testsuite/g++.dg/coroutines/pr98480.C new file mode 100644 index 00000000000..2d872613dac --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/pr98480.C @@ -0,0 +1,20 @@ +#include <coroutine> + +struct future { + struct promise_type { + void return_value(int) {} + auto initial_suspend() { return std::suspend_never{}; } + auto final_suspend() noexcept { return std::suspend_never{}; } + void unhandled_exception() {} + future get_return_object() { return {}; } + }; + bool await_ready() { return true; } + void await_suspend(std::coroutine_handle<>) {} + int await_resume() { return 0; } +}; + +future co_foo() { + for( int i = 0; i < co_await future{}; ++i ); + // ICE -------------^ + co_return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-await-24-for-init.C b/gcc/testsuite/g++.dg/coroutines/torture/co-await-24-for-init.C new file mode 100644 index 00000000000..1bf2f6d912d --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-await-24-for-init.C @@ -0,0 +1,101 @@ +// { 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 +coro_a (bool t) +{ + int accum = 0; + for (int x = co_await IntAwaiter (3); x < 10; x++) + accum += x; + + co_return accum; +} + +struct coro1 +coro_b (bool t) +{ + int accum = 0; + int x; + for (x = co_await IntAwaiter (3); x < 10; x++) + accum += x; + + co_return accum; +} + +struct coro1 +coro_c (bool t) +{ + int accum = 0; + int x = 3; + for (co_await IntAwaiter (3); x < 10; x++) + accum += x; + + co_return accum; +} + +void +check_a_coro (struct coro1& x) +{ + if (x.handle.done()) + { + PRINT ("check_a_coro: apparently done when we shouldn't be..."); + abort (); + } + + PRINT ("check_a_coro: resume initial suspend"); + x.handle.resume(); + + // will be false - so no yield expected. + PRINT ("check_a_coro: resume for init"); + x.handle.resume(); + + int y = x.handle.promise().get_value(); + if ( y != 42 ) + { + PRINTF ("check_a_coro: apparently wrong value : %d\n", y); + abort (); + } + + if (!x.handle.done()) + { + PRINT ("check_a_coro: apparently not done..."); + abort (); + } +} + +int main () +{ + { + struct coro1 x = coro_a (false); + check_a_coro (x); + } + + { + struct coro1 x = coro_b (false); + check_a_coro (x); + } + + { + struct coro1 x = coro_c (false); + check_a_coro (x); + } + + PRINT ("main: done"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-await-25-for-condition.C b/gcc/testsuite/g++.dg/coroutines/torture/co-await-25-for-condition.C new file mode 100644 index 00000000000..2208e341574 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-await-25-for-condition.C @@ -0,0 +1,94 @@ +// { 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 +coro_a (bool t) +{ + int accum = 0; + for (int x = 3; x < co_await IntAwaiter (10); x++) + accum += x; + + co_return accum; +} + +/* An awaiter that suspends always and returns an int as the + await_resume output. */ +struct TenAwaiter { + int v; + TenAwaiter (int _v) : v(_v) {} + bool await_ready () { return false; } + void await_suspend (coro::coroutine_handle<>) {} + bool await_resume () { return v < 10; } +}; + +struct coro1 +coro_b (bool t) +{ + int accum = 0; + for (int x = 3; co_await TenAwaiter (x); x++) + accum += x; + + co_return accum; +} + +void +check_a_coro (struct coro1& x) +{ + if (x.handle.done()) + { + PRINT ("check_a_coro: apparently done when we shouldn't be..."); + abort (); + } + + PRINT ("check_a_coro: resume initial suspend"); + x.handle.resume(); + + // will be false - so no yield expected. + PRINT ("check_a_coro: resume loops"); + while (!x.handle.done()) + x.handle.resume(); + + int y = x.handle.promise().get_value(); + if ( y != 42 ) + { + PRINTF ("check_a_coro: apparently wrong value : %d\n", y); + abort (); + } + + if (!x.handle.done()) + { + PRINT ("check_a_coro: apparently not done..."); + abort (); + } +} + +int main () +{ + { + struct coro1 x = coro_a (false); + check_a_coro (x); + } + { + struct coro1 x = coro_b (false); + check_a_coro (x); + } + + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-await-26-for-iteration-expr.C b/gcc/testsuite/g++.dg/coroutines/torture/co-await-26-for-iteration-expr.C new file mode 100644 index 00000000000..f361fb5ecea --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-await-26-for-iteration-expr.C @@ -0,0 +1,87 @@ +// { 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; } +}; + +coro1 +coro_a (bool t) +{ + int accum = 0; + for (int x = 3; x < 10; x += co_await IntAwaiter (1)) + accum += x; + + co_return accum; +} + +coro1 +coro_b (bool t) +{ + int accum = 0; + for (int x = 3; x < 10; x += co_await IntAwaiter (1)) + { + if (x & 1) + continue; + accum += x; + } + + co_return accum; +} + +void check_a_coro (coro1& x, int expected_answer) +{ + if (x.handle.done()) + { + PRINT ("check_a_coro: apparently done when we shouldn't be..."); + abort (); + } + + PRINT ("check_a_coro: resume initial suspend"); + x.handle.resume(); + + // will be false - so no yield expected. + PRINT ("check_a_coro: resume for init"); + while (!x.handle.done()) + x.handle.resume(); + + int y = x.handle.promise().get_value(); + if ( y != expected_answer ) + { + PRINTF ("check_a_coro: apparently wrong value : %d\n", y); + abort (); + } + + if (!x.handle.done()) + { + PRINT ("check_a_coro: apparently not done..."); + abort (); + } +} + +int main () +{ + { + coro1 x = coro_a (false); + check_a_coro (x, 42); + } + { + coro1 x = coro_b (false); + check_a_coro (x, 18); + } + + PRINT ("main: done"); + return 0; +} -- 2.24.1