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