Major refactor of snmalloc (#343)
# Pagemap The Pagemap now stores all the meta-data for the object allocation. The meta-data in the pagemap is effectively a triple of the sizeclass, the remote allocator, and a pointer to a 64 byte block of meta-data for this chunk of memory. By storing the pointer to a block, it allows the pagemap to handle multiple slab sizes without branching on the fast path. There is one entry in the pagemap per 16KiB of address space, but by using the same entry in the pagemap for 4 adjacent entries, then we can treat a 64KiB range can be treated as a single slab of allocations. This change also means there is almost no capability amplification required by the implementation on CHERI for finding meta-data. The only amplification is required, when we change the way a chunk is used to a size of object allocation. # Backend There is a second major aspect of the refactor that there is now a narrow API that abstracts the Pagemap, PAL and address space management. This should better enable the compartmentalisation and makes it easier to produce alternative backends for various research directions. This is a template parameter that can be used to specialised by the front-end in different ways. # Thread local state The thread local state has been refactored into two components, one (called 'localalloc') that is stored directly in the TLS and is constant initialised, and one that is allocated in the address space (called 'coreallloc') which is lazily created and pooled. # Difference This removes Superslabs/Medium slabs as there meta-data is now part of the pagemap.
This commit is contained in:
committed by
GitHub
parent
18d7cc99b6
commit
f0e2ab702a
@@ -1,5 +1,16 @@
|
||||
#include "../mem/slowalloc.h"
|
||||
#include "../snmalloc.h"
|
||||
// Core implementation of snmalloc independent of the configuration mode
|
||||
#include "../snmalloc_core.h"
|
||||
|
||||
#ifndef SNMALLOC_PROVIDE_OWN_CONFIG
|
||||
// The default configuration for snmalloc is used if alternative not defined
|
||||
namespace snmalloc
|
||||
{
|
||||
using Alloc = snmalloc::LocalAllocator<snmalloc::Globals>;
|
||||
} // namespace snmalloc
|
||||
#endif
|
||||
|
||||
// User facing API surface, needs to know what `Alloc` is.
|
||||
#include "../snmalloc_front.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
@@ -24,78 +35,60 @@ using namespace snmalloc;
|
||||
|
||||
extern "C"
|
||||
{
|
||||
void SNMALLOC_NAME_MANGLE(check_start)(void* ptr)
|
||||
{
|
||||
#if !defined(NDEBUG) && !defined(SNMALLOC_PASS_THROUGH)
|
||||
if (ThreadAlloc::get_noncachable()->external_pointer<Start>(ptr) != ptr)
|
||||
{
|
||||
error("Using pointer that is not to the start of an allocation");
|
||||
}
|
||||
#else
|
||||
UNUSED(ptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
SNMALLOC_EXPORT void* SNMALLOC_NAME_MANGLE(__malloc_end_pointer)(void* ptr)
|
||||
{
|
||||
return ThreadAlloc::get_noncachable()->external_pointer<OnePastEnd>(ptr);
|
||||
return ThreadAlloc::get().external_pointer<OnePastEnd>(ptr);
|
||||
}
|
||||
|
||||
SNMALLOC_EXPORT void* SNMALLOC_NAME_MANGLE(malloc)(size_t size)
|
||||
{
|
||||
return ThreadAlloc::get_noncachable()->alloc(size);
|
||||
return ThreadAlloc::get().alloc(size);
|
||||
}
|
||||
|
||||
SNMALLOC_EXPORT void SNMALLOC_NAME_MANGLE(free)(void* ptr)
|
||||
{
|
||||
SNMALLOC_NAME_MANGLE(check_start)(ptr);
|
||||
ThreadAlloc::get_noncachable()->dealloc(ptr);
|
||||
ThreadAlloc::get().dealloc(ptr);
|
||||
}
|
||||
|
||||
SNMALLOC_EXPORT void SNMALLOC_NAME_MANGLE(cfree)(void* ptr)
|
||||
{
|
||||
SNMALLOC_NAME_MANGLE(free)(ptr);
|
||||
ThreadAlloc::get().dealloc(ptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clang was helpfully inlining the constant return value, and
|
||||
* thus converting from a tail call to an ordinary call.
|
||||
*/
|
||||
SNMALLOC_EXPORT inline void* snmalloc_not_allocated = nullptr;
|
||||
|
||||
static SNMALLOC_SLOW_PATH void* SNMALLOC_NAME_MANGLE(snmalloc_set_error)()
|
||||
{
|
||||
errno = ENOMEM;
|
||||
return snmalloc_not_allocated;
|
||||
}
|
||||
|
||||
SNMALLOC_EXPORT void* SNMALLOC_NAME_MANGLE(calloc)(size_t nmemb, size_t size)
|
||||
{
|
||||
bool overflow = false;
|
||||
size_t sz = bits::umul(size, nmemb, overflow);
|
||||
if (overflow)
|
||||
if (unlikely(overflow))
|
||||
{
|
||||
errno = ENOMEM;
|
||||
return nullptr;
|
||||
return SNMALLOC_NAME_MANGLE(snmalloc_set_error)();
|
||||
}
|
||||
return ThreadAlloc::get_noncachable()->alloc<ZeroMem::YesZero>(sz);
|
||||
return ThreadAlloc::get().alloc<ZeroMem::YesZero>(sz);
|
||||
}
|
||||
|
||||
SNMALLOC_EXPORT
|
||||
size_t SNMALLOC_NAME_MANGLE(malloc_usable_size)(
|
||||
MALLOC_USABLE_SIZE_QUALIFIER void* ptr)
|
||||
{
|
||||
return ThreadAlloc::get_noncachable()->alloc_size(ptr);
|
||||
return ThreadAlloc::get().alloc_size(ptr);
|
||||
}
|
||||
|
||||
SNMALLOC_EXPORT void* SNMALLOC_NAME_MANGLE(realloc)(void* ptr, size_t size)
|
||||
{
|
||||
if (size == (size_t)-1)
|
||||
{
|
||||
errno = ENOMEM;
|
||||
return nullptr;
|
||||
}
|
||||
if (ptr == nullptr)
|
||||
{
|
||||
return SNMALLOC_NAME_MANGLE(malloc)(size);
|
||||
}
|
||||
if (size == 0)
|
||||
{
|
||||
SNMALLOC_NAME_MANGLE(free)(ptr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SNMALLOC_NAME_MANGLE(check_start)(ptr);
|
||||
|
||||
size_t sz = ThreadAlloc::get_noncachable()->alloc_size(ptr);
|
||||
auto& a = ThreadAlloc::get();
|
||||
size_t sz = a.alloc_size(ptr);
|
||||
// Keep the current allocation if the given size is in the same sizeclass.
|
||||
if (sz == round_size(size))
|
||||
{
|
||||
@@ -108,13 +101,26 @@ extern "C"
|
||||
return ptr;
|
||||
#endif
|
||||
}
|
||||
void* p = SNMALLOC_NAME_MANGLE(malloc)(size);
|
||||
if (p != nullptr)
|
||||
|
||||
if (size == (size_t)-1)
|
||||
{
|
||||
errno = ENOMEM;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void* p = a.alloc(size);
|
||||
if (likely(p != nullptr))
|
||||
{
|
||||
SNMALLOC_NAME_MANGLE(check_start)(p);
|
||||
sz = bits::min(size, sz);
|
||||
memcpy(p, ptr, sz);
|
||||
SNMALLOC_NAME_MANGLE(free)(ptr);
|
||||
// Guard memcpy as GCC is assuming not nullptr for ptr after the memcpy
|
||||
// otherwise.
|
||||
if (sz != 0)
|
||||
memcpy(p, ptr, sz);
|
||||
a.dealloc(ptr);
|
||||
}
|
||||
else if (likely(size == 0))
|
||||
{
|
||||
a.dealloc(ptr);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
@@ -149,8 +155,7 @@ extern "C"
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return SNMALLOC_NAME_MANGLE(malloc)(
|
||||
size ? aligned_size(alignment, size) : alignment);
|
||||
return SNMALLOC_NAME_MANGLE(malloc)(aligned_size(alignment, size));
|
||||
}
|
||||
|
||||
SNMALLOC_EXPORT void*
|
||||
@@ -163,17 +168,16 @@ extern "C"
|
||||
SNMALLOC_EXPORT int SNMALLOC_NAME_MANGLE(posix_memalign)(
|
||||
void** memptr, size_t alignment, size_t size)
|
||||
{
|
||||
if (
|
||||
((alignment % sizeof(uintptr_t)) != 0) ||
|
||||
((alignment & (alignment - 1)) != 0) || (alignment == 0))
|
||||
if ((alignment < sizeof(uintptr_t) || ((alignment & (alignment - 1)) != 0)))
|
||||
{
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
void* p = SNMALLOC_NAME_MANGLE(memalign)(alignment, size);
|
||||
if (p == nullptr)
|
||||
if (unlikely(p == nullptr))
|
||||
{
|
||||
return ENOMEM;
|
||||
if (size != 0)
|
||||
return ENOMEM;
|
||||
}
|
||||
*memptr = p;
|
||||
return 0;
|
||||
@@ -212,44 +216,14 @@ extern "C"
|
||||
return ENOENT;
|
||||
}
|
||||
|
||||
#ifdef SNMALLOC_EXPOSE_PAGEMAP
|
||||
/**
|
||||
* Export the pagemap. The return value is a pointer to the pagemap
|
||||
* structure. The argument is used to return a pointer to a `PagemapConfig`
|
||||
* structure describing the type of the pagemap. Static methods on the
|
||||
* concrete pagemap templates can then be used to safely cast the return from
|
||||
* this function to the correct type. This allows us to preserve some
|
||||
* semblance of ABI safety via a pure C API.
|
||||
*/
|
||||
SNMALLOC_EXPORT void* SNMALLOC_NAME_MANGLE(snmalloc_chunkmap_global_get)(
|
||||
PagemapConfig const** config)
|
||||
{
|
||||
auto& pm = GlobalChunkmap::pagemap();
|
||||
if (config)
|
||||
{
|
||||
*config = &ChunkmapPagemap::config;
|
||||
SNMALLOC_ASSERT(ChunkmapPagemap::cast_to_pagemap(&pm, *config) == &pm);
|
||||
}
|
||||
return ±
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef SNMALLOC_EXPOSE_RESERVE
|
||||
SNMALLOC_EXPORT void*
|
||||
SNMALLOC_NAME_MANGLE(snmalloc_reserve_shared)(size_t* size, size_t align)
|
||||
{
|
||||
return snmalloc::default_memory_provider().reserve<true>(size, align);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !defined(__PIC__) && !defined(NO_BOOTSTRAP_ALLOCATOR)
|
||||
#if !defined(__PIC__) && defined(SNMALLOC_BOOTSTRAP_ALLOCATOR)
|
||||
// The following functions are required to work before TLS is set up, in
|
||||
// statically-linked programs. These temporarily grab an allocator from the
|
||||
// pool and return it.
|
||||
|
||||
void* __je_bootstrap_malloc(size_t size)
|
||||
{
|
||||
return get_slow_allocator()->alloc(size);
|
||||
return get_scoped_allocator().alloc(size);
|
||||
}
|
||||
|
||||
void* __je_bootstrap_calloc(size_t nmemb, size_t size)
|
||||
@@ -263,12 +237,12 @@ extern "C"
|
||||
}
|
||||
// Include size 0 in the first sizeclass.
|
||||
sz = ((sz - 1) >> (bits::BITS - 1)) + sz;
|
||||
return get_slow_allocator()->alloc<ZeroMem::YesZero>(sz);
|
||||
return get_scoped_allocator().alloc<ZeroMem::YesZero>(sz);
|
||||
}
|
||||
|
||||
void __je_bootstrap_free(void* ptr)
|
||||
{
|
||||
get_slow_allocator()->dealloc(ptr);
|
||||
get_scoped_allocator().dealloc(ptr);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user