use super::gc_store; use wasmtime::*; use wasmtime_test_macros::wasmtime_test; #[test] fn struct_new_empty() -> Result<()> { let mut store = gc_store()?; let struct_ty = StructType::new(store.engine(), [])?; let pre = StructRefPre::new(&mut store, struct_ty); StructRef::new(&mut store, &pre, &[])?; Ok(()) } #[test] fn struct_new_with_fields() -> Result<()> { let mut store = gc_store()?; let struct_ty = StructType::new( store.engine(), [ FieldType::new(Mutability::Const, StorageType::I8), FieldType::new(Mutability::Const, StorageType::ValType(ValType::I32)), FieldType::new(Mutability::Var, StorageType::ValType(ValType::ANYREF)), ], )?; let pre = StructRefPre::new(&mut store, struct_ty); StructRef::new( &mut store, &pre, &[Val::I32(1), Val::I32(2), Val::null_any_ref()], )?; Ok(()) } #[test] fn struct_new_unrooted_field() -> Result<()> { let mut store = gc_store()?; let struct_ty = StructType::new( store.engine(), [FieldType::new( Mutability::Var, StorageType::ValType(ValType::ANYREF), )], )?; // Passing an unrooted `anyref` to `StructRef::new` results in an error. let anyref = { let mut scope = RootScope::new(&mut store); AnyRef::from_i31(&mut scope, I31::new_i32(1234).unwrap()) }; assert!(anyref.is_i31(&store).is_err()); let pre = StructRefPre::new(&mut store, struct_ty); assert!(StructRef::new(&mut store, &pre, &[anyref.into()]).is_err()); Ok(()) } #[test] #[should_panic = "wrong store"] fn struct_new_cross_store_field() { let mut store = gc_store().unwrap(); let struct_ty = StructType::new( store.engine(), [FieldType::new( Mutability::Var, StorageType::ValType(ValType::ANYREF), )], ) .unwrap(); let mut other_store = gc_store().unwrap(); let anyref = AnyRef::from_i31(&mut other_store, I31::new_i32(1234).unwrap()); let pre = StructRefPre::new(&mut store, struct_ty); // This should panic. let _ = StructRef::new(&mut store, &pre, &[anyref.into()]); } #[test] #[should_panic = "wrong store"] fn struct_new_cross_store_pre() { let mut store = gc_store().unwrap(); let struct_ty = StructType::new(store.engine(), []).unwrap(); let mut other_store = gc_store().unwrap(); let pre = StructRefPre::new(&mut other_store, struct_ty); // This should panic. let _ = StructRef::new(&mut store, &pre, &[]); } #[test] fn anyref_as_struct() -> Result<()> { let mut store = gc_store()?; let struct_ty = StructType::new( store.engine(), [FieldType::new(Mutability::Const, StorageType::I8)], )?; let pre = StructRefPre::new(&mut store, struct_ty.clone()); let s0 = StructRef::new(&mut store, &pre, &[Val::I32(42)])?; let anyref: Rooted = s0.into(); assert!(anyref.is_struct(&store)?); let s1 = anyref.as_struct(&store)?.unwrap(); assert_eq!(s1.field(&mut store, 0)?.unwrap_i32(), 42); assert!(Rooted::ref_eq(&store, &s0, &s1)?); let anyref: Rooted = AnyRef::from_i31(&mut store, I31::new_i32(42).unwrap()); assert!(!anyref.is_struct(&store)?); assert!(anyref.as_struct(&store)?.is_none()); Ok(()) } #[test] fn struct_field_simple() -> Result<()> { let mut store = gc_store()?; let struct_ty = StructType::new( store.engine(), [FieldType::new( Mutability::Var, StorageType::ValType(ValType::I32), )], )?; let pre = StructRefPre::new(&mut store, struct_ty); let s = StructRef::new(&mut store, &pre, &[Val::I32(1234)])?; let val = s.field(&mut store, 0)?; assert_eq!(val.unwrap_i32(), 1234); Ok(()) } #[test] fn struct_field_out_of_bounds() -> Result<()> { let mut store = gc_store()?; let struct_ty = StructType::new( store.engine(), [FieldType::new( Mutability::Var, StorageType::ValType(ValType::I32), )], )?; let pre = StructRefPre::new(&mut store, struct_ty); let s = StructRef::new(&mut store, &pre, &[Val::I32(1234)])?; assert!(s.field(&mut store, 1).is_err()); Ok(()) } #[test] fn struct_field_on_unrooted() -> Result<()> { let mut store = gc_store()?; let struct_ty = StructType::new( store.engine(), [FieldType::new( Mutability::Var, StorageType::ValType(ValType::I32), )], )?; let pre = StructRefPre::new(&mut store, struct_ty); let s = { let mut scope = RootScope::new(&mut store); StructRef::new(&mut scope, &pre, &[Val::I32(1234)])? }; // The root scope ended and unrooted `s`. assert!(s.field(&mut store, 0).is_err()); Ok(()) } #[test] fn struct_set_field_simple() -> Result<()> { let mut store = gc_store()?; let struct_ty = StructType::new( store.engine(), [FieldType::new( Mutability::Var, StorageType::ValType(ValType::I32), )], )?; let pre = StructRefPre::new(&mut store, struct_ty); let s = StructRef::new(&mut store, &pre, &[Val::I32(1234)])?; s.set_field(&mut store, 0, Val::I32(5678))?; let val = s.field(&mut store, 0)?; assert_eq!(val.unwrap_i32(), 5678); Ok(()) } #[test] fn struct_set_field_out_of_bounds() -> Result<()> { let mut store = gc_store()?; let struct_ty = StructType::new( store.engine(), [FieldType::new( Mutability::Var, StorageType::ValType(ValType::I32), )], )?; let pre = StructRefPre::new(&mut store, struct_ty); let s = StructRef::new(&mut store, &pre, &[Val::I32(1234)])?; assert!(s.set_field(&mut store, 1, Val::I32(1)).is_err()); Ok(()) } #[test] fn struct_set_field_on_unrooted() -> Result<()> { let mut store = gc_store()?; let struct_ty = StructType::new( store.engine(), [FieldType::new( Mutability::Var, StorageType::ValType(ValType::I32), )], )?; let pre = StructRefPre::new(&mut store, struct_ty); let s = { let mut scope = RootScope::new(&mut store); StructRef::new(&mut scope, &pre, &[Val::I32(1234)])? }; // The root scope ended and unrooted `s`. assert!(s.set_field(&mut store, 0, Val::I32(1)).is_err()); Ok(()) } #[test] fn struct_set_field_with_unrooted() -> Result<()> { let mut store = gc_store()?; let struct_ty = StructType::new( store.engine(), [FieldType::new( Mutability::Var, StorageType::ValType(ValType::ANYREF), )], )?; let pre = StructRefPre::new(&mut store, struct_ty); let s = StructRef::new(&mut store, &pre, &[Val::null_any_ref()])?; let anyref = { let mut scope = RootScope::new(&mut store); AnyRef::from_i31(&mut scope, I31::wrapping_i32(42)) }; // The root scope ended and `anyref` is unrooted. assert!(s.set_field(&mut store, 0, anyref.into()).is_err()); Ok(()) } #[test] fn struct_set_field_cross_store_value() -> Result<()> { let mut store = gc_store()?; let struct_ty = StructType::new( store.engine(), [FieldType::new( Mutability::Var, StorageType::ValType(ValType::EXTERNREF), )], )?; let pre = StructRefPre::new(&mut store, struct_ty); let s = StructRef::new(&mut store, &pre, &[Val::null_extern_ref()])?; let mut other_store = gc_store()?; let externref = ExternRef::new(&mut other_store, "blah")?; assert!(s.set_field(&mut store, 0, externref.into()).is_err()); Ok(()) } #[test] fn struct_set_field_immutable() -> Result<()> { let mut store = gc_store()?; let struct_ty = StructType::new( store.engine(), [FieldType::new( Mutability::Const, StorageType::ValType(ValType::I32), )], )?; let pre = StructRefPre::new(&mut store, struct_ty); let s = StructRef::new(&mut store, &pre, &[Val::I32(1234)])?; assert!(s.set_field(&mut store, 0, Val::I32(5678)).is_err()); Ok(()) } #[test] fn struct_set_field_wrong_type() -> Result<()> { let mut store = gc_store()?; let struct_ty = StructType::new( store.engine(), [FieldType::new( Mutability::Const, StorageType::ValType(ValType::I32), )], )?; let pre = StructRefPre::new(&mut store, struct_ty); let s = StructRef::new(&mut store, &pre, &[Val::I32(1234)])?; assert!(s.set_field(&mut store, 0, Val::I64(5678)).is_err()); Ok(()) } #[test] fn struct_ty() -> Result<()> { let mut store = gc_store()?; let struct_ty = StructType::new(store.engine(), [])?; let pre = StructRefPre::new(&mut store, struct_ty.clone()); let s = StructRef::new(&mut store, &pre, &[])?; assert!(StructType::eq(&struct_ty, &s.ty(&store)?)); Ok(()) } #[test] fn struct_ty_unrooted() -> Result<()> { let mut store = gc_store()?; let struct_ty = StructType::new(store.engine(), [])?; let pre = StructRefPre::new(&mut store, struct_ty); let s = { let mut scope = RootScope::new(&mut store); StructRef::new(&mut scope, &pre, &[])? }; // The root scope ended and `s` is unrooted. assert!(s.ty(&mut store).is_err()); Ok(()) } #[test] fn struct_fields_empty() -> Result<()> { let mut store = gc_store()?; let struct_ty = StructType::new(store.engine(), [])?; let pre = StructRefPre::new(&mut store, struct_ty.clone()); let s = StructRef::new(&mut store, &pre, &[])?; let fields = s.fields(&mut store)?; assert_eq!(fields.len(), 0); assert!(fields.collect::>().is_empty()); Ok(()) } #[test] fn struct_fields_non_empty() -> Result<()> { let mut store = gc_store()?; let struct_ty = StructType::new( store.engine(), [ FieldType::new(Mutability::Const, StorageType::I8), FieldType::new(Mutability::Var, StorageType::ValType(ValType::ANYREF)), ], )?; let pre = StructRefPre::new(&mut store, struct_ty.clone()); let s = StructRef::new(&mut store, &pre, &[Val::I32(36), Val::null_any_ref()])?; let mut fields = s.fields(&mut store)?; assert_eq!(fields.len(), 2); assert_eq!(fields.next().unwrap().unwrap_i32(), 36); assert!(fields.next().unwrap().unwrap_any_ref().is_none()); assert!(fields.next().is_none()); Ok(()) } #[test] fn struct_fields_unrooted() -> Result<()> { let mut store = gc_store()?; let struct_ty = StructType::new(store.engine(), [])?; let pre = StructRefPre::new(&mut store, struct_ty); let s = { let mut scope = RootScope::new(&mut store); StructRef::new(&mut scope, &pre, &[])? }; // The root scope ended and `s` is unrooted. assert!(s.fields(&mut store).is_err()); Ok(()) } #[test] #[cfg_attr(miri, ignore)] fn passing_structs_through_wasm_with_untyped_calls() -> Result<()> { let mut store = gc_store()?; let module = Module::new( store.engine(), r#" (module (type (struct (field i8))) (import "" "" (func $f (param (ref 0)) (result (ref 0)))) (func (export "run") (param (ref 0)) (result (ref 0)) (call $f (local.get 0)) ) ) "#, )?; let struct_ty = StructType::new( store.engine(), [FieldType::new(Mutability::Const, StorageType::I8)], )?; let ref_ty = RefType::new(false, HeapType::ConcreteStruct(struct_ty.clone())); let func_ty = FuncType::new(store.engine(), [ref_ty.clone().into()], [ref_ty.into()]); let func = Func::new(&mut store, func_ty, |mut caller, args, results| { let s = args[0].unwrap_any_ref().unwrap(); let s = s.unwrap_struct(&mut caller)?; assert_eq!(s.field(&mut caller, 0)?.unwrap_i32(), 42); results[0] = args[0]; Ok(()) }); let instance = Instance::new(&mut store, &module, &[func.into()])?; let run = instance.get_func(&mut store, "run").unwrap(); let pre = StructRefPre::new(&mut store, struct_ty.clone()); let s = StructRef::new(&mut store, &pre, &[Val::I32(42)])?; let mut results = vec![Val::null_any_ref()]; run.call(&mut store, &[s.into()], &mut results)?; let t = results[0].unwrap_any_ref().unwrap(); let t = t.unwrap_struct(&mut store)?; assert_eq!(t.field(&mut store, 0)?.unwrap_i32(), 42); assert!(Rooted::ref_eq(&store, &s, &t)?); Ok(()) } #[test] #[cfg_attr(miri, ignore)] fn passing_structs_through_wasm_with_typed_calls() -> Result<()> { let mut store = gc_store()?; let module = Module::new( store.engine(), r#" (module (type (struct (field i8))) (import "" "" (func $f (param (ref struct)) (result (ref struct)))) (func (export "run") (param (ref 0)) (result (ref struct)) (call $f (local.get 0)) ) ) "#, )?; let struct_ty = StructType::new( store.engine(), [FieldType::new(Mutability::Const, StorageType::I8)], )?; let func = Func::wrap( &mut store, |mut caller: Caller<()>, s: Rooted| -> Result> { assert_eq!(s.field(&mut caller, 0)?.unwrap_i32(), 42); Ok(s) }, ); let instance = Instance::new(&mut store, &module, &[func.into()])?; let run = instance.get_typed_func::, Rooted>(&mut store, "run")?; let pre = StructRefPre::new(&mut store, struct_ty.clone()); let s = StructRef::new(&mut store, &pre, &[Val::I32(42)])?; let t = run.call(&mut store, s)?; assert_eq!(t.field(&mut store, 0)?.unwrap_i32(), 42); assert!(Rooted::ref_eq(&store, &s, &t)?); Ok(()) } #[test] #[cfg_attr(miri, ignore)] fn host_sets_struct_global() -> Result<()> { let mut store = gc_store()?; let module = Module::new( store.engine(), r#" (module (type (struct (field i8))) (global $g (export "g") (mut (ref null 0)) (ref.null 0)) (func (export "f") (result (ref null 0)) global.get $g ) ) "#, )?; let instance = Instance::new(&mut store, &module, &[])?; let g = instance.get_global(&mut store, "g").unwrap(); let struct_ty = StructType::new( store.engine(), [FieldType::new(Mutability::Const, StorageType::I8)], )?; let pre = StructRefPre::new(&mut store, struct_ty.clone()); let s0 = StructRef::new(&mut store, &pre, &[Val::I32(42)])?; g.set(&mut store, s0.into())?; // Get the global from the host. let val = g.get(&mut store); let anyref = val.unwrap_anyref().expect("non-null"); let s1 = anyref.unwrap_struct(&store)?; assert_eq!(s1.field(&mut store, 0)?.unwrap_i32(), 42); assert!(Rooted::ref_eq(&store, &s0, &s1)?); // Get the global from the guest. let f = instance.get_typed_func::<(), Option>>(&mut store, "f")?; let s2 = f.call(&mut store, ())?.expect("non-null"); assert_eq!(s2.field(&mut store, 0)?.unwrap_i32(), 42); assert!(Rooted::ref_eq(&store, &s0, &s2)?); Ok(()) } #[test] #[cfg_attr(miri, ignore)] fn wasm_sets_struct_global() -> Result<()> { let mut store = gc_store()?; let module = Module::new( store.engine(), r#" (module (type (struct (field i8))) (global $g (export "g") (mut (ref null 0)) (ref.null 0)) (func (export "get") (result (ref null 0)) global.get $g ) (func (export "set") (param (ref null 0)) local.get 0 global.set $g ) ) "#, )?; let struct_ty = StructType::new( store.engine(), [FieldType::new(Mutability::Const, StorageType::I8)], )?; let pre = StructRefPre::new(&mut store, struct_ty.clone()); let s0 = StructRef::new(&mut store, &pre, &[Val::I32(42)])?; let instance = Instance::new(&mut store, &module, &[])?; let set = instance.get_func(&mut store, "set").unwrap(); set.call(&mut store, &[s0.into()], &mut [])?; // Get the global from the host. let g = instance.get_global(&mut store, "g").unwrap(); let val = g.get(&mut store); let anyref = val.unwrap_anyref().expect("non-null"); let s1 = anyref.unwrap_struct(&store)?; assert_eq!(s1.field(&mut store, 0)?.unwrap_i32(), 42); assert!(Rooted::ref_eq(&store, &s0, &s1)?); // Get the global from the guest. let f = instance.get_typed_func::<(), Option>>(&mut store, "get")?; let s2 = f.call(&mut store, ())?.expect("non-null"); assert_eq!(s2.field(&mut store, 0)?.unwrap_i32(), 42); assert!(Rooted::ref_eq(&store, &s0, &s2)?); Ok(()) } #[test] #[cfg_attr(miri, ignore)] fn host_sets_struct_in_table() -> Result<()> { let mut store = gc_store()?; let module = Module::new( store.engine(), r#" (module (type (struct (field i8))) (table $t (export "t") 1 1 (ref null 0) (ref.null 0)) (func (export "f") (result (ref null 0)) i32.const 0 table.get $t ) ) "#, )?; let instance = Instance::new(&mut store, &module, &[])?; let t = instance.get_table(&mut store, "t").unwrap(); let struct_ty = StructType::new( store.engine(), [FieldType::new(Mutability::Const, StorageType::I8)], )?; let pre = StructRefPre::new(&mut store, struct_ty.clone()); let s0 = StructRef::new(&mut store, &pre, &[Val::I32(42)])?; t.set(&mut store, 0, s0.into())?; // Get the global from the host. let val = t.get(&mut store, 0).expect("in bounds"); let anyref = val.unwrap_any().expect("non-null"); let s1 = anyref.unwrap_struct(&store)?; assert_eq!(s1.field(&mut store, 0)?.unwrap_i32(), 42); assert!(Rooted::ref_eq(&store, &s0, &s1)?); // Get the global from the guest. let f = instance.get_typed_func::<(), Option>>(&mut store, "f")?; let s2 = f.call(&mut store, ())?.expect("non-null"); assert_eq!(s2.field(&mut store, 0)?.unwrap_i32(), 42); assert!(Rooted::ref_eq(&store, &s0, &s2)?); Ok(()) } #[test] #[cfg_attr(miri, ignore)] fn wasm_sets_struct_in_table() -> Result<()> { let mut store = gc_store()?; let module = Module::new( store.engine(), r#" (module (type (struct (field i8))) (table $t (export "t") 1 1 (ref null 0) (ref.null 0)) (func (export "get") (result (ref null 0)) i32.const 0 table.get $t ) (func (export "set") (param (ref null 0)) i32.const 0 local.get 0 table.set $t ) ) "#, )?; let struct_ty = StructType::new( store.engine(), [FieldType::new(Mutability::Const, StorageType::I8)], )?; let pre = StructRefPre::new(&mut store, struct_ty.clone()); let s0 = StructRef::new(&mut store, &pre, &[Val::I32(42)])?; let instance = Instance::new(&mut store, &module, &[])?; let set = instance.get_func(&mut store, "set").unwrap(); set.call(&mut store, &[s0.into()], &mut [])?; // Get the global from the host. let t = instance.get_table(&mut store, "t").unwrap(); let val = t.get(&mut store, 0).expect("in bounds"); let anyref = val.unwrap_any().expect("non-null"); let s1 = anyref.unwrap_struct(&store)?; assert_eq!(s1.field(&mut store, 0)?.unwrap_i32(), 42); assert!(Rooted::ref_eq(&store, &s0, &s1)?); // Get the global from the guest. let f = instance.get_typed_func::<(), Option>>(&mut store, "get")?; let s2 = f.call(&mut store, ())?.expect("non-null"); assert_eq!(s2.field(&mut store, 0)?.unwrap_i32(), 42); assert!(Rooted::ref_eq(&store, &s0, &s2)?); Ok(()) } #[test] fn instantiate_with_struct_global() -> Result<()> { let mut store = gc_store()?; let module = Module::new( store.engine(), r#" (module (type (struct (field i8))) (import "" "" (global (ref null 0))) (export "g" (global 0)) ) "#, )?; let struct_ty = StructType::new( store.engine(), [FieldType::new(Mutability::Const, StorageType::I8)], )?; let global_ty = GlobalType::new( ValType::Ref(RefType::new( true, HeapType::ConcreteStruct(struct_ty.clone()), )), Mutability::Const, ); // Instantiate with a null-ref global. let g = Global::new(&mut store, global_ty.clone(), Val::AnyRef(None))?; let instance = Instance::new(&mut store, &module, &[g.into()])?; let g = instance.get_global(&mut store, "g").expect("export exists"); let val = g.get(&mut store); assert!(val.unwrap_anyref().is_none()); // Instantiate with a non-null-ref global. let pre = StructRefPre::new(&mut store, struct_ty); let s0 = StructRef::new(&mut store, &pre, &[Val::I32(42)])?; let g = Global::new(&mut store, global_ty, s0.into())?; let instance = Instance::new(&mut store, &module, &[g.into()])?; let g = instance.get_global(&mut store, "g").expect("export exists"); let val = g.get(&mut store); let anyref = val.unwrap_anyref().expect("non-null"); let s1 = anyref.unwrap_struct(&store)?; assert_eq!(s1.field(&mut store, 0)?.unwrap_i32(), 42); assert!(Rooted::ref_eq(&store, &s0, &s1)?); Ok(()) } #[test] fn can_put_funcrefs_in_structs() -> Result<()> { let mut store = gc_store()?; let struct_ty = StructType::new( store.engine(), [FieldType::new(Mutability::Var, RefType::FUNCREF.into())], )?; let f0 = Func::wrap(&mut store, |_caller: Caller<()>| -> u32 { 0x1234 }); let f1 = Func::wrap(&mut store, |_caller: Caller<()>| -> u32 { 0x5678 }); let pre = StructRefPre::new(&mut store, struct_ty.clone()); let s = StructRef::new(&mut store, &pre, &[f0.into()])?; let f = s.field(&mut store, 0)?; let f = f.unwrap_funcref().unwrap(); let f = f.typed::<(), u32>(&store)?; assert_eq!(f.call(&mut store, ())?, 0x1234); s.set_field(&mut store, 0, f1.into())?; let f = s.field(&mut store, 0)?; let f = f.unwrap_funcref().unwrap(); let f = f.typed::<(), u32>(&store)?; assert_eq!(f.call(&mut store, ())?, 0x5678); Ok(()) } #[test] #[cfg_attr(miri, ignore)] fn struct_ref_struct_in_same_rec_group_in_global() -> Result<()> { let mut store = gc_store()?; let module = Module::new( store.engine(), r#" (module (rec (type $b (struct)) (type $a (struct (field (ref $b)))) ) (global (ref $a) struct.new_default $b struct.new $a ) ) "#, )?; let _instance = Instance::new(&mut store, &module, &[])?; Ok(()) } #[wasmtime_test(wasm_features(function_references, gc))] #[cfg_attr(miri, ignore)] fn issue_9714(config: &mut Config) -> Result<()> { let engine = Engine::new(config)?; let module = Module::new( &engine, r#" (module (rec (type $a (struct)) (type $b (struct))) (rec (type $c (struct))) (func (export "fa") (result (ref null $a)) unreachable) (func (export "fb") (result (ref null $b)) unreachable) (func (export "fc") (result (ref null $c)) unreachable) ) "#, )?; let mut store = Store::new(&engine, ()); for exp in module.exports() { let res_ty = exp.ty().unwrap_func().result(0).unwrap(); let struct_ty = res_ty .unwrap_ref() .heap_type() .unwrap_concrete_struct() .clone(); let _ = StructRefPre::new(&mut store, struct_ty); } Ok(()) }