Skip to content

Commit 8a6d864

Browse files
feat: memory backend api
Signed-off-by: Henry <mail@henrygressmann.de>
1 parent 77cf2a6 commit 8a6d864

31 files changed

Lines changed: 2155 additions & 545 deletions

crates/tinywasm/Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ canonicalize_nans=[]
5555
# derive Debug for runtime/types structs
5656
debug=["tinywasm-types/debug"]
5757

58-
# expose module-internal by-index inspection APIs
58+
# expose module-internal by-index inspection APIs for non-exported entities (for testing and debugging)
5959
guest_debug=[]
6060

6161
# enable x86-specific SIMD intrinsics in Value128 (uses unsafe code)
@@ -139,6 +139,10 @@ harness=false
139139
name="tinywasm_modes"
140140
harness=false
141141

142+
[[bench]]
143+
name="memory_backends"
144+
harness=false
145+
142146
[[test]]
143147
name="test-wasm-3"
144148
harness=false
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
use criterion::measurement::WallTime;
2+
use criterion::{BatchSize, BenchmarkGroup, BenchmarkId, Criterion, criterion_group, criterion_main};
3+
use std::hint::black_box;
4+
use tinywasm::{LinearMemory, PagedMemory, VecMemory};
5+
6+
const PAGE_SIZE: usize = 64 * 1024;
7+
const CHUNK_SIZE: usize = 4 * 1024;
8+
const GROW_STEPS: usize = 32;
9+
const BENCH_MEASUREMENT_TIME: std::time::Duration = std::time::Duration::from_secs(10);
10+
11+
const MEMORY_LEN: usize = PAGE_SIZE * 4;
12+
const CONTIGUOUS_OFFSET: usize = 1024;
13+
const CONTIGUOUS_LEN: usize = 2048;
14+
const CROSS_CHUNK_OFFSET: usize = CHUNK_SIZE - 512;
15+
const CROSS_CHUNK_LEN: usize = CHUNK_SIZE * 2;
16+
17+
fn bench_grow<M, F>(group: &mut BenchmarkGroup<'_, WallTime>, backend: &str, make_memory: F)
18+
where
19+
M: LinearMemory,
20+
F: Fn() -> M + Copy,
21+
{
22+
group.bench_function(BenchmarkId::new("grow", backend), |b| {
23+
b.iter_batched(
24+
make_memory,
25+
|mut memory| {
26+
for page_count in 2..=GROW_STEPS + 1 {
27+
memory.grow_to(page_count * PAGE_SIZE).unwrap();
28+
}
29+
black_box(memory.len())
30+
},
31+
BatchSize::SmallInput,
32+
)
33+
});
34+
}
35+
36+
fn bench_write_all<M: LinearMemory>(
37+
group: &mut BenchmarkGroup<'_, WallTime>,
38+
backend: &str,
39+
workload: &str,
40+
mut memory: M,
41+
offset: usize,
42+
len: usize,
43+
) {
44+
let src = vec![0xA5; len];
45+
group.bench_function(BenchmarkId::new(format!("write_all/{workload}"), backend), |b| {
46+
b.iter(|| {
47+
memory.write_all(offset, black_box(&src)).unwrap();
48+
black_box(memory.len())
49+
})
50+
});
51+
}
52+
53+
fn bench_read_exact<M: LinearMemory>(
54+
group: &mut BenchmarkGroup<'_, WallTime>,
55+
backend: &str,
56+
workload: &str,
57+
mut memory: M,
58+
offset: usize,
59+
len: usize,
60+
) {
61+
let src = vec![0x5A; len];
62+
memory.write_all(offset, &src).unwrap();
63+
64+
let mut dst = vec![0; len];
65+
group.bench_function(BenchmarkId::new(format!("read_exact/{workload}"), backend), |b| {
66+
b.iter(|| {
67+
memory.read_exact(offset, black_box(&mut dst)).unwrap();
68+
black_box(&dst);
69+
})
70+
});
71+
}
72+
73+
fn criterion_benchmark(c: &mut Criterion) {
74+
let mut group = c.benchmark_group("memory_backends");
75+
group.measurement_time(BENCH_MEASUREMENT_TIME);
76+
77+
bench_grow(&mut group, "vec", || VecMemory::new(PAGE_SIZE));
78+
bench_grow(&mut group, "paged", || PagedMemory::new(PAGE_SIZE, CHUNK_SIZE));
79+
80+
bench_write_all(&mut group, "vec", "contiguous", VecMemory::new(MEMORY_LEN), CONTIGUOUS_OFFSET, CONTIGUOUS_LEN);
81+
bench_write_all(
82+
&mut group,
83+
"paged",
84+
"contiguous",
85+
PagedMemory::new(MEMORY_LEN, CHUNK_SIZE),
86+
CONTIGUOUS_OFFSET,
87+
CONTIGUOUS_LEN,
88+
);
89+
bench_read_exact(&mut group, "vec", "contiguous", VecMemory::new(MEMORY_LEN), CONTIGUOUS_OFFSET, CONTIGUOUS_LEN);
90+
bench_read_exact(
91+
&mut group,
92+
"paged",
93+
"contiguous",
94+
PagedMemory::new(MEMORY_LEN, CHUNK_SIZE),
95+
CONTIGUOUS_OFFSET,
96+
CONTIGUOUS_LEN,
97+
);
98+
99+
bench_write_all(&mut group, "vec", "cross_chunk", VecMemory::new(MEMORY_LEN), CROSS_CHUNK_OFFSET, CROSS_CHUNK_LEN);
100+
bench_write_all(
101+
&mut group,
102+
"paged",
103+
"cross_chunk",
104+
PagedMemory::new(MEMORY_LEN, CHUNK_SIZE),
105+
CROSS_CHUNK_OFFSET,
106+
CROSS_CHUNK_LEN,
107+
);
108+
bench_read_exact(&mut group, "vec", "cross_chunk", VecMemory::new(MEMORY_LEN), CROSS_CHUNK_OFFSET, CROSS_CHUNK_LEN);
109+
bench_read_exact(
110+
&mut group,
111+
"paged",
112+
"cross_chunk",
113+
PagedMemory::new(MEMORY_LEN, CHUNK_SIZE),
114+
CROSS_CHUNK_OFFSET,
115+
CROSS_CHUNK_LEN,
116+
);
117+
118+
group.finish();
119+
}
120+
121+
criterion_group!(benches, criterion_benchmark);
122+
criterion_main!(benches);

crates/tinywasm/benches/tinywasm.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use criterion::{Criterion, criterion_group, criterion_main};
22
use eyre::Result;
3-
use tinywasm::{FuncContext, HostFunction, Imports, ModuleInstance, Store, types};
3+
use tinywasm::{
4+
Engine, FuncContext, HostFunction, Imports, MemoryBackend, ModuleInstance, Store, engine::Config, types,
5+
};
46
use types::TinyWasmModule;
57

68
const WASM: &[u8] = include_bytes!("../../../examples/rust/out/tinywasm.wasm");
@@ -22,7 +24,8 @@ fn tinywasm_from_twasm(twasm: &[u8]) -> Result<TinyWasmModule> {
2224
}
2325

2426
fn tinywasm_run(module: TinyWasmModule) -> Result<()> {
25-
let mut store = Store::default();
27+
let engine = Engine::new(Config::default().with_memory_backend(MemoryBackend::paged(64 * 1024)));
28+
let mut store = Store::new(engine);
2629
let mut imports = Imports::default();
2730
imports.define("env", "printi32", HostFunction::from(&mut store, |_: FuncContext<'_>, _: i32| Ok(())));
2831
let instance = ModuleInstance::instantiate(&mut store, module.into(), Some(imports)).expect("instantiate");

crates/tinywasm/benches/tinywasm_modes.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ fn criterion_benchmark(c: &mut Criterion) {
5858
let mut group = c.benchmark_group("tinywasm_modes");
5959
group.measurement_time(BENCH_MEASUREMENT_TIME);
6060

61-
let per_instruction_engine = Engine::new(Config::new().fuel_policy(FuelPolicy::PerInstruction));
61+
let per_instruction_engine = Engine::new(Config::new().with_fuel_policy(FuelPolicy::PerInstruction));
6262
group.bench_function("resume_fuel_per_instruction", |b| {
6363
b.iter_batched_ref(
6464
|| {
@@ -70,7 +70,7 @@ fn criterion_benchmark(c: &mut Criterion) {
7070
)
7171
});
7272

73-
let weighted_engine = Engine::new(Config::new().fuel_policy(FuelPolicy::Weighted));
73+
let weighted_engine = Engine::new(Config::new().with_fuel_policy(FuelPolicy::Weighted));
7474
group.bench_function("resume_fuel_weighted", |b| {
7575
b.iter_batched_ref(
7676
|| setup_typed_func(module.clone(), Some(weighted_engine.clone())).expect("setup fuel weighted"),

crates/tinywasm/src/engine.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
use alloc::sync::Arc;
22

3+
/// Memory backend types and traits.
4+
pub use crate::store::{LinearMemory, MemoryBackend, PagedMemory, VecMemory};
5+
36
/// Global configuration for the WebAssembly interpreter
47
///
58
/// Can be cheaply cloned and shared across multiple executions and threads.
@@ -66,6 +69,8 @@ pub struct Config {
6669
pub max_call_stack_size: usize,
6770
/// Fuel accounting policy used by budgeted execution.
6871
pub fuel_policy: FuelPolicy,
72+
/// Backend used for runtime memories.
73+
pub memory_backend: MemoryBackend,
6974
}
7075

7176
impl Config {
@@ -75,10 +80,26 @@ impl Config {
7580
}
7681

7782
/// Set the fuel accounting policy for budgeted execution.
78-
pub fn fuel_policy(mut self, fuel_policy: FuelPolicy) -> Self {
83+
pub fn with_fuel_policy(mut self, fuel_policy: FuelPolicy) -> Self {
7984
self.fuel_policy = fuel_policy;
8085
self
8186
}
87+
88+
/// Set the backend used for runtime memories.
89+
pub fn with_memory_backend(mut self, memory_backend: MemoryBackend) -> Self {
90+
self.memory_backend = memory_backend;
91+
self
92+
}
93+
94+
/// Get the current fuel policy
95+
pub fn fuel_policy(&self) -> FuelPolicy {
96+
self.fuel_policy
97+
}
98+
99+
/// Get the current memory backend
100+
pub fn memory_backend(&self) -> &MemoryBackend {
101+
&self.memory_backend
102+
}
82103
}
83104

84105
impl Default for Config {
@@ -89,6 +110,7 @@ impl Default for Config {
89110
stack_128_size: DEFAULT_VALUE_STACK_128_SIZE,
90111
max_call_stack_size: DEFAULT_MAX_CALL_STACK_SIZE,
91112
fuel_policy: FuelPolicy::default(),
113+
memory_backend: MemoryBackend::default(),
92114
}
93115
}
94116
}

crates/tinywasm/src/error.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use alloc::boxed::Box;
12
use alloc::string::{String, ToString};
23
use alloc::vec::Vec;
34
use core::fmt::Debug;
@@ -18,7 +19,7 @@ pub enum Error {
1819
Linker(LinkingError),
1920

2021
/// A WebAssembly feature is not supported
21-
UnsupportedFeature(String),
22+
UnsupportedFeature(&'static str),
2223

2324
/// An unknown error occurred
2425
Other(String),
@@ -89,6 +90,9 @@ pub enum Trap {
8990
/// An unreachable instruction was executed
9091
Unreachable,
9192

93+
/// A host function returned an error
94+
HostFunction(Box<dyn core::error::Error + Send + Sync>),
95+
9296
/// An out-of-bounds memory access occurred
9397
MemoryOutOfBounds {
9498
/// The offset of the access
@@ -143,6 +147,9 @@ pub enum Trap {
143147
/// The actual type
144148
actual: FuncType,
145149
},
150+
151+
/// Catch-all for other messages
152+
Other(&'static str),
146153
}
147154

148155
impl Trap {
@@ -160,6 +167,8 @@ impl Trap {
160167
Self::UndefinedElement { .. } => "undefined element",
161168
Self::UninitializedElement { .. } => "uninitialized element",
162169
Self::IndirectCallTypeMismatch { .. } => "indirect call type mismatch",
170+
Self::HostFunction(_) => "host function trap",
171+
Self::Other(message) => message,
163172
}
164173
}
165174
}
@@ -231,6 +240,8 @@ impl Display for LinkingError {
231240
impl Display for Trap {
232241
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
233242
match self {
243+
Self::Other(message) => write!(f, "{message}"),
244+
Self::HostFunction(message) => write!(f, "host function trap: {message}"),
234245
Self::Unreachable => write!(f, "unreachable"),
235246
Self::MemoryOutOfBounds { offset, len, max } => {
236247
write!(f, "out of bounds memory access: offset={offset}, len={len}, max={max}")
@@ -265,6 +276,16 @@ impl Debug for Error {
265276

266277
impl core::error::Error for Error {}
267278

279+
#[cfg(feature = "std")]
280+
impl From<Error> for crate::std::io::Error {
281+
fn from(value: Error) -> Self {
282+
match value {
283+
Error::Io(err) => err,
284+
other => Self::other(other.to_string()),
285+
}
286+
}
287+
}
288+
268289
#[cfg(feature = "parser")]
269290
impl From<tinywasm_parser::ParseError> for Error {
270291
fn from(value: tinywasm_parser::ParseError) -> Self {

crates/tinywasm/src/imports.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -276,8 +276,7 @@ impl Imports {
276276
return Err(LinkingError::incompatible_import_type(import).into());
277277
};
278278
let mem = store.state.get_mem(memory.0.addr);
279-
let (size, kind) = { (mem.page_count, mem.kind) };
280-
Self::compare_memory_types(import, &kind, import_ty, size)?;
279+
Self::compare_memory_types(import, &mem.kind, import_ty, mem.page_count)?;
281280
imports.memories.push(memory.0.addr);
282281
}
283282
Extern::Function(func_handle) => {
@@ -327,8 +326,7 @@ impl Imports {
327326
}
328327
(ExternVal::Memory(memory_addr), ImportKind::Memory(ty)) => {
329328
let mem = store.state.get_mem(memory_addr);
330-
let (size, kind) = { (mem.page_count, mem.kind) };
331-
Self::compare_memory_types(import, &kind, ty, size)?;
329+
Self::compare_memory_types(import, &mem.kind, ty, mem.page_count)?;
332330
imports.memories.push(memory_addr);
333331
}
334332
(ExternVal::Func(func_addr), ImportKind::Function(ty)) => {

crates/tinywasm/src/instance.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ impl ModuleInstance {
133133

134134
addrs.funcs.extend(store.init_funcs(&module.0.funcs, idx));
135135
addrs.tables.extend(store.init_tables(&module.0.table_types, idx));
136-
addrs.memories.extend(store.init_memories(&module.0.memory_types, idx));
136+
addrs.memories.extend(store.init_memories(&module.0.memory_types, idx)?);
137137
let global_addrs = store.init_globals(addrs.globals, &module.0.globals, &addrs.funcs, idx)?;
138138
let (elem_addrs, elem_trapped) =
139139
store.init_elements(&addrs.tables, &addrs.funcs, &global_addrs, &module.0.elements, idx)?;

0 commit comments

Comments
 (0)