From 42df3521558778772d8fc23c9db3e5722cdca3ce Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 27 May 2022 17:43:31 -0700 Subject: [PATCH] Redesign interface type value representation Prior to this PR a major feature of calling component exports (#4039) was the usage of the `Value` type. This type represents a value stored in wasm linear memory (the type `T` stored there). This implementation had a number of drawbacks though: * When returning a value it's ABI-specific whether you use `T` or `Value` as a return value. If `T` is represented with one wasm primitive then you have to return `T`, otherwise the return value must be `Value`. This is somewhat non-obvious and leaks ABI-details into the API which is unfortunate. * The `T` in `Value` was somewhat non-obvious. For example a wasm-owned string was `Value`. Using `Value<&str>` didn't work. * Working with `Value` was unergonomic in the sense that you had to first "pair" it with a `&Store` to get a `Cursor` and then you could start reading the value. * Custom structs and enums, while not implemented yet, were planned to be quite wonky where when you had `Cursor` then you would have to import a `CursorMyStructExt` trait generated by a proc-macro (think a `#[derive]` on the definition of `MyStruct`) which would enable field accessors, returning cursors of all the fields. * In general there was no "generic way" to load a `T` from memory. Other operations like lift/lower/store all had methods in the `ComponentValue` trait but load had no equivalent. None of these drawbacks were deal-breakers per-se. When I started to implement imported functions, though, the `Value` type no longer worked. The major difference between imports and exports is that when receiving values from wasm an export returns at most one wasm primitive where an import can yield (through arguments) up to 16 wasm primitives. This means that if an export returned a string it would always be `Value` but if an import took a string as an argument there was actually no way to represent this with `Value` since the value wasn't actually stored in memory but rather the pointer/length pair is received as arguments. Overall this meant that `Value` couldn't be used for arguments-to-imports, which means that altogether something new would be required. This PR completely removes the `Value` and `Cursor` type in favor of a different implementation. The inspiration from this comes from the fact that all primitives can be both lifted and lowered into wasm while it's just some times which can only go one direction. For example `String` can be lowered into wasm but can't be lifted from wasm. Instead some sort of "view" into wasm needs to be created during lifting. One of the realizations from #4039 was that we could leverage run-time-type-checking to reject static constructions that don't make sense. For example if an embedder asserts that a wasm function returns a Rust `String` we can reject that at typechecking time because it's impossible for a wasm module to ever do that. The new system of imports/exports in this PR now looks like: * Type-checking takes into accont an `Op` operation which indicates whether we'll be lifting or lowering the type. This means that we can allow the lowering operation for `String` but disallow the lifting operation. While we can't statically rule out an embedder saying that a component returns a `String` we can now reject it at runtime and disallow it from being called. * The `ComponentValue` trait now sports a new `load` function. This function will load and instance of `Self` from the byte-array provided. This is implemented for all types but only ever actually executed when the `lift` operation is allowed during type-checking. * The `Lift` associated type is removed since it's now expected that the lift operation returns `Self`. * The `ComponentReturn` trait is now no longer necessary and is removed. Instead returns are bounded by `ComponentValue`. During type-checking it's required that the return value can be lifted, disallowing, for example, returning a `String` or `&str`. * With `Value` gone there's no need to specify the ABI details of the return value, or whether it's communicated through memory or not. This means that handling return values through memory is transparently handled by Wasmtime. * Validation is in a sense more eagerly performed now. Whenever a value `T` is loaded the entire immediate structure of `T` is loaded and validated. Note that recursive through memory validation still does not happen, so the contents of lists or strings aren't validated, it's just validated that the pointers are in-bounds. Overall this felt like a much clearer system to work with and should be much easier to integrate with imported functions as well. The new `WasmStr` and `WasmList` types can be used in import arguments and lifted from the immediate arguments provided rather than forcing them to always be stored in memory. --- crates/wasmtime/src/component/func.rs | 7 +- crates/wasmtime/src/component/func/typed.rs | 1338 +++++++++---------- crates/wasmtime/src/component/instance.rs | 4 +- crates/wasmtime/src/component/mod.rs | 4 +- tests/all/component_model/func.rs | 302 ++--- 5 files changed, 774 insertions(+), 881 deletions(-) diff --git a/crates/wasmtime/src/component/func.rs b/crates/wasmtime/src/component/func.rs index 151c5d128e6f..2a5cb0300451 100644 --- a/crates/wasmtime/src/component/func.rs +++ b/crates/wasmtime/src/component/func.rs @@ -167,7 +167,7 @@ impl Func { pub fn typed(&self, store: S) -> Result> where Params: ComponentParams, - Return: ComponentReturn, + Return: ComponentValue, S: AsContext, { self.typecheck::(store.as_context().0)?; @@ -177,13 +177,14 @@ impl Func { fn typecheck(&self, store: &StoreOpaque) -> Result<()> where Params: ComponentParams, - Return: ComponentReturn, + Return: ComponentValue, { 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")?; + Return::typecheck(&ty.result, &data.types, Op::Lift) + .context("type mismatch with result")?; Ok(()) } diff --git a/crates/wasmtime/src/component/func/typed.rs b/crates/wasmtime/src/component/func/typed.rs index ddc0fce9ac83..995f982a626e 100644 --- a/crates/wasmtime/src/component/func/typed.rs +++ b/crates/wasmtime/src/component/func/typed.rs @@ -1,9 +1,8 @@ use crate::component::Func; use crate::store::StoreOpaque; -use crate::{AsContextMut, StoreContextMut, ValRaw}; +use crate::{AsContext, AsContextMut, StoreContext, 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; @@ -11,7 +10,6 @@ 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`. @@ -108,7 +106,7 @@ impl Clone for TypedFunc { impl TypedFunc where Params: ComponentParams, - Return: ComponentReturn, + Return: ComponentValue, { /// Creates a new [`TypedFunc`] from the provided component [`Func`], /// unsafely asserting that the underlying function takes `Params` as @@ -165,98 +163,163 @@ where /// /// 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(); + let store = &mut store.as_context_mut(); + // Note that this is in theory simpler than it might read at this time. + // Here we're doing a runtime dispatch on the `flatten_count` for the + // params/results to see whether they're inbounds. This creates 4 cases + // to handle. In reality this is a highly optimizable branch where LLVM + // will easily figure out that only one branch here is taken. + // + // Otherwise this current construction is done to ensure that the stack + // space reserved for the params/results is always of the appropriate + // size (as the params/results needed differ depending on the "flatten" + // count) if ::flatten_count() <= MAX_STACK_PARAMS { - self.call_stack_args(&mut store, ¶ms) + if Return::flatten_count() <= MAX_STACK_RESULTS { + self.call_raw( + store, + ¶ms, + Self::lower_stack_args, + Self::lift_stack_result, + ) + } else { + self.call_raw( + store, + ¶ms, + Self::lower_stack_args, + Self::lift_heap_result, + ) + } } else { - self.call_heap_args(&mut store, ¶ms) + if Return::flatten_count() <= MAX_STACK_RESULTS { + self.call_raw( + store, + ¶ms, + Self::lower_heap_args, + Self::lift_stack_result, + ) + } else { + self.call_raw( + store, + ¶ms, + Self::lower_heap_args, + Self::lift_heap_result, + ) + } } } - fn call_stack_args( + /// Lower parameters directly onto the stack specified by the `dst` + /// location. + /// + /// This is only valid to call when the "flatten count" is small enough, or + /// when the canonical ABI says arguments go through the stack rather than + /// the heap. + fn lower_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) + dst: &mut MaybeUninit<::Lower>, + ) -> Result<()> { + assert!(::flatten_count() <= MAX_STACK_PARAMS); + params.lower(store, &self.func, dst)?; + Ok(()) } - fn call_heap_args( + /// Lower parameters onto a heap-allocated location. + /// + /// This is used when the stack space to be used for the arguments is above + /// the `MAX_STACK_PARAMS` threshold. Here the wasm's `realloc` function is + /// invoked to allocate space and then parameters are stored at that heap + /// pointer location. + fn lower_heap_args( &self, store: &mut StoreContextMut<'_, T>, params: &Params, - ) -> Result { + dst: &mut MaybeUninit, + ) -> Result<()> { + assert!(::flatten_count() > MAX_STACK_PARAMS); + // 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); + // heap, so we can create a `MemoryMut` at this point. Afterwards + // `realloc` is used to allocate space for all the arguments and then + // they're all stored in linear memory. + // + // Note that `realloc` will bake in a check that the returned pointer is + // in-bounds. + let mut memory = MemoryMut::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. + // 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), - }); + dst.write(ValRaw::i64(ptr as i64)); + + Ok(()) + } + + /// Lift the result of a function directly from the stack result. + /// + /// This is only used when the result fits in the maximum number of stack + /// slots. + fn lift_stack_result(&self, store: &StoreOpaque, dst: &Return::Lower) -> Result { + assert!(Return::flatten_count() <= MAX_STACK_RESULTS); + Return::lift(store, &self.func, dst) + } - self.call_raw(store, params_and_results) + /// Lift the result of a function where the result is stored indirectly on + /// the heap. + fn lift_heap_result(&self, store: &StoreOpaque, dst: &ValRaw) -> Result { + assert!(Return::flatten_count() > MAX_STACK_RESULTS); + // FIXME: needs to read an i64 for memory64 + let ptr = usize::try_from(dst.get_u32()).unwrap(); + let memory = Memory::new(store, &self.func); + let bytes = memory + .as_slice() + .get(ptr..) + .and_then(|b| b.get(..Return::size())) + .ok_or_else(|| anyhow::anyhow!("pointer out of bounds of memory"))?; + Return::load(&memory, bytes) } - fn call_raw( + /// Invokes the underlying wasm function, lowering arguments and lifting the + /// result. + /// + /// The `lower` function and `lift` function provided here are what actually + /// do the lowering and lifting. The `LowerParams` and `LowerReturn` types + /// are what will be allocated on the stack for this function call. They + /// should be appropriately sized for the lowering/lifting operation + /// happening. + fn call_raw( &self, store: &mut StoreContextMut<'_, T>, - space: &mut MaybeUninit>, + params: &Params, + lower: impl FnOnce( + &Self, + &mut StoreContextMut<'_, T>, + &Params, + &mut MaybeUninit, + ) -> Result<()>, + lift: impl FnOnce(&Self, &StoreOpaque, &LowerReturn) -> Result, ) -> Result where - U: Copy, + LowerParams: Copy, + LowerReturn: Copy, { let super::FuncData { trampoline, export, .. } = store.0[self.func.0]; + let space = &mut MaybeUninit::>::uninit(); + // Double-check the size/alignemnt of `space`, just in case. // // Note that this alone is not enough to guarantee the validity of the @@ -272,6 +335,8 @@ where assert!(mem::align_of_val(map_maybe_uninit!(space.params)) == val_align); assert!(mem::align_of_val(map_maybe_uninit!(space.ret)) == val_align); + lower(self, store, params, map_maybe_uninit!(space.params))?; + unsafe { // This is unsafe as we are providing the guarantee that all the // inputs are valid. The various pointers passed in for the function @@ -288,16 +353,16 @@ where )?; // 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( + // on the correctness of the structure of `LowerReturn` and the + // type-checking performed to acquire the `TypedFunc` to make this + // safe. It should be the case that `LowerReturn` 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). + lift( + self, store.0, - &self.func, map_maybe_uninit!(space.ret).assume_init_ref(), ) } @@ -357,7 +422,7 @@ pub unsafe trait ComponentParams { /// 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<()> { + fn store(&self, memory: &mut MemoryMut<'_, T>, offset: usize) -> Result<()> { self.as_tuple().store(memory, offset) } @@ -394,7 +459,7 @@ macro_rules! impl_component_params { bail!("expected {} types, found {}", $n, params.len()); } let mut params = params.iter().map(|i| &i.1); - $($t::typecheck(params.next().unwrap(), _types)?;)* + $($t::typecheck(params.next().unwrap(), _types, Op::Lower)?;)* debug_assert!(params.next().is_none()); Ok(()) } @@ -462,31 +527,44 @@ pub unsafe trait ComponentValue { #[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. + /// Returns the number of core wasm abi values will be used to represent + /// this type in its lowered form. /// - /// 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. + /// This divides the size of `Self::Lower` by the size of `ValRaw`. #[doc(hidden)] - type Lift; + 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 a type-check to see whether this comopnent value type matches /// the interface type `ty` provided. + /// + /// The `op` provided is the operations which could be performed with this + /// type if the typecheck passes, either lifting or lowering. Some Rust + /// types are only valid for one operation and we can't prevent the wrong + /// one from being used at compile time so we rely on the runtime check + /// here. #[doc(hidden)] - fn typecheck(ty: &InterfaceType, types: &ComponentTypes) -> Result<()>; + fn typecheck(ty: &InterfaceType, types: &ComponentTypes, op: Op) -> 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 "lower" function in the canonical ABI. /// @@ -498,6 +576,8 @@ pub unsafe trait ComponentValue { /// 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. + /// + /// This will only be called if `typecheck` passes for `Op::Lower`. #[doc(hidden)] fn lower( &self, @@ -506,22 +586,24 @@ pub unsafe trait ComponentValue { dst: &mut MaybeUninit, ) -> Result<()>; - /// Returns the size, in bytes, that this type has in the canonical ABI. + /// Performs the "lift" oepration 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. + /// This will read the core wasm values from `src` and use the memory + /// specified by `func` and `store` optionally if necessary. An instance of + /// `Self` is then created from the values, assuming validation succeeds. /// - /// Note that it's expected that this function is "simple" to be easily - /// optimizable by LLVM (e.g. inlined and const-evaluated). + /// Note that this has a default implementation but if `typecheck` passes + /// for `Op::Lift` this needs to be overridden. #[doc(hidden)] - fn align() -> u32; + fn lift(store: &StoreOpaque, func: &Func, src: &Self::Lower) -> Result + where + Self: Sized, + { + // NB: ideally there would be no default implementation here but to + // enable `impl ComponentValue for str` this is the way we get it today. + drop((store, func, src)); + unreachable!("this should be rejected during typechecking") + } /// Performs the "store" operation in the canonical ABI. /// @@ -534,34 +616,45 @@ pub unsafe trait ComponentValue { /// 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`. + /// This will only be called if `typecheck` passes for `Op::Lower`. #[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::() - } + fn store(&self, memory: &mut MemoryMut<'_, T>, offset: usize) -> Result<()>; - /// Performs the "lift" oepration in the canonical ABI. + /// Performs the "load" operation 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 is given the linear-memory representation of `Self` in the `bytes` + /// array provided which is guaranteed to be `Self::size()` bytes large. All + /// of memory is then also described with `Memory` for bounds-checks and + /// such as necessary for strings/lists. /// - /// 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. + /// Note that this has a default implementation but if `typecheck` passes + /// for `Op::Lift` this needs to be overridden. #[doc(hidden)] - fn lift(src: &Self::Lower) -> Result; + fn load(memory: &Memory<'_>, bytes: &[u8]) -> Result + where + Self: Sized, + { + // See `lift` above for why there's a default implementation here + drop((memory, bytes)); + unreachable!("this should be rejected during typechecking") + } +} + +/// Operational parameter passed to `ComponentValue::typecheck` indicating how a +/// value will be used in a particular context. +/// +/// This is used to disallow, at runtime, loading a `&Vec` from wasm +/// since that can't be done. Instead that has to be `WasmList`. +#[doc(hidden)] +#[derive(Copy, Clone)] +pub enum Op { + /// A "lift" operation will be performed, meaning values will be read from + /// wasm. + Lift, + /// A "lower" operation will be performed, meaning values will be written to + /// wasm. + Lower, } /// A helper structure to package up proof-of-memory. This holds a store pointer @@ -573,14 +666,14 @@ pub unsafe trait ComponentValue { /// left this in for convenience in the hope that this can be updated in the /// future. #[doc(hidden)] -pub struct Memory<'a, T> { +pub struct MemoryMut<'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 } +impl<'a, T> MemoryMut<'a, T> { + fn new(store: StoreContextMut<'a, T>, func: &'a Func) -> MemoryMut<'a, T> { + MemoryMut { func, store } } #[inline] @@ -622,17 +715,50 @@ impl<'a, T> Memory<'a, T> { } } +/// Like `MemoryMut` but for a read-only version that's used during lifting. +#[doc(hidden)] +pub struct Memory<'a> { + store: &'a StoreOpaque, + func: &'a Func, +} + +impl<'a> Memory<'a> { + fn new(store: &'a StoreOpaque, func: &'a Func) -> Memory<'a> { + Memory { store, func } + } + + fn as_slice(&self) -> &'a [u8] { + self.func.memory(self.store) + } + + fn string_encoding(&self) -> StringEncoding { + self.store[self.func.0].options.string_encoding + } +} + // 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 { + 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 typecheck(ty: &InterfaceType, types: &ComponentTypes, op: Op) -> Result<()> { + match op { + // Lowering rust wrappers is ok since we know how to + // traverse the host to lower the item. + Op::Lower => {} + + // Lifting, however, is not ok and has no meaning. For + // example we can't create a `&str` from wasm. It also + // doesn't really make sense to create `Arc`, for + // example. In these cases different types need to be used + // such as `WasmList` or `WasmStr`. + Op::Lift => bail!("this type cannot be lifted from wasm"), + } + <$b as ComponentValue>::typecheck(ty, types, op) } fn lower( @@ -654,12 +780,16 @@ macro_rules! forward_component_param { <$b as ComponentValue>::align() } - fn store(&self, memory: &mut Memory<'_, U>, offset: usize) -> Result<()> { + fn store(&self, memory: &mut MemoryMut<'_, U>, offset: usize) -> Result<()> { <$b as ComponentValue>::store(self, memory, offset) } - fn lift(src: &Self::Lower) -> Result { - <$b as ComponentValue>::lift(src) + fn lift(_store: &StoreOpaque, _func: &Func,_src: &Self::Lower) -> Result { + unreachable!() + } + + fn load(_mem: &Memory, _bytes: &[u8]) -> Result { + unreachable!() } } )*) @@ -678,9 +808,8 @@ 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<()> { + fn typecheck(ty: &InterfaceType, _types: &ComponentTypes, _op: Op) -> Result<()> { match ty { // FIXME(WebAssembly/component-model#21) this may either want to // match more types, not actually exist as a trait impl, or @@ -712,12 +841,17 @@ unsafe impl ComponentValue for () { } #[inline] - fn store(&self, _memory: &mut Memory<'_, T>, _offset: usize) -> Result<()> { + fn store(&self, _memory: &mut MemoryMut<'_, T>, _offset: usize) -> Result<()> { Ok(()) } #[inline] - fn lift(_src: &Self::Lower) -> Result<()> { + fn lift(_store: &StoreOpaque, _func: &Func, _src: &Self::Lower) -> Result { + Ok(()) + } + + #[inline] + fn load(_mem: &Memory, _bytes: &[u8]) -> Result { Ok(()) } } @@ -728,9 +862,8 @@ macro_rules! integers { ($($primitive:ident = $ty:ident in $field:ident/$get:ident,)*) => ($( unsafe impl ComponentValue for $primitive { type Lower = ValRaw; - type Lift = $primitive; - fn typecheck(ty: &InterfaceType, _types: &ComponentTypes) -> Result<()> { + fn typecheck(ty: &InterfaceType, _types: &ComponentTypes, _op: Op) -> Result<()> { match ty { InterfaceType::$ty => Ok(()), other => bail!("expected `{}` found `{}`", desc(&InterfaceType::$ty), desc(other)) @@ -757,26 +890,23 @@ macro_rules! integers { #[inline] fn align() -> u32 { mem::size_of::<$primitive>() as u32 } - fn store(&self, memory: &mut Memory<'_, T>, offset: usize) -> Result<()> { + fn store(&self, memory: &mut MemoryMut<'_, T>, offset: usize) -> Result<()> { *memory.get(offset) = self.to_le_bytes(); Ok(()) } #[inline] - fn lift(src: &Self::Lower) -> Result { + fn lift(_store: &StoreOpaque, _func: &Func, src: &Self::Lower) -> Result { // 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(src.$get())?) } - } - 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()) + fn load(_mem: &Memory<'_>, bytes: &[u8]) -> Result { + Ok($primitive::from_le_bytes(bytes.try_into().unwrap())) } } )*) @@ -795,10 +925,10 @@ integers! { macro_rules! floats { ($($float:ident/$get_float: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. + /// 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() { @@ -810,9 +940,8 @@ macro_rules! floats { unsafe impl ComponentValue for $float { type Lower = ValRaw; - type Lift = $float; - fn typecheck(ty: &InterfaceType, _types: &ComponentTypes) -> Result<()> { + fn typecheck(ty: &InterfaceType, _types: &ComponentTypes, _op: Op) -> Result<()> { match ty { InterfaceType::$ty => Ok(()), other => bail!("expected `{}` found `{}`", desc(&InterfaceType::$ty), desc(other)) @@ -832,31 +961,25 @@ macro_rules! floats { #[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. + // 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<()> { + fn store(&self, memory: &mut MemoryMut<'_, 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 { + fn lift(_store: &StoreOpaque, _func: &Func, src: &Self::Lower) -> Result { Ok(canonicalize($float::from_bits(src.$get_float()))) } - } - 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())) + fn load(_mem: &Memory<'_>, bytes: &[u8]) -> Result { + Ok(canonicalize($float::from_le_bytes(bytes.try_into().unwrap()))) } } };)*) @@ -869,9 +992,8 @@ floats! { unsafe impl ComponentValue for bool { type Lower = ValRaw; - type Lift = bool; - fn typecheck(ty: &InterfaceType, _types: &ComponentTypes) -> Result<()> { + fn typecheck(ty: &InterfaceType, _types: &ComponentTypes, _op: Op) -> Result<()> { match ty { InterfaceType::Bool => Ok(()), other => bail!("expected `bool` found `{}`", desc(other)), @@ -898,31 +1020,23 @@ unsafe impl ComponentValue for bool { 1 } - fn store(&self, memory: &mut Memory<'_, T>, offset: usize) -> Result<()> { + fn store(&self, memory: &mut MemoryMut<'_, T>, offset: usize) -> Result<()> { memory.get::<1>(offset)[0] = *self as u8; Ok(()) } #[inline] - fn lift(src: &Self::Lower) -> Result { + fn lift(_store: &StoreOpaque, _func: &Func, src: &Self::Lower) -> Result { match src.get_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] { + fn load(_mem: &Memory<'_>, bytes: &[u8]) -> Result { + match bytes[0] { 0 => Ok(false), 1 => Ok(true), _ => bail!("invalid boolean value"), @@ -932,9 +1046,8 @@ impl Cursor<'_, bool> { unsafe impl ComponentValue for char { type Lower = ValRaw; - type Lift = char; - fn typecheck(ty: &InterfaceType, _types: &ComponentTypes) -> Result<()> { + fn typecheck(ty: &InterfaceType, _types: &ComponentTypes, _op: Op) -> Result<()> { match ty { InterfaceType::Char => Ok(()), other => bail!("expected `char` found `{}`", desc(other)), @@ -961,36 +1074,35 @@ unsafe impl ComponentValue for char { 4 } - fn store(&self, memory: &mut Memory<'_, T>, offset: usize) -> Result<()> { + fn store(&self, memory: &mut MemoryMut<'_, T>, offset: usize) -> Result<()> { *memory.get::<4>(offset) = u32::from(*self).to_le_bytes(); Ok(()) } #[inline] - fn lift(src: &Self::Lower) -> Result { + fn lift(_store: &StoreOpaque, _func: &Func, src: &Self::Lower) -> Result { Ok(char::try_from(src.get_u32())?) } -} -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()); + fn load(_memory: &Memory<'_>, bytes: &[u8]) -> Result { + let bits = u32::from_le_bytes(bytes.try_into().unwrap()); Ok(char::try_from(bits)?) } } +// Note that this is similar to `ComponentValue for WasmStr` except it can only +// be used for lowering, not lifting. unsafe impl ComponentValue for str { type Lower = [ValRaw; 2]; - type Lift = Infallible; - fn typecheck(ty: &InterfaceType, _types: &ComponentTypes) -> Result<()> { + fn typecheck(ty: &InterfaceType, _types: &ComponentTypes, op: Op) -> Result<()> { + match op { + Op::Lower => {} + Op::Lift => { + bail!("strings in Rust cannot be lifted from wasm, use `WasmStr` instead") + } + } match ty { InterfaceType::String => Ok(()), other => bail!("expected `string` found `{}`", desc(other)), @@ -1003,7 +1115,7 @@ unsafe impl ComponentValue for str { func: &Func, dst: &mut MaybeUninit<[ValRaw; 2]>, ) -> Result<()> { - let (ptr, len) = lower_string(&mut Memory::new(store.as_context_mut(), func), self)?; + let (ptr, len) = lower_string(&mut MemoryMut::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]).write(ValRaw::i64(ptr as i64)); @@ -1019,20 +1131,16 @@ unsafe impl ComponentValue for str { 4 } - fn store(&self, mem: &mut Memory<'_, T>, offset: usize) -> Result<()> { + fn store(&self, mem: &mut MemoryMut<'_, 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)> { +fn lower_string(mem: &mut MemoryMut<'_, T>, string: &str) -> Result<(usize, usize)> { match mem.string_encoding() { StringEncoding::Utf8 => { let ptr = mem.realloc(0, 0, 1, string.len())?; @@ -1061,50 +1169,87 @@ fn lower_string(mem: &mut Memory<'_, T>, string: &str) -> Result<(usize, usiz } } -impl<'a> Cursor<'a, String> { +/// Representation of a string located in linear memory in a WebAssembly +/// instance. +/// +/// This type is used with [`TypedFunc`], for example, when WebAssembly returns +/// a string. This type cannot be used to give a string to WebAssembly, instead +/// `&str` should be used for that (since it's coming from the host). +/// +/// Note that this type represents an in-bounds string in linear memory, but it +/// does not represent a valid string (e.g. valid utf-8). Validation happens +/// when [`WasmStr::to_str`] is called. +// +// TODO: should probably expand this with examples +pub struct WasmStr { + ptr: usize, + len: usize, + func: Func, +} + +impl WasmStr { + fn new(ptr: usize, len: usize, memory: &Memory<'_>) -> Result { + let byte_len = match memory.string_encoding() { + StringEncoding::Utf8 => Some(len), + StringEncoding::Utf16 => len.checked_mul(2), + StringEncoding::CompactUtf16 => unimplemented!(), + }; + match byte_len.and_then(|len| ptr.checked_add(len)) { + Some(n) if n <= memory.as_slice().len() => {} + _ => bail!("string pointer/length out of bounds of memory"), + } + Ok(WasmStr { + ptr, + len, + func: *memory.func, + }) + } + /// 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. /// + /// The `store` provided must be the store where this string lives to + /// access the correct memory. + /// /// # 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) - } - } + /// Returns an error if the string wasn't encoded correctly (e.g. invalid + /// utf-8). + /// + /// # Panics + /// + /// Panics if this string is not owned by `store`. + // + // TODO: should add accessors for specifically utf-8 and utf-16 that perhaps + // in an opt-in basis don't do validation. Additionally there should be some + // method that returns `[u16]` after validating to avoid the utf16-to-utf8 + // transcode. + pub fn to_str<'a, T: 'a>(&self, store: impl Into>) -> Result> { + self._to_str(store.into().0) + } + + fn _to_str<'a>(&self, store: &'a StoreOpaque) -> Result> { + match store[self.func.0].options.string_encoding { + StringEncoding::Utf8 => self.decode_utf8(store), + StringEncoding::Utf16 => self.decode_utf16(store), + StringEncoding::CompactUtf16 => unimplemented!(), } } - 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_utf8<'a>(&self, store: &'a StoreOpaque) -> Result> { + let memory = self.func.memory(store); + // Note that bounds-checking already happen in construction of `WasmStr` + // so this is never expected to panic. This could theoretically be + // unchecked indexing if we're feeling wild enough. + Ok(str::from_utf8(&memory[self.ptr..][..self.len])?.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"))?; + fn decode_utf16<'a>(&self, store: &'a StoreOpaque) -> Result> { + let memory = self.func.memory(store); + // See notes in `decode_utf8` for why this is panicking indexing. + let memory = &memory[self.ptr..][..self.len * 2]; Ok(std::char::decode_utf16( memory .chunks(2) @@ -1113,10 +1258,61 @@ impl<'a> Cursor<'a, String> { .collect::>()? .into()) } +} + +// Note that this is similar to `ComponentValue for str` except it can only be +// used for lifting, not lowering. +unsafe impl ComponentValue for WasmStr { + type Lower = [ValRaw; 2]; + + fn size() -> usize { + 8 + } + + fn align() -> u32 { + 4 + } + + fn typecheck(ty: &InterfaceType, _types: &ComponentTypes, op: Op) -> Result<()> { + match op { + Op::Lift => {} + Op::Lower => { + bail!("wasm strings cannot be lowered back to wasm, use `&str` instead") + } + } + 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<()> { + unreachable!() + } + + fn store(&self, _mem: &mut MemoryMut<'_, T>, _offset: usize) -> Result<()> { + unreachable!() + } + + fn lift(store: &StoreOpaque, func: &Func, src: &Self::Lower) -> Result { + // FIXME: needs memory64 treatment + let ptr = src[0].get_u32(); + let len = src[1].get_u32(); + let (ptr, len) = (usize::try_from(ptr)?, usize::try_from(len)?); + WasmStr::new(ptr, len, &Memory::new(store, func)) + } - fn decode_latin1(&self, ptr: usize, len: usize) -> Result> { - drop((ptr, len)); - unimplemented!() + fn load(memory: &Memory<'_>, bytes: &[u8]) -> Result { + // FIXME: needs memory64 treatment + let ptr = u32::from_le_bytes(bytes[..4].try_into().unwrap()); + let len = u32::from_le_bytes(bytes[4..].try_into().unwrap()); + let (ptr, len) = (usize::try_from(ptr)?, usize::try_from(len)?); + WasmStr::new(ptr, len, memory) } } @@ -1125,11 +1321,16 @@ where T: ComponentValue, { type Lower = [ValRaw; 2]; - type Lift = Infallible; - fn typecheck(ty: &InterfaceType, types: &ComponentTypes) -> Result<()> { + fn typecheck(ty: &InterfaceType, types: &ComponentTypes, op: Op) -> Result<()> { + match op { + Op::Lower => {} + Op::Lift => { + bail!("slices in Rust cannot be lifted from wasm, use `WasmList` instead") + } + } match ty { - InterfaceType::List(t) => T::typecheck(&types[*t], types), + InterfaceType::List(t) => T::typecheck(&types[*t], types, op), other => bail!("expected `list` found `{}`", desc(other)), } } @@ -1140,7 +1341,7 @@ where func: &Func, dst: &mut MaybeUninit<[ValRaw; 2]>, ) -> Result<()> { - let (ptr, len) = lower_list(&mut Memory::new(store.as_context_mut(), func), self)?; + let (ptr, len) = lower_list(&mut MemoryMut::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]).write(ValRaw::i64(ptr as i64)); @@ -1158,16 +1359,12 @@ where 4 } - fn store(&self, mem: &mut Memory<'_, U>, offset: usize) -> Result<()> { + fn store(&self, mem: &mut MemoryMut<'_, 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`. @@ -1175,17 +1372,17 @@ where // 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 +// * `MemoryMut` 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 +// * The bounds-checks in `MemoryMut::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 +// pointer fo memory (I guess from `MemoryMut` 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)> +fn lower_list(mem: &mut MemoryMut<'_, U>, list: &[T]) -> Result<(usize, usize)> where T: ComponentValue, { @@ -1203,70 +1400,151 @@ where Ok((ptr, list.len())) } -impl<'a, T: ComponentValue> Cursor<'a, Vec> { +/// Representation of a list of values that are owned by a WebAssembly instance. +/// +/// This type is used whenever a `(list T)` is returned from a [`TypedFunc`], +/// for example. This type represents a list of values that are stored in linear +/// memory which are waiting to be read. +/// +/// Note that this type represents only a valid range of bytes for the list +/// itself, it does not represent validity of the elements themselves and that's +/// performed when they're iterated. +pub struct WasmList { + ptr: usize, + len: usize, + func: Func, + _marker: marker::PhantomData, +} + +impl WasmList { + fn new(ptr: usize, len: usize, memory: &Memory<'_>) -> Result> { + match len + .checked_mul(T::size()) + .and_then(|len| ptr.checked_add(len)) + { + Some(n) if n <= memory.as_slice().len() => {} + _ => bail!("list pointer/length out of bounds of memory"), + } + Ok(WasmList { + ptr, + len, + func: *memory.func, + _marker: marker::PhantomData, + }) + } + /// Returns the item length of this vector + #[inline] pub fn len(&self) -> usize { - // FIXME: needs memory64 treatment - u32::from_le_bytes(self.item_bytes()[4..].try_into().unwrap()) as usize + self.len } - /// Returns an iterator over the elements of this vector. + /// Gets the `n`th element of this list. /// - /// 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 `None` if `index` is out of bounds. Returns `Some(Err(..))` if + /// the value couldn't be decoded (it was invalid). Returns `Some(Ok(..))` + /// if the value is valid. + // + // TODO: given that interface values are intended to be consumed in one go + // should we even expose a random access iteration API? In theory all + // consumers should be validating through the iterator. + pub fn get(&self, store: impl AsContext, index: usize) -> Option> { + self._get(store.as_context().0, index) + } + + fn _get(&self, store: &StoreOpaque, index: usize) -> Option> { + if index >= self.len { + return None; + } + let memory = Memory::new(store, &self.func); + // Note that this is using panicking indexing and this is expected to + // never fail. The bounds-checking here happened during the construction + // of the `WasmList` itself which means these should always be in-bounds + // (and wasm memory can only grow). This could theoretically be + // unchecked indexing if we're confident enough and it's actually a perf + // issue one day. + let bytes = &memory.as_slice()[self.ptr + index * T::size()..][..T::size()]; + Some(T::load(&memory, bytes)) + } + + /// Returns an iterator over the elements of this list. /// - /// 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) } - })) + /// Each item of the list may fail to decode and is represented through the + /// `Result` value of the iterator. + pub fn iter<'a, U: 'a>( + &'a self, + store: impl Into>, + ) -> impl ExactSizeIterator> + 'a { + let store = store.into().0; + (0..self.len).map(move |i| self._get(store, i).unwrap()) } } -impl<'a> Cursor<'a, Vec> { +impl WasmList { /// 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")) + pub fn as_slice<'a, T: 'a>(&self, store: impl Into>) -> &'a [u8] { + // See comments in `WasmList::get` for the panicking indexing + &self.func.memory(store.into().0)[self.ptr..][..self.len] + } +} + +// Note that this is similar to `ComponentValue for str` except it can only be +// used for lifting, not lowering. +unsafe impl ComponentValue for WasmList { + type Lower = [ValRaw; 2]; + + fn size() -> usize { + 8 + } + + fn align() -> u32 { + 4 + } + + fn typecheck(ty: &InterfaceType, types: &ComponentTypes, op: Op) -> Result<()> { + match op { + Op::Lift => {} + Op::Lower => { + bail!("wasm lists cannot be lowered back to wasm, use `&[T]` instead") + } + } + match ty { + InterfaceType::List(t) => T::typecheck(&types[*t], types, op), + other => bail!("expected `list` found `{}`", desc(other)), + } + } + + fn lower( + &self, + _store: &mut StoreContextMut, + _func: &Func, + _dst: &mut MaybeUninit<[ValRaw; 2]>, + ) -> Result<()> { + unreachable!() + } + + fn store(&self, _mem: &mut MemoryMut<'_, U>, _offset: usize) -> Result<()> { + unreachable!() + } + + fn lift(store: &StoreOpaque, func: &Func, src: &Self::Lower) -> Result { + // FIXME: needs memory64 treatment + let ptr = src[0].get_u32(); + let len = src[1].get_u32(); + let (ptr, len) = (usize::try_from(ptr)?, usize::try_from(len)?); + WasmList::new(ptr, len, &Memory::new(store, func)) + } + + fn load(memory: &Memory<'_>, bytes: &[u8]) -> Result { + // FIXME: needs memory64 treatment + let ptr = u32::from_le_bytes(bytes[..4].try_into().unwrap()); + let len = u32::from_le_bytes(bytes[4..].try_into().unwrap()); + let (ptr, len) = (usize::try_from(ptr)?, usize::try_from(len)?); + WasmList::new(ptr, len, memory) } } @@ -1282,11 +1560,10 @@ where T: ComponentValue, { type Lower = TupleLower2<::Lower, T::Lower>; - type Lift = Option; - fn typecheck(ty: &InterfaceType, types: &ComponentTypes) -> Result<()> { + fn typecheck(ty: &InterfaceType, types: &ComponentTypes, op: Op) -> Result<()> { match ty { - InterfaceType::Option(t) => T::typecheck(&types[*t], types), + InterfaceType::Option(t) => T::typecheck(&types[*t], types, op), other => bail!("expected `option` found `{}`", desc(other)), } } @@ -1328,7 +1605,7 @@ where T::align() } - fn store(&self, mem: &mut Memory<'_, U>, offset: usize) -> Result<()> { + fn store(&self, mem: &mut MemoryMut<'_, U>, offset: usize) -> Result<()> { match self { None => { mem.get::<1>(offset)[0] = 0; @@ -1341,30 +1618,20 @@ where Ok(()) } - fn lift(src: &Self::Lower) -> Result { + fn lift(store: &StoreOpaque, func: &Func, src: &Self::Lower) -> Result { Ok(match src.A1.get_i32() { 0 => None, - 1 => Some(T::lift(&src.A2)?), + 1 => Some(T::lift(store, func, &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] { + fn load(memory: &Memory<'_>, bytes: &[u8]) -> Result { + let discrim = bytes[0]; + let payload = &bytes[align_to(1, T::align())..]; + match discrim { 0 => Ok(None), - 1 => Ok(Some(self.bump(align_to(1, T::align())))), + 1 => Ok(Some(T::load(memory, payload)?)), _ => bail!("invalid option discriminant"), } } @@ -1390,14 +1657,13 @@ where E: ComponentValue, { type Lower = ResultLower; - type Lift = Result; - fn typecheck(ty: &InterfaceType, types: &ComponentTypes) -> Result<()> { + fn typecheck(ty: &InterfaceType, types: &ComponentTypes, op: Op) -> Result<()> { match ty { InterfaceType::Expected(r) => { let expected = &types[*r]; - T::typecheck(&expected.ok, types)?; - E::typecheck(&expected.err, types)?; + T::typecheck(&expected.ok, types, op)?; + E::typecheck(&expected.err, types, op)?; Ok(()) } other => bail!("expected `expected` found `{}`", desc(other)), @@ -1451,7 +1717,7 @@ where T::align().max(E::align()) } - fn store(&self, mem: &mut Memory<'_, U>, offset: usize) -> Result<()> { + fn store(&self, mem: &mut MemoryMut<'_, U>, offset: usize) -> Result<()> { match self { Ok(e) => { mem.get::<1>(offset)[0] = 0; @@ -1465,49 +1731,43 @@ where 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. + fn lift(store: &StoreOpaque, func: &Func, src: &Self::Lower) -> Result { + // TODO: need to make a case that upper-bits are not validated to be + // zero. + // + // * `Result` flattens as `i32 i64` + // * This impl wants to say `0 u64::MAX` is a valid flattening of + // `Ok(u32::MAX)`. + // * Otherwise validation needs to be performed that, where necessary, + // upper bits are zero. + // * Points in favor: + // * `Result` flattens as `i32 i32 i32` and + // `Ok(0)` doesn't validate that the third `i32` is any + // particular value. + // * Padding bytes are ignored in the in-memory representation. // - // 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); + // Otherwise I don't know how to implement the validation for now. This + // would need to, at compile time via the Rust trait system, figure out + // the flatten lowering for `Result` for `T` and `E` and then ask + // `T`'s lowered type to validate that it's valid within the context of + // the the overall lowered type. This... is trait trickery that is + // beyond me but seems like it should be possible. Would be nice if we + // didn't have to do that though. Ok(match src.tag.get_i32() { - 0 => Ok(unsafe { T::lift(&src.payload.ok)? }), - 1 => Err(unsafe { E::lift(&src.payload.err)? }), + 0 => Ok(unsafe { T::lift(store, func, &src.payload.ok)? }), + 1 => Err(unsafe { E::lift(store, func, &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>>> { + fn load(memory: &Memory<'_>, bytes: &[u8]) -> Result { 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)))), + let discrim = bytes[0]; + let payload = &bytes[align_to(1, align)..]; + match discrim { + 0 => Ok(Ok(T::load(memory, &payload[..T::size()])?)), + 1 => Ok(Err(E::load(memory, &payload[..E::size()])?)), _ => bail!("invalid expected discriminant"), } } @@ -1535,11 +1795,11 @@ macro_rules! impl_component_ty_for_tuples { where $($t: ComponentValue),* { type Lower = []<$($t::Lower),*>; - type Lift = ($($t::Lift,)*); fn typecheck( ty: &InterfaceType, types: &ComponentTypes, + op: Op, ) -> Result<()> { match ty { InterfaceType::Tuple(t) => { @@ -1548,7 +1808,7 @@ macro_rules! impl_component_ty_for_tuples { bail!("expected {}-tuple, found {}-tuple", $n, tuple.types.len()); } let mut tuple = tuple.types.iter(); - $($t::typecheck(tuple.next().unwrap(), types)?;)* + $($t::typecheck(tuple.next().unwrap(), types, op)?;)* debug_assert!(tuple.next().is_none()); Ok(()) } @@ -1581,7 +1841,7 @@ macro_rules! impl_component_ty_for_tuples { align } - fn store(&self, memory: &mut Memory<'_, U>, mut offset: usize) -> Result<()> { + fn store(&self, memory: &mut MemoryMut<'_, U>, mut offset: usize) -> Result<()> { let ($($t,)*) = self; // TODO: this requires that `offset` is aligned which we may not // want to do @@ -1594,45 +1854,22 @@ macro_rules! impl_component_ty_for_tuples { Ok(()) } - #[inline] - fn lift(src: &Self::Lower) -> Result { - Ok(($($t::lift(&src.$t)?,)*)) + fn lift(store: &StoreOpaque, func: &Func, src: &Self::Lower) -> Result { + Ok(($($t::lift(store, func, &src.$t)?,)*)) } - } - impl<'a, $($t),*> Cursor<'a, ($($t,)*)> - where - $($t: ComponentValue),* - { - fn start_offset(&self) -> usize { - 0 + fn load(memory: &Memory<'_>, bytes: &[u8]) -> Result { + let mut _offset = 0; + $( + _offset = align_to(_offset, $t::align()); + let $t = $t::load(memory, &bytes[_offset..][..$t::size()])?; + _offset += $t::size(); + )* + Ok(($($t,)*)) } - - 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); @@ -1665,296 +1902,3 @@ fn desc(ty: &InterfaceType) -> &'static str { 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 = src.get_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 ab5e53325f1f..eba7f8727456 100644 --- a/crates/wasmtime/src/component/instance.rs +++ b/crates/wasmtime/src/component/instance.rs @@ -1,4 +1,4 @@ -use crate::component::{Component, ComponentParams, ComponentReturn, Func, TypedFunc}; +use crate::component::{Component, ComponentParams, ComponentValue, Func, TypedFunc}; use crate::instance::OwnedImports; use crate::store::{StoreOpaque, Stored}; use crate::{AsContextMut, Module, StoreContextMut}; @@ -89,7 +89,7 @@ impl Instance { ) -> Result> where Params: ComponentParams, - Results: ComponentReturn, + Results: ComponentValue, S: AsContextMut, { let f = self diff --git a/crates/wasmtime/src/component/mod.rs b/crates/wasmtime/src/component/mod.rs index 99c7da84b723..0a9014f656a4 100644 --- a/crates/wasmtime/src/component/mod.rs +++ b/crates/wasmtime/src/component/mod.rs @@ -8,9 +8,7 @@ mod func; mod instance; mod store; pub use self::component::Component; -pub use self::func::{ - ComponentParams, ComponentReturn, ComponentValue, Cursor, Func, TypedFunc, Value, -}; +pub use self::func::{ComponentParams, ComponentValue, Func, Op, TypedFunc, WasmList, WasmStr}; pub use self::instance::Instance; // These items are expected to be used by an eventual diff --git a/tests/all/component_model/func.rs b/tests/all/component_model/func.rs index 69eb4d56a952..40d6469cd3c0 100644 --- a/tests/all/component_model/func.rs +++ b/tests/all/component_model/func.rs @@ -134,6 +134,12 @@ fn typecheck() -> Result<()> { (func (export "ret-tuple1") (canon.lift (func (result (tuple u32))) (into $i) (func $i "ret-one")) ) + (func (export "ret-string") + (canon.lift (func (result string)) (into $i) (func $i "ret-one")) + ) + (func (export "ret-list-u8") + (canon.lift (func (result (list u8))) (into $i) (func $i "ret-one")) + ) ) "#; @@ -146,13 +152,15 @@ fn typecheck() -> Result<()> { 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(); + let ret_string = instance.get_func(&mut store, "ret-string").unwrap(); + let ret_list_u8 = instance.get_func(&mut store, "ret-list-u8").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, _>(&store).is_err()); assert!(take_string - .typed::<(String, String), Value, _>(&store) + .typed::<(String, String), String, _>(&store) .is_err()); assert!(take_string.typed::<(String,), (), _>(&store).is_ok()); assert!(take_string.typed::<(&str,), (), _>(&store).is_ok()); @@ -161,17 +169,20 @@ fn typecheck() -> Result<()> { 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_tuple.typed::<(), (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()); + assert!(ret_string.typed::<(), (), _>(&store).is_err()); + assert!(ret_string.typed::<(), String, _>(&store).is_err()); + assert!(ret_string.typed::<(), &str, _>(&store).is_err()); + assert!(ret_string.typed::<(), WasmStr, _>(&store).is_ok()); + assert!(ret_list_u8.typed::<(), &[u8], _>(&store).is_err()); + assert!(ret_list_u8.typed::<(), Vec, _>(&store).is_err()); + assert!(ret_list_u8.typed::<(), WasmList, _>(&store).is_err()); + assert!(ret_list_u8.typed::<(), WasmList, _>(&store).is_err()); + assert!(ret_list_u8.typed::<(), WasmList, _>(&store).is_ok()); Ok(()) } @@ -719,24 +730,15 @@ fn tuple_result() -> Result<()> { 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 input = (-1, 100, 3.0, 100.0); + let output = instance + .get_typed_func::<(i8, u16, f32, f64), (i8, u16, f32, f64), _>(&mut store, "tuple")? + .call(&mut store, input)?; + assert_eq!(input, output); let invalid_func = - instance.get_typed_func::<(), Value<(i8, u16, f32, f64)>, _>(&mut store, "invalid")?; + instance.get_typed_func::<(), (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"), @@ -812,39 +814,27 @@ fn strings() -> Result<()> { 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")?; + instance.get_typed_func::<(&[u8],), WasmStr, _>(&mut store, "list8-to-str")?; let str_to_list8 = - instance.get_typed_func::<(&str,), Value>, _>(&mut store, "str-to-list8")?; + instance.get_typed_func::<(&str,), WasmList, _>(&mut store, "str-to-list8")?; let list16_to_str = - instance.get_typed_func::<(&[u16],), Value, _>(&mut store, "list16-to-str")?; + instance.get_typed_func::<(&[u16],), WasmStr, _>(&mut store, "list16-to-str")?; let str_to_list16 = - instance.get_typed_func::<(&str,), Value>, _>(&mut store, "str-to-list16")?; + instance.get_typed_func::<(&str,), WasmList, _>(&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); + assert_eq!(ret.to_str(&store)?, x); let utf16 = x.encode_utf16().collect::>(); let ret = list16_to_str.call(&mut store, (&utf16[..],))?; - assert_eq!(ret.cursor(&store).to_str()?, x); + assert_eq!(ret.to_str(&store)?, x); let ret = str_to_list8.call(&mut store, (x,))?; - assert_eq!( - ret.cursor(&store) - .iter()? - .map(|s| s.get()) - .collect::>(), - x.as_bytes() - ); + assert_eq!(ret.iter(&store).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, - ); + assert_eq!(ret.iter(&store).collect::>>()?, utf16,); Ok(()) }; @@ -856,23 +846,23 @@ fn strings() -> Result<()> { roundtrip("Löwe 老虎 Léopard")?; let ret = list8_to_str.call(&mut store, (b"\xff",))?; - let err = ret.cursor(&store).to_str().unwrap_err(); + let err = ret.to_str(&store).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(); + let err = ret.to_str(&store).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(); + let err = ret.to_str(&store).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(); + let err = ret.to_str(&store).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(); + let err = ret.to_str(&store).unwrap_err(); assert!(err.to_string().contains("unpaired surrogate"), "{}", err); Ok(()) @@ -962,7 +952,7 @@ fn many_parameters() -> Result<()> { &[bool], &[char], &[&str], - ), Value<(Vec, u32)>, _>(&mut store, "many-param")?; + ), (WasmList, u32), _>(&mut store, "many-param")?; let input = ( -100, @@ -987,12 +977,10 @@ fn many_parameters() -> Result<()> { ] .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 (memory, pointer) = func.call(&mut store, input)?; + let memory = memory.as_slice(&store); - let mut actual = &memory[pointer..][..72]; + let mut actual = &memory[pointer as usize..][..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); @@ -1317,22 +1305,16 @@ fn char_bool_memory() -> Result<()> { 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 func = instance.get_typed_func::<(u32, u32), (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'); + assert_eq!(ret, (false, '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()); + assert_eq!(ret, (true, '🍰')); - let ret = func.call(&mut store, (0, 0xd800))?; - assert!(ret.cursor(&store).a2().get().is_err()); + assert!(func.call(&mut store, (2, 'a' as u32)).is_err()); + assert!(func.call(&mut store, (0, 0xd800)).is_err()); Ok(()) } @@ -1381,17 +1363,14 @@ fn string_list_oob() -> Result<()> { 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 ret_list_u8 = instance.get_typed_func::<(), WasmList, _>(&mut store, "ret-list-u8")?; + let ret_string = instance.get_typed_func::<(), WasmStr, _>(&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 err = ret_list_u8.call(&mut store, ()).err().unwrap(); + assert!(err.to_string().contains("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); + let err = ret_string.call(&mut store, ()).err().unwrap(); + assert!(err.to_string().contains("out of bounds"), "{}", err); Ok(()) } @@ -1574,45 +1553,33 @@ fn option() -> Result<()> { 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)>, _>( + .get_typed_func::<(Option,), (u32, u32), _>(&mut store, "option-u8-to-tuple")?; + assert_eq!(option_u8_to_tuple.call(&mut store, (None,))?, (0, 0)); + assert_eq!(option_u8_to_tuple.call(&mut store, (Some(0),))?, (1, 0)); + assert_eq!(option_u8_to_tuple.call(&mut store, (Some(100),))?, (1, 100)); + + let option_u32_to_tuple = instance + .get_typed_func::<(Option,), (u32, u32), _>(&mut store, "option-u32-to-tuple")?; + assert_eq!(option_u32_to_tuple.call(&mut store, (None,))?, (0, 0)); + assert_eq!(option_u32_to_tuple.call(&mut store, (Some(0),))?, (1, 0)); + assert_eq!( + option_u32_to_tuple.call(&mut store, (Some(100),))?, + (1, 100) + ); + + let option_string_to_tuple = instance.get_typed_func::<(Option<&str>,), (u32, WasmStr), _>( &mut store, - "option-u32-to-tuple", + "option-string-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 (a, b) = option_string_to_tuple.call(&mut store, (None,))?; + assert_eq!(a, 0); + assert_eq!(b.to_str(&store)?, ""); + let (a, b) = option_string_to_tuple.call(&mut store, (Some(""),))?; + assert_eq!(a, 1); + assert_eq!(b.to_str(&store)?, ""); + let (a, b) = option_string_to_tuple.call(&mut store, (Some("hello"),))?; + assert_eq!(a, 1); + assert_eq!(b.to_str(&store)?, "hello"); let to_option_unit = instance.get_typed_func::<(u32,), Option<()>, _>(&mut store, "to-option-unit")?; @@ -1622,37 +1589,31 @@ fn option() -> Result<()> { 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()); + instance.get_typed_func::<(u32, u32), Option, _>(&mut store, "to-option-u8")?; + assert_eq!(to_option_u8.call(&mut store, (0x00_00, 0))?, None); + assert_eq!(to_option_u8.call(&mut store, (0x00_01, 0))?, Some(0)); + assert_eq!(to_option_u8.call(&mut store, (0xfd_01, 0))?, Some(0xfd)); + assert!(to_option_u8.call(&mut store, (0x00_02, 0)).is_err()); + + let to_option_u32 = + instance.get_typed_func::<(u32, u32), Option, _>(&mut store, "to-option-u32")?; + assert_eq!(to_option_u32.call(&mut store, (0, 0))?, None); + assert_eq!(to_option_u32.call(&mut store, (1, 0))?, Some(0)); + assert_eq!( + to_option_u32.call(&mut store, (1, 0x1234fead))?, + Some(0x1234fead) + ); + assert!(to_option_u32.call(&mut store, (2, 0)).is_err()); let to_option_string = instance - .get_typed_func::<(u32, &str), Value>, _>(&mut store, "to-option-string")?; + .get_typed_func::<(u32, &str), Option, _>(&mut store, "to-option-string")?; let ret = to_option_string.call(&mut store, (0, ""))?; - assert!(ret.cursor(&store).get()?.is_none()); + assert!(ret.is_none()); let ret = to_option_string.call(&mut store, (1, ""))?; - assert_eq!(ret.cursor(&store).get()?.unwrap().to_str()?, ""); + assert_eq!(ret.unwrap().to_str(&store)?, ""); 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()); + assert_eq!(ret.unwrap().to_str(&store)?, "cheesecake"); + assert!(to_option_string.call(&mut store, (2, "")).is_err()); Ok(()) } @@ -1757,28 +1718,24 @@ fn expected() -> Result<()> { 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()); + .get_typed_func::<(Result,), (u32, u32), _>(&mut store, "take-expected-u8-f32")?; + assert_eq!(take_expected_u8_f32.call(&mut store, (Ok(1),))?, (0, 1)); + assert_eq!( + take_expected_u8_f32.call(&mut store, (Err(2.0),))?, + (1, 2.0f32.to_bits()) + ); let take_expected_string = instance - .get_typed_func::<(Result<&str, &[u8]>,), Value<(u32, String)>, _>( + .get_typed_func::<(Result<&str, &[u8]>,), (u32, WasmStr), _>( &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 (a, b) = take_expected_string.call(&mut store, (Ok("hello"),))?; + assert_eq!(a, 0); + assert_eq!(b.to_str(&store)?, "hello"); + let (a, b) = take_expected_string.call(&mut store, (Err(b"goodbye"),))?; + assert_eq!(a, 1); + assert_eq!(b.to_str(&store)?, "goodbye"); let to_expected_unit = instance.get_typed_func::<(u32,), Result<(), ()>, _>(&mut store, "to-expected-unit")?; @@ -1787,23 +1744,17 @@ fn expected() -> Result<()> { 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))?; + let to_expected_s16_f32 = instance + .get_typed_func::<(u32, u32), Result, _>(&mut store, "to-expected-s16-f32")?; + assert_eq!(to_expected_s16_f32.call(&mut store, (0, 0))?, Ok(0)); + assert_eq!(to_expected_s16_f32.call(&mut store, (0, 100))?, Ok(100)); assert_eq!( - ret.cursor(&store).get()?.err().unwrap().get().to_bits(), - CANON_32BIT_NAN + to_expected_s16_f32.call(&mut store, (1, 1.0f32.to_bits()))?, + Err(1.0) ); - let ret = to_expected_s16_f32.call(&mut store, (2, 0))?; - assert!(ret.cursor(&store).get().is_err()); + let ret = to_expected_s16_f32.call(&mut store, (1, CANON_32BIT_NAN | 1))?; + assert_eq!(ret.unwrap_err().to_bits(), CANON_32BIT_NAN); + assert!(to_expected_s16_f32.call(&mut store, (2, 0)).is_err()); Ok(()) } @@ -1865,7 +1816,7 @@ fn fancy_list() -> Result<()> { let instance = Instance::new(&mut store, &component)?; let func = instance - .get_typed_func::<(&[(Option, Result<(), &str>)],), Value<(u32, u32, Vec)>, _>( + .get_typed_func::<(&[(Option, Result<(), &str>)],), (u32, u32, WasmList), _>( &mut store, "take", )?; @@ -1874,11 +1825,10 @@ fn fancy_list() -> Result<()> { (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 (ptr, len, list) = func.call(&mut store, (&input,))?; + let memory = list.as_slice(&store); + let ptr = usize::try_from(ptr).unwrap(); + let len = usize::try_from(len).unwrap(); let mut array = &memory[ptr..][..len * 16]; for (a, b) in input.iter() {