Repository: bytecodealliance/wasm-tools
Pull request #2183 (2025-05-08 21:49:33)

CM+GC: Define and validate GC lowerings for most component model types
This commit defines the expected lowerings for most component model types (primitives, records, strings, tuples, etc..) and implements their validation.

This commit also defines futures and streams as `externref`s in the GC ABI, but it does not plumb that through to all of the async intrinsics yet.

Finally, this commit does *not* yet define the expected lowerings for variants and variant-like types (options and results). This is because it isn't 100% clear how to lower sum types to GC types yet, especially with the way that we are doing shallow-matching to allow for the "same" type from any rec group to be used.

fitzgen: > What are you thinking of doing with the `Abi` field here? It looks like it's not used, although I could be missing it. I was naively expecting one direction to require a precise `struct` where the other direction would accept up to an `anyref` or something like that but I didn't see that.

Yeah I wanted to leave the option open for doing just that, but I didn't want to bite it off quite yet.

> Do you have an idea for how variants will be supported? I figure that's something we should at least have a plan for before landing the other bits since it's such a foundational data type in the component model

My thinking is that we will match against an empty `struct` type (in whatever rec group and with whatever subtyping of other empty `struct` types), check that it is not final, and then when lowering a variant at runtime, we lower it to a subtype of that empty `struct` type with the the variant's lowered payload values defined as fields that is defined in a singleton rec group. It doesn't give quite as much flexibility as product types get, but I can't think of anything better.

The main alternative I've come up with is adding new canonical options for saying something like "the `i`th variant of this type should be lowered to core type `$foo`", but that seems super wordy and annoying to use...
fitzgen: Rebased and review comments addressed. This should be good for a final review.

Patch:
diff --git a/crates/wasmparser/src/lib.rs b/crates/wasmparser/src/lib.rs
index f1c9071b..0a15052f 100644
--- a/crates/wasmparser/src/lib.rs
+++ b/crates/wasmparser/src/lib.rs
@@ -1286,6 +1286,15 @@ macro_rules! bail {
     ($($arg:tt)*) => {return Err(format_err!($($arg)*))}
 }
 
+#[cfg(all(feature = "component-model", feature = "validate"))] // Only used in component-model code right now.
+macro_rules! ensure {
+    ($cond:expr, $($arg:tt)*) => {
+        if !$cond {
+            bail!($($arg)*);
+        }
+    }
+}
+
 pub use crate::arity::*;
 pub use crate::binary_reader::{BinaryReader, BinaryReaderError, Result};
 pub use crate::features::*;
diff --git a/crates/wasmparser/src/readers/component/types.rs b/crates/wasmparser/src/readers/component/types.rs
index 888a977c..5a9dfb24 100644
--- a/crates/wasmparser/src/readers/component/types.rs
+++ b/crates/wasmparser/src/readers/component/types.rs
@@ -1,9 +1,8 @@
 use crate::limits::*;
 use crate::prelude::*;
-use crate::RecGroup;
 use crate::{
     BinaryReader, ComponentAlias, ComponentExportName, ComponentImport, ComponentTypeRef,
-    FromReader, Import, Result, SectionLimited, TypeRef, ValType,
+    FromReader, Import, RecGroup, Result, SectionLimited, TypeRef, ValType,
 };
 use core::fmt;
 
diff --git a/crates/wasmparser/src/validator/component.rs b/crates/wasmparser/src/validator/component.rs
index 3799be94..48b511bd 100644
--- a/crates/wasmparser/src/validator/component.rs
+++ b/crates/wasmparser/src/validator/component.rs
@@ -207,6 +207,24 @@ impl ExternKind {
     }
 }
 
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
+pub(crate) enum StringEncoding {
+    #[default]
+    Utf8,
+    Utf16,
+    CompactUtf16,
+}
+
+impl core::fmt::Display for StringEncoding {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        f.write_str(match self {
+            Self::Utf8 => "utf8",
+            Self::Utf16 => "utf16",
+            Self::CompactUtf16 => "latin1-utf16",
+        })
+    }
+}
+
 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
 pub(crate) enum Concurrency {
     /// Synchronous.
@@ -234,6 +252,7 @@ impl Concurrency {
 }
 
 pub(crate) struct CanonicalOptions {
+    pub(crate) string_encoding: StringEncoding,
     pub(crate) memory: Option<u32>,
     pub(crate) realloc: Option<u32>,
     pub(crate) post_return: Option<u32>,
@@ -1237,7 +1256,7 @@ impl ComponentState {
         options.check_lift(types, self, core_ty, offset)?;
         let func_ty = ty.lower(types, &options, Abi::Lift, offset)?;
         debug_assert!(options.core_type.is_none());
-        let lowered_core_ty_id = types.intern_func_type(func_ty, offset);
+        let lowered_core_ty_id = func_ty.intern(types, offset);
 
         if core_ty_id == lowered_core_ty_id {
             self.funcs
@@ -1293,7 +1312,7 @@ impl ComponentState {
         let options = self.check_options(types, options, offset)?;
         options.check_lower(offset)?;
         let func_ty = ty.lower(types, &options, Abi::Lower, offset)?;
-        let ty_id = options.check_core_type(types, func_ty, offset)?;
+        let ty_id = func_ty.intern(types, offset);
 
         self.core_funcs.push(ty_id);
         Ok(())
@@ -1400,8 +1419,7 @@ impl ComponentState {
         options.require_sync(offset, "task.return")?;
 
         let func_ty = func_ty.lower(types, &options, Abi::Lower, offset)?;
-        assert!(func_ty.results().is_empty());
-        let ty_id = options.check_core_type(types, func_ty, offset)?;
+        let ty_id = func_ty.intern(types, offset);
 
         self.core_funcs.push(ty_id);
         Ok(())
@@ -2363,12 +2381,18 @@ impl ComponentState {
                         Some(existing) => {
                             bail!(
                                 offset,
-                                "canonical encoding option `{}` conflicts with option `{}`",
-                                display(existing),
+                                "canonical encoding option `{existing}` conflicts with option `{}`",
                                 display(*option),
                             )
                         }
-                        None => encoding = Some(*option),
+                        None => {
+                            encoding = Some(match option {
+                                CanonicalOption::UTF8 => StringEncoding::Utf8,
+                                CanonicalOption::UTF16 => StringEncoding::Utf16,
+                                CanonicalOption::CompactUTF16 => StringEncoding::CompactUtf16,
+                                _ => unreachable!(),
+                            });
+                        }
                     }
                 }
                 CanonicalOption::Memory(idx) => {
@@ -2503,6 +2527,8 @@ impl ComponentState {
             }
         }
 
+        let string_encoding = encoding.unwrap_or_default();
+
         let concurrency = match (is_async, callback, post_return.is_some()) {
             (false, Some(_), _) => {
                 bail!(offset, "cannot specify callback without async")
@@ -2519,6 +2545,7 @@ impl ComponentState {
         }
 
         Ok(CanonicalOptions {
+            string_encoding,
             memory,
             realloc,
             post_return,
diff --git a/crates/wasmparser/src/validator/component_types.rs b/crates/wasmparser/src/validator/component_types.rs
index 53d0f820..c29dcbd8 100644
--- a/crates/wasmparser/src/validator/component_types.rs
+++ b/crates/wasmparser/src/validator/component_types.rs
@@ -2,12 +2,13 @@
 
 use super::component::ExternKind;
 use super::{CanonicalOptions, Concurrency};
-use crate::prelude::*;
 use crate::validator::names::KebabString;
 use crate::validator::types::{
     CoreTypeId, EntityType, SnapshotList, TypeAlloc, TypeData, TypeIdentifier, TypeInfo, TypeList,
     Types, TypesKind, TypesRef, TypesRefKind,
 };
+use crate::validator::StringEncoding;
+use crate::{prelude::*, AbstractHeapType, CompositeInnerType, HeapType, RefType, StorageType};
 use crate::{
     BinaryReaderError, FuncType, MemoryType, PrimitiveValType, Result, TableType, ValType,
 };
@@ -131,6 +132,176 @@ impl Default for LoweredSignature {
     }
 }
 
+impl PrimitiveValType {
+    pub(crate) fn lower_gc(
+        &self,
+        types: &TypeList,
+        _abi: Abi,
+        options: &CanonicalOptions,
+        offset: usize,
+        core: ArgOrField,
+    ) -> Result<()> {
+        match (self, core) {
+            (
+                PrimitiveValType::Bool,
+                ArgOrField::Field(StorageType::I8) | ArgOrField::Arg(ValType::I32),
+            ) => Ok(()),
+            (PrimitiveValType::Bool, ArgOrField::Arg(_)) => bail!(
+                offset,
+                "expected to lower component `bool` type to core `i32` type, found `{core}`"
+            ),
+            (PrimitiveValType::Bool, ArgOrField::Field(_)) => bail!(
+                offset,
+                "expected to lower component `bool` type to core `i8` type, found `{core}`"
+            ),
+
+            (
+                PrimitiveValType::S8,
+                ArgOrField::Field(StorageType::I8) | ArgOrField::Arg(ValType::I32),
+            ) => Ok(()),
+            (PrimitiveValType::S8, ArgOrField::Arg(_)) => bail!(
+                offset,
+                "expected to lower component `s8` type to core `i32` type, found `{core}`"
+            ),
+            (PrimitiveValType::S8, ArgOrField::Field(_)) => bail!(
+                offset,
+                "expected to lower component `s8` type to core `i8` type, found `{core}`"
+            ),
+
+            (
+                PrimitiveValType::U8,
+                ArgOrField::Field(StorageType::I8) | ArgOrField::Arg(ValType::I32),
+            ) => Ok(()),
+            (PrimitiveValType::U8, ArgOrField::Arg(_)) => bail!(
+                offset,
+                "expected to lower component `u8` type to core `i32` type, found `{core}`"
+            ),
+            (PrimitiveValType::U8, ArgOrField::Field(_)) => bail!(
+                offset,
+                "expected to lower component `u8` type to core `i8` type, found `{core}`"
+            ),
+
+            (
+                PrimitiveValType::S16,
+                ArgOrField::Field(StorageType::I16) | ArgOrField::Arg(ValType::I32),
+            ) => Ok(()),
+            (PrimitiveValType::S16, ArgOrField::Arg(_)) => bail!(
+                offset,
+                "expected to lower component `s16` type to core `i32` type, found `{core}`"
+            ),
+            (PrimitiveValType::S16, ArgOrField::Field(_)) => bail!(
+                offset,
+                "expected to lower component `s16` type to core `i16` type, found `{core}`"
+            ),
+
+            (
+                PrimitiveValType::U16,
+                ArgOrField::Field(StorageType::I16) | ArgOrField::Arg(ValType::I32),
+            ) => Ok(()),
+            (PrimitiveValType::U16, ArgOrField::Arg(_)) => bail!(
+                offset,
+                "expected to lower component `u16` type to core `i32` type, found `{core}`"
+            ),
+            (PrimitiveValType::U16, ArgOrField::Field(_)) => bail!(
+                offset,
+                "expected to lower component `u16` type to core `i16` type, found `{core}`"
+            ),
+
+            (PrimitiveValType::S32, _) if core.as_val_type() == Some(ValType::I32) => Ok(()),
+            (PrimitiveValType::S32, _) => bail!(
+                offset,
+                "expected to lower component `s32` type to core `i32` type, found `{core}`"
+            ),
+
+            (PrimitiveValType::U32, _) if core.as_val_type() == Some(ValType::I32) => Ok(()),
+            (PrimitiveValType::U32, _) => bail!(
+                offset,
+                "expected to lower component `u32` type to core `i32` type, found `{core}`"
+            ),
+
+            (PrimitiveValType::S64, _) if core.as_val_type() == Some(ValType::I64) => Ok(()),
+            (PrimitiveValType::S64, _) => bail!(
+                offset,
+                "expected to lower component `s64` type to core `i64` type, found `{core}`"
+            ),
+
+            (PrimitiveValType::U64, _) if core.as_val_type() == Some(ValType::I64) => Ok(()),
+            (PrimitiveValType::U64, _) => bail!(
+                offset,
+                "expected to lower component `u64` type to core `i64` type, found `{core}`"
+            ),
+
+            (PrimitiveValType::F32, _) if core.as_val_type() == Some(ValType::F32) => Ok(()),
+            (PrimitiveValType::F32, _) => bail!(
+                offset,
+                "expected to lower component `f32` type to core `f32` type, found `{core}`"
+            ),
+
+            (PrimitiveValType::F64, _) if core.as_val_type() == Some(ValType::F64) => Ok(()),
+            (PrimitiveValType::F64, _) => bail!(
+                offset,
+                "expected to lower component `f64` type to core `f64` type, found `{core}`"
+            ),
+
+            (PrimitiveValType::Char, _) if core.as_val_type() == Some(ValType::I32) => Ok(()),
+            (PrimitiveValType::Char, _) => bail!(
+                offset,
+                "expected to lower component `char` type to core `i32` type, found `{core}`"
+            ),
+
+            (PrimitiveValType::String, _) => {
+                let type_mismatch_err = || {
+                    let expected = match options.string_encoding {
+                        StringEncoding::Utf8 | StringEncoding::CompactUtf16 => {
+                            "(ref null? (array (mut? i8)))"
+                        }
+                        StringEncoding::Utf16 => "(ref null? (array (mut? i16)))",
+                    };
+                    bail!(
+                        offset,
+                        "expected to lower component `string` type to core `{expected}` \
+                         type, found `{core}`"
+                    )
+                };
+
+                match core.as_concrete_ref() {
+                    Some(id) => match types[id].composite_type.inner {
+                        CompositeInnerType::Array(ty) => {
+                            match (options.string_encoding, ty.0.element_type) {
+                                (
+                                    StringEncoding::Utf8 | StringEncoding::CompactUtf16,
+                                    StorageType::I8,
+                                )
+                                | (StringEncoding::Utf16, StorageType::I16) => Ok(()),
+                                _ => type_mismatch_err(),
+                            }
+                        }
+                        _ => type_mismatch_err(),
+                    },
+                    _ => type_mismatch_err(),
+                }
+            }
+
+            (PrimitiveValType::ErrorContext, _) => {
+                if let Some(r) = core.as_ref_type() {
+                    if let HeapType::Abstract {
+                        shared: _,
+                        ty: AbstractHeapType::Extern,
+                    } = r.heap_type()
+                    {
+                        return Ok(());
+                    }
+                }
+                bail!(
+                    offset,
+                    "expected to lower component `error-context` type into core `(ref null? extern)` type, but \
+                     found `{core}`",
+                )
+            }
+        }
+    }
+}
+
 fn push_primitive_wasm_types(ty: &PrimitiveValType, lowered_types: &mut LoweredTypes) -> bool {
     match ty {
         PrimitiveValType::Bool
@@ -573,6 +744,20 @@ impl ComponentValType {
             Self::Type(id) => types[*id].type_info(types),
         }
     }
+
+    fn lower_gc(
+        &self,
+        types: &TypeList,
+        abi: Abi,
+        options: &CanonicalOptions,
+        offset: usize,
+        core: ArgOrField,
+    ) -> Result<()> {
+        match self {
+            ComponentValType::Primitive(ty) => ty.lower_gc(types, abi, options, offset, core),
+            ComponentValType::Type(ty) => types[*ty].lower_gc(types, abi, options, offset, core),
+        }
+    }
 }
 
 trait ModuleImportKey {
@@ -921,6 +1106,83 @@ pub(crate) enum Abi {
     Lower,
 }
 
+impl Abi {
+    fn invert(&self) -> Self {
+        match self {
+            Abi::Lift => Abi::Lower,
+            Abi::Lower => Abi::Lift,
+        }
+    }
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub(crate) enum ArgOrField {
+    /// Lifting to, or lowering from, an argument value.
+    Arg(ValType),
+    /// Lifting to, or lowering from, a struct field or array element.
+    Field(StorageType),
+}
+
+impl From<ValType> for ArgOrField {
+    fn from(v: ValType) -> Self {
+        Self::Arg(v)
+    }
+}
+
+impl From<StorageType> for ArgOrField {
+    fn from(v: StorageType) -> Self {
+        Self::Field(v)
+    }
+}
+
+impl core::fmt::Display for ArgOrField {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            ArgOrField::Arg(ty) => core::fmt::Display::fmt(ty, f),
+            ArgOrField::Field(ty) => core::fmt::Display::fmt(ty, f),
+        }
+    }
+}
+
+impl ArgOrField {
+    pub(crate) fn as_val_type(self) -> Option<ValType> {
+        match self {
+            ArgOrField::Arg(ty) | ArgOrField::Field(StorageType::Val(ty)) => Some(ty),
+            _ => None,
+        }
+    }
+
+    pub(crate) fn as_ref_type(self) -> Option<RefType> {
+        self.as_val_type()?.as_reference_type()
+    }
+
+    pub(crate) fn as_concrete_ref(self) -> Option<CoreTypeId> {
+        match self.as_ref_type()?.heap_type() {
+            HeapType::Abstract { .. } => None,
+            HeapType::Concrete(idx) => {
+                let id = idx
+                    .as_core_type_id()
+                    .expect("validation only sees core type ids");
+                Some(id)
+            }
+        }
+    }
+}
+
+pub(crate) enum LoweredFuncType {
+    New(FuncType),
+    Existing(CoreTypeId),
+}
+
+impl LoweredFuncType {
+    pub(crate) fn intern(self, types: &mut TypeAlloc, offset: usize) -> CoreTypeId {
+        match self {
+            LoweredFuncType::New(ty) => types.intern_func_type(ty, offset),
+            LoweredFuncType::Existing(id) => id,
+        }
+    }
+}
+
 impl ComponentFuncType {
     /// Lowers the component function type to core parameter and result types for the
     /// canonical ABI.
@@ -930,9 +1192,13 @@ impl ComponentFuncType {
         options: &CanonicalOptions,
         abi: Abi,
         offset: usize,
-    ) -> Result<FuncType> {
+    ) -> Result<LoweredFuncType> {
         let mut sig = LoweredSignature::default();
 
+        if options.gc {
+            return self.lower_gc(types, abi, options, offset);
+        }
+
         if abi == Abi::Lower && options.concurrency.is_async() {
             sig.params.max = MAX_FLAT_ASYNC_PARAMS;
         }
@@ -1013,7 +1279,48 @@ impl ComponentFuncType {
             }
         }
 
-        Ok(sig.into_func_type())
+        Ok(LoweredFuncType::New(sig.into_func_type()))
+    }
+
+    fn lower_gc(
+        &self,
+        types: &TypeList,
+        abi: Abi,
+        options: &CanonicalOptions,
+        offset: usize,
+    ) -> Result<LoweredFuncType> {
+        let core_type_id = options.core_type.unwrap();
+        let core_func_ty = types[core_type_id].unwrap_func();
+
+        ensure!(
+            core_func_ty.params().len() == self.params.len(),
+            offset,
+            "declared `core-type` has {} parameters, but component function has {} parameters",
+            core_func_ty.params().len(),
+            self.params.len(),
+        );
+        for (core, (_name, comp)) in core_func_ty.params().iter().zip(self.params.iter()) {
+            comp.lower_gc(types, abi.invert(), options, offset, (*core).into())?;
+        }
+
+        ensure!(
+            core_func_ty.results().len() == usize::from(self.result.is_some()),
+            offset,
+            "declared `core-type` has {} results, but component function has {} results",
+            core_func_ty.results().len(),
+            usize::from(self.result.is_some()),
+        );
+        if let Some(result) = self.result {
+            result.lower_gc(
+                types,
+                abi,
+                options,
+                offset,
+                core_func_ty.results()[0].into(),
+            )?;
+        }
+
+        Ok(LoweredFuncType::Existing(core_type_id))
     }
 }
 
@@ -1034,6 +1341,26 @@ pub struct RecordType {
     /// The map of record fields.
     pub fields: IndexMap<KebabString, ComponentValType>,
 }
+impl RecordType {
+    fn lower_gc(
+        &self,
+        types: &TypeList,
+        abi: Abi,
+        options: &CanonicalOptions,
+        offset: usize,
+        core: ArgOrField,
+    ) -> Result<()> {
+        lower_gc_product_type(
+            self.fields.values(),
+            types,
+            abi,
+            options,
+            offset,
+            core,
+            "record",
+        )
+    }
+}
 
 /// Represents a variant type.
 #[derive(Debug, Clone)]
@@ -1043,6 +1370,19 @@ pub struct VariantType {
     /// The map of variant cases.
     pub cases: IndexMap<KebabString, VariantCase>,
 }
+impl VariantType {
+    fn lower_gc(
+        &self,
+        types: &TypeList,
+        abi: Abi,
+        options: &CanonicalOptions,
+        offset: usize,
+        core: ArgOrField,
+    ) -> core::result::Result<(), BinaryReaderError> {
+        let _ = (types, abi, options, offset, core);
+        todo!("variant types and CM+GC")
+    }
+}
 
 /// Represents a tuple type.
 #[derive(Debug, Clone)]
@@ -1052,6 +1392,26 @@ pub struct TupleType {
     /// The types of the tuple.
     pub types: Box<[ComponentValType]>,
 }
+impl TupleType {
+    fn lower_gc(
+        &self,
+        types: &TypeList,
+        abi: Abi,
+        options: &CanonicalOptions,
+        offset: usize,
+        core: ArgOrField,
+    ) -> core::result::Result<(), BinaryReaderError> {
+        lower_gc_product_type(
+            self.types.iter(),
+            types,
+            abi,
+            options,
+            offset,
+            core,
+            "tuple",
+        )
+    }
+}
 
 /// Represents a component defined type.
 #[derive(Debug, Clone)]
@@ -1243,6 +1603,135 @@ impl ComponentDefinedType {
             ComponentDefinedType::Stream(_) => "stream",
         }
     }
+
+    fn lower_gc(
+        &self,
+        types: &TypeList,
+        abi: Abi,
+        options: &CanonicalOptions,
+        offset: usize,
+        core: ArgOrField,
+    ) -> Result<()> {
+        match self {
+            ComponentDefinedType::Primitive(ty) => ty.lower_gc(types, abi, options, offset, core),
+
+            ComponentDefinedType::Record(ty) => ty.lower_gc(types, abi, options, offset, core),
+
+            ComponentDefinedType::Variant(ty) => ty.lower_gc(types, abi, options, offset, core),
+
+            ComponentDefinedType::List(ty) | ComponentDefinedType::FixedSizeList(ty, _) => {
+                let id = match core.as_concrete_ref() {
+                    Some(id) => id,
+                    None => bail!(
+                        offset,
+                        "expected to lower component `list` type into `(ref null? (array ...))`, but \
+                         found `{core}`",
+                    ),
+                };
+                let array_ty = match types[id].composite_type.inner {
+                    CompositeInnerType::Array(ty) => ty,
+                    _ => bail!(
+                        offset,
+                        "expected to lower component `list` type into `(ref null? (array ...))`, but \
+                         found `{core}`",
+                    ),
+                };
+                ty.lower_gc(types, abi, options, offset, array_ty.0.element_type.into())
+            }
+
+            ComponentDefinedType::Tuple(ty) => ty.lower_gc(types, abi, options, offset, core),
+
+            ComponentDefinedType::Flags(flags) => {
+                assert!(flags.len() <= 32, "required by validation");
+                if core.as_val_type() == Some(ValType::I32) {
+                    Ok(())
+                } else {
+                    bail!(
+                        offset,
+                        "expected to lower component `flags` type into core `i32` type, but \
+                         found `{core}`",
+                    )
+                }
+            }
+
+            ComponentDefinedType::Enum(_) => {
+                if core.as_val_type() == Some(ValType::I32) {
+                    Ok(())
+                } else {
+                    bail!(
+                        offset,
+                        "expected to lower component `enum` type into core `i32` type, but \
+                         found `{core}`",
+                    )
+                }
+            }
+
+            ComponentDefinedType::Option(_) => todo!("option types and CM+GC"),
+
+            ComponentDefinedType::Result { .. } => todo!("result types and CM+GC"),
+
+            ComponentDefinedType::Own(_)
+            | ComponentDefinedType::Borrow(_)
+            | ComponentDefinedType::Future(_)
+            | ComponentDefinedType::Stream(_) => {
+                if let Some(r) = core.as_ref_type() {
+                    if let HeapType::Abstract {
+                        shared: _,
+                        ty: AbstractHeapType::Extern,
+                    } = r.heap_type()
+                    {
+                        return Ok(());
+                    }
+                }
+                bail!(
+                    offset,
+                    "expected to lower component `{}` type into core `(ref null? extern)` type, but \
+                     found `{core}`",
+                    self.desc()
+                )
+            }
+        }
+    }
+}
+
+/// Shared helper for lowering component record and tuple types to core GC
+/// types.
+fn lower_gc_product_type<'a, I>(
+    fields: I,
+    types: &TypeList,
+    abi: Abi,
+    options: &CanonicalOptions,
+    offset: usize,
+    core: ArgOrField,
+    kind: &str,
+) -> core::result::Result<(), BinaryReaderError>
+where
+    I: IntoIterator<Item = &'a ComponentValType>,
+    I::IntoIter: ExactSizeIterator,
+{
+    let fields = fields.into_iter();
+    let fields_len = fields.len();
+
+    if let Some(id) = core.as_concrete_ref() {
+        if let CompositeInnerType::Struct(ty) = &types[id].composite_type.inner {
+            ensure!(
+                ty.fields.len() == fields_len,
+                offset,
+                "core `struct` has {} fields, but component `{kind}` has {fields_len} fields",
+                ty.fields.len(),
+            );
+            for (core, comp) in ty.fields.iter().zip(fields) {
+                comp.lower_gc(types, abi, options, offset, core.element_type.into())?;
+            }
+            return Ok(());
+        }
+    }
+
+    bail!(
+        offset,
+        "expected to lower component `{kind}` type to core `(ref null? (struct ...))`, \
+         but found `{core}`",
+    )
 }
 
 /// An opaque identifier intended to be used to distinguish whether two


Test patch:
diff --git a/tests/cli/component-model-gc/core-type.wat b/tests/cli/component-model-gc/core-type.wat
index 70e97f62..ddb9e284 100644
--- a/tests/cli/component-model-gc/core-type.wat
+++ b/tests/cli/component-model-gc/core-type.wat
@@ -26,198 +26,195 @@
   "cannot specify `core-type` without `gc`"
 )
 
-;; TODO: Rewrite and reenable the following tests once we actually support GC
-;; lifting/lowering.
-
-;; (component
-;;   (import "f" (func $f (param "x" u32) (param "y" u32) (result u32)))
-;;   (core type $ty (func (param i32 i32) (result i32)))
-;;   (core func (canon lower (func $f) (core-type $ty)))
-;; )
-
-;; (assert_invalid
-;;   (component
-;;     (core module $m
-;;       (memory (export "memory") 1)
-;;       (func (export "f") (result i32) unreachable)
-;;     )
-;;     (core instance $i (instantiate $m))
-
-;;     (core type $ty (func (result i32)))
-;;     (func (export "f") (result u32)
-;;       (canon lift (core func $i "f") (core-type $ty))
-;;     )
-;;   )
-;;   "canonical option `core-type` is not allowed in `canon lift`"
-;; )
-
-;; (assert_invalid
-;;   (component
-;;     (import "f" (func $f (param "x" u32) (param "y" u32) (result u32)))
-;;     (core type $ty (func (param i64 i32) (result i32)))
-;;     (core func (canon lower (func $f) (core-type $ty)))
-;;   )
-;;   "declared core type has `[I64, I32]` parameter types, but actual lowering has `[I32, I32]` parameter types"
-;; )
-
-;; (assert_invalid
-;;   (component
-;;     (import "f" (func $f (param "x" u32) (param "y" u32) (result u32)))
-;;     (core type $ty (func (param i32 i32) (result i64)))
-;;     (core func (canon lower (func $f) (core-type $ty)))
-;;   )
-;;   "declared core type has `[I64]` result types, but actual lowering has `[I32]` result types"
-;; )
-
-;; (component
-;;   (import "f" (func $f (param "x" u32) (param "y" u32) (result u32)))
-
-;;   (core rec
-;;     (type (func))
-;;     (type $ty (func (param i32 i32) (result i32)))
-;;   )
-
-;;   (core func $f (canon lower (func $f) (core-type $ty)))
-
-;;   (core module $m
-;;     (rec
-;;       (type (func))
-;;       (type $ty (func (param i32 i32) (result i32)))
-;;     )
-;;     (import "a" "b" (func (type $ty)))
-;;   )
-
-;;   (core instance (instantiate $m (with "a" (instance (export "b" (func $f))))))
-;; )
-
-;; ;; Satisfying an import with the "same" type but from the wrong rec group should
-;; ;; fail.
-;; (assert_invalid
-;;   (component
-;;     (import "f" (func $f (param "x" u32) (param "y" u32) (result u32)))
-
-;;     (core type $ty (func (param i32 i32) (result i32)))
-;;     (core func $f (canon lower (func $f) (core-type $ty)))
-
-;;     (core module $m
-;;       (rec
-;;         (type (func))
-;;         (type $ty (func (param i32 i32) (result i32)))
-;;       )
-;;       (import "a" "b" (func (type $ty)))
-;;     )
-
-;;     (core instance (instantiate $m (with "a" (instance (export "b" (func $f))))))
-;;   )
-;;   "type mismatch for export `b` of module instantiation argument `a`"
-;; )
-
-;; ;; Satisfying an import with an exact subtype should succeed.
-;; (component
-;;   (import "f" (func $f (param "x" u32) (param "y" u32) (result u32)))
-
-;;   (core type $super_ty (sub (func (param i32 i32) (result i32))))
-;;   (core type $sub_ty (sub $super_ty (func (param i32 i32) (result i32))))
-
-;;   (core func $f (canon lower (func $f) (core-type $sub_ty)))
-
-;;   (core module $m
-;;     (type $super_ty (sub (func (param i32 i32) (result i32))))
-;;     (type $sub_ty (sub $super_ty (func (param i32 i32) (result i32))))
-;;     (import "a" "b" (func (type $sub_ty)))
-;;   )
-
-;;   (core instance (instantiate $m (with "a" (instance (export "b" (func $f))))))
-;; )
-
-;; ;; Satisfying an import with a subtype should succeed.
-;; (component
-;;   (import "f" (func $f (param "x" u32) (param "y" u32) (result u32)))
-
-;;   (core type $super_ty (sub (func (param i32 i32) (result i32))))
-;;   (core type $sub_ty (sub $super_ty (func (param i32 i32) (result i32))))
-
-;;   (core func $f (canon lower (func $f) (core-type $sub_ty)))
-
-;;   (core module $m
-;;     (type $super_ty (sub (func (param i32 i32) (result i32))))
-;;     (import "a" "b" (func (type $super_ty)))
-;;   )
-
-;;   (core instance (instantiate $m (with "a" (instance (export "b" (func $f))))))
-;; )
-
-;; ;; Satisfying an import with the "same" type but from a different subtyping
-;; ;; hierarchy should fail.
-;; (assert_invalid
-;;   (component
-;;     (import "f" (func $f (param "x" u32) (param "y" u32) (result u32)))
-
-;;     (core type $ty (func (param i32 i32) (result i32)))
-;;     (core func $f (canon lower (func $f) (core-type $ty)))
-
-;;     (core module $m
-;;       (type $super_ty (sub (func (param i32 i32) (result i32))))
-;;       (type $sub_ty (sub $super_ty (func (param i32 i32) (result i32))))
-;;       (import "a" "b" (func (type $sub_ty)))
-;;     )
-
-;;     (core instance (instantiate $m (with "a" (instance (export "b" (func $f))))))
-;;   )
-;;   "type mismatch for export `b` of module instantiation argument `a`"
-;; )
-
-;; ;; Satisfying an import with the "same" type but is a supertype, rather than a
-;; ;; subtype, should fail.
-;; (assert_invalid
-;;   (component
-;;     (import "f" (func $f (param "x" u32) (param "y" u32) (result u32)))
-
-;;     (core type $super_ty (sub (func (param i32 i32) (result i32))))
-;;     (core func $f (canon lower (func $f) (core-type $super_ty)))
-
-;;     (core module $m
-;;       (type $super_ty (sub (func (param i32 i32) (result i32))))
-;;       (type $sub_ty (sub $super_ty (func (param i32 i32) (result i32))))
-;;       (import "a" "b" (func (type $sub_ty)))
-;;     )
-
-;;     (core instance (instantiate $m (with "a" (instance (export "b" (func $f))))))
-;;   )
-;;   "type mismatch for export `b` of module instantiation argument `a`"
-;; )
-
-;; ;; Satisfying an import with the "same" type but with the wrong finality should
-;; ;; fail.
-;; (assert_invalid
-;;   (component
-;;     (import "f" (func $f (param "x" u32) (param "y" u32) (result u32)))
-
-;;     (core type $ty (sub final (func (param i32 i32) (result i32))))
-;;     (core func $f (canon lower (func $f) (core-type $ty)))
-
-;;     (core module $m
-;;       (type $ty (sub (func (param i32 i32) (result i32))))
-;;       (import "a" "b" (func (type $ty)))
-;;     )
-
-;;     (core instance (instantiate $m (with "a" (instance (export "b" (func $f))))))
-;;   )
-;;   "type mismatch for export `b` of module instantiation argument `a`"
-;; )
-;; (assert_invalid
-;;   (component
-;;     (import "f" (func $f (param "x" u32) (param "y" u32) (result u32)))
-
-;;     (core type $ty (sub (func (param i32 i32) (result i32))))
-;;     (core func $f (canon lower (func $f) (core-type $ty)))
-
-;;     (core module $m
-;;       (type $ty (sub final (func (param i32 i32) (result i32))))
-;;       (import "a" "b" (func (type $ty)))
-;;     )
-
-;;     (core instance (instantiate $m (with "a" (instance (export "b" (func $f))))))
-;;   )
-;;   "type mismatch for export `b` of module instantiation argument `a`"
-;; )
+(component
+  (import "f" (func $f (param "x" u32) (param "y" u32) (result u32)))
+  (core type $ty (func (param i32 i32) (result i32)))
+  (core func (canon lower (func $f) gc (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (core module $m
+      (memory (export "memory") 1)
+      (func (export "f") (result i32) unreachable)
+    )
+    (core instance $i (instantiate $m))
+
+    (core type $ty (func (result i32)))
+    (func (export "f") (result u32)
+      (canon lift (core func $i "f") gc (core-type $ty))
+    )
+  )
+  "canonical option `core-type` is not allowed in `canon lift`"
+)
+
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" u32) (param "y" u32) (result u32)))
+    (core type $ty (func (param i64 i32) (result i32)))
+    (core func (canon lower (func $f) gc (core-type $ty)))
+  )
+  "expected to lower component `u32` type to core `i32` type, found `i64`"
+)
+
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" u32) (param "y" u32) (result u32)))
+    (core type $ty (func (param i32 i32) (result i64)))
+    (core func (canon lower (func $f) gc (core-type $ty)))
+  )
+  "expected to lower component `u32` type to core `i32` type, found `i64`"
+)
+
+(component
+  (import "f" (func $f (param "x" u32) (param "y" u32) (result u32)))
+
+  (core rec
+    (type (func))
+    (type $ty (func (param i32 i32) (result i32)))
+  )
+
+  (core func $f (canon lower (func $f) gc (core-type $ty)))
+
+  (core module $m
+    (rec
+      (type (func))
+      (type $ty (func (param i32 i32) (result i32)))
+    )
+    (import "a" "b" (func (type $ty)))
+  )
+
+  (core instance (instantiate $m (with "a" (instance (export "b" (func $f))))))
+)
+
+;; Satisfying an import with the "same" type but from the wrong rec group should
+;; fail.
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" u32) (param "y" u32) (result u32)))
+
+    (core type $ty (func (param i32 i32) (result i32)))
+    (core func $f (canon lower (func $f) gc (core-type $ty)))
+
+    (core module $m
+      (rec
+        (type (func))
+        (type $ty (func (param i32 i32) (result i32)))
+      )
+      (import "a" "b" (func (type $ty)))
+    )
+
+    (core instance (instantiate $m (with "a" (instance (export "b" (func $f))))))
+  )
+  "type mismatch for export `b` of module instantiation argument `a`"
+)
+
+;; Satisfying an import with an exact subtype should succeed.
+(component
+  (import "f" (func $f (param "x" u32) (param "y" u32) (result u32)))
+
+  (core type $super_ty (sub (func (param i32 i32) (result i32))))
+  (core type $sub_ty (sub $super_ty (func (param i32 i32) (result i32))))
+
+  (core func $f (canon lower (func $f) gc (core-type $sub_ty)))
+
+  (core module $m
+    (type $super_ty (sub (func (param i32 i32) (result i32))))
+    (type $sub_ty (sub $super_ty (func (param i32 i32) (result i32))))
+    (import "a" "b" (func (type $sub_ty)))
+  )
+
+  (core instance (instantiate $m (with "a" (instance (export "b" (func $f))))))
+)
+
+;; Satisfying an import with a subtype should succeed.
+(component
+  (import "f" (func $f (param "x" u32) (param "y" u32) (result u32)))
+
+  (core type $super_ty (sub (func (param i32 i32) (result i32))))
+  (core type $sub_ty (sub $super_ty (func (param i32 i32) (result i32))))
+
+  (core func $f (canon lower (func $f) gc (core-type $sub_ty)))
+
+  (core module $m
+    (type $super_ty (sub (func (param i32 i32) (result i32))))
+    (import "a" "b" (func (type $super_ty)))
+  )
+
+  (core instance (instantiate $m (with "a" (instance (export "b" (func $f))))))
+)
+
+;; Satisfying an import with the "same" type but from a different subtyping
+;; hierarchy should fail.
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" u32) (param "y" u32) (result u32)))
+
+    (core type $ty (func (param i32 i32) (result i32)))
+    (core func $f (canon lower (func $f) gc (core-type $ty)))
+
+    (core module $m
+      (type $super_ty (sub (func (param i32 i32) (result i32))))
+      (type $sub_ty (sub $super_ty (func (param i32 i32) (result i32))))
+      (import "a" "b" (func (type $sub_ty)))
+    )
+
+    (core instance (instantiate $m (with "a" (instance (export "b" (func $f))))))
+  )
+  "type mismatch for export `b` of module instantiation argument `a`"
+)
+
+;; Satisfying an import with the "same" type but is a supertype, rather than a
+;; subtype, should fail.
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" u32) (param "y" u32) (result u32)))
+
+    (core type $super_ty (sub (func (param i32 i32) (result i32))))
+    (core func $f (canon lower (func $f) gc (core-type $super_ty)))
+
+    (core module $m
+      (type $super_ty (sub (func (param i32 i32) (result i32))))
+      (type $sub_ty (sub $super_ty (func (param i32 i32) (result i32))))
+      (import "a" "b" (func (type $sub_ty)))
+    )
+
+    (core instance (instantiate $m (with "a" (instance (export "b" (func $f))))))
+  )
+  "type mismatch for export `b` of module instantiation argument `a`"
+)
+
+;; Satisfying an import with the "same" type but with the wrong finality should
+;; fail.
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" u32) (param "y" u32) (result u32)))
+
+    (core type $ty (sub final (func (param i32 i32) (result i32))))
+    (core func $f (canon lower (func $f) gc (core-type $ty)))
+
+    (core module $m
+      (type $ty (sub (func (param i32 i32) (result i32))))
+      (import "a" "b" (func (type $ty)))
+    )
+
+    (core instance (instantiate $m (with "a" (instance (export "b" (func $f))))))
+  )
+  "type mismatch for export `b` of module instantiation argument `a`"
+)
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" u32) (param "y" u32) (result u32)))
+
+    (core type $ty (sub (func (param i32 i32) (result i32))))
+    (core func $f (canon lower (func $f) gc (core-type $ty)))
+
+    (core module $m
+      (type $ty (sub final (func (param i32 i32) (result i32))))
+      (import "a" "b" (func (type $ty)))
+    )
+
+    (core instance (instantiate $m (with "a" (instance (export "b" (func $f))))))
+  )
+  "type mismatch for export `b` of module instantiation argument `a`"
+)
diff --git a/tests/cli/component-model-gc/enum-types.wast b/tests/cli/component-model-gc/enum-types.wast
new file mode 100644
index 00000000..061c6141
--- /dev/null
+++ b/tests/cli/component-model-gc/enum-types.wast
@@ -0,0 +1,26 @@
+;; RUN: wast --assert default --snapshot tests/snapshots % -f gc,cm-gc
+
+(component
+  (type $enum (enum "a" "b" "c"))
+
+  (core type $ty (func (param i32)))
+
+  (import "i" (instance $i
+                (export "ty" (type $enum' (eq $enum)))
+                (export "f" (func (param "x" $enum')))))
+  (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (type $enum (enum "a" "b" "c"))
+
+    (core type $ty (func (param anyref)))
+
+    (import "i" (instance $i
+                  (export "ty" (type $enum' (eq $enum)))
+                  (export "f" (func (param "x" $enum')))))
+    (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty)))
+  )
+  "expected to lower component `enum` type into core `i32` type, but found `anyref`"
+)
diff --git a/tests/cli/component-model-gc/error-context.wast b/tests/cli/component-model-gc/error-context.wast
new file mode 100644
index 00000000..1d2b84c1
--- /dev/null
+++ b/tests/cli/component-model-gc/error-context.wast
@@ -0,0 +1,20 @@
+;; RUN: wast --assert default --snapshot tests/snapshots % -f gc,cm-gc,cm-error-context
+
+(component
+  (core type $ty (func (param externref)))
+
+  (import "i" (instance $i
+                        (export "f" (func (param "x" error-context)))))
+  (core func (canon lower (func $i "f") gc (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (core type $ty (func (param anyref)))
+
+    (import "i" (instance $i
+                          (export "f" (func (param "x" error-context)))))
+    (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty)))
+  )
+  "expected to lower component `error-context` type into core `(ref null? extern)` type, but found `anyref`"
+)
diff --git a/tests/cli/component-model-gc/flag-types.wast b/tests/cli/component-model-gc/flag-types.wast
new file mode 100644
index 00000000..cbe02e16
--- /dev/null
+++ b/tests/cli/component-model-gc/flag-types.wast
@@ -0,0 +1,26 @@
+;; RUN: wast --assert default --snapshot tests/snapshots % -f gc,cm-gc
+
+(component
+  (type $flags (flags "a" "b" "c"))
+
+  (core type $ty (func (param i32)))
+
+  (import "i" (instance $i
+                (export "ty" (type $flags' (eq $flags)))
+                (export "f" (func (param "x" $flags')))))
+  (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (type $flags (flags "a" "b" "c"))
+
+    (core type $ty (func (param anyref)))
+
+    (import "i" (instance $i
+                  (export "ty" (type $flags' (eq $flags)))
+                  (export "f" (func (param "x" $flags')))))
+    (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty)))
+  )
+  "expected to lower component `flags` type into core `i32` type, but found `anyref`"
+)
diff --git a/tests/cli/component-model-gc/futures.wast b/tests/cli/component-model-gc/futures.wast
new file mode 100644
index 00000000..6ebfe50c
--- /dev/null
+++ b/tests/cli/component-model-gc/futures.wast
@@ -0,0 +1,24 @@
+;; RUN: wast --assert default --snapshot tests/snapshots % -f gc,cm-gc,cm-async
+
+(component
+  (type $future (future bool))
+
+  (core type $ty (func (param externref)))
+
+  (import "i" (instance $i
+                        (export "f" (func (param "x" $future)))))
+  (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (type $future (future bool))
+
+    (core type $ty (func (param anyref)))
+
+    (import "i" (instance $i
+                          (export "f" (func (param "x" $future)))))
+    (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty)))
+  )
+  "expected to lower component `future` type into core `(ref null? extern)` type, but found `anyref`"
+)
diff --git a/tests/cli/component-model-gc/list-types.wast b/tests/cli/component-model-gc/list-types.wast
new file mode 100644
index 00000000..f8fa7bf0
--- /dev/null
+++ b/tests/cli/component-model-gc/list-types.wast
@@ -0,0 +1,28 @@
+;; RUN: wast --assert default --snapshot tests/snapshots % -f gc,cm-gc
+
+(component
+  (type $list (list string))
+
+  (core type $string (array i8))
+  (core type $list (array (ref $string)))
+  (core type $ty (func (param (ref $list))))
+
+  (import "i" (instance $i
+                (export "ty" (type $list' (eq $list)))
+                (export "f" (func (param "x" $list')))))
+  (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (type $list (list string))
+
+    (core type $ty (func (param i32)))
+
+    (import "i" (instance $i
+                          (export "ty" (type $list' (eq $list)))
+                          (export "f" (func (param "x" $list')))))
+    (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty)))
+  )
+  "expected to lower component `list` type into `(ref null? (array ...))`, but found `i32`"
+)
diff --git a/tests/cli/component-model-gc/primitive-types.wast b/tests/cli/component-model-gc/primitive-types.wast
new file mode 100644
index 00000000..40bc325f
--- /dev/null
+++ b/tests/cli/component-model-gc/primitive-types.wast
@@ -0,0 +1,517 @@
+;; RUN: wast --assert default --snapshot tests/snapshots % -f gc,cm-gc
+
+;; bool
+
+(component
+  (import "f" (func $f (param "x" bool)))
+  (core type $ty (func (param i32)))
+  (core func (canon lower (func $f) gc (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" bool)))
+    (core type $ty (func (param anyref)))
+    (core func (canon lower (func $f) gc (core-type $ty)))
+  )
+  "expected to lower component `bool` type to core `i32` type, found `anyref`"
+)
+
+(component
+  (import "f" (func $f (param "x" (list bool))))
+  (core type $list (array i8))
+  (core type $ty (func (param (ref $list))))
+  (core func (canon lower (func $f) gc (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" (list bool))))
+    (core type $list (array i32))
+    (core type $ty (func (param (ref $list))))
+    (core func (canon lower (func $f) gc (core-type $ty)))
+  )
+  "expected to lower component `bool` type to core `i8` type, found `i32`"
+)
+
+;; u8
+
+(component
+  (import "f" (func $f (param "x" u8)))
+  (core type $ty (func (param i32)))
+  (core func (canon lower (func $f) gc (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" u8)))
+    (core type $ty (func (param anyref)))
+    (core func (canon lower (func $f) gc (core-type $ty)))
+  )
+  "expected to lower component `u8` type to core `i32` type, found `anyref`"
+)
+
+(component
+  (import "f" (func $f (param "x" (list u8))))
+  (core type $list (array i8))
+  (core type $ty (func (param (ref $list))))
+  (core func (canon lower (func $f) gc (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" (list u8))))
+    (core type $list (array i32))
+    (core type $ty (func (param (ref $list))))
+    (core func (canon lower (func $f) gc (core-type $ty)))
+  )
+  "expected to lower component `u8` type to core `i8` type, found `i32`"
+)
+
+;; s8
+
+(component
+  (import "f" (func $f (param "x" s8)))
+  (core type $ty (func (param i32)))
+  (core func (canon lower (func $f) gc (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" s8)))
+    (core type $ty (func (param anyref)))
+    (core func (canon lower (func $f) gc (core-type $ty)))
+  )
+  "expected to lower component `s8` type to core `i32` type, found `anyref`"
+)
+
+(component
+  (import "f" (func $f (param "x" (list s8))))
+  (core type $list (array i8))
+  (core type $ty (func (param (ref $list))))
+  (core func (canon lower (func $f) gc (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" (list s8))))
+    (core type $list (array i32))
+    (core type $ty (func (param (ref $list))))
+    (core func (canon lower (func $f) gc (core-type $ty)))
+  )
+  "expected to lower component `s8` type to core `i8` type, found `i32`"
+)
+
+;; u16
+
+(component
+  (import "f" (func $f (param "x" u16)))
+  (core type $ty (func (param i32)))
+  (core func (canon lower (func $f) gc (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" u16)))
+    (core type $ty (func (param anyref)))
+    (core func (canon lower (func $f) gc (core-type $ty)))
+  )
+  "expected to lower component `u16` type to core `i32` type, found `anyref`"
+)
+
+(component
+  (import "f" (func $f (param "x" (list u16))))
+  (core type $list (array i16))
+  (core type $ty (func (param (ref $list))))
+  (core func (canon lower (func $f) gc (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" (list u16))))
+    (core type $list (array i32))
+    (core type $ty (func (param (ref $list))))
+    (core func (canon lower (func $f) gc (core-type $ty)))
+  )
+  "expected to lower component `u16` type to core `i16` type, found `i32`"
+)
+
+;; s16
+
+(component
+  (import "f" (func $f (param "x" s16)))
+  (core type $ty (func (param i32)))
+  (core func (canon lower (func $f) gc (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" s16)))
+    (core type $ty (func (param anyref)))
+    (core func (canon lower (func $f) gc (core-type $ty)))
+  )
+  "expected to lower component `s16` type to core `i32` type, found `anyref`"
+)
+
+(component
+  (import "f" (func $f (param "x" (list s16))))
+  (core type $list (array i16))
+  (core type $ty (func (param (ref $list))))
+  (core func (canon lower (func $f) gc (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" (list s16))))
+    (core type $list (array i32))
+    (core type $ty (func (param (ref $list))))
+    (core func (canon lower (func $f) gc (core-type $ty)))
+  )
+  "expected to lower component `s16` type to core `i16` type, found `i32`"
+)
+
+;; u32
+
+(component
+  (import "f" (func $f (param "x" u32)))
+  (core type $ty (func (param i32)))
+  (core func (canon lower (func $f) gc (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" u32)))
+    (core type $ty (func (param anyref)))
+    (core func (canon lower (func $f) gc (core-type $ty)))
+  )
+  "expected to lower component `u32` type to core `i32` type, found `anyref`"
+)
+
+(component
+  (import "f" (func $f (param "x" (list u32))))
+  (core type $list (array i32))
+  (core type $ty (func (param (ref $list))))
+  (core func (canon lower (func $f) gc (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" (list u32))))
+    (core type $list (array anyref))
+    (core type $ty (func (param (ref $list))))
+    (core func (canon lower (func $f) gc (core-type $ty)))
+  )
+  "expected to lower component `u32` type to core `i32` type, found `anyref`"
+)
+
+;; s32
+
+(component
+  (import "f" (func $f (param "x" s32)))
+  (core type $ty (func (param i32)))
+  (core func (canon lower (func $f) gc (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" s32)))
+    (core type $ty (func (param anyref)))
+    (core func (canon lower (func $f) gc (core-type $ty)))
+  )
+  "expected to lower component `s32` type to core `i32` type, found `anyref`"
+)
+
+(component
+  (import "f" (func $f (param "x" (list s32))))
+  (core type $list (array i32))
+  (core type $ty (func (param (ref $list))))
+  (core func (canon lower (func $f) gc (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" (list s32))))
+    (core type $list (array anyref))
+    (core type $ty (func (param (ref $list))))
+    (core func (canon lower (func $f) gc (core-type $ty)))
+  )
+  "expected to lower component `s32` type to core `i32` type, found `anyref`"
+)
+
+;; u64
+
+(component
+  (import "f" (func $f (param "x" u64)))
+  (core type $ty (func (param i64)))
+  (core func (canon lower (func $f) gc (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" u64)))
+    (core type $ty (func (param anyref)))
+    (core func (canon lower (func $f) gc (core-type $ty)))
+  )
+  "expected to lower component `u64` type to core `i64` type, found `anyref`"
+)
+
+(component
+  (import "f" (func $f (param "x" (list u64))))
+  (core type $list (array i64))
+  (core type $ty (func (param (ref $list))))
+  (core func (canon lower (func $f) gc (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" (list u64))))
+    (core type $list (array anyref))
+    (core type $ty (func (param (ref $list))))
+    (core func (canon lower (func $f) gc (core-type $ty)))
+  )
+  "expected to lower component `u64` type to core `i64` type, found `anyref`"
+)
+
+;; s64
+
+(component
+  (import "f" (func $f (param "x" s64)))
+  (core type $ty (func (param i64)))
+  (core func (canon lower (func $f) gc (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" s64)))
+    (core type $ty (func (param anyref)))
+    (core func (canon lower (func $f) gc (core-type $ty)))
+  )
+  "expected to lower component `s64` type to core `i64` type, found `anyref`"
+)
+
+(component
+  (import "f" (func $f (param "x" (list s64))))
+  (core type $list (array i64))
+  (core type $ty (func (param (ref $list))))
+  (core func (canon lower (func $f) gc (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" (list s64))))
+    (core type $list (array anyref))
+    (core type $ty (func (param (ref $list))))
+    (core func (canon lower (func $f) gc (core-type $ty)))
+  )
+  "expected to lower component `s64` type to core `i64` type, found `anyref`"
+)
+
+;; f32
+
+(component
+  (import "f" (func $f (param "x" f32)))
+  (core type $ty (func (param f32)))
+  (core func (canon lower (func $f) gc (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" f32)))
+    (core type $ty (func (param anyref)))
+    (core func (canon lower (func $f) gc (core-type $ty)))
+  )
+  "expected to lower component `f32` type to core `f32` type, found `anyref`"
+)
+
+(component
+  (import "f" (func $f (param "x" (list f32))))
+  (core type $list (array f32))
+  (core type $ty (func (param (ref $list))))
+  (core func (canon lower (func $f) gc (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" (list f32))))
+    (core type $list (array anyref))
+    (core type $ty (func (param (ref $list))))
+    (core func (canon lower (func $f) gc (core-type $ty)))
+  )
+  "expected to lower component `f32` type to core `f32` type, found `anyref`"
+)
+
+;; f64
+
+(component
+  (import "f" (func $f (param "x" f64)))
+  (core type $ty (func (param f64)))
+  (core func (canon lower (func $f) gc (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" f64)))
+    (core type $ty (func (param anyref)))
+    (core func (canon lower (func $f) gc (core-type $ty)))
+  )
+  "expected to lower component `f64` type to core `f64` type, found `anyref`"
+)
+
+(component
+  (import "f" (func $f (param "x" (list f64))))
+  (core type $list (array f64))
+  (core type $ty (func (param (ref $list))))
+  (core func (canon lower (func $f) gc (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" (list f64))))
+    (core type $list (array anyref))
+    (core type $ty (func (param (ref $list))))
+    (core func (canon lower (func $f) gc (core-type $ty)))
+  )
+  "expected to lower component `f64` type to core `f64` type, found `anyref`"
+)
+
+;; char
+
+(component
+  (import "f" (func $f (param "x" char)))
+  (core type $ty (func (param i32)))
+  (core func (canon lower (func $f) gc (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" char)))
+    (core type $ty (func (param anyref)))
+    (core func (canon lower (func $f) gc (core-type $ty)))
+  )
+  "expected to lower component `char` type to core `i32` type, found `anyref`"
+)
+
+(component
+  (import "f" (func $f (param "x" (list char))))
+  (core type $list (array i32))
+  (core type $ty (func (param (ref $list))))
+  (core func (canon lower (func $f) gc (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" (list char))))
+    (core type $list (array anyref))
+    (core type $ty (func (param (ref $list))))
+    (core func (canon lower (func $f) gc (core-type $ty)))
+  )
+  "expected to lower component `char` type to core `i32` type, found `anyref`"
+)
+
+;; utf8 strings
+
+(component
+  (import "f" (func $f (param "x" string)))
+  (core type $s (array i8))
+  (core type $ty (func (param (ref $s))))
+  (core func (canon lower (func $f) gc string-encoding=utf8 (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" string)))
+    (core type $ty (func (param anyref)))
+    (core func (canon lower (func $f) gc string-encoding=utf8 (core-type $ty)))
+  )
+  "expected to lower component `string` type to core `(ref null? (array (mut? i8)))` type, found `anyref`"
+)
+
+(component
+  (import "f" (func $f (param "x" (list string))))
+  (core type $s (array i8))
+  (core type $list (array (ref $s)))
+  (core type $ty (func (param (ref $list))))
+  (core func (canon lower (func $f) gc string-encoding=utf8 (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" (list string))))
+    (core type $list (array anyref))
+    (core type $ty (func (param (ref $list))))
+    (core func (canon lower (func $f) gc string-encoding=utf8 (core-type $ty)))
+  )
+  "expected to lower component `string` type to core `(ref null? (array (mut? i8)))` type, found `anyref`"
+)
+
+;; utf16 strings
+
+(component
+  (import "f" (func $f (param "x" string)))
+  (core type $s (array i16))
+  (core type $ty (func (param (ref $s))))
+  (core func (canon lower (func $f) gc string-encoding=utf16 (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" string)))
+    (core type $ty (func (param anyref)))
+    (core func (canon lower (func $f) gc string-encoding=utf16 (core-type $ty)))
+  )
+  "expected to lower component `string` type to core `(ref null? (array (mut? i16)))` type, found `anyref`"
+)
+
+(component
+  (import "f" (func $f (param "x" (list string))))
+  (core type $s (array i16))
+  (core type $list (array (ref $s)))
+  (core type $ty (func (param (ref $list))))
+  (core func (canon lower (func $f) gc string-encoding=utf16 (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" (list string))))
+    (core type $list (array anyref))
+    (core type $ty (func (param (ref $list))))
+    (core func (canon lower (func $f) gc string-encoding=utf16 (core-type $ty)))
+  )
+  "expected to lower component `string` type to core `(ref null? (array (mut? i16)))` type, found `anyref`"
+)
+
+;; compact utf16 strings
+
+(component
+  (import "f" (func $f (param "x" string)))
+  (core type $s (array i8))
+  (core type $ty (func (param (ref $s))))
+  (core func (canon lower (func $f) gc string-encoding=latin1+utf16 (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" string)))
+    (core type $ty (func (param anyref)))
+    (core func (canon lower (func $f) gc string-encoding=latin1+utf16 (core-type $ty)))
+  )
+  "expected to lower component `string` type to core `(ref null? (array (mut? i8)))` type, found `anyref`"
+)
+
+(component
+  (import "f" (func $f (param "x" (list string))))
+  (core type $s (array i8))
+  (core type $list (array (ref $s)))
+  (core type $ty (func (param (ref $list))))
+  (core func (canon lower (func $f) gc string-encoding=latin1+utf16 (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (import "f" (func $f (param "x" (list string))))
+    (core type $list (array anyref))
+    (core type $ty (func (param (ref $list))))
+    (core func (canon lower (func $f) gc string-encoding=latin1+utf16 (core-type $ty)))
+  )
+  "expected to lower component `string` type to core `(ref null? (array (mut? i8)))` type, found `anyref`"
+)
diff --git a/tests/cli/component-model-gc/record-types.wast b/tests/cli/component-model-gc/record-types.wast
new file mode 100644
index 00000000..9c2dad9a
--- /dev/null
+++ b/tests/cli/component-model-gc/record-types.wast
@@ -0,0 +1,85 @@
+;; RUN: wast --assert default --snapshot tests/snapshots % -f gc,cm-gc
+
+(component
+  (type $record (record (field "a" bool)
+                        (field "b" u8)
+                        (field "c" s8)
+                        (field "d" u16)
+                        (field "e" s16)
+                        (field "f" u32)
+                        (field "g" s32)
+                        (field "h" u64)
+                        (field "i" s64)
+                        (field "j" f32)
+                        (field "k" f64)
+                        (field "l" char)
+                        (field "m" string)))
+
+  (core type $string (array i8))
+  (core type $record (struct (field i8)
+                             (field i8)
+                             (field i8)
+                             (field i16)
+                             (field i16)
+                             (field i32)
+                             (field i32)
+                             (field i64)
+                             (field i64)
+                             (field f32)
+                             (field f64)
+                             (field i32)
+                             (field (ref $string))))
+  (core type $ty (func (param (ref $record))))
+
+  (import "i" (instance $i
+                (export "ty" (type $record' (eq $record)))
+                (export "f" (func (param "x" $record')))))
+  (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (type $record (record (field "a" bool)))
+
+    (core type $record (struct (field i8)
+                               (field i8)))
+    (core type $ty (func (param (ref $record))))
+
+    (import "i" (instance $i
+                  (export "ty" (type  $record' (eq $record)))
+                  (export "f" (func (param "x" $record')))))
+    (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty)))
+  )
+  "core `struct` has 2 fields, but component `record` has 1 fields"
+)
+
+(assert_invalid
+  (component
+    (type $record (record (field "a" bool)
+                          (field "b" bool)))
+
+    (core type $record (struct (field i8)))
+    (core type $ty (func (param (ref $record))))
+
+    (import "i" (instance $i
+                  (export "ty" (type $record' (eq $record)))
+                  (export "f" (func (param "x" $record')))))
+    (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty)))
+  )
+  "core `struct` has 1 fields, but component `record` has 2 fields"
+)
+
+(assert_invalid
+  (component
+    (type $record (record (field "a" bool)
+                          (field "b" bool)))
+
+    (core type $ty (func (param i32)))
+
+    (import "i" (instance $i
+                  (export "ty" (type $record' (eq $record)))
+                  (export "f" (func (param "x" $record')))))
+    (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty)))
+  )
+  "expected to lower component `record` type to core `(ref null? (struct ...))`, but found `i32`"
+)
diff --git a/tests/cli/component-model-gc/resource-types.wast b/tests/cli/component-model-gc/resource-types.wast
new file mode 100644
index 00000000..a56b1c4d
--- /dev/null
+++ b/tests/cli/component-model-gc/resource-types.wast
@@ -0,0 +1,43 @@
+;; RUN: wast --assert default --snapshot tests/snapshots % -f gc,cm-gc
+
+(component
+  (core type $ty (func (param externref)))
+
+  (import "i" (instance $i
+                        (export "r" (type $resource (sub resource)))
+                        (export "f" (func (param "x" (own $resource))))))
+  (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+     (core type $ty (func (param anyref)))
+
+     (import "i" (instance $i
+                           (export "r" (type $resource (sub resource)))
+                           (export "f" (func (param "x" (own $resource))))))
+     (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty)))
+  )
+  "expected to lower component `own` type into core `(ref null? extern)` type, but found `anyref`"
+)
+
+(component
+  (core type $ty (func (param externref)))
+
+  (import "i" (instance $i
+                        (export "r" (type $resource (sub resource)))
+                        (export "f" (func (param "x" (borrow $resource))))))
+  (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+     (core type $ty (func (param anyref)))
+
+     (import "i" (instance $i
+                           (export "r" (type $resource (sub resource)))
+                           (export "f" (func (param "x" (borrow $resource))))))
+     (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty)))
+  )
+  "expected to lower component `borrow` type into core `(ref null? extern)` type, but found `anyref`"
+)
diff --git a/tests/cli/component-model-gc/tuple-types.wast b/tests/cli/component-model-gc/tuple-types.wast
new file mode 100644
index 00000000..4a9456c9
--- /dev/null
+++ b/tests/cli/component-model-gc/tuple-types.wast
@@ -0,0 +1,63 @@
+;; RUN: wast --assert default --snapshot tests/snapshots % -f gc,cm-gc
+
+(component
+  (type $tup (tuple string bool))
+
+  (core type $string (array i8))
+  (core type $tup (struct (field (ref $string))
+                          (field i8)))
+  (core type $ty (func (param (ref $tup))))
+
+  (import "i" (instance $i
+                (export "ty" (type $tup' (eq $tup)))
+                (export "f" (func (param "x" $tup')))))
+  (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty)))
+)
+
+(assert_invalid
+  (component
+    (type $tup (tuple string bool))
+
+    (core type $string (array i8))
+    (core type $tup (struct (field (ref $string))))
+    (core type $ty (func (param (ref $tup))))
+
+    (import "i" (instance $i
+                          (export "ty" (type $tup' (eq $tup)))
+                          (export "f" (func (param "x" $tup')))))
+    (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty)))
+  )
+  "core `struct` has 1 fields, but component `tuple` has 2 fields"
+)
+
+(assert_invalid
+  (component
+    (type $tup (tuple string bool))
+
+    (core type $string (array i8))
+    (core type $tup (struct (field (ref $string))
+                            (field i8)
+                            (field i8)))
+    (core type $ty (func (param (ref $tup))))
+
+    (import "i" (instance $i
+                          (export "ty" (type $tup' (eq $tup)))
+                          (export "f" (func (param "x" $tup')))))
+    (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty)))
+  )
+  "core `struct` has 3 fields, but component `tuple` has 2 fields"
+)
+
+(assert_invalid
+  (component
+    (type $tup (tuple string bool))
+
+    (core type $ty (func (param i32)))
+
+    (import "i" (instance $i
+                          (export "ty" (type $tup' (eq $tup)))
+                          (export "f" (func (param "x" $tup')))))
+    (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty)))
+  )
+  "expected to lower component `tuple` type to core `(ref null? (struct ...))`, but found `i32`"
+)
diff --git a/tests/snapshots/cli/component-model-gc/core-type.wat.json b/tests/snapshots/cli/component-model-gc/core-type.wat.json
index ad809fde..f7ec15c1 100644
--- a/tests/snapshots/cli/component-model-gc/core-type.wat.json
+++ b/tests/snapshots/cli/component-model-gc/core-type.wat.json
@@ -21,6 +21,86 @@
       "filename": "core-type.2.wasm",
       "module_type": "binary",
       "text": "cannot specify `core-type` without `gc`"
+    },
+    {
+      "type": "module",
+      "line": 29,
+      "filename": "core-type.3.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 36,
+      "filename": "core-type.4.wasm",
+      "module_type": "binary",
+      "text": "canonical option `core-type` is not allowed in `canon lift`"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 52,
+      "filename": "core-type.5.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `u32` type to core `i32` type, found `i64`"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 61,
+      "filename": "core-type.6.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `u32` type to core `i32` type, found `i64`"
+    },
+    {
+      "type": "module",
+      "line": 69,
+      "filename": "core-type.7.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 93,
+      "filename": "core-type.8.wasm",
+      "module_type": "binary",
+      "text": "type mismatch for export `b` of module instantiation argument `a`"
+    },
+    {
+      "type": "module",
+      "line": 113,
+      "filename": "core-type.9.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "module",
+      "line": 131,
+      "filename": "core-type.10.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 150,
+      "filename": "core-type.11.wasm",
+      "module_type": "binary",
+      "text": "type mismatch for export `b` of module instantiation argument `a`"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 170,
+      "filename": "core-type.12.wasm",
+      "module_type": "binary",
+      "text": "type mismatch for export `b` of module instantiation argument `a`"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 190,
+      "filename": "core-type.13.wasm",
+      "module_type": "binary",
+      "text": "type mismatch for export `b` of module instantiation argument `a`"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 206,
+      "filename": "core-type.14.wasm",
+      "module_type": "binary",
+      "text": "type mismatch for export `b` of module instantiation argument `a`"
     }
   ]
 }
\ No newline at end of file
diff --git a/tests/snapshots/cli/component-model-gc/core-type.wat/10.print b/tests/snapshots/cli/component-model-gc/core-type.wat/10.print
new file mode 100644
index 00000000..97ea506b
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/core-type.wat/10.print
@@ -0,0 +1,18 @@
+(component
+  (type (;0;) (func (param "x" u32) (param "y" u32) (result u32)))
+  (import "f" (func $f (;0;) (type 0)))
+  (core type $super_ty (;0;) (sub (func (param i32 i32) (result i32))))
+  (core type $sub_ty (;1;) (sub $super_ty (func (param i32 i32) (result i32))))
+  (core func $f (;0;) (canon lower (func $f) gc (core-type $sub_ty)))
+  (core module $m (;0;)
+    (type $super_ty (;0;) (sub (func (param i32 i32) (result i32))))
+    (import "a" "b" (func (;0;) (type $super_ty)))
+  )
+  (core instance (;0;)
+    (export "b" (func $f))
+  )
+  (core instance (;1;) (instantiate $m
+      (with "a" (instance 0))
+    )
+  )
+)
diff --git a/tests/snapshots/cli/component-model-gc/core-type.wat/3.print b/tests/snapshots/cli/component-model-gc/core-type.wat/3.print
new file mode 100644
index 00000000..ede9d37c
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/core-type.wat/3.print
@@ -0,0 +1,6 @@
+(component
+  (type (;0;) (func (param "x" u32) (param "y" u32) (result u32)))
+  (import "f" (func $f (;0;) (type 0)))
+  (core type $ty (;0;) (func (param i32 i32) (result i32)))
+  (core func (;0;) (canon lower (func $f) gc (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/core-type.wat/7.print b/tests/snapshots/cli/component-model-gc/core-type.wat/7.print
new file mode 100644
index 00000000..c1189bc6
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/core-type.wat/7.print
@@ -0,0 +1,23 @@
+(component
+  (type (;0;) (func (param "x" u32) (param "y" u32) (result u32)))
+  (import "f" (func $f (;0;) (type 0)))
+  (core rec
+    (type (;0;) (func))
+    (type $ty (;1;) (func (param i32 i32) (result i32)))
+  )
+  (core func $f (;0;) (canon lower (func $f) gc (core-type $ty)))
+  (core module $m (;0;)
+    (rec
+      (type (;0;) (func))
+      (type $ty (;1;) (func (param i32 i32) (result i32)))
+    )
+    (import "a" "b" (func (;0;) (type $ty)))
+  )
+  (core instance (;0;)
+    (export "b" (func $f))
+  )
+  (core instance (;1;) (instantiate $m
+      (with "a" (instance 0))
+    )
+  )
+)
diff --git a/tests/snapshots/cli/component-model-gc/core-type.wat/9.print b/tests/snapshots/cli/component-model-gc/core-type.wat/9.print
new file mode 100644
index 00000000..a7c2cc3b
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/core-type.wat/9.print
@@ -0,0 +1,19 @@
+(component
+  (type (;0;) (func (param "x" u32) (param "y" u32) (result u32)))
+  (import "f" (func $f (;0;) (type 0)))
+  (core type $super_ty (;0;) (sub (func (param i32 i32) (result i32))))
+  (core type $sub_ty (;1;) (sub $super_ty (func (param i32 i32) (result i32))))
+  (core func $f (;0;) (canon lower (func $f) gc (core-type $sub_ty)))
+  (core module $m (;0;)
+    (type $super_ty (;0;) (sub (func (param i32 i32) (result i32))))
+    (type $sub_ty (;1;) (sub $super_ty (func (param i32 i32) (result i32))))
+    (import "a" "b" (func (;0;) (type $sub_ty)))
+  )
+  (core instance (;0;)
+    (export "b" (func $f))
+  )
+  (core instance (;1;) (instantiate $m
+      (with "a" (instance 0))
+    )
+  )
+)
diff --git a/tests/snapshots/cli/component-model-gc/enum-types.wast.json b/tests/snapshots/cli/component-model-gc/enum-types.wast.json
new file mode 100644
index 00000000..94fd2b8e
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/enum-types.wast.json
@@ -0,0 +1,18 @@
+{
+  "source_filename": "tests/cli/component-model-gc/enum-types.wast",
+  "commands": [
+    {
+      "type": "module",
+      "line": 3,
+      "filename": "enum-types.0.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 15,
+      "filename": "enum-types.1.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `enum` type into core `i32` type, but found `anyref`"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/snapshots/cli/component-model-gc/enum-types.wast/0.print b/tests/snapshots/cli/component-model-gc/enum-types.wast/0.print
new file mode 100644
index 00000000..9c1a337f
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/enum-types.wast/0.print
@@ -0,0 +1,15 @@
+(component
+  (type $enum (;0;) (enum "a" "b" "c"))
+  (core type $ty (;0;) (func (param i32)))
+  (type (;1;)
+    (instance
+      (alias outer 1 $enum (type (;0;)))
+      (export (;1;) "ty" (type (eq 0)))
+      (type (;2;) (func (param "x" 1)))
+      (export (;0;) "f" (func (type 2)))
+    )
+  )
+  (import "i" (instance $i (;0;) (type 1)))
+  (alias export $i "f" (func (;0;)))
+  (core func (;0;) (canon lower (func 0) gc string-encoding=utf8 (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/error-context.wast.json b/tests/snapshots/cli/component-model-gc/error-context.wast.json
new file mode 100644
index 00000000..a58d96c8
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/error-context.wast.json
@@ -0,0 +1,18 @@
+{
+  "source_filename": "tests/cli/component-model-gc/error-context.wast",
+  "commands": [
+    {
+      "type": "module",
+      "line": 3,
+      "filename": "error-context.0.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 12,
+      "filename": "error-context.1.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `error-context` type into core `(ref null? extern)` type, but found `anyref`"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/snapshots/cli/component-model-gc/error-context.wast/0.print b/tests/snapshots/cli/component-model-gc/error-context.wast/0.print
new file mode 100644
index 00000000..21ccd94d
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/error-context.wast/0.print
@@ -0,0 +1,12 @@
+(component
+  (core type $ty (;0;) (func (param externref)))
+  (type (;0;)
+    (instance
+      (type (;0;) (func (param "x" error-context)))
+      (export (;0;) "f" (func (type 0)))
+    )
+  )
+  (import "i" (instance $i (;0;) (type 0)))
+  (alias export $i "f" (func (;0;)))
+  (core func (;0;) (canon lower (func 0) gc (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/flag-types.wast.json b/tests/snapshots/cli/component-model-gc/flag-types.wast.json
new file mode 100644
index 00000000..316ba3f9
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/flag-types.wast.json
@@ -0,0 +1,18 @@
+{
+  "source_filename": "tests/cli/component-model-gc/flag-types.wast",
+  "commands": [
+    {
+      "type": "module",
+      "line": 3,
+      "filename": "flag-types.0.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 15,
+      "filename": "flag-types.1.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `flags` type into core `i32` type, but found `anyref`"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/snapshots/cli/component-model-gc/flag-types.wast/0.print b/tests/snapshots/cli/component-model-gc/flag-types.wast/0.print
new file mode 100644
index 00000000..dc4ad233
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/flag-types.wast/0.print
@@ -0,0 +1,15 @@
+(component
+  (type $flags (;0;) (flags "a" "b" "c"))
+  (core type $ty (;0;) (func (param i32)))
+  (type (;1;)
+    (instance
+      (alias outer 1 $flags (type (;0;)))
+      (export (;1;) "ty" (type (eq 0)))
+      (type (;2;) (func (param "x" 1)))
+      (export (;0;) "f" (func (type 2)))
+    )
+  )
+  (import "i" (instance $i (;0;) (type 1)))
+  (alias export $i "f" (func (;0;)))
+  (core func (;0;) (canon lower (func 0) gc string-encoding=utf8 (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/futures.wast.json b/tests/snapshots/cli/component-model-gc/futures.wast.json
new file mode 100644
index 00000000..d6668f57
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/futures.wast.json
@@ -0,0 +1,18 @@
+{
+  "source_filename": "tests/cli/component-model-gc/futures.wast",
+  "commands": [
+    {
+      "type": "module",
+      "line": 3,
+      "filename": "futures.0.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 14,
+      "filename": "futures.1.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `future` type into core `(ref null? extern)` type, but found `anyref`"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/snapshots/cli/component-model-gc/futures.wast/0.print b/tests/snapshots/cli/component-model-gc/futures.wast/0.print
new file mode 100644
index 00000000..ad3674fa
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/futures.wast/0.print
@@ -0,0 +1,14 @@
+(component
+  (type $future (;0;) (future bool))
+  (core type $ty (;0;) (func (param externref)))
+  (type (;1;)
+    (instance
+      (alias outer 1 $future (type (;0;)))
+      (type (;1;) (func (param "x" 0)))
+      (export (;0;) "f" (func (type 1)))
+    )
+  )
+  (import "i" (instance $i (;0;) (type 1)))
+  (alias export $i "f" (func (;0;)))
+  (core func (;0;) (canon lower (func 0) gc string-encoding=utf8 (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/list-types.wast.json b/tests/snapshots/cli/component-model-gc/list-types.wast.json
new file mode 100644
index 00000000..20e56a9b
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/list-types.wast.json
@@ -0,0 +1,18 @@
+{
+  "source_filename": "tests/cli/component-model-gc/list-types.wast",
+  "commands": [
+    {
+      "type": "module",
+      "line": 3,
+      "filename": "list-types.0.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 17,
+      "filename": "list-types.1.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `list` type into `(ref null? (array ...))`, but found `i32`"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/snapshots/cli/component-model-gc/list-types.wast/0.print b/tests/snapshots/cli/component-model-gc/list-types.wast/0.print
new file mode 100644
index 00000000..d6f8aa54
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/list-types.wast/0.print
@@ -0,0 +1,17 @@
+(component
+  (type $list (;0;) (list string))
+  (core type $string (;0;) (array i8))
+  (core type $list (;1;) (array (ref $string)))
+  (core type $ty (;2;) (func (param (ref $list))))
+  (type (;1;)
+    (instance
+      (alias outer 1 $list (type (;0;)))
+      (export (;1;) "ty" (type (eq 0)))
+      (type (;2;) (func (param "x" 1)))
+      (export (;0;) "f" (func (type 2)))
+    )
+  )
+  (import "i" (instance $i (;0;) (type 1)))
+  (alias export $i "f" (func (;0;)))
+  (core func (;0;) (canon lower (func 0) gc string-encoding=utf8 (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/primitive-types.wast.json b/tests/snapshots/cli/component-model-gc/primitive-types.wast.json
new file mode 100644
index 00000000..0d755f9a
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/primitive-types.wast.json
@@ -0,0 +1,395 @@
+{
+  "source_filename": "tests/cli/component-model-gc/primitive-types.wast",
+  "commands": [
+    {
+      "type": "module",
+      "line": 5,
+      "filename": "primitive-types.0.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 12,
+      "filename": "primitive-types.1.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `bool` type to core `i32` type, found `anyref`"
+    },
+    {
+      "type": "module",
+      "line": 20,
+      "filename": "primitive-types.2.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 28,
+      "filename": "primitive-types.3.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `bool` type to core `i8` type, found `i32`"
+    },
+    {
+      "type": "module",
+      "line": 39,
+      "filename": "primitive-types.4.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 46,
+      "filename": "primitive-types.5.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `u8` type to core `i32` type, found `anyref`"
+    },
+    {
+      "type": "module",
+      "line": 54,
+      "filename": "primitive-types.6.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 62,
+      "filename": "primitive-types.7.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `u8` type to core `i8` type, found `i32`"
+    },
+    {
+      "type": "module",
+      "line": 73,
+      "filename": "primitive-types.8.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 80,
+      "filename": "primitive-types.9.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `s8` type to core `i32` type, found `anyref`"
+    },
+    {
+      "type": "module",
+      "line": 88,
+      "filename": "primitive-types.10.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 96,
+      "filename": "primitive-types.11.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `s8` type to core `i8` type, found `i32`"
+    },
+    {
+      "type": "module",
+      "line": 107,
+      "filename": "primitive-types.12.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 114,
+      "filename": "primitive-types.13.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `u16` type to core `i32` type, found `anyref`"
+    },
+    {
+      "type": "module",
+      "line": 122,
+      "filename": "primitive-types.14.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 130,
+      "filename": "primitive-types.15.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `u16` type to core `i16` type, found `i32`"
+    },
+    {
+      "type": "module",
+      "line": 141,
+      "filename": "primitive-types.16.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 148,
+      "filename": "primitive-types.17.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `s16` type to core `i32` type, found `anyref`"
+    },
+    {
+      "type": "module",
+      "line": 156,
+      "filename": "primitive-types.18.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 164,
+      "filename": "primitive-types.19.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `s16` type to core `i16` type, found `i32`"
+    },
+    {
+      "type": "module",
+      "line": 175,
+      "filename": "primitive-types.20.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 182,
+      "filename": "primitive-types.21.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `u32` type to core `i32` type, found `anyref`"
+    },
+    {
+      "type": "module",
+      "line": 190,
+      "filename": "primitive-types.22.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 198,
+      "filename": "primitive-types.23.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `u32` type to core `i32` type, found `anyref`"
+    },
+    {
+      "type": "module",
+      "line": 209,
+      "filename": "primitive-types.24.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 216,
+      "filename": "primitive-types.25.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `s32` type to core `i32` type, found `anyref`"
+    },
+    {
+      "type": "module",
+      "line": 224,
+      "filename": "primitive-types.26.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 232,
+      "filename": "primitive-types.27.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `s32` type to core `i32` type, found `anyref`"
+    },
+    {
+      "type": "module",
+      "line": 243,
+      "filename": "primitive-types.28.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 250,
+      "filename": "primitive-types.29.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `u64` type to core `i64` type, found `anyref`"
+    },
+    {
+      "type": "module",
+      "line": 258,
+      "filename": "primitive-types.30.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 266,
+      "filename": "primitive-types.31.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `u64` type to core `i64` type, found `anyref`"
+    },
+    {
+      "type": "module",
+      "line": 277,
+      "filename": "primitive-types.32.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 284,
+      "filename": "primitive-types.33.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `s64` type to core `i64` type, found `anyref`"
+    },
+    {
+      "type": "module",
+      "line": 292,
+      "filename": "primitive-types.34.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 300,
+      "filename": "primitive-types.35.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `s64` type to core `i64` type, found `anyref`"
+    },
+    {
+      "type": "module",
+      "line": 311,
+      "filename": "primitive-types.36.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 318,
+      "filename": "primitive-types.37.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `f32` type to core `f32` type, found `anyref`"
+    },
+    {
+      "type": "module",
+      "line": 326,
+      "filename": "primitive-types.38.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 334,
+      "filename": "primitive-types.39.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `f32` type to core `f32` type, found `anyref`"
+    },
+    {
+      "type": "module",
+      "line": 345,
+      "filename": "primitive-types.40.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 352,
+      "filename": "primitive-types.41.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `f64` type to core `f64` type, found `anyref`"
+    },
+    {
+      "type": "module",
+      "line": 360,
+      "filename": "primitive-types.42.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 368,
+      "filename": "primitive-types.43.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `f64` type to core `f64` type, found `anyref`"
+    },
+    {
+      "type": "module",
+      "line": 379,
+      "filename": "primitive-types.44.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 386,
+      "filename": "primitive-types.45.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `char` type to core `i32` type, found `anyref`"
+    },
+    {
+      "type": "module",
+      "line": 394,
+      "filename": "primitive-types.46.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 402,
+      "filename": "primitive-types.47.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `char` type to core `i32` type, found `anyref`"
+    },
+    {
+      "type": "module",
+      "line": 413,
+      "filename": "primitive-types.48.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 421,
+      "filename": "primitive-types.49.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `string` type to core `(ref null? (array (mut? i8)))` type, found `anyref`"
+    },
+    {
+      "type": "module",
+      "line": 429,
+      "filename": "primitive-types.50.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 438,
+      "filename": "primitive-types.51.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `string` type to core `(ref null? (array (mut? i8)))` type, found `anyref`"
+    },
+    {
+      "type": "module",
+      "line": 449,
+      "filename": "primitive-types.52.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 457,
+      "filename": "primitive-types.53.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `string` type to core `(ref null? (array (mut? i16)))` type, found `anyref`"
+    },
+    {
+      "type": "module",
+      "line": 465,
+      "filename": "primitive-types.54.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 474,
+      "filename": "primitive-types.55.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `string` type to core `(ref null? (array (mut? i16)))` type, found `anyref`"
+    },
+    {
+      "type": "module",
+      "line": 485,
+      "filename": "primitive-types.56.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 493,
+      "filename": "primitive-types.57.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `string` type to core `(ref null? (array (mut? i8)))` type, found `anyref`"
+    },
+    {
+      "type": "module",
+      "line": 501,
+      "filename": "primitive-types.58.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 510,
+      "filename": "primitive-types.59.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `string` type to core `(ref null? (array (mut? i8)))` type, found `anyref`"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/snapshots/cli/component-model-gc/primitive-types.wast/0.print b/tests/snapshots/cli/component-model-gc/primitive-types.wast/0.print
new file mode 100644
index 00000000..cf2a0c1a
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/primitive-types.wast/0.print
@@ -0,0 +1,6 @@
+(component
+  (type (;0;) (func (param "x" bool)))
+  (import "f" (func $f (;0;) (type 0)))
+  (core type $ty (;0;) (func (param i32)))
+  (core func (;0;) (canon lower (func $f) gc (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/primitive-types.wast/10.print b/tests/snapshots/cli/component-model-gc/primitive-types.wast/10.print
new file mode 100644
index 00000000..4a0d484c
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/primitive-types.wast/10.print
@@ -0,0 +1,8 @@
+(component
+  (type (;0;) (list s8))
+  (type (;1;) (func (param "x" 0)))
+  (import "f" (func $f (;0;) (type 1)))
+  (core type $list (;0;) (array i8))
+  (core type $ty (;1;) (func (param (ref $list))))
+  (core func (;0;) (canon lower (func $f) gc (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/primitive-types.wast/12.print b/tests/snapshots/cli/component-model-gc/primitive-types.wast/12.print
new file mode 100644
index 00000000..50e93a20
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/primitive-types.wast/12.print
@@ -0,0 +1,6 @@
+(component
+  (type (;0;) (func (param "x" u16)))
+  (import "f" (func $f (;0;) (type 0)))
+  (core type $ty (;0;) (func (param i32)))
+  (core func (;0;) (canon lower (func $f) gc (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/primitive-types.wast/14.print b/tests/snapshots/cli/component-model-gc/primitive-types.wast/14.print
new file mode 100644
index 00000000..7c701657
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/primitive-types.wast/14.print
@@ -0,0 +1,8 @@
+(component
+  (type (;0;) (list u16))
+  (type (;1;) (func (param "x" 0)))
+  (import "f" (func $f (;0;) (type 1)))
+  (core type $list (;0;) (array i16))
+  (core type $ty (;1;) (func (param (ref $list))))
+  (core func (;0;) (canon lower (func $f) gc (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/primitive-types.wast/16.print b/tests/snapshots/cli/component-model-gc/primitive-types.wast/16.print
new file mode 100644
index 00000000..cbc821d9
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/primitive-types.wast/16.print
@@ -0,0 +1,6 @@
+(component
+  (type (;0;) (func (param "x" s16)))
+  (import "f" (func $f (;0;) (type 0)))
+  (core type $ty (;0;) (func (param i32)))
+  (core func (;0;) (canon lower (func $f) gc (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/primitive-types.wast/18.print b/tests/snapshots/cli/component-model-gc/primitive-types.wast/18.print
new file mode 100644
index 00000000..1a4523c5
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/primitive-types.wast/18.print
@@ -0,0 +1,8 @@
+(component
+  (type (;0;) (list s16))
+  (type (;1;) (func (param "x" 0)))
+  (import "f" (func $f (;0;) (type 1)))
+  (core type $list (;0;) (array i16))
+  (core type $ty (;1;) (func (param (ref $list))))
+  (core func (;0;) (canon lower (func $f) gc (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/primitive-types.wast/2.print b/tests/snapshots/cli/component-model-gc/primitive-types.wast/2.print
new file mode 100644
index 00000000..47851e05
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/primitive-types.wast/2.print
@@ -0,0 +1,8 @@
+(component
+  (type (;0;) (list bool))
+  (type (;1;) (func (param "x" 0)))
+  (import "f" (func $f (;0;) (type 1)))
+  (core type $list (;0;) (array i8))
+  (core type $ty (;1;) (func (param (ref $list))))
+  (core func (;0;) (canon lower (func $f) gc (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/primitive-types.wast/20.print b/tests/snapshots/cli/component-model-gc/primitive-types.wast/20.print
new file mode 100644
index 00000000..b41590cc
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/primitive-types.wast/20.print
@@ -0,0 +1,6 @@
+(component
+  (type (;0;) (func (param "x" u32)))
+  (import "f" (func $f (;0;) (type 0)))
+  (core type $ty (;0;) (func (param i32)))
+  (core func (;0;) (canon lower (func $f) gc (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/primitive-types.wast/22.print b/tests/snapshots/cli/component-model-gc/primitive-types.wast/22.print
new file mode 100644
index 00000000..c2dae1b0
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/primitive-types.wast/22.print
@@ -0,0 +1,8 @@
+(component
+  (type (;0;) (list u32))
+  (type (;1;) (func (param "x" 0)))
+  (import "f" (func $f (;0;) (type 1)))
+  (core type $list (;0;) (array i32))
+  (core type $ty (;1;) (func (param (ref $list))))
+  (core func (;0;) (canon lower (func $f) gc (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/primitive-types.wast/24.print b/tests/snapshots/cli/component-model-gc/primitive-types.wast/24.print
new file mode 100644
index 00000000..d50e97e8
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/primitive-types.wast/24.print
@@ -0,0 +1,6 @@
+(component
+  (type (;0;) (func (param "x" s32)))
+  (import "f" (func $f (;0;) (type 0)))
+  (core type $ty (;0;) (func (param i32)))
+  (core func (;0;) (canon lower (func $f) gc (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/primitive-types.wast/26.print b/tests/snapshots/cli/component-model-gc/primitive-types.wast/26.print
new file mode 100644
index 00000000..5301d066
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/primitive-types.wast/26.print
@@ -0,0 +1,8 @@
+(component
+  (type (;0;) (list s32))
+  (type (;1;) (func (param "x" 0)))
+  (import "f" (func $f (;0;) (type 1)))
+  (core type $list (;0;) (array i32))
+  (core type $ty (;1;) (func (param (ref $list))))
+  (core func (;0;) (canon lower (func $f) gc (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/primitive-types.wast/28.print b/tests/snapshots/cli/component-model-gc/primitive-types.wast/28.print
new file mode 100644
index 00000000..4113203a
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/primitive-types.wast/28.print
@@ -0,0 +1,6 @@
+(component
+  (type (;0;) (func (param "x" u64)))
+  (import "f" (func $f (;0;) (type 0)))
+  (core type $ty (;0;) (func (param i64)))
+  (core func (;0;) (canon lower (func $f) gc (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/primitive-types.wast/30.print b/tests/snapshots/cli/component-model-gc/primitive-types.wast/30.print
new file mode 100644
index 00000000..25e8f08b
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/primitive-types.wast/30.print
@@ -0,0 +1,8 @@
+(component
+  (type (;0;) (list u64))
+  (type (;1;) (func (param "x" 0)))
+  (import "f" (func $f (;0;) (type 1)))
+  (core type $list (;0;) (array i64))
+  (core type $ty (;1;) (func (param (ref $list))))
+  (core func (;0;) (canon lower (func $f) gc (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/primitive-types.wast/32.print b/tests/snapshots/cli/component-model-gc/primitive-types.wast/32.print
new file mode 100644
index 00000000..eb3981b1
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/primitive-types.wast/32.print
@@ -0,0 +1,6 @@
+(component
+  (type (;0;) (func (param "x" s64)))
+  (import "f" (func $f (;0;) (type 0)))
+  (core type $ty (;0;) (func (param i64)))
+  (core func (;0;) (canon lower (func $f) gc (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/primitive-types.wast/34.print b/tests/snapshots/cli/component-model-gc/primitive-types.wast/34.print
new file mode 100644
index 00000000..74aaa946
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/primitive-types.wast/34.print
@@ -0,0 +1,8 @@
+(component
+  (type (;0;) (list s64))
+  (type (;1;) (func (param "x" 0)))
+  (import "f" (func $f (;0;) (type 1)))
+  (core type $list (;0;) (array i64))
+  (core type $ty (;1;) (func (param (ref $list))))
+  (core func (;0;) (canon lower (func $f) gc (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/primitive-types.wast/36.print b/tests/snapshots/cli/component-model-gc/primitive-types.wast/36.print
new file mode 100644
index 00000000..210899d3
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/primitive-types.wast/36.print
@@ -0,0 +1,6 @@
+(component
+  (type (;0;) (func (param "x" f32)))
+  (import "f" (func $f (;0;) (type 0)))
+  (core type $ty (;0;) (func (param f32)))
+  (core func (;0;) (canon lower (func $f) gc (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/primitive-types.wast/38.print b/tests/snapshots/cli/component-model-gc/primitive-types.wast/38.print
new file mode 100644
index 00000000..f80dfbac
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/primitive-types.wast/38.print
@@ -0,0 +1,8 @@
+(component
+  (type (;0;) (list f32))
+  (type (;1;) (func (param "x" 0)))
+  (import "f" (func $f (;0;) (type 1)))
+  (core type $list (;0;) (array f32))
+  (core type $ty (;1;) (func (param (ref $list))))
+  (core func (;0;) (canon lower (func $f) gc (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/primitive-types.wast/4.print b/tests/snapshots/cli/component-model-gc/primitive-types.wast/4.print
new file mode 100644
index 00000000..48b0216d
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/primitive-types.wast/4.print
@@ -0,0 +1,6 @@
+(component
+  (type (;0;) (func (param "x" u8)))
+  (import "f" (func $f (;0;) (type 0)))
+  (core type $ty (;0;) (func (param i32)))
+  (core func (;0;) (canon lower (func $f) gc (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/primitive-types.wast/40.print b/tests/snapshots/cli/component-model-gc/primitive-types.wast/40.print
new file mode 100644
index 00000000..c457bbb5
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/primitive-types.wast/40.print
@@ -0,0 +1,6 @@
+(component
+  (type (;0;) (func (param "x" f64)))
+  (import "f" (func $f (;0;) (type 0)))
+  (core type $ty (;0;) (func (param f64)))
+  (core func (;0;) (canon lower (func $f) gc (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/primitive-types.wast/42.print b/tests/snapshots/cli/component-model-gc/primitive-types.wast/42.print
new file mode 100644
index 00000000..6ec82a33
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/primitive-types.wast/42.print
@@ -0,0 +1,8 @@
+(component
+  (type (;0;) (list f64))
+  (type (;1;) (func (param "x" 0)))
+  (import "f" (func $f (;0;) (type 1)))
+  (core type $list (;0;) (array f64))
+  (core type $ty (;1;) (func (param (ref $list))))
+  (core func (;0;) (canon lower (func $f) gc (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/primitive-types.wast/44.print b/tests/snapshots/cli/component-model-gc/primitive-types.wast/44.print
new file mode 100644
index 00000000..c79c06f0
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/primitive-types.wast/44.print
@@ -0,0 +1,6 @@
+(component
+  (type (;0;) (func (param "x" char)))
+  (import "f" (func $f (;0;) (type 0)))
+  (core type $ty (;0;) (func (param i32)))
+  (core func (;0;) (canon lower (func $f) gc (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/primitive-types.wast/46.print b/tests/snapshots/cli/component-model-gc/primitive-types.wast/46.print
new file mode 100644
index 00000000..b7208c2b
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/primitive-types.wast/46.print
@@ -0,0 +1,8 @@
+(component
+  (type (;0;) (list char))
+  (type (;1;) (func (param "x" 0)))
+  (import "f" (func $f (;0;) (type 1)))
+  (core type $list (;0;) (array i32))
+  (core type $ty (;1;) (func (param (ref $list))))
+  (core func (;0;) (canon lower (func $f) gc (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/primitive-types.wast/48.print b/tests/snapshots/cli/component-model-gc/primitive-types.wast/48.print
new file mode 100644
index 00000000..04db7967
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/primitive-types.wast/48.print
@@ -0,0 +1,7 @@
+(component
+  (type (;0;) (func (param "x" string)))
+  (import "f" (func $f (;0;) (type 0)))
+  (core type $s (;0;) (array i8))
+  (core type $ty (;1;) (func (param (ref $s))))
+  (core func (;0;) (canon lower (func $f) gc string-encoding=utf8 (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/primitive-types.wast/50.print b/tests/snapshots/cli/component-model-gc/primitive-types.wast/50.print
new file mode 100644
index 00000000..869be9a2
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/primitive-types.wast/50.print
@@ -0,0 +1,9 @@
+(component
+  (type (;0;) (list string))
+  (type (;1;) (func (param "x" 0)))
+  (import "f" (func $f (;0;) (type 1)))
+  (core type $s (;0;) (array i8))
+  (core type $list (;1;) (array (ref $s)))
+  (core type $ty (;2;) (func (param (ref $list))))
+  (core func (;0;) (canon lower (func $f) gc string-encoding=utf8 (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/primitive-types.wast/52.print b/tests/snapshots/cli/component-model-gc/primitive-types.wast/52.print
new file mode 100644
index 00000000..8c851fd8
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/primitive-types.wast/52.print
@@ -0,0 +1,7 @@
+(component
+  (type (;0;) (func (param "x" string)))
+  (import "f" (func $f (;0;) (type 0)))
+  (core type $s (;0;) (array i16))
+  (core type $ty (;1;) (func (param (ref $s))))
+  (core func (;0;) (canon lower (func $f) gc string-encoding=utf16 (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/primitive-types.wast/54.print b/tests/snapshots/cli/component-model-gc/primitive-types.wast/54.print
new file mode 100644
index 00000000..d075c010
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/primitive-types.wast/54.print
@@ -0,0 +1,9 @@
+(component
+  (type (;0;) (list string))
+  (type (;1;) (func (param "x" 0)))
+  (import "f" (func $f (;0;) (type 1)))
+  (core type $s (;0;) (array i16))
+  (core type $list (;1;) (array (ref $s)))
+  (core type $ty (;2;) (func (param (ref $list))))
+  (core func (;0;) (canon lower (func $f) gc string-encoding=utf16 (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/primitive-types.wast/56.print b/tests/snapshots/cli/component-model-gc/primitive-types.wast/56.print
new file mode 100644
index 00000000..cea475e6
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/primitive-types.wast/56.print
@@ -0,0 +1,7 @@
+(component
+  (type (;0;) (func (param "x" string)))
+  (import "f" (func $f (;0;) (type 0)))
+  (core type $s (;0;) (array i8))
+  (core type $ty (;1;) (func (param (ref $s))))
+  (core func (;0;) (canon lower (func $f) gc string-encoding=latin1+utf16 (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/primitive-types.wast/58.print b/tests/snapshots/cli/component-model-gc/primitive-types.wast/58.print
new file mode 100644
index 00000000..dda22d51
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/primitive-types.wast/58.print
@@ -0,0 +1,9 @@
+(component
+  (type (;0;) (list string))
+  (type (;1;) (func (param "x" 0)))
+  (import "f" (func $f (;0;) (type 1)))
+  (core type $s (;0;) (array i8))
+  (core type $list (;1;) (array (ref $s)))
+  (core type $ty (;2;) (func (param (ref $list))))
+  (core func (;0;) (canon lower (func $f) gc string-encoding=latin1+utf16 (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/primitive-types.wast/6.print b/tests/snapshots/cli/component-model-gc/primitive-types.wast/6.print
new file mode 100644
index 00000000..e79af1e4
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/primitive-types.wast/6.print
@@ -0,0 +1,8 @@
+(component
+  (type (;0;) (list u8))
+  (type (;1;) (func (param "x" 0)))
+  (import "f" (func $f (;0;) (type 1)))
+  (core type $list (;0;) (array i8))
+  (core type $ty (;1;) (func (param (ref $list))))
+  (core func (;0;) (canon lower (func $f) gc (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/primitive-types.wast/8.print b/tests/snapshots/cli/component-model-gc/primitive-types.wast/8.print
new file mode 100644
index 00000000..a48c4193
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/primitive-types.wast/8.print
@@ -0,0 +1,6 @@
+(component
+  (type (;0;) (func (param "x" s8)))
+  (import "f" (func $f (;0;) (type 0)))
+  (core type $ty (;0;) (func (param i32)))
+  (core func (;0;) (canon lower (func $f) gc (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/record-types.wast.json b/tests/snapshots/cli/component-model-gc/record-types.wast.json
new file mode 100644
index 00000000..a1ff58e3
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/record-types.wast.json
@@ -0,0 +1,32 @@
+{
+  "source_filename": "tests/cli/component-model-gc/record-types.wast",
+  "commands": [
+    {
+      "type": "module",
+      "line": 3,
+      "filename": "record-types.0.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 41,
+      "filename": "record-types.1.wasm",
+      "module_type": "binary",
+      "text": "core `struct` has 2 fields, but component `record` has 1 fields"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 57,
+      "filename": "record-types.2.wasm",
+      "module_type": "binary",
+      "text": "core `struct` has 1 fields, but component `record` has 2 fields"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 73,
+      "filename": "record-types.3.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `record` type to core `(ref null? (struct ...))`, but found `i32`"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/snapshots/cli/component-model-gc/record-types.wast/0.print b/tests/snapshots/cli/component-model-gc/record-types.wast/0.print
new file mode 100644
index 00000000..a66553e5
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/record-types.wast/0.print
@@ -0,0 +1,17 @@
+(component
+  (type $record (;0;) (record (field "a" bool) (field "b" u8) (field "c" s8) (field "d" u16) (field "e" s16) (field "f" u32) (field "g" s32) (field "h" u64) (field "i" s64) (field "j" f32) (field "k" f64) (field "l" char) (field "m" string)))
+  (core type $string (;0;) (array i8))
+  (core type $record (;1;) (struct (field i8) (field i8) (field i8) (field i16) (field i16) (field i32) (field i32) (field i64) (field i64) (field f32) (field f64) (field i32) (field (ref $string))))
+  (core type $ty (;2;) (func (param (ref $record))))
+  (type (;1;)
+    (instance
+      (alias outer 1 $record (type (;0;)))
+      (export (;1;) "ty" (type (eq 0)))
+      (type (;2;) (func (param "x" 1)))
+      (export (;0;) "f" (func (type 2)))
+    )
+  )
+  (import "i" (instance $i (;0;) (type 1)))
+  (alias export $i "f" (func (;0;)))
+  (core func (;0;) (canon lower (func 0) gc string-encoding=utf8 (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/resource-types.wast.json b/tests/snapshots/cli/component-model-gc/resource-types.wast.json
new file mode 100644
index 00000000..8d0a6b91
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/resource-types.wast.json
@@ -0,0 +1,31 @@
+{
+  "source_filename": "tests/cli/component-model-gc/resource-types.wast",
+  "commands": [
+    {
+      "type": "module",
+      "line": 3,
+      "filename": "resource-types.0.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 13,
+      "filename": "resource-types.1.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `own` type into core `(ref null? extern)` type, but found `anyref`"
+    },
+    {
+      "type": "module",
+      "line": 24,
+      "filename": "resource-types.2.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 34,
+      "filename": "resource-types.3.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `borrow` type into core `(ref null? extern)` type, but found `anyref`"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/snapshots/cli/component-model-gc/resource-types.wast/0.print b/tests/snapshots/cli/component-model-gc/resource-types.wast/0.print
new file mode 100644
index 00000000..4e59d973
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/resource-types.wast/0.print
@@ -0,0 +1,14 @@
+(component
+  (core type $ty (;0;) (func (param externref)))
+  (type (;0;)
+    (instance
+      (export (;0;) "r" (type (sub resource)))
+      (type (;1;) (own 0))
+      (type (;2;) (func (param "x" 1)))
+      (export (;0;) "f" (func (type 2)))
+    )
+  )
+  (import "i" (instance $i (;0;) (type 0)))
+  (alias export $i "f" (func (;0;)))
+  (core func (;0;) (canon lower (func 0) gc string-encoding=utf8 (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/resource-types.wast/2.print b/tests/snapshots/cli/component-model-gc/resource-types.wast/2.print
new file mode 100644
index 00000000..e8b12f17
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/resource-types.wast/2.print
@@ -0,0 +1,14 @@
+(component
+  (core type $ty (;0;) (func (param externref)))
+  (type (;0;)
+    (instance
+      (export (;0;) "r" (type (sub resource)))
+      (type (;1;) (borrow 0))
+      (type (;2;) (func (param "x" 1)))
+      (export (;0;) "f" (func (type 2)))
+    )
+  )
+  (import "i" (instance $i (;0;) (type 0)))
+  (alias export $i "f" (func (;0;)))
+  (core func (;0;) (canon lower (func 0) gc string-encoding=utf8 (core-type $ty)))
+)
diff --git a/tests/snapshots/cli/component-model-gc/tuple-types.wast.json b/tests/snapshots/cli/component-model-gc/tuple-types.wast.json
new file mode 100644
index 00000000..c8bf04e2
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/tuple-types.wast.json
@@ -0,0 +1,32 @@
+{
+  "source_filename": "tests/cli/component-model-gc/tuple-types.wast",
+  "commands": [
+    {
+      "type": "module",
+      "line": 3,
+      "filename": "tuple-types.0.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 18,
+      "filename": "tuple-types.1.wasm",
+      "module_type": "binary",
+      "text": "core `struct` has 1 fields, but component `tuple` has 2 fields"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 34,
+      "filename": "tuple-types.2.wasm",
+      "module_type": "binary",
+      "text": "core `struct` has 3 fields, but component `tuple` has 2 fields"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 52,
+      "filename": "tuple-types.3.wasm",
+      "module_type": "binary",
+      "text": "expected to lower component `tuple` type to core `(ref null? (struct ...))`, but found `i32`"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/snapshots/cli/component-model-gc/tuple-types.wast/0.print b/tests/snapshots/cli/component-model-gc/tuple-types.wast/0.print
new file mode 100644
index 00000000..e28687b5
--- /dev/null
+++ b/tests/snapshots/cli/component-model-gc/tuple-types.wast/0.print
@@ -0,0 +1,17 @@
+(component
+  (type $tup (;0;) (tuple string bool))
+  (core type $string (;0;) (array i8))
+  (core type $tup (;1;) (struct (field (ref $string)) (field i8)))
+  (core type $ty (;2;) (func (param (ref $tup))))
+  (type (;1;)
+    (instance
+      (alias outer 1 $tup (type (;0;)))
+      (export (;1;) "ty" (type (eq 0)))
+      (type (;2;) (func (param "x" 1)))
+      (export (;0;) "f" (func (type 2)))
+    )
+  )
+  (import "i" (instance $i (;0;) (type 1)))
+  (alias export $i "f" (func (;0;)))
+  (core func (;0;) (canon lower (func 0) gc string-encoding=utf8 (core-type $ty)))
+)