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:
Nathaniel Filardo
2020-11-20 04:02:45 +00:00
committed by Nathaniel Wesley Filardo
parent a9722667ab
commit 6a7e82463c
6 changed files with 164 additions and 31 deletions

View File

@@ -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)
{

View File

@@ -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
/**

View File

@@ -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;

View File

@@ -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.

View File

@@ -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;

View File

@@ -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.