Merge pull request #3393 from bytecodealliance/pch/async_limiting
Add ResourceLimiterAsync, which can yield until resource is available
This commit is contained in:
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -108,9 +108,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.50"
|
version = "0.1.51"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b98e84bbb4cbcdd97da190ba0c58a1bb0de2c1fdf67d159e192ed766aeca722"
|
checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -3362,6 +3362,7 @@ name = "wasmtime"
|
|||||||
version = "0.30.0"
|
version = "0.30.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"async-trait",
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"bincode",
|
"bincode",
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
@@ -3459,6 +3460,7 @@ name = "wasmtime-cli"
|
|||||||
version = "0.30.0"
|
version = "0.30.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"async-trait",
|
||||||
"criterion",
|
"criterion",
|
||||||
"env_logger 0.8.3",
|
"env_logger 0.8.3",
|
||||||
"file-per-thread-logger",
|
"file-per-thread-logger",
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ criterion = "0.3.4"
|
|||||||
num_cpus = "1.13.0"
|
num_cpus = "1.13.0"
|
||||||
winapi = { version = "0.3.9", features = ['memoryapi'] }
|
winapi = { version = "0.3.9", features = ['memoryapi'] }
|
||||||
memchr = "2.4"
|
memchr = "2.4"
|
||||||
|
async-trait = "0.1"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
anyhow = "1.0.19"
|
anyhow = "1.0.19"
|
||||||
|
|||||||
@@ -33,86 +33,6 @@ mod allocator;
|
|||||||
|
|
||||||
pub use 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
|
/// A type that roughly corresponds to a WebAssembly instance, but is also used
|
||||||
/// for host-defined objects.
|
/// for host-defined objects.
|
||||||
///
|
///
|
||||||
@@ -429,7 +349,11 @@ impl Instance {
|
|||||||
/// Returns `None` if memory can't be grown by the specified amount
|
/// Returns `None` if memory can't be grown by the specified amount
|
||||||
/// of pages. Returns `Some` with the old size in bytes if growth was
|
/// of pages. Returns `Some` with the old size in bytes if growth was
|
||||||
/// successful.
|
/// successful.
|
||||||
pub(crate) fn memory_grow(&mut self, index: MemoryIndex, delta: u64) -> Option<usize> {
|
pub(crate) fn memory_grow(
|
||||||
|
&mut self,
|
||||||
|
index: MemoryIndex,
|
||||||
|
delta: u64,
|
||||||
|
) -> Result<Option<usize>, Error> {
|
||||||
let (idx, instance) = if let Some(idx) = self.module.defined_memory_index(index) {
|
let (idx, instance) = if let Some(idx) = self.module.defined_memory_index(index) {
|
||||||
(idx, self)
|
(idx, self)
|
||||||
} else {
|
} else {
|
||||||
@@ -441,10 +365,10 @@ impl Instance {
|
|||||||
(foreign_memory_index, foreign_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 memory = &mut instance.memories[idx];
|
||||||
|
|
||||||
let result = unsafe { memory.grow(delta, limiter) };
|
let result = unsafe { memory.grow(delta, store) };
|
||||||
let vmmemory = memory.vmmemory();
|
let vmmemory = memory.vmmemory();
|
||||||
|
|
||||||
// Update the state used by wasm code in case the base pointer and/or
|
// Update the state used by wasm code in case the base pointer and/or
|
||||||
@@ -468,7 +392,7 @@ impl Instance {
|
|||||||
table_index: TableIndex,
|
table_index: TableIndex,
|
||||||
delta: u32,
|
delta: u32,
|
||||||
init_value: TableElement,
|
init_value: TableElement,
|
||||||
) -> Option<u32> {
|
) -> Result<Option<u32>, Error> {
|
||||||
let (defined_table_index, instance) =
|
let (defined_table_index, instance) =
|
||||||
self.get_defined_table_index_and_instance(table_index);
|
self.get_defined_table_index_and_instance(table_index);
|
||||||
instance.defined_table_grow(defined_table_index, delta, init_value)
|
instance.defined_table_grow(defined_table_index, delta, init_value)
|
||||||
@@ -479,14 +403,14 @@ impl Instance {
|
|||||||
table_index: DefinedTableIndex,
|
table_index: DefinedTableIndex,
|
||||||
delta: u32,
|
delta: u32,
|
||||||
init_value: TableElement,
|
init_value: TableElement,
|
||||||
) -> Option<u32> {
|
) -> Result<Option<u32>, Error> {
|
||||||
let limiter = unsafe { (*self.store()).limiter() };
|
let store = unsafe { &mut *self.store() };
|
||||||
let table = self
|
let table = self
|
||||||
.tables
|
.tables
|
||||||
.get_mut(table_index)
|
.get_mut(table_index)
|
||||||
.unwrap_or_else(|| panic!("no table for index {}", table_index.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
|
// Keep the `VMContext` pointers used by compiled Wasm code up to
|
||||||
// date.
|
// date.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::imports::Imports;
|
use crate::imports::Imports;
|
||||||
use crate::instance::{Instance, InstanceHandle, ResourceLimiter, RuntimeMemoryCreator};
|
use crate::instance::{Instance, InstanceHandle, RuntimeMemoryCreator};
|
||||||
use crate::memory::{DefaultMemoryCreator, Memory};
|
use crate::memory::{DefaultMemoryCreator, Memory};
|
||||||
use crate::table::Table;
|
use crate::table::Table;
|
||||||
use crate::traphandlers::Trap;
|
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
|
/// 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
|
/// itself when it gets called as a host function, and additionally so this
|
||||||
/// runtime can access internals as necessary (such as the
|
/// 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.
|
/// 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.
|
/// 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
|
/// We use a number of `PhantomPinned` declarations to indicate this to the
|
||||||
/// compiler. More info on this in `wasmtime/src/store.rs`
|
/// 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
|
/// 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
|
/// will be allocated. The `Module` given here has active/passive data
|
||||||
@@ -77,6 +77,35 @@ pub struct InstanceAllocationRequest<'a> {
|
|||||||
pub wasm_data: *const [u8],
|
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))
|
||||||
|
}
|
||||||
|
/// The raw contents of this struct
|
||||||
|
pub fn as_raw(&self) -> Option<*mut dyn Store> {
|
||||||
|
self.0.clone()
|
||||||
|
}
|
||||||
|
/// Use the StorePtr as a mut ref to the Store.
|
||||||
|
/// Safety: must not be used outside the original lifetime of the borrow.
|
||||||
|
pub(crate) unsafe fn get(&mut self) -> Option<&mut dyn Store> {
|
||||||
|
match self.0 {
|
||||||
|
Some(ptr) => Some(&mut *ptr),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An link error while instantiating a module.
|
/// An link error while instantiating a module.
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
#[error("Link error: {0}")]
|
#[error("Link error: {0}")]
|
||||||
@@ -430,7 +459,7 @@ fn initialize_instance(
|
|||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn initialize_vmcontext(instance: &mut Instance, req: InstanceAllocationRequest) {
|
unsafe fn initialize_vmcontext(instance: &mut Instance, req: InstanceAllocationRequest) {
|
||||||
if let Some(store) = req.store {
|
if let Some(store) = req.store.as_raw() {
|
||||||
*instance.interrupts() = (*store).vminterrupts();
|
*instance.interrupts() = (*store).vminterrupts();
|
||||||
*instance.externref_activations_table() = (*store).externref_activations_table().0;
|
*instance.externref_activations_table() = (*store).externref_activations_table().0;
|
||||||
instance.set_store(store);
|
instance.set_store(store);
|
||||||
@@ -581,17 +610,6 @@ pub struct OnDemandInstanceAllocator {
|
|||||||
stack_size: usize,
|
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 {
|
impl OnDemandInstanceAllocator {
|
||||||
/// Creates a new on-demand instance allocator.
|
/// Creates a new on-demand instance allocator.
|
||||||
pub fn new(mem_creator: Option<Arc<dyn RuntimeMemoryCreator>>, stack_size: usize) -> Self {
|
pub fn new(mem_creator: Option<Arc<dyn RuntimeMemoryCreator>>, stack_size: usize) -> Self {
|
||||||
@@ -605,15 +623,19 @@ impl OnDemandInstanceAllocator {
|
|||||||
|
|
||||||
fn create_tables(
|
fn create_tables(
|
||||||
module: &Module,
|
module: &Module,
|
||||||
mut limiter: Option<&mut dyn ResourceLimiter>,
|
store: &mut StorePtr,
|
||||||
) -> Result<PrimaryMap<DefinedTableIndex, Table>, InstantiationError> {
|
) -> Result<PrimaryMap<DefinedTableIndex, Table>, InstantiationError> {
|
||||||
let num_imports = module.num_imported_tables;
|
let num_imports = module.num_imported_tables;
|
||||||
let mut tables: PrimaryMap<DefinedTableIndex, _> =
|
let mut tables: PrimaryMap<DefinedTableIndex, _> =
|
||||||
PrimaryMap::with_capacity(module.table_plans.len() - num_imports);
|
PrimaryMap::with_capacity(module.table_plans.len() - num_imports);
|
||||||
for table in &module.table_plans.values().as_slice()[num_imports..] {
|
for table in &module.table_plans.values().as_slice()[num_imports..] {
|
||||||
tables.push(
|
tables.push(
|
||||||
Table::new_dynamic(table, borrow_limiter(&mut limiter))
|
Table::new_dynamic(table, unsafe {
|
||||||
.map_err(InstantiationError::Resource)?,
|
store
|
||||||
|
.get()
|
||||||
|
.expect("if module has table plans, store is not empty")
|
||||||
|
})
|
||||||
|
.map_err(InstantiationError::Resource)?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Ok(tables)
|
Ok(tables)
|
||||||
@@ -622,7 +644,7 @@ impl OnDemandInstanceAllocator {
|
|||||||
fn create_memories(
|
fn create_memories(
|
||||||
&self,
|
&self,
|
||||||
module: &Module,
|
module: &Module,
|
||||||
mut limiter: Option<&mut dyn ResourceLimiter>,
|
store: &mut StorePtr,
|
||||||
) -> Result<PrimaryMap<DefinedMemoryIndex, Memory>, InstantiationError> {
|
) -> Result<PrimaryMap<DefinedMemoryIndex, Memory>, InstantiationError> {
|
||||||
let creator = self
|
let creator = self
|
||||||
.mem_creator
|
.mem_creator
|
||||||
@@ -633,8 +655,12 @@ impl OnDemandInstanceAllocator {
|
|||||||
PrimaryMap::with_capacity(module.memory_plans.len() - num_imports);
|
PrimaryMap::with_capacity(module.memory_plans.len() - num_imports);
|
||||||
for plan in &module.memory_plans.values().as_slice()[num_imports..] {
|
for plan in &module.memory_plans.values().as_slice()[num_imports..] {
|
||||||
memories.push(
|
memories.push(
|
||||||
Memory::new_dynamic(plan, creator, borrow_limiter(&mut limiter))
|
Memory::new_dynamic(plan, creator, unsafe {
|
||||||
.map_err(InstantiationError::Resource)?,
|
store
|
||||||
|
.get()
|
||||||
|
.expect("if module has memory plans, store is not empty")
|
||||||
|
})
|
||||||
|
.map_err(InstantiationError::Resource)?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Ok(memories)
|
Ok(memories)
|
||||||
@@ -656,9 +682,8 @@ unsafe impl InstanceAllocator for OnDemandInstanceAllocator {
|
|||||||
&self,
|
&self,
|
||||||
mut req: InstanceAllocationRequest,
|
mut req: InstanceAllocationRequest,
|
||||||
) -> Result<InstanceHandle, InstantiationError> {
|
) -> Result<InstanceHandle, InstantiationError> {
|
||||||
let mut limiter = req.store.and_then(|s| (*s).limiter());
|
let memories = self.create_memories(&req.module, &mut req.store)?;
|
||||||
let memories = self.create_memories(&req.module, borrow_limiter(&mut limiter))?;
|
let tables = Self::create_tables(&req.module, &mut req.store)?;
|
||||||
let tables = Self::create_tables(&req.module, borrow_limiter(&mut limiter))?;
|
|
||||||
|
|
||||||
let host_state = std::mem::replace(&mut req.host_state, Box::new(()));
|
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
|
//! Using the pooling instance allocator can speed up module instantiation
|
||||||
//! when modules can be constrained based on configurable limits.
|
//! when modules can be constrained based on configurable limits.
|
||||||
|
|
||||||
use super::borrow_limiter;
|
|
||||||
use super::{
|
use super::{
|
||||||
initialize_instance, initialize_vmcontext, InstanceAllocationRequest, InstanceAllocator,
|
initialize_instance, initialize_vmcontext, InstanceAllocationRequest, InstanceAllocator,
|
||||||
InstanceHandle, InstantiationError, ResourceLimiter,
|
InstanceHandle, InstantiationError,
|
||||||
};
|
};
|
||||||
use crate::{instance::Instance, Memory, Mmap, Table, VMContext};
|
use crate::{instance::Instance, Memory, Mmap, Table, VMContext};
|
||||||
use anyhow::{anyhow, bail, Context, Result};
|
use anyhow::{anyhow, bail, Context, Result};
|
||||||
@@ -385,19 +384,22 @@ impl InstancePool {
|
|||||||
instance.host_state = std::mem::replace(&mut req.host_state, Box::new(()));
|
instance.host_state = std::mem::replace(&mut req.host_state, Box::new(()));
|
||||||
instance.wasm_data = &*req.wasm_data;
|
instance.wasm_data = &*req.wasm_data;
|
||||||
|
|
||||||
let mut limiter = req.store.and_then(|s| (*s).limiter());
|
// set_instance_memories and _tables will need the store before we can completely
|
||||||
|
// initialize the vmcontext.
|
||||||
|
if let Some(store) = req.store.as_raw() {
|
||||||
|
instance.set_store(store);
|
||||||
|
}
|
||||||
|
|
||||||
Self::set_instance_memories(
|
Self::set_instance_memories(
|
||||||
instance,
|
instance,
|
||||||
self.memories.get(index),
|
self.memories.get(index),
|
||||||
self.memories.max_wasm_pages,
|
self.memories.max_wasm_pages,
|
||||||
borrow_limiter(&mut limiter),
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Self::set_instance_tables(
|
Self::set_instance_tables(
|
||||||
instance,
|
instance,
|
||||||
self.tables.get(index).map(|x| x as *mut usize),
|
self.tables.get(index).map(|x| x as *mut usize),
|
||||||
self.tables.max_elements,
|
self.tables.max_elements,
|
||||||
borrow_limiter(&mut limiter),
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
initialize_vmcontext(instance, req);
|
initialize_vmcontext(instance, req);
|
||||||
@@ -503,7 +505,6 @@ impl InstancePool {
|
|||||||
instance: &mut Instance,
|
instance: &mut Instance,
|
||||||
mut memories: impl Iterator<Item = *mut u8>,
|
mut memories: impl Iterator<Item = *mut u8>,
|
||||||
max_pages: u64,
|
max_pages: u64,
|
||||||
mut limiter: Option<&mut dyn ResourceLimiter>,
|
|
||||||
) -> Result<(), InstantiationError> {
|
) -> Result<(), InstantiationError> {
|
||||||
let module = instance.module.as_ref();
|
let module = instance.module.as_ref();
|
||||||
|
|
||||||
@@ -519,12 +520,9 @@ impl InstancePool {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
instance.memories.push(
|
instance.memories.push(
|
||||||
Memory::new_static(
|
Memory::new_static(plan, memory, commit_memory_pages, unsafe {
|
||||||
plan,
|
&mut *instance.store()
|
||||||
memory,
|
})
|
||||||
commit_memory_pages,
|
|
||||||
borrow_limiter(&mut limiter),
|
|
||||||
)
|
|
||||||
.map_err(InstantiationError::Resource)?,
|
.map_err(InstantiationError::Resource)?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -538,7 +536,6 @@ impl InstancePool {
|
|||||||
instance: &mut Instance,
|
instance: &mut Instance,
|
||||||
mut tables: impl Iterator<Item = *mut usize>,
|
mut tables: impl Iterator<Item = *mut usize>,
|
||||||
max_elements: u32,
|
max_elements: u32,
|
||||||
mut limiter: Option<&mut dyn ResourceLimiter>,
|
|
||||||
) -> Result<(), InstantiationError> {
|
) -> Result<(), InstantiationError> {
|
||||||
let module = instance.module.as_ref();
|
let module = instance.module.as_ref();
|
||||||
|
|
||||||
@@ -555,7 +552,7 @@ impl InstancePool {
|
|||||||
|
|
||||||
let table = unsafe { std::slice::from_raw_parts_mut(base, max_elements as usize) };
|
let table = unsafe { std::slice::from_raw_parts_mut(base, max_elements as usize) };
|
||||||
instance.tables.push(
|
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)?,
|
.map_err(InstantiationError::Resource)?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1052,7 +1049,7 @@ unsafe impl InstanceAllocator for PoolingInstanceAllocator {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{Imports, VMSharedSignatureIndex};
|
use crate::{Imports, StorePtr, VMSharedSignatureIndex};
|
||||||
use wasmtime_environ::{
|
use wasmtime_environ::{
|
||||||
EntityRef, Global, GlobalInit, Memory, MemoryPlan, ModuleType, SignatureIndex, Table,
|
EntityRef, Global, GlobalInit, Memory, MemoryPlan, ModuleType, SignatureIndex, Table,
|
||||||
TablePlan, TableStyle, WasmType,
|
TablePlan, TableStyle, WasmType,
|
||||||
@@ -1414,7 +1411,7 @@ mod test {
|
|||||||
},
|
},
|
||||||
shared_signatures: VMSharedSignatureIndex::default().into(),
|
shared_signatures: VMSharedSignatureIndex::default().into(),
|
||||||
host_state: Box::new(()),
|
host_state: Box::new(()),
|
||||||
store: None,
|
store: StorePtr::empty(),
|
||||||
wasm_data: &[],
|
wasm_data: &[],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -1438,7 +1435,7 @@ mod test {
|
|||||||
},
|
},
|
||||||
shared_signatures: VMSharedSignatureIndex::default().into(),
|
shared_signatures: VMSharedSignatureIndex::default().into(),
|
||||||
host_state: Box::new(()),
|
host_state: Box::new(()),
|
||||||
store: None,
|
store: StorePtr::empty(),
|
||||||
wasm_data: &[],
|
wasm_data: &[],
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -436,7 +436,7 @@ mod test {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
Imports, InstanceAllocationRequest, InstanceLimits, ModuleLimits,
|
Imports, InstanceAllocationRequest, InstanceLimits, ModuleLimits,
|
||||||
PoolingAllocationStrategy, VMSharedSignatureIndex,
|
PoolingAllocationStrategy, Store, StorePtr, VMSharedSignatureIndex,
|
||||||
};
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use wasmtime_environ::{Memory, MemoryPlan, MemoryStyle, Module, PrimaryMap, Tunables};
|
use wasmtime_environ::{Memory, MemoryPlan, MemoryStyle, Module, PrimaryMap, Tunables};
|
||||||
@@ -506,6 +506,58 @@ mod test {
|
|||||||
|
|
||||||
module_limits.validate(&module).expect("should validate");
|
module_limits.validate(&module).expect("should validate");
|
||||||
|
|
||||||
|
// An InstanceAllocationRequest with a module must also have
|
||||||
|
// a non-null StorePtr. Here we mock just enough of a store
|
||||||
|
// to satisfy this test.
|
||||||
|
struct MockStore {
|
||||||
|
table: crate::VMExternRefActivationsTable,
|
||||||
|
info: MockModuleInfo,
|
||||||
|
}
|
||||||
|
unsafe impl Store for MockStore {
|
||||||
|
fn vminterrupts(&self) -> *mut crate::VMInterrupts {
|
||||||
|
std::ptr::null_mut()
|
||||||
|
}
|
||||||
|
fn externref_activations_table(
|
||||||
|
&mut self,
|
||||||
|
) -> (
|
||||||
|
&mut crate::VMExternRefActivationsTable,
|
||||||
|
&dyn crate::ModuleInfoLookup,
|
||||||
|
) {
|
||||||
|
(&mut self.table, &self.info)
|
||||||
|
}
|
||||||
|
fn memory_growing(
|
||||||
|
&mut self,
|
||||||
|
_current: usize,
|
||||||
|
_desired: usize,
|
||||||
|
_maximum: Option<usize>,
|
||||||
|
) -> Result<bool, anyhow::Error> {
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
fn memory_grow_failed(&mut self, _error: &anyhow::Error) {}
|
||||||
|
fn table_growing(
|
||||||
|
&mut self,
|
||||||
|
_current: u32,
|
||||||
|
_desired: u32,
|
||||||
|
_maximum: Option<u32>,
|
||||||
|
) -> Result<bool, anyhow::Error> {
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
fn table_grow_failed(&mut self, _error: &anyhow::Error) {}
|
||||||
|
fn out_of_gas(&mut self) -> Result<(), anyhow::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
struct MockModuleInfo;
|
||||||
|
impl crate::ModuleInfoLookup for MockModuleInfo {
|
||||||
|
fn lookup(&self, _pc: usize) -> Option<Arc<dyn crate::ModuleInfo>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut mock_store = MockStore {
|
||||||
|
table: crate::VMExternRefActivationsTable::new(),
|
||||||
|
info: MockModuleInfo,
|
||||||
|
};
|
||||||
|
|
||||||
let mut handles = Vec::new();
|
let mut handles = Vec::new();
|
||||||
let module = Arc::new(module);
|
let module = Arc::new(module);
|
||||||
let functions = &PrimaryMap::new();
|
let functions = &PrimaryMap::new();
|
||||||
@@ -528,7 +580,7 @@ mod test {
|
|||||||
},
|
},
|
||||||
shared_signatures: VMSharedSignatureIndex::default().into(),
|
shared_signatures: VMSharedSignatureIndex::default().into(),
|
||||||
host_state: Box::new(()),
|
host_state: Box::new(()),
|
||||||
store: None,
|
store: StorePtr::new(&mut mock_store),
|
||||||
wasm_data: &[],
|
wasm_data: &[],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
|
|
||||||
use std::error::Error;
|
use anyhow::Error;
|
||||||
|
|
||||||
mod export;
|
mod export;
|
||||||
mod externref;
|
mod externref;
|
||||||
@@ -42,8 +42,7 @@ pub use crate::imports::Imports;
|
|||||||
pub use crate::instance::{
|
pub use crate::instance::{
|
||||||
InstanceAllocationRequest, InstanceAllocator, InstanceHandle, InstanceLimits,
|
InstanceAllocationRequest, InstanceAllocator, InstanceHandle, InstanceLimits,
|
||||||
InstantiationError, LinkError, ModuleLimits, OnDemandInstanceAllocator,
|
InstantiationError, LinkError, ModuleLimits, OnDemandInstanceAllocator,
|
||||||
PoolingAllocationStrategy, PoolingInstanceAllocator, ResourceLimiter, DEFAULT_INSTANCE_LIMIT,
|
PoolingAllocationStrategy, PoolingInstanceAllocator, StorePtr,
|
||||||
DEFAULT_MEMORY_LIMIT, DEFAULT_TABLE_LIMIT,
|
|
||||||
};
|
};
|
||||||
pub use crate::jit_int::GdbJitImageRegistration;
|
pub use crate::jit_int::GdbJitImageRegistration;
|
||||||
pub use crate::memory::{Memory, RuntimeLinearMemory, RuntimeMemoryCreator};
|
pub use crate::memory::{Memory, RuntimeLinearMemory, RuntimeMemoryCreator};
|
||||||
@@ -91,11 +90,30 @@ pub unsafe trait Store {
|
|||||||
&mut self,
|
&mut self,
|
||||||
) -> (&mut VMExternRefActivationsTable, &dyn ModuleInfoLookup);
|
) -> (&mut VMExternRefActivationsTable, &dyn ModuleInfoLookup);
|
||||||
|
|
||||||
/// Returns a reference to the store's limiter for limiting resources, if any.
|
/// Callback invoked to allow the store's resource limiter to reject a
|
||||||
fn limiter(&mut self) -> Option<&mut dyn ResourceLimiter>;
|
/// memory grow operation.
|
||||||
|
fn memory_growing(
|
||||||
|
&mut self,
|
||||||
|
current: usize,
|
||||||
|
desired: usize,
|
||||||
|
maximum: Option<usize>,
|
||||||
|
) -> Result<bool, Error>;
|
||||||
|
/// Callback invoked to notify the store's resource limiter that a memory
|
||||||
|
/// grow operation has failed.
|
||||||
|
fn memory_grow_failed(&mut self, error: &Error);
|
||||||
|
/// Callback invoked to allow the store's resource limiter to reject a
|
||||||
|
/// table grow operation.
|
||||||
|
fn table_growing(
|
||||||
|
&mut self,
|
||||||
|
current: u32,
|
||||||
|
desired: u32,
|
||||||
|
maximum: Option<u32>,
|
||||||
|
) -> Result<bool, Error>;
|
||||||
|
/// Callback invoked to notify the store's resource limiter that a table
|
||||||
|
/// grow operation has failed.
|
||||||
|
fn table_grow_failed(&mut self, error: &Error);
|
||||||
/// Callback invoked whenever fuel runs out by a wasm instance. If an error
|
/// 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
|
/// is returned that's raised as a trap. Otherwise wasm execution will
|
||||||
/// continue as normal.
|
/// continue as normal.
|
||||||
fn out_of_gas(&mut self) -> Result<(), Box<dyn Error + Send + Sync>>;
|
fn out_of_gas(&mut self) -> Result<(), Error>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
use crate::externref::VMExternRef;
|
use crate::externref::VMExternRef;
|
||||||
use crate::instance::Instance;
|
use crate::instance::Instance;
|
||||||
use crate::table::{Table, TableElementType};
|
use crate::table::{Table, TableElementType};
|
||||||
use crate::traphandlers::{raise_lib_trap, Trap};
|
use crate::traphandlers::{raise_lib_trap, resume_panic, Trap};
|
||||||
use crate::vmcontext::{VMCallerCheckedAnyfunc, VMContext};
|
use crate::vmcontext::{VMCallerCheckedAnyfunc, VMContext};
|
||||||
use backtrace::Backtrace;
|
use backtrace::Backtrace;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
@@ -190,11 +190,17 @@ pub unsafe extern "C" fn wasmtime_memory32_grow(
|
|||||||
delta: u64,
|
delta: u64,
|
||||||
memory_index: u32,
|
memory_index: u32,
|
||||||
) -> usize {
|
) -> usize {
|
||||||
let instance = (*vmctx).instance_mut();
|
// Memory grow can invoke user code provided in a ResourceLimiter{,Async},
|
||||||
let memory_index = MemoryIndex::from_u32(memory_index);
|
// so we need to catch a possible panic
|
||||||
match instance.memory_grow(memory_index, delta) {
|
match std::panic::catch_unwind(|| {
|
||||||
Some(size_in_bytes) => size_in_bytes / (wasmtime_environ::WASM_PAGE_SIZE as usize),
|
let instance = (*vmctx).instance_mut();
|
||||||
None => usize::max_value(),
|
let memory_index = MemoryIndex::from_u32(memory_index);
|
||||||
|
instance.memory_grow(memory_index, delta)
|
||||||
|
}) {
|
||||||
|
Ok(Ok(Some(size_in_bytes))) => size_in_bytes / (wasmtime_environ::WASM_PAGE_SIZE as usize),
|
||||||
|
Ok(Ok(None)) => usize::max_value(),
|
||||||
|
Ok(Err(err)) => crate::traphandlers::raise_user_trap(err),
|
||||||
|
Err(p) => resume_panic(p),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,22 +213,29 @@ pub unsafe extern "C" fn wasmtime_table_grow(
|
|||||||
// or is a `VMExternRef` until we look at the table type.
|
// or is a `VMExternRef` until we look at the table type.
|
||||||
init_value: *mut u8,
|
init_value: *mut u8,
|
||||||
) -> u32 {
|
) -> u32 {
|
||||||
let instance = (*vmctx).instance_mut();
|
// Table grow can invoke user code provided in a ResourceLimiter{,Async},
|
||||||
let table_index = TableIndex::from_u32(table_index);
|
// so we need to catch a possible panic
|
||||||
let element = match instance.table_element_type(table_index) {
|
match std::panic::catch_unwind(|| {
|
||||||
TableElementType::Func => (init_value as *mut VMCallerCheckedAnyfunc).into(),
|
let instance = (*vmctx).instance_mut();
|
||||||
TableElementType::Extern => {
|
let table_index = TableIndex::from_u32(table_index);
|
||||||
let init_value = if init_value.is_null() {
|
let element = match instance.table_element_type(table_index) {
|
||||||
None
|
TableElementType::Func => (init_value as *mut VMCallerCheckedAnyfunc).into(),
|
||||||
} else {
|
TableElementType::Extern => {
|
||||||
Some(VMExternRef::clone_from_raw(init_value))
|
let init_value = if init_value.is_null() {
|
||||||
};
|
None
|
||||||
init_value.into()
|
} else {
|
||||||
}
|
Some(VMExternRef::clone_from_raw(init_value))
|
||||||
};
|
};
|
||||||
instance
|
init_value.into()
|
||||||
.table_grow(table_index, delta, element)
|
}
|
||||||
.unwrap_or(-1_i32 as u32)
|
};
|
||||||
|
instance.table_grow(table_index, delta, element)
|
||||||
|
}) {
|
||||||
|
Ok(Ok(Some(r))) => r,
|
||||||
|
Ok(Ok(None)) => -1_i32 as u32,
|
||||||
|
Ok(Err(err)) => crate::traphandlers::raise_user_trap(err),
|
||||||
|
Err(p) => resume_panic(p),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implementation of `table.fill`.
|
/// Implementation of `table.fill`.
|
||||||
@@ -436,15 +449,6 @@ pub unsafe extern "C" fn wasmtime_externref_global_set(
|
|||||||
drop(old);
|
drop(old);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct Unimplemented(&'static str);
|
|
||||||
impl std::error::Error for Unimplemented {}
|
|
||||||
impl std::fmt::Display for Unimplemented {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
|
||||||
write!(f, "unimplemented: {}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Implementation of `memory.atomic.notify` for locally defined memories.
|
/// Implementation of `memory.atomic.notify` for locally defined memories.
|
||||||
pub unsafe extern "C" fn wasmtime_memory_atomic_notify(
|
pub unsafe extern "C" fn wasmtime_memory_atomic_notify(
|
||||||
vmctx: *mut VMContext,
|
vmctx: *mut VMContext,
|
||||||
@@ -460,9 +464,9 @@ pub unsafe extern "C" fn wasmtime_memory_atomic_notify(
|
|||||||
// just to be sure.
|
// just to be sure.
|
||||||
let addr_to_check = addr.checked_add(4).unwrap();
|
let addr_to_check = addr.checked_add(4).unwrap();
|
||||||
validate_atomic_addr(instance, memory, addr_to_check).and_then(|()| {
|
validate_atomic_addr(instance, memory, addr_to_check).and_then(|()| {
|
||||||
Err(Trap::User(Box::new(Unimplemented(
|
Err(Trap::User(anyhow::anyhow!(
|
||||||
"wasm atomics (fn wasmtime_memory_atomic_notify) unsupported",
|
"unimplemented: wasm atomics (fn wasmtime_memory_atomic_notify) unsupported",
|
||||||
))))
|
)))
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
match result {
|
match result {
|
||||||
@@ -486,9 +490,9 @@ pub unsafe extern "C" fn wasmtime_memory_atomic_wait32(
|
|||||||
// but we still double-check
|
// but we still double-check
|
||||||
let addr_to_check = addr.checked_add(4).unwrap();
|
let addr_to_check = addr.checked_add(4).unwrap();
|
||||||
validate_atomic_addr(instance, memory, addr_to_check).and_then(|()| {
|
validate_atomic_addr(instance, memory, addr_to_check).and_then(|()| {
|
||||||
Err(Trap::User(Box::new(Unimplemented(
|
Err(Trap::User(anyhow::anyhow!(
|
||||||
"wasm atomics (fn wasmtime_memory_atomic_wait32) unsupported",
|
"unimplemented: wasm atomics (fn wasmtime_memory_atomic_wait32) unsupported",
|
||||||
))))
|
)))
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
match result {
|
match result {
|
||||||
@@ -512,9 +516,9 @@ pub unsafe extern "C" fn wasmtime_memory_atomic_wait64(
|
|||||||
// but we still double-check
|
// but we still double-check
|
||||||
let addr_to_check = addr.checked_add(8).unwrap();
|
let addr_to_check = addr.checked_add(8).unwrap();
|
||||||
validate_atomic_addr(instance, memory, addr_to_check).and_then(|()| {
|
validate_atomic_addr(instance, memory, addr_to_check).and_then(|()| {
|
||||||
Err(Trap::User(Box::new(Unimplemented(
|
Err(Trap::User(anyhow::anyhow!(
|
||||||
"wasm atomics (fn wasmtime_memory_atomic_wait64) unsupported",
|
"unimplemented: wasm atomics (fn wasmtime_memory_atomic_wait64) unsupported",
|
||||||
))))
|
)))
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
match result {
|
match result {
|
||||||
|
|||||||
@@ -4,8 +4,9 @@
|
|||||||
|
|
||||||
use crate::mmap::Mmap;
|
use crate::mmap::Mmap;
|
||||||
use crate::vmcontext::VMMemoryDefinition;
|
use crate::vmcontext::VMMemoryDefinition;
|
||||||
use crate::ResourceLimiter;
|
use crate::Store;
|
||||||
use anyhow::{bail, format_err, Error, Result};
|
use anyhow::Error;
|
||||||
|
use anyhow::{bail, format_err, Result};
|
||||||
use more_asserts::{assert_ge, assert_le};
|
use more_asserts::{assert_ge, assert_le};
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use wasmtime_environ::{MemoryPlan, MemoryStyle, WASM32_MAX_PAGES, WASM64_MAX_PAGES};
|
use wasmtime_environ::{MemoryPlan, MemoryStyle, WASM32_MAX_PAGES, WASM64_MAX_PAGES};
|
||||||
@@ -212,33 +213,14 @@ pub enum Memory {
|
|||||||
Dynamic(Box<dyn RuntimeLinearMemory>),
|
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 {
|
impl Memory {
|
||||||
/// Create a new dynamic (movable) memory instance for the specified plan.
|
/// Create a new dynamic (movable) memory instance for the specified plan.
|
||||||
pub fn new_dynamic(
|
pub fn new_dynamic(
|
||||||
plan: &MemoryPlan,
|
plan: &MemoryPlan,
|
||||||
creator: &dyn RuntimeMemoryCreator,
|
creator: &dyn RuntimeMemoryCreator,
|
||||||
limiter: Option<&mut dyn ResourceLimiter>,
|
store: &mut dyn Store,
|
||||||
) -> Result<Self> {
|
) -> 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)?))
|
Ok(Memory::Dynamic(creator.new_memory(plan, minimum, maximum)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,9 +229,9 @@ impl Memory {
|
|||||||
plan: &MemoryPlan,
|
plan: &MemoryPlan,
|
||||||
base: &'static mut [u8],
|
base: &'static mut [u8],
|
||||||
make_accessible: fn(*mut u8, usize) -> Result<()>,
|
make_accessible: fn(*mut u8, usize) -> Result<()>,
|
||||||
limiter: Option<&mut dyn ResourceLimiter>,
|
store: &mut dyn Store,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let (minimum, maximum) = Self::limit_new(plan, limiter)?;
|
let (minimum, maximum) = Self::limit_new(plan, store)?;
|
||||||
|
|
||||||
let base = match maximum {
|
let base = match maximum {
|
||||||
Some(max) if max < base.len() => &mut base[..max],
|
Some(max) if max < base.len() => &mut base[..max],
|
||||||
@@ -269,15 +251,11 @@ impl Memory {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calls the `limiter`, if specified, to optionally prevent a memory from
|
/// Calls the `store`'s limiter to optionally prevent a memory from being allocated.
|
||||||
/// being allocated.
|
|
||||||
///
|
///
|
||||||
/// Returns the minimum size and optional maximum size of the memory, in
|
/// Returns the minimum size and optional maximum size of the memory, in
|
||||||
/// bytes.
|
/// bytes.
|
||||||
fn limit_new(
|
fn limit_new(plan: &MemoryPlan, store: &mut dyn Store) -> Result<(usize, Option<usize>)> {
|
||||||
plan: &MemoryPlan,
|
|
||||||
mut limiter: Option<&mut dyn ResourceLimiter>,
|
|
||||||
) -> Result<(usize, Option<usize>)> {
|
|
||||||
// Sanity-check what should already be true from wasm module validation.
|
// Sanity-check what should already be true from wasm module validation.
|
||||||
let absolute_max = if plan.memory.memory64 {
|
let absolute_max = if plan.memory.memory64 {
|
||||||
WASM64_MAX_PAGES
|
WASM64_MAX_PAGES
|
||||||
@@ -291,7 +269,7 @@ impl Memory {
|
|||||||
// allocate, which is our entire address space minus a wasm page. That
|
// allocate, which is our entire address space minus a wasm page. That
|
||||||
// shouldn't ever actually work in terms of an allocation because
|
// shouldn't ever actually work in terms of an allocation because
|
||||||
// presumably the kernel wants *something* for itself, but this is used
|
// 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
|
// to approximate the scale of the request that the wasm module is
|
||||||
// making. This is necessary because the limiter works on `usize` bytes
|
// making. This is necessary because the limiter works on `usize` bytes
|
||||||
// whereas we're working with possibly-overflowing `u64` calculations
|
// whereas we're working with possibly-overflowing `u64` calculations
|
||||||
@@ -302,7 +280,7 @@ impl Memory {
|
|||||||
|
|
||||||
// If the minimum memory size overflows the size of our own address
|
// 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
|
// 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.
|
// happening.
|
||||||
let minimum = plan
|
let minimum = plan
|
||||||
.memory
|
.memory
|
||||||
@@ -332,13 +310,13 @@ impl Memory {
|
|||||||
maximum = usize::try_from(1u64 << 32).ok();
|
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
|
// reject anything if necessary, and this also guarantees that we should
|
||||||
// call the limiter for all requested memories, even if our `minimum`
|
// call the limiter for all requested memories, even if our `minimum`
|
||||||
// calculation overflowed. This means that the `minimum` we're informing
|
// calculation overflowed. This means that the `minimum` we're informing
|
||||||
// the limiter is lossy and may not be 100% accurate, but for now the
|
// the limiter is lossy and may not be 100% accurate, but for now the
|
||||||
// expected uses of `limiter` means that's ok.
|
// expected uses of limiter means that's ok.
|
||||||
if !memory_growing(&mut limiter, 0, minimum.unwrap_or(absolute_max), maximum) {
|
if !store.memory_growing(0, minimum.unwrap_or(absolute_max), maximum)? {
|
||||||
bail!(
|
bail!(
|
||||||
"memory minimum size of {} pages exceeds memory limits",
|
"memory minimum size of {} pages exceeds memory limits",
|
||||||
plan.memory.minimum
|
plan.memory.minimum
|
||||||
@@ -400,15 +378,18 @@ impl Memory {
|
|||||||
///
|
///
|
||||||
/// Generally, prefer using `InstanceHandle::memory_grow`, which encapsulates
|
/// Generally, prefer using `InstanceHandle::memory_grow`, which encapsulates
|
||||||
/// this unsafety.
|
/// this unsafety.
|
||||||
|
///
|
||||||
|
/// Ensure that the provided Store is not used to get access any Memory
|
||||||
|
/// which lives inside it.
|
||||||
pub unsafe fn grow(
|
pub unsafe fn grow(
|
||||||
&mut self,
|
&mut self,
|
||||||
delta_pages: u64,
|
delta_pages: u64,
|
||||||
mut limiter: Option<&mut dyn ResourceLimiter>,
|
store: &mut dyn Store,
|
||||||
) -> Option<usize> {
|
) -> Result<Option<usize>, Error> {
|
||||||
let old_byte_size = self.byte_size();
|
let old_byte_size = self.byte_size();
|
||||||
// Wasm spec: when growing by 0 pages, always return the current size.
|
// Wasm spec: when growing by 0 pages, always return the current size.
|
||||||
if delta_pages == 0 {
|
if delta_pages == 0 {
|
||||||
return Some(old_byte_size);
|
return Ok(Some(old_byte_size));
|
||||||
}
|
}
|
||||||
|
|
||||||
// largest wasm-page-aligned region of memory it is possible to
|
// largest wasm-page-aligned region of memory it is possible to
|
||||||
@@ -428,16 +409,16 @@ impl Memory {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let maximum = self.maximum_byte_size();
|
let maximum = self.maximum_byte_size();
|
||||||
// Limiter gets first chance to reject memory_growing.
|
// Store limiter gets first chance to reject memory_growing.
|
||||||
if !memory_growing(&mut limiter, old_byte_size, new_byte_size, maximum) {
|
if !store.memory_growing(old_byte_size, new_byte_size, maximum)? {
|
||||||
return None;
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Never exceed maximum, even if limiter permitted it.
|
// Never exceed maximum, even if limiter permitted it.
|
||||||
if let Some(max) = maximum {
|
if let Some(max) = maximum {
|
||||||
if new_byte_size > max {
|
if new_byte_size > max {
|
||||||
memory_grow_failed(&mut limiter, &format_err!("Memory maximum size exceeded"));
|
store.memory_grow_failed(&format_err!("Memory maximum size exceeded"));
|
||||||
return None;
|
return Ok(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,7 +426,10 @@ impl Memory {
|
|||||||
{
|
{
|
||||||
if self.is_static() {
|
if self.is_static() {
|
||||||
// Reset any faulted guard pages before growing the memory.
|
// Reset any faulted guard pages before growing the memory.
|
||||||
self.reset_guard_pages().ok()?;
|
if let Err(e) = self.reset_guard_pages() {
|
||||||
|
store.memory_grow_failed(&e);
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -458,25 +442,28 @@ impl Memory {
|
|||||||
} => {
|
} => {
|
||||||
// Never exceed static memory size
|
// Never exceed static memory size
|
||||||
if new_byte_size > base.len() {
|
if new_byte_size > base.len() {
|
||||||
memory_grow_failed(&mut limiter, &format_err!("static memory size exceeded"));
|
store.memory_grow_failed(&format_err!("static memory size exceeded"));
|
||||||
return None;
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Operating system can fail to make memory accessible
|
// Operating system can fail to make memory accessible
|
||||||
let r = make_accessible(
|
if let Err(e) = make_accessible(
|
||||||
base.as_mut_ptr().add(old_byte_size),
|
base.as_mut_ptr().add(old_byte_size),
|
||||||
new_byte_size - old_byte_size,
|
new_byte_size - old_byte_size,
|
||||||
);
|
) {
|
||||||
r.map_err(|e| memory_grow_failed(&mut limiter, &e)).ok()?;
|
store.memory_grow_failed(&e);
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
*size = new_byte_size;
|
*size = new_byte_size;
|
||||||
}
|
}
|
||||||
Memory::Dynamic(mem) => {
|
Memory::Dynamic(mem) => {
|
||||||
let r = mem.grow_to(new_byte_size);
|
if let Err(e) = mem.grow_to(new_byte_size) {
|
||||||
r.map_err(|e| memory_grow_failed(&mut limiter, &e)).ok()?;
|
store.memory_grow_failed(&e);
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(old_byte_size)
|
Ok(Some(old_byte_size))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm code.
|
/// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm code.
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
//! `Table` is to WebAssembly tables what `LinearMemory` is to WebAssembly linear memories.
|
//! `Table` is to WebAssembly tables what `LinearMemory` is to WebAssembly linear memories.
|
||||||
|
|
||||||
use crate::vmcontext::{VMCallerCheckedAnyfunc, VMTableDefinition};
|
use crate::vmcontext::{VMCallerCheckedAnyfunc, VMTableDefinition};
|
||||||
use crate::{ResourceLimiter, Trap, VMExternRef};
|
use crate::{Store, Trap, VMExternRef};
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, format_err, Error, Result};
|
||||||
use std::convert::{TryFrom, TryInto};
|
use std::convert::{TryFrom, TryInto};
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
@@ -137,11 +137,8 @@ fn wasm_to_table_type(ty: WasmType) -> Result<TableElementType> {
|
|||||||
|
|
||||||
impl Table {
|
impl Table {
|
||||||
/// Create a new dynamic (movable) table instance for the specified table plan.
|
/// Create a new dynamic (movable) table instance for the specified table plan.
|
||||||
pub fn new_dynamic(
|
pub fn new_dynamic(plan: &TablePlan, store: &mut dyn Store) -> Result<Self> {
|
||||||
plan: &TablePlan,
|
Self::limit_new(plan, store)?;
|
||||||
limiter: Option<&mut dyn ResourceLimiter>,
|
|
||||||
) -> Result<Self> {
|
|
||||||
Self::limit_new(plan, limiter)?;
|
|
||||||
let elements = vec![0; plan.table.minimum as usize];
|
let elements = vec![0; plan.table.minimum as usize];
|
||||||
let ty = wasm_to_table_type(plan.table.wasm_ty)?;
|
let ty = wasm_to_table_type(plan.table.wasm_ty)?;
|
||||||
let maximum = plan.table.maximum;
|
let maximum = plan.table.maximum;
|
||||||
@@ -157,9 +154,9 @@ impl Table {
|
|||||||
pub fn new_static(
|
pub fn new_static(
|
||||||
plan: &TablePlan,
|
plan: &TablePlan,
|
||||||
data: &'static mut [usize],
|
data: &'static mut [usize],
|
||||||
limiter: Option<&mut dyn ResourceLimiter>,
|
store: &mut dyn Store,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
Self::limit_new(plan, limiter)?;
|
Self::limit_new(plan, store)?;
|
||||||
let size = plan.table.minimum;
|
let size = plan.table.minimum;
|
||||||
let ty = wasm_to_table_type(plan.table.wasm_ty)?;
|
let ty = wasm_to_table_type(plan.table.wasm_ty)?;
|
||||||
let data = match plan.table.maximum {
|
let data = match plan.table.maximum {
|
||||||
@@ -170,14 +167,12 @@ impl Table {
|
|||||||
Ok(Table::Static { data, size, ty })
|
Ok(Table::Static { data, size, ty })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn limit_new(plan: &TablePlan, limiter: Option<&mut dyn ResourceLimiter>) -> Result<()> {
|
fn limit_new(plan: &TablePlan, store: &mut dyn Store) -> Result<()> {
|
||||||
if let Some(limiter) = limiter {
|
if !store.table_growing(0, plan.table.minimum, plan.table.maximum)? {
|
||||||
if !limiter.table_growing(0, plan.table.minimum, plan.table.maximum) {
|
bail!(
|
||||||
bail!(
|
"table minimum size of {} elements exceeds table limits",
|
||||||
"table minimum size of {} elements exceeds table limits",
|
plan.table.minimum
|
||||||
plan.table.minimum
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -292,20 +287,22 @@ impl Table {
|
|||||||
&mut self,
|
&mut self,
|
||||||
delta: u32,
|
delta: u32,
|
||||||
init_value: TableElement,
|
init_value: TableElement,
|
||||||
limiter: Option<&mut dyn ResourceLimiter>,
|
store: &mut dyn Store,
|
||||||
) -> Option<u32> {
|
) -> Result<Option<u32>, Error> {
|
||||||
let old_size = self.size();
|
let old_size = self.size();
|
||||||
let new_size = old_size.checked_add(delta)?;
|
let new_size = match old_size.checked_add(delta) {
|
||||||
|
Some(s) => s,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(limiter) = limiter {
|
if !store.table_growing(old_size, new_size, self.maximum())? {
|
||||||
if !limiter.table_growing(old_size, new_size, self.maximum()) {
|
return Ok(None);
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(max) = self.maximum() {
|
if let Some(max) = self.maximum() {
|
||||||
if new_size > max {
|
if new_size > max {
|
||||||
return None;
|
store.table_grow_failed(&format_err!("Table maximum size exceeded"));
|
||||||
|
return Ok(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,7 +324,7 @@ impl Table {
|
|||||||
self.fill(old_size, init_value, delta)
|
self.fill(old_size, init_value, delta)
|
||||||
.expect("table should not be out of bounds");
|
.expect("table should not be out of bounds");
|
||||||
|
|
||||||
Some(old_size)
|
Ok(Some(old_size))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get reference to the specified element.
|
/// Get reference to the specified element.
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
//! signalhandling mechanisms.
|
//! signalhandling mechanisms.
|
||||||
|
|
||||||
use crate::{VMContext, VMInterrupts};
|
use crate::{VMContext, VMInterrupts};
|
||||||
|
use anyhow::Error;
|
||||||
use backtrace::Backtrace;
|
use backtrace::Backtrace;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::cell::{Cell, UnsafeCell};
|
use std::cell::{Cell, UnsafeCell};
|
||||||
use std::error::Error;
|
|
||||||
use std::mem::MaybeUninit;
|
use std::mem::MaybeUninit;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::sync::atomic::Ordering::SeqCst;
|
use std::sync::atomic::Ordering::SeqCst;
|
||||||
@@ -80,7 +80,7 @@ pub fn init_traps(is_wasm_pc: fn(usize) -> bool) {
|
|||||||
/// Only safe to call when wasm code is on the stack, aka `catch_traps` must
|
/// Only safe to call when wasm code is on the stack, aka `catch_traps` must
|
||||||
/// have been previously called. Additionally no Rust destructors can be on the
|
/// have been previously called. Additionally no Rust destructors can be on the
|
||||||
/// stack. They will be skipped and not executed.
|
/// stack. They will be skipped and not executed.
|
||||||
pub unsafe fn raise_user_trap(data: Box<dyn Error + Send + Sync>) -> ! {
|
pub unsafe fn raise_user_trap(data: Error) -> ! {
|
||||||
tls::with(|info| info.unwrap().unwind_with(UnwindReason::UserTrap(data)))
|
tls::with(|info| info.unwrap().unwind_with(UnwindReason::UserTrap(data)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,7 +114,7 @@ pub unsafe fn resume_panic(payload: Box<dyn Any + Send>) -> ! {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Trap {
|
pub enum Trap {
|
||||||
/// A user-raised trap through `raise_user_trap`.
|
/// A user-raised trap through `raise_user_trap`.
|
||||||
User(Box<dyn Error + Send + Sync>),
|
User(Error),
|
||||||
|
|
||||||
/// A trap raised from jit code
|
/// A trap raised from jit code
|
||||||
Jit {
|
Jit {
|
||||||
@@ -206,7 +206,7 @@ pub struct CallThreadState {
|
|||||||
|
|
||||||
enum UnwindReason {
|
enum UnwindReason {
|
||||||
Panic(Box<dyn Any + Send>),
|
Panic(Box<dyn Any + Send>),
|
||||||
UserTrap(Box<dyn Error + Send + Sync>),
|
UserTrap(Error),
|
||||||
LibTrap(Trap),
|
LibTrap(Trap),
|
||||||
JitTrap { backtrace: Backtrace, pc: usize },
|
JitTrap { backtrace: Backtrace, pc: usize },
|
||||||
}
|
}
|
||||||
@@ -431,9 +431,12 @@ mod tls {
|
|||||||
// null out our own previous field for safety in case it's
|
// null out our own previous field for safety in case it's
|
||||||
// accidentally used later.
|
// accidentally used later.
|
||||||
let raw = raw::get();
|
let raw = raw::get();
|
||||||
assert!(!raw.is_null());
|
if !raw.is_null() {
|
||||||
let prev = (*raw).prev.replace(ptr::null());
|
let prev = (*raw).prev.replace(ptr::null());
|
||||||
raw::replace(prev)?;
|
raw::replace(prev)?;
|
||||||
|
}
|
||||||
|
// Null case: we aren't in a wasm context, so theres no tls
|
||||||
|
// to save for restoration.
|
||||||
Ok(TlsRestore(raw))
|
Ok(TlsRestore(raw))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -442,6 +445,11 @@ mod tls {
|
|||||||
/// This is unsafe because it's intended to only be used within the
|
/// This is unsafe because it's intended to only be used within the
|
||||||
/// context of stack switching within wasmtime.
|
/// context of stack switching within wasmtime.
|
||||||
pub unsafe fn replace(self) -> Result<(), Box<super::Trap>> {
|
pub unsafe fn replace(self) -> Result<(), Box<super::Trap>> {
|
||||||
|
// Null case: we aren't in a wasm context, so theres no tls
|
||||||
|
// to restore.
|
||||||
|
if self.0.is_null() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
// We need to configure our previous TLS pointer to whatever is in
|
// We need to configure our previous TLS pointer to whatever is in
|
||||||
// TLS at this time, and then we set the current state to ourselves.
|
// TLS at this time, and then we set the current state to ourselves.
|
||||||
let prev = raw::get();
|
let prev = raw::get();
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ psm = "0.1.11"
|
|||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
rayon = { version = "1.0", optional = true }
|
rayon = { version = "1.0", optional = true }
|
||||||
object = { version = "0.27", default-features = false, features = ['read_core', 'elf'] }
|
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]
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
winapi = "0.3.7"
|
winapi = "0.3.7"
|
||||||
@@ -73,7 +74,7 @@ cache = ["wasmtime-cache"]
|
|||||||
|
|
||||||
# Enables support for "async stores" as well as defining host functions as
|
# Enables support for "async stores" as well as defining host functions as
|
||||||
# `async fn` and calling functions asynchronously.
|
# `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
|
# Enables userfaultfd support in the runtime's pooling allocator when building on Linux
|
||||||
uffd = ["wasmtime-runtime/uffd"]
|
uffd = ["wasmtime-runtime/uffd"]
|
||||||
|
|||||||
@@ -406,6 +406,14 @@ impl Table {
|
|||||||
/// Returns an error if `init` does not match the element type of the table,
|
/// Returns an error if `init` does not match the element type of the table,
|
||||||
/// or if `init` does not belong to the `store` provided.
|
/// or if `init` does not belong to the `store` provided.
|
||||||
///
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This function will panic when used with a [`Store`](`crate::Store`)
|
||||||
|
/// which has a [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`)
|
||||||
|
/// (see also: [`Store::limiter_async`](`crate::Store::limiter_async`).
|
||||||
|
/// When using an async resource limiter, use [`Table::new_async`]
|
||||||
|
/// instead.
|
||||||
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
@@ -436,6 +444,34 @@ impl Table {
|
|||||||
Table::_new(store.as_context_mut().0, ty, init)
|
Table::_new(store.as_context_mut().0, ty, init)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(nightlydoc, doc(cfg(feature = "async")))]
|
||||||
|
/// Async variant of [`Table::new`]. You must use this variant with
|
||||||
|
/// [`Store`](`crate::Store`)s which have a
|
||||||
|
/// [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`).
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This function will panic when used with a non-async
|
||||||
|
/// [`Store`](`crate::Store`)
|
||||||
|
#[cfg(feature = "async")]
|
||||||
|
pub async fn new_async<T>(
|
||||||
|
mut store: impl AsContextMut<Data = T>,
|
||||||
|
ty: TableType,
|
||||||
|
init: Val,
|
||||||
|
) -> Result<Table>
|
||||||
|
where
|
||||||
|
T: Send,
|
||||||
|
{
|
||||||
|
let mut store = store.as_context_mut();
|
||||||
|
assert!(
|
||||||
|
store.0.async_support(),
|
||||||
|
"cannot use `new_async` without enabling async support on the config"
|
||||||
|
);
|
||||||
|
store
|
||||||
|
.on_fiber(|store| Table::_new(store.0, ty, init))
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
|
||||||
fn _new(store: &mut StoreOpaque, ty: TableType, init: Val) -> Result<Table> {
|
fn _new(store: &mut StoreOpaque, ty: TableType, init: Val) -> Result<Table> {
|
||||||
let wasmtime_export = generate_table_export(store, &ty)?;
|
let wasmtime_export = generate_table_export(store, &ty)?;
|
||||||
let init = init.into_table_element(store, ty.element())?;
|
let init = init.into_table_element(store, ty.element())?;
|
||||||
@@ -545,13 +581,19 @@ impl Table {
|
|||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if `store` does not own this table.
|
/// Panics if `store` does not own this table.
|
||||||
|
///
|
||||||
|
/// This function will panic when used with a [`Store`](`crate::Store`)
|
||||||
|
/// which has a [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`)
|
||||||
|
/// (see also: [`Store::limiter_async`](`crate::Store::limiter_async`)).
|
||||||
|
/// When using an async resource limiter, use [`Table::grow_async`]
|
||||||
|
/// instead.
|
||||||
pub fn grow(&self, mut store: impl AsContextMut, delta: u32, init: Val) -> Result<u32> {
|
pub fn grow(&self, mut store: impl AsContextMut, delta: u32, init: Val) -> Result<u32> {
|
||||||
let store = store.as_context_mut().0;
|
let store = store.as_context_mut().0;
|
||||||
let ty = self.ty(&store).element().clone();
|
let ty = self.ty(&store).element().clone();
|
||||||
let init = init.into_table_element(store, ty)?;
|
let init = init.into_table_element(store, ty)?;
|
||||||
let table = self.wasmtime_table(store);
|
let table = self.wasmtime_table(store);
|
||||||
unsafe {
|
unsafe {
|
||||||
match (*table).grow(delta, init, store.limiter()) {
|
match (*table).grow(delta, init, store)? {
|
||||||
Some(size) => {
|
Some(size) => {
|
||||||
let vm = (*table).vmtable();
|
let vm = (*table).vmtable();
|
||||||
*store[self.0].definition = vm;
|
*store[self.0].definition = vm;
|
||||||
@@ -562,6 +604,34 @@ impl Table {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(nightlydoc, doc(cfg(feature = "async")))]
|
||||||
|
/// Async variant of [`Table::grow`]. Required when using a
|
||||||
|
/// [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`).
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This function will panic when used with a non-async
|
||||||
|
/// [`Store`](`crate::Store`).
|
||||||
|
#[cfg(feature = "async")]
|
||||||
|
pub async fn grow_async<T>(
|
||||||
|
&self,
|
||||||
|
mut store: impl AsContextMut<Data = T>,
|
||||||
|
delta: u32,
|
||||||
|
init: Val,
|
||||||
|
) -> Result<u32>
|
||||||
|
where
|
||||||
|
T: Send,
|
||||||
|
{
|
||||||
|
let mut store = store.as_context_mut();
|
||||||
|
assert!(
|
||||||
|
store.0.async_support(),
|
||||||
|
"cannot use `grow_async` without enabling async support on the config"
|
||||||
|
);
|
||||||
|
store
|
||||||
|
.on_fiber(|store| self.grow(store, delta, init))
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
|
||||||
/// Copy `len` elements from `src_table[src_index..]` into
|
/// Copy `len` elements from `src_table[src_index..]` into
|
||||||
/// `dst_table[dst_index..]`.
|
/// `dst_table[dst_index..]`.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ use crate::{
|
|||||||
StoreContext, StoreContextMut, Trap, Val, ValRaw, ValType,
|
StoreContext, StoreContextMut, Trap, Val, ValRaw, ValType,
|
||||||
};
|
};
|
||||||
use anyhow::{bail, Context as _, Result};
|
use anyhow::{bail, Context as _, Result};
|
||||||
use std::error::Error;
|
|
||||||
use std::fmt;
|
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::panic::{self, AssertUnwindSafe};
|
use std::panic::{self, AssertUnwindSafe};
|
||||||
@@ -1784,25 +1782,6 @@ impl<T> AsContextMut for Caller<'_, T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cross_store_trap() -> Box<dyn Error + Send + Sync> {
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct CrossStoreError;
|
|
||||||
|
|
||||||
impl Error for CrossStoreError {}
|
|
||||||
|
|
||||||
impl fmt::Display for CrossStoreError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"host function attempted to return cross-`Store` \
|
|
||||||
value to Wasm",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Box::new(CrossStoreError)
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! impl_into_func {
|
macro_rules! impl_into_func {
|
||||||
($num:tt $($args:ident)*) => {
|
($num:tt $($args:ident)*) => {
|
||||||
// Implement for functions without a leading `&Caller` parameter,
|
// Implement for functions without a leading `&Caller` parameter,
|
||||||
@@ -1852,7 +1831,7 @@ macro_rules! impl_into_func {
|
|||||||
{
|
{
|
||||||
enum CallResult<U> {
|
enum CallResult<U> {
|
||||||
Ok(U),
|
Ok(U),
|
||||||
Trap(Box<dyn Error + Send + Sync>),
|
Trap(anyhow::Error),
|
||||||
Panic(Box<dyn std::any::Any + Send>),
|
Panic(Box<dyn std::any::Any + Send>),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1901,7 +1880,7 @@ macro_rules! impl_into_func {
|
|||||||
// can't assume it returned a value that is
|
// can't assume it returned a value that is
|
||||||
// compatible with this store.
|
// compatible with this store.
|
||||||
if !ret.compatible_with_store(caller.store.0) {
|
if !ret.compatible_with_store(caller.store.0) {
|
||||||
CallResult::Trap(cross_store_trap())
|
CallResult::Trap(anyhow::anyhow!("host function attempted to return cross-`Store` value to Wasm"))
|
||||||
} else {
|
} else {
|
||||||
match ret.into_abi_for_ret(caller.store.0, retptr) {
|
match ret.into_abi_for_ret(caller.store.0, retptr) {
|
||||||
Ok(val) => CallResult::Ok(val),
|
Ok(val) => CallResult::Ok(val),
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use wasmtime_environ::{
|
|||||||
};
|
};
|
||||||
use wasmtime_jit::TypeTables;
|
use wasmtime_jit::TypeTables;
|
||||||
use wasmtime_runtime::{
|
use wasmtime_runtime::{
|
||||||
Imports, InstanceAllocationRequest, InstantiationError, VMContext, VMFunctionBody,
|
Imports, InstanceAllocationRequest, InstantiationError, StorePtr, VMContext, VMFunctionBody,
|
||||||
VMFunctionImport, VMGlobalImport, VMMemoryImport, VMTableImport,
|
VMFunctionImport, VMGlobalImport, VMMemoryImport, VMTableImport,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -126,6 +126,11 @@ impl Instance {
|
|||||||
typecheck_externs(store.0, module, imports)?;
|
typecheck_externs(store.0, module, imports)?;
|
||||||
Instantiator::new(store.0, module, ImportSource::Externs(imports))?
|
Instantiator::new(store.0, module, ImportSource::Externs(imports))?
|
||||||
};
|
};
|
||||||
|
assert!(
|
||||||
|
!store.0.async_support(),
|
||||||
|
"cannot use `new` when async support is enabled on the config"
|
||||||
|
);
|
||||||
|
|
||||||
i.run(&mut store)
|
i.run(&mut store)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,7 +167,14 @@ impl Instance {
|
|||||||
typecheck_externs(store.0, module, imports)?;
|
typecheck_externs(store.0, module, imports)?;
|
||||||
Instantiator::new(store.0, module, ImportSource::Externs(imports))?
|
Instantiator::new(store.0, module, ImportSource::Externs(imports))?
|
||||||
};
|
};
|
||||||
i.run_async(&mut store).await
|
let mut store = store.as_context_mut();
|
||||||
|
assert!(
|
||||||
|
store.0.async_support(),
|
||||||
|
"cannot use `new_async` without enabling async support on the config"
|
||||||
|
);
|
||||||
|
store
|
||||||
|
.on_fiber(|store| i.run(&mut store.as_context_mut()))
|
||||||
|
.await?
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn from_wasmtime(handle: InstanceData, store: &mut StoreOpaque) -> Instance {
|
pub(crate) fn from_wasmtime(handle: InstanceData, store: &mut StoreOpaque) -> Instance {
|
||||||
@@ -472,13 +484,6 @@ impl<'a> Instantiator<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn run<T>(&mut self, store: &mut StoreContextMut<'_, T>) -> Result<Instance, Error> {
|
fn run<T>(&mut self, store: &mut StoreContextMut<'_, T>) -> Result<Instance, Error> {
|
||||||
assert!(
|
|
||||||
!store.0.async_support(),
|
|
||||||
"cannot use `new` when async support is enabled on the config"
|
|
||||||
);
|
|
||||||
|
|
||||||
// NB: this is the same code as `run_async`. It's intentionally
|
|
||||||
// small but should be kept in sync (modulo the async bits).
|
|
||||||
loop {
|
loop {
|
||||||
if let Some((instance, start, toplevel)) = self.step(store.0)? {
|
if let Some((instance, start, toplevel)) = self.step(store.0)? {
|
||||||
if let Some(start) = start {
|
if let Some(start) = start {
|
||||||
@@ -491,33 +496,6 @@ impl<'a> Instantiator<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "async")]
|
|
||||||
async fn run_async<T>(&mut self, store: &mut StoreContextMut<'_, T>) -> Result<Instance, Error>
|
|
||||||
where
|
|
||||||
T: Send,
|
|
||||||
{
|
|
||||||
assert!(
|
|
||||||
store.0.async_support(),
|
|
||||||
"cannot use `new_async` without enabling async support on the config"
|
|
||||||
);
|
|
||||||
|
|
||||||
// NB: this is the same code as `run`. It's intentionally
|
|
||||||
// small but should be kept in sync (modulo the async bits).
|
|
||||||
loop {
|
|
||||||
let step = self.step(store.0)?;
|
|
||||||
if let Some((instance, start, toplevel)) = step {
|
|
||||||
if let Some(start) = start {
|
|
||||||
store
|
|
||||||
.on_fiber(|store| Instantiator::start_raw(store, instance, start))
|
|
||||||
.await??;
|
|
||||||
}
|
|
||||||
if toplevel {
|
|
||||||
break Ok(instance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Processes the next initializer for the next instance being created
|
/// Processes the next initializer for the next instance being created
|
||||||
/// without running any wasm code.
|
/// without running any wasm code.
|
||||||
///
|
///
|
||||||
@@ -734,7 +712,7 @@ impl<'a> Instantiator<'a> {
|
|||||||
imports: self.cur.build(),
|
imports: self.cur.build(),
|
||||||
shared_signatures: self.cur.module.signatures().as_module_map().into(),
|
shared_signatures: self.cur.module.signatures().as_module_map().into(),
|
||||||
host_state: Box::new(Instance(instance_to_be)),
|
host_state: Box::new(Instance(instance_to_be)),
|
||||||
store: Some(store.traitobj()),
|
store: StorePtr::new(store.traitobj()),
|
||||||
wasm_data: compiled_module.wasm_data(),
|
wasm_data: compiled_module.wasm_data(),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@@ -1002,7 +980,15 @@ impl<T> InstancePre<T> {
|
|||||||
ImportSource::Definitions(&self.items),
|
ImportSource::Definitions(&self.items),
|
||||||
)?
|
)?
|
||||||
};
|
};
|
||||||
i.run_async(&mut store.as_context_mut()).await
|
|
||||||
|
let mut store = store.as_context_mut();
|
||||||
|
assert!(
|
||||||
|
store.0.async_support(),
|
||||||
|
"cannot use `new_async` without enabling async support on the config"
|
||||||
|
);
|
||||||
|
store
|
||||||
|
.on_fiber(|store| i.run(&mut store.as_context_mut()))
|
||||||
|
.await?
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ensure_comes_from_same_store(&self, store: &StoreOpaque) -> Result<()> {
|
fn ensure_comes_from_same_store(&self, store: &StoreOpaque) -> Result<()> {
|
||||||
|
|||||||
@@ -1,4 +1,133 @@
|
|||||||
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;
|
||||||
|
|
||||||
|
/// Notifies the resource limiter that growing a linear memory, permitted by
|
||||||
|
/// the `table_growing` method, has failed.
|
||||||
|
///
|
||||||
|
/// Reasons for failure include: the growth exceeds the `maximum` passed to
|
||||||
|
/// `table_growing`. This could expand in the future.
|
||||||
|
fn table_grow_failed(&mut self, _error: &anyhow::Error) {}
|
||||||
|
|
||||||
|
/// 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`](`crate::Store`).
|
||||||
|
///
|
||||||
|
/// This trait is used with
|
||||||
|
/// [`Store::limiter_async`](`crate::Store::limiter_async`)`: see those docs
|
||||||
|
/// for restrictions on using other Wasmtime interfaces with an async resource
|
||||||
|
/// limiter.
|
||||||
|
#[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::table_grow_failed`]
|
||||||
|
fn table_grow_failed(&mut self, _error: &anyhow::Error) {}
|
||||||
|
|
||||||
|
/// 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`].
|
/// Used to build [`StoreLimits`].
|
||||||
pub struct StoreLimitsBuilder(StoreLimits);
|
pub struct StoreLimitsBuilder(StoreLimits);
|
||||||
@@ -79,13 +208,14 @@ impl Default for StoreLimits {
|
|||||||
Self {
|
Self {
|
||||||
memory_size: None,
|
memory_size: None,
|
||||||
table_elements: None,
|
table_elements: None,
|
||||||
instances: wasmtime_runtime::DEFAULT_INSTANCE_LIMIT,
|
instances: DEFAULT_INSTANCE_LIMIT,
|
||||||
tables: wasmtime_runtime::DEFAULT_TABLE_LIMIT,
|
tables: DEFAULT_TABLE_LIMIT,
|
||||||
memories: wasmtime_runtime::DEFAULT_MEMORY_LIMIT,
|
memories: DEFAULT_MEMORY_LIMIT,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "async", async_trait::async_trait)]
|
||||||
impl ResourceLimiter for StoreLimits {
|
impl ResourceLimiter for StoreLimits {
|
||||||
fn memory_growing(&mut self, _current: usize, desired: usize, _maximum: Option<usize>) -> bool {
|
fn memory_growing(&mut self, _current: usize, desired: usize, _maximum: Option<usize>) -> bool {
|
||||||
match self.memory_size {
|
match self.memory_size {
|
||||||
|
|||||||
@@ -202,6 +202,13 @@ impl Memory {
|
|||||||
/// The `store` argument will be the owner of the returned [`Memory`]. All
|
/// The `store` argument will be the owner of the returned [`Memory`]. All
|
||||||
/// WebAssembly memory is initialized to zero.
|
/// WebAssembly memory is initialized to zero.
|
||||||
///
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This function will panic if the [`Store`](`crate::Store`) has a
|
||||||
|
/// [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`) (see also:
|
||||||
|
/// [`Store::limiter_async`](`crate::Store::limiter_async`)). When
|
||||||
|
/// using an async resource limiter, use [`Memory::new_async`] instead.
|
||||||
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
@@ -223,6 +230,31 @@ impl Memory {
|
|||||||
Memory::_new(store.as_context_mut().0, ty)
|
Memory::_new(store.as_context_mut().0, ty)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(nightlydoc, doc(cfg(feature = "async")))]
|
||||||
|
/// Async variant of [`Memory::new`]. You must use this variant with
|
||||||
|
/// [`Store`](`crate::Store`)s which have a
|
||||||
|
/// [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`).
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This function will panic when used with a non-async
|
||||||
|
/// [`Store`](`crate::Store`).
|
||||||
|
#[cfg(feature = "async")]
|
||||||
|
pub async fn new_async<T>(
|
||||||
|
mut store: impl AsContextMut<Data = T>,
|
||||||
|
ty: MemoryType,
|
||||||
|
) -> Result<Memory>
|
||||||
|
where
|
||||||
|
T: Send,
|
||||||
|
{
|
||||||
|
let mut store = store.as_context_mut();
|
||||||
|
assert!(
|
||||||
|
store.0.async_support(),
|
||||||
|
"cannot use `new_async` without enabling async support on the config"
|
||||||
|
);
|
||||||
|
store.on_fiber(|store| Memory::_new(store.0, ty)).await?
|
||||||
|
}
|
||||||
|
|
||||||
fn _new(store: &mut StoreOpaque, ty: MemoryType) -> Result<Memory> {
|
fn _new(store: &mut StoreOpaque, ty: MemoryType) -> Result<Memory> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let export = generate_memory_export(store, &ty)?;
|
let export = generate_memory_export(store, &ty)?;
|
||||||
@@ -437,6 +469,11 @@ impl Memory {
|
|||||||
///
|
///
|
||||||
/// Panics if this memory doesn't belong to `store`.
|
/// Panics if this memory doesn't belong to `store`.
|
||||||
///
|
///
|
||||||
|
/// This function will panic if the [`Store`](`crate::Store`) has a
|
||||||
|
/// [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`) (see also:
|
||||||
|
/// [`Store::limiter_async`](`crate::Store::limiter_async`). When using an
|
||||||
|
/// async resource limiter, use [`Memory::grow_async`] instead.
|
||||||
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
@@ -461,7 +498,7 @@ impl Memory {
|
|||||||
let store = store.as_context_mut().0;
|
let store = store.as_context_mut().0;
|
||||||
let mem = self.wasmtime_memory(store);
|
let mem = self.wasmtime_memory(store);
|
||||||
unsafe {
|
unsafe {
|
||||||
match (*mem).grow(delta, store.limiter()) {
|
match (*mem).grow(delta, store)? {
|
||||||
Some(size) => {
|
Some(size) => {
|
||||||
let vm = (*mem).vmmemory();
|
let vm = (*mem).vmmemory();
|
||||||
*store[self.0].definition = vm;
|
*store[self.0].definition = vm;
|
||||||
@@ -472,6 +509,30 @@ impl Memory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(nightlydoc, doc(cfg(feature = "async")))]
|
||||||
|
/// Async variant of [`Memory::grow`]. Required when using a
|
||||||
|
/// [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`).
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This function will panic when used with a non-async
|
||||||
|
/// [`Store`](`crate::Store`).
|
||||||
|
#[cfg(feature = "async")]
|
||||||
|
pub async fn grow_async<T>(
|
||||||
|
&self,
|
||||||
|
mut store: impl AsContextMut<Data = T>,
|
||||||
|
delta: u64,
|
||||||
|
) -> Result<u64>
|
||||||
|
where
|
||||||
|
T: Send,
|
||||||
|
{
|
||||||
|
let mut store = store.as_context_mut();
|
||||||
|
assert!(
|
||||||
|
store.0.async_support(),
|
||||||
|
"cannot use `grow_async` without enabling async support on the config"
|
||||||
|
);
|
||||||
|
store.on_fiber(|store| self.grow(store, delta)).await?
|
||||||
|
}
|
||||||
fn wasmtime_memory(&self, store: &mut StoreOpaque) -> *mut wasmtime_runtime::Memory {
|
fn wasmtime_memory(&self, store: &mut StoreOpaque) -> *mut wasmtime_runtime::Memory {
|
||||||
unsafe {
|
unsafe {
|
||||||
let export = &store[self.0];
|
let export = &store[self.0];
|
||||||
|
|||||||
@@ -81,7 +81,6 @@ use anyhow::{bail, Result};
|
|||||||
use std::cell::UnsafeCell;
|
use std::cell::UnsafeCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::error::Error;
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::marker;
|
use std::marker;
|
||||||
@@ -93,8 +92,8 @@ use std::sync::Arc;
|
|||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
use wasmtime_runtime::{
|
use wasmtime_runtime::{
|
||||||
InstanceAllocationRequest, InstanceAllocator, InstanceHandle, ModuleInfo,
|
InstanceAllocationRequest, InstanceAllocator, InstanceHandle, ModuleInfo,
|
||||||
OnDemandInstanceAllocator, SignalHandler, VMCallerCheckedAnyfunc, VMContext, VMExternRef,
|
OnDemandInstanceAllocator, SignalHandler, StorePtr, VMCallerCheckedAnyfunc, VMContext,
|
||||||
VMExternRefActivationsTable, VMInterrupts, VMSharedSignatureIndex, VMTrampoline,
|
VMExternRef, VMExternRefActivationsTable, VMInterrupts, VMSharedSignatureIndex, VMTrampoline,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod context;
|
mod context;
|
||||||
@@ -197,12 +196,18 @@ pub struct StoreInner<T> {
|
|||||||
/// Generic metadata about the store that doesn't need access to `T`.
|
/// Generic metadata about the store that doesn't need access to `T`.
|
||||||
inner: StoreOpaque,
|
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>>,
|
call_hook: Option<Box<dyn FnMut(&mut T, CallHook) -> Result<(), crate::Trap> + Send + Sync>>,
|
||||||
// for comments about `ManuallyDrop`, see `Store::into_data`
|
// for comments about `ManuallyDrop`, see `Store::into_data`
|
||||||
data: ManuallyDrop<T>,
|
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>`
|
// Forward methods on `StoreOpaque` to also being on `StoreInner<T>`
|
||||||
impl<T> Deref for StoreInner<T> {
|
impl<T> Deref for StoreInner<T> {
|
||||||
type Target = StoreOpaque;
|
type Target = StoreOpaque;
|
||||||
@@ -402,7 +407,7 @@ impl<T> Store<T> {
|
|||||||
shared_signatures: None.into(),
|
shared_signatures: None.into(),
|
||||||
imports: Default::default(),
|
imports: Default::default(),
|
||||||
module: Arc::new(wasmtime_environ::Module::default()),
|
module: Arc::new(wasmtime_environ::Module::default()),
|
||||||
store: None,
|
store: StorePtr::empty(),
|
||||||
wasm_data: &[],
|
wasm_data: &[],
|
||||||
})
|
})
|
||||||
.expect("failed to allocate default callee")
|
.expect("failed to allocate default callee")
|
||||||
@@ -418,11 +423,11 @@ impl<T> Store<T> {
|
|||||||
modules: ModuleRegistry::default(),
|
modules: ModuleRegistry::default(),
|
||||||
host_trampolines: HashMap::default(),
|
host_trampolines: HashMap::default(),
|
||||||
instance_count: 0,
|
instance_count: 0,
|
||||||
instance_limit: wasmtime_runtime::DEFAULT_INSTANCE_LIMIT,
|
instance_limit: crate::DEFAULT_INSTANCE_LIMIT,
|
||||||
memory_count: 0,
|
memory_count: 0,
|
||||||
memory_limit: wasmtime_runtime::DEFAULT_MEMORY_LIMIT,
|
memory_limit: crate::DEFAULT_MEMORY_LIMIT,
|
||||||
table_count: 0,
|
table_count: 0,
|
||||||
table_limit: wasmtime_runtime::DEFAULT_TABLE_LIMIT,
|
table_limit: crate::DEFAULT_TABLE_LIMIT,
|
||||||
fuel_adj: 0,
|
fuel_adj: 0,
|
||||||
#[cfg(feature = "async")]
|
#[cfg(feature = "async")]
|
||||||
async_state: AsyncState {
|
async_state: AsyncState {
|
||||||
@@ -525,7 +530,56 @@ impl<T> Store<T> {
|
|||||||
innermost.memory_limit = memory_limit;
|
innermost.memory_limit = memory_limit;
|
||||||
|
|
||||||
// Save the limiter accessor function:
|
// Save the limiter accessor function:
|
||||||
inner.limiter = Some(Box::new(limiter));
|
inner.limiter = Some(ResourceLimiterInner::Sync(Box::new(limiter)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(nightlydoc, doc(cfg(feature = "async")))]
|
||||||
|
/// 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`].
|
||||||
|
///
|
||||||
|
/// This variation on the [`ResourceLimiter`](`crate::ResourceLimiter`)
|
||||||
|
/// makes the `memory_growing` and `table_growing` functions `async`. This
|
||||||
|
/// means that, as part of your resource limiting strategy, the async
|
||||||
|
/// resource limiter may yield execution until a resource becomes
|
||||||
|
/// available.
|
||||||
|
///
|
||||||
|
/// By using a [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`)
|
||||||
|
/// with a [`Store`], you can no longer use
|
||||||
|
/// [`Memory::new`](`crate::Memory::new`),
|
||||||
|
/// [`Memory::grow`](`crate::Memory::grow`),
|
||||||
|
/// [`Table::new`](`crate::Table::new`), and
|
||||||
|
/// [`Table::grow`](`crate::Table::grow`). Instead, you must use their
|
||||||
|
/// `async` variants: [`Memory::new_async`](`crate::Memory::new_async`),
|
||||||
|
/// [`Memory::grow_async`](`crate::Memory::grow_async`),
|
||||||
|
/// [`Table::new_async`](`crate::Table::new_async`), and
|
||||||
|
/// [`Table::grow_async`](`crate::Table::grow_async`).
|
||||||
|
#[cfg(feature = "async")]
|
||||||
|
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
|
/// Configure a function that runs on calls and returns between WebAssembly
|
||||||
@@ -872,11 +926,6 @@ impl<T> StoreInner<T> {
|
|||||||
&mut self.data
|
&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> {
|
pub fn call_hook(&mut self, s: CallHook) -> Result<(), Trap> {
|
||||||
if let Some(hook) = &mut self.call_hook {
|
if let Some(hook) = &mut self.call_hook {
|
||||||
hook(&mut self.data, s)
|
hook(&mut self.data, s)
|
||||||
@@ -1496,20 +1545,109 @@ unsafe impl<T> wasmtime_runtime::Store for StoreInner<T> {
|
|||||||
(&mut inner.externref_activations_table, &inner.modules)
|
(&mut inner.externref_activations_table, &inner.modules)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn limiter(&mut self) -> Option<&mut dyn wasmtime_runtime::ResourceLimiter> {
|
fn memory_growing(
|
||||||
<Self>::limiter(self)
|
&mut self,
|
||||||
|
current: usize,
|
||||||
|
desired: usize,
|
||||||
|
maximum: Option<usize>,
|
||||||
|
) -> Result<bool, anyhow::Error> {
|
||||||
|
// 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)) => {
|
||||||
|
Ok(limiter(&mut self.data).memory_growing(current, desired, maximum))
|
||||||
|
}
|
||||||
|
#[cfg(feature = "async")]
|
||||||
|
Some(ResourceLimiterInner::Async(ref mut limiter)) => unsafe {
|
||||||
|
Ok(async_cx
|
||||||
|
.expect("ResourceLimiterAsync requires async Store")
|
||||||
|
.block_on(
|
||||||
|
limiter(&mut self.data)
|
||||||
|
.memory_growing(current, desired, maximum)
|
||||||
|
.as_mut(),
|
||||||
|
)?)
|
||||||
|
},
|
||||||
|
None => Ok(true),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn out_of_gas(&mut self) -> Result<(), Box<dyn Error + Send + Sync>> {
|
fn 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 table_growing(
|
||||||
|
&mut self,
|
||||||
|
current: u32,
|
||||||
|
desired: u32,
|
||||||
|
maximum: Option<u32>,
|
||||||
|
) -> Result<bool, anyhow::Error> {
|
||||||
|
// 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)) => {
|
||||||
|
Ok(limiter(&mut self.data).table_growing(current, desired, maximum))
|
||||||
|
}
|
||||||
|
#[cfg(feature = "async")]
|
||||||
|
Some(ResourceLimiterInner::Async(ref mut limiter)) => unsafe {
|
||||||
|
Ok(async_cx
|
||||||
|
.expect("ResourceLimiterAsync requires async Store")
|
||||||
|
.block_on(
|
||||||
|
limiter(&mut self.data)
|
||||||
|
.table_growing(current, desired, maximum)
|
||||||
|
.as_mut(),
|
||||||
|
)?)
|
||||||
|
},
|
||||||
|
None => Ok(true),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn table_grow_failed(&mut self, error: &anyhow::Error) {
|
||||||
|
match self.limiter {
|
||||||
|
Some(ResourceLimiterInner::Sync(ref mut limiter)) => {
|
||||||
|
limiter(&mut self.data).table_grow_failed(error)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "async")]
|
||||||
|
Some(ResourceLimiterInner::Async(ref mut limiter)) => {
|
||||||
|
limiter(&mut self.data).table_grow_failed(error)
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn out_of_gas(&mut self) -> Result<(), anyhow::Error> {
|
||||||
return match &mut self.out_of_gas_behavior {
|
return match &mut self.out_of_gas_behavior {
|
||||||
OutOfGas::Trap => Err(Box::new(OutOfGasError)),
|
OutOfGas::Trap => Err(anyhow::Error::new(OutOfGasError)),
|
||||||
#[cfg(feature = "async")]
|
#[cfg(feature = "async")]
|
||||||
OutOfGas::InjectFuel {
|
OutOfGas::InjectFuel {
|
||||||
injection_count,
|
injection_count,
|
||||||
fuel_to_inject,
|
fuel_to_inject,
|
||||||
} => {
|
} => {
|
||||||
if *injection_count == 0 {
|
if *injection_count == 0 {
|
||||||
return Err(Box::new(OutOfGasError));
|
return Err(anyhow::Error::new(OutOfGasError));
|
||||||
}
|
}
|
||||||
*injection_count -= 1;
|
*injection_count -= 1;
|
||||||
let fuel = *fuel_to_inject;
|
let fuel = *fuel_to_inject;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ use std::any::Any;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use wasmtime_environ::{EntityIndex, GlobalIndex, MemoryIndex, Module, TableIndex};
|
use wasmtime_environ::{EntityIndex, GlobalIndex, MemoryIndex, Module, TableIndex};
|
||||||
use wasmtime_runtime::{
|
use wasmtime_runtime::{
|
||||||
Imports, InstanceAllocationRequest, InstanceAllocator, OnDemandInstanceAllocator,
|
Imports, InstanceAllocationRequest, InstanceAllocator, OnDemandInstanceAllocator, StorePtr,
|
||||||
VMFunctionImport, VMSharedSignatureIndex,
|
VMFunctionImport, VMSharedSignatureIndex,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ fn create_handle(
|
|||||||
imports,
|
imports,
|
||||||
shared_signatures: shared_signature_id.into(),
|
shared_signatures: shared_signature_id.into(),
|
||||||
host_state,
|
host_state,
|
||||||
store: Some(store.traitobj()),
|
store: StorePtr::new(store.traitobj()),
|
||||||
wasm_data: &[],
|
wasm_data: &[],
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ use wasmtime_environ::{EntityIndex, Module, ModuleType, PrimaryMap, SignatureInd
|
|||||||
use wasmtime_jit::{CodeMemory, MmapVec};
|
use wasmtime_jit::{CodeMemory, MmapVec};
|
||||||
use wasmtime_runtime::{
|
use wasmtime_runtime::{
|
||||||
Imports, InstanceAllocationRequest, InstanceAllocator, InstanceHandle,
|
Imports, InstanceAllocationRequest, InstanceAllocator, InstanceHandle,
|
||||||
OnDemandInstanceAllocator, VMContext, VMFunctionBody, VMSharedSignatureIndex, VMTrampoline,
|
OnDemandInstanceAllocator, StorePtr, VMContext, VMFunctionBody, VMSharedSignatureIndex,
|
||||||
|
VMTrampoline,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TrampolineState<F> {
|
struct TrampolineState<F> {
|
||||||
@@ -56,7 +57,7 @@ unsafe extern "C" fn stub_fn<F>(
|
|||||||
// call-site, which gets unwrapped in `Trap::from_runtime` later on as we
|
// call-site, which gets unwrapped in `Trap::from_runtime` later on as we
|
||||||
// convert from the internal `Trap` type to our own `Trap` type in this
|
// convert from the internal `Trap` type to our own `Trap` type in this
|
||||||
// crate.
|
// crate.
|
||||||
Ok(Err(trap)) => wasmtime_runtime::raise_user_trap(Box::new(trap)),
|
Ok(Err(trap)) => wasmtime_runtime::raise_user_trap(trap.into()),
|
||||||
|
|
||||||
// And finally if the imported function panicked, then we trigger the
|
// And finally if the imported function panicked, then we trigger the
|
||||||
// form of unwinding that's safe to jump over wasm code on all
|
// form of unwinding that's safe to jump over wasm code on all
|
||||||
@@ -131,7 +132,7 @@ pub unsafe fn create_raw_function(
|
|||||||
imports: Imports::default(),
|
imports: Imports::default(),
|
||||||
shared_signatures: sig.into(),
|
shared_signatures: sig.into(),
|
||||||
host_state,
|
host_state,
|
||||||
store: None,
|
store: StorePtr::empty(),
|
||||||
wasm_data: &[],
|
wasm_data: &[],
|
||||||
})?,
|
})?,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -385,7 +385,10 @@ impl std::error::Error for Trap {
|
|||||||
|
|
||||||
impl From<anyhow::Error> for Trap {
|
impl From<anyhow::Error> for Trap {
|
||||||
fn from(e: anyhow::Error) -> Trap {
|
fn from(e: anyhow::Error) -> Trap {
|
||||||
Box::<dyn std::error::Error + Send + Sync>::from(e).into()
|
match e.downcast::<Trap>() {
|
||||||
|
Ok(trap) => trap,
|
||||||
|
Err(e) => Box::<dyn std::error::Error + Send + Sync>::from(e).into(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,12 @@ fn test_limits() -> Result<()> {
|
|||||||
let engine = Engine::default();
|
let engine = Engine::default();
|
||||||
let module = Module::new(
|
let module = Module::new(
|
||||||
&engine,
|
&engine,
|
||||||
r#"(module (memory (export "m") 0) (table (export "t") 0 anyfunc))"#,
|
r#"(module
|
||||||
|
(memory $m (export "m") 0)
|
||||||
|
(table (export "t") 0 anyfunc)
|
||||||
|
(func (export "grow") (param i32) (result i32)
|
||||||
|
(memory.grow $m (local.get 0)))
|
||||||
|
)"#,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut store = Store::new(
|
let mut store = Store::new(
|
||||||
@@ -62,6 +67,118 @@ fn test_limits() -> Result<()> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make a new store and instance to test memory grow through wasm
|
||||||
|
let mut store = Store::new(
|
||||||
|
&engine,
|
||||||
|
StoreLimitsBuilder::new()
|
||||||
|
.memory_size(10 * WASM_PAGE_SIZE)
|
||||||
|
.table_elements(5)
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
store.limiter(|s| s as &mut dyn ResourceLimiter);
|
||||||
|
let instance = Instance::new(&mut store, &module, &[])?;
|
||||||
|
let grow = instance.get_func(&mut store, "grow").unwrap();
|
||||||
|
let grow = grow.typed::<i32, i32, _>(&store).unwrap();
|
||||||
|
|
||||||
|
grow.call(&mut store, 3).unwrap();
|
||||||
|
grow.call(&mut store, 5).unwrap();
|
||||||
|
grow.call(&mut store, 2).unwrap();
|
||||||
|
|
||||||
|
// Wasm grow failure returns -1.
|
||||||
|
assert_eq!(grow.call(&mut store, 1).unwrap(), -1);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_limits_async() -> Result<()> {
|
||||||
|
let mut config = Config::new();
|
||||||
|
config.async_support(true);
|
||||||
|
let engine = Engine::new(&config).unwrap();
|
||||||
|
let module = Module::new(
|
||||||
|
&engine,
|
||||||
|
r#"(module (memory (export "m") 0) (table (export "t") 0 anyfunc))"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
struct LimitsAsync {
|
||||||
|
memory_size: usize,
|
||||||
|
table_elements: u32,
|
||||||
|
}
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl ResourceLimiterAsync for LimitsAsync {
|
||||||
|
async fn memory_growing(
|
||||||
|
&mut self,
|
||||||
|
_current: usize,
|
||||||
|
desired: usize,
|
||||||
|
_maximum: Option<usize>,
|
||||||
|
) -> bool {
|
||||||
|
desired <= self.memory_size
|
||||||
|
}
|
||||||
|
async fn table_growing(
|
||||||
|
&mut self,
|
||||||
|
_current: u32,
|
||||||
|
desired: u32,
|
||||||
|
_maximum: Option<u32>,
|
||||||
|
) -> bool {
|
||||||
|
desired <= self.table_elements
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut store = Store::new(
|
||||||
|
&engine,
|
||||||
|
LimitsAsync {
|
||||||
|
memory_size: 10 * WASM_PAGE_SIZE,
|
||||||
|
table_elements: 5,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
store.limiter_async(|s| s as &mut dyn ResourceLimiterAsync);
|
||||||
|
|
||||||
|
let instance = Instance::new_async(&mut store, &module, &[]).await?;
|
||||||
|
|
||||||
|
// Test instance exports and host objects hitting the limit
|
||||||
|
for memory in std::array::IntoIter::new([
|
||||||
|
instance.get_memory(&mut store, "m").unwrap(),
|
||||||
|
Memory::new_async(&mut store, MemoryType::new(0, None)).await?,
|
||||||
|
]) {
|
||||||
|
memory.grow_async(&mut store, 3).await?;
|
||||||
|
memory.grow_async(&mut store, 5).await?;
|
||||||
|
memory.grow_async(&mut store, 2).await?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
memory
|
||||||
|
.grow_async(&mut store, 1)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
.unwrap_err(),
|
||||||
|
"failed to grow memory by `1`"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test instance exports and host objects hitting the limit
|
||||||
|
for table in std::array::IntoIter::new([
|
||||||
|
instance.get_table(&mut store, "t").unwrap(),
|
||||||
|
Table::new_async(
|
||||||
|
&mut store,
|
||||||
|
TableType::new(ValType::FuncRef, 0, None),
|
||||||
|
Val::FuncRef(None),
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
|
]) {
|
||||||
|
table.grow_async(&mut store, 2, Val::FuncRef(None)).await?;
|
||||||
|
table.grow_async(&mut store, 1, Val::FuncRef(None)).await?;
|
||||||
|
table.grow_async(&mut store, 2, Val::FuncRef(None)).await?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
table
|
||||||
|
.grow_async(&mut store, 1, Val::FuncRef(None))
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
.unwrap_err(),
|
||||||
|
"failed to grow table by `1`"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,14 +417,13 @@ impl ResourceLimiter for MemoryContext {
|
|||||||
self.wasm_memory_used = desired;
|
self.wasm_memory_used = desired;
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn table_growing(&mut self, _current: u32, _desired: u32, _maximum: Option<u32>) -> bool {
|
fn table_growing(&mut self, _current: u32, _desired: u32, _maximum: Option<u32>) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_custom_limiter() -> Result<()> {
|
fn test_custom_memory_limiter() -> Result<()> {
|
||||||
let engine = Engine::default();
|
let engine = Engine::default();
|
||||||
let mut linker = Linker::new(&engine);
|
let mut linker = Linker::new(&engine);
|
||||||
|
|
||||||
@@ -386,29 +502,221 @@ fn test_custom_limiter() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[async_trait::async_trait]
|
||||||
struct MemoryGrowFailureDetector {
|
impl ResourceLimiterAsync for MemoryContext {
|
||||||
/// Arguments of most recent call to memory_growing
|
async fn memory_growing(
|
||||||
current: usize,
|
&mut self,
|
||||||
desired: usize,
|
current: usize,
|
||||||
/// Display impl of most recent call to memory_grow_failed
|
desired: usize,
|
||||||
error: Option<String>,
|
maximum: Option<usize>,
|
||||||
|
) -> bool {
|
||||||
|
// Show we can await in this async context:
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
|
||||||
|
// Check if the desired exceeds a maximum (either from Wasm or from the host)
|
||||||
|
assert!(desired < maximum.unwrap_or(usize::MAX));
|
||||||
|
|
||||||
|
assert_eq!(current as usize, self.wasm_memory_used);
|
||||||
|
let desired = desired as usize;
|
||||||
|
|
||||||
|
if desired + self.host_memory_used > self.memory_limit {
|
||||||
|
self.limit_exceeded = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.wasm_memory_used = desired;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
async fn table_growing(&mut self, _current: u32, _desired: u32, _maximum: Option<u32>) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
fn table_grow_failed(&mut self, _e: &anyhow::Error) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResourceLimiter for MemoryGrowFailureDetector {
|
#[tokio::test]
|
||||||
|
async fn test_custom_memory_limiter_async() -> Result<()> {
|
||||||
|
let mut config = Config::new();
|
||||||
|
config.async_support(true);
|
||||||
|
let engine = Engine::new(&config).unwrap();
|
||||||
|
let mut linker = Linker::new(&engine);
|
||||||
|
|
||||||
|
// This approximates a function that would "allocate" resources that the host tracks.
|
||||||
|
// Here this is a simple function that increments the current host memory "used".
|
||||||
|
linker.func_wrap(
|
||||||
|
"",
|
||||||
|
"alloc",
|
||||||
|
|mut caller: Caller<'_, MemoryContext>, size: u32| -> u32 {
|
||||||
|
let mut ctx = caller.data_mut();
|
||||||
|
let size = size as usize;
|
||||||
|
|
||||||
|
if size + ctx.host_memory_used + ctx.wasm_memory_used <= ctx.memory_limit {
|
||||||
|
ctx.host_memory_used += size;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.limit_exceeded = true;
|
||||||
|
|
||||||
|
0
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let module = Module::new(
|
||||||
|
&engine,
|
||||||
|
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 = 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: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut store = Store::new(&engine, context);
|
||||||
|
store.limiter_async(|s| s as &mut dyn ResourceLimiterAsync);
|
||||||
|
let instance = linker.instantiate_async(&mut store, &module).await?;
|
||||||
|
let memory = instance.get_memory(&mut store, "m").unwrap();
|
||||||
|
|
||||||
|
// Grow the memory by 640 KiB
|
||||||
|
memory.grow_async(&mut store, 3).await?;
|
||||||
|
memory.grow_async(&mut store, 5).await?;
|
||||||
|
memory.grow_async(&mut store, 2).await?;
|
||||||
|
|
||||||
|
assert!(!store.data().limit_exceeded);
|
||||||
|
|
||||||
|
// Grow the host "memory" by 384 KiB
|
||||||
|
let f = instance.get_typed_func::<u32, u32, _>(&mut store, "f")?;
|
||||||
|
|
||||||
|
assert_eq!(f.call_async(&mut store, 1 * 0x10000).await?, 1);
|
||||||
|
assert_eq!(f.call_async(&mut store, 3 * 0x10000).await?, 1);
|
||||||
|
assert_eq!(f.call_async(&mut store, 2 * 0x10000).await?, 1);
|
||||||
|
|
||||||
|
// Memory is at the maximum, but the limit hasn't been exceeded
|
||||||
|
assert!(!store.data().limit_exceeded);
|
||||||
|
|
||||||
|
// Try to grow the memory again
|
||||||
|
assert_eq!(
|
||||||
|
memory
|
||||||
|
.grow_async(&mut store, 1)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
.unwrap_err(),
|
||||||
|
"failed to grow memory by `1`"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(store.data().limit_exceeded);
|
||||||
|
|
||||||
|
// Try to grow the host "memory" again
|
||||||
|
assert_eq!(f.call_async(&mut store, 1).await?, 0);
|
||||||
|
|
||||||
|
assert!(store.data().limit_exceeded);
|
||||||
|
|
||||||
|
drop(store);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TableContext {
|
||||||
|
elements_used: u32,
|
||||||
|
element_limit: u32,
|
||||||
|
limit_exceeded: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResourceLimiter for TableContext {
|
||||||
|
fn memory_growing(
|
||||||
|
&mut self,
|
||||||
|
_current: usize,
|
||||||
|
_desired: usize,
|
||||||
|
_maximum: Option<usize>,
|
||||||
|
) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
fn table_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)
|
||||||
|
assert!(desired < maximum.unwrap_or(u32::MAX));
|
||||||
|
assert_eq!(current, self.elements_used);
|
||||||
|
if desired > self.element_limit {
|
||||||
|
self.limit_exceeded = true;
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
self.elements_used = desired;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_custom_table_limiter() -> Result<()> {
|
||||||
|
let engine = Engine::default();
|
||||||
|
let linker = Linker::new(&engine);
|
||||||
|
|
||||||
|
let module = Module::new(&engine, r#"(module (table (export "t") 0 anyfunc))"#)?;
|
||||||
|
|
||||||
|
let context = TableContext {
|
||||||
|
elements_used: 0,
|
||||||
|
element_limit: 10,
|
||||||
|
limit_exceeded: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut store = Store::new(&engine, context);
|
||||||
|
store.limiter(|s| s as &mut dyn ResourceLimiter);
|
||||||
|
let instance = linker.instantiate(&mut store, &module)?;
|
||||||
|
let table = instance.get_table(&mut store, "t").unwrap();
|
||||||
|
|
||||||
|
// Grow the table by 10 elements
|
||||||
|
table.grow(&mut store, 3, Val::FuncRef(None))?;
|
||||||
|
table.grow(&mut store, 5, Val::FuncRef(None))?;
|
||||||
|
table.grow(&mut store, 2, Val::FuncRef(None))?;
|
||||||
|
|
||||||
|
assert!(!store.data().limit_exceeded);
|
||||||
|
|
||||||
|
// Table is at the maximum, but the limit hasn't been exceeded
|
||||||
|
assert!(!store.data().limit_exceeded);
|
||||||
|
|
||||||
|
// Try to grow the memory again
|
||||||
|
assert_eq!(
|
||||||
|
table
|
||||||
|
.grow(&mut store, 1, Val::FuncRef(None))
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
.unwrap_err(),
|
||||||
|
"failed to grow table by `1`"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(store.data().limit_exceeded);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct FailureDetector {
|
||||||
|
/// Arguments of most recent call to memory_growing
|
||||||
|
memory_current: usize,
|
||||||
|
memory_desired: usize,
|
||||||
|
/// Display impl of most recent call to memory_grow_failed
|
||||||
|
memory_error: Option<String>,
|
||||||
|
/// Arguments of most recent call to table_growing
|
||||||
|
table_current: u32,
|
||||||
|
table_desired: u32,
|
||||||
|
/// Display impl of most recent call to table_grow_failed
|
||||||
|
table_error: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResourceLimiter for FailureDetector {
|
||||||
fn memory_growing(&mut self, current: usize, desired: usize, _maximum: Option<usize>) -> bool {
|
fn memory_growing(&mut self, current: usize, desired: usize, _maximum: Option<usize>) -> bool {
|
||||||
self.current = current;
|
self.memory_current = current;
|
||||||
self.desired = desired;
|
self.memory_desired = desired;
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn memory_grow_failed(&mut self, err: &anyhow::Error) {
|
fn memory_grow_failed(&mut self, err: &anyhow::Error) {
|
||||||
self.error = Some(err.to_string());
|
self.memory_error = Some(err.to_string());
|
||||||
}
|
}
|
||||||
|
fn table_growing(&mut self, current: u32, desired: u32, _maximum: Option<u32>) -> bool {
|
||||||
fn table_growing(&mut self, _current: u32, _desired: u32, _maximum: Option<u32>) -> bool {
|
self.table_current = current;
|
||||||
|
self.table_desired = desired;
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
fn table_grow_failed(&mut self, err: &anyhow::Error) {
|
||||||
|
self.table_error = Some(err.to_string());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -421,6 +729,7 @@ fn custom_limiter_detect_grow_failure() -> Result<()> {
|
|||||||
strategy: PoolingAllocationStrategy::NextAvailable,
|
strategy: PoolingAllocationStrategy::NextAvailable,
|
||||||
module_limits: ModuleLimits {
|
module_limits: ModuleLimits {
|
||||||
memory_pages: 10,
|
memory_pages: 10,
|
||||||
|
table_elements: 10,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
instance_limits: InstanceLimits {
|
instance_limits: InstanceLimits {
|
||||||
@@ -430,9 +739,12 @@ fn custom_limiter_detect_grow_failure() -> Result<()> {
|
|||||||
let engine = Engine::new(&config).unwrap();
|
let engine = Engine::new(&config).unwrap();
|
||||||
let linker = Linker::new(&engine);
|
let linker = Linker::new(&engine);
|
||||||
|
|
||||||
let module = Module::new(&engine, r#"(module (memory (export "m") 0))"#)?;
|
let module = Module::new(
|
||||||
|
&engine,
|
||||||
|
r#"(module (memory (export "m") 0) (table (export "t") 0 anyfunc))"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
let context = MemoryGrowFailureDetector::default();
|
let context = FailureDetector::default();
|
||||||
|
|
||||||
let mut store = Store::new(&engine, context);
|
let mut store = Store::new(&engine, context);
|
||||||
store.limiter(|s| s as &mut dyn ResourceLimiter);
|
store.limiter(|s| s as &mut dyn ResourceLimiter);
|
||||||
@@ -442,25 +754,332 @@ fn custom_limiter_detect_grow_failure() -> Result<()> {
|
|||||||
// Grow the memory by 640 KiB (10 pages)
|
// Grow the memory by 640 KiB (10 pages)
|
||||||
memory.grow(&mut store, 10)?;
|
memory.grow(&mut store, 10)?;
|
||||||
|
|
||||||
assert!(store.data().error.is_none());
|
assert!(store.data().memory_error.is_none());
|
||||||
assert_eq!(store.data().current, 0);
|
assert_eq!(store.data().memory_current, 0);
|
||||||
assert_eq!(store.data().desired, 10 * 64 * 1024);
|
assert_eq!(store.data().memory_desired, 10 * 64 * 1024);
|
||||||
|
|
||||||
// Grow past the static limit set by ModuleLimits.
|
// Grow past the static limit set by ModuleLimits.
|
||||||
// The ResourcLimiter will permit this, but the grow will fail.
|
// The ResourceLimiter will permit this, but the grow will fail.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
memory.grow(&mut store, 1).unwrap_err().to_string(),
|
memory.grow(&mut store, 1).unwrap_err().to_string(),
|
||||||
"failed to grow memory by `1`"
|
"failed to grow memory by `1`"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(store.data().current, 10 * 64 * 1024);
|
assert_eq!(store.data().memory_current, 10 * 64 * 1024);
|
||||||
assert_eq!(store.data().desired, 11 * 64 * 1024);
|
assert_eq!(store.data().memory_desired, 11 * 64 * 1024);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
store.data().error.as_ref().unwrap(),
|
store.data().memory_error.as_ref().unwrap(),
|
||||||
"Memory maximum size exceeded"
|
"Memory maximum size exceeded"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let table = instance.get_table(&mut store, "t").unwrap();
|
||||||
|
// Grow the table 10 elements
|
||||||
|
table.grow(&mut store, 10, Val::FuncRef(None))?;
|
||||||
|
|
||||||
|
assert!(store.data().table_error.is_none());
|
||||||
|
assert_eq!(store.data().table_current, 0);
|
||||||
|
assert_eq!(store.data().table_desired, 10);
|
||||||
|
|
||||||
|
// Grow past the static limit set by ModuleLimits.
|
||||||
|
// The ResourceLimiter will permit this, but the grow will fail.
|
||||||
|
assert_eq!(
|
||||||
|
table
|
||||||
|
.grow(&mut store, 1, Val::FuncRef(None))
|
||||||
|
.unwrap_err()
|
||||||
|
.to_string(),
|
||||||
|
"failed to grow table by `1`"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(store.data().table_current, 10);
|
||||||
|
assert_eq!(store.data().table_desired, 11);
|
||||||
|
assert_eq!(
|
||||||
|
store.data().table_error.as_ref().unwrap(),
|
||||||
|
"Table maximum size exceeded"
|
||||||
|
);
|
||||||
|
|
||||||
drop(store);
|
drop(store);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl ResourceLimiterAsync for FailureDetector {
|
||||||
|
async fn memory_growing(
|
||||||
|
&mut self,
|
||||||
|
current: usize,
|
||||||
|
desired: usize,
|
||||||
|
_maximum: Option<usize>,
|
||||||
|
) -> bool {
|
||||||
|
// Show we can await in this async context:
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
|
||||||
|
self.memory_current = current;
|
||||||
|
self.memory_desired = desired;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
fn memory_grow_failed(&mut self, err: &anyhow::Error) {
|
||||||
|
self.memory_error = Some(err.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn table_growing(&mut self, current: u32, desired: u32, _maximum: Option<u32>) -> bool {
|
||||||
|
self.table_current = current;
|
||||||
|
self.table_desired = desired;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
fn table_grow_failed(&mut self, err: &anyhow::Error) {
|
||||||
|
self.table_error = Some(err.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn custom_limiter_async_detect_grow_failure() -> Result<()> {
|
||||||
|
if std::env::var("WASMTIME_TEST_NO_HOG_MEMORY").is_ok() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let mut config = Config::new();
|
||||||
|
config.async_support(true);
|
||||||
|
config.allocation_strategy(InstanceAllocationStrategy::Pooling {
|
||||||
|
strategy: PoolingAllocationStrategy::NextAvailable,
|
||||||
|
module_limits: ModuleLimits {
|
||||||
|
memory_pages: 10,
|
||||||
|
table_elements: 10,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
instance_limits: InstanceLimits {
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let engine = Engine::new(&config).unwrap();
|
||||||
|
let linker = Linker::new(&engine);
|
||||||
|
|
||||||
|
let module = Module::new(
|
||||||
|
&engine,
|
||||||
|
r#"(module (memory (export "m") 0) (table (export "t") 0 anyfunc))"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let context = FailureDetector::default();
|
||||||
|
|
||||||
|
let mut store = Store::new(&engine, context);
|
||||||
|
store.limiter_async(|s| s as &mut dyn ResourceLimiterAsync);
|
||||||
|
let instance = linker.instantiate_async(&mut store, &module).await?;
|
||||||
|
let memory = instance.get_memory(&mut store, "m").unwrap();
|
||||||
|
|
||||||
|
// Grow the memory by 640 KiB (10 pages)
|
||||||
|
memory.grow_async(&mut store, 10).await?;
|
||||||
|
|
||||||
|
assert!(store.data().memory_error.is_none());
|
||||||
|
assert_eq!(store.data().memory_current, 0);
|
||||||
|
assert_eq!(store.data().memory_desired, 10 * 64 * 1024);
|
||||||
|
|
||||||
|
// Grow past the static limit set by ModuleLimits.
|
||||||
|
// The ResourcLimiterAsync will permit this, but the grow will fail.
|
||||||
|
assert_eq!(
|
||||||
|
memory
|
||||||
|
.grow_async(&mut store, 1)
|
||||||
|
.await
|
||||||
|
.unwrap_err()
|
||||||
|
.to_string(),
|
||||||
|
"failed to grow memory by `1`"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(store.data().memory_current, 10 * 64 * 1024);
|
||||||
|
assert_eq!(store.data().memory_desired, 11 * 64 * 1024);
|
||||||
|
assert_eq!(
|
||||||
|
store.data().memory_error.as_ref().unwrap(),
|
||||||
|
"Memory maximum size exceeded"
|
||||||
|
);
|
||||||
|
|
||||||
|
let table = instance.get_table(&mut store, "t").unwrap();
|
||||||
|
// Grow the table 10 elements
|
||||||
|
table.grow_async(&mut store, 10, Val::FuncRef(None)).await?;
|
||||||
|
|
||||||
|
assert!(store.data().table_error.is_none());
|
||||||
|
assert_eq!(store.data().table_current, 0);
|
||||||
|
assert_eq!(store.data().table_desired, 10);
|
||||||
|
|
||||||
|
// Grow past the static limit set by ModuleLimits.
|
||||||
|
// The ResourceLimiter will permit this, but the grow will fail.
|
||||||
|
assert_eq!(
|
||||||
|
table
|
||||||
|
.grow_async(&mut store, 1, Val::FuncRef(None))
|
||||||
|
.await
|
||||||
|
.unwrap_err()
|
||||||
|
.to_string(),
|
||||||
|
"failed to grow table by `1`"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(store.data().table_current, 10);
|
||||||
|
assert_eq!(store.data().table_desired, 11);
|
||||||
|
assert_eq!(
|
||||||
|
store.data().table_error.as_ref().unwrap(),
|
||||||
|
"Table maximum size exceeded"
|
||||||
|
);
|
||||||
|
|
||||||
|
drop(store);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Panic;
|
||||||
|
|
||||||
|
impl ResourceLimiter for Panic {
|
||||||
|
fn memory_growing(
|
||||||
|
&mut self,
|
||||||
|
_current: usize,
|
||||||
|
_desired: usize,
|
||||||
|
_maximum: Option<usize>,
|
||||||
|
) -> bool {
|
||||||
|
panic!("resource limiter memory growing");
|
||||||
|
}
|
||||||
|
fn table_growing(&mut self, _current: u32, _desired: u32, _maximum: Option<u32>) -> bool {
|
||||||
|
panic!("resource limiter table growing");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl ResourceLimiterAsync for Panic {
|
||||||
|
async fn memory_growing(
|
||||||
|
&mut self,
|
||||||
|
_current: usize,
|
||||||
|
_desired: usize,
|
||||||
|
_maximum: Option<usize>,
|
||||||
|
) -> bool {
|
||||||
|
panic!("async resource limiter memory growing");
|
||||||
|
}
|
||||||
|
async fn table_growing(&mut self, _current: u32, _desired: u32, _maximum: Option<u32>) -> bool {
|
||||||
|
panic!("async resource limiter table growing");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "resource limiter memory growing")]
|
||||||
|
fn panic_in_memory_limiter() {
|
||||||
|
let engine = Engine::default();
|
||||||
|
let linker = Linker::new(&engine);
|
||||||
|
|
||||||
|
let module = Module::new(&engine, r#"(module (memory (export "m") 0))"#).unwrap();
|
||||||
|
|
||||||
|
let mut store = Store::new(&engine, Panic);
|
||||||
|
store.limiter(|s| s as &mut dyn ResourceLimiter);
|
||||||
|
let instance = linker.instantiate(&mut store, &module).unwrap();
|
||||||
|
let memory = instance.get_memory(&mut store, "m").unwrap();
|
||||||
|
|
||||||
|
// Grow the memory, which should panic
|
||||||
|
memory.grow(&mut store, 3).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "resource limiter memory growing")]
|
||||||
|
fn panic_in_memory_limiter_wasm_stack() {
|
||||||
|
// Like the test above, except the memory.grow happens in wasm code
|
||||||
|
// instead of a host function call.
|
||||||
|
let engine = Engine::default();
|
||||||
|
let linker = Linker::new(&engine);
|
||||||
|
|
||||||
|
let module = Module::new(
|
||||||
|
&engine,
|
||||||
|
r#"
|
||||||
|
(module
|
||||||
|
(memory $m (export "m") 0)
|
||||||
|
(func (export "grow") (param i32) (result i32)
|
||||||
|
(memory.grow $m (local.get 0)))
|
||||||
|
)"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut store = Store::new(&engine, Panic);
|
||||||
|
store.limiter(|s| s as &mut dyn ResourceLimiter);
|
||||||
|
let instance = linker.instantiate(&mut store, &module).unwrap();
|
||||||
|
let grow = instance.get_func(&mut store, "grow").unwrap();
|
||||||
|
let grow = grow.typed::<i32, i32, _>(&store).unwrap();
|
||||||
|
|
||||||
|
// Grow the memory, which should panic
|
||||||
|
grow.call(&mut store, 3).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "resource limiter table growing")]
|
||||||
|
fn panic_in_table_limiter() {
|
||||||
|
let engine = Engine::default();
|
||||||
|
let linker = Linker::new(&engine);
|
||||||
|
|
||||||
|
let module = Module::new(&engine, r#"(module (table (export "t") 0 anyfunc))"#).unwrap();
|
||||||
|
|
||||||
|
let mut store = Store::new(&engine, Panic);
|
||||||
|
store.limiter(|s| s as &mut dyn ResourceLimiter);
|
||||||
|
let instance = linker.instantiate(&mut store, &module).unwrap();
|
||||||
|
let table = instance.get_table(&mut store, "t").unwrap();
|
||||||
|
|
||||||
|
// Grow the table, which should panic
|
||||||
|
table.grow(&mut store, 3, Val::FuncRef(None)).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[should_panic(expected = "async resource limiter memory growing")]
|
||||||
|
async fn panic_in_async_memory_limiter() {
|
||||||
|
let mut config = Config::new();
|
||||||
|
config.async_support(true);
|
||||||
|
let engine = Engine::new(&config).unwrap();
|
||||||
|
let linker = Linker::new(&engine);
|
||||||
|
|
||||||
|
let module = Module::new(&engine, r#"(module (memory (export "m") 0))"#).unwrap();
|
||||||
|
|
||||||
|
let mut store = Store::new(&engine, Panic);
|
||||||
|
store.limiter_async(|s| s as &mut dyn ResourceLimiterAsync);
|
||||||
|
let instance = linker.instantiate_async(&mut store, &module).await.unwrap();
|
||||||
|
let memory = instance.get_memory(&mut store, "m").unwrap();
|
||||||
|
|
||||||
|
// Grow the memory, which should panic
|
||||||
|
memory.grow_async(&mut store, 3).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[should_panic(expected = "async resource limiter memory growing")]
|
||||||
|
async fn panic_in_async_memory_limiter_wasm_stack() {
|
||||||
|
// Like the test above, except the memory.grow happens in
|
||||||
|
// wasm code instead of a host function call.
|
||||||
|
let mut config = Config::new();
|
||||||
|
config.async_support(true);
|
||||||
|
let engine = Engine::new(&config).unwrap();
|
||||||
|
let linker = Linker::new(&engine);
|
||||||
|
|
||||||
|
let module = Module::new(
|
||||||
|
&engine,
|
||||||
|
r#"
|
||||||
|
(module
|
||||||
|
(memory $m (export "m") 0)
|
||||||
|
(func (export "grow") (param i32) (result i32)
|
||||||
|
(memory.grow $m (local.get 0)))
|
||||||
|
)"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut store = Store::new(&engine, Panic);
|
||||||
|
store.limiter_async(|s| s as &mut dyn ResourceLimiterAsync);
|
||||||
|
let instance = linker.instantiate_async(&mut store, &module).await.unwrap();
|
||||||
|
let grow = instance.get_func(&mut store, "grow").unwrap();
|
||||||
|
let grow = grow.typed::<i32, i32, _>(&store).unwrap();
|
||||||
|
|
||||||
|
// Grow the memory, which should panic
|
||||||
|
grow.call_async(&mut store, 3).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[should_panic(expected = "async resource limiter table growing")]
|
||||||
|
async fn panic_in_async_table_limiter() {
|
||||||
|
let mut config = Config::new();
|
||||||
|
config.async_support(true);
|
||||||
|
let engine = Engine::new(&config).unwrap();
|
||||||
|
let linker = Linker::new(&engine);
|
||||||
|
|
||||||
|
let module = Module::new(&engine, r#"(module (table (export "t") 0 anyfunc))"#).unwrap();
|
||||||
|
|
||||||
|
let mut store = Store::new(&engine, Panic);
|
||||||
|
store.limiter_async(|s| s as &mut dyn ResourceLimiterAsync);
|
||||||
|
let instance = linker.instantiate_async(&mut store, &module).await.unwrap();
|
||||||
|
let table = instance.get_table(&mut store, "t").unwrap();
|
||||||
|
|
||||||
|
// Grow the table, which should panic
|
||||||
|
table
|
||||||
|
.grow_async(&mut store, 3, Val::FuncRef(None))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|||||||
@@ -311,12 +311,16 @@ fn massive_64_bit_still_limited() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ResourceLimiter for MyLimiter {
|
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;
|
self.hit = true;
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
fn table_growing(&mut self, _current: u32, _request: u32, _max: Option<u32>) -> bool {
|
||||||
fn table_growing(&mut self, _request: u32, _min: u32, _max: Option<u32>) -> bool {
|
|
||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,11 +18,9 @@ impl ResourceLimiter for MemoryGrowFailureDetector {
|
|||||||
self.desired = desired;
|
self.desired = desired;
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn memory_grow_failed(&mut self, err: &anyhow::Error) {
|
fn memory_grow_failed(&mut self, err: &anyhow::Error) {
|
||||||
self.error = Some(err.to_string());
|
self.error = Some(err.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn table_growing(&mut self, _current: u32, _desired: u32, _maximum: Option<u32>) -> bool {
|
fn table_growing(&mut self, _current: u32, _desired: u32, _maximum: Option<u32>) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user