TLA Line data 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 HIT 109312 : static void* operator new(std::size_t size)
75 : {
76 109312 : static auto* const rmr = get_recycling_memory_resource();
77 :
78 109312 : auto* mr = get_current_frame_allocator();
79 109312 : if(!mr)
80 2179 : mr = std::pmr::get_default_resource();
81 :
82 109312 : auto total = size + sizeof(std::pmr::memory_resource*);
83 : void* raw;
84 109312 : if(mr == rmr)
85 : raw = static_cast<recycling_memory_resource*>(mr)
86 103336 : ->allocate_fast(total, detail::frame_alignment);
87 : else
88 5976 : raw = mr->allocate(total, detail::frame_alignment);
89 109312 : std::memcpy(static_cast<char*>(raw) + size, &mr, sizeof(mr));
90 109312 : 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 109312 : static void operator delete(void* ptr, std::size_t size) noexcept
106 : {
107 109312 : static auto* const rmr = get_recycling_memory_resource();
108 :
109 : std::pmr::memory_resource* mr;
110 109312 : std::memcpy(&mr, static_cast<char*>(ptr) + size, sizeof(mr));
111 109312 : auto total = size + sizeof(std::pmr::memory_resource*);
112 109312 : if(mr == rmr)
113 : static_cast<recycling_memory_resource*>(mr)
114 103336 : ->deallocate_fast(ptr, total, detail::frame_alignment);
115 : else
116 5976 : mr->deallocate(ptr, total, detail::frame_alignment);
117 109312 : }
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 422 : static void* operator new(std::size_t size)
128 : {
129 : return static_cast<recycling_memory_resource*>(
130 422 : get_recycling_memory_resource())
131 422 : ->allocate_fast(size, detail::frame_alignment);
132 : }
133 :
134 422 : static void operator delete(void* ptr, std::size_t size) noexcept
135 : {
136 : static_cast<recycling_memory_resource*>(
137 422 : get_recycling_memory_resource())
138 422 : ->deallocate_fast(ptr, size, detail::frame_alignment);
139 422 : }
140 : };
141 :
142 : } // namespace detail
143 :
144 : } // namespace capy
145 : } // namespace boost
146 :
147 : #endif
|