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/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 : @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 io_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 HIT 3162 : static void* operator new(std::size_t size)
74 : {
75 3162 : static auto* const rmr = get_recycling_memory_resource();
76 :
77 3162 : auto* mr = get_current_frame_allocator();
78 3162 : if(!mr)
79 1116 : mr = std::pmr::get_default_resource();
80 :
81 3162 : auto total = size + sizeof(std::pmr::memory_resource*);
82 : void* raw;
83 3162 : if(mr == rmr)
84 : raw = static_cast<recycling_memory_resource*>(mr)
85 1162 : ->allocate_fast(total, alignof(std::max_align_t));
86 : else
87 2000 : raw = mr->allocate(total, alignof(std::max_align_t));
88 3162 : std::memcpy(static_cast<char*>(raw) + size, &mr, sizeof(mr));
89 3162 : 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 3162 : static void operator delete(void* ptr, std::size_t size) noexcept
105 : {
106 3162 : static auto* const rmr = get_recycling_memory_resource();
107 :
108 : std::pmr::memory_resource* mr;
109 3162 : std::memcpy(&mr, static_cast<char*>(ptr) + size, sizeof(mr));
110 3162 : auto total = size + sizeof(std::pmr::memory_resource*);
111 3162 : if(mr == rmr)
112 : static_cast<recycling_memory_resource*>(mr)
113 1162 : ->deallocate_fast(ptr, total, alignof(std::max_align_t));
114 : else
115 2000 : mr->deallocate(ptr, total, alignof(std::max_align_t));
116 3162 : }
117 : };
118 :
119 : } // namespace capy
120 : } // namespace boost
121 :
122 : #endif
|