From 238e7aa76102cf64fa22ac5774326fcf35bc3792 Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 21 Apr 2026 20:14:04 +0200 Subject: feat: oom traps Signed-off-by: Henry --- crates/parser/src/conversion.rs | 6 +- crates/parser/src/module.rs | 6 +- crates/tinywasm/benches/memory_backends.rs | 50 ++++++++--- crates/tinywasm/src/engine.rs | 94 ++++++++++++++++--- crates/tinywasm/src/error.rs | 12 ++- crates/tinywasm/src/func.rs | 14 +-- crates/tinywasm/src/instance.rs | 4 +- crates/tinywasm/src/interpreter/executor.rs | 14 +-- .../tinywasm/src/interpreter/stack/call_stack.rs | 32 ++++++- .../tinywasm/src/interpreter/stack/value_stack.rs | 74 +++++++++++---- crates/tinywasm/src/lib.rs | 2 +- crates/tinywasm/src/reference.rs | 2 +- crates/tinywasm/src/store/function.rs | 4 +- crates/tinywasm/src/store/memory/instance.rs | 35 +++++--- crates/tinywasm/src/store/memory/lazy.rs | 60 ++++++++++--- crates/tinywasm/src/store/memory/mod.rs | 20 +++-- crates/tinywasm/src/store/memory/paged.rs | 100 +++++++++++++++------ crates/tinywasm/src/store/memory/vec.rs | 30 +++++-- crates/tinywasm/tests/memory_backends.rs | 10 ++- crates/tinywasm/tests/testsuite/run.rs | 2 +- 20 files changed, 430 insertions(+), 141 deletions(-) (limited to 'crates') 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 { +pub(crate) fn convert_module_type(ty: wasmparser::RecGroup) -> Result> { 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 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(¶ms, &results)) + Ok(FuncType::new(¶ms, &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::>>>()?; + self.func_types = + reader.into_iter().map(|t| conversion::convert_module_type(t?)).collect::>>()?; } 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, /// The actual value actual: Vec, }, @@ -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, /// The actual type - actual: FuncType, + actual: Arc, }, /// 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, } /// A typed function handle @@ -107,13 +106,13 @@ pub struct FunctionTyped { /// A host function pub struct HostFunction { - pub(crate) ty: tinywasm_types::FuncType, + pub(crate) ty: Arc, pub(crate) func: HostFuncInner, } impl HostFunction { /// Get the function's type - pub fn ty(&self) -> &tinywasm_types::FuncType { + pub fn ty(&self) -> &Arc { &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> + 'static, ) -> Function { + let ty = Arc::new(ty.clone()); let ty_inner = ty.clone(); let inner_func = move |ctx: FuncContext<'_>, args: &[WasmValue]| -> Result> { 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 { 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(¶ms, &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::()), }; - 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::(size)?, false => self.store.value_stack.push::(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, + 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) { @@ -23,14 +26,37 @@ impl CallStack { self.stack.pop() } + #[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 { data: Vec, + max_size: usize, + dynamic: bool, } impl Stack { - 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 Stack { } #[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 Stack { 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 Stack { 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 { + 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> { - 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 { 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 { &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 { + pub(crate) fn grow(&mut self, pages_delta: i64, trap_on_oom: bool) -> Result, 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 { + pub fn try_new(ty: MemoryType, backend: MemoryBackend) -> Result { 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(&self, f: impl FnOnce(&dyn LinearMemory) -> R) -> core::result::Result { + self.try_ensure_materialized()?; + let inner = self.inner.borrow(); + Ok(f(inner.as_deref().expect("lazy memory should be materialized"))) + } + fn with_inner_mut(&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( + &self, + f: impl FnOnce(&mut dyn LinearMemory) -> R, + ) -> core::result::Result { + 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 { - 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 { let storage = match &self.kind { - MemoryBackendKind::Vec => Box::new(VecMemory::new(initial_len)) as Box, + MemoryBackendKind::Vec => { + Box::new(VecMemory::try_new(initial_len).map_err(Error::Trap)?) as Box + } MemoryBackendKind::Paged { chunk_size } => { - Box::new(PagedMemory::new(initial_len, *chunk_size)) as Box + Box::new(PagedMemory::try_new(initial_len, *chunk_size).map_err(Error::Trap)?) as Box } 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 { 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, 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 { + 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 { 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, -- cgit v1.3.1