diff options
| author | Henry <mail@henrygressmann.de> | 2026-04-12 21:16:49 +0200 |
|---|---|---|
| committer | Henry <mail@henrygressmann.de> | 2026-04-12 21:16:49 +0200 |
| commit | 80cf746b50513af77203f382f5f53bd036087358 (patch) | |
| tree | a9493ec0a69caba0dd82e568b6556340be306e0a /crates | |
| parent | d89729df921d39f81fe8a43990678853deb1edde (diff) | |
feat: remove lifetimes from runtime objects
Signed-off-by: Henry <mail@henrygressmann.de>
Diffstat (limited to 'crates')
26 files changed, 917 insertions, 930 deletions
diff --git a/crates/cli/src/bin.rs b/crates/cli/src/bin.rs index 814edd4..5fd50f9 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.func(&store, &func)?; + let func = instance.func_untyped(&store, &func)?; let res = func.call(&mut store, args)?; info!("{res:?}"); } diff --git a/crates/parser/src/conversion.rs b/crates/parser/src/conversion.rs index dfd5415..63a42b2 100644 --- a/crates/parser/src/conversion.rs +++ b/crates/parser/src/conversion.rs @@ -86,7 +86,7 @@ pub(crate) fn convert_module_import(import: wasmparser::Import<'_>) -> Result<Im }), wasmparser::TypeRef::Memory(ty) => ImportKind::Memory(convert_module_memory(ty)), wasmparser::TypeRef::Global(ty) => { - ImportKind::Global(GlobalType { mutable: ty.mutable, ty: convert_valtype(&ty.content_type) }) + ImportKind::Global(GlobalType::new(convert_valtype(&ty.content_type), ty.mutable)) } wasmparser::TypeRef::Tag(ty) => { return Err(crate::ParseError::UnsupportedOperator(format!("Unsupported import kind: {ty:?}"))); @@ -140,7 +140,7 @@ pub(crate) fn convert_module_globals( let global = global?; let ty = convert_valtype(&global.ty.content_type); let ops = global.init_expr.get_operators_reader(); - Ok(Global { init: process_const_operators(ops)?, ty: GlobalType { mutable: global.ty.mutable, ty } }) + Ok(Global { init: process_const_operators(ops)?, ty: GlobalType::new(ty, global.ty.mutable) }) }) .collect::<Result<Vec<_>>>() } diff --git a/crates/parser/src/module.rs b/crates/parser/src/module.rs index b53dfd6..8405c6d 100644 --- a/crates/parser/src/module.rs +++ b/crates/parser/src/module.rs @@ -158,9 +158,9 @@ impl ModuleReader { validator.end(offset)?; self.end_reached = true; } - Payload::CustomSection(_reader) => { + Payload::CustomSection(reader) => { debug!("Found custom section"); - debug!("Skipping custom section: {:?}", _reader.name()); + debug!("Skipping custom section: {:?}", reader.name()); } Payload::UnknownSection { .. } => return Err(ParseError::UnsupportedSection("Unknown section".into())), section => return Err(ParseError::UnsupportedSection(format!("Unsupported section: {section:?}"))), diff --git a/crates/tinywasm/benches/argon2id.rs b/crates/tinywasm/benches/argon2id.rs index 5d1719f..277b904 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.func_typed::<(i32, i32, i32), i32>(&store, "argon2id")?; + let argon2 = instance.func::<(i32, i32, i32), i32>(&store, "argon2id")?; argon2.call(&mut store, (1000, 2, 1))?; Ok(()) } diff --git a/crates/tinywasm/benches/fibonacci.rs b/crates/tinywasm/benches/fibonacci.rs index 2865594..51bfa52 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.func_typed::<i32, i32>( + let argon2 = instance.func::<i32, i32>( &store, match recursive { true => "fibonacci_recursive", diff --git a/crates/tinywasm/benches/tinywasm.rs b/crates/tinywasm/benches/tinywasm.rs index 7ab9587..8de1838 100644 --- a/crates/tinywasm/benches/tinywasm.rs +++ b/crates/tinywasm/benches/tinywasm.rs @@ -1,6 +1,6 @@ use criterion::{Criterion, criterion_group, criterion_main}; use eyre::Result; -use tinywasm::{Extern, FuncContext, Imports, ModuleInstance, Store, types}; +use tinywasm::{FuncContext, HostFunction, Imports, ModuleInstance, Store, types}; use types::TinyWasmModule; const WASM: &[u8] = include_bytes!("../../../examples/rust/out/tinywasm.wasm"); @@ -24,9 +24,9 @@ fn tinywasm_from_twasm(twasm: &[u8]) -> Result<TinyWasmModule> { fn tinywasm_run(module: TinyWasmModule) -> Result<()> { let mut store = Store::default(); let mut imports = Imports::default(); - imports.define("env", "printi32", Extern::typed_func(|_: FuncContext<'_>, _: i32| Ok(()))).expect("define"); + imports.define("env", "printi32", HostFunction::from(&mut store, |_: FuncContext<'_>, _: i32| Ok(()))); let instance = ModuleInstance::instantiate(&mut store, module.into(), Some(imports)).expect("instantiate"); - let hello = instance.func_typed::<(), ()>(&store, "hello").expect("func_typed"); + let hello = instance.func::<(), ()>(&store, "hello").expect("func_typed"); hello.call(&mut store, ()).expect("call"); Ok(()) } diff --git a/crates/tinywasm/benches/tinywasm_modes.rs b/crates/tinywasm/benches/tinywasm_modes.rs index 54f8033..f5f70d0 100644 --- a/crates/tinywasm/benches/tinywasm_modes.rs +++ b/crates/tinywasm/benches/tinywasm_modes.rs @@ -2,7 +2,7 @@ use criterion::{BatchSize, Criterion, criterion_group, criterion_main}; use eyre::Result; use tinywasm::engine::{Config, FuelPolicy}; use tinywasm::types::TinyWasmModule; -use tinywasm::{Engine, ExecProgress, Extern, FuncContext, FuncHandleTyped, Imports, ModuleInstance, Store}; +use tinywasm::{Engine, ExecProgress, FuncContext, FunctionTyped, HostFunction, Imports, ModuleInstance, Store}; const WASM: &[u8] = include_bytes!("../../../examples/rust/out/tinywasm.wasm"); const FUEL_PER_ROUND: u32 = 512; @@ -14,26 +14,26 @@ fn tinywasm_parse() -> Result<TinyWasmModule> { Ok(parser.parse_module_bytes(WASM)?) } -fn setup_typed_func(module: TinyWasmModule, engine: Option<Engine>) -> Result<(Store, FuncHandleTyped<(), ()>)> { +fn setup_typed_func(module: TinyWasmModule, engine: Option<Engine>) -> Result<(Store, FunctionTyped<(), ()>)> { let mut store = match engine { Some(engine) => Store::new(engine), None => Store::default(), }; let mut imports = Imports::default(); - imports.define("env", "printi32", Extern::typed_func(|_: FuncContext<'_>, _: i32| Ok(())))?; + imports.define("env", "printi32", HostFunction::from(&mut store, |_: FuncContext<'_>, _: i32| Ok(()))); let instance = ModuleInstance::instantiate(&mut store, module.into(), Some(imports))?; - let func = instance.func_typed::<(), ()>(&store, "hello")?; + let func = instance.func::<(), ()>(&store, "hello")?; Ok((store, func)) } -fn run_call(store: &mut Store, func: &FuncHandleTyped<(), ()>) -> Result<()> { +fn run_call(store: &mut Store, func: &FunctionTyped<(), ()>) -> Result<()> { func.call(store, ())?; Ok(()) } -fn run_resume_with_fuel(store: &mut Store, func: &FuncHandleTyped<(), ()>) -> Result<()> { +fn run_resume_with_fuel(store: &mut Store, func: &FunctionTyped<(), ()>) -> Result<()> { let mut execution = func.call_resumable(store, ())?; loop { match execution.resume_with_fuel(FUEL_PER_ROUND)? { @@ -43,7 +43,7 @@ fn run_resume_with_fuel(store: &mut Store, func: &FuncHandleTyped<(), ()>) -> Re } } -fn run_resume_with_time_budget(store: &mut Store, func: &FuncHandleTyped<(), ()>) -> Result<()> { +fn run_resume_with_time_budget(store: &mut Store, func: &FunctionTyped<(), ()>) -> Result<()> { let mut execution = func.call_resumable(store, ())?; loop { match execution.resume_with_time_budget(TIME_BUDGET_PER_ROUND)? { diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs index 51ac9b0..18bcff9 100644 --- a/crates/tinywasm/src/func.rs +++ b/crates/tinywasm/src/func.rs @@ -1,71 +1,25 @@ use crate::interpreter::stack::CallFrame; -use crate::{Error, FuncContext, InterpreterRuntime, Result, Store}; -use crate::{Function, unlikely}; +use crate::reference::StoreItem; +use crate::{Error, FunctionDef, InterpreterRuntime, Result, Store, unlikely}; +use alloc::rc::Rc; use alloc::{boxed::Box, format, string::ToString, vec, vec::Vec}; use tinywasm_types::{ExternRef, FuncRef, FuncType, ModuleInstanceAddr, ValType, WasmValue}; -#[derive(Debug, Clone, PartialEq, Eq)] -/// Progress for fuel-limited function execution. -pub enum ExecProgress<T> { - /// Execution completed and produced a result. - Completed(T), - /// Execution suspended after exhausting fuel or time budget. - Suspended, -} - -#[derive(Clone)] -#[cfg_attr(feature = "debug", derive(Debug))] -pub(crate) struct ExecutionState { - pub(crate) callframe: CallFrame, -} - -/// 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, -} - -/// Resumable execution for an untyped function call. -#[cfg_attr(feature = "debug", derive(Debug))] -pub struct FuncExecution<'store> { - store: &'store mut Store, - state: FuncExecutionState, -} - -#[cfg_attr(feature = "debug", derive(Debug))] -enum FuncExecutionState { - Running { exec_state: ExecutionState, root_func_addr: u32 }, - Completed { result: Option<Vec<WasmValue>> }, -} - -/// Resumable execution for a typed function call. -#[cfg_attr(feature = "debug", derive(Debug))] -pub struct FuncExecutionTyped<'store, R> { - execution: FuncExecution<'store>, - marker: core::marker::PhantomData<R>, -} - -impl FuncHandle { +impl Function { /// Call a function (Invocation) /// /// 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); - } - + self.item.validate_store(store)?; validate_call_params(&self.ty, params)?; let func_inst = store.state.get_func(self.addr); let wasm_func = match &func_inst.func { - Function::Host(host_func) => { + FunctionDef::Host(host_func) => { return host_func.clone().call(FuncContext { store, module_addr: self.module_addr }, params); } - Function::Wasm(wasm_func) => wasm_func.clone(), + FunctionDef::Wasm(wasm_func) => wasm_func.clone(), }; // Reset stack, push args, allocate locals, create entry frame. @@ -91,10 +45,7 @@ impl FuncHandle { store: &'store mut Store, params: &[WasmValue], ) -> Result<FuncExecution<'store>> { - if self.store_id != store.id() { - return Err(Error::InvalidStore); - } - + self.item.validate_store(store)?; validate_call_params(&self.ty, params)?; let func_inst = store.state.get_func(self.addr); @@ -102,11 +53,11 @@ impl FuncHandle { let func = func_inst.func.clone(); match func { - Function::Host(host_func) => { + FunctionDef::Host(host_func) => { let result = host_func.call(FuncContext { store, module_addr: self.module_addr }, params)?; Ok(FuncExecution { store, state: FuncExecutionState::Completed { result: Some(result) } }) } - Function::Wasm(wasm_func) => { + FunctionDef::Wasm(wasm_func) => { store.stack.clear(); store.stack.values.extend_from_wasmvalues(params)?; let locals_base = store.stack.values.enter_locals(&wasm_func.params, &wasm_func.locals)?; @@ -125,6 +76,225 @@ impl FuncHandle { } } +#[derive(Clone, PartialEq, Eq)] +/// Progress for fuel-limited function execution. +pub enum ExecProgress<T> { + /// Execution completed and produced a result. + Completed(T), + /// Execution suspended after exhausting fuel or time budget. + Suspended, +} + +#[derive(Clone)] +#[cfg_attr(feature = "debug", derive(core::fmt::Debug))] +pub(crate) struct ExecutionState { + pub(crate) callframe: CallFrame, +} + +/// A function handle +#[derive(Clone)] +#[cfg_attr(feature = "debug", derive(core::fmt::Debug))] +pub struct Function { + pub(crate) item: StoreItem, + pub(crate) module_addr: ModuleInstanceAddr, + pub(crate) addr: u32, + pub(crate) ty: FuncType, +} + +/// A typed function handle +#[cfg_attr(feature = "debug", derive(core::fmt::Debug))] +pub struct FunctionTyped<P, R> { + /// The underlying function handle + pub func: Function, + pub(crate) marker: core::marker::PhantomData<(P, R)>, +} + +/// A host function +pub struct HostFunction { + pub(crate) ty: tinywasm_types::FuncType, + pub(crate) func: HostFuncInner, +} + +impl HostFunction { + /// Get the function's type + pub fn ty(&self) -> &tinywasm_types::FuncType { + &self.ty + } + + /// Call the function + pub fn call(&self, ctx: FuncContext<'_>, args: &[WasmValue]) -> Result<Vec<WasmValue>> { + (self.func)(ctx, args) + } + + /// Create a new untyped host function import. + pub fn from_untyped( + store: &mut Store, + ty: &tinywasm_types::FuncType, + func: impl Fn(FuncContext<'_>, &[WasmValue]) -> Result<Vec<WasmValue>> + 'static, + ) -> Function { + let ty_inner = ty.clone(); + let inner_func = move |ctx: FuncContext<'_>, args: &[WasmValue]| -> Result<Vec<WasmValue>> { + let ty = ty_inner.clone(); + let result = func(ctx, args)?; + + if result.len() != ty.results.len() { + return Err(crate::Error::InvalidHostFnReturn { expected: ty.clone(), actual: result }); + }; + + result.iter().zip(ty.results.iter()).try_for_each(|(val, res_ty)| { + if val.val_type() != *res_ty { + return Err(crate::Error::InvalidHostFnReturn { expected: ty.clone(), actual: result.clone() }); + } + Ok(()) + })?; + + Ok(result) + }; + + let addr = store.add_func(FunctionDef::Host(Rc::new(Self { func: Box::new(inner_func), ty: ty.clone() })), 0); + Function { item: crate::StoreItem::new(store.id(), addr), module_addr: 0, addr, ty: ty.clone() } + } + + /// Create a new typed host function import. + pub fn from<P, R>(store: &mut Store, func: impl Fn(FuncContext<'_>, P) -> Result<R> + 'static) -> Function + where + P: FromWasmValueTuple + ValTypesFromTuple, + R: IntoWasmValueTuple + ValTypesFromTuple, + { + let inner_func = move |ctx: FuncContext<'_>, args: &[WasmValue]| -> Result<Vec<WasmValue>> { + let args = P::from_wasm_value_tuple(args)?; + let result = func(ctx, args)?; + Ok(result.into_wasm_value_tuple()) + }; + + let results = R::val_types(); + let ty = tinywasm_types::FuncType { params: P::val_types(), results }; + let addr = store.add_func(FunctionDef::Host(Rc::new(Self { func: Box::new(inner_func), ty: ty.clone() })), 0); + Function { item: crate::StoreItem::new(store.id(), addr), module_addr: 0, addr, ty } + } +} + +pub(crate) type HostFuncInner = Box<dyn Fn(FuncContext<'_>, &[WasmValue]) -> Result<Vec<WasmValue>>>; + +/// The context of a host-function call +#[cfg_attr(feature = "debug", derive(core::fmt::Debug))] +pub struct FuncContext<'a> { + pub(crate) store: &'a mut crate::Store, + pub(crate) module_addr: ModuleInstanceAddr, +} + +impl FuncContext<'_> { + /// Get the store. + pub fn store(&self) -> &crate::Store { + self.store + } + + /// Get mutable access to the store. + pub fn store_mut(&mut self) -> &mut crate::Store { + self.store + } + + /// Get the module instance. + pub fn module(&self) -> crate::ModuleInstance { + self.store.get_module_instance(self.module_addr).unwrap_or_else(|| { + unreachable!("invalid module instance address in host function context: {}", self.module_addr) + }) + } + + /// Get a memory export. + pub fn memory(&self, name: &str) -> Result<crate::Memory> { + self.module().memory(name) + } + + /// Get any exported extern value by name. + pub fn extern_item(&self, name: &str) -> Result<crate::ExternItem> { + self.module().extern_item(name) + } + + /// Get a table export. + pub fn table(&self, name: &str) -> Result<crate::Table> { + self.module().table(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 global export. + pub fn global(&self, name: &str) -> Result<crate::Global> { + self.module().global(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. + /// + /// This is a no-op when the current invocation is not using fuel-based + /// resumption. + pub fn charge_fuel(&mut self, fuel: u32) { + self.store.execution_fuel = self.store.execution_fuel.saturating_sub(fuel); + } + + /// Get remaining fuel for the current invocation. + /// + /// Returns `0` when fuel-based resumption is not active. + pub fn remaining_fuel(&self) -> u32 { + self.store.execution_fuel + } +} + +impl core::ops::Deref for FuncContext<'_> { + type Target = crate::Store; + + fn deref(&self) -> &Self::Target { + self.store + } +} + +impl core::ops::DerefMut for FuncContext<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.store + } +} + +impl<'a> FuncContext<'a> { + /// Create a new host function context. + pub const fn new(store: &'a mut crate::Store, module_addr: ModuleInstanceAddr) -> Self { + Self { store, module_addr } + } +} + +#[cfg(feature = "debug")] +impl core::fmt::Debug for HostFunction { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("HostFunction").field("ty", &self.ty).field("func", &"...").finish() + } +} + +/// Resumable execution for an untyped function call. +#[cfg_attr(feature = "debug", derive(core::fmt::Debug))] +pub struct FuncExecution<'store> { + store: &'store mut Store, + state: FuncExecutionState, +} + +#[cfg_attr(feature = "debug", derive(core::fmt::Debug))] +enum FuncExecutionState { + Running { exec_state: ExecutionState, root_func_addr: u32 }, + Completed { result: Option<Vec<WasmValue>> }, +} + +/// Resumable execution for a typed function call. +#[cfg_attr(feature = "debug", derive(core::fmt::Debug))] +pub struct FuncExecutionTyped<'store, R> { + execution: FuncExecution<'store>, + marker: core::marker::PhantomData<R>, +} + impl<'store> FuncExecution<'store> { /// Resume execution with up to `fuel` units of fuel. /// @@ -214,21 +384,12 @@ fn validate_call_params(func_ty: &FuncType, params: &[WasmValue]) -> Result<()> } fn collect_call_results(store: &mut Store, func_ty: &FuncType) -> Result<Vec<WasmValue>> { - // m values are on the top of the stack (Ensured by validation) - debug_assert!(store.stack.values.len() >= func_ty.results.len()); + debug_assert!(store.stack.values.len() >= func_ty.results.len()); // m values are on the top of the stack (Ensured by validation) let mut res: Vec<_> = store.stack.values.pop_types(func_ty.results.iter().rev()).collect(); // pop in reverse order since the stack is LIFO res.reverse(); // reverse to get the original order Ok(res) } -/// A typed function handle -#[cfg_attr(feature = "debug", derive(Debug))] -pub struct FuncHandleTyped<P, R> { - /// The underlying function handle - pub func: FuncHandle, - pub(crate) marker: core::marker::PhantomData<(P, R)>, -} - pub trait IntoWasmValueTuple { fn into_wasm_value_tuple(self) -> Vec<WasmValue>; } @@ -239,7 +400,7 @@ pub trait FromWasmValueTuple { Self: Sized; } -impl<P: IntoWasmValueTuple, R: FromWasmValueTuple> FuncHandleTyped<P, R> { +impl<P: IntoWasmValueTuple, R: FromWasmValueTuple> FunctionTyped<P, R> { /// Call a typed function pub fn call(&self, store: &mut Store, params: P) -> Result<R> { // Convert params into Vec<WasmValue> diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs index 18b8f0a..b8dee06 100644 --- a/crates/tinywasm/src/imports.rs +++ b/crates/tinywasm/src/imports.rs @@ -1,252 +1,47 @@ -use alloc::boxed::Box; use alloc::collections::BTreeMap; -use alloc::rc::Rc; use alloc::string::{String, ToString}; use alloc::vec::Vec; use core::fmt::Debug; -use crate::func::{FromWasmValueTuple, IntoWasmValueTuple, ValTypesFromTuple}; -use crate::instance::{ExternItemRef, ExternItemRefMut}; -use crate::{GlobalRef, GlobalRefMut, LinkingError, MemoryRef, MemoryRefMut, Result, TableRef, TableRefMut, log}; +use crate::{Function, Global, LinkingError, Memory, Result, Table, log}; use tinywasm_types::*; -/// The internal representation of a function -#[derive(Clone)] -#[cfg_attr(feature = "debug", derive(Debug))] -pub enum Function { - /// A host function - Host(Rc<HostFunction>), - - /// A pointer to a WebAssembly function - Wasm(Rc<WasmFunction>), -} - -impl Function { - pub(crate) fn ty(&self) -> &FuncType { - match self { - Self::Host(f) => &f.ty, - Self::Wasm(f) => &f.ty, - } - } -} - -/// A host function -pub struct HostFunction { - pub(crate) ty: tinywasm_types::FuncType, - pub(crate) func: HostFuncInner, -} - -impl HostFunction { - /// Get the function's type - pub fn ty(&self) -> &tinywasm_types::FuncType { - &self.ty - } - - /// Call the function - pub fn call(&self, ctx: FuncContext<'_>, args: &[WasmValue]) -> Result<Vec<WasmValue>> { - (self.func)(ctx, args) - } -} - -pub(crate) type HostFuncInner = Box<dyn Fn(FuncContext<'_>, &[WasmValue]) -> Result<Vec<WasmValue>>>; - -/// The context of a host-function call -#[cfg_attr(feature = "debug", derive(Debug))] -pub struct FuncContext<'a> { - pub(crate) store: &'a mut crate::Store, - pub(crate) module_addr: ModuleInstanceAddr, -} - -impl FuncContext<'_> { - /// Get a reference to the store - pub fn store(&self) -> &crate::Store { - self.store - } - - /// Get a mutable reference to the store - pub fn store_mut(&mut self) -> &mut crate::Store { - self.store - } - - /// Get a reference to the module instance - pub fn module(&self) -> crate::ModuleInstance { - self.store.get_module_instance(self.module_addr).unwrap_or_else(|| { - unreachable!("invalid module instance address in host function context: {}", self.module_addr) - }) - } - - /// 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 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. - /// - /// This is a no-op when the current invocation is not using fuel-based - /// resumption. - pub fn charge_fuel(&mut self, fuel: u32) { - self.store.execution_fuel = self.store.execution_fuel.saturating_sub(fuel); - } - - /// Get remaining fuel for the current invocation. - /// - /// Returns `0` when fuel-based resumption is not active. - pub fn remaining_fuel(&self) -> u32 { - self.store.execution_fuel - } -} - -#[cfg(feature = "debug")] -impl Debug for HostFunction { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("HostFunction").field("ty", &self.ty).field("func", &"...").finish() - } -} - #[derive(Clone)] #[cfg_attr(feature = "debug", derive(Debug))] #[non_exhaustive] -/// An external value +/// An external import value. pub enum Extern { - /// A global value - Global { - /// The type of the global value. - ty: GlobalType, - /// The actual value of the global, encapsulated in `WasmValue`. - val: WasmValue, - }, - - /// A table - Table { - /// Defines the type of the table, including its element type and limits. - ty: TableType, - /// The initial value of the table. - init: WasmValue, - }, - - /// A memory - Memory { - /// Defines the type of the memory, including its limits and the type of its pages. - ty: MemoryType, - }, - - /// A function + /// A global instance. + Global(Global), + /// A table instance. + Table(Table), + /// A memory instance. + Memory(Memory), + /// A function import. Function(Function), } -impl Extern { - /// Create a new global import - pub const fn global(val: WasmValue, mutable: bool) -> Self { - Self::Global { ty: GlobalType { ty: val.val_type(), mutable }, val } - } - - /// Create a new table import - pub const fn table(ty: TableType, init: WasmValue) -> Self { - Self::Table { ty, init } +impl From<Global> for Extern { + fn from(value: Global) -> Self { + Self::Global(value) } +} - /// Create a new memory import - pub const fn memory(ty: MemoryType) -> Self { - Self::Memory { ty } - } - - /// Create a new function import - pub fn func( - ty: &tinywasm_types::FuncType, - func: impl Fn(FuncContext<'_>, &[WasmValue]) -> Result<Vec<WasmValue>> + 'static, - ) -> Self { - let ty_inner = ty.clone(); - let inner_func = move |ctx: FuncContext<'_>, args: &[WasmValue]| -> Result<Vec<WasmValue>> { - let ty = ty_inner.clone(); - let result = func(ctx, args)?; - - if result.len() != ty.results.len() { - return Err(crate::Error::InvalidHostFnReturn { expected: ty.clone(), actual: result }); - }; - - result.iter().zip(ty.results.iter()).try_for_each(|(val, res_ty)| { - if val.val_type() != *res_ty { - return Err(crate::Error::InvalidHostFnReturn { expected: ty.clone(), actual: result.clone() }); - } - Ok(()) - })?; - - Ok(result) - }; - - Self::Function(Function::Host(Rc::new(HostFunction { func: Box::new(inner_func), ty: ty.clone() }))) +impl From<Table> for Extern { + fn from(value: Table) -> Self { + Self::Table(value) } +} - /// Create a new typed function import - pub fn typed_func<P, R>(func: impl Fn(FuncContext<'_>, P) -> Result<R> + 'static) -> Self - where - P: FromWasmValueTuple + ValTypesFromTuple, - R: IntoWasmValueTuple + ValTypesFromTuple + Debug, - { - let inner_func = move |ctx: FuncContext<'_>, args: &[WasmValue]| -> Result<Vec<WasmValue>> { - let args = P::from_wasm_value_tuple(args)?; - let result = func(ctx, args)?; - Ok(result.into_wasm_value_tuple()) - }; - - let results = R::val_types(); - let ty = tinywasm_types::FuncType { params: P::val_types(), results }; - Self::Function(Function::Host(Rc::new(HostFunction { func: Box::new(inner_func), ty }))) +impl From<Memory> for Extern { + fn from(value: Memory) -> Self { + Self::Memory(value) } +} - /// Get the kind of the external value - pub const fn kind(&self) -> ExternalKind { - match self { - Self::Global { .. } => ExternalKind::Global, - Self::Table { .. } => ExternalKind::Table, - Self::Memory { .. } => ExternalKind::Memory, - Self::Function { .. } => ExternalKind::Func, - } +impl From<Function> for Extern { + fn from(value: Function) -> Self { + Self::Function(value) } } @@ -271,8 +66,8 @@ impl From<&Import> for ExternName { /// ```rust /// # use log; /// # fn main() -> tinywasm::Result<()> { -/// use tinywasm::{Extern, Imports, Module, Store}; -/// use tinywasm::types::{ValType, TableType, MemoryType, MemoryArch, WasmValue}; +/// use tinywasm::{Global, HostFunction, Imports, Memory, Module, Store, Table}; +/// use tinywasm::types::{ValType, TableType, MemoryType, WasmValue}; /// # let wasm = wat::parse_str("(module)").expect("valid wat"); /// # let module = Module::parse_bytes(&wasm)?; /// # let mut store = Store::default(); @@ -281,19 +76,20 @@ impl From<&Import> for ExternName { /// /// // function args can be either a single /// // value that implements `TryFrom<WasmValue>` or a tuple of them -/// let print_i32 = Extern::typed_func(|_ctx: tinywasm::FuncContext<'_>, arg: i32| { +/// let print_i32 = HostFunction::from(&mut store, |_ctx: tinywasm::FuncContext<'_>, arg: i32| { /// log::debug!("print_i32: {}", arg); /// Ok(()) /// }); /// -/// let table_type = TableType::new(ValType::RefFunc, 10, Some(20)); -/// let table_init = WasmValue::default_for(ValType::RefFunc); +/// let table = Table::new(&mut store, TableType::new(ValType::RefFunc, 10, Some(20)), WasmValue::default_for(ValType::RefFunc))?; +/// let memory = Memory::new(&mut store, MemoryType::default().with_page_count_initial(1).with_page_count_max(Some(2)))?; +/// let global_i32 = Global::new(&mut store, tinywasm::types::GlobalType::default().with_ty(ValType::I32), WasmValue::I32(666))?; /// /// imports -/// .define("my_module", "print_i32", print_i32)? -/// .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))? +/// .define("my_module", "print_i32", print_i32) +/// .define("my_module", "table", table) +/// .define("my_module", "memory", memory) +/// .define("my_module", "global_i32", global_i32) /// .link_module("my_other_module", my_other_instance)?; /// # Ok(()) /// # } @@ -302,15 +98,13 @@ impl From<&Import> for ExternName { #[derive(Default, Clone)] #[cfg_attr(feature = "debug", derive(Debug))] pub struct Imports { - values: BTreeMap<ExternName, Extern>, + globals: BTreeMap<ExternName, Global>, + tables: BTreeMap<ExternName, Table>, + memories: BTreeMap<ExternName, Memory>, + function_handles: BTreeMap<ExternName, Function>, modules: BTreeMap<String, crate::ModuleInstance>, } -pub(crate) enum ResolvedExtern<S, V> { - Store(S), // already in the store - Extern(V), // needs to be added to the store, provided value -} - pub(crate) struct ResolvedImports { pub(crate) globals: Vec<GlobalAddr>, pub(crate) tables: Vec<TableAddr>, @@ -327,12 +121,21 @@ impl ResolvedImports { impl Imports { /// Create a new empty import set pub const fn new() -> Self { - Self { values: BTreeMap::new(), modules: BTreeMap::new() } + Self { + globals: BTreeMap::new(), + tables: BTreeMap::new(), + memories: BTreeMap::new(), + function_handles: BTreeMap::new(), + modules: BTreeMap::new(), + } } /// Merge two import sets pub fn merge(mut self, other: Self) -> Self { - self.values.extend(other.values); + self.globals.extend(other.globals); + self.tables.extend(other.tables); + self.memories.extend(other.memories); + self.function_handles.extend(other.function_handles); self.modules.extend(other.modules); self } @@ -345,30 +148,41 @@ impl Imports { Ok(self) } - /// Define an import - pub fn define(&mut self, module: &str, name: &str, value: Extern) -> Result<&mut Self> { - self.values.insert(ExternName { module: module.to_string(), name: name.to_string() }, value); - Ok(self) + /// Define an import value. + pub fn define(&mut self, module: &str, name: &str, value: impl Into<Extern>) -> &mut Self { + let name = ExternName { module: module.to_string(), name: name.to_string() }; + match value.into() { + Extern::Global(v) => { + self.globals.insert(name, v); + } + Extern::Table(v) => { + self.tables.insert(name, v); + } + Extern::Memory(v) => { + self.memories.insert(name, v); + } + Extern::Function(v) => { + self.function_handles.insert(name, v); + } + } + self } - pub(crate) fn take( - &mut self, - store: &mut crate::Store, - import: &Import, - ) -> Result<Option<ResolvedExtern<ExternVal, Extern>>> { + pub(crate) fn take_defined(&self, import: &Import) -> Option<Extern> { let name = ExternName::from(import); - if let Some(v) = self.values.get(&name) { - return Ok(Some(ResolvedExtern::Extern(v.clone()))); + if let Some(v) = self.globals.get(&name) { + return Some(Extern::Global(*v)); } - 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)); + if let Some(v) = self.tables.get(&name) { + return Some(Extern::Table(*v)); } - - Ok(None) + if let Some(v) = self.memories.get(&name) { + return Some(Extern::Memory(*v)); + } + if let Some(v) = self.function_handles.get(&name) { + return Some(Extern::Function(v.clone())); + } + None } #[cfg(not(feature = "debug"))] @@ -408,12 +222,11 @@ impl Imports { import: &Import, expected: &MemoryType, actual: &MemoryType, - real_size: Option<usize>, + real_size: usize, ) -> Result<()> { Self::compare_types(import, &expected.arch(), &actual.arch())?; - if actual.page_count_initial() > expected.page_count_initial() - && real_size.is_none_or(|size| actual.page_count_initial() > size as u64) + if actual.page_count_initial() > expected.page_count_initial() && actual.page_count_initial() > real_size as u64 { return Err(LinkingError::incompatible_import_type(import).into()); } @@ -430,82 +243,106 @@ impl Imports { } pub(crate) fn link( - mut self, + self, store: &mut crate::Store, module: &crate::Module, - idx: ModuleInstanceAddr, + _idx: ModuleInstanceAddr, ) -> Result<ResolvedImports> { let mut imports = ResolvedImports::new(); for import in &*module.0.imports { - 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)); + if let Some(defined) = self.take_defined(import) { + match defined { + Extern::Global(global) => { + let ImportKind::Global(import_ty) = &import.kind else { + return Err(LinkingError::incompatible_import_type(import).into()); + }; + let global_instance = store.state.get_global(global.0.addr); + Self::compare_types(import, &global_instance.ty, import_ty)?; + imports.globals.push(global.0.addr); } - (Extern::Table { ty, init }, ImportKind::Table(import_ty)) => { - Self::compare_table_types(import, &ty, import_ty)?; - Self::compare_types(import, &ty.element_type, &init.val_type())?; - imports.tables.push(store.add_table(ty, init, idx)?); + Extern::Table(table) => { + let ImportKind::Table(import_ty) = &import.kind else { + return Err(LinkingError::incompatible_import_type(import).into()); + }; + let table_instance = store.state.get_table(table.0.addr); + let mut kind = table_instance.kind.clone(); + kind.size_initial = table_instance.size() as u32; + Self::compare_table_types(import, &kind, import_ty)?; + imports.tables.push(table.0.addr); } - (Extern::Memory { ty }, ImportKind::Memory(import_ty)) => { - Self::compare_memory_types(import, &ty, import_ty, None)?; - imports.memories.push(store.add_mem(ty, idx)?); + Extern::Memory(memory) => { + let ImportKind::Memory(import_ty) = &import.kind else { + return Err(LinkingError::incompatible_import_type(import).into()); + }; + let mem = store.state.get_mem(memory.0.addr); + let (size, kind) = { (mem.page_count, mem.kind) }; + Self::compare_memory_types(import, &kind, import_ty, size)?; + imports.memories.push(memory.0.addr); } - (Extern::Function(extern_func), ImportKind::Function(ty)) => { + Extern::Function(func_handle) => { + let ImportKind::Function(ty) = &import.kind else { + return Err(LinkingError::incompatible_import_type(import).into()); + }; let import_func_type = module .0 .func_types .get(*ty as usize) .ok_or_else(|| LinkingError::incompatible_import_type(import))?; - - Self::compare_types(import, extern_func.ty(), import_func_type)?; - imports.funcs.push(store.add_func(extern_func, idx)); + func_handle.item.validate_store(store)?; + Self::compare_types(import, &func_handle.ty, import_func_type)?; + imports.funcs.push(func_handle.addr); } - _ => return Err(LinkingError::incompatible_import_type(import).into()), - }, + } + continue; + } - // A link to something already in the store - ResolvedExtern::Store(val) => { - // check if the kind matches - if val.kind() != (&import.kind).into() { - return Err(LinkingError::incompatible_import_type(import).into()); - } + let name = ExternName::from(import); + let Some(instance) = self.modules.get(&name.module) else { + return Err(LinkingError::unknown_import(import).into()); + }; + if instance.0.store_id != store.id() { + return Err(crate::Error::InvalidStore); + } + let val = instance.export_addr(&import.name).ok_or_else(|| LinkingError::unknown_import(import))?; - match (val, &import.kind) { - (ExternVal::Global(global_addr), ImportKind::Global(ty)) => { - let global = store.state.get_global(global_addr); - Self::compare_types(import, &global.ty, ty)?; - imports.globals.push(global_addr); - } - (ExternVal::Table(table_addr), ImportKind::Table(ty)) => { - let table = store.state.get_table(table_addr); - let mut kind = table.kind.clone(); - kind.size_initial = table.size() as u32; - Self::compare_table_types(import, &kind, ty)?; - imports.tables.push(table_addr); - } - (ExternVal::Memory(memory_addr), ImportKind::Memory(ty)) => { - let mem = store.state.get_mem(memory_addr); - let (size, kind) = { (mem.page_count, mem.kind) }; - Self::compare_memory_types(import, &kind, ty, Some(size))?; - imports.memories.push(memory_addr); - } - (ExternVal::Func(func_addr), ImportKind::Function(ty)) => { - let func = store.state.get_func(func_addr); - let import_func_type = module - .0 - .func_types - .get(*ty as usize) - .ok_or_else(|| LinkingError::incompatible_import_type(import))?; + { + // check if the kind matches + if val.kind() != (&import.kind).into() { + return Err(LinkingError::incompatible_import_type(import).into()); + } + + match (val, &import.kind) { + (ExternVal::Global(global_addr), ImportKind::Global(ty)) => { + let global = store.state.get_global(global_addr); + Self::compare_types(import, &global.ty, ty)?; + imports.globals.push(global_addr); + } + (ExternVal::Table(table_addr), ImportKind::Table(ty)) => { + let table = store.state.get_table(table_addr); + let mut kind = table.kind.clone(); + kind.size_initial = table.size() as u32; + Self::compare_table_types(import, &kind, ty)?; + imports.tables.push(table_addr); + } + (ExternVal::Memory(memory_addr), ImportKind::Memory(ty)) => { + let mem = store.state.get_mem(memory_addr); + let (size, kind) = { (mem.page_count, mem.kind) }; + Self::compare_memory_types(import, &kind, ty, size)?; + imports.memories.push(memory_addr); + } + (ExternVal::Func(func_addr), ImportKind::Function(ty)) => { + let func = store.state.get_func(func_addr); + let import_func_type = module + .0 + .func_types + .get(*ty as usize) + .ok_or_else(|| LinkingError::incompatible_import_type(import))?; - Self::compare_types(import, func.func.ty(), import_func_type)?; - imports.funcs.push(func_addr); - } - _ => return Err(LinkingError::incompatible_import_type(import).into()), + Self::compare_types(import, func.func.ty(), import_func_type)?; + imports.funcs.push(func_addr); } + _ => return Err(LinkingError::incompatible_import_type(import).into()), } } } diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs index bb9ca28..12148db 100644 --- a/crates/tinywasm/src/instance.rs +++ b/crates/tinywasm/src/instance.rs @@ -3,33 +3,18 @@ use alloc::{format, rc::Rc}; use tinywasm_types::*; use crate::func::{FromWasmValueTuple, IntoWasmValueTuple, ValTypesFromTuple}; -use crate::{ - Error, FuncHandle, FuncHandleTyped, GlobalRef, GlobalRefMut, Imports, MemoryRef, MemoryRefMut, Module, Result, - Store, TableRef, TableRefMut, -}; +use crate::{Error, Function, FunctionTyped, Global, Imports, Memory, Module, Result, Store, Table}; -/// A typed borrowed view over an exported extern value. -pub enum ExternItemRef<'a> { +/// A typed view over an exported extern value. +pub enum ExternItem { /// Exported function handle. - Func(FuncHandle), + Func(Function), /// Exported memory reference. - Memory(MemoryRef<'a>), + Memory(Memory), /// Exported table reference. - Table(TableRef<'a>), + Table(Table), /// 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>), + Global(Global), } /// An instantiated WebAssembly module @@ -191,10 +176,8 @@ impl ModuleInstance { } /// 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| { + pub fn exports(&self) -> impl Iterator<Item = (&str, ExternItem)> + '_ { + self.0.exports.iter().map(move |export| { let name = export.name.as_ref(); let item = match export.kind { ExternalKind::Func => { @@ -204,12 +187,12 @@ impl ModuleInstance { .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, + let ty = self.0.func_ty(export.index).clone(); + ExternItem::Func(Function { + item: crate::StoreItem::new(self.0.store_id, func_addr), module_addr: self.id(), addr: func_addr, - ty: ty.clone(), + ty, }) } ExternalKind::Table => { @@ -219,7 +202,7 @@ impl ModuleInstance { .table_addrs .get(idx) .unwrap_or_else(|| unreachable!("invalid table export index: {}", export.index)); - ExternItemRef::Table(TableRef(store.state.get_table(table_addr))) + ExternItem::Table(Table::from_store_addr(self.0.store_id, table_addr)) } ExternalKind::Memory => { let idx = export.index as usize; @@ -228,7 +211,7 @@ impl ModuleInstance { .mem_addrs .get(idx) .unwrap_or_else(|| unreachable!("invalid memory export index: {}", export.index)); - ExternItemRef::Memory(MemoryRef(store.state.get_mem(mem_addr))) + ExternItem::Memory(Memory::from_store_addr(self.0.store_id, mem_addr)) } ExternalKind::Global => { let idx = export.index as usize; @@ -237,12 +220,12 @@ impl ModuleInstance { .global_addrs .get(idx) .unwrap_or_else(|| unreachable!("invalid global export index: {}", export.index)); - ExternItemRef::Global(GlobalRef(store.state.get_global(global_addr))) + ExternItem::Global(Global::from_store_addr(self.0.store_id, global_addr)) } }; (name, item) - })) + }) } #[inline] @@ -257,35 +240,32 @@ impl ModuleInstance { } /// 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)))), - } - } - - /// 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)?; + pub fn extern_item(&self, name: &str) -> Result<ExternItem> { 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::Func(func_addr) => { + let export = self + .0 + .exports + .iter() + .find(|e| e.name == name.into()) + .ok_or_else(|| Error::Other(format!("Export not found: {name}")))?; + Ok(ExternItem::Func(Function { + item: crate::StoreItem::new(self.0.store_id, func_addr), + module_addr: self.id(), + addr: func_addr, + ty: self.0.func_ty(export.index).clone(), + })) } + ExternVal::Memory(mem_addr) => Ok(ExternItem::Memory(Memory::from_store_addr(self.0.store_id, mem_addr))), + ExternVal::Table(table_addr) => Ok(ExternItem::Table(Table::from_store_addr(self.0.store_id, table_addr))), ExternVal::Global(global_addr) => { - Ok(ExternItemRefMut::Global(GlobalRefMut(store.state.get_global_mut(global_addr)))) + Ok(ExternItem::Global(Global::from_store_addr(self.0.store_id, global_addr))) } } } /// Get a function export by name. - pub fn func(&self, store: &Store, name: &str) -> Result<FuncHandle> { + pub fn func_untyped(&self, store: &Store, name: &str) -> Result<Function> { self.validate_store(store)?; let export = self.require_export(name)?; @@ -294,7 +274,12 @@ impl ModuleInstance { }; 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() }) + Ok(Function { + item: crate::StoreItem::new(self.0.store_id, func_addr), + addr: func_addr, + module_addr: self.id(), + ty: ty.clone(), + }) } /// Get a function by its module-local index. @@ -305,23 +290,28 @@ impl ModuleInstance { /// 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> { + pub fn func_by_index(&self, store: &Store, func_index: FuncAddr) -> Result<Function> { 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() }) + Ok(Function { + item: crate::StoreItem::new(self.0.store_id, func_addr), + addr: func_addr, + module_addr: self.id(), + ty: ty.clone(), + }) } /// Get a typed function export by name. - pub fn func_typed<P: IntoWasmValueTuple + ValTypesFromTuple, R: FromWasmValueTuple + ValTypesFromTuple>( + pub fn func<P: IntoWasmValueTuple + ValTypesFromTuple, R: FromWasmValueTuple + ValTypesFromTuple>( &self, store: &Store, name: &str, - ) -> Result<FuncHandleTyped<P, R>> { - let func = self.func(store, name)?; + ) -> Result<FunctionTyped<P, R>> { + let func = self.func_untyped(store, name)?; Self::validate_typed_func::<P, R>(&func, name)?; - Ok(FuncHandleTyped { func, marker: core::marker::PhantomData }) + Ok(FunctionTyped { func, marker: core::marker::PhantomData }) } /// Get a typed function by its module-local index. @@ -331,16 +321,13 @@ impl ModuleInstance { &self, store: &Store, func_index: FuncAddr, - ) -> Result<FuncHandleTyped<P, R>> { + ) -> Result<FunctionTyped<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 }) + Ok(FunctionTyped { func, marker: core::marker::PhantomData }) } - fn validate_typed_func<P: ValTypesFromTuple, R: ValTypesFromTuple>( - func: &FuncHandle, - func_name: &str, - ) -> Result<()> { + fn validate_typed_func<P: ValTypesFromTuple, R: ValTypesFromTuple>(func: &Function, func_name: &str) -> Result<()> { let expected = FuncType { params: P::val_types(), results: R::val_types() }; if func.ty != expected { #[cfg(feature = "debug")] @@ -356,25 +343,11 @@ impl ModuleInstance { } /// 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}"))); - }; - Ok(MemoryRef(store.state.get_mem(mem_addr))) - } - - /// 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 { + pub fn memory(&self, name: &str) -> Result<Memory> { + let ExternVal::Memory(mem_addr) = self.require_export(name)? else { return Err(Error::Other(format!("Export is not a memory: {name}"))); }; - Ok(MemoryRefMut(store.state.get_mem_mut(mem_addr))) + Ok(Memory::from_store_addr(self.0.store_id, mem_addr)) } /// Get a memory by its module-local index. @@ -385,46 +358,17 @@ impl ModuleInstance { /// 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))) + pub fn memory_by_index(&self, memory_index: MemAddr) -> Result<Memory> { + Ok(Memory::from_store_addr(self.0.store_id, Self::index_addr(&self.0.mem_addrs, memory_index, "memory")?)) } /// 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)?; - + pub fn table(&self, name: &str) -> Result<Table> { 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))) + Ok(Table::from_store_addr(self.0.store_id, table_addr)) } /// Get a table by its module-local index. @@ -435,76 +379,31 @@ impl ModuleInstance { /// 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))) + pub fn table_by_index(&self, table_index: TableAddr) -> Result<Table> { + Ok(Table::from_store_addr(self.0.store_id, Self::index_addr(&self.0.table_addrs, table_index, "table")?)) } /// 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))) + self.global(name)?.get(store) } - /// 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)?; - + /// Get a global export by name. + pub fn global(&self, name: &str) -> Result<Global> { 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))) + Ok(Global::from_store_addr(self.0.store_id, global_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 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))) + self.global(name)?.set(store, value) } - /// Get a mutable reference to a global by its module-local index. + /// Get 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 @@ -512,11 +411,8 @@ impl ModuleInstance { /// 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))) + pub fn global_by_index(&self, global_index: GlobalAddr) -> Result<Global> { + Ok(Global::from_store_addr(self.0.store_id, Self::index_addr(&self.0.global_addrs, global_index, "global")?)) } /// Get the start function of the module @@ -525,7 +421,7 @@ impl ModuleInstance { /// If no start function is specified, also checks for a `_start` function in the exports /// /// See <https://webassembly.github.io/spec/core/syntax/modules.html#start-function> - pub fn start_func(&self, store: &Store) -> Result<Option<FuncHandle>> { + pub fn start_func(&self, store: &Store) -> Result<Option<Function>> { self.validate_store(store)?; let func_index = match self.0.func_start { @@ -542,7 +438,12 @@ 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 { store_id: self.0.store_id, module_addr: self.id(), addr: func_addr, ty: ty.clone() })) + Ok(Some(Function { + item: crate::StoreItem::new(self.0.store_id, func_addr), + 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 3236c0f..d4dafd6 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -12,6 +12,7 @@ use super::ExecState; use super::num_helpers::*; use super::values::*; use crate::engine::FuelPolicy; +use crate::func::{FuncContext, HostFunction}; use crate::instance::ModuleInstanceInner; use crate::interpreter::Value128; use crate::*; @@ -299,7 +300,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { I64Popcnt => stack_op!(unary i64, |v| i64::from(v.count_ones())), // Reference types - RefFunc(func_idx) => self.exec_const::<ValueRef>(Some(*func_idx))?, + RefFunc(func_idx) => self.exec_const::<ValueRef>(Some(self.module.resolve_func_addr(*func_idx)))?, RefNull(_) => self.exec_const::<ValueRef>(None)?, RefIsNull => self.exec_ref_is_null()?, MemorySize(addr) => self.exec_memory_size(*addr)?, @@ -737,7 +738,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { Ok(()) } - fn exec_call_host(&mut self, host_func: Rc<imports::HostFunction>) -> Result<()> { + fn exec_call_host(&mut self, host_func: Rc<HostFunction>) -> Result<()> { let params = self.store.stack.values.pop_types(&host_func.ty.params).collect::<Box<_>>(); let res = host_func.call(FuncContext { store: self.store, module_addr: self.module.idx }, ¶ms)?; self.store.stack.values.extend_from_wasmvalues(&res)?; @@ -749,10 +750,10 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let addr = self.module.resolve_func_addr(v); let func_inst = self.store.state.get_func(addr); match &func_inst.func { - crate::Function::Wasm(wasm_func) => { + crate::FunctionDef::Wasm(wasm_func) => { self.exec_call::<IS_RETURN_CALL>(wasm_func.clone(), addr, func_inst.owner) } - crate::Function::Host(host_func) => self.exec_call_host(host_func.clone()), + crate::FunctionDef::Host(host_func) => self.exec_call_host(host_func.clone()), } } @@ -793,7 +794,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let call_ty = self.module.func_ty(type_addr); match &func_inst.func { - crate::Function::Wasm(wasm_func) => { + crate::FunctionDef::Wasm(wasm_func) => { if wasm_func.ty != *call_ty { return Err(Trap::IndirectCallTypeMismatch { actual: wasm_func.ty.clone(), @@ -804,7 +805,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { self.exec_call::<IS_RETURN_CALL>(wasm_func.clone(), func_ref, func_inst.owner) } - crate::Function::Host(host_func) => { + crate::FunctionDef::Host(host_func) => { if host_func.ty != *call_ty { return Err(Trap::IndirectCallTypeMismatch { actual: host_func.ty.clone(), diff --git a/crates/tinywasm/src/lib.rs b/crates/tinywasm/src/lib.rs index 6847680..37f0013 100644 --- a/crates/tinywasm/src/lib.rs +++ b/crates/tinywasm/src/lib.rs @@ -57,7 +57,7 @@ //! // Get a typed handle to the exported "add" function //! // Alternatively, you can use `instance.func` to get an untyped handle //! // that takes and returns [`WasmValue`]s -//! let func = instance.func_typed::<(i32, i32), i32>(&mut store, "add")?; +//! let func = instance.func::<(i32, i32), i32>(&mut store, "add")?; //! let res = func.call(&mut store, (1, 2))?; //! //! assert_eq!(res, 3); @@ -96,9 +96,9 @@ pub(crate) mod log { mod error; pub use error::*; -pub use func::{ExecProgress, FuncExecution, FuncExecutionTyped, FuncHandle, FuncHandleTyped}; +pub use func::{ExecProgress, FuncContext, FuncExecution, FuncExecutionTyped, Function, FunctionTyped, HostFunction}; pub use imports::*; -pub use instance::{ExternItemRef, ExternItemRefMut, ModuleInstance}; +pub use instance::{ExternItem, ModuleInstance}; pub use module::{ExportType, ImportType, Module, ModuleExport, ModuleImport}; pub use reference::*; pub use store::*; diff --git a/crates/tinywasm/src/reference.rs b/crates/tinywasm/src/reference.rs index 4deac5f..ad0ff84 100644 --- a/crates/tinywasm/src/reference.rs +++ b/crates/tinywasm/src/reference.rs @@ -4,280 +4,306 @@ use alloc::string::{String, ToString}; use alloc::{ffi::CString, format}; use crate::store::{GlobalInstance, TableElement, TableInstance}; -use crate::{Error, MemoryInstance, Result}; -use tinywasm_types::{ExternRef, FuncRef, GlobalType, TableAddr, TableType, ValType, WasmValue}; +use crate::{Error, MemoryInstance, Result, Store}; +use tinywasm_types::{ + Addr, ExternRef, FuncRef, GlobalAddr, GlobalType, MemAddr, MemoryArch, MemoryType, TableAddr, TableType, ValType, + WasmValue, +}; -// This module essentially contains the public APIs to interact with the data stored in the store - -/// A reference to a memory instance +#[derive(Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "debug", derive(Debug))] -pub struct MemoryRef<'a>(pub(crate) &'a MemoryInstance); +pub(crate) struct StoreItem { + pub(crate) store_id: usize, + pub(crate) addr: Addr, +} -/// A mutable reference to a memory instance. -#[cfg_attr(feature = "debug", derive(Debug))] -pub struct MemoryRefMut<'a>(pub(crate) &'a mut MemoryInstance); +impl StoreItem { + #[inline] + pub(crate) const fn new(store_id: usize, addr: Addr) -> Self { + Self { store_id, addr } + } -/// A reference to a table instance. -#[cfg_attr(feature = "debug", derive(Debug))] -pub struct TableRef<'a>(pub(crate) &'a TableInstance); + #[inline] + pub(crate) fn validate_store(&self, store: &Store) -> Result<()> { + if self.store_id != store.id() { + return Err(Error::InvalidStore); + } + Ok(()) + } +} -/// A mutable reference to a table instance. +/// A memory instance in a store. +#[derive(Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "debug", derive(Debug))] -pub struct TableRefMut<'a>(pub(crate) &'a mut TableInstance); +pub struct Memory(pub(crate) StoreItem); -/// A reference to a global instance. +/// A table instance in a store. +#[derive(Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "debug", derive(Debug))] -pub struct GlobalRef<'a>(pub(crate) &'a GlobalInstance); +pub struct Table(pub(crate) StoreItem); -/// A mutable reference to a global instance. +/// A global instance in a store. +#[derive(Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "debug", derive(Debug))] -pub struct GlobalRefMut<'a>(pub(crate) &'a mut GlobalInstance); +pub struct Global(pub(crate) StoreItem); -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"), +impl Memory { + #[inline] + pub(crate) const fn from_store_addr(store_id: usize, addr: MemAddr) -> Self { + Self(StoreItem::new(store_id, addr)) } -} -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())), + /// Create a new memory in the given store. + pub fn new(store: &mut Store, ty: MemoryType) -> Result<Self> { + if let MemoryArch::I64 = ty.arch() { + return Err(Error::UnsupportedFeature("64-bit memories".to_string())); + } + let addr = store.state.memories.len() as MemAddr; + store.state.memories.push(MemoryInstance::new(ty)); + Ok(Self::from_store_addr(store.id(), addr)) } -} -impl MemoryRefLoad for MemoryRef<'_> { - /// Load a slice of memory - fn load(&self, offset: usize, len: usize) -> Result<&[u8]> { - self.0.load(offset, len) + #[inline] + fn instance<'a>(&self, store: &'a Store) -> Result<&'a MemoryInstance> { + self.0.validate_store(store)?; + Ok(store.state.get_mem(self.0.addr)) } -} -impl MemoryRefLoad for MemoryRefMut<'_> { - /// Load a slice of memory - fn load(&self, offset: usize, len: usize) -> Result<&[u8]> { - self.0.load(offset, len) + #[inline] + fn instance_mut<'a>(&self, store: &'a mut Store) -> Result<&'a mut MemoryInstance> { + self.0.validate_store(store)?; + Ok(store.state.get_mem_mut(self.0.addr)) } -} -impl MemoryRef<'_> { /// Returns the full raw memory data. - pub fn data(&self) -> &[u8] { - &self.0.data + pub fn data<'a>(&self, store: &'a Store) -> Result<&'a [u8]> { + Ok(&self.instance(store)?.data) + } + + /// Returns the full raw mutable memory data. + pub fn data_mut<'a>(&self, store: &'a mut Store) -> Result<&'a mut [u8]> { + Ok(&mut self.instance_mut(store)?.data) } /// Returns the raw memory byte length. - pub fn data_size(&self) -> usize { - self.0.data.len() + pub fn data_size(&self, store: &Store) -> Result<usize> { + Ok(self.instance(store)?.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. + pub fn load<'a>(&self, store: &'a Store, offset: usize, len: usize) -> Result<&'a [u8]> { + self.instance(store)?.load(offset, len) } -} -impl MemoryRefMut<'_> { - /// Returns the full raw memory data. - pub fn data(&self) -> &[u8] { - &self.0.data + /// Grow the memory by the given number of pages. + pub fn grow(&self, store: &mut Store, delta_pages: i64) -> Result<Option<i64>> { + Ok(self.instance_mut(store)?.grow(delta_pages)) } - /// Returns the full raw mutable memory data. - pub fn data_mut(&mut self) -> &mut [u8] { - &mut self.0.data + /// Get the current size of the memory in pages. + pub fn page_count(&self, store: &Store) -> Result<usize> { + Ok(self.instance(store)?.page_count) } - /// Returns the raw memory byte length. - pub fn data_size(&self) -> usize { - self.0.data.len() + /// Copy a slice of memory to another place in memory. + pub fn copy_within(&self, store: &mut Store, src: usize, dst: usize, len: usize) -> Result<()> { + self.instance_mut(store)?.copy_within(dst, src, len) } - /// Load a slice of memory - pub fn load(&self, offset: usize, len: usize) -> Result<&[u8]> { - self.0.load(offset, len) + /// Fill a slice of memory with a value. + pub fn fill(&self, store: &mut Store, offset: usize, len: usize, val: u8) -> Result<()> { + self.instance_mut(store)?.fill(offset, len, val) } - /// Grow the memory by the given number of pages - pub fn grow(&mut self, delta_pages: i64) -> Option<i64> { - self.0.grow(delta_pages) + /// Store a slice of memory. + pub fn store(&self, store: &mut Store, offset: usize, len: usize, data: &[u8]) -> Result<()> { + self.instance_mut(store)?.store(offset, len, data) } - /// Get the current size of the memory in pages - pub fn page_count(&mut self) -> usize { - self.0.page_count + /// Load a C-style string from memory. + pub fn load_cstr<'a>(&self, store: &'a Store, offset: usize, len: usize) -> Result<&'a CStr> { + CStr::from_bytes_with_nul(self.load(store, offset, len)?) + .map_err(|e| crate::Error::Other(format!("Invalid C-style string: {e}"))) } - /// 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(dst, src, len) + /// Load a C-style string from memory, stopping at the first nul byte. + pub fn load_cstr_until_nul<'a>(&self, store: &'a Store, offset: usize, max_len: usize) -> Result<&'a CStr> { + CStr::from_bytes_until_nul(self.load(store, offset, max_len)?) + .map_err(|e| crate::Error::Other(format!("Invalid C-style string: {e}"))) } - /// Fill a slice of memory with a value - pub fn fill(&mut self, offset: usize, len: usize, val: u8) -> Result<()> { - self.0.fill(offset, len, val) + /// Load a UTF-8 string from memory. + pub fn load_string(&self, store: &Store, offset: usize, len: usize) -> Result<String> { + String::from_utf8(self.load(store, offset, len)?.to_vec()) + .map_err(|e| crate::Error::Other(format!("Invalid UTF-8 string: {e}"))) } - /// Store a slice of memory - pub fn store(&mut self, offset: usize, len: usize, data: &[u8]) -> Result<()> { - self.0.store(offset, len, data) + /// Load a C-style string from memory. + pub fn load_cstring(&self, store: &Store, offset: usize, len: usize) -> Result<CString> { + Ok(CString::from(self.load_cstr(store, offset, len)?)) } -} -impl TableRef<'_> { - /// Get the type of the table. - pub fn ty(&self) -> TableType { - self.0.kind.clone() + /// Load a C-style string from memory, stopping at the first nul byte. + pub fn load_cstring_until_nul(&self, store: &Store, offset: usize, max_len: usize) -> Result<CString> { + Ok(CString::from(self.load_cstr_until_nul(store, offset, max_len)?)) } - /// Get the current number of elements in the table. - pub fn size(&self) -> usize { - self.0.size() as usize + /// Load a JavaScript-style utf-16 string from memory. + pub fn load_js_string(&self, store: &Store, offset: usize, len: usize) -> Result<String> { + let bytes = self.load(store, offset, len)?; + let mut string = String::new(); + for i in 0..(len / 2) { + let c = u16::from_le_bytes([bytes[i * 2], bytes[i * 2 + 1]]); + string.push( + char::from_u32(u32::from(c)).ok_or_else(|| crate::Error::Other("Invalid UTF-16 string".to_string()))?, + ); + } + Ok(string) } +} - /// Get a table element as a wasm reference value. - pub fn get(&self, index: TableAddr) -> Result<WasmValue> { - self.0.get_wasm_val(index) +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"), } +} - /// 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))) +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 TableRefMut<'_> { +impl Table { + #[inline] + pub(crate) const fn from_store_addr(store_id: usize, addr: TableAddr) -> Self { + Self(StoreItem::new(store_id, addr)) + } + + /// Create a new table in the given store. + pub fn new(store: &mut Store, ty: TableType, init: WasmValue) -> Result<Self> { + let init = match (ty.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())), + }; + let addr = store.state.tables.len() as TableAddr; + store.state.tables.push(TableInstance::new_with_init(ty, init)); + Ok(Self::from_store_addr(store.id(), addr)) + } + + #[inline] + fn instance<'a>(&self, store: &'a Store) -> Result<&'a TableInstance> { + self.0.validate_store(store)?; + Ok(store.state.get_table(self.0.addr)) + } + + #[inline] + fn instance_mut<'a>(&self, store: &'a mut Store) -> Result<&'a mut TableInstance> { + self.0.validate_store(store)?; + Ok(store.state.get_table_mut(self.0.addr)) + } + /// Get the type of the table. - pub fn ty(&self) -> TableType { - self.0.kind.clone() + pub fn ty(&self, store: &Store) -> Result<TableType> { + Ok(self.instance(store)?.kind.clone()) } /// Get the current number of elements in the table. - pub fn size(&self) -> usize { - self.0.size() as usize + pub fn size(&self, store: &Store) -> Result<usize> { + Ok(self.instance(store)?.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) + pub fn get(&self, store: &Store, index: TableAddr) -> Result<WasmValue> { + self.instance(store)?.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))) + pub fn load(&self, store: &Store, offset: usize, len: usize) -> Result<alloc::vec::IntoIter<WasmValue>> { + let table = self.instance(store)?; + let element_type = table.kind.element_type; + let elements = table.load(offset, len)?; + Ok(elements + .iter() + .copied() + .map(move |element| table_element_to_value(element_type, element)) + .collect::<alloc::vec::Vec<_>>() + .into_iter()) } /// 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) + pub fn set(&self, store: &mut Store, index: TableAddr, value: WasmValue) -> Result<()> { + let table = self.instance_mut(store)?; + let value = table_value_to_element(table.kind.element_type, value)?; + table.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) + pub fn copy_within(&self, store: &mut Store, src: usize, dst: usize, len: usize) -> Result<()> { + self.instance_mut(store)?.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)?; + pub fn grow(&self, store: &mut Store, delta: i32, init: WasmValue) -> Result<usize> { + let table = self.instance_mut(store)?; + let old_size = table.size() as usize; + let init = table_value_to_element(table.kind.element_type, init)?; + table.grow(delta, init)?; Ok(old_size) } } -impl GlobalRef<'_> { - /// Get the type of the global. - pub fn ty(&self) -> GlobalType { - self.0.ty +impl Global { + #[inline] + pub(crate) const fn from_store_addr(store_id: usize, addr: GlobalAddr) -> Self { + Self(StoreItem::new(store_id, addr)) } - /// Get the current value of the global. - pub fn get(&self) -> WasmValue { - self.0.value.get().attach_type(self.0.ty.ty) + /// Create a new global in the given store. + pub fn new(store: &mut Store, ty: GlobalType, value: WasmValue) -> Result<Self> { + let addr = store.state.globals.len() as GlobalAddr; + store.state.globals.push(GlobalInstance::new(ty, value.into())); + Ok(Self::from_store_addr(store.id(), addr)) + } + + #[inline] + fn instance<'a>(&self, store: &'a Store) -> Result<&'a GlobalInstance> { + self.0.validate_store(store)?; + Ok(store.state.get_global(self.0.addr)) + } + + #[inline] + fn instance_mut<'a>(&self, store: &'a mut Store) -> Result<&'a mut GlobalInstance> { + self.0.validate_store(store)?; + Ok(store.state.get_global_mut(self.0.addr)) } -} -impl GlobalRefMut<'_> { /// Get the type of the global. - pub fn ty(&self) -> GlobalType { - self.0.ty + pub fn ty(&self, store: &Store) -> Result<GlobalType> { + Ok(self.instance(store)?.ty) } /// Get the current value of the global. - pub fn get(&self) -> WasmValue { - self.0.value.get().attach_type(self.0.ty.ty) + pub fn get(&self, store: &Store) -> Result<WasmValue> { + let global = self.instance(store)?; + Ok(global.value.get().attach_type(global.ty.ty)) } /// Set the current value of the global. - pub fn set(&mut self, value: WasmValue) -> Result<()> { - if !self.0.ty.mutable { + pub fn set(&self, store: &mut Store, value: WasmValue) -> Result<()> { + let global = self.instance_mut(store)?; + if !global.ty.mutable { return Err(Error::Other("global is immutable".to_string())); } - if value.val_type() != self.0.ty.ty { + if value.val_type() != global.ty.ty { return Err(Error::Other("invalid global value type".to_string())); } - self.0.value.set(value.into()); + global.value.set(value.into()); Ok(()) } } - -#[doc(hidden)] -pub trait MemoryRefLoad { - fn load(&self, offset: usize, len: usize) -> Result<&[u8]>; -} - -/// Convenience methods for loading strings from memory -pub trait MemoryStringExt: MemoryRefLoad { - /// Load a C-style string from memory - fn load_cstr(&self, offset: usize, len: usize) -> Result<&CStr> { - let bytes = self.load(offset, len)?; - CStr::from_bytes_with_nul(bytes).map_err(|e| crate::Error::Other(format!("Invalid C-style string: {e}"))) - } - - /// Load a C-style string from memory, stopping at the first nul byte - fn load_cstr_until_nul(&self, offset: usize, max_len: usize) -> Result<&CStr> { - let bytes = self.load(offset, max_len)?; - CStr::from_bytes_until_nul(bytes).map_err(|e| crate::Error::Other(format!("Invalid C-style string: {e}"))) - } - - /// Load a UTF-8 string from memory - fn load_string(&self, offset: usize, len: usize) -> Result<String> { - let bytes = self.load(offset, len)?; - String::from_utf8(bytes.to_vec()).map_err(|e| crate::Error::Other(format!("Invalid UTF-8 string: {e}"))) - } - - /// Load a C-style string from memory - fn load_cstring(&self, offset: usize, len: usize) -> Result<CString> { - Ok(CString::from(self.load_cstr(offset, len)?)) - } - - /// Load a C-style string from memory, stopping at the first nul byte - fn load_cstring_until_nul(&self, offset: usize, max_len: usize) -> Result<CString> { - Ok(CString::from(self.load_cstr_until_nul(offset, max_len)?)) - } - - /// Load a JavaScript-style utf-16 string from memory - fn load_js_string(&self, offset: usize, len: usize) -> Result<String> { - let bytes = self.load(offset, len)?; - let mut string = String::new(); - for i in 0..(len / 2) { - let c = u16::from_le_bytes([bytes[i * 2], bytes[i * 2 + 1]]); - string.push( - char::from_u32(u32::from(c)).ok_or_else(|| crate::Error::Other("Invalid UTF-16 string".to_string()))?, - ); - } - Ok(string) - } -} - -impl MemoryStringExt for MemoryRef<'_> {} -impl MemoryStringExt for MemoryRefMut<'_> {} diff --git a/crates/tinywasm/src/store/function.rs b/crates/tinywasm/src/store/function.rs index 3067a8e..b8edf67 100644 --- a/crates/tinywasm/src/store/function.rs +++ b/crates/tinywasm/src/store/function.rs @@ -1,19 +1,40 @@ -use crate::Function; use alloc::rc::Rc; use tinywasm_types::*; +use crate::func::HostFunction; + /// A WebAssembly Function Instance /// /// See <https://webassembly.github.io/spec/core/exec/runtime.html#function-instances> #[derive(Clone)] #[cfg_attr(feature = "debug", derive(Debug))] pub(crate) struct FunctionInstance { - pub(crate) func: Function, + pub(crate) func: FunctionDef, pub(crate) owner: ModuleInstanceAddr, // index into store.module_instances, none for host functions } +/// The internal representation of a function +#[derive(Clone)] +#[cfg_attr(feature = "debug", derive(Debug))] +pub(crate) enum FunctionDef { + /// A host function + Host(Rc<HostFunction>), + + /// A pointer to a WebAssembly function + Wasm(Rc<WasmFunction>), +} + +impl FunctionDef { + pub(crate) fn ty(&self) -> &FuncType { + match self { + Self::Host(f) => &f.ty, + Self::Wasm(f) => &f.ty, + } + } +} + impl FunctionInstance { pub(crate) fn new_wasm(func: WasmFunction, owner: ModuleInstanceAddr) -> Self { - Self { func: Function::Wasm(Rc::new(func)), owner } + Self { func: FunctionDef::Wasm(Rc::new(func)), owner } } } diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs index 8c6289c..f7bd0e9 100644 --- a/crates/tinywasm/src/store/mod.rs +++ b/crates/tinywasm/src/store/mod.rs @@ -6,7 +6,7 @@ use tinywasm_types::*; use crate::instance::ModuleInstanceInner; use crate::interpreter::TinyWasmValue; use crate::interpreter::stack::Stack; -use crate::{Engine, Error, Function, ModuleInstance, Result, Trap}; +use crate::{Engine, Error, ModuleInstance, Result, Trap}; mod data; mod element; @@ -118,8 +118,8 @@ impl State { pub(crate) fn get_wasm_func(&self, addr: FuncAddr) -> &Rc<WasmFunction> { match self.funcs.get(addr as usize) { Some(func) => match &func.func { - Function::Wasm(wasm_func) => wasm_func, - Function::Host(_) => unreachable!( + FunctionDef::Wasm(wasm_func) => wasm_func, + FunctionDef::Host(_) => unreachable!( "expected a wasm function at address {addr}, but found a host function. This should be unreachable" ), }, @@ -423,36 +423,7 @@ impl Store { Ok((data_addrs.into_boxed_slice(), None)) } - pub(crate) fn add_global(&mut self, ty: GlobalType, value: TinyWasmValue, _idx: ModuleInstanceAddr) -> Addr { - self.state.globals.push(GlobalInstance::new(ty, value)); - self.state.globals.len() as Addr - 1 - } - - 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) - } - - pub(crate) fn add_mem(&mut self, mem: MemoryType, _idx: ModuleInstanceAddr) -> Result<MemAddr> { - if let MemoryArch::I64 = mem.arch() { - return Err(Error::UnsupportedFeature("64-bit memories".to_string())); - } - self.state.memories.push(MemoryInstance::new(mem)); - Ok(self.state.memories.len() as MemAddr - 1) - } - - pub(crate) fn add_func(&mut self, func: Function, idx: ModuleInstanceAddr) -> FuncAddr { + pub(crate) fn add_func(&mut self, func: FunctionDef, idx: ModuleInstanceAddr) -> FuncAddr { self.state.funcs.push(FunctionInstance { func, owner: idx }); self.state.funcs.len() as FuncAddr - 1 } diff --git a/crates/tinywasm/tests/host_func_signature_check.rs b/crates/tinywasm/tests/host_func_signature_check.rs index 4590e77..6667e7a 100644 --- a/crates/tinywasm/tests/host_func_signature_check.rs +++ b/crates/tinywasm/tests/host_func_signature_check.rs @@ -1,7 +1,7 @@ use eyre::Result; use std::fmt::Write; use tinywasm::{ - Extern, FuncContext, Imports, Module, Store, + FuncContext, HostFunction, Imports, Module, Store, types::{FuncType, ValType, WasmValue}, }; use tinywasm_types::ExternRef; @@ -34,18 +34,17 @@ fn module_cases() -> Vec<(Module, FuncType, Vec<WasmValue>)> { fn test_return_invalid_type() -> Result<()> { let cases = module_cases(); - for (module, func_ty, args) in cases { + for (module, ty, args) in cases { for returned_values in VAL_LISTS { let mut store = Store::default(); let mut imports = Imports::new(); - imports - .define("host", "hfn", Extern::func(&func_ty, |_: FuncContext<'_>, _| Ok(returned_values.to_vec()))) - .unwrap(); + let hfn = HostFunction::from_untyped(&mut store, &ty, |_: FuncContext<'_>, _| Ok(returned_values.to_vec())); + imports.define("host", "hfn", hfn); let instance = module.clone().instantiate(&mut store, Some(imports)).unwrap(); - let caller = instance.func(&store, "call_hfn").unwrap(); + let caller = instance.func_untyped(&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 should_succeed = returned_values.iter().map(WasmValue::val_type).eq(ty.results.iter().copied()); let call_res = caller.call(&mut store, &args); assert_eq!(call_res.is_ok(), should_succeed); } @@ -58,15 +57,15 @@ fn test_return_invalid_type() -> Result<()> { fn test_linking_invalid_untyped_func() -> Result<()> { let cases = module_cases(); for (module, expected_func_ty, _) in &cases { - for (_, func_ty_to_try, _) in &cases { - let tried_fn = Extern::func(func_ty_to_try, |_: FuncContext<'_>, _| panic!("not intended to be called")); + for (_, ty, _) in &cases { let mut store = Store::default(); + let tried_fn = + HostFunction::from_untyped(&mut store, ty, |_: FuncContext<'_>, _| panic!("not intended to be called")); let mut imports = Imports::new(); - imports.define("host", "hfn", tried_fn).unwrap(); + imports.define("host", "hfn", tried_fn); - let should_succeed = func_ty_to_try == expected_func_ty; + let should_succeed = ty == expected_func_ty; let link_res = module.clone().instantiate(&mut store, Some(imports)); - assert_eq!(link_res.is_ok(), should_succeed); } } @@ -80,26 +79,37 @@ fn test_linking_invalid_typed_func() -> Result<()> { type NonMatchingTuple = (f64, i32, i32); const DONT_CALL: &str = "not meant to be called"; - // None of these typed host signatures are produced by module_cases(). - let matching_none = vec![ - Extern::typed_func(|_, _: NonMatchingTuple| -> tinywasm::Result<Existing> { panic!("{DONT_CALL}") }), - Extern::typed_func(|_, _: NonMatchingTuple| -> tinywasm::Result<()> { panic!("{DONT_CALL}") }), - Extern::typed_func(|_, _: NonMatchingSingle| -> tinywasm::Result<Existing> { panic!("{DONT_CALL}") }), - Extern::typed_func(|_, _: NonMatchingSingle| -> tinywasm::Result<()> { panic!("{DONT_CALL}") }), - Extern::typed_func(|_, _: Existing| -> tinywasm::Result<NonMatchingTuple> { panic!("{DONT_CALL}") }), - Extern::typed_func(|_, _: Existing| -> tinywasm::Result<NonMatchingSingle> { panic!("{DONT_CALL}") }), - Extern::typed_func(|_, _: ()| -> tinywasm::Result<NonMatchingSingle> { panic!("{DONT_CALL}") }), - Extern::typed_func(|_, _: ()| -> tinywasm::Result<NonMatchingTuple> { panic!("{DONT_CALL}") }), - Extern::typed_func(|_, _: NonMatchingSingle| -> tinywasm::Result<NonMatchingTuple> { panic!("{DONT_CALL}") }), - Extern::typed_func(|_, _: NonMatchingSingle| -> tinywasm::Result<NonMatchingSingle> { panic!("{DONT_CALL}") }), - ]; - let cases = module_cases(); for (module, _, _) in cases { - for typed_fn in matching_none.iter().cloned() { - let mut store = Store::default(); + let mut store = Store::default(); + let matching_none = vec![ + HostFunction::from(&mut store, |_, _: NonMatchingTuple| -> tinywasm::Result<Existing> { + panic!("{DONT_CALL}") + }), + HostFunction::from(&mut store, |_, _: NonMatchingTuple| -> tinywasm::Result<()> { panic!("{DONT_CALL}") }), + HostFunction::from(&mut store, |_, _: NonMatchingSingle| -> tinywasm::Result<Existing> { + panic!("{DONT_CALL}") + }), + HostFunction::from(&mut store, |_, _: NonMatchingSingle| -> tinywasm::Result<()> { panic!("{DONT_CALL}") }), + HostFunction::from(&mut store, |_, _: Existing| -> tinywasm::Result<NonMatchingTuple> { + panic!("{DONT_CALL}") + }), + HostFunction::from(&mut store, |_, _: Existing| -> tinywasm::Result<NonMatchingSingle> { + panic!("{DONT_CALL}") + }), + HostFunction::from(&mut store, |_, _: ()| -> tinywasm::Result<NonMatchingSingle> { panic!("{DONT_CALL}") }), + HostFunction::from(&mut store, |_, _: ()| -> tinywasm::Result<NonMatchingTuple> { panic!("{DONT_CALL}") }), + HostFunction::from(&mut store, |_, _: NonMatchingSingle| -> tinywasm::Result<NonMatchingTuple> { + panic!("{DONT_CALL}") + }), + HostFunction::from(&mut store, |_, _: NonMatchingSingle| -> tinywasm::Result<NonMatchingSingle> { + panic!("{DONT_CALL}") + }), + ]; + + for typed_fn in matching_none { let mut imports = Imports::new(); - imports.define("host", "hfn", typed_fn).unwrap(); + imports.define("host", "hfn", typed_fn); let link_failure = module.clone().instantiate(&mut store, Some(imports)); assert!(link_failure.is_err(), "Expected linking to fail for mismatched typed func, but it succeeded"); } diff --git a/crates/tinywasm/tests/import_linking.rs b/crates/tinywasm/tests/import_linking.rs index 85c41ed..3ab13ff 100644 --- a/crates/tinywasm/tests/import_linking.rs +++ b/crates/tinywasm/tests/import_linking.rs @@ -35,7 +35,7 @@ fn link_module_links_same_store_instance() -> Result<()> { imports.link_module("adder", add_instance)?; let instance = import_module.instantiate(&mut store, Some(imports))?; - let main = instance.func_typed::<(), i32>(&store, "main")?; + let main = instance.func::<(), i32>(&store, "main")?; assert_eq!(main.call(&mut store, ())?, 3); Ok(()) } diff --git a/crates/tinywasm/tests/imported_table_init.rs b/crates/tinywasm/tests/imported_table_init.rs index c040394..8b07000 100644 --- a/crates/tinywasm/tests/imported_table_init.rs +++ b/crates/tinywasm/tests/imported_table_init.rs @@ -1,6 +1,6 @@ use eyre::Result; use tinywasm::types::{FuncRef, TableType, ValType, WasmValue}; -use tinywasm::{Extern, Imports, Module, Store}; +use tinywasm::{Imports, Module, Store, Table}; #[test] fn imported_table_uses_provided_init_value() -> Result<()> { @@ -19,14 +19,12 @@ fn imported_table_uses_provided_init_value() -> Result<()> { 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 table = + Table::new(&mut store, TableType::new(ValType::RefFunc, 3, None), WasmValue::RefFunc(FuncRef::new(Some(0))))?; + imports.define("host", "table", table); let instance = module.instantiate(&mut store, Some(imports))?; - let slot_is_null = instance.func_typed::<i32, i32>(&store, "slot_is_null")?; + let slot_is_null = instance.func::<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); diff --git a/crates/tinywasm/tests/internal_refs.rs b/crates/tinywasm/tests/internal_refs.rs index bd5561f..98dc702 100644 --- a/crates/tinywasm/tests/internal_refs.rs +++ b/crates/tinywasm/tests/internal_refs.rs @@ -1,6 +1,6 @@ use eyre::Result; use tinywasm::types::{FuncRef, WasmValue}; -use tinywasm::{ExternItemRef, ExternItemRefMut, Module, Store}; +use tinywasm::{ExternItem, Module, Store}; #[test] #[cfg(feature = "guest_debug")] @@ -25,16 +25,16 @@ fn private_items_are_accessible_by_index() -> Result<()> { 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]); + instance.memory_by_index(0)?.store(&mut store, 0, 4, &[1, 2, 3, 4])?; + assert_eq!(instance.memory_by_index(0)?.load(&store, 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.table_by_index(0)?.size(&store)?, 2); + assert_eq!(instance.table_by_index(0)?.get(&store, 0)?, WasmValue::RefFunc(FuncRef::new(Some(0)))); + assert!(matches!(instance.table_by_index(0)?.get(&store, 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)); + assert_eq!(instance.global_by_index(0)?.get(&store)?, WasmValue::I32(11)); + instance.global_by_index(0)?.set(&mut store, WasmValue::I32(23))?; + assert_eq!(instance.global_by_index(0)?.get(&store)?, WasmValue::I32(23)); Ok(()) } @@ -55,17 +55,17 @@ fn exported_tables_and_globals_have_handle_and_helper_apis() -> Result<()> { 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)); + assert_eq!(instance.global("g")?.get(&store)?, WasmValue::I32(3)); instance.global_set(&mut store, "g", WasmValue::I32(9))?; - assert_eq!(instance.global_mut(&mut store, "g")?.get(), WasmValue::I32(9)); + assert_eq!(instance.global("g")?.get(&store)?, 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 table = instance.table("t")?; + assert_eq!(table.size(&store)?, 1); + assert!(matches!(table.get(&store, 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()))?; + let old_size = instance.table("t")?.grow(&mut store, 1, WasmValue::RefFunc(FuncRef::null()))?; assert_eq!(old_size, 1); - assert_eq!(instance.table(&store, "t")?.size(), 2); + assert_eq!(instance.table("t")?.size(&store)?, 2); Ok(()) } @@ -87,15 +87,10 @@ fn extern_item_lookup_returns_expected_kinds() -> Result<()> { 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(_))); + assert!(matches!(instance.extern_item("f")?, ExternItem::Func(_))); + assert!(matches!(instance.extern_item("m")?, ExternItem::Memory(_))); + assert!(matches!(instance.extern_item("t")?, ExternItem::Table(_))); + assert!(matches!(instance.extern_item("g")?, ExternItem::Global(_))); Ok(()) } diff --git a/crates/tinywasm/tests/memory_ref_api.rs b/crates/tinywasm/tests/memory_ref_api.rs index 9e57b6b..dd2d01e 100644 --- a/crates/tinywasm/tests/memory_ref_api.rs +++ b/crates/tinywasm/tests/memory_ref_api.rs @@ -15,11 +15,11 @@ fn memory_ref_mut_copy_within_uses_src_then_dst_order() -> Result<()> { 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)?; + let memory = instance.memory("memory")?; + memory.store(&mut store, 0, 4, &[1, 2, 3, 4])?; + memory.copy_within(&mut store, 0, 4, 4)?; - assert_eq!(memory.load(0, 8)?, &[1, 2, 3, 4, 1, 2, 3, 4]); + assert_eq!(memory.load(&store, 0, 8)?, &[1, 2, 3, 4, 1, 2, 3, 4]); Ok(()) } diff --git a/crates/tinywasm/tests/resume_execution.rs b/crates/tinywasm/tests/resume_execution.rs index 5a58f81..a1e2bc6 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.func_typed::<i32, i32>(&store_full, "fibonacci_recursive")?; + let func_full = instance_full.func::<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.func_typed::<i32, i32>(&store_budgeted, "fibonacci_recursive")?; + let func_budgeted = instance_budgeted.func::<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.func(&store, "add")?; + let func = instance.func_untyped(&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.func_typed::<i32, i32>(&per_instr_store, "fibonacci_recursive")?; + let func_per_instr = instance_per_instr.func::<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.func_typed::<i32, i32>(&weighted_store, "fibonacci_recursive")?; + let func_weighted = instance_weighted.func::<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.func_typed::<(i32, i32), i32>(&store, "add")?; + let func = instance.func::<(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 index a9cbad4..b8c3729 100644 --- a/crates/tinywasm/tests/store_ownership.rs +++ b/crates/tinywasm/tests/store_ownership.rs @@ -18,7 +18,7 @@ fn func_handle_rejects_wrong_store() -> Result<()> { let mut owner_store = Store::default(); let instance = module.instantiate(&mut owner_store, None)?; - let func = instance.func(&owner_store, "add")?; + let func = instance.func_untyped(&owner_store, "add")?; let mut other_store = Store::default(); let err = func.call(&mut other_store, &[1.into(), 2.into()]).unwrap_err(); @@ -35,8 +35,9 @@ fn memory_access_rejects_wrong_store() -> Result<()> { let mut owner_store = Store::default(); let instance = module.instantiate(&mut owner_store, None)?; + let memory = instance.memory("memory")?; let other_store = Store::default(); - let err = instance.memory(&other_store, "memory").unwrap_err(); + let err = memory.data(&other_store).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 e8c35c2..b45baa4 100644 --- a/crates/tinywasm/tests/testsuite/run.rs +++ b/crates/tinywasm/tests/testsuite/run.rs @@ -5,7 +5,7 @@ use super::TestSuite; use eyre::{Result, eyre}; use indexmap::IndexMap; use log::{debug, error, info}; -use tinywasm::{Extern, Imports, ModuleInstance}; +use tinywasm::{Global, HostFunction, Imports, Memory, ModuleInstance, Table}; use tinywasm_types::{ExternVal, MemoryType, ModuleInstanceAddr, TableType, ValType, WasmValue}; use wasm_testsuite::data::TestFile; use wasm_testsuite::wast; @@ -87,65 +87,72 @@ impl TestSuite { Ok(()) } - fn imports(modules: &HashMap<std::string::String, ModuleInstance>) -> Result<Imports> { + fn imports(store: &mut tinywasm::Store, modules: &HashMap<std::string::String, ModuleInstance>) -> Result<Imports> { let mut imports = Imports::new(); - let table = - Extern::table(TableType::new(ValType::RefFunc, 10, Some(20)), WasmValue::default_for(ValType::RefFunc)); + let table = Table::new( + store, + TableType::new(ValType::RefFunc, 10, Some(20)), + WasmValue::default_for(ValType::RefFunc), + )?; - let print = Extern::typed_func(|_ctx: tinywasm::FuncContext, (): ()| { + let print = HostFunction::from(store, |_ctx: tinywasm::FuncContext, (): ()| { log::debug!("print"); Ok(()) }); - let print_i32 = Extern::typed_func(|_ctx: tinywasm::FuncContext, arg: i32| { + let print_i32 = HostFunction::from(store, |_ctx: tinywasm::FuncContext, arg: i32| { log::debug!("print_i32: {arg}"); Ok(()) }); - let print_i64 = Extern::typed_func(|_ctx: tinywasm::FuncContext, arg: i64| { + let print_i64 = HostFunction::from(store, |_ctx: tinywasm::FuncContext, arg: i64| { log::debug!("print_i64: {arg}"); Ok(()) }); - let print_f32 = Extern::typed_func(|_ctx: tinywasm::FuncContext, arg: f32| { + let print_f32 = HostFunction::from(store, |_ctx: tinywasm::FuncContext, arg: f32| { log::debug!("print_f32: {arg}"); Ok(()) }); - let print_f64 = Extern::typed_func(|_ctx: tinywasm::FuncContext, arg: f64| { + let print_f64 = HostFunction::from(store, |_ctx: tinywasm::FuncContext, arg: f64| { log::debug!("print_f64: {arg}"); Ok(()) }); - let print_i32_f32 = Extern::typed_func(|_ctx: tinywasm::FuncContext, args: (i32, f32)| { + let print_i32_f32 = HostFunction::from(store, |_ctx: tinywasm::FuncContext, args: (i32, f32)| { log::debug!("print_i32_f32: {}, {}", args.0, args.1); Ok(()) }); - let print_f64_f64 = Extern::typed_func(|_ctx: tinywasm::FuncContext, args: (f64, f64)| { + let print_f64_f64 = HostFunction::from(store, |_ctx: tinywasm::FuncContext, args: (f64, f64)| { log::debug!("print_f64_f64: {}, {}", args.0, args.1); Ok(()) }); + let memory = Memory::new(store, MemoryType::default().with_page_count_initial(1).with_page_count_max(Some(2)))?; + let global_i32 = Global::new(store, tinywasm_types::GlobalType::new(ValType::I32, false), WasmValue::I32(666))?; + let global_i64 = Global::new(store, tinywasm_types::GlobalType::new(ValType::I64, false), WasmValue::I64(666))?; + let global_f32 = + Global::new(store, tinywasm_types::GlobalType::new(ValType::F32, false), WasmValue::F32(666.6))?; + let global_f64 = + Global::new(store, tinywasm_types::GlobalType::new(ValType::F64, false), WasmValue::F64(666.6))?; + imports - .define( - "spectest", - "memory", - Extern::memory(MemoryType::new(tinywasm_types::MemoryArch::I32, 1, Some(2), None)), - )? - .define("spectest", "table", table)? - .define("spectest", "global_i32", Extern::global(WasmValue::I32(666), false))? - .define("spectest", "global_i64", Extern::global(WasmValue::I64(666), false))? - .define("spectest", "global_f32", Extern::global(WasmValue::F32(666.6), false))? - .define("spectest", "global_f64", Extern::global(WasmValue::F64(666.6), false))? - .define("spectest", "print", print)? - .define("spectest", "print_i32", print_i32)? - .define("spectest", "print_i64", print_i64)? - .define("spectest", "print_f32", print_f32)? - .define("spectest", "print_f64", print_f64)? - .define("spectest", "print_i32_f32", print_i32_f32)? - .define("spectest", "print_f64_f64", print_f64_f64)?; + .define("spectest", "memory", memory) + .define("spectest", "table", table) + .define("spectest", "global_i32", global_i32) + .define("spectest", "global_i64", global_i64) + .define("spectest", "global_f32", global_f32) + .define("spectest", "global_f64", global_f64) + .define("spectest", "print", print) + .define("spectest", "print_i32", print_i32) + .define("spectest", "print_i64", print_i64) + .define("spectest", "print_f32", print_f32) + .define("spectest", "print_f64", print_f64) + .define("spectest", "print_i32_f32", print_i32_f32) + .define("spectest", "print_f64_f64", print_f64_f64); for (name, module) in modules { log::debug!("registering module: {name}"); @@ -208,8 +215,9 @@ impl TestSuite { let (name, bytes) = encode_quote_wat(module); let m = parse_module_bytes(&bytes).expect("failed to parse module bytes"); + let imports = Self::imports(&mut store, module_registry.modules()).unwrap(); let module_instance = tinywasm::Module::from(m) - .instantiate(&mut store, Some(Self::imports(module_registry.modules()).unwrap())) + .instantiate(&mut store, Some(imports)) .expect("failed to instantiate module"); (name, module_instance) @@ -310,8 +318,8 @@ impl TestSuite { let module = parse_module_bytes(&wat.encode().expect("failed to encode module")) .expect("failed to parse module"); let module = tinywasm::Module::from(module); - module - .instantiate(&mut store, Some(Self::imports(module_registry.modules()).unwrap()))?; + let imports = Self::imports(&mut store, module_registry.modules()).unwrap(); + module.instantiate(&mut store, Some(imports))?; return Ok(()); } wast::WastExecute::Get { module: _, global: _, .. } => { @@ -361,7 +369,8 @@ impl TestSuite { let module = parse_module_bytes(&module.encode().expect("failed to encode module")) .expect("failed to parse module"); let module = tinywasm::Module::from(module); - module.instantiate(&mut store, Some(Self::imports(module_registry.modules()).unwrap())) + let imports = Self::imports(&mut store, module_registry.modules()).unwrap(); + module.instantiate(&mut store, Some(imports)) }); match res { diff --git a/crates/tinywasm/tests/testsuite/util.rs b/crates/tinywasm/tests/testsuite/util.rs index 000bb1a..4b3e001 100644 --- a/crates/tinywasm/tests/testsuite/util.rs +++ b/crates/tinywasm/tests/testsuite/util.rs @@ -26,7 +26,7 @@ pub fn exec_fn_instance( return Err(tinywasm::Error::Other("no instance found".to_string())); }; - let func = instance.func(store, name)?; + let func = instance.func_untyped(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.func(&store, name)?.call(&mut store, args) + instance.func_untyped(&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 index 7bfc26d..acc6a7a 100644 --- a/crates/tinywasm/tests/typed_lookup.rs +++ b/crates/tinywasm/tests/typed_lookup.rs @@ -18,9 +18,9 @@ fn func_typed_rejects_wrong_param_or_result_types() -> Result<()> { 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()); + assert!(instance.func::<(i32, i32), i32>(&store, "add").is_ok()); + assert!(instance.func::<i32, i32>(&store, "add").is_err()); + assert!(instance.func::<(i32, i32), ()>(&store, "add").is_err()); Ok(()) } @@ -41,8 +41,8 @@ fn func_typed_rejects_partial_multi_value_results() -> Result<()> { 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()); + assert!(instance.func::<(), (i32, i32)>(&store, "pair").is_ok()); + assert!(instance.func::<(), i32>(&store, "pair").is_err()); Ok(()) } diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 737c04e..f74336c 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -336,6 +336,31 @@ pub struct GlobalType { pub ty: ValType, } +impl GlobalType { + /// Create a new global type. + pub const fn new(ty: ValType, mutable: bool) -> Self { + Self { mutable, ty } + } + + /// Set a different value type. + pub const fn with_ty(mut self, ty: ValType) -> Self { + self.ty = ty; + self + } + + /// Set global mutability. + pub const fn with_mutable(mut self, mutable: bool) -> Self { + self.mutable = mutable; + self + } +} + +impl Default for GlobalType { + fn default() -> Self { + Self::new(ValType::I32, false) + } +} + #[derive(Clone, PartialEq, Eq)] #[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] @@ -367,6 +392,7 @@ pub struct MemoryType { } impl MemoryType { + /// Create a new memory type. pub const fn new( arch: MemoryArch, page_count_initial: u64, @@ -405,6 +431,36 @@ impl MemoryType { pub const fn max_size(&self) -> u64 { self.page_count_max() * self.page_size() } + + /// Set a different memory architecture. + pub const fn with_arch(mut self, arch: MemoryArch) -> Self { + self.arch = arch; + self + } + + /// Set a different initial page count. + pub const fn with_page_count_initial(mut self, page_count_initial: u64) -> Self { + self.page_count_initial = page_count_initial; + self + } + + /// Set a different maximum page count. + pub const fn with_page_count_max(mut self, page_count_max: Option<u64>) -> Self { + self.page_count_max = page_count_max; + self + } + + /// Set a different page size. + pub const fn with_page_size(mut self, page_size: Option<u64>) -> Self { + self.page_size = page_size; + self + } +} + +impl Default for MemoryType { + fn default() -> Self { + Self::new(MemoryArch::I32, 0, None, None) + } } #[derive(Copy, Clone, PartialEq, Eq, Hash)] |
