Commit 3dbc7721 by Iain Sandoe

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  <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-23  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.
parent 7291b2ed
2020-04-23 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.
2020-04-22 Jonathan Wakely <jwakely@redhat.com> 2020-04-22 Jonathan Wakely <jwakely@redhat.com>
PR translation/94698 PR translation/94698
......
2020-04-23 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.
2020-04-23 Marek Polacek <polacek@redhat.com> 2020-04-23 Marek Polacek <polacek@redhat.com>
PR c++/94733 PR c++/94733
......
// { 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;
}
// { 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;
}
// { 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;
}
// { 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;
}
// { 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;
}
#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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment