Split out fiber stacks from fibers.
This commit splits out a `FiberStack` from `Fiber`, allowing the instance allocator trait to return `FiberStack` rather than raw stack pointers. This keeps the stack creation mostly in `wasmtime_fiber`, but now the on-demand instance allocator can make use of it. The instance allocators no longer have to return a "not supported" error to indicate that the store should allocate its own fiber stack. This includes a bunch of cleanup in the instance allocator to scope stacks to the new "async" feature in the runtime. Closes #2708.
This commit is contained in:
@@ -68,7 +68,7 @@ FUNCTION(wasmtime_fiber_init):
|
||||
|
||||
// And then we specify the stack pointer resumption should begin at. Our
|
||||
// `wasmtime_fiber_switch` function consumes 6 registers plus a return
|
||||
// pointer, and the top 16 bytes aree resereved, so that's:
|
||||
// pointer, and the top 16 bytes are reserved, so that's:
|
||||
//
|
||||
// (6 + 1) * 16 + 16 = 0x48
|
||||
lea -0x48(%rdi), %rax
|
||||
|
||||
@@ -14,7 +14,38 @@ mod unix;
|
||||
#[cfg(unix)]
|
||||
use unix as imp;
|
||||
|
||||
/// Represents an execution stack to use for a fiber.
|
||||
#[derive(Debug)]
|
||||
pub struct FiberStack(imp::FiberStack);
|
||||
|
||||
impl FiberStack {
|
||||
/// Creates a new fiber stack of the given size.
|
||||
pub fn new(size: usize) -> io::Result<Self> {
|
||||
Ok(Self(imp::FiberStack::new(size)?))
|
||||
}
|
||||
|
||||
/// Creates a new fiber stack with the given pointer to the top of the stack.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This is unsafe because there is no validation of the given stack pointer.
|
||||
///
|
||||
/// The caller must properly allocate the stack space with a guard page and
|
||||
/// make the pages accessible for correct behavior.
|
||||
pub unsafe fn from_top_ptr(top: *mut u8) -> io::Result<Self> {
|
||||
Ok(Self(imp::FiberStack::from_top_ptr(top)?))
|
||||
}
|
||||
|
||||
/// Gets the top of the stack.
|
||||
///
|
||||
/// Returns `None` if the platform does not support getting the top of the stack.
|
||||
pub fn top(&self) -> Option<*mut u8> {
|
||||
self.0.top()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Fiber<'a, Resume, Yield, Return> {
|
||||
stack: FiberStack,
|
||||
inner: imp::Fiber,
|
||||
done: Cell<bool>,
|
||||
_phantom: PhantomData<&'a (Resume, Yield, Return)>,
|
||||
@@ -34,39 +65,20 @@ enum RunResult<Resume, Yield, Return> {
|
||||
}
|
||||
|
||||
impl<'a, Resume, Yield, Return> Fiber<'a, Resume, Yield, Return> {
|
||||
/// Creates a new fiber which will execute `func` on a new native stack of
|
||||
/// size `stack_size`.
|
||||
/// Creates a new fiber which will execute `func` on the given stack.
|
||||
///
|
||||
/// This function returns a `Fiber` which, when resumed, will execute `func`
|
||||
/// to completion. When desired the `func` can suspend itself via
|
||||
/// `Fiber::suspend`.
|
||||
pub fn new(
|
||||
stack_size: usize,
|
||||
stack: FiberStack,
|
||||
func: impl FnOnce(Resume, &Suspend<Resume, Yield, Return>) -> Return + 'a,
|
||||
) -> io::Result<Fiber<'a, Resume, Yield, Return>> {
|
||||
Ok(Fiber {
|
||||
inner: imp::Fiber::new(stack_size, func)?,
|
||||
done: Cell::new(false),
|
||||
_phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
) -> io::Result<Self> {
|
||||
let inner = imp::Fiber::new(&stack.0, func)?;
|
||||
|
||||
/// Creates a new fiber with existing stack space that will execute `func`.
|
||||
///
|
||||
/// This function returns a `Fiber` which, when resumed, will execute `func`
|
||||
/// to completion. When desired the `func` can suspend itself via
|
||||
/// `Fiber::suspend`.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must properly allocate the stack space with a guard page and
|
||||
/// make the pages accessible for correct behavior.
|
||||
pub unsafe fn new_with_stack(
|
||||
top_of_stack: *mut u8,
|
||||
func: impl FnOnce(Resume, &Suspend<Resume, Yield, Return>) -> Return + 'a,
|
||||
) -> io::Result<Fiber<'a, Resume, Yield, Return>> {
|
||||
Ok(Fiber {
|
||||
inner: imp::Fiber::new_with_stack(top_of_stack, func)?,
|
||||
Ok(Self {
|
||||
stack,
|
||||
inner,
|
||||
done: Cell::new(false),
|
||||
_phantom: PhantomData,
|
||||
})
|
||||
@@ -90,7 +102,7 @@ impl<'a, Resume, Yield, Return> Fiber<'a, Resume, Yield, Return> {
|
||||
pub fn resume(&self, val: Resume) -> Result<Return, Yield> {
|
||||
assert!(!self.done.replace(true), "cannot resume a finished fiber");
|
||||
let result = Cell::new(RunResult::Resuming(val));
|
||||
self.inner.resume(&result);
|
||||
self.inner.resume(&self.stack.0, &result);
|
||||
match result.into_inner() {
|
||||
RunResult::Resuming(_) | RunResult::Executing => unreachable!(),
|
||||
RunResult::Yield(y) => {
|
||||
@@ -106,6 +118,11 @@ impl<'a, Resume, Yield, Return> Fiber<'a, Resume, Yield, Return> {
|
||||
pub fn done(&self) -> bool {
|
||||
self.done.get()
|
||||
}
|
||||
|
||||
/// Gets the stack associated with this fiber.
|
||||
pub fn stack(&self) -> &FiberStack {
|
||||
&self.stack
|
||||
}
|
||||
}
|
||||
|
||||
impl<Resume, Yield, Return> Suspend<Resume, Yield, Return> {
|
||||
@@ -148,18 +165,18 @@ impl<A, B, C> Drop for Fiber<'_, A, B, C> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Fiber;
|
||||
use super::{Fiber, FiberStack};
|
||||
use std::cell::Cell;
|
||||
use std::panic::{self, AssertUnwindSafe};
|
||||
use std::rc::Rc;
|
||||
|
||||
#[test]
|
||||
fn small_stacks() {
|
||||
Fiber::<(), (), ()>::new(0, |_, _| {})
|
||||
Fiber::<(), (), ()>::new(FiberStack::new(0).unwrap(), |_, _| {})
|
||||
.unwrap()
|
||||
.resume(())
|
||||
.unwrap();
|
||||
Fiber::<(), (), ()>::new(1, |_, _| {})
|
||||
Fiber::<(), (), ()>::new(FiberStack::new(1).unwrap(), |_, _| {})
|
||||
.unwrap()
|
||||
.resume(())
|
||||
.unwrap();
|
||||
@@ -169,7 +186,7 @@ mod tests {
|
||||
fn smoke() {
|
||||
let hit = Rc::new(Cell::new(false));
|
||||
let hit2 = hit.clone();
|
||||
let fiber = Fiber::<(), (), ()>::new(1024 * 1024, move |_, _| {
|
||||
let fiber = Fiber::<(), (), ()>::new(FiberStack::new(1024 * 1024).unwrap(), move |_, _| {
|
||||
hit2.set(true);
|
||||
})
|
||||
.unwrap();
|
||||
@@ -182,7 +199,7 @@ mod tests {
|
||||
fn suspend_and_resume() {
|
||||
let hit = Rc::new(Cell::new(false));
|
||||
let hit2 = hit.clone();
|
||||
let fiber = Fiber::<(), (), ()>::new(1024 * 1024, move |_, s| {
|
||||
let fiber = Fiber::<(), (), ()>::new(FiberStack::new(1024 * 1024).unwrap(), move |_, s| {
|
||||
s.suspend(());
|
||||
hit2.set(true);
|
||||
s.suspend(());
|
||||
@@ -219,14 +236,15 @@ mod tests {
|
||||
}
|
||||
|
||||
fn run_test() {
|
||||
let fiber = Fiber::<(), (), ()>::new(1024 * 1024, move |(), s| {
|
||||
assert_contains_host();
|
||||
s.suspend(());
|
||||
assert_contains_host();
|
||||
s.suspend(());
|
||||
assert_contains_host();
|
||||
})
|
||||
.unwrap();
|
||||
let fiber =
|
||||
Fiber::<(), (), ()>::new(FiberStack::new(1024 * 1024).unwrap(), move |(), s| {
|
||||
assert_contains_host();
|
||||
s.suspend(());
|
||||
assert_contains_host();
|
||||
s.suspend(());
|
||||
assert_contains_host();
|
||||
})
|
||||
.unwrap();
|
||||
assert!(fiber.resume(()).is_err());
|
||||
assert!(fiber.resume(()).is_err());
|
||||
assert!(fiber.resume(()).is_ok());
|
||||
@@ -239,11 +257,12 @@ mod tests {
|
||||
fn panics_propagated() {
|
||||
let a = Rc::new(Cell::new(false));
|
||||
let b = SetOnDrop(a.clone());
|
||||
let fiber = Fiber::<(), (), ()>::new(1024 * 1024, move |(), _s| {
|
||||
drop(&b);
|
||||
panic!();
|
||||
})
|
||||
.unwrap();
|
||||
let fiber =
|
||||
Fiber::<(), (), ()>::new(FiberStack::new(1024 * 1024).unwrap(), move |(), _s| {
|
||||
drop(&b);
|
||||
panic!();
|
||||
})
|
||||
.unwrap();
|
||||
assert!(panic::catch_unwind(AssertUnwindSafe(|| fiber.resume(()))).is_err());
|
||||
assert!(a.get());
|
||||
|
||||
@@ -258,7 +277,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn suspend_and_resume_values() {
|
||||
let fiber = Fiber::new(1024 * 1024, move |first, s| {
|
||||
let fiber = Fiber::new(FiberStack::new(1024 * 1024).unwrap(), move |first, s| {
|
||||
assert_eq!(first, 2.0);
|
||||
assert_eq!(s.suspend(4), 3.0);
|
||||
"hello".to_string()
|
||||
|
||||
@@ -34,17 +34,81 @@ use std::cell::Cell;
|
||||
use std::io;
|
||||
use std::ptr;
|
||||
|
||||
pub struct Fiber {
|
||||
#[derive(Debug)]
|
||||
pub struct FiberStack {
|
||||
// The top of the stack; for stacks allocated by the fiber implementation itself,
|
||||
// the base address of the allocation will be `top_of_stack.sub(alloc_len.unwrap())`
|
||||
top_of_stack: *mut u8,
|
||||
alloc_len: Option<usize>,
|
||||
// the base address of the allocation will be `top.sub(len.unwrap())`
|
||||
top: *mut u8,
|
||||
// The length of the stack; `None` when the stack was not created by this implementation.
|
||||
len: Option<usize>,
|
||||
}
|
||||
|
||||
pub struct Suspend {
|
||||
top_of_stack: *mut u8,
|
||||
impl FiberStack {
|
||||
pub fn new(size: usize) -> io::Result<Self> {
|
||||
unsafe {
|
||||
// Round up our stack size request to the nearest multiple of the
|
||||
// page size.
|
||||
let page_size = libc::sysconf(libc::_SC_PAGESIZE) as usize;
|
||||
let size = if size == 0 {
|
||||
page_size
|
||||
} else {
|
||||
(size + (page_size - 1)) & (!(page_size - 1))
|
||||
};
|
||||
|
||||
// Add in one page for a guard page and then ask for some memory.
|
||||
let mmap_len = size + page_size;
|
||||
let mmap = libc::mmap(
|
||||
ptr::null_mut(),
|
||||
mmap_len,
|
||||
libc::PROT_NONE,
|
||||
libc::MAP_ANON | libc::MAP_PRIVATE,
|
||||
-1,
|
||||
0,
|
||||
);
|
||||
if mmap == libc::MAP_FAILED {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
if libc::mprotect(
|
||||
mmap.cast::<u8>().add(page_size).cast(),
|
||||
size,
|
||||
libc::PROT_READ | libc::PROT_WRITE,
|
||||
) != 0
|
||||
{
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
top: mmap.cast::<u8>().add(mmap_len),
|
||||
len: Some(mmap_len),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn from_top_ptr(top: *mut u8) -> io::Result<Self> {
|
||||
Ok(Self { top, len: None })
|
||||
}
|
||||
|
||||
pub fn top(&self) -> Option<*mut u8> {
|
||||
Some(self.top)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FiberStack {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
if let Some(len) = self.len {
|
||||
let ret = libc::munmap(self.top.sub(len) as _, len);
|
||||
debug_assert!(ret == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Fiber;
|
||||
|
||||
pub struct Suspend(*mut u8);
|
||||
|
||||
extern "C" {
|
||||
fn wasmtime_fiber_init(
|
||||
top_of_stack: *mut u8,
|
||||
@@ -59,97 +123,35 @@ where
|
||||
F: FnOnce(A, &super::Suspend<A, B, C>) -> C,
|
||||
{
|
||||
unsafe {
|
||||
let inner = Suspend { top_of_stack };
|
||||
let inner = Suspend(top_of_stack);
|
||||
let initial = inner.take_resume::<A, B, C>();
|
||||
super::Suspend::<A, B, C>::execute(inner, initial, Box::from_raw(arg0.cast::<F>()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Fiber {
|
||||
pub fn new<F, A, B, C>(stack_size: usize, func: F) -> io::Result<Self>
|
||||
where
|
||||
F: FnOnce(A, &super::Suspend<A, B, C>) -> C,
|
||||
{
|
||||
let fiber = Self::alloc_with_stack(stack_size)?;
|
||||
fiber.init(func);
|
||||
Ok(fiber)
|
||||
}
|
||||
|
||||
pub fn new_with_stack<F, A, B, C>(top_of_stack: *mut u8, func: F) -> io::Result<Self>
|
||||
where
|
||||
F: FnOnce(A, &super::Suspend<A, B, C>) -> C,
|
||||
{
|
||||
let fiber = Self {
|
||||
top_of_stack,
|
||||
alloc_len: None,
|
||||
};
|
||||
|
||||
fiber.init(func);
|
||||
|
||||
Ok(fiber)
|
||||
}
|
||||
|
||||
fn init<F, A, B, C>(&self, func: F)
|
||||
pub fn new<F, A, B, C>(stack: &FiberStack, func: F) -> io::Result<Self>
|
||||
where
|
||||
F: FnOnce(A, &super::Suspend<A, B, C>) -> C,
|
||||
{
|
||||
unsafe {
|
||||
let data = Box::into_raw(Box::new(func)).cast();
|
||||
wasmtime_fiber_init(self.top_of_stack, fiber_start::<F, A, B, C>, data);
|
||||
wasmtime_fiber_init(stack.top, fiber_start::<F, A, B, C>, data);
|
||||
}
|
||||
|
||||
Ok(Self)
|
||||
}
|
||||
|
||||
fn alloc_with_stack(stack_size: usize) -> io::Result<Self> {
|
||||
unsafe {
|
||||
// Round up our stack size request to the nearest multiple of the
|
||||
// page size.
|
||||
let page_size = libc::sysconf(libc::_SC_PAGESIZE) as usize;
|
||||
let stack_size = if stack_size == 0 {
|
||||
page_size
|
||||
} else {
|
||||
(stack_size + (page_size - 1)) & (!(page_size - 1))
|
||||
};
|
||||
|
||||
// Add in one page for a guard page and then ask for some memory.
|
||||
let mmap_len = stack_size + page_size;
|
||||
let mmap = libc::mmap(
|
||||
ptr::null_mut(),
|
||||
mmap_len,
|
||||
libc::PROT_NONE,
|
||||
libc::MAP_ANON | libc::MAP_PRIVATE,
|
||||
-1,
|
||||
0,
|
||||
);
|
||||
if mmap == libc::MAP_FAILED {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
let ret = Self {
|
||||
top_of_stack: mmap.cast::<u8>().add(mmap_len),
|
||||
alloc_len: Some(mmap_len),
|
||||
};
|
||||
let res = libc::mprotect(
|
||||
mmap.cast::<u8>().add(page_size).cast(),
|
||||
stack_size,
|
||||
libc::PROT_READ | libc::PROT_WRITE,
|
||||
);
|
||||
if res != 0 {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn resume<A, B, C>(&self, result: &Cell<RunResult<A, B, C>>) {
|
||||
pub(crate) fn resume<A, B, C>(&self, stack: &FiberStack, result: &Cell<RunResult<A, B, C>>) {
|
||||
unsafe {
|
||||
// Store where our result is going at the very tip-top of the
|
||||
// stack, otherwise known as our reserved slot for this information.
|
||||
//
|
||||
// In the diagram above this is updating address 0xAff8
|
||||
let addr = self.top_of_stack.cast::<usize>().offset(-1);
|
||||
let addr = stack.top.cast::<usize>().offset(-1);
|
||||
addr.write(result as *const _ as usize);
|
||||
|
||||
wasmtime_fiber_switch(self.top_of_stack);
|
||||
wasmtime_fiber_switch(stack.top);
|
||||
|
||||
// null this out to help catch use-after-free
|
||||
addr.write(0);
|
||||
@@ -157,23 +159,12 @@ impl Fiber {
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Fiber {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
if let Some(alloc_len) = self.alloc_len {
|
||||
let ret = libc::munmap(self.top_of_stack.sub(alloc_len) as _, alloc_len);
|
||||
debug_assert!(ret == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Suspend {
|
||||
pub(crate) fn switch<A, B, C>(&self, result: RunResult<A, B, C>) -> A {
|
||||
unsafe {
|
||||
// Calculate 0xAff8 and then write to it
|
||||
(*self.result_location::<A, B, C>()).set(result);
|
||||
wasmtime_fiber_switch(self.top_of_stack);
|
||||
wasmtime_fiber_switch(self.0);
|
||||
self.take_resume::<A, B, C>()
|
||||
}
|
||||
}
|
||||
@@ -186,8 +177,8 @@ impl Suspend {
|
||||
}
|
||||
|
||||
unsafe fn result_location<A, B, C>(&self) -> *const Cell<RunResult<A, B, C>> {
|
||||
let ret = self.top_of_stack.cast::<*const u8>().offset(-1).read();
|
||||
let ret = self.0.cast::<*const u8>().offset(-1).read();
|
||||
assert!(!ret.is_null());
|
||||
return ret.cast();
|
||||
ret.cast()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,23 @@ use winapi::shared::winerror::ERROR_NOT_SUPPORTED;
|
||||
use winapi::um::fibersapi::*;
|
||||
use winapi::um::winbase::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FiberStack(usize);
|
||||
|
||||
impl FiberStack {
|
||||
pub fn new(size: usize) -> io::Result<Self> {
|
||||
Ok(Self(size))
|
||||
}
|
||||
|
||||
pub unsafe fn from_top_ptr(_top: *mut u8) -> io::Result<Self> {
|
||||
Err(io::Error::from_raw_os_error(ERROR_NOT_SUPPORTED as i32))
|
||||
}
|
||||
|
||||
pub fn top(&self) -> Option<*mut u8> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Fiber {
|
||||
fiber: LPVOID,
|
||||
state: Box<StartState>,
|
||||
@@ -41,7 +58,7 @@ where
|
||||
}
|
||||
|
||||
impl Fiber {
|
||||
pub fn new<F, A, B, C>(stack_size: usize, func: F) -> io::Result<Self>
|
||||
pub fn new<F, A, B, C>(stack: &FiberStack, func: F) -> io::Result<Self>
|
||||
where
|
||||
F: FnOnce(A, &super::Suspend<A, B, C>) -> C,
|
||||
{
|
||||
@@ -51,30 +68,25 @@ impl Fiber {
|
||||
parent: Cell::new(ptr::null_mut()),
|
||||
result_location: Cell::new(ptr::null()),
|
||||
});
|
||||
|
||||
let fiber = CreateFiberEx(
|
||||
0,
|
||||
stack_size,
|
||||
stack.0,
|
||||
FIBER_FLAG_FLOAT_SWITCH,
|
||||
Some(fiber_start::<F, A, B, C>),
|
||||
&*state as *const StartState as *mut _,
|
||||
);
|
||||
|
||||
if fiber.is_null() {
|
||||
drop(Box::from_raw(state.initial_closure.get().cast::<F>()));
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(Self { fiber, state })
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
Ok(Self { fiber, state })
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_stack<F, A, B, C>(_top_of_stack: *mut u8, _func: F) -> io::Result<Self>
|
||||
where
|
||||
F: FnOnce(A, &super::Suspend<A, B, C>) -> C,
|
||||
{
|
||||
Err(io::Error::from_raw_os_error(ERROR_NOT_SUPPORTED as i32))
|
||||
}
|
||||
|
||||
pub(crate) fn resume<A, B, C>(&self, result: &Cell<RunResult<A, B, C>>) {
|
||||
pub(crate) fn resume<A, B, C>(&self, _stack: &FiberStack, result: &Cell<RunResult<A, B, C>>) {
|
||||
unsafe {
|
||||
let is_fiber = IsThreadAFiber() != 0;
|
||||
let parent_fiber = if is_fiber {
|
||||
|
||||
Reference in New Issue
Block a user