Repository: bytecodealliance/wasm-tools
Pull request #1934 (2024-12-03 22:16:35)

Fix Wasm GC validation/generation of field types' mutability and subtyping


Patch:
diff --git a/crates/wasm-smith/src/core.rs b/crates/wasm-smith/src/core.rs
index d9d40660..e74e05fa 100644
--- a/crates/wasm-smith/src/core.rs
+++ b/crates/wasm-smith/src/core.rs
@@ -756,10 +756,14 @@ impl Module {
         u: &mut Unstructured,
         ty: FieldType,
     ) -> Result<FieldType> {
-        Ok(FieldType {
-            element_type: self.arbitrary_matching_storage_type(u, ty.element_type)?,
-            mutable: if ty.mutable { u.arbitrary()? } else { false },
-        })
+        if ty.mutable {
+            Ok(ty)
+        } else {
+            Ok(FieldType {
+                element_type: self.arbitrary_matching_storage_type(u, ty.element_type)?,
+                mutable: false,
+            })
+        }
     }
 
     fn arbitrary_matching_storage_type(
diff --git a/crates/wasmparser/src/readers/core/types/matches.rs b/crates/wasmparser/src/readers/core/types/matches.rs
index 4df4cc3f..030adc5d 100644
--- a/crates/wasmparser/src/readers/core/types/matches.rs
+++ b/crates/wasmparser/src/readers/core/types/matches.rs
@@ -233,11 +233,28 @@ impl<'a> Matches for WithRecGroup<&'a StructType> {
 
 impl Matches for WithRecGroup<FieldType> {
     fn matches(types: &TypeList, a: Self, b: Self) -> bool {
-        (b.mutable || !a.mutable)
+        // `a`'s storage type must match `b`'s storage type.
+        if !Matches::matches(
+            types,
+            WithRecGroup::map(a, |a| a.element_type),
+            WithRecGroup::map(b, |b| b.element_type),
+        ) {
+            return false;
+        }
+
+        // And either both fields are immutable...
+        if !a.mutable && !b.mutable {
+            return true;
+        }
+
+        // Or both fields are mutable and `b`'s storage type must match `a`'s
+        // storage type, a.k.a. they must be the exact same storage type.
+        a.mutable
+            && b.mutable
             && Matches::matches(
                 types,
-                WithRecGroup::map(a, |a| a.element_type),
                 WithRecGroup::map(b, |b| b.element_type),
+                WithRecGroup::map(a, |a| a.element_type),
             )
     }
 }


Test patch:
diff --git a/tests/local/gc/gc-field-subtyping.wast b/tests/local/gc/gc-field-subtyping.wast
new file mode 100644
index 00000000..96ad1554
--- /dev/null
+++ b/tests/local/gc/gc-field-subtyping.wast
@@ -0,0 +1,13 @@
+;; --enable-gc
+
+(module
+  ;; When fields are mutable, a subtype's reference fields must be the exact
+  ;; same the supertype's fields (i.e. are invariant).
+  (type $a (sub    (struct (field (mut (ref null any))))))
+  (type $b (sub $a (struct (field (mut (ref null any))))))
+
+  ;; When fields are non-mutable, a subtype's reference fields can be subtypes
+  ;; of the supertype's fields (i.e. are covariant).
+  (type $c (sub    (struct (field (ref null any)))))
+  (type $d (sub $c (struct (field (ref null none)))))
+)
diff --git a/tests/local/gc/gc-subtypes.wat b/tests/local/gc/gc-subtypes.wat
index 5b33fb59..efa82c21 100644
--- a/tests/local/gc/gc-subtypes.wat
+++ b/tests/local/gc/gc-subtypes.wat
@@ -1,105 +1,120 @@
 ;; --enable-gc
 
 (module
-  (type $a (sub (func)))
-  (type $b (sub $a (func)))
-  (type $c (sub $b (func)))
-  (type $b1 (sub final $a (func)))
+  ;; nullability matching for field types
+  (type $non_null (sub (array (ref null any))))
+  (type $nullable (sub $non_null (array (ref any))))
 
-  ;; struct, ref types, mutability, nullability
-  (type $d (sub (struct)))
-  (type $e (sub $d (struct (field (mut (ref null $d)))))) ;; width
-  (type $f (sub final $e (struct (field (ref $e))))) ;; depth
+  ;; struct width and depth subtyping
+  (type $struct_base  (sub               (struct)))
+  (type $struct_width (sub $struct_base  (struct (field (ref null $struct_base)))))
+  (type $struct_depth (sub $struct_width (struct (field (ref $struct_base)))))
 
-  ;; func
-  (type $g (sub (func (param (ref $e)) (result (ref $e)))))
-  (type $h (sub $g (func (param (ref $d)) (result (ref $f)))))
-
-  (type $j (sub (func (param (ref $b)) (result (ref $b)))))
-  (type $k (sub $j (func (param (ref $a)) (result (ref $c)))))
-
-  ;; valid: expanded param/result types are equal to those of the parent
-  (type $l (sub $j (func (param (ref $b)) (result (ref $b)))))
-
-  ;; array, val types, ref types, mutability, nullability
-  (type $m (sub (array (mut i32))))
-  (type $n (sub $m (array i32)))
-
-  (type $o (sub (array i32)))
-  (type $p (sub $o (array i32)))
-
-  (type $o1 (sub (array i64)))
-  (type $p1 (sub $o1 (array i64)))
+  ;; function param and result subtyping
+  (type $func_base (sub (func (param eqref) (result eqref))))
+  (type $func_contravariant_params (sub $func_base (func (param anyref) (result eqref))))
+  (type $func_covariant_results (sub $func_base (func (param eqref) (result nullref))))
 
   ;; any refs
-  (type $q (sub (array (mut anyref))))
-  (type $q0 (sub $q (array (ref any))))
+  (type $any (sub (array anyref)))
+  (type $any_2 (sub $any (array anyref)))
+  (type $any_mut (sub (array (mut anyref))))
+  (type $any_mut_2 (sub $any_mut (array (mut anyref))))
 
   ;; eq refs
-  (type $q1 (sub $q (array (mut eqref))))
-  (type $q2 (sub $q1 (array (mut (ref eq)))))
-  (type $q3 (sub $q2 (array (ref eq))))
+  (type $eq (sub $any (array eqref)))
+  (type $eq_2 (sub $eq (array eqref)))
+  (type $eq_mut (sub (array (mut eqref))))
+  (type $eq_mut_2 (sub $eq_mut (array (mut eqref))))
 
   ;; i31 refs
-  (type $r (sub $q (array i31ref)))
-  (type $r1 (sub $q1 (array i31ref)))
-  (type $s (sub $r (array (ref i31))))
-  (type $s1 (sub $q1 (array (ref i31))))
-  (type $s2 (sub $q2 (array (ref i31))))
+  (type $i31 (sub $eq (array i31ref)))
+  (type $i31_2 (sub $i31 (array i31ref)))
+  (type $i31_mut (sub (array (mut i31ref))))
+  (type $i31_mut_2 (sub $i31_mut (array (mut i31ref))))
 
   ;; array refs
-  (type $rr (sub $q (array arrayref)))
-  (type $rr1 (sub $q1 (array arrayref)))
-  (type $ss (sub $rr (array (ref array))))
-  (type $ss0 (sub $ss (array (ref $rr))))
-  (type $ss1 (sub $q1 (array (ref array))))
-  (type (sub $q1 (array (ref $rr))))
-  (type $ss2 (sub $q2 (array (ref array))))
-  (type (sub $q2 (array (ref $rr))))
+  (type $array (sub $eq (array arrayref)))
+  (type $array_2 (sub $array (array arrayref)))
+  (type $array_mut (sub (array (mut arrayref))))
+  (type $array_mut_2 (sub $array_mut (array (mut arrayref))))
+
+  ;; concrete array refs
+  (type $concrete_array (sub $array (array (ref null $array))))
+  (type $concrete_array_2 (sub $concrete_array (array (ref null $array))))
+  (type $concrete_array_mut (sub (array (ref null $array_mut))))
+  (type $concrete_array_mut_2 (sub $concrete_array_mut (array (ref null $array_mut))))
 
   ;; struct refs
-  (type $rrr (sub $q (array structref)))
-  (type $rrr1 (sub $q1 (array structref)))
-  (type $sss (sub $rrr (array (ref struct))))
-  (type $sss0 (sub $rrr (array (ref null $d))))
-  (type $sss1 (sub $q1 (array (ref struct))))
-  (type (sub $q1 (array (ref $d))))
-  (type $sss2 (sub $q2 (array (ref struct))))
-  (type (sub $q2 (array (ref $d))))
+  (type $struct (sub $eq (array structref)))
+  (type $struct_2 (sub $struct (array structref)))
+  (type $struct_mut (sub (array (mut structref))))
+  (type $struct_mut_2 (sub $struct_mut (array (mut structref))))
+
+  ;; concrete struct refs
+  (type $my_struct (struct))
+  (type $concrete_struct (sub $struct (array (ref null $my_struct))))
+  (type $concrete_struct_2 (sub $concrete_struct (array (ref null $my_struct))))
+  (type $concrete_struct_mut (sub (array (mut (ref null $my_struct)))))
+  (type $concrete_struct_mut_2 (sub $concrete_struct_mut (array (mut (ref null $my_struct)))))
 
   ;; none refs
-  (type $z1 (sub $q (array (mut nullref))))
-  (type $z2 (sub $q0 (array (ref none))))
-  (type $z3 (sub $z1 (array (mut (ref none)))))
-  (type $z4 (sub $z1 (array nullref)))
-  (type (sub $q1 (array nullref)))
-  (type (sub $r (array nullref)))
-  (type (sub $rr (array nullref)))
-  (type (sub $rrr (array nullref)))
-  (type (sub $q1 (array (ref none))))
-  (type (sub $r (array (ref none))))
-  (type (sub $rr (array (ref none))))
-  (type (sub $rrr (array (ref none))))
+  (type $none (sub (array nullref)))
+  (type $none_2 (sub $none (array nullref)))
+  (type $none_any (sub $any (array nullref)))
+  (type $none_any_2 (sub $none_any (array nullref)))
+  (type $none_eq (sub $eq (array nullref)))
+  (type $none_eq_2 (sub $none_eq (array nullref)))
+  (type $none_i31 (sub $i31 (array nullref)))
+  (type $none_i31_2 (sub $none_i31 (array nullref)))
+  (type $none_array (sub $array (array nullref)))
+  (type $none_array_2 (sub $none_array (array nullref)))
+  (type $none_concrete_array (sub $concrete_array (array nullref)))
+  (type $none_concrete_array_2 (sub $none_concrete_array (array nullref)))
+  (type $none_struct (sub $struct (array nullref)))
+  (type $none_struct_2 (sub $none_struct (array nullref)))
+  (type $none_concrete_struct (sub $concrete_struct (array nullref)))
+  (type $none_concrete_struct_2 (sub $none_concrete_struct (array nullref)))
+  (type $none_mut (sub (array (mut nullref))))
+  (type $none_mut_2 (sub $none_mut (array (mut nullref))))
 
   ;; func refs
-  (type $t (sub (array (mut funcref))))
-  (type $u (sub $t (array (ref null func))))
-  (type $v (sub $u (array (ref func))))
-  (type $w (sub $v (array (ref $a))))
-  (type $x (sub $t (array (ref null $a))))
-  (type $y (sub $w (array (ref nofunc))))
-  (type $z (sub $x (array nullfuncref)))
+  (type $func (sub (array funcref)))
+  (type $func_2 (sub $func (array funcref)))
+  (type $func_mut (sub (array (mut funcref))))
+  (type $func_mut_2 (sub $func_mut (array (mut funcref))))
+
+  ;; concrete func refs
+  (type $my_func (func))
+  (type $concrete_func (sub $func (array (ref null $my_func))))
+  (type $concrete_func_2 (sub $concrete_func (array (ref null $my_func))))
+  (type $concrete_func_mut (sub (array (mut (ref null $my_func)))))
+  (type $concrete_func_mut_2 (sub $concrete_func_mut (array (mut (ref null $my_func)))))
+
+  ;; nofunc
+  (type $nofunc (sub $concrete_func (array nullfuncref)))
+  (type $nofunc_2 (sub $nofunc (array nullfuncref)))
+  (type $nofunc_mut (sub (array (mut nullfuncref))))
+  (type $nofunc_mut_2 (sub $nofunc_mut (array (mut nullfuncref))))
 
   ;; extern refs
-  (type $t0 (sub (array (mut externref))))
-  (type $u0 (sub $t0 (array (ref null extern))))
-  (type $v0 (sub $u0 (array (ref extern))))
-  (type $y0 (sub $v0 (array (ref noextern))))
-  (type $y01 (sub $u0 (array (ref noextern))))
-  (type $z0 (sub $u0 (array nullexternref)))
-
-  (type $A (sub (struct (field $vt (mut i32)))))
-  (type $B (sub $A (struct (field $vt (mut i32)))))
-  (type (sub $A (struct (field $tv (mut i32))))) ;; same field, different name
-  (type (sub $A (struct (field (mut i32)) (field $vt (mut i64))))) ;; different field, same name
+  (type $extern (sub (array externref)))
+  (type $extern_2 (sub $extern (array externref)))
+  (type $extern_mut (sub (array (mut externref))))
+  (type $extern_mut_2 (sub $extern_mut (array (mut externref))))
+
+  ;; noextern
+  (type $noextern (sub $extern (array nullexternref)))
+  (type $noextern_2 (sub $noextern (array nullexternref)))
+  (type $noextern_mut (sub (array (mut nullexternref))))
+  (type $noextern_mut_2 (sub $noextern_mut (array (mut nullexternref))))
+
+  ;; field names
+  (type $struct_with_named_field (sub (struct (field $field (mut i32)))))
+  ;; same field, same name
+  (type (sub $struct_with_named_field (struct (field $field (mut i32)))))
+  ;; same field, different name
+  (type (sub $struct_with_named_field (struct (field $different (mut i32)))))
+  ;; different field, same name
+  (type (sub $struct_with_named_field (struct (field (mut i32)) (field $field (mut i64)))))
 )
diff --git a/tests/local/invalid/gc-field-subtyping.wast b/tests/local/invalid/gc-field-subtyping.wast
new file mode 100644
index 00000000..d3c3369d
--- /dev/null
+++ b/tests/local/invalid/gc-field-subtyping.wast
@@ -0,0 +1,39 @@
+;; --enable-gc
+
+(assert_invalid
+  (module
+    ;; When fields are mutable, a subtype's reference fields cannot be subtypes of
+    ;; the supertype's fields, they must match exactly.
+    (type $a (sub    (struct (field (mut (ref null any))))))
+    (type $b (sub $a (struct (field (mut (ref null none))))))
+  )
+  "sub type must match super type"
+)
+
+(assert_invalid
+  (module
+    ;; When fields are const, a subtype's reference fields cannot be supertypes of
+    ;; the supertype's fields, they must be subtypes.
+    (type $a (sub    (struct (field (mut (ref null none))))))
+    (type $b (sub $a (struct (field (mut (ref null any))))))
+  )
+  "sub type must match super type"
+ )
+
+(assert_invalid
+ (module
+   ;; The mutability of fields must be the same.
+   (type $c (sub    (struct (field (mut (ref null any))))))
+   (type $d (sub $c (struct (field      (ref null none)))))
+  )
+  "sub type must match super type"
+)
+
+(assert_invalid
+ (module
+   ;; The mutability of fields must be the same.
+   (type $c (sub    (struct (field      (ref null any)))))
+   (type $d (sub $c (struct (field (mut (ref null none))))))
+  )
+  "sub type must match super type"
+)
diff --git a/tests/snapshots/local/gc/gc-field-subtyping.wast.json b/tests/snapshots/local/gc/gc-field-subtyping.wast.json
new file mode 100644
index 00000000..ee95f6c4
--- /dev/null
+++ b/tests/snapshots/local/gc/gc-field-subtyping.wast.json
@@ -0,0 +1,11 @@
+{
+  "source_filename": "tests/local/gc/gc-field-subtyping.wast",
+  "commands": [
+    {
+      "type": "module",
+      "line": 3,
+      "filename": "gc-field-subtyping.0.wasm",
+      "module_type": "binary"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/snapshots/local/gc/gc-field-subtyping.wast/0.print b/tests/snapshots/local/gc/gc-field-subtyping.wast/0.print
new file mode 100644
index 00000000..643f6d8a
--- /dev/null
+++ b/tests/snapshots/local/gc/gc-field-subtyping.wast/0.print
@@ -0,0 +1,6 @@
+(module
+  (type $a (;0;) (sub (struct (field (mut anyref)))))
+  (type $b (;1;) (sub $a (struct (field (mut anyref)))))
+  (type $c (;2;) (sub (struct (field anyref))))
+  (type $d (;3;) (sub $c (struct (field nullref))))
+)
diff --git a/tests/snapshots/local/gc/gc-subtypes.wat.print b/tests/snapshots/local/gc/gc-subtypes.wat.print
index 0abcb9e1..298e12bc 100644
--- a/tests/snapshots/local/gc/gc-subtypes.wat.print
+++ b/tests/snapshots/local/gc/gc-subtypes.wat.print
@@ -1,75 +1,82 @@
 (module
-  (type $a (;0;) (sub (func)))
-  (type $b (;1;) (sub $a (func)))
-  (type $c (;2;) (sub $b (func)))
-  (type $b1 (;3;) (sub final $a (func)))
-  (type $d (;4;) (sub (struct)))
-  (type $e (;5;) (sub $d (struct (field (mut (ref null $d))))))
-  (type $f (;6;) (sub final $e (struct (field (ref $e)))))
-  (type $g (;7;) (sub (func (param (ref $e)) (result (ref $e)))))
-  (type $h (;8;) (sub $g (func (param (ref $d)) (result (ref $f)))))
-  (type $j (;9;) (sub (func (param (ref $b)) (result (ref $b)))))
-  (type $k (;10;) (sub $j (func (param (ref $a)) (result (ref $c)))))
-  (type $l (;11;) (sub $j (func (param (ref $b)) (result (ref $b)))))
-  (type $m (;12;) (sub (array (mut i32))))
-  (type $n (;13;) (sub $m (array i32)))
-  (type $o (;14;) (sub (array i32)))
-  (type $p (;15;) (sub $o (array i32)))
-  (type $o1 (;16;) (sub (array i64)))
-  (type $p1 (;17;) (sub $o1 (array i64)))
-  (type $q (;18;) (sub (array (mut anyref))))
-  (type $q0 (;19;) (sub $q (array (ref any))))
-  (type $q1 (;20;) (sub $q (array (mut eqref))))
-  (type $q2 (;21;) (sub $q1 (array (mut (ref eq)))))
-  (type $q3 (;22;) (sub $q2 (array (ref eq))))
-  (type $r (;23;) (sub $q (array i31ref)))
-  (type $r1 (;24;) (sub $q1 (array i31ref)))
-  (type $s (;25;) (sub $r (array (ref i31))))
-  (type $s1 (;26;) (sub $q1 (array (ref i31))))
-  (type $s2 (;27;) (sub $q2 (array (ref i31))))
-  (type $rr (;28;) (sub $q (array arrayref)))
-  (type $rr1 (;29;) (sub $q1 (array arrayref)))
-  (type $ss (;30;) (sub $rr (array (ref array))))
-  (type $ss0 (;31;) (sub $ss (array (ref $rr))))
-  (type $ss1 (;32;) (sub $q1 (array (ref array))))
-  (type (;33;) (sub $q1 (array (ref $rr))))
-  (type $ss2 (;34;) (sub $q2 (array (ref array))))
-  (type (;35;) (sub $q2 (array (ref $rr))))
-  (type $rrr (;36;) (sub $q (array structref)))
-  (type $rrr1 (;37;) (sub $q1 (array structref)))
-  (type $sss (;38;) (sub $rrr (array (ref struct))))
-  (type $sss0 (;39;) (sub $rrr (array (ref null $d))))
-  (type $sss1 (;40;) (sub $q1 (array (ref struct))))
-  (type (;41;) (sub $q1 (array (ref $d))))
-  (type $sss2 (;42;) (sub $q2 (array (ref struct))))
-  (type (;43;) (sub $q2 (array (ref $d))))
-  (type $z1 (;44;) (sub $q (array (mut nullref))))
-  (type $z2 (;45;) (sub $q0 (array (ref none))))
-  (type $z3 (;46;) (sub $z1 (array (mut (ref none)))))
-  (type $z4 (;47;) (sub $z1 (array nullref)))
-  (type (;48;) (sub $q1 (array nullref)))
-  (type (;49;) (sub $r (array nullref)))
-  (type (;50;) (sub $rr (array nullref)))
-  (type (;51;) (sub $rrr (array nullref)))
-  (type (;52;) (sub $q1 (array (ref none))))
-  (type (;53;) (sub $r (array (ref none))))
-  (type (;54;) (sub $rr (array (ref none))))
-  (type (;55;) (sub $rrr (array (ref none))))
-  (type $t (;56;) (sub (array (mut funcref))))
-  (type $u (;57;) (sub $t (array funcref)))
-  (type $v (;58;) (sub $u (array (ref func))))
-  (type $w (;59;) (sub $v (array (ref $a))))
-  (type $x (;60;) (sub $t (array (ref null $a))))
-  (type $y (;61;) (sub $w (array (ref nofunc))))
-  (type $z (;62;) (sub $x (array nullfuncref)))
-  (type $t0 (;63;) (sub (array (mut externref))))
-  (type $u0 (;64;) (sub $t0 (array externref)))
-  (type $v0 (;65;) (sub $u0 (array (ref extern))))
-  (type $y0 (;66;) (sub $v0 (array (ref noextern))))
-  (type $y01 (;67;) (sub $u0 (array (ref noextern))))
-  (type $z0 (;68;) (sub $u0 (array nullexternref)))
-  (type $A (;69;) (sub (struct (field $vt (mut i32)))))
-  (type $B (;70;) (sub $A (struct (field $vt (mut i32)))))
-  (type (;71;) (sub $A (struct (field $tv (mut i32)))))
-  (type (;72;) (sub $A (struct (field (mut i32)) (field $vt (mut i64)))))
+  (type $non_null (;0;) (sub (array anyref)))
+  (type $nullable (;1;) (sub $non_null (array (ref any))))
+  (type $struct_base (;2;) (sub (struct)))
+  (type $struct_width (;3;) (sub $struct_base (struct (field (ref null $struct_base)))))
+  (type $struct_depth (;4;) (sub $struct_width (struct (field (ref $struct_base)))))
+  (type $func_base (;5;) (sub (func (param eqref) (result eqref))))
+  (type $func_contravariant_params (;6;) (sub $func_base (func (param anyref) (result eqref))))
+  (type $func_covariant_results (;7;) (sub $func_base (func (param eqref) (result nullref))))
+  (type $any (;8;) (sub (array anyref)))
+  (type $any_2 (;9;) (sub $any (array anyref)))
+  (type $any_mut (;10;) (sub (array (mut anyref))))
+  (type $any_mut_2 (;11;) (sub $any_mut (array (mut anyref))))
+  (type $eq (;12;) (sub $any (array eqref)))
+  (type $eq_2 (;13;) (sub $eq (array eqref)))
+  (type $eq_mut (;14;) (sub (array (mut eqref))))
+  (type $eq_mut_2 (;15;) (sub $eq_mut (array (mut eqref))))
+  (type $i31 (;16;) (sub $eq (array i31ref)))
+  (type $i31_2 (;17;) (sub $i31 (array i31ref)))
+  (type $i31_mut (;18;) (sub (array (mut i31ref))))
+  (type $i31_mut_2 (;19;) (sub $i31_mut (array (mut i31ref))))
+  (type $array (;20;) (sub $eq (array arrayref)))
+  (type $array_2 (;21;) (sub $array (array arrayref)))
+  (type $array_mut (;22;) (sub (array (mut arrayref))))
+  (type $array_mut_2 (;23;) (sub $array_mut (array (mut arrayref))))
+  (type $concrete_array (;24;) (sub $array (array (ref null $array))))
+  (type $concrete_array_2 (;25;) (sub $concrete_array (array (ref null $array))))
+  (type $concrete_array_mut (;26;) (sub (array (ref null $array_mut))))
+  (type $concrete_array_mut_2 (;27;) (sub $concrete_array_mut (array (ref null $array_mut))))
+  (type $struct (;28;) (sub $eq (array structref)))
+  (type $struct_2 (;29;) (sub $struct (array structref)))
+  (type $struct_mut (;30;) (sub (array (mut structref))))
+  (type $struct_mut_2 (;31;) (sub $struct_mut (array (mut structref))))
+  (type $my_struct (;32;) (struct))
+  (type $concrete_struct (;33;) (sub $struct (array (ref null $my_struct))))
+  (type $concrete_struct_2 (;34;) (sub $concrete_struct (array (ref null $my_struct))))
+  (type $concrete_struct_mut (;35;) (sub (array (mut (ref null $my_struct)))))
+  (type $concrete_struct_mut_2 (;36;) (sub $concrete_struct_mut (array (mut (ref null $my_struct)))))
+  (type $none (;37;) (sub (array nullref)))
+  (type $none_2 (;38;) (sub $none (array nullref)))
+  (type $none_any (;39;) (sub $any (array nullref)))
+  (type $none_any_2 (;40;) (sub $none_any (array nullref)))
+  (type $none_eq (;41;) (sub $eq (array nullref)))
+  (type $none_eq_2 (;42;) (sub $none_eq (array nullref)))
+  (type $none_i31 (;43;) (sub $i31 (array nullref)))
+  (type $none_i31_2 (;44;) (sub $none_i31 (array nullref)))
+  (type $none_array (;45;) (sub $array (array nullref)))
+  (type $none_array_2 (;46;) (sub $none_array (array nullref)))
+  (type $none_concrete_array (;47;) (sub $concrete_array (array nullref)))
+  (type $none_concrete_array_2 (;48;) (sub $none_concrete_array (array nullref)))
+  (type $none_struct (;49;) (sub $struct (array nullref)))
+  (type $none_struct_2 (;50;) (sub $none_struct (array nullref)))
+  (type $none_concrete_struct (;51;) (sub $concrete_struct (array nullref)))
+  (type $none_concrete_struct_2 (;52;) (sub $none_concrete_struct (array nullref)))
+  (type $none_mut (;53;) (sub (array (mut nullref))))
+  (type $none_mut_2 (;54;) (sub $none_mut (array (mut nullref))))
+  (type $func (;55;) (sub (array funcref)))
+  (type $func_2 (;56;) (sub $func (array funcref)))
+  (type $func_mut (;57;) (sub (array (mut funcref))))
+  (type $func_mut_2 (;58;) (sub $func_mut (array (mut funcref))))
+  (type $my_func (;59;) (func))
+  (type $concrete_func (;60;) (sub $func (array (ref null $my_func))))
+  (type $concrete_func_2 (;61;) (sub $concrete_func (array (ref null $my_func))))
+  (type $concrete_func_mut (;62;) (sub (array (mut (ref null $my_func)))))
+  (type $concrete_func_mut_2 (;63;) (sub $concrete_func_mut (array (mut (ref null $my_func)))))
+  (type $nofunc (;64;) (sub $concrete_func (array nullfuncref)))
+  (type $nofunc_2 (;65;) (sub $nofunc (array nullfuncref)))
+  (type $nofunc_mut (;66;) (sub (array (mut nullfuncref))))
+  (type $nofunc_mut_2 (;67;) (sub $nofunc_mut (array (mut nullfuncref))))
+  (type $extern (;68;) (sub (array externref)))
+  (type $extern_2 (;69;) (sub $extern (array externref)))
+  (type $extern_mut (;70;) (sub (array (mut externref))))
+  (type $extern_mut_2 (;71;) (sub $extern_mut (array (mut externref))))
+  (type $noextern (;72;) (sub $extern (array nullexternref)))
+  (type $noextern_2 (;73;) (sub $noextern (array nullexternref)))
+  (type $noextern_mut (;74;) (sub (array (mut nullexternref))))
+  (type $noextern_mut_2 (;75;) (sub $noextern_mut (array (mut nullexternref))))
+  (type $struct_with_named_field (;76;) (sub (struct (field $field (mut i32)))))
+  (type (;77;) (sub $struct_with_named_field (struct (field $field (mut i32)))))
+  (type (;78;) (sub $struct_with_named_field (struct (field $different (mut i32)))))
+  (type (;79;) (sub $struct_with_named_field (struct (field (mut i32)) (field $field (mut i64)))))
 )
diff --git a/tests/snapshots/local/invalid/gc-field-subtyping.wast.json b/tests/snapshots/local/invalid/gc-field-subtyping.wast.json
new file mode 100644
index 00000000..8234d3a9
--- /dev/null
+++ b/tests/snapshots/local/invalid/gc-field-subtyping.wast.json
@@ -0,0 +1,33 @@
+{
+  "source_filename": "tests/local/invalid/gc-field-subtyping.wast",
+  "commands": [
+    {
+      "type": "assert_invalid",
+      "line": 4,
+      "filename": "gc-field-subtyping.0.wasm",
+      "module_type": "binary",
+      "text": "sub type must match super type"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 14,
+      "filename": "gc-field-subtyping.1.wasm",
+      "module_type": "binary",
+      "text": "sub type must match super type"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 24,
+      "filename": "gc-field-subtyping.2.wasm",
+      "module_type": "binary",
+      "text": "sub type must match super type"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 33,
+      "filename": "gc-field-subtyping.3.wasm",
+      "module_type": "binary",
+      "text": "sub type must match super type"
+    }
+  ]
+}
\ No newline at end of file