Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 29 additions & 15 deletions crates/wasmtime/src/runtime/types/matching.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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}`, \
Expand All @@ -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!(
Expand All @@ -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),
}
}
Expand Down
78 changes: 78 additions & 0 deletions tests/all/globals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}