LCOV - code coverage report
Current view: top level - capy/detail - await_suspend_helper.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 100.0 % 9 9
Test Date: 2026-09-22 17:08:26 Functions: 70.0 % 20 14 6

           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
        

Generated by: LCOV version 2.3