summaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/cli/src/bin.rs2
-rw-r--r--crates/parser/src/visit.rs8
-rw-r--r--crates/tinywasm/Cargo.toml7
-rw-r--r--crates/tinywasm/benches/argon2id.rs13
-rw-r--r--crates/tinywasm/benches/fibonacci.rs15
-rw-r--r--crates/tinywasm/benches/tinywasm.rs5
-rw-r--r--crates/tinywasm/benches/tinywasm_modes.rs2
-rw-r--r--crates/tinywasm/src/func.rs9
-rw-r--r--crates/tinywasm/src/imports.rs93
-rw-r--r--crates/tinywasm/src/instance.rs364
-rw-r--r--crates/tinywasm/src/interpreter/executor.rs36
-rw-r--r--crates/tinywasm/src/lib.rs12
-rw-r--r--crates/tinywasm/src/module.rs133
-rw-r--r--crates/tinywasm/src/reference.rs183
-rw-r--r--crates/tinywasm/src/store/mod.rs23
-rw-r--r--crates/tinywasm/src/store/table.rs6
-rw-r--r--crates/tinywasm/tests/host_func_signature_check.rs2
-rw-r--r--crates/tinywasm/tests/import_linking.rs57
-rw-r--r--crates/tinywasm/tests/imported_table_init.rs35
-rw-r--r--crates/tinywasm/tests/internal_refs.rs101
-rw-r--r--crates/tinywasm/tests/memory_ref_api.rs25
-rw-r--r--crates/tinywasm/tests/module_descriptors.rs91
-rw-r--r--crates/tinywasm/tests/resume_execution.rs12
-rw-r--r--crates/tinywasm/tests/store_ownership.rs43
-rw-r--r--crates/tinywasm/tests/testsuite/run.rs62
-rw-r--r--crates/tinywasm/tests/testsuite/util.rs8
-rw-r--r--crates/tinywasm/tests/typed_lookup.rs48
-rw-r--r--crates/types/src/instructions.rs4
28 files changed, 1246 insertions, 153 deletions
diff --git a/crates/cli/src/bin.rs b/crates/cli/src/bin.rs
index 235498c..814edd4 100644
--- a/crates/cli/src/bin.rs
+++ b/crates/cli/src/bin.rs
@@ -109,7 +109,7 @@ fn run(module: Module, func: Option<String>, args: &[WasmValue]) -> Result<()> {
let instance = module.instantiate(&mut store, None)?;
if let Some(func) = func {
- let func = instance.exported_func_untyped(&store, &func)?;
+ let func = instance.func(&store, &func)?;
let res = func.call(&mut store, args)?;
info!("{res:?}");
}
diff --git a/crates/parser/src/visit.rs b/crates/parser/src/visit.rs
index 8313d9f..94cb4ea 100644
--- a/crates/parser/src/visit.rs
+++ b/crates/parser/src/visit.rs
@@ -200,7 +200,7 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild
visit_ref_func(RefFunc, u32), visit_table_fill(TableFill, u32), visit_table_get(TableGet, u32), visit_table_set(TableSet, u32), visit_table_grow(TableGrow, u32), visit_table_size(TableSize, u32),
// Bulk Memory
- visit_memory_init(MemoryInit, u32, u32), visit_memory_fill(MemoryFill, u32), visit_memory_copy(MemoryCopy, u32, u32), visit_table_init(TableInit, u32, u32), visit_data_drop(DataDrop, u32), visit_elem_drop(ElemDrop, u32),
+ visit_memory_init(MemoryInit, u32, u32), visit_memory_fill(MemoryFill, u32), visit_table_init(TableInit, u32, u32), visit_data_drop(DataDrop, u32), visit_elem_drop(ElemDrop, u32),
// Wide Arithmetic
visit_i64_add128(I64Add128), visit_i64_sub128(I64Sub128), visit_i64_mul_wide_s(I64MulWideS), visit_i64_mul_wide_u(I64MulWideU)
@@ -440,7 +440,11 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild
}
fn visit_table_copy(&mut self, dst_table: u32, src_table: u32) -> Self::Output {
- self.instructions.push(Instruction::TableCopy { from: src_table, to: dst_table });
+ self.instructions.push(Instruction::TableCopy { dst_table, src_table });
+ }
+
+ fn visit_memory_copy(&mut self, dst_mem: u32, src_mem: u32) -> Self::Output {
+ self.instructions.push(Instruction::MemoryCopy { dst_mem, src_mem });
}
// Reference Types
diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml
index 2145569..62b592b 100644
--- a/crates/tinywasm/Cargo.toml
+++ b/crates/tinywasm/Cargo.toml
@@ -15,6 +15,10 @@ readme="../../README.md"
name="tinywasm"
path="src/lib.rs"
+[package.metadata.docs.rs]
+features=["std", "parser", "archive", "log", "canonicalize_nans", "debug", "guest_debug"]
+rustdoc-args=["--cfg", "docsrs"]
+
[dependencies]
log={workspace=true, optional=true}
tinywasm-parser={version="0.9.0-alpha.0", path="../parser", default-features=false, optional=true}
@@ -51,6 +55,9 @@ canonicalize_nans=[]
# derive Debug for runtime/types structs
debug=["tinywasm-types/debug"]
+# expose module-internal by-index inspection APIs
+guest_debug=[]
+
# enable x86-specific SIMD intrinsics in Value128 (uses unsafe code)
# note: for x86 backend selection, compile with x86-64-v3 target features
# (for example: `RUSTFLAGS="-C target-cpu=x86-64-v3"`)
diff --git a/crates/tinywasm/benches/argon2id.rs b/crates/tinywasm/benches/argon2id.rs
index 74c51e3..5d1719f 100644
--- a/crates/tinywasm/benches/argon2id.rs
+++ b/crates/tinywasm/benches/argon2id.rs
@@ -24,7 +24,7 @@ fn argon2id_from_twasm(twasm: &[u8]) -> Result<TinyWasmModule> {
fn argon2id_run(module: TinyWasmModule) -> Result<()> {
let mut store = Store::default();
let instance = ModuleInstance::instantiate(&mut store, module.into(), None)?;
- let argon2 = instance.exported_func::<(i32, i32, i32), i32>(&store, "argon2id")?;
+ let argon2 = instance.func_typed::<(i32, i32, i32), i32>(&store, "argon2id")?;
argon2.call(&mut store, (1000, 2, 1))?;
Ok(())
}
@@ -32,11 +32,14 @@ fn argon2id_run(module: TinyWasmModule) -> Result<()> {
fn criterion_benchmark(c: &mut Criterion) {
let module = argon2id_parse().expect("argon2id_parse");
let twasm = argon2id_to_twasm(&module).expect("argon2id_to_twasm");
+ let mut group = c.benchmark_group("argon2id");
- c.bench_function("argon2id_parse", |b| b.iter(argon2id_parse));
- c.bench_function("argon2id_to_twasm", |b| b.iter(|| argon2id_to_twasm(&module)));
- c.bench_function("argon2id_from_twasm", |b| b.iter(|| argon2id_from_twasm(&twasm)));
- c.bench_function("argon2id", |b| b.iter(|| argon2id_run(module.clone())));
+ group.measurement_time(std::time::Duration::from_secs(2));
+ group.bench_function("argon2id_parse", |b| b.iter(argon2id_parse));
+ group.bench_function("argon2id_to_twasm", |b| b.iter(|| argon2id_to_twasm(&module)));
+ group.bench_function("argon2id_from_twasm", |b| b.iter(|| argon2id_from_twasm(&twasm)));
+ group.measurement_time(std::time::Duration::from_secs(10));
+ group.bench_function("argon2id", |b| b.iter(|| argon2id_run(module.clone())));
}
criterion_group!(benches, criterion_benchmark);
diff --git a/crates/tinywasm/benches/fibonacci.rs b/crates/tinywasm/benches/fibonacci.rs
index 75d17a4..2865594 100644
--- a/crates/tinywasm/benches/fibonacci.rs
+++ b/crates/tinywasm/benches/fibonacci.rs
@@ -23,7 +23,7 @@ fn fibonacci_from_twasm(twasm: &[u8]) -> Result<TinyWasmModule> {
fn fibonacci_run(module: TinyWasmModule, recursive: bool, n: i32) -> Result<()> {
let mut store = Store::default();
let instance = ModuleInstance::instantiate(&mut store, module.into(), None)?;
- let argon2 = instance.exported_func::<i32, i32>(
+ let argon2 = instance.func_typed::<i32, i32>(
&store,
match recursive {
true => "fibonacci_recursive",
@@ -37,12 +37,15 @@ fn fibonacci_run(module: TinyWasmModule, recursive: bool, n: i32) -> Result<()>
fn criterion_benchmark(c: &mut Criterion) {
let module = fibonacci_parse().expect("fibonacci_parse");
let twasm = fibonacci_to_twasm(&module).expect("fibonacci_to_twasm");
+ let mut group = c.benchmark_group("fibonacci");
- c.bench_function("fibonacci_parse", |b| b.iter(fibonacci_parse));
- c.bench_function("fibonacci_to_twasm", |b| b.iter(|| fibonacci_to_twasm(&module)));
- c.bench_function("fibonacci_from_twasm", |b| b.iter(|| fibonacci_from_twasm(&twasm)));
- c.bench_function("fibonacci_iterative_60", |b| b.iter(|| fibonacci_run(module.clone(), false, 60)));
- c.bench_function("fibonacci_recursive_26", |b| b.iter(|| fibonacci_run(module.clone(), true, 26)));
+ group.measurement_time(std::time::Duration::from_secs(2));
+ group.bench_function("fibonacci_parse", |b| b.iter(fibonacci_parse));
+ group.bench_function("fibonacci_to_twasm", |b| b.iter(|| fibonacci_to_twasm(&module)));
+ group.bench_function("fibonacci_from_twasm", |b| b.iter(|| fibonacci_from_twasm(&twasm)));
+ group.measurement_time(std::time::Duration::from_secs(10));
+ group.bench_function("fibonacci_iterative_60", |b| b.iter(|| fibonacci_run(module.clone(), false, 60)));
+ group.bench_function("fibonacci_recursive_26", |b| b.iter(|| fibonacci_run(module.clone(), true, 26)));
}
criterion_group!(benches, criterion_benchmark);
diff --git a/crates/tinywasm/benches/tinywasm.rs b/crates/tinywasm/benches/tinywasm.rs
index 73096a5..7ab9587 100644
--- a/crates/tinywasm/benches/tinywasm.rs
+++ b/crates/tinywasm/benches/tinywasm.rs
@@ -26,7 +26,7 @@ fn tinywasm_run(module: TinyWasmModule) -> Result<()> {
let mut imports = Imports::default();
imports.define("env", "printi32", Extern::typed_func(|_: FuncContext<'_>, _: i32| Ok(()))).expect("define");
let instance = ModuleInstance::instantiate(&mut store, module.into(), Some(imports)).expect("instantiate");
- let hello = instance.exported_func::<(), ()>(&store, "hello").expect("exported_func");
+ let hello = instance.func_typed::<(), ()>(&store, "hello").expect("func_typed");
hello.call(&mut store, ()).expect("call");
Ok(())
}
@@ -35,11 +35,12 @@ fn criterion_benchmark(c: &mut Criterion) {
let module = tinywasm_parse().expect("tinywasm_parse");
let twasm = tinywasm_to_twasm(&module).expect("tinywasm_to_twasm");
let mut group = c.benchmark_group("tinywasm");
- group.measurement_time(std::time::Duration::from_secs(10));
+ group.measurement_time(std::time::Duration::from_secs(2));
group.bench_function("tinywasm_parse", |b| b.iter(tinywasm_parse));
group.bench_function("tinywasm_to_twasm", |b| b.iter(|| tinywasm_to_twasm(&module)));
group.bench_function("tinywasm_from_twasm", |b| b.iter(|| tinywasm_from_twasm(&twasm)));
+ group.measurement_time(std::time::Duration::from_secs(10));
group.bench_function("tinywasm", |b| b.iter(|| tinywasm_run(module.clone())));
}
diff --git a/crates/tinywasm/benches/tinywasm_modes.rs b/crates/tinywasm/benches/tinywasm_modes.rs
index edb472d..54f8033 100644
--- a/crates/tinywasm/benches/tinywasm_modes.rs
+++ b/crates/tinywasm/benches/tinywasm_modes.rs
@@ -24,7 +24,7 @@ fn setup_typed_func(module: TinyWasmModule, engine: Option<Engine>) -> Result<(S
imports.define("env", "printi32", Extern::typed_func(|_: FuncContext<'_>, _: i32| Ok(())))?;
let instance = ModuleInstance::instantiate(&mut store, module.into(), Some(imports))?;
- let func = instance.exported_func::<(), ()>(&store, "hello")?;
+ let func = instance.func_typed::<(), ()>(&store, "hello")?;
Ok((store, func))
}
diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs
index 75a094a..51ac9b0 100644
--- a/crates/tinywasm/src/func.rs
+++ b/crates/tinywasm/src/func.rs
@@ -22,6 +22,7 @@ pub(crate) struct ExecutionState {
/// A function handle
#[cfg_attr(feature = "debug", derive(Debug))]
pub struct FuncHandle {
+ pub(crate) store_id: usize,
pub(crate) module_addr: ModuleInstanceAddr,
pub(crate) addr: u32,
pub(crate) ty: FuncType,
@@ -53,6 +54,10 @@ impl FuncHandle {
/// See <https://webassembly.github.io/spec/core/exec/modules.html#invocation>
#[inline]
pub fn call(&self, store: &mut Store, params: &[WasmValue]) -> Result<Vec<WasmValue>> {
+ if self.store_id != store.id() {
+ return Err(Error::InvalidStore);
+ }
+
validate_call_params(&self.ty, params)?;
let func_inst = store.state.get_func(self.addr);
@@ -86,6 +91,10 @@ impl FuncHandle {
store: &'store mut Store,
params: &[WasmValue],
) -> Result<FuncExecution<'store>> {
+ if self.store_id != store.id() {
+ return Err(Error::InvalidStore);
+ }
+
validate_call_params(&self.ty, params)?;
let func_inst = store.state.get_func(self.addr);
diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs
index 3bf35e6..e5fda8c 100644
--- a/crates/tinywasm/src/imports.rs
+++ b/crates/tinywasm/src/imports.rs
@@ -6,7 +6,8 @@ use alloc::vec::Vec;
use core::fmt::Debug;
use crate::func::{FromWasmValueTuple, IntoWasmValueTuple, ValTypesFromTuple};
-use crate::{LinkingError, MemoryRef, MemoryRefMut, Result, log};
+use crate::instance::{ExternItemRef, ExternItemRefMut};
+use crate::{GlobalRef, GlobalRefMut, LinkingError, MemoryRef, MemoryRefMut, Result, TableRef, TableRefMut, log};
use tinywasm_types::*;
/// The internal representation of a function
@@ -74,14 +75,54 @@ impl FuncContext<'_> {
})
}
- /// Get a reference to an exported memory
- pub fn exported_memory(&self, name: &str) -> Result<MemoryRef<'_>> {
- self.module().exported_memory(self.store, name)
+ /// Get a reference to a memory export.
+ pub fn memory(&self, name: &str) -> Result<MemoryRef<'_>> {
+ self.module().memory(self.store, name)
}
- /// Get a mutable reference to an exported memory
- pub fn exported_memory_mut(&mut self, name: &str) -> Result<MemoryRefMut<'_>> {
- self.module().exported_memory_mut(self.store, name)
+ /// Get a mutable reference to a memory export.
+ pub fn memory_mut(&mut self, name: &str) -> Result<MemoryRefMut<'_>> {
+ self.module().memory_mut(self.store, name)
+ }
+
+ /// Get any exported extern value by name.
+ pub fn extern_item(&self, name: &str) -> Result<ExternItemRef<'_>> {
+ self.module().extern_item(self.store, name)
+ }
+
+ /// Get any exported extern value by name with mutable access when applicable.
+ pub fn extern_item_mut(&mut self, name: &str) -> Result<ExternItemRefMut<'_>> {
+ self.module().extern_item_mut(self.store, name)
+ }
+
+ /// Get a reference to a table export.
+ pub fn table(&self, name: &str) -> Result<TableRef<'_>> {
+ self.module().table(self.store, name)
+ }
+
+ /// Get a mutable reference to a table export.
+ pub fn table_mut(&mut self, name: &str) -> Result<TableRefMut<'_>> {
+ self.module().table_mut(self.store, name)
+ }
+
+ /// Get the value of a global export.
+ pub fn global_get(&self, name: &str) -> Result<WasmValue> {
+ self.module().global_get(self.store, name)
+ }
+
+ /// Get a reference to a global export.
+ pub fn global(&self, name: &str) -> Result<GlobalRef<'_>> {
+ self.module().global(self.store, name)
+ }
+
+ /// Get a mutable reference to a global export.
+ pub fn global_mut(&mut self, name: &str) -> Result<GlobalRefMut<'_>> {
+ self.module().global_mut(self.store, name)
+ }
+
+ /// Set the value of a mutable global export.
+ pub fn global_set(&mut self, name: &str, value: WasmValue) -> Result<()> {
+ self.module().global_set(self.store, name, value)
}
/// Charge additional fuel from the currently running resumable invocation.
@@ -230,8 +271,12 @@ impl From<&Import> for ExternName {
/// ```rust
/// # use log;
/// # fn main() -> tinywasm::Result<()> {
-/// use tinywasm::{Imports, Extern};
+/// use tinywasm::{Extern, Imports, Module, Store};
/// use tinywasm::types::{ValType, TableType, MemoryType, MemoryArch, WasmValue};
+/// # let wasm = wat::parse_str("(module)").expect("valid wat");
+/// # let module = Module::parse_bytes(&wasm)?;
+/// # let mut store = Store::default();
+/// # let my_other_instance = module.instantiate(&mut store, None)?;
/// let mut imports = Imports::new();
///
/// // function args can be either a single
@@ -249,18 +294,16 @@ impl From<&Import> for ExternName {
/// .define("my_module", "table", Extern::table(table_type, table_init))?
/// .define("my_module", "memory", Extern::memory(MemoryType::new(MemoryArch::I32, 1, Some(2), None)))?
/// .define("my_module", "global_i32", Extern::global(WasmValue::I32(666), false))?
-/// .link_module("my_other_module", 0)?;
+/// .link_module("my_other_module", my_other_instance)?;
/// # Ok(())
/// # }
/// ```
-///
-/// Note that module instance addresses for [`Imports::link_module`] can be obtained from [`crate::ModuleInstance::id`].
/// Now, the imports object can be passed to [`crate::ModuleInstance::instantiate`].
#[derive(Default, Clone)]
#[cfg_attr(feature = "debug", derive(Debug))]
pub struct Imports {
values: BTreeMap<ExternName, Extern>,
- modules: BTreeMap<String, ModuleInstanceAddr>,
+ modules: BTreeMap<String, crate::ModuleInstance>,
}
pub(crate) enum ResolvedExtern<S, V> {
@@ -297,8 +340,8 @@ impl Imports {
/// Link a module
///
/// This will automatically link all imported values on instantiation
- pub fn link_module(&mut self, name: &str, addr: ModuleInstanceAddr) -> Result<&mut Self> {
- self.modules.insert(name.to_string(), addr);
+ pub fn link_module(&mut self, name: &str, instance: crate::ModuleInstance) -> Result<&mut Self> {
+ self.modules.insert(name.to_string(), instance);
Ok(self)
}
@@ -312,17 +355,20 @@ impl Imports {
&mut self,
store: &mut crate::Store,
import: &Import,
- ) -> Option<ResolvedExtern<ExternVal, Extern>> {
+ ) -> Result<Option<ResolvedExtern<ExternVal, Extern>>> {
let name = ExternName::from(import);
if let Some(v) = self.values.get(&name) {
- return Some(ResolvedExtern::Extern(v.clone()));
+ return Ok(Some(ResolvedExtern::Extern(v.clone())));
}
- if let Some(addr) = self.modules.get(&name.module) {
- let instance = store.get_module_instance(*addr)?;
- return Some(ResolvedExtern::Store(instance.export_addr(&import.name)?));
+ if let Some(instance) = self.modules.get(&name.module) {
+ if instance.0.store_id != store.id() {
+ return Err(crate::Error::InvalidStore);
+ }
+
+ return Ok(instance.export_addr(&import.name).map(ResolvedExtern::Store));
}
- None
+ Ok(None)
}
#[cfg(not(feature = "debug"))]
@@ -392,16 +438,17 @@ impl Imports {
let mut imports = ResolvedImports::new();
for import in &*module.0.imports {
- match self.take(store, import).ok_or_else(|| LinkingError::unknown_import(import))? {
+ match self.take(store, import)?.ok_or_else(|| LinkingError::unknown_import(import))? {
// A link to something that needs to be added to the store
ResolvedExtern::Extern(ex) => match (ex, &import.kind) {
(Extern::Global { ty, val }, ImportKind::Global(import_ty)) => {
Self::compare_types(import, &ty, import_ty)?;
imports.globals.push(store.add_global(ty, val.into(), idx)?);
}
- (Extern::Table { ty, .. }, ImportKind::Table(import_ty)) => {
+ (Extern::Table { ty, init }, ImportKind::Table(import_ty)) => {
Self::compare_table_types(import, &ty, import_ty)?;
- imports.tables.push(store.add_table(ty, idx)?);
+ Self::compare_types(import, &ty.element_type, &init.val_type())?;
+ imports.tables.push(store.add_table(ty, init, idx)?);
}
(Extern::Memory { ty }, ImportKind::Memory(import_ty)) => {
Self::compare_memory_types(import, &ty, import_ty, None)?;
diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs
index b95a94d..a23213b 100644
--- a/crates/tinywasm/src/instance.rs
+++ b/crates/tinywasm/src/instance.rs
@@ -2,8 +2,35 @@ use alloc::boxed::Box;
use alloc::{format, rc::Rc};
use tinywasm_types::*;
-use crate::func::{FromWasmValueTuple, IntoWasmValueTuple};
-use crate::{Error, FuncHandle, FuncHandleTyped, Imports, MemoryRef, MemoryRefMut, Module, Result, Store};
+use crate::func::{FromWasmValueTuple, IntoWasmValueTuple, ValTypesFromTuple};
+use crate::{
+ Error, FuncHandle, FuncHandleTyped, GlobalRef, GlobalRefMut, Imports, MemoryRef, MemoryRefMut, Module, Result,
+ Store, TableRef, TableRefMut,
+};
+
+/// A typed borrowed view over an exported extern value.
+pub enum ExternItemRef<'a> {
+ /// Exported function handle.
+ Func(FuncHandle),
+ /// Exported memory reference.
+ Memory(MemoryRef<'a>),
+ /// Exported table reference.
+ Table(TableRef<'a>),
+ /// Exported global reference.
+ Global(GlobalRef<'a>),
+}
+
+/// A typed mutable borrowed view over an exported extern value.
+pub enum ExternItemRefMut<'a> {
+ /// Exported function handle.
+ Func(FuncHandle),
+ /// Exported mutable memory reference.
+ Memory(MemoryRefMut<'a>),
+ /// Exported mutable table reference.
+ Table(TableRefMut<'a>),
+ /// Exported mutable global reference.
+ Global(GlobalRefMut<'a>),
+}
/// An instantiated WebAssembly module
///
@@ -99,6 +126,14 @@ impl ModuleInstanceInner {
}
impl ModuleInstance {
+ #[inline]
+ fn validate_store(&self, store: &Store) -> Result<()> {
+ if self.0.store_id != store.id() {
+ return Err(Error::InvalidStore);
+ }
+ Ok(())
+ }
+
/// Get the module instance's address
pub fn id(&self) -> ModuleInstanceAddr {
self.0.idx
@@ -154,57 +189,330 @@ impl ModuleInstance {
Some(ExternVal::new(exports.kind, *addr))
}
- /// Get an exported function by name
- pub fn exported_func_untyped(&self, store: &Store, name: &str) -> Result<FuncHandle> {
- if self.0.store_id != store.id() {
- return Err(Error::InvalidStore);
+ /// Returns an iterator over all exported extern values for this instance.
+ pub fn exports<'a>(&'a self, store: &'a Store) -> Result<impl Iterator<Item = (&'a str, ExternItemRef<'a>)> + 'a> {
+ self.validate_store(store)?;
+
+ Ok(self.0.exports.iter().map(move |export| {
+ let name = export.name.as_ref();
+ let item = match export.kind {
+ ExternalKind::Func => {
+ let idx = export.index as usize;
+ let func_addr = *self
+ .0
+ .func_addrs
+ .get(idx)
+ .unwrap_or_else(|| unreachable!("invalid function export index: {}", export.index));
+ let ty = store.state.get_func(func_addr).func.ty();
+ ExternItemRef::Func(FuncHandle {
+ store_id: self.0.store_id,
+ module_addr: self.id(),
+ addr: func_addr,
+ ty: ty.clone(),
+ })
+ }
+ ExternalKind::Table => {
+ let idx = export.index as usize;
+ let table_addr = *self
+ .0
+ .table_addrs
+ .get(idx)
+ .unwrap_or_else(|| unreachable!("invalid table export index: {}", export.index));
+ ExternItemRef::Table(TableRef(store.state.get_table(table_addr)))
+ }
+ ExternalKind::Memory => {
+ let idx = export.index as usize;
+ let mem_addr = *self
+ .0
+ .mem_addrs
+ .get(idx)
+ .unwrap_or_else(|| unreachable!("invalid memory export index: {}", export.index));
+ ExternItemRef::Memory(MemoryRef(store.state.get_mem(mem_addr)))
+ }
+ ExternalKind::Global => {
+ let idx = export.index as usize;
+ let global_addr = *self
+ .0
+ .global_addrs
+ .get(idx)
+ .unwrap_or_else(|| unreachable!("invalid global export index: {}", export.index));
+ ExternItemRef::Global(GlobalRef(store.state.get_global(global_addr)))
+ }
+ };
+
+ (name, item)
+ }))
+ }
+
+ #[inline]
+ fn require_export(&self, name: &str) -> Result<ExternVal> {
+ self.export_addr(name).ok_or_else(|| Error::Other(format!("Export not found: {name}")))
+ }
+
+ #[inline]
+ #[cfg(feature = "guest_debug")]
+ fn index_addr<T: Copy>(slice: &[T], idx: u32, kind: &str) -> Result<T> {
+ slice.get(idx as usize).copied().ok_or_else(|| Error::Other(format!("{kind} index out of bounds: {idx}")))
+ }
+
+ /// Get any exported extern value by name.
+ pub fn extern_item<'a>(&self, store: &'a Store, name: &str) -> Result<ExternItemRef<'a>> {
+ self.validate_store(store)?;
+ match self.require_export(name)? {
+ ExternVal::Func(_) => self.func(store, name).map(ExternItemRef::Func),
+ ExternVal::Memory(mem_addr) => Ok(ExternItemRef::Memory(MemoryRef(store.state.get_mem(mem_addr)))),
+ ExternVal::Table(table_addr) => Ok(ExternItemRef::Table(TableRef(store.state.get_table(table_addr)))),
+ ExternVal::Global(global_addr) => Ok(ExternItemRef::Global(GlobalRef(store.state.get_global(global_addr)))),
}
+ }
- let export = self.export_addr(name).ok_or_else(|| Error::Other(format!("Export not found: {name}")))?;
+ /// Get any exported extern value by name with mutable access when applicable.
+ pub fn extern_item_mut<'a>(&self, store: &'a mut Store, name: &str) -> Result<ExternItemRefMut<'a>> {
+ self.validate_store(store)?;
+ match self.require_export(name)? {
+ ExternVal::Func(_) => self.func(store, name).map(ExternItemRefMut::Func),
+ ExternVal::Memory(mem_addr) => {
+ Ok(ExternItemRefMut::Memory(MemoryRefMut(store.state.get_mem_mut(mem_addr))))
+ }
+ ExternVal::Table(table_addr) => {
+ Ok(ExternItemRefMut::Table(TableRefMut(store.state.get_table_mut(table_addr))))
+ }
+ ExternVal::Global(global_addr) => {
+ Ok(ExternItemRefMut::Global(GlobalRefMut(store.state.get_global_mut(global_addr))))
+ }
+ }
+ }
+
+ /// Get a function export by name.
+ pub fn func(&self, store: &Store, name: &str) -> Result<FuncHandle> {
+ self.validate_store(store)?;
+
+ let export = self.require_export(name)?;
let ExternVal::Func(func_addr) = export else {
return Err(Error::Other(format!("Export is not a function: {name}")));
};
let ty = store.state.get_func(func_addr).func.ty();
- Ok(FuncHandle { addr: func_addr, module_addr: self.id(), ty: ty.clone() })
+ Ok(FuncHandle { store_id: self.0.store_id, addr: func_addr, module_addr: self.id(), ty: ty.clone() })
+ }
+
+ /// Get a function by its module-local index.
+ ///
+ /// This exposes an internal module-owned function directly and bypasses the
+ /// normal export boundary. It is mainly intended for tooling and
+ /// introspection. Calling private functions can change behavior in ways the
+ /// module author did not expose as part of the public API.
+ #[cfg_attr(docsrs, doc(cfg(feature = "guest_debug")))]
+ #[cfg(feature = "guest_debug")]
+ pub fn func_by_index(&self, store: &Store, func_index: FuncAddr) -> Result<FuncHandle> {
+ self.validate_store(store)?;
+ let func_addr = Self::index_addr(&self.0.func_addrs, func_index, "function")?;
+
+ let ty = store.state.get_func(func_addr).func.ty();
+ Ok(FuncHandle { store_id: self.0.store_id, addr: func_addr, module_addr: self.id(), ty: ty.clone() })
}
- /// Get a typed exported function by name
- pub fn exported_func<P: IntoWasmValueTuple, R: FromWasmValueTuple>(
+ /// Get a typed function export by name.
+ pub fn func_typed<P: IntoWasmValueTuple + ValTypesFromTuple, R: FromWasmValueTuple + ValTypesFromTuple>(
&self,
store: &Store,
name: &str,
) -> Result<FuncHandleTyped<P, R>> {
- let func = self.exported_func_untyped(store, name)?;
+ let func = self.func(store, name)?;
+ Self::validate_typed_func::<P, R>(&func, name)?;
Ok(FuncHandleTyped { func, marker: core::marker::PhantomData })
}
- /// Get an exported memory by name
- pub fn exported_memory<'a>(&self, store: &'a Store, name: &str) -> Result<MemoryRef<'a>> {
- let export = self.export_addr(name).ok_or_else(|| Error::Other(format!("Export not found: {name}")))?;
+ /// Get a typed function by its module-local index.
+ #[cfg_attr(docsrs, doc(cfg(feature = "guest_debug")))]
+ #[cfg(feature = "guest_debug")]
+ pub fn func_typed_by_index<P: IntoWasmValueTuple + ValTypesFromTuple, R: FromWasmValueTuple + ValTypesFromTuple>(
+ &self,
+ store: &Store,
+ func_index: FuncAddr,
+ ) -> Result<FuncHandleTyped<P, R>> {
+ let func = self.func_by_index(store, func_index)?;
+ Self::validate_typed_func::<P, R>(&func, &format!("function index {func_index}"))?;
+ Ok(FuncHandleTyped { func, marker: core::marker::PhantomData })
+ }
+
+ fn validate_typed_func<P: ValTypesFromTuple, R: ValTypesFromTuple>(
+ func: &FuncHandle,
+ func_name: &str,
+ ) -> Result<()> {
+ let expected = FuncType { params: P::val_types(), results: R::val_types() };
+ if func.ty != expected {
+ return Err(Error::Other(format!(
+ "function type mismatch for {func_name}: expected {expected:?}, actual {:?}",
+ func.ty
+ )));
+ }
+
+ Ok(())
+ }
+
+ /// Get a memory export by name.
+ pub fn memory<'a>(&self, store: &'a Store, name: &str) -> Result<MemoryRef<'a>> {
+ self.validate_store(store)?;
+
+ let export = self.require_export(name)?;
let ExternVal::Memory(mem_addr) = export else {
return Err(Error::Other(format!("Export is not a memory: {name}")));
};
- self.memory(store, mem_addr)
+ Ok(MemoryRef(store.state.get_mem(mem_addr)))
}
- /// Get an exported memory by name (mutable)
- pub fn exported_memory_mut<'a>(&self, store: &'a mut Store, name: &str) -> Result<MemoryRefMut<'a>> {
- let export = self.export_addr(name).ok_or_else(|| Error::Other(format!("Export not found: {name}")))?;
+ /// Get a mutable memory export by name.
+ pub fn memory_mut<'a>(&self, store: &'a mut Store, name: &str) -> Result<MemoryRefMut<'a>> {
+ self.validate_store(store)?;
+
+ let export = self.require_export(name)?;
let ExternVal::Memory(mem_addr) = export else {
return Err(Error::Other(format!("Export is not a memory: {name}")));
};
- self.memory_mut(store, mem_addr)
+ Ok(MemoryRefMut(store.state.get_mem_mut(mem_addr)))
+ }
+
+ /// Get a memory by its module-local index.
+ ///
+ /// This exposes an internal module-owned memory directly and bypasses the
+ /// normal export boundary. It is mainly intended for tooling and
+ /// inspection. Mutating a private memory can change module behavior in ways
+ /// that are not part of the module's public API.
+ #[cfg_attr(docsrs, doc(cfg(feature = "guest_debug")))]
+ #[cfg(feature = "guest_debug")]
+ pub fn memory_by_index<'a>(&self, store: &'a Store, memory_index: MemAddr) -> Result<MemoryRef<'a>> {
+ self.validate_store(store)?;
+ let mem_addr = Self::index_addr(&self.0.mem_addrs, memory_index, "memory")?;
+ Ok(MemoryRef(store.state.get_mem(mem_addr)))
+ }
+
+ /// Get a mutable memory by its module-local index.
+ ///
+ /// This exposes an internal module-owned memory directly and bypasses the
+ /// normal export boundary. It is mainly intended for tooling and
+ /// inspection. Mutating a private memory can change module behavior in ways
+ /// that are not part of the module's public API.
+ #[cfg_attr(docsrs, doc(cfg(feature = "guest_debug")))]
+ #[cfg(feature = "guest_debug")]
+ pub fn memory_mut_by_index<'a>(&self, store: &'a mut Store, memory_index: MemAddr) -> Result<MemoryRefMut<'a>> {
+ self.validate_store(store)?;
+ let mem_addr = Self::index_addr(&self.0.mem_addrs, memory_index, "memory")?;
+ Ok(MemoryRefMut(store.state.get_mem_mut(mem_addr)))
+ }
+
+ /// Get a table export by name.
+ pub fn table<'a>(&self, store: &'a Store, name: &str) -> Result<TableRef<'a>> {
+ self.validate_store(store)?;
+
+ let export = self.require_export(name)?;
+ let ExternVal::Table(table_addr) = export else {
+ return Err(Error::Other(format!("Export is not a table: {name}")));
+ };
+ Ok(TableRef(store.state.get_table(table_addr)))
+ }
+
+ /// Get a mutable table export by name.
+ pub fn table_mut<'a>(&self, store: &'a mut Store, name: &str) -> Result<TableRefMut<'a>> {
+ self.validate_store(store)?;
+
+ let export = self.require_export(name)?;
+ let ExternVal::Table(table_addr) = export else {
+ return Err(Error::Other(format!("Export is not a table: {name}")));
+ };
+ Ok(TableRefMut(store.state.get_table_mut(table_addr)))
+ }
+
+ /// Get a table by its module-local index.
+ ///
+ /// This exposes an internal module-owned table directly and bypasses the
+ /// normal export boundary. It is mainly intended for tooling and
+ /// inspection. Mutating a private table can change module behavior in ways
+ /// that are not part of the module's public API.
+ #[cfg_attr(docsrs, doc(cfg(feature = "guest_debug")))]
+ #[cfg(feature = "guest_debug")]
+ pub fn table_by_index<'a>(&self, store: &'a Store, table_index: TableAddr) -> Result<TableRef<'a>> {
+ self.validate_store(store)?;
+ let table_addr = Self::index_addr(&self.0.table_addrs, table_index, "table")?;
+ Ok(TableRef(store.state.get_table(table_addr)))
+ }
+
+ /// Get a mutable table by its module-local index.
+ ///
+ /// This exposes an internal module-owned table directly and bypasses the
+ /// normal export boundary. It is mainly intended for tooling and
+ /// inspection. Mutating a private table can change module behavior in ways
+ /// that are not part of the module's public API.
+ #[cfg_attr(docsrs, doc(cfg(feature = "guest_debug")))]
+ #[cfg(feature = "guest_debug")]
+ pub fn table_mut_by_index<'a>(&self, store: &'a mut Store, table_index: TableAddr) -> Result<TableRefMut<'a>> {
+ self.validate_store(store)?;
+ let table_addr = Self::index_addr(&self.0.table_addrs, table_index, "table")?;
+ Ok(TableRefMut(store.state.get_table_mut(table_addr)))
+ }
+
+ /// Get the value of a global export by name.
+ pub fn global_get(&self, store: &Store, name: &str) -> Result<WasmValue> {
+ self.global(store, name).map(|global| global.get())
+ }
+
+ /// Get a reference to a global export by name.
+ pub fn global<'a>(&self, store: &'a Store, name: &str) -> Result<GlobalRef<'a>> {
+ self.validate_store(store)?;
+
+ let export = self.require_export(name)?;
+ let ExternVal::Global(global_addr) = export else {
+ return Err(Error::Other(format!("Export is not a global: {name}")));
+ };
+
+ Ok(GlobalRef(store.state.get_global(global_addr)))
+ }
+
+ /// Get a mutable reference to a global export by name.
+ pub fn global_mut<'a>(&self, store: &'a mut Store, name: &str) -> Result<GlobalRefMut<'a>> {
+ self.validate_store(store)?;
+
+ let export = self.require_export(name)?;
+ let ExternVal::Global(global_addr) = export else {
+ return Err(Error::Other(format!("Export is not a global: {name}")));
+ };
+
+ Ok(GlobalRefMut(store.state.get_global_mut(global_addr)))
}
- /// Get a memory by address
- pub fn memory<'a>(&self, store: &'a Store, addr: MemAddr) -> Result<MemoryRef<'a>> {
- Ok(MemoryRef(store.state.get_mem(self.0.resolve_mem_addr(addr))))
+ /// Set the value of a mutable global export by name.
+ pub fn global_set(&self, store: &mut Store, name: &str, value: WasmValue) -> Result<()> {
+ self.global_mut(store, name)?.set(value)
}
- /// Get a memory by address (mutable)
- pub fn memory_mut<'a>(&self, store: &'a mut Store, addr: MemAddr) -> Result<MemoryRefMut<'a>> {
- Ok(MemoryRefMut(store.state.get_mem_mut(self.0.resolve_mem_addr(addr))))
+ /// Get a reference to a global by its module-local index.
+ ///
+ /// This exposes an internal module-owned global directly and bypasses the
+ /// normal export boundary. It is mainly intended for tooling and
+ /// inspection. Mutating a private global can change module behavior in ways
+ /// that are not part of the module's public API.
+ #[cfg_attr(docsrs, doc(cfg(feature = "guest_debug")))]
+ #[cfg(feature = "guest_debug")]
+ pub fn global_by_index<'a>(&self, store: &'a Store, global_index: GlobalAddr) -> Result<GlobalRef<'a>> {
+ self.validate_store(store)?;
+ let global_addr = Self::index_addr(&self.0.global_addrs, global_index, "global")?;
+
+ Ok(GlobalRef(store.state.get_global(global_addr)))
+ }
+
+ /// Get a mutable reference to a global by its module-local index.
+ ///
+ /// This exposes an internal module-owned global directly and bypasses the
+ /// normal export boundary. It is mainly intended for tooling and
+ /// inspection. Mutating a private global can change module behavior in ways
+ /// that are not part of the module's public API.
+ #[cfg_attr(docsrs, doc(cfg(feature = "guest_debug")))]
+ #[cfg(feature = "guest_debug")]
+ pub fn global_mut_by_index<'a>(&self, store: &'a mut Store, global_index: GlobalAddr) -> Result<GlobalRefMut<'a>> {
+ self.validate_store(store)?;
+ let global_addr = Self::index_addr(&self.0.global_addrs, global_index, "global")?;
+
+ Ok(GlobalRefMut(store.state.get_global_mut(global_addr)))
}
/// Get the start function of the module
@@ -214,9 +522,7 @@ impl ModuleInstance {
///
/// See <https://webassembly.github.io/spec/core/syntax/modules.html#start-function>
pub fn start_func(&self, store: &Store) -> Result<Option<FuncHandle>> {
- if self.0.store_id != store.id() {
- return Err(Error::InvalidStore);
- }
+ self.validate_store(store)?;
let func_index = match self.0.func_start {
Some(func_index) => func_index,
@@ -232,7 +538,7 @@ impl ModuleInstance {
let func_addr = self.0.resolve_func_addr(func_index);
let ty = store.state.get_func(func_addr).func.ty();
- Ok(Some(FuncHandle { module_addr: self.id(), addr: func_addr, ty: ty.clone() }))
+ Ok(Some(FuncHandle { store_id: self.0.store_id, module_addr: self.id(), addr: func_addr, ty: ty.clone() }))
}
/// Invoke the start function of the module
diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs
index 12f28f4..9218ede 100644
--- a/crates/tinywasm/src/interpreter/executor.rs
+++ b/crates/tinywasm/src/interpreter/executor.rs
@@ -306,7 +306,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> {
MemoryGrow(addr) => self.exec_memory_grow(*addr)?,
// Bulk memory operations
- MemoryCopy(from, to) => self.exec_memory_copy(*from, *to)?,
+ MemoryCopy { dst_mem, src_mem } => self.exec_memory_copy(*dst_mem, *src_mem)?,
MemoryFill(addr) => self.exec_memory_fill(*addr)?,
MemoryFillImm(addr, val, size) => self.exec_memory_fill_imm(*addr, *val, *size)?,
MemoryInit(data_idx, mem_idx) => self.exec_memory_init(*data_idx, *mem_idx)?,
@@ -320,7 +320,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> {
TableInit(elem_idx, table_idx) => self.exec_table_init(*elem_idx, *table_idx)?,
TableGrow(table_idx) => self.exec_table_grow(*table_idx)?,
TableFill(table_idx) => self.exec_table_fill(*table_idx)?,
- TableCopy { from, to } => self.exec_table_copy(*from, *to)?,
+ TableCopy { dst_table, src_table } => self.exec_table_copy(*dst_table, *src_table)?,
// Core memory load/store operations
I32Store(m) => self.exec_mem_store::<i32, i32, 4>(m.mem_addr(), m.offset(), |v| v)?,
@@ -919,21 +919,23 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> {
Ok(())
}
- fn exec_memory_copy(&mut self, from: u32, to: u32) -> Result<()> {
+ fn exec_memory_copy(&mut self, dst_mem: u32, src_mem: u32) -> Result<()> {
let size: i32 = self.store.stack.values.pop();
let src: i32 = self.store.stack.values.pop();
let dst: i32 = self.store.stack.values.pop();
- if from == to {
- let mem_from = self.store.state.get_mem_mut(self.module.resolve_mem_addr(from));
+ if dst_mem == src_mem {
+ let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(dst_mem));
// copy within the same memory
- mem_from.copy_within(dst as usize, src as usize, size as usize)?;
+ mem.copy_within(dst as usize, src as usize, size as usize)?;
} else {
// copy between two memories
- let (mem_from, mem_to) =
- self.store.state.get_mems_mut(self.module.resolve_mem_addr(from), self.module.resolve_mem_addr(to))?;
+ let (dst_memory, src_memory) = self
+ .store
+ .state
+ .get_mems_mut(self.module.resolve_mem_addr(dst_mem), self.module.resolve_mem_addr(src_mem))?;
- mem_from.copy_from_slice(dst as usize, mem_to.load(src as usize, size as usize)?)?;
+ dst_memory.copy_from_slice(dst as usize, src_memory.load(src as usize, size as usize)?)?;
}
Ok(())
}
@@ -977,25 +979,25 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> {
let Some(data) = &data.data else { return Err(Trap::MemoryOutOfBounds { offset: 0, len: 0, max: 0 }.into()) };
mem.store(dst as usize, size as usize, &data[offset as usize..((offset + size) as usize)])
}
- fn exec_table_copy(&mut self, from: u32, to: u32) -> Result<()> {
+ fn exec_table_copy(&mut self, dst_table: u32, src_table: u32) -> Result<()> {
let size: i32 = self.store.stack.values.pop();
let src: i32 = self.store.stack.values.pop();
let dst: i32 = self.store.stack.values.pop();
- if from == to {
- // copy within the same memory
- self.store.state.get_table_mut(self.module.resolve_table_addr(from)).copy_within(
+ if dst_table == src_table {
+ // copy within the same table
+ self.store.state.get_table_mut(self.module.resolve_table_addr(dst_table)).copy_within(
dst as usize,
src as usize,
size as usize,
)
} else {
- // copy between two memories
- let (table_from, table_to) = self
+ // copy between two tables
+ let (dst_table_ref, src_table_ref) = self
.store
.state
- .get_tables_mut(self.module.resolve_table_addr(from), self.module.resolve_table_addr(to))?;
- table_to.copy_from_slice(dst as usize, table_from.load(src as usize, size as usize)?)
+ .get_tables_mut(self.module.resolve_table_addr(dst_table), self.module.resolve_table_addr(src_table))?;
+ dst_table_ref.copy_from_slice(dst as usize, src_table_ref.load(src as usize, size as usize)?)
}
}
diff --git a/crates/tinywasm/src/lib.rs b/crates/tinywasm/src/lib.rs
index 7afc2f6..6847680 100644
--- a/crates/tinywasm/src/lib.rs
+++ b/crates/tinywasm/src/lib.rs
@@ -22,10 +22,14 @@
//! Enables the `tinywasm-parser` crate. This is enabled by default.
//!- **`archive`**\
//! Enables pre-parsing of archives. This is enabled by default.
+//!- **`guest_debug`**\
+//! Enables module-internal by-index inspection APIs (`*_by_index`).
//!
//! With all these features disabled, `TinyWasm` only depends on `core`, `alloc` and `libm`.
//! By disabling `std`, you can use `TinyWasm` in `no_std` environments. This requires
//! a custom allocator and removes support for parsing from files and streams, but otherwise the API is the same.
+
+#![cfg_attr(docsrs, feature(doc_cfg))]
//!
//! ## Getting Started
//! The easiest way to get started is to use the [`Module::parse_bytes`] function to load a
@@ -51,9 +55,9 @@
//! let instance = module.instantiate(&mut store, None)?;
//!
//! // Get a typed handle to the exported "add" function
-//! // Alternatively, you can use `instance.get_func` to get an untyped handle
+//! // Alternatively, you can use `instance.func` to get an untyped handle
//! // that takes and returns [`WasmValue`]s
-//! let func = instance.exported_func::<(i32, i32), i32>(&mut store, "add")?;
+//! let func = instance.func_typed::<(i32, i32), i32>(&mut store, "add")?;
//! let res = func.call(&mut store, (1, 2))?;
//!
//! assert_eq!(res, 3);
@@ -94,8 +98,8 @@ mod error;
pub use error::*;
pub use func::{ExecProgress, FuncExecution, FuncExecutionTyped, FuncHandle, FuncHandleTyped};
pub use imports::*;
-pub use instance::ModuleInstance;
-pub use module::Module;
+pub use instance::{ExternItemRef, ExternItemRefMut, ModuleInstance};
+pub use module::{ExportType, ImportType, Module, ModuleExport, ModuleImport};
pub use reference::*;
pub use store::*;
diff --git a/crates/tinywasm/src/module.rs b/crates/tinywasm/src/module.rs
index 1ce3ff0..df8b0ac 100644
--- a/crates/tinywasm/src/module.rs
+++ b/crates/tinywasm/src/module.rs
@@ -1,5 +1,73 @@
use crate::{Imports, ModuleInstance, Result, Store};
-use tinywasm_types::TinyWasmModule;
+use tinywasm_types::{ExternalKind, FuncType, TinyWasmModule};
+
+fn imported_func_type(module: &TinyWasmModule, function_index: usize) -> Option<&FuncType> {
+ let mut seen = 0usize;
+ for import in module.imports.iter() {
+ if let tinywasm_types::ImportKind::Function(type_idx) = import.kind {
+ if seen == function_index {
+ return module.func_types.get(type_idx as usize);
+ }
+ seen += 1;
+ }
+ }
+ None
+}
+
+fn imported_global_type(module: &TinyWasmModule, global_index: usize) -> Option<&tinywasm_types::GlobalType> {
+ let mut seen = 0usize;
+ for import in module.imports.iter() {
+ if let tinywasm_types::ImportKind::Global(global_ty) = &import.kind {
+ if seen == global_index {
+ return Some(global_ty);
+ }
+ seen += 1;
+ }
+ }
+ None
+}
+
+/// A module import descriptor.
+pub struct ModuleImport<'a> {
+ /// Importing module name.
+ pub module: &'a str,
+ /// Import name.
+ pub name: &'a str,
+ /// Import type.
+ pub ty: ImportType<'a>,
+}
+
+/// A module export descriptor.
+pub struct ModuleExport<'a> {
+ /// Export name.
+ pub name: &'a str,
+ /// Export type.
+ pub ty: ExportType<'a>,
+}
+
+/// Imported entity type.
+pub enum ImportType<'a> {
+ /// Imported function type.
+ Func(&'a FuncType),
+ /// Imported table type.
+ Table(&'a tinywasm_types::TableType),
+ /// Imported memory type.
+ Memory(&'a tinywasm_types::MemoryType),
+ /// Imported global type.
+ Global(&'a tinywasm_types::GlobalType),
+}
+
+/// Exported entity type.
+pub enum ExportType<'a> {
+ /// Exported function type.
+ Func(&'a FuncType),
+ /// Exported table type.
+ Table(&'a tinywasm_types::TableType),
+ /// Exported memory type.
+ Memory(&'a tinywasm_types::MemoryType),
+ /// Exported global type.
+ Global(&'a tinywasm_types::GlobalType),
+}
/// A WebAssembly Module
///
@@ -54,4 +122,67 @@ impl Module {
let _ = instance.start(store)?;
Ok(instance)
}
+
+ /// Returns an iterator over the module's import descriptors.
+ ///
+ /// The returned data mirrors the module's import section and preserves order.
+ pub fn imports(&self) -> impl Iterator<Item = ModuleImport<'_>> {
+ self.0.imports.iter().filter_map(|import| {
+ let ty = match &import.kind {
+ tinywasm_types::ImportKind::Function(type_idx) => {
+ Some(ImportType::Func(self.0.func_types.get(*type_idx as usize)?))
+ }
+ tinywasm_types::ImportKind::Table(table_ty) => Some(ImportType::Table(table_ty)),
+ tinywasm_types::ImportKind::Memory(memory_ty) => Some(ImportType::Memory(memory_ty)),
+ tinywasm_types::ImportKind::Global(global_ty) => Some(ImportType::Global(global_ty)),
+ }?;
+
+ Some(ModuleImport { module: import.module.as_ref(), name: import.name.as_ref(), ty })
+ })
+ }
+
+ /// Returns an iterator over the module's export descriptors.
+ ///
+ /// The returned data mirrors the module's export section and preserves order.
+ pub fn exports(&self) -> impl Iterator<Item = ModuleExport<'_>> {
+ self.0.exports.iter().filter_map(|export| {
+ let ty = match export.kind {
+ ExternalKind::Func => {
+ let idx = export.index as usize;
+ let imported_funcs = self
+ .0
+ .imports
+ .iter()
+ .filter(|import| matches!(import.kind, tinywasm_types::ImportKind::Function(_)))
+ .count();
+
+ if idx < imported_funcs {
+ ExportType::Func(imported_func_type(&self.0, idx)?)
+ } else {
+ let local_idx = idx - imported_funcs;
+ ExportType::Func(&self.0.funcs.get(local_idx)?.ty)
+ }
+ }
+ ExternalKind::Table => ExportType::Table(self.0.table_types.get(export.index as usize)?),
+ ExternalKind::Memory => ExportType::Memory(self.0.memory_types.get(export.index as usize)?),
+ ExternalKind::Global => {
+ let idx = export.index as usize;
+ let imported_globals = self
+ .0
+ .imports
+ .iter()
+ .filter(|import| matches!(import.kind, tinywasm_types::ImportKind::Global(_)))
+ .count();
+ if idx < imported_globals {
+ ExportType::Global(imported_global_type(&self.0, idx)?)
+ } else {
+ let local_idx = idx - imported_globals;
+ ExportType::Global(&self.0.globals.get(local_idx)?.ty)
+ }
+ }
+ };
+
+ Some(ModuleExport { name: export.name.as_ref(), ty })
+ })
+ }
}
diff --git a/crates/tinywasm/src/reference.rs b/crates/tinywasm/src/reference.rs
index 7901e57..4deac5f 100644
--- a/crates/tinywasm/src/reference.rs
+++ b/crates/tinywasm/src/reference.rs
@@ -1,9 +1,11 @@
use core::ffi::CStr;
use alloc::string::{String, ToString};
-use alloc::{ffi::CString, format, vec::Vec};
+use alloc::{ffi::CString, format};
-use crate::{MemoryInstance, Result};
+use crate::store::{GlobalInstance, TableElement, TableInstance};
+use crate::{Error, MemoryInstance, Result};
+use tinywasm_types::{ExternRef, FuncRef, GlobalType, TableAddr, TableType, ValType, WasmValue};
// This module essentially contains the public APIs to interact with the data stored in the store
@@ -11,10 +13,42 @@ use crate::{MemoryInstance, Result};
#[cfg_attr(feature = "debug", derive(Debug))]
pub struct MemoryRef<'a>(pub(crate) &'a MemoryInstance);
-/// A borrowed reference to a memory instance
+/// A mutable reference to a memory instance.
#[cfg_attr(feature = "debug", derive(Debug))]
pub struct MemoryRefMut<'a>(pub(crate) &'a mut MemoryInstance);
+/// A reference to a table instance.
+#[cfg_attr(feature = "debug", derive(Debug))]
+pub struct TableRef<'a>(pub(crate) &'a TableInstance);
+
+/// A mutable reference to a table instance.
+#[cfg_attr(feature = "debug", derive(Debug))]
+pub struct TableRefMut<'a>(pub(crate) &'a mut TableInstance);
+
+/// A reference to a global instance.
+#[cfg_attr(feature = "debug", derive(Debug))]
+pub struct GlobalRef<'a>(pub(crate) &'a GlobalInstance);
+
+/// A mutable reference to a global instance.
+#[cfg_attr(feature = "debug", derive(Debug))]
+pub struct GlobalRefMut<'a>(pub(crate) &'a mut GlobalInstance);
+
+fn table_element_to_value(element_type: ValType, element: TableElement) -> WasmValue {
+ match element_type {
+ ValType::RefFunc => WasmValue::RefFunc(FuncRef::new(element.addr())),
+ ValType::RefExtern => WasmValue::RefExtern(ExternRef::new(element.addr())),
+ _ => unreachable!("table element type must be a reference type"),
+ }
+}
+
+fn table_value_to_element(element_type: ValType, value: WasmValue) -> Result<TableElement> {
+ match (element_type, value) {
+ (ValType::RefFunc, WasmValue::RefFunc(func_ref)) => Ok(TableElement::from(func_ref.addr())),
+ (ValType::RefExtern, WasmValue::RefExtern(extern_ref)) => Ok(TableElement::from(extern_ref.addr())),
+ _ => Err(Error::Other("invalid table value type".to_string())),
+ }
+}
+
impl MemoryRefLoad for MemoryRef<'_> {
/// Load a slice of memory
fn load(&self, offset: usize, len: usize) -> Result<&[u8]> {
@@ -30,28 +64,43 @@ impl MemoryRefLoad for MemoryRefMut<'_> {
}
impl MemoryRef<'_> {
+ /// Returns the full raw memory data.
+ pub fn data(&self) -> &[u8] {
+ &self.0.data
+ }
+
+ /// Returns the raw memory byte length.
+ pub fn data_size(&self) -> usize {
+ self.0.data.len()
+ }
+
/// Load a slice of memory
pub fn load(&self, offset: usize, len: usize) -> Result<&[u8]> {
self.0.load(offset, len)
}
-
- /// Load a slice of memory as a vector
- pub fn load_vec(&self, offset: usize, len: usize) -> Result<Vec<u8>> {
- self.load(offset, len).map(<[u8]>::to_vec)
- }
}
impl MemoryRefMut<'_> {
+ /// Returns the full raw memory data.
+ pub fn data(&self) -> &[u8] {
+ &self.0.data
+ }
+
+ /// Returns the full raw mutable memory data.
+ pub fn data_mut(&mut self) -> &mut [u8] {
+ &mut self.0.data
+ }
+
+ /// Returns the raw memory byte length.
+ pub fn data_size(&self) -> usize {
+ self.0.data.len()
+ }
+
/// Load a slice of memory
pub fn load(&self, offset: usize, len: usize) -> Result<&[u8]> {
self.0.load(offset, len)
}
- /// Load a slice of memory as a vector
- pub fn load_vec(&self, offset: usize, len: usize) -> Result<Vec<u8>> {
- self.load(offset, len).map(<[u8]>::to_vec)
- }
-
/// Grow the memory by the given number of pages
pub fn grow(&mut self, delta_pages: i64) -> Option<i64> {
self.0.grow(delta_pages)
@@ -64,7 +113,7 @@ impl MemoryRefMut<'_> {
/// Copy a slice of memory to another place in memory
pub fn copy_within(&mut self, src: usize, dst: usize, len: usize) -> Result<()> {
- self.0.copy_within(src, dst, len)
+ self.0.copy_within(dst, src, len)
}
/// Fill a slice of memory with a value
@@ -78,12 +127,112 @@ impl MemoryRefMut<'_> {
}
}
+impl TableRef<'_> {
+ /// Get the type of the table.
+ pub fn ty(&self) -> TableType {
+ self.0.kind.clone()
+ }
+
+ /// Get the current number of elements in the table.
+ pub fn size(&self) -> usize {
+ self.0.size() as usize
+ }
+
+ /// Get a table element as a wasm reference value.
+ pub fn get(&self, index: TableAddr) -> Result<WasmValue> {
+ self.0.get_wasm_val(index)
+ }
+
+ /// Load a range of table elements and iterate over wasm reference values.
+ pub fn load(&self, offset: usize, len: usize) -> Result<impl Iterator<Item = WasmValue> + '_> {
+ let element_type = self.0.kind.element_type;
+ let elements = self.0.load(offset, len)?;
+ Ok(elements.iter().copied().map(move |element| table_element_to_value(element_type, element)))
+ }
+}
+
+impl TableRefMut<'_> {
+ /// Get the type of the table.
+ pub fn ty(&self) -> TableType {
+ self.0.kind.clone()
+ }
+
+ /// Get the current number of elements in the table.
+ pub fn size(&self) -> usize {
+ self.0.size() as usize
+ }
+
+ /// Get a table element as a wasm reference value.
+ pub fn get(&self, index: TableAddr) -> Result<WasmValue> {
+ self.0.get_wasm_val(index)
+ }
+
+ /// Load a range of table elements and iterate over wasm reference values.
+ pub fn load(&self, offset: usize, len: usize) -> Result<impl Iterator<Item = WasmValue> + '_> {
+ let element_type = self.0.kind.element_type;
+ let elements = self.0.load(offset, len)?;
+ Ok(elements.iter().copied().map(move |element| table_element_to_value(element_type, element)))
+ }
+
+ /// Set a table element.
+ pub fn set(&mut self, index: TableAddr, value: WasmValue) -> Result<()> {
+ let value = table_value_to_element(self.0.kind.element_type, value)?;
+ self.0.set(index, value)
+ }
+
+ /// Copy elements within the same table.
+ pub fn copy_within(&mut self, src: usize, dst: usize, len: usize) -> Result<()> {
+ self.0.copy_within(dst, src, len)
+ }
+
+ /// Grow the table and return the previous size.
+ pub fn grow(&mut self, delta: i32, init: WasmValue) -> Result<usize> {
+ let old_size = self.size();
+ let init = table_value_to_element(self.0.kind.element_type, init)?;
+ self.0.grow(delta, init)?;
+ Ok(old_size)
+ }
+}
+
+impl GlobalRef<'_> {
+ /// Get the type of the global.
+ pub fn ty(&self) -> GlobalType {
+ self.0.ty
+ }
+
+ /// Get the current value of the global.
+ pub fn get(&self) -> WasmValue {
+ self.0.value.get().attach_type(self.0.ty.ty)
+ }
+}
+
+impl GlobalRefMut<'_> {
+ /// Get the type of the global.
+ pub fn ty(&self) -> GlobalType {
+ self.0.ty
+ }
+
+ /// Get the current value of the global.
+ pub fn get(&self) -> WasmValue {
+ self.0.value.get().attach_type(self.0.ty.ty)
+ }
+
+ /// Set the current value of the global.
+ pub fn set(&mut self, value: WasmValue) -> Result<()> {
+ if !self.0.ty.mutable {
+ return Err(Error::Other("global is immutable".to_string()));
+ }
+ if value.val_type() != self.0.ty.ty {
+ return Err(Error::Other("invalid global value type".to_string()));
+ }
+ self.0.value.set(value.into());
+ Ok(())
+ }
+}
+
#[doc(hidden)]
pub trait MemoryRefLoad {
fn load(&self, offset: usize, len: usize) -> Result<&[u8]>;
- fn load_vec(&self, offset: usize, len: usize) -> Result<Vec<u8>> {
- self.load(offset, len).map(<[u8]>::to_vec)
- }
}
/// Convenience methods for loading strings from memory
diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs
index aae8b35..648efaa 100644
--- a/crates/tinywasm/src/store/mod.rs
+++ b/crates/tinywasm/src/store/mod.rs
@@ -208,6 +208,14 @@ impl State {
}
/// Get the global at the actual index in the store
+ pub(crate) fn get_global_mut(&mut self, addr: GlobalAddr) -> &mut GlobalInstance {
+ match self.globals.get_mut(addr as usize) {
+ Some(global) => global,
+ None => unreachable!("global {addr} not found. This should be unreachable"),
+ }
+ }
+
+ /// Get the global at the actual index in the store
pub(crate) fn get_global_val(&self, addr: MemAddr) -> TinyWasmValue {
match self.globals.get(addr as usize) {
Some(global) => global.value.get(),
@@ -444,8 +452,19 @@ impl Store {
Ok(self.state.globals.len() as Addr - 1)
}
- pub(crate) fn add_table(&mut self, table: TableType, _idx: ModuleInstanceAddr) -> Result<TableAddr> {
- self.state.tables.push(TableInstance::new(table));
+ pub(crate) fn add_table(
+ &mut self,
+ table: TableType,
+ init: WasmValue,
+ _idx: ModuleInstanceAddr,
+ ) -> Result<TableAddr> {
+ let init = match (table.element_type, init) {
+ (ValType::RefFunc, WasmValue::RefFunc(func_ref)) => TableElement::from(func_ref.addr()),
+ (ValType::RefExtern, WasmValue::RefExtern(extern_ref)) => TableElement::from(extern_ref.addr()),
+ _ => return Err(Error::Other("invalid table init value".to_string())),
+ };
+
+ self.state.tables.push(TableInstance::new_with_init(table, init));
Ok(self.state.tables.len() as TableAddr - 1)
}
diff --git a/crates/tinywasm/src/store/table.rs b/crates/tinywasm/src/store/table.rs
index 7bbda83..86f9f0d 100644
--- a/crates/tinywasm/src/store/table.rs
+++ b/crates/tinywasm/src/store/table.rs
@@ -15,7 +15,11 @@ pub(crate) struct TableInstance {
impl TableInstance {
pub(crate) fn new(kind: TableType) -> Self {
- Self { elements: vec![TableElement::Uninitialized; kind.size_initial as usize], kind }
+ 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 }
}
#[inline(never)]
diff --git a/crates/tinywasm/tests/host_func_signature_check.rs b/crates/tinywasm/tests/host_func_signature_check.rs
index 0e4ba69..4590e77 100644
--- a/crates/tinywasm/tests/host_func_signature_check.rs
+++ b/crates/tinywasm/tests/host_func_signature_check.rs
@@ -43,7 +43,7 @@ fn test_return_invalid_type() -> Result<()> {
.unwrap();
let instance = module.clone().instantiate(&mut store, Some(imports)).unwrap();
- let caller = instance.exported_func_untyped(&store, "call_hfn").unwrap();
+ let caller = instance.func(&store, "call_hfn").unwrap();
// Return-type mismatch is only observable at call time.
let should_succeed = returned_values.iter().map(WasmValue::val_type).eq(func_ty.results.iter().copied());
let call_res = caller.call(&mut store, &args);
diff --git a/crates/tinywasm/tests/import_linking.rs b/crates/tinywasm/tests/import_linking.rs
new file mode 100644
index 0000000..85c41ed
--- /dev/null
+++ b/crates/tinywasm/tests/import_linking.rs
@@ -0,0 +1,57 @@
+use eyre::Result;
+use tinywasm::{Error, Imports, Module, Store};
+
+const WASM_ADD: &str = r#"
+ (module
+ (func $add (param i32 i32) (result i32)
+ local.get 0
+ local.get 1
+ i32.add)
+ (export "add" (func $add)))
+"#;
+
+const WASM_IMPORT: &str = r#"
+ (module
+ (import "adder" "add" (func $add (param i32 i32) (result i32)))
+ (func (export "main") (result i32)
+ i32.const 1
+ i32.const 2
+ call $add))
+"#;
+
+fn parse_modules() -> Result<(Module, Module)> {
+ let add = Module::parse_bytes(&wat::parse_str(WASM_ADD)?)?;
+ let import = Module::parse_bytes(&wat::parse_str(WASM_IMPORT)?)?;
+ Ok((add, import))
+}
+
+#[test]
+fn link_module_links_same_store_instance() -> Result<()> {
+ let (add_module, import_module) = parse_modules()?;
+ let mut store = Store::default();
+
+ let add_instance = add_module.instantiate(&mut store, None)?;
+ let mut imports = Imports::new();
+ imports.link_module("adder", add_instance)?;
+
+ let instance = import_module.instantiate(&mut store, Some(imports))?;
+ let main = instance.func_typed::<(), i32>(&store, "main")?;
+ assert_eq!(main.call(&mut store, ())?, 3);
+ Ok(())
+}
+
+#[test]
+fn link_module_rejects_cross_store_instance() -> Result<()> {
+ let (add_module, import_module) = parse_modules()?;
+
+ let mut source_store = Store::default();
+ let add_instance = add_module.instantiate(&mut source_store, None)?;
+
+ let mut target_store = Store::default();
+ let mut imports = Imports::new();
+ imports.link_module("adder", add_instance)?;
+
+ let err = import_module.instantiate(&mut target_store, Some(imports)).unwrap_err();
+ assert!(matches!(err, Error::InvalidStore));
+ Ok(())
+}
diff --git a/crates/tinywasm/tests/imported_table_init.rs b/crates/tinywasm/tests/imported_table_init.rs
new file mode 100644
index 0000000..c040394
--- /dev/null
+++ b/crates/tinywasm/tests/imported_table_init.rs
@@ -0,0 +1,35 @@
+use eyre::Result;
+use tinywasm::types::{FuncRef, TableType, ValType, WasmValue};
+use tinywasm::{Extern, Imports, Module, Store};
+
+#[test]
+fn imported_table_uses_provided_init_value() -> Result<()> {
+ let wasm = wat::parse_str(
+ r#"
+ (module
+ (import "host" "table" (table 3 funcref))
+ (func (export "slot_is_null") (param i32) (result i32)
+ local.get 0
+ table.get 0
+ ref.is_null)
+ )
+ "#,
+ )?;
+
+ let module = Module::parse_bytes(&wasm)?;
+ let mut store = Store::default();
+ let mut imports = Imports::new();
+ imports.define(
+ "host",
+ "table",
+ Extern::table(TableType::new(ValType::RefFunc, 3, None), WasmValue::RefFunc(FuncRef::new(Some(0)))),
+ )?;
+
+ let instance = module.instantiate(&mut store, Some(imports))?;
+ let slot_is_null = instance.func_typed::<i32, i32>(&store, "slot_is_null")?;
+
+ assert_eq!(slot_is_null.call(&mut store, 0)?, 0);
+ assert_eq!(slot_is_null.call(&mut store, 1)?, 0);
+
+ Ok(())
+}
diff --git a/crates/tinywasm/tests/internal_refs.rs b/crates/tinywasm/tests/internal_refs.rs
new file mode 100644
index 0000000..bd5561f
--- /dev/null
+++ b/crates/tinywasm/tests/internal_refs.rs
@@ -0,0 +1,101 @@
+use eyre::Result;
+use tinywasm::types::{FuncRef, WasmValue};
+use tinywasm::{ExternItemRef, ExternItemRefMut, Module, Store};
+
+#[test]
+#[cfg(feature = "guest_debug")]
+fn private_items_are_accessible_by_index() -> Result<()> {
+ let wasm = wat::parse_str(
+ r#"
+ (module
+ (func (result i32)
+ i32.const 7)
+ (memory 1)
+ (global (mut i32) (i32.const 11))
+ (table 2 funcref)
+ (elem (i32.const 0) func 0)
+ )
+ "#,
+ )?;
+
+ let module = Module::parse_bytes(&wasm)?;
+ let mut store = Store::default();
+ let instance = module.instantiate(&mut store, None)?;
+
+ let func = instance.func_by_index(&store, 0)?;
+ assert_eq!(func.call(&mut store, &[])?, vec![WasmValue::I32(7)]);
+
+ instance.memory_mut_by_index(&mut store, 0)?.store(0, 4, &[1, 2, 3, 4])?;
+ assert_eq!(instance.memory_by_index(&store, 0)?.load(0, 4)?, &[1, 2, 3, 4]);
+
+ assert_eq!(instance.table_by_index(&store, 0)?.size(), 2);
+ assert_eq!(instance.table_by_index(&store, 0)?.get(0)?, WasmValue::RefFunc(FuncRef::new(Some(0))));
+ assert!(matches!(instance.table_by_index(&store, 0)?.get(1)?, WasmValue::RefFunc(func_ref) if func_ref.is_null()));
+
+ assert_eq!(instance.global_by_index(&store, 0)?.get(), WasmValue::I32(11));
+ instance.global_mut_by_index(&mut store, 0)?.set(WasmValue::I32(23))?;
+ assert_eq!(instance.global_by_index(&store, 0)?.get(), WasmValue::I32(23));
+
+ Ok(())
+}
+
+#[test]
+fn exported_tables_and_globals_have_handle_and_helper_apis() -> Result<()> {
+ let wasm = wat::parse_str(
+ r#"
+ (module
+ (global (export "g") (mut i32) (i32.const 3))
+ (table (export "t") 1 funcref)
+ )
+ "#,
+ )?;
+
+ let module = Module::parse_bytes(&wasm)?;
+ let mut store = Store::default();
+ let instance = module.instantiate(&mut store, None)?;
+
+ assert_eq!(instance.global_get(&store, "g")?, WasmValue::I32(3));
+ assert_eq!(instance.global(&store, "g")?.get(), WasmValue::I32(3));
+ instance.global_set(&mut store, "g", WasmValue::I32(9))?;
+ assert_eq!(instance.global_mut(&mut store, "g")?.get(), WasmValue::I32(9));
+
+ let table = instance.table(&store, "t")?;
+ assert_eq!(table.size(), 1);
+ assert!(matches!(table.get(0)?, WasmValue::RefFunc(func_ref) if func_ref.is_null()));
+
+ let old_size = instance.table_mut(&mut store, "t")?.grow(1, WasmValue::RefFunc(FuncRef::null()))?;
+ assert_eq!(old_size, 1);
+ assert_eq!(instance.table(&store, "t")?.size(), 2);
+
+ Ok(())
+}
+
+#[test]
+fn extern_item_lookup_returns_expected_kinds() -> Result<()> {
+ let wasm = wat::parse_str(
+ r#"
+ (module
+ (func (export "f") (result i32) i32.const 1)
+ (memory (export "m") 1)
+ (table (export "t") 1 funcref)
+ (global (export "g") (mut i32) (i32.const 5))
+ )
+ "#,
+ )?;
+
+ let module = Module::parse_bytes(&wasm)?;
+ let mut store = Store::default();
+ let instance = module.instantiate(&mut store, None)?;
+
+ assert!(matches!(instance.extern_item(&store, "f")?, ExternItemRef::Func(_)));
+ assert!(matches!(instance.extern_item(&store, "m")?, ExternItemRef::Memory(_)));
+ assert!(matches!(instance.extern_item(&store, "t")?, ExternItemRef::Table(_)));
+ assert!(matches!(instance.extern_item(&store, "g")?, ExternItemRef::Global(_)));
+
+ assert!(matches!(instance.extern_item_mut(&mut store, "f")?, ExternItemRefMut::Func(_)));
+ assert!(matches!(instance.extern_item_mut(&mut store, "m")?, ExternItemRefMut::Memory(_)));
+ assert!(matches!(instance.extern_item_mut(&mut store, "t")?, ExternItemRefMut::Table(_)));
+ assert!(matches!(instance.extern_item_mut(&mut store, "g")?, ExternItemRefMut::Global(_)));
+
+ Ok(())
+}
diff --git a/crates/tinywasm/tests/memory_ref_api.rs b/crates/tinywasm/tests/memory_ref_api.rs
new file mode 100644
index 0000000..9e57b6b
--- /dev/null
+++ b/crates/tinywasm/tests/memory_ref_api.rs
@@ -0,0 +1,25 @@
+use eyre::Result;
+use tinywasm::{Module, Store};
+
+#[test]
+fn memory_ref_mut_copy_within_uses_src_then_dst_order() -> Result<()> {
+ let wasm = wat::parse_str(
+ r#"
+ (module
+ (memory (export "memory") 1)
+ )
+ "#,
+ )?;
+
+ let module = Module::parse_bytes(&wasm)?;
+ let mut store = Store::default();
+ let instance = module.instantiate(&mut store, None)?;
+
+ let mut memory = instance.memory_mut(&mut store, "memory")?;
+ memory.store(0, 4, &[1, 2, 3, 4])?;
+ memory.copy_within(0, 4, 4)?;
+
+ assert_eq!(memory.load(0, 8)?, &[1, 2, 3, 4, 1, 2, 3, 4]);
+
+ Ok(())
+}
diff --git a/crates/tinywasm/tests/module_descriptors.rs b/crates/tinywasm/tests/module_descriptors.rs
new file mode 100644
index 0000000..9993599
--- /dev/null
+++ b/crates/tinywasm/tests/module_descriptors.rs
@@ -0,0 +1,91 @@
+use eyre::Result;
+use tinywasm::types::ValType;
+use tinywasm::{ExportType, ImportType, Module};
+
+#[test]
+fn module_descriptors_resolve_imported_and_local_export_types() -> Result<()> {
+ let wasm = wat::parse_str(
+ r#"
+ (module
+ (type $t0 (func (param i32) (result i32)))
+ (import "host" "ifunc" (func $ifunc (type $t0)))
+ (import "host" "iglobal" (global $iglobal (mut i32)))
+
+ (func $lfunc (param i64) (result i64)
+ local.get 0)
+ (global $lglobal i64 (i64.const 9))
+
+ (export "ifunc_export" (func $ifunc))
+ (export "iglobal_export" (global $iglobal))
+ (export "lfunc_export" (func $lfunc))
+ (export "lglobal_export" (global $lglobal))
+ )
+ "#,
+ )?;
+
+ let module = Module::parse_bytes(&wasm)?;
+
+ let imports: Vec<_> = module.imports().collect();
+ assert_eq!(imports.len(), 2);
+
+ let ifunc_import = imports.iter().find(|import| import.name == "ifunc").expect("ifunc import not found");
+ match ifunc_import.ty {
+ ImportType::Func(ty) => {
+ assert_eq!(ty.params.as_ref(), &[ValType::I32]);
+ assert_eq!(ty.results.as_ref(), &[ValType::I32]);
+ }
+ _ => panic!("ifunc import should be a function type"),
+ }
+
+ let iglobal_import = imports.iter().find(|import| import.name == "iglobal").expect("iglobal import not found");
+ match iglobal_import.ty {
+ ImportType::Global(ty) => {
+ assert!(ty.mutable);
+ assert_eq!(ty.ty, ValType::I32);
+ }
+ _ => panic!("iglobal import should be a global type"),
+ }
+
+ let exports: Vec<_> = module.exports().collect();
+ assert_eq!(exports.len(), 4);
+
+ let ifunc_export = exports.iter().find(|export| export.name == "ifunc_export").expect("ifunc export not found");
+ match ifunc_export.ty {
+ ExportType::Func(ty) => {
+ assert_eq!(ty.params.as_ref(), &[ValType::I32]);
+ assert_eq!(ty.results.as_ref(), &[ValType::I32]);
+ }
+ _ => panic!("ifunc export should resolve to imported function type"),
+ }
+
+ let iglobal_export =
+ exports.iter().find(|export| export.name == "iglobal_export").expect("iglobal export not found");
+ match iglobal_export.ty {
+ ExportType::Global(ty) => {
+ assert!(ty.mutable);
+ assert_eq!(ty.ty, ValType::I32);
+ }
+ _ => panic!("iglobal export should resolve to imported global type"),
+ }
+
+ let lfunc_export = exports.iter().find(|export| export.name == "lfunc_export").expect("lfunc export not found");
+ match lfunc_export.ty {
+ ExportType::Func(ty) => {
+ assert_eq!(ty.params.as_ref(), &[ValType::I64]);
+ assert_eq!(ty.results.as_ref(), &[ValType::I64]);
+ }
+ _ => panic!("lfunc export should resolve to local function type"),
+ }
+
+ let lglobal_export =
+ exports.iter().find(|export| export.name == "lglobal_export").expect("lglobal export not found");
+ match lglobal_export.ty {
+ ExportType::Global(ty) => {
+ assert!(!ty.mutable);
+ assert_eq!(ty.ty, ValType::I64);
+ }
+ _ => panic!("lglobal export should resolve to local global type"),
+ }
+
+ Ok(())
+}
diff --git a/crates/tinywasm/tests/resume_execution.rs b/crates/tinywasm/tests/resume_execution.rs
index 93b1c27..5a58f81 100644
--- a/crates/tinywasm/tests/resume_execution.rs
+++ b/crates/tinywasm/tests/resume_execution.rs
@@ -14,12 +14,12 @@ fn typed_resume_matches_non_budgeted_call() -> Result<()> {
let mut store_full = tinywasm::Store::default();
let instance_full = module.clone().instantiate(&mut store_full, None)?;
- let func_full = instance_full.exported_func::<i32, i32>(&store_full, "fibonacci_recursive")?;
+ let func_full = instance_full.func_typed::<i32, i32>(&store_full, "fibonacci_recursive")?;
let expected = func_full.call(&mut store_full, 20)?;
let mut store_budgeted = tinywasm::Store::default();
let instance_budgeted = module.instantiate(&mut store_budgeted, None)?;
- let func_budgeted = instance_budgeted.exported_func::<i32, i32>(&store_budgeted, "fibonacci_recursive")?;
+ let func_budgeted = instance_budgeted.func_typed::<i32, i32>(&store_budgeted, "fibonacci_recursive")?;
let mut exec = func_budgeted.call_resumable(&mut store_budgeted, 20)?;
let mut saw_suspended = false;
@@ -41,7 +41,7 @@ fn untyped_resume_supports_zero_fuel() -> Result<()> {
let module = Module::parse_bytes(ADD_WASM)?;
let mut store = tinywasm::Store::default();
let instance = module.instantiate(&mut store, None)?;
- let func = instance.exported_func_untyped(&store, "add")?;
+ let func = instance.func(&store, "add")?;
let mut exec = func.call_resumable(&mut store, &[WasmValue::I32(20), WasmValue::I32(22)])?;
assert!(matches!(exec.resume_with_fuel(0)?, ExecProgress::Suspended));
@@ -62,12 +62,12 @@ fn weighted_call_fuel_requires_more_rounds() -> Result<()> {
let mut per_instr_store = tinywasm::Store::default();
let instance_per_instr = module.clone().instantiate(&mut per_instr_store, None)?;
- let func_per_instr = instance_per_instr.exported_func::<i32, i32>(&per_instr_store, "fibonacci_recursive")?;
+ let func_per_instr = instance_per_instr.func_typed::<i32, i32>(&per_instr_store, "fibonacci_recursive")?;
let mut weighted_store =
tinywasm::Store::new(tinywasm::Engine::new(Config::new().fuel_policy(FuelPolicy::Weighted)));
let instance_weighted = module.instantiate(&mut weighted_store, None)?;
- let func_weighted = instance_weighted.exported_func::<i32, i32>(&weighted_store, "fibonacci_recursive")?;
+ let func_weighted = instance_weighted.func_typed::<i32, i32>(&weighted_store, "fibonacci_recursive")?;
let fuel = 64;
let n = 20;
@@ -104,7 +104,7 @@ fn time_budget_zero_suspends_then_completes() -> Result<()> {
let module = Module::parse_bytes(ADD_WASM)?;
let mut store = tinywasm::Store::default();
let instance = module.instantiate(&mut store, None)?;
- let func = instance.exported_func::<(i32, i32), i32>(&store, "add")?;
+ let func = instance.func_typed::<(i32, i32), i32>(&store, "add")?;
let mut exec = func.call_resumable(&mut store, (20, 22))?;
assert!(matches!(exec.resume_with_time_budget(Duration::ZERO)?, ExecProgress::Suspended));
diff --git a/crates/tinywasm/tests/store_ownership.rs b/crates/tinywasm/tests/store_ownership.rs
new file mode 100644
index 0000000..a9cbad4
--- /dev/null
+++ b/crates/tinywasm/tests/store_ownership.rs
@@ -0,0 +1,43 @@
+use eyre::Result;
+use tinywasm::{Error, Module, Store};
+
+const MODULE_WAT: &str = r#"
+ (module
+ (func (export "add") (param i32 i32) (result i32)
+ local.get 0
+ local.get 1
+ i32.add)
+ (memory (export "memory") 1)
+ )
+"#;
+
+#[test]
+fn func_handle_rejects_wrong_store() -> Result<()> {
+ let wasm = wat::parse_str(MODULE_WAT)?;
+ let module = Module::parse_bytes(&wasm)?;
+
+ let mut owner_store = Store::default();
+ let instance = module.instantiate(&mut owner_store, None)?;
+ let func = instance.func(&owner_store, "add")?;
+
+ let mut other_store = Store::default();
+ let err = func.call(&mut other_store, &[1.into(), 2.into()]).unwrap_err();
+ assert!(matches!(err, Error::InvalidStore));
+
+ Ok(())
+}
+
+#[test]
+fn memory_access_rejects_wrong_store() -> Result<()> {
+ let wasm = wat::parse_str(MODULE_WAT)?;
+ let module = Module::parse_bytes(&wasm)?;
+
+ let mut owner_store = Store::default();
+ let instance = module.instantiate(&mut owner_store, None)?;
+
+ let other_store = Store::default();
+ let err = instance.memory(&other_store, "memory").unwrap_err();
+ assert!(matches!(err, Error::InvalidStore));
+
+ Ok(())
+}
diff --git a/crates/tinywasm/tests/testsuite/run.rs b/crates/tinywasm/tests/testsuite/run.rs
index d13c59b..9987cd8 100644
--- a/crates/tinywasm/tests/testsuite/run.rs
+++ b/crates/tinywasm/tests/testsuite/run.rs
@@ -13,57 +13,61 @@ use wasm_testsuite::wast::{Wast, lexer::Lexer, parser::ParseBuffer};
#[derive(Default)]
struct ModuleRegistry {
- modules: HashMap<String, ModuleInstanceAddr>,
+ modules: HashMap<String, ModuleInstance>,
- named_modules: HashMap<String, ModuleInstanceAddr>,
- last_module: Option<ModuleInstanceAddr>,
+ named_modules: HashMap<String, ModuleInstance>,
+ last_module: Option<ModuleInstance>,
}
impl ModuleRegistry {
- fn modules(&self) -> &HashMap<String, ModuleInstanceAddr> {
+ fn modules(&self) -> &HashMap<String, ModuleInstance> {
&self.modules
}
- fn update_last_module(&mut self, addr: ModuleInstanceAddr, name: Option<String>) {
- self.last_module = Some(addr);
+ fn update_last_module(&mut self, module: ModuleInstance, name: Option<String>) {
+ self.last_module = Some(module.clone());
if let Some(name) = name {
- self.named_modules.insert(name, addr);
+ self.named_modules.insert(name, module);
}
}
- fn register(&mut self, name: String, addr: ModuleInstanceAddr) {
+ fn register(&mut self, name: String, module: ModuleInstance) {
log::debug!("registering module: {name}");
- self.modules.insert(name.clone(), addr);
+ self.modules.insert(name.clone(), module.clone());
- self.last_module = Some(addr);
- self.named_modules.insert(name, addr);
+ self.last_module = Some(module.clone());
+ self.named_modules.insert(name, module);
}
- fn get_idx(&self, module_id: Option<wast::token::Id<'_>>) -> Option<&ModuleInstanceAddr> {
+ fn get_idx(&self, module_id: Option<wast::token::Id<'_>>) -> Option<ModuleInstanceAddr> {
match module_id {
Some(module) => {
log::debug!("getting module: {}", module.name());
- if let Some(addr) = self.modules.get(module.name()) {
- return Some(addr);
+ if let Some(module) = self.modules.get(module.name()) {
+ return Some(module.id());
}
- if let Some(addr) = self.named_modules.get(module.name()) {
- return Some(addr);
+ if let Some(module) = self.named_modules.get(module.name()) {
+ return Some(module.id());
}
None
}
- None => self.last_module.as_ref(),
+ None => self.last_module.as_ref().map(ModuleInstance::id),
}
}
- fn get(&self, module_id: Option<wast::token::Id<'_>>, store: &tinywasm::Store) -> Option<ModuleInstance> {
- let addr = self.get_idx(module_id)?;
- store.get_module_instance(*addr)
+ fn get(&self, module_id: Option<wast::token::Id<'_>>) -> Option<ModuleInstance> {
+ match module_id {
+ Some(module_id) => {
+ self.modules.get(module_id.name()).or_else(|| self.named_modules.get(module_id.name())).cloned()
+ }
+ None => self.last_module.clone(),
+ }
}
- fn last(&self, store: &tinywasm::Store) -> Option<ModuleInstance> {
- store.get_module_instance(*self.last_module.as_ref()?)
+ fn last(&self) -> Option<ModuleInstance> {
+ self.last_module.clone()
}
}
@@ -83,7 +87,7 @@ impl TestSuite {
Ok(())
}
- fn imports(modules: &HashMap<std::string::String, u32>) -> Result<Imports> {
+ fn imports(modules: &HashMap<std::string::String, ModuleInstance>) -> Result<Imports> {
let mut imports = Imports::new();
let table =
@@ -143,9 +147,9 @@ impl TestSuite {
.define("spectest", "print_i32_f32", print_i32_f32)?
.define("spectest", "print_f64_f64", print_f64_f64)?;
- for (name, addr) in modules {
+ for (name, module) in modules {
log::debug!("registering module: {name}");
- imports.link_module(name, *addr)?;
+ imports.link_module(name, module.clone())?;
}
Ok(imports)
@@ -186,7 +190,7 @@ impl TestSuite {
match directive {
Register { span, name, .. } => {
- let Some(last) = module_registry.last(&store) else {
+ let Some(last) = module_registry.last() else {
test_group.add_result(
&format!("Register({i})"),
span.linecol_in(wast_raw),
@@ -194,7 +198,7 @@ impl TestSuite {
);
continue;
};
- module_registry.register(name.to_string(), last.id());
+ module_registry.register(name.to_string(), last);
test_group.add_result(&format!("Register({i})"), span.linecol_in(wast_raw), Ok(()));
}
@@ -214,7 +218,7 @@ impl TestSuite {
match &result {
Err(err) => debug!("failed to parse module: {err:?}"),
- Ok((name, module)) => module_registry.update_last_module(module.id(), name.clone()),
+ Ok((name, module)) => module_registry.update_last_module(module.clone(), name.clone()),
};
test_group.add_result(&format!("Wat({i})"), span.linecol_in(wast_raw), result.map(|_| ()));
@@ -425,7 +429,7 @@ impl TestSuite {
let invoke = match match exec {
wast::WastExecute::Wat(_) => Err(eyre!("wat not supported")),
wast::WastExecute::Get { module: module_id, global, .. } => {
- let module = module_registry.get(module_id, &store);
+ let module = module_registry.get(module_id);
let Some(module) = module else {
test_group.add_result(
&format!("AssertReturn(unsupported-{i})"),
diff --git a/crates/tinywasm/tests/testsuite/util.rs b/crates/tinywasm/tests/testsuite/util.rs
index 851fa20..000bb1a 100644
--- a/crates/tinywasm/tests/testsuite/util.rs
+++ b/crates/tinywasm/tests/testsuite/util.rs
@@ -13,7 +13,7 @@ pub fn try_downcast_panic(panic: Box<dyn std::any::Any + Send>) -> String {
}
pub fn exec_fn_instance(
- instance: Option<&ModuleInstanceAddr>,
+ instance: Option<ModuleInstanceAddr>,
store: &mut tinywasm::Store,
name: &str,
args: &[tinywasm_types::WasmValue],
@@ -22,11 +22,11 @@ pub fn exec_fn_instance(
return Err(tinywasm::Error::Other("no instance found".to_string()));
};
- let Some(instance) = store.get_module_instance(*instance) else {
+ let Some(instance) = store.get_module_instance(instance) else {
return Err(tinywasm::Error::Other("no instance found".to_string()));
};
- let func = instance.exported_func_untyped(store, name)?;
+ let func = instance.func(store, name)?;
func.call(store, args)
}
@@ -43,7 +43,7 @@ pub fn exec_fn(
let mut store = tinywasm::Store::default();
let module = tinywasm::Module::from(module);
let instance = module.instantiate(&mut store, imports)?;
- instance.exported_func_untyped(&store, name)?.call(&mut store, args)
+ instance.func(&store, name)?.call(&mut store, args)
}
pub fn catch_unwind_silent<R>(f: impl FnOnce() -> R) -> std::thread::Result<R> {
diff --git a/crates/tinywasm/tests/typed_lookup.rs b/crates/tinywasm/tests/typed_lookup.rs
new file mode 100644
index 0000000..7bfc26d
--- /dev/null
+++ b/crates/tinywasm/tests/typed_lookup.rs
@@ -0,0 +1,48 @@
+use eyre::Result;
+use tinywasm::Module;
+
+#[test]
+fn func_typed_rejects_wrong_param_or_result_types() -> Result<()> {
+ let wasm = wat::parse_str(
+ r#"
+ (module
+ (func (export "add") (param i32 i32) (result i32)
+ local.get 0
+ local.get 1
+ i32.add)
+ )
+ "#,
+ )?;
+
+ let module = Module::parse_bytes(&wasm)?;
+ let mut store = tinywasm::Store::default();
+ let instance = module.instantiate(&mut store, None)?;
+
+ assert!(instance.func_typed::<(i32, i32), i32>(&store, "add").is_ok());
+ assert!(instance.func_typed::<i32, i32>(&store, "add").is_err());
+ assert!(instance.func_typed::<(i32, i32), ()>(&store, "add").is_err());
+
+ Ok(())
+}
+
+#[test]
+fn func_typed_rejects_partial_multi_value_results() -> Result<()> {
+ let wasm = wat::parse_str(
+ r#"
+ (module
+ (func (export "pair") (result i32 i32)
+ i32.const 1
+ i32.const 2)
+ )
+ "#,
+ )?;
+
+ let module = Module::parse_bytes(&wasm)?;
+ let mut store = tinywasm::Store::default();
+ let instance = module.instantiate(&mut store, None)?;
+
+ assert!(instance.func_typed::<(), (i32, i32)>(&store, "pair").is_ok());
+ assert!(instance.func_typed::<(), i32>(&store, "pair").is_err());
+
+ Ok(())
+}
diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs
index 0cbcad7..8b12811 100644
--- a/crates/types/src/instructions.rs
+++ b/crates/types/src/instructions.rs
@@ -171,14 +171,14 @@ pub enum Instruction {
TableInit(ElemAddr, TableAddr),
TableGet(TableAddr),
TableSet(TableAddr),
- TableCopy { from: TableAddr, to: TableAddr },
+ TableCopy { dst_table: TableAddr, src_table: TableAddr },
TableGrow(TableAddr),
TableSize(TableAddr),
TableFill(TableAddr),
// > Bulk Memory Instructions
MemoryInit(MemAddr, DataAddr),
- MemoryCopy(MemAddr, MemAddr),
+ MemoryCopy { dst_mem: MemAddr, src_mem: MemAddr },
MemoryFill(MemAddr),
MemoryFillImm(MemAddr, u8, i32),
DataDrop(DataAddr),