Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make ResourceLimiter operate on Store data; add hooks for entering and exiting native code #2952

Merged
merged 11 commits into from
Jun 8, 2021
Merged
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