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,30 +47,25 @@ 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>(
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user