summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/cli/src/output.rs6
-rw-r--r--crates/cli/src/wast_runner.rs6
-rw-r--r--crates/parser/src/conversion.rs26
-rw-r--r--crates/parser/src/module.rs14
-rw-r--r--crates/parser/src/visit.rs15
-rw-r--r--crates/tinywasm/src/imports.rs5
-rw-r--r--crates/tinywasm/src/instance.rs2
-rw-r--r--crates/tinywasm/src/interpreter/executor.rs127
-rw-r--r--crates/tinywasm/src/reference.rs13
-rw-r--r--crates/tinywasm/src/store/mod.rs22
-rw-r--r--crates/tinywasm/src/store/table.rs63
-rw-r--r--crates/tinywasm/tests/wasm-custom/table64.wast51
-rw-r--r--crates/types/src/lib.rs26
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
}
}