Repository: bytecodealliance/wasmtime
Pull request #8466 (2024-04-24 20:58:15)

cranelift: Simplify "no VReg aliases" assertions


Patch:
diff --git a/cranelift/codegen/src/isa/x64/inst/args.rs b/cranelift/codegen/src/isa/x64/inst/args.rs
index a434703bb..aa7f19ce5 100644
--- a/cranelift/codegen/src/isa/x64/inst/args.rs
+++ b/cranelift/codegen/src/isa/x64/inst/args.rs
@@ -55,6 +55,12 @@ macro_rules! newtype_of_reg {
             }
         }
 
+        impl From<&$newtype_reg> for Reg {
+            fn from(r: &$newtype_reg) -> Self {
+                r.0
+            }
+        }
+
         impl $newtype_reg {
             /// Create this newtype from the given register, or return `None` if the register
             /// is not a valid instance of this newtype.
@@ -365,13 +371,13 @@ impl Amode {
                     collector.reg_use(*base);
                 }
             }
-            Amode::ImmRegRegShift { base, index, .. } => {
+            &Amode::ImmRegRegShift { base, index, .. } => {
                 debug_assert_ne!(base.to_reg(), regs::rbp());
                 debug_assert_ne!(base.to_reg(), regs::rsp());
-                collector.reg_use(base.to_reg());
+                collector.reg_use(base);
                 debug_assert_ne!(index.to_reg(), regs::rbp());
                 debug_assert_ne!(index.to_reg(), regs::rsp());
-                collector.reg_use(index.to_reg());
+                collector.reg_use(index);
             }
             Amode::RipRelative { .. } => {
                 // RIP isn't involved in regalloc.
@@ -388,9 +394,9 @@ impl Amode {
             Amode::ImmReg { base, .. } => {
                 collector.reg_late_use(*base);
             }
-            Amode::ImmRegRegShift { base, index, .. } => {
-                collector.reg_late_use(base.to_reg());
-                collector.reg_late_use(index.to_reg());
+            &Amode::ImmRegRegShift { base, index, .. } => {
+                collector.reg_late_use(base);
+                collector.reg_late_use(index);
             }
             Amode::RipRelative { .. } => {
                 // RIP isn't involved in regalloc.
diff --git a/cranelift/codegen/src/isa/x64/inst/mod.rs b/cranelift/codegen/src/isa/x64/inst/mod.rs
index 5785a68c3..277bafd8e 100644
--- a/cranelift/codegen/src/isa/x64/inst/mod.rs
+++ b/cranelift/codegen/src/isa/x64/inst/mod.rs
@@ -1912,29 +1912,29 @@ fn x64_get_operands<F: Fn(VReg) -> VReg>(inst: &Inst, collector: &mut OperandCol
         Inst::AluRmiR {
             src1, src2, dst, ..
         } => {
-            collector.reg_use(src1.to_reg());
-            collector.reg_reuse_def(dst.to_writable_reg(), 0);
+            collector.reg_use(src1);
+            collector.reg_reuse_def(*dst, 0);
             src2.get_operands(collector);
         }
-        Inst::AluConstOp { dst, .. } => collector.reg_def(dst.to_writable_reg()),
+        &Inst::AluConstOp { dst, .. } => collector.reg_def(dst),
         Inst::AluRM { src1_dst, src2, .. } => {
-            collector.reg_use(src2.to_reg());
+            collector.reg_use(src2);
             src1_dst.get_operands(collector);
         }
         Inst::AluRmRVex {
             src1, src2, dst, ..
         } => {
-            collector.reg_def(dst.to_writable_reg());
-            collector.reg_use(src1.to_reg());
+            collector.reg_def(*dst);
+            collector.reg_use(src1);
             src2.get_operands(collector);
         }
-        Inst::Not { src, dst, .. } => {
-            collector.reg_use(src.to_reg());
-            collector.reg_reuse_def(dst.to_writable_reg(), 0);
+        &Inst::Not { src, dst, .. } => {
+            collector.reg_use(src);
+            collector.reg_reuse_def(dst, 0);
         }
-        Inst::Neg { src, dst, .. } => {
-            collector.reg_use(src.to_reg());
-            collector.reg_reuse_def(dst.to_writable_reg(), 0);
+        &Inst::Neg { src, dst, .. } => {
+            collector.reg_use(src);
+            collector.reg_reuse_def(dst, 0);
         }
         Inst::Div {
             dividend_lo,
@@ -1952,22 +1952,22 @@ fn x64_get_operands<F: Fn(VReg) -> VReg>(inst: &Inst, collector: &mut OperandCol
         } => {
             match inst {
                 Inst::Div { divisor, .. } => divisor.get_operands(collector),
-                Inst::CheckedSRemSeq { divisor, .. } => collector.reg_use(divisor.to_reg()),
+                Inst::CheckedSRemSeq { divisor, .. } => collector.reg_use(divisor),
                 _ => {}
             }
-            collector.reg_fixed_use(dividend_lo.to_reg(), regs::rax());
-            collector.reg_fixed_use(dividend_hi.to_reg(), regs::rdx());
-            collector.reg_fixed_def(dst_quotient.to_writable_reg(), regs::rax());
-            collector.reg_fixed_def(dst_remainder.to_writable_reg(), regs::rdx());
+            collector.reg_fixed_use(dividend_lo, regs::rax());
+            collector.reg_fixed_use(dividend_hi, regs::rdx());
+            collector.reg_fixed_def(*dst_quotient, regs::rax());
+            collector.reg_fixed_def(*dst_remainder, regs::rdx());
         }
         Inst::Div8 { dividend, dst, .. } | Inst::CheckedSRemSeq8 { dividend, dst, .. } => {
             match inst {
                 Inst::Div8 { divisor, .. } => divisor.get_operands(collector),
-                Inst::CheckedSRemSeq8 { divisor, .. } => collector.reg_use(divisor.to_reg()),
+                Inst::CheckedSRemSeq8 { divisor, .. } => collector.reg_use(divisor),
                 _ => {}
             }
-            collector.reg_fixed_use(dividend.to_reg(), regs::rax());
-            collector.reg_fixed_def(dst.to_writable_reg(), regs::rax());
+            collector.reg_fixed_use(dividend, regs::rax());
+            collector.reg_fixed_def(*dst, regs::rax());
         }
         Inst::Mul {
             src1,
@@ -1976,53 +1976,53 @@ fn x64_get_operands<F: Fn(VReg) -> VReg>(inst: &Inst, collector: &mut OperandCol
             dst_hi,
             ..
         } => {
-            collector.reg_fixed_use(src1.to_reg(), regs::rax());
-            collector.reg_fixed_def(dst_lo.to_writable_reg(), regs::rax());
-            collector.reg_fixed_def(dst_hi.to_writable_reg(), regs::rdx());
+            collector.reg_fixed_use(src1, regs::rax());
+            collector.reg_fixed_def(*dst_lo, regs::rax());
+            collector.reg_fixed_def(*dst_hi, regs::rdx());
             src2.get_operands(collector);
         }
         Inst::Mul8 {
             src1, src2, dst, ..
         } => {
-            collector.reg_fixed_use(src1.to_reg(), regs::rax());
-            collector.reg_fixed_def(dst.to_writable_reg(), regs::rax());
+            collector.reg_fixed_use(src1, regs::rax());
+            collector.reg_fixed_def(*dst, regs::rax());
             src2.get_operands(collector);
         }
         Inst::IMul {
             src1, src2, dst, ..
         } => {
-            collector.reg_use(src1.to_reg());
-            collector.reg_reuse_def(dst.to_writable_reg(), 0);
+            collector.reg_use(src1);
+            collector.reg_reuse_def(*dst, 0);
             src2.get_operands(collector);
         }
         Inst::IMulImm { src1, dst, .. } => {
-            collector.reg_def(dst.to_writable_reg());
+            collector.reg_def(*dst);
             src1.get_operands(collector);
         }
-        Inst::SignExtendData { size, src, dst } => {
+        &Inst::SignExtendData { size, src, dst } => {
             match size {
                 OperandSize::Size8 => {
                     // Note `rax` on both src and dest: 8->16 extend
                     // does AL -> AX.
-                    collector.reg_fixed_use(src.to_reg(), regs::rax());
-                    collector.reg_fixed_def(dst.to_writable_reg(), regs::rax());
+                    collector.reg_fixed_use(src, regs::rax());
+                    collector.reg_fixed_def(dst, regs::rax());
                 }
                 _ => {
                     // All other widths do RAX -> RDX (AX -> DX:AX,
                     // EAX -> EDX:EAX).
-                    collector.reg_fixed_use(src.to_reg(), regs::rax());
-                    collector.reg_fixed_def(dst.to_writable_reg(), regs::rdx());
+                    collector.reg_fixed_use(src, regs::rax());
+                    collector.reg_fixed_def(dst, regs::rdx());
                 }
             }
         }
         Inst::UnaryRmR { src, dst, .. }
         | Inst::UnaryRmRVex { src, dst, .. }
         | Inst::UnaryRmRImmVex { src, dst, .. } => {
-            collector.reg_def(dst.to_writable_reg());
+            collector.reg_def(*dst);
             src.get_operands(collector);
         }
         Inst::XmmUnaryRmR { src, dst, .. } | Inst::XmmUnaryRmRImm { src, dst, .. } => {
-            collector.reg_def(dst.to_writable_reg());
+            collector.reg_def(*dst);
             src.get_operands(collector);
         }
         Inst::XmmUnaryRmREvex { src, dst, .. }
@@ -2030,21 +2030,21 @@ fn x64_get_operands<F: Fn(VReg) -> VReg>(inst: &Inst, collector: &mut OperandCol
         | Inst::XmmUnaryRmRUnaligned { src, dst, .. }
         | Inst::XmmUnaryRmRVex { src, dst, .. }
         | Inst::XmmUnaryRmRImmVex { src, dst, .. } => {
-            collector.reg_def(dst.to_writable_reg());
+            collector.reg_def(*dst);
             src.get_operands(collector);
         }
         Inst::XmmRmR {
             src1, src2, dst, ..
         } => {
-            collector.reg_use(src1.to_reg());
-            collector.reg_reuse_def(dst.to_writable_reg(), 0);
+            collector.reg_use(src1);
+            collector.reg_reuse_def(*dst, 0);
             src2.get_operands(collector);
         }
         Inst::XmmRmRUnaligned {
             src1, src2, dst, ..
         } => {
-            collector.reg_use(src1.to_reg());
-            collector.reg_reuse_def(dst.to_writable_reg(), 0);
+            collector.reg_use(src1);
+            collector.reg_reuse_def(*dst, 0);
             src2.get_operands(collector);
         }
         Inst::XmmRmRBlend {
@@ -2054,35 +2054,34 @@ fn x64_get_operands<F: Fn(VReg) -> VReg>(inst: &Inst, collector: &mut OperandCol
             dst,
             op,
         } => {
-            assert!(
-                *op == SseOpcode::Blendvpd
-                    || *op == SseOpcode::Blendvps
-                    || *op == SseOpcode::Pblendvb
-            );
-            collector.reg_use(src1.to_reg());
-            collector.reg_fixed_use(mask.to_reg(), regs::xmm0());
-            collector.reg_reuse_def(dst.to_writable_reg(), 0);
+            assert!(matches!(
+                op,
+                SseOpcode::Blendvpd | SseOpcode::Blendvps | SseOpcode::Pblendvb
+            ));
+            collector.reg_use(src1);
+            collector.reg_fixed_use(mask, regs::xmm0());
+            collector.reg_reuse_def(*dst, 0);
             src2.get_operands(collector);
         }
         Inst::XmmRmiRVex {
             src1, src2, dst, ..
         } => {
-            collector.reg_def(dst.to_writable_reg());
-            collector.reg_use(src1.to_reg());
+            collector.reg_def(*dst);
+            collector.reg_use(src1);
             src2.get_operands(collector);
         }
         Inst::XmmRmRImmVex {
             src1, src2, dst, ..
         } => {
-            collector.reg_def(dst.to_writable_reg());
-            collector.reg_use(src1.to_reg());
+            collector.reg_def(*dst);
+            collector.reg_use(src1);
             src2.get_operands(collector);
         }
         Inst::XmmVexPinsr {
             src1, src2, dst, ..
         } => {
-            collector.reg_def(dst.to_writable_reg());
-            collector.reg_use(src1.to_reg());
+            collector.reg_def(*dst);
+            collector.reg_use(src1);
             src2.get_operands(collector);
         }
         Inst::XmmRmRVex3 {
@@ -2092,9 +2091,9 @@ fn x64_get_operands<F: Fn(VReg) -> VReg>(inst: &Inst, collector: &mut OperandCol
             dst,
             ..
         } => {
-            collector.reg_use(src1.to_reg());
-            collector.reg_reuse_def(dst.to_writable_reg(), 0);
-            collector.reg_use(src2.to_reg());
+            collector.reg_use(src1);
+            collector.reg_reuse_def(*dst, 0);
+            collector.reg_use(src2);
             src3.get_operands(collector);
         }
         Inst::XmmRmRBlendVex {
@@ -2104,10 +2103,10 @@ fn x64_get_operands<F: Fn(VReg) -> VReg>(inst: &Inst, collector: &mut OperandCol
             dst,
             ..
         } => {
-            collector.reg_def(dst.to_writable_reg());
-            collector.reg_use(src1.to_reg());
+            collector.reg_def(*dst);
+            collector.reg_use(src1);
             src2.get_operands(collector);
-            collector.reg_use(mask.to_reg());
+            collector.reg_use(mask);
         }
         Inst::XmmRmREvex {
             op,
@@ -2117,9 +2116,9 @@ fn x64_get_operands<F: Fn(VReg) -> VReg>(inst: &Inst, collector: &mut OperandCol
             ..
         } => {
             assert_ne!(*op, Avx512Opcode::Vpermi2b);
-            collector.reg_use(src1.to_reg());
+            collector.reg_use(src1);
             src2.get_operands(collector);
-            collector.reg_def(dst.to_writable_reg());
+            collector.reg_def(*dst);
         }
         Inst::XmmRmREvex3 {
             op,
@@ -2130,10 +2129,10 @@ fn x64_get_operands<F: Fn(VReg) -> VReg>(inst: &Inst, collector: &mut OperandCol
             ..
         } => {
             assert_eq!(*op, Avx512Opcode::Vpermi2b);
-            collector.reg_use(src1.to_reg());
-            collector.reg_use(src2.to_reg());
+            collector.reg_use(src1);
+            collector.reg_use(src2);
             src3.get_operands(collector);
-            collector.reg_reuse_def(dst.to_writable_reg(), 0); // Reuse `src1`.
+            collector.reg_reuse_def(*dst, 0); // Reuse `src1`.
         }
         Inst::XmmRmRImm {
             src1, src2, dst, ..
@@ -2142,101 +2141,101 @@ fn x64_get_operands<F: Fn(VReg) -> VReg>(inst: &Inst, collector: &mut OperandCol
             collector.reg_reuse_def(*dst, 0);
             src2.get_operands(collector);
         }
-        Inst::XmmUninitializedValue { dst } => collector.reg_def(dst.to_writable_reg()),
-        Inst::XmmMinMaxSeq { lhs, rhs, dst, .. } => {
-            collector.reg_use(rhs.to_reg());
-            collector.reg_use(lhs.to_reg());
-            collector.reg_reuse_def(dst.to_writable_reg(), 0); // Reuse RHS.
+        &Inst::XmmUninitializedValue { dst } => collector.reg_def(dst),
+        &Inst::XmmMinMaxSeq { lhs, rhs, dst, .. } => {
+            collector.reg_use(rhs);
+            collector.reg_use(lhs);
+            collector.reg_reuse_def(dst, 0); // Reuse RHS.
         }
         Inst::XmmRmiReg {
             src1, src2, dst, ..
         } => {
-            collector.reg_use(src1.to_reg());
-            collector.reg_reuse_def(dst.to_writable_reg(), 0); // Reuse RHS.
+            collector.reg_use(src1);
+            collector.reg_reuse_def(*dst, 0); // Reuse RHS.
             src2.get_operands(collector);
         }
         Inst::XmmMovRM { src, dst, .. }
         | Inst::XmmMovRMVex { src, dst, .. }
         | Inst::XmmMovRMImm { src, dst, .. }
         | Inst::XmmMovRMImmVex { src, dst, .. } => {
-            collector.reg_use(src.to_reg());
+            collector.reg_use(src);
             dst.get_operands(collector);
         }
         Inst::XmmCmpRmR { src1, src2, .. } => {
-            collector.reg_use(src1.to_reg());
+            collector.reg_use(src1);
             src2.get_operands(collector);
         }
         Inst::XmmCmpRmRVex { src1, src2, .. } => {
-            collector.reg_use(src1.to_reg());
+            collector.reg_use(src1);
             src2.get_operands(collector);
         }
-        Inst::Imm { dst, .. } => {
-            collector.reg_def(dst.to_writable_reg());
+        &Inst::Imm { dst, .. } => {
+            collector.reg_def(dst);
         }
-        Inst::MovRR { src, dst, .. } => {
-            collector.reg_use(src.to_reg());
-            collector.reg_def(dst.to_writable_reg());
+        &Inst::MovRR { src, dst, .. } => {
+            collector.reg_use(src);
+            collector.reg_def(dst);
         }
-        Inst::MovFromPReg { dst, src } => {
+        &Inst::MovFromPReg { dst, src } => {
             debug_assert!(dst.to_reg().to_reg().is_virtual());
-            collector.reg_fixed_nonallocatable(*src);
-            collector.reg_def(dst.to_writable_reg());
+            collector.reg_fixed_nonallocatable(src);
+            collector.reg_def(dst);
         }
         Inst::MovToPReg { dst, src } => {
             debug_assert!(src.to_reg().is_virtual());
-            collector.reg_use(src.to_reg());
+            collector.reg_use(src);
             collector.reg_fixed_nonallocatable(*dst);
         }
-        Inst::XmmToGpr { src, dst, .. }
-        | Inst::XmmToGprVex { src, dst, .. }
-        | Inst::XmmToGprImm { src, dst, .. }
-        | Inst::XmmToGprImmVex { src, dst, .. } => {
-            collector.reg_use(src.to_reg());
-            collector.reg_def(dst.to_writable_reg());
+        &Inst::XmmToGpr { src, dst, .. }
+        | &Inst::XmmToGprVex { src, dst, .. }
+        | &Inst::XmmToGprImm { src, dst, .. }
+        | &Inst::XmmToGprImmVex { src, dst, .. } => {
+            collector.reg_use(src);
+            collector.reg_def(dst);
         }
         Inst::GprToXmm { src, dst, .. } | Inst::GprToXmmVex { src, dst, .. } => {
-            collector.reg_def(dst.to_writable_reg());
+            collector.reg_def(*dst);
             src.get_operands(collector);
         }
         Inst::CvtIntToFloat {
             src1, src2, dst, ..
         } => {
-            collector.reg_use(src1.to_reg());
-            collector.reg_reuse_def(dst.to_writable_reg(), 0);
+            collector.reg_use(src1);
+            collector.reg_reuse_def(*dst, 0);
             src2.get_operands(collector);
         }
         Inst::CvtIntToFloatVex {
             src1, src2, dst, ..
         } => {
-            collector.reg_def(dst.to_writable_reg());
-            collector.reg_use(src1.to_reg());
+            collector.reg_def(*dst);
+            collector.reg_use(src1);
             src2.get_operands(collector);
         }
-        Inst::CvtUint64ToFloatSeq {
+        &Inst::CvtUint64ToFloatSeq {
             src,
             dst,
             tmp_gpr1,
             tmp_gpr2,
             ..
         } => {
-            collector.reg_use(src.to_reg());
-            collector.reg_early_def(dst.to_writable_reg());
-            collector.reg_early_def(tmp_gpr1.to_writable_reg());
-            collector.reg_early_def(tmp_gpr2.to_writable_reg());
+            collector.reg_use(src);
+            collector.reg_early_def(dst);
+            collector.reg_early_def(tmp_gpr1);
+            collector.reg_early_def(tmp_gpr2);
         }
-        Inst::CvtFloatToSintSeq {
+        &Inst::CvtFloatToSintSeq {
             src,
             dst,
             tmp_xmm,
             tmp_gpr,
             ..
         } => {
-            collector.reg_use(src.to_reg());
-            collector.reg_early_def(dst.to_writable_reg());
-            collector.reg_early_def(tmp_gpr.to_writable_reg());
-            collector.reg_early_def(tmp_xmm.to_writable_reg());
+            collector.reg_use(src);
+            collector.reg_early_def(dst);
+            collector.reg_early_def(tmp_gpr);
+            collector.reg_early_def(tmp_xmm);
         }
-        Inst::CvtFloatToUintSeq {
+        &Inst::CvtFloatToUintSeq {
             src,
             dst,
             tmp_gpr,
@@ -2244,11 +2243,11 @@ fn x64_get_operands<F: Fn(VReg) -> VReg>(inst: &Inst, collector: &mut OperandCol
             tmp_xmm2,
             ..
         } => {
-            collector.reg_use(src.to_reg());
-            collector.reg_early_def(dst.to_writable_reg());
-            collector.reg_early_def(tmp_gpr.to_writable_reg());
-            collector.reg_early_def(tmp_xmm.to_writable_reg());
-            collector.reg_early_def(tmp_xmm2.to_writable_reg());
+            collector.reg_use(src);
+            collector.reg_early_def(dst);
+            collector.reg_early_def(tmp_gpr);
+            collector.reg_early_def(tmp_xmm);
+            collector.reg_early_def(tmp_xmm2);
         }
 
         Inst::MovImmM { dst, .. } => {
@@ -2256,44 +2255,44 @@ fn x64_get_operands<F: Fn(VReg) -> VReg>(inst: &Inst, collector: &mut OperandCol
         }
 
         Inst::MovzxRmR { src, dst, .. } => {
-            collector.reg_def(dst.to_writable_reg());
+            collector.reg_def(*dst);
             src.get_operands(collector);
         }
         Inst::Mov64MR { src, dst, .. } => {
-            collector.reg_def(dst.to_writable_reg());
+            collector.reg_def(*dst);
             src.get_operands(collector);
         }
         Inst::LoadEffectiveAddress { addr: src, dst, .. } => {
-            collector.reg_def(dst.to_writable_reg());
+            collector.reg_def(*dst);
             src.get_operands(collector);
         }
         Inst::MovsxRmR { src, dst, .. } => {
-            collector.reg_def(dst.to_writable_reg());
+            collector.reg_def(*dst);
             src.get_operands(collector);
         }
         Inst::MovRM { src, dst, .. } => {
-            collector.reg_use(src.to_reg());
+            collector.reg_use(src);
             dst.get_operands(collector);
         }
         Inst::ShiftR {
             num_bits, src, dst, ..
         } => {
-            collector.reg_use(src.to_reg());
-            collector.reg_reuse_def(dst.to_writable_reg(), 0);
+            collector.reg_use(src);
+            collector.reg_reuse_def(*dst, 0);
             if let &Imm8Reg::Reg { reg } = num_bits.as_imm8_reg() {
                 collector.reg_fixed_use(reg, regs::rcx());
             }
         }
         Inst::CmpRmiR { src1, src2, .. } => {
-            collector.reg_use(src1.to_reg());
+            collector.reg_use(src1);
             src2.get_operands(collector);
         }
         Inst::Setcc { dst, .. } => {
-            collector.reg_def(dst.to_writable_reg());
+            collector.reg_def(*dst);
         }
         Inst::Bswap { src, dst, .. } => {
-            collector.reg_use(src.to_reg());
-            collector.reg_reuse_def(dst.to_writable_reg(), 0);
+            collector.reg_use(src);
+            collector.reg_reuse_def(*dst, 0);
         }
         Inst::Cmove {
             consequent,
@@ -2301,8 +2300,8 @@ fn x64_get_operands<F: Fn(VReg) -> VReg>(inst: &Inst, collector: &mut OperandCol
             dst,
             ..
         } => {
-            collector.reg_use(alternative.to_reg());
-            collector.reg_reuse_def(dst.to_writable_reg(), 0);
+            collector.reg_use(alternative);
+            collector.reg_reuse_def(*dst, 0);
             consequent.get_operands(collector);
         }
         Inst::XmmCmove {
@@ -2311,15 +2310,15 @@ fn x64_get_operands<F: Fn(VReg) -> VReg>(inst: &Inst, collector: &mut OperandCol
             dst,
             ..
         } => {
-            collector.reg_use(alternative.to_reg());
-            collector.reg_reuse_def(dst.to_writable_reg(), 0);
-            collector.reg_use(consequent.to_reg());
+            collector.reg_use(alternative);
+            collector.reg_reuse_def(*dst, 0);
+            collector.reg_use(consequent);
         }
         Inst::Push64 { src } => {
             src.get_operands(collector);
         }
         Inst::Pop64 { dst } => {
-            collector.reg_def(dst.to_writable_reg());
+            collector.reg_def(*dst);
         }
         Inst::StackProbeLoop { tmp, .. } => {
             collector.reg_early_def(*tmp);
@@ -2363,7 +2362,7 @@ fn x64_get_operands<F: Fn(VReg) -> VReg>(inst: &Inst, collector: &mut OperandCol
 
         Inst::ReturnCallKnown { callee, info } => {
             let ReturnCallInfo { uses, tmp, .. } = &**info;
-            collector.reg_fixed_def(tmp.to_writable_reg(), regs::r11());
+            collector.reg_fixed_def(*tmp, regs::r11());
             // Same as in the `Inst::CallKnown` branch.
             debug_assert_ne!(*callee, ExternalName::LibCall(LibCall::Probestack));
             for u in uses {
@@ -2381,24 +2380,21 @@ fn x64_get_operands<F: Fn(VReg) -> VReg>(inst: &Inst, collector: &mut OperandCol
             // safe to use.
             collector.reg_fixed_use(*callee, regs::r10());
 
-            collector.reg_fixed_def(tmp.to_writable_reg(), regs::r11());
+            collector.reg_fixed_def(*tmp, regs::r11());
             for u in uses {
                 collector.reg_fixed_use(u.vreg, u.preg);
             }
         }
 
-        Inst::JmpTableSeq {
-            ref idx,
-            ref tmp1,
-            ref tmp2,
-            ..
+        &Inst::JmpTableSeq {
+            idx, tmp1, tmp2, ..
         } => {
-            collector.reg_use(*idx);
-            collector.reg_early_def(*tmp1);
+            collector.reg_use(idx);
+            collector.reg_early_def(tmp1);
             // In the sequence emitted for this pseudoinstruction in emit.rs,
             // tmp2 is only written after idx is read, so it doesn't need to be
             // an early def.
-            collector.reg_def(*tmp2);
+            collector.reg_def(tmp2);
         }
 
         Inst::JmpUnknown { target } => {
@@ -2466,8 +2462,8 @@ fn x64_get_operands<F: Fn(VReg) -> VReg>(inst: &Inst, collector: &mut OperandCol
             // No registers are used.
         }
 
-        Inst::ElfTlsGetAddr { dst, .. } | Inst::MachOTlsGetAddr { dst, .. } => {
-            collector.reg_fixed_def(dst.to_writable_reg(), regs::rax());
+        &Inst::ElfTlsGetAddr { dst, .. } | &Inst::MachOTlsGetAddr { dst, .. } => {
+            collector.reg_fixed_def(dst, regs::rax());
             // All caller-saves are clobbered.
             //
             // We use the SysV calling convention here because the
@@ -2479,15 +2475,15 @@ fn x64_get_operands<F: Fn(VReg) -> VReg>(inst: &Inst, collector: &mut OperandCol
             collector.reg_clobbers(clobbers);
         }
 
-        Inst::CoffTlsGetAddr { dst, tmp, .. } => {
+        &Inst::CoffTlsGetAddr { dst, tmp, .. } => {
             // We also use the gs register. But that register is not allocatable by the
             // register allocator, so we don't need to mark it as used here.
 
             // We use %rax to set the address
-            collector.reg_fixed_def(dst.to_writable_reg(), regs::rax());
+            collector.reg_fixed_def(dst, regs::rax());
 
             // We use %rcx as a temporary variable to load the _tls_index
-            collector.reg_fixed_def(tmp.to_writable_reg(), regs::rcx());
+            collector.reg_fixed_def(tmp, regs::rcx());
         }
 
         Inst::Unwind { .. } => {}
diff --git a/cranelift/codegen/src/machinst/abi.rs b/cranelift/codegen/src/machinst/abi.rs
index 3e0f3a52a..a413ae98e 100644
--- a/cranelift/codegen/src/machinst/abi.rs
+++ b/cranelift/codegen/src/machinst/abi.rs
@@ -1094,10 +1094,6 @@ pub struct Callee<M: ABIMachineSpec> {
     /// Register-argument defs, to be provided to the `args`
     /// pseudo-inst, and pregs to constrain them to.
     reg_args: Vec<ArgPair>,
-    /// Clobbered registers, from regalloc.
-    clobbered: Vec<Writable<RealReg>>,
-    /// Total number of spillslots, including for 'dynamic' types, from regalloc.
-    spillslots: Option<usize>,
     /// Finalized frame layout for this function.
     frame_layout: Option<FrameLayout>,
     /// The register holding the return-area pointer, if needed.
@@ -1243,8 +1239,6 @@ impl<M: ABIMachineSpec> Callee<M> {
             outgoing_args_size: 0,
             tail_args_size,
             reg_args: vec![],
-            clobbered: vec![],
-            spillslots: None,
             frame_layout: None,
             ret_area_ptr: None,
             arg_temp_reg: vec![],
@@ -1777,16 +1771,6 @@ impl<M: ABIMachineSpec> Callee<M> {
 /// These methods of `Callee` may only be called after
 /// regalloc.
 impl<M: ABIMachineSpec> Callee<M> {
-    /// Update with the number of spillslots, post-regalloc.
-    pub fn set_num_spillslots(&mut self, slots: usize) {
-        self.spillslots = Some(slots);
-    }
-
-    /// Update with the clobbered registers, post-regalloc.
-    pub fn set_clobbered(&mut self, clobbered: Vec<Writable<RealReg>>) {
-        self.clobbered = clobbered;
-    }
-
     /// Generate a stack map, given a list of spillslots and the emission state
     /// at a given program point (prior to emission of the safepointing
     /// instruction).
@@ -1823,16 +1807,21 @@ impl<M: ABIMachineSpec> Callee<M> {
     /// Compute the final frame layout, post-regalloc.
     ///
     /// This must be called before gen_prologue or gen_epilogue.
-    pub fn compute_frame_layout(&mut self, sigs: &SigSet) {
+    pub fn compute_frame_layout(
+        &mut self,
+        sigs: &SigSet,
+        spillslots: usize,
+        clobbered: Vec<Writable<RealReg>>,
+    ) {
         let bytes = M::word_bytes();
-        let total_stacksize = self.stackslots_size + bytes * self.spillslots.unwrap() as u32;
+        let total_stacksize = self.stackslots_size + bytes * spillslots as u32;
         let mask = M::stack_align(self.call_conv) - 1;
         let total_stacksize = (total_stacksize + mask) & !mask; // 16-align the stack.
         self.frame_layout = Some(M::compute_frame_layout(
             self.call_conv,
             &self.flags,
             self.signature(),
-            &self.clobbered,
+            &clobbered,
             self.is_leaf,
             self.stack_args_size(sigs),
             self.tail_args_size,
diff --git a/cranelift/codegen/src/machinst/reg.rs b/cranelift/codegen/src/machinst/reg.rs
index 1407f80a4..4421a38f4 100644
--- a/cranelift/codegen/src/machinst/reg.rs
+++ b/cranelift/codegen/src/machinst/reg.rs
@@ -352,46 +352,47 @@ impl<'a, F: Fn(VReg) -> VReg> OperandCollector<'a, F> {
 
     /// Add a register use, at the start of the instruction (`Before`
     /// position).
-    pub fn reg_use(&mut self, reg: Reg) {
-        self.reg_maybe_fixed(reg, OperandKind::Use, OperandPos::Early);
+    pub fn reg_use(&mut self, reg: impl Into<Reg>) {
+        self.reg_maybe_fixed(reg.into(), OperandKind::Use, OperandPos::Early);
     }
 
     /// Add a register use, at the end of the instruction (`After` position).
-    pub fn reg_late_use(&mut self, reg: Reg) {
-        self.reg_maybe_fixed(reg, OperandKind::Use, OperandPos::Late);
+    pub fn reg_late_use(&mut self, reg: impl Into<Reg>) {
+        self.reg_maybe_fixed(reg.into(), OperandKind::Use, OperandPos::Late);
     }
 
     /// Add a register def, at the end of the instruction (`After`
     /// position). Use only when this def will be written after all
     /// uses are read.
-    pub fn reg_def(&mut self, reg: Writable<Reg>) {
-        self.reg_maybe_fixed(reg.to_reg(), OperandKind::Def, OperandPos::Late);
+    pub fn reg_def(&mut self, reg: Writable<impl Into<Reg>>) {
+        self.reg_maybe_fixed(reg.to_reg().into(), OperandKind::Def, OperandPos::Late);
     }
 
     /// Add a register "early def", which logically occurs at the
     /// beginning of the instruction, alongside all uses. Use this
     /// when the def may be written before all uses are read; the
     /// regalloc will ensure that it does not overwrite any uses.
-    pub fn reg_early_def(&mut self, reg: Writable<Reg>) {
-        self.reg_maybe_fixed(reg.to_reg(), OperandKind::Def, OperandPos::Early);
+    pub fn reg_early_def(&mut self, reg: Writable<impl Into<Reg>>) {
+        self.reg_maybe_fixed(reg.to_reg().into(), OperandKind::Def, OperandPos::Early);
     }
 
     /// Add a register "fixed use", which ties a vreg to a particular
     /// RealReg at the end of the instruction.
-    pub fn reg_fixed_late_use(&mut self, reg: Reg, rreg: Reg) {
-        self.reg_fixed(reg, rreg, OperandKind::Use, OperandPos::Late);
+    pub fn reg_fixed_late_use(&mut self, reg: impl Into<Reg>, rreg: Reg) {
+        self.reg_fixed(reg.into(), rreg, OperandKind::Use, OperandPos::Late);
     }
 
     /// Add a register "fixed use", which ties a vreg to a particular
     /// RealReg at this point.
-    pub fn reg_fixed_use(&mut self, reg: Reg, rreg: Reg) {
-        self.reg_fixed(reg, rreg, OperandKind::Use, OperandPos::Early);
+    pub fn reg_fixed_use(&mut self, reg: impl Into<Reg>, rreg: Reg) {
+        self.reg_fixed(reg.into(), rreg, OperandKind::Use, OperandPos::Early);
     }
 
     /// Add a register "fixed def", which ties a vreg to a particular
     /// RealReg at this point.
-    pub fn reg_fixed_def(&mut self, reg: Writable<Reg>, rreg: Reg) {
-        self.reg_fixed(reg.to_reg(), rreg, OperandKind::Def, OperandPos::Late);
+    pub fn reg_fixed_def(&mut self, reg: Writable<impl Into<Reg>>, rreg: Reg) {
+        let reg = reg.to_reg().into();
+        self.reg_fixed(reg, rreg, OperandKind::Def, OperandPos::Late);
     }
 
     /// Add an operand tying a virtual register to a physical register.
@@ -419,20 +420,21 @@ impl<'a, F: Fn(VReg) -> VReg> OperandCollector<'a, F> {
     /// Add a register def that reuses an earlier use-operand's
     /// allocation. The index of that earlier operand (relative to the
     /// current instruction's start of operands) must be known.
-    pub fn reg_reuse_def(&mut self, reg: Writable<Reg>, idx: usize) {
-        if let Some(rreg) = reg.to_reg().to_real_reg() {
+    pub fn reg_reuse_def(&mut self, reg: Writable<impl Into<Reg>>, idx: usize) {
+        let reg = reg.to_reg().into();
+        if let Some(rreg) = reg.to_real_reg() {
             // In some cases we see real register arguments to a reg_reuse_def
             // constraint. We assume the creator knows what they're doing
             // here, though we do also require that the real register be a
             // fixed-nonallocatable register.
             self.reg_fixed_nonallocatable(rreg.into());
         } else {
-            debug_assert!(reg.to_reg().is_virtual());
+            debug_assert!(reg.is_virtual());
             // The operand we're reusing must not be fixed-nonallocatable, as
             // that would imply that the register has been allocated to a
             // virtual register.
             let constraint = OperandConstraint::Reuse(idx);
-            self.add_operand(reg.to_reg(), constraint, OperandKind::Def, OperandPos::Late);
+            self.add_operand(reg, constraint, OperandKind::Def, OperandPos::Late);
         }
     }
 
diff --git a/cranelift/codegen/src/machinst/vcode.rs b/cranelift/codegen/src/machinst/vcode.rs
index d246a31dd..05f95d6f5 100644
--- a/cranelift/codegen/src/machinst/vcode.rs
+++ b/cranelift/codegen/src/machinst/vcode.rs
@@ -772,9 +772,8 @@ impl<I: VCodeInst> VCode<I> {
         // setting clobbers is fairly minimal so this should be
         // relatively cheap.
         let clobbers = self.compute_clobbers(regalloc);
-        self.abi.set_num_spillslots(regalloc.num_spillslots);
-        self.abi.set_clobbered(clobbers);
-        self.abi.compute_frame_layout(&self.sigs);
+        self.abi
+            .compute_frame_layout(&self.sigs, regalloc.num_spillslots, clobbers);
 
         // Emit blocks.
         let mut cur_srcloc = None;
@@ -1260,26 +1259,8 @@ impl<I: VCodeInst> VCode<I> {
     }
 
     #[inline]
-    fn assert_no_vreg_aliases<'a>(&self, list: &'a [VReg]) -> &'a [VReg] {
-        for vreg in list {
-            self.assert_not_vreg_alias(*vreg);
-        }
-        list
-    }
-
-    #[inline]
-    fn assert_not_vreg_alias(&self, vreg: VReg) -> VReg {
-        debug_assert!(self.resolve_vreg_alias(vreg) == vreg);
-        vreg
-    }
-
-    #[inline]
-    fn assert_operand_not_vreg_alias(&self, op: Operand) -> Operand {
-        // It should be true by construction that `Operand`s do not contain any
-        // aliased vregs since they're all collected and mapped when the VCode
-        // is itself constructed.
-        self.assert_not_vreg_alias(op.vreg());
-        op
+    fn debug_assert_no_vreg_aliases(&self, mut list: impl Iterator<Item = VReg>) {
+        debug_assert!(list.all(|vreg| !self.vreg_aliases.contains_key(&vreg)));
     }
 
     /// Get the type of a VReg.
@@ -1356,7 +1337,8 @@ impl<I: VCodeInst> RegallocFunction for VCode<I> {
         let ret = &self.block_params[start as usize..end as usize];
         // Currently block params are never aliased to another vreg, but
         // double-check just to be sure.
-        self.assert_no_vreg_aliases(ret)
+        self.debug_assert_no_vreg_aliases(ret.iter().copied());
+        ret
     }
 
     fn branch_blockparams(&self, block: BlockIndex, _insn: InsnIndex, succ_idx: usize) -> &[VReg] {
@@ -1366,7 +1348,8 @@ impl<I: VCodeInst> RegallocFunction for VCode<I> {
         let (branch_block_args_start, branch_block_args_end) = succ_ranges[succ_idx];
         let ret = &self.branch_block_args
             [branch_block_args_start as usize..branch_block_args_end as usize];
-        self.assert_no_vreg_aliases(ret)
+        self.debug_assert_no_vreg_aliases(ret.iter().copied());
+        ret
     }
 
     fn is_ret(&self, insn: InsnIndex) -> bool {
@@ -1392,9 +1375,10 @@ impl<I: VCodeInst> RegallocFunction for VCode<I> {
     fn inst_operands(&self, insn: InsnIndex) -> &[Operand] {
         let (start, end) = self.operand_ranges[insn.index()];
         let ret = &self.operands[start as usize..end as usize];
-        for op in ret {
-            self.assert_operand_not_vreg_alias(*op);
-        }
+        // It should be true by construction that `Operand`s do not contain any
+        // aliased vregs since they're all collected and mapped when the VCode
+        // is itself constructed.
+        self.debug_assert_no_vreg_aliases(ret.iter().map(|op| op.vreg()));
         ret
     }
 
@@ -1407,17 +1391,18 @@ impl<I: VCodeInst> RegallocFunction for VCode<I> {
     }
 
     fn reftype_vregs(&self) -> &[VReg] {
-        self.assert_no_vreg_aliases(&self.reftyped_vregs[..])
+        let ret = &self.reftyped_vregs;
+        self.debug_assert_no_vreg_aliases(ret.iter().copied());
+        ret
     }
 
     fn debug_value_labels(&self) -> &[(VReg, InsnIndex, InsnIndex, u32)] {
         // VRegs here are inserted into `debug_value_labels` after code is
-        // generated and aliases are fully defined, so no double-check that
+        // generated and aliases are fully defined, so double-check that
         // aliases are not lingering.
-        for (vreg, ..) in self.debug_value_labels.iter() {
-            self.assert_not_vreg_alias(*vreg);
-        }
-        &self.debug_value_labels[..]
+        let ret = &self.debug_value_labels;
+        self.debug_assert_no_vreg_aliases(ret.iter().map(|&(vreg, ..)| vreg));
+        ret
     }
 
     fn spillslot_size(&self, regclass: RegClass) -> usize {
diff --git a/cranelift/object/src/backend.rs b/cranelift/object/src/backend.rs
index 581f6a87a..af51c5602 100644
--- a/cranelift/object/src/backend.rs
+++ b/cranelift/object/src/backend.rs
@@ -39,10 +39,10 @@ impl ObjectBuilder {
     /// Create a new `ObjectBuilder` using the given Cranelift target, that
     /// can be passed to [`ObjectModule::new`].
     ///
-    /// The `libcall_names` function provides a way to translate `cranelift_codegen`'s [ir::LibCall]
+    /// The `libcall_names` function provides a way to translate `cranelift_codegen`'s [`ir::LibCall`]
     /// enum to symbols. LibCalls are inserted in the IR as part of the legalization for certain
     /// floating point instructions, and for stack probes. If you don't know what to use for this
-    /// argument, use [cranelift_module::default_libcall_names]().
+    /// argument, use [`cranelift_module::default_libcall_names`].
     pub fn new<V: Into<Vec<u8>>>(
         isa: OwnedTargetIsa,
         name: V,
diff --git a/crates/c-api/include/wasmtime/global.h b/crates/c-api/include/wasmtime/global.h
index 5d69c19f5..8b20af915 100644
--- a/crates/c-api/include/wasmtime/global.h
+++ b/crates/c-api/include/wasmtime/global.h
@@ -54,7 +54,7 @@ wasmtime_global_type(const wasmtime_context_t *store,
  * \param out where to store the value in this global.
  *
  * This function returns ownership of the contents of `out`, so
- * #wasmtime_val_delete may need to be called on the value.
+ * #wasmtime_val_unroot may need to be called on the value.
  */
 WASM_API_EXTERN void wasmtime_global_get(wasmtime_context_t *store,
                                          const wasmtime_global_t *global,
diff --git a/crates/c-api/include/wasmtime/val.h b/crates/c-api/include/wasmtime/val.h
index dc2ab3710..bdac5b9c7 100644
--- a/crates/c-api/include/wasmtime/val.h
+++ b/crates/c-api/include/wasmtime/val.h
@@ -20,38 +20,84 @@ extern "C" {
  * \brief Convenience alias for #wasmtime_anyref
  *
  * \struct wasmtime_anyref
- * \brief A host-defined un-forgeable reference to pass into WebAssembly.
+ * \brief A WebAssembly value in the `any` hierarchy of GC types.
  *
- * This structure represents an `anyref` that can be passed to WebAssembly.
- * It cannot be forged by WebAssembly itself and is guaranteed to have been
- * created by the host.
+ * This structure represents an `anyref` that WebAssembly can create and
+ * pass back to the host. The host can also create values to pass to a guest.
+ *
+ * Note that this structure does not itself contain the data that it refers to.
+ * Instead to contains metadata to point back within a #wasmtime_context_t, so
+ * referencing the internal data requires using a `wasmtime_context_t`.
+ *
+ * Anyref values are required to be explicitly unrooted via
+ * #wasmtime_anyref_unroot to enable them to be garbage-collected.
+ *
+ * Null anyref values are represented by this structure and can be tested and
+ * created with the `wasmtime_anyref_is_null` and `wasmtime_anyref_set_null`
+ * functions.
  */
-typedef struct wasmtime_anyref wasmtime_anyref_t;
+typedef struct wasmtime_anyref {
+  /// Internal metadata tracking within the store, embedders should not
+  /// configure or modify these fields.
+  uint64_t store_id;
+  /// Internal to Wasmtime.
+  uint32_t __private1;
+  /// Internal to Wasmtime.
+  uint32_t __private2;
+} wasmtime_anyref_t;
+
+/// \brief Helper function to initialize the `ref` provided to a null anyref
+/// value.
+static inline void wasmtime_anyref_set_null(wasmtime_anyref_t *ref) {
+  ref->store_id = 0;
+}
+
+/// \brief Helper function to return whether the provided `ref` points to a null
+/// `anyref` value.
+///
+/// Note that `ref` itself should not be null as null is represented internally
+/// within a #wasmtime_anyref_t value.
+static inline bool wasmtime_anyref_is_null(const wasmtime_anyref_t *ref) {
+  return ref->store_id == 0;
+}
 
 /**
- * \brief Creates a shallow copy of the `anyref` argument, returning a
- * separately owned pointer (depending on the configured collector this might
- * increase a reference count or create a new GC root).
+ * \brief Creates a new reference pointing to the same data that `anyref`
+ * points to (depending on the configured collector this might increase a
+ * reference count or create a new GC root).
+ *
+ * The returned reference is stored in `out`.
  */
-WASM_API_EXTERN wasmtime_anyref_t *
-wasmtime_anyref_clone(wasmtime_context_t *context, wasmtime_anyref_t *ref);
+WASM_API_EXTERN void wasmtime_anyref_clone(wasmtime_context_t *context,
+                                           const wasmtime_anyref_t *anyref,
+                                           wasmtime_anyref_t *out);
 
 /**
- * \brief Drops an owned pointer to `ref`, potentially deleting it if it's the
- * last reference, or allowing it to be collected during the next GC.
+ * \brief Unroots the `ref` provided within the `context`.
+ *
+ * This API is required to enable the `ref` value provided to be
+ * garbage-collected. This API itself does not necessarily garbage-collect the
+ * value, but it's possible to collect it in the future after this.
+ *
+ * This may modify `ref` and the contents of `ref` are left in an undefined
+ * state after this API is called and it should no longer be used.
+ *
+ * Note that null or i32 anyref values do not need to be unrooted but are still
+ * valid to pass to this function.
  */
-WASM_API_EXTERN void wasmtime_anyref_delete(wasmtime_context_t *context,
+WASM_API_EXTERN void wasmtime_anyref_unroot(wasmtime_context_t *context,
                                             wasmtime_anyref_t *ref);
 
 /**
  * \brief Converts a raw `anyref` value coming from #wasmtime_val_raw_t into
  * a #wasmtime_anyref_t.
  *
- * Note that the returned #wasmtime_anyref_t is an owned value that must be
- * deleted via #wasmtime_anyref_delete by the caller if it is non-null.
+ * The provided `out` pointer is filled in with a reference converted from
+ * `raw`.
  */
-WASM_API_EXTERN wasmtime_anyref_t *
-wasmtime_anyref_from_raw(wasmtime_context_t *context, uint32_t raw);
+WASM_API_EXTERN void wasmtime_anyref_from_raw(wasmtime_context_t *context,
+                                              uint32_t raw,
+                                              wasmtime_anyref_t *out);
 
 /**
  * \brief Converts a #wasmtime_anyref_t to a raw value suitable for storing
@@ -74,8 +120,9 @@ WASM_API_EXTERN uint32_t wasmtime_anyref_to_raw(wasmtime_context_t *context,
  *
  * If `i31val` does not fit in 31 bits, it is wrapped.
  */
-WASM_API_EXTERN wasmtime_anyref_t *
-wasmtime_anyref_from_i31(wasmtime_context_t *context, uint32_t i31val);
+WASM_API_EXTERN void wasmtime_anyref_from_i31(wasmtime_context_t *context,
+                                              uint32_t i31val,
+                                              wasmtime_anyref_t *out);
 
 /**
  * \brief Get the `anyref`'s underlying `i31ref` value, zero extended, if any.
@@ -87,7 +134,7 @@ wasmtime_anyref_from_i31(wasmtime_context_t *context, uint32_t i31val);
  * returned and `dst` is left unmodified.
  */
 WASM_API_EXTERN bool wasmtime_anyref_i31_get_u(wasmtime_context_t *context,
-                                               wasmtime_anyref_t *anyref,
+                                               const wasmtime_anyref_t *anyref,
                                                uint32_t *dst);
 
 /**
@@ -100,7 +147,7 @@ WASM_API_EXTERN bool wasmtime_anyref_i31_get_u(wasmtime_context_t *context,
  * returned and `dst` is left unmodified.
  */
 WASM_API_EXTERN bool wasmtime_anyref_i31_get_s(wasmtime_context_t *context,
-                                               wasmtime_anyref_t *anyref,
+                                               const wasmtime_anyref_t *anyref,
                                                int32_t *dst);
 
 /**
@@ -113,28 +160,65 @@ WASM_API_EXTERN bool wasmtime_anyref_i31_get_s(wasmtime_context_t *context,
  * This structure represents an `externref` that can be passed to WebAssembly.
  * It cannot be forged by WebAssembly itself and is guaranteed to have been
  * created by the host.
+ *
+ * This structure is similar to #wasmtime_anyref_t but represents the
+ * `externref` type in WebAssembly. This can be created on the host from
+ * arbitrary host pointers/destructors. Note that this value is itself a
+ * reference into a #wasmtime_context_t and must be explicitly unrooted to
+ * enable garbage collection.
+ *
+ * Note that null is represented with this structure and created with
+ * `wasmtime_externref_set_null`. Null can be tested for with the
+ * `wasmtime_externref_is_null` function.
  */
-typedef struct wasmtime_externref wasmtime_externref_t;
+typedef struct wasmtime_externref {
+  /// Internal metadata tracking within the store, embedders should not
+  /// configure or modify these fields.
+  uint64_t store_id;
+  /// Internal to Wasmtime.
+  uint32_t __private1;
+  /// Internal to Wasmtime.
+  uint32_t __private2;
+} wasmtime_externref_t;
+
+/// \brief Helper function to initialize the `ref` provided to a null externref
+/// value.
+static inline void wasmtime_externref_set_null(wasmtime_externref_t *ref) {
+  ref->store_id = 0;
+}
+
+/// \brief Helper function to return whether the provided `ref` points to a null
+/// `externref` value.
+///
+/// Note that `ref` itself should not be null as null is represented internally
+/// within a #wasmtime_externref_t value.
+static inline bool wasmtime_externref_is_null(const wasmtime_externref_t *ref) {
+  return ref->store_id == 0;
+}
 
 /**
  * \brief Create a new `externref` value.
  *
- * Creates a new `externref` value wrapping the provided data, returning the
- * pointer to the externref.
+ * Creates a new `externref` value wrapping the provided data, returning whether
+ * it was created or not.
  *
  * \param context the store context to allocate this externref within
  * \param data the host-specific data to wrap
  * \param finalizer an optional finalizer for `data`
+ * \param out where to store the created value.
  *
  * When the reference is reclaimed, the wrapped data is cleaned up with the
  * provided `finalizer`.
  *
- * The returned value must be deleted with #wasmtime_externref_delete and may
- * not be used after the context is destroyed.
+ * If `true` is returned then `out` has been filled in and must be unrooted
+ * in the future with #wasmtime_externref_unroot. If `false` is returned then
+ * the host wasn't able to create more GC values at this time. Performing a GC
+ * may free up enough space to try again.
  */
-WASM_API_EXTERN wasmtime_externref_t *
-wasmtime_externref_new(wasmtime_context_t *context, void *data,
-                       void (*finalizer)(void *));
+WASM_API_EXTERN bool wasmtime_externref_new(wasmtime_context_t *context,
+                                            void *data,
+                                            void (*finalizer)(void *),
+                                            wasmtime_externref_t *out);
 
 /**
  * \brief Get an `externref`'s wrapped data
@@ -143,33 +227,45 @@ wasmtime_externref_new(wasmtime_context_t *context, void *data,
  * that `data` is not `NULL`.
  */
 WASM_API_EXTERN void *wasmtime_externref_data(wasmtime_context_t *context,
-                                              wasmtime_externref_t *data);
+                                              const wasmtime_externref_t *data);
 
 /**
- * \brief Creates a shallow copy of the `externref` argument, returning a
- * separately owned pointer (depending on the configured collector this might
- * increase a reference count or create a new GC root).
+ * \brief Creates a new reference pointing to the same data that `ref` points
+ * to (depending on the configured collector this might increase a reference
+ * count or create a new GC root).
+ *
+ * The `out` parameter stores the cloned reference. This reference must
+ * eventually be unrooted with #wasmtime_externref_unroot in the future to
+ * enable GC'ing it.
  */
-WASM_API_EXTERN wasmtime_externref_t *
-wasmtime_externref_clone(wasmtime_context_t *context,
-                         wasmtime_externref_t *ref);
+WASM_API_EXTERN void wasmtime_externref_clone(wasmtime_context_t *context,
+                                              const wasmtime_externref_t *ref,
+                                              wasmtime_externref_t *out);
 
 /**
- * \brief Drops an owned pointer to `ref`, potentially deleting it if it's the
- * last reference, or allowing it to be collected during the next GC.
+ * \brief Unroots the pointer `ref` from the `context` provided.
+ *
+ * This function will enable future garbage collection of the value pointed to
+ * by `ref` once there are no more references. The `ref` value may be mutated in
+ * place by this function and its contents are undefined after this function
+ * returns. It should not be used until after re-initializing it.
+ *
+ * Note that null externref values do not need to be unrooted but are still
+ * valid to pass to this function.
  */
-WASM_API_EXTERN void wasmtime_externref_delete(wasmtime_context_t *context,
+WASM_API_EXTERN void wasmtime_externref_unroot(wasmtime_context_t *context,
                                                wasmtime_externref_t *ref);
 
 /**
  * \brief Converts a raw `externref` value coming from #wasmtime_val_raw_t into
  * a #wasmtime_externref_t.
  *
- * Note that the returned #wasmtime_externref_t is an owned value that must be
- * deleted via #wasmtime_externref_delete by the caller if it is non-null.
+ * The `out` reference is filled in with the non-raw version of this externref.
+ * It must eventually be unrooted with #wasmtime_externref_unroot.
  */
-WASM_API_EXTERN wasmtime_externref_t *
-wasmtime_externref_from_raw(wasmtime_context_t *context, uint32_t raw);
+WASM_API_EXTERN void wasmtime_externref_from_raw(wasmtime_context_t *context,
+                                                 uint32_t raw,
+                                                 wasmtime_externref_t *out);
 
 /**
  * \brief Converts a #wasmtime_externref_t to a raw value suitable for storing
@@ -230,15 +326,9 @@ typedef union wasmtime_valunion {
   /// Field used if #wasmtime_val_t::kind is #WASMTIME_F64
   float64_t f64;
   /// Field used if #wasmtime_val_t::kind is #WASMTIME_ANYREF
-  ///
-  /// If this value represents a `ref.null any` value then this pointer will
-  /// be `NULL`.
-  wasmtime_anyref_t *anyref;
+  wasmtime_anyref_t anyref;
   /// Field used if #wasmtime_val_t::kind is #WASMTIME_EXTERNREF
-  ///
-  /// If this value represents a `ref.null extern` value then this pointer will
-  /// be `NULL`.
-  wasmtime_externref_t *externref;
+  wasmtime_externref_t externref;
   /// Field used if #wasmtime_val_t::kind is #WASMTIME_FUNCREF
   ///
   /// Use `wasmtime_funcref_is_null` to test whether this is a null function
@@ -345,8 +435,8 @@ static inline void __wasmtime_val_assertions() {
  * Note that this structure may contain an owned value, namely rooted GC
  * references, depending on the context in which this is used. APIs which
  * consume a #wasmtime_val_t do not take ownership, but APIs that return
- * #wasmtime_val_t require that #wasmtime_val_delete is called to deallocate the
- * value.
+ * #wasmtime_val_t require that #wasmtime_val_unroot is called to clean up
+ * any possible GC roots in the value.
  */
 typedef struct wasmtime_val {
   /// Discriminant of which field of #of is valid.
@@ -356,20 +446,29 @@ typedef struct wasmtime_val {
 } wasmtime_val_t;
 
 /**
- * \brief Deletes an owned #wasmtime_val_t.
+ * \brief Unroot the value contained by `val`.
+ *
+ * This function will unroot any GC references that `val` points to, for
+ * example if it has the `WASMTIME_EXTERNREF` or `WASMTIME_ANYREF` kinds. This
+ * function leaves `val` in an undefined state and it should not be used again
+ * without re-initializing.
  *
- * Note that this only deletes the contents, not the memory that `val` points to
- * itself (which is owned by the caller).
+ * This method does not need to be called for integers, floats, v128, or
+ * funcref values.
  */
-WASM_API_EXTERN void wasmtime_val_delete(wasmtime_context_t *context,
+WASM_API_EXTERN void wasmtime_val_unroot(wasmtime_context_t *context,
                                          wasmtime_val_t *val);
 
 /**
- * \brief Copies `src` into `dst`.
+ * \brief Clones the value pointed to by `src` into the `dst` provided.
+ *
+ * This function will clone any rooted GC values in `src` and have them
+ * newly rooted inside of `dst`. When using this API the `dst` should be
+ * later unrooted with #wasmtime_val_unroot if it contains GC values.
  */
-WASM_API_EXTERN void wasmtime_val_copy(wasmtime_context_t *context,
-                                       wasmtime_val_t *dst,
-                                       const wasmtime_val_t *src);
+WASM_API_EXTERN void wasmtime_val_clone(wasmtime_context_t *context,
+                                        const wasmtime_val_t *src,
+                                        wasmtime_val_t *dst);
 
 #ifdef __cplusplus
 } // extern "C"
diff --git a/crates/c-api/src/ref.rs b/crates/c-api/src/ref.rs
index 1af4a194b..44f14c3f9 100644
--- a/crates/c-api/src/ref.rs
+++ b/crates/c-api/src/ref.rs
@@ -1,5 +1,5 @@
 use crate::{abort, WasmtimeStoreContextMut};
-use std::{mem::MaybeUninit, os::raw::c_void, ptr};
+use std::{mem::MaybeUninit, num::NonZeroU64, os::raw::c_void, ptr};
 use wasmtime::{AnyRef, ExternRef, ManuallyRooted, Ref, RootScope, Val, I31};
 
 /// `*mut wasm_ref_t` is a reference type (`externref` or `funcref`), as seen by
@@ -182,25 +182,70 @@ pub extern "C" fn wasm_foreign_new(_store: &crate::wasm_store_t) -> Box<wasm_for
     abort("wasm_foreign_new")
 }
 
-pub type wasmtime_anyref_t = ManuallyRooted<AnyRef>;
-pub type wasmtime_externref_t = ManuallyRooted<ExternRef>;
+/// C-API representation of `anyref`.
+///
+/// This represented differently in the C API from the header to handle how
+/// this is dispatched internally. Null anyref values are represented with a
+/// `store_id` of zero, and otherwise the `rooted` field is valid.
+///
+/// Note that this relies on the Wasmtime definition of `ManuallyRooted` to have
+/// a 64-bit store_id first.
+macro_rules! ref_wrapper {
+    ($wasmtime:ident => $c:ident) => {
+        pub struct $c {
+            store_id: u64,
+            a: u32,
+            b: u32,
+        }
+
+        impl $c {
+            pub unsafe fn as_wasmtime(&self) -> Option<ManuallyRooted<$wasmtime>> {
+                let store_id = NonZeroU64::new(self.store_id)?;
+                Some(ManuallyRooted::from_raw_parts_for_c_api(
+                    store_id, self.a, self.b,
+                ))
+            }
+        }
+
+        impl From<Option<ManuallyRooted<$wasmtime>>> for $c {
+            fn from(rooted: Option<ManuallyRooted<$wasmtime>>) -> $c {
+                let mut ret = $c {
+                    store_id: 0,
+                    a: 0,
+                    b: 0,
+                };
+                if let Some(rooted) = rooted {
+                    let (store_id, a, b) = rooted.into_parts_for_c_api();
+                    ret.store_id = store_id.get();
+                    ret.a = a;
+                    ret.b = b;
+                }
+                ret
+            }
+        }
+    };
+}
+
+ref_wrapper!(AnyRef => wasmtime_anyref_t);
+ref_wrapper!(ExternRef => wasmtime_externref_t);
 
 #[no_mangle]
-pub extern "C" fn wasmtime_anyref_clone(
+pub unsafe extern "C" fn wasmtime_anyref_clone(
     cx: WasmtimeStoreContextMut<'_>,
     anyref: Option<&wasmtime_anyref_t>,
-) -> Option<Box<wasmtime_anyref_t>> {
-    let anyref = anyref?;
-    Some(Box::new((*anyref).clone(cx)))
+    out: &mut MaybeUninit<wasmtime_anyref_t>,
+) {
+    let anyref = anyref.and_then(|a| a.as_wasmtime()).map(|a| a.clone(cx));
+    crate::initialize(out, anyref.into());
 }
 
 #[no_mangle]
-pub extern "C" fn wasmtime_anyref_delete(
+pub unsafe extern "C" fn wasmtime_anyref_unroot(
     cx: WasmtimeStoreContextMut<'_>,
-    val: Option<Box<wasmtime_anyref_t>>,
+    val: Option<&mut MaybeUninit<wasmtime_anyref_t>>,
 ) {
-    if let Some(e) = val {
-        e.unroot(cx);
+    if let Some(val) = val.and_then(|v| v.assume_init_read().as_wasmtime()) {
+        val.unroot(cx);
     }
 }
 
@@ -209,39 +254,42 @@ pub unsafe extern "C" fn wasmtime_anyref_to_raw(
     cx: WasmtimeStoreContextMut<'_>,
     val: Option<&wasmtime_anyref_t>,
 ) -> u32 {
-    val.and_then(|e| e.to_raw(cx).ok()).unwrap_or_default()
+    val.and_then(|v| v.as_wasmtime())
+        .and_then(|e| e.to_raw(cx).ok())
+        .unwrap_or_default()
 }
 
 #[no_mangle]
 pub unsafe extern "C" fn wasmtime_anyref_from_raw(
     cx: WasmtimeStoreContextMut<'_>,
     raw: u32,
-) -> Option<Box<wasmtime_anyref_t>> {
+    val: &mut MaybeUninit<wasmtime_anyref_t>,
+) {
     let mut scope = RootScope::new(cx);
-    let e = AnyRef::from_raw(&mut scope, raw)?;
-    Some(Box::new(
-        e.to_manually_rooted(&mut scope).expect("in scope"),
-    ))
+    let anyref = AnyRef::from_raw(&mut scope, raw)
+        .map(|a| a.to_manually_rooted(&mut scope).expect("in scope"));
+    crate::initialize(val, anyref.into());
 }
 
 #[no_mangle]
 pub extern "C" fn wasmtime_anyref_from_i31(
     cx: WasmtimeStoreContextMut<'_>,
     val: u32,
-) -> Box<wasmtime_anyref_t> {
+    out: &mut MaybeUninit<wasmtime_anyref_t>,
+) {
     let mut scope = RootScope::new(cx);
     let anyref = AnyRef::from_i31(&mut scope, I31::wrapping_u32(val));
     let anyref = anyref.to_manually_rooted(&mut scope).expect("in scope");
-    Box::new(anyref)
+    crate::initialize(out, Some(anyref).into())
 }
 
 #[no_mangle]
-pub extern "C" fn wasmtime_anyref_i31_get_u(
+pub unsafe extern "C" fn wasmtime_anyref_i31_get_u(
     cx: WasmtimeStoreContextMut<'_>,
     anyref: Option<&wasmtime_anyref_t>,
     dst: &mut MaybeUninit<u32>,
 ) -> bool {
-    match anyref {
+    match anyref.and_then(|a| a.as_wasmtime()) {
         Some(anyref) if anyref.is_i31(&cx).expect("ManuallyRooted always in scope") => {
             let val = anyref
                 .unwrap_i31(&cx)
@@ -255,12 +303,12 @@ pub extern "C" fn wasmtime_anyref_i31_get_u(
 }
 
 #[no_mangle]
-pub extern "C" fn wasmtime_anyref_i31_get_s(
+pub unsafe extern "C" fn wasmtime_anyref_i31_get_s(
     cx: WasmtimeStoreContextMut<'_>,
     anyref: Option<&wasmtime_anyref_t>,
     dst: &mut MaybeUninit<i32>,
 ) -> bool {
-    match anyref {
+    match anyref.and_then(|a| a.as_wasmtime()) {
         Some(anyref) if anyref.is_i31(&cx).expect("ManuallyRooted always in scope") => {
             let val = anyref
                 .unwrap_i31(&cx)
@@ -278,19 +326,25 @@ pub extern "C" fn wasmtime_externref_new(
     cx: WasmtimeStoreContextMut<'_>,
     data: *mut c_void,
     finalizer: Option<extern "C" fn(*mut c_void)>,
-) -> Option<Box<wasmtime_externref_t>> {
+    out: &mut MaybeUninit<wasmtime_externref_t>,
+) -> bool {
     let mut scope = RootScope::new(cx);
-    let e = ExternRef::new(&mut scope, crate::ForeignData { data, finalizer }).ok()?;
+    let e = match ExternRef::new(&mut scope, crate::ForeignData { data, finalizer }) {
+        Ok(e) => e,
+        Err(_) => return false,
+    };
     let e = e.to_manually_rooted(&mut scope).expect("in scope");
-    Some(Box::new(e))
+    crate::initialize(out, Some(e).into());
+    true
 }
 
 #[no_mangle]
-pub extern "C" fn wasmtime_externref_data(
+pub unsafe extern "C" fn wasmtime_externref_data(
     cx: WasmtimeStoreContextMut<'_>,
     externref: Option<&wasmtime_externref_t>,
 ) -> *mut c_void {
     externref
+        .and_then(|e| e.as_wasmtime())
         .and_then(|e| {
             let data = e.data(cx).ok()?;
             Some(data.downcast_ref::<crate::ForeignData>().unwrap().data)
@@ -299,21 +353,22 @@ pub extern "C" fn wasmtime_externref_data(
 }
 
 #[no_mangle]
-pub extern "C" fn wasmtime_externref_clone(
+pub unsafe extern "C" fn wasmtime_externref_clone(
     cx: WasmtimeStoreContextMut<'_>,
     externref: Option<&wasmtime_externref_t>,
-) -> Option<Box<wasmtime_externref_t>> {
-    let externref = externref?;
-    Some(Box::new((*externref).clone(cx)))
+    out: &mut MaybeUninit<wasmtime_externref_t>,
+) {
+    let externref = externref.and_then(|e| e.as_wasmtime()).map(|e| e.clone(cx));
+    crate::initialize(out, externref.into());
 }
 
 #[no_mangle]
-pub extern "C" fn wasmtime_externref_delete(
+pub unsafe extern "C" fn wasmtime_externref_unroot(
     cx: WasmtimeStoreContextMut<'_>,
-    val: Option<Box<wasmtime_externref_t>>,
+    val: Option<&mut MaybeUninit<wasmtime_externref_t>>,
 ) {
-    if let Some(e) = val {
-        e.unroot(cx);
+    if let Some(val) = val.and_then(|v| v.assume_init_read().as_wasmtime()) {
+        val.unroot(cx);
     }
 }
 
@@ -322,17 +377,19 @@ pub unsafe extern "C" fn wasmtime_externref_to_raw(
     cx: WasmtimeStoreContextMut<'_>,
     val: Option<&wasmtime_externref_t>,
 ) -> u32 {
-    val.and_then(|e| e.to_raw(cx).ok()).unwrap_or_default()
+    val.and_then(|e| e.as_wasmtime())
+        .and_then(|e| e.to_raw(cx).ok())
+        .unwrap_or_default()
 }
 
 #[no_mangle]
 pub unsafe extern "C" fn wasmtime_externref_from_raw(
     cx: WasmtimeStoreContextMut<'_>,
     raw: u32,
-) -> Option<Box<wasmtime_externref_t>> {
+    val: &mut MaybeUninit<wasmtime_externref_t>,
+) {
     let mut scope = RootScope::new(cx);
-    let e = ExternRef::from_raw(&mut scope, raw)?;
-    Some(Box::new(
-        e.to_manually_rooted(&mut scope).expect("in scope"),
-    ))
+    let rooted = ExternRef::from_raw(&mut scope, raw)
+        .map(|e| e.to_manually_rooted(&mut scope).expect("in scope"));
+    crate::initialize(val, rooted.into());
 }
diff --git a/crates/c-api/src/table.rs b/crates/c-api/src/table.rs
index a3b8f1e36..404121c94 100644
--- a/crates/c-api/src/table.rs
+++ b/crates/c-api/src/table.rs
@@ -34,7 +34,7 @@ impl wasm_table_t {
 
 fn option_wasm_ref_t_to_ref(r: Option<&wasm_ref_t>, table_ty: &TableType) -> Ref {
     match (r.map(|r| r.r.clone()), table_ty.element().heap_type()) {
-        (None, HeapType::NoFunc | HeapType::Func | HeapType::Concrete(_)) => Ref::Func(None),
+        (None, HeapType::NoFunc | HeapType::Func | HeapType::ConcreteFunc(_)) => Ref::Func(None),
         (None, HeapType::Extern) => Ref::Extern(None),
         (None, HeapType::Any | HeapType::I31 | HeapType::None) => Ref::Any(None),
         (Some(r), _) => r,
diff --git a/crates/c-api/src/val.rs b/crates/c-api/src/val.rs
index 9917457ec..070462d63 100644
--- a/crates/c-api/src/val.rs
+++ b/crates/c-api/src/val.rs
@@ -144,8 +144,8 @@ pub union wasmtime_val_union {
     pub i64: i64,
     pub f32: u32,
     pub f64: u64,
-    pub anyref: *mut wasmtime_anyref_t,
-    pub externref: *mut wasmtime_externref_t,
+    pub anyref: ManuallyDrop<wasmtime_anyref_t>,
+    pub externref: ManuallyDrop<wasmtime_externref_t>,
     pub funcref: wasmtime_func_t,
     pub v128: [u8; 16],
 }
@@ -234,17 +234,15 @@ impl wasmtime_val_t {
             Val::AnyRef(a) => wasmtime_val_t {
                 kind: crate::WASMTIME_ANYREF,
                 of: wasmtime_val_union {
-                    anyref: a
-                        .and_then(|a| Some(Box::into_raw(Box::new(a.to_manually_rooted(cx).ok()?))))
-                        .unwrap_or(ptr::null_mut()),
+                    anyref: ManuallyDrop::new(a.and_then(|a| a.to_manually_rooted(cx).ok()).into()),
                 },
             },
             Val::ExternRef(e) => wasmtime_val_t {
                 kind: crate::WASMTIME_EXTERNREF,
                 of: wasmtime_val_union {
-                    externref: e
-                        .and_then(|e| Some(Box::into_raw(Box::new(e.to_manually_rooted(cx).ok()?))))
-                        .unwrap_or(ptr::null_mut()),
+                    externref: ManuallyDrop::new(
+                        e.and_then(|e| e.to_manually_rooted(cx).ok()).into(),
+                    ),
                 },
             },
             Val::FuncRef(func) => wasmtime_val_t {
@@ -284,9 +282,11 @@ impl wasmtime_val_t {
             crate::WASMTIME_F32 => Val::F32(self.of.f32),
             crate::WASMTIME_F64 => Val::F64(self.of.f64),
             crate::WASMTIME_V128 => Val::V128(u128::from_le_bytes(self.of.v128).into()),
-            crate::WASMTIME_ANYREF => Val::AnyRef(self.of.anyref.as_ref().map(|a| a.to_rooted(cx))),
+            crate::WASMTIME_ANYREF => {
+                Val::AnyRef(self.of.anyref.as_wasmtime().map(|a| a.to_rooted(cx)))
+            }
             crate::WASMTIME_EXTERNREF => {
-                Val::ExternRef(self.of.externref.as_ref().map(|e| e.to_rooted(cx)))
+                Val::ExternRef(self.of.externref.as_wasmtime().map(|e| e.to_rooted(cx)))
             }
             crate::WASMTIME_FUNCREF => Val::FuncRef(self.of.funcref.as_wasmtime()),
             other => panic!("unknown wasmtime_valkind_t: {}", other),
@@ -294,45 +294,32 @@ impl wasmtime_val_t {
     }
 }
 
-impl Drop for wasmtime_val_t {
-    fn drop(&mut self) {
-        unsafe {
-            match self.kind {
-                crate::WASMTIME_ANYREF if !self.of.anyref.is_null() => {
-                    let _ = Box::from_raw(self.of.anyref);
-                }
-                crate::WASMTIME_EXTERNREF if !self.of.externref.is_null() => {
-                    let _ = Box::from_raw(self.of.externref);
-                }
-                _ => {}
-            }
-        }
-    }
-}
-
 #[no_mangle]
-pub unsafe extern "C" fn wasmtime_val_delete(
+pub unsafe extern "C" fn wasmtime_val_unroot(
     cx: WasmtimeStoreContextMut<'_>,
-    val: &mut ManuallyDrop<wasmtime_val_t>,
+    val: &mut MaybeUninit<wasmtime_val_t>,
 ) {
+    let val = val.assume_init_read();
     match val.kind {
-        crate::WASMTIME_ANYREF if !val.of.anyref.is_null() => {
-            let _ = ptr::read(val.of.anyref).unroot(cx);
+        crate::WASMTIME_ANYREF => {
+            if let Some(val) = ManuallyDrop::into_inner(val.of.anyref).as_wasmtime() {
+                val.unroot(cx);
+            }
         }
-        crate::WASMTIME_EXTERNREF if !val.of.externref.is_null() => {
-            let _ = ptr::read(val.of.externref).unroot(cx);
+        crate::WASMTIME_EXTERNREF => {
+            if let Some(val) = ManuallyDrop::into_inner(val.of.externref).as_wasmtime() {
+                val.unroot(cx);
+            }
         }
         _ => {}
     }
-
-    ManuallyDrop::drop(val)
 }
 
 #[no_mangle]
-pub unsafe extern "C" fn wasmtime_val_copy(
+pub unsafe extern "C" fn wasmtime_val_clone(
     cx: WasmtimeStoreContextMut<'_>,
-    dst: &mut MaybeUninit<wasmtime_val_t>,
     src: &wasmtime_val_t,
+    dst: &mut MaybeUninit<wasmtime_val_t>,
 ) {
     let mut scope = RootScope::new(cx);
     let val = src.to_val(&mut scope);
diff --git a/crates/cranelift/src/compiler.rs b/crates/cranelift/src/compiler.rs
index 97f7b2410..a6c26c7f5 100644
--- a/crates/cranelift/src/compiler.rs
+++ b/crates/cranelift/src/compiler.rs
@@ -133,7 +133,7 @@ impl wasmtime_environ::Compiler for Compiler {
         let module = &translation.module;
         let func_index = module.func_index(func_index);
         let sig = translation.module.functions[func_index].signature;
-        let wasm_func_ty = &types[sig];
+        let wasm_func_ty = types[sig].unwrap_func();
 
         let mut compiler = self.function_compiler();
 
@@ -241,7 +241,7 @@ impl wasmtime_environ::Compiler for Compiler {
     ) -> Result<Box<dyn Any + Send>, CompileError> {
         let func_index = translation.module.func_index(def_func_index);
         let sig = translation.module.functions[func_index].signature;
-        let wasm_func_ty = &types[sig];
+        let wasm_func_ty = types[sig].unwrap_func();
 
         let isa = &*self.isa;
         let pointer_type = isa.pointer_type();
@@ -309,7 +309,7 @@ impl wasmtime_environ::Compiler for Compiler {
     ) -> Result<Box<dyn Any + Send>, CompileError> {
         let func_index = translation.module.func_index(def_func_index);
         let sig = translation.module.functions[func_index].signature;
-        let wasm_func_ty = &types[sig];
+        let wasm_func_ty = types[sig].unwrap_func();
 
         let isa = &*self.isa;
         let pointer_type = isa.pointer_type();
diff --git a/crates/cranelift/src/compiler/component.rs b/crates/cranelift/src/compiler/component.rs
index dc1736d14..250f7c304 100644
--- a/crates/cranelift/src/compiler/component.rs
+++ b/crates/cranelift/src/compiler/component.rs
@@ -43,7 +43,7 @@ impl<'a> TrampolineCompiler<'a> {
     ) -> TrampolineCompiler<'a> {
         let isa = &*compiler.isa;
         let signature = component.trampolines[index];
-        let ty = &types[signature];
+        let ty = types[signature].unwrap_func();
         let func = ir::Function::with_name_signature(
             ir::UserFuncName::user(0, 0),
             match abi {
@@ -127,7 +127,7 @@ impl<'a> TrampolineCompiler<'a> {
         let pointer_type = self.isa.pointer_type();
         let args = self.builder.func.dfg.block_params(self.block0).to_vec();
         let vmctx = args[0];
-        let wasm_func_ty = &self.types[self.signature];
+        let wasm_func_ty = self.types[self.signature].unwrap_func();
 
         // More handling is necessary here if this changes
         assert!(matches!(
@@ -291,7 +291,10 @@ impl<'a> TrampolineCompiler<'a> {
         host_args.push(args[2]);
 
         // Currently this only support resources represented by `i32`
-        assert_eq!(self.types[self.signature].params()[0], WasmValType::I32);
+        assert_eq!(
+            self.types[self.signature].unwrap_func().params()[0],
+            WasmValType::I32
+        );
         let (host_sig, offset) = host::resource_new32(self.isa, &mut self.builder.func);
 
         let host_fn = self.load_libcall(vmctx, offset);
@@ -322,7 +325,10 @@ impl<'a> TrampolineCompiler<'a> {
         host_args.push(args[2]);
 
         // Currently this only support resources represented by `i32`
-        assert_eq!(self.types[self.signature].returns()[0], WasmValType::I32);
+        assert_eq!(
+            self.types[self.signature].unwrap_func().returns()[0],
+            WasmValType::I32
+        );
         let (host_sig, offset) = host::resource_rep32(self.isa, &mut self.builder.func);
 
         let host_fn = self.load_libcall(vmctx, offset);
@@ -493,7 +499,7 @@ impl<'a> TrampolineCompiler<'a> {
 
             let sig = crate::wasm_call_signature(
                 self.isa,
-                &self.types[self.signature],
+                &self.types[self.signature].unwrap_func(),
                 &self.compiler.tunables,
             );
             let sig_ref = self.builder.import_signature(sig);
@@ -586,7 +592,7 @@ impl<'a> TrampolineCompiler<'a> {
             // and those are used to load the actual wasm parameters.
             Abi::Array => {
                 let results = self.compiler.load_values_from_array(
-                    self.types[self.signature].params(),
+                    self.types[self.signature].unwrap_func().params(),
                     &mut self.builder,
                     block0_params[2],
                     block0_params[3],
@@ -613,7 +619,7 @@ impl<'a> TrampolineCompiler<'a> {
                 let (ptr, len) = (block0_params[2], block0_params[3]);
                 self.compiler.store_values_to_array(
                     &mut self.builder,
-                    self.types[self.signature].returns(),
+                    self.types[self.signature].unwrap_func().returns(),
                     results,
                     ptr,
                     len,
diff --git a/crates/cranelift/src/func_environ.rs b/crates/cranelift/src/func_environ.rs
index a94d694f8..090af7f10 100644
--- a/crates/cranelift/src/func_environ.rs
+++ b/crates/cranelift/src/func_environ.rs
@@ -1180,7 +1180,7 @@ impl<'a, 'func, 'module_env> Call<'a, 'func, 'module_env> {
             // Functions that have a statically known type are either going to
             // always succeed or always fail. Figure out by inspecting the types
             // further.
-            WasmHeapType::Concrete(EngineOrModuleTypeIndex::Module(table_ty)) => {
+            WasmHeapType::ConcreteFunc(EngineOrModuleTypeIndex::Module(table_ty)) => {
                 // If `ty_index` matches `table_ty`, then this call is
                 // statically known to have the right type, so no checks are
                 // necessary.
@@ -1223,8 +1223,8 @@ impl<'a, 'func, 'module_env> Call<'a, 'func, 'module_env> {
             // Engine-indexed types don't show up until runtime and it's a wasm
             // validation error to perform a call through a non-function table,
             // so these cases are dynamically not reachable.
-            WasmHeapType::Concrete(EngineOrModuleTypeIndex::Engine(_))
-            | WasmHeapType::Concrete(EngineOrModuleTypeIndex::RecGroup(_))
+            WasmHeapType::ConcreteFunc(EngineOrModuleTypeIndex::Engine(_))
+            | WasmHeapType::ConcreteFunc(EngineOrModuleTypeIndex::RecGroup(_))
             | WasmHeapType::Extern
             | WasmHeapType::Any
             | WasmHeapType::I31
@@ -1419,7 +1419,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
         init_value: ir::Value,
     ) -> WasmResult<ir::Value> {
         let grow = match self.module.table_plans[table_index].table.wasm_ty.heap_type {
-            WasmHeapType::Func | WasmHeapType::Concrete(_) | WasmHeapType::NoFunc => {
+            WasmHeapType::Func | WasmHeapType::ConcreteFunc(_) | WasmHeapType::NoFunc => {
                 self.builtin_functions.table_grow_func_ref(&mut pos.func)
             }
 
@@ -1479,7 +1479,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
             }
 
             // Function types.
-            WasmHeapType::Func | WasmHeapType::Concrete(_) | WasmHeapType::NoFunc => {
+            WasmHeapType::Func | WasmHeapType::ConcreteFunc(_) | WasmHeapType::NoFunc => {
                 match plan.style {
                     TableStyle::CallerChecksSignature => {
                         Ok(self.get_or_init_func_ref_table_elem(builder, table_index, index))
@@ -1532,7 +1532,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
             }
 
             // Function types.
-            WasmHeapType::Func | WasmHeapType::Concrete(_) | WasmHeapType::NoFunc => {
+            WasmHeapType::Func | WasmHeapType::ConcreteFunc(_) | WasmHeapType::NoFunc => {
                 match plan.style {
                     TableStyle::CallerChecksSignature => {
                         let (elem_addr, flags) = table_data.prepare_table_addr(
@@ -1566,7 +1566,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
         len: ir::Value,
     ) -> WasmResult<()> {
         let libcall = match self.module.table_plans[table_index].table.wasm_ty.heap_type {
-            WasmHeapType::Func | WasmHeapType::Concrete(_) | WasmHeapType::NoFunc => {
+            WasmHeapType::Func | WasmHeapType::ConcreteFunc(_) | WasmHeapType::NoFunc => {
                 self.builtin_functions.table_fill_func_ref(&mut pos.func)
             }
             ty @ WasmHeapType::Extern
@@ -1625,7 +1625,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
         ht: WasmHeapType,
     ) -> WasmResult<ir::Value> {
         Ok(match ht {
-            WasmHeapType::Func | WasmHeapType::Concrete(_) | WasmHeapType::NoFunc => {
+            WasmHeapType::Func | WasmHeapType::ConcreteFunc(_) | WasmHeapType::NoFunc => {
                 pos.ins().iconst(self.pointer_type(), 0)
             }
             WasmHeapType::Extern | WasmHeapType::Any | WasmHeapType::I31 | WasmHeapType::None => {
@@ -1983,7 +1983,8 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
         index: TypeIndex,
     ) -> WasmResult<ir::SigRef> {
         let index = self.module.types[index];
-        let sig = crate::wasm_call_signature(self.isa, &self.types[index], &self.tunables);
+        let sig =
+            crate::wasm_call_signature(self.isa, self.types[index].unwrap_func(), &self.tunables);
         Ok(func.import_signature(sig))
     }
 
@@ -1993,7 +1994,8 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
         index: FuncIndex,
     ) -> WasmResult<ir::FuncRef> {
         let sig = self.module.functions[index].signature;
-        let sig = crate::wasm_call_signature(self.isa, &self.types[sig], &self.tunables);
+        let sig =
+            crate::wasm_call_signature(self.isa, self.types[sig].unwrap_func(), &self.tunables);
         let signature = func.import_signature(sig);
         let name =
             ir::ExternalName::User(func.declare_imported_user_function(ir::UserExternalName {
diff --git a/crates/cranelift/src/gc/enabled.rs b/crates/cranelift/src/gc/enabled.rs
index 43458814d..602a30e75 100644
--- a/crates/cranelift/src/gc/enabled.rs
+++ b/crates/cranelift/src/gc/enabled.rs
@@ -173,7 +173,7 @@ impl FuncEnvironment<'_> {
 
         let might_be_i31 = match ty.heap_type {
             WasmHeapType::Any => true,
-            WasmHeapType::Extern | WasmHeapType::None | WasmHeapType::Concrete(_) => false,
+            WasmHeapType::Extern | WasmHeapType::None | WasmHeapType::ConcreteFunc(_) => false,
             WasmHeapType::Func | WasmHeapType::NoFunc | WasmHeapType::I31 => {
                 unreachable!("we don't manage instances of these types with the GC")
             }
diff --git a/crates/cranelift/src/lib.rs b/crates/cranelift/src/lib.rs
index 41bcad224..fdf976891 100644
--- a/crates/cranelift/src/lib.rs
+++ b/crates/cranelift/src/lib.rs
@@ -267,7 +267,7 @@ fn wasm_call_signature(
 /// Returns the reference type to use for the provided wasm type.
 fn reference_type(wasm_ht: WasmHeapType, pointer_type: ir::Type) -> ir::Type {
     match wasm_ht {
-        WasmHeapType::Func | WasmHeapType::Concrete(_) | WasmHeapType::NoFunc => pointer_type,
+        WasmHeapType::Func | WasmHeapType::ConcreteFunc(_) | WasmHeapType::NoFunc => pointer_type,
         WasmHeapType::Extern | WasmHeapType::Any | WasmHeapType::I31 | WasmHeapType::None => {
             match pointer_type {
                 ir::types::I32 => ir::types::R32,
diff --git a/crates/environ/src/component/types.rs b/crates/environ/src/component/types.rs
index 93d002baf..2afd9d87c 100644
--- a/crates/environ/src/component/types.rs
+++ b/crates/environ/src/component/types.rs
@@ -480,21 +480,24 @@ impl ComponentTypesBuilder {
         (self.component_types, ty)
     }
 
-    /// Smaller helper method to find a `SignatureIndex` which corresponds to
-    /// the `resource.drop` intrinsic in components, namely a core wasm function
-    /// type which takes one `i32` argument and has no results.
+    /// Smaller helper method to find a `ModuleInternedTypeIndex` which
+    /// corresponds to the `resource.drop` intrinsic in components, namely a
+    /// core wasm function type which takes one `i32` argument and has no
+    /// results.
     ///
     /// This is a bit of a hack right now as ideally this find operation
-    /// wouldn't be needed and instead the `SignatureIndex` itself would be
-    /// threaded through appropriately, but that's left for a future
+    /// wouldn't be needed and instead the `ModuleInternedTypeIndex` itself
+    /// would be threaded through appropriately, but that's left for a future
     /// refactoring. Try not to lean too hard on this method though.
     pub fn find_resource_drop_signature(&self) -> Option<ModuleInternedTypeIndex> {
         self.module_types
-            .wasm_signatures()
-            .find(|(_, sig)| {
-                sig.params().len() == 1
-                    && sig.returns().len() == 0
-                    && sig.params()[0] == WasmValType::I32
+            .wasm_types()
+            .find(|(_, ty)| match &ty.composite_type {
+                wasmtime_types::WasmCompositeType::Func(sig) => {
+                    sig.params().len() == 1
+                        && sig.returns().len() == 0
+                        && sig.params()[0] == WasmValType::I32
+                }
             })
             .map(|(i, _)| i)
     }
diff --git a/crates/environ/src/module_environ.rs b/crates/environ/src/module_environ.rs
index 2c70a9fed..9d91ff570 100644
--- a/crates/environ/src/module_environ.rs
+++ b/crates/environ/src/module_environ.rs
@@ -550,7 +550,7 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> {
 
                 if self.tunables.generate_native_debuginfo {
                     let sig_index = self.result.module.functions[func_index].signature;
-                    let sig = &self.types[sig_index];
+                    let sig = self.types[sig_index].unwrap_func();
                     let mut locals = Vec::new();
                     for pair in body.get_locals_reader()? {
                         let (cnt, ty) = pair?;
@@ -1185,7 +1185,7 @@ impl ModuleTranslation<'_> {
                 .wasm_ty
                 .heap_type
             {
-                WasmHeapType::Func | WasmHeapType::Concrete(_) | WasmHeapType::NoFunc => {}
+                WasmHeapType::Func | WasmHeapType::ConcreteFunc(_) | WasmHeapType::NoFunc => {}
                 // If this is not a funcref table, then we can't support a
                 // pre-computed table of function indices. Technically this
                 // initializer won't trap so we could continue processing
diff --git a/crates/environ/src/module_types.rs b/crates/environ/src/module_types.rs
index 693175e8f..6005b7030 100644
--- a/crates/environ/src/module_types.rs
+++ b/crates/environ/src/module_types.rs
@@ -1,14 +1,14 @@
-use crate::{Module, PrimaryMap, TypeConvert, TypeIndex, WasmFuncType, WasmHeapType};
+use crate::{Module, PrimaryMap, TypeConvert, TypeIndex, WasmHeapType};
 use cranelift_entity::EntityRef;
 use serde_derive::{Deserialize, Serialize};
 use std::{
     collections::HashMap,
     ops::{Index, Range},
 };
-use wasmparser::{CompositeType, UnpackedIndex, Validator, ValidatorId};
+use wasmparser::{UnpackedIndex, Validator, ValidatorId};
 use wasmtime_types::{
-    wasm_unsupported, EngineOrModuleTypeIndex, ModuleInternedRecGroupIndex,
-    ModuleInternedTypeIndex, WasmResult,
+    EngineOrModuleTypeIndex, ModuleInternedRecGroupIndex, ModuleInternedTypeIndex, WasmResult,
+    WasmSubType,
 };
 
 /// All types used in a core wasm module.
@@ -21,7 +21,7 @@ use wasmtime_types::{
 #[derive(Default, Serialize, Deserialize)]
 pub struct ModuleTypes {
     rec_groups: PrimaryMap<ModuleInternedRecGroupIndex, Range<ModuleInternedTypeIndex>>,
-    wasm_types: PrimaryMap<ModuleInternedTypeIndex, WasmFuncType>,
+    wasm_types: PrimaryMap<ModuleInternedTypeIndex, WasmSubType>,
 }
 
 impl ModuleTypes {
@@ -29,12 +29,12 @@ impl ModuleTypes {
     /// this module.
     pub fn wasm_types(
         &self,
-    ) -> impl ExactSizeIterator<Item = (ModuleInternedTypeIndex, &WasmFuncType)> {
+    ) -> impl ExactSizeIterator<Item = (ModuleInternedTypeIndex, &WasmSubType)> {
         self.wasm_types.iter()
     }
 
     /// Get the type at the specified index.
-    pub fn get(&self, ty: ModuleInternedTypeIndex) -> &WasmFuncType {
+    pub fn get(&self, ty: ModuleInternedTypeIndex) -> &WasmSubType {
         &self.wasm_types[ty]
     }
 
@@ -58,9 +58,9 @@ impl ModuleTypes {
 }
 
 impl Index<ModuleInternedTypeIndex> for ModuleTypes {
-    type Output = WasmFuncType;
+    type Output = WasmSubType;
 
-    fn index(&self, sig: ModuleInternedTypeIndex) -> &WasmFuncType {
+    fn index(&self, sig: ModuleInternedTypeIndex) -> &WasmSubType {
         &self.wasm_types[sig]
     }
 }
@@ -163,17 +163,8 @@ impl ModuleTypesBuilder {
 
         for id in validator_types.rec_group_elements(rec_group_id) {
             let ty = &validator_types[id];
-            if ty.supertype_idx.is_some() {
-                return Err(wasm_unsupported!("wasm gc: explicit subtyping"));
-            }
-            match &ty.composite_type {
-                CompositeType::Func(ty) => {
-                    let wasm_ty = WasmparserTypeConverter::new(self, module).convert_func_type(ty);
-                    self.wasm_func_type_in_rec_group(id, wasm_ty);
-                }
-                CompositeType::Array(_) => return Err(wasm_unsupported!("wasm gc: array types")),
-                CompositeType::Struct(_) => return Err(wasm_unsupported!("wasm gc: struct types")),
-            }
+            let wasm_ty = WasmparserTypeConverter::new(self, module).convert_sub_type(ty)?;
+            self.wasm_sub_type_in_rec_group(id, wasm_ty);
         }
 
         Ok(self.end_rec_group(rec_group_id))
@@ -275,18 +266,18 @@ impl ModuleTypesBuilder {
         Ok(interned_type)
     }
 
-    /// Define a new Wasm function type while we are defining a rec group.
-    fn wasm_func_type_in_rec_group(
+    /// Define a new Wasm type while we are defining a rec group.
+    fn wasm_sub_type_in_rec_group(
         &mut self,
         id: wasmparser::types::CoreTypeId,
-        func_ty: WasmFuncType,
+        ty: WasmSubType,
     ) -> ModuleInternedTypeIndex {
         assert!(
             self.defining_rec_group.is_some(),
             "must be defining a rec group to define new types"
         );
 
-        let module_interned_index = self.types.wasm_types.push(func_ty);
+        let module_interned_index = self.types.wasm_types.push(ty);
         debug_assert_eq!(
             self.wasmparser_to_wasmtime.get(&id),
             Some(&module_interned_index),
@@ -309,11 +300,9 @@ impl ModuleTypesBuilder {
         self.types.rec_group_elements(rec_group)
     }
 
-    /// Returns an iterator over all the wasm function signatures found within
-    /// this module.
-    pub fn wasm_signatures(
-        &self,
-    ) -> impl Iterator<Item = (ModuleInternedTypeIndex, &WasmFuncType)> {
+    /// Returns an iterator over all the unique wasm types defined thus far
+    /// within this builder.
+    pub fn wasm_types(&self) -> impl Iterator<Item = (ModuleInternedTypeIndex, &WasmSubType)> {
         self.types.wasm_types()
     }
 }
@@ -348,12 +337,12 @@ impl TypeConvert for WasmparserTypeConverter<'_> {
         match index {
             UnpackedIndex::Id(id) => {
                 let signature = self.types.wasmparser_to_wasmtime[&id];
-                WasmHeapType::Concrete(EngineOrModuleTypeIndex::Module(signature))
+                WasmHeapType::ConcreteFunc(EngineOrModuleTypeIndex::Module(signature))
             }
             UnpackedIndex::Module(module_index) => {
                 let module_index = TypeIndex::from_u32(module_index);
                 let interned_index = self.module.types[module_index];
-                WasmHeapType::Concrete(EngineOrModuleTypeIndex::Module(interned_index))
+                WasmHeapType::ConcreteFunc(EngineOrModuleTypeIndex::Module(interned_index))
             }
             UnpackedIndex::RecGroup(_) => unreachable!(),
         }
diff --git a/crates/fuzzing/src/oracles/dummy.rs b/crates/fuzzing/src/oracles/dummy.rs
index da85f367c..0a504c0ad 100644
--- a/crates/fuzzing/src/oracles/dummy.rs
+++ b/crates/fuzzing/src/oracles/dummy.rs
@@ -48,7 +48,7 @@ pub fn dummy_value(val_ty: ValType) -> Result<Val> {
         ValType::Ref(r) => match r.heap_type() {
             _ if !r.is_nullable() => bail!("cannot construct a dummy value of type `{r}`"),
             HeapType::Extern => Val::null_extern_ref(),
-            HeapType::NoFunc | HeapType::Func | HeapType::Concrete(_) => Val::null_func_ref(),
+            HeapType::NoFunc | HeapType::Func | HeapType::ConcreteFunc(_) => Val::null_func_ref(),
             HeapType::Any | HeapType::I31 | HeapType::None => Val::null_any_ref(),
         },
     })
diff --git a/crates/runtime/src/instance.rs b/crates/runtime/src/instance.rs
index 13f75924e..8dc65abfc 100644
--- a/crates/runtime/src/instance.rs
+++ b/crates/runtime/src/instance.rs
@@ -897,8 +897,8 @@ impl Instance {
                                 VMGcRef::from_raw_u32(raw.get_anyref())
                             }),
                         )?,
-                    WasmHeapType::Func | WasmHeapType::Concrete(_) | WasmHeapType::NoFunc => table
-                        .init_func(
+                    WasmHeapType::Func | WasmHeapType::ConcreteFunc(_) | WasmHeapType::NoFunc => {
+                        table.init_func(
                             dst,
                             exprs.iter().map(|expr| unsafe {
                                 const_evaluator
@@ -907,7 +907,8 @@ impl Instance {
                                     .get_funcref()
                                     .cast()
                             }),
-                        )?,
+                        )?
+                    }
                 }
             }
         }
diff --git a/crates/runtime/src/instance/allocator.rs b/crates/runtime/src/instance/allocator.rs
index d1d2a2eb6..f33032ecf 100644
--- a/crates/runtime/src/instance/allocator.rs
+++ b/crates/runtime/src/instance/allocator.rs
@@ -594,7 +594,7 @@ fn initialize_tables(instance: &mut Instance, module: &Module) -> Result<()> {
                     }
 
                     wasmtime_environ::WasmHeapType::Func
-                    | wasmtime_environ::WasmHeapType::Concrete(_)
+                    | wasmtime_environ::WasmHeapType::ConcreteFunc(_)
                     | wasmtime_environ::WasmHeapType::NoFunc => {
                         let funcref = raw.get_funcref().cast::<VMFuncRef>();
                         let items = (0..table.size()).map(|_| funcref);
diff --git a/crates/runtime/src/table.rs b/crates/runtime/src/table.rs
index f138d1a37..484dfa022 100644
--- a/crates/runtime/src/table.rs
+++ b/crates/runtime/src/table.rs
@@ -248,7 +248,7 @@ impl From<DynamicGcRefTable> for Table {
 
 fn wasm_to_table_type(ty: WasmRefType) -> TableElementType {
     match ty.heap_type {
-        WasmHeapType::Func | WasmHeapType::Concrete(_) | WasmHeapType::NoFunc => {
+        WasmHeapType::Func | WasmHeapType::ConcreteFunc(_) | WasmHeapType::NoFunc => {
             TableElementType::Func
         }
         WasmHeapType::Extern | WasmHeapType::Any | WasmHeapType::I31 | WasmHeapType::None => {
diff --git a/crates/runtime/src/vmcontext.rs b/crates/runtime/src/vmcontext.rs
index f4ba5a02a..840512fec 100644
--- a/crates/runtime/src/vmcontext.rs
+++ b/crates/runtime/src/vmcontext.rs
@@ -456,7 +456,7 @@ impl VMGlobalDefinition {
                 WasmHeapType::Any | WasmHeapType::I31 | WasmHeapType::None => {
                     global.init_gc_ref(VMGcRef::from_raw_u32(raw.get_anyref()))
                 }
-                WasmHeapType::Func | WasmHeapType::Concrete(_) | WasmHeapType::NoFunc => {
+                WasmHeapType::Func | WasmHeapType::ConcreteFunc(_) | WasmHeapType::NoFunc => {
                     *global.as_func_ref_mut() = raw.get_funcref().cast()
                 }
             },
@@ -485,7 +485,7 @@ impl VMGlobalDefinition {
                     self.as_gc_ref()
                         .map_or(0, |r| gc_store.clone_gc_ref(r).as_raw_u32()),
                 ),
-                WasmHeapType::Func | WasmHeapType::Concrete(_) | WasmHeapType::NoFunc => {
+                WasmHeapType::Func | WasmHeapType::ConcreteFunc(_) | WasmHeapType::NoFunc => {
                     ValRaw::funcref(self.as_func_ref().cast())
                 }
             },
diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs
index 46a127a77..4a2c04ca3 100644
--- a/crates/types/src/lib.rs
+++ b/crates/types/src/lib.rs
@@ -376,7 +376,7 @@ impl EngineOrModuleTypeIndex {
 pub enum WasmHeapType {
     Extern,
     Func,
-    Concrete(EngineOrModuleTypeIndex),
+    ConcreteFunc(EngineOrModuleTypeIndex),
     NoFunc,
     Any,
     I31,
@@ -388,7 +388,7 @@ impl fmt::Display for WasmHeapType {
         match self {
             Self::Extern => write!(f, "extern"),
             Self::Func => write!(f, "func"),
-            Self::Concrete(i) => write!(f, "{i}"),
+            Self::ConcreteFunc(i) => write!(f, "func {i}"),
             Self::NoFunc => write!(f, "nofunc"),
             Self::Any => write!(f, "any"),
             Self::I31 => write!(f, "i31"),
@@ -403,7 +403,7 @@ impl TypeTrace for WasmHeapType {
         F: FnMut(EngineOrModuleTypeIndex) -> Result<(), E>,
     {
         match *self {
-            Self::Concrete(i) => func(i),
+            Self::ConcreteFunc(i) => func(i),
             _ => Ok(()),
         }
     }
@@ -413,7 +413,7 @@ impl TypeTrace for WasmHeapType {
         F: FnMut(&mut EngineOrModuleTypeIndex) -> Result<(), E>,
     {
         match self {
-            Self::Concrete(i) => func(i),
+            Self::ConcreteFunc(i) => func(i),
             _ => Ok(()),
         }
     }
@@ -428,7 +428,7 @@ impl WasmHeapType {
             Self::Extern | Self::Any | Self::I31 | Self::None => true,
 
             // All `t <: (ref null func)` are not.
-            Self::Func | Self::Concrete(_) | Self::NoFunc => false,
+            Self::Func | Self::ConcreteFunc(_) | Self::NoFunc => false,
         }
     }
 
@@ -523,6 +523,95 @@ impl WasmFuncType {
     }
 }
 
+/// A function, array, or struct type.
+#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
+pub enum WasmCompositeType {
+    Func(WasmFuncType),
+    //
+    // TODO: Array and struct types.
+}
+
+impl WasmCompositeType {
+    #[inline]
+    pub fn is_func(&self) -> bool {
+        matches!(self, Self::Func(_))
+    }
+
+    #[inline]
+    pub fn as_func(&self) -> Option<&WasmFuncType> {
+        match self {
+            WasmCompositeType::Func(f) => Some(f),
+        }
+    }
+
+    #[inline]
+    pub fn unwrap_func(&self) -> &WasmFuncType {
+        self.as_func().unwrap()
+    }
+}
+
+impl TypeTrace for WasmCompositeType {
+    fn trace<F, E>(&self, func: &mut F) -> Result<(), E>
+    where
+        F: FnMut(EngineOrModuleTypeIndex) -> Result<(), E>,
+    {
+        match self {
+            WasmCompositeType::Func(f) => f.trace(func),
+        }
+    }
+
+    fn trace_mut<F, E>(&mut self, func: &mut F) -> Result<(), E>
+    where
+        F: FnMut(&mut EngineOrModuleTypeIndex) -> Result<(), E>,
+    {
+        match self {
+            WasmCompositeType::Func(f) => f.trace_mut(func),
+        }
+    }
+}
+
+/// A concrete, user-defined (or host-defined) Wasm type.
+#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
+pub struct WasmSubType {
+    // TODO: is_final, supertype
+    //
+    /// The array, function, or struct that is defined.
+    pub composite_type: WasmCompositeType,
+}
+
+impl WasmSubType {
+    #[inline]
+    pub fn is_func(&self) -> bool {
+        self.composite_type.is_func()
+    }
+
+    #[inline]
+    pub fn as_func(&self) -> Option<&WasmFuncType> {
+        self.composite_type.as_func()
+    }
+
+    #[inline]
+    pub fn unwrap_func(&self) -> &WasmFuncType {
+        self.composite_type.unwrap_func()
+    }
+}
+
+impl TypeTrace for WasmSubType {
+    fn trace<F, E>(&self, func: &mut F) -> Result<(), E>
+    where
+        F: FnMut(EngineOrModuleTypeIndex) -> Result<(), E>,
+    {
+        self.composite_type.trace(func)
+    }
+
+    fn trace_mut<F, E>(&mut self, func: &mut F) -> Result<(), E>
+    where
+        F: FnMut(&mut EngineOrModuleTypeIndex) -> Result<(), E>,
+    {
+        self.composite_type.trace_mut(func)
+    }
+}
+
 /// A recursive type group.
 ///
 /// Types within a recgroup can have forward references to each other, which
@@ -536,7 +625,7 @@ impl WasmFuncType {
 #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
 pub struct WasmRecGroup {
     /// The types inside of this recgroup.
-    pub types: Box<[WasmFuncType]>,
+    pub types: Box<[WasmSubType]>,
 }
 
 impl TypeTrace for WasmRecGroup {
@@ -1062,6 +1151,27 @@ pub trait TypeConvert {
         }
     }
 
+    fn convert_sub_type(&self, ty: &wasmparser::SubType) -> WasmResult<WasmSubType> {
+        if ty.supertype_idx.is_some() {
+            return Err(wasm_unsupported!("wasm gc: explicit subtyping"));
+        }
+        let composite_type = self.convert_composite_type(&ty.composite_type)?;
+        Ok(WasmSubType { composite_type })
+    }
+
+    fn convert_composite_type(
+        &self,
+        ty: &wasmparser::CompositeType,
+    ) -> WasmResult<WasmCompositeType> {
+        match ty {
+            wasmparser::CompositeType::Func(f) => {
+                Ok(WasmCompositeType::Func(self.convert_func_type(f)))
+            }
+            wasmparser::CompositeType::Array(_) => Err(wasm_unsupported!("wasm gc: array types")),
+            wasmparser::CompositeType::Struct(_) => Err(wasm_unsupported!("wasm gc: struct types")),
+        }
+    }
+
     /// Converts a wasmparser function type to a wasmtime type
     fn convert_func_type(&self, ty: &wasmparser::FuncType) -> WasmFuncType {
         let params = ty
diff --git a/crates/wasi/src/preview1.rs b/crates/wasi/src/preview1.rs
index 41390112f..57fd0ebf5 100644
--- a/crates/wasi/src/preview1.rs
+++ b/crates/wasi/src/preview1.rs
@@ -2321,6 +2321,35 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
             // Indefinite sleeping is not supported in preview1.
             return Err(types::Errno::Inval.into());
         }
+
+        // This is a special case where `poll_oneoff` is just sleeping
+        // on a single relative timer event. This special case was added
+        // after experimental observations showed that std::thread::sleep
+        // results in more consistent sleep times. This design ensures that
+        // wasmtime can handle real-time requirements more accurately.
+        if nsubscriptions == 1 {
+            let sub = subs.read()?;
+            if let types::SubscriptionU::Clock(clocksub) = sub.u {
+                if !clocksub
+                    .flags
+                    .contains(types::Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME)
+                    && self.ctx().allow_blocking_current_thread
+                {
+                    std::thread::sleep(std::time::Duration::from_nanos(clocksub.timeout));
+                    events.write(types::Event {
+                        userdata: sub.userdata,
+                        error: types::Errno::Success,
+                        type_: types::Eventtype::Clock,
+                        fd_readwrite: types::EventFdReadwrite {
+                            flags: types::Eventrwflags::empty(),
+                            nbytes: 1,
+                        },
+                    })?;
+                    return Ok(1);
+                }
+            }
+        }
+
         let subs = subs.as_array(nsubscriptions);
         let events = events.as_array(nsubscriptions);
 
diff --git a/crates/wasmtime/src/compile.rs b/crates/wasmtime/src/compile.rs
index 5ea9e33bc..0727f6f8a 100644
--- a/crates/wasmtime/src/compile.rs
+++ b/crates/wasmtime/src/compile.rs
@@ -401,7 +401,8 @@ impl<'a> CompileInputs<'a> {
         if component.component.num_resources > 0 {
             if let Some(sig) = types.find_resource_drop_signature() {
                 ret.push_input(move |compiler| {
-                    let trampoline = compiler.compile_wasm_to_native_trampoline(&types[sig])?;
+                    let trampoline =
+                        compiler.compile_wasm_to_native_trampoline(types[sig].unwrap_func())?;
                     Ok(CompileOutput {
                         key: CompileKey::resource_drop_wasm_to_native_trampoline(),
                         symbol: "resource_drop_trampoline".to_string(),
@@ -493,7 +494,7 @@ impl<'a> CompileInputs<'a> {
 
         for signature in sigs {
             self.push_input(move |compiler| {
-                let wasm_func_ty = &types[signature];
+                let wasm_func_ty = types[signature].unwrap_func();
                 let trampoline = compiler.compile_wasm_to_native_trampoline(wasm_func_ty)?;
                 Ok(CompileOutput {
                     key: CompileKey::wasm_to_native_trampoline(signature),
diff --git a/crates/wasmtime/src/runtime/component/types.rs b/crates/wasmtime/src/runtime/component/types.rs
index b40780b5d..9536327ad 100644
--- a/crates/wasmtime/src/runtime/component/types.rs
+++ b/crates/wasmtime/src/runtime/component/types.rs
@@ -873,7 +873,7 @@ impl ComponentItem {
             TypeDef::Module(idx) => Self::Module(Module::from(*idx, ty)),
             TypeDef::CoreFunc(idx) => Self::CoreFunc(FuncType::from_wasm_func_type(
                 engine,
-                ty.types[*idx].clone(),
+                ty.types[*idx].unwrap_func().clone(),
             )),
             TypeDef::Resource(idx) => {
                 let resource_index = ty.types[*idx].ty;
diff --git a/crates/wasmtime/src/runtime/coredump.rs b/crates/wasmtime/src/runtime/coredump.rs
index 04b376185..ee98659fd 100644
--- a/crates/wasmtime/src/runtime/coredump.rs
+++ b/crates/wasmtime/src/runtime/coredump.rs
@@ -179,7 +179,7 @@ impl WasmCoreDump {
                     ValType::Ref(r) => match r.heap_type() {
                         HeapType::Extern => wasm_encoder::ValType::EXTERNREF,
 
-                        HeapType::Func | HeapType::Concrete(_) | HeapType::NoFunc => {
+                        HeapType::Func | HeapType::ConcreteFunc(_) | HeapType::NoFunc => {
                             wasm_encoder::ValType::FUNCREF
                         }
 
diff --git a/crates/wasmtime/src/runtime/externals/global.rs b/crates/wasmtime/src/runtime/externals/global.rs
index 5f78ca9ad..2af59fff7 100644
--- a/crates/wasmtime/src/runtime/externals/global.rs
+++ b/crates/wasmtime/src/runtime/externals/global.rs
@@ -117,7 +117,7 @@ impl Global {
                 ValType::V128 => Val::V128((*definition.as_u128()).into()),
                 ValType::Ref(ref_ty) => {
                     let reference: Ref = match ref_ty.heap_type() {
-                        HeapType::Func | HeapType::Concrete(_) => {
+                        HeapType::Func | HeapType::ConcreteFunc(_) => {
                             Func::_from_raw(&mut store, definition.as_func_ref().cast()).into()
                         }
 
diff --git a/crates/wasmtime/src/runtime/gc/enabled/rooting.rs b/crates/wasmtime/src/runtime/gc/enabled/rooting.rs
index 293765e0a..2a120a6e5 100644
--- a/crates/wasmtime/src/runtime/gc/enabled/rooting.rs
+++ b/crates/wasmtime/src/runtime/gc/enabled/rooting.rs
@@ -107,6 +107,7 @@ use crate::{
     AsContext, AsContextMut, GcRef, Result, RootedGcRef,
 };
 use anyhow::anyhow;
+use std::num::NonZeroU64;
 use std::{
     fmt::Debug,
     hash::Hash,
@@ -186,12 +187,19 @@ pub(crate) use sealed::*;
 // Just `pub` to avoid `warn(private_interfaces)` in public APIs, which we can't
 // `allow(...)` on our MSRV yet.
 #[doc(hidden)]
+#[repr(C)] // NB: if this layout changes be sure to change the C API as well
 pub struct GcRootIndex {
     store_id: StoreId,
     generation: u32,
     index: PackedIndex,
 }
 
+const _: () = {
+    // NB: these match the C API which should also be updated if this changes
+    assert!(std::mem::size_of::<GcRootIndex>() == 16);
+    assert!(std::mem::align_of::<GcRootIndex>() == 8);
+};
+
 impl GcRootIndex {
     #[inline]
     pub(crate) fn comes_from_same_store(&self, store: &StoreOpaque) -> bool {
@@ -271,6 +279,7 @@ impl GcRootIndex {
 /// where the high bit is the discriminant and the lower 31 bits are the
 /// payload.
 #[derive(Clone, Copy, PartialEq, Eq, Hash)]
+#[repr(transparent)]
 struct PackedIndex(u32);
 
 impl Debug for PackedIndex {
@@ -1192,6 +1201,7 @@ where
 /// unroot. Sometimes leaking is intentional and desirable, particularly when
 /// dealing with short-lived [`Store`][crate::Store]s where unrooting would just
 /// be busy work since the whole store is about to be dropped.
+#[repr(transparent)] // NB: the C API relies on this
 pub struct ManuallyRooted<T>
 where
     T: GcRef,
@@ -1200,6 +1210,16 @@ where
     _phantom: std::marker::PhantomData<T>,
 }
 
+const _: () = {
+    use crate::{AnyRef, ExternRef};
+
+    // NB: these match the C API which should also be updated if this changes
+    assert!(std::mem::size_of::<ManuallyRooted<AnyRef>>() == 16);
+    assert!(std::mem::align_of::<ManuallyRooted<AnyRef>>() == 8);
+    assert!(std::mem::size_of::<ManuallyRooted<ExternRef>>() == 16);
+    assert!(std::mem::align_of::<ManuallyRooted<ExternRef>>() == 8);
+};
+
 impl<T: GcRef> Debug for ManuallyRooted<T> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         let name = format!("ManuallyRooted<{}>", std::any::type_name::<T>());
@@ -1513,6 +1533,27 @@ where
             .expect("ManuallyRooted's get_gc_ref is infallible");
         gc_ref.hash(state);
     }
+
+    #[doc(hidden)]
+    pub fn into_parts_for_c_api(self) -> (NonZeroU64, u32, u32) {
+        (
+            self.inner.store_id.as_raw(),
+            self.inner.generation,
+            self.inner.index.0,
+        )
+    }
+
+    #[doc(hidden)]
+    pub unsafe fn from_raw_parts_for_c_api(a: NonZeroU64, b: u32, c: u32) -> ManuallyRooted<T> {
+        ManuallyRooted {
+            inner: GcRootIndex {
+                store_id: StoreId::from_raw(a),
+                generation: b,
+                index: PackedIndex(c),
+            },
+            _phantom: std::marker::PhantomData,
+        }
+    }
 }
 
 impl<T: GcRef> RootedGcRefImpl<T> for ManuallyRooted<T> {
diff --git a/crates/wasmtime/src/runtime/linker.rs b/crates/wasmtime/src/runtime/linker.rs
index 096a2a43e..3a93f9c3c 100644
--- a/crates/wasmtime/src/runtime/linker.rs
+++ b/crates/wasmtime/src/runtime/linker.rs
@@ -345,7 +345,7 @@ impl<T> Linker<T> {
                                         debug_assert!(r.is_nullable());
                                         match r.heap_type() {
                                             HeapType::Func
-                                            | HeapType::Concrete(_)
+                                            | HeapType::ConcreteFunc(_)
                                             | HeapType::NoFunc => Val::null_func_ref(),
                                             HeapType::Extern => Val::null_extern_ref(),
                                             HeapType::Any | HeapType::I31 | HeapType::None => {
diff --git a/crates/wasmtime/src/runtime/store/data.rs b/crates/wasmtime/src/runtime/store/data.rs
index 3c390d915..dad8654d0 100644
--- a/crates/wasmtime/src/runtime/store/data.rs
+++ b/crates/wasmtime/src/runtime/store/data.rs
@@ -202,6 +202,7 @@ where
 /// it came from. Comparisons with this value are how panics are generated for
 /// mismatching the item that a store belongs to.
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+#[repr(transparent)] // NB: relied on in the C API
 pub struct StoreId(NonZeroU64);
 
 impl StoreId {
@@ -235,6 +236,16 @@ impl StoreId {
         }
         store_id_mismatch();
     }
+
+    /// Raw accessor for the C API.
+    pub fn as_raw(&self) -> NonZeroU64 {
+        self.0
+    }
+
+    /// Raw constructor for the C API.
+    pub fn from_raw(id: NonZeroU64) -> StoreId {
+        StoreId(id)
+    }
 }
 
 #[repr(C)] // used by reference in the C API, also in `wasmtime_func_t`.
diff --git a/crates/wasmtime/src/runtime/type_registry.rs b/crates/wasmtime/src/runtime/type_registry.rs
index 4262a40eb..729570961 100644
--- a/crates/wasmtime/src/runtime/type_registry.rs
+++ b/crates/wasmtime/src/runtime/type_registry.rs
@@ -20,7 +20,7 @@ use std::{
 };
 use wasmtime_environ::{
     iter_entity_range, EngineOrModuleTypeIndex, ModuleInternedTypeIndex, ModuleTypes, PrimaryMap,
-    TypeTrace, VMSharedTypeIndex, WasmFuncType, WasmRecGroup,
+    TypeTrace, VMSharedTypeIndex, WasmRecGroup, WasmSubType,
 };
 use wasmtime_slab::{Id as SlabId, Slab};
 
@@ -169,11 +169,11 @@ fn slab_id_to_shared_type_index(id: SlabId) -> VMSharedTypeIndex {
 /// Automatically unregisters the type on drop. (Unless other `RegisteredTypes`
 /// are keeping the type registered).
 ///
-/// Dereferences to its underlying `WasmFuncType`.
+/// Dereferences to its underlying `WasmSubType`.
 pub struct RegisteredType {
     engine: Engine,
     entry: RecGroupEntry,
-    ty: Arc<WasmFuncType>,
+    ty: Arc<WasmSubType>,
     index: VMSharedTypeIndex,
 }
 
@@ -218,7 +218,7 @@ impl Drop for RegisteredType {
 }
 
 impl std::ops::Deref for RegisteredType {
-    type Target = WasmFuncType;
+    type Target = WasmSubType;
 
     fn deref(&self) -> &Self::Target {
         &self.ty
@@ -254,7 +254,7 @@ impl Hash for RegisteredType {
 impl RegisteredType {
     /// Constructs a new `RegisteredType`, registering the given type with the
     /// engine's `TypeRegistry`.
-    pub fn new(engine: &Engine, ty: WasmFuncType) -> RegisteredType {
+    pub fn new(engine: &Engine, ty: WasmSubType) -> RegisteredType {
         let (entry, index, ty) = {
             log::trace!("RegisteredType::new({ty:?})");
 
@@ -317,7 +317,7 @@ impl RegisteredType {
         engine: Engine,
         entry: RecGroupEntry,
         index: VMSharedTypeIndex,
-        ty: Arc<WasmFuncType>,
+        ty: Arc<WasmSubType>,
     ) -> Self {
         debug_assert!(entry.0.registrations.load(Acquire) != 0);
         RegisteredType {
@@ -432,7 +432,7 @@ struct TypeRegistryInner {
     // Wasm type.
     //
     // These types are always canonicalized for runtime usage.
-    types: Slab<Arc<WasmFuncType>>,
+    types: Slab<Arc<WasmSubType>>,
 
     // A map that lets you walk backwards from a `VMSharedTypeIndex` to its
     // `RecGroupEntry`.
@@ -511,7 +511,7 @@ impl TypeRegistryInner {
         &mut self,
         map: &PrimaryMap<ModuleInternedTypeIndex, VMSharedTypeIndex>,
         range: Range<ModuleInternedTypeIndex>,
-        types: impl ExactSizeIterator<Item = WasmFuncType>,
+        types: impl ExactSizeIterator<Item = WasmSubType>,
     ) -> RecGroupEntry {
         debug_assert_eq!(iter_entity_range(range.clone()).len(), types.len());
 
@@ -594,7 +594,7 @@ impl TypeRegistryInner {
     }
 
     /// Is the given type canonicalized for runtime usage this registry?
-    fn assert_canonicalized_for_runtime_usage_in_this_registry(&self, ty: &WasmFuncType) {
+    fn assert_canonicalized_for_runtime_usage_in_this_registry(&self, ty: &WasmSubType) {
         ty.trace::<_, ()>(&mut |index| match index {
             EngineOrModuleTypeIndex::RecGroup(_) | EngineOrModuleTypeIndex::Module(_) => {
                 panic!("not canonicalized for runtime usage: {ty:?}")
@@ -619,7 +619,7 @@ impl TypeRegistryInner {
     fn insert_one_type_from_rec_group(
         &mut self,
         module_index: ModuleInternedTypeIndex,
-        ty: WasmFuncType,
+        ty: WasmSubType,
     ) -> VMSharedTypeIndex {
         // Despite being canonicalized for runtime usage, this type may still
         // have forward references to other types in the rec group we haven't
@@ -648,7 +648,7 @@ impl TypeRegistryInner {
     ///
     /// The returned entry will have already had its reference count incremented
     /// on behalf of callers.
-    fn register_singleton_rec_group(&mut self, ty: WasmFuncType) -> RecGroupEntry {
+    fn register_singleton_rec_group(&mut self, ty: WasmSubType) -> RecGroupEntry {
         self.assert_canonicalized_for_runtime_usage_in_this_registry(&ty);
 
         // This type doesn't have any module-level type references, since it is
@@ -800,7 +800,7 @@ impl TypeRegistry {
     /// still using the resulting value! Use the `RegisteredType::root`
     /// constructor if you need to ensure that property and you don't have some
     /// other mechanism already keeping the type registered.
-    pub fn borrow(&self, index: VMSharedTypeIndex) -> Option<Arc<WasmFuncType>> {
+    pub fn borrow(&self, index: VMSharedTypeIndex) -> Option<Arc<WasmSubType>> {
         let id = shared_type_index_to_slab_id(index);
         let inner = self.0.read().unwrap();
         inner.types.get(id).cloned()
diff --git a/crates/wasmtime/src/runtime/types.rs b/crates/wasmtime/src/runtime/types.rs
index 1cca5e5a9..1e306acbb 100644
--- a/crates/wasmtime/src/runtime/types.rs
+++ b/crates/wasmtime/src/runtime/types.rs
@@ -2,7 +2,8 @@ use anyhow::{bail, Result};
 use std::fmt::{self, Display};
 use wasmtime_environ::{
     EngineOrModuleTypeIndex, EntityType, Global, Memory, ModuleTypes, Table, TypeTrace,
-    VMSharedTypeIndex, WasmFuncType, WasmHeapType, WasmRefType, WasmValType,
+    VMSharedTypeIndex, WasmCompositeType, WasmFuncType, WasmHeapType, WasmRefType, WasmSubType,
+    WasmValType,
 };
 
 use crate::{type_registry::RegisteredType, Engine};
@@ -452,7 +453,7 @@ pub enum HeapType {
     /// specific, concrete type.
     ///
     /// This is a subtype of `func` and a supertype of `nofunc`.
-    Concrete(FuncType),
+    ConcreteFunc(FuncType),
 
     /// The `nofunc` heap type represents the null function reference.
     ///
@@ -486,7 +487,7 @@ impl Display for HeapType {
             HeapType::Any => write!(f, "any"),
             HeapType::I31 => write!(f, "i31"),
             HeapType::None => write!(f, "none"),
-            HeapType::Concrete(ty) => write!(f, "(concrete {:?})", ty.type_index()),
+            HeapType::ConcreteFunc(ty) => write!(f, "(concrete {:?})", ty.type_index()),
         }
     }
 }
@@ -494,7 +495,7 @@ impl Display for HeapType {
 impl From<FuncType> for HeapType {
     #[inline]
     fn from(f: FuncType) -> Self {
-        HeapType::Concrete(f)
+        HeapType::ConcreteFunc(f)
     }
 }
 
@@ -540,7 +541,7 @@ impl HeapType {
     ///
     /// Types that are not concrete, user-defined types are abstract types.
     pub fn is_concrete(&self) -> bool {
-        matches!(self, HeapType::Concrete(_))
+        matches!(self, HeapType::ConcreteFunc(_))
     }
 
     /// Get the underlying concrete, user-defined type, if any.
@@ -548,7 +549,7 @@ impl HeapType {
     /// Returns `None` for abstract types.
     pub fn as_concrete(&self) -> Option<&FuncType> {
         match self {
-            HeapType::Concrete(f) => Some(f),
+            HeapType::ConcreteFunc(f) => Some(f),
             _ => None,
         }
     }
@@ -570,7 +571,7 @@ impl HeapType {
         let _ = engine;
 
         match self {
-            HeapType::Func | HeapType::Concrete(_) | HeapType::NoFunc => HeapType::Func,
+            HeapType::Func | HeapType::ConcreteFunc(_) | HeapType::NoFunc => HeapType::Func,
             HeapType::Extern => HeapType::Extern,
             HeapType::Any | HeapType::I31 | HeapType::None => HeapType::Any,
         }
@@ -589,12 +590,14 @@ impl HeapType {
             (HeapType::Extern, HeapType::Extern) => true,
             (HeapType::Extern, _) => false,
 
-            (HeapType::NoFunc, HeapType::NoFunc | HeapType::Concrete(_) | HeapType::Func) => true,
+            (HeapType::NoFunc, HeapType::NoFunc | HeapType::ConcreteFunc(_) | HeapType::Func) => {
+                true
+            }
             (HeapType::NoFunc, _) => false,
 
-            (HeapType::Concrete(_), HeapType::Func) => true,
-            (HeapType::Concrete(a), HeapType::Concrete(b)) => a.matches(b),
-            (HeapType::Concrete(_), _) => false,
+            (HeapType::ConcreteFunc(_), HeapType::Func) => true,
+            (HeapType::ConcreteFunc(a), HeapType::ConcreteFunc(b)) => a.matches(b),
+            (HeapType::ConcreteFunc(_), _) => false,
 
             (HeapType::Func, HeapType::Func) => true,
             (HeapType::Func, _) => false,
@@ -642,7 +645,7 @@ impl HeapType {
             | HeapType::Any
             | HeapType::I31
             | HeapType::None => true,
-            HeapType::Concrete(ty) => ty.comes_from_same_engine(engine),
+            HeapType::ConcreteFunc(ty) => ty.comes_from_same_engine(engine),
         }
     }
 
@@ -654,8 +657,8 @@ impl HeapType {
             HeapType::Any => WasmHeapType::Any,
             HeapType::I31 => WasmHeapType::I31,
             HeapType::None => WasmHeapType::None,
-            HeapType::Concrete(f) => {
-                WasmHeapType::Concrete(EngineOrModuleTypeIndex::Engine(f.type_index()))
+            HeapType::ConcreteFunc(f) => {
+                WasmHeapType::ConcreteFunc(EngineOrModuleTypeIndex::Engine(f.type_index()))
             }
         }
     }
@@ -668,11 +671,11 @@ impl HeapType {
             WasmHeapType::Any => HeapType::Any,
             WasmHeapType::I31 => HeapType::I31,
             WasmHeapType::None => HeapType::None,
-            WasmHeapType::Concrete(EngineOrModuleTypeIndex::Engine(idx)) => {
-                HeapType::Concrete(FuncType::from_shared_type_index(engine, *idx))
+            WasmHeapType::ConcreteFunc(EngineOrModuleTypeIndex::Engine(idx)) => {
+                HeapType::ConcreteFunc(FuncType::from_shared_type_index(engine, *idx))
             }
-            WasmHeapType::Concrete(EngineOrModuleTypeIndex::Module(_))
-            | WasmHeapType::Concrete(EngineOrModuleTypeIndex::RecGroup(_)) => {
+            WasmHeapType::ConcreteFunc(EngineOrModuleTypeIndex::Module(_))
+            | WasmHeapType::ConcreteFunc(EngineOrModuleTypeIndex::RecGroup(_)) => {
                 panic!("HeapType::from_wasm_type on non-canonical heap type")
             }
         }
@@ -688,7 +691,7 @@ impl HeapType {
             // TODO: Once we support concrete struct and array types, we will
             // need to inspect the payload to determine whether this is a
             // GC-managed type or not.
-            Self::Concrete(_) => false,
+            Self::ConcreteFunc(_) => false,
 
             // These are compatible with GC references, but don't actually point
             // to GC objecs. It would generally be safe to return `true` here,
@@ -768,7 +771,7 @@ impl ExternType {
                     FuncType::from_shared_type_index(engine, *e).into()
                 }
                 EngineOrModuleTypeIndex::Module(m) => {
-                    FuncType::from_wasm_func_type(engine, types[*m].clone()).into()
+                    FuncType::from_wasm_func_type(engine, types[*m].unwrap_func().clone()).into()
                 }
                 EngineOrModuleTypeIndex::RecGroup(_) => unreachable!(),
             },
@@ -889,6 +892,7 @@ impl FuncType {
     pub fn param(&self, i: usize) -> Option<ValType> {
         let engine = self.engine();
         self.registered_type
+            .unwrap_func()
             .params()
             .get(i)
             .map(|ty| ValType::from_wasm_type(engine, ty))
@@ -899,6 +903,7 @@ impl FuncType {
     pub fn params(&self) -> impl ExactSizeIterator<Item = ValType> + '_ {
         let engine = self.engine();
         self.registered_type
+            .unwrap_func()
             .params()
             .iter()
             .map(|ty| ValType::from_wasm_type(engine, ty))
@@ -910,6 +915,7 @@ impl FuncType {
     pub fn result(&self, i: usize) -> Option<ValType> {
         let engine = self.engine();
         self.registered_type
+            .unwrap_func()
             .returns()
             .get(i)
             .map(|ty| ValType::from_wasm_type(engine, ty))
@@ -920,6 +926,7 @@ impl FuncType {
     pub fn results(&self) -> impl ExactSizeIterator<Item = ValType> + '_ {
         let engine = self.engine();
         self.registered_type
+            .unwrap_func()
             .returns()
             .iter()
             .map(|ty| ValType::from_wasm_type(engine, ty))
@@ -980,7 +987,7 @@ impl FuncType {
     }
 
     pub(crate) fn as_wasm_func_type(&self) -> &WasmFuncType {
-        &self.registered_type
+        self.registered_type.unwrap_func()
     }
 
     pub(crate) fn into_registered_type(self) -> RegisteredType {
@@ -998,7 +1005,16 @@ impl FuncType {
     /// holding a strong reference to all of its types, including any `(ref null
     /// <index>)` types used in the function's parameters and results.
     pub(crate) fn from_wasm_func_type(engine: &Engine, ty: WasmFuncType) -> FuncType {
-        let ty = RegisteredType::new(engine, ty);
+        let ty = RegisteredType::new(
+            engine,
+            WasmSubType {
+                // TODO:
+                //
+                // is_final: true,
+                // supertype: None,
+                composite_type: WasmCompositeType::Func(ty),
+            },
+        );
         Self {
             registered_type: ty,
         }
diff --git a/crates/wasmtime/src/runtime/types/matching.rs b/crates/wasmtime/src/runtime/types/matching.rs
index 45504aceb..9511d9b03 100644
--- a/crates/wasmtime/src/runtime/types/matching.rs
+++ b/crates/wasmtime/src/runtime/types/matching.rs
@@ -1,8 +1,8 @@
 use crate::{linker::DefinitionType, Engine, FuncType};
 use anyhow::{anyhow, bail, Result};
 use wasmtime_environ::{
-    EntityType, Global, Memory, ModuleTypes, Table, TypeTrace, VMSharedTypeIndex, WasmFuncType,
-    WasmHeapType, WasmRefType, WasmValType,
+    EntityType, Global, Memory, ModuleTypes, Table, TypeTrace, VMSharedTypeIndex,
+    WasmCompositeType, WasmHeapType, WasmRefType, WasmSubType, WasmValType,
 };
 
 pub struct MatchCx<'a> {
@@ -36,7 +36,7 @@ impl MatchCx<'_> {
             None => panic!("{actual:?} is not registered"),
         };
 
-        Err(func_ty_mismatch(msg, &expected, &actual))
+        Err(concrete_type_mismatch(msg, &expected, &actual))
     }
 
     /// Validates that the `expected` type matches the type of `actual`
@@ -96,7 +96,7 @@ pub fn entity_ty(
                 if expected == actual {
                     Ok(())
                 } else {
-                    Err(func_ty_mismatch(
+                    Err(concrete_type_mismatch(
                         "function types incompatible",
                         expected,
                         actual,
@@ -109,22 +109,29 @@ pub fn entity_ty(
     }
 }
 
-fn func_ty_mismatch(msg: &str, expected: &WasmFuncType, actual: &WasmFuncType) -> anyhow::Error {
-    let render = |ty: &WasmFuncType| {
-        let params = ty
-            .params()
-            .iter()
-            .map(|s| s.to_string())
-            .collect::<Vec<_>>()
-            .join(", ");
-        let returns = ty
-            .returns()
-            .iter()
-            .map(|s| s.to_string())
-            .collect::<Vec<_>>()
-            .join(", ");
-        format!("`({}) -> ({})`", params, returns)
+fn concrete_type_mismatch(
+    msg: &str,
+    expected: &WasmSubType,
+    actual: &WasmSubType,
+) -> anyhow::Error {
+    let render = |ty: &WasmSubType| match &ty.composite_type {
+        WasmCompositeType::Func(ty) => {
+            let params = ty
+                .params()
+                .iter()
+                .map(|s| s.to_string())
+                .collect::<Vec<_>>()
+                .join(", ");
+            let returns = ty
+                .returns()
+                .iter()
+                .map(|s| s.to_string())
+                .collect::<Vec<_>>()
+                .join(", ");
+            format!("`({}) -> ({})`", params, returns)
+        }
     };
+
     anyhow!(
         "{msg}: expected func of type {}, found func of type {}",
         render(expected),
@@ -197,15 +204,15 @@ fn match_heap(expected: WasmHeapType, actual: WasmHeapType, desc: &str) -> Resul
     let result = match (actual, expected) {
         // TODO: Wasm GC introduces subtyping between function types, so it will
         // no longer suffice to check whether canonicalized type IDs are equal.
-        (H::Concrete(actual), H::Concrete(expected)) => actual == expected,
+        (H::ConcreteFunc(actual), H::ConcreteFunc(expected)) => actual == expected,
 
         (H::NoFunc, H::NoFunc) => true,
         (_, H::NoFunc) => false,
 
-        (H::NoFunc, H::Concrete(_)) => true,
-        (_, H::Concrete(_)) => false,
+        (H::NoFunc, H::ConcreteFunc(_)) => true,
+        (_, H::ConcreteFunc(_)) => false,
 
-        (H::NoFunc | H::Concrete(_) | H::Func, H::Func) => true,
+        (H::NoFunc | H::ConcreteFunc(_) | H::Func, H::Func) => true,
         (_, H::Func) => false,
 
         (H::Extern, H::Extern) => true,
diff --git a/crates/wasmtime/src/runtime/values.rs b/crates/wasmtime/src/runtime/values.rs
index 96f21a581..0b03ade19 100644
--- a/crates/wasmtime/src/runtime/values.rs
+++ b/crates/wasmtime/src/runtime/values.rs
@@ -80,7 +80,7 @@ impl Val {
     /// Returns the null reference for the given heap type.
     pub fn null_ref(heap_type: HeapType) -> Val {
         match heap_type {
-            HeapType::Func | HeapType::NoFunc | HeapType::Concrete(_) => Val::FuncRef(None),
+            HeapType::Func | HeapType::NoFunc | HeapType::ConcreteFunc(_) => Val::FuncRef(None),
             HeapType::Extern => Val::ExternRef(None),
             HeapType::Any | HeapType::I31 | HeapType::None => Val::AnyRef(None),
         }
@@ -129,9 +129,10 @@ impl Val {
             Val::V128(_) => ValType::V128,
             Val::ExternRef(_) => ValType::EXTERNREF,
             Val::FuncRef(None) => ValType::NULLFUNCREF,
-            Val::FuncRef(Some(f)) => {
-                ValType::Ref(RefType::new(false, HeapType::Concrete(f.load_ty(store))))
-            }
+            Val::FuncRef(Some(f)) => ValType::Ref(RefType::new(
+                false,
+                HeapType::ConcreteFunc(f.load_ty(store)),
+            )),
             Val::AnyRef(None) => ValType::NULLREF,
             Val::AnyRef(Some(_)) => {
                 assert!(VMGcRef::ONLY_EXTERN_REF_AND_I31);
@@ -242,7 +243,7 @@ impl Val {
             ValType::V128 => Val::V128(raw.get_v128().into()),
             ValType::Ref(ref_ty) => {
                 let ref_ = match ref_ty.heap_type() {
-                    HeapType::Func | HeapType::Concrete(_) => {
+                    HeapType::Func | HeapType::ConcreteFunc(_) => {
                         Func::from_raw(store, raw.get_funcref()).into()
                     }
                     HeapType::NoFunc => Ref::Func(None),
@@ -757,7 +758,7 @@ impl Ref {
                 // NB: We choose the most-specific heap type we can here and let
                 // subtyping do its thing if callers are matching against a
                 // `HeapType::Func`.
-                Ref::Func(Some(f)) => HeapType::Concrete(f.load_ty(store)),
+                Ref::Func(Some(f)) => HeapType::ConcreteFunc(f.load_ty(store)),
                 Ref::Func(None) => HeapType::NoFunc,
 
                 Ref::Any(Some(_)) => {
@@ -791,8 +792,8 @@ impl Ref {
             (Ref::Extern(_), _) => false,
 
             (Ref::Func(_), HeapType::Func) => true,
-            (Ref::Func(None), HeapType::NoFunc | HeapType::Concrete(_)) => true,
-            (Ref::Func(Some(f)), HeapType::Concrete(func_ty)) => f._matches_ty(store, func_ty),
+            (Ref::Func(None), HeapType::NoFunc | HeapType::ConcreteFunc(_)) => true,
+            (Ref::Func(Some(f)), HeapType::ConcreteFunc(func_ty)) => f._matches_ty(store, func_ty),
             (Ref::Func(_), _) => false,
 
             (Ref::Any(_), HeapType::Any) => true,
@@ -923,7 +924,7 @@ mod tests {
         // Should panic.
         let _ = Val::FuncRef(Some(f)).matches_ty(
             &s1,
-            &ValType::Ref(RefType::new(true, HeapType::Concrete(t2))),
+            &ValType::Ref(RefType::new(true, HeapType::ConcreteFunc(t2))),
         );
     }
 
@@ -940,6 +941,6 @@ mod tests {
         let f = Func::new(&mut s1, t1.clone(), |_caller, _args, _results| Ok(()));
 
         // Should panic.
-        let _ = Ref::Func(Some(f)).matches_ty(&s1, &RefType::new(true, HeapType::Concrete(t2)));
+        let _ = Ref::Func(Some(f)).matches_ty(&s1, &RefType::new(true, HeapType::ConcreteFunc(t2)));
     }
 }
diff --git a/crates/winch/src/compiler.rs b/crates/winch/src/compiler.rs
index ecb93dacd..217f3daff 100644
--- a/crates/winch/src/compiler.rs
+++ b/crates/winch/src/compiler.rs
@@ -94,7 +94,7 @@ impl wasmtime_environ::Compiler for Compiler {
     ) -> Result<(WasmFunctionInfo, Box<dyn Any + Send>), CompileError> {
         let index = translation.module.func_index(index);
         let sig = translation.module.functions[index].signature;
-        let ty = &types[sig];
+        let ty = types[sig].unwrap_func();
         let FunctionBodyData { body, validator } = data;
         let mut context = self.get_context(translation);
         let mut validator = validator.into_validator(mem::take(&mut context.allocations));
diff --git a/examples/anyref.c b/examples/anyref.c
index a54899b27..cf0a67f3b 100644
--- a/examples/anyref.c
+++ b/examples/anyref.c
@@ -94,12 +94,13 @@ int main() {
 
   // Create a new `anyref` value from an i31 (`i31ref` is a subtype of
   // `anyref`).
-  wasmtime_anyref_t *anyref = wasmtime_anyref_from_i31(context, 1234);
-  assert(anyref != NULL);
+  wasmtime_anyref_t anyref;
+  wasmtime_anyref_from_i31(context, 1234, &anyref);
+  assert(!wasmtime_anyref_is_null(&anyref));
 
   // The `anyref`'s inner i31 value should be 1234.
   uint32_t i31val = 0;
-  bool is_i31 = wasmtime_anyref_i31_get_u(context, anyref, &i31val);
+  bool is_i31 = wasmtime_anyref_i31_get_u(context, &anyref, &i31val);
   assert(is_i31);
   assert(i31val == 1234);
 
@@ -128,10 +129,10 @@ int main() {
   assert(elem.kind == WASMTIME_ANYREF);
   is_i31 = false;
   i31val = 0;
-  is_i31 = wasmtime_anyref_i31_get_u(context, elem.of.anyref, &i31val);
+  is_i31 = wasmtime_anyref_i31_get_u(context, &elem.of.anyref, &i31val);
   assert(is_i31);
   assert(i31val == 1234);
-  wasmtime_val_delete(context, &elem);
+  wasmtime_val_unroot(context, &elem);
 
   printf("Touching `anyref` global...\n");
 
@@ -152,10 +153,10 @@ int main() {
   assert(global_val.kind == WASMTIME_ANYREF);
   is_i31 = false;
   i31val = 0;
-  is_i31 = wasmtime_anyref_i31_get_u(context, global_val.of.anyref, &i31val);
+  is_i31 = wasmtime_anyref_i31_get_u(context, &global_val.of.anyref, &i31val);
   assert(is_i31);
   assert(i31val == 1234);
-  wasmtime_val_delete(context, &global_val);
+  wasmtime_val_unroot(context, &global_val);
 
   printf("Passing `anyref` into func...\n");
 
@@ -190,11 +191,11 @@ int main() {
   assert(results[0].kind == WASMTIME_ANYREF);
   is_i31 = false;
   i31val = 0;
-  is_i31 = wasmtime_anyref_i31_get_u(context, results[0].of.anyref, &i31val);
+  is_i31 = wasmtime_anyref_i31_get_u(context, &results[0].of.anyref, &i31val);
   assert(is_i31);
   assert(i31val == 42);
-  wasmtime_val_delete(context, &results[0]);
-  wasmtime_val_delete(context, &anyref_val);
+  wasmtime_val_unroot(context, &results[0]);
+  wasmtime_val_unroot(context, &anyref_val);
 
   // We can GC any now-unused references to our anyref that the store is
   // holding.
diff --git a/examples/externref.c b/examples/externref.c
index 79fa82841..fe8098a6b 100644
--- a/examples/externref.c
+++ b/examples/externref.c
@@ -94,11 +94,12 @@ int main() {
   //
   // Note that the NULL here is a finalizer callback, but we don't need one for
   // this example.
-  wasmtime_externref_t *externref =
-      wasmtime_externref_new(context, "Hello, World!", NULL);
+  wasmtime_externref_t externref;
+  ok = wasmtime_externref_new(context, "Hello, World!", NULL, &externref);
+  assert(ok);
 
   // The `externref`'s wrapped data should be the string "Hello, World!".
-  void *data = wasmtime_externref_data(context, externref);
+  void *data = wasmtime_externref_data(context, &externref);
   assert(strcmp((char *)data, "Hello, World!") == 0);
 
   wasmtime_extern_t item;
@@ -124,9 +125,9 @@ int main() {
     ok = wasmtime_table_get(context, &item.of.table, 3, &elem);
     assert(ok);
     assert(elem.kind == WASMTIME_EXTERNREF);
-    assert(strcmp((char *)wasmtime_externref_data(context, elem.of.externref),
+    assert(strcmp((char *)wasmtime_externref_data(context, &elem.of.externref),
                   "Hello, World!") == 0);
-    wasmtime_val_delete(context, &elem);
+    wasmtime_val_unroot(context, &elem);
   }
 
   printf("Touching `externref` global...\n");
@@ -148,9 +149,9 @@ int main() {
     wasmtime_global_get(context, &item.of.global, &global_val);
     assert(global_val.kind == WASMTIME_EXTERNREF);
     assert(strcmp((char *)wasmtime_externref_data(context,
-                                                  global_val.of.externref),
+                                                  &global_val.of.externref),
                   "Hello, World!") == 0);
-    wasmtime_val_delete(context, &global_val);
+    wasmtime_val_unroot(context, &global_val);
   }
 
   printf("Calling `externref` func...\n");
@@ -173,11 +174,11 @@ int main() {
     // our `externref`.
     assert(results[0].kind == WASMTIME_EXTERNREF);
     assert(strcmp((char *)wasmtime_externref_data(context,
-                                                  results[0].of.externref),
+                                                  &results[0].of.externref),
                   "Hello, World!") == 0);
-    wasmtime_val_delete(context, &results[0]);
+    wasmtime_val_unroot(context, &results[0]);
   }
-  wasmtime_val_delete(context, &externref_val);
+  wasmtime_val_unroot(context, &externref_val);
 
   // We can GC any now-unused references to our externref that the store is
   // holding.
diff --git a/winch/codegen/src/codegen/env.rs b/winch/codegen/src/codegen/env.rs
index 6818c30f8..a571f2b38 100644
--- a/winch/codegen/src/codegen/env.rs
+++ b/winch/codegen/src/codegen/env.rs
@@ -198,7 +198,7 @@ impl<'a, 'translation, 'data, P: PtrSize> FuncEnv<'a, 'translation, 'data, P> {
             }
             FuncType(idx) => {
                 let sig_index = self.translation.module.types[TypeIndex::from_u32(idx)];
-                let sig = &self.types[sig_index];
+                let sig = self.types[sig_index].unwrap_func();
                 BlockSig::new(control::BlockType::func(sig.clone()))
             }
         }
@@ -340,7 +340,7 @@ impl<'a, 'translation, 'data, P: PtrSize> FuncEnv<'a, 'translation, 'data, P> {
             Callee::FuncRef(idx) => {
                 let val = || {
                     let sig_index = self.translation.module.types[*idx];
-                    let ty = &self.types[sig_index];
+                    let ty = self.types[sig_index].unwrap_func();
                     let sig = wasm_sig::<A>(ty);
                     sig
                 };


Test patch:
diff --git a/tests/all/externals.rs b/tests/all/externals.rs
index be89ef934..d6e5878a8 100644
--- a/tests/all/externals.rs
+++ b/tests/all/externals.rs
@@ -433,7 +433,7 @@ fn dummy_funcs_and_subtypes(
         None,
         Some(ValType::Ref(RefType::new(
             true,
-            HeapType::Concrete(FuncType::new(&engine, None, None)),
+            HeapType::ConcreteFunc(FuncType::new(&engine, None, None)),
         ))),
     );
     let b = Func::new(
diff --git a/tests/all/func.rs b/tests/all/func.rs
index 30cebd6d4..ff69e172a 100644
--- a/tests/all/func.rs
+++ b/tests/all/func.rs
@@ -1773,7 +1773,10 @@ fn call_wasm_passing_subtype_func_param() -> anyhow::Result<()> {
     let h_ty = FuncType::new(
         &engine,
         None,
-        Some(ValType::Ref(RefType::new(true, HeapType::Concrete(g_ty)))),
+        Some(ValType::Ref(RefType::new(
+            true,
+            HeapType::ConcreteFunc(g_ty),
+        ))),
     );
     let h = Func::new(&mut store, h_ty, move |_caller, _params, results| {
         results[0] = Val::FuncRef(Some(g.clone()));
diff --git a/tests/all/linker.rs b/tests/all/linker.rs
index 16722af2b..222f7f38a 100644
--- a/tests/all/linker.rs
+++ b/tests/all/linker.rs
@@ -461,13 +461,13 @@ fn linker_instantiate_with_concrete_func_refs() -> Result<()> {
     )?;
 
     let a = FuncType::new(&engine, None, Some(ValType::I32));
-    let ref_null_a = ValType::from(RefType::new(true, HeapType::Concrete(a.clone())));
+    let ref_null_a = ValType::from(RefType::new(true, HeapType::ConcreteFunc(a.clone())));
 
     let b = FuncType::new(&engine, None, Some(ref_null_a));
-    let ref_null_b = ValType::from(RefType::new(true, HeapType::Concrete(b.clone())));
+    let ref_null_b = ValType::from(RefType::new(true, HeapType::ConcreteFunc(b.clone())));
 
     let c = FuncType::new(&engine, None, Some(ref_null_b));
-    let ref_null_c = ValType::from(RefType::new(true, HeapType::Concrete(c.clone())));
+    let ref_null_c = ValType::from(RefType::new(true, HeapType::ConcreteFunc(c.clone())));
 
     let mut store = Store::new(&engine, ());
     let a_func = Func::new(&mut store, a, |_caller, _args, results| {
@@ -540,7 +540,7 @@ fn linker_defines_func_subtype() -> Result<()> {
         |_caller, _args, _results| Ok(()),
     )?;
     let nop_ty = FuncType::new(&engine, None, None);
-    let ref_null_nop = ValType::from(RefType::new(true, HeapType::Concrete(nop_ty)));
+    let ref_null_nop = ValType::from(RefType::new(true, HeapType::ConcreteFunc(nop_ty)));
     linker.func_new(
         "env",
         "h",
@@ -676,7 +676,7 @@ fn linker_defines_global_subtype_mut_err() -> Result<()> {
     // Not mutable.
     let mut linker = Linker::new(&engine);
     let nop = FuncType::new(&engine, None, None);
-    let ref_null_nop = ValType::from(RefType::new(true, HeapType::Concrete(nop)));
+    let ref_null_nop = ValType::from(RefType::new(true, HeapType::ConcreteFunc(nop)));
     let g = Global::new(
         &mut store,
         GlobalType::new(ref_null_nop, Mutability::Const),