From fc38f39bd20b50342ccfa670a0b90d7cb72451df Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 28 Jun 2022 10:23:58 -0500 Subject: [PATCH] Expose raw list accessors for all integer types (#4330) This commit extends the `WasmList` type to have an `as_slice`-lookalike method (now renamed to `as_le_slice`) for all integer types rather than just the `u8` type. With the guarantees of the component model it's known that all lists are aligned in linear memory. Additionally linear memories themselves are also generally guaranteed to be aligned. This means that hosts where the primitive integer alignment is at most the size (which I think is basically all host platforms) can get a raw view into memory for the wasm linear memory for slices of these types. Note, though, that the remaining caveat after alignment is endianness. Big-endian hosts need to be aware that the integers aren't stored in a native format. Previously tools like wit-bindgen have added an `Le` wrapper but for now I've opted to instead use a method that has "le" in the name - `as_le_slice`. I'm hoping that this is a clear enough indicator for users to little-endian conversions as appropriate when reading the values within the slice. --- crates/wasmtime/src/component/func/typed.rs | 59 +++++-- tests/all/component_model/func.rs | 168 +++++++++++++++++++- 2 files changed, 215 insertions(+), 12 deletions(-) diff --git a/crates/wasmtime/src/component/func/typed.rs b/crates/wasmtime/src/component/func/typed.rs index 36064ba849..221b1bb124 100644 --- a/crates/wasmtime/src/component/func/typed.rs +++ b/crates/wasmtime/src/component/func/typed.rs @@ -1378,16 +1378,55 @@ impl WasmList { } } -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. - pub fn as_slice<'a, T: 'a>(&self, store: impl Into>) -> &'a [u8] { - // See comments in `WasmList::get` for the panicking indexing - &self.options.memory(store.into().0)[self.ptr..][..self.len] - } +macro_rules! raw_wasm_list_accessors { + ($($i:ident)*) => ($( + impl WasmList<$i> { + /// Get access to the raw underlying memory for this list. + /// + /// This method will return a direct slice into the original wasm + /// module's linear memory where the data for this slice is stored. + /// This allows the embedder to have efficient access to the + /// underlying memory if needed and avoid copies and such if + /// desired. + /// + /// Note that multi-byte integers are stored in little-endian format + /// so portable processing of this slice must be aware of the host's + /// byte-endianness. The `from_le` constructors in the Rust standard + /// library should be suitable for converting from little-endian. + /// + /// # Panics + /// + /// Panics if the `store` provided is not the one from which this + /// slice originated. + pub fn as_le_slice<'a, T: 'a>(&self, store: impl Into>) -> &'a [$i] { + // See comments in `WasmList::get` for the panicking indexing + let byte_size = self.len * mem::size_of::<$i>(); + let bytes = &self.options.memory(store.into().0)[self.ptr..][..byte_size]; + + // The canonical ABI requires that everything is aligned to its + // own size, so this should be an aligned array. Furthermore the + // alignment of primitive integers for hosts should be smaller + // than or equal to the size of the primitive itself, meaning + // that a wasm canonical-abi-aligned list is also aligned for + // the host. That should mean that the head/tail slices here are + // empty. + // + // Also note that the `unsafe` here is needed since the type + // we're aligning to isn't guaranteed to be valid, but in our + // case it's just integers and bytes so this should be safe. + unsafe { + let (head, body, tail) = bytes.align_to::<$i>(); + assert!(head.is_empty() && tail.is_empty()); + body + } + } + } + )*) +} + +raw_wasm_list_accessors! { + i8 i16 i32 i64 + u8 u16 u32 u64 } // Note that this is similar to `ComponentValue for str` except it can only be diff --git a/tests/all/component_model/func.rs b/tests/all/component_model/func.rs index 6c89d8c591..84d4500947 100644 --- a/tests/all/component_model/func.rs +++ b/tests/all/component_model/func.rs @@ -932,7 +932,7 @@ fn many_parameters() -> Result<()> { .as_slice(), ); let (memory, pointer) = func.call(&mut store, input)?; - let memory = memory.as_slice(&store); + let memory = memory.as_le_slice(&store); let mut actual = &memory[pointer as usize..][..72]; assert_eq!(i8::from_le_bytes(*actual.take_n::<1>()), input.0); @@ -1780,7 +1780,7 @@ fn fancy_list() -> Result<()> { (Some(200), Err("general kenobi")), ]; let (ptr, len, list) = func.call(&mut store, (&input,))?; - let memory = list.as_slice(&store); + let memory = list.as_le_slice(&store); let ptr = usize::try_from(ptr).unwrap(); let len = usize::try_from(len).unwrap(); let mut array = &memory[ptr..][..len * 16]; @@ -1990,3 +1990,167 @@ fn drop_component_still_works() -> Result<()> { Ok(()) } + +#[test] +fn raw_slice_of_various_types() -> Result<()> { + let component = r#" + (component + (core module $m + (memory (export "memory") 1) + + (func (export "list8") (result i32) + (call $setup_list (i32.const 16)) + ) + (func (export "list16") (result i32) + (call $setup_list (i32.const 8)) + ) + (func (export "list32") (result i32) + (call $setup_list (i32.const 4)) + ) + (func (export "list64") (result i32) + (call $setup_list (i32.const 2)) + ) + + (func $setup_list (param i32) (result i32) + (i32.store offset=0 (i32.const 100) (i32.const 8)) + (i32.store offset=4 (i32.const 100) (local.get 0)) + i32.const 100 + ) + + (data (i32.const 8) "\00\01\02\03\04\05\06\07\08\09\0a\0b\0c\0d\0e\0f") + ) + (core instance $i (instantiate $m)) + (func (export "list-u8") (result (list u8)) + (canon lift (core func $i "list8") (memory $i "memory")) + ) + (func (export "list-i8") (result (list s8)) + (canon lift (core func $i "list8") (memory $i "memory")) + ) + (func (export "list-u16") (result (list u16)) + (canon lift (core func $i "list16") (memory $i "memory")) + ) + (func (export "list-i16") (result (list s16)) + (canon lift (core func $i "list16") (memory $i "memory")) + ) + (func (export "list-u32") (result (list u32)) + (canon lift (core func $i "list32") (memory $i "memory")) + ) + (func (export "list-i32") (result (list s32)) + (canon lift (core func $i "list32") (memory $i "memory")) + ) + (func (export "list-u64") (result (list u64)) + (canon lift (core func $i "list64") (memory $i "memory")) + ) + (func (export "list-i64") (result (list s64)) + (canon lift (core func $i "list64") (memory $i "memory")) + ) + ) + "#; + + let (mut store, instance) = { + let engine = super::engine(); + let component = Component::new(&engine, component)?; + let mut store = Store::new(&engine, ()); + let instance = Linker::new(&engine).instantiate(&mut store, &component)?; + (store, instance) + }; + + let list = instance + .get_typed_func::<(), WasmList, _>(&mut store, "list-u8")? + .call_and_post_return(&mut store, ())?; + assert_eq!( + list.as_le_slice(&store), + [ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0x0e, 0x0f, + ] + ); + let list = instance + .get_typed_func::<(), WasmList, _>(&mut store, "list-i8")? + .call_and_post_return(&mut store, ())?; + assert_eq!( + list.as_le_slice(&store), + [ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0x0e, 0x0f, + ] + ); + + let list = instance + .get_typed_func::<(), WasmList, _>(&mut store, "list-u16")? + .call_and_post_return(&mut store, ())?; + assert_eq!( + list.as_le_slice(&store), + [ + u16::to_le(0x01_00), + u16::to_le(0x03_02), + u16::to_le(0x05_04), + u16::to_le(0x07_06), + u16::to_le(0x09_08), + u16::to_le(0x0b_0a), + u16::to_le(0x0d_0c), + u16::to_le(0x0f_0e), + ] + ); + let list = instance + .get_typed_func::<(), WasmList, _>(&mut store, "list-i16")? + .call_and_post_return(&mut store, ())?; + assert_eq!( + list.as_le_slice(&store), + [ + i16::to_le(0x01_00), + i16::to_le(0x03_02), + i16::to_le(0x05_04), + i16::to_le(0x07_06), + i16::to_le(0x09_08), + i16::to_le(0x0b_0a), + i16::to_le(0x0d_0c), + i16::to_le(0x0f_0e), + ] + ); + let list = instance + .get_typed_func::<(), WasmList, _>(&mut store, "list-u32")? + .call_and_post_return(&mut store, ())?; + assert_eq!( + list.as_le_slice(&store), + [ + u32::to_le(0x03_02_01_00), + u32::to_le(0x07_06_05_04), + u32::to_le(0x0b_0a_09_08), + u32::to_le(0x0f_0e_0d_0c), + ] + ); + let list = instance + .get_typed_func::<(), WasmList, _>(&mut store, "list-i32")? + .call_and_post_return(&mut store, ())?; + assert_eq!( + list.as_le_slice(&store), + [ + i32::to_le(0x03_02_01_00), + i32::to_le(0x07_06_05_04), + i32::to_le(0x0b_0a_09_08), + i32::to_le(0x0f_0e_0d_0c), + ] + ); + let list = instance + .get_typed_func::<(), WasmList, _>(&mut store, "list-u64")? + .call_and_post_return(&mut store, ())?; + assert_eq!( + list.as_le_slice(&store), + [ + u64::to_le(0x07_06_05_04_03_02_01_00), + u64::to_le(0x0f_0e_0d_0c_0b_0a_09_08), + ] + ); + let list = instance + .get_typed_func::<(), WasmList, _>(&mut store, "list-i64")? + .call_and_post_return(&mut store, ())?; + assert_eq!( + list.as_le_slice(&store), + [ + i64::to_le(0x07_06_05_04_03_02_01_00), + i64::to_le(0x0f_0e_0d_0c_0b_0a_09_08), + ] + ); + Ok(()) +}