Reduce dependence on C++ runtime
If the external thread statics are used, then we don't need to include some C++ runtime concepts. This refactoring moves some global initialization under conditional compilation.
This commit is contained in:
@@ -38,7 +38,7 @@ namespace snmalloc
|
||||
|
||||
static AllocPool* make() noexcept
|
||||
{
|
||||
return make(default_memory_provider);
|
||||
return make(default_memory_provider());
|
||||
}
|
||||
|
||||
Alloc* acquire()
|
||||
|
||||
@@ -58,8 +58,39 @@ namespace snmalloc
|
||||
class MemoryProviderStateMixin : public PAL
|
||||
{
|
||||
std::atomic_flag lock = ATOMIC_FLAG_INIT;
|
||||
void* bump;
|
||||
size_t remaining;
|
||||
void* bump = 0;
|
||||
size_t remaining = 0;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Stack of large allocations that have been returned for reuse.
|
||||
*/
|
||||
ModArray<NUM_LARGE_CLASSES, MPMCStack<Largeslab, RequiresInit>> large_stack;
|
||||
|
||||
static MemoryProviderStateMixin<PAL>* make() noexcept
|
||||
{
|
||||
// Temporary storage to start the allocator in.
|
||||
MemoryProviderStateMixin<PAL> local;
|
||||
|
||||
// Allocate permanent storage for the allocator usung temporary allocator
|
||||
MemoryProviderStateMixin<PAL>* allocated
|
||||
= local.alloc_chunk<MemoryProviderStateMixin<PAL>, 1>();
|
||||
|
||||
// Put temporary allocator we have used, into the permanent storage.
|
||||
// memcpy is safe as this is entirely single threaded.
|
||||
memcpy(allocated, &local, sizeof(MemoryProviderStateMixin<PAL>));
|
||||
|
||||
return allocated;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
* The last time we saw a low memory notification.
|
||||
*/
|
||||
std::atomic<uint64_t> last_low_memory_epoch = 0;
|
||||
std::atomic_flag lazy_decommit_guard = {};
|
||||
|
||||
|
||||
void new_block()
|
||||
{
|
||||
@@ -71,11 +102,6 @@ namespace snmalloc
|
||||
remaining = SUPERSLAB_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* The last time we saw a low memory notification.
|
||||
*/
|
||||
std::atomic<uint64_t> last_low_memory_epoch = 0;
|
||||
std::atomic_flag lazy_decommit_guard;
|
||||
SNMALLOC_SLOW_PATH void lazy_decommit()
|
||||
{
|
||||
// If another thread is try to do lazy decommit, let it continue. If
|
||||
@@ -141,11 +167,6 @@ namespace snmalloc
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* Stack of large allocations that have been returned for reuse.
|
||||
*/
|
||||
ModArray<NUM_LARGE_CLASSES, MPMCStack<Largeslab, PreZeroed>> large_stack;
|
||||
|
||||
/**
|
||||
* Primitive allocator for structure that are required before
|
||||
* the allocator can be running.
|
||||
@@ -422,5 +443,8 @@ namespace snmalloc
|
||||
* The memory provider that will be used if no other provider is explicitly
|
||||
* passed as an argument.
|
||||
*/
|
||||
inline GlobalVirtual default_memory_provider;
|
||||
inline GlobalVirtual& default_memory_provider()
|
||||
{
|
||||
return *(Singleton<GlobalVirtual*, GlobalVirtual::make>::get());
|
||||
}
|
||||
} // namespace snmalloc
|
||||
|
||||
@@ -153,7 +153,7 @@ namespace snmalloc
|
||||
if (e->compare_exchange_strong(
|
||||
value, LOCKED_ENTRY, std::memory_order_relaxed))
|
||||
{
|
||||
auto& v = default_memory_provider;
|
||||
auto& v = default_memory_provider();
|
||||
value = v.alloc_chunk<PagemapEntry, OS_PAGE_SIZE>();
|
||||
e->store(value, std::memory_order_release);
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace snmalloc
|
||||
|
||||
static Pool* make() noexcept
|
||||
{
|
||||
return Pool::make(default_memory_provider);
|
||||
return Pool::make(default_memory_provider());
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
|
||||
@@ -11,16 +11,6 @@ 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_CHUNKMAP(), nullptr, true);
|
||||
|
||||
#ifdef SNMALLOC_EXTERNAL_THREAD_ALLOC
|
||||
/**
|
||||
* Version of the `ThreadAlloc` interface that does no management of thread
|
||||
@@ -39,15 +29,39 @@ namespace snmalloc
|
||||
public:
|
||||
static SNMALLOC_FAST_PATH Alloc* get_noncachable()
|
||||
{
|
||||
return (Alloc*&)ThreadAllocUntyped::get();
|
||||
return (Alloc*)ThreadAllocUntyped::get();
|
||||
}
|
||||
|
||||
static SNMALLOC_FAST_PATH Alloc* get()
|
||||
{
|
||||
return (Alloc*&)ThreadAllocUntyped::get();
|
||||
return (Alloc*)ThreadAllocUntyped::get();
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Function passed as a template parameter to `Allocator` to allow lazy
|
||||
* replacement. In this case we are assuming the underlying external thread
|
||||
* alloc is performing initialization, so this is not required, and just
|
||||
* always returns nullptr to specify no new allocator is required.
|
||||
*/
|
||||
SNMALLOC_FAST_PATH void* lazy_replacement(void* existing)
|
||||
{
|
||||
UNUSED(existing);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
using ThreadAlloc = ThreadAllocUntypedWrapper;
|
||||
#else
|
||||
/**
|
||||
* 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 GlobalVirtual dummy_memory_provider;
|
||||
inline Alloc GlobalPlaceHolder(
|
||||
dummy_memory_provider, SNMALLOC_DEFAULT_CHUNKMAP(), nullptr, true);
|
||||
|
||||
/**
|
||||
* Common aspects of thread local allocator. Subclasses handle how releasing
|
||||
@@ -196,8 +210,6 @@ namespace snmalloc
|
||||
ThreadAllocLibcCleanup::inner_release();
|
||||
}
|
||||
using ThreadAlloc = ThreadAllocLibcCleanup;
|
||||
#elif defined(SNMALLOC_EXTERNAL_THREAD_ALLOC)
|
||||
using ThreadAlloc = ThreadAllocUntypedWrapper;
|
||||
#else
|
||||
using ThreadAlloc = ThreadAllocThreadDestructor;
|
||||
#endif
|
||||
@@ -233,5 +245,5 @@ namespace snmalloc
|
||||
}
|
||||
return lazy_replacement_slow();
|
||||
}
|
||||
|
||||
#endif
|
||||
} // namespace snmalloc
|
||||
|
||||
@@ -208,7 +208,7 @@ extern "C"
|
||||
SNMALLOC_EXPORT void*
|
||||
SNMALLOC_NAME_MANGLE(snmalloc_reserve_shared)(size_t* size, size_t align)
|
||||
{
|
||||
return snmalloc::default_memory_provider.reserve<true>(size, align);
|
||||
return snmalloc::default_memory_provider().reserve<true>(size, align);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace snmalloc
|
||||
{
|
||||
class PALOpenEnclave
|
||||
{
|
||||
std::atomic<void*> oe_base;
|
||||
std::atomic<void*> oe_base = nullptr;
|
||||
|
||||
public:
|
||||
/**
|
||||
|
||||
@@ -19,6 +19,8 @@ extern "C" const void* __oe_get_heap_end()
|
||||
|
||||
extern "C" void* oe_memset(void* p, int c, size_t size)
|
||||
{
|
||||
std::cout << "Memset " << p << " - " << size << std::endl;
|
||||
|
||||
return memset(p, c, size);
|
||||
}
|
||||
|
||||
@@ -30,7 +32,7 @@ extern "C" void oe_abort()
|
||||
using namespace snmalloc;
|
||||
int main()
|
||||
{
|
||||
MemoryProviderStateMixin<DefaultPal> mp;
|
||||
auto& mp = *MemoryProviderStateMixin<DefaultPal>::make();
|
||||
|
||||
// 28 is large enough to produce a nested allocator.
|
||||
// It is also large enough for the example to run in.
|
||||
|
||||
40
src/test/func/thread_alloc_external/thread_alloc_external.cc
Normal file
40
src/test/func/thread_alloc_external/thread_alloc_external.cc
Normal file
@@ -0,0 +1,40 @@
|
||||
|
||||
#define SNMALLOC_EXTERNAL_THREAD_ALLOC
|
||||
#include <mem/globalalloc.h>
|
||||
using namespace snmalloc;
|
||||
|
||||
class ThreadAllocUntyped
|
||||
{
|
||||
public:
|
||||
static void* get()
|
||||
{
|
||||
static thread_local void* alloc = nullptr;
|
||||
if (alloc != nullptr)
|
||||
{
|
||||
return alloc;
|
||||
}
|
||||
|
||||
alloc = current_alloc_pool()->acquire();
|
||||
return alloc;
|
||||
}
|
||||
};
|
||||
|
||||
#include <override/malloc.cc>
|
||||
|
||||
int main()
|
||||
{
|
||||
MemoryProviderStateMixin<DefaultPal> mp;
|
||||
|
||||
// 28 is large enough to produce a nested allocator.
|
||||
// It is also large enough for the example to run in.
|
||||
// For 1MiB superslabs, SUPERSLAB_BITS + 4 is not big enough for the example.
|
||||
|
||||
auto a = ThreadAlloc::get();
|
||||
|
||||
for (size_t i = 0; i < 1000; i++)
|
||||
{
|
||||
auto r1 = a->alloc(100);
|
||||
|
||||
free(r1);
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,7 @@ bool has_pressure()
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t current_epoch = default_memory_provider.low_memory_epoch();
|
||||
uint64_t current_epoch = default_memory_provider().low_memory_epoch();
|
||||
bool result = epoch != current_epoch;
|
||||
epoch = current_epoch;
|
||||
return result;
|
||||
|
||||
Reference in New Issue
Block a user