Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/execution/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/// Trait that allows user specified configuration for various items during interpretation. Additionally, the types
/// implementing this trait can act as custom user data within an interpreter instance, passed along to each method of
/// this trait and host functions whenever they are invoked.
///
/// The default implementation of all trait methods have the least overhead, i. e. most can be optimized out fully.
// It must always be checked that there is no additional performance penalty for the default config!
pub trait Config {
/// A hook which is called before every wasm instruction
///
/// This allows the most intricate insight into the interpreters behavior, at the cost of a
/// hefty performance penalty
#[allow(unused_variables)]
#[inline(always)]
fn instruction_hook(&mut self, bytecode: &[u8], pc: usize) {}
}

/// Default implementation of the interpreter configuration, with all hooks empty
impl Config for () {}
5 changes: 3 additions & 2 deletions src/execution/const_interpreter_loop.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::{
assert_validated::UnwrapValidatedExt,
config::Config,
core::{
indices::GlobalIdx,
reader::{span::Span, WasmReadable, WasmReader},
Expand All @@ -21,7 +22,7 @@ use crate::{
/// This function assumes that the expression has been validated. Passing unvalidated code will likely result in a
/// panic, or undefined behaviour.
// TODO this signature might change to support hooks or match the spec better
pub(crate) fn run_const<T>(
pub(crate) fn run_const<T: Config>(
wasm: &mut WasmReader,
stack: &mut Stack,
module: &ModuleInst,
Expand Down Expand Up @@ -118,7 +119,7 @@ pub(crate) fn run_const<T>(
Ok(())
}

pub(crate) fn run_const_span<T>(
pub(crate) fn run_const_span<T: Config>(
wasm: &[u8],
span: &Span,
module: &ModuleInst,
Expand Down
16 changes: 6 additions & 10 deletions src/execution/function_ref.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
use alloc::borrow::ToOwned;
use alloc::vec::Vec;

use crate::execution::{hooks::HookSet, interop::InteropValueList, RuntimeInstance};
use crate::execution::{config::Config, interop::InteropValueList, RuntimeInstance};
use crate::{ExternVal, RuntimeError, Store, Value};

pub struct FunctionRef {
pub func_addr: usize,
}

impl FunctionRef {
pub fn new_from_name<T>(
pub fn new_from_name<T: Config>(
module_name: &str,
function_name: &str,
store: &Store<T>,
Expand All @@ -31,22 +31,18 @@ impl FunctionRef {
}
}

pub fn invoke_typed<
H: HookSet + core::fmt::Debug,
Param: InteropValueList,
Returns: InteropValueList,
>(
pub fn invoke_typed<T: Config, Param: InteropValueList, Returns: InteropValueList>(
&self,
runtime: &mut RuntimeInstance<H>,
runtime: &mut RuntimeInstance<T>,
params: Param,
// store: &mut Store,
) -> Result<Returns, RuntimeError> {
runtime.invoke_typed(self, params /* , store */)
}

pub fn invoke<T, H: HookSet + core::fmt::Debug>(
pub fn invoke<T: Config>(
&self,
runtime: &mut RuntimeInstance<T, H>,
runtime: &mut RuntimeInstance<T>,
params: Vec<Value>,
// store: &mut Store,
) -> Result<Vec<Value>, RuntimeError> {
Expand Down
18 changes: 0 additions & 18 deletions src/execution/hooks.rs

This file was deleted.

9 changes: 5 additions & 4 deletions src/execution/interpreter_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,17 @@ use crate::{
Value,
};

use crate::execution::hooks::HookSet;
use crate::execution::config::Config;

use super::{little_endian::LittleEndianBytes, store::Store};

/// Interprets wasm native functions. Wasm parameters and Wasm return values are passed on the stack.
/// Returns `Ok(None)` in case execution successfully terminates, `Ok(Some(required_fuel))` if execution
/// terminates due to insufficient fuel, indicating how much fuel is required to resume with `required_fuel`,
/// and `[Error::RuntimeError]` otherwise.
pub(super) fn run<T, H: HookSet>(
pub(super) fn run<T: Config>(
resumable: &mut Resumable,
store: &mut Store<T>,
mut hooks: H,
) -> Result<Option<NonZeroU32>, RuntimeError> {
let stack = &mut resumable.stack;
let mut current_func_addr = resumable.current_func_addr;
Expand Down Expand Up @@ -74,7 +73,9 @@ pub(super) fn run<T, H: HookSet>(
use crate::core::reader::types::opcode::*;
loop {
// call the instruction hook
hooks.instruction_hook(store.modules[current_module_idx].wasm_bytecode, wasm.pc);
store
.user_data
.instruction_hook(store.modules[current_module_idx].wasm_bytecode, wasm.pc);

// Fuel mechanism: 1 fuel per instruction
if let Some(fuel) = &mut resumable.maybe_fuel {
Expand Down
37 changes: 12 additions & 25 deletions src/execution/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ use value_stack::Stack;

use crate::core::reader::types::{FuncType, ResultType};
use crate::execution::assert_validated::UnwrapValidatedExt;
use crate::execution::hooks::{EmptyHookSet, HookSet};
use crate::execution::config::Config;
use crate::execution::store::Store;
use crate::execution::value::Value;
use crate::interop::InteropValueList;
use crate::{RuntimeError, ValidationInfo};

pub(crate) mod assert_validated;
pub mod config;
pub mod const_interpreter_loop;
pub mod error;
pub mod function_ref;
pub mod hooks;
pub mod interop;
mod interpreter_loop;
pub(crate) mod linear_memory;
Expand All @@ -32,30 +32,28 @@ pub mod value_stack;
/// The default module name if a [RuntimeInstance] was created using [RuntimeInstance::new].
pub const DEFAULT_MODULE: &str = "__interpreter_default__";

pub struct RuntimeInstance<'b, T = (), H = EmptyHookSet>
where
H: HookSet + core::fmt::Debug,
{
pub hook_set: H,
pub struct RuntimeInstance<'b, T: Config = ()> {
pub store: Store<'b, T>,
}

impl<T: Default> Default for RuntimeInstance<'_, T, EmptyHookSet> {
impl<T: Config + Default> Default for RuntimeInstance<'_, T> {
fn default() -> Self {
Self::new(T::default())
}
}

impl<'b, T> RuntimeInstance<'b, T, EmptyHookSet> {
impl<'b, T: Config> RuntimeInstance<'b, T> {
pub fn new(user_data: T) -> Self {
Self::new_with_hooks(user_data, EmptyHookSet)
RuntimeInstance {
store: Store::new(user_data),
}
}

pub fn new_with_default_module(
user_data: T,
validation_info: &'_ ValidationInfo<'b>,
) -> Result<Self, RuntimeError> {
let mut instance = Self::new_with_hooks(user_data, EmptyHookSet);
let mut instance = Self::new(user_data);
instance.add_module(DEFAULT_MODULE, validation_info)?;
Ok(instance)
}
Expand All @@ -66,16 +64,11 @@ impl<'b, T> RuntimeInstance<'b, T, EmptyHookSet> {
validation_info: &'_ ValidationInfo<'b>,
// store: &mut Store,
) -> Result<Self, RuntimeError> {
let mut instance = Self::new_with_hooks(user_data, EmptyHookSet);
let mut instance = Self::new(user_data);
instance.add_module(module_name, validation_info)?;
Ok(instance)
}
}

impl<'b, T, H> RuntimeInstance<'b, T, H>
where
H: HookSet + core::fmt::Debug,
{
pub fn add_module(
&mut self,
module_name: &str,
Expand All @@ -84,13 +77,6 @@ where
self.store.add_module(module_name, validation_info, None)
}

pub fn new_with_hooks(user_data: T, hook_set: H) -> Self {
RuntimeInstance {
hook_set,
store: Store::new(user_data),
}
}

pub fn get_function_by_name(
&self,
module_name: &str,
Expand Down Expand Up @@ -235,7 +221,8 @@ where
}

/// Helper function to quickly construct host functions without worrying about wasm to Rust
/// type conversion. For user data, simply move the mutable reference into the passed closure.
/// type conversion. For reading/writing user data into the current configuration, simply move
/// `user_data` into the passed closure.
/// # Example
/// ```
/// use wasm::{validate, RuntimeInstance, host_function_wrapper, Value};
Expand Down
12 changes: 6 additions & 6 deletions src/execution/store.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use core::mem;

use crate::config::Config;
use crate::core::indices::TypeIdx;
use crate::core::reader::span::Span;
use crate::core::reader::types::data::{DataModeActive, DataSegment};
Expand Down Expand Up @@ -28,7 +29,6 @@ use alloc::sync::Arc;
use alloc::vec;
use alloc::vec::Vec;

use super::hooks::EmptyHookSet;
use super::interpreter_loop::{data_drop, elem_drop};
use super::UnwrapValidatedExt;

Expand All @@ -39,7 +39,7 @@ use crate::linear_memory::LinearMemory;
/// globals, element segments, and data segments that have been allocated during the life time of
/// the abstract machine.
/// <https://webassembly.github.io/spec/core/exec/runtime.html#store>
pub struct Store<'b, T> {
pub struct Store<'b, T: Config> {
pub functions: Vec<FuncInst<T>>,
pub memories: Vec<MemInst>,
pub globals: Vec<GlobalInst>,
Expand All @@ -59,7 +59,7 @@ pub struct Store<'b, T> {
pub(crate) dormitory: Dormitory,
}

impl<'b, T> Store<'b, T> {
impl<'b, T: Config> Store<'b, T> {
/// Creates a new empty store with some user data
pub fn new(user_data: T) -> Self {
Self {
Expand Down Expand Up @@ -622,7 +622,7 @@ impl<'b, T> Store<'b, T> {
};

// Run the interpreter
let result = interpreter_loop::run(&mut resumable, self, EmptyHookSet)?;
let result = interpreter_loop::run(&mut resumable, self)?;

match result {
None => {
Expand Down Expand Up @@ -666,7 +666,7 @@ impl<'b, T> Store<'b, T> {
.expect("the key to always be valid as self was not dropped yet");

// Resume execution
let result = interpreter_loop::run(resumable, self, EmptyHookSet)?;
let result = interpreter_loop::run(resumable, self)?;

match result {
None => {
Expand Down Expand Up @@ -918,7 +918,7 @@ impl ExternVal {
///
/// Note: This method may panic if self does not come from the given [`Store`].
///<https://webassembly.github.io/spec/core/valid/modules.html#imports>
pub fn extern_type<T>(&self, store: &Store<T>) -> ExternType {
pub fn extern_type<T: Config>(&self, store: &Store<T>) -> ExternType {
match self {
// TODO: fix ugly clone in function types
ExternVal::Func(func_addr) => ExternType::Func(
Expand Down
22 changes: 15 additions & 7 deletions tests/user_data.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
use std::sync::mpsc::Sender;

use wasm::{RuntimeInstance, Value};
use wasm::{config::Config, RuntimeInstance, Value};

#[test_log::test]
fn counter() {
fn add_one(user_data: &mut u32, _params: Vec<Value>) -> Vec<Value> {
*user_data += 1;
#[derive(Debug, PartialEq)]
struct MyCounter(pub u32);
impl Config for MyCounter {}

fn add_one(user_data: &mut MyCounter, _params: Vec<Value>) -> Vec<Value> {
user_data.0 += 1;

Vec::new()
}

let mut instance = RuntimeInstance::new(0);
let mut instance = RuntimeInstance::new(MyCounter(0));
instance
.add_host_function_typed::<(), ()>("host", "add_one", add_one)
.unwrap();
Expand All @@ -23,23 +27,27 @@ fn counter() {
.unwrap();
}

assert_eq!(*instance.user_data(), 5);
assert_eq!(*instance.user_data(), MyCounter(5));
}

#[test_log::test]
fn channels() {
struct MySender(pub Sender<String>);
impl Config for MySender {}

let (tx, rx) = std::sync::mpsc::channel::<String>();

std::thread::spawn(|| {
fn send_message(user_data: &mut Sender<String>, _params: Vec<Value>) -> Vec<Value> {
fn send_message(user_data: &mut MySender, _params: Vec<Value>) -> Vec<Value> {
user_data
.0
.send("Hello from host function!".to_owned())
.unwrap();

Vec::new()
}

let mut instance = RuntimeInstance::new(tx);
let mut instance = RuntimeInstance::new(MySender(tx));
instance
.add_host_function_typed::<(), ()>("host", "send_message", send_message)
.unwrap();
Expand Down