diff --git a/crates/wasmtime/src/component/func.rs b/crates/wasmtime/src/component/func.rs index a1b52dac1821..a26665af2880 100644 --- a/crates/wasmtime/src/component/func.rs +++ b/crates/wasmtime/src/component/func.rs @@ -170,7 +170,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)?; @@ -180,13 +180,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 5ce236853d56..9d3f3555116e 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}; @@ -92,7 +92,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 199f8f11d48e..cd4d60b9c8a5 100644 --- a/crates/wasmtime/src/component/mod.rs +++ b/crates/wasmtime/src/component/mod.rs @@ -10,8 +10,7 @@ mod linker; mod store; pub use self::component::Component; pub use self::func::{ - ComponentParams, ComponentReturn, ComponentValue, Cursor, Func, IntoComponentFunc, TypedFunc, - Value, + ComponentParams, ComponentValue, Func, IntoComponentFunc, Op, TypedFunc, WasmList, WasmStr, }; pub use self::instance::Instance; pub use self::linker::Linker; 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() {