From 99f57646da42a9e9f03b186bbe3d1fb533a82e93 Mon Sep 17 00:00:00 2001 From: Istvan Haller Date: Thu, 19 Aug 2021 19:22:16 +0100 Subject: [PATCH 01/11] Re-enabled generic Pool which uses ChunkAllocator. Allocator pool renamed to AllocPool. --- src/mem/corealloc.h | 2 +- src/mem/globalalloc.h | 28 +++++++------- src/mem/localalloc.h | 9 +++-- src/mem/pool.h | 86 +++++++++++++++++++++++++++++++++++++++++-- src/mem/pooled.h | 4 +- 5 files changed, 107 insertions(+), 22 deletions(-) diff --git a/src/mem/corealloc.h b/src/mem/corealloc.h index 8c34844..467fa81 100644 --- a/src/mem/corealloc.h +++ b/src/mem/corealloc.h @@ -4,7 +4,7 @@ #include "allocconfig.h" #include "localcache.h" #include "metaslab.h" -#include "pooled.h" +#include "pool.h" #include "remotecache.h" #include "sizeclasstable.h" #include "slaballocator.h" diff --git a/src/mem/globalalloc.h b/src/mem/globalalloc.h index cc4ecd5..c44de93 100644 --- a/src/mem/globalalloc.h +++ b/src/mem/globalalloc.h @@ -11,7 +11,7 @@ namespace snmalloc static_assert( SharedStateHandle::Options.CoreAllocIsPoolAllocated, "Global statistics are available only for pool-allocated configurations"); - auto* alloc = Pool>::template iterate< + auto* alloc = AllocPool>::template iterate< SharedStateHandle>(); while (alloc != nullptr) @@ -20,7 +20,7 @@ namespace snmalloc if (a != nullptr) stats.add(*a); stats.add(alloc->stats()); - alloc = Pool>::template iterate< + alloc = AllocPool>::template iterate< SharedStateHandle>(alloc); } } @@ -32,7 +32,7 @@ namespace snmalloc static_assert( SharedStateHandle::Options.CoreAllocIsPoolAllocated, "Global statistics are available only for pool-allocated configurations"); - auto alloc = Pool>::template iterate< + auto alloc = AllocPool>::template iterate< SharedStateHandle>(); while (alloc != nullptr) @@ -40,7 +40,7 @@ namespace snmalloc auto stats = alloc->stats(); if (stats != nullptr) stats->template print(o, dumpid, alloc->id()); - alloc = Pool>::template iterate< + alloc = AllocPool>::template iterate< SharedStateHandle>(alloc); } } @@ -64,7 +64,7 @@ namespace snmalloc // allocators that are not currently in use by any thread. // One atomic operation to extract the stack, another to restore it. // Handling the message queue for each stack is non-atomic. - auto* first = Pool>::extract(); + auto* first = AllocPool>::extract(); auto* alloc = first; decltype(alloc) last; @@ -74,10 +74,10 @@ namespace snmalloc { alloc->flush(); last = alloc; - alloc = Pool>::extract(alloc); + alloc = AllocPool>::extract(alloc); } - Pool>::restore(first, last); + AllocPool>::restore(first, last); } #endif } @@ -96,7 +96,7 @@ namespace snmalloc "Global status is available only for pool-allocated configurations"); // This is a debugging function. It checks that all memory from all // allocators has been freed. - auto* alloc = Pool>::template iterate< + auto* alloc = AllocPool>::template iterate< SharedStateHandle>(); # ifdef SNMALLOC_TRACING @@ -111,7 +111,7 @@ namespace snmalloc std::cout << "debug_check_empty: Check all allocators!" << std::endl; # endif done = true; - alloc = Pool>::template iterate< + alloc = AllocPool>::template iterate< SharedStateHandle>(); okay = true; @@ -134,7 +134,7 @@ namespace snmalloc # ifdef SNMALLOC_TRACING std::cout << "debug check empty: okay = " << okay << std::endl; # endif - alloc = Pool>::template iterate< + alloc = AllocPool>::template iterate< SharedStateHandle>(alloc); } } @@ -148,12 +148,12 @@ namespace snmalloc // Redo check so abort is on allocator with allocation left. if (!okay) { - alloc = Pool>::template iterate< + alloc = AllocPool>::template iterate< SharedStateHandle>(); while (alloc != nullptr) { alloc->debug_is_empty(nullptr); - alloc = Pool>::template iterate< + alloc = AllocPool>::template iterate< SharedStateHandle>(alloc); } } @@ -168,7 +168,7 @@ namespace snmalloc static_assert( SharedStateHandle::Options.CoreAllocIsPoolAllocated, "Global status is available only for pool-allocated configurations"); - auto alloc = Pool>::template iterate< + auto alloc = AllocPool>::template iterate< SharedStateHandle>(); while (alloc != nullptr) { @@ -180,7 +180,7 @@ namespace snmalloc } count--; } - alloc = Pool>::template iterate< + alloc = AllocPool>::template iterate< SharedStateHandle>(alloc); if (count != 0) diff --git a/src/mem/localalloc.h b/src/mem/localalloc.h index 0d9e747..357e897 100644 --- a/src/mem/localalloc.h +++ b/src/mem/localalloc.h @@ -58,9 +58,12 @@ namespace snmalloc template class LocalAllocator { - using CoreAlloc = CoreAllocator; + public: + using StateHandle = SharedStateHandle; private: + using CoreAlloc = CoreAllocator; + // Free list per small size class. These are used for // allocation on the fast path. This part of the code is inspired by // mimalloc. @@ -375,7 +378,7 @@ namespace snmalloc // Initialise the global allocator structures ensure_init(); // Grab an allocator for this thread. - init(Pool::template acquire( + init(AllocPool::template acquire( &(this->local_cache))); } @@ -398,7 +401,7 @@ namespace snmalloc // Return underlying allocator to the system. if constexpr (SharedStateHandle::Options.CoreAllocOwnsLocalState) { - Pool::template release(core_alloc); + AllocPool::template release(core_alloc); } // Set up thread local allocator to look like diff --git a/src/mem/pool.h b/src/mem/pool.h index d3c25b4..49e3451 100644 --- a/src/mem/pool.h +++ b/src/mem/pool.h @@ -4,11 +4,12 @@ #include "../ds/mpmcstack.h" #include "../pal/pal_concept.h" #include "pooled.h" +#include "slaballocator.h" namespace snmalloc { /** - * Pool of a particular type of object. + * Pool of Allocators. * * 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 @@ -20,8 +21,10 @@ namespace snmalloc template class PoolState { - template + template friend class Pool; + template + friend class AllocPool; private: std::atomic_flag lock = ATOMIC_FLAG_INIT; @@ -32,8 +35,85 @@ namespace snmalloc constexpr PoolState() = default; }; - template + template class Pool + { + PoolState state; + + public: + static Pool* make() noexcept + { + return ChunkAllocator::alloc_meta_data(nullptr); + } + + template + T* acquire(Args&&... args) + { + T* p = state.stack.pop(); + + if (p != nullptr) + { + p->set_in_use(); + return p; + } + + p = ChunkAllocator::alloc_meta_data( + nullptr, std::forward(args)...); + + FlagLock f(state.lock); + p->list_next = state.list; + state.list = p; + + p->set_in_use(); + return p; + } + + /** + * Return to the pool an object previously retrieved by `acquire` + * + * Do not return objects from `extract`. + */ + 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(); + state.stack.push(p); + } + + T* extract(T* p = nullptr) + { + // Returns a linked list of all objects in the stack, emptying the stack. + if (p == nullptr) + return 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`. + */ + 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. + state.stack.push(first, last); + } + + T* iterate(T* p = nullptr) + { + if (p == nullptr) + return state.list; + + return p->list_next; + } + }; + + template + class AllocPool { public: template diff --git a/src/mem/pooled.h b/src/mem/pooled.h index 93daad9..b685357 100644 --- a/src/mem/pooled.h +++ b/src/mem/pooled.h @@ -8,8 +8,10 @@ namespace snmalloc class Pooled { private: - template + template friend class Pool; + template + friend class AllocPool; template friend class MPMCStack; From 4d2bf93b7a5e219ef2b394bfbd6cd08c407fcb5a Mon Sep 17 00:00:00 2001 From: Istvan Haller Date: Mon, 23 Aug 2021 16:33:48 +0100 Subject: [PATCH 02/11] Deleted the ability to implicitly copy LocalAllocator --- src/mem/localalloc.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mem/localalloc.h b/src/mem/localalloc.h index 357e897..0ae59f1 100644 --- a/src/mem/localalloc.h +++ b/src/mem/localalloc.h @@ -344,6 +344,8 @@ namespace snmalloc public: constexpr LocalAllocator() = default; + LocalAllocator(const LocalAllocator&) = delete; + LocalAllocator& operator= (const LocalAllocator&) = delete; /** * Initialise the allocator. For allocators that support local From 35c94229137fc5184d64b81e3d55a4c279e14ebe Mon Sep 17 00:00:00 2001 From: Istvan Haller Date: Mon, 23 Aug 2021 19:19:58 +0100 Subject: [PATCH 03/11] Comment fixes --- src/mem/localalloc.h | 2 +- src/mem/pool.h | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/mem/localalloc.h b/src/mem/localalloc.h index 0ae59f1..19a5b9c 100644 --- a/src/mem/localalloc.h +++ b/src/mem/localalloc.h @@ -345,7 +345,7 @@ namespace snmalloc public: constexpr LocalAllocator() = default; LocalAllocator(const LocalAllocator&) = delete; - LocalAllocator& operator= (const LocalAllocator&) = delete; + LocalAllocator& operator=(const LocalAllocator&) = delete; /** * Initialise the allocator. For allocators that support local diff --git a/src/mem/pool.h b/src/mem/pool.h index 49e3451..2b0c177 100644 --- a/src/mem/pool.h +++ b/src/mem/pool.h @@ -9,7 +9,7 @@ namespace snmalloc { /** - * Pool of Allocators. + * 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 @@ -112,6 +112,11 @@ namespace snmalloc } }; + /** + * Collection of static wrappers for the allocator pool. + * The PoolState for this particular pool type is owned by the + * SharedStateHandle, so there is no object state in this class. + */ template class AllocPool { From c89a085c90347e3d64538cba9b117f031f376b13 Mon Sep 17 00:00:00 2001 From: Istvan Haller Date: Mon, 23 Aug 2021 19:54:26 +0100 Subject: [PATCH 04/11] Removed code duplication by making generoc Pool also be static --- src/mem/globalalloc.h | 28 ++++++------- src/mem/localalloc.h | 4 +- src/mem/pool.h | 94 ++++++++----------------------------------- src/mem/pooled.h | 4 +- 4 files changed, 33 insertions(+), 97 deletions(-) diff --git a/src/mem/globalalloc.h b/src/mem/globalalloc.h index c44de93..cc4ecd5 100644 --- a/src/mem/globalalloc.h +++ b/src/mem/globalalloc.h @@ -11,7 +11,7 @@ namespace snmalloc static_assert( SharedStateHandle::Options.CoreAllocIsPoolAllocated, "Global statistics are available only for pool-allocated configurations"); - auto* alloc = AllocPool>::template iterate< + auto* alloc = Pool>::template iterate< SharedStateHandle>(); while (alloc != nullptr) @@ -20,7 +20,7 @@ namespace snmalloc if (a != nullptr) stats.add(*a); stats.add(alloc->stats()); - alloc = AllocPool>::template iterate< + alloc = Pool>::template iterate< SharedStateHandle>(alloc); } } @@ -32,7 +32,7 @@ namespace snmalloc static_assert( SharedStateHandle::Options.CoreAllocIsPoolAllocated, "Global statistics are available only for pool-allocated configurations"); - auto alloc = AllocPool>::template iterate< + auto alloc = Pool>::template iterate< SharedStateHandle>(); while (alloc != nullptr) @@ -40,7 +40,7 @@ namespace snmalloc auto stats = alloc->stats(); if (stats != nullptr) stats->template print(o, dumpid, alloc->id()); - alloc = AllocPool>::template iterate< + alloc = Pool>::template iterate< SharedStateHandle>(alloc); } } @@ -64,7 +64,7 @@ namespace snmalloc // allocators that are not currently in use by any thread. // One atomic operation to extract the stack, another to restore it. // Handling the message queue for each stack is non-atomic. - auto* first = AllocPool>::extract(); + auto* first = Pool>::extract(); auto* alloc = first; decltype(alloc) last; @@ -74,10 +74,10 @@ namespace snmalloc { alloc->flush(); last = alloc; - alloc = AllocPool>::extract(alloc); + alloc = Pool>::extract(alloc); } - AllocPool>::restore(first, last); + Pool>::restore(first, last); } #endif } @@ -96,7 +96,7 @@ namespace snmalloc "Global status is available only for pool-allocated configurations"); // This is a debugging function. It checks that all memory from all // allocators has been freed. - auto* alloc = AllocPool>::template iterate< + auto* alloc = Pool>::template iterate< SharedStateHandle>(); # ifdef SNMALLOC_TRACING @@ -111,7 +111,7 @@ namespace snmalloc std::cout << "debug_check_empty: Check all allocators!" << std::endl; # endif done = true; - alloc = AllocPool>::template iterate< + alloc = Pool>::template iterate< SharedStateHandle>(); okay = true; @@ -134,7 +134,7 @@ namespace snmalloc # ifdef SNMALLOC_TRACING std::cout << "debug check empty: okay = " << okay << std::endl; # endif - alloc = AllocPool>::template iterate< + alloc = Pool>::template iterate< SharedStateHandle>(alloc); } } @@ -148,12 +148,12 @@ namespace snmalloc // Redo check so abort is on allocator with allocation left. if (!okay) { - alloc = AllocPool>::template iterate< + alloc = Pool>::template iterate< SharedStateHandle>(); while (alloc != nullptr) { alloc->debug_is_empty(nullptr); - alloc = AllocPool>::template iterate< + alloc = Pool>::template iterate< SharedStateHandle>(alloc); } } @@ -168,7 +168,7 @@ namespace snmalloc static_assert( SharedStateHandle::Options.CoreAllocIsPoolAllocated, "Global status is available only for pool-allocated configurations"); - auto alloc = AllocPool>::template iterate< + auto alloc = Pool>::template iterate< SharedStateHandle>(); while (alloc != nullptr) { @@ -180,7 +180,7 @@ namespace snmalloc } count--; } - alloc = AllocPool>::template iterate< + alloc = Pool>::template iterate< SharedStateHandle>(alloc); if (count != 0) diff --git a/src/mem/localalloc.h b/src/mem/localalloc.h index 19a5b9c..7b1c912 100644 --- a/src/mem/localalloc.h +++ b/src/mem/localalloc.h @@ -380,7 +380,7 @@ namespace snmalloc // Initialise the global allocator structures ensure_init(); // Grab an allocator for this thread. - init(AllocPool::template acquire( + init(Pool::template acquire( &(this->local_cache))); } @@ -403,7 +403,7 @@ namespace snmalloc // Return underlying allocator to the system. if constexpr (SharedStateHandle::Options.CoreAllocOwnsLocalState) { - AllocPool::template release(core_alloc); + Pool::template release(core_alloc); } // Set up thread local allocator to look like diff --git a/src/mem/pool.h b/src/mem/pool.h index 2b0c177..990c849 100644 --- a/src/mem/pool.h +++ b/src/mem/pool.h @@ -21,10 +21,8 @@ namespace snmalloc template class PoolState { - template - friend class Pool; template - friend class AllocPool; + friend class Pool; private: std::atomic_flag lock = ATOMIC_FLAG_INIT; @@ -35,90 +33,30 @@ namespace snmalloc constexpr PoolState() = default; }; + /** + * Class used to instantiate non-allocator pools using a Singleton PoolState. + */ template - class Pool + class PoolStateHandle { - PoolState state; + static PoolState& pool_state = + Singleton, PoolStateHandle::make_state>::get(); + + static PoolState* make_state() + { + return ChunkAllocator::alloc_meta_data, SharedStateHandle>( + nullptr); + } public: - static Pool* make() noexcept + static PoolState& pool() { - return ChunkAllocator::alloc_meta_data(nullptr); - } - - template - T* acquire(Args&&... args) - { - T* p = state.stack.pop(); - - if (p != nullptr) - { - p->set_in_use(); - return p; - } - - p = ChunkAllocator::alloc_meta_data( - nullptr, std::forward(args)...); - - FlagLock f(state.lock); - p->list_next = state.list; - state.list = p; - - p->set_in_use(); - return p; - } - - /** - * Return to the pool an object previously retrieved by `acquire` - * - * Do not return objects from `extract`. - */ - 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(); - state.stack.push(p); - } - - T* extract(T* p = nullptr) - { - // Returns a linked list of all objects in the stack, emptying the stack. - if (p == nullptr) - return 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`. - */ - 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. - state.stack.push(first, last); - } - - T* iterate(T* p = nullptr) - { - if (p == nullptr) - return state.list; - - return p->list_next; + return pool_state; } }; - /** - * Collection of static wrappers for the allocator pool. - * The PoolState for this particular pool type is owned by the - * SharedStateHandle, so there is no object state in this class. - */ template - class AllocPool + class Pool { public: template diff --git a/src/mem/pooled.h b/src/mem/pooled.h index b685357..93daad9 100644 --- a/src/mem/pooled.h +++ b/src/mem/pooled.h @@ -8,10 +8,8 @@ namespace snmalloc class Pooled { private: - template - friend class Pool; template - friend class AllocPool; + friend class Pool; template friend class MPMCStack; From 769c61e71645b8e68c6d8a34afd0de88d4496efa Mon Sep 17 00:00:00 2001 From: Istvan Haller Date: Mon, 23 Aug 2021 20:08:27 +0100 Subject: [PATCH 05/11] Moved SharedStateHandle to Pool class instead of methods since all of them use it --- src/mem/globalalloc.h | 59 +++++++++++++++++++++++++------------------ src/mem/localalloc.h | 5 ++-- src/mem/pool.h | 10 +++----- src/mem/pooled.h | 2 +- 4 files changed, 40 insertions(+), 36 deletions(-) diff --git a/src/mem/globalalloc.h b/src/mem/globalalloc.h index cc4ecd5..6f5aaad 100644 --- a/src/mem/globalalloc.h +++ b/src/mem/globalalloc.h @@ -11,8 +11,8 @@ namespace snmalloc static_assert( SharedStateHandle::Options.CoreAllocIsPoolAllocated, "Global statistics are available only for pool-allocated configurations"); - auto* alloc = Pool>::template iterate< - SharedStateHandle>(); + auto* alloc = + Pool, SharedStateHandle>::iterate(); while (alloc != nullptr) { @@ -20,8 +20,9 @@ namespace snmalloc if (a != nullptr) stats.add(*a); stats.add(alloc->stats()); - alloc = Pool>::template iterate< - SharedStateHandle>(alloc); + alloc = + Pool, SharedStateHandle>::iterate( + alloc); } } @@ -32,16 +33,17 @@ namespace snmalloc static_assert( SharedStateHandle::Options.CoreAllocIsPoolAllocated, "Global statistics are available only for pool-allocated configurations"); - auto alloc = Pool>::template iterate< - SharedStateHandle>(); + auto alloc = + Pool, SharedStateHandle>::iterate(); while (alloc != nullptr) { auto stats = alloc->stats(); if (stats != nullptr) stats->template print(o, dumpid, alloc->id()); - alloc = Pool>::template iterate< - SharedStateHandle>(alloc); + alloc = + Pool, SharedStateHandle>::iterate( + alloc); } } #else @@ -64,7 +66,8 @@ namespace snmalloc // allocators that are not currently in use by any thread. // One atomic operation to extract the stack, another to restore it. // Handling the message queue for each stack is non-atomic. - auto* first = Pool>::extract(); + auto* first = + Pool, SharedStateHandle>::extract(); auto* alloc = first; decltype(alloc) last; @@ -74,10 +77,13 @@ namespace snmalloc { alloc->flush(); last = alloc; - alloc = Pool>::extract(alloc); + alloc = + Pool, SharedStateHandle>::extract( + alloc); } - Pool>::restore(first, last); + Pool, SharedStateHandle>::restore( + first, last); } #endif } @@ -96,8 +102,8 @@ namespace snmalloc "Global status is available only for pool-allocated configurations"); // This is a debugging function. It checks that all memory from all // allocators has been freed. - auto* alloc = Pool>::template iterate< - SharedStateHandle>(); + auto* alloc = + Pool, SharedStateHandle>::iterate(); # ifdef SNMALLOC_TRACING std::cout << "debug check empty: first " << alloc << std::endl; @@ -111,8 +117,8 @@ namespace snmalloc std::cout << "debug_check_empty: Check all allocators!" << std::endl; # endif done = true; - alloc = Pool>::template iterate< - SharedStateHandle>(); + alloc = + Pool, SharedStateHandle>::iterate(); okay = true; while (alloc != nullptr) @@ -134,8 +140,9 @@ namespace snmalloc # ifdef SNMALLOC_TRACING std::cout << "debug check empty: okay = " << okay << std::endl; # endif - alloc = Pool>::template iterate< - SharedStateHandle>(alloc); + alloc = + Pool, SharedStateHandle>::iterate( + alloc); } } @@ -148,13 +155,14 @@ namespace snmalloc // Redo check so abort is on allocator with allocation left. if (!okay) { - alloc = Pool>::template iterate< - SharedStateHandle>(); + alloc = + Pool, SharedStateHandle>::iterate(); while (alloc != nullptr) { alloc->debug_is_empty(nullptr); - alloc = Pool>::template iterate< - SharedStateHandle>(alloc); + alloc = + Pool, SharedStateHandle>::iterate( + alloc); } } #else @@ -168,8 +176,8 @@ namespace snmalloc static_assert( SharedStateHandle::Options.CoreAllocIsPoolAllocated, "Global status is available only for pool-allocated configurations"); - auto alloc = Pool>::template iterate< - SharedStateHandle>(); + auto alloc = + Pool, SharedStateHandle>::iterate(); while (alloc != nullptr) { if (alloc->debug_is_in_use()) @@ -180,8 +188,9 @@ namespace snmalloc } count--; } - alloc = Pool>::template iterate< - SharedStateHandle>(alloc); + alloc = + Pool, SharedStateHandle>::iterate( + alloc); if (count != 0) { diff --git a/src/mem/localalloc.h b/src/mem/localalloc.h index 7b1c912..96c1e56 100644 --- a/src/mem/localalloc.h +++ b/src/mem/localalloc.h @@ -380,8 +380,7 @@ namespace snmalloc // Initialise the global allocator structures ensure_init(); // Grab an allocator for this thread. - init(Pool::template acquire( - &(this->local_cache))); + init(Pool::acquire(&(this->local_cache))); } // Return all state in the fast allocator and release the underlying @@ -403,7 +402,7 @@ namespace snmalloc // Return underlying allocator to the system. if constexpr (SharedStateHandle::Options.CoreAllocOwnsLocalState) { - Pool::template release(core_alloc); + Pool::release(core_alloc); } // Set up thread local allocator to look like diff --git a/src/mem/pool.h b/src/mem/pool.h index 990c849..184abca 100644 --- a/src/mem/pool.h +++ b/src/mem/pool.h @@ -21,7 +21,7 @@ namespace snmalloc template class PoolState { - template + template friend class Pool; private: @@ -55,11 +55,11 @@ namespace snmalloc } }; - template + template class Pool { public: - template + template static T* acquire(Args&&... args) { PoolState& pool = SharedStateHandle::pool(); @@ -93,7 +93,6 @@ namespace snmalloc * * Do not return objects from `extract`. */ - template static void release(T* p) { // The object's destructor is not run. If the object is "reallocated", it @@ -103,7 +102,6 @@ namespace snmalloc SharedStateHandle::pool().stack.push(p); } - template static T* extract(T* p = nullptr) { // Returns a linked list of all objects in the stack, emptying the stack. @@ -118,7 +116,6 @@ namespace snmalloc * * Do not return objects from `acquire`. */ - template static void restore(T* first, T* last) { // Pushes a linked list of objects onto the stack. Use to put a linked @@ -126,7 +123,6 @@ namespace snmalloc SharedStateHandle::pool().stack.push(first, last); } - template static T* iterate(T* p = nullptr) { if (p == nullptr) diff --git a/src/mem/pooled.h b/src/mem/pooled.h index 93daad9..657ea2d 100644 --- a/src/mem/pooled.h +++ b/src/mem/pooled.h @@ -8,7 +8,7 @@ namespace snmalloc class Pooled { private: - template + template friend class Pool; template friend class MPMCStack; From c01a1215c639295ff7ef5346b001da80dc0e072e Mon Sep 17 00:00:00 2001 From: Istvan Haller Date: Mon, 23 Aug 2021 21:07:51 +0100 Subject: [PATCH 06/11] Cleanup and made new variant work with Verona --- src/mem/corealloc.h | 6 ++++++ src/mem/globalalloc.h | 48 +++++++++++++------------------------------ src/mem/localalloc.h | 4 ++-- src/mem/pool.h | 34 ++++++++++++++---------------- src/mem/pooled.h | 10 +++++++-- 5 files changed, 45 insertions(+), 57 deletions(-) diff --git a/src/mem/corealloc.h b/src/mem/corealloc.h index 467fa81..30dba53 100644 --- a/src/mem/corealloc.h +++ b/src/mem/corealloc.h @@ -785,4 +785,10 @@ namespace snmalloc return debug_is_empty_impl(result); } }; + + template + using AllocPool = Pool< + CoreAllocator, + SharedStateHandle, + SharedStateHandle::pool>; } // namespace snmalloc diff --git a/src/mem/globalalloc.h b/src/mem/globalalloc.h index 6f5aaad..674b7ae 100644 --- a/src/mem/globalalloc.h +++ b/src/mem/globalalloc.h @@ -11,8 +11,7 @@ namespace snmalloc static_assert( SharedStateHandle::Options.CoreAllocIsPoolAllocated, "Global statistics are available only for pool-allocated configurations"); - auto* alloc = - Pool, SharedStateHandle>::iterate(); + auto* alloc = AllocPool::iterate(); while (alloc != nullptr) { @@ -20,9 +19,7 @@ namespace snmalloc if (a != nullptr) stats.add(*a); stats.add(alloc->stats()); - alloc = - Pool, SharedStateHandle>::iterate( - alloc); + alloc = AllocPool::iterate(alloc); } } @@ -33,17 +30,14 @@ namespace snmalloc static_assert( SharedStateHandle::Options.CoreAllocIsPoolAllocated, "Global statistics are available only for pool-allocated configurations"); - auto alloc = - Pool, SharedStateHandle>::iterate(); + auto alloc = AllocPool::iterate(); while (alloc != nullptr) { auto stats = alloc->stats(); if (stats != nullptr) stats->template print(o, dumpid, alloc->id()); - alloc = - Pool, SharedStateHandle>::iterate( - alloc); + alloc = AllocPool::iterate(alloc); } } #else @@ -66,8 +60,7 @@ namespace snmalloc // allocators that are not currently in use by any thread. // One atomic operation to extract the stack, another to restore it. // Handling the message queue for each stack is non-atomic. - auto* first = - Pool, SharedStateHandle>::extract(); + auto* first = AllocPool::extract(); auto* alloc = first; decltype(alloc) last; @@ -77,13 +70,10 @@ namespace snmalloc { alloc->flush(); last = alloc; - alloc = - Pool, SharedStateHandle>::extract( - alloc); + alloc = AllocPool::extract(alloc); } - Pool, SharedStateHandle>::restore( - first, last); + AllocPool::restore(first, last); } #endif } @@ -102,8 +92,7 @@ namespace snmalloc "Global status is available only for pool-allocated configurations"); // This is a debugging function. It checks that all memory from all // allocators has been freed. - auto* alloc = - Pool, SharedStateHandle>::iterate(); + auto* alloc = AllocPool::iterate(); # ifdef SNMALLOC_TRACING std::cout << "debug check empty: first " << alloc << std::endl; @@ -117,8 +106,7 @@ namespace snmalloc std::cout << "debug_check_empty: Check all allocators!" << std::endl; # endif done = true; - alloc = - Pool, SharedStateHandle>::iterate(); + alloc = AllocPool::iterate(); okay = true; while (alloc != nullptr) @@ -140,9 +128,7 @@ namespace snmalloc # ifdef SNMALLOC_TRACING std::cout << "debug check empty: okay = " << okay << std::endl; # endif - alloc = - Pool, SharedStateHandle>::iterate( - alloc); + alloc = AllocPool::iterate(alloc); } } @@ -155,14 +141,11 @@ namespace snmalloc // Redo check so abort is on allocator with allocation left. if (!okay) { - alloc = - Pool, SharedStateHandle>::iterate(); + alloc = AllocPool::iterate(); while (alloc != nullptr) { alloc->debug_is_empty(nullptr); - alloc = - Pool, SharedStateHandle>::iterate( - alloc); + alloc = AllocPool::iterate(alloc); } } #else @@ -176,8 +159,7 @@ namespace snmalloc static_assert( SharedStateHandle::Options.CoreAllocIsPoolAllocated, "Global status is available only for pool-allocated configurations"); - auto alloc = - Pool, SharedStateHandle>::iterate(); + auto alloc = AllocPool::iterate(); while (alloc != nullptr) { if (alloc->debug_is_in_use()) @@ -188,9 +170,7 @@ namespace snmalloc } count--; } - alloc = - Pool, SharedStateHandle>::iterate( - alloc); + alloc = AllocPool::iterate(alloc); if (count != 0) { diff --git a/src/mem/localalloc.h b/src/mem/localalloc.h index 96c1e56..03161fa 100644 --- a/src/mem/localalloc.h +++ b/src/mem/localalloc.h @@ -380,7 +380,7 @@ namespace snmalloc // Initialise the global allocator structures ensure_init(); // Grab an allocator for this thread. - init(Pool::acquire(&(this->local_cache))); + init(AllocPool::acquire(&(this->local_cache))); } // Return all state in the fast allocator and release the underlying @@ -402,7 +402,7 @@ namespace snmalloc // Return underlying allocator to the system. if constexpr (SharedStateHandle::Options.CoreAllocOwnsLocalState) { - Pool::release(core_alloc); + AllocPool::release(core_alloc); } // Set up thread local allocator to look like diff --git a/src/mem/pool.h b/src/mem/pool.h index 184abca..76950c2 100644 --- a/src/mem/pool.h +++ b/src/mem/pool.h @@ -21,7 +21,10 @@ namespace snmalloc template class PoolState { - template + template< + typename TT, + typename SharedStateHandle, + PoolState& get_state()> friend class Pool; private: @@ -34,35 +37,28 @@ namespace snmalloc }; /** - * Class used to instantiate non-allocator pools using a Singleton PoolState. + * Class used to instantiate a global non-allocator PoolState. */ - template - class PoolStateHandle + template + class SingletonPoolState { - static PoolState& pool_state = - Singleton, PoolStateHandle::make_state>::get(); - - static PoolState* make_state() - { - return ChunkAllocator::alloc_meta_data, SharedStateHandle>( - nullptr); - } + static inline PoolState state; public: static PoolState& pool() { - return pool_state; + return state; } }; - template + template& get_state()> class Pool { public: template static T* acquire(Args&&... args) { - PoolState& pool = SharedStateHandle::pool(); + PoolState& pool = get_state(); T* p = pool.stack.pop(); if (p != nullptr) @@ -99,14 +95,14 @@ namespace snmalloc // is returned without the constructor being run, so the object is reused // without re-initialisation. p->reset_in_use(); - SharedStateHandle::pool().stack.push(p); + 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 SharedStateHandle::pool().stack.pop_all(); + return get_state().stack.pop_all(); return p->next; } @@ -120,13 +116,13 @@ namespace snmalloc { // Pushes a linked list of objects onto the stack. Use to put a linked // list returned by extract back onto the stack. - SharedStateHandle::pool().stack.push(first, last); + get_state().stack.push(first, last); } static T* iterate(T* p = nullptr) { if (p == nullptr) - return SharedStateHandle::pool().list; + return get_state().list; return p->list_next; } diff --git a/src/mem/pooled.h b/src/mem/pooled.h index 657ea2d..d5b988c 100644 --- a/src/mem/pooled.h +++ b/src/mem/pooled.h @@ -4,11 +4,17 @@ namespace snmalloc { + template + class PoolState; + template class Pooled { - private: - template + public: + template< + typename TT, + typename SharedStateHandle, + PoolState& get_state()> friend class Pool; template friend class MPMCStack; From df852dba6ace833c68df507b54b4cdedece681ca Mon Sep 17 00:00:00 2001 From: Istvan Haller Date: Tue, 24 Aug 2021 14:18:39 +0100 Subject: [PATCH 07/11] Added test and forced initialization of backend when using custom Pool. --- src/mem/pool.h | 40 ++++++++++++- src/test/func/pool/pool.cc | 117 +++++++++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+), 3 deletions(-) create mode 100644 src/test/func/pool/pool.cc diff --git a/src/mem/pool.h b/src/mem/pool.h index 76950c2..34f58e9 100644 --- a/src/mem/pool.h +++ b/src/mem/pool.h @@ -39,19 +39,53 @@ namespace snmalloc /** * Class used to instantiate a global non-allocator PoolState. */ - template + template class SingletonPoolState { static inline PoolState state; + static thread_local inline bool initialized; + + /** + * SFINAE helper. Matched only if `T` implements `ensure_init`. Calls it + * if it exists. + */ + SNMALLOC_FAST_PATH static auto call_ensure_init(SharedStateHandle*, int) + -> decltype(SharedStateHandle::ensure_init()) + { + SharedStateHandle::ensure_init(); + } + + /** + * SFINAE helper. Matched only if `T` does not implement `ensure_init`. + * Does nothing if called. + */ + SNMALLOC_FAST_PATH static auto call_ensure_init(SharedStateHandle*, long) {} + + /** + * Call `SharedStateHandle::ensure_init()` if it is implemented, do nothing + * otherwise. + */ + SNMALLOC_FAST_PATH static void ensure_init() + { + call_ensure_init(nullptr, 0); + } public: - static PoolState& pool() + SNMALLOC_FAST_PATH static PoolState& pool() { + if (unlikely(!initialized)) + { + ensure_init(); + initialized = true; + } return state; } }; - template& get_state()> + template< + typename T, + typename SharedStateHandle, + PoolState& get_state() = SingletonPoolState::pool> class Pool { public: diff --git a/src/test/func/pool/pool.cc b/src/test/func/pool/pool.cc new file mode 100644 index 0000000..550dd4e --- /dev/null +++ b/src/test/func/pool/pool.cc @@ -0,0 +1,117 @@ +#include +#include +#include +#include + +using namespace snmalloc; + +struct PoolAEntry : Pooled +{ + size_t field; + + PoolAEntry() : field(1){}; +}; + +using PoolA = Pool; + +struct PoolBEntry : Pooled +{ + size_t field; + + PoolBEntry() : field(0){}; + PoolBEntry(size_t f) : field(f){}; +}; + +using PoolB = Pool; + +void test_alloc() +{ + auto ptr = PoolA::acquire(); + SNMALLOC_CHECK(ptr != nullptr); + // Pool allocations should not be visible to debug_check_empty. + snmalloc::debug_check_empty(); +} + +void test_constructor() +{ + auto ptr1 = PoolA::acquire(); + SNMALLOC_CHECK(ptr1 != nullptr); + SNMALLOC_CHECK(ptr1->field == 1); + + auto ptr2 = PoolB::acquire(); + SNMALLOC_CHECK(ptr2 != nullptr); + SNMALLOC_CHECK(ptr2->field == 0); + + auto ptr3 = PoolB::acquire(1); + SNMALLOC_CHECK(ptr3 != nullptr); + SNMALLOC_CHECK(ptr3->field == 1); +} + +void test_alloc_many() +{ + constexpr size_t count = 16'000'000 / MIN_CHUNK_SIZE; + + std::unordered_set allocated; + + for (size_t i = 0; i < count; ++i) + { + auto ptr = PoolA::acquire(); + SNMALLOC_CHECK(ptr != nullptr); + allocated.insert(ptr); + } + + for (auto ptr : allocated) + { + PoolA::release(ptr); + } +} + +void test_alloc_dealloc() +{ + auto ptr = PoolA::acquire(); + SNMALLOC_CHECK(ptr != nullptr); + PoolA::release(ptr); +} + +void test_double_alloc() +{ + auto ptr1 = PoolA::acquire(); + SNMALLOC_CHECK(ptr1 != nullptr); + auto ptr2 = PoolA::acquire(); + SNMALLOC_CHECK(ptr2 != nullptr); + SNMALLOC_CHECK(ptr1 != ptr2); + PoolA::release(ptr2); + auto ptr3 = PoolA::acquire(); + SNMALLOC_CHECK(ptr2 == ptr3); +} + +void test_different_alloc() +{ + auto ptr1 = PoolA::acquire(); + SNMALLOC_CHECK(ptr1 != nullptr); + PoolA::release(ptr1); + auto ptr2 = PoolB::acquire(); + SNMALLOC_CHECK(ptr2 != nullptr); + SNMALLOC_CHECK(static_cast(ptr1) != static_cast(ptr2)); +} + +int main(int argc, char** argv) +{ + setup(); +#ifdef USE_SYSTEMATIC_TESTING + opt::Opt opt(argc, argv); + size_t seed = opt.is("--seed", 0); + Virtual::systematic_bump_ptr() += seed << 17; +#else + UNUSED(argc); + UNUSED(argv); +#endif + + test_alloc(); + test_constructor(); + test_alloc_many(); + test_alloc_dealloc(); + test_double_alloc(); + test_different_alloc(); + return 0; +} From b7fe8ea65436c106ae9c0311527c909465d3ecf6 Mon Sep 17 00:00:00 2001 From: Istvan Haller Date: Tue, 24 Aug 2021 15:32:51 +0100 Subject: [PATCH 08/11] More comments and improved test --- src/mem/corealloc.h | 3 +++ src/mem/pool.h | 27 ++++++++++++++++++++++++- src/test/func/pool/pool.cc | 41 ++++++++++++++++++++++++++++---------- 3 files changed, 60 insertions(+), 11 deletions(-) diff --git a/src/mem/corealloc.h b/src/mem/corealloc.h index 30dba53..b67df18 100644 --- a/src/mem/corealloc.h +++ b/src/mem/corealloc.h @@ -786,6 +786,9 @@ namespace snmalloc } }; + /** + * Use this alias to access the pool of allocators throughout snmalloc. + */ template using AllocPool = Pool< CoreAllocator, diff --git a/src/mem/pool.h b/src/mem/pool.h index 34f58e9..aecc550 100644 --- a/src/mem/pool.h +++ b/src/mem/pool.h @@ -37,12 +37,20 @@ namespace snmalloc }; /** - * Class used to instantiate a global non-allocator PoolState. + * Helper class used to instantiate a global PoolState. + * + * SingletonPoolState::pool is the default provider for the PoolState within + * the Pool class. */ template class SingletonPoolState { static inline PoolState state; + /** + * Thread-local initialization marker for a contention-free way to skip + * initialization. SharedStateHandle::ensure_init allows for multiple calls, + * but it uses a reentrency-safe check which is more expensive. + */ static thread_local inline bool initialized; /** @@ -71,6 +79,10 @@ namespace snmalloc } 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& pool() { if (unlikely(!initialized)) @@ -82,6 +94,19 @@ namespace snmalloc } }; + /** + * 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, typename SharedStateHandle, diff --git a/src/test/func/pool/pool.cc b/src/test/func/pool/pool.cc index 550dd4e..8288c29 100644 --- a/src/test/func/pool/pool.cc +++ b/src/test/func/pool/pool.cc @@ -7,7 +7,7 @@ using namespace snmalloc; struct PoolAEntry : Pooled { - size_t field; + int field; PoolAEntry() : field(1){}; }; @@ -16,7 +16,7 @@ using PoolA = Pool; struct PoolBEntry : Pooled { - size_t field; + int field; PoolBEntry() : field(0){}; PoolBEntry(size_t f) : field(f){}; @@ -30,6 +30,7 @@ void test_alloc() SNMALLOC_CHECK(ptr != nullptr); // Pool allocations should not be visible to debug_check_empty. snmalloc::debug_check_empty(); + PoolA::release(ptr); } void test_constructor() @@ -45,6 +46,10 @@ void test_constructor() auto ptr3 = PoolB::acquire(1); SNMALLOC_CHECK(ptr3 != nullptr); SNMALLOC_CHECK(ptr3->field == 1); + + PoolA::release(ptr1); + PoolB::release(ptr2); + PoolB::release(ptr3); } void test_alloc_many() @@ -66,13 +71,6 @@ void test_alloc_many() } } -void test_alloc_dealloc() -{ - auto ptr = PoolA::acquire(); - SNMALLOC_CHECK(ptr != nullptr); - PoolA::release(ptr); -} - void test_double_alloc() { auto ptr1 = PoolA::acquire(); @@ -83,6 +81,8 @@ void test_double_alloc() PoolA::release(ptr2); auto ptr3 = PoolA::acquire(); SNMALLOC_CHECK(ptr2 == ptr3); + PoolA::release(ptr1); + PoolA::release(ptr3); } void test_different_alloc() @@ -93,6 +93,27 @@ void test_different_alloc() auto ptr2 = PoolB::acquire(); SNMALLOC_CHECK(ptr2 != nullptr); SNMALLOC_CHECK(static_cast(ptr1) != static_cast(ptr2)); + PoolB::release(ptr2); +} + +void test_iterator() +{ + PoolAEntry* before_iteration_ptr = PoolA::acquire(); + + PoolAEntry* ptr = nullptr; + while ((ptr = PoolA::iterate(ptr)) != nullptr) + { + ptr->field = 2; + } + + SNMALLOC_CHECK(before_iteration_ptr->field == 2); + + PoolAEntry* after_iteration_ptr = PoolA::acquire(); + + SNMALLOC_CHECK(after_iteration_ptr->field == 2); + + PoolA::release(before_iteration_ptr); + PoolA::release(after_iteration_ptr); } int main(int argc, char** argv) @@ -110,8 +131,8 @@ int main(int argc, char** argv) test_alloc(); test_constructor(); test_alloc_many(); - test_alloc_dealloc(); test_double_alloc(); test_different_alloc(); + test_iterator(); return 0; } From 33a358e4fe0e4d75f0af9a6179fa44d8d5771311 Mon Sep 17 00:00:00 2001 From: Istvan Haller Date: Tue, 24 Aug 2021 16:18:02 +0100 Subject: [PATCH 09/11] Another attempt to fix CI --- src/test/func/pool/pool.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/func/pool/pool.cc b/src/test/func/pool/pool.cc index 8288c29..f479f4e 100644 --- a/src/test/func/pool/pool.cc +++ b/src/test/func/pool/pool.cc @@ -19,7 +19,7 @@ struct PoolBEntry : Pooled int field; PoolBEntry() : field(0){}; - PoolBEntry(size_t f) : field(f){}; + PoolBEntry(int f) : field(f){}; }; using PoolB = Pool; From e8cc3af6e5f9d0011dc75c685876a3ef92d99821 Mon Sep 17 00:00:00 2001 From: Istvan Haller Date: Wed, 25 Aug 2021 13:35:13 +0100 Subject: [PATCH 10/11] Applied PR feedback --- src/ds/helpers.h | 4 ++-- src/mem/pool.h | 24 +++++++++--------------- src/mem/threadalloc.h | 6 ++---- 3 files changed, 13 insertions(+), 21 deletions(-) diff --git a/src/ds/helpers.h b/src/ds/helpers.h index e34387d..f8ad92c 100644 --- a/src/ds/helpers.h +++ b/src/ds/helpers.h @@ -13,7 +13,7 @@ namespace snmalloc * initialised. This singleton class is designed to not depend on the * runtime. */ - template + template class Singleton { inline static std::atomic_flag flag; @@ -36,7 +36,7 @@ namespace snmalloc FlagLock lock(flag); if (!initialised) { - obj = init(); + init(&obj); initialised.store(true, std::memory_order_release); if (first != nullptr) *first = true; diff --git a/src/mem/pool.h b/src/mem/pool.h index aecc550..c57b525 100644 --- a/src/mem/pool.h +++ b/src/mem/pool.h @@ -45,14 +45,6 @@ namespace snmalloc template class SingletonPoolState { - static inline PoolState state; - /** - * Thread-local initialization marker for a contention-free way to skip - * initialization. SharedStateHandle::ensure_init allows for multiple calls, - * but it uses a reentrency-safe check which is more expensive. - */ - static thread_local inline bool initialized; - /** * SFINAE helper. Matched only if `T` implements `ensure_init`. Calls it * if it exists. @@ -78,6 +70,13 @@ namespace snmalloc call_ensure_init(nullptr, 0); } + static void make_pool(PoolState*) 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. @@ -85,12 +84,7 @@ namespace snmalloc */ SNMALLOC_FAST_PATH static PoolState& pool() { - if (unlikely(!initialized)) - { - ensure_init(); - initialized = true; - } - return state; + return Singleton, &make_pool>::get(); } }; @@ -102,7 +96,7 @@ namespace snmalloc * 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 + * 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. diff --git a/src/mem/threadalloc.h b/src/mem/threadalloc.h index dc9cee6..3777d44 100644 --- a/src/mem/threadalloc.h +++ b/src/mem/threadalloc.h @@ -106,11 +106,9 @@ namespace snmalloc /** * Used to give correct signature to the pthread call for the Singleton class. */ - inline pthread_key_t pthread_create() noexcept + inline void pthread_create(pthread_key_t* key) noexcept { - pthread_key_t key; - pthread_key_create(&key, &pthread_cleanup); - return key; + pthread_key_create(key, &pthread_cleanup); } /** * Performs thread local teardown for the allocator using the pthread library. From 3ceef4086191c506fd1c5bb478e8d70dc9d57803 Mon Sep 17 00:00:00 2001 From: Istvan Haller Date: Wed, 25 Aug 2021 14:38:58 +0100 Subject: [PATCH 11/11] Fixed ensure_init detection --- src/mem/pool.h | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/mem/pool.h b/src/mem/pool.h index c57b525..f8245c5 100644 --- a/src/mem/pool.h +++ b/src/mem/pool.h @@ -49,17 +49,27 @@ namespace snmalloc * SFINAE helper. Matched only if `T` implements `ensure_init`. Calls it * if it exists. */ - SNMALLOC_FAST_PATH static auto call_ensure_init(SharedStateHandle*, int) - -> decltype(SharedStateHandle::ensure_init()) + template + SNMALLOC_FAST_PATH static auto call_ensure_init(SharedStateHandle_*, int) + -> decltype(SharedStateHandle_::ensure_init()) { - SharedStateHandle::ensure_init(); + static_assert( + std::is_same::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. */ - SNMALLOC_FAST_PATH static auto call_ensure_init(SharedStateHandle*, long) {} + template + SNMALLOC_FAST_PATH static auto call_ensure_init(SharedStateHandle_*, long) + { + static_assert( + std::is_same::value, + "SFINAE parameter, should only be used with SharedStateHandle"); + } /** * Call `SharedStateHandle::ensure_init()` if it is implemented, do nothing @@ -67,7 +77,7 @@ namespace snmalloc */ SNMALLOC_FAST_PATH static void ensure_init() { - call_ensure_init(nullptr, 0); + call_ensure_init(nullptr, 0); } static void make_pool(PoolState*) noexcept