diff --git a/crates/wasmtime/src/runtime/types/matching.rs b/crates/wasmtime/src/runtime/types/matching.rs index a038c410f0d5..7be637b7a7f7 100644 --- a/crates/wasmtime/src/runtime/types/matching.rs +++ b/crates/wasmtime/src/runtime/types/matching.rs @@ -19,7 +19,7 @@ impl MatchCx<'_> { pub(crate) fn definition(&self, expected: &EntityType, actual: &DefinitionType) -> Result<()> { match expected { EntityType::Global(expected) => match actual { - DefinitionType::Global(actual) => global_ty(expected, actual), + DefinitionType::Global(actual) => global_ty(self.engine, expected, actual), _ => bail!("expected global, but found {}", actual.desc()), }, EntityType::Table(expected) => match actual { @@ -78,7 +78,7 @@ pub fn entity_ty(engine: &Engine, expected: &EntityType, actual: &EntityType) -> _ => bail!("expected memory found {}", entity_desc(actual)), }, EntityType::Global(expected) => match actual { - EntityType::Global(actual) => global_ty(expected, actual), + EntityType::Global(actual) => global_ty(engine, expected, actual), _ => bail!("expected global found {}", entity_desc(actual)), }, EntityType::Table(expected) => match actual { @@ -108,14 +108,14 @@ fn concrete_type_mismatch( anyhow!("{msg}: expected type `{expected}`, found type `{actual}`") } -fn global_ty(expected: &Global, actual: &Global) -> Result<()> { +fn global_ty(engine: &Engine, expected: &Global, actual: &Global) -> Result<()> { // Subtyping is only sound on immutable global // references. Therefore if either type is mutable we perform a // strict equality check on the types. if expected.mutability || actual.mutability { equal_ty(expected.wasm_ty, actual.wasm_ty, "global")?; } else { - match_ty(expected.wasm_ty, actual.wasm_ty, "global")?; + match_ty(engine, expected.wasm_ty, actual.wasm_ty, "global")?; } match_bool( expected.mutability, @@ -179,15 +179,22 @@ fn tag_ty(expected: &Tag, actual: &Tag) -> Result<()> { } } -fn match_heap(expected: WasmHeapType, actual: WasmHeapType, desc: &str) -> Result<()> { +fn match_heap( + engine: &Engine, + expected: WasmHeapType, + actual: WasmHeapType, + desc: &str, +) -> Result<()> { use WasmHeapType as H; let result = match (actual, expected) { - // TODO: Wasm GC introduces subtyping between function types, so it will - // no longer suffice to check whether canonicalized type IDs are equal. - (H::ConcreteArray(actual), H::ConcreteArray(expected)) => actual == expected, - (H::ConcreteFunc(actual), H::ConcreteFunc(expected)) => actual == expected, - (H::ConcreteStruct(actual), H::ConcreteStruct(expected)) => actual == expected, - (H::ConcreteCont(actual), H::ConcreteCont(expected)) => actual == expected, + (H::ConcreteArray(actual), H::ConcreteArray(expected)) + | (H::ConcreteFunc(actual), H::ConcreteFunc(expected)) + | (H::ConcreteStruct(actual), H::ConcreteStruct(expected)) + | (H::ConcreteCont(actual), H::ConcreteCont(expected)) => { + let actual = actual.unwrap_engine_type_index(); + let expected = expected.unwrap_engine_type_index(); + engine.signatures().is_subtype(actual, expected) + } (H::NoFunc, H::NoFunc) => true, (_, H::NoFunc) => false, @@ -266,9 +273,14 @@ fn match_heap(expected: WasmHeapType, actual: WasmHeapType, desc: &str) -> Resul } } -fn match_ref(expected: WasmRefType, actual: WasmRefType, desc: &str) -> Result<()> { +fn match_ref( + engine: &Engine, + expected: WasmRefType, + actual: WasmRefType, + desc: &str, +) -> Result<()> { if actual.nullable == expected.nullable || expected.nullable { - return match_heap(expected.heap_type, actual.heap_type, desc); + return match_heap(engine, expected.heap_type, actual.heap_type, desc); } bail!( "{desc} types incompatible: expected {desc} of type `{expected}`, \ @@ -278,7 +290,7 @@ fn match_ref(expected: WasmRefType, actual: WasmRefType, desc: &str) -> Result<( // Checks whether actual is a subtype of expected, i.e. `actual <: expected` // (note the parameters are given the other way around in code). -fn match_ty(expected: WasmValType, actual: WasmValType, desc: &str) -> Result<()> { +fn match_ty(engine: &Engine, expected: WasmValType, actual: WasmValType, desc: &str) -> Result<()> { // Assert that both our types are engine-level canonicalized. We can't // compare types otherwise. debug_assert!( @@ -291,7 +303,9 @@ fn match_ty(expected: WasmValType, actual: WasmValType, desc: &str) -> Result<() ); match (actual, expected) { - (WasmValType::Ref(actual), WasmValType::Ref(expected)) => match_ref(expected, actual, desc), + (WasmValType::Ref(actual), WasmValType::Ref(expected)) => { + match_ref(engine, expected, actual, desc) + } (actual, expected) => equal_ty(expected, actual, desc), } } diff --git a/tests/all/globals.rs b/tests/all/globals.rs index 1fa2b214f1e2..dcb1b35aefa8 100644 --- a/tests/all/globals.rs +++ b/tests/all/globals.rs @@ -350,3 +350,81 @@ fn i31ref_as_anyref_global_ty() -> Result<()> { } Ok(()) } + +#[test] +fn instantiate_global_with_subtype() -> Result<()> { + let mut config = Config::new(); + config.wasm_function_references(true); + config.wasm_gc(true); + + let engine = Engine::new(&config)?; + let module = Module::new( + &engine, + r#" + (module + (type $func_ty (sub (func))) + (import "" "" (global (ref null $func_ty))) + ) + "#, + )?; + + { + let func_ty = + FuncType::with_finality_and_supertype(&engine, Finality::NonFinal, None, [], [])?; + let sub_func_ty = FuncType::with_finality_and_supertype( + &engine, + Finality::NonFinal, + Some(&func_ty), + [], + [], + )?; + let global_ty = GlobalType::new( + RefType::new(true, HeapType::ConcreteFunc(sub_func_ty.clone())).into(), + Mutability::Const, + ); + assert!(global_ty.content().matches( + module + .imports() + .nth(0) + .unwrap() + .ty() + .unwrap_global() + .content() + )); + + let mut store = Store::new(&engine, ()); + let func = Func::new(&mut store, sub_func_ty, |_caller, _args, _rets| Ok(())); + let global = Global::new(&mut store, global_ty, func.into())?; + + // This instantiation should succeed: the given global's type is a subtype + // of the import's global type. + let _ = Instance::new(&mut store, &module, &[global.into()])?; + } + + { + let func_ty = FuncType::new(&engine, [], []); + let global_ty = GlobalType::new( + RefType::new(true, HeapType::ConcreteFunc(func_ty.clone())).into(), + Mutability::Const, + ); + assert!(!global_ty.content().matches( + module + .imports() + .nth(0) + .unwrap() + .ty() + .unwrap_global() + .content() + )); + + let mut store = Store::new(&engine, ()); + let func = Func::new(&mut store, func_ty, |_caller, _args, _rets| Ok(())); + let global = Global::new(&mut store, global_ty, func.into())?; + + // This instantiation should fail: the given global's type is *not* a + // subtype of the import's global type. + assert!(Instance::new(&mut store, &module, &[global.into()]).is_err()); + } + + Ok(()) +}