TLA Line data 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/detail/config.hpp>
16 : #include <boost/capy/ex/io_env.hpp>
17 :
18 : #include <type_traits>
19 :
20 : namespace boost {
21 : namespace capy {
22 : namespace detail {
23 :
24 : /** Perform symmetric transfer, working around an MSVC codegen bug.
25 :
26 : MSVC stores the `std::coroutine_handle<>` returned from
27 : `await_suspend` in a hidden `__$ReturnUdt$` variable located
28 : on the coroutine frame. When another thread resumes or destroys
29 : the frame between the store and the read-back for the
30 : symmetric-transfer tail-call, the read hits freed memory.
31 :
32 : This occurs in two scenarios:
33 :
34 : @li `await_suspend` calls `h.destroy()` then returns a handle
35 : (e.g. `when_all_runner` and `when_any_runner` final_suspend).
36 : The return value is written to the now-destroyed frame.
37 :
38 : @li `await_suspend` hands the continuation to another thread
39 : via an executor handoff (e.g. `post()` or `dispatch()`),
40 : which may resume the parent. The parent can destroy this
41 : frame before the runtime reads `__$ReturnUdt$` (e.g.
42 : `boundary_trampoline` final_suspend).
43 :
44 : On affected compilers this function calls `h.resume()` on the
45 : current stack and returns `void`, causing unconditional
46 : suspension. The trade-off is O(n) stack growth instead of
47 : O(1) tail-calls.
48 :
49 : The workaround applies to MSVC 19.34 through 19.44 and
50 : self-retires on MSVC 19.50 (VS 2026 / 18.0). Measured on
51 : 19.44 the caller builds the hidden return slot at
52 : `__coro_frame_ptr$ + 0xC0`, on the coroutine frame; on 19.51
53 : it is an `rsp`-relative stack temporary, so destroying the
54 : frame no longer invalidates it.
55 :
56 : Do not widen this gate on the basis of Developer Community
57 : ticket 10251975, tagged "Fixed in VS 2022 17.9 Preview 2";
58 : 19.39 reproduces the fault identically to 19.34.
59 :
60 : The gate deliberately excludes Clang. Both `clang-cl` and
61 : `clang++` targeting Windows define `_MSC_VER` for ABI
62 : compatibility, but generate a correct tail-call.
63 :
64 : Note that a probe which merely poisons the destroyed frame
65 : cannot validate this gate. Routing the return through this
66 : function moves the frame write to after `destroy()`, which
67 : repairs the poison pattern and hides the defect. The
68 : regression test in
69 : test/unit/detail/await_suspend_helper.cpp unmaps the frame
70 : instead, so any post-destroy access faults.
71 :
72 : On unaffected compilers the handle is returned directly for
73 : proper symmetric transfer.
74 :
75 : Callers must use `auto` return type on their `await_suspend`
76 : so the return type adapts per platform.
77 :
78 : @param h The coroutine handle to transfer to.
79 : */
80 : #if BOOST_CAPY_WORKAROUND(_MSC_VER, < 1950) && !defined(__clang__)
81 : inline void symmetric_transfer(std::coroutine_handle<> h) noexcept
82 : {
83 : // safe_resume is not needed here: the calling coroutine is
84 : // about to suspend unconditionally. When it later resumes,
85 : // await_resume restores TLS from the promise's environment.
86 : h.resume();
87 : }
88 : #else
89 : inline std::coroutine_handle<>
90 HIT 1881 : symmetric_transfer(std::coroutine_handle<> h) noexcept
91 : {
92 1881 : return h;
93 : }
94 : #endif
95 :
96 : // Helper to normalize await_suspend return types to std::coroutine_handle<>
97 : template<typename Awaitable>
98 251 : std::coroutine_handle<> call_await_suspend(
99 : Awaitable* a,
100 : std::coroutine_handle<> h,
101 : io_env const* env)
102 : {
103 : using R = decltype(a->await_suspend(h, env));
104 : if constexpr (std::is_void_v<R>)
105 : {
106 1 : a->await_suspend(h, env);
107 1 : return std::noop_coroutine();
108 : }
109 : else if constexpr (std::is_same_v<R, bool>)
110 : {
111 244 : if(a->await_suspend(h, env))
112 1 : return std::noop_coroutine();
113 243 : return h;
114 : }
115 : else
116 : {
117 6 : return a->await_suspend(h, env);
118 : }
119 : }
120 :
121 : } // namespace detail
122 : } // namespace capy
123 : } // namespace boost
124 :
125 : #endif
|