Refactor use of sizeclasses (#415)

The primary aim for this refactor is to use a representation for
sizeclasses that uniformly covers both large and small.  This allows
certain operations such as alloc_size and external_pointer to be
uniformly implemented.

The additional types make clear which kind of sizeclass is in use.

This also tidies up the code for sizeclass based divisible by and
modulus.

It fixes a bug in rust_realloc that didn't correctly determine a realloc
was required for large classes.
This commit is contained in:
Matthew Parkinson
2021-11-10 16:35:44 +00:00
committed by GitHub
parent 02f36a4b31
commit 3d403aef7f
19 changed files with 465 additions and 273 deletions

View File

@@ -97,7 +97,7 @@ namespace snmalloc
//
// dealloc() can reject attempts to free such MetaEntry-s due to the
// zero sizeclass.
MetaEntry t(reinterpret_cast<Metaslab*>(next.unsafe_ptr()), nullptr, 0);
MetaEntry t(reinterpret_cast<Metaslab*>(next.unsafe_ptr()), nullptr);
Pagemap::set_metaentry(local_state, address_cast(base), 1, t);
return;
}

View File

@@ -68,9 +68,9 @@ namespace snmalloc
#endif
// Maximum size of an object that uses sizeclasses.
static constexpr size_t MAX_SIZECLASS_BITS = 16;
static constexpr size_t MAX_SIZECLASS_SIZE =
bits::one_at_bit(MAX_SIZECLASS_BITS);
static constexpr size_t MAX_SMALL_SIZECLASS_BITS = 16;
static constexpr size_t MAX_SMALL_SIZECLASS_SIZE =
bits::one_at_bit(MAX_SMALL_SIZECLASS_BITS);
// Number of slots for remote deallocation.
static constexpr size_t REMOTE_SLOT_BITS = 8;

View File

@@ -179,7 +179,7 @@ namespace snmalloc
#endif
}
void sizeclass_alloc(sizeclass_t sc)
void sizeclass_alloc(smallsizeclass_t sc)
{
UNUSED(sc);
@@ -189,7 +189,7 @@ namespace snmalloc
#endif
}
void sizeclass_dealloc(sizeclass_t sc)
void sizeclass_dealloc(smallsizeclass_t sc)
{
UNUSED(sc);
@@ -209,7 +209,7 @@ namespace snmalloc
#endif
}
void sizeclass_alloc_slab(sizeclass_t sc)
void sizeclass_alloc_slab(smallsizeclass_t sc)
{
UNUSED(sc);
@@ -219,7 +219,7 @@ namespace snmalloc
#endif
}
void sizeclass_dealloc_slab(sizeclass_t sc)
void sizeclass_dealloc_slab(smallsizeclass_t sc)
{
UNUSED(sc);
@@ -266,7 +266,7 @@ namespace snmalloc
#endif
}
void remote_free(sizeclass_t sc)
void remote_free(smallsizeclass_t sc)
{
UNUSED(sc);
@@ -282,7 +282,7 @@ namespace snmalloc
#endif
}
void remote_receive(sizeclass_t sc)
void remote_receive(smallsizeclass_t sc)
{
UNUSED(sc);
@@ -374,7 +374,7 @@ namespace snmalloc
<< "Count" << csv.endl;
}
for (sizeclass_t i = 0; i < N; i++)
for (smallsizeclass_t i = 0; i < N; i++)
{
if (sizeclass[i].count.is_unused())
continue;
@@ -387,15 +387,15 @@ namespace snmalloc
sizeclass[i].print(csv, sizeclass_to_size(i));
}
for (uint8_t i = 0; i < LARGE_N; i++)
{
if ((large_push_count[i] == 0) && (large_pop_count[i] == 0))
continue;
// for (uint8_t i = 0; i < LARGE_N; i++)
// {
// if ((large_push_count[i] == 0) && (large_pop_count[i] == 0))
// continue;
csv << "LargeBucketedStats" << dumpid << allocatorid << (i + N)
<< large_sizeclass_to_size(i) << large_push_count[i]
<< large_pop_count[i] << csv.endl;
}
// csv << "LargeBucketedStats" << dumpid << allocatorid << (i + N)
// << large_sizeclass_to_size(i) << large_push_count[i]
// << large_pop_count[i] << csv.endl;
// }
size_t low = 0;
size_t high = 0;

View File

@@ -182,7 +182,7 @@ namespace snmalloc
typename SharedStateHandle::LocalState& local_state,
ChunkAllocatorLocalState& chunk_alloc_local_state,
sizeclass_t sizeclass,
sizeclass_t slab_sizeclass, // TODO sizeclass_t
chunksizeclass_t slab_sizeclass,
size_t slab_size,
RemoteAllocator* remote)
{

View File

@@ -56,7 +56,7 @@ namespace snmalloc
/**
* Per size class list of active slabs for this allocator.
*/
MetaslabCache alloc_classes[NUM_SIZECLASSES];
MetaslabCache alloc_classes[NUM_SMALL_SIZECLASSES];
/**
* Local cache for the Chunk allocator.
@@ -196,7 +196,9 @@ namespace snmalloc
};
// Use attached cache, and fill it if it is empty.
return attached_cache->template alloc<NoZero, SharedStateHandle>(
domesticate, size, [&](sizeclass_t sizeclass, freelist::Iter<>* fl) {
domesticate,
size,
[&](smallsizeclass_t sizeclass, freelist::Iter<>* fl) {
return small_alloc<NoZero>(sizeclass, *fl);
});
}
@@ -285,7 +287,7 @@ namespace snmalloc
bumpptr = slab_end;
}
ChunkRecord* clear_slab(Metaslab* meta, sizeclass_t sizeclass)
ChunkRecord* clear_slab(Metaslab* meta, smallsizeclass_t sizeclass)
{
auto& key = entropy.get_free_list_key();
freelist::Iter<> fl;
@@ -347,7 +349,7 @@ namespace snmalloc
}
template<bool check_slabs = false>
SNMALLOC_SLOW_PATH void dealloc_local_slabs(sizeclass_t sizeclass)
SNMALLOC_SLOW_PATH void dealloc_local_slabs(smallsizeclass_t sizeclass)
{
// Return unused slabs of sizeclass_t back to global allocator
alloc_classes[sizeclass].available.filter([this,
@@ -400,7 +402,7 @@ namespace snmalloc
// TODO: Handle message queue on this path?
Metaslab* meta = entry.get_metaslab();
sizeclass_t sizeclass = entry.get_sizeclass();
smallsizeclass_t sizeclass = entry.get_sizeclass().as_small();
UNUSED(entropy);
if (meta->is_sleeping())
@@ -557,11 +559,11 @@ namespace snmalloc
get_backend_local_state(), chunk_local_state);
#ifndef NDEBUG
for (sizeclass_t i = 0; i < NUM_SIZECLASSES; i++)
for (smallsizeclass_t i = 0; i < NUM_SMALL_SIZECLASSES; i++)
{
size_t size = sizeclass_to_size(i);
sizeclass_t sc1 = size_to_sizeclass(size);
sizeclass_t sc2 = size_to_sizeclass_const(size);
smallsizeclass_t sc1 = size_to_sizeclass(size);
smallsizeclass_t sc2 = size_to_sizeclass_const(size);
size_t size1 = sizeclass_to_size(sc1);
size_t size2 = sizeclass_to_size(sc2);
@@ -662,7 +664,8 @@ namespace snmalloc
SNMALLOC_ASSERT(!meta->is_unused());
check_client(
Metaslab::is_start_of_object(entry.get_sizeclass(), address_cast(p)),
Metaslab::is_start_of_object(
entry.get_sizeclass().as_small(), address_cast(p)),
"Not deallocating start of an object");
auto cp = p.as_static<freelist::Object::T<>>();
@@ -677,7 +680,7 @@ namespace snmalloc
template<ZeroMem zero_mem>
SNMALLOC_SLOW_PATH capptr::Alloc<void>
small_alloc(sizeclass_t sizeclass, freelist::Iter<>& fast_free_list)
small_alloc(smallsizeclass_t sizeclass, freelist::Iter<>& fast_free_list)
{
// Look to see if we can grab a free list.
auto& sl = alloc_classes[sizeclass].available;
@@ -737,8 +740,8 @@ namespace snmalloc
}
template<ZeroMem zero_mem>
SNMALLOC_SLOW_PATH capptr::Alloc<void>
small_alloc_slow(sizeclass_t sizeclass, freelist::Iter<>& fast_free_list)
SNMALLOC_SLOW_PATH capptr::Alloc<void> small_alloc_slow(
smallsizeclass_t sizeclass, freelist::Iter<>& fast_free_list)
{
size_t rsize = sizeclass_to_size(sizeclass);
@@ -755,7 +758,7 @@ namespace snmalloc
snmalloc::ChunkAllocator::alloc_chunk<SharedStateHandle>(
get_backend_local_state(),
chunk_local_state,
sizeclass,
sizeclass_t::from_small_class(sizeclass),
slab_sizeclass,
slab_size,
public_state());
@@ -831,7 +834,8 @@ namespace snmalloc
[&](capptr::Alloc<void> p) { dealloc_local_object(p); });
// We may now have unused slabs, return to the global allocator.
for (sizeclass_t sizeclass = 0; sizeclass < NUM_SIZECLASSES; sizeclass++)
for (smallsizeclass_t sizeclass = 0; sizeclass < NUM_SMALL_SIZECLASSES;
sizeclass++)
{
dealloc_local_slabs<true>(sizeclass);
}

View File

@@ -183,7 +183,7 @@ namespace snmalloc
auto [chunk, meta] = ChunkAllocator::alloc_chunk<SharedStateHandle>(
core_alloc->get_backend_local_state(),
core_alloc->chunk_local_state,
bits::next_pow2_bits(size), // TODO
size_to_sizeclass_full(size),
large_size_to_chunk_sizeclass(size),
large_size_to_chunk_size(size),
SharedStateHandle::fake_large_remote);
@@ -211,21 +211,20 @@ namespace snmalloc
template<ZeroMem zero_mem>
SNMALLOC_FAST_PATH capptr::Alloc<void> small_alloc(size_t size)
{
// SNMALLOC_ASSUME(size <= sizeclass_to_size(NUM_SIZECLASSES));
auto domesticate = [this](freelist::QueuePtr p)
SNMALLOC_FAST_PATH_LAMBDA {
return capptr_domesticate<SharedStateHandle>(
core_alloc->backend_state_ptr(), p);
};
auto slowpath = [&](
sizeclass_t sizeclass,
smallsizeclass_t sizeclass,
freelist::Iter<>* fl) SNMALLOC_FAST_PATH_LAMBDA {
if (likely(core_alloc != nullptr))
{
return core_alloc->handle_message_queue(
[](
CoreAlloc* core_alloc,
sizeclass_t sizeclass,
smallsizeclass_t sizeclass,
freelist::Iter<>* fl) {
return core_alloc->template small_alloc<zero_mem>(sizeclass, *fl);
},
@@ -234,7 +233,7 @@ namespace snmalloc
fl);
}
return lazy_init(
[&](CoreAlloc*, sizeclass_t sizeclass) {
[&](CoreAlloc*, smallsizeclass_t sizeclass) {
return small_alloc<zero_mem>(sizeclass_to_size(sizeclass));
},
sizeclass);
@@ -429,7 +428,8 @@ namespace snmalloc
#else
// Perform the - 1 on size, so that zero wraps around and ends up on
// slow path.
if (likely((size - 1) <= (sizeclass_to_size(NUM_SIZECLASSES - 1) - 1)))
if (likely(
(size - 1) <= (sizeclass_to_size(NUM_SMALL_SIZECLASSES - 1) - 1)))
{
// Small allocations are more likely. Improve
// branch prediction by placing this case first.
@@ -446,7 +446,6 @@ namespace snmalloc
template<size_t size, ZeroMem zero_mem = NoZero>
SNMALLOC_FAST_PATH ALLOCATOR void* alloc()
{
// TODO optimise
return alloc<zero_mem>(size);
}
@@ -455,7 +454,6 @@ namespace snmalloc
#ifdef SNMALLOC_PASS_THROUGH
external_alloc::free(p_raw);
#else
// TODO:
// Care is needed so that dealloc(nullptr) works before init
// The backend allocator must ensure that a minimal page map exists
// before init, that maps null to a remote_deallocator that will never
@@ -508,16 +506,10 @@ namespace snmalloc
}
// Large deallocation or null.
if (likely(p_tame != nullptr))
// also checks for managed by page map.
if (likely((p_tame != nullptr) && !entry.get_sizeclass().is_default()))
{
size_t entry_sizeclass = entry.get_sizeclass();
// Check this is managed by this pagemap.
//
// TODO: Should this be tested even in the !CHECK_CLIENT case? Things
// go fairly pear-shaped, with the ASM's ranges[] getting cross-linked
// with a ChunkAllocator's chunk_stack[0], which seems bad.
check_client(entry_sizeclass != 0, "Not allocated by snmalloc.");
size_t entry_sizeclass = entry.get_sizeclass().as_large();
size_t size = bits::one_at_bit(entry_sizeclass);
size_t slab_sizeclass =
@@ -558,6 +550,11 @@ namespace snmalloc
return;
}
// If p_tame is not null, then dealloc has been call on something
// it shouldn't be called on.
// TODO: Should this be tested even in the !CHECK_CLIENT case?
check_client(p_tame == nullptr, "Not allocated by snmalloc.");
# ifdef SNMALLOC_TRACING
std::cout << "nullptr deallocation" << std::endl;
# endif
@@ -611,14 +608,7 @@ namespace snmalloc
MetaEntry entry = SharedStateHandle::Pagemap::get_metaentry(
core_alloc->backend_state_ptr(), address_cast(p_raw));
if (likely(entry.get_remote() != SharedStateHandle::fake_large_remote))
return sizeclass_to_size(entry.get_sizeclass());
// Sizeclass zero is for large is actually zero
if (likely(entry.get_sizeclass() != 0))
return bits::one_at_bit(entry.get_sizeclass());
return 0;
return sizeclass_full_to_size(entry.get_sizeclass());
#endif
}
@@ -630,61 +620,65 @@ namespace snmalloc
* the potential pointer space.
*/
template<Boundary location = Start>
void* external_pointer(void* p_raw)
void* external_pointer(void* p)
{
// Note that each case uses `pointer_offset`, so that on
// CHERI it is monotone with respect to the capability.
// Note that the returned pointer could be outside the CHERI
// bounds of `p`, and thus not something that can be followed.
if constexpr (location == Start)
{
size_t index = index_in_object(p);
return pointer_offset(p, 0 - index);
}
else if constexpr (location == End)
{
return pointer_offset(p, remaining_bytes(p) - 1);
}
else
{
return pointer_offset(p, remaining_bytes(p));
}
}
/**
* Returns the number of remaining bytes in an object.
*
* auto p = (char*)malloc(size)
* remaining_bytes(p + n) == size - n provided n < size
*/
size_t remaining_bytes(const void* p)
{
#ifndef SNMALLOC_PASS_THROUGH
// TODO What's the domestication policy here? At the moment we just
// probe the pagemap with the raw address, without checks. There could
// be implicit domestication through the `SharedStateHandle::Pagemap` or
// we could just leave well enough alone.
capptr::AllocWild<void> p = capptr_from_client(p_raw);
MetaEntry entry =
SharedStateHandle::Pagemap::template get_metaentry<true>(
core_alloc->backend_state_ptr(), address_cast(p));
auto sizeclass = entry.get_sizeclass();
if (likely(entry.get_remote() != SharedStateHandle::fake_large_remote))
{
auto rsize = sizeclass_to_size(sizeclass);
auto offset = address_cast(p) & (sizeclass_to_slab_size(sizeclass) - 1);
auto start_offset = round_by_sizeclass(sizeclass, offset);
if constexpr (location == Start)
{
UNUSED(rsize);
return capptr_reveal_wild(pointer_offset(p, start_offset - offset));
}
else if constexpr (location == End)
return capptr_reveal_wild(
pointer_offset(p, rsize + start_offset - offset - 1));
else
return capptr_reveal_wild(
pointer_offset(p, rsize + start_offset - offset));
}
// Sizeclass zero of a large allocation is used for not managed by us.
if (likely(sizeclass != 0))
{
// This is a large allocation, find start by masking.
auto rsize = bits::one_at_bit(sizeclass);
auto start = pointer_align_down(p, rsize);
if constexpr (location == Start)
return capptr_reveal_wild(start);
else if constexpr (location == End)
return capptr_reveal_wild(pointer_offset(start, rsize - 1));
else
return capptr_reveal_wild(pointer_offset(start, rsize));
}
return snmalloc::remaining_bytes(sizeclass, address_cast(p));
#else
UNUSED(p_raw);
return pointer_diff(p, reinterpret_cast<void*>(UINTPTR_MAX));
#endif
}
if constexpr ((location == End) || (location == OnePastEnd))
// We don't know the End, so return MAX_PTR
return reinterpret_cast<void*>(UINTPTR_MAX);
else
// We don't know the Start, so return MIN_PTR
return nullptr;
/**
* Returns the byte offset into an object.
*
* auto p = (char*)malloc(size)
* index_in_object(p + n) == n provided n < size
*/
size_t index_in_object(const void* p)
{
#ifndef SNMALLOC_PASS_THROUGH
MetaEntry entry =
SharedStateHandle::Pagemap::template get_metaentry<true>(
core_alloc->backend_state_ptr(), address_cast(p));
auto sizeclass = entry.get_sizeclass();
return snmalloc::index_in_object(sizeclass, address_cast(p));
#else
return reinterpret_cast<size_t>(p);
#endif
}
/**

View File

@@ -10,10 +10,10 @@
namespace snmalloc
{
using Stats = AllocStats<NUM_SIZECLASSES, NUM_LARGE_CLASSES>;
using Stats = AllocStats<NUM_SMALL_SIZECLASSES, NUM_LARGE_CLASSES>;
inline static SNMALLOC_FAST_PATH capptr::Alloc<void>
finish_alloc_no_zero(freelist::HeadPtr p, sizeclass_t sizeclass)
finish_alloc_no_zero(freelist::HeadPtr p, smallsizeclass_t sizeclass)
{
SNMALLOC_ASSERT(Metaslab::is_start_of_object(sizeclass, address_cast(p)));
UNUSED(sizeclass);
@@ -23,7 +23,7 @@ namespace snmalloc
template<ZeroMem zero_mem, typename SharedStateHandle>
inline static SNMALLOC_FAST_PATH capptr::Alloc<void>
finish_alloc(freelist::HeadPtr p, sizeclass_t sizeclass)
finish_alloc(freelist::HeadPtr p, smallsizeclass_t sizeclass)
{
auto r = finish_alloc_no_zero(p, sizeclass);
@@ -45,7 +45,7 @@ namespace snmalloc
// Free list per small size class. These are used for
// allocation on the fast path. This part of the code is inspired by
// mimalloc.
freelist::Iter<> small_fast_free_lists[NUM_SIZECLASSES] = {};
freelist::Iter<> small_fast_free_lists[NUM_SMALL_SIZECLASSES] = {};
// This is the entropy for a particular thread.
LocalEntropy entropy;
@@ -83,7 +83,7 @@ namespace snmalloc
return capptr_domesticate<SharedStateHandle>(local_state, p);
};
for (size_t i = 0; i < NUM_SIZECLASSES; i++)
for (size_t i = 0; i < NUM_SMALL_SIZECLASSES; i++)
{
// TODO could optimise this, to return the whole list in one append
// call.
@@ -108,7 +108,7 @@ namespace snmalloc
alloc(Domesticator domesticate, size_t size, Slowpath slowpath)
{
auto& key = entropy.get_free_list_key();
sizeclass_t sizeclass = size_to_sizeclass(size);
smallsizeclass_t sizeclass = size_to_sizeclass(size);
stats.alloc_request(size);
stats.sizeclass_alloc(sizeclass);
auto& fl = small_fast_free_lists[sizeclass];

View File

@@ -73,7 +73,7 @@ namespace snmalloc
/**
* Initialise Metaslab for a slab.
*/
void initialise(sizeclass_t sizeclass)
void initialise(smallsizeclass_t sizeclass)
{
free_queue.init();
// Set up meta data as if the entire slab has been turned into a free
@@ -112,7 +112,7 @@ namespace snmalloc
* and will return true, otherwise it will return false.
*/
SNMALLOC_FAST_PATH bool
set_sleeping(sizeclass_t sizeclass, uint16_t remaining)
set_sleeping(smallsizeclass_t sizeclass, uint16_t remaining)
{
auto threshold = threshold_for_waking_slab(sizeclass);
if (remaining >= threshold)
@@ -130,7 +130,7 @@ namespace snmalloc
return true;
}
SNMALLOC_FAST_PATH void set_not_sleeping(sizeclass_t sizeclass)
SNMALLOC_FAST_PATH void set_not_sleeping(smallsizeclass_t sizeclass)
{
auto allocated = sizeclass_to_slab_object_count(sizeclass);
needed() = allocated - threshold_for_waking_slab(sizeclass);
@@ -145,9 +145,9 @@ namespace snmalloc
}
static SNMALLOC_FAST_PATH bool
is_start_of_object(sizeclass_t sizeclass, address_t p)
is_start_of_object(smallsizeclass_t sizeclass, address_t p)
{
return is_multiple_of_sizeclass(
return divisible_by_sizeclass(
sizeclass,
p - (bits::align_down(p, sizeclass_to_slab_size(sizeclass))));
}
@@ -170,7 +170,7 @@ namespace snmalloc
Metaslab* meta,
freelist::Iter<>& fast_free_list,
LocalEntropy& entropy,
sizeclass_t sizeclass)
smallsizeclass_t sizeclass)
{
auto& key = entropy.get_free_list_key();
@@ -213,8 +213,8 @@ namespace snmalloc
*
* * log_2(size), at least MIN_CHUNK_BITS, for large allocations.
*
* * a value in [0, NUM_SIZECLASSES] for small allocations. These may be
* directly passed to the sizeclass (not slab_sizeclass) functions of
* * a value in [0, NUM_SMALL_SIZECLASSES] for small allocations. These
* may be directly passed to the sizeclass (not slab_sizeclass) functions of
* sizeclasstable.h
*
*/
@@ -235,12 +235,15 @@ namespace snmalloc
{}
SNMALLOC_FAST_PATH
MetaEntry(Metaslab* meta, RemoteAllocator* remote, sizeclass_t sizeclass)
MetaEntry(
Metaslab* meta,
RemoteAllocator* remote,
sizeclass_t sizeclass = sizeclass_t())
: meta(meta)
{
/* remote might be nullptr; cast to uintptr_t before offsetting */
remote_and_sizeclass =
pointer_offset(reinterpret_cast<uintptr_t>(remote), sizeclass);
pointer_offset(reinterpret_cast<uintptr_t>(remote), sizeclass.raw());
}
/**
@@ -285,8 +288,9 @@ namespace snmalloc
{
// TODO: perhaps remove static_cast with resolution of
// https://github.com/CTSRD-CHERI/llvm-project/issues/588
return static_cast<sizeclass_t>(remote_and_sizeclass) &
(alignof(RemoteAllocator) - 1);
return sizeclass_t::from_raw(
static_cast<size_t>(remote_and_sizeclass) &
(alignof(RemoteAllocator) - 1));
}
};

View File

@@ -12,17 +12,8 @@ namespace snmalloc
{
// Remotes need to be aligned enough that the bottom bits have enough room for
// all the size classes, both large and small.
//
// Including large classes in this calculation might seem remarkably strange,
// since large allocations don't have associated Remotes, that is, their
// remote is taken to be 0. However, if there are very few small size
// classes and many large classes, the attempt to align that 0 down by the
// alignment of a Remote might result in a nonzero value.
static constexpr size_t REMOTE_MIN_ALIGN = bits::max<size_t>(
CACHELINE_SIZE,
bits::max<size_t>(
bits::next_pow2_const(NUM_SIZECLASSES + 1),
bits::next_pow2_const(NUM_LARGE_CLASSES + 1)));
static constexpr size_t REMOTE_MIN_ALIGN =
bits::max<size_t>(CACHELINE_SIZE, SIZECLASS_REP_SIZE);
/**
* Global key for all remote lists.

View File

@@ -55,7 +55,7 @@ namespace snmalloc
SNMALLOC_FAST_PATH bool reserve_space(const MetaEntry& entry)
{
auto size =
static_cast<int64_t>(sizeclass_to_size(entry.get_sizeclass()));
static_cast<int64_t>(sizeclass_full_to_size(entry.get_sizeclass()));
bool result = capacity > size;
if (result)

View File

@@ -5,22 +5,28 @@
#include "../ds/helpers.h"
#include "allocconfig.h"
/**
* This file contains all the code for transforming transforming sizes to
* sizeclasses and back. It also contains various sizeclass pre-calculated
* tables for operations based on size class such as `modulus` and `divisible
* by`, and constants for the slab based allocator.
*
* TODO: Due to the current structure for constexpr evaluation this file does
* not well delimit internal versus external APIs. Some refactoring should be
* done.
*/
namespace snmalloc
{
// Both usings should compile
// We use size_t as it generates better code.
using sizeclass_t = size_t;
// using sizeclass_t = uint8_t;
using sizeclass_compress_t = uint8_t;
using smallsizeclass_t = size_t;
using chunksizeclass_t = size_t;
constexpr static uintptr_t SIZECLASS_MASK = 0xFF;
constexpr static inline sizeclass_t size_to_sizeclass_const(size_t size)
constexpr static inline smallsizeclass_t size_to_sizeclass_const(size_t size)
{
// Don't use sizeclasses that are not a multiple of the alignment.
// For example, 24 byte allocations can be
// problematic for some data due to alignment issues.
auto sc = static_cast<sizeclass_t>(
auto sc = static_cast<smallsizeclass_t>(
bits::to_exp_mant_const<INTERMEDIATE_BITS, MIN_ALLOC_BITS>(size));
SNMALLOC_ASSERT(sc == static_cast<uint8_t>(sc));
@@ -28,20 +34,98 @@ namespace snmalloc
return sc;
}
static inline size_t large_sizeclass_to_size(uint8_t large_class)
{
// TODO. Remove
UNUSED(large_class);
abort();
// return bits::one_at_bit(large_class + SUPERSLAB_BITS);
}
static constexpr size_t NUM_SMALL_SIZECLASSES =
size_to_sizeclass_const(MAX_SMALL_SIZECLASS_SIZE);
static constexpr size_t NUM_SIZECLASSES =
size_to_sizeclass_const(MAX_SIZECLASS_SIZE);
// Large classes range from [SUPERSLAB, ADDRESS_SPACE).// TODO
// Large classes range from [MAX_SMALL_SIZECLASS_SIZE, ADDRESS_SPACE).
static constexpr size_t NUM_LARGE_CLASSES =
Pal::address_bits - MAX_SIZECLASS_BITS;
Pal::address_bits - MAX_SMALL_SIZECLASS_BITS;
// How many bits are required to represent either a large or a small
// sizeclass.
static constexpr size_t TAG_SIZECLASS_BITS = bits::max<size_t>(
bits::next_pow2_bits_const(NUM_SMALL_SIZECLASSES + 1),
bits::next_pow2_bits_const(NUM_LARGE_CLASSES + 1));
// Number of bits required to represent a tagged sizeclass that can be
// either small or large.
static constexpr size_t SIZECLASS_REP_SIZE =
bits::one_at_bit(TAG_SIZECLASS_BITS + 1);
/**
* Encapsulates a tagged union of large and small sizeclasses.
*
* Used in various lookup tables to make efficient code that handles
* all objects allocated by snmalloc.
*/
class sizeclass_t
{
static constexpr size_t TAG = bits::one_at_bit(TAG_SIZECLASS_BITS);
size_t value{0};
constexpr sizeclass_t(size_t value) : value(value) {}
public:
constexpr sizeclass_t() = default;
constexpr static sizeclass_t from_small_class(smallsizeclass_t sc)
{
SNMALLOC_ASSERT(sc < TAG);
// Note could use `+` or `|`. Using `+` as will combine nicely with array
// offset.
return {TAG + sc};
}
/**
* Takes the number of leading zero bits from the actual large size-1.
* See size_to_sizeclass_full
*/
constexpr static sizeclass_t from_large_class(size_t large_class)
{
SNMALLOC_ASSERT(large_class < TAG);
return {large_class};
}
constexpr static sizeclass_t from_raw(size_t raw)
{
return {raw};
}
constexpr size_t index()
{
return value & (TAG - 1);
}
constexpr smallsizeclass_t as_small()
{
SNMALLOC_ASSERT(is_small());
return value & (TAG - 1);
}
constexpr chunksizeclass_t as_large()
{
SNMALLOC_ASSERT(!is_small());
return bits::BITS - (value & (TAG - 1));
}
constexpr size_t raw()
{
return value;
}
constexpr bool is_small()
{
return (value & TAG) != 0;
}
constexpr bool is_default()
{
return value == 0;
}
};
using sizeclass_compress_t = uint8_t;
inline SNMALLOC_FAST_PATH static size_t
aligned_size(size_t alignment, size_t size)
@@ -64,9 +148,9 @@ namespace snmalloc
// the slab.
size_t slab_mask;
// Table of constants for reciprocal division for each sizeclass.
size_t div_mult;
// Table of constants for reciprocal modulus for each sizeclass.
size_t mod_mult;
// Table of constants for reciprocal modulus for each sizeclass.
size_t mod_zero_mult;
};
/**
@@ -81,64 +165,118 @@ namespace snmalloc
struct SizeClassTable
{
ModArray<NUM_SIZECLASSES, sizeclass_data_fast> fast;
ModArray<NUM_SIZECLASSES, sizeclass_data_slow> slow;
ModArray<SIZECLASS_REP_SIZE, sizeclass_data_fast> fast_;
ModArray<SIZECLASS_REP_SIZE, sizeclass_data_slow> slow_;
constexpr SizeClassTable() : fast(), slow()
[[nodiscard]] constexpr sizeclass_data_fast& fast(sizeclass_t index)
{
for (sizeclass_compress_t sizeclass = 0; sizeclass < NUM_SIZECLASSES;
return fast_[index.raw()];
}
[[nodiscard]] constexpr sizeclass_data_fast fast(sizeclass_t index) const
{
return fast_[index.raw()];
}
[[nodiscard]] constexpr sizeclass_data_fast& fast_small(smallsizeclass_t sc)
{
return fast_[sizeclass_t::from_small_class(sc).raw()];
}
[[nodiscard]] constexpr sizeclass_data_fast
fast_small(smallsizeclass_t sc) const
{
return fast_[sizeclass_t::from_small_class(sc).raw()];
}
[[nodiscard]] constexpr sizeclass_data_slow& slow(sizeclass_t index)
{
return slow_[index.raw()];
}
[[nodiscard]] constexpr sizeclass_data_slow slow(sizeclass_t index) const
{
return slow_[index.raw()];
}
constexpr SizeClassTable() : fast_(), slow_()
{
for (sizeclass_compress_t sizeclass = 0;
sizeclass < NUM_SMALL_SIZECLASSES;
sizeclass++)
{
auto& meta = fast_small(sizeclass);
size_t rsize =
bits::from_exp_mant<INTERMEDIATE_BITS, MIN_ALLOC_BITS>(sizeclass);
fast[sizeclass].size = rsize;
meta.size = rsize;
size_t slab_bits = bits::max(
bits::next_pow2_bits_const(MIN_OBJECT_COUNT * rsize), MIN_CHUNK_BITS);
fast[sizeclass].slab_mask = bits::one_at_bit(slab_bits) - 1;
meta.slab_mask = bits::one_at_bit(slab_bits) - 1;
slow[sizeclass].capacity =
static_cast<uint16_t>((fast[sizeclass].slab_mask + 1) / rsize);
auto& meta_slow = slow(sizeclass_t::from_small_class(sizeclass));
meta_slow.capacity =
static_cast<uint16_t>((meta.slab_mask + 1) / rsize);
slow[sizeclass].waking =
meta_slow.waking =
#ifdef SNMALLOC_CHECK_CLIENT
static_cast<uint16_t>(slow[sizeclass].capacity / 4);
static_cast<uint16_t>(meta_slow.capacity / 4);
#else
static_cast<uint16_t>(bits::min((slow[sizeclass].capacity / 4), 32));
static_cast<uint16_t>(bits::min((meta_slow.capacity / 4), 32));
#endif
}
for (sizeclass_compress_t sizeclass = 0; sizeclass < NUM_SIZECLASSES;
for (sizeclass_compress_t sizeclass = 0;
sizeclass < NUM_SMALL_SIZECLASSES;
sizeclass++)
{
fast[sizeclass].div_mult = // TODO is MAX_SIZECLASS_BITS right?
(bits::one_at_bit(bits::BITS - 24) /
(fast[sizeclass].size / MIN_ALLOC_SIZE));
if (!bits::is_pow2(fast[sizeclass].size))
fast[sizeclass].div_mult++;
// Calculate reciprocal modulus constant like reciprocal division, but
// constant is choosen to overflow and only leave the modulus as the
// result.
auto& meta = fast_small(sizeclass);
meta.mod_mult = bits::one_at_bit(bits::BITS - 1) / meta.size;
meta.mod_mult *= 2;
fast[sizeclass].mod_mult =
(bits::one_at_bit(bits::BITS - 1) / fast[sizeclass].size);
if (!bits::is_pow2(fast[sizeclass].size))
fast[sizeclass].mod_mult++;
// Shift multiplier, so that the result of division completely
// overflows, and thus the top SUPERSLAB_BITS will be zero if the mod is
// zero.
fast[sizeclass].mod_mult *= 2;
if (bits::is_pow2(meta.size))
{
// Set to zero, so masking path is taken if power of 2.
meta.mod_mult = 0;
}
size_t zero = 0;
meta.mod_zero_mult = (~zero / meta.size) + 1;
}
// Set up table for large classes.
// Note skipping sizeclass == 0 as this is size == 0, so the tables can be
// all zero.
for (size_t sizeclass = 1; sizeclass < bits::BITS; sizeclass++)
{
auto lsc = sizeclass_t::from_large_class(sizeclass);
fast(lsc).size = bits::one_at_bit(lsc.as_large());
// Use slab mask as 0 for power of two sizes.
fast(lsc).slab_mask = 0;
}
}
};
static inline constexpr SizeClassTable sizeclass_metadata = SizeClassTable();
constexpr static inline size_t sizeclass_to_size(sizeclass_t sizeclass)
constexpr static inline size_t sizeclass_to_size(smallsizeclass_t sizeclass)
{
return sizeclass_metadata.fast[sizeclass].size;
return sizeclass_metadata.fast_small(sizeclass).size;
}
inline static size_t sizeclass_to_slab_size(sizeclass_t sizeclass)
static inline size_t sizeclass_full_to_size(sizeclass_t sizeclass)
{
return sizeclass_metadata.fast[sizeclass].slab_mask + 1;
return sizeclass_metadata.fast(sizeclass).size;
}
inline static size_t sizeclass_to_slab_size(smallsizeclass_t sizeclass)
{
return sizeclass_metadata.fast_small(sizeclass).slab_mask + 1;
}
/**
@@ -148,19 +286,20 @@ namespace snmalloc
*
* It also increases entropy, when we have randomisation.
*/
inline uint16_t threshold_for_waking_slab(sizeclass_t sizeclass)
inline uint16_t threshold_for_waking_slab(smallsizeclass_t sizeclass)
{
return sizeclass_metadata.slow[sizeclass].waking;
return sizeclass_metadata.slow(sizeclass_t::from_small_class(sizeclass))
.waking;
}
inline static size_t sizeclass_to_slab_sizeclass(sizeclass_t sizeclass)
inline static size_t sizeclass_to_slab_sizeclass(smallsizeclass_t sizeclass)
{
size_t ssize = sizeclass_to_slab_size(sizeclass);
return bits::next_pow2_bits(ssize) - MIN_CHUNK_BITS;
}
inline static size_t slab_sizeclass_to_size(sizeclass_t sizeclass)
inline static size_t slab_sizeclass_to_size(chunksizeclass_t sizeclass)
{
return bits::one_at_bit(MIN_CHUNK_BITS + sizeclass);
}
@@ -170,74 +309,71 @@ namespace snmalloc
* which must be shifted into the index space of slab_sizeclass-es.
*/
inline static size_t
metaentry_chunk_sizeclass_to_slab_sizeclass(sizeclass_t sizeclass)
metaentry_chunk_sizeclass_to_slab_sizeclass(chunksizeclass_t sizeclass)
{
return sizeclass - MIN_CHUNK_BITS;
}
inline constexpr static uint16_t
sizeclass_to_slab_object_count(sizeclass_t sizeclass)
sizeclass_to_slab_object_count(smallsizeclass_t sizeclass)
{
return sizeclass_metadata.slow[sizeclass].capacity;
return sizeclass_metadata.slow(sizeclass_t::from_small_class(sizeclass))
.capacity;
}
inline static size_t round_by_sizeclass(sizeclass_t sc, size_t offset)
inline static size_t mod_by_sizeclass(smallsizeclass_t sc, size_t offset)
{
// Only works up to certain offsets, exhaustively tested upto
// SUPERSLAB_SIZE.
// SNMALLOC_ASSERT(offset <= SUPERSLAB_SIZE);
// Only works up to certain offsets, exhaustively tested by rounding.cc
auto meta = sizeclass_metadata.fast_small(sc);
auto rsize = sizeclass_to_size(sc);
// Powers of two should use straigt mask.
SNMALLOC_ASSERT(meta.mod_mult != 0);
if constexpr (sizeof(offset) >= 8)
{
// Only works for 64 bit multiplication, as the following will overflow in
// 32bit.
// The code is using reciprocal division. If SUPERSLABS
// get larger then we should review this code. For 24 bits, there are in
// sufficient bits to do this completely efficiently as 24 * 3 is larger
// than 64 bits. But we can pre-round by MIN_ALLOC_SIZE which gets us an
// extra 4 * 3 bits, and thus achievable in 64bit multiplication.
// static_assert(
// SUPERSLAB_BITS <= 24, "The following code assumes max of 24 bits");
// TODO 24 hack
static_assert(bits::BITS >= 24, "About to attempt a negative shift");
static_assert(
(8 * sizeof(offset)) >= (bits::BITS - 24),
"About to shift further than the type");
return (((offset >> MIN_ALLOC_BITS) *
sizeclass_metadata.fast[sc].div_mult) >>
(bits::BITS - 24)) *
rsize;
// Could be made nicer with 128bit multiply (umulh):
// https://lemire.me/blog/2019/02/20/more-fun-with-fast-remainders-when-the-divisor-is-a-constant/
auto bits_l = bits::BITS / 2;
auto bits_h = bits::BITS - bits_l;
return (
((((offset + 1) * meta.mod_mult) >> (bits_l)) * meta.size) >> bits_h);
}
else
// Use 32-bit division as considerably faster than 64-bit, and
// everything fits into 32bits here.
return static_cast<uint32_t>(offset / rsize) * rsize;
return static_cast<uint32_t>(offset % meta.size);
}
inline static bool is_multiple_of_sizeclass(sizeclass_t sc, size_t offset)
inline static size_t index_in_object(sizeclass_t sc, address_t addr)
{
// Only works up to certain offsets, exhaustively tested upto
// SUPERSLAB_SIZE.
// SNMALLOC_ASSERT(offset <= SUPERSLAB_SIZE);
if (sizeclass_metadata.fast(sc).mod_mult == 0)
{
return addr & (sizeclass_metadata.fast(sc).size - 1);
}
address_t offset = addr & (sizeclass_to_slab_size(sc.as_small()) - 1);
return mod_by_sizeclass(sc.as_small(), offset);
}
inline static size_t remaining_bytes(sizeclass_t sc, address_t addr)
{
return sizeclass_metadata.fast(sc).size - index_in_object(sc, addr);
}
inline static bool divisible_by_sizeclass(smallsizeclass_t sc, size_t offset)
{
// Only works up to certain offsets, exhaustively tested by rounding.cc
if constexpr (sizeof(offset) >= 8)
{
// Only works for 64 bit multiplication, as the following will overflow in
// 32bit.
// The code is using reciprocal division. If SUPERSLABS
// get larger then we should review this code. The modulus code
// has fewer restrictions than division, as it only requires the
// square of the offset to be representable.
// TODO 24 hack. Redo the maths given the multiple
// slab sizes
static_assert(bits::BITS >= 25);
static constexpr size_t MASK =
~(bits::one_at_bit(bits::BITS - 1 - 24) - 1);
return ((offset * sizeclass_metadata.fast[sc].mod_mult) & MASK) == 0;
// This is based on:
// https://lemire.me/blog/2019/02/20/more-fun-with-fast-remainders-when-the-divisor-is-a-constant/
auto mod_zero_mult = sizeclass_metadata.fast_small(sc).mod_zero_mult;
return (offset * mod_zero_mult) < mod_zero_mult;
}
else
// Use 32-bit division as considerably faster than 64-bit, and
@@ -262,10 +398,10 @@ namespace snmalloc
return (s - 1) >> MIN_ALLOC_BITS;
}
static inline sizeclass_t size_to_sizeclass(size_t size)
static inline smallsizeclass_t size_to_sizeclass(size_t size)
{
constexpr static size_t sizeclass_lookup_size =
sizeclass_lookup_index(MAX_SIZECLASS_SIZE);
sizeclass_lookup_index(MAX_SMALL_SIZECLASS_SIZE);
/**
* This struct is used to statically initialise a table for looking up
@@ -278,10 +414,11 @@ namespace snmalloc
constexpr SizeClassLookup()
{
size_t curr = 1;
for (sizeclass_compress_t sizeclass = 0; sizeclass < NUM_SIZECLASSES;
for (sizeclass_compress_t sizeclass = 0;
sizeclass < NUM_SMALL_SIZECLASSES;
sizeclass++)
{
for (; curr <= sizeclass_metadata.fast[sizeclass].size;
for (; curr <= sizeclass_metadata.fast_small(sizeclass).size;
curr += 1 << MIN_ALLOC_BITS)
{
auto i = sizeclass_lookup_index(curr);
@@ -307,9 +444,29 @@ namespace snmalloc
return 0;
}
/**
* A compressed size representation,
* either a small size class with the 7th bit set
* or a large class with the 7th bit not set.
* Large classes are stored as a mask shift.
* size = (~0 >> lc) + 1;
* Thus large size class 0, has size 0.
* And large size class 33, has size 2^31
*/
static inline sizeclass_t size_to_sizeclass_full(size_t size)
{
if ((size - 1) < sizeclass_to_size(NUM_SMALL_SIZECLASSES - 1))
{
return sizeclass_t::from_small_class(size_to_sizeclass(size));
}
// bits::clz is undefined on 0, but we have size == 1 has already been
// handled here. We conflate 0 and sizes larger than we can allocate.
return sizeclass_t::from_large_class(bits::clz(size - 1));
}
inline SNMALLOC_FAST_PATH static size_t round_size(size_t size)
{
if (size > sizeclass_to_size(NUM_SIZECLASSES - 1))
if (size > sizeclass_to_size(NUM_SMALL_SIZECLASSES - 1))
{
return bits::next_pow2(size);
}

View File

@@ -136,8 +136,7 @@ namespace
auto& alloc = ThreadAlloc::get();
void* p = const_cast<void*>(ptr);
if (unlikely(
pointer_diff(ptr, alloc.external_pointer<OnePastEnd>(p)) < len))
if (unlikely(alloc.remaining_bytes(ptr) < len))
{
if constexpr (FailFast)
{

View File

@@ -32,7 +32,8 @@ rust_realloc(void* ptr, size_t alignment, size_t old_size, size_t new_size)
size_t aligned_old_size = aligned_size(alignment, old_size),
aligned_new_size = aligned_size(alignment, new_size);
if (
size_to_sizeclass(aligned_old_size) == size_to_sizeclass(aligned_new_size))
size_to_sizeclass_full(aligned_old_size).raw() ==
size_to_sizeclass_full(aligned_new_size).raw())
return ptr;
void* p = ThreadAlloc::get().alloc(aligned_new_size);
if (p)

View File

@@ -166,7 +166,7 @@ int main(int, char**)
f(5);
f(7);
printf("\n");
for (size_t exp = 1; exp < snmalloc::MAX_SIZECLASS_BITS; exp++)
for (size_t exp = 1; exp < snmalloc::MAX_SMALL_SIZECLASS_BITS; exp++)
{
auto shifted = [exp](size_t v) { return v << exp; };

View File

@@ -147,7 +147,7 @@ int main(int argc, char** argv)
our_free(nullptr);
for (sizeclass_t sc = 0; sc < (MAX_SIZECLASS_BITS + 4); sc++)
for (smallsizeclass_t sc = 0; sc < (MAX_SMALL_SIZECLASS_BITS + 4); sc++)
{
const size_t size = bits::one_at_bit(sc);
printf("malloc: %zu\n", size);
@@ -161,12 +161,13 @@ int main(int argc, char** argv)
our_free(nullptr);
for (sizeclass_t sc = 0; sc < NUM_SIZECLASSES; sc++)
for (smallsizeclass_t sc = 0; sc < NUM_SMALL_SIZECLASSES; sc++)
{
const size_t size = sizeclass_to_size(sc);
bool overflow = false;
for (size_t n = 1; bits::umul(size, n, overflow) <= MAX_SIZECLASS_SIZE;
for (size_t n = 1;
bits::umul(size, n, overflow) <= MAX_SMALL_SIZECLASS_SIZE;
n *= 5)
{
if (overflow)
@@ -178,13 +179,13 @@ int main(int argc, char** argv)
test_calloc(0, size, SUCCESS, false);
}
for (sizeclass_t sc = 0; sc < NUM_SIZECLASSES; sc++)
for (smallsizeclass_t sc = 0; sc < NUM_SMALL_SIZECLASSES; sc++)
{
const size_t size = sizeclass_to_size(sc);
test_realloc(our_malloc(size), size, SUCCESS, false);
test_realloc(nullptr, size, SUCCESS, false);
test_realloc(our_malloc(size), ((size_t)-1) / 2, ENOMEM, true);
for (sizeclass_t sc2 = 0; sc2 < NUM_SIZECLASSES; sc2++)
for (smallsizeclass_t sc2 = 0; sc2 < NUM_SMALL_SIZECLASSES; sc2++)
{
const size_t size2 = sizeclass_to_size(sc2);
test_realloc(our_malloc(size), size2, SUCCESS, false);
@@ -192,13 +193,13 @@ int main(int argc, char** argv)
}
}
for (sizeclass_t sc = 0; sc < (MAX_SIZECLASS_BITS + 4); sc++)
for (smallsizeclass_t sc = 0; sc < (MAX_SMALL_SIZECLASS_BITS + 4); sc++)
{
const size_t size = bits::one_at_bit(sc);
test_realloc(our_malloc(size), size, SUCCESS, false);
test_realloc(nullptr, size, SUCCESS, false);
test_realloc(our_malloc(size), ((size_t)-1) / 2, ENOMEM, true);
for (sizeclass_t sc2 = 0; sc2 < (MAX_SIZECLASS_BITS + 4); sc2++)
for (smallsizeclass_t sc2 = 0; sc2 < (MAX_SMALL_SIZECLASS_BITS + 4); sc2++)
{
const size_t size2 = bits::one_at_bit(sc2);
printf("size1: %zu, size2:%zu\n", size, size2);
@@ -213,10 +214,10 @@ int main(int argc, char** argv)
test_posix_memalign(((size_t)-1) / 2, 0, EINVAL, true);
test_posix_memalign(OS_PAGE_SIZE, sizeof(uintptr_t) / 2, EINVAL, true);
for (size_t align = sizeof(uintptr_t); align < MAX_SIZECLASS_SIZE * 8;
for (size_t align = sizeof(uintptr_t); align < MAX_SMALL_SIZECLASS_SIZE * 8;
align <<= 1)
{
for (sizeclass_t sc = 0; sc < NUM_SIZECLASSES - 6; sc++)
for (smallsizeclass_t sc = 0; sc < NUM_SMALL_SIZECLASSES - 6; sc++)
{
const size_t size = sizeclass_to_size(sc);
test_posix_memalign(size, align, SUCCESS, false);

View File

@@ -236,11 +236,18 @@ void test_external_pointer()
// Malloc does not have an external pointer querying mechanism.
auto& alloc = ThreadAlloc::get();
for (uint8_t sc = 0; sc < NUM_SIZECLASSES; sc++)
for (uint8_t sc = 0; sc < NUM_SMALL_SIZECLASSES; sc++)
{
size_t size = sizeclass_to_size(sc);
void* p1 = alloc.alloc(size);
if (size != alloc.alloc_size(p1))
{
std::cout << "Requested size: " << size
<< " alloc_size: " << alloc.alloc_size(p1) << std::endl;
abort();
}
for (size_t offset = 0; offset < size; offset += 17)
{
void* p2 = pointer_offset(p1, offset);
@@ -248,8 +255,9 @@ void test_external_pointer()
void* p4 = alloc.external_pointer<End>(p2);
if (p1 != p3)
{
std::cout << "size: " << size << " offset: " << offset << " p1: " << p1
<< " p3: " << p3 << std::endl;
std::cout << "size: " << size << " alloc_size: " << alloc.alloc_size(p1)
<< " offset: " << offset << " p1: " << p1 << " p3: " << p3
<< std::endl;
}
SNMALLOC_CHECK(p1 == p3);
if ((size_t)p4 != (size_t)p1 + size - 1)
@@ -272,7 +280,11 @@ void check_offset(void* base, void* interior)
auto& alloc = ThreadAlloc::get();
void* calced_base = alloc.external_pointer((void*)interior);
if (calced_base != (void*)base)
{
std::cout << "Calced base: " << calced_base << " actual base: " << base
<< " for interior: " << interior << std::endl;
abort();
}
}
void check_external_pointer_large(size_t* base)
@@ -301,7 +313,7 @@ void test_external_pointer_large()
for (size_t i = 0; i < count; i++)
{
size_t b = MAX_SIZECLASS_BITS + 3;
size_t b = MAX_SMALL_SIZECLASS_BITS + 3;
size_t rand = r.next() & ((1 << b) - 1);
size_t size = (1 << 24) + rand;
total_size += size;
@@ -383,7 +395,7 @@ void test_calloc_large_bug()
// Some PALS have special paths for PAGE aligned zeroing of large
// allocations. This is a large allocation that is intentionally
// not a multiple of page size.
const size_t size = (MAX_SIZECLASS_SIZE << 3) - 7;
const size_t size = (MAX_SMALL_SIZECLASS_SIZE << 3) - 7;
void* p1 = alloc.alloc<YesZero>(size);
SNMALLOC_CHECK(alloc.alloc_size(alloc.external_pointer(p1)) >= size);
@@ -415,7 +427,7 @@ void test_static_sized_allocs()
{
// For each small, medium, and large class, do each kind dealloc. This is
// mostly to ensure that all of these forms compile.
for (size_t sc = 0; sc < NUM_SIZECLASSES; sc++)
for (size_t sc = 0; sc < NUM_SMALL_SIZECLASSES; sc++)
{
// test_static_sized_alloc<sc, 0>();
// test_static_sized_alloc<sc, 1>();
@@ -430,6 +442,32 @@ void test_static_sized_allocs()
// test_static_sized_alloc<large_sizeclass_to_size(0), 2>();
}
void test_remaining_bytes()
{
auto& alloc = ThreadAlloc::get();
for (size_t sc = 0; sc < NUM_SMALL_SIZECLASSES; sc++)
{
auto size = sizeclass_to_size(sc);
char* p = (char*)alloc.alloc(size);
for (size_t offset = 0; offset < size; offset++)
{
auto rem = alloc.remaining_bytes(p + offset);
if (rem != (size - offset))
{
printf(
"Allocation size: %zu, Offset: %zu, Remaining bytes: %zu, "
"Expected: %zu\n",
size,
offset,
rem,
size - offset);
abort();
}
}
alloc.dealloc(p);
}
}
int main(int argc, char** argv)
{
setup();
@@ -455,12 +493,12 @@ int main(int argc, char** argv)
UNUSED(argc);
UNUSED(argv);
#endif
test_alloc_dealloc_64k();
test_random_allocation();
test_calloc();
test_double_alloc();
#ifndef SNMALLOC_PASS_THROUGH // Depends on snmalloc specific features
test_remaining_bytes();
test_static_sized_allocs();
test_calloc_large_bug();
test_external_pointer_dealloc_bug();

View File

@@ -17,35 +17,37 @@ int main(int argc, char** argv)
bool failed = false;
for (size_t size_class = 0; size_class < NUM_SIZECLASSES; size_class++)
for (size_t size_class = 0; size_class < NUM_SMALL_SIZECLASSES; size_class++)
{
size_t rsize = sizeclass_to_size((uint8_t)size_class);
size_t max_offset = sizeclass_to_slab_size(size_class);
sizeclass_t sc = sizeclass_t::from_small_class(size_class);
for (size_t offset = 0; offset < max_offset; offset++)
{
size_t rounded = (offset / rsize) * rsize;
size_t mod = offset % rsize;
bool mod_0 = (offset % rsize) == 0;
size_t opt_rounded = round_by_sizeclass(size_class, offset);
if (rounded != opt_rounded)
size_t opt_mod = index_in_object(sc, offset);
if (mod != opt_mod)
{
std::cout << "rsize " << rsize << " offset " << offset << " opt "
<< opt_rounded << " correct " << rounded << std::endl
<< opt_mod << " correct " << mod << std::endl
<< std::flush;
failed = true;
}
bool opt_mod_0 = is_multiple_of_sizeclass(size_class, offset);
bool opt_mod_0 = divisible_by_sizeclass(size_class, offset);
if (opt_mod_0 != mod_0)
{
std::cout << "rsize " << rsize << " offset " << offset << " opt_mod "
<< opt_mod_0 << " correct " << mod_0 << std::endl
std::cout << "rsize " << rsize << " offset " << offset
<< " opt_mod0 " << opt_mod_0 << " correct " << mod_0
<< std::endl
<< std::flush;
failed = true;
}
}
if (failed)
abort();
}
if (failed)
abort();
return 0;
}

View File

@@ -3,7 +3,7 @@
#include <test/setup.h>
NOINLINE
snmalloc::sizeclass_t size_to_sizeclass(size_t size)
snmalloc::smallsizeclass_t size_to_sizeclass(size_t size)
{
return snmalloc::size_to_sizeclass(size);
}
@@ -15,7 +15,7 @@ void test_align_size()
SNMALLOC_CHECK(snmalloc::aligned_size(128, 160) == 256);
for (size_t size = 1;
size < snmalloc::sizeclass_to_size(snmalloc::NUM_SIZECLASSES - 1);
size < snmalloc::sizeclass_to_size(snmalloc::NUM_SMALL_SIZECLASSES - 1);
size++)
{
size_t rsize = snmalloc::round_size(size);
@@ -39,7 +39,7 @@ void test_align_size()
}
for (size_t alignment_bits = 0;
alignment_bits < snmalloc::MAX_SIZECLASS_BITS;
alignment_bits < snmalloc::MAX_SMALL_SIZECLASS_BITS;
alignment_bits++)
{
auto alignment = (size_t)1 << alignment_bits;
@@ -78,10 +78,11 @@ int main(int, char**)
std::cout << "sizeclass |-> [size_low, size_high] " << std::endl;
size_t slab_size = 0;
for (snmalloc::sizeclass_t sz = 0; sz < snmalloc::NUM_SIZECLASSES; sz++)
for (snmalloc::smallsizeclass_t sz = 0; sz < snmalloc::NUM_SMALL_SIZECLASSES;
sz++)
{
if (
sz < snmalloc::NUM_SIZECLASSES &&
sz < snmalloc::NUM_SMALL_SIZECLASSES &&
slab_size != snmalloc::sizeclass_to_slab_size(sz))
{
slab_size = snmalloc::sizeclass_to_slab_size(sz);

View File

@@ -188,7 +188,7 @@ int main(int, char**)
f(5);
f(7);
printf("\n");
for (size_t exp = 1; exp < snmalloc::MAX_SIZECLASS_BITS; exp++)
for (size_t exp = 1; exp < snmalloc::MAX_SMALL_SIZECLASS_BITS; exp++)
{
auto shifted = [exp](size_t v) { return v << exp; };