coroutines: Fix handling of conditional statements [PR94288]

Message ID E48C4682-B956-4A4B-8F39-EA27BB5CDD2A@sandoe.co.uk
State New
Headers show
Series
  • coroutines: Fix handling of conditional statements [PR94288]
Related show

Commit Message

Iain Sandoe April 20, 2020, 4:48 p.m.
Hi,

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.

This has been tested in various permutations (including without-
checking) on x86_64-darwin, now testing on x86/power Linux.

The testcases do not include the testcase from the PR since that
fails because of PR94661 (it can be included when that’s resolved).

The testcases are appended as a text file.

OK for master, assuming that regstraps on x86/power Linux are OK?
thanks
Iain


gcc/cp/ChangeLog:

2020-04-20  Iain Sandoe  <iain@sandoe.co.uk>

	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-20  Iain Sandoe  <iain@sandoe.co.uk>

	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/coroutines.cc | 433 ++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 423 insertions(+), 10 deletions(-)

-- 
2.24.1
From 86fb8e7583e99a61f6aa00c60d72bcadddc240be Mon Sep 17 00:00:00 2001
From: Iain Sandoe <iain@sandoe.co.uk>

Date: Sat, 18 Apr 2020 09:23:01 +0100
Subject: [PATCH] pr94228 tests

---
 .../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 +++++++++++
 6 files changed, 414 insertions(+)
 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/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.24.1

Comments

Nathan Sidwell April 23, 2020, 2:06 p.m. | #1
On 4/20/20 12:48 PM, Iain Sandoe wrote:
> Hi,

> 

> 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.

> 

> This has been tested in various permutations (including without-

> checking) on x86_64-darwin, now testing on x86/power Linux.

> 

> The testcases do not include the testcase from the PR since that

> fails because of PR94661 (it can be included when that’s resolved).

> 

> The testcases are appended as a text file.

> 

> OK for master, assuming that regstraps on x86/power Linux are OK?

> thanks

> Iain

> 

> 

> gcc/cp/ChangeLog:

> 

> 2020-04-20  Iain Sandoe  <iain@sandoe.co.uk>

> 

> 	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.


ok.

-- 
Nathan Sidwell

Patch

diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc
index 0a8e7521c4f..4e0d50a529f 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);
@@ -2586,12 +2583,14 @@  struct susp_frame_data
   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
@@ -2895,6 +2894,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<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)
 {
@@ -2904,6 +3075,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);
@@ -2911,7 +3085,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)
     {
@@ -2924,13 +3100,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<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.  */
@@ -3072,6 +3440,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<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.
@@ -3159,6 +3548,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.  */
 
@@ -3308,7 +3721,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<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 ();