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