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