Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove type information from dynamic component funcs #8070

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/fuzzing/src/oracles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -883,7 +883,7 @@ pub fn dynamic_component_api_target(input: &mut arbitrary::Unstructured) -> arbi

linker
.root()
.func_new(&component, IMPORT_FUNCTION, {
.func_new(IMPORT_FUNCTION, {
move |mut cx: StoreContextMut<'_, (Vec<Val>, Option<Vec<Val>>)>,
params: &[Val],
results: &mut [Val]|
Expand Down
27 changes: 8 additions & 19 deletions crates/wasmtime/src/runtime/component/func/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ use crate::component::matching::InstanceType;
use crate::component::storage::slice_to_storage_mut;
use crate::component::{ComponentNamedList, ComponentType, Lift, Lower, Val};
use crate::{AsContextMut, StoreContextMut, ValRaw};
use anyhow::{anyhow, bail, Context, Result};
use anyhow::{bail, Context, Result};
use std::any::Any;
use std::mem::{self, MaybeUninit};
use std::panic::{self, AssertUnwindSafe};
use std::ptr::NonNull;
use std::sync::Arc;
use wasmtime_environ::component::{
CanonicalAbiInfo, ComponentTypes, InterfaceType, StringEncoding, TypeFuncIndex,
MAX_FLAT_PARAMS, MAX_FLAT_RESULTS,
CanonicalAbiInfo, InterfaceType, StringEncoding, TypeFuncIndex, MAX_FLAT_PARAMS,
MAX_FLAT_RESULTS,
};
use wasmtime_runtime::component::{
InstanceFlags, VMComponentContext, VMLowering, VMLoweringCallee,
Expand Down Expand Up @@ -71,27 +71,16 @@ impl HostFunc {
}
}

pub(crate) fn new_dynamic<T, F>(
func: F,
index: TypeFuncIndex,
types: &Arc<ComponentTypes>,
) -> Arc<HostFunc>
pub(crate) fn new_dynamic<T, F>(func: F) -> Arc<HostFunc>
where
F: Fn(StoreContextMut<'_, T>, &[Val], &mut [Val]) -> Result<()> + Send + Sync + 'static,
{
Arc::new(HostFunc {
entrypoint: dynamic_entrypoint::<T, F>,
typecheck: Box::new({
let types = types.clone();

move |expected_index, expected_types| {
if index == expected_index && std::ptr::eq(&*types, &**expected_types.types) {
Ok(())
} else {
Err(anyhow!("function type mismatch"))
}
}
}),
// This function performs dynamic type checks and subsequently does
// not need to perform up-front type checks. Instead everything is
// dynamically managed at runtime.
typecheck: Box::new(move |_expected_index, _expected_types| Ok(())),
func: Box::new(func),
})
}
Expand Down
153 changes: 104 additions & 49 deletions crates/wasmtime/src/runtime/component/linker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,13 @@ use crate::component::{
Component, ComponentNamedList, Instance, InstancePre, Lift, Lower, ResourceType, Val,
};
use crate::{AsContextMut, Engine, Module, StoreContextMut};
use anyhow::{anyhow, bail, Context, Result};
use indexmap::IndexMap;
use anyhow::{bail, Context, Result};
use semver::Version;
use std::collections::hash_map::{Entry, HashMap};
use std::future::Future;
use std::marker;
use std::ops::Deref;
use std::pin::Pin;
use std::sync::Arc;
use wasmtime_environ::component::TypeDef;
use wasmtime_environ::PrimaryMap;

/// A type used to instantiate [`Component`]s.
Expand Down Expand Up @@ -413,53 +410,111 @@ impl<T> LinkerInstance<'_, T> {
self.func_wrap(name, ff)
}

/// Define a new host-provided function using dynamic types.
/// Define a new host-provided function using dynamically typed values.
///
/// `name` must refer to a function type import in `component`. If and when
/// that import is invoked by the component, the specified `func` will be
/// called, which must return a `Val` which is an instance of the result
/// type of the import.
pub fn func_new<
F: Fn(StoreContextMut<'_, T>, &[Val], &mut [Val]) -> Result<()> + Send + Sync + 'static,
>(
/// The `name` provided is the name of the function to define and the
/// `func` provided is the host-defined closure to invoke when this
/// function is called.
///
/// This function is the "dynamic" version of defining a host function as
/// copared to [`Linker::func_wrap`]. With [`Linker::func_wrap`] a
alexcrichton marked this conversation as resolved.
Show resolved Hide resolved
/// function's type is statically known but with this method the `func`
/// argument's type isn't known ahead of time. That means that `func` can
/// be by imported component so long as it's imported as a matching name.
///
/// Type information will be available at execution time, however. For
/// example when `func` is invoked the second argument, a `&[Val]` list,
/// contains [`Val`] entries that say what type they are. Additionally the
/// third argument, `&mut [Val]`, is the expected number of results. Note
/// that the expected types of the results cannot be learned during the
/// execution of `func`. Learning that would require runtime introspection
/// of a component.
///
/// Return values, stored in the third argument of `&mut [Val]`, are
/// type-checked at runtime to ensure that they have the appropriate type.
/// A trap will be raised if they do not have the right type.
///
/// # Examples
///
/// ```
/// use wasmtime::{Store, Engine};
/// use wasmtime::component::{Component, Linker, Val};
///
/// # fn main() -> wasmtime::Result<()> {
/// let engine = Engine::default();
/// let component = Component::new(
/// &engine,
/// r#"
/// (component
/// (import "thunk" (func $thunk))
/// (import "is-even" (func $is-even (param "x" u32) (result bool)))
///
/// (core module $m
/// (import "" "thunk" (func $thunk))
/// (import "" "is-even" (func $is-even (param i32) (result i32)))
///
/// (func (export "run")
/// call $thunk
///
/// (call $is-even (i32.const 1))
/// if unreachable end
///
/// (call $is-even (i32.const 2))
/// i32.eqz
/// if unreachable end
/// )
/// )
/// (core func $thunk (canon lower (func $thunk)))
/// (core func $is-even (canon lower (func $is-even)))
/// (core instance $i (instantiate $m
/// (with "" (instance
/// (export "thunk" (func $thunk))
/// (export "is-even" (func $is-even))
/// ))
/// ))
///
/// (func (export "run") (canon lift (core func $i "run")))
/// )
/// "#,
/// )?;
///
/// let mut linker = Linker::<()>::new(&engine);
///
/// // Sample function that takes no arguments.
/// linker.root().func_new("thunk", |_store, params, results| {
/// assert!(params.is_empty());
/// assert!(results.is_empty());
/// println!("Look ma, host hands!");
/// Ok(())
/// })?;
///
/// // This function takes one argument and returns one result.
/// linker.root().func_new("is-even", |_store, params, results| {
/// assert_eq!(params.len(), 1);
/// let param = match params[0] {
/// Val::U32(n) => n,
/// _ => panic!("unexpected type"),
/// };
///
/// assert_eq!(results.len(), 1);
/// results[0] = Val::Bool(param % 2 == 0);
/// Ok(())
/// })?;
///
/// let mut store = Store::new(&engine, ());
/// let instance = linker.instantiate(&mut store, &component)?;
/// let run = instance.get_typed_func::<(), ()>(&mut store, "run")?;
/// run.call(&mut store, ())?;
/// # Ok(())
/// # }
/// ```
pub fn func_new(
&mut self,
component: &Component,
name: &str,
func: F,
func: impl Fn(StoreContextMut<'_, T>, &[Val], &mut [Val]) -> Result<()> + Send + Sync + 'static,
) -> Result<()> {
let mut map = &component
.env_component()
.import_types
.values()
.map(|(k, v)| (k.clone(), *v))
.collect::<IndexMap<_, _>>();

for name in self.path.iter().copied().take(self.path_len) {
let name = self.strings.strings[name].deref();
if let Some(ty) = map.get(name) {
if let TypeDef::ComponentInstance(index) = ty {
map = &component.types()[*index].exports;
} else {
bail!("import `{name}` has the wrong type (expected a component instance)");
}
} else {
bail!("import `{name}` not found");
}
}

if let Some(ty) = map.get(name) {
if let TypeDef::ComponentFunc(index) = ty {
self.insert(
name,
Definition::Func(HostFunc::new_dynamic(func, *index, component.types())),
)?;
Ok(())
} else {
bail!("import `{name}` has the wrong type (expected a function)");
}
} else {
Err(anyhow!("import `{name}` not found"))
}
self.insert(name, Definition::Func(HostFunc::new_dynamic(func)))?;
Ok(())
}

/// Define a new host-provided async function using dynamic types.
Expand All @@ -468,7 +523,7 @@ impl<T> LinkerInstance<'_, T> {
/// host function.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn func_new_async<F>(&mut self, component: &Component, name: &str, f: F) -> Result<()>
pub fn func_new_async<F>(&mut self, name: &str, f: F) -> Result<()>
where
F: for<'a> Fn(
StoreContextMut<'a, T>,
Expand All @@ -488,7 +543,7 @@ impl<T> LinkerInstance<'_, T> {
let mut future = Pin::from(f(store.as_context_mut(), params, results));
unsafe { async_cx.block_on(future.as_mut()) }?
};
self.func_new(component, name, ff)
self.func_new(name, ff)
}

/// Defines a [`Module`] within this instance.
Expand Down
91 changes: 39 additions & 52 deletions tests/all/component_model/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,6 @@ fn simple() -> Result<()> {
*store.data_mut() = None;
let mut linker = Linker::new(&engine);
linker.root().func_new(
&component,
"a",
|mut store: StoreContextMut<'_, Option<String>>, args, _results| {
if let Val::String(s) = &args[0] {
Expand Down Expand Up @@ -249,7 +248,6 @@ fn functions_in_instances() -> Result<()> {
*store.data_mut() = None;
let mut linker = Linker::new(&engine);
linker.instance("test:test/foo")?.func_new(
&component,
"a",
|mut store: StoreContextMut<'_, Option<String>>, args, _results| {
if let Val::String(s) = &args[0] {
Expand Down Expand Up @@ -468,7 +466,6 @@ fn attempt_to_reenter_during_host() -> Result<()> {
let mut store = Store::new(&engine, DynamicState { func: None });
let mut linker = Linker::new(&engine);
linker.root().func_new(
&component,
"thunk",
|mut store: StoreContextMut<'_, DynamicState>, _, _| {
let func = store.data_mut().func.take().unwrap();
Expand Down Expand Up @@ -691,58 +688,50 @@ fn stack_and_heap_args_and_rets() -> Result<()> {
// Next, test the dynamic API

let mut linker = Linker::new(&engine);
linker
.root()
.func_new(&component, "f1", |_, args, results| {
if let Val::U32(x) = &args[0] {
assert_eq!(*x, 1);
results[0] = Val::U32(2);
linker.root().func_new("f1", |_, args, results| {
if let Val::U32(x) = &args[0] {
assert_eq!(*x, 1);
results[0] = Val::U32(2);
Ok(())
} else {
panic!()
}
})?;
linker.root().func_new("f2", |_, args, results| {
if let Val::Tuple(tuple) = &args[0] {
if let Val::String(s) = &tuple[0] {
assert_eq!(s.deref(), "abc");
results[0] = Val::U32(3);
Ok(())
} else {
panic!()
}
})?;
linker
.root()
.func_new(&component, "f2", |_, args, results| {
if let Val::Tuple(tuple) = &args[0] {
if let Val::String(s) = &tuple[0] {
assert_eq!(s.deref(), "abc");
results[0] = Val::U32(3);
Ok(())
} else {
panic!()
}
} else {
panic!()
}
})?;
linker
.root()
.func_new(&component, "f3", |_, args, results| {
if let Val::U32(x) = &args[0] {
assert_eq!(*x, 8);
} else {
panic!()
}
})?;
linker.root().func_new("f3", |_, args, results| {
if let Val::U32(x) = &args[0] {
assert_eq!(*x, 8);
results[0] = Val::String("xyz".into());
Ok(())
} else {
panic!();
}
})?;
linker.root().func_new("f4", |_, args, results| {
if let Val::Tuple(tuple) = &args[0] {
if let Val::String(s) = &tuple[0] {
assert_eq!(s.deref(), "abc");
results[0] = Val::String("xyz".into());
Ok(())
} else {
panic!();
}
})?;
linker
.root()
.func_new(&component, "f4", |_, args, results| {
if let Val::Tuple(tuple) = &args[0] {
if let Val::String(s) = &tuple[0] {
assert_eq!(s.deref(), "abc");
results[0] = Val::String("xyz".into());
Ok(())
} else {
panic!()
}
} else {
panic!()
}
})?;
} else {
panic!()
}
})?;
let instance = linker.instantiate(&mut store, &component)?;
instance
.get_func(&mut store, "run")
Expand Down Expand Up @@ -906,14 +895,12 @@ fn no_actual_wasm_code() -> Result<()> {

*store.data_mut() = 0;
let mut linker = Linker::new(&engine);
linker.root().func_new(
&component,
"f",
|mut store: StoreContextMut<'_, u32>, _, _| {
linker
.root()
.func_new("f", |mut store: StoreContextMut<'_, u32>, _, _| {
*store.data_mut() += 1;
Ok(())
},
)?;
})?;

let instance = linker.instantiate(&mut store, &component)?;
let thunk = instance.get_func(&mut store, "thunk").unwrap();
Expand Down