Commit 68bb7e3b by Iain Sandoe

coroutines: Update to n4849 allocation/deallocation.

This updates the coroutine frame allocation and deallocation usage to
match n4849.

[dcl.fct.def.coroutine] /9, /10, /12.

9 An implementation may need to allocate additional storage for a coroutine.
This storage is known as the coroutine state and is obtained by calling a
non-array allocation function. The allocation function’s name is looked up
in the scope of the promise type. If this lookup fails, the allocation
function’s name is looked up in the global scope. If the lookup finds an
allocation function in the scope of the promise type, overload resolution
is performed on a function call created by assembling an argument list.
The first argument is the amount of space requested, and has type
std::size_t. The lvalues p1 . . . pn are the succeeding [user's function]
arguments. If no viable function is found, overload resolution is performed
again on a function call created by passing just the amount of space required
as an argument of type std::size_t.

10 The unqualified-id get_return_object_on_allocation_failure is looked up in
the scope of the promise type by class member access lookup. If any
declarations are found, then the result of a call to an allocation function
used to obtain storage for the coroutine state is assumed to return nullptr
if it fails to obtain storage, and if a global allocation function is
selected, the ::operator new(size_t, nothrow_t) form is used. The allocation
function used in this case shall have a non-throwing noexcept-specification.
If the allocation function returns nullptr, the coroutine returns control to
the caller of the coroutine and the return value is obtained by a call to
T::get_return_object_on_allocation_failure(), where T is the promise type.

12 The deallocation function’s name is looked up in the scope of the promise
type. If this lookup fails, the deallocation function’s name is looked up in
the global scope. If deallocation function lookup finds both a usual
deallocation function with only a pointer parameter and a usual deallocation
function with both a pointer parameter and a size parameter, then the
selected deallocation function shall be the one with two parameters.
Otherwise, the selected deallocation function shall be the function with one
parameter. If no usual deallocation function is found, the program is ill-
formed. The selected deallocation function shall be called with the address
of the block of storage to be reclaimed as its first argument. If a
deallocation function with a parameter of type std::size_t is used, the size
of the block is passed as the corresponding argument.

gcc/cp/ChangeLog:

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

	* coroutines.cc (build_actor_fn): Implement deallocation function
	selection per n4849, dcl.fct.def.coroutine bullet 12.
	(morph_fn_to_coro): Implement allocation function selection per
	n4849, dcl.fct.def.coroutine bullets 9 and 10.

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

	* g++.dg/coroutines/coro1-allocators.h: New.
	* g++.dg/coroutines/coro-bad-alloc-00-bad-op-new.C: New test.
	* g++.dg/coroutines/coro-bad-alloc-01-bad-op-del.C: New test.
	* g++.dg/coroutines/coro-bad-alloc-02-no-op-new-nt.C: New test.
	* g++.dg/coroutines/torture/alloc-00-gro-on-alloc-fail.C: Use new
	coro1-allocators.h header.
	* g++.dg/coroutines/torture/alloc-01-overload-newdel.C: Likewise.
	* g++.dg/coroutines/torture/alloc-02-fail-new-grooaf-check.C: New.
	* g++.dg/coroutines/torture/alloc-03-overload-new-1.C: New test.
	* g++.dg/coroutines/torture/alloc-04-overload-del-use-two-args.C:New.
parent 1cd9bef8
2020-02-12 Iain Sandoe <iain@sandoe.co.uk>
* coroutines.cc (build_actor_fn): Implement deallocation function
selection per n4849, dcl.fct.def.coroutine bullet 12.
(morph_fn_to_coro): Implement allocation function selection per
n4849, dcl.fct.def.coroutine bullets 9 and 10.
2020-02-12 Marek Polacek <polacek@redhat.com>
PR c++/93684 - ICE-on-invalid with broken attribute.
......
2020-02-12 Iain Sandoe <iain@sandoe.co.uk>
* g++.dg/coroutines/coro1-allocators.h: New.
* g++.dg/coroutines/coro-bad-alloc-00-bad-op-new.C: New test.
* g++.dg/coroutines/coro-bad-alloc-01-bad-op-del.C: New test.
* g++.dg/coroutines/coro-bad-alloc-02-no-op-new-nt.C: New test.
* g++.dg/coroutines/torture/alloc-00-gro-on-alloc-fail.C: Use new
coro1-allocators.h header.
* g++.dg/coroutines/torture/alloc-01-overload-newdel.C: Likewise.
* g++.dg/coroutines/torture/alloc-02-fail-new-grooaf-check.C: New.
* g++.dg/coroutines/torture/alloc-03-overload-new-1.C: New test.
* g++.dg/coroutines/torture/alloc-04-overload-del-use-two-args.C:New.
2020-02-12 Jakub Jelinek <jakub@redhat.com>
* gcc.target/powerpc/pr93122.c: Use -mprefixed instead of
......
// { dg-additional-options "-fsyntax-only -w" }
// check error for a bad overload of operator new.
#define BOGUS_OPNEW_CASE1
#include "coro1-allocators.h"
struct coro1
f () /* { dg-error {'operator new' is provided by 'std::__n4835::coroutine_traits<coro1>::promise_type' \{aka 'coro1::promise_type'\} but is not usable with the function signature 'coro1 f\(\)'} } */
{
co_return;
}
// { dg-additional-options "-fsyntax-only -w" }
// check error for a bad overload of operator delete.
#define BOGUS_OPDEL_CASE1
#include "coro1-allocators.h"
struct coro1
f () /* { dg-error {'operator delete' is provided by 'std::__n4835::coroutine_traits<coro1>::promise_type' \{aka 'coro1::promise_type'\} but is not usable with the function signature 'coro1 f\(\)'} } */
{
co_return;
}
// { dg-additional-options "-fsyntax-only -w" }
// check error for missing new (size, nothrow).
#define PROVIDE_NEW_SZT
#define PROVIDE_DEL_VP
#define PROVIDE_GROOAF
#include "coro1-allocators.h"
struct coro1
f () /* { dg-error {'coro1::promise_type::get_return_object_on_allocation_failure\(\)\(\)' is provided by 'std::__n4835::coroutine_traits<coro1>::promise_type' \{aka 'coro1::promise_type'\} but 'operator new' is not marked 'throw\(\)' or 'noexcept'} } */
{
co_return;
}
/* Include <coroutine> or the equivalent. */
#include "coro.h"
/* Allow for stand-alone testing with no headers installed. */
#if __has_include(<new>)
# include <new>
#else
/* Required when get_return_object_on_allocation_failure() is defined by
the promise. We need a no-throw new, and new etc. build the relevant
pieces here to avoid needing the headers in the test. */
namespace std {
struct nothrow_t {};
constexpr nothrow_t nothrow = {};
typedef __SIZE_TYPE__ size_t;
} // end namespace std
void* operator new(std::size_t, const std::nothrow_t&) noexcept;
void operator delete(void* __p, const std::nothrow_t&) noexcept;
#endif
/* Flags and counters so that we can test that the methods we expected
to be called, were called (and the correct number of times). */
#ifdef USE_FAILING_OP_NEW
extern int used_failing_new;
#endif
#if defined (PROVIDE_NEW_SZT) || defined (PROVIDE_NEW_SZT_INT)
extern int used_ovl_new;
#endif
#ifdef PROVIDE_NEW_SZT_NT
extern void *malloc (size_t);
extern int used_ovl_new_nt;
#endif
#ifdef PROVIDE_DEL_VP
extern int used_ovl_del;
#endif
#ifdef PROVIDE_DEL_VP_SZT
extern int used_ovl_del_2arg;
#endif
#ifdef PROVIDE_GROOAF
extern int used_grooaf;
#endif
struct coro1 {
struct promise_type;
using handle_type = coro::coroutine_handle<coro1::promise_type>;
handle_type handle;
coro1 () noexcept : handle(0) {}
coro1 (handle_type _handle) noexcept
: handle(_handle) {
PRINT("Created coro1 object from handle");
}
coro1 (const coro1 &) = delete; // no copying
coro1 (coro1 &&s) noexcept : handle(s.handle) {
s.handle = nullptr;
PRINT("coro1 mv ctor ");
}
coro1 &operator = (coro1 &&s) noexcept {
handle = s.handle;
s.handle = nullptr;
PRINT("coro1 op= ");
return *this;
}
~coro1() noexcept {
PRINT("Destroyed coro1");
if ( handle )
handle.destroy();
}
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");}
~suspend_never_prt() {};
};
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");}
};
struct promise_type {
promise_type() { PRINT ("Created Promise"); }
~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{};
}
void return_void () {
PRINT ("return_void ()");
}
void unhandled_exception() { PRINT ("** unhandled exception"); }
#ifdef USE_FAILING_OP_NEW
/* Provide an operator, that always fails. */
void *operator new (std::size_t sz) noexcept {
PRINT ("promise_type: used failing op new");
used_failing_new++;
return nullptr;
}
#endif
#ifdef PROVIDE_NEW_SZT
void *operator new (std::size_t sz) {
PRINT ("promise_type: used overloaded operator new");
used_ovl_new++;
return ::operator new(sz);
}
#endif
#ifdef PROVIDE_NEW_SZT_NT
void *operator new (std::size_t sz, const std::nothrow_t&) noexcept {
PRINT ("promise_type: used overloaded operator new NT");
return malloc (sz);
}
#endif
#ifdef PROVIDE_NEW_SZT_INT
void *operator new (std::size_t sz, int x) {
PRINT ("promise_type: used overloaded operator new with int arg");
used_ovl_new += x;
return ::operator new(sz);
}
#endif
#ifdef PROVIDE_DEL_VP
void operator delete (void *p) {
PRINT ("promise_type: used overloaded operator delete 1 arg");
used_ovl_del++;
return ::operator delete(p);
}
#endif
#ifdef PROVIDE_DEL_VP_SZT
void operator delete (void *p, std::size_t sz) {
PRINT ("promise_type: used overloaded operator delete 2 args");
used_ovl_del_2arg++;
return ::operator delete(p);
}
#endif
#ifdef BOGUS_OPNEW_CASE1
/* Provide an operator, but it doesn't match on overload. */
void *operator new (std::size_t sz, char *f) noexcept {
PRINT ("promise_type: used bogus op new");
return nullptr;
}
#endif
#ifdef BOGUS_OPDEL_CASE1
/* Provide an operator, but it doesn't match on overload. */
void operator delete (void *p, char *f) {
PRINT ("promise_type: used bogus overloaded operator delete");
}
#endif
#ifdef PROVIDE_GROOAF
static coro1 get_return_object_on_allocation_failure () noexcept {
PRINT ("alloc fail return");
used_grooaf++;
return coro1 (nullptr);
}
#endif
}; // promise
}; // coro1
// { dg-do run }
// check the code-gen for the failed alloc return.
/* check the code-gen for the failed alloc return.
Here we use an allocator that doesn't fail so that the code
is generated, but the regular path is run. */
#include "../coro.h"
#define PROVIDE_GROOAF
#include "../coro1-allocators.h"
#if __has_include(<new>)
# include <new>
#else
// Required when get_return_object_on_allocation_failure() is defined by
// the promise.
// we need a no-throw new, and new etc. build the relevant pieces here to
// avoid needing the headers in the test.
namespace std {
struct nothrow_t {};
constexpr nothrow_t nothrow = {};
typedef __SIZE_TYPE__ size_t;
} // end namespace std
void* operator new(std::size_t, const std::nothrow_t&) noexcept;
void operator delete(void* __p, const std::nothrow_t&) noexcept;
#endif
struct coro1 {
struct promise_type;
using handle_type = coro::coroutine_handle<coro1::promise_type>;
handle_type handle;
coro1 () noexcept : handle(0) {}
coro1 (handle_type _handle) noexcept
: handle(_handle) {
PRINT("Created coro1 object from handle");
}
coro1 (const coro1 &) = delete; // no copying
coro1 (coro1 &&s) noexcept : handle(s.handle) {
s.handle = nullptr;
PRINT("coro1 mv ctor ");
}
coro1 &operator = (coro1 &&s) noexcept {
handle = s.handle;
s.handle = nullptr;
PRINT("coro1 op= ");
return *this;
}
~coro1() noexcept {
PRINT("Destroyed coro1");
if ( handle )
handle.destroy();
}
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");}
~suspend_never_prt() {};
};
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");}
};
struct promise_type {
promise_type() { PRINT ("Created Promise"); }
~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{};
}
void return_void () {
PRINT ("return_void ()");
}
void unhandled_exception() { PRINT ("** unhandled exception"); }
static coro1 get_return_object_on_allocation_failure () noexcept;
}; // promise
}; // coro1
coro1 coro1::promise_type::
get_return_object_on_allocation_failure () noexcept {
PRINT ("alloc fail return");
return coro1 (nullptr);
}
int used_grooaf = 0;
struct coro1
f () noexcept
......@@ -103,6 +20,9 @@ int main ()
{
PRINT ("main: create coro1");
struct coro1 x = f ();
if (used_grooaf)
abort ();
PRINT ("main: got coro1 - resuming");
if (x.handle.done())
abort();
......
// { dg-do run }
// check codegen for overloaded operator new/delete.
// check codegen for overloaded simple operator new/delete.
#include "../coro.h"
#define PROVIDE_NEW_SZT
#define PROVIDE_DEL_VP
#include "../coro1-allocators.h"
int used_ovl_new = 0;
int used_ovl_del = 0;
struct coro1 {
struct promise_type;
using handle_type = coro::coroutine_handle<coro1::promise_type>;
handle_type handle;
coro1 () noexcept : handle(0) {}
coro1 (handle_type _handle) noexcept
: handle(_handle) {
PRINT("Created coro1 object from handle");
}
coro1 (const coro1 &) = delete; // no copying
coro1 (coro1 &&s) noexcept : handle(s.handle) {
s.handle = nullptr;
PRINT("coro1 mv ctor ");
}
coro1 &operator = (coro1 &&s) noexcept {
handle = s.handle;
s.handle = nullptr;
PRINT("coro1 op= ");
return *this;
}
~coro1() noexcept {
PRINT("Destroyed coro1");
if ( handle )
handle.destroy();
}
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");}
~suspend_never_prt() {};
};
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");}
};
struct promise_type {
promise_type() { PRINT ("Created Promise"); }
~promise_type() { PRINT ("Destroyed Promise"); }
void *operator new (std::size_t sz) {
PRINT ("promise_type: used overloaded operator new");
used_ovl_new++;
return ::operator new(sz);
}
void operator delete (void *p) {
PRINT ("promise_type: used overloaded operator delete");
used_ovl_del++;
return ::operator delete(p);
}
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{};
}
void return_void () {
PRINT ("return_void ()");
}
void unhandled_exception() { PRINT ("** unhandled exception"); }
}; // promise
}; // coro1
struct coro1
f () noexcept
{
......
// { dg-do run }
/* check the code-gen for the failed alloc return.
In this case, we use an operator new that always fails.
So the g-r-o-o-a-f should fire. */
#define PROVIDE_GROOAF
#define USE_FAILING_OP_NEW
#include "../coro1-allocators.h"
int used_grooaf = 0;
int used_failing_new = 0;
struct coro1
f () noexcept
{
PRINT ("coro1: about to return");
co_return;
}
int main ()
{
/* nest a scope so that we can check the counts. */
{
PRINT ("main: create coro1");
struct coro1 x = f ();
/* we don't expect to be able to do anything. */
if (used_failing_new != 1)
{
PRINT ("main: we should have used the failing op new");
abort ();
}
if (used_grooaf != 1)
{
PRINT ("main: we should have used the GROOAF");
abort ();
}
}
PRINT ("main: returning");
return 0;
}
// { dg-do run }
/* check codegen for overloaded simple operator new/delete.
here check that we prefer the overload that accounts the function
args. */
#define PROVIDE_NEW_SZT
#define PROVIDE_NEW_SZT_INT
#define PROVIDE_DEL_VP
#include "../coro1-allocators.h"
int used_ovl_new = 0;
int used_ovl_del = 0;
struct coro1
f (int v) noexcept
{
PRINT ("coro1: about to return");
co_return;
}
int main ()
{
// Nest a scope so that we can inspect the flags after the DTORs run.
{
PRINT ("main: create coro1");
struct coro1 x = f (5);
if (used_ovl_new != 5)
{
PRINT ("main: apparently used the wrong op new...");
abort ();
}
PRINT ("main: got coro1 - resuming");
if (x.handle.done())
abort();
x.handle.resume();
PRINT ("main: after resume");
if (!x.handle.done())
{
PRINT ("main: apparently not done...");
abort ();
}
}
if (used_ovl_del != 1)
{
PRINT ("main: failed to call overloaded operator delete");
abort ();
}
PRINT ("main: returning");
return 0;
}
// { dg-do run }
/* check that we use the deallocation function with two args when both
are available. */
#define PROVIDE_NEW_SZT
#define PROVIDE_DEL_VP
#define PROVIDE_DEL_VP_SZT
#include "../coro1-allocators.h"
int used_ovl_new = 0;
int used_ovl_del = 0;
int used_ovl_del_2arg = 0;
struct coro1
f (int v) noexcept
{
PRINT ("coro1: about to return");
co_return;
}
int main ()
{
// Nest a scope so that we can inspect the flags after the DTORs run.
{
PRINT ("main: create coro1");
struct coro1 x = f (5);
if (used_ovl_new != 1)
{
PRINT ("main: apparently used the wrong op new...");
abort ();
}
PRINT ("main: got coro1 - resuming");
if (x.handle.done())
abort();
x.handle.resume();
PRINT ("main: after resume");
if (!x.handle.done())
{
PRINT ("main: apparently not done...");
abort ();
}
}
if (used_ovl_del != 0)
{
PRINT ("main: wrong call to overloaded operator delete 1 arg");
abort ();
}
if (used_ovl_del_2arg != 1)
{
PRINT ("main: failed to call overloaded operator delete 2 args");
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