Repository: bytecodealliance/wasm-tools
Pull request #1342 (2023-12-18 20:43:33)

`wasmparser`: Get the `array.wast` GC spec test passing
This involved adding support for the following instructions:

* `array.new`
* `array.new_data`
* `array.new_elem`
* `array.new_fixed`
* `array.get`
* `array.get_s`
* `array.get_u`
* `array.set`
* `array.len`


Patch:
diff --git a/crates/wasm-encoder/src/core/code.rs b/crates/wasm-encoder/src/core/code.rs
index e7770751..fc6b5b39 100644
--- a/crates/wasm-encoder/src/core/code.rs
+++ b/crates/wasm-encoder/src/core/code.rs
@@ -323,10 +323,16 @@ pub enum Instruction<'a> {
     Return,
     Call(u32),
     CallRef(u32),
-    CallIndirect { ty: u32, table: u32 },
+    CallIndirect {
+        ty: u32,
+        table: u32,
+    },
     ReturnCallRef(u32),
     ReturnCall(u32),
-    ReturnCallIndirect { ty: u32, table: u32 },
+    ReturnCallIndirect {
+        ty: u32,
+        table: u32,
+    },
     Throw(u32),
     Rethrow(u32),
 
@@ -367,9 +373,15 @@ pub enum Instruction<'a> {
     I64Store32(MemArg),
     MemorySize(u32),
     MemoryGrow(u32),
-    MemoryInit { mem: u32, data_index: u32 },
+    MemoryInit {
+        mem: u32,
+        data_index: u32,
+    },
     DataDrop(u32),
-    MemoryCopy { src_mem: u32, dst_mem: u32 },
+    MemoryCopy {
+        src_mem: u32,
+        dst_mem: u32,
+    },
     MemoryFill(u32),
     MemoryDiscard(u32),
 
@@ -533,9 +545,18 @@ pub enum Instruction<'a> {
 
     ArrayNew(u32),
     ArrayNewDefault(u32),
-    ArrayNewFixed(u32, u32),
-    ArrayNewData(u32, u32),
-    ArrayNewElem(u32, u32),
+    ArrayNewFixed {
+        array_type_index: u32,
+        array_size: u32,
+    },
+    ArrayNewData {
+        array_type_index: u32,
+        array_data_index: u32,
+    },
+    ArrayNewElem {
+        array_type_index: u32,
+        array_elem_index: u32,
+    },
     ArrayGet(u32),
     ArrayGetS(u32),
     ArrayGetU(u32),
@@ -558,14 +579,20 @@ pub enum Instruction<'a> {
     I31GetU,
 
     // Bulk memory instructions.
-    TableInit { elem_index: u32, table: u32 },
+    TableInit {
+        elem_index: u32,
+        table: u32,
+    },
     ElemDrop(u32),
     TableFill(u32),
     TableSet(u32),
     TableGet(u32),
     TableGrow(u32),
     TableSize(u32),
-    TableCopy { src_table: u32, dst_table: u32 },
+    TableCopy {
+        src_table: u32,
+        dst_table: u32,
+    },
 
     // SIMD instructions.
     V128Load(MemArg),
@@ -582,14 +609,38 @@ pub enum Instruction<'a> {
     V128Load32Zero(MemArg),
     V128Load64Zero(MemArg),
     V128Store(MemArg),
-    V128Load8Lane { memarg: MemArg, lane: Lane },
-    V128Load16Lane { memarg: MemArg, lane: Lane },
-    V128Load32Lane { memarg: MemArg, lane: Lane },
-    V128Load64Lane { memarg: MemArg, lane: Lane },
-    V128Store8Lane { memarg: MemArg, lane: Lane },
-    V128Store16Lane { memarg: MemArg, lane: Lane },
-    V128Store32Lane { memarg: MemArg, lane: Lane },
-    V128Store64Lane { memarg: MemArg, lane: Lane },
+    V128Load8Lane {
+        memarg: MemArg,
+        lane: Lane,
+    },
+    V128Load16Lane {
+        memarg: MemArg,
+        lane: Lane,
+    },
+    V128Load32Lane {
+        memarg: MemArg,
+        lane: Lane,
+    },
+    V128Load64Lane {
+        memarg: MemArg,
+        lane: Lane,
+    },
+    V128Store8Lane {
+        memarg: MemArg,
+        lane: Lane,
+    },
+    V128Store16Lane {
+        memarg: MemArg,
+        lane: Lane,
+    },
+    V128Store32Lane {
+        memarg: MemArg,
+        lane: Lane,
+    },
+    V128Store64Lane {
+        memarg: MemArg,
+        lane: Lane,
+    },
     V128Const(i128),
     I8x16Shuffle([Lane; 16]),
     I8x16ExtractLaneS(Lane),
@@ -1396,23 +1447,32 @@ impl Encode for Instruction<'_> {
                 sink.push(0x07);
                 type_index.encode(sink);
             }
-            Instruction::ArrayNewFixed(type_index, size) => {
+            Instruction::ArrayNewFixed {
+                array_type_index,
+                array_size,
+            } => {
                 sink.push(0xfb);
                 sink.push(0x08);
-                type_index.encode(sink);
-                size.encode(sink);
+                array_type_index.encode(sink);
+                array_size.encode(sink);
             }
-            Instruction::ArrayNewData(type_index, data_index) => {
+            Instruction::ArrayNewData {
+                array_type_index,
+                array_data_index,
+            } => {
                 sink.push(0xfb);
                 sink.push(0x09);
-                type_index.encode(sink);
-                data_index.encode(sink);
+                array_type_index.encode(sink);
+                array_data_index.encode(sink);
             }
-            Instruction::ArrayNewElem(type_index, elem_index) => {
+            Instruction::ArrayNewElem {
+                array_type_index,
+                array_elem_index,
+            } => {
                 sink.push(0xfb);
                 sink.push(0x0a);
-                type_index.encode(sink);
-                elem_index.encode(sink);
+                array_type_index.encode(sink);
+                array_elem_index.encode(sink);
             }
             Instruction::ArrayGet(type_index) => {
                 sink.push(0xfb);
diff --git a/crates/wasm-mutate/src/mutators/translate.rs b/crates/wasm-mutate/src/mutators/translate.rs
index 50c97ece..784b9cb3 100644
--- a/crates/wasm-mutate/src/mutators/translate.rs
+++ b/crates/wasm-mutate/src/mutators/translate.rs
@@ -337,12 +337,16 @@ pub fn op(t: &mut dyn Translator, op: &Operator<'_>) -> Result<Instruction<'stat
         (map $arg:ident dst_table) => (t.remap(Item::Table, *$arg)?);
         (map $arg:ident src_table) => (t.remap(Item::Table, *$arg)?);
         (map $arg:ident type_index) => (t.remap(Item::Type, *$arg)?);
+        (map $arg:ident array_type_index) => (t.remap(Item::Type, *$arg)?);
+        (map $arg:ident struct_type_index) => (t.remap(Item::Type, *$arg)?);
         (map $arg:ident global_index) => (t.remap(Item::Global, *$arg)?);
         (map $arg:ident mem) => (t.remap(Item::Memory, *$arg)?);
         (map $arg:ident src_mem) => (t.remap(Item::Memory, *$arg)?);
         (map $arg:ident dst_mem) => (t.remap(Item::Memory, *$arg)?);
         (map $arg:ident data_index) => (t.remap(Item::Data, *$arg)?);
         (map $arg:ident elem_index) => (t.remap(Item::Element, *$arg)?);
+        (map $arg:ident array_data_index) => (t.remap(Item::Data, *$arg)?);
+        (map $arg:ident array_elem_index) => (t.remap(Item::Element, *$arg)?);
         (map $arg:ident blockty) => (t.translate_block_type($arg)?);
         (map $arg:ident relative_depth) => (*$arg);
         (map $arg:ident targets) => ((
@@ -362,6 +366,7 @@ pub fn op(t: &mut dyn Translator, op: &Operator<'_>) -> Result<Instruction<'stat
         (map $arg:ident value) => ($arg);
         (map $arg:ident lane) => (*$arg);
         (map $arg:ident lanes) => (*$arg);
+        (map $arg:ident array_size) => (*$arg);
 
         // This case takes the arguments of a wasmparser instruction and creates
         // a wasm-encoder instruction. There are a few special cases for where
@@ -385,6 +390,10 @@ pub fn op(t: &mut dyn Translator, op: &Operator<'_>) -> Result<Instruction<'stat
         });
         (build MemoryGrow $mem:ident $_:ident) => (I::MemoryGrow($mem));
         (build MemorySize $mem:ident $_:ident) => (I::MemorySize($mem));
+        (build ArrayGet $type_index:ident) => (I::ArrayGet($type_index));
+        (build ArrayGetS $type_index:ident) => (I::ArrayGetS($type_index));
+        (build ArrayGetU $type_index:ident) => (I::ArrayGetU($type_index));
+        (build ArraySet $type_index:ident) => (I::ArraySet($type_index));
         (build $op:ident $($arg:ident)*) => (I::$op { $($arg),* });
     }
 
diff --git a/crates/wasmparser/src/binary_reader.rs b/crates/wasmparser/src/binary_reader.rs
index 167783d1..7493a59b 100644
--- a/crates/wasmparser/src/binary_reader.rs
+++ b/crates/wasmparser/src/binary_reader.rs
@@ -1006,10 +1006,46 @@ impl<'a> BinaryReader<'a> {
                 visitor.visit_struct_new_default(type_index)
             }
 
+            0x06 => {
+                let type_index = self.read_var_u32()?;
+                visitor.visit_array_new(type_index)
+            }
             0x07 => {
                 let type_index = self.read_var_u32()?;
                 visitor.visit_array_new_default(type_index)
             }
+            0x08 => {
+                let type_index = self.read_var_u32()?;
+                let n = self.read_var_u32()?;
+                visitor.visit_array_new_fixed(type_index, n)
+            }
+            0x09 => {
+                let type_index = self.read_var_u32()?;
+                let data_index = self.read_var_u32()?;
+                visitor.visit_array_new_data(type_index, data_index)
+            }
+            0x0a => {
+                let type_index = self.read_var_u32()?;
+                let elem_index = self.read_var_u32()?;
+                visitor.visit_array_new_elem(type_index, elem_index)
+            }
+            0x0b => {
+                let type_index = self.read_var_u32()?;
+                visitor.visit_array_get(type_index)
+            }
+            0x0c => {
+                let type_index = self.read_var_u32()?;
+                visitor.visit_array_get_s(type_index)
+            }
+            0x0d => {
+                let type_index = self.read_var_u32()?;
+                visitor.visit_array_get_u(type_index)
+            }
+            0x0e => {
+                let type_index = self.read_var_u32()?;
+                visitor.visit_array_set(type_index)
+            }
+            0x0f => visitor.visit_array_len(),
 
             0x14 => visitor.visit_ref_test_non_null(self.read()?),
             0x15 => visitor.visit_ref_test_nullable(self.read()?),
diff --git a/crates/wasmparser/src/lib.rs b/crates/wasmparser/src/lib.rs
index 269bb580..0fadec65 100644
--- a/crates/wasmparser/src/lib.rs
+++ b/crates/wasmparser/src/lib.rs
@@ -316,8 +316,17 @@ macro_rules! for_each_operator {
             // 0xFB prefixed operators
             // Garbage Collection
             // http://github.com/WebAssembly/gc
-            @gc StructNewDefault { type_index: u32 } => visit_struct_new_default
-            @gc ArrayNewDefault { type_index: u32 } => visit_array_new_default
+            @gc StructNewDefault { struct_type_index: u32 } => visit_struct_new_default
+            @gc ArrayNew { array_type_index: u32 } => visit_array_new
+            @gc ArrayNewDefault { array_type_index: u32 } => visit_array_new_default
+            @gc ArrayNewFixed { array_type_index: u32, array_size: u32 } => visit_array_new_fixed
+            @gc ArrayNewData { array_type_index: u32, array_data_index: u32 } => visit_array_new_data
+            @gc ArrayNewElem { array_type_index: u32, array_elem_index: u32 } => visit_array_new_elem
+            @gc ArrayGet { array_type_index: u32 } => visit_array_get
+            @gc ArrayGetS { array_type_index: u32 } => visit_array_get_s
+            @gc ArrayGetU { array_type_index: u32 } => visit_array_get_u
+            @gc ArraySet { array_type_index: u32 } => visit_array_set
+            @gc ArrayLen => visit_array_len
             @gc RefTestNonNull { hty: $crate::HeapType } => visit_ref_test_non_null
             @gc RefTestNullable { hty: $crate::HeapType } => visit_ref_test_nullable
             @gc RefCastNonNull { hty: $crate::HeapType } => visit_ref_cast_non_null
diff --git a/crates/wasmparser/src/readers/core/types.rs b/crates/wasmparser/src/readers/core/types.rs
index d3fcd050..64f257e4 100644
--- a/crates/wasmparser/src/readers/core/types.rs
+++ b/crates/wasmparser/src/readers/core/types.rs
@@ -658,6 +658,15 @@ pub enum StorageType {
 }
 
 impl StorageType {
+    /// Is this a packed storage type, i.e. one that must be sign- or
+    /// zero-extended when converted to a `ValType`?
+    pub fn is_packed(&self) -> bool {
+        match self {
+            Self::I8 | Self::I16 => true,
+            Self::Val(_) => false,
+        }
+    }
+
     /// Unpack this storage type into the valtype that it is represented as on
     /// the operand stack.
     pub fn unpack(&self) -> ValType {
diff --git a/crates/wasmparser/src/validator/core.rs b/crates/wasmparser/src/validator/core.rs
index 109a0212..b59edcb7 100644
--- a/crates/wasmparser/src/validator/core.rs
+++ b/crates/wasmparser/src/validator/core.rs
@@ -455,6 +455,18 @@ impl ModuleState {
 
             // These are valid const expressions with the gc proposal is
             // enabled.
+            (@visit $self:ident visit_array_new $type_index:ident) => {{
+                $self.validate_gc("array.new")?;
+                $self.validator().visit_array_new($type_index)
+            }};
+            (@visit $self:ident visit_array_new_default $type_index:ident) => {{
+                $self.validate_gc("array.new_default")?;
+                $self.validator().visit_array_new_default($type_index)
+            }};
+            (@visit $self:ident visit_array_new_fixed $type_index:ident $n:ident) => {{
+                $self.validate_gc("array.new_fixed")?;
+                $self.validator().visit_array_new_fixed($type_index, $n)
+            }};
             (@visit $self:ident visit_ref_i31) => {{
                 $self.validate_gc("ref.i31")?;
                 $self.validator().visit_ref_i31()
diff --git a/crates/wasmparser/src/validator/operators.rs b/crates/wasmparser/src/validator/operators.rs
index 8cf85270..41412a1f 100644
--- a/crates/wasmparser/src/validator/operators.rs
+++ b/crates/wasmparser/src/validator/operators.rs
@@ -437,6 +437,34 @@ where
         Ok(())
     }
 
+    fn push_concrete_ref(&mut self, nullable: bool, type_index: u32) -> Result<()> {
+        let mut heap_ty = HeapType::Concrete(UnpackedIndex::Module(type_index));
+
+        // Canonicalize the module index into an id.
+        self.resources.check_heap_type(&mut heap_ty, self.offset)?;
+        debug_assert!(matches!(heap_ty, HeapType::Concrete(UnpackedIndex::Id(_))));
+
+        let ref_ty = RefType::new(nullable, heap_ty).ok_or_else(|| {
+            format_err!(self.offset, "implementation limit: type index too large")
+        })?;
+
+        self.push_operand(ref_ty)
+    }
+
+    fn pop_concrete_ref(&mut self, nullable: bool, type_index: u32) -> Result<MaybeType> {
+        let mut heap_ty = HeapType::Concrete(UnpackedIndex::Module(type_index));
+
+        // Canonicalize the module index into an id.
+        self.resources.check_heap_type(&mut heap_ty, self.offset)?;
+        debug_assert!(matches!(heap_ty, HeapType::Concrete(UnpackedIndex::Id(_))));
+
+        let ref_ty = RefType::new(nullable, heap_ty).ok_or_else(|| {
+            format_err!(self.offset, "implementation limit: type index too large")
+        })?;
+
+        self.pop_operand(Some(ref_ty.into()))
+    }
+
     /// Attempts to pop a type from the operand stack.
     ///
     /// This function is used to remove types from the operand stack. The
@@ -1026,6 +1054,17 @@ where
         self.push_operand(sub_ty)
     }
 
+    fn element_type_at(&self, elem_index: u32) -> Result<RefType> {
+        match self.resources.element_type_at(elem_index) {
+            Some(ty) => Ok(ty),
+            None => bail!(
+                self.offset,
+                "unknown elem segment {}: segment index out of bounds",
+                elem_index
+            ),
+        }
+    }
+
     fn sub_type_at(&self, at: u32) -> Result<&'resources SubType> {
         self.resources
             .sub_type_at(at)
@@ -3380,14 +3419,7 @@ where
                 table
             ),
         };
-        let segment_ty = match self.resources.element_type_at(segment) {
-            Some(ty) => ty,
-            None => bail!(
-                self.offset,
-                "unknown elem segment {}: segment index out of bounds",
-                segment
-            ),
-        };
+        let segment_ty = self.element_type_at(segment)?;
         if !self
             .resources
             .is_subtype(ValType::Ref(segment_ty), ValType::Ref(table.element_type))
@@ -3501,6 +3533,12 @@ where
 
         self.push_operand(ref_ty)
     }
+    fn visit_array_new(&mut self, type_index: u32) -> Self::Output {
+        let array_ty = self.array_type_at(type_index)?;
+        self.pop_operand(Some(ValType::I32))?;
+        self.pop_operand(Some(array_ty.0.element_type.unpack()))?;
+        self.push_concrete_ref(false, type_index)
+    }
     fn visit_array_new_default(&mut self, type_index: u32) -> Self::Output {
         let ty = self.array_type_at(type_index)?;
         let val_ty = ty.0.element_type.unpack();
@@ -3510,17 +3548,112 @@ where
                 "invalid `array.new_default`: {val_ty} field is not defaultable"
             );
         }
-
-        let mut heap_ty = HeapType::Concrete(UnpackedIndex::Module(type_index));
-        // Call `check_heap_type` to canonicalize the module index into an id.
-        self.resources.check_heap_type(&mut heap_ty, self.offset)?;
-
-        let ref_ty = RefType::new(false, heap_ty).ok_or_else(|| {
-            format_err!(self.offset, "implementation limit: type index too large")
-        })?;
-
         self.pop_operand(Some(ValType::I32))?;
-        self.push_operand(ref_ty)
+        self.push_concrete_ref(false, type_index)
+    }
+    fn visit_array_new_fixed(&mut self, type_index: u32, n: u32) -> Self::Output {
+        let array_ty = self.array_type_at(type_index)?;
+        let elem_ty = array_ty.0.element_type.unpack();
+        for _ in 0..n {
+            self.pop_operand(Some(elem_ty))?;
+        }
+        self.push_concrete_ref(false, type_index)
+    }
+    fn visit_array_new_data(&mut self, type_index: u32, data_index: u32) -> Self::Output {
+        let array_ty = self.array_type_at(type_index)?;
+        let elem_ty = array_ty.0.element_type.unpack();
+        match elem_ty {
+            ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 | ValType::V128 => {}
+            ValType::Ref(_) => bail!(
+                self.offset,
+                "array.new_data can only create arrays with numeric and vector elements"
+            ),
+        }
+        match self.resources.data_count() {
+            None => bail!(self.offset, "data count section required"),
+            Some(count) if data_index < count => {}
+            Some(_) => bail!(self.offset, "unknown data segment {}", data_index),
+        }
+        self.pop_operand(Some(ValType::I32))?;
+        self.pop_operand(Some(ValType::I32))?;
+        self.push_concrete_ref(false, type_index)
+    }
+    fn visit_array_new_elem(&mut self, type_index: u32, elem_index: u32) -> Self::Output {
+        let array_ty = self.array_type_at(type_index)?;
+        let array_ref_ty = match array_ty.0.element_type.unpack() {
+            ValType::Ref(rt) => rt,
+            ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 | ValType::V128 => bail!(
+                self.offset,
+                "array.new_elem can only create arrays with reference elements"
+            ),
+        };
+        let elem_ref_ty = self.element_type_at(elem_index)?;
+        if !self
+            .resources
+            .is_subtype(elem_ref_ty.into(), array_ref_ty.into())
+        {
+            bail!(
+                self.offset,
+                "invalid array.new_elem instruction: element segment {elem_index} type mismatch: \
+                 expected {array_ref_ty}, found {elem_ref_ty}"
+            )
+        }
+        self.pop_operand(Some(ValType::I32))?;
+        self.pop_operand(Some(ValType::I32))?;
+        self.push_concrete_ref(false, type_index)
+    }
+    fn visit_array_get(&mut self, type_index: u32) -> Self::Output {
+        let array_ty = self.array_type_at(type_index)?;
+        let elem_ty = array_ty.0.element_type;
+        if elem_ty.is_packed() {
+            bail!(
+                self.offset,
+                "cannot use array.get with packed storage types"
+            )
+        }
+        self.pop_operand(Some(ValType::I32))?;
+        self.pop_concrete_ref(true, type_index)?;
+        self.push_operand(elem_ty.unpack())
+    }
+    fn visit_array_get_s(&mut self, type_index: u32) -> Self::Output {
+        let array_ty = self.array_type_at(type_index)?;
+        let elem_ty = array_ty.0.element_type;
+        if !elem_ty.is_packed() {
+            bail!(
+                self.offset,
+                "cannot use array.get_s with non-packed storage types"
+            )
+        }
+        self.pop_operand(Some(ValType::I32))?;
+        self.pop_concrete_ref(true, type_index)?;
+        self.push_operand(elem_ty.unpack())
+    }
+    fn visit_array_get_u(&mut self, type_index: u32) -> Self::Output {
+        let array_ty = self.array_type_at(type_index)?;
+        let elem_ty = array_ty.0.element_type;
+        if !elem_ty.is_packed() {
+            bail!(
+                self.offset,
+                "cannot use array.get_u with non-packed storage types"
+            )
+        }
+        self.pop_operand(Some(ValType::I32))?;
+        self.pop_concrete_ref(true, type_index)?;
+        self.push_operand(elem_ty.unpack())
+    }
+    fn visit_array_set(&mut self, type_index: u32) -> Self::Output {
+        let array_ty = self.array_type_at(type_index)?;
+        if !array_ty.0.mutable {
+            bail!(self.offset, "invalid array.set: array is immutable")
+        }
+        self.pop_operand(Some(array_ty.0.element_type.unpack()))?;
+        self.pop_operand(Some(ValType::I32))?;
+        self.pop_concrete_ref(true, type_index)?;
+        Ok(())
+    }
+    fn visit_array_len(&mut self) -> Self::Output {
+        self.pop_operand(Some(RefType::ARRAY.nullable().into()))?;
+        self.push_operand(ValType::I32)
     }
     fn visit_any_convert_extern(&mut self) -> Self::Output {
         let extern_ref = self.pop_operand(Some(RefType::EXTERNREF.into()))?;
diff --git a/crates/wasmprinter/src/operator.rs b/crates/wasmprinter/src/operator.rs
index 75b0170e..bfba957d 100644
--- a/crates/wasmprinter/src/operator.rs
+++ b/crates/wasmprinter/src/operator.rs
@@ -217,9 +217,16 @@ impl<'a, 'b> PrintOperator<'a, 'b> {
         self.printer.print_core_type_ref(self.state, idx)
     }
 
-    /// Like `self.type_index` but without the `(type ..)` wrapper.
-    fn type_index_no_type(&mut self, idx: u32) -> Result<()> {
-        self.push_str(" ");
+    fn array_type_index(&mut self, idx: u32) -> Result<()> {
+        self.printer.print_idx(&self.state.core.type_names, idx)
+    }
+
+    fn array_size(&mut self, array_size: u32) -> Result<()> {
+        write!(&mut self.printer.result, "{array_size}")?;
+        Ok(())
+    }
+
+    fn struct_type_index(&mut self, idx: u32) -> Result<()> {
         self.printer.print_idx(&self.state.core.type_names, idx)
     }
 
@@ -227,10 +234,18 @@ impl<'a, 'b> PrintOperator<'a, 'b> {
         self.printer.print_idx(&self.state.core.data_names, idx)
     }
 
+    fn array_data_index(&mut self, idx: u32) -> Result<()> {
+        self.printer.print_idx(&self.state.core.data_names, idx)
+    }
+
     fn elem_index(&mut self, idx: u32) -> Result<()> {
         self.printer.print_idx(&self.state.core.element_names, idx)
     }
 
+    fn array_elem_index(&mut self, idx: u32) -> Result<()> {
+        self.printer.print_idx(&self.state.core.element_names, idx)
+    }
+
     fn lane(&mut self, lane: u8) -> Result<()> {
         write!(self.result(), "{lane}")?;
         Ok(())
@@ -424,12 +439,6 @@ macro_rules! define_visit {
             )?;
         }
     );
-    (payload $self:ident StructNewDefault $type_index: ident) => (
-        $self.type_index_no_type($type_index)?;
-    );
-    (payload $self:ident ArrayNewDefault $type_index: ident) => (
-        $self.type_index_no_type($type_index)?;
-    );
     (payload $self:ident RefTestNonNull $hty:ident) => (
         $self.push_str(" ");
         let rty = RefType::new(false, $hty)
@@ -1000,7 +1009,16 @@ macro_rules! define_visit {
     (name I16x8RelaxedDotI8x16I7x16S) => ("i16x8.relaxed_dot_i8x16_i7x16_s");
     (name I32x4RelaxedDotI8x16I7x16AddS) => ("i32x4.relaxed_dot_i8x16_i7x16_add_s");
     (name StructNewDefault) => ("struct.new_default");
+    (name ArrayNew) => ("array.new");
     (name ArrayNewDefault) => ("array.new_default");
+    (name ArrayNewFixed) => ("array.new_fixed");
+    (name ArrayNewData) => ("array.new_data");
+    (name ArrayNewElem) => ("array.new_elem");
+    (name ArrayGet) => ("array.get");
+    (name ArrayGetS) => ("array.get_s");
+    (name ArrayGetU) => ("array.get_u");
+    (name ArraySet) => ("array.set");
+    (name ArrayLen) => ("array.len");
     (name AnyConvertExtern) => ("any.convert_extern");
     (name ExternConvertAny) => ("extern.convert_any");
     (name RefTestNonNull) => ("ref.test");
diff --git a/crates/wit-component/src/gc.rs b/crates/wit-component/src/gc.rs
index 9db50319..511a6d3d 100644
--- a/crates/wit-component/src/gc.rs
+++ b/crates/wit-component/src/gc.rs
@@ -1000,6 +1000,8 @@ macro_rules! define_visit {
     };
 
     (mark_live $self:ident $arg:ident type_index) => {$self.ty($arg);};
+    (mark_live $self:ident $arg:ident array_type_index) => {$self.ty($arg);};
+    (mark_live $self:ident $arg:ident struct_type_index) => {$self.ty($arg);};
     (mark_live $self:ident $arg:ident src_table) => {$self.table($arg);};
     (mark_live $self:ident $arg:ident dst_table) => {$self.table($arg);};
     (mark_live $self:ident $arg:ident table_index) => {$self.table($arg);};
@@ -1025,7 +1027,10 @@ macro_rules! define_visit {
     (mark_live $self:ident $arg:ident tag_index) => {};
     (mark_live $self:ident $arg:ident targets) => {};
     (mark_live $self:ident $arg:ident data_index) => {};
+    (mark_live $self:ident $arg:ident array_data_index) => {};
     (mark_live $self:ident $arg:ident elem_index) => {};
+    (mark_live $self:ident $arg:ident array_elem_index) => {};
+    (mark_live $self:ident $arg:ident array_size) => {};
 }
 
 impl<'a> VisitOperator<'a> for Module<'a> {
@@ -1201,15 +1206,20 @@ macro_rules! define_encode {
     (map $self:ident $arg:ident src_table) => {$self.tables.remap($arg)};
     (map $self:ident $arg:ident dst_table) => {$self.tables.remap($arg)};
     (map $self:ident $arg:ident type_index) => {$self.types.remap($arg)};
+    (map $self:ident $arg:ident array_type_index) => {$self.types.remap($arg)};
+    (map $self:ident $arg:ident struct_type_index) => {$self.types.remap($arg)};
     (map $self:ident $arg:ident ty) => {$self.valty($arg)};
     (map $self:ident $arg:ident local_index) => {$arg};
     (map $self:ident $arg:ident lane) => {$arg};
     (map $self:ident $arg:ident lanes) => {$arg};
     (map $self:ident $arg:ident elem_index) => {$arg};
     (map $self:ident $arg:ident data_index) => {$arg};
+    (map $self:ident $arg:ident array_elem_index) => {$arg};
+    (map $self:ident $arg:ident array_data_index) => {$arg};
     (map $self:ident $arg:ident table_byte) => {$arg};
     (map $self:ident $arg:ident mem_byte) => {$arg};
     (map $self:ident $arg:ident value) => {$arg};
+    (map $self:ident $arg:ident array_size) => {$arg};
     (map $self:ident $arg:ident targets) => ((
         $arg.targets().map(|i| i.unwrap()).collect::<Vec<_>>().into(),
         $arg.default(),
diff --git a/crates/wit-component/src/linking.rs b/crates/wit-component/src/linking.rs
index eea2ddad..56adb738 100644
--- a/crates/wit-component/src/linking.rs
+++ b/crates/wit-component/src/linking.rs
@@ -323,7 +323,11 @@ fn make_env_module<'a>(
     }
 
     let mut memory_offset = stack_size_bytes;
-    let mut table_offset = 0;
+
+    // Table offset 0 is reserved for the null function pointer.
+    // This convention follows wasm-ld's table layout:
+    // https://github.com/llvm/llvm-project/blob/913622d012f72edb5ac3a501cef8639d0ebe471b/lld/wasm/Driver.cpp#L581-L584
+    let mut table_offset = 1;
     let mut globals = GlobalSection::new();
     let mut exports = ExportSection::new();
 


Test patch:
diff --git a/crates/wit-component/tests/components/link-bare-funcs/component.wat b/crates/wit-component/tests/components/link-bare-funcs/component.wat
index f2849f91..085b43b2 100644
--- a/crates/wit-component/tests/components/link-bare-funcs/component.wat
+++ b/crates/wit-component/tests/components/link-bare-funcs/component.wat
@@ -6,13 +6,13 @@
   (core module (;0;)
     (type (;0;) (func (param i32 i32 i32 i32) (result i32)))
     (import "foo" "cabi_realloc" (func (;0;) (type 0)))
-    (table (;0;) 0 funcref)
+    (table (;0;) 1 funcref)
     (memory (;0;) 17)
     (global (;0;) (mut i32) i32.const 1048576)
     (global (;1;) i32 i32.const 1048592)
-    (global (;2;) i32 i32.const 0)
+    (global (;2;) i32 i32.const 1)
     (global (;3;) i32 i32.const 1048592)
-    (global (;4;) i32 i32.const 0)
+    (global (;4;) i32 i32.const 1)
     (global (;5;) (mut i32) i32.const 1048608)
     (global (;6;) (mut i32) i32.const 1114112)
     (export "cabi_realloc" (func 0))
@@ -98,8 +98,8 @@
     (import "env" "__indirect_function_table" (table (;0;) 0 funcref))
     (func (;0;) (type 0))
     (start 0)
-    (elem (;0;) (i32.const 0) func)
-    (elem (;1;) (i32.const 0) func)
+    (elem (;0;) (i32.const 1) func)
+    (elem (;1;) (i32.const 1) func)
     (data (;0;) (i32.const 1048576) "\00\00\00\00\00\00\10\00")
     (@producers
       (processed-by "wit-component" "$CARGO_PKG_VERSION")
diff --git a/crates/wit-component/tests/components/link-circular/component.wat b/crates/wit-component/tests/components/link-circular/component.wat
index f8aadfcc..16276da9 100644
--- a/crates/wit-component/tests/components/link-circular/component.wat
+++ b/crates/wit-component/tests/components/link-circular/component.wat
@@ -10,16 +10,16 @@
     (type (;0;) (func (param i32) (result i32)))
     (func (;0;) (type 0) (param i32) (result i32)
       local.get 0
-      i32.const 0
+      i32.const 1
       call_indirect (type 0)
     )
-    (table (;0;) 1 funcref)
+    (table (;0;) 2 funcref)
     (memory (;0;) 17)
     (global (;0;) (mut i32) i32.const 1048576)
     (global (;1;) i32 i32.const 1048592)
-    (global (;2;) i32 i32.const 0)
+    (global (;2;) i32 i32.const 1)
     (global (;3;) i32 i32.const 1048592)
-    (global (;4;) i32 i32.const 0)
+    (global (;4;) i32 i32.const 1)
     (global (;5;) (mut i32) i32.const 1048592)
     (global (;6;) (mut i32) i32.const 1114112)
     (export "__stack_pointer" (global 0))
@@ -72,8 +72,8 @@
     (import "foo" "foo" (func (;0;) (type 2)))
     (func (;1;) (type 0))
     (start 1)
-    (elem (;0;) (i32.const 0) func)
-    (elem (;1;) (i32.const 0) func 0)
+    (elem (;0;) (i32.const 1) func)
+    (elem (;1;) (i32.const 1) func 0)
     (data (;0;) (i32.const 1048576) "\00\00\00\00\00\00\10\00")
     (@producers
       (processed-by "wit-component" "$CARGO_PKG_VERSION")
diff --git a/crates/wit-component/tests/components/link-dl-openable/component.wat b/crates/wit-component/tests/components/link-dl-openable/component.wat
index 05b4a4f7..d1c540de 100644
--- a/crates/wit-component/tests/components/link-dl-openable/component.wat
+++ b/crates/wit-component/tests/components/link-dl-openable/component.wat
@@ -7,11 +7,11 @@
   )
   (import "test:test/test" (instance (;0;) (type 0)))
   (core module (;0;)
-    (table (;0;) 3 funcref)
+    (table (;0;) 4 funcref)
     (memory (;0;) 17)
     (global (;0;) (mut i32) i32.const 1048576)
     (global (;1;) i32 i32.const 1048688)
-    (global (;2;) i32 i32.const 3)
+    (global (;2;) i32 i32.const 4)
     (global (;3;) (mut i32) i32.const 1048688)
     (global (;4;) (mut i32) i32.const 1114112)
     (export "__stack_pointer" (global 0))
@@ -61,9 +61,9 @@
       i32.store
     )
     (start 3)
-    (elem (;0;) (i32.const 0) func 0 1 2)
-    (elem (;1;) (i32.const 3) func)
-    (data (;0;) (i32.const 1048576) "foo\00bar\00baz\00test:test/test#foo\00\00what\03\00\00\00\04\00\10\00\00\00\00\00\03\00\00\00\08\00\10\00\01\00\00\00\12\00\00\00\0c\00\10\00\02\00\00\00\04\00\00\00 \00\10\00\00\00\00\00\03\00\00\00\00\00\10\00\04\00\00\00$\00\10\00\01\00\00\00T\00\10\00")
+    (elem (;0;) (i32.const 1) func 0 1 2)
+    (elem (;1;) (i32.const 4) func)
+    (data (;0;) (i32.const 1048576) "foo\00bar\00baz\00test:test/test#foo\00\00what\03\00\00\00\04\00\10\00\01\00\00\00\03\00\00\00\08\00\10\00\02\00\00\00\12\00\00\00\0c\00\10\00\03\00\00\00\04\00\00\00 \00\10\00\00\00\00\00\03\00\00\00\00\00\10\00\04\00\00\00$\00\10\00\01\00\00\00T\00\10\00")
     (@producers
       (processed-by "wit-component" "$CARGO_PKG_VERSION")
     )
diff --git a/crates/wit-component/tests/components/link-duplicate-symbols/component.wat b/crates/wit-component/tests/components/link-duplicate-symbols/component.wat
index f267ac4b..04ce2271 100644
--- a/crates/wit-component/tests/components/link-duplicate-symbols/component.wat
+++ b/crates/wit-component/tests/components/link-duplicate-symbols/component.wat
@@ -10,16 +10,16 @@
     (type (;0;) (func (param i32) (result i32)))
     (func (;0;) (type 0) (param i32) (result i32)
       local.get 0
-      i32.const 0
+      i32.const 1
       call_indirect (type 0)
     )
-    (table (;0;) 1 funcref)
+    (table (;0;) 2 funcref)
     (memory (;0;) 17)
     (global (;0;) (mut i32) i32.const 1048576)
     (global (;1;) i32 i32.const 1048592)
-    (global (;2;) i32 i32.const 0)
+    (global (;2;) i32 i32.const 1)
     (global (;3;) i32 i32.const 1048592)
-    (global (;4;) i32 i32.const 0)
+    (global (;4;) i32 i32.const 1)
     (global (;5;) (mut i32) i32.const 1048592)
     (global (;6;) (mut i32) i32.const 1114112)
     (export "__stack_pointer" (global 0))
@@ -73,8 +73,8 @@
     (import "bar" "foo" (func (;0;) (type 2)))
     (func (;1;) (type 0))
     (start 1)
-    (elem (;0;) (i32.const 0) func)
-    (elem (;1;) (i32.const 0) func 0)
+    (elem (;0;) (i32.const 1) func)
+    (elem (;1;) (i32.const 1) func 0)
     (data (;0;) (i32.const 1048576) "\00\00\00\00\00\00\10\00")
     (@producers
       (processed-by "wit-component" "$CARGO_PKG_VERSION")
diff --git a/crates/wit-component/tests/components/link-got-func/component.wat b/crates/wit-component/tests/components/link-got-func/component.wat
index c54b8955..915d947b 100644
--- a/crates/wit-component/tests/components/link-got-func/component.wat
+++ b/crates/wit-component/tests/components/link-got-func/component.wat
@@ -10,17 +10,17 @@
     (type (;0;) (func (param i32) (result i32)))
     (func (;0;) (type 0) (param i32) (result i32)
       local.get 0
-      i32.const 0
+      i32.const 1
       call_indirect (type 0)
     )
-    (table (;0;) 1 funcref)
+    (table (;0;) 2 funcref)
     (memory (;0;) 17)
     (global (;0;) (mut i32) i32.const 1048576)
     (global (;1;) i32 i32.const 1048592)
-    (global (;2;) i32 i32.const 0)
+    (global (;2;) i32 i32.const 1)
     (global (;3;) i32 i32.const 1048592)
-    (global (;4;) i32 i32.const 0)
-    (global (;5;) (mut i32) i32.const 0)
+    (global (;4;) i32 i32.const 1)
+    (global (;5;) (mut i32) i32.const 1)
     (global (;6;) (mut i32) i32.const 1048592)
     (global (;7;) (mut i32) i32.const 1114112)
     (export "__stack_pointer" (global 0))
@@ -67,8 +67,8 @@
     (import "bar" "foo" (func (;0;) (type 2)))
     (func (;1;) (type 0))
     (start 1)
-    (elem (;0;) (i32.const 0) func)
-    (elem (;1;) (i32.const 0) func 0)
+    (elem (;0;) (i32.const 1) func)
+    (elem (;1;) (i32.const 1) func 0)
     (data (;0;) (i32.const 1048576) "\00\00\00\00\00\00\10\00")
     (@producers
       (processed-by "wit-component" "$CARGO_PKG_VERSION")
diff --git a/crates/wit-component/tests/components/link-initialize/component.wat b/crates/wit-component/tests/components/link-initialize/component.wat
index dbbf3dae..d801299f 100644
--- a/crates/wit-component/tests/components/link-initialize/component.wat
+++ b/crates/wit-component/tests/components/link-initialize/component.wat
@@ -7,16 +7,16 @@
   )
   (import "test:test/test" (instance (;0;) (type 0)))
   (core module (;0;)
-    (table (;0;) 0 funcref)
+    (table (;0;) 1 funcref)
     (memory (;0;) 17)
     (global (;0;) (mut i32) i32.const 1048576)
     (global (;1;) i32 i32.const 1048592)
-    (global (;2;) i32 i32.const 0)
+    (global (;2;) i32 i32.const 1)
     (global (;3;) (mut i32) i32.const 0)
     (global (;4;) i32 i32.const 1048624)
-    (global (;5;) i32 i32.const 0)
+    (global (;5;) i32 i32.const 1)
     (global (;6;) i32 i32.const 1048624)
-    (global (;7;) i32 i32.const 0)
+    (global (;7;) i32 i32.const 1)
     (global (;8;) (mut i32) i32.const 0)
     (global (;9;) (mut i32) i32.const 1048640)
     (global (;10;) (mut i32) i32.const 1114112)
@@ -173,8 +173,8 @@
       call 3
     )
     (start 4)
-    (elem (;0;) (i32.const 0) func)
-    (elem (;1;) (i32.const 0) func)
+    (elem (;0;) (i32.const 1) func)
+    (elem (;1;) (i32.const 1) func)
     (data (;0;) (i32.const 1048576) "\00\00\00\00\00\00\10\00")
     (@producers
       (processed-by "wit-component" "$CARGO_PKG_VERSION")
diff --git a/crates/wit-component/tests/components/link-resources/component.wat b/crates/wit-component/tests/components/link-resources/component.wat
index ef038e32..b36255f5 100644
--- a/crates/wit-component/tests/components/link-resources/component.wat
+++ b/crates/wit-component/tests/components/link-resources/component.wat
@@ -1,12 +1,12 @@
 (component
   (core module (;0;)
-    (table (;0;) 0 funcref)
+    (table (;0;) 1 funcref)
     (memory (;0;) 17)
     (global (;0;) (mut i32) i32.const 1048576)
     (global (;1;) i32 i32.const 1048592)
-    (global (;2;) i32 i32.const 0)
+    (global (;2;) i32 i32.const 1)
     (global (;3;) i32 i32.const 1048592)
-    (global (;4;) i32 i32.const 0)
+    (global (;4;) i32 i32.const 1)
     (global (;5;) (mut i32) i32.const 1048592)
     (global (;6;) (mut i32) i32.const 1114112)
     (export "__stack_pointer" (global 0))
@@ -61,8 +61,8 @@
     (import "env" "__indirect_function_table" (table (;0;) 0 funcref))
     (func (;0;) (type 0))
     (start 0)
-    (elem (;0;) (i32.const 0) func)
-    (elem (;1;) (i32.const 0) func)
+    (elem (;0;) (i32.const 1) func)
+    (elem (;1;) (i32.const 1) func)
     (data (;0;) (i32.const 1048576) "\00\00\00\00\00\00\10\00")
     (@producers
       (processed-by "wit-component" "$CARGO_PKG_VERSION")
diff --git a/crates/wit-component/tests/components/link-stubs/component.wat b/crates/wit-component/tests/components/link-stubs/component.wat
index 3797346f..2768e916 100644
--- a/crates/wit-component/tests/components/link-stubs/component.wat
+++ b/crates/wit-component/tests/components/link-stubs/component.wat
@@ -7,13 +7,13 @@
   )
   (import "test:test/test" (instance (;0;) (type 0)))
   (core module (;0;)
-    (table (;0;) 0 funcref)
+    (table (;0;) 2 funcref)
     (memory (;0;) 17)
     (global (;0;) (mut i32) i32.const 1048576)
     (global (;1;) i32 i32.const 1048592)
-    (global (;2;) i32 i32.const 0)
+    (global (;2;) i32 i32.const 1)
     (global (;3;) i32 i32.const 1048592)
-    (global (;4;) i32 i32.const 0)
+    (global (;4;) i32 i32.const 2)
     (global (;5;) (mut i32) i32.const 1048592)
     (global (;6;) (mut i32) i32.const 1114112)
     (export "__stack_pointer" (global 0))
@@ -64,8 +64,8 @@
     (import "env" "__indirect_function_table" (table (;0;) 0 funcref))
     (func (;0;) (type 0))
     (start 0)
-    (elem (;0;) (i32.const 0) func)
-    (elem (;1;) (i32.const 0) func)
+    (elem (;0;) (i32.const 1) func)
+    (elem (;1;) (i32.const 2) func)
     (data (;0;) (i32.const 1048576) "\00\00\00\00\00\00\10\00")
     (@producers
       (processed-by "wit-component" "$CARGO_PKG_VERSION")
diff --git a/crates/wit-component/tests/components/link-weak-import/component.wat b/crates/wit-component/tests/components/link-weak-import/component.wat
index 3c1d1d6a..d57404e1 100644
--- a/crates/wit-component/tests/components/link-weak-import/component.wat
+++ b/crates/wit-component/tests/components/link-weak-import/component.wat
@@ -7,13 +7,13 @@
   )
   (import "test:test/test" (instance (;0;) (type 0)))
   (core module (;0;)
-    (table (;0;) 0 funcref)
+    (table (;0;) 2 funcref)
     (memory (;0;) 17)
     (global (;0;) (mut i32) i32.const 1048576)
     (global (;1;) i32 i32.const 1048592)
-    (global (;2;) i32 i32.const 0)
+    (global (;2;) i32 i32.const 1)
     (global (;3;) i32 i32.const 1048592)
-    (global (;4;) i32 i32.const 0)
+    (global (;4;) i32 i32.const 2)
     (global (;5;) (mut i32) i32.const 1048592)
     (global (;6;) (mut i32) i32.const 1114112)
     (export "__stack_pointer" (global 0))
@@ -66,8 +66,8 @@
     (import "env" "__indirect_function_table" (table (;0;) 0 funcref))
     (func (;0;) (type 0))
     (start 0)
-    (elem (;0;) (i32.const 0) func)
-    (elem (;1;) (i32.const 0) func)
+    (elem (;0;) (i32.const 1) func)
+    (elem (;1;) (i32.const 2) func)
     (data (;0;) (i32.const 1048576) "\00\00\00\00\00\00\10\00")
     (@producers
       (processed-by "wit-component" "$CARGO_PKG_VERSION")
diff --git a/crates/wit-component/tests/components/link/component.wat b/crates/wit-component/tests/components/link/component.wat
index b35218f1..d78029e7 100644
--- a/crates/wit-component/tests/components/link/component.wat
+++ b/crates/wit-component/tests/components/link/component.wat
@@ -7,16 +7,16 @@
   )
   (import "test:test/test" (instance (;0;) (type 0)))
   (core module (;0;)
-    (table (;0;) 0 funcref)
+    (table (;0;) 1 funcref)
     (memory (;0;) 17)
     (global (;0;) (mut i32) i32.const 1048576)
     (global (;1;) i32 i32.const 1048592)
-    (global (;2;) i32 i32.const 0)
+    (global (;2;) i32 i32.const 1)
     (global (;3;) (mut i32) i32.const 0)
     (global (;4;) i32 i32.const 1048624)
-    (global (;5;) i32 i32.const 0)
+    (global (;5;) i32 i32.const 1)
     (global (;6;) i32 i32.const 1048624)
-    (global (;7;) i32 i32.const 0)
+    (global (;7;) i32 i32.const 1)
     (global (;8;) (mut i32) i32.const 0)
     (global (;9;) (mut i32) i32.const 1048640)
     (global (;10;) (mut i32) i32.const 1114112)
@@ -173,8 +173,8 @@
       call 3
     )
     (start 4)
-    (elem (;0;) (i32.const 0) func)
-    (elem (;1;) (i32.const 0) func)
+    (elem (;0;) (i32.const 1) func)
+    (elem (;1;) (i32.const 1) func)
     (data (;0;) (i32.const 1048576) "\00\00\00\00\00\00\10\00")
     (@producers
       (processed-by "wit-component" "$CARGO_PKG_VERSION")
diff --git a/tests/roundtrip.rs b/tests/roundtrip.rs
index f3adde04..a9387318 100644
--- a/tests/roundtrip.rs
+++ b/tests/roundtrip.rs
@@ -145,7 +145,6 @@ fn skip_validation(test: &Path) -> bool {
     let broken = &[
         "gc/gc-array.wat",
         "gc/gc-struct.wat",
-        "proposals/gc/array.wast",
         "proposals/gc/array_copy.wast",
         "proposals/gc/array_fill.wast",
         "proposals/gc/array_init_data.wast",
diff --git a/tests/snapshots/testsuite/proposals/gc/array.wast/0.print b/tests/snapshots/testsuite/proposals/gc/array.wast/0.print
new file mode 100644
index 00000000..e062bd57
--- /dev/null
+++ b/tests/snapshots/testsuite/proposals/gc/array.wast/0.print
@@ -0,0 +1,22 @@
+(module
+  (type (;0;) (array i8))
+  (type (;1;) (array i16))
+  (type (;2;) (array i32))
+  (type (;3;) (array i64))
+  (type (;4;) (array f32))
+  (type (;5;) (array f64))
+  (type (;6;) (array anyref))
+  (type (;7;) (array (ref struct)))
+  (type (;8;) (array (ref 0)))
+  (type (;9;) (array (ref null 1)))
+  (type (;10;) (array (mut i8)))
+  (type (;11;) (array (mut i16)))
+  (type (;12;) (array (mut i32)))
+  (type (;13;) (array (mut i64)))
+  (type (;14;) (array (mut i32)))
+  (type (;15;) (array (mut i64)))
+  (type (;16;) (array (mut anyref)))
+  (type (;17;) (array (mut (ref struct))))
+  (type (;18;) (array (mut (ref 0))))
+  (type (;19;) (array (mut i31ref)))
+)
\ No newline at end of file
diff --git a/tests/snapshots/testsuite/proposals/gc/array.wast/13.print b/tests/snapshots/testsuite/proposals/gc/array.wast/13.print
new file mode 100644
index 00000000..3e6570d6
--- /dev/null
+++ b/tests/snapshots/testsuite/proposals/gc/array.wast/13.print
@@ -0,0 +1,57 @@
+(module
+  (type $vec (;0;) (array f32))
+  (type $mvec (;1;) (array (mut f32)))
+  (type (;2;) (func (result (ref 0))))
+  (type (;3;) (func (param i32 (ref 0)) (result f32)))
+  (type (;4;) (func (param i32) (result f32)))
+  (type (;5;) (func (param i32 (ref 1) f32) (result f32)))
+  (type (;6;) (func (param i32 f32) (result f32)))
+  (type (;7;) (func (param (ref array)) (result i32)))
+  (type (;8;) (func (result i32)))
+  (func $new (;0;) (type 2) (result (ref 0))
+    f32.const 0x1p+0 (;=1;)
+    f32.const 0x1p+1 (;=2;)
+    array.new_fixed $vec 2
+  )
+  (func $get (;1;) (type 3) (param $i i32) (param $v (ref 0)) (result f32)
+    local.get $v
+    local.get $i
+    array.get $vec
+  )
+  (func (;2;) (type 4) (param $i i32) (result f32)
+    local.get $i
+    call $new
+    call $get
+  )
+  (func $set_get (;3;) (type 5) (param $i i32) (param $v (ref 1)) (param $y f32) (result f32)
+    local.get $v
+    local.get $i
+    local.get $y
+    array.set $mvec
+    local.get $v
+    local.get $i
+    array.get $mvec
+  )
+  (func (;4;) (type 6) (param $i i32) (param $y f32) (result f32)
+    local.get $i
+    f32.const 0x1p+0 (;=1;)
+    f32.const 0x1p+1 (;=2;)
+    f32.const 0x1.8p+1 (;=3;)
+    array.new_fixed $mvec 3
+    local.get $y
+    call $set_get
+  )
+  (func $len (;5;) (type 7) (param $v (ref array)) (result i32)
+    local.get $v
+    array.len
+  )
+  (func (;6;) (type 8) (result i32)
+    call $new
+    call $len
+  )
+  (global (;0;) (ref 0) f32.const 0x1p+0 (;=1;) f32.const 0x1p+1 (;=2;) array.new_fixed $vec 2)
+  (export "new" (func $new))
+  (export "get" (func 2))
+  (export "set_get" (func 4))
+  (export "len" (func 6))
+)
\ No newline at end of file
diff --git a/tests/snapshots/testsuite/proposals/gc/array.wast/2.print b/tests/snapshots/testsuite/proposals/gc/array.wast/2.print
new file mode 100644
index 00000000..8155ca55
--- /dev/null
+++ b/tests/snapshots/testsuite/proposals/gc/array.wast/2.print
@@ -0,0 +1,9 @@
+(module
+  (rec
+    (type $s0 (;0;) (array (ref 1)))
+    (type $s1 (;1;) (array (ref 0)))
+  )
+  (type $forward (;2;) (array i32))
+  (type (;3;) (func (param (ref 2))))
+  (func (;0;) (type 3) (param (ref 2)))
+)
\ No newline at end of file
diff --git a/tests/snapshots/testsuite/proposals/gc/array.wast/21.print b/tests/snapshots/testsuite/proposals/gc/array.wast/21.print
new file mode 100644
index 00000000..01c55fa8
--- /dev/null
+++ b/tests/snapshots/testsuite/proposals/gc/array.wast/21.print
@@ -0,0 +1,56 @@
+(module
+  (type $vec (;0;) (array i8))
+  (type $mvec (;1;) (array (mut i8)))
+  (type (;2;) (func (result (ref 0))))
+  (type (;3;) (func (param i32 (ref 0)) (result i32)))
+  (type (;4;) (func (param i32) (result i32)))
+  (type (;5;) (func (param i32 (ref 1) i32) (result i32)))
+  (type (;6;) (func (param i32 i32) (result i32)))
+  (type (;7;) (func (param (ref array)) (result i32)))
+  (type (;8;) (func (result i32)))
+  (func $new (;0;) (type 2) (result (ref 0))
+    i32.const 1
+    i32.const 3
+    array.new_data $vec $d
+  )
+  (func $get (;1;) (type 3) (param $i i32) (param $v (ref 0)) (result i32)
+    local.get $v
+    local.get $i
+    array.get_u $vec
+  )
+  (func (;2;) (type 4) (param $i i32) (result i32)
+    local.get $i
+    call $new
+    call $get
+  )
+  (func $set_get (;3;) (type 5) (param $i i32) (param $v (ref 1)) (param $y i32) (result i32)
+    local.get $v
+    local.get $i
+    local.get $y
+    array.set $mvec
+    local.get $v
+    local.get $i
+    array.get_u $mvec
+  )
+  (func (;4;) (type 6) (param $i i32) (param $y i32) (result i32)
+    local.get $i
+    i32.const 1
+    i32.const 3
+    array.new_data $mvec $d
+    local.get $y
+    call $set_get
+  )
+  (func $len (;5;) (type 7) (param $v (ref array)) (result i32)
+    local.get $v
+    array.len
+  )
+  (func (;6;) (type 8) (result i32)
+    call $new
+    call $len
+  )
+  (export "new" (func $new))
+  (export "get" (func 2))
+  (export "set_get" (func 4))
+  (export "len" (func 6))
+  (data $d (;0;) "\00\01\02\03\04")
+)
\ No newline at end of file
diff --git a/tests/snapshots/testsuite/proposals/gc/array.wast/29.print b/tests/snapshots/testsuite/proposals/gc/array.wast/29.print
new file mode 100644
index 00000000..7a67d2f0
--- /dev/null
+++ b/tests/snapshots/testsuite/proposals/gc/array.wast/29.print
@@ -0,0 +1,79 @@
+(module
+  (type $bvec (;0;) (array i8))
+  (type $vec (;1;) (array (ref 0)))
+  (type $mvec (;2;) (array (mut (ref 0))))
+  (type $nvec (;3;) (array (ref null 0)))
+  (type $avec (;4;) (array (mut anyref)))
+  (type (;5;) (func (result (ref 1))))
+  (type (;6;) (func (result (ref 3))))
+  (type (;7;) (func (result (ref 4))))
+  (type (;8;) (func (param i32 i32 (ref 1)) (result i32)))
+  (type (;9;) (func (param i32 i32) (result i32)))
+  (type (;10;) (func (param i32 i32 (ref 2) i32) (result i32)))
+  (type (;11;) (func (param i32 i32 i32) (result i32)))
+  (type (;12;) (func (param (ref array)) (result i32)))
+  (type (;13;) (func (result i32)))
+  (func $new (;0;) (type 5) (result (ref 1))
+    i32.const 0
+    i32.const 2
+    array.new_elem $vec $e
+  )
+  (func $sub1 (;1;) (type 6) (result (ref 3))
+    i32.const 0
+    i32.const 2
+    array.new_elem $nvec $e
+  )
+  (func $sub2 (;2;) (type 7) (result (ref 4))
+    i32.const 0
+    i32.const 2
+    array.new_elem $avec $e
+  )
+  (func $get (;3;) (type 8) (param $i i32) (param $j i32) (param $v (ref 1)) (result i32)
+    local.get $v
+    local.get $i
+    array.get $vec
+    local.get $j
+    array.get_u $bvec
+  )
+  (func (;4;) (type 9) (param $i i32) (param $j i32) (result i32)
+    local.get $i
+    local.get $j
+    call $new
+    call $get
+  )
+  (func $set_get (;5;) (type 10) (param $i i32) (param $j i32) (param $v (ref 2)) (param $y i32) (result i32)
+    local.get $v
+    local.get $i
+    local.get $v
+    local.get $y
+    array.get $mvec
+    array.set $mvec
+    local.get $v
+    local.get $i
+    array.get $mvec
+    local.get $j
+    array.get_u $bvec
+  )
+  (func (;6;) (type 11) (param $i i32) (param $j i32) (param $y i32) (result i32)
+    local.get $i
+    local.get $j
+    i32.const 0
+    i32.const 2
+    array.new_elem $mvec $e
+    local.get $y
+    call $set_get
+  )
+  (func $len (;7;) (type 12) (param $v (ref array)) (result i32)
+    local.get $v
+    array.len
+  )
+  (func (;8;) (type 13) (result i32)
+    call $new
+    call $len
+  )
+  (export "new" (func $new))
+  (export "get" (func 4))
+  (export "set_get" (func 6))
+  (export "len" (func 8))
+  (elem $e (;0;) (ref 0) (item i32.const 7 i32.const 3 array.new $bvec) (item i32.const 1 i32.const 2 array.new_fixed $bvec 2))
+)
\ No newline at end of file
diff --git a/tests/snapshots/testsuite/proposals/gc/array.wast/41.print b/tests/snapshots/testsuite/proposals/gc/array.wast/41.print
new file mode 100644
index 00000000..735f2a43
--- /dev/null
+++ b/tests/snapshots/testsuite/proposals/gc/array.wast/41.print
@@ -0,0 +1,20 @@
+(module
+  (type $t (;0;) (array (mut i32)))
+  (type (;1;) (func))
+  (func (;0;) (type 1)
+    (local (ref null 0))
+    local.get 0
+    i32.const 0
+    array.get $t
+    drop
+  )
+  (func (;1;) (type 1)
+    (local (ref null 0))
+    local.get 0
+    i32.const 0
+    i32.const 0
+    array.set $t
+  )
+  (export "array.get-null" (func 0))
+  (export "array.set-null" (func 1))
+)
\ No newline at end of file
diff --git a/tests/snapshots/testsuite/proposals/gc/array.wast/5.print b/tests/snapshots/testsuite/proposals/gc/array.wast/5.print
new file mode 100644
index 00000000..04d8f668
--- /dev/null
+++ b/tests/snapshots/testsuite/proposals/gc/array.wast/5.print
@@ -0,0 +1,55 @@
+(module
+  (type $vec (;0;) (array f32))
+  (type $mvec (;1;) (array (mut f32)))
+  (type (;2;) (func (result (ref 0))))
+  (type (;3;) (func (param i32 (ref 0)) (result f32)))
+  (type (;4;) (func (param i32) (result f32)))
+  (type (;5;) (func (param i32 (ref 1) f32) (result f32)))
+  (type (;6;) (func (param i32 f32) (result f32)))
+  (type (;7;) (func (param (ref array)) (result i32)))
+  (type (;8;) (func (result i32)))
+  (func $new (;0;) (type 2) (result (ref 0))
+    i32.const 3
+    array.new_default $vec
+  )
+  (func $get (;1;) (type 3) (param $i i32) (param $v (ref 0)) (result f32)
+    local.get $v
+    local.get $i
+    array.get $vec
+  )
+  (func (;2;) (type 4) (param $i i32) (result f32)
+    local.get $i
+    call $new
+    call $get
+  )
+  (func $set_get (;3;) (type 5) (param $i i32) (param $v (ref 1)) (param $y f32) (result f32)
+    local.get $v
+    local.get $i
+    local.get $y
+    array.set $mvec
+    local.get $v
+    local.get $i
+    array.get $mvec
+  )
+  (func (;4;) (type 6) (param $i i32) (param $y f32) (result f32)
+    local.get $i
+    i32.const 3
+    array.new_default $mvec
+    local.get $y
+    call $set_get
+  )
+  (func $len (;5;) (type 7) (param $v (ref array)) (result i32)
+    local.get $v
+    array.len
+  )
+  (func (;6;) (type 8) (result i32)
+    call $new
+    call $len
+  )
+  (global (;0;) (ref 0) f32.const 0x1p+0 (;=1;) i32.const 3 array.new $vec)
+  (global (;1;) (ref 0) i32.const 3 array.new_default $vec)
+  (export "new" (func $new))
+  (export "get" (func 2))
+  (export "set_get" (func 4))
+  (export "len" (func 6))
+)
\ No newline at end of file