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