Commit dc192bbd by Iain Sandoe

coroutines: Amend parameter handling to match n4849.

In n4849 and preceding versions, [class.copy.elision] (1.3)
appears to confer additional permissions on coroutines to elide
parameter copies.

After considerable discussion on this topic by email and during
the February 2020 WG21 meeting, it has been determined that there
are no additional permissions applicable to coroutine parameter
copy elision.

The content of that clause in the standard is expected to be amended
eventually to clarify this.  Other than this, the handling of
parameter lifetimes is expected to be as per n4849:

 * A copy is made before the promise is constructed
 * If the promise CTOR uses the parms, then it should use the copy
   where appropriate.
 * The param copy lifetimes end after the promise is destroyed
   (during the coroutine frame destruction).
 * Otherwise, C++20 copy elision rules apply.

(as an aside) In practice, we expect that copy elision can only occur
when the coroutine body is fully inlined, possibly in conjunction with
heap allocation elision.

The patch:
 * Reorders the copying process to precede the promise CTOR and
    ensures the correct use.
 * Copies all params into the frame regardless of whether the coro
   body uses them (this is a bit unfortunate, and we should figure
   out an amendment for C++23).

gcc/cp/ChangeLog:

2020-02-26  Iain Sandoe  <iain@sandoe.co.uk>

	* class.c (classtype_has_non_deleted_copy_ctor): New.
	* coroutines.cc (struct param_info): Keep track of params
	that are references, and cache the original type and whether
	the DTOR is trivial.
	(build_actor_fn): Handle param copies always, and adjust the
	handling for references.
	(register_param_uses): Only handle uses here.
	(classtype_has_non_deleted_copy_ctor): New.
	(morph_fn_to_coro): Adjust param copy handling to match n4849
	by reordering ahead of the promise CTOR and always making a
	frame copy, even if the param is unused in the coroutine body.
	* cp-tree.h (classtype_has_non_deleted_copy_ctor): New.

gcc/testsuite/ChangeLog:

2020-02-26  Iain Sandoe  <iain@sandoe.co.uk>

	* g++.dg/coroutines/coro1-refs-and-ctors.h: New.
	* g++.dg/coroutines/torture/func-params-07.C: New test.
	* g++.dg/coroutines/torture/func-params-08.C: New test.
parent c5decc83
2020-02-26 Iain Sandoe <iain@sandoe.co.uk>
* class.c (classtype_has_non_deleted_copy_ctor): New.
* coroutines.cc (struct param_info): Keep track of params
that are references, and cache the original type and whether
the DTOR is trivial.
(build_actor_fn): Handle param copies always, and adjust the
handling for references.
(register_param_uses): Only handle uses here.
(classtype_has_non_deleted_copy_ctor): New.
(morph_fn_to_coro): Adjust param copy handling to match n4849
by reordering ahead of the promise CTOR and always making a
frame copy, even if the param is unused in the coroutine body.
* cp-tree.h (classtype_has_non_deleted_copy_ctor): New.
2020-02-26 Patrick Palka <ppalka@redhat.com>
* constraint.cc (finish_constraint_binary_op): Set expr's location range
......
......@@ -5447,6 +5447,19 @@ classtype_has_non_deleted_move_ctor (tree t)
return false;
}
/* True iff T has a copy constructor that is not deleted. */
bool
classtype_has_non_deleted_copy_ctor (tree t)
{
if (CLASSTYPE_LAZY_COPY_CTOR (t))
lazily_declare_fn (sfk_copy_constructor, t);
for (ovl_iterator iter (CLASSTYPE_CONSTRUCTORS (t)); iter; ++iter)
if (copy_fn_p (*iter) && !DECL_DELETED_FN (*iter))
return true;
return false;
}
/* If T, a class, has a user-provided copy constructor, copy assignment
operator, or destructor, returns that function. Otherwise, null. */
......
......@@ -6439,6 +6439,7 @@ extern bool type_has_constexpr_destructor (tree);
extern bool type_has_virtual_destructor (tree);
extern bool classtype_has_move_assign_or_move_ctor_p (tree, bool user_declared);
extern bool classtype_has_non_deleted_move_ctor (tree);
extern bool classtype_has_non_deleted_copy_ctor (tree);
extern tree classtype_has_depr_implicit_copy (tree);
extern bool classtype_has_op (tree, tree_code);
extern tree classtype_has_defaulted_op (tree, tree_code);
......
2020-02-26 Iain Sandoe <iain@sandoe.co.uk>
* g++.dg/coroutines/coro1-refs-and-ctors.h: New.
* g++.dg/coroutines/torture/func-params-07.C: New test.
* g++.dg/coroutines/torture/func-params-08.C: New test.
2020-02-26 Peter Bergner <bergner@linux.ibm.com>
PR target/93913
......
struct coro1 {
struct promise_type {
promise_type () : vv(-1) { PRINT ("Promise def. CTOR"); }
promise_type (int __x) : vv(__x) { PRINTF ("Created Promise with %d\n",__x); }
promise_type (int __x, int& __y, int&& __z)
: vv(__x), v2(__y), v3(__z)
{ PRINTF ("Created Promise with %d, %d, %d\n", __x, __y, __z); }
~promise_type() { PRINT ("Destroyed Promise"); }
auto get_return_object () {
PRINT ("get_return_object: handle from promise");
return handle_type::from_promise (*this);
}
auto initial_suspend () {
PRINT ("get initial_suspend (always)");
return suspend_always_prt{};
}
auto final_suspend () {
PRINT ("get final_suspend (always)");
return suspend_always_prt{};
}
#ifdef USE_AWAIT_TRANSFORM
auto await_transform (int v) {
PRINTF ("await_transform an int () %d\n",v);
return suspend_always_intprt (v);
}
auto await_transform (long v) {
PRINTF ("await_transform a long () %ld\n",v);
return suspend_always_longprtsq (v);
}
#endif
auto yield_value (int v) {
PRINTF ("yield_value (%d)\n", v);
vv = v;
return suspend_always_prt{};
}
void return_value (int v) {
PRINTF ("return_value (%d)\n", v);
vv = v;
}
void unhandled_exception() { PRINT ("** unhandled exception"); }
int get_value () { return vv; }
int get_v2 () { return v2; }
int get_v3 () { return v3; }
private:
int vv;
int v2;
int v3;
};
using handle_type = coro::coroutine_handle<coro1::promise_type>;
handle_type handle;
coro1 () : handle(0) {}
coro1 (handle_type _handle)
: handle(_handle) {
PRINT("Created coro1 object from handle");
}
coro1 (const coro1 &) = delete; // no copying
coro1 (coro1 &&s) : handle(s.handle) {
s.handle = nullptr;
PRINT("coro1 mv ctor ");
}
coro1 &operator = (coro1 &&s) {
handle = s.handle;
s.handle = nullptr;
PRINT("coro1 op= ");
return *this;
}
~coro1() {
PRINT("Destroyed coro1");
if ( handle )
handle.destroy();
}
// Some awaitables to use in tests.
// With progress printing for debug.
struct suspend_never_prt {
bool await_ready() const noexcept { return true; }
void await_suspend(handle_type) const noexcept { PRINT ("susp-never-susp");}
void await_resume() const noexcept { PRINT ("susp-never-resume");}
};
struct suspend_always_prt {
bool await_ready() const noexcept { return false; }
void await_suspend(handle_type) const noexcept { PRINT ("susp-always-susp");}
void await_resume() const noexcept { PRINT ("susp-always-resume");}
~suspend_always_prt() { PRINT ("susp-always-dtor"); }
};
struct suspend_always_intprt {
int x;
suspend_always_intprt() : x(5) {}
suspend_always_intprt(int __x) : x(__x) {}
~suspend_always_intprt() {}
bool await_ready() const noexcept { return false; }
void await_suspend(coro::coroutine_handle<>) const noexcept { PRINT ("susp-always-susp-intprt");}
int await_resume() const noexcept { PRINT ("susp-always-resume-intprt"); return x;}
};
/* This returns the square of the int that it was constructed with. */
struct suspend_always_longprtsq {
long x;
suspend_always_longprtsq() : x(12L) { PRINT ("suspend_always_longprtsq def ctor"); }
suspend_always_longprtsq(long _x) : x(_x) { PRINTF ("suspend_always_longprtsq ctor with %ld\n", x); }
~suspend_always_longprtsq() {}
bool await_ready() const noexcept { return false; }
void await_suspend(coro::coroutine_handle<>) const noexcept { PRINT ("susp-always-susp-longsq");}
long await_resume() const noexcept { PRINT ("susp-always-resume-longsq"); return x * x;}
};
struct suspend_always_intrefprt {
int& x;
suspend_always_intrefprt(int& __x) : x(__x) {}
~suspend_always_intrefprt() {}
bool await_ready() const noexcept { return false; }
void await_suspend(coro::coroutine_handle<>) const noexcept { PRINT ("susp-always-susp-intprt");}
int& await_resume() const noexcept { PRINT ("susp-always-resume-intprt"); return x;}
};
template <typename _AwaitType>
struct suspend_always_tmpl_awaiter {
_AwaitType x;
suspend_always_tmpl_awaiter(_AwaitType __x) : x(__x) {}
~suspend_always_tmpl_awaiter() {}
bool await_ready() const noexcept { return false; }
void await_suspend(coro::coroutine_handle<>) const noexcept { PRINT ("suspend_always_tmpl_awaiter");}
_AwaitType await_resume() const noexcept { PRINT ("suspend_always_tmpl_awaiter"); return x;}
};
};
// { dg-do run }
// Test that we copy simple parms correctly by value, reference or
// rvalue reference.
#include "../coro.h"
// boiler-plate for tests of codegen
#include "../coro1-refs-and-ctors.h"
coro1
my_coro (int v1, int& v2, int&& v3)
{
co_yield v1 + v2 + v3;
co_return v1 + v2 + v3;
}
int main ()
{
PRINT ("main: create coro1");
int lv = 1;
int lvr = 2;
coro1 x = my_coro (lv, lvr, lvr+2);
if (x.handle.done())
abort();
x.handle.resume();
PRINT ("main: after resume (initial suspend)");
/* Now we should have the co_yielded value. */
int y = x.handle.promise().get_value();
if ( y != 7 )
{
PRINTF ("main: wrong result (%d).", y);
abort ();
}
/* So we should be suspended at the yield, now change the
values so that we can determine that the reference persists
and the copy was made correctly. */
lv = 5; // should be ignored
lvr = 3; // should be enacted
x.handle.resume();
PRINT ("main: after resume (yield)");
/* Now we should have the co_returned value. */
y = x.handle.promise().get_value();
if ( y != 8 )
{
PRINTF ("main: wrong result (%d).", y);
abort ();
}
y = x.handle.promise().get_v2();
if ( y != 2 )
{
PRINTF ("main: wrong result 2 (%d).", y);
abort ();
}
y = x.handle.promise().get_v3();
if ( y != 4 )
{
PRINTF ("main: wrong result 3 (%d).", y);
abort ();
}
if (!x.handle.done())
{
PRINT ("main: apparently not done...");
abort ();
}
x.handle.destroy();
x.handle = NULL;
PRINT ("main: returning");
return 0;
}
// { dg-do run }
// Check that we correctly handle params with non-trivial DTORs and
// use the correct copy/move CTORs.
#include "../coro.h"
#include <vector>
// boiler-plate for tests of codegen
#include "../coro1-ret-int-yield-int.h"
int regular = 0;
int copy = 0;
int move = 0;
struct Foo {
Foo(int _v) : value(_v), x(1, _v)
{
regular++;
PRINTF ("FOO(%d)\n",_v);
}
Foo(const Foo& t)
{
value = t.value;
x = t.x;
copy++;
PRINTF ("FOO(&), %d\n",value);
}
Foo(Foo&& s)
{
value = s.value;
s.value = -1;
x = std::move(s.x);
s.x = std::vector<int> ();
move++;
PRINTF ("FOO(&&), %d\n", value);
}
~Foo() {PRINTF ("~FOO(), %d\n", value);}
auto operator co_await()
{
struct awaitable
{
int v;
awaitable (int _v) : v(_v) {}
bool await_ready() { return true; }
void await_suspend(coro::coroutine_handle<>) {}
int await_resume() { return v;}
};
return awaitable{value + x[0]};
};
void return_value(int _v) { value = _v;}
int value;
std::vector<int> x;
};
coro1
my_coro (Foo t_lv, Foo& t_ref, Foo&& t_rv_ref)
{
PRINT ("my_coro");
// We are created suspended, so correct operation depends on
// the parms being copied.
int sum = co_await t_lv;
PRINT ("my_coro 1");
sum += co_await t_ref;
PRINT ("my_coro 2");
sum += co_await t_rv_ref;
PRINT ("my_coro 3");
co_return sum;
}
int main ()
{
PRINT ("main: create coro1");
Foo thing (4);
coro1 x = my_coro (Foo (1), thing, Foo (2));
PRINT ("main: done ramp");
if (x.handle.done())
abort();
x.handle.resume();
PRINT ("main: after resume (initial suspend)");
// now do the three co_awaits.
while(!x.handle.done())
x.handle.resume();
PRINT ("main: after resuming 3 co_awaits");
/* Now we should have the co_returned value. */
int y = x.handle.promise().get_value();
if (y != 14)
{
PRINTF ("main: wrong result (%d).", y);
abort ();
}
if (regular != 3 || copy != 1 || move != 1)
{
PRINTF ("main: unexpected ctor use (R:%d, C:%d, M:%d)\n",
regular, copy, move);
abort ();
}
PRINT ("main: returning");
return 0;
}
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