From f38ee89e720cd6dad0b19d334ca32d73cf84f4eb Mon Sep 17 00:00:00 2001 From: Matthew Parkinson Date: Tue, 3 Oct 2023 14:59:23 +0100 Subject: [PATCH] Template construction of Pool elements (#641) * Template construction of Pool elements The Pool class is used by verona-rt. The recent changes made this less nice to consume as an API. This change makes the construction logic a template parameter to the Pool. This enables standard allocation to be used from Verona. * Drop parameter from acquire Pool::acquire took a list of parameters to initialise the object that it constructed. But if this was serviced from the pool, the parameter would be ignored. This is not an ideal API. This PR removes the ability to pass a parameter. --- src/snmalloc/mem/corealloc.h | 38 +++++++++++++-- src/snmalloc/mem/localalloc.h | 2 +- src/snmalloc/mem/pool.h | 90 +++++++++-------------------------- src/snmalloc/mem/pooled.h | 11 ++++- src/test/func/cheri/cheri.cc | 3 +- src/test/func/pool/pool.cc | 30 +++++------- 6 files changed, 82 insertions(+), 92 deletions(-) diff --git a/src/snmalloc/mem/corealloc.h b/src/snmalloc/mem/corealloc.h index cb70f5d..e2a99f2 100644 --- a/src/snmalloc/mem/corealloc.h +++ b/src/snmalloc/mem/corealloc.h @@ -621,8 +621,7 @@ namespace snmalloc template< typename Config_ = Config, typename = std::enable_if_t> - CoreAllocator(Range& spare, LocalCache* cache) - : attached_cache(cache) + CoreAllocator(Range& spare) { init(spare); } @@ -1015,9 +1014,42 @@ namespace snmalloc } }; + template + class ConstructCoreAlloc + { + using CA = CoreAllocator; + + public: + static capptr::Alloc make() + { + size_t size = sizeof(CA); + size_t round_sizeof = Aal::capptr_size_round(size); + size_t request_size = bits::next_pow2(round_sizeof); + size_t spare = request_size - round_sizeof; + + auto raw = + Config::Backend::template alloc_meta_data(nullptr, request_size); + + if (raw == nullptr) + { + Config::Pal::error("Failed to initialise thread local allocator."); + } + + capptr::Alloc spare_start = pointer_offset(raw, round_sizeof); + Range r{spare_start, spare}; + + auto p = capptr::Alloc::unsafe_from(new (raw.unsafe_ptr()) CA(r)); + + // Remove excess from the bounds. + p = Aal::capptr_bound(p, round_sizeof); + return p; + } + }; + /** * Use this alias to access the pool of allocators throughout snmalloc. */ template - using AllocPool = Pool, Config, Config::pool>; + using AllocPool = + Pool, ConstructCoreAlloc, Config::pool>; } // namespace snmalloc diff --git a/src/snmalloc/mem/localalloc.h b/src/snmalloc/mem/localalloc.h index 75be56e..e26243a 100644 --- a/src/snmalloc/mem/localalloc.h +++ b/src/snmalloc/mem/localalloc.h @@ -393,7 +393,7 @@ namespace snmalloc // Initialise the global allocator structures ensure_init(); // Grab an allocator for this thread. - init(AllocPool::acquire(&(this->local_cache))); + init(AllocPool::acquire()); } // Return all state in the fast allocator and release the underlying diff --git a/src/snmalloc/mem/pool.h b/src/snmalloc/mem/pool.h index cbcbdb1..8db58ec 100644 --- a/src/snmalloc/mem/pool.h +++ b/src/snmalloc/mem/pool.h @@ -22,7 +22,7 @@ namespace snmalloc { template< typename TT, - SNMALLOC_CONCEPT(IsConfig) Config, + SNMALLOC_CONCEPT(Constructable) Construct, PoolState& get_state()> friend class Pool; @@ -45,50 +45,10 @@ namespace snmalloc * SingletonPoolState::pool is the default provider for the PoolState within * the Pool class. */ - template + template class SingletonPoolState { - /** - * SFINAE helper. Matched only if `T` implements `ensure_init`. Calls it - * if it exists. - */ - template - SNMALLOC_FAST_PATH static auto call_ensure_init(SharedStateHandle_*, int) - -> decltype(SharedStateHandle_::ensure_init()) - { - static_assert( - std::is_same::value, - "SFINAE parameter, should only be used with Config"); - SharedStateHandle_::ensure_init(); - } - - /** - * SFINAE helper. Matched only if `T` does not implement `ensure_init`. - * Does nothing if called. - */ - template - SNMALLOC_FAST_PATH static auto call_ensure_init(SharedStateHandle_*, long) - { - static_assert( - std::is_same::value, - "SFINAE parameter, should only be used with Config"); - } - - /** - * Call `Config::ensure_init()` if it is implemented, do nothing - * otherwise. - */ - SNMALLOC_FAST_PATH static void ensure_init() - { - 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. - } + static void make_pool(PoolState*) noexcept {} public: /** @@ -101,6 +61,22 @@ namespace snmalloc } }; + /** + * @brief Default construct helper for the pool. Just uses `new`. This can't + * be used by the allocator pool as it has not created memory yet. + * + * @tparam T + */ + template + class DefaultConstruct + { + public: + static capptr::Alloc make() + { + return capptr::Alloc::unsafe_from(new T()); + } + }; + /** * Wrapper class to access a pool of a particular type of object. * @@ -116,13 +92,12 @@ namespace snmalloc */ template< typename T, - SNMALLOC_CONCEPT(IsConfig) Config, - PoolState& get_state() = SingletonPoolState::pool> + SNMALLOC_CONCEPT(Constructable) ConstructT = DefaultConstruct, + PoolState& get_state() = SingletonPoolState::pool> class Pool { public: - template - static T* acquire(Args&&... args) + static T* acquire() { PoolState& pool = get_state(); { @@ -141,26 +116,7 @@ namespace snmalloc } } - size_t request_size = bits::next_pow2(sizeof(T)); - size_t round_sizeof = Aal::capptr_size_round(sizeof(T)); - size_t spare = request_size - round_sizeof; - - auto raw = - Config::Backend::template alloc_meta_data(nullptr, request_size); - - if (raw == nullptr) - { - Config::Pal::error("Failed to initialise thread local allocator."); - } - - capptr::Alloc spare_start = pointer_offset(raw, round_sizeof); - Range r{spare_start, spare}; - - auto p = capptr::Alloc::unsafe_from( - new (raw.unsafe_ptr()) T(r, std::forward(args)...)); - - // Remove excess from the permissions. - p = Aal::capptr_bound(p, round_sizeof); + auto p = ConstructT::make(); FlagLock f(pool.lock); p->list_next = pool.list; diff --git a/src/snmalloc/mem/pooled.h b/src/snmalloc/mem/pooled.h index 7fb0ce3..4e7c768 100644 --- a/src/snmalloc/mem/pooled.h +++ b/src/snmalloc/mem/pooled.h @@ -15,6 +15,15 @@ namespace snmalloc template class PoolState; +#ifdef __cpp_concepts + template + concept Constructable = requires() { + { + C::make() + } -> ConceptSame>; + }; +#endif // __cpp_concepts + /** * Required to be implemented by all types that are pooled. * @@ -29,7 +38,7 @@ namespace snmalloc public: template< typename TT, - SNMALLOC_CONCEPT(IsConfig) Config, + SNMALLOC_CONCEPT(Constructable) Construct, PoolState& get_state()> friend class Pool; diff --git a/src/test/func/cheri/cheri.cc b/src/test/func/cheri/cheri.cc index 4efaec6..cde8be0 100644 --- a/src/test/func/cheri/cheri.cc +++ b/src/test/func/cheri/cheri.cc @@ -133,8 +133,7 @@ int main() static_assert( std::is_same_v>); - LocalCache lc{&StandardConfig::unused_remote}; - auto* ca = AllocPool::acquire(&lc); + auto* ca = AllocPool::acquire(); SNMALLOC_CHECK(cap_len_is(ca, sizeof(*ca))); SNMALLOC_CHECK(cap_vmem_perm_is(ca, false)); diff --git a/src/test/func/pool/pool.cc b/src/test/func/pool/pool.cc index 600118d..2ed960a 100644 --- a/src/test/func/pool/pool.cc +++ b/src/test/func/pool/pool.cc @@ -11,26 +11,25 @@ struct PoolAEntry : Pooled { int field; - PoolAEntry(Range&) : field(1){}; + PoolAEntry() : field(1){}; }; -using PoolA = Pool; +using PoolA = Pool; struct PoolBEntry : Pooled { int field; - PoolBEntry(Range&) : field(0){}; - PoolBEntry(Range&, int f) : field(f){}; + PoolBEntry() : field(0){}; }; -using PoolB = Pool; +using PoolB = Pool; struct PoolLargeEntry : Pooled { std::array payload; - PoolLargeEntry(Range&) + PoolLargeEntry() { printf("."); fflush(stdout); @@ -41,18 +40,18 @@ struct PoolLargeEntry : Pooled }; }; -using PoolLarge = Pool; +using PoolLarge = Pool; template struct PoolSortEntry : Pooled> { int field; - PoolSortEntry(Range&, int f) : field(f){}; + PoolSortEntry() : field(1){}; }; template -using PoolSort = Pool, Alloc::Config>; +using PoolSort = Pool>; void test_alloc() { @@ -73,13 +72,8 @@ void test_constructor() SNMALLOC_CHECK(ptr2 != nullptr); SNMALLOC_CHECK(ptr2->field == 0); - 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() @@ -181,8 +175,8 @@ void test_sort() // This test checks that `sort` puts the elements in the right order, // so it is the same as if they had been allocated in that order. - auto a1 = PoolSort::acquire(1); - auto a2 = PoolSort::acquire(1); + auto a1 = PoolSort::acquire(); + auto a2 = PoolSort::acquire(); auto position1 = position(a1); auto position2 = position(a2); @@ -201,8 +195,8 @@ void test_sort() PoolSort::sort(); - auto b1 = PoolSort::acquire(1); - auto b2 = PoolSort::acquire(1); + auto b1 = PoolSort::acquire(); + auto b2 = PoolSort::acquire(); SNMALLOC_CHECK(position1 == position(b1)); SNMALLOC_CHECK(position2 == position(b2));