From 8b4bdf92e2593c1ee1ed05b0f64d05c9f5cb7300 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Tue, 8 Jun 2021 07:37:00 -0700 Subject: [PATCH] make ResourceLimiter operate on Store data; add hooks for entering and exiting native code (#2952) * wasmtime_runtime: move ResourceLimiter defaults into this crate In preparation of changing wasmtime::ResourceLimiter to be a re-export of this definition, because translating between two traits was causing problems elsewhere. * wasmtime: make ResourceLimiter a re-export of wasmtime_runtime::ResourceLimiter * refactor Store internals to support ResourceLimiter as part of store's data * add hooks for entering and exiting native code to Store * wasmtime-wast, fuzz: changes to adapt ResourceLimiter API * fix tests * wrap calls into wasm with entering/exiting exit hooks as well * the most trivial test found a bug, lets write some more * store: mark some methods as #[inline] on Store, StoreInner, StoreInnerMost Co-authored-By: Alex Crichton * improve tests for the entering/exiting native hooks Co-authored-by: Alex Crichton --- crates/fuzzing/src/oracles.rs | 13 +- crates/fuzzing/src/oracles/dummy.rs | 14 +- crates/runtime/src/instance.rs | 31 +++- crates/runtime/src/lib.rs | 3 +- crates/wasmtime/src/externals.rs | 8 +- crates/wasmtime/src/func.rs | 28 ++- crates/wasmtime/src/func/typed.rs | 20 ++- crates/wasmtime/src/limits.rs | 101 +---------- crates/wasmtime/src/memory.rs | 6 +- crates/wasmtime/src/store.rs | 232 +++++++++++++++++-------- crates/wasmtime/src/store/context.rs | 12 +- crates/wast/src/spectest.rs | 2 +- crates/wast/src/wast.rs | 10 +- tests/all/limits.rs | 94 +++++------ tests/all/main.rs | 1 + tests/all/module_linking.rs | 12 +- tests/all/native_hooks.rs | 244 +++++++++++++++++++++++++++ 17 files changed, 549 insertions(+), 282 deletions(-) create mode 100644 tests/all/native_hooks.rs diff --git a/crates/fuzzing/src/oracles.rs b/crates/fuzzing/src/oracles.rs index 943037253752..498380af3c9b 100644 --- a/crates/fuzzing/src/oracles.rs +++ b/crates/fuzzing/src/oracles.rs @@ -42,9 +42,9 @@ fn log_wasm(wasm: &[u8]) { } } -fn create_store(engine: &Engine) -> Store<()> { - let mut store = Store::new(&engine, ()); - store.limiter( +fn create_store(engine: &Engine) -> Store { + let mut store = Store::new( + &engine, StoreLimitsBuilder::new() // The limits here are chosen based on the default "maximum type size" // configured in wasm-smith, which is 1000. This means that instances @@ -55,6 +55,7 @@ fn create_store(engine: &Engine) -> Store<()> { .memories(1100) .build(), ); + store.limiter(|s| s as &mut dyn ResourceLimiter); store } @@ -268,7 +269,7 @@ pub fn differential_execution( } } - fn init_hang_limit(store: &mut Store<()>, instance: Instance) { + fn init_hang_limit(store: &mut Store, instance: Instance) { match instance.get_export(&mut *store, "hangLimitInitializer") { None => return, Some(Extern::Func(f)) => { @@ -337,7 +338,7 @@ pub fn make_api_calls(api: crate::generators::api::ApiCalls) { let mut config: Option = None; let mut engine: Option = None; - let mut store: Option> = None; + let mut store: Option> = None; let mut modules: HashMap = Default::default(); let mut instances: HashMap = Default::default(); @@ -501,7 +502,7 @@ pub fn table_ops( const MAX_GCS: usize = 5; let num_gcs = AtomicUsize::new(0); - let gc = Func::wrap(&mut store, move |mut caller: Caller<'_, ()>| { + let gc = Func::wrap(&mut store, move |mut caller: Caller<'_, StoreLimits>| { if num_gcs.fetch_add(1, SeqCst) < MAX_GCS { caller.gc(); } diff --git a/crates/fuzzing/src/oracles/dummy.rs b/crates/fuzzing/src/oracles/dummy.rs index 00be85d0d747..648b0f0ec064 100644 --- a/crates/fuzzing/src/oracles/dummy.rs +++ b/crates/fuzzing/src/oracles/dummy.rs @@ -4,7 +4,7 @@ use std::fmt::Write; use wasmtime::*; /// Create a set of dummy functions/globals/etc for the given imports. -pub fn dummy_linker<'module>(store: &mut Store<()>, module: &Module) -> Linker<()> { +pub fn dummy_linker<'module, T>(store: &mut Store, module: &Module) -> Linker { let mut linker = Linker::new(store.engine()); linker.allow_shadowing(true); for import in module.imports() { @@ -34,7 +34,7 @@ pub fn dummy_linker<'module>(store: &mut Store<()>, module: &Module) -> Linker<( } /// Construct a dummy `Extern` from its type signature -pub fn dummy_extern(store: &mut Store<()>, ty: ExternType) -> Extern { +pub fn dummy_extern(store: &mut Store, ty: ExternType) -> Extern { match ty { ExternType::Func(func_ty) => Extern::Func(dummy_func(store, func_ty)), ExternType::Global(global_ty) => Extern::Global(dummy_global(store, global_ty)), @@ -46,7 +46,7 @@ pub fn dummy_extern(store: &mut Store<()>, ty: ExternType) -> Extern { } /// Construct a dummy function for the given function type -pub fn dummy_func(store: &mut Store<()>, ty: FuncType) -> Func { +pub fn dummy_func(store: &mut Store, ty: FuncType) -> Func { Func::new(store, ty.clone(), move |_, _, results| { for (ret_ty, result) in ty.results().zip(results) { *result = dummy_value(ret_ty); @@ -74,19 +74,19 @@ pub fn dummy_values(val_tys: impl IntoIterator) -> Vec { } /// Construct a dummy global for the given global type. -pub fn dummy_global(store: &mut Store<()>, ty: GlobalType) -> Global { +pub fn dummy_global(store: &mut Store, ty: GlobalType) -> Global { let val = dummy_value(ty.content().clone()); Global::new(store, ty, val).unwrap() } /// Construct a dummy table for the given table type. -pub fn dummy_table(store: &mut Store<()>, ty: TableType) -> Table { +pub fn dummy_table(store: &mut Store, ty: TableType) -> Table { let init_val = dummy_value(ty.element().clone()); Table::new(store, ty, init_val).unwrap() } /// Construct a dummy memory for the given memory type. -pub fn dummy_memory(store: &mut Store<()>, ty: MemoryType) -> Memory { +pub fn dummy_memory(store: &mut Store, ty: MemoryType) -> Memory { Memory::new(store, ty).unwrap() } @@ -94,7 +94,7 @@ pub fn dummy_memory(store: &mut Store<()>, ty: MemoryType) -> Memory { /// /// This is done by using the expected type to generate a module on-the-fly /// which we the instantiate. -pub fn dummy_instance(store: &mut Store<()>, ty: InstanceType) -> Instance { +pub fn dummy_instance(store: &mut Store, ty: InstanceType) -> Instance { let mut wat = WatGenerator::new(); for ty in ty.exports() { wat.export(&ty); diff --git a/crates/runtime/src/instance.rs b/crates/runtime/src/instance.rs index 8c1ea559eadf..5474d430c7d5 100644 --- a/crates/runtime/src/instance.rs +++ b/crates/runtime/src/instance.rs @@ -34,11 +34,18 @@ mod allocator; pub use allocator::*; +/// Value returned by [`ResourceLimiter::instances`] default method +pub const DEFAULT_INSTANCE_LIMIT: usize = 10000; +/// Value returned by [`ResourceLimiter::tables`] default method +pub const DEFAULT_TABLE_LIMIT: usize = 10000; +/// Value returned by [`ResourceLimiter::memories`] default method +pub const DEFAULT_MEMORY_LIMIT: usize = 10000; + /// Used by hosts to limit resource consumption of instances. /// /// An instance can be created with a resource limiter so that hosts can take into account /// non-WebAssembly resource usage to determine if a linear memory or table should grow. -pub trait ResourceLimiter: Send + Sync + 'static { +pub trait ResourceLimiter { /// Notifies the resource limiter that an instance's linear memory has been requested to grow. /// /// * `current` is the current size of the linear memory in WebAssembly page units. @@ -67,17 +74,29 @@ pub trait ResourceLimiter: Send + Sync + 'static { /// The maximum number of instances that can be created for a `Store`. /// /// Module instantiation will fail if this limit is exceeded. - fn instances(&self) -> usize; + /// + /// This value defaults to 10,000. + fn instances(&self) -> usize { + DEFAULT_INSTANCE_LIMIT + } /// The maximum number of tables that can be created for a `Store`. /// /// Module instantiation will fail if this limit is exceeded. - fn tables(&self) -> usize; + /// + /// This value defaults to 10,000. + fn tables(&self) -> usize { + DEFAULT_TABLE_LIMIT + } - /// The maximum number of tables that can be created for a `Store`. + /// The maximum number of linear memories that can be created for a `Store` /// - /// Module instantiation will fail if this limit is exceeded. - fn memories(&self) -> usize; + /// Instantiation will fail with an error if this limit is exceeded. + /// + /// This value defaults to 10,000. + fn memories(&self) -> usize { + DEFAULT_MEMORY_LIMIT + } } /// A WebAssembly instance. diff --git a/crates/runtime/src/lib.rs b/crates/runtime/src/lib.rs index 2c7aa1f58490..cb98c61c0c59 100644 --- a/crates/runtime/src/lib.rs +++ b/crates/runtime/src/lib.rs @@ -42,7 +42,8 @@ pub use crate::imports::Imports; pub use crate::instance::{ InstanceAllocationRequest, InstanceAllocator, InstanceHandle, InstanceLimits, InstantiationError, LinkError, ModuleLimits, OnDemandInstanceAllocator, - PoolingAllocationStrategy, PoolingInstanceAllocator, ResourceLimiter, + PoolingAllocationStrategy, PoolingInstanceAllocator, ResourceLimiter, DEFAULT_INSTANCE_LIMIT, + DEFAULT_MEMORY_LIMIT, DEFAULT_TABLE_LIMIT, }; pub use crate::jit_int::GdbJitImageRegistration; pub use crate::memory::{Memory, RuntimeLinearMemory, RuntimeMemoryCreator}; diff --git a/crates/wasmtime/src/externals.rs b/crates/wasmtime/src/externals.rs index cdc32835e11a..f8d4ef0462c6 100644 --- a/crates/wasmtime/src/externals.rs +++ b/crates/wasmtime/src/externals.rs @@ -566,11 +566,11 @@ impl Table { /// Panics if `store` does not own this table. pub fn grow(&self, mut store: impl AsContextMut, delta: u32, init: Val) -> Result { let ty = self.ty(&store).element().clone(); - let mut store = store.as_context_mut().opaque(); - let init = init.into_table_element(&mut store, ty)?; - let table = self.wasmtime_table(&mut store); + let init = init.into_table_element(&mut store.as_context_mut().opaque(), ty)?; + let table = self.wasmtime_table(&mut store.as_context_mut().opaque()); + let store = store.as_context_mut(); unsafe { - match (*table).grow(delta, init, store.limiter()) { + match (*table).grow(delta, init, store.0.limiter()) { Some(size) => { let vm = (*table).vmtable(); *store[self.0].definition = vm; diff --git a/crates/wasmtime/src/func.rs b/crates/wasmtime/src/func.rs index e821e5affc5b..fb31d0863fb2 100644 --- a/crates/wasmtime/src/func.rs +++ b/crates/wasmtime/src/func.rs @@ -681,7 +681,10 @@ impl Func { "must use `call_async` when async support is enabled on the config", ); let my_ty = self.ty(&store); - self.call_impl(&mut store.as_context_mut().opaque(), my_ty, params) + store.as_context_mut().0.exiting_native_hook()?; + let r = self.call_impl(&mut store.as_context_mut().opaque(), my_ty, params); + store.as_context_mut().0.entering_native_hook()?; + r } /// Invokes this function with the `params` given, returning the results @@ -717,8 +720,12 @@ impl Func { T: Send, { let my_ty = self.ty(&store); - self._call_async(store.as_context_mut().opaque_send(), my_ty, params) - .await + store.as_context_mut().0.exiting_native_hook()?; + let r = self + ._call_async(store.as_context_mut().opaque_send(), my_ty, params) + .await; + store.as_context_mut().0.entering_native_hook()?; + r } #[cfg(feature = "async")] @@ -843,6 +850,7 @@ impl Func { values_vec: *mut u128, func: &dyn Fn(Caller<'_, T>, &[Val], &mut [Val]) -> Result<(), Trap>, ) -> Result<(), Trap> { + caller.store.0.entering_native_hook()?; // We have a dynamic guarantee that `values_vec` has the right // number of arguments and the right types of arguments. As a result // we should be able to safely run through them all and read them. @@ -883,6 +891,7 @@ impl Func { } } + caller.store.0.exiting_native_hook()?; Ok(()) } @@ -1173,7 +1182,7 @@ pub unsafe trait WasmRet { // explicitly, used when wrapping async functions which always bottom-out // in a function that returns a trap because futures can be cancelled. #[doc(hidden)] - type Fallible: WasmRet; + type Fallible: WasmRet; #[doc(hidden)] fn into_fallible(self) -> Self::Fallible; #[doc(hidden)] @@ -1689,12 +1698,19 @@ macro_rules! impl_into_func { let ret = { panic::catch_unwind(AssertUnwindSafe(|| { + if let Err(trap) = caller.store.0.entering_native_hook() { + return R::fallible_from_trap(trap); + } let mut _store = caller.sub_caller().store.opaque(); $(let $args = $args::from_abi($args, &mut _store);)* - func( + let r = func( caller.sub_caller(), $( $args, )* - ) + ); + if let Err(trap) = caller.store.0.exiting_native_hook() { + return R::fallible_from_trap(trap); + } + r.into_fallible() })) }; diff --git a/crates/wasmtime/src/func/typed.rs b/crates/wasmtime/src/func/typed.rs index a01565f1dcca..a51d5a379c8f 100644 --- a/crates/wasmtime/src/func/typed.rs +++ b/crates/wasmtime/src/func/typed.rs @@ -71,12 +71,15 @@ where /// This function will panic if it is called when the underlying [`Func`] is /// connected to an asynchronous store. pub fn call(&self, mut store: impl AsContextMut, params: Params) -> Result { - let mut store = store.as_context_mut().opaque(); + store.as_context_mut().0.exiting_native_hook()?; + let mut store_opaque = store.as_context_mut().opaque(); assert!( - !store.async_support(), + !store_opaque.async_support(), "must use `call_async` with async stores" ); - unsafe { self._call(&mut store, params) } + let r = unsafe { self._call(&mut store_opaque, params) }; + store.as_context_mut().0.entering_native_hook()?; + r } /// Invokes this WebAssembly function with the specified parameters. @@ -100,14 +103,17 @@ where where T: Send, { - let mut store = store.as_context_mut().opaque_send(); + store.as_context_mut().0.exiting_native_hook()?; + let mut store_opaque = store.as_context_mut().opaque_send(); assert!( - store.async_support(), + store_opaque.async_support(), "must use `call` with non-async stores" ); - store + let r = store_opaque .on_fiber(|store| unsafe { self._call(store, params) }) - .await? + .await?; + store.as_context_mut().0.entering_native_hook()?; + r } unsafe fn _call(&self, store: &mut StoreOpaque<'_>, params: Params) -> Result { diff --git a/crates/wasmtime/src/limits.rs b/crates/wasmtime/src/limits.rs index 132f6eed64a1..68ad2f9bf1e0 100644 --- a/crates/wasmtime/src/limits.rs +++ b/crates/wasmtime/src/limits.rs @@ -1,97 +1,4 @@ -pub(crate) const DEFAULT_INSTANCE_LIMIT: usize = 10000; -pub(crate) const DEFAULT_TABLE_LIMIT: usize = 10000; -pub(crate) const DEFAULT_MEMORY_LIMIT: usize = 10000; - -/// Used by hosts to limit resource consumption of instances at runtime. -/// -/// [`Store::limiter`](crate::Store::limiter) can be used -/// with a resource limiter to take into account non-WebAssembly resource -/// usage to determine if a linear memory or table should be grown. -pub trait ResourceLimiter: Send + Sync + 'static { - /// Notifies the resource limiter that an instance's linear memory has been requested to grow. - /// - /// * `current` is the current size of the linear memory in WebAssembly page units. - /// * `desired` is the desired size of the linear memory in WebAssembly page units. - /// * `maximum` is either the linear memory's maximum or a maximum from an instance allocator, - /// also in WebAssembly page units. A value of `None` indicates that the linear memory is - /// unbounded. - /// - /// This function should return `true` to indicate that the growing operation is permitted or - /// `false` if not permitted. - /// - /// Note that this function will be called even when the desired count exceeds the given maximum. - /// - /// Returning `true` when a maximum has been exceeded will have no effect as the linear memory - /// will not be grown. - fn memory_growing(&mut self, current: u32, desired: u32, maximum: Option) -> bool; - - /// Notifies the resource limiter that an instance's table has been requested to grow. - /// - /// * `current` is the current number of elements in the table. - /// * `desired` is the desired number of elements in the table. - /// * `maximum` is either the table's maximum or a maximum from an instance allocator, - /// A value of `None` indicates that the table is unbounded. - /// - /// This function should return `true` to indicate that the growing operation is permitted or - /// `false` if not permitted. - /// - /// Note that this function will be called even when the desired count exceeds the given maximum. - /// - /// Returning `true` when a maximum has been exceeded will have no effect as the table will - /// not be grown. - fn table_growing(&mut self, current: u32, desired: u32, maximum: Option) -> bool; - - /// The maximum number of instances that can be created for a [`Store`](crate::Store). - /// - /// Module instantiation will fail if this limit is exceeded. - /// - /// This value defaults to 10,000. - fn instances(&self) -> usize { - DEFAULT_INSTANCE_LIMIT - } - - /// The maximum number of tables that can be created for a [`Store`](crate::Store). - /// - /// Module instantiation will fail if this limit is exceeded. - /// - /// This value defaults to 10,000. - fn tables(&self) -> usize { - DEFAULT_TABLE_LIMIT - } - - /// The maximum number of linear memories that can be created for a [`Store`](crate::Store). - /// - /// Instantiation will fail with an error if this limit is exceeded. - /// - /// This value defaults to 10,000. - fn memories(&self) -> usize { - DEFAULT_MEMORY_LIMIT - } -} - -pub(crate) struct ResourceLimiterProxy(pub T); - -impl wasmtime_runtime::ResourceLimiter for ResourceLimiterProxy { - fn memory_growing(&mut self, current: u32, desired: u32, maximum: Option) -> bool { - self.0.memory_growing(current, desired, maximum) - } - - fn table_growing(&mut self, current: u32, desired: u32, maximum: Option) -> bool { - self.0.table_growing(current, desired, maximum) - } - - fn instances(&self) -> usize { - self.0.instances() - } - - fn tables(&self) -> usize { - self.0.tables() - } - - fn memories(&self) -> usize { - self.0.memories() - } -} +pub use wasmtime_runtime::ResourceLimiter; /// Used to build [`StoreLimits`]. pub struct StoreLimitsBuilder(StoreLimits); @@ -172,9 +79,9 @@ impl Default for StoreLimits { Self { memory_pages: None, table_elements: None, - instances: DEFAULT_INSTANCE_LIMIT, - tables: DEFAULT_TABLE_LIMIT, - memories: DEFAULT_MEMORY_LIMIT, + instances: wasmtime_runtime::DEFAULT_INSTANCE_LIMIT, + tables: wasmtime_runtime::DEFAULT_TABLE_LIMIT, + memories: wasmtime_runtime::DEFAULT_MEMORY_LIMIT, } } } diff --git a/crates/wasmtime/src/memory.rs b/crates/wasmtime/src/memory.rs index 7dca939737a8..4b94dad250f2 100644 --- a/crates/wasmtime/src/memory.rs +++ b/crates/wasmtime/src/memory.rs @@ -416,10 +416,10 @@ impl Memory { /// # } /// ``` pub fn grow(&self, mut store: impl AsContextMut, delta: u32) -> Result { - let mut store = store.as_context_mut().opaque(); - let mem = self.wasmtime_memory(&mut store); + let mem = self.wasmtime_memory(&mut store.as_context_mut().opaque()); + let store = store.as_context_mut(); unsafe { - match (*mem).grow(delta, store.limiter()) { + match (*mem).grow(delta, store.0.limiter()) { Some(size) => { let vm = (*mem).vmmemory(); *store[self.0].definition = vm; diff --git a/crates/wasmtime/src/store.rs b/crates/wasmtime/src/store.rs index 059247ac3c61..be906b0645b0 100644 --- a/crates/wasmtime/src/store.rs +++ b/crates/wasmtime/src/store.rs @@ -8,6 +8,7 @@ use std::fmt; use std::future::Future; use std::marker; use std::mem::ManuallyDrop; +use std::ops::{Deref, DerefMut}; use std::pin::Pin; use std::ptr; use std::sync::Arc; @@ -78,7 +79,7 @@ pub struct Store { inner: ManuallyDrop>>, } -pub struct StoreInner { +pub struct StoreInner { // This `StoreInner` structure has references to itself. These aren't // immediately evident, however, so we need to tell the compiler that it // contains self-references. This notably suppresses `noalias` annotations @@ -101,7 +102,30 @@ pub struct StoreInner { // least telling the compiler something about all the aliasing happening // within a `Store`. _marker: marker::PhantomPinned, + inner: StoreInnermost, + limiter: Option &mut (dyn crate::ResourceLimiter) + Send + Sync>>, + entering_native_hook: Option Result<(), crate::Trap> + Send + Sync>>, + exiting_native_hook: Option Result<(), crate::Trap> + Send + Sync>>, + // for comments about `ManuallyDrop`, see `Store::into_data` + data: ManuallyDrop, +} + +impl Deref for StoreInner { + type Target = StoreInnermost; + fn deref(&self) -> &Self::Target { + &self.inner + } +} +impl DerefMut for StoreInner { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} +// I apologize for the convoluted structure and the terrible naming of this struct. +// This exists so that most of wasmtime can be monomorphic on StoreInnermost, without +// having to care about the generic in StoreInner. +pub struct StoreInnermost { engine: Engine, interrupts: Arc, instances: Vec, @@ -109,10 +133,13 @@ pub struct StoreInner { externref_activations_table: VMExternRefActivationsTable, modules: ModuleRegistry, host_trampolines: HashMap, - // Numbers of resources instantiated in this store. + // Numbers of resources instantiated in this store, and their limits instance_count: usize, + instance_limit: usize, memory_count: usize, + memory_limit: usize, table_count: usize, + table_limit: usize, /// An adjustment to add to the fuel consumed value in `interrupts` above /// to get the true amount of fuel consumed. fuel_adj: i64, @@ -120,10 +147,7 @@ pub struct StoreInner { async_state: AsyncState, out_of_gas_behavior: OutOfGas, store_data: StoreData, - limiter: Option>, default_callee: InstanceHandle, - // for comments about `ManuallyDrop`, see `Store::into_data` - data: ManuallyDrop, } #[cfg(feature = "async")] @@ -192,26 +216,33 @@ impl Store { }; let mut inner = Box::new(StoreInner { _marker: marker::PhantomPinned, - engine: engine.clone(), - interrupts: Default::default(), - instances: Vec::new(), - signal_handler: None, - externref_activations_table: VMExternRefActivationsTable::new(), - modules: ModuleRegistry::default(), - host_trampolines: HashMap::default(), - instance_count: 0, - memory_count: 0, - table_count: 0, - fuel_adj: 0, - #[cfg(feature = "async")] - async_state: AsyncState { - current_suspend: UnsafeCell::new(ptr::null()), - current_poll_cx: UnsafeCell::new(ptr::null_mut()), + inner: StoreInnermost { + engine: engine.clone(), + interrupts: Default::default(), + instances: Vec::new(), + signal_handler: None, + externref_activations_table: VMExternRefActivationsTable::new(), + modules: ModuleRegistry::default(), + host_trampolines: HashMap::default(), + instance_count: 0, + instance_limit: wasmtime_runtime::DEFAULT_INSTANCE_LIMIT, + memory_count: 0, + memory_limit: wasmtime_runtime::DEFAULT_MEMORY_LIMIT, + table_count: 0, + table_limit: wasmtime_runtime::DEFAULT_TABLE_LIMIT, + fuel_adj: 0, + #[cfg(feature = "async")] + async_state: AsyncState { + current_suspend: UnsafeCell::new(ptr::null()), + current_poll_cx: UnsafeCell::new(ptr::null_mut()), + }, + out_of_gas_behavior: OutOfGas::Trap, + store_data: StoreData::new(), + default_callee, }, - out_of_gas_behavior: OutOfGas::Trap, - store_data: StoreData::new(), limiter: None, - default_callee, + entering_native_hook: None, + exiting_native_hook: None, data: ManuallyDrop::new(data), }); @@ -227,11 +258,13 @@ impl Store { } /// Access the underlying data owned by this `Store`. + #[inline] pub fn data(&self) -> &T { self.inner.data() } /// Access the underlying data owned by this `Store`. + #[inline] pub fn data_mut(&mut self) -> &mut T { self.inner.data_mut() } @@ -275,8 +308,45 @@ impl Store { /// Note that this limiter is only used to limit the creation/growth of /// resources in the future, this does not retroactively attempt to apply /// limits to the [`Store`]. - pub fn limiter(&mut self, limiter: impl crate::ResourceLimiter) { - self.inner.limiter = Some(Box::new(crate::limits::ResourceLimiterProxy(limiter))); + pub fn limiter( + &mut self, + mut limiter: impl FnMut(&mut T) -> &mut (dyn crate::ResourceLimiter) + Send + Sync + 'static, + ) { + // Apply the limits on instances, tables, and memory given by the limiter: + let inner = &mut self.inner; + let (instance_limit, table_limit, memory_limit) = { + let l = limiter(&mut inner.data); + (l.instances(), l.tables(), l.memories()) + }; + let innermost = &mut inner.inner; + innermost.instance_limit = instance_limit; + innermost.table_limit = table_limit; + innermost.memory_limit = memory_limit; + + // Save the limiter accessor function: + inner.limiter = Some(Box::new(limiter)); + } + + /// Configure a function that runs each time WebAssembly code running on this [`Store`] calls + /// into native code. + /// + /// This function may return a [`Trap`], which terminates execution. + pub fn entering_native_code_hook( + &mut self, + hook: impl FnMut(&mut T) -> Result<(), Trap> + Send + Sync + 'static, + ) { + self.inner.entering_native_hook = Some(Box::new(hook)); + } + + /// Configure a function that runs before native code running on this [`Store`] returns to + /// WebAssembly code. + /// + /// This function may return a [`Trap`], which terminates execution. + pub fn exiting_native_code_hook( + &mut self, + hook: impl FnMut(&mut T) -> Result<(), Trap> + Send + Sync + 'static, + ) { + self.inner.exiting_native_hook = Some(Box::new(hook)); } /// Returns the [`Engine`] that this store is associated with. @@ -565,27 +635,79 @@ impl<'a, T> StoreContextMut<'a, T> { } } -impl StoreInner { +impl StoreInner { + #[inline] fn data(&self) -> &T { &self.data } + #[inline] fn data_mut(&mut self) -> &mut T { &mut self.data } + pub fn limiter(&mut self) -> Option<&mut dyn crate::limits::ResourceLimiter> { + let accessor = self.limiter.as_mut()?; + Some(accessor(&mut self.data)) + } + + pub fn entering_native_hook(&mut self) -> Result<(), Trap> { + if let Some(hook) = &mut self.entering_native_hook { + hook(&mut self.data) + } else { + Ok(()) + } + } + + pub fn exiting_native_hook(&mut self) -> Result<(), Trap> { + if let Some(hook) = &mut self.exiting_native_hook { + hook(&mut self.data) + } else { + Ok(()) + } + } +} + +impl StoreInnermost { + pub fn bump_resource_counts(&mut self, module: &Module) -> Result<()> { + fn bump(slot: &mut usize, max: usize, amt: usize, desc: &str) -> Result<()> { + let new = slot.saturating_add(amt); + if new > max { + bail!( + "resource limit exceeded: {} count too high at {}", + desc, + new + ); + } + *slot = new; + Ok(()) + } + + let module = module.env_module(); + let memories = module.memory_plans.len() - module.num_imported_memories; + let tables = module.table_plans.len() - module.num_imported_tables; + + bump(&mut self.instance_count, self.instance_limit, 1, "instance")?; + bump( + &mut self.memory_count, + self.memory_limit, + memories, + "memory", + )?; + bump(&mut self.table_count, self.table_limit, tables, "table")?; + + Ok(()) + } + #[inline] pub fn async_support(&self) -> bool { cfg!(feature = "async") && self.engine().config().async_support } + #[inline] pub fn engine(&self) -> &Engine { &self.engine } - pub fn limiter(&mut self) -> Option<&mut dyn wasmtime_runtime::ResourceLimiter> { - self.limiter.as_mut().map(|l| &mut **l) - } - pub fn store_data(&self) -> &StoreData { &self.store_data } @@ -654,43 +776,6 @@ impl StoreInner { unsafe { wasmtime_runtime::gc(&self.modules, &mut self.externref_activations_table) } } - pub fn bump_resource_counts(&mut self, module: &Module) -> Result<()> { - fn bump(slot: &mut usize, max: usize, amt: usize, desc: &str) -> Result<()> { - let new = slot.saturating_add(amt); - if new > max { - bail!( - "resource limit exceeded: {} count too high at {}", - desc, - new - ); - } - *slot = new; - Ok(()) - } - - let module = module.env_module(); - let memories = module.memory_plans.len() - module.num_imported_memories; - let tables = module.table_plans.len() - module.num_imported_tables; - let (max_instances, max_memories, max_tables) = self.limits(); - - bump(&mut self.instance_count, max_instances, 1, "instance")?; - bump(&mut self.memory_count, max_memories, memories, "memory")?; - bump(&mut self.table_count, max_tables, tables, "table")?; - - Ok(()) - } - - fn limits(&self) -> (usize, usize, usize) { - self.limiter - .as_ref() - .map(|l| (l.instances(), l.memories(), l.tables())) - .unwrap_or(( - crate::limits::DEFAULT_INSTANCE_LIMIT, - crate::limits::DEFAULT_MEMORY_LIMIT, - crate::limits::DEFAULT_TABLE_LIMIT, - )) - } - pub fn lookup_trampoline(&self, anyfunc: &VMCallerCheckedAnyfunc) -> VMTrampoline { // Look up the trampoline with the store's trampolines (from `Func`). if let Some(trampoline) = self.host_trampolines.get(&anyfunc.type_index) { @@ -706,6 +791,7 @@ impl StoreInner { } #[cfg(feature = "async")] + #[inline] pub fn async_cx(&self) -> AsyncCx { debug_assert!(self.async_support()); AsyncCx { @@ -817,11 +903,13 @@ impl StoreInner { Ok(()) } + #[inline] pub fn signal_handler(&self) -> Option<*const SignalHandler<'static>> { let handler = self.signal_handler.as_ref()?; Some(&**handler as *const _) } + #[inline] pub fn vminterrupts(&self) -> *mut VMInterrupts { &*self.interrupts as *const VMInterrupts as *mut VMInterrupts } @@ -831,6 +919,7 @@ impl StoreInner { .insert_with_gc(r, &self.modules) } + #[inline] pub fn default_callee(&self) -> *mut VMContext { self.default_callee.vmctx_ptr() } @@ -1121,7 +1210,7 @@ impl AsyncCx { unsafe impl wasmtime_runtime::Store for StoreInner { fn vminterrupts(&self) -> *mut VMInterrupts { - >::vminterrupts(self) + ::vminterrupts(self) } fn externref_activations_table( @@ -1130,11 +1219,12 @@ unsafe impl wasmtime_runtime::Store for StoreInner { &mut VMExternRefActivationsTable, &dyn wasmtime_runtime::ModuleInfoLookup, ) { - (&mut self.externref_activations_table, &self.modules) + let inner = &mut self.inner; + (&mut inner.externref_activations_table, &inner.modules) } fn limiter(&mut self) -> Option<&mut dyn wasmtime_runtime::ResourceLimiter> { - self.limiter.as_mut().map(|l| &mut **l) + ::limiter(self) } fn out_of_gas(&mut self) -> Result<(), Box> { @@ -1196,7 +1286,7 @@ impl Drop for Store { } } -impl Drop for StoreInner { +impl Drop for StoreInnermost { fn drop(&mut self) { // NB it's important that this destructor does not access `self.data`. // That is deallocated by `Drop for Store` above. diff --git a/crates/wasmtime/src/store/context.rs b/crates/wasmtime/src/store/context.rs index b46a7072a773..94a81619ea97 100644 --- a/crates/wasmtime/src/store/context.rs +++ b/crates/wasmtime/src/store/context.rs @@ -1,4 +1,4 @@ -use crate::store::{Store, StoreInner}; +use crate::store::{Store, StoreInner, StoreInnermost}; use std::ops::{Deref, DerefMut}; /// A temporary handle to a [`&Store`][`Store`]. @@ -17,7 +17,7 @@ pub struct StoreContext<'a, T>(pub(super) &'a StoreInner); /// methods if desired. For more information, see [`Store`]. // NB the repr(transparent) here is for the same reason as above. #[repr(transparent)] -pub struct StoreContextMut<'a, T>(pub(super) &'a mut StoreInner); +pub struct StoreContextMut<'a, T>(pub(crate) &'a mut StoreInner); impl<'a, T> StoreContextMut<'a, T> { /// One of the unsafe lynchpins of Wasmtime. @@ -210,7 +210,7 @@ impl<'a, T: AsContextMut> From<&'a mut T> for StoreContextMut<'a, T::Data> { #[doc(hidden)] // this is part of `WasmTy`, but a hidden part, so hide this pub struct StoreOpaque<'a> { /// The actual pointer to the `StoreInner` internals. - inner: &'a mut StoreInner, + inner: &'a mut StoreInnermost, /// A raw trait object that can be used to invoke functions with. Note that /// this is a pointer which aliases with `inner` above, so extreme care @@ -224,7 +224,7 @@ impl Opaque for T {} // Deref impls to forward all methods on `StoreOpaque` to `StoreInner`. impl<'a> Deref for StoreOpaque<'a> { - type Target = StoreInner; + type Target = StoreInnermost; #[inline] fn deref(&self) -> &Self::Target { @@ -241,7 +241,7 @@ impl<'a> DerefMut for StoreOpaque<'a> { pub struct StoreOpaqueSend<'a> { /// The actual pointer to the `StoreInner` internals. - inner: &'a mut StoreInner, + inner: &'a mut StoreInnermost, pub traitobj: *mut dyn wasmtime_runtime::Store, } @@ -259,7 +259,7 @@ impl StoreOpaqueSend<'_> { } impl<'a> Deref for StoreOpaqueSend<'a> { - type Target = StoreInner; + type Target = StoreInnermost; #[inline] fn deref(&self) -> &Self::Target { diff --git a/crates/wast/src/spectest.rs b/crates/wast/src/spectest.rs index 62ca446dd3d4..e4b5b24d34b6 100644 --- a/crates/wast/src/spectest.rs +++ b/crates/wast/src/spectest.rs @@ -3,7 +3,7 @@ use wasmtime::*; /// Return an instance implementing the "spectest" interface used in the /// spec testsuite. -pub fn link_spectest(linker: &mut Linker<()>, store: &mut Store<()>) -> Result<()> { +pub fn link_spectest(linker: &mut Linker, store: &mut Store) -> Result<()> { linker.func_wrap("spectest", "print", || {})?; linker.func_wrap("spectest", "print_i32", |val: i32| println!("{}: i32", val))?; linker.func_wrap("spectest", "print_i64", |val: i64| println!("{}: i64", val))?; diff --git a/crates/wast/src/wast.rs b/crates/wast/src/wast.rs index 32e95e1aaf95..c0f39b7eca97 100644 --- a/crates/wast/src/wast.rs +++ b/crates/wast/src/wast.rs @@ -32,12 +32,12 @@ fn runtime_value(v: &wast::Expression<'_>) -> Result { /// The wast test script language allows modules to be defined and actions /// to be performed on them. -pub struct WastContext { +pub struct WastContext { /// Wast files have a concept of a "current" module, which is the most /// recently defined. current: Option, - linker: Linker<()>, - store: Store<()>, + linker: Linker, + store: Store, } enum Outcome> { @@ -54,9 +54,9 @@ impl Outcome { } } -impl WastContext { +impl WastContext { /// Construct a new instance of `WastContext`. - pub fn new(store: Store<()>) -> Self { + pub fn new(store: Store) -> Self { // Spec tests will redefine the same module/name sometimes, so we need // to allow shadowing in the linker which picks the most recent // definition as what to link when linking. diff --git a/tests/all/limits.rs b/tests/all/limits.rs index 74a90916feff..b4a0fda2a3d0 100644 --- a/tests/all/limits.rs +++ b/tests/all/limits.rs @@ -1,6 +1,4 @@ use anyhow::Result; -use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst}; -use std::sync::Arc; use wasmtime::*; #[test] @@ -11,13 +9,14 @@ fn test_limits() -> Result<()> { r#"(module (memory (export "m") 0) (table (export "t") 0 anyfunc))"#, )?; - let mut store = Store::new(&engine, ()); - store.limiter( + let mut store = Store::new( + &engine, StoreLimitsBuilder::new() .memory_pages(10) .table_elements(5) .build(), ); + store.limiter(|s| s as &mut dyn ResourceLimiter); let instance = Instance::new(&mut store, &module, &[])?; @@ -72,8 +71,8 @@ fn test_limits_memory_only() -> Result<()> { r#"(module (memory (export "m") 0) (table (export "t") 0 anyfunc))"#, )?; - let mut store = Store::new(&engine, ()); - store.limiter(StoreLimitsBuilder::new().memory_pages(10).build()); + let mut store = Store::new(&engine, StoreLimitsBuilder::new().memory_pages(10).build()); + store.limiter(|s| s as &mut dyn ResourceLimiter); let instance = Instance::new(&mut store, &module, &[])?; @@ -118,8 +117,8 @@ fn test_initial_memory_limits_exceeded() -> Result<()> { let engine = Engine::default(); let module = Module::new(&engine, r#"(module (memory (export "m") 11))"#)?; - let mut store = Store::new(&engine, ()); - store.limiter(StoreLimitsBuilder::new().memory_pages(10).build()); + let mut store = Store::new(&engine, StoreLimitsBuilder::new().memory_pages(10).build()); + store.limiter(|s| s as &mut dyn ResourceLimiter); match Instance::new(&mut store, &module, &[]) { Ok(_) => unreachable!(), @@ -148,8 +147,8 @@ fn test_limits_table_only() -> Result<()> { r#"(module (memory (export "m") 0) (table (export "t") 0 anyfunc))"#, )?; - let mut store = Store::new(&engine, ()); - store.limiter(StoreLimitsBuilder::new().table_elements(5).build()); + let mut store = Store::new(&engine, StoreLimitsBuilder::new().table_elements(5).build()); + store.limiter(|s| s as &mut dyn ResourceLimiter); let instance = Instance::new(&mut store, &module, &[])?; @@ -194,8 +193,8 @@ fn test_initial_table_limits_exceeded() -> Result<()> { let engine = Engine::default(); let module = Module::new(&engine, r#"(module (table (export "t") 23 anyfunc))"#)?; - let mut store = Store::new(&engine, ()); - store.limiter(StoreLimitsBuilder::new().table_elements(4).build()); + let mut store = Store::new(&engine, StoreLimitsBuilder::new().table_elements(4).build()); + store.limiter(|s| s as &mut dyn ResourceLimiter); match Instance::new(&mut store, &module, &[]) { Ok(_) => unreachable!(), @@ -242,8 +241,8 @@ fn test_pooling_allocator_initial_limits_exceeded() -> Result<()> { r#"(module (memory (export "m1") 2) (memory (export "m2") 5))"#, )?; - let mut store = Store::new(&engine, ()); - store.limiter(StoreLimitsBuilder::new().memory_pages(3).build()); + let mut store = Store::new(&engine, StoreLimitsBuilder::new().memory_pages(3).build()); + store.limiter(|s| s as &mut dyn ResourceLimiter); match Instance::new(&mut store, &module, &[]) { Ok(_) => unreachable!(), @@ -262,35 +261,29 @@ fn test_pooling_allocator_initial_limits_exceeded() -> Result<()> { } struct MemoryContext { - host_memory_used: AtomicUsize, - wasm_memory_used: AtomicUsize, + host_memory_used: usize, + wasm_memory_used: usize, memory_limit: usize, - limit_exceeded: AtomicBool, - limiter_dropped: AtomicBool, + limit_exceeded: bool, } -struct HostMemoryLimiter(Arc); - -impl ResourceLimiter for HostMemoryLimiter { +impl ResourceLimiter for MemoryContext { fn memory_growing(&mut self, current: u32, desired: u32, maximum: Option) -> bool { // Check if the desired exceeds a maximum (either from Wasm or from the host) if desired > maximum.unwrap_or(u32::MAX) { - self.0.limit_exceeded.store(true, SeqCst); + self.limit_exceeded = true; return false; } - assert_eq!( - current as usize * 0x10000, - self.0.wasm_memory_used.load(SeqCst) - ); + assert_eq!(current as usize * 0x10000, self.wasm_memory_used,); let desired = desired as usize * 0x10000; - if desired + self.0.host_memory_used.load(SeqCst) > self.0.memory_limit { - self.0.limit_exceeded.store(true, SeqCst); + if desired + self.host_memory_used > self.memory_limit { + self.limit_exceeded = true; return false; } - self.0.wasm_memory_used.store(desired, SeqCst); + self.wasm_memory_used = desired; true } @@ -299,12 +292,6 @@ impl ResourceLimiter for HostMemoryLimiter { } } -impl Drop for HostMemoryLimiter { - fn drop(&mut self) { - self.0.limiter_dropped.store(true, SeqCst); - } -} - #[test] fn test_custom_limiter() -> Result<()> { let engine = Engine::default(); @@ -315,18 +302,16 @@ fn test_custom_limiter() -> Result<()> { linker.func_wrap( "", "alloc", - |caller: Caller<'_, Arc>, size: u32| -> u32 { - let ctx = caller.data(); + |mut caller: Caller<'_, MemoryContext>, size: u32| -> u32 { + let mut ctx = caller.data_mut(); let size = size as usize; - if size + ctx.host_memory_used.load(SeqCst) + ctx.wasm_memory_used.load(SeqCst) - <= ctx.memory_limit - { - ctx.host_memory_used.fetch_add(size, SeqCst); + if size + ctx.host_memory_used + ctx.wasm_memory_used <= ctx.memory_limit { + ctx.host_memory_used += size; return 1; } - ctx.limit_exceeded.store(true, SeqCst); + ctx.limit_exceeded = true; 0 }, @@ -337,16 +322,15 @@ fn test_custom_limiter() -> Result<()> { r#"(module (import "" "alloc" (func $alloc (param i32) (result i32))) (memory (export "m") 0) (func (export "f") (param i32) (result i32) local.get 0 call $alloc))"#, )?; - let context = Arc::new(MemoryContext { - host_memory_used: AtomicUsize::new(0), - wasm_memory_used: AtomicUsize::new(0), + let context = MemoryContext { + host_memory_used: 0, + wasm_memory_used: 0, memory_limit: 1 << 20, // 16 wasm pages is the limit for both wasm + host memory - limit_exceeded: AtomicBool::new(false), - limiter_dropped: AtomicBool::new(false), - }); + limit_exceeded: false, + }; - let mut store = Store::new(&engine, context.clone()); - store.limiter(HostMemoryLimiter(context.clone())); + let mut store = Store::new(&engine, context); + store.limiter(|s| s as &mut dyn ResourceLimiter); let instance = linker.instantiate(&mut store, &module)?; let memory = instance.get_memory(&mut store, "m").unwrap(); @@ -355,7 +339,7 @@ fn test_custom_limiter() -> Result<()> { memory.grow(&mut store, 5)?; memory.grow(&mut store, 2)?; - assert!(!context.limit_exceeded.load(SeqCst)); + assert!(!store.data().limit_exceeded); // Grow the host "memory" by 384 KiB let f = instance.get_typed_func::(&mut store, "f")?; @@ -365,7 +349,7 @@ fn test_custom_limiter() -> Result<()> { assert_eq!(f.call(&mut store, 2 * 0x10000)?, 1); // Memory is at the maximum, but the limit hasn't been exceeded - assert!(!context.limit_exceeded.load(SeqCst)); + assert!(!store.data().limit_exceeded); // Try to grow the memory again assert_eq!( @@ -376,16 +360,14 @@ fn test_custom_limiter() -> Result<()> { "failed to grow memory by `1`" ); - assert!(context.limit_exceeded.load(SeqCst)); + assert!(store.data().limit_exceeded); // Try to grow the host "memory" again assert_eq!(f.call(&mut store, 1)?, 0); - assert!(context.limit_exceeded.load(SeqCst)); + assert!(store.data().limit_exceeded); drop(store); - assert!(context.limiter_dropped.load(SeqCst)); - Ok(()) } diff --git a/tests/all/main.rs b/tests/all/main.rs index 37bb68d7a692..6cb120792343 100644 --- a/tests/all/main.rs +++ b/tests/all/main.rs @@ -20,6 +20,7 @@ mod module; mod module_linking; mod module_serialize; mod name; +mod native_hooks; mod pooling_allocator; mod stack_overflow; mod store; diff --git a/tests/all/module_linking.rs b/tests/all/module_linking.rs index 498ed62b0409..15635ae534e4 100644 --- a/tests/all/module_linking.rs +++ b/tests/all/module_linking.rs @@ -217,8 +217,8 @@ fn limit_instances() -> Result<()> { ) "#, )?; - let mut store = Store::new(&engine, ()); - store.limiter(StoreLimitsBuilder::new().instances(10).build()); + let mut store = Store::new(&engine, StoreLimitsBuilder::new().instances(10).build()); + store.limiter(|s| s as &mut dyn ResourceLimiter); let err = Instance::new(&mut store, &module, &[]).err().unwrap(); assert!( err.to_string().contains("resource limit exceeded"), @@ -253,8 +253,8 @@ fn limit_memories() -> Result<()> { ) "#, )?; - let mut store = Store::new(&engine, ()); - store.limiter(StoreLimitsBuilder::new().memories(10).build()); + let mut store = Store::new(&engine, StoreLimitsBuilder::new().memories(10).build()); + store.limiter(|s| s as &mut dyn ResourceLimiter); let err = Instance::new(&mut store, &module, &[]).err().unwrap(); assert!( err.to_string().contains("resource limit exceeded"), @@ -288,8 +288,8 @@ fn limit_tables() -> Result<()> { ) "#, )?; - let mut store = Store::new(&engine, ()); - store.limiter(StoreLimitsBuilder::new().tables(10).build()); + let mut store = Store::new(&engine, StoreLimitsBuilder::new().tables(10).build()); + store.limiter(|s| s as &mut dyn ResourceLimiter); let err = Instance::new(&mut store, &module, &[]).err().unwrap(); assert!( err.to_string().contains("resource limit exceeded"), diff --git a/tests/all/native_hooks.rs b/tests/all/native_hooks.rs new file mode 100644 index 000000000000..2a2288550b83 --- /dev/null +++ b/tests/all/native_hooks.rs @@ -0,0 +1,244 @@ +use anyhow::Error; +use wasmtime::*; + +// Crate a synchronous Func, call it directly: +#[test] +fn call_wrapped_func() -> Result<(), Error> { + let mut store = Store::::default(); + store.entering_native_code_hook(State::entering_native); + store.exiting_native_code_hook(State::exiting_native); + let f = Func::wrap( + &mut store, + |caller: Caller, a: i32, b: i64, c: f32, d: f64| { + assert_eq!( + caller.data().switches_into_native % 2, + 1, + "odd number of switches into native while in a Func" + ); + assert_eq!(a, 1); + assert_eq!(b, 2); + assert_eq!(c, 3.0); + assert_eq!(d, 4.0); + }, + ); + + f.call( + &mut store, + &[Val::I32(1), Val::I64(2), 3.0f32.into(), 4.0f64.into()], + )?; + + // One switch from vm to native to call f, another in return from f. + assert_eq!(store.data().switches_into_native, 2); + + f.typed::<(i32, i64, f32, f64), (), _>(&store)? + .call(&mut store, (1, 2, 3.0, 4.0))?; + + assert_eq!(store.data().switches_into_native, 4); + + Ok(()) +} + +// Create an async Func, call it directly: +#[tokio::test] +async fn call_wrapped_async_func() -> Result<(), Error> { + let mut config = Config::new(); + config.async_support(true); + let engine = Engine::new(&config)?; + let mut store = Store::new(&engine, State::default()); + store.entering_native_code_hook(State::entering_native); + store.exiting_native_code_hook(State::exiting_native); + let f = Func::wrap4_async( + &mut store, + |caller: Caller, a: i32, b: i64, c: f32, d: f64| { + Box::new(async move { + assert_eq!( + caller.data().switches_into_native % 2, + 1, + "odd number of switches into native while in a Func" + ); + assert_eq!(a, 1); + assert_eq!(b, 2); + assert_eq!(c, 3.0); + assert_eq!(d, 4.0); + }) + }, + ); + + f.call_async( + &mut store, + &[Val::I32(1), Val::I64(2), 3.0f32.into(), 4.0f64.into()], + ) + .await?; + + // One switch from vm to native to call f, another in return from f. + assert_eq!(store.data().switches_into_native, 2); + + f.typed::<(i32, i64, f32, f64), (), _>(&store)? + .call_async(&mut store, (1, 2, 3.0, 4.0)) + .await?; + + assert_eq!(store.data().switches_into_native, 4); + + Ok(()) +} + +// Use the Linker to define a sync func, call it through WebAssembly: +#[test] +fn call_linked_func() -> Result<(), Error> { + let engine = Engine::default(); + let mut store = Store::new(&engine, State::default()); + store.entering_native_code_hook(State::entering_native); + store.exiting_native_code_hook(State::exiting_native); + let mut linker = Linker::new(&engine); + + linker.func_wrap( + "host", + "f", + |caller: Caller, a: i32, b: i64, c: f32, d: f64| { + assert_eq!( + caller.data().switches_into_native % 2, + 1, + "odd number of switches into native while in a Func" + ); + assert_eq!(a, 1); + assert_eq!(b, 2); + assert_eq!(c, 3.0); + assert_eq!(d, 4.0); + }, + )?; + + let wat = r#" + (module + (import "host" "f" + (func $f (param i32) (param i64) (param f32) (param f64))) + (func (export "export") + (call $f (i32.const 1) (i64.const 2) (f32.const 3.0) (f64.const 4.0))) + ) + "#; + let module = Module::new(&engine, wat)?; + + let inst = linker.instantiate(&mut store, &module)?; + let export = inst + .get_export(&mut store, "export") + .expect("get export") + .into_func() + .expect("export is func"); + + export.call(&mut store, &[])?; + + // One switch from vm to native to call f, another in return from f. + assert_eq!(store.data().switches_into_native, 2); + + export.typed::<(), (), _>(&store)?.call(&mut store, ())?; + + assert_eq!(store.data().switches_into_native, 4); + + Ok(()) +} + +// Use the Linker to define an async func, call it through WebAssembly: +#[tokio::test] +async fn call_linked_func_async() -> Result<(), Error> { + let mut config = Config::new(); + config.async_support(true); + let engine = Engine::new(&config)?; + let mut store = Store::new(&engine, State::default()); + store.entering_native_code_hook(State::entering_native); + store.exiting_native_code_hook(State::exiting_native); + + let f = Func::wrap4_async( + &mut store, + |caller: Caller, a: i32, b: i64, c: f32, d: f64| { + Box::new(async move { + assert_eq!( + caller.data().switches_into_native % 2, + 1, + "odd number of switches into native while in a Func" + ); + assert_eq!(a, 1); + assert_eq!(b, 2); + assert_eq!(c, 3.0); + assert_eq!(d, 4.0); + }) + }, + ); + + let mut linker = Linker::new(&engine); + + linker.define("host", "f", f)?; + + let wat = r#" + (module + (import "host" "f" + (func $f (param i32) (param i64) (param f32) (param f64))) + (func (export "export") + (call $f (i32.const 1) (i64.const 2) (f32.const 3.0) (f64.const 4.0))) + ) + "#; + let module = Module::new(&engine, wat)?; + + let inst = linker.instantiate_async(&mut store, &module).await?; + let export = inst + .get_export(&mut store, "export") + .expect("get export") + .into_func() + .expect("export is func"); + + export.call_async(&mut store, &[]).await?; + + // One switch from vm to native to call f, another in return from export. + assert_eq!(store.data().switches_into_native, 2); + + export + .typed::<(), (), _>(&store)? + .call_async(&mut store, ()) + .await?; + + // 2 more switches. + assert_eq!(store.data().switches_into_native, 4); + + Ok(()) +} + +enum Context { + Native, + Vm, +} + +struct State { + context: Context, + switches_into_native: usize, +} + +impl Default for State { + fn default() -> Self { + State { + context: Context::Native, + switches_into_native: 0, + } + } +} + +impl State { + fn entering_native(&mut self) -> Result<(), Trap> { + match self.context { + Context::Vm => { + println!("entering native"); + self.context = Context::Native; + self.switches_into_native += 1; + Ok(()) + } + Context::Native => Err(Trap::new("illegal state: exiting vm when in native")), + } + } + fn exiting_native(&mut self) -> Result<(), Trap> { + match self.context { + Context::Native => { + println!("entering vm"); + self.context = Context::Vm; + Ok(()) + } + Context::Vm => Err(Trap::new("illegal state: exiting native when in vm")), + } + } +}