Repository: bytecodealliance/wasmtime
Pull request #7905 (2024-02-09 18:09:20)

wasi-http: Make the host header forbidden
As the host header is required for correctness, don't allow the guest to manipulate it directly, instead allowing them to set it via the authority field on a request.
<!--
Please make sure you include the following information:

- If this work has been discussed elsewhere, please include a link to that
  conversation. If it was discussed in an issue, just mention "issue #...".

- Explain why this change is needed. If the details are in an issue already,
  this can be brief.

Our development process is documented in the Wasmtime book:
https://docs.wasmtime.dev/contributing-development-process.html

Please ensure all communication follows the code of conduct:
https://github.com/bytecodealliance/wasmtime/blob/main/CODE_OF_CONDUCT.md
-->



Patch:
diff --git a/benches/call.rs b/benches/call.rs
index fbfe96d84..b0baa0e93 100644
--- a/benches/call.rs
+++ b/benches/call.rs
@@ -302,9 +302,9 @@ fn wasm_to_host(c: &mut Criterion) {
 
         let mut untyped = Linker::new(&engine);
         untyped
-            .func_new("", "nop", FuncType::new([], []), |_, _, _| Ok(()))
+            .func_new("", "nop", FuncType::new(&engine, [], []), |_, _, _| Ok(()))
             .unwrap();
-        let ty = FuncType::new([ValType::I32, ValType::I64], [ValType::F32]);
+        let ty = FuncType::new(&engine, [ValType::I32, ValType::I64], [ValType::F32]);
         untyped
             .func_new(
                 "",
@@ -336,9 +336,9 @@ fn wasm_to_host(c: &mut Criterion) {
         unsafe {
             let mut unchecked = Linker::new(&engine);
             unchecked
-                .func_new_unchecked("", "nop", FuncType::new([], []), |_, _| Ok(()))
+                .func_new_unchecked("", "nop", FuncType::new(&engine, [], []), |_, _| Ok(()))
                 .unwrap();
-            let ty = FuncType::new([ValType::I32, ValType::I64], [ValType::F32]);
+            let ty = FuncType::new(&engine, [ValType::I32, ValType::I64], [ValType::F32]);
             unchecked
                 .func_new_unchecked("", "nop-params-and-results", ty, |mut caller, space| {
                     match Val::from_raw(&mut caller, space[0], ValType::I32) {
diff --git a/benches/trap.rs b/benches/trap.rs
index 979f30676..a006c871b 100644
--- a/benches/trap.rs
+++ b/benches/trap.rs
@@ -187,7 +187,7 @@ fn bench_host_wasm_frames_traps(c: &mut Criterion) {
                     let mut store = Store::new(&engine, ());
                     let host_func = Func::new(
                         &mut store,
-                        FuncType::new(vec![ValType::I32], vec![]),
+                        FuncType::new(&engine, vec![ValType::I32], vec![]),
                         |mut caller, args, _results| {
                             let f = caller.get_export("f").unwrap();
                             let f = f.into_func().unwrap();
diff --git a/cranelift/codegen/src/ir/memflags.rs b/cranelift/codegen/src/ir/memflags.rs
index 546e11fa3..72d09dae3 100644
--- a/cranelift/codegen/src/ir/memflags.rs
+++ b/cranelift/codegen/src/ir/memflags.rs
@@ -67,27 +67,25 @@ pub struct MemFlags {
 
 impl MemFlags {
     /// Create a new empty set of flags.
-    pub fn new() -> Self {
+    pub const fn new() -> Self {
         Self { bits: 0 }
     }
 
     /// Create a set of flags representing an access from a "trusted" address, meaning it's
     /// known to be aligned and non-trapping.
-    pub fn trusted() -> Self {
-        let mut result = Self::new();
-        result.set_notrap();
-        result.set_aligned();
-        result
+    pub const fn trusted() -> Self {
+        Self::new().with_notrap().with_aligned()
     }
 
     /// Read a flag bit.
-    fn read(self, bit: FlagBit) -> bool {
+    const fn read(self, bit: FlagBit) -> bool {
         self.bits & (1 << bit as usize) != 0
     }
 
-    /// Set a flag bit.
-    fn set(&mut self, bit: FlagBit) {
-        self.bits |= 1 << bit as usize
+    /// Return a new `MemFlags` with this flag bit set.
+    const fn with(mut self, bit: FlagBit) -> Self {
+        self.bits |= 1 << bit as usize;
+        self
     }
 
     /// Set a flag bit by name.
@@ -116,7 +114,7 @@ impl MemFlags {
     /// endianness otherwise.  The native endianness has to be provided by the
     /// caller since it is not explicitly encoded in CLIF IR -- this allows a
     /// front end to create IR without having to know the target endianness.
-    pub fn endianness(self, native_endianness: Endianness) -> Endianness {
+    pub const fn endianness(self, native_endianness: Endianness) -> Endianness {
         if self.read(FlagBit::LittleEndian) {
             Endianness::Little
         } else if self.read(FlagBit::BigEndian) {
@@ -128,17 +126,17 @@ impl MemFlags {
 
     /// Set endianness of the memory access.
     pub fn set_endianness(&mut self, endianness: Endianness) {
-        match endianness {
-            Endianness::Little => self.set(FlagBit::LittleEndian),
-            Endianness::Big => self.set(FlagBit::BigEndian),
-        };
-        assert!(!(self.read(FlagBit::LittleEndian) && self.read(FlagBit::BigEndian)));
+        *self = self.with_endianness(endianness);
     }
 
     /// Set endianness of the memory access, returning new flags.
-    pub fn with_endianness(mut self, endianness: Endianness) -> Self {
-        self.set_endianness(endianness);
-        self
+    pub const fn with_endianness(self, endianness: Endianness) -> Self {
+        let res = match endianness {
+            Endianness::Little => self.with(FlagBit::LittleEndian),
+            Endianness::Big => self.with(FlagBit::BigEndian),
+        };
+        assert!(!(res.read(FlagBit::LittleEndian) && res.read(FlagBit::BigEndian)));
+        res
     }
 
     /// Test if the `notrap` flag is set.
@@ -150,19 +148,18 @@ impl MemFlags {
     /// The `notrap` flag tells Cranelift that the memory is *accessible*, which means that
     /// accesses will not trap. This makes it possible to delete an unused load or a dead store
     /// instruction.
-    pub fn notrap(self) -> bool {
+    pub const fn notrap(self) -> bool {
         self.read(FlagBit::Notrap)
     }
 
     /// Set the `notrap` flag.
     pub fn set_notrap(&mut self) {
-        self.set(FlagBit::Notrap)
+        *self = self.with_notrap();
     }
 
     /// Set the `notrap` flag, returning new flags.
-    pub fn with_notrap(mut self) -> Self {
-        self.set_notrap();
-        self
+    pub const fn with_notrap(self) -> Self {
+        self.with(FlagBit::Notrap)
     }
 
     /// Test if the `aligned` flag is set.
@@ -170,19 +167,18 @@ impl MemFlags {
     /// By default, Cranelift memory instructions work with any unaligned effective address. If the
     /// `aligned` flag is set, the instruction is permitted to trap or return a wrong result if the
     /// effective address is misaligned.
-    pub fn aligned(self) -> bool {
+    pub const fn aligned(self) -> bool {
         self.read(FlagBit::Aligned)
     }
 
     /// Set the `aligned` flag.
     pub fn set_aligned(&mut self) {
-        self.set(FlagBit::Aligned)
+        *self = self.with_aligned();
     }
 
     /// Set the `aligned` flag, returning new flags.
-    pub fn with_aligned(mut self) -> Self {
-        self.set_aligned();
-        self
+    pub const fn with_aligned(self) -> Self {
+        self.with(FlagBit::Aligned)
     }
 
     /// Test if the `readonly` flag is set.
@@ -190,19 +186,18 @@ impl MemFlags {
     /// Loads with this flag have no memory dependencies.
     /// This results in undefined behavior if the dereferenced memory is mutated at any time
     /// between when the function is called and when it is exited.
-    pub fn readonly(self) -> bool {
+    pub const fn readonly(self) -> bool {
         self.read(FlagBit::Readonly)
     }
 
     /// Set the `readonly` flag.
     pub fn set_readonly(&mut self) {
-        self.set(FlagBit::Readonly)
+        *self = self.with_readonly();
     }
 
     /// Set the `readonly` flag, returning new flags.
-    pub fn with_readonly(mut self) -> Self {
-        self.set_readonly();
-        self
+    pub const fn with_readonly(self) -> Self {
+        self.with(FlagBit::Readonly)
     }
 
     /// Test if the `heap` bit is set.
@@ -213,21 +208,20 @@ impl MemFlags {
     /// means that behavior is undefined if the same memory is also
     /// accessed by another load/store with one of the other
     /// alias-analysis bits (`table`, `vmctx`) set, or `heap` not set.
-    pub fn heap(self) -> bool {
+    pub const fn heap(self) -> bool {
         self.read(FlagBit::Heap)
     }
 
     /// Set the `heap` bit. See the notes about mutual exclusion with
     /// other bits in `heap()`.
     pub fn set_heap(&mut self) {
-        assert!(!self.table() && !self.vmctx());
-        self.set(FlagBit::Heap);
+        *self = self.with_heap();
     }
 
     /// Set the `heap` bit, returning new flags.
-    pub fn with_heap(mut self) -> Self {
-        self.set_heap();
-        self
+    pub const fn with_heap(self) -> Self {
+        assert!(!self.table() && !self.vmctx());
+        self.with(FlagBit::Heap)
     }
 
     /// Test if the `table` bit is set.
@@ -238,21 +232,20 @@ impl MemFlags {
     /// means that behavior is undefined if the same memory is also
     /// accessed by another load/store with one of the other
     /// alias-analysis bits (`heap`, `vmctx`) set, or `table` not set.
-    pub fn table(self) -> bool {
+    pub const fn table(self) -> bool {
         self.read(FlagBit::Table)
     }
 
     /// Set the `table` bit. See the notes about mutual exclusion with
     /// other bits in `table()`.
     pub fn set_table(&mut self) {
-        assert!(!self.heap() && !self.vmctx());
-        self.set(FlagBit::Table);
+        *self = self.with_table();
     }
 
     /// Set the `table` bit, returning new flags.
-    pub fn with_table(mut self) -> Self {
-        self.set_table();
-        self
+    pub const fn with_table(self) -> Self {
+        assert!(!self.heap() && !self.vmctx());
+        self.with(FlagBit::Table)
     }
 
     /// Test if the `vmctx` bit is set.
@@ -263,21 +256,20 @@ impl MemFlags {
     /// means that behavior is undefined if the same memory is also
     /// accessed by another load/store with one of the other
     /// alias-analysis bits (`heap`, `table`) set, or `vmctx` not set.
-    pub fn vmctx(self) -> bool {
+    pub const fn vmctx(self) -> bool {
         self.read(FlagBit::Vmctx)
     }
 
     /// Set the `vmctx` bit. See the notes about mutual exclusion with
     /// other bits in `vmctx()`.
     pub fn set_vmctx(&mut self) {
-        assert!(!self.heap() && !self.table());
-        self.set(FlagBit::Vmctx);
+        *self = self.with_vmctx();
     }
 
     /// Set the `vmctx` bit, returning new flags.
-    pub fn with_vmctx(mut self) -> Self {
-        self.set_vmctx();
-        self
+    pub const fn with_vmctx(self) -> Self {
+        assert!(!self.heap() && !self.table());
+        self.with(FlagBit::Vmctx)
     }
 
     /// Test if the `checked` bit is set.
@@ -291,19 +283,18 @@ impl MemFlags {
     /// `checked`-marked memory accesses are guaranteed (up to the
     /// checker's correctness) to access valid memory. This can be
     /// used to ensure memory safety and sandboxing.
-    pub fn checked(self) -> bool {
+    pub const fn checked(self) -> bool {
         self.read(FlagBit::Checked)
     }
 
     /// Set the `checked` bit.
     pub fn set_checked(&mut self) {
-        self.set(FlagBit::Checked);
+        *self = self.with_checked();
     }
 
     /// Set the `checked` bit, returning new flags.
-    pub fn with_checked(mut self) -> Self {
-        self.set_checked();
-        self
+    pub const fn with_checked(self) -> Self {
+        self.with(FlagBit::Checked)
     }
 }
 
diff --git a/crates/c-api/src/async.rs b/crates/c-api/src/async.rs
index 1e7e887a7..99ee3387b 100644
--- a/crates/c-api/src/async.rs
+++ b/crates/c-api/src/async.rs
@@ -271,7 +271,7 @@ pub unsafe extern "C" fn wasmtime_linker_define_async_func(
     data: *mut c_void,
     finalizer: Option<extern "C" fn(*mut std::ffi::c_void)>,
 ) -> Option<Box<wasmtime_error_t>> {
-    let ty = ty.ty().ty.clone();
+    let ty = ty.ty().ty(linker.linker.engine());
     let module = to_str!(module, module_len);
     let name = to_str!(name, name_len);
     let cb = c_async_callback_to_rust_fn(callback, data, finalizer);
diff --git a/crates/c-api/src/extern.rs b/crates/c-api/src/extern.rs
index 516e21b28..b97ba1f19 100644
--- a/crates/c-api/src/extern.rs
+++ b/crates/c-api/src/extern.rs
@@ -26,7 +26,9 @@ pub extern "C" fn wasm_extern_kind(e: &wasm_extern_t) -> wasm_externkind_t {
 
 #[no_mangle]
 pub unsafe extern "C" fn wasm_extern_type(e: &wasm_extern_t) -> Box<wasm_externtype_t> {
-    Box::new(wasm_externtype_t::new(e.which.ty(&e.store.context())))
+    Box::new(wasm_externtype_t::from_extern_type(
+        e.which.ty(&e.store.context()),
+    ))
 }
 
 #[no_mangle]
@@ -135,5 +137,5 @@ pub unsafe extern "C" fn wasmtime_extern_type(
     store: CStoreContext<'_>,
     e: &wasmtime_extern_t,
 ) -> Box<wasm_externtype_t> {
-    Box::new(wasm_externtype_t::new(e.to_extern().ty(store)))
+    Box::new(wasm_externtype_t::from_extern_type(e.to_extern().ty(store)))
 }
diff --git a/crates/c-api/src/func.rs b/crates/c-api/src/func.rs
index 8fbec441d..95f46b89a 100644
--- a/crates/c-api/src/func.rs
+++ b/crates/c-api/src/func.rs
@@ -55,7 +55,7 @@ unsafe fn create_function(
         + Sync
         + 'static,
 ) -> Box<wasm_func_t> {
-    let ty = ty.ty().ty.clone();
+    let ty = ty.ty().ty(store.store.context().engine());
     let func = Func::new(
         store.store.context_mut(),
         ty,
@@ -228,7 +228,7 @@ pub unsafe extern "C" fn wasmtime_func_new(
     finalizer: Option<extern "C" fn(*mut std::ffi::c_void)>,
     func: &mut Func,
 ) {
-    let ty = ty.ty().ty.clone();
+    let ty = ty.ty().ty(store.engine());
     let cb = c_callback_to_rust_fn(callback, data, finalizer);
     let f = Func::new(store, ty, cb);
     *func = f;
@@ -293,7 +293,7 @@ pub unsafe extern "C" fn wasmtime_func_new_unchecked(
     finalizer: Option<extern "C" fn(*mut std::ffi::c_void)>,
     func: &mut Func,
 ) {
-    let ty = ty.ty().ty.clone();
+    let ty = ty.ty().ty(store.engine());
     let cb = c_unchecked_callback_to_rust_fn(callback, data, finalizer);
     *func = Func::new_unchecked(store, ty, cb);
 }
diff --git a/crates/c-api/src/linker.rs b/crates/c-api/src/linker.rs
index 7cf9391d8..6ea36fa05 100644
--- a/crates/c-api/src/linker.rs
+++ b/crates/c-api/src/linker.rs
@@ -69,7 +69,7 @@ pub unsafe extern "C" fn wasmtime_linker_define_func(
     data: *mut c_void,
     finalizer: Option<extern "C" fn(*mut std::ffi::c_void)>,
 ) -> Option<Box<wasmtime_error_t>> {
-    let ty = ty.ty().ty.clone();
+    let ty = ty.ty().ty(linker.linker.engine());
     let module = to_str!(module, module_len);
     let name = to_str!(name, name_len);
     let cb = crate::func::c_callback_to_rust_fn(callback, data, finalizer);
@@ -88,7 +88,7 @@ pub unsafe extern "C" fn wasmtime_linker_define_func_unchecked(
     data: *mut c_void,
     finalizer: Option<extern "C" fn(*mut std::ffi::c_void)>,
 ) -> Option<Box<wasmtime_error_t>> {
-    let ty = ty.ty().ty.clone();
+    let ty = ty.ty().ty(linker.linker.engine());
     let module = to_str!(module, module_len);
     let name = to_str!(name, name_len);
     let cb = crate::func::c_unchecked_callback_to_rust_fn(callback, data, finalizer);
diff --git a/crates/c-api/src/module.rs b/crates/c-api/src/module.rs
index 3a1a6e666..537bcfd73 100644
--- a/crates/c-api/src/module.rs
+++ b/crates/c-api/src/module.rs
@@ -1,6 +1,6 @@
 use crate::{
     handle_result, wasm_byte_vec_t, wasm_engine_t, wasm_exporttype_t, wasm_exporttype_vec_t,
-    wasm_importtype_t, wasm_importtype_vec_t, wasm_store_t, wasmtime_error_t,
+    wasm_importtype_t, wasm_importtype_vec_t, wasm_store_t, wasmtime_error_t, CExternType,
 };
 use anyhow::Context;
 use std::ffi::CStr;
@@ -53,7 +53,7 @@ fn fill_exports(module: &Module, out: &mut wasm_exporttype_vec_t) {
         .map(|e| {
             Some(Box::new(wasm_exporttype_t::new(
                 e.name().to_owned(),
-                e.ty(),
+                CExternType::new(e.ty()),
             )))
         })
         .collect::<Vec<_>>();
@@ -67,7 +67,7 @@ fn fill_imports(module: &Module, out: &mut wasm_importtype_vec_t) {
             Some(Box::new(wasm_importtype_t::new(
                 i.module().to_owned(),
                 i.name().to_owned(),
-                i.ty(),
+                CExternType::new(i.ty()),
             )))
         })
         .collect::<Vec<_>>();
diff --git a/crates/c-api/src/types/export.rs b/crates/c-api/src/types/export.rs
index 3d83cc41f..2eca463d8 100644
--- a/crates/c-api/src/types/export.rs
+++ b/crates/c-api/src/types/export.rs
@@ -1,12 +1,11 @@
-use crate::{wasm_externtype_t, wasm_name_t};
+use crate::{wasm_externtype_t, wasm_name_t, CExternType};
 use once_cell::unsync::OnceCell;
-use wasmtime::ExternType;
 
 #[repr(C)]
 #[derive(Clone)]
 pub struct wasm_exporttype_t {
     name: String,
-    ty: ExternType,
+    ty: CExternType,
     name_cache: OnceCell<wasm_name_t>,
     type_cache: OnceCell<wasm_externtype_t>,
 }
@@ -14,7 +13,7 @@ pub struct wasm_exporttype_t {
 wasmtime_c_api_macros::declare_ty!(wasm_exporttype_t);
 
 impl wasm_exporttype_t {
-    pub(crate) fn new(name: String, ty: ExternType) -> wasm_exporttype_t {
+    pub(crate) fn new(name: String, ty: CExternType) -> wasm_exporttype_t {
         wasm_exporttype_t {
             name,
             ty,
@@ -31,7 +30,7 @@ pub extern "C" fn wasm_exporttype_new(
 ) -> Option<Box<wasm_exporttype_t>> {
     let name = name.take();
     let name = String::from_utf8(name).ok()?;
-    Some(Box::new(wasm_exporttype_t::new(name, ty.ty())))
+    Some(Box::new(wasm_exporttype_t::new(name, ty.which.clone())))
 }
 
 #[no_mangle]
@@ -43,5 +42,5 @@ pub extern "C" fn wasm_exporttype_name(et: &wasm_exporttype_t) -> &wasm_name_t {
 #[no_mangle]
 pub extern "C" fn wasm_exporttype_type(et: &wasm_exporttype_t) -> &wasm_externtype_t {
     et.type_cache
-        .get_or_init(|| wasm_externtype_t::new(et.ty.clone()))
+        .get_or_init(|| wasm_externtype_t::from_cextern_type(et.ty.clone()))
 }
diff --git a/crates/c-api/src/types/extern.rs b/crates/c-api/src/types/extern.rs
index 21278a25e..c85dcbb25 100644
--- a/crates/c-api/src/types/extern.rs
+++ b/crates/c-api/src/types/extern.rs
@@ -18,6 +18,17 @@ pub(crate) enum CExternType {
     Table(CTableType),
 }
 
+impl CExternType {
+    pub(crate) fn new(ty: ExternType) -> CExternType {
+        match ty {
+            ExternType::Func(f) => CExternType::Func(CFuncType::new(f)),
+            ExternType::Global(f) => CExternType::Global(CGlobalType::new(f)),
+            ExternType::Memory(f) => CExternType::Memory(CMemoryType::new(f)),
+            ExternType::Table(f) => CExternType::Table(CTableType::new(f)),
+        }
+    }
+}
+
 pub type wasm_externkind_t = u8;
 
 pub const WASM_EXTERN_FUNC: wasm_externkind_t = 0;
@@ -26,24 +37,14 @@ pub const WASM_EXTERN_TABLE: wasm_externkind_t = 2;
 pub const WASM_EXTERN_MEMORY: wasm_externkind_t = 3;
 
 impl wasm_externtype_t {
-    pub(crate) fn new(ty: ExternType) -> wasm_externtype_t {
+    pub(crate) fn from_extern_type(ty: ExternType) -> wasm_externtype_t {
         wasm_externtype_t {
-            which: match ty {
-                ExternType::Func(f) => CExternType::Func(CFuncType::new(f)),
-                ExternType::Global(f) => CExternType::Global(CGlobalType::new(f)),
-                ExternType::Memory(f) => CExternType::Memory(CMemoryType::new(f)),
-                ExternType::Table(f) => CExternType::Table(CTableType::new(f)),
-            },
+            which: CExternType::new(ty),
         }
     }
 
-    pub(crate) fn ty(&self) -> ExternType {
-        match &self.which {
-            CExternType::Func(f) => ExternType::Func(f.ty.clone()),
-            CExternType::Table(f) => ExternType::Table(f.ty.clone()),
-            CExternType::Global(f) => ExternType::Global(f.ty.clone()),
-            CExternType::Memory(f) => ExternType::Memory(f.ty.clone()),
-        }
+    pub(crate) fn from_cextern_type(ty: CExternType) -> wasm_externtype_t {
+        wasm_externtype_t { which: ty }
     }
 }
 
diff --git a/crates/c-api/src/types/func.rs b/crates/c-api/src/types/func.rs
index 1b4ca752f..c2158902b 100644
--- a/crates/c-api/src/types/func.rs
+++ b/crates/c-api/src/types/func.rs
@@ -1,6 +1,10 @@
 use crate::{wasm_externtype_t, wasm_valtype_t, wasm_valtype_vec_t, CExternType};
 use once_cell::unsync::OnceCell;
-use wasmtime::FuncType;
+use std::{
+    mem,
+    sync::{Arc, Mutex},
+};
+use wasmtime::{Engine, FuncType, ValType};
 
 #[repr(transparent)]
 #[derive(Clone)]
@@ -10,9 +14,75 @@ pub struct wasm_functype_t {
 
 wasmtime_c_api_macros::declare_ty!(wasm_functype_t);
 
+#[derive(Clone)]
+enum LazyFuncType {
+    Lazy {
+        params: Vec<ValType>,
+        results: Vec<ValType>,
+    },
+    FuncType(FuncType),
+}
+
+impl LazyFuncType {
+    pub(crate) fn force(&mut self, engine: &Engine) -> FuncType {
+        match self {
+            LazyFuncType::FuncType(ty) => ty.clone(),
+            LazyFuncType::Lazy { params, results } => {
+                let params = mem::take(params);
+                let results = mem::take(results);
+                let ty = FuncType::new(engine, params, results);
+                *self = LazyFuncType::FuncType(ty.clone());
+                ty
+            }
+        }
+    }
+
+    fn params(&self) -> impl ExactSizeIterator<Item = ValType> + '_ {
+        match self {
+            LazyFuncType::Lazy { params, .. } => LazyFuncTypeIter::Lazy(params.iter()),
+            LazyFuncType::FuncType(f) => LazyFuncTypeIter::FuncType(f.params()),
+        }
+    }
+
+    fn results(&self) -> impl ExactSizeIterator<Item = ValType> + '_ {
+        match self {
+            LazyFuncType::Lazy { results, .. } => LazyFuncTypeIter::Lazy(results.iter()),
+            LazyFuncType::FuncType(f) => LazyFuncTypeIter::FuncType(f.results()),
+        }
+    }
+}
+
+enum LazyFuncTypeIter<'a, T> {
+    Lazy(std::slice::Iter<'a, ValType>),
+    FuncType(T),
+}
+
+impl<'a, T> Iterator for LazyFuncTypeIter<'a, T>
+where
+    T: Iterator<Item = ValType>,
+{
+    type Item = ValType;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        match self {
+            LazyFuncTypeIter::FuncType(i) => i.next(),
+            LazyFuncTypeIter::Lazy(i) => i.next().cloned(),
+        }
+    }
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        match self {
+            LazyFuncTypeIter::FuncType(i) => i.size_hint(),
+            LazyFuncTypeIter::Lazy(i) => i.size_hint(),
+        }
+    }
+}
+
+impl<'a, T> ExactSizeIterator for LazyFuncTypeIter<'a, T> where T: ExactSizeIterator<Item = ValType> {}
+
 #[derive(Clone)]
 pub(crate) struct CFuncType {
-    pub(crate) ty: FuncType,
+    ty: Arc<Mutex<LazyFuncType>>,
     params_cache: OnceCell<wasm_valtype_vec_t>,
     returns_cache: OnceCell<wasm_valtype_vec_t>,
 }
@@ -20,7 +90,15 @@ pub(crate) struct CFuncType {
 impl wasm_functype_t {
     pub(crate) fn new(ty: FuncType) -> wasm_functype_t {
         wasm_functype_t {
-            ext: wasm_externtype_t::new(ty.into()),
+            ext: wasm_externtype_t::from_extern_type(ty.into()),
+        }
+    }
+
+    pub(crate) fn lazy(params: Vec<ValType>, results: Vec<ValType>) -> wasm_functype_t {
+        wasm_functype_t {
+            ext: wasm_externtype_t::from_cextern_type(CExternType::Func(CFuncType::lazy(
+                params, results,
+            ))),
         }
     }
 
@@ -42,11 +120,24 @@ impl wasm_functype_t {
 impl CFuncType {
     pub(crate) fn new(ty: FuncType) -> CFuncType {
         CFuncType {
-            ty,
+            ty: Arc::new(Mutex::new(LazyFuncType::FuncType(ty))),
             params_cache: OnceCell::new(),
             returns_cache: OnceCell::new(),
         }
     }
+
+    pub(crate) fn lazy(params: Vec<ValType>, results: Vec<ValType>) -> CFuncType {
+        CFuncType {
+            ty: Arc::new(Mutex::new(LazyFuncType::Lazy { params, results })),
+            params_cache: OnceCell::new(),
+            returns_cache: OnceCell::new(),
+        }
+    }
+
+    pub(crate) fn ty(&self, engine: &Engine) -> FuncType {
+        let mut ty = self.ty.lock().unwrap();
+        ty.force(engine)
+    }
 }
 
 #[no_mangle]
@@ -54,18 +145,25 @@ pub extern "C" fn wasm_functype_new(
     params: &mut wasm_valtype_vec_t,
     results: &mut wasm_valtype_vec_t,
 ) -> Box<wasm_functype_t> {
-    let params = params.take().into_iter().map(|vt| vt.unwrap().ty.clone());
-    let results = results.take().into_iter().map(|vt| vt.unwrap().ty.clone());
-    let functype = FuncType::new(params, results);
-    Box::new(wasm_functype_t::new(functype))
+    let params = params
+        .take()
+        .into_iter()
+        .map(|vt| vt.unwrap().ty.clone())
+        .collect();
+    let results = results
+        .take()
+        .into_iter()
+        .map(|vt| vt.unwrap().ty.clone())
+        .collect();
+    Box::new(wasm_functype_t::lazy(params, results))
 }
 
 #[no_mangle]
 pub extern "C" fn wasm_functype_params(ft: &wasm_functype_t) -> &wasm_valtype_vec_t {
     let ft = ft.ty();
     ft.params_cache.get_or_init(|| {
-        ft.ty
-            .params()
+        let ty = ft.ty.lock().unwrap();
+        ty.params()
             .map(|p| Some(Box::new(wasm_valtype_t { ty: p.clone() })))
             .collect::<Vec<_>>()
             .into()
@@ -76,8 +174,8 @@ pub extern "C" fn wasm_functype_params(ft: &wasm_functype_t) -> &wasm_valtype_ve
 pub extern "C" fn wasm_functype_results(ft: &wasm_functype_t) -> &wasm_valtype_vec_t {
     let ft = ft.ty();
     ft.returns_cache.get_or_init(|| {
-        ft.ty
-            .results()
+        let ty = ft.ty.lock().unwrap();
+        ty.results()
             .map(|p| Some(Box::new(wasm_valtype_t { ty: p.clone() })))
             .collect::<Vec<_>>()
             .into()
diff --git a/crates/c-api/src/types/global.rs b/crates/c-api/src/types/global.rs
index 9fde1f304..176c41865 100644
--- a/crates/c-api/src/types/global.rs
+++ b/crates/c-api/src/types/global.rs
@@ -24,7 +24,7 @@ pub(crate) struct CGlobalType {
 impl wasm_globaltype_t {
     pub(crate) fn new(ty: GlobalType) -> wasm_globaltype_t {
         wasm_globaltype_t {
-            ext: wasm_externtype_t::new(ty.into()),
+            ext: wasm_externtype_t::from_extern_type(ty.into()),
         }
     }
 
diff --git a/crates/c-api/src/types/import.rs b/crates/c-api/src/types/import.rs
index f9e97a287..9484ab528 100644
--- a/crates/c-api/src/types/import.rs
+++ b/crates/c-api/src/types/import.rs
@@ -1,13 +1,12 @@
-use crate::{wasm_externtype_t, wasm_name_t};
+use crate::{wasm_externtype_t, wasm_name_t, CExternType};
 use once_cell::unsync::OnceCell;
-use wasmtime::ExternType;
 
 #[repr(C)]
 #[derive(Clone)]
 pub struct wasm_importtype_t {
     pub(crate) module: String,
     pub(crate) name: String,
-    pub(crate) ty: ExternType,
+    pub(crate) ty: CExternType,
     module_cache: OnceCell<wasm_name_t>,
     name_cache: OnceCell<wasm_name_t>,
     type_cache: OnceCell<wasm_externtype_t>,
@@ -16,7 +15,7 @@ pub struct wasm_importtype_t {
 wasmtime_c_api_macros::declare_ty!(wasm_importtype_t);
 
 impl wasm_importtype_t {
-    pub(crate) fn new(module: String, name: String, ty: ExternType) -> wasm_importtype_t {
+    pub(crate) fn new(module: String, name: String, ty: CExternType) -> wasm_importtype_t {
         wasm_importtype_t {
             module,
             name,
@@ -38,7 +37,11 @@ pub extern "C" fn wasm_importtype_new(
     let name = name.take();
     let module = String::from_utf8(module).ok()?;
     let name = String::from_utf8(name).ok()?;
-    Some(Box::new(wasm_importtype_t::new(module, name, ty.ty())))
+    Some(Box::new(wasm_importtype_t::new(
+        module,
+        name,
+        ty.which.clone(),
+    )))
 }
 
 #[no_mangle]
@@ -56,5 +59,5 @@ pub extern "C" fn wasm_importtype_name(it: &wasm_importtype_t) -> &wasm_name_t {
 #[no_mangle]
 pub extern "C" fn wasm_importtype_type(it: &wasm_importtype_t) -> &wasm_externtype_t {
     it.type_cache
-        .get_or_init(|| wasm_externtype_t::new(it.ty.clone()))
+        .get_or_init(|| wasm_externtype_t::from_cextern_type(it.ty.clone()))
 }
diff --git a/crates/c-api/src/types/memory.rs b/crates/c-api/src/types/memory.rs
index 45f9a5c19..034bea862 100644
--- a/crates/c-api/src/types/memory.rs
+++ b/crates/c-api/src/types/memory.rs
@@ -20,7 +20,7 @@ pub(crate) struct CMemoryType {
 impl wasm_memorytype_t {
     pub(crate) fn new(ty: MemoryType) -> wasm_memorytype_t {
         wasm_memorytype_t {
-            ext: wasm_externtype_t::new(ty.into()),
+            ext: wasm_externtype_t::from_extern_type(ty.into()),
         }
     }
 
diff --git a/crates/c-api/src/types/table.rs b/crates/c-api/src/types/table.rs
index 6599baecd..dd8ac0976 100644
--- a/crates/c-api/src/types/table.rs
+++ b/crates/c-api/src/types/table.rs
@@ -20,7 +20,7 @@ pub(crate) struct CTableType {
 impl wasm_tabletype_t {
     pub(crate) fn new(ty: TableType) -> wasm_tabletype_t {
         wasm_tabletype_t {
-            ext: wasm_externtype_t::new(ty.into()),
+            ext: wasm_externtype_t::from_extern_type(ty.into()),
         }
     }
 
diff --git a/crates/fuzzing/src/oracles.rs b/crates/fuzzing/src/oracles.rs
index 844654b4e..8831865d9 100644
--- a/crates/fuzzing/src/oracles.rs
+++ b/crates/fuzzing/src/oracles.rs
@@ -616,36 +616,34 @@ pub fn table_ops(
         // NB: use `Func::new` so that this can still compile on the old x86
         // backend, where `IntoFunc` isn't implemented for multi-value
         // returns.
-        let func = Func::new(
-            &mut store,
-            FuncType::new(
-                vec![],
-                vec![ValType::ExternRef, ValType::ExternRef, ValType::ExternRef],
-            ),
-            {
-                let num_dropped = num_dropped.clone();
-                let expected_drops = expected_drops.clone();
-                let num_gcs = num_gcs.clone();
-                move |mut caller: Caller<'_, StoreLimits>, _params, results| {
-                    log::info!("table_ops: GC");
-                    if num_gcs.fetch_add(1, SeqCst) < MAX_GCS {
-                        caller.gc();
-                    }
+        let func_ty = FuncType::new(
+            store.engine(),
+            vec![],
+            vec![ValType::ExternRef, ValType::ExternRef, ValType::ExternRef],
+        );
+        let func = Func::new(&mut store, func_ty, {
+            let num_dropped = num_dropped.clone();
+            let expected_drops = expected_drops.clone();
+            let num_gcs = num_gcs.clone();
+            move |mut caller: Caller<'_, StoreLimits>, _params, results| {
+                log::info!("table_ops: GC");
+                if num_gcs.fetch_add(1, SeqCst) < MAX_GCS {
+                    caller.gc();
+                }
 
-                    let a = ExternRef::new(CountDrops(num_dropped.clone()));
-                    let b = ExternRef::new(CountDrops(num_dropped.clone()));
-                    let c = ExternRef::new(CountDrops(num_dropped.clone()));
+                let a = ExternRef::new(CountDrops(num_dropped.clone()));
+                let b = ExternRef::new(CountDrops(num_dropped.clone()));
+                let c = ExternRef::new(CountDrops(num_dropped.clone()));
 
-                    log::info!("table_ops: make_refs() -> ({:p}, {:p}, {:p})", a, b, c);
+                log::info!("table_ops: make_refs() -> ({:p}, {:p}, {:p})", a, b, c);
 
-                    expected_drops.fetch_add(3, SeqCst);
-                    results[0] = Some(a).into();
-                    results[1] = Some(b).into();
-                    results[2] = Some(c).into();
-                    Ok(())
-                }
-            },
-        );
+                expected_drops.fetch_add(3, SeqCst);
+                results[0] = Some(a).into();
+                results[1] = Some(b).into();
+                results[2] = Some(c).into();
+                Ok(())
+            }
+        });
         linker.define(&store, "", "gc", func).unwrap();
 
         linker
@@ -691,25 +689,23 @@ pub fn table_ops(
         // NB: use `Func::new` so that this can still compile on the old
         // x86 backend, where `IntoFunc` isn't implemented for
         // multi-value returns.
-        let func = Func::new(
-            &mut store,
-            FuncType::new(
-                vec![],
-                vec![ValType::ExternRef, ValType::ExternRef, ValType::ExternRef],
-            ),
-            {
-                let num_dropped = num_dropped.clone();
-                let expected_drops = expected_drops.clone();
-                move |_caller, _params, results| {
-                    log::info!("table_ops: make_refs");
-                    expected_drops.fetch_add(3, SeqCst);
-                    results[0] = Some(ExternRef::new(CountDrops(num_dropped.clone()))).into();
-                    results[1] = Some(ExternRef::new(CountDrops(num_dropped.clone()))).into();
-                    results[2] = Some(ExternRef::new(CountDrops(num_dropped.clone()))).into();
-                    Ok(())
-                }
-            },
+        let func_ty = FuncType::new(
+            store.engine(),
+            vec![],
+            vec![ValType::ExternRef, ValType::ExternRef, ValType::ExternRef],
         );
+        let func = Func::new(&mut store, func_ty, {
+            let num_dropped = num_dropped.clone();
+            let expected_drops = expected_drops.clone();
+            move |_caller, _params, results| {
+                log::info!("table_ops: make_refs");
+                expected_drops.fetch_add(3, SeqCst);
+                results[0] = Some(ExternRef::new(CountDrops(num_dropped.clone()))).into();
+                results[1] = Some(ExternRef::new(CountDrops(num_dropped.clone()))).into();
+                results[2] = Some(ExternRef::new(CountDrops(num_dropped.clone()))).into();
+                Ok(())
+            }
+        });
         linker.define(&store, "", "make_refs", func).unwrap();
 
         let instance = linker.instantiate(&mut store, &module).unwrap();
diff --git a/crates/fuzzing/src/oracles/dummy.rs b/crates/fuzzing/src/oracles/dummy.rs
index 69350693a..ab3c7dc65 100644
--- a/crates/fuzzing/src/oracles/dummy.rs
+++ b/crates/fuzzing/src/oracles/dummy.rs
@@ -114,7 +114,7 @@ mod tests {
     #[test]
     fn dummy_function_import() {
         let mut store = store();
-        let func_ty = FuncType::new(vec![ValType::I32], vec![ValType::I64]);
+        let func_ty = FuncType::new(store.engine(), vec![ValType::I32], vec![ValType::I64]);
         let func = dummy_func(&mut store, func_ty.clone());
         assert_eq!(func.ty(&store), func_ty);
     }
diff --git a/crates/wasi-http/src/types.rs b/crates/wasi-http/src/types.rs
index 428116dd8..3d8c4ef22 100644
--- a/crates/wasi-http/src/types.rs
+++ b/crates/wasi-http/src/types.rs
@@ -77,7 +77,7 @@ pub trait WasiHttpView: Send {
 
 /// Returns `true` when the header is forbidden according to this [`WasiHttpView`] implementation.
 pub(crate) fn is_forbidden_header(view: &mut dyn WasiHttpView, name: &HeaderName) -> bool {
-    static FORBIDDEN_HEADERS: [HeaderName; 9] = [
+    static FORBIDDEN_HEADERS: [HeaderName; 10] = [
         hyper::header::CONNECTION,
         HeaderName::from_static("keep-alive"),
         hyper::header::PROXY_AUTHENTICATE,
@@ -86,6 +86,7 @@ pub(crate) fn is_forbidden_header(view: &mut dyn WasiHttpView, name: &HeaderName
         hyper::header::TE,
         hyper::header::TRANSFER_ENCODING,
         hyper::header::UPGRADE,
+        hyper::header::HOST,
         HeaderName::from_static("http2-settings"),
     ];
 
diff --git a/crates/wasmtime/src/runtime/component/types.rs b/crates/wasmtime/src/runtime/component/types.rs
index 39d6b176d..dd29efbed 100644
--- a/crates/wasmtime/src/runtime/component/types.rs
+++ b/crates/wasmtime/src/runtime/component/types.rs
@@ -2,7 +2,7 @@
 
 use crate::component::matching::InstanceType;
 use crate::component::values::{self, Val};
-use crate::{ExternType, FuncType};
+use crate::{Engine, ExternType, FuncType};
 use anyhow::{anyhow, Result};
 use std::fmt;
 use std::mem;
@@ -799,24 +799,30 @@ impl Module {
     }
 
     /// Iterates over imports of the module
-    pub fn imports(&self) -> impl ExactSizeIterator<Item = ((&str, &str), ExternType)> {
+    pub fn imports<'a>(
+        &'a self,
+        engine: &'a Engine,
+    ) -> impl ExactSizeIterator<Item = ((&str, &str), ExternType)> + 'a {
         self.0.types[self.0.index]
             .imports
             .iter()
             .map(|((namespace, name), ty)| {
                 (
                     (namespace.as_str(), name.as_str()),
-                    ExternType::from_wasmtime(self.0.types.module_types(), ty),
+                    ExternType::from_wasmtime(engine, self.0.types.module_types(), ty),
                 )
             })
     }
 
     /// Iterates over exports of the module
-    pub fn exports(&self) -> impl ExactSizeIterator<Item = (&str, ExternType)> {
+    pub fn exports<'a>(
+        &'a self,
+        engine: &'a Engine,
+    ) -> impl ExactSizeIterator<Item = (&str, ExternType)> + 'a {
         self.0.types[self.0.index].exports.iter().map(|(name, ty)| {
             (
                 name.as_str(),
-                ExternType::from_wasmtime(self.0.types.module_types(), ty),
+                ExternType::from_wasmtime(engine, self.0.types.module_types(), ty),
             )
         })
     }
@@ -832,35 +838,45 @@ impl Component {
     }
 
     /// Returns import associated with `name`, if such exists in the component
-    pub fn get_import(&self, name: &str) -> Option<ComponentItem> {
+    pub fn get_import(&self, engine: &Engine, name: &str) -> Option<ComponentItem> {
         self.0.types[self.0.index]
             .imports
             .get(name)
-            .map(|ty| ComponentItem::from(ty, &self.0.instance()))
+            .map(|ty| ComponentItem::from(engine, ty, &self.0.instance()))
     }
 
     /// Iterates over imports of the component
-    pub fn imports(&self) -> impl ExactSizeIterator<Item = (&str, ComponentItem)> {
-        self.0.types[self.0.index]
-            .imports
-            .iter()
-            .map(|(name, ty)| (name.as_str(), ComponentItem::from(ty, &self.0.instance())))
+    pub fn imports<'a>(
+        &'a self,
+        engine: &'a Engine,
+    ) -> impl ExactSizeIterator<Item = (&str, ComponentItem)> + 'a {
+        self.0.types[self.0.index].imports.iter().map(|(name, ty)| {
+            (
+                name.as_str(),
+                ComponentItem::from(engine, ty, &self.0.instance()),
+            )
+        })
     }
 
     /// Returns export associated with `name`, if such exists in the component
-    pub fn get_export(&self, name: &str) -> Option<ComponentItem> {
+    pub fn get_export(&self, engine: &Engine, name: &str) -> Option<ComponentItem> {
         self.0.types[self.0.index]
             .exports
             .get(name)
-            .map(|ty| ComponentItem::from(ty, &self.0.instance()))
+            .map(|ty| ComponentItem::from(engine, ty, &self.0.instance()))
     }
 
     /// Iterates over exports of the component
-    pub fn exports(&self) -> impl ExactSizeIterator<Item = (&str, ComponentItem)> {
-        self.0.types[self.0.index]
-            .exports
-            .iter()
-            .map(|(name, ty)| (name.as_str(), ComponentItem::from(ty, &self.0.instance())))
+    pub fn exports<'a>(
+        &'a self,
+        engine: &'a Engine,
+    ) -> impl ExactSizeIterator<Item = (&str, ComponentItem)> + 'a {
+        self.0.types[self.0.index].exports.iter().map(|(name, ty)| {
+            (
+                name.as_str(),
+                ComponentItem::from(engine, ty, &self.0.instance()),
+            )
+        })
     }
 }
 
@@ -874,19 +890,24 @@ impl ComponentInstance {
     }
 
     /// Returns export associated with `name`, if such exists in the component instance
-    pub fn get_export(&self, name: &str) -> Option<ComponentItem> {
+    pub fn get_export(&self, engine: &Engine, name: &str) -> Option<ComponentItem> {
         self.0.types[self.0.index]
             .exports
             .get(name)
-            .map(|ty| ComponentItem::from(ty, &self.0.instance()))
+            .map(|ty| ComponentItem::from(engine, ty, &self.0.instance()))
     }
 
     /// Iterates over exports of the component instance
-    pub fn exports(&self) -> impl ExactSizeIterator<Item = (&str, ComponentItem)> {
-        self.0.types[self.0.index]
-            .exports
-            .iter()
-            .map(|(name, ty)| (name.as_str(), ComponentItem::from(ty, &self.0.instance())))
+    pub fn exports<'a>(
+        &'a self,
+        engine: &'a Engine,
+    ) -> impl ExactSizeIterator<Item = (&str, ComponentItem)> {
+        self.0.types[self.0.index].exports.iter().map(|(name, ty)| {
+            (
+                name.as_str(),
+                ComponentItem::from(engine, ty, &self.0.instance()),
+            )
+        })
     }
 }
 
@@ -910,7 +931,7 @@ pub enum ComponentItem {
 }
 
 impl ComponentItem {
-    pub(crate) fn from(def: &TypeDef, ty: &InstanceType<'_>) -> Self {
+    pub(crate) fn from(engine: &Engine, def: &TypeDef, ty: &InstanceType<'_>) -> Self {
         match def {
             TypeDef::Component(idx) => Self::Component(Component::from(*idx, ty)),
             TypeDef::ComponentInstance(idx) => {
@@ -920,7 +941,7 @@ impl ComponentItem {
             TypeDef::Interface(iface_ty) => Self::Type(Type::from(iface_ty, ty)),
             TypeDef::Module(idx) => Self::Module(Module::from(*idx, ty)),
             TypeDef::CoreFunc(idx) => {
-                Self::CoreFunc(FuncType::from_wasm_func_type(ty.types[*idx].clone()))
+                Self::CoreFunc(FuncType::from_wasm_func_type(engine, &ty.types[*idx]))
             }
             TypeDef::Resource(idx) => Self::Resource(ty.resources[ty.types[*idx].ty]),
         }
diff --git a/crates/wasmtime/src/runtime/func.rs b/crates/wasmtime/src/runtime/func.rs
index 08ba4427e..599fdc0d1 100644
--- a/crates/wasmtime/src/runtime/func.rs
+++ b/crates/wasmtime/src/runtime/func.rs
@@ -1,4 +1,5 @@
 use crate::store::{StoreData, StoreOpaque, Stored};
+use crate::type_registry::RegisteredType;
 use crate::{
     AsContext, AsContextMut, CallHook, Engine, Extern, FuncType, Instance, Module, StoreContext,
     StoreContextMut, Val, ValRaw, ValType,
@@ -147,6 +148,7 @@ use wasmtime_runtime::{
 /// // Here we need to define the type signature of our `Double` function and
 /// // then wrap it up in a `Func`
 /// let double_type = wasmtime::FuncType::new(
+///     store.engine(),
 ///     [wasmtime::ValType::I32].iter().cloned(),
 ///     [wasmtime::ValType::I32].iter().cloned(),
 /// );
@@ -458,6 +460,7 @@ impl Func {
     ///     // ...
     /// });
     /// let get_row_count_type = wasmtime::FuncType::new(
+    ///     &engine,
     ///     None,
     ///     Some(wasmtime::ValType::I32),
     /// );
@@ -767,13 +770,7 @@ impl Func {
     /// Note that this is a somewhat expensive method since it requires taking a
     /// lock as well as cloning a type.
     fn load_ty(&self, store: &StoreOpaque) -> FuncType {
-        FuncType::from_wasm_func_type(
-            store
-                .engine()
-                .signatures()
-                .lookup_type(self.sig_index(store.store_data()))
-                .expect("signature should be registered"),
-        )
+        FuncType::from_shared_type_index(store.engine(), self.type_index(store.store_data()))
     }
 
     /// Gets a reference to the `FuncType` for this function.
@@ -794,7 +791,7 @@ impl Func {
         (store.store_data()[self.0].ty.as_ref().unwrap(), store)
     }
 
-    pub(crate) fn sig_index(&self, data: &StoreData) -> VMSharedTypeIndex {
+    pub(crate) fn type_index(&self, data: &StoreData) -> VMSharedTypeIndex {
         data[self.0].sig_index()
     }
 
@@ -1134,7 +1131,7 @@ impl Func {
                     // trampoline.
                     let _ = VMNativeCallHostFuncContext::from_opaque(f.as_ref().vmctx);
 
-                    let sig = self.sig_index(store.store_data());
+                    let sig = self.type_index(store.store_data());
                     module.runtime_info().wasm_to_native_trampoline(sig).expect(
                         "must have a wasm-to-native trampoline for this signature if the Wasm \
                          module is importing a function of this signature",
@@ -1488,7 +1485,7 @@ pub unsafe trait WasmRet {
     ) -> Result<Self::Abi>;
 
     #[doc(hidden)]
-    fn func_type(params: impl Iterator<Item = ValType>) -> FuncType;
+    fn func_type(engine: &Engine, params: impl Iterator<Item = ValType>) -> FuncType;
 
     #[doc(hidden)]
     unsafe fn wrap_trampoline(ptr: *mut ValRaw, f: impl FnOnce(Self::Retptr) -> Self::Abi);
@@ -1520,8 +1517,8 @@ where
         Ok(<Self as WasmTy>::into_abi(self, store))
     }
 
-    fn func_type(params: impl Iterator<Item = ValType>) -> FuncType {
-        FuncType::new(params, Some(<Self as WasmTy>::valtype()))
+    fn func_type(engine: &Engine, params: impl Iterator<Item = ValType>) -> FuncType {
+        FuncType::new(engine, params, Some(<Self as WasmTy>::valtype()))
     }
 
     unsafe fn wrap_trampoline(ptr: *mut ValRaw, f: impl FnOnce(Self::Retptr) -> Self::Abi) {
@@ -1560,8 +1557,8 @@ where
         self.and_then(|val| val.into_abi_for_ret(store, retptr))
     }
 
-    fn func_type(params: impl Iterator<Item = ValType>) -> FuncType {
-        T::func_type(params)
+    fn func_type(engine: &Engine, params: impl Iterator<Item = ValType>) -> FuncType {
+        T::func_type(engine, params)
     }
 
     unsafe fn wrap_trampoline(ptr: *mut ValRaw, f: impl FnOnce(Self::Retptr) -> Self::Abi) {
@@ -1602,8 +1599,9 @@ macro_rules! impl_wasm_host_results {
                 Ok(<($($t::Abi,)*) as HostAbi>::into_abi(abi, ptr))
             }
 
-            fn func_type(params: impl Iterator<Item = ValType>) -> FuncType {
+            fn func_type(engine: &Engine, params: impl Iterator<Item = ValType>) -> FuncType {
                 FuncType::new(
+                    engine,
                     params,
                     IntoIterator::into_iter([$($t::valtype(),)*]),
                 )
@@ -1893,6 +1891,17 @@ impl<T> AsContextMut for Caller<'_, T> {
     }
 }
 
+// State stored inside a `VMNativeCallHostFuncContext`.
+struct HostFuncState<F> {
+    // The actual host function.
+    func: F,
+
+    // NB: We have to keep our `VMSharedTypeIndex` registered in the engine for
+    // as long as this function exists.
+    #[allow(dead_code)]
+    ty: RegisteredType,
+}
+
 macro_rules! impl_into_func {
     ($num:tt $($args:ident)*) => {
         // Implement for functions without a leading `&Caller` parameter,
@@ -1961,8 +1970,9 @@ macro_rules! impl_into_func {
                         // Double-check ourselves in debug mode, but we control
                         // the `Any` here so an unsafe downcast should also
                         // work.
-                        debug_assert!(state.is::<F>());
-                        let func = &*(state as *const _ as *const F);
+                        debug_assert!(state.is::<HostFuncState<F>>());
+                        let state = &*(state as *const _ as *const HostFuncState<F>);
+                        let func = &state.func;
 
                         let ret = {
                             panic::catch_unwind(AssertUnwindSafe(|| {
@@ -2045,11 +2055,11 @@ macro_rules! impl_into_func {
                 }
 
                 let ty = R::func_type(
+                    engine,
                     None::<ValType>.into_iter()
                         $(.chain(Some($args::valtype())))*
                 );
-
-                let shared_signature_id = engine.signatures().register(ty.as_wasm_func_type());
+                let type_index = ty.type_index();
 
                 let array_call = array_call_trampoline::<T, F, $($args,)* R>;
                 let native_call = NonNull::new(native_call_shim::<T, F, $($args,)* R> as *mut _).unwrap();
@@ -2060,10 +2070,13 @@ macro_rules! impl_into_func {
                             native_call,
                             array_call,
                             wasm_call: None,
-                            type_index: shared_signature_id,
+                            type_index,
                             vmctx: ptr::null_mut(),
                         },
-                        Box::new(self),
+                        Box::new(HostFuncState {
+                            func: self,
+                            ty: ty.into_registered_type(),
+                        }),
                     )
                 };
 
@@ -2256,14 +2269,6 @@ impl HostFunc {
     }
 }
 
-impl Drop for HostFunc {
-    fn drop(&mut self) {
-        unsafe {
-            self.engine.signatures().unregister(self.sig_index());
-        }
-    }
-}
-
 impl FuncData {
     #[inline]
     fn export(&self) -> ExportFunction {
diff --git a/crates/wasmtime/src/runtime/func/typed.rs b/crates/wasmtime/src/runtime/func/typed.rs
index 255a81f49..06ba1b875 100644
--- a/crates/wasmtime/src/runtime/func/typed.rs
+++ b/crates/wasmtime/src/runtime/func/typed.rs
@@ -190,13 +190,7 @@ where
 
     /// Purely a debug-mode assertion, not actually used in release builds.
     fn debug_typecheck(store: &StoreOpaque, func: VMSharedTypeIndex) {
-        let ty = FuncType::from_wasm_func_type(
-            store
-                .engine()
-                .signatures()
-                .lookup_type(func)
-                .expect("signature should be registered"),
-        );
+        let ty = FuncType::from_shared_type_index(store.engine(), func);
         Params::typecheck(ty.params()).expect("params should match");
         Results::typecheck(ty.results()).expect("results should match");
     }
diff --git a/crates/wasmtime/src/runtime/linker.rs b/crates/wasmtime/src/runtime/linker.rs
index ca6577b85..45f9db687 100644
--- a/crates/wasmtime/src/runtime/linker.rs
+++ b/crates/wasmtime/src/runtime/linker.rs
@@ -1366,7 +1366,7 @@ impl DefinitionType {
     pub(crate) fn from(store: &StoreOpaque, item: &Extern) -> DefinitionType {
         let data = store.store_data();
         match item {
-            Extern::Func(f) => DefinitionType::Func(f.sig_index(data)),
+            Extern::Func(f) => DefinitionType::Func(f.type_index(data)),
             Extern::Table(t) => DefinitionType::Table(*t.wasmtime_ty(data), t.internal_size(store)),
             Extern::Global(t) => DefinitionType::Global(*t.wasmtime_ty(data)),
             Extern::Memory(t) => {
diff --git a/crates/wasmtime/src/runtime/module.rs b/crates/wasmtime/src/runtime/module.rs
index 56bd904b0..79c0914f2 100644
--- a/crates/wasmtime/src/runtime/module.rs
+++ b/crates/wasmtime/src/runtime/module.rs
@@ -736,9 +736,10 @@ impl Module {
     ) -> impl ExactSizeIterator<Item = ImportType<'module>> + 'module {
         let module = self.compiled_module().module();
         let types = self.types();
+        let engine = self.engine();
         module
             .imports()
-            .map(move |(module, field, ty)| ImportType::new(module, field, ty, types))
+            .map(move |(module, field, ty)| ImportType::new(module, field, ty, types, engine))
             .collect::<Vec<_>>()
             .into_iter()
     }
@@ -802,8 +803,9 @@ impl Module {
     ) -> impl ExactSizeIterator<Item = ExportType<'module>> + 'module {
         let module = self.compiled_module().module();
         let types = self.types();
+        let engine = self.engine();
         module.exports.iter().map(move |(name, entity_index)| {
-            ExportType::new(name, module.type_of(*entity_index), types)
+            ExportType::new(name, module.type_of(*entity_index), types, engine)
         })
     }
 
@@ -854,6 +856,7 @@ impl Module {
         let module = self.compiled_module().module();
         let entity_index = module.exports.get(name)?;
         Some(ExternType::from_wasmtime(
+            self.engine(),
             self.types(),
             &module.type_of(*entity_index),
         ))
diff --git a/crates/wasmtime/src/runtime/trampoline/func.rs b/crates/wasmtime/src/runtime/trampoline/func.rs
index c6c0f49fb..6c800f82a 100644
--- a/crates/wasmtime/src/runtime/trampoline/func.rs
+++ b/crates/wasmtime/src/runtime/trampoline/func.rs
@@ -1,5 +1,6 @@
 //! Support for a calling of an imported function.
 
+use crate::type_registry::RegisteredType;
 use crate::{code_memory::CodeMemory, Engine, FuncType, ValRaw};
 use anyhow::Result;
 use std::panic::{self, AssertUnwindSafe};
@@ -12,6 +13,10 @@ struct TrampolineState<F> {
     func: F,
     #[allow(dead_code)]
     code_memory: CodeMemory,
+
+    // Need to keep our `VMSharedTypeIndex` registered in the engine.
+    #[allow(dead_code)]
+    sig: RegisteredType,
 }
 
 /// Shim to call a host-defined function that uses the array calling convention.
@@ -126,10 +131,14 @@ where
                 array_call,
                 wasm_call,
                 native_call,
-                type_index: sig,
+                type_index: sig.index(),
                 vmctx: ptr::null_mut(),
             },
-            Box::new(TrampolineState { func, code_memory }),
+            Box::new(TrampolineState {
+                func,
+                code_memory,
+                sig,
+            }),
         ))
     }
 }
diff --git a/crates/wasmtime/src/runtime/type_registry.rs b/crates/wasmtime/src/runtime/type_registry.rs
index 5e6a02704..ce491fdbe 100644
--- a/crates/wasmtime/src/runtime/type_registry.rs
+++ b/crates/wasmtime/src/runtime/type_registry.rs
@@ -3,10 +3,7 @@
 //! Helps implement fast indirect call signature checking, reference type
 //! casting, and etc.
 
-use std::{
-    collections::{hash_map::Entry, HashMap},
-    sync::RwLock,
-};
+use std::{collections::HashMap, sync::RwLock};
 use std::{convert::TryFrom, sync::Arc};
 use wasmtime_environ::{ModuleInternedTypeIndex, ModuleTypes, PrimaryMap, WasmFuncType};
 use wasmtime_runtime::VMSharedTypeIndex;
@@ -66,29 +63,190 @@ impl Drop for TypeCollection {
     }
 }
 
+/// A Wasm type that has been registered in the engine's `TypeRegistry`.
+///
+/// Automatically unregisters the type on drop. (If other things are keeping the
+/// type registered, it will remain registered).
+///
+/// Dereferences to its underlying `WasmFuncType`.
+#[derive(Debug)]
+pub struct RegisteredType {
+    registry: Arc<RwLock<TypeRegistryInner>>,
+    index: VMSharedTypeIndex,
+
+    // This field is not *strictly* necessary to have in this type, since we
+    // could always grab the registry's lock and look it up by index, but
+    // holding this reference should make accessing the actual type that much
+    // cheaper.
+    ty: Arc<WasmFuncType>,
+}
+
+impl Clone for RegisteredType {
+    fn clone(&self) -> Self {
+        {
+            let i = usize::try_from(self.index.bits()).unwrap();
+            let mut registry = self.registry.write().unwrap();
+            let entry = registry.entries[i].unwrap_occupied_mut();
+            entry.references += 1;
+            log::trace!(
+                "cloned registered type {:?} (references -> {})",
+                self.index,
+                entry.references
+            );
+        }
+
+        RegisteredType {
+            registry: Arc::clone(&self.registry),
+            index: self.index,
+            ty: Arc::clone(&self.ty),
+        }
+    }
+}
+
+impl Drop for RegisteredType {
+    fn drop(&mut self) {
+        self.registry
+            .write()
+            .unwrap()
+            .unregister_entry(self.index, 1);
+    }
+}
+
+impl std::ops::Deref for RegisteredType {
+    type Target = Arc<WasmFuncType>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.ty
+    }
+}
+
+impl PartialEq for RegisteredType {
+    fn eq(&self, other: &Self) -> bool {
+        let eq = Arc::ptr_eq(&self.ty, &other.ty);
+
+        if cfg!(debug_assertions) {
+            if eq {
+                assert_eq!(self.index, other.index);
+                assert!(Arc::ptr_eq(&self.registry, &other.registry));
+            } else {
+                assert!(self.index != other.index || !Arc::ptr_eq(&self.registry, &other.registry));
+            }
+        }
+
+        eq
+    }
+}
+
+impl Eq for RegisteredType {}
+
+impl std::hash::Hash for RegisteredType {
+    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+        let ptr = Arc::as_ptr(&self.ty);
+        ptr.hash(state);
+    }
+}
+
+impl RegisteredType {
+    /// Construct a new `RegisteredType`.
+    ///
+    /// It is the caller's responsibility to ensure that the entry's reference
+    /// count has already been incremented.
+    fn new(
+        registry: Arc<RwLock<TypeRegistryInner>>,
+        index: VMSharedTypeIndex,
+        ty: Arc<WasmFuncType>,
+    ) -> Self {
+        debug_assert!({
+            let registry = registry.read().unwrap();
+            let i = usize::try_from(index.bits()).unwrap();
+            let e = registry.entries[i].as_occupied().unwrap();
+            e.references > 0
+        });
+        RegisteredType {
+            registry,
+            index,
+            ty,
+        }
+    }
+
+    /// Get this registered type's index.
+    pub fn index(&self) -> VMSharedTypeIndex {
+        self.index
+    }
+}
+
 #[derive(Debug)]
-struct RegistryEntry {
+struct OccupiedEntry {
+    ty: Arc<WasmFuncType>,
     references: usize,
-    ty: WasmFuncType,
+}
+
+#[derive(Debug)]
+enum RegistryEntry {
+    /// An occupied entry containing a registered type.
+    Occupied(OccupiedEntry),
+
+    /// A vacant entry that is additionally a link in the free list of all
+    /// vacant entries.
+    Vacant {
+        /// The next link in the free list of all vacant entries, if any.
+        next_vacant: Option<VMSharedTypeIndex>,
+    },
+}
+
+impl RegistryEntry {
+    fn is_vacant(&self) -> bool {
+        matches!(self, Self::Vacant { .. })
+    }
+
+    fn as_occupied(&self) -> Option<&OccupiedEntry> {
+        match self {
+            Self::Occupied(o) => Some(o),
+            Self::Vacant { .. } => None,
+        }
+    }
+
+    fn as_occupied_mut(&mut self) -> Option<&mut OccupiedEntry> {
+        match self {
+            Self::Occupied(o) => Some(o),
+            Self::Vacant { .. } => None,
+        }
+    }
+
+    fn unwrap_occupied_mut(&mut self) -> &mut OccupiedEntry {
+        match self {
+            Self::Occupied(o) => o,
+            Self::Vacant { .. } => panic!("unwrap_occupied_mut on vacant entry"),
+        }
+    }
+
+    fn unwrap_next_vacant(&self) -> Option<VMSharedTypeIndex> {
+        match self {
+            Self::Vacant { next_vacant } => *next_vacant,
+            Self::Occupied(_) => panic!("unwrap_next_vacant on occupied entry"),
+        }
+    }
 }
 
 #[derive(Debug, Default)]
 struct TypeRegistryInner {
     // A map from the Wasm function type to a `VMSharedTypeIndex`, for all
     // the Wasm function types we have already registered.
-    map: HashMap<WasmFuncType, VMSharedTypeIndex>,
+    map: HashMap<Arc<WasmFuncType>, VMSharedTypeIndex>,
 
-    // A map from `VMSharedTypeIndex::bits()` to the type index's
-    // associated data, such as the underlying Wasm type.
-    entries: Vec<Option<RegistryEntry>>,
+    // A map from `VMSharedTypeIndex::bits()` to the type index's associated
+    // Wasm type.
+    entries: Vec<RegistryEntry>,
 
-    // A free list of the `VMSharedTypeIndex`es that are no longer being
-    // used by anything, and can therefore be reused.
+    // The head of the free list of the entries that are vacant and can
+    // therefore (along with their associated `VMSharedTypeIndex`) be reused.
     //
-    // This is a size optimization, and not strictly necessary for correctness:
-    // we reuse entries rather than leak them and have logical holes in our
-    // `self.entries` list.
-    free: Vec<VMSharedTypeIndex>,
+    // This is a size optimization, and arguably not strictly necessary for
+    // correctness, but is necessary to avoid unbounded memory growth: if we did
+    // not reuse entries/indices, we would have holes in our `self.entries` list
+    // and, as we load and unload new Wasm modules, `self.entries` would keep
+    // growing indefinitely.
+    first_vacant: Option<VMSharedTypeIndex>,
 }
 
 impl TypeRegistryInner {
@@ -96,60 +254,77 @@ impl TypeRegistryInner {
         &mut self,
         types: &ModuleTypes,
     ) -> PrimaryMap<ModuleInternedTypeIndex, VMSharedTypeIndex> {
+        log::trace!("Registering module types");
         let mut map = PrimaryMap::default();
         for (idx, ty) in types.wasm_types() {
-            let b = map.push(self.register(ty));
-            assert_eq!(idx, b);
+            let (shared_type_index, _) = self.register_raw(ty);
+            let map_idx = map.push(shared_type_index);
+            assert_eq!(idx, map_idx);
         }
         map
     }
 
-    fn register(&mut self, ty: &WasmFuncType) -> VMSharedTypeIndex {
-        let len = self.map.len();
-
-        let index = match self.map.entry(ty.clone()) {
-            Entry::Occupied(e) => *e.get(),
-            Entry::Vacant(e) => {
-                let (index, entry) = match self.free.pop() {
-                    Some(index) => (index, &mut self.entries[index.bits() as usize]),
-                    None => {
-                        // Keep `index_map`'s length under `u32::MAX` because
-                        // `u32::MAX` is reserved for `VMSharedTypeIndex`'s
-                        // default value.
-                        assert!(
-                            len < std::u32::MAX as usize,
-                            "Invariant check: index_map.len() < std::u32::MAX"
-                        );
-                        debug_assert_eq!(len, self.entries.len());
-
-                        let index = VMSharedTypeIndex::new(u32::try_from(len).unwrap());
-                        self.entries.push(None);
-
-                        (index, self.entries.last_mut().unwrap())
-                    }
-                };
-
-                // The entry should be missing for one just allocated or
-                // taken from the free list
-                assert!(entry.is_none());
-
-                *entry = Some(RegistryEntry {
-                    references: 0,
-                    ty: ty.clone(),
-                });
-
-                *e.insert(index)
+    /// Add a new type to this registry.
+    ///
+    /// Does not increment its reference count, that is the responsibility of
+    /// callers.
+    fn register_new(&mut self, ty: Arc<WasmFuncType>) -> VMSharedTypeIndex {
+        let (index, entry) = match self.first_vacant.take() {
+            Some(index) => {
+                let i = usize::try_from(index.bits()).unwrap();
+                let entry = &mut self.entries[i];
+                self.first_vacant = entry.unwrap_next_vacant();
+                (index, entry)
+            }
+            None => {
+                debug_assert_eq!(self.entries.len(), self.map.len());
+
+                let len = self.entries.len();
+                let len = u32::try_from(len).unwrap();
+
+                // Keep `index_map`'s length under `u32::MAX` because
+                // `u32::MAX` is reserved for `VMSharedTypeIndex`'s
+                // default value.
+                assert!(
+                    len < std::u32::MAX,
+                    "Invariant check: self.entries.len() < std::u32::MAX"
+                );
+
+                let index = VMSharedTypeIndex::new(len);
+                self.entries
+                    .push(RegistryEntry::Vacant { next_vacant: None });
+
+                (index, self.entries.last_mut().unwrap())
             }
         };
 
-        self.entries[index.bits() as usize]
-            .as_mut()
-            .unwrap()
-            .references += 1;
+        let old_map_entry = self.map.insert(ty.clone(), index);
+        assert!(old_map_entry.is_none());
+
+        assert!(entry.is_vacant());
+        *entry = RegistryEntry::Occupied(OccupiedEntry { ty, references: 0 });
 
         index
     }
 
+    /// Register the given type, incrementing its reference count.
+    fn register_raw(&mut self, ty: &WasmFuncType) -> (VMSharedTypeIndex, Arc<WasmFuncType>) {
+        let index = if let Some(i) = self.map.get(ty) {
+            *i
+        } else {
+            let ty = Arc::new(ty.clone());
+            self.register_new(ty)
+        };
+
+        let i = usize::try_from(index.bits()).unwrap();
+        let entry = self.entries[i].unwrap_occupied_mut();
+        entry.references += 1;
+
+        log::trace!("registered {index:?} (references -> {})", entry.references);
+
+        (index, Arc::clone(&entry.ty))
+    }
+
     fn unregister_types(&mut self, collection: &TypeCollection) {
         for (_, index) in collection.types.iter() {
             self.unregister_entry(*index, 1);
@@ -157,23 +332,22 @@ impl TypeRegistryInner {
     }
 
     fn unregister_entry(&mut self, index: VMSharedTypeIndex, count: usize) {
-        let removed = {
-            let entry = self.entries[index.bits() as usize].as_mut().unwrap();
-
-            debug_assert!(entry.references >= count);
-            entry.references -= count;
-
-            if entry.references == 0 {
-                self.map.remove(&entry.ty);
-                self.free.push(index);
-                true
-            } else {
-                false
-            }
-        };
+        let i = usize::try_from(index.bits()).unwrap();
+        let entry = self.entries[i].unwrap_occupied_mut();
+
+        assert!(entry.references >= count);
+        entry.references -= count;
+        log::trace!(
+            "unregistered {index:?} by {count} (references -> {})",
+            entry.references
+        );
 
-        if removed {
-            self.entries[index.bits() as usize] = None;
+        if entry.references == 0 {
+            self.map.remove(&entry.ty);
+            self.entries[i] = RegistryEntry::Vacant {
+                next_vacant: self.first_vacant.take(),
+            };
+            self.first_vacant = Some(index);
         }
     }
 }
@@ -187,10 +361,9 @@ impl Drop for TypeRegistryInner {
             self.map.is_empty(),
             "type registry not empty: still have registered types in self.map"
         );
-        assert_eq!(
-            self.free.len(),
-            self.entries.len(),
-            "type registery not empty: not all entries in free list"
+        assert!(
+            self.entries.iter().all(|e| e.is_vacant()),
+            "type registry not empty: not all entries are vacant"
         );
     }
 }
@@ -210,27 +383,41 @@ impl TypeRegistry {
         Self(Arc::new(RwLock::new(TypeRegistryInner::default())))
     }
 
-    /// Looks up a function type from a shared type index.
-    pub fn lookup_type(&self, index: VMSharedTypeIndex) -> Option<WasmFuncType> {
-        self.0
-            .read()
-            .unwrap()
-            .entries
-            .get(index.bits() as usize)
-            .and_then(|e| e.as_ref().map(|e| &e.ty).cloned())
+    /// Get an owning handle to the given index's associated type.
+    ///
+    /// This will keep the type registered as long as the return value is kept
+    /// alive.
+    pub fn root(&self, index: VMSharedTypeIndex) -> Option<RegisteredType> {
+        let i = usize::try_from(index.bits()).unwrap();
+        let registry = Arc::clone(&self.0);
+        let ty = {
+            let mut inner = self.0.write().unwrap();
+            let e = inner.entries.get_mut(i)?.as_occupied_mut()?;
+            e.references += 1;
+            log::trace!("rooting {index:?} (references -> {})", e.references);
+            Arc::clone(&e.ty)
+        };
+        Some(RegisteredType::new(registry, index, ty))
     }
 
-    /// Registers a single function with the collection.
+    /// Looks up a function type from a shared type index.
     ///
-    /// Returns the shared type index for the function.
-    pub fn register(&self, ty: &WasmFuncType) -> VMSharedTypeIndex {
-        self.0.write().unwrap().register(ty)
+    /// This does NOT guarantee that the type remains registered while you use
+    /// the result. Use the `root` method 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>> {
+        let i = usize::try_from(index.bits()).unwrap();
+        let inner = self.0.read().unwrap();
+        let e = inner.entries.get(i)?;
+        Some(e.as_occupied()?.ty.clone())
     }
 
     /// Registers a single function with the collection.
     ///
     /// Returns the shared type index for the function.
-    pub unsafe fn unregister(&self, sig: VMSharedTypeIndex) {
-        self.0.write().unwrap().unregister_entry(sig, 1)
+    pub fn register(&self, ty: &WasmFuncType) -> RegisteredType {
+        let (index, ty) = self.0.write().unwrap().register_raw(ty);
+        RegisteredType::new(Arc::clone(&self.0), index, ty)
     }
 }
diff --git a/crates/wasmtime/src/runtime/types.rs b/crates/wasmtime/src/runtime/types.rs
index 0fe9388a7..bc4413f2a 100644
--- a/crates/wasmtime/src/runtime/types.rs
+++ b/crates/wasmtime/src/runtime/types.rs
@@ -2,6 +2,9 @@ use std::fmt;
 use wasmtime_environ::{
     EntityType, Global, Memory, ModuleTypes, Table, WasmFuncType, WasmRefType, WasmValType,
 };
+use wasmtime_runtime::VMSharedTypeIndex;
+
+use crate::{type_registry::RegisteredType, Engine};
 
 pub(crate) mod matching;
 
@@ -158,9 +161,13 @@ impl ExternType {
         (Memory(MemoryType) memory unwrap_memory)
     }
 
-    pub(crate) fn from_wasmtime(types: &ModuleTypes, ty: &EntityType) -> ExternType {
+    pub(crate) fn from_wasmtime(
+        engine: &Engine,
+        types: &ModuleTypes,
+        ty: &EntityType,
+    ) -> ExternType {
         match ty {
-            EntityType::Function(idx) => FuncType::from_wasm_func_type(types[*idx].clone()).into(),
+            EntityType::Function(idx) => FuncType::from_wasm_func_type(engine, &types[*idx]).into(),
             EntityType::Global(ty) => GlobalType::from_wasmtime_global(ty).into(),
             EntityType::Memory(ty) => MemoryType::from_wasmtime_memory(ty).into(),
             EntityType::Table(ty) => TableType::from_wasmtime_table(ty).into(),
@@ -198,7 +205,7 @@ impl From<TableType> for ExternType {
 /// WebAssembly functions can have 0 or more parameters and results.
 #[derive(Debug, Clone, Hash, Eq, PartialEq)]
 pub struct FuncType {
-    sig: WasmFuncType,
+    ty: RegisteredType,
 }
 
 impl FuncType {
@@ -207,35 +214,54 @@ impl FuncType {
     /// The function descriptor returned will represent a function which takes
     /// `params` as arguments and returns `results` when it is finished.
     pub fn new(
+        engine: &Engine,
         params: impl IntoIterator<Item = ValType>,
         results: impl IntoIterator<Item = ValType>,
     ) -> FuncType {
-        FuncType {
-            sig: WasmFuncType::new(
+        Self::from_wasm_func_type(
+            engine,
+            &WasmFuncType::new(
                 params.into_iter().map(|t| t.to_wasm_type()).collect(),
                 results.into_iter().map(|t| t.to_wasm_type()).collect(),
             ),
-        }
+        )
     }
 
     /// Returns the list of parameter types for this function.
     #[inline]
     pub fn params(&self) -> impl ExactSizeIterator<Item = ValType> + '_ {
-        self.sig.params().iter().map(ValType::from_wasm_type)
+        self.ty.params().iter().map(ValType::from_wasm_type)
     }
 
     /// Returns the list of result types for this function.
     #[inline]
     pub fn results(&self) -> impl ExactSizeIterator<Item = ValType> + '_ {
-        self.sig.returns().iter().map(ValType::from_wasm_type)
+        self.ty.returns().iter().map(ValType::from_wasm_type)
+    }
+
+    pub(crate) fn type_index(&self) -> VMSharedTypeIndex {
+        self.ty.index()
     }
 
     pub(crate) fn as_wasm_func_type(&self) -> &WasmFuncType {
-        &self.sig
+        &self.ty
+    }
+
+    pub(crate) fn into_registered_type(self) -> RegisteredType {
+        self.ty
     }
 
-    pub(crate) fn from_wasm_func_type(sig: WasmFuncType) -> FuncType {
-        Self { sig }
+    pub(crate) fn from_wasm_func_type(engine: &Engine, ty: &WasmFuncType) -> FuncType {
+        let ty = engine.signatures().register(ty);
+        Self { ty }
+    }
+
+    pub(crate) fn from_shared_type_index(engine: &Engine, index: VMSharedTypeIndex) -> FuncType {
+        let ty = engine.signatures().root(index).expect(
+            "VMSharedTypeIndex is not registered in the Engine. Wrong \
+             engine? Didn't root the index somewhere?",
+        );
+        Self { ty }
     }
 }
 
@@ -485,6 +511,7 @@ pub struct ImportType<'module> {
     /// The type of the import.
     ty: EntityType,
     types: &'module ModuleTypes,
+    engine: &'module Engine,
 }
 
 impl<'module> ImportType<'module> {
@@ -495,12 +522,14 @@ impl<'module> ImportType<'module> {
         name: &'module str,
         ty: EntityType,
         types: &'module ModuleTypes,
+        engine: &'module Engine,
     ) -> ImportType<'module> {
         ImportType {
             module,
             name,
             ty,
             types,
+            engine,
         }
     }
 
@@ -517,7 +546,7 @@ impl<'module> ImportType<'module> {
 
     /// Returns the expected type of this import.
     pub fn ty(&self) -> ExternType {
-        ExternType::from_wasmtime(self.types, &self.ty)
+        ExternType::from_wasmtime(self.engine, self.types, &self.ty)
     }
 }
 
@@ -547,6 +576,7 @@ pub struct ExportType<'module> {
     /// The type of the export.
     ty: EntityType,
     types: &'module ModuleTypes,
+    engine: &'module Engine,
 }
 
 impl<'module> ExportType<'module> {
@@ -556,8 +586,14 @@ impl<'module> ExportType<'module> {
         name: &'module str,
         ty: EntityType,
         types: &'module ModuleTypes,
+        engine: &'module Engine,
     ) -> ExportType<'module> {
-        ExportType { name, ty, types }
+        ExportType {
+            name,
+            ty,
+            types,
+            engine,
+        }
     }
 
     /// Returns the name by which this export is known.
@@ -567,7 +603,7 @@ impl<'module> ExportType<'module> {
 
     /// Returns the type of this export.
     pub fn ty(&self) -> ExternType {
-        ExternType::from_wasmtime(self.types, &self.ty)
+        ExternType::from_wasmtime(self.engine, self.types, &self.ty)
     }
 }
 
diff --git a/crates/wasmtime/src/runtime/types/matching.rs b/crates/wasmtime/src/runtime/types/matching.rs
index 9ee8d3326..0fca28227 100644
--- a/crates/wasmtime/src/runtime/types/matching.rs
+++ b/crates/wasmtime/src/runtime/types/matching.rs
@@ -30,7 +30,7 @@ impl MatchCx<'_> {
         }
         let msg = "function types incompatible";
         let expected = &self.types[expected];
-        let actual = match self.engine.signatures().lookup_type(actual) {
+        let actual = match self.engine.signatures().borrow(actual) {
             Some(ty) => ty,
             None => {
                 debug_assert!(false, "all signatures should be registered");
diff --git a/fuzz/fuzz_targets/cranelift-fuzzgen.rs b/fuzz/fuzz_targets/cranelift-fuzzgen.rs
index 3aa9cb0ef..c591fcaa6 100644
--- a/fuzz/fuzz_targets/cranelift-fuzzgen.rs
+++ b/fuzz/fuzz_targets/cranelift-fuzzgen.rs
@@ -346,7 +346,7 @@ fn run_test_inputs(testcase: &TestCase, run: impl Fn(&[DataValue]) -> RunResult)
                 STATISTICS.run_result_timeout.fetch_add(1, Ordering::SeqCst);
                 return;
             }
-            RunResult::Error(_) => panic!("interpreter failed: {:?}", int_res),
+            RunResult::Error(e) => panic!("interpreter failed: {e:?}"),
         }
 
         let res = run(args);
diff --git a/fuzz/fuzz_targets/differential.rs b/fuzz/fuzz_targets/differential.rs
index 56f747e5c..be2ecac64 100644
--- a/fuzz/fuzz_targets/differential.rs
+++ b/fuzz/fuzz_targets/differential.rs
@@ -100,7 +100,7 @@ fn execute_one(data: &[u8]) -> Result<()> {
     // Choose an engine that Wasmtime will be differentially executed against.
     // The chosen engine is then created, which might update `config`, and
     // returned as a trait object.
-    let lhs = u.choose(unsafe { &ALLOWED_ENGINES })?;
+    let lhs = u.choose(unsafe { &*std::ptr::addr_of!(ALLOWED_ENGINES) })?;
     let mut lhs = match engine::build(&mut u, lhs, &mut config)? {
         Some(engine) => engine,
         // The chosen engine does not have support compiled into the fuzzer,
diff --git a/winch/codegen/src/isa/x64/masm.rs b/winch/codegen/src/isa/x64/masm.rs
index 673c62215..eddd47396 100644
--- a/winch/codegen/src/isa/x64/masm.rs
+++ b/winch/codegen/src/isa/x64/masm.rs
@@ -7,7 +7,7 @@ use super::{
 
 use crate::masm::{
     DivKind, ExtendKind, FloatCmpKind, Imm as I, IntCmpKind, MacroAssembler as Masm, OperandSize,
-    RegImm, RemKind, RoundingMode, ShiftKind, TrapCode,
+    RegImm, RemKind, RoundingMode, ShiftKind, TrapCode, TRUSTED_FLAGS, UNTRUSTED_FLAGS,
 };
 use crate::{
     abi::ABI,
@@ -24,7 +24,7 @@ use crate::{
     masm::CalleeKind,
 };
 use cranelift_codegen::{
-    ir::{Endianness, MemFlags},
+    ir::MemFlags,
     isa::unwind::UnwindInst,
     isa::x64::{
         args::{ExtMode, CC},
@@ -47,10 +47,6 @@ pub(crate) struct MacroAssembler {
     shared_flags: settings::Flags,
     /// The target pointer size.
     ptr_size: OperandSize,
-    /// Flags for native loads/stores.
-    trusted_flags: MemFlags,
-    /// Flags for Wasm loads/stores.
-    untrusted_flags: MemFlags,
 }
 
 impl Masm for MacroAssembler {
@@ -114,24 +110,16 @@ impl Masm for MacroAssembler {
                 let bytes = size.bytes();
                 self.reserve_stack(bytes);
                 let sp_offset = SPOffset::from_u32(self.sp_offset);
-                self.asm.mov_rm(
-                    reg,
-                    &self.address_from_sp(sp_offset),
-                    size,
-                    self.trusted_flags,
-                );
+                self.asm
+                    .mov_rm(reg, &self.address_from_sp(sp_offset), size, TRUSTED_FLAGS);
                 bytes
             }
             (RegClass::Float, _) => {
                 let bytes = size.bytes();
                 self.reserve_stack(bytes);
                 let sp_offset = SPOffset::from_u32(self.sp_offset);
-                self.asm.xmm_mov_rm(
-                    reg,
-                    &self.address_from_sp(sp_offset),
-                    size,
-                    self.trusted_flags,
-                );
+                self.asm
+                    .xmm_mov_rm(reg, &self.address_from_sp(sp_offset), size, TRUSTED_FLAGS);
                 bytes
             }
             _ => unreachable!(),
@@ -212,7 +200,7 @@ impl Masm for MacroAssembler {
                 &self.address_at_vmctx(offset),
                 ptr_base,
                 self.ptr_size.into(),
-                self.trusted_flags,
+                TRUSTED_FLAGS,
             );
         } else {
             // Else, simply move the vmctx register into the addr register as
@@ -224,7 +212,7 @@ impl Masm for MacroAssembler {
         let bound_addr = self.address_at_reg(ptr_base, table_data.current_elems_offset);
         let bound_size = table_data.current_elements_size;
         self.asm
-            .movzx_mr(&bound_addr, bound, bound_size.into(), self.trusted_flags);
+            .movzx_mr(&bound_addr, bound, bound_size.into(), TRUSTED_FLAGS);
         self.asm.cmp_rr(bound, index, bound_size);
         self.asm.trapif(IntCmpKind::GeU, TrapCode::TableOutOfBounds);
 
@@ -242,7 +230,7 @@ impl Masm for MacroAssembler {
             &self.address_at_reg(ptr_base, table_data.offset),
             ptr_base,
             self.ptr_size.into(),
-            self.trusted_flags,
+            TRUSTED_FLAGS,
         );
         // Copy the value of the table base into a temporary register
         // so that we can use it later in case of a misspeculation.
@@ -270,7 +258,7 @@ impl Masm for MacroAssembler {
                 &self.address_at_vmctx(offset),
                 scratch,
                 self.ptr_size.into(),
-                self.trusted_flags,
+                TRUSTED_FLAGS,
             );
         } else {
             self.asm.mov_rr(vmctx, scratch, self.ptr_size);
@@ -281,7 +269,7 @@ impl Masm for MacroAssembler {
             &size_addr,
             size,
             table_data.current_elements_size.into(),
-            self.trusted_flags,
+            TRUSTED_FLAGS,
         );
 
         context.stack.push(TypedReg::i32(size).into());
@@ -297,7 +285,7 @@ impl Masm for MacroAssembler {
                 &self.address_at_vmctx(offset),
                 scratch,
                 self.ptr_size.into(),
-                self.trusted_flags,
+                TRUSTED_FLAGS,
             );
             scratch
         } else {
@@ -305,12 +293,8 @@ impl Masm for MacroAssembler {
         };
 
         let size_addr = Address::offset(base, heap_data.current_length_offset);
-        self.asm.movzx_mr(
-            &size_addr,
-            size_reg,
-            self.ptr_size.into(),
-            self.trusted_flags,
-        );
+        self.asm
+            .movzx_mr(&size_addr, size_reg, self.ptr_size.into(), TRUSTED_FLAGS);
         // Prepare the stack to emit a shift to get the size in pages rather
         // than in bytes.
         context
@@ -351,11 +335,11 @@ impl Masm for MacroAssembler {
     }
 
     fn store(&mut self, src: RegImm, dst: Address, size: OperandSize) {
-        self.store_impl(src, dst, size, self.trusted_flags);
+        self.store_impl(src, dst, size, TRUSTED_FLAGS);
     }
 
     fn wasm_store(&mut self, src: Reg, dst: Self::Address, size: OperandSize) {
-        self.store_impl(src.into(), dst, size, self.untrusted_flags);
+        self.store_impl(src.into(), dst, size, UNTRUSTED_FLAGS);
     }
 
     fn pop(&mut self, dst: Reg, size: OperandSize) {
@@ -363,8 +347,7 @@ impl Masm for MacroAssembler {
         match (dst.class(), size) {
             (RegClass::Int, OperandSize::S32) => {
                 let addr = self.address_from_sp(current_sp);
-                self.asm
-                    .movzx_mr(&addr, dst, size.into(), self.trusted_flags);
+                self.asm.movzx_mr(&addr, dst, size.into(), TRUSTED_FLAGS);
                 self.free_stack(size.bytes());
             }
             (RegClass::Int, OperandSize::S64) => {
@@ -373,7 +356,7 @@ impl Masm for MacroAssembler {
             }
             (RegClass::Float, _) => {
                 let addr = self.address_from_sp(current_sp);
-                self.asm.xmm_mov_mr(&addr, dst, size, self.trusted_flags);
+                self.asm.xmm_mov_mr(&addr, dst, size, TRUSTED_FLAGS);
                 self.free_stack(size.bytes());
             }
             _ => unreachable!(),
@@ -409,7 +392,7 @@ impl Masm for MacroAssembler {
     }
 
     fn load(&mut self, src: Address, dst: Reg, size: OperandSize) {
-        self.load_impl::<Self>(src, dst, size, self.trusted_flags);
+        self.load_impl::<Self>(src, dst, size, TRUSTED_FLAGS);
     }
 
     fn wasm_load(
@@ -420,9 +403,9 @@ impl Masm for MacroAssembler {
         kind: Option<ExtendKind>,
     ) {
         if let Some(ext) = kind {
-            self.asm.movsx_mr(&src, dst, ext, self.untrusted_flags);
+            self.asm.movsx_mr(&src, dst, ext, UNTRUSTED_FLAGS);
         } else {
-            self.load_impl::<Self>(src, dst, size, self.untrusted_flags)
+            self.load_impl::<Self>(src, dst, size, UNTRUSTED_FLAGS)
         }
     }
 
@@ -446,11 +429,11 @@ impl Masm for MacroAssembler {
                 I::I64(v) => self.asm.mov_ir(v, dst, size),
                 I::F32(v) => {
                     let addr = self.asm.add_constant(v.to_le_bytes().as_slice());
-                    self.asm.xmm_mov_mr(&addr, dst, size, self.trusted_flags);
+                    self.asm.xmm_mov_mr(&addr, dst, size, TRUSTED_FLAGS);
                 }
                 I::F64(v) => {
                     let addr = self.asm.add_constant(v.to_le_bytes().as_slice());
-                    self.asm.xmm_mov_mr(&addr, dst, size, self.trusted_flags);
+                    self.asm.xmm_mov_mr(&addr, dst, size, TRUSTED_FLAGS);
                 }
             },
         }
@@ -1152,11 +1135,6 @@ impl MacroAssembler {
         isa_flags: x64_settings::Flags,
     ) -> Self {
         let ptr_type: WasmValType = ptr_type_from_ptr_size(ptr_size.size()).into();
-        // Flags used for WebAssembly loads / stores.
-        // Untrusted by default so we don't set `no_trap`.
-        // We also ensure that the endianess is the right one for WebAssembly.
-        let mut untrusted_flags = MemFlags::new();
-        untrusted_flags.set_endianness(Endianness::Little);
 
         Self {
             sp_offset: 0,
@@ -1164,8 +1142,6 @@ impl MacroAssembler {
             flags: isa_flags,
             shared_flags,
             ptr_size: ptr_type.into(),
-            trusted_flags: MemFlags::trusted(),
-            untrusted_flags,
         }
     }
 
diff --git a/winch/codegen/src/masm.rs b/winch/codegen/src/masm.rs
index 0e7339fe3..54b1792c5 100644
--- a/winch/codegen/src/masm.rs
+++ b/winch/codegen/src/masm.rs
@@ -1,7 +1,10 @@
 use crate::abi::{self, align_to, LocalSlot};
 use crate::codegen::{CodeGenContext, HeapData, TableData};
 use crate::isa::reg::Reg;
-use cranelift_codegen::{ir::LibCall, Final, MachBufferFinalized, MachLabel};
+use cranelift_codegen::{
+    ir::{Endianness, LibCall, MemFlags},
+    Final, MachBufferFinalized, MachLabel,
+};
 use std::{fmt::Debug, ops::Range};
 use wasmtime_environ::PtrSize;
 
@@ -336,6 +339,14 @@ pub enum RoundingMode {
     Zero,
 }
 
+/// Memory flags for trusted loads/stores.
+pub const TRUSTED_FLAGS: MemFlags = MemFlags::trusted();
+
+/// Flags used for WebAssembly loads / stores.
+/// Untrusted by default so we don't set `no_trap`.
+/// We also ensure that the endianess is the right one for WebAssembly.
+pub const UNTRUSTED_FLAGS: MemFlags = MemFlags::new().with_endianness(Endianness::Little);
+
 /// Generic MacroAssembler interface used by the code generation.
 ///
 /// The MacroAssembler trait aims to expose an interface, high-level enough,


Test patch:
diff --git a/crates/test-programs/src/bin/api_proxy.rs b/crates/test-programs/src/bin/api_proxy.rs
index a0b26cfd7..c5f3185af 100644
--- a/crates/test-programs/src/bin/api_proxy.rs
+++ b/crates/test-programs/src/bin/api_proxy.rs
@@ -36,6 +36,11 @@ impl bindings::exports::wasi::http::incoming_handler::Guest for T {
             "append of forbidden header succeeded"
         );
 
+        assert!(
+            !req_hdrs.has(&"host".to_owned()),
+            "forbidden host header present in incoming request"
+        );
+
         let hdrs = bindings::wasi::http::types::Headers::new();
         let resp = bindings::wasi::http::types::OutgoingResponse::new(hdrs);
         let body = resp.body().expect("outgoing response");
diff --git a/crates/test-programs/src/bin/http_outbound_request_invalid_header.rs b/crates/test-programs/src/bin/http_outbound_request_invalid_header.rs
index 65931dd61..1f532ee25 100644
--- a/crates/test-programs/src/bin/http_outbound_request_invalid_header.rs
+++ b/crates/test-programs/src/bin/http_outbound_request_invalid_header.rs
@@ -27,6 +27,11 @@ fn main() {
         Err(HeaderError::Forbidden)
     ));
 
+    assert!(matches!(
+        hdrs.append(&"Host".to_owned(), &b"example.com".to_vec()),
+        Err(HeaderError::Forbidden)
+    ));
+
     assert!(matches!(
         hdrs.append(
             &"custom-forbidden-header".to_owned(),
diff --git a/tests/all/async_functions.rs b/tests/all/async_functions.rs
index 3796c9b05..fdc643a34 100644
--- a/tests/all/async_functions.rs
+++ b/tests/all/async_functions.rs
@@ -25,11 +25,10 @@ async fn run_smoke_typed_test(store: &mut Store<()>, func: Func) {
 #[tokio::test]
 async fn smoke() {
     let mut store = async_store();
-    let func = Func::new_async(
-        &mut store,
-        FuncType::new(None, None),
-        move |_caller, _params, _results| Box::new(async { Ok(()) }),
-    );
+    let func_ty = FuncType::new(store.engine(), None, None);
+    let func = Func::new_async(&mut store, func_ty, move |_caller, _params, _results| {
+        Box::new(async { Ok(()) })
+    });
     run_smoke_test(&mut store, func).await;
     run_smoke_typed_test(&mut store, func).await;
 
@@ -46,7 +45,7 @@ async fn smoke_host_func() -> Result<()> {
     linker.func_new_async(
         "",
         "first",
-        FuncType::new(None, None),
+        FuncType::new(store.engine(), None, None),
         move |_caller, _params, _results| Box::new(async { Ok(()) }),
     )?;
 
@@ -74,16 +73,13 @@ async fn smoke_host_func() -> Result<()> {
 #[tokio::test]
 async fn smoke_with_suspension() {
     let mut store = async_store();
-    let func = Func::new_async(
-        &mut store,
-        FuncType::new(None, None),
-        move |_caller, _params, _results| {
-            Box::new(async {
-                tokio::task::yield_now().await;
-                Ok(())
-            })
-        },
-    );
+    let func_ty = FuncType::new(store.engine(), None, None);
+    let func = Func::new_async(&mut store, func_ty, move |_caller, _params, _results| {
+        Box::new(async {
+            tokio::task::yield_now().await;
+            Ok(())
+        })
+    });
     run_smoke_test(&mut store, func).await;
     run_smoke_typed_test(&mut store, func).await;
 
@@ -105,7 +101,7 @@ async fn smoke_host_func_with_suspension() -> Result<()> {
     linker.func_new_async(
         "",
         "first",
-        FuncType::new(None, None),
+        FuncType::new(store.engine(), None, None),
         move |_caller, _params, _results| {
             Box::new(async {
                 tokio::task::yield_now().await;
@@ -143,32 +139,26 @@ async fn smoke_host_func_with_suspension() -> Result<()> {
 #[tokio::test]
 async fn recursive_call() {
     let mut store = async_store();
-    let async_wasm_func = Func::new_async(
-        &mut store,
-        FuncType::new(None, None),
-        |_caller, _params, _results| {
-            Box::new(async {
-                tokio::task::yield_now().await;
-                Ok(())
-            })
-        },
-    );
+    let func_ty = FuncType::new(store.engine(), None, None);
+    let async_wasm_func = Func::new_async(&mut store, func_ty, |_caller, _params, _results| {
+        Box::new(async {
+            tokio::task::yield_now().await;
+            Ok(())
+        })
+    });
 
     // Create an imported function which recursively invokes another wasm
     // function asynchronously, although this one is just our own host function
     // which suffices for this test.
-    let func2 = Func::new_async(
-        &mut store,
-        FuncType::new(None, None),
-        move |mut caller, _params, _results| {
-            Box::new(async move {
-                async_wasm_func
-                    .call_async(&mut caller, &[], &mut [])
-                    .await?;
-                Ok(())
-            })
-        },
-    );
+    let func_ty = FuncType::new(store.engine(), None, None);
+    let func2 = Func::new_async(&mut store, func_ty, move |mut caller, _params, _results| {
+        Box::new(async move {
+            async_wasm_func
+                .call_async(&mut caller, &[], &mut [])
+                .await?;
+            Ok(())
+        })
+    });
 
     // Create an instance which calls an async import twice.
     let module = Module::new(
@@ -204,36 +194,30 @@ async fn suspend_while_suspending() {
     // The purpose of this test is intended to stress various cases in how
     // we manage pointers in ways that are not necessarily common but are still
     // possible in safe code.
-    let async_thunk = Func::new_async(
-        &mut store,
-        FuncType::new(None, None),
-        |_caller, _params, _results| Box::new(async { Ok(()) }),
-    );
-    let sync_call_async_thunk = Func::new(
-        &mut store,
-        FuncType::new(None, None),
-        move |mut caller, _params, _results| {
+    let func_ty = FuncType::new(store.engine(), None, None);
+    let async_thunk = Func::new_async(&mut store, func_ty, |_caller, _params, _results| {
+        Box::new(async { Ok(()) })
+    });
+    let func_ty = FuncType::new(store.engine(), None, None);
+    let sync_call_async_thunk =
+        Func::new(&mut store, func_ty, move |mut caller, _params, _results| {
             let mut future = Box::pin(async_thunk.call_async(&mut caller, &[], &mut []));
             let poll = future
                 .as_mut()
                 .poll(&mut Context::from_waker(&noop_waker()));
             assert!(poll.is_ready());
             Ok(())
-        },
-    );
+        });
 
     // A small async function that simply awaits once to pump the loops and
     // then finishes.
-    let async_import = Func::new_async(
-        &mut store,
-        FuncType::new(None, None),
-        move |_caller, _params, _results| {
-            Box::new(async move {
-                tokio::task::yield_now().await;
-                Ok(())
-            })
-        },
-    );
+    let func_ty = FuncType::new(store.engine(), None, None);
+    let async_import = Func::new_async(&mut store, func_ty, move |_caller, _params, _results| {
+        Box::new(async move {
+            tokio::task::yield_now().await;
+            Ok(())
+        })
+    });
 
     let module = Module::new(
         store.engine(),
@@ -264,23 +248,20 @@ async fn suspend_while_suspending() {
 async fn cancel_during_run() {
     let mut store = Store::new(&Engine::new(Config::new().async_support(true)).unwrap(), 0);
 
-    let async_thunk = Func::new_async(
-        &mut store,
-        FuncType::new(None, None),
-        move |mut caller, _params, _results| {
-            assert_eq!(*caller.data(), 0);
-            *caller.data_mut() = 1;
-            let dtor = SetOnDrop(caller);
-            Box::new(async move {
-                // SetOnDrop is not destroyed when dropping the reference of it
-                // here. Instead, it is moved into the future where it's forced
-                // to live in and will be destroyed at the end of the future.
-                let _ = &dtor;
-                tokio::task::yield_now().await;
-                Ok(())
-            })
-        },
-    );
+    let func_ty = FuncType::new(store.engine(), None, None);
+    let async_thunk = Func::new_async(&mut store, func_ty, move |mut caller, _params, _results| {
+        assert_eq!(*caller.data(), 0);
+        *caller.data_mut() = 1;
+        let dtor = SetOnDrop(caller);
+        Box::new(async move {
+            // SetOnDrop is not destroyed when dropping the reference of it
+            // here. Instead, it is moved into the future where it's forced
+            // to live in and will be destroyed at the end of the future.
+            let _ = &dtor;
+            tokio::task::yield_now().await;
+            Ok(())
+        })
+    });
     // Shouldn't have called anything yet...
     assert_eq!(*store.data(), 0);
 
@@ -373,11 +354,10 @@ async fn async_with_pooling_stacks() {
 
     let engine = Engine::new(&config).unwrap();
     let mut store = Store::new(&engine, ());
-    let func = Func::new_async(
-        &mut store,
-        FuncType::new(None, None),
-        move |_caller, _params, _results| Box::new(async { Ok(()) }),
-    );
+    let func_ty = FuncType::new(store.engine(), None, None);
+    let func = Func::new_async(&mut store, func_ty, move |_caller, _params, _results| {
+        Box::new(async { Ok(()) })
+    });
 
     run_smoke_test(&mut store, func).await;
     run_smoke_typed_test(&mut store, func).await;
@@ -399,7 +379,7 @@ async fn async_host_func_with_pooling_stacks() -> Result<()> {
     linker.func_new_async(
         "",
         "",
-        FuncType::new(None, None),
+        FuncType::new(store.engine(), None, None),
         move |_caller, _params, _results| Box::new(async { Ok(()) }),
     )?;
 
@@ -588,6 +568,7 @@ async fn recursive_async() -> Result<()> {
 async fn linker_module_command() -> Result<()> {
     let mut store = async_store();
     let mut linker = Linker::new(store.engine());
+
     let module1 = Module::new(
         store.engine(),
         r#"
@@ -603,6 +584,7 @@ async fn linker_module_command() -> Result<()> {
             )
         "#,
     )?;
+
     let module2 = Module::new(
         store.engine(),
         r#"
diff --git a/tests/all/call_hook.rs b/tests/all/call_hook.rs
index 9d47ecb85..04b0ee2f8 100644
--- a/tests/all/call_hook.rs
+++ b/tests/all/call_hook.rs
@@ -32,9 +32,14 @@ fn call_wrapped_func() -> Result<(), Error> {
             assert_eq!(d, 4.0);
         },
     ));
+    let func_ty = FuncType::new(
+        store.engine(),
+        [ValType::I32, ValType::I64, ValType::F32, ValType::F64],
+        [],
+    );
     funcs.push(Func::new(
         &mut store,
-        FuncType::new([ValType::I32, ValType::I64, ValType::F32, ValType::F64], []),
+        func_ty,
         |caller: Caller<State>, params, results| {
             verify(caller.data());
 
@@ -47,20 +52,21 @@ fn call_wrapped_func() -> Result<(), Error> {
             Ok(())
         },
     ));
+    let func_ty = FuncType::new(
+        store.engine(),
+        [ValType::I32, ValType::I64, ValType::F32, ValType::F64],
+        [],
+    );
     funcs.push(unsafe {
-        Func::new_unchecked(
-            &mut store,
-            FuncType::new([ValType::I32, ValType::I64, ValType::F32, ValType::F64], []),
-            |caller: Caller<State>, space| {
-                verify(caller.data());
-
-                assert_eq!(space[0].get_i32(), 1i32);
-                assert_eq!(space[1].get_i64(), 2i64);
-                assert_eq!(space[2].get_f32(), 3.0f32.to_bits());
-                assert_eq!(space[3].get_f64(), 4.0f64.to_bits());
-                Ok(())
-            },
-        )
+        Func::new_unchecked(&mut store, func_ty, |caller: Caller<State>, space| {
+            verify(caller.data());
+
+            assert_eq!(space[0].get_i32(), 1i32);
+            assert_eq!(space[1].get_i64(), 2i64);
+            assert_eq!(space[2].get_f32(), 3.0f32.to_bits());
+            assert_eq!(space[3].get_f64(), 4.0f64.to_bits());
+            Ok(())
+        })
     });
 
     let mut n = 0;
diff --git a/tests/all/component_model/dynamic.rs b/tests/all/component_model/dynamic.rs
index eaf60f3be..f83f550d8 100644
--- a/tests/all/component_model/dynamic.rs
+++ b/tests/all/component_model/dynamic.rs
@@ -684,7 +684,7 @@ fn introspection() -> Result<()> {
 
     let component_ty = linker.substituted_component_type(&component)?;
 
-    let mut imports = component_ty.imports();
+    let mut imports = component_ty.imports(linker.engine());
     assert_eq!(imports.len(), 3);
     let (name, res_ty) = imports.next().unwrap();
     assert_eq!(name, "res");
@@ -698,24 +698,24 @@ fn introspection() -> Result<()> {
     let ComponentItem::ComponentInstance(ai_ty) = ai_ty else {
         panic!("`ai` import item of wrong type")
     };
-    assert_eq!(ai_ty.exports().len(), 0);
+    assert_eq!(ai_ty.exports(linker.engine()).len(), 0);
 
     let (name, bi_ty) = imports.next().unwrap();
     assert_eq!(name, "bi");
     let ComponentItem::ComponentInstance(bi_ty) = bi_ty else {
         panic!("`bi` import item of wrong type")
     };
-    let mut bi_exports = bi_ty.exports();
+    let mut bi_exports = bi_ty.exports(linker.engine());
     assert_eq!(bi_exports.len(), 1);
     let (name, bi_m_ty) = bi_exports.next().unwrap();
     assert_eq!(name, "m");
     let ComponentItem::Module(bi_m_ty) = bi_m_ty else {
         panic!("`bi.m` import item of wrong type")
     };
-    assert_eq!(bi_m_ty.imports().len(), 0);
-    assert_eq!(bi_m_ty.exports().len(), 0);
+    assert_eq!(bi_m_ty.imports(linker.engine()).len(), 0);
+    assert_eq!(bi_m_ty.exports(linker.engine()).len(), 0);
 
-    let mut exports = component_ty.exports();
+    let mut exports = component_ty.exports(linker.engine());
     assert_eq!(exports.len(), 11);
 
     let (name, run_ty) = exports.next().unwrap();
@@ -734,44 +734,44 @@ fn introspection() -> Result<()> {
     let ComponentItem::ComponentInstance(i_ty) = i_ty else {
         panic!("`i` export item of wrong type")
     };
-    let mut i_ty_exports = i_ty.exports();
+    let mut i_ty_exports = i_ty.exports(linker.engine());
     assert_eq!(i_ty_exports.len(), 1);
     let (name, i_i_ty) = i_ty_exports.next().unwrap();
     assert_eq!(name, "i");
     let ComponentItem::ComponentInstance(i_i_ty) = i_i_ty else {
         panic!("`i.i` import item of wrong type")
     };
-    let mut i_i_ty_exports = i_i_ty.exports();
+    let mut i_i_ty_exports = i_i_ty.exports(linker.engine());
     assert_eq!(i_i_ty_exports.len(), 1);
     let (name, i_i_m_ty) = i_i_ty_exports.next().unwrap();
     assert_eq!(name, "m");
     let ComponentItem::Module(i_i_m_ty) = i_i_m_ty else {
         panic!("`i.i.m` import item of wrong type")
     };
-    assert_eq!(i_i_m_ty.imports().len(), 0);
-    assert_eq!(i_i_m_ty.exports().len(), 0);
+    assert_eq!(i_i_m_ty.imports(linker.engine()).len(), 0);
+    assert_eq!(i_i_m_ty.exports(linker.engine()).len(), 0);
 
     let (name, r_ty) = exports.next().unwrap();
     assert_eq!(name, "r");
     let ComponentItem::ComponentInstance(r_ty) = r_ty else {
         panic!("`r` export item of wrong type")
     };
-    assert_eq!(r_ty.exports().len(), 0);
+    assert_eq!(r_ty.exports(linker.engine()).len(), 0);
 
     let (name, r2_ty) = exports.next().unwrap();
     assert_eq!(name, "r2");
     let ComponentItem::ComponentInstance(r2_ty) = r2_ty else {
         panic!("`r2` export item of wrong type")
     };
-    let mut r2_exports = r2_ty.exports();
+    let mut r2_exports = r2_ty.exports(linker.engine());
     assert_eq!(r2_exports.len(), 1);
     let (name, r2_m_ty) = r2_exports.next().unwrap();
     assert_eq!(name, "m");
     let ComponentItem::Module(r2_m_ty) = r2_m_ty else {
         panic!("`r2.m` export item of wrong type")
     };
-    assert_eq!(r2_m_ty.imports().len(), 0);
-    assert_eq!(r2_m_ty.exports().len(), 0);
+    assert_eq!(r2_m_ty.imports(linker.engine()).len(), 0);
+    assert_eq!(r2_m_ty.exports(linker.engine()).len(), 0);
 
     let (name, b_ty) = exports.next().unwrap();
     assert_eq!(name, "b");
diff --git a/tests/all/coredump.rs b/tests/all/coredump.rs
index 492bce76d..1290bd825 100644
--- a/tests/all/coredump.rs
+++ b/tests/all/coredump.rs
@@ -17,7 +17,7 @@ fn coredump_attached_to_error() -> Result<()> {
     "#;
 
     let module = Module::new(store.engine(), wat)?;
-    let hello_type = FuncType::new(None, None);
+    let hello_type = FuncType::new(store.engine(), None, None);
     let hello_func = Func::new(&mut store, hello_type, |_, _, _| bail!("test 123"));
 
     let instance = Instance::new(&mut store, &module, &[hello_func.into()])?;
diff --git a/tests/all/epoch_interruption.rs b/tests/all/epoch_interruption.rs
index 51d4b831c..3d6af5aaa 100644
--- a/tests/all/epoch_interruption.rs
+++ b/tests/all/epoch_interruption.rs
@@ -21,7 +21,7 @@ fn make_env<T>(engine: &Engine) -> Linker<T> {
         .func_new(
             "",
             "bump_epoch",
-            FuncType::new(None, None),
+            FuncType::new(&engine, None, None),
             move |_caller, _params, _results| {
                 engine.increment_epoch();
                 Ok(())
@@ -418,7 +418,7 @@ async fn drop_future_on_epoch_yield() {
         .func_new(
             "",
             "oops",
-            FuncType::new(None, None),
+            FuncType::new(&engine, None, None),
             move |_caller, _params, _results| {
                 panic!("Should not have reached this point!");
             },
@@ -428,7 +428,7 @@ async fn drop_future_on_epoch_yield() {
         .func_new(
             "",
             "im_alive",
-            FuncType::new(None, None),
+            FuncType::new(&engine, None, None),
             move |_caller, _params, _results| {
                 alive_flag_clone.store(true, Ordering::Release);
                 Ok(())
diff --git a/tests/all/func.rs b/tests/all/func.rs
index fab609520..2cd3e88a7 100644
--- a/tests/all/func.rs
+++ b/tests/all/func.rs
@@ -71,16 +71,17 @@ fn call_wasm_to_array() -> Result<()> {
     )?;
     let mut store = Store::<()>::default();
     let module = Module::new(store.engine(), &wasm)?;
-    let import_func = Func::new(
-        &mut store,
-        FuncType::new(vec![], vec![ValType::I32, ValType::I32, ValType::I32]),
-        |_, _params, results| {
-            results[0] = Val::I32(1);
-            results[1] = Val::I32(2);
-            results[2] = Val::I32(3);
-            Ok(())
-        },
+    let func_ty = FuncType::new(
+        store.engine(),
+        vec![],
+        vec![ValType::I32, ValType::I32, ValType::I32],
     );
+    let import_func = Func::new(&mut store, func_ty, |_, _params, results| {
+        results[0] = Val::I32(1);
+        results[1] = Val::I32(2);
+        results[2] = Val::I32(3);
+        Ok(())
+    });
     let instance = Instance::new(&mut store, &module, &[import_func.into()])?;
     let func = instance
         .get_typed_func::<(), (i32, i32, i32)>(&mut store, "run")
@@ -133,19 +134,17 @@ fn call_native_to_native() -> Result<()> {
 fn call_native_to_array() -> Result<()> {
     let mut store = Store::<()>::default();
 
-    let func = Func::new(
-        &mut store,
-        FuncType::new(
-            [ValType::I32, ValType::I32, ValType::I32],
-            [ValType::I32, ValType::I32, ValType::I32],
-        ),
-        |_caller, params, results| {
-            results[0] = params[2].clone();
-            results[1] = params[0].clone();
-            results[2] = params[1].clone();
-            Ok(())
-        },
+    let func_ty = FuncType::new(
+        store.engine(),
+        [ValType::I32, ValType::I32, ValType::I32],
+        [ValType::I32, ValType::I32, ValType::I32],
     );
+    let func = Func::new(&mut store, func_ty, |_caller, params, results| {
+        results[0] = params[2].clone();
+        results[1] = params[0].clone();
+        results[2] = params[1].clone();
+        Ok(())
+    });
     let func = func.typed::<(i32, i32, i32), (i32, i32, i32)>(&store)?;
     let results = func.call(&mut store, (1, 2, 3))?;
     assert_eq!(results, (3, 1, 2));
@@ -204,19 +203,17 @@ fn call_array_to_native() -> Result<()> {
 #[cfg_attr(miri, ignore)]
 fn call_array_to_array() -> Result<()> {
     let mut store = Store::<()>::default();
-    let func = Func::new(
-        &mut store,
-        FuncType::new(
-            [ValType::I32, ValType::I32, ValType::I32],
-            [ValType::I32, ValType::I32, ValType::I32],
-        ),
-        |_caller, params, results| {
-            results[0] = params[2].clone();
-            results[1] = params[0].clone();
-            results[2] = params[1].clone();
-            Ok(())
-        },
+    let func_ty = FuncType::new(
+        store.engine(),
+        [ValType::I32, ValType::I32, ValType::I32],
+        [ValType::I32, ValType::I32, ValType::I32],
     );
+    let func = Func::new(&mut store, func_ty, |_caller, params, results| {
+        results[0] = params[2].clone();
+        results[1] = params[0].clone();
+        results[2] = params[1].clone();
+        Ok(())
+    });
     let mut results = [Val::I32(0), Val::I32(0), Val::I32(0)];
     func.call(
         &mut store,
@@ -664,13 +661,13 @@ fn get_from_wrapper() {
 #[cfg_attr(miri, ignore)]
 fn get_from_signature() {
     let mut store = Store::<()>::default();
-    let ty = FuncType::new(None, None);
+    let ty = FuncType::new(store.engine(), None, None);
     let f = Func::new(&mut store, ty, |_, _, _| panic!());
     assert!(f.typed::<(), ()>(&store).is_ok());
     assert!(f.typed::<(), i32>(&store).is_err());
     assert!(f.typed::<i32, ()>(&store).is_err());
 
-    let ty = FuncType::new(Some(ValType::I32), Some(ValType::F64));
+    let ty = FuncType::new(store.engine(), Some(ValType::I32), Some(ValType::F64));
     let f = Func::new(&mut store, ty, |_, _, _| panic!());
     assert!(f.typed::<(), ()>(&store).is_err());
     assert!(f.typed::<(), i32>(&store).is_err());
@@ -820,7 +817,7 @@ fn caller_memory() -> anyhow::Result<()> {
 #[cfg_attr(miri, ignore)]
 fn func_write_nothing() -> anyhow::Result<()> {
     let mut store = Store::<()>::default();
-    let ty = FuncType::new(None, Some(ValType::I32));
+    let ty = FuncType::new(store.engine(), None, Some(ValType::I32));
     let f = Func::new(&mut store, ty, |_, _, _| Ok(()));
     let err = f.call(&mut store, &[], &mut [Val::I32(0)]).unwrap_err();
     assert!(err
@@ -902,14 +899,12 @@ fn externref_signature_no_reference_types() -> anyhow::Result<()> {
     config.wasm_reference_types(false);
     let mut store = Store::new(&Engine::new(&config)?, ());
     Func::wrap(&mut store, |_: Option<Func>| {});
-    Func::new(
-        &mut store,
-        FuncType::new(
-            [ValType::FuncRef, ValType::ExternRef].iter().cloned(),
-            [ValType::FuncRef, ValType::ExternRef].iter().cloned(),
-        ),
-        |_, _, _| Ok(()),
+    let func_ty = FuncType::new(
+        store.engine(),
+        [ValType::FuncRef, ValType::ExternRef].iter().cloned(),
+        [ValType::FuncRef, ValType::ExternRef].iter().cloned(),
     );
+    Func::new(&mut store, func_ty, |_, _, _| Ok(()));
     Ok(())
 }
 
@@ -1004,7 +999,8 @@ fn trap_doesnt_leak() -> anyhow::Result<()> {
     // test that `Func::new` is correct
     let canary2 = Canary::default();
     let dtor2_run = canary2.0.clone();
-    let f2 = Func::new(&mut store, FuncType::new(None, None), move |_, _, _| {
+    let func_ty = FuncType::new(store.engine(), None, None);
+    let f2 = Func::new(&mut store, func_ty, move |_, _, _| {
         let _ = &canary2;
         bail!("")
     });
diff --git a/tests/all/funcref.rs b/tests/all/funcref.rs
index eab2d5939..0500009b8 100644
--- a/tests/all/funcref.rs
+++ b/tests/all/funcref.rs
@@ -141,14 +141,11 @@ fn func_new_returns_wrong_store() -> anyhow::Result<()> {
         let f1 = Func::wrap(&mut store1, move || {
             let _ = &set;
         });
-        let f2 = Func::new(
-            &mut store2,
-            FuncType::new(None, Some(ValType::FuncRef)),
-            move |_, _, results| {
-                results[0] = f1.clone().into();
-                Ok(())
-            },
-        );
+        let func_ty = FuncType::new(store2.engine(), None, Some(ValType::FuncRef));
+        let f2 = Func::new(&mut store2, func_ty, move |_, _, results| {
+            results[0] = f1.clone().into();
+            Ok(())
+        });
         assert!(f2.call(&mut store2, &[], &mut [Val::I32(0)]).is_err());
     }
     assert!(dropped.load(SeqCst));
diff --git a/tests/all/host_funcs.rs b/tests/all/host_funcs.rs
index 76033acd6..23e37a191 100644
--- a/tests/all/host_funcs.rs
+++ b/tests/all/host_funcs.rs
@@ -12,7 +12,7 @@ fn async_required() {
     drop(linker.func_new_async(
         "",
         "",
-        FuncType::new(None, None),
+        FuncType::new(&engine, None, None),
         move |_caller, _params, _results| Box::new(async { Ok(()) }),
     ));
 }
@@ -493,10 +493,10 @@ fn new_from_signature() -> Result<()> {
     let engine = Engine::default();
     let mut linker = Linker::new(&engine);
 
-    let ty = FuncType::new(None, None);
+    let ty = FuncType::new(&engine, None, None);
     linker.func_new("", "f1", ty, |_, _, _| panic!())?;
 
-    let ty = FuncType::new(Some(ValType::I32), Some(ValType::F64));
+    let ty = FuncType::new(&engine, Some(ValType::I32), Some(ValType::F64));
     linker.func_new("", "f2", ty, |_, _, _| panic!())?;
 
     let mut store = Store::new(&engine, ());
@@ -603,7 +603,7 @@ fn call_wrapped_func() -> Result<()> {
 fn func_return_nothing() -> Result<()> {
     let engine = Engine::default();
     let mut linker = Linker::new(&engine);
-    let ty = FuncType::new(None, Some(ValType::I32));
+    let ty = FuncType::new(&engine, None, Some(ValType::I32));
     linker.func_new("", "", ty, |_, _, _| Ok(()))?;
 
     let mut store = Store::new(&engine, ());
diff --git a/tests/all/import_calling_export.rs b/tests/all/import_calling_export.rs
index 247fbcd1e..a0f1d7482 100644
--- a/tests/all/import_calling_export.rs
+++ b/tests/all/import_calling_export.rs
@@ -19,18 +19,15 @@ fn test_import_calling_export() {
     let mut store = Store::<Option<Func>>::default();
     let module = Module::new(store.engine(), WAT).expect("failed to create module");
 
-    let callback_func = Func::new(
-        &mut store,
-        FuncType::new(None, None),
-        move |mut caller, _, _| {
-            caller
-                .data()
-                .unwrap()
-                .call(&mut caller, &[], &mut [])
-                .expect("expected function not to trap");
-            Ok(())
-        },
-    );
+    let func_ty = FuncType::new(store.engine(), None, None);
+    let callback_func = Func::new(&mut store, func_ty, move |mut caller, _, _| {
+        caller
+            .data()
+            .unwrap()
+            .call(&mut caller, &[], &mut [])
+            .expect("expected function not to trap");
+        Ok(())
+    });
 
     let imports = vec![callback_func.into()];
     let instance = Instance::new(&mut store, &module, imports.as_slice())
@@ -64,15 +61,12 @@ fn test_returns_incorrect_type() -> Result<()> {
     let mut store = Store::<()>::default();
     let module = Module::new(store.engine(), WAT)?;
 
-    let callback_func = Func::new(
-        &mut store,
-        FuncType::new(None, Some(ValType::I32)),
-        |_, _, results| {
-            // Evil! Returns I64 here instead of promised in the signature I32.
-            results[0] = Val::I64(228);
-            Ok(())
-        },
-    );
+    let func_ty = FuncType::new(store.engine(), None, Some(ValType::I32));
+    let callback_func = Func::new(&mut store, func_ty, |_, _, results| {
+        // Evil! Returns I64 here instead of promised in the signature I32.
+        results[0] = Val::I64(228);
+        Ok(())
+    });
 
     let imports = vec![callback_func.into()];
     let instance = Instance::new(&mut store, &module, imports.as_slice())?;
diff --git a/tests/all/import_indexes.rs b/tests/all/import_indexes.rs
index 6383d576e..6a92ddd28 100644
--- a/tests/all/import_indexes.rs
+++ b/tests/all/import_indexes.rs
@@ -17,12 +17,13 @@ fn same_import_names_still_distinct() -> anyhow::Result<()> {
     "#;
 
     let mut store = Store::<()>::default();
-    let module = Module::new(store.engine(), WAT)?;
+    let engine = store.engine().clone();
+    let module = Module::new(&engine, WAT)?;
 
     let imports = [
         Func::new(
             &mut store,
-            FuncType::new(None, Some(ValType::I32)),
+            FuncType::new(&engine, None, Some(ValType::I32)),
             |_, params, results| {
                 assert!(params.is_empty());
                 assert_eq!(results.len(), 1);
@@ -33,7 +34,7 @@ fn same_import_names_still_distinct() -> anyhow::Result<()> {
         .into(),
         Func::new(
             &mut store,
-            FuncType::new(None, Some(ValType::F32)),
+            FuncType::new(&engine, None, Some(ValType::F32)),
             |_, params, results| {
                 assert!(params.is_empty());
                 assert_eq!(results.len(), 1);
diff --git a/tests/all/pooling_allocator.rs b/tests/all/pooling_allocator.rs
index dbd6875b3..86f221306 100644
--- a/tests/all/pooling_allocator.rs
+++ b/tests/all/pooling_allocator.rs
@@ -966,7 +966,7 @@ async fn total_stacks_limit() -> Result<()> {
     linker.func_new_async(
         "async",
         "yield",
-        FuncType::new([], []),
+        FuncType::new(&engine, [], []),
         |_caller, _params, _results| {
             Box::new(async {
                 tokio::task::yield_now().await;
diff --git a/tests/all/stack_creator.rs b/tests/all/stack_creator.rs
index 952723890..9545908d1 100644
--- a/tests/all/stack_creator.rs
+++ b/tests/all/stack_creator.rs
@@ -135,7 +135,7 @@ async fn called_on_custom_heap_stack() -> Result<()> {
         "#,
     )?;
 
-    let ty = FuncType::new([], [ValType::I64]);
+    let ty = FuncType::new(store.engine(), [], [ValType::I64]);
     let host_func = Func::new(&mut store, ty, move |_caller, _params, results| {
         let foo = 42;
         // output an address on the stack
diff --git a/tests/all/traps.rs b/tests/all/traps.rs
index 9b3ae2764..3fc509fb4 100644
--- a/tests/all/traps.rs
+++ b/tests/all/traps.rs
@@ -17,7 +17,7 @@ fn test_trap_return() -> Result<()> {
     "#;
 
     let module = Module::new(store.engine(), wat)?;
-    let hello_type = FuncType::new(None, None);
+    let hello_type = FuncType::new(store.engine(), None, None);
     let hello_func = Func::new(&mut store, hello_type, |_, _, _| bail!("test 123"));
 
     let instance = Instance::new(&mut store, &module, &[hello_func.into()])?;
@@ -45,7 +45,7 @@ fn test_anyhow_error_return() -> Result<()> {
     "#;
 
     let module = Module::new(store.engine(), wat)?;
-    let hello_type = FuncType::new(None, None);
+    let hello_type = FuncType::new(store.engine(), None, None);
     let hello_func = Func::new(&mut store, hello_type, |_, _, _| {
         Err(anyhow::Error::msg("test 1234"))
     });
@@ -83,7 +83,7 @@ fn test_trap_return_downcast() -> Result<()> {
     impl std::error::Error for MyTrap {}
 
     let module = Module::new(store.engine(), wat)?;
-    let hello_type = FuncType::new(None, None);
+    let hello_type = FuncType::new(store.engine(), None, None);
     let hello_func = Func::new(&mut store, hello_type, |_, _, _| {
         Err(anyhow::Error::from(MyTrap))
     });
@@ -169,7 +169,7 @@ fn test_trap_through_host() -> Result<()> {
 
     let host_func_a = Func::new(
         &mut store,
-        FuncType::new(vec![], vec![]),
+        FuncType::new(&engine, vec![], vec![]),
         |mut caller, _args, _results| {
             caller
                 .get_export("b")
@@ -182,7 +182,7 @@ fn test_trap_through_host() -> Result<()> {
     );
     let host_func_b = Func::new(
         &mut store,
-        FuncType::new(vec![], vec![]),
+        FuncType::new(&engine, vec![], vec![]),
         |mut caller, _args, _results| {
             caller
                 .get_export("c")
@@ -243,7 +243,7 @@ fn test_trap_trace_cb() -> Result<()> {
         )
     "#;
 
-    let fn_type = FuncType::new(None, None);
+    let fn_type = FuncType::new(store.engine(), None, None);
     let fn_func = Func::new(&mut store, fn_type, |_, _, _| bail!("cb throw"));
 
     let module = Module::new(store.engine(), wat)?;
@@ -382,7 +382,7 @@ fn trap_start_function_import() -> Result<()> {
     )?;
 
     let module = Module::new(store.engine(), &binary)?;
-    let sig = FuncType::new(None, None);
+    let sig = FuncType::new(store.engine(), None, None);
     let func = Func::new(&mut store, sig, |_, _, _| bail!("user trap"));
     let err = Instance::new(&mut store, &module, &[func.into()]).unwrap_err();
     assert!(format!("{err:?}").contains("user trap"));
@@ -404,7 +404,7 @@ fn rust_panic_import() -> Result<()> {
     )?;
 
     let module = Module::new(store.engine(), &binary)?;
-    let sig = FuncType::new(None, None);
+    let sig = FuncType::new(store.engine(), None, None);
     let func = Func::new(&mut store, sig, |_, _, _| panic!("this is a panic"));
     let func2 = Func::wrap(&mut store, || panic!("this is another panic"));
     let instance = Instance::new(&mut store, &module, &[func.into(), func2.into()])?;
@@ -448,7 +448,7 @@ fn rust_catch_panic_import() -> Result<()> {
 
     let module = Module::new(store.engine(), &binary)?;
     let num_panics = std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0));
-    let sig = FuncType::new(None, None);
+    let sig = FuncType::new(store.engine(), None, None);
     let panic = Func::new(&mut store, sig, {
         let num_panics = num_panics.clone();
         move |_, _, _| {
@@ -493,7 +493,7 @@ fn rust_panic_start_function() -> Result<()> {
     )?;
 
     let module = Module::new(store.engine(), &binary)?;
-    let sig = FuncType::new(None, None);
+    let sig = FuncType::new(store.engine(), None, None);
     let func = Func::new(&mut store, sig, |_, _, _| panic!("this is a panic"));
     let err = panic::catch_unwind(AssertUnwindSafe(|| {
         drop(Instance::new(&mut store, &module, &[func.into()]));
@@ -1535,14 +1535,14 @@ fn dont_see_stale_stack_walking_registers() -> Result<()> {
 
     let host_start = Func::new(
         &mut store,
-        FuncType::new([], []),
+        FuncType::new(&engine, [], []),
         |_caller, _args, _results| Ok(()),
     );
     linker.define(&store, "", "host_start", host_start)?;
 
     let host_get_trap = Func::new(
         &mut store,
-        FuncType::new([], []),
+        FuncType::new(&engine, [], []),
         |_caller, _args, _results| Err(anyhow::anyhow!("trap!!!")),
     );
     linker.define(&store, "", "host_get_trap", host_get_trap)?;
@@ -1593,7 +1593,7 @@ fn same_module_multiple_stores() -> Result<()> {
     let stacks = Arc::new(Mutex::new(vec![]));
 
     let mut store3 = Store::new(&engine, ());
-    let f3 = Func::new(&mut store3, FuncType::new([], []), {
+    let f3 = Func::new(&mut store3, FuncType::new(&engine, [], []), {
         let stacks = stacks.clone();
         move |caller, _params, _results| {
             stacks
@@ -1609,7 +1609,7 @@ fn same_module_multiple_stores() -> Result<()> {
     let instance3 = Instance::new(&mut store3, &module, &[f3.into(), call_ref3.into()])?;
 
     let mut store2 = Store::new(&engine, store3);
-    let f2 = Func::new(&mut store2, FuncType::new([], []), {
+    let f2 = Func::new(&mut store2, FuncType::new(&engine, [], []), {
         let stacks = stacks.clone();
         move |mut caller, _params, _results| {
             stacks
@@ -1630,7 +1630,7 @@ fn same_module_multiple_stores() -> Result<()> {
     let instance2 = Instance::new(&mut store2, &module, &[f2.into(), call_ref2.into()])?;
 
     let mut store1 = Store::new(&engine, store2);
-    let f1 = Func::new(&mut store1, FuncType::new([], []), {
+    let f1 = Func::new(&mut store1, FuncType::new(&engine, [], []), {
         let stacks = stacks.clone();
         move |mut caller, _params, _results| {
             stacks