Expose raw list accessors for all integer types (#4330)

This commit extends the `WasmList<T>` 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<T>`
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.
This commit is contained in:
Alex Crichton
2022-06-28 10:23:58 -05:00
committed by GitHub
parent 2efdd5c46b
commit fc38f39bd2
2 changed files with 215 additions and 12 deletions

View File

@@ -1378,17 +1378,56 @@ impl<T: Lift> WasmList<T> {
} }
} }
impl WasmList<u8> { macro_rules! raw_wasm_list_accessors {
/// Get access to the raw underlying memory for this byte slice. ($($i:ident)*) => ($(
impl WasmList<$i> {
/// Get access to the raw underlying memory for this list.
/// ///
/// Note that this is specifically only implemented for a `(list u8)` type /// This method will return a direct slice into the original wasm
/// since it's known to be valid in terms of alignment and representation /// module's linear memory where the data for this slice is stored.
/// validity. /// This allows the embedder to have efficient access to the
pub fn as_slice<'a, T: 'a>(&self, store: impl Into<StoreContext<'a, T>>) -> &'a [u8] { /// 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<StoreContext<'a, T>>) -> &'a [$i] {
// See comments in `WasmList::get` for the panicking indexing // See comments in `WasmList::get` for the panicking indexing
&self.options.memory(store.into().0)[self.ptr..][..self.len] 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 // Note that this is similar to `ComponentValue for str` except it can only be
// used for lifting, not lowering. // used for lifting, not lowering.

View File

@@ -932,7 +932,7 @@ fn many_parameters() -> Result<()> {
.as_slice(), .as_slice(),
); );
let (memory, pointer) = func.call(&mut store, input)?; 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]; let mut actual = &memory[pointer as usize..][..72];
assert_eq!(i8::from_le_bytes(*actual.take_n::<1>()), input.0); 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")), (Some(200), Err("general kenobi")),
]; ];
let (ptr, len, list) = func.call(&mut store, (&input,))?; 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 ptr = usize::try_from(ptr).unwrap();
let len = usize::try_from(len).unwrap(); let len = usize::try_from(len).unwrap();
let mut array = &memory[ptr..][..len * 16]; let mut array = &memory[ptr..][..len * 16];
@@ -1990,3 +1990,167 @@ fn drop_component_still_works() -> Result<()> {
Ok(()) 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<u8>, _>(&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<i8>, _>(&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<u16>, _>(&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<i16>, _>(&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<u32>, _>(&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<i32>, _>(&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<u64>, _>(&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<i64>, _>(&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(())
}