From 9d30a1b0d93b45c05f86e61df77359303aab449d Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Sat, 27 Jan 2024 02:40:08 +0100 Subject: pref: callstack/callframe improvements Signed-off-by: Henry Gressmann --- Cargo.toml | 4 +++ benches/fibonacci.rs | 42 ++++++++++++++++++++++++ benches/selfhosted.rs | 12 +++++-- crates/parser/src/module.rs | 2 +- crates/tinywasm/src/runtime/interpreter/mod.rs | 3 +- crates/tinywasm/src/runtime/stack/blocks.rs | 5 --- crates/tinywasm/src/runtime/stack/call_stack.rs | 29 +++++++--------- crates/tinywasm/src/runtime/stack/value_stack.rs | 4 +-- crates/types/src/instructions.rs | 6 ++-- crates/types/src/lib.rs | 12 +++---- examples/rust/Cargo.toml | 2 +- 11 files changed, 80 insertions(+), 41 deletions(-) create mode 100644 benches/fibonacci.rs diff --git a/Cargo.toml b/Cargo.toml index 20674d9..f52bc22 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,10 @@ test=false name="selfhosted" harness=false +[[bench]] +name="fibonacci" +harness=false + [profile.bench] opt-level=3 lto="thin" diff --git a/benches/fibonacci.rs b/benches/fibonacci.rs new file mode 100644 index 0000000..b0a4834 --- /dev/null +++ b/benches/fibonacci.rs @@ -0,0 +1,42 @@ +mod util; +use criterion::{criterion_group, criterion_main, Criterion}; +use tinywasm::types::TinyWasmModule; +use util::tinywasm_module; + +fn run_tinywasm(module: TinyWasmModule) { + use tinywasm::*; + let module = Module::from(module); + let mut store = Store::default(); + let imports = Imports::default(); + let instance = ModuleInstance::instantiate(&mut store, module, Some(imports)).expect("instantiate"); + let hello = instance.exported_func::(&mut store, "fibonacci").expect("exported_func"); + hello.call(&mut store, 28).expect("call"); +} + +fn run_wasmi() { + use wasmi::*; + let engine = Engine::default(); + let module = wasmi::Module::new(&engine, FIBONACCI).expect("wasmi::Module::new"); + let mut store = Store::new(&engine, ()); + let linker = >::new(&engine); + let instance = linker.instantiate(&mut store, &module).expect("instantiate").start(&mut store).expect("start"); + let hello = instance.get_typed_func::(&mut store, "fibonacci").expect("get_typed_func"); + hello.call(&mut store, 28).expect("call"); +} + +const FIBONACCI: &[u8] = include_bytes!("../examples/rust/out/fibonacci.wasm"); +fn criterion_benchmark(c: &mut Criterion) { + let module = tinywasm_module(FIBONACCI); + + let mut group = c.benchmark_group("fibonacci"); + group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(module.clone()))); + // group.bench_function("wasmi", |b| b.iter(|| run_wasmi())); +} + +criterion_group!( + name = benches; + config = Criterion::default().sample_size(50).measurement_time(std::time::Duration::from_secs(5)).significance_level(0.1); + targets = criterion_benchmark +); + +criterion_main!(benches); diff --git a/benches/selfhosted.rs b/benches/selfhosted.rs index 78ea48b..f78d958 100644 --- a/benches/selfhosted.rs +++ b/benches/selfhosted.rs @@ -30,9 +30,15 @@ const TINYWASM: &[u8] = include_bytes!("../examples/rust/out/tinywasm.wasm"); fn criterion_benchmark(c: &mut Criterion) { let module = tinywasm_module(TINYWASM); - c.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(module.clone()))); - c.bench_function("wasmi", |b| b.iter(|| run_wasmi())); + let mut group = c.benchmark_group("selfhosted"); + group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(module.clone()))); + group.bench_function("wasmi", |b| b.iter(|| run_wasmi())); } -criterion_group!(benches, criterion_benchmark); +criterion_group!( + name = benches; + config = Criterion::default().sample_size(500).measurement_time(std::time::Duration::from_secs(5)).significance_level(0.1); + targets = criterion_benchmark +); + criterion_main!(benches); diff --git a/crates/parser/src/module.rs b/crates/parser/src/module.rs index e0fb0cc..5bc6a7e 100644 --- a/crates/parser/src/module.rs +++ b/crates/parser/src/module.rs @@ -5,7 +5,7 @@ use core::fmt::Debug; use tinywasm_types::{Data, Element, Export, FuncType, Global, Import, Instruction, MemoryType, TableType, ValType}; use wasmparser::{Payload, Validator}; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct CodeSection { pub locals: Box<[ValType]>, pub body: Box<[Instruction]>, diff --git a/crates/tinywasm/src/runtime/interpreter/mod.rs b/crates/tinywasm/src/runtime/interpreter/mod.rs index 80fb3f9..285ba8d 100644 --- a/crates/tinywasm/src/runtime/interpreter/mod.rs +++ b/crates/tinywasm/src/runtime/interpreter/mod.rs @@ -115,6 +115,7 @@ macro_rules! break_to { /// Run a single step of the interpreter /// A seperate function is used so later, we can more easily implement /// a step-by-step debugger (using generators once they're stable?) +// TODO: perf: don't push then pop the call frame, just pass it via ExecResult::Call instead #[inline(always)] // this improves performance by more than 20% in some cases fn exec_one( cf: &mut CallFrame, @@ -611,7 +612,7 @@ fn exec_one( let elem_idx = module.resolve_elem_addr(*elem_index); let elem = store.get_elem(elem_idx as usize)?; - if elem.kind != ElementKind::Passive { + if let ElementKind::Passive = elem.kind { return Err(Trap::TableOutOfBounds { offset: 0, len: 0, max: 0 }.into()); } diff --git a/crates/tinywasm/src/runtime/stack/blocks.rs b/crates/tinywasm/src/runtime/stack/blocks.rs index 76252b5..ab2d28c 100644 --- a/crates/tinywasm/src/runtime/stack/blocks.rs +++ b/crates/tinywasm/src/runtime/stack/blocks.rs @@ -19,11 +19,6 @@ impl Labels { #[inline] /// get the label at the given index, where 0 is the top of the stack pub(crate) fn get_relative_to_top(&self, index: usize) -> Option<&LabelFrame> { - let len = self.0.len(); - if index >= len { - return None; - } - self.0.get(self.0.len() - index - 1) } diff --git a/crates/tinywasm/src/runtime/stack/call_stack.rs b/crates/tinywasm/src/runtime/stack/call_stack.rs index b19e332..46c745a 100644 --- a/crates/tinywasm/src/runtime/stack/call_stack.rs +++ b/crates/tinywasm/src/runtime/stack/call_stack.rs @@ -3,52 +3,45 @@ use crate::{ runtime::{BlockType, RawWasmValue}, Error, FunctionInstance, Result, Trap, }; +use alloc::vec; use alloc::{boxed::Box, rc::Rc, vec::Vec}; use tinywasm_types::{ValType, WasmValue}; use super::{blocks::Labels, LabelFrame}; // minimum call stack size -const CALL_STACK_SIZE: usize = 128; +const CALL_STACK_SIZE: usize = 256; const CALL_STACK_MAX_SIZE: usize = 1024; #[derive(Debug)] pub(crate) struct CallStack { stack: Vec, - top: usize, } impl Default for CallStack { fn default() -> Self { - Self { stack: Vec::with_capacity(CALL_STACK_SIZE), top: 0 } + Self { stack: Vec::with_capacity(CALL_STACK_SIZE) } } } impl CallStack { + #[inline] pub(crate) fn is_empty(&self) -> bool { - self.top == 0 + self.stack.is_empty() } + #[inline] pub(crate) fn pop(&mut self) -> Result { - assert!(self.top <= self.stack.len()); - if self.top == 0 { - return Err(Error::CallStackEmpty); - } - - self.top -= 1; - Ok(self.stack.pop().unwrap()) + self.stack.pop().ok_or_else(|| Error::CallStackEmpty) } #[inline] pub(crate) fn push(&mut self, call_frame: CallFrame) -> Result<()> { - assert!(self.top <= self.stack.len(), "stack is too small"); - log::debug!("stack size: {}", self.stack.len()); if self.stack.len() >= CALL_STACK_MAX_SIZE { return Err(Trap::CallStackOverflow.into()); } - self.top += 1; self.stack.push(call_frame); Ok(()) } @@ -79,7 +72,6 @@ impl CallFrame { /// Break to a block at the given index (relative to the current frame) /// Returns `None` if there is no block at the given index (e.g. if we need to return, this is handled by the caller) pub(crate) fn break_to(&mut self, break_to_relative: u32, value_stack: &mut super::ValueStack) -> Option<()> { - log::debug!("break_to_relative: {}", break_to_relative); let break_to = self.labels.get_relative_to_top(break_to_relative as usize)?; // instr_ptr points to the label instruction, but the next step @@ -111,14 +103,15 @@ impl CallFrame { Some(()) } + // TOOD: perf: this function is pretty hot + // Especially the two `extend` calls pub(crate) fn new_raw( func_instance_ptr: Rc, params: &[RawWasmValue], local_types: Vec, ) -> Self { - let mut locals = Vec::with_capacity(local_types.len() + params.len()); - locals.extend(params.iter().cloned()); - locals.extend(local_types.iter().map(|_| RawWasmValue::default())); + let mut locals = vec![RawWasmValue::default(); local_types.len() + params.len()]; + locals[..params.len()].copy_from_slice(params); Self { instr_ptr: 0, diff --git a/crates/tinywasm/src/runtime/stack/value_stack.rs b/crates/tinywasm/src/runtime/stack/value_stack.rs index d36373b..e1c3107 100644 --- a/crates/tinywasm/src/runtime/stack/value_stack.rs +++ b/crates/tinywasm/src/runtime/stack/value_stack.rs @@ -98,9 +98,7 @@ impl ValueStack { } pub(crate) fn break_to(&mut self, new_stack_size: usize, result_count: usize) { - let len = self.stack.len(); - self.stack.copy_within((len - result_count)..len, new_stack_size); - self.stack.truncate(new_stack_size + result_count); + self.stack.drain(new_stack_size..(self.stack.len() - result_count)); } #[inline] diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index d5de50a..ba13979 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -10,7 +10,7 @@ pub enum BlockArgs { } /// Represents a memory immediate in a WebAssembly memory instruction. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone)] pub struct MemoryArg { pub mem_addr: MemAddr, pub align: u8, @@ -23,7 +23,7 @@ type BrTableLen = usize; type EndOffset = usize; type ElseOffset = usize; -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy)] pub enum ConstInstruction { I32Const(i32), I64Const(i64), @@ -46,7 +46,7 @@ pub enum ConstInstruction { /// This makes it easier to implement the label stack (we call it BlockFrameStack) iteratively. /// /// See -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy)] pub enum Instruction { // Custom Instructions BrLabel(LabelAddr), diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 3729e1a..9af8e11 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -71,7 +71,7 @@ pub struct TinyWasmModule { /// A WebAssembly value. /// /// See -#[derive(Clone, PartialEq, Copy)] +#[derive(Clone, Copy)] pub enum WasmValue { // Num types /// A 32-bit integer. @@ -253,7 +253,7 @@ impl WasmValue { } /// Type of a WebAssembly value. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ValType { /// A 32-bit integer. I32, @@ -380,13 +380,13 @@ pub struct Global { pub init: ConstInstruction, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq)] pub struct GlobalType { pub mutable: bool, pub ty: ValType, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone)] pub struct TableType { pub element_type: ValType, pub size_initial: u32, @@ -406,7 +406,7 @@ impl TableType { #[derive(Debug, Clone)] /// Represents a memory's type. -#[derive(Copy, PartialEq, Eq, Hash)] +#[derive(Copy)] pub struct MemoryType { pub arch: MemoryArch, pub page_count_initial: u64, @@ -480,7 +480,7 @@ pub struct Element { pub ty: ValType, } -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy)] pub enum ElementKind { Passive, Active { table: TableAddr, offset: ConstInstruction }, diff --git a/examples/rust/Cargo.toml b/examples/rust/Cargo.toml index 837f7f3..8a608d0 100644 --- a/examples/rust/Cargo.toml +++ b/examples/rust/Cargo.toml @@ -29,7 +29,7 @@ name="fibonacci" path="src/fibonacci.rs" [profile.wasm] -opt-level="s" +opt-level=3 lto="thin" codegen-units=1 panic="abort" -- cgit v1.3.1