include/boost/capy/ex/frame_alloc_mixin.hpp

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