summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHenry <mail@henrygressmann.de>2026-05-03 17:53:13 +0200
committerHenry <mail@henrygressmann.de>2026-05-03 17:53:13 +0200
commitb99b643fc3991f37a7b6d583d4beeffdaf491ec8 (patch)
tree80c1da4d8bde8ecef62068f7032acf732e150fbc
parent539167fa2665421a061a129f1650b727150d36a8 (diff)
feat: add WasmTupleChain
Signed-off-by: Henry <mail@henrygressmann.de>
-rw-r--r--crates/tinywasm/src/func.rs184
-rw-r--r--crates/tinywasm/src/instance.rs79
-rw-r--r--crates/tinywasm/src/lib.rs5
3 files changed, 215 insertions, 53 deletions
diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs
index 3c94de2..fa8e755 100644
--- a/crates/tinywasm/src/func.rs
+++ b/crates/tinywasm/src/func.rs
@@ -152,13 +152,13 @@ impl HostFunction {
/// 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 + WasmTypesFromTuple,
- R: IntoWasmValueTuple + WasmTypesFromTuple,
+ P: FromWasmValues + ToWasmTypes,
+ R: IntoWasmValues + ToWasmTypes,
{
let inner_func = move |ctx: FuncContext<'_>, args: &[WasmValue]| -> Result<Vec<WasmValue>> {
- let args = P::from_wasm_value_tuple(args)?;
+ let args = P::from_wasm_values(args)?;
let result = func(ctx, args)?;
- Ok(result.into_wasm_value_tuple())
+ Ok(result.into_wasm_values())
};
let ty = Arc::new(tinywasm_types::FuncType::new(&P::wasm_types(), &R::wasm_types()));
@@ -383,47 +383,45 @@ fn collect_call_results(value_stack: &mut ValueStack, func_ty: &FuncType) -> Res
Ok(res)
}
-pub trait IntoWasmValueTuple {
- fn into_wasm_value_tuple(self) -> Vec<WasmValue>;
+pub trait IntoWasmValues {
+ fn into_wasm_values(self) -> Vec<WasmValue>;
}
-pub trait FromWasmValueTuple {
- fn from_wasm_value_tuple(values: &[WasmValue]) -> Result<Self>
- where
- Self: Sized;
+pub trait FromWasmValues: Sized {
+ fn from_wasm_values(values: &[WasmValue]) -> Result<Self>;
}
-impl<P: IntoWasmValueTuple, R: FromWasmValueTuple> FunctionTyped<P, R> {
+impl<P: IntoWasmValues, R: FromWasmValues> FunctionTyped<P, R> {
/// Call a typed function
pub fn call(&self, store: &mut Store, params: P) -> Result<R> {
// Convert params into Vec<WasmValue>
- let wasm_values = params.into_wasm_value_tuple();
+ let wasm_values = params.into_wasm_values();
// Call the underlying WASM function
let result = self.func.call(store, &wasm_values)?;
// Convert the Vec<WasmValue> back to R
- R::from_wasm_value_tuple(&result)
+ R::from_wasm_values(&result)
}
/// Call a typed function and return a resumable execution handle.
///
/// The handle keeps a mutable borrow of the [`Store`] until completion.
pub fn call_resumable<'store>(&self, store: &'store mut Store, params: P) -> Result<FuncExecutionTyped<'store, R>> {
- let wasm_values = params.into_wasm_value_tuple();
+ let wasm_values = params.into_wasm_values();
let execution = self.func.call_resumable(store, &wasm_values)?;
Ok(FuncExecutionTyped { execution, marker: core::marker::PhantomData })
}
}
-impl<'store, R: FromWasmValueTuple> FuncExecutionTyped<'store, R> {
+impl<'store, R: FromWasmValues> FuncExecutionTyped<'store, R> {
/// Resume typed execution with up to `fuel` units of fuel.
///
/// Fuel is accounted in chunks, so execution may overshoot the requested
/// fuel before returning [`ExecProgress::Suspended`].
pub fn resume_with_fuel(&mut self, fuel: u32) -> Result<ExecProgress<R>> {
match self.execution.resume_with_fuel(fuel)? {
- ExecProgress::Completed(values) => Ok(ExecProgress::Completed(R::from_wasm_value_tuple(&values)?)),
+ ExecProgress::Completed(values) => Ok(ExecProgress::Completed(R::from_wasm_values(&values)?)),
ExecProgress::Suspended => Ok(ExecProgress::Suspended),
}
}
@@ -435,18 +433,22 @@ impl<'store, R: FromWasmValueTuple> FuncExecutionTyped<'store, R> {
/// time budget before returning [`ExecProgress::Suspended`].
pub fn resume_with_time_budget(&mut self, time_budget: crate::std::time::Duration) -> Result<ExecProgress<R>> {
match self.execution.resume_with_time_budget(time_budget)? {
- ExecProgress::Completed(values) => Ok(ExecProgress::Completed(R::from_wasm_value_tuple(&values)?)),
+ ExecProgress::Completed(values) => Ok(ExecProgress::Completed(R::from_wasm_values(&values)?)),
ExecProgress::Suspended => Ok(ExecProgress::Suspended),
}
}
}
-pub trait WasmTypesFromTuple {
+/// Describes the WebAssembly value types produced by a Rust value or tuple shape.
+pub trait ToWasmTypes {
+ /// Return the flattened WebAssembly value types for this tuple shape.
fn wasm_types() -> Box<[WasmType]>;
}
+/// Describes the WebAssembly value types produced by a scalar Rust type.
pub trait ToWasmType {
- fn to_wasm_type() -> WasmType;
+ /// Return the single WebAssembly value type for this scalar type.
+ fn wasm_type() -> WasmType;
}
macro_rules! impl_scalar_wasm_traits {
@@ -454,34 +456,34 @@ macro_rules! impl_scalar_wasm_traits {
$(
impl ToWasmType for $T {
#[inline]
- fn to_wasm_type() -> WasmType {
+ fn wasm_type() -> WasmType {
WasmType::$val_ty
}
}
- impl WasmTypesFromTuple for $T {
+ impl ToWasmTypes for $T {
#[inline]
fn wasm_types() -> Box<[WasmType]> {
Box::new([WasmType::$val_ty])
}
}
- impl IntoWasmValueTuple for $T {
+ impl IntoWasmValues for $T {
#[inline]
- fn into_wasm_value_tuple(self) -> Vec<WasmValue> {
+ fn into_wasm_values(self) -> Vec<WasmValue> {
vec![self.into()]
}
}
- impl FromWasmValueTuple for $T {
+ impl FromWasmValues for $T {
#[inline]
- fn from_wasm_value_tuple(values: &[WasmValue]) -> Result<Self> {
+ fn from_wasm_values(values: &[WasmValue]) -> Result<Self> {
let value = *values
.first()
.ok_or(Error::Other("Not enough values in WasmValue vector".to_string()))?;
<$T>::try_from(value).map_err(|e| {
Error::Other(format!(
- "FromWasmValueTuple: Could not convert WasmValue to expected type: {:?}",
+ "FromWasmValues: Could not convert WasmValue to expected type: {:?}",
e
))
})
@@ -493,34 +495,34 @@ macro_rules! impl_scalar_wasm_traits {
macro_rules! impl_tuple_traits {
($($T:ident),+) => {
- impl<$($T),+> WasmTypesFromTuple for ($($T,)+)
+ impl<$($T),+> ToWasmTypes for ($($T,)+)
where
$($T: ToWasmType,)+
{
#[inline]
fn wasm_types() -> Box<[WasmType]> {
- Box::new([$($T::to_wasm_type(),)+])
+ Box::new([$($T::wasm_type(),)+])
}
}
- impl<$($T),+> IntoWasmValueTuple for ($($T,)+)
+ impl<$($T),+> IntoWasmValues for ($($T,)+)
where
$($T: Into<WasmValue>,)+
{
#[allow(non_snake_case)]
#[inline]
- fn into_wasm_value_tuple(self) -> Vec<WasmValue> {
+ fn into_wasm_values(self) -> Vec<WasmValue> {
let ($($T,)+) = self;
vec![$($T.into(),)+]
}
}
- impl<$($T),+> FromWasmValueTuple for ($($T,)+)
+ impl<$($T),+> FromWasmValues for ($($T,)+)
where
$($T: TryFrom<WasmValue, Error = ()>,)+
{
#[inline]
- fn from_wasm_value_tuple(values: &[WasmValue]) -> Result<Self> {
+ fn from_wasm_values(values: &[WasmValue]) -> Result<Self> {
let mut iter = values.iter();
Ok((
@@ -530,7 +532,7 @@ macro_rules! impl_tuple_traits {
.ok_or(Error::Other("Not enough values in WasmValue vector".to_string()))?
)
.map_err(|e| Error::Other(format!(
- "FromWasmValueTuple: Could not convert WasmValue to expected type: {:?}",
+ "FromWasmValues: Could not convert WasmValue to expected type: {:?}",
e,
)))?,
)+
@@ -554,10 +556,6 @@ macro_rules! impl_tuple {
$macro!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
$macro!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
$macro!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12);
- $macro!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13);
- $macro!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14);
- $macro!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15);
- $macro!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16);
};
}
@@ -571,23 +569,125 @@ impl_scalar_wasm_traits!(
);
impl_tuple!(impl_tuple_traits);
-impl WasmTypesFromTuple for () {
+/// A helper type for using tuples of arbitrary number of elements as function parameters or results,
+/// by concatenating the Wasm types of each element.
+///
+/// This is useful when a function signature exceeds tuple arity 12. `tinywasm` only implements
+/// direct tuple conversions up to arity 12, but `WasmTupleChain` lets you describe longer
+/// signatures by combining smaller tuples at the type level.
+///
+/// ## Example
+/// ```rust
+/// # fn main() -> tinywasm::Result<()> {
+/// # use tinywasm::{ModuleInstance, Store, WasmTupleChain};
+/// # let wasm = wat::parse_str(r#"
+/// # (module
+/// # (func (export "echo13")
+/// # (param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32)
+/// # (result i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32)
+/// # local.get 0
+/// # local.get 1
+/// # local.get 2
+/// # local.get 3
+/// # local.get 4
+/// # local.get 5
+/// # local.get 6
+/// # local.get 7
+/// # local.get 8
+/// # local.get 9
+/// # local.get 10
+/// # local.get 11
+/// # local.get 12)
+/// # )
+/// # "#).expect("valid wat");
+/// # let module = tinywasm::parse_bytes(&wasm)?;
+/// # let mut store = Store::default();
+/// # let instance = ModuleInstance::instantiate(&mut store, &module, None)?;
+///
+/// type Params = WasmTupleChain<
+/// (i32, i32, i32, i32, i32, i32),
+/// (i32, i32, i32, i32, i32, i32, i32),
+/// >;
+/// type Results = WasmTupleChain<
+/// (i32, i32, i32, i32, i32, i32),
+/// (i32, i32, i32, i32, i32, i32, i32),
+/// >;
+///
+/// let echo13 = instance.func::<Params, Results>(&store, "echo13")?;
+/// let result = echo13.call(&mut store, ((1, 2, 3, 4, 5, 6), (7, 8, 9, 10, 11, 12, 13)).into())?;
+/// assert_eq!(result.into_inner(), ((1, 2, 3, 4, 5, 6), (7, 8, 9, 10, 11, 12, 13)));
+/// # Ok(())
+/// # }
+/// ```
+#[derive(Default)]
+pub struct WasmTupleChain<T1, T2>(T1, T2);
+
+impl<T1, T2> WasmTupleChain<T1, T2> {
+ /// Create a new concatenated tuple wrapper.
+ pub const fn new(left: T1, right: T2) -> Self {
+ Self(left, right)
+ }
+
+ /// Split the wrapper back into its two component values.
+ pub fn into_inner(self) -> (T1, T2) {
+ (self.0, self.1)
+ }
+}
+
+impl<T1, T2> From<(T1, T2)> for WasmTupleChain<T1, T2> {
+ fn from((left, right): (T1, T2)) -> Self {
+ Self::new(left, right)
+ }
+}
+
+impl<T1: ToWasmTypes, T2: ToWasmTypes> ToWasmTypes for WasmTupleChain<T1, T2> {
+ #[inline]
+ fn wasm_types() -> Box<[WasmType]> {
+ let mut types = Vec::new();
+ types.extend_from_slice(&T1::wasm_types());
+ types.extend_from_slice(&T2::wasm_types());
+ types.into_boxed_slice()
+ }
+}
+
+impl<T1: IntoWasmValues, T2: IntoWasmValues> IntoWasmValues for WasmTupleChain<T1, T2> {
+ #[inline]
+ fn into_wasm_values(self) -> Vec<WasmValue> {
+ let (left, right) = self.into_inner();
+ let mut values = Vec::new();
+ values.extend(left.into_wasm_values());
+ values.extend(right.into_wasm_values());
+ values
+ }
+}
+
+impl<T1: FromWasmValues + ToWasmTypes, T2: FromWasmValues> FromWasmValues for WasmTupleChain<T1, T2> {
+ #[inline]
+ fn from_wasm_values(values: &[WasmValue]) -> Result<Self> {
+ let left_len = T1::wasm_types().len();
+ let left = T1::from_wasm_values(&values[..values.len().min(left_len)])?;
+ let right = T2::from_wasm_values(values.get(left_len..).unwrap_or(&[]))?;
+ Ok(Self::new(left, right))
+ }
+}
+
+impl ToWasmTypes for () {
#[inline]
fn wasm_types() -> Box<[WasmType]> {
Box::new([])
}
}
-impl IntoWasmValueTuple for () {
+impl IntoWasmValues for () {
#[inline]
- fn into_wasm_value_tuple(self) -> Vec<WasmValue> {
+ fn into_wasm_values(self) -> Vec<WasmValue> {
vec![]
}
}
-impl FromWasmValueTuple for () {
+impl FromWasmValues for () {
#[inline]
- fn from_wasm_value_tuple(_values: &[WasmValue]) -> Result<Self> {
+ fn from_wasm_values(_values: &[WasmValue]) -> Result<Self> {
Ok(())
}
}
diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs
index e5a2d88..b050f0b 100644
--- a/crates/tinywasm/src/instance.rs
+++ b/crates/tinywasm/src/instance.rs
@@ -5,7 +5,7 @@ use alloc::sync::Arc;
use alloc::{format, rc::Rc};
use tinywasm_types::*;
-use crate::func::{FromWasmValueTuple, IntoWasmValueTuple, WasmTypesFromTuple};
+use crate::func::{FromWasmValues, IntoWasmValues, ToWasmTypes};
use crate::{Error, Function, FunctionTyped, Global, Imports, Memory, Result, Store, Table, Trap};
/// A typed view over an exported extern value.
@@ -22,6 +22,22 @@ pub enum ExternItem {
/// An instantiated WebAssembly module
///
+/// Create a `ModuleInstance` by parsing a module, creating a [`Store`], and then calling
+/// [`ModuleInstance::instantiate`].
+///
+/// ## Example
+/// ```rust
+/// # fn main() -> tinywasm::Result<()> {
+/// # use tinywasm::{ModuleInstance, Store};
+/// # let wasm = wat::parse_str("(module)").expect("valid wat");
+/// let module = tinywasm::parse_bytes(&wasm)?;
+/// let mut store = Store::default();
+/// let instance = ModuleInstance::instantiate(&mut store, &module, None)?;
+/// # let _ = instance;
+/// # Ok(())
+/// # }
+/// ```
+///
/// Backed by an Rc, so cloning is cheap
///
/// See <https://webassembly.github.io/spec/core/exec/runtime.html#module-instances>
@@ -305,6 +321,30 @@ impl ModuleInstance {
}
/// Get a function export by name.
+ ///
+ /// ## Example
+ /// ```rust
+ /// # fn main() -> tinywasm::Result<()> {
+ /// # use tinywasm::{ModuleInstance, Store};
+ /// # use tinywasm::types::WasmValue;
+ /// # let wasm = wat::parse_str(r#"
+ /// # (module
+ /// # (func (export "add") (param i32 i32) (result i32)
+ /// # local.get 0
+ /// # local.get 1
+ /// # i32.add))
+ /// # "#).expect("valid wat");
+ /// # let module = tinywasm::parse_bytes(&wasm)?;
+ /// # let mut store = Store::default();
+ /// let instance = ModuleInstance::instantiate(&mut store, &module, None)?;
+ /// let add = instance.func_untyped(&store, "add")?;
+ /// let result = add.call(&mut store, &[WasmValue::I32(20), WasmValue::I32(22)])?;
+ /// assert_eq!(result, vec![WasmValue::I32(42)]);
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// For typed access, see [`Self::func`].
pub fn func_untyped(&self, store: &Store, name: &str) -> Result<Function> {
self.validate_store(store)?;
@@ -346,7 +386,32 @@ impl ModuleInstance {
}
/// Get a typed function export by name.
- pub fn func<P: IntoWasmValueTuple + WasmTypesFromTuple, R: FromWasmValueTuple + WasmTypesFromTuple>(
+ ///
+ /// ## Example
+ /// ```rust
+ /// # fn main() -> tinywasm::Result<()> {
+ /// # use tinywasm::{ModuleInstance, Store};
+ /// # let wasm = wat::parse_str(r#"
+ /// # (module
+ /// # (func (export "add") (param i32 i32) (result i32)
+ /// # local.get 0
+ /// # local.get 1
+ /// # i32.add))
+ /// # "#).expect("valid wat");
+ /// # let module = tinywasm::parse_bytes(&wasm)?;
+ /// # let mut store = Store::default();
+ /// let instance = ModuleInstance::instantiate(&mut store, &module, None)?;
+ /// let add = instance.func::<(i32, i32), i32>(&store, "add")?;
+ /// assert_eq!(add.call(&mut store, (20, 22))?, 42);
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// For untyped access, see [`Self::func_untyped`] and [`Self::extern_item`].
+ ///
+ /// For signatures that exceed tuple arity 12, see [`crate::WasmTupleChain`], which can be used
+ /// directly as `instance.func::<crate::WasmTupleChain<_, _>, _>(...)`.
+ pub fn func<P: IntoWasmValues + ToWasmTypes, R: FromWasmValues + ToWasmTypes>(
&self,
store: &Store,
name: &str,
@@ -359,10 +424,7 @@ impl ModuleInstance {
/// Get a typed function by its module-local index.
#[cfg_attr(docsrs, doc(cfg(feature = "guest-debug")))]
#[cfg(feature = "guest-debug")]
- pub fn func_typed_by_index<
- P: IntoWasmValueTuple + WasmTypesFromTuple,
- R: FromWasmValueTuple + WasmTypesFromTuple,
- >(
+ pub fn func_typed_by_index<P: IntoWasmValues + ToWasmTypes, R: FromWasmValues + ToWasmTypes>(
&self,
store: &Store,
func_index: FuncAddr,
@@ -372,10 +434,7 @@ impl ModuleInstance {
Ok(FunctionTyped { func, marker: core::marker::PhantomData })
}
- fn validate_typed_func<P: WasmTypesFromTuple, R: WasmTypesFromTuple>(
- func: &Function,
- func_name: &str,
- ) -> Result<()> {
+ fn validate_typed_func<P: ToWasmTypes, R: ToWasmTypes>(func: &Function, func_name: &str) -> Result<()> {
if *func.ty.params() != *P::wasm_types() || *func.ty.results() != *R::wasm_types() {
cold_path();
diff --git a/crates/tinywasm/src/lib.rs b/crates/tinywasm/src/lib.rs
index 4b6b360..4e9f828 100644
--- a/crates/tinywasm/src/lib.rs
+++ b/crates/tinywasm/src/lib.rs
@@ -107,7 +107,10 @@ pub(crate) mod log {
mod error;
pub use error::*;
-pub use func::{ExecProgress, FuncContext, FuncExecution, FuncExecutionTyped, Function, FunctionTyped, HostFunction};
+pub use func::{
+ ExecProgress, FuncContext, FuncExecution, FuncExecutionTyped, Function, FunctionTyped, HostFunction, ToWasmTypes,
+ WasmTupleChain,
+};
pub use imports::*;
pub use instance::{ExternItem, ModuleInstance};
pub use reference::*;