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 `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 +
symmetric_transfer(std::coroutine_handle<> h) noexcept
 
62 +
{
 
63 +
    return h;
 
64 +
}
 
65 +
#endif
21  

66  

22  
// Helper to normalize await_suspend return types to std::coroutine_handle<>
67  
// Helper to normalize await_suspend return types to std::coroutine_handle<>
23  
template<typename Awaitable>
68  
template<typename Awaitable>
24  
std::coroutine_handle<> call_await_suspend(
69  
std::coroutine_handle<> call_await_suspend(
25  
    Awaitable* a,
70  
    Awaitable* a,
26  
    std::coroutine_handle<> h,
71  
    std::coroutine_handle<> h,
27  
    io_env const* env)
72  
    io_env const* env)
28  
{
73  
{
29  
    using R = decltype(a->await_suspend(h, env));
74  
    using R = decltype(a->await_suspend(h, env));
30  
    if constexpr (std::is_void_v<R>)
75  
    if constexpr (std::is_void_v<R>)
31  
    {
76  
    {
32  
        a->await_suspend(h, env);
77  
        a->await_suspend(h, env);
33  
        return std::noop_coroutine();
78  
        return std::noop_coroutine();
34  
    }
79  
    }
35  
    else if constexpr (std::is_same_v<R, bool>)
80  
    else if constexpr (std::is_same_v<R, bool>)
36  
    {
81  
    {
37  
        if(a->await_suspend(h, env))
82  
        if(a->await_suspend(h, env))
38  
            return std::noop_coroutine();
83  
            return std::noop_coroutine();
39  
        return h;
84  
        return h;
40  
    }
85  
    }
41  
    else
86  
    else
42  
    {
87  
    {
43  
        return a->await_suspend(h, env);
88  
        return a->await_suspend(h, env);
44  
    }
89  
    }
45  
}
90  
}
46  

91  

47  
} // namespace detail
92  
} // namespace detail
48  
} // namespace capy
93  
} // namespace capy
49  
} // namespace boost
94  
} // namespace boost
50  

95  

51  
#endif
96  
#endif