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/memory_tagging.h"
12 #include "base/allocator/dispatcher/notification_data.h"
13 #include "base/allocator/dispatcher/subsystem.h"
14 #include "base/check.h"
15 #include "base/compiler_specific.h"
16 #include "partition_alloc/buildflags.h"
17
18 #if PA_BUILDFLAG(USE_PARTITION_ALLOC)
19 #include "partition_alloc/partition_alloc_allocation_data.h" // nogncheck
20 #endif
21
22 #if PA_BUILDFLAG(USE_ALLOCATOR_SHIM)
23 #include "partition_alloc/shim/allocator_shim.h"
24 #endif
25
26 #include <tuple>
27
28 namespace base::allocator::dispatcher::internal {
29
30 #if PA_BUILDFLAG(USE_ALLOCATOR_SHIM)
31 using allocator_shim::AllocatorDispatch;
32 #endif
33
34 template <typename CheckObserverPredicate,
35 typename... ObserverTypes,
36 size_t... Indices>
PerformObserverCheck(const std::tuple<ObserverTypes...> & observers,std::index_sequence<Indices...>,CheckObserverPredicate check_observer)37 void inline PerformObserverCheck(const std::tuple<ObserverTypes...>& observers,
38 std::index_sequence<Indices...>,
39 CheckObserverPredicate check_observer) {
40 ([](bool b) { DCHECK(b); }(check_observer(std::get<Indices>(observers))),
41 ...);
42 }
43
44 template <typename... ObserverTypes, size_t... Indices>
PerformAllocationNotification(const std::tuple<ObserverTypes...> & observers,std::index_sequence<Indices...>,const AllocationNotificationData & notification_data)45 ALWAYS_INLINE void PerformAllocationNotification(
46 const std::tuple<ObserverTypes...>& observers,
47 std::index_sequence<Indices...>,
48 const AllocationNotificationData& notification_data) {
49 ((std::get<Indices>(observers)->OnAllocation(notification_data)), ...);
50 }
51
52 template <typename... ObserverTypes, size_t... Indices>
PerformFreeNotification(const std::tuple<ObserverTypes...> & observers,std::index_sequence<Indices...>,const FreeNotificationData & notification_data)53 ALWAYS_INLINE void PerformFreeNotification(
54 const std::tuple<ObserverTypes...>& observers,
55 std::index_sequence<Indices...>,
56 const FreeNotificationData& notification_data) {
57 ((std::get<Indices>(observers)->OnFree(notification_data)), ...);
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 PA_BUILDFLAG(USE_PARTITION_ALLOC)
87 .SetAllocationObserverHooks(&PartitionAllocatorAllocationHook,
88 &PartitionAllocatorFreeHook)
89 #endif
90 #if PA_BUILDFLAG(USE_ALLOCATOR_SHIM)
91 .SetAllocatorDispatch(&allocator_dispatch_)
92 #endif
93 ;
94 }
95
96 #if PA_BUILDFLAG(USE_PARTITION_ALLOC)
PartitionAllocatorAllocationHookDispatcherImpl97 static void PartitionAllocatorAllocationHook(
98 const partition_alloc::AllocationNotificationData& pa_notification_data) {
99 AllocationNotificationData dispatcher_notification_data(
100 pa_notification_data.address(), pa_notification_data.size(),
101 pa_notification_data.type_name(),
102 AllocationSubsystem::kPartitionAllocator);
103
104 #if PA_BUILDFLAG(HAS_MEMORY_TAGGING)
105 dispatcher_notification_data.SetMteReportingMode(
106 ConvertToMTEMode(pa_notification_data.mte_reporting_mode()));
107 #endif
108
109 DoNotifyAllocation(dispatcher_notification_data);
110 }
111
PartitionAllocatorFreeHookDispatcherImpl112 static void PartitionAllocatorFreeHook(
113 const partition_alloc::FreeNotificationData& pa_notification_data) {
114 FreeNotificationData dispatcher_notification_data(
115 pa_notification_data.address(),
116 AllocationSubsystem::kPartitionAllocator);
117
118 #if PA_BUILDFLAG(HAS_MEMORY_TAGGING)
119 dispatcher_notification_data.SetMteReportingMode(
120 ConvertToMTEMode(pa_notification_data.mte_reporting_mode()));
121 #endif
122
123 DoNotifyFree(dispatcher_notification_data);
124 }
125 #endif // PA_BUILDFLAG(USE_PARTITION_ALLOC)
126
127 #if PA_BUILDFLAG(USE_ALLOCATOR_SHIM)
AllocFnDispatcherImpl128 static void* AllocFn(size_t size, void* context) {
129 void* const address =
130 allocator_dispatch_.next->alloc_function(size, context);
131
132 DoNotifyAllocationForShim(address, size);
133
134 return address;
135 }
136
AllocUncheckedFnDispatcherImpl137 static void* AllocUncheckedFn(size_t size, void* context) {
138 void* const address =
139 allocator_dispatch_.next->alloc_unchecked_function(size, context);
140
141 DoNotifyAllocationForShim(address, size);
142
143 return address;
144 }
145
AllocZeroInitializedFnDispatcherImpl146 static void* AllocZeroInitializedFn(size_t n, size_t size, void* context) {
147 void* const address =
148 allocator_dispatch_.next->alloc_zero_initialized_function(n, size,
149 context);
150
151 DoNotifyAllocationForShim(address, n * size);
152
153 return address;
154 }
155
AllocAlignedFnDispatcherImpl156 static void* AllocAlignedFn(size_t alignment, size_t size, void* context) {
157 void* const address = allocator_dispatch_.next->alloc_aligned_function(
158 alignment, size, context);
159
160 DoNotifyAllocationForShim(address, size);
161
162 return address;
163 }
164
ReallocFnDispatcherImpl165 static void* ReallocFn(void* address, size_t size, void* context) {
166 // Note: size == 0 actually performs free.
167 DoNotifyFreeForShim(address);
168 void* const reallocated_address =
169 allocator_dispatch_.next->realloc_function(address, size, context);
170
171 DoNotifyAllocationForShim(reallocated_address, size);
172
173 return reallocated_address;
174 }
175
ReallocUncheckedFnDispatcherImpl176 static void* ReallocUncheckedFn(void* address, size_t size, void* context) {
177 // Note: size == 0 actually performs free.
178 DoNotifyFreeForShim(address);
179 void* const reallocated_address =
180 allocator_dispatch_.next->realloc_unchecked_function(address, size,
181 context);
182
183 DoNotifyAllocationForShim(reallocated_address, size);
184
185 return reallocated_address;
186 }
187
FreeFnDispatcherImpl188 static void FreeFn(void* address, void* context) {
189 // Note: DoNotifyFree should be called before free_function (here and in
190 // other places). That is because observers need to handle the allocation
191 // being freed before calling free_function, as once the latter is executed
192 // the address becomes available and can be allocated by another thread.
193 // That would be racy otherwise.
194 DoNotifyFreeForShim(address);
195 MUSTTAIL return allocator_dispatch_.next->free_function(address, context);
196 }
197
BatchMallocFnDispatcherImpl198 static unsigned BatchMallocFn(size_t size,
199 void** results,
200 unsigned num_requested,
201 void* context) {
202 unsigned const num_allocated =
203 allocator_dispatch_.next->batch_malloc_function(size, results,
204 num_requested, context);
205 for (unsigned i = 0; i < num_allocated; ++i) {
206 DoNotifyAllocationForShim(results[i], size);
207 }
208 return num_allocated;
209 }
210
BatchFreeFnDispatcherImpl211 static void BatchFreeFn(void** to_be_freed,
212 unsigned num_to_be_freed,
213 void* context) {
214 for (unsigned i = 0; i < num_to_be_freed; ++i) {
215 DoNotifyFreeForShim(to_be_freed[i]);
216 }
217
218 MUSTTAIL return allocator_dispatch_.next->batch_free_function(
219 to_be_freed, num_to_be_freed, context);
220 }
221
FreeDefiniteSizeFnDispatcherImpl222 static void FreeDefiniteSizeFn(void* address, size_t size, void* context) {
223 DoNotifyFreeForShim(address);
224 MUSTTAIL return allocator_dispatch_.next->free_definite_size_function(
225 address, size, context);
226 }
227
TryFreeDefaultFnDispatcherImpl228 static void TryFreeDefaultFn(void* address, void* context) {
229 DoNotifyFreeForShim(address);
230 MUSTTAIL return allocator_dispatch_.next->try_free_default_function(
231 address, context);
232 }
233
AlignedMallocFnDispatcherImpl234 static void* AlignedMallocFn(size_t size, size_t alignment, void* context) {
235 void* const address = allocator_dispatch_.next->aligned_malloc_function(
236 size, alignment, context);
237
238 DoNotifyAllocationForShim(address, size);
239
240 return address;
241 }
242
AlignedMallocUncheckedFnDispatcherImpl243 static void* AlignedMallocUncheckedFn(size_t size,
244 size_t alignment,
245 void* context) {
246 void* const address =
247 allocator_dispatch_.next->aligned_malloc_unchecked_function(
248 size, alignment, context);
249
250 DoNotifyAllocationForShim(address, size);
251
252 return address;
253 }
254
AlignedReallocFnDispatcherImpl255 static void* AlignedReallocFn(void* address,
256 size_t size,
257 size_t alignment,
258 void* context) {
259 // Note: size == 0 actually performs free.
260 DoNotifyFreeForShim(address);
261 address = allocator_dispatch_.next->aligned_realloc_function(
262 address, size, alignment, context);
263
264 DoNotifyAllocationForShim(address, size);
265
266 return address;
267 }
268
AlignedReallocUncheckedFnDispatcherImpl269 static void* AlignedReallocUncheckedFn(void* address,
270 size_t size,
271 size_t alignment,
272 void* context) {
273 // Note: size == 0 actually performs free.
274 DoNotifyFreeForShim(address);
275 address = allocator_dispatch_.next->aligned_realloc_unchecked_function(
276 address, size, alignment, context);
277
278 DoNotifyAllocationForShim(address, size);
279
280 return address;
281 }
282
AlignedFreeFnDispatcherImpl283 static void AlignedFreeFn(void* address, void* context) {
284 DoNotifyFreeForShim(address);
285 MUSTTAIL return allocator_dispatch_.next->aligned_free_function(address,
286 context);
287 }
288
DoNotifyAllocationForShimDispatcherImpl289 ALWAYS_INLINE static void DoNotifyAllocationForShim(void* address,
290 size_t size) {
291 AllocationNotificationData notification_data(
292 address, size, nullptr, AllocationSubsystem::kAllocatorShim);
293
294 DoNotifyAllocation(notification_data);
295 }
296
DoNotifyFreeForShimDispatcherImpl297 ALWAYS_INLINE static void DoNotifyFreeForShim(void* address) {
298 FreeNotificationData notification_data(address,
299 AllocationSubsystem::kAllocatorShim);
300
301 DoNotifyFree(notification_data);
302 }
303
304 static AllocatorDispatch allocator_dispatch_;
305 #endif // PA_BUILDFLAG(USE_ALLOCATOR_SHIM)
306
DoNotifyAllocationDispatcherImpl307 ALWAYS_INLINE static void DoNotifyAllocation(
308 const AllocationNotificationData& notification_data) {
309 PerformAllocationNotification(s_observers, AllObservers{},
310 notification_data);
311 }
312
DoNotifyFreeDispatcherImpl313 ALWAYS_INLINE static void DoNotifyFree(
314 const FreeNotificationData& notification_data) {
315 PerformFreeNotification(s_observers, AllObservers{}, notification_data);
316 }
317
318 static std::tuple<ObserverTypes*...> s_observers;
319 };
320
321 template <typename... ObserverTypes>
322 std::tuple<ObserverTypes*...> DispatcherImpl<ObserverTypes...>::s_observers;
323
324 #if PA_BUILDFLAG(USE_ALLOCATOR_SHIM)
325 template <typename... ObserverTypes>
326 AllocatorDispatch DispatcherImpl<ObserverTypes...>::allocator_dispatch_ = {
327 AllocFn, // alloc_function
328 AllocUncheckedFn, // alloc_unchecked_function
329 AllocZeroInitializedFn, // alloc_zero_initialized_function
330 AllocAlignedFn, // alloc_aligned_function
331 ReallocFn, // realloc_function
332 ReallocUncheckedFn, // realloc_unchecked_function
333 FreeFn, // free_function
334 nullptr, // get_size_estimate_function
335 nullptr, // good_size_function
336 nullptr, // claimed_address_function
337 BatchMallocFn, // batch_malloc_function
338 BatchFreeFn, // batch_free_function
339 FreeDefiniteSizeFn, // free_definite_size_function
340 TryFreeDefaultFn, // try_free_default_function
341 AlignedMallocFn, // aligned_malloc_function
342 AlignedMallocUncheckedFn, // aligned_malloc_unchecked_function
343 AlignedReallocFn, // aligned_realloc_function
344 AlignedReallocUncheckedFn, // aligned_realloc_unchecked_function
345 AlignedFreeFn, // aligned_free_function
346 nullptr // next
347 };
348 #endif // PA_BUILDFLAG(USE_ALLOCATOR_SHIM)
349
350 // Specialization of DispatcherImpl in case we have no observers to notify. In
351 // this special case we return a set of null pointers as the Dispatcher must not
352 // install any hooks at all.
353 template <>
354 struct DispatcherImpl<> {
355 static DispatchData GetNotificationHooks(std::tuple<> /*observers*/) {
356 return DispatchData()
357 #if PA_BUILDFLAG(USE_PARTITION_ALLOC)
358 .SetAllocationObserverHooks(nullptr, nullptr)
359 #endif
360 #if PA_BUILDFLAG(USE_ALLOCATOR_SHIM)
361 .SetAllocatorDispatch(nullptr)
362 #endif
363 ;
364 }
365 };
366
367 // A little utility function that helps using DispatcherImpl by providing
368 // automated type deduction for templates.
369 template <typename... ObserverTypes>
370 inline DispatchData GetNotificationHooks(
371 std::tuple<ObserverTypes*...> observers) {
372 return DispatcherImpl<ObserverTypes...>::GetNotificationHooks(
373 std::move(observers));
374 }
375
376 } // namespace base::allocator::dispatcher::internal
377
378 #endif // BASE_ALLOCATOR_DISPATCHER_INTERNAL_DISPATCHER_INTERNAL_H_
379