include/boost/capy/detail/await_suspend_helper.hpp

66.7% Lines (4/6) 100.0% List of functions (1/1)
await_suspend_helper.hpp
f(x) Functions (1)
Line TLA Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 // Copyright (c) 2026 Steve Gerbino
4 //
5 // Distributed under the Boost Software License, Version 1.0. (See accompanying
6 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7 //
8 // Official repository: https://github.com/cppalliance/capy
9 //
10
11 #ifndef BOOST_CAPY_DETAIL_AWAIT_SUSPEND_HELPER_HPP
12 #define BOOST_CAPY_DETAIL_AWAIT_SUSPEND_HELPER_HPP
13
14 #include <coroutine>
15 #include <boost/capy/ex/io_env.hpp>
16
17 #include <type_traits>
18
19 namespace boost {
20 namespace capy {
21 namespace detail {
22
23 /** Perform symmetric transfer, working around an MSVC codegen bug.
24
25 MSVC stores the `std::coroutine_handle<>` returned from
26 `await_suspend` in a hidden `__$ReturnUdt$` variable located
27 on the coroutine frame. When another thread resumes or destroys
28 the frame between the store and the read-back for the
29 symmetric-transfer tail-call, the read hits freed memory.
30
31 This occurs in two scenarios:
32
33 @li `await_suspend` calls `h.destroy()` then returns a handle
34 (e.g. `when_all_runner` and `when_any_runner` final_suspend).
35 The return value is written to the now-destroyed frame.
36
37 @li `await_suspend` hands the continuation to another thread
38 via an executor handoff (e.g. `post()` or `dispatch()`),
39 which may resume the parent. The parent can destroy this
40 frame before the runtime reads `__$ReturnUdt$` (e.g.
41 `boundary_trampoline` final_suspend).
42
43 On MSVC this function calls `h.resume()` on the current stack
44 and returns `void`, causing unconditional suspension. The
45 trade-off is O(n) stack growth instead of O(1) tail-calls.
46
47 On other compilers the handle is returned directly for proper
48 symmetric transfer.
49
50 Callers must use `auto` return type on their `await_suspend`
51 so the return type adapts per platform.
52
53 @param h The coroutine handle to transfer to.
54 */
55 #if BOOST_CAPY_WORKAROUND(_MSC_VER, >= 1)
56 inline void symmetric_transfer(std::coroutine_handle<> h) noexcept
57 {
58 // safe_resume is not needed here: the calling coroutine is
59 // about to suspend unconditionally. When it later resumes,
60 // await_resume restores TLS from the promise's environment.
61 h.resume();
62 }
63 #else
64 inline std::coroutine_handle<>
65 2971x symmetric_transfer(std::coroutine_handle<> h) noexcept
66 {
67 2971x return h;
68 }
69 #endif
70
71 // Helper to normalize await_suspend return types to std::coroutine_handle<>
72 template<typename Awaitable>
73 7x std::coroutine_handle<> call_await_suspend(
74 Awaitable* a,
75 std::coroutine_handle<> h,
76 io_env const* env)
77 {
78 using R = decltype(a->await_suspend(h, env));
79 if constexpr (std::is_void_v<R>)
80 {
81 a->await_suspend(h, env);
82 return std::noop_coroutine();
83 }
84 else if constexpr (std::is_same_v<R, bool>)
85 {
86 if(a->await_suspend(h, env))
87 return std::noop_coroutine();
88 return h;
89 }
90 else
91 {
92 7x return a->await_suspend(h, env);
93 }
94 }
95
96 } // namespace detail
97 } // namespace capy
98 } // namespace boost
99
100 #endif
101