unsplat component::Linker::func_wrap args (#5065)

* component::Linker::func_wrap: replace IntoComponentFunc with directly accepting a closure

We find that this makes the Linker::func_wrap type signature much easier
to read. The IntoComponentFunc abstraction was adding a lot of weight to
"splat" a set of arguments from a tuple of types into individual
arguments to the closure. Additionally, making the StoreContextMut
argument optional, or the Result<return> optional, wasn't very
worthwhile.

* Fixes for the new style of closure required by component::Linker::func_wrap

* fix fuzzing generator
This commit is contained in:
Pat Hickey
2022-10-18 07:24:14 -07:00
committed by GitHub
parent 32a7593c94
commit 78ecc17d0f
10 changed files with 107 additions and 176 deletions

View File

@@ -158,7 +158,7 @@ macro_rules! define_static_api_test {
.func_wrap( .func_wrap(
IMPORT_FUNCTION, IMPORT_FUNCTION,
|cx: StoreContextMut<'_, Box<dyn Any>>, |cx: StoreContextMut<'_, Box<dyn Any>>,
$($param_name: $param,)*| ($($param_name,)*): ($($param,)*)|
{ {
log::trace!("received parameters {:?}", ($(&$param_name,)*)); log::trace!("received parameters {:?}", ($(&$param_name,)*));
let data: &($($param,)* R,) = let data: &($($param,)* R,) =

View File

@@ -17,18 +17,28 @@ use wasmtime_runtime::component::{
}; };
use wasmtime_runtime::{VMCallerCheckedAnyfunc, VMMemoryDefinition, VMOpaqueContext}; use wasmtime_runtime::{VMCallerCheckedAnyfunc, VMMemoryDefinition, VMOpaqueContext};
/// Trait representing host-defined functions that can be imported into a wasm pub struct HostFunc {
/// component. entrypoint: VMLoweringCallee,
/// typecheck: Box<dyn (Fn(TypeFuncIndex, &Arc<ComponentTypes>) -> Result<()>) + Send + Sync>,
/// For more information see the func: Box<dyn Any + Send + Sync>,
/// [`func_wrap`](crate::component::LinkerInstance::func_wrap) documentation. }
pub trait IntoComponentFunc<T, Params, Return> {
/// Host entrypoint from a cranelift-generated trampoline. impl HostFunc {
/// pub(crate) fn from_closure<T, F, P, R>(func: F) -> Arc<HostFunc>
/// This function has type `VMLoweringCallee` and delegates to the shared where
/// `call_host` function below. F: Fn(StoreContextMut<T>, P) -> Result<R> + Send + Sync + 'static,
#[doc(hidden)] P: ComponentNamedList + Lift + 'static,
extern "C" fn entrypoint( R: ComponentNamedList + Lower + 'static,
{
let entrypoint = Self::entrypoint::<T, F, P, R>;
Arc::new(HostFunc {
entrypoint,
typecheck: Box::new(typecheck::<P, R>),
func: Box::new(func),
})
}
extern "C" fn entrypoint<T, F, P, R>(
cx: *mut VMOpaqueContext, cx: *mut VMOpaqueContext,
data: *mut u8, data: *mut u8,
flags: InstanceFlags, flags: InstanceFlags,
@@ -37,30 +47,25 @@ pub trait IntoComponentFunc<T, Params, Return> {
string_encoding: StringEncoding, string_encoding: StringEncoding,
storage: *mut ValRaw, storage: *mut ValRaw,
storage_len: usize, storage_len: usize,
); ) where
F: Fn(StoreContextMut<T>, P) -> Result<R>,
#[doc(hidden)]
fn into_host_func(self) -> Arc<HostFunc>;
}
pub struct HostFunc {
entrypoint: VMLoweringCallee,
typecheck: Box<dyn (Fn(TypeFuncIndex, &Arc<ComponentTypes>) -> Result<()>) + Send + Sync>,
func: Box<dyn Any + Send + Sync>,
}
impl HostFunc {
fn new<F, P, R>(func: F, entrypoint: VMLoweringCallee) -> Arc<HostFunc>
where
F: Send + Sync + 'static,
P: ComponentNamedList + Lift + 'static, P: ComponentNamedList + Lift + 'static,
R: ComponentNamedList + Lower + 'static, R: ComponentNamedList + Lower + 'static,
{ {
Arc::new(HostFunc { let data = data as *const F;
entrypoint, unsafe {
typecheck: Box::new(typecheck::<P, R>), handle_result(|| {
func: Box::new(func), call_host::<_, _, _, _>(
}) cx,
flags,
memory,
realloc,
string_encoding,
std::slice::from_raw_parts_mut(storage, storage_len),
|store, args| (*data)(store, args),
)
})
}
} }
pub(crate) fn new_dynamic<T, F>( pub(crate) fn new_dynamic<T, F>(
@@ -122,10 +127,10 @@ where
/// The "meat" of calling a host function from wasm. /// The "meat" of calling a host function from wasm.
/// ///
/// This function is delegated to from implementations of `IntoComponentFunc` /// This function is delegated to from implementations of
/// generated in the macro below. Most of the arguments from the `entrypoint` /// `HostFunc::from_closure`. Most of the arguments from the `entrypoint` are
/// are forwarded here except for the `data` pointer which is encapsulated in /// forwarded here except for the `data` pointer which is encapsulated in the
/// the `closure` argument here. /// `closure` argument here.
/// ///
/// This function is parameterized over: /// This function is parameterized over:
/// ///
@@ -270,88 +275,6 @@ unsafe fn handle_result(func: impl FnOnce() -> Result<()>) {
} }
} }
macro_rules! impl_into_component_func {
($num:tt $($args:ident)*) => {
// Implement for functions without a leading `StoreContextMut` parameter
#[allow(non_snake_case)]
impl<T, F, $($args,)* R> IntoComponentFunc<T, ($($args,)*), R> for F
where
F: Fn($($args),*) -> Result<R> + Send + Sync + 'static,
($($args,)*): ComponentNamedList + Lift + 'static,
R: ComponentNamedList + Lower + 'static,
{
extern "C" fn entrypoint(
cx: *mut VMOpaqueContext,
data: *mut u8,
flags: InstanceFlags,
memory: *mut VMMemoryDefinition,
realloc: *mut VMCallerCheckedAnyfunc,
string_encoding: StringEncoding,
storage: *mut ValRaw,
storage_len: usize,
) {
let data = data as *const Self;
unsafe {
handle_result(|| call_host::<T, _, _, _>(
cx,
flags,
memory,
realloc,
string_encoding,
std::slice::from_raw_parts_mut(storage, storage_len),
|_, ($($args,)*)| (*data)($($args),*),
))
}
}
fn into_host_func(self) -> Arc<HostFunc> {
let entrypoint = <Self as IntoComponentFunc<T, ($($args,)*), R>>::entrypoint;
HostFunc::new::<_, ($($args,)*), R>(self, entrypoint)
}
}
// Implement for functions with a leading `StoreContextMut` parameter
#[allow(non_snake_case)]
impl<T, F, $($args,)* R> IntoComponentFunc<T, (StoreContextMut<'_, T>, $($args,)*), R> for F
where
F: Fn(StoreContextMut<'_, T>, $($args),*) -> Result<R> + Send + Sync + 'static,
($($args,)*): ComponentNamedList + Lift + 'static,
R: ComponentNamedList + Lower + 'static,
{
extern "C" fn entrypoint(
cx: *mut VMOpaqueContext,
data: *mut u8,
flags: InstanceFlags,
memory: *mut VMMemoryDefinition,
realloc: *mut VMCallerCheckedAnyfunc,
string_encoding: StringEncoding,
storage: *mut ValRaw,
storage_len: usize,
) {
let data = data as *const Self;
unsafe {
handle_result(|| call_host::<T, _, _, _>(
cx,
flags,
memory,
realloc,
string_encoding,
std::slice::from_raw_parts_mut(storage, storage_len),
|store, ($($args,)*)| (*data)(store, $($args),*),
))
}
}
fn into_host_func(self) -> Arc<HostFunc> {
let entrypoint = <Self as IntoComponentFunc<T, (StoreContextMut<'_, T>, $($args,)*), R>>::entrypoint;
HostFunc::new::<_, ($($args,)*), R>(self, entrypoint)
}
}
}
}
for_each_function_signature!(impl_into_component_func);
unsafe fn call_host_dynamic<T, F>( unsafe fn call_host_dynamic<T, F>(
Types { params, results }: &Types, Types { params, results }: &Types,
cx: *mut VMOpaqueContext, cx: *mut VMOpaqueContext,

View File

@@ -1,7 +1,7 @@
use crate::component::func::HostFunc; use crate::component::func::HostFunc;
use crate::component::instance::RuntimeImport; use crate::component::instance::RuntimeImport;
use crate::component::matching::TypeChecker; use crate::component::matching::TypeChecker;
use crate::component::{Component, Instance, InstancePre, IntoComponentFunc, Val}; use crate::component::{Component, ComponentNamedList, Instance, InstancePre, Lift, Lower, Val};
use crate::{AsContextMut, Engine, Module, StoreContextMut}; use crate::{AsContextMut, Engine, Module, StoreContextMut};
use anyhow::{anyhow, bail, Context, Result}; use anyhow::{anyhow, bail, Context, Result};
use std::collections::hash_map::{Entry, HashMap}; use std::collections::hash_map::{Entry, HashMap};
@@ -209,11 +209,8 @@ impl<T> LinkerInstance<'_, T> {
/// types that will come from wasm and `Return` is a value coming from the /// types that will come from wasm and `Return` is a value coming from the
/// host going back to wasm. /// host going back to wasm.
/// ///
/// The [`IntoComponentFunc`] trait is implemented for functions whose /// Additionally the `func` takes a
/// arguments and return values implement the /// [`StoreContextMut`](crate::StoreContextMut) as its first parameter.
/// [`ComponentType`](crate::component::ComponentType) trait. Additionally
/// the `func` may take a [`StoreContextMut`](crate::StoreContextMut) as its
/// first parameter.
/// ///
/// Note that `func` must be an `Fn` and must also be `Send + Sync + /// Note that `func` must be an `Fn` and must also be `Send + Sync +
/// 'static`. Shared state within a func is typically accessed with the `T` /// 'static`. Shared state within a func is typically accessed with the `T`
@@ -222,13 +219,14 @@ impl<T> LinkerInstance<'_, T> {
/// argument which can be provided to the `func` given here. /// argument which can be provided to the `func` given here.
// //
// TODO: needs more words and examples // TODO: needs more words and examples
pub fn func_wrap<Params, Return>( pub fn func_wrap<F, Params, Return>(&mut self, name: &str, func: F) -> Result<()>
&mut self, where
name: &str, F: Fn(StoreContextMut<T>, Params) -> Result<Return> + Send + Sync + 'static,
func: impl IntoComponentFunc<T, Params, Return>, Params: ComponentNamedList + Lift + 'static,
) -> Result<()> { Return: ComponentNamedList + Lower + 'static,
{
let name = self.strings.intern(name); let name = self.strings.intern(name);
self.insert(name, Definition::Func(func.into_host_func())) self.insert(name, Definition::Func(HostFunc::from_closure(func)))
} }
/// Define a new host-provided function using dynamic types. /// Define a new host-provided function using dynamic types.

View File

@@ -14,8 +14,7 @@ pub mod types;
mod values; mod values;
pub use self::component::Component; pub use self::component::Component;
pub use self::func::{ pub use self::func::{
ComponentNamedList, ComponentType, Func, IntoComponentFunc, Lift, Lower, TypedFunc, WasmList, ComponentNamedList, ComponentType, Func, Lift, Lower, TypedFunc, WasmList, WasmStr,
WasmStr,
}; };
pub use self::instance::{ExportInstance, Exports, Instance, InstancePre}; pub use self::instance::{ExportInstance, Exports, Instance, InstancePre};
pub use self::linker::{Linker, LinkerInstance}; pub use self::linker::{Linker, LinkerInstance};

View File

@@ -48,11 +48,13 @@ pub fn link_spectest<T>(linker: &mut Linker<T>, store: &mut Store<T>) -> Result<
#[cfg(feature = "component-model")] #[cfg(feature = "component-model")]
pub fn link_component_spectest<T>(linker: &mut component::Linker<T>) -> Result<()> { pub fn link_component_spectest<T>(linker: &mut component::Linker<T>) -> Result<()> {
let engine = linker.engine().clone(); let engine = linker.engine().clone();
linker.root().func_wrap("host-return-two", || Ok((2u32,)))?; linker
.root()
.func_wrap("host-return-two", |_, _: ()| Ok((2u32,)))?;
let mut i = linker.instance("host")?; let mut i = linker.instance("host")?;
i.func_wrap("return-three", || Ok((3u32,)))?; i.func_wrap("return-three", |_, _: ()| Ok((3u32,)))?;
i.instance("nested")? i.instance("nested")?
.func_wrap("return-four", || Ok((4u32,)))?; .func_wrap("return-four", |_, _: ()| Ok((4u32,)))?;
let module = Module::new( let module = Module::new(
&engine, &engine,

View File

@@ -2004,12 +2004,13 @@ fn drop_component_still_works() -> Result<()> {
let component = Component::new(&engine, component)?; let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, 0); let mut store = Store::new(&engine, 0);
let mut linker = Linker::new(&engine); let mut linker = Linker::new(&engine);
linker linker.root().func_wrap(
.root() "f",
.func_wrap("f", |mut store: StoreContextMut<'_, u32>| -> Result<()> { |mut store: StoreContextMut<'_, u32>, _: ()| -> Result<()> {
*store.data_mut() += 1; *store.data_mut() += 1;
Ok(()) Ok(())
})?; },
)?;
let instance = linker.instantiate(&mut store, &component)?; let instance = linker.instantiate(&mut store, &component)?;
(store, instance) (store, instance)
}; };
@@ -2216,7 +2217,7 @@ fn lower_then_lift() -> Result<()> {
let component = Component::new(&engine, component)?; let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ()); let mut store = Store::new(&engine, ());
let mut linker = Linker::new(&engine); let mut linker = Linker::new(&engine);
linker.root().func_wrap("f", || Ok((2u32,)))?; linker.root().func_wrap("f", |_, _: ()| Ok((2u32,)))?;
let instance = linker.instantiate(&mut store, &component)?; let instance = linker.instantiate(&mut store, &component)?;
let f = instance.get_typed_func::<(), (i32,), _>(&mut store, "f")?; let f = instance.get_typed_func::<(), (i32,), _>(&mut store, "f")?;
@@ -2252,7 +2253,7 @@ fn lower_then_lift() -> Result<()> {
let mut store = Store::new(&engine, ()); let mut store = Store::new(&engine, ());
linker linker
.root() .root()
.func_wrap("s", |store: StoreContextMut<'_, ()>, x: WasmStr| { .func_wrap("s", |store: StoreContextMut<'_, ()>, (x,): (WasmStr,)| {
assert_eq!(x.to_str(&store)?, "hello"); assert_eq!(x.to_str(&store)?, "hello");
Ok(()) Ok(())
})?; })?;
@@ -2292,7 +2293,7 @@ fn lower_then_lift() -> Result<()> {
let mut store = Store::new(&engine, ()); let mut store = Store::new(&engine, ());
linker linker
.root() .root()
.func_wrap("s2", |store: StoreContextMut<'_, ()>, x: WasmStr| { .func_wrap("s2", |store: StoreContextMut<'_, ()>, (x,): (WasmStr,)| {
assert_eq!(x.to_str(&store)?, "hello"); assert_eq!(x.to_str(&store)?, "hello");
Ok((u32::MAX,)) Ok((u32::MAX,))
})?; })?;

View File

@@ -127,7 +127,7 @@ fn simple() -> Result<()> {
let mut linker = Linker::new(&engine); let mut linker = Linker::new(&engine);
linker.root().func_wrap( linker.root().func_wrap(
"", "",
|mut store: StoreContextMut<'_, Option<String>>, arg: WasmStr| -> Result<_> { |mut store: StoreContextMut<'_, Option<String>>, (arg,): (WasmStr,)| -> Result<_> {
let s = arg.to_str(&store)?.to_string(); let s = arg.to_str(&store)?.to_string();
assert!(store.data().is_none()); assert!(store.data().is_none());
*store.data_mut() = Some(s); *store.data_mut() = Some(s);
@@ -239,12 +239,14 @@ fn attempt_to_leave_during_malloc() -> Result<()> {
let engine = super::engine(); let engine = super::engine();
let mut linker = Linker::new(&engine); let mut linker = Linker::new(&engine);
linker.root().func_wrap("thunk", |_, _: ()| -> Result<()> {
panic!("should not get here")
})?;
linker linker
.root() .root()
.func_wrap("thunk", || -> Result<()> { panic!("should not get here") })?; .func_wrap("ret-string", |_, _: ()| -> Result<_> {
linker Ok(("hello".to_string(),))
.root() })?;
.func_wrap("ret-string", || -> Result<_> { Ok(("hello".to_string(),)) })?;
let component = Component::new(&engine, component)?; let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ()); let mut store = Store::new(&engine, ());
@@ -338,7 +340,7 @@ fn attempt_to_reenter_during_host() -> Result<()> {
let mut linker = Linker::new(&engine); let mut linker = Linker::new(&engine);
linker.root().func_wrap( linker.root().func_wrap(
"thunk", "thunk",
|mut store: StoreContextMut<'_, StaticState>| -> Result<()> { |mut store: StoreContextMut<'_, StaticState>, _: ()| -> Result<()> {
let func = store.data_mut().func.take().unwrap(); let func = store.data_mut().func.take().unwrap();
let trap = func.call(&mut store, ()).unwrap_err(); let trap = func.call(&mut store, ()).unwrap_err();
assert!( assert!(
@@ -530,14 +532,16 @@ fn stack_and_heap_args_and_rets() -> Result<()> {
// First, test the static API // First, test the static API
let mut linker = Linker::new(&engine); let mut linker = Linker::new(&engine);
linker.root().func_wrap("f1", |x: u32| -> Result<(u32,)> { linker
assert_eq!(x, 1); .root()
Ok((2,)) .func_wrap("f1", |_, (x,): (u32,)| -> Result<(u32,)> {
})?; assert_eq!(x, 1);
Ok((2,))
})?;
linker.root().func_wrap( linker.root().func_wrap(
"f2", "f2",
|cx: StoreContextMut<'_, ()>, |cx: StoreContextMut<'_, ()>,
arg: ( (arg,): ((
WasmStr, WasmStr,
WasmStr, WasmStr,
WasmStr, WasmStr,
@@ -547,7 +551,7 @@ fn stack_and_heap_args_and_rets() -> Result<()> {
WasmStr, WasmStr,
WasmStr, WasmStr,
WasmStr, WasmStr,
)| ),)|
-> Result<(u32,)> { -> Result<(u32,)> {
assert_eq!(arg.0.to_str(&cx).unwrap(), "abc"); assert_eq!(arg.0.to_str(&cx).unwrap(), "abc");
Ok((3,)) Ok((3,))
@@ -555,14 +559,14 @@ fn stack_and_heap_args_and_rets() -> Result<()> {
)?; )?;
linker linker
.root() .root()
.func_wrap("f3", |arg: u32| -> Result<(String,)> { .func_wrap("f3", |_, (arg,): (u32,)| -> Result<(String,)> {
assert_eq!(arg, 8); assert_eq!(arg, 8);
Ok(("xyz".to_string(),)) Ok(("xyz".to_string(),))
})?; })?;
linker.root().func_wrap( linker.root().func_wrap(
"f4", "f4",
|cx: StoreContextMut<'_, ()>, |cx: StoreContextMut<'_, ()>,
arg: ( (arg,): ((
WasmStr, WasmStr,
WasmStr, WasmStr,
WasmStr, WasmStr,
@@ -572,7 +576,7 @@ fn stack_and_heap_args_and_rets() -> Result<()> {
WasmStr, WasmStr,
WasmStr, WasmStr,
WasmStr, WasmStr,
)| ),)|
-> Result<(String,)> { -> Result<(String,)> {
assert_eq!(arg.0.to_str(&cx).unwrap(), "abc"); assert_eq!(arg.0.to_str(&cx).unwrap(), "abc");
Ok(("xyz".to_string(),)) Ok(("xyz".to_string(),))
@@ -703,12 +707,13 @@ fn bad_import_alignment() -> Result<()> {
let mut linker = Linker::new(&engine); let mut linker = Linker::new(&engine);
linker linker
.root() .root()
.func_wrap("unaligned-retptr", || -> Result<(String,)> { .func_wrap("unaligned-retptr", |_, _: ()| -> Result<(String,)> {
Ok((String::new(),)) Ok((String::new(),))
})?; })?;
linker.root().func_wrap( linker.root().func_wrap(
"unaligned-argptr", "unaligned-argptr",
|_: ( |_,
_: ((
WasmStr, WasmStr,
WasmStr, WasmStr,
WasmStr, WasmStr,
@@ -718,7 +723,7 @@ fn bad_import_alignment() -> Result<()> {
WasmStr, WasmStr,
WasmStr, WasmStr,
WasmStr, WasmStr,
)| ),)|
-> Result<()> { unreachable!() }, -> Result<()> { unreachable!() },
)?; )?;
let component = Component::new(&engine, component)?; let component = Component::new(&engine, component)?;
@@ -775,12 +780,13 @@ fn no_actual_wasm_code() -> Result<()> {
// First, test the static API // First, test the static API
let mut linker = Linker::new(&engine); let mut linker = Linker::new(&engine);
linker linker.root().func_wrap(
.root() "f",
.func_wrap("f", |mut store: StoreContextMut<'_, u32>| -> Result<()> { |mut store: StoreContextMut<'_, u32>, _: ()| -> Result<()> {
*store.data_mut() += 1; *store.data_mut() += 1;
Ok(()) Ok(())
})?; },
)?;
let instance = linker.instantiate(&mut store, &component)?; let instance = linker.instantiate(&mut store, &component)?;
let thunk = instance.get_typed_func::<(), (), _>(&mut store, "thunk")?; let thunk = instance.get_typed_func::<(), (), _>(&mut store, "thunk")?;

View File

@@ -95,7 +95,7 @@ fn nested_many_instantiations() -> Result<()> {
let mut linker = Linker::new(&engine); let mut linker = Linker::new(&engine);
linker linker
.root() .root()
.func_wrap("count", |mut store: StoreContextMut<'_, u32>| { .func_wrap("count", |mut store: StoreContextMut<'_, u32>, _: ()| {
*store.data_mut() += 1; *store.data_mut() += 1;
Ok(()) Ok(())
})?; })?;
@@ -162,7 +162,7 @@ fn thread_options_through_inner() -> Result<()> {
let mut linker = Linker::new(&engine); let mut linker = Linker::new(&engine);
linker linker
.root() .root()
.func_wrap("hostfn", |param: u32| Ok((param.to_string(),)))?; .func_wrap("hostfn", |_, (param,): (u32,)| Ok((param.to_string(),)))?;
let instance = linker.instantiate(&mut store, &component)?; let instance = linker.instantiate(&mut store, &component)?;
let result = instance let result = instance
.get_typed_func::<(u32,), (WasmStr,), _>(&mut store, "run")? .get_typed_func::<(u32,), (WasmStr,), _>(&mut store, "run")?

View File

@@ -120,13 +120,14 @@ fn invoke_post_return() -> Result<()> {
let component = Component::new(&engine, component)?; let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, false); let mut store = Store::new(&engine, false);
let mut linker = Linker::new(&engine); let mut linker = Linker::new(&engine);
linker linker.root().func_wrap(
.root() "f",
.func_wrap("f", |mut store: StoreContextMut<'_, bool>| -> Result<()> { |mut store: StoreContextMut<'_, bool>, _: ()| -> Result<()> {
assert!(!*store.data()); assert!(!*store.data());
*store.data_mut() = true; *store.data_mut() = true;
Ok(()) Ok(())
})?; },
)?;
let instance = linker.instantiate(&mut store, &component)?; let instance = linker.instantiate(&mut store, &component)?;
let thunk = instance.get_typed_func::<(), (), _>(&mut store, "thunk")?; let thunk = instance.get_typed_func::<(), (), _>(&mut store, "thunk")?;

View File

@@ -171,12 +171,13 @@ fn test_roundtrip(engine: &Engine, src: &str, dst: &str) -> Result<()> {
let component = Component::new(engine, &component)?; let component = Component::new(engine, &component)?;
let mut store = Store::new(engine, String::new()); let mut store = Store::new(engine, String::new());
let mut linker = Linker::new(engine); let mut linker = Linker::new(engine);
linker linker.root().func_wrap(
.root() "host",
.func_wrap("host", |store: StoreContextMut<String>, arg: String| { |store: StoreContextMut<String>, (arg,): (String,)| {
assert_eq!(*store.data(), arg); assert_eq!(*store.data(), arg);
Ok((arg,)) Ok((arg,))
})?; },
)?;
let instance = linker.instantiate(&mut store, &component)?; let instance = linker.instantiate(&mut store, &component)?;
let func = instance.get_typed_func::<(String,), (String,), _>(&mut store, "echo")?; let func = instance.get_typed_func::<(String,), (String,), _>(&mut store, "echo")?;