summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHenry <mail@henrygressmann.de>2026-04-21 20:14:04 +0200
committerHenry <mail@henrygressmann.de>2026-04-21 20:25:19 +0200
commit238e7aa76102cf64fa22ac5774326fcf35bc3792 (patch)
tree50faea9c9fccdda22119c1b6bb38867cd9bf1dfd
parente667ae4554e816b4081d874fc3564b07c28dbec6 (diff)
feat: oom traps
Signed-off-by: Henry <mail@henrygressmann.de>
-rw-r--r--crates/parser/src/conversion.rs6
-rw-r--r--crates/parser/src/module.rs6
-rw-r--r--crates/tinywasm/benches/memory_backends.rs50
-rw-r--r--crates/tinywasm/src/engine.rs94
-rw-r--r--crates/tinywasm/src/error.rs12
-rw-r--r--crates/tinywasm/src/func.rs14
-rw-r--r--crates/tinywasm/src/instance.rs4
-rw-r--r--crates/tinywasm/src/interpreter/executor.rs14
-rw-r--r--crates/tinywasm/src/interpreter/stack/call_stack.rs32
-rw-r--r--crates/tinywasm/src/interpreter/stack/value_stack.rs74
-rw-r--r--crates/tinywasm/src/lib.rs2
-rw-r--r--crates/tinywasm/src/reference.rs2
-rw-r--r--crates/tinywasm/src/store/function.rs4
-rw-r--r--crates/tinywasm/src/store/memory/instance.rs35
-rw-r--r--crates/tinywasm/src/store/memory/lazy.rs60
-rw-r--r--crates/tinywasm/src/store/memory/mod.rs20
-rw-r--r--crates/tinywasm/src/store/memory/paged.rs100
-rw-r--r--crates/tinywasm/src/store/memory/vec.rs30
-rw-r--r--crates/tinywasm/tests/memory_backends.rs10
-rw-r--r--crates/tinywasm/tests/testsuite/run.rs2
20 files changed, 430 insertions, 141 deletions
diff --git a/crates/parser/src/conversion.rs b/crates/parser/src/conversion.rs
index 5de3a73..08f3c83 100644
--- a/crates/parser/src/conversion.rs
+++ b/crates/parser/src/conversion.rs
@@ -1,3 +1,5 @@
+use alloc::sync::Arc;
+
use crate::{Result, module::Code, visit::process_operators_and_validate};
use alloc::{boxed::Box, format, string::ToString, vec::Vec};
use tinywasm_types::*;
@@ -201,7 +203,7 @@ pub(crate) fn convert_module_code(
Ok(((body, data, local_counts), allocations))
}
-pub(crate) fn convert_module_type(ty: wasmparser::RecGroup) -> Result<FuncType> {
+pub(crate) fn convert_module_type(ty: wasmparser::RecGroup) -> Result<Arc<FuncType>> {
let mut types = ty.types();
if types.len() != 1 {
return Err(crate::ParseError::UnsupportedOperator(
@@ -212,7 +214,7 @@ pub(crate) fn convert_module_type(ty: wasmparser::RecGroup) -> Result<FuncType>
let ty = types.next().unwrap().unwrap_func();
let params: Vec<_> = ty.params().iter().map(convert_valtype).collect();
let results: Vec<_> = ty.results().iter().map(convert_valtype).collect();
- Ok(FuncType::new(&params, &results))
+ Ok(FuncType::new(&params, &results).into())
}
pub(crate) fn convert_reftype(reftype: wasmparser::RefType) -> WasmType {
diff --git a/crates/parser/src/module.rs b/crates/parser/src/module.rs
index 47257fe..164127f 100644
--- a/crates/parser/src/module.rs
+++ b/crates/parser/src/module.rs
@@ -52,10 +52,8 @@ impl ModuleReader {
debug!("Found type section");
validator.type_section(&reader)?;
- self.func_types = reader
- .into_iter()
- .map(|t| conversion::convert_module_type(t?).map(Arc::new))
- .collect::<Result<Vec<Arc<FuncType>>>>()?;
+ self.func_types =
+ reader.into_iter().map(|t| conversion::convert_module_type(t?)).collect::<Result<Vec<_>>>()?;
}
Payload::GlobalSection(reader) => {
diff --git a/crates/tinywasm/benches/memory_backends.rs b/crates/tinywasm/benches/memory_backends.rs
index bbc1b31..3a59f8c 100644
--- a/crates/tinywasm/benches/memory_backends.rs
+++ b/crates/tinywasm/benches/memory_backends.rs
@@ -74,43 +74,73 @@ fn criterion_benchmark(c: &mut Criterion) {
let mut group = c.benchmark_group("memory_backends");
group.measurement_time(BENCH_MEASUREMENT_TIME);
- bench_grow(&mut group, "vec", || VecMemory::new(PAGE_SIZE));
- bench_grow(&mut group, "paged", || PagedMemory::new(PAGE_SIZE, CHUNK_SIZE));
+ bench_grow(&mut group, "vec", || VecMemory::try_new(PAGE_SIZE).expect("bench memory should be constructible"));
+ bench_grow(&mut group, "paged", || {
+ PagedMemory::try_new(PAGE_SIZE, CHUNK_SIZE).expect("bench memory should be constructible")
+ });
- bench_write_all(&mut group, "vec", "contiguous", VecMemory::new(MEMORY_LEN), CONTIGUOUS_OFFSET, CONTIGUOUS_LEN);
+ bench_write_all(
+ &mut group,
+ "vec",
+ "contiguous",
+ VecMemory::try_new(MEMORY_LEN).expect("bench memory should be constructible"),
+ CONTIGUOUS_OFFSET,
+ CONTIGUOUS_LEN,
+ );
bench_write_all(
&mut group,
"paged",
"contiguous",
- PagedMemory::new(MEMORY_LEN, CHUNK_SIZE),
+ PagedMemory::try_new(MEMORY_LEN, CHUNK_SIZE).expect("bench memory should be constructible"),
+ CONTIGUOUS_OFFSET,
+ CONTIGUOUS_LEN,
+ );
+ bench_read_exact(
+ &mut group,
+ "vec",
+ "contiguous",
+ VecMemory::try_new(MEMORY_LEN).expect("bench memory should be constructible"),
CONTIGUOUS_OFFSET,
CONTIGUOUS_LEN,
);
- bench_read_exact(&mut group, "vec", "contiguous", VecMemory::new(MEMORY_LEN), CONTIGUOUS_OFFSET, CONTIGUOUS_LEN);
bench_read_exact(
&mut group,
"paged",
"contiguous",
- PagedMemory::new(MEMORY_LEN, CHUNK_SIZE),
+ PagedMemory::try_new(MEMORY_LEN, CHUNK_SIZE).expect("bench memory should be constructible"),
CONTIGUOUS_OFFSET,
CONTIGUOUS_LEN,
);
- bench_write_all(&mut group, "vec", "cross_chunk", VecMemory::new(MEMORY_LEN), CROSS_CHUNK_OFFSET, CROSS_CHUNK_LEN);
+ bench_write_all(
+ &mut group,
+ "vec",
+ "cross_chunk",
+ VecMemory::try_new(MEMORY_LEN).expect("bench memory should be constructible"),
+ CROSS_CHUNK_OFFSET,
+ CROSS_CHUNK_LEN,
+ );
bench_write_all(
&mut group,
"paged",
"cross_chunk",
- PagedMemory::new(MEMORY_LEN, CHUNK_SIZE),
+ PagedMemory::try_new(MEMORY_LEN, CHUNK_SIZE).expect("bench memory should be constructible"),
+ CROSS_CHUNK_OFFSET,
+ CROSS_CHUNK_LEN,
+ );
+ bench_read_exact(
+ &mut group,
+ "vec",
+ "cross_chunk",
+ VecMemory::try_new(MEMORY_LEN).expect("bench memory should be constructible"),
CROSS_CHUNK_OFFSET,
CROSS_CHUNK_LEN,
);
- bench_read_exact(&mut group, "vec", "cross_chunk", VecMemory::new(MEMORY_LEN), CROSS_CHUNK_OFFSET, CROSS_CHUNK_LEN);
bench_read_exact(
&mut group,
"paged",
"cross_chunk",
- PagedMemory::new(MEMORY_LEN, CHUNK_SIZE),
+ PagedMemory::try_new(MEMORY_LEN, CHUNK_SIZE).expect("bench memory should be constructible"),
CROSS_CHUNK_OFFSET,
CROSS_CHUNK_LEN,
);
diff --git a/crates/tinywasm/src/engine.rs b/crates/tinywasm/src/engine.rs
index 36220fe..3b7b8b8 100644
--- a/crates/tinywasm/src/engine.rs
+++ b/crates/tinywasm/src/engine.rs
@@ -54,23 +54,50 @@ pub const DEFAULT_VALUE_STACK_128_SIZE: usize = 4 * 1024; // 4k slots
/// Default maximum size for the call stack (function frames).
pub const DEFAULT_MAX_CALL_STACK_SIZE: usize = 1024; // 1024 frames
+/// Stack allocation policy.
+#[derive(Clone, Copy, PartialEq, Eq)]
+#[cfg_attr(feature = "debug", derive(Debug))]
+pub struct StackConfig {
+ /// Initial reserved capacity for the stack.
+ pub initial_size: usize,
+ /// Maximum number of elements the stack may contain.
+ pub max_size: usize,
+ /// Whether the stack may grow past its initial capacity.
+ pub dynamic: bool,
+}
+
+impl StackConfig {
+ /// Creates a fixed-capacity stack that reserves all space up front.
+ pub const fn fixed(size: usize) -> Self {
+ Self { initial_size: size, max_size: size, dynamic: false }
+ }
+
+ /// Creates a dynamically growing stack with the given initial and maximum sizes.
+ pub const fn dynamic(initial_size: usize, max_size: usize) -> Self {
+ assert!(initial_size <= max_size, "initial_size must be less than or equal to max_size");
+ Self { initial_size, max_size, dynamic: true }
+ }
+}
+
/// Configuration for the WebAssembly interpreter
#[derive(Clone)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[non_exhaustive]
pub struct Config {
- /// Size of the 32-bit value stack (i32, f32, ref values).
- pub stack_32_size: usize,
- /// Size of the 64-bit value stack (i64, f64 values).
- pub stack_64_size: usize,
- /// Size of the 128-bit value stack (v128 values).
- pub stack_128_size: usize,
- /// Maximum size of the call stack
- pub max_call_stack_size: usize,
+ /// Configuration for the 32-bit value stack (i32, f32, ref values).
+ pub value_stack_32: StackConfig,
+ /// Configuration for the 64-bit value stack (i64, f64 values).
+ pub value_stack_64: StackConfig,
+ /// Configuration for the 128-bit value stack (v128 values).
+ pub value_stack_128: StackConfig,
+ /// Configuration for the call stack.
+ pub call_stack: StackConfig,
/// Fuel accounting policy used by budgeted execution.
pub fuel_policy: FuelPolicy,
/// Backend used for runtime memories.
pub memory_backend: MemoryBackend,
+ /// Whether memory and stack allocation failures should trap instead of degrading into normal operation failure modes.
+ pub trap_on_oom: bool,
}
impl Config {
@@ -91,6 +118,44 @@ impl Config {
self
}
+ /// Set the configuration used for the 32-bit value stack.
+ pub fn with_value_stack_32(mut self, stack: StackConfig) -> Self {
+ self.value_stack_32 = stack;
+ self
+ }
+
+ /// Set the same configuration for all value stack lanes.
+ pub fn with_value_stack(mut self, stack: StackConfig) -> Self {
+ self.value_stack_32 = stack;
+ self.value_stack_64 = stack;
+ self.value_stack_128 = stack;
+ self
+ }
+
+ /// Set the configuration used for the 64-bit value stack.
+ pub fn with_value_stack_64(mut self, stack: StackConfig) -> Self {
+ self.value_stack_64 = stack;
+ self
+ }
+
+ /// Set the configuration used for the 128-bit value stack.
+ pub fn with_value_stack_128(mut self, stack: StackConfig) -> Self {
+ self.value_stack_128 = stack;
+ self
+ }
+
+ /// Set the configuration used for the call stack.
+ pub fn with_call_stack(mut self, stack: StackConfig) -> Self {
+ self.call_stack = stack;
+ self
+ }
+
+ /// Configure whether memory and stack allocation failures trap immediately.
+ pub fn with_trap_on_oom(mut self, trap_on_oom: bool) -> Self {
+ self.trap_on_oom = trap_on_oom;
+ self
+ }
+
/// Get the current fuel policy
pub fn fuel_policy(&self) -> FuelPolicy {
self.fuel_policy
@@ -100,17 +165,22 @@ impl Config {
pub fn memory_backend(&self) -> &MemoryBackend {
&self.memory_backend
}
+
+ pub(crate) const fn trap_on_oom(&self) -> bool {
+ self.trap_on_oom
+ }
}
impl Default for Config {
fn default() -> Self {
Self {
- stack_32_size: DEFAULT_VALUE_STACK_32_SIZE,
- stack_64_size: DEFAULT_VALUE_STACK_64_SIZE,
- stack_128_size: DEFAULT_VALUE_STACK_128_SIZE,
- max_call_stack_size: DEFAULT_MAX_CALL_STACK_SIZE,
+ value_stack_32: StackConfig::fixed(DEFAULT_VALUE_STACK_32_SIZE),
+ value_stack_64: StackConfig::fixed(DEFAULT_VALUE_STACK_64_SIZE),
+ value_stack_128: StackConfig::fixed(DEFAULT_VALUE_STACK_128_SIZE),
+ call_stack: StackConfig::fixed(DEFAULT_MAX_CALL_STACK_SIZE),
fuel_policy: FuelPolicy::default(),
memory_backend: MemoryBackend::default(),
+ trap_on_oom: false,
}
}
}
diff --git a/crates/tinywasm/src/error.rs b/crates/tinywasm/src/error.rs
index 6a0ae26..0d1f368 100644
--- a/crates/tinywasm/src/error.rs
+++ b/crates/tinywasm/src/error.rs
@@ -1,5 +1,6 @@
use alloc::boxed::Box;
use alloc::string::{String, ToString};
+use alloc::sync::Arc;
use alloc::vec::Vec;
use core::fmt::Debug;
use core::fmt::Display;
@@ -27,7 +28,7 @@ pub enum Error {
/// A host function returned an invalid value
InvalidHostFnReturn {
/// The expected type
- expected: FuncType,
+ expected: Arc<FuncType>,
/// The actual value
actual: Vec<tinywasm_types::WasmValue>,
},
@@ -128,6 +129,9 @@ pub enum Trap {
/// Value stack overflow
ValueStackOverflow,
+ /// The runtime could not allocate memory for a stack or linear memory operation.
+ OutOfMemory,
+
/// An undefined element was encountered
UndefinedElement {
/// The element index
@@ -143,9 +147,9 @@ pub enum Trap {
/// Indirect call type mismatch
IndirectCallTypeMismatch {
/// The expected type
- expected: FuncType,
+ expected: Arc<FuncType>,
/// The actual type
- actual: FuncType,
+ actual: Arc<FuncType>,
},
/// Catch-all for other messages
@@ -164,6 +168,7 @@ impl Trap {
Self::IntegerOverflow => "integer overflow",
Self::CallStackOverflow => "call stack exhausted",
Self::ValueStackOverflow => "value stack exhausted",
+ Self::OutOfMemory => "out of memory",
Self::UndefinedElement { .. } => "undefined element",
Self::UninitializedElement { .. } => "uninitialized element",
Self::IndirectCallTypeMismatch { .. } => "indirect call type mismatch",
@@ -254,6 +259,7 @@ impl Display for Trap {
Self::IntegerOverflow => write!(f, "integer overflow"),
Self::CallStackOverflow => write!(f, "call stack exhausted"),
Self::ValueStackOverflow => write!(f, "value stack exhausted"),
+ Self::OutOfMemory => write!(f, "out of memory"),
Self::UndefinedElement { index } => write!(f, "undefined element: index={index}"),
Self::UninitializedElement { index } => {
write!(f, "uninitialized element: index={index}")
diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs
index 536b518..d79951a 100644
--- a/crates/tinywasm/src/func.rs
+++ b/crates/tinywasm/src/func.rs
@@ -1,8 +1,7 @@
use crate::interpreter::stack::{CallFrame, ValueStack};
use crate::reference::StoreItem;
use crate::{Error, FunctionInstance, InterpreterRuntime, Result, Store, unlikely};
-use alloc::rc::Rc;
-use alloc::{boxed::Box, format, string::ToString, vec, vec::Vec};
+use alloc::{boxed::Box, format, rc::Rc, string::ToString, sync::Arc, vec, vec::Vec};
use tinywasm_types::{ExternRef, FuncRef, FuncType, ModuleInstanceAddr, WasmType, WasmValue};
impl Function {
@@ -94,7 +93,7 @@ pub struct Function {
pub(crate) item: StoreItem,
pub(crate) module_addr: ModuleInstanceAddr,
pub(crate) addr: u32,
- pub(crate) ty: FuncType,
+ pub(crate) ty: Arc<FuncType>,
}
/// A typed function handle
@@ -107,13 +106,13 @@ pub struct FunctionTyped<P, R> {
/// A host function
pub struct HostFunction {
- pub(crate) ty: tinywasm_types::FuncType,
+ pub(crate) ty: Arc<tinywasm_types::FuncType>,
pub(crate) func: HostFuncInner,
}
impl HostFunction {
/// Get the function's type
- pub fn ty(&self) -> &tinywasm_types::FuncType {
+ pub fn ty(&self) -> &Arc<tinywasm_types::FuncType> {
&self.ty
}
@@ -125,9 +124,10 @@ impl HostFunction {
/// Create a new untyped host function import.
pub fn from_untyped(
store: &mut Store,
- ty: &tinywasm_types::FuncType,
+ ty: &FuncType,
func: impl Fn(FuncContext<'_>, &[WasmValue]) -> Result<Vec<WasmValue>> + 'static,
) -> Function {
+ let ty = Arc::new(ty.clone());
let ty_inner = ty.clone();
let inner_func = move |ctx: FuncContext<'_>, args: &[WasmValue]| -> Result<Vec<WasmValue>> {
let ty = ty_inner.clone();
@@ -163,7 +163,7 @@ impl HostFunction {
Ok(result.into_wasm_value_tuple())
};
- let ty = tinywasm_types::FuncType::new(&P::wasm_types(), &R::wasm_types());
+ let ty = Arc::new(tinywasm_types::FuncType::new(&P::wasm_types(), &R::wasm_types()));
let addr = store.add_func(FunctionInstance::Host(Rc::new(Self { func: Box::new(inner_func), ty: ty.clone() })));
Function { item: crate::StoreItem::new(store.id(), addr), module_addr: 0, addr, ty }
}
diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs
index 90a95e3..627604e 100644
--- a/crates/tinywasm/src/instance.rs
+++ b/crates/tinywasm/src/instance.rs
@@ -80,7 +80,7 @@ pub(crate) struct ModuleInstanceInner {
impl ModuleInstanceInner {
#[inline]
- pub(crate) fn func_ty(&self, addr: FuncAddr) -> &FuncType {
+ pub(crate) fn func_ty(&self, addr: FuncAddr) -> &Arc<FuncType> {
match self.types.get(addr as usize) {
Some(ty) => ty,
None => unreachable!("invalid function address: {addr}"),
@@ -385,7 +385,7 @@ impl ModuleInstance {
func_name: &str,
) -> Result<()> {
let expected = FuncType::new(&P::wasm_types(), &R::wasm_types());
- if func.ty != expected {
+ if *func.ty != expected {
#[cfg(feature = "debug")]
return Err(Error::Other(format!(
"function type mismatch for {func_name}: expected {expected:?}, actual {:?}",
diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs
index fd7658b..204618a 100644
--- a/crates/tinywasm/src/interpreter/executor.rs
+++ b/crates/tinywasm/src/interpreter/executor.rs
@@ -792,6 +792,9 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> {
if IS_RETURN_CALL {
self.store.value_stack.truncate_keep_counts(self.cf.locals_base, wasm_func.func.params);
+ } else if self.store.call_stack.is_at_limit() {
+ cold_path();
+ return Err(Trap::CallStackOverflow);
}
let locals_base = match self.store.value_stack.enter_locals(&wasm_func.func.params, &wasm_func.func.locals) {
@@ -800,9 +803,8 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> {
cold_path();
if IS_RETURN_CALL {
return Err(err);
- } else {
- return Err(Trap::CallStackOverflow);
}
+ return Err(Trap::CallStackOverflow);
}
};
@@ -849,6 +851,9 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> {
if IS_RETURN_CALL {
self.store.value_stack.truncate_keep_counts(self.cf.locals_base, params);
+ } else if self.store.call_stack.is_at_limit() {
+ cold_path();
+ return Err(Trap::CallStackOverflow);
}
let locals_base = match self.store.value_stack.enter_locals(&params, &locals) {
@@ -857,9 +862,8 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> {
cold_path();
if IS_RETURN_CALL {
return Err(err);
- } else {
- return Err(Trap::CallStackOverflow);
}
+ return Err(Trap::CallStackOverflow);
}
};
@@ -1028,7 +1032,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> {
false => i64::from(self.store.value_stack.pop::<i32>()),
};
- let size = mem.grow(pages_delta).unwrap_or(-1);
+ let size = mem.grow(pages_delta, self.store.engine.config().trap_on_oom())?.unwrap_or(-1);
match is_64bit {
true => self.store.value_stack.push::<i64>(size)?,
false => self.store.value_stack.push::<i32>(size as i32)?,
diff --git a/crates/tinywasm/src/interpreter/stack/call_stack.rs b/crates/tinywasm/src/interpreter/stack/call_stack.rs
index c080929..0592e31 100644
--- a/crates/tinywasm/src/interpreter/stack/call_stack.rs
+++ b/crates/tinywasm/src/interpreter/stack/call_stack.rs
@@ -7,11 +7,14 @@ use tinywasm_types::{FuncAddr, ModuleInstanceAddr, ValueCounts};
#[cfg_attr(feature = "debug", derive(Debug))]
pub(crate) struct CallStack {
stack: Vec<CallFrame>,
+ max_size: usize,
+ dynamic: bool,
}
impl CallStack {
pub(crate) fn new(config: &crate::engine::Config) -> Self {
- Self { stack: Vec::with_capacity(config.max_call_stack_size) }
+ let stack = config.call_stack;
+ Self { stack: Vec::with_capacity(stack.initial_size), max_size: stack.max_size, dynamic: stack.dynamic }
}
pub(crate) fn clear(&mut self) {
@@ -24,13 +27,36 @@ impl CallStack {
}
#[inline(always)]
+ pub(crate) fn is_at_limit(&self) -> bool {
+ self.stack.len() == self.max_size
+ }
+
+ #[inline(always)]
pub(crate) fn push(&mut self, call_frame: CallFrame) -> Result<(), Trap> {
- if self.stack.len() == self.stack.capacity() {
+ self.ensure_capacity_for(self.stack.len() + 1)?;
+ self.stack.push(call_frame);
+ Ok(())
+ }
+
+ #[inline(always)]
+ fn ensure_capacity_for(&mut self, required_len: usize) -> Result<(), Trap> {
+ if required_len <= self.stack.capacity() {
+ return Ok(());
+ }
+
+ if required_len > self.max_size || !self.dynamic {
cold_path();
return Err(Trap::CallStackOverflow);
}
- self.stack.push(call_frame);
+ let target_capacity = required_len.max(self.stack.capacity().max(1).saturating_mul(2)).min(self.max_size);
+ match self.stack.try_reserve(target_capacity.saturating_sub(self.stack.len())) {
+ Ok(()) => {}
+ Err(_) => {
+ cold_path();
+ return Err(Trap::CallStackOverflow);
+ }
+ }
Ok(())
}
}
diff --git a/crates/tinywasm/src/interpreter/stack/value_stack.rs b/crates/tinywasm/src/interpreter/stack/value_stack.rs
index fdf5f83..5ef1e10 100644
--- a/crates/tinywasm/src/interpreter/stack/value_stack.rs
+++ b/crates/tinywasm/src/interpreter/stack/value_stack.rs
@@ -3,7 +3,11 @@ use core::hint::cold_path;
use tinywasm_types::{ExternRef, FuncRef, LocalAddr, ValueCounts, WasmType, WasmValue};
use super::{CallFrame, StackBase};
-use crate::{Result, Trap, engine::Config, interpreter::*};
+use crate::{
+ Result, Trap,
+ engine::{Config, StackConfig},
+ interpreter::*,
+};
#[cfg_attr(feature = "debug", derive(Debug))]
pub(crate) struct ValueStack {
@@ -15,11 +19,13 @@ pub(crate) struct ValueStack {
#[cfg_attr(feature = "debug", derive(Debug))]
pub(crate) struct Stack<T: Copy + Default> {
data: Vec<T>,
+ max_size: usize,
+ dynamic: bool,
}
impl<T: Copy + Default> Stack<T> {
- pub(crate) fn new(size: usize) -> Self {
- Self { data: Vec::with_capacity(size) }
+ pub(crate) fn new(config: StackConfig) -> Self {
+ Self { data: Vec::with_capacity(config.initial_size), max_size: config.max_size, dynamic: config.dynamic }
}
pub(crate) fn clear(&mut self) {
@@ -32,12 +38,13 @@ impl<T: Copy + Default> Stack<T> {
}
#[inline(always)]
- pub(crate) fn push(&mut self, value: T) -> Result<(), Trap> {
- if self.data.len() == self.data.capacity() {
- cold_path();
- return Err(Trap::ValueStackOverflow);
- }
+ pub(crate) fn truncate(&mut self, len: usize) {
+ self.data.truncate(len);
+ }
+ #[inline(always)]
+ pub(crate) fn push(&mut self, value: T) -> Result<(), Trap> {
+ self.ensure_capacity_for(self.data.len() + 1)?;
self.data.push(value);
Ok(())
}
@@ -103,13 +110,32 @@ impl<T: Copy + Default> Stack<T> {
let start = self.data.len() - param_count;
let end = start + local_count;
- if end > self.data.capacity() {
+ self.ensure_capacity_for(end)?;
+
+ self.data.resize(end, T::default());
+ Ok(start as u32)
+ }
+
+ #[inline(always)]
+ fn ensure_capacity_for(&mut self, required_len: usize) -> Result<(), Trap> {
+ if required_len <= self.data.capacity() {
+ return Ok(());
+ }
+
+ if required_len > self.max_size || !self.dynamic {
cold_path();
return Err(Trap::ValueStackOverflow);
}
- self.data.resize(end, T::default());
- Ok(start as u32)
+ let target_capacity = required_len.max(self.data.capacity().max(1).saturating_mul(2)).min(self.max_size);
+ match self.data.try_reserve(target_capacity.saturating_sub(self.data.len())) {
+ Ok(()) => {}
+ Err(_) => {
+ cold_path();
+ return Err(Trap::ValueStackOverflow);
+ }
+ }
+ Ok(())
}
#[inline(always)]
@@ -141,9 +167,9 @@ impl<T: Copy + Default> Stack<T> {
impl ValueStack {
pub(crate) fn new(config: &Config) -> Self {
Self {
- stack_32: Stack::new(config.stack_32_size),
- stack_64: Stack::new(config.stack_64_size),
- stack_128: Stack::new(config.stack_128_size),
+ stack_32: Stack::new(config.value_stack_32),
+ stack_64: Stack::new(config.value_stack_64),
+ stack_128: Stack::new(config.value_stack_128),
}
}
@@ -205,9 +231,25 @@ impl ValueStack {
}
pub(crate) fn enter_locals(&mut self, params: &ValueCounts, locals: &ValueCounts) -> Result<StackBase, Trap> {
+ let len32 = self.stack_32.len();
+ let len64 = self.stack_64.len();
+
let locals_base32 = self.stack_32.enter_locals(params.c32 as usize, locals.c32 as usize)?;
- let locals_base64 = self.stack_64.enter_locals(params.c64 as usize, locals.c64 as usize)?;
- let locals_base128 = self.stack_128.enter_locals(params.c128 as usize, locals.c128 as usize)?;
+ let locals_base64 = match self.stack_64.enter_locals(params.c64 as usize, locals.c64 as usize) {
+ Ok(base) => base,
+ Err(err) => {
+ self.stack_32.truncate(len32);
+ return Err(err);
+ }
+ };
+ let locals_base128 = match self.stack_128.enter_locals(params.c128 as usize, locals.c128 as usize) {
+ Ok(base) => base,
+ Err(err) => {
+ self.stack_32.truncate(len32);
+ self.stack_64.truncate(len64);
+ return Err(err);
+ }
+ };
Ok(StackBase { s32: locals_base32, s64: locals_base64, s128: locals_base128 })
}
diff --git a/crates/tinywasm/src/lib.rs b/crates/tinywasm/src/lib.rs
index 5f7fa5c..b4dad9e 100644
--- a/crates/tinywasm/src/lib.rs
+++ b/crates/tinywasm/src/lib.rs
@@ -113,7 +113,7 @@ use interpreter::InterpreterRuntime;
/// Global configuration for the WebAssembly interpreter
pub mod engine;
-pub use engine::{Engine, LazyLinearMemory, LinearMemory, MemoryBackend, PagedMemory, VecMemory};
+pub use engine::{Engine, LazyLinearMemory, LinearMemory, MemoryBackend, PagedMemory, StackConfig, VecMemory};
#[cfg(feature = "parser")]
/// Re-export of [`tinywasm_parser`]. Requires `parser` feature.
diff --git a/crates/tinywasm/src/reference.rs b/crates/tinywasm/src/reference.rs
index 125fa22..44916b4 100644
--- a/crates/tinywasm/src/reference.rs
+++ b/crates/tinywasm/src/reference.rs
@@ -229,7 +229,7 @@ impl Memory {
/// Grow the memory by the given number of pages.
pub fn grow(&self, store: &mut Store, delta_pages: i64) -> Result<Option<i64>> {
- Ok(self.instance_mut(store)?.grow(delta_pages))
+ self.instance_mut(store)?.grow(delta_pages, true).map_err(Into::into)
}
/// Get the current size of the memory in pages.
diff --git a/crates/tinywasm/src/store/function.rs b/crates/tinywasm/src/store/function.rs
index a7a22d5..e499f61 100644
--- a/crates/tinywasm/src/store/function.rs
+++ b/crates/tinywasm/src/store/function.rs
@@ -18,7 +18,7 @@ pub(crate) enum FunctionInstance {
impl FunctionInstance {
#[inline]
- pub(crate) fn ty(&self) -> &FuncType {
+ pub(crate) fn ty(&self) -> &Arc<FuncType> {
match self {
Self::Host(f) => &f.ty,
Self::Wasm(f) => f.ty(),
@@ -41,7 +41,7 @@ pub(crate) struct WasmFunctionInstance {
impl WasmFunctionInstance {
#[inline]
- pub(crate) fn ty(&self) -> &FuncType {
+ pub(crate) fn ty(&self) -> &Arc<FuncType> {
&self.func.ty
}
}
diff --git a/crates/tinywasm/src/store/memory/instance.rs b/crates/tinywasm/src/store/memory/instance.rs
index 56d1915..19cfd3f 100644
--- a/crates/tinywasm/src/store/memory/instance.rs
+++ b/crates/tinywasm/src/store/memory/instance.rs
@@ -245,38 +245,51 @@ impl MemoryInstance {
})
}
- pub(crate) fn grow(&mut self, pages_delta: i64) -> Option<i64> {
+ pub(crate) fn grow(&mut self, pages_delta: i64, trap_on_oom: bool) -> Result<Option<i64>, Trap> {
if pages_delta < 0 {
cold_path();
crate::log::debug!("memory.grow failed: negative delta {}", pages_delta);
- return None;
+ return Ok(None);
}
let current_pages = self.page_count;
- let pages_delta = usize::try_from(pages_delta).ok()?;
- let new_pages = current_pages.checked_add(pages_delta)?;
+ let Some(pages_delta) = usize::try_from(pages_delta).ok() else {
+ return Ok(None);
+ };
+ let Some(new_pages) = current_pages.checked_add(pages_delta) else {
+ return Ok(None);
+ };
let max_pages = self.kind.page_count_max().try_into().unwrap_or(usize::MAX);
if new_pages > max_pages {
cold_path();
crate::log::debug!("memory.grow failed: new_pages={}, max_pages={}", new_pages, max_pages);
- return None;
+ return Ok(None);
}
- let new_size = (new_pages as u64).checked_mul(self.kind.page_size())?;
+ let Some(new_size) = (new_pages as u64).checked_mul(self.kind.page_size()) else {
+ return Ok(None);
+ };
if new_size > self.kind.max_size() {
cold_path();
crate::log::debug!("memory.grow failed: new_size={}, max_size={}", new_size, self.kind.max_size());
- return None;
+ return Ok(None);
}
- let new_size = usize::try_from(new_size).ok()?;
+ let Some(new_size) = usize::try_from(new_size).ok() else {
+ return Ok(None);
+ };
if new_size == self.inner.len() {
- return i64::try_from(current_pages).ok();
+ return Ok(i64::try_from(current_pages).ok());
}
- self.inner.grow_to(new_size)?;
+ if let Err(err) = self.inner.grow_to(new_size) {
+ if trap_on_oom {
+ return Err(err);
+ }
+ return Ok(None);
+ }
self.page_count = new_pages;
- i64::try_from(current_pages).ok()
+ Ok(i64::try_from(current_pages).ok())
}
}
diff --git a/crates/tinywasm/src/store/memory/lazy.rs b/crates/tinywasm/src/store/memory/lazy.rs
index 54c7ddc..4b0fe56 100644
--- a/crates/tinywasm/src/store/memory/lazy.rs
+++ b/crates/tinywasm/src/store/memory/lazy.rs
@@ -1,6 +1,7 @@
use alloc::boxed::Box;
use alloc::vec::Vec;
use core::cell::RefCell;
+use core::hint::cold_path;
use tinywasm_types::MemoryType;
@@ -21,7 +22,7 @@ pub struct LazyLinearMemory {
impl LazyLinearMemory {
/// Creates a lazy memory for `ty` using `backend` for the eventual materialized storage.
- pub fn new(ty: MemoryType, backend: MemoryBackend) -> Result<Self> {
+ pub fn try_new(ty: MemoryType, backend: MemoryBackend) -> Result<Self> {
let initial_len = usize::try_from(ty.initial_size())
.map_err(|_| Error::UnsupportedFeature("memory size exceeds the host address space"))?;
Ok(Self::new_with_initial_len(ty, initial_len, backend))
@@ -37,21 +38,52 @@ impl LazyLinearMemory {
f(inner.as_deref().expect("lazy memory should be materialized"))
}
+ fn try_with_inner<R>(&self, f: impl FnOnce(&dyn LinearMemory) -> R) -> core::result::Result<R, crate::Trap> {
+ self.try_ensure_materialized()?;
+ let inner = self.inner.borrow();
+ Ok(f(inner.as_deref().expect("lazy memory should be materialized")))
+ }
+
fn with_inner_mut<R>(&self, f: impl FnOnce(&mut dyn LinearMemory) -> R) -> R {
self.ensure_materialized();
let mut inner = self.inner.borrow_mut();
f(inner.as_deref_mut().expect("lazy memory should be materialized"))
}
+ fn try_with_inner_mut<R>(
+ &self,
+ f: impl FnOnce(&mut dyn LinearMemory) -> R,
+ ) -> core::result::Result<R, crate::Trap> {
+ self.try_ensure_materialized()?;
+ let mut inner = self.inner.borrow_mut();
+ Ok(f(inner.as_deref_mut().expect("lazy memory should be materialized")))
+ }
+
fn ensure_materialized(&self) {
if self.inner.borrow().is_some() {
return;
}
- // Lazy materialization happens from trait methods that cannot surface backend creation errors.
let storage = self.backend.create(self.ty, self.initial_len).expect("lazy memory materialization failed");
*self.inner.borrow_mut() = Some(storage.0);
}
+
+ fn try_ensure_materialized(&self) -> core::result::Result<(), crate::Trap> {
+ if self.inner.borrow().is_some() {
+ return Ok(());
+ }
+
+ let storage = match self.backend.create(self.ty, self.initial_len) {
+ Ok(storage) => storage,
+ Err(Error::Trap(trap)) => {
+ cold_path();
+ return Err(trap);
+ }
+ Err(err) => panic!("lazy memory materialization failed: {err}"),
+ };
+ *self.inner.borrow_mut() = Some(storage.0);
+ Ok(())
+ }
}
impl LinearMemory for LazyLinearMemory {
@@ -59,8 +91,8 @@ impl LinearMemory for LazyLinearMemory {
self.with_inner(|inner| inner.len())
}
- fn grow_to(&mut self, new_len: usize) -> Option<()> {
- self.with_inner_mut(|inner| inner.grow_to(new_len))
+ fn grow_to(&mut self, new_len: usize) -> Result<(), crate::Trap> {
+ self.try_with_inner_mut(|inner| inner.grow_to(new_len))?
}
fn read(&self, addr: usize, dst: &mut [u8]) -> usize {
@@ -92,43 +124,43 @@ impl LinearMemory for LazyLinearMemory {
}
fn read_8(&self, base: u64, offset: u64) -> core::result::Result<u8, crate::Trap> {
- self.with_inner(|inner| inner.read_8(base, offset))
+ self.try_with_inner(|inner| inner.read_8(base, offset))?
}
fn read_16(&self, base: u64, offset: u64) -> core::result::Result<[u8; 2], crate::Trap> {
- self.with_inner(|inner| inner.read_16(base, offset))
+ self.try_with_inner(|inner| inner.read_16(base, offset))?
}
fn read_32(&self, base: u64, offset: u64) -> core::result::Result<[u8; 4], crate::Trap> {
- self.with_inner(|inner| inner.read_32(base, offset))
+ self.try_with_inner(|inner| inner.read_32(base, offset))?
}
fn read_64(&self, base: u64, offset: u64) -> core::result::Result<[u8; 8], crate::Trap> {
- self.with_inner(|inner| inner.read_64(base, offset))
+ self.try_with_inner(|inner| inner.read_64(base, offset))?
}
fn read_128(&self, base: u64, offset: u64) -> core::result::Result<[u8; 16], crate::Trap> {
- self.with_inner(|inner| inner.read_128(base, offset))
+ self.try_with_inner(|inner| inner.read_128(base, offset))?
}
fn write_8(&mut self, base: u64, offset: u64, byte: u8) -> core::result::Result<(), crate::Trap> {
- self.with_inner_mut(|inner| inner.write_8(base, offset, byte))
+ self.try_with_inner_mut(|inner| inner.write_8(base, offset, byte))?
}
fn write_16(&mut self, base: u64, offset: u64, bytes: [u8; 2]) -> core::result::Result<(), crate::Trap> {
- self.with_inner_mut(|inner| inner.write_16(base, offset, bytes))
+ self.try_with_inner_mut(|inner| inner.write_16(base, offset, bytes))?
}
fn write_32(&mut self, base: u64, offset: u64, bytes: [u8; 4]) -> core::result::Result<(), crate::Trap> {
- self.with_inner_mut(|inner| inner.write_32(base, offset, bytes))
+ self.try_with_inner_mut(|inner| inner.write_32(base, offset, bytes))?
}
fn write_64(&mut self, base: u64, offset: u64, bytes: [u8; 8]) -> core::result::Result<(), crate::Trap> {
- self.with_inner_mut(|inner| inner.write_64(base, offset, bytes))
+ self.try_with_inner_mut(|inner| inner.write_64(base, offset, bytes))?
}
fn write_128(&mut self, base: u64, offset: u64, bytes: [u8; 16]) -> core::result::Result<(), crate::Trap> {
- self.with_inner_mut(|inner| inner.write_128(base, offset, bytes))
+ self.try_with_inner_mut(|inner| inner.write_128(base, offset, bytes))?
}
}
diff --git a/crates/tinywasm/src/store/memory/mod.rs b/crates/tinywasm/src/store/memory/mod.rs
index 2c6558f..37a8798 100644
--- a/crates/tinywasm/src/store/memory/mod.rs
+++ b/crates/tinywasm/src/store/memory/mod.rs
@@ -35,7 +35,7 @@ pub trait LinearMemory {
///
/// The runtime only calls this with lengths that are exact multiples of the Wasm page size for
/// the owning memory.
- fn grow_to(&mut self, new_len: usize) -> Option<()>;
+ fn grow_to(&mut self, new_len: usize) -> core::result::Result<(), crate::Trap>;
/// Reads up to `dst.len()` bytes starting at `addr` and returns the number of bytes read.
///
@@ -307,9 +307,11 @@ impl MemoryBackend {
pub(crate) fn create(&self, ty: MemoryType, initial_len: usize) -> Result<MemoryStorage> {
let storage = match &self.kind {
- MemoryBackendKind::Vec => Box::new(VecMemory::new(initial_len)) as Box<dyn LinearMemory>,
+ MemoryBackendKind::Vec => {
+ Box::new(VecMemory::try_new(initial_len).map_err(Error::Trap)?) as Box<dyn LinearMemory>
+ }
MemoryBackendKind::Paged { chunk_size } => {
- Box::new(PagedMemory::new(initial_len, *chunk_size)) as Box<dyn LinearMemory>
+ Box::new(PagedMemory::try_new(initial_len, *chunk_size).map_err(Error::Trap)?) as Box<dyn LinearMemory>
}
MemoryBackendKind::Custom(factory) => factory(ty)?,
};
@@ -512,7 +514,7 @@ mod tests {
let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), None);
let mut memory = create_test_memory(kind, backend);
let original_pages = memory.page_count;
- assert_eq!(memory.grow(1), Some(original_pages as i64));
+ assert_eq!(memory.grow(1, false).unwrap(), Some(original_pages as i64));
assert_eq!(memory.page_count, original_pages + 1);
}
}
@@ -522,7 +524,7 @@ mod tests {
for backend in test_backends() {
let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), None);
let mut memory = create_test_memory(kind, backend);
- assert!(memory.grow(memory.kind.max_size() as i64 + 1).is_none());
+ assert_eq!(memory.grow(memory.kind.max_size() as i64 + 1, false).unwrap(), None);
}
}
@@ -531,8 +533,8 @@ mod tests {
for backend in test_backends() {
let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), None);
let mut memory = create_test_memory(kind, backend);
- assert_eq!(memory.grow(1), Some(1));
- assert_eq!(memory.grow(1), None);
+ assert_eq!(memory.grow(1, false).unwrap(), Some(1));
+ assert_eq!(memory.grow(1, false).unwrap(), None);
}
}
@@ -542,7 +544,7 @@ mod tests {
let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), None);
let mut memory = create_test_memory(kind, backend);
let original_pages = memory.page_count;
- assert_eq!(memory.grow(-1), None);
+ assert_eq!(memory.grow(-1, false).unwrap(), None);
assert_eq!(memory.page_count, original_pages);
}
}
@@ -562,7 +564,7 @@ mod tests {
for backend in test_backends() {
let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), Some(1));
let mut memory = create_test_memory(kind, backend);
- assert_eq!(memory.grow(1), Some(1));
+ assert_eq!(memory.grow(1, false).unwrap(), Some(1));
let data = [1, 2];
assert!(memory.inner.write_all(0, &data).is_some());
assert_eq!(memory.inner.read_vec(0, data.len()).unwrap(), data);
diff --git a/crates/tinywasm/src/store/memory/paged.rs b/crates/tinywasm/src/store/memory/paged.rs
index 555261b..fc6b778 100644
--- a/crates/tinywasm/src/store/memory/paged.rs
+++ b/crates/tinywasm/src/store/memory/paged.rs
@@ -1,7 +1,7 @@
use alloc::boxed::Box;
-use alloc::vec;
use alloc::vec::Vec;
use core::cmp::min;
+use core::hint::cold_path;
use super::{LinearMemory, checked_effective_addr};
@@ -25,10 +25,10 @@ pub struct PagedMemory {
}
impl PagedMemory {
- /// Creates a new sparse memory with `len` addressable bytes and the given `chunk_size`.
+ /// Tries to create a new sparse memory with `len` addressable bytes and the given `chunk_size`.
///
/// Prefer this backend when grow behavior matters more than absolute read and write speed.
- pub fn new(len: usize, chunk_size: usize) -> Self {
+ pub fn try_new(len: usize, chunk_size: usize) -> Result<Self, crate::Trap> {
assert!(chunk_size.is_power_of_two(), "chunk_size must be a power of two");
let mut memory = Self {
@@ -38,13 +38,31 @@ impl PagedMemory {
chunk_mask: chunk_size - 1,
chunks: Vec::new(),
};
- memory.grow_to(len).expect("initial length must be growable");
- memory
+ memory.grow_to(len)?;
+ Ok(memory)
}
#[inline(always)]
- fn chunk_mut(&mut self, chunk_idx: usize) -> &mut [u8] {
- self.chunks[chunk_idx].get_or_insert_with(|| vec![0; self.chunk_size].into_boxed_slice()).as_mut()
+ fn allocate_chunk(&self) -> Result<Box<[u8]>, crate::Trap> {
+ let mut chunk = Vec::new();
+ match chunk.try_reserve_exact(self.chunk_size) {
+ Ok(()) => {}
+ Err(_) => {
+ cold_path();
+ return Err(crate::Trap::OutOfMemory);
+ }
+ }
+ chunk.resize(self.chunk_size, 0);
+ Ok(chunk.into_boxed_slice())
+ }
+
+ #[inline(always)]
+ fn chunk_mut(&mut self, chunk_idx: usize) -> Result<&mut [u8], crate::Trap> {
+ if self.chunks[chunk_idx].is_none() {
+ self.chunks[chunk_idx] = Some(self.allocate_chunk()?);
+ }
+
+ Ok(self.chunks[chunk_idx].as_deref_mut().unwrap_or_else(|| unreachable!()))
}
#[inline(always)]
@@ -110,13 +128,27 @@ impl LinearMemory for PagedMemory {
}
#[inline(always)]
- fn grow_to(&mut self, new_len: usize) -> Option<()> {
+ fn grow_to(&mut self, new_len: usize) -> Result<(), crate::Trap> {
if new_len < self.len {
- return None;
+ return Err(crate::Trap::MemoryOutOfBounds { offset: new_len, len: 0, max: self.len });
+ }
+
+ let new_chunk_count = if new_len == 0 { 0 } else { new_len.div_ceil(self.chunk_size) };
+ if new_chunk_count > self.chunks.len() {
+ match self.chunks.try_reserve_exact(new_chunk_count - self.chunks.len()) {
+ Ok(()) => {}
+ Err(_) => {
+ cold_path();
+ return Err(crate::Trap::OutOfMemory);
+ }
+ }
+ self.chunks.resize_with(new_chunk_count, || None);
+ } else {
+ self.chunks.truncate(new_chunk_count);
}
- self.chunks.resize_with(if new_len == 0 { 0 } else { new_len.div_ceil(self.chunk_size) }, || None);
+
self.len = new_len;
- Some(())
+ Ok(())
}
#[inline(always)]
@@ -148,7 +180,9 @@ impl LinearMemory for PagedMemory {
let chunk_offset = addr & self.chunk_mask;
let write_len = min(min(self.chunk_size - chunk_offset, self.len - addr), src.len());
- let chunk = self.chunk_mut(chunk_idx);
+ let Ok(chunk) = self.chunk_mut(chunk_idx) else {
+ return 0;
+ };
chunk[chunk_offset..chunk_offset + write_len].copy_from_slice(&src[..write_len]);
write_len
}
@@ -164,7 +198,7 @@ impl LinearMemory for PagedMemory {
let chunk_offset = pos & self.chunk_mask;
let copy_len = min(self.chunk_size - chunk_offset, end - pos);
- let chunk = self.chunk_mut(chunk_idx);
+ let chunk = self.chunk_mut(chunk_idx).ok()?;
chunk[chunk_offset..chunk_offset + copy_len].copy_from_slice(&src[src_offset..src_offset + copy_len]);
pos += copy_len;
@@ -194,7 +228,7 @@ impl LinearMemory for PagedMemory {
chunk[chunk_offset..chunk_offset + fill_len].fill(0);
}
} else {
- self.chunk_mut(chunk_idx)[chunk_offset..chunk_offset + fill_len].fill(val);
+ self.chunk_mut(chunk_idx).ok()?[chunk_offset..chunk_offset + fill_len].fill(val);
}
pos = chunk_end;
@@ -321,7 +355,7 @@ impl LinearMemory for PagedMemory {
let addr = checked_effective_addr::<1>(self.len, base, offset)?;
let chunk_idx = addr >> self.chunk_shift;
let chunk_offset = addr & self.chunk_mask;
- self.chunk_mut(chunk_idx)[chunk_offset] = byte;
+ self.chunk_mut(chunk_idx)?[chunk_offset] = byte;
Ok(())
}
@@ -331,9 +365,12 @@ impl LinearMemory for PagedMemory {
let chunk_idx = addr >> self.chunk_shift;
let chunk_offset = addr & self.chunk_mask;
if chunk_offset + 2 <= self.chunk_size {
- self.chunk_mut(chunk_idx)[chunk_offset..chunk_offset + 2].copy_from_slice(&bytes);
+ self.chunk_mut(chunk_idx)?[chunk_offset..chunk_offset + 2].copy_from_slice(&bytes);
} else {
- self.write_all(addr, &bytes).unwrap();
+ if self.write_all(addr, &bytes).is_none() {
+ cold_path();
+ return Err(crate::Trap::OutOfMemory);
+ }
}
Ok(())
}
@@ -344,9 +381,12 @@ impl LinearMemory for PagedMemory {
let chunk_idx = addr >> self.chunk_shift;
let chunk_offset = addr & self.chunk_mask;
if chunk_offset + 4 <= self.chunk_size {
- self.chunk_mut(chunk_idx)[chunk_offset..chunk_offset + 4].copy_from_slice(&bytes);
+ self.chunk_mut(chunk_idx)?[chunk_offset..chunk_offset + 4].copy_from_slice(&bytes);
} else {
- self.write_all(addr, &bytes).unwrap();
+ if self.write_all(addr, &bytes).is_none() {
+ cold_path();
+ return Err(crate::Trap::OutOfMemory);
+ }
}
Ok(())
}
@@ -357,9 +397,12 @@ impl LinearMemory for PagedMemory {
let chunk_idx = addr >> self.chunk_shift;
let chunk_offset = addr & self.chunk_mask;
if chunk_offset + 8 <= self.chunk_size {
- self.chunk_mut(chunk_idx)[chunk_offset..chunk_offset + 8].copy_from_slice(&bytes);
+ self.chunk_mut(chunk_idx)?[chunk_offset..chunk_offset + 8].copy_from_slice(&bytes);
} else {
- self.write_all(addr, &bytes).unwrap();
+ if self.write_all(addr, &bytes).is_none() {
+ cold_path();
+ return Err(crate::Trap::OutOfMemory);
+ }
}
Ok(())
}
@@ -370,9 +413,12 @@ impl LinearMemory for PagedMemory {
let chunk_idx = addr >> self.chunk_shift;
let chunk_offset = addr & self.chunk_mask;
if chunk_offset + 16 <= self.chunk_size {
- self.chunk_mut(chunk_idx)[chunk_offset..chunk_offset + 16].copy_from_slice(&bytes);
+ self.chunk_mut(chunk_idx)?[chunk_offset..chunk_offset + 16].copy_from_slice(&bytes);
} else {
- self.write_all(addr, &bytes).unwrap();
+ if self.write_all(addr, &bytes).is_none() {
+ cold_path();
+ return Err(crate::Trap::OutOfMemory);
+ }
}
Ok(())
}
@@ -384,7 +430,7 @@ mod tests {
#[test]
fn paged_memory_reads_zeroes_from_sparse_chunks() {
- let memory = PagedMemory::new(16, 4);
+ let memory = PagedMemory::try_new(16, 4).expect("test memory should be constructible");
let mut dst = [1; 6];
assert_eq!(memory.read(5, &mut dst), 3);
assert_eq!(&dst[..3], &[0; 3]);
@@ -393,7 +439,7 @@ mod tests {
#[test]
fn paged_memory_store_and_load_crosses_chunk_boundaries() {
- let mut memory = PagedMemory::new(16, 4);
+ let mut memory = PagedMemory::try_new(16, 4).expect("test memory should be constructible");
memory.write_all(3, &[1, 2, 3, 4, 5, 6]).unwrap();
let mut dst = [0; 6];
@@ -403,7 +449,7 @@ mod tests {
#[test]
fn paged_memory_copy_within_handles_overlap() {
- let mut memory = PagedMemory::new(16, 4);
+ let mut memory = PagedMemory::try_new(16, 4).expect("test memory should be constructible");
memory.write_all(0, &[1, 2, 3, 4, 5, 6]).unwrap();
memory.copy_within(2, 0, 6).unwrap();
@@ -414,7 +460,7 @@ mod tests {
#[test]
fn paged_memory_write_stops_at_chunk_boundary() {
- let mut memory = PagedMemory::new(16, 4);
+ let mut memory = PagedMemory::try_new(16, 4).expect("test memory should be constructible");
assert_eq!(memory.write(3, &[1, 2, 3, 4]), 1);
let mut dst = [0; 4];
diff --git a/crates/tinywasm/src/store/memory/vec.rs b/crates/tinywasm/src/store/memory/vec.rs
index 6f802f7..f556564 100644
--- a/crates/tinywasm/src/store/memory/vec.rs
+++ b/crates/tinywasm/src/store/memory/vec.rs
@@ -1,5 +1,5 @@
-use alloc::vec;
use alloc::vec::Vec;
+use core::hint::cold_path;
use super::{LinearMemory, checked_effective_addr};
@@ -16,11 +16,20 @@ pub struct VecMemory {
}
impl VecMemory {
- /// Creates a new memory with `len` zero-initialized bytes.
+ /// Tries to create a new memory with `len` zero-initialized bytes.
///
/// Prefer this backend when contiguous access is more important than grow performance.
- pub fn new(len: usize) -> Self {
- Self { data: vec![0; len] }
+ pub fn try_new(len: usize) -> Result<Self, crate::Trap> {
+ let mut data = Vec::new();
+ match data.try_reserve_exact(len) {
+ Ok(()) => {}
+ Err(_) => {
+ cold_path();
+ return Err(crate::Trap::OutOfMemory);
+ }
+ }
+ data.resize(len, 0);
+ Ok(Self { data })
}
}
@@ -31,12 +40,19 @@ impl LinearMemory for VecMemory {
}
#[inline(always)]
- fn grow_to(&mut self, new_len: usize) -> Option<()> {
+ fn grow_to(&mut self, new_len: usize) -> Result<(), crate::Trap> {
if new_len < self.data.len() {
- return None;
+ return Err(crate::Trap::MemoryOutOfBounds { offset: new_len, len: 0, max: self.data.len() });
+ }
+ match self.data.try_reserve_exact(new_len.saturating_sub(self.data.len())) {
+ Ok(()) => {}
+ Err(_) => {
+ cold_path();
+ return Err(crate::Trap::OutOfMemory);
+ }
}
self.data.resize(new_len, 0);
- Some(())
+ Ok(())
}
#[inline(always)]
diff --git a/crates/tinywasm/tests/memory_backends.rs b/crates/tinywasm/tests/memory_backends.rs
index 8b7d630..b965c9f 100644
--- a/crates/tinywasm/tests/memory_backends.rs
+++ b/crates/tinywasm/tests/memory_backends.rs
@@ -1,5 +1,7 @@
+extern crate alloc;
+
+use alloc::sync::Arc;
use core::sync::atomic::{AtomicUsize, Ordering};
-use std::sync::Arc;
#[cfg(feature = "std")]
use std::io::{Read, Seek, SeekFrom, Write};
@@ -15,7 +17,7 @@ fn instantiate_module_with_counting_backend(module: Module) -> Result<usize> {
let factory_calls = created.clone();
let backend = MemoryBackend::custom(move |ty| {
factory_calls.fetch_add(1, Ordering::Relaxed);
- Ok(PagedMemory::new(ty.initial_size() as usize, 16))
+ Ok(PagedMemory::try_new(ty.initial_size() as usize, 16)?)
});
let engine = Engine::new(Config::new().with_memory_backend(backend));
let mut store = Store::new(engine);
@@ -40,7 +42,7 @@ fn instantiate_exported_memory_with_counting_backend(
let factory_calls = created.clone();
let backend = MemoryBackend::custom(move |ty| {
factory_calls.fetch_add(1, Ordering::Relaxed);
- Ok(PagedMemory::new(ty.initial_size() as usize, 16))
+ Ok(PagedMemory::try_new(ty.initial_size() as usize, 16)?)
});
let engine = Engine::new(Config::new().with_memory_backend(backend));
let mut store = Store::new(engine);
@@ -80,7 +82,7 @@ fn custom_backend_factory_is_used_for_host_memories() -> Result<()> {
let backend = MemoryBackend::custom(move |ty| {
factory_calls.fetch_add(1, Ordering::Relaxed);
page_size_seen.store(ty.page_size() as usize, Ordering::Relaxed);
- Ok(PagedMemory::new(ty.initial_size() as usize, 16))
+ Ok(PagedMemory::try_new(ty.initial_size() as usize, 16)?)
});
let engine = Engine::new(Config::new().with_memory_backend(backend));
diff --git a/crates/tinywasm/tests/testsuite/run.rs b/crates/tinywasm/tests/testsuite/run.rs
index cfa7f54..37a408f 100644
--- a/crates/tinywasm/tests/testsuite/run.rs
+++ b/crates/tinywasm/tests/testsuite/run.rs
@@ -321,7 +321,7 @@ impl TestSuite {
ModuleInstance::instantiate(&mut store, &module, Some(imports))?;
return Ok(());
}
- wast::WastExecute::Get { module: _, global: _, .. } => {
+ wast::WastExecute::Get { .. } => {
panic!("get not supported");
}
wast::WastExecute::Invoke(invoke) => invoke,