Add AddressSpaceManager (#214)
This change brings in a new approach to managing address space. It wraps the Pal with a power of two reservation system, that guarantees all returned blocks are naturally aligned to their size. It either lets the Pal perform aligned requests, or over allocates and splits into power of two blocks.
This commit is contained in:
committed by
GitHub
parent
e393ac882f
commit
e16f2aff6f
@@ -4,6 +4,7 @@
|
||||
# include "pal_bsd.h"
|
||||
|
||||
# include <mach/vm_statistics.h>
|
||||
# include <utility>
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
@@ -55,12 +56,17 @@ namespace snmalloc
|
||||
*
|
||||
* See comment below.
|
||||
*/
|
||||
template<bool committed>
|
||||
void* reserve(size_t size)
|
||||
std::pair<void*, size_t> reserve_at_least(size_t size)
|
||||
{
|
||||
// Magic number for over-allocating chosen by the Pal
|
||||
// These should be further refined based on experiments.
|
||||
constexpr size_t min_size =
|
||||
bits::is64() ? bits::one_at_bit(32) : bits::one_at_bit(28);
|
||||
auto size_request = bits::max(size, min_size);
|
||||
|
||||
void* p = mmap(
|
||||
nullptr,
|
||||
size,
|
||||
size_request,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS,
|
||||
pal_anon_id,
|
||||
@@ -69,7 +75,7 @@ namespace snmalloc
|
||||
if (p == MAP_FAILED)
|
||||
error("Out of memory");
|
||||
|
||||
return p;
|
||||
return {p, size_request};
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -23,18 +23,19 @@ namespace snmalloc
|
||||
static constexpr uint64_t pal_features =
|
||||
AlignedAllocation | PALBSD<OS>::pal_features;
|
||||
|
||||
static constexpr size_t minimum_alloc_size = 4096;
|
||||
|
||||
/**
|
||||
* Reserve memory at a specific alignment.
|
||||
*/
|
||||
template<bool committed>
|
||||
void* reserve(size_t size, size_t align) noexcept
|
||||
void* reserve_aligned(size_t size) noexcept
|
||||
{
|
||||
// Alignment must be a power of 2.
|
||||
SNMALLOC_ASSERT(align == bits::next_pow2(align));
|
||||
SNMALLOC_ASSERT(size == bits::next_pow2(size));
|
||||
SNMALLOC_ASSERT(size >= minimum_alloc_size);
|
||||
|
||||
align = bits::max<size_t>(4096, align);
|
||||
|
||||
size_t log2align = bits::next_pow2_bits(align);
|
||||
size_t log2align = bits::next_pow2_bits(size);
|
||||
|
||||
void* p = mmap(
|
||||
nullptr,
|
||||
|
||||
@@ -60,8 +60,12 @@ namespace snmalloc
|
||||
}
|
||||
|
||||
template<bool committed>
|
||||
void* reserve(size_t size, size_t align)
|
||||
void* reserve_aligned(size_t size) noexcept
|
||||
{
|
||||
SNMALLOC_ASSERT(size == bits::next_pow2(size));
|
||||
SNMALLOC_ASSERT(size >= minimum_alloc_size);
|
||||
size_t align = size;
|
||||
|
||||
vm_offset_t addr;
|
||||
if (vmem_xalloc(
|
||||
kernel_arena,
|
||||
|
||||
@@ -13,98 +13,31 @@ namespace snmalloc
|
||||
{
|
||||
class PALOpenEnclave
|
||||
{
|
||||
/**
|
||||
* Implements a power of two allocator, where all blocks are aligned to the
|
||||
* same power of two as their size. This is what snmalloc uses to get
|
||||
* alignment of very large sizeclasses.
|
||||
*
|
||||
* Pals are not required to unreserve memory, so this does not require the
|
||||
* usual complexity of a buddy allocator.
|
||||
*/
|
||||
/// Base of OE heap
|
||||
static inline void* heap_base = nullptr;
|
||||
|
||||
/// Size of OE heap
|
||||
static inline size_t heap_size;
|
||||
|
||||
// There are a maximum of two blocks for any size/align in a range.
|
||||
// One before the point of maximum alignment, and one after.
|
||||
static inline std::array<std::array<void*, 2>, bits::BITS> ranges;
|
||||
// This is infrequently used code, a spin lock simplifies the code
|
||||
// considerably, and should never be on the fast path.
|
||||
static inline std::atomic_flag spin_lock;
|
||||
|
||||
static void add_block(size_t align_bits, void* base)
|
||||
{
|
||||
if (ranges[align_bits][0] == nullptr)
|
||||
{
|
||||
ranges[align_bits][0] = base;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ranges[align_bits][1] != nullptr)
|
||||
error("Critical assumption violated!");
|
||||
|
||||
ranges[align_bits][1] = base;
|
||||
}
|
||||
|
||||
static void* remove_block(size_t align_bits)
|
||||
{
|
||||
auto first = ranges[align_bits][0];
|
||||
if (first == nullptr)
|
||||
{
|
||||
if (align_bits < (bits::BITS - 1))
|
||||
{
|
||||
// Look for larger block and split up recursively
|
||||
void* bigger = remove_block(align_bits + 1);
|
||||
if (bigger == nullptr)
|
||||
{
|
||||
// Out of memory.
|
||||
return bigger;
|
||||
}
|
||||
void* left_over =
|
||||
pointer_offset(bigger, bits::one_at_bit(align_bits));
|
||||
ranges[align_bits][0] = left_over;
|
||||
return bigger;
|
||||
}
|
||||
// Out of memory
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto second = ranges[align_bits][1];
|
||||
if (second != nullptr)
|
||||
{
|
||||
ranges[align_bits][1] = nullptr;
|
||||
return second;
|
||||
}
|
||||
|
||||
ranges[align_bits][0] = nullptr;
|
||||
return first;
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* This will be called by oe_allocator_init to set up enclave heap bounds.
|
||||
*/
|
||||
static void setup_initial_range(void* base, void* end)
|
||||
{
|
||||
// Find the minimum set of maximally aligned blocks in this range.
|
||||
// Each block's alignment and size are equal.
|
||||
size_t length = pointer_diff(base, end);
|
||||
while (length != 0)
|
||||
{
|
||||
size_t base_align_bits = bits::ctz(address_cast(base));
|
||||
size_t length_align_bits = (bits::BITS - 1) - bits::clz(length);
|
||||
size_t align_bits = bits::min(base_align_bits, length_align_bits);
|
||||
size_t align = bits::one_at_bit(align_bits);
|
||||
|
||||
add_block(align_bits, base);
|
||||
|
||||
base = pointer_offset(base, align);
|
||||
length -= align;
|
||||
}
|
||||
heap_size = pointer_diff(base, end);
|
||||
heap_base = base;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 constexpr size_t page_size = 0x1000;
|
||||
|
||||
@@ -114,19 +47,18 @@ namespace snmalloc
|
||||
oe_abort();
|
||||
}
|
||||
|
||||
template<bool committed>
|
||||
static void* reserve(size_t size, size_t align) noexcept
|
||||
static std::pair<void*, size_t>
|
||||
reserve_at_least(size_t request_size) noexcept
|
||||
{
|
||||
// The following are all true from the current way snmalloc uses the PAL.
|
||||
// The implementation here is depending on them.
|
||||
SNMALLOC_ASSERT(size == bits::next_pow2(size));
|
||||
SNMALLOC_ASSERT(align == bits::next_pow2(align));
|
||||
if (size != align)
|
||||
error("Critical assumption violated!");
|
||||
|
||||
// First call returns the entire address space
|
||||
// subsequent calls return {nullptr, 0}
|
||||
FlagLock lock(spin_lock);
|
||||
size_t align_bits = bits::next_pow2_bits(align);
|
||||
return remove_block(align_bits);
|
||||
if (request_size > heap_size)
|
||||
return {nullptr, 0};
|
||||
|
||||
auto result = std::make_pair(heap_base, heap_size);
|
||||
heap_size = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
template<bool page_aligned = false>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#include <utility>
|
||||
|
||||
extern "C" int puts(const char* str);
|
||||
|
||||
@@ -147,12 +148,16 @@ namespace snmalloc
|
||||
* POSIX does not define a portable interface for specifying alignment
|
||||
* greater than a page.
|
||||
*/
|
||||
template<bool committed>
|
||||
void* reserve(size_t size) noexcept
|
||||
std::pair<void*, size_t> reserve_at_least(size_t size) noexcept
|
||||
{
|
||||
// Magic number for over-allocating chosen by the Pal
|
||||
// These should be further refined based on experiments.
|
||||
constexpr size_t min_size =
|
||||
bits::is64() ? bits::one_at_bit(32) : bits::one_at_bit(28);
|
||||
auto size_request = bits::max(size, min_size);
|
||||
void* p = mmap(
|
||||
nullptr,
|
||||
size,
|
||||
size_request,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS,
|
||||
-1,
|
||||
@@ -161,7 +166,7 @@ namespace snmalloc
|
||||
if (p == MAP_FAILED)
|
||||
OS::error("Out of memory");
|
||||
|
||||
return p;
|
||||
return {p, size_request};
|
||||
}
|
||||
};
|
||||
} // namespace snmalloc
|
||||
|
||||
@@ -77,11 +77,13 @@ namespace snmalloc
|
||||
* PAL supports. This PAL supports low-memory notifications.
|
||||
*/
|
||||
static constexpr uint64_t pal_features = LowMemoryNotification
|
||||
# if defined(PLATFORM_HAS_VIRTUALALLOC2)
|
||||
# if defined(PLATFORM_HAS_VIRTUALALLOC2) && !defined(USE_SYSTEMATIC_TESTING)
|
||||
| AlignedAllocation
|
||||
# endif
|
||||
;
|
||||
|
||||
static constexpr size_t minimum_alloc_size = 0x10000;
|
||||
|
||||
static constexpr size_t page_size = 0x1000;
|
||||
|
||||
/**
|
||||
@@ -157,13 +159,16 @@ namespace snmalloc
|
||||
static size_t bump_ptr = (size_t)0x4000'0000'0000;
|
||||
return bump_ptr;
|
||||
}
|
||||
template<bool committed>
|
||||
void* reserve(size_t size) noexcept
|
||||
{
|
||||
DWORD flags = MEM_RESERVE;
|
||||
|
||||
if (committed)
|
||||
flags |= MEM_COMMIT;
|
||||
std::pair<void*, size_t> reserve_at_least(size_t size) noexcept
|
||||
{
|
||||
// Magic number for over-allocating chosen by the Pal
|
||||
// These should be further refined based on experiments.
|
||||
constexpr size_t min_size =
|
||||
bits::is64() ? bits::one_at_bit(32) : bits::one_at_bit(28);
|
||||
auto size_request = bits::max(size, min_size);
|
||||
|
||||
DWORD flags = MEM_RESERVE;
|
||||
|
||||
size_t retries = 1000;
|
||||
void* p;
|
||||
@@ -171,34 +176,30 @@ namespace snmalloc
|
||||
do
|
||||
{
|
||||
p = VirtualAlloc(
|
||||
(void*)systematic_bump_ptr(), size, flags, PAGE_READWRITE);
|
||||
(void*)systematic_bump_ptr(), size_request, flags, PAGE_READWRITE);
|
||||
|
||||
systematic_bump_ptr() += size;
|
||||
systematic_bump_ptr() += size_request;
|
||||
retries--;
|
||||
} while (p == nullptr && retries > 0);
|
||||
|
||||
return p;
|
||||
return {p, size_request};
|
||||
}
|
||||
# elif defined(PLATFORM_HAS_VIRTUALALLOC2)
|
||||
template<bool committed>
|
||||
void* reserve(size_t size, size_t align) noexcept
|
||||
void* reserve_aligned(size_t size) noexcept
|
||||
{
|
||||
SNMALLOC_ASSERT(size == bits::next_pow2(size));
|
||||
SNMALLOC_ASSERT(size >= minimum_alloc_size);
|
||||
|
||||
DWORD flags = MEM_RESERVE;
|
||||
|
||||
if (committed)
|
||||
flags |= MEM_COMMIT;
|
||||
|
||||
// Windows doesn't let you request memory less than 64KB aligned. Most
|
||||
// operating systems will simply give you something more aligned than you
|
||||
// ask for, but Windows complains about invalid parameters.
|
||||
const size_t min_align = 64 * 1024;
|
||||
if (align < min_align)
|
||||
align = min_align;
|
||||
|
||||
// If we're on Windows 10 or newer, we can use the VirtualAlloc2
|
||||
// function. The FromApp variant is useable by UWP applications and
|
||||
// cannot allocate executable memory.
|
||||
MEM_ADDRESS_REQUIREMENTS addressReqs = {NULL, NULL, align};
|
||||
MEM_ADDRESS_REQUIREMENTS addressReqs = {NULL, NULL, size};
|
||||
|
||||
MEM_EXTENDED_PARAMETER param = {
|
||||
{MemExtendedParameterAddressRequirements, 0}, {0}};
|
||||
@@ -215,20 +216,21 @@ namespace snmalloc
|
||||
return ret;
|
||||
}
|
||||
# else
|
||||
template<bool committed>
|
||||
void* reserve(size_t size) noexcept
|
||||
std::pair<void*, size_t> reserve_at_least(size_t size) noexcept
|
||||
{
|
||||
// Magic number for over-allocating chosen by the Pal
|
||||
// These should be further refined based on experiments.
|
||||
constexpr size_t min_size =
|
||||
bits::is64() ? bits::one_at_bit(32) : bits::one_at_bit(28);
|
||||
auto size_request = bits::max(size, min_size);
|
||||
|
||||
DWORD flags = MEM_RESERVE;
|
||||
|
||||
if (committed)
|
||||
flags |= MEM_COMMIT;
|
||||
|
||||
void* ret = VirtualAlloc(nullptr, size, flags, PAGE_READWRITE);
|
||||
void* ret = VirtualAlloc(nullptr, size_request, flags, PAGE_READWRITE);
|
||||
if (ret == nullptr)
|
||||
{
|
||||
error("Failed to allocate memory\n");
|
||||
}
|
||||
return ret;
|
||||
return std::pair(ret, size_request);
|
||||
}
|
||||
# endif
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user