From f6dba823b785cb05a024d52f787b81bd56d6b3e4 Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Thu, 1 Feb 2024 01:36:12 +0100 Subject: perf: small performance improvements Signed-off-by: Henry Gressmann --- crates/tinywasm/src/instance.rs | 20 +++- crates/tinywasm/src/runtime/interpreter/macros.rs | 3 +- crates/tinywasm/src/runtime/interpreter/mod.rs | 22 ++-- crates/tinywasm/src/store/memory.rs | 3 + crates/tinywasm/src/store/mod.rs | 120 ++++++++++++---------- 5 files changed, 100 insertions(+), 68 deletions(-) (limited to 'crates') diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs index 75bd20d..6bfe99c 100644 --- a/crates/tinywasm/src/instance.rs +++ b/crates/tinywasm/src/instance.rs @@ -36,15 +36,18 @@ pub(crate) struct ModuleInstanceInner { impl ModuleInstance { // drop the module instance reference and swap it with another one + #[inline] pub(crate) fn swap(&mut self, other: Self) { self.0 = other.0; } + #[inline] pub(crate) fn swap_with(&mut self, other_addr: ModuleInstanceAddr, store: &mut Store) { self.swap(store.get_module_instance_raw(other_addr)) } /// Get the module instance's address + #[inline] pub fn id(&self) -> ModuleInstanceAddr { self.0.idx } @@ -118,44 +121,53 @@ impl ModuleInstance { Some(ExternVal::new(kind, *addr)) } - pub(crate) fn func_addrs(&self) -> &[FuncAddr] { - &self.0.func_addrs - } - + #[inline] pub(crate) fn new(inner: ModuleInstanceInner) -> Self { Self(Rc::new(inner)) } + #[inline] pub(crate) fn func_ty(&self, addr: FuncAddr) -> &FuncType { self.0.types.get(addr as usize).expect("No func type for func, this is a bug") } + #[inline] + pub(crate) fn func_addrs(&self) -> &[FuncAddr] { + &self.0.func_addrs + } + // resolve a function address to the global store address + #[inline] pub(crate) fn resolve_func_addr(&self, addr: FuncAddr) -> FuncAddr { *self.0.func_addrs.get(addr as usize).expect("No func addr for func, this is a bug") } // resolve a table address to the global store address + #[inline] pub(crate) fn resolve_table_addr(&self, addr: TableAddr) -> TableAddr { *self.0.table_addrs.get(addr as usize).expect("No table addr for table, this is a bug") } // resolve a memory address to the global store address + #[inline] pub(crate) fn resolve_mem_addr(&self, addr: MemAddr) -> MemAddr { *self.0.mem_addrs.get(addr as usize).expect("No mem addr for mem, this is a bug") } // resolve a data address to the global store address + #[inline] pub(crate) fn resolve_data_addr(&self, addr: DataAddr) -> MemAddr { *self.0.data_addrs.get(addr as usize).expect("No data addr for data, this is a bug") } // resolve a memory address to the global store address + #[inline] pub(crate) fn resolve_elem_addr(&self, addr: ElemAddr) -> ElemAddr { *self.0.elem_addrs.get(addr as usize).expect("No elem addr for elem, this is a bug") } // resolve a global address to the global store address + #[inline] pub(crate) fn resolve_global_addr(&self, addr: GlobalAddr) -> GlobalAddr { self.0.global_addrs[addr as usize] } diff --git a/crates/tinywasm/src/runtime/interpreter/macros.rs b/crates/tinywasm/src/runtime/interpreter/macros.rs index 9333a4c..d6c0553 100644 --- a/crates/tinywasm/src/runtime/interpreter/macros.rs +++ b/crates/tinywasm/src/runtime/interpreter/macros.rs @@ -28,13 +28,13 @@ macro_rules! mem_load { }}; ($load_type:ty, $target_type:ty, $arg:ident, $stack:ident, $store:ident, $module:ident) => {{ - // TODO: there could be a lot of performance improvements here let mem_idx = $module.resolve_mem_addr($arg.mem_addr); let mem = $store.get_mem(mem_idx as usize)?; let mem_ref = mem.borrow_mut(); let addr = $stack.values.pop()?.raw_value(); let addr = $arg.offset.checked_add(addr).ok_or_else(|| { + cold(); Error::Trap(crate::Trap::MemoryOutOfBounds { offset: $arg.offset as usize, len: core::mem::size_of::<$load_type>(), @@ -43,6 +43,7 @@ macro_rules! mem_load { })?; let addr: usize = addr.try_into().ok().ok_or_else(|| { + cold(); Error::Trap(crate::Trap::MemoryOutOfBounds { offset: $arg.offset as usize, len: core::mem::size_of::<$load_type>(), diff --git a/crates/tinywasm/src/runtime/interpreter/mod.rs b/crates/tinywasm/src/runtime/interpreter/mod.rs index c8d8791..7e34ac1 100644 --- a/crates/tinywasm/src/runtime/interpreter/mod.rs +++ b/crates/tinywasm/src/runtime/interpreter/mod.rs @@ -29,28 +29,31 @@ impl InterpreterRuntime { let mut current_module = store.get_module_instance_raw(cf.func_instance.1); loop { - match exec_one(&mut cf, stack, store, ¤t_module)? { + match exec_one(&mut cf, stack, store, ¤t_module) { // Continue execution at the new top of the call stack - ExecResult::Call => { + Ok(ExecResult::Call) => { cf = stack.call_stack.pop()?; + + // keeping the pointer seperate from the call frame is about 2% faster + // than storing it in the call frame if cf.func_instance.1 != current_module.id() { current_module.swap_with(cf.func_instance.1, store); } } // return from the function - ExecResult::Return => return Ok(()), + Ok(ExecResult::Return) => return Ok(()), // continue to the next instruction and increment the instruction pointer - ExecResult::Ok => cf.instr_ptr += 1, + Ok(ExecResult::Ok) => cf.instr_ptr += 1, // trap the program - ExecResult::Trap(trap) => { + Err(error) => { cf.instr_ptr += 1; // push the call frame back onto the stack so that it can be resumed // if the trap can be handled stack.call_stack.push(cf)?; - return Err(Error::Trap(trap)); + return Err(error); } } } @@ -61,13 +64,14 @@ enum ExecResult { Ok, Return, Call, - Trap(crate::Trap), } /// 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?) -#[inline(always)] // this improves performance by more than 20% in some cases +// we want this be always part of the loop, rust just doesn't inline it as its too big +// this can be a 30%+ performance difference in some cases +#[inline(always)] fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &ModuleInstance) -> Result { let instrs = &cf.func_instance.0.instructions; if unlikely(cf.instr_ptr >= instrs.len() || instrs.is_empty()) { @@ -84,7 +88,7 @@ fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &M Nop => { /* do nothing */ } Unreachable => { cold(); - return Ok(ExecResult::Trap(crate::Trap::Unreachable)); + return Err(crate::Trap::Unreachable.into()); } // we don't need to include the call frame here because it's already on the stack Drop => stack.values.pop().map(|_| ())?, diff --git a/crates/tinywasm/src/store/memory.rs b/crates/tinywasm/src/store/memory.rs index 64e16c6..588d995 100644 --- a/crates/tinywasm/src/store/memory.rs +++ b/crates/tinywasm/src/store/memory.rs @@ -100,6 +100,7 @@ impl MemoryInstance { Ok(val) } + #[inline] pub(crate) fn page_count(&self) -> usize { self.page_count } @@ -186,10 +187,12 @@ macro_rules! impl_mem_loadable_for_primitive { $( #[allow(unsafe_code)] unsafe impl MemLoadable<$size> for $type { + #[inline] fn from_le_bytes(bytes: [u8; $size]) -> Self { <$type>::from_le_bytes(bytes) } + #[inline] fn from_be_bytes(bytes: [u8; $size]) -> Self { <$type>::from_be_bytes(bytes) } diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs index ad24480..ce02dd7 100644 --- a/crates/tinywasm/src/store/mod.rs +++ b/crates/tinywasm/src/store/mod.rs @@ -116,6 +116,72 @@ impl Store { Ok(()) } + #[cold] + fn not_found_error(name: &str) -> Error { + Error::Other(format!("{} not found", name)) + } + + /// Get the function at the actual index in the store + #[inline] + pub(crate) fn get_func(&self, addr: usize) -> Result<&FunctionInstance> { + self.data.funcs.get(addr).ok_or_else(|| Self::not_found_error("function")) + } + + /// Get the memory at the actual index in the store + #[inline] + pub(crate) fn get_mem(&self, addr: usize) -> Result<&Rc>> { + self.data.memories.get(addr).ok_or_else(|| Self::not_found_error("memory")) + } + + /// Get the table at the actual index in the store + #[inline] + pub(crate) fn get_table(&self, addr: usize) -> Result<&Rc>> { + self.data.tables.get(addr).ok_or_else(|| Self::not_found_error("table")) + } + + /// Get the data at the actual index in the store + #[inline] + pub(crate) fn get_data(&self, addr: usize) -> Result<&DataInstance> { + self.data.datas.get(addr).ok_or_else(|| Self::not_found_error("data")) + } + + /// Get the data at the actual index in the store + #[inline] + pub(crate) fn get_data_mut(&mut self, addr: usize) -> Result<&mut DataInstance> { + self.data.datas.get_mut(addr).ok_or_else(|| Self::not_found_error("data")) + } + + /// Get the element at the actual index in the store + #[inline] + pub(crate) fn get_elem(&self, addr: usize) -> Result<&ElementInstance> { + self.data.elements.get(addr).ok_or_else(|| Self::not_found_error("element")) + } + + /// Get the global at the actual index in the store + #[inline] + pub(crate) fn get_global(&self, addr: usize) -> Result<&Rc>> { + self.data.globals.get(addr).ok_or_else(|| Self::not_found_error("global")) + } + + /// Get the global at the actual index in the store + #[inline] + pub fn get_global_val(&self, addr: usize) -> Result { + self.data.globals.get(addr).ok_or_else(|| Self::not_found_error("global")).map(|global| global.borrow().value) + } + + /// Set the global at the actual index in the store + #[inline] + pub(crate) fn set_global_val(&mut self, addr: usize, value: RawWasmValue) -> Result<()> { + self.data + .globals + .get(addr) + .ok_or_else(|| Self::not_found_error("global")) + .map(|global| global.borrow_mut().value = value) + } +} + +// Linking related functions +impl Store { /// Add functions to the store, returning their addresses in the store pub(crate) fn init_funcs( &mut self, @@ -391,58 +457,4 @@ impl Store { }; Ok(val) } - - #[cold] - fn not_found_error(name: &str) -> Error { - Error::Other(format!("{} not found", name)) - } - - /// Get the function at the actual index in the store - pub(crate) fn get_func(&self, addr: usize) -> Result<&FunctionInstance> { - self.data.funcs.get(addr).ok_or_else(|| Self::not_found_error("function")) - } - - /// Get the memory at the actual index in the store - pub(crate) fn get_mem(&self, addr: usize) -> Result<&Rc>> { - self.data.memories.get(addr).ok_or_else(|| Self::not_found_error("memory")) - } - - /// Get the table at the actual index in the store - pub(crate) fn get_table(&self, addr: usize) -> Result<&Rc>> { - self.data.tables.get(addr).ok_or_else(|| Self::not_found_error("table")) - } - - /// Get the data at the actual index in the store - pub(crate) fn get_data(&self, addr: usize) -> Result<&DataInstance> { - self.data.datas.get(addr).ok_or_else(|| Self::not_found_error("data")) - } - - /// Get the data at the actual index in the store - pub(crate) fn get_data_mut(&mut self, addr: usize) -> Result<&mut DataInstance> { - self.data.datas.get_mut(addr).ok_or_else(|| Self::not_found_error("data")) - } - - /// Get the element at the actual index in the store - pub(crate) fn get_elem(&self, addr: usize) -> Result<&ElementInstance> { - self.data.elements.get(addr).ok_or_else(|| Self::not_found_error("element")) - } - - /// Get the global at the actual index in the store - pub(crate) fn get_global(&self, addr: usize) -> Result<&Rc>> { - self.data.globals.get(addr).ok_or_else(|| Self::not_found_error("global")) - } - - /// Get the global at the actual index in the store - pub fn get_global_val(&self, addr: usize) -> Result { - self.data.globals.get(addr).ok_or_else(|| Self::not_found_error("global")).map(|global| global.borrow().value) - } - - /// Set the global at the actual index in the store - pub(crate) fn set_global_val(&mut self, addr: usize, value: RawWasmValue) -> Result<()> { - self.data - .globals - .get(addr) - .ok_or_else(|| Self::not_found_error("global")) - .map(|global| global.borrow_mut().value = value) - } } -- cgit v1.3.1