diff --git a/crates/environ/src/component/info.rs b/crates/environ/src/component/info.rs index 0ab659e65b57..28b60a8ee318 100644 --- a/crates/environ/src/component/info.rs +++ b/crates/environ/src/component/info.rs @@ -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, + /// 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, } +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)] diff --git a/crates/environ/src/component/translate.rs b/crates/environ/src/component/translate.rs index 2c66aa0c2143..15df12078edd 100644 --- a/crates/environ/src/component/translate.rs +++ b/crates/environ/src/component/translate.rs @@ -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); diff --git a/crates/wasmtime/src/component/func.rs b/crates/wasmtime/src/component/func.rs index 3767eb5d369e..151c5d128e6f 100644 --- a/crates/wasmtime/src/component/func.rs +++ b/crates/wasmtime/src/component/func.rs @@ -1,5 +1,8 @@ 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, @@ -7,6 +10,9 @@ use wasmtime_environ::component::{ 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 @@ -14,7 +20,6 @@ use wasmtime_runtime::{Export, ExportFunction, ExportMemory, VMTrampoline}; pub struct Func(Stored); #[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, + string_encoding: StringEncoding, intrinsics: Option, } -#[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`, `Result`, + /// strings, and `Vec`. As parameters you'll be passing native Rust + /// types. + /// + /// For the `Return` type parameter many types need to be wrapped in a + /// [`Value`]. For example functions which return a string should use the + /// `Return` type parameter as `Value` 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` vs `T` is going to trip + // people up using this API. It's not clear, though, how to fix 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, _>(&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(&self, store: S) -> Result> + where + Params: ComponentParams, + Return: ComponentReturn, + S: AsContext, + { + self.typecheck::(store.as_context().0)?; + unsafe { Ok(TypedFunc::new_unchecked(*self)) } + } + + fn typecheck(&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( + 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) + } + } } diff --git a/crates/wasmtime/src/component/func/typed.rs b/crates/wasmtime/src/component/func/typed.rs new file mode 100644 index 000000000000..cf3964af7f51 --- /dev/null +++ b/crates/wasmtime/src/component/func/typed.rs @@ -0,0 +1,1972 @@ +use crate::component::Func; +use crate::store::StoreOpaque; +use crate::{AsContextMut, StoreContextMut, ValRaw}; +use anyhow::{bail, Result}; +use std::borrow::Cow; +use std::convert::Infallible; +use std::marker; +use std::mem::{self, MaybeUninit}; +use std::str; +use wasmtime_environ::component::{ComponentTypes, InterfaceType, StringEncoding}; + +const MAX_STACK_PARAMS: usize = 16; +const MAX_STACK_RESULTS: usize = 1; +const UTF16_TAG: usize = 1 << 31; + +/// A helper macro to safely map `MaybeUninit` to `MaybeUninit` where `U` +/// is a field projection within `T`. +/// +/// This is intended to be invoked as: +/// +/// ```ignore +/// struct MyType { +/// field: u32, +/// } +/// +/// let initial: &mut MaybeUninit = ...; +/// let field: &mut MaybeUninit = map_maybe_uninit!(initial.field); +/// ``` +/// +/// Note that array accesses are also supported: +/// +/// ```ignore +/// +/// let initial: &mut MaybeUninit<[u32; 2]> = ...; +/// let element: &mut MaybeUninit = map_maybe_uninit!(initial[1]); +/// ``` +macro_rules! map_maybe_uninit { + ($maybe_uninit:ident $($field:tt)*) => (#[allow(unused_unsafe)] unsafe { + let m: &mut MaybeUninit<_> = $maybe_uninit; + // Note the usage of `addr_of_mut!` here which is an attempt to "stay + // safe" here where we never accidentally create `&mut T` where `T` is + // actually uninitialized, hopefully appeasing the Rust unsafe + // guidelines gods. + m.map(|p| std::ptr::addr_of_mut!((*p)$($field)*)) + }) +} + +trait MaybeUninitExt { + /// Maps `MaybeUninit` to `MaybeUninit` using the closure provided. + /// + /// Note that this is `unsafe` as there is no guarantee that `U` comes from + /// `T`. + unsafe fn map(&mut self, f: impl FnOnce(*mut T) -> *mut U) -> &mut MaybeUninit; +} + +impl MaybeUninitExt for MaybeUninit { + unsafe fn map(&mut self, f: impl FnOnce(*mut T) -> *mut U) -> &mut MaybeUninit { + let new_ptr = f(self.as_mut_ptr()); + mem::transmute::<*mut U, &mut MaybeUninit>(new_ptr) + } +} + +/// A statically-typed version of [`Func`] which takes `Params` as input and +/// returns `Return`. +/// +/// This is an efficient way to invoke a WebAssembly component where if the +/// inputs and output are statically known this can eschew the vast majority of +/// machinery and checks when calling WebAssembly. This is the most optimized +/// way to call a WebAssembly component. +/// +/// Note that like [`Func`] this is a pointer within a [`Store`](crate::Store) +/// and usage will panic if used with the wrong store. +/// +/// This type is primarily created with the [`Func::typed`] API. +pub struct TypedFunc { + func: Func, + + // The definition of this field is somewhat subtle and may be surprising. + // Naively one might expect something like + // + // _marker: marker::PhantomData 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 Copy for TypedFunc {} + +impl Clone for TypedFunc { + fn clone(&self) -> TypedFunc { + *self + } +} + +impl TypedFunc +where + Params: ComponentParams, + Return: ComponentReturn, +{ + /// Creates a new [`TypedFunc`] from the provided component [`Func`], + /// unsafely asserting that the underlying function takes `Params` as + /// input and returns `Return`. + /// + /// # Unsafety + /// + /// This is an unsafe function because it does not verify that the [`Func`] + /// provided actually implements this signature. It's up to the caller to + /// have performed some other sort of check to ensure that the signature is + /// correct. + pub unsafe fn new_unchecked(func: Func) -> TypedFunc { + TypedFunc { + _marker: marker::PhantomData, + func, + } + } + + /// Returns the underlying un-typed [`Func`] that this [`TypedFunc`] + /// references. + pub fn func(&self) -> &Func { + &self.func + } + + /// Calls the underlying WebAssembly component function using the provided + /// `params` as input. + /// + /// This method is used to enter into a component. Execution happens within + /// the `store` provided. The `params` are copied into WebAssembly memory + /// as appropriate and a core wasm function is invoked. + /// + /// # Errors + /// + /// This function can return an error for a number of reasons: + /// + /// * If the wasm itself traps during execution. + /// * If the wasm traps while copying arguments into memory. + /// * If the wasm provides bad allocation pointers when copying arguments + /// into memory. + /// * If the wasm returns a value which violates the canonical ABI. + /// + /// In general there are many ways that things could go wrong when copying + /// types in and out of a wasm module with the canonical ABI, and certain + /// error conditions are specific to certain types. For example a + /// WebAssembly module can't return an invalid `char`. When allocating space + /// for this host to copy a string into the returned pointer must be + /// in-bounds in memory. + /// + /// If an error happens then the error should contain detailed enough + /// information to understand which part of the canonical ABI went wrong + /// and what to inspect. + /// + /// # Panics + /// + /// This function will panic if `store` does not own this function. + pub fn call(&self, mut store: impl AsContextMut, params: Params) -> Result { + let mut store = store.as_context_mut(); + if ::flatten_count() <= MAX_STACK_PARAMS { + self.call_stack_args(&mut store, ¶ms) + } else { + self.call_heap_args(&mut store, ¶ms) + } + } + + fn call_stack_args( + &self, + store: &mut StoreContextMut<'_, T>, + params: &Params, + ) -> Result { + // Create storage space for both the parameters and the results (stored + // on top of one another), and initially have it all uninitialized. + let params_and_results = &mut MaybeUninit::< + ParamsAndResults<::Lower, Return::Lower>, + >::uninit(); + + // In debug assertions mode start with an arbitrary bit-pattern which + // should be overwritten for anything actually read by the wasm + // trampoline we'll call later. + if cfg!(debug_assertions) { + unsafe { + const CANON_ABI_UNINIT_PATTERN: u8 = 0xAB; + params_and_results + .as_mut_ptr() + .write_bytes(CANON_ABI_UNINIT_PATTERN, 1); + } + } + + // Perform the lowering operation for the parameters which will write + // all of the parameters to the stack. This stack buffer is then passed + // to core wasm as `*mut ValRaw` which will read the values from the + // stack and later store the results here as well. + params.lower( + store, + &self.func, + map_maybe_uninit!(params_and_results.params), + )?; + + self.call_raw(store, params_and_results) + } + + fn call_heap_args( + &self, + store: &mut StoreContextMut<'_, T>, + params: &Params, + ) -> Result { + // Memory must exist via validation if the arguments are stored on the + // heap, so we can create a `Memory` at this point. Afterwards `realloc` + // is used to allocate space for all the arguments and then they're all + // stored in linear memory. + let mut memory = Memory::new(store.as_context_mut(), &self.func); + let ptr = memory.realloc(0, 0, Params::align(), Params::size())?; + params.store(&mut memory, ptr)?; + + // Space for the parameters and results are created on the stack here. + // Note that the parameter here is a single `ValRaw` since the function + // will only have one parameter which is a pointer into the heap where + // all of the arguments are stored. The space for the results is + // reserved by the other field of the union of `ParamsAndResults`. + // + // Also note that the pointer here is stored as a 64-bit integer. This + // allows this to work with either 32 or 64-bit memories. For a 32-bit + // memory it'll just ignore the upper 32 zero bits, and for 64-bit + // memories this'll have the full 64-bits. Note that for 32-bit + // memories the call to `realloc` above guarantees that the `ptr` is + // in-bounds meaning that we will know that the zero-extended upper + // bits of `ptr` are guaranteed to be zero. + // + // This comment about 64-bit integers is also referred to below with + // "WRITEPTR64". + let params_and_results = &mut MaybeUninit::new(ParamsAndResults { + params: ValRaw { + i64: (ptr as i64).to_le(), + }, + }); + + self.call_raw(store, params_and_results) + } + + fn call_raw( + &self, + store: &mut StoreContextMut<'_, T>, + space: &mut MaybeUninit>, + ) -> Result + where + U: Copy, + { + let super::FuncData { + trampoline, export, .. + } = store.0[self.func.0]; + + // Double-check the size/alignemnt of `space`, just in case. + // + // Note that this alone is not enough to guarantee the validity of the + // `unsafe` block below, but it's definitely required. In any case LLVM + // should be able to trivially see through these assertions and remove + // them in release mode. + let val_size = mem::size_of::(); + let val_align = mem::align_of::(); + assert!(mem::size_of_val(space) % val_size == 0); + assert!(mem::size_of_val(map_maybe_uninit!(space.params)) % val_size == 0); + assert!(mem::size_of_val(map_maybe_uninit!(space.ret)) % val_size == 0); + assert!(mem::align_of_val(space) == val_align); + assert!(mem::align_of_val(map_maybe_uninit!(space.params)) == val_align); + assert!(mem::align_of_val(map_maybe_uninit!(space.ret)) == val_align); + + unsafe { + // This is unsafe as we are providing the guarantee that all the + // inputs are valid. The various pointers passed in for the function + // are all valid since they're coming from our store, and the + // `params_and_results` should have the correct layout for the core + // wasm function we're calling. Note that this latter point relies + // on the correctness of this module and `ComponentValue` + // implementations, hence `ComponentValue` being an `unsafe` trait. + crate::Func::call_unchecked_raw( + store, + export.anyfunc, + trampoline, + space.as_mut_ptr().cast(), + )?; + + // Note that `.assume_init_ref()` here is unsafe but we're relying + // on the correctness of the structure of `params_and_results`, the + // structure of `Return::Lower`, and the type-checking performed to + // acquire the `TypedFunc` to make this safe. It should be the case + // that `Return::Lower` is the exact representation of the return + // value when interpreted as `[ValRaw]`, and additionally they + // should have the correct types for the function we just called + // (which filled in the return values). + Return::lift( + store.0, + &self.func, + map_maybe_uninit!(space.ret).assume_init_ref(), + ) + } + } +} + +#[repr(C)] +union ParamsAndResults { + params: Params, + ret: Return, +} + +/// A trait representing a static list of parameters that can be passed to a +/// [`TypedFunc`]. +/// +/// This trait is implemented for a number of tuple types and is not expected +/// to be implemented externally. The contents of this trait are hidden as it's +/// intended to be an implementation detail of Wasmtime. The contents of this +/// trait are not covered by Wasmtime's stability guarantees. +/// +/// For more information about this trait see [`Func::typed`] and +/// [`TypedFunc`]. +// +// Note that this is an `unsafe` trait, and the unsafety means that +// implementations of this trait must be correct or otherwise [`TypedFunc`] +// would not be memory safe. The main reason this is `unsafe` is the +// `typecheck` function which must operate correctly relative to the `AsTuple` +// interpretation of the implementor. +pub unsafe trait ComponentParams { + /// The tuple type corresponding to this list of parameters if this list is + /// interpreted as a tuple in the canonical ABI. + #[doc(hidden)] + type AsTuple: ComponentValue; + + /// Performs a typecheck to ensure that this `ComponentParams` implementor + /// matches the types of the types in `params`. + #[doc(hidden)] + fn typecheck(params: &[(Option, InterfaceType)], types: &ComponentTypes) -> Result<()>; + + /// Views this instance of `ComponentParams` as a tuple, allowing + /// delegation to all of the methods in `ComponentValue`. + #[doc(hidden)] + fn as_tuple(&self) -> &Self::AsTuple; + + /// Convenience method to `ComponentValue::lower` when viewing this + /// parameter list as a tuple. + #[doc(hidden)] + fn lower( + &self, + store: &mut StoreContextMut, + func: &Func, + dst: &mut MaybeUninit<::Lower>, + ) -> Result<()> { + self.as_tuple().lower(store, func, dst) + } + + /// Convenience method to `ComponentValue::store` when viewing this + /// parameter list as a tuple. + #[doc(hidden)] + fn store(&self, memory: &mut Memory<'_, T>, offset: usize) -> Result<()> { + self.as_tuple().store(memory, offset) + } + + /// Convenience function to return the canonical abi alignment of this list + /// of parameters when viewed as a tuple. + #[doc(hidden)] + #[inline] + fn align() -> u32 { + Self::AsTuple::align() + } + + /// Convenience function to return the canonical abi byte size of this list + /// of parameters when viewed as a tuple. + #[doc(hidden)] + #[inline] + fn size() -> usize { + Self::AsTuple::size() + } +} + +// Macro to generate an implementation of `ComponentParams` for all supported +// lengths of tuples of types in Wasmtime. +macro_rules! impl_component_params { + ($n:tt $($t:ident)*) => {paste::paste!{ + #[allow(non_snake_case)] + unsafe impl<$($t,)*> ComponentParams for ($($t,)*) where $($t: ComponentValue),* { + type AsTuple = ($($t,)*); + + fn typecheck( + params: &[(Option, InterfaceType)], + _types: &ComponentTypes, + ) -> Result<()> { + if params.len() != $n { + bail!("expected {} types, found {}", $n, params.len()); + } + let mut params = params.iter().map(|i| &i.1); + $($t::typecheck(params.next().unwrap(), _types)?;)* + debug_assert!(params.next().is_none()); + Ok(()) + } + + #[inline] + fn as_tuple(&self) -> &Self::AsTuple { + self + } + } + }}; +} + +for_each_function_signature!(impl_component_params); + +/// A trait representing types which can be passed to and read from components +/// with the canonical ABI. +/// +/// This trait is implemented for Rust types which can be communicated to +/// components. This is implemented for Rust types which correspond to +/// interface types in the component model of WebAssembly. The [`Func::typed`] +/// and [`TypedFunc`] Rust items are the main consumers of this trait. +/// +/// For more information on this trait see the examples in [`Func::typed`]. +/// +/// The contents of this trait are hidden as it's intended to be an +/// implementation detail of Wasmtime. The contents of this trait are not +/// covered by Wasmtime's stability guarantees. +// +// Note that this is an `unsafe` trait as `TypedFunc`'s safety heavily relies on +// the correctness of the implementations of this trait. Some ways in which this +// trait must be correct to be safe are: +// +// * The `Lower` associated type must be a `ValRaw` sequence. It doesn't have to +// literally be `[ValRaw; N]` but when laid out in memory it must be adjacent +// `ValRaw` values and have a multiple of the size of `ValRaw` and the same +// alignment. +// +// * The `lower` function must initialize the bits within `Lower` that are going +// to be read by the trampoline that's used to enter core wasm. A trampoline +// is passed `*mut Lower` and will read the canonical abi arguments in +// sequence, so all of the bits must be correctly initialized. +// +// * The `size` and `align` functions must be correct for this value stored in +// the canonical ABI. The `Cursor` iteration of these bytes rely on this +// for correctness as they otherwise eschew bounds-checking. +// +// There are likely some other correctness issues which aren't documented as +// well, this isn't intended to be an exhaustive list. It suffices to say, +// though, that correctness bugs in this trait implementation are highly likely +// to lead to security bugs, which again leads to the `unsafe` in the trait. +// +// Also note that this trait specifically is not sealed because we'll +// eventually have a proc macro that generates implementations of this trait +// for external types in a `#[derive]`-like fashion. +// +// FIXME: need to write a #[derive(ComponentValue)] +pub unsafe trait ComponentValue { + /// Representation of the "lowered" form of this component value. + /// + /// Lowerings lower into core wasm values which are represented by `ValRaw`. + /// This `Lower` type must be a list of `ValRaw` as either a literal array + /// or a struct where every field is a `ValRaw`. This must be `Copy` (as + /// `ValRaw` is `Copy`) and support all byte patterns. This being correct is + /// one reason why the trait is unsafe. + #[doc(hidden)] + type Lower: Copy; + + /// Representation of the "lifted" form of this component value. + /// + /// This is somewhat subtle and is not always what you might expect. This is + /// only used for values which are actually possible to return by-value in + /// the canonical ABI. Everything returned indirectly (e.g. takes up two or + /// more core wasm values to represent) is instead returned as `Value` + /// and this associated type isn't used. + /// + /// For that reason this `Lift` is defined as `Self` for most primitives, + /// but it's actually `Infallible` (some empty void-like enum) for + /// strings/lists because those aren't possible to lift from core wasm + /// values. + /// + /// This is also used for ADT-definitions of tuples/options/results since + /// it's technically possible to return `(u32,)` or something like + /// `option<()>` which is all an immediate return value as well. In general + /// this is expected to largely be `Infallible` (or similar) and functions + /// return `Value` instead at the `TypedFunc` layer. + #[doc(hidden)] + type Lift; + + /// Performs a type-check to see whether this comopnent value type matches + /// the interface type `ty` provided. + #[doc(hidden)] + fn typecheck(ty: &InterfaceType, types: &ComponentTypes) -> Result<()>; + + /// Performs the "lower" function in the canonical ABI. + /// + /// This method will lower the given value into wasm linear memory. The + /// `store` and `func` are provided in case memory is needed (e.g. for + /// strings/lists) so `realloc` can be called. The `dst` is the destination + /// to store the lowered results. + /// + /// Note that `dst` is a pointer to uninitialized memory. It's expected + /// that `dst` is fully initialized by the time this function returns, hence + /// the `unsafe` on the trait implementation. + #[doc(hidden)] + fn lower( + &self, + store: &mut StoreContextMut, + func: &Func, + dst: &mut MaybeUninit, + ) -> Result<()>; + + /// Returns the size, in bytes, that this type has in the canonical ABI. + /// + /// Note that it's expected that this function is "simple" to be easily + /// optimizable by LLVM (e.g. inlined and const-evaluated). + // + // FIXME: needs some sort of parameter indicating the memory size + #[doc(hidden)] + fn size() -> usize; + + /// Returns the alignment, in bytes, that this type has in the canonical + /// ABI. + /// + /// Note that it's expected that this function is "simple" to be easily + /// optimizable by LLVM (e.g. inlined and const-evaluated). + #[doc(hidden)] + fn align() -> u32; + + /// Performs the "store" operation in the canonical ABI. + /// + /// This function will store `self` into the linear memory described by + /// `memory` at the `offset` provided. + /// + /// It is expected that `offset` is a valid offset in memory for + /// `Self::size()` bytes. At this time that's not an unsafe contract as it's + /// always re-checked on all stores, but this is something that will need to + /// be improved in the future to remove extra bounds checks. For now this + /// function will panic if there's a bug and `offset` isn't valid within + /// memory. + #[doc(hidden)] + fn store(&self, memory: &mut Memory<'_, T>, offset: usize) -> Result<()>; + + /// Returns the number of core wasm abi values will be used to represent + /// this type in its lowered form. + /// + /// This divides the size of `Self::Lower` by the size of `ValRaw`. + #[doc(hidden)] + fn flatten_count() -> usize { + assert!(mem::size_of::() % mem::size_of::() == 0); + assert!(mem::align_of::() == mem::align_of::()); + mem::size_of::() / mem::size_of::() + } + + /// Performs the "lift" oepration in the canonical ABI. + /// + /// Like `Self::Lift` this is somewhat special, it's actually only ever + /// called if `Self::Lower` is zero or one `ValRaw` instances. If the + /// lowered representation of this type needs more instances of `ValRaw` + /// then the value is always returned through memory which means a `Cursor` + /// is instead used to iterate over the contents. + /// + /// This takes the lowered representation as input and returns the + /// associated `Lift` type for this implementation. For types where `Lift` + /// is `Infallible` or similar this simply panics as it should never be + /// called at runtime. + #[doc(hidden)] + fn lift(src: &Self::Lower) -> Result; +} + +/// A helper structure to package up proof-of-memory. This holds a store pointer +/// and a `Func` pointer where the function has the pointers to memory. +/// +/// Note that one of the purposes of this type is to make `lower_list` +/// vectorizable by "caching" the last view of memory. CUrrently it doesn't do +/// that, though, because I couldn't get `lower_list::` to vectorize. I've +/// left this in for convenience in the hope that this can be updated in the +/// future. +#[doc(hidden)] +pub struct Memory<'a, T> { + store: StoreContextMut<'a, T>, + func: &'a Func, +} + +impl<'a, T> Memory<'a, T> { + fn new(store: StoreContextMut<'a, T>, func: &'a Func) -> Memory<'a, T> { + Memory { func, store } + } + + #[inline] + fn string_encoding(&self) -> StringEncoding { + self.store.0[self.func.0].options.string_encoding + } + + #[inline] + fn memory(&mut self) -> &mut [u8] { + self.func.memory_mut(self.store.0) + } + + fn realloc( + &mut self, + old: usize, + old_size: usize, + old_align: u32, + new_size: usize, + ) -> Result { + let ret = self + .func + .realloc(&mut self.store, old, old_size, old_align, new_size) + .map(|(_, ptr)| ptr); + return ret; + } + + fn get(&mut self, offset: usize) -> &mut [u8; N] { + // FIXME: this bounds check shouldn't actually be necessary, all + // callers of `ComponentValue::store` have already performed a bounds + // check so we're guaranteed that `offset..offset+N` is in-bounds. That + // being said we at least should do bounds checks in debug mode and + // it's not clear to me how to easily structure this so that it's + // "statically obvious" the bounds check isn't necessary. + // + // For now I figure we can leave in this bounds check and if it becomes + // an issue we can optimize further later, probably with judicious use + // of `unsafe`. + (&mut self.memory()[offset..][..N]).try_into().unwrap() + } +} + +// Macro to help generate "forwarding implementations" of `ComponentValue` to +// another type, used for wrappers in Rust like `&T`, `Box`, etc. +macro_rules! forward_component_param { + ($(($($generics:tt)*) $a:ty => $b:ty,)*) => ($( + unsafe impl <$($generics)*> ComponentValue for $a { + type Lower = <$b as ComponentValue>::Lower; + type Lift = <$b as ComponentValue>::Lift; + + #[inline] + fn typecheck(ty: &InterfaceType, types: &ComponentTypes) -> Result<()> { + <$b as ComponentValue>::typecheck(ty, types) + } + + fn lower( + &self, + store: &mut StoreContextMut, + func: &Func, + dst: &mut MaybeUninit, + ) -> Result<()> { + <$b as ComponentValue>::lower(self, store, func, dst) + } + + #[inline] + fn size() -> usize { + <$b as ComponentValue>::size() + } + + #[inline] + fn align() -> u32 { + <$b as ComponentValue>::align() + } + + fn store(&self, memory: &mut Memory<'_, U>, offset: usize) -> Result<()> { + <$b as ComponentValue>::store(self, memory, offset) + } + + fn lift(src: &Self::Lower) -> Result { + <$b as ComponentValue>::lift(src) + } + } + )*) +} + +forward_component_param! { + (T: ComponentValue + ?Sized) &'_ T => T, + (T: ComponentValue + ?Sized) Box => T, + (T: ComponentValue + ?Sized) std::rc::Rc => T, + (T: ComponentValue + ?Sized) std::sync::Arc => T, + () String => str, + (T: ComponentValue) Vec => [T], +} + +unsafe impl ComponentValue for () { + // A 0-sized array is used here to represent that it has zero-size but it + // still has the alignment of `ValRaw`. + type Lower = [ValRaw; 0]; + type Lift = (); + + fn typecheck(ty: &InterfaceType, _types: &ComponentTypes) -> Result<()> { + match ty { + // FIXME(WebAssembly/component-model#21) this may either want to + // match more types, not actually exist as a trait impl, or + // something like that. Figuring out on that issue about the + // relationship between the 0-tuple, unit, and empty structs. + InterfaceType::Unit => Ok(()), + other => bail!("expected `unit` found `{}`", desc(other)), + } + } + + #[inline] + fn lower( + &self, + _store: &mut StoreContextMut, + _func: &Func, + _dst: &mut MaybeUninit, + ) -> Result<()> { + Ok(()) + } + + #[inline] + fn size() -> usize { + 0 + } + + #[inline] + fn align() -> u32 { + 1 + } + + #[inline] + fn store(&self, _memory: &mut Memory<'_, T>, _offset: usize) -> Result<()> { + Ok(()) + } + + #[inline] + fn lift(_src: &Self::Lower) -> Result<()> { + Ok(()) + } +} + +// Macro to help generate `ComponentValue` implementations for primitive types +// such as integers, char, bool, etc. +macro_rules! integers { + ($($primitive:ident = $ty:ident in $field:ident $(as $unsigned:ident)?,)*) => ($( + unsafe impl ComponentValue for $primitive { + type Lower = ValRaw; + type Lift = $primitive; + + fn typecheck(ty: &InterfaceType, _types: &ComponentTypes) -> Result<()> { + match ty { + InterfaceType::$ty => Ok(()), + other => bail!("expected `{}` found `{}`", desc(&InterfaceType::$ty), desc(other)) + } + } + + fn lower( + &self, + _store: &mut StoreContextMut, + _func: &Func, + dst: &mut MaybeUninit, + ) -> Result<()> { + map_maybe_uninit!(dst.$field) + .write((*self $(as $unsigned)? as $field).to_le()); + Ok(()) + } + + #[inline] + fn size() -> usize { mem::size_of::<$primitive>() } + + // Note that this specifically doesn't use `align_of` as some + // host platforms have a 4-byte alignment for primitive types but + // the canonical abi always has the same size/alignment for these + // types. + #[inline] + fn align() -> u32 { mem::size_of::<$primitive>() as u32 } + + fn store(&self, memory: &mut Memory<'_, T>, offset: usize) -> Result<()> { + *memory.get(offset) = self.to_le_bytes(); + Ok(()) + } + + #[inline] + fn lift(src: &Self::Lower) -> Result { + // Convert from little-endian and then view the signed storage + // as an optionally-unsigned type. + let field = unsafe { + $field::from_le(src.$field) $(as $unsigned)? + }; + + // Perform a lossless cast from our field storage to the + // destination type. Note that `try_from` here is load bearing + // which rejects conversions like `500u32` to `u8` because + // that's out-of-bounds for `u8`. + Ok($primitive::try_from(field)?) + } + } + + impl Cursor<'_, $primitive> { + /// Returns the underlying value that this cursor points to. + #[inline] + pub fn get(&self) -> $primitive { + $primitive::from_le_bytes(self.item_bytes().try_into().unwrap()) + } + } + )*) +} + +integers! { + i8 = S8 in i32, + u8 = U8 in i32 as u32, + i16 = S16 in i32, + u16 = U16 in i32 as u32, + i32 = S32 in i32, + u32 = U32 in i32 as u32, + i64 = S64 in i64, + u64 = U64 in i64 as u64, +} + +macro_rules! floats { + ($($float:ident/$storage:ident = $ty:ident)*) => ($(const _: () = { + /// All floats in-and-out of the canonical ABI always have their NaN + /// payloads canonicalized. Conveniently the `NAN` constant in Rust has + /// the same representation as canonical NAN, so we can use that for the + /// NAN value. + #[inline] + fn canonicalize(float: $float) -> $float { + if float.is_nan() { + $float::NAN + } else { + float + } + } + + unsafe impl ComponentValue for $float { + type Lower = ValRaw; + type Lift = $float; + + fn typecheck(ty: &InterfaceType, _types: &ComponentTypes) -> Result<()> { + match ty { + InterfaceType::$ty => Ok(()), + other => bail!("expected `{}` found `{}`", desc(&InterfaceType::$ty), desc(other)) + } + } + + fn lower( + &self, + _store: &mut StoreContextMut, + _func: &Func, + dst: &mut MaybeUninit, + ) -> Result<()> { + map_maybe_uninit!(dst.$float) + .write(canonicalize(*self).to_bits().to_le()); + Ok(()) + } + + #[inline] + fn size() -> usize { mem::size_of::<$float>() } + + // Note that like integers size is used here instead of alignment to + // respect the canonical ABI, not host platforms. + #[inline] + fn align() -> u32 { mem::size_of::<$float>() as u32 } + + fn store(&self, memory: &mut Memory<'_, T>, offset: usize) -> Result<()> { + let ptr = memory.get(offset); + *ptr = canonicalize(*self).to_bits().to_le_bytes(); + Ok(()) + } + + #[inline] + fn lift(src: &Self::Lower) -> Result { + let field = $storage::from_le(unsafe { src.$float }); + Ok(canonicalize($float::from_bits(field))) + } + } + + impl Cursor<'_, $float> { + /// Returns the underlying value that this cursor points to. + /// + /// Note that NaN values in the component model are canonicalized + /// so any NaN read is guaranteed to be a "canonical NaN". + #[inline] + pub fn get(&self) -> $float { + canonicalize($float::from_le_bytes(self.item_bytes().try_into().unwrap())) + } + } + };)*) +} + +floats! { + f32/u32 = Float32 + f64/u64 = Float64 +} + +unsafe impl ComponentValue for bool { + type Lower = ValRaw; + type Lift = bool; + + fn typecheck(ty: &InterfaceType, _types: &ComponentTypes) -> Result<()> { + match ty { + InterfaceType::Bool => Ok(()), + other => bail!("expected `bool` found `{}`", desc(other)), + } + } + + fn lower( + &self, + _store: &mut StoreContextMut, + _func: &Func, + dst: &mut MaybeUninit, + ) -> Result<()> { + map_maybe_uninit!(dst.i32).write((*self as i32).to_le()); + Ok(()) + } + + #[inline] + fn size() -> usize { + 1 + } + + #[inline] + fn align() -> u32 { + 1 + } + + fn store(&self, memory: &mut Memory<'_, T>, offset: usize) -> Result<()> { + memory.get::<1>(offset)[0] = *self as u8; + Ok(()) + } + + #[inline] + fn lift(src: &Self::Lower) -> Result { + match i32::from_le(unsafe { src.i32 }) { + 0 => Ok(false), + 1 => Ok(true), + _ => bail!("invalid boolean value"), + } + } +} + +impl Cursor<'_, bool> { + /// Returns the underlying value that this cursor points to. + /// + /// # Errors + /// + /// Returns an error if the wasm memory does not have the boolean stored in + /// the correct canonical ABI format. + #[inline] + pub fn get(&self) -> Result { + match self.item_bytes()[0] { + 0 => Ok(false), + 1 => Ok(true), + _ => bail!("invalid boolean value"), + } + } +} + +unsafe impl ComponentValue for char { + type Lower = ValRaw; + type Lift = char; + + fn typecheck(ty: &InterfaceType, _types: &ComponentTypes) -> Result<()> { + match ty { + InterfaceType::Char => Ok(()), + other => bail!("expected `char` found `{}`", desc(other)), + } + } + + fn lower( + &self, + _store: &mut StoreContextMut, + _func: &Func, + dst: &mut MaybeUninit, + ) -> Result<()> { + map_maybe_uninit!(dst.i32).write((u32::from(*self) as i32).to_le()); + Ok(()) + } + + #[inline] + fn size() -> usize { + 4 + } + + #[inline] + fn align() -> u32 { + 4 + } + + fn store(&self, memory: &mut Memory<'_, T>, offset: usize) -> Result<()> { + *memory.get::<4>(offset) = u32::from(*self).to_le_bytes(); + Ok(()) + } + + #[inline] + fn lift(src: &Self::Lower) -> Result { + let bits = i32::from_le(unsafe { src.i32 }) as u32; + Ok(char::try_from(bits)?) + } +} + +impl Cursor<'_, char> { + /// Returns the underlying value that this cursor points to. + /// + /// # Errors + /// + /// Returns an error if the wasm memory does not have the char stored in + /// the correct canonical ABI format (e.g it's an invalid char) + #[inline] + pub fn get(&self) -> Result { + let bits = u32::from_le_bytes(self.item_bytes().try_into().unwrap()); + Ok(char::try_from(bits)?) + } +} + +unsafe impl ComponentValue for str { + type Lower = [ValRaw; 2]; + type Lift = Infallible; + + fn typecheck(ty: &InterfaceType, _types: &ComponentTypes) -> Result<()> { + match ty { + InterfaceType::String => Ok(()), + other => bail!("expected `string` found `{}`", desc(other)), + } + } + + fn lower( + &self, + store: &mut StoreContextMut, + func: &Func, + dst: &mut MaybeUninit<[ValRaw; 2]>, + ) -> Result<()> { + let (ptr, len) = lower_string(&mut Memory::new(store.as_context_mut(), func), self)?; + // See "WRITEPTR64" above for why this is always storing a 64-bit + // integer. + map_maybe_uninit!(dst[0].i64).write((ptr as i64).to_le()); + map_maybe_uninit!(dst[1].i64).write((len as i64).to_le()); + Ok(()) + } + + fn size() -> usize { + 8 + } + + fn align() -> u32 { + 4 + } + + fn store(&self, mem: &mut Memory<'_, T>, offset: usize) -> Result<()> { + let (ptr, len) = lower_string(mem, self)?; + // FIXME: needs memory64 handling + *mem.get(offset + 0) = (ptr as i32).to_le_bytes(); + *mem.get(offset + 4) = (len as i32).to_le_bytes(); + Ok(()) + } + + fn lift(_src: &Self::Lower) -> Result { + unreachable!("never lifted, should use `Value` instead") + } +} + +fn lower_string(mem: &mut Memory<'_, T>, string: &str) -> Result<(usize, usize)> { + match mem.string_encoding() { + StringEncoding::Utf8 => { + let ptr = mem.realloc(0, 0, 1, string.len())?; + mem.memory()[ptr..][..string.len()].copy_from_slice(string.as_bytes()); + Ok((ptr, string.len())) + } + StringEncoding::Utf16 => { + let size = string.len() * 2; + let mut ptr = mem.realloc(0, 0, 2, size)?; + let bytes = &mut mem.memory()[ptr..][..size]; + let mut copied = 0; + for (u, bytes) in string.encode_utf16().zip(bytes.chunks_mut(2)) { + let u_bytes = u.to_le_bytes(); + bytes[0] = u_bytes[0]; + bytes[1] = u_bytes[1]; + copied += 1; + } + if (copied * 2) < size { + ptr = mem.realloc(ptr, size, 2, copied * 2)?; + } + Ok((ptr, copied)) + } + StringEncoding::CompactUtf16 => { + unimplemented!("compact-utf-16"); + } + } +} + +impl<'a> Cursor<'a, String> { + /// Returns the underlying string that this cursor points to. + /// + /// Note that this will internally decode the string from the wasm's + /// encoding to utf-8 and additionally perform validation. + /// + /// # Errors + /// + /// Returns an error if this string's pointer/length are out of bounds or + /// if the string wasn't encoded correctly (e.g. invalid utf-8). + pub fn to_str(&self) -> Result> { + 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()); + let (ptr, len) = (usize::try_from(ptr)?, usize::try_from(len)?); + match self.string_encoding() { + StringEncoding::Utf8 => self.decode_utf8(ptr, len), + StringEncoding::Utf16 => self.decode_utf16(ptr, len), + StringEncoding::CompactUtf16 => { + if len & UTF16_TAG != 0 { + self.decode_utf16(ptr, len ^ UTF16_TAG) + } else { + self.decode_latin1(ptr, len) + } + } + } + } + + fn decode_utf8(&self, ptr: usize, len: usize) -> Result> { + let memory = self.all_memory(); + let memory = memory + .get(ptr..) + .and_then(|s| s.get(..len)) + .ok_or_else(|| anyhow::anyhow!("string out of bounds"))?; + Ok(str::from_utf8(memory)?.into()) + } + + fn decode_utf16(&self, ptr: usize, len: usize) -> Result> { + let memory = self.all_memory(); + let memory = len + .checked_mul(2) + .and_then(|byte_len| memory.get(ptr..)?.get(..byte_len)) + .ok_or_else(|| anyhow::anyhow!("string out of bounds"))?; + Ok(std::char::decode_utf16( + memory + .chunks(2) + .map(|chunk| u16::from_le_bytes(chunk.try_into().unwrap())), + ) + .collect::>()? + .into()) + } + + fn decode_latin1(&self, ptr: usize, len: usize) -> Result> { + drop((ptr, len)); + unimplemented!() + } +} + +unsafe impl ComponentValue for [T] +where + T: ComponentValue, +{ + type Lower = [ValRaw; 2]; + type Lift = Infallible; + + fn typecheck(ty: &InterfaceType, types: &ComponentTypes) -> Result<()> { + match ty { + InterfaceType::List(t) => T::typecheck(&types[*t], types), + other => bail!("expected `list` found `{}`", desc(other)), + } + } + + fn lower( + &self, + store: &mut StoreContextMut, + func: &Func, + dst: &mut MaybeUninit<[ValRaw; 2]>, + ) -> Result<()> { + let (ptr, len) = lower_list(&mut Memory::new(store.as_context_mut(), func), self)?; + // See "WRITEPTR64" above for why this is always storing a 64-bit + // integer. + map_maybe_uninit!(dst[0].i64).write((ptr as i64).to_le()); + map_maybe_uninit!(dst[1].i64).write((len as i64).to_le()); + Ok(()) + } + + #[inline] + fn size() -> usize { + 8 + } + + #[inline] + fn align() -> u32 { + 4 + } + + fn store(&self, mem: &mut Memory<'_, U>, offset: usize) -> Result<()> { + let (ptr, len) = lower_list(mem, self)?; + *mem.get(offset + 0) = (ptr as i32).to_le_bytes(); + *mem.get(offset + 4) = (len as i32).to_le_bytes(); + Ok(()) + } + + fn lift(_src: &Self::Lower) -> Result { + unreachable!("never lifted, should use `Value<[T]>` instead") + } +} + +// FIXME: this is not a memcpy for `T` where `T` is something like `u8`. +// +// Some attempts to fix this have proved not fruitful. In isolation an attempt +// was made where: +// +// * `Memory` stored a `*mut [u8]` as its "last view" of memory to avoid +// reloading the base pointer constantly. This view is reset on `realloc`. +// * The bounds-checks in `Memory::get` were removed (replaced with unsafe +// indexing) +// +// Even then though this didn't correctly vectorized for `Vec`. It's not +// entirely clear why but it appeared that it's related to reloading the base +// pointer fo memory (I guess from `Memory` itself?). Overall I'm not really +// clear on what's happening there, but this is surely going to be a performance +// bottleneck in the future. +fn lower_list(mem: &mut Memory<'_, U>, list: &[T]) -> Result<(usize, usize)> +where + T: ComponentValue, +{ + let elem_size = T::size(); + let size = list + .len() + .checked_mul(elem_size) + .ok_or_else(|| anyhow::anyhow!("size overflow copying a list"))?; + let ptr = mem.realloc(0, 0, T::align(), size)?; + let mut cur = ptr; + for item in list { + item.store(mem, cur)?; + cur += elem_size; + } + Ok((ptr, list.len())) +} + +impl<'a, T: ComponentValue> Cursor<'a, Vec> { + /// Returns the item length of this vector + pub fn len(&self) -> usize { + // FIXME: needs memory64 treatment + u32::from_le_bytes(self.item_bytes()[4..].try_into().unwrap()) as usize + } + + /// Returns an iterator over the elements of this vector. + /// + /// The returned iterator is an exact-size iterator and is of length + /// `self.len()`. Note that the iterator is also an iterator of [`Cursor`] + /// types representing that the desired values all continue to live in wasm + /// linear memory. + /// + /// # Errors + /// + /// Returns an error if this list's pointer/length combination is + /// out-of-bounds, or if the length times the element size is too large to + /// fit in linear memory. + pub fn iter(&self) -> Result> + '_> { + 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)?) + }; + len.checked_mul(T::size()) + .and_then(|byte_len| self.all_memory().get(ptr..)?.get(..byte_len)) + .ok_or_else(|| anyhow::anyhow!("list out of bounds"))?; + + Ok((0..len).map(move |i| { + // The `move_to` function is not safe because `Cursor` is a static + // proof that the offset/length is in-bounds. This bounds-check, + // however, was just performed above so we know that the offset is + // indeed valid, meaning this `unsafe` should be ok. + unsafe { self.move_to(ptr + T::size() * i) } + })) + } +} + +impl<'a> Cursor<'a, Vec> { + /// 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()); + let align = align as usize; + (a + (align - 1)) & !(align - 1) +} + +unsafe impl ComponentValue for Option +where + T: ComponentValue, +{ + type Lower = TupleLower2<::Lower, T::Lower>; + type Lift = Option; + + fn typecheck(ty: &InterfaceType, types: &ComponentTypes) -> Result<()> { + match ty { + InterfaceType::Option(t) => T::typecheck(&types[*t], types), + other => bail!("expected `option` found `{}`", desc(other)), + } + } + + fn lower( + &self, + store: &mut StoreContextMut, + func: &Func, + dst: &mut MaybeUninit, + ) -> Result<()> { + match self { + None => { + map_maybe_uninit!(dst.A1.i32).write(0_i32.to_le()); + // Note that this is unsafe as we're writing an arbitrary + // bit-pattern to an arbitrary type, but part of the unsafe + // contract of the `ComponentValue` trait is that we can assign + // any bit-pattern. By writing all zeros here we're ensuring + // that the core wasm arguments this translates to will all be + // zeros (as the canonical ABI requires). + unsafe { + map_maybe_uninit!(dst.A2).as_mut_ptr().write_bytes(0u8, 1); + } + } + Some(val) => { + map_maybe_uninit!(dst.A1.i32).write(1_i32.to_le()); + val.lower(store, func, map_maybe_uninit!(dst.A2))?; + } + } + Ok(()) + } + + #[inline] + fn size() -> usize { + align_to(1, T::align()) + T::size() + } + + #[inline] + fn align() -> u32 { + T::align() + } + + fn store(&self, mem: &mut Memory<'_, U>, offset: usize) -> Result<()> { + match self { + None => { + mem.get::<1>(offset)[0] = 0; + } + Some(val) => { + mem.get::<1>(offset)[0] = 1; + val.store(mem, offset + align_to(1, T::align()))?; + } + } + Ok(()) + } + + fn lift(src: &Self::Lower) -> Result { + Ok(match i32::from_le(unsafe { src.A1.i32 }) { + 0 => None, + 1 => Some(T::lift(&src.A2)?), + _ => bail!("invalid option discriminant"), + }) + } +} + +impl<'a, T: ComponentValue> Cursor<'a, Option> { + /// Returns the underlying value for this `Option` + /// + /// Note that the payload of the `Option` returned is itself a cursor as it + /// still points into linear memory. + /// + /// # Errors + /// + /// Returns an error if the discriminant for this `Option` in linear + /// memory is invalid. + #[inline] + pub fn get(&self) -> Result>> { + match self.item_bytes()[0] { + 0 => Ok(None), + 1 => Ok(Some(self.bump(align_to(1, T::align())))), + _ => bail!("invalid option discriminant"), + } + } +} + +#[derive(Clone, Copy)] +#[repr(C)] +pub struct ResultLower { + tag: ValRaw, + payload: ResultLowerPayload, +} + +#[derive(Clone, Copy)] +#[repr(C)] +union ResultLowerPayload { + ok: T, + err: E, +} + +unsafe impl ComponentValue for Result +where + T: ComponentValue, + E: ComponentValue, +{ + type Lower = ResultLower; + type Lift = Result; + + fn typecheck(ty: &InterfaceType, types: &ComponentTypes) -> Result<()> { + match ty { + InterfaceType::Expected(r) => { + let expected = &types[*r]; + T::typecheck(&expected.ok, types)?; + E::typecheck(&expected.err, types)?; + Ok(()) + } + other => bail!("expected `expected` found `{}`", desc(other)), + } + } + + fn lower( + &self, + store: &mut StoreContextMut, + func: &Func, + dst: &mut MaybeUninit, + ) -> Result<()> { + // Start out by zeroing out the payload. This will ensure that if either + // arm doesn't initialize some values then everything is still + // deterministically set. + // + // Additionally, this initialization of zero means that the specific + // types written by each `lower` call below on each arm still has the + // correct value even when "joined" with the other arm. + // + // Finally note that this is required by the canonical ABI to some + // degree where if the `Ok` arm initializes fewer values than the `Err` + // arm then all the remaining values must be initialized to zero, and + // that's what this does. + unsafe { + map_maybe_uninit!(dst.payload) + .as_mut_ptr() + .write_bytes(0u8, 1); + } + + match self { + Ok(e) => { + map_maybe_uninit!(dst.tag.i32).write(0_i32.to_le()); + e.lower(store, func, map_maybe_uninit!(dst.payload.ok))?; + } + Err(e) => { + map_maybe_uninit!(dst.tag.i32).write(1_i32.to_le()); + e.lower(store, func, map_maybe_uninit!(dst.payload.err))?; + } + } + Ok(()) + } + + #[inline] + fn size() -> usize { + align_to(1, Self::align()) + T::size().max(E::size()) + } + + #[inline] + fn align() -> u32 { + T::align().max(E::align()) + } + + fn store(&self, mem: &mut Memory<'_, U>, offset: usize) -> Result<()> { + match self { + Ok(e) => { + mem.get::<1>(offset)[0] = 0; + e.store(mem, offset + align_to(1, Self::align()))?; + } + Err(e) => { + mem.get::<1>(offset)[0] = 1; + e.store(mem, offset + align_to(1, Self::align()))?; + } + } + Ok(()) + } + + fn lift(src: &Self::Lower) -> Result { + // This implementation is not correct if there's actually information in + // the payload. This doesn't validate that if `payload` has a nonzero + // size that the "extended" bits are all zero. For example if + // `Result` is returned then that's represented as `i32 i64` + // and `0 i64::MAX` is an invalid return value. This implementation, + // however, would consider that valid since it would not try to read the + // upper bits of the i64. + // + // For now this is ok because `lift` is only called for types where + // `Lower` is at most one `ValRaw`. A `Result` always takes up at + // least one `ValRaw` for the discriminant so we know that if this is + // being used then both `T` and `E` have zero size. + assert!(mem::size_of_val(&src.payload) == 0); + + Ok(match i32::from_le(unsafe { src.tag.i32 }) { + 0 => Ok(unsafe { T::lift(&src.payload.ok)? }), + 1 => Err(unsafe { E::lift(&src.payload.err)? }), + _ => bail!("invalid expected discriminant"), + }) + } +} + +impl<'a, T, E> Cursor<'a, Result> +where + T: ComponentValue, + E: ComponentValue, +{ + /// Returns the underlying value for this `Result` + /// + /// Note that the payloads of the `Result` returned are themselves cursors + /// as they still point into linear memory. + /// + /// # Errors + /// + /// Returns an error if the discriminant for this `Result` in linear + /// memory is invalid. + #[inline] + pub fn get(&self) -> Result, Cursor<'a, E>>> { + let align = as ComponentValue>::align(); + match self.item_bytes()[0] { + 0 => Ok(Ok(self.bump(align_to(1, align)))), + 1 => Ok(Err(self.bump(align_to(1, align)))), + _ => bail!("invalid expected discriminant"), + } + } +} + +macro_rules! impl_component_ty_for_tuples { + // the unit tuple goes to the `Unit` type, not the `Tuple` type + // + // FIXME(WebAssembly/component-model#21) there's some active discussion on + // the relationship between the 0-tuple and the unit type in the component + // model. + (0) => {}; + + ($n:tt $($t:ident)*) => {paste::paste!{ + #[allow(non_snake_case)] + #[doc(hidden)] + #[derive(Clone, Copy)] + #[repr(C)] + pub struct []<$($t),*> { + $($t: $t,)* + } + + #[allow(non_snake_case)] + unsafe impl<$($t,)*> ComponentValue for ($($t,)*) + where $($t: ComponentValue),* + { + type Lower = []<$($t::Lower),*>; + type Lift = ($($t::Lift,)*); + + fn typecheck( + ty: &InterfaceType, + types: &ComponentTypes, + ) -> Result<()> { + match ty { + InterfaceType::Tuple(t) => { + let tuple = &types[*t]; + if tuple.types.len() != $n { + bail!("expected {}-tuple, found {}-tuple", $n, tuple.types.len()); + } + let mut tuple = tuple.types.iter(); + $($t::typecheck(tuple.next().unwrap(), types)?;)* + debug_assert!(tuple.next().is_none()); + Ok(()) + } + other => bail!("expected `tuple` found `{}`", desc(other)), + } + } + + fn lower( + &self, + store: &mut StoreContextMut, + func: &Func, + dst: &mut MaybeUninit, + ) -> Result<()> { + let ($($t,)*) = self; + $($t.lower(store, func, map_maybe_uninit!(dst.$t))?;)* + Ok(()) + } + + #[inline] + fn size() -> usize { + let mut size = 0; + $(size = align_to(size, $t::align()) + $t::size();)* + size + } + + #[inline] + fn align() -> u32 { + let mut align = 1; + $(align = align.max($t::align());)* + align + } + + fn store(&self, memory: &mut Memory<'_, U>, mut offset: usize) -> Result<()> { + let ($($t,)*) = self; + // TODO: this requires that `offset` is aligned which we may not + // want to do + $( + offset = align_to(offset, $t::align()); + $t.store(memory, offset)?; + offset += $t::size(); + )* + drop(offset); // silence warning about last assignment + Ok(()) + } + + #[inline] + fn lift(src: &Self::Lower) -> Result { + Ok(($($t::lift(&src.$t)?,)*)) + } + } + + impl<'a, $($t),*> Cursor<'a, ($($t,)*)> + where + $($t: ComponentValue),* + { + fn start_offset(&self) -> usize { + 0 + } + + define_tuple_cursor_accessors!(start_offset $($t)*); + } + }}; +} + +macro_rules! define_tuple_cursor_accessors { + ($offset:ident) => {}; + ($offset:ident $t:ident $($u:ident)*) => { + paste::paste! { + /// Returns a pointer to the `n`th field of the tuple contained + /// within this cursor. + #[inline] + pub fn [<$t:lower>](&self) -> Cursor<'a, $t> { + self.bump(align_to(self.$offset(), $t::align())) + } + + #[allow(dead_code)] + #[inline] + fn [<$t:lower _end>](&self) -> usize { + align_to(self.$offset(), $t::align()) + $t::size() + } + + define_tuple_cursor_accessors!([<$t:lower _end>] $($u)*); + } + }; +} + +for_each_function_signature!(impl_component_ty_for_tuples); + +fn desc(ty: &InterfaceType) -> &'static str { + match ty { + InterfaceType::U8 => "u8", + InterfaceType::S8 => "s8", + InterfaceType::U16 => "u16", + InterfaceType::S16 => "s16", + InterfaceType::U32 => "u32", + InterfaceType::S32 => "s32", + InterfaceType::U64 => "u64", + InterfaceType::S64 => "s64", + InterfaceType::Float32 => "f32", + InterfaceType::Float64 => "f64", + InterfaceType::Unit => "unit", + InterfaceType::Bool => "bool", + InterfaceType::Char => "char", + InterfaceType::String => "string", + InterfaceType::List(_) => "list", + InterfaceType::Tuple(_) => "tuple", + InterfaceType::Option(_) => "option", + InterfaceType::Expected(_) => "expected", + + InterfaceType::Record(_) => "record", + InterfaceType::Variant(_) => "variant", + InterfaceType::Flags(_) => "flags", + InterfaceType::Enum(_) => "enum", + InterfaceType::Union(_) => "union", + } +} + +/// A trait representing values which can be returned from a [`TypedFunc`]. +/// +/// For all values which implement the [`ComponentValue`] trait this is +/// implemented for either `T` or [`Value`]. For more information on which +/// to use see the documentation at [`Func::typed`]. +/// +/// The contents of this trait are hidden as it's intended to be an +/// implementation detail of Wasmtime. The contents of this trait are not +/// covered by Wasmtime's stability guarantees. +// +// Note that this is an `unsafe` trait because the safety of `TypedFunc` relies +// on `typecheck` being correct relative to `Lower`, among other things. +// +// Also note that this trait specifically is not sealed because we'll +// eventually have a proc macro that generates implementations of this trait +// for external types in a `#[derive]`-like fashion. +pub unsafe trait ComponentReturn: Sized { + /// The core wasm lowered value used to interpret this return value. + /// + /// This is `T::Lower` in the case of `ComponentReturn for T` and this is + /// otherwise a singular `ValRaw` for `Value` to store the i32 return + /// value. + #[doc(hidden)] + type Lower: Copy; + + /// Performs a type-check to ensure that this `ComponentReturn` value + /// matches the interface type specified. + /// + /// Note that even if `Self` matches the `ty` specified this function will + /// also perform a check to ensure that `Lower` is suitable for returning + /// `Self` in the core wasm ABI. For example `Value` has the type + /// `InterfaceType::U8` but is not suitable as a return type since + /// `Value` represents an indirect return value and `u8` is a direct + /// return. That check is done by this function. + #[doc(hidden)] + fn typecheck(ty: &InterfaceType, types: &ComponentTypes) -> Result<()>; + + /// Performs the lifting operation from the core wasm return value into + /// `Self`. + /// + /// Note that this can fail in the case that an indirect pointer was + /// returned and the indirect pointer is out-of-bounds. + #[doc(hidden)] + fn lift(store: &StoreOpaque, func: &Func, src: &Self::Lower) -> Result; +} + +// Note that the trait bound here requires that the lifted value of `T` is +// itself. This is true for primitives and ADTs above and is required to +// implement the `lift` function. That also means that implementations of +// `ComponentValue` for strings/lists statically can't use this impl because +// their `Lift` is not themselves. +unsafe impl> ComponentReturn for T { + type Lower = T::Lower; + + fn typecheck(ty: &InterfaceType, types: &ComponentTypes) -> Result<()> { + // Perform a check that the size of the return value is indeed at most + // one core wasm abi value. If there is more than one core wasm abi + // return value then the `Value` type must be used instead. + if T::flatten_count() > MAX_STACK_RESULTS { + let name = std::any::type_name::(); + bail!( + "cannot use `{name}` as a return value as it is \ + returned indirectly, use `Value<{name}>` instead" + ); + } + + // ... and if the ABI is appropriate then we can otherwise delegate to + // a normal type-check. + T::typecheck(ty, types) + } + + fn lift(_store: &StoreOpaque, _func: &Func, src: &Self::Lower) -> Result { + ::lift(src) + } +} + +unsafe impl ComponentReturn for Value { + type Lower = ValRaw; + + fn typecheck(ty: &InterfaceType, types: &ComponentTypes) -> Result<()> { + // Similar to the impl above, except this is the reverse. When using + // `Value` that means the return value is expected to be indirectly + // returned in linear memory. That means we need to verify that the + // canonical ABI indeed return `T` indirectly by double-checking that + // the core wasm abi makeup of the type requires more than one value. + if T::flatten_count() <= MAX_STACK_RESULTS { + let name = std::any::type_name::(); + bail!( + "cannot use `Value<{name}>` as a return value as it is not \ + returned indirectly, use `{name}` instead" + ); + } + + // ... and like above if the abi lines up then delegate to `T` for + // further type-checking. + T::typecheck(ty, types) + } + + fn lift(store: &StoreOpaque, func: &Func, src: &Self::Lower) -> Result { + // FIXME: needs to read an i64 for memory64 + let ptr = u32::from_le(unsafe { src.i32 as u32 }) as usize; + Value::new(store, func, ptr) + } +} + +pub use self::value::*; + +/// The `Value` and `Cursor` types have internal variants that are important to +/// uphold so they're defined in a small submodule here to statically prevent +/// access to their private internals by the surrounding module. +mod value { + use super::*; + use crate::StoreContext; + + /// A pointer to a type which is stored in WebAssembly linear memory. + /// + /// This structure is used as the return value from [`TypedFunc`] at this + /// time to represent a function that returns its value through linear + /// memory instead of directly through return values. + /// + /// A [`Value`] represents a valid chunk of WebAssembly linear memory. + /// From a [`Value`] a [`Cursor`] can be created which is used to + /// actually inspect the contents of WebAssembly linear memory. + // + // As an implementation note the `Value` type has an unsafe contract where + // `pointer` is valid for `T::size()` bytes within the memory pointed to by + // the `origin` function specified. This `Value` itself does not pin the + // memory as its purpose is to not pin the store. The pinning of the store + // happens later. + pub struct Value { + pointer: usize, + origin: Func, + _marker: marker::PhantomData, + } + + /// A type which is used to inspect the contents of `T` as it resides in + /// WebAssembly linear memory. + /// + /// The [`Cursor`] type is created by the [`Value::cursor`] method which + /// holds a shared borrow onto the [`Store`](crate::Store). This does + /// not necessarily represent that `T` itself is stored in linear memory, + /// for example `Cursor` doesn't mean that a host `String` type + /// is stored in linear memory but rather a canonical ABI string is stored + /// in linear memory. The [`Cursor`] has per-`T` methods on it to access + /// the contents of wasm linear memory. + /// + /// The existence of [`Cursor`] means that the pointer that the cursor + /// has is valid for `T::size()` bytes of linear memory. The actual memory + /// it points to may have invalid contents, but that's left for each + /// method-of-interpretation to determine. + // + // As an implementation detail, like `Value`, the existence of a `Cursor` + // is static proof that `offset` within `all_memory` is valid for + // `T::size()` bytes. This enables the `item_bytes` method to use unchecked + // indexing. + pub struct Cursor<'a, T> { + offset: usize, + all_memory: &'a [u8], + string_encoding: StringEncoding, + _marker: marker::PhantomData, + } + + impl Value + where + T: ComponentValue, + { + pub(super) fn new(store: &StoreOpaque, origin: &Func, pointer: usize) -> Result> { + // Construction of a `Value` indicates proof that the `pointer` is + // valid, so the check is performed here to ensure that it's safe + // to construct the `Value`. + origin + .memory(store) + .get(pointer..) + .and_then(|s| s.get(..T::size())) + .ok_or_else(|| anyhow::anyhow!("pointer out of bounds of memory"))?; + Ok(Value { + pointer, + origin: *origin, + _marker: marker::PhantomData, + }) + } + + /// Returns a [`Cursor`] that can be used to read linear memory. + /// + /// This method will borrow the `store` provided to get access to wasm + /// linear memory and the returned [`Cursor`] is used to iterate + /// over the wasm linear memory using accessor methods specific to + /// the type `T`. + /// + /// # Panics + /// + /// This function will panic if `store` doesn't own the wasm linear + /// memory that this `Value` points to. + pub fn cursor<'a, U: 'a>(&self, store: impl Into>) -> Cursor<'a, T> { + let store = store.into(); + let all_memory = self.origin.memory(store.0); + + // Note that construction of a `Cursor` is static proof that the + // `offset` is valid. This should be ok here because this `Value` + // was already validated and memory cannot shrink, so after the + // `Value` was created the memory should still be of an appropriate + // size. + Cursor { + offset: self.pointer, + all_memory, + string_encoding: store.0[self.origin.0].options.string_encoding, + _marker: marker::PhantomData, + } + } + } + + impl<'a, T: ComponentValue> Cursor<'a, T> { + /// Returns the bytes that `T` is stored within. + #[inline] + pub(super) fn item_bytes(&self) -> &[u8] { + // The existence of `Cursor` as a wrapper type is intended to + // serve as proof that this `unsafe` block is indeed safe. The + // unchecked indexing here is possible due to the bounds checks + // that happen during construction of a `Cursor`. + // + // ... but in debug mode we double-check just to be sure. + unsafe { + if cfg!(debug_assertions) { + drop(&self.all_memory[self.offset..][..T::size()]); + } + self.all_memory + .get_unchecked(self.offset..) + .get_unchecked(..T::size()) + } + } + + /// Returns all of linear memory, useful for strings/lists which have + /// indirect pointers. + #[inline] + pub(super) fn all_memory(&self) -> &'a [u8] { + self.all_memory + } + + /// Returns the string encoding in use. + pub(super) fn string_encoding(&self) -> StringEncoding { + self.string_encoding + } + + /// Increments this `Cursor` forward by `offset` bytes to point to a + /// `U` that is contained within `T`. + /// + /// # Panics + /// + /// Panics if `offset + U::size()` is larger than `T::size()`. + #[inline] + pub(super) fn bump(&self, offset: usize) -> Cursor<'a, U> + where + U: ComponentValue, + { + // Perform a bounds check that if we increase `self.offset` by + // `offset` and point to `U` that the result is still contained + // within this `Cursor`. After doing so it's safe to call + // `move_to` as the bounds check has been performed. + // + // Note that it's expected that this bounds-check can be optimized + // out in most cases. The `offset` argument is typically a constant + // thing like a field or payload offset, and then `{T,U}::size()` + // are also trivially const-evaluatable by LLVM. If this shows up + // in profiles more functions may need `#[inline]`. + assert!(offset + U::size() <= T::size()); + unsafe { self.move_to(self.offset + offset) } + } + + /// An unsafe method to construct a new `Cursor` pointing to within + /// the same linear memory that this `Cursor` points to but for a + /// different type `U` and at a different `offset`. + /// + /// # Unsafety + /// + /// This function is unsafe because `Cursor` is a static proof that the + /// `offset` is valid for `U::size()` bytes within linear memory. + /// Callers must uphold this invariant themselves and perform a bounds + /// check before being able to safely call this method. + #[inline] + pub(super) unsafe fn move_to(&self, offset: usize) -> Cursor<'a, U> + where + U: ComponentValue, + { + Cursor { + offset, + all_memory: self.all_memory, + string_encoding: self.string_encoding, + _marker: marker::PhantomData, + } + } + } +} diff --git a/crates/wasmtime/src/component/instance.rs b/crates/wasmtime/src/component/instance.rs index 8822e1fbb8d8..ab5e53325f1f 100644 --- a/crates/wasmtime/src/component/instance.rs +++ b/crates/wasmtime/src/component/instance.rs @@ -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, }; @@ -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( + &self, + mut store: S, + name: &str, + ) -> Result> + 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::(store) + .with_context(|| format!("failed to convert function `{}` to given type", name))?) + } } impl InstanceData { diff --git a/crates/wasmtime/src/component/mod.rs b/crates/wasmtime/src/component/mod.rs index 8e7aad148629..99c7da84b723 100644 --- a/crates/wasmtime/src/component/mod.rs +++ b/crates/wasmtime/src/component/mod.rs @@ -8,7 +8,15 @@ mod func; mod instance; mod store; pub use self::component::Component; -pub use self::func::Func; +pub use self::func::{ + ComponentParams, ComponentReturn, ComponentValue, Cursor, Func, TypedFunc, Value, +}; pub use self::instance::Instance; +// These items are expected to be used by an eventual +// `#[derive(ComponentValue)]`, they are not part of Wasmtime's API stability +// guarantees +#[doc(hidden)] +pub use {self::func::Memory, wasmtime_environ}; + pub(crate) use self::store::ComponentStoreData; diff --git a/tests/all/component_model.rs b/tests/all/component_model.rs index 2840aae6d3ae..98387f154abd 100644 --- a/tests/all/component_model.rs +++ b/tests/all/component_model.rs @@ -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); diff --git a/tests/all/component_model/func.rs b/tests/all/component_model/func.rs new file mode 100644 index 000000000000..69eb4d56a952 --- /dev/null +++ b/tests/all/component_model/func.rs @@ -0,0 +1,1934 @@ +use anyhow::Result; +use std::rc::Rc; +use std::sync::Arc; +use wasmtime::component::*; +use wasmtime::{Store, Trap, TrapCode}; + +const CANON_32BIT_NAN: u32 = 0b01111111110000000000000000000000; +const CANON_64BIT_NAN: u64 = 0b0111111111111000000000000000000000000000000000000000000000000000; + +// A simple bump allocator which can be used with modules below +const REALLOC_AND_FREE: &str = r#" + (global $last (mut i32) (i32.const 8)) + (func $realloc (export "canonical_abi_realloc") + (param $old_ptr i32) + (param $old_size i32) + (param $align i32) + (param $new_size i32) + (result i32) + + ;; Test if the old pointer is non-null + local.get $old_ptr + if + ;; If the old size is bigger than the new size then + ;; this is a shrink and transparently allow it + local.get $old_size + local.get $new_size + i32.gt_u + if + local.get $old_ptr + return + end + + ;; ... otherwise this is unimplemented + unreachable + end + + ;; align up `$last` + (global.set $last + (i32.and + (i32.add + (global.get $last) + (i32.add + (local.get $align) + (i32.const -1))) + (i32.xor + (i32.add + (local.get $align) + (i32.const -1)) + (i32.const -1)))) + + ;; save the current value of `$last` as the return value + global.get $last + + ;; ensure anything necessary is set to valid data by spraying a bit + ;; pattern that is invalid + global.get $last + i32.const 0xde + local.get $new_size + memory.fill + + ;; bump our pointer + (global.set $last + (i32.add + (global.get $last) + (local.get $new_size))) + ) + + (func (export "canonical_abi_free") (param i32 i32 i32)) +"#; + +#[test] +fn thunks() -> Result<()> { + let component = r#" + (component + (module $m + (func (export "thunk")) + (func (export "thunk-trap") unreachable) + ) + (instance $i (instantiate (module $m))) + (func (export "thunk") + (canon.lift (func) (func $i "thunk")) + ) + (func (export "thunk-trap") + (canon.lift (func) (func $i "thunk-trap")) + ) + ) + "#; + + let engine = super::engine(); + let component = Component::new(&engine, component)?; + let mut store = Store::new(&engine, ()); + let instance = Instance::new(&mut store, &component)?; + instance + .get_typed_func::<(), (), _>(&mut store, "thunk")? + .call(&mut store, ())?; + let err = instance + .get_typed_func::<(), (), _>(&mut store, "thunk-trap")? + .call(&mut store, ()) + .unwrap_err(); + assert!(err.downcast::()?.trap_code() == Some(TrapCode::UnreachableCodeReached)); + + Ok(()) +} + +#[test] +fn typecheck() -> Result<()> { + let component = r#" + (component + (module $m + (func (export "thunk")) + (func (export "take-string") (param i32 i32)) + (func (export "two-args") (param i32 i32 i32)) + (func (export "ret-one") (result i32) unreachable) + + (memory (export "memory") 1) + (func (export "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32) + unreachable) + (func (export "canonical_abi_free") (param i32 i32 i32) + unreachable) + ) + (instance $i (instantiate (module $m))) + (func (export "thunk") + (canon.lift (func) (func $i "thunk")) + ) + (func (export "take-string") + (canon.lift (func (param string)) (into $i) (func $i "take-string")) + ) + (func (export "take-two-args") + (canon.lift (func (param s32) (param (list u8))) (into $i) (func $i "two-args")) + ) + (func (export "ret-tuple") + (canon.lift (func (result (tuple u8 s8))) (into $i) (func $i "ret-one")) + ) + (func (export "ret-tuple1") + (canon.lift (func (result (tuple u32))) (into $i) (func $i "ret-one")) + ) + ) + "#; + + let engine = super::engine(); + let component = Component::new(&engine, component)?; + let mut store = Store::new(&engine, ()); + let instance = Instance::new(&mut store, &component)?; + let thunk = instance.get_func(&mut store, "thunk").unwrap(); + let take_string = instance.get_func(&mut store, "take-string").unwrap(); + let take_two_args = instance.get_func(&mut store, "take-two-args").unwrap(); + let ret_tuple = instance.get_func(&mut store, "ret-tuple").unwrap(); + let ret_tuple1 = instance.get_func(&mut store, "ret-tuple1").unwrap(); + assert!(thunk.typed::<(), u32, _>(&store).is_err()); + assert!(thunk.typed::<(u32,), (), _>(&store).is_err()); + assert!(thunk.typed::<(), (), _>(&store).is_ok()); + assert!(take_string.typed::<(), (), _>(&store).is_err()); + assert!(take_string.typed::<(), Value, _>(&store).is_err()); + assert!(take_string + .typed::<(String, String), Value, _>(&store) + .is_err()); + assert!(take_string.typed::<(String,), (), _>(&store).is_ok()); + assert!(take_string.typed::<(&str,), (), _>(&store).is_ok()); + assert!(take_string.typed::<(&[u8],), (), _>(&store).is_err()); + assert!(take_two_args.typed::<(), (), _>(&store).is_err()); + assert!(take_two_args.typed::<(i32, &[u8]), u32, _>(&store).is_err()); + assert!(take_two_args.typed::<(u32, &[u8]), (), _>(&store).is_err()); + assert!(take_two_args.typed::<(i32, &[u8]), (), _>(&store).is_ok()); + assert!(take_two_args + .typed::<(i32, &[u8]), Value<()>, _>(&store) + .is_err()); + assert!(ret_tuple.typed::<(), (), _>(&store).is_err()); + assert!(ret_tuple.typed::<(), (u8,), _>(&store).is_err()); + assert!(ret_tuple.typed::<(), (u8, i8), _>(&store).is_err()); + assert!(ret_tuple.typed::<(), Value<(u8, i8)>, _>(&store).is_ok()); + assert!(ret_tuple1.typed::<(), (u32,), _>(&store).is_ok()); + assert!(ret_tuple1.typed::<(), u32, _>(&store).is_err()); + assert!(ret_tuple1.typed::<(), Value, _>(&store).is_err()); + assert!(ret_tuple1.typed::<(), Value<(u32,)>, _>(&store).is_err()); + + Ok(()) +} + +#[test] +fn integers() -> Result<()> { + let component = r#" + (component + (module $m + (func (export "take-i32-100") (param i32) + local.get 0 + i32.const 100 + i32.eq + br_if 0 + unreachable + ) + (func (export "take-i64-100") (param i64) + local.get 0 + i64.const 100 + i64.eq + br_if 0 + unreachable + ) + (func (export "ret-i32-0") (result i32) i32.const 0) + (func (export "ret-i64-0") (result i64) i64.const 0) + (func (export "ret-i32-minus-1") (result i32) i32.const -1) + (func (export "ret-i64-minus-1") (result i64) i64.const -1) + (func (export "ret-i32-100000") (result i32) i32.const 100000) + ) + (instance $i (instantiate (module $m))) + (func (export "take-u8") (canon.lift (func (param u8)) (func $i "take-i32-100"))) + (func (export "take-s8") (canon.lift (func (param s8)) (func $i "take-i32-100"))) + (func (export "take-u16") (canon.lift (func (param u16)) (func $i "take-i32-100"))) + (func (export "take-s16") (canon.lift (func (param s16)) (func $i "take-i32-100"))) + (func (export "take-u32") (canon.lift (func (param u32)) (func $i "take-i32-100"))) + (func (export "take-s32") (canon.lift (func (param s32)) (func $i "take-i32-100"))) + (func (export "take-u64") (canon.lift (func (param u64)) (func $i "take-i64-100"))) + (func (export "take-s64") (canon.lift (func (param s64)) (func $i "take-i64-100"))) + + (func (export "ret-u8") (canon.lift (func (result u8)) (func $i "ret-i32-0"))) + (func (export "ret-s8") (canon.lift (func (result s8)) (func $i "ret-i32-0"))) + (func (export "ret-u16") (canon.lift (func (result u16)) (func $i "ret-i32-0"))) + (func (export "ret-s16") (canon.lift (func (result s16)) (func $i "ret-i32-0"))) + (func (export "ret-u32") (canon.lift (func (result u32)) (func $i "ret-i32-0"))) + (func (export "ret-s32") (canon.lift (func (result s32)) (func $i "ret-i32-0"))) + (func (export "ret-u64") (canon.lift (func (result u64)) (func $i "ret-i64-0"))) + (func (export "ret-s64") (canon.lift (func (result s64)) (func $i "ret-i64-0"))) + + (func (export "retm1-u8") (canon.lift (func (result u8)) (func $i "ret-i32-minus-1"))) + (func (export "retm1-s8") (canon.lift (func (result s8)) (func $i "ret-i32-minus-1"))) + (func (export "retm1-u16") (canon.lift (func (result u16)) (func $i "ret-i32-minus-1"))) + (func (export "retm1-s16") (canon.lift (func (result s16)) (func $i "ret-i32-minus-1"))) + (func (export "retm1-u32") (canon.lift (func (result u32)) (func $i "ret-i32-minus-1"))) + (func (export "retm1-s32") (canon.lift (func (result s32)) (func $i "ret-i32-minus-1"))) + (func (export "retm1-u64") (canon.lift (func (result u64)) (func $i "ret-i64-minus-1"))) + (func (export "retm1-s64") (canon.lift (func (result s64)) (func $i "ret-i64-minus-1"))) + + (func (export "retbig-u8") (canon.lift (func (result u8)) (func $i "ret-i32-100000"))) + (func (export "retbig-s8") (canon.lift (func (result s8)) (func $i "ret-i32-100000"))) + (func (export "retbig-u16") (canon.lift (func (result u16)) (func $i "ret-i32-100000"))) + (func (export "retbig-s16") (canon.lift (func (result s16)) (func $i "ret-i32-100000"))) + (func (export "retbig-u32") (canon.lift (func (result u32)) (func $i "ret-i32-100000"))) + (func (export "retbig-s32") (canon.lift (func (result s32)) (func $i "ret-i32-100000"))) + ) + "#; + + let engine = super::engine(); + let component = Component::new(&engine, component)?; + let mut store = Store::new(&engine, ()); + let instance = Instance::new(&mut store, &component)?; + + // Passing in 100 is valid for all primitives + instance + .get_typed_func::<(u8,), (), _>(&mut store, "take-u8")? + .call(&mut store, (100,))?; + instance + .get_typed_func::<(i8,), (), _>(&mut store, "take-s8")? + .call(&mut store, (100,))?; + instance + .get_typed_func::<(u16,), (), _>(&mut store, "take-u16")? + .call(&mut store, (100,))?; + instance + .get_typed_func::<(i16,), (), _>(&mut store, "take-s16")? + .call(&mut store, (100,))?; + instance + .get_typed_func::<(u32,), (), _>(&mut store, "take-u32")? + .call(&mut store, (100,))?; + instance + .get_typed_func::<(i32,), (), _>(&mut store, "take-s32")? + .call(&mut store, (100,))?; + instance + .get_typed_func::<(u64,), (), _>(&mut store, "take-u64")? + .call(&mut store, (100,))?; + instance + .get_typed_func::<(i64,), (), _>(&mut store, "take-s64")? + .call(&mut store, (100,))?; + + // This specific wasm instance traps if any value other than 100 is passed + instance + .get_typed_func::<(u8,), (), _>(&mut store, "take-u8")? + .call(&mut store, (101,)) + .unwrap_err() + .downcast::()?; + instance + .get_typed_func::<(i8,), (), _>(&mut store, "take-s8")? + .call(&mut store, (101,)) + .unwrap_err() + .downcast::()?; + instance + .get_typed_func::<(u16,), (), _>(&mut store, "take-u16")? + .call(&mut store, (101,)) + .unwrap_err() + .downcast::()?; + instance + .get_typed_func::<(i16,), (), _>(&mut store, "take-s16")? + .call(&mut store, (101,)) + .unwrap_err() + .downcast::()?; + instance + .get_typed_func::<(u32,), (), _>(&mut store, "take-u32")? + .call(&mut store, (101,)) + .unwrap_err() + .downcast::()?; + instance + .get_typed_func::<(i32,), (), _>(&mut store, "take-s32")? + .call(&mut store, (101,)) + .unwrap_err() + .downcast::()?; + instance + .get_typed_func::<(u64,), (), _>(&mut store, "take-u64")? + .call(&mut store, (101,)) + .unwrap_err() + .downcast::()?; + instance + .get_typed_func::<(i64,), (), _>(&mut store, "take-s64")? + .call(&mut store, (101,)) + .unwrap_err() + .downcast::()?; + + // Zero can be returned as any integer + assert_eq!( + instance + .get_typed_func::<(), u8, _>(&mut store, "ret-u8")? + .call(&mut store, ())?, + 0 + ); + assert_eq!( + instance + .get_typed_func::<(), i8, _>(&mut store, "ret-s8")? + .call(&mut store, ())?, + 0 + ); + assert_eq!( + instance + .get_typed_func::<(), u16, _>(&mut store, "ret-u16")? + .call(&mut store, ())?, + 0 + ); + assert_eq!( + instance + .get_typed_func::<(), i16, _>(&mut store, "ret-s16")? + .call(&mut store, ())?, + 0 + ); + assert_eq!( + instance + .get_typed_func::<(), u32, _>(&mut store, "ret-u32")? + .call(&mut store, ())?, + 0 + ); + assert_eq!( + instance + .get_typed_func::<(), i32, _>(&mut store, "ret-s32")? + .call(&mut store, ())?, + 0 + ); + assert_eq!( + instance + .get_typed_func::<(), u64, _>(&mut store, "ret-u64")? + .call(&mut store, ())?, + 0 + ); + assert_eq!( + instance + .get_typed_func::<(), i64, _>(&mut store, "ret-s64")? + .call(&mut store, ())?, + 0 + ); + + // Returning -1 should fail for u8 and u16, but succeed for all other types. + let err = instance + .get_typed_func::<(), u8, _>(&mut store, "retm1-u8")? + .call(&mut store, ()) + .unwrap_err(); + assert!( + err.to_string().contains("out of range integral type"), + "{}", + err + ); + assert_eq!( + instance + .get_typed_func::<(), i8, _>(&mut store, "retm1-s8")? + .call(&mut store, ())?, + -1 + ); + let err = instance + .get_typed_func::<(), u16, _>(&mut store, "retm1-u16")? + .call(&mut store, ()) + .unwrap_err(); + assert!( + err.to_string().contains("out of range integral type"), + "{}", + err + ); + assert_eq!( + instance + .get_typed_func::<(), i16, _>(&mut store, "retm1-s16")? + .call(&mut store, ())?, + -1 + ); + assert_eq!( + instance + .get_typed_func::<(), u32, _>(&mut store, "retm1-u32")? + .call(&mut store, ())?, + 0xffffffff + ); + assert_eq!( + instance + .get_typed_func::<(), i32, _>(&mut store, "retm1-s32")? + .call(&mut store, ())?, + -1 + ); + assert_eq!( + instance + .get_typed_func::<(), u64, _>(&mut store, "retm1-u64")? + .call(&mut store, ())?, + 0xffffffff_ffffffff + ); + assert_eq!( + instance + .get_typed_func::<(), i64, _>(&mut store, "retm1-s64")? + .call(&mut store, ())?, + -1 + ); + + // Returning 100000 should fail for small primitives but succeed for 32-bit. + let err = instance + .get_typed_func::<(), u8, _>(&mut store, "retbig-u8")? + .call(&mut store, ()) + .unwrap_err(); + assert!( + err.to_string().contains("out of range integral type"), + "{}", + err + ); + let err = instance + .get_typed_func::<(), i8, _>(&mut store, "retbig-s8")? + .call(&mut store, ()) + .unwrap_err(); + assert!( + err.to_string().contains("out of range integral type"), + "{}", + err + ); + let err = instance + .get_typed_func::<(), u16, _>(&mut store, "retbig-u16")? + .call(&mut store, ()) + .unwrap_err(); + assert!( + err.to_string().contains("out of range integral type"), + "{}", + err + ); + let err = instance + .get_typed_func::<(), i16, _>(&mut store, "retbig-s16")? + .call(&mut store, ()) + .unwrap_err(); + assert!( + err.to_string().contains("out of range integral type"), + "{}", + err + ); + assert_eq!( + instance + .get_typed_func::<(), u32, _>(&mut store, "retbig-u32")? + .call(&mut store, ())?, + 100000 + ); + assert_eq!( + instance + .get_typed_func::<(), i32, _>(&mut store, "retbig-s32")? + .call(&mut store, ())?, + 100000 + ); + + Ok(()) +} + +#[test] +fn type_layers() -> Result<()> { + let component = r#" + (component + (module $m + (func (export "take-i32-100") (param i32) + local.get 0 + i32.const 2 + i32.eq + br_if 0 + unreachable + ) + ) + (instance $i (instantiate (module $m))) + (func (export "take-u32") (canon.lift (func (param u32)) (func $i "take-i32-100"))) + ) + "#; + + let engine = super::engine(); + let component = Component::new(&engine, component)?; + let mut store = Store::new(&engine, ()); + let instance = Instance::new(&mut store, &component)?; + + instance + .get_typed_func::<(Box,), (), _>(&mut store, "take-u32")? + .call(&mut store, (Box::new(2),))?; + instance + .get_typed_func::<(&u32,), (), _>(&mut store, "take-u32")? + .call(&mut store, (&2,))?; + instance + .get_typed_func::<(Rc,), (), _>(&mut store, "take-u32")? + .call(&mut store, (Rc::new(2),))?; + instance + .get_typed_func::<(Arc,), (), _>(&mut store, "take-u32")? + .call(&mut store, (Arc::new(2),))?; + instance + .get_typed_func::<(&Box>>,), (), _>(&mut store, "take-u32")? + .call(&mut store, (&Box::new(Arc::new(Rc::new(2))),))?; + + Ok(()) +} + +#[test] +fn floats() -> Result<()> { + let component = r#" + (component + (module $m + (func (export "i32.reinterpret_f32") (param f32) (result i32) + local.get 0 + i32.reinterpret_f32 + ) + (func (export "i64.reinterpret_f64") (param f64) (result i64) + local.get 0 + i64.reinterpret_f64 + ) + (func (export "f32.reinterpret_i32") (param i32) (result f32) + local.get 0 + f32.reinterpret_i32 + ) + (func (export "f64.reinterpret_i64") (param i64) (result f64) + local.get 0 + f64.reinterpret_i64 + ) + ) + (instance $i (instantiate (module $m))) + + (func (export "f32-to-u32") + (canon.lift (func (param float32) (result u32)) (func $i "i32.reinterpret_f32")) + ) + (func (export "f64-to-u64") + (canon.lift (func (param float64) (result u64)) (func $i "i64.reinterpret_f64")) + ) + (func (export "u32-to-f32") + (canon.lift (func (param u32) (result float32)) (func $i "f32.reinterpret_i32")) + ) + (func (export "u64-to-f64") + (canon.lift (func (param u64) (result float64)) (func $i "f64.reinterpret_i64")) + ) + ) + "#; + + let engine = super::engine(); + let component = Component::new(&engine, component)?; + let mut store = Store::new(&engine, ()); + let instance = Instance::new(&mut store, &component)?; + let f32_to_u32 = instance.get_typed_func::<(f32,), u32, _>(&mut store, "f32-to-u32")?; + let f64_to_u64 = instance.get_typed_func::<(f64,), u64, _>(&mut store, "f64-to-u64")?; + let u32_to_f32 = instance.get_typed_func::<(u32,), f32, _>(&mut store, "u32-to-f32")?; + let u64_to_f64 = instance.get_typed_func::<(u64,), f64, _>(&mut store, "u64-to-f64")?; + + assert_eq!(f32_to_u32.call(&mut store, (1.0,))?, 1.0f32.to_bits()); + assert_eq!(f64_to_u64.call(&mut store, (2.0,))?, 2.0f64.to_bits()); + assert_eq!(u32_to_f32.call(&mut store, (3.0f32.to_bits(),))?, 3.0); + assert_eq!(u64_to_f64.call(&mut store, (4.0f64.to_bits(),))?, 4.0); + + assert_eq!( + u32_to_f32 + .call(&mut store, (CANON_32BIT_NAN | 1,))? + .to_bits(), + CANON_32BIT_NAN + ); + assert_eq!( + u64_to_f64 + .call(&mut store, (CANON_64BIT_NAN | 1,))? + .to_bits(), + CANON_64BIT_NAN + ); + + assert_eq!( + f32_to_u32.call(&mut store, (f32::from_bits(CANON_32BIT_NAN | 1),))?, + CANON_32BIT_NAN + ); + assert_eq!( + f64_to_u64.call(&mut store, (f64::from_bits(CANON_64BIT_NAN | 1),))?, + CANON_64BIT_NAN + ); + + Ok(()) +} + +#[test] +fn bools() -> Result<()> { + let component = r#" + (component + (module $m + (func (export "pass") (param i32) (result i32) local.get 0) + ) + (instance $i (instantiate (module $m))) + + (func (export "u32-to-bool") + (canon.lift (func (param u32) (result bool)) (func $i "pass")) + ) + (func (export "bool-to-u32") + (canon.lift (func (param bool) (result u32)) (func $i "pass")) + ) + ) + "#; + + let engine = super::engine(); + let component = Component::new(&engine, component)?; + let mut store = Store::new(&engine, ()); + let instance = Instance::new(&mut store, &component)?; + let u32_to_bool = instance.get_typed_func::<(u32,), bool, _>(&mut store, "u32-to-bool")?; + let bool_to_u32 = instance.get_typed_func::<(bool,), u32, _>(&mut store, "bool-to-u32")?; + + assert_eq!(bool_to_u32.call(&mut store, (false,))?, 0); + assert_eq!(bool_to_u32.call(&mut store, (true,))?, 1); + assert_eq!(u32_to_bool.call(&mut store, (0,))?, false); + assert_eq!(u32_to_bool.call(&mut store, (1,))?, true); + let err = u32_to_bool.call(&mut store, (2,)).unwrap_err(); + assert!(err.to_string().contains("invalid boolean"), "{}", err); + + Ok(()) +} + +#[test] +fn chars() -> Result<()> { + let component = r#" + (component + (module $m + (func (export "pass") (param i32) (result i32) local.get 0) + ) + (instance $i (instantiate (module $m))) + + (func (export "u32-to-char") + (canon.lift (func (param u32) (result char)) (func $i "pass")) + ) + (func (export "char-to-u32") + (canon.lift (func (param char) (result u32)) (func $i "pass")) + ) + ) + "#; + + let engine = super::engine(); + let component = Component::new(&engine, component)?; + let mut store = Store::new(&engine, ()); + let instance = Instance::new(&mut store, &component)?; + let u32_to_char = instance.get_typed_func::<(u32,), char, _>(&mut store, "u32-to-char")?; + let char_to_u32 = instance.get_typed_func::<(char,), u32, _>(&mut store, "char-to-u32")?; + + let mut roundtrip = |x: char| -> Result<()> { + assert_eq!(char_to_u32.call(&mut store, (x,))?, x as u32); + assert_eq!(u32_to_char.call(&mut store, (x as u32,))?, x); + Ok(()) + }; + + roundtrip('x')?; + roundtrip('a')?; + roundtrip('\0')?; + roundtrip('\n')?; + roundtrip('πŸ’')?; + + let err = u32_to_char.call(&mut store, (0xd800,)).unwrap_err(); + assert!(err.to_string().contains("integer out of range"), "{}", err); + let err = u32_to_char.call(&mut store, (0xdfff,)).unwrap_err(); + assert!(err.to_string().contains("integer out of range"), "{}", err); + let err = u32_to_char.call(&mut store, (0x110000,)).unwrap_err(); + assert!(err.to_string().contains("integer out of range"), "{}", err); + let err = u32_to_char.call(&mut store, (u32::MAX,)).unwrap_err(); + assert!(err.to_string().contains("integer out of range"), "{}", err); + + Ok(()) +} + +#[test] +fn tuple_result() -> Result<()> { + let component = r#" + (component + (module $m + (memory (export "memory") 1) + (func (export "foo") (param i32 i32 f32 f64) (result i32) + (local $base i32) + (local.set $base (i32.const 8)) + (i32.store8 offset=0 (local.get $base) (local.get 0)) + (i32.store16 offset=2 (local.get $base) (local.get 1)) + (f32.store offset=4 (local.get $base) (local.get 2)) + (f64.store offset=8 (local.get $base) (local.get 3)) + local.get $base + ) + + (func (export "invalid") (result i32) + i32.const -1 + ) + + (func (export "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32) + unreachable) + (func (export "canonical_abi_free") (param i32 i32 i32) + unreachable) + ) + (instance $i (instantiate (module $m))) + + (type $result (tuple s8 u16 float32 float64)) + (func (export "tuple") + (canon.lift + (func (param s8) (param u16) (param float32) (param float64) (result $result)) + (into $i) + (func $i "foo") + ) + ) + (func (export "invalid") + (canon.lift (func (result $result)) (into $i) (func $i "invalid")) + ) + ) + "#; + + let engine = super::engine(); + let component = Component::new(&engine, component)?; + let mut store = Store::new(&engine, ()); + let instance = Instance::new(&mut store, &component)?; + let result = instance + .get_typed_func::<(i8, u16, f32, f64), Value<(i8, u16, f32, f64)>, _>(&mut store, "tuple")? + .call(&mut store, (-1, 100, 3.0, 100.0))?; + let cursor = result.cursor(&store); + assert_eq!(cursor.a1().get(), -1); + assert_eq!(cursor.a2().get(), 100); + assert_eq!(cursor.a3().get(), 3.0); + assert_eq!(cursor.a4().get(), 100.0); + + let err = instance + .get_typed_func::<(i8, u16, f32, f64), (i8, u16, f32, f64), _>(&mut store, "tuple") + .err() + .unwrap(); + let err = format!("{:?}", err); + assert!(err.contains("is returned indirectly"), "{}", err); + + let invalid_func = + instance.get_typed_func::<(), Value<(i8, u16, f32, f64)>, _>(&mut store, "invalid")?; + let err = invalid_func.call(&mut store, ()).err().unwrap(); + assert!( + err.to_string().contains("pointer out of bounds of memory"), + "{}", + err + ); + + Ok(()) +} + +#[test] +fn strings() -> Result<()> { + let component = format!( + r#"(component + (module $m + (memory (export "memory") 1) + (func (export "roundtrip") (param i32 i32) (result i32) + (local $base i32) + (local.set $base + (call $realloc + (i32.const 0) + (i32.const 0) + (i32.const 4) + (i32.const 8))) + (i32.store offset=0 + (local.get $base) + (local.get 0)) + (i32.store offset=4 + (local.get $base) + (local.get 1)) + (local.get $base) + ) + + {REALLOC_AND_FREE} + ) + (instance $i (instantiate (module $m))) + + (func (export "list8-to-str") + (canon.lift + (func (param (list u8)) (result string)) + (into $i) + (func $i "roundtrip") + ) + ) + (func (export "str-to-list8") + (canon.lift + (func (param string) (result (list u8))) + (into $i) + (func $i "roundtrip") + ) + ) + (func (export "list16-to-str") + (canon.lift + (func (param (list u16)) (result string)) + string=utf16 + (into $i) + (func $i "roundtrip") + ) + ) + (func (export "str-to-list16") + (canon.lift + (func (param string) (result (list u16))) + string=utf16 + (into $i) + (func $i "roundtrip") + ) + ) + )"# + ); + + let engine = super::engine(); + let component = Component::new(&engine, component)?; + let mut store = Store::new(&engine, ()); + let instance = Instance::new(&mut store, &component)?; + let list8_to_str = + instance.get_typed_func::<(&[u8],), Value, _>(&mut store, "list8-to-str")?; + let str_to_list8 = + instance.get_typed_func::<(&str,), Value>, _>(&mut store, "str-to-list8")?; + let list16_to_str = + instance.get_typed_func::<(&[u16],), Value, _>(&mut store, "list16-to-str")?; + let str_to_list16 = + instance.get_typed_func::<(&str,), Value>, _>(&mut store, "str-to-list16")?; + + let mut roundtrip = |x: &str| -> Result<()> { + let ret = list8_to_str.call(&mut store, (x.as_bytes(),))?; + assert_eq!(ret.cursor(&store).to_str()?, x); + + let utf16 = x.encode_utf16().collect::>(); + let ret = list16_to_str.call(&mut store, (&utf16[..],))?; + assert_eq!(ret.cursor(&store).to_str()?, x); + + let ret = str_to_list8.call(&mut store, (x,))?; + assert_eq!( + ret.cursor(&store) + .iter()? + .map(|s| s.get()) + .collect::>(), + x.as_bytes() + ); + + let ret = str_to_list16.call(&mut store, (x,))?; + assert_eq!( + ret.cursor(&store) + .iter()? + .map(|s| s.get()) + .collect::>(), + utf16, + ); + + Ok(()) + }; + + roundtrip("")?; + roundtrip("foo")?; + roundtrip("hello there")?; + roundtrip("πŸ’")?; + roundtrip("LΓΆwe θ€θ™Ž LΓ©opard")?; + + let ret = list8_to_str.call(&mut store, (b"\xff",))?; + let err = ret.cursor(&store).to_str().unwrap_err(); + assert!(err.to_string().contains("invalid utf-8"), "{}", err); + + let ret = list8_to_str.call(&mut store, (b"hello there \xff invalid",))?; + let err = ret.cursor(&store).to_str().unwrap_err(); + assert!(err.to_string().contains("invalid utf-8"), "{}", err); + + let ret = list16_to_str.call(&mut store, (&[0xd800],))?; + let err = ret.cursor(&store).to_str().unwrap_err(); + assert!(err.to_string().contains("unpaired surrogate"), "{}", err); + + let ret = list16_to_str.call(&mut store, (&[0xdfff],))?; + let err = ret.cursor(&store).to_str().unwrap_err(); + assert!(err.to_string().contains("unpaired surrogate"), "{}", err); + + let ret = list16_to_str.call(&mut store, (&[0xd800, 0xff00],))?; + let err = ret.cursor(&store).to_str().unwrap_err(); + assert!(err.to_string().contains("unpaired surrogate"), "{}", err); + + Ok(()) +} + +#[test] +fn many_parameters() -> Result<()> { + let component = format!( + r#"(component + (module $m + (memory (export "memory") 1) + (func (export "foo") (param i32) (result i32) + (local $base i32) + + ;; Allocate space for the return + (local.set $base + (call $realloc + (i32.const 0) + (i32.const 0) + (i32.const 4) + (i32.const 12))) + + ;; Store the pointer/length of the entire linear memory + ;; so we have access to everything. + (i32.store offset=0 + (local.get $base) + (i32.const 0)) + (i32.store offset=4 + (local.get $base) + (i32.mul + (memory.size) + (i32.const 65536))) + + ;; And also store our pointer parameter + (i32.store offset=8 + (local.get $base) + (local.get 0)) + + (local.get $base) + ) + + {REALLOC_AND_FREE} + ) + (instance $i (instantiate (module $m))) + + (type $result (tuple (list u8) u32)) + (type $t (func + (param s8) ;; offset 0, size 1 + (param u64) ;; offset 8, size 8 + (param float32) ;; offset 16, size 4 + (param u8) ;; offset 20, size 1 + (param unit) ;; offset 21, size 0 + (param s16) ;; offset 22, size 2 + (param string) ;; offset 24, size 8 + (param (list u32)) ;; offset 32, size 8 + (param bool) ;; offset 40, size 1 + (param bool) ;; offset 41, size 1 + (param char) ;; offset 44, size 4 + (param (list bool)) ;; offset 48, size 8 + (param (list char)) ;; offset 56, size 8 + (param (list string)) ;; offset 64, size 8 + + (result $result) + )) + (func (export "many-param") + (canon.lift (type $t) (into $i) (func $i "foo")) + ) + )"# + ); + + let engine = super::engine(); + let component = Component::new(&engine, component)?; + let mut store = Store::new(&engine, ()); + let instance = Instance::new(&mut store, &component)?; + let func = instance.get_typed_func::<( + i8, + u64, + f32, + u8, + (), + i16, + &str, + &[u32], + bool, + bool, + char, + &[bool], + &[char], + &[&str], + ), Value<(Vec, u32)>, _>(&mut store, "many-param")?; + + let input = ( + -100, + u64::MAX / 2, + f32::from_bits(CANON_32BIT_NAN | 1), + 38, + (), + 18831, + "this is the first string", + [1, 2, 3, 4, 5, 6, 7, 8].as_slice(), + true, + false, + '🚩', + [false, true, false, true, true].as_slice(), + ['🍌', 'πŸ₯', 'πŸ—', 'πŸ™', '🍑'].as_slice(), + [ + "the quick", + "brown fox", + "was too lazy", + "to jump over the dog", + "what a demanding dog", + ] + .as_slice(), + ); + let result = func.call(&mut store, input)?; + let cursor = result.cursor(&store); + let memory = cursor.a1().as_slice()?; + let pointer = usize::try_from(cursor.a2().get()).unwrap(); + + let mut actual = &memory[pointer..][..72]; + assert_eq!(i8::from_le_bytes(*actual.take_n::<1>()), input.0); + actual.skip::<7>(); + assert_eq!(u64::from_le_bytes(*actual.take_n::<8>()), input.1); + assert_eq!(u32::from_le_bytes(*actual.take_n::<4>()), CANON_32BIT_NAN); + assert_eq!(u8::from_le_bytes(*actual.take_n::<1>()), input.3); + actual.skip::<1>(); + assert_eq!(i16::from_le_bytes(*actual.take_n::<2>()), input.5); + assert_eq!(actual.ptr_len(memory, 1), input.6.as_bytes()); + let mut mem = actual.ptr_len(memory, 4); + for expected in input.7.iter() { + assert_eq!(u32::from_le_bytes(*mem.take_n::<4>()), *expected); + } + assert!(mem.is_empty()); + assert_eq!(actual.take_n::<1>(), &[input.8 as u8]); + assert_eq!(actual.take_n::<1>(), &[input.9 as u8]); + actual.skip::<2>(); + assert_eq!(u32::from_le_bytes(*actual.take_n::<4>()), input.10 as u32); + + // (list bool) + mem = actual.ptr_len(memory, 1); + for expected in input.11.iter() { + assert_eq!(mem.take_n::<1>(), &[*expected as u8]); + } + assert!(mem.is_empty()); + + // (list char) + mem = actual.ptr_len(memory, 4); + for expected in input.12.iter() { + assert_eq!(u32::from_le_bytes(*mem.take_n::<4>()), *expected as u32); + } + assert!(mem.is_empty()); + + // (list string) + mem = actual.ptr_len(memory, 8); + for expected in input.13.iter() { + let actual = mem.ptr_len(memory, 1); + assert_eq!(actual, expected.as_bytes()); + } + assert!(mem.is_empty()); + assert!(actual.is_empty()); + + Ok(()) +} + +#[test] +fn some_traps() -> Result<()> { + let middle_of_memory = i32::MAX / 2; + let component = format!( + r#"(component + (module $m + (memory (export "memory") 1) + (func (export "take-many") (param i32)) + (func (export "take-list") (param i32 i32)) + + (func (export "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32) + unreachable) + (func (export "canonical_abi_free") (param i32 i32 i32) + unreachable) + ) + (instance $i (instantiate (module $m))) + + (func (export "take-list-unreachable") + (canon.lift (func (param (list u8))) (into $i) (func $i "take-list")) + ) + (func (export "take-string-unreachable") + (canon.lift (func (param string)) (into $i) (func $i "take-list")) + ) + + (type $t (func + (param string) + (param string) + (param string) + (param string) + (param string) + (param string) + (param string) + (param string) + (param string) + (param string) + )) + (func (export "take-many-unreachable") + (canon.lift (type $t) (into $i) (func $i "take-many")) + ) + + (module $m2 + (memory (export "memory") 1) + (func (export "take-many") (param i32)) + (func (export "take-list") (param i32 i32)) + + (func (export "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32) + i32.const {middle_of_memory}) + (func (export "canonical_abi_free") (param i32 i32 i32) + unreachable) + ) + (instance $i2 (instantiate (module $m2))) + + (func (export "take-list-base-oob") + (canon.lift (func (param (list u8))) (into $i2) (func $i2 "take-list")) + ) + (func (export "take-string-base-oob") + (canon.lift (func (param string)) (into $i2) (func $i2 "take-list")) + ) + (func (export "take-many-base-oob") + (canon.lift (type $t) (into $i2) (func $i2 "take-many")) + ) + + (module $m3 + (memory (export "memory") 1) + (func (export "take-many") (param i32)) + (func (export "take-list") (param i32 i32)) + + (func (export "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32) + i32.const 65535) + (func (export "canonical_abi_free") (param i32 i32 i32) + unreachable) + ) + (instance $i3 (instantiate (module $m3))) + + (func (export "take-list-end-oob") + (canon.lift (func (param (list u8))) (into $i3) (func $i3 "take-list")) + ) + (func (export "take-string-end-oob") + (canon.lift (func (param string)) (into $i3) (func $i3 "take-list")) + ) + (func (export "take-many-end-oob") + (canon.lift (type $t) (into $i3) (func $i3 "take-many")) + ) + + (module $m4 + (memory (export "memory") 1) + (func (export "take-many") (param i32)) + + (global $cnt (mut i32) (i32.const 0)) + (func (export "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32) + global.get $cnt + if (result i32) + i32.const 100000 + else + i32.const 1 + global.set $cnt + i32.const 0 + end + ) + (func (export "canonical_abi_free") (param i32 i32 i32) + unreachable) + ) + (instance $i4 (instantiate (module $m4))) + + (func (export "take-many-second-oob") + (canon.lift (type $t) (into $i4) (func $i4 "take-many")) + ) + )"# + ); + + let engine = super::engine(); + let component = Component::new(&engine, component)?; + let mut store = Store::new(&engine, ()); + let instance = Instance::new(&mut store, &component)?; + + // This should fail when calling the allocator function for the argument + let err = instance + .get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-unreachable")? + .call(&mut store, (&[],)) + .unwrap_err() + .downcast::()?; + assert_eq!(err.trap_code(), Some(TrapCode::UnreachableCodeReached)); + + // This should fail when calling the allocator function for the argument + let err = instance + .get_typed_func::<(&str,), (), _>(&mut store, "take-string-unreachable")? + .call(&mut store, ("",)) + .unwrap_err() + .downcast::()?; + assert_eq!(err.trap_code(), Some(TrapCode::UnreachableCodeReached)); + + // This should fail when calling the allocator function for the space + // to store the arguments (before arguments are even lowered) + let err = instance + .get_typed_func::<(&str, &str, &str, &str, &str, &str, &str, &str, &str, &str), (), _>( + &mut store, + "take-many-unreachable", + )? + .call(&mut store, ("", "", "", "", "", "", "", "", "", "")) + .unwrap_err() + .downcast::()?; + assert_eq!(err.trap_code(), Some(TrapCode::UnreachableCodeReached)); + + // Assert that when the base pointer returned by malloc is out of bounds + // that errors are reported as such. Both empty and lists with contents + // should all be invalid here. + // + // FIXME(WebAssembly/component-model#32) confirm the semantics here are + // what's desired. + #[track_caller] + fn assert_oob(err: &anyhow::Error) { + assert!( + err.to_string() + .contains("realloc return: beyond end of memory"), + "{:?}", + err, + ); + } + let err = instance + .get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-base-oob")? + .call(&mut store, (&[],)) + .unwrap_err(); + assert_oob(&err); + let err = instance + .get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-base-oob")? + .call(&mut store, (&[1],)) + .unwrap_err(); + assert_oob(&err); + let err = instance + .get_typed_func::<(&str,), (), _>(&mut store, "take-string-base-oob")? + .call(&mut store, ("",)) + .unwrap_err(); + assert_oob(&err); + let err = instance + .get_typed_func::<(&str,), (), _>(&mut store, "take-string-base-oob")? + .call(&mut store, ("x",)) + .unwrap_err(); + assert_oob(&err); + let err = instance + .get_typed_func::<(&str, &str, &str, &str, &str, &str, &str, &str, &str, &str), (), _>( + &mut store, + "take-many-base-oob", + )? + .call(&mut store, ("", "", "", "", "", "", "", "", "", "")) + .unwrap_err(); + assert_oob(&err); + + // Test here that when the returned pointer from malloc is one byte from the + // end of memory that empty things are fine, but larger things are not. + + instance + .get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-end-oob")? + .call(&mut store, (&[],))?; + instance + .get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-end-oob")? + .call(&mut store, (&[1],))?; + assert_oob(&err); + let err = instance + .get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-end-oob")? + .call(&mut store, (&[1, 2],)) + .unwrap_err(); + assert_oob(&err); + instance + .get_typed_func::<(&str,), (), _>(&mut store, "take-string-end-oob")? + .call(&mut store, ("",))?; + instance + .get_typed_func::<(&str,), (), _>(&mut store, "take-string-end-oob")? + .call(&mut store, ("x",))?; + let err = instance + .get_typed_func::<(&str,), (), _>(&mut store, "take-string-end-oob")? + .call(&mut store, ("xy",)) + .unwrap_err(); + assert_oob(&err); + let err = instance + .get_typed_func::<(&str, &str, &str, &str, &str, &str, &str, &str, &str, &str), (), _>( + &mut store, + "take-many-end-oob", + )? + .call(&mut store, ("", "", "", "", "", "", "", "", "", "")) + .unwrap_err(); + assert_oob(&err); + + // For this function the first allocation, the space to store all the + // arguments, is in-bounds but then all further allocations, such as for + // each individual string, are all out of bounds. + let err = instance + .get_typed_func::<(&str, &str, &str, &str, &str, &str, &str, &str, &str, &str), (), _>( + &mut store, + "take-many-second-oob", + )? + .call(&mut store, ("", "", "", "", "", "", "", "", "", "")) + .unwrap_err(); + assert_oob(&err); + Ok(()) +} + +#[test] +fn char_bool_memory() -> Result<()> { + let component = format!( + r#"(component + (module $m + (memory (export "memory") 1) + (func (export "ret-tuple") (param i32 i32) (result i32) + (local $base i32) + + ;; Allocate space for the return + (local.set $base + (call $realloc + (i32.const 0) + (i32.const 0) + (i32.const 4) + (i32.const 8))) + + ;; store the boolean + (i32.store offset=0 + (local.get $base) + (local.get 0)) + + ;; store the char + (i32.store offset=4 + (local.get $base) + (local.get 1)) + + (local.get $base) + ) + + {REALLOC_AND_FREE} + ) + (instance $i (instantiate (module $m))) + + (func (export "ret-tuple") + (canon.lift (func (param u32) (param u32) (result (tuple bool char))) (into $i) (func $i "ret-tuple")) + ) + )"# + ); + + let engine = super::engine(); + let component = Component::new(&engine, component)?; + let mut store = Store::new(&engine, ()); + let instance = Instance::new(&mut store, &component)?; + let func = + instance.get_typed_func::<(u32, u32), Value<(bool, char)>, _>(&mut store, "ret-tuple")?; + + let ret = func.call(&mut store, (0, 'a' as u32))?; + assert_eq!(ret.cursor(&store).a1().get()?, false); + assert_eq!(ret.cursor(&store).a2().get()?, 'a'); + + let ret = func.call(&mut store, (1, '🍰' as u32))?; + assert_eq!(ret.cursor(&store).a1().get()?, true); + assert_eq!(ret.cursor(&store).a2().get()?, '🍰'); + + let ret = func.call(&mut store, (2, 'a' as u32))?; + assert!(ret.cursor(&store).a1().get().is_err()); + + let ret = func.call(&mut store, (0, 0xd800))?; + assert!(ret.cursor(&store).a2().get().is_err()); + + Ok(()) +} + +#[test] +fn string_list_oob() -> Result<()> { + let component = format!( + r#"(component + (module $m + (memory (export "memory") 1) + (func (export "ret-list") (result i32) + (local $base i32) + + ;; Allocate space for the return + (local.set $base + (call $realloc + (i32.const 0) + (i32.const 0) + (i32.const 4) + (i32.const 8))) + + (i32.store offset=0 + (local.get $base) + (i32.const 100000)) + (i32.store offset=4 + (local.get $base) + (i32.const 1)) + + (local.get $base) + ) + + {REALLOC_AND_FREE} + ) + (instance $i (instantiate (module $m))) + + (func (export "ret-list-u8") + (canon.lift (func (result (list u8))) (into $i) (func $i "ret-list")) + ) + (func (export "ret-string") + (canon.lift (func (result string)) (into $i) (func $i "ret-list")) + ) + )"# + ); + + let engine = super::engine(); + let component = Component::new(&engine, component)?; + let mut store = Store::new(&engine, ()); + let instance = Instance::new(&mut store, &component)?; + let ret_list_u8 = + instance.get_typed_func::<(), Value>, _>(&mut store, "ret-list-u8")?; + let ret_string = instance.get_typed_func::<(), Value, _>(&mut store, "ret-string")?; + + let list = ret_list_u8.call(&mut store, ())?; + let err = list.cursor(&store).iter().err().unwrap(); + assert!(err.to_string().contains("list out of bounds"), "{}", err); + + let ret = ret_string.call(&mut store, ())?; + let err = ret.cursor(&store).to_str().unwrap_err(); + assert!(err.to_string().contains("string out of bounds"), "{}", err); + + Ok(()) +} + +#[test] +fn tuples() -> Result<()> { + let component = format!( + r#"(component + (module $m + (memory (export "memory") 1) + (func (export "foo") + (param i32 f64 i32) + (result i32) + + local.get 0 + i32.const 0 + i32.ne + if unreachable end + + local.get 1 + f64.const 1 + f64.ne + if unreachable end + + local.get 2 + i32.const 2 + i32.ne + if unreachable end + + i32.const 3 + ) + + (func (export "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32) + unreachable) + (func (export "canonical_abi_free") (param i32 i32 i32) + unreachable) + ) + (instance $i (instantiate (module $m))) + + (func (export "foo") + (canon.lift + (func + (param (tuple s32 float64)) + (param (tuple s8)) + (result (tuple u16)) + ) + (func $i "foo") + ) + ) + )"# + ); + + let engine = super::engine(); + let component = Component::new(&engine, component)?; + let mut store = Store::new(&engine, ()); + let instance = Instance::new(&mut store, &component)?; + let foo = instance.get_typed_func::<((i32, f64), (i8,)), (u16,), _>(&mut store, "foo")?; + assert_eq!(foo.call(&mut store, ((0, 1.0), (2,)))?, (3,)); + + Ok(()) +} + +#[test] +fn option() -> Result<()> { + let component = format!( + r#"(component + (module $m + (memory (export "memory") 1) + (func (export "pass0") (param i32) (result i32) + local.get 0 + ) + (func (export "pass1") (param i32 i32) (result i32) + (local $base i32) + (local.set $base + (call $realloc + (i32.const 0) + (i32.const 0) + (i32.const 4) + (i32.const 8))) + + (i32.store offset=0 + (local.get $base) + (local.get 0)) + (i32.store offset=4 + (local.get $base) + (local.get 1)) + + (local.get $base) + ) + (func (export "pass2") (param i32 i32 i32) (result i32) + (local $base i32) + (local.set $base + (call $realloc + (i32.const 0) + (i32.const 0) + (i32.const 4) + (i32.const 12))) + + (i32.store offset=0 + (local.get $base) + (local.get 0)) + (i32.store offset=4 + (local.get $base) + (local.get 1)) + (i32.store offset=8 + (local.get $base) + (local.get 2)) + + (local.get $base) + ) + + {REALLOC_AND_FREE} + ) + (instance $i (instantiate (module $m))) + + (func (export "option-unit-to-u32") + (canon.lift + (func (param (option unit)) (result u32)) + (func $i "pass0") + ) + ) + (func (export "option-u8-to-tuple") + (canon.lift + (func (param (option u8)) (result (tuple u32 u32))) + (into $i) + (func $i "pass1") + ) + ) + (func (export "option-u32-to-tuple") + (canon.lift + (func (param (option u32)) (result (tuple u32 u32))) + (into $i) + (func $i "pass1") + ) + ) + (func (export "option-string-to-tuple") + (canon.lift + (func (param (option string)) (result (tuple u32 string))) + (into $i) + (func $i "pass2") + ) + ) + (func (export "to-option-unit") + (canon.lift + (func (param u32) (result (option unit))) + (func $i "pass0") + ) + ) + (func (export "to-option-u8") + (canon.lift + (func (param u32) (param u32) (result (option u8))) + (into $i) + (func $i "pass1") + ) + ) + (func (export "to-option-u32") + (canon.lift + (func (param u32) (param u32) (result (option u32))) + (into $i) + (func $i "pass1") + ) + ) + (func (export "to-option-string") + (canon.lift + (func (param u32) (param string) (result (option string))) + (into $i) + (func $i "pass2") + ) + ) + )"# + ); + + let engine = super::engine(); + let component = Component::new(&engine, component)?; + let mut store = Store::new(&engine, ()); + let instance = Instance::new(&mut store, &component)?; + let option_unit_to_u32 = + instance.get_typed_func::<(Option<()>,), u32, _>(&mut store, "option-unit-to-u32")?; + assert_eq!(option_unit_to_u32.call(&mut store, (None,))?, 0); + assert_eq!(option_unit_to_u32.call(&mut store, (Some(()),))?, 1); + + let option_u8_to_tuple = instance + .get_typed_func::<(Option,), Value<(u32, u32)>, _>(&mut store, "option-u8-to-tuple")?; + let ret = option_u8_to_tuple.call(&mut store, (None,))?; + assert_eq!(ret.cursor(&store).a1().get(), 0); + assert_eq!(ret.cursor(&store).a2().get(), 0); + let ret = option_u8_to_tuple.call(&mut store, (Some(0),))?; + assert_eq!(ret.cursor(&store).a1().get(), 1); + assert_eq!(ret.cursor(&store).a2().get(), 0); + let ret = option_u8_to_tuple.call(&mut store, (Some(100),))?; + assert_eq!(ret.cursor(&store).a1().get(), 1); + assert_eq!(ret.cursor(&store).a2().get(), 100); + + let option_u32_to_tuple = instance.get_typed_func::<(Option,), Value<(u32, u32)>, _>( + &mut store, + "option-u32-to-tuple", + )?; + let ret = option_u32_to_tuple.call(&mut store, (None,))?; + assert_eq!(ret.cursor(&store).a1().get(), 0); + assert_eq!(ret.cursor(&store).a2().get(), 0); + let ret = option_u32_to_tuple.call(&mut store, (Some(0),))?; + assert_eq!(ret.cursor(&store).a1().get(), 1); + assert_eq!(ret.cursor(&store).a2().get(), 0); + let ret = option_u32_to_tuple.call(&mut store, (Some(100),))?; + assert_eq!(ret.cursor(&store).a1().get(), 1); + assert_eq!(ret.cursor(&store).a2().get(), 100); + + let option_string_to_tuple = instance + .get_typed_func::<(Option<&str>,), Value<(u32, String)>, _>( + &mut store, + "option-string-to-tuple", + )?; + let ret = option_string_to_tuple.call(&mut store, (None,))?; + assert_eq!(ret.cursor(&store).a1().get(), 0); + assert_eq!(ret.cursor(&store).a2().to_str()?, ""); + let ret = option_string_to_tuple.call(&mut store, (Some(""),))?; + assert_eq!(ret.cursor(&store).a1().get(), 1); + assert_eq!(ret.cursor(&store).a2().to_str()?, ""); + let ret = option_string_to_tuple.call(&mut store, (Some("hello"),))?; + assert_eq!(ret.cursor(&store).a1().get(), 1); + assert_eq!(ret.cursor(&store).a2().to_str()?, "hello"); + + let to_option_unit = + instance.get_typed_func::<(u32,), Option<()>, _>(&mut store, "to-option-unit")?; + assert_eq!(to_option_unit.call(&mut store, (0,))?, None); + assert_eq!(to_option_unit.call(&mut store, (1,))?, Some(())); + let err = to_option_unit.call(&mut store, (2,)).unwrap_err(); + assert!(err.to_string().contains("invalid option"), "{}", err); + + let to_option_u8 = + instance.get_typed_func::<(u32, u32), Value>, _>(&mut store, "to-option-u8")?; + let ret = to_option_u8.call(&mut store, (0x00_00, 0))?; + assert!(ret.cursor(&store).get()?.is_none()); + let ret = to_option_u8.call(&mut store, (0x00_01, 0))?; + assert_eq!(ret.cursor(&store).get()?.unwrap().get(), 0x00); + let ret = to_option_u8.call(&mut store, (0xfd_01, 0))?; + assert_eq!(ret.cursor(&store).get()?.unwrap().get(), 0xfd); + let ret = to_option_u8.call(&mut store, (0x00_02, 0))?; + assert!(ret.cursor(&store).get().is_err()); + + let to_option_u32 = instance + .get_typed_func::<(u32, u32), Value>, _>(&mut store, "to-option-u32")?; + let ret = to_option_u32.call(&mut store, (0, 0))?; + assert!(ret.cursor(&store).get()?.is_none()); + let ret = to_option_u32.call(&mut store, (1, 0))?; + assert_eq!(ret.cursor(&store).get()?.unwrap().get(), 0); + let ret = to_option_u32.call(&mut store, (1, 0x1234fead))?; + assert_eq!(ret.cursor(&store).get()?.unwrap().get(), 0x1234fead); + let ret = to_option_u32.call(&mut store, (2, 0))?; + assert!(ret.cursor(&store).get().is_err()); + + let to_option_string = instance + .get_typed_func::<(u32, &str), Value>, _>(&mut store, "to-option-string")?; + let ret = to_option_string.call(&mut store, (0, ""))?; + assert!(ret.cursor(&store).get()?.is_none()); + let ret = to_option_string.call(&mut store, (1, ""))?; + assert_eq!(ret.cursor(&store).get()?.unwrap().to_str()?, ""); + let ret = to_option_string.call(&mut store, (1, "cheesecake"))?; + assert_eq!(ret.cursor(&store).get()?.unwrap().to_str()?, "cheesecake"); + let ret = to_option_string.call(&mut store, (2, ""))?; + assert!(ret.cursor(&store).get().is_err()); + + Ok(()) +} + +#[test] +fn expected() -> Result<()> { + let component = format!( + r#"(component + (module $m + (memory (export "memory") 1) + (func (export "pass0") (param i32) (result i32) + local.get 0 + ) + (func (export "pass1") (param i32 i32) (result i32) + (local $base i32) + (local.set $base + (call $realloc + (i32.const 0) + (i32.const 0) + (i32.const 4) + (i32.const 8))) + + (i32.store offset=0 + (local.get $base) + (local.get 0)) + (i32.store offset=4 + (local.get $base) + (local.get 1)) + + (local.get $base) + ) + (func (export "pass2") (param i32 i32 i32) (result i32) + (local $base i32) + (local.set $base + (call $realloc + (i32.const 0) + (i32.const 0) + (i32.const 4) + (i32.const 12))) + + (i32.store offset=0 + (local.get $base) + (local.get 0)) + (i32.store offset=4 + (local.get $base) + (local.get 1)) + (i32.store offset=8 + (local.get $base) + (local.get 2)) + + (local.get $base) + ) + + {REALLOC_AND_FREE} + ) + (instance $i (instantiate (module $m))) + + (func (export "take-expected-unit") + (canon.lift + (func (param (expected unit unit)) (result u32)) + (func $i "pass0") + ) + ) + (func (export "take-expected-u8-f32") + (canon.lift + (func (param (expected u8 float32)) (result (tuple u32 u32))) + (into $i) + (func $i "pass1") + ) + ) + (type $list (list u8)) + (func (export "take-expected-string") + (canon.lift + (func (param (expected string $list)) (result (tuple u32 string))) + (into $i) + (func $i "pass2") + ) + ) + (func (export "to-expected-unit") + (canon.lift + (func (param u32) (result (expected unit unit))) + (func $i "pass0") + ) + ) + (func (export "to-expected-s16-f32") + (canon.lift + (func (param u32) (param u32) (result (expected s16 float32))) + (into $i) + (func $i "pass1") + ) + ) + )"# + ); + + let engine = super::engine(); + let component = Component::new(&engine, component)?; + let mut store = Store::new(&engine, ()); + let instance = Instance::new(&mut store, &component)?; + let take_expected_unit = + instance.get_typed_func::<(Result<(), ()>,), u32, _>(&mut store, "take-expected-unit")?; + assert_eq!(take_expected_unit.call(&mut store, (Ok(()),))?, 0); + assert_eq!(take_expected_unit.call(&mut store, (Err(()),))?, 1); + + let take_expected_u8_f32 = instance + .get_typed_func::<(Result,), Value<(u32, u32)>, _>( + &mut store, + "take-expected-u8-f32", + )?; + let ret = take_expected_u8_f32.call(&mut store, (Ok(1),))?; + assert_eq!(ret.cursor(&store).a1().get(), 0); + assert_eq!(ret.cursor(&store).a2().get(), 1); + let ret = take_expected_u8_f32.call(&mut store, (Err(2.0),))?; + assert_eq!(ret.cursor(&store).a1().get(), 1); + assert_eq!(ret.cursor(&store).a2().get(), 2.0f32.to_bits()); + + let take_expected_string = instance + .get_typed_func::<(Result<&str, &[u8]>,), Value<(u32, String)>, _>( + &mut store, + "take-expected-string", + )?; + let ret = take_expected_string.call(&mut store, (Ok("hello"),))?; + assert_eq!(ret.cursor(&store).a1().get(), 0); + assert_eq!(ret.cursor(&store).a2().to_str()?, "hello"); + let ret = take_expected_string.call(&mut store, (Err(b"goodbye"),))?; + assert_eq!(ret.cursor(&store).a1().get(), 1); + assert_eq!(ret.cursor(&store).a2().to_str()?, "goodbye"); + + let to_expected_unit = + instance.get_typed_func::<(u32,), Result<(), ()>, _>(&mut store, "to-expected-unit")?; + assert_eq!(to_expected_unit.call(&mut store, (0,))?, Ok(())); + assert_eq!(to_expected_unit.call(&mut store, (1,))?, Err(())); + let err = to_expected_unit.call(&mut store, (2,)).unwrap_err(); + assert!(err.to_string().contains("invalid expected"), "{}", err); + + let to_expected_s16_f32 = instance.get_typed_func::<(u32, u32), Value>, _>( + &mut store, + "to-expected-s16-f32", + )?; + let ret = to_expected_s16_f32.call(&mut store, (0, 0))?; + assert_eq!(ret.cursor(&store).get()?.ok().unwrap().get(), 0); + let ret = to_expected_s16_f32.call(&mut store, (0, 100))?; + assert_eq!(ret.cursor(&store).get()?.ok().unwrap().get(), 100); + let ret = to_expected_s16_f32.call(&mut store, (1, 1.0f32.to_bits()))?; + assert_eq!(ret.cursor(&store).get()?.err().unwrap().get(), 1.0); + let ret = to_expected_s16_f32.call(&mut store, (1, CANON_32BIT_NAN | 1))?; + assert_eq!( + ret.cursor(&store).get()?.err().unwrap().get().to_bits(), + CANON_32BIT_NAN + ); + let ret = to_expected_s16_f32.call(&mut store, (2, 0))?; + assert!(ret.cursor(&store).get().is_err()); + + Ok(()) +} + +#[test] +fn fancy_list() -> Result<()> { + let component = format!( + r#"(component + (module $m + (memory (export "memory") 1) + (func (export "take") (param i32 i32) (result i32) + (local $base i32) + (local.set $base + (call $realloc + (i32.const 0) + (i32.const 0) + (i32.const 4) + (i32.const 16))) + + (i32.store offset=0 + (local.get $base) + (local.get 0)) + (i32.store offset=4 + (local.get $base) + (local.get 1)) + (i32.store offset=8 + (local.get $base) + (i32.const 0)) + (i32.store offset=12 + (local.get $base) + (i32.mul + (memory.size) + (i32.const 65536))) + + (local.get $base) + ) + + {REALLOC_AND_FREE} + ) + (instance $i (instantiate (module $m))) + + (type $a (option u8)) + (type $b (expected unit string)) + (type $input (list (tuple $a $b))) + (type $output (tuple u32 u32 (list u8))) + (func (export "take") + (canon.lift + (func (param $input) (result $output)) + (into $i) + (func $i "take") + ) + ) + )"# + ); + + let engine = super::engine(); + let component = Component::new(&engine, component)?; + let mut store = Store::new(&engine, ()); + let instance = Instance::new(&mut store, &component)?; + + let func = instance + .get_typed_func::<(&[(Option, Result<(), &str>)],), Value<(u32, u32, Vec)>, _>( + &mut store, "take", + )?; + + let input = [ + (None, Ok(())), + (Some(2), Err("hello there")), + (Some(200), Err("general kenobi")), + ]; + let ret = func.call(&mut store, (&input,))?; + let ret = ret.cursor(&store); + let memory = ret.a3().as_slice()?; + let ptr = usize::try_from(ret.a1().get()).unwrap(); + let len = usize::try_from(ret.a2().get()).unwrap(); + let mut array = &memory[ptr..][..len * 16]; + + for (a, b) in input.iter() { + match a { + Some(val) => { + assert_eq!(*array.take_n::<2>(), [1, *val]); + } + None => { + assert_eq!(*array.take_n::<1>(), [0]); + array.skip::<1>(); + } + } + array.skip::<2>(); + match b { + Ok(()) => { + assert_eq!(*array.take_n::<1>(), [0]); + array.skip::<11>(); + } + Err(s) => { + assert_eq!(*array.take_n::<1>(), [1]); + array.skip::<3>(); + assert_eq!(array.ptr_len(memory, 1), s.as_bytes()); + } + } + } + assert!(array.is_empty()); + + Ok(()) +} + +trait SliceExt<'a> { + fn take_n(&mut self) -> &'a [u8; N]; + + fn skip(&mut self) { + self.take_n::(); + } + + fn ptr_len<'b>(&mut self, all_memory: &'b [u8], size: usize) -> &'b [u8] { + let ptr = u32::from_le_bytes(*self.take_n::<4>()); + let len = u32::from_le_bytes(*self.take_n::<4>()); + let ptr = usize::try_from(ptr).unwrap(); + let len = usize::try_from(len).unwrap(); + &all_memory[ptr..][..len * size] + } +} + +impl<'a> SliceExt<'a> for &'a [u8] { + fn take_n(&mut self) -> &'a [u8; N] { + let (a, b) = self.split_at(N); + *self = b; + a.try_into().unwrap() + } +}