From e96a4abe2b08bb39ca7a84bf47734ef6653b229c Mon Sep 17 00:00:00 2001 From: George Cosma Date: Mon, 23 Sep 2024 16:52:59 +0300 Subject: [PATCH] chore: re-implement invoke_dynamic for new API Signed-off-by: George Cosma --- src/execution/function_ref.rs | 18 +++++- src/execution/mod.rs | 115 +++++++++++++++++++++++++++++++--- 2 files changed, 122 insertions(+), 11 deletions(-) diff --git a/src/execution/function_ref.rs b/src/execution/function_ref.rs index 40d91287..c47a7457 100644 --- a/src/execution/function_ref.rs +++ b/src/execution/function_ref.rs @@ -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 { @@ -18,4 +25,13 @@ impl FunctionRef { ) -> Result { runtime.invoke(self, params) } + + pub fn invoke_dynamic( + &self, + runtime: &mut RuntimeInstance, + params: Vec, + ret_types: &[ValType], + ) -> Result, RuntimeError> { + runtime.invoke_dynamic(self, params, ret_types) + } } diff --git a/src/execution/mod.rs b/src/execution/mod.rs index 34dbbd61..6d25a6d1 100644 --- a/src/execution/mod.rs +++ b/src/execution/mod.rs @@ -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; @@ -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 { @@ -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?; } @@ -98,6 +106,7 @@ where function_name: function_name.to_string(), module_index: module_idx, function_index: func_idx, + exported: true, }) } @@ -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, @@ -139,13 +152,7 @@ where params: Param, ) -> Result { // 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"); @@ -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, + ret_types: &[ValType], + ) -> Result, 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::>(); + + 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::>(); + + // 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, @@ -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 = { let mut wasm_reader = WasmReader::new(validation_info.wasm);