Repository: bytecodealliance/wasmtime
Pull request #7892 (2024-02-07 23:41:45)

Refactor `wasmtime::FuncType` to hold a handle to its registered type
Rather than holding a copy of the type directly, it now holds a `RegisteredType` which internally is

* A `VMSharedTypeIndex` pointing into the engine's types registry.
* An `Arc` handle to the engine's type registry.
* An `Arc` handle to the actual type.

The last exists only to keep it so that accessing a `wasmtime::FuncType`'s parameters and results fast, avoiding any new locking on call hot paths.

This is helping set the stage for further types and `TypeRegistry` refactors needed for Wasm GC.

<!--
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
-->


github-actions[bot]: #### Subscribe to Label Action

cc @fitzgen, @peterhuene

<details>
This issue or pull request has been labeled: "fuzzing", "wasmtime:api"

Thus the following users have been cc'd because of the following labels:

* fitzgen: fuzzing
* peterhuene: wasmtime:api

To subscribe or unsubscribe from this label, edit the <code>.github/subscribe-to-label.json</code> configuration file.

[Learn more.](https://github.com/bytecodealliance/subscribe-to-label-action)
</details>
fitzgen: > I'm a little wary in how the type-related stuff feels "heavyweight" with lots of `Arc`

We could make it a single `Arc` if we moved all of `OccupiedEntry` into `RegisteredType` and had an `Arc<RegisteredType>` instead of an `Arc<WasmFuncType>`. Most importantly, the refcount would be in the `RegisteredType` and accessible without the whole registry. Then dropping and cloning a `RegisteredType` wouldn't take a lock at all, it would just decrement its reference count. If the reference count reached zero, then we would either:

* Leave it at zero and not actualy remove the entry yet, instead relying on some subsequent operation on the registry doing a "sweep" for zero-refcount entries at some point in time, like the next time we insert into the registry. A bit hand-wavy, and does leave us with some floating garbage.

* Have the `RegisteredType` hold a weak reference to the to whole type registry, and then only take the lock to remove the entry once the refcount reaches zero. Actual ref counting operations that don't reach zero still wouldn't need the lock. This does mean that we would add back another smart pointer though.

I'm happy to explore either option, or even something else, if you can think of anything. But I think it would be best to do so in a follow up PR.
fitzgen: > One solution would be to store `Engine` in more places but then that also feeds back into the above where types become more heavyweight with more `Arc`s.

Another thing I was thinking that might make sense, but I haven't fully explored yet:

* Stop putting the type registry itself in an `Arc`, just store it in the `Engine` directly.
* Anything that had an arc of the registry, now has an `Engine`.
* We can make more things hold an `Engine` as necessary.

I don't think this would be a magic solution, but I think it would at least make more clear the relationship between an engine and a registry and make it so that if you need access to both things you don't need to choose between

* holding two arcs, or
* an extra indirection to access the registry.

Again, this is something for future exploration, IMO.
fitzgen: FWIW, filed https://github.com/WebAssembly/wasm-c-api/issues/190 so that we can eventually implement the relevant bits of the Wasm C API again.
alexcrichton: Yeah I think it's reasonable to explore this all in the future, I don't have any great ideas myself so it's mostly otherwise about is this semantically what we want which I believe is "yes", and then overhead/perf needs a guiding use case to help inform it.

Also IMO we need to support the C API as-is while it hasn't changed, so it means that `wasm_functype_t` will need to buffer-up the arguments and then when it first gets "connectd to" something with an engine that's where `FuncType::new` would happen.

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 781e1da90..72d09dae3 100644
--- a/cranelift/codegen/src/ir/memflags.rs
+++ b/cranelift/codegen/src/ir/memflags.rs
@@ -127,15 +127,16 @@ impl MemFlags {
     /// Set endianness of the memory access.
     pub fn set_endianness(&mut self, endianness: Endianness) {
         *self = self.with_endianness(endianness);
-        assert!(!(self.read(FlagBit::LittleEndian) && self.read(FlagBit::BigEndian)));
     }
 
     /// Set endianness of the memory access, returning new flags.
     pub const fn with_endianness(self, endianness: Endianness) -> Self {
-        match endianness {
+        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.
@@ -214,12 +215,12 @@ impl MemFlags {
     /// 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 = self.with_heap();
     }
 
     /// Set the `heap` bit, returning new flags.
     pub const fn with_heap(self) -> Self {
+        assert!(!self.table() && !self.vmctx());
         self.with(FlagBit::Heap)
     }
 
@@ -238,12 +239,12 @@ impl MemFlags {
     /// 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 = self.with_table();
     }
 
     /// Set the `table` bit, returning new flags.
     pub const fn with_table(self) -> Self {
+        assert!(!self.heap() && !self.vmctx());
         self.with(FlagBit::Table)
     }
 
@@ -262,12 +263,12 @@ impl MemFlags {
     /// 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 = self.with_vmctx();
     }
 
     /// Set the `vmctx` bit, returning new flags.
     pub const fn with_vmctx(self) -> Self {
+        assert!(!self.heap() && !self.table());
         self.with(FlagBit::Vmctx)
     }
 
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/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");


Test patch:
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