Skip to content
This repository was archived by the owner on Oct 3, 2025. It is now read-only.

Commit 9d30a1b

Browse files
pref: callstack/callframe improvements
Signed-off-by: Henry Gressmann <mail@henrygressmann.de>
1 parent a89f75c commit 9d30a1b

File tree

11 files changed

+80
-41
lines changed

11 files changed

+80
-41
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ test=false
2929
name="selfhosted"
3030
harness=false
3131

32+
[[bench]]
33+
name="fibonacci"
34+
harness=false
35+
3236
[profile.bench]
3337
opt-level=3
3438
lto="thin"

benches/fibonacci.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
mod util;
2+
use criterion::{criterion_group, criterion_main, Criterion};
3+
use tinywasm::types::TinyWasmModule;
4+
use util::tinywasm_module;
5+
6+
fn run_tinywasm(module: TinyWasmModule) {
7+
use tinywasm::*;
8+
let module = Module::from(module);
9+
let mut store = Store::default();
10+
let imports = Imports::default();
11+
let instance = ModuleInstance::instantiate(&mut store, module, Some(imports)).expect("instantiate");
12+
let hello = instance.exported_func::<i32, i32>(&mut store, "fibonacci").expect("exported_func");
13+
hello.call(&mut store, 28).expect("call");
14+
}
15+
16+
fn run_wasmi() {
17+
use wasmi::*;
18+
let engine = Engine::default();
19+
let module = wasmi::Module::new(&engine, FIBONACCI).expect("wasmi::Module::new");
20+
let mut store = Store::new(&engine, ());
21+
let linker = <Linker<()>>::new(&engine);
22+
let instance = linker.instantiate(&mut store, &module).expect("instantiate").start(&mut store).expect("start");
23+
let hello = instance.get_typed_func::<i32, i32>(&mut store, "fibonacci").expect("get_typed_func");
24+
hello.call(&mut store, 28).expect("call");
25+
}
26+
27+
const FIBONACCI: &[u8] = include_bytes!("../examples/rust/out/fibonacci.wasm");
28+
fn criterion_benchmark(c: &mut Criterion) {
29+
let module = tinywasm_module(FIBONACCI);
30+
31+
let mut group = c.benchmark_group("fibonacci");
32+
group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(module.clone())));
33+
// group.bench_function("wasmi", |b| b.iter(|| run_wasmi()));
34+
}
35+
36+
criterion_group!(
37+
name = benches;
38+
config = Criterion::default().sample_size(50).measurement_time(std::time::Duration::from_secs(5)).significance_level(0.1);
39+
targets = criterion_benchmark
40+
);
41+
42+
criterion_main!(benches);

benches/selfhosted.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,15 @@ const TINYWASM: &[u8] = include_bytes!("../examples/rust/out/tinywasm.wasm");
3030
fn criterion_benchmark(c: &mut Criterion) {
3131
let module = tinywasm_module(TINYWASM);
3232

33-
c.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(module.clone())));
34-
c.bench_function("wasmi", |b| b.iter(|| run_wasmi()));
33+
let mut group = c.benchmark_group("selfhosted");
34+
group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(module.clone())));
35+
group.bench_function("wasmi", |b| b.iter(|| run_wasmi()));
3536
}
3637

37-
criterion_group!(benches, criterion_benchmark);
38+
criterion_group!(
39+
name = benches;
40+
config = Criterion::default().sample_size(500).measurement_time(std::time::Duration::from_secs(5)).significance_level(0.1);
41+
targets = criterion_benchmark
42+
);
43+
3844
criterion_main!(benches);

crates/parser/src/module.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use core::fmt::Debug;
55
use tinywasm_types::{Data, Element, Export, FuncType, Global, Import, Instruction, MemoryType, TableType, ValType};
66
use wasmparser::{Payload, Validator};
77

8-
#[derive(Debug, Clone, PartialEq)]
8+
#[derive(Debug, Clone)]
99
pub struct CodeSection {
1010
pub locals: Box<[ValType]>,
1111
pub body: Box<[Instruction]>,

crates/tinywasm/src/runtime/interpreter/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ macro_rules! break_to {
115115
/// Run a single step of the interpreter
116116
/// A seperate function is used so later, we can more easily implement
117117
/// a step-by-step debugger (using generators once they're stable?)
118+
// TODO: perf: don't push then pop the call frame, just pass it via ExecResult::Call instead
118119
#[inline(always)] // this improves performance by more than 20% in some cases
119120
fn exec_one(
120121
cf: &mut CallFrame,
@@ -611,7 +612,7 @@ fn exec_one(
611612
let elem_idx = module.resolve_elem_addr(*elem_index);
612613
let elem = store.get_elem(elem_idx as usize)?;
613614

614-
if elem.kind != ElementKind::Passive {
615+
if let ElementKind::Passive = elem.kind {
615616
return Err(Trap::TableOutOfBounds { offset: 0, len: 0, max: 0 }.into());
616617
}
617618

crates/tinywasm/src/runtime/stack/blocks.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,6 @@ impl Labels {
1919
#[inline]
2020
/// get the label at the given index, where 0 is the top of the stack
2121
pub(crate) fn get_relative_to_top(&self, index: usize) -> Option<&LabelFrame> {
22-
let len = self.0.len();
23-
if index >= len {
24-
return None;
25-
}
26-
2722
self.0.get(self.0.len() - index - 1)
2823
}
2924

crates/tinywasm/src/runtime/stack/call_stack.rs

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,52 +3,45 @@ use crate::{
33
runtime::{BlockType, RawWasmValue},
44
Error, FunctionInstance, Result, Trap,
55
};
6+
use alloc::vec;
67
use alloc::{boxed::Box, rc::Rc, vec::Vec};
78
use tinywasm_types::{ValType, WasmValue};
89

910
use super::{blocks::Labels, LabelFrame};
1011

1112
// minimum call stack size
12-
const CALL_STACK_SIZE: usize = 128;
13+
const CALL_STACK_SIZE: usize = 256;
1314
const CALL_STACK_MAX_SIZE: usize = 1024;
1415

1516
#[derive(Debug)]
1617
pub(crate) struct CallStack {
1718
stack: Vec<CallFrame>,
18-
top: usize,
1919
}
2020

2121
impl Default for CallStack {
2222
fn default() -> Self {
23-
Self { stack: Vec::with_capacity(CALL_STACK_SIZE), top: 0 }
23+
Self { stack: Vec::with_capacity(CALL_STACK_SIZE) }
2424
}
2525
}
2626

2727
impl CallStack {
28+
#[inline]
2829
pub(crate) fn is_empty(&self) -> bool {
29-
self.top == 0
30+
self.stack.is_empty()
3031
}
3132

33+
#[inline]
3234
pub(crate) fn pop(&mut self) -> Result<CallFrame> {
33-
assert!(self.top <= self.stack.len());
34-
if self.top == 0 {
35-
return Err(Error::CallStackEmpty);
36-
}
37-
38-
self.top -= 1;
39-
Ok(self.stack.pop().unwrap())
35+
self.stack.pop().ok_or_else(|| Error::CallStackEmpty)
4036
}
4137

4238
#[inline]
4339
pub(crate) fn push(&mut self, call_frame: CallFrame) -> Result<()> {
44-
assert!(self.top <= self.stack.len(), "stack is too small");
45-
4640
log::debug!("stack size: {}", self.stack.len());
4741
if self.stack.len() >= CALL_STACK_MAX_SIZE {
4842
return Err(Trap::CallStackOverflow.into());
4943
}
5044

51-
self.top += 1;
5245
self.stack.push(call_frame);
5346
Ok(())
5447
}
@@ -79,7 +72,6 @@ impl CallFrame {
7972
/// Break to a block at the given index (relative to the current frame)
8073
/// Returns `None` if there is no block at the given index (e.g. if we need to return, this is handled by the caller)
8174
pub(crate) fn break_to(&mut self, break_to_relative: u32, value_stack: &mut super::ValueStack) -> Option<()> {
82-
log::debug!("break_to_relative: {}", break_to_relative);
8375
let break_to = self.labels.get_relative_to_top(break_to_relative as usize)?;
8476

8577
// instr_ptr points to the label instruction, but the next step
@@ -111,14 +103,15 @@ impl CallFrame {
111103
Some(())
112104
}
113105

106+
// TOOD: perf: this function is pretty hot
107+
// Especially the two `extend` calls
114108
pub(crate) fn new_raw(
115109
func_instance_ptr: Rc<FunctionInstance>,
116110
params: &[RawWasmValue],
117111
local_types: Vec<ValType>,
118112
) -> Self {
119-
let mut locals = Vec::with_capacity(local_types.len() + params.len());
120-
locals.extend(params.iter().cloned());
121-
locals.extend(local_types.iter().map(|_| RawWasmValue::default()));
113+
let mut locals = vec![RawWasmValue::default(); local_types.len() + params.len()];
114+
locals[..params.len()].copy_from_slice(params);
122115

123116
Self {
124117
instr_ptr: 0,

crates/tinywasm/src/runtime/stack/value_stack.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,7 @@ impl ValueStack {
9898
}
9999

100100
pub(crate) fn break_to(&mut self, new_stack_size: usize, result_count: usize) {
101-
let len = self.stack.len();
102-
self.stack.copy_within((len - result_count)..len, new_stack_size);
103-
self.stack.truncate(new_stack_size + result_count);
101+
self.stack.drain(new_stack_size..(self.stack.len() - result_count));
104102
}
105103

106104
#[inline]

crates/types/src/instructions.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ pub enum BlockArgs {
1010
}
1111

1212
/// Represents a memory immediate in a WebAssembly memory instruction.
13-
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
13+
#[derive(Debug, Copy, Clone)]
1414
pub struct MemoryArg {
1515
pub mem_addr: MemAddr,
1616
pub align: u8,
@@ -23,7 +23,7 @@ type BrTableLen = usize;
2323
type EndOffset = usize;
2424
type ElseOffset = usize;
2525

26-
#[derive(Debug, Clone, Copy, PartialEq)]
26+
#[derive(Debug, Clone, Copy)]
2727
pub enum ConstInstruction {
2828
I32Const(i32),
2929
I64Const(i64),
@@ -46,7 +46,7 @@ pub enum ConstInstruction {
4646
/// This makes it easier to implement the label stack (we call it BlockFrameStack) iteratively.
4747
///
4848
/// See <https://webassembly.github.io/spec/core/binary/instructions.html>
49-
#[derive(Debug, Clone, Copy, PartialEq)]
49+
#[derive(Debug, Clone, Copy)]
5050
pub enum Instruction {
5151
// Custom Instructions
5252
BrLabel(LabelAddr),

crates/types/src/lib.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ pub struct TinyWasmModule {
7171
/// A WebAssembly value.
7272
///
7373
/// See <https://webassembly.github.io/spec/core/syntax/types.html#value-types>
74-
#[derive(Clone, PartialEq, Copy)]
74+
#[derive(Clone, Copy)]
7575
pub enum WasmValue {
7676
// Num types
7777
/// A 32-bit integer.
@@ -253,7 +253,7 @@ impl WasmValue {
253253
}
254254

255255
/// Type of a WebAssembly value.
256-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
256+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
257257
pub enum ValType {
258258
/// A 32-bit integer.
259259
I32,
@@ -380,13 +380,13 @@ pub struct Global {
380380
pub init: ConstInstruction,
381381
}
382382

383-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
383+
#[derive(Debug, Clone, Copy, PartialEq)]
384384
pub struct GlobalType {
385385
pub mutable: bool,
386386
pub ty: ValType,
387387
}
388388

389-
#[derive(Debug, Clone, PartialEq, Eq)]
389+
#[derive(Debug, Clone)]
390390
pub struct TableType {
391391
pub element_type: ValType,
392392
pub size_initial: u32,
@@ -406,7 +406,7 @@ impl TableType {
406406
#[derive(Debug, Clone)]
407407

408408
/// Represents a memory's type.
409-
#[derive(Copy, PartialEq, Eq, Hash)]
409+
#[derive(Copy)]
410410
pub struct MemoryType {
411411
pub arch: MemoryArch,
412412
pub page_count_initial: u64,
@@ -480,7 +480,7 @@ pub struct Element {
480480
pub ty: ValType,
481481
}
482482

483-
#[derive(Debug, Clone, Copy, PartialEq)]
483+
#[derive(Debug, Clone, Copy)]
484484
pub enum ElementKind {
485485
Passive,
486486
Active { table: TableAddr, offset: ConstInstruction },

0 commit comments

Comments
 (0)