Skip to content

Commit

Permalink
make ResourceLimiter operate on Store data; add hooks for entering an…
Browse files Browse the repository at this point in the history
…d 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 <[email protected]>

* improve tests for the entering/exiting native hooks

Co-authored-by: Alex Crichton <[email protected]>
  • Loading branch information
Pat Hickey and alexcrichton authored Jun 8, 2021
1 parent ffb92d9 commit 8b4bdf9
Show file tree
Hide file tree
Showing 17 changed files with 549 additions and 282 deletions.
13 changes: 7 additions & 6 deletions crates/fuzzing/src/oracles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -55,6 +55,7 @@ fn create_store(engine: &Engine) -> Store<()> {
.memories(1100)
.build(),
);
store.limiter(|s| s as &mut dyn ResourceLimiter);
store
}

Expand Down Expand Up @@ -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)) => {
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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();
}
Expand Down
14 changes: 7 additions & 7 deletions crates/fuzzing/src/oracles/dummy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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)),
Expand All @@ -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);
Expand Down Expand Up @@ -74,27 +74,27 @@ 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()
}

/// Construct a dummy instance for the given instance type.
///
/// 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);
Expand Down
31 changes: 25 additions & 6 deletions crates/runtime/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion crates/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
8 changes: 4 additions & 4 deletions crates/wasmtime/src/externals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
28 changes: 22 additions & 6 deletions crates/wasmtime/src/func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")]
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -883,6 +891,7 @@ impl Func {
}
}

caller.store.0.exiting_native_hook()?;
Ok(())
}

Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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()
}))
};

Expand Down
20 changes: 13 additions & 7 deletions crates/wasmtime/src/func/typed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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> {
Expand Down
Loading

0 comments on commit 8b4bdf9

Please sign in to comment.