66.67% Lines (4/6) 100.00% Functions (2/2)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) 2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
  3 + // Copyright (c) 2026 Steve Gerbino
3   // 4   //
4   // Distributed under the Boost Software License, Version 1.0. (See accompanying 5   // Distributed under the Boost Software License, Version 1.0. (See accompanying
5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6   // 7   //
7   // Official repository: https://github.com/cppalliance/capy 8   // Official repository: https://github.com/cppalliance/capy
8   // 9   //
9   10  
10   #ifndef BOOST_CAPY_DETAIL_AWAIT_SUSPEND_HELPER_HPP 11   #ifndef BOOST_CAPY_DETAIL_AWAIT_SUSPEND_HELPER_HPP
11   #define BOOST_CAPY_DETAIL_AWAIT_SUSPEND_HELPER_HPP 12   #define BOOST_CAPY_DETAIL_AWAIT_SUSPEND_HELPER_HPP
12   13  
13   #include <coroutine> 14   #include <coroutine>
14   #include <boost/capy/ex/io_env.hpp> 15   #include <boost/capy/ex/io_env.hpp>
15   16  
16   #include <type_traits> 17   #include <type_traits>
17   18  
18   namespace boost { 19   namespace boost {
19   namespace capy { 20   namespace capy {
20   namespace detail { 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<>
HITGNC   65 + 2983 symmetric_transfer(std::coroutine_handle<> h) noexcept
  66 + {
HITGNC   67 + 2983 return h;
  68 + }
  69 + #endif
21   70  
22   // Helper to normalize await_suspend return types to std::coroutine_handle<> 71   // Helper to normalize await_suspend return types to std::coroutine_handle<>
23   template<typename Awaitable> 72   template<typename Awaitable>
HITCBC 24   7 std::coroutine_handle<> call_await_suspend( 73   7 std::coroutine_handle<> call_await_suspend(
25   Awaitable* a, 74   Awaitable* a,
26   std::coroutine_handle<> h, 75   std::coroutine_handle<> h,
27   io_env const* env) 76   io_env const* env)
28   { 77   {
29   using R = decltype(a->await_suspend(h, env)); 78   using R = decltype(a->await_suspend(h, env));
30   if constexpr (std::is_void_v<R>) 79   if constexpr (std::is_void_v<R>)
31   { 80   {
MISUBC 32   a->await_suspend(h, env); 81   a->await_suspend(h, env);
MISUBC 33   return std::noop_coroutine(); 82   return std::noop_coroutine();
34   } 83   }
35   else if constexpr (std::is_same_v<R, bool>) 84   else if constexpr (std::is_same_v<R, bool>)
36   { 85   {
37   if(a->await_suspend(h, env)) 86   if(a->await_suspend(h, env))
38   return std::noop_coroutine(); 87   return std::noop_coroutine();
39   return h; 88   return h;
40   } 89   }
41   else 90   else
42   { 91   {
HITCBC 43   7 return a->await_suspend(h, env); 92   7 return a->await_suspend(h, env);
44   } 93   }
45   } 94   }
46   95  
47   } // namespace detail 96   } // namespace detail
48   } // namespace capy 97   } // namespace capy
49   } // namespace boost 98   } // namespace boost
50   99  
51   #endif 100   #endif