You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

158 lines
5.0 KiB

#![cfg(all(not(target_os = "windows"), not(miri)))]
use anyhow::{bail, Context};
use std::{
alloc::{GlobalAlloc, Layout, System},
ops::Range,
ptr::NonNull,
sync::Arc,
};
use wasmtime::*;
fn align_up(v: usize, align: usize) -> usize {
return (v + (align - 1)) & (!(align - 1));
}
struct CustomStack {
base: NonNull<u8>,
len: usize,
}
unsafe impl Send for CustomStack {}
unsafe impl Sync for CustomStack {}
impl CustomStack {
fn new(base: NonNull<u8>, len: usize) -> Self {
CustomStack { base, len }
}
}
unsafe impl StackMemory for CustomStack {
fn top(&self) -> *mut u8 {
unsafe { self.base.as_ptr().add(self.len) }
}
fn range(&self) -> Range<usize> {
let base = self.base.as_ptr() as usize;
base..base + self.len
}
}
// A creator that allocates stacks on the heap instead of mmap'ing.
struct CustomStackCreator {
memory: NonNull<u8>,
size: usize,
layout: Layout,
}
unsafe impl Send for CustomStackCreator {}
unsafe impl Sync for CustomStackCreator {}
impl CustomStackCreator {
fn new() -> Result<Self> {
// 1MB
const MINIMUM_STACK_SIZE: usize = 1 * 1_024 * 1_024;
let page_size = rustix::param::page_size();
let size = align_up(MINIMUM_STACK_SIZE, page_size);
// Add an extra page for the guard page
let layout = Layout::from_size_align(size + page_size, page_size)
.context("unable to compute stack layout")?;
let memory = unsafe {
let mem = System.alloc(layout);
let notnull = NonNull::new(mem);
if let Some(mem) = notnull {
// It's required that stack memory is zeroed for wasmtime
libc::memset(mem.as_ptr().cast(), 0, layout.size());
// Mark guard page as protected
rustix::mm::mprotect(
mem.as_ptr().cast(),
page_size,
rustix::mm::MprotectFlags::empty(),
)?;
}
notnull
}
.context("unable to allocate stack memory")?;
Ok(CustomStackCreator {
memory,
size,
layout,
})
}
fn range(&self) -> Range<usize> {
let page_size = rustix::param::page_size();
let base = unsafe { self.memory.as_ptr().add(page_size) as usize };
base..base + self.size
}
}
impl Drop for CustomStackCreator {
fn drop(&mut self) {
let page_size = rustix::param::page_size();
unsafe {
// Unprotect the guard page as the allocator could reuse it.
rustix::mm::mprotect(
self.memory.as_ptr().cast(),
page_size,
rustix::mm::MprotectFlags::READ | rustix::mm::MprotectFlags::WRITE,
)
.unwrap();
System.dealloc(self.memory.as_ptr(), self.layout);
}
}
}
unsafe impl StackCreator for CustomStackCreator {
fn new_stack(&self, size: usize) -> Result<Box<dyn StackMemory>> {
if size != self.size {
bail!("must use the size we allocated for this stack memory creator");
}
let page_size = rustix::param::page_size();
// skip over the page size
let base_ptr = unsafe { self.memory.as_ptr().add(page_size) };
let base = NonNull::new(base_ptr).context("unable to compute stack base")?;
Ok(Box::new(CustomStack::new(base, self.size)))
}
}
fn config() -> (Store<()>, Arc<CustomStackCreator>) {
let stack_creator = Arc::new(CustomStackCreator::new().unwrap());
let mut config = Config::new();
config
.async_support(true)
.max_wasm_stack(stack_creator.size / 2)
.async_stack_size(stack_creator.size)
.with_host_stack(stack_creator.clone());
(
Store::new(&Engine::new(&config).unwrap(), ()),
stack_creator,
)
}
#[tokio::test]
async fn called_on_custom_heap_stack() -> Result<()> {
let (mut store, stack_creator) = config();
let module = Module::new(
store.engine(),
r#"
(module
(import "host" "callback" (func $callback (result i64)))
(func $f (result i64) (call $callback))
(export "f" (func $f))
)
"#,
)?;
let ty = FuncType::new([], [ValType::I64]);
let host_func = Func::new(&mut store, ty, move |_caller, _params, results| {
let foo = 42;
// output an address on the stack
results[0] = Val::I64((&foo as *const i32) as usize as i64);
Ok(())
});
let export = wasmtime::Extern::Func(host_func);
let instance = Instance::new_async(&mut store, &module, &[export]).await?;
let mut results = [Val::I64(0)];
instance
.get_func(&mut store, "f")
.context("missing function export")?
.call_async(&mut store, &[], &mut results)
.await?;
// Make sure the stack address we wrote was within our custom stack range
let stack_address = results[0].i64().unwrap() as usize;
assert!(stack_creator.range().contains(&stack_address));
Ok(())
}