From: Arsen Arsenović Date: Fri, 23 Aug 2024 18:19:05 +0000 (+0200) Subject: c++/coros: do not assume coros don't nest [PR113457] X-Git-Tag: releases/gcc-14.3.0~70 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=805b9914846a1228b84a3c3dd8f9ba2f21f9ff59;p=thirdparty%2Fgcc.git c++/coros: do not assume coros don't nest [PR113457] In the testcase presented in the PR, during template expansion, an tsubst of an operand causes a lambda coroutine to be processed, causing it to get an initial suspend and final suspend. The code for assigning awaitable var names (get_awaitable_var) assumed that the sequence Is -> Is -> Fs -> Fs is impossible (i.e. that one could only 'open' one coroutine before closing it at a time), and reset the counter used for unique numbering each time a final suspend occured. This assumption is false in a few cases, usually when lambdas are involved. Instead of storing this counter in a static-storage variable, we can store it in coroutine_info. This struct is local to each function, so we don't need to worry about "cross-contamination" nor resetting. PR c++/113457 gcc/cp/ChangeLog: * coroutines.cc (struct coroutine_info): Add integer field awaitable_number. This is a counter used for assigning unique names to awaitable temporaries. (get_awaitable_var): Use awaitable_number from coroutine_info instead of the static int awn. gcc/testsuite/ChangeLog: * g++.dg/coroutines/pr113457-1.C: New test. * g++.dg/coroutines/pr113457.C: New test. (cherry picked from commit 5cca7517c5868b7b9aa13992145eb6082ac5d5b9) --- diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc index ee0fbc454e6..953297c3c49 100644 --- a/gcc/cp/coroutines.cc +++ b/gcc/cp/coroutines.cc @@ -95,6 +95,10 @@ struct GTY((for_user)) coroutine_info tree return_void; /* The expression for p.return_void() if it exists. */ location_t first_coro_keyword; /* The location of the keyword that made this function into a coroutine. */ + + /* Temporary variable number assigned by get_awaitable_var. */ + int awaitable_number = 0; + /* Flags to avoid repeated errors for per-function issues. */ bool coro_ret_type_error_emitted; bool coro_promise_error_emitted; @@ -981,15 +985,18 @@ enum suspend_point_kind { static tree get_awaitable_var (suspend_point_kind suspend_kind, tree v_type) { - static int awn = 0; + auto cinfo = get_coroutine_info (current_function_decl); + gcc_checking_assert (cinfo); char *buf; switch (suspend_kind) { - default: buf = xasprintf ("Aw%d", awn++); break; - case CO_YIELD_SUSPEND_POINT: buf = xasprintf ("Yd%d", awn++); break; - case INITIAL_SUSPEND_POINT: buf = xasprintf ("Is"); break; - case FINAL_SUSPEND_POINT: buf = xasprintf ("Fs"); awn = 0; break; - } + default: buf = xasprintf ("Aw%d", cinfo->awaitable_number++); break; + case CO_YIELD_SUSPEND_POINT: + buf = xasprintf ("Yd%d", cinfo->awaitable_number++); + break; + case INITIAL_SUSPEND_POINT: buf = xasprintf ("Is"); break; + case FINAL_SUSPEND_POINT: buf = xasprintf ("Fs"); break; + } tree ret = get_identifier (buf); free (buf); ret = build_lang_decl (VAR_DECL, ret, v_type); diff --git a/gcc/testsuite/g++.dg/coroutines/pr113457-1.C b/gcc/testsuite/g++.dg/coroutines/pr113457-1.C new file mode 100644 index 00000000000..fcf67e15271 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/pr113457-1.C @@ -0,0 +1,25 @@ +// https://gcc.gnu.org/PR113457 +#include + +struct coro +{ + struct promise_type + { + std::suspend_never initial_suspend (); + std::suspend_never final_suspend () noexcept; + void return_void (); + void unhandled_exception (); + coro get_return_object (); + }; +}; + +struct not_quite_suspend_never : std::suspend_never +{}; + +coro +foo () +{ + co_await std::suspend_never{}, + [] () -> coro { co_return; }, + co_await not_quite_suspend_never{}; +} diff --git a/gcc/testsuite/g++.dg/coroutines/pr113457.C b/gcc/testsuite/g++.dg/coroutines/pr113457.C new file mode 100644 index 00000000000..add45a2f892 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/pr113457.C @@ -0,0 +1,178 @@ +// https://gcc.gnu.org/PR113457 +namespace std { +template _Up __declval(int); +template auto declval() noexcept -> decltype(__declval<_Tp>(0)); +template struct remove_cv { + using type = __remove_cv(_Tp); +}; +template using remove_cv_t = typename remove_cv<_Tp>::type; +template struct remove_reference { + using type = __remove_reference(_Tp); +}; +template +using remove_reference_t = typename remove_reference<_Tp>::type; +template inline constexpr bool is_array_v = __is_array(_Tp); +template struct remove_cvref {}; +namespace ranges { +} // namespace ranges +template +[[__nodiscard__]] constexpr typename std::remove_reference<_Tp>::type && +move(_Tp &&__t) noexcept { + return static_cast::type &&>(__t); +} +template struct iterator_traits; +namespace __detail { +template struct __iter_traits_impl { + using type = iterator_traits<_Iter>; +}; +template +using __iter_traits = typename __iter_traits_impl<_Iter, _Tp>::type; +} // namespace __detail +template struct indirectly_readable_traits {}; +namespace __detail { +template +using __iter_value_t = + typename __iter_traits<_Tp, indirectly_readable_traits<_Tp>>::value_type; +} +template +using iter_value_t = __detail::__iter_value_t<_Tp>; +namespace ranges::__access { +template auto __begin(_Tp &__t) { + return __t + 0; +} +} // namespace ranges::__access +namespace __detail { +template +using __range_iter_t = + decltype(ranges::__access::__begin(std::declval<_Tp &>())); +} +template struct iterator_traits<_Tp *> { + using value_type = remove_cv_t<_Tp>; +}; + namespace ranges { + namespace __access { + struct _Begin { + template + constexpr auto operator() [[nodiscard]] (_Tp &&__t) const + { + if constexpr (is_array_v>) + { + return __t + 0; + } + } + }; + } // namespace __access + inline namespace _Cpo { + inline constexpr ranges::__access::_Begin begin{}; + } // namespace _Cpo + template + concept range = requires(_Tp &__t) { ranges::begin(__t); }; + template using iterator_t = std::__detail::__range_iter_t<_Tp>; + template + using range_value_t = iter_value_t>; + template + struct elements_of { + _Range range; + }; + template + elements_of(_Range &&) -> elements_of<_Range &&>; + } // namespace ranges + inline namespace __n4861 { + template + struct __coroutine_traits_impl {}; + template struct __coroutine_traits_impl<_Result, void> { + using promise_type = typename _Result::promise_type; + }; + template + struct coroutine_traits : __coroutine_traits_impl<_Result> {}; + template struct coroutine_handle; + template struct coroutine_handle { + constexpr void *address() const noexcept { return _M_fr_ptr; } + constexpr static coroutine_handle from_address(void *__a) noexcept { + coroutine_handle __self; + return __self; + } + constexpr operator coroutine_handle<>() const noexcept { + return coroutine_handle<>::from_address(address()); + } + void *_M_fr_ptr = nullptr; + }; + struct suspend_always { + constexpr bool await_ready() const noexcept { return false; } + constexpr void await_suspend(coroutine_handle<>) const noexcept {} + constexpr void await_resume() const noexcept {} + }; + } // namespace __n4861 + template + class generator; + namespace __gen { + template class _Promise_erased { + template struct _Recursive_awaiter; + struct _Final_awaiter; + public: + suspend_always initial_suspend() const noexcept; + suspend_always yield_value(_Yielded __val) noexcept; + template + auto yield_value( + ranges::elements_of &&> __r) noexcept { + return _Recursive_awaiter{std::move(__r.range)}; + } + template + auto yield_value(ranges::elements_of<_R> __r) noexcept { + auto __n = []( ranges::iterator_t<_R> __i) + -> generator<_Yielded, ranges::range_value_t<_R>> + { + co_yield static_cast<_Yielded>(*__i); + }; + return + yield_value + ( + ranges::elements_of + ( + __n + ( ranges::begin(__r.range)))); + } + _Final_awaiter final_suspend() noexcept; + void unhandled_exception() {} + }; + template + struct _Promise_erased<_Yielded>::_Final_awaiter { + bool await_ready() noexcept; + template + void await_suspend(std::coroutine_handle<_Promise> __c) noexcept {} + void await_resume() noexcept {} + }; + template + template + struct _Promise_erased<_Yielded>::_Recursive_awaiter { + _Gen _M_gen; + constexpr bool await_ready() const noexcept { return false; } + template + void + await_suspend(std::coroutine_handle<_Promise> __p) noexcept {} + void await_resume() {} + }; + } // namespace __gen + template + struct generator + { + struct promise_type + : __gen::_Promise_erased + { + generator get_return_object() noexcept; + }; + }; +} // namespace std +using namespace std; +template +auto concat(Ranges &&...ranges) -> generator { + ( + co_yield + ranges::elements_of(ranges), ...); +} +auto main() -> int { + int const numbers1[] = {4, 8, 15, 16, 23, 42}; + double const numbers2[] = {4, 8, 15, 16, 23, 42}; + concat(numbers1, numbers2) + ; +}