coroutines : Handle for await expressions in for stmts [PR98480].

Message ID 10FEE342-6876-47F6-91CD-F4226E885A4C@sandoe.co.uk
State New
Headers show
Series
  • coroutines : Handle for await expressions in for stmts [PR98480].
Related show

Commit Message

Iain Sandoe March 15, 2021, 12:22 a.m.
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

-- 
2.24.1

Comments

Nathan Sidwell March 15, 2021, 11:49 a.m. | #1
On 3/14/21 8:22 PM, Iain Sandoe wrote:
> 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.

> 


ok


-- 
Nathan Sidwell

Patch

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;
+}