blob: aeed29ca73176165788fe1f98754d65ceedfff7c [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_ALLOCATOR_DISPATCHER_INTERNAL_DISPATCHER_INTERNAL_H_
#define BASE_ALLOCATOR_DISPATCHER_INTERNAL_DISPATCHER_INTERNAL_H_
#include "base/allocator/dispatcher/configuration.h"
#include "base/allocator/dispatcher/internal/dispatch_data.h"
#include "base/allocator/dispatcher/internal/tools.h"
#include "base/allocator/dispatcher/memory_tagging.h"
#include "base/allocator/dispatcher/notification_data.h"
#include "base/allocator/dispatcher/subsystem.h"
#include "base/check.h"
#include "base/compiler_specific.h"
#include "partition_alloc/buildflags.h"
#if PA_BUILDFLAG(USE_PARTITION_ALLOC)
#include "partition_alloc/partition_alloc_allocation_data.h"
#endif
#if PA_BUILDFLAG(USE_ALLOCATOR_SHIM)
#include "partition_alloc/shim/allocator_shim.h"
#endif
#include <tuple>
namespace base::allocator::dispatcher::internal {
#if PA_BUILDFLAG(USE_ALLOCATOR_SHIM)
using allocator_shim::AllocatorDispatch;
#endif
template <typename CheckObserverPredicate,
typename... ObserverTypes,
size_t... Indices>
void inline PerformObserverCheck(const std::tuple<ObserverTypes...>& observers,
std::index_sequence<Indices...>,
CheckObserverPredicate check_observer) {
([](bool b) { DCHECK(b); }(check_observer(std::get<Indices>(observers))),
...);
}
template <typename... ObserverTypes, size_t... Indices>
ALWAYS_INLINE void PerformAllocationNotification(
const std::tuple<ObserverTypes...>& observers,
std::index_sequence<Indices...>,
const AllocationNotificationData& notification_data) {
((std::get<Indices>(observers)->OnAllocation(notification_data)), ...);
}
template <typename... ObserverTypes, size_t... Indices>
ALWAYS_INLINE void PerformFreeNotification(
const std::tuple<ObserverTypes...>& observers,
std::index_sequence<Indices...>,
const FreeNotificationData& notification_data) {
((std::get<Indices>(observers)->OnFree(notification_data)), ...);
}
// DispatcherImpl provides hooks into the various memory subsystems. These hooks
// are responsible for dispatching any notification to the observers.
// In order to provide as many information on the exact type of the observer and
// prevent any conditional jumps in the hot allocation path, observers are
// stored in a std::tuple. DispatcherImpl performs a CHECK at initialization
// time to ensure they are valid.
template <typename... ObserverTypes>
struct DispatcherImpl {
using AllObservers = std::index_sequence_for<ObserverTypes...>;
template <std::enable_if_t<
internal::LessEqual(sizeof...(ObserverTypes),
configuration::kMaximumNumberOfObservers),
bool> = true>
static DispatchData GetNotificationHooks(
std::tuple<ObserverTypes*...> observers) {
s_observers = std::move(observers);
PerformObserverCheck(s_observers, AllObservers{}, IsValidObserver{});
return CreateDispatchData();
}
private:
static DispatchData CreateDispatchData() {
return DispatchData()
#if PA_BUILDFLAG(USE_PARTITION_ALLOC)
.SetAllocationObserverHooks(&PartitionAllocatorAllocationHook,
&PartitionAllocatorFreeHook)
#endif
#if PA_BUILDFLAG(USE_ALLOCATOR_SHIM)
.SetAllocatorDispatch(&allocator_dispatch_)
#endif
;
}
#if PA_BUILDFLAG(USE_PARTITION_ALLOC)
static void PartitionAllocatorAllocationHook(
const partition_alloc::AllocationNotificationData& pa_notification_data) {
AllocationNotificationData dispatcher_notification_data(
pa_notification_data.address(), pa_notification_data.size(),
pa_notification_data.type_name(),
AllocationSubsystem::kPartitionAllocator);
#if PA_BUILDFLAG(HAS_MEMORY_TAGGING)
dispatcher_notification_data.SetMteReportingMode(
ConvertToMTEMode(pa_notification_data.mte_reporting_mode()));
#endif
DoNotifyAllocation(dispatcher_notification_data);
}
static void PartitionAllocatorFreeHook(
const partition_alloc::FreeNotificationData& pa_notification_data) {
FreeNotificationData dispatcher_notification_data(
pa_notification_data.address(),
AllocationSubsystem::kPartitionAllocator);
#if PA_BUILDFLAG(HAS_MEMORY_TAGGING)
dispatcher_notification_data.SetMteReportingMode(
ConvertToMTEMode(pa_notification_data.mte_reporting_mode()));
#endif
DoNotifyFree(dispatcher_notification_data);
}
#endif // PA_BUILDFLAG(USE_PARTITION_ALLOC)
#if PA_BUILDFLAG(USE_ALLOCATOR_SHIM)
static void* AllocFn(const AllocatorDispatch* self,
size_t size,
void* context) {
void* const address = self->next->alloc_function(self->next, size, context);
DoNotifyAllocationForShim(address, size);
return address;
}
static void* AllocUncheckedFn(const AllocatorDispatch* self,
size_t size,
void* context) {
void* const address =
self->next->alloc_unchecked_function(self->next, size, context);
DoNotifyAllocationForShim(address, size);
return address;
}
static void* AllocZeroInitializedFn(const AllocatorDispatch* self,
size_t n,
size_t size,
void* context) {
void* const address = self->next->alloc_zero_initialized_function(
self->next, n, size, context);
DoNotifyAllocationForShim(address, n * size);
return address;
}
static void* AllocAlignedFn(const AllocatorDispatch* self,
size_t alignment,
size_t size,
void* context) {
void* const address = self->next->alloc_aligned_function(
self->next, alignment, size, context);
DoNotifyAllocationForShim(address, size);
return address;
}
static void* ReallocFn(const AllocatorDispatch* self,
void* address,
size_t size,
void* context) {
// Note: size == 0 actually performs free.
DoNotifyFreeForShim(address);
void* const reallocated_address =
self->next->realloc_function(self->next, address, size, context);
DoNotifyAllocationForShim(reallocated_address, size);
return reallocated_address;
}
static void* ReallocUncheckedFn(const AllocatorDispatch* self,
void* address,
size_t size,
void* context) {
// Note: size == 0 actually performs free.
DoNotifyFreeForShim(address);
void* const reallocated_address = self->next->realloc_unchecked_function(
self->next, address, size, context);
DoNotifyAllocationForShim(reallocated_address, size);
return reallocated_address;
}
static void FreeFn(const AllocatorDispatch* self,
void* address,
void* context) {
// Note: DoNotifyFree should be called before free_function (here and in
// other places). That is because observers need to handle the allocation
// being freed before calling free_function, as once the latter is executed
// the address becomes available and can be allocated by another thread.
// That would be racy otherwise.
DoNotifyFreeForShim(address);
MUSTTAIL return self->next->free_function(self->next, address, context);
}
static unsigned BatchMallocFn(const AllocatorDispatch* self,
size_t size,
void** results,
unsigned num_requested,
void* context) {
unsigned const num_allocated = self->next->batch_malloc_function(
self->next, size, results, num_requested, context);
for (unsigned i = 0; i < num_allocated; ++i) {
DoNotifyAllocationForShim(results[i], size);
}
return num_allocated;
}
static void BatchFreeFn(const AllocatorDispatch* self,
void** to_be_freed,
unsigned num_to_be_freed,
void* context) {
for (unsigned i = 0; i < num_to_be_freed; ++i) {
DoNotifyFreeForShim(to_be_freed[i]);
}
MUSTTAIL return self->next->batch_free_function(self->next, to_be_freed,
num_to_be_freed, context);
}
static void FreeDefiniteSizeFn(const AllocatorDispatch* self,
void* address,
size_t size,
void* context) {
DoNotifyFreeForShim(address);
MUSTTAIL return self->next->free_definite_size_function(self->next, address,
size, context);
}
static void TryFreeDefaultFn(const AllocatorDispatch* self,
void* address,
void* context) {
DoNotifyFreeForShim(address);
MUSTTAIL return self->next->try_free_default_function(self->next, address,
context);
}
static void* AlignedMallocFn(const AllocatorDispatch* self,
size_t size,
size_t alignment,
void* context) {
void* const address = self->next->aligned_malloc_function(
self->next, size, alignment, context);
DoNotifyAllocationForShim(address, size);
return address;
}
static void* AlignedMallocUncheckedFn(const AllocatorDispatch* self,
size_t size,
size_t alignment,
void* context) {
void* const address = self->next->aligned_malloc_unchecked_function(
self->next, size, alignment, context);
DoNotifyAllocationForShim(address, size);
return address;
}
static void* AlignedReallocFn(const AllocatorDispatch* self,
void* address,
size_t size,
size_t alignment,
void* context) {
// Note: size == 0 actually performs free.
DoNotifyFreeForShim(address);
address = self->next->aligned_realloc_function(self->next, address, size,
alignment, context);
DoNotifyAllocationForShim(address, size);
return address;
}
static void* AlignedReallocUncheckedFn(const AllocatorDispatch* self,
void* address,
size_t size,
size_t alignment,
void* context) {
// Note: size == 0 actually performs free.
DoNotifyFreeForShim(address);
address = self->next->aligned_realloc_unchecked_function(
self->next, address, size, alignment, context);
DoNotifyAllocationForShim(address, size);
return address;
}
static void AlignedFreeFn(const AllocatorDispatch* self,
void* address,
void* context) {
DoNotifyFreeForShim(address);
MUSTTAIL return self->next->aligned_free_function(self->next, address,
context);
}
ALWAYS_INLINE static void DoNotifyAllocationForShim(void* address,
size_t size) {
AllocationNotificationData notification_data(
address, size, nullptr, AllocationSubsystem::kAllocatorShim);
DoNotifyAllocation(notification_data);
}
ALWAYS_INLINE static void DoNotifyFreeForShim(void* address) {
FreeNotificationData notification_data(address,
AllocationSubsystem::kAllocatorShim);
DoNotifyFree(notification_data);
}
static AllocatorDispatch allocator_dispatch_;
#endif // PA_BUILDFLAG(USE_ALLOCATOR_SHIM)
ALWAYS_INLINE static void DoNotifyAllocation(
const AllocationNotificationData& notification_data) {
PerformAllocationNotification(s_observers, AllObservers{},
notification_data);
}
ALWAYS_INLINE static void DoNotifyFree(
const FreeNotificationData& notification_data) {
PerformFreeNotification(s_observers, AllObservers{}, notification_data);
}
static std::tuple<ObserverTypes*...> s_observers;
};
template <typename... ObserverTypes>
std::tuple<ObserverTypes*...> DispatcherImpl<ObserverTypes...>::s_observers;
#if PA_BUILDFLAG(USE_ALLOCATOR_SHIM)
template <typename... ObserverTypes>
AllocatorDispatch DispatcherImpl<ObserverTypes...>::allocator_dispatch_ = {
AllocFn, // alloc_function
AllocUncheckedFn, // alloc_unchecked_function
AllocZeroInitializedFn, // alloc_zero_initialized_function
AllocAlignedFn, // alloc_aligned_function
ReallocFn, // realloc_function
ReallocUncheckedFn, // realloc_unchecked_function
FreeFn, // free_function
nullptr, // get_size_estimate_function
nullptr, // good_size_function
nullptr, // claimed_address_function
BatchMallocFn, // batch_malloc_function
BatchFreeFn, // batch_free_function
FreeDefiniteSizeFn, // free_definite_size_function
TryFreeDefaultFn, // try_free_default_function
AlignedMallocFn, // aligned_malloc_function
AlignedMallocUncheckedFn, // aligned_malloc_unchecked_function
AlignedReallocFn, // aligned_realloc_function
AlignedReallocUncheckedFn, // aligned_realloc_unchecked_function
AlignedFreeFn, // aligned_free_function
nullptr // next
};
#endif // PA_BUILDFLAG(USE_ALLOCATOR_SHIM)
// Specialization of DispatcherImpl in case we have no observers to notify. In
// this special case we return a set of null pointers as the Dispatcher must not
// install any hooks at all.
template <>
struct DispatcherImpl<> {
static DispatchData GetNotificationHooks(std::tuple<> /*observers*/) {
return DispatchData()
#if PA_BUILDFLAG(USE_PARTITION_ALLOC)
.SetAllocationObserverHooks(nullptr, nullptr)
#endif
#if PA_BUILDFLAG(USE_ALLOCATOR_SHIM)
.SetAllocatorDispatch(nullptr)
#endif
;
}
};
// A little utility function that helps using DispatcherImpl by providing
// automated type deduction for templates.
template <typename... ObserverTypes>
inline DispatchData GetNotificationHooks(
std::tuple<ObserverTypes*...> observers) {
return DispatcherImpl<ObserverTypes...>::GetNotificationHooks(
std::move(observers));
}
} // namespace base::allocator::dispatcher::internal
#endif // BASE_ALLOCATOR_DISPATCHER_INTERNAL_DISPATCHER_INTERNAL_H_