Repository: bytecodealliance/wasmtime
Pull request #5278 (2022-11-16 00:53:41)

Don't use %rcx directly with CoffTlsGetAddr
Avoid naming `%rcx` as written by the `CoffTlsGetAddr` pseudo-instruction in the x64 backend, and instead emit a fixed-def constraint for a fresh `VReg` and `%rcx`.
<!--

Please ensure that the following steps are all taken care of before submitting
the PR.

- [ ] This has been discussed in issue #..., or if not, please tell us why
  here.
- [ ] A short description of what this does, why it is needed; if the
  description becomes long, the matter should probably be discussed in an issue
  first.
- [ ] This PR contains test cases, if meaningful.
- [ ] A reviewer from the core maintainer team has been assigned for this PR.
  If you don't know who could review this, please indicate so. The list of
  suggested reviewers on the right can help you.

Please ensure all communication adheres to the [code of
conduct](https://github.com/bytecodealliance/wasmtime/blob/master/CODE_OF_CONDUCT.md).
-->



Patch:
diff --git a/RELEASES.md b/RELEASES.md
index d1a6dbc1a..6cc80f22c 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -83,6 +83,28 @@ Unreleased.
 
 --------------------------------------------------------------------------------
 
+## 2.0.2
+
+Released 2022-11-10.
+
+### Fixed
+
+* [CVE-2022-39392] - modules may perform out-of-bounds reads/writes when the
+  pooling allocator was configured with `memory_pages: 0`.
+
+* [CVE-2022-39393] - data can be leaked between instances when using the pooling
+  allocator.
+
+* [CVE-2022-39394] - An incorrect Rust signature for the C API
+  `wasmtime_trap_code` function could lead to an out-of-bounds write of three
+  zero bytes.
+
+[CVE-2022-39392]: https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-44mr-8vmm-wjhg
+[CVE-2022-39393]: https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-wh6w-3828-g9qf
+[CVE-2022-39394]: https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-h84q-m8rr-3v9q
+
+--------------------------------------------------------------------------------
+
 ## 2.0.1
 
 Released 2022-10-27.
@@ -146,6 +168,43 @@ Released 2022-10-20
 
 --------------------------------------------------------------------------------
 
+## 1.0.2
+
+Released 2022-11-10.
+
+### Fixed
+
+* [CVE-2022-39392] - modules may perform out-of-bounds reads/writes when the
+  pooling allocator was configured with `memory_pages: 0`.
+
+* [CVE-2022-39393] - data can be leaked between instances when using the pooling
+  allocator.
+
+* [CVE-2022-39394] - An incorrect Rust signature for the C API
+  `wasmtime_trap_code` function could lead to an out-of-bounds write of three
+  zero bytes.
+
+--------------------------------------------------------------------------------
+
+## 1.0.1
+
+Released 2022-09-26
+
+This is a patch release that incorporates a fix for a miscompilation of an
+atomic-CAS operator on aarch64. The instruction is not usable from Wasmtime
+with default settings, but may be used if the Wasm atomics extension is
+enabled. The bug may also be reachable via other uses of Cranelift. Thanks to
+@bjorn3 for reporting and debugging this issue!
+
+### Fixed
+
+* Fixed a miscompilation of `atomic_cas` on aarch64. The output register was
+  swapped with a temporary register in the register-allocator constraints.
+  [#4959](https://github.com/bytecodealliance/wasmtime/pull/4959)
+  [#4960](https://github.com/bytecodealliance/wasmtime/pull/4960)
+
+--------------------------------------------------------------------------------
+
 ## 1.0.0
 
 Released 2022-09-20
diff --git a/benches/call.rs b/benches/call.rs
index f1e5790b5..9ec51d35d 100644
--- a/benches/call.rs
+++ b/benches/call.rs
@@ -117,7 +117,7 @@ fn bench_host_to_wasm<Params, Results>(
     // below.
     c.bench_function(&format!("host-to-wasm - typed - {}", name), |b| {
         let typed = instance
-            .get_typed_func::<Params, Results, _>(&mut *store, name)
+            .get_typed_func::<Params, Results>(&mut *store, name)
             .unwrap();
         b.iter(|| {
             let results = if is_async.use_async() {
@@ -370,7 +370,7 @@ fn wasm_to_host(c: &mut Criterion) {
     ) {
         group.bench_function(&format!("wasm-to-host - {} - nop", desc), |b| {
             let run = instance
-                .get_typed_func::<u64, (), _>(&mut *store, "run-nop")
+                .get_typed_func::<u64, ()>(&mut *store, "run-nop")
                 .unwrap();
             b.iter_custom(|iters| {
                 let start = Instant::now();
@@ -386,7 +386,7 @@ fn wasm_to_host(c: &mut Criterion) {
             &format!("wasm-to-host - {} - nop-params-and-results", desc),
             |b| {
                 let run = instance
-                    .get_typed_func::<u64, (), _>(&mut *store, "run-nop-params-and-results")
+                    .get_typed_func::<u64, ()>(&mut *store, "run-nop-params-and-results")
                     .unwrap();
                 b.iter_custom(|iters| {
                     let start = Instant::now();
diff --git a/benches/thread_eager_init.rs b/benches/thread_eager_init.rs
index c87cca9e5..8572a335c 100644
--- a/benches/thread_eager_init.rs
+++ b/benches/thread_eager_init.rs
@@ -55,7 +55,7 @@ fn duration_of_call(engine: &Engine, module: &Module) -> Duration {
     let mut store = Store::new(engine, ());
     let inst = Instance::new(&mut store, module, &[]).expect("instantiate");
     let f = inst.get_func(&mut store, "f").expect("get f");
-    let f = f.typed::<(), (), _>(&store).expect("type f");
+    let f = f.typed::<(), ()>(&store).expect("type f");
 
     let call = Instant::now();
     f.call(&mut store, ()).expect("call f");
diff --git a/benches/trap.rs b/benches/trap.rs
index 933c77e16..979f30676 100644
--- a/benches/trap.rs
+++ b/benches/trap.rs
@@ -38,9 +38,8 @@ fn bench_multi_threaded_traps(c: &mut Criterion) {
                                 move || {
                                     let mut store = Store::new(&engine, ());
                                     let instance = Instance::new(&mut store, &module, &[]).unwrap();
-                                    let f = instance
-                                        .get_typed_func::<(), (), _>(&mut store, "")
-                                        .unwrap();
+                                    let f =
+                                        instance.get_typed_func::<(), ()>(&mut store, "").unwrap();
 
                                     // Notify the parent thread that we are
                                     // doing background work now.
@@ -67,9 +66,7 @@ fn bench_multi_threaded_traps(c: &mut Criterion) {
 
                     let mut store = Store::new(&engine, ());
                     let instance = Instance::new(&mut store, &module, &[]).unwrap();
-                    let f = instance
-                        .get_typed_func::<(), (), _>(&mut store, "")
-                        .unwrap();
+                    let f = instance.get_typed_func::<(), ()>(&mut store, "").unwrap();
 
                     // Measure how long it takes to do `iters` worth of traps
                     // while there is a bunch of background work going on.
@@ -111,9 +108,7 @@ fn bench_many_modules_registered_traps(c: &mut Criterion) {
                 b.iter_custom(|iters| {
                     let mut store = Store::new(&engine, ());
                     let instance = Instance::new(&mut store, modules.last().unwrap(), &[]).unwrap();
-                    let f = instance
-                        .get_typed_func::<(), (), _>(&mut store, "")
-                        .unwrap();
+                    let f = instance.get_typed_func::<(), ()>(&mut store, "").unwrap();
 
                     let start = std::time::Instant::now();
                     for _ in 0..iters {
@@ -143,9 +138,7 @@ fn bench_many_stack_frames_traps(c: &mut Criterion) {
                 b.iter_custom(|iters| {
                     let mut store = Store::new(&engine, ());
                     let instance = Instance::new(&mut store, &module, &[]).unwrap();
-                    let f = instance
-                        .get_typed_func::<(), (), _>(&mut store, "")
-                        .unwrap();
+                    let f = instance.get_typed_func::<(), ()>(&mut store, "").unwrap();
 
                     let start = std::time::Instant::now();
                     for _ in 0..iters {
@@ -204,7 +197,7 @@ fn bench_host_wasm_frames_traps(c: &mut Criterion) {
                     );
                     let instance = Instance::new(&mut store, &module, &[host_func.into()]).unwrap();
                     let f = instance
-                        .get_typed_func::<(i32,), (), _>(&mut store, "f")
+                        .get_typed_func::<(i32,), ()>(&mut store, "f")
                         .unwrap();
 
                     let start = std::time::Instant::now();
diff --git a/cranelift/codegen/src/isa/x64/abi.rs b/cranelift/codegen/src/isa/x64/abi.rs
index 3e697a33b..91bde772c 100644
--- a/cranelift/codegen/src/isa/x64/abi.rs
+++ b/cranelift/codegen/src/isa/x64/abi.rs
@@ -639,8 +639,6 @@ impl ABIMachineSpec for X64ABIMachineSpec {
         let arg0 = get_intreg_for_arg(&call_conv, 0, 0).unwrap();
         let arg1 = get_intreg_for_arg(&call_conv, 1, 1).unwrap();
         let arg2 = get_intreg_for_arg(&call_conv, 2, 2).unwrap();
-        insts.push(Inst::gen_move(Writable::from_reg(arg0), dst, I64));
-        insts.push(Inst::gen_move(Writable::from_reg(arg1), src, I64));
         insts.extend(
             Inst::gen_constant(ValueRegs::one(temp), size as u128, I64, |_| {
                 panic!("tmp should not be needed")
diff --git a/cranelift/codegen/src/isa/x64/inst.isle b/cranelift/codegen/src/isa/x64/inst.isle
index 792e25d94..316d6e75d 100644
--- a/cranelift/codegen/src/isa/x64/inst.isle
+++ b/cranelift/codegen/src/isa/x64/inst.isle
@@ -513,7 +513,8 @@
        ;; A Coff TLS symbol access. Returns address of the TLS symbol in
        ;; `dst`, which is constrained to `rax`.
        (CoffTlsGetAddr (symbol ExternalName)
-                       (dst WritableGpr))
+                       (dst WritableGpr)
+                       (tmp WritableGpr))
 
        ;; An unwind pseudoinstruction describing the state of the machine at
        ;; this program point.
@@ -3759,7 +3760,8 @@
 (decl coff_tls_get_addr (ExternalName) Gpr)
 (rule (coff_tls_get_addr name)
       (let ((dst WritableGpr (temp_writable_gpr))
-            (_ Unit (emit (MInst.CoffTlsGetAddr name dst))))
+            (tmp WritableGpr (temp_writable_gpr))
+            (_ Unit (emit (MInst.CoffTlsGetAddr name dst tmp))))
         dst))
 
 ;;;; sqmul_round_sat ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
diff --git a/cranelift/codegen/src/isa/x64/inst/emit.rs b/cranelift/codegen/src/isa/x64/inst/emit.rs
index d773ae381..6b468d2bc 100644
--- a/cranelift/codegen/src/isa/x64/inst/emit.rs
+++ b/cranelift/codegen/src/isa/x64/inst/emit.rs
@@ -3195,10 +3195,18 @@ pub(crate) fn emit(
             sink.put1(0x17);
         }
 
-        Inst::CoffTlsGetAddr { ref symbol, dst } => {
+        Inst::CoffTlsGetAddr {
+            ref symbol,
+            dst,
+            tmp,
+        } => {
             let dst = allocs.next(dst.to_reg().to_reg());
             debug_assert_eq!(dst, regs::rax());
 
+            // tmp is used below directly as %rcx
+            let tmp = allocs.next(tmp.to_reg().to_reg());
+            debug_assert_eq!(tmp, regs::rcx());
+
             // See: https://gcc.godbolt.org/z/M8or9x6ss
             // And: https://github.com/bjorn3/rustc_codegen_cranelift/issues/388#issuecomment-532930282
 
diff --git a/cranelift/codegen/src/isa/x64/inst/mod.rs b/cranelift/codegen/src/isa/x64/inst/mod.rs
index 34a9df84a..f9261f1e9 100644
--- a/cranelift/codegen/src/isa/x64/inst/mod.rs
+++ b/cranelift/codegen/src/isa/x64/inst/mod.rs
@@ -1655,9 +1655,22 @@ impl PrettyPrint for Inst {
                 format!("{} = macho_tls_get_addr {:?}", dst, symbol)
             }
 
-            Inst::CoffTlsGetAddr { ref symbol, dst } => {
+            Inst::CoffTlsGetAddr {
+                ref symbol,
+                dst,
+                tmp,
+            } => {
+                use std::fmt::Write;
+
                 let dst = pretty_print_reg(dst.to_reg().to_reg(), 8, allocs);
-                format!("{} = coff_tls_get_addr {:?}", dst, symbol)
+                let tmp = allocs.next(tmp.to_reg().to_reg());
+
+                let mut s = format!("{} = coff_tls_get_addr {:?}", dst, symbol);
+                if tmp.is_virtual() {
+                    write!(&mut s, ", {}", show_ireg_sized(tmp, 8)).unwrap();
+                };
+
+                s
             }
 
             Inst::Unwind { inst } => {
@@ -2161,7 +2174,7 @@ fn x64_get_operands<F: Fn(VReg) -> VReg>(inst: &Inst, collector: &mut OperandCol
             collector.reg_clobbers(clobbers);
         }
 
-        Inst::CoffTlsGetAddr { dst, .. } => {
+        Inst::CoffTlsGetAddr { dst, tmp, .. } => {
             // We also use the gs register. But that register is not allocatable by the
             // register allocator, so we don't need to mark it as used here.
 
@@ -2169,7 +2182,7 @@ fn x64_get_operands<F: Fn(VReg) -> VReg>(inst: &Inst, collector: &mut OperandCol
             collector.reg_fixed_def(dst.to_writable_reg(), regs::rax());
 
             // We use %rcx as a temporary variable to load the _tls_index
-            collector.reg_def(Writable::from_reg(regs::rcx()));
+            collector.reg_fixed_def(tmp.to_writable_reg(), regs::rcx());
         }
 
         Inst::Unwind { .. } => {}
diff --git a/crates/bench-api/src/lib.rs b/crates/bench-api/src/lib.rs
index 498236f64..60291d24f 100644
--- a/crates/bench-api/src/lib.rs
+++ b/crates/bench-api/src/lib.rs
@@ -533,7 +533,7 @@ impl BenchState {
             .take()
             .expect("instantiate the module before executing it");
 
-        let start_func = instance.get_typed_func::<(), (), _>(&mut store, "_start")?;
+        let start_func = instance.get_typed_func::<(), ()>(&mut store, "_start")?;
         match start_func.call(&mut store, ()) {
             Ok(_) => Ok(()),
             Err(trap) => {
diff --git a/crates/fuzzing/src/oracles/stacks.rs b/crates/fuzzing/src/oracles/stacks.rs
index 3216ed10c..8705b5887 100644
--- a/crates/fuzzing/src/oracles/stacks.rs
+++ b/crates/fuzzing/src/oracles/stacks.rs
@@ -53,7 +53,7 @@ pub fn check_stacks(stacks: Stacks) -> usize {
         .expect("should instantiate okay");
 
     let run = instance
-        .get_typed_func::<(u32,), (), _>(&mut store, "run")
+        .get_typed_func::<(u32,), ()>(&mut store, "run")
         .expect("should export `run` function");
 
     let mut max_stack_depth = 0;
@@ -62,7 +62,7 @@ pub fn check_stacks(stacks: Stacks) -> usize {
         if let Err(trap) = run.call(&mut store, (input.into(),)) {
             log::debug!("trap: {:?}", trap);
             let get_stack = instance
-                .get_typed_func::<(), (u32, u32), _>(&mut store, "get_stack")
+                .get_typed_func::<(), (u32, u32)>(&mut store, "get_stack")
                 .expect("should export `get_stack` function as expected");
 
             let (ptr, len) = get_stack
diff --git a/crates/wasmtime/src/func.rs b/crates/wasmtime/src/func.rs
index 587fd8e56..ecc3f22b4 100644
--- a/crates/wasmtime/src/func.rs
+++ b/crates/wasmtime/src/func.rs
@@ -95,7 +95,7 @@ use wasmtime_runtime::{
 /// // ... or we can make a static assertion about its signature and call it.
 /// // Our first call here can fail if the signatures don't match, and then the
 /// // second call can fail if the function traps (like the `match` above).
-/// let foo = foo.typed::<(), (), _>(&store)?;
+/// let foo = foo.typed::<(), ()>(&store)?;
 /// foo.call(&mut store, ())?;
 /// # Ok(())
 /// # }
@@ -130,7 +130,7 @@ use wasmtime_runtime::{
 ///     "#,
 /// )?;
 /// let instance = Instance::new(&mut store, &module, &[add.into()])?;
-/// let call_add_twice = instance.get_typed_func::<(), i32, _>(&mut store, "call_add_twice")?;
+/// let call_add_twice = instance.get_typed_func::<(), i32>(&mut store, "call_add_twice")?;
 ///
 /// assert_eq!(call_add_twice.call(&mut store, ())?, 10);
 /// # Ok(())
@@ -603,7 +603,7 @@ impl Func {
     ///     "#,
     /// )?;
     /// let instance = Instance::new(&mut store, &module, &[add.into()])?;
-    /// let foo = instance.get_typed_func::<(i32, i32), i32, _>(&mut store, "foo")?;
+    /// let foo = instance.get_typed_func::<(i32, i32), i32>(&mut store, "foo")?;
     /// assert_eq!(foo.call(&mut store, (1, 2))?, 3);
     /// # Ok(())
     /// # }
@@ -634,7 +634,7 @@ impl Func {
     ///     "#,
     /// )?;
     /// let instance = Instance::new(&mut store, &module, &[add.into()])?;
-    /// let foo = instance.get_typed_func::<(i32, i32), i32, _>(&mut store, "foo")?;
+    /// let foo = instance.get_typed_func::<(i32, i32), i32>(&mut store, "foo")?;
     /// assert_eq!(foo.call(&mut store, (1, 2))?, 3);
     /// assert!(foo.call(&mut store, (i32::max_value(), 1)).is_err());
     /// # Ok(())
@@ -672,7 +672,7 @@ impl Func {
     ///     "#,
     /// )?;
     /// let instance = Instance::new(&mut store, &module, &[debug.into()])?;
-    /// let foo = instance.get_typed_func::<(), (), _>(&mut store, "foo")?;
+    /// let foo = instance.get_typed_func::<(), ()>(&mut store, "foo")?;
     /// foo.call(&mut store, ())?;
     /// # Ok(())
     /// # }
@@ -720,7 +720,7 @@ impl Func {
     ///     "#,
     /// )?;
     /// let instance = Instance::new(&mut store, &module, &[log_str.into()])?;
-    /// let foo = instance.get_typed_func::<(), (), _>(&mut store, "foo")?;
+    /// let foo = instance.get_typed_func::<(), ()>(&mut store, "foo")?;
     /// foo.call(&mut store, ())?;
     /// # Ok(())
     /// # }
@@ -1178,10 +1178,6 @@ impl Func {
     /// function. This behaves the same way as `Params`, but just for the
     /// results of the function.
     ///
-    /// The `S` type parameter represents the method of passing in the store
-    /// context, and can typically be specified as simply `_` when calling this
-    /// function.
-    ///
     /// Translation between Rust types and WebAssembly types looks like:
     ///
     /// | WebAssembly | Rust                |
@@ -1232,7 +1228,7 @@ impl Func {
     /// // Note that this call can fail due to the typecheck not passing, but
     /// // in our case we statically know the module so we know this should
     /// // pass.
-    /// let typed = foo.typed::<(), (), _>(&store)?;
+    /// let typed = foo.typed::<(), ()>(&store)?;
     ///
     /// // Note that this can fail if the wasm traps at runtime.
     /// typed.call(&mut store, ())?;
@@ -1245,7 +1241,7 @@ impl Func {
     /// ```
     /// # use wasmtime::*;
     /// # fn foo(add: &Func, mut store: Store<()>) -> anyhow::Result<()> {
-    /// let typed = add.typed::<(i32, i64), f32, _>(&store)?;
+    /// let typed = add.typed::<(i32, i64), f32>(&store)?;
     /// assert_eq!(typed.call(&mut store, (1, 2))?, 3.0);
     /// # Ok(())
     /// # }
@@ -1256,18 +1252,20 @@ impl Func {
     /// ```
     /// # use wasmtime::*;
     /// # fn foo(add_with_overflow: &Func, mut store: Store<()>) -> anyhow::Result<()> {
-    /// let typed = add_with_overflow.typed::<(u32, u32), (u32, i32), _>(&store)?;
+    /// let typed = add_with_overflow.typed::<(u32, u32), (u32, i32)>(&store)?;
     /// let (result, overflow) = typed.call(&mut store, (u32::max_value(), 2))?;
     /// assert_eq!(result, 1);
     /// assert_eq!(overflow, 1);
     /// # Ok(())
     /// # }
     /// ```
-    pub fn typed<Params, Results, S>(&self, store: S) -> Result<TypedFunc<Params, Results>>
+    pub fn typed<Params, Results>(
+        &self,
+        store: impl AsContext,
+    ) -> Result<TypedFunc<Params, Results>>
     where
         Params: WasmParams,
         Results: WasmResults,
-        S: AsContext,
     {
         // Type-check that the params/results are all valid
         let ty = self.ty(store);
diff --git a/crates/wasmtime/src/instance.rs b/crates/wasmtime/src/instance.rs
index cb7897ad3..970fb030a 100644
--- a/crates/wasmtime/src/instance.rs
+++ b/crates/wasmtime/src/instance.rs
@@ -457,21 +457,20 @@ impl Instance {
     /// # Panics
     ///
     /// Panics if `store` does not own this instance.
-    pub fn get_typed_func<Params, Results, S>(
+    pub fn get_typed_func<Params, Results>(
         &self,
-        mut store: S,
+        mut store: impl AsContextMut,
         name: &str,
     ) -> Result<TypedFunc<Params, Results>>
     where
         Params: crate::WasmParams,
         Results: crate::WasmResults,
-        S: AsContextMut,
     {
         let f = self
             .get_export(store.as_context_mut(), name)
             .and_then(|f| f.into_func())
             .ok_or_else(|| anyhow!("failed to find function export `{}`", name))?;
-        Ok(f.typed::<Params, Results, _>(store)
+        Ok(f.typed::<Params, Results>(store)
             .with_context(|| format!("failed to convert function `{}` to given type", name))?)
     }
 
diff --git a/crates/wasmtime/src/lib.rs b/crates/wasmtime/src/lib.rs
index 1f0d0a9e0..a11bdd940 100644
--- a/crates/wasmtime/src/lib.rs
+++ b/crates/wasmtime/src/lib.rs
@@ -57,7 +57,7 @@
 //!     // afterwards we can fetch exports by name, as well as asserting the
 //!     // type signature of the function with `get_typed_func`.
 //!     let instance = Instance::new(&mut store, &module, &[host_hello.into()])?;
-//!     let hello = instance.get_typed_func::<(), (), _>(&mut store, "hello")?;
+//!     let hello = instance.get_typed_func::<(), ()>(&mut store, "hello")?;
 //!
 //!     // And finally we can call the wasm!
 //!     hello.call(&mut store, ())?;
@@ -168,7 +168,7 @@
 //!     // resolve the imports of the module using name-based resolution.
 //!     let mut store = Store::new(&engine, 0);
 //!     let instance = linker.instantiate(&mut store, &module)?;
-//!     let hello = instance.get_typed_func::<(), (), _>(&mut store, "hello")?;
+//!     let hello = instance.get_typed_func::<(), ()>(&mut store, "hello")?;
 //!     hello.call(&mut store, ())?;
 //!
 //!     Ok(())
@@ -373,7 +373,7 @@
 //!     "#,
 //! )?;
 //! let instance = Instance::new(&mut store, &module, &[log_str.into()])?;
-//! let foo = instance.get_typed_func::<(), (), _>(&mut store, "foo")?;
+//! let foo = instance.get_typed_func::<(), ()>(&mut store, "foo")?;
 //! foo.call(&mut store, ())?;
 //! # Ok(())
 //! # }
diff --git a/crates/wasmtime/src/linker.rs b/crates/wasmtime/src/linker.rs
index ba7890898..c1891ff80 100644
--- a/crates/wasmtime/src/linker.rs
+++ b/crates/wasmtime/src/linker.rs
@@ -643,7 +643,7 @@ impl<T> Linker<T> {
     /// let module = Module::new(&engine, wat)?;
     /// linker.module(&mut store, "commander", &module)?;
     /// let run = linker.get_default(&mut store, "")?
-    ///     .typed::<(), (), _>(&store)?
+    ///     .typed::<(), ()>(&store)?
     ///     .clone();
     /// run.call(&mut store, ())?;
     /// run.call(&mut store, ())?;
@@ -664,7 +664,7 @@ impl<T> Linker<T> {
     /// let module = Module::new(&engine, wat)?;
     /// linker.module(&mut store, "", &module)?;
     /// let run = linker.get(&mut store, "", "run").unwrap().into_func().unwrap();
-    /// let count = run.typed::<(), i32, _>(&store)?.call(&mut store, ())?;
+    /// let count = run.typed::<(), i32>(&store)?.call(&mut store, ())?;
     /// assert_eq!(count, 0, "a Command should get a fresh instance on each invocation");
     ///
     /// # Ok(())
@@ -727,7 +727,7 @@ impl<T> Linker<T> {
 
                 if let Some(export) = instance.get_export(&mut store, "_initialize") {
                     if let Extern::Func(func) = export {
-                        func.typed::<(), (), _>(&store)
+                        func.typed::<(), ()>(&store)
                             .and_then(|f| f.call(&mut store, ()).map_err(Into::into))
                             .context("calling the Reactor initialization function")?;
                     }
@@ -793,7 +793,7 @@ impl<T> Linker<T> {
                 if let Some(export) = instance.get_export(&mut store, "_initialize") {
                     if let Extern::Func(func) = export {
                         let func = func
-                            .typed::<(), (), _>(&store)
+                            .typed::<(), ()>(&store)
                             .context("loading the Reactor initialization function")?;
                         func.call_async(&mut store, ())
                             .await
diff --git a/crates/wasmtime/src/trap.rs b/crates/wasmtime/src/trap.rs
index 710b3fdd3..7b2631e9b 100644
--- a/crates/wasmtime/src/trap.rs
+++ b/crates/wasmtime/src/trap.rs
@@ -54,12 +54,12 @@ use wasmtime_jit::{demangle_function_name, demangle_function_name_or_index};
 /// let mut store = Store::new(&engine, ());
 /// let instance = Instance::new(&mut store, &module, &[])?;
 ///
-/// let trap = instance.get_typed_func::<(), (), _>(&mut store, "trap")?;
+/// let trap = instance.get_typed_func::<(), ()>(&mut store, "trap")?;
 /// let error = trap.call(&mut store, ()).unwrap_err();
 /// assert_eq!(*error.downcast_ref::<Trap>().unwrap(), Trap::UnreachableCodeReached);
 /// assert!(error.root_cause().is::<Trap>());
 ///
-/// let overflow = instance.get_typed_func::<(), (), _>(&mut store, "overflow")?;
+/// let overflow = instance.get_typed_func::<(), ()>(&mut store, "overflow")?;
 /// let error = overflow.call(&mut store, ()).unwrap_err();
 /// assert_eq!(*error.downcast_ref::<Trap>().unwrap(), Trap::StackOverflow);
 /// # Ok(())
@@ -266,7 +266,7 @@ impl std::error::Error for Trap {}
 /// )?;
 /// let mut store = Store::new(&engine, ());
 /// let instance = Instance::new(&mut store, &module, &[])?;
-/// let func = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
+/// let func = instance.get_typed_func::<(), ()>(&mut store, "run")?;
 /// let error = func.call(&mut store, ()).unwrap_err();
 /// let bt = error.downcast_ref::<WasmBacktrace>().unwrap();
 /// let frames = bt.frames();
diff --git a/crates/wiggle/generate/src/codegen_settings.rs b/crates/wiggle/generate/src/codegen_settings.rs
index 616a12dd7..11391ed71 100644
--- a/crates/wiggle/generate/src/codegen_settings.rs
+++ b/crates/wiggle/generate/src/codegen_settings.rs
@@ -1,6 +1,6 @@
-use crate::config::{AsyncConf, ErrorConf, TracingConf};
+use crate::config::{AsyncConf, ErrorConf, ErrorConfField, TracingConf};
 use anyhow::{anyhow, Error};
-use proc_macro2::TokenStream;
+use proc_macro2::{Ident, TokenStream};
 use quote::quote;
 use std::collections::HashMap;
 use std::rc::Rc;
@@ -39,7 +39,7 @@ impl CodegenSettings {
 }
 
 pub struct ErrorTransform {
-    m: Vec<UserErrorType>,
+    m: Vec<ErrorType>,
 }
 
 impl ErrorTransform {
@@ -49,7 +49,13 @@ impl ErrorTransform {
     pub fn new(conf: &ErrorConf, doc: &Document) -> Result<Self, Error> {
         let mut richtype_identifiers = HashMap::new();
         let m = conf.iter().map(|(ident, field)|
-            if let Some(abi_type) = doc.typename(&Id::new(ident.to_string())) {
+            match field {
+                ErrorConfField::Trappable(field) => if let Some(abi_type) = doc.typename(&Id::new(ident.to_string())) {
+                    Ok(ErrorType::Generated(TrappableErrorType { abi_type, rich_type: field.rich_error.clone() }))
+                } else {
+                    Err(anyhow!("No witx typename \"{}\" found", ident.to_string()))
+                },
+                ErrorConfField::User(field) => if let Some(abi_type) = doc.typename(&Id::new(ident.to_string())) {
                     if let Some(ident) = field.rich_error.get_ident() {
                         if let Some(prior_def) = richtype_identifiers.insert(ident.clone(), field.err_loc.clone())
                          {
@@ -58,11 +64,11 @@ impl ErrorTransform {
                                     ident, prior_def
                                 ));
                         }
-                        Ok(UserErrorType {
+                        Ok(ErrorType::User(UserErrorType {
                             abi_type,
                             rich_type: field.rich_error.clone(),
                             method_fragment: ident.to_string()
-                        })
+                        }))
                     } else {
                         return Err(anyhow!(
                             "rich error type must be identifier for now - TODO add ability to provide a corresponding identifier: {:?}",
@@ -71,23 +77,52 @@ impl ErrorTransform {
                     }
                 }
                 else { Err(anyhow!("No witx typename \"{}\" found", ident.to_string())) }
+            }
         ).collect::<Result<Vec<_>, Error>>()?;
         Ok(Self { m })
     }
 
-    pub fn iter(&self) -> impl Iterator<Item = &UserErrorType> {
+    pub fn iter(&self) -> impl Iterator<Item = &ErrorType> {
         self.m.iter()
     }
 
-    pub fn for_abi_error(&self, tref: &TypeRef) -> Option<&UserErrorType> {
+    pub fn for_abi_error(&self, tref: &TypeRef) -> Option<&ErrorType> {
         match tref {
             TypeRef::Name(nt) => self.for_name(nt),
             TypeRef::Value { .. } => None,
         }
     }
 
-    pub fn for_name(&self, nt: &NamedType) -> Option<&UserErrorType> {
-        self.m.iter().find(|u| u.abi_type.name == nt.name)
+    pub fn for_name(&self, nt: &NamedType) -> Option<&ErrorType> {
+        self.m.iter().find(|e| e.abi_type().name == nt.name)
+    }
+}
+
+pub enum ErrorType {
+    User(UserErrorType),
+    Generated(TrappableErrorType),
+}
+impl ErrorType {
+    pub fn abi_type(&self) -> &NamedType {
+        match self {
+            Self::User(u) => &u.abi_type,
+            Self::Generated(r) => &r.abi_type,
+        }
+    }
+}
+
+pub struct TrappableErrorType {
+    abi_type: Rc<NamedType>,
+    rich_type: Ident,
+}
+
+impl TrappableErrorType {
+    pub fn abi_type(&self) -> TypeRef {
+        TypeRef::Name(self.abi_type.clone())
+    }
+    pub fn typename(&self) -> TokenStream {
+        let richtype = &self.rich_type;
+        quote!(#richtype)
     }
 }
 
diff --git a/crates/wiggle/generate/src/config.rs b/crates/wiggle/generate/src/config.rs
index 1eedc1840..2e42a93ef 100644
--- a/crates/wiggle/generate/src/config.rs
+++ b/crates/wiggle/generate/src/config.rs
@@ -27,6 +27,7 @@ mod kw {
     syn::custom_keyword!(wasmtime);
     syn::custom_keyword!(tracing);
     syn::custom_keyword!(disable_for);
+    syn::custom_keyword!(trappable);
 }
 
 #[derive(Debug, Clone)]
@@ -274,14 +275,14 @@ impl Parse for ErrorConf {
             content.parse_terminated(Parse::parse)?;
         let mut m = HashMap::new();
         for i in items {
-            match m.insert(i.abi_error.clone(), i.clone()) {
+            match m.insert(i.abi_error().clone(), i.clone()) {
                 None => {}
                 Some(prev_def) => {
                     return Err(Error::new(
-                        i.err_loc,
+                        *i.err_loc(),
                         format!(
                         "duplicate definition of rich error type for {:?}: previously defined at {:?}",
-                        i.abi_error, prev_def.err_loc,
+                        i.abi_error(), prev_def.err_loc(),
                     ),
                     ))
                 }
@@ -291,14 +292,67 @@ impl Parse for ErrorConf {
     }
 }
 
+#[derive(Debug, Clone)]
+pub enum ErrorConfField {
+    Trappable(TrappableErrorConfField),
+    User(UserErrorConfField),
+}
+impl ErrorConfField {
+    pub fn abi_error(&self) -> &Ident {
+        match self {
+            Self::Trappable(t) => &t.abi_error,
+            Self::User(u) => &u.abi_error,
+        }
+    }
+    pub fn err_loc(&self) -> &Span {
+        match self {
+            Self::Trappable(t) => &t.err_loc,
+            Self::User(u) => &u.err_loc,
+        }
+    }
+}
+
+impl Parse for ErrorConfField {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let err_loc = input.span();
+        let abi_error = input.parse::<Ident>()?;
+        let _arrow: Token![=>] = input.parse()?;
+
+        let lookahead = input.lookahead1();
+        if lookahead.peek(kw::trappable) {
+            let _ = input.parse::<kw::trappable>()?;
+            let rich_error = input.parse()?;
+            Ok(ErrorConfField::Trappable(TrappableErrorConfField {
+                abi_error,
+                rich_error,
+                err_loc,
+            }))
+        } else {
+            let rich_error = input.parse::<syn::Path>()?;
+            Ok(ErrorConfField::User(UserErrorConfField {
+                abi_error,
+                rich_error,
+                err_loc,
+            }))
+        }
+    }
+}
+
+#[derive(Clone, Debug)]
+pub struct TrappableErrorConfField {
+    pub abi_error: Ident,
+    pub rich_error: Ident,
+    pub err_loc: Span,
+}
+
 #[derive(Clone)]
-pub struct ErrorConfField {
+pub struct UserErrorConfField {
     pub abi_error: Ident,
     pub rich_error: syn::Path,
     pub err_loc: Span,
 }
 
-impl std::fmt::Debug for ErrorConfField {
+impl std::fmt::Debug for UserErrorConfField {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         f.debug_struct("ErrorConfField")
             .field("abi_error", &self.abi_error)
@@ -308,20 +362,6 @@ impl std::fmt::Debug for ErrorConfField {
     }
 }
 
-impl Parse for ErrorConfField {
-    fn parse(input: ParseStream) -> Result<Self> {
-        let err_loc = input.span();
-        let abi_error = input.parse::<Ident>()?;
-        let _arrow: Token![=>] = input.parse()?;
-        let rich_error = input.parse::<syn::Path>()?;
-        Ok(ErrorConfField {
-            abi_error,
-            rich_error,
-            err_loc,
-        })
-    }
-}
-
 #[derive(Clone, Default, Debug)]
 /// Modules and funcs that have async signatures
 pub struct AsyncConf {
diff --git a/crates/wiggle/generate/src/funcs.rs b/crates/wiggle/generate/src/funcs.rs
index bd1f1eebd..63177c3c7 100644
--- a/crates/wiggle/generate/src/funcs.rs
+++ b/crates/wiggle/generate/src/funcs.rs
@@ -1,7 +1,7 @@
-use crate::codegen_settings::CodegenSettings;
+use crate::codegen_settings::{CodegenSettings, ErrorType};
 use crate::lifetimes::anon_lifetime;
 use crate::module_trait::passed_by_reference;
-use crate::names::Names;
+use crate::names;
 use crate::types::WiggleType;
 use proc_macro2::{Ident, Span, TokenStream};
 use quote::quote;
@@ -9,54 +9,50 @@ use std::mem;
 use witx::Instruction;
 
 pub fn define_func(
-    names: &Names,
     module: &witx::Module,
     func: &witx::InterfaceFunc,
     settings: &CodegenSettings,
 ) -> TokenStream {
-    let (ts, _bounds) = _define_func(names, module, func, settings);
+    let (ts, _bounds) = _define_func(module, func, settings);
     ts
 }
 
 pub fn func_bounds(
-    names: &Names,
     module: &witx::Module,
     func: &witx::InterfaceFunc,
     settings: &CodegenSettings,
 ) -> Vec<Ident> {
-    let (_ts, bounds) = _define_func(names, module, func, settings);
+    let (_ts, bounds) = _define_func(module, func, settings);
     bounds
 }
 
 fn _define_func(
-    names: &Names,
     module: &witx::Module,
     func: &witx::InterfaceFunc,
     settings: &CodegenSettings,
 ) -> (TokenStream, Vec<Ident>) {
-    let rt = names.runtime_mod();
-    let ident = names.func(&func.name);
+    let ident = names::func(&func.name);
 
     let (wasm_params, wasm_results) = func.wasm_signature();
     let param_names = (0..wasm_params.len())
         .map(|i| Ident::new(&format!("arg{}", i), Span::call_site()))
         .collect::<Vec<_>>();
     let abi_params = wasm_params.iter().zip(&param_names).map(|(arg, name)| {
-        let wasm = names.wasm_type(*arg);
+        let wasm = names::wasm_type(*arg);
         quote!(#name : #wasm)
     });
 
     let abi_ret = match wasm_results.len() {
         0 => quote!(()),
         1 => {
-            let ty = names.wasm_type(wasm_results[0]);
+            let ty = names::wasm_type(wasm_results[0]);
             quote!(#ty)
         }
         _ => unimplemented!(),
     };
 
     let mut body = TokenStream::new();
-    let mut bounds = vec![names.trait_name(&module.name)];
+    let mut bounds = vec![names::trait_name(&module.name)];
     func.call_interface(
         &module.name,
         &mut Rust {
@@ -64,8 +60,6 @@ fn _define_func(
             params: &param_names,
             block_storage: Vec::new(),
             blocks: Vec::new(),
-            rt: &rt,
-            names,
             module,
             funcname: func.name.as_str(),
             settings,
@@ -76,8 +70,8 @@ fn _define_func(
     let mod_name = &module.name.as_str();
     let func_name = &func.name.as_str();
     let mk_span = quote!(
-        let _span = #rt::tracing::span!(
-            #rt::tracing::Level::TRACE,
+        let _span = wiggle::tracing::span!(
+            wiggle::tracing::Level::TRACE,
             "wiggle abi",
             module = #mod_name,
             function = #func_name
@@ -99,9 +93,9 @@ fn _define_func(
                 #[allow(unreachable_code)] // deals with warnings in noreturn functions
                 pub fn #ident(
                     ctx: &mut (impl #(#bounds)+*),
-                    memory: &dyn #rt::GuestMemory,
+                    memory: &dyn wiggle::GuestMemory,
                     #(#abi_params),*
-                ) -> #rt::anyhow::Result<#abi_ret> {
+                ) -> wiggle::anyhow::Result<#abi_ret> {
                     use std::convert::TryFrom as _;
                     #traced_body
                 }
@@ -111,7 +105,7 @@ fn _define_func(
     } else {
         let traced_body = if settings.tracing.enabled_for(&mod_name, &func_name) {
             quote!(
-                use #rt::tracing::Instrument as _;
+                use wiggle::tracing::Instrument as _;
                 #mk_span
                 async move {
                     #body
@@ -129,9 +123,9 @@ fn _define_func(
                 #[allow(unreachable_code)] // deals with warnings in noreturn functions
                 pub fn #ident<'a>(
                     ctx: &'a mut (impl #(#bounds)+*),
-                    memory: &'a dyn #rt::GuestMemory,
+                    memory: &'a dyn wiggle::GuestMemory,
                     #(#abi_params),*
-                ) -> impl std::future::Future<Output = #rt::anyhow::Result<#abi_ret>> + 'a {
+                ) -> impl std::future::Future<Output = wiggle::anyhow::Result<#abi_ret>> + 'a {
                     use std::convert::TryFrom as _;
                     #traced_body
                 }
@@ -146,8 +140,6 @@ struct Rust<'a> {
     params: &'a [Ident],
     block_storage: Vec<TokenStream>,
     blocks: Vec<TokenStream>,
-    rt: &'a TokenStream,
-    names: &'a Names,
     module: &'a witx::Module,
     funcname: &'a str,
     settings: &'a CodegenSettings,
@@ -196,17 +188,16 @@ impl witx::Bindgen for Rust<'_> {
         operands: &mut Vec<TokenStream>,
         results: &mut Vec<TokenStream>,
     ) {
-        let rt = self.rt;
         let wrap_err = |location: &str| {
             let modulename = self.module.name.as_str();
             let funcname = self.funcname;
             quote! {
                 |e| {
-                    #rt::GuestError::InFunc {
+                    wiggle::GuestError::InFunc {
                         modulename: #modulename,
                         funcname: #funcname,
                         location: #location,
-                        err: Box::new(#rt::GuestError::from(e)),
+                        err: Box::new(wiggle::GuestError::from(e)),
                     }
                 }
             }
@@ -226,9 +217,9 @@ impl witx::Bindgen for Rust<'_> {
 
             Instruction::PointerFromI32 { ty } | Instruction::ConstPointerFromI32 { ty } => {
                 let val = operands.pop().unwrap();
-                let pointee_type = self.names.type_ref(ty, anon_lifetime());
+                let pointee_type = names::type_ref(ty, anon_lifetime());
                 results.push(quote! {
-                    #rt::GuestPtr::<#pointee_type>::new(memory, #val as u32)
+                    wiggle::GuestPtr::<#pointee_type>::new(memory, #val as u32)
                 });
             }
 
@@ -238,12 +229,12 @@ impl witx::Bindgen for Rust<'_> {
                 let ty = match &**ty.type_() {
                     witx::Type::Builtin(witx::BuiltinType::Char) => quote!(str),
                     _ => {
-                        let ty = self.names.type_ref(ty, anon_lifetime());
+                        let ty = names::type_ref(ty, anon_lifetime());
                         quote!([#ty])
                     }
                 };
                 results.push(quote! {
-                    #rt::GuestPtr::<#ty>::new(memory, (#ptr as u32, #len as u32));
+                    wiggle::GuestPtr::<#ty>::new(memory, (#ptr as u32, #len as u32));
                 })
             }
 
@@ -252,7 +243,7 @@ impl witx::Bindgen for Rust<'_> {
                 // out, and afterwards we call the function with those bindings.
                 let mut args = Vec::new();
                 for (i, param) in func.params.iter().enumerate() {
-                    let name = self.names.func_param(&param.name);
+                    let name = names::func_param(&param.name);
                     let val = &operands[i];
                     self.src.extend(quote!(let #name = #val;));
                     if passed_by_reference(param.tref.type_()) {
@@ -271,21 +262,21 @@ impl witx::Bindgen for Rust<'_> {
                         .params
                         .iter()
                         .map(|param| {
-                            let name = self.names.func_param(&param.name);
+                            let name = names::func_param(&param.name);
                             if param.impls_display() {
-                                quote!( #name = #rt::tracing::field::display(&#name) )
+                                quote!( #name = wiggle::tracing::field::display(&#name) )
                             } else {
-                                quote!( #name = #rt::tracing::field::debug(&#name) )
+                                quote!( #name = wiggle::tracing::field::debug(&#name) )
                             }
                         })
                         .collect::<Vec<_>>();
                     self.src.extend(quote! {
-                        #rt::tracing::event!(#rt::tracing::Level::TRACE, #(#args),*);
+                        wiggle::tracing::event!(wiggle::tracing::Level::TRACE, #(#args),*);
                     });
                 }
 
-                let trait_name = self.names.trait_name(&self.module.name);
-                let ident = self.names.func(&func.name);
+                let trait_name = names::trait_name(&self.module.name);
+                let ident = names::func(&func.name);
                 if self.settings.get_async(&self.module, &func).is_sync() {
                     self.src.extend(quote! {
                         let ret = #trait_name::#ident(ctx, #(#args),*);
@@ -301,9 +292,9 @@ impl witx::Bindgen for Rust<'_> {
                     .enabled_for(self.module.name.as_str(), self.funcname)
                 {
                     self.src.extend(quote! {
-                        #rt::tracing::event!(
-                            #rt::tracing::Level::TRACE,
-                            result = #rt::tracing::field::debug(&ret),
+                        wiggle::tracing::event!(
+                            wiggle::tracing::Level::TRACE,
+                            result = wiggle::tracing::field::debug(&ret),
                         );
                     });
                 }
@@ -322,11 +313,12 @@ impl witx::Bindgen for Rust<'_> {
             Instruction::EnumLower { ty } => {
                 let val = operands.pop().unwrap();
                 let val = match self.settings.errors.for_name(ty) {
-                    Some(custom) => {
-                        let method = self.names.user_error_conversion_method(&custom);
+                    Some(ErrorType::User(custom)) => {
+                        let method = names::user_error_conversion_method(&custom);
                         self.bound(quote::format_ident!("UserErrorConversion"));
                         quote!(UserErrorConversion::#method(ctx, #val)?)
                     }
+                    Some(ErrorType::Generated(_)) => quote!(#val.downcast()?),
                     None => val,
                 };
                 results.push(quote!(#val as i32));
@@ -336,10 +328,10 @@ impl witx::Bindgen for Rust<'_> {
                 let err = self.blocks.pop().unwrap();
                 let ok = self.blocks.pop().unwrap();
                 let val = operands.pop().unwrap();
-                let err_typename = self.names.type_ref(err_ty.unwrap(), anon_lifetime());
+                let err_typename = names::type_ref(err_ty.unwrap(), anon_lifetime());
                 results.push(quote! {
                     match #val {
-                        Ok(e) => { #ok; <#err_typename as #rt::GuestErrorType>::success() as i32 }
+                        Ok(e) => { #ok; <#err_typename as wiggle::GuestErrorType>::success() as i32 }
                         Err(e) => { #err }
                     }
                 });
@@ -369,9 +361,9 @@ impl witx::Bindgen for Rust<'_> {
                 let ptr = operands.pop().unwrap();
                 let val = operands.pop().unwrap();
                 let wrap_err = wrap_err(&format!("write {}", ty.name.as_str()));
-                let pointee_type = self.names.type_(&ty.name);
+                let pointee_type = names::type_(&ty.name);
                 self.src.extend(quote! {
-                    #rt::GuestPtr::<#pointee_type>::new(memory, #ptr as u32)
+                    wiggle::GuestPtr::<#pointee_type>::new(memory, #ptr as u32)
                         .write(#val)
                         .map_err(#wrap_err)?;
                 });
@@ -380,9 +372,9 @@ impl witx::Bindgen for Rust<'_> {
             Instruction::Load { ty } => {
                 let ptr = operands.pop().unwrap();
                 let wrap_err = wrap_err(&format!("read {}", ty.name.as_str()));
-                let pointee_type = self.names.type_(&ty.name);
+                let pointee_type = names::type_(&ty.name);
                 results.push(quote! {
-                    #rt::GuestPtr::<#pointee_type>::new(memory, #ptr as u32)
+                    wiggle::GuestPtr::<#pointee_type>::new(memory, #ptr as u32)
                         .read()
                         .map_err(#wrap_err)?
                 });
@@ -390,7 +382,7 @@ impl witx::Bindgen for Rust<'_> {
 
             Instruction::HandleFromI32 { ty } => {
                 let val = operands.pop().unwrap();
-                let ty = self.names.type_(&ty.name);
+                let ty = names::type_(&ty.name);
                 results.push(quote!(#ty::from(#val)));
             }
 
@@ -418,7 +410,7 @@ impl witx::Bindgen for Rust<'_> {
             Instruction::EnumLift { ty }
             | Instruction::BitflagsFromI64 { ty }
             | Instruction::BitflagsFromI32 { ty } => {
-                let ty = self.names.type_(&ty.name);
+                let ty = names::type_(&ty.name);
                 try_from(quote!(#ty))
             }
 
diff --git a/crates/wiggle/generate/src/lib.rs b/crates/wiggle/generate/src/lib.rs
index 9b5cea757..77bbe6b00 100644
--- a/crates/wiggle/generate/src/lib.rs
+++ b/crates/wiggle/generate/src/lib.rs
@@ -3,7 +3,7 @@ pub mod config;
 mod funcs;
 mod lifetimes;
 mod module_trait;
-mod names;
+pub mod names;
 mod types;
 pub mod wasmtime;
 
@@ -12,19 +12,16 @@ use lifetimes::anon_lifetime;
 use proc_macro2::{Literal, TokenStream};
 use quote::quote;
 
-pub use codegen_settings::{CodegenSettings, UserErrorType};
+pub use codegen_settings::{CodegenSettings, ErrorType, UserErrorType};
 pub use config::{Config, WasmtimeConfig};
 pub use funcs::define_func;
 pub use module_trait::define_module_trait;
-pub use names::Names;
 pub use types::define_datatype;
 
-pub fn generate(doc: &witx::Document, names: &Names, settings: &CodegenSettings) -> TokenStream {
-    // TODO at some point config should grow more ability to configure name
-    // overrides.
-    let rt = names.runtime_mod();
-
-    let types = doc.typenames().map(|t| define_datatype(&names, &t));
+pub fn generate(doc: &witx::Document, settings: &CodegenSettings) -> TokenStream {
+    let types = doc
+        .typenames()
+        .map(|t| define_datatype(&t, settings.errors.for_name(&t)));
 
     let constants = doc.constants().map(|c| {
         let name = quote::format_ident!(
@@ -32,18 +29,24 @@ pub fn generate(doc: &witx::Document, names: &Names, settings: &CodegenSettings)
             c.ty.as_str().to_shouty_snake_case(),
             c.name.as_str().to_shouty_snake_case()
         );
-        let ty = names.type_(&c.ty);
+        let ty = names::type_(&c.ty);
         let value = Literal::u64_unsuffixed(c.value);
         quote! {
             pub const #name: #ty = #value;
         }
     });
 
-    let user_error_methods = settings.errors.iter().map(|errtype| {
-        let abi_typename = names.type_ref(&errtype.abi_type(), anon_lifetime());
-        let user_typename = errtype.typename();
-        let methodname = names.user_error_conversion_method(&errtype);
-        quote!(fn #methodname(&mut self, e: super::#user_typename) -> #rt::anyhow::Result<#abi_typename>;)
+    let user_error_methods = settings.errors.iter().filter_map(|errtype| match errtype {
+        ErrorType::User(errtype) => {
+            let abi_typename = names::type_ref(&errtype.abi_type(), anon_lifetime());
+            let user_typename = errtype.typename();
+            let methodname = names::user_error_conversion_method(&errtype);
+            Some(quote! {
+                fn #methodname(&mut self, e: super::#user_typename)
+                    -> wiggle::anyhow::Result<#abi_typename>;
+            })
+        }
+        ErrorType::Generated(_) => None,
     });
     let user_error_conversion = quote! {
         pub trait UserErrorConversion {
@@ -51,13 +54,11 @@ pub fn generate(doc: &witx::Document, names: &Names, settings: &CodegenSettings)
         }
     };
     let modules = doc.modules().map(|module| {
-        let modname = names.module(&module.name);
-        let fs = module
-            .funcs()
-            .map(|f| define_func(&names, &module, &f, &settings));
-        let modtrait = define_module_trait(&names, &module, &settings);
+        let modname = names::module(&module.name);
+        let fs = module.funcs().map(|f| define_func(&module, &f, &settings));
+        let modtrait = define_module_trait(&module, &settings);
         let wasmtime = if settings.wasmtime {
-            crate::wasmtime::link_module(&module, &names, None, &settings)
+            crate::wasmtime::link_module(&module, None, &settings)
         } else {
             quote! {}
         };
@@ -86,14 +87,13 @@ pub fn generate(doc: &witx::Document, names: &Names, settings: &CodegenSettings)
     )
 }
 
-pub fn generate_metadata(doc: &witx::Document, names: &Names) -> TokenStream {
-    let rt = names.runtime_mod();
+pub fn generate_metadata(doc: &witx::Document) -> TokenStream {
     let doc_text = &format!("{}", doc);
     quote! {
         pub mod metadata {
             pub const DOC_TEXT: &str = #doc_text;
-            pub fn document() -> #rt::witx::Document {
-                #rt::witx::parse(DOC_TEXT).unwrap()
+            pub fn document() -> wiggle::witx::Document {
+                wiggle::witx::parse(DOC_TEXT).unwrap()
             }
         }
     }
diff --git a/crates/wiggle/generate/src/module_trait.rs b/crates/wiggle/generate/src/module_trait.rs
index be9a47ad3..c1dbfac21 100644
--- a/crates/wiggle/generate/src/module_trait.rs
+++ b/crates/wiggle/generate/src/module_trait.rs
@@ -1,9 +1,9 @@
 use proc_macro2::TokenStream;
 use quote::quote;
 
-use crate::codegen_settings::CodegenSettings;
+use crate::codegen_settings::{CodegenSettings, ErrorType};
 use crate::lifetimes::{anon_lifetime, LifetimeExt};
-use crate::names::Names;
+use crate::names;
 use witx::Module;
 
 pub fn passed_by_reference(ty: &witx::Type) -> bool {
@@ -15,9 +15,8 @@ pub fn passed_by_reference(ty: &witx::Type) -> bool {
     }
 }
 
-pub fn define_module_trait(names: &Names, m: &Module, settings: &CodegenSettings) -> TokenStream {
-    let traitname = names.trait_name(&m.name);
-    let rt = names.runtime_mod();
+pub fn define_module_trait(m: &Module, settings: &CodegenSettings) -> TokenStream {
+    let traitname = names::trait_name(&m.name);
     let traitmethods = m.funcs().map(|f| {
         // Check if we're returning an entity anotated with a lifetime,
         // in which case, we'll need to annotate the function itself, and
@@ -32,10 +31,10 @@ pub fn define_module_trait(names: &Names, m: &Module, settings: &CodegenSettings
         } else {
             (anon_lifetime(), true)
         };
-        let funcname = names.func(&f.name);
+        let funcname = names::func(&f.name);
         let args = f.params.iter().map(|arg| {
-            let arg_name = names.func_param(&arg.name);
-            let arg_typename = names.type_ref(&arg.tref, lifetime.clone());
+            let arg_name = names::func_param(&arg.name);
+            let arg_typename = names::type_ref(&arg.tref, lifetime.clone());
             let arg_type = if passed_by_reference(&*arg.tref.type_()) {
                 quote!(&#arg_typename)
             } else {
@@ -45,7 +44,7 @@ pub fn define_module_trait(names: &Names, m: &Module, settings: &CodegenSettings
         });
 
         let result = match f.results.len() {
-            0 if f.noreturn => quote!(#rt::anyhow::Error),
+            0 if f.noreturn => quote!(wiggle::anyhow::Error),
             0 => quote!(()),
             1 => {
                 let (ok, err) = match &**f.results[0].tref.type_() {
@@ -57,16 +56,17 @@ pub fn define_module_trait(names: &Names, m: &Module, settings: &CodegenSettings
                 };
 
                 let ok = match ok {
-                    Some(ty) => names.type_ref(ty, lifetime.clone()),
+                    Some(ty) => names::type_ref(ty, lifetime.clone()),
                     None => quote!(()),
                 };
                 let err = match err {
                     Some(ty) => match settings.errors.for_abi_error(ty) {
-                        Some(custom) => {
+                        Some(ErrorType::User(custom)) => {
                             let tn = custom.typename();
                             quote!(super::#tn)
                         }
-                        None => names.type_ref(ty, lifetime.clone()),
+                        Some(ErrorType::Generated(g)) => g.typename(),
+                        None => names::type_ref(ty, lifetime.clone()),
                     },
                     None => quote!(()),
                 };
@@ -89,7 +89,7 @@ pub fn define_module_trait(names: &Names, m: &Module, settings: &CodegenSettings
     });
 
     quote! {
-        #[#rt::async_trait]
+        #[wiggle::async_trait]
         pub trait #traitname {
             #(#traitmethods)*
         }
diff --git a/crates/wiggle/generate/src/names.rs b/crates/wiggle/generate/src/names.rs
index 635ba735a..a32a35be5 100644
--- a/crates/wiggle/generate/src/names.rs
+++ b/crates/wiggle/generate/src/names.rs
@@ -6,204 +6,186 @@ use witx::{BuiltinType, Id, Type, TypeRef, WasmType};
 
 use crate::{lifetimes::LifetimeExt, UserErrorType};
 
-pub struct Names {
-    runtime_mod: TokenStream,
+pub fn type_(id: &Id) -> Ident {
+    escape_id(id, NamingConvention::CamelCase)
 }
 
-impl Names {
-    pub fn new(runtime_mod: TokenStream) -> Names {
-        Names { runtime_mod }
-    }
-
-    pub fn runtime_mod(&self) -> TokenStream {
-        self.runtime_mod.clone()
+pub fn builtin_type(b: BuiltinType) -> TokenStream {
+    match b {
+        BuiltinType::U8 { .. } => quote!(u8),
+        BuiltinType::U16 => quote!(u16),
+        BuiltinType::U32 { .. } => quote!(u32),
+        BuiltinType::U64 => quote!(u64),
+        BuiltinType::S8 => quote!(i8),
+        BuiltinType::S16 => quote!(i16),
+        BuiltinType::S32 => quote!(i32),
+        BuiltinType::S64 => quote!(i64),
+        BuiltinType::F32 => quote!(f32),
+        BuiltinType::F64 => quote!(f64),
+        BuiltinType::Char => quote!(char),
     }
+}
 
-    pub fn type_(&self, id: &Id) -> TokenStream {
-        let ident = escape_id(id, NamingConvention::CamelCase);
-        quote!(#ident)
-    }
-
-    pub fn builtin_type(&self, b: BuiltinType) -> TokenStream {
-        match b {
-            BuiltinType::U8 { .. } => quote!(u8),
-            BuiltinType::U16 => quote!(u16),
-            BuiltinType::U32 { .. } => quote!(u32),
-            BuiltinType::U64 => quote!(u64),
-            BuiltinType::S8 => quote!(i8),
-            BuiltinType::S16 => quote!(i16),
-            BuiltinType::S32 => quote!(i32),
-            BuiltinType::S64 => quote!(i64),
-            BuiltinType::F32 => quote!(f32),
-            BuiltinType::F64 => quote!(f64),
-            BuiltinType::Char => quote!(char),
-        }
+pub fn wasm_type(ty: WasmType) -> TokenStream {
+    match ty {
+        WasmType::I32 => quote!(i32),
+        WasmType::I64 => quote!(i64),
+        WasmType::F32 => quote!(f32),
+        WasmType::F64 => quote!(f64),
     }
+}
 
-    pub fn wasm_type(&self, ty: WasmType) -> TokenStream {
-        match ty {
-            WasmType::I32 => quote!(i32),
-            WasmType::I64 => quote!(i64),
-            WasmType::F32 => quote!(f32),
-            WasmType::F64 => quote!(f64),
+pub fn type_ref(tref: &TypeRef, lifetime: TokenStream) -> TokenStream {
+    match tref {
+        TypeRef::Name(nt) => {
+            let ident = type_(&nt.name);
+            if nt.tref.needs_lifetime() {
+                quote!(#ident<#lifetime>)
+            } else {
+                quote!(#ident)
+            }
         }
-    }
-
-    pub fn type_ref(&self, tref: &TypeRef, lifetime: TokenStream) -> TokenStream {
-        match tref {
-            TypeRef::Name(nt) => {
-                let ident = self.type_(&nt.name);
-                if nt.tref.needs_lifetime() {
-                    quote!(#ident<#lifetime>)
-                } else {
-                    quote!(#ident)
-                }
+        TypeRef::Value(ty) => match &**ty {
+            Type::Builtin(builtin) => builtin_type(*builtin),
+            Type::Pointer(pointee) | Type::ConstPointer(pointee) => {
+                let pointee_type = type_ref(&pointee, lifetime.clone());
+                quote!(wiggle::GuestPtr<#lifetime, #pointee_type>)
             }
-            TypeRef::Value(ty) => match &**ty {
-                Type::Builtin(builtin) => self.builtin_type(*builtin),
-                Type::Pointer(pointee) | Type::ConstPointer(pointee) => {
-                    let rt = self.runtime_mod();
-                    let pointee_type = self.type_ref(&pointee, lifetime.clone());
-                    quote!(#rt::GuestPtr<#lifetime, #pointee_type>)
+            Type::List(pointee) => match &**pointee.type_() {
+                Type::Builtin(BuiltinType::Char) => {
+                    quote!(wiggle::GuestPtr<#lifetime, str>)
                 }
-                Type::List(pointee) => match &**pointee.type_() {
-                    Type::Builtin(BuiltinType::Char) => {
-                        let rt = self.runtime_mod();
-                        quote!(#rt::GuestPtr<#lifetime, str>)
-                    }
-                    _ => {
-                        let rt = self.runtime_mod();
-                        let pointee_type = self.type_ref(&pointee, lifetime.clone());
-                        quote!(#rt::GuestPtr<#lifetime, [#pointee_type]>)
-                    }
-                },
-                Type::Variant(v) => match v.as_expected() {
-                    Some((ok, err)) => {
-                        let ok = match ok {
-                            Some(ty) => self.type_ref(ty, lifetime.clone()),
-                            None => quote!(()),
-                        };
-                        let err = match err {
-                            Some(ty) => self.type_ref(ty, lifetime.clone()),
-                            None => quote!(()),
-                        };
-                        quote!(Result<#ok, #err>)
-                    }
-                    None => unimplemented!("anonymous variant ref {:?}", tref),
-                },
-                Type::Record(r) if r.is_tuple() => {
-                    let types = r
-                        .members
-                        .iter()
-                        .map(|m| self.type_ref(&m.tref, lifetime.clone()))
-                        .collect::<Vec<_>>();
-                    quote!((#(#types,)*))
+                _ => {
+                    let pointee_type = type_ref(&pointee, lifetime.clone());
+                    quote!(wiggle::GuestPtr<#lifetime, [#pointee_type]>)
                 }
-                _ => unimplemented!("anonymous type ref {:?}", tref),
             },
-        }
+            Type::Variant(v) => match v.as_expected() {
+                Some((ok, err)) => {
+                    let ok = match ok {
+                        Some(ty) => type_ref(ty, lifetime.clone()),
+                        None => quote!(()),
+                    };
+                    let err = match err {
+                        Some(ty) => type_ref(ty, lifetime.clone()),
+                        None => quote!(()),
+                    };
+                    quote!(Result<#ok, #err>)
+                }
+                None => unimplemented!("anonymous variant ref {:?}", tref),
+            },
+            Type::Record(r) if r.is_tuple() => {
+                let types = r
+                    .members
+                    .iter()
+                    .map(|m| type_ref(&m.tref, lifetime.clone()))
+                    .collect::<Vec<_>>();
+                quote!((#(#types,)*))
+            }
+            _ => unimplemented!("anonymous type ref {:?}", tref),
+        },
     }
+}
 
-    /// Convert an enum variant from its [`Id`][witx] name to its Rust [`Ident`][id] representation.
-    ///
-    /// [id]: https://docs.rs/proc-macro2/*/proc_macro2/struct.Ident.html
-    /// [witx]: https://docs.rs/witx/*/witx/struct.Id.html
-    pub fn enum_variant(&self, id: &Id) -> Ident {
-        handle_2big_enum_variant(id).unwrap_or_else(|| escape_id(id, NamingConvention::CamelCase))
-    }
+/// Convert an enum variant from its [`Id`][witx] name to its Rust [`Ident`][id] representation.
+///
+/// [id]: https://docs.rs/proc-macro2/*/proc_macro2/struct.Ident.html
+/// [witx]: https://docs.rs/witx/*/witx/struct.Id.html
+pub fn enum_variant(id: &Id) -> Ident {
+    handle_2big_enum_variant(id).unwrap_or_else(|| escape_id(id, NamingConvention::CamelCase))
+}
 
-    pub fn flag_member(&self, id: &Id) -> Ident {
-        format_ident!("{}", id.as_str().to_shouty_snake_case())
-    }
+pub fn flag_member(id: &Id) -> Ident {
+    format_ident!("{}", id.as_str().to_shouty_snake_case())
+}
 
-    pub fn int_member(&self, id: &Id) -> Ident {
-        format_ident!("{}", id.as_str().to_shouty_snake_case())
-    }
+pub fn int_member(id: &Id) -> Ident {
+    format_ident!("{}", id.as_str().to_shouty_snake_case())
+}
 
-    /// Convert a struct member from its [`Id`][witx] name to its Rust [`Ident`][id] representation.
-    ///
-    /// [id]: https://docs.rs/proc-macro2/*/proc_macro2/struct.Ident.html
-    /// [witx]: https://docs.rs/witx/*/witx/struct.Id.html
-    pub fn struct_member(&self, id: &Id) -> Ident {
-        escape_id(id, NamingConvention::SnakeCase)
-    }
+/// Convert a struct member from its [`Id`][witx] name to its Rust [`Ident`][id] representation.
+///
+/// [id]: https://docs.rs/proc-macro2/*/proc_macro2/struct.Ident.html
+/// [witx]: https://docs.rs/witx/*/witx/struct.Id.html
+pub fn struct_member(id: &Id) -> Ident {
+    escape_id(id, NamingConvention::SnakeCase)
+}
 
-    /// Convert a module name from its [`Id`][witx] name to its Rust [`Ident`][id] representation.
-    ///
-    /// [id]: https://docs.rs/proc-macro2/*/proc_macro2/struct.Ident.html
-    /// [witx]: https://docs.rs/witx/*/witx/struct.Id.html
-    pub fn module(&self, id: &Id) -> Ident {
-        escape_id(id, NamingConvention::SnakeCase)
-    }
+/// Convert a module name from its [`Id`][witx] name to its Rust [`Ident`][id] representation.
+///
+/// [id]: https://docs.rs/proc-macro2/*/proc_macro2/struct.Ident.html
+/// [witx]: https://docs.rs/witx/*/witx/struct.Id.html
+pub fn module(id: &Id) -> Ident {
+    escape_id(id, NamingConvention::SnakeCase)
+}
 
-    /// Convert a trait name from its [`Id`][witx] name to its Rust [`Ident`][id] representation.
-    ///
-    /// [id]: https://docs.rs/proc-macro2/*/proc_macro2/struct.Ident.html
-    /// [witx]: https://docs.rs/witx/*/witx/struct.Id.html
-    pub fn trait_name(&self, id: &Id) -> Ident {
-        escape_id(id, NamingConvention::CamelCase)
-    }
+/// Convert a trait name from its [`Id`][witx] name to its Rust [`Ident`][id] representation.
+///
+/// [id]: https://docs.rs/proc-macro2/*/proc_macro2/struct.Ident.html
+/// [witx]: https://docs.rs/witx/*/witx/struct.Id.html
+pub fn trait_name(id: &Id) -> Ident {
+    escape_id(id, NamingConvention::CamelCase)
+}
 
-    /// Convert a function name from its [`Id`][witx] name to its Rust [`Ident`][id] representation.
-    ///
-    /// [id]: https://docs.rs/proc-macro2/*/proc_macro2/struct.Ident.html
-    /// [witx]: https://docs.rs/witx/*/witx/struct.Id.html
-    pub fn func(&self, id: &Id) -> Ident {
-        escape_id(id, NamingConvention::SnakeCase)
-    }
+/// Convert a function name from its [`Id`][witx] name to its Rust [`Ident`][id] representation.
+///
+/// [id]: https://docs.rs/proc-macro2/*/proc_macro2/struct.Ident.html
+/// [witx]: https://docs.rs/witx/*/witx/struct.Id.html
+pub fn func(id: &Id) -> Ident {
+    escape_id(id, NamingConvention::SnakeCase)
+}
 
-    /// Convert a parameter name from its [`Id`][witx] name to its Rust [`Ident`][id] representation.
-    ///
-    /// [id]: https://docs.rs/proc-macro2/*/proc_macro2/struct.Ident.html
-    /// [witx]: https://docs.rs/witx/*/witx/struct.Id.html
-    pub fn func_param(&self, id: &Id) -> Ident {
-        escape_id(id, NamingConvention::SnakeCase)
-    }
+/// Convert a parameter name from its [`Id`][witx] name to its Rust [`Ident`][id] representation.
+///
+/// [id]: https://docs.rs/proc-macro2/*/proc_macro2/struct.Ident.html
+/// [witx]: https://docs.rs/witx/*/witx/struct.Id.html
+pub fn func_param(id: &Id) -> Ident {
+    escape_id(id, NamingConvention::SnakeCase)
+}
 
-    /// For when you need a {name}_ptr binding for passing a value by reference:
-    pub fn func_ptr_binding(&self, id: &Id) -> Ident {
-        format_ident!("{}_ptr", id.as_str().to_snake_case())
-    }
+/// For when you need a {name}_ptr binding for passing a value by reference:
+pub fn func_ptr_binding(id: &Id) -> Ident {
+    format_ident!("{}_ptr", id.as_str().to_snake_case())
+}
 
-    /// For when you need a {name}_len binding for passing an array:
-    pub fn func_len_binding(&self, id: &Id) -> Ident {
-        format_ident!("{}_len", id.as_str().to_snake_case())
-    }
+/// For when you need a {name}_len binding for passing an array:
+pub fn func_len_binding(id: &Id) -> Ident {
+    format_ident!("{}_len", id.as_str().to_snake_case())
+}
 
-    fn builtin_name(b: &BuiltinType) -> &'static str {
-        match b {
-            BuiltinType::U8 { .. } => "u8",
-            BuiltinType::U16 => "u16",
-            BuiltinType::U32 { .. } => "u32",
-            BuiltinType::U64 => "u64",
-            BuiltinType::S8 => "i8",
-            BuiltinType::S16 => "i16",
-            BuiltinType::S32 => "i32",
-            BuiltinType::S64 => "i64",
-            BuiltinType::F32 => "f32",
-            BuiltinType::F64 => "f64",
-            BuiltinType::Char => "char",
-        }
+fn builtin_name(b: &BuiltinType) -> &'static str {
+    match b {
+        BuiltinType::U8 { .. } => "u8",
+        BuiltinType::U16 => "u16",
+        BuiltinType::U32 { .. } => "u32",
+        BuiltinType::U64 => "u64",
+        BuiltinType::S8 => "i8",
+        BuiltinType::S16 => "i16",
+        BuiltinType::S32 => "i32",
+        BuiltinType::S64 => "i64",
+        BuiltinType::F32 => "f32",
+        BuiltinType::F64 => "f64",
+        BuiltinType::Char => "char",
     }
+}
 
-    fn snake_typename(tref: &TypeRef) -> String {
-        match tref {
-            TypeRef::Name(nt) => nt.name.as_str().to_snake_case(),
-            TypeRef::Value(ty) => match &**ty {
-                Type::Builtin(b) => Self::builtin_name(&b).to_owned(),
-                _ => panic!("unexpected anonymous type: {:?}", ty),
-            },
-        }
+fn snake_typename(tref: &TypeRef) -> String {
+    match tref {
+        TypeRef::Name(nt) => nt.name.as_str().to_snake_case(),
+        TypeRef::Value(ty) => match &**ty {
+            Type::Builtin(b) => builtin_name(&b).to_owned(),
+            _ => panic!("unexpected anonymous type: {:?}", ty),
+        },
     }
+}
 
-    pub fn user_error_conversion_method(&self, user_type: &UserErrorType) -> Ident {
-        let abi_type = Self::snake_typename(&user_type.abi_type());
-        format_ident!(
-            "{}_from_{}",
-            abi_type,
-            user_type.method_fragment().to_snake_case()
-        )
-    }
+pub fn user_error_conversion_method(user_type: &UserErrorType) -> Ident {
+    let abi_type = snake_typename(&user_type.abi_type());
+    format_ident!(
+        "{}_from_{}",
+        abi_type,
+        user_type.method_fragment().to_snake_case()
+    )
 }
 
 /// Identifier escaping utilities.
diff --git a/crates/wiggle/generate/src/types/error.rs b/crates/wiggle/generate/src/types/error.rs
new file mode 100644
index 000000000..9f0d51f7a
--- /dev/null
+++ b/crates/wiggle/generate/src/types/error.rs
@@ -0,0 +1,53 @@
+use crate::codegen_settings::TrappableErrorType;
+use crate::names;
+
+use proc_macro2::TokenStream;
+use quote::quote;
+
+pub(super) fn define_error(
+    name: &witx::Id,
+    _v: &witx::Variant,
+    e: &TrappableErrorType,
+) -> TokenStream {
+    let abi_error = names::type_(name);
+    let rich_error = e.typename();
+
+    quote! {
+        #[derive(Debug)]
+        pub struct #rich_error {
+            inner: anyhow::Error,
+        }
+
+        impl std::fmt::Display for #rich_error {
+            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+                write!(f, "{}", self.inner)
+            }
+        }
+        impl std::error::Error for #rich_error {
+            fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+                self.inner.source()
+            }
+        }
+
+        impl #rich_error {
+            pub fn trap(inner: anyhow::Error) -> #rich_error {
+                Self { inner }
+            }
+            pub fn downcast(self) -> Result<#abi_error, anyhow::Error> {
+                self.inner.downcast()
+            }
+            pub fn downcast_ref(&self) -> Option<&#abi_error> {
+                self.inner.downcast_ref()
+            }
+            pub fn context(self, s: impl Into<String>) -> Self {
+                Self { inner: self.inner.context(s.into()) }
+            }
+        }
+
+        impl From<#abi_error> for #rich_error {
+            fn from(abi: #abi_error) -> #rich_error {
+                #rich_error { inner: anyhow::Error::from(abi) }
+            }
+        }
+    }
+}
diff --git a/crates/wiggle/generate/src/types/flags.rs b/crates/wiggle/generate/src/types/flags.rs
index 34e35eeb2..a55d3f002 100644
--- a/crates/wiggle/generate/src/types/flags.rs
+++ b/crates/wiggle/generate/src/types/flags.rs
@@ -1,30 +1,28 @@
-use crate::names::Names;
+use crate::names;
 
 use proc_macro2::{Literal, TokenStream};
 use quote::quote;
 
 pub(super) fn define_flags(
-    names: &Names,
     name: &witx::Id,
     repr: witx::IntRepr,
     record: &witx::RecordDatatype,
 ) -> TokenStream {
-    let rt = names.runtime_mod();
-    let ident = names.type_(&name);
-    let abi_repr = names.wasm_type(repr.into());
+    let ident = names::type_(&name);
+    let abi_repr = names::wasm_type(repr.into());
     let repr = super::int_repr_tokens(repr);
 
     let mut names_ = vec![];
     let mut values_ = vec![];
     for (i, member) in record.members.iter().enumerate() {
-        let name = names.flag_member(&member.name);
+        let name = names::flag_member(&member.name);
         let value_token = Literal::usize_unsuffixed(1 << i);
         names_.push(name);
         values_.push(value_token);
     }
 
     quote! {
-        #rt::bitflags::bitflags! {
+        wiggle::bitflags::bitflags! {
             pub struct #ident: #repr {
                 #(const #names_ = #values_;)*
             }
@@ -43,10 +41,10 @@ pub(super) fn define_flags(
         }
 
         impl TryFrom<#repr> for #ident {
-            type Error = #rt::GuestError;
-            fn try_from(value: #repr) -> Result<Self, #rt::GuestError> {
+            type Error = wiggle::GuestError;
+            fn try_from(value: #repr) -> Result<Self, wiggle::GuestError> {
                 if #repr::from(!#ident::all()) & value != 0 {
-                    Err(#rt::GuestError::InvalidFlagValue(stringify!(#ident)))
+                    Err(wiggle::GuestError::InvalidFlagValue(stringify!(#ident)))
                 } else {
                     Ok(#ident { bits: value })
                 }
@@ -54,8 +52,8 @@ pub(super) fn define_flags(
         }
 
         impl TryFrom<#abi_repr> for #ident {
-            type Error = #rt::GuestError;
-            fn try_from(value: #abi_repr) -> Result<Self, #rt::GuestError> {
+            type Error = wiggle::GuestError;
+            fn try_from(value: #abi_repr) -> Result<Self, wiggle::GuestError> {
                 #ident::try_from(#repr::try_from(value)?)
             }
         }
@@ -66,7 +64,7 @@ pub(super) fn define_flags(
             }
         }
 
-        impl<'a> #rt::GuestType<'a> for #ident {
+        impl<'a> wiggle::GuestType<'a> for #ident {
             fn guest_size() -> u32 {
                 #repr::guest_size()
             }
@@ -75,14 +73,14 @@ pub(super) fn define_flags(
                 #repr::guest_align()
             }
 
-            fn read(location: &#rt::GuestPtr<#ident>) -> Result<#ident, #rt::GuestError> {
+            fn read(location: &wiggle::GuestPtr<#ident>) -> Result<#ident, wiggle::GuestError> {
                 use std::convert::TryFrom;
                 let reprval = #repr::read(&location.cast())?;
                 let value = #ident::try_from(reprval)?;
                 Ok(value)
             }
 
-            fn write(location: &#rt::GuestPtr<'_, #ident>, val: Self) -> Result<(), #rt::GuestError> {
+            fn write(location: &wiggle::GuestPtr<'_, #ident>, val: Self) -> Result<(), wiggle::GuestError> {
                 let val: #repr = #repr::from(val);
                 #repr::write(&location.cast(), val)
             }
diff --git a/crates/wiggle/generate/src/types/handle.rs b/crates/wiggle/generate/src/types/handle.rs
index 7eb954c91..cf126484e 100644
--- a/crates/wiggle/generate/src/types/handle.rs
+++ b/crates/wiggle/generate/src/types/handle.rs
@@ -1,16 +1,11 @@
-use crate::names::Names;
+use crate::names;
 
 use proc_macro2::TokenStream;
 use quote::quote;
 use witx::Layout;
 
-pub(super) fn define_handle(
-    names: &Names,
-    name: &witx::Id,
-    h: &witx::HandleDatatype,
-) -> TokenStream {
-    let rt = names.runtime_mod();
-    let ident = names.type_(name);
+pub(super) fn define_handle(name: &witx::Id, h: &witx::HandleDatatype) -> TokenStream {
+    let ident = names::type_(name);
     let size = h.mem_size_align().size as u32;
     let align = h.mem_size_align().align as usize;
     quote! {
@@ -53,7 +48,7 @@ pub(super) fn define_handle(
             }
         }
 
-        impl<'a> #rt::GuestType<'a> for #ident {
+        impl<'a> wiggle::GuestType<'a> for #ident {
             fn guest_size() -> u32 {
                 #size
             }
@@ -62,11 +57,11 @@ pub(super) fn define_handle(
                 #align
             }
 
-            fn read(location: &#rt::GuestPtr<'a, #ident>) -> Result<#ident, #rt::GuestError> {
+            fn read(location: &wiggle::GuestPtr<'a, #ident>) -> Result<#ident, wiggle::GuestError> {
                 Ok(#ident(u32::read(&location.cast())?))
             }
 
-            fn write(location: &#rt::GuestPtr<'_, Self>, val: Self) -> Result<(), #rt::GuestError> {
+            fn write(location: &wiggle::GuestPtr<'_, Self>, val: Self) -> Result<(), wiggle::GuestError> {
                 u32::write(&location.cast(), val.0)
             }
         }
diff --git a/crates/wiggle/generate/src/types/mod.rs b/crates/wiggle/generate/src/types/mod.rs
index 255c2ce7b..2820db9fb 100644
--- a/crates/wiggle/generate/src/types/mod.rs
+++ b/crates/wiggle/generate/src/types/mod.rs
@@ -1,42 +1,49 @@
 // mod r#enum;
+mod error;
 mod flags;
 mod handle;
 mod record;
 mod variant;
 
+use crate::codegen_settings::ErrorType;
 use crate::lifetimes::LifetimeExt;
-use crate::names::Names;
+use crate::names;
 
 use proc_macro2::TokenStream;
 use quote::quote;
 
-pub fn define_datatype(names: &Names, namedtype: &witx::NamedType) -> TokenStream {
+pub fn define_datatype(namedtype: &witx::NamedType, error: Option<&ErrorType>) -> TokenStream {
     match &namedtype.tref {
-        witx::TypeRef::Name(alias_to) => define_alias(names, &namedtype.name, &alias_to),
+        witx::TypeRef::Name(alias_to) => define_alias(&namedtype.name, &alias_to),
         witx::TypeRef::Value(v) => match &**v {
             witx::Type::Record(r) => match r.bitflags_repr() {
-                Some(repr) => flags::define_flags(names, &namedtype.name, repr, &r),
-                None => record::define_struct(names, &namedtype.name, &r),
+                Some(repr) => flags::define_flags(&namedtype.name, repr, &r),
+                None => record::define_struct(&namedtype.name, &r),
             },
-            witx::Type::Variant(v) => variant::define_variant(names, &namedtype.name, &v),
-            witx::Type::Handle(h) => handle::define_handle(names, &namedtype.name, &h),
-            witx::Type::Builtin(b) => define_builtin(names, &namedtype.name, *b),
+            witx::Type::Variant(v) => match error {
+                Some(ErrorType::Generated(error)) => {
+                    let d = variant::define_variant(&namedtype.name, &v, true);
+                    let e = error::define_error(&namedtype.name, &v, error);
+                    quote!( #d #e )
+                }
+                _ => variant::define_variant(&namedtype.name, &v, false),
+            },
+            witx::Type::Handle(h) => handle::define_handle(&namedtype.name, &h),
+            witx::Type::Builtin(b) => define_builtin(&namedtype.name, *b),
             witx::Type::Pointer(p) => {
-                let rt = names.runtime_mod();
-                define_witx_pointer(names, &namedtype.name, quote!(#rt::GuestPtr), p)
+                define_witx_pointer(&namedtype.name, quote!(wiggle::GuestPtr), p)
             }
             witx::Type::ConstPointer(p) => {
-                let rt = names.runtime_mod();
-                define_witx_pointer(names, &namedtype.name, quote!(#rt::GuestPtr), p)
+                define_witx_pointer(&namedtype.name, quote!(wiggle::GuestPtr), p)
             }
-            witx::Type::List(arr) => define_witx_list(names, &namedtype.name, &arr),
+            witx::Type::List(arr) => define_witx_list(&namedtype.name, &arr),
         },
     }
 }
 
-fn define_alias(names: &Names, name: &witx::Id, to: &witx::NamedType) -> TokenStream {
-    let ident = names.type_(name);
-    let rhs = names.type_(&to.name);
+fn define_alias(name: &witx::Id, to: &witx::NamedType) -> TokenStream {
+    let ident = names::type_(name);
+    let rhs = names::type_(&to.name);
     if to.tref.needs_lifetime() {
         quote!(pub type #ident<'a> = #rhs<'a>;)
     } else {
@@ -44,29 +51,27 @@ fn define_alias(names: &Names, name: &witx::Id, to: &witx::NamedType) -> TokenSt
     }
 }
 
-fn define_builtin(names: &Names, name: &witx::Id, builtin: witx::BuiltinType) -> TokenStream {
-    let ident = names.type_(name);
-    let built = names.builtin_type(builtin);
+fn define_builtin(name: &witx::Id, builtin: witx::BuiltinType) -> TokenStream {
+    let ident = names::type_(name);
+    let built = names::builtin_type(builtin);
     quote!(pub type #ident = #built;)
 }
 
 fn define_witx_pointer(
-    names: &Names,
     name: &witx::Id,
     pointer_type: TokenStream,
     pointee: &witx::TypeRef,
 ) -> TokenStream {
-    let ident = names.type_(name);
-    let pointee_type = names.type_ref(pointee, quote!('a));
+    let ident = names::type_(name);
+    let pointee_type = names::type_ref(pointee, quote!('a));
 
     quote!(pub type #ident<'a> = #pointer_type<'a, #pointee_type>;)
 }
 
-fn define_witx_list(names: &Names, name: &witx::Id, arr_raw: &witx::TypeRef) -> TokenStream {
-    let ident = names.type_(name);
-    let rt = names.runtime_mod();
-    let pointee_type = names.type_ref(arr_raw, quote!('a));
-    quote!(pub type #ident<'a> = #rt::GuestPtr<'a, [#pointee_type]>;)
+fn define_witx_list(name: &witx::Id, arr_raw: &witx::TypeRef) -> TokenStream {
+    let ident = names::type_(name);
+    let pointee_type = names::type_ref(arr_raw, quote!('a));
+    quote!(pub type #ident<'a> = wiggle::GuestPtr<'a, [#pointee_type]>;)
 }
 
 pub fn int_repr_tokens(int_repr: witx::IntRepr) -> TokenStream {
diff --git a/crates/wiggle/generate/src/types/record.rs b/crates/wiggle/generate/src/types/record.rs
index d50124d7e..a5b48d182 100644
--- a/crates/wiggle/generate/src/types/record.rs
+++ b/crates/wiggle/generate/src/types/record.rs
@@ -1,26 +1,21 @@
 use crate::lifetimes::{anon_lifetime, LifetimeExt};
-use crate::names::Names;
+use crate::names;
 
 use proc_macro2::TokenStream;
 use quote::quote;
 use witx::Layout;
 
-pub(super) fn define_struct(
-    names: &Names,
-    name: &witx::Id,
-    s: &witx::RecordDatatype,
-) -> TokenStream {
-    let rt = names.runtime_mod();
-    let ident = names.type_(name);
+pub(super) fn define_struct(name: &witx::Id, s: &witx::RecordDatatype) -> TokenStream {
+    let ident = names::type_(name);
     let size = s.mem_size_align().size as u32;
     let align = s.mem_size_align().align as usize;
 
-    let member_names = s.members.iter().map(|m| names.struct_member(&m.name));
+    let member_names = s.members.iter().map(|m| names::struct_member(&m.name));
     let member_decls = s.members.iter().map(|m| {
-        let name = names.struct_member(&m.name);
+        let name = names::struct_member(&m.name);
         let type_ = match &m.tref {
             witx::TypeRef::Name(nt) => {
-                let tt = names.type_(&nt.name);
+                let tt = names::type_(&nt.name);
                 if m.tref.needs_lifetime() {
                     quote!(#tt<'a>)
                 } else {
@@ -28,10 +23,10 @@ pub(super) fn define_struct(
                 }
             }
             witx::TypeRef::Value(ty) => match &**ty {
-                witx::Type::Builtin(builtin) => names.builtin_type(*builtin),
+                witx::Type::Builtin(builtin) => names::builtin_type(*builtin),
                 witx::Type::Pointer(pointee) | witx::Type::ConstPointer(pointee) => {
-                    let pointee_type = names.type_ref(&pointee, quote!('a));
-                    quote!(#rt::GuestPtr<'a, #pointee_type>)
+                    let pointee_type = names::type_ref(&pointee, quote!('a));
+                    quote!(wiggle::GuestPtr<'a, #pointee_type>)
                 }
                 _ => unimplemented!("other anonymous struct members: {:?}", m.tref),
             },
@@ -40,27 +35,27 @@ pub(super) fn define_struct(
     });
 
     let member_reads = s.member_layout().into_iter().map(|ml| {
-        let name = names.struct_member(&ml.member.name);
+        let name = names::struct_member(&ml.member.name);
         let offset = ml.offset as u32;
         let location = quote!(location.cast::<u8>().add(#offset)?.cast());
         match &ml.member.tref {
             witx::TypeRef::Name(nt) => {
-                let type_ = names.type_(&nt.name);
+                let type_ = names::type_(&nt.name);
                 quote! {
-                    let #name = <#type_ as #rt::GuestType>::read(&#location)?;
+                    let #name = <#type_ as wiggle::GuestType>::read(&#location)?;
                 }
             }
             witx::TypeRef::Value(ty) => match &**ty {
                 witx::Type::Builtin(builtin) => {
-                    let type_ = names.builtin_type(*builtin);
+                    let type_ = names::builtin_type(*builtin);
                     quote! {
-                        let #name = <#type_ as #rt::GuestType>::read(&#location)?;
+                        let #name = <#type_ as wiggle::GuestType>::read(&#location)?;
                     }
                 }
                 witx::Type::Pointer(pointee) | witx::Type::ConstPointer(pointee) => {
-                    let pointee_type = names.type_ref(&pointee, anon_lifetime());
+                    let pointee_type = names::type_ref(&pointee, anon_lifetime());
                     quote! {
-                        let #name = <#rt::GuestPtr::<#pointee_type> as #rt::GuestType>::read(&#location)?;
+                        let #name = <wiggle::GuestPtr::<#pointee_type> as wiggle::GuestType>::read(&#location)?;
                     }
                 }
                 _ => unimplemented!("other anonymous struct members: {:?}", ty),
@@ -69,10 +64,10 @@ pub(super) fn define_struct(
     });
 
     let member_writes = s.member_layout().into_iter().map(|ml| {
-        let name = names.struct_member(&ml.member.name);
+        let name = names::struct_member(&ml.member.name);
         let offset = ml.offset as u32;
         quote! {
-            #rt::GuestType::write(
+            wiggle::GuestType::write(
                 &location.cast::<u8>().add(#offset)?.cast(),
                 val.#name,
             )?;
@@ -91,7 +86,7 @@ pub(super) fn define_struct(
             #(#member_decls),*
         }
 
-        impl<'a> #rt::GuestType<'a> for #ident #struct_lifetime {
+        impl<'a> wiggle::GuestType<'a> for #ident #struct_lifetime {
             fn guest_size() -> u32 {
                 #size
             }
@@ -100,12 +95,12 @@ pub(super) fn define_struct(
                 #align
             }
 
-            fn read(location: &#rt::GuestPtr<'a, Self>) -> Result<Self, #rt::GuestError> {
+            fn read(location: &wiggle::GuestPtr<'a, Self>) -> Result<Self, wiggle::GuestError> {
                 #(#member_reads)*
                 Ok(#ident { #(#member_names),* })
             }
 
-            fn write(location: &#rt::GuestPtr<'_, Self>, val: Self) -> Result<(), #rt::GuestError> {
+            fn write(location: &wiggle::GuestPtr<'_, Self>, val: Self) -> Result<(), wiggle::GuestError> {
                 #(#member_writes)*
                 Ok(())
             }
diff --git a/crates/wiggle/generate/src/types/variant.rs b/crates/wiggle/generate/src/types/variant.rs
index 5624b43eb..c16e3bc72 100644
--- a/crates/wiggle/generate/src/types/variant.rs
+++ b/crates/wiggle/generate/src/types/variant.rs
@@ -1,13 +1,16 @@
 use crate::lifetimes::LifetimeExt;
-use crate::names::Names;
+use crate::names;
 
 use proc_macro2::{Literal, TokenStream};
 use quote::quote;
 use witx::Layout;
 
-pub(super) fn define_variant(names: &Names, name: &witx::Id, v: &witx::Variant) -> TokenStream {
-    let rt = names.runtime_mod();
-    let ident = names.type_(name);
+pub(super) fn define_variant(
+    name: &witx::Id,
+    v: &witx::Variant,
+    derive_std_error: bool,
+) -> TokenStream {
+    let ident = names::type_(name);
     let size = v.mem_size_align().size as u32;
     let align = v.mem_size_align().align as usize;
     let contents_offset = v.payload_offset() as u32;
@@ -16,9 +19,9 @@ pub(super) fn define_variant(names: &Names, name: &witx::Id, v: &witx::Variant)
     let tag_ty = super::int_repr_tokens(v.tag_repr);
 
     let variants = v.cases.iter().map(|c| {
-        let var_name = names.enum_variant(&c.name);
+        let var_name = names::enum_variant(&c.name);
         if let Some(tref) = &c.tref {
-            let var_type = names.type_ref(&tref, lifetime.clone());
+            let var_type = names::type_ref(&tref, lifetime.clone());
             quote!(#var_name(#var_type))
         } else {
             quote!(#var_name)
@@ -27,13 +30,13 @@ pub(super) fn define_variant(names: &Names, name: &witx::Id, v: &witx::Variant)
 
     let read_variant = v.cases.iter().enumerate().map(|(i, c)| {
         let i = Literal::usize_unsuffixed(i);
-        let variantname = names.enum_variant(&c.name);
+        let variantname = names::enum_variant(&c.name);
         if let Some(tref) = &c.tref {
-            let varianttype = names.type_ref(tref, lifetime.clone());
+            let varianttype = names::type_ref(tref, lifetime.clone());
             quote! {
                 #i => {
                     let variant_ptr = location.cast::<u8>().add(#contents_offset)?;
-                    let variant_val = <#varianttype as #rt::GuestType>::read(&variant_ptr.cast())?;
+                    let variant_val = <#varianttype as wiggle::GuestType>::read(&variant_ptr.cast())?;
                     Ok(#ident::#variantname(variant_val))
                 }
             }
@@ -43,17 +46,17 @@ pub(super) fn define_variant(names: &Names, name: &witx::Id, v: &witx::Variant)
     });
 
     let write_variant = v.cases.iter().enumerate().map(|(i, c)| {
-        let variantname = names.enum_variant(&c.name);
+        let variantname = names::enum_variant(&c.name);
         let write_tag = quote! {
             location.cast().write(#i as #tag_ty)?;
         };
         if let Some(tref) = &c.tref {
-            let varianttype = names.type_ref(tref, lifetime.clone());
+            let varianttype = names::type_ref(tref, lifetime.clone());
             quote! {
                 #ident::#variantname(contents) => {
                     #write_tag
                     let variant_ptr = location.cast::<u8>().add(#contents_offset)?;
-                    <#varianttype as #rt::GuestType>::write(&variant_ptr.cast(), contents)?;
+                    <#varianttype as wiggle::GuestType>::write(&variant_ptr.cast(), contents)?;
                 }
             }
         } else {
@@ -68,26 +71,26 @@ pub(super) fn define_variant(names: &Names, name: &witx::Id, v: &witx::Variant)
     let mut extra_derive = quote!();
     let enum_try_from = if v.cases.iter().all(|c| c.tref.is_none()) {
         let tryfrom_repr_cases = v.cases.iter().enumerate().map(|(i, c)| {
-            let variant_name = names.enum_variant(&c.name);
+            let variant_name = names::enum_variant(&c.name);
             let n = Literal::usize_unsuffixed(i);
             quote!(#n => Ok(#ident::#variant_name))
         });
-        let abi_ty = names.wasm_type(v.tag_repr.into());
+        let abi_ty = names::wasm_type(v.tag_repr.into());
         extra_derive = quote!(, Copy);
         quote! {
             impl TryFrom<#tag_ty> for #ident {
-                type Error = #rt::GuestError;
-                fn try_from(value: #tag_ty) -> Result<#ident, #rt::GuestError> {
+                type Error = wiggle::GuestError;
+                fn try_from(value: #tag_ty) -> Result<#ident, wiggle::GuestError> {
                     match value {
                         #(#tryfrom_repr_cases),*,
-                        _ => Err( #rt::GuestError::InvalidEnumValue(stringify!(#ident))),
+                        _ => Err(wiggle::GuestError::InvalidEnumValue(stringify!(#ident))),
                     }
                 }
             }
 
             impl TryFrom<#abi_ty> for #ident {
-                type Error = #rt::GuestError;
-                fn try_from(value: #abi_ty) -> Result<#ident, #rt::GuestError> {
+                type Error = wiggle::GuestError;
+                fn try_from(value: #abi_ty) -> Result<#ident, wiggle::GuestError> {
                     #ident::try_from(#tag_ty::try_from(value)?)
                 }
             }
@@ -98,7 +101,7 @@ pub(super) fn define_variant(names: &Names, name: &witx::Id, v: &witx::Variant)
 
     let enum_from = if v.cases.iter().all(|c| c.tref.is_none()) {
         let from_repr_cases = v.cases.iter().enumerate().map(|(i, c)| {
-            let variant_name = names.enum_variant(&c.name);
+            let variant_name = names::enum_variant(&c.name);
             let n = Literal::usize_unsuffixed(i);
             quote!(#ident::#variant_name => #n)
         });
@@ -121,16 +124,30 @@ pub(super) fn define_variant(names: &Names, name: &witx::Id, v: &witx::Variant)
         (quote!(), quote!(, PartialEq #extra_derive))
     };
 
+    let error_impls = if derive_std_error {
+        quote! {
+            impl std::fmt::Display for #ident {
+                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+                    write!(f, "{:?}", self)
+                }
+            }
+            impl std::error::Error for #ident {}
+        }
+    } else {
+        quote!()
+    };
+
     quote! {
         #[derive(Clone, Debug #extra_derive)]
         pub enum #ident #enum_lifetime {
             #(#variants),*
         }
+        #error_impls
 
         #enum_try_from
         #enum_from
 
-        impl<'a> #rt::GuestType<'a> for #ident #enum_lifetime {
+        impl<'a> wiggle::GuestType<'a> for #ident #enum_lifetime {
             fn guest_size() -> u32 {
                 #size
             }
@@ -139,19 +156,19 @@ pub(super) fn define_variant(names: &Names, name: &witx::Id, v: &witx::Variant)
                 #align
             }
 
-            fn read(location: &#rt::GuestPtr<'a, Self>)
-                -> Result<Self, #rt::GuestError>
+            fn read(location: &wiggle::GuestPtr<'a, Self>)
+                -> Result<Self, wiggle::GuestError>
             {
                 let tag = location.cast::<#tag_ty>().read()?;
                 match tag {
                     #(#read_variant)*
-                    _ => Err(#rt::GuestError::InvalidEnumValue(stringify!(#ident))),
+                    _ => Err(wiggle::GuestError::InvalidEnumValue(stringify!(#ident))),
                 }
 
             }
 
-            fn write(location: &#rt::GuestPtr<'_, Self>, val: Self)
-                -> Result<(), #rt::GuestError>
+            fn write(location: &wiggle::GuestPtr<'_, Self>, val: Self)
+                -> Result<(), wiggle::GuestError>
             {
                 match val {
                     #(#write_variant)*
diff --git a/crates/wiggle/generate/src/wasmtime.rs b/crates/wiggle/generate/src/wasmtime.rs
index 98c9f342c..fcab9da62 100644
--- a/crates/wiggle/generate/src/wasmtime.rs
+++ b/crates/wiggle/generate/src/wasmtime.rs
@@ -1,17 +1,17 @@
 use crate::config::Asyncness;
 use crate::funcs::func_bounds;
-use crate::{CodegenSettings, Names};
+use crate::names;
+use crate::CodegenSettings;
 use proc_macro2::{Ident, Span, TokenStream};
 use quote::{format_ident, quote};
 use std::collections::HashSet;
 
 pub fn link_module(
     module: &witx::Module,
-    names: &Names,
     target_path: Option<&syn::Path>,
     settings: &CodegenSettings,
 ) -> TokenStream {
-    let module_ident = names.module(&module.name);
+    let module_ident = names::module(&module.name);
 
     let send_bound = if settings.async_.contains_async(module) {
         quote! { + Send, T: Send }
@@ -23,8 +23,8 @@ pub fn link_module(
     let mut bounds = HashSet::new();
     for f in module.funcs() {
         let asyncness = settings.async_.get(module.name.as_str(), f.name.as_str());
-        bodies.push(generate_func(&module, &f, names, target_path, asyncness));
-        let bound = func_bounds(names, module, &f, settings);
+        bodies.push(generate_func(&module, &f, target_path, asyncness));
+        let bound = func_bounds(module, &f, settings);
         for b in bound {
             bounds.insert(b);
         }
@@ -46,14 +46,12 @@ pub fn link_module(
         format_ident!("add_{}_to_linker", module_ident)
     };
 
-    let rt = names.runtime_mod();
-
     quote! {
         /// Adds all instance items to the specified `Linker`.
         pub fn #func_name<T, U>(
-            linker: &mut #rt::wasmtime_crate::Linker<T>,
+            linker: &mut wiggle::wasmtime_crate::Linker<T>,
             get_cx: impl Fn(&mut T) -> &mut U + Send + Sync + Copy + 'static,
-        ) -> #rt::anyhow::Result<()>
+        ) -> wiggle::anyhow::Result<()>
             where
                 U: #ctx_bound #send_bound
         {
@@ -66,17 +64,14 @@ pub fn link_module(
 fn generate_func(
     module: &witx::Module,
     func: &witx::InterfaceFunc,
-    names: &Names,
     target_path: Option<&syn::Path>,
     asyncness: Asyncness,
 ) -> TokenStream {
-    let rt = names.runtime_mod();
-
     let module_str = module.name.as_str();
-    let module_ident = names.module(&module.name);
+    let module_ident = names::module(&module.name);
 
     let field_str = func.name.as_str();
-    let field_ident = names.func(&func.name);
+    let field_ident = names::func(&func.name);
 
     let (params, results) = func.wasm_signature();
 
@@ -88,14 +83,14 @@ fn generate_func(
         .enumerate()
         .map(|(i, ty)| {
             let name = &arg_names[i];
-            let wasm = names.wasm_type(*ty);
+            let wasm = names::wasm_type(*ty);
             quote! { #name: #wasm }
         })
         .collect::<Vec<_>>();
 
     let ret_ty = match results.len() {
         0 => quote!(()),
-        1 => names.wasm_type(results[0]),
+        1 => names::wasm_type(results[0]),
         _ => unimplemented!(),
     };
 
@@ -114,16 +109,16 @@ fn generate_func(
     let body = quote! {
         let export = caller.get_export("memory");
         let (mem, ctx) = match &export {
-            Some(#rt::wasmtime_crate::Extern::Memory(m)) => {
+            Some(wiggle::wasmtime_crate::Extern::Memory(m)) => {
                 let (mem, ctx) = m.data_and_store_mut(&mut caller);
                 let ctx = get_cx(ctx);
-                (#rt::wasmtime::WasmtimeGuestMemory::new(mem), ctx)
+                (wiggle::wasmtime::WasmtimeGuestMemory::new(mem), ctx)
             }
-            Some(#rt::wasmtime_crate::Extern::SharedMemory(m)) => {
+            Some(wiggle::wasmtime_crate::Extern::SharedMemory(m)) => {
                 let ctx = get_cx(caller.data_mut());
-                (#rt::wasmtime::WasmtimeGuestMemory::shared(m.data()), ctx)
+                (wiggle::wasmtime::WasmtimeGuestMemory::shared(m.data()), ctx)
             }
-            _ => #rt::anyhow::bail!("missing required memory export"),
+            _ => wiggle::anyhow::bail!("missing required memory export"),
         };
         Ok(<#ret_ty>::from(#abi_func(ctx, &mem #(, #arg_names)*) #await_ ?))
     };
@@ -135,7 +130,7 @@ fn generate_func(
                 linker.#wrapper(
                     #module_str,
                     #field_str,
-                    move |mut caller: #rt::wasmtime_crate::Caller<'_, T> #(, #arg_decls)*| {
+                    move |mut caller: wiggle::wasmtime_crate::Caller<'_, T> #(, #arg_decls)*| {
                         Box::new(async move { #body })
                     },
                 )?;
@@ -147,9 +142,9 @@ fn generate_func(
                 linker.func_wrap(
                     #module_str,
                     #field_str,
-                    move |mut caller: #rt::wasmtime_crate::Caller<'_, T> #(, #arg_decls)*| -> #rt::anyhow::Result<#ret_ty> {
+                    move |mut caller: wiggle::wasmtime_crate::Caller<'_, T> #(, #arg_decls)*| -> wiggle::anyhow::Result<#ret_ty> {
                         let result = async { #body };
-                        #rt::run_in_dummy_executor(result)?
+                        wiggle::run_in_dummy_executor(result)?
                     },
                 )?;
             }
@@ -160,7 +155,7 @@ fn generate_func(
                 linker.func_wrap(
                     #module_str,
                     #field_str,
-                    move |mut caller: #rt::wasmtime_crate::Caller<'_, T> #(, #arg_decls)*| -> #rt::anyhow::Result<#ret_ty> {
+                    move |mut caller: wiggle::wasmtime_crate::Caller<'_, T> #(, #arg_decls)*| -> wiggle::anyhow::Result<#ret_ty> {
                         #body
                     },
                 )?;
diff --git a/crates/wiggle/macro/src/lib.rs b/crates/wiggle/macro/src/lib.rs
index 11bbbfc01..9ea2cf104 100644
--- a/crates/wiggle/macro/src/lib.rs
+++ b/crates/wiggle/macro/src/lib.rs
@@ -39,6 +39,10 @@ use syn::parse_macro_input;
 ///   `{ errno => YourErrnoType }`. This allows you to use the `UserErrorConversion`
 ///   trait to map these rich errors into the flat witx type, or to terminate
 ///   WebAssembly execution by trapping.
+///     * Instead of requiring the user to define an error type, wiggle can
+///       generate an error type for the user which has conversions to/from
+///       the base type, and permits trapping, using the syntax
+///       `errno => trappable AnErrorType`.
 /// * Optional: `async` takes a set of witx modules and functions which are
 ///   made Rust `async` functions in the module trait.
 ///
@@ -146,7 +150,6 @@ pub fn from_witx(args: TokenStream) -> TokenStream {
     let config = parse_macro_input!(args as wiggle_generate::Config);
 
     let doc = config.load_document();
-    let names = wiggle_generate::Names::new(quote!(wiggle));
 
     let settings = wiggle_generate::CodegenSettings::new(
         &config.errors,
@@ -157,9 +160,9 @@ pub fn from_witx(args: TokenStream) -> TokenStream {
     )
     .expect("validating codegen settings");
 
-    let code = wiggle_generate::generate(&doc, &names, &settings);
+    let code = wiggle_generate::generate(&doc, &settings);
     let metadata = if cfg!(feature = "wiggle_metadata") {
-        wiggle_generate::generate_metadata(&doc, &names)
+        wiggle_generate::generate_metadata(&doc)
     } else {
         quote!()
     };
@@ -188,7 +191,6 @@ pub fn async_trait(attr: TokenStream, item: TokenStream) -> TokenStream {
 pub fn wasmtime_integration(args: TokenStream) -> TokenStream {
     let config = parse_macro_input!(args as wiggle_generate::WasmtimeConfig);
     let doc = config.c.load_document();
-    let names = wiggle_generate::Names::new(quote!(wiggle));
 
     let settings = wiggle_generate::CodegenSettings::new(
         &config.c.errors,
@@ -200,7 +202,7 @@ pub fn wasmtime_integration(args: TokenStream) -> TokenStream {
     .expect("validating codegen settings");
 
     let modules = doc.modules().map(|module| {
-        wiggle_generate::wasmtime::link_module(&module, &names, Some(&config.target), &settings)
+        wiggle_generate::wasmtime::link_module(&module, Some(&config.target), &settings)
     });
     quote!( #(#modules)* ).into()
 }
diff --git a/docs/lang-rust.md b/docs/lang-rust.md
index 5a4a3747e..cc5d57248 100644
--- a/docs/lang-rust.md
+++ b/docs/lang-rust.md
@@ -86,7 +86,7 @@ fn main() -> Result<(), Box<dyn Error>> {
     // There's a few ways we can call the `answer` `Func` value. The easiest
     // is to statically assert its signature with `typed` (in this case
     // asserting it takes no arguments and returns one i32) and then call it.
-    let answer = answer.typed::<(), i32, _>(&store)?;
+    let answer = answer.typed::<(), i32>(&store)?;
 
     // And finally we can call our function! Note that the error propagation
     // with `?` is done to handle the case where the wasm function traps.
@@ -184,7 +184,7 @@ fn main() -> Result<(), Box<dyn Error>> {
     let instance = linker.instantiate(&mut store, &module)?;
 
     // Like before, we can get the run function and execute it.
-    let run = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let run = instance.get_typed_func::<(), ()>(&mut store, "run")?;
     run.call(&mut store, ())?;
 
     // We can also inspect what integers were logged:
diff --git a/docs/wasm-wat.md b/docs/wasm-wat.md
index c08fc49e7..634d35730 100644
--- a/docs/wasm-wat.md
+++ b/docs/wasm-wat.md
@@ -47,7 +47,7 @@ let wat = r#"
 "#;
 let module = Module::new(store.engine(), wat)?;
 let instance = Instance::new(&mut store, &module, &[])?;
-let add = instance.get_typed_func::<(i32, i32), i32, _>(&mut store, "add")?;
+let add = instance.get_typed_func::<(i32, i32), i32>(&mut store, "add")?;
 println!("1 + 2 = {}", add.call(&mut store, (1, 2))?);
 # Ok(())
 # }
diff --git a/examples/epochs.rs b/examples/epochs.rs
index fd19fd28d..47d72d7b3 100644
--- a/examples/epochs.rs
+++ b/examples/epochs.rs
@@ -36,7 +36,7 @@ fn main() -> Result<(), Error> {
 
     // Invoke `fibonacci` with a large argument such that a normal
     // invocation would take many seconds to complete.
-    let fibonacci = instance.get_typed_func::<i32, i32, _>(&mut store, "fibonacci")?;
+    let fibonacci = instance.get_typed_func::<i32, i32>(&mut store, "fibonacci")?;
     match fibonacci.call(&mut store, 100) {
         Ok(_) => panic!("Somehow we computed recursive fib(100) in less than a second!"),
         Err(_) => {
diff --git a/examples/externref.rs b/examples/externref.rs
index 794e5fbb7..67c91d8af 100644
--- a/examples/externref.rs
+++ b/examples/externref.rs
@@ -44,7 +44,7 @@ fn main() -> Result<()> {
 
     println!("Calling `externref` func...");
     let func =
-        instance.get_typed_func::<Option<ExternRef>, Option<ExternRef>, _>(&mut store, "func")?;
+        instance.get_typed_func::<Option<ExternRef>, Option<ExternRef>>(&mut store, "func")?;
     let ret = func.call(&mut store, Some(externref.clone()))?;
     assert!(ret.is_some());
     assert!(ret.unwrap().ptr_eq(&externref));
diff --git a/examples/fib-debug/main.rs b/examples/fib-debug/main.rs
index 66216081a..b9e51f229 100644
--- a/examples/fib-debug/main.rs
+++ b/examples/fib-debug/main.rs
@@ -21,7 +21,7 @@ fn main() -> Result<()> {
     let instance = Instance::new(&mut store, &module, &[])?;
 
     // Invoke `fib` export
-    let fib = instance.get_typed_func::<i32, i32, _>(&mut store, "fib")?;
+    let fib = instance.get_typed_func::<i32, i32>(&mut store, "fib")?;
     println!("fib(6) = {}", fib.call(&mut store, 6)?);
     Ok(())
 }
diff --git a/examples/fuel.rs b/examples/fuel.rs
index acca9f2d9..e6f4c74ec 100644
--- a/examples/fuel.rs
+++ b/examples/fuel.rs
@@ -15,7 +15,7 @@ fn main() -> Result<()> {
     let instance = Instance::new(&mut store, &module, &[])?;
 
     // Invoke `fibonacci` export with higher and higher numbers until we exhaust our fuel.
-    let fibonacci = instance.get_typed_func::<i32, i32, _>(&mut store, "fibonacci")?;
+    let fibonacci = instance.get_typed_func::<i32, i32>(&mut store, "fibonacci")?;
     for n in 1.. {
         let fuel_before = store.fuel_consumed().unwrap();
         let output = match fibonacci.call(&mut store, n) {
diff --git a/examples/gcd.rs b/examples/gcd.rs
index a748c6d82..176ee656e 100644
--- a/examples/gcd.rs
+++ b/examples/gcd.rs
@@ -15,7 +15,7 @@ fn main() -> Result<()> {
     let instance = Instance::new(&mut store, &module, &[])?;
 
     // Invoke `gcd` export
-    let gcd = instance.get_typed_func::<(i32, i32), i32, _>(&mut store, "gcd")?;
+    let gcd = instance.get_typed_func::<(i32, i32), i32>(&mut store, "gcd")?;
 
     println!("gcd(6, 27) = {}", gcd.call(&mut store, (6, 27))?);
     Ok(())
diff --git a/examples/hello.rs b/examples/hello.rs
index 62ae9fa35..67df450ec 100644
--- a/examples/hello.rs
+++ b/examples/hello.rs
@@ -53,7 +53,7 @@ fn main() -> Result<()> {
 
     // Next we poke around a bit to extract the `run` function from the module.
     println!("Extracting export...");
-    let run = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let run = instance.get_typed_func::<(), ()>(&mut store, "run")?;
 
     // And last but not least we can call it!
     println!("Calling export...");
diff --git a/examples/interrupt.rs b/examples/interrupt.rs
index 749a63304..87d6d4dd0 100644
--- a/examples/interrupt.rs
+++ b/examples/interrupt.rs
@@ -16,7 +16,7 @@ fn main() -> Result<()> {
     // Compile and instantiate a small example with an infinite loop.
     let module = Module::from_file(&engine, "examples/interrupt.wat")?;
     let instance = Instance::new(&mut store, &module, &[])?;
-    let run = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let run = instance.get_typed_func::<(), ()>(&mut store, "run")?;
 
     // Spin up a thread to send us an interrupt in a second
     std::thread::spawn(move || {
diff --git a/examples/linking.rs b/examples/linking.rs
index fbcd11dfd..a2d756057 100644
--- a/examples/linking.rs
+++ b/examples/linking.rs
@@ -32,7 +32,7 @@ fn main() -> Result<()> {
 
     // And with that we can perform the final link and the execute the module.
     let linking1 = linker.instantiate(&mut store, &linking1)?;
-    let run = linking1.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let run = linking1.get_typed_func::<(), ()>(&mut store, "run")?;
     run.call(&mut store, ())?;
     Ok(())
 }
diff --git a/examples/memory.rs b/examples/memory.rs
index ab971b2d0..93c712cf1 100644
--- a/examples/memory.rs
+++ b/examples/memory.rs
@@ -20,9 +20,9 @@ fn main() -> Result<()> {
     let memory = instance
         .get_memory(&mut store, "memory")
         .ok_or(anyhow::format_err!("failed to find `memory` export"))?;
-    let size = instance.get_typed_func::<(), i32, _>(&mut store, "size")?;
-    let load_fn = instance.get_typed_func::<i32, i32, _>(&mut store, "load")?;
-    let store_fn = instance.get_typed_func::<(i32, i32), (), _>(&mut store, "store")?;
+    let size = instance.get_typed_func::<(), i32>(&mut store, "size")?;
+    let load_fn = instance.get_typed_func::<i32, i32>(&mut store, "load")?;
+    let store_fn = instance.get_typed_func::<(i32, i32), ()>(&mut store, "store")?;
 
     println!("Checking memory...");
     assert_eq!(memory.size(&store), 2);
diff --git a/examples/multi.rs b/examples/multi.rs
index b243d83cd..8f495847b 100644
--- a/examples/multi.rs
+++ b/examples/multi.rs
@@ -33,7 +33,7 @@ fn main() -> Result<()> {
 
     // Extract exports.
     println!("Extracting export...");
-    let g = instance.get_typed_func::<(i32, i64), (i64, i32), _>(&mut store, "g")?;
+    let g = instance.get_typed_func::<(i32, i64), (i64, i32)>(&mut store, "g")?;
 
     // Call `$g`.
     println!("Calling export \"g\"...");
@@ -51,7 +51,6 @@ fn main() -> Result<()> {
         .get_typed_func::<
         (i64, i64, i64, i64, i64, i64, i64, i64, i64, i64),
         (i64, i64, i64, i64, i64, i64, i64, i64, i64, i64),
-        _,
         >
         (&mut store, "round_trip_many")?;
     let results = round_trip_many.call(&mut store, (0, 1, 2, 3, 4, 5, 6, 7, 8, 9))?;
diff --git a/examples/multimemory.rs b/examples/multimemory.rs
index 9a1dd8e16..023817f6a 100644
--- a/examples/multimemory.rs
+++ b/examples/multimemory.rs
@@ -25,16 +25,16 @@ fn main() -> Result<()> {
     let memory0 = instance
         .get_memory(&mut store, "memory0")
         .ok_or(anyhow::format_err!("failed to find `memory0` export"))?;
-    let size0 = instance.get_typed_func::<(), i32, _>(&mut store, "size0")?;
-    let load0 = instance.get_typed_func::<i32, i32, _>(&mut store, "load0")?;
-    let store0 = instance.get_typed_func::<(i32, i32), (), _>(&mut store, "store0")?;
+    let size0 = instance.get_typed_func::<(), i32>(&mut store, "size0")?;
+    let load0 = instance.get_typed_func::<i32, i32>(&mut store, "load0")?;
+    let store0 = instance.get_typed_func::<(i32, i32), ()>(&mut store, "store0")?;
 
     let memory1 = instance
         .get_memory(&mut store, "memory1")
         .ok_or(anyhow::format_err!("failed to find `memory1` export"))?;
-    let size1 = instance.get_typed_func::<(), i32, _>(&mut store, "size1")?;
-    let load1 = instance.get_typed_func::<i32, i32, _>(&mut store, "load1")?;
-    let store1 = instance.get_typed_func::<(i32, i32), (), _>(&mut store, "store1")?;
+    let size1 = instance.get_typed_func::<(), i32>(&mut store, "size1")?;
+    let load1 = instance.get_typed_func::<i32, i32>(&mut store, "load1")?;
+    let store1 = instance.get_typed_func::<(i32, i32), ()>(&mut store, "store1")?;
 
     println!("Checking memory...");
     assert_eq!(memory0.size(&store), 2);
diff --git a/examples/serialize.rs b/examples/serialize.rs
index 22a281698..0d38fd5f5 100644
--- a/examples/serialize.rs
+++ b/examples/serialize.rs
@@ -53,7 +53,7 @@ fn deserialize(buffer: &[u8]) -> Result<()> {
 
     // Next we poke around a bit to extract the `run` function from the module.
     println!("Extracting export...");
-    let run = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let run = instance.get_typed_func::<(), ()>(&mut store, "run")?;
 
     // And last but not least we can call it!
     println!("Calling export...");
diff --git a/examples/threads.rs b/examples/threads.rs
index 9586022a5..3c3564833 100644
--- a/examples/threads.rs
+++ b/examples/threads.rs
@@ -52,7 +52,7 @@ fn run(engine: &Engine, module: &Module, linker: &Linker<()>) -> Result<()> {
     println!("Instantiating module...");
     let mut store = Store::new(&engine, ());
     let instance = linker.instantiate(&mut store, module)?;
-    let run = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let run = instance.get_typed_func::<(), ()>(&mut store, "run")?;
 
     println!("Executing...");
     for _ in 0..N_REPS {
diff --git a/examples/tokio/main.rs b/examples/tokio/main.rs
index f26e7beae..e90f6de97 100644
--- a/examples/tokio/main.rs
+++ b/examples/tokio/main.rs
@@ -106,7 +106,7 @@ async fn run_wasm(inputs: Inputs) -> Result<(), Error> {
         .instantiate_async(&mut store, &inputs.env.module)
         .await?;
     instance
-        .get_typed_func::<(), (), _>(&mut store, "_start")?
+        .get_typed_func::<(), ()>(&mut store, "_start")?
         .call_async(&mut store, ())
         .await?;
 
diff --git a/examples/wasi/main.rs b/examples/wasi/main.rs
index 8ffd98d78..30d8e1f2a 100644
--- a/examples/wasi/main.rs
+++ b/examples/wasi/main.rs
@@ -30,7 +30,7 @@ fn main() -> Result<()> {
     linker.module(&mut store, "", &module)?;
     linker
         .get_default(&mut store, "")?
-        .typed::<(), (), _>(&store)?
+        .typed::<(), ()>(&store)?
         .call(&mut store, ())?;
 
     Ok(())
diff --git a/src/commands/compile.rs b/src/commands/compile.rs
index 0988e5749..e7562447a 100644
--- a/src/commands/compile.rs
+++ b/src/commands/compile.rs
@@ -133,7 +133,7 @@ mod test {
         let module = unsafe { Module::deserialize(&engine, contents)? };
         let mut store = Store::new(&engine, ());
         let instance = Instance::new(&mut store, &module, &[])?;
-        let f = instance.get_typed_func::<i32, i32, _>(&mut store, "f")?;
+        let f = instance.get_typed_func::<i32, i32>(&mut store, "f")?;
         assert_eq!(f.call(&mut store, 1234).unwrap(), 1234);
 
         Ok(())


Test patch:
diff --git a/cranelift/codegen/src/isa/x64/inst/emit_tests.rs b/cranelift/codegen/src/isa/x64/inst/emit_tests.rs
index a56be39f4..4249846a0 100644
--- a/cranelift/codegen/src/isa/x64/inst/emit_tests.rs
+++ b/cranelift/codegen/src/isa/x64/inst/emit_tests.rs
@@ -4966,6 +4966,7 @@ fn test_x64_emit() {
         Inst::CoffTlsGetAddr {
             symbol: ExternalName::User(UserExternalNameRef::new(0)),
             dst: WritableGpr::from_writable_reg(w_rax).unwrap(),
+            tmp: WritableGpr::from_writable_reg(w_rcx).unwrap(),
         },
         "8B050000000065488B0C2558000000488B04C1488D8000000000",
         "%rax = coff_tls_get_addr User(userextname0)",
diff --git a/cranelift/filetests/filetests/isa/x64/struct-arg.clif b/cranelift/filetests/filetests/isa/x64/struct-arg.clif
index c3055b76b..cc65b6e68 100644
--- a/cranelift/filetests/filetests/isa/x64/struct-arg.clif
+++ b/cranelift/filetests/filetests/isa/x64/struct-arg.clif
@@ -51,8 +51,8 @@ block0(v0: i64):
 ;   virtual_sp_offset_adjust 64
 ;   lea     0(%rsp), %rdi
 ;   movl    $64, %edx
-;   load_ext_name %Memcpy+0, %rax
-;   call    *%rax
+;   load_ext_name %Memcpy+0, %r11
+;   call    *%r11
 ;   call    User(userextname0)
 ;   addq    %rsp, $64, %rsp
 ;   virtual_sp_offset_adjust -64
@@ -130,14 +130,13 @@ block0(v0: i64, v1: i64, v2: i64):
 ;   virtual_sp_offset_adjust 192
 ;   lea     0(%rsp), %rdi
 ;   movl    $128, %edx
-;   load_ext_name %Memcpy+0, %rcx
-;   call    *%rcx
+;   load_ext_name %Memcpy+0, %rax
+;   call    *%rax
 ;   lea     128(%rsp), %rdi
-;   movq    %r14, %rsi
 ;   movl    $64, %edx
-;   load_ext_name %Memcpy+0, %rcx
+;   load_ext_name %Memcpy+0, %r11
 ;   movq    %r14, %rsi
-;   call    *%rcx
+;   call    *%r11
 ;   movq    %r12, %rdi
 ;   call    User(userextname0)
 ;   addq    %rsp, $192, %rsp
diff --git a/crates/test-programs/tests/wasm_tests/runtime/cap_std_sync.rs b/crates/test-programs/tests/wasm_tests/runtime/cap_std_sync.rs
index 80569ee7a..939b0bdb9 100644
--- a/crates/test-programs/tests/wasm_tests/runtime/cap_std_sync.rs
+++ b/crates/test-programs/tests/wasm_tests/runtime/cap_std_sync.rs
@@ -59,7 +59,7 @@ fn run(
 
         let mut store = Store::new(&engine, builder.build());
         let instance = linker.instantiate(&mut store, &module)?;
-        let start = instance.get_typed_func::<(), (), _>(&mut store, "_start")?;
+        let start = instance.get_typed_func::<(), ()>(&mut store, "_start")?;
         start.call(&mut store, ()).map_err(anyhow::Error::from)
     };
 
diff --git a/crates/test-programs/tests/wasm_tests/runtime/tokio.rs b/crates/test-programs/tests/wasm_tests/runtime/tokio.rs
index 3eb23e8a3..565dc088d 100644
--- a/crates/test-programs/tests/wasm_tests/runtime/tokio.rs
+++ b/crates/test-programs/tests/wasm_tests/runtime/tokio.rs
@@ -67,7 +67,7 @@ fn run(
             let mut store = Store::new(&engine, builder.build());
 
             let instance = linker.instantiate_async(&mut store, &module).await?;
-            let start = instance.get_typed_func::<(), (), _>(&mut store, "_start")?;
+            let start = instance.get_typed_func::<(), ()>(&mut store, "_start")?;
             start
                 .call_async(&mut store, ())
                 .await
diff --git a/crates/wiggle/tests/errors.rs b/crates/wiggle/tests/errors.rs
index 737b7ea5a..594937843 100644
--- a/crates/wiggle/tests/errors.rs
+++ b/crates/wiggle/tests/errors.rs
@@ -24,34 +24,32 @@ mod convert_just_errno {
      (param $strike u32)
      (result $err (expected (error $errno)))))
     ",
-        errors: { errno => RichError },
+        errors: { errno => trappable ErrnoT },
     });
 
     impl_errno!(types::Errno);
 
-    /// When the `errors` mapping in witx is non-empty, we need to impl the
-    /// types::UserErrorConversion trait that wiggle generates from that mapping.
-    impl<'a> types::UserErrorConversion for WasiCtx<'a> {
-        fn errno_from_rich_error(&mut self, e: RichError) -> Result<types::Errno> {
-            // WasiCtx can collect a Vec<String> log so we can test this. We're
-            // logging the Display impl that `thiserror::Error` provides us.
-            self.log.borrow_mut().push(e.to_string());
-            // Then do the trivial mapping down to the flat enum.
-            match e {
-                RichError::InvalidArg { .. } => Ok(types::Errno::InvalidArg),
-                RichError::PicketLine { .. } => Ok(types::Errno::PicketLine),
+    impl From<RichError> for types::ErrnoT {
+        fn from(rich: RichError) -> types::ErrnoT {
+            match rich {
+                RichError::InvalidArg(s) => {
+                    types::ErrnoT::from(types::Errno::InvalidArg).context(s)
+                }
+                RichError::PicketLine(s) => {
+                    types::ErrnoT::from(types::Errno::PicketLine).context(s)
+                }
             }
         }
     }
 
     impl<'a> one_error_conversion::OneErrorConversion for WasiCtx<'a> {
-        fn foo(&mut self, strike: u32) -> Result<(), RichError> {
+        fn foo(&mut self, strike: u32) -> Result<(), types::ErrnoT> {
             // We use the argument to this function to exercise all of the
             // possible error cases we could hit here
             match strike {
                 0 => Ok(()),
-                1 => Err(RichError::PicketLine(format!("I'm not a scab"))),
-                _ => Err(RichError::InvalidArg(format!("out-of-bounds: {}", strike))),
+                1 => Err(RichError::PicketLine(format!("I'm not a scab")))?,
+                _ => Err(RichError::InvalidArg(format!("out-of-bounds: {}", strike)))?,
             }
         }
     }
@@ -78,11 +76,6 @@ mod convert_just_errno {
             types::Errno::PicketLine as i32,
             "Expected return value for strike=1"
         );
-        assert_eq!(
-            ctx.log.borrow_mut().pop().expect("one log entry"),
-            "Won't cross picket line: I'm not a scab",
-            "Expected log entry for strike=1",
-        );
 
         // Second error case:
         let r2 = one_error_conversion::foo(&mut ctx, &host_memory, 2).unwrap();
@@ -91,11 +84,6 @@ mod convert_just_errno {
             types::Errno::InvalidArg as i32,
             "Expected return value for strike=2"
         );
-        assert_eq!(
-            ctx.log.borrow_mut().pop().expect("one log entry"),
-            "Invalid argument: out-of-bounds: 2",
-            "Expected log entry for strike=2",
-        );
     }
 }
 
diff --git a/tests/all/async_functions.rs b/tests/all/async_functions.rs
index d2b0d03cb..7d8a389b3 100644
--- a/tests/all/async_functions.rs
+++ b/tests/all/async_functions.rs
@@ -14,7 +14,7 @@ async fn run_smoke_test(store: &mut Store<()>, func: Func) {
 }
 
 async fn run_smoke_typed_test(store: &mut Store<()>, func: Func) {
-    let func = func.typed::<(), (), _>(&store).unwrap();
+    let func = func.typed::<(), ()>(&store).unwrap();
     func.call_async(&mut *store, ()).await.unwrap();
     func.call_async(&mut *store, ()).await.unwrap();
 }
@@ -545,8 +545,8 @@ async fn recursive_async() -> Result<()> {
         )",
     )?;
     let i = Instance::new_async(&mut store, &m, &[]).await?;
-    let overflow = i.get_typed_func::<(), (), _>(&mut store, "overflow")?;
-    let normal = i.get_typed_func::<(), (), _>(&mut store, "normal")?;
+    let overflow = i.get_typed_func::<(), ()>(&mut store, "overflow")?;
+    let normal = i.get_typed_func::<(), ()>(&mut store, "normal")?;
     let f2 = Func::wrap0_async(&mut store, move |mut caller| {
         Box::new(async move {
             // recursive async calls shouldn't immediately stack overflow...
@@ -600,7 +600,7 @@ async fn linker_module_command() -> Result<()> {
 
     linker.module_async(&mut store, "", &module1).await?;
     let instance = linker.instantiate_async(&mut store, &module2).await?;
-    let f = instance.get_typed_func::<(), i32, _>(&mut store, "get")?;
+    let f = instance.get_typed_func::<(), i32>(&mut store, "get")?;
     assert_eq!(f.call_async(&mut store, ()).await?, 0);
     assert_eq!(f.call_async(&mut store, ()).await?, 0);
 
@@ -638,7 +638,7 @@ async fn linker_module_reactor() -> Result<()> {
 
     linker.module_async(&mut store, "", &module1).await?;
     let instance = linker.instantiate_async(&mut store, &module2).await?;
-    let f = instance.get_typed_func::<(), i32, _>(&mut store, "get")?;
+    let f = instance.get_typed_func::<(), i32>(&mut store, "get")?;
     assert_eq!(f.call_async(&mut store, ()).await?, 0);
     assert_eq!(f.call_async(&mut store, ()).await?, 1);
 
diff --git a/tests/all/call_hook.rs b/tests/all/call_hook.rs
index f28771a2d..7184f06db 100644
--- a/tests/all/call_hook.rs
+++ b/tests/all/call_hook.rs
@@ -76,7 +76,7 @@ fn call_wrapped_func() -> Result<(), Error> {
         assert_eq!(store.data().calls_into_wasm, n);
         assert_eq!(store.data().returns_from_wasm, n);
 
-        f.typed::<(i32, i64, f32, f64), (), _>(&store)?
+        f.typed::<(i32, i64, f32, f64), ()>(&store)?
             .call(&mut store, (1, 2, 3.0, 4.0))?;
         n += 1;
 
@@ -150,7 +150,7 @@ async fn call_wrapped_async_func() -> Result<(), Error> {
     assert_eq!(store.data().calls_into_wasm, 1);
     assert_eq!(store.data().returns_from_wasm, 1);
 
-    f.typed::<(i32, i64, f32, f64), (), _>(&store)?
+    f.typed::<(i32, i64, f32, f64), ()>(&store)?
         .call_async(&mut store, (1, 2, 3.0, 4.0))
         .await?;
 
@@ -218,7 +218,7 @@ fn call_linked_func() -> Result<(), Error> {
     assert_eq!(store.data().calls_into_wasm, 1);
     assert_eq!(store.data().returns_from_wasm, 1);
 
-    export.typed::<(), (), _>(&store)?.call(&mut store, ())?;
+    export.typed::<(), ()>(&store)?.call(&mut store, ())?;
 
     assert_eq!(store.data().calls_into_host, 2);
     assert_eq!(store.data().returns_from_host, 2);
@@ -290,7 +290,7 @@ async fn call_linked_func_async() -> Result<(), Error> {
     assert_eq!(store.data().returns_from_wasm, 1);
 
     export
-        .typed::<(), (), _>(&store)?
+        .typed::<(), ()>(&store)?
         .call_async(&mut store, ())
         .await?;
 
@@ -362,7 +362,7 @@ fn recursion() -> Result<(), Error> {
                 .expect("caller exports \"export\"")
                 .into_func()
                 .expect("export is a func")
-                .typed::<i32, (), _>(&caller)
+                .typed::<i32, ()>(&caller)
                 .expect("export typing")
                 .call(&mut caller, n - 1)
                 .unwrap()
@@ -398,7 +398,7 @@ fn recursion() -> Result<(), Error> {
     assert_eq!(store.data().returns_from_wasm, n + 1);
 
     export
-        .typed::<i32, (), _>(&store)?
+        .typed::<i32, ()>(&store)?
         .call(&mut store, n as i32)?;
 
     assert_eq!(store.data().calls_into_host, 2 * (n + 1));
@@ -445,7 +445,7 @@ fn trapping() -> Result<(), Error> {
                     .expect("caller exports \"export\"")
                     .into_func()
                     .expect("export is a func")
-                    .typed::<(i32, i32), (), _>(&caller)
+                    .typed::<(i32, i32), ()>(&caller)
                     .expect("export typing")
                     .call(&mut caller, (action, 0))?;
             }
@@ -676,7 +676,7 @@ async fn timeout_async_hook() -> Result<(), Error> {
 
     let inst = linker.instantiate_async(&mut store, &module).await?;
     let export = inst
-        .get_typed_func::<(), (), _>(&mut store, "export")
+        .get_typed_func::<(), ()>(&mut store, "export")
         .expect("export is func");
 
     store.set_epoch_deadline(1);
@@ -743,7 +743,7 @@ async fn drop_suspended_async_hook() -> Result<(), Error> {
     let inst = linker.instantiate_async(&mut store, &module).await?;
     assert_eq!(*store.data(), 0);
     let export = inst
-        .get_typed_func::<(), (), _>(&mut store, "")
+        .get_typed_func::<(), ()>(&mut store, "")
         .expect("export is func");
 
     // First test that if we drop in the middle of an async hook that everything
diff --git a/tests/all/custom_signal_handler.rs b/tests/all/custom_signal_handler.rs
index 22cf65416..498014093 100644
--- a/tests/all/custom_signal_handler.rs
+++ b/tests/all/custom_signal_handler.rs
@@ -47,7 +47,7 @@ mod tests {
 
     fn invoke_export(store: &mut Store<()>, instance: Instance, func_name: &str) -> Result<i32> {
         let ret = instance
-            .get_typed_func::<(), i32, _>(&mut *store, func_name)?
+            .get_typed_func::<(), i32>(&mut *store, func_name)?
             .call(store, ())?;
         Ok(ret)
     }
@@ -175,7 +175,7 @@ mod tests {
 
         // these invoke wasmtime_call_trampoline from callable.rs
         {
-            let read_func = instance.get_typed_func::<(), i32, _>(&mut store, "read")?;
+            let read_func = instance.get_typed_func::<(), i32>(&mut store, "read")?;
             println!("calling read...");
             let result = read_func
                 .call(&mut store, ())
@@ -185,7 +185,7 @@ mod tests {
 
         {
             let read_out_of_bounds_func =
-                instance.get_typed_func::<(), i32, _>(&mut store, "read_out_of_bounds")?;
+                instance.get_typed_func::<(), i32>(&mut store, "read_out_of_bounds")?;
             println!("calling read_out_of_bounds...");
             let trap = read_out_of_bounds_func
                 .call(&mut store, ())
diff --git a/tests/all/externals.rs b/tests/all/externals.rs
index 04742fa50..baf15c9d1 100644
--- a/tests/all/externals.rs
+++ b/tests/all/externals.rs
@@ -133,8 +133,8 @@ fn cross_store() -> anyhow::Result<()> {
         .call(&mut store2, &[Some(s2_f.clone()).into()], &mut [])
         .is_ok());
 
-    let s1_f_t = s1_f.typed::<Option<Func>, (), _>(&store1)?;
-    let s2_f_t = s2_f.typed::<Option<Func>, (), _>(&store2)?;
+    let s1_f_t = s1_f.typed::<Option<Func>, ()>(&store1)?;
+    let s2_f_t = s2_f.typed::<Option<Func>, ()>(&store2)?;
 
     assert!(s1_f_t.call(&mut store1, None).is_ok());
     assert!(s2_f_t.call(&mut store2, None).is_ok());
diff --git a/tests/all/fuel.rs b/tests/all/fuel.rs
index a42c3084d..eec8f9dff 100644
--- a/tests/all/fuel.rs
+++ b/tests/all/fuel.rs
@@ -170,9 +170,7 @@ fn host_function_consumes_all() {
     });
 
     let instance = Instance::new(&mut store, &module, &[func.into()]).unwrap();
-    let export = instance
-        .get_typed_func::<(), (), _>(&mut store, "")
-        .unwrap();
+    let export = instance.get_typed_func::<(), ()>(&mut store, "").unwrap();
     let trap = export.call(&mut store, ()).unwrap_err();
     assert!(
         format!("{trap:?}").contains("all fuel consumed"),
diff --git a/tests/all/func.rs b/tests/all/func.rs
index d56d44e50..30bc0c95b 100644
--- a/tests/all/func.rs
+++ b/tests/all/func.rs
@@ -246,40 +246,40 @@ fn trap_import() -> Result<()> {
 fn get_from_wrapper() {
     let mut store = Store::<()>::default();
     let f = Func::wrap(&mut store, || {});
-    assert!(f.typed::<(), (), _>(&store).is_ok());
-    assert!(f.typed::<(), i32, _>(&store).is_err());
-    assert!(f.typed::<(), (), _>(&store).is_ok());
-    assert!(f.typed::<i32, (), _>(&store).is_err());
-    assert!(f.typed::<i32, i32, _>(&store).is_err());
-    assert!(f.typed::<(i32, i32), (), _>(&store).is_err());
-    assert!(f.typed::<(i32, i32), i32, _>(&store).is_err());
+    assert!(f.typed::<(), ()>(&store).is_ok());
+    assert!(f.typed::<(), i32>(&store).is_err());
+    assert!(f.typed::<(), ()>(&store).is_ok());
+    assert!(f.typed::<i32, ()>(&store).is_err());
+    assert!(f.typed::<i32, i32>(&store).is_err());
+    assert!(f.typed::<(i32, i32), ()>(&store).is_err());
+    assert!(f.typed::<(i32, i32), i32>(&store).is_err());
 
     let f = Func::wrap(&mut store, || -> i32 { loop {} });
-    assert!(f.typed::<(), i32, _>(&store).is_ok());
+    assert!(f.typed::<(), i32>(&store).is_ok());
     let f = Func::wrap(&mut store, || -> f32 { loop {} });
-    assert!(f.typed::<(), f32, _>(&store).is_ok());
+    assert!(f.typed::<(), f32>(&store).is_ok());
     let f = Func::wrap(&mut store, || -> f64 { loop {} });
-    assert!(f.typed::<(), f64, _>(&store).is_ok());
+    assert!(f.typed::<(), f64>(&store).is_ok());
     let f = Func::wrap(&mut store, || -> Option<ExternRef> { loop {} });
-    assert!(f.typed::<(), Option<ExternRef>, _>(&store).is_ok());
+    assert!(f.typed::<(), Option<ExternRef>>(&store).is_ok());
     let f = Func::wrap(&mut store, || -> Option<Func> { loop {} });
-    assert!(f.typed::<(), Option<Func>, _>(&store).is_ok());
+    assert!(f.typed::<(), Option<Func>>(&store).is_ok());
 
     let f = Func::wrap(&mut store, |_: i32| {});
-    assert!(f.typed::<i32, (), _>(&store).is_ok());
-    assert!(f.typed::<i64, (), _>(&store).is_err());
-    assert!(f.typed::<f32, (), _>(&store).is_err());
-    assert!(f.typed::<f64, (), _>(&store).is_err());
+    assert!(f.typed::<i32, ()>(&store).is_ok());
+    assert!(f.typed::<i64, ()>(&store).is_err());
+    assert!(f.typed::<f32, ()>(&store).is_err());
+    assert!(f.typed::<f64, ()>(&store).is_err());
     let f = Func::wrap(&mut store, |_: i64| {});
-    assert!(f.typed::<i64, (), _>(&store).is_ok());
+    assert!(f.typed::<i64, ()>(&store).is_ok());
     let f = Func::wrap(&mut store, |_: f32| {});
-    assert!(f.typed::<f32, (), _>(&store).is_ok());
+    assert!(f.typed::<f32, ()>(&store).is_ok());
     let f = Func::wrap(&mut store, |_: f64| {});
-    assert!(f.typed::<f64, (), _>(&store).is_ok());
+    assert!(f.typed::<f64, ()>(&store).is_ok());
     let f = Func::wrap(&mut store, |_: Option<ExternRef>| {});
-    assert!(f.typed::<Option<ExternRef>, (), _>(&store).is_ok());
+    assert!(f.typed::<Option<ExternRef>, ()>(&store).is_ok());
     let f = Func::wrap(&mut store, |_: Option<Func>| {});
-    assert!(f.typed::<Option<Func>, (), _>(&store).is_ok());
+    assert!(f.typed::<Option<Func>, ()>(&store).is_ok());
 }
 
 #[test]
@@ -287,16 +287,16 @@ fn get_from_signature() {
     let mut store = Store::<()>::default();
     let ty = FuncType::new(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());
+    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 f = Func::new(&mut store, ty, |_, _, _| panic!());
-    assert!(f.typed::<(), (), _>(&store).is_err());
-    assert!(f.typed::<(), i32, _>(&store).is_err());
-    assert!(f.typed::<i32, (), _>(&store).is_err());
-    assert!(f.typed::<i32, f64, _>(&store).is_ok());
+    assert!(f.typed::<(), ()>(&store).is_err());
+    assert!(f.typed::<(), i32>(&store).is_err());
+    assert!(f.typed::<i32, ()>(&store).is_err());
+    assert!(f.typed::<i32, f64>(&store).is_ok());
 }
 
 #[test]
@@ -316,17 +316,17 @@ fn get_from_module() -> anyhow::Result<()> {
     )?;
     let instance = Instance::new(&mut store, &module, &[])?;
     let f0 = instance.get_func(&mut store, "f0").unwrap();
-    assert!(f0.typed::<(), (), _>(&store).is_ok());
-    assert!(f0.typed::<(), i32, _>(&store).is_err());
+    assert!(f0.typed::<(), ()>(&store).is_ok());
+    assert!(f0.typed::<(), i32>(&store).is_err());
     let f1 = instance.get_func(&mut store, "f1").unwrap();
-    assert!(f1.typed::<(), (), _>(&store).is_err());
-    assert!(f1.typed::<i32, (), _>(&store).is_ok());
-    assert!(f1.typed::<i32, f32, _>(&store).is_err());
+    assert!(f1.typed::<(), ()>(&store).is_err());
+    assert!(f1.typed::<i32, ()>(&store).is_ok());
+    assert!(f1.typed::<i32, f32>(&store).is_err());
     let f2 = instance.get_func(&mut store, "f2").unwrap();
-    assert!(f2.typed::<(), (), _>(&store).is_err());
-    assert!(f2.typed::<(), i32, _>(&store).is_ok());
-    assert!(f2.typed::<i32, (), _>(&store).is_err());
-    assert!(f2.typed::<i32, f32, _>(&store).is_err());
+    assert!(f2.typed::<(), ()>(&store).is_err());
+    assert!(f2.typed::<(), i32>(&store).is_ok());
+    assert!(f2.typed::<i32, ()>(&store).is_err());
+    assert!(f2.typed::<i32, f32>(&store).is_err());
     Ok(())
 }
 
@@ -344,29 +344,29 @@ fn call_wrapped_func() -> Result<()> {
         &[Val::I32(1), Val::I64(2), 3.0f32.into(), 4.0f64.into()],
         &mut [],
     )?;
-    f.typed::<(i32, i64, f32, f64), (), _>(&store)?
+    f.typed::<(i32, i64, f32, f64), ()>(&store)?
         .call(&mut store, (1, 2, 3.0, 4.0))?;
 
     let mut results = [Val::I32(0)];
     let f = Func::wrap(&mut store, || 1i32);
     f.call(&mut store, &[], &mut results)?;
     assert_eq!(results[0].unwrap_i32(), 1);
-    assert_eq!(f.typed::<(), i32, _>(&store)?.call(&mut store, ())?, 1);
+    assert_eq!(f.typed::<(), i32>(&store)?.call(&mut store, ())?, 1);
 
     let f = Func::wrap(&mut store, || 2i64);
     f.call(&mut store, &[], &mut results)?;
     assert_eq!(results[0].unwrap_i64(), 2);
-    assert_eq!(f.typed::<(), i64, _>(&store)?.call(&mut store, ())?, 2);
+    assert_eq!(f.typed::<(), i64>(&store)?.call(&mut store, ())?, 2);
 
     let f = Func::wrap(&mut store, || 3.0f32);
     f.call(&mut store, &[], &mut results)?;
     assert_eq!(results[0].unwrap_f32(), 3.0);
-    assert_eq!(f.typed::<(), f32, _>(&store)?.call(&mut store, ())?, 3.0);
+    assert_eq!(f.typed::<(), f32>(&store)?.call(&mut store, ())?, 3.0);
 
     let f = Func::wrap(&mut store, || 4.0f64);
     f.call(&mut store, &[], &mut results)?;
     assert_eq!(results[0].unwrap_f64(), 4.0);
-    assert_eq!(f.typed::<(), f64, _>(&store)?.call(&mut store, ())?, 4.0);
+    assert_eq!(f.typed::<(), f64>(&store)?.call(&mut store, ())?, 4.0);
     Ok(())
 }
 
@@ -502,7 +502,7 @@ fn pass_cross_store_arg() -> anyhow::Result<()> {
 
     // And using `.get` followed by a function call also fails with cross-Store
     // arguments.
-    let f = store1_func.typed::<Option<Func>, (), _>(&store1)?;
+    let f = store1_func.typed::<Option<Func>, ()>(&store1)?;
     let result = f.call(&mut store1, Some(store2_func));
     assert!(result.is_err());
     assert!(result.unwrap_err().to_string().contains("cross-`Store`"));
@@ -572,18 +572,17 @@ fn typed_multiple_results() -> anyhow::Result<()> {
     )?;
     let instance = Instance::new(&mut store, &module, &[])?;
     let f0 = instance.get_func(&mut store, "f0").unwrap();
-    assert!(f0.typed::<(), (), _>(&store).is_err());
-    assert!(f0.typed::<(), (i32, f32), _>(&store).is_err());
-    assert!(f0.typed::<(), i32, _>(&store).is_err());
+    assert!(f0.typed::<(), ()>(&store).is_err());
+    assert!(f0.typed::<(), (i32, f32)>(&store).is_err());
+    assert!(f0.typed::<(), i32>(&store).is_err());
     assert_eq!(
-        f0.typed::<(), (i32, i64), _>(&store)?
-            .call(&mut store, ())?,
+        f0.typed::<(), (i32, i64)>(&store)?.call(&mut store, ())?,
         (0, 1)
     );
 
     let f1 = instance.get_func(&mut store, "f1").unwrap();
     assert_eq!(
-        f1.typed::<(i32, i32, i32), (f32, f64), _>(&store)?
+        f1.typed::<(i32, i32, i32), (f32, f64)>(&store)?
             .call(&mut store, (1, 2, 3))?,
         (2., 3.)
     );
@@ -610,7 +609,7 @@ fn trap_doesnt_leak() -> anyhow::Result<()> {
         drop(&canary1);
         bail!("")
     });
-    assert!(f1.typed::<(), (), _>(&store)?.call(&mut store, ()).is_err());
+    assert!(f1.typed::<(), ()>(&store)?.call(&mut store, ()).is_err());
     assert!(f1.call(&mut store, &[], &mut []).is_err());
 
     // test that `Func::new` is correct
@@ -620,7 +619,7 @@ fn trap_doesnt_leak() -> anyhow::Result<()> {
         drop(&canary2);
         bail!("")
     });
-    assert!(f2.typed::<(), (), _>(&store)?.call(&mut store, ()).is_err());
+    assert!(f2.typed::<(), ()>(&store)?.call(&mut store, ()).is_err());
     assert!(f2.call(&mut store, &[], &mut []).is_err());
 
     // drop everything and ensure dtors are run
@@ -646,7 +645,7 @@ fn wrap_multiple_results() -> anyhow::Result<()> {
     {
         let f = Func::wrap(&mut *store, move || t);
         let mut results = vec![Val::I32(0); f.ty(&store).results().len()];
-        assert_eq!(f.typed::<(), T, _>(&store)?.call(&mut *store, ())?, t);
+        assert_eq!(f.typed::<(), T>(&store)?.call(&mut *store, ())?, t);
         f.call(&mut *store, &[], &mut results)?;
         assert!(t.eq_values(&results));
 
@@ -654,7 +653,7 @@ fn wrap_multiple_results() -> anyhow::Result<()> {
         let instance = Instance::new(&mut *store, &module, &[f.into()])?;
         let f = instance.get_func(&mut *store, "foo").unwrap();
 
-        assert_eq!(f.typed::<(), T, _>(&store)?.call(&mut *store, ())?, t);
+        assert_eq!(f.typed::<(), T>(&store)?.call(&mut *store, ())?, t);
         f.call(&mut *store, &[], &mut results)?;
         assert!(t.eq_values(&results));
         Ok(())
@@ -814,7 +813,7 @@ fn trampoline_for_declared_elem() -> anyhow::Result<()> {
     let mut store = Store::new(&engine, ());
     let instance = Instance::new(&mut store, &module, &[])?;
 
-    let g = instance.get_typed_func::<(), Option<Func>, _>(&mut store, "g")?;
+    let g = instance.get_typed_func::<(), Option<Func>>(&mut store, "g")?;
 
     let func = g.call(&mut store, ())?;
     func.unwrap().call(&mut store, &[], &mut [])?;
@@ -871,8 +870,7 @@ fn wasm_ty_roundtrip() -> Result<(), anyhow::Error> {
          "#,
     )?;
     let instance = Instance::new(&mut store, &module, &[debug.into()])?;
-    let foo =
-        instance.get_typed_func::<(i32, u32, f32, i64, u64, f64), (), _>(&mut store, "foo")?;
+    let foo = instance.get_typed_func::<(i32, u32, f32, i64, u64, f64), ()>(&mut store, "foo")?;
     foo.call(&mut store, (-1, 1, 2.0, -3, 3, 4.0))?;
     Ok(())
 }
@@ -892,14 +890,14 @@ fn typed_funcs_count_params_correctly_in_error_messages() -> anyhow::Result<()>
     let instance = Instance::new(&mut store, &module, &[])?;
 
     // Too few parameters.
-    match instance.get_typed_func::<(), (), _>(&mut store, "f") {
+    match instance.get_typed_func::<(), ()>(&mut store, "f") {
         Ok(_) => panic!("should be wrong signature"),
         Err(e) => {
             let msg = format!("{:?}", e);
             assert!(dbg!(msg).contains("expected 0 types, found 2"))
         }
     }
-    match instance.get_typed_func::<(i32,), (), _>(&mut store, "f") {
+    match instance.get_typed_func::<(i32,), ()>(&mut store, "f") {
         Ok(_) => panic!("should be wrong signature"),
         Err(e) => {
             let msg = format!("{:?}", e);
@@ -908,7 +906,7 @@ fn typed_funcs_count_params_correctly_in_error_messages() -> anyhow::Result<()>
     }
 
     // Too many parameters.
-    match instance.get_typed_func::<(i32, i32, i32), (), _>(&mut store, "f") {
+    match instance.get_typed_func::<(i32, i32, i32), ()>(&mut store, "f") {
         Ok(_) => panic!("should be wrong signature"),
         Err(e) => {
             let msg = format!("{:?}", e);
diff --git a/tests/all/gc.rs b/tests/all/gc.rs
index 4730f7941..e9b1e0abd 100644
--- a/tests/all/gc.rs
+++ b/tests/all/gc.rs
@@ -285,7 +285,7 @@ fn global_drops_externref() -> anyhow::Result<()> {
             "#,
         )?;
         let instance = Instance::new(&mut store, &module, &[])?;
-        let run = instance.get_typed_func::<Option<ExternRef>, (), _>(&mut store, "run")?;
+        let run = instance.get_typed_func::<Option<ExternRef>, ()>(&mut store, "run")?;
         let flag = Arc::new(AtomicBool::new(false));
         let externref = ExternRef::new(SetFlagOnDrop(flag.clone()));
         run.call(&mut store, Some(externref))?;
@@ -335,7 +335,7 @@ fn table_drops_externref() -> anyhow::Result<()> {
         "#,
         )?;
         let instance = Instance::new(&mut store, &module, &[])?;
-        let run = instance.get_typed_func::<Option<ExternRef>, (), _>(&mut store, "run")?;
+        let run = instance.get_typed_func::<Option<ExternRef>, ()>(&mut store, "run")?;
         let flag = Arc::new(AtomicBool::new(false));
         let externref = ExternRef::new(SetFlagOnDrop(flag.clone()));
         run.call(&mut store, Some(externref))?;
@@ -387,7 +387,7 @@ fn gee_i_sure_hope_refcounting_is_atomic() -> anyhow::Result<()> {
     )?;
 
     let instance = Instance::new(&mut store, &module, &[])?;
-    let run = instance.get_typed_func::<Option<ExternRef>, (), _>(&mut store, "run")?;
+    let run = instance.get_typed_func::<Option<ExternRef>, ()>(&mut store, "run")?;
 
     let flag = Arc::new(AtomicBool::new(false));
     let externref = ExternRef::new(SetFlagOnDrop(flag.clone()));
@@ -482,7 +482,7 @@ fn no_gc_middle_of_args() -> anyhow::Result<()> {
     )?;
 
     let instance = linker.instantiate(&mut store, &module)?;
-    let func = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let func = instance.get_typed_func::<(), ()>(&mut store, "run")?;
     func.call(&mut store, ())?;
 
     Ok(())
diff --git a/tests/all/host_funcs.rs b/tests/all/host_funcs.rs
index 9b96b69ee..795305efb 100644
--- a/tests/all/host_funcs.rs
+++ b/tests/all/host_funcs.rs
@@ -430,7 +430,7 @@ fn call_wasm_many_args() -> Result<()> {
     )?;
 
     let typed_run = instance
-        .get_typed_func::<(i32, i32, i32, i32, i32, i32, i32, i32, i32, i32), (), _>(
+        .get_typed_func::<(i32, i32, i32, i32, i32, i32, i32, i32, i32, i32), ()>(
             &mut store, "run",
         )?;
     typed_run.call(&mut store, (1, 2, 3, 4, 5, 6, 7, 8, 9, 10))?;
@@ -499,19 +499,19 @@ fn new_from_signature() -> Result<()> {
         .unwrap()
         .into_func()
         .unwrap();
-    assert!(f.typed::<(), (), _>(&store).is_ok());
-    assert!(f.typed::<(), i32, _>(&store).is_err());
-    assert!(f.typed::<i32, (), _>(&store).is_err());
+    assert!(f.typed::<(), ()>(&store).is_ok());
+    assert!(f.typed::<(), i32>(&store).is_err());
+    assert!(f.typed::<i32, ()>(&store).is_err());
 
     let f = linker
         .get(&mut store, "", "f2")
         .unwrap()
         .into_func()
         .unwrap();
-    assert!(f.typed::<(), (), _>(&store).is_err());
-    assert!(f.typed::<(), i32, _>(&store).is_err());
-    assert!(f.typed::<i32, (), _>(&store).is_err());
-    assert!(f.typed::<i32, f64, _>(&store).is_ok());
+    assert!(f.typed::<(), ()>(&store).is_err());
+    assert!(f.typed::<(), i32>(&store).is_err());
+    assert!(f.typed::<i32, ()>(&store).is_err());
+    assert!(f.typed::<i32, f64>(&store).is_ok());
 
     Ok(())
 }
@@ -549,7 +549,7 @@ fn call_wrapped_func() -> Result<()> {
         &[Val::I32(1), Val::I64(2), 3.0f32.into(), 4.0f64.into()],
         &mut [],
     )?;
-    f.typed::<(i32, i64, f32, f64), (), _>(&store)?
+    f.typed::<(i32, i64, f32, f64), ()>(&store)?
         .call(&mut store, (1, 2, 3.0, 4.0))?;
 
     let f = linker
@@ -559,7 +559,7 @@ fn call_wrapped_func() -> Result<()> {
         .unwrap();
     f.call(&mut store, &[], &mut results)?;
     assert_eq!(results[0].unwrap_i32(), 1);
-    assert_eq!(f.typed::<(), i32, _>(&store)?.call(&mut store, ())?, 1);
+    assert_eq!(f.typed::<(), i32>(&store)?.call(&mut store, ())?, 1);
 
     let f = linker
         .get(&mut store, "", "f3")
@@ -568,7 +568,7 @@ fn call_wrapped_func() -> Result<()> {
         .unwrap();
     f.call(&mut store, &[], &mut results)?;
     assert_eq!(results[0].unwrap_i64(), 2);
-    assert_eq!(f.typed::<(), i64, _>(&store)?.call(&mut store, ())?, 2);
+    assert_eq!(f.typed::<(), i64>(&store)?.call(&mut store, ())?, 2);
 
     let f = linker
         .get(&mut store, "", "f4")
@@ -577,7 +577,7 @@ fn call_wrapped_func() -> Result<()> {
         .unwrap();
     f.call(&mut store, &[], &mut results)?;
     assert_eq!(results[0].unwrap_f32(), 3.0);
-    assert_eq!(f.typed::<(), f32, _>(&store)?.call(&mut store, ())?, 3.0);
+    assert_eq!(f.typed::<(), f32>(&store)?.call(&mut store, ())?, 3.0);
 
     let f = linker
         .get(&mut store, "", "f5")
@@ -586,7 +586,7 @@ fn call_wrapped_func() -> Result<()> {
         .unwrap();
     f.call(&mut store, &[], &mut results)?;
     assert_eq!(results[0].unwrap_f64(), 4.0);
-    assert_eq!(f.typed::<(), f64, _>(&store)?.call(&mut store, ())?, 4.0);
+    assert_eq!(f.typed::<(), f64>(&store)?.call(&mut store, ())?, 4.0);
 
     Ok(())
 }
@@ -714,7 +714,7 @@ fn wasi_imports() -> Result<()> {
     let mut store = Store::new(&engine, WasiCtxBuilder::new().build());
     let instance = linker.instantiate(&mut store, &module)?;
 
-    let start = instance.get_typed_func::<(), (), _>(&mut store, "_start")?;
+    let start = instance.get_typed_func::<(), ()>(&mut store, "_start")?;
     let exit = start
         .call(&mut store, ())
         .unwrap_err()
diff --git a/tests/all/iloop.rs b/tests/all/iloop.rs
index 68614ddd7..fedef6f6d 100644
--- a/tests/all/iloop.rs
+++ b/tests/all/iloop.rs
@@ -29,7 +29,7 @@ fn loops_interruptable() -> anyhow::Result<()> {
     let mut store = interruptable_store();
     let module = Module::new(store.engine(), r#"(func (export "loop") (loop br 0))"#)?;
     let instance = Instance::new(&mut store, &module, &[])?;
-    let iloop = instance.get_typed_func::<(), (), _>(&mut store, "loop")?;
+    let iloop = instance.get_typed_func::<(), ()>(&mut store, "loop")?;
     store.engine().increment_epoch();
     let trap = iloop.call(&mut store, ()).unwrap_err().downcast::<Trap>()?;
     assert_eq!(trap, Trap::Interrupt);
@@ -42,7 +42,7 @@ fn functions_interruptable() -> anyhow::Result<()> {
     let module = hugely_recursive_module(store.engine())?;
     let func = Func::wrap(&mut store, || {});
     let instance = Instance::new(&mut store, &module, &[func.into()])?;
-    let iloop = instance.get_typed_func::<(), (), _>(&mut store, "loop")?;
+    let iloop = instance.get_typed_func::<(), ()>(&mut store, "loop")?;
     store.engine().increment_epoch();
     let trap = iloop.call(&mut store, ()).unwrap_err().downcast::<Trap>()?;
     assert_eq!(trap, Trap::Interrupt);
@@ -89,7 +89,7 @@ fn loop_interrupt_from_afar() -> anyhow::Result<()> {
 
     // Enter the infinitely looping function and assert that our interrupt
     // handle does indeed actually interrupt the function.
-    let iloop = instance.get_typed_func::<(), (), _>(&mut store, "loop")?;
+    let iloop = instance.get_typed_func::<(), ()>(&mut store, "loop")?;
     let trap = iloop.call(&mut store, ()).unwrap_err().downcast::<Trap>()?;
     STOP.store(true, SeqCst);
     thread.join().unwrap();
@@ -125,7 +125,7 @@ fn function_interrupt_from_afar() -> anyhow::Result<()> {
 
     // Enter the infinitely looping function and assert that our interrupt
     // handle does indeed actually interrupt the function.
-    let iloop = instance.get_typed_func::<(), (), _>(&mut store, "loop")?;
+    let iloop = instance.get_typed_func::<(), ()>(&mut store, "loop")?;
     let trap = iloop.call(&mut store, ()).unwrap_err().downcast::<Trap>()?;
     STOP.store(true, SeqCst);
     thread.join().unwrap();
diff --git a/tests/all/import_indexes.rs b/tests/all/import_indexes.rs
index 94c58639b..d6718f166 100644
--- a/tests/all/import_indexes.rs
+++ b/tests/all/import_indexes.rs
@@ -43,7 +43,7 @@ fn same_import_names_still_distinct() -> anyhow::Result<()> {
     ];
     let instance = Instance::new(&mut store, &module, &imports)?;
 
-    let func = instance.get_typed_func::<(), i32, _>(&mut store, "foo")?;
+    let func = instance.get_typed_func::<(), i32>(&mut store, "foo")?;
     let result = func.call(&mut store, ())?;
     assert_eq!(result, 3);
     Ok(())
diff --git a/tests/all/instance.rs b/tests/all/instance.rs
index e37dd95a9..95ed175df 100644
--- a/tests/all/instance.rs
+++ b/tests/all/instance.rs
@@ -63,8 +63,8 @@ fn linear_memory_limits() -> Result<()> {
 
         let mut store = Store::new(engine, ());
         let instance = Instance::new(&mut store, &module, &[])?;
-        let size = instance.get_typed_func::<(), i32, _>(&mut store, "size")?;
-        let grow = instance.get_typed_func::<(), i32, _>(&mut store, "grow")?;
+        let size = instance.get_typed_func::<(), i32>(&mut store, "size")?;
+        let grow = instance.get_typed_func::<(), i32>(&mut store, "grow")?;
 
         assert_eq!(size.call(&mut store, ())?, 65534);
         assert_eq!(grow.call(&mut store, ())?, 65534);
diff --git a/tests/all/limits.rs b/tests/all/limits.rs
index d1b013cb1..333583f65 100644
--- a/tests/all/limits.rs
+++ b/tests/all/limits.rs
@@ -78,7 +78,7 @@ fn test_limits() -> Result<()> {
     store.limiter(|s| s as &mut dyn ResourceLimiter);
     let instance = Instance::new(&mut store, &module, &[])?;
     let grow = instance.get_func(&mut store, "grow").unwrap();
-    let grow = grow.typed::<i32, i32, _>(&store).unwrap();
+    let grow = grow.typed::<i32, i32>(&store).unwrap();
 
     grow.call(&mut store, 3).unwrap();
     grow.call(&mut store, 5).unwrap();
@@ -464,7 +464,7 @@ fn test_custom_memory_limiter() -> Result<()> {
     assert!(!store.data().limit_exceeded);
 
     // Grow the host "memory" by 384 KiB
-    let f = instance.get_typed_func::<u32, u32, _>(&mut store, "f")?;
+    let f = instance.get_typed_func::<u32, u32>(&mut store, "f")?;
 
     assert_eq!(f.call(&mut store, 1 * 0x10000)?, 1);
     assert_eq!(f.call(&mut store, 3 * 0x10000)?, 1);
@@ -576,7 +576,7 @@ async fn test_custom_memory_limiter_async() -> Result<()> {
     assert!(!store.data().limit_exceeded);
 
     // Grow the host "memory" by 384 KiB
-    let f = instance.get_typed_func::<u32, u32, _>(&mut store, "f")?;
+    let f = instance.get_typed_func::<u32, u32>(&mut store, "f")?;
 
     assert_eq!(f.call_async(&mut store, 1 * 0x10000).await?, 1);
     assert_eq!(f.call_async(&mut store, 3 * 0x10000).await?, 1);
@@ -965,7 +965,7 @@ fn panic_in_memory_limiter_wasm_stack() {
     store.limiter(|s| s as &mut dyn ResourceLimiter);
     let instance = linker.instantiate(&mut store, &module).unwrap();
     let grow = instance.get_func(&mut store, "grow").unwrap();
-    let grow = grow.typed::<i32, i32, _>(&store).unwrap();
+    let grow = grow.typed::<i32, i32>(&store).unwrap();
 
     // Grow the memory, which should panic
     grow.call(&mut store, 3).unwrap();
@@ -1032,7 +1032,7 @@ async fn panic_in_async_memory_limiter_wasm_stack() {
     store.limiter_async(|s| s as &mut dyn ResourceLimiterAsync);
     let instance = linker.instantiate_async(&mut store, &module).await.unwrap();
     let grow = instance.get_func(&mut store, "grow").unwrap();
-    let grow = grow.typed::<i32, i32, _>(&store).unwrap();
+    let grow = grow.typed::<i32, i32>(&store).unwrap();
 
     // Grow the memory, which should panic
     grow.call_async(&mut store, 3).await.unwrap();
diff --git a/tests/all/linker.rs b/tests/all/linker.rs
index 9728d757f..1f65f017b 100644
--- a/tests/all/linker.rs
+++ b/tests/all/linker.rs
@@ -101,7 +101,7 @@ fn function_interposition() -> Result<()> {
         .unwrap()
         .into_func()
         .unwrap();
-    let func = func.typed::<(), i32, _>(&store)?;
+    let func = func.typed::<(), i32>(&store)?;
     assert_eq!(func.call(&mut store, ())?, 112);
     Ok(())
 }
@@ -134,7 +134,7 @@ fn function_interposition_renamed() -> Result<()> {
     }
     let instance = linker.instantiate(&mut store, &module)?;
     let func = instance.get_func(&mut store, "export").unwrap();
-    let func = func.typed::<(), i32, _>(&store)?;
+    let func = func.typed::<(), i32>(&store)?;
     assert_eq!(func.call(&mut store, ())?, 112);
     Ok(())
 }
@@ -167,7 +167,7 @@ fn module_interposition() -> Result<()> {
         .unwrap()
         .into_func()
         .unwrap();
-    let func = func.typed::<(), i32, _>(&store)?;
+    let func = func.typed::<(), i32>(&store)?;
     assert_eq!(func.call(&mut store, ())?, 112);
     Ok(())
 }
diff --git a/tests/all/memory.rs b/tests/all/memory.rs
index 2005479f0..281a2e7ab 100644
--- a/tests/all/memory.rs
+++ b/tests/all/memory.rs
@@ -346,7 +346,7 @@ fn tiny_static_heap() -> Result<()> {
     )?;
 
     let i = Instance::new(&mut store, &module, &[])?;
-    let f = i.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let f = i.get_typed_func::<(), ()>(&mut store, "run")?;
     f.call(&mut store, ())?;
     Ok(())
 }
diff --git a/tests/all/module.rs b/tests/all/module.rs
index dd5e53f59..904bc6dc4 100644
--- a/tests/all/module.rs
+++ b/tests/all/module.rs
@@ -62,7 +62,7 @@ fn aot_compiles() -> Result<()> {
     let mut store = Store::new(&engine, ());
     let instance = Instance::new(&mut store, &module, &[])?;
 
-    let f = instance.get_typed_func::<i32, i32, _>(&mut store, "f")?;
+    let f = instance.get_typed_func::<i32, i32>(&mut store, "f")?;
     assert_eq!(f.call(&mut store, 101)?, 101);
 
     Ok(())
diff --git a/tests/all/module_serialize.rs b/tests/all/module_serialize.rs
index 121afc371..614803019 100644
--- a/tests/all/module_serialize.rs
+++ b/tests/all/module_serialize.rs
@@ -51,7 +51,7 @@ fn test_module_serialize_simple() -> Result<()> {
 
     let mut store = Store::default();
     let instance = unsafe { deserialize_and_instantiate(&mut store, &buffer)? };
-    let run = instance.get_typed_func::<(), i32, _>(&mut store, "run")?;
+    let run = instance.get_typed_func::<(), i32>(&mut store, "run")?;
     let result = run.call(&mut store, ())?;
 
     assert_eq!(42, result);
@@ -98,7 +98,7 @@ fn test_deserialize_from_file() -> Result<()> {
         fs::write(&path, &buffer)?;
         let module = unsafe { Module::deserialize_file(store.engine(), &path)? };
         let instance = Instance::new(&mut store, &module, &[])?;
-        let func = instance.get_typed_func::<(), i32, _>(&mut store, "run")?;
+        let func = instance.get_typed_func::<(), i32>(&mut store, "run")?;
         assert_eq!(func.call(&mut store, ())?, 42);
         Ok(())
     }
diff --git a/tests/all/pooling_allocator.rs b/tests/all/pooling_allocator.rs
index b97ebd474..d2f38924d 100644
--- a/tests/all/pooling_allocator.rs
+++ b/tests/all/pooling_allocator.rs
@@ -67,7 +67,7 @@ fn memory_limit() -> Result<()> {
     {
         let mut store = Store::new(&engine, ());
         let instance = Instance::new(&mut store, &module, &[])?;
-        let f = instance.get_typed_func::<(), i32, _>(&mut store, "f")?;
+        let f = instance.get_typed_func::<(), i32>(&mut store, "f")?;
 
         assert_eq!(f.call(&mut store, ()).expect("function should not trap"), 0);
         assert_eq!(f.call(&mut store, ()).expect("function should not trap"), 1);
@@ -149,7 +149,7 @@ fn memory_guard_page_trap() -> Result<()> {
         let mut store = Store::new(&engine, ());
         let instance = Instance::new(&mut store, &module, &[])?;
         let m = instance.get_memory(&mut store, "m").unwrap();
-        let f = instance.get_typed_func::<i32, (), _>(&mut store, "f")?;
+        let f = instance.get_typed_func::<i32, ()>(&mut store, "f")?;
 
         let trap = f
             .call(&mut store, 0)
@@ -273,7 +273,7 @@ fn table_limit() -> Result<()> {
     {
         let mut store = Store::new(&engine, ());
         let instance = Instance::new(&mut store, &module, &[])?;
-        let f = instance.get_typed_func::<(), i32, _>(&mut store, "f")?;
+        let f = instance.get_typed_func::<(), i32>(&mut store, "f")?;
 
         for i in 0..TABLE_ELEMENTS {
             assert_eq!(
@@ -611,7 +611,7 @@ fn switch_image_and_non_image() -> Result<()> {
     let assert_zero = || -> Result<()> {
         let mut store = Store::new(&engine, ());
         let instance = Instance::new(&mut store, &module1, &[])?;
-        let func = instance.get_typed_func::<i32, i32, _>(&mut store, "load")?;
+        let func = instance.get_typed_func::<i32, i32>(&mut store, "load")?;
         assert_eq!(func.call(&mut store, 0)?, 0);
         Ok(())
     };
@@ -719,10 +719,10 @@ fn dynamic_memory_pooling_allocator() -> Result<()> {
     let mut store = Store::new(&engine, ());
     let instance = Instance::new(&mut store, &module, &[])?;
 
-    let grow = instance.get_typed_func::<u32, i32, _>(&mut store, "grow")?;
-    let size = instance.get_typed_func::<(), u32, _>(&mut store, "size")?;
-    let i32_load = instance.get_typed_func::<u32, i32, _>(&mut store, "i32.load")?;
-    let i32_store = instance.get_typed_func::<(u32, i32), (), _>(&mut store, "i32.store")?;
+    let grow = instance.get_typed_func::<u32, i32>(&mut store, "grow")?;
+    let size = instance.get_typed_func::<(), u32>(&mut store, "size")?;
+    let i32_load = instance.get_typed_func::<u32, i32>(&mut store, "i32.load")?;
+    let i32_store = instance.get_typed_func::<(u32, i32), ()>(&mut store, "i32.store")?;
     let memory = instance.get_memory(&mut store, "memory").unwrap();
 
     // basic length 1 tests
@@ -757,7 +757,7 @@ fn dynamic_memory_pooling_allocator() -> Result<()> {
     // Re-instantiate in another store.
     store = Store::new(&engine, ());
     let instance = Instance::new(&mut store, &module, &[])?;
-    let i32_load = instance.get_typed_func::<u32, i32, _>(&mut store, "i32.load")?;
+    let i32_load = instance.get_typed_func::<u32, i32>(&mut store, "i32.load")?;
     let memory = instance.get_memory(&mut store, "memory").unwrap();
 
     // Technically this is out of bounds...
@@ -806,8 +806,8 @@ fn zero_memory_pages_disallows_oob() -> Result<()> {
     )?;
     let mut store = Store::new(&engine, ());
     let instance = Instance::new(&mut store, &module, &[])?;
-    let load32 = instance.get_typed_func::<i32, i32, _>(&mut store, "load")?;
-    let store32 = instance.get_typed_func::<i32, (), _>(&mut store, "store")?;
+    let load32 = instance.get_typed_func::<i32, i32>(&mut store, "load")?;
+    let store32 = instance.get_typed_func::<i32, ()>(&mut store, "store")?;
     for i in 0..31 {
         assert!(load32.call(&mut store, 1 << i).is_err());
         assert!(store32.call(&mut store, 1 << i).is_err());
diff --git a/tests/all/relocs.rs b/tests/all/relocs.rs
index c799fe1cb..a27ff6a00 100644
--- a/tests/all/relocs.rs
+++ b/tests/all/relocs.rs
@@ -43,7 +43,7 @@ fn forward_call_works() -> Result<()> {
     )?;
 
     let i = Instance::new(&mut store, &module, &[])?;
-    let foo = i.get_typed_func::<(), i32, _>(&mut store, "foo")?;
+    let foo = i.get_typed_func::<(), i32>(&mut store, "foo")?;
     assert_eq!(foo.call(&mut store, ())?, 4);
     Ok(())
 }
@@ -64,7 +64,7 @@ fn backwards_call_works() -> Result<()> {
     )?;
 
     let i = Instance::new(&mut store, &module, &[])?;
-    let foo = i.get_typed_func::<(), i32, _>(&mut store, "foo")?;
+    let foo = i.get_typed_func::<(), i32>(&mut store, "foo")?;
     assert_eq!(foo.call(&mut store, ())?, 4);
     Ok(())
 }
@@ -108,7 +108,7 @@ fn test_many_call_module(mut store: Store<()>) -> Result<()> {
 
     for i in 0..N {
         let name = i.to_string();
-        let func = instance.get_typed_func::<(), (i32, i32), _>(&mut store, &name)?;
+        let func = instance.get_typed_func::<(), (i32, i32)>(&mut store, &name)?;
         let (a, b) = func.call(&mut store, ())?;
         assert_eq!(a, i + 1);
         assert_eq!(b, i + 2);
diff --git a/tests/all/stack_overflow.rs b/tests/all/stack_overflow.rs
index 9cd90eb9c..67ab7e22a 100644
--- a/tests/all/stack_overflow.rs
+++ b/tests/all/stack_overflow.rs
@@ -24,7 +24,7 @@ fn host_always_has_some_stack() -> anyhow::Result<()> {
     )?;
     let func = Func::wrap(&mut store, test_host_stack);
     let instance = Instance::new(&mut store, &module, &[func.into()])?;
-    let foo = instance.get_typed_func::<(), (), _>(&mut store, "foo")?;
+    let foo = instance.get_typed_func::<(), ()>(&mut store, "foo")?;
 
     // Make sure that our function traps and the trap says that the call stack
     // has been exhausted.
diff --git a/tests/all/threads.rs b/tests/all/threads.rs
index 4d05a8fd8..fe497beec 100644
--- a/tests/all/threads.rs
+++ b/tests/all/threads.rs
@@ -82,10 +82,10 @@ fn test_sharing_of_shared_memory() -> Result<()> {
         ]
     });
     let instance1_first_word = instance1
-        .get_typed_func::<(), i32, _>(&mut store, "first_word")?
+        .get_typed_func::<(), i32>(&mut store, "first_word")?
         .call(&mut store, ())?;
     let instance2_first_word = instance2
-        .get_typed_func::<(), i32, _>(&mut store, "first_word")?
+        .get_typed_func::<(), i32>(&mut store, "first_word")?
         .call(&mut store, ())?;
     assert_eq!(shared_memory_first_word, 42);
     assert_eq!(instance1_first_word, 42);
@@ -106,7 +106,7 @@ fn test_probe_shared_memory_size() -> Result<()> {
     let module = Module::new(&engine, wat)?;
     let mut store = Store::new(&engine, ());
     let instance = Instance::new(&mut store, &module, &[])?;
-    let size_fn = instance.get_typed_func::<(), i32, _>(&mut store, "size")?;
+    let size_fn = instance.get_typed_func::<(), i32>(&mut store, "size")?;
     let mut shared_memory = instance.get_shared_memory(&mut store, "memory").unwrap();
 
     assert_eq!(size_fn.call(&mut store, ())?, 1);
@@ -185,7 +185,7 @@ fn test_grow_memory_in_multiple_threads() -> Result<()> {
             let mut store = Store::new(&engine, ());
             let instance = Instance::new(&mut store, &module, &[shared_memory.into()]).unwrap();
             let grow_fn = instance
-                .get_typed_func::<i32, i32, _>(&mut store, "grow")
+                .get_typed_func::<i32, i32>(&mut store, "grow")
                 .unwrap();
             let mut thread_local_observed_sizes: Vec<_> = (0..NUM_GROW_OPS / NUM_THREADS)
                 .map(|_| grow_fn.call(&mut store, 1).unwrap() as u32)
@@ -260,7 +260,7 @@ fn test_memory_size_accessibility() -> Result<()> {
         let mut store = Store::new(&engine, ());
         let instance = Instance::new(&mut store, &module, &[probe_memory.into()]).unwrap();
         let probe_fn = instance
-            .get_typed_func::<(), i32, _>(&mut store, "probe_last_available")
+            .get_typed_func::<(), i32>(&mut store, "probe_last_available")
             .unwrap();
         while !probe_done.load(Ordering::SeqCst) {
             let value = probe_fn.call(&mut store, ()).unwrap() as u32;
diff --git a/tests/all/traps.rs b/tests/all/traps.rs
index c01ddb88e..f1776c875 100644
--- a/tests/all/traps.rs
+++ b/tests/all/traps.rs
@@ -18,7 +18,7 @@ fn test_trap_return() -> Result<()> {
     let hello_func = Func::new(&mut store, hello_type, |_, _, _| bail!("test 123"));
 
     let instance = Instance::new(&mut store, &module, &[hello_func.into()])?;
-    let run_func = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let run_func = instance.get_typed_func::<(), ()>(&mut store, "run")?;
 
     let e = run_func.call(&mut store, ()).unwrap_err();
     assert!(format!("{e:?}").contains("test 123"));
@@ -48,7 +48,7 @@ fn test_anyhow_error_return() -> Result<()> {
     });
 
     let instance = Instance::new(&mut store, &module, &[hello_func.into()])?;
-    let run_func = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let run_func = instance.get_typed_func::<(), ()>(&mut store, "run")?;
 
     let e = run_func.call(&mut store, ()).unwrap_err();
     assert!(!e.to_string().contains("test 1234"));
@@ -86,7 +86,7 @@ fn test_trap_return_downcast() -> Result<()> {
     });
 
     let instance = Instance::new(&mut store, &module, &[hello_func.into()])?;
-    let run_func = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let run_func = instance.get_typed_func::<(), ()>(&mut store, "run")?;
 
     let e = run_func
         .call(&mut store, ())
@@ -121,7 +121,7 @@ fn test_trap_trace() -> Result<()> {
 
     let module = Module::new(store.engine(), wat)?;
     let instance = Instance::new(&mut store, &module, &[])?;
-    let run_func = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let run_func = instance.get_typed_func::<(), ()>(&mut store, "run")?;
 
     let e = run_func.call(&mut store, ()).unwrap_err();
 
@@ -196,7 +196,7 @@ fn test_trap_through_host() -> Result<()> {
         &module,
         &[host_func_a.into(), host_func_b.into()],
     )?;
-    let a = instance.get_typed_func::<(), (), _>(&mut store, "a")?;
+    let a = instance.get_typed_func::<(), ()>(&mut store, "a")?;
     let err = a.call(&mut store, ()).unwrap_err();
     let trace = err.downcast_ref::<WasmBacktrace>().unwrap().frames();
     assert_eq!(trace.len(), 3);
@@ -222,7 +222,7 @@ fn test_trap_backtrace_disabled() -> Result<()> {
 
     let module = Module::new(store.engine(), wat)?;
     let instance = Instance::new(&mut store, &module, &[])?;
-    let run_func = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let run_func = instance.get_typed_func::<(), ()>(&mut store, "run")?;
 
     let e = run_func.call(&mut store, ()).unwrap_err();
     assert!(e.downcast_ref::<WasmBacktrace>().is_none());
@@ -245,7 +245,7 @@ fn test_trap_trace_cb() -> Result<()> {
 
     let module = Module::new(store.engine(), wat)?;
     let instance = Instance::new(&mut store, &module, &[fn_func.into()])?;
-    let run_func = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let run_func = instance.get_typed_func::<(), ()>(&mut store, "run")?;
 
     let e = run_func.call(&mut store, ()).unwrap_err();
 
@@ -271,7 +271,7 @@ fn test_trap_stack_overflow() -> Result<()> {
 
     let module = Module::new(store.engine(), wat)?;
     let instance = Instance::new(&mut store, &module, &[])?;
-    let run_func = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let run_func = instance.get_typed_func::<(), ()>(&mut store, "run")?;
 
     let e = run_func.call(&mut store, ()).unwrap_err();
 
@@ -301,7 +301,7 @@ fn trap_display_pretty() -> Result<()> {
 
     let module = Module::new(store.engine(), wat)?;
     let instance = Instance::new(&mut store, &module, &[])?;
-    let run_func = instance.get_typed_func::<(), (), _>(&mut store, "bar")?;
+    let run_func = instance.get_typed_func::<(), ()>(&mut store, "bar")?;
 
     let e = run_func.call(&mut store, ()).unwrap_err();
     assert_eq!(
@@ -345,7 +345,7 @@ fn trap_display_multi_module() -> Result<()> {
     "#;
     let module = Module::new(store.engine(), wat)?;
     let instance = Instance::new(&mut store, &module, &[bar])?;
-    let bar2 = instance.get_typed_func::<(), (), _>(&mut store, "bar2")?;
+    let bar2 = instance.get_typed_func::<(), ()>(&mut store, "bar2")?;
 
     let e = bar2.call(&mut store, ()).unwrap_err();
     assert_eq!(
@@ -405,12 +405,12 @@ fn rust_panic_import() -> Result<()> {
     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()])?;
-    let func = instance.get_typed_func::<(), (), _>(&mut store, "foo")?;
+    let func = instance.get_typed_func::<(), ()>(&mut store, "foo")?;
     let err =
         panic::catch_unwind(AssertUnwindSafe(|| drop(func.call(&mut store, ())))).unwrap_err();
     assert_eq!(err.downcast_ref::<&'static str>(), Some(&"this is a panic"));
 
-    let func = instance.get_typed_func::<(), (), _>(&mut store, "bar")?;
+    let func = instance.get_typed_func::<(), ()>(&mut store, "bar")?;
     let err = panic::catch_unwind(AssertUnwindSafe(|| {
         drop(func.call(&mut store, ()));
     }))
@@ -468,7 +468,7 @@ fn rust_catch_panic_import() -> Result<()> {
     });
 
     let instance = Instance::new(&mut store, &module, &[panic.into(), catch_panic.into()])?;
-    let run = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let run = instance.get_typed_func::<(), ()>(&mut store, "run")?;
     let trap = run.call(&mut store, ()).unwrap_err();
     let trace = trap.downcast_ref::<WasmBacktrace>().unwrap().frames();
     assert_eq!(trace.len(), 1);
@@ -613,7 +613,7 @@ fn present_after_module_drop() -> Result<()> {
     let mut store = Store::<()>::default();
     let module = Module::new(store.engine(), r#"(func (export "foo") unreachable)"#)?;
     let instance = Instance::new(&mut store, &module, &[])?;
-    let func = instance.get_typed_func::<(), (), _>(&mut store, "foo")?;
+    let func = instance.get_typed_func::<(), ()>(&mut store, "foo")?;
 
     println!("asserting before we drop modules");
     assert_trap(func.call(&mut store, ()).unwrap_err());
@@ -811,13 +811,13 @@ fn multithreaded_traps() -> Result<()> {
     let instance = Instance::new(&mut store, &module, &[])?;
 
     assert!(instance
-        .get_typed_func::<(), (), _>(&mut store, "run")?
+        .get_typed_func::<(), ()>(&mut store, "run")?
         .call(&mut store, ())
         .is_err());
 
     let handle = std::thread::spawn(move || {
         assert!(instance
-            .get_typed_func::<(), (), _>(&mut store, "run")
+            .get_typed_func::<(), ()>(&mut store, "run")
             .unwrap()
             .call(&mut store, ())
             .is_err());
@@ -843,7 +843,7 @@ fn traps_without_address_map() -> Result<()> {
 
     let module = Module::new(store.engine(), wat)?;
     let instance = Instance::new(&mut store, &module, &[])?;
-    let run_func = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let run_func = instance.get_typed_func::<(), ()>(&mut store, "run")?;
 
     let e = run_func.call(&mut store, ()).unwrap_err();
 
@@ -891,7 +891,7 @@ fn catch_trap_calling_across_stores() -> Result<()> {
             let data = ctx.data_mut();
             let func = data
                 .child_instance
-                .get_typed_func::<(), (), _>(&mut data.child_store, "trap")
+                .get_typed_func::<(), ()>(&mut data.child_store, "trap")
                 .expect("trap function should be exported");
 
             let trap = func.call(&mut data.child_store, ()).unwrap_err();
@@ -935,7 +935,7 @@ fn catch_trap_calling_across_stores() -> Result<()> {
 
     let parent_instance = linker.instantiate(&mut store, &parent_module)?;
 
-    let func = parent_instance.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let func = parent_instance.get_typed_func::<(), ()>(&mut store, "run")?;
     func.call(store, ())?;
 
     Ok(())
@@ -994,7 +994,7 @@ async fn async_then_sync_trap() -> Result<()> {
 
         log::info!("Calling `c`...");
         let c = sync_instance
-            .get_typed_func::<(), (), _>(&mut *sync_store, "c")
+            .get_typed_func::<(), ()>(&mut *sync_store, "c")
             .unwrap();
         c.call(sync_store, ())?;
         Ok(())
@@ -1006,7 +1006,7 @@ async fn async_then_sync_trap() -> Result<()> {
 
     log::info!("Calling `a`...");
     let a = async_instance
-        .get_typed_func::<(), (), _>(&mut async_store, "a")
+        .get_typed_func::<(), ()>(&mut async_store, "a")
         .unwrap();
     let trap = a.call_async(&mut async_store, ()).await.unwrap_err();
 
@@ -1074,7 +1074,7 @@ async fn sync_then_async_trap() -> Result<()> {
 
         log::info!("Calling `c`...");
         let c = async_instance
-            .get_typed_func::<(), (), _>(&mut *async_store, "c")
+            .get_typed_func::<(), ()>(&mut *async_store, "c")
             .unwrap();
         tokio::task::block_in_place(|| {
             tokio::runtime::Handle::current()
@@ -1087,7 +1087,7 @@ async fn sync_then_async_trap() -> Result<()> {
 
     log::info!("Calling `a`...");
     let a = sync_instance
-        .get_typed_func::<(), (), _>(&mut sync_store, "a")
+        .get_typed_func::<(), ()>(&mut sync_store, "a")
         .unwrap();
     let trap = a.call(&mut sync_store, ()).unwrap_err();