Merge pull request #124 from microsoft/alignment

Make Large allocations naturally aligned
This commit is contained in:
Matthew Parkinson
2020-02-05 14:40:13 +00:00
committed by GitHub
14 changed files with 133 additions and 117 deletions

View File

@@ -9,6 +9,12 @@ option(EXPOSE_EXTERNAL_RESERVE "Expose an interface to reserve memory using the
option(SNMALLOC_RUST_SUPPORT "Build static library for rust" OFF)
set(CACHE_FRIENDLY_OFFSET OFF CACHE STRING "Base offset to place linked-list nodes.")
if ((CMAKE_BUILD_TYPE STREQUAL "Release") AND (NOT SNMALLOC_CI_BUILD))
option(USE_POSIX_COMMIT_CHECKS "Instrument Posix PAL to check for access to unused blocks of memory." Off)
else ()
option(USE_POSIX_COMMIT_CHECKS "Instrument Posix PAL to check for access to unused blocks of memory." On)
endif ()
# Provide as macro so other projects can reuse
macro(warnings_high)
if(MSVC)
@@ -111,6 +117,11 @@ if(CACHE_FRIENDLY_OFFSET)
target_compile_definitions(snmalloc_lib INTERFACE -DCACHE_FRIENDLY_OFFSET=${CACHE_FRIENDLY_OFFSET})
endif()
if(USE_POSIX_COMMIT_CHECKS)
target_compile_definitions(snmalloc_lib INTERFACE -DUSE_POSIX_COMMIT_CHECKS)
endif()
# To build with just the header library target define SNMALLOC_ONLY_HEADER_LIBRARY
# in containing Cmake file.
if(NOT DEFINED SNMALLOC_ONLY_HEADER_LIBRARY)

View File

@@ -163,12 +163,12 @@ pages, rather than zeroing them synchronously in this call
```c++
template<bool committed>
void* reserve(size_t* size, size_t align);
void* reserve(size_t size, size_t align);
template<bool committed>
void* reserve(const size_t* size) noexcept;
void* reserve(size_t size) noexcept;
```
Only one of these needs to be implemented, depending on whether the underlying
system can provide strongly (e.g. 16MiB) aligned memory regions.
system can provide strongly aligned memory regions.
If the system guarantees only page alignment, implement the second and snmalloc
will over-allocate and then trim the requested region.
If the system provides strong alignment, implement the first to return memory

View File

@@ -63,16 +63,12 @@ namespace snmalloc
void new_block()
{
size_t size = SUPERSLAB_SIZE;
void* r = reserve<false>(&size, SUPERSLAB_SIZE);
if (size < SUPERSLAB_SIZE)
error("out of memory");
// Reserve the smallest large_class which is SUPERSLAB_SIZE
void* r = reserve<false>(0);
PAL::template notify_using<NoZero>(r, OS_PAGE_SIZE);
bump = r;
remaining = size;
remaining = SUPERSLAB_SIZE;
}
/**
@@ -126,6 +122,24 @@ namespace snmalloc
lazy_decommit_guard.clear();
}
void push_space(address_t start, size_t large_class)
{
void* p = pointer_cast<void>(start);
if (large_class > 0)
PAL::template notify_using<YesZero>(p, OS_PAGE_SIZE);
else
{
if (decommit_strategy == DecommitSuperLazy)
{
PAL::template notify_using<YesZero>(p, OS_PAGE_SIZE);
p = new (p) Decommittedslab();
}
else
PAL::template notify_using<YesZero>(p, SUPERSLAB_SIZE);
}
large_stack[large_class].push(reinterpret_cast<Largeslab*>(p));
}
public:
/**
* Stack of large allocations that have been returned for reuse.
@@ -202,35 +216,68 @@ namespace snmalloc
}
template<bool committed>
void* reserve(size_t* size, size_t align) noexcept
void* reserve(size_t large_class) noexcept
{
size_t size = bits::one_at_bit(SUPERSLAB_BITS) << large_class;
size_t align = size;
if constexpr (pal_supports<AlignedAllocation, PAL>)
{
return PAL::template reserve<committed>(size, align);
}
else
{
size_t request = *size;
// Add align, so we can guarantee to provide at least size.
request += align;
// Alignment must be a power of 2.
assert(align == bits::next_pow2(align));
// Reserve 4 times the amount, and put aligned leftovers into the
// large_stack
size_t request = bits::max(size * 4, SUPERSLAB_SIZE * 8);
void* p = PAL::template reserve<false>(request);
void* p = PAL::template reserve<committed>(&request);
address_t p0 = address_cast(p);
address_t start = bits::align_up(p0, align);
address_t p1 = p0 + request;
address_t end = start + size;
*size = request;
auto p0 = address_cast(p);
auto start = bits::align_up(p0, align);
if (start > p0)
for (; end < bits::align_down(p1, align); end += size)
{
uintptr_t end = bits::align_down(p0 + request, align);
*size = end - start;
PAL::notify_not_using(p, start - p0);
PAL::notify_not_using(pointer_cast<void>(end), (p0 + request) - end);
p = pointer_cast<void>(start);
push_space(end, large_class);
}
return p;
// Put offcuts before alignment into the large stack
address_t offcut_end = start;
address_t offcut_start;
for (size_t i = large_class; i > 0;)
{
i--;
size_t offcut_align = bits::one_at_bit(SUPERSLAB_BITS) << i;
offcut_start = bits::align_up(p0, offcut_align);
if (offcut_start != offcut_end)
{
push_space(offcut_start, i);
offcut_end = offcut_start;
}
}
// Put offcuts after returned block into the large stack
offcut_start = end;
for (size_t i = large_class; i > 0;)
{
i--;
auto offcut_align = bits::one_at_bit(SUPERSLAB_BITS) << i;
offcut_end = bits::align_down(p1, offcut_align);
if (offcut_start != offcut_end)
{
push_space(offcut_start, i);
offcut_start = offcut_end;
}
}
// printf("Alloc %zx (size = %zx)\n", start, size);
void* result = pointer_cast<void>(start);
if (committed)
PAL::template notify_using<NoZero>(result, size);
return result;
}
}
@@ -281,9 +328,6 @@ namespace snmalloc
template<class MemoryProvider>
class LargeAlloc
{
void* reserved_start = nullptr;
void* reserved_end = nullptr;
public:
// This will be a zero-size structure if stats are not enabled.
Stats stats;
@@ -292,38 +336,6 @@ namespace snmalloc
LargeAlloc(MemoryProvider& mp) : memory_provider(mp) {}
template<AllowReserve allow_reserve>
bool reserve_memory(size_t need, size_t add)
{
assert(reserved_start <= reserved_end);
/*
* Spell this comparison in terms of pointer subtraction like this,
* rather than "reserved_start + need < reserved_end" because the
* sum might not be representable on CHERI.
*/
if (pointer_diff(reserved_start, reserved_end) < need)
{
if constexpr (allow_reserve == YesReserve)
{
stats.segment_create();
reserved_start =
memory_provider.template reserve<false>(&add, SUPERSLAB_SIZE);
reserved_end = pointer_offset(reserved_start, add);
reserved_start = pointer_align_up<SUPERSLAB_SIZE>(reserved_start);
if (add < need)
return false;
}
else
{
return false;
}
}
return true;
}
template<ZeroMem zero_mem = NoZero, AllowReserve allow_reserve = YesReserve>
void* alloc(size_t large_class, size_t size)
{
@@ -337,23 +349,8 @@ namespace snmalloc
if (p == nullptr)
{
assert(reserved_start <= reserved_end);
size_t add;
if ((rsize + SUPERSLAB_SIZE) < RESERVE_SIZE)
add = RESERVE_SIZE;
else
add = rsize + SUPERSLAB_SIZE;
if (!reserve_memory<allow_reserve>(rsize, add))
return nullptr;
p = reserved_start;
reserved_start = pointer_offset(p, rsize);
stats.superslab_fresh();
// All memory is zeroed since it comes from reserved space.
memory_provider.template notify_using<NoZero>(p, size);
p = memory_provider.template reserve<false>(large_class);
memory_provider.template notify_using<zero_mem>(p, size);
}
else
{
@@ -389,6 +386,7 @@ namespace snmalloc
}
}
assert(p == pointer_align_up(p, rsize));
return p;
}

View File

@@ -179,8 +179,6 @@ namespace snmalloc
{
// Client responsible for checking alignment is not zero
assert(alignment != 0);
// Client responsible for checking alignment is not above SUPERSLAB_SIZE
assert(alignment <= SUPERSLAB_SIZE);
// Client responsible for checking alignment is a power of two
assert(bits::next_pow2(alignment) == alignment);

View File

@@ -107,9 +107,7 @@ extern "C"
SNMALLOC_EXPORT void*
SNMALLOC_NAME_MANGLE(memalign)(size_t alignment, size_t size)
{
if (
(alignment == 0) || (alignment == size_t(-1)) ||
(alignment > SUPERSLAB_SIZE))
if ((alignment == 0) || (alignment == size_t(-1)))
{
errno = EINVAL;
return nullptr;

View File

@@ -56,11 +56,11 @@ namespace snmalloc
* See comment below.
*/
template<bool committed>
void* reserve(const size_t* size)
void* reserve(size_t size)
{
void* p = mmap(
nullptr,
*size,
size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
pal_anon_id,

View File

@@ -27,7 +27,7 @@ namespace snmalloc
* Reserve memory at a specific alignment.
*/
template<bool committed>
void* reserve(const size_t* size, size_t align) noexcept
void* reserve(size_t size, size_t align) noexcept
{
// Alignment must be a power of 2.
assert(align == bits::next_pow2(align));
@@ -38,7 +38,7 @@ namespace snmalloc
void* p = mmap(
nullptr,
*size,
size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_ALIGNED(log2align),
-1,

View File

@@ -61,13 +61,12 @@ namespace snmalloc
}
template<bool committed>
void* reserve(size_t* size, size_t align)
void* reserve(size_t size, size_t align)
{
size_t request = *size;
vm_offset_t addr;
if (vmem_xalloc(
kernel_arena,
request,
size,
align,
0,
0,
@@ -81,10 +80,10 @@ namespace snmalloc
if (committed)
{
if (
kmem_back(kernel_object, addr, request, M_ZERO | M_WAITOK) !=
kmem_back(kernel_object, addr, size, M_ZERO | M_WAITOK) !=
KERN_SUCCESS)
{
vmem_xfree(kernel_arena, addr, request);
vmem_xfree(kernel_arena, addr, size);
return nullptr;
}
}

View File

@@ -18,7 +18,7 @@ namespace snmalloc
* Bitmap of PalFeatures flags indicating the optional features that this
* PAL supports.
*/
static constexpr uint64_t pal_features = AlignedAllocation;
static constexpr uint64_t pal_features = 0;
static void error(const char* const str)
{
UNUSED(str);
@@ -26,7 +26,7 @@ namespace snmalloc
}
template<bool committed>
void* reserve(size_t* size, size_t align) noexcept
void* reserve(size_t size) noexcept
{
if (oe_base == 0)
{
@@ -36,21 +36,18 @@ namespace snmalloc
}
void* old_base = oe_base;
void* old_base2 = old_base;
void* next_base;
auto end = __oe_get_heap_end();
do
{
old_base2 = old_base;
auto new_base = pointer_align_up(old_base, align);
next_base = pointer_offset(new_base, *size);
auto new_base = old_base;
next_base = pointer_offset(new_base, size);
if (next_base > end)
error("Out of memory");
} while (oe_base.compare_exchange_strong(old_base, next_base));
*size = pointer_diff(old_base2, next_base);
return old_base;
}

View File

@@ -54,8 +54,12 @@ namespace snmalloc
void notify_not_using(void* p, size_t size) noexcept
{
assert(is_aligned_block<OS_PAGE_SIZE>(p, size));
#ifdef USE_POSIX_COMMIT_CHECKS
mprotect(p, size, PROT_NONE);
#else
UNUSED(p);
UNUSED(size);
#endif
}
/**
@@ -72,11 +76,13 @@ namespace snmalloc
if constexpr (zero_mem == YesZero)
static_cast<OS*>(this)->template zero<true>(p, size);
else
{
UNUSED(p);
UNUSED(size);
}
#ifdef USE_POSIX_COMMIT_CHECKS
mprotect(p, size, PROT_READ | PROT_WRITE);
#else
UNUSED(p);
UNUSED(size);
#endif
}
/**
@@ -121,11 +127,11 @@ namespace snmalloc
* greater than a page.
*/
template<bool committed>
void* reserve(const size_t* size) noexcept
void* reserve(size_t size) noexcept
{
void* p = mmap(
nullptr,
*size,
size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1,

View File

@@ -183,7 +183,7 @@ namespace snmalloc
}
# elif defined(PLATFORM_HAS_VIRTUALALLOC2)
template<bool committed>
void* reserve(size_t* size, size_t align) noexcept
void* reserve(size_t size, size_t align) noexcept
{
DWORD flags = MEM_RESERVE;
@@ -209,7 +209,7 @@ namespace snmalloc
param.Pointer = &addressReqs;
void* ret = VirtualAlloc2FromApp(
nullptr, nullptr, *size, flags, PAGE_READWRITE, &param, 1);
nullptr, nullptr, size, flags, PAGE_READWRITE, &param, 1);
if (ret == nullptr)
{
error("Failed to allocate memory\n");
@@ -218,14 +218,14 @@ namespace snmalloc
}
# else
template<bool committed>
void* reserve(size_t* size) noexcept
void* reserve(size_t size) noexcept
{
DWORD flags = MEM_RESERVE;
if (committed)
flags |= MEM_COMMIT;
void* ret = VirtualAlloc(nullptr, *size, flags, PAGE_READWRITE);
void* ret = VirtualAlloc(nullptr, size, flags, PAGE_READWRITE);
if (ret == nullptr)
{
error("Failed to allocate memory\n");

View File

@@ -32,8 +32,12 @@ int main()
{
MemoryProviderStateMixin<DefaultPal> mp;
size_t size = 1ULL << 28;
oe_base = mp.reserve<true>(&size, 0);
// 28 is large enough to produce a nested allocator.
// It is also large enough for the example to run in.
// For 1MiB superslabs, SUPERSLAB_BITS + 4 is not big enough for the example.
size_t large_class = 28 - SUPERSLAB_BITS;
size_t size = 1ULL << (SUPERSLAB_BITS + large_class);
oe_base = mp.reserve<true>(large_class);
oe_end = (uint8_t*)oe_base + size;
std::cout << "Allocated region " << oe_base << " - " << oe_end << std::endl;

View File

@@ -108,7 +108,8 @@ int main(int argc, char** argv)
test_posix_memalign((size_t)-1, 0, EINVAL, true);
test_posix_memalign(OS_PAGE_SIZE, sizeof(uintptr_t) / 2, EINVAL, true);
for (size_t align = sizeof(uintptr_t); align <= SUPERSLAB_SIZE; align <<= 1)
for (size_t align = sizeof(uintptr_t); align <= SUPERSLAB_SIZE * 8;
align <<= 1)
{
for (snmalloc::sizeclass_t sc = 0; sc < NUM_SIZECLASSES; sc++)
{

View File

@@ -45,8 +45,12 @@ int main()
MemoryProviderStateMixin<DefaultPal> mp;
size_t size = 1ULL << 26;
oe_base = mp.reserve<true>(&size, 1);
// 26 is large enough to produce a nested allocator.
// It is also large enough for the example to run in.
// For 1MiB superslabs, SUPERSLAB_BITS + 2 is not big enough for the example.
size_t large_class = 26 - SUPERSLAB_BITS;
size_t size = 1ULL << (SUPERSLAB_BITS + large_class);
oe_base = mp.reserve<true>(large_class);
oe_end = (uint8_t*)oe_base + size;
std::cout << "Allocated region " << oe_base << " - " << oe_end << std::endl;