TLA Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 : // Copyright (c) 2026 Michael Vandeberg
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_FRAME_ALLOCATOR_HPP
12 : #define BOOST_CAPY_FRAME_ALLOCATOR_HPP
13 :
14 : #include <boost/capy/detail/config.hpp>
15 :
16 : #include <coroutine>
17 : #include <memory_resource>
18 :
19 : /* Design rationale (pdimov):
20 :
21 : This accessor is a thin wrapper over a thread-local pointer.
22 : It returns exactly what was stored, including nullptr. No
23 : dynamic initializer on the thread-local; a dynamic TLS
24 : initializer moves you into a costlier implementation bucket
25 : on some platforms - avoid it.
26 :
27 : Null handling is the caller's responsibility (e.g. in
28 : promise_type::operator new). The accessor must not substitute
29 : a default, because there are multiple valid choices
30 : (new_delete_resource, the default pmr resource, etc.). If
31 : the allocator is not set, it reports "not set" and the
32 : caller interprets that however it wants.
33 : */
34 :
35 : namespace boost {
36 : namespace capy {
37 :
38 : namespace detail {
39 :
40 : #if (defined(BOOST_CAPY_DYN_LINK) || defined(BOOST_ALL_DYN_LINK)) && \
41 : !defined(BOOST_CAPY_STATIC_LINK)
42 :
43 : /** Return this thread's frame allocator slot.
44 :
45 : A shared library build defines it in the library: an inline
46 : definition gives each module its own slot, and a coroutine created
47 : in one would not see the allocator a launcher in another installed.
48 : A static build keeps it inline, since resuming a coroutine writes
49 : it.
50 :
51 : @return A reference to the calling thread's slot.
52 : */
53 : BOOST_CAPY_DECL
54 : std::pmr::memory_resource*&
55 : current_frame_allocator_ref() noexcept;
56 :
57 : #else
58 :
59 : // MSVC may keep an inlined thread_local's address in a coroutine
60 : // frame across a suspension, so a resumption on another thread
61 : // would write the first thread's slot.
62 : #if BOOST_CAPY_WORKAROUND(_MSC_VER, >= 1) && !defined(__clang__)
63 : BOOST_CAPY_NOINLINE
64 : #endif
65 : inline std::pmr::memory_resource*&
66 : current_frame_allocator_ref() noexcept
67 : {
68 : static thread_local std::pmr::memory_resource* mr = nullptr;
69 : return mr;
70 : }
71 :
72 : #endif
73 :
74 : } // namespace detail
75 :
76 : /** Return the current frame allocator for this thread.
77 :
78 : These accessors exist to implement the allocator
79 : propagation portion of the @ref ContextAwaitable protocol.
80 : Launcher functions (`run_async`, `run`) set the
81 : thread-local value before invoking a child coroutine.
82 : The child's `promise_type::operator new` reads it to
83 : allocate the coroutine frame from the correct resource.
84 :
85 : The value is only valid during a narrow execution
86 : window. Between a coroutine's resumption
87 : and the next suspension point, the protocol guarantees
88 : that TLS contains the allocator associated with the
89 : currently running chain. Outside that window the value
90 : is indeterminate. Only code that implements an
91 : @ref ContextAwaitable should call these functions.
92 :
93 : A return value of `nullptr` means "not specified" -
94 : no allocator is established for this chain.
95 : The awaitable is free to use whatever allocation
96 : strategy makes best sense (e.g.
97 : `std::pmr::new_delete_resource()`).
98 :
99 : Use of the frame allocator is optional. An awaitable
100 : that does not consult this value to allocate its
101 : coroutine frame is never wrong. However, a conforming
102 : awaitable must still propagate the allocator faithfully
103 : so that downstream coroutines can use it.
104 :
105 : @return The thread-local memory_resource pointer,
106 : or `nullptr` if none is set.
107 :
108 : @see set_current_frame_allocator, ContextAwaitable
109 : */
110 : inline
111 : std::pmr::memory_resource*
112 HIT 372705 : get_current_frame_allocator() noexcept
113 : {
114 372705 : return detail::current_frame_allocator_ref();
115 : }
116 :
117 : /** Set the current frame allocator for this thread.
118 :
119 : Installs @p mr as the frame allocator read by the
120 : next coroutine's `promise_type::operator new` on
121 : this thread. Only launcher functions and
122 : @ref ContextAwaitable machinery should call this; see
123 : @ref get_current_frame_allocator for the full protocol
124 : description.
125 :
126 : Passing `nullptr` means "not specified" - no
127 : particular allocator is established for the chain.
128 :
129 : @param mr The memory_resource to install, or
130 : `nullptr` to clear.
131 :
132 : @see get_current_frame_allocator, ContextAwaitable
133 : */
134 : inline void
135 478380 : set_current_frame_allocator(
136 : std::pmr::memory_resource* mr) noexcept
137 : {
138 478380 : detail::current_frame_allocator_ref() = mr;
139 478380 : }
140 :
141 : /** Resume a coroutine handle with frame-allocator TLS protection.
142 :
143 : Saves the current thread-local frame allocator before
144 : calling `h.resume()`, then restores it after the call
145 : returns. This prevents a resumed coroutine's
146 : `await_resume` from permanently overwriting the caller's
147 : allocator value.
148 :
149 : Between a coroutine's resumption and its next child
150 : invocation, arbitrary user code may run. If that code
151 : resumes a coroutine from a different chain on this
152 : thread, the other coroutine's `await_resume` overwrites
153 : TLS with its own allocator. Without save/restore, the
154 : original coroutine's next child would allocate from
155 : the wrong resource.
156 :
157 : Event loops, strand dispatch loops, and any code that
158 : calls `.resume()` on a coroutine handle should use
159 : this function instead of calling `.resume()` directly.
160 : See the @ref Executor concept documentation for details.
161 :
162 : @param h The coroutine handle to resume.
163 :
164 : @see get_current_frame_allocator, set_current_frame_allocator
165 : */
166 : // tag::safe_resume[]
167 : inline void
168 154401 : safe_resume(std::coroutine_handle<> h) noexcept
169 : {
170 154401 : auto* saved = get_current_frame_allocator();
171 154401 : h.resume();
172 154401 : set_current_frame_allocator(saved);
173 154401 : }
174 : // end::safe_resume[]
175 :
176 : } // namespace capy
177 : } // namespace boost
178 :
179 : #endif
|