100.00% Lines (19/19) 100.00% Functions (2/2)
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   io_awaitable_promise_base. Use this mixin directly when your 39   io_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 `io_awaitable_promise_base` provides. 41   support that `io_awaitable_promise_base` provides.
42   42  
43   @par Example 43   @par Example
44   @code 44   @code
45   struct my_internal_coroutine 45   struct my_internal_coroutine
46   { 46   {
47   struct promise_type : frame_alloc_mixin 47   struct promise_type : frame_alloc_mixin
48   { 48   {
49   my_internal_coroutine get_return_object(); 49   my_internal_coroutine get_return_object();
50   std::suspend_always initial_suspend() noexcept; 50   std::suspend_always initial_suspend() noexcept;
51   std::suspend_always final_suspend() noexcept; 51   std::suspend_always final_suspend() noexcept;
52   void return_void(); 52   void return_void();
53   void unhandled_exception() noexcept; 53   void unhandled_exception() noexcept;
54   }; 54   };
55   }; 55   };
56   @endcode 56   @endcode
57   57  
58   @par Thread Safety 58   @par Thread Safety
59   The allocation fast path uses thread-local storage and requires 59   The allocation fast path uses thread-local storage and requires
60   no synchronization. The global pool fallback is mutex-protected. 60   no synchronization. The global pool fallback is mutex-protected.
61   61  
62   @see io_awaitable_promise_base, frame_allocator, recycling_memory_resource 62   @see io_awaitable_promise_base, frame_allocator, recycling_memory_resource
63   */ 63   */
64   struct frame_alloc_mixin 64   struct frame_alloc_mixin
65   { 65   {
66   /** Allocate a coroutine frame. 66   /** Allocate a coroutine frame.
67   67  
68   Uses the thread-local frame allocator set by run_async. 68   Uses the thread-local frame allocator set by run_async.
69   Falls back to default memory resource if not set. 69   Falls back to default memory resource if not set.
70   Stores the allocator pointer at the end of each frame for 70   Stores the allocator pointer at the end of each frame for
71   correct deallocation even when TLS changes. Uses memcpy 71   correct deallocation even when TLS changes. Uses memcpy
72   to avoid alignment requirements on the trailing pointer. 72   to avoid alignment requirements on the trailing pointer.
73   Bypasses virtual dispatch for the recycling allocator. 73   Bypasses virtual dispatch for the recycling allocator.
74   74  
75   @param size The size, in bytes, of the coroutine frame. 75   @param size The size, in bytes, of the coroutine frame.
76   76  
77   @return A pointer to storage for the frame. 77   @return A pointer to storage for the frame.
78   78  
79   @par Exception Safety 79   @par Exception Safety
80   Propagates any exception thrown by the underlying memory 80   Propagates any exception thrown by the underlying memory
81   resource's `allocate`, for example `std::bad_alloc` from 81   resource's `allocate`, for example `std::bad_alloc` from
82   `::operator new`. 82   `::operator new`.
83   */ 83   */
HITCBC 84   3149 static void* operator new(std::size_t size) 84   3168 static void* operator new(std::size_t size)
85   { 85   {
HITCBC 86   3149 static auto* const rmr = get_recycling_memory_resource(); 86   3168 static auto* const rmr = get_recycling_memory_resource();
87   87  
HITCBC 88   3149 auto* mr = get_current_frame_allocator(); 88   3168 auto* mr = get_current_frame_allocator();
HITCBC 89   3149 if(!mr) 89   3168 if(!mr)
HITCBC 90   1116 mr = std::pmr::get_default_resource(); 90   1116 mr = std::pmr::get_default_resource();
91   91  
HITCBC 92   3149 auto total = size + sizeof(std::pmr::memory_resource*); 92   3168 auto total = size + sizeof(std::pmr::memory_resource*);
93   void* raw; 93   void* raw;
HITCBC 94   3149 if(mr == rmr) 94   3168 if(mr == rmr)
95   raw = static_cast<recycling_memory_resource*>(mr) 95   raw = static_cast<recycling_memory_resource*>(mr)
HITCBC 96   1162 ->allocate_fast(total, alignof(std::max_align_t)); 96   1162 ->allocate_fast(total, alignof(std::max_align_t));
97   else 97   else
HITCBC 98   1987 raw = mr->allocate(total, alignof(std::max_align_t)); 98   2006 raw = mr->allocate(total, alignof(std::max_align_t));
HITCBC 99   3149 std::memcpy(static_cast<char*>(raw) + size, &mr, sizeof(mr)); 99   3168 std::memcpy(static_cast<char*>(raw) + size, &mr, sizeof(mr));
HITCBC 100   3149 return raw; 100   3168 return raw;
101   } 101   }
102   102  
103   /** Deallocate a coroutine frame. 103   /** Deallocate a coroutine frame.
104   104  
105   Reads the allocator pointer stored at the end of the frame 105   Reads the allocator pointer stored at the end of the frame
106   to ensure correct deallocation regardless of current TLS. 106   to ensure correct deallocation regardless of current TLS.
107   Bypasses virtual dispatch for the recycling allocator. 107   Bypasses virtual dispatch for the recycling allocator.
108   108  
109   @param ptr The frame storage returned by `operator new`. 109   @param ptr The frame storage returned by `operator new`.
110   110  
111   @param size The size, in bytes, that was passed to `operator new`. 111   @param size The size, in bytes, that was passed to `operator new`.
112   The allocator pointer is read from `ptr + size`, which is where 112   The allocator pointer is read from `ptr + size`, which is where
113   `operator new` wrote it, so this value must match. 113   `operator new` wrote it, so this value must match.
114   */ 114   */
HITCBC 115   3149 static void operator delete(void* ptr, std::size_t size) noexcept 115   3168 static void operator delete(void* ptr, std::size_t size) noexcept
116   { 116   {
HITCBC 117   3149 static auto* const rmr = get_recycling_memory_resource(); 117   3168 static auto* const rmr = get_recycling_memory_resource();
118   118  
119   std::pmr::memory_resource* mr; 119   std::pmr::memory_resource* mr;
HITCBC 120   3149 std::memcpy(&mr, static_cast<char*>(ptr) + size, sizeof(mr)); 120   3168 std::memcpy(&mr, static_cast<char*>(ptr) + size, sizeof(mr));
HITCBC 121   3149 auto total = size + sizeof(std::pmr::memory_resource*); 121   3168 auto total = size + sizeof(std::pmr::memory_resource*);
HITCBC 122   3149 if(mr == rmr) 122   3168 if(mr == rmr)
123   static_cast<recycling_memory_resource*>(mr) 123   static_cast<recycling_memory_resource*>(mr)
HITCBC 124   1162 ->deallocate_fast(ptr, total, alignof(std::max_align_t)); 124   1162 ->deallocate_fast(ptr, total, alignof(std::max_align_t));
125   else 125   else
HITCBC 126   1987 mr->deallocate(ptr, total, alignof(std::max_align_t)); 126   2006 mr->deallocate(ptr, total, alignof(std::max_align_t));
HITCBC 127   3149 } 127   3168 }
128   }; 128   };
129   129  
130   } // namespace capy 130   } // namespace capy
131   } // namespace boost 131   } // namespace boost
132   132  
133   #endif 133   #endif