give sychronous ResourceLimiter an async alternative
This commit is contained in:
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -108,9 +108,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.50"
|
||||
version = "0.1.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b98e84bbb4cbcdd97da190ba0c58a1bb0de2c1fdf67d159e192ed766aeca722"
|
||||
checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3362,6 +3362,7 @@ name = "wasmtime"
|
||||
version = "0.30.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"backtrace",
|
||||
"bincode",
|
||||
"cfg-if 1.0.0",
|
||||
@@ -3459,6 +3460,7 @@ name = "wasmtime-cli"
|
||||
version = "0.30.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"criterion",
|
||||
"env_logger 0.8.3",
|
||||
"file-per-thread-logger",
|
||||
|
||||
@@ -62,6 +62,7 @@ criterion = "0.3.4"
|
||||
num_cpus = "1.13.0"
|
||||
winapi = { version = "0.3.9", features = ['memoryapi'] }
|
||||
memchr = "2.4"
|
||||
async-trait = "0.1"
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = "1.0.19"
|
||||
|
||||
@@ -12,7 +12,6 @@ use crate::vmcontext::{
|
||||
VMInterrupts, VMMemoryDefinition, VMMemoryImport, VMTableDefinition, VMTableImport,
|
||||
};
|
||||
use crate::{ExportFunction, ExportGlobal, ExportMemory, ExportTable, Store};
|
||||
use anyhow::Error;
|
||||
use memoffset::offset_of;
|
||||
use more_asserts::assert_lt;
|
||||
use std::alloc::Layout;
|
||||
@@ -33,86 +32,6 @@ 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 {
|
||||
/// 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 bytes.
|
||||
/// * `desired` is the desired size of the linear memory in bytes.
|
||||
/// * `maximum` is either the linear memory's maximum or a maximum from an
|
||||
/// instance allocator, also in bytes. 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. Returning `true`
|
||||
/// when a maximum has been exceeded will have no effect as the linear
|
||||
/// memory will not grow.
|
||||
///
|
||||
/// This function is not guaranteed to be invoked for all requests to
|
||||
/// `memory.grow`. Requests where the allocation requested size doesn't fit
|
||||
/// in `usize` or exceeds the memory's listed maximum size may not invoke
|
||||
/// this method.
|
||||
fn memory_growing(&mut self, current: usize, desired: usize, maximum: Option<usize>) -> bool;
|
||||
|
||||
/// Notifies the resource limiter that growing a linear memory, permitted by
|
||||
/// the `memory_growing` method, has failed.
|
||||
///
|
||||
/// Reasons for failure include: the growth exceeds the `maximum` passed to
|
||||
/// `memory_growing`, or the operating system failed to allocate additional
|
||||
/// memory. In that case, `error` might be downcastable to a `std::io::Error`.
|
||||
fn memory_grow_failed(&mut self, _error: &Error) {}
|
||||
|
||||
/// 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. Returning `true` when a maximum has been exceeded will have no
|
||||
/// effect as the table will not grow.
|
||||
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`.
|
||||
///
|
||||
/// 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`.
|
||||
///
|
||||
/// 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`
|
||||
///
|
||||
/// 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 type that roughly corresponds to a WebAssembly instance, but is also used
|
||||
/// for host-defined objects.
|
||||
///
|
||||
@@ -441,10 +360,10 @@ impl Instance {
|
||||
(foreign_memory_index, foreign_instance)
|
||||
}
|
||||
};
|
||||
let limiter = unsafe { (*instance.store()).limiter() };
|
||||
let store = unsafe { &mut *instance.store() };
|
||||
let memory = &mut instance.memories[idx];
|
||||
|
||||
let result = unsafe { memory.grow(delta, limiter) };
|
||||
let result = unsafe { memory.grow(delta, store) };
|
||||
let vmmemory = memory.vmmemory();
|
||||
|
||||
// Update the state used by wasm code in case the base pointer and/or
|
||||
@@ -480,13 +399,13 @@ impl Instance {
|
||||
delta: u32,
|
||||
init_value: TableElement,
|
||||
) -> Option<u32> {
|
||||
let limiter = unsafe { (*self.store()).limiter() };
|
||||
let store = unsafe { &mut *self.store() };
|
||||
let table = self
|
||||
.tables
|
||||
.get_mut(table_index)
|
||||
.unwrap_or_else(|| panic!("no table for index {}", table_index.index()));
|
||||
|
||||
let result = unsafe { table.grow(delta, init_value, limiter) };
|
||||
let result = unsafe { table.grow(delta, init_value, store) };
|
||||
|
||||
// Keep the `VMContext` pointers used by compiled Wasm code up to
|
||||
// date.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::imports::Imports;
|
||||
use crate::instance::{Instance, InstanceHandle, ResourceLimiter, RuntimeMemoryCreator};
|
||||
use crate::instance::{Instance, InstanceHandle, RuntimeMemoryCreator};
|
||||
use crate::memory::{DefaultMemoryCreator, Memory};
|
||||
use crate::table::Table;
|
||||
use crate::traphandlers::Trap;
|
||||
@@ -58,13 +58,13 @@ pub struct InstanceAllocationRequest<'a> {
|
||||
/// are a bit of a lie. This is done purely so a store can learn about
|
||||
/// itself when it gets called as a host function, and additionally so this
|
||||
/// runtime can access internals as necessary (such as the
|
||||
/// VMExternRefActivationsTable or the ResourceLimiter).
|
||||
/// VMExternRefActivationsTable or the resource limiter methods).
|
||||
///
|
||||
/// Note that this ends up being a self-pointer to the instance when stored.
|
||||
/// The reason is that the instance itself is then stored within the store.
|
||||
/// We use a number of `PhantomPinned` declarations to indicate this to the
|
||||
/// compiler. More info on this in `wasmtime/src/store.rs`
|
||||
pub store: Option<*mut dyn Store>,
|
||||
pub store: StorePtr,
|
||||
|
||||
/// A list of all wasm data that can be referenced by the module that
|
||||
/// will be allocated. The `Module` given here has active/passive data
|
||||
@@ -77,6 +77,37 @@ pub struct InstanceAllocationRequest<'a> {
|
||||
pub wasm_data: *const [u8],
|
||||
}
|
||||
|
||||
/// A pointer to a Store. This Option<*mut dyn Store> is wrapped in a struct
|
||||
/// so that the function to create a &mut dyn Store is a method on a member of
|
||||
/// InstanceAllocationRequest, rather than on a &mut InstanceAllocationRequest
|
||||
/// itself, because several use-sites require a split mut borrow on the
|
||||
/// InstanceAllocationRequest.
|
||||
pub struct StorePtr(Option<*mut dyn Store>);
|
||||
impl StorePtr {
|
||||
/// A pointer to no Store.
|
||||
pub fn empty() -> Self {
|
||||
Self(None)
|
||||
}
|
||||
/// A pointer to a Store.
|
||||
pub fn new(ptr: *mut dyn Store) -> Self {
|
||||
Self(Some(ptr))
|
||||
}
|
||||
/*
|
||||
/// Update an empty StorePtr to point to a Store.
|
||||
pub fn set(&mut self, ptr: *mut dyn Store) {
|
||||
self.0 = Some(ptr)
|
||||
}
|
||||
*/
|
||||
/// Use the StorePtr as a mut ref to the Store.
|
||||
// XXX should this be an unsafe fn? is it always safe at a use site?
|
||||
pub(crate) fn get(&mut self) -> Option<&mut dyn Store> {
|
||||
match self.0 {
|
||||
Some(ptr) => Some(unsafe { &mut *ptr }),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An link error while instantiating a module.
|
||||
#[derive(Error, Debug)]
|
||||
#[error("Link error: {0}")]
|
||||
@@ -430,7 +461,7 @@ fn initialize_instance(
|
||||
}
|
||||
|
||||
unsafe fn initialize_vmcontext(instance: &mut Instance, req: InstanceAllocationRequest) {
|
||||
if let Some(store) = req.store {
|
||||
if let Some(store) = req.store.0 {
|
||||
*instance.interrupts() = (*store).vminterrupts();
|
||||
*instance.externref_activations_table() = (*store).externref_activations_table().0;
|
||||
instance.set_store(store);
|
||||
@@ -581,17 +612,6 @@ pub struct OnDemandInstanceAllocator {
|
||||
stack_size: usize,
|
||||
}
|
||||
|
||||
// rustc is quite strict with the lifetimes when dealing with mutable borrows,
|
||||
// so this is a little helper to get a shorter lifetime on `Option<&mut T>`
|
||||
fn borrow_limiter<'a>(
|
||||
limiter: &'a mut Option<&mut dyn ResourceLimiter>,
|
||||
) -> Option<&'a mut dyn ResourceLimiter> {
|
||||
match limiter {
|
||||
Some(limiter) => Some(&mut **limiter),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
impl OnDemandInstanceAllocator {
|
||||
/// Creates a new on-demand instance allocator.
|
||||
pub fn new(mem_creator: Option<Arc<dyn RuntimeMemoryCreator>>, stack_size: usize) -> Self {
|
||||
@@ -605,14 +625,19 @@ impl OnDemandInstanceAllocator {
|
||||
|
||||
fn create_tables(
|
||||
module: &Module,
|
||||
mut limiter: Option<&mut dyn ResourceLimiter>,
|
||||
store: &mut StorePtr,
|
||||
) -> Result<PrimaryMap<DefinedTableIndex, Table>, InstantiationError> {
|
||||
let num_imports = module.num_imported_tables;
|
||||
let mut tables: PrimaryMap<DefinedTableIndex, _> =
|
||||
PrimaryMap::with_capacity(module.table_plans.len() - num_imports);
|
||||
for table in &module.table_plans.values().as_slice()[num_imports..] {
|
||||
tables.push(
|
||||
Table::new_dynamic(table, borrow_limiter(&mut limiter))
|
||||
Table::new_dynamic(
|
||||
table,
|
||||
store
|
||||
.get()
|
||||
.expect("if module has table plans, store is not empty"),
|
||||
)
|
||||
.map_err(InstantiationError::Resource)?,
|
||||
);
|
||||
}
|
||||
@@ -622,7 +647,7 @@ impl OnDemandInstanceAllocator {
|
||||
fn create_memories(
|
||||
&self,
|
||||
module: &Module,
|
||||
mut limiter: Option<&mut dyn ResourceLimiter>,
|
||||
store: &mut StorePtr,
|
||||
) -> Result<PrimaryMap<DefinedMemoryIndex, Memory>, InstantiationError> {
|
||||
let creator = self
|
||||
.mem_creator
|
||||
@@ -633,7 +658,13 @@ impl OnDemandInstanceAllocator {
|
||||
PrimaryMap::with_capacity(module.memory_plans.len() - num_imports);
|
||||
for plan in &module.memory_plans.values().as_slice()[num_imports..] {
|
||||
memories.push(
|
||||
Memory::new_dynamic(plan, creator, borrow_limiter(&mut limiter))
|
||||
Memory::new_dynamic(
|
||||
plan,
|
||||
creator,
|
||||
store
|
||||
.get()
|
||||
.expect("if module has memory plans, store is not empty"),
|
||||
)
|
||||
.map_err(InstantiationError::Resource)?,
|
||||
);
|
||||
}
|
||||
@@ -656,9 +687,8 @@ unsafe impl InstanceAllocator for OnDemandInstanceAllocator {
|
||||
&self,
|
||||
mut req: InstanceAllocationRequest,
|
||||
) -> Result<InstanceHandle, InstantiationError> {
|
||||
let mut limiter = req.store.and_then(|s| (*s).limiter());
|
||||
let memories = self.create_memories(&req.module, borrow_limiter(&mut limiter))?;
|
||||
let tables = Self::create_tables(&req.module, borrow_limiter(&mut limiter))?;
|
||||
let memories = self.create_memories(&req.module, &mut req.store)?;
|
||||
let tables = Self::create_tables(&req.module, &mut req.store)?;
|
||||
|
||||
let host_state = std::mem::replace(&mut req.host_state, Box::new(()));
|
||||
|
||||
|
||||
@@ -7,10 +7,9 @@
|
||||
//! Using the pooling instance allocator can speed up module instantiation
|
||||
//! when modules can be constrained based on configurable limits.
|
||||
|
||||
use super::borrow_limiter;
|
||||
use super::{
|
||||
initialize_instance, initialize_vmcontext, InstanceAllocationRequest, InstanceAllocator,
|
||||
InstanceHandle, InstantiationError, ResourceLimiter,
|
||||
InstanceHandle, InstantiationError,
|
||||
};
|
||||
use crate::{instance::Instance, Memory, Mmap, Table, VMContext};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
@@ -385,19 +384,16 @@ impl InstancePool {
|
||||
instance.host_state = std::mem::replace(&mut req.host_state, Box::new(()));
|
||||
instance.wasm_data = &*req.wasm_data;
|
||||
|
||||
let mut limiter = req.store.and_then(|s| (*s).limiter());
|
||||
Self::set_instance_memories(
|
||||
instance,
|
||||
self.memories.get(index),
|
||||
self.memories.max_wasm_pages,
|
||||
borrow_limiter(&mut limiter),
|
||||
)?;
|
||||
|
||||
Self::set_instance_tables(
|
||||
instance,
|
||||
self.tables.get(index).map(|x| x as *mut usize),
|
||||
self.tables.max_elements,
|
||||
borrow_limiter(&mut limiter),
|
||||
)?;
|
||||
|
||||
initialize_vmcontext(instance, req);
|
||||
@@ -503,7 +499,6 @@ impl InstancePool {
|
||||
instance: &mut Instance,
|
||||
mut memories: impl Iterator<Item = *mut u8>,
|
||||
max_pages: u64,
|
||||
mut limiter: Option<&mut dyn ResourceLimiter>,
|
||||
) -> Result<(), InstantiationError> {
|
||||
let module = instance.module.as_ref();
|
||||
|
||||
@@ -519,12 +514,9 @@ impl InstancePool {
|
||||
)
|
||||
};
|
||||
instance.memories.push(
|
||||
Memory::new_static(
|
||||
plan,
|
||||
memory,
|
||||
commit_memory_pages,
|
||||
borrow_limiter(&mut limiter),
|
||||
)
|
||||
Memory::new_static(plan, memory, commit_memory_pages, unsafe {
|
||||
&mut *instance.store()
|
||||
})
|
||||
.map_err(InstantiationError::Resource)?,
|
||||
);
|
||||
}
|
||||
@@ -538,7 +530,6 @@ impl InstancePool {
|
||||
instance: &mut Instance,
|
||||
mut tables: impl Iterator<Item = *mut usize>,
|
||||
max_elements: u32,
|
||||
mut limiter: Option<&mut dyn ResourceLimiter>,
|
||||
) -> Result<(), InstantiationError> {
|
||||
let module = instance.module.as_ref();
|
||||
|
||||
@@ -555,7 +546,7 @@ impl InstancePool {
|
||||
|
||||
let table = unsafe { std::slice::from_raw_parts_mut(base, max_elements as usize) };
|
||||
instance.tables.push(
|
||||
Table::new_static(plan, table, borrow_limiter(&mut limiter))
|
||||
Table::new_static(plan, table, unsafe { &mut *instance.store() })
|
||||
.map_err(InstantiationError::Resource)?,
|
||||
);
|
||||
}
|
||||
@@ -1052,7 +1043,7 @@ unsafe impl InstanceAllocator for PoolingInstanceAllocator {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::{Imports, VMSharedSignatureIndex};
|
||||
use crate::{Imports, StorePtr, VMSharedSignatureIndex};
|
||||
use wasmtime_environ::{
|
||||
EntityRef, Global, GlobalInit, Memory, MemoryPlan, ModuleType, SignatureIndex, Table,
|
||||
TablePlan, TableStyle, WasmType,
|
||||
@@ -1414,7 +1405,7 @@ mod test {
|
||||
},
|
||||
shared_signatures: VMSharedSignatureIndex::default().into(),
|
||||
host_state: Box::new(()),
|
||||
store: None,
|
||||
store: StorePtr::empty(),
|
||||
wasm_data: &[],
|
||||
},
|
||||
)
|
||||
@@ -1438,7 +1429,7 @@ mod test {
|
||||
},
|
||||
shared_signatures: VMSharedSignatureIndex::default().into(),
|
||||
host_state: Box::new(()),
|
||||
store: None,
|
||||
store: StorePtr::empty(),
|
||||
wasm_data: &[],
|
||||
},
|
||||
) {
|
||||
|
||||
@@ -42,8 +42,7 @@ pub use crate::imports::Imports;
|
||||
pub use crate::instance::{
|
||||
InstanceAllocationRequest, InstanceAllocator, InstanceHandle, InstanceLimits,
|
||||
InstantiationError, LinkError, ModuleLimits, OnDemandInstanceAllocator,
|
||||
PoolingAllocationStrategy, PoolingInstanceAllocator, ResourceLimiter, DEFAULT_INSTANCE_LIMIT,
|
||||
DEFAULT_MEMORY_LIMIT, DEFAULT_TABLE_LIMIT,
|
||||
PoolingAllocationStrategy, PoolingInstanceAllocator, StorePtr,
|
||||
};
|
||||
pub use crate::jit_int::GdbJitImageRegistration;
|
||||
pub use crate::memory::{Memory, RuntimeLinearMemory, RuntimeMemoryCreator};
|
||||
@@ -91,8 +90,18 @@ pub unsafe trait Store {
|
||||
&mut self,
|
||||
) -> (&mut VMExternRefActivationsTable, &dyn ModuleInfoLookup);
|
||||
|
||||
/// Returns a reference to the store's limiter for limiting resources, if any.
|
||||
fn limiter(&mut self) -> Option<&mut dyn ResourceLimiter>;
|
||||
/// Callback invoked to allow the store's resource limiter to reject a memory grow operation.
|
||||
fn limiter_memory_growing(
|
||||
&mut self,
|
||||
current: usize,
|
||||
desired: usize,
|
||||
maximum: Option<usize>,
|
||||
) -> bool;
|
||||
/// Callback invoked to notify the store's resource limiter that a memory grow operation has
|
||||
/// failed.
|
||||
fn limiter_memory_grow_failed(&mut self, error: &anyhow::Error);
|
||||
/// Callback invoked to allow the store's resource limiter to reject a table grow operation.
|
||||
fn limiter_table_growing(&mut self, current: u32, desired: u32, maximum: Option<u32>) -> bool;
|
||||
|
||||
/// Callback invoked whenever fuel runs out by a wasm instance. If an error
|
||||
/// is returned that's raised as a trap. Otherwise wasm execution will
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
use crate::mmap::Mmap;
|
||||
use crate::vmcontext::VMMemoryDefinition;
|
||||
use crate::ResourceLimiter;
|
||||
use anyhow::{bail, format_err, Error, Result};
|
||||
use crate::Store;
|
||||
use anyhow::{bail, format_err, Result};
|
||||
use more_asserts::{assert_ge, assert_le};
|
||||
use std::convert::TryFrom;
|
||||
use wasmtime_environ::{MemoryPlan, MemoryStyle, WASM32_MAX_PAGES, WASM64_MAX_PAGES};
|
||||
@@ -212,33 +212,14 @@ pub enum Memory {
|
||||
Dynamic(Box<dyn RuntimeLinearMemory>),
|
||||
}
|
||||
|
||||
fn memory_growing(
|
||||
limiter: &mut Option<&mut dyn ResourceLimiter>,
|
||||
current: usize,
|
||||
desired: usize,
|
||||
maximum: Option<usize>,
|
||||
) -> bool {
|
||||
match limiter {
|
||||
Some(ref mut l) => l.memory_growing(current, desired, maximum),
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn memory_grow_failed(limiter: &mut Option<&mut dyn ResourceLimiter>, error: &Error) {
|
||||
match limiter {
|
||||
Some(l) => l.memory_grow_failed(error),
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Memory {
|
||||
/// Create a new dynamic (movable) memory instance for the specified plan.
|
||||
pub fn new_dynamic(
|
||||
plan: &MemoryPlan,
|
||||
creator: &dyn RuntimeMemoryCreator,
|
||||
limiter: Option<&mut dyn ResourceLimiter>,
|
||||
store: &mut dyn Store,
|
||||
) -> Result<Self> {
|
||||
let (minimum, maximum) = Self::limit_new(plan, limiter)?;
|
||||
let (minimum, maximum) = Self::limit_new(plan, store)?;
|
||||
Ok(Memory::Dynamic(creator.new_memory(plan, minimum, maximum)?))
|
||||
}
|
||||
|
||||
@@ -247,9 +228,9 @@ impl Memory {
|
||||
plan: &MemoryPlan,
|
||||
base: &'static mut [u8],
|
||||
make_accessible: fn(*mut u8, usize) -> Result<()>,
|
||||
limiter: Option<&mut dyn ResourceLimiter>,
|
||||
store: &mut dyn Store,
|
||||
) -> Result<Self> {
|
||||
let (minimum, maximum) = Self::limit_new(plan, limiter)?;
|
||||
let (minimum, maximum) = Self::limit_new(plan, store)?;
|
||||
|
||||
let base = match maximum {
|
||||
Some(max) if max < base.len() => &mut base[..max],
|
||||
@@ -269,15 +250,11 @@ impl Memory {
|
||||
})
|
||||
}
|
||||
|
||||
/// Calls the `limiter`, if specified, to optionally prevent a memory from
|
||||
/// being allocated.
|
||||
/// Calls the `store`'s limiter to optionally prevent a memory from being allocated.
|
||||
///
|
||||
/// Returns the minimum size and optional maximum size of the memory, in
|
||||
/// bytes.
|
||||
fn limit_new(
|
||||
plan: &MemoryPlan,
|
||||
mut limiter: Option<&mut dyn ResourceLimiter>,
|
||||
) -> Result<(usize, Option<usize>)> {
|
||||
fn limit_new(plan: &MemoryPlan, store: &mut dyn Store) -> Result<(usize, Option<usize>)> {
|
||||
// Sanity-check what should already be true from wasm module validation.
|
||||
let absolute_max = if plan.memory.memory64 {
|
||||
WASM64_MAX_PAGES
|
||||
@@ -291,7 +268,7 @@ impl Memory {
|
||||
// allocate, which is our entire address space minus a wasm page. That
|
||||
// shouldn't ever actually work in terms of an allocation because
|
||||
// presumably the kernel wants *something* for itself, but this is used
|
||||
// to pass to the `limiter` specified, if present, for a requested size
|
||||
// to pass to the `store`'s limiter for a requested size
|
||||
// to approximate the scale of the request that the wasm module is
|
||||
// making. This is necessary because the limiter works on `usize` bytes
|
||||
// whereas we're working with possibly-overflowing `u64` calculations
|
||||
@@ -302,7 +279,7 @@ impl Memory {
|
||||
|
||||
// If the minimum memory size overflows the size of our own address
|
||||
// space, then we can't satisfy this request, but defer the error to
|
||||
// later so the `limiter` can be informed that an effective oom is
|
||||
// later so the `store` can be informed that an effective oom is
|
||||
// happening.
|
||||
let minimum = plan
|
||||
.memory
|
||||
@@ -332,13 +309,13 @@ impl Memory {
|
||||
maximum = usize::try_from(1u64 << 32).ok();
|
||||
}
|
||||
|
||||
// Inform the limiter what's about to happen. This will let the limiter
|
||||
// Inform the store's limiter what's about to happen. This will let the limiter
|
||||
// reject anything if necessary, and this also guarantees that we should
|
||||
// call the limiter for all requested memories, even if our `minimum`
|
||||
// calculation overflowed. This means that the `minimum` we're informing
|
||||
// the limiter is lossy and may not be 100% accurate, but for now the
|
||||
// expected uses of `limiter` means that's ok.
|
||||
if !memory_growing(&mut limiter, 0, minimum.unwrap_or(absolute_max), maximum) {
|
||||
// expected uses of limiter means that's ok.
|
||||
if !store.limiter_memory_growing(0, minimum.unwrap_or(absolute_max), maximum) {
|
||||
bail!(
|
||||
"memory minimum size of {} pages exceeds memory limits",
|
||||
plan.memory.minimum
|
||||
@@ -400,11 +377,7 @@ impl Memory {
|
||||
///
|
||||
/// Generally, prefer using `InstanceHandle::memory_grow`, which encapsulates
|
||||
/// this unsafety.
|
||||
pub unsafe fn grow(
|
||||
&mut self,
|
||||
delta_pages: u64,
|
||||
mut limiter: Option<&mut dyn ResourceLimiter>,
|
||||
) -> Option<usize> {
|
||||
pub unsafe fn grow(&mut self, delta_pages: u64, store: &mut dyn Store) -> Option<usize> {
|
||||
let old_byte_size = self.byte_size();
|
||||
// Wasm spec: when growing by 0 pages, always return the current size.
|
||||
if delta_pages == 0 {
|
||||
@@ -428,15 +401,15 @@ impl Memory {
|
||||
};
|
||||
|
||||
let maximum = self.maximum_byte_size();
|
||||
// Limiter gets first chance to reject memory_growing.
|
||||
if !memory_growing(&mut limiter, old_byte_size, new_byte_size, maximum) {
|
||||
// Store limiter gets first chance to reject memory_growing.
|
||||
if !store.limiter_memory_growing(old_byte_size, new_byte_size, maximum) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Never exceed maximum, even if limiter permitted it.
|
||||
if let Some(max) = maximum {
|
||||
if new_byte_size > max {
|
||||
memory_grow_failed(&mut limiter, &format_err!("Memory maximum size exceeded"));
|
||||
store.limiter_memory_grow_failed(&format_err!("Memory maximum size exceeded"));
|
||||
return None;
|
||||
}
|
||||
}
|
||||
@@ -458,7 +431,7 @@ impl Memory {
|
||||
} => {
|
||||
// Never exceed static memory size
|
||||
if new_byte_size > base.len() {
|
||||
memory_grow_failed(&mut limiter, &format_err!("static memory size exceeded"));
|
||||
store.limiter_memory_grow_failed(&format_err!("static memory size exceeded"));
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -467,13 +440,13 @@ impl Memory {
|
||||
base.as_mut_ptr().add(old_byte_size),
|
||||
new_byte_size - old_byte_size,
|
||||
);
|
||||
r.map_err(|e| memory_grow_failed(&mut limiter, &e)).ok()?;
|
||||
r.map_err(|e| store.limiter_memory_grow_failed(&e)).ok()?;
|
||||
|
||||
*size = new_byte_size;
|
||||
}
|
||||
Memory::Dynamic(mem) => {
|
||||
let r = mem.grow_to(new_byte_size);
|
||||
r.map_err(|e| memory_grow_failed(&mut limiter, &e)).ok()?;
|
||||
r.map_err(|e| store.limiter_memory_grow_failed(&e)).ok()?;
|
||||
}
|
||||
}
|
||||
Some(old_byte_size)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//! `Table` is to WebAssembly tables what `LinearMemory` is to WebAssembly linear memories.
|
||||
|
||||
use crate::vmcontext::{VMCallerCheckedAnyfunc, VMTableDefinition};
|
||||
use crate::{ResourceLimiter, Trap, VMExternRef};
|
||||
use crate::{Store, Trap, VMExternRef};
|
||||
use anyhow::{bail, Result};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::ops::Range;
|
||||
@@ -137,11 +137,8 @@ fn wasm_to_table_type(ty: WasmType) -> Result<TableElementType> {
|
||||
|
||||
impl Table {
|
||||
/// Create a new dynamic (movable) table instance for the specified table plan.
|
||||
pub fn new_dynamic(
|
||||
plan: &TablePlan,
|
||||
limiter: Option<&mut dyn ResourceLimiter>,
|
||||
) -> Result<Self> {
|
||||
Self::limit_new(plan, limiter)?;
|
||||
pub fn new_dynamic(plan: &TablePlan, store: &mut dyn Store) -> Result<Self> {
|
||||
Self::limit_new(plan, store)?;
|
||||
let elements = vec![0; plan.table.minimum as usize];
|
||||
let ty = wasm_to_table_type(plan.table.wasm_ty)?;
|
||||
let maximum = plan.table.maximum;
|
||||
@@ -157,9 +154,9 @@ impl Table {
|
||||
pub fn new_static(
|
||||
plan: &TablePlan,
|
||||
data: &'static mut [usize],
|
||||
limiter: Option<&mut dyn ResourceLimiter>,
|
||||
store: &mut dyn Store,
|
||||
) -> Result<Self> {
|
||||
Self::limit_new(plan, limiter)?;
|
||||
Self::limit_new(plan, store)?;
|
||||
let size = plan.table.minimum;
|
||||
let ty = wasm_to_table_type(plan.table.wasm_ty)?;
|
||||
let data = match plan.table.maximum {
|
||||
@@ -170,15 +167,13 @@ impl Table {
|
||||
Ok(Table::Static { data, size, ty })
|
||||
}
|
||||
|
||||
fn limit_new(plan: &TablePlan, limiter: Option<&mut dyn ResourceLimiter>) -> Result<()> {
|
||||
if let Some(limiter) = limiter {
|
||||
if !limiter.table_growing(0, plan.table.minimum, plan.table.maximum) {
|
||||
fn limit_new(plan: &TablePlan, store: &mut dyn Store) -> Result<()> {
|
||||
if !store.limiter_table_growing(0, plan.table.minimum, plan.table.maximum) {
|
||||
bail!(
|
||||
"table minimum size of {} elements exceeds table limits",
|
||||
plan.table.minimum
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -292,16 +287,14 @@ impl Table {
|
||||
&mut self,
|
||||
delta: u32,
|
||||
init_value: TableElement,
|
||||
limiter: Option<&mut dyn ResourceLimiter>,
|
||||
store: &mut dyn Store,
|
||||
) -> Option<u32> {
|
||||
let old_size = self.size();
|
||||
let new_size = old_size.checked_add(delta)?;
|
||||
|
||||
if let Some(limiter) = limiter {
|
||||
if !limiter.table_growing(old_size, new_size, self.maximum()) {
|
||||
if !store.limiter_table_growing(old_size, new_size, self.maximum()) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(max) = self.maximum() {
|
||||
if new_size > max {
|
||||
|
||||
@@ -38,6 +38,7 @@ psm = "0.1.11"
|
||||
lazy_static = "1.4"
|
||||
rayon = { version = "1.0", optional = true }
|
||||
object = { version = "0.27", default-features = false, features = ['read_core', 'elf'] }
|
||||
async-trait = { version = "0.1.51", optional = true }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winapi = "0.3.7"
|
||||
@@ -73,7 +74,7 @@ cache = ["wasmtime-cache"]
|
||||
|
||||
# Enables support for "async stores" as well as defining host functions as
|
||||
# `async fn` and calling functions asynchronously.
|
||||
async = ["wasmtime-fiber", "wasmtime-runtime/async"]
|
||||
async = ["wasmtime-fiber", "wasmtime-runtime/async", "async-trait"]
|
||||
|
||||
# Enables userfaultfd support in the runtime's pooling allocator when building on Linux
|
||||
uffd = ["wasmtime-runtime/uffd"]
|
||||
|
||||
@@ -551,7 +551,7 @@ impl Table {
|
||||
let init = init.into_table_element(store, ty)?;
|
||||
let table = self.wasmtime_table(store);
|
||||
unsafe {
|
||||
match (*table).grow(delta, init, store.limiter()) {
|
||||
match (*table).grow(delta, init, store) {
|
||||
Some(size) => {
|
||||
let vm = (*table).vmtable();
|
||||
*store[self.0].definition = vm;
|
||||
|
||||
@@ -15,7 +15,7 @@ use wasmtime_environ::{
|
||||
};
|
||||
use wasmtime_jit::TypeTables;
|
||||
use wasmtime_runtime::{
|
||||
Imports, InstanceAllocationRequest, InstantiationError, VMContext, VMFunctionBody,
|
||||
Imports, InstanceAllocationRequest, InstantiationError, StorePtr, VMContext, VMFunctionBody,
|
||||
VMFunctionImport, VMGlobalImport, VMMemoryImport, VMTableImport,
|
||||
};
|
||||
|
||||
@@ -734,7 +734,7 @@ impl<'a> Instantiator<'a> {
|
||||
imports: self.cur.build(),
|
||||
shared_signatures: self.cur.module.signatures().as_module_map().into(),
|
||||
host_state: Box::new(Instance(instance_to_be)),
|
||||
store: Some(store.traitobj()),
|
||||
store: StorePtr::new(store.traitobj()),
|
||||
wasm_data: compiled_module.wasm_data(),
|
||||
})?;
|
||||
|
||||
|
||||
@@ -1,4 +1,118 @@
|
||||
pub use wasmtime_runtime::ResourceLimiter;
|
||||
/// 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 {
|
||||
/// 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 bytes.
|
||||
/// * `desired` is the desired size of the linear memory in bytes.
|
||||
/// * `maximum` is either the linear memory's maximum or a maximum from an
|
||||
/// instance allocator, also in bytes. 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. Returning `true`
|
||||
/// when a maximum has been exceeded will have no effect as the linear
|
||||
/// memory will not grow.
|
||||
///
|
||||
/// This function is not guaranteed to be invoked for all requests to
|
||||
/// `memory.grow`. Requests where the allocation requested size doesn't fit
|
||||
/// in `usize` or exceeds the memory's listed maximum size may not invoke
|
||||
/// this method.
|
||||
fn memory_growing(&mut self, current: usize, desired: usize, maximum: Option<usize>) -> bool;
|
||||
|
||||
/// Notifies the resource limiter that growing a linear memory, permitted by
|
||||
/// the `memory_growing` method, has failed.
|
||||
///
|
||||
/// Reasons for failure include: the growth exceeds the `maximum` passed to
|
||||
/// `memory_growing`, or the operating system failed to allocate additional
|
||||
/// memory. In that case, `error` might be downcastable to a `std::io::Error`.
|
||||
fn memory_grow_failed(&mut self, _error: &anyhow::Error) {}
|
||||
|
||||
/// 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. Returning `true` when a maximum has been exceeded will have no
|
||||
/// effect as the table will not grow.
|
||||
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`.
|
||||
///
|
||||
/// 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`.
|
||||
///
|
||||
/// 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`
|
||||
///
|
||||
/// Instantiation will fail with an error if this limit is exceeded.
|
||||
///
|
||||
/// This value defaults to 10,000.
|
||||
fn memories(&self) -> usize {
|
||||
DEFAULT_MEMORY_LIMIT
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
/// Used by hosts to limit resource consumption of instances.
|
||||
/// Identical to [`ResourceLimiter`], except that the `memory_growing` and `table_growing`
|
||||
/// functions are async. Must be used with an async [`Store`].
|
||||
#[async_trait::async_trait]
|
||||
pub trait ResourceLimiterAsync {
|
||||
/// Async version of [`ResourceLimiter::memory_growing`]
|
||||
async fn memory_growing(
|
||||
&mut self,
|
||||
current: usize,
|
||||
desired: usize,
|
||||
maximum: Option<usize>,
|
||||
) -> bool;
|
||||
|
||||
/// Identical to [`ResourceLimiter::memory_grow_failed`]
|
||||
fn memory_grow_failed(&mut self, error: &anyhow::Error);
|
||||
|
||||
/// Asynchronous version of [`ResourceLimiter::table_growing`]
|
||||
async fn table_growing(&mut self, current: u32, desired: u32, maximum: Option<u32>) -> bool;
|
||||
|
||||
/// Identical to [`ResourceLimiter::instances`]`
|
||||
fn instances(&self) -> usize {
|
||||
DEFAULT_INSTANCE_LIMIT
|
||||
}
|
||||
|
||||
/// Identical to [`ResourceLimiter::tables`]`
|
||||
fn tables(&self) -> usize {
|
||||
DEFAULT_TABLE_LIMIT
|
||||
}
|
||||
|
||||
/// Identical to [`ResourceLimiter::memories`]`
|
||||
fn memories(&self) -> usize {
|
||||
DEFAULT_MEMORY_LIMIT
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to build [`StoreLimits`].
|
||||
pub struct StoreLimitsBuilder(StoreLimits);
|
||||
@@ -79,13 +193,14 @@ impl Default for StoreLimits {
|
||||
Self {
|
||||
memory_size: None,
|
||||
table_elements: None,
|
||||
instances: wasmtime_runtime::DEFAULT_INSTANCE_LIMIT,
|
||||
tables: wasmtime_runtime::DEFAULT_TABLE_LIMIT,
|
||||
memories: wasmtime_runtime::DEFAULT_MEMORY_LIMIT,
|
||||
instances: DEFAULT_INSTANCE_LIMIT,
|
||||
tables: DEFAULT_TABLE_LIMIT,
|
||||
memories: DEFAULT_MEMORY_LIMIT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "async", async_trait::async_trait)]
|
||||
impl ResourceLimiter for StoreLimits {
|
||||
fn memory_growing(&mut self, _current: usize, desired: usize, _maximum: Option<usize>) -> bool {
|
||||
match self.memory_size {
|
||||
|
||||
@@ -461,7 +461,7 @@ impl Memory {
|
||||
let store = store.as_context_mut().0;
|
||||
let mem = self.wasmtime_memory(store);
|
||||
unsafe {
|
||||
match (*mem).grow(delta, store.limiter()) {
|
||||
match (*mem).grow(delta, store) {
|
||||
Some(size) => {
|
||||
let vm = (*mem).vmmemory();
|
||||
*store[self.0].definition = vm;
|
||||
|
||||
@@ -93,8 +93,8 @@ use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
use wasmtime_runtime::{
|
||||
InstanceAllocationRequest, InstanceAllocator, InstanceHandle, ModuleInfo,
|
||||
OnDemandInstanceAllocator, SignalHandler, VMCallerCheckedAnyfunc, VMContext, VMExternRef,
|
||||
VMExternRefActivationsTable, VMInterrupts, VMSharedSignatureIndex, VMTrampoline,
|
||||
OnDemandInstanceAllocator, SignalHandler, StorePtr, VMCallerCheckedAnyfunc, VMContext,
|
||||
VMExternRef, VMExternRefActivationsTable, VMInterrupts, VMSharedSignatureIndex, VMTrampoline,
|
||||
};
|
||||
|
||||
mod context;
|
||||
@@ -197,12 +197,18 @@ pub struct StoreInner<T> {
|
||||
/// Generic metadata about the store that doesn't need access to `T`.
|
||||
inner: StoreOpaque,
|
||||
|
||||
limiter: Option<Box<dyn FnMut(&mut T) -> &mut (dyn crate::ResourceLimiter) + Send + Sync>>,
|
||||
limiter: Option<ResourceLimiterInner<T>>,
|
||||
call_hook: Option<Box<dyn FnMut(&mut T, CallHook) -> Result<(), crate::Trap> + Send + Sync>>,
|
||||
// for comments about `ManuallyDrop`, see `Store::into_data`
|
||||
data: ManuallyDrop<T>,
|
||||
}
|
||||
|
||||
enum ResourceLimiterInner<T> {
|
||||
Sync(Box<dyn FnMut(&mut T) -> &mut (dyn crate::ResourceLimiter) + Send + Sync>),
|
||||
#[cfg(feature = "async")]
|
||||
Async(Box<dyn FnMut(&mut T) -> &mut (dyn crate::ResourceLimiterAsync) + Send + Sync>),
|
||||
}
|
||||
|
||||
// Forward methods on `StoreOpaque` to also being on `StoreInner<T>`
|
||||
impl<T> Deref for StoreInner<T> {
|
||||
type Target = StoreOpaque;
|
||||
@@ -402,7 +408,7 @@ impl<T> Store<T> {
|
||||
shared_signatures: None.into(),
|
||||
imports: Default::default(),
|
||||
module: Arc::new(wasmtime_environ::Module::default()),
|
||||
store: None,
|
||||
store: StorePtr::empty(),
|
||||
wasm_data: &[],
|
||||
})
|
||||
.expect("failed to allocate default callee")
|
||||
@@ -418,11 +424,11 @@ impl<T> Store<T> {
|
||||
modules: ModuleRegistry::default(),
|
||||
host_trampolines: HashMap::default(),
|
||||
instance_count: 0,
|
||||
instance_limit: wasmtime_runtime::DEFAULT_INSTANCE_LIMIT,
|
||||
instance_limit: crate::DEFAULT_INSTANCE_LIMIT,
|
||||
memory_count: 0,
|
||||
memory_limit: wasmtime_runtime::DEFAULT_MEMORY_LIMIT,
|
||||
memory_limit: crate::DEFAULT_MEMORY_LIMIT,
|
||||
table_count: 0,
|
||||
table_limit: wasmtime_runtime::DEFAULT_TABLE_LIMIT,
|
||||
table_limit: crate::DEFAULT_TABLE_LIMIT,
|
||||
fuel_adj: 0,
|
||||
#[cfg(feature = "async")]
|
||||
async_state: AsyncState {
|
||||
@@ -525,7 +531,36 @@ impl<T> Store<T> {
|
||||
innermost.memory_limit = memory_limit;
|
||||
|
||||
// Save the limiter accessor function:
|
||||
inner.limiter = Some(Box::new(limiter));
|
||||
inner.limiter = Some(ResourceLimiterInner::Sync(Box::new(limiter)));
|
||||
}
|
||||
|
||||
/// Configures the [`ResourceLimiterAsync`](crate::ResourceLimiterAsync) used to limit
|
||||
/// resource creation within this [`Store`]. Must be used with an async `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_async(
|
||||
&mut self,
|
||||
mut limiter: impl FnMut(&mut T) -> &mut (dyn crate::ResourceLimiterAsync)
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
) {
|
||||
debug_assert!(self.inner.async_support());
|
||||
// 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(ResourceLimiterInner::Async(Box::new(limiter)));
|
||||
}
|
||||
|
||||
/// Configure a function that runs on calls and returns between WebAssembly
|
||||
@@ -872,11 +907,6 @@ impl<T> StoreInner<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 call_hook(&mut self, s: CallHook) -> Result<(), Trap> {
|
||||
if let Some(hook) = &mut self.call_hook {
|
||||
hook(&mut self.data, s)
|
||||
@@ -1496,8 +1526,81 @@ unsafe impl<T> wasmtime_runtime::Store for StoreInner<T> {
|
||||
(&mut inner.externref_activations_table, &inner.modules)
|
||||
}
|
||||
|
||||
fn limiter(&mut self) -> Option<&mut dyn wasmtime_runtime::ResourceLimiter> {
|
||||
<Self>::limiter(self)
|
||||
fn limiter_memory_growing(
|
||||
&mut self,
|
||||
current: usize,
|
||||
desired: usize,
|
||||
maximum: Option<usize>,
|
||||
) -> bool {
|
||||
// Need to borrow async_cx before the mut borrow of the limiter.
|
||||
// self.async_cx() panicks when used with a non-async store, so
|
||||
// wrap this in an option.
|
||||
#[cfg(feature = "async")]
|
||||
let async_cx = if self.async_support() {
|
||||
Some(self.async_cx())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
match self.limiter {
|
||||
Some(ResourceLimiterInner::Sync(ref mut limiter)) => {
|
||||
limiter(&mut self.data).memory_growing(current, desired, maximum)
|
||||
}
|
||||
#[cfg(feature = "async")]
|
||||
Some(ResourceLimiterInner::Async(ref mut limiter)) => unsafe {
|
||||
async_cx
|
||||
.expect("ResourceLimiterAsync requires async Store")
|
||||
.block_on(
|
||||
limiter(&mut self.data)
|
||||
.memory_growing(current, desired, maximum)
|
||||
.as_mut(),
|
||||
)
|
||||
.expect("FIXME idk how to deal with a trap here!")
|
||||
},
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn limiter_memory_grow_failed(&mut self, error: &anyhow::Error) {
|
||||
match self.limiter {
|
||||
Some(ResourceLimiterInner::Sync(ref mut limiter)) => {
|
||||
limiter(&mut self.data).memory_grow_failed(error)
|
||||
}
|
||||
#[cfg(feature = "async")]
|
||||
Some(ResourceLimiterInner::Async(ref mut limiter)) => {
|
||||
limiter(&mut self.data).memory_grow_failed(error)
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn limiter_table_growing(&mut self, current: u32, desired: u32, maximum: Option<u32>) -> bool {
|
||||
// Need to borrow async_cx before the mut borrow of the limiter.
|
||||
// self.async_cx() panicks when used with a non-async store, so
|
||||
// wrap this in an option.
|
||||
#[cfg(feature = "async")]
|
||||
let async_cx = if self.async_support() {
|
||||
Some(self.async_cx())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
match self.limiter {
|
||||
Some(ResourceLimiterInner::Sync(ref mut limiter)) => {
|
||||
limiter(&mut self.data).table_growing(current, desired, maximum)
|
||||
}
|
||||
#[cfg(feature = "async")]
|
||||
Some(ResourceLimiterInner::Async(ref mut limiter)) => unsafe {
|
||||
async_cx
|
||||
.expect("ResourceLimiterAsync requires async Store")
|
||||
.block_on(
|
||||
limiter(&mut self.data)
|
||||
.table_growing(current, desired, maximum)
|
||||
.as_mut(),
|
||||
)
|
||||
.expect("FIXME idk how to deal with a trap here!")
|
||||
},
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn out_of_gas(&mut self) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
|
||||
@@ -18,7 +18,7 @@ use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
use wasmtime_environ::{EntityIndex, GlobalIndex, MemoryIndex, Module, TableIndex};
|
||||
use wasmtime_runtime::{
|
||||
Imports, InstanceAllocationRequest, InstanceAllocator, OnDemandInstanceAllocator,
|
||||
Imports, InstanceAllocationRequest, InstanceAllocator, OnDemandInstanceAllocator, StorePtr,
|
||||
VMFunctionImport, VMSharedSignatureIndex,
|
||||
};
|
||||
|
||||
@@ -46,7 +46,7 @@ fn create_handle(
|
||||
imports,
|
||||
shared_signatures: shared_signature_id.into(),
|
||||
host_state,
|
||||
store: Some(store.traitobj()),
|
||||
store: StorePtr::new(store.traitobj()),
|
||||
wasm_data: &[],
|
||||
},
|
||||
)?;
|
||||
|
||||
@@ -9,7 +9,8 @@ use wasmtime_environ::{EntityIndex, Module, ModuleType, PrimaryMap, SignatureInd
|
||||
use wasmtime_jit::{CodeMemory, MmapVec};
|
||||
use wasmtime_runtime::{
|
||||
Imports, InstanceAllocationRequest, InstanceAllocator, InstanceHandle,
|
||||
OnDemandInstanceAllocator, VMContext, VMFunctionBody, VMSharedSignatureIndex, VMTrampoline,
|
||||
OnDemandInstanceAllocator, StorePtr, VMContext, VMFunctionBody, VMSharedSignatureIndex,
|
||||
VMTrampoline,
|
||||
};
|
||||
|
||||
struct TrampolineState<F> {
|
||||
@@ -131,7 +132,7 @@ pub unsafe fn create_raw_function(
|
||||
imports: Imports::default(),
|
||||
shared_signatures: sig.into(),
|
||||
host_state,
|
||||
store: None,
|
||||
store: StorePtr::empty(),
|
||||
wasm_data: &[],
|
||||
})?,
|
||||
)
|
||||
|
||||
@@ -300,7 +300,6 @@ impl ResourceLimiter for MemoryContext {
|
||||
self.wasm_memory_used = desired;
|
||||
true
|
||||
}
|
||||
|
||||
fn table_growing(&mut self, _current: u32, _desired: u32, _maximum: Option<u32>) -> bool {
|
||||
true
|
||||
}
|
||||
@@ -401,11 +400,9 @@ impl ResourceLimiter for MemoryGrowFailureDetector {
|
||||
self.desired = desired;
|
||||
true
|
||||
}
|
||||
|
||||
fn memory_grow_failed(&mut self, err: &anyhow::Error) {
|
||||
self.error = Some(err.to_string());
|
||||
}
|
||||
|
||||
fn table_growing(&mut self, _current: u32, _desired: u32, _maximum: Option<u32>) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
@@ -311,12 +311,16 @@ fn massive_64_bit_still_limited() -> Result<()> {
|
||||
}
|
||||
|
||||
impl ResourceLimiter for MyLimiter {
|
||||
fn memory_growing(&mut self, _request: usize, _min: usize, _max: Option<usize>) -> bool {
|
||||
fn memory_growing(
|
||||
&mut self,
|
||||
_current: usize,
|
||||
_request: usize,
|
||||
_max: Option<usize>,
|
||||
) -> bool {
|
||||
self.hit = true;
|
||||
true
|
||||
}
|
||||
|
||||
fn table_growing(&mut self, _request: u32, _min: u32, _max: Option<u32>) -> bool {
|
||||
fn table_growing(&mut self, _current: u32, _request: u32, _max: Option<u32>) -> bool {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,11 +18,9 @@ impl ResourceLimiter for MemoryGrowFailureDetector {
|
||||
self.desired = desired;
|
||||
true
|
||||
}
|
||||
|
||||
fn memory_grow_failed(&mut self, err: &anyhow::Error) {
|
||||
self.error = Some(err.to_string());
|
||||
}
|
||||
|
||||
fn table_growing(&mut self, _current: u32, _desired: u32, _maximum: Option<u32>) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user