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

components: Implement the ability to call component exports #4039

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
15 changes: 12 additions & 3 deletions crates/environ/src/component/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,15 +137,24 @@ pub struct LiftedFunction {
}

/// Canonical ABI options associated with a lifted function.
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CanonicalOptions {
/// The optionally-specified encoding used for strings.
pub string_encoding: Option<StringEncoding>,
/// The encoding used for strings.
pub string_encoding: StringEncoding,
/// Representation of the `into` option where intrinsics are peeled out and
/// identified from an instance.
pub intrinsics: Option<Intrinsics>,
}

impl Default for CanonicalOptions {
fn default() -> CanonicalOptions {
CanonicalOptions {
string_encoding: StringEncoding::Utf8,
intrinsics: None,
}
}
}

/// Possible encodings of strings within the component model.
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[allow(missing_docs)]
Expand Down
6 changes: 3 additions & 3 deletions crates/environ/src/component/translate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -650,13 +650,13 @@ impl<'a, 'data> Translator<'a, 'data> {
for opt in opts {
match opt {
wasmparser::CanonicalOption::UTF8 => {
ret.string_encoding = Some(StringEncoding::Utf8);
ret.string_encoding = StringEncoding::Utf8;
}
wasmparser::CanonicalOption::UTF16 => {
ret.string_encoding = Some(StringEncoding::Utf16);
ret.string_encoding = StringEncoding::Utf16;
}
wasmparser::CanonicalOption::CompactUTF16 => {
ret.string_encoding = Some(StringEncoding::CompactUtf16);
ret.string_encoding = StringEncoding::CompactUtf16;
}
wasmparser::CanonicalOption::Into(instance) => {
let instance = InstanceIndex::from_u32(*instance);
Expand Down
2 changes: 1 addition & 1 deletion crates/wasmtime/src/component/func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ pub struct FuncData {
}

pub(crate) struct Options {
string_encoding: Option<StringEncoding>,
string_encoding: StringEncoding,
intrinsics: Option<Intrinsics>,
}

Expand Down
68 changes: 55 additions & 13 deletions crates/wasmtime/src/component/func/typed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,27 @@ impl<T> MaybeUninitExt<T> for MaybeUninit<T> {
/// This type is primarily created with the [`Func::typed`] API.
pub struct TypedFunc<Params, Return> {
func: Func,
_marker: marker::PhantomData<fn(Params) -> Return>,

// The definition of this field is somewhat subtle and may be surprising.
// Naively one might expect something like
//
// _marker: marker::PhantomData<fn(Params) -> Return>,
//
// Since this is a function pointer after all. The problem with this
// definition though is that it imposes the wrong variance on `Params` from
// what we want. Abstractly a `fn(Params)` is able to store `Params` within
// it meaning you can only give it `Params` that live longer than the
// function pointer.
//
// With a component model function, however, we're always copying data from
// the host into the guest, so we are never storing pointers to `Params`
// into the guest outside the duration of a `call`, meaning we can actually
// accept values in `TypedFunc::call` which live for a shorter duration
// than the `Params` argument on the struct.
//
// This all means that we don't use a phantom function pointer, but instead
// feign phantom storage here to get the variance desired.
_marker: marker::PhantomData<(Params, Return)>,
}

impl<Params, Return> Copy for TypedFunc<Params, Return> {}
Expand Down Expand Up @@ -565,7 +585,7 @@ impl<'a, T> Memory<'a, T> {

#[inline]
fn string_encoding(&self) -> StringEncoding {
self.store.0[self.func.0].options.string_encoding.unwrap()
self.store.0[self.func.0].options.string_encoding
}

#[inline]
Expand Down Expand Up @@ -1040,7 +1060,7 @@ fn lower_string<T>(mem: &mut Memory<'_, T>, string: &str) -> Result<(usize, usiz
bytes[1] = u_bytes[1];
copied += 1;
}
if copied < size {
if (copied * 2) < size {
ptr = mem.realloc(ptr, size, 2, copied * 2)?;
}
Ok((ptr, copied))
Expand Down Expand Up @@ -1184,10 +1204,11 @@ where
.len()
.checked_mul(elem_size)
.ok_or_else(|| anyhow::anyhow!("size overflow copying a list"))?;
let mut ptr = mem.realloc(0, 0, T::align(), size)?;
let ptr = mem.realloc(0, 0, T::align(), size)?;
let mut cur = ptr;
for item in list {
item.store(mem, ptr)?;
ptr += elem_size;
item.store(mem, cur)?;
cur += elem_size;
}
Ok((ptr, list.len()))
}
Expand Down Expand Up @@ -1233,6 +1254,32 @@ impl<'a, T: ComponentValue> Cursor<'a, Vec<T>> {
}
}

impl<'a> Cursor<'a, Vec<u8>> {
/// Get access to the raw underlying memory for this byte slice.
///
/// Note that this is specifically only implemented for a `(list u8)` type
/// since it's known to be valid in terms of alignment and representation
/// validity.
///
/// # Errors
///
/// Returns an error if the pointer or of this slice point outside of linear
/// memory.
pub fn as_slice(&self) -> Result<&'a [u8]> {
let (ptr, len) = {
let ptr_and_len = self.item_bytes();
// FIXME: needs memory64 treatment
let ptr = u32::from_le_bytes(ptr_and_len[..4].try_into().unwrap());
let len = u32::from_le_bytes(ptr_and_len[4..].try_into().unwrap());
(usize::try_from(ptr)?, usize::try_from(len)?)
};
self.all_memory()
.get(ptr..)
.and_then(|m| m.get(..len))
.ok_or_else(|| anyhow::anyhow!("list out of bounds"))
}
}

#[inline]
const fn align_to(a: usize, align: u32) -> usize {
debug_assert!(align.is_power_of_two());
Expand Down Expand Up @@ -1787,7 +1834,7 @@ mod value {
pub struct Cursor<'a, T> {
offset: usize,
all_memory: &'a [u8],
string_encoding: Option<StringEncoding>,
string_encoding: StringEncoding,
_marker: marker::PhantomData<T>,
}

Expand Down Expand Up @@ -1868,13 +1915,8 @@ mod value {
}

/// Returns the string encoding in use.
///
/// # Panics
///
/// Panics if string encoding isn't specified, meaning this should only
/// be called when lifting strings.
pub(super) fn string_encoding(&self) -> StringEncoding {
self.string_encoding.unwrap()
self.string_encoding
}

/// Increments this `Cursor` forward by `offset` bytes to point to a
Expand Down
32 changes: 30 additions & 2 deletions crates/wasmtime/src/component/instance.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::component::{Component, Func};
use crate::component::{Component, ComponentParams, ComponentReturn, Func, TypedFunc};
use crate::instance::OwnedImports;
use crate::store::{StoreOpaque, Stored};
use crate::{AsContextMut, Module, StoreContextMut};
use anyhow::Result;
use anyhow::{anyhow, Context, Result};
use wasmtime_environ::component::{
CoreExport, Export, ExportItem, Instantiation, RuntimeInstanceIndex,
};
Expand Down Expand Up @@ -70,6 +70,34 @@ impl Instance {
store[self.0] = Some(data);
return result;
}

/// Looks up an exported [`Func`] value by name and with its type.
///
/// This function is a convenience wrapper over [`Instance::get_func`] and
/// [`Func::typed`]. For more information see the linked documentation.
///
/// Returns an error if `name` isn't a function export or if the export's
/// type did not match `Params` or `Results`
///
/// # Panics
///
/// Panics if `store` does not own this instance.
pub fn get_typed_func<Params, Results, S>(
&self,
mut store: S,
name: &str,
) -> Result<TypedFunc<Params, Results>>
where
Params: ComponentParams,
Results: ComponentReturn,
S: AsContextMut,
{
let f = self
.get_func(store.as_context_mut(), name)
.ok_or_else(|| anyhow!("failed to find function export `{}`", name))?;
Ok(f.typed::<Params, Results, _>(store)
.with_context(|| format!("failed to convert function `{}` to given type", name))?)
}
}

impl InstanceData {
Expand Down
2 changes: 2 additions & 0 deletions tests/all/component_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use anyhow::Result;
use wasmtime::component::Component;
use wasmtime::{Config, Engine};

mod func;

fn engine() -> Engine {
let mut config = Config::new();
config.wasm_component_model(true);
Expand Down
Loading