Skip to content

Commit 0f7431c

Browse files
committed
Implement creating fibers in the instance allocator.
This commit implements creating fibers in the instance allocator. The allocator will return "raw" (platform-dependent) fibers that `Store` can use to run async code on. The future pooling allocator will use these changes to create fibers from preallocated regions of memory on Linux and macOS. On Windows, native fiber implementations are always used.
1 parent e7e98cb commit 0f7431c

8 files changed

Lines changed: 114 additions & 44 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/fiber/src/lib.rs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ mod unix;
1414
#[cfg(unix)]
1515
use unix as imp;
1616

17+
pub use imp::RawFiber;
18+
1719
pub struct Fiber<'a, Resume, Yield, Return> {
18-
inner: imp::Fiber,
20+
inner: RawFiber,
1921
done: Cell<bool>,
2022
_phantom: PhantomData<&'a (Resume, Yield, Return)>,
2123
}
@@ -37,18 +39,35 @@ impl<'a, Resume, Yield, Return> Fiber<'a, Resume, Yield, Return> {
3739
/// Creates a new fiber which will execute `func` on a new native stack of
3840
/// size `stack_size`.
3941
///
42+
/// A stack size of `0` will use the default stack size for the current platform.
43+
///
4044
/// This function returns a `Fiber` which, when resumed, will execute `func`
4145
/// to completion. When desired the `func` can suspend itself via
4246
/// `Fiber::suspend`.
4347
pub fn new(
4448
stack_size: usize,
4549
func: impl FnOnce(Resume, &Suspend<Resume, Yield, Return>) -> Return + 'a,
4650
) -> io::Result<Fiber<'a, Resume, Yield, Return>> {
47-
Ok(Fiber {
48-
inner: imp::Fiber::new(stack_size, func)?,
51+
let fiber = RawFiber::new(stack_size)?;
52+
Ok(Self::new_from_raw(fiber, func))
53+
}
54+
55+
/// Creates a new fiber which will execute `func` on the given raw fiber.
56+
///
57+
/// This function returns a `Fiber` which, when resumed, will execute `func`
58+
/// to completion. When desired the `func` can suspend itself via
59+
/// `Fiber::suspend`.
60+
pub fn new_from_raw(
61+
fiber: RawFiber,
62+
func: impl FnOnce(Resume, &Suspend<Resume, Yield, Return>) -> Return + 'a,
63+
) -> Self {
64+
fiber.init(func);
65+
66+
Self {
67+
inner: fiber,
4968
done: Cell::new(false),
5069
_phantom: PhantomData,
51-
})
70+
}
5271
}
5372

5473
/// Resumes execution of this fiber.

crates/fiber/src/unix.rs

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,11 @@ use std::cell::Cell;
3434
use std::io;
3535
use std::ptr;
3636

37-
pub struct Fiber {
38-
// Description of the mmap region we own. This should be abstracted
39-
// eventually so we aren't personally mmap-ing this region.
40-
mmap: *mut libc::c_void,
41-
mmap_len: usize,
37+
pub struct RawFiber {
38+
// The top of the stack; for stacks allocated by the fiber implementation itself,
39+
// the base address of the allocation will be `top.sub(alloc_len.unwrap())`
40+
top_of_stack: *mut u8,
41+
alloc_len: Option<usize>,
4242
}
4343

4444
pub struct Suspend {
@@ -65,28 +65,37 @@ where
6565
}
6666
}
6767

68-
impl Fiber {
69-
pub fn new<F, A, B, C>(stack_size: usize, func: F) -> io::Result<Fiber>
68+
impl RawFiber {
69+
pub fn new(stack_size: usize) -> io::Result<Self> {
70+
Self::alloc_with_stack(stack_size)
71+
}
72+
73+
pub fn from_stack(top: *mut u8) -> Self {
74+
Self {
75+
top_of_stack: top,
76+
alloc_len: None,
77+
}
78+
}
79+
80+
pub(crate) fn init<F, A, B, C>(&self, func: F)
7081
where
7182
F: FnOnce(A, &super::Suspend<A, B, C>) -> C,
7283
{
73-
let fiber = Fiber::alloc_with_stack(stack_size)?;
7484
unsafe {
75-
// Initialize the top of the stack to be resumed from
76-
let top_of_stack = fiber.top_of_stack();
7785
let data = Box::into_raw(Box::new(func)).cast();
78-
wasmtime_fiber_init(top_of_stack, fiber_start::<F, A, B, C>, data);
79-
Ok(fiber)
86+
wasmtime_fiber_init(self.top_of_stack, fiber_start::<F, A, B, C>, data);
8087
}
8188
}
8289

83-
fn alloc_with_stack(stack_size: usize) -> io::Result<Fiber> {
90+
fn alloc_with_stack(stack_size: usize) -> io::Result<Self> {
8491
unsafe {
8592
// Round up our stack size request to the nearest multiple of the
8693
// page size.
8794
let page_size = libc::sysconf(libc::_SC_PAGESIZE) as usize;
8895
let stack_size = if stack_size == 0 {
89-
page_size
96+
// Default to an 8 MB stack size (common default for 64-bit Linux and macOS)
97+
// TODO: respect stack rlimit
98+
8 * 1024 * 1024
9099
} else {
91100
(stack_size + (page_size - 1)) & (!(page_size - 1))
92101
};
@@ -104,7 +113,10 @@ impl Fiber {
104113
if mmap == libc::MAP_FAILED {
105114
return Err(io::Error::last_os_error());
106115
}
107-
let ret = Fiber { mmap, mmap_len };
116+
let ret = Self {
117+
top_of_stack: mmap.cast::<u8>().add(mmap_len),
118+
alloc_len: Some(mmap_len),
119+
};
108120
let res = libc::mprotect(
109121
mmap.cast::<u8>().add(page_size).cast(),
110122
stack_size,
@@ -124,27 +136,24 @@ impl Fiber {
124136
// stack, otherwise known as our reserved slot for this information.
125137
//
126138
// In the diagram above this is updating address 0xAff8
127-
let top_of_stack = self.top_of_stack();
128-
let addr = top_of_stack.cast::<usize>().offset(-1);
139+
let addr = self.top_of_stack.cast::<usize>().offset(-1);
129140
addr.write(result as *const _ as usize);
130141

131-
wasmtime_fiber_switch(top_of_stack);
142+
wasmtime_fiber_switch(self.top_of_stack);
132143

133144
// null this out to help catch use-after-free
134145
addr.write(0);
135146
}
136147
}
137-
138-
unsafe fn top_of_stack(&self) -> *mut u8 {
139-
self.mmap.cast::<u8>().add(self.mmap_len)
140-
}
141148
}
142149

143-
impl Drop for Fiber {
150+
impl Drop for RawFiber {
144151
fn drop(&mut self) {
145152
unsafe {
146-
let ret = libc::munmap(self.mmap, self.mmap_len);
147-
debug_assert!(ret == 0);
153+
if let Some(alloc_len) = self.alloc_len {
154+
let ret = libc::munmap(self.top_of_stack.sub(alloc_len) as _, alloc_len);
155+
debug_assert!(ret == 0);
156+
}
148157
}
149158
}
150159
}

crates/fiber/src/windows.rs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use winapi::shared::minwindef::*;
66
use winapi::um::fibersapi::*;
77
use winapi::um::winbase::*;
88

9-
pub struct Fiber {
9+
pub struct RawFiber {
1010
fiber: LPVOID,
1111
state: Box<StartState>,
1212
}
@@ -37,14 +37,11 @@ where
3737
super::Suspend::<A, B, C>::execute(suspend, initial, *func);
3838
}
3939

40-
impl Fiber {
41-
pub fn new<F, A, B, C>(stack_size: usize, func: F) -> io::Result<Fiber>
42-
where
43-
F: FnOnce(A, &super::Suspend<A, B, C>) -> C,
44-
{
40+
impl RawFiber {
41+
pub fn new(stack_size: usize) -> io::Result<Self> {
4542
unsafe {
4643
let state = Box::new(StartState {
47-
initial_closure: Cell::new(Box::into_raw(Box::new(func)).cast()),
44+
initial_closure: Cell::new(ptr::null_mut()),
4845
parent: Cell::new(ptr::null_mut()),
4946
result_location: Cell::new(ptr::null()),
5047
});
@@ -57,11 +54,21 @@ impl Fiber {
5754
drop(Box::from_raw(state.initial_closure.get().cast::<F>()));
5855
Err(io::Error::last_os_error())
5956
} else {
60-
Ok(Fiber { fiber, state })
57+
Ok(Self { fiber, state })
6158
}
6259
}
6360
}
6461

62+
pub(crate) fn init<F, A, B, C>(&self, func: F)
63+
where
64+
F: FnOnce(A, &super::Suspend<A, B, C>) -> C,
65+
{
66+
assert!(self.state.initial_closure.is_null());
67+
self.state
68+
.initial_closure
69+
.set(Box::into_raw(Box::new(func)).cast());
70+
}
71+
6572
pub(crate) fn resume<A, B, C>(&self, result: &Cell<RunResult<A, B, C>>) {
6673
unsafe {
6774
let is_fiber = IsThreadAFiber() != 0;
@@ -89,7 +96,7 @@ impl Fiber {
8996
}
9097
}
9198

92-
impl Drop for Fiber {
99+
impl Drop for RawFiber {
93100
fn drop(&mut self) {
94101
unsafe {
95102
DeleteFiber(self.fiber);

crates/runtime/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ edition = "2018"
1313

1414
[dependencies]
1515
wasmtime-environ = { path = "../environ", version = "0.22.0" }
16+
wasmtime-fiber = { path = "../fiber", version = "0.22.0", optional = true }
1617
region = "2.1.0"
1718
libc = { version = "0.2.82", default-features = false }
1819
log = "0.4.8"
@@ -33,3 +34,9 @@ cc = "1.0"
3334

3435
[badges]
3536
maintenance = { status = "actively-developed" }
37+
38+
[features]
39+
default = ["async"]
40+
41+
# Enables support for "async" fibers in the instance allocator
42+
async = ["wasmtime-fiber"]

crates/runtime/src/instance/allocator.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,18 @@ pub enum InstantiationError {
7474
Trap(Trap),
7575
}
7676

77+
/// An error while creating a fiber.
78+
#[derive(Error, Debug)]
79+
pub enum FiberError {
80+
/// An I/O error occured while creating the fiber.
81+
#[error("I/O error: {0}")]
82+
Io(std::io::Error),
83+
84+
/// A limit on how many fibers are supported has been reached.
85+
#[error("Limit of {0} concurrent fibers has been reached")]
86+
Limit(u32),
87+
}
88+
7789
/// Represents a runtime instance allocator.
7890
///
7991
/// # Safety
@@ -127,6 +139,10 @@ pub unsafe trait InstanceAllocator: Send + Sync {
127139
///
128140
/// Use extreme care when deallocating an instance so that there are no dangling instance pointers.
129141
unsafe fn deallocate(&self, handle: &InstanceHandle);
142+
143+
/// Creates a fiber for executing async instances on.
144+
#[cfg(feature = "async")]
145+
fn create_fiber(&self) -> Result<wasmtime_fiber::RawFiber, FiberError>;
130146
}
131147

132148
unsafe fn initialize_vmcontext(
@@ -544,4 +560,10 @@ unsafe impl InstanceAllocator for OnDemandInstanceAllocator {
544560
ptr::drop_in_place(instance as *const Instance as *mut Instance);
545561
alloc::dealloc(instance as *const Instance as *mut _, layout);
546562
}
563+
564+
/// Creates a fiber for executing async instances on.
565+
#[cfg(feature = "async")]
566+
fn create_fiber(&self) -> Result<wasmtime_fiber::RawFiber, FiberError> {
567+
wasmtime_fiber::RawFiber::new(0).map_err(FiberError::Io)
568+
}
547569
}

crates/wasmtime/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,4 @@ experimental_x64 = ["wasmtime-jit/experimental_x64"]
7171

7272
# Enables support for "async stores" as well as defining host functions as
7373
# `async fn` and calling functions asynchronously.
74-
async = ["wasmtime-fiber"]
74+
async = ["wasmtime-runtime/async", "wasmtime-fiber"]

crates/wasmtime/src/store.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -713,10 +713,16 @@ impl Store {
713713
pub(crate) async fn on_fiber<R>(&self, func: impl FnOnce() -> R) -> Result<R, Trap> {
714714
debug_assert!(self.is_async());
715715

716-
// TODO: allocation of a fiber should be much more abstract where we
717-
// shouldn't be allocating huge stacks on every async wasm function call.
716+
// Create the raw fiber to run the function on
717+
let raw_fiber = self
718+
.engine()
719+
.config()
720+
.instance_allocator()
721+
.create_fiber()
722+
.map_err(|e| Trap::from(anyhow::Error::from(e)))?;
723+
718724
let mut slot = None;
719-
let fiber = wasmtime_fiber::Fiber::new(10 * 1024 * 1024, |keep_going, suspend| {
725+
let fiber = wasmtime_fiber::Fiber::new_from_raw(raw_fiber, |keep_going, suspend| {
720726
// First check and see if we were interrupted/dropped, and only
721727
// continue if we haven't been.
722728
keep_going?;
@@ -734,8 +740,7 @@ impl Store {
734740

735741
slot = Some(func());
736742
Ok(())
737-
})
738-
.map_err(|e| Trap::from(anyhow::Error::from(e)))?;
743+
});
739744

740745
// Once we have the fiber representing our synchronous computation, we
741746
// wrap that in a custom future implementation which does the

0 commit comments

Comments
 (0)