Skip to content

Commit

Permalink
Template construction of Pool elements (#641)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
mjp41 authored Oct 3, 2023
1 parent 5543347 commit f38ee89
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 92 deletions.
38 changes: 35 additions & 3 deletions src/snmalloc/mem/corealloc.h
Original file line number Diff line number Diff line change
Expand Up @@ -621,8 +621,7 @@ namespace snmalloc
template<
typename Config_ = Config,
typename = std::enable_if_t<Config_::Options.CoreAllocOwnsLocalState>>
CoreAllocator(Range<capptr::bounds::Alloc>& spare, LocalCache* cache)
: attached_cache(cache)
CoreAllocator(Range<capptr::bounds::Alloc>& spare)
{
init(spare);
}
Expand Down Expand Up @@ -1015,9 +1014,42 @@ namespace snmalloc
}
};

template<typename Config>
class ConstructCoreAlloc
{
using CA = CoreAllocator<Config>;

public:
static capptr::Alloc<CA> 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<CA>(nullptr, request_size);

if (raw == nullptr)
{
Config::Pal::error("Failed to initialise thread local allocator.");
}

capptr::Alloc<void> spare_start = pointer_offset(raw, round_sizeof);
Range<capptr::bounds::Alloc> r{spare_start, spare};

auto p = capptr::Alloc<CA>::unsafe_from(new (raw.unsafe_ptr()) CA(r));

// Remove excess from the bounds.
p = Aal::capptr_bound<CA, capptr::bounds::Alloc>(p, round_sizeof);
return p;
}
};

/**
* Use this alias to access the pool of allocators throughout snmalloc.
*/
template<typename Config>
using AllocPool = Pool<CoreAllocator<Config>, Config, Config::pool>;
using AllocPool =
Pool<CoreAllocator<Config>, ConstructCoreAlloc<Config>, Config::pool>;
} // namespace snmalloc
2 changes: 1 addition & 1 deletion src/snmalloc/mem/localalloc.h
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ namespace snmalloc
// Initialise the global allocator structures
ensure_init();
// Grab an allocator for this thread.
init(AllocPool<Config>::acquire(&(this->local_cache)));
init(AllocPool<Config>::acquire());
}

// Return all state in the fast allocator and release the underlying
Expand Down
90 changes: 23 additions & 67 deletions src/snmalloc/mem/pool.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ namespace snmalloc
{
template<
typename TT,
SNMALLOC_CONCEPT(IsConfig) Config,
SNMALLOC_CONCEPT(Constructable<TT>) Construct,
PoolState<TT>& get_state()>
friend class Pool;

Expand All @@ -45,50 +45,10 @@ namespace snmalloc
* SingletonPoolState::pool is the default provider for the PoolState within
* the Pool class.
*/
template<typename T, SNMALLOC_CONCEPT(IsConfig) Config>
template<typename T>
class SingletonPoolState
{
/**
* SFINAE helper. Matched only if `T` implements `ensure_init`. Calls it
* if it exists.
*/
template<typename SharedStateHandle_>
SNMALLOC_FAST_PATH static auto call_ensure_init(SharedStateHandle_*, int)
-> decltype(SharedStateHandle_::ensure_init())
{
static_assert(
std::is_same<Config, SharedStateHandle_>::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<typename SharedStateHandle_>
SNMALLOC_FAST_PATH static auto call_ensure_init(SharedStateHandle_*, long)
{
static_assert(
std::is_same<Config, SharedStateHandle_>::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<Config>(nullptr, 0);
}

static void make_pool(PoolState<T>*) noexcept
{
ensure_init();
// Default initializer already called on PoolState, no need to use
// placement new.
}
static void make_pool(PoolState<T>*) noexcept {}

public:
/**
Expand All @@ -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<typename T>
class DefaultConstruct
{
public:
static capptr::Alloc<T> make()
{
return capptr::Alloc<T>::unsafe_from(new T());
}
};

/**
* Wrapper class to access a pool of a particular type of object.
*
Expand All @@ -116,13 +92,12 @@ namespace snmalloc
*/
template<
typename T,
SNMALLOC_CONCEPT(IsConfig) Config,
PoolState<T>& get_state() = SingletonPoolState<T, Config>::pool>
SNMALLOC_CONCEPT(Constructable<T>) ConstructT = DefaultConstruct<T>,
PoolState<T>& get_state() = SingletonPoolState<T>::pool>
class Pool
{
public:
template<typename... Args>
static T* acquire(Args&&... args)
static T* acquire()
{
PoolState<T>& pool = get_state();
{
Expand All @@ -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<T>(nullptr, request_size);

if (raw == nullptr)
{
Config::Pal::error("Failed to initialise thread local allocator.");
}

capptr::Alloc<void> spare_start = pointer_offset(raw, round_sizeof);
Range<capptr::bounds::Alloc> r{spare_start, spare};

auto p = capptr::Alloc<T>::unsafe_from(
new (raw.unsafe_ptr()) T(r, std::forward<Args>(args)...));

// Remove excess from the permissions.
p = Aal::capptr_bound<T, capptr::bounds::Alloc>(p, round_sizeof);
auto p = ConstructT::make();

FlagLock f(pool.lock);
p->list_next = pool.list;
Expand Down
11 changes: 10 additions & 1 deletion src/snmalloc/mem/pooled.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ namespace snmalloc
template<class T>
class PoolState;

#ifdef __cpp_concepts
template<typename C, typename T>
concept Constructable = requires() {
{
C::make()
} -> ConceptSame<capptr::Alloc<T>>;
};
#endif // __cpp_concepts

/**
* Required to be implemented by all types that are pooled.
*
Expand All @@ -29,7 +38,7 @@ namespace snmalloc
public:
template<
typename TT,
SNMALLOC_CONCEPT(IsConfig) Config,
SNMALLOC_CONCEPT(Constructable<TT>) Construct,
PoolState<TT>& get_state()>
friend class Pool;

Expand Down
3 changes: 1 addition & 2 deletions src/test/func/cheri/cheri.cc
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,7 @@ int main()
static_assert(
std::is_same_v<decltype(alloc.alloc), LocalAllocator<StandardConfig>>);

LocalCache lc{&StandardConfig::unused_remote};
auto* ca = AllocPool<StandardConfig>::acquire(&lc);
auto* ca = AllocPool<StandardConfig>::acquire();

SNMALLOC_CHECK(cap_len_is(ca, sizeof(*ca)));
SNMALLOC_CHECK(cap_vmem_perm_is(ca, false));
Expand Down
30 changes: 12 additions & 18 deletions src/test/func/pool/pool.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,25 @@ struct PoolAEntry : Pooled<PoolAEntry>
{
int field;

PoolAEntry(Range<capptr::bounds::Alloc>&) : field(1){};
PoolAEntry() : field(1){};
};

using PoolA = Pool<PoolAEntry, Alloc::Config>;
using PoolA = Pool<PoolAEntry>;

struct PoolBEntry : Pooled<PoolBEntry>
{
int field;

PoolBEntry(Range<capptr::bounds::Alloc>&) : field(0){};
PoolBEntry(Range<capptr::bounds::Alloc>&, int f) : field(f){};
PoolBEntry() : field(0){};
};

using PoolB = Pool<PoolBEntry, Alloc::Config>;
using PoolB = Pool<PoolBEntry>;

struct PoolLargeEntry : Pooled<PoolLargeEntry>
{
std::array<int, 2'000'000> payload;

PoolLargeEntry(Range<capptr::bounds::Alloc>&)
PoolLargeEntry()
{
printf(".");
fflush(stdout);
Expand All @@ -41,18 +40,18 @@ struct PoolLargeEntry : Pooled<PoolLargeEntry>
};
};

using PoolLarge = Pool<PoolLargeEntry, Alloc::Config>;
using PoolLarge = Pool<PoolLargeEntry>;

template<bool order>
struct PoolSortEntry : Pooled<PoolSortEntry<order>>
{
int field;

PoolSortEntry(Range<capptr::bounds::Alloc>&, int f) : field(f){};
PoolSortEntry() : field(1){};
};

template<bool order>
using PoolSort = Pool<PoolSortEntry<order>, Alloc::Config>;
using PoolSort = Pool<PoolSortEntry<order>>;

void test_alloc()
{
Expand All @@ -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()
Expand Down Expand Up @@ -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<order>::acquire(1);
auto a2 = PoolSort<order>::acquire(1);
auto a1 = PoolSort<order>::acquire();
auto a2 = PoolSort<order>::acquire();

auto position1 = position(a1);
auto position2 = position(a2);
Expand All @@ -201,8 +195,8 @@ void test_sort()

PoolSort<order>::sort();

auto b1 = PoolSort<order>::acquire(1);
auto b2 = PoolSort<order>::acquire(1);
auto b1 = PoolSort<order>::acquire();
auto b2 = PoolSort<order>::acquire();

SNMALLOC_CHECK(position1 == position(b1));
SNMALLOC_CHECK(position2 == position(b2));
Expand Down

0 comments on commit f38ee89

Please sign in to comment.