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:
Peter Huene
2021-03-18 17:09:36 -07:00
parent 59dfe4b9f4
commit f8f51afac1
20 changed files with 343 additions and 292 deletions

View File

@@ -72,7 +72,7 @@ experimental_x64 = ["wasmtime-jit/experimental_x64"]
# Enables support for "async stores" as well as defining host functions as
# `async fn` and calling functions asynchronously.
async = ["wasmtime-fiber"]
async = ["wasmtime-fiber", "wasmtime-runtime/async"]
# Enables userfaultfd support in the runtime's pooling allocator when building on Linux
uffd = ["wasmtime-runtime/uffd"]

View File

@@ -1332,25 +1332,20 @@ impl Config {
match self.allocation_strategy {
InstanceAllocationStrategy::OnDemand => Ok(Box::new(OnDemandInstanceAllocator::new(
self.mem_creator.clone(),
#[cfg(feature = "async")]
self.async_stack_size,
))),
InstanceAllocationStrategy::Pooling {
strategy,
module_limits,
instance_limits,
} => {
} => Ok(Box::new(PoolingInstanceAllocator::new(
strategy.into(),
module_limits.into(),
instance_limits.into(),
#[cfg(feature = "async")]
let stack_size = self.async_stack_size;
#[cfg(not(feature = "async"))]
let stack_size = 0;
Ok(Box::new(PoolingInstanceAllocator::new(
strategy.into(),
module_limits.into(),
instance_limits.into(),
stack_size,
)?))
}
self.async_stack_size,
)?)),
}
}
}

View File

@@ -106,7 +106,7 @@ impl HostFunc {
impl Drop for HostFunc {
fn drop(&mut self) {
// Host functions are always allocated with the default (on-demand) allocator
unsafe { OnDemandInstanceAllocator::new(None).deallocate(&self.instance) }
unsafe { OnDemandInstanceAllocator::default().deallocate(&self.instance) }
}
}

View File

@@ -219,7 +219,7 @@ impl Store {
pub fn get<T: Any>(&self) -> Option<&T> {
let values = self.inner.context_values.borrow();
// Safety: a context value cannot be removed once added and therefore the addres is
// Safety: a context value cannot be removed once added and therefore the address is
// stable for the life of the store
values
.get(&TypeId::of::<T>())
@@ -740,9 +740,15 @@ impl Store {
debug_assert!(self.async_support());
debug_assert!(config.async_stack_size > 0);
type SuspendType = wasmtime_fiber::Suspend<Result<(), Trap>, (), Result<(), Trap>>;
let stack = self
.inner
.engine
.allocator()
.allocate_fiber_stack()
.map_err(|e| Trap::from(anyhow::Error::from(e)))?;
let mut slot = None;
let func = |keep_going, suspend: &SuspendType| {
let fiber = wasmtime_fiber::Fiber::new(stack, |keep_going, suspend| {
// First check and see if we were interrupted/dropped, and only
// continue if we haven't been.
keep_going?;
@@ -760,46 +766,19 @@ impl Store {
slot = Some(func());
Ok(())
};
let (fiber, stack) = match self.inner.engine.allocator().allocate_fiber_stack() {
Ok(stack) => {
// Use the returned stack and deallocate it when finished
(
unsafe {
wasmtime_fiber::Fiber::new_with_stack(stack, func)
.map_err(|e| Trap::from(anyhow::Error::from(e)))?
},
stack,
)
}
Err(wasmtime_runtime::FiberStackError::NotSupported) => {
// The allocator doesn't support custom fiber stacks for the current platform
// Request that the fiber itself allocate the stack
(
wasmtime_fiber::Fiber::new(config.async_stack_size, func)
.map_err(|e| Trap::from(anyhow::Error::from(e)))?,
std::ptr::null_mut(),
)
}
Err(e) => return Err(Trap::from(anyhow::Error::from(e))),
};
})
.map_err(|e| Trap::from(anyhow::Error::from(e)))?;
// Once we have the fiber representing our synchronous computation, we
// wrap that in a custom future implementation which does the
// translation from the future protocol to our fiber API.
FiberFuture {
fiber,
store: self,
stack,
}
.await?;
FiberFuture { fiber, store: self }.await?;
return Ok(slot.unwrap());
struct FiberFuture<'a> {
fiber: wasmtime_fiber::Fiber<'a, Result<(), Trap>, (), Result<(), Trap>>,
store: &'a Store,
stack: *mut u8,
}
impl Future for FiberFuture<'_> {
@@ -807,7 +786,7 @@ impl Store {
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
// We need to carry over this `cx` into our fiber's runtime
// for when it trys to poll sub-futures that are created. Doing
// for when it tries to poll sub-futures that are created. Doing
// this must be done unsafely, however, since `cx` is only alive
// for this one singular function call. Here we do a `transmute`
// to extend the lifetime of `Context` so it can be stored in
@@ -864,13 +843,12 @@ impl Store {
// callers that they shouldn't be doing that.
debug_assert!(result.is_ok());
}
if !self.stack.is_null() {
unsafe {
self.store
.engine()
.allocator()
.deallocate_fiber_stack(self.stack)
};
unsafe {
self.store
.engine()
.allocator()
.deallocate_fiber_stack(self.fiber.stack());
}
}
}
@@ -999,7 +977,7 @@ impl fmt::Debug for Store {
impl Drop for StoreInner {
fn drop(&mut self) {
let allocator = self.engine.allocator();
let ondemand = OnDemandInstanceAllocator::new(self.engine.config().mem_creator.clone());
let ondemand = OnDemandInstanceAllocator::default();
for instance in self.instances.borrow().iter() {
unsafe {
if instance.ondemand {

View File

@@ -62,22 +62,27 @@ fn create_handle(
imports.functions = func_imports;
unsafe {
let config = store.engine().config();
// Use the on-demand allocator when creating handles associated with host objects
// The configured instance allocator should only be used when creating module instances
// as we don't want host objects to count towards instance limits.
let handle = OnDemandInstanceAllocator::new(store.engine().config().mem_creator.clone())
.allocate(InstanceAllocationRequest {
module: Arc::new(module),
finished_functions: &finished_functions,
imports,
lookup_shared_signature: &|_| shared_signature_id.unwrap(),
host_state,
interrupts: store.interrupts(),
externref_activations_table: store.externref_activations_table()
as *const VMExternRefActivationsTable
as *mut _,
stack_map_registry: store.stack_map_registry() as *const StackMapRegistry as *mut _,
})?;
let handle = OnDemandInstanceAllocator::new(
config.mem_creator.clone(),
#[cfg(feature = "async")]
config.async_stack_size,
)
.allocate(InstanceAllocationRequest {
module: Arc::new(module),
finished_functions: &finished_functions,
imports,
lookup_shared_signature: &|_| shared_signature_id.unwrap(),
host_state,
interrupts: store.interrupts(),
externref_activations_table: store.externref_activations_table()
as *const VMExternRefActivationsTable
as *mut _,
stack_map_registry: store.stack_map_registry() as *const StackMapRegistry as *mut _,
})?;
Ok(store.add_instance(handle, true))
}

View File

@@ -276,7 +276,7 @@ pub fn create_function(
unsafe {
Ok((
OnDemandInstanceAllocator::new(None).allocate(InstanceAllocationRequest {
OnDemandInstanceAllocator::default().allocate(InstanceAllocationRequest {
module: Arc::new(module),
finished_functions: &finished_functions,
imports: Imports::default(),
@@ -308,7 +308,7 @@ pub unsafe fn create_raw_function(
finished_functions.push(func);
Ok(
OnDemandInstanceAllocator::new(None).allocate(InstanceAllocationRequest {
OnDemandInstanceAllocator::default().allocate(InstanceAllocationRequest {
module: Arc::new(module),
finished_functions: &finished_functions,
imports: Imports::default(),