HEADER_GLOBAL was using non-standard attributes to achieve what C++17 now permits with a keyword. Use the standard formulation. Update the README to note that gcc is still not recommended, but because of its poor codegen for 128-bit atomic compare and exchange, rather than because it doesn't support the attribute used for HEADER_GLOBAL.
230 lines
7.0 KiB
C++
230 lines
7.0 KiB
C++
#pragma once
|
|
|
|
#include "../ds/helpers.h"
|
|
#include "globalalloc.h"
|
|
#if defined(SNMALLOC_USE_THREAD_DESTRUCTOR) && \
|
|
defined(SNMALLOC_USE_THREAD_CLEANUP)
|
|
#error At most one out of SNMALLOC_USE_THREAD_CLEANUP and SNMALLOC_USE_THREAD_DESTRUCTOR may be defined.
|
|
#endif
|
|
|
|
extern "C" void _malloc_thread_cleanup();
|
|
|
|
namespace snmalloc
|
|
{
|
|
/**
|
|
* A global fake allocator object. This never allocates memory and, as a
|
|
* result, never owns any slabs. On the slow paths, where it would fetch
|
|
* slabs to allocate from, it will discover that it is the placeholder and
|
|
* replace itself with the thread-local allocator, allocating one if
|
|
* required. This avoids a branch on the fast path.
|
|
*/
|
|
inline Alloc GlobalPlaceHolder(
|
|
default_memory_provider, SNMALLOC_DEFAULT_PAGEMAP(), nullptr, true);
|
|
|
|
#ifdef SNMALLOC_EXTERNAL_THREAD_ALLOC
|
|
/**
|
|
* Version of the `ThreadAlloc` interface that does no management of thread
|
|
* local state, and just assumes that "ThreadAllocUntyped::get" has been
|
|
* declared before including snmalloc.h. As it is included before, it cannot
|
|
* know the allocator type, hence the casting.
|
|
*
|
|
* This class is used only when snmalloc is compiled as part of a runtime,
|
|
* which has its own management of the thread local allocator pointer.
|
|
*/
|
|
class ThreadAllocUntypedWrapper
|
|
{
|
|
protected:
|
|
static void register_cleanup() {}
|
|
|
|
public:
|
|
static SNMALLOC_FAST_PATH Alloc* get_noncachable()
|
|
{
|
|
return (Alloc*&)ThreadAllocUntyped::get();
|
|
}
|
|
|
|
static SNMALLOC_FAST_PATH Alloc* get()
|
|
{
|
|
return (Alloc*&)ThreadAllocUntyped::get();
|
|
}
|
|
};
|
|
#endif
|
|
|
|
/**
|
|
* Common aspects of thread local allocator. Subclasses handle how releasing
|
|
* the allocator is triggered.
|
|
*/
|
|
class ThreadAllocCommon
|
|
{
|
|
friend void* lazy_replacement_slow();
|
|
|
|
protected:
|
|
static inline void inner_release()
|
|
{
|
|
auto& per_thread = get_reference();
|
|
if (per_thread != &GlobalPlaceHolder)
|
|
{
|
|
current_alloc_pool()->release(per_thread);
|
|
per_thread = &GlobalPlaceHolder;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Default clean up does nothing except print statistics if enabled.
|
|
**/
|
|
static void register_cleanup()
|
|
{
|
|
#ifdef USE_SNMALLOC_STATS
|
|
Singleton<int, atexit_print_stats>::get();
|
|
#endif
|
|
}
|
|
|
|
#ifdef USE_SNMALLOC_STATS
|
|
static void print_stats()
|
|
{
|
|
Stats s;
|
|
current_alloc_pool()->aggregate_stats(s);
|
|
s.print<Alloc>(std::cout);
|
|
}
|
|
|
|
static int atexit_print_stats() noexcept
|
|
{
|
|
return atexit(print_stats);
|
|
}
|
|
#endif
|
|
|
|
static inline Alloc*& get_reference()
|
|
{
|
|
static thread_local Alloc* alloc = &GlobalPlaceHolder;
|
|
return alloc;
|
|
}
|
|
|
|
public:
|
|
/**
|
|
* Public interface, returns the allocator for this thread, constructing
|
|
* one if necessary.
|
|
*
|
|
* If no operations have been performed on an allocator returned by either
|
|
* `get()` nor `get_noncachable()`, then the value contained in the return
|
|
* will be an Alloc* that will always use the slow path.
|
|
*
|
|
* Only use this API if you intend to use the returned allocator just once
|
|
* per call, or if you know other calls have already been made to the
|
|
* allocator.
|
|
*/
|
|
static inline Alloc* get_noncachable()
|
|
{
|
|
return get_reference();
|
|
}
|
|
|
|
/**
|
|
* Public interface, returns the allocator for this thread, constructing
|
|
* one if necessary.
|
|
*
|
|
* The returned Alloc* is guaranteed to be initialised. This incurs a cost,
|
|
* so use `get_noncachable` if you can meet its criteria.
|
|
*/
|
|
static SNMALLOC_FAST_PATH Alloc* get()
|
|
{
|
|
auto alloc = get_reference();
|
|
auto new_alloc = lazy_replacement(alloc);
|
|
return (likely(new_alloc == nullptr)) ?
|
|
alloc :
|
|
reinterpret_cast<Alloc*>(new_alloc);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Version of the `ThreadAlloc` interface that uses a hook provided by libc
|
|
* to destroy thread-local state. This is the ideal option, because it
|
|
* enforces ordering of destruction such that the malloc state is destroyed
|
|
* after anything that can allocate memory.
|
|
*
|
|
* This class is used only when snmalloc is compiled as part of a compatible
|
|
* libc (for example, FreeBSD libc).
|
|
*/
|
|
class ThreadAllocLibcCleanup : public ThreadAllocCommon
|
|
{
|
|
/**
|
|
* Libc will call `_malloc_thread_cleanup` just before a thread terminates.
|
|
* This function must be allowed to call back into this class to destroy
|
|
* the state.
|
|
*/
|
|
friend void ::_malloc_thread_cleanup();
|
|
};
|
|
|
|
/**
|
|
* Version of the `ThreadAlloc` interface that uses C++ `thread_local`
|
|
* destructors for cleanup. If a per-thread allocator is used during the
|
|
* destruction of other per-thread data, this class will create a new
|
|
* instance and register its destructor, so should eventually result in
|
|
* cleanup, but may result in allocators being returned to the global pool
|
|
* and then reacquired multiple times.
|
|
*
|
|
* This implementation depends on nothing outside of a working C++
|
|
* environment and so should be the simplest for initial bringup on an
|
|
* unsupported platform. It is currently used in the FreeBSD kernel version.
|
|
*/
|
|
class ThreadAllocThreadDestructor : public ThreadAllocCommon
|
|
{
|
|
template<void f()>
|
|
friend class OnDestruct;
|
|
|
|
public:
|
|
static void register_cleanup()
|
|
{
|
|
static thread_local OnDestruct<ThreadAllocCommon::inner_release> tidier;
|
|
|
|
ThreadAllocCommon::register_cleanup();
|
|
}
|
|
};
|
|
|
|
#ifdef SNMALLOC_USE_THREAD_CLEANUP
|
|
/**
|
|
* Entry point the allows libc to call into the allocator for per-thread
|
|
* cleanup.
|
|
*/
|
|
extern "C" void _malloc_thread_cleanup()
|
|
{
|
|
ThreadAllocLibcCleanup::inner_release();
|
|
}
|
|
using ThreadAlloc = ThreadAllocLibcCleanup;
|
|
#elif defined(SNMALLOC_EXTERNAL_THREAD_ALLOC)
|
|
using ThreadAlloc = ThreadAllocUntypedWrapper;
|
|
#else
|
|
using ThreadAlloc = ThreadAllocThreadDestructor;
|
|
#endif
|
|
|
|
/**
|
|
* Slow path for the placeholder replacement. The simple check that this is
|
|
* the global placeholder is inlined, the rest of it is only hit in a very
|
|
* unusual case and so should go off the fast path.
|
|
*/
|
|
SNMALLOC_SLOW_PATH inline void* lazy_replacement_slow()
|
|
{
|
|
auto*& local_alloc = ThreadAlloc::get_reference();
|
|
assert(local_alloc == &GlobalPlaceHolder);
|
|
local_alloc = current_alloc_pool()->acquire();
|
|
assert(local_alloc != &GlobalPlaceHolder);
|
|
ThreadAlloc::register_cleanup();
|
|
return local_alloc;
|
|
}
|
|
|
|
/**
|
|
* Function passed as a template parameter to `Allocator` to allow lazy
|
|
* replacement. This is called on all of the slow paths in `Allocator`. If
|
|
* the caller is the global placeholder allocator then this function will
|
|
* check if we've already allocated a per-thread allocator, returning it if
|
|
* so. If we have not allocated a per-thread allocator yet, then this
|
|
* function will allocate one.
|
|
*/
|
|
SNMALLOC_FAST_PATH void* lazy_replacement(void* existing)
|
|
{
|
|
if (existing != &GlobalPlaceHolder)
|
|
{
|
|
return nullptr;
|
|
}
|
|
return lazy_replacement_slow();
|
|
}
|
|
|
|
} // namespace snmalloc
|