Branch data Line data Source code
1 : : // Copyright 2026 HPActor Contributors
2 : : //
3 : : // Licensed under the Apache License, Version 2.0 (the "License");
4 : : // you may not use this file except in compliance with the License.
5 : : // You may obtain a copy of the License at
6 : : //
7 : : // http://www.apache.org/licenses/LICENSE-2.0
8 : : //
9 : : // Unless required by applicable law or agreed to in writing, software
10 : : // distributed under the License is distributed on an "AS IS" BASIS,
11 : : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : : // See the License for the specific language governing permissions and
13 : : // limitations under the License.
14 : :
15 : : #pragma once
16 : :
17 : : #include <hpactor/mem/memory_config.hpp>
18 : : #include <hpactor/mem/memory_region.hpp>
19 : : #include <hpactor/mem/size_class.hpp>
20 : : #include <hpactor/types/types.hpp>
21 : :
22 : : #include <cstddef>
23 : : #include <cstdlib>
24 : : #include <memory>
25 : : #include <type_traits>
26 : :
27 : : namespace hpactor::mem {
28 : :
29 : : /// std::allocator-compatible adapter for the HPActor slab allocator.
30 : : ///
31 : : /// Stateful: stores an ActorId for per-actor memory tracking and a RegionType
32 : : /// for observability. All containers within an actor should use this allocator
33 : : /// to route allocations through the slab allocator instead of the global heap.
34 : : ///
35 : : /// Two modes:
36 : : /// - Value mode: MemStdAllocator(id(), RegionType::kActor) for local vars
37 : : /// - Pointer mode: MemStdAllocator(id_ptr(), RegionType::kActor) for actor
38 : : /// member containers — dereferences the pointer on each allocation so the
39 : : /// allocator tracks the live ActorId even when set_address() changes it.
40 : : ///
41 : : /// Allocations <= 4KB use the thread-local slab cache (bump + freelist).
42 : : /// Oversized allocations fall back to std::malloc / std::free.
43 : : template <typename T>
44 : : class MemStdAllocator {
45 : : public:
46 : : using value_type = T;
47 : : using size_type = std::size_t;
48 : : using difference_type = std::ptrdiff_t;
49 : :
50 : : using propagate_on_container_copy_assignment = std::true_type;
51 : : using propagate_on_container_move_assignment = std::true_type;
52 : : using propagate_on_container_swap = std::true_type;
53 : : using is_always_equal = std::false_type;
54 : :
55 : 3 : constexpr MemStdAllocator() noexcept = default;
56 : :
57 : : /// Value-based owner. Use for temporary/local containers.
58 : 678 : explicit MemStdAllocator(ActorId owner,
59 : : RegionType region = RegionType::kActor) noexcept
60 : 678 : : owner_val_(owner), region_(region) {}
61 : :
62 : : /// Pointer-based owner. Use for actor member containers so the allocator
63 : : /// tracks the live ActorId (which may change via set_address).
64 : 125 : explicit MemStdAllocator(const ActorId* owner_ptr,
65 : : RegionType region = RegionType::kActor) noexcept
66 : 125 : : owner_ptr_(owner_ptr), region_(region) {}
67 : :
68 : : template <typename U>
69 : 1454 : MemStdAllocator(const MemStdAllocator<U>& other) noexcept
70 : 1454 : : owner_ptr_(other.owner_ptr_), owner_val_(other.owner_val_),
71 : 1454 : region_(other.region_) {}
72 : :
73 : 695 : [[nodiscard]] T* allocate(size_t n) {
74 : 695 : if (n == 0) {
75 : 1 : return nullptr;
76 : : }
77 : 694 : size_t bytes = n * sizeof(T);
78 : :
79 : 694 : if (bytes > size_for_class(SizeClass::k4KB)) {
80 : : // NOLINTNEXTLINE(cppcoreguidelines-no-malloc)
81 : 1 : void* ptr = std::malloc(bytes);
82 : 1 : if (!ptr) {
83 : 0 : std::abort();
84 : : }
85 : 1 : return static_cast<T*>(ptr);
86 : : }
87 : :
88 : 693 : void* ptr = nullptr;
89 : 693 : ActorId owner = (owner_ptr_ != nullptr) ? *owner_ptr_ : owner_val_;
90 : 693 : if (t_tla) {
91 : 19 : ptr = t_tla->allocate_bytes(bytes, owner);
92 : : } else {
93 : : // NOLINTNEXTLINE(cppcoreguidelines-no-malloc)
94 : 674 : ptr = std::malloc(bytes);
95 : : }
96 : 693 : if (!ptr) {
97 : 0 : std::abort();
98 : : }
99 : 693 : return static_cast<T*>(ptr);
100 : : }
101 : :
102 : 696 : void deallocate(T* ptr, size_t n) noexcept {
103 : 696 : if (!ptr || n == 0) {
104 : 2 : return;
105 : : }
106 : 694 : size_t bytes = n * sizeof(T);
107 : :
108 : 694 : if (bytes > size_for_class(SizeClass::k4KB)) {
109 : : // NOLINTNEXTLINE(cppcoreguidelines-no-malloc)
110 : 1 : std::free(ptr);
111 : 1 : return;
112 : : }
113 : :
114 : 693 : if (t_tla) {
115 : 19 : t_tla->deallocate(ptr);
116 : : } else {
117 : : // NOLINTNEXTLINE(cppcoreguidelines-no-malloc)
118 : 674 : std::free(ptr);
119 : : }
120 : : }
121 : :
122 : 14 : ActorId owner() const noexcept {
123 : 14 : return (owner_ptr_ != nullptr) ? *owner_ptr_ : owner_val_;
124 : : }
125 : 2 : RegionType region() const noexcept { return region_; }
126 : :
127 : : template <typename U>
128 : : friend class MemStdAllocator;
129 : :
130 : : private:
131 : : const ActorId* owner_ptr_{nullptr};
132 : : ActorId owner_val_{};
133 : : RegionType region_{RegionType::kActor};
134 : : };
135 : :
136 : : template <typename T, typename U>
137 : 6 : bool operator==(const MemStdAllocator<T>& a,
138 : : const MemStdAllocator<U>& b) noexcept {
139 : 6 : return a.owner() == b.owner();
140 : : }
141 : :
142 : : template <typename T, typename U>
143 : 2 : bool operator!=(const MemStdAllocator<T>& a,
144 : : const MemStdAllocator<U>& b) noexcept {
145 : 2 : return !(a == b);
146 : : }
147 : :
148 : : // =========================================================================
149 : : // MemDeleter — custom deleter for std::unique_ptr that routes deallocation
150 : : // through the slab allocator instead of ::operator delete.
151 : : // =========================================================================
152 : : template <typename T>
153 : : struct MemDeleter {
154 : : void operator()(T* ptr) const noexcept {
155 : : if (ptr) {
156 : : ptr->~T();
157 : : deallocate(ptr);
158 : : }
159 : : }
160 : : };
161 : :
162 : : // Unique pointer alias using slab deallocation.
163 : : template <typename T>
164 : : using MemUniquePtr = std::unique_ptr<T, MemDeleter<T>>;
165 : :
166 : : // =========================================================================
167 : : // allocate_shared — std::allocate_shared wrapper with MemStdAllocator.
168 : : // Same return type as std::make_shared (no cascading type changes).
169 : : // =========================================================================
170 : :
171 : : /// Value-based owner.
172 : : template <typename T, typename... Args>
173 : 658 : std::shared_ptr<T> allocate_shared(ActorId owner, RegionType region,
174 : : Args&&... args) {
175 : : return std::allocate_shared<T>(
176 : 1316 : MemStdAllocator<T>(owner, region),
177 : 658 : std::forward<Args>(args)...);
178 : : }
179 : :
180 : : /// Pointer-based owner — tracks the live ActorId (for actor members).
181 : : template <typename T, typename... Args>
182 : 0 : std::shared_ptr<T> allocate_shared(const ActorId* owner_ptr,
183 : : RegionType region, Args&&... args) {
184 : : return std::allocate_shared<T>(
185 : 0 : MemStdAllocator<T>(owner_ptr, region),
186 : 0 : std::forward<Args>(args)...);
187 : : }
188 : :
189 : : // =========================================================================
190 : : // allocate_unique — placement-new + slab allocation + MemDeleter.
191 : : // Returns std::unique_ptr<T, MemDeleter<T>> (type differs from make_unique).
192 : : // =========================================================================
193 : :
194 : : /// Value-based owner.
195 : : template <typename T, typename... Args>
196 : : MemUniquePtr<T> allocate_unique(ActorId owner, RegionType region,
197 : : Args&&... args) {
198 : : void* mem = allocate(region, sizeof(T), owner);
199 : : if (!mem) {
200 : : std::abort();
201 : : }
202 : : // NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
203 : : return MemUniquePtr<T>(::new (mem) T(std::forward<Args>(args)...));
204 : : }
205 : :
206 : : /// Pointer-based owner — tracks the live ActorId.
207 : : template <typename T, typename... Args>
208 : : MemUniquePtr<T> allocate_unique(const ActorId* owner_ptr,
209 : : RegionType region, Args&&... args) {
210 : : ActorId owner =
211 : : (owner_ptr != nullptr) ? *owner_ptr : ActorId{};
212 : : return allocate_unique<T>(owner, region, std::forward<Args>(args)...);
213 : : }
214 : :
215 : : // =========================================================================
216 : : // SlabAllocated — CRTP base that overrides operator new/delete to route
217 : : // through the slab allocator. Inherit from this to make std::make_unique
218 : : // (and any other new-expression) use the slab allocator automatically.
219 : : //
220 : : // Requires t_tla and t_current_actor_id to be set on the calling thread
221 : : // (the scheduler does this before dispatching actor work).
222 : : // =========================================================================
223 : : template <typename Derived>
224 : : class SlabAllocated {
225 : : public:
226 : : // NOLINTNEXTLINE(cppcoreguidelines-no-malloc)
227 : 1031 : static void* operator new(size_t size) {
228 : 1031 : void* ptr = nullptr;
229 : 1031 : if (t_tla) {
230 : 0 : ptr = t_tla->allocate_bytes(size, current_actor_id());
231 : : }
232 : 1031 : if (!ptr) {
233 : 1031 : ptr = ::operator new(size); // NOLINT
234 : : }
235 : 1031 : return ptr;
236 : : }
237 : :
238 : 1024 : static void operator delete(void* ptr) noexcept {
239 : 1024 : if (!ptr) return;
240 : 1024 : if (t_tla) {
241 : 0 : t_tla->deallocate(ptr);
242 : : } else {
243 : 1024 : ::operator delete(ptr); // NOLINT
244 : : }
245 : : }
246 : :
247 : : // Array new/delete — required by C++ for completeness.
248 : : static void* operator new[](size_t size) = delete;
249 : : static void operator delete[](void* ptr) = delete;
250 : :
251 : : protected:
252 : : ~SlabAllocated() = default;
253 : : };
254 : :
255 : : } // namespace hpactor::mem
|