1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3  
//
3  
//
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
4  
// 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)
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  
//
6  
//
7  
// Official repository: https://github.com/cppalliance/capy
7  
// Official repository: https://github.com/cppalliance/capy
8  
//
8  
//
9  

9  

10  
#ifndef BOOST_CAPY_FRAME_ALLOCATOR_HPP
10  
#ifndef BOOST_CAPY_FRAME_ALLOCATOR_HPP
11  
#define BOOST_CAPY_FRAME_ALLOCATOR_HPP
11  
#define BOOST_CAPY_FRAME_ALLOCATOR_HPP
12  

12  

13  
#include <boost/capy/detail/config.hpp>
13  
#include <boost/capy/detail/config.hpp>
14  

14  

15  
#include <coroutine>
15  
#include <coroutine>
16  
#include <memory_resource>
16  
#include <memory_resource>
17  

17  

18  
/*  Design rationale (pdimov):
18  
/*  Design rationale (pdimov):
19  

19  

20  
    This accessor is a thin wrapper over a thread-local pointer.
20  
    This accessor is a thin wrapper over a thread-local pointer.
21  
    It returns exactly what was stored, including nullptr. No
21  
    It returns exactly what was stored, including nullptr. No
22  
    dynamic initializer on the thread-local; a dynamic TLS
22  
    dynamic initializer on the thread-local; a dynamic TLS
23  
    initializer moves you into a costlier implementation bucket
23  
    initializer moves you into a costlier implementation bucket
24  
    on some platforms - avoid it.
24  
    on some platforms - avoid it.
25  

25  

26  
    Null handling is the caller's responsibility (e.g. in
26  
    Null handling is the caller's responsibility (e.g. in
27  
    promise_type::operator new). The accessor must not substitute
27  
    promise_type::operator new). The accessor must not substitute
28  
    a default, because there are multiple valid choices
28  
    a default, because there are multiple valid choices
29  
    (new_delete_resource, the default pmr resource, etc.). If
29  
    (new_delete_resource, the default pmr resource, etc.). If
30  
    the allocator is not set, it reports "not set" and the
30  
    the allocator is not set, it reports "not set" and the
31  
    caller interprets that however it wants.
31  
    caller interprets that however it wants.
32  
*/
32  
*/
33  

33  

34  
namespace boost {
34  
namespace boost {
35  
namespace capy {
35  
namespace capy {
36  

36  

37  
namespace detail {
37  
namespace detail {
38  

38  

39  
inline std::pmr::memory_resource*&
39  
inline std::pmr::memory_resource*&
40  
current_frame_allocator_ref() noexcept
40  
current_frame_allocator_ref() noexcept
41  
{
41  
{
42  
    static thread_local std::pmr::memory_resource* mr = nullptr;
42  
    static thread_local std::pmr::memory_resource* mr = nullptr;
43  
    return mr;
43  
    return mr;
44  
}
44  
}
45  

45  

46  
} // namespace detail
46  
} // namespace detail
47  

47  

48  
/** Return the current frame allocator for this thread.
48  
/** Return the current frame allocator for this thread.
49  

49  

50  
    These accessors exist to implement the allocator
50  
    These accessors exist to implement the allocator
51  
    propagation portion of the @ref IoAwaitable protocol.
51  
    propagation portion of the @ref IoAwaitable protocol.
52  
    Launch functions (`run_async`, `run`) set the
52  
    Launch functions (`run_async`, `run`) set the
53  
    thread-local value before invoking a child coroutine;
53  
    thread-local value before invoking a child coroutine;
54  
    the child's `promise_type::operator new` reads it to
54  
    the child's `promise_type::operator new` reads it to
55  
    allocate the coroutine frame from the correct resource.
55  
    allocate the coroutine frame from the correct resource.
56  

56  

57  
    The value is only valid during a narrow execution
57  
    The value is only valid during a narrow execution
58  
    window. Between a coroutine's resumption
58  
    window. Between a coroutine's resumption
59  
    and the next suspension point, the protocol guarantees
59  
    and the next suspension point, the protocol guarantees
60  
    that TLS contains the allocator associated with the
60  
    that TLS contains the allocator associated with the
61  
    currently running chain. Outside that window the value
61  
    currently running chain. Outside that window the value
62  
    is indeterminate. Only code that implements an
62  
    is indeterminate. Only code that implements an
63  
    @ref IoAwaitable should call these functions.
63  
    @ref IoAwaitable should call these functions.
64  

64  

65  
    A return value of `nullptr` means "not specified" -
65  
    A return value of `nullptr` means "not specified" -
66  
    no allocator has been established for this chain.
66  
    no allocator has been established for this chain.
67  
    The awaitable is free to use whatever allocation
67  
    The awaitable is free to use whatever allocation
68  
    strategy makes best sense (e.g.
68  
    strategy makes best sense (e.g.
69  
    `std::pmr::new_delete_resource()`).
69  
    `std::pmr::new_delete_resource()`).
70  

70  

71  
    Use of the frame allocator is optional. An awaitable
71  
    Use of the frame allocator is optional. An awaitable
72  
    that does not consult this value to allocate its
72  
    that does not consult this value to allocate its
73  
    coroutine frame is never wrong. However, a conforming
73  
    coroutine frame is never wrong. However, a conforming
74  
    awaitable must still propagate the allocator faithfully
74  
    awaitable must still propagate the allocator faithfully
75  
    so that downstream coroutines can use it.
75  
    so that downstream coroutines can use it.
76  

76  

77  
    @return The thread-local memory_resource pointer,
77  
    @return The thread-local memory_resource pointer,
78  
    or `nullptr` if none has been set.
78  
    or `nullptr` if none has been set.
79  

79  

80  
    @see set_current_frame_allocator, IoAwaitable
80  
    @see set_current_frame_allocator, IoAwaitable
81  
*/
81  
*/
82  
inline
82  
inline
83  
std::pmr::memory_resource*
83  
std::pmr::memory_resource*
84  
get_current_frame_allocator() noexcept
84  
get_current_frame_allocator() noexcept
85  
{
85  
{
86  
    return detail::current_frame_allocator_ref();
86  
    return detail::current_frame_allocator_ref();
87  
}
87  
}
88  

88  

89  
/** Set the current frame allocator for this thread.
89  
/** Set the current frame allocator for this thread.
90  

90  

91  
    Installs @p mr as the frame allocator that will be
91  
    Installs @p mr as the frame allocator that will be
92  
    read by the next coroutine's `promise_type::operator
92  
    read by the next coroutine's `promise_type::operator
93  
    new` on this thread. Only launch functions and
93  
    new` on this thread. Only launch functions and
94  
    @ref IoAwaitable machinery should call this; see
94  
    @ref IoAwaitable machinery should call this; see
95  
    @ref get_current_frame_allocator for the full protocol
95  
    @ref get_current_frame_allocator for the full protocol
96  
    description.
96  
    description.
97  

97  

98  
    Passing `nullptr` means "not specified" - no
98  
    Passing `nullptr` means "not specified" - no
99  
    particular allocator is established for the chain.
99  
    particular allocator is established for the chain.
100  

100  

101  
    @param mr The memory_resource to install, or
101  
    @param mr The memory_resource to install, or
102  
    `nullptr` to clear.
102  
    `nullptr` to clear.
103  

103  

104  
    @see get_current_frame_allocator, IoAwaitable
104  
    @see get_current_frame_allocator, IoAwaitable
105  
*/
105  
*/
106  
inline void
106  
inline void
107  
set_current_frame_allocator(
107  
set_current_frame_allocator(
108  
    std::pmr::memory_resource* mr) noexcept
108  
    std::pmr::memory_resource* mr) noexcept
109  
{
109  
{
110  
    detail::current_frame_allocator_ref() = mr;
110  
    detail::current_frame_allocator_ref() = mr;
111  
}
111  
}
112  

112  

113  
/** Resume a coroutine handle with frame-allocator TLS protection.
113  
/** Resume a coroutine handle with frame-allocator TLS protection.
114  

114  

115  
    Saves the current thread-local frame allocator before
115  
    Saves the current thread-local frame allocator before
116  
    calling `h.resume()`, then restores it after the call
116  
    calling `h.resume()`, then restores it after the call
117  
    returns. This prevents a resumed coroutine's
117  
    returns. This prevents a resumed coroutine's
118  
    `await_resume` from permanently overwriting the caller's
118  
    `await_resume` from permanently overwriting the caller's
119  
    allocator value.
119  
    allocator value.
120  

120  

121  
    Between a coroutine's resumption and its next child
121  
    Between a coroutine's resumption and its next child
122  
    invocation, arbitrary user code may run. If that code
122  
    invocation, arbitrary user code may run. If that code
123  
    resumes a coroutine from a different chain on this
123  
    resumes a coroutine from a different chain on this
124  
    thread, the other coroutine's `await_resume` overwrites
124  
    thread, the other coroutine's `await_resume` overwrites
125  
    TLS with its own allocator. Without save/restore, the
125  
    TLS with its own allocator. Without save/restore, the
126  
    original coroutine's next child would allocate from
126  
    original coroutine's next child would allocate from
127  
    the wrong resource.
127  
    the wrong resource.
128  

128  

129  
    Event loops, strand dispatch loops, and any code that
129  
    Event loops, strand dispatch loops, and any code that
130  
    calls `.resume()` on a coroutine handle should use
130  
    calls `.resume()` on a coroutine handle should use
131  
    this function instead of calling `.resume()` directly.
131  
    this function instead of calling `.resume()` directly.
132  
    See the @ref Executor concept documentation for details.
132  
    See the @ref Executor concept documentation for details.
133  

133  

134  
    @param h The coroutine handle to resume.
134  
    @param h The coroutine handle to resume.
135  

135  

136  
    @see get_current_frame_allocator, set_current_frame_allocator
136  
    @see get_current_frame_allocator, set_current_frame_allocator
137  
*/
137  
*/
138  
inline void
138  
inline void
139  
safe_resume(std::coroutine_handle<> h) noexcept
139  
safe_resume(std::coroutine_handle<> h) noexcept
140  
{
140  
{
141  
    auto* saved = get_current_frame_allocator();
141  
    auto* saved = get_current_frame_allocator();
142  
    h.resume();
142  
    h.resume();
143  
    set_current_frame_allocator(saved);
143  
    set_current_frame_allocator(saved);
144  
}
144  
}
145  

145  

146  
} // namespace capy
146  
} // namespace capy
147  
} // namespace boost
147  
} // namespace boost
148  

148  

149  
#endif
149  
#endif