1 // Copyright 2022 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #ifndef BASE_ALLOCATOR_DISPATCHER_INTERNAL_DISPATCHER_INTERNAL_H_
6 #define BASE_ALLOCATOR_DISPATCHER_INTERNAL_DISPATCHER_INTERNAL_H_
7
8 #include "base/allocator/dispatcher/configuration.h"
9 #include "base/allocator/dispatcher/internal/dispatch_data.h"
10 #include "base/allocator/dispatcher/internal/tools.h"
11 #include "base/allocator/dispatcher/subsystem.h"
12 #include "base/allocator/partition_allocator/src/partition_alloc/partition_alloc_allocation_data.h"
13 #include "base/allocator/partition_allocator/src/partition_alloc/partition_alloc_buildflags.h"
14 #include "base/check.h"
15 #include "base/compiler_specific.h"
16 #include "build/build_config.h"
17
18 #if BUILDFLAG(USE_ALLOCATOR_SHIM)
19 #include "base/allocator/partition_allocator/src/partition_alloc/shim/allocator_shim.h"
20 #endif
21
22 #include <tuple>
23
24 namespace base::allocator::dispatcher::internal {
25
26 #if BUILDFLAG(USE_ALLOCATOR_SHIM)
27 using allocator_shim::AllocatorDispatch;
28 #endif
29
30 template <typename CheckObserverPredicate,
31 typename... ObserverTypes,
32 size_t... Indices>
PerformObserverCheck(const std::tuple<ObserverTypes...> & observers,std::index_sequence<Indices...>,CheckObserverPredicate check_observer)33 void inline PerformObserverCheck(const std::tuple<ObserverTypes...>& observers,
34 std::index_sequence<Indices...>,
35 CheckObserverPredicate check_observer) {
36 ([](bool b) { DCHECK(b); }(check_observer(std::get<Indices>(observers))),
37 ...);
38 }
39
40 template <typename... ObserverTypes, size_t... Indices>
PerformAllocationNotification(const std::tuple<ObserverTypes...> & observers,std::index_sequence<Indices...>,const partition_alloc::AllocationNotificationData & notification_data,AllocationSubsystem sub_system)41 ALWAYS_INLINE void PerformAllocationNotification(
42 const std::tuple<ObserverTypes...>& observers,
43 std::index_sequence<Indices...>,
44 const partition_alloc::AllocationNotificationData& notification_data,
45 AllocationSubsystem sub_system) {
46 ((std::get<Indices>(observers)->OnAllocation(
47 notification_data.address(), notification_data.size(), sub_system,
48 notification_data.type_name())),
49 ...);
50 }
51
52 template <typename... ObserverTypes, size_t... Indices>
PerformFreeNotification(const std::tuple<ObserverTypes...> & observers,std::index_sequence<Indices...>,const partition_alloc::FreeNotificationData & notification_data)53 ALWAYS_INLINE void PerformFreeNotification(
54 const std::tuple<ObserverTypes...>& observers,
55 std::index_sequence<Indices...>,
56 const partition_alloc::FreeNotificationData& notification_data) {
57 ((std::get<Indices>(observers)->OnFree(notification_data.address())), ...);
58 }
59
60 // DispatcherImpl provides hooks into the various memory subsystems. These hooks
61 // are responsible for dispatching any notification to the observers.
62 // In order to provide as many information on the exact type of the observer and
63 // prevent any conditional jumps in the hot allocation path, observers are
64 // stored in a std::tuple. DispatcherImpl performs a CHECK at initialization
65 // time to ensure they are valid.
66 template <typename... ObserverTypes>
67 struct DispatcherImpl {
68 using AllObservers = std::index_sequence_for<ObserverTypes...>;
69
70 template <std::enable_if_t<
71 internal::LessEqual(sizeof...(ObserverTypes),
72 configuration::kMaximumNumberOfObservers),
73 bool> = true>
GetNotificationHooksDispatcherImpl74 static DispatchData GetNotificationHooks(
75 std::tuple<ObserverTypes*...> observers) {
76 s_observers = std::move(observers);
77
78 PerformObserverCheck(s_observers, AllObservers{}, IsValidObserver{});
79
80 return CreateDispatchData();
81 }
82
83 private:
CreateDispatchDataDispatcherImpl84 static DispatchData CreateDispatchData() {
85 return DispatchData()
86 #if BUILDFLAG(USE_PARTITION_ALLOC)
87 .SetAllocationObserverHooks(&PartitionAllocatorAllocationHook,
88 &PartitionAllocatorFreeHook)
89 #endif
90 #if BUILDFLAG(USE_ALLOCATOR_SHIM)
91 .SetAllocatorDispatch(&allocator_dispatch_)
92 #endif
93 ;
94 }
95
96 #if BUILDFLAG(USE_PARTITION_ALLOC)
PartitionAllocatorAllocationHookDispatcherImpl97 static void PartitionAllocatorAllocationHook(
98 const partition_alloc::AllocationNotificationData& notification_data) {
99 DoNotifyAllocation(notification_data,
100 AllocationSubsystem::kPartitionAllocator);
101 }
102
PartitionAllocatorFreeHookDispatcherImpl103 static void PartitionAllocatorFreeHook(
104 const partition_alloc::FreeNotificationData& notification_data) {
105 DoNotifyFree(notification_data);
106 }
107 #endif
108
109 #if BUILDFLAG(USE_ALLOCATOR_SHIM)
AllocFnDispatcherImpl110 static void* AllocFn(const AllocatorDispatch* self,
111 size_t size,
112 void* context) {
113 void* const address = self->next->alloc_function(self->next, size, context);
114
115 DoNotifyAllocation(
116 partition_alloc::AllocationNotificationData(address, size, nullptr),
117 AllocationSubsystem::kAllocatorShim);
118
119 return address;
120 }
121
AllocUncheckedFnDispatcherImpl122 static void* AllocUncheckedFn(const AllocatorDispatch* self,
123 size_t size,
124 void* context) {
125 void* const address =
126 self->next->alloc_unchecked_function(self->next, size, context);
127
128 DoNotifyAllocation(
129 partition_alloc::AllocationNotificationData(address, size, nullptr),
130 AllocationSubsystem::kAllocatorShim);
131
132 return address;
133 }
134
AllocZeroInitializedFnDispatcherImpl135 static void* AllocZeroInitializedFn(const AllocatorDispatch* self,
136 size_t n,
137 size_t size,
138 void* context) {
139 void* const address = self->next->alloc_zero_initialized_function(
140 self->next, n, size, context);
141
142 DoNotifyAllocation(
143 partition_alloc::AllocationNotificationData(address, n * size, nullptr),
144 AllocationSubsystem::kAllocatorShim);
145
146 return address;
147 }
148
AllocAlignedFnDispatcherImpl149 static void* AllocAlignedFn(const AllocatorDispatch* self,
150 size_t alignment,
151 size_t size,
152 void* context) {
153 void* const address = self->next->alloc_aligned_function(
154 self->next, alignment, size, context);
155
156 DoNotifyAllocation(
157 partition_alloc::AllocationNotificationData(address, size, nullptr),
158 AllocationSubsystem::kAllocatorShim);
159
160 return address;
161 }
162
ReallocFnDispatcherImpl163 static void* ReallocFn(const AllocatorDispatch* self,
164 void* address,
165 size_t size,
166 void* context) {
167 // Note: size == 0 actually performs free.
168 DoNotifyFree(partition_alloc::FreeNotificationData(address));
169 void* const reallocated_address =
170 self->next->realloc_function(self->next, address, size, context);
171
172 DoNotifyAllocation(partition_alloc::AllocationNotificationData(
173 reallocated_address, size, nullptr),
174 AllocationSubsystem::kAllocatorShim);
175
176 return reallocated_address;
177 }
178
FreeFnDispatcherImpl179 static void FreeFn(const AllocatorDispatch* self,
180 void* address,
181 void* context) {
182 // Note: The RecordFree should be called before free_function (here and in
183 // other places). That is because observers need to handle the allocation
184 // being freed before calling free_function, as once the latter is executed
185 // the address becomes available and can be allocated by another thread.
186 // That would be racy otherwise.
187 DoNotifyFree(partition_alloc::FreeNotificationData(address));
188 self->next->free_function(self->next, address, context);
189 }
190
GetSizeEstimateFnDispatcherImpl191 static size_t GetSizeEstimateFn(const AllocatorDispatch* self,
192 void* address,
193 void* context) {
194 return self->next->get_size_estimate_function(self->next, address, context);
195 }
196
GoodSizeFnDispatcherImpl197 static size_t GoodSizeFn(const AllocatorDispatch* self,
198 size_t size,
199 void* context) {
200 return self->next->good_size_function(self->next, size, context);
201 }
202
ClaimedAddressFnDispatcherImpl203 static bool ClaimedAddressFn(const AllocatorDispatch* self,
204 void* address,
205 void* context) {
206 return self->next->claimed_address_function(self->next, address, context);
207 }
208
BatchMallocFnDispatcherImpl209 static unsigned BatchMallocFn(const AllocatorDispatch* self,
210 size_t size,
211 void** results,
212 unsigned num_requested,
213 void* context) {
214 unsigned const num_allocated = self->next->batch_malloc_function(
215 self->next, size, results, num_requested, context);
216 for (unsigned i = 0; i < num_allocated; ++i) {
217 DoNotifyAllocation(partition_alloc::AllocationNotificationData(
218 results[i], size, nullptr),
219 AllocationSubsystem::kAllocatorShim);
220 }
221 return num_allocated;
222 }
223
BatchFreeFnDispatcherImpl224 static void BatchFreeFn(const AllocatorDispatch* self,
225 void** to_be_freed,
226 unsigned num_to_be_freed,
227 void* context) {
228 for (unsigned i = 0; i < num_to_be_freed; ++i) {
229 DoNotifyFree(partition_alloc::FreeNotificationData(to_be_freed[i]));
230 }
231
232 self->next->batch_free_function(self->next, to_be_freed, num_to_be_freed,
233 context);
234 }
235
FreeDefiniteSizeFnDispatcherImpl236 static void FreeDefiniteSizeFn(const AllocatorDispatch* self,
237 void* address,
238 size_t size,
239 void* context) {
240 DoNotifyFree(partition_alloc::FreeNotificationData(address));
241 self->next->free_definite_size_function(self->next, address, size, context);
242 }
243
TryFreeDefaultFnDispatcherImpl244 static void TryFreeDefaultFn(const AllocatorDispatch* self,
245 void* address,
246 void* context) {
247 DoNotifyFree(partition_alloc::FreeNotificationData(address));
248 self->next->try_free_default_function(self->next, address, context);
249 }
250
AlignedMallocFnDispatcherImpl251 static void* AlignedMallocFn(const AllocatorDispatch* self,
252 size_t size,
253 size_t alignment,
254 void* context) {
255 void* const address = self->next->aligned_malloc_function(
256 self->next, size, alignment, context);
257
258 DoNotifyAllocation(
259 partition_alloc::AllocationNotificationData(address, size, nullptr),
260 AllocationSubsystem::kAllocatorShim);
261
262 return address;
263 }
264
AlignedReallocFnDispatcherImpl265 static void* AlignedReallocFn(const AllocatorDispatch* self,
266 void* address,
267 size_t size,
268 size_t alignment,
269 void* context) {
270 // Note: size == 0 actually performs free.
271 DoNotifyFree(partition_alloc::FreeNotificationData(address));
272 address = self->next->aligned_realloc_function(self->next, address, size,
273 alignment, context);
274
275 DoNotifyAllocation(
276 partition_alloc::AllocationNotificationData(address, size, nullptr),
277 AllocationSubsystem::kAllocatorShim);
278
279 return address;
280 }
281
AlignedFreeFnDispatcherImpl282 static void AlignedFreeFn(const AllocatorDispatch* self,
283 void* address,
284 void* context) {
285 DoNotifyFree(partition_alloc::FreeNotificationData(address));
286 self->next->aligned_free_function(self->next, address, context);
287 }
288
289 static AllocatorDispatch allocator_dispatch_;
290 #endif
291
DoNotifyAllocationDispatcherImpl292 ALWAYS_INLINE static void DoNotifyAllocation(
293 const partition_alloc::AllocationNotificationData& notification_data,
294 AllocationSubsystem sub_system) {
295 PerformAllocationNotification(s_observers, AllObservers{},
296 notification_data, sub_system);
297 }
298
DoNotifyFreeDispatcherImpl299 ALWAYS_INLINE static void DoNotifyFree(
300 const partition_alloc::FreeNotificationData& notification_data) {
301 PerformFreeNotification(s_observers, AllObservers{}, notification_data);
302 }
303
304 static std::tuple<ObserverTypes*...> s_observers;
305 };
306
307 template <typename... ObserverTypes>
308 std::tuple<ObserverTypes*...> DispatcherImpl<ObserverTypes...>::s_observers;
309
310 #if BUILDFLAG(USE_ALLOCATOR_SHIM)
311 template <typename... ObserverTypes>
312 AllocatorDispatch DispatcherImpl<ObserverTypes...>::allocator_dispatch_ = {
313 &AllocFn,
314 &AllocUncheckedFn,
315 &AllocZeroInitializedFn,
316 &AllocAlignedFn,
317 &ReallocFn,
318 &FreeFn,
319 &GetSizeEstimateFn,
320 &GoodSizeFn,
321 &ClaimedAddressFn,
322 &BatchMallocFn,
323 &BatchFreeFn,
324 &FreeDefiniteSizeFn,
325 &TryFreeDefaultFn,
326 &AlignedMallocFn,
327 &AlignedReallocFn,
328 &AlignedFreeFn,
329 nullptr};
330 #endif
331
332 // Specialization of DispatcherImpl in case we have no observers to notify. In
333 // this special case we return a set of null pointers as the Dispatcher must not
334 // install any hooks at all.
335 template <>
336 struct DispatcherImpl<> {
337 static DispatchData GetNotificationHooks(std::tuple<> /*observers*/) {
338 return DispatchData()
339 #if BUILDFLAG(USE_PARTITION_ALLOC)
340 .SetAllocationObserverHooks(nullptr, nullptr)
341 #endif
342 #if BUILDFLAG(USE_ALLOCATOR_SHIM)
343 .SetAllocatorDispatch(nullptr)
344 #endif
345 ;
346 }
347 };
348
349 // A little utility function that helps using DispatcherImpl by providing
350 // automated type deduction for templates.
351 template <typename... ObserverTypes>
352 inline DispatchData GetNotificationHooks(
353 std::tuple<ObserverTypes*...> observers) {
354 return DispatcherImpl<ObserverTypes...>::GetNotificationHooks(
355 std::move(observers));
356 }
357
358 } // namespace base::allocator::dispatcher::internal
359
360 #endif // BASE_ALLOCATOR_DISPATCHER_INTERNAL_DISPATCHER_INTERNAL_H_
361