include/boost/capy/ex/frame_alloc_mixin.hpp

100.0% Lines (19/19) 100.0% List of functions (2/2)
frame_alloc_mixin.hpp
f(x) Functions (2)
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 io_awaitable_promise_base. Use this mixin directly when your
40 promise type does not need the full environment and continuation
41 support that `io_awaitable_promise_base` provides.
42
43 @par Example
44 @code
45 struct my_internal_coroutine
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
58 @par Thread Safety
59 The allocation fast path uses thread-local storage and requires
60 no synchronization. The global pool fallback is mutex-protected.
61
62 @see io_awaitable_promise_base, frame_allocator, recycling_memory_resource
63 */
64 struct frame_alloc_mixin
65 {
66 /** Allocate a coroutine frame.
67
68 Uses the thread-local frame allocator set by run_async.
69 Falls back to default memory resource if not set.
70 Stores the allocator pointer at the end of each frame for
71 correct deallocation even when TLS changes. Uses memcpy
72 to avoid alignment requirements on the trailing pointer.
73 Bypasses virtual dispatch for the recycling allocator.
74
75 @param size The size, in bytes, of the coroutine frame.
76
77 @return A pointer to storage for the frame.
78
79 @throws Propagates any exception thrown by the underlying
80 memory resource's `allocate` (for example `std::bad_alloc`
81 from `::operator new`).
82 */
83 5417x static void* operator new(std::size_t size)
84 {
85 5417x static auto* const rmr = get_recycling_memory_resource();
86
87 5417x auto* mr = get_current_frame_allocator();
88 5417x if(!mr)
89 2848x mr = std::pmr::get_default_resource();
90
91 5417x auto total = size + sizeof(std::pmr::memory_resource*);
92 void* raw;
93 5417x if(mr == rmr)
94 raw = static_cast<recycling_memory_resource*>(mr)
95 2149x ->allocate_fast(total, alignof(std::max_align_t));
96 else
97 3268x raw = mr->allocate(total, alignof(std::max_align_t));
98 5417x std::memcpy(static_cast<char*>(raw) + size, &mr, sizeof(mr));
99 5417x return raw;
100 }
101
102 /** Deallocate a coroutine frame.
103
104 Reads the allocator pointer stored at the end of the frame
105 to ensure correct deallocation regardless of current TLS.
106 Bypasses virtual dispatch for the recycling allocator.
107 */
108 5417x static void operator delete(void* ptr, std::size_t size) noexcept
109 {
110 5417x static auto* const rmr = get_recycling_memory_resource();
111
112 std::pmr::memory_resource* mr;
113 5417x std::memcpy(&mr, static_cast<char*>(ptr) + size, sizeof(mr));
114 5417x auto total = size + sizeof(std::pmr::memory_resource*);
115 5417x if(mr == rmr)
116 static_cast<recycling_memory_resource*>(mr)
117 2149x ->deallocate_fast(ptr, total, alignof(std::max_align_t));
118 else
119 3268x mr->deallocate(ptr, total, alignof(std::max_align_t));
120 5417x }
121 };
122
123 } // namespace capy
124 } // namespace boost
125
126 #endif
127