From f26a0caadab5da373c00ed1269c98d58fec3dec0 Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Sun, 28 Jan 2024 13:46:00 +0100 Subject: feat: improve memory perf + basic bulk memory access Signed-off-by: Henry Gressmann --- crates/parser/src/conversion.rs | 6 + crates/tinywasm/Cargo.toml | 1 + crates/tinywasm/src/lib.rs | 2 +- crates/tinywasm/src/runtime/interpreter/macros.rs | 18 +- crates/tinywasm/src/runtime/interpreter/mod.rs | 38 +- crates/tinywasm/src/runtime/stack/call_stack.rs | 9 +- crates/tinywasm/src/runtime/stack/value_stack.rs | 3 +- crates/tinywasm/src/runtime/value.rs | 1 + crates/tinywasm/src/store.rs | 754 ---------------------- crates/tinywasm/src/store/memory.rs | 326 ++++++++++ crates/tinywasm/src/store/mod.rs | 632 ++++++++++++++++++ crates/tinywasm/tests/generated/2.0.csv | 2 +- crates/types/src/instructions.rs | 8 +- 13 files changed, 1024 insertions(+), 776 deletions(-) delete mode 100644 crates/tinywasm/src/store.rs create mode 100644 crates/tinywasm/src/store/memory.rs create mode 100644 crates/tinywasm/src/store/mod.rs (limited to 'crates') diff --git a/crates/parser/src/conversion.rs b/crates/parser/src/conversion.rs index 835842f..79069e1 100644 --- a/crates/parser/src/conversion.rs +++ b/crates/parser/src/conversion.rs @@ -332,6 +332,12 @@ pub fn process_operators<'a>( GlobalSet { global_index } => Instruction::GlobalSet(global_index), MemorySize { mem, mem_byte } => Instruction::MemorySize(mem, mem_byte), MemoryGrow { mem, mem_byte } => Instruction::MemoryGrow(mem, mem_byte), + + MemoryCopy { dst_mem, src_mem } => Instruction::MemoryCopy(src_mem, dst_mem), + MemoryFill { mem } => Instruction::MemoryFill(mem), + MemoryInit { data_index, mem } => Instruction::MemoryInit(data_index, mem), + DataDrop { data_index } => Instruction::DataDrop(data_index), + I32Const { value } => Instruction::I32Const(value), I64Const { value } => Instruction::I64Const(value), F32Const { value } => Instruction::F32Const(f32::from_bits(value.bits())), diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index f1a3242..a9e24bf 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -33,6 +33,7 @@ default=["std", "parser", "logging"] logging=["log", "tinywasm-types/logging", "tinywasm-parser?/logging"] std=["tinywasm-parser?/std", "tinywasm-types/std"] parser=["tinywasm-parser"] +unsafe=[] [[test]] name="generate-charts" diff --git a/crates/tinywasm/src/lib.rs b/crates/tinywasm/src/lib.rs index bc64ac4..d786ab8 100644 --- a/crates/tinywasm/src/lib.rs +++ b/crates/tinywasm/src/lib.rs @@ -1,11 +1,11 @@ #![no_std] -#![forbid(unsafe_code)] #![doc(test( no_crate_inject, attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_assignments, unused_variables)) ))] #![warn(missing_docs, missing_debug_implementations, rust_2018_idioms, unreachable_pub)] #![cfg_attr(nightly, feature(error_in_core))] +#![cfg_attr(not(feature = "unsafe"), deny(unsafe_code))] //! A tiny WebAssembly Runtime written in Rust //! diff --git a/crates/tinywasm/src/runtime/interpreter/macros.rs b/crates/tinywasm/src/runtime/interpreter/macros.rs index eba866c..a769b12 100644 --- a/crates/tinywasm/src/runtime/interpreter/macros.rs +++ b/crates/tinywasm/src/runtime/interpreter/macros.rs @@ -14,14 +14,14 @@ macro_rules! mem_load { // TODO: there could be a lot of performance improvements here let mem_idx = $module.resolve_mem_addr($arg.mem_addr); let mem = $store.get_mem(mem_idx as usize)?; + let mem_ref = mem.borrow_mut(); let addr = $stack.values.pop()?.raw_value(); - let addr = $arg.offset.checked_add(addr).ok_or_else(|| { Error::Trap(crate::Trap::MemoryOutOfBounds { offset: $arg.offset as usize, len: core::mem::size_of::<$load_type>(), - max: mem.borrow().max_pages(), + max: mem_ref.max_pages(), }) })?; @@ -29,18 +29,14 @@ macro_rules! mem_load { Error::Trap(crate::Trap::MemoryOutOfBounds { offset: $arg.offset as usize, len: core::mem::size_of::<$load_type>(), - max: mem.borrow().max_pages(), + max: mem_ref.max_pages(), }) })?; - let val: [u8; core::mem::size_of::<$load_type>()] = { - let mem = mem.borrow_mut(); - let val = mem.load(addr, $arg.align as usize, core::mem::size_of::<$load_type>())?; - val.try_into().expect("slice with incorrect length") - }; - - let loaded_value = <$load_type>::from_le_bytes(val); - $stack.values.push((loaded_value as $target_type).into()); + const LEN: usize = core::mem::size_of::<$load_type>(); + let val = mem_ref.load_as::(addr, $arg.align as usize)?; + // let loaded_value = mem_ref.load_as::<$load_type>(addr, $arg.align as usize)?; + $stack.values.push((val as $target_type).into()); }}; } diff --git a/crates/tinywasm/src/runtime/interpreter/mod.rs b/crates/tinywasm/src/runtime/interpreter/mod.rs index fdbe697..3c3b776 100644 --- a/crates/tinywasm/src/runtime/interpreter/mod.rs +++ b/crates/tinywasm/src/runtime/interpreter/mod.rs @@ -23,7 +23,7 @@ use macros::*; use traits::*; impl InterpreterRuntime { - #[inline(always)] // a small 2-3% performance improvement in some cases + // #[inline(always)] // a small 2-3% performance improvement in some cases pub(crate) fn exec(&self, store: &mut Store, stack: &mut Stack) -> Result<()> { // The current call frame, gets updated inside of exec_one let mut cf = stack.call_stack.pop()?; @@ -388,6 +388,42 @@ fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &M } } + // Bulk memory operations + MemoryCopy(from, to) => { + let size = stack.values.pop_t::()?; + let src = stack.values.pop_t::()?; + let dst = stack.values.pop_t::()?; + + let mem = store.get_mem(module.resolve_mem_addr(*from) as usize)?; + let mut mem = mem.borrow_mut(); + + if from == to { + // copy within the same memory + mem.copy_within(dst as usize, src as usize, size as usize)?; + } else { + // copy between two memories + let mem2 = store.get_mem(module.resolve_mem_addr(*to) as usize)?; + let mut mem2 = mem2.borrow_mut(); + mem2.copy_from_slice(dst as usize, mem.load(src as usize, 0, size as usize)?)?; + } + } + + MemoryFill(addr) => { + let size = stack.values.pop_t::()?; + let val = stack.values.pop_t::()?; + let dst = stack.values.pop_t::()?; + + let mem = store.get_mem(module.resolve_mem_addr(*addr) as usize)?; + let mut mem = mem.borrow_mut(); + mem.fill(dst as usize, size as usize, val as u8)?; + } + + // MemoryInit(data_index, mem_index) => {} + // DataDrop(data_index) => { + // // let data_idx = module.resolve_data_addr(*data_index); + // // let data = store.get_data(data_idx as usize)?; + // // data.borrow_mut().drop()?; + // } I32Store(arg) => mem_store!(i32, arg, stack, store, module), I64Store(arg) => mem_store!(i64, arg, stack, store, module), F32Store(arg) => mem_store!(f32, arg, stack, store, module), diff --git a/crates/tinywasm/src/runtime/stack/call_stack.rs b/crates/tinywasm/src/runtime/stack/call_stack.rs index dbe420d..dcbfcac 100644 --- a/crates/tinywasm/src/runtime/stack/call_stack.rs +++ b/crates/tinywasm/src/runtime/stack/call_stack.rs @@ -58,7 +58,6 @@ pub(crate) struct CallFrame { } impl CallFrame { - // TOOD: perf: this is called a lot, and it's a bit slow /// Push a new label to the label stack and ensure the stack has the correct values pub(crate) fn enter_label(&mut self, label_frame: LabelFrame, stack: &mut super::ValueStack) { if label_frame.params > 0 { @@ -77,14 +76,14 @@ impl CallFrame { // will increment it by 1 since we're changing the "current" instr_ptr match break_to.ty { BlockType::Loop => { + // this is a loop, so we want to jump back to the start of the loop self.instr_ptr = break_to.instr_ptr; + // We also want to push the params to the stack + value_stack.break_to(break_to.stack_ptr, break_to.params); + // check if we're breaking to the loop if break_to_relative != 0 { - // this is a loop, so we want to jump back to the start of the loop - // We also want to push the params to the stack - value_stack.break_to(break_to.stack_ptr, break_to.params); - // we also want to trim the label stack to the loop (but not including the loop) self.labels.truncate(self.labels.len() - break_to_relative as usize); return Some(()); diff --git a/crates/tinywasm/src/runtime/stack/value_stack.rs b/crates/tinywasm/src/runtime/stack/value_stack.rs index 3c5f48b..9b8f82d 100644 --- a/crates/tinywasm/src/runtime/stack/value_stack.rs +++ b/crates/tinywasm/src/runtime/stack/value_stack.rs @@ -1,7 +1,6 @@ use core::ops::Range; use crate::{cold, runtime::RawWasmValue, unlikely, Error, Result}; -use alloc::vec; use alloc::vec::Vec; use tinywasm_types::{ValType, WasmValue}; @@ -15,7 +14,7 @@ pub(crate) struct ValueStack { impl Default for ValueStack { fn default() -> Self { - Self { stack: vec![RawWasmValue::default(); MIN_VALUE_STACK_SIZE] } + Self { stack: Vec::with_capacity(MIN_VALUE_STACK_SIZE) } } } diff --git a/crates/tinywasm/src/runtime/value.rs b/crates/tinywasm/src/runtime/value.rs index 329a60b..5341361 100644 --- a/crates/tinywasm/src/runtime/value.rs +++ b/crates/tinywasm/src/runtime/value.rs @@ -18,6 +18,7 @@ impl Debug for RawWasmValue { } impl RawWasmValue { + #[inline(always)] pub fn raw_value(&self) -> u64 { self.0 } diff --git a/crates/tinywasm/src/store.rs b/crates/tinywasm/src/store.rs deleted file mode 100644 index a4f9b70..0000000 --- a/crates/tinywasm/src/store.rs +++ /dev/null @@ -1,754 +0,0 @@ -use crate::log; -use alloc::{boxed::Box, format, rc::Rc, string::ToString, vec, vec::Vec}; -use core::{ - cell::RefCell, - sync::atomic::{AtomicUsize, Ordering}, -}; -use tinywasm_types::*; - -use crate::{ - runtime::{self, InterpreterRuntime, RawWasmValue}, - Error, Function, ModuleInstance, Result, Trap, -}; - -// global store id counter -static STORE_ID: AtomicUsize = AtomicUsize::new(0); - -/// Global state that can be manipulated by WebAssembly programs -/// -/// Data should only be addressable by the module that owns it -/// -/// Note that the state doesn't do any garbage collection - so it will grow -/// indefinitely if you keep adding modules to it. When calling temporary -/// functions, you should create a new store and then drop it when you're done (e.g. in a request handler) -/// -/// See -#[derive(Debug)] -pub struct Store { - id: usize, - module_instances: Vec, - module_instance_count: usize, - - pub(crate) data: StoreData, - pub(crate) runtime: Runtime, -} - -#[derive(Debug, Clone, Copy)] -pub(crate) enum Runtime { - Default, -} - -impl Store { - /// Create a new store - pub fn new() -> Self { - Self::default() - } - - /// Get a module instance by the internal id - pub fn get_module_instance(&self, addr: ModuleInstanceAddr) -> Option<&ModuleInstance> { - self.module_instances.get(addr as usize) - } - - pub(crate) fn get_module_instance_raw(&self, addr: ModuleInstanceAddr) -> ModuleInstance { - self.module_instances[addr as usize].clone() - } - - /// Create a new store with the given runtime - pub(crate) fn runtime(&self) -> runtime::InterpreterRuntime { - match self.runtime { - Runtime::Default => InterpreterRuntime::default(), - } - } -} - -impl PartialEq for Store { - fn eq(&self, other: &Self) -> bool { - self.id == other.id - } -} - -impl Default for Store { - fn default() -> Self { - let id = STORE_ID.fetch_add(1, Ordering::Relaxed); - - Self { - id, - module_instances: Vec::new(), - module_instance_count: 0, - data: StoreData::default(), - runtime: Runtime::Default, - } - } -} - -#[derive(Debug, Default)] -/// Global state that can be manipulated by WebAssembly programs -/// -/// Data should only be addressable by the module that owns it -/// See -pub(crate) struct StoreData { - pub(crate) funcs: Vec, - pub(crate) tables: Vec>>, - pub(crate) memories: Vec>>, - pub(crate) globals: Vec>>, - pub(crate) elements: Vec, - pub(crate) datas: Vec, -} - -impl Store { - /// Get the store's ID (unique per process) - pub fn id(&self) -> usize { - self.id - } - - pub(crate) fn next_module_instance_idx(&self) -> ModuleInstanceAddr { - self.module_instance_count as ModuleInstanceAddr - } - - pub(crate) fn add_instance(&mut self, instance: ModuleInstance) -> Result<()> { - assert!(instance.id() == self.module_instance_count as ModuleInstanceAddr); - self.module_instances.push(instance); - self.module_instance_count += 1; - Ok(()) - } - - /// Add functions to the store, returning their addresses in the store - pub(crate) fn init_funcs( - &mut self, - funcs: Vec<(u32, WasmFunction)>, - idx: ModuleInstanceAddr, - ) -> Result> { - let func_count = self.data.funcs.len(); - let mut func_addrs = Vec::with_capacity(func_count); - - for (i, (_, func)) in funcs.into_iter().enumerate() { - self.data.funcs.push(FunctionInstance { func: Function::Wasm(Rc::new(func)), owner: idx }); - func_addrs.push((i + func_count) as FuncAddr); - } - - Ok(func_addrs) - } - - /// Add tables to the store, returning their addresses in the store - pub(crate) fn init_tables(&mut self, tables: Vec, idx: ModuleInstanceAddr) -> Result> { - let table_count = self.data.tables.len(); - let mut table_addrs = Vec::with_capacity(table_count); - for (i, table) in tables.into_iter().enumerate() { - self.data.tables.push(Rc::new(RefCell::new(TableInstance::new(table, idx)))); - table_addrs.push((i + table_count) as TableAddr); - } - Ok(table_addrs) - } - - /// Add memories to the store, returning their addresses in the store - pub(crate) fn init_memories(&mut self, memories: Vec, idx: ModuleInstanceAddr) -> Result> { - let mem_count = self.data.memories.len(); - let mut mem_addrs = Vec::with_capacity(mem_count); - for (i, mem) in memories.into_iter().enumerate() { - if let MemoryArch::I64 = mem.arch { - return Err(Error::UnsupportedFeature("64-bit memories".to_string())); - } - log::info!("adding memory: {:?}", mem); - self.data.memories.push(Rc::new(RefCell::new(MemoryInstance::new(mem, idx)))); - - mem_addrs.push((i + mem_count) as MemAddr); - } - Ok(mem_addrs) - } - - /// Add globals to the store, returning their addresses in the store - pub(crate) fn init_globals( - &mut self, - mut imported_globals: Vec, - new_globals: Vec, - func_addrs: &[FuncAddr], - idx: ModuleInstanceAddr, - ) -> Result> { - let global_count = self.data.globals.len(); - imported_globals.reserve_exact(new_globals.len()); - let mut global_addrs = imported_globals; - - for (i, global) in new_globals.iter().enumerate() { - self.data.globals.push(Rc::new(RefCell::new(GlobalInstance::new( - global.ty, - self.eval_const(&global.init, &global_addrs, func_addrs)?, - idx, - )))); - global_addrs.push((i + global_count) as Addr); - } - - Ok(global_addrs) - } - - fn elem_addr(&self, item: &ElementItem, globals: &[Addr], funcs: &[FuncAddr]) -> Result> { - let res = match item { - ElementItem::Func(addr) | ElementItem::Expr(ConstInstruction::RefFunc(addr)) => { - Some(funcs.get(*addr as usize).copied().ok_or_else(|| { - Error::Other(format!("function {} not found. This should have been caught by the validator", addr)) - })?) - } - ElementItem::Expr(ConstInstruction::RefNull(_ty)) => None, - ElementItem::Expr(ConstInstruction::GlobalGet(addr)) => { - let addr = globals.get(*addr as usize).copied().ok_or_else(|| { - Error::Other(format!("global {} not found. This should have been caught by the validator", addr)) - })?; - - let global = self.data.globals[addr as usize].clone(); - let val = i64::from(global.borrow().value); - log::error!("global: {}", val); - if val < 0 { - // the global is actually a null reference - None - } else { - Some(val as u32) - } - } - _ => return Err(Error::UnsupportedFeature(format!("const expression other than ref: {:?}", item))), - }; - - Ok(res) - } - - /// Add elements to the store, returning their addresses in the store - /// Should be called after the tables have been added - pub(crate) fn init_elements( - &mut self, - table_addrs: &[TableAddr], - func_addrs: &[FuncAddr], - global_addrs: &[Addr], - elements: &[Element], - idx: ModuleInstanceAddr, - ) -> Result<(Box<[Addr]>, Option)> { - let elem_count = self.data.elements.len(); - let mut elem_addrs = Vec::with_capacity(elem_count); - for (i, element) in elements.iter().enumerate() { - let init = element - .items - .iter() - .map(|item| Ok(TableElement::from(self.elem_addr(item, global_addrs, func_addrs)?))) - .collect::>>()?; - - log::error!("element kind: {:?}", element.kind); - - let items = match element.kind { - // doesn't need to be initialized, can be initialized lazily using the `table.init` instruction - ElementKind::Passive => Some(init), - - // this one is not available to the runtime but needs to be initialized to declare references - ElementKind::Declared => { - // a. Execute the instruction elm.drop i - None - } - - // this one is active, so we need to initialize it (essentially a `table.init` instruction) - ElementKind::Active { offset, table } => { - let offset = self.eval_i32_const(&offset)?; - let table_addr = table_addrs - .get(table as usize) - .copied() - .ok_or_else(|| Error::Other(format!("table {} not found for element {}", table, i)))?; - - if let Some(table) = self.data.tables.get_mut(table_addr as usize) { - // In wasm 2.0, it's possible to call a function that hasn't been instantiated yet, - // when using a partially initialized active element segments. - // This isn't mentioned in the spec, but the "unofficial" testsuite has a test for it: - // https://github.com/WebAssembly/testsuite/blob/5a1a590603d81f40ef471abba70a90a9ae5f4627/linking.wast#L264-L276 - // I have NO IDEA why this is allowed, but it is. - if let Err(Error::Trap(trap)) = table.borrow_mut().init_raw(offset, &init) { - return Ok((elem_addrs.into_boxed_slice(), Some(trap))); - } - } else { - return Err(Error::Other(format!("table {} not found for element {}", table, i))); - } - - // f. Execute the instruction elm.drop i - None - } - }; - - self.data.elements.push(ElementInstance::new(element.kind, idx, items)); - elem_addrs.push((i + elem_count) as Addr); - } - - // this should be optimized out by the compiler - Ok((elem_addrs.into_boxed_slice(), None)) - } - - /// Add data to the store, returning their addresses in the store - pub(crate) fn init_datas( - &mut self, - mem_addrs: &[MemAddr], - datas: Vec, - idx: ModuleInstanceAddr, - ) -> Result<(Box<[Addr]>, Option)> { - let data_count = self.data.datas.len(); - let mut data_addrs = Vec::with_capacity(data_count); - for (i, data) in datas.into_iter().enumerate() { - use tinywasm_types::DataKind::*; - match data.kind { - Active { mem: mem_addr, offset } => { - // a. Assert: memidx == 0 - if mem_addr != 0 { - return Err(Error::UnsupportedFeature("data segments for non-zero memories".to_string())); - } - - let mem_addr = mem_addrs - .get(mem_addr as usize) - .copied() - .ok_or_else(|| Error::Other(format!("memory {} not found for data segment {}", mem_addr, i)))?; - - let offset = self.eval_i32_const(&offset)?; - - let mem = - self.data.memories.get_mut(mem_addr as usize).ok_or_else(|| { - Error::Other(format!("memory {} not found for data segment {}", mem_addr, i)) - })?; - - // See comment for active element sections in the function above why we need to do this here - if let Err(Error::Trap(trap)) = - mem.borrow_mut().store(offset as usize, 0, &data.data, data.data.len()) - { - return Ok((data_addrs.into_boxed_slice(), Some(trap))); - } - - // drop the data - continue; - } - Passive => {} - } - - self.data.datas.push(DataInstance::new(data.data.to_vec(), idx)); - data_addrs.push((i + data_count) as Addr); - } - - // this should be optimized out by the compiler - Ok((data_addrs.into_boxed_slice(), None)) - } - - pub(crate) fn add_global(&mut self, ty: GlobalType, value: RawWasmValue, idx: ModuleInstanceAddr) -> Result { - self.data.globals.push(Rc::new(RefCell::new(GlobalInstance::new(ty, value, idx)))); - Ok(self.data.globals.len() as Addr - 1) - } - - pub(crate) fn add_table(&mut self, table: TableType, idx: ModuleInstanceAddr) -> Result { - self.data.tables.push(Rc::new(RefCell::new(TableInstance::new(table, idx)))); - Ok(self.data.tables.len() as TableAddr - 1) - } - - pub(crate) fn add_mem(&mut self, mem: MemoryType, idx: ModuleInstanceAddr) -> Result { - if let MemoryArch::I64 = mem.arch { - return Err(Error::UnsupportedFeature("64-bit memories".to_string())); - } - self.data.memories.push(Rc::new(RefCell::new(MemoryInstance::new(mem, idx)))); - Ok(self.data.memories.len() as MemAddr - 1) - } - - pub(crate) fn add_func(&mut self, func: Function, idx: ModuleInstanceAddr) -> Result { - self.data.funcs.push(FunctionInstance { func, owner: idx }); - Ok(self.data.funcs.len() as FuncAddr - 1) - } - - /// Evaluate a constant expression, only supporting i32 globals and i32.const - pub(crate) fn eval_i32_const(&self, const_instr: &tinywasm_types::ConstInstruction) -> Result { - use tinywasm_types::ConstInstruction::*; - let val = match const_instr { - I32Const(i) => *i, - GlobalGet(addr) => { - let addr = *addr as usize; - let global = self.data.globals[addr].clone(); - let val = global.borrow().value; - i32::from(val) - } - _ => return Err(Error::Other("expected i32".to_string())), - }; - Ok(val) - } - - /// Evaluate a constant expression - pub(crate) fn eval_const( - &self, - const_instr: &tinywasm_types::ConstInstruction, - module_global_addrs: &[Addr], - module_func_addrs: &[FuncAddr], - ) -> Result { - use tinywasm_types::ConstInstruction::*; - let val = match const_instr { - F32Const(f) => RawWasmValue::from(*f), - F64Const(f) => RawWasmValue::from(*f), - I32Const(i) => RawWasmValue::from(*i), - I64Const(i) => RawWasmValue::from(*i), - GlobalGet(addr) => { - let addr = module_global_addrs.get(*addr as usize).copied().ok_or_else(|| { - Error::Other(format!("global {} not found. This should have been caught by the validator", addr)) - })?; - - let global = - self.data.globals.get(addr as usize).expect("global not found. This should be unreachable"); - - global.borrow().value - } - RefNull(t) => RawWasmValue::from(t.default_value()), - RefFunc(idx) => RawWasmValue::from(module_func_addrs.get(*idx as usize).copied().ok_or_else(|| { - Error::Other(format!("function {} not found. This should have been caught by the validator", idx)) - })?), - }; - Ok(val) - } - - /// Get the function at the actual index in the store - pub(crate) fn get_func(&self, addr: usize) -> Result<&FunctionInstance> { - self.data.funcs.get(addr).ok_or_else(|| Error::Other(format!("function {} not found", addr))) - } - - /// Get the memory at the actual index in the store - pub(crate) fn get_mem(&self, addr: usize) -> Result<&Rc>> { - self.data.memories.get(addr).ok_or_else(|| Error::Other(format!("memory {} not found", addr))) - } - - /// Get the table at the actual index in the store - pub(crate) fn get_table(&self, addr: usize) -> Result<&Rc>> { - self.data.tables.get(addr).ok_or_else(|| Error::Other(format!("table {} not found", addr))) - } - - /// Get the element at the actual index in the store - pub(crate) fn get_elem(&self, addr: usize) -> Result<&ElementInstance> { - self.data.elements.get(addr).ok_or_else(|| Error::Other(format!("element {} not found", addr))) - } - - /// Get the global at the actual index in the store - pub(crate) fn get_global(&self, addr: usize) -> Result<&Rc>> { - self.data.globals.get(addr).ok_or_else(|| Error::Other(format!("global {} not found", addr))) - } - - /// Get the global at the actual index in the store - pub fn get_global_val(&self, addr: usize) -> Result { - self.data - .globals - .get(addr) - .ok_or_else(|| Error::Other(format!("global {} not found", addr))) - .map(|global| global.borrow().value) - } - - pub(crate) fn set_global_val(&mut self, addr: usize, value: RawWasmValue) -> Result<()> { - self.data - .globals - .get(addr) - .ok_or_else(|| Error::Other(format!("global {} not found", addr))) - .map(|global| global.borrow_mut().value = value) - } -} - -#[derive(Debug, Clone)] -/// A WebAssembly Function Instance -/// -/// See -pub(crate) struct FunctionInstance { - pub(crate) func: Function, - pub(crate) owner: ModuleInstanceAddr, // index into store.module_instances, none for host functions -} - -#[derive(Debug, Clone, Copy)] -pub(crate) enum TableElement { - Uninitialized, - Initialized(Addr), -} - -impl From> for TableElement { - fn from(addr: Option) -> Self { - match addr { - None => TableElement::Uninitialized, - Some(addr) => TableElement::Initialized(addr), - } - } -} - -impl TableElement { - pub(crate) fn addr(&self) -> Option { - match self { - TableElement::Uninitialized => None, - TableElement::Initialized(addr) => Some(*addr), - } - } - - pub(crate) fn map Addr>(self, f: F) -> Self { - match self { - TableElement::Uninitialized => TableElement::Uninitialized, - TableElement::Initialized(addr) => TableElement::Initialized(f(addr)), - } - } -} - -const MAX_TABLE_SIZE: u32 = 10000000; - -/// A WebAssembly Table Instance -/// -/// See -#[derive(Debug)] -pub(crate) struct TableInstance { - pub(crate) elements: Vec, - pub(crate) kind: TableType, - pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances -} - -impl TableInstance { - pub(crate) fn new(kind: TableType, owner: ModuleInstanceAddr) -> Self { - Self { elements: vec![TableElement::Uninitialized; kind.size_initial as usize], kind, _owner: owner } - } - - pub(crate) fn get_wasm_val(&self, addr: usize) -> Result { - let val = self.get(addr)?.addr(); - - Ok(match self.kind.element_type { - ValType::RefFunc => val.map(WasmValue::RefFunc).unwrap_or(WasmValue::RefNull(ValType::RefFunc)), - ValType::RefExtern => val.map(WasmValue::RefExtern).unwrap_or(WasmValue::RefNull(ValType::RefExtern)), - _ => unimplemented!("unsupported table type: {:?}", self.kind.element_type), - }) - } - - pub(crate) fn get(&self, addr: usize) -> Result<&TableElement> { - self.elements.get(addr).ok_or_else(|| Error::Trap(Trap::UndefinedElement { index: addr })) - } - - pub(crate) fn set(&mut self, table_idx: usize, value: Addr) -> Result<()> { - self.grow_to_fit(table_idx + 1).map(|_| self.elements[table_idx] = TableElement::Initialized(value)) - } - - pub(crate) fn grow_to_fit(&mut self, new_size: usize) -> Result<()> { - if new_size > self.elements.len() { - if new_size > self.kind.size_max.unwrap_or(MAX_TABLE_SIZE) as usize { - return Err(crate::Trap::TableOutOfBounds { offset: new_size, len: 1, max: self.elements.len() }.into()); - } - - self.elements.resize(new_size, TableElement::Uninitialized); - } - Ok(()) - } - - pub(crate) fn size(&self) -> i32 { - self.elements.len() as i32 - } - - fn resolve_func_ref(&self, func_addrs: &[u32], addr: Addr) -> Addr { - if self.kind.element_type != ValType::RefFunc { - return addr; - } - - *func_addrs - .get(addr as usize) - .expect("error initializing table: function not found. This should have been caught by the validator") - } - - // Initialize the table with the given elements - pub(crate) fn init_raw(&mut self, offset: i32, init: &[TableElement]) -> Result<()> { - let offset = offset as usize; - let end = offset.checked_add(init.len()).ok_or_else(|| { - Error::Trap(crate::Trap::TableOutOfBounds { offset, len: init.len(), max: self.elements.len() }) - })?; - - if end > self.elements.len() || end < offset { - return Err(crate::Trap::TableOutOfBounds { offset, len: init.len(), max: self.elements.len() }.into()); - } - - self.elements[offset..end].copy_from_slice(init); - log::debug!("table: {:?}", self.elements); - Ok(()) - } - - // Initialize the table with the given elements (resolves function references) - pub(crate) fn init(&mut self, func_addrs: &[u32], offset: i32, init: &[TableElement]) -> Result<()> { - let init = init.iter().map(|item| item.map(|addr| self.resolve_func_ref(func_addrs, addr))).collect::>(); - - self.init_raw(offset, &init) - } -} - -pub(crate) const PAGE_SIZE: usize = 65536; -pub(crate) const MAX_PAGES: usize = 65536; -pub(crate) const MAX_SIZE: u64 = PAGE_SIZE as u64 * MAX_PAGES as u64; - -/// A WebAssembly Memory Instance -/// -/// See -#[derive(Debug)] -pub(crate) struct MemoryInstance { - pub(crate) kind: MemoryType, - pub(crate) data: Vec, - pub(crate) page_count: usize, - pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances -} - -impl MemoryInstance { - pub(crate) fn new(kind: MemoryType, owner: ModuleInstanceAddr) -> Self { - assert!(kind.page_count_initial <= kind.page_count_max.unwrap_or(MAX_PAGES as u64)); - log::debug!("initializing memory with {} pages", kind.page_count_initial); - - Self { - kind, - data: vec![0; PAGE_SIZE * kind.page_count_initial as usize], - page_count: kind.page_count_initial as usize, - _owner: owner, - } - } - - pub(crate) fn store(&mut self, addr: usize, _align: usize, data: &[u8], len: usize) -> Result<()> { - let end = addr.checked_add(len).ok_or_else(|| { - Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len: data.len(), max: self.data.len() }) - })?; - - if end > self.data.len() || end < addr { - return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { - offset: addr, - len: data.len(), - max: self.data.len(), - })); - } - - // WebAssembly doesn't require alignment for stores - self.data[addr..end].copy_from_slice(data); - Ok(()) - } - - pub(crate) fn max_pages(&self) -> usize { - self.kind.page_count_max.unwrap_or(MAX_PAGES as u64) as usize - } - - pub(crate) fn load(&self, addr: usize, _align: usize, len: usize) -> Result<&[u8]> { - let end = addr - .checked_add(len) - .ok_or_else(|| Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len, max: self.max_pages() }))?; - - if end > self.data.len() { - return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len, max: self.data.len() })); - } - - // WebAssembly doesn't require alignment for loads - Ok(&self.data[addr..end]) - } - - pub(crate) fn page_count(&self) -> usize { - self.page_count - } - - pub(crate) fn fill(&mut self, addr: usize, len: usize, val: u8) -> Result<()> { - let end = addr - .checked_add(len) - .ok_or_else(|| Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len, max: self.data.len() }))?; - if end > self.data.len() { - return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len, max: self.data.len() })); - } - self.data[addr..end].fill(val); - Ok(()) - } - - pub(crate) fn copy_within(&mut self, dst: usize, src: usize, len: usize) -> Result<()> { - let end = src - .checked_add(len) - .ok_or_else(|| Error::Trap(crate::Trap::MemoryOutOfBounds { offset: src, len, max: self.data.len() }))?; - if end > self.data.len() || end < src { - return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: src, len, max: self.data.len() })); - } - let end = dst - .checked_add(len) - .ok_or_else(|| Error::Trap(crate::Trap::MemoryOutOfBounds { offset: dst, len, max: self.data.len() }))?; - if end > self.data.len() || end < dst { - return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: dst, len, max: self.data.len() })); - } - self.data[dst..end].copy_within(src..end, len); - Ok(()) - } - - pub(crate) fn grow(&mut self, pages_delta: i32) -> Option { - let current_pages = self.page_count(); - let new_pages = current_pages as i64 + pages_delta as i64; - - if new_pages < 0 || new_pages > MAX_PAGES as i64 { - return None; - } - - if new_pages as usize > self.max_pages() { - log::info!("memory size out of bounds: {}", new_pages); - return None; - } - - let new_size = new_pages as usize * PAGE_SIZE; - if new_size as u64 > MAX_SIZE { - return None; - } - - // Zero initialize the new pages - self.data.resize(new_size, 0); - self.page_count = new_pages as usize; - - log::debug!("memory was {} pages", current_pages); - log::debug!("memory grown by {} pages", pages_delta); - log::debug!("memory grown to {} pages", self.page_count); - - Some(current_pages.try_into().expect("memory size out of bounds, this should have been caught earlier")) - } -} - -/// A WebAssembly Global Instance -/// -/// See -#[derive(Debug)] -pub(crate) struct GlobalInstance { - pub(crate) value: RawWasmValue, - pub(crate) ty: GlobalType, - pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances -} - -impl GlobalInstance { - pub(crate) fn new(ty: GlobalType, value: RawWasmValue, owner: ModuleInstanceAddr) -> Self { - Self { ty, value, _owner: owner } - } - - pub(crate) fn get(&self) -> WasmValue { - self.value.attach_type(self.ty.ty) - } - - pub(crate) fn set(&mut self, val: WasmValue) -> Result<()> { - if val.val_type() != self.ty.ty { - return Err(Error::Other(format!( - "global type mismatch: expected {:?}, got {:?}", - self.ty.ty, - val.val_type() - ))); - } - if !self.ty.mutable { - return Err(Error::Other("global is immutable".to_string())); - } - self.value = val.into(); - Ok(()) - } -} - -/// A WebAssembly Element Instance -/// -/// See -#[derive(Debug)] -pub(crate) struct ElementInstance { - pub(crate) kind: ElementKind, - pub(crate) items: Option>, // none is the element was dropped - _owner: ModuleInstanceAddr, // index into store.module_instances -} - -impl ElementInstance { - pub(crate) fn new(kind: ElementKind, owner: ModuleInstanceAddr, items: Option>) -> Self { - Self { kind, _owner: owner, items } - } -} - -/// A WebAssembly Data Instance -/// -/// See -#[derive(Debug)] -pub(crate) struct DataInstance { - pub(crate) _data: Vec, - pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances -} - -impl DataInstance { - pub(crate) fn new(data: Vec, owner: ModuleInstanceAddr) -> Self { - Self { _data: data, _owner: owner } - } -} diff --git a/crates/tinywasm/src/store/memory.rs b/crates/tinywasm/src/store/memory.rs new file mode 100644 index 0000000..a087b1d --- /dev/null +++ b/crates/tinywasm/src/store/memory.rs @@ -0,0 +1,326 @@ +use alloc::vec; +use alloc::vec::Vec; +use tinywasm_types::{MemoryType, ModuleInstanceAddr}; + +use crate::{cold, unlikely, Error, Result}; + +pub(crate) const PAGE_SIZE: usize = 65536; +pub(crate) const MAX_PAGES: usize = 65536; +pub(crate) const MAX_SIZE: u64 = PAGE_SIZE as u64 * MAX_PAGES as u64; + +/// A WebAssembly Memory Instance +/// +/// See +#[derive(Debug)] +pub(crate) struct MemoryInstance { + pub(crate) kind: MemoryType, + pub(crate) data: Vec, + pub(crate) page_count: usize, + pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances +} + +impl MemoryInstance { + pub(crate) fn new(kind: MemoryType, owner: ModuleInstanceAddr) -> Self { + assert!(kind.page_count_initial <= kind.page_count_max.unwrap_or(MAX_PAGES as u64)); + log::debug!("initializing memory with {} pages", kind.page_count_initial); + + Self { + kind, + data: vec![0; PAGE_SIZE * kind.page_count_initial as usize], + page_count: kind.page_count_initial as usize, + _owner: owner, + } + } + + pub(crate) fn store(&mut self, addr: usize, _align: usize, data: &[u8], len: usize) -> Result<()> { + let Some(end) = addr.checked_add(len) else { + cold(); + return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { + offset: addr, + len: data.len(), + max: self.data.len(), + })); + }; + + if unlikely(end > self.data.len() || end < addr) { + return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { + offset: addr, + len: data.len(), + max: self.data.len(), + })); + } + + // WebAssembly doesn't require alignment for stores + #[cfg(not(feature = "unsafe"))] + self.data[addr..end].copy_from_slice(data); + + #[cfg(feature = "unsafe")] + // SAFETY: we checked that `end` is in bounds above, this is the same as `copy_from_slice` + // src must is for reads of count * size_of::() bytes. + // dst must is for writes of count * size_of::() bytes. + // Both src and dst are properly aligned. + // The region of memory beginning at src does not overlap with the region of memory beginning at dst with the same size. + unsafe { + core::ptr::copy_nonoverlapping(data.as_ptr(), self.data[addr..end].as_mut_ptr(), len); + } + + Ok(()) + } + + pub(crate) fn max_pages(&self) -> usize { + self.kind.page_count_max.unwrap_or(MAX_PAGES as u64) as usize + } + + pub(crate) fn load(&self, addr: usize, _align: usize, len: usize) -> Result<&[u8]> { + let Some(end) = addr.checked_add(len) else { + cold(); + return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len, max: self.data.len() })); + }; + + if unlikely(end > self.data.len() || end < addr) { + return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len, max: self.data.len() })); + } + + Ok(&self.data[addr..end]) + } + + // this is a workaround since we can't use generic const expressions yet (https://github.com/rust-lang/rust/issues/76560) + pub(crate) fn load_as>(&self, addr: usize, _align: usize) -> Result { + let Some(end) = addr.checked_add(SIZE) else { + cold(); + return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len: SIZE, max: self.max_pages() })); + }; + + if unlikely(end > self.data.len()) { + return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len: SIZE, max: self.data.len() })); + } + + #[cfg(feature = "unsafe")] + // WebAssembly doesn't require alignment for loads + // SAFETY: we checked that `end` is in bounds above. All types that implement `Into` are valid + // to load from unaligned addresses. + let val = unsafe { core::ptr::read_unaligned(self.data[addr..end].as_ptr() as *const T) }; + + #[cfg(not(feature = "unsafe"))] + let val = T::from_le_bytes(self.data[addr..end].try_into().expect("slice size mismatch")); + + Ok(val) + } + + pub(crate) fn page_count(&self) -> usize { + self.page_count + } + + pub(crate) fn fill(&mut self, addr: usize, len: usize, val: u8) -> Result<()> { + let end = addr + .checked_add(len) + .ok_or_else(|| Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len, max: self.data.len() }))?; + if unlikely(end > self.data.len()) { + return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len, max: self.data.len() })); + } + + self.data[addr..end].fill(val); + Ok(()) + } + + pub(crate) fn copy_from_slice(&mut self, dst: usize, src: &[u8]) -> Result<()> { + let end = dst.checked_add(src.len()).ok_or_else(|| { + Error::Trap(crate::Trap::MemoryOutOfBounds { offset: dst, len: src.len(), max: self.data.len() }) + })?; + if unlikely(end > self.data.len()) { + return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { + offset: dst, + len: src.len(), + max: self.data.len(), + })); + } + + self.data[dst..end].copy_from_slice(src); + Ok(()) + } + + pub(crate) fn copy_within(&mut self, dst: usize, src: usize, len: usize) -> Result<()> { + // Calculate the end of the source slice + let src_end = src + .checked_add(len) + .ok_or_else(|| Error::Trap(crate::Trap::MemoryOutOfBounds { offset: src, len, max: self.data.len() }))?; + if src_end > self.data.len() { + return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: src, len, max: self.data.len() })); + } + + // Calculate the end of the destination slice + let dst_end = dst + .checked_add(len) + .ok_or_else(|| Error::Trap(crate::Trap::MemoryOutOfBounds { offset: dst, len, max: self.data.len() }))?; + if dst_end > self.data.len() { + return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: dst, len, max: self.data.len() })); + } + + // Perform the copy + self.data.copy_within(src..src_end, dst); + Ok(()) + } + + pub(crate) fn grow(&mut self, pages_delta: i32) -> Option { + let current_pages = self.page_count(); + let new_pages = current_pages as i64 + pages_delta as i64; + + if new_pages < 0 || new_pages > MAX_PAGES as i64 { + return None; + } + + if new_pages as usize > self.max_pages() { + log::info!("memory size out of bounds: {}", new_pages); + return None; + } + + let new_size = new_pages as usize * PAGE_SIZE; + if new_size as u64 > MAX_SIZE { + return None; + } + + // Zero initialize the new pages + self.data.resize(new_size, 0); + self.page_count = new_pages as usize; + + log::debug!("memory was {} pages", current_pages); + log::debug!("memory grown by {} pages", pages_delta); + log::debug!("memory grown to {} pages", self.page_count); + + Some(current_pages.try_into().expect("memory size out of bounds, this should have been caught earlier")) + } +} + +#[allow(unsafe_code)] +/// A trait for types that can be loaded from memory +/// +/// # Safety +/// Only implemented for primitive types, unsafe to not allow it for other types. +/// Only actually unsafe to implement if the `unsafe` feature is enabled since there might be +/// UB for loading things things like packed structs +pub(crate) unsafe trait MemLoadable: Sized + Copy { + /// Load a value from memory + fn from_le_bytes(bytes: [u8; T]) -> Self; + /// Load a value from memory + fn from_be_bytes(bytes: [u8; T]) -> Self; +} + +#[allow(unsafe_code)] +unsafe impl MemLoadable<1> for u8 { + fn from_le_bytes(bytes: [u8; 1]) -> Self { + bytes[0] + } + fn from_be_bytes(bytes: [u8; 1]) -> Self { + bytes[0] + } +} + +#[allow(unsafe_code)] +unsafe impl MemLoadable<2> for u16 { + fn from_le_bytes(bytes: [u8; 2]) -> Self { + Self::from_le_bytes(bytes) + } + fn from_be_bytes(bytes: [u8; 2]) -> Self { + Self::from_be_bytes(bytes) + } +} + +#[allow(unsafe_code)] +unsafe impl MemLoadable<4> for u32 { + fn from_le_bytes(bytes: [u8; 4]) -> Self { + Self::from_le_bytes(bytes) + } + fn from_be_bytes(bytes: [u8; 4]) -> Self { + Self::from_be_bytes(bytes) + } +} + +#[allow(unsafe_code)] +unsafe impl MemLoadable<8> for u64 { + fn from_le_bytes(bytes: [u8; 8]) -> Self { + Self::from_le_bytes(bytes) + } + fn from_be_bytes(bytes: [u8; 8]) -> Self { + Self::from_be_bytes(bytes) + } +} + +#[allow(unsafe_code)] +unsafe impl MemLoadable<16> for u128 { + fn from_le_bytes(bytes: [u8; 16]) -> Self { + Self::from_le_bytes(bytes) + } + fn from_be_bytes(bytes: [u8; 16]) -> Self { + Self::from_be_bytes(bytes) + } +} + +#[allow(unsafe_code)] +unsafe impl MemLoadable<1> for i8 { + fn from_le_bytes(bytes: [u8; 1]) -> Self { + bytes[0] as i8 + } + fn from_be_bytes(bytes: [u8; 1]) -> Self { + bytes[0] as i8 + } +} + +#[allow(unsafe_code)] +unsafe impl MemLoadable<2> for i16 { + fn from_le_bytes(bytes: [u8; 2]) -> Self { + Self::from_le_bytes(bytes) + } + fn from_be_bytes(bytes: [u8; 2]) -> Self { + Self::from_be_bytes(bytes) + } +} + +#[allow(unsafe_code)] +unsafe impl MemLoadable<4> for i32 { + fn from_le_bytes(bytes: [u8; 4]) -> Self { + Self::from_le_bytes(bytes) + } + fn from_be_bytes(bytes: [u8; 4]) -> Self { + Self::from_be_bytes(bytes) + } +} + +#[allow(unsafe_code)] +unsafe impl MemLoadable<8> for i64 { + fn from_le_bytes(bytes: [u8; 8]) -> Self { + Self::from_le_bytes(bytes) + } + fn from_be_bytes(bytes: [u8; 8]) -> Self { + Self::from_be_bytes(bytes) + } +} + +#[allow(unsafe_code)] +unsafe impl MemLoadable<16> for i128 { + fn from_le_bytes(bytes: [u8; 16]) -> Self { + Self::from_le_bytes(bytes) + } + fn from_be_bytes(bytes: [u8; 16]) -> Self { + Self::from_be_bytes(bytes) + } +} + +#[allow(unsafe_code)] +unsafe impl MemLoadable<4> for f32 { + fn from_le_bytes(bytes: [u8; 4]) -> Self { + Self::from_le_bytes(bytes) + } + fn from_be_bytes(bytes: [u8; 4]) -> Self { + Self::from_be_bytes(bytes) + } +} + +#[allow(unsafe_code)] +unsafe impl MemLoadable<8> for f64 { + fn from_le_bytes(bytes: [u8; 8]) -> Self { + Self::from_le_bytes(bytes) + } + fn from_be_bytes(bytes: [u8; 8]) -> Self { + Self::from_be_bytes(bytes) + } +} diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs new file mode 100644 index 0000000..f89b931 --- /dev/null +++ b/crates/tinywasm/src/store/mod.rs @@ -0,0 +1,632 @@ +use crate::log; +use alloc::{boxed::Box, format, rc::Rc, string::ToString, vec, vec::Vec}; +use core::{ + cell::RefCell, + sync::atomic::{AtomicUsize, Ordering}, +}; +use tinywasm_types::*; + +use crate::{ + runtime::{self, InterpreterRuntime, RawWasmValue}, + Error, Function, ModuleInstance, Result, Trap, +}; + +mod memory; +pub(crate) use memory::*; + +// global store id counter +static STORE_ID: AtomicUsize = AtomicUsize::new(0); + +/// Global state that can be manipulated by WebAssembly programs +/// +/// Data should only be addressable by the module that owns it +/// +/// Note that the state doesn't do any garbage collection - so it will grow +/// indefinitely if you keep adding modules to it. When calling temporary +/// functions, you should create a new store and then drop it when you're done (e.g. in a request handler) +/// +/// See +#[derive(Debug)] +pub struct Store { + id: usize, + module_instances: Vec, + module_instance_count: usize, + + pub(crate) data: StoreData, + pub(crate) runtime: Runtime, +} + +#[derive(Debug, Clone, Copy)] +pub(crate) enum Runtime { + Default, +} + +impl Store { + /// Create a new store + pub fn new() -> Self { + Self::default() + } + + /// Get a module instance by the internal id + pub fn get_module_instance(&self, addr: ModuleInstanceAddr) -> Option<&ModuleInstance> { + self.module_instances.get(addr as usize) + } + + pub(crate) fn get_module_instance_raw(&self, addr: ModuleInstanceAddr) -> ModuleInstance { + self.module_instances[addr as usize].clone() + } + + /// Create a new store with the given runtime + pub(crate) fn runtime(&self) -> runtime::InterpreterRuntime { + match self.runtime { + Runtime::Default => InterpreterRuntime::default(), + } + } +} + +impl PartialEq for Store { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl Default for Store { + fn default() -> Self { + let id = STORE_ID.fetch_add(1, Ordering::Relaxed); + + Self { + id, + module_instances: Vec::new(), + module_instance_count: 0, + data: StoreData::default(), + runtime: Runtime::Default, + } + } +} + +#[derive(Debug, Default)] +/// Global state that can be manipulated by WebAssembly programs +/// +/// Data should only be addressable by the module that owns it +/// See +pub(crate) struct StoreData { + pub(crate) funcs: Vec, + pub(crate) tables: Vec>>, + pub(crate) memories: Vec>>, + pub(crate) globals: Vec>>, + pub(crate) elements: Vec, + pub(crate) datas: Vec, +} + +impl Store { + /// Get the store's ID (unique per process) + pub fn id(&self) -> usize { + self.id + } + + pub(crate) fn next_module_instance_idx(&self) -> ModuleInstanceAddr { + self.module_instance_count as ModuleInstanceAddr + } + + pub(crate) fn add_instance(&mut self, instance: ModuleInstance) -> Result<()> { + assert!(instance.id() == self.module_instance_count as ModuleInstanceAddr); + self.module_instances.push(instance); + self.module_instance_count += 1; + Ok(()) + } + + /// Add functions to the store, returning their addresses in the store + pub(crate) fn init_funcs( + &mut self, + funcs: Vec<(u32, WasmFunction)>, + idx: ModuleInstanceAddr, + ) -> Result> { + let func_count = self.data.funcs.len(); + let mut func_addrs = Vec::with_capacity(func_count); + + for (i, (_, func)) in funcs.into_iter().enumerate() { + self.data.funcs.push(FunctionInstance { func: Function::Wasm(Rc::new(func)), owner: idx }); + func_addrs.push((i + func_count) as FuncAddr); + } + + Ok(func_addrs) + } + + /// Add tables to the store, returning their addresses in the store + pub(crate) fn init_tables(&mut self, tables: Vec, idx: ModuleInstanceAddr) -> Result> { + let table_count = self.data.tables.len(); + let mut table_addrs = Vec::with_capacity(table_count); + for (i, table) in tables.into_iter().enumerate() { + self.data.tables.push(Rc::new(RefCell::new(TableInstance::new(table, idx)))); + table_addrs.push((i + table_count) as TableAddr); + } + Ok(table_addrs) + } + + /// Add memories to the store, returning their addresses in the store + pub(crate) fn init_memories(&mut self, memories: Vec, idx: ModuleInstanceAddr) -> Result> { + let mem_count = self.data.memories.len(); + let mut mem_addrs = Vec::with_capacity(mem_count); + for (i, mem) in memories.into_iter().enumerate() { + if let MemoryArch::I64 = mem.arch { + return Err(Error::UnsupportedFeature("64-bit memories".to_string())); + } + log::info!("adding memory: {:?}", mem); + self.data.memories.push(Rc::new(RefCell::new(MemoryInstance::new(mem, idx)))); + + mem_addrs.push((i + mem_count) as MemAddr); + } + Ok(mem_addrs) + } + + /// Add globals to the store, returning their addresses in the store + pub(crate) fn init_globals( + &mut self, + mut imported_globals: Vec, + new_globals: Vec, + func_addrs: &[FuncAddr], + idx: ModuleInstanceAddr, + ) -> Result> { + let global_count = self.data.globals.len(); + imported_globals.reserve_exact(new_globals.len()); + let mut global_addrs = imported_globals; + + for (i, global) in new_globals.iter().enumerate() { + self.data.globals.push(Rc::new(RefCell::new(GlobalInstance::new( + global.ty, + self.eval_const(&global.init, &global_addrs, func_addrs)?, + idx, + )))); + global_addrs.push((i + global_count) as Addr); + } + + Ok(global_addrs) + } + + fn elem_addr(&self, item: &ElementItem, globals: &[Addr], funcs: &[FuncAddr]) -> Result> { + let res = match item { + ElementItem::Func(addr) | ElementItem::Expr(ConstInstruction::RefFunc(addr)) => { + Some(funcs.get(*addr as usize).copied().ok_or_else(|| { + Error::Other(format!("function {} not found. This should have been caught by the validator", addr)) + })?) + } + ElementItem::Expr(ConstInstruction::RefNull(_ty)) => None, + ElementItem::Expr(ConstInstruction::GlobalGet(addr)) => { + let addr = globals.get(*addr as usize).copied().ok_or_else(|| { + Error::Other(format!("global {} not found. This should have been caught by the validator", addr)) + })?; + + let global = self.data.globals[addr as usize].clone(); + let val = i64::from(global.borrow().value); + log::error!("global: {}", val); + if val < 0 { + // the global is actually a null reference + None + } else { + Some(val as u32) + } + } + _ => return Err(Error::UnsupportedFeature(format!("const expression other than ref: {:?}", item))), + }; + + Ok(res) + } + + /// Add elements to the store, returning their addresses in the store + /// Should be called after the tables have been added + pub(crate) fn init_elements( + &mut self, + table_addrs: &[TableAddr], + func_addrs: &[FuncAddr], + global_addrs: &[Addr], + elements: &[Element], + idx: ModuleInstanceAddr, + ) -> Result<(Box<[Addr]>, Option)> { + let elem_count = self.data.elements.len(); + let mut elem_addrs = Vec::with_capacity(elem_count); + for (i, element) in elements.iter().enumerate() { + let init = element + .items + .iter() + .map(|item| Ok(TableElement::from(self.elem_addr(item, global_addrs, func_addrs)?))) + .collect::>>()?; + + log::error!("element kind: {:?}", element.kind); + + let items = match element.kind { + // doesn't need to be initialized, can be initialized lazily using the `table.init` instruction + ElementKind::Passive => Some(init), + + // this one is not available to the runtime but needs to be initialized to declare references + ElementKind::Declared => { + // a. Execute the instruction elm.drop i + None + } + + // this one is active, so we need to initialize it (essentially a `table.init` instruction) + ElementKind::Active { offset, table } => { + let offset = self.eval_i32_const(&offset)?; + let table_addr = table_addrs + .get(table as usize) + .copied() + .ok_or_else(|| Error::Other(format!("table {} not found for element {}", table, i)))?; + + if let Some(table) = self.data.tables.get_mut(table_addr as usize) { + // In wasm 2.0, it's possible to call a function that hasn't been instantiated yet, + // when using a partially initialized active element segments. + // This isn't mentioned in the spec, but the "unofficial" testsuite has a test for it: + // https://github.com/WebAssembly/testsuite/blob/5a1a590603d81f40ef471abba70a90a9ae5f4627/linking.wast#L264-L276 + // I have NO IDEA why this is allowed, but it is. + if let Err(Error::Trap(trap)) = table.borrow_mut().init_raw(offset, &init) { + return Ok((elem_addrs.into_boxed_slice(), Some(trap))); + } + } else { + return Err(Error::Other(format!("table {} not found for element {}", table, i))); + } + + // f. Execute the instruction elm.drop i + None + } + }; + + self.data.elements.push(ElementInstance::new(element.kind, idx, items)); + elem_addrs.push((i + elem_count) as Addr); + } + + // this should be optimized out by the compiler + Ok((elem_addrs.into_boxed_slice(), None)) + } + + /// Add data to the store, returning their addresses in the store + pub(crate) fn init_datas( + &mut self, + mem_addrs: &[MemAddr], + datas: Vec, + idx: ModuleInstanceAddr, + ) -> Result<(Box<[Addr]>, Option)> { + let data_count = self.data.datas.len(); + let mut data_addrs = Vec::with_capacity(data_count); + for (i, data) in datas.into_iter().enumerate() { + use tinywasm_types::DataKind::*; + match data.kind { + Active { mem: mem_addr, offset } => { + // a. Assert: memidx == 0 + if mem_addr != 0 { + return Err(Error::UnsupportedFeature("data segments for non-zero memories".to_string())); + } + + let mem_addr = mem_addrs + .get(mem_addr as usize) + .copied() + .ok_or_else(|| Error::Other(format!("memory {} not found for data segment {}", mem_addr, i)))?; + + let offset = self.eval_i32_const(&offset)?; + + let mem = + self.data.memories.get_mut(mem_addr as usize).ok_or_else(|| { + Error::Other(format!("memory {} not found for data segment {}", mem_addr, i)) + })?; + + // See comment for active element sections in the function above why we need to do this here + if let Err(Error::Trap(trap)) = + mem.borrow_mut().store(offset as usize, 0, &data.data, data.data.len()) + { + return Ok((data_addrs.into_boxed_slice(), Some(trap))); + } + + // drop the data + continue; + } + Passive => {} + } + + self.data.datas.push(DataInstance::new(data.data.to_vec(), idx)); + data_addrs.push((i + data_count) as Addr); + } + + // this should be optimized out by the compiler + Ok((data_addrs.into_boxed_slice(), None)) + } + + pub(crate) fn add_global(&mut self, ty: GlobalType, value: RawWasmValue, idx: ModuleInstanceAddr) -> Result { + self.data.globals.push(Rc::new(RefCell::new(GlobalInstance::new(ty, value, idx)))); + Ok(self.data.globals.len() as Addr - 1) + } + + pub(crate) fn add_table(&mut self, table: TableType, idx: ModuleInstanceAddr) -> Result { + self.data.tables.push(Rc::new(RefCell::new(TableInstance::new(table, idx)))); + Ok(self.data.tables.len() as TableAddr - 1) + } + + pub(crate) fn add_mem(&mut self, mem: MemoryType, idx: ModuleInstanceAddr) -> Result { + if let MemoryArch::I64 = mem.arch { + return Err(Error::UnsupportedFeature("64-bit memories".to_string())); + } + self.data.memories.push(Rc::new(RefCell::new(MemoryInstance::new(mem, idx)))); + Ok(self.data.memories.len() as MemAddr - 1) + } + + pub(crate) fn add_func(&mut self, func: Function, idx: ModuleInstanceAddr) -> Result { + self.data.funcs.push(FunctionInstance { func, owner: idx }); + Ok(self.data.funcs.len() as FuncAddr - 1) + } + + /// Evaluate a constant expression, only supporting i32 globals and i32.const + pub(crate) fn eval_i32_const(&self, const_instr: &tinywasm_types::ConstInstruction) -> Result { + use tinywasm_types::ConstInstruction::*; + let val = match const_instr { + I32Const(i) => *i, + GlobalGet(addr) => { + let addr = *addr as usize; + let global = self.data.globals[addr].clone(); + let val = global.borrow().value; + i32::from(val) + } + _ => return Err(Error::Other("expected i32".to_string())), + }; + Ok(val) + } + + /// Evaluate a constant expression + pub(crate) fn eval_const( + &self, + const_instr: &tinywasm_types::ConstInstruction, + module_global_addrs: &[Addr], + module_func_addrs: &[FuncAddr], + ) -> Result { + use tinywasm_types::ConstInstruction::*; + let val = match const_instr { + F32Const(f) => RawWasmValue::from(*f), + F64Const(f) => RawWasmValue::from(*f), + I32Const(i) => RawWasmValue::from(*i), + I64Const(i) => RawWasmValue::from(*i), + GlobalGet(addr) => { + let addr = module_global_addrs.get(*addr as usize).copied().ok_or_else(|| { + Error::Other(format!("global {} not found. This should have been caught by the validator", addr)) + })?; + + let global = + self.data.globals.get(addr as usize).expect("global not found. This should be unreachable"); + + global.borrow().value + } + RefNull(t) => RawWasmValue::from(t.default_value()), + RefFunc(idx) => RawWasmValue::from(module_func_addrs.get(*idx as usize).copied().ok_or_else(|| { + Error::Other(format!("function {} not found. This should have been caught by the validator", idx)) + })?), + }; + Ok(val) + } + + /// Get the function at the actual index in the store + pub(crate) fn get_func(&self, addr: usize) -> Result<&FunctionInstance> { + self.data.funcs.get(addr).ok_or_else(|| Error::Other(format!("function {} not found", addr))) + } + + /// Get the memory at the actual index in the store + pub(crate) fn get_mem(&self, addr: usize) -> Result<&Rc>> { + self.data.memories.get(addr).ok_or_else(|| Error::Other(format!("memory {} not found", addr))) + } + + /// Get the table at the actual index in the store + pub(crate) fn get_table(&self, addr: usize) -> Result<&Rc>> { + self.data.tables.get(addr).ok_or_else(|| Error::Other(format!("table {} not found", addr))) + } + + /// Get the element at the actual index in the store + pub(crate) fn get_elem(&self, addr: usize) -> Result<&ElementInstance> { + self.data.elements.get(addr).ok_or_else(|| Error::Other(format!("element {} not found", addr))) + } + + /// Get the global at the actual index in the store + pub(crate) fn get_global(&self, addr: usize) -> Result<&Rc>> { + self.data.globals.get(addr).ok_or_else(|| Error::Other(format!("global {} not found", addr))) + } + + /// Get the global at the actual index in the store + pub fn get_global_val(&self, addr: usize) -> Result { + self.data + .globals + .get(addr) + .ok_or_else(|| Error::Other(format!("global {} not found", addr))) + .map(|global| global.borrow().value) + } + + pub(crate) fn set_global_val(&mut self, addr: usize, value: RawWasmValue) -> Result<()> { + self.data + .globals + .get(addr) + .ok_or_else(|| Error::Other(format!("global {} not found", addr))) + .map(|global| global.borrow_mut().value = value) + } +} + +#[derive(Debug, Clone)] +/// A WebAssembly Function Instance +/// +/// See +pub(crate) struct FunctionInstance { + pub(crate) func: Function, + pub(crate) owner: ModuleInstanceAddr, // index into store.module_instances, none for host functions +} + +#[derive(Debug, Clone, Copy)] +pub(crate) enum TableElement { + Uninitialized, + Initialized(Addr), +} + +impl From> for TableElement { + fn from(addr: Option) -> Self { + match addr { + None => TableElement::Uninitialized, + Some(addr) => TableElement::Initialized(addr), + } + } +} + +impl TableElement { + pub(crate) fn addr(&self) -> Option { + match self { + TableElement::Uninitialized => None, + TableElement::Initialized(addr) => Some(*addr), + } + } + + pub(crate) fn map Addr>(self, f: F) -> Self { + match self { + TableElement::Uninitialized => TableElement::Uninitialized, + TableElement::Initialized(addr) => TableElement::Initialized(f(addr)), + } + } +} + +const MAX_TABLE_SIZE: u32 = 10000000; + +/// A WebAssembly Table Instance +/// +/// See +#[derive(Debug)] +pub(crate) struct TableInstance { + pub(crate) elements: Vec, + pub(crate) kind: TableType, + pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances +} + +impl TableInstance { + pub(crate) fn new(kind: TableType, owner: ModuleInstanceAddr) -> Self { + Self { elements: vec![TableElement::Uninitialized; kind.size_initial as usize], kind, _owner: owner } + } + + pub(crate) fn get_wasm_val(&self, addr: usize) -> Result { + let val = self.get(addr)?.addr(); + + Ok(match self.kind.element_type { + ValType::RefFunc => val.map(WasmValue::RefFunc).unwrap_or(WasmValue::RefNull(ValType::RefFunc)), + ValType::RefExtern => val.map(WasmValue::RefExtern).unwrap_or(WasmValue::RefNull(ValType::RefExtern)), + _ => unimplemented!("unsupported table type: {:?}", self.kind.element_type), + }) + } + + pub(crate) fn get(&self, addr: usize) -> Result<&TableElement> { + self.elements.get(addr).ok_or_else(|| Error::Trap(Trap::UndefinedElement { index: addr })) + } + + pub(crate) fn set(&mut self, table_idx: usize, value: Addr) -> Result<()> { + self.grow_to_fit(table_idx + 1).map(|_| self.elements[table_idx] = TableElement::Initialized(value)) + } + + pub(crate) fn grow_to_fit(&mut self, new_size: usize) -> Result<()> { + if new_size > self.elements.len() { + if new_size > self.kind.size_max.unwrap_or(MAX_TABLE_SIZE) as usize { + return Err(crate::Trap::TableOutOfBounds { offset: new_size, len: 1, max: self.elements.len() }.into()); + } + + self.elements.resize(new_size, TableElement::Uninitialized); + } + Ok(()) + } + + pub(crate) fn size(&self) -> i32 { + self.elements.len() as i32 + } + + fn resolve_func_ref(&self, func_addrs: &[u32], addr: Addr) -> Addr { + if self.kind.element_type != ValType::RefFunc { + return addr; + } + + *func_addrs + .get(addr as usize) + .expect("error initializing table: function not found. This should have been caught by the validator") + } + + // Initialize the table with the given elements + pub(crate) fn init_raw(&mut self, offset: i32, init: &[TableElement]) -> Result<()> { + let offset = offset as usize; + let end = offset.checked_add(init.len()).ok_or_else(|| { + Error::Trap(crate::Trap::TableOutOfBounds { offset, len: init.len(), max: self.elements.len() }) + })?; + + if end > self.elements.len() || end < offset { + return Err(crate::Trap::TableOutOfBounds { offset, len: init.len(), max: self.elements.len() }.into()); + } + + self.elements[offset..end].copy_from_slice(init); + log::debug!("table: {:?}", self.elements); + Ok(()) + } + + // Initialize the table with the given elements (resolves function references) + pub(crate) fn init(&mut self, func_addrs: &[u32], offset: i32, init: &[TableElement]) -> Result<()> { + let init = init.iter().map(|item| item.map(|addr| self.resolve_func_ref(func_addrs, addr))).collect::>(); + + self.init_raw(offset, &init) + } +} + +/// A WebAssembly Global Instance +/// +/// See +#[derive(Debug)] +pub(crate) struct GlobalInstance { + pub(crate) value: RawWasmValue, + pub(crate) ty: GlobalType, + pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances +} + +impl GlobalInstance { + pub(crate) fn new(ty: GlobalType, value: RawWasmValue, owner: ModuleInstanceAddr) -> Self { + Self { ty, value, _owner: owner } + } + + pub(crate) fn get(&self) -> WasmValue { + self.value.attach_type(self.ty.ty) + } + + pub(crate) fn set(&mut self, val: WasmValue) -> Result<()> { + if val.val_type() != self.ty.ty { + return Err(Error::Other(format!( + "global type mismatch: expected {:?}, got {:?}", + self.ty.ty, + val.val_type() + ))); + } + if !self.ty.mutable { + return Err(Error::Other("global is immutable".to_string())); + } + self.value = val.into(); + Ok(()) + } +} + +/// A WebAssembly Element Instance +/// +/// See +#[derive(Debug)] +pub(crate) struct ElementInstance { + pub(crate) kind: ElementKind, + pub(crate) items: Option>, // none is the element was dropped + _owner: ModuleInstanceAddr, // index into store.module_instances +} + +impl ElementInstance { + pub(crate) fn new(kind: ElementKind, owner: ModuleInstanceAddr, items: Option>) -> Self { + Self { kind, _owner: owner, items } + } +} + +/// A WebAssembly Data Instance +/// +/// See +#[derive(Debug)] +pub(crate) struct DataInstance { + pub(crate) _data: Vec, + pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances +} + +impl DataInstance { + pub(crate) fn new(data: Vec, owner: ModuleInstanceAddr) -> Self { + Self { _data: data, _owner: owner } + } +} diff --git a/crates/tinywasm/tests/generated/2.0.csv b/crates/tinywasm/tests/generated/2.0.csv index b5ea4cc..404fe16 100644 --- a/crates/tinywasm/tests/generated/2.0.csv +++ b/crates/tinywasm/tests/generated/2.0.csv @@ -1,2 +1,2 @@ 0.3.0,26722,1161,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":156,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":112,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":174,"failed":0},{"name":"bulk.wast","passed":8,"failed":109},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":170,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":98,"failed":1},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":900,"failed":0},{"name":"float_literals.wast","passed":163,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":441,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":110,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":171,"failed":12},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":132,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":97,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":79,"failed":0},{"name":"memory_copy.wast","passed":3928,"failed":522},{"name":"memory_fill.wast","passed":64,"failed":36},{"name":"memory_grow.wast","passed":96,"failed":0},{"name":"memory_init.wast","passed":177,"failed":63},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"ref_func.wast","passed":9,"failed":8},{"name":"ref_is_null.wast","passed":4,"failed":12},{"name":"ref_null.wast","passed":1,"failed":2},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":148,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table-sub.wast","passed":2,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"table_copy.wast","passed":1613,"failed":115},{"name":"table_fill.wast","passed":23,"failed":22},{"name":"table_get.wast","passed":10,"failed":6},{"name":"table_grow.wast","passed":21,"failed":29},{"name":"table_init.wast","passed":594,"failed":186},{"name":"table_set.wast","passed":19,"failed":7},{"name":"table_size.wast","passed":8,"failed":31},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":7,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] -0.4.0-alpha.0,26861,1022,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":156,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":112,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":174,"failed":0},{"name":"bulk.wast","passed":8,"failed":109},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":170,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":99,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":900,"failed":0},{"name":"float_literals.wast","passed":163,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":441,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":110,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":183,"failed":0},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":132,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":97,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":79,"failed":0},{"name":"memory_copy.wast","passed":3928,"failed":522},{"name":"memory_fill.wast","passed":64,"failed":36},{"name":"memory_grow.wast","passed":96,"failed":0},{"name":"memory_init.wast","passed":177,"failed":63},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"ref_func.wast","passed":9,"failed":8},{"name":"ref_is_null.wast","passed":4,"failed":12},{"name":"ref_null.wast","passed":1,"failed":2},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":148,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table-sub.wast","passed":2,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"table_copy.wast","passed":1613,"failed":115},{"name":"table_fill.wast","passed":23,"failed":22},{"name":"table_get.wast","passed":10,"failed":6},{"name":"table_grow.wast","passed":21,"failed":29},{"name":"table_init.wast","passed":720,"failed":60},{"name":"table_set.wast","passed":19,"failed":7},{"name":"table_size.wast","passed":8,"failed":31},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":7,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] +0.4.0-alpha.0,27464,419,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":156,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":112,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":174,"failed":0},{"name":"bulk.wast","passed":53,"failed":64},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":170,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":99,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":900,"failed":0},{"name":"float_literals.wast","passed":163,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":441,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":110,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":183,"failed":0},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":132,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":97,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":79,"failed":0},{"name":"memory_copy.wast","passed":4450,"failed":0},{"name":"memory_fill.wast","passed":100,"failed":0},{"name":"memory_grow.wast","passed":96,"failed":0},{"name":"memory_init.wast","passed":177,"failed":63},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"ref_func.wast","passed":9,"failed":8},{"name":"ref_is_null.wast","passed":4,"failed":12},{"name":"ref_null.wast","passed":1,"failed":2},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":148,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table-sub.wast","passed":2,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"table_copy.wast","passed":1613,"failed":115},{"name":"table_fill.wast","passed":23,"failed":22},{"name":"table_get.wast","passed":10,"failed":6},{"name":"table_grow.wast","passed":21,"failed":29},{"name":"table_init.wast","passed":720,"failed":60},{"name":"table_set.wast","passed":19,"failed":7},{"name":"table_size.wast","passed":8,"failed":31},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":7,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index ba13979..9098812 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -1,4 +1,4 @@ -use crate::{ElemAddr, MemAddr}; +use crate::{DataAddr, ElemAddr, MemAddr}; use super::{FuncAddr, GlobalAddr, LabelAddr, LocalAddr, TableAddr, TypeAddr, ValType}; @@ -266,4 +266,10 @@ pub enum Instruction { TableGrow(TableAddr), TableSize(TableAddr), TableFill(TableAddr), + + // Bulk Memory Instructions + MemoryInit(MemAddr, DataAddr), + MemoryCopy(MemAddr, MemAddr), + MemoryFill(MemAddr), + DataDrop(DataAddr), } -- cgit v1.3.1