#![cfg(not(miri))]
use super::{ApiStyle, REALLOC_AND_FREE};
use std::sync::{
Arc,
atomic::{AtomicBool, Ordering::SeqCst},
};
use wasmtime::Result;
use wasmtime::component::*;
use wasmtime::{Config, Engine, Store, StoreContextMut, Trap};
const CANON_32BIT_NAN: u32 = 0b01111111110000000000000000000000;
const CANON_64BIT_NAN: u64 = 0b0111111111111000000000000000000000000000000000000000000000000000;
#[test]
fn thunks() -> Result<()> {
let component = r#"
(component
(core module $m
(func (export "thunk"))
(func (export "thunk-trap") unreachable)
)
(core instance $i (instantiate $m))
(func (export "thunk")
(canon lift (core func $i "thunk"))
)
(func (export "thunk-trap")
(canon lift (core 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_eq!(err.downcast::<Trap>()?, Trap::UnreachableCodeReached);
Ok(())
}
#[test]
fn typecheck() -> Result<()> {
let component = r#"
(component
(core 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 "realloc") (param i32 i32 i32 i32) (result i32)
unreachable)
)
(core instance $i (instantiate (module $m)))
(func (export "thunk")
(canon lift (core func $i "thunk"))
)
(func (export "take-string") (param "a" string)
(canon lift (core func $i "take-string") (memory $i "memory") (realloc (func $i "realloc")))
)
(func (export "take-two-args") (param "a" s32) (param "b" (list u8))
(canon lift (core func $i "two-args") (memory $i "memory") (realloc (func $i "realloc")))
)
(func (export "ret-tuple") (result (tuple u8 s8))
(canon lift (core func $i "ret-one") (memory $i "memory") (realloc (func $i "realloc")))
)
(func (export "ret-tuple1") (result (tuple u32))
(canon lift (core func $i "ret-one") (memory $i "memory") (realloc (func $i "realloc")))
)
(func (export "ret-string") (result string)
(canon lift (core func $i "ret-one") (memory $i "memory") (realloc (func $i "realloc")))
)
(func (export "ret-list-u8") (result (list u8))
(canon lift (core func $i "ret-one") (memory $i "memory") (realloc (func $i "realloc")))
)
)
"#;
let engine = Engine::default();
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
(core 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)
)
(core instance $i (instantiate (module $m)))
(func (export "take-u8") (param "a" u8) (canon lift (core func $i "take-i32-100")))
(func (export "take-s8") (param "a" s8) (canon lift (core func $i "take-i32-100")))
(func (export "take-u16") (param "a" u16) (canon lift (core func $i "take-i32-100")))
(func (export "take-s16") (param "a" s16) (canon lift (core func $i "take-i32-100")))
(func (export "take-u32") (param "a" u32) (canon lift (core func $i "take-i32-100")))
(func (export "take-s32") (param "a" s32) (canon lift (core func $i "take-i32-100")))
(func (export "take-u64") (param "a" u64) (canon lift (core func $i "take-i64-100")))
(func (export "take-s64") (param "a" s64) (canon lift (core func $i "take-i64-100")))
(func (export "ret-u8") (result u8) (canon lift (core func $i "ret-i32-0")))
(func (export "ret-s8") (result s8) (canon lift (core func $i "ret-i32-0")))
(func (export "ret-u16") (result u16) (canon lift (core func $i "ret-i32-0")))
(func (export "ret-s16") (result s16) (canon lift (core func $i "ret-i32-0")))
(func (export "ret-u32") (result u32) (canon lift (core func $i "ret-i32-0")))
(func (export "ret-s32") (result s32) (canon lift (core func $i "ret-i32-0")))
(func (export "ret-u64") (result u64) (canon lift (core func $i "ret-i64-0")))
(func (export "ret-s64") (result s64) (canon lift (core func $i "ret-i64-0")))
(func (export "retm1-u8") (result u8) (canon lift (core func $i "ret-i32-minus-1")))
(func (export "retm1-s8") (result s8) (canon lift (core func $i "ret-i32-minus-1")))
(func (export "retm1-u16") (result u16) (canon lift (core func $i "ret-i32-minus-1")))
(func (export "retm1-s16") (result s16) (canon lift (core func $i "ret-i32-minus-1")))
(func (export "retm1-u32") (result u32) (canon lift (core func $i "ret-i32-minus-1")))
(func (export "retm1-s32") (result s32) (canon lift (core func $i "ret-i32-minus-1")))
(func (export "retm1-u64") (result u64) (canon lift (core func $i "ret-i64-minus-1")))
(func (export "retm1-s64") (result s64) (canon lift (core func $i "ret-i64-minus-1")))
(func (export "retbig-u8") (result u8) (canon lift (core func $i "ret-i32-100000")))
(func (export "retbig-s8") (result s8) (canon lift (core func $i "ret-i32-100000")))
(func (export "retbig-u16") (result u16) (canon lift (core func $i "ret-i32-100000")))
(func (export "retbig-s16") (result s16) (canon lift (core func $i "ret-i32-100000")))
(func (export "retbig-u32") (result u32) (canon lift (core func $i "ret-i32-100000")))
(func (export "retbig-s32") (result s32) (canon lift (core 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)?;
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,))?;
with_new_instance(&engine, &component, |store, instance| {
instance
.get_typed_func::<(u8,), ()>(&mut *store, "take-u8")?
.call(store, (101,))
.unwrap_err()
.downcast::<Trap>()
})?;
with_new_instance(&engine, &component, |store, instance| {
instance
.get_typed_func::<(i8,), ()>(&mut *store, "take-s8")?
.call(store, (101,))
.unwrap_err()
.downcast::<Trap>()
})?;
with_new_instance(&engine, &component, |store, instance| {
instance
.get_typed_func::<(u16,), ()>(&mut *store, "take-u16")?
.call(store, (101,))
.unwrap_err()
.downcast::<Trap>()
})?;
with_new_instance(&engine, &component, |store, instance| {
instance
.get_typed_func::<(i16,), ()>(&mut *store, "take-s16")?
.call(store, (101,))
.unwrap_err()
.downcast::<Trap>()
})?;
with_new_instance(&engine, &component, |store, instance| {
instance
.get_typed_func::<(u32,), ()>(&mut *store, "take-u32")?
.call(store, (101,))
.unwrap_err()
.downcast::<Trap>()
})?;
with_new_instance(&engine, &component, |store, instance| {
instance
.get_typed_func::<(i32,), ()>(&mut *store, "take-s32")?
.call(store, (101,))
.unwrap_err()
.downcast::<Trap>()
})?;
with_new_instance(&engine, &component, |store, instance| {
instance
.get_typed_func::<(u64,), ()>(&mut *store, "take-u64")?
.call(store, (101,))
.unwrap_err()
.downcast::<Trap>()
})?;
with_new_instance(&engine, &component, |store, instance| {
instance
.get_typed_func::<(i64,), ()>(&mut *store, "take-s64")?
.call(store, (101,))
.unwrap_err()
.downcast::<Trap>()
})?;
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,)
);
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,)
);
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
(core module $m
(func (export "take-i32-100") (param i32)
local.get 0
i32.const 2
i32.eq
br_if 0
unreachable
)
)
(core instance $i (instantiate $m))
(func (export "take-u32") (param "a" u32) (canon lift (core 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::<(Arc<u32>,), ()>(&mut store, "take-u32")?
.call(&mut store, (Arc::new(2),))?;
instance
.get_typed_func::<(&Box<Arc<Box<u32>>>,), ()>(&mut store, "take-u32")?
.call(&mut store, (&Box::new(Arc::new(Box::new(2))),))?;
Ok(())
}
#[test]
fn floats() -> Result<()> {
let component = r#"
(component
(core 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
)
)
(core instance $i (instantiate $m))
(func (export "f32-to-u32") (param "a" float32) (result u32)
(canon lift (core func $i "i32.reinterpret_f32"))
)
(func (export "f64-to-u64") (param "a" float64) (result u64)
(canon lift (core func $i "i64.reinterpret_f64"))
)
(func (export "u32-to-f32") (param "a" u32) (result float32)
(canon lift (core func $i "f32.reinterpret_i32"))
)
(func (export "u64-to-f64") (param "a" u64) (result float64)
(canon lift (core 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,))?
.0
.to_bits(),
CANON_32BIT_NAN | 1
);
assert_eq!(
u64_to_f64
.call(&mut store, (CANON_64BIT_NAN | 1,))?
.0
.to_bits(),
CANON_64BIT_NAN | 1,
);
assert_eq!(
f32_to_u32.call(&mut store, (f32::from_bits(CANON_32BIT_NAN | 1),))?,
(CANON_32BIT_NAN | 1,)
);
assert_eq!(
f64_to_u64.call(&mut store, (f64::from_bits(CANON_64BIT_NAN | 1),))?,
(CANON_64BIT_NAN | 1,)
);
Ok(())
}
#[test]
fn bools() -> Result<()> {
let component = r#"
(component
(core module $m
(func (export "pass") (param i32) (result i32) local.get 0)
)
(core instance $i (instantiate $m))
(func (export "u32-to-bool") (param "a" u32) (result bool)
(canon lift (core func $i "pass"))
)
(func (export "bool-to-u32") (param "a" bool) (result u32)
(canon lift (core 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
(core module $m
(func (export "pass") (param i32) (result i32) local.get 0)
)
(core instance $i (instantiate $m))
(func (export "u32-to-char") (param "a" u32) (result char)
(canon lift (core func $i "pass"))
)
(func (export "char-to-u32") (param "a" char) (result u32)
(canon lift (core 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 u32_to_char = |store: &mut Store<()>| {
Linker::new(&engine)
.instantiate(&mut *store, &component)?
.get_typed_func::<(u32,), (char,)>(&mut *store, "u32-to-char")
};
let err = u32_to_char(&mut store)?
.call(&mut store, (0xd800,))
.unwrap_err();
assert!(err.to_string().contains("integer out of range"), "{}", err);
let err = u32_to_char(&mut store)?
.call(&mut store, (0xdfff,))
.unwrap_err();
assert!(err.to_string().contains("integer out of range"), "{}", err);
let err = u32_to_char(&mut store)?
.call(&mut store, (0x110000,))
.unwrap_err();
assert!(err.to_string().contains("integer out of range"), "{}", err);
let err = u32_to_char(&mut store)?
.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
(core 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 -8
)
)
(core instance $i (instantiate $m))
(type $result (tuple s8 u16 float32 float64))
(func (export "tuple")
(param "a" s8) (param "b" u16) (param "c" float32) (param "d" float64) (result $result)
(canon lift (core func $i "foo") (memory $i "memory"))
)
(func (export "invalid") (result $result)
(canon lift (core func $i "invalid") (memory $i "memory"))
)
)
"#;
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
(core 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}
)
(core instance $i (instantiate $m))
(func (export "list8-to-str") (param "a" (list u8)) (result string)
(canon lift
(core func $i "roundtrip")
(memory $i "memory")
(realloc (func $i "realloc"))
)
)
(func (export "str-to-list8") (param "a" string) (result (list u8))
(canon lift
(core func $i "roundtrip")
(memory $i "memory")
(realloc (func $i "realloc"))
)
)
(func (export "list16-to-str") (param "a" (list u16)) (result string)
(canon lift
(core func $i "roundtrip")
string-encoding=utf16
(memory $i "memory")
(realloc (func $i "realloc"))
)
)
(func (export "str-to-list16") (param "a" string) (result (list u16))
(canon lift
(core func $i "roundtrip")
string-encoding=utf16
(memory $i "memory")
(realloc (func $i "realloc"))
)
)
)"#
);
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(),))?.0;
assert_eq!(ret.to_str(&store)?, x);
let utf16 = x.encode_utf16().collect::<Vec<_>>();
let ret = list16_to_str.call(&mut store, (&utf16[..],))?.0;
assert_eq!(ret.to_str(&store)?, x);
let ret = str_to_list8.call(&mut store, (x,))?.0;
assert_eq!(
ret.iter(&mut store).collect::<Result<Vec<_>>>()?,
x.as_bytes()
);
let ret = str_to_list16.call(&mut store, (x,))?.0;
assert_eq!(ret.iter(&mut 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",))?.0;
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",))?
.0;
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],))?.0;
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],))?.0;
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],))?.0;
let err = ret.to_str(&store).unwrap_err();
assert!(err.to_string().contains("unpaired surrogate"), "{}", err);
Ok(())
}
#[tokio::test]
async fn async_reentrance() -> Result<()> {
_ = env_logger::try_init();
let component = r#"
(component
(core module $shim
(import "" "task.return" (func $task-return (param i32)))
(table (export "funcs") 1 1 funcref)
(func (export "export") (param i32) (result i32)
(call_indirect (i32.const 0) (local.get 0))
)
(func (export "callback") (param i32 i32 i32) (result i32) unreachable)
)
(core func $task-return (canon task.return (result u32)))
(core instance $shim (instantiate $shim
(with "" (instance (export "task.return" (func $task-return))))
))
(func $shim-export (param "p1" u32) (result u32)
(canon lift (core func $shim "export") async (callback (func $shim "callback")))
)
(component $inner
(import "import" (func $import (param "p1" u32) (result u32)))
(core module $libc (memory (export "memory") 1))
(core instance $libc (instantiate $libc))
(core func $import (canon lower (func $import) async (memory $libc "memory")))
(core module $m
(import "libc" "memory" (memory 1))
(import "" "import" (func $import (param i32 i32) (result i32)))
(import "" "task.return" (func $task-return (param i32)))
(func (export "export") (param i32) (result i32)
(i32.store offset=0 (i32.const 1200) (local.get 0))
(call $import (i32.const 1200) (i32.const 1204))
drop
(call $task-return (i32.load offset=0 (i32.const 1204)))
i32.const 0
)
(func (export "callback") (param i32 i32 i32) (result i32) unreachable)
)
(core type $task-return-type (func (param i32)))
(core func $task-return (canon task.return (result u32)))
(core instance $i (instantiate $m
(with "" (instance
(export "task.return" (func $task-return))
(export "import" (func $import))
))
(with "libc" (instance $libc))
))
(func (export "export") (param "p1" u32) (result u32)
(canon lift (core func $i "export") async (callback (func $i "callback")))
)
)
(instance $inner (instantiate $inner (with "import" (func $shim-export))))
(core module $libc (memory (export "memory") 1))
(core instance $libc (instantiate $libc))
(core func $inner-export (canon lower (func $inner "export") async (memory $libc "memory")))
(core module $donut
(import "" "funcs" (table 1 1 funcref))
(import "libc" "memory" (memory 1))
(import "" "import" (func $import (param i32 i32) (result i32)))
(import "" "task.return" (func $task-return (param i32)))
(func $host-export (export "export") (param i32) (result i32)
(i32.store offset=0 (i32.const 1200) (local.get 0))
(call $import (i32.const 1200) (i32.const 1204))
drop
(call $task-return (i32.load offset=0 (i32.const 1204)))
i32.const 0
)
(func $guest-export (export "guest-export") (param i32) (result i32) unreachable)
(func (export "callback") (param i32 i32 i32) (result i32) unreachable)
(func $start
(table.set (i32.const 0) (ref.func $guest-export))
)
(start $start)
)
(core instance $donut (instantiate $donut
(with "" (instance
(export "task.return" (func $task-return))
(export "import" (func $inner-export))
(export "funcs" (table $shim "funcs"))
))
(with "libc" (instance $libc))
))
(func (export "export") (param "p1" u32) (result u32)
(canon lift (core func $donut "export") async (callback (func $donut "callback")))
)
)"#;
let mut config = Config::new();
config.wasm_component_model_async(true);
config.wasm_component_model_async_stackful(true);
config.wasm_component_model_threading(true);
let engine = &Engine::new(&config)?;
let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ());
let instance = Linker::new(&engine)
.instantiate_async(&mut store, &component)
.await?;
let func = instance.get_typed_func::<(u32,), (u32,)>(&mut store, "export")?;
let message = "cannot enter component instance";
match store
.run_concurrent(async move |accessor| {
wasmtime::error::Ok(func.call_concurrent(accessor, (42,)).await?.0)
})
.await
{
Ok(_) => panic!(),
Err(e) => assert!(
format!("{e:?}").contains(message),
"expected `{message}`; got `{e:?}`"
),
}
Ok(())
}
#[tokio::test]
async fn missing_task_return_call_stackless() -> Result<()> {
task_return_trap(
r#"(component
(core module $m
(import "" "task.return" (func $task-return))
(func (export "foo") (result i32)
i32.const 0
)
(func (export "callback") (param i32 i32 i32) (result i32) unreachable)
)
(core func $task-return (canon task.return))
(core instance $i (instantiate $m
(with "" (instance (export "task.return" (func $task-return))))
))
(func (export "foo") (canon lift (core func $i "foo") async (callback (func $i "callback"))))
)"#,
"wasm trap: async-lifted export failed to produce a result",
)
.await
}
#[tokio::test]
async fn missing_task_return_call_stackless_explicit_thread() -> Result<()> {
task_return_trap(
r#"(component
(core module $libc
(table (export "__indirect_function_table") 1 funcref))
(core module $m
(import "" "task.return" (func $task-return))
(import "" "thread.new-indirect" (func $thread-new-indirect (param i32 i32) (result i32)))
(import "" "thread.unsuspend" (func $thread-unsuspend (param i32)))
(import "libc" "__indirect_function_table" (table $indirect-function-table 1 funcref))
(func $thread-start (param i32) (; empty ;))
(elem (table $indirect-function-table) (i32.const 0) func $thread-start)
(func (export "foo") (result i32)
(call $thread-unsuspend
(call $thread-new-indirect (i32.const 0) (i32.const 0)))
i32.const 0
)
(func (export "callback") (param i32 i32 i32) (result i32) unreachable)
)
(core instance $libc (instantiate $libc))
(core type $start-func-ty (func (param i32)))
(alias core export $libc "__indirect_function_table" (core table $indirect-function-table))
(core func $thread-new-indirect
(canon thread.new-indirect $start-func-ty (table $indirect-function-table)))
(core func $thread-unsuspend (canon thread.unsuspend))
(core func $task-return (canon task.return))
(core instance $i (instantiate $m
(with "" (instance
(export "thread.new-indirect" (func $thread-new-indirect))
(export "thread.unsuspend" (func $thread-unsuspend))
(export "task.return" (func $task-return))
))
(with "libc" (instance $libc))
))
(func (export "foo") (canon lift (core func $i "foo") async (callback (func $i "callback"))))
)"#,
"wasm trap: async-lifted export failed to produce a result",
)
.await
}
#[tokio::test]
async fn missing_task_return_call_stackful_explicit_thread() -> Result<()> {
task_return_trap(
r#"(component
(core module $libc
(table (export "__indirect_function_table") 1 funcref))
(core module $m
(import "" "task.return" (func $task-return))
(import "" "thread.new-indirect" (func $thread-new-indirect (param i32 i32) (result i32)))
(import "" "thread.unsuspend" (func $thread-unsuspend (param i32)))
(import "libc" "__indirect_function_table" (table $indirect-function-table 1 funcref))
(func $thread-start (param i32) (; empty ;))
(elem (table $indirect-function-table) (i32.const 0) func $thread-start)
(func (export "foo")
(call $thread-unsuspend
(call $thread-new-indirect (i32.const 0) (i32.const 0)))
)
)
(core instance $libc (instantiate $libc))
(core type $start-func-ty (func (param i32)))
(alias core export $libc "__indirect_function_table" (core table $indirect-function-table))
(core func $thread-new-indirect
(canon thread.new-indirect $start-func-ty (table $indirect-function-table)))
(core func $thread-unsuspend (canon thread.unsuspend))
(core func $task-return (canon task.return))
(core instance $i (instantiate $m
(with "" (instance
(export "thread.new-indirect" (func $thread-new-indirect))
(export "thread.unsuspend" (func $thread-unsuspend))
(export "task.return" (func $task-return))
))
(with "libc" (instance $libc))
))
(func (export "foo") (canon lift (core func $i "foo") async))
)"#,
"wasm trap: async-lifted export failed to produce a result",
)
.await
}
#[tokio::test]
async fn missing_task_return_call_stackful() -> Result<()> {
task_return_trap(
r#"(component
(core module $m
(import "" "task.return" (func $task-return))
(func (export "foo"))
)
(core func $task-return (canon task.return))
(core instance $i (instantiate $m
(with "" (instance (export "task.return" (func $task-return))))
))
(func (export "foo") (canon lift (core func $i "foo") async))
)"#,
"wasm trap: async-lifted export failed to produce a result",
)
.await
}
#[tokio::test]
async fn task_return_type_mismatch() -> Result<()> {
task_return_trap(
r#"(component
(core module $m
(import "" "task.return" (func $task-return (param i32)))
(func (export "foo") (call $task-return (i32.const 42)))
)
(core func $task-return (canon task.return (result u32)))
(core instance $i (instantiate $m
(with "" (instance (export "task.return" (func $task-return))))
))
(func (export "foo") (canon lift (core func $i "foo") async))
)"#,
"invalid `task.return` signature and/or options for current task",
)
.await
}
#[tokio::test]
async fn task_return_memory_mismatch() -> Result<()> {
task_return_trap(
r#"(component
(core module $libc (memory (export "memory") 1))
(core instance $libc (instantiate $libc))
(core module $m
(import "" "task.return" (func $task-return))
(func (export "foo") (call $task-return))
)
(core func $task-return (canon task.return (memory $libc "memory")))
(core instance $i (instantiate $m
(with "" (instance (export "task.return" (func $task-return))))
))
(func (export "foo") (canon lift (core func $i "foo") async))
)"#,
"invalid `task.return` signature and/or options for current task",
)
.await
}
#[tokio::test]
async fn task_return_string_encoding_mismatch() -> Result<()> {
task_return_trap(
r#"(component
(core module $m
(import "" "task.return" (func $task-return))
(func (export "foo") (call $task-return))
)
(core func $task-return (canon task.return string-encoding=utf16))
(core instance $i (instantiate $m
(with "" (instance (export "task.return" (func $task-return))))
))
(func (export "foo") (canon lift (core func $i "foo") async))
)"#,
"invalid `task.return` signature and/or options for current task",
)
.await
}
async fn task_return_trap(component: &str, substring: &str) -> Result<()> {
let mut config = Config::new();
config.wasm_component_model_async(true);
config.wasm_component_model_async_stackful(true);
config.wasm_component_model_threading(true);
let engine = &Engine::new(&config)?;
let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ());
let instance = Linker::new(&engine)
.instantiate_async(&mut store, &component)
.await?;
let func = instance.get_typed_func::<(), ()>(&mut store, "foo")?;
match store
.run_concurrent(async move |accessor| {
wasmtime::error::Ok(func.call_concurrent(accessor, ()).await?)
})
.await
{
Ok(_) => panic!(),
Err(e) => {
assert!(
format!("{e:?}").contains(substring),
"could not find `{substring}` in `{e:?}`"
)
}
}
Ok(())
}
#[tokio::test]
async fn many_parameters() -> Result<()> {
test_many_parameters(false, false).await
}
#[tokio::test]
async fn many_parameters_concurrent() -> Result<()> {
test_many_parameters(false, true).await
}
#[tokio::test]
async fn many_parameters_dynamic() -> Result<()> {
test_many_parameters(true, false).await
}
#[tokio::test]
async fn many_parameters_dynamic_concurrent() -> Result<()> {
test_many_parameters(true, true).await
}
async fn test_many_parameters(dynamic: bool, concurrent: bool) -> Result<()> {
let (body, async_opts) = if concurrent {
(
r#"
(call $task-return
(i32.const 0)
(i32.mul
(memory.size)
(i32.const 65536)
)
(local.get 0)
)
(i32.const 0)
"#,
r#"async (callback (func $i "callback"))"#,
)
} else {
(
r#"
(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)
"#,
"",
)
};
let component = format!(
r#"(component
(core module $libc
(memory (export "memory") 1)
{REALLOC_AND_FREE}
)
(core instance $libc (instantiate $libc))
(core module $m
(import "libc" "memory" (memory 1))
(import "libc" "realloc" (func $realloc (param i32 i32 i32 i32) (result i32)))
(import "" "task.return" (func $task-return (param i32 i32 i32)))
(func (export "foo") (param i32) (result i32)
{body}
)
(func (export "callback") (param i32 i32 i32) (result i32) unreachable)
)
(type $tuple (tuple (list u8) u32))
(core func $task-return (canon task.return
(result $tuple)
(memory $libc "memory")
))
(core instance $i (instantiate $m
(with "" (instance (export "task.return" (func $task-return))))
(with "libc" (instance $libc))
))
(type $t (func
(param "p1" s8) ;; offset 0, size 1
(param "p2" u64) ;; offset 8, size 8
(param "p3" float32) ;; offset 16, size 4
(param "p4" u8) ;; offset 20, size 1
(param "p5" s16) ;; offset 22, size 2
(param "p6" string) ;; offset 24, size 8
(param "p7" (list u32)) ;; offset 32, size 8
(param "p8" bool) ;; offset 40, size 1
(param "p9" bool) ;; offset 41, size 1
(param "p0" char) ;; offset 44, size 4
(param "pa" (list bool)) ;; offset 48, size 8
(param "pb" (list char)) ;; offset 56, size 8
(param "pc" (list string)) ;; offset 64, size 8
(result $tuple)
))
(func (export "many-param") (type $t)
(canon lift
(core func $i "foo")
(memory $libc "memory")
(realloc (func $libc "realloc"))
{async_opts}
)
)
)"#
);
let mut config = Config::new();
config.wasm_component_model_async(true);
let engine = &Engine::new(&config)?;
let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ());
let instance = Linker::new(&engine)
.instantiate_async(&mut store, &component)
.await?;
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) = if dynamic {
let input = vec![
Val::S8(input.0),
Val::U64(input.1),
Val::Float32(input.2),
Val::U8(input.3),
Val::S16(input.4),
Val::String(input.5.into()),
Val::List(input.6.iter().copied().map(Val::U32).collect()),
Val::Bool(input.7),
Val::Bool(input.8),
Val::Char(input.9),
Val::List(input.10.iter().copied().map(Val::Bool).collect()),
Val::List(input.11.iter().copied().map(Val::Char).collect()),
Val::List(input.12.iter().map(|&s| Val::String(s.into())).collect()),
];
let func = instance.get_func(&mut store, "many-param").unwrap();
let mut results = vec![Val::Bool(false)];
if concurrent {
store
.run_concurrent(async |store| {
func.call_concurrent(store, &input, &mut results).await?;
wasmtime::error::Ok(())
})
.await??;
} else {
func.call_async(&mut store, &input, &mut results).await?;
};
let mut results = results.into_iter();
let Some(Val::Tuple(results)) = results.next() else {
panic!()
};
let mut results = results.into_iter();
let Some(Val::List(memory)) = results.next() else {
panic!()
};
let Some(Val::U32(pointer)) = results.next() else {
panic!()
};
(
memory
.into_iter()
.map(|v| if let Val::U8(v) = v { v } else { panic!() })
.collect(),
pointer,
)
} else {
let func = instance.get_typed_func::<(
i8,
u64,
f32,
u8,
i16,
&str,
&[u32],
bool,
bool,
char,
&[bool],
&[char],
&[&str],
), ((Vec<u8>, u32),)>(&mut store, "many-param")?;
if concurrent {
store
.run_concurrent(async move |accessor| {
wasmtime::error::Ok(func.call_concurrent(accessor, input).await?)
})
.await??
.0
} else {
func.call_async(&mut store, input).await?.0
}
};
let memory = &memory[..];
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 | 1
);
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.4);
assert_eq!(actual.ptr_len(memory, 1), input.5.as_bytes());
let mut mem = actual.ptr_len(memory, 4);
for expected in input.6.iter() {
assert_eq!(u32::from_le_bytes(*mem.take_n::<4>()), *expected);
}
assert!(mem.is_empty());
assert_eq!(actual.take_n::<1>(), &[input.7 as u8]);
assert_eq!(actual.take_n::<1>(), &[input.8 as u8]);
actual.skip::<2>();
assert_eq!(u32::from_le_bytes(*actual.take_n::<4>()), input.9 as u32);
mem = actual.ptr_len(memory, 1);
for expected in input.10.iter() {
assert_eq!(mem.take_n::<1>(), &[*expected as u8]);
}
assert!(mem.is_empty());
mem = actual.ptr_len(memory, 4);
for expected in input.11.iter() {
assert_eq!(u32::from_le_bytes(*mem.take_n::<4>()), *expected as u32);
}
assert!(mem.is_empty());
mem = actual.ptr_len(memory, 8);
for expected in input.12.iter() {
let actual = mem.ptr_len(memory, 1);
assert_eq!(actual, expected.as_bytes());
}
assert!(mem.is_empty());
assert!(actual.is_empty());
Ok(())
}
#[tokio::test]
async fn many_results() -> Result<()> {
test_many_results(false, false).await
}
#[tokio::test]
async fn many_results_concurrent() -> Result<()> {
test_many_results(false, true).await
}
#[tokio::test]
async fn many_results_dynamic() -> Result<()> {
test_many_results(true, false).await
}
#[tokio::test]
async fn many_results_dynamic_concurrent() -> Result<()> {
test_many_results(true, true).await
}
async fn test_many_results(dynamic: bool, concurrent: bool) -> Result<()> {
let (ret, async_opts) = if concurrent {
(
r#"
call $task-return
i32.const 0
"#,
r#"async (callback (func $i "callback"))"#,
)
} else {
("", "")
};
let my_nan = CANON_32BIT_NAN | 1;
let component = format!(
r#"(component
(core module $libc
(memory (export "memory") 1)
{REALLOC_AND_FREE}
)
(core instance $libc (instantiate $libc))
(core module $m
(import "libc" "memory" (memory 1))
(import "libc" "realloc" (func $realloc (param i32 i32 i32 i32) (result i32)))
(import "" "task.return" (func $task-return (param i32)))
(func (export "foo") (result i32)
(local $base i32)
(local $string i32)
(local $list i32)
(local.set $base
(call $realloc
(i32.const 0)
(i32.const 0)
(i32.const 8)
(i32.const 72)))
(i32.store8 offset=0
(local.get $base)
(i32.const -100))
(i64.store offset=8
(local.get $base)
(i64.const 9223372036854775807))
(f32.store offset=16
(local.get $base)
(f32.reinterpret_i32 (i32.const {my_nan})))
(i32.store8 offset=20
(local.get $base)
(i32.const 38))
(i32.store16 offset=22
(local.get $base)
(i32.const 18831))
(local.set $string
(call $realloc
(i32.const 0)
(i32.const 0)
(i32.const 1)
(i32.const 6)))
(i32.store8 offset=0
(local.get $string)
(i32.const 97)) ;; 'a'
(i32.store8 offset=1
(local.get $string)
(i32.const 98)) ;; 'b'
(i32.store8 offset=2
(local.get $string)
(i32.const 99)) ;; 'c'
(i32.store8 offset=3
(local.get $string)
(i32.const 100)) ;; 'd'
(i32.store8 offset=4
(local.get $string)
(i32.const 101)) ;; 'e'
(i32.store8 offset=5
(local.get $string)
(i32.const 102)) ;; 'f'
(i32.store offset=24
(local.get $base)
(local.get $string))
(i32.store offset=28
(local.get $base)
(i32.const 2))
(local.set $list
(call $realloc
(i32.const 0)
(i32.const 0)
(i32.const 4)
(i32.const 32)))
(i32.store offset=0
(local.get $list)
(i32.const 1))
(i32.store offset=4
(local.get $list)
(i32.const 2))
(i32.store offset=8
(local.get $list)
(i32.const 3))
(i32.store offset=12
(local.get $list)
(i32.const 4))
(i32.store offset=16
(local.get $list)
(i32.const 5))
(i32.store offset=20
(local.get $list)
(i32.const 6))
(i32.store offset=24
(local.get $list)
(i32.const 7))
(i32.store offset=28
(local.get $list)
(i32.const 8))
(i32.store offset=32
(local.get $base)
(local.get $list))
(i32.store offset=36
(local.get $base)
(i32.const 8))
(i32.store8 offset=40
(local.get $base)
(i32.const 1))
(i32.store8 offset=41
(local.get $base)
(i32.const 0))
(i32.store offset=44
(local.get $base)
(i32.const 128681)) ;; '🚩'
(local.set $list
(call $realloc
(i32.const 0)
(i32.const 0)
(i32.const 1)
(i32.const 5)))
(i32.store8 offset=0
(local.get $list)
(i32.const 0))
(i32.store8 offset=1
(local.get $list)
(i32.const 1))
(i32.store8 offset=2
(local.get $list)
(i32.const 0))
(i32.store8 offset=3
(local.get $list)
(i32.const 1))
(i32.store8 offset=4
(local.get $list)
(i32.const 1))
(i32.store offset=48
(local.get $base)
(local.get $list))
(i32.store offset=52
(local.get $base)
(i32.const 5))
(local.set $list
(call $realloc
(i32.const 0)
(i32.const 0)
(i32.const 4)
(i32.const 20)))
(i32.store offset=0
(local.get $list)
(i32.const 127820)) ;; '🍌'
(i32.store offset=4
(local.get $list)
(i32.const 129360)) ;; '🥐'
(i32.store offset=8
(local.get $list)
(i32.const 127831)) ;; '🍗'
(i32.store offset=12
(local.get $list)
(i32.const 127833)) ;; '🍙'
(i32.store offset=16
(local.get $list)
(i32.const 127841)) ;; '🍡'
(i32.store offset=56
(local.get $base)
(local.get $list))
(i32.store offset=60
(local.get $base)
(i32.const 5))
(local.set $list
(call $realloc
(i32.const 0)
(i32.const 0)
(i32.const 4)
(i32.const 16)))
(i32.store offset=0
(local.get $list)
(i32.add (local.get $string) (i32.const 2)))
(i32.store offset=4
(local.get $list)
(i32.const 2))
(i32.store offset=8
(local.get $list)
(i32.add (local.get $string) (i32.const 4)))
(i32.store offset=12
(local.get $list)
(i32.const 2))
(i32.store offset=64
(local.get $base)
(local.get $list))
(i32.store offset=68
(local.get $base)
(i32.const 2))
local.get $base
{ret}
)
(func (export "callback") (param i32 i32 i32) (result i32) unreachable)
)
(type $tuple (tuple
s8
u64
float32
u8
s16
string
(list u32)
bool
bool
char
(list bool)
(list char)
(list string)
))
(core func $task-return (canon task.return
(result $tuple)
(memory $libc "memory")
))
(core instance $i (instantiate $m
(with "" (instance (export "task.return" (func $task-return))))
(with "libc" (instance $libc))
))
(type $t (func (result $tuple)))
(func (export "many-results") (type $t)
(canon lift
(core func $i "foo")
(memory $libc "memory")
(realloc (func $libc "realloc"))
{async_opts}
)
)
)"#
);
let mut config = Config::new();
config.wasm_component_model_async(true);
let engine = &Engine::new(&config)?;
let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ());
let instance = Linker::new(&engine)
.instantiate_async(&mut store, &component)
.await?;
let expected = (
-100i8,
u64::MAX / 2,
f32::from_bits(CANON_32BIT_NAN | 1),
38u8,
18831i16,
"ab".to_string(),
vec![1u32, 2, 3, 4, 5, 6, 7, 8],
true,
false,
'🚩',
vec![false, true, false, true, true],
vec!['🍌', '🥐', '🍗', '🍙', '🍡'],
vec!["cd".to_string(), "ef".to_string()],
);
let actual = if dynamic {
let func = instance.get_func(&mut store, "many-results").unwrap();
let mut results = vec![Val::Bool(false)];
if concurrent {
store
.run_concurrent(async |store| {
func.call_concurrent(store, &[], &mut results).await?;
wasmtime::error::Ok(())
})
.await??;
} else {
func.call_async(&mut store, &[], &mut results).await?;
};
let mut results = results.into_iter();
let Some(Val::Tuple(results)) = results.next() else {
panic!()
};
let mut results = results.into_iter();
let Some(Val::S8(p1)) = results.next() else {
panic!()
};
let Some(Val::U64(p2)) = results.next() else {
panic!()
};
let Some(Val::Float32(p3)) = results.next() else {
panic!()
};
let Some(Val::U8(p4)) = results.next() else {
panic!()
};
let Some(Val::S16(p5)) = results.next() else {
panic!()
};
let Some(Val::String(p6)) = results.next() else {
panic!()
};
let Some(Val::List(p7)) = results.next() else {
panic!()
};
let p7 = p7
.into_iter()
.map(|v| if let Val::U32(v) = v { v } else { panic!() })
.collect();
let Some(Val::Bool(p8)) = results.next() else {
panic!()
};
let Some(Val::Bool(p9)) = results.next() else {
panic!()
};
let Some(Val::Char(p0)) = results.next() else {
panic!()
};
let Some(Val::List(pa)) = results.next() else {
panic!()
};
let pa = pa
.into_iter()
.map(|v| if let Val::Bool(v) = v { v } else { panic!() })
.collect();
let Some(Val::List(pb)) = results.next() else {
panic!()
};
let pb = pb
.into_iter()
.map(|v| if let Val::Char(v) = v { v } else { panic!() })
.collect();
let Some(Val::List(pc)) = results.next() else {
panic!()
};
let pc = pc
.into_iter()
.map(|v| if let Val::String(v) = v { v } else { panic!() })
.collect();
(p1, p2, p3, p4, p5, p6, p7, p8, p9, p0, pa, pb, pc)
} else {
let func = instance.get_typed_func::<(), ((
i8,
u64,
f32,
u8,
i16,
String,
Vec<u32>,
bool,
bool,
char,
Vec<bool>,
Vec<char>,
Vec<String>,
),)>(&mut store, "many-results")?;
if concurrent {
store
.run_concurrent(async move |accessor| {
wasmtime::error::Ok(func.call_concurrent(accessor, ()).await?)
})
.await??
.0
} else {
func.call_async(&mut store, ()).await?.0
}
};
assert_eq!(expected.0, actual.0);
assert_eq!(expected.1, actual.1);
assert!(expected.2.is_nan());
assert!(actual.2.is_nan());
assert_eq!(expected.3, actual.3);
assert_eq!(expected.4, actual.4);
assert_eq!(expected.5, actual.5);
assert_eq!(expected.6, actual.6);
assert_eq!(expected.7, actual.7);
assert_eq!(expected.8, actual.8);
assert_eq!(expected.9, actual.9);
assert_eq!(expected.10, actual.10);
assert_eq!(expected.11, actual.11);
assert_eq!(expected.12, actual.12);
Ok(())
}
#[test]
fn some_traps() -> Result<()> {
let middle_of_memory = (i32::MAX / 2) & (!0xff);
let component = format!(
r#"(component
(core module $m
(memory (export "memory") 1)
(func (export "take-many") (param i32))
(func (export "take-list") (param i32 i32))
(func (export "realloc") (param i32 i32 i32 i32) (result i32)
unreachable)
)
(core instance $i (instantiate $m))
(func (export "take-list-unreachable") (param "a" (list u8))
(canon lift (core func $i "take-list") (memory $i "memory") (realloc (func $i "realloc")))
)
(func (export "take-string-unreachable") (param "a" string)
(canon lift (core func $i "take-list") (memory $i "memory") (realloc (func $i "realloc")))
)
(type $t (func
(param "s1" string)
(param "s2" string)
(param "s3" string)
(param "s4" string)
(param "s5" string)
(param "s6" string)
(param "s7" string)
(param "s8" string)
(param "s9" string)
(param "s10" string)
))
(func (export "take-many-unreachable") (type $t)
(canon lift (core func $i "take-many") (memory $i "memory") (realloc (func $i "realloc")))
)
(core module $m2
(memory (export "memory") 1)
(func (export "take-many") (param i32))
(func (export "take-list") (param i32 i32))
(func (export "realloc") (param i32 i32 i32 i32) (result i32)
i32.const {middle_of_memory})
)
(core instance $i2 (instantiate $m2))
(func (export "take-list-base-oob") (param "a" (list u8))
(canon lift (core func $i2 "take-list") (memory $i2 "memory") (realloc (func $i2 "realloc")))
)
(func (export "take-string-base-oob") (param "a" string)
(canon lift (core func $i2 "take-list") (memory $i2 "memory") (realloc (func $i2 "realloc")))
)
(func (export "take-many-base-oob") (type $t)
(canon lift (core func $i2 "take-many") (memory $i2 "memory") (realloc (func $i2 "realloc")))
)
(core module $m3
(memory (export "memory") 1)
(func (export "take-many") (param i32))
(func (export "take-list") (param i32 i32))
(func (export "realloc") (param i32 i32 i32 i32) (result i32)
i32.const 65532)
)
(core instance $i3 (instantiate $m3))
(func (export "take-list-end-oob") (param "a" (list u8))
(canon lift (core func $i3 "take-list") (memory $i3 "memory") (realloc (func $i3 "realloc")))
)
(func (export "take-string-end-oob") (param "a" string)
(canon lift (core func $i3 "take-list") (memory $i3 "memory") (realloc (func $i3 "realloc")))
)
(func (export "take-many-end-oob") (type $t)
(canon lift (core func $i3 "take-many") (memory $i3 "memory") (realloc (func $i3 "realloc")))
)
(core module $m4
(memory (export "memory") 1)
(func (export "take-many") (param i32))
(global $cnt (mut i32) (i32.const 0))
(func (export "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
)
)
(core instance $i4 (instantiate $m4))
(func (export "take-many-second-oob") (type $t)
(canon lift (core func $i4 "take-many") (memory $i4 "memory") (realloc (func $i4 "realloc")))
)
)"#
);
let engine = super::engine();
let component = Component::new(&engine, component)?;
let err = with_new_instance(&engine, &component, |store, instance| {
instance
.get_typed_func::<(&[u8],), ()>(&mut *store, "take-list-unreachable")?
.call(store, (&[],))
.unwrap_err()
.downcast::<Trap>()
})?;
assert_eq!(err, Trap::UnreachableCodeReached);
let err = with_new_instance(&engine, &component, |store, instance| {
instance
.get_typed_func::<(&str,), ()>(&mut *store, "take-string-unreachable")?
.call(store, ("",))
.unwrap_err()
.downcast::<Trap>()
})?;
assert_eq!(err, Trap::UnreachableCodeReached);
let err = with_new_instance(&engine, &component, |store, instance| {
instance
.get_typed_func::<(&str, &str, &str, &str, &str, &str, &str, &str, &str, &str), ()>(
&mut *store,
"take-many-unreachable",
)?
.call(store, ("", "", "", "", "", "", "", "", "", ""))
.unwrap_err()
.downcast::<Trap>()
})?;
assert_eq!(err, Trap::UnreachableCodeReached);
#[track_caller]
fn assert_oob(err: &wasmtime::Error) {
assert!(
err.to_string()
.contains("realloc return: beyond end of memory"),
"{err:?}",
);
}
let err = with_new_instance(&engine, &component, |store, instance| {
instance
.get_typed_func::<(&[u8],), ()>(&mut *store, "take-list-base-oob")?
.call(store, (&[],))
})
.unwrap_err();
assert_oob(&err);
let err = with_new_instance(&engine, &component, |store, instance| {
instance
.get_typed_func::<(&[u8],), ()>(&mut *store, "take-list-base-oob")?
.call(store, (&[1],))
})
.unwrap_err();
assert_oob(&err);
let err = with_new_instance(&engine, &component, |store, instance| {
instance
.get_typed_func::<(&str,), ()>(&mut *store, "take-string-base-oob")?
.call(store, ("",))
})
.unwrap_err();
assert_oob(&err);
let err = with_new_instance(&engine, &component, |store, instance| {
instance
.get_typed_func::<(&str,), ()>(&mut *store, "take-string-base-oob")?
.call(store, ("x",))
})
.unwrap_err();
assert_oob(&err);
let err = with_new_instance(&engine, &component, |store, instance| {
instance
.get_typed_func::<(&str, &str, &str, &str, &str, &str, &str, &str, &str, &str), ()>(
&mut *store,
"take-many-base-oob",
)?
.call(store, ("", "", "", "", "", "", "", "", "", ""))
})
.unwrap_err();
assert_oob(&err);
with_new_instance(&engine, &component, |store, instance| {
instance
.get_typed_func::<(&[u8],), ()>(&mut *store, "take-list-end-oob")?
.call(store, (&[],))
})?;
with_new_instance(&engine, &component, |store, instance| {
instance
.get_typed_func::<(&[u8],), ()>(&mut *store, "take-list-end-oob")?
.call(store, (&[1, 2, 3, 4],))
})?;
let err = with_new_instance(&engine, &component, |store, instance| {
instance
.get_typed_func::<(&[u8],), ()>(&mut *store, "take-list-end-oob")?
.call(store, (&[1, 2, 3, 4, 5],))
})
.unwrap_err();
assert_oob(&err);
with_new_instance(&engine, &component, |store, instance| {
instance
.get_typed_func::<(&str,), ()>(&mut *store, "take-string-end-oob")?
.call(store, ("",))
})?;
with_new_instance(&engine, &component, |store, instance| {
instance
.get_typed_func::<(&str,), ()>(&mut *store, "take-string-end-oob")?
.call(store, ("abcd",))
})?;
let err = with_new_instance(&engine, &component, |store, instance| {
instance
.get_typed_func::<(&str,), ()>(&mut *store, "take-string-end-oob")?
.call(store, ("abcde",))
})
.unwrap_err();
assert_oob(&err);
let err = with_new_instance(&engine, &component, |store, instance| {
instance
.get_typed_func::<(&str, &str, &str, &str, &str, &str, &str, &str, &str, &str), ()>(
&mut *store,
"take-many-end-oob",
)?
.call(store, ("", "", "", "", "", "", "", "", "", ""))
})
.unwrap_err();
assert_oob(&err);
let err = with_new_instance(&engine, &component, |store, instance| {
instance
.get_typed_func::<(&str, &str, &str, &str, &str, &str, &str, &str, &str, &str), ()>(
&mut *store,
"take-many-second-oob",
)?
.call(store, ("", "", "", "", "", "", "", "", "", ""))
})
.unwrap_err();
assert_oob(&err);
let err = with_new_instance(&engine, &component, |store, instance| {
instance
.get_typed_func::<(&str, &str, &str, &str, &str, &str, &str, &str, &str, &str), ()>(
&mut *store,
"take-many-second-oob",
)?
.call(store, ("", "", "", "", "", "", "", "", "", "x"))
})
.unwrap_err();
assert_oob(&err);
Ok(())
}
#[test]
fn char_bool_memory() -> Result<()> {
let component = format!(
r#"(component
(core 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}
)
(core instance $i (instantiate $m))
(func (export "ret-tuple") (param "a" u32) (param "b" u32) (result (tuple bool char))
(canon lift (core func $i "ret-tuple")
(memory $i "memory")
(realloc (func $i "realloc")))
)
)"#
);
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
(core 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}
)
(core instance $i (instantiate $m))
(func (export "ret-list-u8") (result (list u8))
(canon lift (core func $i "ret-list")
(memory $i "memory")
(realloc (func $i "realloc"))
)
)
(func (export "ret-string") (result string)
(canon lift (core func $i "ret-list")
(memory $i "memory")
(realloc (func $i "realloc"))
)
)
)"#
);
let engine = super::engine();
let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ());
let ret_list_u8 = Linker::new(&engine)
.instantiate(&mut store, &component)?
.get_typed_func::<(), (WasmList<u8>,)>(&mut store, "ret-list-u8")?;
let ret_string = Linker::new(&engine)
.instantiate(&mut store, &component)?
.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
(core 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
)
)
(core instance $i (instantiate $m))
(func (export "foo")
(param "a" (tuple s32 float64))
(param "b" (tuple s8))
(result (tuple u16))
(canon lift (core 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
(core module $m
(memory (export "memory") 1)
(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}
)
(core instance $i (instantiate $m))
(func (export "option-u8-to-tuple") (param "a" (option u8)) (result (tuple u32 u32))
(canon lift (core func $i "pass1") (memory $i "memory"))
)
(func (export "option-u32-to-tuple") (param "a" (option u32)) (result (tuple u32 u32))
(canon lift (core func $i "pass1") (memory $i "memory"))
)
(func (export "option-string-to-tuple") (param "a" (option string)) (result (tuple u32 string))
(canon lift
(core func $i "pass2")
(memory $i "memory")
(realloc (func $i "realloc"))
)
)
(func (export "to-option-u8") (param "a" u32) (param "b" u32) (result (option u8))
(canon lift (core func $i "pass1") (memory $i "memory"))
)
(func (export "to-option-u32") (param "a" u32) (param "b" u32) (result (option u32))
(canon lift
(core func $i "pass1")
(memory $i "memory")
)
)
(func (export "to-option-string") (param "a" u32) (param "b" string) (result (option string))
(canon lift
(core func $i "pass2")
(memory $i "memory")
(realloc (func $i "realloc"))
)
)
)"#
);
let engine = super::engine();
let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ());
let linker = Linker::new(&engine);
let instance = linker.instantiate(&mut store, &component)?;
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 instance = linker.instantiate(&mut store, &component)?;
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 instance = linker.instantiate(&mut store, &component)?;
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 instance = linker.instantiate(&mut store, &component)?;
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, ""))?.0;
assert!(ret.is_none());
let ret = to_option_string.call(&mut store, (1, ""))?.0;
assert_eq!(ret.unwrap().to_str(&store)?, "");
let ret = to_option_string.call(&mut store, (1, "cheesecake"))?.0;
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
(core 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}
)
(core instance $i (instantiate $m))
(func (export "take-expected-unit") (param "a" (result)) (result u32)
(canon lift (core func $i "pass0"))
)
(func (export "take-expected-u8-f32") (param "a" (result u8 (error float32))) (result (tuple u32 u32))
(canon lift (core func $i "pass1") (memory $i "memory"))
)
(type $list (list u8))
(func (export "take-expected-string") (param "a" (result string (error $list))) (result (tuple u32 string))
(canon lift
(core func $i "pass2")
(memory $i "memory")
(realloc (func $i "realloc"))
)
)
(func (export "to-expected-unit") (param "a" u32) (result (result))
(canon lift (core func $i "pass0"))
)
(func (export "to-expected-s16-f32") (param "a" u32) (param "b" u32) (result (result s16 (error float32)))
(canon lift
(core func $i "pass1")
(memory $i "memory")
(realloc (func $i "realloc"))
)
)
)"#
);
let engine = super::engine();
let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ());
let linker = Linker::new(&engine);
let instance = linker.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 instance = linker.instantiate(&mut store, &component)?;
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 instance = linker.instantiate(&mut store, &component)?;
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))?
.0;
assert_eq!(ret.unwrap_err().to_bits(), CANON_32BIT_NAN | 1);
assert!(to_expected_s16_f32.call(&mut store, (2, 0)).is_err());
Ok(())
}
#[test]
fn fancy_list() -> Result<()> {
let component = format!(
r#"(component
(core 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}
)
(core instance $i (instantiate $m))
(type $a (option u8))
(type $b (result (error string)))
(type $input (list (tuple $a $b)))
(func (export "take")
(param "a" $input)
(result (tuple u32 u32 (list u8)))
(canon lift
(core func $i "take")
(memory $i "memory")
(realloc (func $i "realloc"))
)
)
)"#
);
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_le_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()
}
}
#[test]
fn invalid_alignment() -> Result<()> {
let component = format!(
r#"(component
(core module $m
(memory (export "memory") 1)
(func (export "realloc") (param i32 i32 i32 i32) (result i32)
i32.const 1)
(func (export "take-i32") (param i32))
(func (export "ret-1") (result i32) i32.const 1)
(func (export "ret-unaligned-list") (result i32)
(i32.store offset=0 (i32.const 8) (i32.const 1))
(i32.store offset=4 (i32.const 8) (i32.const 1))
i32.const 8)
)
(core instance $i (instantiate $m))
(func (export "many-params")
(param "s1" string) (param "s2" string) (param "s3" string) (param "s4" string)
(param "s5" string) (param "s6" string) (param "s7" string) (param "s8" string)
(param "s9" string) (param "s10" string) (param "s11" string) (param "s12" string)
(canon lift
(core func $i "take-i32")
(memory $i "memory")
(realloc (func $i "realloc"))
)
)
(func (export "string-ret") (result string)
(canon lift
(core func $i "ret-1")
(memory $i "memory")
(realloc (func $i "realloc"))
)
)
(func (export "list-u32-ret") (result (list u32))
(canon lift
(core func $i "ret-unaligned-list")
(memory $i "memory")
(realloc (func $i "realloc"))
)
)
)"#
);
let engine = super::engine();
let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ());
let instance = |store: &mut Store<()>| Linker::new(&engine).instantiate(store, &component);
let err = instance(&mut store)?
.get_typed_func::<(
&str,
&str,
&str,
&str,
&str,
&str,
&str,
&str,
&str,
&str,
&str,
&str,
), ()>(&mut store, "many-params")?
.call(&mut store, ("", "", "", "", "", "", "", "", "", "", "", ""))
.unwrap_err();
assert!(
err.to_string()
.contains("realloc return: result not aligned"),
"{}",
err
);
let err = instance(&mut store)?
.get_typed_func::<(), (WasmStr,)>(&mut store, "string-ret")?
.call(&mut store, ())
.err()
.unwrap();
assert!(
err.to_string().contains("return pointer not aligned"),
"{}",
err
);
let err = instance(&mut store)?
.get_typed_func::<(), (WasmList<u32>,)>(&mut store, "list-u32-ret")?
.call(&mut store, ())
.err()
.unwrap();
assert!(
err.to_string().contains("list pointer is not aligned"),
"{}",
err
);
Ok(())
}
#[test]
fn drop_component_still_works() -> Result<()> {
let component = r#"
(component
(import "f" (func $f))
(core func $f_lower
(canon lower (func $f))
)
(core module $m
(import "" "" (func $f))
(func $f2
call $f
call $f
)
(export "f" (func $f2))
)
(core instance $i (instantiate $m
(with "" (instance
(export "" (func $f_lower))
))
))
(func (export "g")
(canon lift
(core func $i "f")
)
)
)
"#;
let (mut store, instance) = {
let engine = super::engine();
let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, 0);
let mut linker = Linker::new(&engine);
linker.root().func_wrap(
"f",
|mut store: StoreContextMut<'_, u32>, _: ()| -> Result<()> {
*store.data_mut() += 1;
Ok(())
},
)?;
let instance = linker.instantiate(&mut store, &component)?;
(store, instance)
};
let f = instance.get_typed_func::<(), ()>(&mut store, "g")?;
assert_eq!(*store.data(), 0);
f.call(&mut store, ())?;
assert_eq!(*store.data(), 2);
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(&mut store, ())?
.0;
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(&mut store, ())?
.0;
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(&mut store, ())?
.0;
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(&mut store, ())?
.0;
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(&mut store, ())?
.0;
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(&mut store, ())?
.0;
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(&mut store, ())?
.0;
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(&mut store, ())?
.0;
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(())
}
#[test]
fn lower_then_lift() -> Result<()> {
let component = r#"
(component $c
(import "f" (func $f (result u32)))
(core func $f_lower
(canon lower (func $f))
)
(func $f2 (result s32)
(canon lift (core func $f_lower))
)
(export "f2" (func $f2))
)
"#;
let engine = super::engine();
let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ());
let mut linker = Linker::new(&engine);
linker.root().func_wrap("f", |_, _: ()| Ok((2u32,)))?;
let instance = linker.instantiate(&mut store, &component)?;
let f = instance.get_typed_func::<(), (i32,)>(&mut store, "f2")?;
assert_eq!(f.call(&mut store, ())?, (2,));
let component = format!(
r#"
(component $c
(import "s" (func $f (param "a" string)))
(core module $libc
(memory (export "memory") 1)
{REALLOC_AND_FREE}
)
(core instance $libc (instantiate $libc))
(core func $f_lower
(canon lower (func $f) (memory $libc "memory"))
)
(func $f2 (param "a" string)
(canon lift (core func $f_lower)
(memory $libc "memory")
(realloc (func $libc "realloc"))
)
)
(export "f" (func $f2))
)
"#
);
let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ());
linker
.root()
.func_wrap("s", |store: StoreContextMut<'_, ()>, (x,): (WasmStr,)| {
assert_eq!(x.to_str(&store)?, "hello");
Ok(())
})?;
let instance = linker.instantiate(&mut store, &component)?;
let f = instance.get_typed_func::<(&str,), ()>(&mut store, "f")?;
f.call(&mut store, ("hello",))?;
let component = format!(
r#"
(component $c
(import "s2" (func $f (param "a" string) (result u32)))
(core module $libc
(memory (export "memory") 1)
{REALLOC_AND_FREE}
)
(core instance $libc (instantiate $libc))
(core func $f_lower
(canon lower (func $f) (memory $libc "memory"))
)
(func $f2 (param "a" string) (result string)
(canon lift (core func $f_lower)
(memory $libc "memory")
(realloc (func $libc "realloc"))
)
)
(export "f" (func $f2))
)
"#
);
let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ());
linker
.root()
.func_wrap("s2", |store: StoreContextMut<'_, ()>, (x,): (WasmStr,)| {
assert_eq!(x.to_str(&store)?, "hello");
Ok((u32::MAX,))
})?;
let instance = linker.instantiate(&mut store, &component)?;
let f = instance.get_typed_func::<(&str,), (WasmStr,)>(&mut store, "f")?;
let err = f.call(&mut store, ("hello",)).err().unwrap();
assert!(
err.to_string().contains("return pointer not aligned"),
"{}",
err
);
Ok(())
}
#[test]
fn errors_that_poison_instance() -> Result<()> {
let component = format!(
r#"
(component $c
(core module $m1
(func (export "f1") unreachable)
(func (export "f2"))
)
(core instance $m1 (instantiate $m1))
(func (export "f1") (canon lift (core func $m1 "f1")))
(func (export "f2") (canon lift (core func $m1 "f2")))
(core module $m2
(func (export "f") (param i32 i32))
(func (export "r") (param i32 i32 i32 i32) (result i32) unreachable)
(memory (export "m") 1)
)
(core instance $m2 (instantiate $m2))
(func (export "f3") (param "a" string)
(canon lift (core func $m2 "f") (realloc (func $m2 "r")) (memory $m2 "m"))
)
(core module $m3
(func (export "f") (result i32) i32.const 1)
(memory (export "m") 1)
)
(core instance $m3 (instantiate $m3))
(func (export "f4") (result string)
(canon lift (core func $m3 "f") (memory $m3 "m"))
)
)
"#
);
let engine = super::engine();
let component = Component::new(&engine, component)?;
let linker = Linker::new(&engine);
{
let mut store = Store::new(&engine, ());
let instance = linker.instantiate(&mut store, &component)?;
let f1 = instance.get_typed_func::<(), ()>(&mut store, "f1")?;
let f2 = instance.get_typed_func::<(), ()>(&mut store, "f2")?;
assert_unreachable(f1.call(&mut store, ()));
assert_poisoned(f1.call(&mut store, ()));
assert_poisoned(f2.call(&mut store, ()));
}
{
let mut store = Store::new(&engine, ());
let instance = linker.instantiate(&mut store, &component)?;
let f3 = instance.get_typed_func::<(&str,), ()>(&mut store, "f3")?;
assert_unreachable(f3.call(&mut store, ("x",)));
assert_poisoned(f3.call(&mut store, ("x",)));
let instance = linker.instantiate(&mut store, &component)?;
let f3 = instance.get_typed_func::<(&str,), ()>(&mut store, "f3")?;
assert_poisoned(f3.call(&mut store, ("x",)));
}
{
let mut store = Store::new(&engine, ());
let instance = linker.instantiate(&mut store, &component)?;
let f4 = instance.get_typed_func::<(), (WasmStr,)>(&mut store, "f4")?;
assert!(f4.call(&mut store, ()).is_err());
assert_poisoned(f4.call(&mut store, ()));
}
return Ok(());
#[track_caller]
fn assert_unreachable<T>(err: Result<T>) {
let err = match err {
Ok(_) => panic!("expected an error"),
Err(e) => e,
};
assert_eq!(
err.downcast::<Trap>().unwrap(),
Trap::UnreachableCodeReached
);
}
#[track_caller]
fn assert_poisoned<T>(err: Result<T>) {
let err = match err {
Ok(_) => panic!("expected an error"),
Err(e) => e,
};
assert_eq!(
err.downcast_ref::<Trap>(),
Some(&Trap::CannotEnterComponent),
"{err}",
);
}
}
#[test]
fn run_export_with_internal_adapter() -> Result<()> {
let component = r#"
(component
(type $t (func (param "a" u32) (result u32)))
(component $a
(core module $m
(func (export "add-five") (param i32) (result i32)
local.get 0
i32.const 5
i32.add)
)
(core instance $m (instantiate $m))
(func (export "add-five") (type $t) (canon lift (core func $m "add-five")))
)
(component $b
(import "interface-v1" (instance $i
(export "add-five" (func (type $t)))))
(core module $m
(func $add-five (import "interface-0.1.0" "add-five") (param i32) (result i32))
(func) ;; causes index out of bounds
(func (export "run") (result i32) i32.const 0 call $add-five)
)
(core func $add-five (canon lower (func $i "add-five")))
(core instance $i (instantiate 0
(with "interface-0.1.0" (instance
(export "add-five" (func $add-five))
))
))
(func (result u32) (canon lift (core func $i "run")))
(export "run" (func 1))
)
(instance $a (instantiate $a))
(instance $b (instantiate $b (with "interface-v1" (instance $a))))
(export "run" (func $b "run"))
)
"#;
let engine = super::engine();
let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ());
let linker = Linker::new(&engine);
let instance = linker.instantiate(&mut store, &component)?;
let run = instance.get_typed_func::<(), (u32,)>(&mut store, "run")?;
assert_eq!(run.call(&mut store, ())?, (5,));
Ok(())
}
enum RecurseKind {
AThenA,
AThenB,
AThenBThenA,
}
#[test]
fn recurse() -> Result<()> {
test_recurse(RecurseKind::AThenB)
}
#[test]
fn recurse_trap() -> Result<()> {
let error = test_recurse(RecurseKind::AThenA).unwrap_err();
assert_eq!(error.downcast::<Trap>()?, Trap::CannotEnterComponent);
Ok(())
}
#[test]
fn recurse_more_trap() -> Result<()> {
let error = test_recurse(RecurseKind::AThenBThenA).unwrap_err();
assert_eq!(error.downcast::<Trap>()?, Trap::CannotEnterComponent);
Ok(())
}
fn test_recurse(kind: RecurseKind) -> Result<()> {
#[derive(Default)]
struct Ctx {
instances: Vec<Arc<Instance>>,
}
let component = r#"
(component
(import "import" (func $import))
(core func $import (canon lower (func $import)))
(core module $m
(func $import (import "" "import"))
(func (export "export") call $import)
)
(core instance $m (instantiate $m (with "" (instance
(export "import" (func $import))
))))
(func (export "export") (canon lift (core func $m "export")))
)
"#;
let engine = super::engine();
let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, Ctx::default());
let mut linker = Linker::<Ctx>::new(&engine);
linker.root().func_wrap("import", |mut store, (): ()| {
if let Some(instance) = store.data_mut().instances.pop() {
let run = instance.get_typed_func::<(), ()>(&mut store, "export")?;
run.call(&mut store, ())?;
store.data_mut().instances.push(instance);
}
Ok(())
})?;
let instance = Arc::new(linker.instantiate(&mut store, &component)?);
let instance = match kind {
RecurseKind::AThenA => {
store.data_mut().instances.push(instance.clone());
instance
}
RecurseKind::AThenB => {
let other = Arc::new(linker.instantiate(&mut store, &component)?);
store.data_mut().instances.push(other);
instance
}
RecurseKind::AThenBThenA => {
store.data_mut().instances.push(instance.clone());
let other = Arc::new(linker.instantiate(&mut store, &component)?);
store.data_mut().instances.push(other);
instance
}
};
let export = instance.get_typed_func::<(), ()>(&mut store, "export")?;
export.call(&mut store, ())?;
Ok(())
}
#[tokio::test]
async fn thread_index_via_instantiation_sync() -> Result<()> {
thread_index_via_instantiation(ApiStyle::Sync).await
}
#[tokio::test]
async fn thread_index_via_instantiation_async() -> Result<()> {
thread_index_via_instantiation(ApiStyle::Async).await
}
async fn thread_index_via_instantiation(style: ApiStyle) -> Result<()> {
let component = r#"
(component
(core module $m
(import "" "thread.index" (func $thread-index (result i32)))
(func $start
(if (i32.eqz (call $thread-index)) (then unreachable))
)
(start $start)
)
(core func $thread-index (canon thread.index))
(core instance $m (instantiate $m (with "" (instance
(export "thread.index" (func $thread-index))
))))
)
"#;
let engine = Engine::new(&style.config())?;
let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ());
let linker = Linker::new(&engine);
style.instantiate(&mut store, &linker, &component).await?;
Ok(())
}
#[tokio::test]
async fn thread_index_via_call_sync() -> Result<()> {
thread_index_via_call(ApiStyle::Sync).await
}
#[tokio::test]
async fn thread_index_via_call_async() -> Result<()> {
thread_index_via_call(ApiStyle::Async).await
}
#[tokio::test]
async fn thread_index_via_call_concurrent() -> Result<()> {
thread_index_via_call(ApiStyle::Concurrent).await
}
async fn thread_index_via_call(style: ApiStyle) -> Result<()> {
let component = r#"
(component
(core module $m
(import "" "thread.index" (func $thread-index (result i32)))
(func (export "run")
(if (i32.eqz (call $thread-index)) (then unreachable))
)
)
(core func $thread-index (canon thread.index))
(core instance $m (instantiate $m (with "" (instance
(export "thread.index" (func $thread-index))
))))
(func (export "run") (canon lift (core func $m "run")))
)
"#;
let engine = Engine::new(&style.config())?;
let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ());
let linker = Linker::new(&engine);
let instance = style.instantiate(&mut store, &linker, &component).await?;
let run = instance.get_typed_func::<(), ()>(&mut store, "run")?;
style.call(&mut store, run, ()).await?;
Ok(())
}
#[tokio::test]
async fn thread_index_via_post_return_sync() -> Result<()> {
thread_index_via_post_return(ApiStyle::Sync).await
}
#[tokio::test]
async fn thread_index_via_post_return_async() -> Result<()> {
thread_index_via_post_return(ApiStyle::Async).await
}
#[tokio::test]
async fn thread_index_via_post_return_concurrent() -> Result<()> {
thread_index_via_post_return(ApiStyle::Concurrent).await
}
async fn thread_index_via_post_return(style: ApiStyle) -> Result<()> {
let component = r#"
(component
(core module $m
(import "" "thread.index" (func $thread-index (result i32)))
(global $index (mut i32) (i32.const 0))
(func (export "run")
(global.set $index (call $thread-index))
(if (i32.eqz (global.get $index)) (then unreachable))
)
(func (export "run-post-return")
(local $index i32)
(local.set $index (call $thread-index))
(if (i32.eqz (local.get $index)) (then unreachable))
(if (i32.ne (local.get $index) (global.get $index)) (then unreachable))
)
)
(core func $thread-index (canon thread.index))
(core instance $m (instantiate $m (with "" (instance
(export "thread.index" (func $thread-index))
))))
(func (export "run") (canon lift (core func $m "run") (post-return (func $m "run-post-return"))))
)
"#;
let engine = Engine::new(&style.config())?;
let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ());
let linker = Linker::new(&engine);
let instance = style.instantiate(&mut store, &linker, &component).await?;
let run = instance.get_typed_func::<(), ()>(&mut store, "run")?;
style.call(&mut store, run, ()).await?;
Ok(())
}
#[tokio::test]
async fn thread_index_via_cabi_realloc_sync() -> Result<()> {
thread_index_via_cabi_realloc(ApiStyle::Sync).await
}
#[tokio::test]
async fn thread_index_via_cabi_realloc_async() -> Result<()> {
thread_index_via_cabi_realloc(ApiStyle::Async).await
}
#[tokio::test]
async fn thread_index_via_cabi_realloc_concurrent() -> Result<()> {
thread_index_via_cabi_realloc(ApiStyle::Concurrent).await
}
async fn thread_index_via_cabi_realloc(style: ApiStyle) -> Result<()> {
let component = r#"
(component
(core module $m
(import "" "thread.index" (func $thread-index (result i32)))
(global $index (mut i32) (i32.const 0))
(memory (export "memory") 1)
(func (export "realloc") (param i32 i32 i32 i32) (result i32)
(global.set $index (call $thread-index))
(if (i32.eqz (global.get $index)) (then unreachable))
(i32.const 100)
)
(func (export "run") (param i32 i32)
(local $index i32)
(local.set $index (call $thread-index))
(if (i32.eqz (local.get $index)) (then unreachable))
(if (i32.ne (local.get $index) (global.get $index)) (then unreachable))
)
)
(core func $thread-index (canon thread.index))
(core instance $m (instantiate $m (with "" (instance
(export "thread.index" (func $thread-index))
))))
(func (export "run") (param "s" string) (canon lift
(core func $m "run")
(memory $m "memory")
(realloc (func $m "realloc"))
))
)
"#;
let engine = Engine::new(&style.config())?;
let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ());
let linker = Linker::new(&engine);
let instance = style.instantiate(&mut store, &linker, &component).await?;
let run = instance.get_typed_func::<(String,), ()>(&mut store, "run")?;
style.call(&mut store, run, ("hola".to_string(),)).await?;
Ok(())
}
#[tokio::test]
async fn thread_index_via_resource_drop_sync() -> Result<()> {
thread_index_via_resource_drop(ApiStyle::Sync).await
}
#[tokio::test]
async fn thread_index_via_resource_drop_async() -> Result<()> {
thread_index_via_resource_drop(ApiStyle::Async).await
}
#[tokio::test]
async fn thread_index_via_resource_drop_concurrent() -> Result<()> {
thread_index_via_resource_drop(ApiStyle::Concurrent).await
}
async fn thread_index_via_resource_drop(style: ApiStyle) -> Result<()> {
let component = r#"
(component
(core module $m
(import "" "thread.index" (func $thread-index (result i32)))
(func (export "dtor") (param i32)
(if (i32.eqz (call $thread-index)) (then unreachable))
)
)
(core func $thread-index (canon thread.index))
(core instance $m (instantiate $m (with "" (instance
(export "thread.index" (func $thread-index))
))))
(type $r (resource (rep i32) (dtor (func $m "dtor"))))
(core func $new (canon resource.new $r))
(core module $m2
(import "" "new" (func $new (param i32) (result i32)))
(func (export "new") (result i32)
(call $new (i32.const 100))
)
)
(core instance $m2 (instantiate $m2 (with "" (instance
(export "new" (func $new))
))))
(func $new (result (own $r)) (canon lift (core func $m2 "new")))
(component $c
(import "r" (type $r (sub resource)))
(import "new" (func $new (result (own $r))))
(export $r-export "r" (type $r))
(export "new" (func $new) (func (result (own $r-export))))
)
(instance $c (instantiate $c
(with "r" (type $r))
(with "new" (func $new))
))
(export "i" (instance $c))
)
"#;
let engine = Engine::new(&style.config())?;
let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ());
let linker = Linker::new(&engine);
let instance = style.instantiate(&mut store, &linker, &component).await?;
let instance_index = instance.get_export_index(&mut store, None, "i").unwrap();
let func_index = instance
.get_export_index(&mut store, Some(&instance_index), "new")
.unwrap();
let run = instance.get_typed_func::<(), (ResourceAny,)>(&mut store, &func_index)?;
let (resource,) = style.call(&mut store, run, ()).await?;
style.resource_drop(&mut store, resource).await?;
Ok(())
}
#[tokio::test]
async fn thread_index_via_guest_call_sync() -> Result<()> {
thread_index_via_guest_call(ApiStyle::Sync).await
}
#[tokio::test]
async fn thread_index_via_guest_call_async() -> Result<()> {
thread_index_via_guest_call(ApiStyle::Async).await
}
#[tokio::test]
async fn thread_index_via_guest_call_concurrent() -> Result<()> {
thread_index_via_guest_call(ApiStyle::Concurrent).await
}
async fn thread_index_via_guest_call(style: ApiStyle) -> Result<()> {
let component = r#"
(component
(component $c
(core module $m
(import "" "thread.index" (func $thread-index (result i32)))
(func (export "run") (result i32)
(call $thread-index)
)
)
(core func $thread-index (canon thread.index))
(core instance $m (instantiate $m (with "" (instance
(export "thread.index" (func $thread-index))
))))
(func (export "run") (result u32) (canon lift (core func $m "run")))
)
(instance $c (instantiate $c))
(component $d
(import "c" (instance $c
(export "run" (func (result u32)))
))
(core func $run (canon lower (func $c "run")))
(core module $m
(import "" "thread.index" (func $thread-index (result i32)))
(import "" "run" (func $run (result i32)))
(func (export "run")
(local $mine i32)
(local $theirs i32)
(local.set $mine (call $thread-index))
(if (i32.eqz (local.get $mine)) (then unreachable))
(local.set $theirs (call $run))
(if (i32.eqz (local.get $theirs)) (then unreachable))
)
)
(core func $thread-index (canon thread.index))
(core instance $m (instantiate $m (with "" (instance
(export "thread.index" (func $thread-index))
(export "run" (func $run))
))))
(func (export "run") (canon lift (core func $m "run")))
)
(instance $d (instantiate $d (with "c" (instance $c))))
(func (export "run") (alias export $d "run"))
)
"#;
let engine = Engine::new(&style.config())?;
let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ());
let linker = Linker::new(&engine);
let instance = style.instantiate(&mut store, &linker, &component).await?;
let run = instance.get_typed_func::<(), ()>(&mut store, "run")?;
style.call(&mut store, run, ()).await?;
Ok(())
}
fn with_new_instance<T>(
engine: &Engine,
component: &Component,
fun: impl Fn(&mut Store<()>, Instance) -> wasmtime::Result<T>,
) -> wasmtime::Result<T> {
let mut store = Store::new(engine, ());
let instance = Linker::new(engine).instantiate(&mut store, component)?;
fun(&mut store, instance)
}
#[tokio::test]
async fn drop_call_async_future() -> Result<()> {
let component = r#"
(component
(import "foo" (func $f))
(core module $m
(func $f (import "" "foo"))
(func (export "foo") call $f)
)
(core func $f (canon lower (func $f)))
(core instance $m (instantiate $m (with "" (instance
(export "foo" (func $f))
))))
(func (export "foo") (canon lift (core func $m "foo")))
)
"#;
let engine = &Engine::new(&Config::new())?;
let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ());
let mut linker = Linker::new(&engine);
linker.root().func_wrap_async("foo", |_, _: ()| {
Box::new(async {
tokio::task::yield_now().await;
Ok(())
})
})?;
let instance = linker.instantiate_async(&mut store, &component).await?;
let foo = instance.get_typed_func::<(), ()>(&mut store, "foo")?;
for _ in 0..5 {
let mut future = std::pin::pin!(foo.call_async(&mut store, ()));
if let std::task::Poll::Ready(result) =
std::future::poll_fn(|cx| std::task::Poll::Ready(future.as_mut().poll(cx))).await
{
_ = result;
}
}
Ok(())
}
#[test]
fn host_call_with_concurrency_disabled() -> Result<()> {
let mut config = Config::default();
config.concurrency_support(false);
struct MyResource;
let engine = Engine::new(&config)?;
let mut store = Store::new(&engine, ());
let mut linker = Linker::<()>::new(&engine);
linker
.root()
.resource("r", ResourceType::host::<MyResource>(), |_, _| Ok(()))?;
let f_called = Arc::new(AtomicBool::new(false));
linker.root().func_wrap("f", {
let f_called = f_called.clone();
move |_ctx, _: (Resource<MyResource>,)| -> Result<()> {
f_called.store(true, SeqCst);
Ok(())
}
})?;
let component = Component::new(
&engine,
r#"
(component
(import "r" (type $r (sub resource)))
(import "f" (func $f (param "r" (borrow $r))))
(core func $f' (canon lower (func $f)))
(core func $drop (canon resource.drop $r))
(core module $m
(import "" "f" (func $f (param i32)))
(import "" "drop" (func $drop (param i32)))
(func (export "g") (param i32)
(call $f (local.get 0))
(call $drop (local.get 0))
)
)
(core instance $i (instantiate $m (with
"" (instance
(export "f" (func $f'))
(export "drop" (func $drop))
)
)))
(func (export "g") (param "r" (borrow $r))
(canon lift (core func $i "g"))
)
)
"#
.as_bytes(),
)?;
let instance = linker.instantiate(&mut store, &component)?;
let g = instance.get_typed_func::<(&Resource<MyResource>,), ()>(&mut store, "g")?;
let resource = Resource::new_own(100);
g.call(&mut store, (&resource,))?;
assert!(f_called.load(SeqCst));
Ok(())
}