[PATCH] coroutines : Handle for await expressions in for stmts [PR98480].
Iain Sandoe
iain@sandoe.co.uk
Mon Mar 15 00:22:30 GMT 2021
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
More information about the Gcc-patches
mailing list