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 <alex@alexcrichton.com> * improve tests for the entering/exiting native hooks Co-authored-by: Alex Crichton <alex@alexcrichton.com>
This commit is contained in:
@@ -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<StoreLimits> {
|
||||
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<T>(store: &mut Store<T>, 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<Config> = None;
|
||||
let mut engine: Option<Engine> = None;
|
||||
let mut store: Option<Store<()>> = None;
|
||||
let mut store: Option<Store<StoreLimits>> = None;
|
||||
let mut modules: HashMap<usize, Module> = Default::default();
|
||||
let mut instances: HashMap<usize, Instance> = 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();
|
||||
}
|
||||
|
||||
@@ -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<T>, module: &Module) -> Linker<T> {
|
||||
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<T>(store: &mut Store<T>, 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<T>(store: &mut Store<T>, 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<Item = ValType>) -> Vec<Val> {
|
||||
}
|
||||
|
||||
/// Construct a dummy global for the given global type.
|
||||
pub fn dummy_global(store: &mut Store<()>, ty: GlobalType) -> Global {
|
||||
pub fn dummy_global<T>(store: &mut Store<T>, 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<T>(store: &mut Store<T>, 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<T>(store: &mut Store<T>, 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<T>(store: &mut Store<T>, ty: InstanceType) -> Instance {
|
||||
let mut wat = WatGenerator::new();
|
||||
for ty in ty.exports() {
|
||||
wat.export(&ty);
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// The maximum number of tables that can be created for a `Store`.
|
||||
///
|
||||
/// Module instantiation will fail if this limit is exceeded.
|
||||
fn memories(&self) -> usize;
|
||||
/// 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`
|
||||
///
|
||||
/// 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.
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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<u32> {
|
||||
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;
|
||||
|
||||
@@ -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<Abi = Self::Abi, Retptr = Self::Retptr>;
|
||||
#[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()
|
||||
}))
|
||||
};
|
||||
|
||||
|
||||
@@ -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<Results, Trap> {
|
||||
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<Results, Trap> {
|
||||
|
||||
@@ -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<u32>) -> 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<u32>) -> 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<T>(pub T);
|
||||
|
||||
impl<T: ResourceLimiter> wasmtime_runtime::ResourceLimiter for ResourceLimiterProxy<T> {
|
||||
fn memory_growing(&mut self, current: u32, desired: u32, maximum: Option<u32>) -> bool {
|
||||
self.0.memory_growing(current, desired, maximum)
|
||||
}
|
||||
|
||||
fn table_growing(&mut self, current: u32, desired: u32, maximum: Option<u32>) -> 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -416,10 +416,10 @@ impl Memory {
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn grow(&self, mut store: impl AsContextMut, delta: u32) -> Result<u32> {
|
||||
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;
|
||||
|
||||
@@ -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<T> {
|
||||
inner: ManuallyDrop<Box<StoreInner<T>>>,
|
||||
}
|
||||
|
||||
pub struct StoreInner<T: ?Sized> {
|
||||
pub struct StoreInner<T> {
|
||||
// This `StoreInner<T>` 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<T: ?Sized> {
|
||||
// least telling the compiler something about all the aliasing happening
|
||||
// within a `Store`.
|
||||
_marker: marker::PhantomPinned,
|
||||
inner: StoreInnermost,
|
||||
limiter: Option<Box<dyn FnMut(&mut T) -> &mut (dyn crate::ResourceLimiter) + Send + Sync>>,
|
||||
entering_native_hook: Option<Box<dyn FnMut(&mut T) -> Result<(), crate::Trap> + Send + Sync>>,
|
||||
exiting_native_hook: Option<Box<dyn FnMut(&mut T) -> Result<(), crate::Trap> + Send + Sync>>,
|
||||
// for comments about `ManuallyDrop`, see `Store::into_data`
|
||||
data: ManuallyDrop<T>,
|
||||
}
|
||||
|
||||
impl<T> Deref for StoreInner<T> {
|
||||
type Target = StoreInnermost;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
impl<T> DerefMut for StoreInner<T> {
|
||||
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<T>.
|
||||
pub struct StoreInnermost {
|
||||
engine: Engine,
|
||||
interrupts: Arc<VMInterrupts>,
|
||||
instances: Vec<StoreInstance>,
|
||||
@@ -109,10 +133,13 @@ pub struct StoreInner<T: ?Sized> {
|
||||
externref_activations_table: VMExternRefActivationsTable,
|
||||
modules: ModuleRegistry,
|
||||
host_trampolines: HashMap<VMSharedSignatureIndex, VMTrampoline>,
|
||||
// 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<T: ?Sized> {
|
||||
async_state: AsyncState,
|
||||
out_of_gas_behavior: OutOfGas,
|
||||
store_data: StoreData,
|
||||
limiter: Option<Box<dyn wasmtime_runtime::ResourceLimiter>>,
|
||||
default_callee: InstanceHandle,
|
||||
// for comments about `ManuallyDrop`, see `Store::into_data`
|
||||
data: ManuallyDrop<T>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
@@ -192,6 +216,7 @@ impl<T> Store<T> {
|
||||
};
|
||||
let mut inner = Box::new(StoreInner {
|
||||
_marker: marker::PhantomPinned,
|
||||
inner: StoreInnermost {
|
||||
engine: engine.clone(),
|
||||
interrupts: Default::default(),
|
||||
instances: Vec::new(),
|
||||
@@ -200,8 +225,11 @@ impl<T> Store<T> {
|
||||
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 {
|
||||
@@ -210,8 +238,11 @@ impl<T> Store<T> {
|
||||
},
|
||||
out_of_gas_behavior: OutOfGas::Trap,
|
||||
store_data: StoreData::new(),
|
||||
limiter: None,
|
||||
default_callee,
|
||||
},
|
||||
limiter: None,
|
||||
entering_native_hook: None,
|
||||
exiting_native_hook: None,
|
||||
data: ManuallyDrop::new(data),
|
||||
});
|
||||
|
||||
@@ -227,11 +258,13 @@ impl<T> Store<T> {
|
||||
}
|
||||
|
||||
/// 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<T> Store<T> {
|
||||
/// 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<T: ?Sized> StoreInner<T> {
|
||||
impl<T> StoreInner<T> {
|
||||
#[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<T: ?Sized> StoreInner<T> {
|
||||
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<T: ?Sized> StoreInner<T> {
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
#[inline]
|
||||
pub fn async_cx(&self) -> AsyncCx {
|
||||
debug_assert!(self.async_support());
|
||||
AsyncCx {
|
||||
@@ -817,11 +903,13 @@ impl<T: ?Sized> StoreInner<T> {
|
||||
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<T: ?Sized> StoreInner<T> {
|
||||
.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<T> wasmtime_runtime::Store for StoreInner<T> {
|
||||
fn vminterrupts(&self) -> *mut VMInterrupts {
|
||||
<StoreInner<T>>::vminterrupts(self)
|
||||
<StoreInnermost>::vminterrupts(self)
|
||||
}
|
||||
|
||||
fn externref_activations_table(
|
||||
@@ -1130,11 +1219,12 @@ unsafe impl<T> wasmtime_runtime::Store for StoreInner<T> {
|
||||
&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)
|
||||
<Self>::limiter(self)
|
||||
}
|
||||
|
||||
fn out_of_gas(&mut self) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
@@ -1196,7 +1286,7 @@ impl<T> Drop for Store<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Drop for StoreInner<T> {
|
||||
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<T>` above.
|
||||
|
||||
@@ -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<T>`][`Store`].
|
||||
@@ -17,7 +17,7 @@ pub struct StoreContext<'a, T>(pub(super) &'a StoreInner<T>);
|
||||
/// 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<T>);
|
||||
pub struct StoreContextMut<'a, T>(pub(crate) &'a mut StoreInner<T>);
|
||||
|
||||
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<dyn Opaque + 'a>,
|
||||
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<T> Opaque for T {}
|
||||
|
||||
// Deref impls to forward all methods on `StoreOpaque` to `StoreInner`.
|
||||
impl<'a> Deref for StoreOpaque<'a> {
|
||||
type Target = StoreInner<dyn Opaque + 'a>;
|
||||
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<dyn Opaque + Send + 'a>,
|
||||
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<dyn Opaque + Send + 'a>;
|
||||
type Target = StoreInnermost;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
|
||||
@@ -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<T>(linker: &mut Linker<T>, store: &mut Store<T>) -> 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))?;
|
||||
|
||||
@@ -32,12 +32,12 @@ fn runtime_value(v: &wast::Expression<'_>) -> Result<Val> {
|
||||
|
||||
/// The wast test script language allows modules to be defined and actions
|
||||
/// to be performed on them.
|
||||
pub struct WastContext {
|
||||
pub struct WastContext<T> {
|
||||
/// Wast files have a concept of a "current" module, which is the most
|
||||
/// recently defined.
|
||||
current: Option<Instance>,
|
||||
linker: Linker<()>,
|
||||
store: Store<()>,
|
||||
linker: Linker<T>,
|
||||
store: Store<T>,
|
||||
}
|
||||
|
||||
enum Outcome<T = Vec<Val>> {
|
||||
@@ -54,9 +54,9 @@ impl<T> Outcome<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl WastContext {
|
||||
impl<T> WastContext<T> {
|
||||
/// Construct a new instance of `WastContext`.
|
||||
pub fn new(store: Store<()>) -> Self {
|
||||
pub fn new(store: Store<T>) -> 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.
|
||||
|
||||
@@ -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<MemoryContext>);
|
||||
|
||||
impl ResourceLimiter for HostMemoryLimiter {
|
||||
impl ResourceLimiter for MemoryContext {
|
||||
fn memory_growing(&mut self, current: u32, desired: u32, maximum: Option<u32>) -> 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<MemoryContext>>, 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::<u32, u32, _>(&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(())
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"),
|
||||
|
||||
244
tests/all/native_hooks.rs
Normal file
244
tests/all/native_hooks.rs
Normal file
@@ -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::<State>::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<State>, 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<State>, 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<State>, 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<State>, 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")),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user