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(()) +}