Files
snmalloc/src/mem/slab.h
2021-04-09 12:39:29 +01:00

198 lines
6.4 KiB
C++

#pragma once
#include "freelist.h"
#include "ptrhelpers.h"
#include "superslab.h"
#include <array>
namespace snmalloc
{
class Slab
{
private:
uint16_t address_to_index(address_t p)
{
// Get the offset from the slab for a memory location.
return static_cast<uint16_t>(p - address_cast(this));
}
public:
template<capptr_bounds B>
static CapPtr<Metaslab, B> get_meta(CapPtr<Slab, B> self)
{
static_assert(B == CBArena || B == CBChunkD || B == CBChunk);
auto super = Superslab::get(self);
return super->get_meta(self);
}
/**
* Given a bumpptr and a fast_free_list head reference, builds a new free
* list, and stores it in the fast_free_list. It will only create a page
* worth of allocations, or one if the allocation size is larger than a
* page.
*/
static SNMALLOC_FAST_PATH void alloc_new_list(
CapPtr<void, CBChunk>& bumpptr,
FreeListIter& fast_free_list,
size_t rsize,
LocalEntropy& entropy)
{
auto slab_end = pointer_align_up<SLAB_SIZE>(pointer_offset(bumpptr, 1));
FreeListBuilder<false> b;
SNMALLOC_ASSERT(b.empty());
b.open(bumpptr);
#ifdef CHECK_CLIENT
// Structure to represent the temporary list elements
struct PreAllocObject
{
CapPtr<PreAllocObject, CBAlloc> next;
};
// The following code implements Sattolo's algorithm for generating
// random cyclic permutations. This implementation is in the opposite
// direction, so that the original space does not need initialising. This
// is described as outside-in without citation on Wikipedia, appears to be
// Folklore algorithm.
// Note the wide bounds on curr relative to each of the ->next fields;
// curr is not persisted once the list is built.
CapPtr<PreAllocObject, CBChunk> curr =
pointer_offset(bumpptr, 0).template as_static<PreAllocObject>();
curr->next = Aal::capptr_bound<PreAllocObject, CBAlloc>(curr, rsize);
uint16_t count = 1;
for (curr =
pointer_offset(curr, rsize).template as_static<PreAllocObject>();
curr.as_void() < slab_end;
curr =
pointer_offset(curr, rsize).template as_static<PreAllocObject>())
{
size_t insert_index = entropy.sample(count);
curr->next = std::exchange(
pointer_offset(bumpptr, insert_index * rsize)
.template as_static<PreAllocObject>()
->next,
Aal::capptr_bound<PreAllocObject, CBAlloc>(curr, rsize));
count++;
}
// Pick entry into space, and then build linked list by traversing cycle
// to the start. Use ->next to jump from CBArena to CBAlloc.
auto start_index = entropy.sample(count);
auto start_ptr = pointer_offset(bumpptr, start_index * rsize)
.template as_static<PreAllocObject>()
->next;
auto curr_ptr = start_ptr;
do
{
b.add(FreeObject::make(curr_ptr.as_void()), entropy);
curr_ptr = curr_ptr->next;
} while (curr_ptr != start_ptr);
#else
for (auto p = bumpptr; p < slab_end; p = pointer_offset(p, rsize))
{
b.add(Aal::capptr_bound<FreeObject, CBAlloc>(p, rsize), entropy);
}
#endif
// This code consumes everything up to slab_end.
bumpptr = slab_end;
SNMALLOC_ASSERT(!b.empty());
b.close(fast_free_list, entropy);
}
// Returns true, if it deallocation can proceed without changing any status
// bits. Note that this does remove the use from the meta slab, so it
// doesn't need doing on the slow path.
static SNMALLOC_FAST_PATH bool dealloc_fast(
CapPtr<Slab, CBChunkD> self,
CapPtr<Superslab, CBChunkD> super,
CapPtr<FreeObject, CBAlloc> p,
LocalEntropy& entropy)
{
auto meta = super->get_meta(self);
SNMALLOC_ASSERT(!meta->is_unused());
if (unlikely(meta->return_object()))
return false;
// Update the head and the next pointer in the free list.
meta->free_queue.add(p, entropy);
return true;
}
// If dealloc fast returns false, then call this.
// This does not need to remove the "use" as done by the fast path.
// Returns a complex return code for managing the superslab meta data.
// i.e. This deallocation could make an entire superslab free.
static SNMALLOC_SLOW_PATH typename Superslab::Action dealloc_slow(
CapPtr<Slab, CBChunkD> self,
SlabList* sl,
CapPtr<Superslab, CBChunkD> super,
CapPtr<FreeObject, CBAlloc> p,
LocalEntropy& entropy)
{
auto meta = super->get_meta(self);
meta->debug_slab_invariant(self, entropy);
if (meta->is_full())
{
auto allocated = get_slab_capacity(
meta->sizeclass(),
Metaslab::is_short(
Metaslab::get_slab(Aal::capptr_rebound(super.as_void(), p))));
// We are not on the sizeclass list.
if (allocated == 1)
{
// Dealloc on the superslab.
if (Metaslab::is_short(self))
return super->dealloc_short_slab();
return super->dealloc_slab(self);
}
meta->free_queue.add(p, entropy);
// Remove trigger threshold from how many we need before we have fully
// freed the slab.
meta->needed() =
allocated - meta->threshold_for_waking_slab(Metaslab::is_short(self));
// Push on the list of slabs for this sizeclass.
// ChunkD-to-Chunk conversion might apply bounds, so we need to do so to
// the aligned object and then shift over to these bounds.
auto super_chunk = capptr_chunk_from_chunkd(super, SUPERSLAB_SIZE);
auto metalink = Aal::capptr_rebound(
super_chunk.as_void(), meta.template as_static<SlabLink>());
sl->insert_prev(metalink);
meta->debug_slab_invariant(self, entropy);
return Superslab::NoSlabReturn;
}
#ifdef CHECK_CLIENT
size_t count = 1;
// Check free list is well-formed on platforms with
// integers as pointers.
FreeListIter fl;
meta->free_queue.close(fl, entropy);
while (!fl.empty())
{
fl.take(entropy);
count++;
}
#endif
meta->remove();
if (Metaslab::is_short(self))
return super->dealloc_short_slab();
return super->dealloc_slab(self);
}
};
} // namespace snmalloc