diff options
38 files changed, 1290 insertions, 184 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index b9b5fcc..8a6b138 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Support for the custom memory page sizes proposal ([#22](https://github.com/explodingcamera/tinywasm/pull/22) by [@danielstuart14](https://github.com/danielstuart14)) +- Support for the `custom_page_sizes` proposal ([#22](https://github.com/explodingcamera/tinywasm/pull/22) by [@danielstuart14](https://github.com/danielstuart14)) - Support for the `tail_call` proposal - Support for the `memory64` proposal -- Support for the fixed-width `simd` proposal +- Support for the `simd` proposal - Support for the `relaxed_simd` proposal - Support for the `wide_arithmetic` proposal - New `Engine` API (`tinywasm::Engine` and `engine::Config`) for runtime configuration @@ -20,12 +20,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Host-function fuel APIs: `FuncContext::charge_fuel` and `FuncContext::remaining_fuel` - `engine::FuelPolicy` and `engine::Config::fuel_policy` for fuel accounting behavior - New `canonicalize_nans` feature flag to enable canonicalizing NaN values in the `f32`, `f64`, and `v128` types +- Public API rework for runtime object access: + - export lookups: `func`, `func_typed`, `memory`, `memory_mut` + - table/global access: `table`, `table_mut`, `global`, `global_mut`, `global_get`, `global_set` + - generic export access: `extern_item`, `extern_item_mut` + - export iteration: `ModuleInstance::exports` + - module descriptors: `Module::imports`, `Module::exports` + - raw memory data access: `MemoryRef::data`/`data_size`, `MemoryRefMut::data`/`data_mut`/`data_size` ### Changed - Locals are now stored in the typed value stacks instead of a separate locals structure - Structured control flow is fully lowered to jump-oriented internal instructions during parsing -- Stack and call-stack limits are configured via `engine::Config` +- Stack and call-stack limits can now be configured via `engine::Config` +- Module-internal by-index inspection APIs are now gated behind the `guest_debug` feature ### Breaking Changes @@ -41,12 +49,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Cargo feature `logging` was renamed to `log` - Increased MSRV to 1.90 - `Error::ParseError` was renamed to `Error::Parser`, and `Error::Twasm` was added +- `ModuleInstance` export lookup APIs were renamed: + - `exported_func_untyped` -> `func` + - `exported_func` -> `func_typed` + - `exported_memory` -> `memory` + - `exported_memory_mut` -> `memory_mut` +- `Imports::link_module` now takes a `ModuleInstance` instead of a raw module instance id +- `func_typed` now validates the exact wasm signature at lookup time and fails immediately on mismatches ### Fixed - Fixed archive **no_std** support which was broken in the previous release, and added more tests to ensure it stays working - `ModuleInstance::exported_memory` and `FuncContext::exported_memory` are now actually immutable ([#41](https://github.com/explodingcamera/tinywasm/pull/41)) - Check returns in untyped host functions ([#27](https://github.com/explodingcamera/tinywasm/pull/27)) (thanks [@WhaleKit](https://github.com/WhaleKit)) +- `MemoryRefMut::copy_within(src, dst, len)` now follows its documented argument order +- Imported tables created with `Extern::table(ty, init)` now honor the provided init value ## [0.8.0] - 2024-08-29 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), diff --git a/examples/archive.rs b/examples/archive.rs index 7a1021b..7025fba 100644 --- a/examples/archive.rs +++ b/examples/archive.rs @@ -21,7 +21,7 @@ fn main() -> Result<()> { let module: Module = TinyWasmModule::from_twasm(&twasm)?.into(); let mut store = Store::default(); let instance = module.instantiate(&mut store, None)?; - let add = instance.exported_func::<(i32, i32), i32>(&store, "add")?; + let add = instance.func_typed::<(i32, i32), i32>(&store, "add")?; assert_eq!(add.call(&mut store, (1, 2))?, 3); diff --git a/examples/funcref_callbacks.rs b/examples/funcref_callbacks.rs index a460df8..6e1d3ef 100644 --- a/examples/funcref_callbacks.rs +++ b/examples/funcref_callbacks.rs @@ -56,8 +56,7 @@ fn run_passed_funcref_example() -> Result<()> { "call_this", Extern::typed_func(|mut ctx: FuncContext<'_>, func_ref: FuncRef| -> tinywasm::Result<()> { // Host cannot call a funcref directly, so it routes through Wasm. - let call_by_ref = - ctx.module().exported_func::<(FuncRef, i32, i32), i32>(ctx.store(), "call_binop_by_ref")?; + let call_by_ref = ctx.module().func_typed::<(FuncRef, i32, i32), i32>(ctx.store(), "call_binop_by_ref")?; let _result = call_by_ref.call(ctx.store_mut(), (func_ref, LHS, RHS))?; Ok(()) }), @@ -70,7 +69,7 @@ fn run_passed_funcref_example() -> Result<()> { )?; let instance = module.instantiate(&mut store, Some(imports))?; - let caller = instance.exported_func::<(), ()>(&store, "tell_host_to_call")?; + let caller = instance.func_typed::<(), ()>(&store, "tell_host_to_call")?; caller.call(&mut store, ())?; @@ -123,12 +122,11 @@ fn run_returned_funcref_example() -> Result<()> { let instance = module.instantiate(&mut store, Some(imports))?; let (add_ref, sub_ref, mul_ref) = { - let get_funcrefs = - instance.exported_func::<(), (FuncRef, FuncRef, FuncRef)>(&store, "what_should_host_call")?; + let get_funcrefs = instance.func_typed::<(), (FuncRef, FuncRef, FuncRef)>(&store, "what_should_host_call")?; get_funcrefs.call(&mut store, ())? }; - let call_by_ref = instance.exported_func::<(FuncRef, i32, i32), i32>(&store, "call_binop_by_ref")?; + let call_by_ref = instance.func_typed::<(FuncRef, i32, i32), i32>(&store, "call_binop_by_ref")?; for func_ref in [add_ref, sub_ref, mul_ref] { let _result = call_by_ref.call(&mut store, (func_ref, LHS, RHS))?; diff --git a/examples/linking.rs b/examples/linking.rs index f94773a..ce9748b 100644 --- a/examples/linking.rs +++ b/examples/linking.rs @@ -37,13 +37,13 @@ fn main() -> Result<()> { // Link the `adder` namespace to the `add` module's instance. let mut imports = tinywasm::Imports::new(); - imports.link_module("adder", add_instance.id())?; + imports.link_module("adder", add_instance)?; // Instantiate the `import` module with the linked imports. let import_instance = import_module.instantiate(&mut store, Some(imports))?; // Call the `main` function, which uses the imported `add` function. - let main = import_instance.exported_func::<(), i32>(&store, "main")?; + let main = import_instance.func_typed::<(), i32>(&store, "main")?; assert_eq!(main.call(&mut store, ())?, 3); Ok(()) diff --git a/examples/resumable.rs b/examples/resumable.rs index 6838e20..f3ebc89 100644 --- a/examples/resumable.rs +++ b/examples/resumable.rs @@ -27,7 +27,7 @@ fn main() -> Result<()> { let module = Module::parse_bytes(&wasm)?; let mut store = Store::default(); let instance = module.instantiate(&mut store, None)?; - let count_down = instance.exported_func::<i32, i32>(&store, "count_down")?; + let count_down = instance.func_typed::<i32, i32>(&store, "count_down")?; let mut execution = count_down.call_resumable(&mut store, 10_000)?; let fuel_per_round = 128; diff --git a/examples/rust/src/tinywasm.rs b/examples/rust/src/tinywasm.rs index 11d7637..e06cf81 100644 --- a/examples/rust/src/tinywasm.rs +++ b/examples/rust/src/tinywasm.rs @@ -26,7 +26,7 @@ fn run() -> tinywasm::Result<()> { )?; let instance = module.instantiate(&mut store, Some(imports))?; - let add_and_print = instance.exported_func::<(i32, i32), ()>(&store, "add_and_print")?; + let add_and_print = instance.func_typed::<(i32, i32), ()>(&store, "add_and_print")?; add_and_print.call(&mut store, (1, 2))?; Ok(()) } diff --git a/examples/rust/src/tinywasm_no_std.rs b/examples/rust/src/tinywasm_no_std.rs index 6a31ff0..9a032dd 100644 --- a/examples/rust/src/tinywasm_no_std.rs +++ b/examples/rust/src/tinywasm_no_std.rs @@ -43,7 +43,7 @@ fn run() -> tinywasm::Result<()> { )?; let instance = module.instantiate(&mut store, Some(imports))?; - let add_and_print = instance.exported_func::<(i32, i32), ()>(&store, "add_and_print")?; + let add_and_print = instance.func_typed::<(i32, i32), ()>(&store, "add_and_print")?; add_and_print.call(&mut store, (1, 2))?; Ok(()) } diff --git a/examples/simple.rs b/examples/simple.rs index e0842dd..8371992 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -15,7 +15,7 @@ fn main() -> Result<()> { let module = Module::parse_bytes(&wasm)?; let mut store = Store::default(); let instance = module.instantiate(&mut store, None)?; - let add = instance.exported_func::<(i32, i32), i32>(&store, "add")?; + let add = instance.func_typed::<(i32, i32), i32>(&store, "add")?; assert_eq!(add.call(&mut store, (1, 2))?, 3); Ok(()) diff --git a/examples/simple2.rs b/examples/simple2.rs index a3124f8..66e85b3 100644 --- a/examples/simple2.rs +++ b/examples/simple2.rs @@ -14,7 +14,7 @@ fn main() -> Result<()> { let module = Module::parse_bytes(&wasm)?; let mut store = Store::default(); let instance = module.instantiate(&mut store, None)?; - let add = instance.exported_func::<(i32, i64), (i32, i64)>(&store, "return")?; + let add = instance.func_typed::<(i32, i64), (i32, i64)>(&store, "return")?; assert_eq!(add.call(&mut store, (1, 2))?, (1, 2)); Ok(()) diff --git a/examples/wasm-rust.rs b/examples/wasm-rust.rs index d376130..0ffa499 100644 --- a/examples/wasm-rust.rs +++ b/examples/wasm-rust.rs @@ -79,7 +79,7 @@ fn tinywasm() -> Result<()> { imports.define("env", "printi32", Extern::typed_func(|_: FuncContext<'_>, _x: i32| Ok(())))?; let instance = module.instantiate(&mut store, Some(black_box(imports)))?; - let hello = instance.exported_func::<(), ()>(&store, "hello")?; + let hello = instance.func_typed::<(), ()>(&store, "hello")?; hello.call(&mut store, black_box(()))?; hello.call(&mut store, black_box(()))?; hello.call(&mut store, black_box(()))?; @@ -94,7 +94,7 @@ fn tinywasm_no_std() -> Result<()> { imports.define("env", "printi32", Extern::typed_func(|_: FuncContext<'_>, _x: i32| Ok(())))?; let instance = module.instantiate(&mut store, Some(black_box(imports)))?; - let hello = instance.exported_func::<(), ()>(&store, "hello")?; + let hello = instance.func_typed::<(), ()>(&store, "hello")?; hello.call(&mut store, black_box(()))?; hello.call(&mut store, black_box(()))?; hello.call(&mut store, black_box(()))?; @@ -109,22 +109,20 @@ fn hello() -> Result<()> { imports.define( "env", "print_utf8", - Extern::typed_func(|ctx: FuncContext<'_>, args: (i64, i32)| { - let mem = ctx.exported_memory("memory")?; - let ptr = args.0 as usize; - let len = args.1 as usize; - let string = mem.load_string(ptr, len)?; + Extern::typed_func(|ctx: FuncContext<'_>, (ptr, len): (i64, i32)| { + let mem = ctx.memory("memory")?; + let string = mem.load_string(ptr as usize, len as usize)?; println!("{string}"); Ok(()) }), )?; let instance = module.instantiate(&mut store, Some(imports))?; - let arg_ptr = instance.exported_func::<(), i32>(&store, "arg_ptr")?.call(&mut store, ())?; + let arg_ptr = instance.func_typed::<(), i32>(&store, "arg_ptr")?.call(&mut store, ())?; let arg = b"world"; - instance.exported_memory_mut(&mut store, "memory")?.store(arg_ptr as usize, arg.len(), arg)?; - let hello = instance.exported_func::<i32, ()>(&store, "hello")?; + instance.memory_mut(&mut store, "memory")?.store(arg_ptr as usize, arg.len(), arg)?; + let hello = instance.func_typed::<i32, ()>(&store, "hello")?; hello.call(&mut store, arg.len() as i32)?; Ok(()) @@ -145,7 +143,7 @@ fn host_fn() -> Result<()> { )?; let instance = module.instantiate(&mut store, Some(imports))?; - let host_fn = instance.exported_func::<(), i32>(&store, "foo")?; + let host_fn = instance.func_typed::<(), i32>(&store, "foo")?; assert_eq!(host_fn.call(&mut store, ())?, 3); Ok(()) } @@ -165,7 +163,7 @@ fn printi32() -> Result<()> { )?; let instance = module.instantiate(&mut store, Some(imports))?; - let add_and_print = instance.exported_func::<(i32, i32), ()>(&store, "add_and_print")?; + let add_and_print = instance.func_typed::<(i32, i32), ()>(&store, "add_and_print")?; add_and_print.call(&mut store, (1, 2))?; Ok(()) @@ -176,7 +174,7 @@ fn fibonacci() -> Result<()> { let mut store = Store::default(); let instance = module.instantiate(&mut store, None)?; - let fibonacci = instance.exported_func::<i32, i32>(&store, "fibonacci_recursive")?; + let fibonacci = instance.func_typed::<i32, i32>(&store, "fibonacci_recursive")?; let n = 26; let result = fibonacci.call(&mut store, n)?; println!("fibonacci({n}) = {result}"); @@ -189,7 +187,7 @@ fn argon2id() -> Result<()> { let mut store = Store::default(); let instance = module.instantiate(&mut store, None)?; - let argon2id = instance.exported_func::<(i32, i32, i32), i32>(&store, "argon2id")?; + let argon2id = instance.func_typed::<(i32, i32, i32), i32>(&store, "argon2id")?; argon2id.call(&mut store, (1000, 2, 1))?; Ok(()) |
