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

Commit bf30b77

Browse files
perf: reduce block overhead (#3)
1 parent 7f99f96 commit bf30b77

File tree

14 files changed

+108
-79
lines changed

14 files changed

+108
-79
lines changed

BENCHMARKS.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ All runtimes are compiled with the following settings:
3131
| Benchmark | Native | TinyWasm\* | Wasmi | Wasmer (Single Pass) |
3232
| ------------ | -------- | ---------- | --------- | -------------------- |
3333
| `fib` | \*\* | ` 44.11µs` | `49.46µs` | ` 50.65µs` |
34-
| `fib-rec` | `0.26ms` | ` 24.91ms` | ` 4.62ms` | ` 0.49ms` |
34+
| `fib-rec` | `0.26ms` | ` 20.99ms` | ` 4.64ms` | ` 0.50ms` |
3535
| `argon2id` | `0.53ms` | `109.38ms` | `45.85ms` | ` 4.82ms` |
36-
| `selfhosted` | `0.05ms` | ` 2.07ms` | ` 4.26ms` | `260.32ms` |
36+
| `selfhosted` | `0.05ms` | ` 1.97ms` | ` 4.26ms` | `260.32ms` |
3737

38-
_\* converting WASM to TinyWasm bytecode is not included. 7.2.ms is the time it takes to convert `tinywasm.wasm` to TinyWasm bytecode._
38+
_\* converting WASM to TinyWasm bytecode is not included. I takes ~7ms to convert `tinywasm.wasm` to TinyWasm bytecode._
3939

4040
_\*\* essentially instant as it gets computed at compile time._
4141

Cargo.lock

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ test=false
3030
color-eyre="0.6"
3131
tinywasm={path="crates/tinywasm", features=["unsafe"]}
3232
wat={version="1.0"}
33+
pretty_env_logger="0.5"
3334

3435
[profile.bench]
3536
opt-level=3

crates/parser/src/conversion.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -291,8 +291,6 @@ pub(crate) fn process_operators(
291291
}
292292
End => {
293293
if let Some(label_pointer) = labels_ptrs.pop() {
294-
log::debug!("ending block: {:?}", instructions[label_pointer]);
295-
296294
let current_instr_ptr = instructions.len();
297295

298296
// last_label_pointer is Some if we're ending a block

crates/tinywasm/src/error.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,17 @@ pub enum Error {
2424
FuncDidNotReturn,
2525

2626
/// The stack is empty
27-
StackUnderflow,
27+
ValueStackUnderflow,
2828

2929
/// The label stack is empty
30-
LabelStackUnderflow,
30+
BlockStackUnderflow,
31+
32+
/// The call stack is empty
33+
CallStackUnderflow,
3134

3235
/// An invalid label type was encountered
3336
InvalidLabelType,
3437

35-
/// The call stack is empty
36-
CallStackEmpty,
37-
3838
/// The store is not the one that the module instance was instantiated in
3939
InvalidStore,
4040

@@ -189,13 +189,13 @@ impl Display for Error {
189189

190190
Self::Trap(trap) => write!(f, "trap: {}", trap),
191191
Self::Linker(err) => write!(f, "linking error: {}", err),
192-
Self::CallStackEmpty => write!(f, "call stack empty"),
192+
Self::CallStackUnderflow => write!(f, "call stack empty"),
193193
Self::InvalidLabelType => write!(f, "invalid label type"),
194194
Self::Other(message) => write!(f, "unknown error: {}", message),
195195
Self::UnsupportedFeature(feature) => write!(f, "unsupported feature: {}", feature),
196196
Self::FuncDidNotReturn => write!(f, "function did not return"),
197-
Self::LabelStackUnderflow => write!(f, "label stack underflow"),
198-
Self::StackUnderflow => write!(f, "stack underflow"),
197+
Self::BlockStackUnderflow => write!(f, "label stack underflow"),
198+
Self::ValueStackUnderflow => write!(f, "value stack underflow"),
199199
Self::InvalidStore => write!(f, "invalid store"),
200200
}
201201
}

crates/tinywasm/src/func.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ impl FuncHandle {
5858

5959
// 6. Let f be the dummy frame
6060
let call_frame =
61-
CallFrame::new(wasm_func.clone(), func_inst.owner, params.iter().map(|v| RawWasmValue::from(*v)));
61+
CallFrame::new(wasm_func.clone(), func_inst.owner, params.iter().map(|v| RawWasmValue::from(*v)), 0);
6262

6363
// 7. Push the frame f to the call stack
6464
// & 8. Push the values to the stack (Not needed since the call frame owns the values)

crates/tinywasm/src/instance.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ impl ModuleInstance {
6161
// don't need to create a auxiliary frame etc.
6262

6363
let idx = store.next_module_instance_idx();
64-
log::error!("Instantiating module at index {}", idx);
64+
log::info!("Instantiating module at index {}", idx);
6565
let imports = imports.unwrap_or_default();
6666

6767
let mut addrs = imports.link(store, &module, idx)?;

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
// from a function, so we need to check if the label stack is empty
1212
macro_rules! break_to {
1313
($cf:ident, $stack:ident, $break_to_relative:ident) => {{
14-
if $cf.break_to(*$break_to_relative, &mut $stack.values).is_none() {
14+
if $cf.break_to(*$break_to_relative, &mut $stack.values, &mut $stack.blocks).is_none() {
1515
if $stack.call_stack.is_empty() {
1616
return Ok(ExecResult::Return);
1717
} else {
@@ -53,7 +53,6 @@ macro_rules! mem_load {
5353

5454
const LEN: usize = core::mem::size_of::<$load_type>();
5555
let val = mem_ref.load_as::<LEN, $load_type>(addr, $arg.align as usize)?;
56-
// let loaded_value = mem_ref.load_as::<$load_type>(addr, $arg.align as usize)?;
5756
$stack.values.push((val as $target_type).into());
5857
}};
5958
}

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

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use core::ops::{BitAnd, BitOr, BitXor, Neg};
44
use tinywasm_types::{ElementKind, ValType};
55

66
use super::{InterpreterRuntime, Stack};
7-
use crate::runtime::{BlockType, CallFrame, LabelFrame};
7+
use crate::runtime::{BlockFrame, BlockType, CallFrame};
88
use crate::{cold, log, unlikely};
99
use crate::{Error, FuncContext, ModuleInstance, Result, Store, Trap};
1010

@@ -32,8 +32,13 @@ impl InterpreterRuntime {
3232
match exec_one(&mut cf, stack, store, &current_module) {
3333
// Continue execution at the new top of the call stack
3434
Ok(ExecResult::Call) => {
35+
let old = cf.block_ptr;
3536
cf = stack.call_stack.pop()?;
3637

38+
if old > cf.block_ptr {
39+
stack.blocks.truncate(old);
40+
}
41+
3742
// keeping the pointer seperate from the call frame is about 2% faster
3843
// than storing it in the call frame
3944
if cf.func_instance.1 != current_module.id() {
@@ -123,7 +128,7 @@ fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &M
123128
};
124129

125130
let params = stack.values.pop_n_rev(wasm_func.ty.params.len())?;
126-
let call_frame = CallFrame::new(wasm_func, func_inst.owner, params);
131+
let call_frame = CallFrame::new(wasm_func, func_inst.owner, params, stack.blocks.len());
127132

128133
// push the call frame
129134
cf.instr_ptr += 1; // skip the call instruction
@@ -180,7 +185,7 @@ fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &M
180185
}
181186

182187
let params = stack.values.pop_n_rev(wasm_func.ty.params.len())?;
183-
let call_frame = CallFrame::new(wasm_func, func_inst.owner, params);
188+
let call_frame = CallFrame::new(wasm_func, func_inst.owner, params, stack.blocks.len());
184189

185190
// push the call frame
186191
cf.instr_ptr += 1; // skip the call instruction
@@ -194,8 +199,8 @@ fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &M
194199
If(args, else_offset, end_offset) => {
195200
// truthy value is on the top of the stack, so enter the then block
196201
if stack.values.pop_t::<i32>()? != 0 {
197-
cf.enter_label(
198-
LabelFrame::new(
202+
cf.enter_block(
203+
BlockFrame::new(
199204
cf.instr_ptr,
200205
cf.instr_ptr + *end_offset,
201206
stack.values.len(), // - params,
@@ -204,13 +209,14 @@ fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &M
204209
module,
205210
),
206211
&mut stack.values,
212+
&mut stack.blocks,
207213
);
208214
return Ok(ExecResult::Ok);
209215
}
210216

211217
// falsy value is on the top of the stack
212218
if let Some(else_offset) = else_offset {
213-
let label = LabelFrame::new(
219+
let label = BlockFrame::new(
214220
cf.instr_ptr + *else_offset,
215221
cf.instr_ptr + *end_offset,
216222
stack.values.len(), // - params,
@@ -219,15 +225,15 @@ fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &M
219225
module,
220226
);
221227
cf.instr_ptr += *else_offset;
222-
cf.enter_label(label, &mut stack.values);
228+
cf.enter_block(label, &mut stack.values, &mut stack.blocks);
223229
} else {
224230
cf.instr_ptr += *end_offset;
225231
}
226232
}
227233

228234
Loop(args, end_offset) => {
229-
cf.enter_label(
230-
LabelFrame::new(
235+
cf.enter_block(
236+
BlockFrame::new(
231237
cf.instr_ptr,
232238
cf.instr_ptr + *end_offset,
233239
stack.values.len(), // - params,
@@ -236,12 +242,13 @@ fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &M
236242
module,
237243
),
238244
&mut stack.values,
245+
&mut stack.blocks,
239246
);
240247
}
241248

242249
Block(args, end_offset) => {
243-
cf.enter_label(
244-
LabelFrame::new(
250+
cf.enter_block(
251+
BlockFrame::new(
245252
cf.instr_ptr,
246253
cf.instr_ptr + *end_offset,
247254
stack.values.len(), // - params,
@@ -250,6 +257,7 @@ fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &M
250257
module,
251258
),
252259
&mut stack.values,
260+
&mut stack.blocks,
253261
);
254262
}
255263

@@ -291,10 +299,10 @@ fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &M
291299
},
292300

293301
EndFunc => {
294-
assert!(
295-
cf.labels.len() == 0,
296-
"endfunc: block frames not empty, this should have been validated by the parser"
297-
);
302+
if stack.blocks.len() != cf.block_ptr {
303+
cold();
304+
panic!("endfunc: block frames not empty, this should have been validated by the parser");
305+
}
298306

299307
match stack.call_stack.is_empty() {
300308
true => return Ok(ExecResult::Return),
@@ -304,7 +312,7 @@ fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &M
304312

305313
// We're essentially using else as a EndBlockFrame instruction for if blocks
306314
Else(end_offset) => {
307-
let Some(block) = cf.labels.pop() else {
315+
let Some(block) = stack.blocks.pop() else {
308316
cold();
309317
panic!("else: no label to end, this should have been validated by the parser");
310318
};
@@ -316,16 +324,28 @@ fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &M
316324

317325
EndBlockFrame => {
318326
// remove the label from the label stack
319-
let Some(block) = cf.labels.pop() else {
327+
let Some(block) = stack.blocks.pop() else {
320328
cold();
321-
panic!("end: no label to end, this should have been validated by the parser");
329+
panic!("end blockframe: no label to end, this should have been validated by the parser");
322330
};
323-
stack.values.truncate_keep(block.stack_ptr, block.results)
331+
332+
stack.values.truncate_keep(block.stack_ptr, block.results);
324333
}
325334

326335
LocalGet(local_index) => stack.values.push(cf.get_local(*local_index as usize)),
327336
LocalSet(local_index) => cf.set_local(*local_index as usize, stack.values.pop()?),
328-
LocalTee(local_index) => cf.set_local(*local_index as usize, *stack.values.last()?),
337+
LocalTee(local_index) => {
338+
let last_val = match stack.values.last() {
339+
Ok(val) => val,
340+
Err(_) => {
341+
log::error!("index: {}", local_index);
342+
log::error!("stack: {:?}", stack.values);
343+
344+
panic!();
345+
}
346+
};
347+
cf.set_local(*local_index as usize, *last_val)
348+
}
329349

330350
GlobalGet(global_index) => {
331351
let idx = module.resolve_global_addr(*global_index);
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
1-
mod blocks;
1+
mod block_stack;
22
mod call_stack;
33
mod value_stack;
44

55
use self::{call_stack::CallStack, value_stack::ValueStack};
6-
pub(crate) use blocks::{BlockType, LabelFrame};
6+
pub(crate) use block_stack::{BlockFrame, BlockStack, BlockType};
77
pub(crate) use call_stack::CallFrame;
88

99
/// A WebAssembly Stack
1010
#[derive(Debug)]
1111
pub struct Stack {
1212
pub(crate) values: ValueStack,
13+
pub(crate) blocks: BlockStack,
1314
pub(crate) call_stack: CallStack,
1415
}
1516

1617
impl Stack {
1718
pub(crate) fn new(call_frame: CallFrame) -> Self {
18-
Self { values: ValueStack::default(), call_stack: CallStack::new(call_frame) }
19+
Self { values: ValueStack::default(), blocks: BlockStack::default(), call_stack: CallStack::new(call_frame) }
1920
}
2021
}

0 commit comments

Comments
 (0)