diff --git a/src/core/reader/types/import.rs b/src/core/reader/types/import.rs index 7e2607658..11f7dc620 100644 --- a/src/core/reader/types/import.rs +++ b/src/core/reader/types/import.rs @@ -6,7 +6,7 @@ use crate::core::reader::{WasmReadable, WasmReader}; use crate::execution::assert_validated::UnwrapValidatedExt; use crate::{unreachable_validated, Error, Result}; -use super::TableType; +use super::{MemType, TableType}; #[derive(Debug)] pub struct Import { @@ -52,7 +52,7 @@ pub enum ImportDesc { Table(TableType), // TODO TableType #[allow(dead_code)] - Mem(()), + Mem(MemType), // TODO MemType #[allow(dead_code)] Global(()), // TODO GlobalType @@ -64,7 +64,7 @@ impl WasmReadable for ImportDesc { 0x00 => Self::Func(wasm.read_var_u32()? as TypeIdx), // https://webassembly.github.io/spec/core/binary/types.html#table-types 0x01 => Self::Table(TableType::read(wasm)?), - 0x02 => todo!("read MemType"), + 0x02 => Self::Mem(MemType::read(wasm)?), 0x03 => todo!("read GlobalType"), other => return Err(Error::InvalidImportDesc(other)), }; @@ -76,7 +76,7 @@ impl WasmReadable for ImportDesc { match wasm.read_u8().unwrap_validated() { 0x00 => Self::Func(wasm.read_var_u32().unwrap_validated() as TypeIdx), 0x01 => Self::Table(TableType::read_unvalidated(wasm)), - 0x02 => todo!("read MemType"), + 0x02 => Self::Mem(MemType::read_unvalidated(wasm)), 0x03 => todo!("read GlobalType"), _ => unreachable_validated!(), } diff --git a/src/execution/execution_info.rs b/src/execution/execution_info.rs index 8f7afe5ab..663a0c31f 100644 --- a/src/execution/execution_info.rs +++ b/src/execution/execution_info.rs @@ -3,27 +3,40 @@ use alloc::vec::Vec; use crate::core::reader::types::FuncType; use crate::core::reader::WasmReader; +use crate::core::sidetable::Sidetable; use crate::execution::Store; /// ExecutionInfo is a compilation of relevant information needed by the [interpreter loop]( /// crate::execution::interpreter_loop::run). The lifetime annotation `'r` represents that this structure needs to be /// valid at least as long as the [RuntimeInstance](crate::execution::RuntimeInstance) that creates it. -pub struct ExecutionInfo<'r> { +pub struct ExecutionInfo { 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 { +impl ExecutionInfo { + pub fn new(name: &str, fn_types: Vec, store: Store) -> Self { ExecutionInfo { name: name.to_string(), - wasm_bytecode, - wasm_reader: WasmReader::new(wasm_bytecode), fn_types, store, } } } + +pub struct StateData<'r> { + pub wasm_bytecode: &'r [u8], + pub wasm_reader: WasmReader<'r>, + pub sidetables: Vec, +} + +impl<'r> StateData<'r> { + pub fn new(wasm_bytecode: &'r [u8], sidetables: Vec) -> Self { + StateData { + wasm_bytecode, + wasm_reader: WasmReader::new(wasm_bytecode), + sidetables, + } + } +} diff --git a/src/execution/interpreter_loop.rs b/src/execution/interpreter_loop.rs index a19f625a9..bf959a32b 100644 --- a/src/execution/interpreter_loop.rs +++ b/src/execution/interpreter_loop.rs @@ -10,6 +10,8 @@ //! [`Error::RuntimeError`](crate::Error::RuntimeError) variant, which as per 2., we don not //! want +use core::cmp::Ordering; + use alloc::vec; use alloc::vec::Vec; @@ -24,7 +26,7 @@ use crate::{ sidetable::Sidetable, }, locals::Locals, - store::{DataInst, FuncInst}, + store::{DataInst, FuncInst, MemInst}, value::{self, FuncAddr, Ref}, value_stack::Stack, Limits, NumType, RefType, RuntimeError, ValType, Value, @@ -33,11 +35,16 @@ use crate::{ #[cfg(feature = "hooks")] use crate::execution::hooks::HookSet; -use super::{execution_info::ExecutionInfo, lut::Lut}; +use super::{ + execution_info::{ExecutionInfo, StateData}, + lut::Lut, + store::LocalMemInst, +}; /// Interprets a functions. Parameters and return values are passed on the stack. pub(super) fn run( modules: &mut [ExecutionInfo], + state_data: &mut [StateData], current_module_idx: &mut usize, lut: &Lut, stack: &mut Stack, @@ -52,11 +59,12 @@ pub(super) fn run( .unwrap_validated(); // Start reading the function's instructions - let mut wasm = &mut modules[*current_module_idx].wasm_reader; + let mut wasm = &mut state_data[*current_module_idx].wasm_reader; // the sidetable and stp for this function, stp will reset to 0 every call // since function instances have their own sidetable. - let mut current_sidetable: &Sidetable = &func_inst.sidetable; + let mut current_sidetable: &Sidetable = + &state_data[*current_module_idx].sidetables[stack.current_stackframe().func_idx]; let mut stp = 0; // unwrap is sound, because the validation assures that the function points to valid subslice of the WASM binary @@ -66,7 +74,7 @@ pub(super) fn run( loop { // call the instruction hook #[cfg(feature = "hooks")] - hooks.instruction_hook(modules[*current_module_idx].wasm_bytecode, wasm.pc); + hooks.instruction_hook(state_data[*current_module_idx].wasm_bytecode, wasm.pc); let first_instr_byte = wasm.read_u8().unwrap_validated(); @@ -105,18 +113,12 @@ pub(super) fn run( } trace!("end of function reached, returning to previous stack frame"); - wasm = &mut modules[return_module].wasm_reader; + wasm = &mut state_data[return_module].wasm_reader; wasm.pc = maybe_return_address; stp = maybe_return_stp; - current_sidetable = &modules[return_module] - .store - .funcs - .get(stack.current_stackframe().func_idx) - .unwrap_validated() - .try_into_local() - .unwrap_validated() - .sidetable; + current_sidetable = + &state_data[return_module].sidetables[stack.current_stackframe().func_idx]; *current_module_idx = return_module; } @@ -210,7 +212,8 @@ pub(super) fn run( .unwrap_validated(); stp = 0; - current_sidetable = &local_func_inst.sidetable; + current_sidetable = + &state_data[*current_module_idx].sidetables[func_to_call_idx]; } FuncInst::Imported(_imported_func_inst) => { let (next_module, next_func_idx) = lut @@ -233,14 +236,14 @@ pub(super) fn run( stp, ); - wasm = &mut modules[next_module].wasm_reader; + wasm = &mut state_data[next_module].wasm_reader; *current_module_idx = next_module; wasm.move_start_to(local_func_inst.code_expr) .unwrap_validated(); stp = 0; - current_sidetable = &local_func_inst.sidetable; + current_sidetable = &state_data[next_module].sidetables[next_func_idx]; } } } @@ -310,7 +313,7 @@ pub(super) fn run( .unwrap_validated(); stp = 0; - current_sidetable = &local_func_inst.sidetable; + current_sidetable = &state_data[*current_module_idx].sidetables[func_addr]; } FuncInst::Imported(_imported_func_inst) => { let (next_module, next_func_idx) = lut @@ -335,14 +338,14 @@ pub(super) fn run( stp, ); - wasm = &mut modules[next_module].wasm_reader; + wasm = &mut state_data[next_module].wasm_reader; *current_module_idx = next_module; wasm.move_start_to(local_func_inst.code_expr) .unwrap_validated(); stp = 0; - current_sidetable = &local_func_inst.sidetable; + current_sidetable = &state_data[next_module].sidetables[next_func_idx]; } } } @@ -444,11 +447,8 @@ pub(super) fn run( let memarg = MemArg::read_unvalidated(wasm); let relative_address: u32 = stack.pop_value(ValType::NumType(NumType::I32)).into(); - let mem = modules[*current_module_idx] - .store - .mems - .first() - .unwrap_validated(); // there is only one memory allowed as of now + // Currently, we support a single memory + let mem = get_mem(modules, lut, *current_module_idx, 0); let data: u32 = { // The spec states that this should be a 33 bit integer @@ -474,11 +474,8 @@ pub(super) fn run( let memarg = MemArg::read_unvalidated(wasm); let relative_address: u32 = stack.pop_value(ValType::NumType(NumType::I32)).into(); - let mem = modules[*current_module_idx] - .store - .mems - .first() - .unwrap_validated(); + // Currently, we support a single memory + let mem = get_mem(modules, lut, *current_module_idx, 0); let data: u64 = { // The spec states that this should be a 33 bit integer @@ -505,11 +502,8 @@ pub(super) fn run( let memarg = MemArg::read_unvalidated(wasm); let relative_address: u32 = stack.pop_value(ValType::NumType(NumType::I32)).into(); - let mem = modules[*current_module_idx] - .store - .mems - .first() - .unwrap_validated(); // there is only one memory allowed as of now + // Currently, we support a single memory + let mem = get_mem(modules, lut, *current_module_idx, 0); let data: f32 = { // The spec states that this should be a 33 bit integer @@ -535,11 +529,8 @@ pub(super) fn run( let memarg = MemArg::read_unvalidated(wasm); let relative_address: u32 = stack.pop_value(ValType::NumType(NumType::I32)).into(); - let mem = modules[*current_module_idx] - .store - .mems - .first() - .unwrap_validated(); + // Currently, we support a single memory + let mem = get_mem(modules, lut, *current_module_idx, 0); let data: f64 = { // The spec states that this should be a 33 bit integer @@ -566,11 +557,8 @@ pub(super) fn run( let memarg = MemArg::read_unvalidated(wasm); let relative_address: u32 = stack.pop_value(ValType::NumType(NumType::I32)).into(); - let mem = modules[*current_module_idx] - .store - .mems - .first() - .unwrap_validated(); // there is only one memory allowed as of now + // Currently, we support a single memory + let mem = get_mem(modules, lut, *current_module_idx, 0); let data: i8 = { // The spec states that this should be a 33 bit integer @@ -598,11 +586,8 @@ pub(super) fn run( let memarg = MemArg::read_unvalidated(wasm); let relative_address: u32 = stack.pop_value(ValType::NumType(NumType::I32)).into(); - let mem = modules[*current_module_idx] - .store - .mems - .first() - .unwrap_validated(); // there is only one memory allowed as of now + // Currently, we support a single memory + let mem = get_mem(modules, lut, *current_module_idx, 0); let data: u8 = { // The spec states that this should be a 33 bit integer @@ -629,11 +614,8 @@ pub(super) fn run( let memarg = MemArg::read_unvalidated(wasm); let relative_address: u32 = stack.pop_value(ValType::NumType(NumType::I32)).into(); - let mem = modules[*current_module_idx] - .store - .mems - .first() - .unwrap_validated(); // there is only one memory allowed as of now + // Currently, we support a single memory + let mem = get_mem(modules, lut, *current_module_idx, 0); let data: i16 = { // The spec states that this should be a 33 bit integer @@ -660,11 +642,8 @@ pub(super) fn run( let memarg = MemArg::read_unvalidated(wasm); let relative_address: u32 = stack.pop_value(ValType::NumType(NumType::I32)).into(); - let mem = modules[*current_module_idx] - .store - .mems - .first() - .unwrap_validated(); // there is only one memory allowed as of now + // Currently, we support a single memory + let mem = get_mem(modules, lut, *current_module_idx, 0); let data: u16 = { // The spec states that this should be a 33 bit integer @@ -691,11 +670,8 @@ pub(super) fn run( let memarg = MemArg::read_unvalidated(wasm); let relative_address: u32 = stack.pop_value(ValType::NumType(NumType::I32)).into(); - let mem = modules[*current_module_idx] - .store - .mems - .first() - .unwrap_validated(); // there is only one memory allowed as of now + // Currently, we support a single memory + let mem = get_mem(modules, lut, *current_module_idx, 0); let data: i8 = { // The spec states that this should be a 33 bit integer @@ -723,11 +699,8 @@ pub(super) fn run( let memarg = MemArg::read_unvalidated(wasm); let relative_address: u32 = stack.pop_value(ValType::NumType(NumType::I32)).into(); - let mem = modules[*current_module_idx] - .store - .mems - .first() - .unwrap_validated(); // there is only one memory allowed as of now + // Currently, we support a single memory + let mem = get_mem(modules, lut, *current_module_idx, 0); let data: u8 = { // The spec states that this should be a 33 bit integer @@ -755,11 +728,8 @@ pub(super) fn run( let memarg = MemArg::read_unvalidated(wasm); let relative_address: u32 = stack.pop_value(ValType::NumType(NumType::I32)).into(); - let mem = modules[*current_module_idx] - .store - .mems - .first() - .unwrap_validated(); // there is only one memory allowed as of now + // Currently, we support a single memory + let mem = get_mem(modules, lut, *current_module_idx, 0); let data: i16 = { // The spec states that this should be a 33 bit integer @@ -786,11 +756,8 @@ pub(super) fn run( let memarg = MemArg::read_unvalidated(wasm); let relative_address: u32 = stack.pop_value(ValType::NumType(NumType::I32)).into(); - let mem = modules[*current_module_idx] - .store - .mems - .first() - .unwrap_validated(); // there is only one memory allowed as of now + // Currently, we support a single memory + let mem = get_mem(modules, lut, *current_module_idx, 0); let data: u16 = { // The spec states that this should be a 33 bit integer @@ -817,11 +784,8 @@ pub(super) fn run( let memarg = MemArg::read_unvalidated(wasm); let relative_address: u32 = stack.pop_value(ValType::NumType(NumType::I32)).into(); - let mem = modules[*current_module_idx] - .store - .mems - .first() - .unwrap_validated(); // there is only one memory allowed as of now + // Currently, we support a single memory + let mem = get_mem(modules, lut, *current_module_idx, 0); let data: i32 = { // The spec states that this should be a 33 bit integer @@ -848,11 +812,8 @@ pub(super) fn run( let memarg = MemArg::read_unvalidated(wasm); let relative_address: u32 = stack.pop_value(ValType::NumType(NumType::I32)).into(); - let mem = modules[*current_module_idx] - .store - .mems - .first() - .unwrap_validated(); // there is only one memory allowed as of now + // Currently, we support a single memory + let mem = get_mem(modules, lut, *current_module_idx, 0); let data: u32 = { // The spec states that this should be a 33 bit integer @@ -881,11 +842,8 @@ pub(super) fn run( let data_to_store: u32 = stack.pop_value(ValType::NumType(NumType::I32)).into(); let relative_address: u32 = stack.pop_value(ValType::NumType(NumType::I32)).into(); - let mem = modules[*current_module_idx] - .store - .mems - .get_mut(0) - .unwrap_validated(); // there is only one memory allowed as of now + // Currently, we support a single memory + let mem = get_mem(modules, lut, *current_module_idx, 0); // The spec states that this should be a 33 bit integer // See: https://webassembly.github.io/spec/core/syntax/instructions.html#memory-instructions @@ -906,11 +864,8 @@ pub(super) fn run( let data_to_store: u32 = stack.pop_value(ValType::NumType(NumType::I64)).into(); let relative_address: u32 = stack.pop_value(ValType::NumType(NumType::I32)).into(); - let mem = modules[*current_module_idx] - .store - .mems - .get_mut(0) - .unwrap_validated(); // there is only one memory allowed as of now + // Currently, we support a single memory + let mem = get_mem(modules, lut, *current_module_idx, 0); // The spec states that this should be a 33 bit integer // See: https://webassembly.github.io/spec/core/syntax/instructions.html#memory-instructions @@ -931,11 +886,8 @@ pub(super) fn run( let data_to_store: f32 = stack.pop_value(ValType::NumType(NumType::F32)).into(); let relative_address: u32 = stack.pop_value(ValType::NumType(NumType::I32)).into(); - let mem = modules[*current_module_idx] - .store - .mems - .get_mut(0) - .unwrap_validated(); // there is only one memory allowed as of now + // Currently, we support a single memory + let mem = get_mem(modules, lut, *current_module_idx, 0); // The spec states that this should be a 33 bit integer // See: https://webassembly.github.io/spec/core/syntax/instructions.html#memory-instructions @@ -956,11 +908,8 @@ pub(super) fn run( let data_to_store: f64 = stack.pop_value(ValType::NumType(NumType::F64)).into(); let relative_address: u32 = stack.pop_value(ValType::NumType(NumType::I32)).into(); - let mem = modules[*current_module_idx] - .store - .mems - .get_mut(0) - .unwrap_validated(); // there is only one memory allowed as of now + // Currently, we support a single memory + let mem = get_mem(modules, lut, *current_module_idx, 0); // The spec states that this should be a 33 bit integer // See: https://webassembly.github.io/spec/core/syntax/instructions.html#memory-instructions @@ -981,11 +930,8 @@ pub(super) fn run( let data_to_store: i32 = stack.pop_value(ValType::NumType(NumType::I32)).into(); let relative_address: u32 = stack.pop_value(ValType::NumType(NumType::I32)).into(); - let mem = modules[*current_module_idx] - .store - .mems - .get_mut(0) - .unwrap_validated(); + // Currently, we support a single memory + let mem = get_mem(modules, lut, *current_module_idx, 0); // The spec states that this should be a 33 bit integer // See: https://webassembly.github.io/spec/core/syntax/instructions.html#memory-instructions @@ -1007,11 +953,8 @@ pub(super) fn run( let data_to_store: i32 = stack.pop_value(ValType::NumType(NumType::I32)).into(); let relative_address: u32 = stack.pop_value(ValType::NumType(NumType::I32)).into(); - let mem = modules[*current_module_idx] - .store - .mems - .get_mut(0) - .unwrap_validated(); + // Currently, we support a single memory + let mem = get_mem(modules, lut, *current_module_idx, 0); // The spec states that this should be a 33 bit integer // See: https://webassembly.github.io/spec/core/syntax/instructions.html#memory-instructions @@ -1033,11 +976,8 @@ pub(super) fn run( let data_to_store: i64 = stack.pop_value(ValType::NumType(NumType::I64)).into(); let relative_address: u32 = stack.pop_value(ValType::NumType(NumType::I32)).into(); - let mem = modules[*current_module_idx] - .store - .mems - .get_mut(0) - .unwrap_validated(); + // Currently, we support a single memory + let mem = get_mem(modules, lut, *current_module_idx, 0); // The spec states that this should be a 33 bit integer // See: https://webassembly.github.io/spec/core/syntax/instructions.html#memory-instructions @@ -1059,11 +999,8 @@ pub(super) fn run( let data_to_store: i64 = stack.pop_value(ValType::NumType(NumType::I64)).into(); let relative_address: u32 = stack.pop_value(ValType::NumType(NumType::I32)).into(); - let mem = modules[*current_module_idx] - .store - .mems - .get_mut(0) - .unwrap_validated(); + // Currently, we support a single memory + let mem = get_mem(modules, lut, *current_module_idx, 0); // The spec states that this should be a 33 bit integer // See: https://webassembly.github.io/spec/core/syntax/instructions.html#memory-instructions @@ -1085,11 +1022,8 @@ pub(super) fn run( let data_to_store: i64 = stack.pop_value(ValType::NumType(NumType::I64)).into(); let relative_address: u32 = stack.pop_value(ValType::NumType(NumType::I32)).into(); - let mem = modules[*current_module_idx] - .store - .mems - .get_mut(0) - .unwrap_validated(); + // Currently, we support a single memory + let mem = get_mem(modules, lut, *current_module_idx, 0); // The spec states that this should be a 33 bit integer // See: https://webassembly.github.io/spec/core/syntax/instructions.html#memory-instructions @@ -1107,22 +1041,16 @@ pub(super) fn run( } MEMORY_SIZE => { let mem_idx = wasm.read_u8().unwrap_validated() as usize; - let mem = modules[*current_module_idx] - .store - .mems - .get(mem_idx) - .unwrap_validated(); + let mem = get_mem(modules, lut, *current_module_idx, mem_idx); + let size = mem.size() as u32; stack.push_value(Value::I32(size)); trace!("Instruction: memory.size [] -> [{}]", size); } MEMORY_GROW => { let mem_idx = wasm.read_u8().unwrap_validated() as usize; - let mem = modules[*current_module_idx] - .store - .mems - .get_mut(mem_idx) - .unwrap_validated(); + let mem = get_mem(modules, lut, *current_module_idx, mem_idx); + let delta: i32 = stack.pop_value(ValType::NumType(NumType::I32)).into(); let upper_limit = mem.ty.limits.max.unwrap_or(Limits::MAX_MEM_BYTES); @@ -2468,11 +2396,25 @@ pub(super) fn run( .data .len(); let mem_idx = wasm.read_u8().unwrap_validated() as usize; - let mem = modules[*current_module_idx] - .store - .mems - .get(mem_idx) - .unwrap_validated(); + + // We use both the memory and data segments. We cannot use our get_mem method. + let mem = &mut modules[*current_module_idx].store.mems[mem_idx]; + + let mem = match mem { + MemInst::Local(local_mem_inst) => local_mem_inst, + MemInst::Imported(_imported_mem_inst) => { + let (next_module, next_mem_idx) = lut + .lookup_mem(*current_module_idx, mem_idx) + .expect("invalid state for lookup"); + + let local_mem_inst = modules[next_module].store.mems[next_mem_idx] + .try_into_local() + .unwrap(); + + local_mem_inst + } + }; + let n: i32 = stack.pop_value(ValType::NumType(NumType::I32)).into(); let s: i32 = stack.pop_value(ValType::NumType(NumType::I32)).into(); let d: i32 = stack.pop_value(ValType::NumType(NumType::I32)).into(); @@ -2493,12 +2435,8 @@ pub(super) fn run( .get(data_idx) .unwrap() .data[(s as usize)..final_src_offset]; - modules[*current_module_idx] - .store - .mems - .get_mut(mem_idx) - .unwrap_validated() - .data + + mem.data .get_mut(d as usize..final_dst_offset) .unwrap_validated() .copy_from_slice(data); @@ -2531,25 +2469,63 @@ pub(super) fn run( let s: i32 = stack.pop_value(ValType::NumType(NumType::I32)).into(); let d: i32 = stack.pop_value(ValType::NumType(NumType::I32)).into(); - let len_src = modules[*current_module_idx] - .store - .mems - .get(src) - .unwrap_validated() - .data - .len(); + let len_src = { + let src_mem = modules[*current_module_idx] + .store + .mems + .get_mut(src) + .unwrap_validated(); + + let src_mem = match src_mem { + MemInst::Local(local_mem_inst) => local_mem_inst, + MemInst::Imported(_imported_mem_inst) => { + let (next_module, next_func_idx) = lut + .lookup_mem(*current_module_idx, 0) + .expect("invalid state for lookup"); + + let local_func_inst = modules[next_module].store.mems + [next_func_idx] + .try_into_local() + .unwrap(); + + local_func_inst + } + }; + + src_mem.data.len() + }; + + let len_dest = { + let dst_mem = modules[*current_module_idx] + .store + .mems + .get_mut(dst) + .unwrap_validated(); + + let dst_mem = match dst_mem { + MemInst::Local(local_mem_inst) => local_mem_inst, + MemInst::Imported(_imported_mem_inst) => { + let (next_module, next_func_idx) = lut + .lookup_mem(*current_module_idx, 0) + .expect("invalid state for lookup"); + + let local_func_inst = modules[next_module].store.mems + [next_func_idx] + .try_into_local() + .unwrap(); + + local_func_inst + } + }; + + dst_mem.data.len() + }; + let final_src_offset = (n as usize) .checked_add(s as usize) .filter(|&res| res <= len_src) .ok_or(RuntimeError::MemoryAccessOutOfBounds)?; - let len_dest = modules[*current_module_idx] - .store - .mems - .get(dst) - .unwrap_validated() - .data - .len(); // let final_dst_offset = (n as usize) .checked_add(d as usize) @@ -2558,31 +2534,119 @@ pub(super) fn run( if dst == src { // we copy from memory X to memory X - let mem = modules[*current_module_idx] + + let src_mem = modules[*current_module_idx] .store .mems .get_mut(src) .unwrap_validated(); - mem.data + + let src_mem = match src_mem { + MemInst::Local(local_mem_inst) => local_mem_inst, + MemInst::Imported(_imported_mem_inst) => { + let (next_module, next_func_idx) = lut + .lookup_mem(*current_module_idx, 0) // TODO: this point to mem0, since we don't have the multimemory proposal + .expect("invalid state for lookup"); + + let local_func_inst = modules[next_module].store.mems + [next_func_idx] + .try_into_local() + .unwrap(); + + local_func_inst + } + }; + + src_mem + .data .copy_within(s as usize..final_src_offset, d as usize); } else { - // we copy from one memory to another - use core::cmp::Ordering::*; - let (src_mem, dst_mem) = match dst.cmp(&src) { - Greater => { - let (left, right) = - modules[*current_module_idx].store.mems.split_at_mut(dst); - (&left[src], &mut right[0]) + // Find left_module and right_module + let (src_module, src_mem_idx) = { + match &modules[*current_module_idx].store.mems[src] { + MemInst::Local(_src_local_mem_inst) => { + (*current_module_idx, src) + } + MemInst::Imported(_imported_mem_inst) => lut + .lookup_mem(*current_module_idx, src) + .expect("invalid state for lookup"), } - Less => { - let (left, right) = - modules[*current_module_idx].store.mems.split_at_mut(src); - (&right[0], &mut left[dst]) + }; + + let (dst_module, dst_mem_idx) = { + match &modules[*current_module_idx].store.mems[dst] { + MemInst::Local(_dst_local_mem_inst) => { + (*current_module_idx, dst) + } + MemInst::Imported(_imported_mem_inst) => lut + .lookup_mem(*current_module_idx, dst) + .expect("invalid state for lookup"), } - Equal => unreachable!(), }; - dst_mem.data[d as usize..(d + n) as usize] - .copy_from_slice(&src_mem.data[s as usize..(s + n) as usize]); + + if src_module == dst_module { + let (src_mem, dst_mem) = match src_mem_idx.cmp(&dst_mem_idx) { + Ordering::Less => { + let (left, right) = modules[src_module] + .store + .mems + .split_at_mut(dst_mem_idx); + + (&mut left[src_mem_idx], &mut right[0]) + } + Ordering::Greater => { + let (left, right) = modules[src_module] + .store + .mems + .split_at_mut(src_mem_idx); + + (&mut right[0], &mut left[dst_mem_idx]) + } + Ordering::Equal => unreachable!(), + }; + + // At this point, we don't allow recursive or + // chained imports. As such we know these must + // be local instances. + + dst_mem + .try_into_local() + .expect("Chained imports not allowed") + .data[d as usize..(d + n) as usize] + .copy_from_slice( + &src_mem + .try_into_local() + .expect("Chained imports not allowed") + .data + [s as usize..(s + n) as usize], + ); + } else { + let (src_module_store, dst_module_store) = + match src_module.cmp(&dst_module) { + Ordering::Less => { + let (left, right) = modules.split_at_mut(dst_module); + + (&mut left[src_module].store, &mut right[0].store) + } + Ordering::Greater => { + let (left, right) = modules.split_at_mut(src_module); + + (&mut right[0].store, &mut left[dst_module].store) + } + Ordering::Equal => unreachable!(), + }; + + let src_mem = src_module_store.mems[src_mem_idx] + .try_into_local() + .expect("Chained imports not allowed"); + + let dst_mem = dst_module_store.mems[dst_mem_idx] + .try_into_local() + .expect("Chained imports not allowed"); + + dst_mem.data[d as usize..(d + n) as usize] + .copy_from_slice(&src_mem.data[s as usize..(s + n) as usize]); + } } trace!("Instruction: memory.copy"); @@ -2594,11 +2658,8 @@ pub(super) fn run( // val => the value to set each byte to (must be < 256) // d => the pointer to the region to update let mem_idx = wasm.read_u8().unwrap_validated() as usize; - let mem = modules[*current_module_idx] - .store - .mems - .get(mem_idx) - .unwrap_validated(); + let mem = get_mem(modules, lut, *current_module_idx, mem_idx); + let n: i32 = stack.pop_value(ValType::NumType(NumType::I32)).into(); let val: i32 = stack.pop_value(ValType::NumType(NumType::I32)).into(); @@ -2614,12 +2675,7 @@ pub(super) fn run( .ok_or(RuntimeError::MemoryAccessOutOfBounds)?; let data: Vec = vec![val as u8; (n - d) as usize]; - modules[*current_module_idx] - .store - .mems - .get_mut(mem_idx) - .unwrap_validated() - .data + mem.data .get_mut(d as usize..final_dst_offset) .unwrap_validated() .copy_from_slice(&data); @@ -2888,3 +2944,23 @@ fn do_sidetable_control_transfer( *current_stp = (*current_stp as isize + sidetable_entry.delta_stp) as usize; wasm.pc = (wasm.pc as isize + sidetable_entry.delta_pc) as usize; } + +fn get_mem<'a>( + modules: &'a mut [ExecutionInfo], + lut: &Lut, + current_module_idx: usize, + current_memory_idx: usize, +) -> &'a mut LocalMemInst { + let mem = &mut modules[current_module_idx].store.mems[current_memory_idx]; + + let (mod_idx, mem_idx) = match mem { + MemInst::Local(_) => (current_module_idx, current_memory_idx), + MemInst::Imported(_) => lut + .lookup_mem(current_module_idx, current_memory_idx) + .expect("invalid state for lookup"), + }; + + modules[mod_idx].store.mems[mem_idx] + .try_into_local() + .unwrap() +} diff --git a/src/execution/lut.rs b/src/execution/lut.rs index e4fd48c4f..16f860988 100644 --- a/src/execution/lut.rs +++ b/src/execution/lut.rs @@ -1,6 +1,8 @@ use crate::{core::reader::types::export::ExportDesc, execution::execution_info::ExecutionInfo}; use alloc::{collections::btree_map::BTreeMap, string::String, vec::Vec}; +use super::store::MemInst; + pub struct Lut { /// function_lut\[local_module_idx\]\[function_local_idx\] = (foreign_module_idx, function_foreign_idx) /// @@ -9,6 +11,8 @@ pub struct Lut { /// - Module B exports a function "foo". Inside module B, the function has the index "function_foreign_idx". Module /// B is assigned the index "foreign_module_idx". function_lut: Vec>, + + memory_lut: Vec>, } impl Lut { @@ -44,7 +48,36 @@ impl Lut { function_lut.push(module_lut); } - Some(Self { function_lut }) + let mut memory_lut = Vec::new(); + for module in modules { + let module_lut = module + .store + .mems + .iter() + .filter_map(|f| match &f { + MemInst::Local(_local_mem_inst) => None, + MemInst::Imported(imported_mem_inst) => Some(imported_mem_inst), + }) + .map(|import| { + Self::manual_lookup( + modules, + module_map, + &import.module_name, + &import.function_name, + ) + }) + .collect::>>()?; + + // If there is a missing import/export pair, we fail the entire + // operation. Better safe than sorry... + + memory_lut.push(module_lut); + } + + Some(Self { + function_lut, + memory_lut, + }) } /// Lookup a function by its module and function index. @@ -65,6 +98,10 @@ impl Lut { .copied() } + pub fn lookup_mem(&self, module_idx: usize, function_idx: usize) -> Option<(usize, usize)> { + self.memory_lut.get(module_idx)?.get(function_idx).copied() + } + /// Manually lookup a function by its module and function name. /// /// This function is used to resolve import directives before the [Lut] is created, and can be used to resolve @@ -103,10 +140,11 @@ impl Lut { } }) .find_map(|desc| { - if let ExportDesc::FuncIdx(func_idx) = desc { - Some((*module_idx, *func_idx)) - } else { - None + match desc { + ExportDesc::FuncIdx(func_idx) => Some((*module_idx, *func_idx)), + // TODO: this will break if a function and memory share a name. Need to modify function to be told what to look for + ExportDesc::MemIdx(mem_idx) => Some((*module_idx, *mem_idx)), + _ => None, } }) } diff --git a/src/execution/mod.rs b/src/execution/mod.rs index 8ba6b019c..cab511725 100644 --- a/src/execution/mod.rs +++ b/src/execution/mod.rs @@ -4,12 +4,14 @@ use alloc::vec; use alloc::vec::Vec; use const_interpreter_loop::{run_const, run_const_span}; -use execution_info::ExecutionInfo; +use execution_info::{ExecutionInfo, StateData}; use function_ref::FunctionRef; use interpreter_loop::run; use locals::Locals; use lut::Lut; -use store::{DataInst, ElemInst, ImportedFuncInst, LocalFuncInst, TableInst}; +use store::{ + DataInst, ElemInst, ImportedFuncInst, ImportedMemInst, LocalFuncInst, LocalMemInst, TableInst, +}; use value::{ExternAddr, FuncAddr, Ref}; use value_stack::Stack; @@ -18,6 +20,7 @@ use crate::core::reader::types::element::{ElemItems, ElemMode}; use crate::core::reader::types::export::ExportDesc; use crate::core::reader::types::import::ImportDesc; use crate::core::reader::WasmReader; +use crate::core::sidetable::Sidetable; use crate::execution::assert_validated::UnwrapValidatedExt; use crate::execution::hooks::{EmptyHookSet, HookSet}; use crate::execution::store::{FuncInst, GlobalInst, MemInst, Store}; @@ -46,7 +49,8 @@ pub struct RuntimeInstance<'b, H = EmptyHookSet> where H: HookSet, { - pub modules: Vec>, + pub modules: Vec, + pub state_data: Vec>, module_map: BTreeMap, lut: Option, pub hook_set: H, @@ -78,6 +82,7 @@ where let mut instance = RuntimeInstance { modules: Vec::new(), + state_data: Vec::new(), module_map: BTreeMap::new(), lut: None, hook_set, @@ -153,17 +158,38 @@ where validation_info: &'_ ValidationInfo<'b>, ) -> CustomResult<()> { let store = Self::init_store(validation_info)?; - let exec_info = ExecutionInfo::new( - module_name, - validation_info.wasm, - validation_info.types.clone(), - store, - ); + let exec_info = ExecutionInfo::new(module_name, validation_info.types.clone(), store); self.module_map .insert(module_name.to_string(), self.modules.len()); self.modules.push(exec_info); + let local_sidetables = validation_info + .func_blocks + .iter() + .map(|block| block.1.clone()); + + // In order to be able to index the `sidetables` vec from the + // `StateData` using the function index, we need to insert some blank + // sidetables for the imported functions. An alternative solution + // would've been to offset the indexing, this was just faster to + // implement. + let dummy_sidetables = + validation_info + .imports + .iter() + .filter_map(|import| match &import.desc { + ImportDesc::Func(_type_idx) => Some(Sidetable::new()), + _ => None, + }); + + let sidetables = dummy_sidetables + .chain(local_sidetables) + .collect::>(); + + self.state_data + .push(StateData::new(validation_info.wasm, sidetables)); + self.lut = Lut::new(&self.modules, &self.module_map); Ok(()) @@ -222,6 +248,7 @@ where // Run the interpreter run( &mut self.modules, + &mut self.state_data, &mut current_module_idx, self.lut.as_ref().ok_or(RuntimeError::UnmetImport)?, &mut stack, @@ -285,6 +312,7 @@ where // Run the interpreter run( &mut self.modules, + &mut self.state_data, &mut currrent_module_idx, self.lut.as_ref().ok_or(RuntimeError::UnmetImport)?, &mut stack, @@ -441,7 +469,7 @@ where ty: *ty, locals, code_expr, - // TODO fix this ugly clone + // TODO: Not used any more. Should we remove it? sidetable: sidetable.clone(), }) }); @@ -551,11 +579,29 @@ where }) .collect(); - let mut memory_instances: Vec = validation_info - .memories - .iter() - .map(|ty| MemInst::new(*ty)) - .collect(); + let mut memory_instances: Vec = { + let local_mems = validation_info + .memories + .iter() + .map(|ty| MemInst::Local(LocalMemInst::new(*ty))); + + let imported_mems = + validation_info + .imports + .iter() + .filter_map(|import| match &import.desc { + ImportDesc::Mem(mem_type) => Some(MemInst::Imported(ImportedMemInst { + ty: *mem_type, + module_name: import.module_name.clone(), + function_name: import.name.clone(), + })), + _ => None, + }); + + imported_mems.chain(local_mems).collect() + }; + + // TODO: assert only at most 1 memory? let data_sections: Vec = validation_info .data @@ -593,7 +639,11 @@ where _ => todo!(), }; - let mem_inst = memory_instances.get_mut(mem_idx).unwrap(); + let mem_inst = memory_instances + .get_mut(mem_idx) + .unwrap() + .try_into_local() // TODO: remove + .unwrap(); let len = mem_inst.data.len(); if offset as usize + d.init.len() > len { diff --git a/src/execution/store.rs b/src/execution/store.rs index 0f278bfad..33cbf6d31 100644 --- a/src/execution/store.rs +++ b/src/execution/store.rs @@ -111,13 +111,46 @@ impl TableInst { } } -pub struct MemInst { - #[allow(warnings)] +pub enum MemInst { + Local(LocalMemInst), + Imported(ImportedMemInst), +} + +pub struct LocalMemInst { pub ty: MemType, pub data: Vec, } +pub struct ImportedMemInst { + pub ty: MemType, + pub module_name: String, + pub function_name: String, +} + impl MemInst { + pub fn ty(&self) -> MemType { + match self { + MemInst::Local(f) => f.ty, + MemInst::Imported(f) => f.ty, + } + } + + pub fn try_into_local(&mut self) -> Option<&mut LocalMemInst> { + match self { + MemInst::Local(f) => Some(f), + MemInst::Imported(_) => None, + } + } + + pub fn try_into_imported(&mut self) -> Option<&mut ImportedMemInst> { + match self { + MemInst::Local(_) => None, + MemInst::Imported(f) => Some(f), + } + } +} + +impl LocalMemInst { pub fn new(ty: MemType) -> Self { let initial_size = (crate::Limits::MEM_PAGE_SIZE as usize) * ty.limits.min as usize; diff --git a/src/validation/code.rs b/src/validation/code.rs index 55d63e66a..4a8ca195e 100644 --- a/src/validation/code.rs +++ b/src/validation/code.rs @@ -26,6 +26,7 @@ pub fn validate_code_section( num_imported_funcs: usize, globals: &[Global], memories: &[MemType], + _num_imported_memories: usize, data_count: &Option, tables: &[TableType], elements: &[ElemType], diff --git a/src/validation/mod.rs b/src/validation/mod.rs index bdfd49465..d0f2dc910 100644 --- a/src/validation/mod.rs +++ b/src/validation/mod.rs @@ -111,14 +111,32 @@ pub fn validate(wasm: &[u8]) -> Result { while (skip_section(&mut wasm, &mut header)?).is_some() {} - let memories = handle_section(&mut wasm, &mut header, SectionTy::Memory, |wasm, _| { + let local_memories = handle_section(&mut wasm, &mut header, SectionTy::Memory, |wasm, _| { wasm.read_vec(MemType::read) })? .unwrap_or_default(); - if memories.len() > 1 { + if local_memories.len() > 1 { return Err(Error::MoreThanOneMemory); } + let imported_memories = imports + .iter() + .filter_map(|import| match &import.desc { + ImportDesc::Mem(mem_type) => Some(*mem_type), + _ => None, + }) + .collect::>(); + + if imported_memories.len() > 1 { + return Err(Error::MoreThanOneMemory); + } + + let all_memories = imported_memories + .iter() + .chain(local_memories.iter()) + .cloned() + .collect::>(); + while (skip_section(&mut wasm, &mut header)?).is_some() {} let globals = handle_section(&mut wasm, &mut header, SectionTy::Global, |wasm, h| { @@ -178,7 +196,8 @@ pub fn validate(wasm: &[u8]) -> Result { &all_functions, imported_functions.count(), &globals, - &memories, + &all_memories, + imported_memories.len(), &data_count, &tables, &elements, @@ -219,7 +238,7 @@ pub fn validate(wasm: &[u8]) -> Result { imports, functions: local_functions, tables, - memories, + memories: local_memories, globals, exports, func_blocks: func_blocks_sidetables, diff --git a/tests/imports.rs b/tests/imports.rs index 1ec113fea..ddde92975 100644 --- a/tests/imports.rs +++ b/tests/imports.rs @@ -1,3 +1,4 @@ +use log::trace; use wasm::{validate, RuntimeError, RuntimeInstance, DEFAULT_MODULE}; const UNMET_IMPORTS: &str = r#" @@ -148,3 +149,89 @@ pub fn run_cyclical() { // Currently, this passes since we don't allow chained imports. assert!(instance.invoke::<(), i32>(&run, ()).unwrap_err() == wasm::RuntimeError::UnmetImport); } + +const EXPORT_MEMORY: &str = r#" +(module + ;; Initialize with 1 page, maximum of 2 pages. + (memory (export "shared_memory") 1 2) +) +"#; + +const USE_MEMORY: &str = r#" +(module + (import "env" "shared_memory" (memory 1)) + + (func (export "store_i32") (param $offset i32) (param $value i32) + local.get $offset + local.get $value + i32.store + ) + + (func (export "load_i32") (param $offset i32) (result i32) + local.get $offset + i32.load + ) + + (func (export "memory_copy") + (param $source i32) (param $dest i32) (param $size i32) + local.get $dest ;; destination offset + local.get $source ;; source offset + local.get $size ;; size in bytes + memory.copy + ) +) +"#; + +#[test_log::test] +pub fn run_memory() { + let wasm_bytes = wat::parse_str(USE_MEMORY).unwrap(); + let validation_info = validate(&wasm_bytes).expect("validation failed"); + let mut instance = + RuntimeInstance::new_named("base", &validation_info).expect("instantiation failed"); + + let wasm_bytes = wat::parse_str(EXPORT_MEMORY).unwrap(); + let validation_info = validate(&wasm_bytes).expect("validation failed"); + instance + .add_module("env", &validation_info) + .expect("Successful instantiation"); + + let store_i32 = instance.get_function_by_name("base", "store_i32").unwrap(); + let load_i32 = instance.get_function_by_name("base", "load_i32").unwrap(); + let memory_copy = instance + .get_function_by_name("base", "memory_copy") + .unwrap(); + + trace!( + "{:?}", + &instance.modules[1].store.mems[0] + .try_into_local() + .unwrap() + .data[0..16] + ); + + let _: () = instance.invoke(&store_i32, (0, 123)).unwrap(); + let res: i32 = instance.invoke(&load_i32, 0).unwrap(); + + trace!( + "{:?}", + &instance.modules[1].store.mems[0] + .try_into_local() + .unwrap() + .data[0..16] + ); + assert_eq!(res, 123); + + let _: () = instance.invoke(&memory_copy, (0, 4, 4)).unwrap(); + let res: i32 = instance.invoke(&load_i32, 0).unwrap(); + let res2: i32 = instance.invoke(&load_i32, 4).unwrap(); + + trace!( + "{:?}", + &instance.modules[1].store.mems[0] + .try_into_local() + .unwrap() + .data[0..16] + ); + assert_eq!(res, 123); + assert_eq!(res2, 123); +} diff --git a/tests/memory_fill.rs b/tests/memory_fill.rs index 4c61f5e70..355930858 100644 --- a/tests/memory_fill.rs +++ b/tests/memory_fill.rs @@ -43,8 +43,8 @@ fn memory_fill() { let fill = get_func!(i, "fill"); i.invoke::<(), ()>(fill, ()).unwrap(); - let mem = &i.modules[0].store.mems[0]; - assert!(mem.data.as_slice()[0..105] + let mem = &mut i.modules[0].store.mems[0]; + assert!(mem.try_into_local().unwrap().data.as_slice()[0..105] .eq_ignore_ascii_case(&vec![vec![217u8; 100], vec![0u8; 5]].concat())) }