From: Iain Sandoe Date: Thu, 16 Apr 2020 13:17:51 +0000 (+0100) Subject: coroutines: Back out mandate for tail-calls at O < 2 [PR94359] X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=d0ce5baeb642bab57252c687cdf0ffb522e7a2a3;p=gcc.git coroutines: Back out mandate for tail-calls at O < 2 [PR94359] For symmetric transfers to work with C++20 coroutines, it is currently necessary to tail call the callee coroutine from resume method of the caller coroutine. However there are several targets which don't support an indirect tail call to an arbitrary callee. Unfortunately, the target 'function_ok_for_sibcall' is not usable from the front end in all cases. While it is possible to add a new hook to cover this circumstance, it is too late in the release cycle to be sure of getting the setting correct for all targets. So, this patch backs out the use of function_ok_for_sibcall () and the mandate of CALL_EXPR_MUST_TAIL_CALL from the symmetric transfer. Targets that can make indirect tail calls to arbitrary callees will still be able to make use of the symmetric transfer (without risking overrunning the stack) for optimization levels >= 2. The draft standard does not mandate unlimited symmetric transfers, so removing this is a QOI issue (albeit an important one) rather than a correctness one. The test is moved and adjusted so that it can be opted into by any target that supports the necessary tailcall. gcc/cp/ChangeLog: 2020-04-16 Iain Sandoe PR c++/94359 * coroutines.cc (build_actor_fn): Back out use of targetm.function_ok_for_sibcall. Do not mark the resume call as CALL_EXPR_MUST_TAIL_CALL. gcc/testsuite/ChangeLog: 2020-04-16 Iain Sandoe PR c++/94359 * g++.dg/coroutines/torture/symmetric-transfer-00-basic.C: Move.. * g++.dg/coroutines/symmetric-transfer-00-basic.C: ..here and adjust to run at O2 for targets supporting the necessary tail call. --- diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc index e4ba642d527..0a8e7521c4f 100644 --- a/gcc/cp/coroutines.cc +++ b/gcc/cp/coroutines.cc @@ -2377,21 +2377,9 @@ build_actor_fn (location_t loc, tree coro_frame_type, tree actor, tree fnbody, (loc, builtin_decl_explicit (BUILT_IN_CORO_RESUME), 1, addr); /* In order to support an arbitrary number of coroutine continuations, - we must tail call them. However, some targets might not support this - for indirect calls, or calls between DSOs. - FIXME: see if there's an alternate strategy for such targets. */ - /* Now we have the actual call, and we can mark it as a tail. */ + we must tail call them. However, some targets do not support indirect + tail calls to arbitrary callees. See PR94359. */ CALL_EXPR_TAILCALL (resume) = true; - /* Temporarily, switch cfun so that we can use the target hook. */ - push_struct_function (actor); - if (targetm.function_ok_for_sibcall (NULL_TREE, resume)) - { - /* ... and for optimisation levels 0..1, which do not normally tail- - -call, mark it as requiring a tail-call for correctness. */ - if (optimize < 2) - CALL_EXPR_MUST_TAIL_CALL (resume) = true; - } - pop_cfun (); resume = coro_build_cvt_void_expr_stmt (resume, loc); add_stmt (resume); diff --git a/gcc/testsuite/g++.dg/coroutines/symmetric-transfer-00-basic.C b/gcc/testsuite/g++.dg/coroutines/symmetric-transfer-00-basic.C new file mode 100644 index 00000000000..b78ae20d9d4 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/symmetric-transfer-00-basic.C @@ -0,0 +1,116 @@ +// See PR94359, we will need either a general solution to this, or at least +// some hook for targets to opt in, for now the test will work on targets that +// can do the tailcall (which would normally be available for O2+) + +// { dg-do run { target { i?86-*-linux-gnu x86_64-*-linux-gnu *-*-darwin* } } } +// { dg-additional-options "-O2" } + +#if __has_include() + +#include +namespace coro = std; + +#elif __has_include() + +#include +namespace coro = std::experimental; + +#endif + +#if __clang__ +# include +#endif + +#include +#include +#include + +template +struct Loopy { + struct promise_type; + using handle_type = coro::coroutine_handle; + handle_type handle; + + struct promise_type { + // Cause the Loopy object to be created from the handle. + auto get_return_object () { + return handle_type::from_promise (*this); + } + + coro::suspend_never yield_value(T value) { + //fprintf (stderr, "%s yields %d\n", me, value); + current_value = value; + return coro::suspend_never{}; + } + + coro::suspend_always initial_suspend() { return {}; } + coro::suspend_always final_suspend() { return {}; } + + void unhandled_exception() { /*std::terminate();*/ } + void return_void() {} + + void set_peer (handle_type _p) { peer = _p; } + void set_me (const char *m) { me = m; } + + T get_value () {return current_value;} + T current_value; + handle_type peer; + const char *me; + }; + + Loopy () : handle(0) {} + Loopy (handle_type _handle) : handle(_handle) {} + Loopy (const Loopy &) = delete; + Loopy (Loopy &&s) : handle(s.handle) { s.handle = nullptr; } + ~Loopy() { if ( handle ) handle.destroy(); } + + struct loopy_awaiter { + handle_type p; + bool await_ready () { return false; } + /* continue the peer. */ + handle_type await_suspend (handle_type h) { + p = h.promise().peer; + return p; + } + T await_resume () { return p.promise().get_value (); } + }; +}; + +Loopy +pingpong (const char *id) +{ + int v = 0; + Loopy::loopy_awaiter aw{}; + for (;;) + { + co_yield (v+1); + //std::this_thread::sleep_for(std::chrono::milliseconds(500)); + if (v > 10'000'000) + break; + v = co_await aw; + //fprintf (stderr, "%s = %d\n", id, v); + } + //fprintf (stderr, "%s = %d\n", id, v); +} + +int main () +{ + // identify for fun.. + const char *ping_id = "ping"; + const char *pong_id = "pong"; + auto ping = pingpong (ping_id); // created suspended. + auto pong = pingpong (pong_id); + + // point them at each other... + ping.handle.promise().set_peer (pong.handle); + pong.handle.promise().set_peer (ping.handle); + + ping.handle.promise().set_me (ping_id); + pong.handle.promise().set_me (pong_id); + + // and start them ... + ping.handle.resume (); + pong.handle.resume (); + +} + diff --git a/gcc/testsuite/g++.dg/coroutines/torture/symmetric-transfer-00-basic.C b/gcc/testsuite/g++.dg/coroutines/torture/symmetric-transfer-00-basic.C deleted file mode 100644 index 6f379c8e77a..00000000000 --- a/gcc/testsuite/g++.dg/coroutines/torture/symmetric-transfer-00-basic.C +++ /dev/null @@ -1,117 +0,0 @@ -// { dg-do run } -// See PR94359 - some targets are unable to make general indirect tailcalls -// for example, between different DSOs. -// { dg-xfail-run-if "" { hppa*-*-hpux11* } } -// { dg-xfail-run-if "" { ia64-*-linux-gnu } } -// { dg-xfail-run-if "" { { lp64 && { powerpc*-linux-gnu } } || { *-*-aix* } } } -// { dg-xfail-run-if "" { sparc*-*-* } } - -#if __has_include() - -#include -namespace coro = std; - -#elif __has_include() - -#include -namespace coro = std::experimental; - -#endif - -#if __clang__ -# include -#endif - -#include -#include -#include - -template -struct Loopy { - struct promise_type; - using handle_type = coro::coroutine_handle; - handle_type handle; - - struct promise_type { - // Cause the Loopy object to be created from the handle. - auto get_return_object () { - return handle_type::from_promise (*this); - } - - coro::suspend_never yield_value(T value) { - //fprintf (stderr, "%s yields %d\n", me, value); - current_value = value; - return coro::suspend_never{}; - } - - coro::suspend_always initial_suspend() { return {}; } - coro::suspend_always final_suspend() { return {}; } - - void unhandled_exception() { /*std::terminate();*/ } - void return_void() {} - - void set_peer (handle_type _p) { peer = _p; } - void set_me (const char *m) { me = m; } - - T get_value () {return current_value;} - T current_value; - handle_type peer; - const char *me; - }; - - Loopy () : handle(0) {} - Loopy (handle_type _handle) : handle(_handle) {} - Loopy (const Loopy &) = delete; - Loopy (Loopy &&s) : handle(s.handle) { s.handle = nullptr; } - ~Loopy() { if ( handle ) handle.destroy(); } - - struct loopy_awaiter { - handle_type p; - bool await_ready () { return false; } - /* continue the peer. */ - handle_type await_suspend (handle_type h) { - p = h.promise().peer; - return p; - } - T await_resume () { return p.promise().get_value (); } - }; -}; - -Loopy -pingpong (const char *id) -{ - int v = 0; - Loopy::loopy_awaiter aw{}; - for (;;) - { - co_yield (v+1); - //std::this_thread::sleep_for(std::chrono::milliseconds(500)); - if (v > 10'000'000) - break; - v = co_await aw; - //fprintf (stderr, "%s = %d\n", id, v); - } - //fprintf (stderr, "%s = %d\n", id, v); -} - -int main () -{ - // identify for fun.. - const char *ping_id = "ping"; - const char *pong_id = "pong"; - auto ping = pingpong (ping_id); // created suspended. - auto pong = pingpong (pong_id); - - // point them at each other... - ping.handle.promise().set_peer (pong.handle); - pong.handle.promise().set_peer (ping.handle); - - ping.handle.promise().set_me (ping_id); - pong.handle.promise().set_me (pong_id); - - // and start them ... - ping.handle.resume (); - pong.handle.resume (); - -} -