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:
@@ -158,7 +158,7 @@ macro_rules! define_static_api_test {
|
||||
.func_wrap(
|
||||
IMPORT_FUNCTION,
|
||||
|cx: StoreContextMut<'_, Box<dyn Any>>,
|
||||
$($param_name: $param,)*|
|
||||
($($param_name,)*): ($($param,)*)|
|
||||
{
|
||||
log::trace!("received parameters {:?}", ($(&$param_name,)*));
|
||||
let data: &($($param,)* R,) =
|
||||
|
||||
@@ -17,18 +17,28 @@ use wasmtime_runtime::component::{
|
||||
};
|
||||
use wasmtime_runtime::{VMCallerCheckedAnyfunc, VMMemoryDefinition, VMOpaqueContext};
|
||||
|
||||
/// Trait representing host-defined functions that can be imported into a wasm
|
||||
/// component.
|
||||
///
|
||||
/// For more information see the
|
||||
/// [`func_wrap`](crate::component::LinkerInstance::func_wrap) documentation.
|
||||
pub trait IntoComponentFunc<T, Params, Return> {
|
||||
/// Host entrypoint from a cranelift-generated trampoline.
|
||||
///
|
||||
/// This function has type `VMLoweringCallee` and delegates to the shared
|
||||
/// `call_host` function below.
|
||||
#[doc(hidden)]
|
||||
extern "C" fn entrypoint(
|
||||
pub struct HostFunc {
|
||||
entrypoint: VMLoweringCallee,
|
||||
typecheck: Box<dyn (Fn(TypeFuncIndex, &Arc<ComponentTypes>) -> Result<()>) + Send + Sync>,
|
||||
func: Box<dyn Any + Send + Sync>,
|
||||
}
|
||||
|
||||
impl HostFunc {
|
||||
pub(crate) fn from_closure<T, F, P, R>(func: F) -> Arc<HostFunc>
|
||||
where
|
||||
F: Fn(StoreContextMut<T>, P) -> Result<R> + Send + Sync + 'static,
|
||||
P: ComponentNamedList + Lift + 'static,
|
||||
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,
|
||||
data: *mut u8,
|
||||
flags: InstanceFlags,
|
||||
@@ -37,31 +47,26 @@ pub trait IntoComponentFunc<T, Params, Return> {
|
||||
string_encoding: StringEncoding,
|
||||
storage: *mut ValRaw,
|
||||
storage_len: usize,
|
||||
);
|
||||
|
||||
#[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,
|
||||
) where
|
||||
F: Fn(StoreContextMut<T>, P) -> Result<R>,
|
||||
P: ComponentNamedList + Lift + 'static,
|
||||
R: ComponentNamedList + Lower + 'static,
|
||||
{
|
||||
Arc::new(HostFunc {
|
||||
entrypoint,
|
||||
typecheck: Box::new(typecheck::<P, R>),
|
||||
func: Box::new(func),
|
||||
let data = data as *const F;
|
||||
unsafe {
|
||||
handle_result(|| {
|
||||
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>(
|
||||
func: F,
|
||||
@@ -122,10 +127,10 @@ where
|
||||
|
||||
/// The "meat" of calling a host function from wasm.
|
||||
///
|
||||
/// This function is delegated to from implementations of `IntoComponentFunc`
|
||||
/// generated in the macro below. Most of the arguments from the `entrypoint`
|
||||
/// are forwarded here except for the `data` pointer which is encapsulated in
|
||||
/// the `closure` argument here.
|
||||
/// This function is delegated to from implementations of
|
||||
/// `HostFunc::from_closure`. Most of the arguments from the `entrypoint` are
|
||||
/// forwarded here except for the `data` pointer which is encapsulated in the
|
||||
/// `closure` argument here.
|
||||
///
|
||||
/// 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>(
|
||||
Types { params, results }: &Types,
|
||||
cx: *mut VMOpaqueContext,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::component::func::HostFunc;
|
||||
use crate::component::instance::RuntimeImport;
|
||||
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 anyhow::{anyhow, bail, Context, Result};
|
||||
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
|
||||
/// host going back to wasm.
|
||||
///
|
||||
/// The [`IntoComponentFunc`] trait is implemented for functions whose
|
||||
/// arguments and return values implement the
|
||||
/// [`ComponentType`](crate::component::ComponentType) trait. Additionally
|
||||
/// the `func` may take a [`StoreContextMut`](crate::StoreContextMut) as its
|
||||
/// first parameter.
|
||||
/// Additionally the `func` takes a
|
||||
/// [`StoreContextMut`](crate::StoreContextMut) as its first parameter.
|
||||
///
|
||||
/// 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`
|
||||
@@ -222,13 +219,14 @@ impl<T> LinkerInstance<'_, T> {
|
||||
/// argument which can be provided to the `func` given here.
|
||||
//
|
||||
// TODO: needs more words and examples
|
||||
pub fn func_wrap<Params, Return>(
|
||||
&mut self,
|
||||
name: &str,
|
||||
func: impl IntoComponentFunc<T, Params, Return>,
|
||||
) -> Result<()> {
|
||||
pub fn func_wrap<F, Params, Return>(&mut self, name: &str, func: F) -> Result<()>
|
||||
where
|
||||
F: Fn(StoreContextMut<T>, Params) -> Result<Return> + Send + Sync + 'static,
|
||||
Params: ComponentNamedList + Lift + 'static,
|
||||
Return: ComponentNamedList + Lower + 'static,
|
||||
{
|
||||
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.
|
||||
|
||||
@@ -14,8 +14,7 @@ pub mod types;
|
||||
mod values;
|
||||
pub use self::component::Component;
|
||||
pub use self::func::{
|
||||
ComponentNamedList, ComponentType, Func, IntoComponentFunc, Lift, Lower, TypedFunc, WasmList,
|
||||
WasmStr,
|
||||
ComponentNamedList, ComponentType, Func, Lift, Lower, TypedFunc, WasmList, WasmStr,
|
||||
};
|
||||
pub use self::instance::{ExportInstance, Exports, Instance, InstancePre};
|
||||
pub use self::linker::{Linker, LinkerInstance};
|
||||
|
||||
@@ -48,11 +48,13 @@ pub fn link_spectest<T>(linker: &mut Linker<T>, store: &mut Store<T>) -> Result<
|
||||
#[cfg(feature = "component-model")]
|
||||
pub fn link_component_spectest<T>(linker: &mut component::Linker<T>) -> Result<()> {
|
||||
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")?;
|
||||
i.func_wrap("return-three", || Ok((3u32,)))?;
|
||||
i.func_wrap("return-three", |_, _: ()| Ok((3u32,)))?;
|
||||
i.instance("nested")?
|
||||
.func_wrap("return-four", || Ok((4u32,)))?;
|
||||
.func_wrap("return-four", |_, _: ()| Ok((4u32,)))?;
|
||||
|
||||
let module = Module::new(
|
||||
&engine,
|
||||
|
||||
@@ -2004,12 +2004,13 @@ fn drop_component_still_works() -> Result<()> {
|
||||
let component = Component::new(&engine, component)?;
|
||||
let mut store = Store::new(&engine, 0);
|
||||
let mut linker = Linker::new(&engine);
|
||||
linker
|
||||
.root()
|
||||
.func_wrap("f", |mut store: StoreContextMut<'_, u32>| -> Result<()> {
|
||||
linker.root().func_wrap(
|
||||
"f",
|
||||
|mut store: StoreContextMut<'_, u32>, _: ()| -> Result<()> {
|
||||
*store.data_mut() += 1;
|
||||
Ok(())
|
||||
})?;
|
||||
},
|
||||
)?;
|
||||
let instance = linker.instantiate(&mut store, &component)?;
|
||||
(store, instance)
|
||||
};
|
||||
@@ -2216,7 +2217,7 @@ fn lower_then_lift() -> Result<()> {
|
||||
let component = Component::new(&engine, component)?;
|
||||
let mut store = Store::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 f = instance.get_typed_func::<(), (i32,), _>(&mut store, "f")?;
|
||||
@@ -2252,7 +2253,7 @@ fn lower_then_lift() -> Result<()> {
|
||||
let mut store = Store::new(&engine, ());
|
||||
linker
|
||||
.root()
|
||||
.func_wrap("s", |store: StoreContextMut<'_, ()>, x: WasmStr| {
|
||||
.func_wrap("s", |store: StoreContextMut<'_, ()>, (x,): (WasmStr,)| {
|
||||
assert_eq!(x.to_str(&store)?, "hello");
|
||||
Ok(())
|
||||
})?;
|
||||
@@ -2292,7 +2293,7 @@ fn lower_then_lift() -> Result<()> {
|
||||
let mut store = Store::new(&engine, ());
|
||||
linker
|
||||
.root()
|
||||
.func_wrap("s2", |store: StoreContextMut<'_, ()>, x: WasmStr| {
|
||||
.func_wrap("s2", |store: StoreContextMut<'_, ()>, (x,): (WasmStr,)| {
|
||||
assert_eq!(x.to_str(&store)?, "hello");
|
||||
Ok((u32::MAX,))
|
||||
})?;
|
||||
|
||||
@@ -127,7 +127,7 @@ fn simple() -> Result<()> {
|
||||
let mut linker = Linker::new(&engine);
|
||||
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();
|
||||
assert!(store.data().is_none());
|
||||
*store.data_mut() = Some(s);
|
||||
@@ -239,12 +239,14 @@ fn attempt_to_leave_during_malloc() -> Result<()> {
|
||||
|
||||
let engine = super::engine();
|
||||
let mut linker = Linker::new(&engine);
|
||||
linker.root().func_wrap("thunk", |_, _: ()| -> Result<()> {
|
||||
panic!("should not get here")
|
||||
})?;
|
||||
linker
|
||||
.root()
|
||||
.func_wrap("thunk", || -> Result<()> { panic!("should not get here") })?;
|
||||
linker
|
||||
.root()
|
||||
.func_wrap("ret-string", || -> Result<_> { Ok(("hello".to_string(),)) })?;
|
||||
.func_wrap("ret-string", |_, _: ()| -> Result<_> {
|
||||
Ok(("hello".to_string(),))
|
||||
})?;
|
||||
let component = Component::new(&engine, component)?;
|
||||
let mut store = Store::new(&engine, ());
|
||||
|
||||
@@ -338,7 +340,7 @@ fn attempt_to_reenter_during_host() -> Result<()> {
|
||||
let mut linker = Linker::new(&engine);
|
||||
linker.root().func_wrap(
|
||||
"thunk",
|
||||
|mut store: StoreContextMut<'_, StaticState>| -> Result<()> {
|
||||
|mut store: StoreContextMut<'_, StaticState>, _: ()| -> Result<()> {
|
||||
let func = store.data_mut().func.take().unwrap();
|
||||
let trap = func.call(&mut store, ()).unwrap_err();
|
||||
assert!(
|
||||
@@ -530,14 +532,16 @@ fn stack_and_heap_args_and_rets() -> Result<()> {
|
||||
// First, test the static API
|
||||
|
||||
let mut linker = Linker::new(&engine);
|
||||
linker.root().func_wrap("f1", |x: u32| -> Result<(u32,)> {
|
||||
linker
|
||||
.root()
|
||||
.func_wrap("f1", |_, (x,): (u32,)| -> Result<(u32,)> {
|
||||
assert_eq!(x, 1);
|
||||
Ok((2,))
|
||||
})?;
|
||||
linker.root().func_wrap(
|
||||
"f2",
|
||||
|cx: StoreContextMut<'_, ()>,
|
||||
arg: (
|
||||
(arg,): ((
|
||||
WasmStr,
|
||||
WasmStr,
|
||||
WasmStr,
|
||||
@@ -547,7 +551,7 @@ fn stack_and_heap_args_and_rets() -> Result<()> {
|
||||
WasmStr,
|
||||
WasmStr,
|
||||
WasmStr,
|
||||
)|
|
||||
),)|
|
||||
-> Result<(u32,)> {
|
||||
assert_eq!(arg.0.to_str(&cx).unwrap(), "abc");
|
||||
Ok((3,))
|
||||
@@ -555,14 +559,14 @@ fn stack_and_heap_args_and_rets() -> Result<()> {
|
||||
)?;
|
||||
linker
|
||||
.root()
|
||||
.func_wrap("f3", |arg: u32| -> Result<(String,)> {
|
||||
.func_wrap("f3", |_, (arg,): (u32,)| -> Result<(String,)> {
|
||||
assert_eq!(arg, 8);
|
||||
Ok(("xyz".to_string(),))
|
||||
})?;
|
||||
linker.root().func_wrap(
|
||||
"f4",
|
||||
|cx: StoreContextMut<'_, ()>,
|
||||
arg: (
|
||||
(arg,): ((
|
||||
WasmStr,
|
||||
WasmStr,
|
||||
WasmStr,
|
||||
@@ -572,7 +576,7 @@ fn stack_and_heap_args_and_rets() -> Result<()> {
|
||||
WasmStr,
|
||||
WasmStr,
|
||||
WasmStr,
|
||||
)|
|
||||
),)|
|
||||
-> Result<(String,)> {
|
||||
assert_eq!(arg.0.to_str(&cx).unwrap(), "abc");
|
||||
Ok(("xyz".to_string(),))
|
||||
@@ -703,12 +707,13 @@ fn bad_import_alignment() -> Result<()> {
|
||||
let mut linker = Linker::new(&engine);
|
||||
linker
|
||||
.root()
|
||||
.func_wrap("unaligned-retptr", || -> Result<(String,)> {
|
||||
.func_wrap("unaligned-retptr", |_, _: ()| -> Result<(String,)> {
|
||||
Ok((String::new(),))
|
||||
})?;
|
||||
linker.root().func_wrap(
|
||||
"unaligned-argptr",
|
||||
|_: (
|
||||
|_,
|
||||
_: ((
|
||||
WasmStr,
|
||||
WasmStr,
|
||||
WasmStr,
|
||||
@@ -718,7 +723,7 @@ fn bad_import_alignment() -> Result<()> {
|
||||
WasmStr,
|
||||
WasmStr,
|
||||
WasmStr,
|
||||
)|
|
||||
),)|
|
||||
-> Result<()> { unreachable!() },
|
||||
)?;
|
||||
let component = Component::new(&engine, component)?;
|
||||
@@ -775,12 +780,13 @@ fn no_actual_wasm_code() -> Result<()> {
|
||||
// First, test the static API
|
||||
|
||||
let mut linker = Linker::new(&engine);
|
||||
linker
|
||||
.root()
|
||||
.func_wrap("f", |mut store: StoreContextMut<'_, u32>| -> Result<()> {
|
||||
linker.root().func_wrap(
|
||||
"f",
|
||||
|mut store: StoreContextMut<'_, u32>, _: ()| -> Result<()> {
|
||||
*store.data_mut() += 1;
|
||||
Ok(())
|
||||
})?;
|
||||
},
|
||||
)?;
|
||||
|
||||
let instance = linker.instantiate(&mut store, &component)?;
|
||||
let thunk = instance.get_typed_func::<(), (), _>(&mut store, "thunk")?;
|
||||
|
||||
@@ -95,7 +95,7 @@ fn nested_many_instantiations() -> Result<()> {
|
||||
let mut linker = Linker::new(&engine);
|
||||
linker
|
||||
.root()
|
||||
.func_wrap("count", |mut store: StoreContextMut<'_, u32>| {
|
||||
.func_wrap("count", |mut store: StoreContextMut<'_, u32>, _: ()| {
|
||||
*store.data_mut() += 1;
|
||||
Ok(())
|
||||
})?;
|
||||
@@ -162,7 +162,7 @@ fn thread_options_through_inner() -> Result<()> {
|
||||
let mut linker = Linker::new(&engine);
|
||||
linker
|
||||
.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 result = instance
|
||||
.get_typed_func::<(u32,), (WasmStr,), _>(&mut store, "run")?
|
||||
|
||||
@@ -120,13 +120,14 @@ fn invoke_post_return() -> Result<()> {
|
||||
let component = Component::new(&engine, component)?;
|
||||
let mut store = Store::new(&engine, false);
|
||||
let mut linker = Linker::new(&engine);
|
||||
linker
|
||||
.root()
|
||||
.func_wrap("f", |mut store: StoreContextMut<'_, bool>| -> Result<()> {
|
||||
linker.root().func_wrap(
|
||||
"f",
|
||||
|mut store: StoreContextMut<'_, bool>, _: ()| -> Result<()> {
|
||||
assert!(!*store.data());
|
||||
*store.data_mut() = true;
|
||||
Ok(())
|
||||
})?;
|
||||
},
|
||||
)?;
|
||||
|
||||
let instance = linker.instantiate(&mut store, &component)?;
|
||||
let thunk = instance.get_typed_func::<(), (), _>(&mut store, "thunk")?;
|
||||
|
||||
@@ -171,12 +171,13 @@ fn test_roundtrip(engine: &Engine, src: &str, dst: &str) -> Result<()> {
|
||||
let component = Component::new(engine, &component)?;
|
||||
let mut store = Store::new(engine, String::new());
|
||||
let mut linker = Linker::new(engine);
|
||||
linker
|
||||
.root()
|
||||
.func_wrap("host", |store: StoreContextMut<String>, arg: String| {
|
||||
linker.root().func_wrap(
|
||||
"host",
|
||||
|store: StoreContextMut<String>, (arg,): (String,)| {
|
||||
assert_eq!(*store.data(), arg);
|
||||
Ok((arg,))
|
||||
})?;
|
||||
},
|
||||
)?;
|
||||
let instance = linker.instantiate(&mut store, &component)?;
|
||||
let func = instance.get_typed_func::<(String,), (String,), _>(&mut store, "echo")?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user