Skip to content

Commit 238e7aa

Browse files
feat: oom traps
Signed-off-by: Henry <mail@henrygressmann.de>
1 parent e667ae4 commit 238e7aa

20 files changed

Lines changed: 430 additions & 141 deletions

File tree

crates/parser/src/conversion.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use alloc::sync::Arc;
2+
13
use crate::{Result, module::Code, visit::process_operators_and_validate};
24
use alloc::{boxed::Box, format, string::ToString, vec::Vec};
35
use tinywasm_types::*;
@@ -201,7 +203,7 @@ pub(crate) fn convert_module_code(
201203
Ok(((body, data, local_counts), allocations))
202204
}
203205

204-
pub(crate) fn convert_module_type(ty: wasmparser::RecGroup) -> Result<FuncType> {
206+
pub(crate) fn convert_module_type(ty: wasmparser::RecGroup) -> Result<Arc<FuncType>> {
205207
let mut types = ty.types();
206208
if types.len() != 1 {
207209
return Err(crate::ParseError::UnsupportedOperator(
@@ -212,7 +214,7 @@ pub(crate) fn convert_module_type(ty: wasmparser::RecGroup) -> Result<FuncType>
212214
let ty = types.next().unwrap().unwrap_func();
213215
let params: Vec<_> = ty.params().iter().map(convert_valtype).collect();
214216
let results: Vec<_> = ty.results().iter().map(convert_valtype).collect();
215-
Ok(FuncType::new(&params, &results))
217+
Ok(FuncType::new(&params, &results).into())
216218
}
217219

218220
pub(crate) fn convert_reftype(reftype: wasmparser::RefType) -> WasmType {

crates/parser/src/module.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,8 @@ impl ModuleReader {
5252

5353
debug!("Found type section");
5454
validator.type_section(&reader)?;
55-
self.func_types = reader
56-
.into_iter()
57-
.map(|t| conversion::convert_module_type(t?).map(Arc::new))
58-
.collect::<Result<Vec<Arc<FuncType>>>>()?;
55+
self.func_types =
56+
reader.into_iter().map(|t| conversion::convert_module_type(t?)).collect::<Result<Vec<_>>>()?;
5957
}
6058

6159
Payload::GlobalSection(reader) => {

crates/tinywasm/benches/memory_backends.rs

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -74,43 +74,73 @@ fn criterion_benchmark(c: &mut Criterion) {
7474
let mut group = c.benchmark_group("memory_backends");
7575
group.measurement_time(BENCH_MEASUREMENT_TIME);
7676

77-
bench_grow(&mut group, "vec", || VecMemory::new(PAGE_SIZE));
78-
bench_grow(&mut group, "paged", || PagedMemory::new(PAGE_SIZE, CHUNK_SIZE));
77+
bench_grow(&mut group, "vec", || VecMemory::try_new(PAGE_SIZE).expect("bench memory should be constructible"));
78+
bench_grow(&mut group, "paged", || {
79+
PagedMemory::try_new(PAGE_SIZE, CHUNK_SIZE).expect("bench memory should be constructible")
80+
});
7981

80-
bench_write_all(&mut group, "vec", "contiguous", VecMemory::new(MEMORY_LEN), CONTIGUOUS_OFFSET, CONTIGUOUS_LEN);
82+
bench_write_all(
83+
&mut group,
84+
"vec",
85+
"contiguous",
86+
VecMemory::try_new(MEMORY_LEN).expect("bench memory should be constructible"),
87+
CONTIGUOUS_OFFSET,
88+
CONTIGUOUS_LEN,
89+
);
8190
bench_write_all(
8291
&mut group,
8392
"paged",
8493
"contiguous",
85-
PagedMemory::new(MEMORY_LEN, CHUNK_SIZE),
94+
PagedMemory::try_new(MEMORY_LEN, CHUNK_SIZE).expect("bench memory should be constructible"),
95+
CONTIGUOUS_OFFSET,
96+
CONTIGUOUS_LEN,
97+
);
98+
bench_read_exact(
99+
&mut group,
100+
"vec",
101+
"contiguous",
102+
VecMemory::try_new(MEMORY_LEN).expect("bench memory should be constructible"),
86103
CONTIGUOUS_OFFSET,
87104
CONTIGUOUS_LEN,
88105
);
89-
bench_read_exact(&mut group, "vec", "contiguous", VecMemory::new(MEMORY_LEN), CONTIGUOUS_OFFSET, CONTIGUOUS_LEN);
90106
bench_read_exact(
91107
&mut group,
92108
"paged",
93109
"contiguous",
94-
PagedMemory::new(MEMORY_LEN, CHUNK_SIZE),
110+
PagedMemory::try_new(MEMORY_LEN, CHUNK_SIZE).expect("bench memory should be constructible"),
95111
CONTIGUOUS_OFFSET,
96112
CONTIGUOUS_LEN,
97113
);
98114

99-
bench_write_all(&mut group, "vec", "cross_chunk", VecMemory::new(MEMORY_LEN), CROSS_CHUNK_OFFSET, CROSS_CHUNK_LEN);
115+
bench_write_all(
116+
&mut group,
117+
"vec",
118+
"cross_chunk",
119+
VecMemory::try_new(MEMORY_LEN).expect("bench memory should be constructible"),
120+
CROSS_CHUNK_OFFSET,
121+
CROSS_CHUNK_LEN,
122+
);
100123
bench_write_all(
101124
&mut group,
102125
"paged",
103126
"cross_chunk",
104-
PagedMemory::new(MEMORY_LEN, CHUNK_SIZE),
127+
PagedMemory::try_new(MEMORY_LEN, CHUNK_SIZE).expect("bench memory should be constructible"),
128+
CROSS_CHUNK_OFFSET,
129+
CROSS_CHUNK_LEN,
130+
);
131+
bench_read_exact(
132+
&mut group,
133+
"vec",
134+
"cross_chunk",
135+
VecMemory::try_new(MEMORY_LEN).expect("bench memory should be constructible"),
105136
CROSS_CHUNK_OFFSET,
106137
CROSS_CHUNK_LEN,
107138
);
108-
bench_read_exact(&mut group, "vec", "cross_chunk", VecMemory::new(MEMORY_LEN), CROSS_CHUNK_OFFSET, CROSS_CHUNK_LEN);
109139
bench_read_exact(
110140
&mut group,
111141
"paged",
112142
"cross_chunk",
113-
PagedMemory::new(MEMORY_LEN, CHUNK_SIZE),
143+
PagedMemory::try_new(MEMORY_LEN, CHUNK_SIZE).expect("bench memory should be constructible"),
114144
CROSS_CHUNK_OFFSET,
115145
CROSS_CHUNK_LEN,
116146
);

crates/tinywasm/src/engine.rs

Lines changed: 82 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -54,23 +54,50 @@ pub const DEFAULT_VALUE_STACK_128_SIZE: usize = 4 * 1024; // 4k slots
5454
/// Default maximum size for the call stack (function frames).
5555
pub const DEFAULT_MAX_CALL_STACK_SIZE: usize = 1024; // 1024 frames
5656

57+
/// Stack allocation policy.
58+
#[derive(Clone, Copy, PartialEq, Eq)]
59+
#[cfg_attr(feature = "debug", derive(Debug))]
60+
pub struct StackConfig {
61+
/// Initial reserved capacity for the stack.
62+
pub initial_size: usize,
63+
/// Maximum number of elements the stack may contain.
64+
pub max_size: usize,
65+
/// Whether the stack may grow past its initial capacity.
66+
pub dynamic: bool,
67+
}
68+
69+
impl StackConfig {
70+
/// Creates a fixed-capacity stack that reserves all space up front.
71+
pub const fn fixed(size: usize) -> Self {
72+
Self { initial_size: size, max_size: size, dynamic: false }
73+
}
74+
75+
/// Creates a dynamically growing stack with the given initial and maximum sizes.
76+
pub const fn dynamic(initial_size: usize, max_size: usize) -> Self {
77+
assert!(initial_size <= max_size, "initial_size must be less than or equal to max_size");
78+
Self { initial_size, max_size, dynamic: true }
79+
}
80+
}
81+
5782
/// Configuration for the WebAssembly interpreter
5883
#[derive(Clone)]
5984
#[cfg_attr(feature = "debug", derive(Debug))]
6085
#[non_exhaustive]
6186
pub struct Config {
62-
/// Size of the 32-bit value stack (i32, f32, ref values).
63-
pub stack_32_size: usize,
64-
/// Size of the 64-bit value stack (i64, f64 values).
65-
pub stack_64_size: usize,
66-
/// Size of the 128-bit value stack (v128 values).
67-
pub stack_128_size: usize,
68-
/// Maximum size of the call stack
69-
pub max_call_stack_size: usize,
87+
/// Configuration for the 32-bit value stack (i32, f32, ref values).
88+
pub value_stack_32: StackConfig,
89+
/// Configuration for the 64-bit value stack (i64, f64 values).
90+
pub value_stack_64: StackConfig,
91+
/// Configuration for the 128-bit value stack (v128 values).
92+
pub value_stack_128: StackConfig,
93+
/// Configuration for the call stack.
94+
pub call_stack: StackConfig,
7095
/// Fuel accounting policy used by budgeted execution.
7196
pub fuel_policy: FuelPolicy,
7297
/// Backend used for runtime memories.
7398
pub memory_backend: MemoryBackend,
99+
/// Whether memory and stack allocation failures should trap instead of degrading into normal operation failure modes.
100+
pub trap_on_oom: bool,
74101
}
75102

76103
impl Config {
@@ -91,6 +118,44 @@ impl Config {
91118
self
92119
}
93120

121+
/// Set the configuration used for the 32-bit value stack.
122+
pub fn with_value_stack_32(mut self, stack: StackConfig) -> Self {
123+
self.value_stack_32 = stack;
124+
self
125+
}
126+
127+
/// Set the same configuration for all value stack lanes.
128+
pub fn with_value_stack(mut self, stack: StackConfig) -> Self {
129+
self.value_stack_32 = stack;
130+
self.value_stack_64 = stack;
131+
self.value_stack_128 = stack;
132+
self
133+
}
134+
135+
/// Set the configuration used for the 64-bit value stack.
136+
pub fn with_value_stack_64(mut self, stack: StackConfig) -> Self {
137+
self.value_stack_64 = stack;
138+
self
139+
}
140+
141+
/// Set the configuration used for the 128-bit value stack.
142+
pub fn with_value_stack_128(mut self, stack: StackConfig) -> Self {
143+
self.value_stack_128 = stack;
144+
self
145+
}
146+
147+
/// Set the configuration used for the call stack.
148+
pub fn with_call_stack(mut self, stack: StackConfig) -> Self {
149+
self.call_stack = stack;
150+
self
151+
}
152+
153+
/// Configure whether memory and stack allocation failures trap immediately.
154+
pub fn with_trap_on_oom(mut self, trap_on_oom: bool) -> Self {
155+
self.trap_on_oom = trap_on_oom;
156+
self
157+
}
158+
94159
/// Get the current fuel policy
95160
pub fn fuel_policy(&self) -> FuelPolicy {
96161
self.fuel_policy
@@ -100,17 +165,22 @@ impl Config {
100165
pub fn memory_backend(&self) -> &MemoryBackend {
101166
&self.memory_backend
102167
}
168+
169+
pub(crate) const fn trap_on_oom(&self) -> bool {
170+
self.trap_on_oom
171+
}
103172
}
104173

105174
impl Default for Config {
106175
fn default() -> Self {
107176
Self {
108-
stack_32_size: DEFAULT_VALUE_STACK_32_SIZE,
109-
stack_64_size: DEFAULT_VALUE_STACK_64_SIZE,
110-
stack_128_size: DEFAULT_VALUE_STACK_128_SIZE,
111-
max_call_stack_size: DEFAULT_MAX_CALL_STACK_SIZE,
177+
value_stack_32: StackConfig::fixed(DEFAULT_VALUE_STACK_32_SIZE),
178+
value_stack_64: StackConfig::fixed(DEFAULT_VALUE_STACK_64_SIZE),
179+
value_stack_128: StackConfig::fixed(DEFAULT_VALUE_STACK_128_SIZE),
180+
call_stack: StackConfig::fixed(DEFAULT_MAX_CALL_STACK_SIZE),
112181
fuel_policy: FuelPolicy::default(),
113182
memory_backend: MemoryBackend::default(),
183+
trap_on_oom: false,
114184
}
115185
}
116186
}

crates/tinywasm/src/error.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use alloc::boxed::Box;
22
use alloc::string::{String, ToString};
3+
use alloc::sync::Arc;
34
use alloc::vec::Vec;
45
use core::fmt::Debug;
56
use core::fmt::Display;
@@ -27,7 +28,7 @@ pub enum Error {
2728
/// A host function returned an invalid value
2829
InvalidHostFnReturn {
2930
/// The expected type
30-
expected: FuncType,
31+
expected: Arc<FuncType>,
3132
/// The actual value
3233
actual: Vec<tinywasm_types::WasmValue>,
3334
},
@@ -128,6 +129,9 @@ pub enum Trap {
128129
/// Value stack overflow
129130
ValueStackOverflow,
130131

132+
/// The runtime could not allocate memory for a stack or linear memory operation.
133+
OutOfMemory,
134+
131135
/// An undefined element was encountered
132136
UndefinedElement {
133137
/// The element index
@@ -143,9 +147,9 @@ pub enum Trap {
143147
/// Indirect call type mismatch
144148
IndirectCallTypeMismatch {
145149
/// The expected type
146-
expected: FuncType,
150+
expected: Arc<FuncType>,
147151
/// The actual type
148-
actual: FuncType,
152+
actual: Arc<FuncType>,
149153
},
150154

151155
/// Catch-all for other messages
@@ -164,6 +168,7 @@ impl Trap {
164168
Self::IntegerOverflow => "integer overflow",
165169
Self::CallStackOverflow => "call stack exhausted",
166170
Self::ValueStackOverflow => "value stack exhausted",
171+
Self::OutOfMemory => "out of memory",
167172
Self::UndefinedElement { .. } => "undefined element",
168173
Self::UninitializedElement { .. } => "uninitialized element",
169174
Self::IndirectCallTypeMismatch { .. } => "indirect call type mismatch",
@@ -254,6 +259,7 @@ impl Display for Trap {
254259
Self::IntegerOverflow => write!(f, "integer overflow"),
255260
Self::CallStackOverflow => write!(f, "call stack exhausted"),
256261
Self::ValueStackOverflow => write!(f, "value stack exhausted"),
262+
Self::OutOfMemory => write!(f, "out of memory"),
257263
Self::UndefinedElement { index } => write!(f, "undefined element: index={index}"),
258264
Self::UninitializedElement { index } => {
259265
write!(f, "uninitialized element: index={index}")

crates/tinywasm/src/func.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
use crate::interpreter::stack::{CallFrame, ValueStack};
22
use crate::reference::StoreItem;
33
use crate::{Error, FunctionInstance, InterpreterRuntime, Result, Store, unlikely};
4-
use alloc::rc::Rc;
5-
use alloc::{boxed::Box, format, string::ToString, vec, vec::Vec};
4+
use alloc::{boxed::Box, format, rc::Rc, string::ToString, sync::Arc, vec, vec::Vec};
65
use tinywasm_types::{ExternRef, FuncRef, FuncType, ModuleInstanceAddr, WasmType, WasmValue};
76

87
impl Function {
@@ -94,7 +93,7 @@ pub struct Function {
9493
pub(crate) item: StoreItem,
9594
pub(crate) module_addr: ModuleInstanceAddr,
9695
pub(crate) addr: u32,
97-
pub(crate) ty: FuncType,
96+
pub(crate) ty: Arc<FuncType>,
9897
}
9998

10099
/// A typed function handle
@@ -107,13 +106,13 @@ pub struct FunctionTyped<P, R> {
107106

108107
/// A host function
109108
pub struct HostFunction {
110-
pub(crate) ty: tinywasm_types::FuncType,
109+
pub(crate) ty: Arc<tinywasm_types::FuncType>,
111110
pub(crate) func: HostFuncInner,
112111
}
113112

114113
impl HostFunction {
115114
/// Get the function's type
116-
pub fn ty(&self) -> &tinywasm_types::FuncType {
115+
pub fn ty(&self) -> &Arc<tinywasm_types::FuncType> {
117116
&self.ty
118117
}
119118

@@ -125,9 +124,10 @@ impl HostFunction {
125124
/// Create a new untyped host function import.
126125
pub fn from_untyped(
127126
store: &mut Store,
128-
ty: &tinywasm_types::FuncType,
127+
ty: &FuncType,
129128
func: impl Fn(FuncContext<'_>, &[WasmValue]) -> Result<Vec<WasmValue>> + 'static,
130129
) -> Function {
130+
let ty = Arc::new(ty.clone());
131131
let ty_inner = ty.clone();
132132
let inner_func = move |ctx: FuncContext<'_>, args: &[WasmValue]| -> Result<Vec<WasmValue>> {
133133
let ty = ty_inner.clone();
@@ -163,7 +163,7 @@ impl HostFunction {
163163
Ok(result.into_wasm_value_tuple())
164164
};
165165

166-
let ty = tinywasm_types::FuncType::new(&P::wasm_types(), &R::wasm_types());
166+
let ty = Arc::new(tinywasm_types::FuncType::new(&P::wasm_types(), &R::wasm_types()));
167167
let addr = store.add_func(FunctionInstance::Host(Rc::new(Self { func: Box::new(inner_func), ty: ty.clone() })));
168168
Function { item: crate::StoreItem::new(store.id(), addr), module_addr: 0, addr, ty }
169169
}

crates/tinywasm/src/instance.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ pub(crate) struct ModuleInstanceInner {
8080

8181
impl ModuleInstanceInner {
8282
#[inline]
83-
pub(crate) fn func_ty(&self, addr: FuncAddr) -> &FuncType {
83+
pub(crate) fn func_ty(&self, addr: FuncAddr) -> &Arc<FuncType> {
8484
match self.types.get(addr as usize) {
8585
Some(ty) => ty,
8686
None => unreachable!("invalid function address: {addr}"),
@@ -385,7 +385,7 @@ impl ModuleInstance {
385385
func_name: &str,
386386
) -> Result<()> {
387387
let expected = FuncType::new(&P::wasm_types(), &R::wasm_types());
388-
if func.ty != expected {
388+
if *func.ty != expected {
389389
#[cfg(feature = "debug")]
390390
return Err(Error::Other(format!(
391391
"function type mismatch for {func_name}: expected {expected:?}, actual {:?}",

0 commit comments

Comments
 (0)