From 8b71675ae7a7704605210607a9e008c6dc29e691 Mon Sep 17 00:00:00 2001 From: George Cosma Date: Mon, 17 Feb 2025 17:05:26 +0200 Subject: [PATCH 1/3] Global Store --- src/execution/execution_info.rs | 37 ++-- src/execution/mod.rs | 2 +- src/execution/store.rs | 345 +++++++++++++++++++++++++++++++- 3 files changed, 364 insertions(+), 20 deletions(-) diff --git a/src/execution/execution_info.rs b/src/execution/execution_info.rs index 8f7afe5ab..37c17b27c 100644 --- a/src/execution/execution_info.rs +++ b/src/execution/execution_info.rs @@ -12,18 +12,29 @@ pub struct ExecutionInfo<'r> { pub name: String, pub wasm_bytecode: &'r [u8], pub wasm_reader: WasmReader<'r>, - pub fn_types: Vec, - pub store: Store, -} -impl<'r> ExecutionInfo<'r> { - pub fn new(name: &str, wasm_bytecode: &'r [u8], fn_types: Vec, store: Store) -> Self { - ExecutionInfo { - name: name.to_string(), - wasm_bytecode, - wasm_reader: WasmReader::new(wasm_bytecode), - fn_types, - store, - } - } + pub functions: Vec, + pub functions_offset: usize, + pub imported_functions_len: usize, + + pub memories: Vec, + pub memories_offset: usize, + pub imported_memories_len: usize, + + pub globals: Vec, + pub globals_offset: usize, + pub imported_globals_len: usize, + + pub tables: Vec, + pub tables_offset: usize, + pub imported_tables_len: usize, + + pub data: Vec, + pub data_offset: usize, + + pub elements: Vec, + pub elements_offset: usize, + + pub passive_element_indexes: Vec, + // pub exports: Vec, } diff --git a/src/execution/mod.rs b/src/execution/mod.rs index e9dc3c7ef..208cf6113 100644 --- a/src/execution/mod.rs +++ b/src/execution/mod.rs @@ -735,7 +735,7 @@ where let exports = validation_info.exports.clone(); Ok(Store { funcs: function_instances, - mems: memory_instances, + memories: memory_instances, globals: global_instances, data: data_sections, tables, diff --git a/src/execution/store.rs b/src/execution/store.rs index 0f278bfad..a61cebd63 100644 --- a/src/execution/store.rs +++ b/src/execution/store.rs @@ -3,14 +3,22 @@ use alloc::vec; use alloc::vec::Vec; use core::iter; +use crate::core::error::{Proposal, Result, StoreInstantiationError}; use crate::core::indices::TypeIdx; use crate::core::reader::span::Span; -use crate::core::reader::types::export::Export; +use crate::core::reader::types::element::{ElemItems, ElemMode}; use crate::core::reader::types::global::Global; +use crate::core::reader::types::import::ImportDesc; use crate::core::reader::types::{MemType, TableType, ValType}; +use crate::core::reader::WasmReader; use crate::core::sidetable::Sidetable; use crate::execution::value::{Ref, Value}; -use crate::RefType; +use crate::execution::{get_address_offset, run_const, run_const_span, Stack}; +use crate::value::{ExternAddr, FuncAddr}; +use crate::{Error, RefType, ValidationInfo}; + +use super::execution_info::ExecutionInfo; +use super::UnwrapValidatedExt; /// The store represents all global state that can be manipulated by WebAssembly programs. It /// consists of the runtime representation of all instances of functions, tables, memories, and @@ -18,14 +26,339 @@ use crate::RefType; /// the abstract machine. /// pub struct Store { - pub funcs: Vec, - pub mems: Vec, + pub functions: Vec, + pub memories: Vec, pub globals: Vec, pub data: Vec, pub tables: Vec, pub elements: Vec, - pub passive_elem_indexes: Vec, - pub exports: Vec, + // pub exports: Vec, +} + +impl<'b> Store { + pub fn add_module( + &mut self, + name: String, + module: ValidationInfo<'b>, + ) -> Result> { + // TODO: we can do validation at linktime such that if another module expects module `name` to export something, + // and it doesn't, we can reject it here instead of accepting it and failing later. + + let function_inst = module.instantiate_functions()?; + let mut table_inst = module.instantiate_tables()?; + let (element_inst, passive_idxs) = module.instantiate_elements(&mut table_inst)?; + let mut memories = module.instantiate_memories()?; + let data = module.instantiate_data(&mut memories)?; + let globals = module.instantiate_globals()?; + + let imported_functions = function_inst + .iter() + .filter(|func| matches!(func, FuncInst::Imported(_))) + .count(); + let imported_memories = 0; // TODO: not yet supported + let imported_globals = 0; // TODO: not yet supported + let imported_tables = 0; // TODO: not yet supported + + let functions_offset = self.functions.len(); + let exec_functions = (functions_offset..(functions_offset + function_inst.len())).collect(); + self.functions.extend(function_inst); + + let memories_offset = self.memories.len(); + let exec_memories = (memories_offset..(memories_offset + memories.len())).collect(); + self.memories.extend(memories); + + let globals_offset = self.globals.len(); + let exec_globals = (globals_offset..(globals_offset + globals.len())).collect(); + self.globals.extend(globals); + + let data_offset = self.data.len(); + let exec_data = (data_offset..(data_offset + data.len())).collect(); + self.data.extend(data); + + let tables_offset = self.tables.len(); + let exec_tables = (tables_offset..(tables_offset + table_inst.len())).collect(); + self.tables.extend(table_inst); + + let elements_offset = self.elements.len(); + let exec_elements = (elements_offset..(elements_offset + element_inst.len())).collect(); + self.elements.extend(element_inst); + + let execution_info = ExecutionInfo { + name, + wasm_bytecode: module.wasm, + wasm_reader: WasmReader::new(module.wasm), + + functions: exec_functions, + functions_offset, + imported_functions_len: imported_functions, + + memories: exec_memories, + memories_offset, + imported_memories_len: imported_memories, + + globals: exec_globals, + globals_offset, + imported_globals_len: imported_globals, + + tables: exec_tables, + tables_offset, + imported_tables_len: imported_tables, + + data: exec_data, + data_offset, + + elements: exec_elements, + elements_offset, + + passive_element_indexes: passive_idxs, + }; + + Ok(execution_info) + } +} + +impl<'b> ValidationInfo<'b> { + pub fn instantiate_functions(&self) -> Result> { + let mut wasm_reader = WasmReader::new(self.wasm); + + let functions = self.functions.iter(); + let func_blocks = self.func_blocks.iter(); + + let local_function_inst = functions.zip(func_blocks).map(|(ty, (func, sidetable))| { + wasm_reader + .move_start_to(*func) + .expect("function index to be in the bounds of the WASM binary"); + + let (locals, bytes_read) = wasm_reader + .measure_num_read_bytes(crate::code::read_declared_locals) + .unwrap_validated(); + + let code_expr = wasm_reader + .make_span(func.len() - bytes_read) + .expect("TODO remove this expect"); + + FuncInst::Local(LocalFuncInst { + ty: *ty, + locals, + code_expr, + // TODO figure out where we want our sidetables + sidetable: sidetable.clone(), + }) + }); + + let imported_function_inst = self.imports.iter().filter_map(|import| match &import.desc { + ImportDesc::Func(type_idx) => Some(FuncInst::Imported(ImportedFuncInst { + ty: *type_idx, + module_name: import.module_name.clone(), + function_name: import.name.clone(), + })), + _ => None, + }); + + Ok(imported_function_inst.chain(local_function_inst).collect()) + } + + pub fn instantiate_tables(&self) -> Result> { + Ok(self.tables.iter().map(|ty| TableInst::new(*ty)).collect()) + } + + pub fn instantiate_elements( + &self, + tables: &mut [TableInst], + ) -> Result<(Vec, Vec)> { + let mut passive_elem_indexes: Vec = vec![]; + // https://webassembly.github.io/spec/core/syntax/modules.html#element-segments + let elements: Vec = self + .elements + .iter() + .enumerate() + .filter_map(|(i, elem)| { + trace!("Instantiating element {:#?}", elem); + + let offsets = match &elem.init { + ElemItems::Exprs(_ref_type, init_exprs) => init_exprs + .iter() + .map(|expr| { + get_address_offset( + run_const_span(self.wasm, expr, ()).unwrap_validated(), + ) + }) + .collect::>>(), + ElemItems::RefFuncs(indicies) => { + // This branch gets taken when the elements are direct function references (i32 values), so we just return the indices + indicies + .iter() + .map(|el| Some(*el)) + .collect::>>() + } + }; + + let references: Vec = offsets + .iter() + .map(|offset| { + let offset = offset.as_ref().map(|offset| *offset as usize); + match elem.ty() { + RefType::FuncRef => Ref::Func(FuncAddr::new(offset)), + RefType::ExternRef => Ref::Extern(ExternAddr::new(offset)), + } + }) + .collect(); + + let instance = ElemInst { + ty: elem.ty(), + references, + }; + + match &elem.mode { + // As per https://webassembly.github.io/spec/core/syntax/modules.html#element-segments + // A declarative element segment is not available at runtime but merely serves to forward-declare + // references that are formed in code with instructions like `ref.func` + + // Also, the answer given by Andreas Rossberg (the editor of the WASM Spec - Release 2.0) + // Per https://stackoverflow.com/questions/78672934/what-is-the-purpose-of-a-wasm-declarative-element-segment + // "[...] The reason Wasm requires this (admittedly ugly) forward declaration is to support streaming compilation [...]" + ElemMode::Declarative => None, + ElemMode::Passive => { + passive_elem_indexes.push(i); + Some(instance) + } + ElemMode::Active(active_elem) => { + let table_idx = active_elem.table_idx as usize; + + let offset = match run_const_span(self.wasm, &active_elem.init_expr, ()) + .unwrap_validated() + { + Value::I32(offset) => offset as usize, + // We are already asserting that on top of the stack there is an I32 at validation time + _ => unreachable!(), + }; + + let table = &mut tables[table_idx]; + // This can't be verified at validation-time because we don't keep track of actual values when validating expressions + // we only keep track of the type of the values. As such we can't pop the exact value of an i32 from the validation stack + assert!(table.len() >= (offset + instance.len())); + + table.elem[offset..offset + instance.references.len()] + .copy_from_slice(&instance.references); + + Some(instance) + } + } + }) + .collect(); + + Ok((elements, passive_elem_indexes)) + } + + pub fn instantiate_memories(&self) -> Result> { + let memories: Vec = self.memories.iter().map(|ty| MemInst::new(*ty)).collect(); + + let import_memory_instances_len = self + .imports + .iter() + .filter(|import| matches!(import.desc, ImportDesc::Mem(_))) + .count(); + + match memories.len().checked_add(import_memory_instances_len) { + None => { + return Err(Error::StoreInstantiationError( + StoreInstantiationError::TooManyMemories(usize::MAX), + )) + } + Some(mem_instances) => { + if mem_instances > 1 { + return Err(Error::UnsupportedProposal(Proposal::MultipleMemories)); + } + } + }; + + Ok(memories) + } + + pub fn instantiate_data(&self, memory_instances: &mut [MemInst]) -> Result> { + self.data + .iter() + .map(|d| { + use crate::core::reader::types::data::DataMode; + use crate::NumType; + if let DataMode::Active(active_data) = d.mode.clone() { + let mem_idx = active_data.memory_idx; + if mem_idx != 0 { + todo!("Active data has memory_idx different than 0"); + } + assert!( + memory_instances.len() > mem_idx, + "Multiple memories not yet supported" + ); + + let boxed_value = { + let mut wasm = WasmReader::new(self.wasm); + wasm.move_start_to(active_data.offset).unwrap_validated(); + let mut stack = Stack::new(); + run_const(wasm, &mut stack, ()); + stack.pop_value(ValType::NumType(NumType::I32)) + // stack.peek_unknown_value().ok_or(MissingValueOnTheStack)? + }; + + // TODO: this shouldn't be a simple value, should it? I mean it can't be, but it can also be any type of ValType + // TODO: also, do we need to forcefully make it i32? + let offset: u32 = match boxed_value { + Value::I32(val) => val, + // Value::I64(val) => { + // if val > u32::MAX as u64 { + // return Err(I64ValueOutOfReach("data segment".to_owned())); + // } + // val as u32 + // } + // TODO: implement all value types + _ => todo!(), + }; + + let mem_inst = memory_instances.get_mut(mem_idx).unwrap(); + + let len = mem_inst.data.len(); + if offset as usize + d.init.len() > len { + return Err(Error::StoreInstantiationError( + StoreInstantiationError::ActiveDataWriteOutOfBounds, + )); + } + let data = mem_inst + .data + .get_mut(offset as usize..offset as usize + d.init.len()) + .unwrap(); + data.copy_from_slice(&d.init); + } + Ok(DataInst { + data: d.init.clone(), + }) + }) + .collect::>>() + } + + pub fn instantiate_globals(&self) -> Result> { + Ok(self + .globals + .iter() + .map({ + let mut stack = Stack::new(); + move |global| { + let mut wasm = WasmReader::new(self.wasm); + // The place we are moving the start to should, by all means, be inside the wasm bytecode. + wasm.move_start_to(global.init_expr).unwrap_validated(); + // We shouldn't need to clear the stack. If validation is correct, it will remain empty after execution. + + // TODO: imported globals + run_const(wasm, &mut stack, ()); + let value = stack.pop_value(global.ty.ty); + + GlobalInst { + global: *global, + value, + } + } + }) + .collect()) + } } #[derive(Debug)] From 50bb20c38754333625ba5c5fb418a2832ca54ce9 Mon Sep 17 00:00:00 2001 From: George Cosma Date: Tue, 18 Feb 2025 13:56:12 +0200 Subject: [PATCH 2/3] wip: manual lookup --- src/execution/execution_info.rs | 3 +- src/execution/store.rs | 111 +++++++++++++++++++++++++++++--- 2 files changed, 103 insertions(+), 11 deletions(-) diff --git a/src/execution/execution_info.rs b/src/execution/execution_info.rs index 37c17b27c..3284a983b 100644 --- a/src/execution/execution_info.rs +++ b/src/execution/execution_info.rs @@ -1,6 +1,7 @@ use alloc::string::{String, ToString}; use alloc::vec::Vec; +use crate::core::reader::types::export::Export; use crate::core::reader::types::FuncType; use crate::core::reader::WasmReader; use crate::execution::Store; @@ -36,5 +37,5 @@ pub struct ExecutionInfo<'r> { pub elements_offset: usize, pub passive_element_indexes: Vec, - // pub exports: Vec, + pub exports: Vec, } diff --git a/src/execution/store.rs b/src/execution/store.rs index a61cebd63..9407b0d51 100644 --- a/src/execution/store.rs +++ b/src/execution/store.rs @@ -1,4 +1,5 @@ -use alloc::string::String; +use alloc::collections::btree_map::BTreeMap; +use alloc::string::{String, ToString}; use alloc::vec; use alloc::vec::Vec; use core::iter; @@ -7,6 +8,7 @@ use crate::core::error::{Proposal, Result, StoreInstantiationError}; use crate::core::indices::TypeIdx; use crate::core::reader::span::Span; use crate::core::reader::types::element::{ElemItems, ElemMode}; +use crate::core::reader::types::export::ExportDesc; use crate::core::reader::types::global::Global; use crate::core::reader::types::import::ImportDesc; use crate::core::reader::types::{MemType, TableType, ValType}; @@ -25,7 +27,7 @@ use super::UnwrapValidatedExt; /// globals, element segments, and data segments that have been allocated during the life time of /// the abstract machine. /// -pub struct Store { +pub struct Store<'b> { pub functions: Vec, pub memories: Vec, pub globals: Vec, @@ -33,14 +35,12 @@ pub struct Store { pub tables: Vec, pub elements: Vec, // pub exports: Vec, + pub modules: Vec>, + pub module_names: BTreeMap, } -impl<'b> Store { - pub fn add_module( - &mut self, - name: String, - module: ValidationInfo<'b>, - ) -> Result> { +impl<'b> Store<'b> { + pub fn add_module(&mut self, name: String, module: ValidationInfo<'b>) -> Result<()> { // TODO: we can do validation at linktime such that if another module expects module `name` to export something, // and it doesn't, we can reject it here instead of accepting it and failing later. @@ -84,7 +84,7 @@ impl<'b> Store { self.elements.extend(element_inst); let execution_info = ExecutionInfo { - name, + name: name.clone(), wasm_bytecode: module.wasm, wasm_reader: WasmReader::new(module.wasm), @@ -111,9 +111,100 @@ impl<'b> Store { elements_offset, passive_element_indexes: passive_idxs, + exports: module.exports, }; - Ok(execution_info) + self.module_names.insert(name, self.modules.len()); + self.modules.push(execution_info); + + // TODO: At this point of the code, we can continue in two ways with imports/exports: + // 1. Lazy import resolution: We do the lookup during the interprer loop either directly or via a lookup-table + // 2. Active import resolution: We resolve the import dependency now, failing if there are unresolved imports. + // This limits the order in which modules need to be added. + // 3. Delayed active import resolution: We resolve the whatever import dependencies we can, but imports which + // can not be resolved are left to wait for another module addition. If an import that should be satisfied by + // this module isn't, we can fail. + + // TODO: failing is harder since we already modified 'self'. We will circle back to this later. + + for module in &mut self.modules { + for fn_store_idx in &mut module.functions { + let func = &self.functions[*fn_store_idx]; + if let FuncInst::Imported(import) = func { + let resolved_idx = + self.lookup_function(&import.module_name, &import.function_name); + + if resolved_idx.is_none() && import.module_name == name { + // TODO: Failed resolution... BAD! + } else { + *fn_store_idx = resolved_idx.unwrap(); + } + } + } + } + + Ok(()) + } + + pub fn lookup_function(&self, target_module: &str, target_function: &str) -> Option { + let mut module_name: &str = target_module; + let mut function_name: &str = target_function; + let mut import_path: Vec<(String, String)> = vec![]; + + for _ in 0..100 { + import_path.push((module_name.to_string(), function_name.to_string())); + let module_idx = self.module_names.get(module_name)?; + let module = &self.modules[*module_idx]; + + let mut same_name_exports = module.exports.iter().filter_map(|export| { + if export.name == function_name { + Some(&export.desc) + } else { + None + } + }); + + // TODO: what if there are two exports with the same name -- error out? + if same_name_exports.clone().count() != 1 { + return None; + } + + let target_export = same_name_exports.next()?; + + match target_export { + ExportDesc::FuncIdx(local_idx) => { + // Note: if we go ahead with the offset proposal, we can do + // store_idx = module.functions_offset + *local_idx + let store_idx = module.functions[*local_idx]; + + match &self.functions[store_idx] { + FuncInst::Local(_local_func_inst) => { + return Some(store_idx); + } + FuncInst::Imported(import) => { + if import_path.contains(&( + import.module_name.clone(), + import.function_name.clone(), + )) { + // TODO: find a way around this reference to clone thing. Rust is uppsety spaghetti for + // understandable but dumb reasons. + + // TODO: cycle detected :( + return None; + } + + module_name = &import.module_name; + function_name = &import.function_name; + } + } + } + _ => return None, + } + } + + // At this point, we are 100-imports deep. This isn't okay, and could be a sign of an infinte loop. We don't + // want our plane's CPU to keep searching for imports so we just assume we haven't found any. + None } } From f3a614e27ba46271af1b5fe04766aaacf14035b8 Mon Sep 17 00:00:00 2001 From: nerodesu017 Date: Tue, 18 Feb 2025 19:28:28 +0200 Subject: [PATCH 3/3] wip: a lil' more fixes, still not working --- src/core/error.rs | 16 ++++ src/execution/function_ref.rs | 8 +- src/execution/interpreter_loop.rs | 3 +- src/execution/mod.rs | 130 +++++++++++++++++++----------- src/execution/store.rs | 49 +++++++++-- 5 files changed, 147 insertions(+), 59 deletions(-) diff --git a/src/core/error.rs b/src/core/error.rs index a7977eb53..841507009 100644 --- a/src/core/error.rs +++ b/src/core/error.rs @@ -48,6 +48,11 @@ pub enum StoreInstantiationError { TooManyMemories(usize), } +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum LinkerError { + UnmetImport, +} + #[derive(Debug, PartialEq, Eq, Clone)] pub enum Error { /// The magic number at the very start of the given WASM file is invalid. @@ -107,6 +112,7 @@ pub enum Error { TooManyLocals(usize), UnsupportedProposal(Proposal), Overflow, + LinkerError(LinkerError), } impl Display for Error { @@ -264,6 +270,7 @@ impl Display for Error { f.write_fmt(format_args!("Unsupported proposal: {:?}", proposal)) } Error::Overflow => f.write_str("Overflow"), + Error::LinkerError(err) => err.fmt(f) } } } @@ -316,6 +323,15 @@ impl Display for StoreInstantiationError { } } +impl Display for LinkerError { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + use LinkerError::*; + match self { + UnmetImport => f.write_str("Unmet import"), + } + } +} + pub type Result = core::result::Result; impl From for Error { diff --git a/src/execution/function_ref.rs b/src/execution/function_ref.rs index a92868665..a0b36968e 100644 --- a/src/execution/function_ref.rs +++ b/src/execution/function_ref.rs @@ -4,6 +4,8 @@ use alloc::vec::Vec; use crate::execution::{hooks::HookSet, value::InteropValueList, RuntimeInstance}; use crate::{RuntimeError, ValType, Value}; +use super::store::Store; + pub struct FunctionRef { pub(crate) module_name: String, pub(crate) function_name: String, @@ -22,8 +24,9 @@ impl FunctionRef { &self, runtime: &mut RuntimeInstance, params: Param, + store: &mut Store, ) -> Result { - runtime.invoke(self, params) + runtime.invoke(self, params, store) } pub fn invoke_dynamic( @@ -31,8 +34,9 @@ impl FunctionRef { runtime: &mut RuntimeInstance, params: Vec, ret_types: &[ValType], + store: &mut Store, ) -> Result, RuntimeError> { - runtime.invoke_dynamic(self, params, ret_types) + runtime.invoke_dynamic(self, params, ret_types, store) } // pub fn get_return_types(&self) -> Vec( @@ -42,6 +42,7 @@ pub(super) fn run( lut: &Lut, stack: &mut Stack, mut hooks: H, + store: &mut Store, ) -> Result<(), RuntimeError> { let func_inst = modules[*current_module_idx] .store diff --git a/src/execution/mod.rs b/src/execution/mod.rs index 208cf6113..3426bde06 100644 --- a/src/execution/mod.rs +++ b/src/execution/mod.rs @@ -53,15 +53,16 @@ where } impl<'b> RuntimeInstance<'b, EmptyHookSet> { - pub fn new(validation_info: &'_ ValidationInfo<'b>) -> CustomResult { - Self::new_with_hooks(DEFAULT_MODULE, validation_info, EmptyHookSet) + pub fn new(validation_info: &'_ ValidationInfo<'b>, store: &mut Store) -> CustomResult { + Self::new_with_hooks(DEFAULT_MODULE, validation_info, EmptyHookSet, store) } pub fn new_named( module_name: &str, validation_info: &'_ ValidationInfo<'b>, + store: &mut Store, ) -> CustomResult { - Self::new_with_hooks(module_name, validation_info, EmptyHookSet) + Self::new_with_hooks(module_name, validation_info, EmptyHookSet, store) } } @@ -73,6 +74,7 @@ where module_name: &str, validation_info: &'_ ValidationInfo<'b>, hook_set: H, + store: &mut Store, ) -> CustomResult { trace!("Starting instantiation of bytecode"); @@ -95,7 +97,7 @@ where function_index: start, exported: false, }; - instance.invoke::<(), ()>(&start_fn, ())?; + instance.invoke::<(), ()>(&start_fn, (), store)?; } Ok(instance) @@ -173,24 +175,42 @@ where &mut self, function_ref: &FunctionRef, params: Param, + store: &mut Store, ) -> Result { // First, verify that the function reference is valid let (module_idx, func_idx) = self.verify_function_ref(function_ref)?; // -=-= Verification =-=- - trace!("{:?}", self.modules[module_idx].store.funcs); + // trace!("{:?}", self.modules[module_idx].store.funcs); + trace!("{:?}", self.modules[module_idx].functions); - let func_inst = self.modules[module_idx] - .store - .funcs + let func_inst_idx = self.modules[module_idx] + .functions .get(func_idx) .ok_or(RuntimeError::FunctionNotFound)? - .try_into_local() + .clone(); + + let func_inst = store + .functions + .get(func_inst_idx) .ok_or(RuntimeError::FunctionNotFound)?; - let func_ty = self.modules[module_idx] - .fn_types - .get(func_inst.ty) - .unwrap_validated(); + + let func_ty = func_inst.ty(); + + // let func_ty = self.modules + + // let func_inst = self.modules[module_idx] + // .store + // .funcs + // .get(func_idx) + // .ok_or(RuntimeError::FunctionNotFound)? + // .try_into_local() + // .ok_or(RuntimeError::FunctionNotFound)?; + + // let func_ty = self.modules[module_idx] + // .fn_types + // .get(func_inst.ty_idx) + // .unwrap_validated(); // Check correct function parameters and return types if func_ty.params.valtypes != Param::TYS { @@ -204,7 +224,7 @@ where let mut stack = Stack::new(); let locals = Locals::new( params.into_values().into_iter(), - func_inst.locals.iter().cloned(), + func_inst.try_into_local().unwrap().locals.iter().cloned(), ); // setting `usize::MAX` as return address for the outermost function ensures that we @@ -212,7 +232,7 @@ where stack.push_stackframe( module_idx, func_idx, - func_ty, + &func_ty, locals, usize::MAX, usize::MAX, @@ -226,6 +246,7 @@ where self.lut.as_ref().ok_or(RuntimeError::UnmetImport)?, &mut stack, EmptyHookSet, + store, )?; // Pop return values from stack @@ -247,22 +268,24 @@ where function_ref: &FunctionRef, params: Vec, ret_types: &[ValType], + store: &mut Store, ) -> 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.modules[module_idx] - .store - .funcs + let func_inst_idx = self.modules[module_idx] + .functions .get(func_idx) .ok_or(RuntimeError::FunctionNotFound)? - .try_into_local() + .clone(); + + let func_inst = store + .functions + .get(func_inst_idx) .ok_or(RuntimeError::FunctionNotFound)?; - let func_ty = self.modules[module_idx] - .fn_types - .get(func_inst.ty) - .unwrap_validated(); + + let func_ty = func_inst.ty(); // Verify that the given parameters match the function parameters let param_types = params.iter().map(|v| v.to_ty()).collect::>(); @@ -278,8 +301,11 @@ where // 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(module_idx, func_idx, func_ty, locals, 0, 0); + let locals = Locals::new( + params.into_iter(), + func_inst.try_into_local().unwrap().locals.iter().cloned(), + ); + stack.push_stackframe(module_idx, func_idx, &func_ty, locals, 0, 0); let mut currrent_module_idx = module_idx; // Run the interpreter @@ -289,19 +315,21 @@ where self.lut.as_ref().ok_or(RuntimeError::UnmetImport)?, &mut stack, EmptyHookSet, + &mut store, )?; - let func_inst = self.modules[module_idx] - .store - .funcs + let func_inst_idx = self.modules[module_idx] + .functions .get(func_idx) .ok_or(RuntimeError::FunctionNotFound)? - .try_into_local() + .clone(); + + let func_inst = store + .functions + .get(func_inst_idx) .ok_or(RuntimeError::FunctionNotFound)?; - let func_ty = self.modules[module_idx] - .fn_types - .get(func_inst.ty) - .unwrap_validated(); + + let func_ty = func_inst.ty(); // Pop return values from stack let return_values = func_ty @@ -334,22 +362,24 @@ where &mut self, function_ref: &FunctionRef, params: Vec, + store: &mut Store, ) -> 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.modules[module_idx] - .store - .funcs + let func_inst_idx = self.modules[module_idx] + .functions .get(func_idx) .ok_or(RuntimeError::FunctionNotFound)? - .try_into_local() + .clone(); + + let func_inst = store + .functions + .get(func_inst_idx) .ok_or(RuntimeError::FunctionNotFound)?; - let func_ty = self.modules[module_idx] - .fn_types - .get(func_inst.ty) - .unwrap_validated(); + + let func_ty = func_inst.ty(); // Verify that the given parameters match the function parameters let param_types = params.iter().map(|v| v.to_ty()).collect::>(); @@ -371,19 +401,21 @@ where self.lut.as_ref().ok_or(RuntimeError::UnmetImport)?, &mut stack, EmptyHookSet, + &mut store, )?; - let func_inst = self.modules[module_idx] - .store - .funcs + let func_inst_idx = self.modules[module_idx] + .functions .get(func_idx) .ok_or(RuntimeError::FunctionNotFound)? - .try_into_local() + .clone(); + + let func_inst = store + .functions + .get(func_inst_idx) .ok_or(RuntimeError::FunctionNotFound)?; - let func_ty = self.modules[module_idx] - .fn_types - .get(func_inst.ty) - .unwrap_validated(); + + let func_ty = func_inst.ty(); // Pop return values from stack let return_values = func_ty diff --git a/src/execution/store.rs b/src/execution/store.rs index 9407b0d51..4197f4b9c 100644 --- a/src/execution/store.rs +++ b/src/execution/store.rs @@ -1,4 +1,5 @@ use alloc::collections::btree_map::BTreeMap; +use alloc::collections::btree_set::BTreeSet; use alloc::string::{String, ToString}; use alloc::vec; use alloc::vec::Vec; @@ -11,7 +12,7 @@ use crate::core::reader::types::element::{ElemItems, ElemMode}; use crate::core::reader::types::export::ExportDesc; use crate::core::reader::types::global::Global; use crate::core::reader::types::import::ImportDesc; -use crate::core::reader::types::{MemType, TableType, ValType}; +use crate::core::reader::types::{FuncType, MemType, TableType, ValType}; use crate::core::reader::WasmReader; use crate::core::sidetable::Sidetable; use crate::execution::value::{Ref, Value}; @@ -22,6 +23,8 @@ use crate::{Error, RefType, ValidationInfo}; use super::execution_info::ExecutionInfo; use super::UnwrapValidatedExt; +use crate::core::error::LinkerError; + /// The store represents all global state that can be manipulated by WebAssembly programs. It /// consists of the runtime representation of all instances of functions, tables, memories, and /// globals, element segments, and data segments that have been allocated during the life time of @@ -114,7 +117,7 @@ impl<'b> Store<'b> { exports: module.exports, }; - self.module_names.insert(name, self.modules.len()); + self.module_names.insert(name.clone(), self.modules.len()); self.modules.push(execution_info); // TODO: At this point of the code, we can continue in two ways with imports/exports: @@ -127,22 +130,43 @@ impl<'b> Store<'b> { // TODO: failing is harder since we already modified 'self'. We will circle back to this later. - for module in &mut self.modules { - for fn_store_idx in &mut module.functions { - let func = &self.functions[*fn_store_idx]; + // let temp = Vec::new(); + + for module_idx in 0..self.modules.len() { + for function_idx in 0..self.modules[module_idx].functions.len() { + let fn_store_idx = self.modules[module_idx].functions[function_idx]; + let func: &FuncInst = &self.functions[fn_store_idx]; if let FuncInst::Imported(import) = func { let resolved_idx = self.lookup_function(&import.module_name, &import.function_name); if resolved_idx.is_none() && import.module_name == name { // TODO: Failed resolution... BAD! + return Err(Error::LinkerError(LinkerError::UnmetImport)); } else { - *fn_store_idx = resolved_idx.unwrap(); + self.modules[module_idx].functions[function_idx] = resolved_idx.unwrap(); } } } } + // for module in &self.modules { + // for fn_store_idx in &module.functions { + // let func: &FuncInst = &self.functions[*fn_store_idx]; + // if let FuncInst::Imported(import) = func { + // let resolved_idx = + // self.lookup_function(&import.module_name, &import.function_name); + + // if resolved_idx.is_none() && import.module_name == name { + // // TODO: Failed resolution... BAD! + // return Err(Error::LinkerError(LinkerError::UnmetImport)); + // } else { + // // *fn_store_idx = resolved_idx.unwrap(); + // } + // } + // } + // } + Ok(()) } @@ -234,6 +258,7 @@ impl<'b> ValidationInfo<'b> { code_expr, // TODO figure out where we want our sidetables sidetable: sidetable.clone(), + function_type: self.types[*ty].clone(), }) }); @@ -242,6 +267,7 @@ impl<'b> ValidationInfo<'b> { ty: *type_idx, module_name: import.module_name.clone(), function_name: import.name.clone(), + function_type: self.types[*type_idx].clone(), })), _ => None, }); @@ -464,6 +490,7 @@ pub struct LocalFuncInst { pub locals: Vec, pub code_expr: Span, pub sidetable: Sidetable, + pub function_type: FuncType, } #[derive(Debug)] @@ -471,16 +498,24 @@ pub struct ImportedFuncInst { pub ty: TypeIdx, pub module_name: String, pub function_name: String, + pub function_type: FuncType, } impl FuncInst { - pub fn ty(&self) -> TypeIdx { + pub fn ty_idx(&self) -> TypeIdx { match self { FuncInst::Local(f) => f.ty, FuncInst::Imported(f) => f.ty, } } + pub fn ty(&self) -> FuncType { + match self { + FuncInst::Local(f) => f.function_type.clone(), + FuncInst::Imported(f) => f.function_type.clone(), + } + } + pub fn try_into_local(&self) -> Option<&LocalFuncInst> { match self { FuncInst::Local(f) => Some(f),