Skip to content

Commit

Permalink
Increase alignment of empty mmap pointers
Browse files Browse the repository at this point in the history
This commit updates platform implementations of `Mmap::new_empty()` to
use a shared helper to create a pointer with a higher alignment than
one. This fixes a debug assert tripping in niche configurations of the
pooling allocator where if no virtual memory is given and table lazy
init is disabled then a module could trip a debug-only assert about a
slice being created from a raw pointer that is not properly aligned. The
fix here is to use a helper that starts with an over-aligned empty slice
which is then casted down to a byte slice.
  • Loading branch information
alexcrichton committed Oct 8, 2024
1 parent 1c5976a commit e7ac8cd
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 4 deletions.
2 changes: 1 addition & 1 deletion crates/wasmtime/src/runtime/vm/sys/custom/mmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub struct Mmap {
impl Mmap {
pub fn new_empty() -> Mmap {
Mmap {
memory: SendSyncPtr::from(&mut [][..]),
memory: crate::vm::sys::empty_mmap(),
}
}

Expand Down
2 changes: 1 addition & 1 deletion crates/wasmtime/src/runtime/vm/sys/miri/mmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub struct Mmap {
impl Mmap {
pub fn new_empty() -> Mmap {
Mmap {
memory: SendSyncPtr::from(&mut [][..]),
memory: crate::vm::sys::empty_mmap(),
}
}

Expand Down
27 changes: 27 additions & 0 deletions crates/wasmtime/src/runtime/vm/sys/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@

#![allow(clippy::cast_sign_loss)] // platforms too fiddly to worry about this

use crate::runtime::vm::SendSyncPtr;
use core::ptr::{self, NonNull};

/// What happens to a mapping after it is decommitted?
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum DecommitBehavior {
Expand All @@ -19,6 +22,30 @@ pub enum DecommitBehavior {
RestoreOriginalMapping,
}

fn empty_mmap() -> SendSyncPtr<[u8]> {
// Callers of this API assume that `.as_ptr()` below returns something
// page-aligned and non-null. This is because the pointer returned from
// that location is casted to other types which reside at a higher
// alignment than a byte for example. Despite the length being zero we
// still need to ensure that the pointer is suitably aligned.
//
// To handle that do a bit of trickery here to get the compiler to
// generate an empty array to a high-alignment type (here 4k which is
// the min page size we work with today). Then use this empty array as
// the source pointer for an empty byte slice. It's a bit wonky but this
// makes it such that the returned length is always zero (so this is
// safe) but the pointer is always 4096 or suitably aligned.
#[repr(C, align(4096))]
struct PageAligned;
let empty_page_alloc: &mut [PageAligned] = &mut [];
let empty = NonNull::new(ptr::slice_from_raw_parts_mut(
empty_page_alloc.as_mut_ptr().cast(),
0,
))
.unwrap();
SendSyncPtr::from(empty)
}

cfg_if::cfg_if! {
if #[cfg(miri)] {
mod miri;
Expand Down
2 changes: 1 addition & 1 deletion crates/wasmtime/src/runtime/vm/sys/unix/mmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub struct Mmap {
impl Mmap {
pub fn new_empty() -> Mmap {
Mmap {
memory: SendSyncPtr::from(&mut [][..]),
memory: crate::vm::sys::empty_mmap(),
}
}

Expand Down
2 changes: 1 addition & 1 deletion crates/wasmtime/src/runtime/vm/sys/windows/mmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub struct Mmap {
impl Mmap {
pub fn new_empty() -> Mmap {
Mmap {
memory: SendSyncPtr::from(&mut [][..]),
memory: crate::vm::sys::empty_mmap(),
is_file: false,
}
}
Expand Down
42 changes: 42 additions & 0 deletions tests/all/pooling_allocator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1215,3 +1215,45 @@ fn decommit_batching() -> Result<()> {

Ok(())
}

#[test]
fn tricky_empty_table_with_empty_virtual_memory_alloc() -> Result<()> {
// Configure the pooling allocator to have no access to virtual memory, e.g.
// no table elements but a single table. This should technically support a
// single empty table being allocated into it but virtual memory isn't
// actually allocated here.
let mut cfg = PoolingAllocationConfig::default();
cfg.table_elements(0);
cfg.total_memories(0);
cfg.total_tables(1);
cfg.total_stacks(0);
cfg.total_core_instances(1);
cfg.max_memory_size(0);

let mut c = Config::new();
c.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg));

// Disable lazy init to actually try to get this to do something interesting
// at runtime.
c.table_lazy_init(false);

let engine = Engine::new(&c)?;

// This module has a single empty table, with a single empty element
// segment. Nothing actually goes wrong here, it should instantiate
// successfully. Along the way though the empty mmap above will get viewed
// as an array-of-pointers, so everything internally should all line up to
// work ok.
let module = Module::new(
&engine,
r#"
(module
(table 0 funcref)
(elem (i32.const 0) func)
)
"#,
)?;
let mut store = Store::new(&engine, ());
Instance::new(&mut store, &module, &[])?;
Ok(())
}

0 comments on commit e7ac8cd

Please sign in to comment.