#[cfg(feature = "sys")] pub mod reference_types { use anyhow::Result; use macro_wasmer_universal_test::universal_test; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; #[cfg(feature = "js")] use wasm_bindgen_test::*; use wasmer::*; #[universal_test] fn func_ref_passed_and_returned() -> Result<()> { let mut store = Store::default(); let wat = r#"(module (import "env" "func_ref_identity" (func (param funcref) (result funcref))) (type $ret_i32_ty (func (result i32))) (table $table (export "table") 2 2 funcref) (func (export "run") (param) (result funcref) (call 0 (ref.null func))) (func (export "call_set_value") (param $fr funcref) (result i32) (table.set $table (i32.const 0) (local.get $fr)) (call_indirect $table (type $ret_i32_ty) (i32.const 0))) )"#; let module = Module::new(&store, wat)?; #[derive(Clone, Debug)] pub struct Env(Arc); let env = Env(Arc::new(AtomicBool::new(false))); let env = FunctionEnv::new(&mut store, env); let imports = imports! { "env" => { "func_ref_identity" => Function::new_with_env(&mut store, &env, FunctionType::new([Type::FuncRef], [Type::FuncRef]), |_env: FunctionEnvMut, values: &[Value]| -> Result, _> { Ok(vec![values[0].clone()]) }) }, }; let instance = Instance::new(&mut store, &module, &imports)?; let f: &Function = instance.exports.get_function("run")?; let results = f.call(&mut store, &[]).unwrap(); if let Value::FuncRef(fr) = &results[0] { assert!(fr.is_none()); } else { panic!("funcref not found!"); } let func_to_call = Function::new_typed_with_env(&mut store, &env, |env: FunctionEnvMut| -> i32 { env.data().0.store(true, Ordering::SeqCst); 343 }); let call_set_value: &Function = instance.exports.get_function("call_set_value")?; let results: Box<[Value]> = call_set_value.call(&mut store, &[Value::FuncRef(Some(func_to_call))])?; assert!(env.as_ref(&store.as_store_ref()).0.load(Ordering::SeqCst)); assert_eq!(&*results, &[Value::I32(343)]); Ok(()) } #[universal_test] fn func_ref_passed_and_called() -> Result<()> { let mut store = Store::default(); let wat = r#"(module (func $func_ref_call (import "env" "func_ref_call") (param funcref) (result i32)) (type $ret_i32_ty (func (result i32))) (table $table (export "table") 2 2 funcref) (func $product (param $x i32) (param $y i32) (result i32) (i32.mul (local.get $x) (local.get $y))) ;; TODO: figure out exactly why this statement is needed (elem declare func $product) (func (export "call_set_value") (param $fr funcref) (result i32) (table.set $table (i32.const 0) (local.get $fr)) (call_indirect $table (type $ret_i32_ty) (i32.const 0))) (func (export "call_func") (param $fr funcref) (result i32) (call $func_ref_call (local.get $fr))) (func (export "call_host_func_with_wasm_func") (result i32) (call $func_ref_call (ref.func $product))) )"#; let module = Module::new(&store, wat)?; let env = FunctionEnv::new(&mut store, ()); fn func_ref_call( mut env: FunctionEnvMut<()>, values: &[Value], ) -> Result, RuntimeError> { // TODO: look into `Box<[Value]>` being returned breakage let f = values[0].unwrap_funcref().as_ref().unwrap(); let f: TypedFunction<(i32, i32), i32> = f.typed(&env)?; Ok(vec![Value::I32(f.call(&mut env, 7, 9)?)]) } let imports = imports! { "env" => { "func_ref_call" => Function::new_with_env( &mut store, &env, FunctionType::new([Type::FuncRef], [Type::I32]), func_ref_call ), // "func_ref_call_native" => Function::new_native(&mut store, |f: Function| -> Result { // let f: TypedFunction::<(i32, i32), i32> = f.typed(&mut store)?; // f.call(&mut store, 7, 9) // }) }, }; let instance = Instance::new(&mut store, &module, &imports)?; { fn sum(a: i32, b: i32) -> i32 { a + b } let sum_func = Function::new_typed(&mut store, sum); let call_func: &Function = instance.exports.get_function("call_func")?; let result = call_func.call(&mut store, &[Value::FuncRef(Some(sum_func))])?; assert_eq!(result[0].unwrap_i32(), 16); } { let f: TypedFunction<(), i32> = instance .exports .get_typed_function(&store, "call_host_func_with_wasm_func")?; let result = f.call(&mut store)?; assert_eq!(result, 63); } Ok(()) } #[macro_wasmer_universal_test::universal_test] fn extern_ref_passed_and_returned() -> Result<()> { use std::collections::HashMap; let mut store = Store::default(); let wat = r#"(module (func $extern_ref_identity (import "env" "extern_ref_identity") (param externref) (result externref)) (func $extern_ref_identity_native (import "env" "extern_ref_identity_native") (param externref) (result externref)) (func $get_new_extern_ref (import "env" "get_new_extern_ref") (result externref)) (func $get_new_extern_ref_native (import "env" "get_new_extern_ref_native") (result externref)) (func (export "run") (param) (result externref) (call $extern_ref_identity (ref.null extern))) (func (export "run_native") (param) (result externref) (call $extern_ref_identity_native (ref.null extern))) (func (export "get_hashmap") (param) (result externref) (call $get_new_extern_ref)) (func (export "get_hashmap_native") (param) (result externref) (call $get_new_extern_ref_native)) )"#; let module = Module::new(&store, wat)?; let env = FunctionEnv::new(&mut store, ()); let imports = imports! { "env" => { "extern_ref_identity" => Function::new_with_env(&mut store, &env, FunctionType::new([Type::ExternRef], [Type::ExternRef]), |_env, values| -> Result, _> { Ok(vec![values[0].clone()]) }), "extern_ref_identity_native" => Function::new_typed(&mut store, |er: Option| -> Option { er }), "get_new_extern_ref" => Function::new_with_env(&mut store, &env, FunctionType::new([], [Type::ExternRef]), |mut env, _| -> Result, _> { let inner = [("hello".to_string(), "world".to_string()), ("color".to_string(), "orange".to_string())] .iter() .cloned() .collect::>(); let new_extern_ref = ExternRef::new(&mut env, inner); Ok(vec![Value::ExternRef(Some(new_extern_ref))]) }), "get_new_extern_ref_native" => Function::new_typed_with_env(&mut store, &env, |mut env: FunctionEnvMut<()>| -> Option { let inner = [("hello".to_string(), "world".to_string()), ("color".to_string(), "orange".to_string())] .iter() .cloned() .collect::>(); Some(ExternRef::new(&mut env.as_store_mut(), inner)) }) }, }; let instance = Instance::new(&mut store, &module, &imports)?; for run in &["run", "run_native"] { let f: &Function = instance.exports.get_function(run)?; let results = f.call(&mut store, &[]).unwrap(); if let Value::ExternRef(er) = &results[0] { assert!(er.is_none()); } else { panic!("result is not an extern ref!"); } let f: TypedFunction<(), Option> = instance.exports.get_typed_function(&store, run)?; let result: Option = f.call(&mut store)?; assert!(result.is_none()); } for get_hashmap in &["get_hashmap", "get_hashmap_native"] { let f: &Function = instance.exports.get_function(get_hashmap)?; let results = f.call(&mut store, &[]).unwrap(); if let Value::ExternRef(er) = &results[0] { let inner: &HashMap = er.as_ref().unwrap().downcast(&store).unwrap(); assert_eq!(inner["hello"], "world"); assert_eq!(inner["color"], "orange"); } else { panic!("result is not an extern ref!"); } let f: TypedFunction<(), Option> = instance.exports.get_typed_function(&store, get_hashmap)?; let result: Option = f.call(&mut store)?; let inner: &HashMap = result.unwrap().downcast(&store).unwrap(); assert_eq!(inner["hello"], "world"); assert_eq!(inner["color"], "orange"); } Ok(()) } #[universal_test] fn extern_ref_ref_counting_basic() -> Result<()> { let mut store = Store::default(); let wat = r#"(module (func (export "drop") (param $er externref) (result) (drop (local.get $er))) )"#; let module = Module::new(&store, wat)?; let instance = Instance::new(&mut store, &module, &imports! {})?; let f: TypedFunction, ()> = instance.exports.get_typed_function(&store, "drop")?; let er = ExternRef::new(&mut store, 3u32); f.call(&mut store, Some(er.clone()))?; let tmp: Option<&u32> = er.downcast(&store); assert_eq!(tmp.unwrap(), &3); Ok(()) } #[universal_test] fn refs_in_globals() -> Result<()> { let mut store = Store::default(); let wat = r#"(module (global $er_global (export "er_global") (mut externref) (ref.null extern)) (global $fr_global (export "fr_global") (mut funcref) (ref.null func)) (global $fr_immutable_global (export "fr_immutable_global") funcref (ref.func $hello)) (func $hello (param) (result i32) (i32.const 73)) )"#; let module = Module::new(&store, wat)?; let instance = Instance::new(&mut store, &module, &imports! {})?; { let er_global: &Global = instance.exports.get_global("er_global")?; if let Value::ExternRef(er) = er_global.get(&mut store) { assert!(er.is_none()); } else { panic!("Did not find extern ref in the global"); } let extref = Some(ExternRef::new(&mut store, 3u32)); er_global.set(&mut store, Value::ExternRef(extref))?; if let Value::ExternRef(er) = er_global.get(&mut store) { let tmp: Option<&u32> = er.unwrap().downcast(&store); assert_eq!(tmp.unwrap(), &3); } else { panic!("Did not find extern ref in the global"); } } { let fr_global: &Global = instance.exports.get_global("fr_immutable_global")?; if let Value::FuncRef(Some(f)) = fr_global.get(&mut store) { let native_func: TypedFunction<(), u32> = f.typed(&store)?; assert_eq!(native_func.call(&mut store)?, 73); } else { panic!("Did not find non-null func ref in the global"); } } { let fr_global: &Global = instance.exports.get_global("fr_global")?; if let Value::FuncRef(None) = fr_global.get(&mut store) { } else { panic!("Did not find a null func ref in the global"); } let f = Function::new_typed(&mut store, |arg1: i32, arg2: i32| -> i32 { arg1 + arg2 }); fr_global.set(&mut store, Value::FuncRef(Some(f)))?; if let Value::FuncRef(Some(f)) = fr_global.get(&mut store) { let native: TypedFunction<(i32, i32), i32> = f.typed(&store)?; assert_eq!(native.call(&mut store, 5, 7)?, 12); } else { panic!("Did not find extern ref in the global"); } } Ok(()) } #[universal_test] fn extern_ref_ref_counting_table_basic() -> Result<()> { let mut store = Store::default(); let wat = r#"(module (global $global (export "global") (mut externref) (ref.null extern)) (table $table (export "table") 4 4 externref) (func $insert (param $er externref) (param $idx i32) (table.set $table (local.get $idx) (local.get $er))) (func $intermediate (param $er externref) (param $idx i32) (call $insert (local.get $er) (local.get $idx))) (func $insert_into_table (export "insert_into_table") (param $er externref) (param $idx i32) (result externref) (call $intermediate (local.get $er) (local.get $idx)) (local.get $er)) )"#; let module = Module::new(&store, wat)?; let instance = Instance::new(&mut store, &module, &imports! {})?; let f: TypedFunction<(Option, i32), Option> = instance .exports .get_typed_function(&store, "insert_into_table")?; let er = ExternRef::new(&mut store, 3usize); let er = f.call(&mut store, Some(er), 1)?; assert!(er.is_some()); let table: &Table = instance.exports.get_table("table")?; { let er2 = table.get(&mut store, 1).unwrap(); let er2 = er2.externref().unwrap(); assert!(er2.is_some()); } assert!(er.is_some()); table.set(&mut store, 1, Value::ExternRef(None))?; assert!(er.is_some()); Ok(()) } #[universal_test] fn extern_ref_ref_counting_global_basic() -> Result<()> { let mut store = Store::default(); let wat = r#"(module (global $global (export "global") (mut externref) (ref.null extern)) (func $get_from_global (export "get_from_global") (result externref) (drop (global.get $global)) (global.get $global)) )"#; let module = Module::new(&store, wat)?; let instance = Instance::new(&mut store, &module, &imports! {})?; let global: &Global = instance.exports.get_global("global")?; { let er = ExternRef::new(&mut store, 3usize); global.set(&mut store, Value::ExternRef(Some(er)))?; } let get_from_global: TypedFunction<(), Option> = instance .exports .get_typed_function(&store, "get_from_global")?; let er = get_from_global.call(&mut store)?; assert!(er.is_some()); global.set(&mut store, Value::ExternRef(None))?; assert!(er.is_some()); Ok(()) } #[universal_test] fn extern_ref_ref_counting_traps() -> Result<()> { let mut store = Store::default(); let wat = r#"(module (func $pass_er (export "pass_extern_ref") (param externref) (local.get 0) (unreachable)) )"#; let module = Module::new(&store, wat)?; let instance = Instance::new(&mut store, &module, &imports! {})?; let pass_extern_ref: TypedFunction, ()> = instance .exports .get_typed_function(&store, "pass_extern_ref")?; let er = ExternRef::new(&mut store, 3usize); let result = pass_extern_ref.call(&mut store, Some(er)); assert!(result.is_err()); Ok(()) } #[universal_test] fn extern_ref_ref_counting_table_instructions() -> Result<()> { let mut store = Store::default(); let wat = r#"(module (table $table1 (export "table1") 2 12 externref) (table $table2 (export "table2") 6 12 externref) (func $grow_table_with_ref (export "grow_table_with_ref") (param $er externref) (param $size i32) (result i32) (table.grow $table1 (local.get $er) (local.get $size))) (func $fill_table_with_ref (export "fill_table_with_ref") (param $er externref) (param $start i32) (param $end i32) (table.fill $table1 (local.get $start) (local.get $er) (local.get $end))) (func $copy_into_table2 (export "copy_into_table2") (table.copy $table2 $table1 (i32.const 0) (i32.const 0) (i32.const 4))) )"#; let module = Module::new(&store, wat)?; let instance = Instance::new(&mut store, &module, &imports! {})?; let grow_table_with_ref: TypedFunction<(Option, i32), i32> = instance .exports .get_typed_function(&store, "grow_table_with_ref")?; let fill_table_with_ref: TypedFunction<(Option, i32, i32), ()> = instance .exports .get_typed_function(&store, "fill_table_with_ref")?; let copy_into_table2: TypedFunction<(), ()> = instance .exports .get_typed_function(&store, "copy_into_table2")?; let table1: &Table = instance.exports.get_table("table1")?; let table2: &Table = instance.exports.get_table("table2")?; let er1 = ExternRef::new(&mut store, 3usize); let er2 = ExternRef::new(&mut store, 5usize); let er3 = ExternRef::new(&mut store, 7usize); { let result = grow_table_with_ref.call(&mut store, Some(er1.clone()), 0)?; assert_eq!(result, 2); let result = grow_table_with_ref.call(&mut store, Some(er1.clone()), 10_000)?; assert_eq!(result, -1); let result = grow_table_with_ref.call(&mut store, Some(er1), 8)?; assert_eq!(result, 2); for i in 2..10 { let v = table1.get(&mut store, i); let e = v.as_ref().unwrap().unwrap_externref(); let e_val: Option<&usize> = e.as_ref().unwrap().downcast(&store); assert_eq!(*e_val.unwrap(), 3); } } { fill_table_with_ref.call(&mut store, Some(er2), 0, 2)?; } { table2.set(&mut store, 0, Value::ExternRef(Some(er3.clone())))?; table2.set(&mut store, 1, Value::ExternRef(Some(er3.clone())))?; table2.set(&mut store, 2, Value::ExternRef(Some(er3.clone())))?; table2.set(&mut store, 3, Value::ExternRef(Some(er3.clone())))?; table2.set(&mut store, 4, Value::ExternRef(Some(er3)))?; } { copy_into_table2.call(&mut store)?; for i in 1..5 { let v = table2.get(&mut store, i); let e = v.as_ref().unwrap().unwrap_externref(); let value: &usize = e.as_ref().unwrap().downcast(&store).unwrap(); match i { 0 | 1 => assert_eq!(*value, 5), 4 => assert_eq!(*value, 7), _ => assert_eq!(*value, 3), } } } { for i in 0..table1.size(&store) { table1.set(&mut store, i, Value::ExternRef(None))?; } for i in 0..table2.size(&store) { table2.set(&mut store, i, Value::ExternRef(None))?; } } Ok(()) } }