Skip to content

Commit

Permalink
chore: re-implement invoke_dynamic for new API
Browse files Browse the repository at this point in the history
Signed-off-by: George Cosma <[email protected]>
  • Loading branch information
george-cosma committed Sep 24, 2024
1 parent a973d0f commit e96a4ab
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 11 deletions.
18 changes: 17 additions & 1 deletion src/execution/function_ref.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
use alloc::string::String;
use alloc::vec::Vec;

use crate::execution::{hooks::HookSet, value::InteropValueList, RuntimeInstance};
use crate::RuntimeError;
use crate::{RuntimeError, ValType, Value};

pub struct FunctionRef {
pub(crate) module_name: String,
pub(crate) function_name: String,
pub(crate) module_index: usize,
pub(crate) function_index: usize,
/// If the function is exported from the module or not. This is used to determine if the function name - index
/// mapping should be verified. The module name - index mapping is always verified.
///
/// If this is set to false then the user must make sure that the function reference will still be valid when the
/// function is called. This means that the module must not be unloaded.
pub(crate) exported: bool,
}

impl FunctionRef {
Expand All @@ -18,4 +25,13 @@ impl FunctionRef {
) -> Result<Returns, RuntimeError> {
runtime.invoke(self, params)
}

pub fn invoke_dynamic<H: HookSet>(
&self,
runtime: &mut RuntimeInstance<H>,
params: Vec<Value>,
ret_types: &[ValType],
) -> Result<Vec<Value>, RuntimeError> {
runtime.invoke_dynamic(self, params, ret_types)
}
}
115 changes: 105 additions & 10 deletions src/execution/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::execution::store::{FuncInst, GlobalInst, MemInst, Store};
use crate::execution::value::Value;
use crate::validation::code::read_declared_locals;
use crate::value::InteropValueList;
use crate::{RuntimeError, ValidationInfo};
use crate::{RuntimeError, ValType, ValidationInfo};

// TODO
pub(crate) mod assert_validated;
Expand Down Expand Up @@ -61,7 +61,7 @@ where
H: HookSet,
{
pub fn new_with_hooks(
_module_name: &str,
module_name: &str,
validation_info: &'_ ValidationInfo<'b>,
hook_set: H,
) -> Result<Self, RuntimeError> {
Expand All @@ -78,7 +78,15 @@ where
};

if let Some(start) = validation_info.start {
let start_fn = instance.get_fn_idx(0, start)?;
// "start" is not always exported, so we need create a non-API exposed function reference.
// Note: function name is not important here, as it is not used in the verification process.
let start_fn = FunctionRef {
module_name: module_name.to_string(),
function_name: "start".to_string(),
module_index: 0,
function_index: start,
exported: false,
};
let result = instance.invoke::<(), ()>(&start_fn, ());
result?;
}
Expand All @@ -98,6 +106,7 @@ where
function_name: function_name.to_string(),
module_index: module_idx,
function_index: func_idx,
exported: true,
})
}

Expand All @@ -118,13 +127,17 @@ where
.ok_or(RuntimeError::FunctionNotFound)?;

Ok(FunctionRef {
// TODO: get the module name from the module index
module_name: DEFAULT_MODULE.to_string(),
function_name,
module_index: module_idx,
function_index: function_idx,
exported: true,
})
}

// TODO: remove this annotation when implementing the function
#[allow(clippy::result_unit_err)]
pub fn add_module(
&mut self,
_module_name: &str,
Expand All @@ -139,13 +152,7 @@ where
params: Param,
) -> Result<Returns, RuntimeError> {
// First, verify that the function reference is valid
let (module_idx, func_idx) =
self.get_indicies(&function_ref.module_name, &function_ref.function_name)?;

if module_idx != function_ref.module_index || func_idx != function_ref.function_index {
// TODO: should we return a different error here?
return Err(RuntimeError::FunctionNotFound);
}
let (_module_idx, func_idx) = self.verify_function_ref(function_ref)?;

// -=-= Verification =-=-
let func_inst = self.store.funcs.get(func_idx).expect("valid FuncIdx");
Expand Down Expand Up @@ -192,6 +199,64 @@ where
Ok(ret)
}

/// Invokes a function with the given parameters, and return types which are not known at compile time.
pub fn invoke_dynamic(
&mut self,
function_ref: &FunctionRef,
params: Vec<Value>,
ret_types: &[ValType],
) -> Result<Vec<Value>, RuntimeError> {
// First, verify that the function reference is valid
let (_module_idx, func_idx) = self.verify_function_ref(function_ref)?;

// -=-= Verification =-=-
let func_inst = self.store.funcs.get(func_idx).expect("valid FuncIdx");
let func_ty = self.types.get(func_inst.ty).unwrap_validated();

// Verify that the given parameters match the function parameters
let param_types = params.iter().map(|v| v.to_ty()).collect::<Vec<_>>();

if func_ty.params.valtypes != param_types {
panic!("Invalid parameters for function");
}

// Verify that the given return types match the function return types
if func_ty.returns.valtypes != ret_types {
panic!("Invalid return types for function");
}

// Prepare a new stack with the locals for the entry function
let mut stack = Stack::new();
let locals = Locals::new(params.into_iter(), func_inst.locals.iter().cloned());
stack.push_stackframe(func_idx, func_ty, locals, 0);

// Run the interpreter
run(
self.wasm_bytecode,
&self.types,
&mut self.store,
&mut stack,
EmptyHookSet,
)?;

let func_inst = self.store.funcs.get(func_idx).expect("valid FuncIdx");
let func_ty = self.types.get(func_inst.ty).unwrap_validated();

// Pop return values from stack
let return_values = func_ty
.returns
.valtypes
.iter()
.map(|ty| stack.pop_value(*ty))
.collect::<Vec<Value>>();

// Values are reversed because they were popped from stack one-by-one. Now reverse them back
let reversed_values = return_values.into_iter().rev();
let ret = reversed_values.collect();
debug!("Successfully invoked function");
Ok(ret)
}

// TODO: replace this with the lookup table when implmenting the linker
fn get_indicies(
&self,
Expand All @@ -216,6 +281,36 @@ where
Ok((0, func_idx))
}

fn verify_function_ref(
&self,
function_ref: &FunctionRef,
) -> Result<(usize, usize), RuntimeError> {
if function_ref.exported {
let (module_idx, func_idx) =
self.get_indicies(&function_ref.module_name, &function_ref.function_name)?;

if module_idx != function_ref.module_index || func_idx != function_ref.function_index {
// TODO: should we return a different error here?
return Err(RuntimeError::FunctionNotFound);
}

Ok((module_idx, func_idx))
} else {
let (module_idx, func_idx) = (function_ref.module_index, function_ref.function_index);

// TODO: verify module named - index mapping.

// Sanity check that the function index is at least in the bounds of the store, though this doesn't mean
// that it's a valid function.
self.store
.funcs
.get(func_idx)
.ok_or(RuntimeError::FunctionNotFound)?;

Ok((module_idx, func_idx))
}
}

fn init_store(validation_info: &ValidationInfo) -> Store {
let function_instances: Vec<FuncInst> = {
let mut wasm_reader = WasmReader::new(validation_info.wasm);
Expand Down

0 comments on commit e96a4ab

Please sign in to comment.