Skip to content

Commit

Permalink
v1: Optimize calls (#412)
Browse files Browse the repository at this point in the history
* swap printing fields in Debug impl of ValueStack

* move value_stack.rs into stack folder/module

* move call_stack.rs into stack folder/module

* merge ValueStack and CallStack into Stack

Stack is still very transparent towards its users but this is going to change soon.

* extract Config into its own submodule/file

* remove unnecessary getters from Config

Add a wasm_features constructor method instead to Config.

* remove redundant stack limit constants

* make Config fields private again

* panic from ValueStack::new if initial_len > maximum_len

* properly use stack limits from Config to initialize Stack

* decrease default max recursion depth from 64k to 1024

This requires benchmarks to use a custom config.

* rename FunctionFrame -> FuncFrame

* fix small doc typo

* mark error path as #[cold] in CallStack::push

* move err_stack_overflow to stack/mod.rs

* refactor ValueStack::reserve

This also fixes a rare overflow bug.

* use capacity method in debug_assert instead of duplicating code

* apply rustfmt

* improve encapsulation of Stack and change execution model around it

* remove unnecessary #[inline(never)] annotation

* remove pessimizing #[inline(always)] annotation

* remove commented out code

* rename EngineInner::execute_wasm_func2 -> execute_wasm_func

* add docs to EngineInner::execute_wasm_func

* rename FuncFrame::new2 -> new

* use pub visibility instead of pub(crate)

The type is non-pub anyways.

* remove FuncFrameRef indirection

Now the top most FuncFrame is always handled manually and passed around by value instead of indirectly via its FuncFrameRef.

* only resolve FuncBody once and create Instructions abstraction

* introduce lightweight InstructionsRef

* introduce function headers to codemap

While this introduces an indirection upon calling a function this also heavily simplifies code.

* remove unsafe code since unneeded

* removed commented out code

* handle FuncFrame by reference instead of by value

* remove unused ResolvedFuncFrame type

* add docs to FuncFrame::iref method

* make CallStack::len method private

* remove Func field from FuncFrame

This reduces call stack size during execution of many nested function calls.

* add important #[inline] annotations

This mostly pushes the branch on perf par with master.

* remove unused where bound

* cache instance related data in InstanceCache

No longer store instance related data on the CallStack.
An advantage of the InstanceCache is that it synchronizes across function calls.

* apply clippy suggestions

* fix docs
  • Loading branch information
Robbepop authored Aug 21, 2022
1 parent 71a913f commit d789570
Show file tree
Hide file tree
Showing 15 changed files with 880 additions and 775 deletions.
13 changes: 11 additions & 2 deletions wasmi_v1/benches/bench/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::{fs::File, io::Read as _};

use wasmi::{Config, StackLimits};

/// Returns the Wasm binary at the given `file_name` as `Vec<u8>`.
///
/// # Note
Expand All @@ -19,6 +21,13 @@ pub fn load_wasm_from_file(file_name: &str) -> Vec<u8> {
buffer
}

/// Returns a [`Config`] useful for benchmarking.
fn bench_config() -> Config {
let mut config = Config::default();
config.set_stack_limits(StackLimits::new(1024, 1024 * 1024, 64 * 1024).unwrap());
config
}

/// Parses the Wasm binary at the given `file_name` into a `wasmi` module.
///
/// # Note
Expand All @@ -30,7 +39,7 @@ pub fn load_wasm_from_file(file_name: &str) -> Vec<u8> {
/// If the benchmark Wasm file could not be opened, read or parsed.
pub fn load_module_from_file_v1(file_name: &str) -> wasmi::Module {
let wasm = load_wasm_from_file(file_name);
let engine = wasmi::Engine::default();
let engine = wasmi::Engine::new(&bench_config());
wasmi::Module::new(&engine, &wasm[..]).unwrap_or_else(|error| {
panic!(
"could not parse Wasm module from file {}: {}",
Expand Down Expand Up @@ -76,7 +85,7 @@ pub fn wat2wasm(bytes: &[u8]) -> Vec<u8> {
/// If the benchmark Wasm file could not be opened, read or parsed.
pub fn load_instance_from_wat_v1(wat_bytes: &[u8]) -> (wasmi::Store<()>, wasmi::Instance) {
let wasm = wat2wasm(wat_bytes);
let engine = wasmi::Engine::default();
let engine = wasmi::Engine::new(&bench_config());
let module = wasmi::Module::new(&engine, &wasm[..]).unwrap();
let mut linker = <wasmi::Linker<()>>::default();
let mut store = wasmi::Store::new(&engine, ());
Expand Down
53 changes: 4 additions & 49 deletions wasmi_v1/src/engine/bytecode/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,14 @@ use wasmi_core::UntypedValue;
/// each representing either the `BrTable` head or one of its branching targets.
#[derive(Copy, Debug, Clone, PartialEq, Eq)]
pub enum Instruction {
GetLocal {
local_depth: LocalIdx,
},
SetLocal {
local_depth: LocalIdx,
},
TeeLocal {
local_depth: LocalIdx,
},
GetLocal { local_depth: LocalIdx },
SetLocal { local_depth: LocalIdx },
TeeLocal { local_depth: LocalIdx },
Br(Target),
BrIfEqz(Target),
BrIfNez(Target),
ReturnIfNez(DropKeep),
BrTable {
len_targets: usize,
},
BrTable { len_targets: usize },
Unreachable,
Return(DropKeep),
Call(FuncIdx),
Expand Down Expand Up @@ -204,43 +196,6 @@ pub enum Instruction {
I64TruncSatF32U,
I64TruncSatF64S,
I64TruncSatF64U,

/// The start of a Wasm function body.
///
/// - This stores the `wasmi` bytecode length of the function body as well
/// as the amount of local variables.
/// - Note that the length of the `wasmi` bytecode might differ from the length
/// of the original WebAssembly bytecode.
/// - The types of the local variables do not matter since all stack values
/// are equally sized with 64-bits per value. Storing the amount of local
/// variables eliminates one indirection when calling a Wasm function.
///
/// # Note
///
/// This is a non-WebAssembly instruction that is specific to how the `wasmi`
/// interpreter organizes its internal bytecode.
FuncBodyStart {
/// This field represents the amount of instruction of the function body.
///
/// Note: This does not include any meta instructions such as
/// [`Instruction::FuncBodyStart`] or [`Instruction::FuncBodyEnd`].
len_instructions: u32,
/// Represents the number of local variables of the function body.
///
/// Note: The types of the locals do not matter since all stack values
/// use 64-bit encoding in the `wasmi` bytecode interpreter.
/// Note: Storing the amount of locals inline with the rest of the
/// function body eliminates one indirection when calling a function.
len_locals: u32,
max_stack_height: u32,
},
/// The end of a Wasm function body.
///
/// # Note
///
/// This is a non-WebAssembly instruction that is specific to how the `wasmi`
/// interpreter organizes its internal bytecode.
FuncBodyEnd,
}

impl Instruction {
Expand Down
143 changes: 143 additions & 0 deletions wasmi_v1/src/engine/cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
use crate::{
module::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX},
AsContext,
Func,
Instance,
Memory,
Table,
};

/// A cache for frequently used entities of an [`Instance`].
#[derive(Debug)]
pub struct InstanceCache {
/// The current instance in use.
instance: Instance,
/// The default linear memory of the currently used [`Instance`].
default_memory: Option<Memory>,
/// The default table of the currently used [`Instance`].
default_table: Option<Table>,
/// The last accessed function of the currently used [`Instance`].
last_func: Option<(u32, Func)>,
}

impl From<Instance> for InstanceCache {
fn from(instance: Instance) -> Self {
Self {
instance,
default_memory: None,
default_table: None,
last_func: None,
}
}
}

impl InstanceCache {
/// Resolves the instances.
fn instance(&self) -> Instance {
self.instance
}

/// Updates the cached [`Instance`].
fn set_instance(&mut self, instance: Instance) {
self.instance = instance;
}

/// Updates the currently used instance resetting all cached entities.
pub fn update_instance(&mut self, instance: Instance) {
if instance == self.instance() {
return;
}
self.set_instance(instance);
self.default_memory = None;
self.default_table = None;
self.last_func = None;
}

/// Loads the default [`Memory`] of the currently used [`Instance`].
///
/// # Panics
///
/// If the currently used [`Instance`] does not have a default linear memory.
fn load_default_memory(&mut self, ctx: impl AsContext) -> Memory {
let default_memory = self
.instance()
.get_memory(ctx.as_context(), DEFAULT_MEMORY_INDEX)
.unwrap_or_else(|| {
panic!(
"missing default linear memory for instance: {:?}",
self.instance
)
});
self.default_memory = Some(default_memory);
default_memory
}

/// Loads the default [`Table`] of the currently used [`Instance`].
///
/// # Panics
///
/// If the currently used [`Instance`] does not have a default table.
fn load_default_table(&mut self, ctx: impl AsContext) -> Table {
let default_table = self
.instance()
.get_table(ctx.as_context(), DEFAULT_TABLE_INDEX)
.unwrap_or_else(|| panic!("missing default table for instance: {:?}", self.instance));
self.default_table = Some(default_table);
default_table
}

/// Returns the default [`Memory`] of the currently used [`Instance`].
///
/// # Panics
///
/// If the currently used [`Instance`] does not have a default linear memory.
pub fn default_memory(&mut self, ctx: impl AsContext, _instance: Instance) -> Memory {
match self.default_memory {
Some(default_memory) => default_memory,
None => self.load_default_memory(ctx),
}
}

/// Returns the default [`Table`] of the currently used [`Instance`].
///
/// # Panics
///
/// If the currently used [`Instance`] does not have a default table.
pub fn default_table(&mut self, ctx: impl AsContext, _instance: Instance) -> Table {
match self.default_table {
Some(default_table) => default_table,
None => self.load_default_table(ctx),
}
}

/// Loads the [`Func`] at `index` of the currently used [`Instance`].
///
/// # Panics
///
/// If the currently used [`Instance`] does not have a default table.
fn load_func_at(&mut self, ctx: impl AsContext, index: u32) -> Func {
let func = self
.instance()
.get_func(ctx.as_context(), index)
.unwrap_or_else(|| {
panic!(
"missing func at index {index} for instance: {:?}",
self.instance
)
});
self.last_func = Some((index, func));
func
}

/// Loads the [`Func`] at `index` of the currently used [`Instance`].
///
/// # Panics
///
/// If the currently used [`Instance`] does not have a [`Func`] at the index.
pub fn get_func(&mut self, ctx: impl AsContext, _instance: Instance, func_idx: u32) -> Func {
match self.last_func {
Some((index, func)) if index == func_idx => func,
_ => self.load_func_at(ctx, func_idx),
}
}
}
Loading

0 comments on commit d789570

Please sign in to comment.