From 9f53ec0ef8d23a3902c246f178b09e671ad35886 Mon Sep 17 00:00:00 2001 From: Matthew Parkinson Date: Wed, 26 Feb 2020 17:55:29 +0000 Subject: [PATCH] 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. --- src/mem/globalalloc.h | 2 +- src/mem/largealloc.h | 50 ++++++++++++++----- src/mem/pagemap.h | 2 +- src/mem/pool.h | 2 +- src/mem/threadalloc.h | 44 ++++++++++------ src/override/malloc.cc | 2 +- src/pal/pal_open_enclave.h | 2 +- src/test/func/fixed_region/fixed_region.cc | 4 +- .../thread_alloc_external.cc | 40 +++++++++++++++ src/test/perf/low_memory/low-memory.cc | 2 +- 10 files changed, 114 insertions(+), 36 deletions(-) create mode 100644 src/test/func/thread_alloc_external/thread_alloc_external.cc diff --git a/src/mem/globalalloc.h b/src/mem/globalalloc.h index 55dabac..432b434 100644 --- a/src/mem/globalalloc.h +++ b/src/mem/globalalloc.h @@ -38,7 +38,7 @@ namespace snmalloc static AllocPool* make() noexcept { - return make(default_memory_provider); + return make(default_memory_provider()); } Alloc* acquire() diff --git a/src/mem/largealloc.h b/src/mem/largealloc.h index d481a55..b3cca67 100644 --- a/src/mem/largealloc.h +++ b/src/mem/largealloc.h @@ -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> large_stack; + + static MemoryProviderStateMixin* make() noexcept + { + // Temporary storage to start the allocator in. + MemoryProviderStateMixin local; + + // Allocate permanent storage for the allocator usung temporary allocator + MemoryProviderStateMixin* allocated + = local.alloc_chunk, 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)); + + return allocated; + } + + private: + + /** + * The last time we saw a low memory notification. + */ + std::atomic 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 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> 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::get()); + } } // namespace snmalloc diff --git a/src/mem/pagemap.h b/src/mem/pagemap.h index f931ea0..aa77eb5 100644 --- a/src/mem/pagemap.h +++ b/src/mem/pagemap.h @@ -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(); e->store(value, std::memory_order_release); } diff --git a/src/mem/pool.h b/src/mem/pool.h index 8497664..2736c56 100644 --- a/src/mem/pool.h +++ b/src/mem/pool.h @@ -41,7 +41,7 @@ namespace snmalloc static Pool* make() noexcept { - return Pool::make(default_memory_provider); + return Pool::make(default_memory_provider()); } template diff --git a/src/mem/threadalloc.h b/src/mem/threadalloc.h index 9cee039..ee3ff5a 100644 --- a/src/mem/threadalloc.h +++ b/src/mem/threadalloc.h @@ -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 diff --git a/src/override/malloc.cc b/src/override/malloc.cc index 9c238d3..a3f1802 100644 --- a/src/override/malloc.cc +++ b/src/override/malloc.cc @@ -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(size, align); + return snmalloc::default_memory_provider().reserve(size, align); } #endif diff --git a/src/pal/pal_open_enclave.h b/src/pal/pal_open_enclave.h index 0361d53..3b33378 100644 --- a/src/pal/pal_open_enclave.h +++ b/src/pal/pal_open_enclave.h @@ -11,7 +11,7 @@ namespace snmalloc { class PALOpenEnclave { - std::atomic oe_base; + std::atomic oe_base = nullptr; public: /** diff --git a/src/test/func/fixed_region/fixed_region.cc b/src/test/func/fixed_region/fixed_region.cc index b3205ed..c8a8177 100644 --- a/src/test/func/fixed_region/fixed_region.cc +++ b/src/test/func/fixed_region/fixed_region.cc @@ -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 mp; + auto& mp = *MemoryProviderStateMixin::make(); // 28 is large enough to produce a nested allocator. // It is also large enough for the example to run in. diff --git a/src/test/func/thread_alloc_external/thread_alloc_external.cc b/src/test/func/thread_alloc_external/thread_alloc_external.cc new file mode 100644 index 0000000..e0216fa --- /dev/null +++ b/src/test/func/thread_alloc_external/thread_alloc_external.cc @@ -0,0 +1,40 @@ + +#define SNMALLOC_EXTERNAL_THREAD_ALLOC +#include +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 + +int main() +{ + MemoryProviderStateMixin 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); + } +} diff --git a/src/test/perf/low_memory/low-memory.cc b/src/test/perf/low_memory/low-memory.cc index 240373f..dd890ae 100644 --- a/src/test/perf/low_memory/low-memory.cc +++ b/src/test/perf/low_memory/low-memory.cc @@ -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;