From 140b83597b396a006890a4119e707354aad5548e Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 24 May 2022 17:02:31 -0500 Subject: [PATCH] components: Implement the ability to call component exports (#4039) * components: Implement the ability to call component exports This commit is an implementation of the typed method of calling component exports. This is intended to represent the most efficient way of calling a component in Wasmtime, similar to what `TypedFunc` represents today for core wasm. Internally this contains all the traits and implementations necessary to invoke component exports with any type signature (e.g. arbitrary parameters and/or results). The expectation is that for results we'll reuse all of this infrastructure except in reverse (arguments and results will be swapped when defining imports). Some features of this implementation are: * Arbitrary type hierarchies are supported * The Rust-standard `Option`, `Result`, `String`, `Vec`, and tuple types all map down to the corresponding type in the component model. * Basic utf-16 string support is implemented as proof-of-concept to show what handling might look like. This will need further testing and benchmarking. * Arguments can be behind "smart pointers", so for example `&Rc>` corresponds to `list` in interface types. * Bulk copies from linear memory never happen unless explicitly instructed to do so. The goal of this commit is to create the ability to actually invoke wasm components. This represents what is expected to be the performance threshold for these calls where it ideally should be optimal how WebAssembly is invoked. One major missing piece of this is a `#[derive]` of some sort to generate Rust types for arbitrary `*.wit` types such as custom records, variants, flags, unions, etc. The current trait impls for tuples and `Result` are expected to have fleshed out most of what such a derive would look like. There are some downsides and missing pieces to this commit and method of calling components, however, such as: * Passing `&[u8]` to WebAssembly is currently not optimal. Ideally this compiles down to a `memcpy`-equivalent somewhere but that currently doesn't happen due to all the bounds checks of copying data into memory. I have been unsuccessful so far at getting these bounds checks to be removed. * There is no finalization at this time (the "post return" functionality in the canonical ABI). Implementing this should be relatively straightforward but at this time requires `wasmparser` changes to catch up with the current canonical ABI. * There is no guarantee that results of a wasm function will be validated. As results are consumed they are validated but this means that if function returns an invalid string which the host doesn't look at then no trap will be generated. This is probably not the intended semantics of hosts in the component model. * At this time there's no support for memory64 memories, just a bunch of `FIXME`s to get around to. It's expected that this won't be too onerous, however. Some extra care will need to ensure that the various methods related to size/alignment all optimize to the same thing they do today (e.g. constants). * The return value of a typed component function is either `T` or `Value`, and it depends on the ABI details of `T` and whether it takes up more than one return value slot or not. This is an ABI-implementation detail which is being forced through to the API layer which is pretty unfortunate. For example if you say the return value of a function is `(u8, u32)` then it's a runtime type-checking error. I don't know of a great way to solve this at this time. Overall I'm feeling optimistic about this trajectory of implementing value lifting/lowering in Wasmtime. While there are a number of downsides none seem completely insurmountable. There's naturally still a good deal of work with the component model but this should be a significant step up towards implementing and testing the component model. * Review comments * Write tests for calling functions This commit adds a new test file for actually executing functions and testing their results. This is not written as a `*.wast` test yet since it's not 100% clear if that's the best way to do that for now (given that dynamic signatures aren't supported yet). The tests themselves could all largely be translated to `*.wast` testing in the future, though, if supported. Along the way a number of minor issues were fixed with lowerings with the bugs exposed here. * Fix an endian mistake * Fix a typo and the `memory.fill` instruction --- crates/environ/src/component/info.rs | 15 +- crates/environ/src/component/translate.rs | 6 +- crates/wasmtime/src/component/func.rs | 189 +- crates/wasmtime/src/component/func/typed.rs | 1972 +++++++++++++++++++ crates/wasmtime/src/component/instance.rs | 32 +- crates/wasmtime/src/component/mod.rs | 10 +- tests/all/component_model.rs | 2 + tests/all/component_model/func.rs | 1934 ++++++++++++++++++ 8 files changed, 4145 insertions(+), 15 deletions(-) create mode 100644 crates/wasmtime/src/component/func/typed.rs create mode 100644 tests/all/component_model/func.rs diff --git a/crates/environ/src/component/info.rs b/crates/environ/src/component/info.rs index 0ab659e65b..28b60a8ee3 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 2c66aa0c21..15df12078e 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 3767eb5d36..151c5d128e 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 0000000000..cf3964af7f --- /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 8822e1fbb8..ab5e53325f 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 8e7aad1486..99c7da84b7 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 2840aae6d3..98387f154a 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 0000000000..69eb4d56a9 --- /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() + } +}