Tested against folly and cppcoro, currently regstrapping on x86_64-pc-linux-gnu. coroutine.exp and coro-torture.exp passed.
OK for trunk? (after regstrap) ---------- >8 ---------- 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 - Nesting coroutine definitions (e.g. via a lambda or a template expansion) can ICE the compiler 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. --- gcc/cp/coroutines.cc | 19 +- gcc/testsuite/g++.dg/coroutines/pr113457-1.C | 25 +++ gcc/testsuite/g++.dg/coroutines/pr113457.C | 178 +++++++++++++++++++ 3 files changed, 216 insertions(+), 6 deletions(-) create mode 100644 gcc/testsuite/g++.dg/coroutines/pr113457-1.C create mode 100644 gcc/testsuite/g++.dg/coroutines/pr113457.C diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc index 81096784b4d7..65d17dac4d89 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_expr; /* The location of the expression that turned this funtion 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; @@ -1007,15 +1011,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_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 000000000000..fcf67e15271c --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/pr113457-1.C @@ -0,0 +1,25 @@ +// https://gcc.gnu.org/PR113457 +#include <coroutine> + +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 000000000000..77b1a3ceaa2b --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/pr113457.C @@ -0,0 +1,178 @@ +// https://gcc.gnu.org/PR113457 +namespace std { +template <typename _Tp, typename _Up = _Tp &&> _Up __declval(int); +template <typename _Tp> auto declval() noexcept -> decltype(__declval<_Tp>(0)); +template <typename _Tp> struct remove_cv { + using type = __remove_cv(_Tp); +}; +template <typename _Tp> using remove_cv_t = typename remove_cv<_Tp>::type; +template <typename _Tp> struct remove_reference { + using type = __remove_reference(_Tp); +}; +template <typename _Tp> +using remove_reference_t = typename remove_reference<_Tp>::type; +template <typename _Tp> inline constexpr bool is_array_v = __is_array(_Tp); +template <typename _Tp> struct remove_cvref {}; +namespace ranges { +} // namespace ranges +template <typename _Tp> +[[__nodiscard__]] constexpr typename std::remove_reference<_Tp>::type && +move(_Tp &&__t) noexcept { + return static_cast<typename std::remove_reference<_Tp>::type &&>(__t); +} +template <typename _Iterator> struct iterator_traits; +namespace __detail { +template <typename _Iter, typename _Tp> struct __iter_traits_impl { + using type = iterator_traits<_Iter>; +}; +template <typename _Iter, typename _Tp = _Iter> +using __iter_traits = typename __iter_traits_impl<_Iter, _Tp>::type; +} // namespace __detail +template <typename> struct indirectly_readable_traits {}; +namespace __detail { +template <typename _Tp> +using __iter_value_t = + typename __iter_traits<_Tp, indirectly_readable_traits<_Tp>>::value_type; +} +template <typename _Tp> +using iter_value_t = __detail::__iter_value_t<_Tp>; +namespace ranges::__access { +template <typename _Tp> auto __begin(_Tp &__t) { + return __t + 0; +} +} // namespace ranges::__access +namespace __detail { +template <typename _Tp> +using __range_iter_t = + decltype(ranges::__access::__begin(std::declval<_Tp &>())); +} +template <typename _Tp> struct iterator_traits<_Tp *> { + using value_type = remove_cv_t<_Tp>; +}; + namespace ranges { + namespace __access { + struct _Begin { + template <typename _Tp> + constexpr auto operator() [[nodiscard]] (_Tp &&__t) const + { + if constexpr (is_array_v<remove_reference_t<_Tp>>) + { + return __t + 0; + } + } + }; + } // namespace __access + inline namespace _Cpo { + inline constexpr ranges::__access::_Begin begin{}; + } // namespace _Cpo + template <typename _Tp> + concept range = requires(_Tp &__t) { ranges::begin(__t); }; + template <typename _Tp> using iterator_t = std::__detail::__range_iter_t<_Tp>; + template <range _Range> + using range_value_t = iter_value_t<iterator_t<_Range>>; + template <range _Range> + struct elements_of { + _Range range; + }; + template <typename _Range> + elements_of(_Range &&) -> elements_of<_Range &&>; + } // namespace ranges + inline namespace __n4861 { + template <typename _Result, typename = void> + struct __coroutine_traits_impl {}; + template <typename _Result> struct __coroutine_traits_impl<_Result, void> { + using promise_type = typename _Result::promise_type; + }; + template <typename _Result, typename... _ArgTypes> + struct coroutine_traits : __coroutine_traits_impl<_Result> {}; + template <typename _Promise = void> struct coroutine_handle; + template <typename _Promise> 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 <typename _Ref, typename _V = void> + class generator; + namespace __gen { + template <typename _Yielded> class _Promise_erased { + template <typename _Gen> struct _Recursive_awaiter; + struct _Final_awaiter; + public: + suspend_always initial_suspend() const noexcept {} + suspend_always yield_value(_Yielded __val) noexcept {} + template <typename _R2, typename _V2> + auto yield_value( + ranges::elements_of<generator<_R2, _V2> &&> __r) noexcept { + return _Recursive_awaiter{std::move(__r.range)}; + } + template <typename _R> + 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 <typename _Yielded> + struct _Promise_erased<_Yielded>::_Final_awaiter { + bool await_ready() noexcept {} + template <typename _Promise> + auto await_suspend(std::coroutine_handle<_Promise> __c) noexcept {} + void await_resume() noexcept {} + }; + template <typename _Yielded> + template <typename _Gen> + struct _Promise_erased<_Yielded>::_Recursive_awaiter { + _Gen _M_gen; + constexpr bool await_ready() const noexcept { return false; } + template <typename _Promise> + std::coroutine_handle<> + await_suspend(std::coroutine_handle<_Promise> __p) noexcept {} + void await_resume() {} + }; + } // namespace __gen + template <typename _Ref, typename _V> + struct generator + { + struct promise_type + : __gen::_Promise_erased<const int &> + { + generator get_return_object() noexcept {} + }; + }; +} // namespace std +using namespace std; +template <typename... Ranges> +[[nodiscard]] auto concat(Ranges &&...ranges) -> generator<double, double> { + ( + 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) + ; +} -- 2.46.0