Add a pooling allocator mode based on copy-on-write mappings of memfds.
As first suggested by Jan on the Zulip here [1], a cheap and effective way to obtain copy-on-write semantics of a "backing image" for a Wasm memory is to mmap a file with `MAP_PRIVATE`. The `memfd` mechanism provided by the Linux kernel allows us to create anonymous, in-memory-only files that we can use for this mapping, so we can construct the image contents on-the-fly then effectively create a CoW overlay. Furthermore, and importantly, `madvise(MADV_DONTNEED, ...)` will discard the CoW overlay, returning the mapping to its original state. By itself this is almost enough for a very fast instantiation-termination loop of the same image over and over, without changing the address space mapping at all (which is expensive). The only missing bit is how to implement heap *growth*. But here memfds can help us again: if we create another anonymous file and map it where the extended parts of the heap would go, we can take advantage of the fact that a `mmap()` mapping can be *larger than the file itself*, with accesses beyond the end generating a `SIGBUS`, and the fact that we can cheaply resize the file with `ftruncate`, even after a mapping exists. So we can map the "heap extension" file once with the maximum memory-slot size and grow the memfd itself as `memory.grow` operations occur. The above CoW technique and heap-growth technique together allow us a fastpath of `madvise()` and `ftruncate()` only when we re-instantiate the same module over and over, as long as we can reuse the same slot. This fastpath avoids all whole-process address-space locks in the Linux kernel, which should mean it is highly scalable. It also avoids the cost of copying data on read, as the `uffd` heap backend does when servicing pagefaults; the kernel's own optimized CoW logic (same as used by all file mmaps) is used instead. [1] https://bytecodealliance.zulipchat.com/#narrow/stream/206238-general/topic/Copy.20on.20write.20based.20instance.20reuse/near/266657772
This commit is contained in:
@@ -97,6 +97,29 @@ pub(crate) struct Instance {
|
||||
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
impl Instance {
|
||||
/// Helper for allocators; not a public API.
|
||||
pub(crate) fn create_raw(
|
||||
module: &Arc<Module>,
|
||||
wasm_data: &'static [u8],
|
||||
memories: PrimaryMap<DefinedMemoryIndex, Memory>,
|
||||
tables: PrimaryMap<DefinedTableIndex, Table>,
|
||||
host_state: Box<dyn Any + Send + Sync>,
|
||||
) -> Instance {
|
||||
Instance {
|
||||
module: module.clone(),
|
||||
offsets: VMOffsets::new(HostPtr, &module),
|
||||
memories,
|
||||
tables,
|
||||
dropped_elements: EntitySet::with_capacity(module.passive_elements.len()),
|
||||
dropped_data: EntitySet::with_capacity(module.passive_data_map.len()),
|
||||
host_state,
|
||||
wasm_data,
|
||||
vmctx: VMContext {
|
||||
_marker: std::marker::PhantomPinned,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to access various locations offset from our `*mut
|
||||
/// VMContext` object.
|
||||
unsafe fn vmctx_plus_offset<T>(&self, offset: u32) -> *mut T {
|
||||
|
||||
Reference in New Issue
Block a user