See src/snmalloc/README.md for an explanation of the layers. Some other cleanups on the way: Fine-grained stats support is now gone. It's been broken for two years, it depends on iostream (which then causes linker failures with libstdc++) and it's collecting the wrong stats for the new design. After discussion with @mjp41, it's better to remove it and introduce new stats support later, rather than keep broken code in the main branch. Tracing was controlled with a preprocessor macro, now there's also a CMake option.
197 lines
5.2 KiB
C++
197 lines
5.2 KiB
C++
#pragma once
|
|
|
|
#include "../ds/ds.h"
|
|
#include "pooled.h"
|
|
|
|
#include <new>
|
|
|
|
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;
|
|
}
|
|
|
|
auto raw =
|
|
SharedStateHandle::template alloc_meta_data<T>(nullptr, sizeof(T));
|
|
|
|
if (raw == nullptr)
|
|
{
|
|
SharedStateHandle::Pal::error(
|
|
"Failed to initialise thread local allocator.");
|
|
}
|
|
|
|
p = new (raw.unsafe_ptr()) T(std::forward<Args>(args)...);
|
|
|
|
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
|