include/boost/capy/detail/await_suspend_helper.hpp

66.7% Lines (4/6) 100.0% Functions (1/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 `executor::dispatch()`, which may resume the parent.
39 The parent can destroy this frame before the runtime reads
40 `__$ReturnUdt$` (e.g. `dispatch_trampoline` final_suspend).
41
42 On MSVC this function calls `h.resume()` on the current stack
43 and returns `void`, causing unconditional suspension. The
44 trade-off is O(n) stack growth instead of O(1) tail-calls.
45
46 On other compilers the handle is returned directly for proper
47 symmetric transfer.
48
49 Callers must use `auto` return type on their `await_suspend`
50 so the return type adapts per platform.
51
52 @param h The coroutine handle to transfer to.
53 */
54 #ifdef _MSC_VER
55 inline void symmetric_transfer(std::coroutine_handle<> h) noexcept
56 {
57 h.resume();
58 }
59 #else
60 inline std::coroutine_handle<>
61 2878x symmetric_transfer(std::coroutine_handle<> h) noexcept
62 {
63 2878x return h;
64 }
65 #endif
66
67 // Helper to normalize await_suspend return types to std::coroutine_handle<>
68 template<typename Awaitable>
69 7x std::coroutine_handle<> call_await_suspend(
70 Awaitable* a,
71 std::coroutine_handle<> h,
72 io_env const* env)
73 {
74 using R = decltype(a->await_suspend(h, env));
75 if constexpr (std::is_void_v<R>)
76 {
77 a->await_suspend(h, env);
78 return std::noop_coroutine();
79 }
80 else if constexpr (std::is_same_v<R, bool>)
81 {
82 if(a->await_suspend(h, env))
83 return std::noop_coroutine();
84 return h;
85 }
86 else
87 {
88 7x return a->await_suspend(h, env);
89 }
90 }
91
92 } // namespace detail
93 } // namespace capy
94 } // namespace boost
95
96 #endif
97