# Small changes before rewrite
* Additional bit in remote allocator to prevent type confusion with the backend.
* Move Chunk allocator to backend.
* Improvements to RedBlack tree
* Expose message from Pal
# Complete backend rewrite
This provides two key changes:
* We use buddy allocators to allow memory to reconsolidated
* The backend is factored into a series of small operations that
allocate and deallocate memory.
The backend now uses "Ranges", there are two ranges that don't require a
parent range:
* EmptyRange - Never returns any memory
* PalRange - Returns memory from the platform.
All other ranges require a parent range to supply memory to them. Some
ranges support both allocation and deallocation, and some just
deallocation. For instance, CommitRange supports both, and maps
requests to the parent range, but will Commit and Decommit the memory.
As the ranges perform only a single task, they are generally small and
easy to follow. The two exceptions to this are the two BuddyRanges
(Large and Small). Large is for CHUNK_SIZE and above blocks, while
Small is for below CHUNK_SIZE blocks. Both are implemented with a buddy
allocator, but the SmallBuddyRange uses in place meta-data, while the
LargeBuddyRange uses the pagemap for its meta-data. This means the
LargeBuddyRange can keep the majority of memory it is managing
decommitted.
The Backend glues together the various ranges to support the appropriate
way to manage memory on the platform.
196 lines
5.2 KiB
C++
196 lines
5.2 KiB
C++
#pragma once
|
|
|
|
#include "../backend/chunkallocator.h"
|
|
#include "../ds/flaglock.h"
|
|
#include "../ds/mpmcstack.h"
|
|
#include "../pal/pal_concept.h"
|
|
#include "pooled.h"
|
|
|
|
namespace snmalloc
|
|
{
|
|
/**
|
|
* Pool of a particular type of object.
|
|
*
|
|
* This pool will never return objects to the OS. It maintains a list of all
|
|
* objects ever allocated that can be iterated (not concurrency safe). Pooled
|
|
* types can be acquired from the pool, and released back to the pool. This is
|
|
* concurrency safe.
|
|
*
|
|
* This is used to bootstrap the allocation of allocators.
|
|
*/
|
|
template<class T>
|
|
class PoolState
|
|
{
|
|
template<
|
|
typename TT,
|
|
SNMALLOC_CONCEPT(ConceptBackendGlobals) SharedStateHandle,
|
|
PoolState<TT>& get_state()>
|
|
friend class Pool;
|
|
|
|
private:
|
|
MPMCStack<T, PreZeroed> stack;
|
|
FlagWord lock{};
|
|
T* list{nullptr};
|
|
|
|
public:
|
|
constexpr PoolState() = default;
|
|
};
|
|
|
|
/**
|
|
* Helper class used to instantiate a global PoolState.
|
|
*
|
|
* SingletonPoolState::pool is the default provider for the PoolState within
|
|
* the Pool class.
|
|
*/
|
|
template<
|
|
typename T,
|
|
SNMALLOC_CONCEPT(ConceptBackendGlobals) SharedStateHandle>
|
|
class SingletonPoolState
|
|
{
|
|
/**
|
|
* SFINAE helper. Matched only if `T` implements `ensure_init`. Calls it
|
|
* if it exists.
|
|
*/
|
|
template<typename SharedStateHandle_>
|
|
SNMALLOC_FAST_PATH static auto call_ensure_init(SharedStateHandle_*, int)
|
|
-> decltype(SharedStateHandle_::ensure_init())
|
|
{
|
|
static_assert(
|
|
std::is_same<SharedStateHandle, SharedStateHandle_>::value,
|
|
"SFINAE parameter, should only be used with SharedStateHandle");
|
|
SharedStateHandle_::ensure_init();
|
|
}
|
|
|
|
/**
|
|
* SFINAE helper. Matched only if `T` does not implement `ensure_init`.
|
|
* Does nothing if called.
|
|
*/
|
|
template<typename SharedStateHandle_>
|
|
SNMALLOC_FAST_PATH static auto call_ensure_init(SharedStateHandle_*, long)
|
|
{
|
|
static_assert(
|
|
std::is_same<SharedStateHandle, SharedStateHandle_>::value,
|
|
"SFINAE parameter, should only be used with SharedStateHandle");
|
|
}
|
|
|
|
/**
|
|
* Call `SharedStateHandle::ensure_init()` if it is implemented, do nothing
|
|
* otherwise.
|
|
*/
|
|
SNMALLOC_FAST_PATH static void ensure_init()
|
|
{
|
|
call_ensure_init<SharedStateHandle>(nullptr, 0);
|
|
}
|
|
|
|
static void make_pool(PoolState<T>*) noexcept
|
|
{
|
|
ensure_init();
|
|
// Default initializer already called on PoolState, no need to use
|
|
// placement new.
|
|
}
|
|
|
|
public:
|
|
/**
|
|
* Returns a reference for the global PoolState for the given type.
|
|
* Also forces the initialization of the backend state, if needed.
|
|
*/
|
|
SNMALLOC_FAST_PATH static PoolState<T>& pool()
|
|
{
|
|
return Singleton<PoolState<T>, &make_pool>::get();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Wrapper class to access a pool of a particular type of object.
|
|
*
|
|
* The third template argument is a method to retrieve the actual PoolState.
|
|
*
|
|
* For the pool of allocators, refer to the AllocPool alias defined in
|
|
* corealloc.h.
|
|
*
|
|
* For a pool of another type, it is recommended to leave the
|
|
* third template argument with its default value. The SingletonPoolState
|
|
* class is used as a helper to provide a default PoolState management for
|
|
* this use case.
|
|
*/
|
|
template<
|
|
typename T,
|
|
SNMALLOC_CONCEPT(ConceptBackendGlobals) SharedStateHandle,
|
|
PoolState<T>& get_state() = SingletonPoolState<T, SharedStateHandle>::pool>
|
|
class Pool
|
|
{
|
|
public:
|
|
template<typename... Args>
|
|
static T* acquire(Args&&... args)
|
|
{
|
|
PoolState<T>& pool = get_state();
|
|
T* p = pool.stack.pop();
|
|
|
|
if (p != nullptr)
|
|
{
|
|
p->set_in_use();
|
|
return p;
|
|
}
|
|
|
|
p = ChunkAllocator::alloc_meta_data<T, SharedStateHandle>(
|
|
nullptr, std::forward<Args>(args)...);
|
|
|
|
if (p == nullptr)
|
|
{
|
|
SharedStateHandle::Pal::error(
|
|
"Failed to initialise thread local allocator.");
|
|
}
|
|
|
|
FlagLock f(pool.lock);
|
|
p->list_next = pool.list;
|
|
pool.list = p;
|
|
|
|
p->set_in_use();
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Return to the pool an object previously retrieved by `acquire`
|
|
*
|
|
* Do not return objects from `extract`.
|
|
*/
|
|
static void release(T* p)
|
|
{
|
|
// The object's destructor is not run. If the object is "reallocated", it
|
|
// is returned without the constructor being run, so the object is reused
|
|
// without re-initialisation.
|
|
p->reset_in_use();
|
|
get_state().stack.push(p);
|
|
}
|
|
|
|
static T* extract(T* p = nullptr)
|
|
{
|
|
// Returns a linked list of all objects in the stack, emptying the stack.
|
|
if (p == nullptr)
|
|
return get_state().stack.pop_all();
|
|
|
|
return p->next;
|
|
}
|
|
|
|
/**
|
|
* Return to the pool a list of object previously retrieved by `extract`
|
|
*
|
|
* Do not return objects from `acquire`.
|
|
*/
|
|
static void restore(T* first, T* last)
|
|
{
|
|
// Pushes a linked list of objects onto the stack. Use to put a linked
|
|
// list returned by extract back onto the stack.
|
|
get_state().stack.push(first, last);
|
|
}
|
|
|
|
static T* iterate(T* p = nullptr)
|
|
{
|
|
if (p == nullptr)
|
|
return get_state().list;
|
|
|
|
return p->list_next;
|
|
}
|
|
};
|
|
} // namespace snmalloc
|