Various fixes for OE (#418)
* Add default for getting chunk allocator state Makes the API same between the two configurations. * Reduce address space usage for Open Enclave * Fix OE Pal concept * Add support for Pal not to provide time. The lazy return of pages to the OS uses a simple time based heuristic. This enables a PAL to not support time, and return the memory to a central pool immediately. * Update src/backend/backend.h Co-authored-by: Amaury Chamayou <amaury@xargs.fr> Co-authored-by: Amaury Chamayou <amaury@xargs.fr>
This commit is contained in:
committed by
GitHub
parent
91919f6014
commit
dc542cdc9d
@@ -6,6 +6,19 @@
|
||||
#include "commonconfig.h"
|
||||
#include "pagemap.h"
|
||||
|
||||
#if defined(SNMALLOC_CHECK_CLIENT) && !defined(OPEN_ENCLAVE)
|
||||
/**
|
||||
* Protect meta data blocks by allocating separate from chunks for
|
||||
* user allocations. This involves leaving gaps in address space.
|
||||
* This is less efficient, so should only be applied for the checked
|
||||
* build.
|
||||
*
|
||||
* On Open Enclave the address space is limited, so we disable this
|
||||
* feature.
|
||||
*/
|
||||
# define SNMALLOC_META_PROTECTED
|
||||
#endif
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
/**
|
||||
@@ -22,14 +35,23 @@ namespace snmalloc
|
||||
// Size of local address space requests. Currently aimed at 2MiB large
|
||||
// pages but should make this configurable (i.e. for OE, so we don't need as
|
||||
// much space).
|
||||
#ifdef OPEN_ENCLAVE
|
||||
// Don't prefetch address space on OE, as it is limited.
|
||||
// This could cause perf issues during warm-up phases.
|
||||
constexpr static size_t LOCAL_CACHE_BLOCK = 0;
|
||||
#else
|
||||
constexpr static size_t LOCAL_CACHE_BLOCK = bits::one_at_bit(21);
|
||||
#endif
|
||||
|
||||
#ifdef SNMALLOC_CHECK_CLIENT
|
||||
#ifdef SNMALLOC_META_PROTECTED
|
||||
// When protecting the meta-data, we use a smaller block for the meta-data
|
||||
// that is randomised inside a larger block. This needs to be at least a
|
||||
// page so that we can use guard pages.
|
||||
constexpr static size_t LOCAL_CACHE_META_BLOCK =
|
||||
bits::max(MIN_CHUNK_SIZE * 2, OS_PAGE_SIZE);
|
||||
static_assert(
|
||||
LOCAL_CACHE_META_BLOCK <= LOCAL_CACHE_BLOCK,
|
||||
"LOCAL_CACHE_META_BLOCK must be smaller than LOCAL_CACHE_BLOCK");
|
||||
#endif
|
||||
|
||||
public:
|
||||
@@ -103,7 +125,7 @@ namespace snmalloc
|
||||
static capptr::Chunk<void> reserve(
|
||||
AddressSpaceManager<PAL>& global, LocalState* local_state, size_t size)
|
||||
{
|
||||
#ifdef SNMALLOC_CHECK_CLIENT
|
||||
#ifdef SNMALLOC_META_PROTECTED
|
||||
constexpr auto MAX_CACHED_SIZE =
|
||||
is_meta ? LOCAL_CACHE_META_BLOCK : LOCAL_CACHE_BLOCK;
|
||||
#else
|
||||
@@ -113,7 +135,7 @@ namespace snmalloc
|
||||
capptr::Chunk<void> p;
|
||||
if ((local_state != nullptr) && (size <= MAX_CACHED_SIZE))
|
||||
{
|
||||
#ifdef SNMALLOC_CHECK_CLIENT
|
||||
#ifdef SNMALLOC_META_PROTECTED
|
||||
auto& local = is_meta ? local_state->local_meta_address_space :
|
||||
local_state->local_address_space;
|
||||
#else
|
||||
@@ -133,7 +155,7 @@ namespace snmalloc
|
||||
if (refill == nullptr)
|
||||
return nullptr;
|
||||
|
||||
#ifdef SNMALLOC_CHECK_CLIENT
|
||||
#ifdef SNMALLOC_META_PROTECTED
|
||||
if (is_meta)
|
||||
{
|
||||
refill = sub_range(refill, LOCAL_CACHE_BLOCK, LOCAL_CACHE_META_BLOCK);
|
||||
@@ -149,7 +171,7 @@ namespace snmalloc
|
||||
local_state, size);
|
||||
}
|
||||
|
||||
#ifdef SNMALLOC_CHECK_CLIENT
|
||||
#ifdef SNMALLOC_META_PROTECTED
|
||||
// During start up we need meta-data before we have a local allocator
|
||||
// This code protects that meta-data with randomisation, and guard pages.
|
||||
if (local_state == nullptr && is_meta)
|
||||
@@ -178,7 +200,7 @@ namespace snmalloc
|
||||
return p;
|
||||
}
|
||||
|
||||
#ifdef SNMALLOC_CHECK_CLIENT
|
||||
#ifdef SNMALLOC_META_PROTECTED
|
||||
/**
|
||||
* Returns a sub-range of [return, return+sub_size] that is contained in
|
||||
* the range [base, base+full_size]. The first and last slot are not used
|
||||
@@ -236,7 +258,7 @@ namespace snmalloc
|
||||
|
||||
AddressSpaceManagerCore local_address_space;
|
||||
|
||||
#ifdef SNMALLOC_CHECK_CLIENT
|
||||
#ifdef SNMALLOC_META_PROTECTED
|
||||
/**
|
||||
* Secondary local address space, so we can apply some randomisation
|
||||
* and guard pages to protect the meta-data.
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace snmalloc
|
||||
|
||||
public:
|
||||
static ChunkAllocatorState&
|
||||
get_chunk_allocator_state(typename Backend::LocalState*)
|
||||
get_chunk_allocator_state(typename Backend::LocalState* = nullptr)
|
||||
{
|
||||
return chunk_allocator_state;
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ namespace snmalloc
|
||||
{
|
||||
// Unsafe downcast here. Don't want vtable and RTTI.
|
||||
auto self = reinterpret_cast<DecayMemoryTimerObject*>(p);
|
||||
ChunkAllocator::handle_decay_tick(self->state);
|
||||
ChunkAllocator::handle_decay_tick<Pal>(self->state);
|
||||
}
|
||||
|
||||
// Specify that we notify the ChunkAllocator every 500ms.
|
||||
@@ -143,6 +143,7 @@ namespace snmalloc
|
||||
{}
|
||||
};
|
||||
|
||||
template<SNMALLOC_CONCEPT(ConceptPAL) Pal>
|
||||
static void handle_decay_tick(ChunkAllocatorState* state)
|
||||
{
|
||||
auto new_epoch = (state->epoch + 1) % NUM_EPOCHS;
|
||||
@@ -186,6 +187,7 @@ namespace snmalloc
|
||||
size_t slab_size,
|
||||
RemoteAllocator* remote)
|
||||
{
|
||||
using PAL = typename SharedStateHandle::Pal;
|
||||
ChunkAllocatorState& state =
|
||||
SharedStateHandle::get_chunk_allocator_state(&local_state);
|
||||
|
||||
@@ -196,13 +198,16 @@ namespace snmalloc
|
||||
}
|
||||
|
||||
ChunkRecord* chunk_record = nullptr;
|
||||
// Try local cache of chunks first
|
||||
for (size_t e = 0; e < NUM_EPOCHS && chunk_record == nullptr; e++)
|
||||
if constexpr (pal_supports<Time, PAL>)
|
||||
{
|
||||
chunk_record =
|
||||
chunk_alloc_local_state
|
||||
.chunk_stack[slab_sizeclass][(state.epoch - e) % NUM_EPOCHS]
|
||||
.pop();
|
||||
// Try local cache of chunks first
|
||||
for (size_t e = 0; e < NUM_EPOCHS && chunk_record == nullptr; e++)
|
||||
{
|
||||
chunk_record =
|
||||
chunk_alloc_local_state
|
||||
.chunk_stack[slab_sizeclass][(state.epoch - e) % NUM_EPOCHS]
|
||||
.pop();
|
||||
}
|
||||
}
|
||||
|
||||
// Try global cache.
|
||||
@@ -210,8 +215,10 @@ namespace snmalloc
|
||||
{
|
||||
chunk_record = state.decommitted_chunk_stack[slab_sizeclass].pop();
|
||||
if (chunk_record != nullptr)
|
||||
Pal::notify_using<NoZero>(
|
||||
{
|
||||
PAL::template notify_using<NoZero>(
|
||||
chunk_record->meta_common.chunk.unsafe_ptr(), slab_size);
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk_record != nullptr)
|
||||
@@ -258,14 +265,31 @@ namespace snmalloc
|
||||
{
|
||||
ChunkAllocatorState& state =
|
||||
SharedStateHandle::get_chunk_allocator_state(&local_state);
|
||||
#ifdef SNMALLOC_TRACING
|
||||
std::cout << "Return slab:" << p->meta_common.chunk.unsafe_ptr()
|
||||
<< " slab_sizeclass " << slab_sizeclass << " size "
|
||||
<< slab_sizeclass_to_size(slab_sizeclass)
|
||||
<< " memory in stacks " << state.memory_in_stacks << std::endl;
|
||||
#endif
|
||||
|
||||
chunk_alloc_local_state.chunk_stack[slab_sizeclass][state.epoch].push(p);
|
||||
if constexpr (pal_supports<Time, typename SharedStateHandle::Pal>)
|
||||
{
|
||||
// If we have a time source use decay based local cache.
|
||||
#ifdef SNMALLOC_TRACING
|
||||
std::cout << "Return slab:" << p->meta_common.chunk.unsafe_ptr()
|
||||
<< " slab_sizeclass " << slab_sizeclass << " size "
|
||||
<< slab_sizeclass_to_size(slab_sizeclass)
|
||||
<< " memory in stacks " << state.memory_in_stacks
|
||||
<< std::endl;
|
||||
#endif
|
||||
chunk_alloc_local_state.chunk_stack[slab_sizeclass][state.epoch].push(
|
||||
p);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No time source share immediately with global state.
|
||||
// Disable pages for this chunk.
|
||||
SharedStateHandle::Pal::notify_not_using(
|
||||
p->meta_common.chunk.unsafe_ptr(),
|
||||
slab_sizeclass_to_size(slab_sizeclass));
|
||||
|
||||
// Add to global state
|
||||
state.decommitted_chunk_stack[slab_sizeclass].push(p);
|
||||
}
|
||||
|
||||
state.memory_in_stacks += slab_sizeclass_to_size(slab_sizeclass);
|
||||
}
|
||||
@@ -300,35 +324,43 @@ namespace snmalloc
|
||||
typename SharedStateHandle::LocalState& local_state,
|
||||
ChunkAllocatorLocalState& chunk_alloc_local_state)
|
||||
{
|
||||
ChunkAllocatorState& state =
|
||||
SharedStateHandle::get_chunk_allocator_state(&local_state);
|
||||
|
||||
// Register with the Pal to receive notifications.
|
||||
if (!state.register_decay.test_and_set())
|
||||
if constexpr (pal_supports<Time, typename SharedStateHandle::Pal>)
|
||||
{
|
||||
auto timer = alloc_meta_data<
|
||||
DecayMemoryTimerObject<typename SharedStateHandle::Pal>,
|
||||
SharedStateHandle>(&local_state, &state);
|
||||
if (timer != nullptr)
|
||||
ChunkAllocatorState& state =
|
||||
SharedStateHandle::get_chunk_allocator_state(&local_state);
|
||||
|
||||
// Register with the Pal to receive notifications.
|
||||
if (!state.register_decay.test_and_set())
|
||||
{
|
||||
SharedStateHandle::Pal::register_timer(timer);
|
||||
auto timer = alloc_meta_data<
|
||||
DecayMemoryTimerObject<typename SharedStateHandle::Pal>,
|
||||
SharedStateHandle>(&local_state, &state);
|
||||
if (timer != nullptr)
|
||||
{
|
||||
SharedStateHandle::Pal::register_timer(timer);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We failed to register the notification.
|
||||
// This is not catarophic, but if we can't allocate this
|
||||
// state something else will fail shortly.
|
||||
state.register_decay.clear();
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
// Add to the list of local states.
|
||||
auto* head = state.all_local.load();
|
||||
do
|
||||
{
|
||||
// We failed to register the notification.
|
||||
// This is not catarophic, but if we can't allocate this
|
||||
// state something else will fail shortly.
|
||||
state.register_decay.clear();
|
||||
}
|
||||
chunk_alloc_local_state.next = head;
|
||||
} while (!state.all_local.compare_exchange_strong(
|
||||
head, &chunk_alloc_local_state));
|
||||
}
|
||||
|
||||
// Add to the list of local states.
|
||||
auto* head = state.all_local.load();
|
||||
do
|
||||
else
|
||||
{
|
||||
chunk_alloc_local_state.next = head;
|
||||
} while (!state.all_local.compare_exchange_strong(
|
||||
head, &chunk_alloc_local_state));
|
||||
UNUSED(local_state);
|
||||
UNUSED(chunk_alloc_local_state);
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace snmalloc
|
||||
|
||||
@@ -83,13 +83,16 @@ namespace snmalloc
|
||||
template<typename T = void*>
|
||||
SNMALLOC_FAST_PATH T check_tick(T p = nullptr)
|
||||
{
|
||||
// Check before decrement, so that later calcations can use
|
||||
// count_down == 0 for check on the next call.
|
||||
// This is used if the ticks are way below the frequency of
|
||||
// heart beat.
|
||||
if (--count_down == 0)
|
||||
if constexpr (pal_supports<Time, PAL>)
|
||||
{
|
||||
return check_tick_slow(p);
|
||||
// Check before decrement, so that later calcations can use
|
||||
// count_down == 0 for check on the next call.
|
||||
// This is used if the ticks are way below the frequency of
|
||||
// heart beat.
|
||||
if (--count_down == 0)
|
||||
{
|
||||
return check_tick_slow(p);
|
||||
}
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace snmalloc
|
||||
* The features exported by this PAL.
|
||||
*/
|
||||
static constexpr uint64_t pal_features =
|
||||
AlignedAllocation | LazyCommit | Entropy;
|
||||
AlignedAllocation | LazyCommit | Entropy | Time;
|
||||
|
||||
/*
|
||||
* `page_size`
|
||||
|
||||
@@ -41,6 +41,7 @@ namespace snmalloc
|
||||
* whether low memory conditions are still in effect.
|
||||
*/
|
||||
LowMemoryNotification = (1 << 0),
|
||||
|
||||
/**
|
||||
* This PAL natively supports allocation with a guaranteed alignment. If
|
||||
* this is not supported, then we will over-allocate and round the
|
||||
@@ -51,22 +52,31 @@ namespace snmalloc
|
||||
* `request()` method that takes only a size.
|
||||
*/
|
||||
AlignedAllocation = (1 << 1),
|
||||
|
||||
/**
|
||||
* This PAL natively supports lazy commit of pages. This means have large
|
||||
* allocations and not touching them does not increase memory usage. This is
|
||||
* exposed in the Pal.
|
||||
*/
|
||||
LazyCommit = (1 << 2),
|
||||
|
||||
/**
|
||||
* This Pal does not support allocation. All memory used with this Pal
|
||||
* should be pre-allocated.
|
||||
*/
|
||||
NoAllocation = (1 << 3),
|
||||
|
||||
/**
|
||||
* This Pal provides a source of Entropy
|
||||
*/
|
||||
Entropy = (1 << 4),
|
||||
|
||||
/**
|
||||
* This Pal provides a millisecond time source
|
||||
*/
|
||||
Time = (1 << 5),
|
||||
};
|
||||
|
||||
/**
|
||||
* Flag indicating whether requested memory should be zeroed.
|
||||
*/
|
||||
@@ -76,6 +86,7 @@ namespace snmalloc
|
||||
* Memory should not be zeroed, contents are undefined.
|
||||
*/
|
||||
NoZero,
|
||||
|
||||
/**
|
||||
* Memory must be zeroed. This can be lazily allocated via a copy-on-write
|
||||
* mechanism as long as any load from the memory returns zero.
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace snmalloc
|
||||
* ever use.
|
||||
*/
|
||||
template<SNMALLOC_CONCEPT(PALNoAllocBase) BasePAL>
|
||||
struct PALNoAlloc : public PalTimerDefaultImpl<BasePAL>
|
||||
struct PALNoAlloc : public BasePAL
|
||||
{
|
||||
/**
|
||||
* Bitmap of PalFeatures flags indicating the optional features that this
|
||||
@@ -38,7 +38,7 @@ namespace snmalloc
|
||||
|
||||
static constexpr size_t page_size = BasePAL::page_size;
|
||||
|
||||
static constexpr size_t address_bits = Aal::address_bits;
|
||||
static constexpr size_t address_bits = BasePAL::address_bits;
|
||||
|
||||
/**
|
||||
* Print a stack trace.
|
||||
|
||||
@@ -19,6 +19,8 @@ namespace snmalloc
|
||||
UNUSED(str);
|
||||
oe_abort();
|
||||
}
|
||||
static constexpr size_t address_bits = Aal::address_bits;
|
||||
static constexpr size_t page_size = Aal::smallest_page_size;
|
||||
};
|
||||
|
||||
using OpenEnclaveBasePAL = PALNoAlloc<OpenEnclaveErrorHandler>;
|
||||
|
||||
@@ -125,7 +125,7 @@ namespace snmalloc
|
||||
* POSIX systems are assumed to support lazy commit. The build system checks
|
||||
* getentropy is available, only then this PAL supports Entropy.
|
||||
*/
|
||||
static constexpr uint64_t pal_features = LazyCommit
|
||||
static constexpr uint64_t pal_features = LazyCommit | Time
|
||||
#if defined(SNMALLOC_PLATFORM_HAS_GETENTROPY)
|
||||
| Entropy
|
||||
#endif
|
||||
|
||||
@@ -55,7 +55,8 @@ namespace snmalloc
|
||||
* Bitmap of PalFeatures flags indicating the optional features that this
|
||||
* PAL supports. This PAL supports low-memory notifications.
|
||||
*/
|
||||
static constexpr uint64_t pal_features = LowMemoryNotification | Entropy
|
||||
static constexpr uint64_t pal_features = LowMemoryNotification | Entropy |
|
||||
Time
|
||||
# if defined(PLATFORM_HAS_VIRTUALALLOC2) && !defined(USE_SYSTEMATIC_TESTING)
|
||||
| AlignedAllocation
|
||||
# endif
|
||||
|
||||
Reference in New Issue
Block a user