Files
wasmtime/tests/all/component_model/func.rs
Alex Crichton 479def00b9 Update lifting for integers and bools (#4237)
This commit updates lifting for integer types and boolean types to
account for WebAssembly/component-model#35 where extra bits are now
discarded instead of being validated as all zero.
2022-06-07 12:51:32 -05:00

1861 lines
66 KiB
Rust

use anyhow::Result;
use std::rc::Rc;
use std::sync::Arc;
use wasmtime::component::*;
use wasmtime::{Store, Trap, TrapCode};
const CANON_32BIT_NAN: u32 = 0b01111111110000000000000000000000;
const CANON_64BIT_NAN: u64 = 0b0111111111111000000000000000000000000000000000000000000000000000;
// A simple bump allocator which can be used with modules below
const REALLOC_AND_FREE: &str = r#"
(global $last (mut i32) (i32.const 8))
(func $realloc (export "canonical_abi_realloc")
(param $old_ptr i32)
(param $old_size i32)
(param $align i32)
(param $new_size i32)
(result i32)
;; Test if the old pointer is non-null
local.get $old_ptr
if
;; If the old size is bigger than the new size then
;; this is a shrink and transparently allow it
local.get $old_size
local.get $new_size
i32.gt_u
if
local.get $old_ptr
return
end
;; ... otherwise this is unimplemented
unreachable
end
;; align up `$last`
(global.set $last
(i32.and
(i32.add
(global.get $last)
(i32.add
(local.get $align)
(i32.const -1)))
(i32.xor
(i32.add
(local.get $align)
(i32.const -1))
(i32.const -1))))
;; save the current value of `$last` as the return value
global.get $last
;; ensure anything necessary is set to valid data by spraying a bit
;; pattern that is invalid
global.get $last
i32.const 0xde
local.get $new_size
memory.fill
;; bump our pointer
(global.set $last
(i32.add
(global.get $last)
(local.get $new_size)))
)
(func (export "canonical_abi_free") (param i32 i32 i32))
"#;
#[test]
fn thunks() -> Result<()> {
let component = r#"
(component
(module $m
(func (export "thunk"))
(func (export "thunk-trap") unreachable)
)
(instance $i (instantiate (module $m)))
(func (export "thunk")
(canon.lift (func) (func $i "thunk"))
)
(func (export "thunk-trap")
(canon.lift (func) (func $i "thunk-trap"))
)
)
"#;
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)?;
instance
.get_typed_func::<(), (), _>(&mut store, "thunk")?
.call(&mut store, ())?;
let err = instance
.get_typed_func::<(), (), _>(&mut store, "thunk-trap")?
.call(&mut store, ())
.unwrap_err();
assert!(err.downcast::<Trap>()?.trap_code() == Some(TrapCode::UnreachableCodeReached));
Ok(())
}
#[test]
fn typecheck() -> Result<()> {
let component = r#"
(component
(module $m
(func (export "thunk"))
(func (export "take-string") (param i32 i32))
(func (export "two-args") (param i32 i32 i32))
(func (export "ret-one") (result i32) unreachable)
(memory (export "memory") 1)
(func (export "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32)
unreachable)
(func (export "canonical_abi_free") (param i32 i32 i32)
unreachable)
)
(instance $i (instantiate (module $m)))
(func (export "thunk")
(canon.lift (func) (func $i "thunk"))
)
(func (export "take-string")
(canon.lift (func (param string)) (into $i) (func $i "take-string"))
)
(func (export "take-two-args")
(canon.lift (func (param s32) (param (list u8))) (into $i) (func $i "two-args"))
)
(func (export "ret-tuple")
(canon.lift (func (result (tuple u8 s8))) (into $i) (func $i "ret-one"))
)
(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"))
)
)
"#;
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)?;
let thunk = instance.get_func(&mut store, "thunk").unwrap();
let take_string = instance.get_func(&mut store, "take-string").unwrap();
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::<(String,), (), _>(&store).is_ok());
assert!(take_string.typed::<(&str,), (), _>(&store).is_ok());
assert!(take_string.typed::<(&[u8],), (), _>(&store).is_err());
assert!(take_two_args.typed::<(), (), _>(&store).is_err());
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!(ret_tuple.typed::<(), (), _>(&store).is_err());
assert!(ret_tuple.typed::<(), (u8,), _>(&store).is_err());
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_string.typed::<(), (), _>(&store).is_err());
assert!(ret_string.typed::<(), WasmStr, _>(&store).is_ok());
assert!(ret_list_u8.typed::<(), WasmList<u16>, _>(&store).is_err());
assert!(ret_list_u8.typed::<(), WasmList<i8>, _>(&store).is_err());
assert!(ret_list_u8.typed::<(), WasmList<u8>, _>(&store).is_ok());
Ok(())
}
#[test]
fn integers() -> Result<()> {
let component = r#"
(component
(module $m
(func (export "take-i32-100") (param i32)
local.get 0
i32.const 100
i32.eq
br_if 0
unreachable
)
(func (export "take-i64-100") (param i64)
local.get 0
i64.const 100
i64.eq
br_if 0
unreachable
)
(func (export "ret-i32-0") (result i32) i32.const 0)
(func (export "ret-i64-0") (result i64) i64.const 0)
(func (export "ret-i32-minus-1") (result i32) i32.const -1)
(func (export "ret-i64-minus-1") (result i64) i64.const -1)
(func (export "ret-i32-100000") (result i32) i32.const 100000)
)
(instance $i (instantiate (module $m)))
(func (export "take-u8") (canon.lift (func (param u8)) (func $i "take-i32-100")))
(func (export "take-s8") (canon.lift (func (param s8)) (func $i "take-i32-100")))
(func (export "take-u16") (canon.lift (func (param u16)) (func $i "take-i32-100")))
(func (export "take-s16") (canon.lift (func (param s16)) (func $i "take-i32-100")))
(func (export "take-u32") (canon.lift (func (param u32)) (func $i "take-i32-100")))
(func (export "take-s32") (canon.lift (func (param s32)) (func $i "take-i32-100")))
(func (export "take-u64") (canon.lift (func (param u64)) (func $i "take-i64-100")))
(func (export "take-s64") (canon.lift (func (param s64)) (func $i "take-i64-100")))
(func (export "ret-u8") (canon.lift (func (result u8)) (func $i "ret-i32-0")))
(func (export "ret-s8") (canon.lift (func (result s8)) (func $i "ret-i32-0")))
(func (export "ret-u16") (canon.lift (func (result u16)) (func $i "ret-i32-0")))
(func (export "ret-s16") (canon.lift (func (result s16)) (func $i "ret-i32-0")))
(func (export "ret-u32") (canon.lift (func (result u32)) (func $i "ret-i32-0")))
(func (export "ret-s32") (canon.lift (func (result s32)) (func $i "ret-i32-0")))
(func (export "ret-u64") (canon.lift (func (result u64)) (func $i "ret-i64-0")))
(func (export "ret-s64") (canon.lift (func (result s64)) (func $i "ret-i64-0")))
(func (export "retm1-u8") (canon.lift (func (result u8)) (func $i "ret-i32-minus-1")))
(func (export "retm1-s8") (canon.lift (func (result s8)) (func $i "ret-i32-minus-1")))
(func (export "retm1-u16") (canon.lift (func (result u16)) (func $i "ret-i32-minus-1")))
(func (export "retm1-s16") (canon.lift (func (result s16)) (func $i "ret-i32-minus-1")))
(func (export "retm1-u32") (canon.lift (func (result u32)) (func $i "ret-i32-minus-1")))
(func (export "retm1-s32") (canon.lift (func (result s32)) (func $i "ret-i32-minus-1")))
(func (export "retm1-u64") (canon.lift (func (result u64)) (func $i "ret-i64-minus-1")))
(func (export "retm1-s64") (canon.lift (func (result s64)) (func $i "ret-i64-minus-1")))
(func (export "retbig-u8") (canon.lift (func (result u8)) (func $i "ret-i32-100000")))
(func (export "retbig-s8") (canon.lift (func (result s8)) (func $i "ret-i32-100000")))
(func (export "retbig-u16") (canon.lift (func (result u16)) (func $i "ret-i32-100000")))
(func (export "retbig-s16") (canon.lift (func (result s16)) (func $i "ret-i32-100000")))
(func (export "retbig-u32") (canon.lift (func (result u32)) (func $i "ret-i32-100000")))
(func (export "retbig-s32") (canon.lift (func (result s32)) (func $i "ret-i32-100000")))
)
"#;
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)?;
// Passing in 100 is valid for all primitives
instance
.get_typed_func::<(u8,), (), _>(&mut store, "take-u8")?
.call(&mut store, (100,))?;
instance
.get_typed_func::<(i8,), (), _>(&mut store, "take-s8")?
.call(&mut store, (100,))?;
instance
.get_typed_func::<(u16,), (), _>(&mut store, "take-u16")?
.call(&mut store, (100,))?;
instance
.get_typed_func::<(i16,), (), _>(&mut store, "take-s16")?
.call(&mut store, (100,))?;
instance
.get_typed_func::<(u32,), (), _>(&mut store, "take-u32")?
.call(&mut store, (100,))?;
instance
.get_typed_func::<(i32,), (), _>(&mut store, "take-s32")?
.call(&mut store, (100,))?;
instance
.get_typed_func::<(u64,), (), _>(&mut store, "take-u64")?
.call(&mut store, (100,))?;
instance
.get_typed_func::<(i64,), (), _>(&mut store, "take-s64")?
.call(&mut store, (100,))?;
// This specific wasm instance traps if any value other than 100 is passed
instance
.get_typed_func::<(u8,), (), _>(&mut store, "take-u8")?
.call(&mut store, (101,))
.unwrap_err()
.downcast::<Trap>()?;
instance
.get_typed_func::<(i8,), (), _>(&mut store, "take-s8")?
.call(&mut store, (101,))
.unwrap_err()
.downcast::<Trap>()?;
instance
.get_typed_func::<(u16,), (), _>(&mut store, "take-u16")?
.call(&mut store, (101,))
.unwrap_err()
.downcast::<Trap>()?;
instance
.get_typed_func::<(i16,), (), _>(&mut store, "take-s16")?
.call(&mut store, (101,))
.unwrap_err()
.downcast::<Trap>()?;
instance
.get_typed_func::<(u32,), (), _>(&mut store, "take-u32")?
.call(&mut store, (101,))
.unwrap_err()
.downcast::<Trap>()?;
instance
.get_typed_func::<(i32,), (), _>(&mut store, "take-s32")?
.call(&mut store, (101,))
.unwrap_err()
.downcast::<Trap>()?;
instance
.get_typed_func::<(u64,), (), _>(&mut store, "take-u64")?
.call(&mut store, (101,))
.unwrap_err()
.downcast::<Trap>()?;
instance
.get_typed_func::<(i64,), (), _>(&mut store, "take-s64")?
.call(&mut store, (101,))
.unwrap_err()
.downcast::<Trap>()?;
// Zero can be returned as any integer
assert_eq!(
instance
.get_typed_func::<(), u8, _>(&mut store, "ret-u8")?
.call(&mut store, ())?,
0
);
assert_eq!(
instance
.get_typed_func::<(), i8, _>(&mut store, "ret-s8")?
.call(&mut store, ())?,
0
);
assert_eq!(
instance
.get_typed_func::<(), u16, _>(&mut store, "ret-u16")?
.call(&mut store, ())?,
0
);
assert_eq!(
instance
.get_typed_func::<(), i16, _>(&mut store, "ret-s16")?
.call(&mut store, ())?,
0
);
assert_eq!(
instance
.get_typed_func::<(), u32, _>(&mut store, "ret-u32")?
.call(&mut store, ())?,
0
);
assert_eq!(
instance
.get_typed_func::<(), i32, _>(&mut store, "ret-s32")?
.call(&mut store, ())?,
0
);
assert_eq!(
instance
.get_typed_func::<(), u64, _>(&mut store, "ret-u64")?
.call(&mut store, ())?,
0
);
assert_eq!(
instance
.get_typed_func::<(), i64, _>(&mut store, "ret-s64")?
.call(&mut store, ())?,
0
);
// Returning -1 should reinterpret the bytes as defined by each type.
assert_eq!(
instance
.get_typed_func::<(), u8, _>(&mut store, "retm1-u8")?
.call(&mut store, ())?,
0xff
);
assert_eq!(
instance
.get_typed_func::<(), i8, _>(&mut store, "retm1-s8")?
.call(&mut store, ())?,
-1
);
assert_eq!(
instance
.get_typed_func::<(), u16, _>(&mut store, "retm1-u16")?
.call(&mut store, ())?,
0xffff
);
assert_eq!(
instance
.get_typed_func::<(), i16, _>(&mut store, "retm1-s16")?
.call(&mut store, ())?,
-1
);
assert_eq!(
instance
.get_typed_func::<(), u32, _>(&mut store, "retm1-u32")?
.call(&mut store, ())?,
0xffffffff
);
assert_eq!(
instance
.get_typed_func::<(), i32, _>(&mut store, "retm1-s32")?
.call(&mut store, ())?,
-1
);
assert_eq!(
instance
.get_typed_func::<(), u64, _>(&mut store, "retm1-u64")?
.call(&mut store, ())?,
0xffffffff_ffffffff
);
assert_eq!(
instance
.get_typed_func::<(), i64, _>(&mut store, "retm1-s64")?
.call(&mut store, ())?,
-1
);
// Returning 100000 should chop off bytes as necessary
let ret: u32 = 100000;
assert_eq!(
instance
.get_typed_func::<(), u8, _>(&mut store, "retbig-u8")?
.call(&mut store, ())?,
ret as u8,
);
assert_eq!(
instance
.get_typed_func::<(), i8, _>(&mut store, "retbig-s8")?
.call(&mut store, ())?,
ret as i8,
);
assert_eq!(
instance
.get_typed_func::<(), u16, _>(&mut store, "retbig-u16")?
.call(&mut store, ())?,
ret as u16,
);
assert_eq!(
instance
.get_typed_func::<(), i16, _>(&mut store, "retbig-s16")?
.call(&mut store, ())?,
ret as i16,
);
assert_eq!(
instance
.get_typed_func::<(), u32, _>(&mut store, "retbig-u32")?
.call(&mut store, ())?,
ret,
);
assert_eq!(
instance
.get_typed_func::<(), i32, _>(&mut store, "retbig-s32")?
.call(&mut store, ())?,
ret as i32,
);
Ok(())
}
#[test]
fn type_layers() -> Result<()> {
let component = r#"
(component
(module $m
(func (export "take-i32-100") (param i32)
local.get 0
i32.const 2
i32.eq
br_if 0
unreachable
)
)
(instance $i (instantiate (module $m)))
(func (export "take-u32") (canon.lift (func (param u32)) (func $i "take-i32-100")))
)
"#;
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)?;
instance
.get_typed_func::<(Box<u32>,), (), _>(&mut store, "take-u32")?
.call(&mut store, (Box::new(2),))?;
instance
.get_typed_func::<(&u32,), (), _>(&mut store, "take-u32")?
.call(&mut store, (&2,))?;
instance
.get_typed_func::<(Rc<u32>,), (), _>(&mut store, "take-u32")?
.call(&mut store, (Rc::new(2),))?;
instance
.get_typed_func::<(Arc<u32>,), (), _>(&mut store, "take-u32")?
.call(&mut store, (Arc::new(2),))?;
instance
.get_typed_func::<(&Box<Arc<Rc<u32>>>,), (), _>(&mut store, "take-u32")?
.call(&mut store, (&Box::new(Arc::new(Rc::new(2))),))?;
Ok(())
}
#[test]
fn floats() -> Result<()> {
let component = r#"
(component
(module $m
(func (export "i32.reinterpret_f32") (param f32) (result i32)
local.get 0
i32.reinterpret_f32
)
(func (export "i64.reinterpret_f64") (param f64) (result i64)
local.get 0
i64.reinterpret_f64
)
(func (export "f32.reinterpret_i32") (param i32) (result f32)
local.get 0
f32.reinterpret_i32
)
(func (export "f64.reinterpret_i64") (param i64) (result f64)
local.get 0
f64.reinterpret_i64
)
)
(instance $i (instantiate (module $m)))
(func (export "f32-to-u32")
(canon.lift (func (param float32) (result u32)) (func $i "i32.reinterpret_f32"))
)
(func (export "f64-to-u64")
(canon.lift (func (param float64) (result u64)) (func $i "i64.reinterpret_f64"))
)
(func (export "u32-to-f32")
(canon.lift (func (param u32) (result float32)) (func $i "f32.reinterpret_i32"))
)
(func (export "u64-to-f64")
(canon.lift (func (param u64) (result float64)) (func $i "f64.reinterpret_i64"))
)
)
"#;
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)?;
let f32_to_u32 = instance.get_typed_func::<(f32,), u32, _>(&mut store, "f32-to-u32")?;
let f64_to_u64 = instance.get_typed_func::<(f64,), u64, _>(&mut store, "f64-to-u64")?;
let u32_to_f32 = instance.get_typed_func::<(u32,), f32, _>(&mut store, "u32-to-f32")?;
let u64_to_f64 = instance.get_typed_func::<(u64,), f64, _>(&mut store, "u64-to-f64")?;
assert_eq!(f32_to_u32.call(&mut store, (1.0,))?, 1.0f32.to_bits());
assert_eq!(f64_to_u64.call(&mut store, (2.0,))?, 2.0f64.to_bits());
assert_eq!(u32_to_f32.call(&mut store, (3.0f32.to_bits(),))?, 3.0);
assert_eq!(u64_to_f64.call(&mut store, (4.0f64.to_bits(),))?, 4.0);
assert_eq!(
u32_to_f32
.call(&mut store, (CANON_32BIT_NAN | 1,))?
.to_bits(),
CANON_32BIT_NAN
);
assert_eq!(
u64_to_f64
.call(&mut store, (CANON_64BIT_NAN | 1,))?
.to_bits(),
CANON_64BIT_NAN
);
assert_eq!(
f32_to_u32.call(&mut store, (f32::from_bits(CANON_32BIT_NAN | 1),))?,
CANON_32BIT_NAN
);
assert_eq!(
f64_to_u64.call(&mut store, (f64::from_bits(CANON_64BIT_NAN | 1),))?,
CANON_64BIT_NAN
);
Ok(())
}
#[test]
fn bools() -> Result<()> {
let component = r#"
(component
(module $m
(func (export "pass") (param i32) (result i32) local.get 0)
)
(instance $i (instantiate (module $m)))
(func (export "u32-to-bool")
(canon.lift (func (param u32) (result bool)) (func $i "pass"))
)
(func (export "bool-to-u32")
(canon.lift (func (param bool) (result u32)) (func $i "pass"))
)
)
"#;
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)?;
let u32_to_bool = instance.get_typed_func::<(u32,), bool, _>(&mut store, "u32-to-bool")?;
let bool_to_u32 = instance.get_typed_func::<(bool,), u32, _>(&mut store, "bool-to-u32")?;
assert_eq!(bool_to_u32.call(&mut store, (false,))?, 0);
assert_eq!(bool_to_u32.call(&mut store, (true,))?, 1);
assert_eq!(u32_to_bool.call(&mut store, (0,))?, false);
assert_eq!(u32_to_bool.call(&mut store, (1,))?, true);
assert_eq!(u32_to_bool.call(&mut store, (2,))?, true);
Ok(())
}
#[test]
fn chars() -> Result<()> {
let component = r#"
(component
(module $m
(func (export "pass") (param i32) (result i32) local.get 0)
)
(instance $i (instantiate (module $m)))
(func (export "u32-to-char")
(canon.lift (func (param u32) (result char)) (func $i "pass"))
)
(func (export "char-to-u32")
(canon.lift (func (param char) (result u32)) (func $i "pass"))
)
)
"#;
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)?;
let u32_to_char = instance.get_typed_func::<(u32,), char, _>(&mut store, "u32-to-char")?;
let char_to_u32 = instance.get_typed_func::<(char,), u32, _>(&mut store, "char-to-u32")?;
let mut roundtrip = |x: char| -> Result<()> {
assert_eq!(char_to_u32.call(&mut store, (x,))?, x as u32);
assert_eq!(u32_to_char.call(&mut store, (x as u32,))?, x);
Ok(())
};
roundtrip('x')?;
roundtrip('a')?;
roundtrip('\0')?;
roundtrip('\n')?;
roundtrip('💝')?;
let err = u32_to_char.call(&mut store, (0xd800,)).unwrap_err();
assert!(err.to_string().contains("integer out of range"), "{}", err);
let err = u32_to_char.call(&mut store, (0xdfff,)).unwrap_err();
assert!(err.to_string().contains("integer out of range"), "{}", err);
let err = u32_to_char.call(&mut store, (0x110000,)).unwrap_err();
assert!(err.to_string().contains("integer out of range"), "{}", err);
let err = u32_to_char.call(&mut store, (u32::MAX,)).unwrap_err();
assert!(err.to_string().contains("integer out of range"), "{}", err);
Ok(())
}
#[test]
fn tuple_result() -> Result<()> {
let component = r#"
(component
(module $m
(memory (export "memory") 1)
(func (export "foo") (param i32 i32 f32 f64) (result i32)
(local $base i32)
(local.set $base (i32.const 8))
(i32.store8 offset=0 (local.get $base) (local.get 0))
(i32.store16 offset=2 (local.get $base) (local.get 1))
(f32.store offset=4 (local.get $base) (local.get 2))
(f64.store offset=8 (local.get $base) (local.get 3))
local.get $base
)
(func (export "invalid") (result i32)
i32.const -1
)
(func (export "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32)
unreachable)
(func (export "canonical_abi_free") (param i32 i32 i32)
unreachable)
)
(instance $i (instantiate (module $m)))
(type $result (tuple s8 u16 float32 float64))
(func (export "tuple")
(canon.lift
(func (param s8) (param u16) (param float32) (param float64) (result $result))
(into $i)
(func $i "foo")
)
)
(func (export "invalid")
(canon.lift (func (result $result)) (into $i) (func $i "invalid"))
)
)
"#;
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)?;
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::<(), (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"),
"{}",
err
);
Ok(())
}
#[test]
fn strings() -> Result<()> {
let component = format!(
r#"(component
(module $m
(memory (export "memory") 1)
(func (export "roundtrip") (param i32 i32) (result i32)
(local $base i32)
(local.set $base
(call $realloc
(i32.const 0)
(i32.const 0)
(i32.const 4)
(i32.const 8)))
(i32.store offset=0
(local.get $base)
(local.get 0))
(i32.store offset=4
(local.get $base)
(local.get 1))
(local.get $base)
)
{REALLOC_AND_FREE}
)
(instance $i (instantiate (module $m)))
(func (export "list8-to-str")
(canon.lift
(func (param (list u8)) (result string))
(into $i)
(func $i "roundtrip")
)
)
(func (export "str-to-list8")
(canon.lift
(func (param string) (result (list u8)))
(into $i)
(func $i "roundtrip")
)
)
(func (export "list16-to-str")
(canon.lift
(func (param (list u16)) (result string))
string=utf16
(into $i)
(func $i "roundtrip")
)
)
(func (export "str-to-list16")
(canon.lift
(func (param string) (result (list u16)))
string=utf16
(into $i)
(func $i "roundtrip")
)
)
)"#
);
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)?;
let list8_to_str =
instance.get_typed_func::<(&[u8],), WasmStr, _>(&mut store, "list8-to-str")?;
let str_to_list8 =
instance.get_typed_func::<(&str,), WasmList<u8>, _>(&mut store, "str-to-list8")?;
let list16_to_str =
instance.get_typed_func::<(&[u16],), WasmStr, _>(&mut store, "list16-to-str")?;
let str_to_list16 =
instance.get_typed_func::<(&str,), WasmList<u16>, _>(&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.to_str(&store)?, x);
let utf16 = x.encode_utf16().collect::<Vec<_>>();
let ret = list16_to_str.call(&mut store, (&utf16[..],))?;
assert_eq!(ret.to_str(&store)?, x);
let ret = str_to_list8.call(&mut store, (x,))?;
assert_eq!(ret.iter(&store).collect::<Result<Vec<_>>>()?, x.as_bytes());
let ret = str_to_list16.call(&mut store, (x,))?;
assert_eq!(ret.iter(&store).collect::<Result<Vec<_>>>()?, utf16,);
Ok(())
};
roundtrip("")?;
roundtrip("foo")?;
roundtrip("hello there")?;
roundtrip("💝")?;
roundtrip("Löwe 老虎 Léopard")?;
let ret = list8_to_str.call(&mut store, (b"\xff",))?;
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.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.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.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.to_str(&store).unwrap_err();
assert!(err.to_string().contains("unpaired surrogate"), "{}", err);
Ok(())
}
#[test]
fn many_parameters() -> Result<()> {
let component = format!(
r#"(component
(module $m
(memory (export "memory") 1)
(func (export "foo") (param i32) (result i32)
(local $base i32)
;; Allocate space for the return
(local.set $base
(call $realloc
(i32.const 0)
(i32.const 0)
(i32.const 4)
(i32.const 12)))
;; Store the pointer/length of the entire linear memory
;; so we have access to everything.
(i32.store offset=0
(local.get $base)
(i32.const 0))
(i32.store offset=4
(local.get $base)
(i32.mul
(memory.size)
(i32.const 65536)))
;; And also store our pointer parameter
(i32.store offset=8
(local.get $base)
(local.get 0))
(local.get $base)
)
{REALLOC_AND_FREE}
)
(instance $i (instantiate (module $m)))
(type $result (tuple (list u8) u32))
(type $t (func
(param s8) ;; offset 0, size 1
(param u64) ;; offset 8, size 8
(param float32) ;; offset 16, size 4
(param u8) ;; offset 20, size 1
(param unit) ;; offset 21, size 0
(param s16) ;; offset 22, size 2
(param string) ;; offset 24, size 8
(param (list u32)) ;; offset 32, size 8
(param bool) ;; offset 40, size 1
(param bool) ;; offset 41, size 1
(param char) ;; offset 44, size 4
(param (list bool)) ;; offset 48, size 8
(param (list char)) ;; offset 56, size 8
(param (list string)) ;; offset 64, size 8
(result $result)
))
(func (export "many-param")
(canon.lift (type $t) (into $i) (func $i "foo"))
)
)"#
);
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)?;
let func = instance.get_typed_func::<(
i8,
u64,
f32,
u8,
(),
i16,
&str,
&[u32],
bool,
bool,
char,
&[bool],
&[char],
&[&str],
), (WasmList<u8>, u32), _>(&mut store, "many-param")?;
let input = (
-100,
u64::MAX / 2,
f32::from_bits(CANON_32BIT_NAN | 1),
38,
(),
18831,
"this is the first string",
[1, 2, 3, 4, 5, 6, 7, 8].as_slice(),
true,
false,
'🚩',
[false, true, false, true, true].as_slice(),
['🍌', '🥐', '🍗', '🍙', '🍡'].as_slice(),
[
"the quick",
"brown fox",
"was too lazy",
"to jump over the dog",
"what a demanding dog",
]
.as_slice(),
);
let (memory, pointer) = func.call(&mut store, input)?;
let memory = memory.as_slice(&store);
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);
assert_eq!(u32::from_le_bytes(*actual.take_n::<4>()), CANON_32BIT_NAN);
assert_eq!(u8::from_le_bytes(*actual.take_n::<1>()), input.3);
actual.skip::<1>();
assert_eq!(i16::from_le_bytes(*actual.take_n::<2>()), input.5);
assert_eq!(actual.ptr_len(memory, 1), input.6.as_bytes());
let mut mem = actual.ptr_len(memory, 4);
for expected in input.7.iter() {
assert_eq!(u32::from_le_bytes(*mem.take_n::<4>()), *expected);
}
assert!(mem.is_empty());
assert_eq!(actual.take_n::<1>(), &[input.8 as u8]);
assert_eq!(actual.take_n::<1>(), &[input.9 as u8]);
actual.skip::<2>();
assert_eq!(u32::from_le_bytes(*actual.take_n::<4>()), input.10 as u32);
// (list bool)
mem = actual.ptr_len(memory, 1);
for expected in input.11.iter() {
assert_eq!(mem.take_n::<1>(), &[*expected as u8]);
}
assert!(mem.is_empty());
// (list char)
mem = actual.ptr_len(memory, 4);
for expected in input.12.iter() {
assert_eq!(u32::from_le_bytes(*mem.take_n::<4>()), *expected as u32);
}
assert!(mem.is_empty());
// (list string)
mem = actual.ptr_len(memory, 8);
for expected in input.13.iter() {
let actual = mem.ptr_len(memory, 1);
assert_eq!(actual, expected.as_bytes());
}
assert!(mem.is_empty());
assert!(actual.is_empty());
Ok(())
}
#[test]
fn some_traps() -> Result<()> {
let middle_of_memory = i32::MAX / 2;
let component = format!(
r#"(component
(module $m
(memory (export "memory") 1)
(func (export "take-many") (param i32))
(func (export "take-list") (param i32 i32))
(func (export "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32)
unreachable)
(func (export "canonical_abi_free") (param i32 i32 i32)
unreachable)
)
(instance $i (instantiate (module $m)))
(func (export "take-list-unreachable")
(canon.lift (func (param (list u8))) (into $i) (func $i "take-list"))
)
(func (export "take-string-unreachable")
(canon.lift (func (param string)) (into $i) (func $i "take-list"))
)
(type $t (func
(param string)
(param string)
(param string)
(param string)
(param string)
(param string)
(param string)
(param string)
(param string)
(param string)
))
(func (export "take-many-unreachable")
(canon.lift (type $t) (into $i) (func $i "take-many"))
)
(module $m2
(memory (export "memory") 1)
(func (export "take-many") (param i32))
(func (export "take-list") (param i32 i32))
(func (export "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32)
i32.const {middle_of_memory})
(func (export "canonical_abi_free") (param i32 i32 i32)
unreachable)
)
(instance $i2 (instantiate (module $m2)))
(func (export "take-list-base-oob")
(canon.lift (func (param (list u8))) (into $i2) (func $i2 "take-list"))
)
(func (export "take-string-base-oob")
(canon.lift (func (param string)) (into $i2) (func $i2 "take-list"))
)
(func (export "take-many-base-oob")
(canon.lift (type $t) (into $i2) (func $i2 "take-many"))
)
(module $m3
(memory (export "memory") 1)
(func (export "take-many") (param i32))
(func (export "take-list") (param i32 i32))
(func (export "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32)
i32.const 65535)
(func (export "canonical_abi_free") (param i32 i32 i32)
unreachable)
)
(instance $i3 (instantiate (module $m3)))
(func (export "take-list-end-oob")
(canon.lift (func (param (list u8))) (into $i3) (func $i3 "take-list"))
)
(func (export "take-string-end-oob")
(canon.lift (func (param string)) (into $i3) (func $i3 "take-list"))
)
(func (export "take-many-end-oob")
(canon.lift (type $t) (into $i3) (func $i3 "take-many"))
)
(module $m4
(memory (export "memory") 1)
(func (export "take-many") (param i32))
(global $cnt (mut i32) (i32.const 0))
(func (export "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32)
global.get $cnt
if (result i32)
i32.const 100000
else
i32.const 1
global.set $cnt
i32.const 0
end
)
(func (export "canonical_abi_free") (param i32 i32 i32)
unreachable)
)
(instance $i4 (instantiate (module $m4)))
(func (export "take-many-second-oob")
(canon.lift (type $t) (into $i4) (func $i4 "take-many"))
)
)"#
);
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)?;
// This should fail when calling the allocator function for the argument
let err = instance
.get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-unreachable")?
.call(&mut store, (&[],))
.unwrap_err()
.downcast::<Trap>()?;
assert_eq!(err.trap_code(), Some(TrapCode::UnreachableCodeReached));
// This should fail when calling the allocator function for the argument
let err = instance
.get_typed_func::<(&str,), (), _>(&mut store, "take-string-unreachable")?
.call(&mut store, ("",))
.unwrap_err()
.downcast::<Trap>()?;
assert_eq!(err.trap_code(), Some(TrapCode::UnreachableCodeReached));
// This should fail when calling the allocator function for the space
// to store the arguments (before arguments are even lowered)
let err = instance
.get_typed_func::<(&str, &str, &str, &str, &str, &str, &str, &str, &str, &str), (), _>(
&mut store,
"take-many-unreachable",
)?
.call(&mut store, ("", "", "", "", "", "", "", "", "", ""))
.unwrap_err()
.downcast::<Trap>()?;
assert_eq!(err.trap_code(), Some(TrapCode::UnreachableCodeReached));
// Assert that when the base pointer returned by malloc is out of bounds
// that errors are reported as such. Both empty and lists with contents
// should all be invalid here.
//
// FIXME(WebAssembly/component-model#32) confirm the semantics here are
// what's desired.
#[track_caller]
fn assert_oob(err: &anyhow::Error) {
assert!(
err.to_string()
.contains("realloc return: beyond end of memory"),
"{:?}",
err,
);
}
let err = instance
.get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-base-oob")?
.call(&mut store, (&[],))
.unwrap_err();
assert_oob(&err);
let err = instance
.get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-base-oob")?
.call(&mut store, (&[1],))
.unwrap_err();
assert_oob(&err);
let err = instance
.get_typed_func::<(&str,), (), _>(&mut store, "take-string-base-oob")?
.call(&mut store, ("",))
.unwrap_err();
assert_oob(&err);
let err = instance
.get_typed_func::<(&str,), (), _>(&mut store, "take-string-base-oob")?
.call(&mut store, ("x",))
.unwrap_err();
assert_oob(&err);
let err = instance
.get_typed_func::<(&str, &str, &str, &str, &str, &str, &str, &str, &str, &str), (), _>(
&mut store,
"take-many-base-oob",
)?
.call(&mut store, ("", "", "", "", "", "", "", "", "", ""))
.unwrap_err();
assert_oob(&err);
// Test here that when the returned pointer from malloc is one byte from the
// end of memory that empty things are fine, but larger things are not.
instance
.get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-end-oob")?
.call(&mut store, (&[],))?;
instance
.get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-end-oob")?
.call(&mut store, (&[1],))?;
assert_oob(&err);
let err = instance
.get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-end-oob")?
.call(&mut store, (&[1, 2],))
.unwrap_err();
assert_oob(&err);
instance
.get_typed_func::<(&str,), (), _>(&mut store, "take-string-end-oob")?
.call(&mut store, ("",))?;
instance
.get_typed_func::<(&str,), (), _>(&mut store, "take-string-end-oob")?
.call(&mut store, ("x",))?;
let err = instance
.get_typed_func::<(&str,), (), _>(&mut store, "take-string-end-oob")?
.call(&mut store, ("xy",))
.unwrap_err();
assert_oob(&err);
let err = instance
.get_typed_func::<(&str, &str, &str, &str, &str, &str, &str, &str, &str, &str), (), _>(
&mut store,
"take-many-end-oob",
)?
.call(&mut store, ("", "", "", "", "", "", "", "", "", ""))
.unwrap_err();
assert_oob(&err);
// For this function the first allocation, the space to store all the
// arguments, is in-bounds but then all further allocations, such as for
// each individual string, are all out of bounds.
let err = instance
.get_typed_func::<(&str, &str, &str, &str, &str, &str, &str, &str, &str, &str), (), _>(
&mut store,
"take-many-second-oob",
)?
.call(&mut store, ("", "", "", "", "", "", "", "", "", ""))
.unwrap_err();
assert_oob(&err);
Ok(())
}
#[test]
fn char_bool_memory() -> Result<()> {
let component = format!(
r#"(component
(module $m
(memory (export "memory") 1)
(func (export "ret-tuple") (param i32 i32) (result i32)
(local $base i32)
;; Allocate space for the return
(local.set $base
(call $realloc
(i32.const 0)
(i32.const 0)
(i32.const 4)
(i32.const 8)))
;; store the boolean
(i32.store offset=0
(local.get $base)
(local.get 0))
;; store the char
(i32.store offset=4
(local.get $base)
(local.get 1))
(local.get $base)
)
{REALLOC_AND_FREE}
)
(instance $i (instantiate (module $m)))
(func (export "ret-tuple")
(canon.lift (func (param u32) (param u32) (result (tuple bool char))) (into $i) (func $i "ret-tuple"))
)
)"#
);
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)?;
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, (false, 'a'));
let ret = func.call(&mut store, (1, '🍰' as u32))?;
assert_eq!(ret, (true, '🍰'));
let ret = func.call(&mut store, (2, 'a' as u32))?;
assert_eq!(ret, (true, 'a'));
assert!(func.call(&mut store, (0, 0xd800)).is_err());
Ok(())
}
#[test]
fn string_list_oob() -> Result<()> {
let component = format!(
r#"(component
(module $m
(memory (export "memory") 1)
(func (export "ret-list") (result i32)
(local $base i32)
;; Allocate space for the return
(local.set $base
(call $realloc
(i32.const 0)
(i32.const 0)
(i32.const 4)
(i32.const 8)))
(i32.store offset=0
(local.get $base)
(i32.const 100000))
(i32.store offset=4
(local.get $base)
(i32.const 1))
(local.get $base)
)
{REALLOC_AND_FREE}
)
(instance $i (instantiate (module $m)))
(func (export "ret-list-u8")
(canon.lift (func (result (list u8))) (into $i) (func $i "ret-list"))
)
(func (export "ret-string")
(canon.lift (func (result string)) (into $i) (func $i "ret-list"))
)
)"#
);
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)?;
let ret_list_u8 = instance.get_typed_func::<(), WasmList<u8>, _>(&mut store, "ret-list-u8")?;
let ret_string = instance.get_typed_func::<(), WasmStr, _>(&mut store, "ret-string")?;
let err = ret_list_u8.call(&mut store, ()).err().unwrap();
assert!(err.to_string().contains("out of bounds"), "{}", err);
let err = ret_string.call(&mut store, ()).err().unwrap();
assert!(err.to_string().contains("out of bounds"), "{}", err);
Ok(())
}
#[test]
fn tuples() -> Result<()> {
let component = format!(
r#"(component
(module $m
(memory (export "memory") 1)
(func (export "foo")
(param i32 f64 i32)
(result i32)
local.get 0
i32.const 0
i32.ne
if unreachable end
local.get 1
f64.const 1
f64.ne
if unreachable end
local.get 2
i32.const 2
i32.ne
if unreachable end
i32.const 3
)
(func (export "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32)
unreachable)
(func (export "canonical_abi_free") (param i32 i32 i32)
unreachable)
)
(instance $i (instantiate (module $m)))
(func (export "foo")
(canon.lift
(func
(param (tuple s32 float64))
(param (tuple s8))
(result (tuple u16))
)
(func $i "foo")
)
)
)"#
);
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)?;
let foo = instance.get_typed_func::<((i32, f64), (i8,)), (u16,), _>(&mut store, "foo")?;
assert_eq!(foo.call(&mut store, ((0, 1.0), (2,)))?, (3,));
Ok(())
}
#[test]
fn option() -> Result<()> {
let component = format!(
r#"(component
(module $m
(memory (export "memory") 1)
(func (export "pass0") (param i32) (result i32)
local.get 0
)
(func (export "pass1") (param i32 i32) (result i32)
(local $base i32)
(local.set $base
(call $realloc
(i32.const 0)
(i32.const 0)
(i32.const 4)
(i32.const 8)))
(i32.store offset=0
(local.get $base)
(local.get 0))
(i32.store offset=4
(local.get $base)
(local.get 1))
(local.get $base)
)
(func (export "pass2") (param i32 i32 i32) (result i32)
(local $base i32)
(local.set $base
(call $realloc
(i32.const 0)
(i32.const 0)
(i32.const 4)
(i32.const 12)))
(i32.store offset=0
(local.get $base)
(local.get 0))
(i32.store offset=4
(local.get $base)
(local.get 1))
(i32.store offset=8
(local.get $base)
(local.get 2))
(local.get $base)
)
{REALLOC_AND_FREE}
)
(instance $i (instantiate (module $m)))
(func (export "option-unit-to-u32")
(canon.lift
(func (param (option unit)) (result u32))
(func $i "pass0")
)
)
(func (export "option-u8-to-tuple")
(canon.lift
(func (param (option u8)) (result (tuple u32 u32)))
(into $i)
(func $i "pass1")
)
)
(func (export "option-u32-to-tuple")
(canon.lift
(func (param (option u32)) (result (tuple u32 u32)))
(into $i)
(func $i "pass1")
)
)
(func (export "option-string-to-tuple")
(canon.lift
(func (param (option string)) (result (tuple u32 string)))
(into $i)
(func $i "pass2")
)
)
(func (export "to-option-unit")
(canon.lift
(func (param u32) (result (option unit)))
(func $i "pass0")
)
)
(func (export "to-option-u8")
(canon.lift
(func (param u32) (param u32) (result (option u8)))
(into $i)
(func $i "pass1")
)
)
(func (export "to-option-u32")
(canon.lift
(func (param u32) (param u32) (result (option u32)))
(into $i)
(func $i "pass1")
)
)
(func (export "to-option-string")
(canon.lift
(func (param u32) (param string) (result (option string)))
(into $i)
(func $i "pass2")
)
)
)"#
);
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)?;
let option_unit_to_u32 =
instance.get_typed_func::<(Option<()>,), u32, _>(&mut store, "option-unit-to-u32")?;
assert_eq!(option_unit_to_u32.call(&mut store, (None,))?, 0);
assert_eq!(option_unit_to_u32.call(&mut store, (Some(()),))?, 1);
let option_u8_to_tuple = instance
.get_typed_func::<(Option<u8>,), (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, 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-string-to-tuple",
)?;
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")?;
assert_eq!(to_option_unit.call(&mut store, (0,))?, None);
assert_eq!(to_option_unit.call(&mut store, (1,))?, Some(()));
let err = to_option_unit.call(&mut store, (2,)).unwrap_err();
assert!(err.to_string().contains("invalid option"), "{}", err);
let to_option_u8 =
instance.get_typed_func::<(u32, u32), Option<u8>, _>(&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<u32>, _>(&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), Option<WasmStr>, _>(&mut store, "to-option-string")?;
let ret = to_option_string.call(&mut store, (0, ""))?;
assert!(ret.is_none());
let ret = to_option_string.call(&mut store, (1, ""))?;
assert_eq!(ret.unwrap().to_str(&store)?, "");
let ret = to_option_string.call(&mut store, (1, "cheesecake"))?;
assert_eq!(ret.unwrap().to_str(&store)?, "cheesecake");
assert!(to_option_string.call(&mut store, (2, "")).is_err());
Ok(())
}
#[test]
fn expected() -> Result<()> {
let component = format!(
r#"(component
(module $m
(memory (export "memory") 1)
(func (export "pass0") (param i32) (result i32)
local.get 0
)
(func (export "pass1") (param i32 i32) (result i32)
(local $base i32)
(local.set $base
(call $realloc
(i32.const 0)
(i32.const 0)
(i32.const 4)
(i32.const 8)))
(i32.store offset=0
(local.get $base)
(local.get 0))
(i32.store offset=4
(local.get $base)
(local.get 1))
(local.get $base)
)
(func (export "pass2") (param i32 i32 i32) (result i32)
(local $base i32)
(local.set $base
(call $realloc
(i32.const 0)
(i32.const 0)
(i32.const 4)
(i32.const 12)))
(i32.store offset=0
(local.get $base)
(local.get 0))
(i32.store offset=4
(local.get $base)
(local.get 1))
(i32.store offset=8
(local.get $base)
(local.get 2))
(local.get $base)
)
{REALLOC_AND_FREE}
)
(instance $i (instantiate (module $m)))
(func (export "take-expected-unit")
(canon.lift
(func (param (expected unit unit)) (result u32))
(func $i "pass0")
)
)
(func (export "take-expected-u8-f32")
(canon.lift
(func (param (expected u8 float32)) (result (tuple u32 u32)))
(into $i)
(func $i "pass1")
)
)
(type $list (list u8))
(func (export "take-expected-string")
(canon.lift
(func (param (expected string $list)) (result (tuple u32 string)))
(into $i)
(func $i "pass2")
)
)
(func (export "to-expected-unit")
(canon.lift
(func (param u32) (result (expected unit unit)))
(func $i "pass0")
)
)
(func (export "to-expected-s16-f32")
(canon.lift
(func (param u32) (param u32) (result (expected s16 float32)))
(into $i)
(func $i "pass1")
)
)
)"#
);
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)?;
let take_expected_unit =
instance.get_typed_func::<(Result<(), ()>,), u32, _>(&mut store, "take-expected-unit")?;
assert_eq!(take_expected_unit.call(&mut store, (Ok(()),))?, 0);
assert_eq!(take_expected_unit.call(&mut store, (Err(()),))?, 1);
let take_expected_u8_f32 = instance
.get_typed_func::<(Result<u8, f32>,), (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]>,), (u32, WasmStr), _>(
&mut store,
"take-expected-string",
)?;
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")?;
assert_eq!(to_expected_unit.call(&mut store, (0,))?, Ok(()));
assert_eq!(to_expected_unit.call(&mut store, (1,))?, Err(()));
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), Result<i16, f32>, _>(&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!(
to_expected_s16_f32.call(&mut store, (1, 1.0f32.to_bits()))?,
Err(1.0)
);
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(())
}
#[test]
fn fancy_list() -> Result<()> {
let component = format!(
r#"(component
(module $m
(memory (export "memory") 1)
(func (export "take") (param i32 i32) (result i32)
(local $base i32)
(local.set $base
(call $realloc
(i32.const 0)
(i32.const 0)
(i32.const 4)
(i32.const 16)))
(i32.store offset=0
(local.get $base)
(local.get 0))
(i32.store offset=4
(local.get $base)
(local.get 1))
(i32.store offset=8
(local.get $base)
(i32.const 0))
(i32.store offset=12
(local.get $base)
(i32.mul
(memory.size)
(i32.const 65536)))
(local.get $base)
)
{REALLOC_AND_FREE}
)
(instance $i (instantiate (module $m)))
(type $a (option u8))
(type $b (expected unit string))
(type $input (list (tuple $a $b)))
(type $output (tuple u32 u32 (list u8)))
(func (export "take")
(canon.lift
(func (param $input) (result $output))
(into $i)
(func $i "take")
)
)
)"#
);
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)?;
let func = instance
.get_typed_func::<(&[(Option<u8>, Result<(), &str>)],), (u32, u32, WasmList<u8>), _>(
&mut store, "take",
)?;
let input = [
(None, Ok(())),
(Some(2), Err("hello there")),
(Some(200), Err("general kenobi")),
];
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() {
match a {
Some(val) => {
assert_eq!(*array.take_n::<2>(), [1, *val]);
}
None => {
assert_eq!(*array.take_n::<1>(), [0]);
array.skip::<1>();
}
}
array.skip::<2>();
match b {
Ok(()) => {
assert_eq!(*array.take_n::<1>(), [0]);
array.skip::<11>();
}
Err(s) => {
assert_eq!(*array.take_n::<1>(), [1]);
array.skip::<3>();
assert_eq!(array.ptr_len(memory, 1), s.as_bytes());
}
}
}
assert!(array.is_empty());
Ok(())
}
trait SliceExt<'a> {
fn take_n<const N: usize>(&mut self) -> &'a [u8; N];
fn skip<const N: usize>(&mut self) {
self.take_n::<N>();
}
fn ptr_len<'b>(&mut self, all_memory: &'b [u8], size: usize) -> &'b [u8] {
let ptr = u32::from_le_bytes(*self.take_n::<4>());
let len = u32::from_le_bytes(*self.take_n::<4>());
let ptr = usize::try_from(ptr).unwrap();
let len = usize::try_from(len).unwrap();
&all_memory[ptr..][..len * size]
}
}
impl<'a> SliceExt<'a> for &'a [u8] {
fn take_n<const N: usize>(&mut self) -> &'a [u8; N] {
let (a, b) = self.split_at(N);
*self = b;
a.try_into().unwrap()
}
}