SP: MemoryProviderStateMixin, AddressSpaceManager
* The AddressSpaceManager now requests address space in specified granule sizes and registers those allocations with an external ArenaMap. * The DefaultArenaMap is a (somewhat erroneously named) Pagemap sparse array / tree for these provenance roots. Nothing is stored on non-StrictProvenance architectures. * In the Sandbox test, give an example of a different ArenaMap structure, which confines amplification to sandbox memory. * Adjust some other tests to compile.
This commit is contained in:
committed by
Nathaniel Wesley Filardo
parent
a9722667ab
commit
6a7e82463c
@@ -1,6 +1,7 @@
|
||||
#include "../ds/address.h"
|
||||
#include "../ds/flaglock.h"
|
||||
#include "../pal/pal.h"
|
||||
#include "arenamap.h"
|
||||
|
||||
#include <array>
|
||||
namespace snmalloc
|
||||
@@ -13,7 +14,7 @@ namespace snmalloc
|
||||
* It cannot unreserve memory, so this does not require the
|
||||
* usual complexity of a buddy allocator.
|
||||
*/
|
||||
template<SNMALLOC_CONCEPT(ConceptPAL) PAL>
|
||||
template<SNMALLOC_CONCEPT(ConceptPAL) PAL, typename ArenaMap>
|
||||
class AddressSpaceManager
|
||||
{
|
||||
/**
|
||||
@@ -173,14 +174,24 @@ namespace snmalloc
|
||||
* The returned block is guaranteed to be aligened to the size.
|
||||
*
|
||||
* Only request 2^n sizes, and not less than a pointer.
|
||||
*
|
||||
* On StrictProvenance architectures, any underlying allocations made as
|
||||
* part of satisfying the request will be registered with the provided
|
||||
* arena_map for use in subsequent amplification.
|
||||
*/
|
||||
template<bool committed>
|
||||
CapPtr<void, CBArena> reserve(size_t size)
|
||||
CapPtr<void, CBArena> reserve(size_t size, ArenaMap& arena_map)
|
||||
{
|
||||
SNMALLOC_ASSERT(bits::is_pow2(size));
|
||||
SNMALLOC_ASSERT(size >= sizeof(void*));
|
||||
|
||||
if constexpr (pal_supports<AlignedAllocation, PAL>)
|
||||
/*
|
||||
* For sufficiently large allocations with platforms that support aligned
|
||||
* allocations and architectures that don't require StrictProvenance,
|
||||
* try asking the platform first.
|
||||
*/
|
||||
if constexpr (
|
||||
pal_supports<AlignedAllocation, PAL> && !aal_supports<StrictProvenance>)
|
||||
{
|
||||
if (size >= PAL::minimum_alloc_size)
|
||||
return CapPtr<void, CBArena>(
|
||||
@@ -198,9 +209,43 @@ namespace snmalloc
|
||||
size_t block_size = 0;
|
||||
if constexpr (pal_supports<AlignedAllocation, PAL>)
|
||||
{
|
||||
block_size = PAL::minimum_alloc_size;
|
||||
block = CapPtr<void, CBArena>(
|
||||
PAL::template reserve_aligned<false>(block_size));
|
||||
/*
|
||||
* aal_supports<StrictProvenance> ends up here, too, and we ensure
|
||||
* that we always allocate whole ArenaMap granules.
|
||||
*/
|
||||
if constexpr (aal_supports<StrictProvenance>)
|
||||
{
|
||||
static_assert(
|
||||
!aal_supports<StrictProvenance> ||
|
||||
(ArenaMap::alloc_size >= PAL::minimum_alloc_size),
|
||||
"Provenance root granule must be at least PAL's "
|
||||
"minimum_alloc_size");
|
||||
block_size = bits::align_up(size, ArenaMap::alloc_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* We will have handled the case where size >= minimum_alloc_size
|
||||
* above, so we are left to handle only small things here.
|
||||
*/
|
||||
block_size = PAL::minimum_alloc_size;
|
||||
}
|
||||
|
||||
void* block_raw = PAL::template reserve_aligned<false>(block_size);
|
||||
|
||||
block = CapPtr<void, CBArena>(block_raw);
|
||||
|
||||
if constexpr (aal_supports<StrictProvenance>)
|
||||
{
|
||||
auto root_block = CapPtr<void, CBArena>(block_raw);
|
||||
auto root_size = block_size;
|
||||
do
|
||||
{
|
||||
arena_map.register_root(root_block);
|
||||
root_block = pointer_offset(root_block, ArenaMap::alloc_size);
|
||||
root_size -= ArenaMap::alloc_size;
|
||||
} while (root_size > 0);
|
||||
}
|
||||
}
|
||||
else if constexpr (!pal_supports<NoAllocation, PAL>)
|
||||
{
|
||||
@@ -249,7 +294,8 @@ namespace snmalloc
|
||||
* used, by smaller objects.
|
||||
*/
|
||||
template<bool committed>
|
||||
CapPtr<void, CBArena> reserve_with_left_over(size_t size)
|
||||
CapPtr<void, CBArena>
|
||||
reserve_with_left_over(size_t size, ArenaMap& arena_map)
|
||||
{
|
||||
SNMALLOC_ASSERT(size >= sizeof(void*));
|
||||
|
||||
@@ -257,7 +303,7 @@ namespace snmalloc
|
||||
|
||||
size_t rsize = bits::next_pow2(size);
|
||||
|
||||
auto res = reserve<false>(rsize);
|
||||
auto res = reserve<false>(rsize, arena_map);
|
||||
|
||||
if (res != nullptr)
|
||||
{
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
template<SNMALLOC_CONCEPT(ConceptPAL) PAL>
|
||||
template<SNMALLOC_CONCEPT(ConceptPAL) PAL, typename ArenaMap>
|
||||
class MemoryProviderStateMixin;
|
||||
|
||||
class Largeslab : public Baseslab
|
||||
@@ -30,7 +30,7 @@ namespace snmalloc
|
||||
template<typename>
|
||||
typename AP>
|
||||
friend class MPMCStack;
|
||||
template<SNMALLOC_CONCEPT(ConceptPAL) PAL>
|
||||
template<SNMALLOC_CONCEPT(ConceptPAL) PAL, typename ArenaMap>
|
||||
friend class MemoryProviderStateMixin;
|
||||
AtomicCapPtr<Largeslab, CBArena> next = nullptr;
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace snmalloc
|
||||
// This represents the state that the large allcoator needs to add to the
|
||||
// global state of the allocator. This is currently stored in the memory
|
||||
// provider, so we add this in.
|
||||
template<SNMALLOC_CONCEPT(ConceptPAL) PAL>
|
||||
template<SNMALLOC_CONCEPT(ConceptPAL) PAL, typename ArenaMap>
|
||||
class MemoryProviderStateMixin
|
||||
{
|
||||
/**
|
||||
@@ -71,10 +71,23 @@ namespace snmalloc
|
||||
*/
|
||||
std::atomic_flag lazy_decommit_guard = {};
|
||||
|
||||
/**
|
||||
* Instantiate the ArenaMap here.
|
||||
*
|
||||
* In most cases, this will be a purely static object (a DefaultArenaMap
|
||||
* using a GlobalPagemapTemplate or ExternalGlobalPagemapTemplate). For
|
||||
* sandboxes, this may have per-instance state (e.g., the sandbox root);
|
||||
* presently, that's handled by the MemoryProviderStateMixin constructor
|
||||
* that takes a pointer to address space it owns. There is some
|
||||
* non-orthogonality of concerns here.
|
||||
*/
|
||||
ArenaMap arena_map = {};
|
||||
|
||||
using ASM = AddressSpaceManager<PAL, ArenaMap>;
|
||||
/**
|
||||
* Manages address space for this memory provider.
|
||||
*/
|
||||
AddressSpaceManager<PAL> address_space = {};
|
||||
ASM address_space = {};
|
||||
|
||||
/**
|
||||
* High-water mark of used memory.
|
||||
@@ -143,24 +156,26 @@ namespace snmalloc
|
||||
/**
|
||||
* Make a new memory provide for this PAL.
|
||||
*/
|
||||
static MemoryProviderStateMixin<PAL>* make() noexcept
|
||||
static MemoryProviderStateMixin* make() noexcept
|
||||
{
|
||||
// Temporary stack-based storage to start the allocator in.
|
||||
AddressSpaceManager<PAL> local{};
|
||||
ASM local_asm{};
|
||||
ArenaMap local_am{};
|
||||
|
||||
// Allocate permanent storage for the allocator usung temporary allocator
|
||||
MemoryProviderStateMixin<PAL>* allocated =
|
||||
local
|
||||
MemoryProviderStateMixin* allocated =
|
||||
local_asm
|
||||
.template reserve_with_left_over<true>(
|
||||
sizeof(MemoryProviderStateMixin<PAL>))
|
||||
.template as_static<MemoryProviderStateMixin<PAL>>()
|
||||
sizeof(MemoryProviderStateMixin), local_am)
|
||||
.template as_static<MemoryProviderStateMixin>()
|
||||
.unsafe_capptr;
|
||||
|
||||
if (allocated == nullptr)
|
||||
error("Failed to initialise system!");
|
||||
|
||||
// Move address range inside itself
|
||||
allocated->address_space = std::move(local);
|
||||
allocated->address_space = std::move(local_asm);
|
||||
allocated->arena_map = std::move(local_am);
|
||||
|
||||
// Register this allocator for low-memory call-backs
|
||||
if constexpr (pal_supports<LowMemoryNotification, PAL>)
|
||||
@@ -224,7 +239,7 @@ namespace snmalloc
|
||||
|
||||
class LowMemoryNotificationObject : public PalNotificationObject
|
||||
{
|
||||
MemoryProviderStateMixin<PAL>* memory_provider;
|
||||
MemoryProviderStateMixin* memory_provider;
|
||||
|
||||
/***
|
||||
* Method for callback object to perform lazy decommit.
|
||||
@@ -237,8 +252,7 @@ namespace snmalloc
|
||||
}
|
||||
|
||||
public:
|
||||
LowMemoryNotificationObject(
|
||||
MemoryProviderStateMixin<PAL>* memory_provider)
|
||||
LowMemoryNotificationObject(MemoryProviderStateMixin* memory_provider)
|
||||
: PalNotificationObject(&process), memory_provider(memory_provider)
|
||||
{}
|
||||
};
|
||||
@@ -254,7 +268,8 @@ namespace snmalloc
|
||||
// Cache line align
|
||||
size_t size = bits::align_up(sizeof(T), 64);
|
||||
size = bits::max(size, alignment);
|
||||
auto p = address_space.template reserve_with_left_over<true>(size);
|
||||
auto p =
|
||||
address_space.template reserve_with_left_over<true>(size, arena_map);
|
||||
if (p == nullptr)
|
||||
return nullptr;
|
||||
|
||||
@@ -268,7 +283,7 @@ namespace snmalloc
|
||||
{
|
||||
size_t size = bits::one_at_bit(SUPERSLAB_BITS) << large_class;
|
||||
peak_memory_used_bytes += size;
|
||||
return address_space.template reserve<committed>(size)
|
||||
return address_space.template reserve<committed>(size, arena_map)
|
||||
.template as_static<Largeslab>();
|
||||
}
|
||||
|
||||
@@ -282,6 +297,17 @@ namespace snmalloc
|
||||
size_t peak = peak_memory_used_bytes;
|
||||
return {peak - avail, peak};
|
||||
}
|
||||
|
||||
template<typename T, typename U, capptr_bounds B>
|
||||
SNMALLOC_FAST_PATH CapPtr<T, CBArena> capptr_amplify(CapPtr<U, B> r)
|
||||
{
|
||||
return arena_map.template capptr_amplify<T, U, B>(r);
|
||||
}
|
||||
|
||||
ArenaMap& arenamap()
|
||||
{
|
||||
return arena_map;
|
||||
}
|
||||
};
|
||||
|
||||
using Stats = AllocStats<NUM_SIZECLASSES, NUM_LARGE_CLASSES>;
|
||||
@@ -379,10 +405,19 @@ namespace snmalloc
|
||||
stats.superslab_push();
|
||||
memory_provider.push_large_stack(p, large_class);
|
||||
}
|
||||
|
||||
template<typename T = void, typename U, capptr_bounds B>
|
||||
SNMALLOC_FAST_PATH CapPtr<T, CBArena> capptr_amplify(CapPtr<U, B> r)
|
||||
{
|
||||
return memory_provider.template capptr_amplify<T, U, B>(r);
|
||||
}
|
||||
};
|
||||
|
||||
struct DefaultPrimAlloc;
|
||||
|
||||
#ifndef SNMALLOC_DEFAULT_MEMORY_PROVIDER
|
||||
# define SNMALLOC_DEFAULT_MEMORY_PROVIDER MemoryProviderStateMixin<Pal>
|
||||
# define SNMALLOC_DEFAULT_MEMORY_PROVIDER \
|
||||
MemoryProviderStateMixin<Pal, DefaultArenaMap<Pal, DefaultPrimAlloc>>
|
||||
#endif
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace snmalloc
|
||||
{
|
||||
private:
|
||||
friend Pooled<T>;
|
||||
template<SNMALLOC_CONCEPT(ConceptPAL) PAL>
|
||||
template<SNMALLOC_CONCEPT(ConceptPAL) PAL, typename ArenaMap>
|
||||
friend class MemoryProviderStateMixin;
|
||||
friend SNMALLOC_DEFAULT_MEMORY_PROVIDER;
|
||||
|
||||
|
||||
@@ -33,7 +33,9 @@ using namespace snmalloc;
|
||||
int main()
|
||||
{
|
||||
#ifndef SNMALLOC_PASS_THROUGH // Depends on snmalloc specific features
|
||||
auto& mp = *MemoryProviderStateMixin<DefaultPal>::make();
|
||||
auto& mp = *MemoryProviderStateMixin<
|
||||
DefaultPal,
|
||||
DefaultArenaMap<DefaultPal, DefaultPrimAlloc>>::make();
|
||||
|
||||
// 28 is large enough to produce a nested allocator.
|
||||
// It is also large enough for the example to run in.
|
||||
|
||||
@@ -43,14 +43,48 @@ namespace
|
||||
struct Sandbox
|
||||
{
|
||||
using NoOpPal = PALNoAlloc<DefaultPal>;
|
||||
|
||||
struct ArenaMap
|
||||
{
|
||||
/**
|
||||
* A pointer with authority to the entire sandbox region
|
||||
*/
|
||||
CapPtr<void, CBArena> arena_root;
|
||||
|
||||
/**
|
||||
* Amplify using arena_root; that is, exclusively within the sandbox.
|
||||
*/
|
||||
template<typename T = void, typename U, capptr_bounds B>
|
||||
SNMALLOC_FAST_PATH CapPtr<T, CBArena> capptr_amplify(CapPtr<U, B> r)
|
||||
{
|
||||
return Aal::capptr_rebound<T>(arena_root, r);
|
||||
}
|
||||
|
||||
/*
|
||||
* This class does not implement register_root; there should be no
|
||||
* attempts to call that function.
|
||||
*/
|
||||
};
|
||||
|
||||
/**
|
||||
* The MemoryProvider for sandbox-memory-backed Allocs, both inside and
|
||||
* outside the sandbox proper: no memory allocation operations and
|
||||
* amplification confined to sandbox memory.
|
||||
*/
|
||||
using NoOpMemoryProvider = MemoryProviderStateMixin<NoOpPal, ArenaMap>;
|
||||
|
||||
/**
|
||||
* Type for the allocator that lives outside of the sandbox and allocates
|
||||
* sandbox-owned memory.
|
||||
* This Allocator, by virtue of having its amplification confined to
|
||||
* the sandbox, can be used to free only allocations made from sandbox
|
||||
* memory. It (insecurely) routes messages to in-sandbox snmallocs,
|
||||
* though, so it can free any sandbox-backed snmalloc allocation.
|
||||
*/
|
||||
using ExternalAlloc = Allocator<
|
||||
never_init,
|
||||
no_op_init,
|
||||
MemoryProviderStateMixin<NoOpPal>,
|
||||
NoOpMemoryProvider,
|
||||
SNMALLOC_DEFAULT_CHUNKMAP,
|
||||
false>;
|
||||
/**
|
||||
@@ -71,7 +105,7 @@ namespace
|
||||
* likely be only one of these inside any given sandbox and so this would
|
||||
* not have to be per-instance state.
|
||||
*/
|
||||
MemoryProviderStateMixin<NoOpPal>* real_state;
|
||||
NoOpMemoryProvider* real_state;
|
||||
|
||||
/**
|
||||
* Pop an element from the large stack for the specified size class,
|
||||
@@ -106,6 +140,16 @@ namespace
|
||||
{
|
||||
return real_state->template reserve<committed>(large_class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Amplify by appealing to the real_state, which has our sandbox
|
||||
* ArenaMap implementation.
|
||||
*/
|
||||
template<typename T = void, typename U, capptr_bounds B>
|
||||
SNMALLOC_FAST_PATH CapPtr<T, CBArena> capptr_amplify(CapPtr<U, B> r)
|
||||
{
|
||||
return real_state->template capptr_amplify<T>(r);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -143,7 +187,7 @@ namespace
|
||||
/**
|
||||
* The memory provider for this sandbox.
|
||||
*/
|
||||
MemoryProviderStateMixin<NoOpPal> state;
|
||||
NoOpMemoryProvider state;
|
||||
|
||||
/**
|
||||
* The allocator for callers outside the sandbox to allocate memory inside.
|
||||
@@ -167,6 +211,9 @@ namespace
|
||||
sb_size - sizeof(SharedState)),
|
||||
alloc(state, SNMALLOC_DEFAULT_CHUNKMAP(), &shared_state->queue)
|
||||
{
|
||||
// Register the sandbox memory with the sandbox arenamap
|
||||
state.arenamap().arena_root = CapPtr<void, CBArena>(start);
|
||||
|
||||
auto* state_proxy = static_cast<MemoryProviderProxy*>(
|
||||
alloc.alloc(sizeof(MemoryProviderProxy)));
|
||||
state_proxy->real_state = &state;
|
||||
|
||||
@@ -41,7 +41,10 @@ int main()
|
||||
{
|
||||
setup();
|
||||
|
||||
MemoryProviderStateMixin<DefaultPal> mp;
|
||||
MemoryProviderStateMixin<
|
||||
DefaultPal,
|
||||
DefaultArenaMap<DefaultPal, DefaultPrimAlloc>>
|
||||
mp;
|
||||
|
||||
// 26 is large enough to produce a nested allocator.
|
||||
// It is also large enough for the example to run in.
|
||||
|
||||
Reference in New Issue
Block a user