From df0896572411572689e3b9cd0785b406837b2331 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sun, 9 Nov 2025 18:33:13 +0100 Subject: [PATCH 01/49] First stage --- lib/api/src/entities/function/mod.rs | 89 ++++++++++++++++++++++++++ lib/api/src/utils/native/typed_func.rs | 19 ++++++ 2 files changed, 108 insertions(+) diff --git a/lib/api/src/entities/function/mod.rs b/lib/api/src/entities/function/mod.rs index 76ef91e7dfa..c72be3b2e96 100644 --- a/lib/api/src/entities/function/mod.rs +++ b/lib/api/src/entities/function/mod.rs @@ -10,6 +10,8 @@ pub use host::*; pub(crate) mod env; pub use env::*; +use std::future::Future; + use wasmer_types::{FunctionType, RawValue}; use crate::{ @@ -147,6 +149,77 @@ impl Function { Self(BackendFunction::new_typed_with_env(store, env, func)) } + /// Creates a new async host `Function` (dynamic) with the provided signature. + /// + /// This API is a placeholder for JSPI support. It will be wired to the + /// runtime implementation in a follow-up change. + #[allow(unused_variables)] + pub fn new_async(store: &mut impl AsStoreMut, ty: FT, func: F) -> Self + where + FT: Into, + F: Fn(&[Value]) -> Fut + 'static + Send + Sync, + Fut: Future, RuntimeError>> + 'static + Send, + { + let _ = store.as_store_ref(); + let _ = ty.into(); + let _ = &func; + unimplemented!("Function::new_async is not implemented yet") + } + + /// Creates a new async host `Function` (dynamic) with the provided signature + /// and environment. + #[allow(unused_variables)] + pub fn new_with_env_async( + store: &mut impl AsStoreMut, + env: &FunctionEnv, + ty: FT, + func: F, + ) -> Self + where + FT: Into, + F: Fn(FunctionEnvMut, &[Value]) -> Fut + 'static + Send + Sync, + Fut: Future, RuntimeError>> + 'static + Send, + { + let _ = store.as_store_ref(); + let _ = env; + let _ = ty.into(); + let _ = &func; + unimplemented!("Function::new_with_env_async is not implemented yet") + } + + /// Creates a new async host `Function` from a native typed function. + #[allow(unused_variables)] + pub fn new_typed_async(store: &mut impl AsStoreMut, func: F) -> Self + where + F: Fn(Args) -> Fut + 'static + Send + Sync, + Fut: Future + 'static + Send, + Args: WasmTypeList, + Rets: WasmTypeList, + { + let _ = store.as_store_ref(); + let _ = &func; + unimplemented!("Function::new_typed_async is not implemented yet") + } + + /// Creates a new async host `Function` with an environment from a typed function. + #[allow(unused_variables)] + pub fn new_typed_with_env_async( + store: &mut impl AsStoreMut, + env: &FunctionEnv, + func: F, + ) -> Self + where + F: Fn(FunctionEnvMut, Args) -> Fut + 'static + Send + Sync, + Fut: Future + 'static + Send, + Args: WasmTypeList, + Rets: WasmTypeList, + { + let _ = store.as_store_ref(); + let _ = env; + let _ = &func; + unimplemented!("Function::new_typed_with_env_async is not implemented yet") + } + /// Returns the [`FunctionType`] of the `Function`. /// /// # Example @@ -250,6 +323,22 @@ impl Function { self.0.call(store, params) } + /// Calls the function asynchronously. + /// + /// This is a placeholder that will panic when awaited until JSPI support + /// is fully implemented. + pub fn call_async<'a>( + &'a self, + store: &'a mut impl AsStoreMut, + params: &'a [Value], + ) -> impl Future, RuntimeError>> + 'a { + async move { + let _ = store.as_store_ref(); + let _ = params; + unimplemented!("Function::call_async is not implemented yet") + } + } + #[doc(hidden)] #[allow(missing_docs)] pub fn call_raw( diff --git a/lib/api/src/utils/native/typed_func.rs b/lib/api/src/utils/native/typed_func.rs index edc32439c32..6ad2e8e2190 100644 --- a/lib/api/src/utils/native/typed_func.rs +++ b/lib/api/src/utils/native/typed_func.rs @@ -11,6 +11,7 @@ use crate::{ AsStoreMut, BackendStore, FromToNativeWasmType, Function, NativeWasmTypeInto, RuntimeError, WasmTypeList, store::AsStoreRef, }; +use std::future::Future; use std::marker::PhantomData; use wasmer_types::RawValue; @@ -78,6 +79,24 @@ macro_rules! impl_native_traits { } } + /// Call the typed func asynchronously. + #[allow(unused_mut)] + #[allow(clippy::too_many_arguments)] + pub fn call_async<'a>( + &'a self, + store: &'a mut impl AsStoreMut, + $( $x: $x, )* + ) -> impl Future> + 'a + where + $( $x: FromToNativeWasmType, )* + { + async move { + let _ = store.as_store_ref(); + let _ = ($($x,)*); + unimplemented!("TypedFunction::call_async is not implemented yet") + } + } + #[doc(hidden)] #[allow(missing_docs)] #[allow(unused_mut)] From 4b8b3270931a645ac8d3d58d7691af557e9a3a54 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sun, 9 Nov 2025 19:21:11 +0100 Subject: [PATCH 02/49] Sys implementation of the async functions --- Cargo.lock | 1 + Cargo.toml | 1 + lib/api/Cargo.toml | 3 +- lib/api/src/backend/sys/async_runtime.rs | 165 ++++++++++++++++++ .../src/backend/sys/entities/function/mod.rs | 39 +++++ lib/api/src/backend/sys/mod.rs | 1 + lib/api/src/entities/function/inner.rs | 96 ++++++++++ lib/api/src/entities/function/mod.rs | 39 ++--- lib/vm/Cargo.toml | 2 +- 9 files changed, 323 insertions(+), 24 deletions(-) create mode 100644 lib/api/src/backend/sys/async_runtime.rs diff --git a/Cargo.lock b/Cargo.lock index d41d172bc08..71b6153d179 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6954,6 +6954,7 @@ dependencies = [ "bytes", "cfg-if", "cmake", + "corosensei", "derive_more 2.0.1", "hashbrown 0.11.2", "indexmap 2.12.0", diff --git a/Cargo.toml b/Cargo.toml index d007713ca64..e3f0e7b38fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -125,6 +125,7 @@ smallvec = "1.6" region = "3.0" once_cell = "1.17.1" num_enum = "0.7.3" +corosensei = "0.3.0" dashmap = "6.0.1" http = "1.0.0" hyper = "1" diff --git a/lib/api/Cargo.toml b/lib/api/Cargo.toml index adac3b151e5..57de314ebd9 100644 --- a/lib/api/Cargo.toml +++ b/lib/api/Cargo.toml @@ -60,6 +60,7 @@ wasmer-compiler = { path = "../compiler", version = "=6.1.0", optional = true } wasmer-derive = { path = "../derive", version = "=6.1.0" } wasmer-types = { path = "../types", version = "=6.1.0" } target-lexicon = { workspace = true, default-features = false } +corosensei = { workspace = true, optional = true } # - Optional dependencies for `sys`. wasmer-compiler-singlepass = { path = "../compiler-singlepass", version = "=6.1.0", optional = true } wasmer-compiler-cranelift = { path = "../compiler-cranelift", version = "=6.1.0", optional = true } @@ -125,7 +126,7 @@ artifact-size = [ ] # Features for `sys`. -sys = ["std", "dep:wasmer-vm", "dep:wasmer-compiler"] +sys = ["std", "dep:wasmer-vm", "dep:wasmer-compiler", "dep:corosensei"] sys-default = ["sys", "wat", "cranelift"] # - Compilers. diff --git a/lib/api/src/backend/sys/async_runtime.rs b/lib/api/src/backend/sys/async_runtime.rs new file mode 100644 index 00000000000..fe6d04eb764 --- /dev/null +++ b/lib/api/src/backend/sys/async_runtime.rs @@ -0,0 +1,165 @@ +use std::{ + cell::Cell, + future::Future, + marker::PhantomData, + pin::Pin, + ptr, + task::{Context, Poll}, +}; + +use corosensei::{Coroutine, CoroutineResult, Yielder}; + +use super::entities::function::Function as SysFunction; +use crate::{AsStoreMut, RuntimeError, Value}; + +type HostFuture = Pin, RuntimeError>> + Send + 'static>>; + +pub(crate) struct AsyncCallFuture<'a, S: AsStoreMut + 'static> { + coroutine: Option, RuntimeError>>>, + pending_future: Option, + next_resume: Option, + result: Option, RuntimeError>>, + _marker: PhantomData<&'a mut S>, +} + +impl<'a, S> AsyncCallFuture<'a, S> +where + S: AsStoreMut + 'static, +{ + pub(crate) fn new(function: SysFunction, store: &'a mut S, params: Vec) -> Self { + let store_ptr = store as *mut S; + let coroutine = + Coroutine::new(move |yielder: &Yielder, _resume| { + let ctx_state = AsyncContextState::new(yielder); + let _guard = ctx_state.enter(); + let result = { + let store_ref = unsafe { &mut *store_ptr }; + function.call(store_ref, ¶ms) + }; + result + }); + + Self { + coroutine: Some(coroutine), + pending_future: None, + next_resume: Some(AsyncResume::Start), + result: None, + _marker: PhantomData, + } + } +} + +impl<'a, S> Future for AsyncCallFuture<'a, S> +where + S: AsStoreMut + 'static, +{ + type Output = Result, RuntimeError>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + loop { + if let Some(future) = self.pending_future.as_mut() { + match future.as_mut().poll(cx) { + Poll::Ready(result) => { + self.pending_future = None; + self.next_resume = Some(AsyncResume::HostFutureReady(result)); + } + Poll::Pending => return Poll::Pending, + } + } + + let resume_arg = self.next_resume.take().unwrap_or(AsyncResume::Start); + let coroutine = match self.coroutine.as_mut() { + Some(coro) => coro, + None => return Poll::Ready(self.result.take().expect("polled after completion")), + }; + match coroutine.resume(resume_arg) { + CoroutineResult::Yield(AsyncYield::HostFuture(fut)) => { + self.pending_future = Some(fut); + } + CoroutineResult::Return(result) => { + self.coroutine = None; + self.result = Some(result); + } + } + } + } +} + +thread_local! { + static CURRENT_CONTEXT: Cell<*const AsyncContextState> = const { Cell::new(ptr::null()) }; +} + +pub(crate) fn block_on_host_future(future: Fut) -> Result, RuntimeError> +where + Fut: Future, RuntimeError>> + Send + 'static, +{ + CURRENT_CONTEXT.with(|cell| { + let ptr = cell.get(); + if ptr.is_null() { + Err(RuntimeError::new( + "async host functions can only be used inside `call_async`", + )) + } else { + unsafe { (&*ptr).block_on_future(Box::pin(future)) } + } + }) +} + +pub(crate) fn call_function_async<'a, S>( + function: SysFunction, + store: &'a mut S, + params: Vec, +) -> AsyncCallFuture<'a, S> +where + S: AsStoreMut + 'static, +{ + AsyncCallFuture::new(function, store, params) +} + +enum AsyncYield { + HostFuture(HostFuture), +} + +enum AsyncResume { + Start, + HostFutureReady(Result, RuntimeError>), +} + +struct AsyncContextState { + yielder: *const Yielder, +} + +impl AsyncContextState { + fn new(yielder: &Yielder) -> Self { + Self { + yielder: yielder as *const _, + } + } + + fn enter(&self) -> AsyncContextGuard { + CURRENT_CONTEXT.with(|cell| { + let previous = cell.replace(self as *const _); + AsyncContextGuard { previous } + }) + } + + fn block_on_future(&self, future: HostFuture) -> Result, RuntimeError> { + let yielder = unsafe { &*self.yielder }; + match yielder.suspend(AsyncYield::HostFuture(future)) { + AsyncResume::HostFutureReady(result) => result, + AsyncResume::Start => unreachable!("coroutine resumed without start"), + } + } +} + +struct AsyncContextGuard { + previous: *const AsyncContextState, +} + +impl Drop for AsyncContextGuard { + fn drop(&mut self) { + CURRENT_CONTEXT.with(|cell| { + cell.set(self.previous); + }); + } +} diff --git a/lib/api/src/backend/sys/entities/function/mod.rs b/lib/api/src/backend/sys/entities/function/mod.rs index ca87731996d..c319903e73e 100644 --- a/lib/api/src/backend/sys/entities/function/mod.rs +++ b/lib/api/src/backend/sys/entities/function/mod.rs @@ -13,6 +13,7 @@ use crate::{ }; use std::panic::{self, AssertUnwindSafe}; use std::{cell::UnsafeCell, cmp::max, error::Error, ffi::c_void}; +use std::{future::Future, pin::Pin}; use wasmer_types::{NativeWasmType, RawValue}; use wasmer_vm::{ MaybeInstanceOwned, StoreHandle, VMCallerCheckedAnyfunc, VMContext, VMDynamicFunctionContext, @@ -20,6 +21,8 @@ use wasmer_vm::{ on_host_stack, raise_user_trap, resume_panic, wasmer_call_trampoline, }; +use crate::backend::sys::async_runtime::{block_on_host_future, call_function_async}; + #[cfg_attr(feature = "artifact-size", derive(loupe::MemoryUsage))] #[derive(Debug, Clone, PartialEq, Eq)] /// A WebAssembly `function` instance, in the `sys` runtime. @@ -127,6 +130,33 @@ impl Function { } } + pub(crate) fn new_async(store: &mut impl AsStoreMut, ty: FT, func: F) -> Self + where + FT: Into, + F: Fn(&[Value]) -> Fut + 'static + Send + Sync, + Fut: Future, RuntimeError>> + 'static + Send, + { + let env = FunctionEnv::new(&mut store.as_store_mut(), ()); + let wrapped = move |_env: FunctionEnvMut<()>, values: &[Value]| func(values); + Self::new_with_env_async(store, &env, ty, wrapped) + } + + pub(crate) fn new_with_env_async( + store: &mut impl AsStoreMut, + env: &FunctionEnv, + ty: FT, + func: F, + ) -> Self + where + FT: Into, + F: Fn(FunctionEnvMut, &[Value]) -> Fut + 'static + Send + Sync, + Fut: Future, RuntimeError>> + 'static + Send, + { + Self::new_with_env(store, env, ty, move |env_mut, values| { + block_on_host_future(func(env_mut, values)) + }) + } + /// Creates a new host `Function` from a native function. pub(crate) fn new_typed(store: &mut impl AsStoreMut, func: F) -> Self where @@ -359,6 +389,15 @@ impl Function { Ok(results.into_boxed_slice()) } + pub(crate) fn call_async<'a>( + &self, + store: &'a mut (impl AsStoreMut + 'static), + params: Vec, + ) -> Pin, RuntimeError>> + 'a>> { + let function = self.clone(); + Box::pin(call_function_async(function, store, params)) + } + #[doc(hidden)] #[allow(missing_docs)] pub(crate) fn call_raw( diff --git a/lib/api/src/backend/sys/mod.rs b/lib/api/src/backend/sys/mod.rs index 9417ed3227c..34b48cc3865 100644 --- a/lib/api/src/backend/sys/mod.rs +++ b/lib/api/src/backend/sys/mod.rs @@ -1,5 +1,6 @@ //! Data types, functions and traits for the `sys` runtime. +pub(crate) mod async_runtime; pub(crate) mod entities; pub(crate) mod error; pub(crate) mod tunables; diff --git a/lib/api/src/entities/function/inner.rs b/lib/api/src/entities/function/inner.rs index 2a38d6ad1b2..6c492d82726 100644 --- a/lib/api/src/entities/function/inner.rs +++ b/lib/api/src/entities/function/inner.rs @@ -1,3 +1,5 @@ +use std::{future::Future, pin::Pin}; + use wasmer_types::{FunctionType, RawValue}; use crate::{ @@ -247,6 +249,63 @@ impl BackendFunction { } } + #[inline] + pub fn new_async(store: &mut impl AsStoreMut, ty: FT, func: F) -> Self + where + FT: Into, + F: Fn(&[Value]) -> Fut + 'static + Send + Sync, + Fut: Future, RuntimeError>> + 'static + Send, + { + match &store.as_store_mut().inner.store { + #[cfg(feature = "sys")] + crate::BackendStore::Sys(_) => Self::Sys( + crate::backend::sys::entities::function::Function::new_async(store, ty, func), + ), + #[cfg(feature = "wamr")] + crate::BackendStore::Wamr(_) => unsupported_async_backend("wamr"), + #[cfg(feature = "wasmi")] + crate::BackendStore::Wasmi(_) => unsupported_async_backend("wasmi"), + #[cfg(feature = "v8")] + crate::BackendStore::V8(_) => unsupported_async_backend("v8"), + #[cfg(feature = "js")] + crate::BackendStore::Js(_) => unsupported_async_backend("js"), + #[cfg(feature = "jsc")] + crate::BackendStore::Jsc(_) => unsupported_async_backend("jsc"), + } + } + + #[inline] + pub fn new_with_env_async( + store: &mut impl AsStoreMut, + env: &FunctionEnv, + ty: FT, + func: F, + ) -> Self + where + FT: Into, + F: Fn(FunctionEnvMut, &[Value]) -> Fut + 'static + Send + Sync, + Fut: Future, RuntimeError>> + 'static + Send, + { + match &store.as_store_mut().inner.store { + #[cfg(feature = "sys")] + crate::BackendStore::Sys(_) => Self::Sys( + crate::backend::sys::entities::function::Function::new_with_env_async( + store, env, ty, func, + ), + ), + #[cfg(feature = "wamr")] + crate::BackendStore::Wamr(_) => unsupported_async_backend("wamr"), + #[cfg(feature = "wasmi")] + crate::BackendStore::Wasmi(_) => unsupported_async_backend("wasmi"), + #[cfg(feature = "v8")] + crate::BackendStore::V8(_) => unsupported_async_backend("v8"), + #[cfg(feature = "js")] + crate::BackendStore::Js(_) => unsupported_async_backend("js"), + #[cfg(feature = "jsc")] + crate::BackendStore::Jsc(_) => unsupported_async_backend("jsc"), + } + } + /// Returns the [`FunctionType`] of the `Function`. /// /// # Example @@ -371,6 +430,27 @@ impl BackendFunction { }) } + pub fn call_async<'a>( + &'a self, + store: &'a mut (impl AsStoreMut + 'static), + params: Vec, + ) -> Pin, RuntimeError>> + 'a>> { + match self { + #[cfg(feature = "sys")] + Self::Sys(f) => f.call_async(store, params), + #[cfg(feature = "wamr")] + Self::Wamr(_) => unsupported_async_future(), + #[cfg(feature = "wasmi")] + Self::Wasmi(_) => unsupported_async_future(), + #[cfg(feature = "v8")] + Self::V8(_) => unsupported_async_future(), + #[cfg(feature = "js")] + Self::Js(_) => unsupported_async_future(), + #[cfg(feature = "jsc")] + Self::Jsc(_) => unsupported_async_future(), + } + } + #[inline] pub(crate) fn vm_funcref(&self, store: &impl AsStoreRef) -> VMFuncRef { match self { @@ -605,6 +685,22 @@ impl BackendFunction { } } +#[cold] +fn unsupported_async_backend(backend: &str) -> ! { + panic!( + "async host functions are only supported with the `sys` backend (attempted on {backend})" + ) +} + +fn unsupported_async_future<'a>() +-> Pin, RuntimeError>> + 'a>> { + Box::pin(async { + Err(RuntimeError::new( + "async calls are only supported with the `sys` backend", + )) + }) +} + impl<'a> Exportable<'a> for BackendFunction { fn get_self_from_extern(_extern: &'a Extern) -> Result<&'a Self, ExportError> { match _extern { diff --git a/lib/api/src/entities/function/mod.rs b/lib/api/src/entities/function/mod.rs index c72be3b2e96..86df1286e17 100644 --- a/lib/api/src/entities/function/mod.rs +++ b/lib/api/src/entities/function/mod.rs @@ -10,7 +10,7 @@ pub use host::*; pub(crate) mod env; pub use env::*; -use std::future::Future; +use std::{future::Future, pin::Pin}; use wasmer_types::{FunctionType, RawValue}; @@ -21,6 +21,9 @@ use crate::{ vm::{VMExtern, VMExternFunction, VMFuncRef}, }; +#[cfg(feature = "sys")] +use crate::backend::sys::async_runtime::{block_on_host_future, call_function_async}; + /// A WebAssembly `function` instance. /// /// A function instance is the runtime representation of a function. @@ -151,24 +154,21 @@ impl Function { /// Creates a new async host `Function` (dynamic) with the provided signature. /// - /// This API is a placeholder for JSPI support. It will be wired to the - /// runtime implementation in a follow-up change. - #[allow(unused_variables)] + /// The provided closure returns a future that resolves to the function results. + /// When invoked synchronously (via [`Function::call`]) the future will run to + /// completion immediately. When invoked through [`Function::call_async`], the + /// future may suspend and resume according to JSPI semantics. pub fn new_async(store: &mut impl AsStoreMut, ty: FT, func: F) -> Self where FT: Into, F: Fn(&[Value]) -> Fut + 'static + Send + Sync, Fut: Future, RuntimeError>> + 'static + Send, { - let _ = store.as_store_ref(); - let _ = ty.into(); - let _ = &func; - unimplemented!("Function::new_async is not implemented yet") + Self(BackendFunction::new_async(store, ty, func)) } /// Creates a new async host `Function` (dynamic) with the provided signature /// and environment. - #[allow(unused_variables)] pub fn new_with_env_async( store: &mut impl AsStoreMut, env: &FunctionEnv, @@ -180,11 +180,7 @@ impl Function { F: Fn(FunctionEnvMut, &[Value]) -> Fut + 'static + Send + Sync, Fut: Future, RuntimeError>> + 'static + Send, { - let _ = store.as_store_ref(); - let _ = env; - let _ = ty.into(); - let _ = &func; - unimplemented!("Function::new_with_env_async is not implemented yet") + Self(BackendFunction::new_with_env_async(store, env, ty, func)) } /// Creates a new async host `Function` from a native typed function. @@ -325,18 +321,17 @@ impl Function { /// Calls the function asynchronously. /// - /// This is a placeholder that will panic when awaited until JSPI support - /// is fully implemented. + /// The returned future drives execution of the WebAssembly function on a + /// coroutine stack. Host functions created with [`Function::new_async`] may + /// suspend execution by awaiting futures, and their completion will resume + /// the Wasm instance according to the JSPI proposal. pub fn call_async<'a>( &'a self, - store: &'a mut impl AsStoreMut, + store: &'a mut (impl AsStoreMut + 'static), params: &'a [Value], ) -> impl Future, RuntimeError>> + 'a { - async move { - let _ = store.as_store_ref(); - let _ = params; - unimplemented!("Function::call_async is not implemented yet") - } + let params_vec = params.to_vec(); + self.0.call_async(store, params_vec) } #[doc(hidden)] diff --git a/lib/vm/Cargo.toml b/lib/vm/Cargo.toml index 0d18f699e6d..4f060d17316 100644 --- a/lib/vm/Cargo.toml +++ b/lib/vm/Cargo.toml @@ -26,7 +26,7 @@ serde = { workspace = true, features = ["derive", "rc"], optional = true } enum-iterator.workspace = true scopeguard = "1.1.0" region.workspace = true -corosensei = { version = "0.3.0" } +corosensei = { workspace = true } fnv = "1.0.3" parking_lot.workspace = true # - Optional shared dependencies. From b392a9774f3588242edd26952a281ea875ef5150 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sun, 9 Nov 2025 19:49:23 +0100 Subject: [PATCH 03/49] Added example --- Cargo.lock | 1 + lib/api/Cargo.toml | 1 + lib/api/src/backend/sys/async_runtime.rs | 29 ++++++++++++++++++++---- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 71b6153d179..308e72757dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6956,6 +6956,7 @@ dependencies = [ "cmake", "corosensei", "derive_more 2.0.1", + "futures", "hashbrown 0.11.2", "indexmap 2.12.0", "js-sys", diff --git a/lib/api/Cargo.toml b/lib/api/Cargo.toml index 57de314ebd9..1d7a1184259 100644 --- a/lib/api/Cargo.toml +++ b/lib/api/Cargo.toml @@ -80,6 +80,7 @@ wat = "1.0" tempfile.workspace = true anyhow.workspace = true macro-wasmer-universal-test = { version = "6.1.0", path = "./macro-wasmer-universal-test" } +futures = "0.3" # Dependencies and Develoment Dependencies for `js`. [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/lib/api/src/backend/sys/async_runtime.rs b/lib/api/src/backend/sys/async_runtime.rs index fe6d04eb764..7ccba984007 100644 --- a/lib/api/src/backend/sys/async_runtime.rs +++ b/lib/api/src/backend/sys/async_runtime.rs @@ -4,7 +4,7 @@ use std::{ marker::PhantomData, pin::Pin, ptr, - task::{Context, Poll}, + task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, }; use corosensei::{Coroutine, CoroutineResult, Yielder}; @@ -96,9 +96,7 @@ where CURRENT_CONTEXT.with(|cell| { let ptr = cell.get(); if ptr.is_null() { - Err(RuntimeError::new( - "async host functions can only be used inside `call_async`", - )) + run_immediate(future) } else { unsafe { (&*ptr).block_on_future(Box::pin(future)) } } @@ -163,3 +161,26 @@ impl Drop for AsyncContextGuard { }); } } + +fn run_immediate( + future: impl Future, RuntimeError>> + Send + 'static, +) -> Result, RuntimeError> { + fn noop_raw_waker() -> RawWaker { + fn no_op(_: *const ()) {} + fn clone(_: *const ()) -> RawWaker { + noop_raw_waker() + } + let vtable = &RawWakerVTable::new(clone, no_op, no_op, no_op); + RawWaker::new(ptr::null(), vtable) + } + + let mut future = Box::pin(future); + let waker = unsafe { Waker::from_raw(noop_raw_waker()) }; + let mut cx = Context::from_waker(&waker); + match future.as_mut().poll(&mut cx) { + Poll::Ready(result) => result, + Poll::Pending => Err(RuntimeError::new( + "async host functions can only yield inside `call_async`", + )), + } +} From e8c1efd28962ac49015b0fd95ff13d1483717488 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sun, 9 Nov 2025 20:48:38 +0100 Subject: [PATCH 04/49] Make first iteration --- .../src/backend/sys/entities/function/mod.rs | 169 ++++++++++++++---- 1 file changed, 139 insertions(+), 30 deletions(-) diff --git a/lib/api/src/backend/sys/entities/function/mod.rs b/lib/api/src/backend/sys/entities/function/mod.rs index c319903e73e..439ea085e21 100644 --- a/lib/api/src/backend/sys/entities/function/mod.rs +++ b/lib/api/src/backend/sys/entities/function/mod.rs @@ -12,8 +12,7 @@ use crate::{ vm::{VMExtern, VMExternFunction}, }; use std::panic::{self, AssertUnwindSafe}; -use std::{cell::UnsafeCell, cmp::max, error::Error, ffi::c_void}; -use std::{future::Future, pin::Pin}; +use std::{cell::UnsafeCell, cmp::max, error::Error, ffi::c_void, future::Future, pin::Pin}; use wasmer_types::{NativeWasmType, RawValue}; use wasmer_vm::{ MaybeInstanceOwned, StoreHandle, VMCallerCheckedAnyfunc, VMContext, VMDynamicFunctionContext, @@ -53,10 +52,10 @@ impl Function { let function_type = ty.into(); let func_ty = function_type.clone(); let func_env = env.clone().into_sys(); - let raw_store = store.as_store_mut().as_raw() as *mut u8; - let wrapper = move |values_vec: *mut RawValue| -> Result<(), RuntimeError> { + let raw_store = store.as_store_mut().as_raw() as *mut StoreInner; + let wrapper = move |values_vec: *mut RawValue| -> HostCallOutcome { unsafe { - let mut store = StoreMut::from_raw(raw_store as *mut StoreInner); + let mut store = StoreMut::from_raw(raw_store); let mut args = Vec::with_capacity(func_ty.params().len()); for (i, ty) in func_ty.params().iter().enumerate() { @@ -66,29 +65,19 @@ impl Function { values_vec.add(i).read_unaligned(), )); } - let store_mut = StoreMut::from_raw(raw_store as *mut StoreInner); + let store_mut = StoreMut::from_raw(raw_store); let env = env::FunctionEnvMut { store_mut, func_env: func_env.clone(), } .into(); - let returns = func(env, &args)?; - - // We need to dynamically check that the returns - // match the expected types, as well as expected length. - let return_types = returns.iter().map(|ret| ret.ty()); - if return_types.ne(func_ty.results().iter().copied()) { - return Err(RuntimeError::new(format!( - "Dynamic function returned wrong signature. Expected {:?} but got {:?}", - func_ty.results(), - returns.iter().map(|ret| ret.ty()) - ))); - } - for (i, ret) in returns.iter().enumerate() { - values_vec.add(i).write_unaligned(ret.as_raw(&store)); + let sig = func_ty.clone(); + let result = func(env, &args); + HostCallOutcome::Ready { + func_ty: sig, + result, } } - Ok(()) }; let mut host_data = Box::new(VMDynamicFunctionContext { address: std::ptr::null(), @@ -97,7 +86,7 @@ impl Function { raw_store, }, }); - host_data.address = host_data.ctx.func_body_ptr(); + host_data.address = host_data.ctx.func_body_ptr() as *const VMFunctionBody; // We don't yet have the address with the Wasm ABI signature. // The engine linker will replace the address with one pointing to a @@ -152,9 +141,72 @@ impl Function { F: Fn(FunctionEnvMut, &[Value]) -> Fut + 'static + Send + Sync, Fut: Future, RuntimeError>> + 'static + Send, { - Self::new_with_env(store, env, ty, move |env_mut, values| { - block_on_host_future(func(env_mut, values)) - }) + let function_type = ty.into(); + let func_ty = function_type.clone(); + let func_env = env.clone().into_sys(); + let raw_store = store.as_store_mut().as_raw() as *mut StoreInner; + let wrapper = move |values_vec: *mut RawValue| -> HostCallOutcome { + unsafe { + let mut store = StoreMut::from_raw(raw_store); + let mut args = Vec::with_capacity(func_ty.params().len()); + + for (i, ty) in func_ty.params().iter().enumerate() { + args.push(Value::from_raw( + &mut store, + *ty, + values_vec.add(i).read_unaligned(), + )); + } + let store_mut = StoreMut::from_raw(raw_store); + let env = env::FunctionEnvMut { + store_mut, + func_env: func_env.clone(), + } + .into(); + let sig = func_ty.clone(); + let future = func(env, &args); + HostCallOutcome::Future { + func_ty: sig, + future: Box::pin(future) + as Pin, RuntimeError>> + Send>>, + } + } + }; + let mut host_data = Box::new(VMDynamicFunctionContext { + address: std::ptr::null(), + ctx: DynamicFunction { + func: wrapper, + raw_store, + }, + }); + host_data.address = host_data.ctx.func_body_ptr() as *const VMFunctionBody; + + let func_ptr = std::ptr::null() as VMFunctionCallback; + let type_index = store + .as_store_mut() + .engine() + .as_sys() + .register_signature(&function_type); + let vmctx = VMFunctionContext { + host_env: host_data.as_ref() as *const _ as *mut c_void, + }; + let call_trampoline = host_data.ctx.call_trampoline_address(); + let anyfunc = VMCallerCheckedAnyfunc { + func_ptr, + type_index, + vmctx, + call_trampoline, + }; + + let vm_function = VMFunction { + anyfunc: MaybeInstanceOwned::Host(Box::new(UnsafeCell::new(anyfunc))), + kind: VMFunctionKind::Dynamic, + signature: function_type, + host_data, + }; + Self { + handle: StoreHandle::new(store.as_store_mut().objects_mut().as_sys_mut(), vm_function), + } } /// Creates a new host `Function` from a native function. @@ -499,15 +551,61 @@ where } } +fn write_dynamic_results( + raw_store: *mut StoreInner, + func_ty: &FunctionType, + returns: Vec, + values_vec: *mut RawValue, +) -> Result<(), RuntimeError> { + let mut store = unsafe { StoreMut::from_raw(raw_store) }; + let return_types = returns.iter().map(|ret| ret.ty()); + if return_types.ne(func_ty.results().iter().copied()) { + return Err(RuntimeError::new(format!( + "Dynamic function returned wrong signature. Expected {:?} but got {:?}", + func_ty.results(), + returns.iter().map(|ret| ret.ty()) + ))); + } + for (i, ret) in returns.iter().enumerate() { + unsafe { + values_vec.add(i).write_unaligned(ret.as_raw(&store)); + } + } + Ok(()) +} + +fn finalize_dynamic_call( + raw_store: *mut StoreInner, + func_ty: FunctionType, + values_vec: *mut RawValue, + result: Result, RuntimeError>, +) -> Result<(), RuntimeError> { + match result { + Ok(values) => write_dynamic_results(raw_store, &func_ty, values, values_vec), + Err(err) => Err(err), + } +} + +pub(crate) enum HostCallOutcome { + Ready { + func_ty: FunctionType, + result: Result, RuntimeError>, + }, + Future { + func_ty: FunctionType, + future: Pin, RuntimeError>> + Send>>, + }, +} + /// Host state for a dynamic function. pub(crate) struct DynamicFunction { func: F, - raw_store: *mut u8, + raw_store: *mut StoreInner, } impl DynamicFunction where - F: Fn(*mut RawValue) -> Result<(), RuntimeError> + 'static, + F: Fn(*mut RawValue) -> HostCallOutcome + 'static, { // This function wraps our func, to make it compatible with the // reverse trampoline signature @@ -516,8 +614,19 @@ where values_vec: *mut RawValue, ) { let result = on_host_stack(|| { - panic::catch_unwind(AssertUnwindSafe(|| { - to_invocation_result((this.ctx.func)(values_vec)) + panic::catch_unwind(AssertUnwindSafe(|| match (this.ctx.func)(values_vec) { + HostCallOutcome::Ready { func_ty, result } => to_invocation_result( + finalize_dynamic_call(this.ctx.raw_store, func_ty, values_vec, result), + ), + HostCallOutcome::Future { func_ty, future } => { + let awaited = block_on_host_future(future); + to_invocation_result(finalize_dynamic_call( + this.ctx.raw_store, + func_ty, + values_vec, + awaited, + )) + } })) }); @@ -527,7 +636,7 @@ where match result { Ok(InvocationResult::Success(())) => {} Ok(InvocationResult::Exception(exception)) => unsafe { - let store = StoreMut::from_raw(this.ctx.raw_store as *mut _); + let store = StoreMut::from_raw(this.ctx.raw_store); wasmer_vm::libcalls::throw( store.as_store_ref().objects().as_sys(), exception.vm_exceptionref().as_sys().to_u32_exnref(), From cdb474c7aa84319c88e23cb45dfd796d7369cd3f Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sun, 9 Nov 2025 23:50:02 +0100 Subject: [PATCH 05/49] Added support for async typed func --- .../src/backend/sys/entities/function/mod.rs | 122 +++++++++++++++++- .../backend/sys/entities/function/typed.rs | 61 ++++++++- lib/api/src/entities/function/inner.rs | 64 +++++++++ lib/api/src/entities/function/mod.rs | 28 ++-- lib/api/src/utils/native/typed_func.rs | 30 ++++- 5 files changed, 285 insertions(+), 20 deletions(-) diff --git a/lib/api/src/backend/sys/entities/function/mod.rs b/lib/api/src/backend/sys/entities/function/mod.rs index 439ea085e21..4952c815dca 100644 --- a/lib/api/src/backend/sys/entities/function/mod.rs +++ b/lib/api/src/backend/sys/entities/function/mod.rs @@ -12,7 +12,9 @@ use crate::{ vm::{VMExtern, VMExternFunction}, }; use std::panic::{self, AssertUnwindSafe}; -use std::{cell::UnsafeCell, cmp::max, error::Error, ffi::c_void, future::Future, pin::Pin}; +use std::{ + cell::UnsafeCell, cmp::max, error::Error, ffi::c_void, future::Future, pin::Pin, sync::Arc, +}; use wasmer_types::{NativeWasmType, RawValue}; use wasmer_vm::{ MaybeInstanceOwned, StoreHandle, VMCallerCheckedAnyfunc, VMContext, VMDynamicFunctionContext, @@ -253,6 +255,66 @@ impl Function { } } + pub(crate) fn new_typed_async( + store: &mut impl AsStoreMut, + func: F, + ) -> Self + where + F: Fn(Args) -> Fut + 'static + Send + Sync, + Fut: Future + 'static + Send, + Args: WasmTypeList, + Rets: WasmTypeList, + RetsAsResult: IntoResult, + { + let env = FunctionEnv::new(store, ()); + let func = Arc::new(func); + Self::new_typed_with_env_async(store, &env, move |_env, args| { + let func = func.clone(); + func(args) + }) + } + + pub(crate) fn new_typed_with_env_async( + store: &mut impl AsStoreMut, + env: &FunctionEnv, + func: F, + ) -> Self + where + T: Send + 'static, + F: Fn(FunctionEnvMut, Args) -> Fut + 'static + Send + Sync, + Fut: Future + 'static + Send, + Args: WasmTypeList, + Rets: WasmTypeList, + RetsAsResult: IntoResult, + { + let signature = FunctionType::new(Args::wasm_types(), Rets::wasm_types()); + let args_sig = Arc::new(signature.clone()); + let results_sig = Arc::new(signature.clone()); + let func = Arc::new(func); + Self::new_with_env_async(store, env, signature, move |mut env_mut, values| -> Pin< + Box, RuntimeError>> + Send>, + > { + let raw_store = RawStorePtr { + ptr: env_mut.as_store_mut().as_raw(), + }; + let args_sig = args_sig.clone(); + let results_sig = results_sig.clone(); + let func = func.clone(); + let args = match typed_args_from_values::(raw_store, args_sig.as_ref(), values) { + Ok(args) => args, + Err(err) => return Box::pin(async { Err(err) }), + }; + let future = (*func)(env_mut, args); + Box::pin(async move { + let typed_result = future + .await + .into_result() + .map_err(|err| RuntimeError::user(Box::new(err)))?; + typed_results_to_values::(raw_store, results_sig.as_ref(), typed_result) + }) + }) + } + pub(crate) fn new_typed_with_env( store: &mut impl AsStoreMut, env: &FunctionEnv, @@ -586,6 +648,64 @@ fn finalize_dynamic_call( } } +#[derive(Clone, Copy)] +struct RawStorePtr { + ptr: *mut StoreInner, +} + +unsafe impl Send for RawStorePtr {} +unsafe impl Sync for RawStorePtr {} + +fn typed_args_from_values( + raw_store: RawStorePtr, + func_ty: &FunctionType, + values: &[Value], +) -> Result +where + Args: WasmTypeList, +{ + if values.len() != func_ty.params().len() { + return Err(RuntimeError::new( + "typed host function received wrong number of parameters", + )); + } + let mut store = unsafe { StoreMut::from_raw(raw_store.ptr) }; + let mut raw_array = Args::empty_array(); + for ((slot, value), expected_ty) in raw_array + .as_mut() + .iter_mut() + .zip(values.iter()) + .zip(func_ty.params().iter()) + { + debug_assert_eq!( + value.ty(), + *expected_ty, + "wasm should only call host functions with matching signatures" + ); + *slot = value.as_raw(&store); + } + unsafe { Ok(Args::from_array(&mut store, raw_array)) } +} + +fn typed_results_to_values( + raw_store: RawStorePtr, + func_ty: &FunctionType, + rets: Rets, +) -> Result, RuntimeError> +where + Rets: WasmTypeList, +{ + let mut store = unsafe { StoreMut::from_raw(raw_store.ptr) }; + let mut raw_array = unsafe { rets.into_array(&mut store) }; + let mut values = Vec::with_capacity(func_ty.results().len()); + for (raw, ty) in raw_array.as_mut().iter().zip(func_ty.results().iter()) { + unsafe { + values.push(Value::from_raw(&mut store, *ty, *raw)); + } + } + Ok(values) +} + pub(crate) enum HostCallOutcome { Ready { func_ty: FunctionType, diff --git a/lib/api/src/backend/sys/entities/function/typed.rs b/lib/api/src/backend/sys/entities/function/typed.rs index 1bae7786ab7..186cc0cb6ff 100644 --- a/lib/api/src/backend/sys/entities/function/typed.rs +++ b/lib/api/src/backend/sys/entities/function/typed.rs @@ -1,7 +1,10 @@ use crate::backend::sys::engine::NativeEngineExt; use crate::store::{AsStoreMut, AsStoreRef}; -use crate::{FromToNativeWasmType, NativeWasmTypeInto, RuntimeError, TypedFunction, WasmTypeList}; -use wasmer_types::RawValue; +use crate::{ + FromToNativeWasmType, NativeWasmTypeInto, RuntimeError, TypedFunction, Value, WasmTypeList, +}; +use std::future::Future; +use wasmer_types::{FunctionType, RawValue, Type}; macro_rules! impl_native_traits { ( $( $x:ident ),* ) => { @@ -99,6 +102,36 @@ macro_rules! impl_native_traits { // Ok(Rets::from_c_struct(results)) } + /// Call the typed func asynchronously. + #[allow(unused_mut)] + #[allow(clippy::too_many_arguments)] + pub fn call_async_sys<'a>( + &'a self, + store: &'a mut (impl AsStoreMut + 'static), + $( $x: $x, )* + ) -> impl Future> + 'a + where + $( $x: FromToNativeWasmType, )* + { + let func = self.func.clone(); + let func_ty = func.ty(store); + let mut params_raw = [ $( $x.to_native().into_raw(store) ),* ]; + let mut params_values = Vec::with_capacity(params_raw.len()); + { + let mut tmp_store = store.as_store_mut(); + for (raw, ty) in params_raw.iter().zip(func_ty.params()) { + unsafe { + params_values.push(Value::from_raw(&mut tmp_store, *ty, *raw)); + } + } + } + + async move { + let results = func.call_async(store, ¶ms_values).await?; + convert_results::(store, func_ty, results) + } + } + #[doc(hidden)] #[allow(missing_docs)] #[allow(unused_mut)] @@ -218,3 +251,27 @@ impl_native_traits!( impl_native_traits!( A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20 ); + +fn convert_results( + store: &mut impl AsStoreMut, + ty: FunctionType, + results: Box<[Value]>, +) -> Result +where + Rets: WasmTypeList, +{ + if results.len() != ty.results().len() { + return Err(RuntimeError::new("result arity mismatch")); + } + let mut raw_array = Rets::empty_array(); + for ((slot, value_ty), value) in raw_array + .as_mut() + .iter_mut() + .zip(ty.results().iter()) + .zip(results.iter()) + { + debug_assert_eq!(value.ty(), *value_ty); + *slot = value.as_raw(store); + } + unsafe { Ok(Rets::from_array(store, raw_array)) } +} diff --git a/lib/api/src/entities/function/inner.rs b/lib/api/src/entities/function/inner.rs index 6c492d82726..0e6e87ee009 100644 --- a/lib/api/src/entities/function/inner.rs +++ b/lib/api/src/entities/function/inner.rs @@ -7,6 +7,7 @@ use crate::{ HostFunction, StoreMut, StoreRef, TypedFunction, Value, WasmTypeList, WithEnv, WithoutEnv, error::RuntimeError, macros::backend::{gen_rt_ty, match_rt}, + utils::IntoResult, vm::{VMExtern, VMExternFunction, VMFuncRef}, }; @@ -306,6 +307,69 @@ impl BackendFunction { } } + #[inline] + pub fn new_typed_async( + store: &mut impl AsStoreMut, + func: F, + ) -> Self + where + F: Fn(Args) -> Fut + 'static + Send + Sync, + Fut: Future + 'static + Send, + Args: WasmTypeList, + Rets: WasmTypeList, + RetsAsResult: IntoResult, + { + match &store.as_store_mut().inner.store { + #[cfg(feature = "sys")] + crate::BackendStore::Sys(_) => Self::Sys( + crate::backend::sys::entities::function::Function::new_typed_async(store, func), + ), + #[cfg(feature = "wamr")] + crate::BackendStore::Wamr(_) => unsupported_async_backend("wamr"), + #[cfg(feature = "wasmi")] + crate::BackendStore::Wasmi(_) => unsupported_async_backend("wasmi"), + #[cfg(feature = "v8")] + crate::BackendStore::V8(_) => unsupported_async_backend("v8"), + #[cfg(feature = "js")] + crate::BackendStore::Js(_) => unsupported_async_backend("js"), + #[cfg(feature = "jsc")] + crate::BackendStore::Jsc(_) => unsupported_async_backend("jsc"), + } + } + + #[inline] + pub fn new_typed_with_env_async( + store: &mut impl AsStoreMut, + env: &FunctionEnv, + func: F, + ) -> Self + where + F: Fn(FunctionEnvMut, Args) -> Fut + 'static + Send + Sync, + Fut: Future + 'static + Send, + Args: WasmTypeList, + Rets: WasmTypeList, + RetsAsResult: IntoResult, + { + match &store.as_store_mut().inner.store { + #[cfg(feature = "sys")] + crate::BackendStore::Sys(_) => Self::Sys( + crate::backend::sys::entities::function::Function::new_typed_with_env_async( + store, env, func, + ), + ), + #[cfg(feature = "wamr")] + crate::BackendStore::Wamr(_) => unsupported_async_backend("wamr"), + #[cfg(feature = "wasmi")] + crate::BackendStore::Wasmi(_) => unsupported_async_backend("wasmi"), + #[cfg(feature = "v8")] + crate::BackendStore::V8(_) => unsupported_async_backend("v8"), + #[cfg(feature = "js")] + crate::BackendStore::Js(_) => unsupported_async_backend("js"), + #[cfg(feature = "jsc")] + crate::BackendStore::Jsc(_) => unsupported_async_backend("jsc"), + } + } + /// Returns the [`FunctionType`] of the `Function`. /// /// # Example diff --git a/lib/api/src/entities/function/mod.rs b/lib/api/src/entities/function/mod.rs index 86df1286e17..564cc58d486 100644 --- a/lib/api/src/entities/function/mod.rs +++ b/lib/api/src/entities/function/mod.rs @@ -18,6 +18,7 @@ use crate::{ AsStoreMut, AsStoreRef, ExportError, Exportable, Extern, StoreMut, StoreRef, TypedFunction, Value, WasmTypeList, error::RuntimeError, + utils::IntoResult, vm::{VMExtern, VMExternFunction, VMFuncRef}, }; @@ -184,36 +185,37 @@ impl Function { } /// Creates a new async host `Function` from a native typed function. - #[allow(unused_variables)] - pub fn new_typed_async(store: &mut impl AsStoreMut, func: F) -> Self + /// + /// The future can return either the raw result tuple or any type that implements + /// [`IntoResult`] for the result tuple (e.g. `Result`). + pub fn new_typed_async( + store: &mut impl AsStoreMut, + func: F, + ) -> Self where F: Fn(Args) -> Fut + 'static + Send + Sync, - Fut: Future + 'static + Send, + Fut: Future + 'static + Send, Args: WasmTypeList, Rets: WasmTypeList, + RetsAsResult: IntoResult, { - let _ = store.as_store_ref(); - let _ = &func; - unimplemented!("Function::new_typed_async is not implemented yet") + Self(BackendFunction::new_typed_async(store, func)) } /// Creates a new async host `Function` with an environment from a typed function. - #[allow(unused_variables)] - pub fn new_typed_with_env_async( + pub fn new_typed_with_env_async( store: &mut impl AsStoreMut, env: &FunctionEnv, func: F, ) -> Self where F: Fn(FunctionEnvMut, Args) -> Fut + 'static + Send + Sync, - Fut: Future + 'static + Send, + Fut: Future + 'static + Send, Args: WasmTypeList, Rets: WasmTypeList, + RetsAsResult: IntoResult, { - let _ = store.as_store_ref(); - let _ = env; - let _ = &func; - unimplemented!("Function::new_typed_with_env_async is not implemented yet") + Self(BackendFunction::new_typed_with_env_async(store, env, func)) } /// Returns the [`FunctionType`] of the `Function`. diff --git a/lib/api/src/utils/native/typed_func.rs b/lib/api/src/utils/native/typed_func.rs index 6ad2e8e2190..e95a436b60e 100644 --- a/lib/api/src/utils/native/typed_func.rs +++ b/lib/api/src/utils/native/typed_func.rs @@ -84,16 +84,32 @@ macro_rules! impl_native_traits { #[allow(clippy::too_many_arguments)] pub fn call_async<'a>( &'a self, - store: &'a mut impl AsStoreMut, + store: &'a mut (impl AsStoreMut + 'static), $( $x: $x, )* ) -> impl Future> + 'a where $( $x: FromToNativeWasmType, )* { + $( + let [] = $x; + )* async move { - let _ = store.as_store_ref(); - let _ = ($($x,)*); - unimplemented!("TypedFunction::call_async is not implemented yet") + match store.as_store_mut().inner.store { + #[cfg(feature = "sys")] + BackendStore::Sys(_) => { + self.call_async_sys(store, $([]),*).await + } + #[cfg(feature = "wamr")] + BackendStore::Wamr(_) => async_backend_error(), + #[cfg(feature = "wasmi")] + BackendStore::Wasmi(_) => async_backend_error(), + #[cfg(feature = "v8")] + BackendStore::V8(_) => async_backend_error(), + #[cfg(feature = "js")] + BackendStore::Js(_) => async_backend_error(), + #[cfg(feature = "jsc")] + BackendStore::Jsc(_) => async_backend_error(), + } } } @@ -155,3 +171,9 @@ impl_native_traits!( impl_native_traits!( A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20 ); + +fn async_backend_error() -> Result { + Err(RuntimeError::new( + "async calls are only supported with the `sys` backend", + )) +} From 96ebd6263fe4ee0ffb0527a2d449d91121632cea Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Mon, 10 Nov 2025 02:13:17 +0100 Subject: [PATCH 06/49] Added basic jsapi --- lib/api/tests/jspi_async.rs | 95 +++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 lib/api/tests/jspi_async.rs diff --git a/lib/api/tests/jspi_async.rs b/lib/api/tests/jspi_async.rs new file mode 100644 index 00000000000..6393f075dc6 --- /dev/null +++ b/lib/api/tests/jspi_async.rs @@ -0,0 +1,95 @@ +use std::sync::OnceLock; + +use anyhow::Result; +use futures::{executor::block_on, future}; +use wasmer::{ + Function, FunctionEnv, FunctionEnvMut, FunctionType, Instance, Module, Store, Type, Value, + imports, +}; + +#[derive(Default)] +struct DeltaState { + deltas: Vec, + index: usize, +} + +impl DeltaState { + fn next(&mut self) -> f64 { + let value = self.deltas.get(self.index).copied().unwrap_or(0.0); + self.index += 1; + value + } +} + +fn jspi_module() -> &'static [u8] { + static BYTES: OnceLock> = OnceLock::new(); + const JSPI_WAT: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../tests/examples/jspi.wat"); + BYTES.get_or_init(|| wat::parse_file(JSPI_WAT).expect("valid example module")) +} + +#[test] +fn async_state_updates_follow_jspi_example() -> Result<()> { + let wasm = jspi_module(); + let mut store = Store::default(); + let module = Module::new(&store, wasm)?; + + let init_state = Function::new_async( + &mut store, + FunctionType::new(vec![], vec![Type::F64]), + |_values| async move { + future::ready(()).await; + Ok(vec![Value::F64(1.0)]) + }, + ); + + let delta_env = FunctionEnv::new( + &mut store, + DeltaState { + deltas: vec![0.5, -1.0, 2.5], + index: 0, + }, + ); + let compute_delta = Function::new_with_env_async( + &mut store, + &delta_env, + FunctionType::new(vec![], vec![Type::F64]), + |mut env: FunctionEnvMut, _values| { + let delta = env.data_mut().next(); + async move { + future::ready(()).await; + Ok(vec![Value::F64(delta)]) + } + }, + ); + + let import_object = imports! { + "js" => { + "init_state" => init_state, + "compute_delta" => compute_delta, + } + }; + + let instance = Instance::new(&mut store, &module, &import_object)?; + let get_state = instance.exports.get_function("get_state")?; + let update_state = instance.exports.get_function("update_state")?; + + fn as_f64(values: &[Value]) -> f64 { + match &values[0] { + Value::F64(v) => *v, + other => panic!("expected f64 value, got {other:?}"), + } + } + + assert_eq!(as_f64(&get_state.call(&mut store, &[])?), 1.0); + + let step = |store: &mut Store, func: &wasmer::Function| -> Result { + let result = block_on(func.call_async(store, &[]))?; + Ok(as_f64(&result)) + }; + + assert_eq!(step(&mut store, update_state)?, 1.5); + assert_eq!(step(&mut store, update_state)?, 0.5); + assert_eq!(step(&mut store, update_state)?, 3.0); + + Ok(()) +} From 87df386cc12037f6ec1a5d479f3243cab8e7678e Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Mon, 10 Nov 2025 20:53:54 +0400 Subject: [PATCH 07/49] Update jspi test to actually suspend --- Cargo.lock | 2 ++ lib/api/Cargo.toml | 1 + lib/api/tests/jspi_async.rs | 18 +++++++++++++++--- tests/examples/jspi.wat | 14 ++++++++++++++ 4 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 tests/examples/jspi.wat diff --git a/Cargo.lock b/Cargo.lock index 308e72757dc..5c92ef4c035 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5682,6 +5682,7 @@ dependencies = [ "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2 0.6.1", @@ -6973,6 +6974,7 @@ dependencies = [ "target-lexicon 0.13.3", "tempfile", "thiserror 1.0.69", + "tokio", "tracing", "ureq", "wasm-bindgen", diff --git a/lib/api/Cargo.toml b/lib/api/Cargo.toml index 1d7a1184259..fc7821dcba0 100644 --- a/lib/api/Cargo.toml +++ b/lib/api/Cargo.toml @@ -81,6 +81,7 @@ tempfile.workspace = true anyhow.workspace = true macro-wasmer-universal-test = { version = "6.1.0", path = "./macro-wasmer-universal-test" } futures = "0.3" +tokio = { workspace = true, features = ["full"] } # Dependencies and Develoment Dependencies for `js`. [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/lib/api/tests/jspi_async.rs b/lib/api/tests/jspi_async.rs index 6393f075dc6..7aefa6ec943 100644 --- a/lib/api/tests/jspi_async.rs +++ b/lib/api/tests/jspi_async.rs @@ -1,7 +1,7 @@ use std::sync::OnceLock; use anyhow::Result; -use futures::{executor::block_on, future}; +use futures::future; use wasmer::{ Function, FunctionEnv, FunctionEnvMut, FunctionType, Instance, Module, Store, Type, Value, imports, @@ -37,6 +37,12 @@ fn async_state_updates_follow_jspi_example() -> Result<()> { &mut store, FunctionType::new(vec![], vec![Type::F64]), |_values| async move { + // Note: future::ready doesn't actually suspend. It's important + // to note that, while we're in an async context here, it's + // impossible to suspend during module instantiation, which is + // where this import is called. + // To see this in action, uncomment the following line: + // tokio::task::yield_now().await; future::ready(()).await; Ok(vec![Value::F64(1.0)]) }, @@ -56,7 +62,9 @@ fn async_state_updates_follow_jspi_example() -> Result<()> { |mut env: FunctionEnvMut, _values| { let delta = env.data_mut().next(); async move { - future::ready(()).await; + // We can, however, actually suspend whenever + // `Function::call_async` is used to call WASM functions. + tokio::time::sleep(std::time::Duration::from_millis(10)).await; Ok(vec![Value::F64(delta)]) } }, @@ -83,7 +91,11 @@ fn async_state_updates_follow_jspi_example() -> Result<()> { assert_eq!(as_f64(&get_state.call(&mut store, &[])?), 1.0); let step = |store: &mut Store, func: &wasmer::Function| -> Result { - let result = block_on(func.call_async(store, &[]))?; + let result = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap() + .block_on(func.call_async(store, &[]))?; Ok(as_f64(&result)) }; diff --git a/tests/examples/jspi.wat b/tests/examples/jspi.wat new file mode 100644 index 00000000000..71ed9b57181 --- /dev/null +++ b/tests/examples/jspi.wat @@ -0,0 +1,14 @@ +(module + (import "js" "init_state" (func $init_state (result f64))) + (import "js" "compute_delta" (func $compute_delta (result f64))) + (global $state (mut f64) (f64.const 0)) + (func $init (global.set $state (call $init_state))) + (start $init) + (func $get_state (export "get_state") (result f64) (global.get $state)) + (func $update_state (export "update_state") (result f64) + (local $delta f64) + (local.set $delta (call $compute_delta)) + (global.set $state (f64.add (global.get $state) (local.get $delta) )) + (global.get $state) + ) +) \ No newline at end of file From d85f3a391e5aecb90076194c1ce85c210d09a861 Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Mon, 10 Nov 2025 21:16:37 +0400 Subject: [PATCH 08/49] Guard against improper use of nested call_async --- lib/api/src/backend/sys/async_runtime.rs | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/lib/api/src/backend/sys/async_runtime.rs b/lib/api/src/backend/sys/async_runtime.rs index 7ccba984007..9e34ca5107e 100644 --- a/lib/api/src/backend/sys/async_runtime.rs +++ b/lib/api/src/backend/sys/async_runtime.rs @@ -12,6 +12,13 @@ use corosensei::{Coroutine, CoroutineResult, Yielder}; use super::entities::function::Function as SysFunction; use crate::{AsStoreMut, RuntimeError, Value}; +const ASYNC_STACK_CORRUPTED: &str = "\ +Async runtime state is corrupted. This is usually caused \ +by making a Function::call_async call within an imported \ +async function, but not awaiting the resulting future \ +before the imported function returns. This scenario \ +is currently not supported."; + type HostFuture = Pin, RuntimeError>> + Send + 'static>>; pub(crate) struct AsyncCallFuture<'a, S: AsStoreMut + 'static> { @@ -98,7 +105,14 @@ where if ptr.is_null() { run_immediate(future) } else { - unsafe { (&*ptr).block_on_future(Box::pin(future)) } + let result = unsafe { (&*ptr).block_on_future(Box::pin(future)) }; + if cell.get() != ptr { + // If a `Function::call_async` was made inside the future, + // but not awaited properly, the pointer in the cell will + // have changed; catch it here. + panic!("{ASYNC_STACK_CORRUPTED}"); + } + result } }) } @@ -157,7 +171,12 @@ struct AsyncContextGuard { impl Drop for AsyncContextGuard { fn drop(&mut self) { CURRENT_CONTEXT.with(|cell| { - cell.set(self.previous); + let existing = cell.replace(self.previous); + if existing != self as *mut _ as *const _ { + // This can happen if an outer coroutine completes + // before an inner one + panic!("{ASYNC_STACK_CORRUPTED}"); + } }); } } From ab86057964835308db8c3cf7e1f84c64f2da4dcb Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Wed, 12 Nov 2025 16:27:30 +0400 Subject: [PATCH 09/49] * Support nested coroutines suspending out of order * Add proper trap code for suspending in non-async context --- lib/api/src/backend/sys/async_runtime.rs | 154 +++++--- .../src/backend/sys/entities/function/mod.rs | 27 +- lib/api/src/entities/store/inner.rs | 3 +- lib/api/src/entities/store/store_ref.rs | 11 +- lib/api/tests/jspi_async.rs | 343 +++++++++++++++++- lib/types/src/trapcode.rs | 9 + 6 files changed, 493 insertions(+), 54 deletions(-) diff --git a/lib/api/src/backend/sys/async_runtime.rs b/lib/api/src/backend/sys/async_runtime.rs index 9e34ca5107e..05f9c9ad146 100644 --- a/lib/api/src/backend/sys/async_runtime.rs +++ b/lib/api/src/backend/sys/async_runtime.rs @@ -1,9 +1,11 @@ use std::{ - cell::Cell, + cell::RefCell, + collections::HashMap, future::Future, marker::PhantomData, pin::Pin, ptr, + rc::Rc, task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, }; @@ -12,13 +14,6 @@ use corosensei::{Coroutine, CoroutineResult, Yielder}; use super::entities::function::Function as SysFunction; use crate::{AsStoreMut, RuntimeError, Value}; -const ASYNC_STACK_CORRUPTED: &str = "\ -Async runtime state is corrupted. This is usually caused \ -by making a Function::call_async call within an imported \ -async function, but not awaiting the resulting future \ -before the imported function returns. This scenario \ -is currently not supported."; - type HostFuture = Pin, RuntimeError>> + Send + 'static>>; pub(crate) struct AsyncCallFuture<'a, S: AsStoreMut + 'static> { @@ -26,7 +21,9 @@ pub(crate) struct AsyncCallFuture<'a, S: AsStoreMut + 'static> { pending_future: Option, next_resume: Option, result: Option, RuntimeError>>, - _marker: PhantomData<&'a mut S>, + + // Use Rc> to make sure that the future is !Send and !Sync + _marker: PhantomData>>, } impl<'a, S> AsyncCallFuture<'a, S> @@ -37,7 +34,7 @@ where let store_ptr = store as *mut S; let coroutine = Coroutine::new(move |yielder: &Yielder, _resume| { - let ctx_state = AsyncContextState::new(yielder); + let ctx_state = CoroutineContext::new(yielder); let _guard = ctx_state.enter(); let result = { let store_ref = unsafe { &mut *store_ptr }; @@ -92,27 +89,77 @@ where } } +struct AsyncContext { + next: u64, + active: Vec, + coroutines: HashMap, +} + thread_local! { - static CURRENT_CONTEXT: Cell<*const AsyncContextState> = const { Cell::new(ptr::null()) }; + static CURRENT_CONTEXT: RefCell> = const { RefCell::new(None) }; +} + +pub enum AsyncRuntimeError { + YieldOutsideAsyncContext, + RuntimeError(RuntimeError), } -pub(crate) fn block_on_host_future(future: Fut) -> Result, RuntimeError> +pub(crate) fn block_on_host_future(future: Fut) -> Result, AsyncRuntimeError> where Fut: Future, RuntimeError>> + Send + 'static, { CURRENT_CONTEXT.with(|cell| { - let ptr = cell.get(); - if ptr.is_null() { - run_immediate(future) - } else { - let result = unsafe { (&*ptr).block_on_future(Box::pin(future)) }; - if cell.get() != ptr { - // If a `Function::call_async` was made inside the future, - // but not awaited properly, the pointer in the cell will - // have changed; catch it here. - panic!("{ASYNC_STACK_CORRUPTED}"); + let mut borrow = cell.borrow_mut(); + match borrow.as_ref().and_then(|ctx| ctx.active.last().copied()) { + None => { + // If there is no async context or we haven't entered it, + // we can still directly run a future that doesn't block + // inline. + // Note, there can be an async context without an active + // coroutine in the following scenario: + // call_async -> wasm code -> imported function -> + // call (non-async) -> wasm_code -> imported async function + + // We drop the borrow anyway to let the future start a new + // async context if needed. + drop(borrow); + run_immediate(future) + } + Some(current) => { + // If we have an active coroutine, get the context and yielder + let context = borrow.as_mut().unwrap(); + let coro_context = *context + .coroutines + .get(¤t) + .expect("Coroutine context was already destroyed"); + + // Then pop ourselves from the active stack since we're not active anymore + // and un-borrow the cell for others to use. + assert_eq!( + context.active.pop(), + Some(current), + "Active coroutine stack corrupted" + ); + drop(borrow); + + // Now we can yield back to the runtime while we wait + let result = unsafe { &*coro_context } + .block_on_future(Box::pin(future)) + .map_err(AsyncRuntimeError::RuntimeError); + + // Once the future is ready, we borrow again and restore the current + // coroutine. + let mut borrow = cell.borrow_mut(); + let context = borrow + .as_mut() + .expect("The async context was destroyed while a coroutine was pending"); + if !context.coroutines.contains_key(¤t) { + panic!("The coroutine context was destroyed while a coroutine was pending"); + } + context.active.push(current); + + result } - result } }) } @@ -137,21 +184,40 @@ enum AsyncResume { HostFutureReady(Result, RuntimeError>), } -struct AsyncContextState { +struct CoroutineContext { yielder: *const Yielder, } -impl AsyncContextState { +impl CoroutineContext { fn new(yielder: &Yielder) -> Self { Self { yielder: yielder as *const _, } } - fn enter(&self) -> AsyncContextGuard { + fn enter(&self) -> CoroutineContextGuard { CURRENT_CONTEXT.with(|cell| { - let previous = cell.replace(self as *const _); - AsyncContextGuard { previous } + let mut borrow = cell.borrow_mut(); + + // If there is no context yet, create one. + if borrow.is_none() { + *borrow = Some(AsyncContext { + next: 0, + active: vec![], + coroutines: HashMap::new(), + }); + } + + let context = borrow.as_mut().unwrap(); + + // Assign an ID to this coroutine context. + let id = context.next; + context.next += 1; + + // Add this context to the map and push it on top of the active stack. + context.coroutines.insert(id, self as *const _); + context.active.push(id); + CoroutineContextGuard { id } }) } @@ -164,18 +230,26 @@ impl AsyncContextState { } } -struct AsyncContextGuard { - previous: *const AsyncContextState, +struct CoroutineContextGuard { + id: u64, } -impl Drop for AsyncContextGuard { +impl Drop for CoroutineContextGuard { fn drop(&mut self) { CURRENT_CONTEXT.with(|cell| { - let existing = cell.replace(self.previous); - if existing != self as *mut _ as *const _ { - // This can happen if an outer coroutine completes - // before an inner one - panic!("{ASYNC_STACK_CORRUPTED}"); + let mut borrow = cell.borrow_mut(); + let context = borrow + .as_mut() + .expect("The async context was destroyed while a coroutine was active"); + context.coroutines.remove(&self.id); + assert_eq!( + context.active.pop(), + Some(self.id), + "Active coroutine stack corrupted" + ); + if context.coroutines.is_empty() { + // If there are no more coroutines, remove the context. + *borrow = None; } }); } @@ -183,7 +257,7 @@ impl Drop for AsyncContextGuard { fn run_immediate( future: impl Future, RuntimeError>> + Send + 'static, -) -> Result, RuntimeError> { +) -> Result, AsyncRuntimeError> { fn noop_raw_waker() -> RawWaker { fn no_op(_: *const ()) {} fn clone(_: *const ()) -> RawWaker { @@ -197,9 +271,7 @@ fn run_immediate( let waker = unsafe { Waker::from_raw(noop_raw_waker()) }; let mut cx = Context::from_waker(&waker); match future.as_mut().poll(&mut cx) { - Poll::Ready(result) => result, - Poll::Pending => Err(RuntimeError::new( - "async host functions can only yield inside `call_async`", - )), + Poll::Ready(result) => result.map_err(AsyncRuntimeError::RuntimeError), + Poll::Pending => Err(AsyncRuntimeError::YieldOutsideAsyncContext), } } diff --git a/lib/api/src/backend/sys/entities/function/mod.rs b/lib/api/src/backend/sys/entities/function/mod.rs index 4952c815dca..1613ff32732 100644 --- a/lib/api/src/backend/sys/entities/function/mod.rs +++ b/lib/api/src/backend/sys/entities/function/mod.rs @@ -8,6 +8,7 @@ use crate::{ StoreInner, Value, WithEnv, WithoutEnv, backend::sys::{engine::NativeEngineExt, vm::VMFunctionCallback}, entities::store::{AsStoreMut, AsStoreRef, StoreMut}, + sys::async_runtime::AsyncRuntimeError, utils::{FromToNativeWasmType, IntoResult, NativeWasmTypeInto, WasmTypeList}, vm::{VMExtern, VMExternFunction}, }; @@ -17,9 +18,10 @@ use std::{ }; use wasmer_types::{NativeWasmType, RawValue}; use wasmer_vm::{ - MaybeInstanceOwned, StoreHandle, VMCallerCheckedAnyfunc, VMContext, VMDynamicFunctionContext, - VMFuncRef, VMFunction, VMFunctionBody, VMFunctionContext, VMFunctionKind, VMTrampoline, - on_host_stack, raise_user_trap, resume_panic, wasmer_call_trampoline, + MaybeInstanceOwned, StoreHandle, Trap, TrapCode, VMCallerCheckedAnyfunc, VMContext, + VMDynamicFunctionContext, VMFuncRef, VMFunction, VMFunctionBody, VMFunctionContext, + VMFunctionKind, VMTrampoline, on_host_stack, raise_lib_trap, raise_user_trap, resume_panic, + wasmer_call_trampoline, }; use crate::backend::sys::async_runtime::{block_on_host_future, call_function_async}; @@ -593,6 +595,7 @@ enum InvocationResult { Success(T), Exception(crate::Exception), Trap(Box), + YieldOutsideAsyncContext, } fn to_invocation_result(result: Result) -> InvocationResult @@ -740,11 +743,18 @@ where ), HostCallOutcome::Future { func_ty, future } => { let awaited = block_on_host_future(future); + let result = match awaited { + Ok(value) => Ok(value), + Err(AsyncRuntimeError::RuntimeError(e)) => Err(e), + Err(AsyncRuntimeError::YieldOutsideAsyncContext) => { + return InvocationResult::YieldOutsideAsyncContext; + } + }; to_invocation_result(finalize_dynamic_call( this.ctx.raw_store, func_ty, values_vec, - awaited, + result, )) } })) @@ -763,6 +773,9 @@ where ) }, Ok(InvocationResult::Trap(trap)) => unsafe { raise_user_trap(trap) }, + Ok(InvocationResult::YieldOutsideAsyncContext) => unsafe { + raise_lib_trap(Trap::lib(TrapCode::YieldOutsideAsyncContext)) + }, Err(panic) => unsafe { resume_panic(panic) }, } } @@ -869,6 +882,9 @@ macro_rules! impl_host_function { ) } Ok(InvocationResult::Trap(trap)) => unsafe { raise_user_trap(trap) }, + Ok(InvocationResult::YieldOutsideAsyncContext) => unsafe { + raise_lib_trap(Trap::lib(TrapCode::YieldOutsideAsyncContext)) + }, Err(panic) => unsafe { resume_panic(panic) }, } } @@ -953,6 +969,9 @@ macro_rules! impl_host_function { ) } Ok(InvocationResult::Trap(trap)) => unsafe { raise_user_trap(trap) }, + Ok(InvocationResult::YieldOutsideAsyncContext) => unsafe { + raise_lib_trap(Trap::lib(TrapCode::YieldOutsideAsyncContext)) + }, Err(panic) => unsafe { resume_panic(panic) }, } } diff --git a/lib/api/src/entities/store/inner.rs b/lib/api/src/entities/store/inner.rs index 875b8ec203c..0cd7479152b 100644 --- a/lib/api/src/entities/store/inner.rs +++ b/lib/api/src/entities/store/inner.rs @@ -13,7 +13,8 @@ use wasmer_vm::TrapHandlerFn; /// We require the context to have a fixed memory address for its lifetime since /// various bits of the VM have raw pointers that point back to it. Hence we /// wrap the actual context in a box. -pub(crate) struct StoreInner { +// TODO: make this private again +pub struct StoreInner { pub(crate) objects: StoreObjects, pub(crate) store: BackendStore, pub(crate) on_called: Option, diff --git a/lib/api/src/entities/store/store_ref.rs b/lib/api/src/entities/store/store_ref.rs index 6ec36ece852..e6e155d7b11 100644 --- a/lib/api/src/entities/store/store_ref.rs +++ b/lib/api/src/entities/store/store_ref.rs @@ -56,13 +56,16 @@ impl StoreMut<'_> { StoreObjects::same(&a.inner.objects, &b.inner.objects) } - #[allow(unused)] - pub(crate) fn as_raw(&self) -> *mut StoreInner { + /// TODO: temporarily public, make private again + pub fn as_raw(&self) -> *mut StoreInner { self.inner as *const StoreInner as *mut StoreInner } - #[allow(unused)] - pub(crate) unsafe fn from_raw(raw: *mut StoreInner) -> Self { + /// TODO: temporarily public, make private again + /// + /// # Safety + /// Don't use it XD + pub unsafe fn from_raw(raw: *mut StoreInner) -> Self { Self { inner: unsafe { &mut *raw }, } diff --git a/lib/api/tests/jspi_async.rs b/lib/api/tests/jspi_async.rs index 7aefa6ec943..915c1cdeea6 100644 --- a/lib/api/tests/jspi_async.rs +++ b/lib/api/tests/jspi_async.rs @@ -1,11 +1,17 @@ -use std::sync::OnceLock; +use std::{ + cell::RefCell, + pin::Pin, + sync::OnceLock, + task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, +}; use anyhow::Result; -use futures::future; +use futures::{FutureExt, future}; use wasmer::{ - Function, FunctionEnv, FunctionEnvMut, FunctionType, Instance, Module, Store, Type, Value, - imports, + AsStoreMut, Function, FunctionEnv, FunctionEnvMut, FunctionType, Instance, Module, + RuntimeError, Store, StoreMut, Type, Value, imports, }; +use wasmer_vm::TrapCode; #[derive(Default)] struct DeltaState { @@ -105,3 +111,332 @@ fn async_state_updates_follow_jspi_example() -> Result<()> { Ok(()) } + +#[test] +fn cannot_yield_when_not_in_async_context() -> Result<()> { + const WAT: &str = r#" + (module + (import "env" "yield_now" (func $yield_now)) + (func (export "yield_outside") + call $yield_now + ) + ) + "#; + let wasm = wat::parse_str(WAT).expect("valid WAT module"); + + let mut store = Store::default(); + let module = Module::new(&store, wasm)?; + + let yield_now = Function::new_async( + &mut store, + FunctionType::new(vec![], vec![]), + |_values| async move { + // Attempting to yield when not in an async context should trap. + tokio::task::yield_now().await; + Ok(vec![]) + }, + ); + + let import_object = imports! { + "env" => { + "yield_now" => yield_now, + } + }; + let instance = Instance::new(&mut store, &module, &import_object)?; + let yield_outside = instance.exports.get_function("yield_outside")?; + + let trap = yield_outside + .call(&mut store, &[]) + .expect_err("expected trap calling yield outside async context"); + + // TODO: wasm trace generation appears to be broken? + // assert!(!trap.trace().is_empty(), "should have a stack trace"); + let trap_code = trap.to_trap().expect("expected trap code"); + assert_eq!( + trap_code, + TrapCode::YieldOutsideAsyncContext, + "expected YieldOutsideAsyncContext trap code" + ); + + Ok(()) +} + +/* This test is slightly weird to explain; what we're testing here + is that multiple coroutines can be active at the same time, + and that they can be polled in any order. + + To achieve this, we have 2 main imports: + * spawn_future spawns new, pending futures, and polls them once. + The futures are set up in a way that they will suspend the first time + they are polled, and complete the second time. However, by polling + once, we will kickstart the corresponding coroutine into action, + which then stays active but suspended. + * resolve_future polls an already spawned future a second time, which + will cause it to be resolved. With this, we are once again activating + the coroutine. + + We also have the future_func export and the poll_future import. future_func + is the "body" of the inner coroutine, while poll_future retrieves the future + constructed by spawn_future and uses it to suspend the coroutine. + + Note that the coroutine will start executing twice, once during spawn_future + (which is a sync imported function) and once during resolve_future + (which is async). This way we ensure that any combination of active coroutines + is possible. + + The proper order of the log numbers for a given future is: + * spawning the future: + 10 + id: spawn_future called initially + 20 + id: the future is constructed, but not polled yet + 30 + id: future_func polled first time + 40 + id: poll_future called, suspending the coroutine + 50 + id: spawn_future finished + * resolving the future: + 60 + id: resolve_future called + 70 + id: future_func resumed second time + 80 + id: resolve_future finished +*/ +#[test] +fn async_multiple_active_coroutines() -> Result<()> { + const WAT: &str = r#" + (module + (import "env" "spawn_future" (func $spawn_future (param i32))) + (import "env" "poll_future" (func $poll_future (param i32))) + (import "env" "resolve_future" (func $resolve_future (param i32))) + (import "env" "yield_now" (func $yield_now)) + (import "env" "log" (func $log (param i32))) + (func (export "main") + (call $spawn_future (i32.const 0)) + (call $spawn_future (i32.const 1)) + (call $yield_now) + (call $spawn_future (i32.const 2)) + (call $resolve_future (i32.const 1)) + (call $yield_now) + (call $spawn_future (i32.const 3)) + (call $resolve_future (i32.const 2)) + (call $yield_now) + (call $resolve_future (i32.const 3)) + (call $resolve_future (i32.const 0)) + ) + (func (export "future_func") (param i32) (result i32) + (call $log (i32.add (i32.const 30) (local.get 0))) + (call $poll_future (local.get 0)) + (call $log (i32.add (i32.const 70) (local.get 0))) + (return (local.get 0)) + ) + ) + "#; + let wasm = wat::parse_str(WAT).expect("valid WAT module"); + + struct Yielder { + yielded: bool, + } + + impl Future for Yielder { + type Output = (); + + fn poll( + mut self: std::pin::Pin<&mut Self>, + _cx: &mut std::task::Context<'_>, + ) -> std::task::Poll { + if self.yielded { + std::task::Poll::Ready(()) + } else { + self.yielded = true; + std::task::Poll::Pending + } + } + } + + struct Env { + log: Vec, + futures: [Option, RuntimeError>>>>>; 4], + yielders: [Option; 4], + future_func: Option, + } + + let mut store = Store::default(); + let module = Module::new(&store, wasm)?; + + thread_local! { + static ENV: RefCell = RefCell::new(Env { + log: Vec::new(), + futures: [None, None, None, None], + yielders: [None, None, None, None], + future_func: None, + }) + } + let mut env = FunctionEnv::new(&mut store, ()); + + fn log(value: i32) { + ENV.with(|env| { + env.borrow_mut().log.push(value); + }); + } + + let spawn_future = Function::new_with_env( + &mut store, + &mut env, + FunctionType::new(vec![Type::I32], vec![]), + |mut env, values| { + ENV.with(|data| { + let ((), mut store) = env.data_and_store_mut(); + + let future_id = values[0].unwrap_i32(); + + log(future_id + 10); + + data.borrow_mut().yielders[future_id as usize] = Some(Yielder { yielded: false }); + + // This spawns the coroutine and the corresponding future + let store_raw = store.as_store_mut().as_raw(); + let func = data.borrow().future_func.as_ref().unwrap().clone(); + let mut future = Box::pin(async move { + let mut store = unsafe { StoreMut::<'static>::from_raw(store_raw) }; + let result = func.call_async(&mut store, &[Value::I32(future_id)]).await; + result + }); + log(future_id + 20); + + // We then poll it once to get it started - it'll suspend once, then + // complete the next time we poll it + let w = noop_waker(); + let mut cx = Context::from_waker(&w); + assert!(future.as_mut().poll(&mut cx).is_pending()); + + log(future_id + 50); + // We then store the future without letting it complete, and return + data.borrow_mut().futures[future_id as usize] = Some(future); + + Ok(vec![]) + }) + }, + ); + + let poll_future = Function::new_async( + &mut store, + FunctionType::new(vec![Type::I32], vec![]), + |values| { + let future_id = values[0].unwrap_i32(); + let yielder = ENV.with(|data| { + log(future_id + 40); + + let mut borrow = data.borrow_mut(); + let yielder = unsafe { + (borrow.yielders[future_id as usize].as_mut().unwrap() as *mut Yielder) + .as_mut::<'static>() + .unwrap() + }; + yielder + }); + yielder.map(|()| Ok(vec![])) + }, + ); + + let resolve_future = Function::new_async( + &mut store, + FunctionType::new(vec![Type::I32], vec![]), + |values| { + let future_id = values[0].unwrap_i32(); + + async move { + ENV.with(|data| { + log(future_id + 60); + + let mut future = data.borrow_mut().futures[future_id as usize] + .take() + .unwrap(); + + let w = noop_waker(); + let mut cx = Context::from_waker(&w); + let Poll::Ready(result) = future.as_mut().poll(&mut cx) else { + panic!("expected future to be ready"); + }; + let result_id = result.unwrap()[0].unwrap_i32(); + assert_eq!(result_id, future_id); + + log(future_id + 80); + + Ok(vec![]) + }) + } + }, + ); + + let yield_now = Function::new_async( + &mut store, + FunctionType::new(vec![], vec![]), + |_values| async move { + tokio::task::yield_now().await; + Ok(vec![]) + }, + ); + + let log = Function::new( + &mut store, + FunctionType::new(vec![Type::I32], vec![]), + |values| { + let value = values[0].unwrap_i32(); + log(value); + Ok(vec![]) + }, + ); + + let import_object = imports! { + "env" => { + "spawn_future" => spawn_future, + "poll_future" => poll_future, + "resolve_future" => resolve_future, + "yield_now" => yield_now, + "log" => log, + } + }; + let instance = Instance::new(&mut store, &module, &import_object)?; + + ENV.with(|env| { + env.borrow_mut().future_func = Some( + instance + .exports + .get_function("future_func") + .unwrap() + .clone(), + ) + }); + + let main = instance.exports.get_function("main")?; + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap() + .block_on(main.call_async(&mut store, &[]))?; + + ENV.with(|env| { + assert_eq!( + env.borrow().log, + vec![ + 10, 20, 30, 40, 50, // future 0 spawned + 11, 21, 31, 41, 51, // future 1 spawned + 12, 22, 32, 42, 52, // future 2 spawned + 61, 71, 81, // future 1 resolved + 13, 23, 33, 43, 53, // future 3 spawned + 62, 72, 82, // future 2 resolved + 63, 73, 83, // future 3 resolved + 60, 70, 80, // future 0 resolved + ] + ); + }); + + Ok(()) +} + +fn noop_waker() -> Waker { + fn noop_raw_waker() -> RawWaker { + fn no_op(_: *const ()) {} + fn clone(_: *const ()) -> RawWaker { + noop_raw_waker() + } + let vtable = &RawWakerVTable::new(clone, no_op, no_op, no_op); + RawWaker::new(std::ptr::null(), vtable) + } + unsafe { Waker::from_raw(noop_raw_waker()) } +} diff --git a/lib/types/src/trapcode.rs b/lib/types/src/trapcode.rs index f1bee87d1a4..2e85a785a83 100644 --- a/lib/types/src/trapcode.rs +++ b/lib/types/src/trapcode.rs @@ -66,6 +66,10 @@ pub enum TrapCode { /// A throw_ref was executed but the exnref was not initialized. UninitializedExnRef = 12, + + /// An async imported function tried to yield when not called + /// via `Function::call_async`. + YieldOutsideAsyncContext = 13, } impl TrapCode { @@ -85,6 +89,9 @@ impl TrapCode { Self::UnalignedAtomic => "unaligned atomic access", Self::UncaughtException => "uncaught exception", Self::UninitializedExnRef => "uninitialized exnref", + Self::YieldOutsideAsyncContext => { + "async imported function yielded when not called via `Function::call_async`" + } } } } @@ -105,6 +112,7 @@ impl Display for TrapCode { Self::UnalignedAtomic => "unalign_atom", Self::UncaughtException => "uncaught_exception", Self::UninitializedExnRef => "uninitialized_exnref", + Self::YieldOutsideAsyncContext => "yield_outside_async_context", }; f.write_str(identifier) } @@ -128,6 +136,7 @@ impl FromStr for TrapCode { "unalign_atom" => Ok(Self::UnalignedAtomic), "uncaught_exception" => Ok(Self::UncaughtException), "uninitialized_exnref" => Ok(Self::UninitializedExnRef), + "yield_outside_async_context" => Ok(Self::YieldOutsideAsyncContext), _ => Err(()), } } From b19c393869f7c10d990802a7cfd6653685381f8d Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Wed, 12 Nov 2025 17:26:09 +0400 Subject: [PATCH 10/49] Simplify the async runtime state to just a stack of context pointers --- lib/api/src/backend/sys/async_runtime.rs | 159 ++++++++--------------- 1 file changed, 56 insertions(+), 103 deletions(-) diff --git a/lib/api/src/backend/sys/async_runtime.rs b/lib/api/src/backend/sys/async_runtime.rs index 05f9c9ad146..f1f66b97e5b 100644 --- a/lib/api/src/backend/sys/async_runtime.rs +++ b/lib/api/src/backend/sys/async_runtime.rs @@ -16,6 +16,26 @@ use crate::{AsStoreMut, RuntimeError, Value}; type HostFuture = Pin, RuntimeError>> + Send + 'static>>; +pub(crate) fn call_function_async<'a, S>( + function: SysFunction, + store: &'a mut S, + params: Vec, +) -> AsyncCallFuture<'a, S> +where + S: AsStoreMut + 'static, +{ + AsyncCallFuture::new(function, store, params) +} + +enum AsyncYield { + HostFuture(HostFuture), +} + +enum AsyncResume { + Start, + HostFutureReady(Result, RuntimeError>), +} + pub(crate) struct AsyncCallFuture<'a, S: AsStoreMut + 'static> { coroutine: Option, RuntimeError>>>, pending_future: Option, @@ -35,11 +55,12 @@ where let coroutine = Coroutine::new(move |yielder: &Yielder, _resume| { let ctx_state = CoroutineContext::new(yielder); - let _guard = ctx_state.enter(); + ctx_state.enter(); let result = { let store_ref = unsafe { &mut *store_ptr }; function.call(store_ref, ¶ms) }; + ctx_state.leave(); result }); @@ -89,16 +110,6 @@ where } } -struct AsyncContext { - next: u64, - active: Vec, - coroutines: HashMap, -} - -thread_local! { - static CURRENT_CONTEXT: RefCell> = const { RefCell::new(None) }; -} - pub enum AsyncRuntimeError { YieldOutsideAsyncContext, RuntimeError(RuntimeError), @@ -109,8 +120,7 @@ where Fut: Future, RuntimeError>> + Send + 'static, { CURRENT_CONTEXT.with(|cell| { - let mut borrow = cell.borrow_mut(); - match borrow.as_ref().and_then(|ctx| ctx.active.last().copied()) { + match CoroutineContext::get_current() { None => { // If there is no async context or we haven't entered it, // we can still directly run a future that doesn't block @@ -119,44 +129,23 @@ where // coroutine in the following scenario: // call_async -> wasm code -> imported function -> // call (non-async) -> wasm_code -> imported async function - - // We drop the borrow anyway to let the future start a new - // async context if needed. - drop(borrow); run_immediate(future) } - Some(current) => { - // If we have an active coroutine, get the context and yielder - let context = borrow.as_mut().unwrap(); - let coro_context = *context - .coroutines - .get(¤t) - .expect("Coroutine context was already destroyed"); + Some(context) => { + let ctx_ref = unsafe { context.as_ref().expect("valid context pointer") }; - // Then pop ourselves from the active stack since we're not active anymore - // and un-borrow the cell for others to use. - assert_eq!( - context.active.pop(), - Some(current), - "Active coroutine stack corrupted" - ); - drop(borrow); + // Leave the coroutine context since we're yielding back to the + // parent stack, and will be inactive until the future is ready. + ctx_ref.leave(); // Now we can yield back to the runtime while we wait - let result = unsafe { &*coro_context } + let result = ctx_ref .block_on_future(Box::pin(future)) .map_err(AsyncRuntimeError::RuntimeError); // Once the future is ready, we borrow again and restore the current // coroutine. - let mut borrow = cell.borrow_mut(); - let context = borrow - .as_mut() - .expect("The async context was destroyed while a coroutine was pending"); - if !context.coroutines.contains_key(¤t) { - panic!("The coroutine context was destroyed while a coroutine was pending"); - } - context.active.push(current); + ctx_ref.enter(); result } @@ -164,24 +153,8 @@ where }) } -pub(crate) fn call_function_async<'a, S>( - function: SysFunction, - store: &'a mut S, - params: Vec, -) -> AsyncCallFuture<'a, S> -where - S: AsStoreMut + 'static, -{ - AsyncCallFuture::new(function, store, params) -} - -enum AsyncYield { - HostFuture(HostFuture), -} - -enum AsyncResume { - Start, - HostFutureReady(Result, RuntimeError>), +thread_local! { + static CURRENT_CONTEXT: RefCell> = const { RefCell::new(Vec::new()) }; } struct CoroutineContext { @@ -195,34 +168,39 @@ impl CoroutineContext { } } - fn enter(&self) -> CoroutineContextGuard { + fn enter(&self) { CURRENT_CONTEXT.with(|cell| { let mut borrow = cell.borrow_mut(); - // If there is no context yet, create one. - if borrow.is_none() { - *borrow = Some(AsyncContext { - next: 0, - active: vec![], - coroutines: HashMap::new(), - }); - } + // Push this coroutine on top of the active stack. + borrow.push(self as *const _); + }) + } - let context = borrow.as_mut().unwrap(); + // Note: we don't use a drop-style guard here on purpose; if a panic + // happens while a coroutine is running, CURRENT_CONTEXT will be in + // an inconsistent state. corosensei will unwind all coroutine stacks + // anyway, and if we had a guard that would get dropped and try to + // leave its context, it'd panic again at the assert_eq! below. + fn leave(&self) { + CURRENT_CONTEXT.with(|cell| { + let mut borrow = cell.borrow_mut(); - // Assign an ID to this coroutine context. - let id = context.next; - context.next += 1; + // Pop this coroutine from the active stack. + assert_eq!( + borrow.pop(), + Some(self as *const _), + "Active coroutine stack corrupted" + ); + }); + } - // Add this context to the map and push it on top of the active stack. - context.coroutines.insert(id, self as *const _); - context.active.push(id); - CoroutineContextGuard { id } - }) + fn get_current() -> Option<*const Self> { + CURRENT_CONTEXT.with(|cell| cell.borrow().last().copied()) } fn block_on_future(&self, future: HostFuture) -> Result, RuntimeError> { - let yielder = unsafe { &*self.yielder }; + let yielder = unsafe { self.yielder.as_ref().expect("yielder pointer valid") }; match yielder.suspend(AsyncYield::HostFuture(future)) { AsyncResume::HostFutureReady(result) => result, AsyncResume::Start => unreachable!("coroutine resumed without start"), @@ -230,31 +208,6 @@ impl CoroutineContext { } } -struct CoroutineContextGuard { - id: u64, -} - -impl Drop for CoroutineContextGuard { - fn drop(&mut self) { - CURRENT_CONTEXT.with(|cell| { - let mut borrow = cell.borrow_mut(); - let context = borrow - .as_mut() - .expect("The async context was destroyed while a coroutine was active"); - context.coroutines.remove(&self.id); - assert_eq!( - context.active.pop(), - Some(self.id), - "Active coroutine stack corrupted" - ); - if context.coroutines.is_empty() { - // If there are no more coroutines, remove the context. - *borrow = None; - } - }); - } -} - fn run_immediate( future: impl Future, RuntimeError>> + Send + 'static, ) -> Result, AsyncRuntimeError> { From a4f9896e309d9bc1582a6836f018465621267617 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Mon, 10 Nov 2025 13:05:04 +0100 Subject: [PATCH 11/49] Added typed function implementation --- .../src/backend/sys/entities/function/mod.rs | 64 ++++--- lib/api/src/entities/function/async_host.rs | 178 ++++++++++++++++++ lib/api/src/entities/function/inner.rs | 35 ++-- lib/api/src/entities/function/mod.rs | 37 ++-- lib/api/tests/jspi_async.rs | 63 ++++++- lib/vm/src/store.rs | 2 +- 6 files changed, 311 insertions(+), 68 deletions(-) create mode 100644 lib/api/src/entities/function/async_host.rs diff --git a/lib/api/src/backend/sys/entities/function/mod.rs b/lib/api/src/backend/sys/entities/function/mod.rs index 1613ff32732..6780ba390bb 100644 --- a/lib/api/src/backend/sys/entities/function/mod.rs +++ b/lib/api/src/backend/sys/entities/function/mod.rs @@ -7,6 +7,7 @@ use crate::{ BackendFunction, FunctionEnv, FunctionEnvMut, FunctionType, HostFunction, RuntimeError, StoreInner, Value, WithEnv, WithoutEnv, backend::sys::{engine::NativeEngineExt, vm::VMFunctionCallback}, + entities::function::async_host::{AsyncFunctionEnv, AsyncHostFunction}, entities::store::{AsStoreMut, AsStoreRef, StoreMut}, sys::async_runtime::AsyncRuntimeError, utils::{FromToNativeWasmType, IntoResult, NativeWasmTypeInto, WasmTypeList}, @@ -14,7 +15,8 @@ use crate::{ }; use std::panic::{self, AssertUnwindSafe}; use std::{ - cell::UnsafeCell, cmp::max, error::Error, ffi::c_void, future::Future, pin::Pin, sync::Arc, + cell::UnsafeCell, cmp::max, error::Error, ffi::c_void, future::Future, marker::PhantomData, + pin::Pin, sync::Arc, }; use wasmer_types::{NativeWasmType, RawValue}; use wasmer_vm::{ @@ -257,37 +259,51 @@ impl Function { } } - pub(crate) fn new_typed_async( - store: &mut impl AsStoreMut, - func: F, - ) -> Self + pub(crate) fn new_typed_async(store: &mut impl AsStoreMut, func: F) -> Self where - F: Fn(Args) -> Fut + 'static + Send + Sync, - Fut: Future + 'static + Send, - Args: WasmTypeList, - Rets: WasmTypeList, - RetsAsResult: IntoResult, + Args: WasmTypeList + 'static, + Rets: WasmTypeList + 'static, + F: AsyncHostFunction<(), Args, Rets, WithoutEnv> + Send + Sync + 'static, { let env = FunctionEnv::new(store, ()); + let signature = FunctionType::new(Args::wasm_types(), Rets::wasm_types()); + let args_sig = Arc::new(signature.clone()); + let results_sig = Arc::new(signature.clone()); let func = Arc::new(func); - Self::new_typed_with_env_async(store, &env, move |_env, args| { + Self::new_with_env_async(store, &env, signature, move |mut env_mut, values| -> Pin< + Box, RuntimeError>> + Send>, + > { + let raw_store = RawStorePtr { + ptr: env_mut.as_store_mut().as_raw(), + }; + let args_sig = args_sig.clone(); + let results_sig = results_sig.clone(); let func = func.clone(); - func(args) + let args = + match typed_args_from_values::(raw_store, args_sig.as_ref(), values) { + Ok(args) => args, + Err(err) => return Box::pin(async { Err(err) }), + }; + let future = func + .as_ref() + .call_async(AsyncFunctionEnv::new(), args); + Box::pin(async move { + let typed_result = future.await?; + typed_results_to_values::(raw_store, results_sig.as_ref(), typed_result) + }) }) } - pub(crate) fn new_typed_with_env_async( + pub(crate) fn new_typed_with_env_async( store: &mut impl AsStoreMut, env: &FunctionEnv, func: F, ) -> Self where T: Send + 'static, - F: Fn(FunctionEnvMut, Args) -> Fut + 'static + Send + Sync, - Fut: Future + 'static + Send, - Args: WasmTypeList, - Rets: WasmTypeList, - RetsAsResult: IntoResult, + F: AsyncHostFunction + Send + Sync + 'static, + Args: WasmTypeList + 'static, + Rets: WasmTypeList + 'static, { let signature = FunctionType::new(Args::wasm_types(), Rets::wasm_types()); let args_sig = Arc::new(signature.clone()); @@ -302,16 +318,16 @@ impl Function { let args_sig = args_sig.clone(); let results_sig = results_sig.clone(); let func = func.clone(); - let args = match typed_args_from_values::(raw_store, args_sig.as_ref(), values) { + let args = + match typed_args_from_values::(raw_store, args_sig.as_ref(), values) { Ok(args) => args, Err(err) => return Box::pin(async { Err(err) }), }; - let future = (*func)(env_mut, args); + let future = func + .as_ref() + .call_async(AsyncFunctionEnv::with_env(env_mut), args); Box::pin(async move { - let typed_result = future - .await - .into_result() - .map_err(|err| RuntimeError::user(Box::new(err)))?; + let typed_result = future.await?; typed_results_to_values::(raw_store, results_sig.as_ref(), typed_result) }) }) diff --git a/lib/api/src/entities/function/async_host.rs b/lib/api/src/entities/function/async_host.rs new file mode 100644 index 00000000000..f611ab88c2a --- /dev/null +++ b/lib/api/src/entities/function/async_host.rs @@ -0,0 +1,178 @@ +use std::{future::Future, marker::PhantomData, pin::Pin}; + +use crate::{ + FunctionEnvMut, HostFunctionKind, RuntimeError, WasmTypeList, WithEnv, WithoutEnv, + utils::{FromToNativeWasmType, IntoResult}, +}; + +/// Wrapper conveying whether an async host function receives an environment. +pub enum AsyncFunctionEnv<'a, T, Kind> { + /// Used by host functions without an environment. + WithoutEnv(PhantomData<(&'a T, Kind)>), + /// Used by host functions that capture an environment. + WithEnv(FunctionEnvMut<'a, T>), +} + +impl<'a, T> AsyncFunctionEnv<'a, T, WithoutEnv> { + /// Create an environment wrapper for functions without host state. + pub fn new() -> Self { + Self::WithoutEnv(PhantomData) + } +} + +impl<'a, T> AsyncFunctionEnv<'a, T, WithEnv> { + /// Create an environment wrapper carrying [`FunctionEnvMut`]. + pub fn with_env(env: FunctionEnvMut<'a, T>) -> Self { + Self::WithEnv(env) + } + + /// Extract the underlying [`FunctionEnvMut`]. + pub fn into_env(self) -> FunctionEnvMut<'a, T> { + match self { + Self::WithEnv(env) => env, + Self::WithoutEnv(_) => unreachable!("with-env async function called without env"), + } + } +} + +/// Async counterpart to [`HostFunction`](super::host::HostFunction). +pub trait AsyncHostFunction +where + Args: WasmTypeList + 'static, + Rets: WasmTypeList, + Kind: HostFunctionKind, +{ + /// Invoke the host function asynchronously. + fn call_async( + &self, + env: AsyncFunctionEnv<'_, T, Kind>, + args: Args, + ) -> Pin> + Send>>; +} + +macro_rules! impl_async_host_function_without_env { + ( $( $x:ident ),* ) => { + impl<$( $x, )* Rets, RetsAsResult, F, Fut > AsyncHostFunction<(), ( $( $x ),* ), Rets, WithoutEnv> for F + where + F: Fn($( $x ),*) -> Fut + Send + Sync + 'static, + Fut: Future + Send + 'static, + RetsAsResult: IntoResult, + Rets: WasmTypeList, + ( $( $x ),* ): WasmTypeList + 'static, + $( $x: FromToNativeWasmType + 'static, )* + { + fn call_async( + &self, + _env: AsyncFunctionEnv<'_, (), WithoutEnv>, + args: ( $( $x ),* ), + ) -> Pin> + Send>> { + #[allow(non_snake_case)] + let ( $( $x ),* ) = args; + let fut = (self)( $( $x ),* ); + Box::pin(async move { + fut.await + .into_result() + .map_err(|err| RuntimeError::user(Box::new(err))) + }) + } + } + }; +} + +macro_rules! impl_async_host_function_with_env { + ( $( $x:ident ),* ) => { + impl<$( $x, )* Rets, RetsAsResult, T, F, Fut > AsyncHostFunction for F + where + T: Send + 'static, + F: Fn(FunctionEnvMut<'_, T>, $( $x ),*) -> Fut + Send + 'static, + Fut: Future + Send + 'static, + RetsAsResult: IntoResult, + Rets: WasmTypeList, + ( $( $x ),* ): WasmTypeList + 'static, + $( $x: FromToNativeWasmType + 'static, )* + { + fn call_async( + &self, + env: AsyncFunctionEnv<'_, T, WithEnv>, + args: ( $( $x ),* ), + ) -> Pin> + Send>> { + #[allow(non_snake_case)] + let ( $( $x ),* ) = args; + let fut = (self)(env.into_env(), $( $x ),* ); + Box::pin(async move { + fut.await + .into_result() + .map_err(|err| RuntimeError::user(Box::new(err))) + }) + } + } + }; +} + +impl_async_host_function_without_env!(); +impl_async_host_function_without_env!(A1); +impl_async_host_function_without_env!(A1, A2); +impl_async_host_function_without_env!(A1, A2, A3); +impl_async_host_function_without_env!(A1, A2, A3, A4); +impl_async_host_function_without_env!(A1, A2, A3, A4, A5); +impl_async_host_function_without_env!(A1, A2, A3, A4, A5, A6); +impl_async_host_function_without_env!(A1, A2, A3, A4, A5, A6, A7); +impl_async_host_function_without_env!(A1, A2, A3, A4, A5, A6, A7, A8); +impl_async_host_function_without_env!(A1, A2, A3, A4, A5, A6, A7, A8, A9); +impl_async_host_function_without_env!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10); +impl_async_host_function_without_env!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11); +impl_async_host_function_without_env!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12); +impl_async_host_function_without_env!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13); +impl_async_host_function_without_env!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14); +impl_async_host_function_without_env!( + A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15 +); +impl_async_host_function_without_env!( + A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16 +); +impl_async_host_function_without_env!( + A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17 +); +impl_async_host_function_without_env!( + A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18 +); +impl_async_host_function_without_env!( + A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19 +); +impl_async_host_function_without_env!( + A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20 +); + +impl_async_host_function_with_env!(); +impl_async_host_function_with_env!(A1); +impl_async_host_function_with_env!(A1, A2); +impl_async_host_function_with_env!(A1, A2, A3); +impl_async_host_function_with_env!(A1, A2, A3, A4); +impl_async_host_function_with_env!(A1, A2, A3, A4, A5); +impl_async_host_function_with_env!(A1, A2, A3, A4, A5, A6); +impl_async_host_function_with_env!(A1, A2, A3, A4, A5, A6, A7); +impl_async_host_function_with_env!(A1, A2, A3, A4, A5, A6, A7, A8); +impl_async_host_function_with_env!(A1, A2, A3, A4, A5, A6, A7, A8, A9); +impl_async_host_function_with_env!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10); +impl_async_host_function_with_env!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11); +impl_async_host_function_with_env!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12); +impl_async_host_function_with_env!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13); +impl_async_host_function_with_env!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14); +impl_async_host_function_with_env!( + A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15 +); +impl_async_host_function_with_env!( + A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16 +); +impl_async_host_function_with_env!( + A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17 +); +impl_async_host_function_with_env!( + A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18 +); +impl_async_host_function_with_env!( + A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19 +); +impl_async_host_function_with_env!( + A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20 +); diff --git a/lib/api/src/entities/function/inner.rs b/lib/api/src/entities/function/inner.rs index 0e6e87ee009..778908f67bc 100644 --- a/lib/api/src/entities/function/inner.rs +++ b/lib/api/src/entities/function/inner.rs @@ -1,13 +1,13 @@ -use std::{future::Future, pin::Pin}; +use std::pin::Pin; use wasmer_types::{FunctionType, RawValue}; use crate::{ AsStoreMut, AsStoreRef, ExportError, Exportable, Extern, FunctionEnv, FunctionEnvMut, HostFunction, StoreMut, StoreRef, TypedFunction, Value, WasmTypeList, WithEnv, WithoutEnv, + entities::function::async_host::AsyncHostFunction, error::RuntimeError, macros::backend::{gen_rt_ty, match_rt}, - utils::IntoResult, vm::{VMExtern, VMExternFunction, VMFuncRef}, }; @@ -147,8 +147,8 @@ impl BackendFunction { pub fn new_typed(store: &mut impl AsStoreMut, func: F) -> Self where F: HostFunction<(), Args, Rets, WithoutEnv> + 'static + Send + Sync, - Args: WasmTypeList, - Rets: WasmTypeList, + Args: WasmTypeList + 'static, + Rets: WasmTypeList + 'static, { match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] @@ -206,8 +206,8 @@ impl BackendFunction { ) -> Self where F: HostFunction + 'static + Send + Sync, - Args: WasmTypeList, - Rets: WasmTypeList, + Args: WasmTypeList + 'static, + Rets: WasmTypeList + 'static, { match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] @@ -308,16 +308,11 @@ impl BackendFunction { } #[inline] - pub fn new_typed_async( - store: &mut impl AsStoreMut, - func: F, - ) -> Self + pub fn new_typed_async(store: &mut impl AsStoreMut, func: F) -> Self where - F: Fn(Args) -> Fut + 'static + Send + Sync, - Fut: Future + 'static + Send, - Args: WasmTypeList, - Rets: WasmTypeList, - RetsAsResult: IntoResult, + F: AsyncHostFunction<(), Args, Rets, WithoutEnv> + Send + Sync + 'static, + Args: WasmTypeList + 'static, + Rets: WasmTypeList + 'static, { match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] @@ -338,17 +333,15 @@ impl BackendFunction { } #[inline] - pub fn new_typed_with_env_async( + pub fn new_typed_with_env_async( store: &mut impl AsStoreMut, env: &FunctionEnv, func: F, ) -> Self where - F: Fn(FunctionEnvMut, Args) -> Fut + 'static + Send + Sync, - Fut: Future + 'static + Send, - Args: WasmTypeList, - Rets: WasmTypeList, - RetsAsResult: IntoResult, + F: AsyncHostFunction + Send + Sync + 'static, + Args: WasmTypeList + 'static, + Rets: WasmTypeList + 'static, { match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] diff --git a/lib/api/src/entities/function/mod.rs b/lib/api/src/entities/function/mod.rs index 564cc58d486..0def8df4eaa 100644 --- a/lib/api/src/entities/function/mod.rs +++ b/lib/api/src/entities/function/mod.rs @@ -10,6 +10,9 @@ pub use host::*; pub(crate) mod env; pub use env::*; +pub(crate) mod async_host; +pub use async_host::{AsyncFunctionEnv, AsyncHostFunction}; + use std::{future::Future, pin::Pin}; use wasmer_types::{FunctionType, RawValue}; @@ -18,7 +21,6 @@ use crate::{ AsStoreMut, AsStoreRef, ExportError, Exportable, Extern, StoreMut, StoreRef, TypedFunction, Value, WasmTypeList, error::RuntimeError, - utils::IntoResult, vm::{VMExtern, VMExternFunction, VMFuncRef}, }; @@ -116,8 +118,8 @@ impl Function { pub fn new_typed(store: &mut impl AsStoreMut, func: F) -> Self where F: HostFunction<(), Args, Rets, WithoutEnv> + 'static + Send + Sync, - Args: WasmTypeList, - Rets: WasmTypeList, + Args: WasmTypeList + 'static, + Rets: WasmTypeList + 'static, { Self(BackendFunction::new_typed(store, func)) } @@ -147,8 +149,8 @@ impl Function { ) -> Self where F: HostFunction + 'static + Send + Sync, - Args: WasmTypeList, - Rets: WasmTypeList, + Args: WasmTypeList + 'static, + Rets: WasmTypeList + 'static, { Self(BackendFunction::new_typed_with_env(store, env, func)) } @@ -187,33 +189,26 @@ impl Function { /// Creates a new async host `Function` from a native typed function. /// /// The future can return either the raw result tuple or any type that implements - /// [`IntoResult`] for the result tuple (e.g. `Result`). - pub fn new_typed_async( - store: &mut impl AsStoreMut, - func: F, - ) -> Self + /// [`IntoResult`] for the result tuple (e.g. `Result(store: &mut impl AsStoreMut, func: F) -> Self where - F: Fn(Args) -> Fut + 'static + Send + Sync, - Fut: Future + 'static + Send, - Args: WasmTypeList, - Rets: WasmTypeList, - RetsAsResult: IntoResult, + Rets: WasmTypeList + 'static, + Args: WasmTypeList + 'static, + F: AsyncHostFunction<(), Args, Rets, WithoutEnv> + Send + Sync + 'static, { Self(BackendFunction::new_typed_async(store, func)) } /// Creates a new async host `Function` with an environment from a typed function. - pub fn new_typed_with_env_async( + pub fn new_typed_with_env_async( store: &mut impl AsStoreMut, env: &FunctionEnv, func: F, ) -> Self where - F: Fn(FunctionEnvMut, Args) -> Fut + 'static + Send + Sync, - Fut: Future + 'static + Send, - Args: WasmTypeList, - Rets: WasmTypeList, - RetsAsResult: IntoResult, + Rets: WasmTypeList + 'static, + Args: WasmTypeList + 'static, + F: AsyncHostFunction + Send + Sync + 'static, { Self(BackendFunction::new_typed_with_env_async(store, env, func)) } diff --git a/lib/api/tests/jspi_async.rs b/lib/api/tests/jspi_async.rs index 915c1cdeea6..408eb3bcaa1 100644 --- a/lib/api/tests/jspi_async.rs +++ b/lib/api/tests/jspi_async.rs @@ -9,7 +9,7 @@ use anyhow::Result; use futures::{FutureExt, future}; use wasmer::{ AsStoreMut, Function, FunctionEnv, FunctionEnvMut, FunctionType, Instance, Module, - RuntimeError, Store, StoreMut, Type, Value, imports, + RuntimeError, Store, StoreMut, Type, TypedFunction, Value, imports, }; use wasmer_vm::TrapCode; @@ -112,6 +112,67 @@ fn async_state_updates_follow_jspi_example() -> Result<()> { Ok(()) } +#[test] +fn typed_async_host_and_calls_work() -> Result<()> { + let wasm = wat::parse_str( + r#" + (module + (import "host" "async_add" (func $async_add (param i32 i32) (result i32))) + (import "host" "async_double" (func $async_double (param i32) (result i32))) + (func (export "compute") (param i32) (result i32) + local.get 0 + i32.const 10 + call $async_add + local.get 0 + call $async_double + i32.add)) + "#, + )?; + + #[derive(Clone, Copy)] + struct AddBias { + bias: i32, + } + + let mut store = Store::default(); + let module = Module::new(&store, wasm)?; + + let add_env = FunctionEnv::new(&mut store, AddBias { bias: 5 }); + let async_add = Function::new_typed_with_env_async( + &mut store, + &add_env, + async move |mut env: FunctionEnvMut, a: i32, b: i32| { + let bias = env.data().bias; + future::ready(()).await; + Ok(a + b + bias) + }, + ); + let async_double = Function::new_typed_async(&mut store, async move |value: i32| { + future::ready(()).await; + Ok(value * 2) + }); + + let import_object = imports! { + "host" => { + "async_add" => async_add, + "async_double" => async_double, + } + }; + + let instance = Instance::new(&mut store, &module, &import_object)?; + let compute: TypedFunction = + instance.exports.get_typed_function(&mut store, "compute")?; + + let result = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap() + .block_on(compute.call_async(&mut store, 4))?; + assert_eq!(result, 27); + + Ok(()) +} + #[test] fn cannot_yield_when_not_in_async_context() -> Result<()> { const WAT: &str = r#" diff --git a/lib/vm/src/store.rs b/lib/vm/src/store.rs index a4ded1927b9..0611e74912a 100644 --- a/lib/vm/src/store.rs +++ b/lib/vm/src/store.rs @@ -241,7 +241,7 @@ pub struct InternalStoreHandle { #[cfg(feature = "artifact-size")] impl loupe::MemoryUsage for InternalStoreHandle { fn size_of_val(&self, _tracker: &mut dyn loupe::MemoryUsageTracker) -> usize { - std::mem::size_of_val(&self) + std::mem::size_of_val(self) } } From 84733451d55070d3f38dd89959be93a257fab38b Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Fri, 14 Nov 2025 18:16:48 +0400 Subject: [PATCH 12/49] Remove a bunch of unused code --- lib/api/src/backend/sys/entities/store/mod.rs | 34 -------------- lib/api/src/backend/sys/entities/store/obj.rs | 47 ------------------- 2 files changed, 81 deletions(-) diff --git a/lib/api/src/backend/sys/entities/store/mod.rs b/lib/api/src/backend/sys/entities/store/mod.rs index 5764b10b8aa..2763b2c08c3 100644 --- a/lib/api/src/backend/sys/entities/store/mod.rs +++ b/lib/api/src/backend/sys/entities/store/mod.rs @@ -70,18 +70,6 @@ impl NativeStoreExt for Store { } } -impl NativeStoreExt for crate::Store { - fn set_trap_handler(&mut self, handler: Option>>) { - self.inner.store.as_sys_mut().set_trap_handler(handler) - } - - /// The signal handler - #[inline] - fn signal_handler(&self) -> Option<*const TrapHandlerFn<'static>> { - self.inner.store.as_sys().signal_handler() - } -} - impl crate::BackendStore { /// Consume [`self`] into [`crate::backend::sys::store::Store`]. pub fn into_sys(self) -> crate::backend::sys::store::Store { @@ -111,25 +99,3 @@ impl crate::BackendStore { matches!(self, Self::Sys(_)) } } - -impl crate::Store { - /// Consume [`self`] into [`crate::backend::sys::store::Store`]. - pub(crate) fn into_sys(self) -> crate::backend::sys::store::Store { - self.inner.store.into_sys() - } - - /// Convert a reference to [`self`] into a reference [`crate::backend::sys::store::Store`]. - pub(crate) fn as_sys(&self) -> &crate::backend::sys::store::Store { - self.inner.store.as_sys() - } - - /// Convert a mutable reference to [`self`] into a mutable reference [`crate::backend::sys::store::Store`]. - pub(crate) fn as_sys_mut(&mut self) -> &mut crate::backend::sys::store::Store { - self.inner.store.as_sys_mut() - } - - /// Return true if [`self`] is a store from the `sys` runtime. - pub fn is_sys(&self) -> bool { - self.inner.store.is_sys() - } -} diff --git a/lib/api/src/backend/sys/entities/store/obj.rs b/lib/api/src/backend/sys/entities/store/obj.rs index ba90370dc1b..0bb27b89670 100644 --- a/lib/api/src/backend/sys/entities/store/obj.rs +++ b/lib/api/src/backend/sys/entities/store/obj.rs @@ -27,50 +27,3 @@ impl crate::StoreObjects { } } } - -//pub trait GetStoreObjects { -// /// Return a mutable reference to [`wasmer_vm::StoreObjects`] and a reference to the current -// /// engine. -// fn engine_and_objects_mut( -// &mut self, -// ) -> (&crate::Engine, &mut crate::backend::sys::store::StoreObjects); -// -// /// Return a mutable reference to [`wasmer_vm::StoreObjects`]. -// fn objects_mut(&mut self) -> &mut crate::backend::sys::store::StoreObjects; -//} -// -//impl GetStoreObjects for crate::StoreInner { -// fn objects_mut(&mut self) -> &mut crate::backend::sys::store::StoreObjects { -// self.objects.as_sys_mut() -// } -// -// fn engine_and_objects_mut( -// &mut self, -// ) -> (&crate::Engine, &mut crate::backend::sys::store::StoreObjects) { -// match (&mut self.objects, &self.store) { -// (crate::StoreObjects::Sys(o), crate::BackendStore::Sys(s)) => (&s.engine, o), -// _ => panic!("Not a `sys` store!"), -// } -// } -//} -// -//impl GetStoreObjects for T { -// fn objects_mut<'a>(&'a mut self) -> &'a mut crate::backend::sys::store::StoreObjects { -// match self.as_store_mut().inner.objects { -// crate::StoreObjects::Sys(ref mut s) => s, -// _ => panic!("Not a `sys` store!"), -// } -// } -// -// fn engine_and_objects_mut( -// &mut self, -// ) -> (&crate::Engine, &mut crate::backend::sys::store::StoreObjects) { -// let mut store = self.as_store_mut(); -// match (&mut store.inner.objects, &store.inner.store) { -// (crate::StoreObjects::Sys(ref mut o), crate::BackendStore::Sys(ref s)) => { -// (&s.engine, o) -// } -// _ => panic!("Not a `sys` store!"), -// } -// } -//} From 9fcde4b81bf2c0f71a49e3832e4d897755f55b8a Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Tue, 18 Nov 2025 18:57:29 +0400 Subject: [PATCH 13/49] WIP: Rework store refs, sync function calls now work --- Cargo.lock | 13 + Cargo.toml | 4 +- lib/api/Cargo.toml | 1 + lib/api/src/backend/sys/entities/exception.rs | 8 +- lib/api/src/backend/sys/entities/external.rs | 4 +- .../src/backend/sys/entities/function/env.rs | 30 +- .../src/backend/sys/entities/function/mod.rs | 487 ++++---- .../backend/sys/entities/function/typed.rs | 113 +- lib/api/src/backend/sys/entities/global.rs | 22 +- lib/api/src/backend/sys/entities/instance.rs | 5 +- .../src/backend/sys/entities/memory/mod.rs | 24 +- .../src/backend/sys/entities/memory/view.rs | 12 +- lib/api/src/backend/sys/entities/module.rs | 16 +- lib/api/src/backend/sys/entities/table.rs | 22 +- lib/api/src/backend/sys/entities/tag.rs | 4 +- lib/api/src/backend/sys/tunables.rs | 8 +- lib/api/src/entities/engine/engine_ref.rs | 4 +- lib/api/src/entities/exception/inner.rs | 2 +- lib/api/src/entities/external/extref/inner.rs | 4 +- lib/api/src/entities/function/env/inner.rs | 22 +- lib/api/src/entities/function/env/mod.rs | 19 +- lib/api/src/entities/function/inner.rs | 237 ++-- lib/api/src/entities/function/mod.rs | 138 +-- lib/api/src/entities/global/inner.rs | 4 +- lib/api/src/entities/imports.rs | 5 + lib/api/src/entities/instance.rs | 4 +- lib/api/src/entities/memory/inner.rs | 6 +- lib/api/src/entities/memory/view/inner.rs | 2 +- lib/api/src/entities/store/context.rs | 202 ++++ lib/api/src/entities/store/inner.rs | 2 +- lib/api/src/entities/store/mod.rs | 145 ++- lib/api/src/entities/store/store_ref.rs | 202 ++-- lib/api/src/entities/table/inner.rs | 12 +- lib/api/src/entities/table/mod.rs | 4 +- lib/api/src/entities/tag/inner.rs | 4 +- lib/api/src/entities/value.rs | 11 +- lib/api/src/error.rs | 4 +- lib/api/src/lib.rs | 22 +- lib/api/src/utils/native/convert.rs | 8 +- lib/api/src/utils/native/typed_func.rs | 71 +- lib/api/tests/externals.rs | 12 + lib/api/tests/function_env.rs | 1 + lib/api/tests/import_function.rs | 13 +- lib/api/tests/instance.rs | 4 +- lib/api/tests/jspi_async.rs | 1008 +++++++++-------- lib/api/tests/memory.rs | 8 +- lib/api/tests/module.rs | 14 +- lib/api/tests/reference_types.rs | 46 +- lib/api/tests/typed_functions.rs | 9 +- lib/vm/src/trap/traphandlers.rs | 1 + 50 files changed, 1707 insertions(+), 1316 deletions(-) create mode 100644 lib/api/src/entities/store/context.rs diff --git a/Cargo.lock b/Cargo.lock index 5c92ef4c035..44371f745a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -278,6 +278,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "async-lock" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + [[package]] name = "async-stream" version = "0.3.6" @@ -6951,6 +6962,7 @@ name = "wasmer" version = "6.1.0" dependencies = [ "anyhow", + "async-lock", "bindgen", "bytes", "cfg-if", @@ -7732,6 +7744,7 @@ name = "wasmer-workspace" version = "6.1.0" dependencies = [ "anyhow", + "async-lock", "build-deps", "cfg-if", "clap", diff --git a/Cargo.toml b/Cargo.toml index e3f0e7b38fc..a3aec1b3f5a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ tokio = { version = "1.39", features = [ "macros", ], optional = true } crossbeam-queue = "0.3.8" +async-lock.workspace = true [workspace] members = [ @@ -175,7 +176,8 @@ clap = { version = "=4.5.50" } clap_builder = { version = "=4.5.50" } clap_derive = { version = "=4.5.49" } clap_lex = { version = "=0.7.6" } -fs_extra = {version = "1.3.0"} +fs_extra = { version = "1.3.0" } +async-lock = { version = "=3.4.1" } [build-dependencies] test-generator = { path = "tests/lib/test-generator" } diff --git a/lib/api/Cargo.toml b/lib/api/Cargo.toml index fc7821dcba0..7f2c28a816b 100644 --- a/lib/api/Cargo.toml +++ b/lib/api/Cargo.toml @@ -51,6 +51,7 @@ loupe = { workspace = true, optional = true, features = [ ] } paste = "1.0.15" derive_more = { workspace = true, features = ["from", "debug"] } +async-lock.workspace = true # Dependencies and Development Dependencies for `sys`. [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/lib/api/src/backend/sys/entities/exception.rs b/lib/api/src/backend/sys/entities/exception.rs index 32c3d2e2c4f..cc9a80aef91 100644 --- a/lib/api/src/backend/sys/entities/exception.rs +++ b/lib/api/src/backend/sys/entities/exception.rs @@ -24,7 +24,7 @@ impl Exception { panic!("cannot create Exception with Tag from another Store"); } - let store_objects = store.as_store_ref().objects().as_sys(); + let store_objects = store.objects().as_sys(); let store_id = store_objects.id(); let tag_ty = tag.handle.get(store_objects).signature.params(); @@ -62,14 +62,14 @@ impl Exception { } pub fn is_from_store(&self, store: &impl AsStoreRef) -> bool { - self.exnref.0.store_id() == store.as_store_ref().objects().id() + self.exnref.0.store_id() == store.objects().id() } pub fn tag(&self, store: &impl AsStoreRef) -> crate::sys::tag::Tag { if !self.is_from_store(store) { panic!("Exception is from another Store"); } - let ctx = store.as_store_ref().objects().as_sys(); + let ctx = store.objects().as_sys(); let exception = self.exnref.0.get(ctx); let tag_handle = exception.tag(); crate::sys::tag::Tag { @@ -81,7 +81,7 @@ impl Exception { if !self.is_from_store(store) { panic!("Exception is from another Store"); } - let ctx = store.as_store_ref().objects().as_sys(); + let ctx = store.objects().as_sys(); let exception = self.exnref.0.get(ctx); let params_ty = exception.tag().get(ctx).signature.params().to_vec(); let payload_ptr = exception.payload(); diff --git a/lib/api/src/backend/sys/entities/external.rs b/lib/api/src/backend/sys/entities/external.rs index ac1cc3ad1a4..0254a79c8b6 100644 --- a/lib/api/src/backend/sys/entities/external.rs +++ b/lib/api/src/backend/sys/entities/external.rs @@ -32,7 +32,7 @@ impl ExternRef { T: Any + Send + Sync + 'static + Sized, { self.handle - .get(store.as_store_ref().objects().as_sys()) + .get(store.objects().as_sys()) .as_ref() .downcast_ref::() } @@ -60,6 +60,6 @@ impl ExternRef { /// Externref and funcref values are tied to a context and can only be used /// with that context. pub fn is_from_store(&self, store: &impl AsStoreRef) -> bool { - self.handle.store_id() == store.as_store_ref().objects().id() + self.handle.store_id() == store.objects().id() } } diff --git a/lib/api/src/backend/sys/entities/function/env.rs b/lib/api/src/backend/sys/entities/function/env.rs index be3be2ac5a3..6a30a3850bb 100644 --- a/lib/api/src/backend/sys/entities/function/env.rs +++ b/lib/api/src/backend/sys/entities/function/env.rs @@ -24,7 +24,7 @@ impl FunctionEnv { { Self { handle: StoreHandle::new( - store.as_store_mut().objects_mut().as_sys_mut(), + store.objects_mut().as_sys_mut(), VMFunctionEnvironment::new(value), ), marker: PhantomData, @@ -37,7 +37,7 @@ impl FunctionEnv { T: Any + Send + 'static + Sized, { self.handle - .get(store.as_store_ref().objects().as_sys()) + .get(store.objects().as_sys()) .as_ref() .downcast_ref::() .unwrap() @@ -69,7 +69,7 @@ impl FunctionEnv { T: Any + Send + 'static + Sized, { FunctionEnvMut { - store_mut: store.as_store_mut(), + store_mut: store.reborrow_mut(), func_env: self, } } @@ -127,7 +127,7 @@ impl Clone for FunctionEnv { /// A temporary handle to a [`FunctionEnv`]. pub struct FunctionEnvMut<'a, T: 'a> { - pub(crate) store_mut: StoreMut<'a>, + pub(crate) store_mut: &'a mut StoreMut, pub(crate) func_env: FunctionEnv, } @@ -159,40 +159,36 @@ impl FunctionEnvMut<'_, T> { /// Borrows a new mutable reference pub fn as_mut(&mut self) -> FunctionEnvMut<'_, T> { FunctionEnvMut { - store_mut: self.store_mut.as_store_mut(), + store_mut: self.store_mut.reborrow_mut(), func_env: self.func_env.clone(), } } /// Borrows a new mutable reference of both the attached Store and host state - pub fn data_and_store_mut(&mut self) -> (&mut T, StoreMut<'_>) { + pub fn data_and_store_mut(&mut self) -> (&mut T, &mut StoreMut) { let data = self.func_env.as_mut(&mut self.store_mut) as *mut T; // telling the borrow check to close his eyes here // this is still relatively safe to do as func_env are // stored in a specific vec of Store, separate from the other objects // and not really directly accessible with the StoreMut let data = unsafe { &mut *data }; - (data, self.store_mut.as_store_mut()) + (data, self.store_mut.reborrow_mut()) } } impl AsStoreRef for FunctionEnvMut<'_, T> { - fn as_store_ref(&self) -> StoreRef<'_> { - StoreRef { - inner: self.store_mut.inner, - } + fn as_ref(&self) -> &crate::StoreInner { + self.store_mut.as_ref() } } impl AsStoreMut for FunctionEnvMut<'_, T> { - fn as_store_mut(&mut self) -> StoreMut<'_> { - StoreMut { - inner: self.store_mut.inner, - } + fn as_mut(&mut self) -> &mut crate::StoreInner { + self.store_mut.as_mut() } - fn objects_mut(&mut self) -> &mut crate::StoreObjects { - self.store_mut.objects_mut() + fn reborrow_mut(&mut self) -> &mut StoreMut { + self.store_mut.reborrow_mut() } } diff --git a/lib/api/src/backend/sys/entities/function/mod.rs b/lib/api/src/backend/sys/entities/function/mod.rs index 6780ba390bb..c3ee27dc2b2 100644 --- a/lib/api/src/backend/sys/entities/function/mod.rs +++ b/lib/api/src/backend/sys/entities/function/mod.rs @@ -5,10 +5,12 @@ pub(crate) mod typed; use crate::{ BackendFunction, FunctionEnv, FunctionEnvMut, FunctionType, HostFunction, RuntimeError, - StoreInner, Value, WithEnv, WithoutEnv, + StoreContext, StoreInner, Value, WithEnv, WithoutEnv, backend::sys::{engine::NativeEngineExt, vm::VMFunctionCallback}, - entities::function::async_host::{AsyncFunctionEnv, AsyncHostFunction}, - entities::store::{AsStoreMut, AsStoreRef, StoreMut}, + entities::{ + function::async_host::{AsyncFunctionEnv, AsyncHostFunction}, + store::{AsStoreMut, AsStoreRef, StoreMut}, + }, sys::async_runtime::AsyncRuntimeError, utils::{FromToNativeWasmType, IntoResult, NativeWasmTypeInto, WasmTypeList}, vm::{VMExtern, VMExternFunction}, @@ -18,7 +20,7 @@ use std::{ cell::UnsafeCell, cmp::max, error::Error, ffi::c_void, future::Future, marker::PhantomData, pin::Pin, sync::Arc, }; -use wasmer_types::{NativeWasmType, RawValue}; +use wasmer_types::{NativeWasmType, RawValue, StoreId}; use wasmer_vm::{ MaybeInstanceOwned, StoreHandle, Trap, TrapCode, VMCallerCheckedAnyfunc, VMContext, VMDynamicFunctionContext, VMFuncRef, VMFunction, VMFunctionBody, VMFunctionContext, @@ -58,22 +60,22 @@ impl Function { let function_type = ty.into(); let func_ty = function_type.clone(); let func_env = env.clone().into_sys(); - let raw_store = store.as_store_mut().as_raw() as *mut StoreInner; + let store_id = store.objects().id(); let wrapper = move |values_vec: *mut RawValue| -> HostCallOutcome { unsafe { - let mut store = StoreMut::from_raw(raw_store); + let mut store_wrapper = unsafe { StoreContext::get_current(store_id) }; + let mut store = store_wrapper.as_mut(); let mut args = Vec::with_capacity(func_ty.params().len()); for (i, ty) in func_ty.params().iter().enumerate() { args.push(Value::from_raw( - &mut store, + store, *ty, values_vec.add(i).read_unaligned(), )); } - let store_mut = StoreMut::from_raw(raw_store); let env = env::FunctionEnvMut { - store_mut, + store_mut: store, func_env: func_env.clone(), } .into(); @@ -89,7 +91,7 @@ impl Function { address: std::ptr::null(), ctx: DynamicFunction { func: wrapper, - raw_store, + store_id, }, }); host_data.address = host_data.ctx.func_body_ptr() as *const VMFunctionBody; @@ -98,11 +100,7 @@ impl Function { // The engine linker will replace the address with one pointing to a // generated dynamic trampoline. let func_ptr = std::ptr::null() as VMFunctionCallback; - let type_index = store - .as_store_mut() - .engine() - .as_sys() - .register_signature(&function_type); + let type_index = store.engine().as_sys().register_signature(&function_type); let vmctx = VMFunctionContext { host_env: host_data.as_ref() as *const _ as *mut c_void, }; @@ -121,99 +119,97 @@ impl Function { host_data, }; Self { - handle: StoreHandle::new(store.as_store_mut().objects_mut().as_sys_mut(), vm_function), + handle: StoreHandle::new(store.objects_mut().as_sys_mut(), vm_function), } } - pub(crate) fn new_async(store: &mut impl AsStoreMut, ty: FT, func: F) -> Self - where - FT: Into, - F: Fn(&[Value]) -> Fut + 'static + Send + Sync, - Fut: Future, RuntimeError>> + 'static + Send, - { - let env = FunctionEnv::new(&mut store.as_store_mut(), ()); - let wrapped = move |_env: FunctionEnvMut<()>, values: &[Value]| func(values); - Self::new_with_env_async(store, &env, ty, wrapped) - } - - pub(crate) fn new_with_env_async( - store: &mut impl AsStoreMut, - env: &FunctionEnv, - ty: FT, - func: F, - ) -> Self - where - FT: Into, - F: Fn(FunctionEnvMut, &[Value]) -> Fut + 'static + Send + Sync, - Fut: Future, RuntimeError>> + 'static + Send, - { - let function_type = ty.into(); - let func_ty = function_type.clone(); - let func_env = env.clone().into_sys(); - let raw_store = store.as_store_mut().as_raw() as *mut StoreInner; - let wrapper = move |values_vec: *mut RawValue| -> HostCallOutcome { - unsafe { - let mut store = StoreMut::from_raw(raw_store); - let mut args = Vec::with_capacity(func_ty.params().len()); - - for (i, ty) in func_ty.params().iter().enumerate() { - args.push(Value::from_raw( - &mut store, - *ty, - values_vec.add(i).read_unaligned(), - )); - } - let store_mut = StoreMut::from_raw(raw_store); - let env = env::FunctionEnvMut { - store_mut, - func_env: func_env.clone(), - } - .into(); - let sig = func_ty.clone(); - let future = func(env, &args); - HostCallOutcome::Future { - func_ty: sig, - future: Box::pin(future) - as Pin, RuntimeError>> + Send>>, - } - } - }; - let mut host_data = Box::new(VMDynamicFunctionContext { - address: std::ptr::null(), - ctx: DynamicFunction { - func: wrapper, - raw_store, - }, - }); - host_data.address = host_data.ctx.func_body_ptr() as *const VMFunctionBody; - - let func_ptr = std::ptr::null() as VMFunctionCallback; - let type_index = store - .as_store_mut() - .engine() - .as_sys() - .register_signature(&function_type); - let vmctx = VMFunctionContext { - host_env: host_data.as_ref() as *const _ as *mut c_void, - }; - let call_trampoline = host_data.ctx.call_trampoline_address(); - let anyfunc = VMCallerCheckedAnyfunc { - func_ptr, - type_index, - vmctx, - call_trampoline, - }; - - let vm_function = VMFunction { - anyfunc: MaybeInstanceOwned::Host(Box::new(UnsafeCell::new(anyfunc))), - kind: VMFunctionKind::Dynamic, - signature: function_type, - host_data, - }; - Self { - handle: StoreHandle::new(store.as_store_mut().objects_mut().as_sys_mut(), vm_function), - } - } + // TODO: async API + // pub(crate) fn new_async(store: &mut impl AsStoreMut, ty: FT, func: F) -> Self + // where + // FT: Into, + // F: Fn(&[Value]) -> Fut + 'static + Send + Sync, + // Fut: Future, RuntimeError>> + 'static + Send, + // { + // let env = FunctionEnv::new(store, ()); + // let wrapped = move |_env: FunctionEnvMut<()>, values: &[Value]| func(values); + // Self::new_with_env_async(store, &env, ty, wrapped) + // } + + // TODO: async API + // pub(crate) fn new_with_env_async( + // store: &mut impl AsStoreMut, + // env: &FunctionEnv, + // ty: FT, + // func: F, + // ) -> Self + // where + // FT: Into, + // F: Fn(FunctionEnvMut, &[Value]) -> Fut + 'static + Send + Sync, + // Fut: Future, RuntimeError>> + 'static + Send, + // { + // let function_type = ty.into(); + // let func_ty = function_type.clone(); + // let func_env = env.clone().into_sys(); + // let store_id = store.objects().id(); + // let wrapper = move |values_vec: *mut RawValue| -> HostCallOutcome { + // unsafe { + // let mut store_wrapper = StoreContext::get_current(store_id); + // let mut store = store_wrapper.as_mut(); + // let mut args = Vec::with_capacity(func_ty.params().len()); + + // for (i, ty) in func_ty.params().iter().enumerate() { + // args.push(Value::from_raw( + // store, + // *ty, + // values_vec.add(i).read_unaligned(), + // )); + // } + // let env = env::FunctionEnvMut { + // store_mut: store, + // func_env: func_env.clone(), + // } + // .into(); + // let sig = func_ty.clone(); + // let future = func(env, &args); + // HostCallOutcome::Future { + // func_ty: sig, + // future: Box::pin(future) + // as Pin, RuntimeError>> + Send>>, + // } + // } + // }; + // let mut host_data = Box::new(VMDynamicFunctionContext { + // address: std::ptr::null(), + // ctx: DynamicFunction { + // func: wrapper, + // store_id, + // }, + // }); + // host_data.address = host_data.ctx.func_body_ptr() as *const VMFunctionBody; + + // let func_ptr = std::ptr::null() as VMFunctionCallback; + // let type_index = store.engine().as_sys().register_signature(&function_type); + // let vmctx = VMFunctionContext { + // host_env: host_data.as_ref() as *const _ as *mut c_void, + // }; + // let call_trampoline = host_data.ctx.call_trampoline_address(); + // let anyfunc = VMCallerCheckedAnyfunc { + // func_ptr, + // type_index, + // vmctx, + // call_trampoline, + // }; + + // let vm_function = VMFunction { + // anyfunc: MaybeInstanceOwned::Host(Box::new(UnsafeCell::new(anyfunc))), + // kind: VMFunctionKind::Dynamic, + // signature: function_type, + // host_data, + // }; + // Self { + // handle: StoreHandle::new(store.objects_mut().as_sys_mut(), vm_function), + // } + // } /// Creates a new host `Function` from a native function. pub(crate) fn new_typed(store: &mut impl AsStoreMut, func: F) -> Self @@ -225,17 +221,13 @@ impl Function { let env = FunctionEnv::new(store, ()); let func_ptr = func.function_callback_sys().into_sys(); let host_data = Box::new(StaticFunction { - raw_store: store.as_store_mut().as_raw() as *mut u8, + store_id: store.objects().id(), env, func, }); let function_type = FunctionType::new(Args::wasm_types(), Rets::wasm_types()); - let type_index = store - .as_store_mut() - .engine() - .as_sys() - .register_signature(&function_type); + let type_index = store.engine().as_sys().register_signature(&function_type); let vmctx = VMFunctionContext { host_env: host_data.as_ref() as *const _ as *mut c_void, }; @@ -255,83 +247,81 @@ impl Function { host_data, }; Self { - handle: StoreHandle::new(store.as_store_mut().objects_mut().as_sys_mut(), vm_function), + handle: StoreHandle::new(store.objects_mut().as_sys_mut(), vm_function), } } - pub(crate) fn new_typed_async(store: &mut impl AsStoreMut, func: F) -> Self - where - Args: WasmTypeList + 'static, - Rets: WasmTypeList + 'static, - F: AsyncHostFunction<(), Args, Rets, WithoutEnv> + Send + Sync + 'static, - { - let env = FunctionEnv::new(store, ()); - let signature = FunctionType::new(Args::wasm_types(), Rets::wasm_types()); - let args_sig = Arc::new(signature.clone()); - let results_sig = Arc::new(signature.clone()); - let func = Arc::new(func); - Self::new_with_env_async(store, &env, signature, move |mut env_mut, values| -> Pin< - Box, RuntimeError>> + Send>, - > { - let raw_store = RawStorePtr { - ptr: env_mut.as_store_mut().as_raw(), - }; - let args_sig = args_sig.clone(); - let results_sig = results_sig.clone(); - let func = func.clone(); - let args = - match typed_args_from_values::(raw_store, args_sig.as_ref(), values) { - Ok(args) => args, - Err(err) => return Box::pin(async { Err(err) }), - }; - let future = func - .as_ref() - .call_async(AsyncFunctionEnv::new(), args); - Box::pin(async move { - let typed_result = future.await?; - typed_results_to_values::(raw_store, results_sig.as_ref(), typed_result) - }) - }) - } - - pub(crate) fn new_typed_with_env_async( - store: &mut impl AsStoreMut, - env: &FunctionEnv, - func: F, - ) -> Self - where - T: Send + 'static, - F: AsyncHostFunction + Send + Sync + 'static, - Args: WasmTypeList + 'static, - Rets: WasmTypeList + 'static, - { - let signature = FunctionType::new(Args::wasm_types(), Rets::wasm_types()); - let args_sig = Arc::new(signature.clone()); - let results_sig = Arc::new(signature.clone()); - let func = Arc::new(func); - Self::new_with_env_async(store, env, signature, move |mut env_mut, values| -> Pin< - Box, RuntimeError>> + Send>, - > { - let raw_store = RawStorePtr { - ptr: env_mut.as_store_mut().as_raw(), - }; - let args_sig = args_sig.clone(); - let results_sig = results_sig.clone(); - let func = func.clone(); - let args = - match typed_args_from_values::(raw_store, args_sig.as_ref(), values) { - Ok(args) => args, - Err(err) => return Box::pin(async { Err(err) }), - }; - let future = func - .as_ref() - .call_async(AsyncFunctionEnv::with_env(env_mut), args); - Box::pin(async move { - let typed_result = future.await?; - typed_results_to_values::(raw_store, results_sig.as_ref(), typed_result) - }) - }) - } + // TODO: async API + // pub(crate) fn new_typed_async(store: &mut impl AsStoreMut, func: F) -> Self + // where + // Args: WasmTypeList + 'static, + // Rets: WasmTypeList + 'static, + // F: AsyncHostFunction<(), Args, Rets, WithoutEnv> + Send + Sync + 'static, + // { + // let env = FunctionEnv::new(store, ()); + // let signature = FunctionType::new(Args::wasm_types(), Rets::wasm_types()); + // let args_sig = Arc::new(signature.clone()); + // let results_sig = Arc::new(signature.clone()); + // let func = Arc::new(func); + // Self::new_with_env_async(store, &env, signature, move |mut env_mut, values| -> Pin< + // Box, RuntimeError>> + Send>, + // > { + // let store = AsStoreMut::reborrow_mut(&mut env_mut); + // let args_sig = args_sig.clone(); + // let results_sig = results_sig.clone(); + // let func = func.clone(); + // let args = + // match typed_args_from_values::(store, args_sig.as_ref(), values) { + // Ok(args) => args, + // Err(err) => return Box::pin(async { Err(err) }), + // }; + // let future = func + // .as_ref() + // .call_async(AsyncFunctionEnv::new(), args); + // Box::pin(async move { + // let typed_result = future.await?; + // typed_results_to_values::(store, results_sig.as_ref(), typed_result) + // }) + // }) + // } + + // TODO: async API + // pub(crate) fn new_typed_with_env_async( + // store: &mut impl AsStoreMut, + // env: &FunctionEnv, + // func: F, + // ) -> Self + // where + // T: Send + 'static, + // F: AsyncHostFunction + Send + Sync + 'static, + // Args: WasmTypeList + 'static, + // Rets: WasmTypeList + 'static, + // { + // let signature = FunctionType::new(Args::wasm_types(), Rets::wasm_types()); + // let args_sig = Arc::new(signature.clone()); + // let results_sig = Arc::new(signature.clone()); + // let func = Arc::new(func); + // Self::new_with_env_async(store, env, signature, move |mut env_mut, values| -> Pin< + // Box, RuntimeError>> + Send>, + // > { + // let store = AsStoreMut::reborrow_mut(&mut env_mut); + // let args_sig = args_sig.clone(); + // let results_sig = results_sig.clone(); + // let func = func.clone(); + // let args = + // match typed_args_from_values::(store, args_sig.as_ref(), values) { + // Ok(args) => args, + // Err(err) => return Box::pin(async { Err(err) }), + // }; + // let future = func + // .as_ref() + // .call_async(AsyncFunctionEnv::with_env(env_mut), args); + // Box::pin(async move { + // let typed_result = future.await?; + // typed_results_to_values::(store, results_sig.as_ref(), typed_result) + // }) + // }) + // } pub(crate) fn new_typed_with_env( store: &mut impl AsStoreMut, @@ -345,17 +335,13 @@ impl Function { { let func_ptr = func.function_callback_sys().into_sys(); let host_data = Box::new(StaticFunction { - raw_store: store.as_store_mut().as_raw() as *mut u8, + store_id: store.objects().id(), env: env.as_sys().clone().into(), func, }); let function_type = FunctionType::new(Args::wasm_types(), Rets::wasm_types()); - let type_index = store - .as_store_mut() - .engine() - .as_sys() - .register_signature(&function_type); + let type_index = store.engine().as_sys().register_signature(&function_type); let vmctx = VMFunctionContext { host_env: host_data.as_ref() as *const _ as *mut c_void, }; @@ -375,15 +361,12 @@ impl Function { host_data, }; Self { - handle: StoreHandle::new(store.as_store_mut().objects_mut().as_sys_mut(), vm_function), + handle: StoreHandle::new(store.objects_mut().as_sys_mut(), vm_function), } } pub(crate) fn ty(&self, store: &impl AsStoreRef) -> FunctionType { - self.handle - .get(store.as_store_ref().objects().as_sys()) - .signature - .clone() + self.handle.get(store.objects().as_sys()).signature.clone() } fn call_wasm( @@ -449,24 +432,35 @@ impl Function { ) -> Result<(), RuntimeError> { // Call the trampoline. let result = { + let vm_function = self.handle.get(store.as_mut().objects.as_sys()); + let anyfunc = vm_function.anyfunc.as_ptr(); + let config = store.engine().tunables().vmconfig().clone(); + let signal_handler = store.signal_handler(); + + // Install the store into the store context + let store_id = store.objects().id(); + let store_install_guard = StoreContext::ensure_installed(store); + let mut r; // TODO: This loop is needed for asyncify. It will be refactored with https://github.com/wasmerio/wasmer/issues/3451 loop { - let storeref = store.as_store_ref(); - let vm_function = self.handle.get(storeref.objects().as_sys()); - let config = storeref.engine().tunables().vmconfig(); r = unsafe { wasmer_call_trampoline( - store.as_store_ref().signal_handler(), - config, - vm_function.anyfunc.as_ptr().as_ref().vmctx, + signal_handler, + &config, + anyfunc.as_ref().vmctx, trampoline, - vm_function.anyfunc.as_ptr().as_ref().func_ptr, + anyfunc.as_ref().func_ptr, params.as_mut_ptr() as *mut u8, ) }; - let store_mut = store.as_store_mut(); - if let Some(callback) = store_mut.inner.on_called.take() { + + // The `store` parameter potentially doesn't have its StoreMut anymore; + // so borrow another reference from the store context which owns the + // StoreMut at this point anyway. + let mut store_wrapper = unsafe { StoreContext::get_current(store_id) }; + let mut store_mut = store_wrapper.as_mut(); + if let Some(callback) = store_mut.as_mut().on_called.take() { match callback(store_mut) { Ok(wasmer_types::OnCalledAction::InvokeAgain) => { continue; @@ -482,8 +476,12 @@ impl Function { } break; } + + drop(store_install_guard); + r }; + if let Err(error) = result { return Err(error.into()); } @@ -510,7 +508,7 @@ impl Function { ) -> Result, RuntimeError> { let trampoline = unsafe { self.handle - .get(store.as_store_ref().objects().as_sys()) + .get(store.objects().as_sys()) .anyfunc .as_ptr() .as_ref() @@ -539,7 +537,7 @@ impl Function { ) -> Result, RuntimeError> { let trampoline = unsafe { self.handle - .get(store.as_store_ref().objects().as_sys()) + .get(store.objects().as_sys()) .anyfunc .as_ptr() .as_ref() @@ -551,7 +549,7 @@ impl Function { } pub(crate) fn vm_funcref(&self, store: &impl AsStoreRef) -> VMFuncRef { - let vm_function = self.handle.get(store.as_store_ref().objects().as_sys()); + let vm_function = self.handle.get(store.objects().as_sys()); if vm_function.kind == VMFunctionKind::Dynamic { panic!("dynamic functions cannot be used in tables or as funcrefs"); } @@ -562,7 +560,6 @@ impl Function { let signature = { let anyfunc = unsafe { funcref.0.as_ref() }; store - .as_store_ref() .engine() .as_sys() .lookup_signature(anyfunc.type_index) @@ -584,17 +581,14 @@ impl Function { pub(crate) fn from_vm_extern(store: &mut impl AsStoreMut, vm_extern: VMExternFunction) -> Self { Self { handle: unsafe { - StoreHandle::from_internal( - store.as_store_ref().objects().id(), - vm_extern.into_sys(), - ) + StoreHandle::from_internal(store.objects().id(), vm_extern.into_sys()) }, } } /// Checks whether this `Function` can be used with the given store. pub(crate) fn is_from_store(&self, store: &impl AsStoreRef) -> bool { - self.handle.store_id() == store.as_store_ref().objects().id() + self.handle.store_id() == store.objects().id() } pub(crate) fn to_vm_extern(&self) -> VMExtern { @@ -633,12 +627,13 @@ where } fn write_dynamic_results( - raw_store: *mut StoreInner, + store_id: StoreId, func_ty: &FunctionType, returns: Vec, values_vec: *mut RawValue, ) -> Result<(), RuntimeError> { - let mut store = unsafe { StoreMut::from_raw(raw_store) }; + let mut store_wrapper = unsafe { StoreContext::get_current(store_id) }; + let mut store = store_wrapper.as_mut(); let return_types = returns.iter().map(|ret| ret.ty()); if return_types.ne(func_ty.results().iter().copied()) { return Err(RuntimeError::new(format!( @@ -656,27 +651,19 @@ fn write_dynamic_results( } fn finalize_dynamic_call( - raw_store: *mut StoreInner, + store_id: StoreId, func_ty: FunctionType, values_vec: *mut RawValue, result: Result, RuntimeError>, ) -> Result<(), RuntimeError> { match result { - Ok(values) => write_dynamic_results(raw_store, &func_ty, values, values_vec), + Ok(values) => write_dynamic_results(store_id, &func_ty, values, values_vec), Err(err) => Err(err), } } -#[derive(Clone, Copy)] -struct RawStorePtr { - ptr: *mut StoreInner, -} - -unsafe impl Send for RawStorePtr {} -unsafe impl Sync for RawStorePtr {} - fn typed_args_from_values( - raw_store: RawStorePtr, + store: &mut StoreMut, func_ty: &FunctionType, values: &[Value], ) -> Result @@ -688,7 +675,6 @@ where "typed host function received wrong number of parameters", )); } - let mut store = unsafe { StoreMut::from_raw(raw_store.ptr) }; let mut raw_array = Args::empty_array(); for ((slot, value), expected_ty) in raw_array .as_mut() @@ -701,25 +687,24 @@ where *expected_ty, "wasm should only call host functions with matching signatures" ); - *slot = value.as_raw(&store); + *slot = value.as_raw(store); } - unsafe { Ok(Args::from_array(&mut store, raw_array)) } + unsafe { Ok(Args::from_array(store, raw_array)) } } fn typed_results_to_values( - raw_store: RawStorePtr, + store: &mut StoreMut, func_ty: &FunctionType, rets: Rets, ) -> Result, RuntimeError> where Rets: WasmTypeList, { - let mut store = unsafe { StoreMut::from_raw(raw_store.ptr) }; - let mut raw_array = unsafe { rets.into_array(&mut store) }; + let mut raw_array = unsafe { rets.into_array(store) }; let mut values = Vec::with_capacity(func_ty.results().len()); for (raw, ty) in raw_array.as_mut().iter().zip(func_ty.results().iter()) { unsafe { - values.push(Value::from_raw(&mut store, *ty, *raw)); + values.push(Value::from_raw(store, *ty, *raw)); } } Ok(values) @@ -739,7 +724,7 @@ pub(crate) enum HostCallOutcome { /// Host state for a dynamic function. pub(crate) struct DynamicFunction { func: F, - raw_store: *mut StoreInner, + store_id: StoreId, } impl DynamicFunction @@ -755,7 +740,7 @@ where let result = on_host_stack(|| { panic::catch_unwind(AssertUnwindSafe(|| match (this.ctx.func)(values_vec) { HostCallOutcome::Ready { func_ty, result } => to_invocation_result( - finalize_dynamic_call(this.ctx.raw_store, func_ty, values_vec, result), + finalize_dynamic_call(this.ctx.store_id, func_ty, values_vec, result), ), HostCallOutcome::Future { func_ty, future } => { let awaited = block_on_host_future(future); @@ -767,7 +752,7 @@ where } }; to_invocation_result(finalize_dynamic_call( - this.ctx.raw_store, + this.ctx.store_id, func_ty, values_vec, result, @@ -782,9 +767,10 @@ where match result { Ok(InvocationResult::Success(())) => {} Ok(InvocationResult::Exception(exception)) => unsafe { - let store = StoreMut::from_raw(this.ctx.raw_store); + let mut store_wrapper = unsafe { StoreContext::get_current(this.ctx.store_id) }; + let mut store = store_wrapper.as_mut(); wasmer_vm::libcalls::throw( - store.as_store_ref().objects().as_sys(), + store.objects().as_sys(), exception.vm_exceptionref().as_sys().to_u32_exnref(), ) }, @@ -822,7 +808,7 @@ where /// [`crate::Function::new_typed`] and /// [`crate::Function::new_typed_with_env`] to learn more. pub(crate) struct StaticFunction { - pub(crate) raw_store: *mut u8, + pub(crate) store_id: StoreId, pub(crate) env: FunctionEnv, pub(crate) func: F, } @@ -870,12 +856,13 @@ macro_rules! impl_host_function { RetsAsResult: IntoResult, Func: Fn($( $x , )*) -> RetsAsResult + 'static, { - let mut store = unsafe { StoreMut::from_raw(env.raw_store as *mut _) }; + let mut store_wrapper = unsafe { StoreContext::get_current(env.store_id) }; + let mut store = store_wrapper.as_mut(); let result = on_host_stack(|| { panic::catch_unwind(AssertUnwindSafe(|| { $( let $x = unsafe { - FromToNativeWasmType::from_native(NativeWasmTypeInto::from_abi(&mut store, $x)) + FromToNativeWasmType::from_native(NativeWasmTypeInto::from_abi(store, $x)) }; )* to_invocation_result((env.func)($($x),* ).into_result()) @@ -888,12 +875,12 @@ macro_rules! impl_host_function { match result { Ok(InvocationResult::Success(result)) => { unsafe { - return result.into_c_struct(&mut store); + return result.into_c_struct(store); } }, Ok(InvocationResult::Exception(exception)) => unsafe { wasmer_vm::libcalls::throw( - store.as_store_ref().objects().as_sys(), + store.objects().as_sys(), exception.vm_exceptionref().as_sys().to_u32_exnref() ) } @@ -951,18 +938,18 @@ macro_rules! impl_host_function { RetsAsResult: IntoResult, Func: Fn(FunctionEnvMut, $( $x , )*) -> RetsAsResult + 'static, { + let mut store_wrapper = unsafe { StoreContext::get_current(env.store_id) }; + let mut store = store_wrapper.as_mut(); - let mut store = unsafe { StoreMut::from_raw(env.raw_store as *mut _) }; let result = wasmer_vm::on_host_stack(|| { panic::catch_unwind(AssertUnwindSafe(|| { $( let $x = unsafe { - FromToNativeWasmType::from_native(NativeWasmTypeInto::from_abi(&mut store, $x)) + FromToNativeWasmType::from_native(NativeWasmTypeInto::from_abi(store, $x)) }; )* - let store_mut = unsafe { StoreMut::from_raw(env.raw_store as *mut _) }; let f_env = crate::backend::sys::function::env::FunctionEnvMut { - store_mut, + store_mut: store, func_env: env.env.as_sys().clone(), }.into(); to_invocation_result((env.func)(f_env, $($x),* ).into_result()) @@ -975,12 +962,12 @@ macro_rules! impl_host_function { match result { Ok(InvocationResult::Success(result)) => { unsafe { - return result.into_c_struct(&mut store); + return result.into_c_struct(store); } }, Ok(InvocationResult::Exception(exception)) => unsafe { wasmer_vm::libcalls::throw( - store.as_store_ref().objects().as_sys(), + store.objects().as_sys(), exception.vm_exceptionref().as_sys().to_u32_exnref() ) } diff --git a/lib/api/src/backend/sys/entities/function/typed.rs b/lib/api/src/backend/sys/entities/function/typed.rs index 186cc0cb6ff..37930181718 100644 --- a/lib/api/src/backend/sys/entities/function/typed.rs +++ b/lib/api/src/backend/sys/entities/function/typed.rs @@ -1,7 +1,8 @@ use crate::backend::sys::engine::NativeEngineExt; use crate::store::{AsStoreMut, AsStoreRef}; use crate::{ - FromToNativeWasmType, NativeWasmTypeInto, RuntimeError, TypedFunction, Value, WasmTypeList, + FromToNativeWasmType, NativeWasmTypeInto, RuntimeError, StoreContext, TypedFunction, Value, + WasmTypeList, }; use std::future::Future; use wasmer_types::{FunctionType, RawValue, Type}; @@ -21,7 +22,7 @@ macro_rules! impl_native_traits { let anyfunc = unsafe { *self.func.as_sys() .handle - .get(store.as_store_ref().objects().as_sys()) + .get(store.objects().as_sys()) .anyfunc .as_ptr() .as_ref() @@ -49,22 +50,32 @@ macro_rules! impl_native_traits { rets_list.as_mut() }; + let config = store.engine().tunables().vmconfig().clone(); + let signal_handler = store.signal_handler(); + + // Install the store into the store context + let store_id = store.objects().id(); + let store_install_guard = StoreContext::ensure_installed(store); + let mut r; loop { - let storeref = store.as_store_ref(); - let config = storeref.engine().tunables().vmconfig(); r = unsafe { wasmer_vm::wasmer_call_trampoline( - store.as_store_ref().signal_handler(), - config, + signal_handler, + &config, anyfunc.vmctx, anyfunc.call_trampoline, anyfunc.func_ptr, args_rets.as_mut_ptr() as *mut u8, ) }; - let store_mut = store.as_store_mut(); - if let Some(callback) = store_mut.inner.on_called.take() { + + // The `store` parameter potentially doesn't have its StoreMut anymore; + // so borrow another reference from the store context which owns the + // StoreMut at this point anyway. + let mut store_wrapper = unsafe { StoreContext::get_current(store_id) }; + let mut store_mut = store_wrapper.as_mut(); + if let Some(callback) = store_mut.as_mut().on_called.take() { match callback(store_mut) { Ok(wasmer_types::OnCalledAction::InvokeAgain) => { continue; } Ok(wasmer_types::OnCalledAction::Finish) => { break; } @@ -74,6 +85,9 @@ macro_rules! impl_native_traits { } break; } + + drop(store_install_guard); + r?; let num_rets = rets_list.len(); @@ -102,35 +116,35 @@ macro_rules! impl_native_traits { // Ok(Rets::from_c_struct(results)) } - /// Call the typed func asynchronously. - #[allow(unused_mut)] - #[allow(clippy::too_many_arguments)] - pub fn call_async_sys<'a>( - &'a self, - store: &'a mut (impl AsStoreMut + 'static), - $( $x: $x, )* - ) -> impl Future> + 'a - where - $( $x: FromToNativeWasmType, )* - { - let func = self.func.clone(); - let func_ty = func.ty(store); - let mut params_raw = [ $( $x.to_native().into_raw(store) ),* ]; - let mut params_values = Vec::with_capacity(params_raw.len()); - { - let mut tmp_store = store.as_store_mut(); - for (raw, ty) in params_raw.iter().zip(func_ty.params()) { - unsafe { - params_values.push(Value::from_raw(&mut tmp_store, *ty, *raw)); - } - } - } + // TODO: async api + // /// Call the typed func asynchronously. + // #[allow(unused_mut)] + // #[allow(clippy::too_many_arguments)] + // pub fn call_async_sys<'a>( + // &'a self, + // store: &'a mut (impl AsStoreMut + 'static), + // $( $x: $x, )* + // ) -> impl Future> + 'a + // where + // $( $x: FromToNativeWasmType, )* + // { + // let func = self.func.clone(); + // let func_ty = func.ty(store); + // let mut params_raw = [ $( $x.to_native().into_raw(store) ),* ]; + // let mut params_values = Vec::with_capacity(params_raw.len()); + // { + // for (raw, ty) in params_raw.iter().zip(func_ty.params()) { + // unsafe { + // params_values.push(Value::from_raw(store, *ty, *raw)); + // } + // } + // } - async move { - let results = func.call_async(store, ¶ms_values).await?; - convert_results::(store, func_ty, results) - } - } + // async move { + // let results = func.call_async(store, ¶ms_values).await?; + // convert_results::(store, func_ty, results) + // } + // } #[doc(hidden)] #[allow(missing_docs)] @@ -140,7 +154,7 @@ macro_rules! impl_native_traits { let anyfunc = unsafe { *self.func.as_sys() .handle - .get(store.as_store_ref().objects().as_sys()) + .get(store.objects().as_sys()) .anyfunc .as_ptr() .as_ref() @@ -161,22 +175,32 @@ macro_rules! impl_native_traits { rets_list.as_mut() }; + let config = store.engine().tunables().vmconfig().clone(); + let signal_handler = store.signal_handler(); + + // Install the store into the store context + let store_id = store.objects().id(); + let store_install_guard = StoreContext::ensure_installed(store); + let mut r; loop { - let storeref = store.as_store_ref(); - let config = storeref.engine().tunables().vmconfig(); r = unsafe { wasmer_vm::wasmer_call_trampoline( - store.as_store_ref().signal_handler(), - config, + signal_handler, + &config, anyfunc.vmctx, anyfunc.call_trampoline, anyfunc.func_ptr, args_rets.as_mut_ptr() as *mut u8, ) }; - let store_mut = store.as_store_mut(); - if let Some(callback) = store_mut.inner.on_called.take() { + + // The `store` parameter potentially doesn't have its StoreMut anymore; + // so borrow another reference from the store context which owns the + // StoreMut at this point anyway. + let mut store_wrapper = unsafe { StoreContext::get_current(store_id) }; + let mut store_mut = store_wrapper.as_mut(); + if let Some(callback) = store_mut.as_mut().on_called.take() { // TODO: OnCalledAction is needed for asyncify. It will be refactored with https://github.com/wasmerio/wasmer/issues/3451 match callback(store_mut) { Ok(wasmer_types::OnCalledAction::InvokeAgain) => { continue; } @@ -187,6 +211,9 @@ macro_rules! impl_native_traits { } break; } + + drop(store_install_guard); + r?; let num_rets = rets_list.len(); diff --git a/lib/api/src/backend/sys/entities/global.rs b/lib/api/src/backend/sys/entities/global.rs index 3548bc3a6a5..77dd35a2886 100644 --- a/lib/api/src/backend/sys/entities/global.rs +++ b/lib/api/src/backend/sys/entities/global.rs @@ -40,25 +40,18 @@ impl Global { } pub(crate) fn ty(&self, store: &impl AsStoreRef) -> GlobalType { - *self - .handle - .get(store.as_store_ref().objects().as_sys()) - .ty() + *self.handle.get(store.objects().as_sys()).ty() } pub(crate) fn get(&self, store: &mut impl AsStoreMut) -> Value { unsafe { let raw = self .handle - .get(store.as_store_ref().objects().as_sys()) + .get(store.objects().as_sys()) .vmglobal() .as_ref() .val; - let ty = self - .handle - .get(store.as_store_ref().objects().as_sys()) - .ty() - .ty; + let ty = self.handle.get(store.objects().as_sys()).ty().ty; Value::from_raw(store, ty, raw) } } @@ -79,7 +72,7 @@ impl Global { } unsafe { self.handle - .get_mut(store.as_store_mut().objects_mut().as_sys_mut()) + .get_mut(store.objects_mut().as_sys_mut()) .vmglobal() .as_mut() .val = val.as_raw(store); @@ -90,16 +83,13 @@ impl Global { pub(crate) fn from_vm_extern(store: &mut impl AsStoreMut, vm_extern: VMExternGlobal) -> Self { Self { handle: unsafe { - StoreHandle::from_internal( - store.as_store_ref().objects().id(), - vm_extern.into_sys(), - ) + StoreHandle::from_internal(store.objects().id(), vm_extern.into_sys()) }, } } pub(crate) fn is_from_store(&self, store: &impl AsStoreRef) -> bool { - self.handle.store_id() == store.as_store_ref().objects().id() + self.handle.store_id() == store.objects().id() } pub(crate) fn to_vm_extern(&self) -> VMExtern { diff --git a/lib/api/src/backend/sys/entities/instance.rs b/lib/api/src/backend/sys/entities/instance.rs index 15edc83407d..790b937b7d7 100644 --- a/lib/api/src/backend/sys/entities/instance.rs +++ b/lib/api/src/backend/sys/entities/instance.rs @@ -54,10 +54,7 @@ impl Instance { let mut handle = module.as_sys().instantiate(store, &externs)?; let exports = Self::get_exports(store, module, handle.as_sys_mut()); let instance = Self { - _handle: StoreHandle::new( - store.as_store_mut().objects_mut().as_sys_mut(), - handle.into_sys(), - ), + _handle: StoreHandle::new(store.objects_mut().as_sys_mut(), handle.into_sys()), }; Ok((instance, exports)) diff --git a/lib/api/src/backend/sys/entities/memory/mod.rs b/lib/api/src/backend/sys/entities/memory/mod.rs index ed16d220958..8c83203ac45 100644 --- a/lib/api/src/backend/sys/entities/memory/mod.rs +++ b/lib/api/src/backend/sys/entities/memory/mod.rs @@ -32,13 +32,12 @@ pub struct Memory { impl Memory { pub(crate) fn new(store: &mut impl AsStoreMut, ty: MemoryType) -> Result { - let mut store = store.as_store_mut(); let tunables = store.engine().tunables(); let style = tunables.memory_style(&ty); let memory = tunables.create_host_memory(&ty, &style)?; Ok(Self { - handle: StoreHandle::new(store.as_store_mut().objects_mut().as_sys_mut(), memory), + handle: StoreHandle::new(store.objects_mut().as_sys_mut(), memory), }) } @@ -48,15 +47,11 @@ impl Memory { } pub(crate) fn ty(&self, store: &impl AsStoreRef) -> MemoryType { - self.handle - .get(store.as_store_ref().objects().as_sys()) - .ty() + self.handle.get(store.objects().as_sys()).ty() } pub(crate) fn size(&self, store: &impl AsStoreRef) -> Pages { - self.handle - .get(store.as_store_ref().objects().as_sys()) - .size() + self.handle.get(store.objects().as_sys()).size() } pub(crate) fn grow( @@ -84,7 +79,7 @@ impl Memory { pub(crate) fn reset(&self, store: &mut impl AsStoreMut) -> Result<(), MemoryError> { self.handle - .get_mut(store.as_store_mut().objects_mut().as_sys_mut()) + .get_mut(store.objects_mut().as_sys_mut()) .reset()?; Ok(()) } @@ -92,23 +87,20 @@ impl Memory { pub(crate) fn from_vm_extern(store: &impl AsStoreRef, vm_extern: VMExternMemory) -> Self { Self { handle: unsafe { - StoreHandle::from_internal( - store.as_store_ref().objects().id(), - vm_extern.into_sys(), - ) + StoreHandle::from_internal(store.objects().id(), vm_extern.into_sys()) }, } } /// Checks whether this `Memory` can be used with the given context. pub(crate) fn is_from_store(&self, store: &impl AsStoreRef) -> bool { - self.handle.store_id() == store.as_store_ref().objects().id() + self.handle.store_id() == store.objects().id() } /// Cloning memory will create another reference to the same memory that /// can be put into a new store pub(crate) fn try_clone(&self, store: &impl AsStoreRef) -> Result { - let mem = self.handle.get(store.as_store_ref().objects().as_sys()); + let mem = self.handle.get(store.objects().as_sys()); let cloned = mem.try_clone()?; Ok(cloned.into()) } @@ -127,7 +119,7 @@ impl Memory { &self, store: &impl AsStoreRef, ) -> Option { - let mem = self.handle.get(store.as_store_ref().objects().as_sys()); + let mem = self.handle.get(store.objects().as_sys()); let conds = mem.thread_conditions()?.downgrade(); Some(crate::memory::shared::SharedMemory::new( diff --git a/lib/api/src/backend/sys/entities/memory/view.rs b/lib/api/src/backend/sys/entities/memory/view.rs index e68c9b81e58..0136ae6f604 100644 --- a/lib/api/src/backend/sys/entities/memory/view.rs +++ b/lib/api/src/backend/sys/entities/memory/view.rs @@ -23,15 +23,9 @@ pub struct MemoryView<'a> { impl<'a> MemoryView<'a> { pub(crate) fn new(memory: &Memory, store: &'a (impl AsStoreRef + ?Sized)) -> Self { - let size = memory - .handle - .get(store.as_store_ref().objects().as_sys()) - .size(); - - let definition = memory - .handle - .get(store.as_store_ref().objects().as_sys()) - .vmmemory(); + let size = memory.handle.get(store.objects().as_sys()).size(); + + let definition = memory.handle.get(store.objects().as_sys()).vmmemory(); let def = unsafe { definition.as_ref() }; Self { diff --git a/lib/api/src/backend/sys/entities/module.rs b/lib/api/src/backend/sys/entities/module.rs index 167092c6ece..988886d1f7e 100644 --- a/lib/api/src/backend/sys/entities/module.rs +++ b/lib/api/src/backend/sys/entities/module.rs @@ -10,7 +10,7 @@ use wasmer_types::{ }; use crate::{ - AsStoreMut, AsStoreRef, BackendModule, IntoBytes, + AsStoreMut, AsStoreRef, BackendModule, IntoBytes, StoreContext, backend::sys::entities::engine::NativeEngineExt, engine::AsEngineRef, error::InstantiationError, vm::VMInstance, }; @@ -165,10 +165,9 @@ impl Module { return Err(InstantiationError::DifferentStores); } } - let signal_handler = store.as_store_ref().signal_handler(); - let mut store_mut = store.as_store_mut(); - let (engine, objects) = store_mut.engine_and_objects_mut(); - let config = engine.tunables().vmconfig(); + let signal_handler = store.signal_handler(); + let (engine, objects) = store.engine_and_objects_mut(); + let config = engine.tunables().vmconfig().clone(); unsafe { let mut instance_handle = self.artifact.instantiate( engine.tunables(), @@ -179,13 +178,18 @@ impl Module { objects.as_sys_mut(), )?; + let store_id = objects.id(); + let store_install_guard = StoreContext::ensure_installed(store); + // After the instance handle is created, we need to initialize // the data, call the start function and so. However, if any // of this steps traps, we still need to keep the instance alive // as some of the Instance elements may have placed in other // instance tables. self.artifact - .finish_instantiation(config, signal_handler, &mut instance_handle)?; + .finish_instantiation(&config, signal_handler, &mut instance_handle)?; + + drop(store_install_guard); Ok(VMInstance::Sys(instance_handle)) } diff --git a/lib/api/src/backend/sys/entities/table.rs b/lib/api/src/backend/sys/entities/table.rs index 88cd08ae0de..4caa0347901 100644 --- a/lib/api/src/backend/sys/entities/table.rs +++ b/lib/api/src/backend/sys/entities/table.rs @@ -65,7 +65,6 @@ impl Table { init: Value, ) -> Result { let item = value_to_table_element(&mut store, init)?; - let mut store = store.as_store_mut(); let tunables = store.engine().tunables(); let style = tunables.table_style(&ty); let mut table = tunables @@ -83,17 +82,11 @@ impl Table { } pub(crate) fn ty(&self, store: &impl AsStoreRef) -> TableType { - *self - .handle - .get(store.as_store_ref().objects().as_sys()) - .ty() + *self.handle.get(store.objects().as_sys()).ty() } pub(crate) fn get(&self, store: &mut impl AsStoreMut, index: u32) -> Option { - let item = self - .handle - .get(store.as_store_ref().objects().as_sys()) - .get(index)?; + let item = self.handle.get(store.objects().as_sys()).get(index)?; Some(value_from_table_element(store, item)) } @@ -112,9 +105,7 @@ impl Table { } pub(crate) fn size(&self, store: &impl AsStoreRef) -> u32 { - self.handle - .get(store.as_store_ref().objects().as_sys()) - .size() + self.handle.get(store.objects().as_sys()).size() } pub(crate) fn grow( @@ -162,17 +153,14 @@ impl Table { pub(crate) fn from_vm_extern(store: &mut impl AsStoreMut, vm_extern: VMExternTable) -> Self { Self { handle: unsafe { - StoreHandle::from_internal( - store.as_store_ref().objects().id(), - vm_extern.into_sys(), - ) + StoreHandle::from_internal(store.objects().id(), vm_extern.into_sys()) }, } } /// Checks whether this `Table` can be used with the given context. pub(crate) fn is_from_store(&self, store: &impl AsStoreRef) -> bool { - self.handle.store_id() == store.as_store_ref().objects().id() + self.handle.store_id() == store.objects().id() } pub(crate) fn to_vm_extern(&self) -> VMExtern { diff --git a/lib/api/src/backend/sys/entities/tag.rs b/lib/api/src/backend/sys/entities/tag.rs index 58452513430..19e297377d8 100644 --- a/lib/api/src/backend/sys/entities/tag.rs +++ b/lib/api/src/backend/sys/entities/tag.rs @@ -41,7 +41,7 @@ impl Tag { kind: wasmer_types::TagKind::Exception, params: self .handle - .get(store.as_store_ref().objects().as_sys()) + .get(store.objects().as_sys()) .signature .params() .into(), @@ -58,7 +58,7 @@ impl Tag { /// Check whether or not the [`Tag`] is from the given store. pub fn is_from_store(&self, store: &impl AsStoreRef) -> bool { - self.handle.store_id() == store.as_store_ref().objects().id() + self.handle.store_id() == store.objects().id() } pub(crate) fn to_vm_extern(&self) -> VMExtern { diff --git a/lib/api/src/backend/sys/tunables.rs b/lib/api/src/backend/sys/tunables.rs index 232de0c60ad..3b2cf8e8bb3 100644 --- a/lib/api/src/backend/sys/tunables.rs +++ b/lib/api/src/backend/sys/tunables.rs @@ -285,7 +285,9 @@ mod tests { engine.set_tunables(tunables); let mut store = Store::new(engine); //let mut store = Store::new(compiler); - let module = Module::new(&store, wasm_bytes)?; + let module = Module::new(&store.engine(), wasm_bytes)?; + + let mut store = store.as_mut(); let import_object = imports! {}; let instance = Instance::new(&mut store, &module, &import_object)?; @@ -464,7 +466,9 @@ mod tests { let mut engine = Engine::new(compiler.into(), Default::default(), Default::default()); engine.set_tunables(tunables); let mut store = Store::new(engine); - let module = Module::new(&store, wasm_bytes)?; + let module = Module::new(&store.engine(), wasm_bytes)?; + + let mut store = store.as_mut(); let import_object = imports! {}; let instance = Instance::new(&mut store, &module, &import_object)?; diff --git a/lib/api/src/entities/engine/engine_ref.rs b/lib/api/src/entities/engine/engine_ref.rs index 8edaa93d542..62beab8c809 100644 --- a/lib/api/src/entities/engine/engine_ref.rs +++ b/lib/api/src/entities/engine/engine_ref.rs @@ -32,7 +32,7 @@ pub trait AsEngineRef { /// /// NOTE: this function will return [`None`] if the [`AsEngineRef`] implementor is not an /// actual [`crate::Store`]. - fn maybe_as_store(&self) -> Option> { + fn maybe_as_store(&self) -> Option<&StoreRef> { None } } @@ -54,7 +54,7 @@ where (**self).as_engine_ref() } - fn maybe_as_store(&self) -> Option> { + fn maybe_as_store(&self) -> Option<&StoreRef> { (**self).maybe_as_store() } } diff --git a/lib/api/src/entities/exception/inner.rs b/lib/api/src/entities/exception/inner.rs index 08d0344ca3e..d2fa6f4b367 100644 --- a/lib/api/src/entities/exception/inner.rs +++ b/lib/api/src/entities/exception/inner.rs @@ -21,7 +21,7 @@ impl BackendException { #[inline] #[allow(irrefutable_let_patterns)] pub fn new(store: &mut impl AsStoreMut, tag: &Tag, payload: &[Value]) -> Self { - match &store.as_store_mut().inner.store { + match &store.as_mut().store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => { let BackendTag::Sys(tag) = &tag.0 else { diff --git a/lib/api/src/entities/external/extref/inner.rs b/lib/api/src/entities/external/extref/inner.rs index 9c974f7cdab..ae05baebff9 100644 --- a/lib/api/src/entities/external/extref/inner.rs +++ b/lib/api/src/entities/external/extref/inner.rs @@ -14,7 +14,7 @@ impl BackendExternRef { where T: Any + Send + Sync + 'static + Sized, { - match &store.as_store_mut().inner.store { + match &store.as_mut().store { #[cfg(feature = "sys")] crate::BackendStore::Sys(s) => Self::Sys( crate::backend::sys::entities::external::ExternRef::new(store, value), @@ -78,7 +78,7 @@ impl BackendExternRef { store: &mut impl AsStoreMut, vm_externref: VMExternRef, ) -> Self { - match &store.as_store_mut().inner.store { + match &store.as_mut().store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => Self::Sys(unsafe { crate::backend::sys::entities::external::ExternRef::from_vm_externref( diff --git a/lib/api/src/entities/function/env/inner.rs b/lib/api/src/entities/function/env/inner.rs index 1e60b6dc03d..a70c64bb636 100644 --- a/lib/api/src/entities/function/env/inner.rs +++ b/lib/api/src/entities/function/env/inner.rs @@ -54,7 +54,7 @@ impl BackendFunctionEnv { where T: Any + Send + 'static + Sized, { - match store.as_store_mut().inner.store { + match store.as_mut().store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => Self::Sys( crate::backend::sys::function::env::FunctionEnv::new(store, value), @@ -199,7 +199,7 @@ impl BackendFunctionEnvMut<'_, T> { } /// Borrows a new mutable reference of both the attached Store and host state - pub fn data_and_store_mut(&mut self) -> (&mut T, StoreMut<'_>) { + pub fn data_and_store_mut(&mut self) -> (&mut T, &mut StoreMut) { match_rt!(on self => f { f.data_and_store_mut() }) @@ -207,23 +207,23 @@ impl BackendFunctionEnvMut<'_, T> { } impl AsStoreRef for BackendFunctionEnvMut<'_, T> { - fn as_store_ref(&self) -> StoreRef<'_> { - match_rt!(on &self => f { - f.as_store_ref() + fn as_ref(&self) -> &crate::StoreInner { + match_rt!(on self => f { + f.as_ref() }) } } impl AsStoreMut for BackendFunctionEnvMut<'_, T> { - fn as_store_mut(&mut self) -> StoreMut<'_> { - match_rt!(on self => s { - s.as_store_mut() + fn as_mut(&mut self) -> &mut crate::StoreInner { + match_rt!(on self => f { + f.as_mut() }) } - fn objects_mut(&mut self) -> &mut crate::StoreObjects { - match_rt!(on self => s { - s.objects_mut() + fn reborrow_mut(&mut self) -> &mut StoreMut { + match_rt!(on self => f { + f.reborrow_mut() }) } } diff --git a/lib/api/src/entities/function/env/mod.rs b/lib/api/src/entities/function/env/mod.rs index ed897289cf8..9856070fee3 100644 --- a/lib/api/src/entities/function/env/mod.rs +++ b/lib/api/src/entities/function/env/mod.rs @@ -79,25 +79,30 @@ impl FunctionEnvMut<'_, T> { self.0.as_mut() } + /// Borrows a new mutable reference to the attached Store + pub fn as_store_mut(&mut self) -> &mut StoreMut { + self.reborrow_mut() + } + /// Borrows a new mutable reference of both the attached Store and host state - pub fn data_and_store_mut(&mut self) -> (&mut T, StoreMut<'_>) { + pub fn data_and_store_mut(&mut self) -> (&mut T, &mut StoreMut) { self.0.data_and_store_mut() } } impl AsStoreRef for FunctionEnvMut<'_, T> { - fn as_store_ref(&self) -> StoreRef<'_> { - self.0.as_store_ref() + fn as_ref(&self) -> &crate::StoreInner { + self.0.as_ref() } } impl AsStoreMut for FunctionEnvMut<'_, T> { - fn as_store_mut(&mut self) -> StoreMut<'_> { - self.0.as_store_mut() + fn as_mut(&mut self) -> &mut crate::StoreInner { + self.0.as_mut() } - fn objects_mut(&mut self) -> &mut crate::StoreObjects { - self.0.objects_mut() + fn reborrow_mut(&mut self) -> &mut StoreMut { + self.0.reborrow_mut() } } diff --git a/lib/api/src/entities/function/inner.rs b/lib/api/src/entities/function/inner.rs index 778908f67bc..0785c39b171 100644 --- a/lib/api/src/entities/function/inner.rs +++ b/lib/api/src/entities/function/inner.rs @@ -44,7 +44,7 @@ impl BackendFunction { FT: Into, F: Fn(&[Value]) -> Result, RuntimeError> + 'static + Send + Sync, { - let env = FunctionEnv::new(&mut store.as_store_mut(), ()); + let env = FunctionEnv::new(store, ()); let wrapped_func = move |_env: FunctionEnvMut<()>, args: &[Value]| -> Result, RuntimeError> { func(args) }; @@ -102,7 +102,7 @@ impl BackendFunction { + Send + Sync, { - match &store.as_store_mut().inner.store { + match &store.as_mut().store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => Self::Sys( crate::backend::sys::entities::function::Function::new_with_env( @@ -150,7 +150,7 @@ impl BackendFunction { Args: WasmTypeList + 'static, Rets: WasmTypeList + 'static, { - match &store.as_store_mut().inner.store { + match &store.as_mut().store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => { Self::Sys(crate::backend::sys::entities::function::Function::new_typed(store, func)) @@ -209,7 +209,7 @@ impl BackendFunction { Args: WasmTypeList + 'static, Rets: WasmTypeList + 'static, { - match &store.as_store_mut().inner.store { + match &store.as_mut().store { #[cfg(feature = "sys")] crate::BackendStore::Sys(s) => Self::Sys( crate::backend::sys::entities::function::Function::new_typed_with_env( @@ -250,118 +250,119 @@ impl BackendFunction { } } - #[inline] - pub fn new_async(store: &mut impl AsStoreMut, ty: FT, func: F) -> Self - where - FT: Into, - F: Fn(&[Value]) -> Fut + 'static + Send + Sync, - Fut: Future, RuntimeError>> + 'static + Send, - { - match &store.as_store_mut().inner.store { - #[cfg(feature = "sys")] - crate::BackendStore::Sys(_) => Self::Sys( - crate::backend::sys::entities::function::Function::new_async(store, ty, func), - ), - #[cfg(feature = "wamr")] - crate::BackendStore::Wamr(_) => unsupported_async_backend("wamr"), - #[cfg(feature = "wasmi")] - crate::BackendStore::Wasmi(_) => unsupported_async_backend("wasmi"), - #[cfg(feature = "v8")] - crate::BackendStore::V8(_) => unsupported_async_backend("v8"), - #[cfg(feature = "js")] - crate::BackendStore::Js(_) => unsupported_async_backend("js"), - #[cfg(feature = "jsc")] - crate::BackendStore::Jsc(_) => unsupported_async_backend("jsc"), - } - } - - #[inline] - pub fn new_with_env_async( - store: &mut impl AsStoreMut, - env: &FunctionEnv, - ty: FT, - func: F, - ) -> Self - where - FT: Into, - F: Fn(FunctionEnvMut, &[Value]) -> Fut + 'static + Send + Sync, - Fut: Future, RuntimeError>> + 'static + Send, - { - match &store.as_store_mut().inner.store { - #[cfg(feature = "sys")] - crate::BackendStore::Sys(_) => Self::Sys( - crate::backend::sys::entities::function::Function::new_with_env_async( - store, env, ty, func, - ), - ), - #[cfg(feature = "wamr")] - crate::BackendStore::Wamr(_) => unsupported_async_backend("wamr"), - #[cfg(feature = "wasmi")] - crate::BackendStore::Wasmi(_) => unsupported_async_backend("wasmi"), - #[cfg(feature = "v8")] - crate::BackendStore::V8(_) => unsupported_async_backend("v8"), - #[cfg(feature = "js")] - crate::BackendStore::Js(_) => unsupported_async_backend("js"), - #[cfg(feature = "jsc")] - crate::BackendStore::Jsc(_) => unsupported_async_backend("jsc"), - } - } - - #[inline] - pub fn new_typed_async(store: &mut impl AsStoreMut, func: F) -> Self - where - F: AsyncHostFunction<(), Args, Rets, WithoutEnv> + Send + Sync + 'static, - Args: WasmTypeList + 'static, - Rets: WasmTypeList + 'static, - { - match &store.as_store_mut().inner.store { - #[cfg(feature = "sys")] - crate::BackendStore::Sys(_) => Self::Sys( - crate::backend::sys::entities::function::Function::new_typed_async(store, func), - ), - #[cfg(feature = "wamr")] - crate::BackendStore::Wamr(_) => unsupported_async_backend("wamr"), - #[cfg(feature = "wasmi")] - crate::BackendStore::Wasmi(_) => unsupported_async_backend("wasmi"), - #[cfg(feature = "v8")] - crate::BackendStore::V8(_) => unsupported_async_backend("v8"), - #[cfg(feature = "js")] - crate::BackendStore::Js(_) => unsupported_async_backend("js"), - #[cfg(feature = "jsc")] - crate::BackendStore::Jsc(_) => unsupported_async_backend("jsc"), - } - } - - #[inline] - pub fn new_typed_with_env_async( - store: &mut impl AsStoreMut, - env: &FunctionEnv, - func: F, - ) -> Self - where - F: AsyncHostFunction + Send + Sync + 'static, - Args: WasmTypeList + 'static, - Rets: WasmTypeList + 'static, - { - match &store.as_store_mut().inner.store { - #[cfg(feature = "sys")] - crate::BackendStore::Sys(_) => Self::Sys( - crate::backend::sys::entities::function::Function::new_typed_with_env_async( - store, env, func, - ), - ), - #[cfg(feature = "wamr")] - crate::BackendStore::Wamr(_) => unsupported_async_backend("wamr"), - #[cfg(feature = "wasmi")] - crate::BackendStore::Wasmi(_) => unsupported_async_backend("wasmi"), - #[cfg(feature = "v8")] - crate::BackendStore::V8(_) => unsupported_async_backend("v8"), - #[cfg(feature = "js")] - crate::BackendStore::Js(_) => unsupported_async_backend("js"), - #[cfg(feature = "jsc")] - crate::BackendStore::Jsc(_) => unsupported_async_backend("jsc"), - } - } + // TODO: async API + // #[inline] + // pub fn new_async(store: &mut impl AsStoreMut, ty: FT, func: F) -> Self + // where + // FT: Into, + // F: Fn(&[Value]) -> Fut + 'static + Send + Sync, + // Fut: Future, RuntimeError>> + 'static + Send, + // { + // match &store.as_mut().store { + // #[cfg(feature = "sys")] + // crate::BackendStore::Sys(_) => Self::Sys( + // crate::backend::sys::entities::function::Function::new_async(store, ty, func), + // ), + // #[cfg(feature = "wamr")] + // crate::BackendStore::Wamr(_) => unsupported_async_backend("wamr"), + // #[cfg(feature = "wasmi")] + // crate::BackendStore::Wasmi(_) => unsupported_async_backend("wasmi"), + // #[cfg(feature = "v8")] + // crate::BackendStore::V8(_) => unsupported_async_backend("v8"), + // #[cfg(feature = "js")] + // crate::BackendStore::Js(_) => unsupported_async_backend("js"), + // #[cfg(feature = "jsc")] + // crate::BackendStore::Jsc(_) => unsupported_async_backend("jsc"), + // } + // } + + // #[inline] + // pub fn new_with_env_async( + // store: &mut impl AsStoreMut, + // env: &FunctionEnv, + // ty: FT, + // func: F, + // ) -> Self + // where + // FT: Into, + // F: Fn(FunctionEnvMut, &[Value]) -> Fut + 'static + Send + Sync, + // Fut: Future, RuntimeError>> + 'static + Send, + // { + // match &store.as_mut().store { + // #[cfg(feature = "sys")] + // crate::BackendStore::Sys(_) => Self::Sys( + // crate::backend::sys::entities::function::Function::new_with_env_async( + // store, env, ty, func, + // ), + // ), + // #[cfg(feature = "wamr")] + // crate::BackendStore::Wamr(_) => unsupported_async_backend("wamr"), + // #[cfg(feature = "wasmi")] + // crate::BackendStore::Wasmi(_) => unsupported_async_backend("wasmi"), + // #[cfg(feature = "v8")] + // crate::BackendStore::V8(_) => unsupported_async_backend("v8"), + // #[cfg(feature = "js")] + // crate::BackendStore::Js(_) => unsupported_async_backend("js"), + // #[cfg(feature = "jsc")] + // crate::BackendStore::Jsc(_) => unsupported_async_backend("jsc"), + // } + // } + + // #[inline] + // pub fn new_typed_async(store: &mut impl AsStoreMut, func: F) -> Self + // where + // F: AsyncHostFunction<(), Args, Rets, WithoutEnv> + Send + Sync + 'static, + // Args: WasmTypeList + 'static, + // Rets: WasmTypeList + 'static, + // { + // match &store.as_mut().store { + // #[cfg(feature = "sys")] + // crate::BackendStore::Sys(_) => Self::Sys( + // crate::backend::sys::entities::function::Function::new_typed_async(store, func), + // ), + // #[cfg(feature = "wamr")] + // crate::BackendStore::Wamr(_) => unsupported_async_backend("wamr"), + // #[cfg(feature = "wasmi")] + // crate::BackendStore::Wasmi(_) => unsupported_async_backend("wasmi"), + // #[cfg(feature = "v8")] + // crate::BackendStore::V8(_) => unsupported_async_backend("v8"), + // #[cfg(feature = "js")] + // crate::BackendStore::Js(_) => unsupported_async_backend("js"), + // #[cfg(feature = "jsc")] + // crate::BackendStore::Jsc(_) => unsupported_async_backend("jsc"), + // } + // } + + // #[inline] + // pub fn new_typed_with_env_async( + // store: &mut impl AsStoreMut, + // env: &FunctionEnv, + // func: F, + // ) -> Self + // where + // F: AsyncHostFunction + Send + Sync + 'static, + // Args: WasmTypeList + 'static, + // Rets: WasmTypeList + 'static, + // { + // match &store.as_mut().store { + // #[cfg(feature = "sys")] + // crate::BackendStore::Sys(_) => Self::Sys( + // crate::backend::sys::entities::function::Function::new_typed_with_env_async( + // store, env, func, + // ), + // ), + // #[cfg(feature = "wamr")] + // crate::BackendStore::Wamr(_) => unsupported_async_backend("wamr"), + // #[cfg(feature = "wasmi")] + // crate::BackendStore::Wasmi(_) => unsupported_async_backend("wasmi"), + // #[cfg(feature = "v8")] + // crate::BackendStore::V8(_) => unsupported_async_backend("v8"), + // #[cfg(feature = "js")] + // crate::BackendStore::Js(_) => unsupported_async_backend("js"), + // #[cfg(feature = "jsc")] + // crate::BackendStore::Jsc(_) => unsupported_async_backend("jsc"), + // } + // } /// Returns the [`FunctionType`] of the `Function`. /// @@ -528,7 +529,7 @@ impl BackendFunction { #[inline] pub(crate) unsafe fn from_vm_funcref(store: &mut impl AsStoreMut, funcref: VMFuncRef) -> Self { - match &store.as_store_mut().inner.store { + match &store.as_mut().store { #[cfg(feature = "sys")] crate::BackendStore::Sys(s) => Self::Sys(unsafe { crate::backend::sys::entities::function::Function::from_vm_funcref( @@ -694,7 +695,7 @@ impl BackendFunction { } pub(crate) fn from_vm_extern(store: &mut impl AsStoreMut, vm_extern: VMExternFunction) -> Self { - match &store.as_store_mut().inner.store { + match &store.as_mut().store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => Self::Sys( crate::backend::sys::entities::function::Function::from_vm_extern(store, vm_extern), diff --git a/lib/api/src/entities/function/mod.rs b/lib/api/src/entities/function/mod.rs index 0def8df4eaa..da59c7128ac 100644 --- a/lib/api/src/entities/function/mod.rs +++ b/lib/api/src/entities/function/mod.rs @@ -155,63 +155,64 @@ impl Function { Self(BackendFunction::new_typed_with_env(store, env, func)) } - /// Creates a new async host `Function` (dynamic) with the provided signature. - /// - /// The provided closure returns a future that resolves to the function results. - /// When invoked synchronously (via [`Function::call`]) the future will run to - /// completion immediately. When invoked through [`Function::call_async`], the - /// future may suspend and resume according to JSPI semantics. - pub fn new_async(store: &mut impl AsStoreMut, ty: FT, func: F) -> Self - where - FT: Into, - F: Fn(&[Value]) -> Fut + 'static + Send + Sync, - Fut: Future, RuntimeError>> + 'static + Send, - { - Self(BackendFunction::new_async(store, ty, func)) - } + // TODO: async API + // /// Creates a new async host `Function` (dynamic) with the provided signature. + // /// + // /// The provided closure returns a future that resolves to the function results. + // /// When invoked synchronously (via [`Function::call`]) the future will run to + // /// completion immediately, provided it doesn't suspend. When invoked through + // /// [`Function::call_async`], the future may suspend and resume as needed. + // pub fn new_async(store: &mut impl AsStoreMut, ty: FT, func: F) -> Self + // where + // FT: Into, + // F: Fn(&[Value]) -> Fut + 'static + Send + Sync, + // Fut: Future, RuntimeError>> + 'static + Send, + // { + // Self(BackendFunction::new_async(store, ty, func)) + // } - /// Creates a new async host `Function` (dynamic) with the provided signature - /// and environment. - pub fn new_with_env_async( - store: &mut impl AsStoreMut, - env: &FunctionEnv, - ty: FT, - func: F, - ) -> Self - where - FT: Into, - F: Fn(FunctionEnvMut, &[Value]) -> Fut + 'static + Send + Sync, - Fut: Future, RuntimeError>> + 'static + Send, - { - Self(BackendFunction::new_with_env_async(store, env, ty, func)) - } + // /// Creates a new async host `Function` (dynamic) with the provided signature + // /// and environment. + // pub fn new_with_env_async( + // store: &mut impl AsStoreMut, + // env: &FunctionEnv, + // ty: FT, + // func: F, + // ) -> Self + // where + // FT: Into, + // F: Fn(FunctionEnvMut, &[Value]) -> Fut + 'static + Send + Sync, + // Fut: Future, RuntimeError>> + 'static + Send, + // { + // Self(BackendFunction::new_with_env_async(store, env, ty, func)) + // } - /// Creates a new async host `Function` from a native typed function. - /// - /// The future can return either the raw result tuple or any type that implements - /// [`IntoResult`] for the result tuple (e.g. `Result(store: &mut impl AsStoreMut, func: F) -> Self - where - Rets: WasmTypeList + 'static, - Args: WasmTypeList + 'static, - F: AsyncHostFunction<(), Args, Rets, WithoutEnv> + Send + Sync + 'static, - { - Self(BackendFunction::new_typed_async(store, func)) - } + // /// Creates a new async host `Function` from a native typed function. + // /// + // /// The future can return either the raw result tuple or any type that implements + // /// [`IntoResult`] for the result tuple (e.g. `Result(store: &mut impl AsStoreMut, func: F) -> Self + // where + // Rets: WasmTypeList + 'static, + // Args: WasmTypeList + 'static, + // F: AsyncHostFunction<(), Args, Rets, WithoutEnv> + Send + Sync + 'static, + // { + // Self(BackendFunction::new_typed_async(store, func)) + // } - /// Creates a new async host `Function` with an environment from a typed function. - pub fn new_typed_with_env_async( - store: &mut impl AsStoreMut, - env: &FunctionEnv, - func: F, - ) -> Self - where - Rets: WasmTypeList + 'static, - Args: WasmTypeList + 'static, - F: AsyncHostFunction + Send + Sync + 'static, - { - Self(BackendFunction::new_typed_with_env_async(store, env, func)) - } + // /// Creates a new async host `Function` with an environment from a typed function. + // pub fn new_typed_with_env_async( + // store: &mut impl AsStoreMut, + // env: &FunctionEnv, + // func: F, + // ) -> Self + // where + // Rets: WasmTypeList + 'static, + // Args: WasmTypeList + 'static, + // F: AsyncHostFunction + Send + Sync + 'static, + // { + // Self(BackendFunction::new_typed_with_env_async(store, env, func)) + // } /// Returns the [`FunctionType`] of the `Function`. /// @@ -316,20 +317,21 @@ impl Function { self.0.call(store, params) } - /// Calls the function asynchronously. - /// - /// The returned future drives execution of the WebAssembly function on a - /// coroutine stack. Host functions created with [`Function::new_async`] may - /// suspend execution by awaiting futures, and their completion will resume - /// the Wasm instance according to the JSPI proposal. - pub fn call_async<'a>( - &'a self, - store: &'a mut (impl AsStoreMut + 'static), - params: &'a [Value], - ) -> impl Future, RuntimeError>> + 'a { - let params_vec = params.to_vec(); - self.0.call_async(store, params_vec) - } + // TODO: async API + // /// Calls the function asynchronously. + // /// + // /// The returned future drives execution of the WebAssembly function on a + // /// coroutine stack. Host functions created with [`Function::new_async`] may + // /// suspend execution by awaiting futures, and their completion will resume + // /// the Wasm instance according to the JSPI proposal. + // pub fn call_async<'a>( + // &'a self, + // store: &'a mut (impl AsStoreMut + 'static), + // params: &'a [Value], + // ) -> impl Future, RuntimeError>> + 'a { + // let params_vec = params.to_vec(); + // self.0.call_async(store, params_vec) + // } #[doc(hidden)] #[allow(missing_docs)] diff --git a/lib/api/src/entities/global/inner.rs b/lib/api/src/entities/global/inner.rs index bb867eeba4a..363925a4baf 100644 --- a/lib/api/src/entities/global/inner.rs +++ b/lib/api/src/entities/global/inner.rs @@ -63,7 +63,7 @@ impl BackendGlobal { val: Value, mutability: Mutability, ) -> Result { - match &store.as_store_mut().inner.store { + match &store.as_mut().store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => Ok(Self::Sys( crate::backend::sys::global::Global::from_value(store, val, mutability)?, @@ -182,7 +182,7 @@ impl BackendGlobal { #[inline] pub(crate) fn from_vm_extern(store: &mut impl AsStoreMut, vm_extern: VMExternGlobal) -> Self { - match &store.as_store_mut().inner.store { + match &store.as_mut().store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => Self::Sys( crate::backend::sys::global::Global::from_vm_extern(store, vm_extern), diff --git a/lib/api/src/entities/imports.rs b/lib/api/src/entities/imports.rs index 2efb2f2606e..0074b1ddd13 100644 --- a/lib/api/src/entities/imports.rs +++ b/lib/api/src/entities/imports.rs @@ -311,6 +311,7 @@ mod test { #[test] fn namespace() { let mut store = Store::default(); + let mut store = store.as_mut(); let g1 = Global::new(&mut store, Value::I32(0)); let namespace = namespace! { "happy" => g1 @@ -333,6 +334,7 @@ mod test { use crate::Function; let mut store: Store = Default::default(); + let mut store = store.as_mut(); fn func(arg: i32) -> i32 { arg + 1 @@ -383,6 +385,7 @@ mod test { #[test] fn chaining_works() { let mut store = Store::default(); + let mut store = store.as_mut(); let g = Global::new(&mut store, Value::I32(0)); @@ -415,6 +418,7 @@ mod test { #[test] fn extending_conflict_overwrites() { let mut store = Store::default(); + let mut store = store.as_mut(); let g1 = Global::new(&mut store, Value::I32(0)); let g2 = Global::new(&mut store, Value::I64(0)); @@ -443,6 +447,7 @@ mod test { */ // now test it in reverse let mut store = Store::default(); + let mut store = store.as_mut(); let g1 = Global::new(&mut store, Value::I32(0)); let g2 = Global::new(&mut store, Value::I64(0)); diff --git a/lib/api/src/entities/instance.rs b/lib/api/src/entities/instance.rs index 761e160e9ce..7d78f941156 100644 --- a/lib/api/src/entities/instance.rs +++ b/lib/api/src/entities/instance.rs @@ -56,7 +56,7 @@ impl Instance { module: &Module, imports: &Imports, ) -> Result { - let (_inner, exports) = match &store.as_store_mut().inner.store { + let (_inner, exports) = match &store.as_mut().store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => { let (i, e) = crate::backend::sys::instance::Instance::new(store, module, imports)?; @@ -115,7 +115,7 @@ impl Instance { module: &Module, externs: &[Extern], ) -> Result { - let (_inner, exports) = match &store.as_store_mut().inner.store { + let (_inner, exports) = match &store.as_mut().store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => { let (i, e) = diff --git a/lib/api/src/entities/memory/inner.rs b/lib/api/src/entities/memory/inner.rs index 74dd52c9602..376477c79ca 100644 --- a/lib/api/src/entities/memory/inner.rs +++ b/lib/api/src/entities/memory/inner.rs @@ -28,7 +28,7 @@ impl BackendMemory { /// ``` #[inline] pub fn new(store: &mut impl AsStoreMut, ty: MemoryType) -> Result { - match &store.as_store_mut().inner.store { + match &store.as_mut().store { #[cfg(feature = "sys")] crate::BackendStore::Sys(s) => Ok(Self::Sys( crate::backend::sys::entities::memory::Memory::new(store, ty)?, @@ -59,7 +59,7 @@ impl BackendMemory { /// Create a memory object from an existing memory and attaches it to the store #[inline] pub fn new_from_existing(new_store: &mut impl AsStoreMut, memory: VMMemory) -> Self { - match new_store.as_store_mut().inner.store { + match new_store.as_mut().store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => Self::Sys( crate::backend::sys::entities::memory::Memory::new_from_existing( @@ -251,7 +251,7 @@ impl BackendMemory { #[inline] pub(crate) fn from_vm_extern(store: &mut impl AsStoreMut, vm_extern: VMExternMemory) -> Self { - match &store.as_store_mut().inner.store { + match &store.as_mut().store { #[cfg(feature = "sys")] crate::BackendStore::Sys(s) => Self::Sys( crate::backend::sys::entities::memory::Memory::from_vm_extern(store, vm_extern), diff --git a/lib/api/src/entities/memory/view/inner.rs b/lib/api/src/entities/memory/view/inner.rs index c0ff9103883..08d734b391c 100644 --- a/lib/api/src/entities/memory/view/inner.rs +++ b/lib/api/src/entities/memory/view/inner.rs @@ -19,7 +19,7 @@ impl<'a> BackendMemoryView<'a> { #[inline] #[allow(clippy::needless_return)] pub(crate) fn new(memory: &Memory, store: &'a (impl AsStoreRef + ?Sized)) -> Self { - match &store.as_store_ref().inner.store { + match &store.as_ref().store { #[cfg(feature = "sys")] crate::BackendStore::Sys(s) => Self::Sys( crate::backend::sys::entities::memory::view::MemoryView::new( diff --git a/lib/api/src/entities/store/context.rs b/lib/api/src/entities/store/context.rs new file mode 100644 index 00000000000..9703cbcf5fc --- /dev/null +++ b/lib/api/src/entities/store/context.rs @@ -0,0 +1,202 @@ +//! Thread-local storage for storing the current store context, +//! i.e. the currently active `StoreMut`(s). When a function is +//! called, an owned `StoreMut` value must be placed in the +//! store context, so it can be retrieved later when needed +//! (mainly when calling imported functions). We maintain a +//! stack because it is technically possible to have nested +//! `Function::call` invocations that use different stores, +//! such as: +//! call(store1, func1) -> wasm code -> imported func -> +//! call(store2, func2) +//! +//! Also note that this stack is maintained by both function +//! calls and the async_runtime to reflect the exact WASM +//! functions running on a given thread at any moment in +//! time. If a function suspends, its store context is +//! cleared and later reinstalled when it resumes. This lets +//! us use thread-local storage for the context without +//! requiring that async tasks are tied to specific threads. +//! +//! When something needs the "currently active" store context, +//! they will only look at the top entry in the stack. It is +//! always an error for code to try to access a store that's +//! "paused", i.e. not the top entry. This should be impossible +//! due to how the function call code is structured, but we +//! guard against it anyway. + +use std::{ + borrow::BorrowMut, + cell::{RefCell, UnsafeCell}, + mem::MaybeUninit, +}; + +use super::{AsStoreMut, AsStoreRef, StoreMut}; + +use wasmer_types::StoreId; + +pub(crate) struct StoreContext { + id: StoreId, + + // StoreContexts can be used recursively when Function::call + // is used in an imported function. In the scenario, we're + // essentially passing a mutable borrow of the store into + // Function::call. However, entering the WASM code loses the + // reference, and it needs to be re-acquired from the + // StoreContext. This is why we use an UnsafeCell to allow + // multiple mutable references to the StoreMut; we do however + // keep track of how many borrows there are so we don't drop + // it prematurely. + borrow_count: u32, + store_mut: UnsafeCell, +} + +pub(crate) struct StoreMutWrapper { + // Need MaybeUninit for the Drop impl + store_mut: *mut StoreMut, +} + +pub(crate) enum StoreInstallGuard<'a> { + Installed { + store_id: StoreId, + store_mut: &'a mut dyn AsStoreMut, + }, + NotInstalled, +} + +thread_local! { + static STORE_CONTEXT_STACK: RefCell> = RefCell::new(Vec::new()); +} + +impl StoreContext { + fn is_active(id: StoreId) -> bool { + STORE_CONTEXT_STACK.with(|cell| { + let stack = cell.borrow(); + stack.last().map_or(false, |ctx| ctx.id == id) + }) + } + + fn is_suspended(id: StoreId) -> bool { + STORE_CONTEXT_STACK.with(|cell| { + let stack = cell.borrow(); + stack.iter().rev().skip(1).any(|ctx| ctx.id == id) + }) + } + + fn install(store_mut: StoreMut) { + // No need to scan through the list, only one StoreMut + // can be active at any time because of the RwLock in + // Store. + let id = store_mut.objects().id(); + STORE_CONTEXT_STACK.with(|cell| { + let mut stack = cell.borrow_mut(); + stack.push(StoreContext { + id, + borrow_count: 0, + store_mut: UnsafeCell::new(store_mut), + }); + }) + } + + /// Ensure that a store context with the given id is installed. + /// Returns true if the [`StoreMut`] was taken out of the provided + /// [`AsStoreMut`] and installed, false if it was already active. + pub(crate) fn ensure_installed<'a>( + store_mut: &'a mut impl AsStoreMut, + ) -> StoreInstallGuard<'a> { + let store_id = store_mut.objects().id(); + if Self::is_active(store_id) { + StoreInstallGuard::NotInstalled + } else { + let Some(store_mut_instance) = store_mut.take() else { + if Self::is_suspended(store_id) { + // Impossible because you can't have two writable locks on the Store + unreachable!( + "Cannot install store context recursively. \ + This should be impossible; please open an issue \ + describing how you ran into this panic at + https://github.com/wasmerio/wasmer/issues/new/choose" + ); + } + // Document the expected usage of Function::call here in case someone + // does too many weird things since, without doing weird things, the + // only way for embedder code to gain access to an AsStoreMut is by + // going through Store::as_mut anyway. + panic!( + "Failed to install store context because the provided AsStoreMut \ + implementation does not own its StoreMut. The usual cause of this \ + error is Function::call or Module::instantiate not being called \ + with the output from Store::as_mut." + ); + }; + Self::install(store_mut_instance); + StoreInstallGuard::Installed { + store_id, + store_mut, + } + } + } + + /// Safety: This method lets you borrow multiple mutable references + /// to the currently active StoreMut. The caller must ensure that: + /// * there is only one mutable reference alive, or + /// * all but one mutable reference are inaccessible and passed + /// into a function that lost the reference (e.g. into WASM code) + /// The intended, valid use-case for this method is from within + /// imported function trampolines. + pub(crate) unsafe fn get_current(id: StoreId) -> StoreMutWrapper { + STORE_CONTEXT_STACK.with(|cell| { + let mut stack = cell.borrow_mut(); + let top = stack + .last_mut() + .expect("No store context installed on this thread"); + assert_eq!(top.id, id, "Mismatched store context access"); + top.borrow_count += 1; + StoreMutWrapper { + store_mut: top.store_mut.get(), + } + }) + } +} + +impl StoreMutWrapper { + pub(crate) fn as_mut(&mut self) -> &mut StoreMut { + // Safety: the store_mut is always initialized unless the StoreMutWrapper + // is dropped, at which point it's impossible to call this function + unsafe { self.store_mut.as_mut().unwrap() } + } +} + +impl Drop for StoreMutWrapper { + fn drop(&mut self) { + let id = self.as_mut().objects().id(); + STORE_CONTEXT_STACK.with(|cell| { + let mut stack = cell.borrow_mut(); + let top = stack + .last_mut() + .expect("No store context installed on this thread"); + assert_eq!(top.id, id, "Mismatched store context reinstall"); + top.borrow_count -= 1; + }) + } +} + +impl Drop for StoreInstallGuard<'_> { + fn drop(&mut self) { + if let StoreInstallGuard::Installed { + store_id, + store_mut, + } = self + { + STORE_CONTEXT_STACK.with(|cell| { + let mut stack = cell.borrow_mut(); + let top = stack.pop().expect("Store context stack underflow"); + assert_eq!(top.id, *store_id, "Mismatched store context uninstall"); + assert_eq!( + top.borrow_count, 0, + "Cannot uninstall store context while it is still borrowed" + ); + store_mut.put_back(top.store_mut.into_inner()); + }) + } + } +} diff --git a/lib/api/src/entities/store/inner.rs b/lib/api/src/entities/store/inner.rs index 0cd7479152b..56d52806f9c 100644 --- a/lib/api/src/entities/store/inner.rs +++ b/lib/api/src/entities/store/inner.rs @@ -34,7 +34,7 @@ impl std::fmt::Debug for StoreInner { // TODO: better documentation! pub type OnCalledHandler = Box< dyn FnOnce( - StoreMut<'_>, + &mut StoreMut, ) -> Result>, >; diff --git a/lib/api/src/entities/store/mod.rs b/lib/api/src/entities/store/mod.rs index cc403861f41..e573cf21c3c 100644 --- a/lib/api/src/entities/store/mod.rs +++ b/lib/api/src/entities/store/mod.rs @@ -1,17 +1,28 @@ //! Defines the [`Store`] data type and various useful traits and data types to interact with a //! store. +/// Defines the [`StoreContext`] type. +mod context; + /// Defines the [`StoreInner`] data type. mod inner; /// Create temporary handles to engines. mod store_ref; + +use async_lock::RwLock; +use std::{ + ops::{Deref, DerefMut}, + sync::{Arc, TryLockError}, +}; + pub use store_ref::*; mod obj; pub use obj::*; use crate::{AsEngineRef, BackendEngine, Engine, EngineRef}; +pub(crate) use context::*; pub(crate) use inner::*; use wasmer_types::StoreId; @@ -29,7 +40,7 @@ use wasmer_vm::TrapHandlerFn; /// For more informations, check out the [related WebAssembly specification] /// [related WebAssembly specification]: pub struct Store { - pub(crate) inner: Box, + pub(crate) inner: Arc>, } impl Store { @@ -65,11 +76,34 @@ impl Store { }; Self { - inner: Box::new(StoreInner { + inner: std::sync::Arc::new(async_lock::RwLock::new(StoreInner { objects: StoreObjects::from_store_ref(&store), on_called: None, store, - }), + })), + } + } + + /// Creates a new [`StoreRef`] if the store is available for reading. + pub(crate) fn make_ref(&self) -> Option { + self.inner + .try_read_arc() + .map(|guard| StoreRef { inner: guard }) + } + + /// Creates a new [`StoreRef`] if the store is available for reading. + pub(crate) fn make_mut(&self) -> Option { + self.inner + .try_write_arc() + .map(|guard| StoreMut { inner: guard }) + } + + /// Builds an [`AsStoreMut`] handle to this store, provided + /// the store is not locked. Panics if the store is already locked. + pub fn as_mut<'a>(&'a mut self) -> impl AsStoreMut + 'a { + StoreMutGuard { + inner: Some(self.make_mut().expect("Store is locked")), + marker: std::marker::PhantomData, } } @@ -83,19 +117,28 @@ impl Store { pub fn set_trap_handler(&mut self, handler: Option>>) { use crate::backend::sys::entities::store::NativeStoreExt; #[allow(irrefutable_let_patterns)] - if let BackendStore::Sys(ref mut s) = self.inner.store { + if let BackendStore::Sys(ref mut s) = self.make_mut().expect("Store is locked").inner.store + { s.set_trap_handler(handler) } } /// Returns the [`Engine`]. - pub fn engine(&self) -> &Engine { - self.inner.store.engine() + pub fn engine<'a>(&'a self) -> StoreEngineRef<'a> { + // Happily unwrap the read lock here because we don't expect + // embedder code to access stores in parallel. + StoreEngineRef { + inner: self.make_ref().expect("Store is locked"), + marker: std::marker::PhantomData, + } } /// Returns mutable reference to [`Engine`]. - pub fn engine_mut(&mut self) -> &mut Engine { - self.inner.store.engine_mut() + pub fn engine_mut<'a>(&'a mut self) -> StoreEngineMut<'a> { + StoreEngineMut { + inner: self.make_mut().expect("Store is locked"), + marker: std::marker::PhantomData, + } } /// Checks whether two stores are identical. A store is considered @@ -106,7 +149,7 @@ impl Store { /// Returns the ID of this store pub fn id(&self) -> StoreId { - self.inner.objects.id() + self.make_ref().expect("Store is locked").objects().id() } } @@ -133,29 +176,83 @@ impl std::fmt::Debug for Store { } } -impl AsEngineRef for Store { - fn as_engine_ref(&self) -> EngineRef<'_> { - self.inner.store.as_engine_ref() +/// Marker used to make the engine accessible from a store reference. +/// Needed because the store's lock must be held while accessing the engine. +/// +/// This struct borrows the [`Store`] to help prevent accidental deadlocks. +pub struct StoreEngineRef<'a> { + inner: StoreRef, + marker: std::marker::PhantomData<&'a ()>, +} + +impl Deref for StoreEngineRef<'_> { + type Target = Engine; + + fn deref(&self) -> &Self::Target { + self.inner.engine() + } +} + +/// Marker used to make the engine accessible from a store reference. +/// Needed because the store's lock must be held while accessing the engine. +/// +/// This struct borrows the [`Store`] to help prevent accidental deadlocks. +pub struct StoreEngineMut<'a> { + inner: StoreMut, + marker: std::marker::PhantomData<&'a ()>, +} + +impl Deref for StoreEngineMut<'_> { + type Target = Engine; + + fn deref(&self) -> &Self::Target { + self.inner.engine() } +} - fn maybe_as_store(&self) -> Option> { - Some(self.as_store_ref()) +impl DerefMut for StoreEngineMut<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.inner.engine_mut() } } -impl AsStoreRef for Store { - fn as_store_ref(&self) -> StoreRef<'_> { - StoreRef { inner: &self.inner } +/// A guard that provides mutable access to a [`Store`]. This is +/// the only way for embedders to construct an [`AsStoreMut`] +/// from a [`Store`]. The internal [`StoreMut`] is taken when +/// using this value to invoke [`Function::call`](crate::Function::call). +// TODO: can we put the value back after the function returns? We should be able to +// TODO: what would the API look like? +pub struct StoreMutGuard<'a> { + inner: Option, + marker: std::marker::PhantomData<&'a ()>, +} + +impl AsStoreRef for StoreMutGuard<'_> { + fn as_ref(&self) -> &StoreInner { + self.inner + .as_ref() + .expect("StoreMutGuard is taken") + .as_ref() } } -impl AsStoreMut for Store { - fn as_store_mut(&mut self) -> StoreMut<'_> { - StoreMut { - inner: &mut self.inner, - } + +impl AsStoreMut for StoreMutGuard<'_> { + fn as_mut(&mut self) -> &mut StoreInner { + self.inner + .as_mut() + .expect("StoreMutGuard is taken") + .as_mut() + } + + fn reborrow_mut(&mut self) -> &mut StoreMut { + self.inner.as_mut().expect("StoreMutGuard is taken") + } + + fn take(&mut self) -> Option { + self.inner.take() } - fn objects_mut(&mut self) -> &mut StoreObjects { - &mut self.inner.objects + fn put_back(&mut self, store_mut: StoreMut) { + assert!(self.inner.replace(store_mut).is_none()); } } diff --git a/lib/api/src/entities/store/store_ref.rs b/lib/api/src/entities/store/store_ref.rs index e6e155d7b11..6b304785842 100644 --- a/lib/api/src/entities/store/store_ref.rs +++ b/lib/api/src/entities/store/store_ref.rs @@ -1,133 +1,141 @@ -use std::ops::{Deref, DerefMut}; +use std::{ + ops::{Deref, DerefMut}, + sync::Arc, +}; use super::{StoreObjects, inner::StoreInner}; use crate::entities::engine::{AsEngineRef, Engine, EngineRef}; use wasmer_types::{ExternType, OnCalledAction}; -//use wasmer_vm::{StoreObjects, TrapHandlerFn}; #[cfg(feature = "sys")] use wasmer_vm::TrapHandlerFn; /// A temporary handle to a [`crate::Store`]. #[derive(Debug)] -pub struct StoreRef<'a> { - pub(crate) inner: &'a StoreInner, +pub struct StoreRef { + pub(crate) inner: async_lock::RwLockReadGuardArc, } -impl<'a> StoreRef<'a> { - pub(crate) fn objects(&self) -> &'a StoreObjects { - &self.inner.objects - } - - /// Returns the [`Engine`]. - pub fn engine(&self) -> &Engine { - self.inner.store.engine() - } - - /// Checks whether two stores are identical. A store is considered - /// equal to another store if both have the same engine. - pub fn same(a: &Self, b: &Self) -> bool { - StoreObjects::same(&a.inner.objects, &b.inner.objects) - } - - /// The signal handler - #[cfg(feature = "sys")] - #[inline] - pub fn signal_handler(&self) -> Option<*const TrapHandlerFn<'static>> { - use crate::backend::sys::entities::store::NativeStoreExt; - self.inner.store.as_sys().signal_handler() +impl StoreRef { + fn as_ref(&self) -> &StoreInner { + &self.inner } } /// A temporary handle to a [`crate::Store`]. -pub struct StoreMut<'a> { - pub(crate) inner: &'a mut StoreInner, +pub struct StoreMut { + pub(crate) inner: async_lock::RwLockWriteGuardArc, } -impl StoreMut<'_> { - /// Returns the [`Engine`]. - pub fn engine(&self) -> &Engine { - self.inner.store.engine() +impl StoreMut { + pub(crate) fn as_ref(&self) -> &StoreInner { + &self.inner } - /// Checks whether two stores are identical. A store is considered - /// equal to another store if both have the same engine. - pub fn same(a: &Self, b: &Self) -> bool { - StoreObjects::same(&a.inner.objects, &b.inner.objects) - } - - /// TODO: temporarily public, make private again - pub fn as_raw(&self) -> *mut StoreInner { - self.inner as *const StoreInner as *mut StoreInner - } - - /// TODO: temporarily public, make private again - /// - /// # Safety - /// Don't use it XD - pub unsafe fn from_raw(raw: *mut StoreInner) -> Self { - Self { - inner: unsafe { &mut *raw }, - } - } - - #[allow(unused)] - pub(crate) fn engine_and_objects_mut(&mut self) -> (&Engine, &mut StoreObjects) { - (self.inner.store.engine(), &mut self.inner.objects) + pub(crate) fn as_mut(&mut self) -> &mut StoreInner { + &mut self.inner } // TODO: OnCalledAction is needed for asyncify. It will be refactored with https://github.com/wasmerio/wasmer/issues/3451 /// Sets the unwind callback which will be invoked when the call finishes - pub fn on_called(&mut self, callback: F) + fn on_called(&mut self, callback: F) where - F: FnOnce(StoreMut<'_>) -> Result> + F: FnOnce( + &mut StoreMut, + ) -> Result> + Send + Sync + 'static, { - self.inner.on_called.replace(Box::new(callback)); + self.as_mut().on_called.replace(Box::new(callback)); } } -/// Helper trait for a value that is convertible to a [`StoreRef`]. +/// Helper trait for a value that provides immutable access to a [`Store`](crate::entities::Store). pub trait AsStoreRef { - /// Returns a `StoreRef` pointing to the underlying context. - fn as_store_ref(&self) -> StoreRef<'_>; + /// Returns a reference to the inner store. + fn as_ref(&self) -> &StoreInner; + + /// Returns a reference to the store objects. + fn objects(&self) -> &StoreObjects { + &self.as_ref().objects + } + + /// Returns the [`Engine`]. + fn engine(&self) -> &Engine { + self.as_ref().store.engine() + } + + /// Checks whether two stores are identical. A store is considered + /// equal to another store if both have the same engine. + fn same(&self, other: &dyn AsStoreRef) -> bool { + StoreObjects::same(&self.as_ref().objects, &other.as_ref().objects) + } + + /// The signal handler + #[cfg(feature = "sys")] + fn signal_handler(&self) -> Option<*const TrapHandlerFn<'static>> { + use crate::backend::sys::entities::store::NativeStoreExt; + self.as_ref().store.as_sys().signal_handler() + } } -/// Helper trait for a value that is convertible to a [`StoreMut`]. +/// Helper trait for a value that provides mutable access to a [`Store`](crate::entities::Store). pub trait AsStoreMut: AsStoreRef { - /// Returns a `StoreMut` pointing to the underlying context. - fn as_store_mut(&mut self) -> StoreMut<'_>; + /// Returns a mutable reference to the inner store. + fn as_mut(&mut self) -> &mut StoreInner; - /// Returns the ObjectMutable - fn objects_mut(&mut self) -> &mut StoreObjects; -} + /// Re-borrow this as a mutable reference to the underlying StoreMut. + /// This is useful for passing a generic `&mut impl AsStoreMut` to + /// non-generic functions. + fn reborrow_mut(&mut self) -> &mut StoreMut; -impl AsStoreRef for StoreRef<'_> { - fn as_store_ref(&self) -> StoreRef<'_> { - StoreRef { inner: self.inner } + /// Attempts to take the [`StoreMut`] instance out of this implementor. + fn take(&mut self) -> Option { + None + } + + /// Place the [`StoreMut`] instance back in this implementor. + fn put_back(&mut self, store_mut: StoreMut) { + panic!("Not supported") + } + + /// Returns a mutable reference to the store objects. + fn objects_mut(&mut self) -> &mut StoreObjects { + &mut self.as_mut().objects + } + + /// Returns the [`Engine`]. + fn engine_mut(&mut self) -> &mut Engine { + self.as_mut().store.engine_mut() + } + + /// Returns mutable references to the engine and store objects. + fn engine_and_objects_mut(&mut self) -> (&Engine, &mut StoreObjects) { + let self_ref = self.as_mut(); + (self_ref.store.engine(), &mut self_ref.objects) } } -impl AsEngineRef for StoreRef<'_> { - fn as_engine_ref(&self) -> EngineRef<'_> { - self.inner.store.as_engine_ref() +impl AsStoreRef for StoreRef { + fn as_ref(&self) -> &StoreInner { + self.as_ref() } } -impl AsStoreRef for StoreMut<'_> { - fn as_store_ref(&self) -> StoreRef<'_> { - StoreRef { inner: self.inner } +impl AsStoreRef for StoreMut { + fn as_ref(&self) -> &StoreInner { + self.as_ref() } } -impl AsStoreMut for StoreMut<'_> { - fn as_store_mut(&mut self) -> StoreMut<'_> { - StoreMut { inner: self.inner } + +impl AsStoreMut for StoreMut { + fn as_mut(&mut self) -> &mut StoreInner { + self.as_mut() } - fn objects_mut(&mut self) -> &mut StoreObjects { - &mut self.inner.objects + fn reborrow_mut(&mut self) -> &mut StoreMut { + self } } @@ -136,8 +144,8 @@ where P: Deref, P::Target: AsStoreRef, { - fn as_store_ref(&self) -> StoreRef<'_> { - (**self).as_store_ref() + fn as_ref(&self) -> &StoreInner { + (**self).as_ref() } } @@ -146,17 +154,31 @@ where P: DerefMut, P::Target: AsStoreMut, { - fn as_store_mut(&mut self) -> StoreMut<'_> { - (**self).as_store_mut() + fn as_mut(&mut self) -> &mut StoreInner { + (**self).as_mut() } - fn objects_mut(&mut self) -> &mut StoreObjects { - (**self).objects_mut() + fn reborrow_mut(&mut self) -> &mut StoreMut { + (**self).reborrow_mut() + } + + fn take(&mut self) -> Option { + (**self).take() + } + + fn put_back(&mut self, store_mut: StoreMut) { + (**self).put_back(store_mut) + } +} + +impl AsEngineRef for StoreRef { + fn as_engine_ref(&self) -> EngineRef<'_> { + self.as_ref().store.as_engine_ref() } } -impl AsEngineRef for StoreMut<'_> { +impl AsEngineRef for StoreMut { fn as_engine_ref(&self) -> EngineRef<'_> { - self.inner.store.as_engine_ref() + self.as_ref().store.as_engine_ref() } } diff --git a/lib/api/src/entities/table/inner.rs b/lib/api/src/entities/table/inner.rs index 13e6f86e1d0..a0f32103b7d 100644 --- a/lib/api/src/entities/table/inner.rs +++ b/lib/api/src/entities/table/inner.rs @@ -33,7 +33,7 @@ impl BackendTable { ty: TableType, init: Value, ) -> Result { - match &store.as_store_mut().inner.store { + match &store.as_mut().store { #[cfg(feature = "sys")] BackendStore::Sys(_) => Ok(Self::Sys( crate::backend::sys::entities::table::Table::new(store, ty, init)?, @@ -135,7 +135,7 @@ impl BackendTable { src_index: u32, len: u32, ) -> Result<(), RuntimeError> { - match &store.as_store_mut().inner.store { + match &store.as_mut().store { #[cfg(feature = "sys")] BackendStore::Sys(_) => crate::backend::sys::entities::table::Table::copy( store, @@ -196,7 +196,7 @@ impl BackendTable { #[inline] pub(crate) fn from_vm_extern(store: &mut impl AsStoreMut, ext: VMExternTable) -> Self { - match &store.as_store_mut().inner.store { + match &store.as_mut().store { #[cfg(feature = "sys")] BackendStore::Sys(_) => Self::Sys( crate::backend::sys::entities::table::Table::from_vm_extern(store, ext), @@ -242,6 +242,8 @@ impl BackendTable { #[cfg(test)] mod test { + use crate::AsStoreRef; + /// Check the example from . #[test] #[cfg_attr( @@ -261,7 +263,9 @@ mod test { // Tests that the table type of `table` is compatible with the export in the WAT // This tests that `wasmer_types::types::is_table_compatible` works as expected. let mut store = Store::default(); - let module = Module::new(&store, WAT).unwrap(); + let module = Module::new(&store.engine(), WAT).unwrap(); + + let mut store = store.as_mut(); let ty = TableType::new(Type::FuncRef, 0, None); let table = Table::new(&mut store, ty, Value::FuncRef(None)).unwrap(); table.grow(&mut store, 100, Value::FuncRef(None)).unwrap(); diff --git a/lib/api/src/entities/table/mod.rs b/lib/api/src/entities/table/mod.rs index bcb05aa5e21..317cc789677 100644 --- a/lib/api/src/entities/table/mod.rs +++ b/lib/api/src/entities/table/mod.rs @@ -142,7 +142,9 @@ mod test { // Tests that the table type of `table` is compatible with the export in the WAT // This tests that `wasmer_types::types::is_table_compatible` works as expected. let mut store = Store::default(); - let module = Module::new(&store, WAT).unwrap(); + let module = Module::new(&store.engine(), WAT).unwrap(); + + let mut store = store.as_mut(); let ty = TableType::new(Type::FuncRef, 0, None); let table = Table::new(&mut store, ty, Value::FuncRef(None)).unwrap(); table.grow(&mut store, 100, Value::FuncRef(None)).unwrap(); diff --git a/lib/api/src/entities/tag/inner.rs b/lib/api/src/entities/tag/inner.rs index 5d05c4bbd12..48ccf7ab20a 100644 --- a/lib/api/src/entities/tag/inner.rs +++ b/lib/api/src/entities/tag/inner.rs @@ -22,7 +22,7 @@ impl BackendTag { /// and has no return value. #[inline] pub fn new>>(store: &mut impl AsStoreMut, params: P) -> Self { - match &store.as_store_mut().inner.store { + match &store.as_mut().store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => { Self::Sys(crate::backend::sys::tag::Tag::new(store, params)) @@ -60,7 +60,7 @@ impl BackendTag { #[inline] pub(crate) fn from_vm_extern(store: &mut impl AsStoreMut, vm_extern: VMExternTag) -> Self { - match &store.as_store_mut().inner.store { + match &store.as_mut().store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => Self::Sys( crate::backend::sys::tag::Tag::from_vm_extern(store, vm_extern), diff --git a/lib/api/src/entities/value.rs b/lib/api/src/entities/value.rs index e50da08c10e..e2955c20c58 100644 --- a/lib/api/src/entities/value.rs +++ b/lib/api/src/entities/value.rs @@ -120,7 +120,7 @@ impl Value { Type::F32 => Self::F32(unsafe { raw.f32 }), Type::F64 => Self::F64(unsafe { raw.f64 }), Type::V128 => Self::V128(unsafe { raw.u128 }), - Type::FuncRef => match store.as_store_ref().inner.store { + Type::FuncRef => match store.as_ref().store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => Self::FuncRef({ unsafe { crate::backend::sys::vm::VMFuncRef::from_raw(raw).map(VMFuncRef::Sys) } @@ -157,7 +157,7 @@ impl Value { .map(|f| unsafe { Function::from_vm_funcref(store, f) }) }), }, - Type::ExternRef => match store.as_store_ref().inner.store { + Type::ExternRef => match store.as_ref().store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => Self::ExternRef({ unsafe { @@ -203,14 +203,11 @@ impl Value { .map(|f| unsafe { ExternRef::from_vm_externref(store, f) }) }), }, - Type::ExceptionRef => match store.as_store_ref().inner.store { + Type::ExceptionRef => match store.as_ref().store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => Self::ExceptionRef( unsafe { - crate::backend::sys::vm::VMExceptionRef::from_raw( - store.as_store_ref().objects().id(), - raw, - ) + crate::backend::sys::vm::VMExceptionRef::from_raw(store.objects().id(), raw) } .map(VMExceptionRef::Sys) .map(Exception::from_vm_exceptionref), diff --git a/lib/api/src/error.rs b/lib/api/src/error.rs index c283b8c106f..45802fbf7b9 100644 --- a/lib/api/src/error.rs +++ b/lib/api/src/error.rs @@ -159,8 +159,8 @@ impl RuntimeError { /// will be thrown in the WebAssembly code instead of the usual trapping. pub fn exception(ctx: &impl AsStoreRef, exception: Exception) -> Self { let exnref = exception.vm_exceptionref(); - let store = ctx.as_store_ref(); - match store.inner.objects { + let store = ctx.as_ref(); + match store.objects { #[cfg(feature = "sys")] crate::StoreObjects::Sys(ref store_objects) => { crate::backend::sys::vm::Trap::uncaught_exception( diff --git a/lib/api/src/lib.rs b/lib/api/src/lib.rs index c85f05cfa08..d93493305eb 100644 --- a/lib/api/src/lib.rs +++ b/lib/api/src/lib.rs @@ -51,7 +51,8 @@ //! "#; //! //! let mut store = Store::default(); -//! let module = Module::new(&store, &module_wat)?; +//! let module = Module::new(&store.engine(), &module_wat)?; +//! let mut store = store.as_mut(); //! // The module doesn't import anything, so we create an empty import object. //! let import_object = imports! {}; //! let instance = Instance::new(&mut store, &module, &import_object)?; @@ -146,12 +147,12 @@ //! [`imports!`] macro: //! //! ``` -//! # use wasmer::{imports, Function, FunctionEnv, FunctionEnvMut, Memory, MemoryType, Store, Imports}; -//! # fn imports_example(mut store: &mut Store) -> Imports { -//! let memory = Memory::new(&mut store, MemoryType::new(1, None, false)).unwrap(); +//! # use wasmer::{imports, Function, FunctionEnv, FunctionEnvMut, Memory, MemoryType, AsStoreMut, Imports}; +//! # fn imports_example(store: &mut impl AsStoreMut) -> Imports { +//! let memory = Memory::new(store, MemoryType::new(1, None, false)).unwrap(); //! imports! { //! "env" => { -//! "my_function" => Function::new_typed(&mut store, || println!("Hello")), +//! "my_function" => Function::new_typed(store, || println!("Hello")), //! "memory" => memory, //! } //! } @@ -162,12 +163,12 @@ //! from any instance via `instance.exports`: //! //! ``` -//! # use wasmer::{imports, Instance, FunctionEnv, Memory, TypedFunction, Store}; -//! # fn exports_example(mut env: FunctionEnv<()>, mut store: &mut Store, instance: &Instance) -> anyhow::Result<()> { +//! # use wasmer::{imports, Instance, FunctionEnv, Memory, TypedFunction, AsStoreMut}; +//! # fn exports_example(mut env: FunctionEnv<()>, store: &mut impl AsStoreMut, instance: &Instance) -> anyhow::Result<()> { //! let memory = instance.exports.get_memory("memory")?; //! let memory: &Memory = instance.exports.get("some_other_memory")?; -//! let add: TypedFunction<(i32, i32), i32> = instance.exports.get_typed_function(&mut store, "add")?; -//! let result = add.call(&mut store, 5, 37)?; +//! let add: TypedFunction<(i32, i32), i32> = instance.exports.get_typed_function(store, "add")?; +//! let result = add.call(store, 5, 37)?; //! assert_eq!(result, 42); //! # Ok(()) //! # } @@ -379,7 +380,8 @@ //! i32.add)) //! "#; //! let mut store = Store::default(); -//! let module = Module::new(&store, &module_wat).unwrap(); +//! let module = Module::new(&store.engine(), &module_wat).unwrap(); +//! let mut store = store.as_mut(); //! // The module doesn't import anything, so we create an empty import object. //! let import_object = imports! {}; //! let instance = Instance::new(&mut store, &module, &import_object).unwrap(); diff --git a/lib/api/src/utils/native/convert.rs b/lib/api/src/utils/native/convert.rs index 05c4a1b9771..9a755c4977a 100644 --- a/lib/api/src/utils/native/convert.rs +++ b/lib/api/src/utils/native/convert.rs @@ -196,7 +196,7 @@ impl NativeWasmType for ExternRef { impl NativeWasmTypeInto for Option { #[inline] unsafe fn from_abi(store: &mut impl AsStoreMut, abi: Self::Abi) -> Self { - match store.as_store_ref().inner.store { + match store.as_ref().store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => unsafe { wasmer_vm::VMExternRef::from_raw(RawValue { externref: abi }).map(VMExternRef::Sys) @@ -242,7 +242,7 @@ impl NativeWasmTypeInto for Option { #[inline] unsafe fn from_raw(store: &mut impl AsStoreMut, raw: RawValue) -> Self { - match store.as_store_ref().inner.store { + match store.as_ref().store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => unsafe { wasmer_vm::VMExternRef::from_raw(raw).map(VMExternRef::Sys) @@ -290,7 +290,7 @@ impl NativeWasmType for Function { impl NativeWasmTypeInto for Option { #[inline] unsafe fn from_abi(store: &mut impl AsStoreMut, abi: Self::Abi) -> Self { - match store.as_store_ref().inner.store { + match store.as_ref().store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => unsafe { wasmer_vm::VMFuncRef::from_raw(RawValue { funcref: abi }).map(VMFuncRef::Sys) @@ -338,7 +338,7 @@ impl NativeWasmTypeInto for Option { #[inline] unsafe fn from_raw(store: &mut impl AsStoreMut, raw: RawValue) -> Self { - match store.as_store_ref().inner.store { + match store.as_ref().store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => unsafe { wasmer_vm::VMFuncRef::from_raw(raw).map(VMFuncRef::Sys) diff --git a/lib/api/src/utils/native/typed_func.rs b/lib/api/src/utils/native/typed_func.rs index e95a436b60e..e16855e63e4 100644 --- a/lib/api/src/utils/native/typed_func.rs +++ b/lib/api/src/utils/native/typed_func.rs @@ -62,7 +62,7 @@ macro_rules! impl_native_traits { $( let [] = $x; )* - match store.as_store_mut().inner.store { + match store.as_mut().store { #[cfg(feature = "sys")] BackendStore::Sys(_) => self.call_sys(store, $([]),*), #[cfg(feature = "wamr")] @@ -79,46 +79,47 @@ macro_rules! impl_native_traits { } } - /// Call the typed func asynchronously. - #[allow(unused_mut)] - #[allow(clippy::too_many_arguments)] - pub fn call_async<'a>( - &'a self, - store: &'a mut (impl AsStoreMut + 'static), - $( $x: $x, )* - ) -> impl Future> + 'a - where - $( $x: FromToNativeWasmType, )* - { - $( - let [] = $x; - )* - async move { - match store.as_store_mut().inner.store { - #[cfg(feature = "sys")] - BackendStore::Sys(_) => { - self.call_async_sys(store, $([]),*).await - } - #[cfg(feature = "wamr")] - BackendStore::Wamr(_) => async_backend_error(), - #[cfg(feature = "wasmi")] - BackendStore::Wasmi(_) => async_backend_error(), - #[cfg(feature = "v8")] - BackendStore::V8(_) => async_backend_error(), - #[cfg(feature = "js")] - BackendStore::Js(_) => async_backend_error(), - #[cfg(feature = "jsc")] - BackendStore::Jsc(_) => async_backend_error(), - } - } - } + // TODO: async api + // /// Call the typed func asynchronously. + // #[allow(unused_mut)] + // #[allow(clippy::too_many_arguments)] + // pub fn call_async<'a>( + // &'a self, + // store: &'a mut (impl AsStoreMut + 'static), + // $( $x: $x, )* + // ) -> impl Future> + 'a + // where + // $( $x: FromToNativeWasmType, )* + // { + // $( + // let [] = $x; + // )* + // async move { + // match store.as_mut().store { + // #[cfg(feature = "sys")] + // BackendStore::Sys(_) => { + // self.call_async_sys(store, $([]),*).await + // } + // #[cfg(feature = "wamr")] + // BackendStore::Wamr(_) => async_backend_error(), + // #[cfg(feature = "wasmi")] + // BackendStore::Wasmi(_) => async_backend_error(), + // #[cfg(feature = "v8")] + // BackendStore::V8(_) => async_backend_error(), + // #[cfg(feature = "js")] + // BackendStore::Js(_) => async_backend_error(), + // #[cfg(feature = "jsc")] + // BackendStore::Jsc(_) => async_backend_error(), + // } + // } + // } #[doc(hidden)] #[allow(missing_docs)] #[allow(unused_mut)] #[allow(clippy::too_many_arguments)] pub fn call_raw(&self, store: &mut impl AsStoreMut, mut params_list: Vec ) -> Result { - match store.as_store_mut().inner.store { + match store.as_mut().store { #[cfg(feature = "sys")] BackendStore::Sys(_) => self.call_raw_sys(store, params_list), #[cfg(feature = "wamr")] diff --git a/lib/api/tests/externals.rs b/lib/api/tests/externals.rs index f59611a8c81..328c6ca6e79 100644 --- a/lib/api/tests/externals.rs +++ b/lib/api/tests/externals.rs @@ -7,6 +7,7 @@ use wasmer::*; #[universal_test] fn global_new() -> Result<(), String> { let mut store = Store::default(); + let mut store = store.as_mut(); let global = Global::new(&mut store, Value::I32(10)); assert_eq!( global.ty(&store), @@ -35,6 +36,7 @@ fn global_new() -> Result<(), String> { )] fn global_get() -> Result<(), String> { let mut store = Store::default(); + let mut store = store.as_mut(); let global_i32 = Global::new(&mut store, Value::I32(10)); assert_eq!(global_i32.get(&mut store), Value::I32(10)); @@ -66,6 +68,7 @@ fn global_get() -> Result<(), String> { )] fn global_set() -> Result<(), String> { let mut store = Store::default(); + let mut store = store.as_mut(); let global_i32 = Global::new(&mut store, Value::I32(10)); // Set on a constant should error assert!(global_i32.set(&mut store, Value::I32(20)).is_err()); @@ -87,6 +90,7 @@ fn global_set() -> Result<(), String> { #[cfg_attr(feature = "wasmi", ignore = "wasmi does not support funcrefs")] fn table_new() -> Result<(), String> { let mut store = Store::default(); + let mut store = store.as_mut(); let table_type = TableType { ty: Type::FuncRef, minimum: 0, @@ -136,6 +140,7 @@ fn table_set() -> Result<(), String> { #[cfg(feature = "sys")] { let mut store = Store::default(); + let mut store = store.as_mut(); let table_type = TableType { ty: Type::ExternRef, @@ -213,6 +218,7 @@ fn table_grow() -> Result<(), String> { #[cfg(feature = "sys")] { let mut store = Store::default(); + let mut store = store.as_mut(); let table_type = TableType { ty: Type::FuncRef, minimum: 0, @@ -249,6 +255,7 @@ fn table_copy() -> Result<(), String> { #[universal_test] fn memory_new() -> Result<(), String> { let mut store = Store::default(); + let mut store = store.as_mut(); let memory_type = MemoryType { shared: cfg!(feature = "wamr"), minimum: Pages(0), @@ -267,6 +274,7 @@ fn memory_new() -> Result<(), String> { )] fn memory_grow() -> Result<(), String> { let mut store = Store::default(); + let mut store = store.as_mut(); let desc = MemoryType::new(Pages(10), Some(Pages(16)), false); let memory = Memory::new(&mut store, desc).map_err(|e| format!("{e:?}"))?; assert_eq!(memory.view(&store).size(), Pages(10)); @@ -298,6 +306,7 @@ fn memory_grow() -> Result<(), String> { #[universal_test] fn function_new() -> Result<(), String> { let mut store = Store::default(); + let mut store = store.as_mut(); let function = Function::new_typed(&mut store, || {}); assert_eq!(function.ty(&store), FunctionType::new(vec![], vec![])); let function = Function::new_typed(&mut store, |_a: i32| {}); @@ -326,6 +335,7 @@ fn function_new() -> Result<(), String> { #[universal_test] fn function_new_env() -> Result<(), String> { let mut store = Store::default(); + let mut store = store.as_mut(); #[derive(Clone)] struct MyEnv {} @@ -369,6 +379,7 @@ fn function_new_env() -> Result<(), String> { #[universal_test] fn function_new_dynamic() -> Result<(), String> { let mut store = Store::default(); + let mut store = store.as_mut(); // Using &FunctionType signature let function_type = FunctionType::new(vec![], vec![]); @@ -430,6 +441,7 @@ fn function_new_dynamic() -> Result<(), String> { #[universal_test] fn function_new_dynamic_env() -> Result<(), String> { let mut store = Store::default(); + let mut store = store.as_mut(); #[derive(Clone)] struct MyEnv {} let my_env = MyEnv {}; diff --git a/lib/api/tests/function_env.rs b/lib/api/tests/function_env.rs index f6305dc0192..4d7afcd21a2 100644 --- a/lib/api/tests/function_env.rs +++ b/lib/api/tests/function_env.rs @@ -11,6 +11,7 @@ use wasmer::*; )] fn data_and_store_mut() -> Result<(), String> { let mut store = Store::default(); + let mut store = store.as_mut(); let global_mut = Global::new_mut(&mut store, Value::I32(10)); struct Env { value: i32, diff --git a/lib/api/tests/import_function.rs b/lib/api/tests/import_function.rs index fb8edec98bd..1edbc4cc696 100644 --- a/lib/api/tests/import_function.rs +++ b/lib/api/tests/import_function.rs @@ -19,8 +19,9 @@ fn pass_i64_between_host_and_plugin() -> Result<(), String> { (i64.add (call $add_one_i64 (i64.add (local.get 0) (i64.const 1))) (i64.const 1)) ) )"#; - let module = Module::new(&store, wat).map_err(|e| format!("{e:?}"))?; + let module = Module::new(&store.engine(), wat).map_err(|e| format!("{e:?}"))?; + let mut store = store.as_mut(); let imports = { imports! { "host" => { @@ -65,8 +66,9 @@ fn pass_u64_between_host_and_plugin() -> Result<(), String> { (i64.add (call $add_one_u64 (i64.add (local.get 0) (i64.const 1))) (i64.const 1)) ) )"#; - let module = Module::new(&store, wat).map_err(|e| format!("{e:?}"))?; + let module = Module::new(&store.engine(), wat).map_err(|e| format!("{e:?}"))?; + let mut store = store.as_mut(); let imports = { imports! { "host" => { @@ -106,7 +108,8 @@ fn calling_function_exports() -> Result<()> { local.get $rhs i32.add) )"#; - let module = Module::new(&store, wat)?; + let module = Module::new(&store.engine(), wat)?; + let mut store = store.as_mut(); let imports = imports! { // "host" => { // "host_func1" => Function::new_typed(&mut store, |p: u64| { @@ -130,7 +133,7 @@ fn back_and_forth_with_imports() -> Result<()> { let mut store = Store::default(); // We can use the WAT syntax as well! let module = Module::new( - &store, + &store.engine(), br#"(module (func $sum (import "env" "sum") (param i32 i32) (result i32)) (func (export "add_one") (param i32) (result i32) @@ -139,6 +142,8 @@ fn back_and_forth_with_imports() -> Result<()> { )"#, )?; + let mut store = store.as_mut(); + fn sum(a: i32, b: i32) -> i32 { println!("Summing: {a}+{b}"); a + b diff --git a/lib/api/tests/instance.rs b/lib/api/tests/instance.rs index a0c5e48ed3a..576d0df4551 100644 --- a/lib/api/tests/instance.rs +++ b/lib/api/tests/instance.rs @@ -8,7 +8,7 @@ use wasmer::*; fn exports_work_after_multiple_instances_have_been_freed() -> Result<(), String> { let mut store = Store::default(); let module = Module::new( - &store, + &store.engine(), " (module (type $sum_t (func (param i32 i32) (result i32))) @@ -21,6 +21,7 @@ fn exports_work_after_multiple_instances_have_been_freed() -> Result<(), String> ) .map_err(|e| format!("{e:?}"))?; + let mut store = store.as_mut(); let imports = Imports::new(); let instance = Instance::new(&mut store, &module, &imports).map_err(|e| format!("{e:?}"))?; let instance2 = instance.clone(); @@ -55,6 +56,7 @@ fn exports_work_after_multiple_instances_have_been_freed() -> Result<(), String> )] fn unit_native_function_env() -> Result<(), String> { let mut store = Store::default(); + let mut store = store.as_mut(); #[derive(Clone, Debug)] struct Env { diff --git a/lib/api/tests/jspi_async.rs b/lib/api/tests/jspi_async.rs index 408eb3bcaa1..1d4a3d0cf00 100644 --- a/lib/api/tests/jspi_async.rs +++ b/lib/api/tests/jspi_async.rs @@ -1,503 +1,505 @@ -use std::{ - cell::RefCell, - pin::Pin, - sync::OnceLock, - task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, -}; - -use anyhow::Result; -use futures::{FutureExt, future}; -use wasmer::{ - AsStoreMut, Function, FunctionEnv, FunctionEnvMut, FunctionType, Instance, Module, - RuntimeError, Store, StoreMut, Type, TypedFunction, Value, imports, -}; -use wasmer_vm::TrapCode; - -#[derive(Default)] -struct DeltaState { - deltas: Vec, - index: usize, -} - -impl DeltaState { - fn next(&mut self) -> f64 { - let value = self.deltas.get(self.index).copied().unwrap_or(0.0); - self.index += 1; - value - } -} - -fn jspi_module() -> &'static [u8] { - static BYTES: OnceLock> = OnceLock::new(); - const JSPI_WAT: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../tests/examples/jspi.wat"); - BYTES.get_or_init(|| wat::parse_file(JSPI_WAT).expect("valid example module")) -} - -#[test] -fn async_state_updates_follow_jspi_example() -> Result<()> { - let wasm = jspi_module(); - let mut store = Store::default(); - let module = Module::new(&store, wasm)?; - - let init_state = Function::new_async( - &mut store, - FunctionType::new(vec![], vec![Type::F64]), - |_values| async move { - // Note: future::ready doesn't actually suspend. It's important - // to note that, while we're in an async context here, it's - // impossible to suspend during module instantiation, which is - // where this import is called. - // To see this in action, uncomment the following line: - // tokio::task::yield_now().await; - future::ready(()).await; - Ok(vec![Value::F64(1.0)]) - }, - ); - - let delta_env = FunctionEnv::new( - &mut store, - DeltaState { - deltas: vec![0.5, -1.0, 2.5], - index: 0, - }, - ); - let compute_delta = Function::new_with_env_async( - &mut store, - &delta_env, - FunctionType::new(vec![], vec![Type::F64]), - |mut env: FunctionEnvMut, _values| { - let delta = env.data_mut().next(); - async move { - // We can, however, actually suspend whenever - // `Function::call_async` is used to call WASM functions. - tokio::time::sleep(std::time::Duration::from_millis(10)).await; - Ok(vec![Value::F64(delta)]) - } - }, - ); - - let import_object = imports! { - "js" => { - "init_state" => init_state, - "compute_delta" => compute_delta, - } - }; - - let instance = Instance::new(&mut store, &module, &import_object)?; - let get_state = instance.exports.get_function("get_state")?; - let update_state = instance.exports.get_function("update_state")?; - - fn as_f64(values: &[Value]) -> f64 { - match &values[0] { - Value::F64(v) => *v, - other => panic!("expected f64 value, got {other:?}"), - } - } - - assert_eq!(as_f64(&get_state.call(&mut store, &[])?), 1.0); - - let step = |store: &mut Store, func: &wasmer::Function| -> Result { - let result = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap() - .block_on(func.call_async(store, &[]))?; - Ok(as_f64(&result)) - }; - - assert_eq!(step(&mut store, update_state)?, 1.5); - assert_eq!(step(&mut store, update_state)?, 0.5); - assert_eq!(step(&mut store, update_state)?, 3.0); - - Ok(()) -} - -#[test] -fn typed_async_host_and_calls_work() -> Result<()> { - let wasm = wat::parse_str( - r#" - (module - (import "host" "async_add" (func $async_add (param i32 i32) (result i32))) - (import "host" "async_double" (func $async_double (param i32) (result i32))) - (func (export "compute") (param i32) (result i32) - local.get 0 - i32.const 10 - call $async_add - local.get 0 - call $async_double - i32.add)) - "#, - )?; - - #[derive(Clone, Copy)] - struct AddBias { - bias: i32, - } - - let mut store = Store::default(); - let module = Module::new(&store, wasm)?; - - let add_env = FunctionEnv::new(&mut store, AddBias { bias: 5 }); - let async_add = Function::new_typed_with_env_async( - &mut store, - &add_env, - async move |mut env: FunctionEnvMut, a: i32, b: i32| { - let bias = env.data().bias; - future::ready(()).await; - Ok(a + b + bias) - }, - ); - let async_double = Function::new_typed_async(&mut store, async move |value: i32| { - future::ready(()).await; - Ok(value * 2) - }); - - let import_object = imports! { - "host" => { - "async_add" => async_add, - "async_double" => async_double, - } - }; - - let instance = Instance::new(&mut store, &module, &import_object)?; - let compute: TypedFunction = - instance.exports.get_typed_function(&mut store, "compute")?; - - let result = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap() - .block_on(compute.call_async(&mut store, 4))?; - assert_eq!(result, 27); - - Ok(()) -} - -#[test] -fn cannot_yield_when_not_in_async_context() -> Result<()> { - const WAT: &str = r#" - (module - (import "env" "yield_now" (func $yield_now)) - (func (export "yield_outside") - call $yield_now - ) - ) - "#; - let wasm = wat::parse_str(WAT).expect("valid WAT module"); - - let mut store = Store::default(); - let module = Module::new(&store, wasm)?; - - let yield_now = Function::new_async( - &mut store, - FunctionType::new(vec![], vec![]), - |_values| async move { - // Attempting to yield when not in an async context should trap. - tokio::task::yield_now().await; - Ok(vec![]) - }, - ); - - let import_object = imports! { - "env" => { - "yield_now" => yield_now, - } - }; - let instance = Instance::new(&mut store, &module, &import_object)?; - let yield_outside = instance.exports.get_function("yield_outside")?; - - let trap = yield_outside - .call(&mut store, &[]) - .expect_err("expected trap calling yield outside async context"); - - // TODO: wasm trace generation appears to be broken? - // assert!(!trap.trace().is_empty(), "should have a stack trace"); - let trap_code = trap.to_trap().expect("expected trap code"); - assert_eq!( - trap_code, - TrapCode::YieldOutsideAsyncContext, - "expected YieldOutsideAsyncContext trap code" - ); - - Ok(()) -} - -/* This test is slightly weird to explain; what we're testing here - is that multiple coroutines can be active at the same time, - and that they can be polled in any order. - - To achieve this, we have 2 main imports: - * spawn_future spawns new, pending futures, and polls them once. - The futures are set up in a way that they will suspend the first time - they are polled, and complete the second time. However, by polling - once, we will kickstart the corresponding coroutine into action, - which then stays active but suspended. - * resolve_future polls an already spawned future a second time, which - will cause it to be resolved. With this, we are once again activating - the coroutine. - - We also have the future_func export and the poll_future import. future_func - is the "body" of the inner coroutine, while poll_future retrieves the future - constructed by spawn_future and uses it to suspend the coroutine. - - Note that the coroutine will start executing twice, once during spawn_future - (which is a sync imported function) and once during resolve_future - (which is async). This way we ensure that any combination of active coroutines - is possible. - - The proper order of the log numbers for a given future is: - * spawning the future: - 10 + id: spawn_future called initially - 20 + id: the future is constructed, but not polled yet - 30 + id: future_func polled first time - 40 + id: poll_future called, suspending the coroutine - 50 + id: spawn_future finished - * resolving the future: - 60 + id: resolve_future called - 70 + id: future_func resumed second time - 80 + id: resolve_future finished -*/ -#[test] -fn async_multiple_active_coroutines() -> Result<()> { - const WAT: &str = r#" - (module - (import "env" "spawn_future" (func $spawn_future (param i32))) - (import "env" "poll_future" (func $poll_future (param i32))) - (import "env" "resolve_future" (func $resolve_future (param i32))) - (import "env" "yield_now" (func $yield_now)) - (import "env" "log" (func $log (param i32))) - (func (export "main") - (call $spawn_future (i32.const 0)) - (call $spawn_future (i32.const 1)) - (call $yield_now) - (call $spawn_future (i32.const 2)) - (call $resolve_future (i32.const 1)) - (call $yield_now) - (call $spawn_future (i32.const 3)) - (call $resolve_future (i32.const 2)) - (call $yield_now) - (call $resolve_future (i32.const 3)) - (call $resolve_future (i32.const 0)) - ) - (func (export "future_func") (param i32) (result i32) - (call $log (i32.add (i32.const 30) (local.get 0))) - (call $poll_future (local.get 0)) - (call $log (i32.add (i32.const 70) (local.get 0))) - (return (local.get 0)) - ) - ) - "#; - let wasm = wat::parse_str(WAT).expect("valid WAT module"); - - struct Yielder { - yielded: bool, - } - - impl Future for Yielder { - type Output = (); - - fn poll( - mut self: std::pin::Pin<&mut Self>, - _cx: &mut std::task::Context<'_>, - ) -> std::task::Poll { - if self.yielded { - std::task::Poll::Ready(()) - } else { - self.yielded = true; - std::task::Poll::Pending - } - } - } - - struct Env { - log: Vec, - futures: [Option, RuntimeError>>>>>; 4], - yielders: [Option; 4], - future_func: Option, - } - - let mut store = Store::default(); - let module = Module::new(&store, wasm)?; - - thread_local! { - static ENV: RefCell = RefCell::new(Env { - log: Vec::new(), - futures: [None, None, None, None], - yielders: [None, None, None, None], - future_func: None, - }) - } - let mut env = FunctionEnv::new(&mut store, ()); - - fn log(value: i32) { - ENV.with(|env| { - env.borrow_mut().log.push(value); - }); - } - - let spawn_future = Function::new_with_env( - &mut store, - &mut env, - FunctionType::new(vec![Type::I32], vec![]), - |mut env, values| { - ENV.with(|data| { - let ((), mut store) = env.data_and_store_mut(); - - let future_id = values[0].unwrap_i32(); - - log(future_id + 10); - - data.borrow_mut().yielders[future_id as usize] = Some(Yielder { yielded: false }); - - // This spawns the coroutine and the corresponding future - let store_raw = store.as_store_mut().as_raw(); - let func = data.borrow().future_func.as_ref().unwrap().clone(); - let mut future = Box::pin(async move { - let mut store = unsafe { StoreMut::<'static>::from_raw(store_raw) }; - let result = func.call_async(&mut store, &[Value::I32(future_id)]).await; - result - }); - log(future_id + 20); - - // We then poll it once to get it started - it'll suspend once, then - // complete the next time we poll it - let w = noop_waker(); - let mut cx = Context::from_waker(&w); - assert!(future.as_mut().poll(&mut cx).is_pending()); - - log(future_id + 50); - // We then store the future without letting it complete, and return - data.borrow_mut().futures[future_id as usize] = Some(future); - - Ok(vec![]) - }) - }, - ); - - let poll_future = Function::new_async( - &mut store, - FunctionType::new(vec![Type::I32], vec![]), - |values| { - let future_id = values[0].unwrap_i32(); - let yielder = ENV.with(|data| { - log(future_id + 40); - - let mut borrow = data.borrow_mut(); - let yielder = unsafe { - (borrow.yielders[future_id as usize].as_mut().unwrap() as *mut Yielder) - .as_mut::<'static>() - .unwrap() - }; - yielder - }); - yielder.map(|()| Ok(vec![])) - }, - ); - - let resolve_future = Function::new_async( - &mut store, - FunctionType::new(vec![Type::I32], vec![]), - |values| { - let future_id = values[0].unwrap_i32(); - - async move { - ENV.with(|data| { - log(future_id + 60); - - let mut future = data.borrow_mut().futures[future_id as usize] - .take() - .unwrap(); - - let w = noop_waker(); - let mut cx = Context::from_waker(&w); - let Poll::Ready(result) = future.as_mut().poll(&mut cx) else { - panic!("expected future to be ready"); - }; - let result_id = result.unwrap()[0].unwrap_i32(); - assert_eq!(result_id, future_id); - - log(future_id + 80); - - Ok(vec![]) - }) - } - }, - ); - - let yield_now = Function::new_async( - &mut store, - FunctionType::new(vec![], vec![]), - |_values| async move { - tokio::task::yield_now().await; - Ok(vec![]) - }, - ); - - let log = Function::new( - &mut store, - FunctionType::new(vec![Type::I32], vec![]), - |values| { - let value = values[0].unwrap_i32(); - log(value); - Ok(vec![]) - }, - ); - - let import_object = imports! { - "env" => { - "spawn_future" => spawn_future, - "poll_future" => poll_future, - "resolve_future" => resolve_future, - "yield_now" => yield_now, - "log" => log, - } - }; - let instance = Instance::new(&mut store, &module, &import_object)?; - - ENV.with(|env| { - env.borrow_mut().future_func = Some( - instance - .exports - .get_function("future_func") - .unwrap() - .clone(), - ) - }); - - let main = instance.exports.get_function("main")?; - tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap() - .block_on(main.call_async(&mut store, &[]))?; - - ENV.with(|env| { - assert_eq!( - env.borrow().log, - vec![ - 10, 20, 30, 40, 50, // future 0 spawned - 11, 21, 31, 41, 51, // future 1 spawned - 12, 22, 32, 42, 52, // future 2 spawned - 61, 71, 81, // future 1 resolved - 13, 23, 33, 43, 53, // future 3 spawned - 62, 72, 82, // future 2 resolved - 63, 73, 83, // future 3 resolved - 60, 70, 80, // future 0 resolved - ] - ); - }); - - Ok(()) -} - -fn noop_waker() -> Waker { - fn noop_raw_waker() -> RawWaker { - fn no_op(_: *const ()) {} - fn clone(_: *const ()) -> RawWaker { - noop_raw_waker() - } - let vtable = &RawWakerVTable::new(clone, no_op, no_op, no_op); - RawWaker::new(std::ptr::null(), vtable) - } - unsafe { Waker::from_raw(noop_raw_waker()) } -} +// TODO: async api + +// use std::{ +// cell::RefCell, +// pin::Pin, +// sync::OnceLock, +// task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, +// }; + +// use anyhow::Result; +// use futures::{FutureExt, future}; +// use wasmer::{ +// AsStoreMut, Function, FunctionEnv, FunctionEnvMut, FunctionType, Instance, Module, +// RuntimeError, Store, StoreMut, Type, TypedFunction, Value, imports, +// }; +// use wasmer_vm::TrapCode; + +// #[derive(Default)] +// struct DeltaState { +// deltas: Vec, +// index: usize, +// } + +// impl DeltaState { +// fn next(&mut self) -> f64 { +// let value = self.deltas.get(self.index).copied().unwrap_or(0.0); +// self.index += 1; +// value +// } +// } + +// fn jspi_module() -> &'static [u8] { +// static BYTES: OnceLock> = OnceLock::new(); +// const JSPI_WAT: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../tests/examples/jspi.wat"); +// BYTES.get_or_init(|| wat::parse_file(JSPI_WAT).expect("valid example module")) +// } + +// #[test] +// fn async_state_updates_follow_jspi_example() -> Result<()> { +// let wasm = jspi_module(); +// let mut store = Store::default(); +// let module = Module::new(&store, wasm)?; + +// let init_state = Function::new_async( +// &mut store, +// FunctionType::new(vec![], vec![Type::F64]), +// |_values| async move { +// // Note: future::ready doesn't actually suspend. It's important +// // to note that, while we're in an async context here, it's +// // impossible to suspend during module instantiation, which is +// // where this import is called. +// // To see this in action, uncomment the following line: +// // tokio::task::yield_now().await; +// future::ready(()).await; +// Ok(vec![Value::F64(1.0)]) +// }, +// ); + +// let delta_env = FunctionEnv::new( +// &mut store, +// DeltaState { +// deltas: vec![0.5, -1.0, 2.5], +// index: 0, +// }, +// ); +// let compute_delta = Function::new_with_env_async( +// &mut store, +// &delta_env, +// FunctionType::new(vec![], vec![Type::F64]), +// |mut env: FunctionEnvMut, _values| { +// let delta = env.data_mut().next(); +// async move { +// // We can, however, actually suspend whenever +// // `Function::call_async` is used to call WASM functions. +// tokio::time::sleep(std::time::Duration::from_millis(10)).await; +// Ok(vec![Value::F64(delta)]) +// } +// }, +// ); + +// let import_object = imports! { +// "js" => { +// "init_state" => init_state, +// "compute_delta" => compute_delta, +// } +// }; + +// let instance = Instance::new(&mut store, &module, &import_object)?; +// let get_state = instance.exports.get_function("get_state")?; +// let update_state = instance.exports.get_function("update_state")?; + +// fn as_f64(values: &[Value]) -> f64 { +// match &values[0] { +// Value::F64(v) => *v, +// other => panic!("expected f64 value, got {other:?}"), +// } +// } + +// assert_eq!(as_f64(&get_state.call(&mut store, &[])?), 1.0); + +// let step = |store: &mut Store, func: &wasmer::Function| -> Result { +// let result = tokio::runtime::Builder::new_current_thread() +// .enable_all() +// .build() +// .unwrap() +// .block_on(func.call_async(store, &[]))?; +// Ok(as_f64(&result)) +// }; + +// assert_eq!(step(&mut store, update_state)?, 1.5); +// assert_eq!(step(&mut store, update_state)?, 0.5); +// assert_eq!(step(&mut store, update_state)?, 3.0); + +// Ok(()) +// } + +// #[test] +// fn typed_async_host_and_calls_work() -> Result<()> { +// let wasm = wat::parse_str( +// r#" +// (module +// (import "host" "async_add" (func $async_add (param i32 i32) (result i32))) +// (import "host" "async_double" (func $async_double (param i32) (result i32))) +// (func (export "compute") (param i32) (result i32) +// local.get 0 +// i32.const 10 +// call $async_add +// local.get 0 +// call $async_double +// i32.add)) +// "#, +// )?; + +// #[derive(Clone, Copy)] +// struct AddBias { +// bias: i32, +// } + +// let mut store = Store::default(); +// let module = Module::new(&store, wasm)?; + +// let add_env = FunctionEnv::new(&mut store, AddBias { bias: 5 }); +// let async_add = Function::new_typed_with_env_async( +// &mut store, +// &add_env, +// async move |mut env: FunctionEnvMut, a: i32, b: i32| { +// let bias = env.data().bias; +// future::ready(()).await; +// Ok(a + b + bias) +// }, +// ); +// let async_double = Function::new_typed_async(&mut store, async move |value: i32| { +// future::ready(()).await; +// Ok(value * 2) +// }); + +// let import_object = imports! { +// "host" => { +// "async_add" => async_add, +// "async_double" => async_double, +// } +// }; + +// let instance = Instance::new(&mut store, &module, &import_object)?; +// let compute: TypedFunction = +// instance.exports.get_typed_function(&mut store, "compute")?; + +// let result = tokio::runtime::Builder::new_current_thread() +// .enable_all() +// .build() +// .unwrap() +// .block_on(compute.call_async(&mut store, 4))?; +// assert_eq!(result, 27); + +// Ok(()) +// } + +// #[test] +// fn cannot_yield_when_not_in_async_context() -> Result<()> { +// const WAT: &str = r#" +// (module +// (import "env" "yield_now" (func $yield_now)) +// (func (export "yield_outside") +// call $yield_now +// ) +// ) +// "#; +// let wasm = wat::parse_str(WAT).expect("valid WAT module"); + +// let mut store = Store::default(); +// let module = Module::new(&store, wasm)?; + +// let yield_now = Function::new_async( +// &mut store, +// FunctionType::new(vec![], vec![]), +// |_values| async move { +// // Attempting to yield when not in an async context should trap. +// tokio::task::yield_now().await; +// Ok(vec![]) +// }, +// ); + +// let import_object = imports! { +// "env" => { +// "yield_now" => yield_now, +// } +// }; +// let instance = Instance::new(&mut store, &module, &import_object)?; +// let yield_outside = instance.exports.get_function("yield_outside")?; + +// let trap = yield_outside +// .call(&mut store, &[]) +// .expect_err("expected trap calling yield outside async context"); + +// // TODO: wasm trace generation appears to be broken? +// // assert!(!trap.trace().is_empty(), "should have a stack trace"); +// let trap_code = trap.to_trap().expect("expected trap code"); +// assert_eq!( +// trap_code, +// TrapCode::YieldOutsideAsyncContext, +// "expected YieldOutsideAsyncContext trap code" +// ); + +// Ok(()) +// } + +// /* This test is slightly weird to explain; what we're testing here +// is that multiple coroutines can be active at the same time, +// and that they can be polled in any order. + +// To achieve this, we have 2 main imports: +// * spawn_future spawns new, pending futures, and polls them once. +// The futures are set up in a way that they will suspend the first time +// they are polled, and complete the second time. However, by polling +// once, we will kickstart the corresponding coroutine into action, +// which then stays active but suspended. +// * resolve_future polls an already spawned future a second time, which +// will cause it to be resolved. With this, we are once again activating +// the coroutine. + +// We also have the future_func export and the poll_future import. future_func +// is the "body" of the inner coroutine, while poll_future retrieves the future +// constructed by spawn_future and uses it to suspend the coroutine. + +// Note that the coroutine will start executing twice, once during spawn_future +// (which is a sync imported function) and once during resolve_future +// (which is async). This way we ensure that any combination of active coroutines +// is possible. + +// The proper order of the log numbers for a given future is: +// * spawning the future: +// 10 + id: spawn_future called initially +// 20 + id: the future is constructed, but not polled yet +// 30 + id: future_func polled first time +// 40 + id: poll_future called, suspending the coroutine +// 50 + id: spawn_future finished +// * resolving the future: +// 60 + id: resolve_future called +// 70 + id: future_func resumed second time +// 80 + id: resolve_future finished +// */ +// #[test] +// fn async_multiple_active_coroutines() -> Result<()> { +// const WAT: &str = r#" +// (module +// (import "env" "spawn_future" (func $spawn_future (param i32))) +// (import "env" "poll_future" (func $poll_future (param i32))) +// (import "env" "resolve_future" (func $resolve_future (param i32))) +// (import "env" "yield_now" (func $yield_now)) +// (import "env" "log" (func $log (param i32))) +// (func (export "main") +// (call $spawn_future (i32.const 0)) +// (call $spawn_future (i32.const 1)) +// (call $yield_now) +// (call $spawn_future (i32.const 2)) +// (call $resolve_future (i32.const 1)) +// (call $yield_now) +// (call $spawn_future (i32.const 3)) +// (call $resolve_future (i32.const 2)) +// (call $yield_now) +// (call $resolve_future (i32.const 3)) +// (call $resolve_future (i32.const 0)) +// ) +// (func (export "future_func") (param i32) (result i32) +// (call $log (i32.add (i32.const 30) (local.get 0))) +// (call $poll_future (local.get 0)) +// (call $log (i32.add (i32.const 70) (local.get 0))) +// (return (local.get 0)) +// ) +// ) +// "#; +// let wasm = wat::parse_str(WAT).expect("valid WAT module"); + +// struct Yielder { +// yielded: bool, +// } + +// impl Future for Yielder { +// type Output = (); + +// fn poll( +// mut self: std::pin::Pin<&mut Self>, +// _cx: &mut std::task::Context<'_>, +// ) -> std::task::Poll { +// if self.yielded { +// std::task::Poll::Ready(()) +// } else { +// self.yielded = true; +// std::task::Poll::Pending +// } +// } +// } + +// struct Env { +// log: Vec, +// futures: [Option, RuntimeError>>>>>; 4], +// yielders: [Option; 4], +// future_func: Option, +// } + +// let mut store = Store::default(); +// let module = Module::new(&store, wasm)?; + +// thread_local! { +// static ENV: RefCell = RefCell::new(Env { +// log: Vec::new(), +// futures: [None, None, None, None], +// yielders: [None, None, None, None], +// future_func: None, +// }) +// } +// let mut env = FunctionEnv::new(&mut store, ()); + +// fn log(value: i32) { +// ENV.with(|env| { +// env.borrow_mut().log.push(value); +// }); +// } + +// let spawn_future = Function::new_with_env( +// &mut store, +// &mut env, +// FunctionType::new(vec![Type::I32], vec![]), +// |mut env, values| { +// ENV.with(|data| { +// let ((), mut store) = env.data_and_store_mut(); + +// let future_id = values[0].unwrap_i32(); + +// log(future_id + 10); + +// data.borrow_mut().yielders[future_id as usize] = Some(Yielder { yielded: false }); + +// // This spawns the coroutine and the corresponding future +// let store_raw = store.as_store_mut().as_raw(); +// let func = data.borrow().future_func.as_ref().unwrap().clone(); +// let mut future = Box::pin(async move { +// let mut store = unsafe { StoreMut::<'static>::from_raw(store_raw) }; +// let result = func.call_async(&mut store, &[Value::I32(future_id)]).await; +// result +// }); +// log(future_id + 20); + +// // We then poll it once to get it started - it'll suspend once, then +// // complete the next time we poll it +// let w = noop_waker(); +// let mut cx = Context::from_waker(&w); +// assert!(future.as_mut().poll(&mut cx).is_pending()); + +// log(future_id + 50); +// // We then store the future without letting it complete, and return +// data.borrow_mut().futures[future_id as usize] = Some(future); + +// Ok(vec![]) +// }) +// }, +// ); + +// let poll_future = Function::new_async( +// &mut store, +// FunctionType::new(vec![Type::I32], vec![]), +// |values| { +// let future_id = values[0].unwrap_i32(); +// let yielder = ENV.with(|data| { +// log(future_id + 40); + +// let mut borrow = data.borrow_mut(); +// let yielder = unsafe { +// (borrow.yielders[future_id as usize].as_mut().unwrap() as *mut Yielder) +// .as_mut::<'static>() +// .unwrap() +// }; +// yielder +// }); +// yielder.map(|()| Ok(vec![])) +// }, +// ); + +// let resolve_future = Function::new_async( +// &mut store, +// FunctionType::new(vec![Type::I32], vec![]), +// |values| { +// let future_id = values[0].unwrap_i32(); + +// async move { +// ENV.with(|data| { +// log(future_id + 60); + +// let mut future = data.borrow_mut().futures[future_id as usize] +// .take() +// .unwrap(); + +// let w = noop_waker(); +// let mut cx = Context::from_waker(&w); +// let Poll::Ready(result) = future.as_mut().poll(&mut cx) else { +// panic!("expected future to be ready"); +// }; +// let result_id = result.unwrap()[0].unwrap_i32(); +// assert_eq!(result_id, future_id); + +// log(future_id + 80); + +// Ok(vec![]) +// }) +// } +// }, +// ); + +// let yield_now = Function::new_async( +// &mut store, +// FunctionType::new(vec![], vec![]), +// |_values| async move { +// tokio::task::yield_now().await; +// Ok(vec![]) +// }, +// ); + +// let log = Function::new( +// &mut store, +// FunctionType::new(vec![Type::I32], vec![]), +// |values| { +// let value = values[0].unwrap_i32(); +// log(value); +// Ok(vec![]) +// }, +// ); + +// let import_object = imports! { +// "env" => { +// "spawn_future" => spawn_future, +// "poll_future" => poll_future, +// "resolve_future" => resolve_future, +// "yield_now" => yield_now, +// "log" => log, +// } +// }; +// let instance = Instance::new(&mut store, &module, &import_object)?; + +// ENV.with(|env| { +// env.borrow_mut().future_func = Some( +// instance +// .exports +// .get_function("future_func") +// .unwrap() +// .clone(), +// ) +// }); + +// let main = instance.exports.get_function("main")?; +// tokio::runtime::Builder::new_current_thread() +// .enable_all() +// .build() +// .unwrap() +// .block_on(main.call_async(&mut store, &[]))?; + +// ENV.with(|env| { +// assert_eq!( +// env.borrow().log, +// vec![ +// 10, 20, 30, 40, 50, // future 0 spawned +// 11, 21, 31, 41, 51, // future 1 spawned +// 12, 22, 32, 42, 52, // future 2 spawned +// 61, 71, 81, // future 1 resolved +// 13, 23, 33, 43, 53, // future 3 spawned +// 62, 72, 82, // future 2 resolved +// 63, 73, 83, // future 3 resolved +// 60, 70, 80, // future 0 resolved +// ] +// ); +// }); + +// Ok(()) +// } + +// fn noop_waker() -> Waker { +// fn noop_raw_waker() -> RawWaker { +// fn no_op(_: *const ()) {} +// fn clone(_: *const ()) -> RawWaker { +// noop_raw_waker() +// } +// let vtable = &RawWakerVTable::new(clone, no_op, no_op, no_op); +// RawWaker::new(std::ptr::null(), vtable) +// } +// unsafe { Waker::from_raw(noop_raw_waker()) } +// } diff --git a/lib/api/tests/memory.rs b/lib/api/tests/memory.rs index 4119e75d3ee..2a0a60dde9c 100644 --- a/lib/api/tests/memory.rs +++ b/lib/api/tests/memory.rs @@ -16,10 +16,11 @@ fn test_shared_memory_atomics_notify_send() { let wat = r#"(module (import "host" "memory" (memory 10 65536 shared)) )"#; - let module = Module::new(&store, wat) + let module = Module::new(&store.engine(), wat) .map_err(|e| format!("{e:?}")) .unwrap(); + let mut store = store.as_mut(); let mem = Memory::new(&mut store, MemoryType::new(10, Some(65536), true)).unwrap(); let imports = imports! { @@ -75,6 +76,7 @@ fn test_shared_memory_disable_atomics() { use wasmer::AtomicsError; let mut store = Store::default(); + let mut store = store.as_mut(); let mem = Memory::new(&mut store, MemoryType::new(10, Some(65536), true)).unwrap(); let mem = mem.as_shared(&store).unwrap(); @@ -92,10 +94,11 @@ fn test_wasm_slice_issue_5444() { let wat = r#"(module (import "host" "memory" (memory 10 65536)) )"#; - let module = Module::new(&store, wat) + let module = Module::new(&store.engine(), wat) .map_err(|e| format!("{e:?}")) .unwrap(); + let mut store = store.as_mut(); let mem = Memory::new(&mut store, MemoryType::new(10, Some(65536), false)).unwrap(); let imports = imports! { @@ -123,6 +126,7 @@ fn test_wasm_slice_issue_5444() { #[test] fn test_wasm_memory_size() { let mut store = Store::default(); + let mut store = store.as_mut(); // Test once with not-shared memory... { diff --git a/lib/api/tests/module.rs b/lib/api/tests/module.rs index 83296c136e4..a0587574c8a 100644 --- a/lib/api/tests/module.rs +++ b/lib/api/tests/module.rs @@ -8,7 +8,7 @@ use wasmer::*; fn module_get_name() -> Result<(), String> { let store = Store::default(); let wat = r#"(module)"#; - let module = Module::new(&store, wat).map_err(|e| format!("{e:?}"))?; + let module = Module::new(&store.engine(), wat).map_err(|e| format!("{e:?}"))?; assert_eq!(module.name(), None); Ok(()) @@ -18,7 +18,7 @@ fn module_get_name() -> Result<(), String> { fn module_set_name() -> Result<(), String> { let store = Store::default(); let wat = r#"(module $name)"#; - let mut module = Module::new(&store, wat).map_err(|e| format!("{e:?}"))?; + let mut module = Module::new(&store.engine(), wat).map_err(|e| format!("{e:?}"))?; assert_eq!(module.name(), Some("name")); module.set_name("new_name"); @@ -36,7 +36,7 @@ fn imports() -> Result<(), String> { (import "host" "table" (table 1 funcref)) (import "host" "global" (global i32)) )"#; - let module = Module::new(&store, wat).map_err(|e| format!("{e:?}"))?; + let module = Module::new(&store.engine(), wat).map_err(|e| format!("{e:?}"))?; assert_eq!( module.imports().collect::>(), vec![ @@ -108,7 +108,7 @@ fn exports() -> Result<(), String> { (table (export "table") 1 funcref) (global (export "global") i32 (i32.const 0)) )"#; - let module = Module::new(&store, wat).map_err(|e| format!("{e:?}"))?; + let module = Module::new(&store.engine(), wat).map_err(|e| format!("{e:?}"))?; assert_eq!( module.exports().collect::>(), vec![ @@ -190,7 +190,8 @@ fn calling_host_functions_with_negative_values_works() -> Result<(), String> { (func (export "call_host_func8") (call 7 (i32.const -1))) )"#; - let module = Module::new(&store, wat).map_err(|e| format!("{e:?}"))?; + let module = Module::new(&store.engine(), wat).map_err(|e| format!("{e:?}"))?; + let mut store = store.as_mut(); let imports = imports! { "host" => { "host_func1" => Function::new_typed(&mut store, |p: u64| { @@ -281,7 +282,8 @@ fn calling_host_functions_with_negative_values_works() -> Result<(), String> { fn module_custom_sections() -> Result<(), String> { let store = Store::default(); let custom_section_wasm_bytes = include_bytes!("simple-name-section.wasm"); - let module = Module::new(&store, custom_section_wasm_bytes).map_err(|e| format!("{e:?}"))?; + let module = + Module::new(&store.engine(), custom_section_wasm_bytes).map_err(|e| format!("{e:?}"))?; let sections = module.custom_sections("name"); let sections_vec: Vec> = sections.collect(); assert_eq!(sections_vec.len(), 1); diff --git a/lib/api/tests/reference_types.rs b/lib/api/tests/reference_types.rs index 3350873bfe4..b42d37ad15e 100644 --- a/lib/api/tests/reference_types.rs +++ b/lib/api/tests/reference_types.rs @@ -23,7 +23,9 @@ pub mod reference_types { (table.set $table (i32.const 0) (local.get $fr)) (call_indirect $table (type $ret_i32_ty) (i32.const 0))) )"#; - let module = Module::new(&store, wat)?; + let module = Module::new(&store.engine(), wat)?; + + let mut store = store.as_mut(); #[derive(Clone, Debug)] pub struct Env(Arc); let env = Env(Arc::new(AtomicBool::new(false))); @@ -54,7 +56,7 @@ pub mod reference_types { let call_set_value: &Function = instance.exports.get_function("call_set_value")?; let results: Box<[Value]> = call_set_value.call(&mut store, &[Value::FuncRef(Some(func_to_call))])?; - assert!(env.as_ref(&store.as_store_ref()).0.load(Ordering::SeqCst)); + assert!(env.as_ref(&store).0.load(Ordering::SeqCst)); assert_eq!(&*results, &[Value::I32(343)]); Ok(()) @@ -80,7 +82,9 @@ pub mod reference_types { (func (export "call_host_func_with_wasm_func") (result i32) (call $func_ref_call (ref.func $product))) )"#; - let module = Module::new(&store, wat)?; + let module = Module::new(&store.engine(), wat)?; + + let mut store = store.as_mut(); let env = FunctionEnv::new(&mut store, ()); fn func_ref_call( mut env: FunctionEnvMut<()>, @@ -149,7 +153,9 @@ pub mod reference_types { (func (export "get_hashmap_native") (param) (result externref) (call $get_new_extern_ref_native)) )"#; - let module = Module::new(&store, wat)?; + let module = Module::new(&store.engine(), wat)?; + + let mut store = store.as_mut(); let env = FunctionEnv::new(&mut store, ()); let imports = imports! { "env" => { @@ -228,7 +234,9 @@ pub mod reference_types { (func (export "drop") (param $er externref) (result) (drop (local.get $er))) )"#; - let module = Module::new(&store, wat)?; + let module = Module::new(&store.engine(), wat)?; + + let mut store = store.as_mut(); let instance = Instance::new(&mut store, &module, &imports! {})?; let f: TypedFunction, ()> = instance.exports.get_typed_function(&store, "drop")?; @@ -252,7 +260,9 @@ pub mod reference_types { (func $hello (param) (result i32) (i32.const 73)) )"#; - let module = Module::new(&store, wat)?; + let module = Module::new(&store.engine(), wat)?; + + let mut store = store.as_mut(); let instance = Instance::new(&mut store, &module, &imports! {})?; { let er_global: &Global = instance.exports.get_global("er_global")?; @@ -321,7 +331,9 @@ pub mod reference_types { (call $intermediate (local.get $er) (local.get $idx)) (local.get $er)) )"#; - let module = Module::new(&store, wat)?; + let module = Module::new(&store.engine(), wat)?; + + let mut store = store.as_mut(); let instance = Instance::new(&mut store, &module, &imports! {})?; let f: TypedFunction<(Option, i32), Option> = instance @@ -358,7 +370,9 @@ pub mod reference_types { (drop (global.get $global)) (global.get $global)) )"#; - let module = Module::new(&store, wat)?; + let module = Module::new(&store.engine(), wat)?; + + let mut store = store.as_mut(); let instance = Instance::new(&mut store, &module, &imports! {})?; let global: &Global = instance.exports.get_global("global")?; @@ -386,7 +400,9 @@ pub mod reference_types { (local.get 0) (unreachable)) )"#; - let module = Module::new(&store, wat)?; + let module = Module::new(&store.engine(), wat)?; + + let mut store = store.as_mut(); let instance = Instance::new(&mut store, &module, &imports! {})?; let pass_extern_ref: TypedFunction, ()> = instance @@ -414,7 +430,9 @@ pub mod reference_types { (func $copy_into_table2 (export "copy_into_table2") (table.copy $table2 $table1 (i32.const 0) (i32.const 0) (i32.const 4))) )"#; - let module = Module::new(&store, wat)?; + let module = Module::new(&store.engine(), wat)?; + + let mut store = store.as_mut(); let instance = Instance::new(&mut store, &module, &imports! {})?; let grow_table_with_ref: TypedFunction<(Option, i32), i32> = instance @@ -503,7 +521,9 @@ pub mod reference_types { (func (export "call_set_value") (param $er externref) (param $idx i32) (table.set $table2 (local.get $idx) (local.get $er))) )"#; - let module = Module::new(&store, wat)?; + let module = Module::new(&store.engine(), wat)?; + + let mut store = store.as_mut(); let instance = Instance::new(&mut store, &module, &imports! {})?; let grow_table_with_ref: TypedFunction<(Option, i32), i32> = instance @@ -593,7 +613,9 @@ pub mod reference_types { (table.get $table (local.get $idx))) )"#; - let module = Module::new(&store, wat)?; + let module = Module::new(&store.engine(), wat)?; + + let mut store = store.as_mut(); let instance = Instance::new(&mut store, &module, &imports! {})?; let call_set_value: TypedFunction<(Option, i32), ()> = instance .exports diff --git a/lib/api/tests/typed_functions.rs b/lib/api/tests/typed_functions.rs index d746a1a95f2..42fd8c25827 100644 --- a/lib/api/tests/typed_functions.rs +++ b/lib/api/tests/typed_functions.rs @@ -11,6 +11,7 @@ use wasm_bindgen_test::wasm_bindgen_test; )] fn typed_host_function_closure_panics() -> Result<(), String> { let mut store = Store::default(); + let mut store = store.as_mut(); let state = 3; Function::new_typed(&mut store, move |_: i32| { @@ -27,6 +28,7 @@ fn typed_host_function_closure_panics() -> Result<(), String> { )] fn typed_with_env_host_function_closure_panics() -> Result<(), String> { let mut store = Store::default(); + let mut store = store.as_mut(); let env: i32 = 4; let env = FunctionEnv::new(&mut store, env); let state = 3; @@ -65,7 +67,8 @@ fn non_typed_functions_and_closures_with_no_env_work() -> anyhow::Result<()> { (local.get 3)) (local.get 4))) )"#; - let module = Module::new(&store, wat).unwrap(); + let module = Module::new(&store.engine(), wat).unwrap(); + let mut store = store.as_mut(); let env: i32 = 10; let env = FunctionEnv::new(&mut store, env); let ty = FunctionType::new(vec![Type::I32, Type::I32], vec![Type::I32]); @@ -134,8 +137,10 @@ fn holochain_typed_function() -> anyhow::Result<()> { )?; let mut store = Store::default(); struct MyEnv {} + let module = Module::new(&store.engine(), wasm_bytes)?; + + let mut store = store.as_mut(); let env = FunctionEnv::new(&mut store, MyEnv {}); - let module = Module::new(&store, wasm_bytes)?; // Define some context data that the host function closure will use static STATIC_CONTEXT_VAL2: i32 = 1234; diff --git a/lib/vm/src/trap/traphandlers.rs b/lib/vm/src/trap/traphandlers.rs index 6c4b8c540c6..8840028184b 100644 --- a/lib/vm/src/trap/traphandlers.rs +++ b/lib/vm/src/trap/traphandlers.rs @@ -28,6 +28,7 @@ use wasmer_types::TrapCode; /// Configuration for the runtime VM /// Currently only the stack size is configurable +#[derive(Clone)] pub struct VMConfig { /// Optional stack size (in byte) of the VM. Value lower than 8K will be rounded to 8K. pub wasm_stack_size: Option, From 021ae2d7fe9e48c466e40d82ce7f9550a0b7ad16 Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Tue, 18 Nov 2025 17:45:46 +0000 Subject: [PATCH 14/49] WIP: fix doctests --- .../src/backend/sys/entities/memory/view.rs | 1 + lib/api/src/entities/exports.rs | 6 +++-- lib/api/src/entities/function/inner.rs | 26 +++++++++++++------ lib/api/src/entities/function/mod.rs | 26 +++++++++++++------ lib/api/src/entities/global/inner.rs | 7 +++++ lib/api/src/entities/global/mod.rs | 7 +++++ lib/api/src/entities/imports.rs | 10 ++++--- lib/api/src/entities/instance.rs | 3 ++- lib/api/src/entities/memory/inner.rs | 4 +++ lib/api/src/entities/memory/mod.rs | 4 +++ lib/api/src/entities/memory/view/inner.rs | 1 + lib/api/src/entities/memory/view/mod.rs | 1 + lib/api/src/entities/module/inner.rs | 14 +++++----- lib/api/src/entities/module/mod.rs | 18 +++++++------ lib/api/src/entities/store/context.rs | 1 - 15 files changed, 91 insertions(+), 38 deletions(-) diff --git a/lib/api/src/backend/sys/entities/memory/view.rs b/lib/api/src/backend/sys/entities/memory/view.rs index 0136ae6f604..437e49bd1fb 100644 --- a/lib/api/src/backend/sys/entities/memory/view.rs +++ b/lib/api/src/backend/sys/entities/memory/view.rs @@ -86,6 +86,7 @@ impl<'a> MemoryView<'a> { /// ``` /// # use wasmer::{Memory, MemoryType, Pages, Store, Type, Value}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # /// let m = Memory::new(&mut store, MemoryType::new(1, None, false)).unwrap(); /// diff --git a/lib/api/src/entities/exports.rs b/lib/api/src/entities/exports.rs index 5bda72891dd..d20a946f8bb 100644 --- a/lib/api/src/entities/exports.rs +++ b/lib/api/src/entities/exports.rs @@ -21,7 +21,8 @@ use thiserror::Error; /// # (module /// # (global $one (export "glob") f32 (f32.const 1))) /// # "#.as_bytes()).unwrap(); -/// # let module = Module::new(&store, wasm_bytes).unwrap(); +/// # let module = Module::new(&store.engine(), wasm_bytes).unwrap(); +/// # let mut store = store.as_mut(); /// # let import_object = imports! {}; /// # let instance = Instance::new(&mut store, &module, &import_object).unwrap(); /// # @@ -35,7 +36,8 @@ use thiserror::Error; /// # use wasmer::{imports, wat2wasm, Function, Instance, Module, Store, Type, Value, ExportError}; /// # let mut store = Store::default(); /// # let wasm_bytes = wat2wasm("(module)".as_bytes()).unwrap(); -/// # let module = Module::new(&store, wasm_bytes).unwrap(); +/// # let module = Module::new(&store.engine(), wasm_bytes).unwrap(); +/// # let mut store = store.as_mut(); /// # let import_object = imports! {}; /// # let instance = Instance::new(&mut store, &module, &import_object).unwrap(); /// # diff --git a/lib/api/src/entities/function/inner.rs b/lib/api/src/entities/function/inner.rs index 0785c39b171..7bdf4c60768 100644 --- a/lib/api/src/entities/function/inner.rs +++ b/lib/api/src/entities/function/inner.rs @@ -64,6 +64,7 @@ impl BackendFunction { /// ``` /// # use wasmer::{Function, FunctionEnv, FunctionType, Type, Store, Value}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # let env = FunctionEnv::new(&mut store, ()); /// # /// let signature = FunctionType::new(vec![Type::I32, Type::I32], vec![Type::I32]); @@ -79,6 +80,7 @@ impl BackendFunction { /// ``` /// # use wasmer::{Function, FunctionEnv, FunctionType, Type, Store, Value}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # let env = FunctionEnv::new(&mut store, ()); /// # /// const I32_I32_TO_I32: ([Type; 2], [Type; 1]) = ([Type::I32, Type::I32], [Type::I32]); @@ -190,6 +192,7 @@ impl BackendFunction { /// ``` /// # use wasmer::{Store, Function, FunctionEnv, FunctionEnvMut}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # let env = FunctionEnv::new(&mut store, ()); /// # /// fn sum(_env: FunctionEnvMut<()>, a: i32, b: i32) -> i32 { @@ -371,6 +374,7 @@ impl BackendFunction { /// ``` /// # use wasmer::{Function, FunctionEnv, FunctionEnvMut, Store, Type}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # let env = FunctionEnv::new(&mut store, ()); /// # /// fn sum(_env: FunctionEnvMut<()>, a: i32, b: i32) -> i32 { @@ -396,6 +400,7 @@ impl BackendFunction { /// ``` /// # use wasmer::{Function, FunctionEnv, FunctionEnvMut, Store, Type}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # let env = FunctionEnv::new(&mut store, ()); /// # /// fn sum(_env: FunctionEnvMut<()>, a: i32, b: i32) -> i32 { @@ -418,6 +423,7 @@ impl BackendFunction { /// ``` /// # use wasmer::{Function, FunctionEnv, FunctionEnvMut, Store, Type}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # let env = FunctionEnv::new(&mut store, ()); /// # /// fn sum(_env: FunctionEnvMut<()>, a: i32, b: i32) -> i32 { @@ -447,7 +453,6 @@ impl BackendFunction { /// # use wasmer::{imports, wat2wasm, Function, Instance, Module, Store, Type, Value}; /// # use wasmer::FunctionEnv; /// # let mut store = Store::default(); - /// # let env = FunctionEnv::new(&mut store, ()); /// # let wasm_bytes = wat2wasm(r#" /// # (module /// # (func (export "sum") (param $x i32) (param $y i32) (result i32) @@ -456,7 +461,9 @@ impl BackendFunction { /// # i32.add /// # )) /// # "#.as_bytes()).unwrap(); - /// # let module = Module::new(&store, wasm_bytes).unwrap(); + /// # let module = Module::new(&store.engine(), wasm_bytes).unwrap(); + /// # let mut store = store.as_mut(); + /// # let env = FunctionEnv::new(&mut store, ()); /// # let import_object = imports! {}; /// # let instance = Instance::new(&mut store, &module, &import_object).unwrap(); /// # @@ -584,7 +591,6 @@ impl BackendFunction { /// # use wasmer::{imports, wat2wasm, Function, Instance, Module, Store, Type, TypedFunction, Value}; /// # use wasmer::FunctionEnv; /// # let mut store = Store::default(); - /// # let env = FunctionEnv::new(&mut store, ()); /// # let wasm_bytes = wat2wasm(r#" /// # (module /// # (func (export "sum") (param $x i32) (param $y i32) (result i32) @@ -593,7 +599,9 @@ impl BackendFunction { /// # i32.add /// # )) /// # "#.as_bytes()).unwrap(); - /// # let module = Module::new(&store, wasm_bytes).unwrap(); + /// # let module = Module::new(&store.engine(), wasm_bytes).unwrap(); + /// # let mut store = store.as_mut(); + /// # let env = FunctionEnv::new(&mut store, ()); /// # let import_object = imports! {}; /// # let instance = Instance::new(&mut store, &module, &import_object).unwrap(); /// # @@ -612,7 +620,6 @@ impl BackendFunction { /// # use wasmer::{imports, wat2wasm, Function, Instance, Module, Store, Type, TypedFunction, Value}; /// # use wasmer::FunctionEnv; /// # let mut store = Store::default(); - /// # let env = FunctionEnv::new(&mut store, ()); /// # let wasm_bytes = wat2wasm(r#" /// # (module /// # (func (export "sum") (param $x i32) (param $y i32) (result i32) @@ -621,7 +628,9 @@ impl BackendFunction { /// # i32.add /// # )) /// # "#.as_bytes()).unwrap(); - /// # let module = Module::new(&store, wasm_bytes).unwrap(); + /// # let module = Module::new(&store.engine(), wasm_bytes).unwrap(); + /// # let mut store = store.as_mut(); + /// # let env = FunctionEnv::new(&mut store, ()); /// # let import_object = imports! {}; /// # let instance = Instance::new(&mut store, &module, &import_object).unwrap(); /// # @@ -638,7 +647,6 @@ impl BackendFunction { /// # use wasmer::{imports, wat2wasm, Function, Instance, Module, Store, Type, TypedFunction, Value}; /// # use wasmer::FunctionEnv; /// # let mut store = Store::default(); - /// # let env = FunctionEnv::new(&mut store, ()); /// # let wasm_bytes = wat2wasm(r#" /// # (module /// # (func (export "sum") (param $x i32) (param $y i32) (result i32) @@ -647,7 +655,9 @@ impl BackendFunction { /// # i32.add /// # )) /// # "#.as_bytes()).unwrap(); - /// # let module = Module::new(&store, wasm_bytes).unwrap(); + /// # let module = Module::new(&store.engine(), wasm_bytes).unwrap(); + /// # let mut store = store.as_mut(); + /// # let env = FunctionEnv::new(&mut store, ()); /// # let import_object = imports! {}; /// # let instance = Instance::new(&mut store, &module, &import_object).unwrap(); /// # diff --git a/lib/api/src/entities/function/mod.rs b/lib/api/src/entities/function/mod.rs index da59c7128ac..80900aa185a 100644 --- a/lib/api/src/entities/function/mod.rs +++ b/lib/api/src/entities/function/mod.rs @@ -74,6 +74,7 @@ impl Function { /// ``` /// # use wasmer::{Function, FunctionEnv, FunctionType, Type, Store, Value}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # let env = FunctionEnv::new(&mut store, ()); /// # /// let signature = FunctionType::new(vec![Type::I32, Type::I32], vec![Type::I32]); @@ -89,6 +90,7 @@ impl Function { /// ``` /// # use wasmer::{Function, FunctionEnv, FunctionType, Type, Store, Value}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # let env = FunctionEnv::new(&mut store, ()); /// # /// const I32_I32_TO_I32: ([Type; 2], [Type; 1]) = ([Type::I32, Type::I32], [Type::I32]); @@ -134,6 +136,7 @@ impl Function { /// ``` /// # use wasmer::{Store, Function, FunctionEnv, FunctionEnvMut}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # let env = FunctionEnv::new(&mut store, ()); /// # /// fn sum(_env: FunctionEnvMut<()>, a: i32, b: i32) -> i32 { @@ -221,6 +224,7 @@ impl Function { /// ``` /// # use wasmer::{Function, FunctionEnv, FunctionEnvMut, Store, Type}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # let env = FunctionEnv::new(&mut store, ()); /// # /// fn sum(_env: FunctionEnvMut<()>, a: i32, b: i32) -> i32 { @@ -243,6 +247,7 @@ impl Function { /// ``` /// # use wasmer::{Function, FunctionEnv, FunctionEnvMut, Store, Type}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # let env = FunctionEnv::new(&mut store, ()); /// # /// fn sum(_env: FunctionEnvMut<()>, a: i32, b: i32) -> i32 { @@ -264,6 +269,7 @@ impl Function { /// ``` /// # use wasmer::{Function, FunctionEnv, FunctionEnvMut, Store, Type}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # let env = FunctionEnv::new(&mut store, ()); /// # /// fn sum(_env: FunctionEnvMut<()>, a: i32, b: i32) -> i32 { @@ -292,7 +298,6 @@ impl Function { /// # use wasmer::{imports, wat2wasm, Function, Instance, Module, Store, Type, Value}; /// # use wasmer::FunctionEnv; /// # let mut store = Store::default(); - /// # let env = FunctionEnv::new(&mut store, ()); /// # let wasm_bytes = wat2wasm(r#" /// # (module /// # (func (export "sum") (param $x i32) (param $y i32) (result i32) @@ -301,7 +306,9 @@ impl Function { /// # i32.add /// # )) /// # "#.as_bytes()).unwrap(); - /// # let module = Module::new(&store, wasm_bytes).unwrap(); + /// # let module = Module::new(&store.engine(), wasm_bytes).unwrap(); + /// # let mut store = store.as_mut(); + /// # let env = FunctionEnv::new(&mut store, ()); /// # let import_object = imports! {}; /// # let instance = Instance::new(&mut store, &module, &import_object).unwrap(); /// # @@ -360,7 +367,6 @@ impl Function { /// # use wasmer::{imports, wat2wasm, Function, Instance, Module, Store, Type, TypedFunction, Value}; /// # use wasmer::FunctionEnv; /// # let mut store = Store::default(); - /// # let env = FunctionEnv::new(&mut store, ()); /// # let wasm_bytes = wat2wasm(r#" /// # (module /// # (func (export "sum") (param $x i32) (param $y i32) (result i32) @@ -369,7 +375,9 @@ impl Function { /// # i32.add /// # )) /// # "#.as_bytes()).unwrap(); - /// # let module = Module::new(&store, wasm_bytes).unwrap(); + /// # let module = Module::new(&store.engine(), wasm_bytes).unwrap(); + /// # let mut store = store.as_mut(); + /// # let env = FunctionEnv::new(&mut store, ()); /// # let import_object = imports! {}; /// # let instance = Instance::new(&mut store, &module, &import_object).unwrap(); /// # @@ -388,7 +396,6 @@ impl Function { /// # use wasmer::{imports, wat2wasm, Function, Instance, Module, Store, Type, TypedFunction, Value}; /// # use wasmer::FunctionEnv; /// # let mut store = Store::default(); - /// # let env = FunctionEnv::new(&mut store, ()); /// # let wasm_bytes = wat2wasm(r#" /// # (module /// # (func (export "sum") (param $x i32) (param $y i32) (result i32) @@ -397,7 +404,9 @@ impl Function { /// # i32.add /// # )) /// # "#.as_bytes()).unwrap(); - /// # let module = Module::new(&store, wasm_bytes).unwrap(); + /// # let module = Module::new(&store.engine(), wasm_bytes).unwrap(); + /// # let mut store = store.as_mut(); + /// # let env = FunctionEnv::new(&mut store, ()); /// # let import_object = imports! {}; /// # let instance = Instance::new(&mut store, &module, &import_object).unwrap(); /// # @@ -414,7 +423,6 @@ impl Function { /// # use wasmer::{imports, wat2wasm, Function, Instance, Module, Store, Type, TypedFunction, Value}; /// # use wasmer::FunctionEnv; /// # let mut store = Store::default(); - /// # let env = FunctionEnv::new(&mut store, ()); /// # let wasm_bytes = wat2wasm(r#" /// # (module /// # (func (export "sum") (param $x i32) (param $y i32) (result i32) @@ -423,7 +431,9 @@ impl Function { /// # i32.add /// # )) /// # "#.as_bytes()).unwrap(); - /// # let module = Module::new(&store, wasm_bytes).unwrap(); + /// # let module = Module::new(&store.engine(), wasm_bytes).unwrap(); + /// # let mut store = store.as_mut(); + /// # let env = FunctionEnv::new(&mut store, ()); /// # let import_object = imports! {}; /// # let instance = Instance::new(&mut store, &module, &import_object).unwrap(); /// # diff --git a/lib/api/src/entities/global/inner.rs b/lib/api/src/entities/global/inner.rs index 363925a4baf..fca4982c42c 100644 --- a/lib/api/src/entities/global/inner.rs +++ b/lib/api/src/entities/global/inner.rs @@ -27,6 +27,7 @@ impl BackendGlobal { /// ``` /// # use wasmer::{Global, Mutability, Store, Value}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # /// let g = Global::new(&mut store, Value::I32(1)); /// @@ -45,6 +46,7 @@ impl BackendGlobal { /// ``` /// # use wasmer::{Global, Mutability, Store, Value}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # /// let g = Global::new_mut(&mut store, Value::I32(1)); /// @@ -99,6 +101,7 @@ impl BackendGlobal { /// ``` /// # use wasmer::{Global, Mutability, Store, Type, Value, GlobalType}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # /// let c = Global::new(&mut store, Value::I32(1)); /// let v = Global::new_mut(&mut store, Value::I64(1)); @@ -120,6 +123,7 @@ impl BackendGlobal { /// ``` /// # use wasmer::{Global, Store, Value}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # /// let g = Global::new(&mut store, Value::I32(1)); /// @@ -139,6 +143,7 @@ impl BackendGlobal { /// ``` /// # use wasmer::{Global, Store, Value}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # /// let g = Global::new_mut(&mut store, Value::I32(1)); /// @@ -156,6 +161,7 @@ impl BackendGlobal { /// ```should_panic /// # use wasmer::{Global, Store, Value}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # /// let g = Global::new(&mut store, Value::I32(1)); /// @@ -167,6 +173,7 @@ impl BackendGlobal { /// ```should_panic /// # use wasmer::{Global, Store, Value}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # /// let g = Global::new(&mut store, Value::I32(1)); /// diff --git a/lib/api/src/entities/global/mod.rs b/lib/api/src/entities/global/mod.rs index 93b3fc50253..3d436937754 100644 --- a/lib/api/src/entities/global/mod.rs +++ b/lib/api/src/entities/global/mod.rs @@ -28,6 +28,7 @@ impl Global { /// ``` /// # use wasmer::{Global, Mutability, Store, Value}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # /// let g = Global::new(&mut store, Value::I32(1)); /// @@ -45,6 +46,7 @@ impl Global { /// ``` /// # use wasmer::{Global, Mutability, Store, Value}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # /// let g = Global::new_mut(&mut store, Value::I32(1)); /// @@ -71,6 +73,7 @@ impl Global { /// ``` /// # use wasmer::{Global, Mutability, Store, Type, Value, GlobalType}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # /// let c = Global::new(&mut store, Value::I32(1)); /// let v = Global::new_mut(&mut store, Value::I64(1)); @@ -89,6 +92,7 @@ impl Global { /// ``` /// # use wasmer::{Global, Store, Value}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # /// let g = Global::new(&mut store, Value::I32(1)); /// @@ -105,6 +109,7 @@ impl Global { /// ``` /// # use wasmer::{Global, Store, Value}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # /// let g = Global::new_mut(&mut store, Value::I32(1)); /// @@ -122,6 +127,7 @@ impl Global { /// ```should_panic /// # use wasmer::{Global, Store, Value}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # /// let g = Global::new(&mut store, Value::I32(1)); /// @@ -133,6 +139,7 @@ impl Global { /// ```should_panic /// # use wasmer::{Global, Store, Value}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # /// let g = Global::new(&mut store, Value::I32(1)); /// diff --git a/lib/api/src/entities/imports.rs b/lib/api/src/entities/imports.rs index 0074b1ddd13..500d089260d 100644 --- a/lib/api/src/entities/imports.rs +++ b/lib/api/src/entities/imports.rs @@ -15,17 +15,17 @@ use wasmer_types::ImportError; /// /// # Usage: /// ```no_run -/// use wasmer::{Store, Exports, Module, Instance, imports, Imports, Function, FunctionEnvMut}; -/// # fn foo_test(mut store: &mut Store, module: Module) { +/// use wasmer::{AsStoreMut, Exports, Module, Instance, imports, Imports, Function, FunctionEnvMut}; +/// # fn foo_test(store: &mut impl AsStoreMut, module: Module) { /// -/// let host_fn = Function::new_typed(&mut store, foo); +/// let host_fn = Function::new_typed(store, foo); /// let import_object: Imports = imports! { /// "env" => { /// "foo" => host_fn, /// }, /// }; /// -/// let instance = Instance::new(&mut store, &module, &import_object).expect("Could not instantiate module."); +/// let instance = Instance::new(store, &module, &import_object).expect("Could not instantiate module."); /// /// fn foo(n: i32) -> i32 { /// n @@ -108,6 +108,7 @@ impl Imports { /// ```no_run /// # use wasmer::{FunctionEnv, Store}; /// # let mut store: Store = Default::default(); + /// # let mut store = store.as_mut(); /// use wasmer::{StoreMut, Imports, Function, FunctionEnvMut}; /// fn foo(n: i32) -> i32 { /// n @@ -244,6 +245,7 @@ impl fmt::Debug for Imports { /// ``` /// # use wasmer::{StoreMut, Function, FunctionEnvMut, Store}; /// # let mut store = Store::default(); +/// # let mut store = store.as_mut(); /// use wasmer::imports; /// /// let import_object = imports! { diff --git a/lib/api/src/entities/instance.rs b/lib/api/src/entities/instance.rs index 7d78f941156..b3d40b3c055 100644 --- a/lib/api/src/entities/instance.rs +++ b/lib/api/src/entities/instance.rs @@ -31,8 +31,9 @@ impl Instance { /// # use wasmer::FunctionEnv; /// # fn main() -> anyhow::Result<()> { /// let mut store = Store::default(); + /// let module = Module::new(&store.engine(), "(module)")?; + /// let mut store = store.as_mut(); /// let env = FunctionEnv::new(&mut store, ()); - /// let module = Module::new(&store, "(module)")?; /// let imports = imports!{ /// "host" => { /// "var" => Global::new(&mut store, Value::I32(2)) diff --git a/lib/api/src/entities/memory/inner.rs b/lib/api/src/entities/memory/inner.rs index 376477c79ca..c7d7cf777a3 100644 --- a/lib/api/src/entities/memory/inner.rs +++ b/lib/api/src/entities/memory/inner.rs @@ -23,6 +23,7 @@ impl BackendMemory { /// ``` /// # use wasmer::{Memory, MemoryType, Pages, Store, Type, Value}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # /// let m = Memory::new(&mut store, MemoryType::new(1, None, false)).unwrap(); /// ``` @@ -112,6 +113,7 @@ impl BackendMemory { /// ``` /// # use wasmer::{Memory, MemoryType, Pages, Store, Type, Value}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # /// let mt = MemoryType::new(1, None, false); /// let m = Memory::new(&mut store, mt).unwrap(); @@ -140,6 +142,7 @@ impl BackendMemory { /// ``` /// # use wasmer::{Memory, MemoryType, Pages, Store, Type, Value, WASM_MAX_PAGES}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # /// let m = Memory::new(&mut store, MemoryType::new(1, Some(3), false)).unwrap(); /// let p = m.grow(&mut store, 2).unwrap(); @@ -157,6 +160,7 @@ impl BackendMemory { /// # use wasmer::{Memory, MemoryType, Pages, Store, Type, Value, WASM_MAX_PAGES}; /// # use wasmer::FunctionEnv; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # let env = FunctionEnv::new(&mut store, ()); /// # /// let m = Memory::new(&mut store, MemoryType::new(1, Some(1), false)).unwrap(); diff --git a/lib/api/src/entities/memory/mod.rs b/lib/api/src/entities/memory/mod.rs index e869b2fb703..5ea88f1e5ae 100644 --- a/lib/api/src/entities/memory/mod.rs +++ b/lib/api/src/entities/memory/mod.rs @@ -44,6 +44,7 @@ impl Memory { /// ``` /// # use wasmer::{Memory, MemoryType, Pages, Store, Type, Value}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # /// let m = Memory::new(&mut store, MemoryType::new(1, None, false)).unwrap(); /// ``` @@ -69,6 +70,7 @@ impl Memory { /// ``` /// # use wasmer::{Memory, MemoryType, Pages, Store, Type, Value}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # /// let mt = MemoryType::new(1, None, false); /// let m = Memory::new(&mut store, mt).unwrap(); @@ -98,6 +100,7 @@ impl Memory { /// ``` /// # use wasmer::{Memory, MemoryType, Pages, Store, Type, Value, WASM_MAX_PAGES}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # /// let m = Memory::new(&mut store, MemoryType::new(1, Some(3), false)).unwrap(); /// let p = m.grow(&mut store, 2).unwrap(); @@ -115,6 +118,7 @@ impl Memory { /// # use wasmer::{Memory, MemoryType, Pages, Store, Type, Value, WASM_MAX_PAGES}; /// # use wasmer::FunctionEnv; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # let env = FunctionEnv::new(&mut store, ()); /// # /// let m = Memory::new(&mut store, MemoryType::new(1, Some(1), false)).unwrap(); diff --git a/lib/api/src/entities/memory/view/inner.rs b/lib/api/src/entities/memory/view/inner.rs index 08d734b391c..c95acfd6246 100644 --- a/lib/api/src/entities/memory/view/inner.rs +++ b/lib/api/src/entities/memory/view/inner.rs @@ -119,6 +119,7 @@ impl<'a> BackendMemoryView<'a> { /// ``` /// # use wasmer::{Memory, MemoryType, Pages, Store, Type, Value}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # /// let m = Memory::new(&mut store, MemoryType::new(1, None, false)).unwrap(); /// diff --git a/lib/api/src/entities/memory/view/mod.rs b/lib/api/src/entities/memory/view/mod.rs index 8f16105dc53..915ea4804b6 100644 --- a/lib/api/src/entities/memory/view/mod.rs +++ b/lib/api/src/entities/memory/view/mod.rs @@ -68,6 +68,7 @@ impl<'a> MemoryView<'a> { /// ``` /// # use wasmer::{Memory, MemoryType, Pages, Store, Type, Value}; /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// # /// let m = Memory::new(&mut store, MemoryType::new(1, None, false)).unwrap(); /// diff --git a/lib/api/src/entities/module/inner.rs b/lib/api/src/entities/module/inner.rs index 6c9fd76242f..a5e7df862ed 100644 --- a/lib/api/src/entities/module/inner.rs +++ b/lib/api/src/entities/module/inner.rs @@ -438,8 +438,9 @@ impl BackendModule { /// /// ```ignore /// # use wasmer::*; - /// # let mut store = Store::default(); /// # fn main() -> anyhow::Result<()> { + /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// let module = Module::deserialize_from_file(&store, path)?; /// # Ok(()) /// # } @@ -525,8 +526,9 @@ impl BackendModule { /// /// ```ignore /// # use wasmer::*; - /// # let mut store = Store::default(); /// # fn main() -> anyhow::Result<()> { + /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// let module = Module::deserialize_from_file_unchecked(&store, path)?; /// # Ok(()) /// # } @@ -607,7 +609,7 @@ impl BackendModule { /// # fn main() -> anyhow::Result<()> { /// # let mut store = Store::default(); /// let wat = "(module $moduleName)"; - /// let module = Module::new(&store, wat)?; + /// let module = Module::new(&store.engine(), wat)?; /// assert_eq!(module.name(), Some("moduleName")); /// # Ok(()) /// # } @@ -633,7 +635,7 @@ impl BackendModule { /// # fn main() -> anyhow::Result<()> { /// # let mut store = Store::default(); /// let wat = "(module)"; - /// let mut module = Module::new(&store, wat)?; + /// let mut module = Module::new(&store.engine(), wat)?; /// assert_eq!(module.name(), None); /// module.set_name("foo"); /// assert_eq!(module.name(), Some("foo")); @@ -662,7 +664,7 @@ impl BackendModule { /// (import "host" "func1" (func)) /// (import "host" "func2" (func)) /// )"#; - /// let module = Module::new(&store, wat)?; + /// let module = Module::new(&store.engine(), wat)?; /// for import in module.imports() { /// assert_eq!(import.module(), "host"); /// assert!(import.name().contains("func")); @@ -693,7 +695,7 @@ impl BackendModule { /// (func (export "namedfunc")) /// (memory (export "namedmemory") 1) /// )"#; - /// let module = Module::new(&store, wat)?; + /// let module = Module::new(&store.engine(), wat)?; /// for export_ in module.exports() { /// assert!(export_.name().contains("named")); /// export_.ty(); diff --git a/lib/api/src/entities/module/mod.rs b/lib/api/src/entities/module/mod.rs index 15c2cbe3490..12af2f6d189 100644 --- a/lib/api/src/entities/module/mod.rs +++ b/lib/api/src/entities/module/mod.rs @@ -69,7 +69,7 @@ impl Module { /// # fn main() -> anyhow::Result<()> { /// # let mut store = Store::default(); /// let wat = "(module)"; - /// let module = Module::new(&store, wat)?; + /// let module = Module::new(&store.engine(), wat)?; /// # Ok(()) /// # } /// ``` @@ -96,7 +96,7 @@ impl Module { /// 0x61, 0x6d, 0x65, 0x01, 0x0a, 0x01, 0x00, 0x07, 0x61, 0x64, 0x64, 0x5f, /// 0x6f, 0x6e, 0x65, 0x02, 0x07, 0x01, 0x00, 0x01, 0x00, 0x02, 0x70, 0x30, /// ]; - /// let module = Module::new(&store, bytes)?; + /// let module = Module::new(&store.engine(), bytes)?; /// # Ok(()) /// # } /// ``` @@ -276,8 +276,9 @@ impl Module { /// /// ```ignore /// # use wasmer::*; - /// # let mut store = Store::default(); /// # fn main() -> anyhow::Result<()> { + /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// let module = Module::deserialize_from_file(&store, path)?; /// # Ok(()) /// # } @@ -306,8 +307,9 @@ impl Module { /// /// ```ignore /// # use wasmer::*; - /// # let mut store = Store::default(); /// # fn main() -> anyhow::Result<()> { + /// # let mut store = Store::default(); + /// # let mut store = store.as_mut(); /// let module = Module::deserialize_from_file_unchecked(&store, path)?; /// # Ok(()) /// # } @@ -331,7 +333,7 @@ impl Module { /// # fn main() -> anyhow::Result<()> { /// # let mut store = Store::default(); /// let wat = "(module $moduleName)"; - /// let module = Module::new(&store, wat)?; + /// let module = Module::new(&store.engine(), wat)?; /// assert_eq!(module.name(), Some("moduleName")); /// # Ok(()) /// # } @@ -354,7 +356,7 @@ impl Module { /// # fn main() -> anyhow::Result<()> { /// # let mut store = Store::default(); /// let wat = "(module)"; - /// let mut module = Module::new(&store, wat)?; + /// let mut module = Module::new(&store.engine(), wat)?; /// assert_eq!(module.name(), None); /// module.set_name("foo"); /// assert_eq!(module.name(), Some("foo")); @@ -380,7 +382,7 @@ impl Module { /// (import "host" "func1" (func)) /// (import "host" "func2" (func)) /// )"#; - /// let module = Module::new(&store, wat)?; + /// let module = Module::new(&store.engine(), wat)?; /// for import in module.imports() { /// assert_eq!(import.module(), "host"); /// assert!(import.name().contains("func")); @@ -408,7 +410,7 @@ impl Module { /// (func (export "namedfunc")) /// (memory (export "namedmemory") 1) /// )"#; - /// let module = Module::new(&store, wat)?; + /// let module = Module::new(&store.engine(), wat)?; /// for export_ in module.exports() { /// assert!(export_.name().contains("named")); /// export_.ty(); diff --git a/lib/api/src/entities/store/context.rs b/lib/api/src/entities/store/context.rs index 9703cbcf5fc..296a2092b41 100644 --- a/lib/api/src/entities/store/context.rs +++ b/lib/api/src/entities/store/context.rs @@ -51,7 +51,6 @@ pub(crate) struct StoreContext { } pub(crate) struct StoreMutWrapper { - // Need MaybeUninit for the Drop impl store_mut: *mut StoreMut, } From 4ced3be1c913a0128755609d3d8d1227a21d9276 Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Thu, 20 Nov 2025 17:49:20 +0400 Subject: [PATCH 15/49] Rework Function::call_async and Function::new_async w.r.t the new Store API --- lib/api/src/backend/sys/async_runtime.rs | 162 ++- .../src/backend/sys/entities/function/env.rs | 166 ++- .../src/backend/sys/entities/function/mod.rs | 339 +++--- .../backend/sys/entities/function/typed.rs | 60 +- lib/api/src/entities/function/async_host.rs | 28 +- lib/api/src/entities/function/env/inner.rs | 187 ++- lib/api/src/entities/function/env/mod.rs | 103 +- lib/api/src/entities/function/inner.rs | 234 ++-- lib/api/src/entities/function/mod.rs | 142 ++- lib/api/src/entities/store/asynk.rs | 163 +++ lib/api/src/entities/store/context.rs | 88 ++ lib/api/src/entities/store/inner.rs | 4 + lib/api/src/entities/store/mod.rs | 60 +- lib/api/src/entities/store/store_ref.rs | 11 +- lib/api/src/utils/native/typed_func.rs | 73 +- lib/api/tests/jspi_async.rs | 1023 +++++++++-------- 16 files changed, 1858 insertions(+), 985 deletions(-) create mode 100644 lib/api/src/entities/store/asynk.rs diff --git a/lib/api/src/backend/sys/async_runtime.rs b/lib/api/src/backend/sys/async_runtime.rs index f1f66b97e5b..be45b0d8fa9 100644 --- a/lib/api/src/backend/sys/async_runtime.rs +++ b/lib/api/src/backend/sys/async_runtime.rs @@ -12,53 +12,104 @@ use std::{ use corosensei::{Coroutine, CoroutineResult, Yielder}; use super::entities::function::Function as SysFunction; -use crate::{AsStoreMut, RuntimeError, Value}; +use crate::{AsStoreMut, AsStoreRef, RuntimeError, Store, StoreContext, StoreMut, Value}; +use wasmer_types::StoreId; type HostFuture = Pin, RuntimeError>> + Send + 'static>>; -pub(crate) fn call_function_async<'a, S>( +pub(crate) fn call_function_async<'a>( function: SysFunction, - store: &'a mut S, + store: Store, params: Vec, -) -> AsyncCallFuture<'a, S> -where - S: AsStoreMut + 'static, -{ +) -> AsyncCallFuture<'a> { AsyncCallFuture::new(function, store, params) } -enum AsyncYield { - HostFuture(HostFuture), -} +struct AsyncYield(HostFuture); enum AsyncResume { Start, HostFutureReady(Result, RuntimeError>), } -pub(crate) struct AsyncCallFuture<'a, S: AsStoreMut + 'static> { +pub(crate) struct AsyncCallFuture<'a> { coroutine: Option, RuntimeError>>>, + pending_store_install: Option + 'a>>>, pending_future: Option, next_resume: Option, result: Option, RuntimeError>>, + // Store handle we can use to lock the store down + store: Store, + // Use Rc> to make sure that the future is !Send and !Sync - _marker: PhantomData>>, + _marker: PhantomData>>, } -impl<'a, S> AsyncCallFuture<'a, S> -where - S: AsStoreMut + 'static, -{ - pub(crate) fn new(function: SysFunction, store: &'a mut S, params: Vec) -> Self { - let store_ptr = store as *mut S; +// We can't use any of the existing AsStoreMut types here, since we keep +// changing the store context underneath us while the coroutine yields. +// To work around it, we use this dummy struct, which just grabs the store +// from the store context. Since we always have a store context installed +// when resuming the coroutine, this is safe in that it can access the store +// through the store context. HOWEVER, references returned from this struct +// CAN NOT BE HELD ACROSS A YIELD POINT. We don't do this anywhere in the +// `Function::call code. +struct AsyncCallStoreMut { + store_id: StoreId, +} + +impl AsStoreRef for AsyncCallStoreMut { + fn as_ref(&self) -> &crate::StoreInner { + // Safety: This is only used with Function::call, which doesn't store + // the returned reference anywhere, including when calling into WASM + // code. + unsafe { + StoreContext::get_current_transient(self.store_id) + .as_ref() + .unwrap() + .as_ref() + } + } +} + +impl AsStoreMut for AsyncCallStoreMut { + fn as_mut(&mut self) -> &mut crate::StoreInner { + // Safety: This is only used with Function::call, which doesn't store + // the returned reference anywhere, including when calling into WASM + // code. + unsafe { + StoreContext::get_current_transient(self.store_id) + .as_mut() + .unwrap() + .as_mut() + } + } + + fn reborrow_mut(&mut self) -> &mut StoreMut { + // Safety: This is only used with Function::call, which doesn't store + // the returned reference anywhere, including when calling into WASM + // code. + unsafe { + StoreContext::get_current_transient(self.store_id) + .as_mut() + .unwrap() + .reborrow_mut() + } + } +} + +impl<'a> AsyncCallFuture<'a> { + pub(crate) fn new(function: SysFunction, store: crate::Store, params: Vec) -> Self { + let store_id = store.id; let coroutine = - Coroutine::new(move |yielder: &Yielder, _resume| { + Coroutine::new(move |yielder: &Yielder, resume| { + assert!(matches!(resume, AsyncResume::Start)); + let ctx_state = CoroutineContext::new(yielder); ctx_state.enter(); let result = { - let store_ref = unsafe { &mut *store_ptr }; - function.call(store_ref, ¶ms) + let mut store_mut = AsyncCallStoreMut { store_id }; + function.call(&mut store_mut, ¶ms) }; ctx_state.leave(); result @@ -66,18 +117,17 @@ where Self { coroutine: Some(coroutine), + pending_store_install: None, pending_future: None, next_resume: Some(AsyncResume::Start), result: None, + store, _marker: PhantomData, } } } -impl<'a, S> Future for AsyncCallFuture<'a, S> -where - S: AsStoreMut + 'static, -{ +impl Future for AsyncCallFuture<'_> { type Output = Result, RuntimeError>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { @@ -92,13 +142,40 @@ where } } - let resume_arg = self.next_resume.take().unwrap_or(AsyncResume::Start); - let coroutine = match self.coroutine.as_mut() { - Some(coro) => coro, - None => return Poll::Ready(self.result.take().expect("polled after completion")), + // If we're ready, return early + if self.coroutine.is_none() { + return Poll::Ready(self.result.take().expect("polled after completion")); + } + + // Start a store installation if not in progress already + if let None = self.pending_store_install { + self.pending_store_install = + Some(Box::pin(StoreContextInstaller::install(Store { + id: self.store.id, + inner: self.store.inner.clone(), + }))); + } + + // Acquiring a store lock should be the last step before resuming + // the coroutine, to minimize the time we hold the lock. + let store_context_guard = match self + .pending_store_install + .as_mut() + .unwrap() + .as_mut() + .poll(cx) + { + Poll::Ready(guard) => { + self.pending_store_install = None; + guard + } + Poll::Pending => return Poll::Pending, }; + + let resume_arg = self.next_resume.take().expect("no resume arg available"); + let coroutine = self.coroutine.as_mut().unwrap(); match coroutine.resume(resume_arg) { - CoroutineResult::Yield(AsyncYield::HostFuture(fut)) => { + CoroutineResult::Yield(AsyncYield(fut)) => { self.pending_future = Some(fut); } CoroutineResult::Return(result) => { @@ -106,6 +183,29 @@ where self.result = Some(result); } } + + // Uninstall the store context to unlock the store after the coroutine + // yields or returns. + drop(store_context_guard); + } + } +} + +enum StoreContextInstaller { + FromThreadContext(crate::StoreMutWrapper), + Installed(crate::ForcedStoreInstallGuard), +} + +impl StoreContextInstaller { + async fn install(store: Store) -> Self { + if let Some(wrapper) = unsafe { crate::StoreContext::try_get_current(store.id) } { + // If we're already in the scope of this store, we can just reuse it. + StoreContextInstaller::FromThreadContext(wrapper) + } else { + // Otherwise, need to acquire a new StoreMut. + let store_mut = store.make_mut_async().await; + let guard = crate::StoreContext::force_install(store_mut); + StoreContextInstaller::Installed(guard) } } } @@ -201,7 +301,7 @@ impl CoroutineContext { fn block_on_future(&self, future: HostFuture) -> Result, RuntimeError> { let yielder = unsafe { self.yielder.as_ref().expect("yielder pointer valid") }; - match yielder.suspend(AsyncYield::HostFuture(future)) { + match yielder.suspend(AsyncYield(future)) { AsyncResume::HostFutureReady(result) => result, AsyncResume::Start => unreachable!("coroutine resumed without start"), } diff --git a/lib/api/src/backend/sys/entities/function/env.rs b/lib/api/src/backend/sys/entities/function/env.rs index 6a30a3850bb..f06497a9350 100644 --- a/lib/api/src/backend/sys/entities/function/env.rs +++ b/lib/api/src/backend/sys/entities/function/env.rs @@ -1,11 +1,12 @@ use std::{any::Any, fmt::Debug, marker::PhantomData}; use crate::{ - StoreMut, + AsAsyncStore, AsyncStoreReadLock, AsyncStoreWriteLock, Store, StoreContext, StoreMut, + StoreMutGuard, StoreMutWrapper, store::{AsStoreMut, AsStoreRef, StoreRef}, }; -use wasmer_vm::{StoreHandle, StoreObject, StoreObjects, VMFunctionEnvironment}; +use wasmer_vm::{StoreHandle, StoreId, StoreObject, StoreObjects, VMFunctionEnvironment}; #[derive(Debug)] #[repr(transparent)] @@ -174,6 +175,14 @@ impl FunctionEnvMut<'_, T> { let data = unsafe { &mut *data }; (data, self.store_mut.reborrow_mut()) } + + /// Creates an [`AsAsyncStore`] from this [`FunctionEnvMut`]. + pub fn as_async_store(&mut self) -> impl AsAsyncStore + 'static { + Store { + id: self.store_mut.store_handle.id, + inner: self.store_mut.store_handle.inner.clone(), + } + } } impl AsStoreRef for FunctionEnvMut<'_, T> { @@ -203,3 +212,156 @@ impl From> for crate::FunctionEnv { Self(crate::BackendFunctionEnv::Sys(value)) } } + +/// A temporary handle to a [`FunctionEnv`], suitable for use +/// in async imports. +pub struct AsyncFunctionEnvMut { + pub(crate) store: Store, + pub(crate) func_env: FunctionEnv, +} + +/// A read-only handle to the [`FunctionEnv`] in an [`AsyncFunctionEnvMut`]. +pub struct AsyncFunctionEnvHandle<'a, T> { + read_lock: AsyncStoreReadLock<'a>, + pub(crate) func_env: FunctionEnv, + + // This type needs to be !Send + _marker: PhantomData<*const &'a ()>, +} + +/// A mutable handle to the [`FunctionEnv`] in an [`AsyncFunctionEnvMut`]. +/// Internally, a [`StoreMutGuard`] is used, so the store handle from this +/// type can be used to invoke [`Function::call`](crate::Function::call) +/// while outside a store's context. +pub struct AsyncFunctionEnvHandleMut<'a, T> { + write_lock: AsyncStoreWriteLock<'a>, + pub(crate) func_env: FunctionEnv, + + // This type needs to be !Send + _marker: PhantomData<*const ()>, +} + +impl Debug for AsyncFunctionEnvMut +where + T: Send + Debug + 'static, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.store.try_make_mut() { + Some(mut store_mut) => self.func_env.as_ref(&mut store_mut).fmt(f), + None => write!(f, "AsyncFunctionEnvMut {{ }}"), + } + } +} + +impl AsyncFunctionEnvMut { + pub(crate) fn store_id(&self) -> StoreId { + self.store.id + } + + /// Waits for a store lock and returns a read-only handle to the + /// function environment. + pub async fn read<'a>(&'a self) -> AsyncFunctionEnvHandle<'a, T> { + let read_lock = self.store.read_lock().await; + AsyncFunctionEnvHandle { + read_lock, + func_env: self.func_env.clone(), + _marker: PhantomData, + } + } + + /// Waits for a store lock and returns a mutable handle to the + /// function environment. + pub async fn write<'a>(&'a self) -> AsyncFunctionEnvHandleMut<'a, T> { + let write_lock = self.store.write_lock().await; + AsyncFunctionEnvHandleMut { + write_lock, + func_env: self.func_env.clone(), + _marker: PhantomData, + } + } + + /// Borrows a new immmutable reference + pub fn as_ref(&self) -> FunctionEnv { + self.func_env.clone() + } + + /// Borrows a new mutable reference + pub fn as_mut(&mut self) -> AsyncFunctionEnvMut { + AsyncFunctionEnvMut { + store: Store { + id: self.store.id, + inner: self.store.inner.clone(), + }, + func_env: self.func_env.clone(), + } + } + + /// Creates an [`AsAsyncStore`] from this [`AsyncFunctionEnvMut`]. + pub fn as_async_store(&mut self) -> impl AsAsyncStore + 'static { + Store { + id: self.store.id, + inner: self.store.inner.clone(), + } + } +} + +impl AsyncFunctionEnvHandle<'_, T> { + /// Returns a reference to the host state in this function environment. + pub fn data(&self) -> &T { + self.func_env.as_ref(&self.read_lock) + } + + /// Returns both the host state and the attached StoreRef + pub fn data_and_store(&self) -> (&T, &impl AsStoreRef) { + (self.data(), &self.read_lock) + } +} + +impl AsStoreRef for AsyncFunctionEnvHandle<'_, T> { + fn as_ref(&self) -> &crate::StoreInner { + AsStoreRef::as_ref(&self.read_lock) + } +} + +impl AsyncFunctionEnvHandleMut<'_, T> { + /// Returns a mutable reference to the host state in this function environment. + pub fn data_mut(&mut self) -> &mut T { + self.func_env.as_mut(&mut self.write_lock) + } + + /// Returns both the host state and the attached StoreMut + pub fn data_and_store_mut(&mut self) -> (&mut T, &mut impl AsStoreMut) { + let data = self.data_mut() as *mut T; + // Wisdom of the ancients: + // telling the borrow check to close his eyes here + // this is still relatively safe to do as func_env are + // stored in a specific vec of Store, separate from the other objects + // and not really directly accessible with the StoreMut + let data = unsafe { &mut *data }; + (data, &mut self.write_lock) + } +} + +impl AsStoreRef for AsyncFunctionEnvHandleMut<'_, T> { + fn as_ref(&self) -> &crate::StoreInner { + AsStoreRef::as_ref(&self.write_lock) + } +} + +impl AsStoreMut for AsyncFunctionEnvHandleMut<'_, T> { + fn as_mut(&mut self) -> &mut crate::StoreInner { + AsStoreMut::as_mut(&mut self.write_lock) + } + + fn reborrow_mut(&mut self) -> &mut StoreMut { + AsStoreMut::reborrow_mut(&mut self.write_lock) + } + + fn take(&mut self) -> Option { + AsStoreMut::take(&mut self.write_lock) + } + + fn put_back(&mut self, store_mut: StoreMut) { + AsStoreMut::put_back(&mut self.write_lock, store_mut); + } +} diff --git a/lib/api/src/backend/sys/entities/function/mod.rs b/lib/api/src/backend/sys/entities/function/mod.rs index c3ee27dc2b2..913ff26a180 100644 --- a/lib/api/src/backend/sys/entities/function/mod.rs +++ b/lib/api/src/backend/sys/entities/function/mod.rs @@ -4,8 +4,9 @@ pub(crate) mod env; pub(crate) mod typed; use crate::{ - BackendFunction, FunctionEnv, FunctionEnvMut, FunctionType, HostFunction, RuntimeError, - StoreContext, StoreInner, Value, WithEnv, WithoutEnv, + AsAsyncStore, AsyncFunctionEnvMut, BackendAsyncFunctionEnvMut, BackendFunction, FunctionEnv, + FunctionEnvMut, FunctionType, HostFunction, RuntimeError, StoreContext, StoreInner, Value, + WithEnv, WithoutEnv, backend::sys::{engine::NativeEngineExt, vm::VMFunctionCallback}, entities::{ function::async_host::{AsyncFunctionEnv, AsyncHostFunction}, @@ -123,93 +124,95 @@ impl Function { } } - // TODO: async API - // pub(crate) fn new_async(store: &mut impl AsStoreMut, ty: FT, func: F) -> Self - // where - // FT: Into, - // F: Fn(&[Value]) -> Fut + 'static + Send + Sync, - // Fut: Future, RuntimeError>> + 'static + Send, - // { - // let env = FunctionEnv::new(store, ()); - // let wrapped = move |_env: FunctionEnvMut<()>, values: &[Value]| func(values); - // Self::new_with_env_async(store, &env, ty, wrapped) - // } - - // TODO: async API - // pub(crate) fn new_with_env_async( - // store: &mut impl AsStoreMut, - // env: &FunctionEnv, - // ty: FT, - // func: F, - // ) -> Self - // where - // FT: Into, - // F: Fn(FunctionEnvMut, &[Value]) -> Fut + 'static + Send + Sync, - // Fut: Future, RuntimeError>> + 'static + Send, - // { - // let function_type = ty.into(); - // let func_ty = function_type.clone(); - // let func_env = env.clone().into_sys(); - // let store_id = store.objects().id(); - // let wrapper = move |values_vec: *mut RawValue| -> HostCallOutcome { - // unsafe { - // let mut store_wrapper = StoreContext::get_current(store_id); - // let mut store = store_wrapper.as_mut(); - // let mut args = Vec::with_capacity(func_ty.params().len()); - - // for (i, ty) in func_ty.params().iter().enumerate() { - // args.push(Value::from_raw( - // store, - // *ty, - // values_vec.add(i).read_unaligned(), - // )); - // } - // let env = env::FunctionEnvMut { - // store_mut: store, - // func_env: func_env.clone(), - // } - // .into(); - // let sig = func_ty.clone(); - // let future = func(env, &args); - // HostCallOutcome::Future { - // func_ty: sig, - // future: Box::pin(future) - // as Pin, RuntimeError>> + Send>>, - // } - // } - // }; - // let mut host_data = Box::new(VMDynamicFunctionContext { - // address: std::ptr::null(), - // ctx: DynamicFunction { - // func: wrapper, - // store_id, - // }, - // }); - // host_data.address = host_data.ctx.func_body_ptr() as *const VMFunctionBody; - - // let func_ptr = std::ptr::null() as VMFunctionCallback; - // let type_index = store.engine().as_sys().register_signature(&function_type); - // let vmctx = VMFunctionContext { - // host_env: host_data.as_ref() as *const _ as *mut c_void, - // }; - // let call_trampoline = host_data.ctx.call_trampoline_address(); - // let anyfunc = VMCallerCheckedAnyfunc { - // func_ptr, - // type_index, - // vmctx, - // call_trampoline, - // }; - - // let vm_function = VMFunction { - // anyfunc: MaybeInstanceOwned::Host(Box::new(UnsafeCell::new(anyfunc))), - // kind: VMFunctionKind::Dynamic, - // signature: function_type, - // host_data, - // }; - // Self { - // handle: StoreHandle::new(store.objects_mut().as_sys_mut(), vm_function), - // } - // } + pub(crate) fn new_async(store: &mut impl AsStoreMut, ty: FT, func: F) -> Self + where + FT: Into, + F: Fn(&[Value]) -> Fut + 'static + Send + Sync, + Fut: Future, RuntimeError>> + 'static + Send, + { + let env = FunctionEnv::new(store, ()); + let wrapped = move |_env: AsyncFunctionEnvMut<()>, values: &[Value]| func(values); + Self::new_with_env_async(store, &env, ty, wrapped) + } + + pub(crate) fn new_with_env_async( + store: &mut impl AsStoreMut, + env: &FunctionEnv, + ty: FT, + func: F, + ) -> Self + where + FT: Into, + F: Fn(AsyncFunctionEnvMut, &[Value]) -> Fut + 'static + Send + Sync, + Fut: Future, RuntimeError>> + 'static + Send, + { + let function_type = ty.into(); + let func_ty = function_type.clone(); + let func_env = env.clone().into_sys(); + let store_id = store.objects().id(); + let wrapper = move |values_vec: *mut RawValue| -> HostCallOutcome { + unsafe { + let mut store_wrapper = StoreContext::get_current(store_id); + let mut store = store_wrapper.as_mut(); + let mut args = Vec::with_capacity(func_ty.params().len()); + + for (i, ty) in func_ty.params().iter().enumerate() { + args.push(Value::from_raw( + store, + *ty, + values_vec.add(i).read_unaligned(), + )); + } + let env = crate::AsyncFunctionEnvMut(crate::BackendAsyncFunctionEnvMut::Sys( + env::AsyncFunctionEnvMut { + store: crate::Store { + id: store.store_handle.id, + inner: store.store_handle.inner.clone(), + }, + func_env: func_env.clone(), + }, + )); + let sig = func_ty.clone(); + let future = func(env, &args); + HostCallOutcome::Future { + func_ty: sig, + future: Box::pin(future) + as Pin, RuntimeError>> + Send>>, + } + } + }; + let mut host_data = Box::new(VMDynamicFunctionContext { + address: std::ptr::null(), + ctx: DynamicFunction { + func: wrapper, + store_id, + }, + }); + host_data.address = host_data.ctx.func_body_ptr() as *const VMFunctionBody; + + let func_ptr = std::ptr::null() as VMFunctionCallback; + let type_index = store.engine().as_sys().register_signature(&function_type); + let vmctx = VMFunctionContext { + host_env: host_data.as_ref() as *const _ as *mut c_void, + }; + let call_trampoline = host_data.ctx.call_trampoline_address(); + let anyfunc = VMCallerCheckedAnyfunc { + func_ptr, + type_index, + vmctx, + call_trampoline, + }; + + let vm_function = VMFunction { + anyfunc: MaybeInstanceOwned::Host(Box::new(UnsafeCell::new(anyfunc))), + kind: VMFunctionKind::Dynamic, + signature: function_type, + host_data, + }; + Self { + handle: StoreHandle::new(store.objects_mut().as_sys_mut(), vm_function), + } + } /// Creates a new host `Function` from a native function. pub(crate) fn new_typed(store: &mut impl AsStoreMut, func: F) -> Self @@ -251,77 +254,90 @@ impl Function { } } - // TODO: async API - // pub(crate) fn new_typed_async(store: &mut impl AsStoreMut, func: F) -> Self - // where - // Args: WasmTypeList + 'static, - // Rets: WasmTypeList + 'static, - // F: AsyncHostFunction<(), Args, Rets, WithoutEnv> + Send + Sync + 'static, - // { - // let env = FunctionEnv::new(store, ()); - // let signature = FunctionType::new(Args::wasm_types(), Rets::wasm_types()); - // let args_sig = Arc::new(signature.clone()); - // let results_sig = Arc::new(signature.clone()); - // let func = Arc::new(func); - // Self::new_with_env_async(store, &env, signature, move |mut env_mut, values| -> Pin< - // Box, RuntimeError>> + Send>, - // > { - // let store = AsStoreMut::reborrow_mut(&mut env_mut); - // let args_sig = args_sig.clone(); - // let results_sig = results_sig.clone(); - // let func = func.clone(); - // let args = - // match typed_args_from_values::(store, args_sig.as_ref(), values) { - // Ok(args) => args, - // Err(err) => return Box::pin(async { Err(err) }), - // }; - // let future = func - // .as_ref() - // .call_async(AsyncFunctionEnv::new(), args); - // Box::pin(async move { - // let typed_result = future.await?; - // typed_results_to_values::(store, results_sig.as_ref(), typed_result) - // }) - // }) - // } - - // TODO: async API - // pub(crate) fn new_typed_with_env_async( - // store: &mut impl AsStoreMut, - // env: &FunctionEnv, - // func: F, - // ) -> Self - // where - // T: Send + 'static, - // F: AsyncHostFunction + Send + Sync + 'static, - // Args: WasmTypeList + 'static, - // Rets: WasmTypeList + 'static, - // { - // let signature = FunctionType::new(Args::wasm_types(), Rets::wasm_types()); - // let args_sig = Arc::new(signature.clone()); - // let results_sig = Arc::new(signature.clone()); - // let func = Arc::new(func); - // Self::new_with_env_async(store, env, signature, move |mut env_mut, values| -> Pin< - // Box, RuntimeError>> + Send>, - // > { - // let store = AsStoreMut::reborrow_mut(&mut env_mut); - // let args_sig = args_sig.clone(); - // let results_sig = results_sig.clone(); - // let func = func.clone(); - // let args = - // match typed_args_from_values::(store, args_sig.as_ref(), values) { - // Ok(args) => args, - // Err(err) => return Box::pin(async { Err(err) }), - // }; - // let future = func - // .as_ref() - // .call_async(AsyncFunctionEnv::with_env(env_mut), args); - // Box::pin(async move { - // let typed_result = future.await?; - // typed_results_to_values::(store, results_sig.as_ref(), typed_result) - // }) - // }) - // } + pub(crate) fn new_typed_async(store: &mut impl AsStoreMut, func: F) -> Self + where + Args: WasmTypeList + 'static, + Rets: WasmTypeList + Send + 'static, + F: AsyncHostFunction<(), Args, Rets, WithoutEnv> + Send + Sync + 'static, + { + let env = FunctionEnv::new(store, ()); + let signature = FunctionType::new(Args::wasm_types(), Rets::wasm_types()); + let args_sig = Arc::new(signature.clone()); + let results_sig = Arc::new(signature.clone()); + let func = Arc::new(func); + Self::new_with_env_async(store, &env, signature, move |mut env_mut, values| -> Pin< + Box, RuntimeError>> + Send>, + > { + let sys_env = match env_mut.0 { + BackendAsyncFunctionEnvMut::Sys(ref mut sys_env) => sys_env, + _ => panic!("Not a sys backend"), + }; + let mut store_mut_wrapper = unsafe { StoreContext::get_current(sys_env.store_id()) }; + let store_mut = store_mut_wrapper.as_mut(); + let args_sig = args_sig.clone(); + let results_sig = results_sig.clone(); + let func = func.clone(); + let args = + match typed_args_from_values::(store_mut, args_sig.as_ref(), values) { + Ok(args) => args, + Err(err) => return Box::pin(async { Err(err) }), + }; + drop(store_mut_wrapper); + let future = func + .as_ref() + .call_async(AsyncFunctionEnv::new(), args); + Box::pin(async move { + let typed_result = future.await?; + let mut store_mut = env_mut.write().await; + typed_results_to_values::(store_mut.reborrow_mut(), results_sig.as_ref(), typed_result) + }) + }) + } + + pub(crate) fn new_typed_with_env_async( + store: &mut impl AsStoreMut, + env: &FunctionEnv, + func: F, + ) -> Self + where + T: Send + Sync + 'static, + F: AsyncHostFunction + Send + Sync + 'static, + Args: WasmTypeList + 'static, + Rets: WasmTypeList + Send + 'static, + { + let signature = FunctionType::new(Args::wasm_types(), Rets::wasm_types()); + let args_sig = Arc::new(signature.clone()); + let results_sig = Arc::new(signature.clone()); + let func = Arc::new(func); + Self::new_with_env_async(store, env, signature, move |mut env_mut, values| -> Pin< + Box, RuntimeError>> + Send>, + > { + let sys_env = match env_mut.0 { + BackendAsyncFunctionEnvMut::Sys(ref mut sys_env) => sys_env, + _ => panic!("Not a sys backend"), + }; + let mut store_mut_wrapper = unsafe { StoreContext::get_current(sys_env.store_id()) }; + let store_mut = store_mut_wrapper.as_mut(); + let args_sig = args_sig.clone(); + let results_sig = results_sig.clone(); + let func = func.clone(); + let args = + match typed_args_from_values::(store_mut, args_sig.as_ref(), values) { + Ok(args) => args, + Err(err) => return Box::pin(async { Err(err) }), + }; + drop(store_mut_wrapper); + let env_mut_clone = env_mut.as_mut(); + let future = func + .as_ref() + .call_async(AsyncFunctionEnv::with_env(env_mut), args); + Box::pin(async move { + let typed_result = future.await?; + let mut store_mut = env_mut_clone.write().await; + typed_results_to_values::(store_mut.reborrow_mut(), results_sig.as_ref(), typed_result) + }) + }) + } pub(crate) fn new_typed_with_env( store: &mut impl AsStoreMut, @@ -521,10 +537,11 @@ impl Function { pub(crate) fn call_async<'a>( &self, - store: &'a mut (impl AsStoreMut + 'static), + store: &'a impl AsAsyncStore, params: Vec, ) -> Pin, RuntimeError>> + 'a>> { let function = self.clone(); + let store = store.store(); Box::pin(call_function_async(function, store, params)) } diff --git a/lib/api/src/backend/sys/entities/function/typed.rs b/lib/api/src/backend/sys/entities/function/typed.rs index 37930181718..293d2a0990b 100644 --- a/lib/api/src/backend/sys/entities/function/typed.rs +++ b/lib/api/src/backend/sys/entities/function/typed.rs @@ -1,5 +1,5 @@ use crate::backend::sys::engine::NativeEngineExt; -use crate::store::{AsStoreMut, AsStoreRef}; +use crate::store::{AsAsyncStore, AsStoreMut, AsStoreRef}; use crate::{ FromToNativeWasmType, NativeWasmTypeInto, RuntimeError, StoreContext, TypedFunction, Value, WasmTypeList, @@ -116,35 +116,37 @@ macro_rules! impl_native_traits { // Ok(Rets::from_c_struct(results)) } - // TODO: async api - // /// Call the typed func asynchronously. - // #[allow(unused_mut)] - // #[allow(clippy::too_many_arguments)] - // pub fn call_async_sys<'a>( - // &'a self, - // store: &'a mut (impl AsStoreMut + 'static), - // $( $x: $x, )* - // ) -> impl Future> + 'a - // where - // $( $x: FromToNativeWasmType, )* - // { - // let func = self.func.clone(); - // let func_ty = func.ty(store); - // let mut params_raw = [ $( $x.to_native().into_raw(store) ),* ]; - // let mut params_values = Vec::with_capacity(params_raw.len()); - // { - // for (raw, ty) in params_raw.iter().zip(func_ty.params()) { - // unsafe { - // params_values.push(Value::from_raw(store, *ty, *raw)); - // } - // } - // } + /// Call the typed func asynchronously. + #[allow(unused_mut)] + #[allow(clippy::too_many_arguments)] + pub fn call_async_sys<'a>( + &'a self, + store: &'a impl AsAsyncStore, + $( $x: $x, )* + ) -> impl Future> + 'a + where + $( $x: FromToNativeWasmType, )* + { + async move { + let mut write = store.write_lock().await; + let func = self.func.clone(); + let func_ty = func.ty(&mut write); + let mut params_raw = [ $( $x.to_native().into_raw(&mut write) ),* ]; + let mut params_values = Vec::with_capacity(params_raw.len()); + { + for (raw, ty) in params_raw.iter().zip(func_ty.params()) { + unsafe { + params_values.push(Value::from_raw(&mut write, *ty, *raw)); + } + } + } + drop(write); - // async move { - // let results = func.call_async(store, ¶ms_values).await?; - // convert_results::(store, func_ty, results) - // } - // } + let results = func.call_async(store, ¶ms_values).await?; + let mut write = store.write_lock().await; + convert_results::(&mut write, func_ty, results) + } + } #[doc(hidden)] #[allow(missing_docs)] diff --git a/lib/api/src/entities/function/async_host.rs b/lib/api/src/entities/function/async_host.rs index f611ab88c2a..d2cc5d4f0dc 100644 --- a/lib/api/src/entities/function/async_host.rs +++ b/lib/api/src/entities/function/async_host.rs @@ -1,33 +1,33 @@ use std::{future::Future, marker::PhantomData, pin::Pin}; use crate::{ - FunctionEnvMut, HostFunctionKind, RuntimeError, WasmTypeList, WithEnv, WithoutEnv, + AsyncFunctionEnvMut, HostFunctionKind, RuntimeError, WasmTypeList, WithEnv, WithoutEnv, utils::{FromToNativeWasmType, IntoResult}, }; /// Wrapper conveying whether an async host function receives an environment. -pub enum AsyncFunctionEnv<'a, T, Kind> { +pub enum AsyncFunctionEnv { /// Used by host functions without an environment. - WithoutEnv(PhantomData<(&'a T, Kind)>), + WithoutEnv(PhantomData<(T, Kind)>), /// Used by host functions that capture an environment. - WithEnv(FunctionEnvMut<'a, T>), + WithEnv(AsyncFunctionEnvMut), } -impl<'a, T> AsyncFunctionEnv<'a, T, WithoutEnv> { +impl AsyncFunctionEnv { /// Create an environment wrapper for functions without host state. pub fn new() -> Self { Self::WithoutEnv(PhantomData) } } -impl<'a, T> AsyncFunctionEnv<'a, T, WithEnv> { - /// Create an environment wrapper carrying [`FunctionEnvMut`]. - pub fn with_env(env: FunctionEnvMut<'a, T>) -> Self { +impl AsyncFunctionEnv { + /// Create an environment wrapper carrying [`AsyncFunctionEnvMut`]. + pub fn with_env(env: AsyncFunctionEnvMut) -> Self { Self::WithEnv(env) } - /// Extract the underlying [`FunctionEnvMut`]. - pub fn into_env(self) -> FunctionEnvMut<'a, T> { + /// Extract the underlying [`AsyncFunctionEnvMut`]. + pub fn into_env(self) -> AsyncFunctionEnvMut { match self { Self::WithEnv(env) => env, Self::WithoutEnv(_) => unreachable!("with-env async function called without env"), @@ -45,7 +45,7 @@ where /// Invoke the host function asynchronously. fn call_async( &self, - env: AsyncFunctionEnv<'_, T, Kind>, + env: AsyncFunctionEnv, args: Args, ) -> Pin> + Send>>; } @@ -63,7 +63,7 @@ macro_rules! impl_async_host_function_without_env { { fn call_async( &self, - _env: AsyncFunctionEnv<'_, (), WithoutEnv>, + _env: AsyncFunctionEnv<(), WithoutEnv>, args: ( $( $x ),* ), ) -> Pin> + Send>> { #[allow(non_snake_case)] @@ -84,7 +84,7 @@ macro_rules! impl_async_host_function_with_env { impl<$( $x, )* Rets, RetsAsResult, T, F, Fut > AsyncHostFunction for F where T: Send + 'static, - F: Fn(FunctionEnvMut<'_, T>, $( $x ),*) -> Fut + Send + 'static, + F: Fn(AsyncFunctionEnvMut, $( $x ),*) -> Fut + Send + 'static, Fut: Future + Send + 'static, RetsAsResult: IntoResult, Rets: WasmTypeList, @@ -93,7 +93,7 @@ macro_rules! impl_async_host_function_with_env { { fn call_async( &self, - env: AsyncFunctionEnv<'_, T, WithEnv>, + env: AsyncFunctionEnv, args: ( $( $x ),* ), ) -> Pin> + Send>> { #[allow(non_snake_case)] diff --git a/lib/api/src/entities/function/env/inner.rs b/lib/api/src/entities/function/env/inner.rs index a70c64bb636..1923d35dbc1 100644 --- a/lib/api/src/entities/function/env/inner.rs +++ b/lib/api/src/entities/function/env/inner.rs @@ -1,5 +1,5 @@ use crate::{ - AsStoreMut, AsStoreRef, FunctionEnv, FunctionEnvMut, StoreMut, StoreRef, + AsAsyncStore, AsStoreMut, AsStoreRef, FunctionEnv, FunctionEnvMut, StoreMut, StoreRef, macros::backend::match_rt, }; use std::{any::Any, marker::PhantomData}; @@ -204,6 +204,15 @@ impl BackendFunctionEnvMut<'_, T> { f.data_and_store_mut() }) } + + /// Creates an [`AsAsyncStore`] from this [`BackendAsyncFunctionEnvMut`]. + pub fn as_async_store(&mut self) -> impl AsAsyncStore + 'static { + match self { + #[cfg(feature = "sys")] + Self::Sys(f) => f.as_async_store(), + _ => unsupported_async_backend(), + } + } } impl AsStoreRef for BackendFunctionEnvMut<'_, T> { @@ -238,3 +247,179 @@ where }) } } + +/// A temporary handle to a [`FunctionEnv`], suitable for use +/// in async imports. +#[derive(derive_more::From)] +#[non_exhaustive] +pub enum BackendAsyncFunctionEnvMut { + #[cfg(feature = "sys")] + /// The function environment for the `sys` runtime. + Sys(crate::backend::sys::function::env::AsyncFunctionEnvMut), +} + +/// A read-only handle to the [`FunctionEnv`] in an [`AsyncFunctionEnvMut`]. +#[non_exhaustive] +pub enum BackendAsyncFunctionEnvHandle<'a, T> { + #[cfg(feature = "sys")] + /// The function environment handle for the `sys` runtime. + Sys(crate::backend::sys::function::env::AsyncFunctionEnvHandle<'a, T>), +} + +/// A mutable handle to the [`FunctionEnv`] in an [`AsyncFunctionEnvMut`]. +/// Internally, a [`StoreMutGuard`] is used, so the store handle from this +/// type can be used to invoke [`Function::call`](crate::Function::call) +/// while outside a store's context. +#[non_exhaustive] +pub enum BackendAsyncFunctionEnvHandleMut<'a, T> { + #[cfg(feature = "sys")] + /// The function environment handle for the `sys` runtime. + Sys(crate::backend::sys::function::env::AsyncFunctionEnvHandleMut<'a, T>), +} + +impl BackendAsyncFunctionEnvMut { + /// Waits for a store lock and returns a read-only handle to the + /// function environment. + pub async fn read<'a>(&'a self) -> BackendAsyncFunctionEnvHandle<'a, T> { + match self { + #[cfg(feature = "sys")] + Self::Sys(f) => BackendAsyncFunctionEnvHandle::Sys(f.read().await), + _ => unsupported_async_backend(), + } + } + + /// Waits for a store lock and returns a mutable handle to the + /// function environment. + pub async fn write<'a>(&'a self) -> BackendAsyncFunctionEnvHandleMut<'a, T> { + match self { + #[cfg(feature = "sys")] + Self::Sys(f) => BackendAsyncFunctionEnvHandleMut::Sys(f.write().await), + _ => unsupported_async_backend(), + } + } + + /// Borrows a new immmutable reference + pub fn as_ref(&self) -> BackendFunctionEnv { + match self { + #[cfg(feature = "sys")] + Self::Sys(f) => BackendFunctionEnv::Sys(f.as_ref()).into(), + _ => unsupported_async_backend(), + } + } + + /// Borrows a new mutable reference + pub fn as_mut(&mut self) -> BackendAsyncFunctionEnvMut { + match self { + #[cfg(feature = "sys")] + Self::Sys(f) => BackendAsyncFunctionEnvMut::Sys(f.as_mut()), + _ => unsupported_async_backend(), + } + } + + /// Creates an [`AsAsyncStore`] from this [`BackendAsyncFunctionEnvMut`]. + pub fn as_async_store(&mut self) -> impl AsAsyncStore + 'static { + match self { + #[cfg(feature = "sys")] + Self::Sys(f) => f.as_async_store(), + _ => unsupported_async_backend(), + } + } +} + +impl BackendAsyncFunctionEnvHandle<'_, T> { + /// Returns a reference to the host state in this function environment. + pub fn data(&self) -> &T { + match self { + #[cfg(feature = "sys")] + Self::Sys(f) => f.data(), + _ => unsupported_async_backend(), + } + } + + /// Returns both the host state and the attached StoreRef + pub fn data_and_store(&self) -> (&T, &impl AsStoreRef) { + match self { + #[cfg(feature = "sys")] + Self::Sys(f) => f.data_and_store(), + _ => unsupported_async_backend(), + } + } +} + +impl AsStoreRef for BackendAsyncFunctionEnvHandle<'_, T> { + fn as_ref(&self) -> &crate::StoreInner { + match self { + #[cfg(feature = "sys")] + Self::Sys(f) => AsStoreRef::as_ref(f), + _ => unsupported_async_backend(), + } + } +} + +impl BackendAsyncFunctionEnvHandleMut<'_, T> { + /// Returns a mutable reference to the host state in this function environment. + pub fn data_mut(&mut self) -> &mut T { + match self { + #[cfg(feature = "sys")] + Self::Sys(f) => f.data_mut(), + _ => unsupported_async_backend(), + } + } + + /// Returns both the host state and the attached StoreMut + pub fn data_and_store_mut(&mut self) -> (&mut T, &mut impl AsStoreMut) { + match self { + #[cfg(feature = "sys")] + Self::Sys(f) => f.data_and_store_mut(), + _ => unsupported_async_backend(), + } + } +} + +impl AsStoreRef for BackendAsyncFunctionEnvHandleMut<'_, T> { + fn as_ref(&self) -> &crate::StoreInner { + match self { + #[cfg(feature = "sys")] + Self::Sys(f) => AsStoreRef::as_ref(f), + _ => unsupported_async_backend(), + } + } +} + +impl AsStoreMut for BackendAsyncFunctionEnvHandleMut<'_, T> { + fn as_mut(&mut self) -> &mut crate::StoreInner { + match self { + #[cfg(feature = "sys")] + Self::Sys(f) => AsStoreMut::as_mut(f), + _ => unsupported_async_backend(), + } + } + + fn reborrow_mut(&mut self) -> &mut StoreMut { + match self { + #[cfg(feature = "sys")] + Self::Sys(f) => f.reborrow_mut(), + _ => unsupported_async_backend(), + } + } + + fn take(&mut self) -> Option { + match self { + #[cfg(feature = "sys")] + Self::Sys(f) => f.take(), + _ => unsupported_async_backend(), + } + } + + fn put_back(&mut self, store_mut: StoreMut) { + match self { + #[cfg(feature = "sys")] + Self::Sys(f) => f.put_back(store_mut), + _ => unsupported_async_backend(), + } + } +} + +fn unsupported_async_backend() -> ! { + panic!("async functions are only supported with the `sys` backend"); +} diff --git a/lib/api/src/entities/function/env/mod.rs b/lib/api/src/entities/function/env/mod.rs index 9856070fee3..c9123851105 100644 --- a/lib/api/src/entities/function/env/mod.rs +++ b/lib/api/src/entities/function/env/mod.rs @@ -1,7 +1,7 @@ pub(crate) mod inner; pub(crate) use inner::*; -use crate::{AsStoreMut, AsStoreRef, StoreMut, StoreRef, macros::backend::match_rt}; +use crate::{AsAsyncStore, AsStoreMut, AsStoreRef, StoreMut, StoreRef, macros::backend::match_rt}; use std::{any::Any, fmt::Debug, marker::PhantomData}; #[derive(Debug, derive_more::From)] @@ -88,6 +88,11 @@ impl FunctionEnvMut<'_, T> { pub fn data_and_store_mut(&mut self) -> (&mut T, &mut StoreMut) { self.0.data_and_store_mut() } + + /// Creates an [`AsAsyncStore`] from this [`FunctionEnvMut`]. + pub fn as_async_store(&mut self) -> impl AsAsyncStore + 'static { + self.0.as_async_store() + } } impl AsStoreRef for FunctionEnvMut<'_, T> { @@ -114,3 +119,99 @@ where self.0.fmt(f) } } + +/// A temporary handle to a [`FunctionEnv`], suitable for use +/// in async imports. +pub struct AsyncFunctionEnvMut(pub(crate) BackendAsyncFunctionEnvMut); + +/// A read-only handle to the [`FunctionEnv`] in an [`AsyncFunctionEnvMut`]. +pub struct AsyncFunctionEnvHandle<'a, T>(pub(crate) BackendAsyncFunctionEnvHandle<'a, T>); + +/// A mutable handle to the [`FunctionEnv`] in an [`AsyncFunctionEnvMut`]. +/// Internally, a [`StoreMutGuard`] is used, so the store handle from this +/// type can be used to invoke [`Function::call`](crate::Function::call) +/// while outside a store's context. +pub struct AsyncFunctionEnvHandleMut<'a, T>(pub(crate) BackendAsyncFunctionEnvHandleMut<'a, T>); + +impl AsyncFunctionEnvMut { + /// Waits for a store lock and returns a read-only handle to the + /// function environment. + pub async fn read<'a>(&'a self) -> AsyncFunctionEnvHandle<'a, T> { + AsyncFunctionEnvHandle(self.0.read().await) + } + + /// Waits for a store lock and returns a mutable handle to the + /// function environment. + pub async fn write<'a>(&'a self) -> AsyncFunctionEnvHandleMut<'a, T> { + AsyncFunctionEnvHandleMut(self.0.write().await) + } + + /// Borrows a new immmutable reference + pub fn as_ref(&self) -> FunctionEnv { + FunctionEnv(self.0.as_ref()) + } + + /// Borrows a new mutable reference + pub fn as_mut(&mut self) -> AsyncFunctionEnvMut { + AsyncFunctionEnvMut(self.0.as_mut()) + } + + /// Creates an [`AsAsyncStore`] from this [`AsyncFunctionEnvMut`]. + pub fn as_async_store(&mut self) -> impl AsAsyncStore + 'static { + self.0.as_async_store() + } +} + +impl AsyncFunctionEnvHandle<'_, T> { + /// Returns a reference to the host state in this function environment. + pub fn data(&self) -> &T { + self.0.data() + } + + /// Returns both the host state and the attached StoreRef + pub fn data_and_store(&self) -> (&T, &impl AsStoreRef) { + self.0.data_and_store() + } +} + +impl AsStoreRef for AsyncFunctionEnvHandle<'_, T> { + fn as_ref(&self) -> &crate::StoreInner { + AsStoreRef::as_ref(&self.0) + } +} + +impl AsyncFunctionEnvHandleMut<'_, T> { + /// Returns a mutable reference to the host state in this function environment. + pub fn data_mut(&mut self) -> &mut T { + self.0.data_mut() + } + + /// Returns both the host state and the attached StoreMut + pub fn data_and_store_mut(&mut self) -> (&mut T, &mut impl AsStoreMut) { + self.0.data_and_store_mut() + } +} + +impl AsStoreRef for AsyncFunctionEnvHandleMut<'_, T> { + fn as_ref(&self) -> &crate::StoreInner { + AsStoreRef::as_ref(&self.0) + } +} + +impl AsStoreMut for AsyncFunctionEnvHandleMut<'_, T> { + fn as_mut(&mut self) -> &mut crate::StoreInner { + AsStoreMut::as_mut(&mut self.0) + } + + fn reborrow_mut(&mut self) -> &mut StoreMut { + AsStoreMut::reborrow_mut(&mut self.0) + } + + fn take(&mut self) -> Option { + AsStoreMut::take(&mut self.0) + } + + fn put_back(&mut self, store_mut: StoreMut) { + AsStoreMut::put_back(&mut self.0, store_mut); + } +} diff --git a/lib/api/src/entities/function/inner.rs b/lib/api/src/entities/function/inner.rs index 7bdf4c60768..0a720d8cd54 100644 --- a/lib/api/src/entities/function/inner.rs +++ b/lib/api/src/entities/function/inner.rs @@ -3,8 +3,9 @@ use std::pin::Pin; use wasmer_types::{FunctionType, RawValue}; use crate::{ - AsStoreMut, AsStoreRef, ExportError, Exportable, Extern, FunctionEnv, FunctionEnvMut, - HostFunction, StoreMut, StoreRef, TypedFunction, Value, WasmTypeList, WithEnv, WithoutEnv, + AsAsyncStore, AsStoreMut, AsStoreRef, AsyncFunctionEnvMut, ExportError, Exportable, Extern, + FunctionEnv, FunctionEnvMut, HostFunction, StoreMut, StoreRef, TypedFunction, Value, + WasmTypeList, WithEnv, WithoutEnv, entities::function::async_host::AsyncHostFunction, error::RuntimeError, macros::backend::{gen_rt_ty, match_rt}, @@ -253,119 +254,118 @@ impl BackendFunction { } } - // TODO: async API - // #[inline] - // pub fn new_async(store: &mut impl AsStoreMut, ty: FT, func: F) -> Self - // where - // FT: Into, - // F: Fn(&[Value]) -> Fut + 'static + Send + Sync, - // Fut: Future, RuntimeError>> + 'static + Send, - // { - // match &store.as_mut().store { - // #[cfg(feature = "sys")] - // crate::BackendStore::Sys(_) => Self::Sys( - // crate::backend::sys::entities::function::Function::new_async(store, ty, func), - // ), - // #[cfg(feature = "wamr")] - // crate::BackendStore::Wamr(_) => unsupported_async_backend("wamr"), - // #[cfg(feature = "wasmi")] - // crate::BackendStore::Wasmi(_) => unsupported_async_backend("wasmi"), - // #[cfg(feature = "v8")] - // crate::BackendStore::V8(_) => unsupported_async_backend("v8"), - // #[cfg(feature = "js")] - // crate::BackendStore::Js(_) => unsupported_async_backend("js"), - // #[cfg(feature = "jsc")] - // crate::BackendStore::Jsc(_) => unsupported_async_backend("jsc"), - // } - // } - - // #[inline] - // pub fn new_with_env_async( - // store: &mut impl AsStoreMut, - // env: &FunctionEnv, - // ty: FT, - // func: F, - // ) -> Self - // where - // FT: Into, - // F: Fn(FunctionEnvMut, &[Value]) -> Fut + 'static + Send + Sync, - // Fut: Future, RuntimeError>> + 'static + Send, - // { - // match &store.as_mut().store { - // #[cfg(feature = "sys")] - // crate::BackendStore::Sys(_) => Self::Sys( - // crate::backend::sys::entities::function::Function::new_with_env_async( - // store, env, ty, func, - // ), - // ), - // #[cfg(feature = "wamr")] - // crate::BackendStore::Wamr(_) => unsupported_async_backend("wamr"), - // #[cfg(feature = "wasmi")] - // crate::BackendStore::Wasmi(_) => unsupported_async_backend("wasmi"), - // #[cfg(feature = "v8")] - // crate::BackendStore::V8(_) => unsupported_async_backend("v8"), - // #[cfg(feature = "js")] - // crate::BackendStore::Js(_) => unsupported_async_backend("js"), - // #[cfg(feature = "jsc")] - // crate::BackendStore::Jsc(_) => unsupported_async_backend("jsc"), - // } - // } - - // #[inline] - // pub fn new_typed_async(store: &mut impl AsStoreMut, func: F) -> Self - // where - // F: AsyncHostFunction<(), Args, Rets, WithoutEnv> + Send + Sync + 'static, - // Args: WasmTypeList + 'static, - // Rets: WasmTypeList + 'static, - // { - // match &store.as_mut().store { - // #[cfg(feature = "sys")] - // crate::BackendStore::Sys(_) => Self::Sys( - // crate::backend::sys::entities::function::Function::new_typed_async(store, func), - // ), - // #[cfg(feature = "wamr")] - // crate::BackendStore::Wamr(_) => unsupported_async_backend("wamr"), - // #[cfg(feature = "wasmi")] - // crate::BackendStore::Wasmi(_) => unsupported_async_backend("wasmi"), - // #[cfg(feature = "v8")] - // crate::BackendStore::V8(_) => unsupported_async_backend("v8"), - // #[cfg(feature = "js")] - // crate::BackendStore::Js(_) => unsupported_async_backend("js"), - // #[cfg(feature = "jsc")] - // crate::BackendStore::Jsc(_) => unsupported_async_backend("jsc"), - // } - // } - - // #[inline] - // pub fn new_typed_with_env_async( - // store: &mut impl AsStoreMut, - // env: &FunctionEnv, - // func: F, - // ) -> Self - // where - // F: AsyncHostFunction + Send + Sync + 'static, - // Args: WasmTypeList + 'static, - // Rets: WasmTypeList + 'static, - // { - // match &store.as_mut().store { - // #[cfg(feature = "sys")] - // crate::BackendStore::Sys(_) => Self::Sys( - // crate::backend::sys::entities::function::Function::new_typed_with_env_async( - // store, env, func, - // ), - // ), - // #[cfg(feature = "wamr")] - // crate::BackendStore::Wamr(_) => unsupported_async_backend("wamr"), - // #[cfg(feature = "wasmi")] - // crate::BackendStore::Wasmi(_) => unsupported_async_backend("wasmi"), - // #[cfg(feature = "v8")] - // crate::BackendStore::V8(_) => unsupported_async_backend("v8"), - // #[cfg(feature = "js")] - // crate::BackendStore::Js(_) => unsupported_async_backend("js"), - // #[cfg(feature = "jsc")] - // crate::BackendStore::Jsc(_) => unsupported_async_backend("jsc"), - // } - // } + #[inline] + pub fn new_async(store: &mut impl AsStoreMut, ty: FT, func: F) -> Self + where + FT: Into, + F: Fn(&[Value]) -> Fut + 'static + Send + Sync, + Fut: Future, RuntimeError>> + 'static + Send, + { + match &store.as_mut().store { + #[cfg(feature = "sys")] + crate::BackendStore::Sys(_) => Self::Sys( + crate::backend::sys::entities::function::Function::new_async(store, ty, func), + ), + #[cfg(feature = "wamr")] + crate::BackendStore::Wamr(_) => unsupported_async_backend("wamr"), + #[cfg(feature = "wasmi")] + crate::BackendStore::Wasmi(_) => unsupported_async_backend("wasmi"), + #[cfg(feature = "v8")] + crate::BackendStore::V8(_) => unsupported_async_backend("v8"), + #[cfg(feature = "js")] + crate::BackendStore::Js(_) => unsupported_async_backend("js"), + #[cfg(feature = "jsc")] + crate::BackendStore::Jsc(_) => unsupported_async_backend("jsc"), + } + } + + #[inline] + pub fn new_with_env_async( + store: &mut impl AsStoreMut, + env: &FunctionEnv, + ty: FT, + func: F, + ) -> Self + where + FT: Into, + F: Fn(AsyncFunctionEnvMut, &[Value]) -> Fut + 'static + Send + Sync, + Fut: Future, RuntimeError>> + 'static + Send, + { + match &store.as_mut().store { + #[cfg(feature = "sys")] + crate::BackendStore::Sys(_) => Self::Sys( + crate::backend::sys::entities::function::Function::new_with_env_async( + store, env, ty, func, + ), + ), + #[cfg(feature = "wamr")] + crate::BackendStore::Wamr(_) => unsupported_async_backend("wamr"), + #[cfg(feature = "wasmi")] + crate::BackendStore::Wasmi(_) => unsupported_async_backend("wasmi"), + #[cfg(feature = "v8")] + crate::BackendStore::V8(_) => unsupported_async_backend("v8"), + #[cfg(feature = "js")] + crate::BackendStore::Js(_) => unsupported_async_backend("js"), + #[cfg(feature = "jsc")] + crate::BackendStore::Jsc(_) => unsupported_async_backend("jsc"), + } + } + + #[inline] + pub fn new_typed_async(store: &mut impl AsStoreMut, func: F) -> Self + where + F: AsyncHostFunction<(), Args, Rets, WithoutEnv> + Send + Sync + 'static, + Args: WasmTypeList + 'static, + Rets: WasmTypeList + Send + 'static, + { + match &store.as_mut().store { + #[cfg(feature = "sys")] + crate::BackendStore::Sys(_) => Self::Sys( + crate::backend::sys::entities::function::Function::new_typed_async(store, func), + ), + #[cfg(feature = "wamr")] + crate::BackendStore::Wamr(_) => unsupported_async_backend("wamr"), + #[cfg(feature = "wasmi")] + crate::BackendStore::Wasmi(_) => unsupported_async_backend("wasmi"), + #[cfg(feature = "v8")] + crate::BackendStore::V8(_) => unsupported_async_backend("v8"), + #[cfg(feature = "js")] + crate::BackendStore::Js(_) => unsupported_async_backend("js"), + #[cfg(feature = "jsc")] + crate::BackendStore::Jsc(_) => unsupported_async_backend("jsc"), + } + } + + #[inline] + pub fn new_typed_with_env_async( + store: &mut impl AsStoreMut, + env: &FunctionEnv, + func: F, + ) -> Self + where + F: AsyncHostFunction + Send + Sync + 'static, + Args: WasmTypeList + 'static, + Rets: WasmTypeList + Send + 'static, + { + match &store.as_mut().store { + #[cfg(feature = "sys")] + crate::BackendStore::Sys(_) => Self::Sys( + crate::backend::sys::entities::function::Function::new_typed_with_env_async( + store, env, func, + ), + ), + #[cfg(feature = "wamr")] + crate::BackendStore::Wamr(_) => unsupported_async_backend("wamr"), + #[cfg(feature = "wasmi")] + crate::BackendStore::Wasmi(_) => unsupported_async_backend("wasmi"), + #[cfg(feature = "v8")] + crate::BackendStore::V8(_) => unsupported_async_backend("v8"), + #[cfg(feature = "js")] + crate::BackendStore::Js(_) => unsupported_async_backend("js"), + #[cfg(feature = "jsc")] + crate::BackendStore::Jsc(_) => unsupported_async_backend("jsc"), + } + } /// Returns the [`FunctionType`] of the `Function`. /// @@ -497,7 +497,7 @@ impl BackendFunction { pub fn call_async<'a>( &'a self, - store: &'a mut (impl AsStoreMut + 'static), + store: &'a impl AsAsyncStore, params: Vec, ) -> Pin, RuntimeError>> + 'a>> { match self { @@ -760,7 +760,7 @@ fn unsupported_async_backend(backend: &str) -> ! { ) } -fn unsupported_async_future<'a>() +pub(super) fn unsupported_async_future<'a>() -> Pin, RuntimeError>> + 'a>> { Box::pin(async { Err(RuntimeError::new( diff --git a/lib/api/src/entities/function/mod.rs b/lib/api/src/entities/function/mod.rs index 80900aa185a..ef9d7ddfd58 100644 --- a/lib/api/src/entities/function/mod.rs +++ b/lib/api/src/entities/function/mod.rs @@ -18,8 +18,8 @@ use std::{future::Future, pin::Pin}; use wasmer_types::{FunctionType, RawValue}; use crate::{ - AsStoreMut, AsStoreRef, ExportError, Exportable, Extern, StoreMut, StoreRef, TypedFunction, - Value, WasmTypeList, + AsAsyncStore, AsStoreMut, AsStoreRef, ExportError, Exportable, Extern, StoreMut, StoreRef, + TypedFunction, Value, WasmTypeList, error::RuntimeError, vm::{VMExtern, VMExternFunction, VMFuncRef}, }; @@ -158,64 +158,63 @@ impl Function { Self(BackendFunction::new_typed_with_env(store, env, func)) } - // TODO: async API - // /// Creates a new async host `Function` (dynamic) with the provided signature. - // /// - // /// The provided closure returns a future that resolves to the function results. - // /// When invoked synchronously (via [`Function::call`]) the future will run to - // /// completion immediately, provided it doesn't suspend. When invoked through - // /// [`Function::call_async`], the future may suspend and resume as needed. - // pub fn new_async(store: &mut impl AsStoreMut, ty: FT, func: F) -> Self - // where - // FT: Into, - // F: Fn(&[Value]) -> Fut + 'static + Send + Sync, - // Fut: Future, RuntimeError>> + 'static + Send, - // { - // Self(BackendFunction::new_async(store, ty, func)) - // } + /// Creates a new async host `Function` (dynamic) with the provided signature. + /// + /// The provided closure returns a future that resolves to the function results. + /// When invoked synchronously (via [`Function::call`]) the future will run to + /// completion immediately, provided it doesn't suspend. When invoked through + /// [`Function::call_async`], the future may suspend and resume as needed. + pub fn new_async(store: &mut impl AsStoreMut, ty: FT, func: F) -> Self + where + FT: Into, + F: Fn(&[Value]) -> Fut + 'static + Send + Sync, + Fut: Future, RuntimeError>> + 'static + Send, + { + Self(BackendFunction::new_async(store, ty, func)) + } - // /// Creates a new async host `Function` (dynamic) with the provided signature - // /// and environment. - // pub fn new_with_env_async( - // store: &mut impl AsStoreMut, - // env: &FunctionEnv, - // ty: FT, - // func: F, - // ) -> Self - // where - // FT: Into, - // F: Fn(FunctionEnvMut, &[Value]) -> Fut + 'static + Send + Sync, - // Fut: Future, RuntimeError>> + 'static + Send, - // { - // Self(BackendFunction::new_with_env_async(store, env, ty, func)) - // } + /// Creates a new async host `Function` (dynamic) with the provided signature + /// and environment. + pub fn new_with_env_async( + store: &mut impl AsStoreMut, + env: &FunctionEnv, + ty: FT, + func: F, + ) -> Self + where + FT: Into, + F: Fn(AsyncFunctionEnvMut, &[Value]) -> Fut + 'static + Send + Sync, + Fut: Future, RuntimeError>> + 'static + Send, + { + Self(BackendFunction::new_with_env_async(store, env, ty, func)) + } - // /// Creates a new async host `Function` from a native typed function. - // /// - // /// The future can return either the raw result tuple or any type that implements - // /// [`IntoResult`] for the result tuple (e.g. `Result(store: &mut impl AsStoreMut, func: F) -> Self - // where - // Rets: WasmTypeList + 'static, - // Args: WasmTypeList + 'static, - // F: AsyncHostFunction<(), Args, Rets, WithoutEnv> + Send + Sync + 'static, - // { - // Self(BackendFunction::new_typed_async(store, func)) - // } + /// Creates a new async host `Function` from a native typed function. + /// + /// The future can return either the raw result tuple or any type that implements + /// [`IntoResult`] for the result tuple (e.g. `Result(store: &mut impl AsStoreMut, func: F) -> Self + where + Rets: WasmTypeList + Send + 'static, + Args: WasmTypeList + 'static, + F: AsyncHostFunction<(), Args, Rets, WithoutEnv> + Send + Sync + 'static, + { + Self(BackendFunction::new_typed_async(store, func)) + } - // /// Creates a new async host `Function` with an environment from a typed function. - // pub fn new_typed_with_env_async( - // store: &mut impl AsStoreMut, - // env: &FunctionEnv, - // func: F, - // ) -> Self - // where - // Rets: WasmTypeList + 'static, - // Args: WasmTypeList + 'static, - // F: AsyncHostFunction + Send + Sync + 'static, - // { - // Self(BackendFunction::new_typed_with_env_async(store, env, func)) - // } + /// Creates a new async host `Function` with an environment from a typed function. + pub fn new_typed_with_env_async( + store: &mut impl AsStoreMut, + env: &FunctionEnv, + func: F, + ) -> Self + where + Rets: WasmTypeList + Send + 'static, + Args: WasmTypeList + 'static, + F: AsyncHostFunction + Send + Sync + 'static, + { + Self(BackendFunction::new_typed_with_env_async(store, env, func)) + } /// Returns the [`FunctionType`] of the `Function`. /// @@ -324,21 +323,20 @@ impl Function { self.0.call(store, params) } - // TODO: async API - // /// Calls the function asynchronously. - // /// - // /// The returned future drives execution of the WebAssembly function on a - // /// coroutine stack. Host functions created with [`Function::new_async`] may - // /// suspend execution by awaiting futures, and their completion will resume - // /// the Wasm instance according to the JSPI proposal. - // pub fn call_async<'a>( - // &'a self, - // store: &'a mut (impl AsStoreMut + 'static), - // params: &'a [Value], - // ) -> impl Future, RuntimeError>> + 'a { - // let params_vec = params.to_vec(); - // self.0.call_async(store, params_vec) - // } + /// Calls the function asynchronously. + /// + /// The returned future drives execution of the WebAssembly function on a + /// coroutine stack. Host functions created with [`Function::new_async`] may + /// suspend execution by awaiting futures, and their completion will resume + /// the Wasm instance according to the JSPI proposal. + pub fn call_async<'a>( + &'a self, + store: &'a impl AsAsyncStore, + params: &'a [Value], + ) -> impl Future, RuntimeError>> + 'a { + let params_vec = params.to_vec(); + self.0.call_async(store, params_vec) + } #[doc(hidden)] #[allow(missing_docs)] diff --git a/lib/api/src/entities/store/asynk.rs b/lib/api/src/entities/store/asynk.rs new file mode 100644 index 00000000000..0e8f181d8a9 --- /dev/null +++ b/lib/api/src/entities/store/asynk.rs @@ -0,0 +1,163 @@ +use std::marker::PhantomData; + +use crate::{AsStoreMut, AsStoreRef, Store, StoreContext, StoreMut, StoreMutWrapper, StoreRef}; + +use wasmer_types::StoreId; + +/// A trait for types that can be used with +/// [`Function::call_async`](crate::Function::call_async). +/// +/// Note that, while this trait can easily be implemented for a lot +/// of types (including [`StoreMut`]), implementations are left +/// out on purpose to help avoid common deadlock scenarios. +pub trait AsAsyncStore { + /// Returns a reference to the inner store. + fn store_ref(&self) -> &Store; + + /// Returns a copy of the store. + fn store(&self) -> Store { + let store = self.store_ref(); + Store { + id: store.id, + inner: store.inner.clone(), + } + } + + /// Returns the store id. + fn store_id(&self) -> StoreId { + self.store().id + } + + /// Acquires a read lock on the store. + fn read_lock<'a>(&'a self) -> impl Future> + Send + Sync + 'a { + AsyncStoreReadLock::acquire(self.store_ref()) + } + + /// Acquires a write lock on the store. + fn write_lock<'a>( + &'a self, + ) -> impl Future> + Send + Sync + 'a { + AsyncStoreWriteLock::acquire(self.store_ref()) + } +} + +impl AsAsyncStore for Store { + fn store_ref(&self) -> &Store { + self + } +} +pub(crate) enum AsyncStoreReadLockInner { + Owned(StoreRef), + FromStoreContext(StoreMutWrapper), +} + +/// A read lock on a store that can be used in concurrent contexts; +/// mostly useful in conjunction with [`AsAsyncStore`]. +pub struct AsyncStoreReadLock<'a> { + pub(crate) inner: AsyncStoreReadLockInner, + _marker: PhantomData<&'a ()>, +} + +impl<'a> AsyncStoreReadLock<'a> { + pub(crate) async fn acquire(store: &'a Store) -> Self { + let store_context = unsafe { StoreContext::try_get_current(store.id) }; + match store_context { + Some(store_mut_wrapper) => Self { + inner: AsyncStoreReadLockInner::FromStoreContext(store_mut_wrapper), + _marker: PhantomData, + }, + None => { + // Drop the option before awaiting, since the value isn't Send + drop(store_context); + let store_ref = store.make_ref_async().await; + Self { + inner: AsyncStoreReadLockInner::Owned(store_ref), + _marker: PhantomData, + } + } + } + } +} + +impl AsStoreRef for AsyncStoreReadLock<'_> { + fn as_ref(&self) -> &crate::StoreInner { + match &self.inner { + AsyncStoreReadLockInner::Owned(guard) => guard.as_ref(), + AsyncStoreReadLockInner::FromStoreContext(wrapper) => wrapper.as_ref().as_ref(), + } + } +} + +pub(crate) enum AsyncStoreWriteLockInner { + Owned(StoreMut), + FromStoreContext(StoreMutWrapper), +} + +/// A write lock on a store that can be used in concurrent contexts; +/// mostly useful in conjunction with [`AsAsyncStore`]. +pub struct AsyncStoreWriteLock<'a> { + pub(crate) inner: AsyncStoreWriteLockInner, + _marker: PhantomData<&'a ()>, +} + +impl<'a> AsyncStoreWriteLock<'a> { + pub(crate) async fn acquire(store: &'a Store) -> Self { + let store_context = unsafe { StoreContext::try_get_current(store.id) }; + match store_context { + Some(store_mut_wrapper) => Self { + inner: AsyncStoreWriteLockInner::FromStoreContext(store_mut_wrapper), + _marker: PhantomData, + }, + None => { + // Drop the option before awaiting, since the value isn't Send + drop(store_context); + let store_mut = store.make_mut_async().await; + Self { + inner: AsyncStoreWriteLockInner::Owned(store_mut), + _marker: PhantomData, + } + } + } + } +} + +impl AsStoreRef for AsyncStoreWriteLock<'_> { + fn as_ref(&self) -> &crate::StoreInner { + match &self.inner { + AsyncStoreWriteLockInner::Owned(guard) => guard.as_ref(), + AsyncStoreWriteLockInner::FromStoreContext(wrapper) => wrapper.as_ref().as_ref(), + } + } +} + +impl AsStoreMut for AsyncStoreWriteLock<'_> { + fn as_mut(&mut self) -> &mut crate::StoreInner { + match &mut self.inner { + AsyncStoreWriteLockInner::Owned(guard) => guard.as_mut(), + AsyncStoreWriteLockInner::FromStoreContext(wrapper) => wrapper.as_mut().as_mut(), + } + } + + fn reborrow_mut(&mut self) -> &mut StoreMut { + match &mut self.inner { + AsyncStoreWriteLockInner::Owned(guard) => guard.reborrow_mut(), + AsyncStoreWriteLockInner::FromStoreContext(wrapper) => wrapper.as_mut(), + } + } + + fn take(&mut self) -> Option { + match &mut self.inner { + AsyncStoreWriteLockInner::Owned(guard) => guard.take(), + AsyncStoreWriteLockInner::FromStoreContext(wrapper) => wrapper.as_mut().take(), + } + } + + fn put_back(&mut self, store_mut: StoreMut) { + match &mut self.inner { + AsyncStoreWriteLockInner::Owned(guard) => guard.put_back(store_mut), + AsyncStoreWriteLockInner::FromStoreContext(wrapper) => { + wrapper.as_mut().put_back(store_mut) + } + } + } +} diff --git a/lib/api/src/entities/store/context.rs b/lib/api/src/entities/store/context.rs index 296a2092b41..92716320b40 100644 --- a/lib/api/src/entities/store/context.rs +++ b/lib/api/src/entities/store/context.rs @@ -54,6 +54,10 @@ pub(crate) struct StoreMutWrapper { store_mut: *mut StoreMut, } +pub(crate) struct ForcedStoreInstallGuard { + store_id: Option, +} + pub(crate) enum StoreInstallGuard<'a> { Installed { store_id: StoreId, @@ -96,9 +100,22 @@ impl StoreContext { }) } + /// Install the given [`StoreMut`] as the current store context. + /// This function can only be used when the caller has a [`StoreMut`], + /// which itself is proof that the store is not already active. + pub(crate) fn force_install(store_mut: StoreMut) -> ForcedStoreInstallGuard { + let id = store_mut.objects().id(); + Self::install(store_mut); + ForcedStoreInstallGuard { store_id: Some(id) } + } + /// Ensure that a store context with the given id is installed. /// Returns true if the [`StoreMut`] was taken out of the provided /// [`AsStoreMut`] and installed, false if it was already active. + /// This function takes care of the problem of initial + /// [`Function::call`](crate::Function::call) needing to install the + /// store context vs nested calls having only a reference to the + /// store and needing to reuse the existing context. pub(crate) fn ensure_installed<'a>( store_mut: &'a mut impl AsStoreMut, ) -> StoreInstallGuard<'a> { @@ -155,9 +172,46 @@ impl StoreContext { } }) } + + /// Safety: In addition to the safety requirements of [`Self::get_current`], + /// the pointer returned from this function will become invalid if + /// the store context is changed in any way (via installing or uninstalling + /// a store context). The caller must ensure that the store context + /// remains unchanged for the entire lifetime of the returned reference. + pub(crate) unsafe fn get_current_transient(id: StoreId) -> *mut StoreMut { + STORE_CONTEXT_STACK.with(|cell| { + let mut stack = cell.borrow_mut(); + let top = stack + .last_mut() + .expect("No store context installed on this thread"); + assert_eq!(top.id, id, "Mismatched store context access"); + unsafe { top.store_mut.get() } + }) + } + + /// Safety: See [`get_current`]. + pub(crate) unsafe fn try_get_current(id: StoreId) -> Option { + STORE_CONTEXT_STACK.with(|cell| { + let mut stack = cell.borrow_mut(); + let top = stack.last_mut()?; + if top.id != id { + return None; + } + top.borrow_count += 1; + Some(StoreMutWrapper { + store_mut: top.store_mut.get(), + }) + }) + } } impl StoreMutWrapper { + pub(crate) fn as_ref(&self) -> &StoreMut { + // Safety: the store_mut is always initialized unless the StoreMutWrapper + // is dropped, at which point it's impossible to call this function + unsafe { self.store_mut.as_ref().unwrap() } + } + pub(crate) fn as_mut(&mut self) -> &mut StoreMut { // Safety: the store_mut is always initialized unless the StoreMutWrapper // is dropped, at which point it's impossible to call this function @@ -199,3 +253,37 @@ impl Drop for StoreInstallGuard<'_> { } } } + +impl ForcedStoreInstallGuard { + // Need to do this via mutable ref for the Drop impl + fn uninstall_by_ref(&mut self) -> StoreMut { + if let Some(store_id) = self.store_id.take() { + STORE_CONTEXT_STACK.with(|cell| { + let mut stack = cell.borrow_mut(); + let top = stack.pop().expect("Store context stack underflow"); + assert_eq!(top.id, store_id, "Mismatched store context uninstall"); + assert_eq!( + top.borrow_count, 0, + "Cannot uninstall store context while it is still borrowed" + ); + top.store_mut.into_inner() + }) + } else { + unreachable!("ForcedStoreInstallGuard already uninstalled") + } + } + + // However, the public API will take self by value to + // prevent double-uninstalling + pub(crate) fn uninstall(mut self) -> StoreMut { + self.uninstall_by_ref() + } +} + +impl Drop for ForcedStoreInstallGuard { + fn drop(&mut self) { + if self.store_id.is_some() { + self.uninstall_by_ref(); + } + } +} diff --git a/lib/api/src/entities/store/inner.rs b/lib/api/src/entities/store/inner.rs index 56d52806f9c..fe9bf5c2c53 100644 --- a/lib/api/src/entities/store/inner.rs +++ b/lib/api/src/entities/store/inner.rs @@ -20,6 +20,10 @@ pub struct StoreInner { pub(crate) on_called: Option, } +// TODO: async api - figure this out! +unsafe impl Send for StoreInner {} +unsafe impl Sync for StoreInner {} + impl std::fmt::Debug for StoreInner { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.debug_struct("StoreInner") diff --git a/lib/api/src/entities/store/mod.rs b/lib/api/src/entities/store/mod.rs index e573cf21c3c..9dee457fb6a 100644 --- a/lib/api/src/entities/store/mod.rs +++ b/lib/api/src/entities/store/mod.rs @@ -1,6 +1,10 @@ //! Defines the [`Store`] data type and various useful traits and data types to interact with a //! store. +/// Defines the [`AsAsyncStore`] trait and its supporting types. +mod asynk; +pub use asynk::*; + /// Defines the [`StoreContext`] type. mod context; @@ -40,6 +44,7 @@ use wasmer_vm::TrapHandlerFn; /// For more informations, check out the [related WebAssembly specification] /// [related WebAssembly specification]: pub struct Store { + pub(crate) id: StoreId, pub(crate) inner: Arc>, } @@ -75,9 +80,11 @@ impl Store { } }; + let objects = StoreObjects::from_store_ref(&store); Self { + id: objects.id(), inner: std::sync::Arc::new(async_lock::RwLock::new(StoreInner { - objects: StoreObjects::from_store_ref(&store), + objects, on_called: None, store, })), @@ -85,24 +92,48 @@ impl Store { } /// Creates a new [`StoreRef`] if the store is available for reading. - pub(crate) fn make_ref(&self) -> Option { + pub(crate) fn try_make_ref(&self) -> Option { self.inner .try_read_arc() .map(|guard| StoreRef { inner: guard }) } - /// Creates a new [`StoreRef`] if the store is available for reading. - pub(crate) fn make_mut(&self) -> Option { - self.inner - .try_write_arc() - .map(|guard| StoreMut { inner: guard }) + /// Waits for the store to become available and creates a new + /// [`StoreRef`] afterwards. + pub(crate) async fn make_ref_async(&self) -> StoreRef { + let guard = self.inner.read_arc().await; + StoreRef { inner: guard } + } + + /// Creates a new [`StoreMut`] if the store is available for writing. + pub(crate) fn try_make_mut(&self) -> Option { + self.inner.try_write_arc().map(|guard| StoreMut { + inner: guard, + store_handle: crate::Store { + id: self.id, + inner: self.inner.clone(), + }, + }) + } + + /// Waits for the store to become available and creates a new + /// [`StoreMut`] afterwards. + pub(crate) async fn make_mut_async(&self) -> StoreMut { + let guard = self.inner.write_arc().await; + StoreMut { + inner: guard, + store_handle: Self { + id: self.id, + inner: self.inner.clone(), + }, + } } /// Builds an [`AsStoreMut`] handle to this store, provided /// the store is not locked. Panics if the store is already locked. pub fn as_mut<'a>(&'a mut self) -> impl AsStoreMut + 'a { StoreMutGuard { - inner: Some(self.make_mut().expect("Store is locked")), + inner: Some(self.try_make_mut().expect("Store is locked")), marker: std::marker::PhantomData, } } @@ -117,7 +148,8 @@ impl Store { pub fn set_trap_handler(&mut self, handler: Option>>) { use crate::backend::sys::entities::store::NativeStoreExt; #[allow(irrefutable_let_patterns)] - if let BackendStore::Sys(ref mut s) = self.make_mut().expect("Store is locked").inner.store + if let BackendStore::Sys(ref mut s) = + self.try_make_mut().expect("Store is locked").inner.store { s.set_trap_handler(handler) } @@ -128,7 +160,7 @@ impl Store { // Happily unwrap the read lock here because we don't expect // embedder code to access stores in parallel. StoreEngineRef { - inner: self.make_ref().expect("Store is locked"), + inner: self.try_make_ref().expect("Store is locked"), marker: std::marker::PhantomData, } } @@ -136,7 +168,7 @@ impl Store { /// Returns mutable reference to [`Engine`]. pub fn engine_mut<'a>(&'a mut self) -> StoreEngineMut<'a> { StoreEngineMut { - inner: self.make_mut().expect("Store is locked"), + inner: self.try_make_mut().expect("Store is locked"), marker: std::marker::PhantomData, } } @@ -149,7 +181,7 @@ impl Store { /// Returns the ID of this store pub fn id(&self) -> StoreId { - self.make_ref().expect("Store is locked").objects().id() + self.id } } @@ -223,8 +255,8 @@ impl DerefMut for StoreEngineMut<'_> { // TODO: can we put the value back after the function returns? We should be able to // TODO: what would the API look like? pub struct StoreMutGuard<'a> { - inner: Option, - marker: std::marker::PhantomData<&'a ()>, + pub(crate) inner: Option, + pub(crate) marker: std::marker::PhantomData<&'a ()>, } impl AsStoreRef for StoreMutGuard<'_> { diff --git a/lib/api/src/entities/store/store_ref.rs b/lib/api/src/entities/store/store_ref.rs index 6b304785842..669a59e5ba6 100644 --- a/lib/api/src/entities/store/store_ref.rs +++ b/lib/api/src/entities/store/store_ref.rs @@ -4,8 +4,11 @@ use std::{ }; use super::{StoreObjects, inner::StoreInner}; -use crate::entities::engine::{AsEngineRef, Engine, EngineRef}; -use wasmer_types::{ExternType, OnCalledAction}; +use crate::{ + Store, + entities::engine::{AsEngineRef, Engine, EngineRef}, +}; +use wasmer_types::{ExternType, OnCalledAction, StoreId}; #[cfg(feature = "sys")] use wasmer_vm::TrapHandlerFn; @@ -25,6 +28,10 @@ impl StoreRef { /// A temporary handle to a [`crate::Store`]. pub struct StoreMut { pub(crate) inner: async_lock::RwLockWriteGuardArc, + + // Also keep an Arc to the store itself, so we can recreate + // the store for async functions. + pub(crate) store_handle: Store, } impl StoreMut { diff --git a/lib/api/src/utils/native/typed_func.rs b/lib/api/src/utils/native/typed_func.rs index e16855e63e4..63ae6310a78 100644 --- a/lib/api/src/utils/native/typed_func.rs +++ b/lib/api/src/utils/native/typed_func.rs @@ -8,8 +8,8 @@ //! let add_one_native: TypedFunction = add_one.native().unwrap(); //! ``` use crate::{ - AsStoreMut, BackendStore, FromToNativeWasmType, Function, NativeWasmTypeInto, RuntimeError, - WasmTypeList, store::AsStoreRef, + AsAsyncStore, AsStoreMut, BackendStore, FromToNativeWasmType, Function, NativeWasmTypeInto, + RuntimeError, WasmTypeList, store::AsStoreRef, }; use std::future::Future; use std::marker::PhantomData; @@ -79,40 +79,41 @@ macro_rules! impl_native_traits { } } - // TODO: async api - // /// Call the typed func asynchronously. - // #[allow(unused_mut)] - // #[allow(clippy::too_many_arguments)] - // pub fn call_async<'a>( - // &'a self, - // store: &'a mut (impl AsStoreMut + 'static), - // $( $x: $x, )* - // ) -> impl Future> + 'a - // where - // $( $x: FromToNativeWasmType, )* - // { - // $( - // let [] = $x; - // )* - // async move { - // match store.as_mut().store { - // #[cfg(feature = "sys")] - // BackendStore::Sys(_) => { - // self.call_async_sys(store, $([]),*).await - // } - // #[cfg(feature = "wamr")] - // BackendStore::Wamr(_) => async_backend_error(), - // #[cfg(feature = "wasmi")] - // BackendStore::Wasmi(_) => async_backend_error(), - // #[cfg(feature = "v8")] - // BackendStore::V8(_) => async_backend_error(), - // #[cfg(feature = "js")] - // BackendStore::Js(_) => async_backend_error(), - // #[cfg(feature = "jsc")] - // BackendStore::Jsc(_) => async_backend_error(), - // } - // } - // } + /// Call the typed func asynchronously. + #[allow(unused_mut)] + #[allow(clippy::too_many_arguments)] + pub fn call_async<'a>( + &'a self, + store: &'a impl AsAsyncStore, + $( $x: $x, )* + ) -> impl Future> + 'a + where + $( $x: FromToNativeWasmType, )* + { + $( + let [] = $x; + )* + async move { + let read_lock = store.read_lock().await; + match read_lock.as_ref().store { + #[cfg(feature = "sys")] + BackendStore::Sys(_) => { + drop(read_lock); + self.call_async_sys(store, $([]),*).await + } + #[cfg(feature = "wamr")] + BackendStore::Wamr(_) => async_backend_error(), + #[cfg(feature = "wasmi")] + BackendStore::Wasmi(_) => async_backend_error(), + #[cfg(feature = "v8")] + BackendStore::V8(_) => async_backend_error(), + #[cfg(feature = "js")] + BackendStore::Js(_) => async_backend_error(), + #[cfg(feature = "jsc")] + BackendStore::Jsc(_) => async_backend_error(), + } + } + } #[doc(hidden)] #[allow(missing_docs)] diff --git a/lib/api/tests/jspi_async.rs b/lib/api/tests/jspi_async.rs index 1d4a3d0cf00..1b67d56bacc 100644 --- a/lib/api/tests/jspi_async.rs +++ b/lib/api/tests/jspi_async.rs @@ -1,505 +1,518 @@ -// TODO: async api - -// use std::{ -// cell::RefCell, -// pin::Pin, -// sync::OnceLock, -// task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, -// }; - -// use anyhow::Result; -// use futures::{FutureExt, future}; -// use wasmer::{ -// AsStoreMut, Function, FunctionEnv, FunctionEnvMut, FunctionType, Instance, Module, -// RuntimeError, Store, StoreMut, Type, TypedFunction, Value, imports, -// }; -// use wasmer_vm::TrapCode; - -// #[derive(Default)] -// struct DeltaState { -// deltas: Vec, -// index: usize, -// } - -// impl DeltaState { -// fn next(&mut self) -> f64 { -// let value = self.deltas.get(self.index).copied().unwrap_or(0.0); -// self.index += 1; -// value -// } -// } - -// fn jspi_module() -> &'static [u8] { -// static BYTES: OnceLock> = OnceLock::new(); -// const JSPI_WAT: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../tests/examples/jspi.wat"); -// BYTES.get_or_init(|| wat::parse_file(JSPI_WAT).expect("valid example module")) -// } - -// #[test] -// fn async_state_updates_follow_jspi_example() -> Result<()> { -// let wasm = jspi_module(); -// let mut store = Store::default(); -// let module = Module::new(&store, wasm)?; - -// let init_state = Function::new_async( -// &mut store, -// FunctionType::new(vec![], vec![Type::F64]), -// |_values| async move { -// // Note: future::ready doesn't actually suspend. It's important -// // to note that, while we're in an async context here, it's -// // impossible to suspend during module instantiation, which is -// // where this import is called. -// // To see this in action, uncomment the following line: -// // tokio::task::yield_now().await; -// future::ready(()).await; -// Ok(vec![Value::F64(1.0)]) -// }, -// ); - -// let delta_env = FunctionEnv::new( -// &mut store, -// DeltaState { -// deltas: vec![0.5, -1.0, 2.5], -// index: 0, -// }, -// ); -// let compute_delta = Function::new_with_env_async( -// &mut store, -// &delta_env, -// FunctionType::new(vec![], vec![Type::F64]), -// |mut env: FunctionEnvMut, _values| { -// let delta = env.data_mut().next(); -// async move { -// // We can, however, actually suspend whenever -// // `Function::call_async` is used to call WASM functions. -// tokio::time::sleep(std::time::Duration::from_millis(10)).await; -// Ok(vec![Value::F64(delta)]) -// } -// }, -// ); - -// let import_object = imports! { -// "js" => { -// "init_state" => init_state, -// "compute_delta" => compute_delta, -// } -// }; - -// let instance = Instance::new(&mut store, &module, &import_object)?; -// let get_state = instance.exports.get_function("get_state")?; -// let update_state = instance.exports.get_function("update_state")?; - -// fn as_f64(values: &[Value]) -> f64 { -// match &values[0] { -// Value::F64(v) => *v, -// other => panic!("expected f64 value, got {other:?}"), -// } -// } - -// assert_eq!(as_f64(&get_state.call(&mut store, &[])?), 1.0); - -// let step = |store: &mut Store, func: &wasmer::Function| -> Result { -// let result = tokio::runtime::Builder::new_current_thread() -// .enable_all() -// .build() -// .unwrap() -// .block_on(func.call_async(store, &[]))?; -// Ok(as_f64(&result)) -// }; - -// assert_eq!(step(&mut store, update_state)?, 1.5); -// assert_eq!(step(&mut store, update_state)?, 0.5); -// assert_eq!(step(&mut store, update_state)?, 3.0); - -// Ok(()) -// } - -// #[test] -// fn typed_async_host_and_calls_work() -> Result<()> { -// let wasm = wat::parse_str( -// r#" -// (module -// (import "host" "async_add" (func $async_add (param i32 i32) (result i32))) -// (import "host" "async_double" (func $async_double (param i32) (result i32))) -// (func (export "compute") (param i32) (result i32) -// local.get 0 -// i32.const 10 -// call $async_add -// local.get 0 -// call $async_double -// i32.add)) -// "#, -// )?; - -// #[derive(Clone, Copy)] -// struct AddBias { -// bias: i32, -// } - -// let mut store = Store::default(); -// let module = Module::new(&store, wasm)?; - -// let add_env = FunctionEnv::new(&mut store, AddBias { bias: 5 }); -// let async_add = Function::new_typed_with_env_async( -// &mut store, -// &add_env, -// async move |mut env: FunctionEnvMut, a: i32, b: i32| { -// let bias = env.data().bias; -// future::ready(()).await; -// Ok(a + b + bias) -// }, -// ); -// let async_double = Function::new_typed_async(&mut store, async move |value: i32| { -// future::ready(()).await; -// Ok(value * 2) -// }); - -// let import_object = imports! { -// "host" => { -// "async_add" => async_add, -// "async_double" => async_double, -// } -// }; - -// let instance = Instance::new(&mut store, &module, &import_object)?; -// let compute: TypedFunction = -// instance.exports.get_typed_function(&mut store, "compute")?; - -// let result = tokio::runtime::Builder::new_current_thread() -// .enable_all() -// .build() -// .unwrap() -// .block_on(compute.call_async(&mut store, 4))?; -// assert_eq!(result, 27); - -// Ok(()) -// } - -// #[test] -// fn cannot_yield_when_not_in_async_context() -> Result<()> { -// const WAT: &str = r#" -// (module -// (import "env" "yield_now" (func $yield_now)) -// (func (export "yield_outside") -// call $yield_now -// ) -// ) -// "#; -// let wasm = wat::parse_str(WAT).expect("valid WAT module"); - -// let mut store = Store::default(); -// let module = Module::new(&store, wasm)?; - -// let yield_now = Function::new_async( -// &mut store, -// FunctionType::new(vec![], vec![]), -// |_values| async move { -// // Attempting to yield when not in an async context should trap. -// tokio::task::yield_now().await; -// Ok(vec![]) -// }, -// ); - -// let import_object = imports! { -// "env" => { -// "yield_now" => yield_now, -// } -// }; -// let instance = Instance::new(&mut store, &module, &import_object)?; -// let yield_outside = instance.exports.get_function("yield_outside")?; - -// let trap = yield_outside -// .call(&mut store, &[]) -// .expect_err("expected trap calling yield outside async context"); - -// // TODO: wasm trace generation appears to be broken? -// // assert!(!trap.trace().is_empty(), "should have a stack trace"); -// let trap_code = trap.to_trap().expect("expected trap code"); -// assert_eq!( -// trap_code, -// TrapCode::YieldOutsideAsyncContext, -// "expected YieldOutsideAsyncContext trap code" -// ); - -// Ok(()) -// } - -// /* This test is slightly weird to explain; what we're testing here -// is that multiple coroutines can be active at the same time, -// and that they can be polled in any order. - -// To achieve this, we have 2 main imports: -// * spawn_future spawns new, pending futures, and polls them once. -// The futures are set up in a way that they will suspend the first time -// they are polled, and complete the second time. However, by polling -// once, we will kickstart the corresponding coroutine into action, -// which then stays active but suspended. -// * resolve_future polls an already spawned future a second time, which -// will cause it to be resolved. With this, we are once again activating -// the coroutine. - -// We also have the future_func export and the poll_future import. future_func -// is the "body" of the inner coroutine, while poll_future retrieves the future -// constructed by spawn_future and uses it to suspend the coroutine. - -// Note that the coroutine will start executing twice, once during spawn_future -// (which is a sync imported function) and once during resolve_future -// (which is async). This way we ensure that any combination of active coroutines -// is possible. - -// The proper order of the log numbers for a given future is: -// * spawning the future: -// 10 + id: spawn_future called initially -// 20 + id: the future is constructed, but not polled yet -// 30 + id: future_func polled first time -// 40 + id: poll_future called, suspending the coroutine -// 50 + id: spawn_future finished -// * resolving the future: -// 60 + id: resolve_future called -// 70 + id: future_func resumed second time -// 80 + id: resolve_future finished -// */ -// #[test] -// fn async_multiple_active_coroutines() -> Result<()> { -// const WAT: &str = r#" -// (module -// (import "env" "spawn_future" (func $spawn_future (param i32))) -// (import "env" "poll_future" (func $poll_future (param i32))) -// (import "env" "resolve_future" (func $resolve_future (param i32))) -// (import "env" "yield_now" (func $yield_now)) -// (import "env" "log" (func $log (param i32))) -// (func (export "main") -// (call $spawn_future (i32.const 0)) -// (call $spawn_future (i32.const 1)) -// (call $yield_now) -// (call $spawn_future (i32.const 2)) -// (call $resolve_future (i32.const 1)) -// (call $yield_now) -// (call $spawn_future (i32.const 3)) -// (call $resolve_future (i32.const 2)) -// (call $yield_now) -// (call $resolve_future (i32.const 3)) -// (call $resolve_future (i32.const 0)) -// ) -// (func (export "future_func") (param i32) (result i32) -// (call $log (i32.add (i32.const 30) (local.get 0))) -// (call $poll_future (local.get 0)) -// (call $log (i32.add (i32.const 70) (local.get 0))) -// (return (local.get 0)) -// ) -// ) -// "#; -// let wasm = wat::parse_str(WAT).expect("valid WAT module"); - -// struct Yielder { -// yielded: bool, -// } - -// impl Future for Yielder { -// type Output = (); - -// fn poll( -// mut self: std::pin::Pin<&mut Self>, -// _cx: &mut std::task::Context<'_>, -// ) -> std::task::Poll { -// if self.yielded { -// std::task::Poll::Ready(()) -// } else { -// self.yielded = true; -// std::task::Poll::Pending -// } -// } -// } - -// struct Env { -// log: Vec, -// futures: [Option, RuntimeError>>>>>; 4], -// yielders: [Option; 4], -// future_func: Option, -// } - -// let mut store = Store::default(); -// let module = Module::new(&store, wasm)?; - -// thread_local! { -// static ENV: RefCell = RefCell::new(Env { -// log: Vec::new(), -// futures: [None, None, None, None], -// yielders: [None, None, None, None], -// future_func: None, -// }) -// } -// let mut env = FunctionEnv::new(&mut store, ()); - -// fn log(value: i32) { -// ENV.with(|env| { -// env.borrow_mut().log.push(value); -// }); -// } - -// let spawn_future = Function::new_with_env( -// &mut store, -// &mut env, -// FunctionType::new(vec![Type::I32], vec![]), -// |mut env, values| { -// ENV.with(|data| { -// let ((), mut store) = env.data_and_store_mut(); - -// let future_id = values[0].unwrap_i32(); - -// log(future_id + 10); - -// data.borrow_mut().yielders[future_id as usize] = Some(Yielder { yielded: false }); - -// // This spawns the coroutine and the corresponding future -// let store_raw = store.as_store_mut().as_raw(); -// let func = data.borrow().future_func.as_ref().unwrap().clone(); -// let mut future = Box::pin(async move { -// let mut store = unsafe { StoreMut::<'static>::from_raw(store_raw) }; -// let result = func.call_async(&mut store, &[Value::I32(future_id)]).await; -// result -// }); -// log(future_id + 20); - -// // We then poll it once to get it started - it'll suspend once, then -// // complete the next time we poll it -// let w = noop_waker(); -// let mut cx = Context::from_waker(&w); -// assert!(future.as_mut().poll(&mut cx).is_pending()); - -// log(future_id + 50); -// // We then store the future without letting it complete, and return -// data.borrow_mut().futures[future_id as usize] = Some(future); - -// Ok(vec![]) -// }) -// }, -// ); - -// let poll_future = Function::new_async( -// &mut store, -// FunctionType::new(vec![Type::I32], vec![]), -// |values| { -// let future_id = values[0].unwrap_i32(); -// let yielder = ENV.with(|data| { -// log(future_id + 40); - -// let mut borrow = data.borrow_mut(); -// let yielder = unsafe { -// (borrow.yielders[future_id as usize].as_mut().unwrap() as *mut Yielder) -// .as_mut::<'static>() -// .unwrap() -// }; -// yielder -// }); -// yielder.map(|()| Ok(vec![])) -// }, -// ); - -// let resolve_future = Function::new_async( -// &mut store, -// FunctionType::new(vec![Type::I32], vec![]), -// |values| { -// let future_id = values[0].unwrap_i32(); - -// async move { -// ENV.with(|data| { -// log(future_id + 60); - -// let mut future = data.borrow_mut().futures[future_id as usize] -// .take() -// .unwrap(); - -// let w = noop_waker(); -// let mut cx = Context::from_waker(&w); -// let Poll::Ready(result) = future.as_mut().poll(&mut cx) else { -// panic!("expected future to be ready"); -// }; -// let result_id = result.unwrap()[0].unwrap_i32(); -// assert_eq!(result_id, future_id); - -// log(future_id + 80); - -// Ok(vec![]) -// }) -// } -// }, -// ); - -// let yield_now = Function::new_async( -// &mut store, -// FunctionType::new(vec![], vec![]), -// |_values| async move { -// tokio::task::yield_now().await; -// Ok(vec![]) -// }, -// ); - -// let log = Function::new( -// &mut store, -// FunctionType::new(vec![Type::I32], vec![]), -// |values| { -// let value = values[0].unwrap_i32(); -// log(value); -// Ok(vec![]) -// }, -// ); - -// let import_object = imports! { -// "env" => { -// "spawn_future" => spawn_future, -// "poll_future" => poll_future, -// "resolve_future" => resolve_future, -// "yield_now" => yield_now, -// "log" => log, -// } -// }; -// let instance = Instance::new(&mut store, &module, &import_object)?; - -// ENV.with(|env| { -// env.borrow_mut().future_func = Some( -// instance -// .exports -// .get_function("future_func") -// .unwrap() -// .clone(), -// ) -// }); - -// let main = instance.exports.get_function("main")?; -// tokio::runtime::Builder::new_current_thread() -// .enable_all() -// .build() -// .unwrap() -// .block_on(main.call_async(&mut store, &[]))?; - -// ENV.with(|env| { -// assert_eq!( -// env.borrow().log, -// vec![ -// 10, 20, 30, 40, 50, // future 0 spawned -// 11, 21, 31, 41, 51, // future 1 spawned -// 12, 22, 32, 42, 52, // future 2 spawned -// 61, 71, 81, // future 1 resolved -// 13, 23, 33, 43, 53, // future 3 spawned -// 62, 72, 82, // future 2 resolved -// 63, 73, 83, // future 3 resolved -// 60, 70, 80, // future 0 resolved -// ] -// ); -// }); - -// Ok(()) -// } - -// fn noop_waker() -> Waker { -// fn noop_raw_waker() -> RawWaker { -// fn no_op(_: *const ()) {} -// fn clone(_: *const ()) -> RawWaker { -// noop_raw_waker() -// } -// let vtable = &RawWakerVTable::new(clone, no_op, no_op, no_op); -// RawWaker::new(std::ptr::null(), vtable) -// } -// unsafe { Waker::from_raw(noop_raw_waker()) } -// } +use std::{ + cell::RefCell, + pin::Pin, + sync::OnceLock, + task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, +}; + +use anyhow::Result; +use futures::{FutureExt, future}; +use wasmer::{ + AsAsyncStore, AsyncFunctionEnvMut, Function, FunctionEnv, FunctionType, Instance, Module, + RuntimeError, Store, Type, TypedFunction, Value, imports, +}; +use wasmer_vm::TrapCode; + +#[derive(Default)] +struct DeltaState { + deltas: Vec, + index: usize, +} + +impl DeltaState { + fn next(&mut self) -> f64 { + let value = self.deltas.get(self.index).copied().unwrap_or(0.0); + self.index += 1; + value + } +} + +fn jspi_module() -> &'static [u8] { + static BYTES: OnceLock> = OnceLock::new(); + const JSPI_WAT: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../tests/examples/jspi.wat"); + BYTES.get_or_init(|| wat::parse_file(JSPI_WAT).expect("valid example module")) +} + +#[test] +fn async_state_updates_follow_jspi_example() -> Result<()> { + let wasm = jspi_module(); + let mut store = Store::default(); + let module = Module::new(&store.engine(), wasm)?; + + let mut store_mut = store.as_mut(); + let init_state = Function::new_async( + &mut store_mut, + FunctionType::new(vec![], vec![Type::F64]), + |_values| async move { + // Note: future::ready doesn't actually suspend. It's important + // to note that, while we're in an async context here, it's + // impossible to suspend during module instantiation, which is + // where this import is called. + // To see this in action, uncomment the following line: + // tokio::task::yield_now().await; + future::ready(()).await; + Ok(vec![Value::F64(1.0)]) + }, + ); + + let delta_env = FunctionEnv::new( + &mut store_mut, + DeltaState { + deltas: vec![0.5, -1.0, 2.5], + index: 0, + }, + ); + let compute_delta = Function::new_with_env_async( + &mut store_mut, + &delta_env, + FunctionType::new(vec![], vec![Type::F64]), + |env: AsyncFunctionEnvMut, _values| async move { + // We can, however, actually suspend whenever + // `Function::call_async` is used to call WASM functions. + let delta = { + let mut env_write = env.write().await; + env_write.data_mut().next() + }; + tokio::time::sleep(std::time::Duration::from_millis(10)).await; + Ok(vec![Value::F64(delta)]) + }, + ); + + let import_object = imports! { + "js" => { + "init_state" => init_state, + "compute_delta" => compute_delta, + } + }; + + let instance = Instance::new(&mut store_mut, &module, &import_object)?; + let get_state = instance.exports.get_function("get_state")?; + let update_state = instance.exports.get_function("update_state")?; + + fn as_f64(values: &[Value]) -> f64 { + match &values[0] { + Value::F64(v) => *v, + other => panic!("expected f64 value, got {other:?}"), + } + } + + assert_eq!(as_f64(&get_state.call(&mut store_mut, &[])?), 1.0); + + fn step(store: &mut impl AsAsyncStore, func: &wasmer::Function) -> Result { + let result = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap() + .block_on(func.call_async(store, &[]))?; + Ok(as_f64(&result)) + } + + drop(store_mut); + + assert_eq!(step(&mut store, update_state)?, 1.5); + assert_eq!(step(&mut store, update_state)?, 0.5); + assert_eq!(step(&mut store, update_state)?, 3.0); + + Ok(()) +} + +#[test] +fn typed_async_host_and_calls_work() -> Result<()> { + let wasm = wat::parse_str( + r#" + (module + (import "host" "async_add" (func $async_add (param i32 i32) (result i32))) + (import "host" "async_double" (func $async_double (param i32) (result i32))) + (func (export "compute") (param i32) (result i32) + local.get 0 + i32.const 10 + call $async_add + local.get 0 + call $async_double + i32.add)) + "#, + )?; + + #[derive(Clone, Copy)] + struct AddBias { + bias: i32, + } + + let mut store = Store::default(); + let module = Module::new(&store.engine(), wasm)?; + + let mut store_mut = store.as_mut(); + let add_env = FunctionEnv::new(&mut store_mut, AddBias { bias: 5 }); + let async_add = Function::new_typed_with_env_async( + &mut store_mut, + &add_env, + async move |env: AsyncFunctionEnvMut, a: i32, b: i32| { + let bias = { + let read = env.read().await; + read.data().bias + }; + future::ready(()).await; + a + b + bias + }, + ); + let async_double = Function::new_typed_async(&mut store_mut, async move |value: i32| { + future::ready(()).await; + value * 2 + }); + + let import_object = imports! { + "host" => { + "async_add" => async_add, + "async_double" => async_double, + } + }; + + let instance = Instance::new(&mut store_mut, &module, &import_object)?; + let compute: TypedFunction = instance + .exports + .get_typed_function(&mut store_mut, "compute")?; + + drop(store_mut); + + let result = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap() + .block_on(compute.call_async(&store, 4))?; + assert_eq!(result, 27); + + Ok(()) +} + +#[test] +fn cannot_yield_when_not_in_async_context() -> Result<()> { + const WAT: &str = r#" + (module + (import "env" "yield_now" (func $yield_now)) + (func (export "yield_outside") + call $yield_now + ) + ) + "#; + let wasm = wat::parse_str(WAT).expect("valid WAT module"); + + let mut store = Store::default(); + let module = Module::new(&store.engine(), wasm)?; + + let mut store_mut = store.as_mut(); + let yield_now = Function::new_async( + &mut store_mut, + FunctionType::new(vec![], vec![]), + |_values| async move { + // Attempting to yield when not in an async context should trap. + tokio::task::yield_now().await; + Ok(vec![]) + }, + ); + + let import_object = imports! { + "env" => { + "yield_now" => yield_now, + } + }; + let instance = Instance::new(&mut store_mut, &module, &import_object)?; + let yield_outside = instance.exports.get_function("yield_outside")?; + + let trap = yield_outside + .call(&mut store_mut, &[]) + .expect_err("expected trap calling yield outside async context"); + + // TODO: wasm trace generation appears to be broken? + // assert!(!trap.trace().is_empty(), "should have a stack trace"); + let trap_code = trap.to_trap().expect("expected trap code"); + assert_eq!( + trap_code, + TrapCode::YieldOutsideAsyncContext, + "expected YieldOutsideAsyncContext trap code" + ); + + Ok(()) +} + +/* This test is slightly weird to explain; what we're testing here + is that multiple coroutines can be active at the same time, + and that they can be polled in any order. + + To achieve this, we have 2 main imports: + * spawn_future spawns new, pending futures, and polls them once. + The futures are set up in a way that they will suspend the first time + they are polled, and complete the second time. However, by polling + once, we will kickstart the corresponding coroutine into action, + which then stays active but suspended. + * resolve_future polls an already spawned future a second time, which + will cause it to be resolved. With this, we are once again activating + the coroutine. + + We also have the future_func export and the poll_future import. future_func + is the "body" of the inner coroutine, while poll_future retrieves the future + constructed by spawn_future and uses it to suspend the coroutine. + + Note that the coroutine will start executing twice, once during spawn_future + (which is a sync imported function) and once during resolve_future + (which is async). This way we ensure that any combination of active coroutines + is possible. + + The proper order of the log numbers for a given future is: + * spawning the future: + 10 + id: spawn_future called initially + 20 + id: the future is constructed, but not polled yet + 30 + id: future_func polled first time + 40 + id: poll_future called, suspending the coroutine + 50 + id: spawn_future finished + * resolving the future: + 60 + id: resolve_future called + 70 + id: future_func resumed second time + 80 + id: resolve_future finished +*/ +#[test] +fn async_multiple_active_coroutines() -> Result<()> { + const WAT: &str = r#" + (module + (import "env" "spawn_future" (func $spawn_future (param i32))) + (import "env" "poll_future" (func $poll_future (param i32))) + (import "env" "resolve_future" (func $resolve_future (param i32))) + (import "env" "yield_now" (func $yield_now)) + (import "env" "log" (func $log (param i32))) + (func (export "main") + (call $spawn_future (i32.const 0)) + (call $spawn_future (i32.const 1)) + (call $yield_now) + (call $spawn_future (i32.const 2)) + (call $resolve_future (i32.const 1)) + (call $yield_now) + (call $spawn_future (i32.const 3)) + (call $resolve_future (i32.const 2)) + (call $yield_now) + (call $resolve_future (i32.const 3)) + (call $resolve_future (i32.const 0)) + ) + (func (export "future_func") (param i32) (result i32) + (call $log (i32.add (i32.const 30) (local.get 0))) + (call $poll_future (local.get 0)) + (call $log (i32.add (i32.const 70) (local.get 0))) + (return (local.get 0)) + ) + ) + "#; + let wasm = wat::parse_str(WAT).expect("valid WAT module"); + + struct Yielder { + yielded: bool, + } + + impl Future for Yielder { + type Output = (); + + fn poll( + mut self: std::pin::Pin<&mut Self>, + _cx: &mut std::task::Context<'_>, + ) -> std::task::Poll { + if self.yielded { + std::task::Poll::Ready(()) + } else { + self.yielded = true; + std::task::Poll::Pending + } + } + } + + struct Env { + log: Vec, + futures: [Option, RuntimeError>>>>>; 4], + yielders: [Option; 4], + future_func: Option, + } + + let mut store = Store::default(); + let module = Module::new(&store.engine(), wasm)?; + + let mut store_mut = store.as_mut(); + + thread_local! { + static ENV: RefCell = RefCell::new(Env { + log: Vec::new(), + futures: [None, None, None, None], + yielders: [None, None, None, None], + future_func: None, + }) + } + let mut env = FunctionEnv::new(&mut store_mut, ()); + + fn log(value: i32) { + ENV.with(|env| { + env.borrow_mut().log.push(value); + }); + } + + let spawn_future = Function::new_with_env( + &mut store_mut, + &mut env, + FunctionType::new(vec![Type::I32], vec![]), + |mut env, values| { + ENV.with(|data| { + let future_id = values[0].unwrap_i32(); + + log(future_id + 10); + + data.borrow_mut().yielders[future_id as usize] = Some(Yielder { yielded: false }); + + // This spawns the coroutine and the corresponding future + // Note the use of `FunctionEnvMut` as `AsAsyncStore` to + // spawn a new execution context for the coroutine + let func = data.borrow().future_func.as_ref().unwrap().clone(); + let async_store = env.as_async_store(); + let mut future = Box::pin(async move { + func.call_async(&async_store, &[Value::I32(future_id)]) + .await + }); + log(future_id + 20); + + // We then poll it once to get it started - it'll suspend once, then + // complete the next time we poll it + let w = noop_waker(); + let mut cx = Context::from_waker(&w); + assert!(future.as_mut().poll(&mut cx).is_pending()); + + log(future_id + 50); + // We then store the future without letting it complete, and return + data.borrow_mut().futures[future_id as usize] = Some(future); + + Ok(vec![]) + }) + }, + ); + + let poll_future = Function::new_async( + &mut store_mut, + FunctionType::new(vec![Type::I32], vec![]), + |values| { + let future_id = values[0].unwrap_i32(); + let yielder = ENV.with(|data| { + log(future_id + 40); + + let mut borrow = data.borrow_mut(); + let yielder = unsafe { + (borrow.yielders[future_id as usize].as_mut().unwrap() as *mut Yielder) + .as_mut::<'static>() + .unwrap() + }; + yielder + }); + yielder.map(|()| Ok(vec![])) + }, + ); + + let resolve_future = Function::new_async( + &mut store_mut, + FunctionType::new(vec![Type::I32], vec![]), + |values| { + let future_id = values[0].unwrap_i32(); + + async move { + ENV.with(|data| { + log(future_id + 60); + + let mut future = data.borrow_mut().futures[future_id as usize] + .take() + .unwrap(); + + let w = noop_waker(); + let mut cx = Context::from_waker(&w); + let Poll::Ready(result) = future.as_mut().poll(&mut cx) else { + panic!("expected future to be ready"); + }; + let result_id = result.unwrap()[0].unwrap_i32(); + assert_eq!(result_id, future_id); + + log(future_id + 80); + + Ok(vec![]) + }) + } + }, + ); + + let yield_now = Function::new_async( + &mut store_mut, + FunctionType::new(vec![], vec![]), + |_values| async move { + tokio::task::yield_now().await; + Ok(vec![]) + }, + ); + + let log = Function::new( + &mut store_mut, + FunctionType::new(vec![Type::I32], vec![]), + |values| { + let value = values[0].unwrap_i32(); + log(value); + Ok(vec![]) + }, + ); + + let import_object = imports! { + "env" => { + "spawn_future" => spawn_future, + "poll_future" => poll_future, + "resolve_future" => resolve_future, + "yield_now" => yield_now, + "log" => log, + } + }; + let instance = Instance::new(&mut store_mut, &module, &import_object)?; + + ENV.with(|env| { + env.borrow_mut().future_func = Some( + instance + .exports + .get_function("future_func") + .unwrap() + .clone(), + ) + }); + + drop(store_mut); + + let main = instance.exports.get_function("main")?; + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap() + .block_on(main.call_async(&store, &[]))?; + + ENV.with(|env| { + assert_eq!( + env.borrow().log, + vec![ + 10, 20, 30, 40, 50, // future 0 spawned + 11, 21, 31, 41, 51, // future 1 spawned + 12, 22, 32, 42, 52, // future 2 spawned + 61, 71, 81, // future 1 resolved + 13, 23, 33, 43, 53, // future 3 spawned + 62, 72, 82, // future 2 resolved + 63, 73, 83, // future 3 resolved + 60, 70, 80, // future 0 resolved + ] + ); + }); + + Ok(()) +} + +fn noop_waker() -> Waker { + fn noop_raw_waker() -> RawWaker { + fn no_op(_: *const ()) {} + fn clone(_: *const ()) -> RawWaker { + noop_raw_waker() + } + let vtable = &RawWakerVTable::new(clone, no_op, no_op, no_op); + RawWaker::new(std::ptr::null(), vtable) + } + unsafe { Waker::from_raw(noop_raw_waker()) } +} From 50d5b04675ecba0ac20a6b172207bfd31ef513b4 Mon Sep 17 00:00:00 2001 From: Zebreus Date: Tue, 11 Nov 2025 14:06:34 +0100 Subject: [PATCH 16/49] Add simple greenthread test --- lib/api/tests/simple-greenthread.rs | 273 ++++++++++++++++++++++++++ tests/examples/simple-greenthread.wat | 101 ++++++++++ 2 files changed, 374 insertions(+) create mode 100644 lib/api/tests/simple-greenthread.rs create mode 100644 tests/examples/simple-greenthread.wat diff --git a/lib/api/tests/simple-greenthread.rs b/lib/api/tests/simple-greenthread.rs new file mode 100644 index 00000000000..1c27cf62c05 --- /dev/null +++ b/lib/api/tests/simple-greenthread.rs @@ -0,0 +1,273 @@ +use std::collections::BTreeMap; +use std::sync::atomic::AtomicU32; +use std::sync::{Arc, OnceLock, RwLock}; + +use anyhow::Result; +use futures::task::LocalSpawnExt; +use futures::{FutureExt, channel::oneshot}; +use wasmer::{ + AsStoreMut, Function, FunctionEnv, FunctionEnvMut, FunctionType, Instance, Memory, Module, + RuntimeError, Store, StoreMut, Type, Value, imports, +}; + +const GREENTHREAD_WAT: &[u8] = include_bytes!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../tests/examples/simple-greenthread.wat" +)); + +thread_local! { + static LOCAL_SPAWNER: OnceLock = OnceLock::new(); +} +fn spawn_local(future: F) +where + F: std::future::Future + 'static, +{ + LOCAL_SPAWNER.with(|spawner_lock| { + let spawner = spawner_lock.get().expect("Local spawner not initialized"); + spawner + .spawn_local(future) + .expect("Failed to spawn local future"); + }); +} + +struct GreenEnv { + logs: Vec, + memory: Option, + greenthreads: Arc>>, + current_greenthread_id: Arc>, + next_free_id: AtomicU32, + entrypoint: Option, +} + +impl GreenEnv { + fn new() -> Self { + Self { + logs: Vec::new(), + memory: None, + greenthreads: Arc::new(RwLock::new(BTreeMap::new())), + current_greenthread_id: Arc::new(RwLock::new(0)), + next_free_id: AtomicU32::new(1), + entrypoint: None, + } + } +} + +struct Greenthread { + entrypoint: Option, + resumer: Option>, +} + +impl Clone for Greenthread { + fn clone(&self) -> Self { + if self.resumer.is_some() { + panic!("Cannot clone a greenthread with a resumer"); + } + Self { + entrypoint: self.entrypoint, + resumer: None, + } + } +} + +fn greenthread_new( + mut env: FunctionEnvMut, + params: &[Value], +) -> core::result::Result, RuntimeError> { + let (data, mut store) = env.data_and_store_mut(); + let new_greenthread_id = data + .next_free_id + .fetch_add(1, std::sync::atomic::Ordering::SeqCst); + + let function = data.entrypoint.clone().expect("entrypoint set"); + let (sender, receiver) = oneshot::channel::<()>(); + + let entrypoint_data = params[0].unwrap_i32() as u32; + let new_greenthread = Greenthread { + entrypoint: Some(entrypoint_data), + resumer: Some(sender), + }; + + data.greenthreads + .write() + .unwrap() + .insert(new_greenthread_id, new_greenthread); + + // SAFETY: This is only sound if the following invariants are upheld: + // A: The future spawned here must not outlive the lifetime of the store. + // In this test, we ensure that the store lives at least as long as the future, + // and the future completes before the store is dropped. + // B: There must not be multiple mutable references (no aliasing) to the store at the same time. + // We must ensure that the store is not accessed elsewhere while the future is running. + // If these invariants are not guaranteed, this code is unsound and may cause undefined behavior. + let mut unsafe_static_store = + unsafe { std::mem::transmute::<_, StoreMut<'static>>(store.as_store_mut()) }; + + spawn_local(async move { + receiver.await.unwrap(); + let resumer = function + .call_async( + &mut unsafe_static_store, + &[Value::I32(entrypoint_data as i32)], + ) + .await; + panic!("Greenthread function returned {:?}", resumer); + }); + + Ok(vec![Value::I32(new_greenthread_id as i32)]) +} + +fn greenthread_switch( + mut env: FunctionEnvMut, + params: &[Value], +) -> impl futures::Future, RuntimeError>> + use<> + Send { + let next_greenthread_id = params[0].unwrap_i32() as u32; + + let (data, _store) = env.data_and_store_mut(); + let current_greenthread_id = { + let mut current = data.current_greenthread_id.write().unwrap(); + let old = *current; + *current = next_greenthread_id; + old + }; + + if current_greenthread_id == next_greenthread_id { + panic!("Switching to self is not allowed"); + } + + let (sender, receiver) = oneshot::channel::<()>(); + + { + let mut greenthreads = data.greenthreads.write().unwrap(); + let this_one = greenthreads.get_mut(¤t_greenthread_id).unwrap(); + if this_one.resumer.is_some() { + panic!("Switching from a greenthread that is already switched out"); + } + this_one.resumer = Some(sender); + } + + { + let mut greenthreads = data.greenthreads.write().unwrap(); + let next_one = greenthreads.get_mut(&next_greenthread_id).unwrap(); + let Some(resumer) = next_one.resumer.take() else { + panic!("Switching to greenthread that has no resumer"); + }; + resumer.send(()).unwrap(); + } + + let current_id_arc = data.current_greenthread_id.clone(); + + async move { + let _ = receiver.map(|_| ()).await; + + *current_id_arc.write().unwrap() = current_greenthread_id; + + Ok(vec![]) + } +} + +#[cfg(not(target_arch = "wasm32"))] +#[test] +fn green_threads_switch_and_log_in_expected_order() -> Result<()> { + let mut store = Store::default(); + let module = Module::new(&store, GREENTHREAD_WAT)?; + + let env = FunctionEnv::new(&mut store, GreenEnv::new()); + + // log(ptr, len) + let log_fn = Function::new_with_env( + &mut store, + &env, + FunctionType::new(vec![Type::I32, Type::I32], vec![]), + |mut env: FunctionEnvMut, params: &[Value]| { + let ptr = params[0].unwrap_i32() as u32; + let len = params[1].unwrap_i32() as u32; + let (data, storemut) = env.data_and_store_mut(); + let memory = data.memory.as_ref().expect("memory set"); + let view = memory.view(&storemut); + let mut bytes = Vec::with_capacity(len as usize); + for i in ptr..ptr + len { + bytes.push(view.read_u8(i as u64).expect("in bounds")); + } + let s = String::from_utf8_lossy(&bytes).to_string(); + data.logs.push(s); + Ok(vec![]) + }, + ); + + let greenthread_new = Function::new_with_env( + &mut store, + &env, + FunctionType::new(vec![Type::I32], vec![Type::I32]), + greenthread_new, + ); + + let greenthread_switch = Function::new_with_env_async( + &mut store, + &env, + FunctionType::new(vec![Type::I32], Vec::::new()), + greenthread_switch, + ); + + let import_object = imports! { + "test" => { + "log" => log_fn, + "greenthread_new" => greenthread_new, + "greenthread_switch" => greenthread_switch, + } + }; + + let instance = Instance::new(&mut store, &module, &import_object)?; + + let entrypoint = instance.exports.get_function("entrypoint")?.clone(); + env.as_mut(&mut store).entrypoint = Some(entrypoint); + + let memory = instance.exports.get_memory("memory")?.clone(); + env.as_mut(&mut store).memory = Some(memory); + + let main_fn = instance.exports.get_function("_main")?; + + let main_greenthread = Greenthread { + entrypoint: None, + resumer: None, + }; + + env.as_mut(&mut store) + .greenthreads + .write() + .unwrap() + .insert(0, main_greenthread); + + let mut localpool = futures::executor::LocalPool::new(); + let local_spawner = localpool.spawner(); + LOCAL_SPAWNER.with(|spawner_lock| { + spawner_lock + .set(local_spawner) + .expect("Failed to set local spawner"); + }); + localpool + .run_until(main_fn.call_async(&mut store, &[])) + .unwrap(); + + // Expected log order + let expected = [ + "[gr1] main -> test1", + "[gr2] test1 -> test2", + "[gr1] test1 <- test2", + "[gr2] test1 -> test2", + "[gr1] test1 <- test2", + "[main] main <- test1", + ]; + + let logs = &env.as_ref(&store).logs; + assert_eq!( + logs.len(), + expected.len(), + "Unexpected number of log entries: {:?}", + logs + ); + for (i, exp) in expected.iter().enumerate() { + assert_eq!(logs[i], *exp, "Log entry mismatch at index {i}: {:?}", logs); + } + + Ok(()) +} diff --git a/tests/examples/simple-greenthread.wat b/tests/examples/simple-greenthread.wat new file mode 100644 index 00000000000..5984086a542 --- /dev/null +++ b/tests/examples/simple-greenthread.wat @@ -0,0 +1,101 @@ +(module + (import "test" "log" (func $log (param i32 i32))) + (import "test" "greenthread_new" (func $greenthread_new (param i32) (result i32))) + (import "test" "greenthread_switch" (func $greenthread_switch (param i32))) + + + (memory (export "memory") 1) + + (data (i32.const 0) "[gr1] main -> test1") + (data (i32.const 100) "[gr1] test1 <- test2") + (data (i32.const 200) "[gr2] test1 -> test2") + (data (i32.const 300) "[main] main <- test1") + + (global $main (mut i32) (i32.const 0)) + (global $gr1 (mut i32) (i32.const 0)) + (global $gr2 (mut i32) (i32.const 0)) + + (func (export "_main") + i32.const 0 + global.set $main + (call $greenthread_new (i32.const 0)) + global.set $gr1 + (call $greenthread_new (i32.const 1)) + global.set $gr2 + + ;; Switch to gr1 + global.get $gr1 + (call $greenthread_switch) + + ;; Print [main] main <- test1 + i32.const 300 + i32.const 20 + call $log + ) + (func $gr1 + ;; Print [gr1] main -> test1 + i32.const 0 + i32.const 20 + call $log + + ;; Switch to gr2 + global.get $gr2 + call $greenthread_switch + + ;; Print [gr1] test1 <- test2 + i32.const 100 + i32.const 20 + call $log + + ;; Switch to gr2 + global.get $gr2 + call $greenthread_switch + + ;; Print [gr1] test1 <- test2 + i32.const 100 + i32.const 20 + call $log + + ;; Switch back to main + global.get $main + call $greenthread_switch + unreachable + ) + (func $gr2 + ;; Print [gr2] test1 -> test2 + i32.const 200 + i32.const 20 + call $log + + ;; Switch to gr1 + global.get $gr1 + call $greenthread_switch + + ;; Print [gr2] test1 -> test2 + i32.const 200 + i32.const 20 + call $log + + ;; Switch to gr1 + global.get $gr1 + call $greenthread_switch + + unreachable + ) + (func (export "entrypoint") (param i32) + local.get 0 + i32.const 0 + i32.eq + (if + (then + (call $gr1) + ) + (else + (call $gr2) + )) + unreachable + + + ) + +) From 40b47ecfc7046df0c5487f597ff76e77beb4a5e2 Mon Sep 17 00:00:00 2001 From: Zebreus Date: Fri, 14 Nov 2025 11:44:41 +0000 Subject: [PATCH 17/49] Make simple greenthread example use typed functions --- lib/api/tests/simple-greenthread.rs | 30 +++++++++-------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/lib/api/tests/simple-greenthread.rs b/lib/api/tests/simple-greenthread.rs index 1c27cf62c05..b71127d6256 100644 --- a/lib/api/tests/simple-greenthread.rs +++ b/lib/api/tests/simple-greenthread.rs @@ -71,8 +71,8 @@ impl Clone for Greenthread { fn greenthread_new( mut env: FunctionEnvMut, - params: &[Value], -) -> core::result::Result, RuntimeError> { + entrypoint_data: u32, +) -> core::result::Result { let (data, mut store) = env.data_and_store_mut(); let new_greenthread_id = data .next_free_id @@ -81,7 +81,6 @@ fn greenthread_new( let function = data.entrypoint.clone().expect("entrypoint set"); let (sender, receiver) = oneshot::channel::<()>(); - let entrypoint_data = params[0].unwrap_i32() as u32; let new_greenthread = Greenthread { entrypoint: Some(entrypoint_data), resumer: Some(sender), @@ -113,15 +112,13 @@ fn greenthread_new( panic!("Greenthread function returned {:?}", resumer); }); - Ok(vec![Value::I32(new_greenthread_id as i32)]) + Ok(new_greenthread_id) } fn greenthread_switch( mut env: FunctionEnvMut, - params: &[Value], -) -> impl futures::Future, RuntimeError>> + use<> + Send { - let next_greenthread_id = params[0].unwrap_i32() as u32; - + next_greenthread_id: u32, +) -> impl futures::Future> + use<> + Send { let (data, _store) = env.data_and_store_mut(); let current_greenthread_id = { let mut current = data.current_greenthread_id.write().unwrap(); @@ -161,7 +158,7 @@ fn greenthread_switch( *current_id_arc.write().unwrap() = current_greenthread_id; - Ok(vec![]) + Ok(()) } } @@ -194,19 +191,10 @@ fn green_threads_switch_and_log_in_expected_order() -> Result<()> { }, ); - let greenthread_new = Function::new_with_env( - &mut store, - &env, - FunctionType::new(vec![Type::I32], vec![Type::I32]), - greenthread_new, - ); + let greenthread_new = Function::new_typed_with_env(&mut store, &env, greenthread_new); - let greenthread_switch = Function::new_with_env_async( - &mut store, - &env, - FunctionType::new(vec![Type::I32], Vec::::new()), - greenthread_switch, - ); + let greenthread_switch = + Function::new_typed_with_env_async(&mut store, &env, greenthread_switch); let import_object = imports! { "test" => { From cdc80fd97bb2be3c7620516364119cb95672ddb3 Mon Sep 17 00:00:00 2001 From: Zebreus Date: Thu, 20 Nov 2025 14:42:55 +0100 Subject: [PATCH 18/49] Add repro for a segfault with greenthreads --- lib/api/tests/simple-greenthread.rs | 111 ++++++++++++++----------- tests/examples/simple-greenthread2.wat | 74 +++++++++++++++++ 2 files changed, 137 insertions(+), 48 deletions(-) create mode 100644 tests/examples/simple-greenthread2.wat diff --git a/lib/api/tests/simple-greenthread.rs b/lib/api/tests/simple-greenthread.rs index b71127d6256..d53a439c83a 100644 --- a/lib/api/tests/simple-greenthread.rs +++ b/lib/api/tests/simple-greenthread.rs @@ -1,6 +1,6 @@ use std::collections::BTreeMap; use std::sync::atomic::AtomicU32; -use std::sync::{Arc, OnceLock, RwLock}; +use std::sync::{Arc, RwLock}; use anyhow::Result; use futures::task::LocalSpawnExt; @@ -10,26 +10,6 @@ use wasmer::{ RuntimeError, Store, StoreMut, Type, Value, imports, }; -const GREENTHREAD_WAT: &[u8] = include_bytes!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../tests/examples/simple-greenthread.wat" -)); - -thread_local! { - static LOCAL_SPAWNER: OnceLock = OnceLock::new(); -} -fn spawn_local(future: F) -where - F: std::future::Future + 'static, -{ - LOCAL_SPAWNER.with(|spawner_lock| { - let spawner = spawner_lock.get().expect("Local spawner not initialized"); - spawner - .spawn_local(future) - .expect("Failed to spawn local future"); - }); -} - struct GreenEnv { logs: Vec, memory: Option, @@ -37,8 +17,15 @@ struct GreenEnv { current_greenthread_id: Arc>, next_free_id: AtomicU32, entrypoint: Option, + spawner: Option, } +// Required for carrying the spawner around. Safe because we don't do threads. +// It worked before with a thread-local! spawner, so this is equivalent. +// The thread-local version does not work with multiple tests +unsafe impl Send for GreenEnv {} +unsafe impl Sync for GreenEnv {} + impl GreenEnv { fn new() -> Self { Self { @@ -48,6 +35,7 @@ impl GreenEnv { current_greenthread_id: Arc::new(RwLock::new(0)), next_free_id: AtomicU32::new(1), entrypoint: None, + spawner: None, } } } @@ -101,16 +89,19 @@ fn greenthread_new( let mut unsafe_static_store = unsafe { std::mem::transmute::<_, StoreMut<'static>>(store.as_store_mut()) }; - spawn_local(async move { - receiver.await.unwrap(); - let resumer = function - .call_async( - &mut unsafe_static_store, - &[Value::I32(entrypoint_data as i32)], - ) - .await; - panic!("Greenthread function returned {:?}", resumer); - }); + let spawner = env.data().spawner.as_ref().expect("spawner set").clone(); + spawner + .spawn_local(async move { + receiver.await.unwrap(); + let resumer = function + .call_async( + &mut unsafe_static_store, + &[Value::I32(entrypoint_data as i32)], + ) + .await; + panic!("Greenthread function returned {:?}", resumer); + }) + .unwrap(); Ok(new_greenthread_id) } @@ -162,11 +153,9 @@ fn greenthread_switch( } } -#[cfg(not(target_arch = "wasm32"))] -#[test] -fn green_threads_switch_and_log_in_expected_order() -> Result<()> { +fn run_greenthread_test(wat: &[u8]) -> Result> { let mut store = Store::default(); - let module = Module::new(&store, GREENTHREAD_WAT)?; + let module = Module::new(&store, wat)?; let env = FunctionEnv::new(&mut store, GreenEnv::new()); @@ -227,16 +216,24 @@ fn green_threads_switch_and_log_in_expected_order() -> Result<()> { let mut localpool = futures::executor::LocalPool::new(); let local_spawner = localpool.spawner(); - LOCAL_SPAWNER.with(|spawner_lock| { - spawner_lock - .set(local_spawner) - .expect("Failed to set local spawner"); - }); + env.as_mut(&mut store).spawner = Some(local_spawner); + localpool .run_until(main_fn.call_async(&mut store, &[])) .unwrap(); - // Expected log order + return Ok(env.as_ref(&store).logs.clone()); +} + +#[cfg(not(target_arch = "wasm32"))] +#[test] +fn green_threads_switch_and_log_in_expected_order() -> Result<()> { + let logs = run_greenthread_test(include_bytes!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../tests/examples/simple-greenthread.wat" + )))?; + + // Expected logs let expected = [ "[gr1] main -> test1", "[gr2] test1 -> test2", @@ -246,13 +243,31 @@ fn green_threads_switch_and_log_in_expected_order() -> Result<()> { "[main] main <- test1", ]; - let logs = &env.as_ref(&store).logs; - assert_eq!( - logs.len(), - expected.len(), - "Unexpected number of log entries: {:?}", - logs - ); + assert_eq!(logs.len(), expected.len(),); + for (i, exp) in expected.iter().enumerate() { + assert_eq!(logs[i], *exp, "Log entry mismatch at index {i}: {:?}", logs); + } + + Ok(()) +} + +#[cfg(not(target_arch = "wasm32"))] +#[test] +fn green_threads_switch_main_crashed() -> Result<()> { + let logs = run_greenthread_test(include_bytes!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../tests/examples/simple-greenthread2.wat" + )))?; + + // Expected logs + let expected = [ + "[main] switching to side", + "[side] switching to main", + "[main] switching to side", + "[side] switching to main", + ]; + + assert_eq!(logs.len(), expected.len(),); for (i, exp) in expected.iter().enumerate() { assert_eq!(logs[i], *exp, "Log entry mismatch at index {i}: {:?}", logs); } diff --git a/tests/examples/simple-greenthread2.wat b/tests/examples/simple-greenthread2.wat new file mode 100644 index 00000000000..ad51945314f --- /dev/null +++ b/tests/examples/simple-greenthread2.wat @@ -0,0 +1,74 @@ +;; This example crashed in early versions of our async API because switching from the first context to another one worked only once. +(module + (import "test" "log" (func $log (param i32 i32))) + (import "test" "greenthread_new" (func $greenthread_new (param i32) (result i32))) + (import "test" "greenthread_switch" (func $greenthread_switch (param i32))) + + (memory (export "memory") 1) + + (data (i32.const 0) "[main] switching to side") + (data (i32.const 100) "[side] switching to main") + + (global $main (mut i32) (i32.const 0)) + (global $side (mut i32) (i32.const 0)) + + (func (export "_main") + ;; Setup thread ids + i32.const 0 + global.set $main + (call $greenthread_new (i32.const 0)) + global.set $side + + ;; Print [main] switching to side + i32.const 0 + i32.const 20 + call $log + + ;; Switch to side + global.get $side + (call $greenthread_switch) + + ;; Print [main] switching to side + i32.const 0 + i32.const 20 + call $log + + ;; Switch to side + global.get $side + (call $greenthread_switch) + + ;; Print [main] returned from side + i32.const 100 + i32.const 20 + call $log + ) + (func $side + ;; Print [side] switching to main + i32.const 100 + i32.const 20 + call $log + + ;; Switch to main + global.get $main + call $greenthread_switch + + ;; Print [side] switching to main + i32.const 100 + i32.const 20 + call $log + + ;; Switch to main + global.get $main + call $greenthread_switch + + unreachable + ) + (func (export "entrypoint") (param i32) + local.get 0 + i32.const 0 + i32.eq + call $side + unreachable + ) + +) From 2d6f4573dc47c62f3289c699f42038b4db73f267 Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Thu, 20 Nov 2025 18:53:27 +0400 Subject: [PATCH 19/49] Update simple-greenthread test w.r.t. new API --- lib/api/tests/simple-greenthread.rs | 125 +++++++++++++--------------- 1 file changed, 58 insertions(+), 67 deletions(-) diff --git a/lib/api/tests/simple-greenthread.rs b/lib/api/tests/simple-greenthread.rs index d53a439c83a..ceed8908c59 100644 --- a/lib/api/tests/simple-greenthread.rs +++ b/lib/api/tests/simple-greenthread.rs @@ -6,8 +6,8 @@ use anyhow::Result; use futures::task::LocalSpawnExt; use futures::{FutureExt, channel::oneshot}; use wasmer::{ - AsStoreMut, Function, FunctionEnv, FunctionEnvMut, FunctionType, Instance, Memory, Module, - RuntimeError, Store, StoreMut, Type, Value, imports, + AsyncFunctionEnvMut, Function, FunctionEnv, FunctionEnvMut, FunctionType, Instance, Memory, + Module, RuntimeError, Store, Type, Value, imports, }; struct GreenEnv { @@ -61,7 +61,8 @@ fn greenthread_new( mut env: FunctionEnvMut, entrypoint_data: u32, ) -> core::result::Result { - let (data, mut store) = env.data_and_store_mut(); + let async_store = env.as_async_store(); + let data = env.data_mut(); let new_greenthread_id = data .next_free_id .fetch_add(1, std::sync::atomic::Ordering::SeqCst); @@ -79,25 +80,12 @@ fn greenthread_new( .unwrap() .insert(new_greenthread_id, new_greenthread); - // SAFETY: This is only sound if the following invariants are upheld: - // A: The future spawned here must not outlive the lifetime of the store. - // In this test, we ensure that the store lives at least as long as the future, - // and the future completes before the store is dropped. - // B: There must not be multiple mutable references (no aliasing) to the store at the same time. - // We must ensure that the store is not accessed elsewhere while the future is running. - // If these invariants are not guaranteed, this code is unsound and may cause undefined behavior. - let mut unsafe_static_store = - unsafe { std::mem::transmute::<_, StoreMut<'static>>(store.as_store_mut()) }; - let spawner = env.data().spawner.as_ref().expect("spawner set").clone(); spawner .spawn_local(async move { receiver.await.unwrap(); let resumer = function - .call_async( - &mut unsafe_static_store, - &[Value::I32(entrypoint_data as i32)], - ) + .call_async(&async_store, &[Value::I32(entrypoint_data as i32)]) .await; panic!("Greenthread function returned {:?}", resumer); }) @@ -106,62 +94,61 @@ fn greenthread_new( Ok(new_greenthread_id) } -fn greenthread_switch( - mut env: FunctionEnvMut, - next_greenthread_id: u32, -) -> impl futures::Future> + use<> + Send { - let (data, _store) = env.data_and_store_mut(); - let current_greenthread_id = { - let mut current = data.current_greenthread_id.write().unwrap(); - let old = *current; - *current = next_greenthread_id; - old - }; - - if current_greenthread_id == next_greenthread_id { - panic!("Switching to self is not allowed"); - } +async fn greenthread_switch(env: AsyncFunctionEnvMut, next_greenthread_id: u32) { + let (receiver, current_id_arc, current_greenthread_id) = { + let mut write_lock = env.write().await; + let data = write_lock.data_mut(); - let (sender, receiver) = oneshot::channel::<()>(); + let current_greenthread_id = { + let mut current = data.current_greenthread_id.write().unwrap(); + let old = *current; + *current = next_greenthread_id; + old + }; - { - let mut greenthreads = data.greenthreads.write().unwrap(); - let this_one = greenthreads.get_mut(¤t_greenthread_id).unwrap(); - if this_one.resumer.is_some() { - panic!("Switching from a greenthread that is already switched out"); + if current_greenthread_id == next_greenthread_id { + panic!("Switching to self is not allowed"); } - this_one.resumer = Some(sender); - } - { - let mut greenthreads = data.greenthreads.write().unwrap(); - let next_one = greenthreads.get_mut(&next_greenthread_id).unwrap(); - let Some(resumer) = next_one.resumer.take() else { - panic!("Switching to greenthread that has no resumer"); - }; - resumer.send(()).unwrap(); - } + let (sender, receiver) = oneshot::channel::<()>(); - let current_id_arc = data.current_greenthread_id.clone(); + { + let mut greenthreads = data.greenthreads.write().unwrap(); + let this_one = greenthreads.get_mut(¤t_greenthread_id).unwrap(); + if this_one.resumer.is_some() { + panic!("Switching from a greenthread that is already switched out"); + } + this_one.resumer = Some(sender); + } - async move { - let _ = receiver.map(|_| ()).await; + { + let mut greenthreads = data.greenthreads.write().unwrap(); + let next_one = greenthreads.get_mut(&next_greenthread_id).unwrap(); + let Some(resumer) = next_one.resumer.take() else { + panic!("Switching to greenthread that has no resumer"); + }; + resumer.send(()).unwrap(); + } + let current_id_arc = data.current_greenthread_id.clone(); - *current_id_arc.write().unwrap() = current_greenthread_id; + (receiver, current_id_arc, current_greenthread_id) + }; - Ok(()) - } + let _ = receiver.map(|_| ()).await; + + *current_id_arc.write().unwrap() = current_greenthread_id; } fn run_greenthread_test(wat: &[u8]) -> Result> { let mut store = Store::default(); - let module = Module::new(&store, wat)?; + let module = Module::new(&store.engine(), wat)?; - let env = FunctionEnv::new(&mut store, GreenEnv::new()); + let mut store_mut = store.as_mut(); + let env = FunctionEnv::new(&mut store_mut, GreenEnv::new()); // log(ptr, len) let log_fn = Function::new_with_env( - &mut store, + &mut store_mut, &env, FunctionType::new(vec![Type::I32, Type::I32], vec![]), |mut env: FunctionEnvMut, params: &[Value]| { @@ -180,10 +167,10 @@ fn run_greenthread_test(wat: &[u8]) -> Result> { }, ); - let greenthread_new = Function::new_typed_with_env(&mut store, &env, greenthread_new); + let greenthread_new = Function::new_typed_with_env(&mut store_mut, &env, greenthread_new); let greenthread_switch = - Function::new_typed_with_env_async(&mut store, &env, greenthread_switch); + Function::new_typed_with_env_async(&mut store_mut, &env, greenthread_switch); let import_object = imports! { "test" => { @@ -193,13 +180,13 @@ fn run_greenthread_test(wat: &[u8]) -> Result> { } }; - let instance = Instance::new(&mut store, &module, &import_object)?; + let instance = Instance::new(&mut store_mut, &module, &import_object)?; let entrypoint = instance.exports.get_function("entrypoint")?.clone(); - env.as_mut(&mut store).entrypoint = Some(entrypoint); + env.as_mut(&mut store_mut).entrypoint = Some(entrypoint); let memory = instance.exports.get_memory("memory")?.clone(); - env.as_mut(&mut store).memory = Some(memory); + env.as_mut(&mut store_mut).memory = Some(memory); let main_fn = instance.exports.get_function("_main")?; @@ -208,7 +195,7 @@ fn run_greenthread_test(wat: &[u8]) -> Result> { resumer: None, }; - env.as_mut(&mut store) + env.as_mut(&mut store_mut) .greenthreads .write() .unwrap() @@ -216,13 +203,16 @@ fn run_greenthread_test(wat: &[u8]) -> Result> { let mut localpool = futures::executor::LocalPool::new(); let local_spawner = localpool.spawner(); - env.as_mut(&mut store).spawner = Some(local_spawner); + env.as_mut(&mut store_mut).spawner = Some(local_spawner); + + drop(store_mut); localpool - .run_until(main_fn.call_async(&mut store, &[])) + .run_until(main_fn.call_async(&store, &[])) .unwrap(); - return Ok(env.as_ref(&store).logs.clone()); + let mut store_mut = store.as_mut(); + return Ok(env.as_ref(&mut store_mut).logs.clone()); } #[cfg(not(target_arch = "wasm32"))] @@ -267,7 +257,8 @@ fn green_threads_switch_main_crashed() -> Result<()> { "[side] switching to main", ]; - assert_eq!(logs.len(), expected.len(),); + eprintln!("logs: {:?}", logs); + assert_eq!(logs.len(), expected.len()); for (i, exp) in expected.iter().enumerate() { assert_eq!(logs[i], *exp, "Log entry mismatch at index {i}: {:?}", logs); } From 36c1fad2d59638f8b6a7acf2f7a68aabd6747805 Mon Sep 17 00:00:00 2001 From: Zebreus Date: Fri, 21 Nov 2025 10:28:12 +0100 Subject: [PATCH 20/49] Fix not crashing greenthread test --- lib/api/tests/simple-greenthread.rs | 3 ++- tests/examples/simple-greenthread2.wat | 15 ++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/api/tests/simple-greenthread.rs b/lib/api/tests/simple-greenthread.rs index ceed8908c59..0bb735eb7b8 100644 --- a/lib/api/tests/simple-greenthread.rs +++ b/lib/api/tests/simple-greenthread.rs @@ -162,7 +162,7 @@ fn run_greenthread_test(wat: &[u8]) -> Result> { bytes.push(view.read_u8(i as u64).expect("in bounds")); } let s = String::from_utf8_lossy(&bytes).to_string(); - data.logs.push(s); + data.logs.push(s.trim_matches('\0').to_string()); Ok(vec![]) }, ); @@ -255,6 +255,7 @@ fn green_threads_switch_main_crashed() -> Result<()> { "[side] switching to main", "[main] switching to side", "[side] switching to main", + "[main] returned", ]; eprintln!("logs: {:?}", logs); diff --git a/tests/examples/simple-greenthread2.wat b/tests/examples/simple-greenthread2.wat index ad51945314f..064bb671d1b 100644 --- a/tests/examples/simple-greenthread2.wat +++ b/tests/examples/simple-greenthread2.wat @@ -8,6 +8,7 @@ (data (i32.const 0) "[main] switching to side") (data (i32.const 100) "[side] switching to main") + (data (i32.const 200) "[main] returned") (global $main (mut i32) (i32.const 0)) (global $side (mut i32) (i32.const 0)) @@ -21,7 +22,7 @@ ;; Print [main] switching to side i32.const 0 - i32.const 20 + i32.const 30 call $log ;; Switch to side @@ -30,22 +31,22 @@ ;; Print [main] switching to side i32.const 0 - i32.const 20 + i32.const 30 call $log ;; Switch to side global.get $side (call $greenthread_switch) - ;; Print [main] returned from side - i32.const 100 - i32.const 20 + ;; Print [main] returned + i32.const 200 + i32.const 30 call $log ) (func $side ;; Print [side] switching to main i32.const 100 - i32.const 20 + i32.const 30 call $log ;; Switch to main @@ -54,7 +55,7 @@ ;; Print [side] switching to main i32.const 100 - i32.const 20 + i32.const 30 call $log ;; Switch to main From fb77c93fab8a75691b32ecac7f70713d46d6075e Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Fri, 21 Nov 2025 15:59:09 +0400 Subject: [PATCH 21/49] Restrict concurrent executions against the same store to a single thread --- lib/api/src/backend/sys/async_runtime.rs | 6 +- .../src/backend/sys/entities/function/env.rs | 18 +- .../src/backend/sys/entities/function/mod.rs | 29 +- lib/api/src/entities/function/async_host.rs | 16 +- lib/api/src/entities/function/env/inner.rs | 12 +- lib/api/src/entities/function/env/mod.rs | 12 +- lib/api/src/entities/function/inner.rs | 20 +- lib/api/src/entities/function/mod.rs | 22 +- lib/api/src/entities/store/asynk.rs | 4 +- lib/api/src/entities/store/inner.rs | 4 - lib/api/src/entities/store/local_rwlock.rs | 797 ++++++++++++++++++ lib/api/src/entities/store/mod.rs | 24 +- lib/api/src/entities/store/store_ref.rs | 12 +- 13 files changed, 882 insertions(+), 94 deletions(-) create mode 100644 lib/api/src/entities/store/local_rwlock.rs diff --git a/lib/api/src/backend/sys/async_runtime.rs b/lib/api/src/backend/sys/async_runtime.rs index be45b0d8fa9..e8d484380dc 100644 --- a/lib/api/src/backend/sys/async_runtime.rs +++ b/lib/api/src/backend/sys/async_runtime.rs @@ -15,7 +15,7 @@ use super::entities::function::Function as SysFunction; use crate::{AsStoreMut, AsStoreRef, RuntimeError, Store, StoreContext, StoreMut, Value}; use wasmer_types::StoreId; -type HostFuture = Pin, RuntimeError>> + Send + 'static>>; +type HostFuture = Pin, RuntimeError>> + 'static>>; pub(crate) fn call_function_async<'a>( function: SysFunction, @@ -217,7 +217,7 @@ pub enum AsyncRuntimeError { pub(crate) fn block_on_host_future(future: Fut) -> Result, AsyncRuntimeError> where - Fut: Future, RuntimeError>> + Send + 'static, + Fut: Future, RuntimeError>> + 'static, { CURRENT_CONTEXT.with(|cell| { match CoroutineContext::get_current() { @@ -309,7 +309,7 @@ impl CoroutineContext { } fn run_immediate( - future: impl Future, RuntimeError>> + Send + 'static, + future: impl Future, RuntimeError>> + 'static, ) -> Result, AsyncRuntimeError> { fn noop_raw_waker() -> RawWaker { fn no_op(_: *const ()) {} diff --git a/lib/api/src/backend/sys/entities/function/env.rs b/lib/api/src/backend/sys/entities/function/env.rs index f06497a9350..79c55df9c74 100644 --- a/lib/api/src/backend/sys/entities/function/env.rs +++ b/lib/api/src/backend/sys/entities/function/env.rs @@ -35,7 +35,7 @@ impl FunctionEnv { /// Get the data as reference pub fn as_ref<'a>(&self, store: &'a impl AsStoreRef) -> &'a T where - T: Any + Send + 'static + Sized, + T: Any + 'static + Sized, { self.handle .get(store.objects().as_sys()) @@ -55,7 +55,7 @@ impl FunctionEnv { /// Get the data as mutable pub fn as_mut<'a>(&self, store: &'a mut impl AsStoreMut) -> &'a mut T where - T: Any + Send + 'static + Sized, + T: Any + 'static + Sized, { self.handle .get_mut(store.objects_mut().as_sys_mut()) @@ -67,7 +67,7 @@ impl FunctionEnv { /// Convert it into a `FunctionEnvMut` pub fn into_mut(self, store: &mut impl AsStoreMut) -> FunctionEnvMut<'_, T> where - T: Any + Send + 'static + Sized, + T: Any + 'static + Sized, { FunctionEnvMut { store_mut: store.reborrow_mut(), @@ -253,7 +253,7 @@ where } } -impl AsyncFunctionEnvMut { +impl AsyncFunctionEnvMut { pub(crate) fn store_id(&self) -> StoreId { self.store.id } @@ -305,7 +305,7 @@ impl AsyncFunctionEnvMut { } } -impl AsyncFunctionEnvHandle<'_, T> { +impl AsyncFunctionEnvHandle<'_, T> { /// Returns a reference to the host state in this function environment. pub fn data(&self) -> &T { self.func_env.as_ref(&self.read_lock) @@ -317,13 +317,13 @@ impl AsyncFunctionEnvHandle<'_, T> { } } -impl AsStoreRef for AsyncFunctionEnvHandle<'_, T> { +impl AsStoreRef for AsyncFunctionEnvHandle<'_, T> { fn as_ref(&self) -> &crate::StoreInner { AsStoreRef::as_ref(&self.read_lock) } } -impl AsyncFunctionEnvHandleMut<'_, T> { +impl AsyncFunctionEnvHandleMut<'_, T> { /// Returns a mutable reference to the host state in this function environment. pub fn data_mut(&mut self) -> &mut T { self.func_env.as_mut(&mut self.write_lock) @@ -342,13 +342,13 @@ impl AsyncFunctionEnvHandleMut<'_, T> { } } -impl AsStoreRef for AsyncFunctionEnvHandleMut<'_, T> { +impl AsStoreRef for AsyncFunctionEnvHandleMut<'_, T> { fn as_ref(&self) -> &crate::StoreInner { AsStoreRef::as_ref(&self.write_lock) } } -impl AsStoreMut for AsyncFunctionEnvHandleMut<'_, T> { +impl AsStoreMut for AsyncFunctionEnvHandleMut<'_, T> { fn as_mut(&mut self) -> &mut crate::StoreInner { AsStoreMut::as_mut(&mut self.write_lock) } diff --git a/lib/api/src/backend/sys/entities/function/mod.rs b/lib/api/src/backend/sys/entities/function/mod.rs index 913ff26a180..305ebd8fb9e 100644 --- a/lib/api/src/backend/sys/entities/function/mod.rs +++ b/lib/api/src/backend/sys/entities/function/mod.rs @@ -127,15 +127,15 @@ impl Function { pub(crate) fn new_async(store: &mut impl AsStoreMut, ty: FT, func: F) -> Self where FT: Into, - F: Fn(&[Value]) -> Fut + 'static + Send + Sync, - Fut: Future, RuntimeError>> + 'static + Send, + F: Fn(&[Value]) -> Fut + 'static, + Fut: Future, RuntimeError>> + 'static, { let env = FunctionEnv::new(store, ()); let wrapped = move |_env: AsyncFunctionEnvMut<()>, values: &[Value]| func(values); Self::new_with_env_async(store, &env, ty, wrapped) } - pub(crate) fn new_with_env_async( + pub(crate) fn new_with_env_async( store: &mut impl AsStoreMut, env: &FunctionEnv, ty: FT, @@ -143,8 +143,8 @@ impl Function { ) -> Self where FT: Into, - F: Fn(AsyncFunctionEnvMut, &[Value]) -> Fut + 'static + Send + Sync, - Fut: Future, RuntimeError>> + 'static + Send, + F: Fn(AsyncFunctionEnvMut, &[Value]) -> Fut + 'static, + Fut: Future, RuntimeError>> + 'static, { let function_type = ty.into(); let func_ty = function_type.clone(); @@ -176,8 +176,7 @@ impl Function { let future = func(env, &args); HostCallOutcome::Future { func_ty: sig, - future: Box::pin(future) - as Pin, RuntimeError>> + Send>>, + future: Box::pin(future), } } }; @@ -257,8 +256,8 @@ impl Function { pub(crate) fn new_typed_async(store: &mut impl AsStoreMut, func: F) -> Self where Args: WasmTypeList + 'static, - Rets: WasmTypeList + Send + 'static, - F: AsyncHostFunction<(), Args, Rets, WithoutEnv> + Send + Sync + 'static, + Rets: WasmTypeList + 'static, + F: AsyncHostFunction<(), Args, Rets, WithoutEnv> + 'static, { let env = FunctionEnv::new(store, ()); let signature = FunctionType::new(Args::wasm_types(), Rets::wasm_types()); @@ -266,7 +265,7 @@ impl Function { let results_sig = Arc::new(signature.clone()); let func = Arc::new(func); Self::new_with_env_async(store, &env, signature, move |mut env_mut, values| -> Pin< - Box, RuntimeError>> + Send>, + Box, RuntimeError>>>, > { let sys_env = match env_mut.0 { BackendAsyncFunctionEnvMut::Sys(ref mut sys_env) => sys_env, @@ -300,17 +299,17 @@ impl Function { func: F, ) -> Self where - T: Send + Sync + 'static, - F: AsyncHostFunction + Send + Sync + 'static, + T: 'static, + F: AsyncHostFunction + 'static, Args: WasmTypeList + 'static, - Rets: WasmTypeList + Send + 'static, + Rets: WasmTypeList + 'static, { let signature = FunctionType::new(Args::wasm_types(), Rets::wasm_types()); let args_sig = Arc::new(signature.clone()); let results_sig = Arc::new(signature.clone()); let func = Arc::new(func); Self::new_with_env_async(store, env, signature, move |mut env_mut, values| -> Pin< - Box, RuntimeError>> + Send>, + Box, RuntimeError>>>, > { let sys_env = match env_mut.0 { BackendAsyncFunctionEnvMut::Sys(ref mut sys_env) => sys_env, @@ -734,7 +733,7 @@ pub(crate) enum HostCallOutcome { }, Future { func_ty: FunctionType, - future: Pin, RuntimeError>> + Send>>, + future: Pin, RuntimeError>>>>, }, } diff --git a/lib/api/src/entities/function/async_host.rs b/lib/api/src/entities/function/async_host.rs index d2cc5d4f0dc..149f9c5c546 100644 --- a/lib/api/src/entities/function/async_host.rs +++ b/lib/api/src/entities/function/async_host.rs @@ -47,15 +47,15 @@ where &self, env: AsyncFunctionEnv, args: Args, - ) -> Pin> + Send>>; + ) -> Pin>>>; } macro_rules! impl_async_host_function_without_env { ( $( $x:ident ),* ) => { impl<$( $x, )* Rets, RetsAsResult, F, Fut > AsyncHostFunction<(), ( $( $x ),* ), Rets, WithoutEnv> for F where - F: Fn($( $x ),*) -> Fut + Send + Sync + 'static, - Fut: Future + Send + 'static, + F: Fn($( $x ),*) -> Fut + 'static, + Fut: Future + 'static, RetsAsResult: IntoResult, Rets: WasmTypeList, ( $( $x ),* ): WasmTypeList + 'static, @@ -65,7 +65,7 @@ macro_rules! impl_async_host_function_without_env { &self, _env: AsyncFunctionEnv<(), WithoutEnv>, args: ( $( $x ),* ), - ) -> Pin> + Send>> { + ) -> Pin>>> { #[allow(non_snake_case)] let ( $( $x ),* ) = args; let fut = (self)( $( $x ),* ); @@ -83,9 +83,9 @@ macro_rules! impl_async_host_function_with_env { ( $( $x:ident ),* ) => { impl<$( $x, )* Rets, RetsAsResult, T, F, Fut > AsyncHostFunction for F where - T: Send + 'static, - F: Fn(AsyncFunctionEnvMut, $( $x ),*) -> Fut + Send + 'static, - Fut: Future + Send + 'static, + T: 'static, + F: Fn(AsyncFunctionEnvMut, $( $x ),*) -> Fut + 'static, + Fut: Future + 'static, RetsAsResult: IntoResult, Rets: WasmTypeList, ( $( $x ),* ): WasmTypeList + 'static, @@ -95,7 +95,7 @@ macro_rules! impl_async_host_function_with_env { &self, env: AsyncFunctionEnv, args: ( $( $x ),* ), - ) -> Pin> + Send>> { + ) -> Pin>>> { #[allow(non_snake_case)] let ( $( $x ),* ) = args; let fut = (self)(env.into_env(), $( $x ),* ); diff --git a/lib/api/src/entities/function/env/inner.rs b/lib/api/src/entities/function/env/inner.rs index 1923d35dbc1..a65d73c4bb9 100644 --- a/lib/api/src/entities/function/env/inner.rs +++ b/lib/api/src/entities/function/env/inner.rs @@ -277,7 +277,7 @@ pub enum BackendAsyncFunctionEnvHandleMut<'a, T> { Sys(crate::backend::sys::function::env::AsyncFunctionEnvHandleMut<'a, T>), } -impl BackendAsyncFunctionEnvMut { +impl BackendAsyncFunctionEnvMut { /// Waits for a store lock and returns a read-only handle to the /// function environment. pub async fn read<'a>(&'a self) -> BackendAsyncFunctionEnvHandle<'a, T> { @@ -326,7 +326,7 @@ impl BackendAsyncFunctionEnvMut { } } -impl BackendAsyncFunctionEnvHandle<'_, T> { +impl BackendAsyncFunctionEnvHandle<'_, T> { /// Returns a reference to the host state in this function environment. pub fn data(&self) -> &T { match self { @@ -346,7 +346,7 @@ impl BackendAsyncFunctionEnvHandle<'_, T> { } } -impl AsStoreRef for BackendAsyncFunctionEnvHandle<'_, T> { +impl AsStoreRef for BackendAsyncFunctionEnvHandle<'_, T> { fn as_ref(&self) -> &crate::StoreInner { match self { #[cfg(feature = "sys")] @@ -356,7 +356,7 @@ impl AsStoreRef for BackendAsyncFunctionEnvHandle<'_, T> { } } -impl BackendAsyncFunctionEnvHandleMut<'_, T> { +impl BackendAsyncFunctionEnvHandleMut<'_, T> { /// Returns a mutable reference to the host state in this function environment. pub fn data_mut(&mut self) -> &mut T { match self { @@ -376,7 +376,7 @@ impl BackendAsyncFunctionEnvHandleMut<'_, T> { } } -impl AsStoreRef for BackendAsyncFunctionEnvHandleMut<'_, T> { +impl AsStoreRef for BackendAsyncFunctionEnvHandleMut<'_, T> { fn as_ref(&self) -> &crate::StoreInner { match self { #[cfg(feature = "sys")] @@ -386,7 +386,7 @@ impl AsStoreRef for BackendAsyncFunctionEnvHandleMut<'_, T> { } } -impl AsStoreMut for BackendAsyncFunctionEnvHandleMut<'_, T> { +impl AsStoreMut for BackendAsyncFunctionEnvHandleMut<'_, T> { fn as_mut(&mut self) -> &mut crate::StoreInner { match self { #[cfg(feature = "sys")] diff --git a/lib/api/src/entities/function/env/mod.rs b/lib/api/src/entities/function/env/mod.rs index c9123851105..b1eb5eed39e 100644 --- a/lib/api/src/entities/function/env/mod.rs +++ b/lib/api/src/entities/function/env/mod.rs @@ -133,7 +133,7 @@ pub struct AsyncFunctionEnvHandle<'a, T>(pub(crate) BackendAsyncFunctionEnvHandl /// while outside a store's context. pub struct AsyncFunctionEnvHandleMut<'a, T>(pub(crate) BackendAsyncFunctionEnvHandleMut<'a, T>); -impl AsyncFunctionEnvMut { +impl AsyncFunctionEnvMut { /// Waits for a store lock and returns a read-only handle to the /// function environment. pub async fn read<'a>(&'a self) -> AsyncFunctionEnvHandle<'a, T> { @@ -162,7 +162,7 @@ impl AsyncFunctionEnvMut { } } -impl AsyncFunctionEnvHandle<'_, T> { +impl AsyncFunctionEnvHandle<'_, T> { /// Returns a reference to the host state in this function environment. pub fn data(&self) -> &T { self.0.data() @@ -174,13 +174,13 @@ impl AsyncFunctionEnvHandle<'_, T> { } } -impl AsStoreRef for AsyncFunctionEnvHandle<'_, T> { +impl AsStoreRef for AsyncFunctionEnvHandle<'_, T> { fn as_ref(&self) -> &crate::StoreInner { AsStoreRef::as_ref(&self.0) } } -impl AsyncFunctionEnvHandleMut<'_, T> { +impl AsyncFunctionEnvHandleMut<'_, T> { /// Returns a mutable reference to the host state in this function environment. pub fn data_mut(&mut self) -> &mut T { self.0.data_mut() @@ -192,13 +192,13 @@ impl AsyncFunctionEnvHandleMut<'_, T> { } } -impl AsStoreRef for AsyncFunctionEnvHandleMut<'_, T> { +impl AsStoreRef for AsyncFunctionEnvHandleMut<'_, T> { fn as_ref(&self) -> &crate::StoreInner { AsStoreRef::as_ref(&self.0) } } -impl AsStoreMut for AsyncFunctionEnvHandleMut<'_, T> { +impl AsStoreMut for AsyncFunctionEnvHandleMut<'_, T> { fn as_mut(&mut self) -> &mut crate::StoreInner { AsStoreMut::as_mut(&mut self.0) } diff --git a/lib/api/src/entities/function/inner.rs b/lib/api/src/entities/function/inner.rs index 0a720d8cd54..6bd07452d8b 100644 --- a/lib/api/src/entities/function/inner.rs +++ b/lib/api/src/entities/function/inner.rs @@ -258,8 +258,8 @@ impl BackendFunction { pub fn new_async(store: &mut impl AsStoreMut, ty: FT, func: F) -> Self where FT: Into, - F: Fn(&[Value]) -> Fut + 'static + Send + Sync, - Fut: Future, RuntimeError>> + 'static + Send, + F: Fn(&[Value]) -> Fut + 'static, + Fut: Future, RuntimeError>> + 'static, { match &store.as_mut().store { #[cfg(feature = "sys")] @@ -280,7 +280,7 @@ impl BackendFunction { } #[inline] - pub fn new_with_env_async( + pub fn new_with_env_async( store: &mut impl AsStoreMut, env: &FunctionEnv, ty: FT, @@ -288,8 +288,8 @@ impl BackendFunction { ) -> Self where FT: Into, - F: Fn(AsyncFunctionEnvMut, &[Value]) -> Fut + 'static + Send + Sync, - Fut: Future, RuntimeError>> + 'static + Send, + F: Fn(AsyncFunctionEnvMut, &[Value]) -> Fut + 'static, + Fut: Future, RuntimeError>> + 'static, { match &store.as_mut().store { #[cfg(feature = "sys")] @@ -314,9 +314,9 @@ impl BackendFunction { #[inline] pub fn new_typed_async(store: &mut impl AsStoreMut, func: F) -> Self where - F: AsyncHostFunction<(), Args, Rets, WithoutEnv> + Send + Sync + 'static, + F: AsyncHostFunction<(), Args, Rets, WithoutEnv> + 'static, Args: WasmTypeList + 'static, - Rets: WasmTypeList + Send + 'static, + Rets: WasmTypeList + 'static, { match &store.as_mut().store { #[cfg(feature = "sys")] @@ -337,15 +337,15 @@ impl BackendFunction { } #[inline] - pub fn new_typed_with_env_async( + pub fn new_typed_with_env_async( store: &mut impl AsStoreMut, env: &FunctionEnv, func: F, ) -> Self where - F: AsyncHostFunction + Send + Sync + 'static, + F: AsyncHostFunction + 'static, Args: WasmTypeList + 'static, - Rets: WasmTypeList + Send + 'static, + Rets: WasmTypeList + 'static, { match &store.as_mut().store { #[cfg(feature = "sys")] diff --git a/lib/api/src/entities/function/mod.rs b/lib/api/src/entities/function/mod.rs index ef9d7ddfd58..fe480996247 100644 --- a/lib/api/src/entities/function/mod.rs +++ b/lib/api/src/entities/function/mod.rs @@ -167,15 +167,15 @@ impl Function { pub fn new_async(store: &mut impl AsStoreMut, ty: FT, func: F) -> Self where FT: Into, - F: Fn(&[Value]) -> Fut + 'static + Send + Sync, - Fut: Future, RuntimeError>> + 'static + Send, + F: Fn(&[Value]) -> Fut + 'static, + Fut: Future, RuntimeError>> + 'static, { Self(BackendFunction::new_async(store, ty, func)) } /// Creates a new async host `Function` (dynamic) with the provided signature /// and environment. - pub fn new_with_env_async( + pub fn new_with_env_async( store: &mut impl AsStoreMut, env: &FunctionEnv, ty: FT, @@ -183,8 +183,8 @@ impl Function { ) -> Self where FT: Into, - F: Fn(AsyncFunctionEnvMut, &[Value]) -> Fut + 'static + Send + Sync, - Fut: Future, RuntimeError>> + 'static + Send, + F: Fn(AsyncFunctionEnvMut, &[Value]) -> Fut + 'static, + Fut: Future, RuntimeError>> + 'static, { Self(BackendFunction::new_with_env_async(store, env, ty, func)) } @@ -192,26 +192,26 @@ impl Function { /// Creates a new async host `Function` from a native typed function. /// /// The future can return either the raw result tuple or any type that implements - /// [`IntoResult`] for the result tuple (e.g. `Result`). pub fn new_typed_async(store: &mut impl AsStoreMut, func: F) -> Self where - Rets: WasmTypeList + Send + 'static, + Rets: WasmTypeList + 'static, Args: WasmTypeList + 'static, - F: AsyncHostFunction<(), Args, Rets, WithoutEnv> + Send + Sync + 'static, + F: AsyncHostFunction<(), Args, Rets, WithoutEnv> + 'static, { Self(BackendFunction::new_typed_async(store, func)) } /// Creates a new async host `Function` with an environment from a typed function. - pub fn new_typed_with_env_async( + pub fn new_typed_with_env_async( store: &mut impl AsStoreMut, env: &FunctionEnv, func: F, ) -> Self where - Rets: WasmTypeList + Send + 'static, + Rets: WasmTypeList + 'static, Args: WasmTypeList + 'static, - F: AsyncHostFunction + Send + Sync + 'static, + F: AsyncHostFunction + 'static, { Self(BackendFunction::new_typed_with_env_async(store, env, func)) } diff --git a/lib/api/src/entities/store/asynk.rs b/lib/api/src/entities/store/asynk.rs index 0e8f181d8a9..16482e53218 100644 --- a/lib/api/src/entities/store/asynk.rs +++ b/lib/api/src/entities/store/asynk.rs @@ -29,14 +29,14 @@ pub trait AsAsyncStore { } /// Acquires a read lock on the store. - fn read_lock<'a>(&'a self) -> impl Future> + Send + Sync + 'a { + fn read_lock<'a>(&'a self) -> impl Future> + 'a { AsyncStoreReadLock::acquire(self.store_ref()) } /// Acquires a write lock on the store. fn write_lock<'a>( &'a self, - ) -> impl Future> + Send + Sync + 'a { + ) -> impl Future> + 'a { AsyncStoreWriteLock::acquire(self.store_ref()) } } diff --git a/lib/api/src/entities/store/inner.rs b/lib/api/src/entities/store/inner.rs index fe9bf5c2c53..56d52806f9c 100644 --- a/lib/api/src/entities/store/inner.rs +++ b/lib/api/src/entities/store/inner.rs @@ -20,10 +20,6 @@ pub struct StoreInner { pub(crate) on_called: Option, } -// TODO: async api - figure this out! -unsafe impl Send for StoreInner {} -unsafe impl Sync for StoreInner {} - impl std::fmt::Debug for StoreInner { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.debug_struct("StoreInner") diff --git a/lib/api/src/entities/store/local_rwlock.rs b/lib/api/src/entities/store/local_rwlock.rs new file mode 100644 index 00000000000..9694f9a3139 --- /dev/null +++ b/lib/api/src/entities/store/local_rwlock.rs @@ -0,0 +1,797 @@ +//! A single-threaded async-aware RwLock implementation. +//! +//! This module provides [`LocalRwLock`], which is similar to `async_lock::RwLock` +//! but optimized for single-threaded async runtimes. It avoids atomic operations +//! and is `!Send + !Sync`, making it more efficient when thread safety is not needed. +//! +//! Like `async_lock::RwLock`, it provides `read_rc()` and `write_rc()` methods +//! that return guards with `'static` lifetimes by holding an `Rc` to the lock. + +use std::cell::{Cell, RefCell, UnsafeCell}; +use std::future::Future; +use std::marker::PhantomData; +use std::ops::{Deref, DerefMut}; +use std::pin::Pin; +use std::rc::Rc; +use std::task::{Context, Poll, Waker}; + +/// A single-threaded async-aware read-write lock. +/// +/// This lock allows multiple concurrent readers or a single writer. +/// When a lock is not available, tasks will wait asynchronously rather +/// than blocking or panicking. +/// +/// Unlike `std::sync::RwLock` or `async_lock::RwLock`, this implementation +/// is not `Send` or `Sync`, making it suitable only for single-threaded +/// async runtimes. The benefit is zero atomic operations. +pub struct LocalRwLock { + inner: Rc>, +} + +struct LocalRwLockInner { + value: UnsafeCell, + state: Cell, + read_waiters: RefCell>>, + write_waiters: RefCell>>, +} + +#[derive(Clone, Copy, PartialEq, Debug)] +enum LockState { + Unlocked, + Reading(usize), // count of readers + Writing, +} + +impl LocalRwLock { + /// Creates a new `LocalRwLock` with the given value. + pub fn new(value: T) -> Self { + Self { + inner: Rc::new(LocalRwLockInner { + value: UnsafeCell::new(value), + state: Cell::new(LockState::Unlocked), + read_waiters: RefCell::new(Vec::new()), + write_waiters: RefCell::new(Vec::new()), + }), + } + } + + /// Attempts to acquire a read lock without waiting. + /// + /// Returns `Some(guard)` if the lock was acquired, or `None` if a writer + /// currently holds the lock. + pub fn try_read(&self) -> Option> { + if self.inner.try_read() { + Some(LocalReadGuard { + inner: &self.inner, + _marker: PhantomData, + }) + } else { + None + } + } + + /// Attempts to acquire a write lock without waiting. + /// + /// Returns `Some(guard)` if the lock was acquired, or `None` if any + /// readers or writers currently hold the lock. + pub fn try_write(&self) -> Option> { + if self.inner.try_write() { + Some(LocalWriteGuard { + inner: &self.inner, + _marker: PhantomData, + }) + } else { + None + } + } + + /// Acquires a read lock, waiting asynchronously if necessary. + /// + /// Returns a future that resolves to a read guard. + pub fn read(&self) -> ReadFuture<'_, T> { + ReadFuture { + inner: &self.inner, + waiter_index: Cell::new(None), + _marker: PhantomData, + } + } + + /// Acquires a write lock, waiting asynchronously if necessary. + /// + /// Returns a future that resolves to a write guard. + pub fn write(&self) -> WriteFuture<'_, T> { + WriteFuture { + inner: &self.inner, + waiter_index: Cell::new(None), + _marker: PhantomData, + } + } + + /// Acquires a read lock with a `'static` lifetime, waiting asynchronously if necessary. + /// + /// This is similar to `read()`, but the returned guard holds an `Rc` clone, + /// allowing it to have a `'static` lifetime. + pub async fn read_rc(&self) -> LocalReadGuardRc { + ReadRcFuture { + inner: self.inner.clone(), + waiter_index: Cell::new(None), + } + .await + } + + /// Acquires a write lock with a `'static` lifetime, waiting asynchronously if necessary. + /// + /// This is similar to `write()`, but the returned guard holds an `Rc` clone, + /// allowing it to have a `'static` lifetime. + pub async fn write_rc(&self) -> LocalWriteGuardRc { + WriteRcFuture { + inner: self.inner.clone(), + waiter_index: Cell::new(None), + } + .await + } + + /// Attempts to acquire a read lock with a `'static` lifetime without waiting. + pub fn try_read_rc(&self) -> Option> { + if self.inner.try_read() { + Some(LocalReadGuardRc { + inner: self.inner.clone(), + }) + } else { + None + } + } + + /// Attempts to acquire a write lock with a `'static` lifetime without waiting. + pub fn try_write_rc(&self) -> Option> { + if self.inner.try_write() { + Some(LocalWriteGuardRc { + inner: self.inner.clone(), + }) + } else { + None + } + } +} + +impl Clone for LocalRwLock { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + } + } +} + +impl LocalRwLockInner { + fn try_read(&self) -> bool { + match self.state.get() { + LockState::Unlocked => { + self.state.set(LockState::Reading(1)); + true + } + LockState::Reading(n) => { + self.state.set(LockState::Reading(n + 1)); + true + } + LockState::Writing => false, + } + } + + fn try_write(&self) -> bool { + match self.state.get() { + LockState::Unlocked => { + self.state.set(LockState::Writing); + true + } + _ => false, + } + } + + fn release_read(&self) { + match self.state.get() { + LockState::Reading(1) => { + self.state.set(LockState::Unlocked); + self.wake_waiters(); + } + LockState::Reading(n) if n > 1 => { + self.state.set(LockState::Reading(n - 1)); + } + _ => panic!("LocalRwLock: release_read called but not in Reading state"), + } + } + + fn release_write(&self) { + match self.state.get() { + LockState::Writing => { + self.state.set(LockState::Unlocked); + self.wake_waiters(); + } + _ => panic!("LocalRwLock: release_write called but not in Writing state"), + } + } + + fn wake_waiters(&self) { + // Wake writers first (priority) + let mut write_waiters = self.write_waiters.borrow_mut(); + let has_writers = write_waiters.iter().any(|w| w.is_some()); + + if has_writers { + // If there are waiting writers, only wake them (they need exclusive access) + for waker_slot in write_waiters.drain(..) { + if let Some(waker) = waker_slot { + waker.wake(); + } + } + } else { + // No writers waiting, wake all readers (they can share the lock) + drop(write_waiters); // Release borrow before borrowing read_waiters + let mut read_waiters = self.read_waiters.borrow_mut(); + for waker_slot in read_waiters.drain(..) { + if let Some(waker) = waker_slot { + waker.wake(); + } + } + } + } + + /// Shared polling logic for all futures. + /// Returns Poll::Ready(()) if the lock was acquired, Poll::Pending otherwise. + fn poll_lock( + &self, + waiter_index: &Cell>, + cx: &mut Context<'_>, + try_lock: impl FnOnce(&Self) -> bool, + is_write: bool, + ) -> Poll<()> { + if try_lock(self) { + // If we successfully acquired the lock, remove our waiter slot if we registered one + if let Some(index) = waiter_index.get() { + let waiters = if is_write { + &self.write_waiters + } else { + &self.read_waiters + }; + let mut waiters = waiters.borrow_mut(); + if index < waiters.len() { + waiters[index] = None; + } + } + return Poll::Ready(()); + } + + // Register or update our waker in the appropriate waiters list + let waiters = if is_write { + &self.write_waiters + } else { + &self.read_waiters + }; + let mut waiters = waiters.borrow_mut(); + + if let Some(index) = waiter_index.get() { + // We already have a slot, check if we need to update it + if index < waiters.len() { + if let Some(existing) = &waiters[index] { + if !existing.will_wake(cx.waker()) { + waiters[index] = Some(cx.waker().clone()); + } + } else { + waiters[index] = Some(cx.waker().clone()); + } + } else { + // Our slot was somehow removed, register a new one + let new_index = waiters.len(); + waiters.push(Some(cx.waker().clone())); + waiter_index.set(Some(new_index)); + } + } else { + // First time registering + let index = waiters.len(); + waiters.push(Some(cx.waker().clone())); + waiter_index.set(Some(index)); + } + + Poll::Pending + } + + /// Cleanup waiter slot on drop + fn cleanup_waiter(&self, waiter_index: &Cell>, is_write: bool) { + if let Some(index) = waiter_index.get() { + let waiters = if is_write { + &self.write_waiters + } else { + &self.read_waiters + }; + let mut waiters = waiters.borrow_mut(); + if index < waiters.len() { + waiters[index] = None; + } + } + } +} + +// Guards with borrowed lifetime + +/// A read guard with a borrowed lifetime. +pub struct LocalReadGuard<'a, T> { + inner: &'a LocalRwLockInner, + _marker: PhantomData<&'a T>, +} + +impl Deref for LocalReadGuard<'_, T> { + type Target = T; + + fn deref(&self) -> &T { + unsafe { &*self.inner.value.get() } + } +} + +impl Drop for LocalReadGuard<'_, T> { + fn drop(&mut self) { + self.inner.release_read(); + } +} + +/// A write guard with a borrowed lifetime. +pub struct LocalWriteGuard<'a, T> { + inner: &'a LocalRwLockInner, + _marker: PhantomData<&'a mut T>, +} + +impl Deref for LocalWriteGuard<'_, T> { + type Target = T; + + fn deref(&self) -> &T { + unsafe { &*self.inner.value.get() } + } +} + +impl DerefMut for LocalWriteGuard<'_, T> { + fn deref_mut(&mut self) -> &mut T { + unsafe { &mut *self.inner.value.get() } + } +} + +impl Drop for LocalWriteGuard<'_, T> { + fn drop(&mut self) { + self.inner.release_write(); + } +} + +// Guards with 'static lifetime (Rc-like) + +/// A read guard with a `'static` lifetime, holding an `Rc` to the lock. +pub struct LocalReadGuardRc { + inner: Rc>, +} + +impl Deref for LocalReadGuardRc { + type Target = T; + + fn deref(&self) -> &T { + unsafe { &*self.inner.value.get() } + } +} + +impl Drop for LocalReadGuardRc { + fn drop(&mut self) { + self.inner.release_read(); + } +} + +/// A write guard with a `'static` lifetime, holding an `Rc` to the lock. +pub struct LocalWriteGuardRc { + inner: Rc>, +} + +impl Deref for LocalWriteGuardRc { + type Target = T; + + fn deref(&self) -> &T { + unsafe { &*self.inner.value.get() } + } +} + +impl DerefMut for LocalWriteGuardRc { + fn deref_mut(&mut self) -> &mut T { + unsafe { &mut *self.inner.value.get() } + } +} + +impl Drop for LocalWriteGuardRc { + fn drop(&mut self) { + self.inner.release_write(); + } +} + +// Futures + +/// Future returned by `read()`. +pub struct ReadFuture<'a, T> { + inner: &'a LocalRwLockInner, + waiter_index: Cell>, + _marker: PhantomData<&'a T>, +} + +impl<'a, T> Future for ReadFuture<'a, T> { + type Output = LocalReadGuard<'a, T>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if self + .inner + .poll_lock(&self.waiter_index, cx, |inner| inner.try_read(), false) + .is_ready() + { + Poll::Ready(LocalReadGuard { + inner: self.inner, + _marker: PhantomData, + }) + } else { + Poll::Pending + } + } +} + +impl<'a, T> Drop for ReadFuture<'a, T> { + fn drop(&mut self) { + self.inner.cleanup_waiter(&self.waiter_index, false); + } +} + +/// Future returned by `write()`. +pub struct WriteFuture<'a, T> { + inner: &'a LocalRwLockInner, + waiter_index: Cell>, + _marker: PhantomData<&'a mut T>, +} + +impl<'a, T> Future for WriteFuture<'a, T> { + type Output = LocalWriteGuard<'a, T>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if self + .inner + .poll_lock(&self.waiter_index, cx, |inner| inner.try_write(), true) + .is_ready() + { + Poll::Ready(LocalWriteGuard { + inner: self.inner, + _marker: PhantomData, + }) + } else { + Poll::Pending + } + } +} + +impl<'a, T> Drop for WriteFuture<'a, T> { + fn drop(&mut self) { + self.inner.cleanup_waiter(&self.waiter_index, true); + } +} + +/// Future returned by `read_rc()`. +pub struct ReadRcFuture { + inner: Rc>, + waiter_index: Cell>, +} + +impl Future for ReadRcFuture { + type Output = LocalReadGuardRc; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if self + .inner + .poll_lock(&self.waiter_index, cx, |inner| inner.try_read(), false) + .is_ready() + { + Poll::Ready(LocalReadGuardRc { + inner: self.inner.clone(), + }) + } else { + Poll::Pending + } + } +} + +impl Drop for ReadRcFuture { + fn drop(&mut self) { + self.inner.cleanup_waiter(&self.waiter_index, false); + } +} + +/// Future returned by `write_rc()`. +pub struct WriteRcFuture { + inner: Rc>, + waiter_index: Cell>, +} + +impl Future for WriteRcFuture { + type Output = LocalWriteGuardRc; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if self + .inner + .poll_lock(&self.waiter_index, cx, |inner| inner.try_write(), true) + .is_ready() + { + Poll::Ready(LocalWriteGuardRc { + inner: self.inner.clone(), + }) + } else { + Poll::Pending + } + } +} + +impl Drop for WriteRcFuture { + fn drop(&mut self) { + self.inner.cleanup_waiter(&self.waiter_index, true); + } +} + +// Note: LocalRwLock is NOT Send or Sync because it uses Rc and RefCell. +// This is intentional - it's designed for single-threaded async contexts only. +// The Rc> automatically makes the type !Send + !Sync. + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_basic_read_write() { + let lock = LocalRwLock::new(42); + + // Can acquire read lock + let guard = lock.try_read().unwrap(); + assert_eq!(*guard, 42); + drop(guard); + + // Can acquire write lock + let mut guard = lock.try_write().unwrap(); + *guard = 100; + drop(guard); + + // Value was updated + let guard = lock.try_read().unwrap(); + assert_eq!(*guard, 100); + } + + #[test] + fn test_multiple_readers() { + let lock = LocalRwLock::new(42); + + let guard1 = lock.try_read().unwrap(); + let guard2 = lock.try_read().unwrap(); + let guard3 = lock.try_read().unwrap(); + + assert_eq!(*guard1, 42); + assert_eq!(*guard2, 42); + assert_eq!(*guard3, 42); + + // Cannot acquire write lock while readers exist + assert!(lock.try_write().is_none()); + + drop(guard1); + assert!(lock.try_write().is_none()); + + drop(guard2); + assert!(lock.try_write().is_none()); + + drop(guard3); + // Now we can acquire write lock + assert!(lock.try_write().is_some()); + } + + #[test] + fn test_exclusive_writer() { + let lock = LocalRwLock::new(42); + + let guard = lock.try_write().unwrap(); + + // Cannot acquire another write lock + assert!(lock.try_write().is_none()); + + // Cannot acquire read lock + assert!(lock.try_read().is_none()); + + drop(guard); + + // Now we can acquire locks again + assert!(lock.try_read().is_some()); + } + + #[test] + fn test_arc_guards() { + let lock = LocalRwLock::new(42); + + // Try read_rc + let guard = lock.try_read_rc().unwrap(); + assert_eq!(*guard, 42); + drop(guard); + + // Try write_rc + let mut guard = lock.try_write_rc().unwrap(); + *guard = 100; + drop(guard); + + let guard = lock.try_read_rc().unwrap(); + assert_eq!(*guard, 100); + } + + #[test] + fn test_writer_priority() { + use std::sync::{Arc, Mutex}; + use std::task::{Poll, Wake}; + + // Custom waker that tracks when it's woken + struct TrackingWaker { + name: &'static str, + wake_order: Arc>>, + } + + impl Wake for TrackingWaker { + fn wake(self: Arc) { + self.wake_order.lock().unwrap().push(self.name); + } + } + + let lock = LocalRwLock::new(0); + let wake_order = Arc::new(Mutex::new(Vec::new())); + + // Acquire write lock + let write_guard = lock.try_write().unwrap(); + + // Create read and write futures that will need to wait + let mut read_future = Box::pin(lock.read()); + let mut write_future = Box::pin(lock.write()); + + // Create tracking wakers + let read_waker = Arc::new(TrackingWaker { + name: "reader", + wake_order: wake_order.clone(), + }) + .into(); + let mut read_cx = std::task::Context::from_waker(&read_waker); + + let write_waker = Arc::new(TrackingWaker { + name: "writer", + wake_order: wake_order.clone(), + }) + .into(); + let mut write_cx = std::task::Context::from_waker(&write_waker); + + // Poll both to register them as waiters + assert!(matches!( + write_future.as_mut().poll(&mut write_cx), + Poll::Pending + )); + assert!(matches!( + read_future.as_mut().poll(&mut read_cx), + Poll::Pending + )); + + // Verify they're in separate queues + assert_eq!( + lock.inner + .write_waiters + .borrow() + .iter() + .filter(|w| w.is_some()) + .count(), + 1 + ); + assert_eq!( + lock.inner + .read_waiters + .borrow() + .iter() + .filter(|w| w.is_some()) + .count(), + 1 + ); + + // No wakers called yet + assert_eq!(wake_order.lock().unwrap().len(), 0); + + // Release the write lock - this should wake waiters + drop(write_guard); + + // Verify ONLY writer was woken (not reader, since writer has priority) + let order = wake_order.lock().unwrap(); + assert_eq!( + order.len(), + 1, + "Only writer should be woken when writers are waiting" + ); + assert_eq!(order[0], "writer", "Writer should be woken, not reader"); + } + + #[test] + fn test_readers_woken_when_no_writers() { + use std::sync::{Arc, Mutex}; + use std::task::{Poll, Wake}; + + // Custom waker that tracks when it's woken + struct TrackingWaker { + name: String, + wake_order: Arc>>, + } + + impl Wake for TrackingWaker { + fn wake(self: Arc) { + self.wake_order.lock().unwrap().push(self.name.clone()); + } + } + + let lock = LocalRwLock::new(0); + let wake_order = Arc::new(Mutex::new(Vec::new())); + + // Acquire write lock + let write_guard = lock.try_write().unwrap(); + + // Create multiple read futures (no writers) + let mut read_future1 = Box::pin(lock.read()); + let mut read_future2 = Box::pin(lock.read()); + + // Create tracking wakers + let read_waker1 = Arc::new(TrackingWaker { + name: "reader1".to_string(), + wake_order: wake_order.clone(), + }) + .into(); + let mut read_cx1 = std::task::Context::from_waker(&read_waker1); + + let read_waker2 = Arc::new(TrackingWaker { + name: "reader2".to_string(), + wake_order: wake_order.clone(), + }) + .into(); + let mut read_cx2 = std::task::Context::from_waker(&read_waker2); + + // Poll both to register them as waiters + assert!(matches!( + read_future1.as_mut().poll(&mut read_cx1), + Poll::Pending + )); + assert!(matches!( + read_future2.as_mut().poll(&mut read_cx2), + Poll::Pending + )); + + // Verify they're in read queue + assert_eq!( + lock.inner + .read_waiters + .borrow() + .iter() + .filter(|w| w.is_some()) + .count(), + 2 + ); + assert_eq!( + lock.inner + .write_waiters + .borrow() + .iter() + .filter(|w| w.is_some()) + .count(), + 0 + ); + + // No wakers called yet + assert_eq!(wake_order.lock().unwrap().len(), 0); + + // Release the write lock - this should wake all readers since no writers are waiting + drop(write_guard); + + // Verify both readers were woken + let order = wake_order.lock().unwrap(); + assert_eq!( + order.len(), + 2, + "Both readers should be woken when no writers are waiting" + ); + assert!(order.contains(&"reader1".to_string())); + assert!(order.contains(&"reader2".to_string())); + } +} diff --git a/lib/api/src/entities/store/mod.rs b/lib/api/src/entities/store/mod.rs index 9dee457fb6a..20969ef6e1d 100644 --- a/lib/api/src/entities/store/mod.rs +++ b/lib/api/src/entities/store/mod.rs @@ -14,11 +14,11 @@ mod inner; /// Create temporary handles to engines. mod store_ref; -use async_lock::RwLock; -use std::{ - ops::{Deref, DerefMut}, - sync::{Arc, TryLockError}, -}; +/// Single-threaded async-aware RwLock. +mod local_rwlock; +pub(crate) use local_rwlock::*; + +use std::ops::{Deref, DerefMut}; pub use store_ref::*; @@ -45,7 +45,7 @@ use wasmer_vm::TrapHandlerFn; /// [related WebAssembly specification]: pub struct Store { pub(crate) id: StoreId, - pub(crate) inner: Arc>, + pub(crate) inner: LocalRwLock, } impl Store { @@ -83,31 +83,31 @@ impl Store { let objects = StoreObjects::from_store_ref(&store); Self { id: objects.id(), - inner: std::sync::Arc::new(async_lock::RwLock::new(StoreInner { + inner: LocalRwLock::new(StoreInner { objects, on_called: None, store, - })), + }), } } /// Creates a new [`StoreRef`] if the store is available for reading. pub(crate) fn try_make_ref(&self) -> Option { self.inner - .try_read_arc() + .try_read_rc() .map(|guard| StoreRef { inner: guard }) } /// Waits for the store to become available and creates a new /// [`StoreRef`] afterwards. pub(crate) async fn make_ref_async(&self) -> StoreRef { - let guard = self.inner.read_arc().await; + let guard = self.inner.read_rc().await; StoreRef { inner: guard } } /// Creates a new [`StoreMut`] if the store is available for writing. pub(crate) fn try_make_mut(&self) -> Option { - self.inner.try_write_arc().map(|guard| StoreMut { + self.inner.try_write_rc().map(|guard| StoreMut { inner: guard, store_handle: crate::Store { id: self.id, @@ -119,7 +119,7 @@ impl Store { /// Waits for the store to become available and creates a new /// [`StoreMut`] afterwards. pub(crate) async fn make_mut_async(&self) -> StoreMut { - let guard = self.inner.write_arc().await; + let guard = self.inner.write_rc().await; StoreMut { inner: guard, store_handle: Self { diff --git a/lib/api/src/entities/store/store_ref.rs b/lib/api/src/entities/store/store_ref.rs index 669a59e5ba6..354b15ce575 100644 --- a/lib/api/src/entities/store/store_ref.rs +++ b/lib/api/src/entities/store/store_ref.rs @@ -1,9 +1,6 @@ -use std::{ - ops::{Deref, DerefMut}, - sync::Arc, -}; +use std::ops::{Deref, DerefMut}; -use super::{StoreObjects, inner::StoreInner}; +use super::{local_rwlock::{LocalReadGuardRc, LocalWriteGuardRc}, inner::StoreInner, StoreObjects}; use crate::{ Store, entities::engine::{AsEngineRef, Engine, EngineRef}, @@ -14,9 +11,8 @@ use wasmer_types::{ExternType, OnCalledAction, StoreId}; use wasmer_vm::TrapHandlerFn; /// A temporary handle to a [`crate::Store`]. -#[derive(Debug)] pub struct StoreRef { - pub(crate) inner: async_lock::RwLockReadGuardArc, + pub(crate) inner: LocalReadGuardRc, } impl StoreRef { @@ -27,7 +23,7 @@ impl StoreRef { /// A temporary handle to a [`crate::Store`]. pub struct StoreMut { - pub(crate) inner: async_lock::RwLockWriteGuardArc, + pub(crate) inner: LocalWriteGuardRc, // Also keep an Arc to the store itself, so we can recreate // the store for async functions. From b82c860f9031a074751a469c3ed73814612b8049 Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Mon, 24 Nov 2025 13:02:32 +0400 Subject: [PATCH 22/49] Fix build of wasmer-compiler crate without the compiler feature --- lib/compiler/src/engine/artifact.rs | 2 +- lib/compiler/src/engine/inner.rs | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/compiler/src/engine/artifact.rs b/lib/compiler/src/engine/artifact.rs index 2189bf80e4a..23a8be9fdeb 100644 --- a/lib/compiler/src/engine/artifact.rs +++ b/lib/compiler/src/engine/artifact.rs @@ -454,7 +454,7 @@ impl Artifact { get_got_address(RelocationTarget::LibCall(wasmer_vm::LibCall::EHPersonality)), )?; - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(not(target_arch = "wasm32"), feature = "compiler"))] { engine_inner.register_perfmap(&finished_functions, module_info)?; } diff --git a/lib/compiler/src/engine/inner.rs b/lib/compiler/src/engine/inner.rs index abf898c2cec..62355ed5dfe 100644 --- a/lib/compiler/src/engine/inner.rs +++ b/lib/compiler/src/engine/inner.rs @@ -13,11 +13,15 @@ use wasmer_types::{CompileError, HashAlgorithm, target::Target}; #[cfg(not(target_arch = "wasm32"))] use shared_buffer::OwnedBuffer; +#[cfg(all(not(target_arch = "wasm32"), feature = "compiler"))] +use std::io::Write; #[cfg(not(target_arch = "wasm32"))] -use std::{io::Write, path::Path}; +use std::path::Path; +#[cfg(all(not(target_arch = "wasm32"), feature = "compiler"))] +use wasmer_types::ModuleInfo; #[cfg(not(target_arch = "wasm32"))] use wasmer_types::{ - DeserializeError, FunctionIndex, FunctionType, LocalFunctionIndex, ModuleInfo, SignatureIndex, + DeserializeError, FunctionIndex, FunctionType, LocalFunctionIndex, SignatureIndex, entity::PrimaryMap, }; @@ -540,7 +544,7 @@ impl EngineInner { .register_frame_info(frame_info); } - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(not(target_arch = "wasm32"), feature = "compiler"))] pub(crate) fn register_perfmap( &self, finished_functions: &PrimaryMap, From ea29fe65b38eb13aa67e2d1753201c980627e1af Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Mon, 24 Nov 2025 18:16:53 +0400 Subject: [PATCH 23/49] Acquire only transient store context refs on the WASM stack, since destructors are not guaranteed to run --- .../src/backend/sys/entities/function/mod.rs | 186 +++++++++++------- lib/api/src/entities/store/context.rs | 39 ++++ tests/wasix/test.sh | 2 + 3 files changed, 153 insertions(+), 74 deletions(-) diff --git a/lib/api/src/backend/sys/entities/function/mod.rs b/lib/api/src/backend/sys/entities/function/mod.rs index 305ebd8fb9e..a71877c84df 100644 --- a/lib/api/src/backend/sys/entities/function/mod.rs +++ b/lib/api/src/backend/sys/entities/function/mod.rs @@ -264,33 +264,41 @@ impl Function { let args_sig = Arc::new(signature.clone()); let results_sig = Arc::new(signature.clone()); let func = Arc::new(func); - Self::new_with_env_async(store, &env, signature, move |mut env_mut, values| -> Pin< - Box, RuntimeError>>>, - > { - let sys_env = match env_mut.0 { - BackendAsyncFunctionEnvMut::Sys(ref mut sys_env) => sys_env, - _ => panic!("Not a sys backend"), - }; - let mut store_mut_wrapper = unsafe { StoreContext::get_current(sys_env.store_id()) }; - let store_mut = store_mut_wrapper.as_mut(); - let args_sig = args_sig.clone(); - let results_sig = results_sig.clone(); - let func = func.clone(); - let args = - match typed_args_from_values::(store_mut, args_sig.as_ref(), values) { - Ok(args) => args, - Err(err) => return Box::pin(async { Err(err) }), - }; - drop(store_mut_wrapper); - let future = func - .as_ref() - .call_async(AsyncFunctionEnv::new(), args); - Box::pin(async move { - let typed_result = future.await?; - let mut store_mut = env_mut.write().await; - typed_results_to_values::(store_mut.reborrow_mut(), results_sig.as_ref(), typed_result) - }) - }) + Self::new_with_env_async( + store, + &env, + signature, + move |mut env_mut, + values| + -> Pin, RuntimeError>>>> { + let sys_env = match env_mut.0 { + BackendAsyncFunctionEnvMut::Sys(ref mut sys_env) => sys_env, + _ => panic!("Not a sys backend"), + }; + let mut store_mut_wrapper = + unsafe { StoreContext::get_current(sys_env.store_id()) }; + let store_mut = store_mut_wrapper.as_mut(); + let args_sig = args_sig.clone(); + let results_sig = results_sig.clone(); + let func = func.clone(); + let args = + match typed_args_from_values::(store_mut, args_sig.as_ref(), values) { + Ok(args) => args, + Err(err) => return Box::pin(async { Err(err) }), + }; + drop(store_mut_wrapper); + let future = func.as_ref().call_async(AsyncFunctionEnv::new(), args); + Box::pin(async move { + let typed_result = future.await?; + let mut store_mut = env_mut.write().await; + typed_results_to_values::( + store_mut.reborrow_mut(), + results_sig.as_ref(), + typed_result, + ) + }) + }, + ) } pub(crate) fn new_typed_with_env_async( @@ -308,34 +316,44 @@ impl Function { let args_sig = Arc::new(signature.clone()); let results_sig = Arc::new(signature.clone()); let func = Arc::new(func); - Self::new_with_env_async(store, env, signature, move |mut env_mut, values| -> Pin< - Box, RuntimeError>>>, - > { - let sys_env = match env_mut.0 { - BackendAsyncFunctionEnvMut::Sys(ref mut sys_env) => sys_env, - _ => panic!("Not a sys backend"), - }; - let mut store_mut_wrapper = unsafe { StoreContext::get_current(sys_env.store_id()) }; - let store_mut = store_mut_wrapper.as_mut(); - let args_sig = args_sig.clone(); - let results_sig = results_sig.clone(); - let func = func.clone(); - let args = - match typed_args_from_values::(store_mut, args_sig.as_ref(), values) { - Ok(args) => args, - Err(err) => return Box::pin(async { Err(err) }), - }; - drop(store_mut_wrapper); - let env_mut_clone = env_mut.as_mut(); - let future = func - .as_ref() - .call_async(AsyncFunctionEnv::with_env(env_mut), args); - Box::pin(async move { - let typed_result = future.await?; - let mut store_mut = env_mut_clone.write().await; - typed_results_to_values::(store_mut.reborrow_mut(), results_sig.as_ref(), typed_result) - }) - }) + Self::new_with_env_async( + store, + env, + signature, + move |mut env_mut, + values| + -> Pin, RuntimeError>>>> { + let sys_env = match env_mut.0 { + BackendAsyncFunctionEnvMut::Sys(ref mut sys_env) => sys_env, + _ => panic!("Not a sys backend"), + }; + let mut store_mut_wrapper = + unsafe { StoreContext::get_current(sys_env.store_id()) }; + let store_mut = store_mut_wrapper.as_mut(); + let args_sig = args_sig.clone(); + let results_sig = results_sig.clone(); + let func = func.clone(); + let args = + match typed_args_from_values::(store_mut, args_sig.as_ref(), values) { + Ok(args) => args, + Err(err) => return Box::pin(async { Err(err) }), + }; + drop(store_mut_wrapper); + let env_mut_clone = env_mut.as_mut(); + let future = func + .as_ref() + .call_async(AsyncFunctionEnv::with_env(env_mut), args); + Box::pin(async move { + let typed_result = future.await?; + let mut store_mut = env_mut_clone.write().await; + typed_results_to_values::( + store_mut.reborrow_mut(), + results_sig.as_ref(), + typed_result, + ) + }) + }, + ) } pub(crate) fn new_typed_with_env( @@ -783,12 +801,17 @@ where match result { Ok(InvocationResult::Success(())) => {} Ok(InvocationResult::Exception(exception)) => unsafe { - let mut store_wrapper = unsafe { StoreContext::get_current(this.ctx.store_id) }; - let mut store = store_wrapper.as_mut(); - wasmer_vm::libcalls::throw( - store.objects().as_sys(), - exception.vm_exceptionref().as_sys().to_u32_exnref(), - ) + unsafe { + // Note: can't acquire a proper ref-counted context ref here, since we can switch + // away from the WASM stack at any time. + // Safety: The pointer is only used for the duration of the call to `throw`. + let mut store_wrapper = StoreContext::get_current_transient(this.ctx.store_id); + let mut store = store_wrapper.as_mut().unwrap(); + wasmer_vm::libcalls::throw( + store.objects().as_sys(), + exception.vm_exceptionref().as_sys().to_u32_exnref(), + ) + } }, Ok(InvocationResult::Trap(trap)) => unsafe { raise_user_trap(trap) }, Ok(InvocationResult::YieldOutsideAsyncContext) => unsafe { @@ -872,10 +895,10 @@ macro_rules! impl_host_function { RetsAsResult: IntoResult, Func: Fn($( $x , )*) -> RetsAsResult + 'static, { - let mut store_wrapper = unsafe { StoreContext::get_current(env.store_id) }; - let mut store = store_wrapper.as_mut(); let result = on_host_stack(|| { panic::catch_unwind(AssertUnwindSafe(|| { + let mut store_wrapper = unsafe { StoreContext::get_current(env.store_id) }; + let mut store = store_wrapper.as_mut(); $( let $x = unsafe { FromToNativeWasmType::from_native(NativeWasmTypeInto::from_abi(store, $x)) @@ -889,12 +912,20 @@ macro_rules! impl_host_function { // AS WE ARE IN THE WASM STACK, NOT ON THE HOST ONE. // See: https://github.com/wasmerio/wasmer/pull/5700 match result { - Ok(InvocationResult::Success(result)) => { - unsafe { - return result.into_c_struct(store); - } + Ok(InvocationResult::Success(result)) => unsafe { + // Note: can't acquire a proper ref-counted context ref here, since we can switch + // away from the WASM stack at any time. + // Safety: The pointer is only used for the duration of the call to `into_c_struct`. + let mut store_wrapper = StoreContext::get_current_transient(env.store_id); + let mut store = store_wrapper.as_mut().unwrap(); + return result.into_c_struct(store); }, Ok(InvocationResult::Exception(exception)) => unsafe { + // Note: can't acquire a proper ref-counted context ref here, since we can switch + // away from the WASM stack at any time. + // Safety: The pointer is only used for the duration of the call to `throw`. + let mut store_wrapper = StoreContext::get_current_transient(env.store_id); + let mut store = store_wrapper.as_mut().unwrap(); wasmer_vm::libcalls::throw( store.objects().as_sys(), exception.vm_exceptionref().as_sys().to_u32_exnref() @@ -954,11 +985,10 @@ macro_rules! impl_host_function { RetsAsResult: IntoResult, Func: Fn(FunctionEnvMut, $( $x , )*) -> RetsAsResult + 'static, { - let mut store_wrapper = unsafe { StoreContext::get_current(env.store_id) }; - let mut store = store_wrapper.as_mut(); - let result = wasmer_vm::on_host_stack(|| { panic::catch_unwind(AssertUnwindSafe(|| { + let mut store_wrapper = unsafe { StoreContext::get_current(env.store_id) }; + let mut store = store_wrapper.as_mut(); $( let $x = unsafe { FromToNativeWasmType::from_native(NativeWasmTypeInto::from_abi(store, $x)) @@ -976,12 +1006,20 @@ macro_rules! impl_host_function { // AS WE ARE IN THE WASM STACK, NOT ON THE HOST ONE. // See: https://github.com/wasmerio/wasmer/pull/5700 match result { - Ok(InvocationResult::Success(result)) => { - unsafe { - return result.into_c_struct(store); - } + Ok(InvocationResult::Success(result)) => unsafe { + // Note: can't acquire a proper ref-counted context ref here, since we can switch + // away from the WASM stack at any time. + // Safety: The pointer is only used for the duration of the call to `into_c_struct`. + let mut store_wrapper = StoreContext::get_current_transient(env.store_id); + let mut store = store_wrapper.as_mut().unwrap(); + return result.into_c_struct(store); }, Ok(InvocationResult::Exception(exception)) => unsafe { + // Note: can't acquire a proper ref-counted context ref here, since we can switch + // away from the WASM stack at any time. + // Safety: The pointer is only used for the duration of the call to `throw`. + let mut store_wrapper = StoreContext::get_current_transient(env.store_id); + let mut store = store_wrapper.as_mut().unwrap(); wasmer_vm::libcalls::throw( store.objects().as_sys(), exception.vm_exceptionref().as_sys().to_u32_exnref() diff --git a/lib/api/src/entities/store/context.rs b/lib/api/src/entities/store/context.rs index 92716320b40..760dd9b1213 100644 --- a/lib/api/src/entities/store/context.rs +++ b/lib/api/src/entities/store/context.rs @@ -106,6 +106,11 @@ impl StoreContext { pub(crate) fn force_install(store_mut: StoreMut) -> ForcedStoreInstallGuard { let id = store_mut.objects().id(); Self::install(store_mut); + tracing::trace!( + "Force-installed store context for store id {}\n{}", + id, + std::backtrace::Backtrace::capture() + ); ForcedStoreInstallGuard { store_id: Some(id) } } @@ -145,6 +150,11 @@ impl StoreContext { ); }; Self::install(store_mut_instance); + tracing::trace!( + "Installed store context for store id {}\n{}", + store_id, + std::backtrace::Backtrace::capture() + ); StoreInstallGuard::Installed { store_id, store_mut, @@ -167,6 +177,12 @@ impl StoreContext { .expect("No store context installed on this thread"); assert_eq!(top.id, id, "Mismatched store context access"); top.borrow_count += 1; + tracing::trace!( + "Acquired mutable borrow for store id {}, current borrow count {}\n{}", + id, + top.borrow_count, + std::backtrace::Backtrace::capture() + ); StoreMutWrapper { store_mut: top.store_mut.get(), } @@ -185,6 +201,12 @@ impl StoreContext { .last_mut() .expect("No store context installed on this thread"); assert_eq!(top.id, id, "Mismatched store context access"); + tracing::trace!( + "Acquired transient mutable borrow for store id {}, current borrow count {}\n{}", + id, + top.borrow_count, + std::backtrace::Backtrace::capture() + ); unsafe { top.store_mut.get() } }) } @@ -198,6 +220,12 @@ impl StoreContext { return None; } top.borrow_count += 1; + tracing::trace!( + "Acquired mutable borrow for store id {}, current borrow count {}\n{}", + id, + top.borrow_count, + std::backtrace::Backtrace::capture() + ); Some(StoreMutWrapper { store_mut: top.store_mut.get(), }) @@ -229,6 +257,12 @@ impl Drop for StoreMutWrapper { .expect("No store context installed on this thread"); assert_eq!(top.id, id, "Mismatched store context reinstall"); top.borrow_count -= 1; + tracing::trace!( + "Dropped mutable borrow for store id {}, current borrow count {}\n{}", + id, + top.borrow_count, + std::backtrace::Backtrace::capture() + ); }) } } @@ -249,6 +283,11 @@ impl Drop for StoreInstallGuard<'_> { "Cannot uninstall store context while it is still borrowed" ); store_mut.put_back(top.store_mut.into_inner()); + tracing::trace!( + "Uninstalled store context for store id {}\n{}", + *store_id, + std::backtrace::Backtrace::capture() + ); }) } } diff --git a/tests/wasix/test.sh b/tests/wasix/test.sh index bf0be0bd9a2..6f708d36a48 100755 --- a/tests/wasix/test.sh +++ b/tests/wasix/test.sh @@ -11,9 +11,11 @@ while read dir; do if [ -e "$dir/.no-build" ]; then cmd="cd $dir; \ + find . -name 'output*' | xargs rm -f; \ ./run.sh" else cmd="cd $dir; \ + find . -name 'output*' | xargs rm -f; \ wasixcc main.c -o main.wasm; \ ./run.sh" fi From a6dad3986ca7f0b2daf8ad7faf4f13cf7cb7e762 Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Tue, 25 Nov 2025 16:20:18 +0400 Subject: [PATCH 24/49] use __builtin_trap instead of a bad function pointer in the WASIX vfork test --- tests/wasix/test.sh | 7 ++++--- tests/wasix/vfork/main.c | 8 ++------ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/tests/wasix/test.sh b/tests/wasix/test.sh index 6f708d36a48..dc197060231 100755 --- a/tests/wasix/test.sh +++ b/tests/wasix/test.sh @@ -7,7 +7,7 @@ printf "\n\nStarting WASIX Test Suite:\n" status=0 while read dir; do dir=$(basename "$dir") - printf "Testing $dir..." + printf "Testing $dir...\r" if [ -e "$dir/.no-build" ]; then cmd="cd $dir; \ @@ -16,14 +16,15 @@ while read dir; do else cmd="cd $dir; \ find . -name 'output*' | xargs rm -f; \ + find . -name '*.wasm' | xargs rm -f; \ wasixcc main.c -o main.wasm; \ ./run.sh" fi if bash -c "$cmd"; then - printf "\rTesting $dir ✅\n" + printf "Testing $dir ✅\n" else - printf "\rTesting $dir ❌\n" + printf "Testing $dir ❌\n" status=1 fi done < <(find . -mindepth 1 -maxdepth 1 -type d | sort) diff --git a/tests/wasix/vfork/main.c b/tests/wasix/vfork/main.c index 25ba4054178..c1b1de2720f 100644 --- a/tests/wasix/vfork/main.c +++ b/tests/wasix/vfork/main.c @@ -269,9 +269,7 @@ int subprocess_trap(int fd) return 1; } - // A bad function pointer is guaranteed to trap one way or another - void (*f)(void) = (void (*)(void))0x12345678; - f(); + __builtin_trap(); return 1; } @@ -295,9 +293,7 @@ int trap_before_exec() return 1; } - // A bad function pointer is guaranteed to trap one way or another - void (*f)(void) = (void (*)(void))0x12345678; - f(); + __builtin_trap(); return 100; } From d909833f52430e6e2c87f38478268cd1bbd89d32 Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Thu, 27 Nov 2025 00:10:21 +0400 Subject: [PATCH 25/49] Restore Store to its previous implementation, separate new impl out into StoreAsync --- .../entities/store/{asynk.rs => async_.rs} | 82 +++---- lib/api/src/entities/store/context.rs | 227 +++++------------- lib/api/src/entities/store/inner.rs | 22 +- lib/api/src/entities/store/local_rwlock.rs | 16 ++ lib/api/src/entities/store/mod.rs | 178 +++----------- lib/api/src/entities/store/store_ref.rs | 206 +++++++--------- 6 files changed, 265 insertions(+), 466 deletions(-) rename lib/api/src/entities/store/{asynk.rs => async_.rs} (65%) diff --git a/lib/api/src/entities/store/asynk.rs b/lib/api/src/entities/store/async_.rs similarity index 65% rename from lib/api/src/entities/store/asynk.rs rename to lib/api/src/entities/store/async_.rs index 16482e53218..dc19b8be45d 100644 --- a/lib/api/src/entities/store/asynk.rs +++ b/lib/api/src/entities/store/async_.rs @@ -1,9 +1,23 @@ use std::marker::PhantomData; -use crate::{AsStoreMut, AsStoreRef, Store, StoreContext, StoreMut, StoreMutWrapper, StoreRef}; +use crate::{ + AsStoreMut, AsStoreRef, LocalReadGuardRc, LocalRwLock, LocalWriteGuardRc, StoreContext, + StoreInner, StoreMut, StorePtrWrapper, StoreRef, +}; use wasmer_types::StoreId; +pub struct StoreAsync { + pub(crate) id: StoreId, + pub(crate) inner: LocalRwLock, +} + +impl StoreAsync { + pub fn into_store(self) -> Result { + todo!() + } +} + /// A trait for types that can be used with /// [`Function::call_async`](crate::Function::call_async). /// @@ -12,12 +26,12 @@ use wasmer_types::StoreId; /// out on purpose to help avoid common deadlock scenarios. pub trait AsAsyncStore { /// Returns a reference to the inner store. - fn store_ref(&self) -> &Store; + fn store_ref(&self) -> &StoreAsync; /// Returns a copy of the store. - fn store(&self) -> Store { + fn store(&self) -> StoreAsync { let store = self.store_ref(); - Store { + StoreAsync { id: store.id, inner: store.inner.clone(), } @@ -41,14 +55,15 @@ pub trait AsAsyncStore { } } -impl AsAsyncStore for Store { - fn store_ref(&self) -> &Store { +impl AsAsyncStore for StoreAsync { + fn store_ref(&self) -> &StoreAsync { self } } + pub(crate) enum AsyncStoreReadLockInner { - Owned(StoreRef), - FromStoreContext(StoreMutWrapper), + Owned(LocalReadGuardRc), + FromStoreContext(StorePtrWrapper), } /// A read lock on a store that can be used in concurrent contexts; @@ -59,7 +74,7 @@ pub struct AsyncStoreReadLock<'a> { } impl<'a> AsyncStoreReadLock<'a> { - pub(crate) async fn acquire(store: &'a Store) -> Self { + pub(crate) async fn acquire(store: &'a StoreAsync) -> Self { let store_context = unsafe { StoreContext::try_get_current(store.id) }; match store_context { Some(store_mut_wrapper) => Self { @@ -69,7 +84,7 @@ impl<'a> AsyncStoreReadLock<'a> { None => { // Drop the option before awaiting, since the value isn't Send drop(store_context); - let store_ref = store.make_ref_async().await; + let store_ref = store.inner.read_rc().await; Self { inner: AsyncStoreReadLockInner::Owned(store_ref), _marker: PhantomData, @@ -80,17 +95,17 @@ impl<'a> AsyncStoreReadLock<'a> { } impl AsStoreRef for AsyncStoreReadLock<'_> { - fn as_ref(&self) -> &crate::StoreInner { + fn as_store_ref(&self) -> StoreRef<'_> { match &self.inner { - AsyncStoreReadLockInner::Owned(guard) => guard.as_ref(), - AsyncStoreReadLockInner::FromStoreContext(wrapper) => wrapper.as_ref().as_ref(), + AsyncStoreReadLockInner::Owned(guard) => StoreRef { inner: &*guard }, + AsyncStoreReadLockInner::FromStoreContext(wrapper) => wrapper.as_ref(), } } } pub(crate) enum AsyncStoreWriteLockInner { - Owned(StoreMut), - FromStoreContext(StoreMutWrapper), + Owned(LocalWriteGuardRc), + FromStoreContext(StorePtrWrapper), } /// A write lock on a store that can be used in concurrent contexts; @@ -101,7 +116,7 @@ pub struct AsyncStoreWriteLock<'a> { } impl<'a> AsyncStoreWriteLock<'a> { - pub(crate) async fn acquire(store: &'a Store) -> Self { + pub(crate) async fn acquire(store: &'a StoreAsync) -> Self { let store_context = unsafe { StoreContext::try_get_current(store.id) }; match store_context { Some(store_mut_wrapper) => Self { @@ -111,7 +126,7 @@ impl<'a> AsyncStoreWriteLock<'a> { None => { // Drop the option before awaiting, since the value isn't Send drop(store_context); - let store_mut = store.make_mut_async().await; + let store_mut = store.inner.write_rc().await; Self { inner: AsyncStoreWriteLockInner::Owned(store_mut), _marker: PhantomData, @@ -122,42 +137,23 @@ impl<'a> AsyncStoreWriteLock<'a> { } impl AsStoreRef for AsyncStoreWriteLock<'_> { - fn as_ref(&self) -> &crate::StoreInner { + fn as_store_ref(&self) -> StoreRef<'_> { match &self.inner { - AsyncStoreWriteLockInner::Owned(guard) => guard.as_ref(), - AsyncStoreWriteLockInner::FromStoreContext(wrapper) => wrapper.as_ref().as_ref(), + AsyncStoreWriteLockInner::Owned(guard) => StoreRef { inner: &*guard }, + AsyncStoreWriteLockInner::FromStoreContext(wrapper) => wrapper.as_ref(), } } } impl AsStoreMut for AsyncStoreWriteLock<'_> { - fn as_mut(&mut self) -> &mut crate::StoreInner { + fn as_store_mut(&mut self) -> StoreMut<'_> { match &mut self.inner { - AsyncStoreWriteLockInner::Owned(guard) => guard.as_mut(), - AsyncStoreWriteLockInner::FromStoreContext(wrapper) => wrapper.as_mut().as_mut(), - } - } - - fn reborrow_mut(&mut self) -> &mut StoreMut { - match &mut self.inner { - AsyncStoreWriteLockInner::Owned(guard) => guard.reborrow_mut(), + AsyncStoreWriteLockInner::Owned(guard) => StoreMut { inner: &mut *guard }, AsyncStoreWriteLockInner::FromStoreContext(wrapper) => wrapper.as_mut(), } } - fn take(&mut self) -> Option { - match &mut self.inner { - AsyncStoreWriteLockInner::Owned(guard) => guard.take(), - AsyncStoreWriteLockInner::FromStoreContext(wrapper) => wrapper.as_mut().take(), - } - } - - fn put_back(&mut self, store_mut: StoreMut) { - match &mut self.inner { - AsyncStoreWriteLockInner::Owned(guard) => guard.put_back(store_mut), - AsyncStoreWriteLockInner::FromStoreContext(wrapper) => { - wrapper.as_mut().put_back(store_mut) - } - } + fn objects_mut(&mut self) -> &mut super::StoreObjects { + todo!() } } diff --git a/lib/api/src/entities/store/context.rs b/lib/api/src/entities/store/context.rs index 760dd9b1213..c43b70f39aa 100644 --- a/lib/api/src/entities/store/context.rs +++ b/lib/api/src/entities/store/context.rs @@ -1,15 +1,17 @@ //! Thread-local storage for storing the current store context, -//! i.e. the currently active `StoreMut`(s). When a function is -//! called, an owned `StoreMut` value must be placed in the -//! store context, so it can be retrieved later when needed -//! (mainly when calling imported functions). We maintain a -//! stack because it is technically possible to have nested -//! `Function::call` invocations that use different stores, -//! such as: +//! i.e. the currently active `Store`(s). When a function is +//! called, a pointer to the [`StoreInner`] in placed inside +//! the store context so it can be retrieved when needed. +//! This lets code that needs access to the store get it with +//! just the store ID. +//! +//! We maintain a stack because it is technically possible to +//! have nested `Function::call` invocations that use different +//! stores, such as: //! call(store1, func1) -> wasm code -> imported func -> //! call(store2, func2) //! -//! Also note that this stack is maintained by both function +//! Note that this stack is maintained by both function //! calls and the async_runtime to reflect the exact WASM //! functions running on a given thread at any moment in //! time. If a function suspends, its store context is @@ -30,7 +32,9 @@ use std::{ mem::MaybeUninit, }; -use super::{AsStoreMut, AsStoreRef, StoreMut}; +use crate::LocalWriteGuardRc; + +use super::{AsStoreMut, AsStoreRef, StoreInner, StoreMut, StoreRef}; use wasmer_types::StoreId; @@ -47,22 +51,19 @@ pub(crate) struct StoreContext { // keep track of how many borrows there are so we don't drop // it prematurely. borrow_count: u32, - store_mut: UnsafeCell, + store_ptr: UnsafeCell<*mut StoreInner>, } -pub(crate) struct StoreMutWrapper { - store_mut: *mut StoreMut, +pub(crate) struct StorePtrWrapper { + store_mut: *mut StoreInner, } pub(crate) struct ForcedStoreInstallGuard { - store_id: Option, + store_id: StoreId, } -pub(crate) enum StoreInstallGuard<'a> { - Installed { - store_id: StoreId, - store_mut: &'a mut dyn AsStoreMut, - }, +pub(crate) enum StoreInstallGuard { + Installed(StoreId), NotInstalled, } @@ -79,86 +80,44 @@ impl StoreContext { } fn is_suspended(id: StoreId) -> bool { - STORE_CONTEXT_STACK.with(|cell| { - let stack = cell.borrow(); - stack.iter().rev().skip(1).any(|ctx| ctx.id == id) - }) + !Self::is_active(id) + && STORE_CONTEXT_STACK.with(|cell| { + let stack = cell.borrow(); + stack.iter().rev().skip(1).any(|ctx| ctx.id == id) + }) } - fn install(store_mut: StoreMut) { - // No need to scan through the list, only one StoreMut - // can be active at any time because of the RwLock in - // Store. - let id = store_mut.objects().id(); + fn install(store_ptr: *mut StoreInner) { + let id = unsafe { *store_ptr }.objects().id(); STORE_CONTEXT_STACK.with(|cell| { let mut stack = cell.borrow_mut(); stack.push(StoreContext { id, borrow_count: 0, - store_mut: UnsafeCell::new(store_mut), + store_ptr: UnsafeCell::new(store_mut), }); }) } - /// Install the given [`StoreMut`] as the current store context. - /// This function can only be used when the caller has a [`StoreMut`], - /// which itself is proof that the store is not already active. - pub(crate) fn force_install(store_mut: StoreMut) -> ForcedStoreInstallGuard { - let id = store_mut.objects().id(); - Self::install(store_mut); - tracing::trace!( - "Force-installed store context for store id {}\n{}", - id, - std::backtrace::Backtrace::capture() - ); + /// # Safety + /// The pointer must be dereferenceable and remain valid until the + /// store context is uninstalled. + pub(crate) unsafe fn force_install(store_ptr: *mut StoreInner) -> ForcedStoreInstallGuard { + let id = unsafe { *store_ptr }.objects().id(); + Self::install(store_ptr); ForcedStoreInstallGuard { store_id: Some(id) } } - /// Ensure that a store context with the given id is installed. - /// Returns true if the [`StoreMut`] was taken out of the provided - /// [`AsStoreMut`] and installed, false if it was already active. - /// This function takes care of the problem of initial - /// [`Function::call`](crate::Function::call) needing to install the - /// store context vs nested calls having only a reference to the - /// store and needing to reuse the existing context. - pub(crate) fn ensure_installed<'a>( - store_mut: &'a mut impl AsStoreMut, - ) -> StoreInstallGuard<'a> { - let store_id = store_mut.objects().id(); + /// # Safety + /// The pointer must be dereferenceable and remain valid until the + /// store context is uninstalled. + pub(crate) unsafe fn ensure_installed<'a>(store_ptr: *mut StoreInner) -> StoreInstallGuard { + let store_id = unsafe { *store_ptr }.objects().id(); if Self::is_active(store_id) { StoreInstallGuard::NotInstalled } else { - let Some(store_mut_instance) = store_mut.take() else { - if Self::is_suspended(store_id) { - // Impossible because you can't have two writable locks on the Store - unreachable!( - "Cannot install store context recursively. \ - This should be impossible; please open an issue \ - describing how you ran into this panic at - https://github.com/wasmerio/wasmer/issues/new/choose" - ); - } - // Document the expected usage of Function::call here in case someone - // does too many weird things since, without doing weird things, the - // only way for embedder code to gain access to an AsStoreMut is by - // going through Store::as_mut anyway. - panic!( - "Failed to install store context because the provided AsStoreMut \ - implementation does not own its StoreMut. The usual cause of this \ - error is Function::call or Module::instantiate not being called \ - with the output from Store::as_mut." - ); - }; - Self::install(store_mut_instance); - tracing::trace!( - "Installed store context for store id {}\n{}", - store_id, - std::backtrace::Backtrace::capture() - ); - StoreInstallGuard::Installed { - store_id, - store_mut, - } + Self::install(store_ptr); + StoreInstallGuard::Installed(store_id) } } @@ -169,7 +128,7 @@ impl StoreContext { /// into a function that lost the reference (e.g. into WASM code) /// The intended, valid use-case for this method is from within /// imported function trampolines. - pub(crate) unsafe fn get_current(id: StoreId) -> StoreMutWrapper { + pub(crate) unsafe fn get_current(id: StoreId) -> StorePtrWrapper { STORE_CONTEXT_STACK.with(|cell| { let mut stack = cell.borrow_mut(); let top = stack @@ -177,14 +136,8 @@ impl StoreContext { .expect("No store context installed on this thread"); assert_eq!(top.id, id, "Mismatched store context access"); top.borrow_count += 1; - tracing::trace!( - "Acquired mutable borrow for store id {}, current borrow count {}\n{}", - id, - top.borrow_count, - std::backtrace::Backtrace::capture() - ); - StoreMutWrapper { - store_mut: top.store_mut.get(), + StorePtrWrapper { + store_mut: top.store_ptr.get(), } }) } @@ -194,25 +147,19 @@ impl StoreContext { /// the store context is changed in any way (via installing or uninstalling /// a store context). The caller must ensure that the store context /// remains unchanged for the entire lifetime of the returned reference. - pub(crate) unsafe fn get_current_transient(id: StoreId) -> *mut StoreMut { + pub(crate) unsafe fn get_current_transient(id: StoreId) -> *mut StoreInner { STORE_CONTEXT_STACK.with(|cell| { let mut stack = cell.borrow_mut(); let top = stack .last_mut() .expect("No store context installed on this thread"); assert_eq!(top.id, id, "Mismatched store context access"); - tracing::trace!( - "Acquired transient mutable borrow for store id {}, current borrow count {}\n{}", - id, - top.borrow_count, - std::backtrace::Backtrace::capture() - ); - unsafe { top.store_mut.get() } + unsafe { top.store_ptr.get() } }) } - /// Safety: See [`get_current`]. - pub(crate) unsafe fn try_get_current(id: StoreId) -> Option { + /// Safety: See [`Self::get_current`]. + pub(crate) unsafe fn try_get_current(id: StoreId) -> Option { STORE_CONTEXT_STACK.with(|cell| { let mut stack = cell.borrow_mut(); let top = stack.last_mut()?; @@ -220,34 +167,28 @@ impl StoreContext { return None; } top.borrow_count += 1; - tracing::trace!( - "Acquired mutable borrow for store id {}, current borrow count {}\n{}", - id, - top.borrow_count, - std::backtrace::Backtrace::capture() - ); - Some(StoreMutWrapper { - store_mut: top.store_mut.get(), + Some(StorePtrWrapper { + store_mut: top.store_ptr.get(), }) }) } } -impl StoreMutWrapper { - pub(crate) fn as_ref(&self) -> &StoreMut { +impl StorePtrWrapper { + pub(crate) fn as_ref(&self) -> StoreRef<'_> { // Safety: the store_mut is always initialized unless the StoreMutWrapper // is dropped, at which point it's impossible to call this function - unsafe { self.store_mut.as_ref().unwrap() } + unsafe { self.store_mut.as_ref().unwrap().as_store_ref() } } - pub(crate) fn as_mut(&mut self) -> &mut StoreMut { + pub(crate) fn as_mut(&mut self) -> StoreMut<'_> { // Safety: the store_mut is always initialized unless the StoreMutWrapper // is dropped, at which point it's impossible to call this function - unsafe { self.store_mut.as_mut().unwrap() } + unsafe { self.store_mut.as_mut().unwrap().as_store_mut() } } } -impl Drop for StoreMutWrapper { +impl Drop for StorePtrWrapper { fn drop(&mut self) { let id = self.as_mut().objects().id(); STORE_CONTEXT_STACK.with(|cell| { @@ -257,23 +198,13 @@ impl Drop for StoreMutWrapper { .expect("No store context installed on this thread"); assert_eq!(top.id, id, "Mismatched store context reinstall"); top.borrow_count -= 1; - tracing::trace!( - "Dropped mutable borrow for store id {}, current borrow count {}\n{}", - id, - top.borrow_count, - std::backtrace::Backtrace::capture() - ); }) } } -impl Drop for StoreInstallGuard<'_> { +impl Drop for StoreInstallGuard { fn drop(&mut self) { - if let StoreInstallGuard::Installed { - store_id, - store_mut, - } = self - { + if let StoreInstallGuard::Installed(store_id) = self { STORE_CONTEXT_STACK.with(|cell| { let mut stack = cell.borrow_mut(); let top = stack.pop().expect("Store context stack underflow"); @@ -282,47 +213,21 @@ impl Drop for StoreInstallGuard<'_> { top.borrow_count, 0, "Cannot uninstall store context while it is still borrowed" ); - store_mut.put_back(top.store_mut.into_inner()); - tracing::trace!( - "Uninstalled store context for store id {}\n{}", - *store_id, - std::backtrace::Backtrace::capture() - ); - }) - } - } -} - -impl ForcedStoreInstallGuard { - // Need to do this via mutable ref for the Drop impl - fn uninstall_by_ref(&mut self) -> StoreMut { - if let Some(store_id) = self.store_id.take() { - STORE_CONTEXT_STACK.with(|cell| { - let mut stack = cell.borrow_mut(); - let top = stack.pop().expect("Store context stack underflow"); - assert_eq!(top.id, store_id, "Mismatched store context uninstall"); - assert_eq!( - top.borrow_count, 0, - "Cannot uninstall store context while it is still borrowed" - ); - top.store_mut.into_inner() }) - } else { - unreachable!("ForcedStoreInstallGuard already uninstalled") } } - - // However, the public API will take self by value to - // prevent double-uninstalling - pub(crate) fn uninstall(mut self) -> StoreMut { - self.uninstall_by_ref() - } } impl Drop for ForcedStoreInstallGuard { fn drop(&mut self) { - if self.store_id.is_some() { - self.uninstall_by_ref(); - } + STORE_CONTEXT_STACK.with(|cell| { + let mut stack = cell.borrow_mut(); + let top = stack.pop().expect("Store context stack underflow"); + assert_eq!(top.id, self.store_id, "Mismatched store context uninstall"); + assert_eq!( + top.borrow_count, 0, + "Cannot uninstall store context while it is still borrowed" + ); + }) } } diff --git a/lib/api/src/entities/store/inner.rs b/lib/api/src/entities/store/inner.rs index 56d52806f9c..c977b01acbf 100644 --- a/lib/api/src/entities/store/inner.rs +++ b/lib/api/src/entities/store/inner.rs @@ -1,5 +1,5 @@ use crate::{ - AsStoreMut, + AsStoreMut, AsStoreRef, StoreRef, entities::{ engine::{AsEngineRef, Engine}, store::{StoreMut, StoreObjects}, @@ -13,8 +13,7 @@ use wasmer_vm::TrapHandlerFn; /// We require the context to have a fixed memory address for its lifetime since /// various bits of the VM have raw pointers that point back to it. Hence we /// wrap the actual context in a box. -// TODO: make this private again -pub struct StoreInner { +pub(crate) struct StoreInner { pub(crate) objects: StoreObjects, pub(crate) store: BackendStore, pub(crate) on_called: Option, @@ -30,11 +29,26 @@ impl std::fmt::Debug for StoreInner { } } +impl AsStoreRef for StoreInner { + fn as_store_ref(&self) -> StoreRef<'_> { + StoreRef { inner: self } + } +} +impl AsStoreMut for StoreInner { + fn as_store_mut(&mut self) -> StoreMut<'_> { + StoreMut { inner: self } + } + + fn objects_mut(&mut self) -> &mut StoreObjects { + &mut self.objects + } +} + /// Call handler for a store. // TODO: better documentation! pub type OnCalledHandler = Box< dyn FnOnce( - &mut StoreMut, + StoreMut<'_>, ) -> Result>, >; diff --git a/lib/api/src/entities/store/local_rwlock.rs b/lib/api/src/entities/store/local_rwlock.rs index 9694f9a3139..557c2423d4a 100644 --- a/lib/api/src/entities/store/local_rwlock.rs +++ b/lib/api/src/entities/store/local_rwlock.rs @@ -152,6 +152,22 @@ impl LocalRwLock { None } } + + /// Attempts to consume the lock if there are no active or waiting + /// readers or writers, and returns the inner value if successful. + pub fn consume(self) -> Result { + if self.inner.state.get() == LockState::Unlocked + && self.inner.read_waiters.borrow().is_empty() + && self.inner.write_waiters.borrow().is_empty() + { + match Rc::try_unwrap(self.inner) { + Ok(inner) => Ok(inner.value.into_inner()), + Err(rc) => Err(Self { inner: rc }), + } + } else { + Err(self) + } + } } impl Clone for LocalRwLock { diff --git a/lib/api/src/entities/store/mod.rs b/lib/api/src/entities/store/mod.rs index 20969ef6e1d..c1da1e2faf4 100644 --- a/lib/api/src/entities/store/mod.rs +++ b/lib/api/src/entities/store/mod.rs @@ -2,8 +2,8 @@ //! store. /// Defines the [`AsAsyncStore`] trait and its supporting types. -mod asynk; -pub use asynk::*; +mod async_; +pub use async_::*; /// Defines the [`StoreContext`] type. mod context; @@ -18,7 +18,10 @@ mod store_ref; mod local_rwlock; pub(crate) use local_rwlock::*; -use std::ops::{Deref, DerefMut}; +use std::{ + boxed::Box, + ops::{Deref, DerefMut}, +}; pub use store_ref::*; @@ -44,8 +47,7 @@ use wasmer_vm::TrapHandlerFn; /// For more informations, check out the [related WebAssembly specification] /// [related WebAssembly specification]: pub struct Store { - pub(crate) id: StoreId, - pub(crate) inner: LocalRwLock, + pub(crate) inner: Box, } impl Store { @@ -80,64 +82,15 @@ impl Store { } }; - let objects = StoreObjects::from_store_ref(&store); Self { - id: objects.id(), - inner: LocalRwLock::new(StoreInner { - objects, + inner: Box::new(StoreInner { + objects: StoreObjects::from_store_ref(&store), on_called: None, store, }), } } - /// Creates a new [`StoreRef`] if the store is available for reading. - pub(crate) fn try_make_ref(&self) -> Option { - self.inner - .try_read_rc() - .map(|guard| StoreRef { inner: guard }) - } - - /// Waits for the store to become available and creates a new - /// [`StoreRef`] afterwards. - pub(crate) async fn make_ref_async(&self) -> StoreRef { - let guard = self.inner.read_rc().await; - StoreRef { inner: guard } - } - - /// Creates a new [`StoreMut`] if the store is available for writing. - pub(crate) fn try_make_mut(&self) -> Option { - self.inner.try_write_rc().map(|guard| StoreMut { - inner: guard, - store_handle: crate::Store { - id: self.id, - inner: self.inner.clone(), - }, - }) - } - - /// Waits for the store to become available and creates a new - /// [`StoreMut`] afterwards. - pub(crate) async fn make_mut_async(&self) -> StoreMut { - let guard = self.inner.write_rc().await; - StoreMut { - inner: guard, - store_handle: Self { - id: self.id, - inner: self.inner.clone(), - }, - } - } - - /// Builds an [`AsStoreMut`] handle to this store, provided - /// the store is not locked. Panics if the store is already locked. - pub fn as_mut<'a>(&'a mut self) -> impl AsStoreMut + 'a { - StoreMutGuard { - inner: Some(self.try_make_mut().expect("Store is locked")), - marker: std::marker::PhantomData, - } - } - #[cfg(feature = "sys")] /// Set the [`TrapHandlerFn`] for this store. /// @@ -148,29 +101,19 @@ impl Store { pub fn set_trap_handler(&mut self, handler: Option>>) { use crate::backend::sys::entities::store::NativeStoreExt; #[allow(irrefutable_let_patterns)] - if let BackendStore::Sys(ref mut s) = - self.try_make_mut().expect("Store is locked").inner.store - { + if let BackendStore::Sys(ref mut s) = self.inner.store { s.set_trap_handler(handler) } } /// Returns the [`Engine`]. - pub fn engine<'a>(&'a self) -> StoreEngineRef<'a> { - // Happily unwrap the read lock here because we don't expect - // embedder code to access stores in parallel. - StoreEngineRef { - inner: self.try_make_ref().expect("Store is locked"), - marker: std::marker::PhantomData, - } + pub fn engine(&self) -> &Engine { + self.inner.store.engine() } /// Returns mutable reference to [`Engine`]. - pub fn engine_mut<'a>(&'a mut self) -> StoreEngineMut<'a> { - StoreEngineMut { - inner: self.try_make_mut().expect("Store is locked"), - marker: std::marker::PhantomData, - } + pub fn engine_mut(&mut self) -> &mut Engine { + self.inner.store.engine_mut() } /// Checks whether two stores are identical. A store is considered @@ -181,7 +124,14 @@ impl Store { /// Returns the ID of this store pub fn id(&self) -> StoreId { - self.id + self.inner.objects.id() + } + + pub fn into_async(self) -> StoreAsync { + StoreAsync { + id: self.id(), + inner: LocalRwLock::new(*self.inner), + } } } @@ -208,83 +158,29 @@ impl std::fmt::Debug for Store { } } -/// Marker used to make the engine accessible from a store reference. -/// Needed because the store's lock must be held while accessing the engine. -/// -/// This struct borrows the [`Store`] to help prevent accidental deadlocks. -pub struct StoreEngineRef<'a> { - inner: StoreRef, - marker: std::marker::PhantomData<&'a ()>, -} - -impl Deref for StoreEngineRef<'_> { - type Target = Engine; - - fn deref(&self) -> &Self::Target { - self.inner.engine() +impl AsEngineRef for Store { + fn as_engine_ref(&self) -> EngineRef<'_> { + self.inner.store.as_engine_ref() } -} - -/// Marker used to make the engine accessible from a store reference. -/// Needed because the store's lock must be held while accessing the engine. -/// -/// This struct borrows the [`Store`] to help prevent accidental deadlocks. -pub struct StoreEngineMut<'a> { - inner: StoreMut, - marker: std::marker::PhantomData<&'a ()>, -} - -impl Deref for StoreEngineMut<'_> { - type Target = Engine; - fn deref(&self) -> &Self::Target { - self.inner.engine() + fn maybe_as_store(&self) -> Option> { + Some(self.as_store_ref()) } } -impl DerefMut for StoreEngineMut<'_> { - fn deref_mut(&mut self) -> &mut Self::Target { - self.inner.engine_mut() +impl AsStoreRef for Store { + fn as_store_ref(&self) -> StoreRef<'_> { + StoreRef { inner: &self.inner } } } - -/// A guard that provides mutable access to a [`Store`]. This is -/// the only way for embedders to construct an [`AsStoreMut`] -/// from a [`Store`]. The internal [`StoreMut`] is taken when -/// using this value to invoke [`Function::call`](crate::Function::call). -// TODO: can we put the value back after the function returns? We should be able to -// TODO: what would the API look like? -pub struct StoreMutGuard<'a> { - pub(crate) inner: Option, - pub(crate) marker: std::marker::PhantomData<&'a ()>, -} - -impl AsStoreRef for StoreMutGuard<'_> { - fn as_ref(&self) -> &StoreInner { - self.inner - .as_ref() - .expect("StoreMutGuard is taken") - .as_ref() - } -} - -impl AsStoreMut for StoreMutGuard<'_> { - fn as_mut(&mut self) -> &mut StoreInner { - self.inner - .as_mut() - .expect("StoreMutGuard is taken") - .as_mut() - } - - fn reborrow_mut(&mut self) -> &mut StoreMut { - self.inner.as_mut().expect("StoreMutGuard is taken") - } - - fn take(&mut self) -> Option { - self.inner.take() +impl AsStoreMut for Store { + fn as_store_mut(&mut self) -> StoreMut<'_> { + StoreMut { + inner: &mut self.inner, + } } - fn put_back(&mut self, store_mut: StoreMut) { - assert!(self.inner.replace(store_mut).is_none()); + fn objects_mut(&mut self) -> &mut StoreObjects { + &mut self.inner.objects } } diff --git a/lib/api/src/entities/store/store_ref.rs b/lib/api/src/entities/store/store_ref.rs index 354b15ce575..efc460faa9a 100644 --- a/lib/api/src/entities/store/store_ref.rs +++ b/lib/api/src/entities/store/store_ref.rs @@ -1,144 +1,130 @@ use std::ops::{Deref, DerefMut}; -use super::{local_rwlock::{LocalReadGuardRc, LocalWriteGuardRc}, inner::StoreInner, StoreObjects}; -use crate::{ - Store, - entities::engine::{AsEngineRef, Engine, EngineRef}, -}; -use wasmer_types::{ExternType, OnCalledAction, StoreId}; +use super::{StoreObjects, inner::StoreInner}; +use crate::entities::engine::{AsEngineRef, Engine, EngineRef}; +use wasmer_types::{ExternType, OnCalledAction}; +//use wasmer_vm::{StoreObjects, TrapHandlerFn}; #[cfg(feature = "sys")] use wasmer_vm::TrapHandlerFn; /// A temporary handle to a [`crate::Store`]. -pub struct StoreRef { - pub(crate) inner: LocalReadGuardRc, +#[derive(Debug)] +pub struct StoreRef<'a> { + pub(crate) inner: &'a StoreInner, } -impl StoreRef { - fn as_ref(&self) -> &StoreInner { - &self.inner +impl<'a> StoreRef<'a> { + pub(crate) fn objects(&self) -> &'a StoreObjects { + &self.inner.objects + } + + /// Returns the [`Engine`]. + pub fn engine(&self) -> &Engine { + self.inner.store.engine() + } + + /// Checks whether two stores are identical. A store is considered + /// equal to another store if both have the same engine. + pub fn same(a: &Self, b: &Self) -> bool { + StoreObjects::same(&a.inner.objects, &b.inner.objects) + } + + /// The signal handler + #[cfg(feature = "sys")] + #[inline] + pub fn signal_handler(&self) -> Option<*const TrapHandlerFn<'static>> { + use crate::backend::sys::entities::store::NativeStoreExt; + self.inner.store.as_sys().signal_handler() } } /// A temporary handle to a [`crate::Store`]. -pub struct StoreMut { - pub(crate) inner: LocalWriteGuardRc, - - // Also keep an Arc to the store itself, so we can recreate - // the store for async functions. - pub(crate) store_handle: Store, +pub struct StoreMut<'a> { + pub(crate) inner: &'a mut StoreInner, } -impl StoreMut { - pub(crate) fn as_ref(&self) -> &StoreInner { - &self.inner +impl StoreMut<'_> { + /// Returns the [`Engine`]. + pub fn engine(&self) -> &Engine { + self.inner.store.engine() + } + + /// Checks whether two stores are identical. A store is considered + /// equal to another store if both have the same engine. + pub fn same(a: &Self, b: &Self) -> bool { + StoreObjects::same(&a.inner.objects, &b.inner.objects) + } + + #[allow(unused)] + pub(crate) fn as_raw(&self) -> *mut StoreInner { + self.inner as *const StoreInner as *mut StoreInner + } + + #[allow(unused)] + pub(crate) unsafe fn from_raw(raw: *mut StoreInner) -> Self { + Self { + inner: unsafe { &mut *raw }, + } } - pub(crate) fn as_mut(&mut self) -> &mut StoreInner { - &mut self.inner + #[allow(unused)] + pub(crate) fn engine_and_objects_mut(&mut self) -> (&Engine, &mut StoreObjects) { + (self.inner.store.engine(), &mut self.inner.objects) } // TODO: OnCalledAction is needed for asyncify. It will be refactored with https://github.com/wasmerio/wasmer/issues/3451 /// Sets the unwind callback which will be invoked when the call finishes fn on_called(&mut self, callback: F) where - F: FnOnce( - &mut StoreMut, - ) -> Result> + F: FnOnce(StoreMut<'_>) -> Result> + Send + Sync + 'static, { - self.as_mut().on_called.replace(Box::new(callback)); + self.inner.on_called.replace(Box::new(callback)); } } -/// Helper trait for a value that provides immutable access to a [`Store`](crate::entities::Store). +/// Helper trait for a value that is convertible to a [`StoreRef`]. pub trait AsStoreRef { - /// Returns a reference to the inner store. - fn as_ref(&self) -> &StoreInner; - - /// Returns a reference to the store objects. - fn objects(&self) -> &StoreObjects { - &self.as_ref().objects - } - - /// Returns the [`Engine`]. - fn engine(&self) -> &Engine { - self.as_ref().store.engine() - } - - /// Checks whether two stores are identical. A store is considered - /// equal to another store if both have the same engine. - fn same(&self, other: &dyn AsStoreRef) -> bool { - StoreObjects::same(&self.as_ref().objects, &other.as_ref().objects) - } - - /// The signal handler - #[cfg(feature = "sys")] - fn signal_handler(&self) -> Option<*const TrapHandlerFn<'static>> { - use crate::backend::sys::entities::store::NativeStoreExt; - self.as_ref().store.as_sys().signal_handler() - } + /// Returns a `StoreRef` pointing to the underlying context. + fn as_store_ref(&self) -> StoreRef<'_>; } -/// Helper trait for a value that provides mutable access to a [`Store`](crate::entities::Store). +/// Helper trait for a value that is convertible to a [`StoreMut`]. pub trait AsStoreMut: AsStoreRef { - /// Returns a mutable reference to the inner store. - fn as_mut(&mut self) -> &mut StoreInner; + /// Returns a `StoreMut` pointing to the underlying context. + fn as_store_mut(&mut self) -> StoreMut<'_>; - /// Re-borrow this as a mutable reference to the underlying StoreMut. - /// This is useful for passing a generic `&mut impl AsStoreMut` to - /// non-generic functions. - fn reborrow_mut(&mut self) -> &mut StoreMut; - - /// Attempts to take the [`StoreMut`] instance out of this implementor. - fn take(&mut self) -> Option { - None - } - - /// Place the [`StoreMut`] instance back in this implementor. - fn put_back(&mut self, store_mut: StoreMut) { - panic!("Not supported") - } - - /// Returns a mutable reference to the store objects. - fn objects_mut(&mut self) -> &mut StoreObjects { - &mut self.as_mut().objects - } - - /// Returns the [`Engine`]. - fn engine_mut(&mut self) -> &mut Engine { - self.as_mut().store.engine_mut() - } + /// Returns the ObjectMutable + fn objects_mut(&mut self) -> &mut StoreObjects; +} - /// Returns mutable references to the engine and store objects. - fn engine_and_objects_mut(&mut self) -> (&Engine, &mut StoreObjects) { - let self_ref = self.as_mut(); - (self_ref.store.engine(), &mut self_ref.objects) +impl AsStoreRef for StoreRef<'_> { + fn as_store_ref(&self) -> StoreRef<'_> { + StoreRef { inner: self.inner } } } -impl AsStoreRef for StoreRef { - fn as_ref(&self) -> &StoreInner { - self.as_ref() +impl AsEngineRef for StoreRef<'_> { + fn as_engine_ref(&self) -> EngineRef<'_> { + self.inner.store.as_engine_ref() } } -impl AsStoreRef for StoreMut { - fn as_ref(&self) -> &StoreInner { - self.as_ref() +impl AsStoreRef for StoreMut<'_> { + fn as_store_ref(&self) -> StoreRef<'_> { + StoreRef { inner: self.inner } } } - -impl AsStoreMut for StoreMut { - fn as_mut(&mut self) -> &mut StoreInner { - self.as_mut() +impl AsStoreMut for StoreMut<'_> { + fn as_store_mut(&mut self) -> StoreMut<'_> { + StoreMut { inner: self.inner } } - fn reborrow_mut(&mut self) -> &mut StoreMut { - self + fn objects_mut(&mut self) -> &mut StoreObjects { + &mut self.inner.objects } } @@ -147,8 +133,8 @@ where P: Deref, P::Target: AsStoreRef, { - fn as_ref(&self) -> &StoreInner { - (**self).as_ref() + fn as_store_ref(&self) -> StoreRef<'_> { + (**self).as_store_ref() } } @@ -157,31 +143,17 @@ where P: DerefMut, P::Target: AsStoreMut, { - fn as_mut(&mut self) -> &mut StoreInner { - (**self).as_mut() - } - - fn reborrow_mut(&mut self) -> &mut StoreMut { - (**self).reborrow_mut() + fn as_store_mut(&mut self) -> StoreMut<'_> { + (**self).as_store_mut() } - fn take(&mut self) -> Option { - (**self).take() - } - - fn put_back(&mut self, store_mut: StoreMut) { - (**self).put_back(store_mut) - } -} - -impl AsEngineRef for StoreRef { - fn as_engine_ref(&self) -> EngineRef<'_> { - self.as_ref().store.as_engine_ref() + fn objects_mut(&mut self) -> &mut StoreObjects { + (**self).objects_mut() } } -impl AsEngineRef for StoreMut { +impl AsEngineRef for StoreMut<'_> { fn as_engine_ref(&self) -> EngineRef<'_> { - self.as_ref().store.as_engine_ref() + self.inner.store.as_engine_ref() } } From 1dd7a64c7a92758d13cf249729ce3021db629c8f Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Thu, 27 Nov 2025 02:28:59 +0400 Subject: [PATCH 26/49] Get API crate building again! --- lib/api/src/backend/sys/async_runtime.rs | 69 ++++++---- lib/api/src/backend/sys/entities/exception.rs | 8 +- lib/api/src/backend/sys/entities/external.rs | 4 +- .../src/backend/sys/entities/function/env.rs | 126 +++++++---------- .../src/backend/sys/entities/function/mod.rs | 128 +++++++++++------- .../backend/sys/entities/function/typed.rs | 50 +++---- lib/api/src/backend/sys/entities/global.rs | 22 ++- lib/api/src/backend/sys/entities/instance.rs | 5 +- .../src/backend/sys/entities/memory/mod.rs | 24 ++-- .../src/backend/sys/entities/memory/view.rs | 13 +- lib/api/src/backend/sys/entities/module.rs | 12 +- lib/api/src/backend/sys/entities/table.rs | 22 ++- lib/api/src/backend/sys/entities/tag.rs | 4 +- lib/api/src/entities/engine/engine_ref.rs | 4 +- lib/api/src/entities/exception/inner.rs | 2 +- lib/api/src/entities/external/extref/inner.rs | 4 +- lib/api/src/entities/function/env/inner.rs | 63 +++------ lib/api/src/entities/function/env/mod.rs | 48 ++----- lib/api/src/entities/function/inner.rs | 46 +++---- lib/api/src/entities/global/inner.rs | 11 +- lib/api/src/entities/instance.rs | 7 +- lib/api/src/entities/memory/inner.rs | 10 +- lib/api/src/entities/memory/view/inner.rs | 3 +- lib/api/src/entities/memory/view/mod.rs | 1 - lib/api/src/entities/store/async_.rs | 25 +++- lib/api/src/entities/store/context.rs | 99 +++++++++++--- lib/api/src/entities/store/local_rwlock.rs | 24 +++- lib/api/src/entities/store/mod.rs | 2 + lib/api/src/entities/table/inner.rs | 12 +- lib/api/src/entities/tag/inner.rs | 4 +- lib/api/src/entities/value.rs | 11 +- lib/api/src/error.rs | 4 +- lib/api/src/utils/native/convert.rs | 8 +- lib/api/src/utils/native/typed_func.rs | 6 +- 34 files changed, 474 insertions(+), 407 deletions(-) diff --git a/lib/api/src/backend/sys/async_runtime.rs b/lib/api/src/backend/sys/async_runtime.rs index e8d484380dc..e4e9fff3d71 100644 --- a/lib/api/src/backend/sys/async_runtime.rs +++ b/lib/api/src/backend/sys/async_runtime.rs @@ -12,14 +12,17 @@ use std::{ use corosensei::{Coroutine, CoroutineResult, Yielder}; use super::entities::function::Function as SysFunction; -use crate::{AsStoreMut, AsStoreRef, RuntimeError, Store, StoreContext, StoreMut, Value}; +use crate::{ + AsStoreMut, AsStoreRef, LocalWriteGuardRc, RuntimeError, Store, StoreAsync, StoreContext, + StoreInner, StoreMut, StoreRef, Value, +}; use wasmer_types::StoreId; type HostFuture = Pin, RuntimeError>> + 'static>>; pub(crate) fn call_function_async<'a>( function: SysFunction, - store: Store, + store: StoreAsync, params: Vec, ) -> AsyncCallFuture<'a> { AsyncCallFuture::new(function, store, params) @@ -40,7 +43,7 @@ pub(crate) struct AsyncCallFuture<'a> { result: Option, RuntimeError>>, // Store handle we can use to lock the store down - store: Store, + store: StoreAsync, // Use Rc> to make sure that the future is !Send and !Sync _marker: PhantomData>>, @@ -59,47 +62,49 @@ struct AsyncCallStoreMut { } impl AsStoreRef for AsyncCallStoreMut { - fn as_ref(&self) -> &crate::StoreInner { + fn as_store_ref(&self) -> StoreRef<'_> { // Safety: This is only used with Function::call, which doesn't store // the returned reference anywhere, including when calling into WASM // code. unsafe { - StoreContext::get_current_transient(self.store_id) - .as_ref() - .unwrap() - .as_ref() + StoreRef { + inner: StoreContext::get_current_transient(self.store_id) + .as_ref() + .unwrap(), + } } } } impl AsStoreMut for AsyncCallStoreMut { - fn as_mut(&mut self) -> &mut crate::StoreInner { + fn as_store_mut(&mut self) -> StoreMut<'_> { // Safety: This is only used with Function::call, which doesn't store // the returned reference anywhere, including when calling into WASM // code. unsafe { - StoreContext::get_current_transient(self.store_id) - .as_mut() - .unwrap() - .as_mut() + StoreMut { + inner: StoreContext::get_current_transient(self.store_id) + .as_mut() + .unwrap(), + } } } - fn reborrow_mut(&mut self) -> &mut StoreMut { + fn objects_mut(&mut self) -> &mut crate::StoreObjects { // Safety: This is only used with Function::call, which doesn't store // the returned reference anywhere, including when calling into WASM // code. unsafe { - StoreContext::get_current_transient(self.store_id) + &mut StoreContext::get_current_transient(self.store_id) .as_mut() .unwrap() - .reborrow_mut() + .objects } } } impl<'a> AsyncCallFuture<'a> { - pub(crate) fn new(function: SysFunction, store: crate::Store, params: Vec) -> Self { + pub(crate) fn new(function: SysFunction, store: StoreAsync, params: Vec) -> Self { let store_id = store.id; let coroutine = Coroutine::new(move |yielder: &Yielder, resume| { @@ -150,7 +155,7 @@ impl Future for AsyncCallFuture<'_> { // Start a store installation if not in progress already if let None = self.pending_store_install { self.pending_store_install = - Some(Box::pin(StoreContextInstaller::install(Store { + Some(Box::pin(StoreContextInstaller::install(StoreAsync { id: self.store.id, inner: self.store.inner.clone(), }))); @@ -192,20 +197,28 @@ impl Future for AsyncCallFuture<'_> { } enum StoreContextInstaller { - FromThreadContext(crate::StoreMutWrapper), + FromThreadContext(crate::AsyncStoreGuardWrapper), Installed(crate::ForcedStoreInstallGuard), } impl StoreContextInstaller { - async fn install(store: Store) -> Self { - if let Some(wrapper) = unsafe { crate::StoreContext::try_get_current(store.id) } { - // If we're already in the scope of this store, we can just reuse it. - StoreContextInstaller::FromThreadContext(wrapper) - } else { - // Otherwise, need to acquire a new StoreMut. - let store_mut = store.make_mut_async().await; - let guard = crate::StoreContext::force_install(store_mut); - StoreContextInstaller::Installed(guard) + async fn install(store: StoreAsync) -> Self { + match unsafe { crate::StoreContext::try_get_current_async(store.id) } { + crate::GetAsyncStoreGuardResult::NotAsync => { + // If the store was installed as a sync store, panic - this + // should be impossible + unreachable!("Sync store context installed in async call") + } + crate::GetAsyncStoreGuardResult::Ok(wrapper) => { + // If we're already in the scope of this store, we can just reuse it. + StoreContextInstaller::FromThreadContext(wrapper) + } + crate::GetAsyncStoreGuardResult::NotInstalled => { + // Otherwise, need to acquire a new StoreMut. + let store_guard = store.inner.write_rc().await; + let install_guard = unsafe { crate::StoreContext::install_async(store_guard) }; + StoreContextInstaller::Installed(install_guard) + } } } } diff --git a/lib/api/src/backend/sys/entities/exception.rs b/lib/api/src/backend/sys/entities/exception.rs index cc9a80aef91..32c3d2e2c4f 100644 --- a/lib/api/src/backend/sys/entities/exception.rs +++ b/lib/api/src/backend/sys/entities/exception.rs @@ -24,7 +24,7 @@ impl Exception { panic!("cannot create Exception with Tag from another Store"); } - let store_objects = store.objects().as_sys(); + let store_objects = store.as_store_ref().objects().as_sys(); let store_id = store_objects.id(); let tag_ty = tag.handle.get(store_objects).signature.params(); @@ -62,14 +62,14 @@ impl Exception { } pub fn is_from_store(&self, store: &impl AsStoreRef) -> bool { - self.exnref.0.store_id() == store.objects().id() + self.exnref.0.store_id() == store.as_store_ref().objects().id() } pub fn tag(&self, store: &impl AsStoreRef) -> crate::sys::tag::Tag { if !self.is_from_store(store) { panic!("Exception is from another Store"); } - let ctx = store.objects().as_sys(); + let ctx = store.as_store_ref().objects().as_sys(); let exception = self.exnref.0.get(ctx); let tag_handle = exception.tag(); crate::sys::tag::Tag { @@ -81,7 +81,7 @@ impl Exception { if !self.is_from_store(store) { panic!("Exception is from another Store"); } - let ctx = store.objects().as_sys(); + let ctx = store.as_store_ref().objects().as_sys(); let exception = self.exnref.0.get(ctx); let params_ty = exception.tag().get(ctx).signature.params().to_vec(); let payload_ptr = exception.payload(); diff --git a/lib/api/src/backend/sys/entities/external.rs b/lib/api/src/backend/sys/entities/external.rs index 0254a79c8b6..ac1cc3ad1a4 100644 --- a/lib/api/src/backend/sys/entities/external.rs +++ b/lib/api/src/backend/sys/entities/external.rs @@ -32,7 +32,7 @@ impl ExternRef { T: Any + Send + Sync + 'static + Sized, { self.handle - .get(store.objects().as_sys()) + .get(store.as_store_ref().objects().as_sys()) .as_ref() .downcast_ref::() } @@ -60,6 +60,6 @@ impl ExternRef { /// Externref and funcref values are tied to a context and can only be used /// with that context. pub fn is_from_store(&self, store: &impl AsStoreRef) -> bool { - self.handle.store_id() == store.objects().id() + self.handle.store_id() == store.as_store_ref().objects().id() } } diff --git a/lib/api/src/backend/sys/entities/function/env.rs b/lib/api/src/backend/sys/entities/function/env.rs index 79c55df9c74..691a0af15aa 100644 --- a/lib/api/src/backend/sys/entities/function/env.rs +++ b/lib/api/src/backend/sys/entities/function/env.rs @@ -1,8 +1,8 @@ use std::{any::Any, fmt::Debug, marker::PhantomData}; use crate::{ - AsAsyncStore, AsyncStoreReadLock, AsyncStoreWriteLock, Store, StoreContext, StoreMut, - StoreMutGuard, StoreMutWrapper, + AsAsyncStore, AsyncStoreReadLock, AsyncStoreWriteLock, Store, StoreAsync, StoreContext, + StoreMut, store::{AsStoreMut, AsStoreRef, StoreRef}, }; @@ -17,33 +17,18 @@ pub struct FunctionEnv { marker: PhantomData, } -impl FunctionEnv { +impl FunctionEnv { /// Make a new FunctionEnv - pub fn new(store: &mut impl AsStoreMut, value: T) -> Self - where - T: Any + Send + 'static + Sized, - { + pub fn new(store: &mut impl AsStoreMut, value: T) -> Self { Self { handle: StoreHandle::new( - store.objects_mut().as_sys_mut(), + store.as_store_mut().objects_mut().as_sys_mut(), VMFunctionEnvironment::new(value), ), marker: PhantomData, } } - /// Get the data as reference - pub fn as_ref<'a>(&self, store: &'a impl AsStoreRef) -> &'a T - where - T: Any + 'static + Sized, - { - self.handle - .get(store.objects().as_sys()) - .as_ref() - .downcast_ref::() - .unwrap() - } - #[allow(dead_code)] // This function is only used in js pub(crate) fn from_handle(handle: StoreHandle) -> Self { Self { @@ -52,28 +37,33 @@ impl FunctionEnv { } } + /// Convert it into a `FunctionEnvMut` + pub fn into_mut(self, store: &mut impl AsStoreMut) -> FunctionEnvMut<'_, T> { + FunctionEnvMut { + store_mut: store.as_store_mut(), + func_env: self, + } + } +} + +impl FunctionEnv { + /// Get the data as reference + pub fn as_ref<'a>(&self, store: &'a impl AsStoreRef) -> &'a T { + self.handle + .get(store.as_store_ref().objects().as_sys()) + .as_ref() + .downcast_ref::() + .unwrap() + } + /// Get the data as mutable - pub fn as_mut<'a>(&self, store: &'a mut impl AsStoreMut) -> &'a mut T - where - T: Any + 'static + Sized, - { + pub fn as_mut<'a>(&self, store: &'a mut impl AsStoreMut) -> &'a mut T { self.handle .get_mut(store.objects_mut().as_sys_mut()) .as_mut() .downcast_mut::() .unwrap() } - - /// Convert it into a `FunctionEnvMut` - pub fn into_mut(self, store: &mut impl AsStoreMut) -> FunctionEnvMut<'_, T> - where - T: Any + 'static + Sized, - { - FunctionEnvMut { - store_mut: store.reborrow_mut(), - func_env: self, - } - } } impl crate::FunctionEnv { @@ -128,7 +118,7 @@ impl Clone for FunctionEnv { /// A temporary handle to a [`FunctionEnv`]. pub struct FunctionEnvMut<'a, T: 'a> { - pub(crate) store_mut: &'a mut StoreMut, + pub(crate) store_mut: StoreMut<'a>, pub(crate) func_env: FunctionEnv, } @@ -160,44 +150,40 @@ impl FunctionEnvMut<'_, T> { /// Borrows a new mutable reference pub fn as_mut(&mut self) -> FunctionEnvMut<'_, T> { FunctionEnvMut { - store_mut: self.store_mut.reborrow_mut(), + store_mut: self.store_mut.as_store_mut(), func_env: self.func_env.clone(), } } /// Borrows a new mutable reference of both the attached Store and host state - pub fn data_and_store_mut(&mut self) -> (&mut T, &mut StoreMut) { + pub fn data_and_store_mut(&mut self) -> (&mut T, StoreMut<'_>) { let data = self.func_env.as_mut(&mut self.store_mut) as *mut T; // telling the borrow check to close his eyes here // this is still relatively safe to do as func_env are // stored in a specific vec of Store, separate from the other objects // and not really directly accessible with the StoreMut let data = unsafe { &mut *data }; - (data, self.store_mut.reborrow_mut()) - } - - /// Creates an [`AsAsyncStore`] from this [`FunctionEnvMut`]. - pub fn as_async_store(&mut self) -> impl AsAsyncStore + 'static { - Store { - id: self.store_mut.store_handle.id, - inner: self.store_mut.store_handle.inner.clone(), - } + (data, self.store_mut.as_store_mut()) } } impl AsStoreRef for FunctionEnvMut<'_, T> { - fn as_ref(&self) -> &crate::StoreInner { - self.store_mut.as_ref() + fn as_store_ref(&self) -> StoreRef<'_> { + StoreRef { + inner: self.store_mut.inner, + } } } impl AsStoreMut for FunctionEnvMut<'_, T> { - fn as_mut(&mut self) -> &mut crate::StoreInner { - self.store_mut.as_mut() + fn as_store_mut(&mut self) -> StoreMut<'_> { + StoreMut { + inner: self.store_mut.inner, + } } - fn reborrow_mut(&mut self) -> &mut StoreMut { - self.store_mut.reborrow_mut() + fn objects_mut(&mut self) -> &mut crate::StoreObjects { + self.store_mut.objects_mut() } } @@ -216,7 +202,7 @@ impl From> for crate::FunctionEnv { /// A temporary handle to a [`FunctionEnv`], suitable for use /// in async imports. pub struct AsyncFunctionEnvMut { - pub(crate) store: Store, + pub(crate) store: StoreAsync, pub(crate) func_env: FunctionEnv, } @@ -246,8 +232,8 @@ where T: Send + Debug + 'static, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self.store.try_make_mut() { - Some(mut store_mut) => self.func_env.as_ref(&mut store_mut).fmt(f), + match self.store.inner.try_read_rc() { + Some(read_lock) => self.func_env.as_ref(&read_lock).fmt(f), None => write!(f, "AsyncFunctionEnvMut {{ }}"), } } @@ -288,7 +274,7 @@ impl AsyncFunctionEnvMut { /// Borrows a new mutable reference pub fn as_mut(&mut self) -> AsyncFunctionEnvMut { AsyncFunctionEnvMut { - store: Store { + store: StoreAsync { id: self.store.id, inner: self.store.inner.clone(), }, @@ -298,7 +284,7 @@ impl AsyncFunctionEnvMut { /// Creates an [`AsAsyncStore`] from this [`AsyncFunctionEnvMut`]. pub fn as_async_store(&mut self) -> impl AsAsyncStore + 'static { - Store { + StoreAsync { id: self.store.id, inner: self.store.inner.clone(), } @@ -318,8 +304,8 @@ impl AsyncFunctionEnvHandle<'_, T> { } impl AsStoreRef for AsyncFunctionEnvHandle<'_, T> { - fn as_ref(&self) -> &crate::StoreInner { - AsStoreRef::as_ref(&self.read_lock) + fn as_store_ref(&self) -> StoreRef<'_> { + AsStoreRef::as_store_ref(&self.read_lock) } } @@ -343,25 +329,17 @@ impl AsyncFunctionEnvHandleMut<'_, T> { } impl AsStoreRef for AsyncFunctionEnvHandleMut<'_, T> { - fn as_ref(&self) -> &crate::StoreInner { - AsStoreRef::as_ref(&self.write_lock) + fn as_store_ref(&self) -> StoreRef<'_> { + AsStoreRef::as_store_ref(&self.write_lock) } } impl AsStoreMut for AsyncFunctionEnvHandleMut<'_, T> { - fn as_mut(&mut self) -> &mut crate::StoreInner { - AsStoreMut::as_mut(&mut self.write_lock) - } - - fn reborrow_mut(&mut self) -> &mut StoreMut { - AsStoreMut::reborrow_mut(&mut self.write_lock) - } - - fn take(&mut self) -> Option { - AsStoreMut::take(&mut self.write_lock) + fn as_store_mut(&mut self) -> StoreMut<'_> { + AsStoreMut::as_store_mut(&mut self.write_lock) } - fn put_back(&mut self, store_mut: StoreMut) { - AsStoreMut::put_back(&mut self.write_lock, store_mut); + fn objects_mut(&mut self) -> &mut crate::StoreObjects { + AsStoreMut::objects_mut(&mut self.write_lock) } } diff --git a/lib/api/src/backend/sys/entities/function/mod.rs b/lib/api/src/backend/sys/entities/function/mod.rs index a71877c84df..67ed3f9354f 100644 --- a/lib/api/src/backend/sys/entities/function/mod.rs +++ b/lib/api/src/backend/sys/entities/function/mod.rs @@ -61,7 +61,7 @@ impl Function { let function_type = ty.into(); let func_ty = function_type.clone(); let func_env = env.clone().into_sys(); - let store_id = store.objects().id(); + let store_id = store.objects_mut().id(); let wrapper = move |values_vec: *mut RawValue| -> HostCallOutcome { unsafe { let mut store_wrapper = unsafe { StoreContext::get_current(store_id) }; @@ -70,7 +70,7 @@ impl Function { for (i, ty) in func_ty.params().iter().enumerate() { args.push(Value::from_raw( - store, + &mut store, *ty, values_vec.add(i).read_unaligned(), )); @@ -101,7 +101,11 @@ impl Function { // The engine linker will replace the address with one pointing to a // generated dynamic trampoline. let func_ptr = std::ptr::null() as VMFunctionCallback; - let type_index = store.engine().as_sys().register_signature(&function_type); + let type_index = store + .as_store_ref() + .engine() + .as_sys() + .register_signature(&function_type); let vmctx = VMFunctionContext { host_env: host_data.as_ref() as *const _ as *mut c_void, }; @@ -149,25 +153,35 @@ impl Function { let function_type = ty.into(); let func_ty = function_type.clone(); let func_env = env.clone().into_sys(); - let store_id = store.objects().id(); + let store_id = store.objects_mut().id(); let wrapper = move |values_vec: *mut RawValue| -> HostCallOutcome { unsafe { - let mut store_wrapper = StoreContext::get_current(store_id); - let mut store = store_wrapper.as_mut(); + let mut store_wrapper = match StoreContext::try_get_current_async(store_id) { + crate::GetAsyncStoreGuardResult::Ok(wrapper) => wrapper, + _ => panic!( + "Sync store context encountered when attempting to \ + invoke async imported function" + ), + }; + let mut store_write_guard = store_wrapper.guard.as_mut().unwrap(); + let mut store = StoreMut { + inner: &mut **store_write_guard, + }; + let id = store.as_store_ref().objects().id(); let mut args = Vec::with_capacity(func_ty.params().len()); for (i, ty) in func_ty.params().iter().enumerate() { args.push(Value::from_raw( - store, + &mut store, *ty, values_vec.add(i).read_unaligned(), )); } let env = crate::AsyncFunctionEnvMut(crate::BackendAsyncFunctionEnvMut::Sys( env::AsyncFunctionEnvMut { - store: crate::Store { - id: store.store_handle.id, - inner: store.store_handle.inner.clone(), + store: crate::StoreAsync { + id, + inner: crate::LocalWriteGuardRc::lock_handle(store_write_guard), }, func_env: func_env.clone(), }, @@ -190,7 +204,11 @@ impl Function { host_data.address = host_data.ctx.func_body_ptr() as *const VMFunctionBody; let func_ptr = std::ptr::null() as VMFunctionCallback; - let type_index = store.engine().as_sys().register_signature(&function_type); + let type_index = store + .as_store_ref() + .engine() + .as_sys() + .register_signature(&function_type); let vmctx = VMFunctionContext { host_env: host_data.as_ref() as *const _ as *mut c_void, }; @@ -223,13 +241,17 @@ impl Function { let env = FunctionEnv::new(store, ()); let func_ptr = func.function_callback_sys().into_sys(); let host_data = Box::new(StaticFunction { - store_id: store.objects().id(), + store_id: store.objects_mut().id(), env, func, }); let function_type = FunctionType::new(Args::wasm_types(), Rets::wasm_types()); - let type_index = store.engine().as_sys().register_signature(&function_type); + let type_index = store + .as_store_ref() + .engine() + .as_sys() + .register_signature(&function_type); let vmctx = VMFunctionContext { host_env: host_data.as_ref() as *const _ as *mut c_void, }; @@ -277,12 +299,13 @@ impl Function { }; let mut store_mut_wrapper = unsafe { StoreContext::get_current(sys_env.store_id()) }; - let store_mut = store_mut_wrapper.as_mut(); + let mut store_mut = store_mut_wrapper.as_mut(); let args_sig = args_sig.clone(); let results_sig = results_sig.clone(); let func = func.clone(); let args = - match typed_args_from_values::(store_mut, args_sig.as_ref(), values) { + match typed_args_from_values::(&mut store_mut, args_sig.as_ref(), values) + { Ok(args) => args, Err(err) => return Box::pin(async { Err(err) }), }; @@ -292,7 +315,7 @@ impl Function { let typed_result = future.await?; let mut store_mut = env_mut.write().await; typed_results_to_values::( - store_mut.reborrow_mut(), + &mut store_mut.as_store_mut(), results_sig.as_ref(), typed_result, ) @@ -329,12 +352,13 @@ impl Function { }; let mut store_mut_wrapper = unsafe { StoreContext::get_current(sys_env.store_id()) }; - let store_mut = store_mut_wrapper.as_mut(); + let mut store_mut = store_mut_wrapper.as_mut(); let args_sig = args_sig.clone(); let results_sig = results_sig.clone(); let func = func.clone(); let args = - match typed_args_from_values::(store_mut, args_sig.as_ref(), values) { + match typed_args_from_values::(&mut store_mut, args_sig.as_ref(), values) + { Ok(args) => args, Err(err) => return Box::pin(async { Err(err) }), }; @@ -347,7 +371,7 @@ impl Function { let typed_result = future.await?; let mut store_mut = env_mut_clone.write().await; typed_results_to_values::( - store_mut.reborrow_mut(), + &mut store_mut.as_store_mut(), results_sig.as_ref(), typed_result, ) @@ -368,13 +392,17 @@ impl Function { { let func_ptr = func.function_callback_sys().into_sys(); let host_data = Box::new(StaticFunction { - store_id: store.objects().id(), + store_id: store.objects_mut().id(), env: env.as_sys().clone().into(), func, }); let function_type = FunctionType::new(Args::wasm_types(), Rets::wasm_types()); - let type_index = store.engine().as_sys().register_signature(&function_type); + let type_index = store + .as_store_ref() + .engine() + .as_sys() + .register_signature(&function_type); let vmctx = VMFunctionContext { host_env: host_data.as_ref() as *const _ as *mut c_void, }; @@ -399,7 +427,10 @@ impl Function { } pub(crate) fn ty(&self, store: &impl AsStoreRef) -> FunctionType { - self.handle.get(store.objects().as_sys()).signature.clone() + self.handle + .get(store.as_store_ref().objects().as_sys()) + .signature + .clone() } fn call_wasm( @@ -465,35 +496,29 @@ impl Function { ) -> Result<(), RuntimeError> { // Call the trampoline. let result = { - let vm_function = self.handle.get(store.as_mut().objects.as_sys()); - let anyfunc = vm_function.anyfunc.as_ptr(); - let config = store.engine().tunables().vmconfig().clone(); - let signal_handler = store.signal_handler(); - - // Install the store into the store context - let store_id = store.objects().id(); - let store_install_guard = StoreContext::ensure_installed(store); + // Safety: the store context is uninstalled before we return, and the + // store mut is valid for the duration of the call. + let store_install_guard = + unsafe { StoreContext::ensure_installed(store.as_store_mut().inner as *mut _) }; let mut r; // TODO: This loop is needed for asyncify. It will be refactored with https://github.com/wasmerio/wasmer/issues/3451 loop { + let storeref = store.as_store_ref(); + let vm_function = self.handle.get(storeref.objects().as_sys()); + let config = storeref.engine().tunables().vmconfig(); r = unsafe { wasmer_call_trampoline( - signal_handler, - &config, - anyfunc.as_ref().vmctx, + store.as_store_ref().signal_handler(), + config, + vm_function.anyfunc.as_ptr().as_ref().vmctx, trampoline, - anyfunc.as_ref().func_ptr, + vm_function.anyfunc.as_ptr().as_ref().func_ptr, params.as_mut_ptr() as *mut u8, ) }; - - // The `store` parameter potentially doesn't have its StoreMut anymore; - // so borrow another reference from the store context which owns the - // StoreMut at this point anyway. - let mut store_wrapper = unsafe { StoreContext::get_current(store_id) }; - let mut store_mut = store_wrapper.as_mut(); - if let Some(callback) = store_mut.as_mut().on_called.take() { + let store_mut = store.as_store_mut(); + if let Some(callback) = store_mut.inner.on_called.take() { match callback(store_mut) { Ok(wasmer_types::OnCalledAction::InvokeAgain) => { continue; @@ -541,7 +566,7 @@ impl Function { ) -> Result, RuntimeError> { let trampoline = unsafe { self.handle - .get(store.objects().as_sys()) + .get(store.objects_mut().as_sys()) .anyfunc .as_ptr() .as_ref() @@ -571,7 +596,7 @@ impl Function { ) -> Result, RuntimeError> { let trampoline = unsafe { self.handle - .get(store.objects().as_sys()) + .get(store.objects_mut().as_sys()) .anyfunc .as_ptr() .as_ref() @@ -583,7 +608,7 @@ impl Function { } pub(crate) fn vm_funcref(&self, store: &impl AsStoreRef) -> VMFuncRef { - let vm_function = self.handle.get(store.objects().as_sys()); + let vm_function = self.handle.get(store.as_store_ref().objects().as_sys()); if vm_function.kind == VMFunctionKind::Dynamic { panic!("dynamic functions cannot be used in tables or as funcrefs"); } @@ -594,6 +619,7 @@ impl Function { let signature = { let anyfunc = unsafe { funcref.0.as_ref() }; store + .as_store_mut() .engine() .as_sys() .lookup_signature(anyfunc.type_index) @@ -615,14 +641,14 @@ impl Function { pub(crate) fn from_vm_extern(store: &mut impl AsStoreMut, vm_extern: VMExternFunction) -> Self { Self { handle: unsafe { - StoreHandle::from_internal(store.objects().id(), vm_extern.into_sys()) + StoreHandle::from_internal(store.objects_mut().id(), vm_extern.into_sys()) }, } } /// Checks whether this `Function` can be used with the given store. pub(crate) fn is_from_store(&self, store: &impl AsStoreRef) -> bool { - self.handle.store_id() == store.objects().id() + self.handle.store_id() == store.as_store_ref().objects().id() } pub(crate) fn to_vm_extern(&self) -> VMExtern { @@ -808,7 +834,7 @@ where let mut store_wrapper = StoreContext::get_current_transient(this.ctx.store_id); let mut store = store_wrapper.as_mut().unwrap(); wasmer_vm::libcalls::throw( - store.objects().as_sys(), + store.objects.as_sys(), exception.vm_exceptionref().as_sys().to_u32_exnref(), ) } @@ -901,7 +927,7 @@ macro_rules! impl_host_function { let mut store = store_wrapper.as_mut(); $( let $x = unsafe { - FromToNativeWasmType::from_native(NativeWasmTypeInto::from_abi(store, $x)) + FromToNativeWasmType::from_native(NativeWasmTypeInto::from_abi(&mut store, $x)) }; )* to_invocation_result((env.func)($($x),* ).into_result()) @@ -927,7 +953,7 @@ macro_rules! impl_host_function { let mut store_wrapper = StoreContext::get_current_transient(env.store_id); let mut store = store_wrapper.as_mut().unwrap(); wasmer_vm::libcalls::throw( - store.objects().as_sys(), + store.objects.as_sys(), exception.vm_exceptionref().as_sys().to_u32_exnref() ) } @@ -991,7 +1017,7 @@ macro_rules! impl_host_function { let mut store = store_wrapper.as_mut(); $( let $x = unsafe { - FromToNativeWasmType::from_native(NativeWasmTypeInto::from_abi(store, $x)) + FromToNativeWasmType::from_native(NativeWasmTypeInto::from_abi(&mut store, $x)) }; )* let f_env = crate::backend::sys::function::env::FunctionEnvMut { @@ -1021,7 +1047,7 @@ macro_rules! impl_host_function { let mut store_wrapper = StoreContext::get_current_transient(env.store_id); let mut store = store_wrapper.as_mut().unwrap(); wasmer_vm::libcalls::throw( - store.objects().as_sys(), + store.objects.as_sys(), exception.vm_exceptionref().as_sys().to_u32_exnref() ) } diff --git a/lib/api/src/backend/sys/entities/function/typed.rs b/lib/api/src/backend/sys/entities/function/typed.rs index 293d2a0990b..abebc3ee9c4 100644 --- a/lib/api/src/backend/sys/entities/function/typed.rs +++ b/lib/api/src/backend/sys/entities/function/typed.rs @@ -22,7 +22,7 @@ macro_rules! impl_native_traits { let anyfunc = unsafe { *self.func.as_sys() .handle - .get(store.objects().as_sys()) + .get(store.as_store_ref().objects().as_sys()) .anyfunc .as_ptr() .as_ref() @@ -50,32 +50,27 @@ macro_rules! impl_native_traits { rets_list.as_mut() }; - let config = store.engine().tunables().vmconfig().clone(); - let signal_handler = store.signal_handler(); - // Install the store into the store context - let store_id = store.objects().id(); - let store_install_guard = StoreContext::ensure_installed(store); + let store_install_guard = unsafe { + StoreContext::ensure_installed(store.as_store_mut().inner as *mut _) + }; let mut r; loop { + let storeref = store.as_store_ref(); + let config = storeref.engine().tunables().vmconfig(); r = unsafe { wasmer_vm::wasmer_call_trampoline( - signal_handler, - &config, + store.as_store_ref().signal_handler(), + config, anyfunc.vmctx, anyfunc.call_trampoline, anyfunc.func_ptr, args_rets.as_mut_ptr() as *mut u8, ) }; - - // The `store` parameter potentially doesn't have its StoreMut anymore; - // so borrow another reference from the store context which owns the - // StoreMut at this point anyway. - let mut store_wrapper = unsafe { StoreContext::get_current(store_id) }; - let mut store_mut = store_wrapper.as_mut(); - if let Some(callback) = store_mut.as_mut().on_called.take() { + let store_mut = store.as_store_mut(); + if let Some(callback) = store_mut.inner.on_called.take() { match callback(store_mut) { Ok(wasmer_types::OnCalledAction::InvokeAgain) => { continue; } Ok(wasmer_types::OnCalledAction::Finish) => { break; } @@ -156,7 +151,7 @@ macro_rules! impl_native_traits { let anyfunc = unsafe { *self.func.as_sys() .handle - .get(store.objects().as_sys()) + .get(store.as_store_ref().objects().as_sys()) .anyfunc .as_ptr() .as_ref() @@ -177,32 +172,27 @@ macro_rules! impl_native_traits { rets_list.as_mut() }; - let config = store.engine().tunables().vmconfig().clone(); - let signal_handler = store.signal_handler(); - // Install the store into the store context - let store_id = store.objects().id(); - let store_install_guard = StoreContext::ensure_installed(store); + let store_install_guard = unsafe { + StoreContext::ensure_installed(store.as_store_mut().inner as *mut _) + }; let mut r; loop { + let storeref = store.as_store_ref(); + let config = storeref.engine().tunables().vmconfig(); r = unsafe { wasmer_vm::wasmer_call_trampoline( - signal_handler, - &config, + store.as_store_ref().signal_handler(), + config, anyfunc.vmctx, anyfunc.call_trampoline, anyfunc.func_ptr, args_rets.as_mut_ptr() as *mut u8, ) }; - - // The `store` parameter potentially doesn't have its StoreMut anymore; - // so borrow another reference from the store context which owns the - // StoreMut at this point anyway. - let mut store_wrapper = unsafe { StoreContext::get_current(store_id) }; - let mut store_mut = store_wrapper.as_mut(); - if let Some(callback) = store_mut.as_mut().on_called.take() { + let store_mut = store.as_store_mut(); + if let Some(callback) = store_mut.inner.on_called.take() { // TODO: OnCalledAction is needed for asyncify. It will be refactored with https://github.com/wasmerio/wasmer/issues/3451 match callback(store_mut) { Ok(wasmer_types::OnCalledAction::InvokeAgain) => { continue; } diff --git a/lib/api/src/backend/sys/entities/global.rs b/lib/api/src/backend/sys/entities/global.rs index 77dd35a2886..3548bc3a6a5 100644 --- a/lib/api/src/backend/sys/entities/global.rs +++ b/lib/api/src/backend/sys/entities/global.rs @@ -40,18 +40,25 @@ impl Global { } pub(crate) fn ty(&self, store: &impl AsStoreRef) -> GlobalType { - *self.handle.get(store.objects().as_sys()).ty() + *self + .handle + .get(store.as_store_ref().objects().as_sys()) + .ty() } pub(crate) fn get(&self, store: &mut impl AsStoreMut) -> Value { unsafe { let raw = self .handle - .get(store.objects().as_sys()) + .get(store.as_store_ref().objects().as_sys()) .vmglobal() .as_ref() .val; - let ty = self.handle.get(store.objects().as_sys()).ty().ty; + let ty = self + .handle + .get(store.as_store_ref().objects().as_sys()) + .ty() + .ty; Value::from_raw(store, ty, raw) } } @@ -72,7 +79,7 @@ impl Global { } unsafe { self.handle - .get_mut(store.objects_mut().as_sys_mut()) + .get_mut(store.as_store_mut().objects_mut().as_sys_mut()) .vmglobal() .as_mut() .val = val.as_raw(store); @@ -83,13 +90,16 @@ impl Global { pub(crate) fn from_vm_extern(store: &mut impl AsStoreMut, vm_extern: VMExternGlobal) -> Self { Self { handle: unsafe { - StoreHandle::from_internal(store.objects().id(), vm_extern.into_sys()) + StoreHandle::from_internal( + store.as_store_ref().objects().id(), + vm_extern.into_sys(), + ) }, } } pub(crate) fn is_from_store(&self, store: &impl AsStoreRef) -> bool { - self.handle.store_id() == store.objects().id() + self.handle.store_id() == store.as_store_ref().objects().id() } pub(crate) fn to_vm_extern(&self) -> VMExtern { diff --git a/lib/api/src/backend/sys/entities/instance.rs b/lib/api/src/backend/sys/entities/instance.rs index 790b937b7d7..15edc83407d 100644 --- a/lib/api/src/backend/sys/entities/instance.rs +++ b/lib/api/src/backend/sys/entities/instance.rs @@ -54,7 +54,10 @@ impl Instance { let mut handle = module.as_sys().instantiate(store, &externs)?; let exports = Self::get_exports(store, module, handle.as_sys_mut()); let instance = Self { - _handle: StoreHandle::new(store.objects_mut().as_sys_mut(), handle.into_sys()), + _handle: StoreHandle::new( + store.as_store_mut().objects_mut().as_sys_mut(), + handle.into_sys(), + ), }; Ok((instance, exports)) diff --git a/lib/api/src/backend/sys/entities/memory/mod.rs b/lib/api/src/backend/sys/entities/memory/mod.rs index 8c83203ac45..ed16d220958 100644 --- a/lib/api/src/backend/sys/entities/memory/mod.rs +++ b/lib/api/src/backend/sys/entities/memory/mod.rs @@ -32,12 +32,13 @@ pub struct Memory { impl Memory { pub(crate) fn new(store: &mut impl AsStoreMut, ty: MemoryType) -> Result { + let mut store = store.as_store_mut(); let tunables = store.engine().tunables(); let style = tunables.memory_style(&ty); let memory = tunables.create_host_memory(&ty, &style)?; Ok(Self { - handle: StoreHandle::new(store.objects_mut().as_sys_mut(), memory), + handle: StoreHandle::new(store.as_store_mut().objects_mut().as_sys_mut(), memory), }) } @@ -47,11 +48,15 @@ impl Memory { } pub(crate) fn ty(&self, store: &impl AsStoreRef) -> MemoryType { - self.handle.get(store.objects().as_sys()).ty() + self.handle + .get(store.as_store_ref().objects().as_sys()) + .ty() } pub(crate) fn size(&self, store: &impl AsStoreRef) -> Pages { - self.handle.get(store.objects().as_sys()).size() + self.handle + .get(store.as_store_ref().objects().as_sys()) + .size() } pub(crate) fn grow( @@ -79,7 +84,7 @@ impl Memory { pub(crate) fn reset(&self, store: &mut impl AsStoreMut) -> Result<(), MemoryError> { self.handle - .get_mut(store.objects_mut().as_sys_mut()) + .get_mut(store.as_store_mut().objects_mut().as_sys_mut()) .reset()?; Ok(()) } @@ -87,20 +92,23 @@ impl Memory { pub(crate) fn from_vm_extern(store: &impl AsStoreRef, vm_extern: VMExternMemory) -> Self { Self { handle: unsafe { - StoreHandle::from_internal(store.objects().id(), vm_extern.into_sys()) + StoreHandle::from_internal( + store.as_store_ref().objects().id(), + vm_extern.into_sys(), + ) }, } } /// Checks whether this `Memory` can be used with the given context. pub(crate) fn is_from_store(&self, store: &impl AsStoreRef) -> bool { - self.handle.store_id() == store.objects().id() + self.handle.store_id() == store.as_store_ref().objects().id() } /// Cloning memory will create another reference to the same memory that /// can be put into a new store pub(crate) fn try_clone(&self, store: &impl AsStoreRef) -> Result { - let mem = self.handle.get(store.objects().as_sys()); + let mem = self.handle.get(store.as_store_ref().objects().as_sys()); let cloned = mem.try_clone()?; Ok(cloned.into()) } @@ -119,7 +127,7 @@ impl Memory { &self, store: &impl AsStoreRef, ) -> Option { - let mem = self.handle.get(store.objects().as_sys()); + let mem = self.handle.get(store.as_store_ref().objects().as_sys()); let conds = mem.thread_conditions()?.downgrade(); Some(crate::memory::shared::SharedMemory::new( diff --git a/lib/api/src/backend/sys/entities/memory/view.rs b/lib/api/src/backend/sys/entities/memory/view.rs index 437e49bd1fb..e68c9b81e58 100644 --- a/lib/api/src/backend/sys/entities/memory/view.rs +++ b/lib/api/src/backend/sys/entities/memory/view.rs @@ -23,9 +23,15 @@ pub struct MemoryView<'a> { impl<'a> MemoryView<'a> { pub(crate) fn new(memory: &Memory, store: &'a (impl AsStoreRef + ?Sized)) -> Self { - let size = memory.handle.get(store.objects().as_sys()).size(); - - let definition = memory.handle.get(store.objects().as_sys()).vmmemory(); + let size = memory + .handle + .get(store.as_store_ref().objects().as_sys()) + .size(); + + let definition = memory + .handle + .get(store.as_store_ref().objects().as_sys()) + .vmmemory(); let def = unsafe { definition.as_ref() }; Self { @@ -86,7 +92,6 @@ impl<'a> MemoryView<'a> { /// ``` /// # use wasmer::{Memory, MemoryType, Pages, Store, Type, Value}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # /// let m = Memory::new(&mut store, MemoryType::new(1, None, false)).unwrap(); /// diff --git a/lib/api/src/backend/sys/entities/module.rs b/lib/api/src/backend/sys/entities/module.rs index 988886d1f7e..cd12fd60c1b 100644 --- a/lib/api/src/backend/sys/entities/module.rs +++ b/lib/api/src/backend/sys/entities/module.rs @@ -165,9 +165,11 @@ impl Module { return Err(InstantiationError::DifferentStores); } } - let signal_handler = store.signal_handler(); - let (engine, objects) = store.engine_and_objects_mut(); - let config = engine.tunables().vmconfig().clone(); + let signal_handler = store.as_store_ref().signal_handler(); + let mut store_mut = store.as_store_mut(); + let store_ptr = store_mut.inner as *mut _; + let (engine, objects) = store_mut.engine_and_objects_mut(); + let config = engine.tunables().vmconfig(); unsafe { let mut instance_handle = self.artifact.instantiate( engine.tunables(), @@ -179,7 +181,7 @@ impl Module { )?; let store_id = objects.id(); - let store_install_guard = StoreContext::ensure_installed(store); + let store_install_guard = StoreContext::ensure_installed(store_ptr); // After the instance handle is created, we need to initialize // the data, call the start function and so. However, if any @@ -187,7 +189,7 @@ impl Module { // as some of the Instance elements may have placed in other // instance tables. self.artifact - .finish_instantiation(&config, signal_handler, &mut instance_handle)?; + .finish_instantiation(config, signal_handler, &mut instance_handle)?; drop(store_install_guard); diff --git a/lib/api/src/backend/sys/entities/table.rs b/lib/api/src/backend/sys/entities/table.rs index 4caa0347901..88cd08ae0de 100644 --- a/lib/api/src/backend/sys/entities/table.rs +++ b/lib/api/src/backend/sys/entities/table.rs @@ -65,6 +65,7 @@ impl Table { init: Value, ) -> Result { let item = value_to_table_element(&mut store, init)?; + let mut store = store.as_store_mut(); let tunables = store.engine().tunables(); let style = tunables.table_style(&ty); let mut table = tunables @@ -82,11 +83,17 @@ impl Table { } pub(crate) fn ty(&self, store: &impl AsStoreRef) -> TableType { - *self.handle.get(store.objects().as_sys()).ty() + *self + .handle + .get(store.as_store_ref().objects().as_sys()) + .ty() } pub(crate) fn get(&self, store: &mut impl AsStoreMut, index: u32) -> Option { - let item = self.handle.get(store.objects().as_sys()).get(index)?; + let item = self + .handle + .get(store.as_store_ref().objects().as_sys()) + .get(index)?; Some(value_from_table_element(store, item)) } @@ -105,7 +112,9 @@ impl Table { } pub(crate) fn size(&self, store: &impl AsStoreRef) -> u32 { - self.handle.get(store.objects().as_sys()).size() + self.handle + .get(store.as_store_ref().objects().as_sys()) + .size() } pub(crate) fn grow( @@ -153,14 +162,17 @@ impl Table { pub(crate) fn from_vm_extern(store: &mut impl AsStoreMut, vm_extern: VMExternTable) -> Self { Self { handle: unsafe { - StoreHandle::from_internal(store.objects().id(), vm_extern.into_sys()) + StoreHandle::from_internal( + store.as_store_ref().objects().id(), + vm_extern.into_sys(), + ) }, } } /// Checks whether this `Table` can be used with the given context. pub(crate) fn is_from_store(&self, store: &impl AsStoreRef) -> bool { - self.handle.store_id() == store.objects().id() + self.handle.store_id() == store.as_store_ref().objects().id() } pub(crate) fn to_vm_extern(&self) -> VMExtern { diff --git a/lib/api/src/backend/sys/entities/tag.rs b/lib/api/src/backend/sys/entities/tag.rs index 19e297377d8..58452513430 100644 --- a/lib/api/src/backend/sys/entities/tag.rs +++ b/lib/api/src/backend/sys/entities/tag.rs @@ -41,7 +41,7 @@ impl Tag { kind: wasmer_types::TagKind::Exception, params: self .handle - .get(store.objects().as_sys()) + .get(store.as_store_ref().objects().as_sys()) .signature .params() .into(), @@ -58,7 +58,7 @@ impl Tag { /// Check whether or not the [`Tag`] is from the given store. pub fn is_from_store(&self, store: &impl AsStoreRef) -> bool { - self.handle.store_id() == store.objects().id() + self.handle.store_id() == store.as_store_ref().objects().id() } pub(crate) fn to_vm_extern(&self) -> VMExtern { diff --git a/lib/api/src/entities/engine/engine_ref.rs b/lib/api/src/entities/engine/engine_ref.rs index 62beab8c809..8edaa93d542 100644 --- a/lib/api/src/entities/engine/engine_ref.rs +++ b/lib/api/src/entities/engine/engine_ref.rs @@ -32,7 +32,7 @@ pub trait AsEngineRef { /// /// NOTE: this function will return [`None`] if the [`AsEngineRef`] implementor is not an /// actual [`crate::Store`]. - fn maybe_as_store(&self) -> Option<&StoreRef> { + fn maybe_as_store(&self) -> Option> { None } } @@ -54,7 +54,7 @@ where (**self).as_engine_ref() } - fn maybe_as_store(&self) -> Option<&StoreRef> { + fn maybe_as_store(&self) -> Option> { (**self).maybe_as_store() } } diff --git a/lib/api/src/entities/exception/inner.rs b/lib/api/src/entities/exception/inner.rs index d2fa6f4b367..08d0344ca3e 100644 --- a/lib/api/src/entities/exception/inner.rs +++ b/lib/api/src/entities/exception/inner.rs @@ -21,7 +21,7 @@ impl BackendException { #[inline] #[allow(irrefutable_let_patterns)] pub fn new(store: &mut impl AsStoreMut, tag: &Tag, payload: &[Value]) -> Self { - match &store.as_mut().store { + match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => { let BackendTag::Sys(tag) = &tag.0 else { diff --git a/lib/api/src/entities/external/extref/inner.rs b/lib/api/src/entities/external/extref/inner.rs index ae05baebff9..9c974f7cdab 100644 --- a/lib/api/src/entities/external/extref/inner.rs +++ b/lib/api/src/entities/external/extref/inner.rs @@ -14,7 +14,7 @@ impl BackendExternRef { where T: Any + Send + Sync + 'static + Sized, { - match &store.as_mut().store { + match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] crate::BackendStore::Sys(s) => Self::Sys( crate::backend::sys::entities::external::ExternRef::new(store, value), @@ -78,7 +78,7 @@ impl BackendExternRef { store: &mut impl AsStoreMut, vm_externref: VMExternRef, ) -> Self { - match &store.as_mut().store { + match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => Self::Sys(unsafe { crate::backend::sys::entities::external::ExternRef::from_vm_externref( diff --git a/lib/api/src/entities/function/env/inner.rs b/lib/api/src/entities/function/env/inner.rs index a65d73c4bb9..2e5941fbdfb 100644 --- a/lib/api/src/entities/function/env/inner.rs +++ b/lib/api/src/entities/function/env/inner.rs @@ -54,7 +54,7 @@ impl BackendFunctionEnv { where T: Any + Send + 'static + Sized, { - match store.as_mut().store { + match store.as_store_mut().inner.store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => Self::Sys( crate::backend::sys::function::env::FunctionEnv::new(store, value), @@ -199,40 +199,31 @@ impl BackendFunctionEnvMut<'_, T> { } /// Borrows a new mutable reference of both the attached Store and host state - pub fn data_and_store_mut(&mut self) -> (&mut T, &mut StoreMut) { + pub fn data_and_store_mut(&mut self) -> (&mut T, StoreMut<'_>) { match_rt!(on self => f { f.data_and_store_mut() }) } - - /// Creates an [`AsAsyncStore`] from this [`BackendAsyncFunctionEnvMut`]. - pub fn as_async_store(&mut self) -> impl AsAsyncStore + 'static { - match self { - #[cfg(feature = "sys")] - Self::Sys(f) => f.as_async_store(), - _ => unsupported_async_backend(), - } - } } impl AsStoreRef for BackendFunctionEnvMut<'_, T> { - fn as_ref(&self) -> &crate::StoreInner { - match_rt!(on self => f { - f.as_ref() + fn as_store_ref(&self) -> StoreRef<'_> { + match_rt!(on self => s { + s.as_store_ref() }) } } impl AsStoreMut for BackendFunctionEnvMut<'_, T> { - fn as_mut(&mut self) -> &mut crate::StoreInner { - match_rt!(on self => f { - f.as_mut() + fn as_store_mut(&mut self) -> StoreMut<'_> { + match_rt!(on self => s { + s.as_store_mut() }) } - fn reborrow_mut(&mut self) -> &mut StoreMut { - match_rt!(on self => f { - f.reborrow_mut() + fn objects_mut(&mut self) -> &mut crate::StoreObjects { + match_rt!(on self => s { + s.objects_mut() }) } } @@ -347,10 +338,10 @@ impl BackendAsyncFunctionEnvHandle<'_, T> { } impl AsStoreRef for BackendAsyncFunctionEnvHandle<'_, T> { - fn as_ref(&self) -> &crate::StoreInner { + fn as_store_ref(&self) -> StoreRef<'_> { match self { #[cfg(feature = "sys")] - Self::Sys(f) => AsStoreRef::as_ref(f), + Self::Sys(f) => AsStoreRef::as_store_ref(f), _ => unsupported_async_backend(), } } @@ -377,44 +368,28 @@ impl BackendAsyncFunctionEnvHandleMut<'_, T> { } impl AsStoreRef for BackendAsyncFunctionEnvHandleMut<'_, T> { - fn as_ref(&self) -> &crate::StoreInner { + fn as_store_ref(&self) -> StoreRef<'_> { match self { #[cfg(feature = "sys")] - Self::Sys(f) => AsStoreRef::as_ref(f), + Self::Sys(f) => AsStoreRef::as_store_ref(f), _ => unsupported_async_backend(), } } } impl AsStoreMut for BackendAsyncFunctionEnvHandleMut<'_, T> { - fn as_mut(&mut self) -> &mut crate::StoreInner { - match self { - #[cfg(feature = "sys")] - Self::Sys(f) => AsStoreMut::as_mut(f), - _ => unsupported_async_backend(), - } - } - - fn reborrow_mut(&mut self) -> &mut StoreMut { - match self { - #[cfg(feature = "sys")] - Self::Sys(f) => f.reborrow_mut(), - _ => unsupported_async_backend(), - } - } - - fn take(&mut self) -> Option { + fn as_store_mut(&mut self) -> StoreMut<'_> { match self { #[cfg(feature = "sys")] - Self::Sys(f) => f.take(), + Self::Sys(f) => AsStoreMut::as_store_mut(f), _ => unsupported_async_backend(), } } - fn put_back(&mut self, store_mut: StoreMut) { + fn objects_mut(&mut self) -> &mut crate::StoreObjects { match self { #[cfg(feature = "sys")] - Self::Sys(f) => f.put_back(store_mut), + Self::Sys(f) => AsStoreMut::objects_mut(f), _ => unsupported_async_backend(), } } diff --git a/lib/api/src/entities/function/env/mod.rs b/lib/api/src/entities/function/env/mod.rs index b1eb5eed39e..0998189ca32 100644 --- a/lib/api/src/entities/function/env/mod.rs +++ b/lib/api/src/entities/function/env/mod.rs @@ -79,35 +79,25 @@ impl FunctionEnvMut<'_, T> { self.0.as_mut() } - /// Borrows a new mutable reference to the attached Store - pub fn as_store_mut(&mut self) -> &mut StoreMut { - self.reborrow_mut() - } - /// Borrows a new mutable reference of both the attached Store and host state - pub fn data_and_store_mut(&mut self) -> (&mut T, &mut StoreMut) { + pub fn data_and_store_mut(&mut self) -> (&mut T, StoreMut<'_>) { self.0.data_and_store_mut() } - - /// Creates an [`AsAsyncStore`] from this [`FunctionEnvMut`]. - pub fn as_async_store(&mut self) -> impl AsAsyncStore + 'static { - self.0.as_async_store() - } } impl AsStoreRef for FunctionEnvMut<'_, T> { - fn as_ref(&self) -> &crate::StoreInner { - self.0.as_ref() + fn as_store_ref(&self) -> StoreRef<'_> { + self.0.as_store_ref() } } impl AsStoreMut for FunctionEnvMut<'_, T> { - fn as_mut(&mut self) -> &mut crate::StoreInner { - self.0.as_mut() + fn as_store_mut(&mut self) -> StoreMut<'_> { + self.0.as_store_mut() } - fn reborrow_mut(&mut self) -> &mut StoreMut { - self.0.reborrow_mut() + fn objects_mut(&mut self) -> &mut crate::StoreObjects { + self.0.objects_mut() } } @@ -175,8 +165,8 @@ impl AsyncFunctionEnvHandle<'_, T> { } impl AsStoreRef for AsyncFunctionEnvHandle<'_, T> { - fn as_ref(&self) -> &crate::StoreInner { - AsStoreRef::as_ref(&self.0) + fn as_store_ref(&self) -> StoreRef<'_> { + AsStoreRef::as_store_ref(&self.0) } } @@ -193,25 +183,17 @@ impl AsyncFunctionEnvHandleMut<'_, T> { } impl AsStoreRef for AsyncFunctionEnvHandleMut<'_, T> { - fn as_ref(&self) -> &crate::StoreInner { - AsStoreRef::as_ref(&self.0) + fn as_store_ref(&self) -> StoreRef<'_> { + AsStoreRef::as_store_ref(&self.0) } } impl AsStoreMut for AsyncFunctionEnvHandleMut<'_, T> { - fn as_mut(&mut self) -> &mut crate::StoreInner { - AsStoreMut::as_mut(&mut self.0) - } - - fn reborrow_mut(&mut self) -> &mut StoreMut { - AsStoreMut::reborrow_mut(&mut self.0) - } - - fn take(&mut self) -> Option { - AsStoreMut::take(&mut self.0) + fn as_store_mut(&mut self) -> StoreMut<'_> { + AsStoreMut::as_store_mut(&mut self.0) } - fn put_back(&mut self, store_mut: StoreMut) { - AsStoreMut::put_back(&mut self.0, store_mut); + fn objects_mut(&mut self) -> &mut crate::StoreObjects { + AsStoreMut::objects_mut(&mut self.0) } } diff --git a/lib/api/src/entities/function/inner.rs b/lib/api/src/entities/function/inner.rs index 6bd07452d8b..2f7ddee55d3 100644 --- a/lib/api/src/entities/function/inner.rs +++ b/lib/api/src/entities/function/inner.rs @@ -45,7 +45,7 @@ impl BackendFunction { FT: Into, F: Fn(&[Value]) -> Result, RuntimeError> + 'static + Send + Sync, { - let env = FunctionEnv::new(store, ()); + let env = FunctionEnv::new(&mut store.as_store_mut(), ()); let wrapped_func = move |_env: FunctionEnvMut<()>, args: &[Value]| -> Result, RuntimeError> { func(args) }; @@ -65,7 +65,6 @@ impl BackendFunction { /// ``` /// # use wasmer::{Function, FunctionEnv, FunctionType, Type, Store, Value}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # let env = FunctionEnv::new(&mut store, ()); /// # /// let signature = FunctionType::new(vec![Type::I32, Type::I32], vec![Type::I32]); @@ -81,7 +80,6 @@ impl BackendFunction { /// ``` /// # use wasmer::{Function, FunctionEnv, FunctionType, Type, Store, Value}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # let env = FunctionEnv::new(&mut store, ()); /// # /// const I32_I32_TO_I32: ([Type; 2], [Type; 1]) = ([Type::I32, Type::I32], [Type::I32]); @@ -105,7 +103,7 @@ impl BackendFunction { + Send + Sync, { - match &store.as_mut().store { + match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => Self::Sys( crate::backend::sys::entities::function::Function::new_with_env( @@ -153,7 +151,7 @@ impl BackendFunction { Args: WasmTypeList + 'static, Rets: WasmTypeList + 'static, { - match &store.as_mut().store { + match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => { Self::Sys(crate::backend::sys::entities::function::Function::new_typed(store, func)) @@ -193,7 +191,6 @@ impl BackendFunction { /// ``` /// # use wasmer::{Store, Function, FunctionEnv, FunctionEnvMut}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # let env = FunctionEnv::new(&mut store, ()); /// # /// fn sum(_env: FunctionEnvMut<()>, a: i32, b: i32) -> i32 { @@ -213,7 +210,7 @@ impl BackendFunction { Args: WasmTypeList + 'static, Rets: WasmTypeList + 'static, { - match &store.as_mut().store { + match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] crate::BackendStore::Sys(s) => Self::Sys( crate::backend::sys::entities::function::Function::new_typed_with_env( @@ -261,7 +258,7 @@ impl BackendFunction { F: Fn(&[Value]) -> Fut + 'static, Fut: Future, RuntimeError>> + 'static, { - match &store.as_mut().store { + match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => Self::Sys( crate::backend::sys::entities::function::Function::new_async(store, ty, func), @@ -291,7 +288,7 @@ impl BackendFunction { F: Fn(AsyncFunctionEnvMut, &[Value]) -> Fut + 'static, Fut: Future, RuntimeError>> + 'static, { - match &store.as_mut().store { + match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => Self::Sys( crate::backend::sys::entities::function::Function::new_with_env_async( @@ -318,7 +315,7 @@ impl BackendFunction { Args: WasmTypeList + 'static, Rets: WasmTypeList + 'static, { - match &store.as_mut().store { + match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => Self::Sys( crate::backend::sys::entities::function::Function::new_typed_async(store, func), @@ -347,7 +344,7 @@ impl BackendFunction { Args: WasmTypeList + 'static, Rets: WasmTypeList + 'static, { - match &store.as_mut().store { + match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => Self::Sys( crate::backend::sys::entities::function::Function::new_typed_with_env_async( @@ -374,7 +371,6 @@ impl BackendFunction { /// ``` /// # use wasmer::{Function, FunctionEnv, FunctionEnvMut, Store, Type}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # let env = FunctionEnv::new(&mut store, ()); /// # /// fn sum(_env: FunctionEnvMut<()>, a: i32, b: i32) -> i32 { @@ -400,7 +396,6 @@ impl BackendFunction { /// ``` /// # use wasmer::{Function, FunctionEnv, FunctionEnvMut, Store, Type}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # let env = FunctionEnv::new(&mut store, ()); /// # /// fn sum(_env: FunctionEnvMut<()>, a: i32, b: i32) -> i32 { @@ -423,7 +418,6 @@ impl BackendFunction { /// ``` /// # use wasmer::{Function, FunctionEnv, FunctionEnvMut, Store, Type}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # let env = FunctionEnv::new(&mut store, ()); /// # /// fn sum(_env: FunctionEnvMut<()>, a: i32, b: i32) -> i32 { @@ -453,6 +447,7 @@ impl BackendFunction { /// # use wasmer::{imports, wat2wasm, Function, Instance, Module, Store, Type, Value}; /// # use wasmer::FunctionEnv; /// # let mut store = Store::default(); + /// # let env = FunctionEnv::new(&mut store, ()); /// # let wasm_bytes = wat2wasm(r#" /// # (module /// # (func (export "sum") (param $x i32) (param $y i32) (result i32) @@ -461,9 +456,7 @@ impl BackendFunction { /// # i32.add /// # )) /// # "#.as_bytes()).unwrap(); - /// # let module = Module::new(&store.engine(), wasm_bytes).unwrap(); - /// # let mut store = store.as_mut(); - /// # let env = FunctionEnv::new(&mut store, ()); + /// # let module = Module::new(&store, wasm_bytes).unwrap(); /// # let import_object = imports! {}; /// # let instance = Instance::new(&mut store, &module, &import_object).unwrap(); /// # @@ -536,7 +529,7 @@ impl BackendFunction { #[inline] pub(crate) unsafe fn from_vm_funcref(store: &mut impl AsStoreMut, funcref: VMFuncRef) -> Self { - match &store.as_mut().store { + match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] crate::BackendStore::Sys(s) => Self::Sys(unsafe { crate::backend::sys::entities::function::Function::from_vm_funcref( @@ -591,6 +584,7 @@ impl BackendFunction { /// # use wasmer::{imports, wat2wasm, Function, Instance, Module, Store, Type, TypedFunction, Value}; /// # use wasmer::FunctionEnv; /// # let mut store = Store::default(); + /// # let env = FunctionEnv::new(&mut store, ()); /// # let wasm_bytes = wat2wasm(r#" /// # (module /// # (func (export "sum") (param $x i32) (param $y i32) (result i32) @@ -599,9 +593,7 @@ impl BackendFunction { /// # i32.add /// # )) /// # "#.as_bytes()).unwrap(); - /// # let module = Module::new(&store.engine(), wasm_bytes).unwrap(); - /// # let mut store = store.as_mut(); - /// # let env = FunctionEnv::new(&mut store, ()); + /// # let module = Module::new(&store, wasm_bytes).unwrap(); /// # let import_object = imports! {}; /// # let instance = Instance::new(&mut store, &module, &import_object).unwrap(); /// # @@ -620,6 +612,7 @@ impl BackendFunction { /// # use wasmer::{imports, wat2wasm, Function, Instance, Module, Store, Type, TypedFunction, Value}; /// # use wasmer::FunctionEnv; /// # let mut store = Store::default(); + /// # let env = FunctionEnv::new(&mut store, ()); /// # let wasm_bytes = wat2wasm(r#" /// # (module /// # (func (export "sum") (param $x i32) (param $y i32) (result i32) @@ -628,9 +621,7 @@ impl BackendFunction { /// # i32.add /// # )) /// # "#.as_bytes()).unwrap(); - /// # let module = Module::new(&store.engine(), wasm_bytes).unwrap(); - /// # let mut store = store.as_mut(); - /// # let env = FunctionEnv::new(&mut store, ()); + /// # let module = Module::new(&store, wasm_bytes).unwrap(); /// # let import_object = imports! {}; /// # let instance = Instance::new(&mut store, &module, &import_object).unwrap(); /// # @@ -647,6 +638,7 @@ impl BackendFunction { /// # use wasmer::{imports, wat2wasm, Function, Instance, Module, Store, Type, TypedFunction, Value}; /// # use wasmer::FunctionEnv; /// # let mut store = Store::default(); + /// # let env = FunctionEnv::new(&mut store, ()); /// # let wasm_bytes = wat2wasm(r#" /// # (module /// # (func (export "sum") (param $x i32) (param $y i32) (result i32) @@ -655,9 +647,7 @@ impl BackendFunction { /// # i32.add /// # )) /// # "#.as_bytes()).unwrap(); - /// # let module = Module::new(&store.engine(), wasm_bytes).unwrap(); - /// # let mut store = store.as_mut(); - /// # let env = FunctionEnv::new(&mut store, ()); + /// # let module = Module::new(&store, wasm_bytes).unwrap(); /// # let import_object = imports! {}; /// # let instance = Instance::new(&mut store, &module, &import_object).unwrap(); /// # @@ -705,7 +695,7 @@ impl BackendFunction { } pub(crate) fn from_vm_extern(store: &mut impl AsStoreMut, vm_extern: VMExternFunction) -> Self { - match &store.as_mut().store { + match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => Self::Sys( crate::backend::sys::entities::function::Function::from_vm_extern(store, vm_extern), diff --git a/lib/api/src/entities/global/inner.rs b/lib/api/src/entities/global/inner.rs index fca4982c42c..bb867eeba4a 100644 --- a/lib/api/src/entities/global/inner.rs +++ b/lib/api/src/entities/global/inner.rs @@ -27,7 +27,6 @@ impl BackendGlobal { /// ``` /// # use wasmer::{Global, Mutability, Store, Value}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # /// let g = Global::new(&mut store, Value::I32(1)); /// @@ -46,7 +45,6 @@ impl BackendGlobal { /// ``` /// # use wasmer::{Global, Mutability, Store, Value}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # /// let g = Global::new_mut(&mut store, Value::I32(1)); /// @@ -65,7 +63,7 @@ impl BackendGlobal { val: Value, mutability: Mutability, ) -> Result { - match &store.as_mut().store { + match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => Ok(Self::Sys( crate::backend::sys::global::Global::from_value(store, val, mutability)?, @@ -101,7 +99,6 @@ impl BackendGlobal { /// ``` /// # use wasmer::{Global, Mutability, Store, Type, Value, GlobalType}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # /// let c = Global::new(&mut store, Value::I32(1)); /// let v = Global::new_mut(&mut store, Value::I64(1)); @@ -123,7 +120,6 @@ impl BackendGlobal { /// ``` /// # use wasmer::{Global, Store, Value}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # /// let g = Global::new(&mut store, Value::I32(1)); /// @@ -143,7 +139,6 @@ impl BackendGlobal { /// ``` /// # use wasmer::{Global, Store, Value}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # /// let g = Global::new_mut(&mut store, Value::I32(1)); /// @@ -161,7 +156,6 @@ impl BackendGlobal { /// ```should_panic /// # use wasmer::{Global, Store, Value}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # /// let g = Global::new(&mut store, Value::I32(1)); /// @@ -173,7 +167,6 @@ impl BackendGlobal { /// ```should_panic /// # use wasmer::{Global, Store, Value}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # /// let g = Global::new(&mut store, Value::I32(1)); /// @@ -189,7 +182,7 @@ impl BackendGlobal { #[inline] pub(crate) fn from_vm_extern(store: &mut impl AsStoreMut, vm_extern: VMExternGlobal) -> Self { - match &store.as_mut().store { + match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => Self::Sys( crate::backend::sys::global::Global::from_vm_extern(store, vm_extern), diff --git a/lib/api/src/entities/instance.rs b/lib/api/src/entities/instance.rs index b3d40b3c055..761e160e9ce 100644 --- a/lib/api/src/entities/instance.rs +++ b/lib/api/src/entities/instance.rs @@ -31,9 +31,8 @@ impl Instance { /// # use wasmer::FunctionEnv; /// # fn main() -> anyhow::Result<()> { /// let mut store = Store::default(); - /// let module = Module::new(&store.engine(), "(module)")?; - /// let mut store = store.as_mut(); /// let env = FunctionEnv::new(&mut store, ()); + /// let module = Module::new(&store, "(module)")?; /// let imports = imports!{ /// "host" => { /// "var" => Global::new(&mut store, Value::I32(2)) @@ -57,7 +56,7 @@ impl Instance { module: &Module, imports: &Imports, ) -> Result { - let (_inner, exports) = match &store.as_mut().store { + let (_inner, exports) = match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => { let (i, e) = crate::backend::sys::instance::Instance::new(store, module, imports)?; @@ -116,7 +115,7 @@ impl Instance { module: &Module, externs: &[Extern], ) -> Result { - let (_inner, exports) = match &store.as_mut().store { + let (_inner, exports) = match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => { let (i, e) = diff --git a/lib/api/src/entities/memory/inner.rs b/lib/api/src/entities/memory/inner.rs index c7d7cf777a3..74dd52c9602 100644 --- a/lib/api/src/entities/memory/inner.rs +++ b/lib/api/src/entities/memory/inner.rs @@ -23,13 +23,12 @@ impl BackendMemory { /// ``` /// # use wasmer::{Memory, MemoryType, Pages, Store, Type, Value}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # /// let m = Memory::new(&mut store, MemoryType::new(1, None, false)).unwrap(); /// ``` #[inline] pub fn new(store: &mut impl AsStoreMut, ty: MemoryType) -> Result { - match &store.as_mut().store { + match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] crate::BackendStore::Sys(s) => Ok(Self::Sys( crate::backend::sys::entities::memory::Memory::new(store, ty)?, @@ -60,7 +59,7 @@ impl BackendMemory { /// Create a memory object from an existing memory and attaches it to the store #[inline] pub fn new_from_existing(new_store: &mut impl AsStoreMut, memory: VMMemory) -> Self { - match new_store.as_mut().store { + match new_store.as_store_mut().inner.store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => Self::Sys( crate::backend::sys::entities::memory::Memory::new_from_existing( @@ -113,7 +112,6 @@ impl BackendMemory { /// ``` /// # use wasmer::{Memory, MemoryType, Pages, Store, Type, Value}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # /// let mt = MemoryType::new(1, None, false); /// let m = Memory::new(&mut store, mt).unwrap(); @@ -142,7 +140,6 @@ impl BackendMemory { /// ``` /// # use wasmer::{Memory, MemoryType, Pages, Store, Type, Value, WASM_MAX_PAGES}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # /// let m = Memory::new(&mut store, MemoryType::new(1, Some(3), false)).unwrap(); /// let p = m.grow(&mut store, 2).unwrap(); @@ -160,7 +157,6 @@ impl BackendMemory { /// # use wasmer::{Memory, MemoryType, Pages, Store, Type, Value, WASM_MAX_PAGES}; /// # use wasmer::FunctionEnv; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # let env = FunctionEnv::new(&mut store, ()); /// # /// let m = Memory::new(&mut store, MemoryType::new(1, Some(1), false)).unwrap(); @@ -255,7 +251,7 @@ impl BackendMemory { #[inline] pub(crate) fn from_vm_extern(store: &mut impl AsStoreMut, vm_extern: VMExternMemory) -> Self { - match &store.as_mut().store { + match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] crate::BackendStore::Sys(s) => Self::Sys( crate::backend::sys::entities::memory::Memory::from_vm_extern(store, vm_extern), diff --git a/lib/api/src/entities/memory/view/inner.rs b/lib/api/src/entities/memory/view/inner.rs index c95acfd6246..c0ff9103883 100644 --- a/lib/api/src/entities/memory/view/inner.rs +++ b/lib/api/src/entities/memory/view/inner.rs @@ -19,7 +19,7 @@ impl<'a> BackendMemoryView<'a> { #[inline] #[allow(clippy::needless_return)] pub(crate) fn new(memory: &Memory, store: &'a (impl AsStoreRef + ?Sized)) -> Self { - match &store.as_ref().store { + match &store.as_store_ref().inner.store { #[cfg(feature = "sys")] crate::BackendStore::Sys(s) => Self::Sys( crate::backend::sys::entities::memory::view::MemoryView::new( @@ -119,7 +119,6 @@ impl<'a> BackendMemoryView<'a> { /// ``` /// # use wasmer::{Memory, MemoryType, Pages, Store, Type, Value}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # /// let m = Memory::new(&mut store, MemoryType::new(1, None, false)).unwrap(); /// diff --git a/lib/api/src/entities/memory/view/mod.rs b/lib/api/src/entities/memory/view/mod.rs index 915ea4804b6..8f16105dc53 100644 --- a/lib/api/src/entities/memory/view/mod.rs +++ b/lib/api/src/entities/memory/view/mod.rs @@ -68,7 +68,6 @@ impl<'a> MemoryView<'a> { /// ``` /// # use wasmer::{Memory, MemoryType, Pages, Store, Type, Value}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # /// let m = Memory::new(&mut store, MemoryType::new(1, None, false)).unwrap(); /// diff --git a/lib/api/src/entities/store/async_.rs b/lib/api/src/entities/store/async_.rs index dc19b8be45d..362dd15da7e 100644 --- a/lib/api/src/entities/store/async_.rs +++ b/lib/api/src/entities/store/async_.rs @@ -1,20 +1,33 @@ use std::marker::PhantomData; use crate::{ - AsStoreMut, AsStoreRef, LocalReadGuardRc, LocalRwLock, LocalWriteGuardRc, StoreContext, + AsStoreMut, AsStoreRef, LocalReadGuardRc, LocalRwLock, LocalWriteGuardRc, Store, StoreContext, StoreInner, StoreMut, StorePtrWrapper, StoreRef, }; use wasmer_types::StoreId; +/// A store that can be used to invoke +/// [`Function::call_async`](crate::Function::call_async). pub struct StoreAsync { pub(crate) id: StoreId, pub(crate) inner: LocalRwLock, } impl StoreAsync { + /// Transform this [`StoreAsync`] back into a [`Store`] + /// if there are no coroutines running or waiting to run + /// against it. pub fn into_store(self) -> Result { - todo!() + match self.inner.consume() { + Ok(unwrapped) => Ok(Store { + inner: Box::new(unwrapped), + }), + Err(lock) => Err(Self { + id: self.id, + inner: lock, + }), + } } } @@ -48,9 +61,7 @@ pub trait AsAsyncStore { } /// Acquires a write lock on the store. - fn write_lock<'a>( - &'a self, - ) -> impl Future> + 'a { + fn write_lock<'a>(&'a self) -> impl Future> + 'a { AsyncStoreWriteLock::acquire(self.store_ref()) } } @@ -126,9 +137,9 @@ impl<'a> AsyncStoreWriteLock<'a> { None => { // Drop the option before awaiting, since the value isn't Send drop(store_context); - let store_mut = store.inner.write_rc().await; + let store_guard = store.inner.write_rc().await; Self { - inner: AsyncStoreWriteLockInner::Owned(store_mut), + inner: AsyncStoreWriteLockInner::Owned(store_guard), _marker: PhantomData, } } diff --git a/lib/api/src/entities/store/context.rs b/lib/api/src/entities/store/context.rs index c43b70f39aa..7bf05e36dff 100644 --- a/lib/api/src/entities/store/context.rs +++ b/lib/api/src/entities/store/context.rs @@ -38,6 +38,20 @@ use super::{AsStoreMut, AsStoreRef, StoreInner, StoreMut, StoreRef}; use wasmer_types::StoreId; +enum StoreContextEntry { + Sync(*mut StoreInner), + Async(LocalWriteGuardRc), +} + +impl StoreContextEntry { + fn as_ptr(&self) -> *mut StoreInner { + match self { + StoreContextEntry::Sync(ptr) => *ptr, + StoreContextEntry::Async(guard) => &**guard as *const _ as *mut _, + } + } +} + pub(crate) struct StoreContext { id: StoreId, @@ -51,11 +65,21 @@ pub(crate) struct StoreContext { // keep track of how many borrows there are so we don't drop // it prematurely. borrow_count: u32, - store_ptr: UnsafeCell<*mut StoreInner>, + entry: UnsafeCell, } pub(crate) struct StorePtrWrapper { - store_mut: *mut StoreInner, + store_ptr: *mut StoreInner, +} + +pub(crate) struct AsyncStoreGuardWrapper { + pub(crate) guard: *mut LocalWriteGuardRc, +} + +pub(crate) enum GetAsyncStoreGuardResult { + Ok(AsyncStoreGuardWrapper), + NotInstalled, + NotAsync, } pub(crate) struct ForcedStoreInstallGuard { @@ -87,36 +111,35 @@ impl StoreContext { }) } - fn install(store_ptr: *mut StoreInner) { - let id = unsafe { *store_ptr }.objects().id(); + fn install(id: StoreId, entry: StoreContextEntry) { STORE_CONTEXT_STACK.with(|cell| { let mut stack = cell.borrow_mut(); stack.push(StoreContext { id, borrow_count: 0, - store_ptr: UnsafeCell::new(store_mut), + entry: UnsafeCell::new(entry), }); }) } - /// # Safety - /// The pointer must be dereferenceable and remain valid until the - /// store context is uninstalled. - pub(crate) unsafe fn force_install(store_ptr: *mut StoreInner) -> ForcedStoreInstallGuard { - let id = unsafe { *store_ptr }.objects().id(); - Self::install(store_ptr); - ForcedStoreInstallGuard { store_id: Some(id) } + /// The write guard ensures this is the only reference to the store, + /// so installation can never fail. + pub(crate) fn install_async(guard: LocalWriteGuardRc) -> ForcedStoreInstallGuard { + let store_id = guard.objects.id(); + Self::install(store_id, StoreContextEntry::Async(guard)); + ForcedStoreInstallGuard { store_id } } + /// Install the store context as sync if it is not already installed. /// # Safety /// The pointer must be dereferenceable and remain valid until the /// store context is uninstalled. pub(crate) unsafe fn ensure_installed<'a>(store_ptr: *mut StoreInner) -> StoreInstallGuard { - let store_id = unsafe { *store_ptr }.objects().id(); + let store_id = unsafe { store_ptr.as_ref().unwrap().objects.id() }; if Self::is_active(store_id) { StoreInstallGuard::NotInstalled } else { - Self::install(store_ptr); + Self::install(store_id, StoreContextEntry::Sync(store_ptr)); StoreInstallGuard::Installed(store_id) } } @@ -137,7 +160,7 @@ impl StoreContext { assert_eq!(top.id, id, "Mismatched store context access"); top.borrow_count += 1; StorePtrWrapper { - store_mut: top.store_ptr.get(), + store_ptr: unsafe { top.entry.get().as_mut().unwrap().as_ptr() }, } }) } @@ -154,7 +177,7 @@ impl StoreContext { .last_mut() .expect("No store context installed on this thread"); assert_eq!(top.id, id, "Mismatched store context access"); - unsafe { top.store_ptr.get() } + unsafe { top.entry.get().as_mut().unwrap().as_ptr() } }) } @@ -168,29 +191,65 @@ impl StoreContext { } top.borrow_count += 1; Some(StorePtrWrapper { - store_mut: top.store_ptr.get(), + store_ptr: unsafe { top.entry.get().as_mut().unwrap().as_ptr() }, }) }) } + + /// Safety: See [`Self::get_current`]. + pub(crate) unsafe fn try_get_current_async(id: StoreId) -> GetAsyncStoreGuardResult { + STORE_CONTEXT_STACK.with(|cell| { + let mut stack = cell.borrow_mut(); + let Some(top) = stack.last_mut() else { + return GetAsyncStoreGuardResult::NotInstalled; + }; + if top.id != id { + return GetAsyncStoreGuardResult::NotInstalled; + } + match unsafe { top.entry.get().as_mut().unwrap() } { + StoreContextEntry::Async(guard) => { + top.borrow_count += 1; + GetAsyncStoreGuardResult::Ok(AsyncStoreGuardWrapper { + guard: guard as *mut _, + }) + } + StoreContextEntry::Sync(_) => GetAsyncStoreGuardResult::NotAsync, + } + }) + } } impl StorePtrWrapper { pub(crate) fn as_ref(&self) -> StoreRef<'_> { // Safety: the store_mut is always initialized unless the StoreMutWrapper // is dropped, at which point it's impossible to call this function - unsafe { self.store_mut.as_ref().unwrap().as_store_ref() } + unsafe { self.store_ptr.as_ref().unwrap().as_store_ref() } } pub(crate) fn as_mut(&mut self) -> StoreMut<'_> { // Safety: the store_mut is always initialized unless the StoreMutWrapper // is dropped, at which point it's impossible to call this function - unsafe { self.store_mut.as_mut().unwrap().as_store_mut() } + unsafe { self.store_ptr.as_mut().unwrap().as_store_mut() } } } impl Drop for StorePtrWrapper { fn drop(&mut self) { - let id = self.as_mut().objects().id(); + let id = self.as_mut().objects_mut().id(); + STORE_CONTEXT_STACK.with(|cell| { + let mut stack = cell.borrow_mut(); + let top = stack + .last_mut() + .expect("No store context installed on this thread"); + assert_eq!(top.id, id, "Mismatched store context reinstall"); + top.borrow_count -= 1; + }) + } +} + +impl Drop for AsyncStoreGuardWrapper { + fn drop(&mut self) { + let id = unsafe { self.guard.as_ref().unwrap().objects.id() }; STORE_CONTEXT_STACK.with(|cell| { let mut stack = cell.borrow_mut(); let top = stack diff --git a/lib/api/src/entities/store/local_rwlock.rs b/lib/api/src/entities/store/local_rwlock.rs index 557c2423d4a..33ae6c2276f 100644 --- a/lib/api/src/entities/store/local_rwlock.rs +++ b/lib/api/src/entities/store/local_rwlock.rs @@ -242,10 +242,8 @@ impl LocalRwLockInner { // No writers waiting, wake all readers (they can share the lock) drop(write_waiters); // Release borrow before borrowing read_waiters let mut read_waiters = self.read_waiters.borrow_mut(); - for waker_slot in read_waiters.drain(..) { - if let Some(waker) = waker_slot { - waker.wake(); - } + for waker in read_waiters.drain(..).flatten() { + waker.wake(); } } } @@ -380,6 +378,15 @@ pub struct LocalReadGuardRc { inner: Rc>, } +impl LocalReadGuardRc { + /// Rebuild a handle to the lock from this [`LocalReadGuardRc`]. + pub fn lock_handle(me: &Self) -> LocalRwLock { + LocalRwLock { + inner: me.inner.clone(), + } + } +} + impl Deref for LocalReadGuardRc { type Target = T; @@ -399,6 +406,15 @@ pub struct LocalWriteGuardRc { inner: Rc>, } +impl LocalWriteGuardRc { + /// Rebuild a handle to the lock from this [`LocalWriteGuardRc`]. + pub fn lock_handle(me: &Self) -> LocalRwLock { + LocalRwLock { + inner: me.inner.clone(), + } + } +} + impl Deref for LocalWriteGuardRc { type Target = T; diff --git a/lib/api/src/entities/store/mod.rs b/lib/api/src/entities/store/mod.rs index c1da1e2faf4..72c2221781a 100644 --- a/lib/api/src/entities/store/mod.rs +++ b/lib/api/src/entities/store/mod.rs @@ -127,6 +127,8 @@ impl Store { self.inner.objects.id() } + /// Transforms this store into a [`StoreAsync`] which can be used + /// to invoke [`Function::call_async`](crate::Function::call_async). pub fn into_async(self) -> StoreAsync { StoreAsync { id: self.id(), diff --git a/lib/api/src/entities/table/inner.rs b/lib/api/src/entities/table/inner.rs index a0f32103b7d..13e6f86e1d0 100644 --- a/lib/api/src/entities/table/inner.rs +++ b/lib/api/src/entities/table/inner.rs @@ -33,7 +33,7 @@ impl BackendTable { ty: TableType, init: Value, ) -> Result { - match &store.as_mut().store { + match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] BackendStore::Sys(_) => Ok(Self::Sys( crate::backend::sys::entities::table::Table::new(store, ty, init)?, @@ -135,7 +135,7 @@ impl BackendTable { src_index: u32, len: u32, ) -> Result<(), RuntimeError> { - match &store.as_mut().store { + match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] BackendStore::Sys(_) => crate::backend::sys::entities::table::Table::copy( store, @@ -196,7 +196,7 @@ impl BackendTable { #[inline] pub(crate) fn from_vm_extern(store: &mut impl AsStoreMut, ext: VMExternTable) -> Self { - match &store.as_mut().store { + match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] BackendStore::Sys(_) => Self::Sys( crate::backend::sys::entities::table::Table::from_vm_extern(store, ext), @@ -242,8 +242,6 @@ impl BackendTable { #[cfg(test)] mod test { - use crate::AsStoreRef; - /// Check the example from . #[test] #[cfg_attr( @@ -263,9 +261,7 @@ mod test { // Tests that the table type of `table` is compatible with the export in the WAT // This tests that `wasmer_types::types::is_table_compatible` works as expected. let mut store = Store::default(); - let module = Module::new(&store.engine(), WAT).unwrap(); - - let mut store = store.as_mut(); + let module = Module::new(&store, WAT).unwrap(); let ty = TableType::new(Type::FuncRef, 0, None); let table = Table::new(&mut store, ty, Value::FuncRef(None)).unwrap(); table.grow(&mut store, 100, Value::FuncRef(None)).unwrap(); diff --git a/lib/api/src/entities/tag/inner.rs b/lib/api/src/entities/tag/inner.rs index 48ccf7ab20a..5d05c4bbd12 100644 --- a/lib/api/src/entities/tag/inner.rs +++ b/lib/api/src/entities/tag/inner.rs @@ -22,7 +22,7 @@ impl BackendTag { /// and has no return value. #[inline] pub fn new>>(store: &mut impl AsStoreMut, params: P) -> Self { - match &store.as_mut().store { + match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => { Self::Sys(crate::backend::sys::tag::Tag::new(store, params)) @@ -60,7 +60,7 @@ impl BackendTag { #[inline] pub(crate) fn from_vm_extern(store: &mut impl AsStoreMut, vm_extern: VMExternTag) -> Self { - match &store.as_mut().store { + match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => Self::Sys( crate::backend::sys::tag::Tag::from_vm_extern(store, vm_extern), diff --git a/lib/api/src/entities/value.rs b/lib/api/src/entities/value.rs index e2955c20c58..06ec15ec82b 100644 --- a/lib/api/src/entities/value.rs +++ b/lib/api/src/entities/value.rs @@ -120,7 +120,7 @@ impl Value { Type::F32 => Self::F32(unsafe { raw.f32 }), Type::F64 => Self::F64(unsafe { raw.f64 }), Type::V128 => Self::V128(unsafe { raw.u128 }), - Type::FuncRef => match store.as_ref().store { + Type::FuncRef => match store.as_store_ref().inner.store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => Self::FuncRef({ unsafe { crate::backend::sys::vm::VMFuncRef::from_raw(raw).map(VMFuncRef::Sys) } @@ -157,7 +157,7 @@ impl Value { .map(|f| unsafe { Function::from_vm_funcref(store, f) }) }), }, - Type::ExternRef => match store.as_ref().store { + Type::ExternRef => match store.as_store_ref().inner.store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => Self::ExternRef({ unsafe { @@ -203,11 +203,14 @@ impl Value { .map(|f| unsafe { ExternRef::from_vm_externref(store, f) }) }), }, - Type::ExceptionRef => match store.as_ref().store { + Type::ExceptionRef => match store.as_store_ref().inner.store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => Self::ExceptionRef( unsafe { - crate::backend::sys::vm::VMExceptionRef::from_raw(store.objects().id(), raw) + crate::backend::sys::vm::VMExceptionRef::from_raw( + store.objects_mut().id(), + raw, + ) } .map(VMExceptionRef::Sys) .map(Exception::from_vm_exceptionref), diff --git a/lib/api/src/error.rs b/lib/api/src/error.rs index 45802fbf7b9..c283b8c106f 100644 --- a/lib/api/src/error.rs +++ b/lib/api/src/error.rs @@ -159,8 +159,8 @@ impl RuntimeError { /// will be thrown in the WebAssembly code instead of the usual trapping. pub fn exception(ctx: &impl AsStoreRef, exception: Exception) -> Self { let exnref = exception.vm_exceptionref(); - let store = ctx.as_ref(); - match store.objects { + let store = ctx.as_store_ref(); + match store.inner.objects { #[cfg(feature = "sys")] crate::StoreObjects::Sys(ref store_objects) => { crate::backend::sys::vm::Trap::uncaught_exception( diff --git a/lib/api/src/utils/native/convert.rs b/lib/api/src/utils/native/convert.rs index 9a755c4977a..05c4a1b9771 100644 --- a/lib/api/src/utils/native/convert.rs +++ b/lib/api/src/utils/native/convert.rs @@ -196,7 +196,7 @@ impl NativeWasmType for ExternRef { impl NativeWasmTypeInto for Option { #[inline] unsafe fn from_abi(store: &mut impl AsStoreMut, abi: Self::Abi) -> Self { - match store.as_ref().store { + match store.as_store_ref().inner.store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => unsafe { wasmer_vm::VMExternRef::from_raw(RawValue { externref: abi }).map(VMExternRef::Sys) @@ -242,7 +242,7 @@ impl NativeWasmTypeInto for Option { #[inline] unsafe fn from_raw(store: &mut impl AsStoreMut, raw: RawValue) -> Self { - match store.as_ref().store { + match store.as_store_ref().inner.store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => unsafe { wasmer_vm::VMExternRef::from_raw(raw).map(VMExternRef::Sys) @@ -290,7 +290,7 @@ impl NativeWasmType for Function { impl NativeWasmTypeInto for Option { #[inline] unsafe fn from_abi(store: &mut impl AsStoreMut, abi: Self::Abi) -> Self { - match store.as_ref().store { + match store.as_store_ref().inner.store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => unsafe { wasmer_vm::VMFuncRef::from_raw(RawValue { funcref: abi }).map(VMFuncRef::Sys) @@ -338,7 +338,7 @@ impl NativeWasmTypeInto for Option { #[inline] unsafe fn from_raw(store: &mut impl AsStoreMut, raw: RawValue) -> Self { - match store.as_ref().store { + match store.as_store_ref().inner.store { #[cfg(feature = "sys")] crate::BackendStore::Sys(_) => unsafe { wasmer_vm::VMFuncRef::from_raw(raw).map(VMFuncRef::Sys) diff --git a/lib/api/src/utils/native/typed_func.rs b/lib/api/src/utils/native/typed_func.rs index 63ae6310a78..695d619f925 100644 --- a/lib/api/src/utils/native/typed_func.rs +++ b/lib/api/src/utils/native/typed_func.rs @@ -62,7 +62,7 @@ macro_rules! impl_native_traits { $( let [] = $x; )* - match store.as_mut().store { + match store.as_store_mut().inner.store { #[cfg(feature = "sys")] BackendStore::Sys(_) => self.call_sys(store, $([]),*), #[cfg(feature = "wamr")] @@ -95,7 +95,7 @@ macro_rules! impl_native_traits { )* async move { let read_lock = store.read_lock().await; - match read_lock.as_ref().store { + match read_lock.as_store_ref().inner.store { #[cfg(feature = "sys")] BackendStore::Sys(_) => { drop(read_lock); @@ -120,7 +120,7 @@ macro_rules! impl_native_traits { #[allow(unused_mut)] #[allow(clippy::too_many_arguments)] pub fn call_raw(&self, store: &mut impl AsStoreMut, mut params_list: Vec ) -> Result { - match store.as_mut().store { + match store.as_store_mut().inner.store { #[cfg(feature = "sys")] BackendStore::Sys(_) => self.call_raw_sys(store, params_list), #[cfg(feature = "wamr")] From c74678f61a22abcede1558fe062d143c2424e33c Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Thu, 27 Nov 2025 03:10:50 +0400 Subject: [PATCH 27/49] WIP: update API tests --- .../src/backend/sys/entities/function/env.rs | 6 +- .../src/backend/sys/entities/function/mod.rs | 4 +- .../backend/sys/entities/function/typed.rs | 4 +- lib/api/src/backend/sys/tunables.rs | 8 +- lib/api/src/entities/function/env/inner.rs | 8 +- lib/api/src/entities/function/env/mod.rs | 8 +- lib/api/src/entities/function/inner.rs | 4 +- lib/api/src/entities/function/mod.rs | 4 +- lib/api/src/entities/imports.rs | 15 +- lib/api/src/entities/store/async_.rs | 15 +- lib/api/src/entities/store/mod.rs | 2 +- lib/api/src/entities/store/store_ref.rs | 2 +- lib/api/src/entities/table/mod.rs | 4 +- lib/api/src/utils/native/typed_func.rs | 4 +- lib/api/tests/externals.rs | 12 - lib/api/tests/function_env.rs | 1 - lib/api/tests/import_function.rs | 13 +- lib/api/tests/instance.rs | 4 +- lib/api/tests/jspi_async.rs | 251 +++++++++--------- lib/api/tests/memory.rs | 8 +- lib/api/tests/module.rs | 14 +- lib/api/tests/reference_types.rs | 46 +--- ...e-greenthread.rs => simple_greenthread.rs} | 38 +-- lib/api/tests/typed_functions.rs | 9 +- 24 files changed, 207 insertions(+), 277 deletions(-) rename lib/api/tests/{simple-greenthread.rs => simple_greenthread.rs} (88%) diff --git a/lib/api/src/backend/sys/entities/function/env.rs b/lib/api/src/backend/sys/entities/function/env.rs index 691a0af15aa..fde3c869e11 100644 --- a/lib/api/src/backend/sys/entities/function/env.rs +++ b/lib/api/src/backend/sys/entities/function/env.rs @@ -1,7 +1,7 @@ use std::{any::Any, fmt::Debug, marker::PhantomData}; use crate::{ - AsAsyncStore, AsyncStoreReadLock, AsyncStoreWriteLock, Store, StoreAsync, StoreContext, + AsStoreAsync, AsyncStoreReadLock, AsyncStoreWriteLock, Store, StoreAsync, StoreContext, StoreMut, store::{AsStoreMut, AsStoreRef, StoreRef}, }; @@ -282,8 +282,8 @@ impl AsyncFunctionEnvMut { } } - /// Creates an [`AsAsyncStore`] from this [`AsyncFunctionEnvMut`]. - pub fn as_async_store(&mut self) -> impl AsAsyncStore + 'static { + /// Creates an [`AsStoreAsync`] from this [`AsyncFunctionEnvMut`]. + pub fn as_store_async(&self) -> impl AsStoreAsync + 'static { StoreAsync { id: self.store.id, inner: self.store.inner.clone(), diff --git a/lib/api/src/backend/sys/entities/function/mod.rs b/lib/api/src/backend/sys/entities/function/mod.rs index 67ed3f9354f..dd2ccc39e0f 100644 --- a/lib/api/src/backend/sys/entities/function/mod.rs +++ b/lib/api/src/backend/sys/entities/function/mod.rs @@ -4,7 +4,7 @@ pub(crate) mod env; pub(crate) mod typed; use crate::{ - AsAsyncStore, AsyncFunctionEnvMut, BackendAsyncFunctionEnvMut, BackendFunction, FunctionEnv, + AsStoreAsync, AsyncFunctionEnvMut, BackendAsyncFunctionEnvMut, BackendFunction, FunctionEnv, FunctionEnvMut, FunctionType, HostFunction, RuntimeError, StoreContext, StoreInner, Value, WithEnv, WithoutEnv, backend::sys::{engine::NativeEngineExt, vm::VMFunctionCallback}, @@ -579,7 +579,7 @@ impl Function { pub(crate) fn call_async<'a>( &self, - store: &'a impl AsAsyncStore, + store: &'a impl AsStoreAsync, params: Vec, ) -> Pin, RuntimeError>> + 'a>> { let function = self.clone(); diff --git a/lib/api/src/backend/sys/entities/function/typed.rs b/lib/api/src/backend/sys/entities/function/typed.rs index abebc3ee9c4..c48765d7d59 100644 --- a/lib/api/src/backend/sys/entities/function/typed.rs +++ b/lib/api/src/backend/sys/entities/function/typed.rs @@ -1,5 +1,5 @@ use crate::backend::sys::engine::NativeEngineExt; -use crate::store::{AsAsyncStore, AsStoreMut, AsStoreRef}; +use crate::store::{AsStoreAsync, AsStoreMut, AsStoreRef}; use crate::{ FromToNativeWasmType, NativeWasmTypeInto, RuntimeError, StoreContext, TypedFunction, Value, WasmTypeList, @@ -116,7 +116,7 @@ macro_rules! impl_native_traits { #[allow(clippy::too_many_arguments)] pub fn call_async_sys<'a>( &'a self, - store: &'a impl AsAsyncStore, + store: &'a impl AsStoreAsync, $( $x: $x, )* ) -> impl Future> + 'a where diff --git a/lib/api/src/backend/sys/tunables.rs b/lib/api/src/backend/sys/tunables.rs index 3b2cf8e8bb3..232de0c60ad 100644 --- a/lib/api/src/backend/sys/tunables.rs +++ b/lib/api/src/backend/sys/tunables.rs @@ -285,9 +285,7 @@ mod tests { engine.set_tunables(tunables); let mut store = Store::new(engine); //let mut store = Store::new(compiler); - let module = Module::new(&store.engine(), wasm_bytes)?; - - let mut store = store.as_mut(); + let module = Module::new(&store, wasm_bytes)?; let import_object = imports! {}; let instance = Instance::new(&mut store, &module, &import_object)?; @@ -466,9 +464,7 @@ mod tests { let mut engine = Engine::new(compiler.into(), Default::default(), Default::default()); engine.set_tunables(tunables); let mut store = Store::new(engine); - let module = Module::new(&store.engine(), wasm_bytes)?; - - let mut store = store.as_mut(); + let module = Module::new(&store, wasm_bytes)?; let import_object = imports! {}; let instance = Instance::new(&mut store, &module, &import_object)?; diff --git a/lib/api/src/entities/function/env/inner.rs b/lib/api/src/entities/function/env/inner.rs index 2e5941fbdfb..cc1ae292589 100644 --- a/lib/api/src/entities/function/env/inner.rs +++ b/lib/api/src/entities/function/env/inner.rs @@ -1,5 +1,5 @@ use crate::{ - AsAsyncStore, AsStoreMut, AsStoreRef, FunctionEnv, FunctionEnvMut, StoreMut, StoreRef, + AsStoreAsync, AsStoreMut, AsStoreRef, FunctionEnv, FunctionEnvMut, StoreMut, StoreRef, macros::backend::match_rt, }; use std::{any::Any, marker::PhantomData}; @@ -307,11 +307,11 @@ impl BackendAsyncFunctionEnvMut { } } - /// Creates an [`AsAsyncStore`] from this [`BackendAsyncFunctionEnvMut`]. - pub fn as_async_store(&mut self) -> impl AsAsyncStore + 'static { + /// Creates an [`AsStoreAsync`] from this [`BackendAsyncFunctionEnvMut`]. + pub fn as_store_async(&self) -> impl AsStoreAsync + 'static { match self { #[cfg(feature = "sys")] - Self::Sys(f) => f.as_async_store(), + Self::Sys(f) => f.as_store_async(), _ => unsupported_async_backend(), } } diff --git a/lib/api/src/entities/function/env/mod.rs b/lib/api/src/entities/function/env/mod.rs index 0998189ca32..e06460b2a65 100644 --- a/lib/api/src/entities/function/env/mod.rs +++ b/lib/api/src/entities/function/env/mod.rs @@ -1,7 +1,7 @@ pub(crate) mod inner; pub(crate) use inner::*; -use crate::{AsAsyncStore, AsStoreMut, AsStoreRef, StoreMut, StoreRef, macros::backend::match_rt}; +use crate::{AsStoreAsync, AsStoreMut, AsStoreRef, StoreMut, StoreRef, macros::backend::match_rt}; use std::{any::Any, fmt::Debug, marker::PhantomData}; #[derive(Debug, derive_more::From)] @@ -146,9 +146,9 @@ impl AsyncFunctionEnvMut { AsyncFunctionEnvMut(self.0.as_mut()) } - /// Creates an [`AsAsyncStore`] from this [`AsyncFunctionEnvMut`]. - pub fn as_async_store(&mut self) -> impl AsAsyncStore + 'static { - self.0.as_async_store() + /// Creates an [`AsStoreAsync`] from this [`AsyncFunctionEnvMut`]. + pub fn as_store_async(&self) -> impl AsStoreAsync + 'static { + self.0.as_store_async() } } diff --git a/lib/api/src/entities/function/inner.rs b/lib/api/src/entities/function/inner.rs index 2f7ddee55d3..5d325786750 100644 --- a/lib/api/src/entities/function/inner.rs +++ b/lib/api/src/entities/function/inner.rs @@ -3,7 +3,7 @@ use std::pin::Pin; use wasmer_types::{FunctionType, RawValue}; use crate::{ - AsAsyncStore, AsStoreMut, AsStoreRef, AsyncFunctionEnvMut, ExportError, Exportable, Extern, + AsStoreAsync, AsStoreMut, AsStoreRef, AsyncFunctionEnvMut, ExportError, Exportable, Extern, FunctionEnv, FunctionEnvMut, HostFunction, StoreMut, StoreRef, TypedFunction, Value, WasmTypeList, WithEnv, WithoutEnv, entities::function::async_host::AsyncHostFunction, @@ -490,7 +490,7 @@ impl BackendFunction { pub fn call_async<'a>( &'a self, - store: &'a impl AsAsyncStore, + store: &'a impl AsStoreAsync, params: Vec, ) -> Pin, RuntimeError>> + 'a>> { match self { diff --git a/lib/api/src/entities/function/mod.rs b/lib/api/src/entities/function/mod.rs index fe480996247..198b36374d1 100644 --- a/lib/api/src/entities/function/mod.rs +++ b/lib/api/src/entities/function/mod.rs @@ -18,7 +18,7 @@ use std::{future::Future, pin::Pin}; use wasmer_types::{FunctionType, RawValue}; use crate::{ - AsAsyncStore, AsStoreMut, AsStoreRef, ExportError, Exportable, Extern, StoreMut, StoreRef, + AsStoreAsync, AsStoreMut, AsStoreRef, ExportError, Exportable, Extern, StoreMut, StoreRef, TypedFunction, Value, WasmTypeList, error::RuntimeError, vm::{VMExtern, VMExternFunction, VMFuncRef}, @@ -331,7 +331,7 @@ impl Function { /// the Wasm instance according to the JSPI proposal. pub fn call_async<'a>( &'a self, - store: &'a impl AsAsyncStore, + store: &'a impl AsStoreAsync, params: &'a [Value], ) -> impl Future, RuntimeError>> + 'a { let params_vec = params.to_vec(); diff --git a/lib/api/src/entities/imports.rs b/lib/api/src/entities/imports.rs index 500d089260d..2efb2f2606e 100644 --- a/lib/api/src/entities/imports.rs +++ b/lib/api/src/entities/imports.rs @@ -15,17 +15,17 @@ use wasmer_types::ImportError; /// /// # Usage: /// ```no_run -/// use wasmer::{AsStoreMut, Exports, Module, Instance, imports, Imports, Function, FunctionEnvMut}; -/// # fn foo_test(store: &mut impl AsStoreMut, module: Module) { +/// use wasmer::{Store, Exports, Module, Instance, imports, Imports, Function, FunctionEnvMut}; +/// # fn foo_test(mut store: &mut Store, module: Module) { /// -/// let host_fn = Function::new_typed(store, foo); +/// let host_fn = Function::new_typed(&mut store, foo); /// let import_object: Imports = imports! { /// "env" => { /// "foo" => host_fn, /// }, /// }; /// -/// let instance = Instance::new(store, &module, &import_object).expect("Could not instantiate module."); +/// let instance = Instance::new(&mut store, &module, &import_object).expect("Could not instantiate module."); /// /// fn foo(n: i32) -> i32 { /// n @@ -108,7 +108,6 @@ impl Imports { /// ```no_run /// # use wasmer::{FunctionEnv, Store}; /// # let mut store: Store = Default::default(); - /// # let mut store = store.as_mut(); /// use wasmer::{StoreMut, Imports, Function, FunctionEnvMut}; /// fn foo(n: i32) -> i32 { /// n @@ -245,7 +244,6 @@ impl fmt::Debug for Imports { /// ``` /// # use wasmer::{StoreMut, Function, FunctionEnvMut, Store}; /// # let mut store = Store::default(); -/// # let mut store = store.as_mut(); /// use wasmer::imports; /// /// let import_object = imports! { @@ -313,7 +311,6 @@ mod test { #[test] fn namespace() { let mut store = Store::default(); - let mut store = store.as_mut(); let g1 = Global::new(&mut store, Value::I32(0)); let namespace = namespace! { "happy" => g1 @@ -336,7 +333,6 @@ mod test { use crate::Function; let mut store: Store = Default::default(); - let mut store = store.as_mut(); fn func(arg: i32) -> i32 { arg + 1 @@ -387,7 +383,6 @@ mod test { #[test] fn chaining_works() { let mut store = Store::default(); - let mut store = store.as_mut(); let g = Global::new(&mut store, Value::I32(0)); @@ -420,7 +415,6 @@ mod test { #[test] fn extending_conflict_overwrites() { let mut store = Store::default(); - let mut store = store.as_mut(); let g1 = Global::new(&mut store, Value::I32(0)); let g2 = Global::new(&mut store, Value::I64(0)); @@ -449,7 +443,6 @@ mod test { */ // now test it in reverse let mut store = Store::default(); - let mut store = store.as_mut(); let g1 = Global::new(&mut store, Value::I32(0)); let g2 = Global::new(&mut store, Value::I64(0)); diff --git a/lib/api/src/entities/store/async_.rs b/lib/api/src/entities/store/async_.rs index 362dd15da7e..c46b19c25da 100644 --- a/lib/api/src/entities/store/async_.rs +++ b/lib/api/src/entities/store/async_.rs @@ -37,7 +37,7 @@ impl StoreAsync { /// Note that, while this trait can easily be implemented for a lot /// of types (including [`StoreMut`]), implementations are left /// out on purpose to help avoid common deadlock scenarios. -pub trait AsAsyncStore { +pub trait AsStoreAsync { /// Returns a reference to the inner store. fn store_ref(&self) -> &StoreAsync; @@ -66,7 +66,7 @@ pub trait AsAsyncStore { } } -impl AsAsyncStore for StoreAsync { +impl AsStoreAsync for StoreAsync { fn store_ref(&self) -> &StoreAsync { self } @@ -78,7 +78,7 @@ pub(crate) enum AsyncStoreReadLockInner { } /// A read lock on a store that can be used in concurrent contexts; -/// mostly useful in conjunction with [`AsAsyncStore`]. +/// mostly useful in conjunction with [`AsStoreAsync`]. pub struct AsyncStoreReadLock<'a> { pub(crate) inner: AsyncStoreReadLockInner, _marker: PhantomData<&'a ()>, @@ -120,7 +120,7 @@ pub(crate) enum AsyncStoreWriteLockInner { } /// A write lock on a store that can be used in concurrent contexts; -/// mostly useful in conjunction with [`AsAsyncStore`]. +/// mostly useful in conjunction with [`AsStoreAsync`]. pub struct AsyncStoreWriteLock<'a> { pub(crate) inner: AsyncStoreWriteLockInner, _marker: PhantomData<&'a ()>, @@ -165,6 +165,11 @@ impl AsStoreMut for AsyncStoreWriteLock<'_> { } fn objects_mut(&mut self) -> &mut super::StoreObjects { - todo!() + match &mut self.inner { + AsyncStoreWriteLockInner::Owned(guard) => &mut guard.objects, + AsyncStoreWriteLockInner::FromStoreContext(wrapper) => { + &mut wrapper.as_mut().inner.objects + } + } } } diff --git a/lib/api/src/entities/store/mod.rs b/lib/api/src/entities/store/mod.rs index 72c2221781a..8e19a87e813 100644 --- a/lib/api/src/entities/store/mod.rs +++ b/lib/api/src/entities/store/mod.rs @@ -1,7 +1,7 @@ //! Defines the [`Store`] data type and various useful traits and data types to interact with a //! store. -/// Defines the [`AsAsyncStore`] trait and its supporting types. +/// Defines the [`AsStoreAsync`] trait and its supporting types. mod async_; pub use async_::*; diff --git a/lib/api/src/entities/store/store_ref.rs b/lib/api/src/entities/store/store_ref.rs index efc460faa9a..6ec36ece852 100644 --- a/lib/api/src/entities/store/store_ref.rs +++ b/lib/api/src/entities/store/store_ref.rs @@ -75,7 +75,7 @@ impl StoreMut<'_> { // TODO: OnCalledAction is needed for asyncify. It will be refactored with https://github.com/wasmerio/wasmer/issues/3451 /// Sets the unwind callback which will be invoked when the call finishes - fn on_called(&mut self, callback: F) + pub fn on_called(&mut self, callback: F) where F: FnOnce(StoreMut<'_>) -> Result> + Send diff --git a/lib/api/src/entities/table/mod.rs b/lib/api/src/entities/table/mod.rs index 317cc789677..bcb05aa5e21 100644 --- a/lib/api/src/entities/table/mod.rs +++ b/lib/api/src/entities/table/mod.rs @@ -142,9 +142,7 @@ mod test { // Tests that the table type of `table` is compatible with the export in the WAT // This tests that `wasmer_types::types::is_table_compatible` works as expected. let mut store = Store::default(); - let module = Module::new(&store.engine(), WAT).unwrap(); - - let mut store = store.as_mut(); + let module = Module::new(&store, WAT).unwrap(); let ty = TableType::new(Type::FuncRef, 0, None); let table = Table::new(&mut store, ty, Value::FuncRef(None)).unwrap(); table.grow(&mut store, 100, Value::FuncRef(None)).unwrap(); diff --git a/lib/api/src/utils/native/typed_func.rs b/lib/api/src/utils/native/typed_func.rs index 695d619f925..c49e7d69ec4 100644 --- a/lib/api/src/utils/native/typed_func.rs +++ b/lib/api/src/utils/native/typed_func.rs @@ -8,7 +8,7 @@ //! let add_one_native: TypedFunction = add_one.native().unwrap(); //! ``` use crate::{ - AsAsyncStore, AsStoreMut, BackendStore, FromToNativeWasmType, Function, NativeWasmTypeInto, + AsStoreAsync, AsStoreMut, BackendStore, FromToNativeWasmType, Function, NativeWasmTypeInto, RuntimeError, WasmTypeList, store::AsStoreRef, }; use std::future::Future; @@ -84,7 +84,7 @@ macro_rules! impl_native_traits { #[allow(clippy::too_many_arguments)] pub fn call_async<'a>( &'a self, - store: &'a impl AsAsyncStore, + store: &'a impl AsStoreAsync, $( $x: $x, )* ) -> impl Future> + 'a where diff --git a/lib/api/tests/externals.rs b/lib/api/tests/externals.rs index 328c6ca6e79..f59611a8c81 100644 --- a/lib/api/tests/externals.rs +++ b/lib/api/tests/externals.rs @@ -7,7 +7,6 @@ use wasmer::*; #[universal_test] fn global_new() -> Result<(), String> { let mut store = Store::default(); - let mut store = store.as_mut(); let global = Global::new(&mut store, Value::I32(10)); assert_eq!( global.ty(&store), @@ -36,7 +35,6 @@ fn global_new() -> Result<(), String> { )] fn global_get() -> Result<(), String> { let mut store = Store::default(); - let mut store = store.as_mut(); let global_i32 = Global::new(&mut store, Value::I32(10)); assert_eq!(global_i32.get(&mut store), Value::I32(10)); @@ -68,7 +66,6 @@ fn global_get() -> Result<(), String> { )] fn global_set() -> Result<(), String> { let mut store = Store::default(); - let mut store = store.as_mut(); let global_i32 = Global::new(&mut store, Value::I32(10)); // Set on a constant should error assert!(global_i32.set(&mut store, Value::I32(20)).is_err()); @@ -90,7 +87,6 @@ fn global_set() -> Result<(), String> { #[cfg_attr(feature = "wasmi", ignore = "wasmi does not support funcrefs")] fn table_new() -> Result<(), String> { let mut store = Store::default(); - let mut store = store.as_mut(); let table_type = TableType { ty: Type::FuncRef, minimum: 0, @@ -140,7 +136,6 @@ fn table_set() -> Result<(), String> { #[cfg(feature = "sys")] { let mut store = Store::default(); - let mut store = store.as_mut(); let table_type = TableType { ty: Type::ExternRef, @@ -218,7 +213,6 @@ fn table_grow() -> Result<(), String> { #[cfg(feature = "sys")] { let mut store = Store::default(); - let mut store = store.as_mut(); let table_type = TableType { ty: Type::FuncRef, minimum: 0, @@ -255,7 +249,6 @@ fn table_copy() -> Result<(), String> { #[universal_test] fn memory_new() -> Result<(), String> { let mut store = Store::default(); - let mut store = store.as_mut(); let memory_type = MemoryType { shared: cfg!(feature = "wamr"), minimum: Pages(0), @@ -274,7 +267,6 @@ fn memory_new() -> Result<(), String> { )] fn memory_grow() -> Result<(), String> { let mut store = Store::default(); - let mut store = store.as_mut(); let desc = MemoryType::new(Pages(10), Some(Pages(16)), false); let memory = Memory::new(&mut store, desc).map_err(|e| format!("{e:?}"))?; assert_eq!(memory.view(&store).size(), Pages(10)); @@ -306,7 +298,6 @@ fn memory_grow() -> Result<(), String> { #[universal_test] fn function_new() -> Result<(), String> { let mut store = Store::default(); - let mut store = store.as_mut(); let function = Function::new_typed(&mut store, || {}); assert_eq!(function.ty(&store), FunctionType::new(vec![], vec![])); let function = Function::new_typed(&mut store, |_a: i32| {}); @@ -335,7 +326,6 @@ fn function_new() -> Result<(), String> { #[universal_test] fn function_new_env() -> Result<(), String> { let mut store = Store::default(); - let mut store = store.as_mut(); #[derive(Clone)] struct MyEnv {} @@ -379,7 +369,6 @@ fn function_new_env() -> Result<(), String> { #[universal_test] fn function_new_dynamic() -> Result<(), String> { let mut store = Store::default(); - let mut store = store.as_mut(); // Using &FunctionType signature let function_type = FunctionType::new(vec![], vec![]); @@ -441,7 +430,6 @@ fn function_new_dynamic() -> Result<(), String> { #[universal_test] fn function_new_dynamic_env() -> Result<(), String> { let mut store = Store::default(); - let mut store = store.as_mut(); #[derive(Clone)] struct MyEnv {} let my_env = MyEnv {}; diff --git a/lib/api/tests/function_env.rs b/lib/api/tests/function_env.rs index 4d7afcd21a2..f6305dc0192 100644 --- a/lib/api/tests/function_env.rs +++ b/lib/api/tests/function_env.rs @@ -11,7 +11,6 @@ use wasmer::*; )] fn data_and_store_mut() -> Result<(), String> { let mut store = Store::default(); - let mut store = store.as_mut(); let global_mut = Global::new_mut(&mut store, Value::I32(10)); struct Env { value: i32, diff --git a/lib/api/tests/import_function.rs b/lib/api/tests/import_function.rs index 1edbc4cc696..fb8edec98bd 100644 --- a/lib/api/tests/import_function.rs +++ b/lib/api/tests/import_function.rs @@ -19,9 +19,8 @@ fn pass_i64_between_host_and_plugin() -> Result<(), String> { (i64.add (call $add_one_i64 (i64.add (local.get 0) (i64.const 1))) (i64.const 1)) ) )"#; - let module = Module::new(&store.engine(), wat).map_err(|e| format!("{e:?}"))?; + let module = Module::new(&store, wat).map_err(|e| format!("{e:?}"))?; - let mut store = store.as_mut(); let imports = { imports! { "host" => { @@ -66,9 +65,8 @@ fn pass_u64_between_host_and_plugin() -> Result<(), String> { (i64.add (call $add_one_u64 (i64.add (local.get 0) (i64.const 1))) (i64.const 1)) ) )"#; - let module = Module::new(&store.engine(), wat).map_err(|e| format!("{e:?}"))?; + let module = Module::new(&store, wat).map_err(|e| format!("{e:?}"))?; - let mut store = store.as_mut(); let imports = { imports! { "host" => { @@ -108,8 +106,7 @@ fn calling_function_exports() -> Result<()> { local.get $rhs i32.add) )"#; - let module = Module::new(&store.engine(), wat)?; - let mut store = store.as_mut(); + let module = Module::new(&store, wat)?; let imports = imports! { // "host" => { // "host_func1" => Function::new_typed(&mut store, |p: u64| { @@ -133,7 +130,7 @@ fn back_and_forth_with_imports() -> Result<()> { let mut store = Store::default(); // We can use the WAT syntax as well! let module = Module::new( - &store.engine(), + &store, br#"(module (func $sum (import "env" "sum") (param i32 i32) (result i32)) (func (export "add_one") (param i32) (result i32) @@ -142,8 +139,6 @@ fn back_and_forth_with_imports() -> Result<()> { )"#, )?; - let mut store = store.as_mut(); - fn sum(a: i32, b: i32) -> i32 { println!("Summing: {a}+{b}"); a + b diff --git a/lib/api/tests/instance.rs b/lib/api/tests/instance.rs index 576d0df4551..a0c5e48ed3a 100644 --- a/lib/api/tests/instance.rs +++ b/lib/api/tests/instance.rs @@ -8,7 +8,7 @@ use wasmer::*; fn exports_work_after_multiple_instances_have_been_freed() -> Result<(), String> { let mut store = Store::default(); let module = Module::new( - &store.engine(), + &store, " (module (type $sum_t (func (param i32 i32) (result i32))) @@ -21,7 +21,6 @@ fn exports_work_after_multiple_instances_have_been_freed() -> Result<(), String> ) .map_err(|e| format!("{e:?}"))?; - let mut store = store.as_mut(); let imports = Imports::new(); let instance = Instance::new(&mut store, &module, &imports).map_err(|e| format!("{e:?}"))?; let instance2 = instance.clone(); @@ -56,7 +55,6 @@ fn exports_work_after_multiple_instances_have_been_freed() -> Result<(), String> )] fn unit_native_function_env() -> Result<(), String> { let mut store = Store::default(); - let mut store = store.as_mut(); #[derive(Clone, Debug)] struct Env { diff --git a/lib/api/tests/jspi_async.rs b/lib/api/tests/jspi_async.rs index 1b67d56bacc..ca987d6ab6d 100644 --- a/lib/api/tests/jspi_async.rs +++ b/lib/api/tests/jspi_async.rs @@ -8,8 +8,8 @@ use std::{ use anyhow::Result; use futures::{FutureExt, future}; use wasmer::{ - AsAsyncStore, AsyncFunctionEnvMut, Function, FunctionEnv, FunctionType, Instance, Module, - RuntimeError, Store, Type, TypedFunction, Value, imports, + AsyncFunctionEnvMut, Function, FunctionEnv, FunctionType, Instance, Module, RuntimeError, + Store, StoreAsync, Type, TypedFunction, Value, imports, }; use wasmer_vm::TrapCode; @@ -37,11 +37,10 @@ fn jspi_module() -> &'static [u8] { fn async_state_updates_follow_jspi_example() -> Result<()> { let wasm = jspi_module(); let mut store = Store::default(); - let module = Module::new(&store.engine(), wasm)?; + let module = Module::new(&store, wasm)?; - let mut store_mut = store.as_mut(); let init_state = Function::new_async( - &mut store_mut, + &mut store, FunctionType::new(vec![], vec![Type::F64]), |_values| async move { // Note: future::ready doesn't actually suspend. It's important @@ -56,23 +55,21 @@ fn async_state_updates_follow_jspi_example() -> Result<()> { ); let delta_env = FunctionEnv::new( - &mut store_mut, + &mut store, DeltaState { deltas: vec![0.5, -1.0, 2.5], index: 0, }, ); let compute_delta = Function::new_with_env_async( - &mut store_mut, + &mut store, &delta_env, FunctionType::new(vec![], vec![Type::F64]), |env: AsyncFunctionEnvMut, _values| async move { + let mut env_write = env.write().await; + let delta = env_write.data_mut().next(); // We can, however, actually suspend whenever // `Function::call_async` is used to call WASM functions. - let delta = { - let mut env_write = env.write().await; - env_write.data_mut().next() - }; tokio::time::sleep(std::time::Duration::from_millis(10)).await; Ok(vec![Value::F64(delta)]) }, @@ -85,7 +82,7 @@ fn async_state_updates_follow_jspi_example() -> Result<()> { } }; - let instance = Instance::new(&mut store_mut, &module, &import_object)?; + let instance = Instance::new(&mut store, &module, &import_object)?; let get_state = instance.exports.get_function("get_state")?; let update_state = instance.exports.get_function("update_state")?; @@ -96,22 +93,22 @@ fn async_state_updates_follow_jspi_example() -> Result<()> { } } - assert_eq!(as_f64(&get_state.call(&mut store_mut, &[])?), 1.0); + assert_eq!(as_f64(&get_state.call(&mut store, &[])?), 1.0); - fn step(store: &mut impl AsAsyncStore, func: &wasmer::Function) -> Result { + let step = |store: &StoreAsync, func: &wasmer::Function| -> Result { let result = tokio::runtime::Builder::new_current_thread() .enable_all() .build() .unwrap() .block_on(func.call_async(store, &[]))?; Ok(as_f64(&result)) - } + }; - drop(store_mut); + let store_async = store.into_async(); - assert_eq!(step(&mut store, update_state)?, 1.5); - assert_eq!(step(&mut store, update_state)?, 0.5); - assert_eq!(step(&mut store, update_state)?, 3.0); + assert_eq!(step(&store_async, update_state)?, 1.5); + assert_eq!(step(&store_async, update_state)?, 0.5); + assert_eq!(step(&store_async, update_state)?, 3.0); Ok(()) } @@ -139,24 +136,21 @@ fn typed_async_host_and_calls_work() -> Result<()> { } let mut store = Store::default(); - let module = Module::new(&store.engine(), wasm)?; + let module = Module::new(&store, wasm)?; - let mut store_mut = store.as_mut(); - let add_env = FunctionEnv::new(&mut store_mut, AddBias { bias: 5 }); + let add_env = FunctionEnv::new(&mut store, AddBias { bias: 5 }); let async_add = Function::new_typed_with_env_async( - &mut store_mut, + &mut store, &add_env, async move |env: AsyncFunctionEnvMut, a: i32, b: i32| { - let bias = { - let read = env.read().await; - read.data().bias - }; - future::ready(()).await; + let env_read = env.read().await; + let bias = env_read.data().bias; + tokio::task::yield_now().await; a + b + bias }, ); - let async_double = Function::new_typed_async(&mut store_mut, async move |value: i32| { - future::ready(()).await; + let async_double = Function::new_typed_async(&mut store, async move |value: i32| { + tokio::task::yield_now().await; value * 2 }); @@ -167,72 +161,70 @@ fn typed_async_host_and_calls_work() -> Result<()> { } }; - let instance = Instance::new(&mut store_mut, &module, &import_object)?; - let compute: TypedFunction = instance - .exports - .get_typed_function(&mut store_mut, "compute")?; + let instance = Instance::new(&mut store, &module, &import_object)?; + let compute: TypedFunction = + instance.exports.get_typed_function(&mut store, "compute")?; - drop(store_mut); + let store_async = store.into_async(); let result = tokio::runtime::Builder::new_current_thread() .enable_all() .build() .unwrap() - .block_on(compute.call_async(&store, 4))?; + .block_on(compute.call_async(&store_async, 4))?; assert_eq!(result, 27); Ok(()) } -#[test] -fn cannot_yield_when_not_in_async_context() -> Result<()> { - const WAT: &str = r#" - (module - (import "env" "yield_now" (func $yield_now)) - (func (export "yield_outside") - call $yield_now - ) - ) - "#; - let wasm = wat::parse_str(WAT).expect("valid WAT module"); - - let mut store = Store::default(); - let module = Module::new(&store.engine(), wasm)?; - - let mut store_mut = store.as_mut(); - let yield_now = Function::new_async( - &mut store_mut, - FunctionType::new(vec![], vec![]), - |_values| async move { - // Attempting to yield when not in an async context should trap. - tokio::task::yield_now().await; - Ok(vec![]) - }, - ); - - let import_object = imports! { - "env" => { - "yield_now" => yield_now, - } - }; - let instance = Instance::new(&mut store_mut, &module, &import_object)?; - let yield_outside = instance.exports.get_function("yield_outside")?; - - let trap = yield_outside - .call(&mut store_mut, &[]) - .expect_err("expected trap calling yield outside async context"); - - // TODO: wasm trace generation appears to be broken? - // assert!(!trap.trace().is_empty(), "should have a stack trace"); - let trap_code = trap.to_trap().expect("expected trap code"); - assert_eq!( - trap_code, - TrapCode::YieldOutsideAsyncContext, - "expected YieldOutsideAsyncContext trap code" - ); - - Ok(()) -} +// #[test] +// fn cannot_yield_when_not_in_async_context() -> Result<()> { +// const WAT: &str = r#" +// (module +// (import "env" "yield_now" (func $yield_now)) +// (func (export "yield_outside") +// call $yield_now +// ) +// ) +// "#; +// let wasm = wat::parse_str(WAT).expect("valid WAT module"); + +// let mut store = Store::default(); +// let module = Module::new(&store, wasm)?; + +// let yield_now = Function::new_async( +// &mut store, +// FunctionType::new(vec![], vec![]), +// |_values| async move { +// // Attempting to yield when not in an async context should trap. +// tokio::task::yield_now().await; +// Ok(vec![]) +// }, +// ); + +// let import_object = imports! { +// "env" => { +// "yield_now" => yield_now, +// } +// }; +// let instance = Instance::new(&mut store, &module, &import_object)?; +// let yield_outside = instance.exports.get_function("yield_outside")?; + +// let trap = yield_outside +// .call(&mut store, &[]) +// .expect_err("expected trap calling yield outside async context"); + +// // TODO: wasm trace generation appears to be broken? +// // assert!(!trap.trace().is_empty(), "should have a stack trace"); +// let trap_code = trap.to_trap().expect("expected trap code"); +// assert_eq!( +// trap_code, +// TrapCode::YieldOutsideAsyncContext, +// "expected YieldOutsideAsyncContext trap code" +// ); + +// Ok(()) +// } /* This test is slightly weird to explain; what we're testing here is that multiple coroutines can be active at the same time, @@ -329,9 +321,7 @@ fn async_multiple_active_coroutines() -> Result<()> { } let mut store = Store::default(); - let module = Module::new(&store.engine(), wasm)?; - - let mut store_mut = store.as_mut(); + let module = Module::new(&store, wasm)?; thread_local! { static ENV: RefCell = RefCell::new(Env { @@ -341,7 +331,7 @@ fn async_multiple_active_coroutines() -> Result<()> { future_func: None, }) } - let mut env = FunctionEnv::new(&mut store_mut, ()); + let mut env = FunctionEnv::new(&mut store, ()); fn log(value: i32) { ENV.with(|env| { @@ -349,46 +339,48 @@ fn async_multiple_active_coroutines() -> Result<()> { }); } - let spawn_future = Function::new_with_env( - &mut store_mut, + let spawn_future = Function::new_with_env_async( + &mut store, &mut env, FunctionType::new(vec![Type::I32], vec![]), - |mut env, values| { - ENV.with(|data| { - let future_id = values[0].unwrap_i32(); - - log(future_id + 10); - - data.borrow_mut().yielders[future_id as usize] = Some(Yielder { yielded: false }); - - // This spawns the coroutine and the corresponding future - // Note the use of `FunctionEnvMut` as `AsAsyncStore` to - // spawn a new execution context for the coroutine - let func = data.borrow().future_func.as_ref().unwrap().clone(); - let async_store = env.as_async_store(); - let mut future = Box::pin(async move { - func.call_async(&async_store, &[Value::I32(future_id)]) - .await - }); - log(future_id + 20); - - // We then poll it once to get it started - it'll suspend once, then - // complete the next time we poll it - let w = noop_waker(); - let mut cx = Context::from_waker(&w); - assert!(future.as_mut().poll(&mut cx).is_pending()); - - log(future_id + 50); - // We then store the future without letting it complete, and return - data.borrow_mut().futures[future_id as usize] = Some(future); - - Ok(vec![]) - }) + |env, values| { + let future_id = values[0].unwrap_i32(); + + async move { + ENV.with(move |data| { + let store_async = env.as_store_async(); + + log(future_id + 10); + + data.borrow_mut().yielders[future_id as usize] = + Some(Yielder { yielded: false }); + + // This spawns the coroutine and the corresponding future + let func = data.borrow().future_func.as_ref().unwrap().clone(); + let mut future = Box::pin(async move { + func.call_async(&store_async, &[Value::I32(future_id)]) + .await + }); + log(future_id + 20); + + // We then poll it once to get it started - it'll suspend once, then + // complete the next time we poll it + let w = noop_waker(); + let mut cx = Context::from_waker(&w); + assert!(future.as_mut().poll(&mut cx).is_pending()); + + log(future_id + 50); + // We then store the future without letting it complete, and return + data.borrow_mut().futures[future_id as usize] = Some(future); + + Ok(vec![]) + }) + } }, ); let poll_future = Function::new_async( - &mut store_mut, + &mut store, FunctionType::new(vec![Type::I32], vec![]), |values| { let future_id = values[0].unwrap_i32(); @@ -408,7 +400,7 @@ fn async_multiple_active_coroutines() -> Result<()> { ); let resolve_future = Function::new_async( - &mut store_mut, + &mut store, FunctionType::new(vec![Type::I32], vec![]), |values| { let future_id = values[0].unwrap_i32(); @@ -438,7 +430,7 @@ fn async_multiple_active_coroutines() -> Result<()> { ); let yield_now = Function::new_async( - &mut store_mut, + &mut store, FunctionType::new(vec![], vec![]), |_values| async move { tokio::task::yield_now().await; @@ -447,7 +439,7 @@ fn async_multiple_active_coroutines() -> Result<()> { ); let log = Function::new( - &mut store_mut, + &mut store, FunctionType::new(vec![Type::I32], vec![]), |values| { let value = values[0].unwrap_i32(); @@ -465,7 +457,7 @@ fn async_multiple_active_coroutines() -> Result<()> { "log" => log, } }; - let instance = Instance::new(&mut store_mut, &module, &import_object)?; + let instance = Instance::new(&mut store, &module, &import_object)?; ENV.with(|env| { env.borrow_mut().future_func = Some( @@ -477,14 +469,13 @@ fn async_multiple_active_coroutines() -> Result<()> { ) }); - drop(store_mut); - let main = instance.exports.get_function("main")?; + let store_async = store.into_async(); tokio::runtime::Builder::new_current_thread() .enable_all() .build() .unwrap() - .block_on(main.call_async(&store, &[]))?; + .block_on(main.call_async(&store_async, &[]))?; ENV.with(|env| { assert_eq!( diff --git a/lib/api/tests/memory.rs b/lib/api/tests/memory.rs index 2a0a60dde9c..4119e75d3ee 100644 --- a/lib/api/tests/memory.rs +++ b/lib/api/tests/memory.rs @@ -16,11 +16,10 @@ fn test_shared_memory_atomics_notify_send() { let wat = r#"(module (import "host" "memory" (memory 10 65536 shared)) )"#; - let module = Module::new(&store.engine(), wat) + let module = Module::new(&store, wat) .map_err(|e| format!("{e:?}")) .unwrap(); - let mut store = store.as_mut(); let mem = Memory::new(&mut store, MemoryType::new(10, Some(65536), true)).unwrap(); let imports = imports! { @@ -76,7 +75,6 @@ fn test_shared_memory_disable_atomics() { use wasmer::AtomicsError; let mut store = Store::default(); - let mut store = store.as_mut(); let mem = Memory::new(&mut store, MemoryType::new(10, Some(65536), true)).unwrap(); let mem = mem.as_shared(&store).unwrap(); @@ -94,11 +92,10 @@ fn test_wasm_slice_issue_5444() { let wat = r#"(module (import "host" "memory" (memory 10 65536)) )"#; - let module = Module::new(&store.engine(), wat) + let module = Module::new(&store, wat) .map_err(|e| format!("{e:?}")) .unwrap(); - let mut store = store.as_mut(); let mem = Memory::new(&mut store, MemoryType::new(10, Some(65536), false)).unwrap(); let imports = imports! { @@ -126,7 +123,6 @@ fn test_wasm_slice_issue_5444() { #[test] fn test_wasm_memory_size() { let mut store = Store::default(); - let mut store = store.as_mut(); // Test once with not-shared memory... { diff --git a/lib/api/tests/module.rs b/lib/api/tests/module.rs index a0587574c8a..83296c136e4 100644 --- a/lib/api/tests/module.rs +++ b/lib/api/tests/module.rs @@ -8,7 +8,7 @@ use wasmer::*; fn module_get_name() -> Result<(), String> { let store = Store::default(); let wat = r#"(module)"#; - let module = Module::new(&store.engine(), wat).map_err(|e| format!("{e:?}"))?; + let module = Module::new(&store, wat).map_err(|e| format!("{e:?}"))?; assert_eq!(module.name(), None); Ok(()) @@ -18,7 +18,7 @@ fn module_get_name() -> Result<(), String> { fn module_set_name() -> Result<(), String> { let store = Store::default(); let wat = r#"(module $name)"#; - let mut module = Module::new(&store.engine(), wat).map_err(|e| format!("{e:?}"))?; + let mut module = Module::new(&store, wat).map_err(|e| format!("{e:?}"))?; assert_eq!(module.name(), Some("name")); module.set_name("new_name"); @@ -36,7 +36,7 @@ fn imports() -> Result<(), String> { (import "host" "table" (table 1 funcref)) (import "host" "global" (global i32)) )"#; - let module = Module::new(&store.engine(), wat).map_err(|e| format!("{e:?}"))?; + let module = Module::new(&store, wat).map_err(|e| format!("{e:?}"))?; assert_eq!( module.imports().collect::>(), vec![ @@ -108,7 +108,7 @@ fn exports() -> Result<(), String> { (table (export "table") 1 funcref) (global (export "global") i32 (i32.const 0)) )"#; - let module = Module::new(&store.engine(), wat).map_err(|e| format!("{e:?}"))?; + let module = Module::new(&store, wat).map_err(|e| format!("{e:?}"))?; assert_eq!( module.exports().collect::>(), vec![ @@ -190,8 +190,7 @@ fn calling_host_functions_with_negative_values_works() -> Result<(), String> { (func (export "call_host_func8") (call 7 (i32.const -1))) )"#; - let module = Module::new(&store.engine(), wat).map_err(|e| format!("{e:?}"))?; - let mut store = store.as_mut(); + let module = Module::new(&store, wat).map_err(|e| format!("{e:?}"))?; let imports = imports! { "host" => { "host_func1" => Function::new_typed(&mut store, |p: u64| { @@ -282,8 +281,7 @@ fn calling_host_functions_with_negative_values_works() -> Result<(), String> { fn module_custom_sections() -> Result<(), String> { let store = Store::default(); let custom_section_wasm_bytes = include_bytes!("simple-name-section.wasm"); - let module = - Module::new(&store.engine(), custom_section_wasm_bytes).map_err(|e| format!("{e:?}"))?; + let module = Module::new(&store, custom_section_wasm_bytes).map_err(|e| format!("{e:?}"))?; let sections = module.custom_sections("name"); let sections_vec: Vec> = sections.collect(); assert_eq!(sections_vec.len(), 1); diff --git a/lib/api/tests/reference_types.rs b/lib/api/tests/reference_types.rs index b42d37ad15e..3350873bfe4 100644 --- a/lib/api/tests/reference_types.rs +++ b/lib/api/tests/reference_types.rs @@ -23,9 +23,7 @@ pub mod reference_types { (table.set $table (i32.const 0) (local.get $fr)) (call_indirect $table (type $ret_i32_ty) (i32.const 0))) )"#; - let module = Module::new(&store.engine(), wat)?; - - let mut store = store.as_mut(); + let module = Module::new(&store, wat)?; #[derive(Clone, Debug)] pub struct Env(Arc); let env = Env(Arc::new(AtomicBool::new(false))); @@ -56,7 +54,7 @@ pub mod reference_types { let call_set_value: &Function = instance.exports.get_function("call_set_value")?; let results: Box<[Value]> = call_set_value.call(&mut store, &[Value::FuncRef(Some(func_to_call))])?; - assert!(env.as_ref(&store).0.load(Ordering::SeqCst)); + assert!(env.as_ref(&store.as_store_ref()).0.load(Ordering::SeqCst)); assert_eq!(&*results, &[Value::I32(343)]); Ok(()) @@ -82,9 +80,7 @@ pub mod reference_types { (func (export "call_host_func_with_wasm_func") (result i32) (call $func_ref_call (ref.func $product))) )"#; - let module = Module::new(&store.engine(), wat)?; - - let mut store = store.as_mut(); + let module = Module::new(&store, wat)?; let env = FunctionEnv::new(&mut store, ()); fn func_ref_call( mut env: FunctionEnvMut<()>, @@ -153,9 +149,7 @@ pub mod reference_types { (func (export "get_hashmap_native") (param) (result externref) (call $get_new_extern_ref_native)) )"#; - let module = Module::new(&store.engine(), wat)?; - - let mut store = store.as_mut(); + let module = Module::new(&store, wat)?; let env = FunctionEnv::new(&mut store, ()); let imports = imports! { "env" => { @@ -234,9 +228,7 @@ pub mod reference_types { (func (export "drop") (param $er externref) (result) (drop (local.get $er))) )"#; - let module = Module::new(&store.engine(), wat)?; - - let mut store = store.as_mut(); + let module = Module::new(&store, wat)?; let instance = Instance::new(&mut store, &module, &imports! {})?; let f: TypedFunction, ()> = instance.exports.get_typed_function(&store, "drop")?; @@ -260,9 +252,7 @@ pub mod reference_types { (func $hello (param) (result i32) (i32.const 73)) )"#; - let module = Module::new(&store.engine(), wat)?; - - let mut store = store.as_mut(); + let module = Module::new(&store, wat)?; let instance = Instance::new(&mut store, &module, &imports! {})?; { let er_global: &Global = instance.exports.get_global("er_global")?; @@ -331,9 +321,7 @@ pub mod reference_types { (call $intermediate (local.get $er) (local.get $idx)) (local.get $er)) )"#; - let module = Module::new(&store.engine(), wat)?; - - let mut store = store.as_mut(); + let module = Module::new(&store, wat)?; let instance = Instance::new(&mut store, &module, &imports! {})?; let f: TypedFunction<(Option, i32), Option> = instance @@ -370,9 +358,7 @@ pub mod reference_types { (drop (global.get $global)) (global.get $global)) )"#; - let module = Module::new(&store.engine(), wat)?; - - let mut store = store.as_mut(); + let module = Module::new(&store, wat)?; let instance = Instance::new(&mut store, &module, &imports! {})?; let global: &Global = instance.exports.get_global("global")?; @@ -400,9 +386,7 @@ pub mod reference_types { (local.get 0) (unreachable)) )"#; - let module = Module::new(&store.engine(), wat)?; - - let mut store = store.as_mut(); + let module = Module::new(&store, wat)?; let instance = Instance::new(&mut store, &module, &imports! {})?; let pass_extern_ref: TypedFunction, ()> = instance @@ -430,9 +414,7 @@ pub mod reference_types { (func $copy_into_table2 (export "copy_into_table2") (table.copy $table2 $table1 (i32.const 0) (i32.const 0) (i32.const 4))) )"#; - let module = Module::new(&store.engine(), wat)?; - - let mut store = store.as_mut(); + let module = Module::new(&store, wat)?; let instance = Instance::new(&mut store, &module, &imports! {})?; let grow_table_with_ref: TypedFunction<(Option, i32), i32> = instance @@ -521,9 +503,7 @@ pub mod reference_types { (func (export "call_set_value") (param $er externref) (param $idx i32) (table.set $table2 (local.get $idx) (local.get $er))) )"#; - let module = Module::new(&store.engine(), wat)?; - - let mut store = store.as_mut(); + let module = Module::new(&store, wat)?; let instance = Instance::new(&mut store, &module, &imports! {})?; let grow_table_with_ref: TypedFunction<(Option, i32), i32> = instance @@ -613,9 +593,7 @@ pub mod reference_types { (table.get $table (local.get $idx))) )"#; - let module = Module::new(&store.engine(), wat)?; - - let mut store = store.as_mut(); + let module = Module::new(&store, wat)?; let instance = Instance::new(&mut store, &module, &imports! {})?; let call_set_value: TypedFunction<(Option, i32), ()> = instance .exports diff --git a/lib/api/tests/simple-greenthread.rs b/lib/api/tests/simple_greenthread.rs similarity index 88% rename from lib/api/tests/simple-greenthread.rs rename to lib/api/tests/simple_greenthread.rs index 0bb735eb7b8..c471f72997e 100644 --- a/lib/api/tests/simple-greenthread.rs +++ b/lib/api/tests/simple_greenthread.rs @@ -57,12 +57,13 @@ impl Clone for Greenthread { } } -fn greenthread_new( - mut env: FunctionEnvMut, +async fn greenthread_new( + env: AsyncFunctionEnvMut, entrypoint_data: u32, ) -> core::result::Result { - let async_store = env.as_async_store(); - let data = env.data_mut(); + let async_store = env.as_store_async(); + let mut env_write = env.write().await; + let data = env_write.data_mut(); let new_greenthread_id = data .next_free_id .fetch_add(1, std::sync::atomic::Ordering::SeqCst); @@ -80,7 +81,7 @@ fn greenthread_new( .unwrap() .insert(new_greenthread_id, new_greenthread); - let spawner = env.data().spawner.as_ref().expect("spawner set").clone(); + let spawner = data.spawner.as_ref().expect("spawner set").clone(); spawner .spawn_local(async move { receiver.await.unwrap(); @@ -143,12 +144,11 @@ fn run_greenthread_test(wat: &[u8]) -> Result> { let mut store = Store::default(); let module = Module::new(&store.engine(), wat)?; - let mut store_mut = store.as_mut(); - let env = FunctionEnv::new(&mut store_mut, GreenEnv::new()); + let env = FunctionEnv::new(&mut store, GreenEnv::new()); // log(ptr, len) let log_fn = Function::new_with_env( - &mut store_mut, + &mut store, &env, FunctionType::new(vec![Type::I32, Type::I32], vec![]), |mut env: FunctionEnvMut, params: &[Value]| { @@ -167,10 +167,10 @@ fn run_greenthread_test(wat: &[u8]) -> Result> { }, ); - let greenthread_new = Function::new_typed_with_env(&mut store_mut, &env, greenthread_new); + let greenthread_new = Function::new_typed_with_env_async(&mut store, &env, greenthread_new); let greenthread_switch = - Function::new_typed_with_env_async(&mut store_mut, &env, greenthread_switch); + Function::new_typed_with_env_async(&mut store, &env, greenthread_switch); let import_object = imports! { "test" => { @@ -180,13 +180,13 @@ fn run_greenthread_test(wat: &[u8]) -> Result> { } }; - let instance = Instance::new(&mut store_mut, &module, &import_object)?; + let instance = Instance::new(&mut store, &module, &import_object)?; let entrypoint = instance.exports.get_function("entrypoint")?.clone(); - env.as_mut(&mut store_mut).entrypoint = Some(entrypoint); + env.as_mut(&mut store).entrypoint = Some(entrypoint); let memory = instance.exports.get_memory("memory")?.clone(); - env.as_mut(&mut store_mut).memory = Some(memory); + env.as_mut(&mut store).memory = Some(memory); let main_fn = instance.exports.get_function("_main")?; @@ -195,7 +195,7 @@ fn run_greenthread_test(wat: &[u8]) -> Result> { resumer: None, }; - env.as_mut(&mut store_mut) + env.as_mut(&mut store) .greenthreads .write() .unwrap() @@ -203,16 +203,16 @@ fn run_greenthread_test(wat: &[u8]) -> Result> { let mut localpool = futures::executor::LocalPool::new(); let local_spawner = localpool.spawner(); - env.as_mut(&mut store_mut).spawner = Some(local_spawner); + env.as_mut(&mut store).spawner = Some(local_spawner); - drop(store_mut); + let store_async = store.into_async(); localpool - .run_until(main_fn.call_async(&store, &[])) + .run_until(main_fn.call_async(&store_async, &[])) .unwrap(); - let mut store_mut = store.as_mut(); - return Ok(env.as_ref(&mut store_mut).logs.clone()); + let mut store = store_async.into_store().ok().unwrap(); + return Ok(env.as_ref(&mut store).logs.clone()); } #[cfg(not(target_arch = "wasm32"))] diff --git a/lib/api/tests/typed_functions.rs b/lib/api/tests/typed_functions.rs index 42fd8c25827..d746a1a95f2 100644 --- a/lib/api/tests/typed_functions.rs +++ b/lib/api/tests/typed_functions.rs @@ -11,7 +11,6 @@ use wasm_bindgen_test::wasm_bindgen_test; )] fn typed_host_function_closure_panics() -> Result<(), String> { let mut store = Store::default(); - let mut store = store.as_mut(); let state = 3; Function::new_typed(&mut store, move |_: i32| { @@ -28,7 +27,6 @@ fn typed_host_function_closure_panics() -> Result<(), String> { )] fn typed_with_env_host_function_closure_panics() -> Result<(), String> { let mut store = Store::default(); - let mut store = store.as_mut(); let env: i32 = 4; let env = FunctionEnv::new(&mut store, env); let state = 3; @@ -67,8 +65,7 @@ fn non_typed_functions_and_closures_with_no_env_work() -> anyhow::Result<()> { (local.get 3)) (local.get 4))) )"#; - let module = Module::new(&store.engine(), wat).unwrap(); - let mut store = store.as_mut(); + let module = Module::new(&store, wat).unwrap(); let env: i32 = 10; let env = FunctionEnv::new(&mut store, env); let ty = FunctionType::new(vec![Type::I32, Type::I32], vec![Type::I32]); @@ -137,10 +134,8 @@ fn holochain_typed_function() -> anyhow::Result<()> { )?; let mut store = Store::default(); struct MyEnv {} - let module = Module::new(&store.engine(), wasm_bytes)?; - - let mut store = store.as_mut(); let env = FunctionEnv::new(&mut store, MyEnv {}); + let module = Module::new(&store, wasm_bytes)?; // Define some context data that the host function closure will use static STATIC_CONTEXT_VAL2: i32 = 1234; From ad785b6f56675abd8b04263062e05740f864d663 Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Thu, 27 Nov 2025 16:20:10 +0400 Subject: [PATCH 28/49] Update comments, undo unwanted changes from previous version of the code --- Cargo.lock | 1 - lib/api/Cargo.toml | 2 +- lib/api/src/backend/sys/async_runtime.rs | 59 ++--- .../src/backend/sys/entities/function/env.rs | 15 +- .../src/backend/sys/entities/function/mod.rs | 30 +-- lib/api/src/entities/exports.rs | 6 +- lib/api/src/entities/function/env/inner.rs | 10 +- lib/api/src/entities/function/env/mod.rs | 5 +- lib/api/src/entities/function/inner.rs | 23 ++ lib/api/src/entities/function/mod.rs | 42 +-- lib/api/src/entities/global/mod.rs | 7 - lib/api/src/entities/memory/mod.rs | 4 - lib/api/src/entities/module/inner.rs | 10 +- lib/api/src/entities/module/mod.rs | 14 +- lib/api/src/entities/store/async_.rs | 22 +- lib/api/src/entities/store/context.rs | 43 ++- lib/api/src/entities/store/local_rwlock.rs | 247 +++--------------- lib/api/src/lib.rs | 22 +- lib/api/tests/jspi_async.rs | 16 +- lib/vm/src/trap/traphandlers.rs | 1 - 20 files changed, 182 insertions(+), 397 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 44371f745a0..f6d3b611a7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6962,7 +6962,6 @@ name = "wasmer" version = "6.1.0" dependencies = [ "anyhow", - "async-lock", "bindgen", "bytes", "cfg-if", diff --git a/lib/api/Cargo.toml b/lib/api/Cargo.toml index 7f2c28a816b..1407c9413b0 100644 --- a/lib/api/Cargo.toml +++ b/lib/api/Cargo.toml @@ -51,7 +51,6 @@ loupe = { workspace = true, optional = true, features = [ ] } paste = "1.0.15" derive_more = { workspace = true, features = ["from", "debug"] } -async-lock.workspace = true # Dependencies and Development Dependencies for `sys`. [target.'cfg(not(target_arch = "wasm32"))'.dependencies] @@ -66,6 +65,7 @@ corosensei = { workspace = true, optional = true } wasmer-compiler-singlepass = { path = "../compiler-singlepass", version = "=6.1.0", optional = true } wasmer-compiler-cranelift = { path = "../compiler-cranelift", version = "=6.1.0", optional = true } wasmer-compiler-llvm = { path = "../compiler-llvm", version = "=6.1.0", optional = true } +futures = "0.3" wasm-bindgen = { version = "0.2.74", optional = true } js-sys = { version = "0.3.51", optional = true } diff --git a/lib/api/src/backend/sys/async_runtime.rs b/lib/api/src/backend/sys/async_runtime.rs index e4e9fff3d71..6e065f7b7d2 100644 --- a/lib/api/src/backend/sys/async_runtime.rs +++ b/lib/api/src/backend/sys/async_runtime.rs @@ -13,7 +13,7 @@ use corosensei::{Coroutine, CoroutineResult, Yielder}; use super::entities::function::Function as SysFunction; use crate::{ - AsStoreMut, AsStoreRef, LocalWriteGuardRc, RuntimeError, Store, StoreAsync, StoreContext, + AsStoreMut, AsStoreRef, LocalRwLockWriteGuard, RuntimeError, Store, StoreAsync, StoreContext, StoreInner, StoreMut, StoreRef, Value, }; use wasmer_types::StoreId; @@ -44,9 +44,6 @@ pub(crate) struct AsyncCallFuture<'a> { // Store handle we can use to lock the store down store: StoreAsync, - - // Use Rc> to make sure that the future is !Send and !Sync - _marker: PhantomData>>, } // We can't use any of the existing AsStoreMut types here, since we keep @@ -127,7 +124,6 @@ impl<'a> AsyncCallFuture<'a> { next_resume: Some(AsyncResume::Start), result: None, store, - _marker: PhantomData, } } } @@ -215,7 +211,7 @@ impl StoreContextInstaller { } crate::GetAsyncStoreGuardResult::NotInstalled => { // Otherwise, need to acquire a new StoreMut. - let store_guard = store.inner.write_rc().await; + let store_guard = store.inner.write().await; let install_guard = unsafe { crate::StoreContext::install_async(store_guard) }; StoreContextInstaller::Installed(install_guard) } @@ -238,30 +234,11 @@ where // If there is no async context or we haven't entered it, // we can still directly run a future that doesn't block // inline. - // Note, there can be an async context without an active - // coroutine in the following scenario: - // call_async -> wasm code -> imported function -> - // call (non-async) -> wasm_code -> imported async function run_immediate(future) } - Some(context) => { - let ctx_ref = unsafe { context.as_ref().expect("valid context pointer") }; - - // Leave the coroutine context since we're yielding back to the - // parent stack, and will be inactive until the future is ready. - ctx_ref.leave(); - - // Now we can yield back to the runtime while we wait - let result = ctx_ref - .block_on_future(Box::pin(future)) - .map_err(AsyncRuntimeError::RuntimeError); - - // Once the future is ready, we borrow again and restore the current - // coroutine. - ctx_ref.enter(); - - result - } + Some(context) => unsafe { context.as_ref().expect("valid context pointer") } + .block_on_future(Box::pin(future)) + .map_err(AsyncRuntimeError::RuntimeError), } }) } @@ -313,28 +290,30 @@ impl CoroutineContext { } fn block_on_future(&self, future: HostFuture) -> Result, RuntimeError> { + // Leave the coroutine context since we're yielding back to the + // parent stack, and will be inactive until the future is ready. + self.leave(); + let yielder = unsafe { self.yielder.as_ref().expect("yielder pointer valid") }; - match yielder.suspend(AsyncYield(future)) { + let result = match yielder.suspend(AsyncYield(future)) { AsyncResume::HostFutureReady(result) => result, AsyncResume::Start => unreachable!("coroutine resumed without start"), - } + }; + + // Once the future is ready, we restore the current coroutine + // context. + self.enter(); + + result } } fn run_immediate( future: impl Future, RuntimeError>> + 'static, ) -> Result, AsyncRuntimeError> { - fn noop_raw_waker() -> RawWaker { - fn no_op(_: *const ()) {} - fn clone(_: *const ()) -> RawWaker { - noop_raw_waker() - } - let vtable = &RawWakerVTable::new(clone, no_op, no_op, no_op); - RawWaker::new(ptr::null(), vtable) - } - let mut future = Box::pin(future); - let waker = unsafe { Waker::from_raw(noop_raw_waker()) }; + let waker = futures::task::noop_waker(); + let mut cx = Context::from_waker(&waker); match future.as_mut().poll(&mut cx) { Poll::Ready(result) => result.map_err(AsyncRuntimeError::RuntimeError), diff --git a/lib/api/src/backend/sys/entities/function/env.rs b/lib/api/src/backend/sys/entities/function/env.rs index fde3c869e11..e69d9d64a74 100644 --- a/lib/api/src/backend/sys/entities/function/env.rs +++ b/lib/api/src/backend/sys/entities/function/env.rs @@ -199,7 +199,7 @@ impl From> for crate::FunctionEnv { } } -/// A temporary handle to a [`FunctionEnv`], suitable for use +/// A shared handle to a [`FunctionEnv`], suitable for use /// in async imports. pub struct AsyncFunctionEnvMut { pub(crate) store: StoreAsync, @@ -210,21 +210,12 @@ pub struct AsyncFunctionEnvMut { pub struct AsyncFunctionEnvHandle<'a, T> { read_lock: AsyncStoreReadLock<'a>, pub(crate) func_env: FunctionEnv, - - // This type needs to be !Send - _marker: PhantomData<*const &'a ()>, } /// A mutable handle to the [`FunctionEnv`] in an [`AsyncFunctionEnvMut`]. -/// Internally, a [`StoreMutGuard`] is used, so the store handle from this -/// type can be used to invoke [`Function::call`](crate::Function::call) -/// while outside a store's context. pub struct AsyncFunctionEnvHandleMut<'a, T> { write_lock: AsyncStoreWriteLock<'a>, pub(crate) func_env: FunctionEnv, - - // This type needs to be !Send - _marker: PhantomData<*const ()>, } impl Debug for AsyncFunctionEnvMut @@ -232,7 +223,7 @@ where T: Send + Debug + 'static, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self.store.inner.try_read_rc() { + match self.store.inner.try_read() { Some(read_lock) => self.func_env.as_ref(&read_lock).fmt(f), None => write!(f, "AsyncFunctionEnvMut {{ }}"), } @@ -251,7 +242,6 @@ impl AsyncFunctionEnvMut { AsyncFunctionEnvHandle { read_lock, func_env: self.func_env.clone(), - _marker: PhantomData, } } @@ -262,7 +252,6 @@ impl AsyncFunctionEnvMut { AsyncFunctionEnvHandleMut { write_lock, func_env: self.func_env.clone(), - _marker: PhantomData, } } diff --git a/lib/api/src/backend/sys/entities/function/mod.rs b/lib/api/src/backend/sys/entities/function/mod.rs index dd2ccc39e0f..2fd17f4dc59 100644 --- a/lib/api/src/backend/sys/entities/function/mod.rs +++ b/lib/api/src/backend/sys/entities/function/mod.rs @@ -65,18 +65,18 @@ impl Function { let wrapper = move |values_vec: *mut RawValue| -> HostCallOutcome { unsafe { let mut store_wrapper = unsafe { StoreContext::get_current(store_id) }; - let mut store = store_wrapper.as_mut(); + let mut store_mut = store_wrapper.as_mut(); let mut args = Vec::with_capacity(func_ty.params().len()); for (i, ty) in func_ty.params().iter().enumerate() { args.push(Value::from_raw( - &mut store, + &mut store_mut, *ty, values_vec.add(i).read_unaligned(), )); } let env = env::FunctionEnvMut { - store_mut: store, + store_mut, func_env: func_env.clone(), } .into(); @@ -95,7 +95,7 @@ impl Function { store_id, }, }); - host_data.address = host_data.ctx.func_body_ptr() as *const VMFunctionBody; + host_data.address = host_data.ctx.func_body_ptr(); // We don't yet have the address with the Wasm ABI signature. // The engine linker will replace the address with one pointing to a @@ -181,7 +181,7 @@ impl Function { env::AsyncFunctionEnvMut { store: crate::StoreAsync { id, - inner: crate::LocalWriteGuardRc::lock_handle(store_write_guard), + inner: crate::LocalRwLockWriteGuard::lock_handle(store_write_guard), }, func_env: func_env.clone(), }, @@ -827,17 +827,15 @@ where match result { Ok(InvocationResult::Success(())) => {} Ok(InvocationResult::Exception(exception)) => unsafe { - unsafe { - // Note: can't acquire a proper ref-counted context ref here, since we can switch - // away from the WASM stack at any time. - // Safety: The pointer is only used for the duration of the call to `throw`. - let mut store_wrapper = StoreContext::get_current_transient(this.ctx.store_id); - let mut store = store_wrapper.as_mut().unwrap(); - wasmer_vm::libcalls::throw( - store.objects.as_sys(), - exception.vm_exceptionref().as_sys().to_u32_exnref(), - ) - } + // Note: can't acquire a proper ref-counted context ref here, since we can switch + // away from the WASM stack at any time. + // Safety: The pointer is only used for the duration of the call to `throw`. + let mut store_wrapper = StoreContext::get_current_transient(this.ctx.store_id); + let mut store = store_wrapper.as_mut().unwrap(); + wasmer_vm::libcalls::throw( + store.objects.as_sys(), + exception.vm_exceptionref().as_sys().to_u32_exnref(), + ) }, Ok(InvocationResult::Trap(trap)) => unsafe { raise_user_trap(trap) }, Ok(InvocationResult::YieldOutsideAsyncContext) => unsafe { diff --git a/lib/api/src/entities/exports.rs b/lib/api/src/entities/exports.rs index d20a946f8bb..5bda72891dd 100644 --- a/lib/api/src/entities/exports.rs +++ b/lib/api/src/entities/exports.rs @@ -21,8 +21,7 @@ use thiserror::Error; /// # (module /// # (global $one (export "glob") f32 (f32.const 1))) /// # "#.as_bytes()).unwrap(); -/// # let module = Module::new(&store.engine(), wasm_bytes).unwrap(); -/// # let mut store = store.as_mut(); +/// # let module = Module::new(&store, wasm_bytes).unwrap(); /// # let import_object = imports! {}; /// # let instance = Instance::new(&mut store, &module, &import_object).unwrap(); /// # @@ -36,8 +35,7 @@ use thiserror::Error; /// # use wasmer::{imports, wat2wasm, Function, Instance, Module, Store, Type, Value, ExportError}; /// # let mut store = Store::default(); /// # let wasm_bytes = wat2wasm("(module)".as_bytes()).unwrap(); -/// # let module = Module::new(&store.engine(), wasm_bytes).unwrap(); -/// # let mut store = store.as_mut(); +/// # let module = Module::new(&store, wasm_bytes).unwrap(); /// # let import_object = imports! {}; /// # let instance = Instance::new(&mut store, &module, &import_object).unwrap(); /// # diff --git a/lib/api/src/entities/function/env/inner.rs b/lib/api/src/entities/function/env/inner.rs index cc1ae292589..de62dabb220 100644 --- a/lib/api/src/entities/function/env/inner.rs +++ b/lib/api/src/entities/function/env/inner.rs @@ -85,11 +85,6 @@ impl BackendFunctionEnv { } } - //#[allow(dead_code)] // This function is only used in js - //pub(crate) fn from_handle(handle: StoreHandle) -> Self { - // todo!() - //} - /// Get the data as reference pub fn as_ref<'a>(&self, store: &'a impl AsStoreRef) -> &'a T where @@ -239,7 +234,7 @@ where } } -/// A temporary handle to a [`FunctionEnv`], suitable for use +/// A shared handle to a [`FunctionEnv`], suitable for use /// in async imports. #[derive(derive_more::From)] #[non_exhaustive] @@ -258,9 +253,6 @@ pub enum BackendAsyncFunctionEnvHandle<'a, T> { } /// A mutable handle to the [`FunctionEnv`] in an [`AsyncFunctionEnvMut`]. -/// Internally, a [`StoreMutGuard`] is used, so the store handle from this -/// type can be used to invoke [`Function::call`](crate::Function::call) -/// while outside a store's context. #[non_exhaustive] pub enum BackendAsyncFunctionEnvHandleMut<'a, T> { #[cfg(feature = "sys")] diff --git a/lib/api/src/entities/function/env/mod.rs b/lib/api/src/entities/function/env/mod.rs index e06460b2a65..aef63b6a1e0 100644 --- a/lib/api/src/entities/function/env/mod.rs +++ b/lib/api/src/entities/function/env/mod.rs @@ -110,7 +110,7 @@ where } } -/// A temporary handle to a [`FunctionEnv`], suitable for use +/// A shared handle to a [`FunctionEnv`], suitable for use /// in async imports. pub struct AsyncFunctionEnvMut(pub(crate) BackendAsyncFunctionEnvMut); @@ -118,9 +118,6 @@ pub struct AsyncFunctionEnvMut(pub(crate) BackendAsyncFunctionEnvMut); pub struct AsyncFunctionEnvHandle<'a, T>(pub(crate) BackendAsyncFunctionEnvHandle<'a, T>); /// A mutable handle to the [`FunctionEnv`] in an [`AsyncFunctionEnvMut`]. -/// Internally, a [`StoreMutGuard`] is used, so the store handle from this -/// type can be used to invoke [`Function::call`](crate::Function::call) -/// while outside a store's context. pub struct AsyncFunctionEnvHandleMut<'a, T>(pub(crate) BackendAsyncFunctionEnvHandleMut<'a, T>); impl AsyncFunctionEnvMut { diff --git a/lib/api/src/entities/function/inner.rs b/lib/api/src/entities/function/inner.rs index 5d325786750..d69ca10c248 100644 --- a/lib/api/src/entities/function/inner.rs +++ b/lib/api/src/entities/function/inner.rs @@ -251,6 +251,16 @@ impl BackendFunction { } } + /// Creates a new async host `Function` (dynamic) with the provided + /// signature. + /// + /// If you know the signature of the host function at compile time, + /// consider using [`Self::new_typed_async`] for less runtime overhead. + /// + /// The provided closure returns a future that resolves to the function results. + /// When invoked synchronously (via [`Function::call`]) the future will run to + /// completion immediately, provided it doesn't suspend. When invoked through + /// [`Function::call_async`], the future may suspend and resume as needed. #[inline] pub fn new_async(store: &mut impl AsStoreMut, ty: FT, func: F) -> Self where @@ -276,6 +286,14 @@ impl BackendFunction { } } + /// Creates a new async host `Function` (dynamic) with the provided + /// signature and environment. + /// + /// If you know the signature of the host function at compile time, + /// consider using [`Self::new_typed_with_env_async`] for less runtime overhead. + /// + /// Takes an [`AsyncFunctionEnvMut`] that is passed into func. If + /// that is not required, [`Self::new_async`] might be an option as well. #[inline] pub fn new_with_env_async( store: &mut impl AsStoreMut, @@ -308,6 +326,10 @@ impl BackendFunction { } } + /// Creates a new async host `Function` from a native typed function. + /// + /// The future can return either the raw result tuple or any type that implements + /// [`IntoResult`] for the result tuple (e.g. `Result`). #[inline] pub fn new_typed_async(store: &mut impl AsStoreMut, func: F) -> Self where @@ -333,6 +355,7 @@ impl BackendFunction { } } + /// Creates a new async host `Function` with an environment from a typed function. #[inline] pub fn new_typed_with_env_async( store: &mut impl AsStoreMut, diff --git a/lib/api/src/entities/function/mod.rs b/lib/api/src/entities/function/mod.rs index 198b36374d1..9655a5b1ba0 100644 --- a/lib/api/src/entities/function/mod.rs +++ b/lib/api/src/entities/function/mod.rs @@ -74,7 +74,6 @@ impl Function { /// ``` /// # use wasmer::{Function, FunctionEnv, FunctionType, Type, Store, Value}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # let env = FunctionEnv::new(&mut store, ()); /// # /// let signature = FunctionType::new(vec![Type::I32, Type::I32], vec![Type::I32]); @@ -90,7 +89,6 @@ impl Function { /// ``` /// # use wasmer::{Function, FunctionEnv, FunctionType, Type, Store, Value}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # let env = FunctionEnv::new(&mut store, ()); /// # /// const I32_I32_TO_I32: ([Type; 2], [Type; 1]) = ([Type::I32, Type::I32], [Type::I32]); @@ -136,7 +134,6 @@ impl Function { /// ``` /// # use wasmer::{Store, Function, FunctionEnv, FunctionEnvMut}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # let env = FunctionEnv::new(&mut store, ()); /// # /// fn sum(_env: FunctionEnvMut<()>, a: i32, b: i32) -> i32 { @@ -158,7 +155,11 @@ impl Function { Self(BackendFunction::new_typed_with_env(store, env, func)) } - /// Creates a new async host `Function` (dynamic) with the provided signature. + /// Creates a new async host `Function` (dynamic) with the provided + /// signature. + /// + /// If you know the signature of the host function at compile time, + /// consider using [`Self::new_typed_async`] for less runtime overhead. /// /// The provided closure returns a future that resolves to the function results. /// When invoked synchronously (via [`Function::call`]) the future will run to @@ -173,8 +174,14 @@ impl Function { Self(BackendFunction::new_async(store, ty, func)) } - /// Creates a new async host `Function` (dynamic) with the provided signature - /// and environment. + /// Creates a new async host `Function` (dynamic) with the provided + /// signature and environment. + /// + /// If you know the signature of the host function at compile time, + /// consider using [`Self::new_typed_with_env_async`] for less runtime overhead. + /// + /// Takes an [`AsyncFunctionEnvMut`] that is passed into func. If + /// that is not required, [`Self::new_async`] might be an option as well. pub fn new_with_env_async( store: &mut impl AsStoreMut, env: &FunctionEnv, @@ -223,7 +230,6 @@ impl Function { /// ``` /// # use wasmer::{Function, FunctionEnv, FunctionEnvMut, Store, Type}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # let env = FunctionEnv::new(&mut store, ()); /// # /// fn sum(_env: FunctionEnvMut<()>, a: i32, b: i32) -> i32 { @@ -246,7 +252,6 @@ impl Function { /// ``` /// # use wasmer::{Function, FunctionEnv, FunctionEnvMut, Store, Type}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # let env = FunctionEnv::new(&mut store, ()); /// # /// fn sum(_env: FunctionEnvMut<()>, a: i32, b: i32) -> i32 { @@ -268,7 +273,6 @@ impl Function { /// ``` /// # use wasmer::{Function, FunctionEnv, FunctionEnvMut, Store, Type}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # let env = FunctionEnv::new(&mut store, ()); /// # /// fn sum(_env: FunctionEnvMut<()>, a: i32, b: i32) -> i32 { @@ -297,6 +301,7 @@ impl Function { /// # use wasmer::{imports, wat2wasm, Function, Instance, Module, Store, Type, Value}; /// # use wasmer::FunctionEnv; /// # let mut store = Store::default(); + /// # let env = FunctionEnv::new(&mut store, ()); /// # let wasm_bytes = wat2wasm(r#" /// # (module /// # (func (export "sum") (param $x i32) (param $y i32) (result i32) @@ -305,9 +310,7 @@ impl Function { /// # i32.add /// # )) /// # "#.as_bytes()).unwrap(); - /// # let module = Module::new(&store.engine(), wasm_bytes).unwrap(); - /// # let mut store = store.as_mut(); - /// # let env = FunctionEnv::new(&mut store, ()); + /// # let module = Module::new(&store, wasm_bytes).unwrap(); /// # let import_object = imports! {}; /// # let instance = Instance::new(&mut store, &module, &import_object).unwrap(); /// # @@ -365,6 +368,7 @@ impl Function { /// # use wasmer::{imports, wat2wasm, Function, Instance, Module, Store, Type, TypedFunction, Value}; /// # use wasmer::FunctionEnv; /// # let mut store = Store::default(); + /// # let env = FunctionEnv::new(&mut store, ()); /// # let wasm_bytes = wat2wasm(r#" /// # (module /// # (func (export "sum") (param $x i32) (param $y i32) (result i32) @@ -373,9 +377,7 @@ impl Function { /// # i32.add /// # )) /// # "#.as_bytes()).unwrap(); - /// # let module = Module::new(&store.engine(), wasm_bytes).unwrap(); - /// # let mut store = store.as_mut(); - /// # let env = FunctionEnv::new(&mut store, ()); + /// # let module = Module::new(&store, wasm_bytes).unwrap(); /// # let import_object = imports! {}; /// # let instance = Instance::new(&mut store, &module, &import_object).unwrap(); /// # @@ -394,6 +396,7 @@ impl Function { /// # use wasmer::{imports, wat2wasm, Function, Instance, Module, Store, Type, TypedFunction, Value}; /// # use wasmer::FunctionEnv; /// # let mut store = Store::default(); + /// # let env = FunctionEnv::new(&mut store, ()); /// # let wasm_bytes = wat2wasm(r#" /// # (module /// # (func (export "sum") (param $x i32) (param $y i32) (result i32) @@ -402,9 +405,7 @@ impl Function { /// # i32.add /// # )) /// # "#.as_bytes()).unwrap(); - /// # let module = Module::new(&store.engine(), wasm_bytes).unwrap(); - /// # let mut store = store.as_mut(); - /// # let env = FunctionEnv::new(&mut store, ()); + /// # let module = Module::new(&store, wasm_bytes).unwrap(); /// # let import_object = imports! {}; /// # let instance = Instance::new(&mut store, &module, &import_object).unwrap(); /// # @@ -421,6 +422,7 @@ impl Function { /// # use wasmer::{imports, wat2wasm, Function, Instance, Module, Store, Type, TypedFunction, Value}; /// # use wasmer::FunctionEnv; /// # let mut store = Store::default(); + /// # let env = FunctionEnv::new(&mut store, ()); /// # let wasm_bytes = wat2wasm(r#" /// # (module /// # (func (export "sum") (param $x i32) (param $y i32) (result i32) @@ -429,9 +431,7 @@ impl Function { /// # i32.add /// # )) /// # "#.as_bytes()).unwrap(); - /// # let module = Module::new(&store.engine(), wasm_bytes).unwrap(); - /// # let mut store = store.as_mut(); - /// # let env = FunctionEnv::new(&mut store, ()); + /// # let module = Module::new(&store, wasm_bytes).unwrap(); /// # let import_object = imports! {}; /// # let instance = Instance::new(&mut store, &module, &import_object).unwrap(); /// # diff --git a/lib/api/src/entities/global/mod.rs b/lib/api/src/entities/global/mod.rs index 3d436937754..93b3fc50253 100644 --- a/lib/api/src/entities/global/mod.rs +++ b/lib/api/src/entities/global/mod.rs @@ -28,7 +28,6 @@ impl Global { /// ``` /// # use wasmer::{Global, Mutability, Store, Value}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # /// let g = Global::new(&mut store, Value::I32(1)); /// @@ -46,7 +45,6 @@ impl Global { /// ``` /// # use wasmer::{Global, Mutability, Store, Value}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # /// let g = Global::new_mut(&mut store, Value::I32(1)); /// @@ -73,7 +71,6 @@ impl Global { /// ``` /// # use wasmer::{Global, Mutability, Store, Type, Value, GlobalType}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # /// let c = Global::new(&mut store, Value::I32(1)); /// let v = Global::new_mut(&mut store, Value::I64(1)); @@ -92,7 +89,6 @@ impl Global { /// ``` /// # use wasmer::{Global, Store, Value}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # /// let g = Global::new(&mut store, Value::I32(1)); /// @@ -109,7 +105,6 @@ impl Global { /// ``` /// # use wasmer::{Global, Store, Value}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # /// let g = Global::new_mut(&mut store, Value::I32(1)); /// @@ -127,7 +122,6 @@ impl Global { /// ```should_panic /// # use wasmer::{Global, Store, Value}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # /// let g = Global::new(&mut store, Value::I32(1)); /// @@ -139,7 +133,6 @@ impl Global { /// ```should_panic /// # use wasmer::{Global, Store, Value}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # /// let g = Global::new(&mut store, Value::I32(1)); /// diff --git a/lib/api/src/entities/memory/mod.rs b/lib/api/src/entities/memory/mod.rs index 5ea88f1e5ae..e869b2fb703 100644 --- a/lib/api/src/entities/memory/mod.rs +++ b/lib/api/src/entities/memory/mod.rs @@ -44,7 +44,6 @@ impl Memory { /// ``` /// # use wasmer::{Memory, MemoryType, Pages, Store, Type, Value}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # /// let m = Memory::new(&mut store, MemoryType::new(1, None, false)).unwrap(); /// ``` @@ -70,7 +69,6 @@ impl Memory { /// ``` /// # use wasmer::{Memory, MemoryType, Pages, Store, Type, Value}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # /// let mt = MemoryType::new(1, None, false); /// let m = Memory::new(&mut store, mt).unwrap(); @@ -100,7 +98,6 @@ impl Memory { /// ``` /// # use wasmer::{Memory, MemoryType, Pages, Store, Type, Value, WASM_MAX_PAGES}; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # /// let m = Memory::new(&mut store, MemoryType::new(1, Some(3), false)).unwrap(); /// let p = m.grow(&mut store, 2).unwrap(); @@ -118,7 +115,6 @@ impl Memory { /// # use wasmer::{Memory, MemoryType, Pages, Store, Type, Value, WASM_MAX_PAGES}; /// # use wasmer::FunctionEnv; /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// # let env = FunctionEnv::new(&mut store, ()); /// # /// let m = Memory::new(&mut store, MemoryType::new(1, Some(1), false)).unwrap(); diff --git a/lib/api/src/entities/module/inner.rs b/lib/api/src/entities/module/inner.rs index a5e7df862ed..56b7c542788 100644 --- a/lib/api/src/entities/module/inner.rs +++ b/lib/api/src/entities/module/inner.rs @@ -440,7 +440,6 @@ impl BackendModule { /// # use wasmer::*; /// # fn main() -> anyhow::Result<()> { /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// let module = Module::deserialize_from_file(&store, path)?; /// # Ok(()) /// # } @@ -528,7 +527,6 @@ impl BackendModule { /// # use wasmer::*; /// # fn main() -> anyhow::Result<()> { /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// let module = Module::deserialize_from_file_unchecked(&store, path)?; /// # Ok(()) /// # } @@ -609,7 +607,7 @@ impl BackendModule { /// # fn main() -> anyhow::Result<()> { /// # let mut store = Store::default(); /// let wat = "(module $moduleName)"; - /// let module = Module::new(&store.engine(), wat)?; + /// let module = Module::new(&store, wat)?; /// assert_eq!(module.name(), Some("moduleName")); /// # Ok(()) /// # } @@ -635,7 +633,7 @@ impl BackendModule { /// # fn main() -> anyhow::Result<()> { /// # let mut store = Store::default(); /// let wat = "(module)"; - /// let mut module = Module::new(&store.engine(), wat)?; + /// let mut module = Module::new(&store, wat)?; /// assert_eq!(module.name(), None); /// module.set_name("foo"); /// assert_eq!(module.name(), Some("foo")); @@ -664,7 +662,7 @@ impl BackendModule { /// (import "host" "func1" (func)) /// (import "host" "func2" (func)) /// )"#; - /// let module = Module::new(&store.engine(), wat)?; + /// let module = Module::new(&store, wat)?; /// for import in module.imports() { /// assert_eq!(import.module(), "host"); /// assert!(import.name().contains("func")); @@ -695,7 +693,7 @@ impl BackendModule { /// (func (export "namedfunc")) /// (memory (export "namedmemory") 1) /// )"#; - /// let module = Module::new(&store.engine(), wat)?; + /// let module = Module::new(&store, wat)?; /// for export_ in module.exports() { /// assert!(export_.name().contains("named")); /// export_.ty(); diff --git a/lib/api/src/entities/module/mod.rs b/lib/api/src/entities/module/mod.rs index 12af2f6d189..daa3de03121 100644 --- a/lib/api/src/entities/module/mod.rs +++ b/lib/api/src/entities/module/mod.rs @@ -69,7 +69,7 @@ impl Module { /// # fn main() -> anyhow::Result<()> { /// # let mut store = Store::default(); /// let wat = "(module)"; - /// let module = Module::new(&store.engine(), wat)?; + /// let module = Module::new(&store, wat)?; /// # Ok(()) /// # } /// ``` @@ -96,7 +96,7 @@ impl Module { /// 0x61, 0x6d, 0x65, 0x01, 0x0a, 0x01, 0x00, 0x07, 0x61, 0x64, 0x64, 0x5f, /// 0x6f, 0x6e, 0x65, 0x02, 0x07, 0x01, 0x00, 0x01, 0x00, 0x02, 0x70, 0x30, /// ]; - /// let module = Module::new(&store.engine(), bytes)?; + /// let module = Module::new(&store, bytes)?; /// # Ok(()) /// # } /// ``` @@ -278,7 +278,6 @@ impl Module { /// # use wasmer::*; /// # fn main() -> anyhow::Result<()> { /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// let module = Module::deserialize_from_file(&store, path)?; /// # Ok(()) /// # } @@ -309,7 +308,6 @@ impl Module { /// # use wasmer::*; /// # fn main() -> anyhow::Result<()> { /// # let mut store = Store::default(); - /// # let mut store = store.as_mut(); /// let module = Module::deserialize_from_file_unchecked(&store, path)?; /// # Ok(()) /// # } @@ -333,7 +331,7 @@ impl Module { /// # fn main() -> anyhow::Result<()> { /// # let mut store = Store::default(); /// let wat = "(module $moduleName)"; - /// let module = Module::new(&store.engine(), wat)?; + /// let module = Module::new(&store, wat)?; /// assert_eq!(module.name(), Some("moduleName")); /// # Ok(()) /// # } @@ -356,7 +354,7 @@ impl Module { /// # fn main() -> anyhow::Result<()> { /// # let mut store = Store::default(); /// let wat = "(module)"; - /// let mut module = Module::new(&store.engine(), wat)?; + /// let mut module = Module::new(&store, wat)?; /// assert_eq!(module.name(), None); /// module.set_name("foo"); /// assert_eq!(module.name(), Some("foo")); @@ -382,7 +380,7 @@ impl Module { /// (import "host" "func1" (func)) /// (import "host" "func2" (func)) /// )"#; - /// let module = Module::new(&store.engine(), wat)?; + /// let module = Module::new(&store, wat)?; /// for import in module.imports() { /// assert_eq!(import.module(), "host"); /// assert!(import.name().contains("func")); @@ -410,7 +408,7 @@ impl Module { /// (func (export "namedfunc")) /// (memory (export "namedmemory") 1) /// )"#; - /// let module = Module::new(&store.engine(), wat)?; + /// let module = Module::new(&store, wat)?; /// for export_ in module.exports() { /// assert!(export_.name().contains("named")); /// export_.ty(); diff --git a/lib/api/src/entities/store/async_.rs b/lib/api/src/entities/store/async_.rs index c46b19c25da..31e1dd6037b 100644 --- a/lib/api/src/entities/store/async_.rs +++ b/lib/api/src/entities/store/async_.rs @@ -1,8 +1,8 @@ use std::marker::PhantomData; use crate::{ - AsStoreMut, AsStoreRef, LocalReadGuardRc, LocalRwLock, LocalWriteGuardRc, Store, StoreContext, - StoreInner, StoreMut, StorePtrWrapper, StoreRef, + AsStoreMut, AsStoreRef, LocalRwLock, LocalRwLockReadGuard, LocalRwLockWriteGuard, Store, + StoreContext, StoreInner, StoreMut, StorePtrWrapper, StoreRef, }; use wasmer_types::StoreId; @@ -33,10 +33,6 @@ impl StoreAsync { /// A trait for types that can be used with /// [`Function::call_async`](crate::Function::call_async). -/// -/// Note that, while this trait can easily be implemented for a lot -/// of types (including [`StoreMut`]), implementations are left -/// out on purpose to help avoid common deadlock scenarios. pub trait AsStoreAsync { /// Returns a reference to the inner store. fn store_ref(&self) -> &StoreAsync; @@ -73,12 +69,11 @@ impl AsStoreAsync for StoreAsync { } pub(crate) enum AsyncStoreReadLockInner { - Owned(LocalReadGuardRc), + Owned(LocalRwLockReadGuard), FromStoreContext(StorePtrWrapper), } -/// A read lock on a store that can be used in concurrent contexts; -/// mostly useful in conjunction with [`AsStoreAsync`]. +/// A read lock on an async store. pub struct AsyncStoreReadLock<'a> { pub(crate) inner: AsyncStoreReadLockInner, _marker: PhantomData<&'a ()>, @@ -95,7 +90,7 @@ impl<'a> AsyncStoreReadLock<'a> { None => { // Drop the option before awaiting, since the value isn't Send drop(store_context); - let store_ref = store.inner.read_rc().await; + let store_ref = store.inner.read().await; Self { inner: AsyncStoreReadLockInner::Owned(store_ref), _marker: PhantomData, @@ -115,12 +110,11 @@ impl AsStoreRef for AsyncStoreReadLock<'_> { } pub(crate) enum AsyncStoreWriteLockInner { - Owned(LocalWriteGuardRc), + Owned(LocalRwLockWriteGuard), FromStoreContext(StorePtrWrapper), } -/// A write lock on a store that can be used in concurrent contexts; -/// mostly useful in conjunction with [`AsStoreAsync`]. +/// A write lock on an async store. pub struct AsyncStoreWriteLock<'a> { pub(crate) inner: AsyncStoreWriteLockInner, _marker: PhantomData<&'a ()>, @@ -137,7 +131,7 @@ impl<'a> AsyncStoreWriteLock<'a> { None => { // Drop the option before awaiting, since the value isn't Send drop(store_context); - let store_guard = store.inner.write_rc().await; + let store_guard = store.inner.write().await; Self { inner: AsyncStoreWriteLockInner::Owned(store_guard), _marker: PhantomData, diff --git a/lib/api/src/entities/store/context.rs b/lib/api/src/entities/store/context.rs index 7bf05e36dff..eee4921554b 100644 --- a/lib/api/src/entities/store/context.rs +++ b/lib/api/src/entities/store/context.rs @@ -1,10 +1,32 @@ //! Thread-local storage for storing the current store context, -//! i.e. the currently active `Store`(s). When a function is -//! called, a pointer to the [`StoreInner`] in placed inside -//! the store context so it can be retrieved when needed. +//! i.e. the currently active [`Store`](crate::Store)(s). When +//! a function is called, a pointer to the [`StoreInner`] in placed +//! inside the store context so it can be retrieved when needed. //! This lets code that needs access to the store get it with //! just the store ID. //! +//! The currently active store context can be a sync or async +//! context. +//! +//! For sync contexts, we just store a raw pointer +//! to the `StoreInner`, which is owned by the embedder's stack. +//! +//! For async contexts, we store a write guard taken from the +//! [`StoreAsync`](crate::StoreAsync); This achieves two goals: +//! * Makes the [`StoreAsync`](crate::StoreAsync) available +//! to whoever needs it, including when code needs to spawn +//! new coroutines +//! * Makes sure a write lock is held on the store as long as +//! the context is active, preventing other tasks from +//! accessing the store concurrently. +//! +//! Because async contexts can't be entered recursively (you +//! can't take a write lock twice, you have to use the existing +//! one), all code in this crate takes care to check for an +//! active store context first before trying to enter one. This +//! gives rise to the enums with cases for temporary locks vs +//! store context pointers, such as [`AsyncStoreReadLockInner`]. +//! //! We maintain a stack because it is technically possible to //! have nested `Function::call` invocations that use different //! stores, such as: @@ -32,7 +54,7 @@ use std::{ mem::MaybeUninit, }; -use crate::LocalWriteGuardRc; +use crate::LocalRwLockWriteGuard; use super::{AsStoreMut, AsStoreRef, StoreInner, StoreMut, StoreRef}; @@ -40,7 +62,7 @@ use wasmer_types::StoreId; enum StoreContextEntry { Sync(*mut StoreInner), - Async(LocalWriteGuardRc), + Async(LocalRwLockWriteGuard), } impl StoreContextEntry { @@ -73,7 +95,7 @@ pub(crate) struct StorePtrWrapper { } pub(crate) struct AsyncStoreGuardWrapper { - pub(crate) guard: *mut LocalWriteGuardRc, + pub(crate) guard: *mut LocalRwLockWriteGuard, } pub(crate) enum GetAsyncStoreGuardResult { @@ -124,13 +146,16 @@ impl StoreContext { /// The write guard ensures this is the only reference to the store, /// so installation can never fail. - pub(crate) fn install_async(guard: LocalWriteGuardRc) -> ForcedStoreInstallGuard { + pub(crate) fn install_async( + guard: LocalRwLockWriteGuard, + ) -> ForcedStoreInstallGuard { let store_id = guard.objects.id(); Self::install(store_id, StoreContextEntry::Async(guard)); ForcedStoreInstallGuard { store_id } } /// Install the store context as sync if it is not already installed. + /// /// # Safety /// The pointer must be dereferenceable and remain valid until the /// store context is uninstalled. @@ -145,7 +170,7 @@ impl StoreContext { } /// Safety: This method lets you borrow multiple mutable references - /// to the currently active StoreMut. The caller must ensure that: + /// to the currently active store context. The caller must ensure that: /// * there is only one mutable reference alive, or /// * all but one mutable reference are inaccessible and passed /// into a function that lost the reference (e.g. into WASM code) @@ -169,7 +194,7 @@ impl StoreContext { /// the pointer returned from this function will become invalid if /// the store context is changed in any way (via installing or uninstalling /// a store context). The caller must ensure that the store context - /// remains unchanged for the entire lifetime of the returned reference. + /// remains unchanged as long as the pointer is being accessed. pub(crate) unsafe fn get_current_transient(id: StoreId) -> *mut StoreInner { STORE_CONTEXT_STACK.with(|cell| { let mut stack = cell.borrow_mut(); diff --git a/lib/api/src/entities/store/local_rwlock.rs b/lib/api/src/entities/store/local_rwlock.rs index 33ae6c2276f..47cc1e82e17 100644 --- a/lib/api/src/entities/store/local_rwlock.rs +++ b/lib/api/src/entities/store/local_rwlock.rs @@ -5,7 +5,8 @@ //! and is `!Send + !Sync`, making it more efficient when thread safety is not needed. //! //! Like `async_lock::RwLock`, it provides `read_rc()` and `write_rc()` methods -//! that return guards with `'static` lifetimes by holding an `Rc` to the lock. +//! that allow callers to asynchronously wait for the lock to become available, +//! and return guards with `'static` lifetimes by holding an `Rc` to the lock. use std::cell::{Cell, RefCell, UnsafeCell}; use std::future::Future; @@ -15,15 +16,7 @@ use std::pin::Pin; use std::rc::Rc; use std::task::{Context, Poll, Waker}; -/// A single-threaded async-aware read-write lock. -/// -/// This lock allows multiple concurrent readers or a single writer. -/// When a lock is not available, tasks will wait asynchronously rather -/// than blocking or panicking. -/// -/// Unlike `std::sync::RwLock` or `async_lock::RwLock`, this implementation -/// is not `Send` or `Sync`, making it suitable only for single-threaded -/// async runtimes. The benefit is zero atomic operations. +/// The main lock type. pub struct LocalRwLock { inner: Rc>, } @@ -55,86 +48,30 @@ impl LocalRwLock { } } - /// Attempts to acquire a read lock without waiting. - /// - /// Returns `Some(guard)` if the lock was acquired, or `None` if a writer - /// currently holds the lock. - pub fn try_read(&self) -> Option> { - if self.inner.try_read() { - Some(LocalReadGuard { - inner: &self.inner, - _marker: PhantomData, - }) - } else { - None - } - } - - /// Attempts to acquire a write lock without waiting. - /// - /// Returns `Some(guard)` if the lock was acquired, or `None` if any - /// readers or writers currently hold the lock. - pub fn try_write(&self) -> Option> { - if self.inner.try_write() { - Some(LocalWriteGuard { - inner: &self.inner, - _marker: PhantomData, - }) - } else { - None - } - } - /// Acquires a read lock, waiting asynchronously if necessary. /// - /// Returns a future that resolves to a read guard. - pub fn read(&self) -> ReadFuture<'_, T> { + /// The returned guard holds an `Rc` clone, allowing it to have a `'static` lifetime. + pub fn read(&self) -> ReadFuture { ReadFuture { - inner: &self.inner, + inner: self.inner.clone(), waiter_index: Cell::new(None), - _marker: PhantomData, } } /// Acquires a write lock, waiting asynchronously if necessary. /// - /// Returns a future that resolves to a write guard. - pub fn write(&self) -> WriteFuture<'_, T> { + /// The returned guard holds an `Rc` clone, allowing it to have a `'static` lifetime. + pub fn write(&self) -> WriteFuture { WriteFuture { - inner: &self.inner, - waiter_index: Cell::new(None), - _marker: PhantomData, - } - } - - /// Acquires a read lock with a `'static` lifetime, waiting asynchronously if necessary. - /// - /// This is similar to `read()`, but the returned guard holds an `Rc` clone, - /// allowing it to have a `'static` lifetime. - pub async fn read_rc(&self) -> LocalReadGuardRc { - ReadRcFuture { - inner: self.inner.clone(), - waiter_index: Cell::new(None), - } - .await - } - - /// Acquires a write lock with a `'static` lifetime, waiting asynchronously if necessary. - /// - /// This is similar to `write()`, but the returned guard holds an `Rc` clone, - /// allowing it to have a `'static` lifetime. - pub async fn write_rc(&self) -> LocalWriteGuardRc { - WriteRcFuture { inner: self.inner.clone(), waiter_index: Cell::new(None), } - .await } /// Attempts to acquire a read lock with a `'static` lifetime without waiting. - pub fn try_read_rc(&self) -> Option> { + pub fn try_read(&self) -> Option> { if self.inner.try_read() { - Some(LocalReadGuardRc { + Some(LocalRwLockReadGuard { inner: self.inner.clone(), }) } else { @@ -143,9 +80,9 @@ impl LocalRwLock { } /// Attempts to acquire a write lock with a `'static` lifetime without waiting. - pub fn try_write_rc(&self) -> Option> { + pub fn try_write(&self) -> Option> { if self.inner.try_write() { - Some(LocalWriteGuardRc { + Some(LocalRwLockWriteGuard { inner: self.inner.clone(), }) } else { @@ -323,62 +260,14 @@ impl LocalRwLockInner { } } -// Guards with borrowed lifetime - -/// A read guard with a borrowed lifetime. -pub struct LocalReadGuard<'a, T> { - inner: &'a LocalRwLockInner, - _marker: PhantomData<&'a T>, -} - -impl Deref for LocalReadGuard<'_, T> { - type Target = T; - - fn deref(&self) -> &T { - unsafe { &*self.inner.value.get() } - } -} - -impl Drop for LocalReadGuard<'_, T> { - fn drop(&mut self) { - self.inner.release_read(); - } -} - -/// A write guard with a borrowed lifetime. -pub struct LocalWriteGuard<'a, T> { - inner: &'a LocalRwLockInner, - _marker: PhantomData<&'a mut T>, -} - -impl Deref for LocalWriteGuard<'_, T> { - type Target = T; - - fn deref(&self) -> &T { - unsafe { &*self.inner.value.get() } - } -} - -impl DerefMut for LocalWriteGuard<'_, T> { - fn deref_mut(&mut self) -> &mut T { - unsafe { &mut *self.inner.value.get() } - } -} - -impl Drop for LocalWriteGuard<'_, T> { - fn drop(&mut self) { - self.inner.release_write(); - } -} - // Guards with 'static lifetime (Rc-like) /// A read guard with a `'static` lifetime, holding an `Rc` to the lock. -pub struct LocalReadGuardRc { +pub struct LocalRwLockReadGuard { inner: Rc>, } -impl LocalReadGuardRc { +impl LocalRwLockReadGuard { /// Rebuild a handle to the lock from this [`LocalReadGuardRc`]. pub fn lock_handle(me: &Self) -> LocalRwLock { LocalRwLock { @@ -387,7 +276,7 @@ impl LocalReadGuardRc { } } -impl Deref for LocalReadGuardRc { +impl Deref for LocalRwLockReadGuard { type Target = T; fn deref(&self) -> &T { @@ -395,18 +284,18 @@ impl Deref for LocalReadGuardRc { } } -impl Drop for LocalReadGuardRc { +impl Drop for LocalRwLockReadGuard { fn drop(&mut self) { self.inner.release_read(); } } /// A write guard with a `'static` lifetime, holding an `Rc` to the lock. -pub struct LocalWriteGuardRc { +pub struct LocalRwLockWriteGuard { inner: Rc>, } -impl LocalWriteGuardRc { +impl LocalRwLockWriteGuard { /// Rebuild a handle to the lock from this [`LocalWriteGuardRc`]. pub fn lock_handle(me: &Self) -> LocalRwLock { LocalRwLock { @@ -415,7 +304,7 @@ impl LocalWriteGuardRc { } } -impl Deref for LocalWriteGuardRc { +impl Deref for LocalRwLockWriteGuard { type Target = T; fn deref(&self) -> &T { @@ -423,13 +312,13 @@ impl Deref for LocalWriteGuardRc { } } -impl DerefMut for LocalWriteGuardRc { +impl DerefMut for LocalRwLockWriteGuard { fn deref_mut(&mut self) -> &mut T { unsafe { &mut *self.inner.value.get() } } } -impl Drop for LocalWriteGuardRc { +impl Drop for LocalRwLockWriteGuard { fn drop(&mut self) { self.inner.release_write(); } @@ -437,78 +326,14 @@ impl Drop for LocalWriteGuardRc { // Futures -/// Future returned by `read()`. -pub struct ReadFuture<'a, T> { - inner: &'a LocalRwLockInner, - waiter_index: Cell>, - _marker: PhantomData<&'a T>, -} - -impl<'a, T> Future for ReadFuture<'a, T> { - type Output = LocalReadGuard<'a, T>; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - if self - .inner - .poll_lock(&self.waiter_index, cx, |inner| inner.try_read(), false) - .is_ready() - { - Poll::Ready(LocalReadGuard { - inner: self.inner, - _marker: PhantomData, - }) - } else { - Poll::Pending - } - } -} - -impl<'a, T> Drop for ReadFuture<'a, T> { - fn drop(&mut self) { - self.inner.cleanup_waiter(&self.waiter_index, false); - } -} - -/// Future returned by `write()`. -pub struct WriteFuture<'a, T> { - inner: &'a LocalRwLockInner, - waiter_index: Cell>, - _marker: PhantomData<&'a mut T>, -} - -impl<'a, T> Future for WriteFuture<'a, T> { - type Output = LocalWriteGuard<'a, T>; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - if self - .inner - .poll_lock(&self.waiter_index, cx, |inner| inner.try_write(), true) - .is_ready() - { - Poll::Ready(LocalWriteGuard { - inner: self.inner, - _marker: PhantomData, - }) - } else { - Poll::Pending - } - } -} - -impl<'a, T> Drop for WriteFuture<'a, T> { - fn drop(&mut self) { - self.inner.cleanup_waiter(&self.waiter_index, true); - } -} - /// Future returned by `read_rc()`. -pub struct ReadRcFuture { +pub struct ReadFuture { inner: Rc>, waiter_index: Cell>, } -impl Future for ReadRcFuture { - type Output = LocalReadGuardRc; +impl Future for ReadFuture { + type Output = LocalRwLockReadGuard; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { if self @@ -516,7 +341,7 @@ impl Future for ReadRcFuture { .poll_lock(&self.waiter_index, cx, |inner| inner.try_read(), false) .is_ready() { - Poll::Ready(LocalReadGuardRc { + Poll::Ready(LocalRwLockReadGuard { inner: self.inner.clone(), }) } else { @@ -525,20 +350,20 @@ impl Future for ReadRcFuture { } } -impl Drop for ReadRcFuture { +impl Drop for ReadFuture { fn drop(&mut self) { self.inner.cleanup_waiter(&self.waiter_index, false); } } /// Future returned by `write_rc()`. -pub struct WriteRcFuture { +pub struct WriteFuture { inner: Rc>, waiter_index: Cell>, } -impl Future for WriteRcFuture { - type Output = LocalWriteGuardRc; +impl Future for WriteFuture { + type Output = LocalRwLockWriteGuard; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { if self @@ -546,7 +371,7 @@ impl Future for WriteRcFuture { .poll_lock(&self.waiter_index, cx, |inner| inner.try_write(), true) .is_ready() { - Poll::Ready(LocalWriteGuardRc { + Poll::Ready(LocalRwLockWriteGuard { inner: self.inner.clone(), }) } else { @@ -555,16 +380,12 @@ impl Future for WriteRcFuture { } } -impl Drop for WriteRcFuture { +impl Drop for WriteFuture { fn drop(&mut self) { self.inner.cleanup_waiter(&self.waiter_index, true); } } -// Note: LocalRwLock is NOT Send or Sync because it uses Rc and RefCell. -// This is intentional - it's designed for single-threaded async contexts only. -// The Rc> automatically makes the type !Send + !Sync. - #[cfg(test)] mod tests { use super::*; @@ -637,16 +458,16 @@ mod tests { let lock = LocalRwLock::new(42); // Try read_rc - let guard = lock.try_read_rc().unwrap(); + let guard = lock.try_read().unwrap(); assert_eq!(*guard, 42); drop(guard); // Try write_rc - let mut guard = lock.try_write_rc().unwrap(); + let mut guard = lock.try_write().unwrap(); *guard = 100; drop(guard); - let guard = lock.try_read_rc().unwrap(); + let guard = lock.try_read().unwrap(); assert_eq!(*guard, 100); } diff --git a/lib/api/src/lib.rs b/lib/api/src/lib.rs index d93493305eb..c85f05cfa08 100644 --- a/lib/api/src/lib.rs +++ b/lib/api/src/lib.rs @@ -51,8 +51,7 @@ //! "#; //! //! let mut store = Store::default(); -//! let module = Module::new(&store.engine(), &module_wat)?; -//! let mut store = store.as_mut(); +//! let module = Module::new(&store, &module_wat)?; //! // The module doesn't import anything, so we create an empty import object. //! let import_object = imports! {}; //! let instance = Instance::new(&mut store, &module, &import_object)?; @@ -147,12 +146,12 @@ //! [`imports!`] macro: //! //! ``` -//! # use wasmer::{imports, Function, FunctionEnv, FunctionEnvMut, Memory, MemoryType, AsStoreMut, Imports}; -//! # fn imports_example(store: &mut impl AsStoreMut) -> Imports { -//! let memory = Memory::new(store, MemoryType::new(1, None, false)).unwrap(); +//! # use wasmer::{imports, Function, FunctionEnv, FunctionEnvMut, Memory, MemoryType, Store, Imports}; +//! # fn imports_example(mut store: &mut Store) -> Imports { +//! let memory = Memory::new(&mut store, MemoryType::new(1, None, false)).unwrap(); //! imports! { //! "env" => { -//! "my_function" => Function::new_typed(store, || println!("Hello")), +//! "my_function" => Function::new_typed(&mut store, || println!("Hello")), //! "memory" => memory, //! } //! } @@ -163,12 +162,12 @@ //! from any instance via `instance.exports`: //! //! ``` -//! # use wasmer::{imports, Instance, FunctionEnv, Memory, TypedFunction, AsStoreMut}; -//! # fn exports_example(mut env: FunctionEnv<()>, store: &mut impl AsStoreMut, instance: &Instance) -> anyhow::Result<()> { +//! # use wasmer::{imports, Instance, FunctionEnv, Memory, TypedFunction, Store}; +//! # fn exports_example(mut env: FunctionEnv<()>, mut store: &mut Store, instance: &Instance) -> anyhow::Result<()> { //! let memory = instance.exports.get_memory("memory")?; //! let memory: &Memory = instance.exports.get("some_other_memory")?; -//! let add: TypedFunction<(i32, i32), i32> = instance.exports.get_typed_function(store, "add")?; -//! let result = add.call(store, 5, 37)?; +//! let add: TypedFunction<(i32, i32), i32> = instance.exports.get_typed_function(&mut store, "add")?; +//! let result = add.call(&mut store, 5, 37)?; //! assert_eq!(result, 42); //! # Ok(()) //! # } @@ -380,8 +379,7 @@ //! i32.add)) //! "#; //! let mut store = Store::default(); -//! let module = Module::new(&store.engine(), &module_wat).unwrap(); -//! let mut store = store.as_mut(); +//! let module = Module::new(&store, &module_wat).unwrap(); //! // The module doesn't import anything, so we create an empty import object. //! let import_object = imports! {}; //! let instance = Instance::new(&mut store, &module, &import_object).unwrap(); diff --git a/lib/api/tests/jspi_async.rs b/lib/api/tests/jspi_async.rs index ca987d6ab6d..094c5532b51 100644 --- a/lib/api/tests/jspi_async.rs +++ b/lib/api/tests/jspi_async.rs @@ -365,7 +365,7 @@ fn async_multiple_active_coroutines() -> Result<()> { // We then poll it once to get it started - it'll suspend once, then // complete the next time we poll it - let w = noop_waker(); + let w = futures::task::noop_waker(); let mut cx = Context::from_waker(&w); assert!(future.as_mut().poll(&mut cx).is_pending()); @@ -413,7 +413,7 @@ fn async_multiple_active_coroutines() -> Result<()> { .take() .unwrap(); - let w = noop_waker(); + let w = futures::task::noop_waker(); let mut cx = Context::from_waker(&w); let Poll::Ready(result) = future.as_mut().poll(&mut cx) else { panic!("expected future to be ready"); @@ -495,15 +495,3 @@ fn async_multiple_active_coroutines() -> Result<()> { Ok(()) } - -fn noop_waker() -> Waker { - fn noop_raw_waker() -> RawWaker { - fn no_op(_: *const ()) {} - fn clone(_: *const ()) -> RawWaker { - noop_raw_waker() - } - let vtable = &RawWakerVTable::new(clone, no_op, no_op, no_op); - RawWaker::new(std::ptr::null(), vtable) - } - unsafe { Waker::from_raw(noop_raw_waker()) } -} diff --git a/lib/vm/src/trap/traphandlers.rs b/lib/vm/src/trap/traphandlers.rs index 8840028184b..6c4b8c540c6 100644 --- a/lib/vm/src/trap/traphandlers.rs +++ b/lib/vm/src/trap/traphandlers.rs @@ -28,7 +28,6 @@ use wasmer_types::TrapCode; /// Configuration for the runtime VM /// Currently only the stack size is configurable -#[derive(Clone)] pub struct VMConfig { /// Optional stack size (in byte) of the VM. Value lower than 8K will be rounded to 8K. pub wasm_stack_size: Option, From b02a4fdb8e5ce0cff36e8c5e4bb5622450a4de6d Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Thu, 27 Nov 2025 20:55:29 +0400 Subject: [PATCH 29/49] Restore ability to call non-suspending async imports in sync contexts and to spawn coroutines from sync imports --- lib/api/src/backend/sys/async_runtime.rs | 21 +- .../src/backend/sys/entities/function/env.rs | 101 +++++++-- .../src/backend/sys/entities/function/mod.rs | 48 +++-- lib/api/src/entities/function/env/inner.rs | 10 + lib/api/src/entities/function/env/mod.rs | 6 + lib/api/src/entities/store/async_.rs | 4 +- lib/api/src/entities/store/context.rs | 27 ++- lib/api/tests/jspi_async.rs | 196 +++++++++--------- 8 files changed, 268 insertions(+), 145 deletions(-) diff --git a/lib/api/src/backend/sys/async_runtime.rs b/lib/api/src/backend/sys/async_runtime.rs index 6e065f7b7d2..60abc292fbd 100644 --- a/lib/api/src/backend/sys/async_runtime.rs +++ b/lib/api/src/backend/sys/async_runtime.rs @@ -200,10 +200,20 @@ enum StoreContextInstaller { impl StoreContextInstaller { async fn install(store: StoreAsync) -> Self { match unsafe { crate::StoreContext::try_get_current_async(store.id) } { - crate::GetAsyncStoreGuardResult::NotAsync => { - // If the store was installed as a sync store, panic - this - // should be impossible - unreachable!("Sync store context installed in async call") + crate::GetAsyncStoreGuardResult::NotAsync(_) => { + // This async call is being polled from inside a sync call, such as: + // call() -> imported function (sync or async) -> call_async().poll() + // In this case, we can never progress. + // + // If the imported function is async, suspending here will cause a + // YieldOutsideAsyncContext trap to be raised. + // + // If the imported function is sync and you're hoping to drive this + // future to completion (maybe using an inline blocker), may God have + // mercy on you. Also, I *think* that's impossible given the way this + // crate's public API is currently set up. + tracing::warn!("Async function polled from sync context; suspending forever"); + futures::future::pending().await } crate::GetAsyncStoreGuardResult::Ok(wrapper) => { // If we're already in the scope of this store, we can just reuse it. @@ -311,10 +321,9 @@ impl CoroutineContext { fn run_immediate( future: impl Future, RuntimeError>> + 'static, ) -> Result, AsyncRuntimeError> { - let mut future = Box::pin(future); let waker = futures::task::noop_waker(); - let mut cx = Context::from_waker(&waker); + let mut future = Box::pin(future); match future.as_mut().poll(&mut cx) { Poll::Ready(result) => result.map_err(AsyncRuntimeError::RuntimeError), Poll::Pending => Err(AsyncRuntimeError::YieldOutsideAsyncContext), diff --git a/lib/api/src/backend/sys/entities/function/env.rs b/lib/api/src/backend/sys/entities/function/env.rs index e69d9d64a74..e96cc47c61c 100644 --- a/lib/api/src/backend/sys/entities/function/env.rs +++ b/lib/api/src/backend/sys/entities/function/env.rs @@ -2,7 +2,7 @@ use std::{any::Any, fmt::Debug, marker::PhantomData}; use crate::{ AsStoreAsync, AsyncStoreReadLock, AsyncStoreWriteLock, Store, StoreAsync, StoreContext, - StoreMut, + StoreInner, StoreMut, StorePtrWrapper, store::{AsStoreMut, AsStoreRef, StoreRef}, }; @@ -165,6 +165,22 @@ impl FunctionEnvMut<'_, T> { let data = unsafe { &mut *data }; (data, self.store_mut.as_store_mut()) } + + pub fn as_store_async(&self) -> Option { + let id = self.store_mut.inner.objects.id(); + + // Safety: we don't keep the guard around, it's just used to + // build a safe lock handle. + match unsafe { StoreContext::try_get_current_async(id) } { + crate::GetAsyncStoreGuardResult::Ok(guard) => Some(StoreAsync { + id, + inner: crate::LocalRwLockWriteGuard::lock_handle(unsafe { + guard.guard.as_ref().unwrap() + }), + }), + _ => None, + } + } } impl AsStoreRef for FunctionEnvMut<'_, T> { @@ -202,10 +218,19 @@ impl From> for crate::FunctionEnv { /// A shared handle to a [`FunctionEnv`], suitable for use /// in async imports. pub struct AsyncFunctionEnvMut { - pub(crate) store: StoreAsync, + pub(crate) store: AsyncFunctionEnvMutStore, pub(crate) func_env: FunctionEnv, } +// We need to let async functions that *don't suspend* run +// in a sync context. To that end, `AsyncFunctionEnvMut` +// must be able to be constructed without an actual +// StoreAsync instance, hence this enum. +pub(crate) enum AsyncFunctionEnvMutStore { + Async(StoreAsync), + Sync(StorePtrWrapper), +} + /// A read-only handle to the [`FunctionEnv`] in an [`AsyncFunctionEnvMut`]. pub struct AsyncFunctionEnvHandle<'a, T> { read_lock: AsyncStoreReadLock<'a>, @@ -223,22 +248,35 @@ where T: Send + Debug + 'static, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self.store.inner.try_read() { - Some(read_lock) => self.func_env.as_ref(&read_lock).fmt(f), - None => write!(f, "AsyncFunctionEnvMut {{ }}"), + match &self.store { + AsyncFunctionEnvMutStore::Sync(ptr) => self.func_env.as_ref(&ptr.as_ref()).fmt(f), + AsyncFunctionEnvMutStore::Async(store) => match store.inner.try_read() { + Some(read_lock) => self.func_env.as_ref(&read_lock).fmt(f), + None => write!(f, "AsyncFunctionEnvMut {{ }}"), + }, } } } impl AsyncFunctionEnvMut { pub(crate) fn store_id(&self) -> StoreId { - self.store.id + match &self.store { + AsyncFunctionEnvMutStore::Sync(ptr) => ptr.as_ref().objects().id(), + AsyncFunctionEnvMutStore::Async(store) => store.id, + } } /// Waits for a store lock and returns a read-only handle to the /// function environment. pub async fn read<'a>(&'a self) -> AsyncFunctionEnvHandle<'a, T> { - let read_lock = self.store.read_lock().await; + let read_lock = match &self.store { + AsyncFunctionEnvMutStore::Sync(ptr) => AsyncStoreReadLock { + inner: crate::AsyncStoreReadLockInner::FromStoreContext(ptr.clone()), + _marker: PhantomData, + }, + AsyncFunctionEnvMutStore::Async(store) => store.read_lock().await, + }; + AsyncFunctionEnvHandle { read_lock, func_env: self.func_env.clone(), @@ -248,7 +286,14 @@ impl AsyncFunctionEnvMut { /// Waits for a store lock and returns a mutable handle to the /// function environment. pub async fn write<'a>(&'a self) -> AsyncFunctionEnvHandleMut<'a, T> { - let write_lock = self.store.write_lock().await; + let write_lock = match &self.store { + AsyncFunctionEnvMutStore::Sync(ptr) => AsyncStoreWriteLock { + inner: crate::AsyncStoreWriteLockInner::FromStoreContext(ptr.clone()), + _marker: PhantomData, + }, + AsyncFunctionEnvMutStore::Async(store) => store.write_lock().await, + }; + AsyncFunctionEnvHandleMut { write_lock, func_env: self.func_env.clone(), @@ -261,21 +306,41 @@ impl AsyncFunctionEnvMut { } /// Borrows a new mutable reference - pub fn as_mut(&mut self) -> AsyncFunctionEnvMut { - AsyncFunctionEnvMut { - store: StoreAsync { - id: self.store.id, - inner: self.store.inner.clone(), + pub fn as_mut(&mut self) -> Self { + self.clone() + } + + /// Creates an [`AsStoreAsync`] from this [`AsyncFunctionEnvMut`]. + pub fn as_store_async(&self) -> impl AsStoreAsync + 'static { + match &self.store { + AsyncFunctionEnvMutStore::Sync(_) => { + panic!("Cannot build a StoreAsync within a sync context") + } + AsyncFunctionEnvMutStore::Async(store) => StoreAsync { + id: store.id, + inner: store.inner.clone(), }, + } + } +} + +impl Clone for AsyncFunctionEnvMut { + fn clone(&self) -> Self { + Self { + store: self.store.clone(), func_env: self.func_env.clone(), } } +} - /// Creates an [`AsStoreAsync`] from this [`AsyncFunctionEnvMut`]. - pub fn as_store_async(&self) -> impl AsStoreAsync + 'static { - StoreAsync { - id: self.store.id, - inner: self.store.inner.clone(), +impl Clone for AsyncFunctionEnvMutStore { + fn clone(&self) -> Self { + match self { + Self::Async(store) => Self::Async(StoreAsync { + id: store.id, + inner: store.inner.clone(), + }), + Self::Sync(ptr) => Self::Sync(ptr.clone()), } } } diff --git a/lib/api/src/backend/sys/entities/function/mod.rs b/lib/api/src/backend/sys/entities/function/mod.rs index 2fd17f4dc59..c7e1bdc21a0 100644 --- a/lib/api/src/backend/sys/entities/function/mod.rs +++ b/lib/api/src/backend/sys/entities/function/mod.rs @@ -5,14 +5,14 @@ pub(crate) mod typed; use crate::{ AsStoreAsync, AsyncFunctionEnvMut, BackendAsyncFunctionEnvMut, BackendFunction, FunctionEnv, - FunctionEnvMut, FunctionType, HostFunction, RuntimeError, StoreContext, StoreInner, Value, - WithEnv, WithoutEnv, + FunctionEnvMut, FunctionType, HostFunction, RuntimeError, StoreAsync, StoreContext, StoreInner, + Value, WithEnv, WithoutEnv, backend::sys::{engine::NativeEngineExt, vm::VMFunctionCallback}, entities::{ function::async_host::{AsyncFunctionEnv, AsyncHostFunction}, store::{AsStoreMut, AsStoreRef, StoreMut}, }, - sys::async_runtime::AsyncRuntimeError, + sys::{async_runtime::AsyncRuntimeError, function::env::AsyncFunctionEnvMutStore}, utils::{FromToNativeWasmType, IntoResult, NativeWasmTypeInto, WasmTypeList}, vm::{VMExtern, VMExternFunction}, }; @@ -156,33 +156,43 @@ impl Function { let store_id = store.objects_mut().id(); let wrapper = move |values_vec: *mut RawValue| -> HostCallOutcome { unsafe { - let mut store_wrapper = match StoreContext::try_get_current_async(store_id) { - crate::GetAsyncStoreGuardResult::Ok(wrapper) => wrapper, - _ => panic!( - "Sync store context encountered when attempting to \ - invoke async imported function" - ), - }; - let mut store_write_guard = store_wrapper.guard.as_mut().unwrap(); - let mut store = StoreMut { - inner: &mut **store_write_guard, + let mut context = StoreContext::try_get_current_async(store_id); + let mut store_mut = match &mut context { + crate::GetAsyncStoreGuardResult::Ok(wrapper) => StoreMut { + inner: &mut **wrapper.guard.as_mut().unwrap(), + }, + crate::GetAsyncStoreGuardResult::NotAsync(ptr) => ptr.as_mut(), + crate::GetAsyncStoreGuardResult::NotInstalled => { + panic!("No store context installed on this thread") + } }; - let id = store.as_store_ref().objects().id(); + let id = store_mut.as_store_ref().objects().id(); let mut args = Vec::with_capacity(func_ty.params().len()); for (i, ty) in func_ty.params().iter().enumerate() { args.push(Value::from_raw( - &mut store, + &mut store_mut, *ty, values_vec.add(i).read_unaligned(), )); } + let store_async = match context { + crate::GetAsyncStoreGuardResult::Ok(wrapper) => { + AsyncFunctionEnvMutStore::Async(StoreAsync { + id, + inner: crate::LocalRwLockWriteGuard::lock_handle( + wrapper.guard.as_mut().unwrap(), + ), + }) + } + crate::GetAsyncStoreGuardResult::NotAsync(ptr) => { + AsyncFunctionEnvMutStore::Sync(ptr) + } + crate::GetAsyncStoreGuardResult::NotInstalled => unreachable!(), + }; let env = crate::AsyncFunctionEnvMut(crate::BackendAsyncFunctionEnvMut::Sys( env::AsyncFunctionEnvMut { - store: crate::StoreAsync { - id, - inner: crate::LocalRwLockWriteGuard::lock_handle(store_write_guard), - }, + store: store_async, func_env: func_env.clone(), }, )); diff --git a/lib/api/src/entities/function/env/inner.rs b/lib/api/src/entities/function/env/inner.rs index de62dabb220..5a7b9baf854 100644 --- a/lib/api/src/entities/function/env/inner.rs +++ b/lib/api/src/entities/function/env/inner.rs @@ -199,6 +199,16 @@ impl BackendFunctionEnvMut<'_, T> { f.data_and_store_mut() }) } + + /// Creates an [`AsStoreAsync`] from this [`AsyncFunctionEnvMut`] if the current + /// context is async. + pub fn as_store_async(&self) -> Option { + match self { + #[cfg(feature = "sys")] + Self::Sys(f) => f.as_store_async(), + _ => unsupported_async_backend(), + } + } } impl AsStoreRef for BackendFunctionEnvMut<'_, T> { diff --git a/lib/api/src/entities/function/env/mod.rs b/lib/api/src/entities/function/env/mod.rs index aef63b6a1e0..fde4f9860ca 100644 --- a/lib/api/src/entities/function/env/mod.rs +++ b/lib/api/src/entities/function/env/mod.rs @@ -83,6 +83,12 @@ impl FunctionEnvMut<'_, T> { pub fn data_and_store_mut(&mut self) -> (&mut T, StoreMut<'_>) { self.0.data_and_store_mut() } + + /// Creates an [`AsStoreAsync`] from this [`AsyncFunctionEnvMut`] if the current + /// context is async. + pub fn as_store_async(&self) -> Option { + self.0.as_store_async() + } } impl AsStoreRef for FunctionEnvMut<'_, T> { diff --git a/lib/api/src/entities/store/async_.rs b/lib/api/src/entities/store/async_.rs index 31e1dd6037b..d9756458e03 100644 --- a/lib/api/src/entities/store/async_.rs +++ b/lib/api/src/entities/store/async_.rs @@ -76,7 +76,7 @@ pub(crate) enum AsyncStoreReadLockInner { /// A read lock on an async store. pub struct AsyncStoreReadLock<'a> { pub(crate) inner: AsyncStoreReadLockInner, - _marker: PhantomData<&'a ()>, + pub(crate) _marker: PhantomData<&'a ()>, } impl<'a> AsyncStoreReadLock<'a> { @@ -117,7 +117,7 @@ pub(crate) enum AsyncStoreWriteLockInner { /// A write lock on an async store. pub struct AsyncStoreWriteLock<'a> { pub(crate) inner: AsyncStoreWriteLockInner, - _marker: PhantomData<&'a ()>, + pub(crate) _marker: PhantomData<&'a ()>, } impl<'a> AsyncStoreWriteLock<'a> { diff --git a/lib/api/src/entities/store/context.rs b/lib/api/src/entities/store/context.rs index eee4921554b..9056a54f707 100644 --- a/lib/api/src/entities/store/context.rs +++ b/lib/api/src/entities/store/context.rs @@ -100,8 +100,8 @@ pub(crate) struct AsyncStoreGuardWrapper { pub(crate) enum GetAsyncStoreGuardResult { Ok(AsyncStoreGuardWrapper), + NotAsync(StorePtrWrapper), NotInstalled, - NotAsync, } pub(crate) struct ForcedStoreInstallGuard { @@ -231,14 +231,16 @@ impl StoreContext { if top.id != id { return GetAsyncStoreGuardResult::NotInstalled; } + top.borrow_count += 1; match unsafe { top.entry.get().as_mut().unwrap() } { StoreContextEntry::Async(guard) => { - top.borrow_count += 1; GetAsyncStoreGuardResult::Ok(AsyncStoreGuardWrapper { guard: guard as *mut _, }) } - StoreContextEntry::Sync(_) => GetAsyncStoreGuardResult::NotAsync, + StoreContextEntry::Sync(ptr) => { + GetAsyncStoreGuardResult::NotAsync(StorePtrWrapper { store_ptr: *ptr }) + } } }) } @@ -258,6 +260,25 @@ impl StorePtrWrapper { } } +impl Clone for StorePtrWrapper { + fn clone(&self) -> Self { + STORE_CONTEXT_STACK.with(|cell| { + let mut stack = cell.borrow_mut(); + let top = stack + .last_mut() + .expect("No store context installed on this thread"); + match unsafe { top.entry.get().as_ref().unwrap() } { + StoreContextEntry::Sync(ptr) if *ptr == self.store_ptr => (), + _ => panic!("Mismatched store context access"), + } + top.borrow_count += 1; + StorePtrWrapper { + store_ptr: self.store_ptr, + } + }) + } +} + impl Drop for StorePtrWrapper { fn drop(&mut self) { let id = self.as_mut().objects_mut().id(); diff --git a/lib/api/tests/jspi_async.rs b/lib/api/tests/jspi_async.rs index 094c5532b51..7fc52d769da 100644 --- a/lib/api/tests/jspi_async.rs +++ b/lib/api/tests/jspi_async.rs @@ -2,7 +2,7 @@ use std::{ cell::RefCell, pin::Pin, sync::OnceLock, - task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, + task::{Context, Poll}, }; use anyhow::Result; @@ -44,7 +44,7 @@ fn async_state_updates_follow_jspi_example() -> Result<()> { FunctionType::new(vec![], vec![Type::F64]), |_values| async move { // Note: future::ready doesn't actually suspend. It's important - // to note that, while we're in an async context here, it's + // to note that, while we're in an async import here, it's // impossible to suspend during module instantiation, which is // where this import is called. // To see this in action, uncomment the following line: @@ -66,8 +66,13 @@ fn async_state_updates_follow_jspi_example() -> Result<()> { &delta_env, FunctionType::new(vec![], vec![Type::F64]), |env: AsyncFunctionEnvMut, _values| async move { - let mut env_write = env.write().await; - let delta = env_write.data_mut().next(); + // Note: holding a lock across an await point prevents + // other coroutines from progressing, so it's a good + // idea to drop the lock before awaiting. + let delta = { + let mut env_write = env.write().await; + env_write.data_mut().next() + }; // We can, however, actually suspend whenever // `Function::call_async` is used to call WASM functions. tokio::time::sleep(std::time::Duration::from_millis(10)).await; @@ -177,54 +182,54 @@ fn typed_async_host_and_calls_work() -> Result<()> { Ok(()) } -// #[test] -// fn cannot_yield_when_not_in_async_context() -> Result<()> { -// const WAT: &str = r#" -// (module -// (import "env" "yield_now" (func $yield_now)) -// (func (export "yield_outside") -// call $yield_now -// ) -// ) -// "#; -// let wasm = wat::parse_str(WAT).expect("valid WAT module"); - -// let mut store = Store::default(); -// let module = Module::new(&store, wasm)?; - -// let yield_now = Function::new_async( -// &mut store, -// FunctionType::new(vec![], vec![]), -// |_values| async move { -// // Attempting to yield when not in an async context should trap. -// tokio::task::yield_now().await; -// Ok(vec![]) -// }, -// ); - -// let import_object = imports! { -// "env" => { -// "yield_now" => yield_now, -// } -// }; -// let instance = Instance::new(&mut store, &module, &import_object)?; -// let yield_outside = instance.exports.get_function("yield_outside")?; - -// let trap = yield_outside -// .call(&mut store, &[]) -// .expect_err("expected trap calling yield outside async context"); - -// // TODO: wasm trace generation appears to be broken? -// // assert!(!trap.trace().is_empty(), "should have a stack trace"); -// let trap_code = trap.to_trap().expect("expected trap code"); -// assert_eq!( -// trap_code, -// TrapCode::YieldOutsideAsyncContext, -// "expected YieldOutsideAsyncContext trap code" -// ); - -// Ok(()) -// } +#[test] +fn cannot_yield_when_not_in_async_context() -> Result<()> { + const WAT: &str = r#" + (module + (import "env" "yield_now" (func $yield_now)) + (func (export "yield_outside") + call $yield_now + ) + ) + "#; + let wasm = wat::parse_str(WAT).expect("valid WAT module"); + + let mut store = Store::default(); + let module = Module::new(&store, wasm)?; + + let yield_now = Function::new_async( + &mut store, + FunctionType::new(vec![], vec![]), + |_values| async move { + // Attempting to yield when not in an async context should trap. + tokio::task::yield_now().await; + Ok(vec![]) + }, + ); + + let import_object = imports! { + "env" => { + "yield_now" => yield_now, + } + }; + let instance = Instance::new(&mut store, &module, &import_object)?; + let yield_outside = instance.exports.get_function("yield_outside")?; + + let trap = yield_outside + .call(&mut store, &[]) + .expect_err("expected trap calling yield outside async context"); + + // TODO: wasm trace generation appears to be broken? + // assert!(!trap.trace().is_empty(), "should have a stack trace"); + let trap_code = trap.to_trap().expect("expected trap code"); + assert_eq!( + trap_code, + TrapCode::YieldOutsideAsyncContext, + "expected YieldOutsideAsyncContext trap code" + ); + + Ok(()) +} /* This test is slightly weird to explain; what we're testing here is that multiple coroutines can be active at the same time, @@ -339,43 +344,42 @@ fn async_multiple_active_coroutines() -> Result<()> { }); } - let spawn_future = Function::new_with_env_async( + let spawn_future = Function::new_with_env( &mut store, &mut env, FunctionType::new(vec![Type::I32], vec![]), |env, values| { - let future_id = values[0].unwrap_i32(); + ENV.with(move |data| { + let future_id = values[0].unwrap_i32(); - async move { - ENV.with(move |data| { - let store_async = env.as_store_async(); + // As long as we're in an async context, we can create an + // AsStoreAsync even from a sync FunctionEnvMut. + let store_async = env.as_store_async().expect("Not in an async context"); - log(future_id + 10); + log(future_id + 10); - data.borrow_mut().yielders[future_id as usize] = - Some(Yielder { yielded: false }); + data.borrow_mut().yielders[future_id as usize] = Some(Yielder { yielded: false }); - // This spawns the coroutine and the corresponding future - let func = data.borrow().future_func.as_ref().unwrap().clone(); - let mut future = Box::pin(async move { - func.call_async(&store_async, &[Value::I32(future_id)]) - .await - }); - log(future_id + 20); + // This spawns the coroutine and the corresponding future + let func = data.borrow().future_func.as_ref().unwrap().clone(); + let mut future = Box::pin(async move { + func.call_async(&store_async, &[Value::I32(future_id)]) + .await + }); + log(future_id + 20); - // We then poll it once to get it started - it'll suspend once, then - // complete the next time we poll it - let w = futures::task::noop_waker(); - let mut cx = Context::from_waker(&w); - assert!(future.as_mut().poll(&mut cx).is_pending()); + // We then poll it once to get it started - it'll suspend once, then + // complete the next time we poll it + let w = futures::task::noop_waker(); + let mut cx = Context::from_waker(&w); + assert!(future.as_mut().poll(&mut cx).is_pending()); - log(future_id + 50); - // We then store the future without letting it complete, and return - data.borrow_mut().futures[future_id as usize] = Some(future); + log(future_id + 50); + // We then store the future without letting it complete, and return + data.borrow_mut().futures[future_id as usize] = Some(future); - Ok(vec![]) - }) - } + Ok(vec![]) + }) }, ); @@ -399,33 +403,31 @@ fn async_multiple_active_coroutines() -> Result<()> { }, ); - let resolve_future = Function::new_async( + let resolve_future = Function::new( &mut store, FunctionType::new(vec![Type::I32], vec![]), |values| { - let future_id = values[0].unwrap_i32(); + ENV.with(|data| { + let future_id = values[0].unwrap_i32(); - async move { - ENV.with(|data| { - log(future_id + 60); + log(future_id + 60); - let mut future = data.borrow_mut().futures[future_id as usize] - .take() - .unwrap(); + let mut future = data.borrow_mut().futures[future_id as usize] + .take() + .unwrap(); - let w = futures::task::noop_waker(); - let mut cx = Context::from_waker(&w); - let Poll::Ready(result) = future.as_mut().poll(&mut cx) else { - panic!("expected future to be ready"); - }; - let result_id = result.unwrap()[0].unwrap_i32(); - assert_eq!(result_id, future_id); + let w = futures::task::noop_waker(); + let mut cx = Context::from_waker(&w); + let Poll::Ready(result) = future.as_mut().poll(&mut cx) else { + panic!("expected future to be ready"); + }; + let result_id = result.unwrap()[0].unwrap_i32(); + assert_eq!(result_id, future_id); - log(future_id + 80); + log(future_id + 80); - Ok(vec![]) - }) - } + Ok(vec![]) + }) }, ); From 174a5c771aec56a3c70ed0aa3407879610bd9a9d Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Thu, 27 Nov 2025 21:26:54 +0400 Subject: [PATCH 30/49] Add read() and write() methods to StoreAsync, fix greenthread test --- Cargo.lock | 12 ---------- Cargo.toml | 2 -- lib/api/src/entities/store/async_.rs | 34 +++++++++++++++++++++++++-- lib/api/src/entities/store/context.rs | 8 +++++++ lib/api/tests/simple_greenthread.rs | 9 +++++-- 5 files changed, 47 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f6d3b611a7b..5c92ef4c035 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -278,17 +278,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "async-lock" -version = "3.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" -dependencies = [ - "event-listener", - "event-listener-strategy", - "pin-project-lite", -] - [[package]] name = "async-stream" version = "0.3.6" @@ -7743,7 +7732,6 @@ name = "wasmer-workspace" version = "6.1.0" dependencies = [ "anyhow", - "async-lock", "build-deps", "cfg-if", "clap", diff --git a/Cargo.toml b/Cargo.toml index a3aec1b3f5a..f0256ed8dcc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,6 @@ tokio = { version = "1.39", features = [ "macros", ], optional = true } crossbeam-queue = "0.3.8" -async-lock.workspace = true [workspace] members = [ @@ -177,7 +176,6 @@ clap_builder = { version = "=4.5.50" } clap_derive = { version = "=4.5.49" } clap_lex = { version = "=0.7.6" } fs_extra = { version = "1.3.0" } -async-lock = { version = "=3.4.1" } [build-dependencies] test-generator = { path = "tests/lib/test-generator" } diff --git a/lib/api/src/entities/store/async_.rs b/lib/api/src/entities/store/async_.rs index d9756458e03..8e0b9fab7f8 100644 --- a/lib/api/src/entities/store/async_.rs +++ b/lib/api/src/entities/store/async_.rs @@ -16,8 +16,7 @@ pub struct StoreAsync { impl StoreAsync { /// Transform this [`StoreAsync`] back into a [`Store`] - /// if there are no coroutines running or waiting to run - /// against it. + /// if this is the only clone of it and is unlocked. pub fn into_store(self) -> Result { match self.inner.consume() { Ok(unwrapped) => Ok(Store { @@ -29,6 +28,37 @@ impl StoreAsync { }), } } + + /// Acquire a read lock on the store. Panics if the store is + /// locked for writing. + pub fn read<'a>(&'a self) -> AsyncStoreReadLock<'a> { + if !StoreContext::is_empty() { + panic!("This method cannot be called from inside imported functions"); + } + + let store_ref = self + .inner + .try_read() + .expect("StoreAsync is locked for write"); + AsyncStoreReadLock { + inner: AsyncStoreReadLockInner::Owned(store_ref), + _marker: PhantomData, + } + } + + /// Acquire a write lock on the store. Panics if the store is + /// locked. + pub fn write<'a>(&'a self) -> AsyncStoreWriteLock<'a> { + if !StoreContext::is_empty() { + panic!("This method cannot be called from inside imported functions"); + } + + let store_guard = self.inner.try_write().expect("StoreAsync is locked"); + AsyncStoreWriteLock { + inner: AsyncStoreWriteLockInner::Owned(store_guard), + _marker: PhantomData, + } + } } /// A trait for types that can be used with diff --git a/lib/api/src/entities/store/context.rs b/lib/api/src/entities/store/context.rs index 9056a54f707..dd86f9ece34 100644 --- a/lib/api/src/entities/store/context.rs +++ b/lib/api/src/entities/store/context.rs @@ -144,6 +144,14 @@ impl StoreContext { }) } + /// Returns true if there are no active store context entries. + pub(crate) fn is_empty() -> bool { + STORE_CONTEXT_STACK.with(|cell| { + let stack = cell.borrow(); + stack.is_empty() + }) + } + /// The write guard ensures this is the only reference to the store, /// so installation can never fail. pub(crate) fn install_async( diff --git a/lib/api/tests/simple_greenthread.rs b/lib/api/tests/simple_greenthread.rs index c471f72997e..5f8cc155278 100644 --- a/lib/api/tests/simple_greenthread.rs +++ b/lib/api/tests/simple_greenthread.rs @@ -211,8 +211,13 @@ fn run_greenthread_test(wat: &[u8]) -> Result> { .run_until(main_fn.call_async(&store_async, &[])) .unwrap(); - let mut store = store_async.into_store().ok().unwrap(); - return Ok(env.as_ref(&mut store).logs.clone()); + // If there are no more clones of it, StoreAsync can also be + // turned back into a store. We need to drop the local pool + // first to drop the futures and their references to the store. + drop(localpool); + + let store = store_async.into_store().ok().unwrap(); + return Ok(env.as_ref(&store).logs.clone()); } #[cfg(not(target_arch = "wasm32"))] From f43966ede73c87598ceb8deadbb0d48dc973e669 Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Fri, 28 Nov 2025 17:15:40 +0400 Subject: [PATCH 31/49] Add `AsStoreRef::as_store_async` --- .../src/backend/sys/entities/function/env.rs | 19 ++++++------------- lib/api/src/entities/store/async_.rs | 14 ++++++++++++++ lib/api/src/entities/store/store_ref.rs | 15 ++++++++++++++- 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/lib/api/src/backend/sys/entities/function/env.rs b/lib/api/src/backend/sys/entities/function/env.rs index e96cc47c61c..06ea196dd54 100644 --- a/lib/api/src/backend/sys/entities/function/env.rs +++ b/lib/api/src/backend/sys/entities/function/env.rs @@ -166,20 +166,13 @@ impl FunctionEnvMut<'_, T> { (data, self.store_mut.as_store_mut()) } + /// Returns a [`StoreAsync`] if the current + /// context is asynchronous. The store will be locked since + /// it's already active in the current context, but can be used + /// to spawn new coroutines via + /// [`Function::call_async`](crate::Function::call_async). pub fn as_store_async(&self) -> Option { - let id = self.store_mut.inner.objects.id(); - - // Safety: we don't keep the guard around, it's just used to - // build a safe lock handle. - match unsafe { StoreContext::try_get_current_async(id) } { - crate::GetAsyncStoreGuardResult::Ok(guard) => Some(StoreAsync { - id, - inner: crate::LocalRwLockWriteGuard::lock_handle(unsafe { - guard.guard.as_ref().unwrap() - }), - }), - _ => None, - } + self.store_mut.as_store_async() } } diff --git a/lib/api/src/entities/store/async_.rs b/lib/api/src/entities/store/async_.rs index 8e0b9fab7f8..6af6bd3c581 100644 --- a/lib/api/src/entities/store/async_.rs +++ b/lib/api/src/entities/store/async_.rs @@ -15,6 +15,20 @@ pub struct StoreAsync { } impl StoreAsync { + pub(crate) fn from_context(id: StoreId) -> Option { + // Safety: we don't keep the guard around, it's just used to + // build a safe lock handle. + match unsafe { StoreContext::try_get_current_async(id) } { + crate::GetAsyncStoreGuardResult::Ok(guard) => Some(StoreAsync { + id, + inner: crate::LocalRwLockWriteGuard::lock_handle(unsafe { + guard.guard.as_ref().unwrap() + }), + }), + _ => None, + } + } + /// Transform this [`StoreAsync`] back into a [`Store`] /// if this is the only clone of it and is unlocked. pub fn into_store(self) -> Result { diff --git a/lib/api/src/entities/store/store_ref.rs b/lib/api/src/entities/store/store_ref.rs index 6ec36ece852..da3074687a1 100644 --- a/lib/api/src/entities/store/store_ref.rs +++ b/lib/api/src/entities/store/store_ref.rs @@ -1,7 +1,10 @@ use std::ops::{Deref, DerefMut}; use super::{StoreObjects, inner::StoreInner}; -use crate::entities::engine::{AsEngineRef, Engine, EngineRef}; +use crate::{ + AsStoreAsync, StoreAsync, + entities::engine::{AsEngineRef, Engine, EngineRef}, +}; use wasmer_types::{ExternType, OnCalledAction}; //use wasmer_vm::{StoreObjects, TrapHandlerFn}; @@ -90,6 +93,16 @@ impl StoreMut<'_> { pub trait AsStoreRef { /// Returns a `StoreRef` pointing to the underlying context. fn as_store_ref(&self) -> StoreRef<'_>; + + /// Returns a [`StoreAsync`] if the current + /// context is asynchronous. The store will be locked since + /// it's already active in the current context, but can be used + /// to spawn new coroutines via + /// [`Function::call_async`](crate::Function::call_async). + fn as_store_async(&self) -> Option { + let id = self.as_store_ref().inner.objects.id(); + StoreAsync::from_context(id) + } } /// Helper trait for a value that is convertible to a [`StoreMut`]. From 1956e0f3c8fbee757989ba23a60809eafab49e41 Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Fri, 28 Nov 2025 17:53:20 +0400 Subject: [PATCH 32/49] Fix lint errors --- examples/throw_exception.rs | 9 +-- .../src/backend/js/entities/function/mod.rs | 19 ++---- .../src/backend/jsc/entities/function/mod.rs | 13 +--- lib/api/src/backend/sys/async_runtime.rs | 26 ++++---- .../src/backend/sys/entities/function/mod.rs | 45 +++++-------- .../backend/sys/entities/function/typed.rs | 4 +- .../src/backend/v8/entities/function/mod.rs | 23 ++----- .../src/backend/wamr/entities/function/mod.rs | 23 ++----- .../backend/wasmi/entities/function/mod.rs | 23 ++----- lib/api/src/entities/function/env/inner.rs | 8 +-- lib/api/src/entities/function/env/mod.rs | 4 +- lib/api/src/entities/function/inner.rs | 36 +++++------ lib/api/src/entities/function/mod.rs | 27 ++++---- lib/api/src/entities/store/async_.rs | 6 +- lib/api/src/entities/store/context.rs | 17 ++--- lib/api/src/entities/store/local_rwlock.rs | 6 +- lib/api/tests/instance.rs | 2 +- lib/api/tests/jspi_async.rs | 2 +- lib/api/tests/reference_types.rs | 5 +- .../src/wasm_c_api/externals/function.rs | 63 ++++++++++--------- lib/cli/src/commands/run/mod.rs | 6 +- lib/compiler-singlepass/src/codegen.rs | 10 +-- lib/wasix/src/bin_factory/exec.rs | 6 +- 23 files changed, 152 insertions(+), 231 deletions(-) diff --git a/examples/throw_exception.rs b/examples/throw_exception.rs index 6c528ed9eaa..037989f8833 100644 --- a/examples/throw_exception.rs +++ b/examples/throw_exception.rs @@ -1,8 +1,8 @@ use std::sync::{Arc, atomic::AtomicUsize}; use wasmer::{ - Function, FunctionEnv, FunctionEnvMut, FunctionType, Instance, Module, RuntimeError, Store, - Type, TypedFunction, Value, imports, sys::Target, wat2wasm, + DynamicFunctionResult, Function, FunctionEnv, FunctionEnvMut, FunctionType, Instance, Module, + RuntimeError, Store, Type, TypedFunction, Value, imports, sys::Target, wat2wasm, }; use wasmer_types::Features; @@ -83,10 +83,7 @@ fn main() -> Result<(), Box> { // Both Function::new_with_env and Function::new_typed_with_env can throw // exceptions if they return a Result with a RuntimeError. - fn throw1( - mut env: FunctionEnvMut, - _params: &[Value], - ) -> Result, RuntimeError> { + fn throw1(mut env: FunctionEnvMut, _params: &[Value]) -> DynamicFunctionResult { println!("Throwing exception 1"); // To "throw" an exception from native code, we create a new one and diff --git a/lib/api/src/backend/js/entities/function/mod.rs b/lib/api/src/backend/js/entities/function/mod.rs index d69b21d4243..43b658ae1dc 100644 --- a/lib/api/src/backend/js/entities/function/mod.rs +++ b/lib/api/src/backend/js/entities/function/mod.rs @@ -10,9 +10,9 @@ use wasmer_types::{FunctionType, RawValue}; use crate::{ AsStoreMut, AsStoreRef, BackendFunction, BackendFunctionEnv, BackendFunctionEnvMut, - FromToNativeWasmType, FunctionEnv, FunctionEnvMut, HostFunction, HostFunctionKind, IntoResult, - NativeWasmType, NativeWasmTypeInto, RuntimeError, StoreMut, Value, WasmTypeList, WithEnv, - WithoutEnv, + DynamicFunctionResult, FromToNativeWasmType, FunctionEnv, FunctionEnvMut, HostFunction, + HostFunctionKind, IntoResult, NativeWasmType, NativeWasmTypeInto, RuntimeError, StoreMut, + Value, WasmTypeList, WithEnv, WithoutEnv, js::{ utils::convert::{AsJs as _, js_value_to_wasmer, wasmer_value_to_js}, vm::{VMFuncRef, VMFunctionCallback, function::VMFunction}, @@ -59,10 +59,7 @@ impl Function { ) -> Self where FT: Into, - F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> Result, RuntimeError> - + 'static - + Send - + Sync, + F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> DynamicFunctionResult + 'static + Send + Sync, { let mut store = store.as_store_mut(); let function_type = ty.into(); @@ -191,18 +188,14 @@ impl Function { &self, _store: &mut impl AsStoreMut, _params: Vec, - ) -> Result, RuntimeError> { + ) -> DynamicCallResult { // There is no optimal call_raw in JS, so we just // simply rely the call // self.call(store, params) unimplemented!(); } - pub fn call( - &self, - store: &mut impl AsStoreMut, - params: &[Value], - ) -> Result, RuntimeError> { + pub fn call(&self, store: &mut impl AsStoreMut, params: &[Value]) -> DynamicCallResult { // Annotation is here to prevent spurious IDE warnings. let arr = js_sys::Array::new_with_length(params.len() as u32); diff --git a/lib/api/src/backend/jsc/entities/function/mod.rs b/lib/api/src/backend/jsc/entities/function/mod.rs index fb315e5d855..9570b89b0b9 100644 --- a/lib/api/src/backend/jsc/entities/function/mod.rs +++ b/lib/api/src/backend/jsc/entities/function/mod.rs @@ -57,10 +57,7 @@ impl Function { ) -> Self where FT: Into, - F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> Result, RuntimeError> - + 'static - + Send - + Sync, + F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> DynamicFunctionResult + 'static + Send + Sync, { let store = store.as_store_mut(); let context = store.jsc().context(); @@ -182,18 +179,14 @@ impl Function { &self, _store: &mut impl AsStoreMut, _params: Vec, - ) -> Result, RuntimeError> { + ) -> DynamicCallResult { // There is no optimal call_raw in JSC, so we just // simply rely the call // self.call(store, params) unimplemented!(); } - pub fn call( - &self, - store: &mut impl AsStoreMut, - params: &[Value], - ) -> Result, RuntimeError> { + pub fn call(&self, store: &mut impl AsStoreMut, params: &[Value]) -> DynamicCallResult { let store_mut = store.as_store_mut(); let engine = store_mut.engine(); let context = engine.as_jsc().context(); diff --git a/lib/api/src/backend/sys/async_runtime.rs b/lib/api/src/backend/sys/async_runtime.rs index 60abc292fbd..10dd2ed604a 100644 --- a/lib/api/src/backend/sys/async_runtime.rs +++ b/lib/api/src/backend/sys/async_runtime.rs @@ -13,12 +13,12 @@ use corosensei::{Coroutine, CoroutineResult, Yielder}; use super::entities::function::Function as SysFunction; use crate::{ - AsStoreMut, AsStoreRef, LocalRwLockWriteGuard, RuntimeError, Store, StoreAsync, StoreContext, - StoreInner, StoreMut, StoreRef, Value, + AsStoreMut, AsStoreRef, DynamicCallResult, DynamicFunctionResult, LocalRwLockWriteGuard, + RuntimeError, Store, StoreAsync, StoreContext, StoreInner, StoreMut, StoreRef, Value, }; use wasmer_types::StoreId; -type HostFuture = Pin, RuntimeError>> + 'static>>; +type HostFuture = Pin + 'static>>; pub(crate) fn call_function_async<'a>( function: SysFunction, @@ -32,15 +32,15 @@ struct AsyncYield(HostFuture); enum AsyncResume { Start, - HostFutureReady(Result, RuntimeError>), + HostFutureReady(DynamicFunctionResult), } pub(crate) struct AsyncCallFuture<'a> { - coroutine: Option, RuntimeError>>>, + coroutine: Option>, pending_store_install: Option + 'a>>>, pending_future: Option, next_resume: Option, - result: Option, RuntimeError>>, + result: Option, // Store handle we can use to lock the store down store: StoreAsync, @@ -129,7 +129,7 @@ impl<'a> AsyncCallFuture<'a> { } impl Future for AsyncCallFuture<'_> { - type Output = Result, RuntimeError>; + type Output = DynamicCallResult; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { loop { @@ -149,7 +149,7 @@ impl Future for AsyncCallFuture<'_> { } // Start a store installation if not in progress already - if let None = self.pending_store_install { + if self.pending_store_install.is_none() { self.pending_store_install = Some(Box::pin(StoreContextInstaller::install(StoreAsync { id: self.store.id, @@ -217,13 +217,13 @@ impl StoreContextInstaller { } crate::GetAsyncStoreGuardResult::Ok(wrapper) => { // If we're already in the scope of this store, we can just reuse it. - StoreContextInstaller::FromThreadContext(wrapper) + Self::FromThreadContext(wrapper) } crate::GetAsyncStoreGuardResult::NotInstalled => { // Otherwise, need to acquire a new StoreMut. let store_guard = store.inner.write().await; let install_guard = unsafe { crate::StoreContext::install_async(store_guard) }; - StoreContextInstaller::Installed(install_guard) + Self::Installed(install_guard) } } } @@ -236,7 +236,7 @@ pub enum AsyncRuntimeError { pub(crate) fn block_on_host_future(future: Fut) -> Result, AsyncRuntimeError> where - Fut: Future, RuntimeError>> + 'static, + Fut: Future + 'static, { CURRENT_CONTEXT.with(|cell| { match CoroutineContext::get_current() { @@ -299,7 +299,7 @@ impl CoroutineContext { CURRENT_CONTEXT.with(|cell| cell.borrow().last().copied()) } - fn block_on_future(&self, future: HostFuture) -> Result, RuntimeError> { + fn block_on_future(&self, future: HostFuture) -> DynamicFunctionResult { // Leave the coroutine context since we're yielding back to the // parent stack, and will be inactive until the future is ready. self.leave(); @@ -319,7 +319,7 @@ impl CoroutineContext { } fn run_immediate( - future: impl Future, RuntimeError>> + 'static, + future: impl Future + 'static, ) -> Result, AsyncRuntimeError> { let waker = futures::task::noop_waker(); let mut cx = Context::from_waker(&waker); diff --git a/lib/api/src/backend/sys/entities/function/mod.rs b/lib/api/src/backend/sys/entities/function/mod.rs index c7e1bdc21a0..270ccdde753 100644 --- a/lib/api/src/backend/sys/entities/function/mod.rs +++ b/lib/api/src/backend/sys/entities/function/mod.rs @@ -4,9 +4,9 @@ pub(crate) mod env; pub(crate) mod typed; use crate::{ - AsStoreAsync, AsyncFunctionEnvMut, BackendAsyncFunctionEnvMut, BackendFunction, FunctionEnv, - FunctionEnvMut, FunctionType, HostFunction, RuntimeError, StoreAsync, StoreContext, StoreInner, - Value, WithEnv, WithoutEnv, + AsStoreAsync, AsyncFunctionEnvMut, BackendAsyncFunctionEnvMut, BackendFunction, + DynamicCallResult, DynamicFunctionResult, FunctionEnv, FunctionEnvMut, FunctionType, + HostFunction, RuntimeError, StoreAsync, StoreContext, StoreInner, Value, WithEnv, WithoutEnv, backend::sys::{engine::NativeEngineExt, vm::VMFunctionCallback}, entities::{ function::async_host::{AsyncFunctionEnv, AsyncHostFunction}, @@ -53,10 +53,7 @@ impl Function { ) -> Self where FT: Into, - F: Fn(FunctionEnvMut, &[Value]) -> Result, RuntimeError> - + 'static - + Send - + Sync, + F: Fn(FunctionEnvMut, &[Value]) -> DynamicFunctionResult + 'static + Send + Sync, { let function_type = ty.into(); let func_ty = function_type.clone(); @@ -132,7 +129,7 @@ impl Function { where FT: Into, F: Fn(&[Value]) -> Fut + 'static, - Fut: Future, RuntimeError>> + 'static, + Fut: Future + 'static, { let env = FunctionEnv::new(store, ()); let wrapped = move |_env: AsyncFunctionEnvMut<()>, values: &[Value]| func(values); @@ -148,7 +145,7 @@ impl Function { where FT: Into, F: Fn(AsyncFunctionEnvMut, &[Value]) -> Fut + 'static, - Fut: Future, RuntimeError>> + 'static, + Fut: Future + 'static, { let function_type = ty.into(); let func_ty = function_type.clone(); @@ -159,7 +156,7 @@ impl Function { let mut context = StoreContext::try_get_current_async(store_id); let mut store_mut = match &mut context { crate::GetAsyncStoreGuardResult::Ok(wrapper) => StoreMut { - inner: &mut **wrapper.guard.as_mut().unwrap(), + inner: wrapper.guard.as_mut().unwrap(), }, crate::GetAsyncStoreGuardResult::NotAsync(ptr) => ptr.as_mut(), crate::GetAsyncStoreGuardResult::NotInstalled => { @@ -211,7 +208,7 @@ impl Function { store_id, }, }); - host_data.address = host_data.ctx.func_body_ptr() as *const VMFunctionBody; + host_data.address = host_data.ctx.func_body_ptr(); let func_ptr = std::ptr::null() as VMFunctionCallback; let type_index = store @@ -300,9 +297,7 @@ impl Function { store, &env, signature, - move |mut env_mut, - values| - -> Pin, RuntimeError>>>> { + move |mut env_mut, values| -> Pin>> { let sys_env = match env_mut.0 { BackendAsyncFunctionEnvMut::Sys(ref mut sys_env) => sys_env, _ => panic!("Not a sys backend"), @@ -353,9 +348,7 @@ impl Function { store, env, signature, - move |mut env_mut, - values| - -> Pin, RuntimeError>>>> { + move |mut env_mut, values| -> Pin>> { let sys_env = match env_mut.0 { BackendAsyncFunctionEnvMut::Sys(ref mut sys_env) => sys_env, _ => panic!("Not a sys backend"), @@ -569,11 +562,7 @@ impl Function { self.ty(store).results().len() } - pub(crate) fn call( - &self, - store: &mut impl AsStoreMut, - params: &[Value], - ) -> Result, RuntimeError> { + pub(crate) fn call(&self, store: &mut impl AsStoreMut, params: &[Value]) -> DynamicCallResult { let trampoline = unsafe { self.handle .get(store.objects_mut().as_sys()) @@ -591,7 +580,7 @@ impl Function { &self, store: &'a impl AsStoreAsync, params: Vec, - ) -> Pin, RuntimeError>> + 'a>> { + ) -> Pin + 'a>> { let function = self.clone(); let store = store.store(); Box::pin(call_function_async(function, store, params)) @@ -603,7 +592,7 @@ impl Function { &self, store: &mut impl AsStoreMut, params: Vec, - ) -> Result, RuntimeError> { + ) -> DynamicCallResult { let trampoline = unsafe { self.handle .get(store.objects_mut().as_sys()) @@ -724,7 +713,7 @@ fn finalize_dynamic_call( store_id: StoreId, func_ty: FunctionType, values_vec: *mut RawValue, - result: Result, RuntimeError>, + result: DynamicFunctionResult, ) -> Result<(), RuntimeError> { match result { Ok(values) => write_dynamic_results(store_id, &func_ty, values, values_vec), @@ -766,7 +755,7 @@ fn typed_results_to_values( store: &mut StoreMut, func_ty: &FunctionType, rets: Rets, -) -> Result, RuntimeError> +) -> DynamicFunctionResult where Rets: WasmTypeList, { @@ -783,11 +772,11 @@ where pub(crate) enum HostCallOutcome { Ready { func_ty: FunctionType, - result: Result, RuntimeError>, + result: DynamicFunctionResult, }, Future { func_ty: FunctionType, - future: Pin, RuntimeError>>>>, + future: Pin>>, }, } diff --git a/lib/api/src/backend/sys/entities/function/typed.rs b/lib/api/src/backend/sys/entities/function/typed.rs index c48765d7d59..0d96a2a17c5 100644 --- a/lib/api/src/backend/sys/entities/function/typed.rs +++ b/lib/api/src/backend/sys/entities/function/typed.rs @@ -139,7 +139,7 @@ macro_rules! impl_native_traits { let results = func.call_async(store, ¶ms_values).await?; let mut write = store.write_lock().await; - convert_results::(&mut write, func_ty, results) + convert_results::(&mut write, func_ty, &results) } } @@ -274,7 +274,7 @@ impl_native_traits!( fn convert_results( store: &mut impl AsStoreMut, ty: FunctionType, - results: Box<[Value]>, + results: &[Value], ) -> Result where Rets: WasmTypeList, diff --git a/lib/api/src/backend/v8/entities/function/mod.rs b/lib/api/src/backend/v8/entities/function/mod.rs index fd5fe497e6a..e11da467391 100644 --- a/lib/api/src/backend/v8/entities/function/mod.rs +++ b/lib/api/src/backend/v8/entities/function/mod.rs @@ -80,10 +80,7 @@ impl Function { ) -> Self where FT: Into, - F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> Result, RuntimeError> - + 'static - + Send - + Sync, + F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> DynamicFunctionResult + 'static + Send + Sync, { check_isolate(store); @@ -338,18 +335,14 @@ impl Function { &self, _store: &mut impl AsStoreMut, _params: Vec, - ) -> Result, RuntimeError> { + ) -> DynamicCallResult { // There is no optimal call_raw in JSC, so we just // simply rely the call // self.call(store, params) unimplemented!(); } - pub fn call( - &self, - store: &mut impl AsStoreMut, - params: &[Value], - ) -> Result, RuntimeError> { + pub fn call(&self, store: &mut impl AsStoreMut, params: &[Value]) -> DynamicCallResult { check_isolate(store); // unimplemented!(); let store_mut = store.as_store_mut(); @@ -438,10 +431,7 @@ impl Function { fn make_fn_callback(func: &F, args: usize) -> CCallback where - F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> Result, RuntimeError> - + 'static - + Send - + Sync, + F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> DynamicFunctionResult + 'static + Send + Sync, { unsafe extern "C" fn fn_callback( env: *mut c_void, @@ -449,10 +439,7 @@ where rets: *mut wasm_val_vec_t, ) -> *mut wasm_trap_t where - F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> Result, RuntimeError> - + 'static - + Send - + Sync, + F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> DynamicFunctionResult + 'static + Send + Sync, { let r: *mut (FunctionCallbackEnv<'_, F>) = env as _; diff --git a/lib/api/src/backend/wamr/entities/function/mod.rs b/lib/api/src/backend/wamr/entities/function/mod.rs index 903cbad7025..c8d67fe859d 100644 --- a/lib/api/src/backend/wamr/entities/function/mod.rs +++ b/lib/api/src/backend/wamr/entities/function/mod.rs @@ -81,10 +81,7 @@ impl Function { ) -> Self where FT: Into, - F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> Result, RuntimeError> - + 'static - + Send - + Sync, + F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> DynamicFunctionResult + Send + Sync, { let fn_ty: FunctionType = ty.into(); let params = fn_ty.params(); @@ -344,18 +341,14 @@ impl Function { &self, _store: &mut impl AsStoreMut, _params: Vec, - ) -> Result, RuntimeError> { + ) -> DynamicCallResult { // There is no optimal call_raw in JSC, so we just // simply rely the call // self.call(store, params) unimplemented!(); } - pub fn call( - &self, - store: &mut impl AsStoreMut, - params: &[Value], - ) -> Result, RuntimeError> { + pub fn call(&self, store: &mut impl AsStoreMut, params: &[Value]) -> DynamicCallResult { // unimplemented!(); let store_mut = store.as_store_mut(); // let wasm_func_param_arity(self.handle) @@ -441,10 +434,7 @@ impl Function { fn make_fn_callback(func: &F, args: usize) -> CCallback where - F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> Result, RuntimeError> - + 'static - + Send - + Sync, + F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> DynamicFunctionResult + 'static + Send + Sync, { unsafe extern "C" fn fn_callback( env: *mut c_void, @@ -452,10 +442,7 @@ where rets: *mut wasm_val_vec_t, ) -> *mut wasm_trap_t where - F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> Result, RuntimeError> - + 'static - + Send - + Sync, + F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> DynamicFunctionResult + 'static + Send + Sync, { let r: *mut (FunctionCallbackEnv<'_, F>) = env as _; diff --git a/lib/api/src/backend/wasmi/entities/function/mod.rs b/lib/api/src/backend/wasmi/entities/function/mod.rs index 7526fd56b20..7f0422d8ca7 100644 --- a/lib/api/src/backend/wasmi/entities/function/mod.rs +++ b/lib/api/src/backend/wasmi/entities/function/mod.rs @@ -81,10 +81,7 @@ impl Function { ) -> Self where FT: Into, - F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> Result, RuntimeError> - + 'static - + Send - + Sync, + F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> DynamicFunctionResult + 'static + Send + Sync, { let fn_ty: FunctionType = ty.into(); let params = fn_ty.params(); @@ -340,18 +337,14 @@ impl Function { &self, _store: &mut impl AsStoreMut, _params: Vec, - ) -> Result, RuntimeError> { + ) -> DynamicCallResult { // There is no optimal call_raw in JSC, so we just // simply rely the call // self.call(store, params) unimplemented!(); } - pub fn call( - &self, - store: &mut impl AsStoreMut, - params: &[Value], - ) -> Result, RuntimeError> { + pub fn call(&self, store: &mut impl AsStoreMut, params: &[Value]) -> DynamicCallResult { // unimplemented!(); let store_mut = store.as_store_mut(); // let wasm_func_param_arity(self.handle) @@ -442,10 +435,7 @@ impl Function { fn make_fn_callback(func: &F, args: usize) -> CCallback where - F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> Result, RuntimeError> - + 'static - + Send - + Sync, + F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> DynamicFunctionResult + 'static + Send + Sync, { unsafe extern "C" fn fn_callback( env: *mut c_void, @@ -453,10 +443,7 @@ where rets: *mut wasm_val_vec_t, ) -> *mut wasm_trap_t where - F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> Result, RuntimeError> - + 'static - + Send - + Sync, + F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> DynamicFunctionResult + 'static + Send + Sync, { let r: *mut (FunctionCallbackEnv<'_, F>) = env as _; diff --git a/lib/api/src/entities/function/env/inner.rs b/lib/api/src/entities/function/env/inner.rs index 5a7b9baf854..10c5a388f61 100644 --- a/lib/api/src/entities/function/env/inner.rs +++ b/lib/api/src/entities/function/env/inner.rs @@ -291,20 +291,20 @@ impl BackendAsyncFunctionEnvMut { } } - /// Borrows a new immmutable reference + /// Borrows a new immutable reference pub fn as_ref(&self) -> BackendFunctionEnv { match self { #[cfg(feature = "sys")] - Self::Sys(f) => BackendFunctionEnv::Sys(f.as_ref()).into(), + Self::Sys(f) => BackendFunctionEnv::Sys(f.as_ref()), _ => unsupported_async_backend(), } } /// Borrows a new mutable reference - pub fn as_mut(&mut self) -> BackendAsyncFunctionEnvMut { + pub fn as_mut(&mut self) -> Self { match self { #[cfg(feature = "sys")] - Self::Sys(f) => BackendAsyncFunctionEnvMut::Sys(f.as_mut()), + Self::Sys(f) => Self::Sys(f.as_mut()), _ => unsupported_async_backend(), } } diff --git a/lib/api/src/entities/function/env/mod.rs b/lib/api/src/entities/function/env/mod.rs index fde4f9860ca..c917aa4be5c 100644 --- a/lib/api/src/entities/function/env/mod.rs +++ b/lib/api/src/entities/function/env/mod.rs @@ -145,8 +145,8 @@ impl AsyncFunctionEnvMut { } /// Borrows a new mutable reference - pub fn as_mut(&mut self) -> AsyncFunctionEnvMut { - AsyncFunctionEnvMut(self.0.as_mut()) + pub fn as_mut(&mut self) -> Self { + Self(self.0.as_mut()) } /// Creates an [`AsStoreAsync`] from this [`AsyncFunctionEnvMut`]. diff --git a/lib/api/src/entities/function/inner.rs b/lib/api/src/entities/function/inner.rs index d69ca10c248..61a3798fe58 100644 --- a/lib/api/src/entities/function/inner.rs +++ b/lib/api/src/entities/function/inner.rs @@ -3,9 +3,9 @@ use std::pin::Pin; use wasmer_types::{FunctionType, RawValue}; use crate::{ - AsStoreAsync, AsStoreMut, AsStoreRef, AsyncFunctionEnvMut, ExportError, Exportable, Extern, - FunctionEnv, FunctionEnvMut, HostFunction, StoreMut, StoreRef, TypedFunction, Value, - WasmTypeList, WithEnv, WithoutEnv, + AsStoreAsync, AsStoreMut, AsStoreRef, AsyncFunctionEnvMut, DynamicCallResult, + DynamicFunctionResult, ExportError, Exportable, Extern, FunctionEnv, FunctionEnvMut, + HostFunction, StoreMut, StoreRef, TypedFunction, Value, WasmTypeList, WithEnv, WithoutEnv, entities::function::async_host::AsyncHostFunction, error::RuntimeError, macros::backend::{gen_rt_ty, match_rt}, @@ -43,12 +43,11 @@ impl BackendFunction { pub fn new(store: &mut impl AsStoreMut, ty: FT, func: F) -> Self where FT: Into, - F: Fn(&[Value]) -> Result, RuntimeError> + 'static + Send + Sync, + F: Fn(&[Value]) -> DynamicFunctionResult + 'static + Send + Sync, { let env = FunctionEnv::new(&mut store.as_store_mut(), ()); - let wrapped_func = move |_env: FunctionEnvMut<()>, - args: &[Value]| - -> Result, RuntimeError> { func(args) }; + let wrapped_func = + move |_env: FunctionEnvMut<()>, args: &[Value]| -> DynamicFunctionResult { func(args) }; Self::new_with_env(store, &env, ty, wrapped_func) } @@ -98,10 +97,7 @@ impl BackendFunction { ) -> Self where FT: Into, - F: Fn(FunctionEnvMut, &[Value]) -> Result, RuntimeError> - + 'static - + Send - + Sync, + F: Fn(FunctionEnvMut, &[Value]) -> DynamicFunctionResult + 'static + Send + Sync, { match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] @@ -266,7 +262,7 @@ impl BackendFunction { where FT: Into, F: Fn(&[Value]) -> Fut + 'static, - Fut: Future, RuntimeError>> + 'static, + Fut: Future + 'static, { match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] @@ -304,7 +300,7 @@ impl BackendFunction { where FT: Into, F: Fn(AsyncFunctionEnvMut, &[Value]) -> Fut + 'static, - Fut: Future, RuntimeError>> + 'static, + Fut: Future + 'static, { match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] @@ -488,11 +484,7 @@ impl BackendFunction { /// assert_eq!(sum.call(&mut store, &[Value::I32(1), Value::I32(2)]).unwrap().to_vec(), vec![Value::I32(3)]); /// ``` #[inline] - pub fn call( - &self, - store: &mut impl AsStoreMut, - params: &[Value], - ) -> Result, RuntimeError> { + pub fn call(&self, store: &mut impl AsStoreMut, params: &[Value]) -> DynamicCallResult { match_rt!(on self => f { f.call(store, params) }) @@ -505,7 +497,7 @@ impl BackendFunction { &self, store: &mut impl AsStoreMut, params: Vec, - ) -> Result, RuntimeError> { + ) -> DynamicCallResult { match_rt!(on self => f { f.call_raw(store, params) }) @@ -515,7 +507,7 @@ impl BackendFunction { &'a self, store: &'a impl AsStoreAsync, params: Vec, - ) -> Pin, RuntimeError>> + 'a>> { + ) -> Pin + 'a>> { match self { #[cfg(feature = "sys")] Self::Sys(f) => f.call_async(store, params), @@ -773,8 +765,8 @@ fn unsupported_async_backend(backend: &str) -> ! { ) } -pub(super) fn unsupported_async_future<'a>() --> Pin, RuntimeError>> + 'a>> { +pub(super) fn unsupported_async_future<'a>() -> Pin + 'a>> +{ Box::pin(async { Err(RuntimeError::new( "async calls are only supported with the `sys` backend", diff --git a/lib/api/src/entities/function/mod.rs b/lib/api/src/entities/function/mod.rs index 9655a5b1ba0..dab0de6975c 100644 --- a/lib/api/src/entities/function/mod.rs +++ b/lib/api/src/entities/function/mod.rs @@ -27,6 +27,12 @@ use crate::{ #[cfg(feature = "sys")] use crate::backend::sys::async_runtime::{block_on_host_future, call_function_async}; +/// The return type from dynamic imported functions. +pub type DynamicFunctionResult = Result, RuntimeError>; + +/// The return type from dynamically calling Wasm functions. +pub type DynamicCallResult = Result, RuntimeError>; + /// A WebAssembly `function` instance. /// /// A function instance is the runtime representation of a function. @@ -56,7 +62,7 @@ impl Function { pub fn new(store: &mut impl AsStoreMut, ty: FT, func: F) -> Self where FT: Into, - F: Fn(&[Value]) -> Result, RuntimeError> + 'static + Send + Sync, + F: Fn(&[Value]) -> DynamicFunctionResult + 'static + Send + Sync, { Self(BackendFunction::new(store, ty, func)) } @@ -106,10 +112,7 @@ impl Function { ) -> Self where FT: Into, - F: Fn(FunctionEnvMut, &[Value]) -> Result, RuntimeError> - + 'static - + Send - + Sync, + F: Fn(FunctionEnvMut, &[Value]) -> DynamicFunctionResult + 'static + Send + Sync, { Self(BackendFunction::new_with_env(store, env, ty, func)) } @@ -169,7 +172,7 @@ impl Function { where FT: Into, F: Fn(&[Value]) -> Fut + 'static, - Fut: Future, RuntimeError>> + 'static, + Fut: Future + 'static, { Self(BackendFunction::new_async(store, ty, func)) } @@ -191,7 +194,7 @@ impl Function { where FT: Into, F: Fn(AsyncFunctionEnvMut, &[Value]) -> Fut + 'static, - Fut: Future, RuntimeError>> + 'static, + Fut: Future + 'static, { Self(BackendFunction::new_with_env_async(store, env, ty, func)) } @@ -318,11 +321,7 @@ impl Function { /// /// assert_eq!(sum.call(&mut store, &[Value::I32(1), Value::I32(2)]).unwrap().to_vec(), vec![Value::I32(3)]); /// ``` - pub fn call( - &self, - store: &mut impl AsStoreMut, - params: &[Value], - ) -> Result, RuntimeError> { + pub fn call(&self, store: &mut impl AsStoreMut, params: &[Value]) -> DynamicCallResult { self.0.call(store, params) } @@ -336,7 +335,7 @@ impl Function { &'a self, store: &'a impl AsStoreAsync, params: &'a [Value], - ) -> impl Future, RuntimeError>> + 'a { + ) -> impl Future + 'a { let params_vec = params.to_vec(); self.0.call_async(store, params_vec) } @@ -347,7 +346,7 @@ impl Function { &self, store: &mut impl AsStoreMut, params: Vec, - ) -> Result, RuntimeError> { + ) -> DynamicCallResult { self.0.call_raw(store, params) } diff --git a/lib/api/src/entities/store/async_.rs b/lib/api/src/entities/store/async_.rs index 6af6bd3c581..a578b9ceab6 100644 --- a/lib/api/src/entities/store/async_.rs +++ b/lib/api/src/entities/store/async_.rs @@ -19,7 +19,7 @@ impl StoreAsync { // Safety: we don't keep the guard around, it's just used to // build a safe lock handle. match unsafe { StoreContext::try_get_current_async(id) } { - crate::GetAsyncStoreGuardResult::Ok(guard) => Some(StoreAsync { + crate::GetAsyncStoreGuardResult::Ok(guard) => Some(Self { id, inner: crate::LocalRwLockWriteGuard::lock_handle(unsafe { guard.guard.as_ref().unwrap() @@ -147,7 +147,7 @@ impl<'a> AsyncStoreReadLock<'a> { impl AsStoreRef for AsyncStoreReadLock<'_> { fn as_store_ref(&self) -> StoreRef<'_> { match &self.inner { - AsyncStoreReadLockInner::Owned(guard) => StoreRef { inner: &*guard }, + AsyncStoreReadLockInner::Owned(guard) => StoreRef { inner: guard }, AsyncStoreReadLockInner::FromStoreContext(wrapper) => wrapper.as_ref(), } } @@ -188,7 +188,7 @@ impl<'a> AsyncStoreWriteLock<'a> { impl AsStoreRef for AsyncStoreWriteLock<'_> { fn as_store_ref(&self) -> StoreRef<'_> { match &self.inner { - AsyncStoreWriteLockInner::Owned(guard) => StoreRef { inner: &*guard }, + AsyncStoreWriteLockInner::Owned(guard) => StoreRef { inner: guard }, AsyncStoreWriteLockInner::FromStoreContext(wrapper) => wrapper.as_ref(), } } diff --git a/lib/api/src/entities/store/context.rs b/lib/api/src/entities/store/context.rs index dd86f9ece34..990e7823e0b 100644 --- a/lib/api/src/entities/store/context.rs +++ b/lib/api/src/entities/store/context.rs @@ -68,8 +68,8 @@ enum StoreContextEntry { impl StoreContextEntry { fn as_ptr(&self) -> *mut StoreInner { match self { - StoreContextEntry::Sync(ptr) => *ptr, - StoreContextEntry::Async(guard) => &**guard as *const _ as *mut _, + Self::Sync(ptr) => *ptr, + Self::Async(guard) => &**guard as *const _ as *mut _, } } } @@ -114,14 +114,14 @@ pub(crate) enum StoreInstallGuard { } thread_local! { - static STORE_CONTEXT_STACK: RefCell> = RefCell::new(Vec::new()); + static STORE_CONTEXT_STACK: RefCell> = const { RefCell::new(Vec::new()) }; } impl StoreContext { fn is_active(id: StoreId) -> bool { STORE_CONTEXT_STACK.with(|cell| { let stack = cell.borrow(); - stack.last().map_or(false, |ctx| ctx.id == id) + stack.last().is_some_and(|ctx| ctx.id == id) }) } @@ -136,7 +136,7 @@ impl StoreContext { fn install(id: StoreId, entry: StoreContextEntry) { STORE_CONTEXT_STACK.with(|cell| { let mut stack = cell.borrow_mut(); - stack.push(StoreContext { + stack.push(Self { id, borrow_count: 0, entry: UnsafeCell::new(entry), @@ -167,7 +167,7 @@ impl StoreContext { /// # Safety /// The pointer must be dereferenceable and remain valid until the /// store context is uninstalled. - pub(crate) unsafe fn ensure_installed<'a>(store_ptr: *mut StoreInner) -> StoreInstallGuard { + pub(crate) unsafe fn ensure_installed(store_ptr: *mut StoreInner) -> StoreInstallGuard { let store_id = unsafe { store_ptr.as_ref().unwrap().objects.id() }; if Self::is_active(store_id) { StoreInstallGuard::NotInstalled @@ -182,6 +182,7 @@ impl StoreContext { /// * there is only one mutable reference alive, or /// * all but one mutable reference are inaccessible and passed /// into a function that lost the reference (e.g. into WASM code) + /// /// The intended, valid use-case for this method is from within /// imported function trampolines. pub(crate) unsafe fn get_current(id: StoreId) -> StorePtrWrapper { @@ -280,7 +281,7 @@ impl Clone for StorePtrWrapper { _ => panic!("Mismatched store context access"), } top.borrow_count += 1; - StorePtrWrapper { + Self { store_ptr: self.store_ptr, } }) @@ -317,7 +318,7 @@ impl Drop for AsyncStoreGuardWrapper { impl Drop for StoreInstallGuard { fn drop(&mut self) { - if let StoreInstallGuard::Installed(store_id) = self { + if let Self::Installed(store_id) = self { STORE_CONTEXT_STACK.with(|cell| { let mut stack = cell.borrow_mut(); let top = stack.pop().expect("Store context stack underflow"); diff --git a/lib/api/src/entities/store/local_rwlock.rs b/lib/api/src/entities/store/local_rwlock.rs index 47cc1e82e17..4af6264a1ac 100644 --- a/lib/api/src/entities/store/local_rwlock.rs +++ b/lib/api/src/entities/store/local_rwlock.rs @@ -170,10 +170,8 @@ impl LocalRwLockInner { if has_writers { // If there are waiting writers, only wake them (they need exclusive access) - for waker_slot in write_waiters.drain(..) { - if let Some(waker) = waker_slot { - waker.wake(); - } + for waker in write_waiters.drain(..).flatten() { + waker.wake(); } } else { // No writers waiting, wake all readers (they can share the lock) diff --git a/lib/api/tests/instance.rs b/lib/api/tests/instance.rs index a0c5e48ed3a..e6e954d47f5 100644 --- a/lib/api/tests/instance.rs +++ b/lib/api/tests/instance.rs @@ -61,7 +61,7 @@ fn unit_native_function_env() -> Result<(), String> { multiplier: u32, } - fn imported_fn(env: FunctionEnvMut, args: &[Value]) -> Result, RuntimeError> { + fn imported_fn(env: FunctionEnvMut, args: &[Value]) -> DynamicFunctionResult { let value = env.data().multiplier * args[0].unwrap_i32() as u32; Ok(vec![Value::I32(value as _)]) } diff --git a/lib/api/tests/jspi_async.rs b/lib/api/tests/jspi_async.rs index 7fc52d769da..09a875e79f7 100644 --- a/lib/api/tests/jspi_async.rs +++ b/lib/api/tests/jspi_async.rs @@ -320,7 +320,7 @@ fn async_multiple_active_coroutines() -> Result<()> { struct Env { log: Vec, - futures: [Option, RuntimeError>>>>>; 4], + futures: [Option>>>; 4], yielders: [Option; 4], future_func: Option, } diff --git a/lib/api/tests/reference_types.rs b/lib/api/tests/reference_types.rs index 3350873bfe4..e3df626cb8a 100644 --- a/lib/api/tests/reference_types.rs +++ b/lib/api/tests/reference_types.rs @@ -82,10 +82,7 @@ pub mod reference_types { )"#; let module = Module::new(&store, wat)?; let env = FunctionEnv::new(&mut store, ()); - fn func_ref_call( - mut env: FunctionEnvMut<()>, - values: &[Value], - ) -> Result, RuntimeError> { + fn func_ref_call(mut env: FunctionEnvMut<()>, values: &[Value]) -> DynamicFunctionResult { // TODO: look into `Box<[Value]>` being returned breakage let f = values[0].unwrap_funcref().as_ref().unwrap(); let f: TypedFunction<(i32, i32), i32> = f.typed(&env)?; diff --git a/lib/c-api/src/wasm_c_api/externals/function.rs b/lib/c-api/src/wasm_c_api/externals/function.rs index ed6c55d4830..d607f09c4b9 100644 --- a/lib/c-api/src/wasm_c_api/externals/function.rs +++ b/lib/c-api/src/wasm_c_api/externals/function.rs @@ -8,7 +8,9 @@ use libc::c_void; use std::convert::TryInto; use std::mem::MaybeUninit; use std::sync::{Arc, Mutex}; -use wasmer_api::{Extern, Function, FunctionEnv, FunctionEnvMut, RuntimeError, Value}; +use wasmer_api::{ + DynamicFunctionResult, Extern, Function, FunctionEnv, FunctionEnvMut, RuntimeError, Value, +}; #[derive(Clone)] #[allow(non_camel_case_types)] @@ -57,7 +59,7 @@ pub unsafe extern "C" fn wasm_func_new( let num_rets = func_sig.results().len(); let inner_callback = move |mut _env: FunctionEnvMut<'_, FunctionCEnv>, args: &[Value]| - -> Result, RuntimeError> { + -> DynamicFunctionResult { let processed_args: wasm_val_vec_t = args .iter() .map(TryInto::try_into) @@ -134,40 +136,39 @@ pub unsafe extern "C" fn wasm_func_new_with_env( } } } - let inner_callback = move |env: FunctionEnvMut<'_, WrapperEnv>, - args: &[Value]| - -> Result, RuntimeError> { - let processed_args: wasm_val_vec_t = args - .iter() - .map(TryInto::try_into) - .collect::, _>>() - .expect("Argument conversion failed") + let inner_callback = + move |env: FunctionEnvMut<'_, WrapperEnv>, args: &[Value]| -> DynamicFunctionResult { + let processed_args: wasm_val_vec_t = args + .iter() + .map(TryInto::try_into) + .collect::, _>>() + .expect("Argument conversion failed") + .into(); + + let mut results: wasm_val_vec_t = vec![ + wasm_val_t { + kind: wasm_valkind_enum::WASM_I64 as _, + of: wasm_val_inner { int64_t: 0 }, + }; + num_rets + ] .into(); - let mut results: wasm_val_vec_t = vec![ - wasm_val_t { - kind: wasm_valkind_enum::WASM_I64 as _, - of: wasm_val_inner { int64_t: 0 }, - }; - num_rets - ] - .into(); + let trap = unsafe { callback(env.data().env.as_ptr(), &processed_args, &mut results) }; - let trap = unsafe { callback(env.data().env.as_ptr(), &processed_args, &mut results) }; + if let Some(trap) = trap { + return Err(trap.inner); + } - if let Some(trap) = trap { - return Err(trap.inner); - } + let processed_results = results + .take() + .into_iter() + .map(TryInto::try_into) + .collect::, _>>() + .expect("Result conversion failed"); - let processed_results = results - .take() - .into_iter() - .map(TryInto::try_into) - .collect::, _>>() - .expect("Result conversion failed"); - - Ok(processed_results) - }; + Ok(processed_results) + }; let env = FunctionEnv::new( &mut store_mut, WrapperEnv { diff --git a/lib/cli/src/commands/run/mod.rs b/lib/cli/src/commands/run/mod.rs index 881681a8967..87deae32795 100644 --- a/lib/cli/src/commands/run/mod.rs +++ b/lib/cli/src/commands/run/mod.rs @@ -29,8 +29,8 @@ use url::Url; #[cfg(feature = "sys")] use wasmer::sys::NativeEngineExt; use wasmer::{ - AsStoreMut, DeserializeError, Engine, Function, Imports, Instance, Module, RuntimeError, Store, - Type, TypedFunction, Value, + AsStoreMut, DeserializeError, DynamicCallResult, Engine, Function, Imports, Instance, Module, + RuntimeError, Store, Type, TypedFunction, Value, }; use wasmer_types::{Features, target::Target}; @@ -645,7 +645,7 @@ fn invoke_function( store: &mut Store, func: &Function, args: &[String], -) -> anyhow::Result, RuntimeError>> { +) -> anyhow::Result { let func_ty = func.ty(store); let required_arguments = func_ty.params().len(); let provided_arguments = args.len(); diff --git a/lib/compiler-singlepass/src/codegen.rs b/lib/compiler-singlepass/src/codegen.rs index 2a38184ee90..532c77be335 100644 --- a/lib/compiler-singlepass/src/codegen.rs +++ b/lib/compiler-singlepass/src/codegen.rs @@ -3617,11 +3617,11 @@ impl<'a, M: Machine> FuncGen<'a, M> { self.machine.emit_function_epilog()?; // Make a copy of the return value in XMM0, as required by the SysV CC. - if let Ok(&return_type) = self.signature.results().iter().exactly_one() { - if return_type == Type::F32 || return_type == Type::F64 { - self.machine.emit_function_return_float()?; - } - }; + if let Ok(&return_type) = self.signature.results().iter().exactly_one() + && (return_type == Type::F32 || return_type == Type::F64) + { + self.machine.emit_function_return_float()?; + } self.machine.emit_ret()?; } else { let released = &self.value_stack.clone()[frame.value_stack_depth_after()..]; diff --git a/lib/wasix/src/bin_factory/exec.rs b/lib/wasix/src/bin_factory/exec.rs index d077394ddb8..b51ad958ae7 100644 --- a/lib/wasix/src/bin_factory/exec.rs +++ b/lib/wasix/src/bin_factory/exec.rs @@ -18,7 +18,7 @@ use crate::{ }; use tracing::*; use virtual_mio::block_on; -use wasmer::{Function, Memory32, Memory64, Module, RuntimeError, Store, Value}; +use wasmer::{DynamicCallResult, Function, Memory32, Memory64, Module, RuntimeError, Store}; use wasmer_wasix_types::wasi::Errno; use super::{BinaryPackage, BinaryPackageCommand}; @@ -397,8 +397,8 @@ fn resume_vfork( ctx: &WasiFunctionEnv, store: &mut Store, start: &Function, - call_ret: &Result, RuntimeError>, -) -> Result, RuntimeError>>, Errno> { + call_ret: &DynamicCallResult, +) -> Result, Errno> { let (err, code) = match call_ret { Ok(_) => (None, wasmer_wasix_types::wasi::ExitCode::from(0u16)), Err(err) => match err.downcast_ref::() { From bb8cb8498e82f5e10cbf0110c01fe4f3cd95b27d Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Fri, 28 Nov 2025 18:00:30 +0400 Subject: [PATCH 33/49] Because @zebreus had a misconception about what Function::call_async does XD --- lib/api/src/entities/function/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/api/src/entities/function/mod.rs b/lib/api/src/entities/function/mod.rs index dab0de6975c..f612bb4a19b 100644 --- a/lib/api/src/entities/function/mod.rs +++ b/lib/api/src/entities/function/mod.rs @@ -331,6 +331,7 @@ impl Function { /// coroutine stack. Host functions created with [`Function::new_async`] may /// suspend execution by awaiting futures, and their completion will resume /// the Wasm instance according to the JSPI proposal. + #[must_use("This function spawns a future that must be awaited to produce results")] pub fn call_async<'a>( &'a self, store: &'a impl AsStoreAsync, From e1f7ec3ad9cd5364a8a93d158564a08926c5b4b3 Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Sat, 29 Nov 2025 16:56:43 +0400 Subject: [PATCH 34/49] * Disallow recursive coroutines * make the call_async future 'static * Eliminate the ugly and now-impossible multiple active coroutines test in favor of the greenthreads test --- lib/api/src/backend/sys/async_runtime.rs | 55 ++-- .../src/backend/sys/entities/function/mod.rs | 6 +- .../backend/sys/entities/function/typed.rs | 32 +- lib/api/src/entities/function/inner.rs | 8 +- lib/api/src/entities/function/mod.rs | 18 +- lib/api/src/utils/native/typed_func.rs | 14 +- lib/api/tests/jspi_async.rs | 282 +----------------- lib/api/tests/simple_greenthread.rs | 4 +- 8 files changed, 81 insertions(+), 338 deletions(-) diff --git a/lib/api/src/backend/sys/async_runtime.rs b/lib/api/src/backend/sys/async_runtime.rs index 10dd2ed604a..df755ab79b5 100644 --- a/lib/api/src/backend/sys/async_runtime.rs +++ b/lib/api/src/backend/sys/async_runtime.rs @@ -20,11 +20,11 @@ use wasmer_types::StoreId; type HostFuture = Pin + 'static>>; -pub(crate) fn call_function_async<'a>( +pub(crate) fn call_function_async( function: SysFunction, store: StoreAsync, params: Vec, -) -> AsyncCallFuture<'a> { +) -> AsyncCallFuture { AsyncCallFuture::new(function, store, params) } @@ -35,9 +35,9 @@ enum AsyncResume { HostFutureReady(DynamicFunctionResult), } -pub(crate) struct AsyncCallFuture<'a> { +pub(crate) struct AsyncCallFuture { coroutine: Option>, - pending_store_install: Option + 'a>>>, + pending_store_install: Option>>>, pending_future: Option, next_resume: Option, result: Option, @@ -100,7 +100,7 @@ impl AsStoreMut for AsyncCallStoreMut { } } -impl<'a> AsyncCallFuture<'a> { +impl AsyncCallFuture { pub(crate) fn new(function: SysFunction, store: StoreAsync, params: Vec) -> Self { let store_id = store.id; let coroutine = @@ -128,7 +128,7 @@ impl<'a> AsyncCallFuture<'a> { } } -impl Future for AsyncCallFuture<'_> { +impl Future for AsyncCallFuture { type Output = DynamicCallResult; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { @@ -200,31 +200,34 @@ enum StoreContextInstaller { impl StoreContextInstaller { async fn install(store: StoreAsync) -> Self { match unsafe { crate::StoreContext::try_get_current_async(store.id) } { - crate::GetAsyncStoreGuardResult::NotAsync(_) => { - // This async call is being polled from inside a sync call, such as: - // call() -> imported function (sync or async) -> call_async().poll() - // In this case, we can never progress. - // - // If the imported function is async, suspending here will cause a - // YieldOutsideAsyncContext trap to be raised. - // - // If the imported function is sync and you're hoping to drive this - // future to completion (maybe using an inline blocker), may God have - // mercy on you. Also, I *think* that's impossible given the way this - // crate's public API is currently set up. - tracing::warn!("Async function polled from sync context; suspending forever"); - futures::future::pending().await - } - crate::GetAsyncStoreGuardResult::Ok(wrapper) => { - // If we're already in the scope of this store, we can just reuse it. - Self::FromThreadContext(wrapper) - } crate::GetAsyncStoreGuardResult::NotInstalled => { - // Otherwise, need to acquire a new StoreMut. + // We always need to acquire a new write lock on the store. let store_guard = store.inner.write().await; let install_guard = unsafe { crate::StoreContext::install_async(store_guard) }; Self::Installed(install_guard) } + _ => { + // If we're already in a store context, it is unsafe to reuse + // the existing store ref since it'll also be accessible from + // the imported function that tried to poll us, which is a + // double mutable borrow. + // Note to people who discover this code: this *would* be safe + // if we had a separate variation of call_async that just + // used the existing coroutine context instead of spawning a + // new coroutine. However, the current call_async always spawns + // a new coroutine, so we can't allow this; every coroutine + // needs to own its write lock on the store to make sure there + // are no overlapping mutable borrows. If this is something + // you're interested in, feel free to open a GitHub issue outlining + // your use-case. + panic!( + "Function::call_async futures cannot be polled recursively \ + from within another imported function. If you need to await \ + a recursive call_async, consider spawning the future into \ + your async runtime and awaiting the resulting task; \ + e.g. tokio::task::spawn(func.call_async(...)).await" + ); + } } } } diff --git a/lib/api/src/backend/sys/entities/function/mod.rs b/lib/api/src/backend/sys/entities/function/mod.rs index 270ccdde753..a8d9d53fdd8 100644 --- a/lib/api/src/backend/sys/entities/function/mod.rs +++ b/lib/api/src/backend/sys/entities/function/mod.rs @@ -576,11 +576,11 @@ impl Function { Ok(results.into_boxed_slice()) } - pub(crate) fn call_async<'a>( + pub(crate) fn call_async( &self, - store: &'a impl AsStoreAsync, + store: &impl AsStoreAsync, params: Vec, - ) -> Pin + 'a>> { + ) -> Pin + 'static>> { let function = self.clone(); let store = store.store(); Box::pin(call_function_async(function, store, params)) diff --git a/lib/api/src/backend/sys/entities/function/typed.rs b/lib/api/src/backend/sys/entities/function/typed.rs index 0d96a2a17c5..dcd35c7925c 100644 --- a/lib/api/src/backend/sys/entities/function/typed.rs +++ b/lib/api/src/backend/sys/entities/function/typed.rs @@ -1,8 +1,8 @@ use crate::backend::sys::engine::NativeEngineExt; use crate::store::{AsStoreAsync, AsStoreMut, AsStoreRef}; use crate::{ - FromToNativeWasmType, NativeWasmTypeInto, RuntimeError, StoreContext, TypedFunction, Value, - WasmTypeList, + FromToNativeWasmType, Function, NativeWasmTypeInto, RuntimeError, StoreAsync, StoreContext, + TypedFunction, Value, WasmTypeList, }; use std::future::Future; use wasmer_types::{FunctionType, RawValue, Type}; @@ -111,20 +111,34 @@ macro_rules! impl_native_traits { // Ok(Rets::from_c_struct(results)) } + #[allow(unused_mut)] + #[allow(clippy::too_many_arguments)] + pub(crate) fn call_async_sys( + &self, + store: &impl AsStoreAsync, + $( $x: $x, )* + ) -> impl Future> + 'static + where + $( $x: FromToNativeWasmType + 'static, )* + { + let func = self.func.clone(); + let store = store.store(); + Self::call_async_sys_internal(func, store, $($x),*) + } + /// Call the typed func asynchronously. #[allow(unused_mut)] #[allow(clippy::too_many_arguments)] - pub fn call_async_sys<'a>( - &'a self, - store: &'a impl AsStoreAsync, + pub(crate) fn call_async_sys_internal( + func: Function, + store: StoreAsync, $( $x: $x, )* - ) -> impl Future> + 'a + ) -> impl Future> + 'static where - $( $x: FromToNativeWasmType, )* + $( $x: FromToNativeWasmType + 'static, )* { async move { let mut write = store.write_lock().await; - let func = self.func.clone(); let func_ty = func.ty(&mut write); let mut params_raw = [ $( $x.to_native().into_raw(&mut write) ),* ]; let mut params_values = Vec::with_capacity(params_raw.len()); @@ -137,7 +151,7 @@ macro_rules! impl_native_traits { } drop(write); - let results = func.call_async(store, ¶ms_values).await?; + let results = func.call_async(&store, params_values).await?; let mut write = store.write_lock().await; convert_results::(&mut write, func_ty, &results) } diff --git a/lib/api/src/entities/function/inner.rs b/lib/api/src/entities/function/inner.rs index 61a3798fe58..b86b60f2147 100644 --- a/lib/api/src/entities/function/inner.rs +++ b/lib/api/src/entities/function/inner.rs @@ -503,11 +503,11 @@ impl BackendFunction { }) } - pub fn call_async<'a>( - &'a self, - store: &'a impl AsStoreAsync, + pub fn call_async( + &self, + store: &impl AsStoreAsync, params: Vec, - ) -> Pin + 'a>> { + ) -> Pin + 'static>> { match self { #[cfg(feature = "sys")] Self::Sys(f) => f.call_async(store, params), diff --git a/lib/api/src/entities/function/mod.rs b/lib/api/src/entities/function/mod.rs index f612bb4a19b..e04ade2a269 100644 --- a/lib/api/src/entities/function/mod.rs +++ b/lib/api/src/entities/function/mod.rs @@ -24,9 +24,6 @@ use crate::{ vm::{VMExtern, VMExternFunction, VMFuncRef}, }; -#[cfg(feature = "sys")] -use crate::backend::sys::async_runtime::{block_on_host_future, call_function_async}; - /// The return type from dynamic imported functions. pub type DynamicFunctionResult = Result, RuntimeError>; @@ -331,14 +328,13 @@ impl Function { /// coroutine stack. Host functions created with [`Function::new_async`] may /// suspend execution by awaiting futures, and their completion will resume /// the Wasm instance according to the JSPI proposal. - #[must_use("This function spawns a future that must be awaited to produce results")] - pub fn call_async<'a>( - &'a self, - store: &'a impl AsStoreAsync, - params: &'a [Value], - ) -> impl Future + 'a { - let params_vec = params.to_vec(); - self.0.call_async(store, params_vec) + #[must_use = "This function spawns a future that must be awaited to produce results"] + pub fn call_async( + &self, + store: &impl AsStoreAsync, + params: Vec, + ) -> impl Future + 'static { + self.0.call_async(store, params) } #[doc(hidden)] diff --git a/lib/api/src/utils/native/typed_func.rs b/lib/api/src/utils/native/typed_func.rs index c49e7d69ec4..bc00d59cd26 100644 --- a/lib/api/src/utils/native/typed_func.rs +++ b/lib/api/src/utils/native/typed_func.rs @@ -82,24 +82,26 @@ macro_rules! impl_native_traits { /// Call the typed func asynchronously. #[allow(unused_mut)] #[allow(clippy::too_many_arguments)] - pub fn call_async<'a>( - &'a self, - store: &'a impl AsStoreAsync, + pub fn call_async( + &self, + store: &impl AsStoreAsync, $( $x: $x, )* - ) -> impl Future> + 'a + ) -> impl Future> + Sized + 'static where - $( $x: FromToNativeWasmType, )* + $( $x: FromToNativeWasmType + 'static, )* { $( let [] = $x; )* + let store = store.store(); + let func = self.func.clone(); async move { let read_lock = store.read_lock().await; match read_lock.as_store_ref().inner.store { #[cfg(feature = "sys")] BackendStore::Sys(_) => { drop(read_lock); - self.call_async_sys(store, $([]),*).await + Self::call_async_sys_internal(func, store, $([]),*).await } #[cfg(feature = "wamr")] BackendStore::Wamr(_) => async_backend_error(), diff --git a/lib/api/tests/jspi_async.rs b/lib/api/tests/jspi_async.rs index 09a875e79f7..2dbdc681bff 100644 --- a/lib/api/tests/jspi_async.rs +++ b/lib/api/tests/jspi_async.rs @@ -1,15 +1,10 @@ -use std::{ - cell::RefCell, - pin::Pin, - sync::OnceLock, - task::{Context, Poll}, -}; +use std::sync::OnceLock; use anyhow::Result; -use futures::{FutureExt, future}; +use futures::future; use wasmer::{ - AsyncFunctionEnvMut, Function, FunctionEnv, FunctionType, Instance, Module, RuntimeError, - Store, StoreAsync, Type, TypedFunction, Value, imports, + AsyncFunctionEnvMut, Function, FunctionEnv, FunctionType, Instance, Module, Store, StoreAsync, + Type, TypedFunction, Value, imports, }; use wasmer_vm::TrapCode; @@ -105,7 +100,7 @@ fn async_state_updates_follow_jspi_example() -> Result<()> { .enable_all() .build() .unwrap() - .block_on(func.call_async(store, &[]))?; + .block_on(func.call_async(store, vec![]))?; Ok(as_f64(&result)) }; @@ -230,270 +225,3 @@ fn cannot_yield_when_not_in_async_context() -> Result<()> { Ok(()) } - -/* This test is slightly weird to explain; what we're testing here - is that multiple coroutines can be active at the same time, - and that they can be polled in any order. - - To achieve this, we have 2 main imports: - * spawn_future spawns new, pending futures, and polls them once. - The futures are set up in a way that they will suspend the first time - they are polled, and complete the second time. However, by polling - once, we will kickstart the corresponding coroutine into action, - which then stays active but suspended. - * resolve_future polls an already spawned future a second time, which - will cause it to be resolved. With this, we are once again activating - the coroutine. - - We also have the future_func export and the poll_future import. future_func - is the "body" of the inner coroutine, while poll_future retrieves the future - constructed by spawn_future and uses it to suspend the coroutine. - - Note that the coroutine will start executing twice, once during spawn_future - (which is a sync imported function) and once during resolve_future - (which is async). This way we ensure that any combination of active coroutines - is possible. - - The proper order of the log numbers for a given future is: - * spawning the future: - 10 + id: spawn_future called initially - 20 + id: the future is constructed, but not polled yet - 30 + id: future_func polled first time - 40 + id: poll_future called, suspending the coroutine - 50 + id: spawn_future finished - * resolving the future: - 60 + id: resolve_future called - 70 + id: future_func resumed second time - 80 + id: resolve_future finished -*/ -#[test] -fn async_multiple_active_coroutines() -> Result<()> { - const WAT: &str = r#" - (module - (import "env" "spawn_future" (func $spawn_future (param i32))) - (import "env" "poll_future" (func $poll_future (param i32))) - (import "env" "resolve_future" (func $resolve_future (param i32))) - (import "env" "yield_now" (func $yield_now)) - (import "env" "log" (func $log (param i32))) - (func (export "main") - (call $spawn_future (i32.const 0)) - (call $spawn_future (i32.const 1)) - (call $yield_now) - (call $spawn_future (i32.const 2)) - (call $resolve_future (i32.const 1)) - (call $yield_now) - (call $spawn_future (i32.const 3)) - (call $resolve_future (i32.const 2)) - (call $yield_now) - (call $resolve_future (i32.const 3)) - (call $resolve_future (i32.const 0)) - ) - (func (export "future_func") (param i32) (result i32) - (call $log (i32.add (i32.const 30) (local.get 0))) - (call $poll_future (local.get 0)) - (call $log (i32.add (i32.const 70) (local.get 0))) - (return (local.get 0)) - ) - ) - "#; - let wasm = wat::parse_str(WAT).expect("valid WAT module"); - - struct Yielder { - yielded: bool, - } - - impl Future for Yielder { - type Output = (); - - fn poll( - mut self: std::pin::Pin<&mut Self>, - _cx: &mut std::task::Context<'_>, - ) -> std::task::Poll { - if self.yielded { - std::task::Poll::Ready(()) - } else { - self.yielded = true; - std::task::Poll::Pending - } - } - } - - struct Env { - log: Vec, - futures: [Option>>>; 4], - yielders: [Option; 4], - future_func: Option, - } - - let mut store = Store::default(); - let module = Module::new(&store, wasm)?; - - thread_local! { - static ENV: RefCell = RefCell::new(Env { - log: Vec::new(), - futures: [None, None, None, None], - yielders: [None, None, None, None], - future_func: None, - }) - } - let mut env = FunctionEnv::new(&mut store, ()); - - fn log(value: i32) { - ENV.with(|env| { - env.borrow_mut().log.push(value); - }); - } - - let spawn_future = Function::new_with_env( - &mut store, - &mut env, - FunctionType::new(vec![Type::I32], vec![]), - |env, values| { - ENV.with(move |data| { - let future_id = values[0].unwrap_i32(); - - // As long as we're in an async context, we can create an - // AsStoreAsync even from a sync FunctionEnvMut. - let store_async = env.as_store_async().expect("Not in an async context"); - - log(future_id + 10); - - data.borrow_mut().yielders[future_id as usize] = Some(Yielder { yielded: false }); - - // This spawns the coroutine and the corresponding future - let func = data.borrow().future_func.as_ref().unwrap().clone(); - let mut future = Box::pin(async move { - func.call_async(&store_async, &[Value::I32(future_id)]) - .await - }); - log(future_id + 20); - - // We then poll it once to get it started - it'll suspend once, then - // complete the next time we poll it - let w = futures::task::noop_waker(); - let mut cx = Context::from_waker(&w); - assert!(future.as_mut().poll(&mut cx).is_pending()); - - log(future_id + 50); - // We then store the future without letting it complete, and return - data.borrow_mut().futures[future_id as usize] = Some(future); - - Ok(vec![]) - }) - }, - ); - - let poll_future = Function::new_async( - &mut store, - FunctionType::new(vec![Type::I32], vec![]), - |values| { - let future_id = values[0].unwrap_i32(); - let yielder = ENV.with(|data| { - log(future_id + 40); - - let mut borrow = data.borrow_mut(); - let yielder = unsafe { - (borrow.yielders[future_id as usize].as_mut().unwrap() as *mut Yielder) - .as_mut::<'static>() - .unwrap() - }; - yielder - }); - yielder.map(|()| Ok(vec![])) - }, - ); - - let resolve_future = Function::new( - &mut store, - FunctionType::new(vec![Type::I32], vec![]), - |values| { - ENV.with(|data| { - let future_id = values[0].unwrap_i32(); - - log(future_id + 60); - - let mut future = data.borrow_mut().futures[future_id as usize] - .take() - .unwrap(); - - let w = futures::task::noop_waker(); - let mut cx = Context::from_waker(&w); - let Poll::Ready(result) = future.as_mut().poll(&mut cx) else { - panic!("expected future to be ready"); - }; - let result_id = result.unwrap()[0].unwrap_i32(); - assert_eq!(result_id, future_id); - - log(future_id + 80); - - Ok(vec![]) - }) - }, - ); - - let yield_now = Function::new_async( - &mut store, - FunctionType::new(vec![], vec![]), - |_values| async move { - tokio::task::yield_now().await; - Ok(vec![]) - }, - ); - - let log = Function::new( - &mut store, - FunctionType::new(vec![Type::I32], vec![]), - |values| { - let value = values[0].unwrap_i32(); - log(value); - Ok(vec![]) - }, - ); - - let import_object = imports! { - "env" => { - "spawn_future" => spawn_future, - "poll_future" => poll_future, - "resolve_future" => resolve_future, - "yield_now" => yield_now, - "log" => log, - } - }; - let instance = Instance::new(&mut store, &module, &import_object)?; - - ENV.with(|env| { - env.borrow_mut().future_func = Some( - instance - .exports - .get_function("future_func") - .unwrap() - .clone(), - ) - }); - - let main = instance.exports.get_function("main")?; - let store_async = store.into_async(); - tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap() - .block_on(main.call_async(&store_async, &[]))?; - - ENV.with(|env| { - assert_eq!( - env.borrow().log, - vec![ - 10, 20, 30, 40, 50, // future 0 spawned - 11, 21, 31, 41, 51, // future 1 spawned - 12, 22, 32, 42, 52, // future 2 spawned - 61, 71, 81, // future 1 resolved - 13, 23, 33, 43, 53, // future 3 spawned - 62, 72, 82, // future 2 resolved - 63, 73, 83, // future 3 resolved - 60, 70, 80, // future 0 resolved - ] - ); - }); - - Ok(()) -} diff --git a/lib/api/tests/simple_greenthread.rs b/lib/api/tests/simple_greenthread.rs index 5f8cc155278..387cfbad16d 100644 --- a/lib/api/tests/simple_greenthread.rs +++ b/lib/api/tests/simple_greenthread.rs @@ -86,7 +86,7 @@ async fn greenthread_new( .spawn_local(async move { receiver.await.unwrap(); let resumer = function - .call_async(&async_store, &[Value::I32(entrypoint_data as i32)]) + .call_async(&async_store, vec![Value::I32(entrypoint_data as i32)]) .await; panic!("Greenthread function returned {:?}", resumer); }) @@ -208,7 +208,7 @@ fn run_greenthread_test(wat: &[u8]) -> Result> { let store_async = store.into_async(); localpool - .run_until(main_fn.call_async(&store_async, &[])) + .run_until(main_fn.call_async(&store_async, vec![])) .unwrap(); // If there are no more clones of it, StoreAsync can also be From 3618a2343540873bee163ffb3b4aa330df797596 Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Sat, 29 Nov 2025 18:32:16 +0400 Subject: [PATCH 35/49] * Disallow taking references to the store context in async imports * Remove lifetime bound on AsyncFunctionEnvHandle and friends, since it's a properly owned type now --- lib/api/src/backend/sys/async_runtime.rs | 72 +++++------- .../src/backend/sys/entities/function/env.rs | 36 +++--- lib/api/src/entities/function/env/inner.rs | 22 ++-- lib/api/src/entities/function/env/mod.rs | 18 +-- lib/api/src/entities/store/async_.rs | 110 +++++------------- 5 files changed, 95 insertions(+), 163 deletions(-) diff --git a/lib/api/src/backend/sys/async_runtime.rs b/lib/api/src/backend/sys/async_runtime.rs index df755ab79b5..5b218ddb9db 100644 --- a/lib/api/src/backend/sys/async_runtime.rs +++ b/lib/api/src/backend/sys/async_runtime.rs @@ -13,8 +13,9 @@ use corosensei::{Coroutine, CoroutineResult, Yielder}; use super::entities::function::Function as SysFunction; use crate::{ - AsStoreMut, AsStoreRef, DynamicCallResult, DynamicFunctionResult, LocalRwLockWriteGuard, - RuntimeError, Store, StoreAsync, StoreContext, StoreInner, StoreMut, StoreRef, Value, + AsStoreMut, AsStoreRef, DynamicCallResult, DynamicFunctionResult, ForcedStoreInstallGuard, + LocalRwLockWriteGuard, RuntimeError, Store, StoreAsync, StoreContext, StoreInner, StoreMut, + StoreRef, Value, }; use wasmer_types::StoreId; @@ -37,7 +38,7 @@ enum AsyncResume { pub(crate) struct AsyncCallFuture { coroutine: Option>, - pending_store_install: Option>>>, + pending_store_install: Option>>>, pending_future: Option, next_resume: Option, result: Option, @@ -150,11 +151,10 @@ impl Future for AsyncCallFuture { // Start a store installation if not in progress already if self.pending_store_install.is_none() { - self.pending_store_install = - Some(Box::pin(StoreContextInstaller::install(StoreAsync { - id: self.store.id, - inner: self.store.inner.clone(), - }))); + self.pending_store_install = Some(Box::pin(install_store_context(StoreAsync { + id: self.store.id, + inner: self.store.inner.clone(), + }))); } // Acquiring a store lock should be the last step before resuming @@ -192,42 +192,34 @@ impl Future for AsyncCallFuture { } } -enum StoreContextInstaller { - FromThreadContext(crate::AsyncStoreGuardWrapper), - Installed(crate::ForcedStoreInstallGuard), -} - -impl StoreContextInstaller { - async fn install(store: StoreAsync) -> Self { - match unsafe { crate::StoreContext::try_get_current_async(store.id) } { - crate::GetAsyncStoreGuardResult::NotInstalled => { - // We always need to acquire a new write lock on the store. - let store_guard = store.inner.write().await; - let install_guard = unsafe { crate::StoreContext::install_async(store_guard) }; - Self::Installed(install_guard) - } - _ => { - // If we're already in a store context, it is unsafe to reuse - // the existing store ref since it'll also be accessible from - // the imported function that tried to poll us, which is a - // double mutable borrow. - // Note to people who discover this code: this *would* be safe - // if we had a separate variation of call_async that just - // used the existing coroutine context instead of spawning a - // new coroutine. However, the current call_async always spawns - // a new coroutine, so we can't allow this; every coroutine - // needs to own its write lock on the store to make sure there - // are no overlapping mutable borrows. If this is something - // you're interested in, feel free to open a GitHub issue outlining - // your use-case. - panic!( - "Function::call_async futures cannot be polled recursively \ +async fn install_store_context(store: StoreAsync) -> ForcedStoreInstallGuard { + match unsafe { crate::StoreContext::try_get_current_async(store.id) } { + crate::GetAsyncStoreGuardResult::NotInstalled => { + // We always need to acquire a new write lock on the store. + let store_guard = store.inner.write().await; + unsafe { crate::StoreContext::install_async(store_guard) } + } + _ => { + // If we're already in a store context, it is unsafe to reuse + // the existing store ref since it'll also be accessible from + // the imported function that tried to poll us, which is a + // double mutable borrow. + // Note to people who discover this code: this *would* be safe + // if we had a separate variation of call_async that just + // used the existing coroutine context instead of spawning a + // new coroutine. However, the current call_async always spawns + // a new coroutine, so we can't allow this; every coroutine + // needs to own its write lock on the store to make sure there + // are no overlapping mutable borrows. If this is something + // you're interested in, feel free to open a GitHub issue outlining + // your use-case. + panic!( + "Function::call_async futures cannot be polled recursively \ from within another imported function. If you need to await \ a recursive call_async, consider spawning the future into \ your async runtime and awaiting the resulting task; \ e.g. tokio::task::spawn(func.call_async(...)).await" - ); - } + ); } } } diff --git a/lib/api/src/backend/sys/entities/function/env.rs b/lib/api/src/backend/sys/entities/function/env.rs index 06ea196dd54..3d61ab14d54 100644 --- a/lib/api/src/backend/sys/entities/function/env.rs +++ b/lib/api/src/backend/sys/entities/function/env.rs @@ -225,14 +225,14 @@ pub(crate) enum AsyncFunctionEnvMutStore { } /// A read-only handle to the [`FunctionEnv`] in an [`AsyncFunctionEnvMut`]. -pub struct AsyncFunctionEnvHandle<'a, T> { - read_lock: AsyncStoreReadLock<'a>, +pub struct AsyncFunctionEnvHandle { + read_lock: AsyncStoreReadLock, pub(crate) func_env: FunctionEnv, } /// A mutable handle to the [`FunctionEnv`] in an [`AsyncFunctionEnvMut`]. -pub struct AsyncFunctionEnvHandleMut<'a, T> { - write_lock: AsyncStoreWriteLock<'a>, +pub struct AsyncFunctionEnvHandleMut { + write_lock: AsyncStoreWriteLock, pub(crate) func_env: FunctionEnv, } @@ -261,13 +261,12 @@ impl AsyncFunctionEnvMut { /// Waits for a store lock and returns a read-only handle to the /// function environment. - pub async fn read<'a>(&'a self) -> AsyncFunctionEnvHandle<'a, T> { + pub async fn read(&self) -> AsyncFunctionEnvHandle { let read_lock = match &self.store { - AsyncFunctionEnvMutStore::Sync(ptr) => AsyncStoreReadLock { - inner: crate::AsyncStoreReadLockInner::FromStoreContext(ptr.clone()), - _marker: PhantomData, - }, AsyncFunctionEnvMutStore::Async(store) => store.read_lock().await, + + // We can never acquire a store lock in a sync context + AsyncFunctionEnvMutStore::Sync(_) => futures::future::pending().await, }; AsyncFunctionEnvHandle { @@ -278,13 +277,12 @@ impl AsyncFunctionEnvMut { /// Waits for a store lock and returns a mutable handle to the /// function environment. - pub async fn write<'a>(&'a self) -> AsyncFunctionEnvHandleMut<'a, T> { + pub async fn write(&self) -> AsyncFunctionEnvHandleMut { let write_lock = match &self.store { - AsyncFunctionEnvMutStore::Sync(ptr) => AsyncStoreWriteLock { - inner: crate::AsyncStoreWriteLockInner::FromStoreContext(ptr.clone()), - _marker: PhantomData, - }, AsyncFunctionEnvMutStore::Async(store) => store.write_lock().await, + + // We can never acquire a store lock in a sync context + AsyncFunctionEnvMutStore::Sync(_) => futures::future::pending().await, }; AsyncFunctionEnvHandleMut { @@ -338,7 +336,7 @@ impl Clone for AsyncFunctionEnvMutStore { } } -impl AsyncFunctionEnvHandle<'_, T> { +impl AsyncFunctionEnvHandle { /// Returns a reference to the host state in this function environment. pub fn data(&self) -> &T { self.func_env.as_ref(&self.read_lock) @@ -350,13 +348,13 @@ impl AsyncFunctionEnvHandle<'_, T> { } } -impl AsStoreRef for AsyncFunctionEnvHandle<'_, T> { +impl AsStoreRef for AsyncFunctionEnvHandle { fn as_store_ref(&self) -> StoreRef<'_> { AsStoreRef::as_store_ref(&self.read_lock) } } -impl AsyncFunctionEnvHandleMut<'_, T> { +impl AsyncFunctionEnvHandleMut { /// Returns a mutable reference to the host state in this function environment. pub fn data_mut(&mut self) -> &mut T { self.func_env.as_mut(&mut self.write_lock) @@ -375,13 +373,13 @@ impl AsyncFunctionEnvHandleMut<'_, T> { } } -impl AsStoreRef for AsyncFunctionEnvHandleMut<'_, T> { +impl AsStoreRef for AsyncFunctionEnvHandleMut { fn as_store_ref(&self) -> StoreRef<'_> { AsStoreRef::as_store_ref(&self.write_lock) } } -impl AsStoreMut for AsyncFunctionEnvHandleMut<'_, T> { +impl AsStoreMut for AsyncFunctionEnvHandleMut { fn as_store_mut(&mut self) -> StoreMut<'_> { AsStoreMut::as_store_mut(&mut self.write_lock) } diff --git a/lib/api/src/entities/function/env/inner.rs b/lib/api/src/entities/function/env/inner.rs index 10c5a388f61..d5f2bcd3be9 100644 --- a/lib/api/src/entities/function/env/inner.rs +++ b/lib/api/src/entities/function/env/inner.rs @@ -256,24 +256,24 @@ pub enum BackendAsyncFunctionEnvMut { /// A read-only handle to the [`FunctionEnv`] in an [`AsyncFunctionEnvMut`]. #[non_exhaustive] -pub enum BackendAsyncFunctionEnvHandle<'a, T> { +pub enum BackendAsyncFunctionEnvHandle { #[cfg(feature = "sys")] /// The function environment handle for the `sys` runtime. - Sys(crate::backend::sys::function::env::AsyncFunctionEnvHandle<'a, T>), + Sys(crate::backend::sys::function::env::AsyncFunctionEnvHandle), } /// A mutable handle to the [`FunctionEnv`] in an [`AsyncFunctionEnvMut`]. #[non_exhaustive] -pub enum BackendAsyncFunctionEnvHandleMut<'a, T> { +pub enum BackendAsyncFunctionEnvHandleMut { #[cfg(feature = "sys")] /// The function environment handle for the `sys` runtime. - Sys(crate::backend::sys::function::env::AsyncFunctionEnvHandleMut<'a, T>), + Sys(crate::backend::sys::function::env::AsyncFunctionEnvHandleMut), } impl BackendAsyncFunctionEnvMut { /// Waits for a store lock and returns a read-only handle to the /// function environment. - pub async fn read<'a>(&'a self) -> BackendAsyncFunctionEnvHandle<'a, T> { + pub async fn read(&self) -> BackendAsyncFunctionEnvHandle { match self { #[cfg(feature = "sys")] Self::Sys(f) => BackendAsyncFunctionEnvHandle::Sys(f.read().await), @@ -283,7 +283,7 @@ impl BackendAsyncFunctionEnvMut { /// Waits for a store lock and returns a mutable handle to the /// function environment. - pub async fn write<'a>(&'a self) -> BackendAsyncFunctionEnvHandleMut<'a, T> { + pub async fn write(&self) -> BackendAsyncFunctionEnvHandleMut { match self { #[cfg(feature = "sys")] Self::Sys(f) => BackendAsyncFunctionEnvHandleMut::Sys(f.write().await), @@ -319,7 +319,7 @@ impl BackendAsyncFunctionEnvMut { } } -impl BackendAsyncFunctionEnvHandle<'_, T> { +impl BackendAsyncFunctionEnvHandle { /// Returns a reference to the host state in this function environment. pub fn data(&self) -> &T { match self { @@ -339,7 +339,7 @@ impl BackendAsyncFunctionEnvHandle<'_, T> { } } -impl AsStoreRef for BackendAsyncFunctionEnvHandle<'_, T> { +impl AsStoreRef for BackendAsyncFunctionEnvHandle { fn as_store_ref(&self) -> StoreRef<'_> { match self { #[cfg(feature = "sys")] @@ -349,7 +349,7 @@ impl AsStoreRef for BackendAsyncFunctionEnvHandle<'_, T> { } } -impl BackendAsyncFunctionEnvHandleMut<'_, T> { +impl BackendAsyncFunctionEnvHandleMut { /// Returns a mutable reference to the host state in this function environment. pub fn data_mut(&mut self) -> &mut T { match self { @@ -369,7 +369,7 @@ impl BackendAsyncFunctionEnvHandleMut<'_, T> { } } -impl AsStoreRef for BackendAsyncFunctionEnvHandleMut<'_, T> { +impl AsStoreRef for BackendAsyncFunctionEnvHandleMut { fn as_store_ref(&self) -> StoreRef<'_> { match self { #[cfg(feature = "sys")] @@ -379,7 +379,7 @@ impl AsStoreRef for BackendAsyncFunctionEnvHandleMut<'_, T> { } } -impl AsStoreMut for BackendAsyncFunctionEnvHandleMut<'_, T> { +impl AsStoreMut for BackendAsyncFunctionEnvHandleMut { fn as_store_mut(&mut self) -> StoreMut<'_> { match self { #[cfg(feature = "sys")] diff --git a/lib/api/src/entities/function/env/mod.rs b/lib/api/src/entities/function/env/mod.rs index c917aa4be5c..b9351c92b0d 100644 --- a/lib/api/src/entities/function/env/mod.rs +++ b/lib/api/src/entities/function/env/mod.rs @@ -121,21 +121,21 @@ where pub struct AsyncFunctionEnvMut(pub(crate) BackendAsyncFunctionEnvMut); /// A read-only handle to the [`FunctionEnv`] in an [`AsyncFunctionEnvMut`]. -pub struct AsyncFunctionEnvHandle<'a, T>(pub(crate) BackendAsyncFunctionEnvHandle<'a, T>); +pub struct AsyncFunctionEnvHandle(pub(crate) BackendAsyncFunctionEnvHandle); /// A mutable handle to the [`FunctionEnv`] in an [`AsyncFunctionEnvMut`]. -pub struct AsyncFunctionEnvHandleMut<'a, T>(pub(crate) BackendAsyncFunctionEnvHandleMut<'a, T>); +pub struct AsyncFunctionEnvHandleMut(pub(crate) BackendAsyncFunctionEnvHandleMut); impl AsyncFunctionEnvMut { /// Waits for a store lock and returns a read-only handle to the /// function environment. - pub async fn read<'a>(&'a self) -> AsyncFunctionEnvHandle<'a, T> { + pub async fn read(&self) -> AsyncFunctionEnvHandle { AsyncFunctionEnvHandle(self.0.read().await) } /// Waits for a store lock and returns a mutable handle to the /// function environment. - pub async fn write<'a>(&'a self) -> AsyncFunctionEnvHandleMut<'a, T> { + pub async fn write(&self) -> AsyncFunctionEnvHandleMut { AsyncFunctionEnvHandleMut(self.0.write().await) } @@ -155,7 +155,7 @@ impl AsyncFunctionEnvMut { } } -impl AsyncFunctionEnvHandle<'_, T> { +impl AsyncFunctionEnvHandle { /// Returns a reference to the host state in this function environment. pub fn data(&self) -> &T { self.0.data() @@ -167,13 +167,13 @@ impl AsyncFunctionEnvHandle<'_, T> { } } -impl AsStoreRef for AsyncFunctionEnvHandle<'_, T> { +impl AsStoreRef for AsyncFunctionEnvHandle { fn as_store_ref(&self) -> StoreRef<'_> { AsStoreRef::as_store_ref(&self.0) } } -impl AsyncFunctionEnvHandleMut<'_, T> { +impl AsyncFunctionEnvHandleMut { /// Returns a mutable reference to the host state in this function environment. pub fn data_mut(&mut self) -> &mut T { self.0.data_mut() @@ -185,13 +185,13 @@ impl AsyncFunctionEnvHandleMut<'_, T> { } } -impl AsStoreRef for AsyncFunctionEnvHandleMut<'_, T> { +impl AsStoreRef for AsyncFunctionEnvHandleMut { fn as_store_ref(&self) -> StoreRef<'_> { AsStoreRef::as_store_ref(&self.0) } } -impl AsStoreMut for AsyncFunctionEnvHandleMut<'_, T> { +impl AsStoreMut for AsyncFunctionEnvHandleMut { fn as_store_mut(&mut self) -> StoreMut<'_> { AsStoreMut::as_store_mut(&mut self.0) } diff --git a/lib/api/src/entities/store/async_.rs b/lib/api/src/entities/store/async_.rs index a578b9ceab6..b3cb562dc02 100644 --- a/lib/api/src/entities/store/async_.rs +++ b/lib/api/src/entities/store/async_.rs @@ -45,7 +45,7 @@ impl StoreAsync { /// Acquire a read lock on the store. Panics if the store is /// locked for writing. - pub fn read<'a>(&'a self) -> AsyncStoreReadLock<'a> { + pub fn read(&self) -> AsyncStoreReadLock { if !StoreContext::is_empty() { panic!("This method cannot be called from inside imported functions"); } @@ -54,24 +54,18 @@ impl StoreAsync { .inner .try_read() .expect("StoreAsync is locked for write"); - AsyncStoreReadLock { - inner: AsyncStoreReadLockInner::Owned(store_ref), - _marker: PhantomData, - } + AsyncStoreReadLock { inner: store_ref } } /// Acquire a write lock on the store. Panics if the store is /// locked. - pub fn write<'a>(&'a self) -> AsyncStoreWriteLock<'a> { + pub fn write(self) -> AsyncStoreWriteLock { if !StoreContext::is_empty() { panic!("This method cannot be called from inside imported functions"); } let store_guard = self.inner.try_write().expect("StoreAsync is locked"); - AsyncStoreWriteLock { - inner: AsyncStoreWriteLockInner::Owned(store_guard), - _marker: PhantomData, - } + AsyncStoreWriteLock { inner: store_guard } } } @@ -96,12 +90,12 @@ pub trait AsStoreAsync { } /// Acquires a read lock on the store. - fn read_lock<'a>(&'a self) -> impl Future> + 'a { + fn read_lock(&self) -> impl Future { AsyncStoreReadLock::acquire(self.store_ref()) } /// Acquires a write lock on the store. - fn write_lock<'a>(&'a self) -> impl Future> + 'a { + fn write_lock(&self) -> impl Future { AsyncStoreWriteLock::acquire(self.store_ref()) } } @@ -112,102 +106,50 @@ impl AsStoreAsync for StoreAsync { } } -pub(crate) enum AsyncStoreReadLockInner { - Owned(LocalRwLockReadGuard), - FromStoreContext(StorePtrWrapper), -} - /// A read lock on an async store. -pub struct AsyncStoreReadLock<'a> { - pub(crate) inner: AsyncStoreReadLockInner, - pub(crate) _marker: PhantomData<&'a ()>, +pub struct AsyncStoreReadLock { + pub(crate) inner: LocalRwLockReadGuard, } -impl<'a> AsyncStoreReadLock<'a> { - pub(crate) async fn acquire(store: &'a StoreAsync) -> Self { - let store_context = unsafe { StoreContext::try_get_current(store.id) }; - match store_context { - Some(store_mut_wrapper) => Self { - inner: AsyncStoreReadLockInner::FromStoreContext(store_mut_wrapper), - _marker: PhantomData, - }, - None => { - // Drop the option before awaiting, since the value isn't Send - drop(store_context); - let store_ref = store.inner.read().await; - Self { - inner: AsyncStoreReadLockInner::Owned(store_ref), - _marker: PhantomData, - } - } - } +impl AsyncStoreReadLock { + pub(crate) async fn acquire(store: &StoreAsync) -> Self { + let store_ref = store.inner.read().await; + Self { inner: store_ref } } } -impl AsStoreRef for AsyncStoreReadLock<'_> { +impl AsStoreRef for AsyncStoreReadLock { fn as_store_ref(&self) -> StoreRef<'_> { - match &self.inner { - AsyncStoreReadLockInner::Owned(guard) => StoreRef { inner: guard }, - AsyncStoreReadLockInner::FromStoreContext(wrapper) => wrapper.as_ref(), - } + StoreRef { inner: &self.inner } } } -pub(crate) enum AsyncStoreWriteLockInner { - Owned(LocalRwLockWriteGuard), - FromStoreContext(StorePtrWrapper), -} - /// A write lock on an async store. -pub struct AsyncStoreWriteLock<'a> { - pub(crate) inner: AsyncStoreWriteLockInner, - pub(crate) _marker: PhantomData<&'a ()>, +pub struct AsyncStoreWriteLock { + pub(crate) inner: LocalRwLockWriteGuard, } -impl<'a> AsyncStoreWriteLock<'a> { - pub(crate) async fn acquire(store: &'a StoreAsync) -> Self { - let store_context = unsafe { StoreContext::try_get_current(store.id) }; - match store_context { - Some(store_mut_wrapper) => Self { - inner: AsyncStoreWriteLockInner::FromStoreContext(store_mut_wrapper), - _marker: PhantomData, - }, - None => { - // Drop the option before awaiting, since the value isn't Send - drop(store_context); - let store_guard = store.inner.write().await; - Self { - inner: AsyncStoreWriteLockInner::Owned(store_guard), - _marker: PhantomData, - } - } - } +impl AsyncStoreWriteLock { + pub(crate) async fn acquire(store: &StoreAsync) -> Self { + let store_guard = store.inner.write().await; + Self { inner: store_guard } } } -impl AsStoreRef for AsyncStoreWriteLock<'_> { +impl AsStoreRef for AsyncStoreWriteLock { fn as_store_ref(&self) -> StoreRef<'_> { - match &self.inner { - AsyncStoreWriteLockInner::Owned(guard) => StoreRef { inner: guard }, - AsyncStoreWriteLockInner::FromStoreContext(wrapper) => wrapper.as_ref(), - } + StoreRef { inner: &self.inner } } } -impl AsStoreMut for AsyncStoreWriteLock<'_> { +impl AsStoreMut for AsyncStoreWriteLock { fn as_store_mut(&mut self) -> StoreMut<'_> { - match &mut self.inner { - AsyncStoreWriteLockInner::Owned(guard) => StoreMut { inner: &mut *guard }, - AsyncStoreWriteLockInner::FromStoreContext(wrapper) => wrapper.as_mut(), + StoreMut { + inner: &mut self.inner, } } fn objects_mut(&mut self) -> &mut super::StoreObjects { - match &mut self.inner { - AsyncStoreWriteLockInner::Owned(guard) => &mut guard.objects, - AsyncStoreWriteLockInner::FromStoreContext(wrapper) => { - &mut wrapper.as_mut().inner.objects - } - } + &mut self.inner.objects } } From 888801b60eea304ddba55d27021db78482607622 Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Sat, 29 Nov 2025 18:43:02 +0400 Subject: [PATCH 36/49] oopsie! --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 21b5e206e2e..8505e4a4fab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -275,7 +275,6 @@ iprange = "0.6.7" smoltcp = { version = "0.8", default-features = false } tokio-serde = "0.9" tokio-util = "0.7.8" -corosensei = "0.3.0" crossbeam-queue = "0.3.8" fnv = "1.0.3" scopeguard = "1.1.0" From 2a6cdb8ae703c0fd023ccecb907e7ab35946317d Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Mon, 1 Dec 2025 10:03:10 +0400 Subject: [PATCH 37/49] Update and fix lints in other backends --- .../src/backend/js/entities/function/mod.rs | 6 +++--- .../src/backend/jsc/entities/function/env.rs | 4 ++-- .../src/backend/jsc/entities/function/mod.rs | 9 +++++---- lib/api/src/backend/jsc/entities/store/obj.rs | 2 +- .../src/backend/v8/entities/function/env.rs | 4 ++-- .../src/backend/v8/entities/function/mod.rs | 7 ++++--- lib/api/src/backend/v8/entities/store/obj.rs | 2 +- lib/api/src/backend/v8/vm/mod.rs | 2 +- .../src/backend/wamr/entities/function/mod.rs | 9 +++++---- .../backend/wasmi/entities/function/env.rs | 4 ++-- .../backend/wasmi/entities/function/mod.rs | 7 ++++--- .../src/backend/wasmi/entities/store/obj.rs | 2 +- lib/api/src/backend/wasmi/vm/mod.rs | 2 +- lib/api/src/entities/function/env/inner.rs | 19 ++++++++++++++----- .../src/wasm_c_api/externals/function.rs | 4 +--- lib/compiler/src/misc.rs | 13 +++++++------ 16 files changed, 54 insertions(+), 42 deletions(-) diff --git a/lib/api/src/backend/js/entities/function/mod.rs b/lib/api/src/backend/js/entities/function/mod.rs index 43b658ae1dc..d37915d03cf 100644 --- a/lib/api/src/backend/js/entities/function/mod.rs +++ b/lib/api/src/backend/js/entities/function/mod.rs @@ -10,9 +10,9 @@ use wasmer_types::{FunctionType, RawValue}; use crate::{ AsStoreMut, AsStoreRef, BackendFunction, BackendFunctionEnv, BackendFunctionEnvMut, - DynamicFunctionResult, FromToNativeWasmType, FunctionEnv, FunctionEnvMut, HostFunction, - HostFunctionKind, IntoResult, NativeWasmType, NativeWasmTypeInto, RuntimeError, StoreMut, - Value, WasmTypeList, WithEnv, WithoutEnv, + DynamicCallResult, DynamicFunctionResult, FromToNativeWasmType, FunctionEnv, FunctionEnvMut, + HostFunction, HostFunctionKind, IntoResult, NativeWasmType, NativeWasmTypeInto, RuntimeError, + StoreMut, Value, WasmTypeList, WithEnv, WithoutEnv, js::{ utils::convert::{AsJs as _, js_value_to_wasmer, wasmer_value_to_js}, vm::{VMFuncRef, VMFunctionCallback, function::VMFunction}, diff --git a/lib/api/src/backend/jsc/entities/function/env.rs b/lib/api/src/backend/jsc/entities/function/env.rs index 70e7c7453f0..88739f08a50 100644 --- a/lib/api/src/backend/jsc/entities/function/env.rs +++ b/lib/api/src/backend/jsc/entities/function/env.rs @@ -62,7 +62,7 @@ impl FunctionEnv { } /// Convert it into a `FunctionEnvMut` - pub fn into_mut(self, store: &mut impl AsStoreMut) -> FunctionEnvMut + pub fn into_mut<'a>(self, store: &'a mut impl AsStoreMut) -> FunctionEnvMut<'a, T> where T: Any + Send + 'static + Sized, { @@ -137,7 +137,7 @@ impl FunctionEnvMut<'_, T> { } /// Borrows a new mutable reference of both the attached Store and host state - pub fn data_and_store_mut(&mut self) -> (&mut T, StoreMut) { + pub fn data_and_store_mut<'a>(&'a mut self) -> (&'a mut T, StoreMut<'a>) { let data = self.func_env.as_mut(&mut self.store_mut) as *mut T; // telling the borrow check to close his eyes here // this is still relatively safe to do as func_env are diff --git a/lib/api/src/backend/jsc/entities/function/mod.rs b/lib/api/src/backend/jsc/entities/function/mod.rs index 9570b89b0b9..6f1ea59190c 100644 --- a/lib/api/src/backend/jsc/entities/function/mod.rs +++ b/lib/api/src/backend/jsc/entities/function/mod.rs @@ -9,9 +9,10 @@ use std::panic::{self, AssertUnwindSafe}; use wasmer_types::{FunctionType, RawValue}; use crate::{ - AsStoreMut, AsStoreRef, BackendFunction, BackendFunctionEnvMut, FromToNativeWasmType, - FunctionEnv, FunctionEnvMut, HostFunction, HostFunctionKind, IntoResult, NativeWasmType, - NativeWasmTypeInto, RuntimeError, StoreMut, Value, WasmTypeList, WithEnv, WithoutEnv, + AsStoreMut, AsStoreRef, BackendFunction, BackendFunctionEnvMut, DynamicCallResult, + DynamicFunctionResult, FromToNativeWasmType, FunctionEnv, FunctionEnvMut, HostFunction, + HostFunctionKind, IntoResult, NativeWasmType, NativeWasmTypeInto, RuntimeError, StoreMut, + Value, WasmTypeList, WithEnv, WithoutEnv, jsc::{ store::{InternalStoreHandle, StoreHandle}, utils::convert::{AsJsc, jsc_value_to_wasmer}, @@ -287,7 +288,7 @@ impl std::fmt::Debug for Function { /// Represents a low-level Wasm static host function. See /// `super::Function::new` and `super::Function::new_env` to learn /// more. -#[derive(Clone, Debug, Hash, PartialEq, Eq)] +#[derive(Clone, Debug, Hash)] pub struct WasmFunction { callback: JSObjectCallAsFunctionCallback, _phantom: PhantomData<(Args, Rets)>, diff --git a/lib/api/src/backend/jsc/entities/store/obj.rs b/lib/api/src/backend/jsc/entities/store/obj.rs index e15b1cde400..e840a257a2a 100644 --- a/lib/api/src/backend/jsc/entities/store/obj.rs +++ b/lib/api/src/backend/jsc/entities/store/obj.rs @@ -80,7 +80,7 @@ impl StoreObjects { } /// Return an immutable iterator over all globals - pub fn iter_globals(&self) -> core::slice::Iter { + pub fn iter_globals<'a>(&'a self) -> core::slice::Iter<'a, VMGlobal> { self.globals.iter() } diff --git a/lib/api/src/backend/v8/entities/function/env.rs b/lib/api/src/backend/v8/entities/function/env.rs index 17e668e8159..cff3675a385 100644 --- a/lib/api/src/backend/v8/entities/function/env.rs +++ b/lib/api/src/backend/v8/entities/function/env.rs @@ -62,7 +62,7 @@ impl FunctionEnv { } /// Convert it into a `FunctionEnvMut` - pub fn into_mut(self, store: &mut impl AsStoreMut) -> FunctionEnvMut + pub fn into_mut<'a>(self, store: &'a mut impl AsStoreMut) -> FunctionEnvMut<'a, T> where T: Any + Send + 'static + Sized, { @@ -137,7 +137,7 @@ impl FunctionEnvMut<'_, T> { } /// Borrows a new mutable reference of both the attached Store and host state - pub fn data_and_store_mut(&mut self) -> (&mut T, StoreMut) { + pub fn data_and_store_mut<'a>(&'a mut self) -> (&'a mut T, StoreMut<'a>) { let data = self.func_env.as_mut(&mut self.store_mut) as *mut T; // telling the borrow check to close his eyes here // this is still relatively safe to do as func_env are diff --git a/lib/api/src/backend/v8/entities/function/mod.rs b/lib/api/src/backend/v8/entities/function/mod.rs index e11da467391..c11c2bd436c 100644 --- a/lib/api/src/backend/v8/entities/function/mod.rs +++ b/lib/api/src/backend/v8/entities/function/mod.rs @@ -6,9 +6,10 @@ use std::{ }; use crate::{ - AsStoreMut, AsStoreRef, BackendFunction, BackendFunctionEnvMut, BackendTrap, - FromToNativeWasmType, FunctionEnv, FunctionEnvMut, IntoResult, NativeWasmType, - NativeWasmTypeInto, RuntimeError, StoreMut, Value, WasmTypeList, WithEnv, WithoutEnv, + AsStoreMut, AsStoreRef, BackendFunction, BackendFunctionEnvMut, BackendTrap, DynamicCallResult, + DynamicFunctionResult, FromToNativeWasmType, FunctionEnv, FunctionEnvMut, IntoResult, + NativeWasmType, NativeWasmTypeInto, RuntimeError, StoreMut, Value, WasmTypeList, WithEnv, + WithoutEnv, v8::{ bindings::*, utils::convert::{IntoCApiType, IntoCApiValue, IntoWasmerType, IntoWasmerValue}, diff --git a/lib/api/src/backend/v8/entities/store/obj.rs b/lib/api/src/backend/v8/entities/store/obj.rs index d33c7bd5842..fbfb0dd9018 100644 --- a/lib/api/src/backend/v8/entities/store/obj.rs +++ b/lib/api/src/backend/v8/entities/store/obj.rs @@ -107,7 +107,7 @@ impl StoreObjects { } /// Return an immutable iterator over all globals - pub fn iter_globals(&self) -> core::slice::Iter { + pub fn iter_globals<'a>(&'a self) -> core::slice::Iter<'a, VMGlobal> { self.globals.iter() } diff --git a/lib/api/src/backend/v8/vm/mod.rs b/lib/api/src/backend/v8/vm/mod.rs index f0827da873e..7c798c2589c 100644 --- a/lib/api/src/backend/v8/vm/mod.rs +++ b/lib/api/src/backend/v8/vm/mod.rs @@ -133,7 +133,7 @@ impl VMFuncRef { } } -pub(crate) struct VMExceptionRef(*mut wasm_ref_t); +pub struct VMExceptionRef(*mut wasm_ref_t); impl VMExceptionRef { /// Converts the `VMExceptionRef` into a `RawValue`. pub fn into_raw(self) -> RawValue { diff --git a/lib/api/src/backend/wamr/entities/function/mod.rs b/lib/api/src/backend/wamr/entities/function/mod.rs index c8d67fe859d..955cd7779ec 100644 --- a/lib/api/src/backend/wamr/entities/function/mod.rs +++ b/lib/api/src/backend/wamr/entities/function/mod.rs @@ -7,9 +7,10 @@ use std::{ }; use crate::{ - AsStoreMut, AsStoreRef, BackendFunction, BackendFunctionEnvMut, BackendTrap, - FromToNativeWasmType, FunctionEnv, FunctionEnvMut, IntoResult, NativeWasmType, - NativeWasmTypeInto, RuntimeError, StoreMut, Value, WasmTypeList, WithEnv, WithoutEnv, + AsStoreMut, AsStoreRef, BackendFunction, BackendFunctionEnvMut, BackendTrap, DynamicCallResult, + DynamicFunctionResult, FromToNativeWasmType, FunctionEnv, FunctionEnvMut, IntoResult, + NativeWasmType, NativeWasmTypeInto, RuntimeError, StoreMut, Value, WasmTypeList, WithEnv, + WithoutEnv, vm::{VMExtern, VMExternFunction}, wamr::{ bindings::*, @@ -81,7 +82,7 @@ impl Function { ) -> Self where FT: Into, - F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> DynamicFunctionResult + Send + Sync, + F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> DynamicFunctionResult + 'static + Send + Sync, { let fn_ty: FunctionType = ty.into(); let params = fn_ty.params(); diff --git a/lib/api/src/backend/wasmi/entities/function/env.rs b/lib/api/src/backend/wasmi/entities/function/env.rs index 5469aa6ab1c..04e691d96d0 100644 --- a/lib/api/src/backend/wasmi/entities/function/env.rs +++ b/lib/api/src/backend/wasmi/entities/function/env.rs @@ -62,7 +62,7 @@ impl FunctionEnv { } /// Convert it into a `FunctionEnvMut` - pub fn into_mut(self, store: &mut impl AsStoreMut) -> FunctionEnvMut + pub fn into_mut<'a>(self, store: &'a mut impl AsStoreMut) -> FunctionEnvMut<'a, T> where T: Any + Send + 'static + Sized, { @@ -137,7 +137,7 @@ impl FunctionEnvMut<'_, T> { } /// Borrows a new mutable reference of both the attached Store and host state - pub fn data_and_store_mut(&mut self) -> (&mut T, StoreMut) { + pub fn data_and_store_mut<'a>(&'a mut self) -> (&'a mut T, StoreMut<'a>) { let data = self.func_env.as_mut(&mut self.store_mut) as *mut T; // telling the borrow check to close his eyes here // this is still relatively safe to do as func_env are diff --git a/lib/api/src/backend/wasmi/entities/function/mod.rs b/lib/api/src/backend/wasmi/entities/function/mod.rs index 7f0422d8ca7..d6909fc5bb3 100644 --- a/lib/api/src/backend/wasmi/entities/function/mod.rs +++ b/lib/api/src/backend/wasmi/entities/function/mod.rs @@ -7,9 +7,10 @@ use std::{ }; use crate::{ - AsStoreMut, AsStoreRef, BackendFunction, BackendFunctionEnvMut, BackendTrap, - FromToNativeWasmType, FunctionEnv, FunctionEnvMut, IntoResult, NativeWasmType, - NativeWasmTypeInto, RuntimeError, StoreMut, Value, WasmTypeList, WithEnv, WithoutEnv, + AsStoreMut, AsStoreRef, BackendFunction, BackendFunctionEnvMut, BackendTrap, DynamicCallResult, + DynamicFunctionResult, FromToNativeWasmType, FunctionEnv, FunctionEnvMut, IntoResult, + NativeWasmType, NativeWasmTypeInto, RuntimeError, StoreMut, Value, WasmTypeList, WithEnv, + WithoutEnv, vm::{VMExtern, VMExternFunction}, wasmi::{ bindings::*, diff --git a/lib/api/src/backend/wasmi/entities/store/obj.rs b/lib/api/src/backend/wasmi/entities/store/obj.rs index 26a9d9cde51..3ee18e5bfea 100644 --- a/lib/api/src/backend/wasmi/entities/store/obj.rs +++ b/lib/api/src/backend/wasmi/entities/store/obj.rs @@ -107,7 +107,7 @@ impl StoreObjects { } /// Return an immutable iterator over all globals - pub fn iter_globals(&self) -> core::slice::Iter { + pub fn iter_globals<'a>(&'a self) -> core::slice::Iter<'a, VMGlobal> { self.globals.iter() } diff --git a/lib/api/src/backend/wasmi/vm/mod.rs b/lib/api/src/backend/wasmi/vm/mod.rs index c712232362d..055785fb8f3 100644 --- a/lib/api/src/backend/wasmi/vm/mod.rs +++ b/lib/api/src/backend/wasmi/vm/mod.rs @@ -128,7 +128,7 @@ impl VMFuncRef { } } -pub(crate) struct VMExceptionRef(*mut wasm_ref_t); +pub struct VMExceptionRef(*mut wasm_ref_t); impl VMExceptionRef { /// Converts the `VMExceptionRef` into a `RawValue`. pub fn into_raw(self) -> RawValue { diff --git a/lib/api/src/entities/function/env/inner.rs b/lib/api/src/entities/function/env/inner.rs index d5f2bcd3be9..34e9a1d055a 100644 --- a/lib/api/src/entities/function/env/inner.rs +++ b/lib/api/src/entities/function/env/inner.rs @@ -206,7 +206,7 @@ impl BackendFunctionEnvMut<'_, T> { match self { #[cfg(feature = "sys")] Self::Sys(f) => f.as_store_async(), - _ => unsupported_async_backend(), + _ => unsupported_async_backend::>(), } } } @@ -252,6 +252,9 @@ pub enum BackendAsyncFunctionEnvMut { #[cfg(feature = "sys")] /// The function environment for the `sys` runtime. Sys(crate::backend::sys::function::env::AsyncFunctionEnvMut), + #[cfg(not(feature = "sys"))] + /// Placeholder for unsupported backends. + Unsupported(PhantomData), } /// A read-only handle to the [`FunctionEnv`] in an [`AsyncFunctionEnvMut`]. @@ -260,6 +263,9 @@ pub enum BackendAsyncFunctionEnvHandle { #[cfg(feature = "sys")] /// The function environment handle for the `sys` runtime. Sys(crate::backend::sys::function::env::AsyncFunctionEnvHandle), + #[cfg(not(feature = "sys"))] + /// Placeholder for unsupported backends. + Unsupported(PhantomData), } /// A mutable handle to the [`FunctionEnv`] in an [`AsyncFunctionEnvMut`]. @@ -268,6 +274,9 @@ pub enum BackendAsyncFunctionEnvHandleMut { #[cfg(feature = "sys")] /// The function environment handle for the `sys` runtime. Sys(crate::backend::sys::function::env::AsyncFunctionEnvHandleMut), + #[cfg(not(feature = "sys"))] + /// Placeholder for unsupported backends. + Unsupported(PhantomData), } impl BackendAsyncFunctionEnvMut { @@ -314,7 +323,7 @@ impl BackendAsyncFunctionEnvMut { match self { #[cfg(feature = "sys")] Self::Sys(f) => f.as_store_async(), - _ => unsupported_async_backend(), + _ => unsupported_async_backend::(), } } } @@ -334,7 +343,7 @@ impl BackendAsyncFunctionEnvHandle { match self { #[cfg(feature = "sys")] Self::Sys(f) => f.data_and_store(), - _ => unsupported_async_backend(), + _ => unsupported_async_backend::<(&T, &StoreRef)>(), } } } @@ -364,7 +373,7 @@ impl BackendAsyncFunctionEnvHandleMut { match self { #[cfg(feature = "sys")] Self::Sys(f) => f.data_and_store_mut(), - _ => unsupported_async_backend(), + _ => unsupported_async_backend::<(&mut T, &mut crate::StoreMut)>(), } } } @@ -397,6 +406,6 @@ impl AsStoreMut for BackendAsyncFunctionEnvHandleMut { } } -fn unsupported_async_backend() -> ! { +fn unsupported_async_backend() -> T { panic!("async functions are only supported with the `sys` backend"); } diff --git a/lib/c-api/src/wasm_c_api/externals/function.rs b/lib/c-api/src/wasm_c_api/externals/function.rs index d607f09c4b9..c64c0741d7f 100644 --- a/lib/c-api/src/wasm_c_api/externals/function.rs +++ b/lib/c-api/src/wasm_c_api/externals/function.rs @@ -8,9 +8,7 @@ use libc::c_void; use std::convert::TryInto; use std::mem::MaybeUninit; use std::sync::{Arc, Mutex}; -use wasmer_api::{ - DynamicFunctionResult, Extern, Function, FunctionEnv, FunctionEnvMut, RuntimeError, Value, -}; +use wasmer_api::{DynamicFunctionResult, Extern, Function, FunctionEnv, FunctionEnvMut, Value}; #[derive(Clone)] #[allow(non_camel_case_types)] diff --git a/lib/compiler/src/misc.rs b/lib/compiler/src/misc.rs index d6c2b9ed8ad..3511b6baac0 100644 --- a/lib/compiler/src/misc.rs +++ b/lib/compiler/src/misc.rs @@ -1,17 +1,18 @@ //! A common functionality used among various compilers. use core::fmt::Display; -use std::{ - collections::HashMap, - path::PathBuf, - process::{Command, Stdio}, -}; +use std::{collections::HashMap, path::PathBuf}; + +#[cfg(not(target_arch = "wasm32"))] +use std::process::{Command, Stdio}; use itertools::Itertools; use target_lexicon::Architecture; -use tempfile::NamedTempFile; use wasmer_types::{CompileError, FunctionType, LocalFunctionIndex, Type}; +#[cfg(not(target_arch = "wasm32"))] +use tempfile::NamedTempFile; + /// Represents the kind of compiled function or module, used for debugging and identification /// purposes across multiple compiler backends (e.g., LLVM, Cranelift). #[derive(Debug, Clone)] From 3ed7a1e1e20bf62d7258daf5622b4b92e15a6d26 Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Mon, 1 Dec 2025 11:12:30 +0400 Subject: [PATCH 38/49] * Add experimental-async feature flag to API crate * Add async and non-async checks in CI * Fix docs build --- .github/workflows/test.yaml | 5 +++ Makefile | 11 ++++--- lib/api/Cargo.toml | 9 +++-- .../src/backend/sys/entities/function/env.rs | 19 +++++++++-- .../src/backend/sys/entities/function/mod.rs | 29 ++++++++++------ .../backend/sys/entities/function/typed.rs | 10 ++++-- lib/api/src/backend/sys/mod.rs | 1 + lib/api/src/entities/function/env/inner.rs | 33 ++++++++++++++++--- lib/api/src/entities/function/env/mod.rs | 16 +++++++-- lib/api/src/entities/function/inner.rs | 22 +++++++++---- lib/api/src/entities/function/mod.rs | 15 +++++++-- lib/api/src/entities/store/context.rs | 16 +++++---- lib/api/src/entities/store/local_rwlock.rs | 4 +-- lib/api/src/entities/store/mod.rs | 5 +++ lib/api/src/entities/store/store_ref.rs | 8 ++--- lib/api/src/utils/native/typed_func.rs | 7 ++-- lib/api/tests/jspi_async.rs | 2 ++ lib/api/tests/simple_greenthread.rs | 2 ++ lib/wasix/Cargo.toml | 3 +- 19 files changed, 162 insertions(+), 55 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 7b8a86892b3..543d2a7b6a2 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -518,6 +518,11 @@ jobs: build-cmd: "make build-wasmer && make package-wasmer && make tar-wasmer", name: "Build wasmer-cli", }, + { + key: "check", + build-cmd: "make check", + name: "Check wasmer API with all sys features enabled", + }, { key: "api-feats", build-cmd: "make check-api-features", diff --git a/Makefile b/Makefile index 4935a12b91c..336ccc7e352 100644 --- a/Makefile +++ b/Makefile @@ -437,11 +437,14 @@ endif # install will go through. all: build-wasmer build-capi build-capi-headless -check: check-wasmer check-capi +check: check-wasmer check-api-no-async check-capi check-wasmer: $(CARGO_BINARY) check $(CARGO_TARGET_FLAG) --manifest-path lib/cli/Cargo.toml $(compiler_features) --bin wasmer --locked +check-api-no-async: + $(CARGO_BINARY) check $(CARGO_TARGET_FLAG) --manifest-path lib/api/Cargo.toml $(compiler_features) --locked + check-capi: RUSTFLAGS="${RUSTFLAGS}" $(CARGO_BINARY) check $(CARGO_TARGET_FLAG) --manifest-path lib/c-api/Cargo.toml \ --no-default-features --features wat,compiler,wasi,middlewares $(capi_compiler_features) @@ -495,7 +498,7 @@ else endif build-docs: - $(CARGO_BINARY) doc $(CARGO_TARGET_FLAG) --release $(compiler_features) --document-private-items --no-deps --workspace --exclude wasmer-c-api --exclude wasmer-swift --locked + $(CARGO_BINARY) doc $(CARGO_TARGET_FLAG) --release $(compiler_features) --features wasmer/experimental-async --document-private-items --no-deps --workspace --exclude wasmer-c-api --exclude wasmer-swift --locked # The tokio crate was excluded from the docs build because the code (which is not under our control) # does not currently compile its docs successfully @@ -628,8 +631,8 @@ test-stage-0-wast: # test packages test-stage-1-test-all: - $(CARGO_BINARY) nextest run $(CARGO_TARGET_FLAG) --workspace --release $(exclude_tests) --exclude wasmer-c-api-test-runner --exclude wasmer-capi-examples-runner $(compiler_features) --locked && \ - $(CARGO_BINARY) test --doc $(CARGO_TARGET_FLAG) --workspace --release $(exclude_tests) --exclude wasmer-c-api-test-runner --exclude wasmer-capi-examples-runner $(compiler_features) --locked + $(CARGO_BINARY) nextest run $(CARGO_TARGET_FLAG) --workspace --release $(exclude_tests) --exclude wasmer-c-api-test-runner --exclude wasmer-capi-examples-runner $(compiler_features) --features wasmer/experimental-async --locked && \ + $(CARGO_BINARY) test --doc $(CARGO_TARGET_FLAG) --workspace --release $(exclude_tests) --exclude wasmer-c-api-test-runner --exclude wasmer-capi-examples-runner $(compiler_features) --features wasmer/experimental-async --locked test-stage-2-test-compiler-cranelift-nostd: $(CARGO_BINARY) test $(CARGO_TARGET_FLAG) --manifest-path lib/compiler-cranelift/Cargo.toml --release --no-default-features --features=std --locked test-stage-3-test-compiler-singlepass-nostd: diff --git a/lib/api/Cargo.toml b/lib/api/Cargo.toml index 423b175cbc1..a5c0ed4b562 100644 --- a/lib/api/Cargo.toml +++ b/lib/api/Cargo.toml @@ -60,12 +60,12 @@ wasmer-compiler = { path = "../compiler", version = "=6.1.0", optional = true } wasmer-derive = { path = "../derive", version = "=6.1.0" } wasmer-types = { path = "../types", version = "=6.1.0" } target-lexicon = { workspace = true, default-features = false } -corosensei = { workspace = true, optional = true } # - Optional dependencies for `sys`. wasmer-compiler-singlepass = { path = "../compiler-singlepass", version = "=6.1.0", optional = true } wasmer-compiler-cranelift = { path = "../compiler-cranelift", version = "=6.1.0", optional = true } wasmer-compiler-llvm = { path = "../compiler-llvm", version = "=6.1.0", optional = true } -futures = "0.3" +corosensei = { workspace = true, optional = true } +futures = { workspace = true, optional = true } wasm-bindgen = { workspace = true, optional = true } js-sys = { workspace = true, optional = true } @@ -129,9 +129,12 @@ artifact-size = [ ] # Features for `sys`. -sys = ["std", "dep:wasmer-vm", "dep:wasmer-compiler", "dep:corosensei"] +sys = ["std", "dep:wasmer-vm", "dep:wasmer-compiler"] sys-default = ["sys", "wat", "cranelift"] +# Experimental concurrent execution support +experimental-async = ["sys", "dep:futures", "dep:corosensei"] + # - Compilers. compiler = [ "sys", diff --git a/lib/api/src/backend/sys/entities/function/env.rs b/lib/api/src/backend/sys/entities/function/env.rs index 3d61ab14d54..61f43bd36a1 100644 --- a/lib/api/src/backend/sys/entities/function/env.rs +++ b/lib/api/src/backend/sys/entities/function/env.rs @@ -1,8 +1,9 @@ use std::{any::Any, fmt::Debug, marker::PhantomData}; +#[cfg(feature = "experimental-async")] +use crate::{AsStoreAsync, AsyncStoreReadLock, AsyncStoreWriteLock, StoreAsync}; use crate::{ - AsStoreAsync, AsyncStoreReadLock, AsyncStoreWriteLock, Store, StoreAsync, StoreContext, - StoreInner, StoreMut, StorePtrWrapper, + Store, StoreContext, StoreInner, StoreMut, StorePtrWrapper, store::{AsStoreMut, AsStoreRef, StoreRef}, }; @@ -171,6 +172,7 @@ impl FunctionEnvMut<'_, T> { /// it's already active in the current context, but can be used /// to spawn new coroutines via /// [`Function::call_async`](crate::Function::call_async). + #[cfg(feature = "experimental-async")] pub fn as_store_async(&self) -> Option { self.store_mut.as_store_async() } @@ -210,6 +212,7 @@ impl From> for crate::FunctionEnv { /// A shared handle to a [`FunctionEnv`], suitable for use /// in async imports. +#[cfg(feature = "experimental-async")] pub struct AsyncFunctionEnvMut { pub(crate) store: AsyncFunctionEnvMutStore, pub(crate) func_env: FunctionEnv, @@ -219,23 +222,27 @@ pub struct AsyncFunctionEnvMut { // in a sync context. To that end, `AsyncFunctionEnvMut` // must be able to be constructed without an actual // StoreAsync instance, hence this enum. +#[cfg(feature = "experimental-async")] pub(crate) enum AsyncFunctionEnvMutStore { Async(StoreAsync), Sync(StorePtrWrapper), } /// A read-only handle to the [`FunctionEnv`] in an [`AsyncFunctionEnvMut`]. +#[cfg(feature = "experimental-async")] pub struct AsyncFunctionEnvHandle { read_lock: AsyncStoreReadLock, pub(crate) func_env: FunctionEnv, } /// A mutable handle to the [`FunctionEnv`] in an [`AsyncFunctionEnvMut`]. +#[cfg(feature = "experimental-async")] pub struct AsyncFunctionEnvHandleMut { write_lock: AsyncStoreWriteLock, pub(crate) func_env: FunctionEnv, } +#[cfg(feature = "experimental-async")] impl Debug for AsyncFunctionEnvMut where T: Send + Debug + 'static, @@ -251,6 +258,7 @@ where } } +#[cfg(feature = "experimental-async")] impl AsyncFunctionEnvMut { pub(crate) fn store_id(&self) -> StoreId { match &self.store { @@ -315,6 +323,7 @@ impl AsyncFunctionEnvMut { } } +#[cfg(feature = "experimental-async")] impl Clone for AsyncFunctionEnvMut { fn clone(&self) -> Self { Self { @@ -324,6 +333,7 @@ impl Clone for AsyncFunctionEnvMut { } } +#[cfg(feature = "experimental-async")] impl Clone for AsyncFunctionEnvMutStore { fn clone(&self) -> Self { match self { @@ -336,6 +346,7 @@ impl Clone for AsyncFunctionEnvMutStore { } } +#[cfg(feature = "experimental-async")] impl AsyncFunctionEnvHandle { /// Returns a reference to the host state in this function environment. pub fn data(&self) -> &T { @@ -348,12 +359,14 @@ impl AsyncFunctionEnvHandle { } } +#[cfg(feature = "experimental-async")] impl AsStoreRef for AsyncFunctionEnvHandle { fn as_store_ref(&self) -> StoreRef<'_> { AsStoreRef::as_store_ref(&self.read_lock) } } +#[cfg(feature = "experimental-async")] impl AsyncFunctionEnvHandleMut { /// Returns a mutable reference to the host state in this function environment. pub fn data_mut(&mut self) -> &mut T { @@ -373,12 +386,14 @@ impl AsyncFunctionEnvHandleMut { } } +#[cfg(feature = "experimental-async")] impl AsStoreRef for AsyncFunctionEnvHandleMut { fn as_store_ref(&self) -> StoreRef<'_> { AsStoreRef::as_store_ref(&self.write_lock) } } +#[cfg(feature = "experimental-async")] impl AsStoreMut for AsyncFunctionEnvHandleMut { fn as_store_mut(&mut self) -> StoreMut<'_> { AsStoreMut::as_store_mut(&mut self.write_lock) diff --git a/lib/api/src/backend/sys/entities/function/mod.rs b/lib/api/src/backend/sys/entities/function/mod.rs index a8d9d53fdd8..75fdaa42baf 100644 --- a/lib/api/src/backend/sys/entities/function/mod.rs +++ b/lib/api/src/backend/sys/entities/function/mod.rs @@ -3,16 +3,20 @@ pub(crate) mod env; pub(crate) mod typed; +#[cfg(feature = "experimental-async")] use crate::{ - AsStoreAsync, AsyncFunctionEnvMut, BackendAsyncFunctionEnvMut, BackendFunction, - DynamicCallResult, DynamicFunctionResult, FunctionEnv, FunctionEnvMut, FunctionType, - HostFunction, RuntimeError, StoreAsync, StoreContext, StoreInner, Value, WithEnv, WithoutEnv, - backend::sys::{engine::NativeEngineExt, vm::VMFunctionCallback}, - entities::{ - function::async_host::{AsyncFunctionEnv, AsyncHostFunction}, - store::{AsStoreMut, AsStoreRef, StoreMut}, + AsStoreAsync, AsyncFunctionEnvMut, BackendAsyncFunctionEnvMut, StoreAsync, + entities::function::async_host::{AsyncFunctionEnv, AsyncHostFunction}, + sys::{ + async_runtime::{AsyncRuntimeError, block_on_host_future, call_function_async}, + function::env::AsyncFunctionEnvMutStore, }, - sys::{async_runtime::AsyncRuntimeError, function::env::AsyncFunctionEnvMutStore}, +}; +use crate::{ + BackendFunction, DynamicCallResult, DynamicFunctionResult, FunctionEnv, FunctionEnvMut, + FunctionType, HostFunction, RuntimeError, StoreContext, StoreInner, Value, WithEnv, WithoutEnv, + backend::sys::{engine::NativeEngineExt, vm::VMFunctionCallback}, + entities::store::{AsStoreMut, AsStoreRef, StoreMut}, utils::{FromToNativeWasmType, IntoResult, NativeWasmTypeInto, WasmTypeList}, vm::{VMExtern, VMExternFunction}, }; @@ -29,8 +33,6 @@ use wasmer_vm::{ wasmer_call_trampoline, }; -use crate::backend::sys::async_runtime::{block_on_host_future, call_function_async}; - #[cfg_attr(feature = "artifact-size", derive(loupe::MemoryUsage))] #[derive(Debug, Clone, PartialEq, Eq)] /// A WebAssembly `function` instance, in the `sys` runtime. @@ -125,6 +127,7 @@ impl Function { } } + #[cfg(feature = "experimental-async")] pub(crate) fn new_async(store: &mut impl AsStoreMut, ty: FT, func: F) -> Self where FT: Into, @@ -136,6 +139,7 @@ impl Function { Self::new_with_env_async(store, &env, ty, wrapped) } + #[cfg(feature = "experimental-async")] pub(crate) fn new_with_env_async( store: &mut impl AsStoreMut, env: &FunctionEnv, @@ -282,6 +286,7 @@ impl Function { } } + #[cfg(feature = "experimental-async")] pub(crate) fn new_typed_async(store: &mut impl AsStoreMut, func: F) -> Self where Args: WasmTypeList + 'static, @@ -329,6 +334,7 @@ impl Function { ) } + #[cfg(feature = "experimental-async")] pub(crate) fn new_typed_with_env_async( store: &mut impl AsStoreMut, env: &FunctionEnv, @@ -576,6 +582,7 @@ impl Function { Ok(results.into_boxed_slice()) } + #[cfg(feature = "experimental-async")] pub(crate) fn call_async( &self, store: &impl AsStoreAsync, @@ -774,6 +781,7 @@ pub(crate) enum HostCallOutcome { func_ty: FunctionType, result: DynamicFunctionResult, }, + #[cfg(feature = "experimental-async")] Future { func_ty: FunctionType, future: Pin>>, @@ -801,6 +809,7 @@ where HostCallOutcome::Ready { func_ty, result } => to_invocation_result( finalize_dynamic_call(this.ctx.store_id, func_ty, values_vec, result), ), + #[cfg(feature = "experimental-async")] HostCallOutcome::Future { func_ty, future } => { let awaited = block_on_host_future(future); let result = match awaited { diff --git a/lib/api/src/backend/sys/entities/function/typed.rs b/lib/api/src/backend/sys/entities/function/typed.rs index dcd35c7925c..923095e898a 100644 --- a/lib/api/src/backend/sys/entities/function/typed.rs +++ b/lib/api/src/backend/sys/entities/function/typed.rs @@ -1,9 +1,11 @@ use crate::backend::sys::engine::NativeEngineExt; -use crate::store::{AsStoreAsync, AsStoreMut, AsStoreRef}; use crate::{ - FromToNativeWasmType, Function, NativeWasmTypeInto, RuntimeError, StoreAsync, StoreContext, - TypedFunction, Value, WasmTypeList, + FromToNativeWasmType, Function, NativeWasmTypeInto, RuntimeError, StoreContext, TypedFunction, + Value, WasmTypeList, + store::{AsStoreMut, AsStoreRef}, }; +#[cfg(feature = "experimental-async")] +use crate::{StoreAsync, store::AsStoreAsync}; use std::future::Future; use wasmer_types::{FunctionType, RawValue, Type}; @@ -113,6 +115,7 @@ macro_rules! impl_native_traits { #[allow(unused_mut)] #[allow(clippy::too_many_arguments)] + #[cfg(feature = "experimental-async")] pub(crate) fn call_async_sys( &self, store: &impl AsStoreAsync, @@ -129,6 +132,7 @@ macro_rules! impl_native_traits { /// Call the typed func asynchronously. #[allow(unused_mut)] #[allow(clippy::too_many_arguments)] + #[cfg(feature = "experimental-async")] pub(crate) fn call_async_sys_internal( func: Function, store: StoreAsync, diff --git a/lib/api/src/backend/sys/mod.rs b/lib/api/src/backend/sys/mod.rs index 34b48cc3865..4e8ea874b76 100644 --- a/lib/api/src/backend/sys/mod.rs +++ b/lib/api/src/backend/sys/mod.rs @@ -1,5 +1,6 @@ //! Data types, functions and traits for the `sys` runtime. +#[cfg(feature = "experimental-async")] pub(crate) mod async_runtime; pub(crate) mod entities; pub(crate) mod error; diff --git a/lib/api/src/entities/function/env/inner.rs b/lib/api/src/entities/function/env/inner.rs index 34e9a1d055a..5b86ae6cd86 100644 --- a/lib/api/src/entities/function/env/inner.rs +++ b/lib/api/src/entities/function/env/inner.rs @@ -1,5 +1,7 @@ +#[cfg(feature = "experimental-async")] +use crate::AsStoreAsync; use crate::{ - AsStoreAsync, AsStoreMut, AsStoreRef, FunctionEnv, FunctionEnvMut, StoreMut, StoreRef, + AsStoreMut, AsStoreRef, FunctionEnv, FunctionEnvMut, StoreMut, StoreRef, macros::backend::match_rt, }; use std::{any::Any, marker::PhantomData}; @@ -200,12 +202,16 @@ impl BackendFunctionEnvMut<'_, T> { }) } - /// Creates an [`AsStoreAsync`] from this [`AsyncFunctionEnvMut`] if the current + /// Creates an [`AsStoreAsync`] from this [`BackendFunctionEnvMut`] if the current /// context is async. + #[cfg(feature = "experimental-async")] pub fn as_store_async(&self) -> Option { match self { #[cfg(feature = "sys")] Self::Sys(f) => f.as_store_async(), + #[cfg(feature = "sys")] + _ => unsupported_async_backend(), + #[cfg(not(feature = "sys"))] _ => unsupported_async_backend::>(), } } @@ -248,6 +254,7 @@ where /// in async imports. #[derive(derive_more::From)] #[non_exhaustive] +#[cfg(feature = "experimental-async")] pub enum BackendAsyncFunctionEnvMut { #[cfg(feature = "sys")] /// The function environment for the `sys` runtime. @@ -257,8 +264,9 @@ pub enum BackendAsyncFunctionEnvMut { Unsupported(PhantomData), } -/// A read-only handle to the [`FunctionEnv`] in an [`AsyncFunctionEnvMut`]. +/// A read-only handle to the [`FunctionEnv`] in an [`BackendAsyncFunctionEnvMut`]. #[non_exhaustive] +#[cfg(feature = "experimental-async")] pub enum BackendAsyncFunctionEnvHandle { #[cfg(feature = "sys")] /// The function environment handle for the `sys` runtime. @@ -268,8 +276,8 @@ pub enum BackendAsyncFunctionEnvHandle { Unsupported(PhantomData), } -/// A mutable handle to the [`FunctionEnv`] in an [`AsyncFunctionEnvMut`]. -#[non_exhaustive] +/// A mutable handle to the [`FunctionEnv`] in an [`BackendAsyncFunctionEnvMut`]. +#[cfg(feature = "experimental-async")] pub enum BackendAsyncFunctionEnvHandleMut { #[cfg(feature = "sys")] /// The function environment handle for the `sys` runtime. @@ -279,6 +287,7 @@ pub enum BackendAsyncFunctionEnvHandleMut { Unsupported(PhantomData), } +#[cfg(feature = "experimental-async")] impl BackendAsyncFunctionEnvMut { /// Waits for a store lock and returns a read-only handle to the /// function environment. @@ -323,11 +332,13 @@ impl BackendAsyncFunctionEnvMut { match self { #[cfg(feature = "sys")] Self::Sys(f) => f.as_store_async(), + #[cfg(not(feature = "sys"))] _ => unsupported_async_backend::(), } } } +#[cfg(feature = "experimental-async")] impl BackendAsyncFunctionEnvHandle { /// Returns a reference to the host state in this function environment. pub fn data(&self) -> &T { @@ -343,27 +354,32 @@ impl BackendAsyncFunctionEnvHandle { match self { #[cfg(feature = "sys")] Self::Sys(f) => f.data_and_store(), + #[cfg(not(feature = "sys"))] _ => unsupported_async_backend::<(&T, &StoreRef)>(), } } } +#[cfg(feature = "experimental-async")] impl AsStoreRef for BackendAsyncFunctionEnvHandle { fn as_store_ref(&self) -> StoreRef<'_> { match self { #[cfg(feature = "sys")] Self::Sys(f) => AsStoreRef::as_store_ref(f), + #[cfg(not(feature = "sys"))] _ => unsupported_async_backend(), } } } +#[cfg(feature = "experimental-async")] impl BackendAsyncFunctionEnvHandleMut { /// Returns a mutable reference to the host state in this function environment. pub fn data_mut(&mut self) -> &mut T { match self { #[cfg(feature = "sys")] Self::Sys(f) => f.data_mut(), + #[cfg(not(feature = "sys"))] _ => unsupported_async_backend(), } } @@ -373,26 +389,31 @@ impl BackendAsyncFunctionEnvHandleMut { match self { #[cfg(feature = "sys")] Self::Sys(f) => f.data_and_store_mut(), + #[cfg(not(feature = "sys"))] _ => unsupported_async_backend::<(&mut T, &mut crate::StoreMut)>(), } } } +#[cfg(feature = "experimental-async")] impl AsStoreRef for BackendAsyncFunctionEnvHandleMut { fn as_store_ref(&self) -> StoreRef<'_> { match self { #[cfg(feature = "sys")] Self::Sys(f) => AsStoreRef::as_store_ref(f), + #[cfg(not(feature = "sys"))] _ => unsupported_async_backend(), } } } +#[cfg(feature = "experimental-async")] impl AsStoreMut for BackendAsyncFunctionEnvHandleMut { fn as_store_mut(&mut self) -> StoreMut<'_> { match self { #[cfg(feature = "sys")] Self::Sys(f) => AsStoreMut::as_store_mut(f), + #[cfg(not(feature = "sys"))] _ => unsupported_async_backend(), } } @@ -401,11 +422,13 @@ impl AsStoreMut for BackendAsyncFunctionEnvHandleMut { match self { #[cfg(feature = "sys")] Self::Sys(f) => AsStoreMut::objects_mut(f), + #[cfg(not(feature = "sys"))] _ => unsupported_async_backend(), } } } +#[cfg(feature = "experimental-async")] fn unsupported_async_backend() -> T { panic!("async functions are only supported with the `sys` backend"); } diff --git a/lib/api/src/entities/function/env/mod.rs b/lib/api/src/entities/function/env/mod.rs index b9351c92b0d..451e1d06fe0 100644 --- a/lib/api/src/entities/function/env/mod.rs +++ b/lib/api/src/entities/function/env/mod.rs @@ -1,7 +1,9 @@ pub(crate) mod inner; pub(crate) use inner::*; -use crate::{AsStoreAsync, AsStoreMut, AsStoreRef, StoreMut, StoreRef, macros::backend::match_rt}; +#[cfg(feature = "experimental-async")] +use crate::AsStoreAsync; +use crate::{AsStoreMut, AsStoreRef, StoreMut, StoreRef, macros::backend::match_rt}; use std::{any::Any, fmt::Debug, marker::PhantomData}; #[derive(Debug, derive_more::From)] @@ -84,8 +86,9 @@ impl FunctionEnvMut<'_, T> { self.0.data_and_store_mut() } - /// Creates an [`AsStoreAsync`] from this [`AsyncFunctionEnvMut`] if the current + /// Creates an [`AsStoreAsync`] from this [`FunctionEnvMut`] if the current /// context is async. + #[cfg(feature = "experimental-async")] pub fn as_store_async(&self) -> Option { self.0.as_store_async() } @@ -118,14 +121,18 @@ where /// A shared handle to a [`FunctionEnv`], suitable for use /// in async imports. +#[cfg(feature = "experimental-async")] pub struct AsyncFunctionEnvMut(pub(crate) BackendAsyncFunctionEnvMut); /// A read-only handle to the [`FunctionEnv`] in an [`AsyncFunctionEnvMut`]. +#[cfg(feature = "experimental-async")] pub struct AsyncFunctionEnvHandle(pub(crate) BackendAsyncFunctionEnvHandle); /// A mutable handle to the [`FunctionEnv`] in an [`AsyncFunctionEnvMut`]. +#[cfg(feature = "experimental-async")] pub struct AsyncFunctionEnvHandleMut(pub(crate) BackendAsyncFunctionEnvHandleMut); +#[cfg(feature = "experimental-async")] impl AsyncFunctionEnvMut { /// Waits for a store lock and returns a read-only handle to the /// function environment. @@ -155,6 +162,7 @@ impl AsyncFunctionEnvMut { } } +#[cfg(feature = "experimental-async")] impl AsyncFunctionEnvHandle { /// Returns a reference to the host state in this function environment. pub fn data(&self) -> &T { @@ -167,12 +175,14 @@ impl AsyncFunctionEnvHandle { } } +#[cfg(feature = "experimental-async")] impl AsStoreRef for AsyncFunctionEnvHandle { fn as_store_ref(&self) -> StoreRef<'_> { AsStoreRef::as_store_ref(&self.0) } } +#[cfg(feature = "experimental-async")] impl AsyncFunctionEnvHandleMut { /// Returns a mutable reference to the host state in this function environment. pub fn data_mut(&mut self) -> &mut T { @@ -185,12 +195,14 @@ impl AsyncFunctionEnvHandleMut { } } +#[cfg(feature = "experimental-async")] impl AsStoreRef for AsyncFunctionEnvHandleMut { fn as_store_ref(&self) -> StoreRef<'_> { AsStoreRef::as_store_ref(&self.0) } } +#[cfg(feature = "experimental-async")] impl AsStoreMut for AsyncFunctionEnvHandleMut { fn as_store_mut(&mut self) -> StoreMut<'_> { AsStoreMut::as_store_mut(&mut self.0) diff --git a/lib/api/src/entities/function/inner.rs b/lib/api/src/entities/function/inner.rs index b86b60f2147..b5de485b353 100644 --- a/lib/api/src/entities/function/inner.rs +++ b/lib/api/src/entities/function/inner.rs @@ -2,11 +2,12 @@ use std::pin::Pin; use wasmer_types::{FunctionType, RawValue}; +#[cfg(feature = "experimental-async")] +use crate::{AsStoreAsync, AsyncFunctionEnvMut, entities::function::async_host::AsyncHostFunction}; use crate::{ - AsStoreAsync, AsStoreMut, AsStoreRef, AsyncFunctionEnvMut, DynamicCallResult, - DynamicFunctionResult, ExportError, Exportable, Extern, FunctionEnv, FunctionEnvMut, - HostFunction, StoreMut, StoreRef, TypedFunction, Value, WasmTypeList, WithEnv, WithoutEnv, - entities::function::async_host::AsyncHostFunction, + AsStoreMut, AsStoreRef, DynamicCallResult, DynamicFunctionResult, ExportError, Exportable, + Extern, FunctionEnv, FunctionEnvMut, HostFunction, StoreMut, StoreRef, TypedFunction, Value, + WasmTypeList, WithEnv, WithoutEnv, error::RuntimeError, macros::backend::{gen_rt_ty, match_rt}, vm::{VMExtern, VMExternFunction, VMFuncRef}, @@ -254,10 +255,13 @@ impl BackendFunction { /// consider using [`Self::new_typed_async`] for less runtime overhead. /// /// The provided closure returns a future that resolves to the function results. - /// When invoked synchronously (via [`Function::call`]) the future will run to + /// When invoked synchronously + /// (via [`Function::call`](crate::Function::call)) the future will run to /// completion immediately, provided it doesn't suspend. When invoked through - /// [`Function::call_async`], the future may suspend and resume as needed. + /// [`Function::call_async`](crate::Function::call_async), the future may suspend + /// and resume as needed. #[inline] + #[cfg(feature = "experimental-async")] pub fn new_async(store: &mut impl AsStoreMut, ty: FT, func: F) -> Self where FT: Into, @@ -291,6 +295,7 @@ impl BackendFunction { /// Takes an [`AsyncFunctionEnvMut`] that is passed into func. If /// that is not required, [`Self::new_async`] might be an option as well. #[inline] + #[cfg(feature = "experimental-async")] pub fn new_with_env_async( store: &mut impl AsStoreMut, env: &FunctionEnv, @@ -325,8 +330,9 @@ impl BackendFunction { /// Creates a new async host `Function` from a native typed function. /// /// The future can return either the raw result tuple or any type that implements - /// [`IntoResult`] for the result tuple (e.g. `Result`). + /// [`IntoResult`](crate::IntoResult) for the result tuple (e.g. `Result`). #[inline] + #[cfg(feature = "experimental-async")] pub fn new_typed_async(store: &mut impl AsStoreMut, func: F) -> Self where F: AsyncHostFunction<(), Args, Rets, WithoutEnv> + 'static, @@ -353,6 +359,7 @@ impl BackendFunction { /// Creates a new async host `Function` with an environment from a typed function. #[inline] + #[cfg(feature = "experimental-async")] pub fn new_typed_with_env_async( store: &mut impl AsStoreMut, env: &FunctionEnv, @@ -503,6 +510,7 @@ impl BackendFunction { }) } + #[cfg(feature = "experimental-async")] pub fn call_async( &self, store: &impl AsStoreAsync, diff --git a/lib/api/src/entities/function/mod.rs b/lib/api/src/entities/function/mod.rs index e04ade2a269..10179263ce7 100644 --- a/lib/api/src/entities/function/mod.rs +++ b/lib/api/src/entities/function/mod.rs @@ -10,16 +10,20 @@ pub use host::*; pub(crate) mod env; pub use env::*; +#[cfg(feature = "experimental-async")] pub(crate) mod async_host; +#[cfg(feature = "experimental-async")] pub use async_host::{AsyncFunctionEnv, AsyncHostFunction}; use std::{future::Future, pin::Pin}; use wasmer_types::{FunctionType, RawValue}; +#[cfg(feature = "experimental-async")] +use crate::AsStoreAsync; use crate::{ - AsStoreAsync, AsStoreMut, AsStoreRef, ExportError, Exportable, Extern, StoreMut, StoreRef, - TypedFunction, Value, WasmTypeList, + AsStoreMut, AsStoreRef, ExportError, Exportable, Extern, StoreMut, StoreRef, TypedFunction, + Value, WasmTypeList, error::RuntimeError, vm::{VMExtern, VMExternFunction, VMFuncRef}, }; @@ -165,6 +169,7 @@ impl Function { /// When invoked synchronously (via [`Function::call`]) the future will run to /// completion immediately, provided it doesn't suspend. When invoked through /// [`Function::call_async`], the future may suspend and resume as needed. + #[cfg(feature = "experimental-async")] pub fn new_async(store: &mut impl AsStoreMut, ty: FT, func: F) -> Self where FT: Into, @@ -182,6 +187,7 @@ impl Function { /// /// Takes an [`AsyncFunctionEnvMut`] that is passed into func. If /// that is not required, [`Self::new_async`] might be an option as well. + #[cfg(feature = "experimental-async")] pub fn new_with_env_async( store: &mut impl AsStoreMut, env: &FunctionEnv, @@ -199,7 +205,8 @@ impl Function { /// Creates a new async host `Function` from a native typed function. /// /// The future can return either the raw result tuple or any type that implements - /// [`IntoResult`] for the result tuple (e.g. `Result`). + /// [`IntoResult`](crate::IntoResult) for the result tuple (e.g. `Result`). + #[cfg(feature = "experimental-async")] pub fn new_typed_async(store: &mut impl AsStoreMut, func: F) -> Self where Rets: WasmTypeList + 'static, @@ -210,6 +217,7 @@ impl Function { } /// Creates a new async host `Function` with an environment from a typed function. + #[cfg(feature = "experimental-async")] pub fn new_typed_with_env_async( store: &mut impl AsStoreMut, env: &FunctionEnv, @@ -329,6 +337,7 @@ impl Function { /// suspend execution by awaiting futures, and their completion will resume /// the Wasm instance according to the JSPI proposal. #[must_use = "This function spawns a future that must be awaited to produce results"] + #[cfg(feature = "experimental-async")] pub fn call_async( &self, store: &impl AsStoreAsync, diff --git a/lib/api/src/entities/store/context.rs b/lib/api/src/entities/store/context.rs index 990e7823e0b..eb9feada35f 100644 --- a/lib/api/src/entities/store/context.rs +++ b/lib/api/src/entities/store/context.rs @@ -20,13 +20,6 @@ //! the context is active, preventing other tasks from //! accessing the store concurrently. //! -//! Because async contexts can't be entered recursively (you -//! can't take a write lock twice, you have to use the existing -//! one), all code in this crate takes care to check for an -//! active store context first before trying to enter one. This -//! gives rise to the enums with cases for temporary locks vs -//! store context pointers, such as [`AsyncStoreReadLockInner`]. -//! //! We maintain a stack because it is technically possible to //! have nested `Function::call` invocations that use different //! stores, such as: @@ -54,6 +47,7 @@ use std::{ mem::MaybeUninit, }; +#[cfg(feature = "experimental-async")] use crate::LocalRwLockWriteGuard; use super::{AsStoreMut, AsStoreRef, StoreInner, StoreMut, StoreRef}; @@ -62,6 +56,8 @@ use wasmer_types::StoreId; enum StoreContextEntry { Sync(*mut StoreInner), + + #[cfg(feature = "experimental-async")] Async(LocalRwLockWriteGuard), } @@ -69,6 +65,7 @@ impl StoreContextEntry { fn as_ptr(&self) -> *mut StoreInner { match self { Self::Sync(ptr) => *ptr, + #[cfg(feature = "experimental-async")] Self::Async(guard) => &**guard as *const _ as *mut _, } } @@ -94,10 +91,12 @@ pub(crate) struct StorePtrWrapper { store_ptr: *mut StoreInner, } +#[cfg(feature = "experimental-async")] pub(crate) struct AsyncStoreGuardWrapper { pub(crate) guard: *mut LocalRwLockWriteGuard, } +#[cfg(feature = "experimental-async")] pub(crate) enum GetAsyncStoreGuardResult { Ok(AsyncStoreGuardWrapper), NotAsync(StorePtrWrapper), @@ -154,6 +153,7 @@ impl StoreContext { /// The write guard ensures this is the only reference to the store, /// so installation can never fail. + #[cfg(feature = "experimental-async")] pub(crate) fn install_async( guard: LocalRwLockWriteGuard, ) -> ForcedStoreInstallGuard { @@ -231,6 +231,7 @@ impl StoreContext { } /// Safety: See [`Self::get_current`]. + #[cfg(feature = "experimental-async")] pub(crate) unsafe fn try_get_current_async(id: StoreId) -> GetAsyncStoreGuardResult { STORE_CONTEXT_STACK.with(|cell| { let mut stack = cell.borrow_mut(); @@ -302,6 +303,7 @@ impl Drop for StorePtrWrapper { } } +#[cfg(feature = "experimental-async")] impl Drop for AsyncStoreGuardWrapper { fn drop(&mut self) { let id = unsafe { self.guard.as_ref().unwrap().objects.id() }; diff --git a/lib/api/src/entities/store/local_rwlock.rs b/lib/api/src/entities/store/local_rwlock.rs index 4af6264a1ac..8eb6c53a87e 100644 --- a/lib/api/src/entities/store/local_rwlock.rs +++ b/lib/api/src/entities/store/local_rwlock.rs @@ -266,7 +266,7 @@ pub struct LocalRwLockReadGuard { } impl LocalRwLockReadGuard { - /// Rebuild a handle to the lock from this [`LocalReadGuardRc`]. + /// Rebuild a handle to the lock from this [`LocalRwLockReadGuard`]. pub fn lock_handle(me: &Self) -> LocalRwLock { LocalRwLock { inner: me.inner.clone(), @@ -294,7 +294,7 @@ pub struct LocalRwLockWriteGuard { } impl LocalRwLockWriteGuard { - /// Rebuild a handle to the lock from this [`LocalWriteGuardRc`]. + /// Rebuild a handle to the lock from this [`LocalRwLockWriteGuard`]. pub fn lock_handle(me: &Self) -> LocalRwLock { LocalRwLock { inner: me.inner.clone(), diff --git a/lib/api/src/entities/store/mod.rs b/lib/api/src/entities/store/mod.rs index 8e19a87e813..910a1b77ddc 100644 --- a/lib/api/src/entities/store/mod.rs +++ b/lib/api/src/entities/store/mod.rs @@ -2,7 +2,9 @@ //! store. /// Defines the [`AsStoreAsync`] trait and its supporting types. +#[cfg(feature = "experimental-async")] mod async_; +#[cfg(feature = "experimental-async")] pub use async_::*; /// Defines the [`StoreContext`] type. @@ -15,7 +17,9 @@ mod inner; mod store_ref; /// Single-threaded async-aware RwLock. +#[cfg(feature = "experimental-async")] mod local_rwlock; +#[cfg(feature = "experimental-async")] pub(crate) use local_rwlock::*; use std::{ @@ -127,6 +131,7 @@ impl Store { self.inner.objects.id() } + #[cfg(feature = "experimental-async")] /// Transforms this store into a [`StoreAsync`] which can be used /// to invoke [`Function::call_async`](crate::Function::call_async). pub fn into_async(self) -> StoreAsync { diff --git a/lib/api/src/entities/store/store_ref.rs b/lib/api/src/entities/store/store_ref.rs index da3074687a1..42ad59ec125 100644 --- a/lib/api/src/entities/store/store_ref.rs +++ b/lib/api/src/entities/store/store_ref.rs @@ -1,10 +1,9 @@ use std::ops::{Deref, DerefMut}; use super::{StoreObjects, inner::StoreInner}; -use crate::{ - AsStoreAsync, StoreAsync, - entities::engine::{AsEngineRef, Engine, EngineRef}, -}; +use crate::entities::engine::{AsEngineRef, Engine, EngineRef}; +#[cfg(feature = "experimental-async")] +use crate::{AsStoreAsync, StoreAsync}; use wasmer_types::{ExternType, OnCalledAction}; //use wasmer_vm::{StoreObjects, TrapHandlerFn}; @@ -99,6 +98,7 @@ pub trait AsStoreRef { /// it's already active in the current context, but can be used /// to spawn new coroutines via /// [`Function::call_async`](crate::Function::call_async). + #[cfg(feature = "experimental-async")] fn as_store_async(&self) -> Option { let id = self.as_store_ref().inner.objects.id(); StoreAsync::from_context(id) diff --git a/lib/api/src/utils/native/typed_func.rs b/lib/api/src/utils/native/typed_func.rs index bc00d59cd26..6368db56ca1 100644 --- a/lib/api/src/utils/native/typed_func.rs +++ b/lib/api/src/utils/native/typed_func.rs @@ -7,9 +7,11 @@ //! let add_one = instance.exports.get_function("function_name")?; //! let add_one_native: TypedFunction = add_one.native().unwrap(); //! ``` +#[cfg(feature = "experimental-async")] +use crate::AsStoreAsync; use crate::{ - AsStoreAsync, AsStoreMut, BackendStore, FromToNativeWasmType, Function, NativeWasmTypeInto, - RuntimeError, WasmTypeList, store::AsStoreRef, + AsStoreMut, BackendStore, FromToNativeWasmType, Function, NativeWasmTypeInto, RuntimeError, + WasmTypeList, store::AsStoreRef, }; use std::future::Future; use std::marker::PhantomData; @@ -80,6 +82,7 @@ macro_rules! impl_native_traits { } /// Call the typed func asynchronously. + #[cfg(feature = "experimental-async")] #[allow(unused_mut)] #[allow(clippy::too_many_arguments)] pub fn call_async( diff --git a/lib/api/tests/jspi_async.rs b/lib/api/tests/jspi_async.rs index 2dbdc681bff..edf3660a75e 100644 --- a/lib/api/tests/jspi_async.rs +++ b/lib/api/tests/jspi_async.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "experimental-async")] + use std::sync::OnceLock; use anyhow::Result; diff --git a/lib/api/tests/simple_greenthread.rs b/lib/api/tests/simple_greenthread.rs index 387cfbad16d..be8ee26c390 100644 --- a/lib/api/tests/simple_greenthread.rs +++ b/lib/api/tests/simple_greenthread.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "experimental-async")] + use std::collections::BTreeMap; use std::sync::atomic::AtomicU32; use std::sync::{Arc, RwLock}; diff --git a/lib/wasix/Cargo.toml b/lib/wasix/Cargo.toml index 4be81db5fce..a46dbf9402f 100644 --- a/lib/wasix/Cargo.toml +++ b/lib/wasix/Cargo.toml @@ -173,7 +173,7 @@ wasm-bindgen-test.workspace = true tracing-wasm.workspace = true [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] -tracing-subscriber = { workspace = true} +tracing-subscriber = { workspace = true } wasmer = { path = "../api", version = "=6.1.0", default-features = false, features = [ "wat", "js-serializable-module", @@ -221,6 +221,7 @@ sys = [ "ctrlc", "wasmer/wat", "wasmer/js-serializable-module", + "wasmer/experimental-async", ] sys-default = ["sys", "wasmer/sys"] sys-poll = [] From b909de9396ca90db5c0163ad03e8d7949ca952b2 Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Mon, 1 Dec 2025 11:16:04 +0400 Subject: [PATCH 39/49] Apply review comments --- lib/api/src/backend/sys/async_runtime.rs | 2 +- .../src/backend/sys/entities/function/env.rs | 6 +-- .../src/backend/sys/entities/function/mod.rs | 12 +++--- lib/api/src/entities/store/async_.rs | 40 +++++++++++-------- lib/api/src/entities/store/context.rs | 18 ++++----- 5 files changed, 42 insertions(+), 36 deletions(-) diff --git a/lib/api/src/backend/sys/async_runtime.rs b/lib/api/src/backend/sys/async_runtime.rs index 5b218ddb9db..5c3aea56cff 100644 --- a/lib/api/src/backend/sys/async_runtime.rs +++ b/lib/api/src/backend/sys/async_runtime.rs @@ -194,7 +194,7 @@ impl Future for AsyncCallFuture { async fn install_store_context(store: StoreAsync) -> ForcedStoreInstallGuard { match unsafe { crate::StoreContext::try_get_current_async(store.id) } { - crate::GetAsyncStoreGuardResult::NotInstalled => { + crate::GetStoreAsyncGuardResult::NotInstalled => { // We always need to acquire a new write lock on the store. let store_guard = store.inner.write().await; unsafe { crate::StoreContext::install_async(store_guard) } diff --git a/lib/api/src/backend/sys/entities/function/env.rs b/lib/api/src/backend/sys/entities/function/env.rs index 61f43bd36a1..7f1de1d3fd7 100644 --- a/lib/api/src/backend/sys/entities/function/env.rs +++ b/lib/api/src/backend/sys/entities/function/env.rs @@ -1,7 +1,7 @@ use std::{any::Any, fmt::Debug, marker::PhantomData}; #[cfg(feature = "experimental-async")] -use crate::{AsStoreAsync, AsyncStoreReadLock, AsyncStoreWriteLock, StoreAsync}; +use crate::{AsStoreAsync, StoreAsync, StoreAsyncReadLock, StoreAsyncWriteLock}; use crate::{ Store, StoreContext, StoreInner, StoreMut, StorePtrWrapper, store::{AsStoreMut, AsStoreRef, StoreRef}, @@ -231,14 +231,14 @@ pub(crate) enum AsyncFunctionEnvMutStore { /// A read-only handle to the [`FunctionEnv`] in an [`AsyncFunctionEnvMut`]. #[cfg(feature = "experimental-async")] pub struct AsyncFunctionEnvHandle { - read_lock: AsyncStoreReadLock, + read_lock: StoreAsyncReadLock, pub(crate) func_env: FunctionEnv, } /// A mutable handle to the [`FunctionEnv`] in an [`AsyncFunctionEnvMut`]. #[cfg(feature = "experimental-async")] pub struct AsyncFunctionEnvHandleMut { - write_lock: AsyncStoreWriteLock, + write_lock: StoreAsyncWriteLock, pub(crate) func_env: FunctionEnv, } diff --git a/lib/api/src/backend/sys/entities/function/mod.rs b/lib/api/src/backend/sys/entities/function/mod.rs index 75fdaa42baf..6e4a6c9cbf3 100644 --- a/lib/api/src/backend/sys/entities/function/mod.rs +++ b/lib/api/src/backend/sys/entities/function/mod.rs @@ -159,11 +159,11 @@ impl Function { unsafe { let mut context = StoreContext::try_get_current_async(store_id); let mut store_mut = match &mut context { - crate::GetAsyncStoreGuardResult::Ok(wrapper) => StoreMut { + crate::GetStoreAsyncGuardResult::Ok(wrapper) => StoreMut { inner: wrapper.guard.as_mut().unwrap(), }, - crate::GetAsyncStoreGuardResult::NotAsync(ptr) => ptr.as_mut(), - crate::GetAsyncStoreGuardResult::NotInstalled => { + crate::GetStoreAsyncGuardResult::NotAsync(ptr) => ptr.as_mut(), + crate::GetStoreAsyncGuardResult::NotInstalled => { panic!("No store context installed on this thread") } }; @@ -178,7 +178,7 @@ impl Function { )); } let store_async = match context { - crate::GetAsyncStoreGuardResult::Ok(wrapper) => { + crate::GetStoreAsyncGuardResult::Ok(wrapper) => { AsyncFunctionEnvMutStore::Async(StoreAsync { id, inner: crate::LocalRwLockWriteGuard::lock_handle( @@ -186,10 +186,10 @@ impl Function { ), }) } - crate::GetAsyncStoreGuardResult::NotAsync(ptr) => { + crate::GetStoreAsyncGuardResult::NotAsync(ptr) => { AsyncFunctionEnvMutStore::Sync(ptr) } - crate::GetAsyncStoreGuardResult::NotInstalled => unreachable!(), + crate::GetStoreAsyncGuardResult::NotInstalled => unreachable!(), }; let env = crate::AsyncFunctionEnvMut(crate::BackendAsyncFunctionEnvMut::Sys( env::AsyncFunctionEnvMut { diff --git a/lib/api/src/entities/store/async_.rs b/lib/api/src/entities/store/async_.rs index b3cb562dc02..5bf66bfc6f9 100644 --- a/lib/api/src/entities/store/async_.rs +++ b/lib/api/src/entities/store/async_.rs @@ -1,4 +1,4 @@ -use std::marker::PhantomData; +use std::{fmt::Debug, marker::PhantomData}; use crate::{ AsStoreMut, AsStoreRef, LocalRwLock, LocalRwLockReadGuard, LocalRwLockWriteGuard, Store, @@ -19,7 +19,7 @@ impl StoreAsync { // Safety: we don't keep the guard around, it's just used to // build a safe lock handle. match unsafe { StoreContext::try_get_current_async(id) } { - crate::GetAsyncStoreGuardResult::Ok(guard) => Some(Self { + crate::GetStoreAsyncGuardResult::Ok(guard) => Some(Self { id, inner: crate::LocalRwLockWriteGuard::lock_handle(unsafe { guard.guard.as_ref().unwrap() @@ -45,7 +45,7 @@ impl StoreAsync { /// Acquire a read lock on the store. Panics if the store is /// locked for writing. - pub fn read(&self) -> AsyncStoreReadLock { + pub fn read(&self) -> StoreAsyncReadLock { if !StoreContext::is_empty() { panic!("This method cannot be called from inside imported functions"); } @@ -54,18 +54,24 @@ impl StoreAsync { .inner .try_read() .expect("StoreAsync is locked for write"); - AsyncStoreReadLock { inner: store_ref } + StoreAsyncReadLock { inner: store_ref } } /// Acquire a write lock on the store. Panics if the store is /// locked. - pub fn write(self) -> AsyncStoreWriteLock { + pub fn write(self) -> StoreAsyncWriteLock { if !StoreContext::is_empty() { panic!("This method cannot be called from inside imported functions"); } let store_guard = self.inner.try_write().expect("StoreAsync is locked"); - AsyncStoreWriteLock { inner: store_guard } + StoreAsyncWriteLock { inner: store_guard } + } +} + +impl Debug for StoreAsync { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("StoreAsync").field("id", &self.id).finish() } } @@ -90,13 +96,13 @@ pub trait AsStoreAsync { } /// Acquires a read lock on the store. - fn read_lock(&self) -> impl Future { - AsyncStoreReadLock::acquire(self.store_ref()) + fn read_lock(&self) -> impl Future { + StoreAsyncReadLock::acquire(self.store_ref()) } /// Acquires a write lock on the store. - fn write_lock(&self) -> impl Future { - AsyncStoreWriteLock::acquire(self.store_ref()) + fn write_lock(&self) -> impl Future { + StoreAsyncWriteLock::acquire(self.store_ref()) } } @@ -107,42 +113,42 @@ impl AsStoreAsync for StoreAsync { } /// A read lock on an async store. -pub struct AsyncStoreReadLock { +pub struct StoreAsyncReadLock { pub(crate) inner: LocalRwLockReadGuard, } -impl AsyncStoreReadLock { +impl StoreAsyncReadLock { pub(crate) async fn acquire(store: &StoreAsync) -> Self { let store_ref = store.inner.read().await; Self { inner: store_ref } } } -impl AsStoreRef for AsyncStoreReadLock { +impl AsStoreRef for StoreAsyncReadLock { fn as_store_ref(&self) -> StoreRef<'_> { StoreRef { inner: &self.inner } } } /// A write lock on an async store. -pub struct AsyncStoreWriteLock { +pub struct StoreAsyncWriteLock { pub(crate) inner: LocalRwLockWriteGuard, } -impl AsyncStoreWriteLock { +impl StoreAsyncWriteLock { pub(crate) async fn acquire(store: &StoreAsync) -> Self { let store_guard = store.inner.write().await; Self { inner: store_guard } } } -impl AsStoreRef for AsyncStoreWriteLock { +impl AsStoreRef for StoreAsyncWriteLock { fn as_store_ref(&self) -> StoreRef<'_> { StoreRef { inner: &self.inner } } } -impl AsStoreMut for AsyncStoreWriteLock { +impl AsStoreMut for StoreAsyncWriteLock { fn as_store_mut(&mut self) -> StoreMut<'_> { StoreMut { inner: &mut self.inner, diff --git a/lib/api/src/entities/store/context.rs b/lib/api/src/entities/store/context.rs index eb9feada35f..5d3976243b0 100644 --- a/lib/api/src/entities/store/context.rs +++ b/lib/api/src/entities/store/context.rs @@ -92,13 +92,13 @@ pub(crate) struct StorePtrWrapper { } #[cfg(feature = "experimental-async")] -pub(crate) struct AsyncStoreGuardWrapper { +pub(crate) struct StoreAsyncGuardWrapper { pub(crate) guard: *mut LocalRwLockWriteGuard, } #[cfg(feature = "experimental-async")] -pub(crate) enum GetAsyncStoreGuardResult { - Ok(AsyncStoreGuardWrapper), +pub(crate) enum GetStoreAsyncGuardResult { + Ok(StoreAsyncGuardWrapper), NotAsync(StorePtrWrapper), NotInstalled, } @@ -232,24 +232,24 @@ impl StoreContext { /// Safety: See [`Self::get_current`]. #[cfg(feature = "experimental-async")] - pub(crate) unsafe fn try_get_current_async(id: StoreId) -> GetAsyncStoreGuardResult { + pub(crate) unsafe fn try_get_current_async(id: StoreId) -> GetStoreAsyncGuardResult { STORE_CONTEXT_STACK.with(|cell| { let mut stack = cell.borrow_mut(); let Some(top) = stack.last_mut() else { - return GetAsyncStoreGuardResult::NotInstalled; + return GetStoreAsyncGuardResult::NotInstalled; }; if top.id != id { - return GetAsyncStoreGuardResult::NotInstalled; + return GetStoreAsyncGuardResult::NotInstalled; } top.borrow_count += 1; match unsafe { top.entry.get().as_mut().unwrap() } { StoreContextEntry::Async(guard) => { - GetAsyncStoreGuardResult::Ok(AsyncStoreGuardWrapper { + GetStoreAsyncGuardResult::Ok(StoreAsyncGuardWrapper { guard: guard as *mut _, }) } StoreContextEntry::Sync(ptr) => { - GetAsyncStoreGuardResult::NotAsync(StorePtrWrapper { store_ptr: *ptr }) + GetStoreAsyncGuardResult::NotAsync(StorePtrWrapper { store_ptr: *ptr }) } } }) @@ -304,7 +304,7 @@ impl Drop for StorePtrWrapper { } #[cfg(feature = "experimental-async")] -impl Drop for AsyncStoreGuardWrapper { +impl Drop for StoreAsyncGuardWrapper { fn drop(&mut self) { let id = unsafe { self.guard.as_ref().unwrap().objects.id() }; STORE_CONTEXT_STACK.with(|cell| { From 4f1f27e9e83d624ae2cffaabd4eec59f0269f9c8 Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Mon, 1 Dec 2025 13:39:43 +0400 Subject: [PATCH 40/49] Put the entire Box in the async store's RW-lock because StoreObjects shouldn't be moved --- lib/api/src/entities/store/async_.rs | 11 +++++------ lib/api/src/entities/store/context.rs | 8 ++++---- lib/api/src/entities/store/mod.rs | 2 +- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/lib/api/src/entities/store/async_.rs b/lib/api/src/entities/store/async_.rs index 5bf66bfc6f9..fa557c174ff 100644 --- a/lib/api/src/entities/store/async_.rs +++ b/lib/api/src/entities/store/async_.rs @@ -11,7 +11,8 @@ use wasmer_types::StoreId; /// [`Function::call_async`](crate::Function::call_async). pub struct StoreAsync { pub(crate) id: StoreId, - pub(crate) inner: LocalRwLock, + // We use a box inside the RW lock because the StoreInner shouldn't be moved + pub(crate) inner: LocalRwLock>, } impl StoreAsync { @@ -33,9 +34,7 @@ impl StoreAsync { /// if this is the only clone of it and is unlocked. pub fn into_store(self) -> Result { match self.inner.consume() { - Ok(unwrapped) => Ok(Store { - inner: Box::new(unwrapped), - }), + Ok(unwrapped) => Ok(Store { inner: unwrapped }), Err(lock) => Err(Self { id: self.id, inner: lock, @@ -114,7 +113,7 @@ impl AsStoreAsync for StoreAsync { /// A read lock on an async store. pub struct StoreAsyncReadLock { - pub(crate) inner: LocalRwLockReadGuard, + pub(crate) inner: LocalRwLockReadGuard>, } impl StoreAsyncReadLock { @@ -132,7 +131,7 @@ impl AsStoreRef for StoreAsyncReadLock { /// A write lock on an async store. pub struct StoreAsyncWriteLock { - pub(crate) inner: LocalRwLockWriteGuard, + pub(crate) inner: LocalRwLockWriteGuard>, } impl StoreAsyncWriteLock { diff --git a/lib/api/src/entities/store/context.rs b/lib/api/src/entities/store/context.rs index 5d3976243b0..87d92ed71b9 100644 --- a/lib/api/src/entities/store/context.rs +++ b/lib/api/src/entities/store/context.rs @@ -58,7 +58,7 @@ enum StoreContextEntry { Sync(*mut StoreInner), #[cfg(feature = "experimental-async")] - Async(LocalRwLockWriteGuard), + Async(LocalRwLockWriteGuard>), } impl StoreContextEntry { @@ -66,7 +66,7 @@ impl StoreContextEntry { match self { Self::Sync(ptr) => *ptr, #[cfg(feature = "experimental-async")] - Self::Async(guard) => &**guard as *const _ as *mut _, + Self::Async(guard) => &***guard as *const _ as *mut _, } } } @@ -93,7 +93,7 @@ pub(crate) struct StorePtrWrapper { #[cfg(feature = "experimental-async")] pub(crate) struct StoreAsyncGuardWrapper { - pub(crate) guard: *mut LocalRwLockWriteGuard, + pub(crate) guard: *mut LocalRwLockWriteGuard>, } #[cfg(feature = "experimental-async")] @@ -155,7 +155,7 @@ impl StoreContext { /// so installation can never fail. #[cfg(feature = "experimental-async")] pub(crate) fn install_async( - guard: LocalRwLockWriteGuard, + guard: LocalRwLockWriteGuard>, ) -> ForcedStoreInstallGuard { let store_id = guard.objects.id(); Self::install(store_id, StoreContextEntry::Async(guard)); diff --git a/lib/api/src/entities/store/mod.rs b/lib/api/src/entities/store/mod.rs index 910a1b77ddc..e1aee0709fa 100644 --- a/lib/api/src/entities/store/mod.rs +++ b/lib/api/src/entities/store/mod.rs @@ -137,7 +137,7 @@ impl Store { pub fn into_async(self) -> StoreAsync { StoreAsync { id: self.id(), - inner: LocalRwLock::new(*self.inner), + inner: LocalRwLock::new(self.inner), } } } From a165cac84e825612de20522d64497c9388a1ffd7 Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Tue, 2 Dec 2025 13:55:35 +0000 Subject: [PATCH 41/49] Prevent async functions from returning double-wrapped RuntimeError --- lib/api/src/entities/function/async_host.rs | 4 ++-- lib/api/src/error.rs | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/api/src/entities/function/async_host.rs b/lib/api/src/entities/function/async_host.rs index 149f9c5c546..01702a91183 100644 --- a/lib/api/src/entities/function/async_host.rs +++ b/lib/api/src/entities/function/async_host.rs @@ -72,7 +72,7 @@ macro_rules! impl_async_host_function_without_env { Box::pin(async move { fut.await .into_result() - .map_err(|err| RuntimeError::user(Box::new(err))) + .map_err(|err| RuntimeError::from_dyn(Box::new(err))) }) } } @@ -102,7 +102,7 @@ macro_rules! impl_async_host_function_with_env { Box::pin(async move { fut.await .into_result() - .map_err(|err| RuntimeError::user(Box::new(err))) + .map_err(|err| RuntimeError::from_dyn(Box::new(err))) }) } } diff --git a/lib/api/src/error.rs b/lib/api/src/error.rs index c283b8c106f..d37d543d323 100644 --- a/lib/api/src/error.rs +++ b/lib/api/src/error.rs @@ -265,6 +265,13 @@ impl RuntimeError { } Ok(()) } + + pub(crate) fn from_dyn(err: Box) -> Self { + match err.downcast::() { + Ok(runtime_error) => *runtime_error, + Err(error) => Trap::user(error).into(), + } + } } impl std::fmt::Debug for RuntimeError { From 663111c38b0a6ac6e3f393b6a9c9ce36ff63e55c Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Tue, 2 Dec 2025 14:01:10 +0000 Subject: [PATCH 42/49] Fix sync imports calling code that calls async imports in an async context --- .../src/backend/sys/entities/function/mod.rs | 7 +- .../backend/sys/entities/function/typed.rs | 8 ++ lib/api/src/entities/store/context.rs | 130 ++++++++++++++++-- lib/api/src/error.rs | 2 +- lib/api/tests/jspi_async.rs | 82 ++++++++++- tests/compilers/typed_functions.rs | 2 +- 6 files changed, 215 insertions(+), 16 deletions(-) diff --git a/lib/api/src/backend/sys/entities/function/mod.rs b/lib/api/src/backend/sys/entities/function/mod.rs index 6e4a6c9cbf3..a26f79e14ba 100644 --- a/lib/api/src/backend/sys/entities/function/mod.rs +++ b/lib/api/src/backend/sys/entities/function/mod.rs @@ -505,6 +505,7 @@ impl Function { ) -> Result<(), RuntimeError> { // Call the trampoline. let result = { + let store_id = store.objects_mut().id(); // Safety: the store context is uninstalled before we return, and the // store mut is valid for the duration of the call. let store_install_guard = @@ -516,9 +517,13 @@ impl Function { let storeref = store.as_store_ref(); let vm_function = self.handle.get(storeref.objects().as_sys()); let config = storeref.engine().tunables().vmconfig(); + let signal_handler = storeref.signal_handler(); r = unsafe { + // Safety: This is the intended use-case for StoreContext::pause, as + // documented in the function's doc comments. + let pause_guard = StoreContext::pause(store_id); wasmer_call_trampoline( - store.as_store_ref().signal_handler(), + signal_handler, config, vm_function.anyfunc.as_ptr().as_ref().vmctx, trampoline, diff --git a/lib/api/src/backend/sys/entities/function/typed.rs b/lib/api/src/backend/sys/entities/function/typed.rs index 923095e898a..23f1a054c1b 100644 --- a/lib/api/src/backend/sys/entities/function/typed.rs +++ b/lib/api/src/backend/sys/entities/function/typed.rs @@ -52,6 +52,7 @@ macro_rules! impl_native_traits { rets_list.as_mut() }; + let store_id = store.objects_mut().id(); // Install the store into the store context let store_install_guard = unsafe { StoreContext::ensure_installed(store.as_store_mut().inner as *mut _) @@ -62,6 +63,9 @@ macro_rules! impl_native_traits { let storeref = store.as_store_ref(); let config = storeref.engine().tunables().vmconfig(); r = unsafe { + // Safety: This is the intended use-case for StoreContext::pause, as + // documented in the function's doc comments. + let pause_guard = StoreContext::pause(store_id); wasmer_vm::wasmer_call_trampoline( store.as_store_ref().signal_handler(), config, @@ -190,6 +194,7 @@ macro_rules! impl_native_traits { rets_list.as_mut() }; + let store_id = store.objects_mut().id(); // Install the store into the store context let store_install_guard = unsafe { StoreContext::ensure_installed(store.as_store_mut().inner as *mut _) @@ -200,6 +205,9 @@ macro_rules! impl_native_traits { let storeref = store.as_store_ref(); let config = storeref.engine().tunables().vmconfig(); r = unsafe { + // Safety: This is the intended use-case for StoreContext::pause, as + // documented in the function's doc comments. + let pause_guard = StoreContext::pause(store_id); wasmer_vm::wasmer_call_trampoline( store.as_store_ref().signal_handler(), config, diff --git a/lib/api/src/entities/store/context.rs b/lib/api/src/entities/store/context.rs index 87d92ed71b9..3c4285bf3bd 100644 --- a/lib/api/src/entities/store/context.rs +++ b/lib/api/src/entities/store/context.rs @@ -96,6 +96,12 @@ pub(crate) struct StoreAsyncGuardWrapper { pub(crate) guard: *mut LocalRwLockWriteGuard>, } +pub(crate) struct StorePtrPauseGuard { + store_id: StoreId, + ptr: *mut StoreInner, + ref_count_decremented: bool, +} + #[cfg(feature = "experimental-async")] pub(crate) enum GetStoreAsyncGuardResult { Ok(StoreAsyncGuardWrapper), @@ -170,6 +176,11 @@ impl StoreContext { pub(crate) unsafe fn ensure_installed(store_ptr: *mut StoreInner) -> StoreInstallGuard { let store_id = unsafe { store_ptr.as_ref().unwrap().objects.id() }; if Self::is_active(store_id) { + let current_ptr = STORE_CONTEXT_STACK.with(|cell| { + let stack = cell.borrow(); + unsafe { stack.last().unwrap().entry.get().as_ref().unwrap().as_ptr() } + }); + assert_eq!(store_ptr, current_ptr, "Store context pointer mismatch"); StoreInstallGuard::NotInstalled } else { Self::install(store_id, StoreContextEntry::Sync(store_ptr)); @@ -177,6 +188,44 @@ impl StoreContext { } } + /// "Pause" one borrow of the store context. + /// + /// # Safety + /// Code must ensure it does not use the StorePtrWrapper or + /// StoreAsyncGuardWrapper that it owns, or any StoreRef/StoreMut + /// derived from them, while the store context is paused. + /// + /// The safe, correct use-case for this method is to + /// pause the store context while executing WASM code, which + /// cannot use the store context directly. This allows an async + /// context to uninstall the store context when suspending if it's + /// called from a sync imported function. The imported function + /// will have borrowed the store context in its trampoline, which + /// will prevent the async context from uninstalling the store. + /// However, since the imported function passes a mutable borrow + /// of its store into `Function::call`, it will expect the store + /// to change before the call returns. + pub(crate) unsafe fn pause(id: StoreId) -> StorePtrPauseGuard { + STORE_CONTEXT_STACK.with(|cell| { + let mut stack = cell.borrow_mut(); + let top = stack + .last_mut() + .expect("No store context installed on this thread"); + assert_eq!(top.id, id, "Mismatched store context access"); + let ref_count_decremented = if top.borrow_count > 0 { + top.borrow_count -= 1; + true + } else { + false + }; + StorePtrPauseGuard { + store_id: id, + ptr: unsafe { top.entry.get().as_ref().unwrap().as_ptr() }, + ref_count_decremented, + } + }) + } + /// Safety: This method lets you borrow multiple mutable references /// to the currently active store context. The caller must ensure that: /// * there is only one mutable reference alive, or @@ -291,6 +340,9 @@ impl Clone for StorePtrWrapper { impl Drop for StorePtrWrapper { fn drop(&mut self) { + if std::thread::panicking() { + return; + } let id = self.as_mut().objects_mut().id(); STORE_CONTEXT_STACK.with(|cell| { let mut stack = cell.borrow_mut(); @@ -306,6 +358,9 @@ impl Drop for StorePtrWrapper { #[cfg(feature = "experimental-async")] impl Drop for StoreAsyncGuardWrapper { fn drop(&mut self) { + if std::thread::panicking() { + return; + } let id = unsafe { self.guard.as_ref().unwrap().objects.id() }; STORE_CONTEXT_STACK.with(|cell| { let mut stack = cell.borrow_mut(); @@ -323,12 +378,28 @@ impl Drop for StoreInstallGuard { if let Self::Installed(store_id) = self { STORE_CONTEXT_STACK.with(|cell| { let mut stack = cell.borrow_mut(); - let top = stack.pop().expect("Store context stack underflow"); - assert_eq!(top.id, *store_id, "Mismatched store context uninstall"); - assert_eq!( - top.borrow_count, 0, - "Cannot uninstall store context while it is still borrowed" - ); + match (stack.pop(), std::thread::panicking()) { + (Some(top), false) => { + assert_eq!(top.id, *store_id, "Mismatched store context uninstall"); + assert_eq!( + top.borrow_count, 0, + "Cannot uninstall store context while it is still borrowed" + ); + } + (Some(top), true) => { + // If we're panicking and there's a store ID mismatch, just + // put the store back in the hope that its own install guard + // take care of uninstalling it later. + if top.id != *store_id { + stack.push(top); + } + } + (None, false) => panic!("Store context stack underflow"), + (None, true) => { + // Nothing to do if we're panicking; panics can put the context + // in an invalid state, and we don't to cause another panic here. + } + } }) } } @@ -338,12 +409,51 @@ impl Drop for ForcedStoreInstallGuard { fn drop(&mut self) { STORE_CONTEXT_STACK.with(|cell| { let mut stack = cell.borrow_mut(); - let top = stack.pop().expect("Store context stack underflow"); - assert_eq!(top.id, self.store_id, "Mismatched store context uninstall"); + match (stack.pop(), std::thread::panicking()) { + (Some(top), false) => { + assert_eq!(top.id, self.store_id, "Mismatched store context uninstall"); + assert_eq!( + top.borrow_count, 0, + "Cannot uninstall store context while it is still borrowed" + ); + } + (Some(top), true) => { + // If we're panicking and there's a store ID mismatch, just + // put the store back in the hope that its own install guard + // take care of uninstalling it later. + if top.id != self.store_id { + stack.push(top); + } + } + (None, false) => panic!("Store context stack underflow"), + (None, true) => { + // Nothing to do if we're panicking; panics can put the context + // in an invalid state, and we don't to cause another panic here. + } + } + }) + } +} + +impl Drop for StorePtrPauseGuard { + fn drop(&mut self) { + if std::thread::panicking() { + return; + } + STORE_CONTEXT_STACK.with(|cell| { + let mut stack = cell.borrow_mut(); + let top = stack + .last_mut() + .expect("No store context installed on this thread"); + assert_eq!(top.id, self.store_id, "Mismatched store context access"); assert_eq!( - top.borrow_count, 0, - "Cannot uninstall store context while it is still borrowed" + unsafe { top.entry.get().as_ref().unwrap() }.as_ptr(), + self.ptr, + "Mismatched store context access" ); + if self.ref_count_decremented { + top.borrow_count += 1; + } }) } } diff --git a/lib/api/src/error.rs b/lib/api/src/error.rs index d37d543d323..f80ffb67da8 100644 --- a/lib/api/src/error.rs +++ b/lib/api/src/error.rs @@ -269,7 +269,7 @@ impl RuntimeError { pub(crate) fn from_dyn(err: Box) -> Self { match err.downcast::() { Ok(runtime_error) => *runtime_error, - Err(error) => Trap::user(error).into(), + Err(error) => Trap::user(error), } } } diff --git a/lib/api/tests/jspi_async.rs b/lib/api/tests/jspi_async.rs index edf3660a75e..5eec79d31b9 100644 --- a/lib/api/tests/jspi_async.rs +++ b/lib/api/tests/jspi_async.rs @@ -1,12 +1,12 @@ #![cfg(feature = "experimental-async")] -use std::sync::OnceLock; +use std::{cell::RefCell, sync::OnceLock}; use anyhow::Result; use futures::future; use wasmer::{ - AsyncFunctionEnvMut, Function, FunctionEnv, FunctionType, Instance, Module, Store, StoreAsync, - Type, TypedFunction, Value, imports, + AsyncFunctionEnvMut, Function, FunctionEnv, FunctionEnvMut, FunctionType, Instance, Module, + Store, StoreAsync, Type, TypedFunction, Value, imports, }; use wasmer_vm::TrapCode; @@ -227,3 +227,79 @@ fn cannot_yield_when_not_in_async_context() -> Result<()> { Ok(()) } + +#[test] +fn nested_async_in_sync() -> Result<()> { + const WAT: &str = r#" + (module + (import "env" "sync" (func $sync (result i32))) + (import "env" "async" (func $async (result i32))) + (func (export "entry") (result i32) + call $sync + ) + (func (export "inner_async") (result i32) + call $async + ) + ) + "#; + let wasm = wat::parse_str(WAT).expect("valid WAT module"); + + let mut store = Store::default(); + let module = Module::new(&store, wasm)?; + + struct Env { + inner_async: RefCell>>, + } + let env = FunctionEnv::new( + &mut store, + Env { + inner_async: RefCell::new(None), + }, + ); + + let sync = Function::new_typed_with_env(&mut store, &env, |mut env: FunctionEnvMut| { + let (env, mut store) = env.data_and_store_mut(); + env.inner_async + .borrow() + .as_ref() + .expect("inner_async function to be set") + .call(&mut store) + .expect("inner async call to succeed") + }); + + let async_ = Function::new_typed_async(&mut store, async || { + tokio::task::yield_now().await; + 42 + }); + + let imports = imports! { + "env" => { + "sync" => sync, + "async" => async_, + } + }; + + let instance = Instance::new(&mut store, &module, &imports)?; + + let inner_async = instance + .exports + .get_typed_function::<(), i32>(&mut store, "inner_async") + .unwrap(); + env.as_mut(&mut store) + .inner_async + .borrow_mut() + .replace(inner_async); + + let entry = instance + .exports + .get_typed_function::<(), i32>(&mut store, "entry")?; + let result = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap() + .block_on(entry.call_async(&store.into_async()))?; + + assert_eq!(result, 42); + + Ok(()) +} diff --git a/tests/compilers/typed_functions.rs b/tests/compilers/typed_functions.rs index 17754291b48..f93a3b8e612 100644 --- a/tests/compilers/typed_functions.rs +++ b/tests/compilers/typed_functions.rs @@ -22,7 +22,7 @@ fn long_f(a: u32, b: u32, c: u32, d: u32, e: u32, f: u16, g: u64, h: u64, i: u16 + a as u64 * 1000000000 } -fn long_f_dynamic(values: &[Value]) -> Result, RuntimeError> { +fn long_f_dynamic(values: &[Value]) -> DynamicFunctionResult { Ok(vec![Value::I64( values[9].unwrap_i32() as i64 + values[8].unwrap_i32() as i64 * 10 From f9adac814fabe0037e490ab9d28c1ae47c1d5396 Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Tue, 2 Dec 2025 14:41:25 +0000 Subject: [PATCH 43/49] Add AsyncFunctionEnvHandleMut::as_function_env_mut --- lib/api/src/backend/sys/entities/function/env.rs | 8 ++++++++ lib/api/src/entities/function/env/inner.rs | 11 +++++++++++ lib/api/src/entities/function/env/mod.rs | 6 ++++++ 3 files changed, 25 insertions(+) diff --git a/lib/api/src/backend/sys/entities/function/env.rs b/lib/api/src/backend/sys/entities/function/env.rs index 7f1de1d3fd7..db823169b83 100644 --- a/lib/api/src/backend/sys/entities/function/env.rs +++ b/lib/api/src/backend/sys/entities/function/env.rs @@ -384,6 +384,14 @@ impl AsyncFunctionEnvHandleMut { let data = unsafe { &mut *data }; (data, &mut self.write_lock) } + + /// Borrows a new [`FunctionEnvMut`] from this [`AsyncFunctionEnvHandleMut`]. + pub fn as_function_env_mut(&mut self) -> FunctionEnvMut<'_, T> { + FunctionEnvMut { + store_mut: self.write_lock.as_store_mut(), + func_env: self.func_env.clone(), + } + } } #[cfg(feature = "experimental-async")] diff --git a/lib/api/src/entities/function/env/inner.rs b/lib/api/src/entities/function/env/inner.rs index 5b86ae6cd86..ee9d85410f7 100644 --- a/lib/api/src/entities/function/env/inner.rs +++ b/lib/api/src/entities/function/env/inner.rs @@ -393,6 +393,17 @@ impl BackendAsyncFunctionEnvHandleMut { _ => unsupported_async_backend::<(&mut T, &mut crate::StoreMut)>(), } } + + /// Borrows a new [`BackendFunctionEnvMut`] from this + /// [`BackendAsyncFunctionEnvHandleMut`]. + pub fn as_function_env_mut(&mut self) -> BackendFunctionEnvMut<'_, T> { + match self { + #[cfg(feature = "sys")] + Self::Sys(f) => BackendFunctionEnvMut::Sys(f.as_function_env_mut()), + #[cfg(not(feature = "sys"))] + _ => unsupported_async_backend(), + } + } } #[cfg(feature = "experimental-async")] diff --git a/lib/api/src/entities/function/env/mod.rs b/lib/api/src/entities/function/env/mod.rs index 451e1d06fe0..41b49f942b1 100644 --- a/lib/api/src/entities/function/env/mod.rs +++ b/lib/api/src/entities/function/env/mod.rs @@ -193,6 +193,12 @@ impl AsyncFunctionEnvHandleMut { pub fn data_and_store_mut(&mut self) -> (&mut T, &mut impl AsStoreMut) { self.0.data_and_store_mut() } + + /// Borrows a new [`FunctionEnvMut`] from this + /// [`AsyncFunctionEnvHandleMut`]. + pub fn as_function_env_mut(&mut self) -> FunctionEnvMut<'_, T> { + FunctionEnvMut(self.0.as_function_env_mut()) + } } #[cfg(feature = "experimental-async")] From e74066e0075bd11e4318ddd4697160c0284929dd Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Fri, 5 Dec 2025 10:49:48 +0400 Subject: [PATCH 44/49] Fix tests enabling the LLVM compiler unexpectedly --- Cargo.toml | 1 + Makefile | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8505e4a4fab..bc68b86bc4c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -390,6 +390,7 @@ wasmer-artifact-load = ["wasmer-compiler/wasmer-artifact-load"] wasmer-artifact-create = ["wasmer-compiler/wasmer-artifact-create"] static-artifact-load = ["wasmer-compiler/static-artifact-load"] static-artifact-create = ["wasmer-compiler/static-artifact-create"] +experimental-async = ["wasmer/experimental-async"] # Testing features test-singlepass = ["singlepass"] diff --git a/Makefile b/Makefile index 336ccc7e352..1f06fd9aff3 100644 --- a/Makefile +++ b/Makefile @@ -631,8 +631,8 @@ test-stage-0-wast: # test packages test-stage-1-test-all: - $(CARGO_BINARY) nextest run $(CARGO_TARGET_FLAG) --workspace --release $(exclude_tests) --exclude wasmer-c-api-test-runner --exclude wasmer-capi-examples-runner $(compiler_features) --features wasmer/experimental-async --locked && \ - $(CARGO_BINARY) test --doc $(CARGO_TARGET_FLAG) --workspace --release $(exclude_tests) --exclude wasmer-c-api-test-runner --exclude wasmer-capi-examples-runner $(compiler_features) --features wasmer/experimental-async --locked + $(CARGO_BINARY) nextest run $(CARGO_TARGET_FLAG) --workspace --release $(exclude_tests) --exclude wasmer-c-api-test-runner --exclude wasmer-capi-examples-runner $(compiler_features) --features experimental-async --locked && \ + $(CARGO_BINARY) test --doc $(CARGO_TARGET_FLAG) --workspace --release $(exclude_tests) --exclude wasmer-c-api-test-runner --exclude wasmer-capi-examples-runner $(compiler_features) --features experimental-async --locked test-stage-2-test-compiler-cranelift-nostd: $(CARGO_BINARY) test $(CARGO_TARGET_FLAG) --manifest-path lib/compiler-cranelift/Cargo.toml --release --no-default-features --features=std --locked test-stage-3-test-compiler-singlepass-nostd: From dc74aa51770a7bb283ed0324eb621ecb2d9e7144 Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Sat, 6 Dec 2025 01:38:47 +0400 Subject: [PATCH 45/49] Take DynamicFunctionResult and DynamicCallResult back out --- examples/throw_exception.rs | 9 ++- .../src/backend/js/entities/function/mod.rs | 19 ++++-- .../src/backend/jsc/entities/function/mod.rs | 20 +++--- lib/api/src/backend/sys/async_runtime.rs | 22 +++---- .../src/backend/sys/entities/function/mod.rs | 40 +++++++----- .../src/backend/v8/entities/function/mod.rs | 30 ++++++--- .../src/backend/wamr/entities/function/mod.rs | 30 ++++++--- .../backend/wasmi/entities/function/mod.rs | 30 ++++++--- lib/api/src/entities/function/inner.rs | 37 ++++++----- lib/api/src/entities/function/mod.rs | 27 ++++---- lib/api/tests/instance.rs | 2 +- lib/api/tests/reference_types.rs | 5 +- .../src/wasm_c_api/externals/function.rs | 61 ++++++++++--------- lib/cli/src/commands/run/mod.rs | 6 +- lib/wasix/src/bin_factory/exec.rs | 6 +- tests/compilers/typed_functions.rs | 2 +- 16 files changed, 212 insertions(+), 134 deletions(-) diff --git a/examples/throw_exception.rs b/examples/throw_exception.rs index 69f8fb851e1..df36c8a4e5f 100644 --- a/examples/throw_exception.rs +++ b/examples/throw_exception.rs @@ -1,8 +1,8 @@ use std::sync::{Arc, atomic::AtomicUsize}; use wasmer::{ - DynamicFunctionResult, Function, FunctionEnv, FunctionEnvMut, FunctionType, Instance, Module, - RuntimeError, Store, Type, TypedFunction, Value, imports, sys::Target, wat2wasm, + Function, FunctionEnv, FunctionEnvMut, FunctionType, Instance, Module, RuntimeError, Store, + Type, TypedFunction, Value, imports, sys::Target, wat2wasm, }; use wasmer_types::Features; @@ -83,7 +83,10 @@ fn main() -> Result<(), Box> { // Both Function::new_with_env and Function::new_typed_with_env can throw // exceptions if they return a Result with a RuntimeError. - fn throw1(mut env: FunctionEnvMut, _params: &[Value]) -> DynamicFunctionResult { + fn throw1( + mut env: FunctionEnvMut, + _params: &[Value], + ) -> Result, RuntimeError> { println!("Throwing exception 1"); // To "throw" an exception from native code, we create a new one and diff --git a/lib/api/src/backend/js/entities/function/mod.rs b/lib/api/src/backend/js/entities/function/mod.rs index d37915d03cf..d69b21d4243 100644 --- a/lib/api/src/backend/js/entities/function/mod.rs +++ b/lib/api/src/backend/js/entities/function/mod.rs @@ -10,9 +10,9 @@ use wasmer_types::{FunctionType, RawValue}; use crate::{ AsStoreMut, AsStoreRef, BackendFunction, BackendFunctionEnv, BackendFunctionEnvMut, - DynamicCallResult, DynamicFunctionResult, FromToNativeWasmType, FunctionEnv, FunctionEnvMut, - HostFunction, HostFunctionKind, IntoResult, NativeWasmType, NativeWasmTypeInto, RuntimeError, - StoreMut, Value, WasmTypeList, WithEnv, WithoutEnv, + FromToNativeWasmType, FunctionEnv, FunctionEnvMut, HostFunction, HostFunctionKind, IntoResult, + NativeWasmType, NativeWasmTypeInto, RuntimeError, StoreMut, Value, WasmTypeList, WithEnv, + WithoutEnv, js::{ utils::convert::{AsJs as _, js_value_to_wasmer, wasmer_value_to_js}, vm::{VMFuncRef, VMFunctionCallback, function::VMFunction}, @@ -59,7 +59,10 @@ impl Function { ) -> Self where FT: Into, - F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> DynamicFunctionResult + 'static + Send + Sync, + F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> Result, RuntimeError> + + 'static + + Send + + Sync, { let mut store = store.as_store_mut(); let function_type = ty.into(); @@ -188,14 +191,18 @@ impl Function { &self, _store: &mut impl AsStoreMut, _params: Vec, - ) -> DynamicCallResult { + ) -> Result, RuntimeError> { // There is no optimal call_raw in JS, so we just // simply rely the call // self.call(store, params) unimplemented!(); } - pub fn call(&self, store: &mut impl AsStoreMut, params: &[Value]) -> DynamicCallResult { + pub fn call( + &self, + store: &mut impl AsStoreMut, + params: &[Value], + ) -> Result, RuntimeError> { // Annotation is here to prevent spurious IDE warnings. let arr = js_sys::Array::new_with_length(params.len() as u32); diff --git a/lib/api/src/backend/jsc/entities/function/mod.rs b/lib/api/src/backend/jsc/entities/function/mod.rs index 6f1ea59190c..afe36b214a9 100644 --- a/lib/api/src/backend/jsc/entities/function/mod.rs +++ b/lib/api/src/backend/jsc/entities/function/mod.rs @@ -9,10 +9,9 @@ use std::panic::{self, AssertUnwindSafe}; use wasmer_types::{FunctionType, RawValue}; use crate::{ - AsStoreMut, AsStoreRef, BackendFunction, BackendFunctionEnvMut, DynamicCallResult, - DynamicFunctionResult, FromToNativeWasmType, FunctionEnv, FunctionEnvMut, HostFunction, - HostFunctionKind, IntoResult, NativeWasmType, NativeWasmTypeInto, RuntimeError, StoreMut, - Value, WasmTypeList, WithEnv, WithoutEnv, + AsStoreMut, AsStoreRef, BackendFunction, BackendFunctionEnvMut, FromToNativeWasmType, + FunctionEnv, FunctionEnvMut, HostFunction, HostFunctionKind, IntoResult, NativeWasmType, + NativeWasmTypeInto, RuntimeError, StoreMut, Value, WasmTypeList, WithEnv, WithoutEnv, jsc::{ store::{InternalStoreHandle, StoreHandle}, utils::convert::{AsJsc, jsc_value_to_wasmer}, @@ -58,7 +57,10 @@ impl Function { ) -> Self where FT: Into, - F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> DynamicFunctionResult + 'static + Send + Sync, + F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> Result, RuntimeError> + + 'static + + Send + + Sync, { let store = store.as_store_mut(); let context = store.jsc().context(); @@ -180,14 +182,18 @@ impl Function { &self, _store: &mut impl AsStoreMut, _params: Vec, - ) -> DynamicCallResult { + ) -> Result, RuntimeError> { // There is no optimal call_raw in JSC, so we just // simply rely the call // self.call(store, params) unimplemented!(); } - pub fn call(&self, store: &mut impl AsStoreMut, params: &[Value]) -> DynamicCallResult { + pub fn call( + &self, + store: &mut impl AsStoreMut, + params: &[Value], + ) -> Result, RuntimeError> { let store_mut = store.as_store_mut(); let engine = store_mut.engine(); let context = engine.as_jsc().context(); diff --git a/lib/api/src/backend/sys/async_runtime.rs b/lib/api/src/backend/sys/async_runtime.rs index 5c3aea56cff..0ebaf406842 100644 --- a/lib/api/src/backend/sys/async_runtime.rs +++ b/lib/api/src/backend/sys/async_runtime.rs @@ -13,13 +13,12 @@ use corosensei::{Coroutine, CoroutineResult, Yielder}; use super::entities::function::Function as SysFunction; use crate::{ - AsStoreMut, AsStoreRef, DynamicCallResult, DynamicFunctionResult, ForcedStoreInstallGuard, - LocalRwLockWriteGuard, RuntimeError, Store, StoreAsync, StoreContext, StoreInner, StoreMut, - StoreRef, Value, + AsStoreMut, AsStoreRef, ForcedStoreInstallGuard, LocalRwLockWriteGuard, RuntimeError, Store, + StoreAsync, StoreContext, StoreInner, StoreMut, StoreRef, Value, }; use wasmer_types::StoreId; -type HostFuture = Pin + 'static>>; +type HostFuture = Pin, RuntimeError>> + 'static>>; pub(crate) fn call_function_async( function: SysFunction, @@ -33,15 +32,16 @@ struct AsyncYield(HostFuture); enum AsyncResume { Start, - HostFutureReady(DynamicFunctionResult), + HostFutureReady(Result, RuntimeError>), } +#[allow(clippy::type_complexity)] pub(crate) struct AsyncCallFuture { - coroutine: Option>, + coroutine: Option, RuntimeError>>>, pending_store_install: Option>>>, pending_future: Option, next_resume: Option, - result: Option, + result: Option, RuntimeError>>, // Store handle we can use to lock the store down store: StoreAsync, @@ -130,7 +130,7 @@ impl AsyncCallFuture { } impl Future for AsyncCallFuture { - type Output = DynamicCallResult; + type Output = Result, RuntimeError>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { loop { @@ -231,7 +231,7 @@ pub enum AsyncRuntimeError { pub(crate) fn block_on_host_future(future: Fut) -> Result, AsyncRuntimeError> where - Fut: Future + 'static, + Fut: Future, RuntimeError>> + 'static, { CURRENT_CONTEXT.with(|cell| { match CoroutineContext::get_current() { @@ -294,7 +294,7 @@ impl CoroutineContext { CURRENT_CONTEXT.with(|cell| cell.borrow().last().copied()) } - fn block_on_future(&self, future: HostFuture) -> DynamicFunctionResult { + fn block_on_future(&self, future: HostFuture) -> Result, RuntimeError> { // Leave the coroutine context since we're yielding back to the // parent stack, and will be inactive until the future is ready. self.leave(); @@ -314,7 +314,7 @@ impl CoroutineContext { } fn run_immediate( - future: impl Future + 'static, + future: impl Future, RuntimeError>> + 'static, ) -> Result, AsyncRuntimeError> { let waker = futures::task::noop_waker(); let mut cx = Context::from_waker(&waker); diff --git a/lib/api/src/backend/sys/entities/function/mod.rs b/lib/api/src/backend/sys/entities/function/mod.rs index a26f79e14ba..04a364f9bfd 100644 --- a/lib/api/src/backend/sys/entities/function/mod.rs +++ b/lib/api/src/backend/sys/entities/function/mod.rs @@ -13,8 +13,8 @@ use crate::{ }, }; use crate::{ - BackendFunction, DynamicCallResult, DynamicFunctionResult, FunctionEnv, FunctionEnvMut, - FunctionType, HostFunction, RuntimeError, StoreContext, StoreInner, Value, WithEnv, WithoutEnv, + BackendFunction, FunctionEnv, FunctionEnvMut, FunctionType, HostFunction, RuntimeError, + StoreContext, StoreInner, Value, WithEnv, WithoutEnv, backend::sys::{engine::NativeEngineExt, vm::VMFunctionCallback}, entities::store::{AsStoreMut, AsStoreRef, StoreMut}, utils::{FromToNativeWasmType, IntoResult, NativeWasmTypeInto, WasmTypeList}, @@ -55,7 +55,10 @@ impl Function { ) -> Self where FT: Into, - F: Fn(FunctionEnvMut, &[Value]) -> DynamicFunctionResult + 'static + Send + Sync, + F: Fn(FunctionEnvMut, &[Value]) -> Result, RuntimeError> + + 'static + + Send + + Sync, { let function_type = ty.into(); let func_ty = function_type.clone(); @@ -132,7 +135,7 @@ impl Function { where FT: Into, F: Fn(&[Value]) -> Fut + 'static, - Fut: Future + 'static, + Fut: Future, RuntimeError>> + 'static, { let env = FunctionEnv::new(store, ()); let wrapped = move |_env: AsyncFunctionEnvMut<()>, values: &[Value]| func(values); @@ -149,7 +152,7 @@ impl Function { where FT: Into, F: Fn(AsyncFunctionEnvMut, &[Value]) -> Fut + 'static, - Fut: Future + 'static, + Fut: Future, RuntimeError>> + 'static, { let function_type = ty.into(); let func_ty = function_type.clone(); @@ -302,7 +305,9 @@ impl Function { store, &env, signature, - move |mut env_mut, values| -> Pin>> { + move |mut env_mut, + values| + -> Pin, RuntimeError>>>> { let sys_env = match env_mut.0 { BackendAsyncFunctionEnvMut::Sys(ref mut sys_env) => sys_env, _ => panic!("Not a sys backend"), @@ -354,7 +359,9 @@ impl Function { store, env, signature, - move |mut env_mut, values| -> Pin>> { + move |mut env_mut, + values| + -> Pin, RuntimeError>>>> { let sys_env = match env_mut.0 { BackendAsyncFunctionEnvMut::Sys(ref mut sys_env) => sys_env, _ => panic!("Not a sys backend"), @@ -573,7 +580,11 @@ impl Function { self.ty(store).results().len() } - pub(crate) fn call(&self, store: &mut impl AsStoreMut, params: &[Value]) -> DynamicCallResult { + pub(crate) fn call( + &self, + store: &mut impl AsStoreMut, + params: &[Value], + ) -> Result, RuntimeError> { let trampoline = unsafe { self.handle .get(store.objects_mut().as_sys()) @@ -588,11 +599,12 @@ impl Function { } #[cfg(feature = "experimental-async")] + #[allow(clippy::type_complexity)] pub(crate) fn call_async( &self, store: &impl AsStoreAsync, params: Vec, - ) -> Pin + 'static>> { + ) -> Pin, RuntimeError>> + 'static>> { let function = self.clone(); let store = store.store(); Box::pin(call_function_async(function, store, params)) @@ -604,7 +616,7 @@ impl Function { &self, store: &mut impl AsStoreMut, params: Vec, - ) -> DynamicCallResult { + ) -> Result, RuntimeError> { let trampoline = unsafe { self.handle .get(store.objects_mut().as_sys()) @@ -725,7 +737,7 @@ fn finalize_dynamic_call( store_id: StoreId, func_ty: FunctionType, values_vec: *mut RawValue, - result: DynamicFunctionResult, + result: Result, RuntimeError>, ) -> Result<(), RuntimeError> { match result { Ok(values) => write_dynamic_results(store_id, &func_ty, values, values_vec), @@ -767,7 +779,7 @@ fn typed_results_to_values( store: &mut StoreMut, func_ty: &FunctionType, rets: Rets, -) -> DynamicFunctionResult +) -> Result, RuntimeError> where Rets: WasmTypeList, { @@ -784,12 +796,12 @@ where pub(crate) enum HostCallOutcome { Ready { func_ty: FunctionType, - result: DynamicFunctionResult, + result: Result, RuntimeError>, }, #[cfg(feature = "experimental-async")] Future { func_ty: FunctionType, - future: Pin>>, + future: Pin, RuntimeError>>>>, }, } diff --git a/lib/api/src/backend/v8/entities/function/mod.rs b/lib/api/src/backend/v8/entities/function/mod.rs index c11c2bd436c..fd5fe497e6a 100644 --- a/lib/api/src/backend/v8/entities/function/mod.rs +++ b/lib/api/src/backend/v8/entities/function/mod.rs @@ -6,10 +6,9 @@ use std::{ }; use crate::{ - AsStoreMut, AsStoreRef, BackendFunction, BackendFunctionEnvMut, BackendTrap, DynamicCallResult, - DynamicFunctionResult, FromToNativeWasmType, FunctionEnv, FunctionEnvMut, IntoResult, - NativeWasmType, NativeWasmTypeInto, RuntimeError, StoreMut, Value, WasmTypeList, WithEnv, - WithoutEnv, + AsStoreMut, AsStoreRef, BackendFunction, BackendFunctionEnvMut, BackendTrap, + FromToNativeWasmType, FunctionEnv, FunctionEnvMut, IntoResult, NativeWasmType, + NativeWasmTypeInto, RuntimeError, StoreMut, Value, WasmTypeList, WithEnv, WithoutEnv, v8::{ bindings::*, utils::convert::{IntoCApiType, IntoCApiValue, IntoWasmerType, IntoWasmerValue}, @@ -81,7 +80,10 @@ impl Function { ) -> Self where FT: Into, - F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> DynamicFunctionResult + 'static + Send + Sync, + F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> Result, RuntimeError> + + 'static + + Send + + Sync, { check_isolate(store); @@ -336,14 +338,18 @@ impl Function { &self, _store: &mut impl AsStoreMut, _params: Vec, - ) -> DynamicCallResult { + ) -> Result, RuntimeError> { // There is no optimal call_raw in JSC, so we just // simply rely the call // self.call(store, params) unimplemented!(); } - pub fn call(&self, store: &mut impl AsStoreMut, params: &[Value]) -> DynamicCallResult { + pub fn call( + &self, + store: &mut impl AsStoreMut, + params: &[Value], + ) -> Result, RuntimeError> { check_isolate(store); // unimplemented!(); let store_mut = store.as_store_mut(); @@ -432,7 +438,10 @@ impl Function { fn make_fn_callback(func: &F, args: usize) -> CCallback where - F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> DynamicFunctionResult + 'static + Send + Sync, + F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> Result, RuntimeError> + + 'static + + Send + + Sync, { unsafe extern "C" fn fn_callback( env: *mut c_void, @@ -440,7 +449,10 @@ where rets: *mut wasm_val_vec_t, ) -> *mut wasm_trap_t where - F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> DynamicFunctionResult + 'static + Send + Sync, + F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> Result, RuntimeError> + + 'static + + Send + + Sync, { let r: *mut (FunctionCallbackEnv<'_, F>) = env as _; diff --git a/lib/api/src/backend/wamr/entities/function/mod.rs b/lib/api/src/backend/wamr/entities/function/mod.rs index 955cd7779ec..903cbad7025 100644 --- a/lib/api/src/backend/wamr/entities/function/mod.rs +++ b/lib/api/src/backend/wamr/entities/function/mod.rs @@ -7,10 +7,9 @@ use std::{ }; use crate::{ - AsStoreMut, AsStoreRef, BackendFunction, BackendFunctionEnvMut, BackendTrap, DynamicCallResult, - DynamicFunctionResult, FromToNativeWasmType, FunctionEnv, FunctionEnvMut, IntoResult, - NativeWasmType, NativeWasmTypeInto, RuntimeError, StoreMut, Value, WasmTypeList, WithEnv, - WithoutEnv, + AsStoreMut, AsStoreRef, BackendFunction, BackendFunctionEnvMut, BackendTrap, + FromToNativeWasmType, FunctionEnv, FunctionEnvMut, IntoResult, NativeWasmType, + NativeWasmTypeInto, RuntimeError, StoreMut, Value, WasmTypeList, WithEnv, WithoutEnv, vm::{VMExtern, VMExternFunction}, wamr::{ bindings::*, @@ -82,7 +81,10 @@ impl Function { ) -> Self where FT: Into, - F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> DynamicFunctionResult + 'static + Send + Sync, + F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> Result, RuntimeError> + + 'static + + Send + + Sync, { let fn_ty: FunctionType = ty.into(); let params = fn_ty.params(); @@ -342,14 +344,18 @@ impl Function { &self, _store: &mut impl AsStoreMut, _params: Vec, - ) -> DynamicCallResult { + ) -> Result, RuntimeError> { // There is no optimal call_raw in JSC, so we just // simply rely the call // self.call(store, params) unimplemented!(); } - pub fn call(&self, store: &mut impl AsStoreMut, params: &[Value]) -> DynamicCallResult { + pub fn call( + &self, + store: &mut impl AsStoreMut, + params: &[Value], + ) -> Result, RuntimeError> { // unimplemented!(); let store_mut = store.as_store_mut(); // let wasm_func_param_arity(self.handle) @@ -435,7 +441,10 @@ impl Function { fn make_fn_callback(func: &F, args: usize) -> CCallback where - F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> DynamicFunctionResult + 'static + Send + Sync, + F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> Result, RuntimeError> + + 'static + + Send + + Sync, { unsafe extern "C" fn fn_callback( env: *mut c_void, @@ -443,7 +452,10 @@ where rets: *mut wasm_val_vec_t, ) -> *mut wasm_trap_t where - F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> DynamicFunctionResult + 'static + Send + Sync, + F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> Result, RuntimeError> + + 'static + + Send + + Sync, { let r: *mut (FunctionCallbackEnv<'_, F>) = env as _; diff --git a/lib/api/src/backend/wasmi/entities/function/mod.rs b/lib/api/src/backend/wasmi/entities/function/mod.rs index d6909fc5bb3..7526fd56b20 100644 --- a/lib/api/src/backend/wasmi/entities/function/mod.rs +++ b/lib/api/src/backend/wasmi/entities/function/mod.rs @@ -7,10 +7,9 @@ use std::{ }; use crate::{ - AsStoreMut, AsStoreRef, BackendFunction, BackendFunctionEnvMut, BackendTrap, DynamicCallResult, - DynamicFunctionResult, FromToNativeWasmType, FunctionEnv, FunctionEnvMut, IntoResult, - NativeWasmType, NativeWasmTypeInto, RuntimeError, StoreMut, Value, WasmTypeList, WithEnv, - WithoutEnv, + AsStoreMut, AsStoreRef, BackendFunction, BackendFunctionEnvMut, BackendTrap, + FromToNativeWasmType, FunctionEnv, FunctionEnvMut, IntoResult, NativeWasmType, + NativeWasmTypeInto, RuntimeError, StoreMut, Value, WasmTypeList, WithEnv, WithoutEnv, vm::{VMExtern, VMExternFunction}, wasmi::{ bindings::*, @@ -82,7 +81,10 @@ impl Function { ) -> Self where FT: Into, - F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> DynamicFunctionResult + 'static + Send + Sync, + F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> Result, RuntimeError> + + 'static + + Send + + Sync, { let fn_ty: FunctionType = ty.into(); let params = fn_ty.params(); @@ -338,14 +340,18 @@ impl Function { &self, _store: &mut impl AsStoreMut, _params: Vec, - ) -> DynamicCallResult { + ) -> Result, RuntimeError> { // There is no optimal call_raw in JSC, so we just // simply rely the call // self.call(store, params) unimplemented!(); } - pub fn call(&self, store: &mut impl AsStoreMut, params: &[Value]) -> DynamicCallResult { + pub fn call( + &self, + store: &mut impl AsStoreMut, + params: &[Value], + ) -> Result, RuntimeError> { // unimplemented!(); let store_mut = store.as_store_mut(); // let wasm_func_param_arity(self.handle) @@ -436,7 +442,10 @@ impl Function { fn make_fn_callback(func: &F, args: usize) -> CCallback where - F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> DynamicFunctionResult + 'static + Send + Sync, + F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> Result, RuntimeError> + + 'static + + Send + + Sync, { unsafe extern "C" fn fn_callback( env: *mut c_void, @@ -444,7 +453,10 @@ where rets: *mut wasm_val_vec_t, ) -> *mut wasm_trap_t where - F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> DynamicFunctionResult + 'static + Send + Sync, + F: Fn(FunctionEnvMut<'_, T>, &[Value]) -> Result, RuntimeError> + + 'static + + Send + + Sync, { let r: *mut (FunctionCallbackEnv<'_, F>) = env as _; diff --git a/lib/api/src/entities/function/inner.rs b/lib/api/src/entities/function/inner.rs index b5de485b353..b6108e17bf3 100644 --- a/lib/api/src/entities/function/inner.rs +++ b/lib/api/src/entities/function/inner.rs @@ -5,9 +5,8 @@ use wasmer_types::{FunctionType, RawValue}; #[cfg(feature = "experimental-async")] use crate::{AsStoreAsync, AsyncFunctionEnvMut, entities::function::async_host::AsyncHostFunction}; use crate::{ - AsStoreMut, AsStoreRef, DynamicCallResult, DynamicFunctionResult, ExportError, Exportable, - Extern, FunctionEnv, FunctionEnvMut, HostFunction, StoreMut, StoreRef, TypedFunction, Value, - WasmTypeList, WithEnv, WithoutEnv, + AsStoreMut, AsStoreRef, ExportError, Exportable, Extern, FunctionEnv, FunctionEnvMut, + HostFunction, StoreMut, StoreRef, TypedFunction, Value, WasmTypeList, WithEnv, WithoutEnv, error::RuntimeError, macros::backend::{gen_rt_ty, match_rt}, vm::{VMExtern, VMExternFunction, VMFuncRef}, @@ -44,11 +43,12 @@ impl BackendFunction { pub fn new(store: &mut impl AsStoreMut, ty: FT, func: F) -> Self where FT: Into, - F: Fn(&[Value]) -> DynamicFunctionResult + 'static + Send + Sync, + F: Fn(&[Value]) -> Result, RuntimeError> + 'static + Send + Sync, { let env = FunctionEnv::new(&mut store.as_store_mut(), ()); - let wrapped_func = - move |_env: FunctionEnvMut<()>, args: &[Value]| -> DynamicFunctionResult { func(args) }; + let wrapped_func = move |_env: FunctionEnvMut<()>, + args: &[Value]| + -> Result, RuntimeError> { func(args) }; Self::new_with_env(store, &env, ty, wrapped_func) } @@ -98,7 +98,10 @@ impl BackendFunction { ) -> Self where FT: Into, - F: Fn(FunctionEnvMut, &[Value]) -> DynamicFunctionResult + 'static + Send + Sync, + F: Fn(FunctionEnvMut, &[Value]) -> Result, RuntimeError> + + 'static + + Send + + Sync, { match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] @@ -266,7 +269,7 @@ impl BackendFunction { where FT: Into, F: Fn(&[Value]) -> Fut + 'static, - Fut: Future + 'static, + Fut: Future, RuntimeError>> + 'static, { match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] @@ -305,7 +308,7 @@ impl BackendFunction { where FT: Into, F: Fn(AsyncFunctionEnvMut, &[Value]) -> Fut + 'static, - Fut: Future + 'static, + Fut: Future, RuntimeError>> + 'static, { match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] @@ -491,7 +494,11 @@ impl BackendFunction { /// assert_eq!(sum.call(&mut store, &[Value::I32(1), Value::I32(2)]).unwrap().to_vec(), vec![Value::I32(3)]); /// ``` #[inline] - pub fn call(&self, store: &mut impl AsStoreMut, params: &[Value]) -> DynamicCallResult { + pub fn call( + &self, + store: &mut impl AsStoreMut, + params: &[Value], + ) -> Result, RuntimeError> { match_rt!(on self => f { f.call(store, params) }) @@ -504,18 +511,19 @@ impl BackendFunction { &self, store: &mut impl AsStoreMut, params: Vec, - ) -> DynamicCallResult { + ) -> Result, RuntimeError> { match_rt!(on self => f { f.call_raw(store, params) }) } #[cfg(feature = "experimental-async")] + #[allow(clippy::type_complexity)] pub fn call_async( &self, store: &impl AsStoreAsync, params: Vec, - ) -> Pin + 'static>> { + ) -> Pin, RuntimeError>> + 'static>> { match self { #[cfg(feature = "sys")] Self::Sys(f) => f.call_async(store, params), @@ -773,8 +781,9 @@ fn unsupported_async_backend(backend: &str) -> ! { ) } -pub(super) fn unsupported_async_future<'a>() -> Pin + 'a>> -{ +#[allow(clippy::type_complexity)] +pub(super) fn unsupported_async_future<'a>() +-> Pin, RuntimeError>> + 'a>> { Box::pin(async { Err(RuntimeError::new( "async calls are only supported with the `sys` backend", diff --git a/lib/api/src/entities/function/mod.rs b/lib/api/src/entities/function/mod.rs index 10179263ce7..c2c08ce0e41 100644 --- a/lib/api/src/entities/function/mod.rs +++ b/lib/api/src/entities/function/mod.rs @@ -28,12 +28,6 @@ use crate::{ vm::{VMExtern, VMExternFunction, VMFuncRef}, }; -/// The return type from dynamic imported functions. -pub type DynamicFunctionResult = Result, RuntimeError>; - -/// The return type from dynamically calling Wasm functions. -pub type DynamicCallResult = Result, RuntimeError>; - /// A WebAssembly `function` instance. /// /// A function instance is the runtime representation of a function. @@ -63,7 +57,7 @@ impl Function { pub fn new(store: &mut impl AsStoreMut, ty: FT, func: F) -> Self where FT: Into, - F: Fn(&[Value]) -> DynamicFunctionResult + 'static + Send + Sync, + F: Fn(&[Value]) -> Result, RuntimeError> + 'static + Send + Sync, { Self(BackendFunction::new(store, ty, func)) } @@ -113,7 +107,10 @@ impl Function { ) -> Self where FT: Into, - F: Fn(FunctionEnvMut, &[Value]) -> DynamicFunctionResult + 'static + Send + Sync, + F: Fn(FunctionEnvMut, &[Value]) -> Result, RuntimeError> + + 'static + + Send + + Sync, { Self(BackendFunction::new_with_env(store, env, ty, func)) } @@ -174,7 +171,7 @@ impl Function { where FT: Into, F: Fn(&[Value]) -> Fut + 'static, - Fut: Future + 'static, + Fut: Future, RuntimeError>> + 'static, { Self(BackendFunction::new_async(store, ty, func)) } @@ -197,7 +194,7 @@ impl Function { where FT: Into, F: Fn(AsyncFunctionEnvMut, &[Value]) -> Fut + 'static, - Fut: Future + 'static, + Fut: Future, RuntimeError>> + 'static, { Self(BackendFunction::new_with_env_async(store, env, ty, func)) } @@ -326,7 +323,11 @@ impl Function { /// /// assert_eq!(sum.call(&mut store, &[Value::I32(1), Value::I32(2)]).unwrap().to_vec(), vec![Value::I32(3)]); /// ``` - pub fn call(&self, store: &mut impl AsStoreMut, params: &[Value]) -> DynamicCallResult { + pub fn call( + &self, + store: &mut impl AsStoreMut, + params: &[Value], + ) -> Result, RuntimeError> { self.0.call(store, params) } @@ -342,7 +343,7 @@ impl Function { &self, store: &impl AsStoreAsync, params: Vec, - ) -> impl Future + 'static { + ) -> impl Future, RuntimeError>> + 'static { self.0.call_async(store, params) } @@ -352,7 +353,7 @@ impl Function { &self, store: &mut impl AsStoreMut, params: Vec, - ) -> DynamicCallResult { + ) -> Result, RuntimeError> { self.0.call_raw(store, params) } diff --git a/lib/api/tests/instance.rs b/lib/api/tests/instance.rs index e6e954d47f5..a0c5e48ed3a 100644 --- a/lib/api/tests/instance.rs +++ b/lib/api/tests/instance.rs @@ -61,7 +61,7 @@ fn unit_native_function_env() -> Result<(), String> { multiplier: u32, } - fn imported_fn(env: FunctionEnvMut, args: &[Value]) -> DynamicFunctionResult { + fn imported_fn(env: FunctionEnvMut, args: &[Value]) -> Result, RuntimeError> { let value = env.data().multiplier * args[0].unwrap_i32() as u32; Ok(vec![Value::I32(value as _)]) } diff --git a/lib/api/tests/reference_types.rs b/lib/api/tests/reference_types.rs index e3df626cb8a..3350873bfe4 100644 --- a/lib/api/tests/reference_types.rs +++ b/lib/api/tests/reference_types.rs @@ -82,7 +82,10 @@ pub mod reference_types { )"#; let module = Module::new(&store, wat)?; let env = FunctionEnv::new(&mut store, ()); - fn func_ref_call(mut env: FunctionEnvMut<()>, values: &[Value]) -> DynamicFunctionResult { + fn func_ref_call( + mut env: FunctionEnvMut<()>, + values: &[Value], + ) -> Result, RuntimeError> { // TODO: look into `Box<[Value]>` being returned breakage let f = values[0].unwrap_funcref().as_ref().unwrap(); let f: TypedFunction<(i32, i32), i32> = f.typed(&env)?; diff --git a/lib/c-api/src/wasm_c_api/externals/function.rs b/lib/c-api/src/wasm_c_api/externals/function.rs index c64c0741d7f..19eeb971967 100644 --- a/lib/c-api/src/wasm_c_api/externals/function.rs +++ b/lib/c-api/src/wasm_c_api/externals/function.rs @@ -8,7 +8,7 @@ use libc::c_void; use std::convert::TryInto; use std::mem::MaybeUninit; use std::sync::{Arc, Mutex}; -use wasmer_api::{DynamicFunctionResult, Extern, Function, FunctionEnv, FunctionEnvMut, Value}; +use wasmer_api::{Extern, Function, FunctionEnv, FunctionEnvMut, Value}; #[derive(Clone)] #[allow(non_camel_case_types)] @@ -57,7 +57,7 @@ pub unsafe extern "C" fn wasm_func_new( let num_rets = func_sig.results().len(); let inner_callback = move |mut _env: FunctionEnvMut<'_, FunctionCEnv>, args: &[Value]| - -> DynamicFunctionResult { + -> Result, RuntimeError> { let processed_args: wasm_val_vec_t = args .iter() .map(TryInto::try_into) @@ -134,39 +134,40 @@ pub unsafe extern "C" fn wasm_func_new_with_env( } } } - let inner_callback = - move |env: FunctionEnvMut<'_, WrapperEnv>, args: &[Value]| -> DynamicFunctionResult { - let processed_args: wasm_val_vec_t = args - .iter() - .map(TryInto::try_into) - .collect::, _>>() - .expect("Argument conversion failed") - .into(); - - let mut results: wasm_val_vec_t = vec![ - wasm_val_t { - kind: wasm_valkind_enum::WASM_I64 as _, - of: wasm_val_inner { int64_t: 0 }, - }; - num_rets - ] + let inner_callback = move |env: FunctionEnvMut<'_, WrapperEnv>, + args: &[Value]| + -> Result, RuntimeError> { + let processed_args: wasm_val_vec_t = args + .iter() + .map(TryInto::try_into) + .collect::, _>>() + .expect("Argument conversion failed") .into(); - let trap = unsafe { callback(env.data().env.as_ptr(), &processed_args, &mut results) }; + let mut results: wasm_val_vec_t = vec![ + wasm_val_t { + kind: wasm_valkind_enum::WASM_I64 as _, + of: wasm_val_inner { int64_t: 0 }, + }; + num_rets + ] + .into(); - if let Some(trap) = trap { - return Err(trap.inner); - } + let trap = unsafe { callback(env.data().env.as_ptr(), &processed_args, &mut results) }; - let processed_results = results - .take() - .into_iter() - .map(TryInto::try_into) - .collect::, _>>() - .expect("Result conversion failed"); + if let Some(trap) = trap { + return Err(trap.inner); + } - Ok(processed_results) - }; + let processed_results = results + .take() + .into_iter() + .map(TryInto::try_into) + .collect::, _>>() + .expect("Result conversion failed"); + + Ok(processed_results) + }; let env = FunctionEnv::new( &mut store_mut, WrapperEnv { diff --git a/lib/cli/src/commands/run/mod.rs b/lib/cli/src/commands/run/mod.rs index 87deae32795..881681a8967 100644 --- a/lib/cli/src/commands/run/mod.rs +++ b/lib/cli/src/commands/run/mod.rs @@ -29,8 +29,8 @@ use url::Url; #[cfg(feature = "sys")] use wasmer::sys::NativeEngineExt; use wasmer::{ - AsStoreMut, DeserializeError, DynamicCallResult, Engine, Function, Imports, Instance, Module, - RuntimeError, Store, Type, TypedFunction, Value, + AsStoreMut, DeserializeError, Engine, Function, Imports, Instance, Module, RuntimeError, Store, + Type, TypedFunction, Value, }; use wasmer_types::{Features, target::Target}; @@ -645,7 +645,7 @@ fn invoke_function( store: &mut Store, func: &Function, args: &[String], -) -> anyhow::Result { +) -> anyhow::Result, RuntimeError>> { let func_ty = func.ty(store); let required_arguments = func_ty.params().len(); let provided_arguments = args.len(); diff --git a/lib/wasix/src/bin_factory/exec.rs b/lib/wasix/src/bin_factory/exec.rs index b51ad958ae7..d077394ddb8 100644 --- a/lib/wasix/src/bin_factory/exec.rs +++ b/lib/wasix/src/bin_factory/exec.rs @@ -18,7 +18,7 @@ use crate::{ }; use tracing::*; use virtual_mio::block_on; -use wasmer::{DynamicCallResult, Function, Memory32, Memory64, Module, RuntimeError, Store}; +use wasmer::{Function, Memory32, Memory64, Module, RuntimeError, Store, Value}; use wasmer_wasix_types::wasi::Errno; use super::{BinaryPackage, BinaryPackageCommand}; @@ -397,8 +397,8 @@ fn resume_vfork( ctx: &WasiFunctionEnv, store: &mut Store, start: &Function, - call_ret: &DynamicCallResult, -) -> Result, Errno> { + call_ret: &Result, RuntimeError>, +) -> Result, RuntimeError>>, Errno> { let (err, code) = match call_ret { Ok(_) => (None, wasmer_wasix_types::wasi::ExitCode::from(0u16)), Err(err) => match err.downcast_ref::() { diff --git a/tests/compilers/typed_functions.rs b/tests/compilers/typed_functions.rs index f93a3b8e612..17754291b48 100644 --- a/tests/compilers/typed_functions.rs +++ b/tests/compilers/typed_functions.rs @@ -22,7 +22,7 @@ fn long_f(a: u32, b: u32, c: u32, d: u32, e: u32, f: u16, g: u64, h: u64, i: u16 + a as u64 * 1000000000 } -fn long_f_dynamic(values: &[Value]) -> DynamicFunctionResult { +fn long_f_dynamic(values: &[Value]) -> Result, RuntimeError> { Ok(vec![Value::I64( values[9].unwrap_i32() as i64 + values[8].unwrap_i32() as i64 * 10 From a6f6530e406eff0349d45e9351b2c83b743c9c9c Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Sat, 6 Dec 2025 01:44:49 +0400 Subject: [PATCH 46/49] Fix BackendAsyncFunctionEnvMut when both sys and other backends are enabled --- lib/api/src/entities/function/env/inner.rs | 177 +++++++++++++++++++-- 1 file changed, 163 insertions(+), 14 deletions(-) diff --git a/lib/api/src/entities/function/env/inner.rs b/lib/api/src/entities/function/env/inner.rs index ee9d85410f7..e1b576623b2 100644 --- a/lib/api/src/entities/function/env/inner.rs +++ b/lib/api/src/entities/function/env/inner.rs @@ -124,6 +124,7 @@ pub enum BackendFunctionEnvMut<'a, T: 'a> { #[cfg(feature = "sys")] /// The function environment for the `sys` runtime. Sys(crate::backend::sys::function::env::FunctionEnvMut<'a, T>), + #[cfg(feature = "wamr")] /// The function environment for the `wamr` runtime. Wamr(crate::backend::wamr::function::env::FunctionEnvMut<'a, T>), @@ -131,6 +132,7 @@ pub enum BackendFunctionEnvMut<'a, T: 'a> { #[cfg(feature = "wasmi")] /// The function environment for the `wasmi` runtime. Wasmi(crate::backend::wasmi::function::env::FunctionEnvMut<'a, T>), + #[cfg(feature = "v8")] /// The function environment for the `v8` runtime. V8(crate::backend::v8::function::env::FunctionEnvMut<'a, T>), @@ -253,25 +255,35 @@ where /// A shared handle to a [`FunctionEnv`], suitable for use /// in async imports. #[derive(derive_more::From)] -#[non_exhaustive] #[cfg(feature = "experimental-async")] pub enum BackendAsyncFunctionEnvMut { #[cfg(feature = "sys")] /// The function environment for the `sys` runtime. Sys(crate::backend::sys::function::env::AsyncFunctionEnvMut), - #[cfg(not(feature = "sys"))] + #[cfg(any( + feature = "wamr", + feature = "wasmi", + feature = "v8", + feature = "js", + feature = "jsc" + ))] /// Placeholder for unsupported backends. Unsupported(PhantomData), } /// A read-only handle to the [`FunctionEnv`] in an [`BackendAsyncFunctionEnvMut`]. -#[non_exhaustive] #[cfg(feature = "experimental-async")] pub enum BackendAsyncFunctionEnvHandle { #[cfg(feature = "sys")] /// The function environment handle for the `sys` runtime. Sys(crate::backend::sys::function::env::AsyncFunctionEnvHandle), - #[cfg(not(feature = "sys"))] + #[cfg(any( + feature = "wamr", + feature = "wasmi", + feature = "v8", + feature = "js", + feature = "jsc" + ))] /// Placeholder for unsupported backends. Unsupported(PhantomData), } @@ -282,7 +294,13 @@ pub enum BackendAsyncFunctionEnvHandleMut { #[cfg(feature = "sys")] /// The function environment handle for the `sys` runtime. Sys(crate::backend::sys::function::env::AsyncFunctionEnvHandleMut), - #[cfg(not(feature = "sys"))] + #[cfg(any( + feature = "wamr", + feature = "wasmi", + feature = "v8", + feature = "js", + feature = "jsc" + ))] /// Placeholder for unsupported backends. Unsupported(PhantomData), } @@ -295,6 +313,13 @@ impl BackendAsyncFunctionEnvMut { match self { #[cfg(feature = "sys")] Self::Sys(f) => BackendAsyncFunctionEnvHandle::Sys(f.read().await), + #[cfg(any( + feature = "wamr", + feature = "wasmi", + feature = "v8", + feature = "js", + feature = "jsc" + ))] _ => unsupported_async_backend(), } } @@ -305,6 +330,13 @@ impl BackendAsyncFunctionEnvMut { match self { #[cfg(feature = "sys")] Self::Sys(f) => BackendAsyncFunctionEnvHandleMut::Sys(f.write().await), + #[cfg(any( + feature = "wamr", + feature = "wasmi", + feature = "v8", + feature = "js", + feature = "jsc" + ))] _ => unsupported_async_backend(), } } @@ -314,6 +346,13 @@ impl BackendAsyncFunctionEnvMut { match self { #[cfg(feature = "sys")] Self::Sys(f) => BackendFunctionEnv::Sys(f.as_ref()), + #[cfg(any( + feature = "wamr", + feature = "wasmi", + feature = "v8", + feature = "js", + feature = "jsc" + ))] _ => unsupported_async_backend(), } } @@ -323,6 +362,13 @@ impl BackendAsyncFunctionEnvMut { match self { #[cfg(feature = "sys")] Self::Sys(f) => Self::Sys(f.as_mut()), + #[cfg(any( + feature = "wamr", + feature = "wasmi", + feature = "v8", + feature = "js", + feature = "jsc" + ))] _ => unsupported_async_backend(), } } @@ -332,7 +378,27 @@ impl BackendAsyncFunctionEnvMut { match self { #[cfg(feature = "sys")] Self::Sys(f) => f.as_store_async(), - #[cfg(not(feature = "sys"))] + #[cfg(all( + feature = "sys", + any( + feature = "wamr", + feature = "wasmi", + feature = "v8", + feature = "js", + feature = "jsc" + ) + ))] + _ => unsupported_async_backend(), + #[cfg(all( + not(feature = "sys"), + any( + feature = "wamr", + feature = "wasmi", + feature = "v8", + feature = "js", + feature = "jsc" + ) + ))] _ => unsupported_async_backend::(), } } @@ -345,6 +411,13 @@ impl BackendAsyncFunctionEnvHandle { match self { #[cfg(feature = "sys")] Self::Sys(f) => f.data(), + #[cfg(any( + feature = "wamr", + feature = "wasmi", + feature = "v8", + feature = "js", + feature = "jsc" + ))] _ => unsupported_async_backend(), } } @@ -354,7 +427,27 @@ impl BackendAsyncFunctionEnvHandle { match self { #[cfg(feature = "sys")] Self::Sys(f) => f.data_and_store(), - #[cfg(not(feature = "sys"))] + #[cfg(all( + feature = "sys", + any( + feature = "wamr", + feature = "wasmi", + feature = "v8", + feature = "js", + feature = "jsc" + ) + ))] + _ => unsupported_async_backend(), + #[cfg(all( + not(feature = "sys"), + any( + feature = "wamr", + feature = "wasmi", + feature = "v8", + feature = "js", + feature = "jsc" + ) + ))] _ => unsupported_async_backend::<(&T, &StoreRef)>(), } } @@ -366,7 +459,13 @@ impl AsStoreRef for BackendAsyncFunctionEnvHandle { match self { #[cfg(feature = "sys")] Self::Sys(f) => AsStoreRef::as_store_ref(f), - #[cfg(not(feature = "sys"))] + #[cfg(any( + feature = "wamr", + feature = "wasmi", + feature = "v8", + feature = "js", + feature = "jsc" + ))] _ => unsupported_async_backend(), } } @@ -379,7 +478,13 @@ impl BackendAsyncFunctionEnvHandleMut { match self { #[cfg(feature = "sys")] Self::Sys(f) => f.data_mut(), - #[cfg(not(feature = "sys"))] + #[cfg(any( + feature = "wamr", + feature = "wasmi", + feature = "v8", + feature = "js", + feature = "jsc" + ))] _ => unsupported_async_backend(), } } @@ -389,7 +494,27 @@ impl BackendAsyncFunctionEnvHandleMut { match self { #[cfg(feature = "sys")] Self::Sys(f) => f.data_and_store_mut(), - #[cfg(not(feature = "sys"))] + #[cfg(all( + feature = "sys", + any( + feature = "wamr", + feature = "wasmi", + feature = "v8", + feature = "js", + feature = "jsc" + ) + ))] + _ => unsupported_async_backend(), + #[cfg(all( + not(feature = "sys"), + any( + feature = "wamr", + feature = "wasmi", + feature = "v8", + feature = "js", + feature = "jsc" + ) + ))] _ => unsupported_async_backend::<(&mut T, &mut crate::StoreMut)>(), } } @@ -400,7 +525,13 @@ impl BackendAsyncFunctionEnvHandleMut { match self { #[cfg(feature = "sys")] Self::Sys(f) => BackendFunctionEnvMut::Sys(f.as_function_env_mut()), - #[cfg(not(feature = "sys"))] + #[cfg(any( + feature = "wamr", + feature = "wasmi", + feature = "v8", + feature = "js", + feature = "jsc" + ))] _ => unsupported_async_backend(), } } @@ -412,7 +543,13 @@ impl AsStoreRef for BackendAsyncFunctionEnvHandleMut { match self { #[cfg(feature = "sys")] Self::Sys(f) => AsStoreRef::as_store_ref(f), - #[cfg(not(feature = "sys"))] + #[cfg(any( + feature = "wamr", + feature = "wasmi", + feature = "v8", + feature = "js", + feature = "jsc" + ))] _ => unsupported_async_backend(), } } @@ -424,7 +561,13 @@ impl AsStoreMut for BackendAsyncFunctionEnvHandleMut { match self { #[cfg(feature = "sys")] Self::Sys(f) => AsStoreMut::as_store_mut(f), - #[cfg(not(feature = "sys"))] + #[cfg(any( + feature = "wamr", + feature = "wasmi", + feature = "v8", + feature = "js", + feature = "jsc" + ))] _ => unsupported_async_backend(), } } @@ -433,7 +576,13 @@ impl AsStoreMut for BackendAsyncFunctionEnvHandleMut { match self { #[cfg(feature = "sys")] Self::Sys(f) => AsStoreMut::objects_mut(f), - #[cfg(not(feature = "sys"))] + #[cfg(any( + feature = "wamr", + feature = "wasmi", + feature = "v8", + feature = "js", + feature = "jsc" + ))] _ => unsupported_async_backend(), } } From 753580b93553885e0771a51c293f9ac2e9abcd85 Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Sat, 6 Dec 2025 01:46:53 +0400 Subject: [PATCH 47/49] eliminate call_async_sys_internal --- .../src/backend/sys/entities/function/typed.rs | 18 +----------------- lib/api/src/utils/native/typed_func.rs | 2 +- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/lib/api/src/backend/sys/entities/function/typed.rs b/lib/api/src/backend/sys/entities/function/typed.rs index 23f1a054c1b..8616234ca92 100644 --- a/lib/api/src/backend/sys/entities/function/typed.rs +++ b/lib/api/src/backend/sys/entities/function/typed.rs @@ -117,27 +117,11 @@ macro_rules! impl_native_traits { // Ok(Rets::from_c_struct(results)) } - #[allow(unused_mut)] - #[allow(clippy::too_many_arguments)] - #[cfg(feature = "experimental-async")] - pub(crate) fn call_async_sys( - &self, - store: &impl AsStoreAsync, - $( $x: $x, )* - ) -> impl Future> + 'static - where - $( $x: FromToNativeWasmType + 'static, )* - { - let func = self.func.clone(); - let store = store.store(); - Self::call_async_sys_internal(func, store, $($x),*) - } - /// Call the typed func asynchronously. #[allow(unused_mut)] #[allow(clippy::too_many_arguments)] #[cfg(feature = "experimental-async")] - pub(crate) fn call_async_sys_internal( + pub(crate) fn call_async_sys( func: Function, store: StoreAsync, $( $x: $x, )* diff --git a/lib/api/src/utils/native/typed_func.rs b/lib/api/src/utils/native/typed_func.rs index 6368db56ca1..546858309e4 100644 --- a/lib/api/src/utils/native/typed_func.rs +++ b/lib/api/src/utils/native/typed_func.rs @@ -104,7 +104,7 @@ macro_rules! impl_native_traits { #[cfg(feature = "sys")] BackendStore::Sys(_) => { drop(read_lock); - Self::call_async_sys_internal(func, store, $([]),*).await + Self::call_async_sys(func, store, $([]),*).await } #[cfg(feature = "wamr")] BackendStore::Wamr(_) => async_backend_error(), From d3885791ee714909162121739d8546f77e38b481 Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Sat, 6 Dec 2025 01:49:16 +0400 Subject: [PATCH 48/49] Eliminate left-over 'static bounds on sync typed function args and results --- lib/api/src/entities/function/inner.rs | 8 ++++---- lib/api/src/entities/function/mod.rs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/api/src/entities/function/inner.rs b/lib/api/src/entities/function/inner.rs index b6108e17bf3..54bbf742dc6 100644 --- a/lib/api/src/entities/function/inner.rs +++ b/lib/api/src/entities/function/inner.rs @@ -148,8 +148,8 @@ impl BackendFunction { pub fn new_typed(store: &mut impl AsStoreMut, func: F) -> Self where F: HostFunction<(), Args, Rets, WithoutEnv> + 'static + Send + Sync, - Args: WasmTypeList + 'static, - Rets: WasmTypeList + 'static, + Args: WasmTypeList, + Rets: WasmTypeList, { match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] @@ -207,8 +207,8 @@ impl BackendFunction { ) -> Self where F: HostFunction + 'static + Send + Sync, - Args: WasmTypeList + 'static, - Rets: WasmTypeList + 'static, + Args: WasmTypeList, + Rets: WasmTypeList, { match &store.as_store_mut().inner.store { #[cfg(feature = "sys")] diff --git a/lib/api/src/entities/function/mod.rs b/lib/api/src/entities/function/mod.rs index c2c08ce0e41..32668df27a2 100644 --- a/lib/api/src/entities/function/mod.rs +++ b/lib/api/src/entities/function/mod.rs @@ -119,8 +119,8 @@ impl Function { pub fn new_typed(store: &mut impl AsStoreMut, func: F) -> Self where F: HostFunction<(), Args, Rets, WithoutEnv> + 'static + Send + Sync, - Args: WasmTypeList + 'static, - Rets: WasmTypeList + 'static, + Args: WasmTypeList, + Rets: WasmTypeList, { Self(BackendFunction::new_typed(store, func)) } @@ -150,8 +150,8 @@ impl Function { ) -> Self where F: HostFunction + 'static + Send + Sync, - Args: WasmTypeList + 'static, - Rets: WasmTypeList + 'static, + Args: WasmTypeList, + Rets: WasmTypeList, { Self(BackendFunction::new_typed_with_env(store, env, func)) } From 5d95f14b8a912644ac21d951d7339f9f4434fe86 Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Sat, 6 Dec 2025 01:57:32 +0400 Subject: [PATCH 49/49] Of *course* there are failing CI pipelines XD --- lib/c-api/src/wasm_c_api/externals/function.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/c-api/src/wasm_c_api/externals/function.rs b/lib/c-api/src/wasm_c_api/externals/function.rs index 19eeb971967..ed6c55d4830 100644 --- a/lib/c-api/src/wasm_c_api/externals/function.rs +++ b/lib/c-api/src/wasm_c_api/externals/function.rs @@ -8,7 +8,7 @@ use libc::c_void; use std::convert::TryInto; use std::mem::MaybeUninit; use std::sync::{Arc, Mutex}; -use wasmer_api::{Extern, Function, FunctionEnv, FunctionEnvMut, Value}; +use wasmer_api::{Extern, Function, FunctionEnv, FunctionEnvMut, RuntimeError, Value}; #[derive(Clone)] #[allow(non_camel_case_types)]