100.00% Lines (26/26) 100.00% Functions (4/4)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2026 Michael Vandeberg 2   // Copyright (c) 2026 Michael Vandeberg
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_EX_FRAME_ALLOC_MIXIN_HPP 10   #ifndef BOOST_CAPY_EX_FRAME_ALLOC_MIXIN_HPP
11   #define BOOST_CAPY_EX_FRAME_ALLOC_MIXIN_HPP 11   #define BOOST_CAPY_EX_FRAME_ALLOC_MIXIN_HPP
12   12  
13   #include <boost/capy/detail/config.hpp> 13   #include <boost/capy/detail/config.hpp>
14   #include <boost/capy/ex/frame_allocator.hpp> 14   #include <boost/capy/ex/frame_allocator.hpp>
15   #include <boost/capy/ex/recycling_memory_resource.hpp> 15   #include <boost/capy/ex/recycling_memory_resource.hpp>
16   16  
17   #include <cstddef> 17   #include <cstddef>
18   #include <cstring> 18   #include <cstring>
19   #include <memory_resource> 19   #include <memory_resource>
20   20  
21   namespace boost { 21   namespace boost {
22   namespace capy { 22   namespace capy {
23   23  
24   /** Mixin that adds frame-allocator-aware allocation to a promise type. 24   /** Mixin that adds frame-allocator-aware allocation to a promise type.
25   25  
26   Inherit from this class in any coroutine promise type to opt into 26   Inherit from this class in any coroutine promise type to opt into
27   TLS-based frame allocation with the recycling memory resource 27   TLS-based frame allocation with the recycling memory resource
28   fast path. The mixin provides `operator new` and `operator delete` 28   fast path. The mixin provides `operator new` and `operator delete`
29   that: 29   that:
30   30  
31   1. Read the thread-local frame allocator set by `run_async` or `run`. 31   1. Read the thread-local frame allocator set by `run_async` or `run`.
32   2. Bypass virtual dispatch when the allocator is the default 32   2. Bypass virtual dispatch when the allocator is the default
33   recycling memory resource. 33   recycling memory resource.
34   3. Store the allocator pointer at the end of each frame for 34   3. Store the allocator pointer at the end of each frame for
35   correct deallocation even when TLS changes between allocation 35   correct deallocation even when TLS changes between allocation
36   and deallocation. 36   and deallocation.
37   37  
38   This is the same allocation strategy used by @ref 38   This is the same allocation strategy used by @ref
39   context_awaitable_promise_base. Use this mixin directly when your 39   context_awaitable_promise_base. Use this mixin directly when your
40   promise type does not need the full environment and continuation 40   promise type does not need the full environment and continuation
41   support that `context_awaitable_promise_base` provides. 41   support that `context_awaitable_promise_base` provides.
42   42  
43   @par Example 43   @par Example
44   @par !example example 44   @par !example example
45   45  
46   46  
47   @par Thread Safety 47   @par Thread Safety
48   The allocation fast path uses thread-local storage and requires 48   The allocation fast path uses thread-local storage and requires
49   no synchronization. The global pool fallback is mutex-protected. 49   no synchronization. The global pool fallback is mutex-protected.
50   50  
51   @see context_awaitable_promise_base, frame_allocator, recycling_memory_resource 51   @see context_awaitable_promise_base, frame_allocator, recycling_memory_resource
52   */ 52   */
53   struct frame_alloc_mixin 53   struct frame_alloc_mixin
54   { 54   {
55   /** Allocate a coroutine frame. 55   /** Allocate a coroutine frame.
56   56  
57   Uses the thread-local frame allocator set by run_async. 57   Uses the thread-local frame allocator set by run_async.
58   Falls back to default memory resource if not set. 58   Falls back to default memory resource if not set.
59   Stores the allocator pointer at the end of each frame for 59   Stores the allocator pointer at the end of each frame for
60   correct deallocation even when TLS changes. Uses memcpy 60   correct deallocation even when TLS changes. Uses memcpy
61   to avoid alignment requirements on the trailing pointer. 61   to avoid alignment requirements on the trailing pointer.
62   Bypasses virtual dispatch for the recycling allocator. 62   Bypasses virtual dispatch for the recycling allocator.
63   63  
64   @param size The size, in bytes, of the coroutine frame. 64   @param size The size, in bytes, of the coroutine frame.
65   65  
66   @return A pointer to storage for the frame. 66   @return A pointer to storage for the frame.
67   67  
68   @par Exception Safety 68   @par Exception Safety
69   Propagates any exception thrown by the underlying memory 69   Propagates any exception thrown by the underlying memory
70   resource's `allocate`, for example `std::bad_alloc` from 70   resource's `allocate`, for example `std::bad_alloc` from
71   `::operator new`. 71   `::operator new`.
72   */ 72   */
HITCBC 73   112748 static void* operator new(std::size_t size) 73   112756 static void* operator new(std::size_t size)
74   { 74   {
HITCBC 75   112748 static auto* const rmr = get_recycling_memory_resource(); 75   112756 static auto* const rmr = get_recycling_memory_resource();
76   76  
HITCBC 77   112748 auto* mr = get_current_frame_allocator(); 77   112756 auto* mr = get_current_frame_allocator();
HITCBC 78   112748 if(!mr) 78   112756 if(!mr)
HITCBC 79   6156 mr = std::pmr::get_default_resource(); 79   6156 mr = std::pmr::get_default_resource();
80   80  
HITCBC 81   112748 auto total = size + sizeof(std::pmr::memory_resource*); 81   112756 auto total = size + sizeof(std::pmr::memory_resource*);
82   void* raw; 82   void* raw;
HITCBC 83   112748 if(mr == rmr) 83   112756 if(mr == rmr)
84   raw = static_cast<recycling_memory_resource*>(mr) 84   raw = static_cast<recycling_memory_resource*>(mr)
HITCBC 85   103303 ->allocate_fast(total, alignof(std::max_align_t)); 85   103303 ->allocate_fast(total, alignof(std::max_align_t));
86   else 86   else
HITCBC 87   9445 raw = mr->allocate(total, alignof(std::max_align_t)); 87   9453 raw = mr->allocate(total, alignof(std::max_align_t));
HITCBC 88   112748 std::memcpy(static_cast<char*>(raw) + size, &mr, sizeof(mr)); 88   112756 std::memcpy(static_cast<char*>(raw) + size, &mr, sizeof(mr));
HITCBC 89   112748 return raw; 89   112756 return raw;
90   } 90   }
91   91  
92   /** Deallocate a coroutine frame. 92   /** Deallocate a coroutine frame.
93   93  
94   Reads the allocator pointer stored at the end of the frame 94   Reads the allocator pointer stored at the end of the frame
95   to ensure correct deallocation regardless of current TLS. 95   to ensure correct deallocation regardless of current TLS.
96   Bypasses virtual dispatch for the recycling allocator. 96   Bypasses virtual dispatch for the recycling allocator.
97   97  
98   @param ptr The frame storage returned by `operator new`. 98   @param ptr The frame storage returned by `operator new`.
99   99  
100   @param size The size, in bytes, that was passed to `operator new`. 100   @param size The size, in bytes, that was passed to `operator new`.
101   The allocator pointer is read from `ptr + size`, which is where 101   The allocator pointer is read from `ptr + size`, which is where
102   `operator new` wrote it, so this value must match. 102   `operator new` wrote it, so this value must match.
103   */ 103   */
HITCBC 104   112748 static void operator delete(void* ptr, std::size_t size) noexcept 104   112756 static void operator delete(void* ptr, std::size_t size) noexcept
105   { 105   {
HITCBC 106   112748 static auto* const rmr = get_recycling_memory_resource(); 106   112756 static auto* const rmr = get_recycling_memory_resource();
107   107  
108   std::pmr::memory_resource* mr; 108   std::pmr::memory_resource* mr;
HITCBC 109   112748 std::memcpy(&mr, static_cast<char*>(ptr) + size, sizeof(mr)); 109   112756 std::memcpy(&mr, static_cast<char*>(ptr) + size, sizeof(mr));
HITCBC 110   112748 auto total = size + sizeof(std::pmr::memory_resource*); 110   112756 auto total = size + sizeof(std::pmr::memory_resource*);
HITCBC 111   112748 if(mr == rmr) 111   112756 if(mr == rmr)
112   static_cast<recycling_memory_resource*>(mr) 112   static_cast<recycling_memory_resource*>(mr)
HITCBC 113   103303 ->deallocate_fast(ptr, total, alignof(std::max_align_t)); 113   103303 ->deallocate_fast(ptr, total, alignof(std::max_align_t));
114   else 114   else
HITCBC 115   9445 mr->deallocate(ptr, total, alignof(std::max_align_t)); 115   9453 mr->deallocate(ptr, total, alignof(std::max_align_t));
HITCBC 116   112748 } 116   112756 }
117   }; 117   };
118   118  
119   namespace detail { 119   namespace detail {
120   120  
121   // For capy's own frames whose teardown can release the chain that owns 121   // For capy's own frames whose teardown can release the chain that owns
122   // the frame allocator: they are never allocated from it, so freeing them 122   // the frame allocator: they are never allocated from it, so freeing them
123   // afterwards never reaches a resource that died with that chain. 123   // afterwards never reaches a resource that died with that chain.
124   struct process_frame_mixin 124   struct process_frame_mixin
125   { 125   {
HITCBC 126   422 static void* operator new(std::size_t size) 126   422 static void* operator new(std::size_t size)
127   { 127   {
128   return static_cast<recycling_memory_resource*>( 128   return static_cast<recycling_memory_resource*>(
HITCBC 129   422 get_recycling_memory_resource()) 129   422 get_recycling_memory_resource())
HITCBC 130   422 ->allocate_fast(size, alignof(std::max_align_t)); 130   422 ->allocate_fast(size, alignof(std::max_align_t));
131   } 131   }
132   132  
HITCBC 133   422 static void operator delete(void* ptr, std::size_t size) noexcept 133   422 static void operator delete(void* ptr, std::size_t size) noexcept
134   { 134   {
135   static_cast<recycling_memory_resource*>( 135   static_cast<recycling_memory_resource*>(
HITCBC 136   422 get_recycling_memory_resource()) 136   422 get_recycling_memory_resource())
HITCBC 137   422 ->deallocate_fast(ptr, size, alignof(std::max_align_t)); 137   422 ->deallocate_fast(ptr, size, alignof(std::max_align_t));
HITCBC 138   422 } 138   422 }
139   }; 139   };
140   140  
141   } // namespace detail 141   } // namespace detail
142   142  
143   } // namespace capy 143   } // namespace capy
144   } // namespace boost 144   } // namespace boost
145   145  
146   #endif 146   #endif