-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
components: Implement the ability to call component exports #4039
Changes from all commits
f799909
3764ebd
864124c
21c6885
1ac0c89
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,25 @@ | ||
use crate::component::instance::lookup; | ||
use crate::store::{StoreOpaque, Stored}; | ||
use crate::{AsContext, StoreContextMut}; | ||
use anyhow::{bail, Context, Result}; | ||
use std::convert::TryFrom; | ||
use std::sync::Arc; | ||
use wasmtime_environ::component::{ | ||
ComponentTypes, FuncTypeIndex, LiftedFunction, RuntimeInstanceIndex, StringEncoding, | ||
}; | ||
use wasmtime_environ::PrimaryMap; | ||
use wasmtime_runtime::{Export, ExportFunction, ExportMemory, VMTrampoline}; | ||
|
||
mod typed; | ||
pub use self::typed::*; | ||
|
||
/// A WebAssembly component function. | ||
// | ||
// FIXME: write more docs here | ||
#[derive(Copy, Clone, Debug)] | ||
pub struct Func(Stored<FuncData>); | ||
|
||
#[doc(hidden)] | ||
#[allow(dead_code)] // FIXME: remove this when fields are actually used | ||
pub struct FuncData { | ||
trampoline: VMTrampoline, | ||
export: ExportFunction, | ||
|
@@ -23,18 +28,15 @@ pub struct FuncData { | |
options: Options, | ||
} | ||
|
||
#[derive(Clone)] | ||
#[allow(dead_code)] // FIXME: remove this when fields are actually used | ||
pub(crate) struct Options { | ||
string_encoding: Option<StringEncoding>, | ||
string_encoding: StringEncoding, | ||
intrinsics: Option<Intrinsics>, | ||
} | ||
|
||
#[derive(Clone)] | ||
#[allow(dead_code)] // FIXME: remove this when fields are actually used | ||
struct Intrinsics { | ||
memory: ExportMemory, | ||
realloc: ExportFunction, | ||
#[allow(dead_code)] // FIXME: remove this when actually used | ||
free: ExportFunction, | ||
} | ||
|
||
|
@@ -80,4 +82,179 @@ impl Func { | |
types: types.clone(), | ||
})) | ||
} | ||
|
||
/// Attempt to cast this [`Func`] to a statically typed [`TypedFunc`] with | ||
/// the provided `Params` and `Return`. | ||
/// | ||
/// This function will perform a type-check at runtime that the [`Func`] | ||
/// takes `Params` as parameters and returns `Return`. If the type-check | ||
/// passes then a [`TypedFunc`] will be returned which can be used to invoke | ||
/// the function in an efficient, statically-typed, and ergonomic manner. | ||
/// | ||
/// The `Params` type parameter here is a tuple of the parameters to the | ||
/// function. A function which takes no arguments should use `()`, a | ||
/// function with one argument should use `(T,)`, etc. | ||
/// | ||
/// The `Return` type parameter is the return value of this function. A | ||
/// return value of `()` means that there's no return (similar to a Rust | ||
/// unit return) and otherwise a type `T` can be specified. | ||
/// | ||
/// Types specified here are mainly those that implement the | ||
/// [`ComponentValue`] trait. This trait is implemented for built-in types | ||
/// to Rust such as integer primitives, floats, `Option<T>`, `Result<T, E>`, | ||
/// strings, and `Vec<T>`. As parameters you'll be passing native Rust | ||
/// types. | ||
/// | ||
/// For the `Return` type parameter many types need to be wrapped in a | ||
/// [`Value<T>`]. For example functions which return a string should use the | ||
/// `Return` type parameter as `Value<String>` instead of a bare `String`. | ||
/// The usage of [`Value`] indicates that a type is stored in linear memory. | ||
// | ||
// FIXME: Having to remember when to use `Value<T>` vs `T` is going to trip | ||
// people up using this API. It's not clear, though, how to fix that. | ||
Comment on lines
+108
to
+114
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe easier for folks to understand if we talk about how scalars don't use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately this isn't 100% accurate advice in the sense that |
||
/// | ||
/// # Errors | ||
/// | ||
/// If the function does not actually take `Params` as its parameters or | ||
/// return `Return` then an error will be returned. | ||
/// | ||
/// # Panics | ||
/// | ||
/// This function will panic if `self` is not owned by the `store` | ||
/// specified. | ||
/// | ||
/// # Examples | ||
/// | ||
/// Calling a function which takes no parameters and has no return value: | ||
/// | ||
/// ``` | ||
/// # use wasmtime::component::Func; | ||
/// # use wasmtime::Store; | ||
/// # fn foo(func: &Func, store: &mut Store<()>) -> anyhow::Result<()> { | ||
/// let typed = func.typed::<(), (), _>(&store)?; | ||
/// typed.call(store, ())?; | ||
/// # Ok(()) | ||
/// # } | ||
/// ``` | ||
/// | ||
/// Calling a function which takes one string parameter and returns a | ||
/// string: | ||
/// | ||
/// ``` | ||
/// # use wasmtime::component::{Func, Value}; | ||
/// # use wasmtime::Store; | ||
/// # fn foo(func: &Func, mut store: Store<()>) -> anyhow::Result<()> { | ||
/// let typed = func.typed::<(&str,), Value<String>, _>(&store)?; | ||
/// let ret = typed.call(&mut store, ("Hello, ",))?; | ||
/// let ret = ret.cursor(&store); | ||
/// println!("returned string was: {}", ret.to_str()?); | ||
/// # Ok(()) | ||
/// # } | ||
/// ``` | ||
/// | ||
/// Calling a function which takes multiple parameters and returns a boolean: | ||
/// | ||
/// ``` | ||
/// # use wasmtime::component::Func; | ||
/// # use wasmtime::Store; | ||
/// # fn foo(func: &Func, mut store: Store<()>) -> anyhow::Result<()> { | ||
/// let typed = func.typed::<(u32, Option<&str>, &[u8]), bool, _>(&store)?; | ||
/// let ok: bool = typed.call(&mut store, (1, Some("hello"), b"bytes!"))?; | ||
/// println!("return value was: {ok}"); | ||
/// # Ok(()) | ||
/// # } | ||
/// ``` | ||
pub fn typed<Params, Return, S>(&self, store: S) -> Result<TypedFunc<Params, Return>> | ||
where | ||
Params: ComponentParams, | ||
Return: ComponentReturn, | ||
S: AsContext, | ||
{ | ||
self.typecheck::<Params, Return>(store.as_context().0)?; | ||
unsafe { Ok(TypedFunc::new_unchecked(*self)) } | ||
} | ||
|
||
fn typecheck<Params, Return>(&self, store: &StoreOpaque) -> Result<()> | ||
where | ||
Params: ComponentParams, | ||
Return: ComponentReturn, | ||
{ | ||
let data = &store[self.0]; | ||
let ty = &data.types[data.ty]; | ||
|
||
Params::typecheck(&ty.params, &data.types).context("type mismatch with parameters")?; | ||
Return::typecheck(&ty.result, &data.types).context("type mismatch with result")?; | ||
|
||
Ok(()) | ||
} | ||
|
||
fn realloc<'a, T>( | ||
&self, | ||
store: &'a mut StoreContextMut<'_, T>, | ||
old: usize, | ||
old_size: usize, | ||
old_align: u32, | ||
new_size: usize, | ||
) -> Result<(&'a mut [u8], usize)> { | ||
let (realloc, memory) = match &store.0[self.0].options.intrinsics { | ||
Some(Intrinsics { | ||
memory, realloc, .. | ||
}) => (realloc.clone(), memory.clone()), | ||
None => unreachable!(), | ||
}; | ||
|
||
// Invoke the wasm malloc function using its raw and statically known | ||
// signature. | ||
let result = unsafe { | ||
// FIXME: needs memory64 support | ||
assert!(!memory.memory.memory.memory64); | ||
usize::try_from(crate::TypedFunc::<(u32, u32, u32, u32), u32>::call_raw( | ||
alexcrichton marked this conversation as resolved.
Show resolved
Hide resolved
|
||
store, | ||
realloc.anyfunc, | ||
( | ||
u32::try_from(old)?, | ||
u32::try_from(old_size)?, | ||
old_align, | ||
u32::try_from(new_size)?, | ||
), | ||
)?)? | ||
}; | ||
|
||
let memory = self.memory_mut(store.0); | ||
|
||
let result_slice = match memory.get_mut(result..).and_then(|s| s.get_mut(..new_size)) { | ||
Some(end) => end, | ||
None => bail!("realloc return: beyond end of memory"), | ||
}; | ||
|
||
Ok((result_slice, result)) | ||
} | ||
|
||
/// Asserts that this function has an associated memory attached to it and | ||
/// then returns the slice of memory tied to the lifetime of the provided | ||
/// store. | ||
fn memory<'a>(&self, store: &'a StoreOpaque) -> &'a [u8] { | ||
let memory = match &store[self.0].options.intrinsics { | ||
Some(Intrinsics { memory, .. }) => memory, | ||
None => unreachable!(), | ||
}; | ||
|
||
unsafe { | ||
let memory = &*memory.definition; | ||
std::slice::from_raw_parts(memory.base, memory.current_length) | ||
} | ||
} | ||
|
||
/// Same as above, just `_mut` | ||
fn memory_mut<'a>(&self, store: &'a mut StoreOpaque) -> &'a mut [u8] { | ||
let memory = match &store[self.0].options.intrinsics { | ||
Some(Intrinsics { memory, .. }) => memory.clone(), | ||
None => unreachable!(), | ||
}; | ||
|
||
unsafe { | ||
let memory = &*memory.definition; | ||
std::slice::from_raw_parts_mut(memory.base, memory.current_length) | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't we have impls of plain
T
forTypedFunc
? Can we do the same here? I guess that would introduce ambiguity between a single tuple parameter and N parameters?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do have impls for plain
T
, yeah. But yes as you deduced the ambiguity prevents the impl from existing for interface types.