use wamrx::{
Engine, Error, FuncType, GlobalType, InstanceConfig, Linker, Module, Mutability, Val, ValType,
};
fn instantiate(wat: &str) -> wamrx::Instance {
let engine = Engine::new().expect("engine init");
let wasm = wat::parse_str(wat).expect("wat parse");
let module = Module::new(&engine, &wasm).expect("module load");
let linker = Linker::new(&engine);
linker.instantiate(module).expect("instantiate")
}
#[test]
fn call_exported_add_i32() {
let instance = instantiate(
r#"(module
(func (export "add") (param i32 i32) (result i32)
local.get 0 local.get 1 i32.add))"#,
);
let mut results = [Val::I32(0)];
instance
.get_func("add")
.unwrap()
.call(&[Val::I32(2), Val::I32(40)], &mut results)
.unwrap();
assert_eq!(results[0], Val::I32(42));
}
#[test]
fn call_roundtrips_all_numeric_types() {
let instance = instantiate(
r#"(module
(func (export "id_i32") (param i32) (result i32) local.get 0)
(func (export "id_i64") (param i64) (result i64) local.get 0)
(func (export "id_f32") (param f32) (result f32) local.get 0)
(func (export "id_f64") (param f64) (result f64) local.get 0))"#,
);
let mut r = [Val::I32(0)];
instance
.get_func("id_i32")
.unwrap()
.call(&[Val::I32(-123456)], &mut r)
.unwrap();
assert_eq!(r, [Val::I32(-123456)]);
let mut r = [Val::I64(0)];
instance
.get_func("id_i64")
.unwrap()
.call(&[Val::I64(i64::MIN + 7)], &mut r)
.unwrap();
assert_eq!(r, [Val::I64(i64::MIN + 7)]);
let mut r = [Val::F32(0.0)];
instance
.get_func("id_f32")
.unwrap()
.call(&[Val::F32(-3.5)], &mut r)
.unwrap();
assert_eq!(r, [Val::F32(-3.5)]);
let mut r = [Val::F64(0.0)];
instance
.get_func("id_f64")
.unwrap()
.call(&[Val::F64(-123456.5)], &mut r)
.unwrap();
assert_eq!(r, [Val::F64(-123456.5)]);
}
#[test]
fn host_function_via_raw_trampoline() {
fn mul(params: &[Val], results: &mut [Val]) {
let a = match params[0] {
Val::I32(v) => v,
_ => unreachable!(),
};
let b = match params[1] {
Val::I32(v) => v,
_ => unreachable!(),
};
results[0] = Val::I32(a * b);
}
let engine = Engine::new().unwrap();
let mut linker = Linker::new(&engine);
linker
.define_func(
"env",
"mul",
FuncType::new([ValType::I32, ValType::I32], [ValType::I32]),
mul,
)
.unwrap();
let wasm = wat::parse_str(
r#"(module
(import "env" "mul" (func $mul (param i32 i32) (result i32)))
(func (export "run") (param i32 i32) (result i32)
local.get 0 local.get 1 call $mul))"#,
)
.unwrap();
let module = Module::new(&engine, &wasm).unwrap();
let instance = linker.instantiate(module).unwrap();
let mut results = [Val::I32(0)];
instance
.get_func("run")
.unwrap()
.call(&[Val::I32(6), Val::I32(7)], &mut results)
.unwrap();
assert_eq!(results[0], Val::I32(42));
}
#[test]
fn host_function_mixed_types_and_no_result() {
use std::cell::Cell;
std::thread_local! {
static SEEN: Cell<(i64, f64)> = const { Cell::new((0, 0.0)) };
}
fn observe(params: &[Val], _results: &mut [Val]) {
let i = match params[0] {
Val::I64(v) => v,
_ => unreachable!(),
};
let f = match params[1] {
Val::F64(v) => v,
_ => unreachable!(),
};
SEEN.with(|s| s.set((i, f)));
}
let engine = Engine::new().unwrap();
let mut linker = Linker::new(&engine);
linker
.define_func(
"env",
"observe",
FuncType::new([ValType::I64, ValType::F64], []),
observe,
)
.unwrap();
let wasm = wat::parse_str(
r#"(module
(import "env" "observe" (func $observe (param i64 f64)))
(func (export "run") (param i64 f64)
local.get 0 local.get 1 call $observe))"#,
)
.unwrap();
let module = Module::new(&engine, &wasm).unwrap();
let instance = linker.instantiate(module).unwrap();
instance
.get_func("run")
.unwrap()
.call(&[Val::I64(9_000_000_000), Val::F64(1.25)], &mut [])
.unwrap();
assert_eq!(SEEN.with(|s| s.get()), (9_000_000_000, 1.25));
}
#[test]
fn trap_surfaces_as_error() {
let instance = instantiate(r#"(module (func (export "boom") (result i32) unreachable))"#);
let err = instance
.get_func("boom")
.unwrap()
.call(&[], &mut [Val::I32(0)])
.unwrap_err();
assert!(matches!(err, Error::Trap(_)), "expected trap, got {err:?}");
}
#[test]
fn missing_export_is_error() {
let instance = instantiate(r#"(module (func (export "present")))"#);
let err = instance.get_func("absent").unwrap_err();
assert!(matches!(err, Error::FuncNotFound(_)));
}
#[test]
fn invalid_module_is_error() {
let engine = Engine::new().unwrap();
let err = Module::new(&engine, &[0, 1, 2, 3]).unwrap_err();
assert!(matches!(err, Error::ModuleLoad(_)));
}
#[test]
fn host_function_capturing_state() {
use std::cell::Cell;
use std::rc::Rc;
let sum = Rc::new(Cell::new(0i32));
let sum_in_host = Rc::clone(&sum);
let engine = Engine::new().unwrap();
let mut linker = Linker::new(&engine);
linker
.define_func(
"env",
"record",
FuncType::new([ValType::I32], []),
move |params, _results| {
let x = match params[0] {
Val::I32(v) => v,
_ => unreachable!(),
};
sum_in_host.set(sum_in_host.get() + x);
},
)
.unwrap();
let wasm = wat::parse_str(
r#"(module
(import "env" "record" (func $record (param i32)))
(func (export "run")
i32.const 10 call $record
i32.const 32 call $record))"#,
)
.unwrap();
let module = Module::new(&engine, &wasm).unwrap();
let instance = linker.instantiate(module).unwrap();
instance
.get_func("run")
.unwrap()
.call(&[], &mut [])
.unwrap();
assert_eq!(sum.get(), 42);
}
#[test]
fn func_ty_reports_signature() {
let instance = instantiate(
r#"(module
(func (export "f") (param i32 f64) (result i64)
local.get 0 i64.extend_i32_s))"#,
);
let func = instance.get_func("f").unwrap();
let ty = func.ty();
assert_eq!(ty.params(), &[ValType::I32, ValType::F64]);
assert_eq!(ty.results(), &[ValType::I64]);
assert_eq!(
ty,
FuncType::new([ValType::I32, ValType::F64], [ValType::I64])
);
}
#[test]
fn exported_globals_get_set_and_type() {
let instance = instantiate(
r#"(module
(global $g (export "g") (mut i32) (i32.const 7))
(global $c (export "c") i64 (i64.const 100))
(func (export "read_g") (result i32) global.get $g))"#,
);
let mut g = instance.get_global("g").unwrap();
assert_eq!(g.ty(), GlobalType::new(ValType::I32, Mutability::Mutable));
assert_eq!(g.get(), Val::I32(7));
g.set(Val::I32(42)).unwrap();
assert_eq!(g.get(), Val::I32(42));
let mut r = [Val::I32(0)];
instance
.get_func("read_g")
.unwrap()
.call(&[], &mut r)
.unwrap();
assert_eq!(r[0], Val::I32(42));
assert!(matches!(
g.set(Val::I64(1)),
Err(Error::TypeMismatch { .. })
));
let mut c = instance.get_global("c").unwrap();
assert_eq!(c.ty(), GlobalType::new(ValType::I64, Mutability::Const));
assert_eq!(c.get(), Val::I64(100));
assert_eq!(c.set(Val::I64(1)), Err(Error::GlobalImmutable));
assert!(matches!(
instance.get_global("nope"),
Err(Error::GlobalNotFound(_))
));
}
#[test]
fn instantiate_with_custom_config() {
let engine = Engine::new().unwrap();
let wasm =
wat::parse_str(r#"(module (func (export "answer") (result i32) i32.const 42))"#).unwrap();
let module = Module::new(&engine, &wasm).unwrap();
let mut config = InstanceConfig::new();
config
.exec_stack_size(1024 * 1024)
.aux_stack_size(32 * 1024)
.heap_size(0);
let linker = Linker::new(&engine);
let instance = linker.instantiate_with(module, &config).unwrap();
let mut results = [Val::I32(0)];
instance
.get_func("answer")
.unwrap()
.call(&[], &mut results)
.unwrap();
assert_eq!(results[0], Val::I32(42));
}