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   @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 io_awaitable_promise_base, frame_allocator, recycling_memory_resource 51   @see io_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   3162 static void* operator new(std::size_t size) 73   3169 static void* operator new(std::size_t size)
74   { 74   {
HITCBC 75   3162 static auto* const rmr = get_recycling_memory_resource(); 75   3169 static auto* const rmr = get_recycling_memory_resource();
76   76  
HITCBC 77   3162 auto* mr = get_current_frame_allocator(); 77   3169 auto* mr = get_current_frame_allocator();
HITCBC 78   3162 if(!mr) 78   3169 if(!mr)
HITCBC 79   1116 mr = std::pmr::get_default_resource(); 79   1116 mr = std::pmr::get_default_resource();
80   80  
HITCBC 81   3162 auto total = size + sizeof(std::pmr::memory_resource*); 81   3169 auto total = size + sizeof(std::pmr::memory_resource*);
82   void* raw; 82   void* raw;
HITCBC 83   3162 if(mr == rmr) 83   3169 if(mr == rmr)
84   raw = static_cast<recycling_memory_resource*>(mr) 84   raw = static_cast<recycling_memory_resource*>(mr)
HITCBC 85   1162 ->allocate_fast(total, alignof(std::max_align_t)); 85   1162 ->allocate_fast(total, alignof(std::max_align_t));
86   else 86   else
HITCBC 87   2000 raw = mr->allocate(total, alignof(std::max_align_t)); 87   2007 raw = mr->allocate(total, alignof(std::max_align_t));
HITCBC 88   3162 std::memcpy(static_cast<char*>(raw) + size, &mr, sizeof(mr)); 88   3169 std::memcpy(static_cast<char*>(raw) + size, &mr, sizeof(mr));
HITCBC 89   3162 return raw; 89   3169 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   3162 static void operator delete(void* ptr, std::size_t size) noexcept 104   3169 static void operator delete(void* ptr, std::size_t size) noexcept
105   { 105   {
HITCBC 106   3162 static auto* const rmr = get_recycling_memory_resource(); 106   3169 static auto* const rmr = get_recycling_memory_resource();
107   107  
108   std::pmr::memory_resource* mr; 108   std::pmr::memory_resource* mr;
HITCBC 109   3162 std::memcpy(&mr, static_cast<char*>(ptr) + size, sizeof(mr)); 109   3169 std::memcpy(&mr, static_cast<char*>(ptr) + size, sizeof(mr));
HITCBC 110   3162 auto total = size + sizeof(std::pmr::memory_resource*); 110   3169 auto total = size + sizeof(std::pmr::memory_resource*);
HITCBC 111   3162 if(mr == rmr) 111   3169 if(mr == rmr)
112   static_cast<recycling_memory_resource*>(mr) 112   static_cast<recycling_memory_resource*>(mr)
HITCBC 113   1162 ->deallocate_fast(ptr, total, alignof(std::max_align_t)); 113   1162 ->deallocate_fast(ptr, total, alignof(std::max_align_t));
114   else 114   else
HITCBC 115   2000 mr->deallocate(ptr, total, alignof(std::max_align_t)); 115   2007 mr->deallocate(ptr, total, alignof(std::max_align_t));
HITCBC 116   3162 } 116   3169 }
117   }; 117   };
118   118  
119   } // namespace capy 119   } // namespace capy
120   } // namespace boost 120   } // namespace boost
121   121  
122   #endif 122   #endif