Hi Arsen, sorry, I missed one point when I looked through this earlier ..
> On 23 Aug 2024, at 20:23, Arsen Arsenović <ar...@aarsen.me> wrote: > > 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); If the purpose of this is to check for mistakes during development (i.e. we do not see a reason for having it in a released compiler) - then it’s better to use gcc_checking_assert() which will disappear for non-checking builds. > 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 >