diff options
Diffstat (limited to 'crates')
| -rw-r--r-- | crates/cli/src/output.rs | 6 | ||||
| -rw-r--r-- | crates/cli/src/wast_runner.rs | 6 | ||||
| -rw-r--r-- | crates/parser/src/conversion.rs | 26 | ||||
| -rw-r--r-- | crates/parser/src/module.rs | 14 | ||||
| -rw-r--r-- | crates/parser/src/visit.rs | 15 | ||||
| -rw-r--r-- | crates/tinywasm/src/imports.rs | 5 | ||||
| -rw-r--r-- | crates/tinywasm/src/instance.rs | 2 | ||||
| -rw-r--r-- | crates/tinywasm/src/interpreter/executor.rs | 127 | ||||
| -rw-r--r-- | crates/tinywasm/src/reference.rs | 13 | ||||
| -rw-r--r-- | crates/tinywasm/src/store/mod.rs | 22 | ||||
| -rw-r--r-- | crates/tinywasm/src/store/table.rs | 63 | ||||
| -rw-r--r-- | crates/tinywasm/tests/wasm-custom/table64.wast | 51 | ||||
| -rw-r--r-- | crates/types/src/lib.rs | 26 |
13 files changed, 227 insertions, 149 deletions
diff --git a/crates/cli/src/output.rs b/crates/cli/src/output.rs index bc0e450..bf568ed 100644 --- a/crates/cli/src/output.rs +++ b/crates/cli/src/output.rs @@ -57,8 +57,12 @@ pub fn format_memory_type(ty: &MemoryType) -> String { } pub fn format_table_type(ty: &TableType) -> String { + let arch = match ty.arch() { + MemoryArch::I32 => "i32", + MemoryArch::I64 => "i64", + }; let max = ty.size_max.map(|v| v.to_string()).unwrap_or_else(|| "unbounded".to_string()); - format!("table[{}] initial={} max={max}", format_wasm_type(ty.element_type), ty.size_initial) + format!("table[{arch} {}] initial={} max={max}", format_wasm_type(ty.element_type), ty.size_initial) } pub fn format_global_type(ty: &GlobalType) -> String { diff --git a/crates/cli/src/wast_runner.rs b/crates/cli/src/wast_runner.rs index 0634e76..3c0a1cd 100644 --- a/crates/cli/src/wast_runner.rs +++ b/crates/cli/src/wast_runner.rs @@ -159,6 +159,11 @@ impl WastRunner { TableType::new(WasmType::RefFunc, 10, Some(20)), WasmValue::default_for(WasmType::RefFunc), )?; + let table64 = Table::new( + store, + TableType::new64(WasmType::RefFunc, 10, Some(20)), + WasmValue::default_for(WasmType::RefFunc), + )?; let memory = Memory::new(store, MemoryType::default().with_page_count_initial(1).with_page_count_max(Some(2)))?; let global_i32 = Global::new(store, tinywasm::types::GlobalType::new(WasmType::I32, false), WasmValue::I32(666))?; @@ -172,6 +177,7 @@ impl WastRunner { imports .define("spectest", "memory", memory) .define("spectest", "table", table) + .define("spectest", "table64", table64) .define("spectest", "global_i32", global_i32) .define("spectest", "global_i64", global_i64) .define("spectest", "global_f32", global_f32) diff --git a/crates/parser/src/conversion.rs b/crates/parser/src/conversion.rs index 34dcdfd..cfc0e84 100644 --- a/crates/parser/src/conversion.rs +++ b/crates/parser/src/conversion.rs @@ -60,8 +60,11 @@ pub(crate) fn convert_module_import(import: wasmparser::Import<'_>) -> Result<Im wasmparser::TypeRef::Func(ty) => ImportKind::Function(ty), wasmparser::TypeRef::Table(ty) => { let element_type = convert_reftype(ty.element_type)?; - let (size_initial, size_max) = convert_table_limits(ty)?; - ImportKind::Table(TableType { element_type, size_initial, size_max }) + ImportKind::Table(if ty.table64 { + TableType::new64(element_type, ty.initial, ty.maximum) + } else { + TableType::new(element_type, ty.initial, ty.maximum) + }) } wasmparser::TypeRef::Memory(ty) => ImportKind::Memory(convert_module_memory(ty)), wasmparser::TypeRef::Global(ty) => { @@ -87,25 +90,6 @@ pub(crate) fn convert_module_memory(memory: wasmparser::MemoryType) -> MemoryTyp ) } -pub(crate) fn convert_module_table(table: wasmparser::Table<'_>) -> Result<TableType> { - let (size_initial, size_max) = convert_table_limits(table.ty)?; - Ok(TableType { element_type: convert_reftype(table.ty.element_type)?, size_initial, size_max }) -} - -fn convert_table_limits(table: wasmparser::TableType) -> Result<(u32, Option<u32>)> { - let size_initial = table.initial.try_into().map_err(|_| { - crate::ParseError::UnsupportedOperator(format!("Table size initial is too large: {}", table.initial)) - })?; - let size_max = table - .maximum - .map(|max| { - u32::try_from(max) - .map_err(|_| crate::ParseError::UnsupportedOperator(format!("Table size max is too large: {max}"))) - }) - .transpose()?; - Ok((size_initial, size_max)) -} - pub(crate) fn convert_module_globals( globals: wasmparser::SectionLimited<'_, wasmparser::Global<'_>>, ) -> Result<Box<[Global]>> { diff --git a/crates/parser/src/module.rs b/crates/parser/src/module.rs index 9273d44..05811ec 100644 --- a/crates/parser/src/module.rs +++ b/crates/parser/src/module.rs @@ -128,8 +128,18 @@ impl<'a> ModuleReader<'a> { if let Some(validator) = validator.as_mut() { validator.table_section(&reader)?; } - self.table_types = - reader.into_iter().map(|table| convert_module_table(table?)).collect::<Result<_>>()?; + self.table_types = reader + .into_iter() + .map(|table| { + let table = table?; + let element_type = convert_reftype(table.ty.element_type)?; + Ok(if table.ty.table64 { + TableType::new64(element_type, table.ty.initial, table.ty.maximum) + } else { + TableType::new(element_type, table.ty.initial, table.ty.maximum) + }) + }) + .collect::<Result<_>>()?; } Payload::MemorySection(reader) => { check_section("memory", !self.memory_types.is_empty())?; diff --git a/crates/parser/src/visit.rs b/crates/parser/src/visit.rs index 0ca9494..cb13387 100644 --- a/crates/parser/src/visit.rs +++ b/crates/parser/src/visit.rs @@ -168,14 +168,14 @@ impl ModuleMetadata { ImportKind::Function(ty) => functions.push(*ty), ImportKind::Global(ty) => global_sizes.push(OperandSize::from(&ty.ty)), ImportKind::Memory(ty) => memory_sizes.push(OperandSize::from(ty.arch())), - ImportKind::Table(_) => table_sizes.push(OperandSize::S32), + ImportKind::Table(ty) => table_sizes.push(OperandSize::from(ty.arch())), } } functions.extend_from_slice(code_type_addrs); global_sizes.extend(globals.iter().map(|global| OperandSize::from(&global.ty.ty))); memory_sizes.extend(memories.iter().map(|ty| OperandSize::from(ty.arch()))); - table_sizes.extend(tables.iter().map(|_| OperandSize::S32)); + table_sizes.extend(tables.iter().map(|ty| OperandSize::from(ty.arch()))); let signatures = types .iter() @@ -653,13 +653,10 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder<'_> { } fn visit_table_copy(&mut self, dst_table: u32, src_table: u32) -> Self::Output { - self.metadata.table_size(dst_table)?; - self.metadata.table_size(src_table)?; - self.emit( - &[OperandSize::S32, OperandSize::S32, OperandSize::S32], - &[], - Instruction::TableCopy { dst_table, src_table }, - ) + let dst = self.metadata.table_size(dst_table)?; + let src = self.metadata.table_size(src_table)?; + let len = if dst == OperandSize::S32 || src == OperandSize::S32 { OperandSize::S32 } else { OperandSize::S64 }; + self.emit(&[dst, src, len], &[], Instruction::TableCopy { dst_table, src_table }) } fn visit_memory_copy(&mut self, dst_mem: u32, src_mem: u32) -> Self::Output { diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs index aa1f350..056d706 100644 --- a/crates/tinywasm/src/imports.rs +++ b/crates/tinywasm/src/imports.rs @@ -158,6 +158,7 @@ impl Imports { } fn compare_table_types(import: &Import, expected: &TableType, actual: &TableType) -> Result<()> { + Self::compare_types(import, &actual.arch(), &expected.arch())?; Self::compare_types(import, &actual.element_type, &expected.element_type)?; if actual.size_initial > expected.size_initial { cold_path(); @@ -248,8 +249,8 @@ impl Imports { } (ExternVal::Table(table_addr), ImportKind::Table(ty)) => { let table = store.state.get_table(table_addr); - let mut kind = table.kind.clone(); - kind.size_initial = table.size() as u32; + let mut kind = table.kind; + kind.size_initial = table.size() as u64; Self::compare_table_types(import, &kind, ty)?; imports.tables.push(table_addr); } diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs index 358b706..b509d0f 100644 --- a/crates/tinywasm/src/instance.rs +++ b/crates/tinywasm/src/instance.rs @@ -172,7 +172,7 @@ impl ModuleInstance { let idx = store.next_module_instance_idx(); let mut addrs = imports.unwrap_or_default().link(store, module)?; addrs.funcs.extend(store.init_funcs(&module.funcs, idx)); - addrs.tables.extend(store.init_tables(&module.table_types)); + addrs.tables.extend(store.init_tables(&module.table_types)?); match module.local_memory_allocation { LocalMemoryAllocation::Skip => {} LocalMemoryAllocation::Lazy => { diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index d7a0b27..0405546 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -1036,18 +1036,19 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { self.charge_call_fuel(FUEL_COST_CALL_TOTAL); // verify that the table is of the right type, this should be validated by the parser already - let table_idx: u32 = <i32>::stack_pop(&mut self.store.value_stack) as u32; - let table = self.store.state.get_table(self.module.resolve_table_addr(table_addr)); + let table_addr = self.module.resolve_table_addr(table_addr); + let table_idx = self.pop_table_operand(self.store.state.get_table(table_addr).kind.arch())?; + let table = self.store.state.get_table(table_addr); debug_assert!(table.kind.element_type == WasmType::RefFunc, "table is not of type funcref"); let Ok(table) = table.get(table_idx) else { cold_path(); - return Err(Trap::UndefinedElement { index: table_idx as usize }); + return Err(Trap::UndefinedElement { index: table_idx }); }; let Some(func_ref) = table.addr() else { cold_path(); - return Err(Trap::UninitializedElement { index: table_idx as usize }); + return Err(Trap::UninitializedElement { index: table_idx }); }; let call_ty = self.module.func_type_by_type_index(type_addr); @@ -1347,19 +1348,23 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { } fn exec_table_copy(&mut self, dst_table: u32, src_table: u32) -> Result<(), Trap> { - let size = i32::stack_pop(&mut self.store.value_stack); - let src = i32::stack_pop(&mut self.store.value_stack); - let dst = i32::stack_pop(&mut self.store.value_stack); let dst_table_addr = self.module.resolve_table_addr(dst_table); + let src_table_addr = self.module.resolve_table_addr(src_table); + let dst_arch = self.store.state.get_table(dst_table_addr).kind.arch(); + let src_arch = self.store.state.get_table(src_table_addr).kind.arch(); + let len_arch = + if dst_arch == MemoryArch::I32 || src_arch == MemoryArch::I32 { MemoryArch::I32 } else { MemoryArch::I64 }; + let size = self.pop_table_operand(len_arch)?; + let src = self.pop_table_operand(src_arch)?; + let dst = self.pop_table_operand(dst_arch)?; - if dst_table == src_table { + if dst_table_addr == src_table_addr { // copy within the same table - self.store.state.get_table_mut(dst_table_addr).copy_within(dst as usize, src as usize, size as usize) + self.store.state.get_table_mut(dst_table_addr).copy_within(dst, src, size) } else { // copy between two tables - let src_table_addr = self.module.resolve_table_addr(src_table); let (dst_table_ref, src_table_ref) = self.store.state.get_tables_mut(dst_table_addr, src_table_addr); - dst_table_ref.copy_from_slice(dst as usize, src_table_ref.load(src as usize, size as usize)?) + dst_table_ref.copy_from_slice(dst, src_table_ref.load(src, size)?) } } @@ -1463,87 +1468,83 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { } fn exec_table_get(&mut self, table_index: u32) -> Result<(), Trap> { - let idx: i32 = <i32>::stack_pop(&mut self.store.value_stack); - let table = self.store.state.get_table(self.module.resolve_table_addr(table_index)); - let v = table.get_wasm_val(idx as u32)?; + let table_addr = self.module.resolve_table_addr(table_index); + let idx = self.pop_table_operand(self.store.state.get_table(table_addr).kind.arch())?; + let v = self.store.state.get_table(table_addr).get_wasm_val(idx)?; self.store.value_stack.push_dyn(v.into()) } fn exec_table_set(&mut self, table_index: u32) -> Result<(), Trap> { let val = <ValueRef>::stack_pop(&mut self.store.value_stack); - let idx = <i32>::stack_pop(&mut self.store.value_stack) as u32; - let table = self.store.state.get_table_mut(self.module.resolve_table_addr(table_index)); + let table_addr = self.module.resolve_table_addr(table_index); + let idx = self.pop_table_operand(self.store.state.get_table(table_addr).kind.arch())?; + let table = self.store.state.get_table_mut(table_addr); table.set(idx, val.addr().into()) } fn exec_table_size(&mut self, table_index: u32) -> Result<(), Trap> { let table = self.store.state.get_table(self.module.resolve_table_addr(table_index)); - self.store.value_stack.push(table.size()) + match table.kind.arch() { + MemoryArch::I32 => self.store.value_stack.push(table.size() as i32), + MemoryArch::I64 => self.store.value_stack.push(table.size() as i64), + } } fn exec_table_init(&mut self, elem_index: u32, table_index: u32) -> Result<(), Trap> { - let size = i32::stack_pop(&mut self.store.value_stack); // n - let offset = i32::stack_pop(&mut self.store.value_stack); // s - let dst = i32::stack_pop(&mut self.store.value_stack); // d + let size = self.pop_table_operand(MemoryArch::I32)?; // n + let offset = self.pop_table_operand(MemoryArch::I32)?; // s + let table_addr = self.module.resolve_table_addr(table_index); + let dst = self.pop_table_operand(self.store.state.get_table(table_addr).kind.arch())?; // d let elem_addr = self.module.resolve_elem_addr(elem_index) as usize; let elem = self.store.state.elements.get(elem_addr).ok_or_else(|| Trap::Other("element not found"))?; - - let table_addr = self.module.resolve_table_addr(table_index) as usize; - let table = self.store.state.tables.get_mut(table_addr).ok_or_else(|| Trap::Other("table not found"))?; - - let elem_len = elem.items.as_ref().map_or(0, alloc::vec::Vec::len); - let table_len = table.size(); - - if size < 0 || ((size + offset) as usize > elem_len) || ((dst + size) > table_len) { + // Element kind storage is removed separately; table.init only depends on retained items. + let _ = &elem.kind; + let items = elem.items.as_deref().unwrap_or(&[]); + let Some(end) = offset.checked_add(size) else { cold_path(); - return Err(Trap::TableOutOfBounds { offset: offset as usize, len: size as usize, max: elem_len }); - } - - if size == 0 { - return Ok(()); - } - - if let ElementKind::Active { .. } = elem.kind { + return Err(Trap::TableOutOfBounds { offset, len: size, max: items.len() }); + }; + if end > items.len() { cold_path(); - return Err(Trap::Other("table.init with active element")); + return Err(Trap::TableOutOfBounds { offset, len: size, max: items.len() }); } - let Some(items) = elem.items.as_ref() else { - cold_path(); - return Err(Trap::TableOutOfBounds { offset: 0, len: 0, max: 0 }); - }; - - table.init(i64::from(dst), &items[offset as usize..(offset + size) as usize]) + let table = + self.store.state.tables.get_mut(table_addr as usize).ok_or_else(|| Trap::Other("table not found"))?; + table.init(dst, &items[offset..end]) } fn exec_table_grow(&mut self, table_index: u32) -> Result<(), Trap> { - let table = self.store.state.get_table_mut(self.module.resolve_table_addr(table_index)); - let sz = table.size(); - let n = <i32>::stack_pop(&mut self.store.value_stack); + let table_addr = self.module.resolve_table_addr(table_index); + let arch = self.store.state.get_table(table_addr).kind.arch(); + let n = self.pop_table_operand(arch)?; let val = <ValueRef>::stack_pop(&mut self.store.value_stack); - match table.grow(n, val.addr().into()) { - Ok(()) => self.store.value_stack.push(sz), - Err(_) => self.store.value_stack.push(-1_i32), + let table = self.store.state.get_table_mut(table_addr); + let sz = table.size(); + let result = table.grow(n, val.addr().into()); + match (arch, result) { + (MemoryArch::I32, Ok(())) => self.store.value_stack.push(sz as i32), + (MemoryArch::I32, Err(_)) => self.store.value_stack.push(-1_i32), + (MemoryArch::I64, Ok(())) => self.store.value_stack.push(sz as i64), + (MemoryArch::I64, Err(_)) => self.store.value_stack.push(-1_i64), } } fn exec_table_fill(&mut self, table_index: u32) -> Result<(), Trap> { - let table = self.store.state.get_table_mut(self.module.resolve_table_addr(table_index)); - - let n = <i32>::stack_pop(&mut self.store.value_stack); + let table_addr = self.module.resolve_table_addr(table_index); + let arch = self.store.state.get_table(table_addr).kind.arch(); + let n = self.pop_table_operand(arch)?; let val = <ValueRef>::stack_pop(&mut self.store.value_stack); - let i = <i32>::stack_pop(&mut self.store.value_stack); - - if i + n > table.size() { - cold_path(); - return Err(Trap::TableOutOfBounds { offset: i as usize, len: n as usize, max: table.size() as usize }); - } - - if n == 0 { - return Ok(()); - } + let i = self.pop_table_operand(arch)?; + self.store.state.get_table_mut(table_addr).fill(self.module.func_addrs(), i, n, val.addr().into()) + } - table.fill(self.module.func_addrs(), i as usize, n as usize, val.addr().into()) + fn pop_table_operand(&mut self, arch: MemoryArch) -> Result<usize, Trap> { + let value = match arch { + MemoryArch::I32 => <i32>::stack_pop(&mut self.store.value_stack) as u32 as u64, + MemoryArch::I64 => <i64>::stack_pop(&mut self.store.value_stack) as u64, + }; + usize::try_from(value).map_err(|_| Trap::TableOutOfBounds { offset: usize::MAX, len: 1, max: usize::MAX }) } } diff --git a/crates/tinywasm/src/reference.rs b/crates/tinywasm/src/reference.rs index b439760..b8a1aa1 100644 --- a/crates/tinywasm/src/reference.rs +++ b/crates/tinywasm/src/reference.rs @@ -346,7 +346,7 @@ impl Table { _ => return Err(Error::other("invalid table init value")), }; let addr = store.state.tables.len() as TableAddr; - store.state.tables.push(TableInstance::new_with_init(ty, init)); + store.state.tables.push(TableInstance::new_with_init(ty, init)?); Ok(Self(StoreItem::new(store.id(), addr))) } @@ -364,17 +364,17 @@ impl Table { /// Get the type of the table. pub fn ty(&self, store: &Store) -> Result<TableType> { - Ok(self.instance(store)?.kind.clone()) + Ok(self.instance(store)?.kind) } /// Get the current number of elements in the table. pub fn size(&self, store: &Store) -> Result<usize> { - Ok(self.instance(store)?.size() as usize) + Ok(self.instance(store)?.size()) } /// Get a table element as a wasm reference value. pub fn get(&self, store: &Store, index: TableAddr) -> Result<WasmValue> { - Ok(self.instance(store)?.get_wasm_val(index)?) + Ok(self.instance(store)?.get_wasm_val(index as usize)?) } /// Load a range of table elements and iterate over wasm reference values. @@ -394,7 +394,7 @@ impl Table { pub fn set(&self, store: &mut Store, index: TableAddr, value: WasmValue) -> Result<(), Trap> { let table = self.instance_mut(store)?; let value = table_value_to_element(table.kind.element_type, value)?; - table.set(index, value) + table.set(index as usize, value) } /// Copy elements within the same table. @@ -406,8 +406,9 @@ impl Table { pub fn grow(&self, store: &mut Store, delta: i32, init: WasmValue) -> Result<usize> { self.0.validate_store(store)?; let table = store.state.get_table_mut(self.0.addr); - let old_size = table.size() as usize; + let old_size = table.size(); let init = table_value_to_element(table.kind.element_type, init)?; + let delta = usize::try_from(delta).map_err(|_| Trap::TableOutOfBounds { offset: 0, len: 1, max: old_size })?; table.grow(delta, init)?; Ok(old_size) } diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs index d02631b..2d34432 100644 --- a/crates/tinywasm/src/store/mod.rs +++ b/crates/tinywasm/src/store/mod.rs @@ -268,10 +268,13 @@ impl Store { } /// Add tables to the store, returning their addresses in the store - pub(crate) fn init_tables(&mut self, tables: &[TableType]) -> impl ExactSizeIterator<Item = TableAddr> { + pub(crate) fn init_tables(&mut self, tables: &[TableType]) -> Result<impl ExactSizeIterator<Item = TableAddr>> { let start = self.state.tables.len() as TableAddr; - self.state.tables.extend(tables.iter().map(|table| TableInstance::new(table.clone()))); - start..start + tables.len() as TableAddr + self.state.tables.reserve_exact(tables.len()); + for &table in tables { + self.state.tables.push(TableInstance::new(table)?); + } + Ok(start..start + tables.len() as TableAddr) } /// Add memories to the store, returning their addresses in the store @@ -374,6 +377,13 @@ impl Store { // 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. + let Ok(offset) = usize::try_from(offset) else { + return Ok(( + elem_addrs.into_boxed_slice(), + Some(Trap::TableOutOfBounds { offset: usize::MAX, len: init.len(), max: table.size() }), + )); + }; + if let Err(trap) = table.init(offset, &init) { return Ok((elem_addrs.into_boxed_slice(), Some(trap))); } @@ -449,11 +459,11 @@ impl Store { const_instrs: &[tinywasm_types::ConstInstruction], module_global_addrs: &[Addr], module_func_addrs: &[FuncAddr], - ) -> Result<i64> { + ) -> Result<u64> { let value = self.eval_const(const_instrs, module_global_addrs, module_func_addrs)?; match value { - TinyWasmValue::Value32(i) => Ok(i64::from(i)), - TinyWasmValue::Value64(i) => Ok(i as i64), + TinyWasmValue::Value32(i) => Ok(u64::from(i)), + TinyWasmValue::Value64(i) => Ok(i), other => Err(Error::Other(format!("expected i32 or i64, got {other:?}"))), } } diff --git a/crates/tinywasm/src/store/table.rs b/crates/tinywasm/src/store/table.rs index d3dbee7..d5f1019 100644 --- a/crates/tinywasm/src/store/table.rs +++ b/crates/tinywasm/src/store/table.rs @@ -1,9 +1,9 @@ use crate::{Result, Trap}; -use alloc::{vec, vec::Vec}; +use alloc::vec::Vec; use core::ops::Range; use tinywasm_types::*; -const MAX_TABLE_SIZE: u32 = 10_000_000; +const MAX_TABLE_SIZE: usize = 10_000_000; /// A WebAssembly Table Instance /// @@ -15,12 +15,19 @@ pub(crate) struct TableInstance { } impl TableInstance { - pub(crate) fn new(kind: TableType) -> Self { + pub(crate) fn new(kind: TableType) -> Result<Self> { Self::new_with_init(kind, TableElement::Uninitialized) } - pub(crate) fn new_with_init(kind: TableType, init: TableElement) -> Self { - Self { elements: vec![init; kind.size_initial as usize], kind } + pub(crate) fn new_with_init(kind: TableType, init: TableElement) -> Result<Self> { + let size = usize::try_from(kind.size_initial).map_err(|_| Trap::OutOfMemory)?; + if size > MAX_TABLE_SIZE { + return Err(Trap::OutOfMemory.into()); + } + let mut elements = Vec::new(); + elements.try_reserve_exact(size).map_err(|_| Trap::OutOfMemory)?; + elements.resize(size, init); + Ok(Self { elements, kind }) } #[inline(never)] @@ -37,7 +44,7 @@ impl TableInstance { Ok(addr..end) } - pub(crate) fn get_wasm_val(&self, addr: TableAddr) -> Result<WasmValue, Trap> { + pub(crate) fn get_wasm_val(&self, addr: usize) -> Result<WasmValue, Trap> { let val = self.get(addr)?.addr(); Ok(match self.kind.element_type { @@ -54,12 +61,8 @@ impl TableInstance { Ok(()) } - pub(crate) fn get(&self, addr: TableAddr) -> Result<&TableElement, Trap> { - self.elements.get(addr as usize).ok_or(Trap::TableOutOfBounds { - offset: addr as usize, - len: 1, - max: self.elements.len(), - }) + pub(crate) fn get(&self, addr: usize) -> Result<&TableElement, Trap> { + self.elements.get(addr).ok_or_else(|| self.trap_oob(addr, 1)) } pub(crate) fn copy_from_slice(&mut self, dst: usize, src: &[TableElement]) -> Result<(), Trap> { @@ -79,29 +82,27 @@ impl TableInstance { Ok(()) } - pub(crate) fn set(&mut self, table_idx: TableAddr, value: TableElement) -> Result<(), Trap> { - let range = self.checked_range(table_idx as usize, 1)?; + pub(crate) fn set(&mut self, table_idx: usize, value: TableElement) -> Result<(), Trap> { + let range = self.checked_range(table_idx, 1)?; self.elements[range.start] = value; Ok(()) } - pub(crate) fn grow(&mut self, n: i32, init: TableElement) -> Result<(), Trap> { - if n < 0 { - return Err(crate::Trap::TableOutOfBounds { offset: 0, len: 1, max: self.elements.len() }); - } - - let len = n as usize + self.elements.len(); - let max = self.kind.size_max.unwrap_or(MAX_TABLE_SIZE) as usize; + pub(crate) fn grow(&mut self, n: usize, init: TableElement) -> Result<(), Trap> { + let len = n.checked_add(self.elements.len()).ok_or(Trap::OutOfMemory)?; + let declared_max = self.kind.size_max.and_then(|max| usize::try_from(max).ok()).unwrap_or(usize::MAX); + let max = declared_max.min(MAX_TABLE_SIZE); if len > max { return Err(crate::Trap::TableOutOfBounds { offset: len, len: 1, max: self.elements.len() }); } + self.elements.try_reserve_exact(n).map_err(|_| Trap::OutOfMemory)?; self.elements.resize(len, init); Ok(()) } - pub(crate) fn size(&self) -> i32 { - self.elements.len() as i32 + pub(crate) fn size(&self) -> usize { + self.elements.len() } fn resolve_func_ref(&self, func_addrs: &[u32], addr: Addr) -> Addr { @@ -114,8 +115,7 @@ impl TableInstance { .expect("error initializing table: function not found. This should have been caught by the validator") } - pub(crate) fn init(&mut self, offset: i64, init: &[TableElement]) -> Result<(), Trap> { - let offset = offset as usize; + pub(crate) fn init(&mut self, offset: usize, init: &[TableElement]) -> Result<(), Trap> { let range = self.checked_range(offset, init.len())?; self.elements[range].copy_from_slice(init); Ok(()) @@ -157,23 +157,24 @@ impl TableElement { #[cfg(test)] mod tests { use super::*; + use alloc::vec; // Helper to create a dummy TableType fn dummy_table_type() -> TableType { - TableType { element_type: WasmType::RefFunc, size_initial: 10, size_max: Some(20) } + TableType::new(WasmType::RefFunc, 10, Some(20)) } #[test] fn test_table_instance_creation() { let kind = dummy_table_type(); - let table_instance = TableInstance::new(kind.clone()); - assert_eq!(table_instance.size(), kind.size_initial as i32, "Table instance creation failed: size mismatch"); + let table_instance = TableInstance::new(kind).unwrap(); + assert_eq!(table_instance.size() as u64, kind.size_initial, "Table instance creation failed: size mismatch"); } #[test] fn test_get_wasm_val() { let kind = dummy_table_type(); - let mut table_instance = TableInstance::new(kind); + let mut table_instance = TableInstance::new(kind).unwrap(); table_instance.set(0, TableElement::Initialized(0)).expect("Setting table element failed"); table_instance.set(1, TableElement::Uninitialized).expect("Setting table element failed"); @@ -197,7 +198,7 @@ mod tests { #[test] fn test_set_and_get() { let kind = dummy_table_type(); - let mut table_instance = TableInstance::new(kind); + let mut table_instance = TableInstance::new(kind).unwrap(); let result = table_instance.set(0, TableElement::Initialized(1)); assert!(result.is_ok(), "Setting table element failed"); @@ -212,7 +213,7 @@ mod tests { #[test] fn test_table_init() { let kind = dummy_table_type(); - let mut table_instance = TableInstance::new(kind); + let mut table_instance = TableInstance::new(kind).unwrap(); let init_elements = vec![TableElement::Initialized(0); 5]; let result = table_instance.init(0, &init_elements); diff --git a/crates/tinywasm/tests/wasm-custom/table64.wast b/crates/tinywasm/tests/wasm-custom/table64.wast new file mode 100644 index 0000000..dac1044 --- /dev/null +++ b/crates/tinywasm/tests/wasm-custom/table64.wast @@ -0,0 +1,51 @@ +;; Focused table64 execution and mixed-width operand coverage. +(module + (type $result (func (result i32))) + (func $seven (type $result) (i32.const 7)) + (func $nine (type $result) (i32.const 9)) + + (table $t64 i64 4 8 funcref) + (table $other64 i64 4 funcref) + (table $t32 4 funcref) + (elem (table $t64) (i64.const 1) func $seven) + (elem $passive func $nine) + + (func (export "size64") (result i64) + (table.size $t64)) + (func (export "grow64") (result i64) + (table.grow $t64 (ref.null func) (i64.const 2))) + (func (export "call-active") (result i32) + (call_indirect $t64 (type $result) (i64.const 1))) + (func (export "set-and-call") (result i32) + (table.set $t64 (i64.const 0) (ref.func $nine)) + (call_indirect $t64 (type $result) (i64.const 0))) + (func (export "init-and-call") (result i32) + (table.init $t64 $passive (i64.const 2) (i32.const 0) (i32.const 1)) + (call_indirect $t64 (type $result) (i64.const 2))) + (func (export "copy64-and-call") (result i32) + (table.copy $other64 $t64 (i64.const 0) (i64.const 1) (i64.const 1)) + (call_indirect $other64 (type $result) (i64.const 0))) + (func (export "copy-to-32-and-call") (result i32) + (table.copy $t32 $t64 (i32.const 0) (i64.const 1) (i32.const 1)) + (call_indirect $t32 (type $result) (i32.const 0))) + (func (export "copy-to-64-and-call") (result i32) + (table.copy $other64 $t32 (i64.const 1) (i32.const 0) (i32.const 1)) + (call_indirect $other64 (type $result) (i64.const 1))) + (func (export "fill64") + (table.fill $t64 (i64.const 1) (ref.null func) (i64.const 1))) + (func (export "get-oob") (result funcref) + (table.get $t64 (i64.const -1))) +) + +(assert_return (invoke "size64") (i64.const 4)) +(assert_return (invoke "call-active") (i32.const 7)) +(assert_return (invoke "set-and-call") (i32.const 9)) +(assert_return (invoke "init-and-call") (i32.const 9)) +(assert_return (invoke "copy64-and-call") (i32.const 7)) +(assert_return (invoke "copy-to-32-and-call") (i32.const 7)) +(assert_return (invoke "copy-to-64-and-call") (i32.const 7)) +(assert_return (invoke "grow64") (i64.const 4)) +(assert_return (invoke "size64") (i64.const 6)) +(assert_return (invoke "fill64")) +(assert_trap (invoke "call-active") "uninitialized element") +(assert_trap (invoke "get-oob") "out of bounds table access") diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index e918e46..fcac23c 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -499,22 +499,34 @@ impl Default for GlobalType { } } -#[derive(Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct TableType { + arch: MemoryArch, pub element_type: WasmType, - pub size_initial: u32, - pub size_max: Option<u32>, + pub size_initial: u64, + pub size_max: Option<u64>, } impl TableType { - pub fn empty() -> Self { - Self { element_type: WasmType::RefFunc, size_initial: 0, size_max: None } + pub const fn empty() -> Self { + Self::new(WasmType::RefFunc, 0, None) + } + + /// Create a table with 32-bit indices. + pub const fn new(element_type: WasmType, size_initial: u64, size_max: Option<u64>) -> Self { + Self { arch: MemoryArch::I32, element_type, size_initial, size_max } } - pub fn new(element_type: WasmType, size_initial: u32, size_max: Option<u32>) -> Self { - Self { element_type, size_initial, size_max } + /// Create a table with 64-bit indices. + pub const fn new64(element_type: WasmType, size_initial: u64, size_max: Option<u64>) -> Self { + Self { arch: MemoryArch::I64, element_type, size_initial, size_max } + } + + #[inline] + pub const fn arch(&self) -> MemoryArch { + self.arch } } |
