summaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorHenry <mail@henrygressmann.de>2026-05-02 21:14:02 +0200
committerHenry <mail@henrygressmann.de>2026-05-02 21:19:54 +0200
commitba1cf56464f8e5c3c7bcc2dc75f10aad2cd56e6b (patch)
treedd0bef203b31c494f48e72c1169f2f50ae7b6d21 /crates
parentd3c6b705a3b1035adda66a92131e9ddf59fb46ab (diff)
chore: add more tests, fix issues with import types, cleanup doom example
Signed-off-by: Henry <mail@henrygressmann.de>
Diffstat (limited to 'crates')
-rw-r--r--crates/parser/src/conversion.rs64
-rw-r--r--crates/parser/src/error.rs2
-rw-r--r--crates/parser/src/lib.rs20
-rw-r--r--crates/parser/src/module.rs10
-rw-r--r--crates/parser/src/visit.rs7
-rw-r--r--crates/tinywasm/src/error.rs34
-rw-r--r--crates/tinywasm/src/instance.rs53
-rw-r--r--crates/tinywasm/src/interpreter/executor.rs2
-rw-r--r--crates/tinywasm/src/reference.rs15
-rw-r--r--crates/tinywasm/src/store/memory/mod.rs8
-rw-r--r--crates/tinywasm/src/store/mod.rs8
-rw-r--r--crates/tinywasm/tests/import_linking.rs4
-rw-r--r--crates/tinywasm/tests/internal_refs.rs97
-rw-r--r--crates/tinywasm/tests/module_descriptors.rs63
-rw-r--r--crates/tinywasm/tests/store_ownership.rs46
-rw-r--r--crates/types/src/archive.rs2
-rw-r--r--crates/types/src/lib.rs72
-rw-r--r--crates/types/src/value.rs3
18 files changed, 433 insertions, 77 deletions
diff --git a/crates/parser/src/conversion.rs b/crates/parser/src/conversion.rs
index 487f992..8a12daa 100644
--- a/crates/parser/src/conversion.rs
+++ b/crates/parser/src/conversion.rs
@@ -3,7 +3,7 @@ use alloc::sync::Arc;
use crate::{Result, module::FunctionCode, visit::process_operators_and_validate};
use alloc::{boxed::Box, format, string::ToString, vec::Vec};
use tinywasm_types::*;
-use wasmparser::{FuncValidator, FuncValidatorAllocations, OperatorsReader, ValidatorResources};
+use wasmparser::{CompositeInnerType, FuncValidator, FuncValidatorAllocations, OperatorsReader, ValidatorResources};
pub(crate) fn convert_module_elements<'a, T: IntoIterator<Item = wasmparser::Result<wasmparser::Element<'a>>>>(
elements: T,
@@ -39,7 +39,7 @@ pub(crate) fn convert_module_element(element: wasmparser::Element<'_>) -> Result
.collect::<Result<Vec<_>>>()?
.into_boxed_slice();
- Ok(tinywasm_types::Element { kind, items, ty: convert_reftype(ty), range: element.range })
+ Ok(tinywasm_types::Element { kind, items, ty: convert_reftype(ty)?, range: element.range })
}
}
}
@@ -74,7 +74,7 @@ pub(crate) fn convert_module_import(import: wasmparser::Import<'_>) -> Result<Im
let kind = match import.ty {
wasmparser::TypeRef::Func(ty) => ImportKind::Function(ty),
wasmparser::TypeRef::Table(ty) => ImportKind::Table(TableType {
- element_type: convert_reftype(ty.element_type),
+ element_type: convert_reftype(ty.element_type)?,
size_initial: ty.initial.try_into().map_err(|_| {
crate::ParseError::UnsupportedOperator(format!("Table size initial is too large: {}", ty.initial))
})?,
@@ -87,7 +87,7 @@ pub(crate) fn convert_module_import(import: wasmparser::Import<'_>) -> Result<Im
}),
wasmparser::TypeRef::Memory(ty) => ImportKind::Memory(convert_module_memory(ty)),
wasmparser::TypeRef::Global(ty) => {
- ImportKind::Global(GlobalType::new(convert_valtype(&ty.content_type), ty.mutable))
+ ImportKind::Global(GlobalType::new(convert_valtype(&ty.content_type)?, ty.mutable))
}
wasmparser::TypeRef::Tag(ty) => {
return Err(crate::ParseError::UnsupportedOperator(format!("Unsupported import kind: {ty:?}")));
@@ -129,7 +129,7 @@ pub(crate) fn convert_module_table(table: wasmparser::Table<'_>) -> Result<Table
let size_max = table.ty.maximum.map(|max| max.try_into()).transpose();
let size_max =
size_max.map_err(|e| crate::ParseError::UnsupportedOperator(format!("Table size max is too large: {e}")))?;
- Ok(TableType { element_type: convert_reftype(table.ty.element_type), size_initial, size_max })
+ Ok(TableType { element_type: convert_reftype(table.ty.element_type)?, size_initial, size_max })
}
pub(crate) fn convert_module_globals(
@@ -139,7 +139,7 @@ pub(crate) fn convert_module_globals(
.into_iter()
.map(|global| {
let global = global?;
- let ty = convert_valtype(&global.ty.content_type);
+ let ty = convert_valtype(&global.ty.content_type)?;
let ops = global.init_expr.get_operators_reader();
Ok(Global { init: process_const_operators(ops)?, ty: GlobalType::new(ty, global.ty.mutable) })
})
@@ -211,27 +211,37 @@ pub(crate) fn convert_module_type(ty: wasmparser::RecGroup) -> Result<Arc<FuncTy
));
}
- let ty = types.next().unwrap().unwrap_func();
+ let ty = types.next().unwrap();
+ let CompositeInnerType::Func(ty) = &ty.composite_type.inner else {
+ return Err(crate::ParseError::UnsupportedOperator(format!(
+ "Unsupported non-function type in type section: {}",
+ ty.composite_type
+ )));
+ };
let params: Vec<_> = ty.params().iter().map(convert_valtype).collect();
- let results: Vec<_> = ty.results().iter().map(convert_valtype).collect();
+ let params = params.into_iter().collect::<Result<Vec<_>>>()?;
+ let results = ty.results().iter().map(convert_valtype).collect::<Result<Vec<_>>>()?;
Ok(FuncType::new(&params, &results).into())
}
-pub(crate) fn convert_reftype(reftype: wasmparser::RefType) -> WasmType {
+pub(crate) fn convert_reftype(reftype: wasmparser::RefType) -> Result<WasmType> {
match reftype {
- _ if reftype.is_func_ref() => WasmType::RefFunc,
- _ if reftype.is_extern_ref() => WasmType::RefExtern,
- _ => unimplemented!("Unsupported reference type: {:?}, {:?}", reftype, reftype.heap_type()),
+ _ if reftype.is_func_ref() => Ok(WasmType::RefFunc),
+ _ if reftype.is_extern_ref() => Ok(WasmType::RefExtern),
+ _ => Err(crate::ParseError::UnsupportedOperator(format!(
+ "Unsupported reference type: {reftype:?}, {:?}",
+ reftype.heap_type()
+ ))),
}
}
-pub(crate) fn convert_valtype(valtype: &wasmparser::ValType) -> WasmType {
+pub(crate) fn convert_valtype(valtype: &wasmparser::ValType) -> Result<WasmType> {
match valtype {
- wasmparser::ValType::I32 => WasmType::I32,
- wasmparser::ValType::I64 => WasmType::I64,
- wasmparser::ValType::F32 => WasmType::F32,
- wasmparser::ValType::F64 => WasmType::F64,
- wasmparser::ValType::V128 => WasmType::V128,
+ wasmparser::ValType::I32 => Ok(WasmType::I32),
+ wasmparser::ValType::I64 => Ok(WasmType::I64),
+ wasmparser::ValType::F32 => Ok(WasmType::F32),
+ wasmparser::ValType::F64 => Ok(WasmType::F64),
+ wasmparser::ValType::V128 => Ok(WasmType::V128),
wasmparser::ValType::Ref(r) => convert_reftype(*r),
}
}
@@ -247,10 +257,14 @@ pub(crate) fn process_const_operators(ops: OperatorsReader<'_>) -> Result<Box<[C
let mut out = Vec::with_capacity(ops.len().saturating_sub(1));
for op in ops.iter().take(ops.len() - 1) {
let instr = match op {
- wasmparser::Operator::RefNull { hty } => match convert_heaptype(*hty) {
+ wasmparser::Operator::RefNull { hty } => match convert_heaptype(*hty)? {
WasmType::RefFunc => ConstInstruction::RefFunc(None),
WasmType::RefExtern => ConstInstruction::RefExtern(None),
- _ => unimplemented!("Unsupported heap type: {:?}", hty),
+ other => {
+ return Err(crate::ParseError::UnsupportedOperator(format!(
+ "Unsupported ref.null heap type lowered to {other:?}"
+ )));
+ }
},
wasmparser::Operator::RefFunc { function_index } => ConstInstruction::RefFunc(Some(*function_index)),
wasmparser::Operator::I32Const { value } => ConstInstruction::I32Const(*value),
@@ -277,12 +291,14 @@ pub(crate) fn process_const_operators(ops: OperatorsReader<'_>) -> Result<Box<[C
Ok(out.into_boxed_slice())
}
-pub(crate) fn convert_heaptype(heap: wasmparser::HeapType) -> WasmType {
+pub(crate) fn convert_heaptype(heap: wasmparser::HeapType) -> Result<WasmType> {
match heap {
- wasmparser::HeapType::Abstract { shared: false, ty: wasmparser::AbstractHeapType::Func } => WasmType::RefFunc,
+ wasmparser::HeapType::Abstract { shared: false, ty: wasmparser::AbstractHeapType::Func } => {
+ Ok(WasmType::RefFunc)
+ }
wasmparser::HeapType::Abstract { shared: false, ty: wasmparser::AbstractHeapType::Extern } => {
- WasmType::RefExtern
+ Ok(WasmType::RefExtern)
}
- _ => unimplemented!("Unsupported heap type: {:?}", heap),
+ _ => Err(crate::ParseError::UnsupportedOperator(format!("Unsupported heap type: {heap:?}"))),
}
}
diff --git a/crates/parser/src/error.rs b/crates/parser/src/error.rs
index 22edd39..7b5dc34 100644
--- a/crates/parser/src/error.rs
+++ b/crates/parser/src/error.rs
@@ -2,7 +2,7 @@ use alloc::string::{String, ToString};
use core::fmt::{Debug, Display};
use wasmparser::Encoding;
-#[derive(Debug)]
+#[derive(Debug, PartialEq, Eq)]
/// Errors that can occur when parsing a WebAssembly module
pub enum ParseError {
/// An invalid type was encountered
diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs
index c4f6ff7..c6ae326 100644
--- a/crates/parser/src/lib.rs
+++ b/crates/parser/src/lib.rs
@@ -283,7 +283,25 @@ impl Parser {
continue;
}
- if eof || reader.end_reached {
+ if reader.end_reached {
+ if !buffer.is_empty() {
+ return Err(ParseError::Other("trailing bytes after end of module".into()));
+ }
+
+ if !eof {
+ let read_bytes = Self::read_more(&mut stream, &mut buffer, 1)?;
+ eof = read_bytes == 0;
+
+ if !eof {
+ return Err(ParseError::Other("trailing bytes after end of module".into()));
+ }
+ }
+
+ reader.process_pending_functions(&self.options)?;
+ return reader.into_module(&self.options);
+ }
+
+ if eof {
reader.process_pending_functions(&self.options)?;
return reader.into_module(&self.options);
}
diff --git a/crates/parser/src/module.rs b/crates/parser/src/module.rs
index 02a3297..5f5e5e2 100644
--- a/crates/parser/src/module.rs
+++ b/crates/parser/src/module.rs
@@ -377,6 +377,15 @@ impl<'a> ModuleReader<'a> {
};
let mut funcs = Vec::with_capacity(self.code.len());
+ let mut func_type_idxs = self
+ .imports
+ .iter()
+ .filter_map(|import| match import.kind {
+ ImportKind::Function(type_idx) => Some(type_idx),
+ _ => None,
+ })
+ .collect::<Vec<_>>();
+ func_type_idxs.extend(self.code_type_addrs.iter().copied());
for (code, ty_idx) in self.code.into_iter().zip(self.code_type_addrs) {
let ty = self.func_types.get(ty_idx as usize).expect("No func type for func, this is a bug").clone();
@@ -402,6 +411,7 @@ impl<'a> ModuleReader<'a> {
Ok(ModuleInner {
funcs: funcs.into(),
func_types: self.func_types.into(),
+ func_type_idxs: func_type_idxs.into(),
globals: self.globals.into(),
table_types: self.table_types.into(),
imports: self.imports.into(),
diff --git a/crates/parser/src/visit.rs b/crates/parser/src/visit.rs
index f8d5cfd..62db307 100644
--- a/crates/parser/src/visit.rs
+++ b/crates/parser/src/visit.rs
@@ -417,7 +417,12 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild
// Reference Types
fn visit_ref_null(&mut self, ty: wasmparser::HeapType) -> Self::Output {
- self.instructions.push(Instruction::RefNull(convert_heaptype(ty)));
+ match convert_heaptype(ty) {
+ Ok(ty) => self.instructions.push(Instruction::RefNull(ty)),
+ Err(err) => {
+ self.error.get_or_insert(err);
+ }
+ };
}
fn visit_typed_select_multi(&mut self, tys: Vec<wasmparser::ValType>) -> Self::Output {
diff --git a/crates/tinywasm/src/error.rs b/crates/tinywasm/src/error.rs
index 0d1f368..adac5a7 100644
--- a/crates/tinywasm/src/error.rs
+++ b/crates/tinywasm/src/error.rs
@@ -36,9 +36,6 @@ pub enum Error {
/// An invalid label type was encountered
InvalidLabelType,
- /// The store is not the one that the module instance was instantiated in
- InvalidStore,
-
#[cfg(feature = "std")]
/// An I/O error occurred
Io(crate::std::io::Error),
@@ -51,9 +48,27 @@ pub enum Error {
Twasm(TwasmError),
}
+impl PartialEq for Error {
+ fn eq(&self, other: &Self) -> bool {
+ match (self, other) {
+ (Self::Trap(a), Self::Trap(b)) => a == b,
+ (Self::Linker(a), Self::Linker(b)) => a == b,
+ (Self::UnsupportedFeature(a), Self::UnsupportedFeature(b)) => a == b,
+ (Self::Other(a), Self::Other(b)) => a == b,
+ #[cfg(feature = "std")]
+ (Self::Io(a), Self::Io(b)) => a.kind() == b.kind(),
+ #[cfg(feature = "parser")]
+ (Self::Parser(a), Self::Parser(b)) => a == b,
+ (Self::Twasm(a), Self::Twasm(b)) => a == b,
+ _ => false,
+ }
+ }
+}
+
/// Errors that can occur when linking a WebAssembly module
#[non_exhaustive]
#[cfg_attr(feature = "debug", derive(Debug))]
+#[derive(PartialEq, Eq)]
pub enum LinkingError {
/// An unknown import was encountered
UnknownImport {
@@ -120,6 +135,9 @@ pub enum Trap {
/// Invalid Integer Conversion
InvalidConversionToInt,
+ /// The store is not the one that the module instance was instantiated in
+ InvalidStore,
+
/// Integer Overflow
IntegerOverflow,
@@ -173,11 +191,18 @@ impl Trap {
Self::UninitializedElement { .. } => "uninitialized element",
Self::IndirectCallTypeMismatch { .. } => "indirect call type mismatch",
Self::HostFunction(_) => "host function trap",
+ Self::InvalidStore => "invalid store",
Self::Other(message) => message,
}
}
}
+impl PartialEq for Trap {
+ fn eq(&self, other: &Self) -> bool {
+ self.message() == other.message()
+ }
+}
+
impl LinkingError {
/// Get the message of the linking error
pub fn message(&self) -> &'static str {
@@ -199,6 +224,7 @@ impl From<TwasmError> for Error {
Self::Twasm(value)
}
}
+
impl From<Trap> for Error {
fn from(value: Trap) -> Self {
Self::Trap(value)
@@ -226,7 +252,6 @@ impl Display for Error {
}
#[cfg(not(feature = "debug"))]
Self::InvalidHostFnReturn { .. } => write!(f, "invalid host function return"),
- Self::InvalidStore => write!(f, "invalid store"),
}
}
}
@@ -264,6 +289,7 @@ impl Display for Trap {
Self::UninitializedElement { index } => {
write!(f, "uninitialized element: index={index}")
}
+ Self::InvalidStore => write!(f, "invalid store"),
#[cfg(feature = "debug")]
Self::IndirectCallTypeMismatch { expected, actual } => {
write!(f, "indirect call type mismatch: expected={expected:?}, actual={actual:?}")
diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs
index 8ef572d..e5a2d88 100644
--- a/crates/tinywasm/src/instance.rs
+++ b/crates/tinywasm/src/instance.rs
@@ -6,7 +6,7 @@ use alloc::{format, rc::Rc};
use tinywasm_types::*;
use crate::func::{FromWasmValueTuple, IntoWasmValueTuple, WasmTypesFromTuple};
-use crate::{Error, Function, FunctionTyped, Global, Imports, Memory, Result, Store, Table};
+use crate::{Error, Function, FunctionTyped, Global, Imports, Memory, Result, Store, Table, Trap};
/// A typed view over an exported extern value.
pub enum ExternItem {
@@ -34,6 +34,7 @@ struct ModuleInstanceInner {
store_id: usize,
idx: ModuleInstanceAddr,
types: Arc<[Arc<FuncType>]>,
+ func_type_idxs: Arc<[u32]>,
func_addrs: Box<[FuncAddr]>,
table_addrs: Box<[TableAddr]>,
mem_addrs: Box<[MemAddr]>,
@@ -50,12 +51,25 @@ impl ModuleInstance {
self.0.idx
}
+ /// Type indices come from the module type section and are used by indirect calls.
#[inline]
- pub(crate) fn func_ty(&self, addr: FuncAddr) -> &Arc<FuncType> {
- match self.0.types.get(addr as usize) {
+ pub(crate) fn func_type_by_type_index(&self, type_idx: u32) -> &Arc<FuncType> {
+ match self.0.types.get(type_idx as usize) {
Some(ty) => ty,
None => {
cold_path();
+ unreachable!("invalid type index: {type_idx}")
+ }
+ }
+ }
+
+ /// Function indices need their own lookup because they are not type-section indices.
+ #[inline]
+ pub(crate) fn func_type_idx(&self, addr: FuncAddr) -> u32 {
+ match self.0.func_type_idxs.get(addr as usize) {
+ Some(idx) => *idx,
+ None => {
+ cold_path();
unreachable!("invalid function address: {addr}")
}
}
@@ -66,7 +80,7 @@ impl ModuleInstance {
&self.0.func_addrs
}
- // resolve a function address to the global store address
+ /// resolve a function address to the global store address
#[inline]
pub(crate) fn resolve_func_addr(&self, addr: FuncAddr) -> FuncAddr {
match self.0.func_addrs.get(addr as usize) {
@@ -78,7 +92,7 @@ impl ModuleInstance {
}
}
- // resolve a table address to the global store address
+ /// resolve a table address to the global store address
#[inline]
pub(crate) fn resolve_table_addr(&self, addr: TableAddr) -> TableAddr {
match self.0.table_addrs.get(addr as usize) {
@@ -90,7 +104,7 @@ impl ModuleInstance {
}
}
- // resolve a memory address to the global store address
+ /// resolve a memory address to the global store address
#[inline]
pub(crate) fn resolve_mem_addr(&self, addr: MemAddr) -> MemAddr {
match self.0.mem_addrs.get(addr as usize) {
@@ -102,7 +116,7 @@ impl ModuleInstance {
}
}
- // resolve a data address to the global store address
+ /// resolve a data address to the global store address
#[inline]
pub(crate) fn resolve_data_addr(&self, addr: DataAddr) -> DataAddr {
match self.0.data_addrs.get(addr as usize) {
@@ -114,7 +128,7 @@ impl ModuleInstance {
}
}
- // resolve an element address to the global store address
+ /// resolve an element address to the global store address
#[inline]
pub(crate) fn resolve_elem_addr(&self, addr: ElemAddr) -> ElemAddr {
match self.0.elem_addrs.get(addr as usize) {
@@ -126,7 +140,7 @@ impl ModuleInstance {
}
}
- // resolve a global address to the global store address
+ /// resolve a global address to the global store address
#[inline]
pub(crate) fn resolve_global_addr(&self, addr: GlobalAddr) -> GlobalAddr {
match self.0.global_addrs.get(addr as usize) {
@@ -142,7 +156,7 @@ impl ModuleInstance {
pub(crate) fn validate_store(&self, store: &Store) -> Result<()> {
if self.0.store_id != store.id() {
cold_path();
- return Err(Error::InvalidStore);
+ return Err(Trap::InvalidStore.into());
}
Ok(())
}
@@ -167,7 +181,6 @@ impl ModuleInstance {
pub fn instantiate_no_start(store: &mut Store, module: &Module, imports: Option<Imports>) -> Result<Self> {
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));
match module.local_memory_allocation {
@@ -186,6 +199,7 @@ impl ModuleInstance {
store_id: store.id(),
idx,
types: module.func_types.clone(),
+ func_type_idxs: module.func_type_idxs.clone(),
func_addrs: addrs.funcs.into_boxed_slice(),
table_addrs: addrs.tables.into_boxed_slice(),
mem_addrs: addrs.memories.into_boxed_slice(),
@@ -230,7 +244,7 @@ impl ModuleInstance {
item: crate::StoreItem::new(self.0.store_id, func_addr),
module_addr: self.id(),
addr: func_addr,
- ty: self.func_ty(export.index).clone(),
+ ty: self.func_type_by_type_index(self.func_type_idx(export.index)).clone(),
})
}
ExternalKind::Table => {
@@ -281,7 +295,7 @@ impl ModuleInstance {
item: crate::StoreItem::new(self.0.store_id, addr),
module_addr: self.id(),
addr,
- ty: self.func_ty(export.index).clone(),
+ ty: self.func_type_by_type_index(self.func_type_idx(export.index)).clone(),
}))
}
ExternVal::Memory(addr) => Ok(ExternItem::Memory(Memory::from_store_addr(self.0.store_id, addr))),
@@ -461,19 +475,24 @@ impl ModuleInstance {
pub fn start_func(&self, store: &Store) -> Result<Option<Function>> {
self.validate_store(store)?;
- let func_index = match self.0.func_start {
+ let func_addr = match self.0.func_start {
Some(func_index) => func_index,
None => {
- // alternatively, check for a _start function in the exports
+ // Alternatively, check for a _start function in the exports.
let Some(ExternVal::Func(func_addr)) = self.export_addr("_start") else {
return Ok(None);
};
- func_addr
+ return Ok(Some(Function {
+ item: crate::StoreItem::new(self.0.store_id, func_addr),
+ module_addr: self.id(),
+ addr: func_addr,
+ ty: store.state.get_func(func_addr).ty().clone(),
+ }));
}
};
- let func_addr = self.resolve_func_addr(func_index);
+ let func_addr = self.resolve_func_addr(func_addr);
Ok(Some(Function {
item: crate::StoreItem::new(self.0.store_id, func_addr),
module_addr: self.id(),
diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs
index 4d862d6..4c941c8 100644
--- a/crates/tinywasm/src/interpreter/executor.rs
+++ b/crates/tinywasm/src/interpreter/executor.rs
@@ -1046,7 +1046,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> {
return Err(Trap::UninitializedElement { index: table_idx as usize });
};
- let call_ty = self.module.func_ty(type_addr);
+ let call_ty = self.module.func_type_by_type_index(type_addr);
match self.store.state.get_func(func_ref) {
crate::FunctionInstance::Wasm(wasm_func) => {
if wasm_func.ty() != call_ty {
diff --git a/crates/tinywasm/src/reference.rs b/crates/tinywasm/src/reference.rs
index 5f8d46f..8f02317 100644
--- a/crates/tinywasm/src/reference.rs
+++ b/crates/tinywasm/src/reference.rs
@@ -7,8 +7,7 @@ use alloc::{ffi::CString, format};
use crate::store::{GlobalInstance, TableElement, TableInstance};
use crate::{Error, MemoryInstance, Result, Store, Trap};
use tinywasm_types::{
- Addr, ExternRef, FuncRef, GlobalAddr, GlobalType, MemAddr, MemoryArch, MemoryType, TableAddr, TableType, WasmType,
- WasmValue,
+ Addr, ExternRef, FuncRef, GlobalAddr, GlobalType, MemAddr, MemoryType, TableAddr, TableType, WasmType, WasmValue,
};
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
@@ -27,7 +26,7 @@ impl StoreItem {
#[inline]
pub(crate) fn validate_store(&self, store: &Store) -> Result<(), Trap> {
if self.store_id != store.id() {
- return Err(Trap::Other("invalid store"));
+ return Err(Trap::InvalidStore);
}
Ok(())
}
@@ -146,9 +145,6 @@ impl Memory {
/// Create a new memory in the given store.
pub fn new(store: &mut Store, ty: MemoryType) -> Result<Self> {
- if let MemoryArch::I64 = ty.arch() {
- return Err(Error::UnsupportedFeature("64-bit memories"));
- }
let addr = store.state.memories.len() as MemAddr;
store.state.memories.push(MemoryInstance::new(ty, &store.engine.config().memory_backend)?);
Ok(Self::from_store_addr(store.id(), addr))
@@ -399,7 +395,8 @@ impl Table {
/// Grow the table and return the previous size.
pub fn grow(&self, store: &mut Store, delta: i32, init: WasmValue) -> Result<usize> {
- let table = self.instance_mut(store)?;
+ self.0.validate_store(store)?;
+ let table = store.state.get_table_mut(self.0.addr);
let old_size = table.size() as usize;
let init = table_value_to_element(table.kind.element_type, init)?;
table.grow(delta, init)?;
@@ -415,6 +412,10 @@ impl Global {
/// Create a new global in the given store.
pub fn new(store: &mut Store, ty: GlobalType, value: WasmValue) -> Result<Self> {
+ if WasmType::from(value) != ty.ty {
+ cold_path();
+ return Err(Error::Other("invalid global value type".to_string()));
+ }
let addr = store.state.globals.len() as GlobalAddr;
store.state.globals.push(GlobalInstance::new(ty, value.into()));
Ok(Self::from_store_addr(store.id(), addr))
diff --git a/crates/tinywasm/src/store/memory/mod.rs b/crates/tinywasm/src/store/memory/mod.rs
index 50ad520..2e58939 100644
--- a/crates/tinywasm/src/store/memory/mod.rs
+++ b/crates/tinywasm/src/store/memory/mod.rs
@@ -103,8 +103,8 @@ pub trait LinearMemory {
let mut offset = 0;
while offset < len {
let chunk_len = min(len - offset, 1024);
- let chunk = vec![0; chunk_len];
- self.read_exact(src + offset, &mut chunk.clone())?;
+ let mut chunk = vec![0; chunk_len];
+ self.read_exact(src + offset, &mut chunk)?;
self.write_all(dst + offset, &chunk)?;
offset += chunk_len;
}
@@ -114,8 +114,8 @@ pub trait LinearMemory {
while offset > 0 {
let chunk_len = min(offset, 1024);
offset -= chunk_len;
- let chunk = vec![0; chunk_len];
- self.read_exact(src + offset, &mut chunk.clone())?;
+ let mut chunk = vec![0; chunk_len];
+ self.read_exact(src + offset, &mut chunk)?;
self.write_all(dst + offset, &chunk)?;
}
}
diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs
index 2b8ba73..a4d26e3 100644
--- a/crates/tinywasm/src/store/mod.rs
+++ b/crates/tinywasm/src/store/mod.rs
@@ -249,7 +249,7 @@ impl State {
}
/// Get the global at the actual index in the store
- pub(crate) fn get_global_val(&self, addr: MemAddr) -> TinyWasmValue {
+ pub(crate) fn get_global_val(&self, addr: GlobalAddr) -> TinyWasmValue {
match self.globals.get(addr as usize) {
Some(global) => global.value.get(),
None => {
@@ -260,7 +260,7 @@ impl State {
}
/// Set the global at the actual index in the store
- pub(crate) fn set_global_val(&mut self, addr: MemAddr, value: TinyWasmValue) {
+ pub(crate) fn set_global_val(&mut self, addr: GlobalAddr, value: TinyWasmValue) {
match self.globals.get_mut(addr as usize) {
Some(global) => global.value.set(value),
None => {
@@ -288,13 +288,13 @@ impl Store {
/// Get the global at the actual index in the store
#[doc(hidden)]
- pub fn get_global_val(&self, addr: MemAddr) -> TinyWasmValue {
+ pub fn get_global_val(&self, addr: GlobalAddr) -> TinyWasmValue {
self.state.get_global_val(addr)
}
/// Set the global at the actual index in the store
#[doc(hidden)]
- pub fn set_global_val(&mut self, addr: MemAddr, value: TinyWasmValue) {
+ pub fn set_global_val(&mut self, addr: GlobalAddr, value: TinyWasmValue) {
self.state.set_global_val(addr, value);
}
}
diff --git a/crates/tinywasm/tests/import_linking.rs b/crates/tinywasm/tests/import_linking.rs
index 712148e..e7948fa 100644
--- a/crates/tinywasm/tests/import_linking.rs
+++ b/crates/tinywasm/tests/import_linking.rs
@@ -1,5 +1,5 @@
use eyre::Result;
-use tinywasm::{Error, Imports, Module, ModuleInstance, Store};
+use tinywasm::{Error, Imports, Module, ModuleInstance, Store, Trap};
const WASM_ADD: &str = r#"
(module
@@ -52,6 +52,6 @@ fn link_module_rejects_cross_store_instance() -> Result<()> {
imports.link_module("adder", add_instance)?;
let err = ModuleInstance::instantiate(&mut target_store, &import_module, Some(imports)).unwrap_err();
- assert!(matches!(err, Error::InvalidStore));
+ assert_eq!(err, Error::from(Trap::InvalidStore));
Ok(())
}
diff --git a/crates/tinywasm/tests/internal_refs.rs b/crates/tinywasm/tests/internal_refs.rs
index 9e96d4c..2382597 100644
--- a/crates/tinywasm/tests/internal_refs.rs
+++ b/crates/tinywasm/tests/internal_refs.rs
@@ -94,3 +94,100 @@ fn extern_item_lookup_returns_expected_kinds() -> Result<()> {
Ok(())
}
+
+#[test]
+fn extern_item_and_exports_use_actual_function_type() -> Result<()> {
+ let wasm = wat::parse_str(
+ r#"
+ (module
+ (type $local_ty (func))
+ (type $import_ty (func (param i64)))
+ (import "host" "imported" (func (type $import_ty)))
+ (func (export "f") (type $local_ty)
+ nop)
+ )
+ "#,
+ )?;
+
+ let module = tinywasm::parse_bytes(&wasm)?;
+ let mut store = Store::default();
+ let mut imports = tinywasm::Imports::new();
+ imports.define(
+ "host",
+ "imported",
+ tinywasm::HostFunction::from(&mut store, |_ctx: tinywasm::FuncContext<'_>, _arg: i64| Ok(())),
+ );
+ let instance = ModuleInstance::instantiate(&mut store, &module, Some(imports))?;
+
+ let ExternItem::Func(func) = instance.extern_item("f")? else { panic!("expected function export") };
+ assert_eq!(func.call(&mut store, &[])?, vec![]);
+
+ let (_, ExternItem::Func(func)) = instance.exports().find(|(name, _)| *name == "f").expect("export f not found")
+ else {
+ panic!("expected function export")
+ };
+ assert_eq!(func.call(&mut store, &[])?, vec![]);
+
+ Ok(())
+}
+
+#[test]
+fn export_func_type_index_mismatch_fixture_would_break_old_lookup() -> Result<()> {
+ let wasm = wat::parse_str(
+ r#"
+ (module
+ (type $local_ty (func))
+ (type $import_ty (func (param i64)))
+ (import "spectest" "print_i64" (func (type $import_ty)))
+ (func (export "f") (type $local_ty)
+ nop)
+ )
+ "#,
+ )?;
+ let module = tinywasm::parse_bytes(&wasm)?;
+
+ let export = module.exports.iter().find(|export| export.name.as_ref() == "f").expect("export f not found");
+ let old_lookup_ty = module.func_types.get(export.index as usize).expect("old lookup type missing");
+
+ assert_eq!(old_lookup_ty.params(), &[tinywasm::types::WasmType::I64]);
+ assert_eq!(module.funcs[0].ty.params(), &[]);
+ assert_ne!(old_lookup_ty.params(), module.funcs[0].ty.params());
+
+ let mut store = Store::default();
+ let mut imports = tinywasm::Imports::new();
+ imports.define(
+ "spectest",
+ "print_i64",
+ tinywasm::HostFunction::from(&mut store, |_ctx: tinywasm::FuncContext<'_>, _arg: i64| Ok(())),
+ );
+ let instance = ModuleInstance::instantiate(&mut store, &module, Some(imports))?;
+
+ let ExternItem::Func(func) = instance.extern_item("f")? else { panic!("expected function export") };
+ assert_eq!(func.call(&mut store, &[])?, vec![]);
+
+ Ok(())
+}
+
+#[test]
+fn start_prefers_exported_start_without_re_resolving_store_addr() -> Result<()> {
+ let wasm = wat::parse_str(
+ r#"
+ (module
+ (global (export "g") (mut i32) (i32.const 0))
+ (func (export "_start")
+ i32.const 1
+ global.set 0)
+ )
+ "#,
+ )?;
+
+ let module = tinywasm::parse_bytes(&wasm)?;
+ let mut store = Store::default();
+ let _unused = tinywasm::HostFunction::from(&mut store, |_ctx: tinywasm::FuncContext<'_>, (): ()| Ok(()));
+ let instance = ModuleInstance::instantiate_no_start(&mut store, &module, None)?;
+
+ instance.start(&mut store)?;
+ assert_eq!(instance.global_get(&store, "g")?, WasmValue::I32(1));
+
+ Ok(())
+}
diff --git a/crates/tinywasm/tests/module_descriptors.rs b/crates/tinywasm/tests/module_descriptors.rs
index 3804df8..d7fabfa 100644
--- a/crates/tinywasm/tests/module_descriptors.rs
+++ b/crates/tinywasm/tests/module_descriptors.rs
@@ -89,3 +89,66 @@ fn module_descriptors_resolve_imported_and_local_export_types() -> Result<()> {
Ok(())
}
+
+#[test]
+fn module_descriptors_resolve_imported_and_local_table_and_memory_exports() -> Result<()> {
+ let wasm = wat::parse_str(
+ r#"
+ (module
+ (import "host" "itable" (table 2 4 funcref))
+ (import "host" "imemory" (memory 1 3))
+ (table $ltable 5 7 funcref)
+ (memory $lmemory 2 6)
+ (export "itable_export" (table 0))
+ (export "imemory_export" (memory 0))
+ (export "ltable_export" (table 1))
+ (export "lmemory_export" (memory 1))
+ )
+ "#,
+ )?;
+
+ let module = tinywasm::parse_bytes(&wasm)?;
+ let exports: Vec<_> = module.exports().collect();
+
+ let itable_export = exports.iter().find(|export| export.name == "itable_export").expect("itable export not found");
+ match itable_export.ty {
+ ExportType::Table(ty) => {
+ assert_eq!(ty.element_type, WasmType::RefFunc);
+ assert_eq!(ty.size_initial, 2);
+ assert_eq!(ty.size_max, Some(4));
+ }
+ _ => panic!("itable export should resolve to imported table type"),
+ }
+
+ let imemory_export =
+ exports.iter().find(|export| export.name == "imemory_export").expect("imemory export not found");
+ match imemory_export.ty {
+ ExportType::Memory(ty) => {
+ assert_eq!(ty.page_count_initial(), 1);
+ assert_eq!(ty.page_count_max(), 3);
+ }
+ _ => panic!("imemory export should resolve to imported memory type"),
+ }
+
+ let ltable_export = exports.iter().find(|export| export.name == "ltable_export").expect("ltable export not found");
+ match ltable_export.ty {
+ ExportType::Table(ty) => {
+ assert_eq!(ty.element_type, WasmType::RefFunc);
+ assert_eq!(ty.size_initial, 5);
+ assert_eq!(ty.size_max, Some(7));
+ }
+ _ => panic!("ltable export should resolve to local table type"),
+ }
+
+ let lmemory_export =
+ exports.iter().find(|export| export.name == "lmemory_export").expect("lmemory export not found");
+ match lmemory_export.ty {
+ ExportType::Memory(ty) => {
+ assert_eq!(ty.page_count_initial(), 2);
+ assert_eq!(ty.page_count_max(), 6);
+ }
+ _ => panic!("lmemory export should resolve to local memory type"),
+ }
+
+ Ok(())
+}
diff --git a/crates/tinywasm/tests/store_ownership.rs b/crates/tinywasm/tests/store_ownership.rs
index acf8e42..61cc88c 100644
--- a/crates/tinywasm/tests/store_ownership.rs
+++ b/crates/tinywasm/tests/store_ownership.rs
@@ -38,7 +38,51 @@ fn memory_access_rejects_wrong_store() -> Result<()> {
let memory = instance.memory("memory")?;
let other_store = Store::default();
let err = memory.len(&other_store).unwrap_err();
- assert!(err.to_string().contains("invalid store"));
+ assert_eq!(err, tinywasm::Error::Trap(tinywasm::Trap::InvalidStore));
+
+ Ok(())
+}
+
+#[test]
+fn global_access_rejects_wrong_store() -> Result<()> {
+ let wasm = wat::parse_str(
+ r#"
+ (module
+ (global (export "g") (mut i32) (i32.const 1))
+ )
+ "#,
+ )?;
+ let module = tinywasm::parse_bytes(&wasm)?;
+
+ let mut owner_store = Store::default();
+ let instance = ModuleInstance::instantiate(&mut owner_store, &module, None)?;
+ let global = instance.global("g")?;
+
+ let other_store = Store::default();
+ let err = global.get(&other_store).unwrap_err();
+ assert_eq!(err, tinywasm::Error::Trap(tinywasm::Trap::InvalidStore));
+
+ Ok(())
+}
+
+#[test]
+fn table_grow_rejects_wrong_store_with_invalid_store_error() -> Result<()> {
+ let wasm = wat::parse_str(
+ r#"
+ (module
+ (table (export "t") 1 funcref)
+ )
+ "#,
+ )?;
+ let module = tinywasm::parse_bytes(&wasm)?;
+
+ let mut owner_store = Store::default();
+ let instance = ModuleInstance::instantiate(&mut owner_store, &module, None)?;
+ let table = instance.table("t")?;
+
+ let mut other_store = Store::default();
+ let err = table.grow(&mut other_store, 1, tinywasm::types::FuncRef::null().into()).unwrap_err();
+ assert_eq!(err, tinywasm::Error::Trap(tinywasm::Trap::InvalidStore));
Ok(())
}
diff --git a/crates/types/src/archive.rs b/crates/types/src/archive.rs
index 2636230..92bb87a 100644
--- a/crates/types/src/archive.rs
+++ b/crates/types/src/archive.rs
@@ -23,7 +23,7 @@ fn validate_magic(wasm: &[u8]) -> Result<usize, TwasmError> {
Ok(TWASM_MAGIC.len())
}
-#[derive(Debug)]
+#[derive(Debug, PartialEq, Eq)]
pub enum TwasmError {
InvalidMagic,
InvalidVersion,
diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs
index b65e4b7..8057518 100644
--- a/crates/types/src/lib.rs
+++ b/crates/types/src/lib.rs
@@ -101,6 +101,9 @@ pub struct ModuleInner {
/// Corresponds to the `type` section of the original WebAssembly module.
pub func_types: Arc<[Arc<FuncType>]>,
+ /// Function index to type index mapping in module index space, including imports.
+ pub func_type_idxs: Arc<[u32]>,
+
/// Exported items of the WebAssembly module.
///
/// Corresponds to the `export` section of the original WebAssembly module.
@@ -161,6 +164,22 @@ impl Module {
///
/// The returned data mirrors the module's export section and preserves order.
pub fn exports(&self) -> impl Iterator<Item = ModuleExport<'_>> {
+ fn imported_count(module: &ModuleInner, kind: ExternalKind) -> usize {
+ module
+ .imports
+ .iter()
+ .filter(|import| {
+ matches!(
+ (kind, &import.kind),
+ (ExternalKind::Func, ImportKind::Function(_))
+ | (ExternalKind::Table, ImportKind::Table(_))
+ | (ExternalKind::Memory, ImportKind::Memory(_))
+ | (ExternalKind::Global, ImportKind::Global(_))
+ )
+ })
+ .count()
+ }
+
fn imported_func_type(module: &ModuleInner, function_index: usize) -> Option<&FuncType> {
let mut seen = 0usize;
for import in module.imports.iter() {
@@ -174,6 +193,32 @@ impl Module {
None
}
+ fn imported_table_type(module: &ModuleInner, table_index: usize) -> Option<&TableType> {
+ let mut seen = 0usize;
+ for import in module.imports.iter() {
+ if let ImportKind::Table(table_ty) = &import.kind {
+ if seen == table_index {
+ return Some(table_ty);
+ }
+ seen += 1;
+ }
+ }
+ None
+ }
+
+ fn imported_memory_type(module: &ModuleInner, memory_index: usize) -> Option<&MemoryType> {
+ let mut seen = 0usize;
+ for import in module.imports.iter() {
+ if let ImportKind::Memory(memory_ty) = &import.kind {
+ if seen == memory_index {
+ return Some(memory_ty);
+ }
+ seen += 1;
+ }
+ }
+ None
+ }
+
fn imported_global_type(module: &Module, global_index: usize) -> Option<&GlobalType> {
let mut seen = 0usize;
for import in module.imports.iter() {
@@ -188,12 +233,10 @@ impl Module {
}
self.0.exports.iter().filter_map(move |export| {
- let imports = self.0.imports.iter();
let idx = export.index as usize;
let ty = match export.kind {
ExternalKind::Func => {
- let imported_funcs =
- imports.filter(|import| matches!(import.kind, ImportKind::Function(_))).count();
+ let imported_funcs = imported_count(&self.0, ExternalKind::Func);
if idx < imported_funcs {
ExportType::Func(imported_func_type(&self.0, idx)?)
} else {
@@ -201,11 +244,26 @@ impl Module {
ExportType::Func(&self.0.funcs.get(local_idx)?.ty)
}
}
- ExternalKind::Table => ExportType::Table(self.0.table_types.get(idx)?),
- ExternalKind::Memory => ExportType::Memory(self.0.memory_types.get(idx)?),
+ ExternalKind::Table => {
+ let imported_tables = imported_count(&self.0, ExternalKind::Table);
+ if idx < imported_tables {
+ ExportType::Table(imported_table_type(&self.0, idx)?)
+ } else {
+ let local_idx = idx - imported_tables;
+ ExportType::Table(self.0.table_types.get(local_idx)?)
+ }
+ }
+ ExternalKind::Memory => {
+ let imported_memories = imported_count(&self.0, ExternalKind::Memory);
+ if idx < imported_memories {
+ ExportType::Memory(imported_memory_type(&self.0, idx)?)
+ } else {
+ let local_idx = idx - imported_memories;
+ ExportType::Memory(self.0.memory_types.get(local_idx)?)
+ }
+ }
ExternalKind::Global => {
- let imported_globals =
- imports.filter(|import| matches!(import.kind, ImportKind::Global(_))).count();
+ let imported_globals = imported_count(&self.0, ExternalKind::Global);
if idx < imported_globals {
ExportType::Global(imported_global_type(self, idx)?)
} else {
diff --git a/crates/types/src/value.rs b/crates/types/src/value.rs
index 20c822e..c57edc0 100644
--- a/crates/types/src/value.rs
+++ b/crates/types/src/value.rs
@@ -341,8 +341,7 @@ impl From<WasmValue> for WasmType {
}
/// Type of a WebAssembly value.
-#[derive(Clone, Copy, PartialEq, Eq)]
-#[cfg_attr(feature = "debug", derive(Debug))]
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
pub enum WasmType {
/// A 32-bit integer.