Skip to content

Commit ccc58c4

Browse files
committed
Shrink the inline storage in connect_awaitable
This diff adapts an idea originally due to @lewissbaker, originally shared at https://godbolt.org/z/zGG9fsPrz. Changes to Lewis's original include: * never invoke `coro.destroy()` since it's a no-op anyway * hard-code the integration with `connect_awaitable` since that's all we're using it for anyway * integrate with stdexec's support for various ways of making senders awaitable * MOAR `std::unreachable()` * support `unhandled_stopped()`
1 parent 5473e9d commit ccc58c4

1 file changed

Lines changed: 136 additions & 96 deletions

File tree

include/stdexec/__detail/__connect_awaitable.hpp

Lines changed: 136 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,7 @@ namespace STDEXEC
3838
// __connect_await
3939
namespace __connect_await
4040
{
41-
STDEXEC_PRAGMA_OPTIMIZE_BEGIN()
42-
43-
static constexpr std::size_t __storage_size = 8 * sizeof(void*);
41+
static constexpr std::size_t __storage_size = 5 * sizeof(void*);
4442
static constexpr std::size_t __storage_align = __STDCPP_DEFAULT_NEW_ALIGNMENT__;
4543

4644
// clang-format off
@@ -76,32 +74,33 @@ namespace STDEXEC
7674
__with_await_transform() = default;
7775
};
7876

79-
struct __awaiter_base
77+
struct __final_awaiter
8078
{
8179
static constexpr auto await_ready() noexcept -> bool
8280
{
8381
return false;
8482
}
8583

84+
template <class _Promise>
85+
static constexpr void await_suspend(__std::coroutine_handle<_Promise> __h) noexcept
86+
{
87+
try
88+
{
89+
__h.promise().__opstate_.__on_resume();
90+
}
91+
catch (...)
92+
{
93+
__std::unreachable();
94+
}
95+
}
96+
8697
[[noreturn]]
87-
inline void await_resume() noexcept
98+
static void await_resume() noexcept
8899
{
89100
__std::unreachable();
90101
}
91102
};
92103

93-
inline void __destroy_coro(__std::coroutine_handle<> __coro) noexcept
94-
{
95-
# if STDEXEC_MSVC()
96-
// MSVCBUG https://developercommunity.visualstudio.com/t/Double-destroy-of-a-local-in-coroutine-d/10456428
97-
// Reassign __coro before calling destroy to make the mutation
98-
// observable and to hopefully ensure that the compiler does not eliminate it.
99-
std::exchange(__coro, {}).destroy();
100-
# else
101-
__coro.destroy();
102-
# endif
103-
}
104-
105104
template <class _Awaitable, class _Receiver>
106105
struct __opstate;
107106

@@ -110,71 +109,44 @@ namespace STDEXEC
110109
{
111110
using __opstate_t = __opstate<_Awaitable, _Receiver>;
112111

113-
struct __task : __immovable
114-
{
115-
using promise_type = __promise;
116-
117-
~__task()
118-
{
119-
__connect_await::__destroy_coro(__coro_);
120-
}
121-
122-
__std::coroutine_handle<__promise> __coro_{};
123-
};
124-
125-
struct __final_awaiter : __awaiter_base
126-
{
127-
void await_suspend(__std::coroutine_handle<>) noexcept
128-
{
129-
using __awaitable_t = __result_of<__get_awaitable, _Awaitable, __promise&>;
130-
using __awaiter_t = __awaiter_of_t<__awaitable_t>;
131-
using __result_t = decltype(__declval<__awaiter_t>().await_resume());
132-
133-
if (__opstate_.__eptr_)
134-
{
135-
STDEXEC::set_error(static_cast<_Receiver&&>(__opstate_.__rcvr_),
136-
std::move(__opstate_.__eptr_));
137-
}
138-
else if constexpr (__same_as<__result_t, void>)
139-
{
140-
STDEXEC_ASSERT(__opstate_.__result_.has_value());
141-
STDEXEC::set_value(static_cast<_Receiver&&>(__opstate_.__rcvr_));
142-
}
143-
else
144-
{
145-
STDEXEC_ASSERT(__opstate_.__result_.has_value());
146-
STDEXEC::set_value(static_cast<_Receiver&&>(__opstate_.__rcvr_),
147-
static_cast<__result_t&&>(*__opstate_.__result_));
148-
}
149-
// This coroutine is never resumed; its work is done.
150-
}
151-
152-
__opstate<_Awaitable, _Receiver>& __opstate_;
153-
};
154-
155112
constexpr explicit(!STDEXEC_EDG()) __promise(__opstate_t& __opstate) noexcept
156113
: __opstate_(__opstate)
157114
{}
158115

116+
~__promise()
117+
{
118+
// never invoked
119+
__std::unreachable();
120+
}
121+
159122
static constexpr auto
160123
operator new([[maybe_unused]] std::size_t __bytes, __opstate_t& __opstate) noexcept -> void*
161124
{
162-
STDEXEC_ASSERT(__bytes <= sizeof(__opstate.__storage_));
125+
STDEXEC_ASSERT(__bytes == __storage_size);
163126
return __opstate.__storage_;
164127
}
165128

166-
static constexpr void operator delete([[maybe_unused]] void* __ptr) noexcept
129+
static constexpr void operator delete(void*, std::size_t) noexcept
167130
{
168-
// no-op
131+
// never invoked
132+
__std::unreachable();
169133
}
170134

171-
constexpr auto get_return_object() noexcept -> __task
135+
constexpr auto get_return_object() noexcept -> __std::coroutine_handle<__promise>
172136
{
173-
return __task{{}, __std::coroutine_handle<__promise>::from_promise(*this)};
137+
try
138+
{
139+
return __std::coroutine_handle<__promise>::from_promise(*this);
140+
}
141+
catch (...)
142+
{
143+
__std::unreachable();
144+
}
174145
}
175146

176147
[[noreturn]]
177-
static auto get_return_object_on_allocation_failure() noexcept -> __task
148+
static auto
149+
get_return_object_on_allocation_failure() noexcept -> __std::coroutine_handle<__promise>
178150
{
179151
__std::unreachable();
180152
}
@@ -184,26 +156,27 @@ namespace STDEXEC
184156
return {};
185157
}
186158

187-
void unhandled_exception() noexcept
159+
[[noreturn]]
160+
static void unhandled_exception() noexcept
188161
{
189-
__opstate_.__eptr_ = std::current_exception();
162+
__std::unreachable();
190163
}
191164

192165
constexpr auto unhandled_stopped() noexcept -> __std::coroutine_handle<>
193166
{
194-
STDEXEC::set_stopped(static_cast<_Receiver&&>(__opstate_.__rcvr_));
167+
__opstate_.__on_stopped();
195168
// Returning noop_coroutine here causes the __connect_awaitable
196169
// coroutine to never resume past the point where it co_await's
197170
// the awaitable.
198171
return __std::noop_coroutine();
199172
}
200173

201-
constexpr auto final_suspend() noexcept -> __final_awaiter
174+
static constexpr auto final_suspend() noexcept -> __final_awaiter
202175
{
203-
return __final_awaiter{{}, __opstate_};
176+
return __final_awaiter{};
204177
}
205178

206-
static void return_void() noexcept
179+
static constexpr void return_void() noexcept
207180
{
208181
// no-op
209182
}
@@ -216,67 +189,134 @@ namespace STDEXEC
216189

217190
__opstate<_Awaitable, _Receiver>& __opstate_;
218191
};
192+
} // namespace __connect_await
193+
}
194+
195+
template <class _Awaitable, class _Receiver>
196+
struct std::coroutine_traits<
197+
STDEXEC::__std::coroutine_handle<STDEXEC::__connect_await::__promise<_Awaitable, _Receiver>>,
198+
STDEXEC::__connect_await::__opstate<_Awaitable, _Receiver>&>
199+
{
200+
using promise_type = STDEXEC::__connect_await::__promise<_Awaitable, _Receiver>;
201+
};
219202

203+
namespace STDEXEC
204+
{
205+
namespace __connect_await
206+
{
220207
template <class _Awaitable, class _Receiver>
221208
struct __opstate
222209
{
223210
constexpr explicit __opstate(_Awaitable&& __awaitable, _Receiver&& __rcvr)
224211
noexcept(__is_nothrow)
225212
: __rcvr_(static_cast<_Receiver&&>(__rcvr))
226-
, __task_(__co_impl(*this))
213+
, __coro(__co_impl(*this))
227214
, __awaitable1_(static_cast<_Awaitable&&>(__awaitable))
228-
, __awaitable2_(
229-
__get_awaitable(static_cast<_Awaitable&&>(__awaitable1_), __task_.__coro_.promise()))
215+
, __awaitable2_(__get_awaitable(static_cast<_Awaitable&&>(__awaitable1_), __coro.promise()))
230216
, __awaiter_(__get_awaiter(static_cast<__awaitable_t&&>(__awaitable2_)))
231217
{}
232218

233219
void start() & noexcept
234220
{
235-
__task_.__coro_.resume();
221+
try
222+
{
223+
if (!__awaiter_.await_ready())
224+
{
225+
using __suspend_result_t = decltype(__awaiter_.await_suspend(__coro));
226+
227+
// suspended
228+
if constexpr (std::is_void_v<__suspend_result_t>)
229+
{
230+
// void-returning await_suspend means "always suspend"
231+
__awaiter_.await_suspend(__coro);
232+
return;
233+
}
234+
else if constexpr (std::same_as<bool, __suspend_result_t>)
235+
{
236+
if (__awaiter_.await_suspend(__coro))
237+
{
238+
// returning true from a bool-returning await_suspend means suspend
239+
return;
240+
}
241+
else
242+
{
243+
// returning false means immediately resume
244+
}
245+
}
246+
else
247+
{
248+
static_assert(__std::convertible_to<__suspend_result_t, __std::coroutine_handle<>>);
249+
auto __resume_target = __awaiter_.await_suspend(__coro);
250+
__resume_target.resume();
251+
return;
252+
}
253+
}
254+
255+
// immediate resumption
256+
__on_resume();
257+
}
258+
catch (...)
259+
{
260+
if constexpr (!noexcept(__awaiter_.await_ready())
261+
|| !noexcept(__awaiter_.await_suspend(__coro)))
262+
{
263+
STDEXEC::set_error(static_cast<_Receiver&&>(__rcvr_), std::current_exception());
264+
}
265+
}
236266
}
237267

238268
private:
239269
using __promise_t = __promise<_Awaitable, _Receiver>;
240-
using __task_t = __promise_t::__task;
241270
using __awaitable_t = __result_of<__get_awaitable, _Awaitable, __promise_t&>;
242271
using __awaiter_t = __awaiter_of_t<__awaitable_t>;
243-
using __result_t = decltype(__declval<__awaiter_t>().await_resume());
244272

245273
friend __promise_t;
274+
friend __final_awaiter;
246275

247276
static constexpr bool __is_nothrow = __nothrow_move_constructible<_Awaitable>
248277
&& __noexcept_of<__get_awaitable, _Awaitable, __promise_t&>
249278
&& __noexcept_of<__get_awaiter, __awaitable_t>;
250279

251-
static constexpr std::size_t __storage_size = __connect_await::__storage_size
252-
+ sizeof(__manual_lifetime<__result_t>)
253-
- __same_as<__result_t, void>;
280+
static auto __co_impl(__opstate&) noexcept -> __std::coroutine_handle<__promise_t>
281+
{
282+
co_return;
283+
}
254284

255-
static auto __co_impl(__opstate& __op) noexcept -> __task_t
285+
constexpr void __on_resume() noexcept
256286
{
257-
using __awaiter_t = decltype(__op.__awaiter_);
258-
if constexpr (__same_as<decltype(*__op.__result_), void>)
287+
try
259288
{
260-
co_await static_cast<__awaiter_t&&>(__op.__awaiter_);
261-
__op.__result_.emplace();
289+
if constexpr (std::is_void_v<decltype(__awaiter_.await_resume())>)
290+
{
291+
__awaiter_.await_resume();
292+
STDEXEC::set_value(static_cast<_Receiver&&>(__rcvr_));
293+
}
294+
else
295+
{
296+
STDEXEC::set_value(static_cast<_Receiver&&>(__rcvr_), __awaiter_.await_resume());
297+
}
262298
}
263-
else
299+
catch (...)
264300
{
265-
__op.__result_.emplace(co_await static_cast<__awaiter_t&&>(__op.__awaiter_));
301+
if constexpr (!noexcept(__awaiter_.await_resume()))
302+
{
303+
STDEXEC::set_error(static_cast<_Receiver&&>(__rcvr_), std::current_exception());
304+
}
266305
}
267306
}
268307

308+
constexpr void __on_stopped() noexcept
309+
{
310+
STDEXEC::set_stopped(static_cast<_Receiver&&>(__rcvr_));
311+
}
312+
269313
alignas(__storage_align) std::byte __storage_[__storage_size];
270-
_Receiver __rcvr_;
271-
__promise_t::__task __task_;
272-
_Awaitable __awaitable1_;
273-
__awaitable_t __awaitable2_;
274-
__awaiter_t __awaiter_;
275-
std::exception_ptr __eptr_{};
276-
__optional<__result_t> __result_{};
314+
_Receiver __rcvr_;
315+
__std::coroutine_handle<__promise_t> __coro;
316+
_Awaitable __awaitable1_;
317+
__awaitable_t __awaitable2_;
318+
__awaiter_t __awaiter_;
277319
};
278-
279-
STDEXEC_PRAGMA_OPTIMIZE_END()
280320
} // namespace __connect_await
281321

282322
struct __connect_awaitable_t

0 commit comments

Comments
 (0)