blob: dd1886dbd89f8676e8377e19c19b3aa1a88e02ae [file] [log] [blame] [edit]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FBL_SLAB_ALLOCATOR_H_
#define FBL_SLAB_ALLOCATOR_H_
#include <zircon/compiler.h>
#include <algorithm>
#include <memory>
#include <new>
#include <type_traits>
#include <utility>
#include <fbl/algorithm.h>
#include <fbl/auto_lock.h>
#include <fbl/intrusive_single_list.h>
#include <fbl/mutex.h>
#include <fbl/null_lock.h>
#include <fbl/ref_ptr.h>
#include <fbl/slab_malloc.h>
// Usage Notes:
//
// fbl::SlabAllocator<> is a utility class which implements a slab-style
// allocator for a given object type. It can be used to dispense either managed
// or unmanaged pointer types. Managed pointers automatically return to the
// allocator when they go completely out of scope, while unmanaged pointers must
// be manually returned to the allocator they came from. Allocators may be
// "static" (meaning that there is only one allocator for the type for the
// entire process), or "instanced" meaning that there may be multiple instances
// of the allocator for the type, each with independent quotas.
//
// :: SlabAllocatorTraits<> ::
// The properties and behavior of a type of slab allocator is controlled using
// the SlabAllocatorTraits<> struct. Things which can be controlled include...
//
// ++ The type of object and pointer created by the allocator.
// ++ The size of the slabs of memory which get allocated.
// ++ The synchronization primitive used to achieve thread safety.
// ++ The static/instanced/manual-delete nature of the allocator.
//
// Details on each of these items are included in the sections below.
//
// :: Memory limits and allocation behavior ::
//
// Slab allocators allocate large regions of memory (slabs) and carve them into
// properly aligned regions just large enough to hold an instance of the
// allocator's object type. Internally, the allocator maintains a list of the
// slabs it has allocated as well as a free list of currently unused blocks of
// object memory.
//
// When allocations are performed...
// 1) Objects from the free list are used first.
// 2) If the free list is empty and the currently active slab has not been
// completely used, a block of object memory is taken from the currently
// active slab.
// 3) If the currently active slab has no more space, and the slab allocation
// limit has not been reached, a new slab will be allocated using malloc and
// single block of object memory will be carved out of it.
// 4) If all of the above fail, the allocation fails an nullptr is returned.
//
// Typically, allocation operations are O(1), but occasionally will be
// O(SlabMalloc::Allocate) if a new slab needs to be allocated. Free operations
// are always O(1).
//
// Slab size is determined by the SLAB_SIZE parameter of the
// SlabAllocatorTraits<> struct and defaults to 16KB. The maximum number of
// slabs the allocator is allow to create is determined at construction time.
// Additionally, an optional bool (default == false) may be passed to the
// constructor telling it to attempt to allocate at least one slab up front.
// Setting the slab limit to 1 and pre-allocating that slab during construction
// will ensure O(1) for all allocations.
//
// :: Thread Safety ::
//
// By default, SlabAllocators use a fbl::Mutex is used internally to ensure
// that allocation and free operations are thread safe. The only external
// function called while holding the internal mutex is ::malloc.
//
// This behavior may be changed by changing the LockType template parameter of
// the SlabAllocatorTraits<> struct to be the name of the class which will
// implement the locking behavior. The class chosen must be compatible with
// fbl::AutoLock. fbl::NullLock may be used if no locking is what is wanted.
// UnlockedInstancedSlabAllocatorTraits or UnlockedStaticSlabAllocatorTraits may
// be used as a shorthand for this.
//
// ** Example **
//
// using MyAllocatorTraits =
// fbl::SlabAllocatorTraits<std::unique_ptr<MyObject>,
// fbl::DEFAULT_SLAB_ALLOCATOR_SLAB_SIZE,
// fbl::NullLock,
// true>;
// fbl::SlabAllocator<MyAllocatorTraits> allocator;
//
// or...
//
// fbl::SlabAllocator<UnlockedStaticSlabAllocator<std::unique_ptr<MyObject>> allocator;
//
// :: Object Requirements ::
//
// Objects must be small enough that at least 1 can be allocated from a slab
// after taking alignment and internal slab bookkeeping into account. If the
// object is too large (unlikely) the slab size must be increased. This
// requirement is enforced with a static_assert, so any problems here should be
// caught at compile time. MyAllocatorType::AllocsPerSlab is a constexpr which
// reports the number of allocations the compiler was able to carve out of each
// slab.
//
// All objects must derive from SlabAllocated<T> where T are the same
// SlabAllocatorTraits<> used to create the SlabAllocator itself.
//
// Deriving from SlabAllocated<> automatically provides the custom deletion
// behavior which allows the pointer to automatically return to the proper
// allocator when delete is called on the pointer (in the case of unmanaged
// pointers) or when the pointer goes completely out of scope (in the case of
// managed pointers).
//
// In the case of instanced slab allocators, the SlabAllocated<> class also
// provides storage for the pointer which will be used for the allocation to
// find its way back to its originating allocator.
//
// In the case of static slab allocators, the SlabAllocated<> class introduces
// no storage overhead to the object, it just supplies the type information
// needed for the object to automatically return to its allocator.
//
// In addition to instanced and static, there is a manual-delete flavor of slab
// allocator as well. Manual-delete allocators are instanced (and have
// independent quotas), but objects allocated from manual-delete slab allocators
// do not pay the cost of a pointer to find their way back to the allocator they
// came from. Instead, it is the user's responsibility to return the object to
// the allocator by calling allocator.Delete(obj_ptr) at the end of its life.
// Users are responsible for tracking which objects came from which allocator.
//
// :: Static Allocator Storage ::
//
// Static slab allocators require that the storage required for the allocator to
// function be declared instantiated in a translation unit somewhere in the
// program. In addition, if the allocator is going to be used outside of just
// this one translation unit, the existance of the storage must be forward
// declared to all of the users of the allocator.
//
// Given the precondition...
//
// class MyObject;
// using SATraits = fbl::StaticAllocatorTraits<std::unique_ptr<MyObject>>;
//
// The formal syntax for forward declaring the existance of the allocator
// storage would be...
//
// template<>
// typename fbl::SlabAllocator<SATraits>::InternalAllocatorType
// fbl::SlabAllocator<SATraits>::allocator_;
//
// And the formal syntax for instantiating the allocator storage would be...
//
// template<>
// typename fbl::SlabAllocator<SATraits>::InternalAllocatorType
// fbl::SlabAllocator<SATraits>::allocator_(ctor_args...);
//
// To ease some of this pain, a two helper macro is provided. In the header
// file in your program defines the allocator, you can write...
//
// FWD_DECL_STATIC_SLAB_ALLOCATOR(SATraits);
//
// Then, in a .cpp file somewhere in your program, you can write...
//
// DECLARE_STATIC_SLAB_ALLOCATOR_STORAGE(SATraits, ctor_args...);
//
// :: API ::
//
// The slab allocator API consists of 2 methods.
// ++ Ctor(size_t, bool)
// ++ New(...)
//
// The allocator constructor takes two arguments. The first is the maximum
// number of slabs the allocator is permitted to allocate. The second is a bool
// which specifies whether or not an attempt should be made to pre-allocate the
// first slab. By default, this defaults to false. As noted earlier, limiting
// the total number of slabs to 1 and pre-allocating the slab during
// construction guarantees O(1) allocations during operation.
//
// New(...) is used to construct and return a pointer (of designated type) to an
// object allocated from slab memory. An appropriate form of nullptr will be
// returned if the allocator has reached its allocation limit. New(...) will
// accept any set of parameters compatible with one of an object's constructors.
//
// ***********************
// ** Unmanaged Example **
// ***********************
//
// class MyObject : public fbl::SinglyLinkedListable<MyObject*> {
// public:
// explicit MyObject(int val) : my_int_(val) { }
// explicit MyObject(const char* val) : my_string_(val) { }
// private:
// int my_int_ = 0;
// const char* my_string_ = nullptr;
// };
//
// /* Make an instanced slab allocator with 4KB slabs which dispenses
// * unmanaged pointers and uses no locks */
// using AllocatorTraits = fbl::UnlockedSlabAllocatorTraits<MyObject*, 4096u>;
// using AllocatorType = fbl::SlabAllocator<AllocatorTraits>;
// using ObjListType = fbl::SinglyLinkedList<MyObject*>;
//
// void my_function() {
// AllocatorType allocator(1, true); /* one pre-allocated slab */
// ObjListType list;
//
// /* Allocate a slab's worth of objects and put them on a list. Use both
// * constructors. */
// for (size_t i = 0; i < AllocatorType::AllocsPerSlab; ++i) {
// auto ptr = FlipACoin()
// ? allocator.New(5) /* int form */
// : allocator.New("this is a string"); /* string form */
// list.push_front(ptr);
// }
//
// /* Do something with all of our objects */
// for (auto& obj_ref : list)
// DoSomething(obj_ref);
//
// /* Return all of the objects to the allocator */
// while(!list.is_empty())
// delete list.pop_front();
// }
//
// ********************
// ** RefPtr Example **
// ********************
//
// /* Make a static slab allocator with default (16KB) sized slabs which
// * dispenses RefPtr<>s and uses default (fbl::Mutex) locking. Give the
// * allocator permission to allocate up to 64 slabs, but do not attempt to
// * pre-allocate the first.
// */
// class MyObject;
// using AllocatorTraits = fbl::StaticSlabAllocatorTraits<fbl::RefPtr<MyObject>>;
// using AllocatorType = fbl::SlabAllocator<AllocatorTraits>;
// using ObjListType = fbl::SinglyLinkedList<fbl::RefPtr<MyObject>>;
//
// DECLARE_STATIC_SLAB_ALLOCATOR_STORAGE(AllocatorTraits, 64);
//
// class MyObject : public fbl::SlabAllocated<AllocatorTraits>,
// public fbl::RefCounted<MyObject>,
// public fbl::SinglyLinkedListable<fbl::RefPtr<MyObject>> {
// public:
// explicit MyObject(int val) : my_int_(val) { }
// explicit MyObject(const char* val) : my_string_(val) { }
// private:
// int my_int_ = 0;
// const char* my_string_ = nullptr;
// };
//
// void my_function() {
// ObjListType list;
//
// /* Allocate two slabs' worth of objects and put them on a list. Use both
// * constructors. */
// for (size_t i = 0; i < (2 * AllocatorType::AllocsPerSlab); ++i) {
// auto ptr = FlipACoin()
// ? AllocatorType::New(5) /* int form */
// : AllocatorType::New("this is a string"); /* string form */
// list.push_front(ptr);
// }
//
// /* Do something with all of our objects */
// for (auto& obj_ref : list)
// DoSomething(obj_ref);
//
// /* Clear the list and automatically return all of our objects */
// list.clear();
// }
//
namespace fbl {
enum class SlabAllocatorFlavor {
INSTANCED,
STATIC,
MANUAL_DELETE,
};
// Flags passed as a template argument to SlabAllocatorTraits which may be used
// to control optional behaviors of the SlabAllocator and its objects.
enum class SlabAllocatorOptions : uint64_t {
None = 0,
// When set, causes the slab allocator to track how many objects are allocated
// at any point in time, as well as the maximum number of currently allocated
// objects over the life of the allocator.
EnableObjectCount = (1 << 0),
// When set, MANUAL slab allocator objects will not assert when their delete
// operator is invoked. This allows objects to exist either as manual slab
// allocated objects, or as objects directly allocated from the heap.
AllowManualDeleteOperator = (1 << 1),
};
// Helper operators which allow us to easily combine and test slab allocator
// option bitfields using a standard bitwise operator syntax. For example:
//
// Options = SlabAllocatorOptions::OptA | SlabAllocatorOptions::OptB
//
// if constexpr (Options & SlabAllocatorOptions::OptA) { ... }
//
constexpr fbl::SlabAllocatorOptions operator|(fbl::SlabAllocatorOptions A,
fbl::SlabAllocatorOptions B) {
return static_cast<fbl::SlabAllocatorOptions>(
static_cast<std::underlying_type<fbl::SlabAllocatorOptions>::type>(A) |
static_cast<std::underlying_type<fbl::SlabAllocatorOptions>::type>(B));
}
constexpr bool operator&(fbl::SlabAllocatorOptions A, fbl::SlabAllocatorOptions B) {
return (static_cast<std::underlying_type<fbl::SlabAllocatorOptions>::type>(A) &
static_cast<std::underlying_type<fbl::SlabAllocatorOptions>::type>(B)) != 0;
}
// fwd decls
template <typename T, size_t SLAB_SIZE, typename LockType, SlabAllocatorFlavor AllocatorFlavor,
SlabAllocatorOptions Options>
struct SlabAllocatorTraits;
template <typename SATraits, typename = void>
class SlabAllocator;
template <typename SATraits, typename = void>
class SlabAllocated;
constexpr size_t DEFAULT_SLAB_ALLOCATOR_SLAB_SIZE = (16 << 10U);
namespace internal {
template <bool>
class SAObjCounter;
template <>
class SAObjCounter<false> {
public:
void Inc(void*) {}
void Dec() {}
void ResetMaxObjCount() {}
size_t obj_count() const { return 0; }
size_t max_obj_count() const { return 0; }
};
template <>
class SAObjCounter<true> {
public:
void Inc(void* allocated_ptr) {
if (allocated_ptr == nullptr) {
return;
}
++obj_count_;
max_obj_count_ = std::max(obj_count_, max_obj_count_);
}
void Dec() { --obj_count_; }
void ResetMaxObjCount() { max_obj_count_ = obj_count_; }
size_t obj_count() const { return obj_count_; }
size_t max_obj_count() const { return max_obj_count_; }
private:
size_t obj_count_ = 0;
size_t max_obj_count_ = 0;
};
// internal fwd-decls
template <typename T>
struct SlabAllocatorPtrTraits;
template <typename SATraits>
class SlabAllocator;
// Support for raw pointers
template <typename T>
struct SlabAllocatorPtrTraits<T*> {
using ObjType = T;
using PtrType = T*;
static constexpr bool IsManaged = false;
static constexpr PtrType CreatePtr(ObjType* ptr) { return ptr; }
};
// Support for std::unique_ptr
template <typename T>
struct SlabAllocatorPtrTraits<std::unique_ptr<T>> {
using ObjType = T;
using PtrType = std::unique_ptr<T>;
static constexpr bool IsManaged = true;
static constexpr PtrType CreatePtr(ObjType* ptr) { return PtrType(ptr); }
};
// Support for RefPtr
template <typename T>
struct SlabAllocatorPtrTraits<RefPtr<T>> {
using ObjType = T;
using PtrType = RefPtr<T>;
static constexpr bool IsManaged = true;
static constexpr PtrType CreatePtr(ObjType* ptr) { return AdoptRef<ObjType>(ptr); }
};
// Trait class used to set the origin of a slab allocated object, if needed.
template <typename SATraits, typename = void>
struct SlabOriginSetter {
static inline void SetOrigin(typename SATraits::ObjType* ptr,
internal::SlabAllocator<SATraits>* origin) {
ZX_DEBUG_ASSERT((ptr != nullptr) && (origin != nullptr));
ptr->slab_origin_ = origin;
}
};
// Slab allocated objects from STATIC and MANUAL_DELETE slab allocators do not
// have (or need) a slab_origin. Their "origin setter" is a no-op.
template <typename SATraits>
struct SlabOriginSetter<
SATraits, std::enable_if_t<(SATraits::AllocatorFlavor == SlabAllocatorFlavor::STATIC) ||
(SATraits::AllocatorFlavor == SlabAllocatorFlavor::MANUAL_DELETE)>> {
static inline void SetOrigin(typename SATraits::ObjType* ptr,
internal::SlabAllocator<SATraits>* origin) {}
};
// Non-templated SlabAllocatorBase. Any code which does not strictly depend on
// trait/type awareness lives here in order to minimize code size explosion due
// to template expansion.
class SlabAllocatorBase {
protected:
struct FreeListEntry
: public SinglyLinkedListable<FreeListEntry*, NodeOptions::AllowClearUnsafe> {};
struct Slab {
explicit Slab(size_t initial_bytes_used) : bytes_used_(initial_bytes_used) {}
void* Allocate(size_t alloc_size, size_t slab_storage_limit) {
if ((bytes_used_ + alloc_size) > slab_storage_limit)
return nullptr;
void* ret = storage_ + bytes_used_;
bytes_used_ += alloc_size;
return ret;
}
SinglyLinkedListNodeState<Slab*> sll_node_state_;
size_t bytes_used_;
uint8_t storage_[];
};
static constexpr size_t SlabOverhead = offsetof(Slab, storage_);
public:
DISALLOW_COPY_ASSIGN_AND_MOVE(SlabAllocatorBase);
SlabAllocatorBase(size_t slab_size, size_t alloc_size, size_t alloc_alignment,
size_t initial_slab_used, size_t max_slabs, bool alloc_initial)
: slab_size_(slab_size),
slab_alignment_(std::max(alignof(Slab), alloc_alignment)),
slab_storage_limit_(slab_size - SlabOverhead + initial_slab_used),
alloc_size_(alloc_size),
initial_slab_used_(initial_slab_used),
max_slabs_(max_slabs) {
// Attempt to ensure that at least one slab has been allocated before
// finishing construction if the user has asked us to do so. In some
// situations, this can help to ensure that allocation performance is
// always O(1), provided that the slab limit has been configured to be
// 1.
if (alloc_initial) {
// No need to take the lock here, no one can possible know about us
// yet.
void* first_alloc = AllocateLocked();
if (first_alloc != nullptr)
ReturnToFreeListLocked(first_alloc);
}
}
~SlabAllocatorBase() {
#if ZX_DEBUG_ASSERT_IMPLEMENTED
size_t allocated_count = 0;
size_t free_list_size = this->free_list_.size_slow();
#endif
// null out the free list so that it does not assert that we left
// unmanaged pointers on it as we destruct, and so that the free list
// does not attempt to auto-destruct the managed objects which were
// present on it after the slab memory has been freed
this->free_list_.clear_unsafe();
while (!slab_list_.is_empty()) {
Slab* free_me = slab_list_.pop_front();
#if ZX_DEBUG_ASSERT_IMPLEMENTED
size_t bytes_used = free_me->bytes_used_ - initial_slab_used_;
ZX_DEBUG_ASSERT(free_me->bytes_used_ >= initial_slab_used_);
ZX_DEBUG_ASSERT((bytes_used % alloc_size_) == 0);
allocated_count += (bytes_used / alloc_size_);
#endif
SlabMalloc::Free(reinterpret_cast<void*>(free_me));
}
// Make sure that everything which was ever allocated had been returned
// to the free list before we were destroyed.
ZX_DEBUG_ASSERT_COND(free_list_size == allocated_count);
}
size_t max_slabs() const { return max_slabs_; }
size_t slab_count() const { return slab_count_; }
protected:
void* AllocateLocked() {
// If we can alloc from the free list, do so.
if (!free_list_.is_empty()) {
return free_list_.pop_front();
}
// If we can allocate from the currently active slab, do so.
if (!slab_list_.is_empty()) {
auto& active_slab = slab_list_.front();
void* mem = active_slab.Allocate(alloc_size_, slab_storage_limit_);
if (mem)
return mem;
}
// If we are allowed to allocate new slabs, try to do so.
if (slab_count_ < max_slabs_) {
void* slab_mem = SlabMalloc::Allocate(slab_size_, slab_alignment_);
if (slab_mem != nullptr) {
Slab* slab = new (slab_mem) Slab(initial_slab_used_);
slab_count_++;
slab_list_.push_front(slab);
return slab->Allocate(alloc_size_, slab_storage_limit_);
}
}
// Looks like we have run out of resources.
return nullptr;
}
void ReturnToFreeListLocked(void* ptr) {
FreeListEntry* free_obj = new (ptr) FreeListEntry;
free_list_.push_front(free_obj);
}
private:
// Constant properties of the allocator passed to us by our templated
// wrapper during construction.
const size_t slab_size_;
const size_t slab_alignment_;
const size_t slab_storage_limit_;
const size_t alloc_size_;
const size_t initial_slab_used_;
const size_t max_slabs_;
SinglyLinkedList<FreeListEntry*> free_list_;
SinglyLinkedList<Slab*> slab_list_;
size_t slab_count_ = 0;
};
template <typename SATraits>
class SlabAllocator : public SlabAllocatorBase {
public:
using PtrTraits = typename SATraits::PtrTraits;
using PtrType = typename SATraits::PtrType;
using ObjType = typename SATraits::ObjType;
protected:
static constexpr size_t SLAB_SIZE = SATraits::SLAB_SIZE;
static constexpr size_t AllocSize = std::max(sizeof(FreeListEntry), sizeof(ObjType));
static constexpr size_t AllocAlign = std::max(alignof(FreeListEntry), alignof(ObjType));
static_assert(AllocAlign > 0, "Alignment requirements cannot be zero!");
static_assert(!(AllocSize % AllocAlign),
"Allocation size must be a multiple of allocation alignment!");
static constexpr size_t SlabStorageMisalignment = SlabAllocatorBase::SlabOverhead % AllocAlign;
static constexpr size_t InitialSlabUse =
SlabStorageMisalignment ? AllocAlign - SlabStorageMisalignment : 0;
static constexpr size_t TotalSlabOverhead = SlabAllocatorBase::SlabOverhead + InitialSlabUse;
static_assert((sizeof(Slab) < SATraits::SLAB_SIZE) || (TotalSlabOverhead < SATraits::SLAB_SIZE),
"SLAB_SIZE too small to hold slab bookkeeping");
public:
static constexpr size_t AllocsPerSlab = (SLAB_SIZE - TotalSlabOverhead) / AllocSize;
static_assert(AllocsPerSlab > 0, "SLAB_SIZE too small to hold even 1 allocation");
// Slab allocated objects must derive from SlabAllocated<SATraits>.
static_assert(std::is_base_of_v<SlabAllocated<SATraits>, ObjType>,
"Objects which are slab allocated from an allocator of type "
"SlabAllocator<T> must derive from SlabAllocated<T>.");
DISALLOW_COPY_ASSIGN_AND_MOVE(SlabAllocator);
explicit SlabAllocator(size_t max_slabs, bool alloc_initial = false)
: SlabAllocatorBase(SLAB_SIZE, AllocSize, AllocAlign, InitialSlabUse, max_slabs,
alloc_initial) {}
~SlabAllocator() {}
template <typename... ConstructorSignature>
PtrType New(ConstructorSignature&&... args) {
void* mem = Allocate();
if (mem == nullptr)
return nullptr;
// Construct the object
//
// Note: This rather odd forwarding of this construction operation to
// the non-internal form of the slab allocator is deliberate. This
// prevents object with private constructors from needing to be friends
// of a fbl::internal class (a class which they should not need to know
// about).
ObjType* obj = ::fbl::SlabAllocator<SATraits>::ConstructObject(
mem, std::forward<ConstructorSignature>(args)...);
// Now, record the slab allocator this object came from so it can be
// returned later on.
//
// Note: This is a no-op in the case of an object which came from a
// static slab allocator (who's road home is determined purely by type)
SlabOriginSetter<SATraits>::SetOrigin(obj, this);
return PtrTraits::CreatePtr(obj);
}
size_t obj_count() const {
static_assert(SATraits::Options & SlabAllocatorOptions::EnableObjectCount,
"Error accessing obj_count: Object counter not enabled in SATraits.");
return sa_obj_counter_.obj_count();
}
size_t max_obj_count() const {
static_assert(SATraits::Options & SlabAllocatorOptions::EnableObjectCount,
"Error accessing max_obj_count: Object counter not enabled in SATraits.");
return sa_obj_counter_.max_obj_count();
}
void ResetMaxObjCount() {
static_assert(SATraits::Options & SlabAllocatorOptions::EnableObjectCount,
"Error performing ResetMaxObjCount: Object counter not enabled in SATraits.");
AutoLock alloc_lock(&alloc_lock_);
sa_obj_counter_.ResetMaxObjCount();
}
protected:
friend class ::fbl::SlabAllocator<SATraits>;
friend class ::fbl::SlabAllocated<SATraits>;
void* Allocate() {
AutoLock alloc_lock(&this->alloc_lock_);
void* ptr = AllocateLocked();
sa_obj_counter_.Inc(ptr);
return ptr;
}
void ReturnToFreeList(void* ptr) {
FreeListEntry* free_obj = new (ptr) FreeListEntry;
{
AutoLock alloc_lock(&alloc_lock_);
ReturnToFreeListLocked(free_obj);
sa_obj_counter_.Dec();
}
}
typename SATraits::LockType alloc_lock_;
SAObjCounter<SATraits::Options & SlabAllocatorOptions::EnableObjectCount> sa_obj_counter_;
};
} // namespace internal
////////////////////////////////////////////////////////////////////////////////
//
// Fundamental traits which control the properties of a slab allocator.
//
// Parameters:
// ++ T
// The pointer type of the object to be created by the allocator. Must be one
// of the following...
// ++ ObjectType*
// ++ std::unique_ptr<ObjectType>
// ++ fbl::RefPtr<ObjectType>
//
// ++ SLAB_SIZE
// The size (in bytes) of an individual slab. Defaults to 16KB
//
// ++ LockType
// The fbl::AutoLock compatible class which will handle synchronization.
//
// ++ AllocatorType
// Selects between a the three flavors of allocator.
// ++ INSTANCED - Allocations come from an instance of an allocator.
// Allocation objects carry the overhead of an "origin pointer" which will
// be used to find their way home when the delete operator is applied to the
// object. Each instance of an allocator has it's own slab quota.
// ++ STATIC - Allocations come from a static instance of an allocator. There
// is only one allocation pool for the entire process. All allocator
// methods are static members of the allocator's type and use the
// MyAllocator::Method syntax (instead of my_allocator.Method). Allocation
// objects carry no overhead and will find their way home based on type when
// the delete operator is applied to them.
// ++ MANUAL_DELETE - A type of INSTANCED allocator where objects have no
// pointer overhead. The delete operator of the object is hidden in the
// SlabAllocated<> class preventing users from delete'ing these objects, and
// will also trigger a static_assert if invoked. Users must be aware of
// where their allocations came from and are responsible for calling
// allocator.Delete in order to destruct and return the object to the
// allocator it came from.
//
////////////////////////////////////////////////////////////////////////////////
template <typename T, size_t _SLAB_SIZE = DEFAULT_SLAB_ALLOCATOR_SLAB_SIZE,
typename _LockType = ::fbl::Mutex,
SlabAllocatorFlavor _AllocatorFlavor = SlabAllocatorFlavor::INSTANCED,
SlabAllocatorOptions _Options = SlabAllocatorOptions::None>
struct SlabAllocatorTraits {
using PtrTraits = internal::SlabAllocatorPtrTraits<T>;
using PtrType = typename PtrTraits::PtrType;
using ObjType = typename PtrTraits::ObjType;
using LockType = _LockType;
static constexpr size_t SLAB_SIZE = _SLAB_SIZE;
static constexpr SlabAllocatorFlavor AllocatorFlavor = _AllocatorFlavor;
static constexpr SlabAllocatorOptions Options = _Options;
};
////////////////////////////////////////////////////////////////////////////////
//
// Implementation of an instanced slab allocator.
//
////////////////////////////////////////////////////////////////////////////////
template <typename SATraits>
class SlabAllocator<
SATraits, std::enable_if_t<(SATraits::AllocatorFlavor == SlabAllocatorFlavor::INSTANCED) ||
(SATraits::AllocatorFlavor == SlabAllocatorFlavor::MANUAL_DELETE)>>
: public internal::SlabAllocator<SATraits> {
public:
static_assert((SATraits::AllocatorFlavor == SlabAllocatorFlavor::MANUAL_DELETE) ||
!(SATraits::Options & SlabAllocatorOptions::AllowManualDeleteOperator),
"AllowManualDeleteOperator may only be used with MANUAL_DELETE slab allocators");
using PtrTraits = typename SATraits::PtrTraits;
using PtrType = typename SATraits::PtrType;
using ObjType = typename SATraits::ObjType;
using BaseAllocatorType = internal::SlabAllocator<SATraits>;
static constexpr size_t AllocsPerSlab = BaseAllocatorType::AllocsPerSlab;
explicit SlabAllocator(size_t max_slabs, bool alloc_initial = false)
: BaseAllocatorType(max_slabs, alloc_initial) {}
~SlabAllocator() {}
void Delete(ObjType* ptr) {
static_assert(SATraits::AllocatorFlavor == SlabAllocatorFlavor::MANUAL_DELETE,
"Only MANUAL_DELETE slab allocators have a Delete method!");
ptr->~ObjType();
BaseAllocatorType::ReturnToFreeList(ptr);
}
private:
friend class internal::SlabAllocator<SATraits>; // internal::SA<> gets to call ConstructObject
template <typename... ConstructorSignature>
static ObjType* ConstructObject(void* mem, ConstructorSignature&&... args) {
return new (mem) ObjType(std::forward<ConstructorSignature>(args)...);
}
};
template <typename SATraits>
class SlabAllocated<
SATraits, std::enable_if_t<(SATraits::AllocatorFlavor == SlabAllocatorFlavor::INSTANCED)>> {
public:
static_assert(!(SATraits::Options & SlabAllocatorOptions::AllowManualDeleteOperator),
"AllowManualDeleteOperator may only be used with MANUAL_DELETE slab allocators");
using AllocatorType = internal::SlabAllocator<SATraits>;
using ObjType = typename SATraits::ObjType;
SlabAllocated() {}
~SlabAllocated() {}
DISALLOW_COPY_ASSIGN_AND_MOVE(SlabAllocated);
void operator delete(void* ptr) {
// Note: this is a bit sketchy... We have been destructed at this point
// in time, but we are about to access our slab_origin_ member variable.
// The *only* reason that this is OK is that we know that our destructor
// does not touch slab_origin_, and no one else in our hierarchy should
// be able to modify slab_origin_ because it is private.
ObjType* obj_ptr = reinterpret_cast<ObjType*>(ptr);
ZX_DEBUG_ASSERT(obj_ptr != nullptr);
ZX_DEBUG_ASSERT(obj_ptr->slab_origin_ != nullptr);
obj_ptr->slab_origin_->ReturnToFreeList(obj_ptr);
}
private:
friend struct internal::SlabOriginSetter<SATraits>;
AllocatorType* slab_origin_ = nullptr;
};
template <typename SATraits>
class SlabAllocated<
SATraits, std::enable_if_t<(SATraits::PtrTraits::IsManaged == false) &&
(SATraits::AllocatorFlavor == SlabAllocatorFlavor::MANUAL_DELETE)>> {
public:
SlabAllocated() {}
~SlabAllocated() {}
DISALLOW_COPY_ASSIGN_AND_MOVE(SlabAllocated);
protected:
// Object which come from a MANUAL_DELETE slab allocator may not be destroyed
// using the delete operator unless the user has explicitly allowed it by
// passing the AllowManualDeleteOperator in their Options. Instead, users
// must return the object to its allocator using the Delete method of the
// allocator instance.
//
// Hide the delete operator, and halt-and-catch-fire if some Bad Person ever
// manages to generate a call to this operator without passing the option.
// Otherwise, make sure that we passthru to the global delete operator.
void operator delete(void* obj) {
if constexpr (SATraits::Options & SlabAllocatorOptions::AllowManualDeleteOperator) {
::operator delete(obj);
} else {
ZX_DEBUG_ASSERT(false);
}
}
};
// Shorthand for declaring the properties of an instanced allocator (somewhat
// superfluous as the default is instanced)
template <typename T, size_t SLAB_SIZE = DEFAULT_SLAB_ALLOCATOR_SLAB_SIZE,
typename LockType = ::fbl::Mutex,
SlabAllocatorOptions Options = SlabAllocatorOptions::None>
using InstancedSlabAllocatorTraits =
SlabAllocatorTraits<T, SLAB_SIZE, LockType, SlabAllocatorFlavor::INSTANCED, Options>;
template <typename T, size_t SLAB_SIZE = DEFAULT_SLAB_ALLOCATOR_SLAB_SIZE,
SlabAllocatorOptions Options = SlabAllocatorOptions::None>
using UnlockedInstancedSlabAllocatorTraits =
SlabAllocatorTraits<T, SLAB_SIZE, ::fbl::NullLock, SlabAllocatorFlavor::INSTANCED, Options>;
template <typename T, size_t SLAB_SIZE = DEFAULT_SLAB_ALLOCATOR_SLAB_SIZE,
SlabAllocatorOptions Options = SlabAllocatorOptions::None>
using UnlockedSlabAllocatorTraits =
SlabAllocatorTraits<T, SLAB_SIZE, ::fbl::NullLock, SlabAllocatorFlavor::INSTANCED, Options>;
// Shorthand for declaring the properties of a MANUAL_DELETE slab allocator.
template <typename T, size_t SLAB_SIZE = DEFAULT_SLAB_ALLOCATOR_SLAB_SIZE,
typename LockType = ::fbl::Mutex,
SlabAllocatorOptions Options = SlabAllocatorOptions::None>
using ManualDeleteSlabAllocatorTraits =
SlabAllocatorTraits<T, SLAB_SIZE, LockType, SlabAllocatorFlavor::MANUAL_DELETE, Options>;
template <typename T, size_t SLAB_SIZE = DEFAULT_SLAB_ALLOCATOR_SLAB_SIZE,
SlabAllocatorOptions Options = SlabAllocatorOptions::None>
using UnlockedManualDeleteSlabAllocatorTraits =
SlabAllocatorTraits<T, SLAB_SIZE, ::fbl::NullLock, SlabAllocatorFlavor::MANUAL_DELETE, Options>;
////////////////////////////////////////////////////////////////////////////////
//
// Implementation of a static slab allocator.
//
////////////////////////////////////////////////////////////////////////////////
template <typename SATraits>
class SlabAllocator<SATraits,
std::enable_if_t<(SATraits::AllocatorFlavor == SlabAllocatorFlavor::STATIC)>> {
public:
static_assert(!(SATraits::Options & SlabAllocatorOptions::AllowManualDeleteOperator),
"AllowManualDeleteOperator may only be used with MANUAL_DELETE slab allocators");
using PtrTraits = typename SATraits::PtrTraits;
using PtrType = typename SATraits::PtrType;
using ObjType = typename SATraits::ObjType;
using InternalAllocatorType = internal::SlabAllocator<SATraits>;
static constexpr size_t AllocsPerSlab = InternalAllocatorType::AllocsPerSlab;
// Do not allow instantiation of static slab allocators.
SlabAllocator() = delete;
template <typename... ConstructorSignature>
static PtrType New(ConstructorSignature&&... args) {
return allocator_.New(std::forward<ConstructorSignature>(args)...);
}
static size_t max_slabs() { return allocator_.max_slabs(); }
static size_t obj_count() { return allocator_.obj_count(); }
static size_t max_obj_count() { return allocator_.max_obj_count(); }
static size_t slab_count() { return allocator_.slab_count(); }
static void ResetMaxObjCount() { allocator_.ResetMaxObjCount(); }
private:
friend class SlabAllocated<SATraits>; // SlabAllocated<> gets to call ReturnToFreeList
friend class internal::SlabAllocator<SATraits>; // internal::SA<> gets to call ConstructObject
template <typename... ConstructorSignature>
static ObjType* ConstructObject(void* mem, ConstructorSignature&&... args) {
return new (mem) ObjType(std::forward<ConstructorSignature>(args)...);
}
static void ReturnToFreeList(void* ptr) { allocator_.ReturnToFreeList(ptr); }
static InternalAllocatorType allocator_;
};
template <typename SATraits>
class SlabAllocated<SATraits,
std::enable_if_t<(SATraits::AllocatorFlavor == SlabAllocatorFlavor::STATIC)>> {
public:
static_assert(!(SATraits::Options & SlabAllocatorOptions::AllowManualDeleteOperator),
"AllowManualDeleteOperator may only be used with MANUAL_DELETE slab allocators");
SlabAllocated() {}
DISALLOW_COPY_ASSIGN_AND_MOVE(SlabAllocated);
using AllocatorType = SlabAllocator<SATraits>;
using ObjType = typename SATraits::ObjType;
void operator delete(void* ptr) {
ZX_DEBUG_ASSERT(ptr != nullptr);
AllocatorType::ReturnToFreeList(reinterpret_cast<ObjType*>(ptr));
}
};
// Shorthand for declaring the properties of a static allocator
template <typename T, size_t SLAB_SIZE = DEFAULT_SLAB_ALLOCATOR_SLAB_SIZE,
typename LockType = ::fbl::Mutex,
SlabAllocatorOptions Options = SlabAllocatorOptions::None>
using StaticSlabAllocatorTraits =
SlabAllocatorTraits<T, SLAB_SIZE, LockType, SlabAllocatorFlavor::STATIC, Options>;
template <typename T, size_t SLAB_SIZE = DEFAULT_SLAB_ALLOCATOR_SLAB_SIZE,
SlabAllocatorOptions Options = SlabAllocatorOptions::None>
using UnlockedStaticSlabAllocatorTraits =
SlabAllocatorTraits<T, SLAB_SIZE, ::fbl::NullLock, SlabAllocatorFlavor::STATIC, Options>;
// Shorthand for declaring the global storage required for a static allocator
#define DECLARE_STATIC_SLAB_ALLOCATOR_STORAGE(ALLOC_TRAITS, ...) \
template <> \
::fbl::SlabAllocator<ALLOC_TRAITS>::InternalAllocatorType \
fbl::SlabAllocator<ALLOC_TRAITS>::allocator_(__VA_ARGS__)
// Shorthand for forward declaring the existance of the storage required to use
// a static slab allocator. Use this macro in your header file if your static
// slab allocator is to be used outside of a single translational unit.
#define FWD_DECL_STATIC_SLAB_ALLOCATOR(ALLOC_TRAITS) \
template <> \
::fbl::SlabAllocator<ALLOC_TRAITS>::InternalAllocatorType \
fbl::SlabAllocator<ALLOC_TRAITS>::allocator_
} // namespace fbl
#endif // FBL_SLAB_ALLOCATOR_H_