Tested on x86_64-darwin, powerpc64le-linux, pushed to trunk, thanks, Iain --- 8< ---
Although it is most common for the ramp function to see a return when a coroutine first suspends, there are other possibilities. For example all the awaits could be ready - effectively the coroutine will then run to completion and deallocation. Another case is where the first active suspension point causes the current routine to be cancelled and thence destroyed. These cases are tested here. gcc/testsuite/ChangeLog: * g++.dg/coroutines/torture/special-termination-00-sync-completion.C: New test. * g++.dg/coroutines/torture/special-termination-01-self-destruct.C: New test. Signed-off-by: Iain Sandoe <i...@sandoe.co.uk> --- .../special-termination-00-sync-completion.C | 127 +++++++++++++++++ .../special-termination-01-self-destruct.C | 129 ++++++++++++++++++ 2 files changed, 256 insertions(+) create mode 100644 gcc/testsuite/g++.dg/coroutines/torture/special-termination-00-sync-completion.C create mode 100644 gcc/testsuite/g++.dg/coroutines/torture/special-termination-01-self-destruct.C diff --git a/gcc/testsuite/g++.dg/coroutines/torture/special-termination-00-sync-completion.C b/gcc/testsuite/g++.dg/coroutines/torture/special-termination-00-sync-completion.C new file mode 100644 index 00000000000..21ce721d0ac --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/special-termination-00-sync-completion.C @@ -0,0 +1,127 @@ +// { dg-do run } +#include <coroutine> + +#ifndef OUTPUT +# define PRINT(X) +# define PRINTF(...) +#else +#include <stdio.h> +# define PRINT(X) puts(X) +# define PRINTF(...) printf(__VA_ARGS__) +#endif + +// coroutine management object with simple interlocks for the underlying +// coroutine. + +struct coro1 { + struct promise_type; + using handle_type = std::coroutine_handle<coro1::promise_type>; + + handle_type handle = nullptr; + + coro1 () : handle(0) {} + coro1 (handle_type _handle) + : handle(_handle) + { + PRINT("Created coro1 object from handle"); + // We're alive - let the promise object know so that it can notify us + // if the coroutine terminates before this object is destroyed. + handle.promise().set_owner (this); + } + + coro1 (const coro1 &) = delete; // no copying + + coro1 (coro1 &&s) : handle (s.handle) { + s.handle = nullptr; + PRINT("coro1 mv ctor "); + handle.promise().set_owner (this); + } + + coro1 &operator = (coro1 &&s) { + handle = s.handle; + s.handle = nullptr; + PRINT("coro1 op= "); + handle.promise().set_owner (this); + return *this; + } + + ~coro1() { + PRINT("Destroyed coro1"); + // We might come here before the coroutine has finished so... + if ( handle ) + { + handle.promise().set_owner (nullptr); + handle.destroy(); + } + } + + // Special awaiters. + struct suspend_always_prt { + bool await_ready() const noexcept { PRINT ("susp-always-is-not-ready") ; 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_never_prt { + bool await_ready () const noexcept { PRINT ("susp-never-is-ready") ; 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 () { PRINT ("susp-never-dtor"); } + }; + + struct promise_type { + + promise_type () : vv(-1) { PRINT ("Created Promise"); } + + promise_type (int __x) : vv(__x) { PRINTF ("Created Promise with %d\n",__x); } + + ~promise_type () { + PRINT ("Destroyed Promise"); + // The coroutine is about to become invalid - so remove the handle from the + // owner. + if (owner) + owner->handle = nullptr; + } + + // The coro1 ramp return object will be constructed from this. + auto get_return_object () { + PRINT ("get_return_object: handle from promise"); + return handle_type::from_promise (*this); + } + + auto initial_suspend () { return suspend_never_prt{}; } + auto final_suspend () noexcept { return suspend_never_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; } + void set_owner (coro1 *new_owner) { owner = new_owner; } + + private: + coro1 *owner = nullptr; + int vv; + }; +}; + +struct coro1 +finishes_synchronously (const int v) +{ + co_return v; +} + +int main () +{ + struct coro1 x = finishes_synchronously (42); + // the underlying coroutine is done and destroyed... and we should have + // signalled that by clearing coro1's handle. + if (x.handle) + __builtin_abort (); + // ... so we just clean up the return object here. + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/special-termination-01-self-destruct.C b/gcc/testsuite/g++.dg/coroutines/torture/special-termination-01-self-destruct.C new file mode 100644 index 00000000000..609b860ad02 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/special-termination-01-self-destruct.C @@ -0,0 +1,129 @@ +// { dg-do run } + +#include <coroutine> + +#ifndef OUTPUT +# define PRINT(X) +# define PRINTF(...) +#else +#include <stdio.h> +# define PRINT(X) puts(X) +# define PRINTF(...) printf(__VA_ARGS__) +#endif + +// coroutine management object with simple interlocks for the underlying +// coroutine. +struct coro1 { + struct promise_type; + using handle_type = std::coroutine_handle<coro1::promise_type>; + + handle_type handle = nullptr; + + coro1 () : handle(0) {} + coro1 (handle_type _handle) + : handle(_handle) + { + PRINT("Created coro1 object from handle"); + handle.promise().set_owner (this); + } + + coro1 (const coro1 &) = delete; // no copying + + coro1 (coro1 &&s) : handle (s.handle) { + s.handle = nullptr; + PRINT("coro1 mv ctor "); + handle.promise().set_owner (this); + } + + coro1 &operator = (coro1 &&s) { + handle = s.handle; + s.handle = nullptr; + PRINT("coro1 op= "); + handle.promise().set_owner (this); + return *this; + } + + ~coro1() { + PRINT("Destroyed coro1"); + // We might come here before the coroutine has finished so... + if ( handle ) + { + handle.promise().set_owner (nullptr); + handle.destroy(); + } + } + + // Special awaiters. + struct suspend_always_self_destr_prt { + bool await_ready() const noexcept { + PRINT ("susp-always-self-destr-is-not-ready") ; + return false; + } + void await_suspend(handle_type h) const noexcept { + PRINT ("susp-always-self-destr-susp - about to destroy"); + // destroy ourself... + h.destroy (); + } + void await_resume() const noexcept { PRINT ("susp-always-self-destr-resume");} + ~suspend_always_self_destr_prt () { PRINT ("susp-always-self-destr-dtor"); } + }; + + struct suspend_never_prt { + bool await_ready () const noexcept { PRINT ("susp-never-is-ready") ; 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 () { PRINT ("susp-never-dtor"); } + }; + + struct promise_type { + + promise_type () : vv(-1) { PRINT ("Created Promise"); } + promise_type (int __x) : vv(__x) { PRINTF ("Created Promise with %d\n",__x); } + promise_type (const promise_type &) = delete; // no copying + + ~promise_type () { + PRINT ("Destroyed Promise"); + if (owner) + owner->handle = nullptr; + } + + // The coro1 ramp return object will be constructed from this. + auto get_return_object () { + PRINT ("get_return_object: handle from promise"); + return handle_type::from_promise (*this); + } + + auto initial_suspend () { return suspend_always_self_destr_prt{}; } + auto final_suspend () noexcept { return suspend_never_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; } + void set_owner (coro1 *new_owner) { owner = new_owner; } + + private: + coro1 *owner = nullptr; + int vv; + }; +}; + +struct coro1 +self_destructs_before_doing_anything (const int v) +{ + co_return v; +} + +int main () +{ + struct coro1 x = self_destructs_before_doing_anything (42); + // the underlying coroutine is done and destroyed... + if (x.handle) + __builtin_abort (); + // ... so we just clean up the return object here. + return 0; +} -- 2.39.2 (Apple Git-143)