Repository: bytecodealliance/wasmtime
Pull request #8303 (2024-04-05 06:08:40)

optimize perfermance of fd_read/fd_write
- Issue #7973

Compared to native(C/C++), ```fd-read``` can only reach 20% of native. I will divide the performance hotspots  into the following two categories:,
1. Wasi implementation of ```fd-read```(approximately 30% of the hotspots), such as the cost of [redundant memory copies,](https://github.com/bytecodealliance/wasmtime/blob/86dcae460b5437703570e52253cc1b7d5bad8597/crates/wasi/src/preview1.rs#L1602) the cost of [inserting](https://github.com/bytecodealliance/wasmtime/blob/86dcae460b5437703570e52253cc1b7d5bad8597/crates/wasi/src/preview1.rs#L1569) and [deleting](https://github.com/bytecodealliance/wasmtime/blob/86dcae460b5437703570e52253cc1b7d5bad8597/crates/wasi/src/preview1.rs#L1577) ```HostInputStream``` into ```WasiView```, and the cost of accessing guest memory(merged [PR);](https://github.com/bytecodealliance/wasmtime/pull/8277)
2. Wasm program call host function(approximately 40% of the hotspots): I am not very familiar with this mechanism yet, and currently I cannot understand the reasons behind the hotspots here;

This PR is mainly optimized for the first type of performance hotspot:
1. Avoid unnecessary copying;
2. Avoid inserting and deleting ```HostInputStream``` into```WasiView```;

The perfermance hotspots of ```fd_write``` is the same ```fd_read```:
https://github.com/bytecodealliance/wasmtime/blob/86dcae460b5437703570e52253cc1b7d5bad8597/crates/wasi/src/preview1.rs#L1682

https://github.com/bytecodealliance/wasmtime/blob/86dcae460b5437703570e52253cc1b7d5bad8597/crates/wasi/src/preview1.rs#L244


Patch:
diff --git a/RELEASES.md b/RELEASES.md
index 5fbf568bf..9fad07589 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -24,6 +24,15 @@ Unreleased.
 
 --------------------------------------------------------------------------------
 
+## 19.0.1
+
+Released 2024-04-02.
+
+* Fix a panic using tables with the wrong type.
+  [#8284](https://github.com/bytecodealliance/wasmtime/pull/8284)
+
+--------------------------------------------------------------------------------
+
 ## 19.0.0
 
 Released 2024-03-20
@@ -101,8 +110,8 @@ Released 2024-03-20
   specification.
   [#7661](https://github.com/bytecodealliance/wasmtime/pull/7661)
 
-* Support for `externref` has been moved behind a new `gc` Cargo feature. This
-  will also gate support for wasm gc in the future.
+* Support for `externref` and similar features has been moved behind a new `gc`
+  Cargo feature. This will also gate support for Wasm GC in the future.
   [#7975](https://github.com/bytecodealliance/wasmtime/pull/7975)
 
 * Require the `WASMTIME_WASI_CONFIG_PREOPEN_SOCKET_ALLOW` environment variable
diff --git a/cranelift/codegen/src/bitset.rs b/cranelift/codegen/src/bitset.rs
index 7d81b3221..70c86d722 100644
--- a/cranelift/codegen/src/bitset.rs
+++ b/cranelift/codegen/src/bitset.rs
@@ -10,13 +10,35 @@ use core::mem::size_of;
 use core::ops::{Add, BitOr, Shl, Sub};
 
 /// A small bitset built on a single primitive integer type
-#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
+#[derive(Clone, Copy, Default, PartialEq, Eq)]
 #[cfg_attr(
     feature = "enable-serde",
     derive(serde_derive::Serialize, serde_derive::Deserialize)
 )]
 pub struct BitSet<T>(pub T);
 
+impl<T> std::fmt::Debug for BitSet<T>
+where
+    T: Into<u32>
+        + From<u8>
+        + BitOr<T, Output = T>
+        + Shl<u8, Output = T>
+        + Sub<T, Output = T>
+        + Add<T, Output = T>
+        + PartialEq
+        + Copy,
+{
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        let mut s = f.debug_struct(std::any::type_name::<Self>());
+        for i in 0..Self::bits() {
+            use std::string::ToString;
+            let i = u32::try_from(i).unwrap();
+            s.field(&i.to_string(), &self.contains(i));
+        }
+        s.finish()
+    }
+}
+
 impl<T> BitSet<T>
 where
     T: Into<u32>
diff --git a/cranelift/codegen/src/context.rs b/cranelift/codegen/src/context.rs
index c5efafaee..93e955c9a 100644
--- a/cranelift/codegen/src/context.rs
+++ b/cranelift/codegen/src/context.rs
@@ -31,6 +31,7 @@ use crate::{timing, CompileError};
 use alloc::string::String;
 use alloc::vec::Vec;
 use cranelift_control::ControlPlane;
+use target_lexicon::Architecture;
 
 #[cfg(feature = "souper-harvest")]
 use crate::souper_harvest::do_souper_harvest;
@@ -282,7 +283,15 @@ impl Context {
 
     /// Perform NaN canonicalizing rewrites on the function.
     pub fn canonicalize_nans(&mut self, isa: &dyn TargetIsa) -> CodegenResult<()> {
-        do_nan_canonicalization(&mut self.func);
+        // Currently only RiscV64 is the only arch that may not have vector support.
+        let has_vector_support = match isa.triple().architecture {
+            Architecture::Riscv64(_) => match isa.isa_flags().iter().find(|f| f.name == "has_v") {
+                Some(value) => value.as_bool().unwrap_or(false),
+                None => false,
+            },
+            _ => true,
+        };
+        do_nan_canonicalization(&mut self.func, has_vector_support);
         self.verify_if(isa)
     }
 
diff --git a/cranelift/codegen/src/inst_predicates.rs b/cranelift/codegen/src/inst_predicates.rs
index 707a01c1a..c0e7ed665 100644
--- a/cranelift/codegen/src/inst_predicates.rs
+++ b/cranelift/codegen/src/inst_predicates.rs
@@ -44,6 +44,17 @@ pub fn has_side_effect(func: &Function, inst: Inst) -> bool {
     trivially_has_side_effects(opcode) || is_load_with_defined_trapping(opcode, data)
 }
 
+/// Is the given instruction a bitcast to or from a reference type (e.g. `r64`)?
+pub fn is_bitcast_from_ref(func: &Function, inst: Inst) -> bool {
+    let op = func.dfg.insts[inst].opcode();
+    if op != ir::Opcode::Bitcast {
+        return false;
+    }
+
+    let arg = func.dfg.inst_args(inst)[0];
+    func.dfg.value_type(arg).is_ref()
+}
+
 /// Does the given instruction behave as a "pure" node with respect to
 /// aegraph semantics?
 ///
@@ -58,6 +69,7 @@ pub fn is_pure_for_egraph(func: &Function, inst: Inst) -> bool {
         } => flags.readonly() && flags.notrap(),
         _ => false,
     };
+
     // Multi-value results do not play nicely with much of the egraph
     // infrastructure. They are in practice used only for multi-return
     // calls and some other odd instructions (e.g. uadd_overflow) which,
@@ -70,7 +82,11 @@ pub fn is_pure_for_egraph(func: &Function, inst: Inst) -> bool {
 
     let op = func.dfg.insts[inst].opcode();
 
-    has_one_result && (is_readonly_load || (!op.can_load() && !trivially_has_side_effects(op)))
+    has_one_result
+        && (is_readonly_load || (!op.can_load() && !trivially_has_side_effects(op)))
+        // Cannot optimize ref-y bitcasts, as that can interact badly with
+        // safepoints and stack maps.
+        && !is_bitcast_from_ref(func, inst)
 }
 
 /// Can the given instruction be merged into another copy of itself?
@@ -90,6 +106,9 @@ pub fn is_mergeable_for_egraph(func: &Function, inst: Inst) -> bool {
         && !op.can_store()
         // Can only have idempotent side-effects.
         && (!has_side_effect(func, inst) || op.side_effects_idempotent())
+        // Cannot optimize ref-y bitcasts, as that can interact badly with
+        // safepoints and stack maps.
+        && !is_bitcast_from_ref(func, inst)
 }
 
 /// Does the given instruction have any side-effect as per [has_side_effect], or else is a load,
diff --git a/cranelift/codegen/src/isa/aarch64/abi.rs b/cranelift/codegen/src/isa/aarch64/abi.rs
index 94b5897b6..8139ac81f 100644
--- a/cranelift/codegen/src/isa/aarch64/abi.rs
+++ b/cranelift/codegen/src/isa/aarch64/abi.rs
@@ -36,9 +36,9 @@ impl Into<AMode> for StackAMode {
     fn into(self) -> AMode {
         match self {
             // Argument area begins after saved frame pointer + return address.
-            StackAMode::ArgOffset(off, _ty) => AMode::FPOffset { off: off + 16 },
-            StackAMode::NominalSPOffset(off, _ty) => AMode::NominalSPOffset { off },
-            StackAMode::SPOffset(off, _ty) => AMode::SPOffset { off },
+            StackAMode::IncomingArg(off) => AMode::FPOffset { off: off + 16 },
+            StackAMode::Slot(off) => AMode::NominalSPOffset { off },
+            StackAMode::OutgoingArg(off) => AMode::SPOffset { off },
         }
     }
 }
@@ -457,7 +457,7 @@ impl ABIMachineSpec for AArch64MachineDeps {
         insts
     }
 
-    fn gen_get_stack_addr(mem: StackAMode, into_reg: Writable<Reg>, _ty: Type) -> Inst {
+    fn gen_get_stack_addr(mem: StackAMode, into_reg: Writable<Reg>) -> Inst {
         // FIXME: Do something different for dynamic types?
         let mem = mem.into();
         Inst::LoadAddr { rd: into_reg, mem }
diff --git a/cranelift/codegen/src/isa/riscv64/abi.rs b/cranelift/codegen/src/isa/riscv64/abi.rs
index 8f99caa69..dca2cda68 100644
--- a/cranelift/codegen/src/isa/riscv64/abi.rs
+++ b/cranelift/codegen/src/isa/riscv64/abi.rs
@@ -293,7 +293,7 @@ impl ABIMachineSpec for Riscv64MachineDeps {
         insts
     }
 
-    fn gen_get_stack_addr(mem: StackAMode, into_reg: Writable<Reg>, _ty: Type) -> Inst {
+    fn gen_get_stack_addr(mem: StackAMode, into_reg: Writable<Reg>) -> Inst {
         Inst::LoadAddr {
             rd: into_reg,
             mem: mem.into(),
diff --git a/cranelift/codegen/src/isa/riscv64/inst/args.rs b/cranelift/codegen/src/isa/riscv64/inst/args.rs
index 9a9fe4d8f..4f8c7beed 100644
--- a/cranelift/codegen/src/isa/riscv64/inst/args.rs
+++ b/cranelift/codegen/src/isa/riscv64/inst/args.rs
@@ -200,9 +200,9 @@ impl Into<AMode> for StackAMode {
     fn into(self) -> AMode {
         match self {
             // Argument area begins after saved lr + fp.
-            StackAMode::ArgOffset(offset, _ty) => AMode::FPOffset(offset + 16),
-            StackAMode::SPOffset(offset, _ty) => AMode::SPOffset(offset),
-            StackAMode::NominalSPOffset(offset, _ty) => AMode::NominalSPOffset(offset),
+            StackAMode::IncomingArg(offset) => AMode::FPOffset(offset + 16),
+            StackAMode::OutgoingArg(offset) => AMode::SPOffset(offset),
+            StackAMode::Slot(offset) => AMode::NominalSPOffset(offset),
         }
     }
 }
diff --git a/cranelift/codegen/src/isa/s390x/abi.rs b/cranelift/codegen/src/isa/s390x/abi.rs
index b5fb077bf..55ebf6b2f 100644
--- a/cranelift/codegen/src/isa/s390x/abi.rs
+++ b/cranelift/codegen/src/isa/s390x/abi.rs
@@ -192,9 +192,9 @@ impl Into<MemArg> for StackAMode {
     fn into(self) -> MemArg {
         match self {
             // Argument area always begins at the initial SP.
-            StackAMode::ArgOffset(off, _ty) => MemArg::InitialSPOffset { off },
-            StackAMode::NominalSPOffset(off, _ty) => MemArg::NominalSPOffset { off },
-            StackAMode::SPOffset(off, _ty) => {
+            StackAMode::IncomingArg(off) => MemArg::InitialSPOffset { off },
+            StackAMode::Slot(off) => MemArg::NominalSPOffset { off },
+            StackAMode::OutgoingArg(off) => {
                 MemArg::reg_plus_off(stack_reg(), off, MemFlags::trusted())
             }
         }
@@ -495,7 +495,7 @@ impl ABIMachineSpec for S390xMachineDeps {
         insts
     }
 
-    fn gen_get_stack_addr(mem: StackAMode, into_reg: Writable<Reg>, _ty: Type) -> Inst {
+    fn gen_get_stack_addr(mem: StackAMode, into_reg: Writable<Reg>) -> Inst {
         let mem = mem.into();
         Inst::LoadAddr { rd: into_reg, mem }
     }
diff --git a/cranelift/codegen/src/isa/x64/abi.rs b/cranelift/codegen/src/isa/x64/abi.rs
index b3eaea14c..a4c36a523 100644
--- a/cranelift/codegen/src/isa/x64/abi.rs
+++ b/cranelift/codegen/src/isa/x64/abi.rs
@@ -478,7 +478,7 @@ impl ABIMachineSpec for X64ABIMachineSpec {
         ]
     }
 
-    fn gen_get_stack_addr(mem: StackAMode, into_reg: Writable<Reg>, _ty: Type) -> Self::I {
+    fn gen_get_stack_addr(mem: StackAMode, into_reg: Writable<Reg>) -> Self::I {
         let mem: SyntheticAmode = mem.into();
         Inst::lea(mem, into_reg)
     }
@@ -1018,27 +1018,26 @@ impl From<StackAMode> for SyntheticAmode {
         // We enforce a 128 MB stack-frame size limit above, so these
         // `expect()`s should never fail.
         match amode {
-            StackAMode::ArgOffset(off, _ty) => {
-                let off =
-                    i32::try_from(off + 16) // frame pointer + return address
-                        .expect(
-                            "Offset in ArgOffset is greater than 2GB; should hit impl limit first",
-                        );
+            StackAMode::IncomingArg(off) => {
+                let off = i32::try_from(off + 16) // frame pointer + return address
+                    .expect(
+                        "Offset in IncomingArg is greater than 2GB; should hit impl limit first",
+                    );
                 SyntheticAmode::Real(Amode::ImmReg {
                     simm32: off,
                     base: regs::rbp(),
                     flags: MemFlags::trusted(),
                 })
             }
-            StackAMode::NominalSPOffset(off, _ty) => {
-                let off = i32::try_from(off).expect(
-                    "Offset in NominalSPOffset is greater than 2GB; should hit impl limit first",
-                );
+            StackAMode::Slot(off) => {
+                let off = i32::try_from(off)
+                    .expect("Offset in Slot is greater than 2GB; should hit impl limit first");
                 SyntheticAmode::nominal_sp_offset(off)
             }
-            StackAMode::SPOffset(off, _ty) => {
-                let off = i32::try_from(off)
-                    .expect("Offset in SPOffset is greater than 2GB; should hit impl limit first");
+            StackAMode::OutgoingArg(off) => {
+                let off = i32::try_from(off).expect(
+                    "Offset in OutgoingArg is greater than 2GB; should hit impl limit first",
+                );
                 SyntheticAmode::Real(Amode::ImmReg {
                     simm32: off,
                     base: regs::rsp(),
diff --git a/cranelift/codegen/src/isa/x64/lower.isle b/cranelift/codegen/src/isa/x64/lower.isle
index 64f738053..e180d35bb 100644
--- a/cranelift/codegen/src/isa/x64/lower.isle
+++ b/cranelift/codegen/src/isa/x64/lower.isle
@@ -1428,6 +1428,7 @@
 (decl pure partial all_ones_or_all_zeros (Value) bool)
 (rule (all_ones_or_all_zeros (and (icmp _ _ _) (value_type (multi_lane _ _)))) $true)
 (rule (all_ones_or_all_zeros (and (fcmp _ _ _) (value_type (multi_lane _ _)))) $true)
+(rule (all_ones_or_all_zeros (and (bitcast _ (fcmp _ _ _)) (value_type (multi_lane _ _)))) $true)
 (rule (all_ones_or_all_zeros (vconst (vconst_all_ones_or_all_zeros))) $true)
 
 (decl pure vconst_all_ones_or_all_zeros () Constant)
diff --git a/cranelift/codegen/src/machinst/abi.rs b/cranelift/codegen/src/machinst/abi.rs
index 3f195baaa..bb65431a2 100644
--- a/cranelift/codegen/src/machinst/abi.rs
+++ b/cranelift/codegen/src/machinst/abi.rs
@@ -281,15 +281,12 @@ pub enum ArgsOrRets {
 /// appropriate addressing mode.
 #[derive(Clone, Copy, Debug)]
 pub enum StackAMode {
-    /// Offset into the current frame's argument area, possibly making use of a
-    /// specific type for a scaled indexing operation.
-    ArgOffset(i64, ir::Type),
-    /// Offset from the nominal stack pointer, possibly making use of a specific
-    /// type for a scaled indexing operation.
-    NominalSPOffset(i64, ir::Type),
-    /// Offset from the real stack pointer, possibly making use of a specific
-    /// type for a scaled indexing operation.
-    SPOffset(i64, ir::Type),
+    /// Offset into the current frame's argument area.
+    IncomingArg(i64),
+    /// Offset within the stack slots in the current frame.
+    Slot(i64),
+    /// Offset into the callee frame's argument area.
+    OutgoingArg(i64),
 }
 
 /// Trait implemented by machine-specific backend to represent ISA flags.
@@ -447,7 +444,7 @@ pub trait ABIMachineSpec {
 
     /// Generate an instruction to compute an address of a stack slot (FP- or
     /// SP-based offset).
-    fn gen_get_stack_addr(mem: StackAMode, into_reg: Writable<Reg>, ty: Type) -> Self::I;
+    fn gen_get_stack_addr(mem: StackAMode, into_reg: Writable<Reg>) -> Self::I;
 
     /// Get a fixed register to use to compute a stack limit. This is needed for
     /// certain sequences generated after the register allocator has already
@@ -1475,7 +1472,7 @@ impl<M: ABIMachineSpec> Callee<M> {
                             ty
                         };
                     insts.push(M::gen_load_stack(
-                        StackAMode::ArgOffset(offset, ty),
+                        StackAMode::IncomingArg(offset),
                         *into_reg,
                         ty,
                     ));
@@ -1500,9 +1497,8 @@ impl<M: ABIMachineSpec> Callee<M> {
                 } else {
                     // Buffer address is implicitly defined by the ABI.
                     insts.push(M::gen_get_stack_addr(
-                        StackAMode::ArgOffset(offset, I8),
+                        StackAMode::IncomingArg(offset),
                         into_reg,
-                        I8,
                     ));
                 }
             }
@@ -1523,7 +1519,7 @@ impl<M: ABIMachineSpec> Callee<M> {
                         // This was allocated in the `init` routine.
                         let addr_reg = self.arg_temp_reg[idx].unwrap();
                         insts.push(M::gen_load_stack(
-                            StackAMode::ArgOffset(offset, ty),
+                            StackAMode::IncomingArg(offset),
                             addr_reg,
                             ty,
                         ));
@@ -1694,17 +1690,13 @@ impl<M: ABIMachineSpec> Callee<M> {
         // [MemArg::NominalSPOffset] for more details on nominal SP tracking).
         let stack_off = self.sized_stackslots[slot] as i64;
         let sp_off: i64 = stack_off + (offset as i64);
-        M::gen_get_stack_addr(StackAMode::NominalSPOffset(sp_off, I8), into_reg, I8)
+        M::gen_get_stack_addr(StackAMode::Slot(sp_off), into_reg)
     }
 
     /// Produce an instruction that computes a dynamic stackslot address.
     pub fn dynamic_stackslot_addr(&self, slot: DynamicStackSlot, into_reg: Writable<Reg>) -> M::I {
         let stack_off = self.dynamic_stackslots[slot] as i64;
-        M::gen_get_stack_addr(
-            StackAMode::NominalSPOffset(stack_off, I64X2XN),
-            into_reg,
-            I64X2XN,
-        )
+        M::gen_get_stack_addr(StackAMode::Slot(stack_off), into_reg)
     }
 
     /// Get an `args` pseudo-inst, if any, that should appear at the
@@ -1977,7 +1969,7 @@ impl<M: ABIMachineSpec> Callee<M> {
         let sp_off = self.get_spillslot_offset(to_slot);
         trace!("gen_spill: {from_reg:?} into slot {to_slot:?} at offset {sp_off}");
 
-        let from = StackAMode::NominalSPOffset(sp_off, ty);
+        let from = StackAMode::Slot(sp_off);
         <M>::gen_store_stack(from, Reg::from(from_reg), ty)
     }
 
@@ -1989,7 +1981,7 @@ impl<M: ABIMachineSpec> Callee<M> {
         let sp_off = self.get_spillslot_offset(from_slot);
         trace!("gen_reload: {to_reg:?} from slot {from_slot:?} at offset {sp_off}");
 
-        let from = StackAMode::NominalSPOffset(sp_off, ty);
+        let from = StackAMode::Slot(sp_off);
         <M>::gen_load_stack(from, to_reg.map(Reg::from), ty)
     }
 }
@@ -2242,9 +2234,8 @@ impl<M: ABIMachineSpec> CallSite<M> {
                 let src_ptr = from_regs.only_reg().unwrap();
                 let dst_ptr = ctx.alloc_tmp(M::word_type()).only_reg().unwrap();
                 ctx.emit(M::gen_get_stack_addr(
-                    StackAMode::SPOffset(offset, I8),
+                    StackAMode::OutgoingArg(offset),
                     dst_ptr,
-                    I8,
                 ));
                 // Emit a memcpy from `src_ptr` to `dst_ptr` of `size` bytes.
                 // N.B.: because we process StructArg params *first*, this is
@@ -2283,9 +2274,9 @@ impl<M: ABIMachineSpec> CallSite<M> {
                             "tail calls require frame pointers to be enabled"
                         );
 
-                        StackAMode::ArgOffset(offset, ty)
+                        StackAMode::IncomingArg(offset)
                     } else {
-                        StackAMode::SPOffset(offset, ty)
+                        StackAMode::OutgoingArg(offset)
                     };
                     ctx.emit(M::gen_store_stack(amode, vreg, ty))
                 }
@@ -2391,9 +2382,9 @@ impl<M: ABIMachineSpec> CallSite<M> {
             } => {
                 assert_eq!(from_regs.len(), 1);
                 let vreg = from_regs.regs()[0];
-                let amode = StackAMode::SPOffset(offset, ty);
+                let amode = StackAMode::OutgoingArg(offset);
                 let tmp = ctx.alloc_tmp(M::word_type()).only_reg().unwrap();
-                ctx.emit(M::gen_get_stack_addr(amode, tmp, ty));
+                ctx.emit(M::gen_get_stack_addr(amode, tmp));
                 let tmp = tmp.to_reg();
                 ctx.emit(M::gen_store_base_offset(tmp, 0, vreg, ty));
                 let loc = match pointer {
@@ -2504,7 +2495,7 @@ impl<M: ABIMachineSpec> CallSite<M> {
                                 sig_data.sized_stack_arg_space()
                             };
                             insts.push(M::gen_load_stack(
-                                StackAMode::SPOffset(offset + ret_area_base, ty),
+                                StackAMode::OutgoingArg(offset + ret_area_base),
                                 *into_reg,
                                 ty,
                             ));
@@ -2541,9 +2532,8 @@ impl<M: ABIMachineSpec> CallSite<M> {
             let rd = ctx.alloc_tmp(word_type).only_reg().unwrap();
             let ret_area_base = ctx.sigs()[self.sig].sized_stack_arg_space();
             ctx.emit(M::gen_get_stack_addr(
-                StackAMode::SPOffset(ret_area_base, I8),
+                StackAMode::OutgoingArg(ret_area_base),
                 rd,
-                I8,
             ));
             let moves = self.gen_arg(ctx, i.into(), ValueRegs::one(rd.to_reg()));
             self.emit_arg_moves(ctx, moves);
diff --git a/cranelift/codegen/src/machinst/buffer.rs b/cranelift/codegen/src/machinst/buffer.rs
index 8abe64a68..a18adea65 100644
--- a/cranelift/codegen/src/machinst/buffer.rs
+++ b/cranelift/codegen/src/machinst/buffer.rs
@@ -1675,7 +1675,7 @@ impl<I: VCodeInst> MachBuffer<I> {
                 (start_offset, end_offset)
             }
         };
-        trace!("Adding stack map for offsets {start:#x}..{end:#x}");
+        trace!("Adding stack map for offsets {start:#x}..{end:#x}: {stack_map:?}");
         self.stack_maps.push(MachStackMap {
             offset: start,
             offset_end: end,
diff --git a/cranelift/codegen/src/nan_canonicalization.rs b/cranelift/codegen/src/nan_canonicalization.rs
index c1bc16f77..5be41857a 100644
--- a/cranelift/codegen/src/nan_canonicalization.rs
+++ b/cranelift/codegen/src/nan_canonicalization.rs
@@ -5,7 +5,7 @@
 use crate::cursor::{Cursor, FuncCursor};
 use crate::ir::condcodes::FloatCC;
 use crate::ir::immediates::{Ieee32, Ieee64};
-use crate::ir::types;
+use crate::ir::types::{self};
 use crate::ir::{Function, Inst, InstBuilder, InstructionData, Opcode, Value};
 use crate::opts::MemFlags;
 use crate::timing;
@@ -15,13 +15,13 @@ static CANON_32BIT_NAN: u32 = 0b01111111110000000000000000000000;
 static CANON_64BIT_NAN: u64 = 0b0111111111111000000000000000000000000000000000000000000000000000;
 
 /// Perform the NaN canonicalization pass.
-pub fn do_nan_canonicalization(func: &mut Function) {
+pub fn do_nan_canonicalization(func: &mut Function, has_vector_support: bool) {
     let _tt = timing::canonicalize_nans();
     let mut pos = FuncCursor::new(func);
     while let Some(_block) = pos.next_block() {
         while let Some(inst) = pos.next_inst() {
             if is_fp_arith(&mut pos, inst) {
-                add_nan_canon_seq(&mut pos, inst);
+                add_nan_canon_seq(&mut pos, inst, has_vector_support);
             }
         }
     }
@@ -57,7 +57,7 @@ fn is_fp_arith(pos: &mut FuncCursor, inst: Inst) -> bool {
 }
 
 /// Append a sequence of canonicalizing instructions after the given instruction.
-fn add_nan_canon_seq(pos: &mut FuncCursor, inst: Inst) {
+fn add_nan_canon_seq(pos: &mut FuncCursor, inst: Inst, has_vector_support: bool) {
     // Select the instruction result, result type. Replace the instruction
     // result and step forward before inserting the canonicalization sequence.
     let val = pos.func.dfg.first_result(inst);
@@ -65,16 +65,28 @@ fn add_nan_canon_seq(pos: &mut FuncCursor, inst: Inst) {
     let new_res = pos.func.dfg.replace_result(val, val_type);
     let _next_inst = pos.next_inst().expect("block missing terminator!");
 
-    // Insert a comparison instruction, to check if `inst_res` is NaN. Select
-    // the canonical NaN value if `val` is NaN, assign the result to `inst`.
-    let is_nan = pos.ins().fcmp(FloatCC::NotEqual, new_res, new_res);
+    // Insert a comparison instruction, to check if `inst_res` is NaN (comparing
+    // against NaN is always unordered). Select the canonical NaN value if `val`
+    // is NaN, assign the result to `inst`.
+    let comparison = FloatCC::Unordered;
 
+    let vectorized_scalar_select = |pos: &mut FuncCursor, canon_nan: Value, ty: types::Type| {
+        let canon_nan = pos.ins().scalar_to_vector(ty, canon_nan);
+        let new_res = pos.ins().scalar_to_vector(ty, new_res);
+        let is_nan = pos.ins().fcmp(comparison, new_res, new_res);
+        let is_nan = pos.ins().bitcast(ty, MemFlags::new(), is_nan);
+        let simd_result = pos.ins().bitselect(is_nan, canon_nan, new_res);
+        pos.ins().with_result(val).extractlane(simd_result, 0);
+    };
     let scalar_select = |pos: &mut FuncCursor, canon_nan: Value| {
+        let is_nan = pos.ins().fcmp(comparison, new_res, new_res);
         pos.ins()
             .with_result(val)
             .select(is_nan, canon_nan, new_res);
     };
+
     let vector_select = |pos: &mut FuncCursor, canon_nan: Value| {
+        let is_nan = pos.ins().fcmp(comparison, new_res, new_res);
         let is_nan = pos.ins().bitcast(val_type, MemFlags::new(), is_nan);
         pos.ins()
             .with_result(val)
@@ -84,11 +96,19 @@ fn add_nan_canon_seq(pos: &mut FuncCursor, inst: Inst) {
     match val_type {
         types::F32 => {
             let canon_nan = pos.ins().f32const(Ieee32::with_bits(CANON_32BIT_NAN));
-            scalar_select(pos, canon_nan);
+            if has_vector_support {
+                vectorized_scalar_select(pos, canon_nan, types::F32X4);
+            } else {
+                scalar_select(pos, canon_nan);
+            }
         }
         types::F64 => {
             let canon_nan = pos.ins().f64const(Ieee64::with_bits(CANON_64BIT_NAN));
-            scalar_select(pos, canon_nan);
+            if has_vector_support {
+                vectorized_scalar_select(pos, canon_nan, types::F64X2);
+            } else {
+                scalar_select(pos, canon_nan);
+            }
         }
         types::F32X4 => {
             let canon_nan = pos.ins().f32const(Ieee32::with_bits(CANON_32BIT_NAN));
diff --git a/crates/component-macro/src/bindgen.rs b/crates/component-macro/src/bindgen.rs
index 5bfd968b3..d0aee83a0 100644
--- a/crates/component-macro/src/bindgen.rs
+++ b/crates/component-macro/src/bindgen.rs
@@ -7,7 +7,7 @@ use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
 use syn::parse::{Error, Parse, ParseStream, Result};
 use syn::punctuated::Punctuated;
 use syn::{braced, token, Token};
-use wasmtime_wit_bindgen::{AsyncConfig, Opts, Ownership, TrappableError};
+use wasmtime_wit_bindgen::{AsyncConfig, Opts, Ownership, TrappableError, TrappableImports};
 use wit_parser::{PackageId, Resolve, UnresolvedPackage, WorldId};
 
 pub struct Config {
@@ -107,6 +107,7 @@ impl Parse for Config {
                         opts.async_ = val;
                     }
                     Opt::TrappableErrorType(val) => opts.trappable_error_type = val,
+                    Opt::TrappableImports(val) => opts.trappable_imports = val,
                     Opt::Ownership(val) => opts.ownership = val,
                     Opt::Interfaces(s) => {
                         if inline.is_some() {
@@ -201,6 +202,7 @@ mod kw {
     syn::custom_keyword!(with);
     syn::custom_keyword!(except_imports);
     syn::custom_keyword!(only_imports);
+    syn::custom_keyword!(trappable_imports);
 }
 
 enum Opt {
@@ -213,6 +215,7 @@ enum Opt {
     Ownership(Ownership),
     Interfaces(syn::LitStr),
     With(HashMap<String, String>),
+    TrappableImports(TrappableImports),
 }
 
 impl Parse for Opt {
@@ -330,6 +333,22 @@ impl Parse for Opt {
             let fields: Punctuated<(String, String), Token![,]> =
                 contents.parse_terminated(with_field_parse, Token![,])?;
             Ok(Opt::With(HashMap::from_iter(fields)))
+        } else if l.peek(kw::trappable_imports) {
+            input.parse::<kw::trappable_imports>()?;
+            input.parse::<Token![:]>()?;
+            let config = if input.peek(syn::LitBool) {
+                match input.parse::<syn::LitBool>()?.value {
+                    true => TrappableImports::All,
+                    false => TrappableImports::None,
+                }
+            } else {
+                let contents;
+                syn::bracketed!(contents in input);
+                let fields: Punctuated<syn::LitStr, Token![,]> =
+                    contents.parse_terminated(Parse::parse, Token![,])?;
+                TrappableImports::Only(fields.iter().map(|s| s.value()).collect())
+            };
+            Ok(Opt::TrappableImports(config))
         } else {
             Err(l.error())
         }
diff --git a/crates/environ/src/component/translate.rs b/crates/environ/src/component/translate.rs
index 3334b1736..873105b34 100644
--- a/crates/environ/src/component/translate.rs
+++ b/crates/environ/src/component/translate.rs
@@ -4,6 +4,7 @@ use crate::{
     EntityIndex, ModuleEnvironment, ModuleTranslation, ModuleTypesBuilder, PrimaryMap, Tunables,
     TypeConvert, WasmHeapType, WasmValType,
 };
+use anyhow::anyhow;
 use anyhow::{bail, Result};
 use indexmap::IndexMap;
 use std::collections::HashMap;
@@ -534,7 +535,18 @@ impl<'a, 'data> Translator<'a, 'data> {
                     self.validator,
                     self.types.module_types_builder(),
                 )
-                .translate(parser, &component[range.start..range.end])?;
+                .translate(
+                    parser,
+                    component.get(range.start..range.end).ok_or_else(|| {
+                        anyhow!(
+                            "section range {}..{} is out of bounds (bound = {})",
+                            range.start,
+                            range.end,
+                            component.len()
+                        )
+                        .context("wasm component contains an invalid module section")
+                    })?,
+                )?;
                 let static_idx = self.static_modules.push(translation);
                 self.result
                     .initializers
diff --git a/crates/runtime/src/gc/enabled/drc.rs b/crates/runtime/src/gc/enabled/drc.rs
index 6987984da..56bba9838 100644
--- a/crates/runtime/src/gc/enabled/drc.rs
+++ b/crates/runtime/src/gc/enabled/drc.rs
@@ -160,6 +160,12 @@ impl DrcHeap {
 
         let drc_ref = drc_ref(gc_ref);
         let header = self.index_mut(&drc_ref);
+        debug_assert_ne!(
+            *header.ref_count.get_mut(),
+            0,
+            "{:#p} is supposedly live; should have nonzero ref count",
+            *gc_ref
+        );
         *header.ref_count.get_mut() += 1;
         log::trace!(
             "increment {:#p} ref count -> {}",
@@ -179,6 +185,12 @@ impl DrcHeap {
 
         let drc_ref = drc_ref(gc_ref);
         let header = self.index_mut(drc_ref);
+        debug_assert_ne!(
+            *header.ref_count.get_mut(),
+            0,
+            "{:#p} is supposedly live; should have nonzero ref count",
+            *gc_ref
+        );
         *header.ref_count.get_mut() -= 1;
         log::trace!(
             "decrement {:#p} ref count -> {}",
@@ -261,6 +273,13 @@ impl DrcHeap {
                 continue;
             }
 
+            debug_assert_ne!(
+                *self.index_mut(drc_ref(&gc_ref)).ref_count.get_mut(),
+                0,
+                "{gc_ref:#p} is on the Wasm stack and therefore should be held \
+                 by the activations table; should have nonzero ref count",
+            );
+
             log::trace!("Found GC reference on the stack: {:#p}", gc_ref);
             let is_new = self
                 .activations_table
@@ -272,26 +291,76 @@ impl DrcHeap {
         }
     }
 
-    /// Sweep the bump allocation table after we've discovered our precise stack
-    /// roots.
-    fn sweep(&mut self, host_data_table: &mut ExternRefHostDataTable) {
-        // Sweep our bump chunk.
+    fn iter_bump_chunk(&mut self) -> impl Iterator<Item = VMGcRef> + '_ {
         let num_filled = self.activations_table.num_filled_in_bump_chunk();
+        self.activations_table
+            .alloc
+            .chunk
+            .iter_mut()
+            .take(num_filled)
+            .map(|slot| {
+                let r64 = *slot.get_mut();
+                VMGcRef::from_r64(r64)
+                    .expect("valid r64")
+                    .expect("non-null")
+            })
+    }
+
+    #[inline(never)]
+    #[cold]
+    fn log_gc_ref_set(prefix: &str, items: impl Iterator<Item = VMGcRef>) {
+        assert!(log::log_enabled!(log::Level::Trace));
+        let mut set = "{".to_string();
+        let mut any = false;
+        for gc_ref in items {
+            any = true;
+            set += &format!("\n  {gc_ref:#p},");
+        }
+        if any {
+            set.push('\n');
+        }
+        set.push('}');
+        log::trace!("{prefix}: {set}");
+    }
+
+    fn drain_bump_chunk(&mut self, mut f: impl FnMut(&mut Self, VMGcRef)) {
+        let num_filled = self.activations_table.num_filled_in_bump_chunk();
+
+        // Temporarily take the allocation out of `self` to avoid conflicting
+        // borrows.
         let mut alloc = mem::take(&mut self.activations_table.alloc);
         for slot in alloc.chunk.iter_mut().take(num_filled) {
             let r64 = mem::take(slot.get_mut());
             let gc_ref = VMGcRef::from_r64(r64)
                 .expect("valid r64")
                 .expect("non-null");
-            self.dec_ref_and_maybe_dealloc(host_data_table, &gc_ref);
-
+            f(self, gc_ref);
             *slot.get_mut() = 0;
         }
+
         debug_assert!(
-            alloc.chunk.iter().all(|slot| unsafe { *slot.get() == 0 }),
-            "after sweeping the bump chunk, all slots should be empty"
+            alloc.chunk.iter_mut().all(|slot| *slot.get_mut() == 0),
+            "after sweeping the bump chunk, all slots should be empty",
         );
 
+        debug_assert!(self.activations_table.alloc.chunk.is_empty());
+        self.activations_table.alloc = alloc;
+    }
+
+    /// Sweep the bump allocation table after we've discovered our precise stack
+    /// roots.
+    fn sweep(&mut self, host_data_table: &mut ExternRefHostDataTable) {
+        if log::log_enabled!(log::Level::Trace) {
+            Self::log_gc_ref_set("bump chunk before sweeping", self.iter_bump_chunk());
+        }
+
+        // Sweep our bump chunk.
+        log::trace!("Begin sweeping bump chunk");
+        self.drain_bump_chunk(|heap, gc_ref| {
+            heap.dec_ref_and_maybe_dealloc(host_data_table, &gc_ref);
+        });
+        log::trace!("Done sweeping bump chunk");
+
         if self.activations_table.alloc.chunk.is_empty() {
             // If this is the first collection, then the bump chunk is empty
             // since we lazily allocate it. Force that lazy allocation now so we
@@ -302,6 +371,16 @@ impl DrcHeap {
             self.activations_table.alloc.reset();
         }
 
+        if log::log_enabled!(log::Level::Trace) {
+            Self::log_gc_ref_set(
+                "hash set before sweeping",
+                self.activations_table
+                    .over_approximated_stack_roots
+                    .iter()
+                    .map(|r| r.unchecked_copy()),
+            );
+        }
+
         // The current `precise_stack_roots` becomes our new over-appoximated
         // set for the next GC cycle.
         mem::swap(
@@ -315,14 +394,26 @@ impl DrcHeap {
         // Note that this may run arbitrary code as we run gc_ref
         // destructors. Because of our `&mut` borrow above on this table,
         // though, we're guaranteed that nothing will touch this table.
+        log::trace!("Begin sweeping hash set");
         let mut precise_stack_roots = mem::take(&mut self.activations_table.precise_stack_roots);
         for gc_ref in precise_stack_roots.drain() {
             self.dec_ref_and_maybe_dealloc(host_data_table, &gc_ref);
         }
+        log::trace!("Done sweeping hash set");
 
         // Make sure to replace the `precise_stack_roots` so that we reuse any
         // allocated capacity.
         self.activations_table.precise_stack_roots = precise_stack_roots;
+
+        if log::log_enabled!(log::Level::Trace) {
+            Self::log_gc_ref_set(
+                "hash set after sweeping",
+                self.activations_table
+                    .over_approximated_stack_roots
+                    .iter()
+                    .map(|r| r.unchecked_copy()),
+            );
+        }
     }
 }
 
diff --git a/crates/runtime/src/gc/gc_runtime.rs b/crates/runtime/src/gc/gc_runtime.rs
index e3cbff94f..1d2741dc5 100644
--- a/crates/runtime/src/gc/gc_runtime.rs
+++ b/crates/runtime/src/gc/gc_runtime.rs
@@ -311,19 +311,22 @@ enum RawGcRoot {
 
 impl GcRootsList {
     /// Add a GC root that is inside a Wasm stack frame to this list.
-    //
-    // TODO: we shouldn't need to differentiate between on-stack GC roots and
-    // others, and both should just be `*mut VMGcRef`, but because Cranelift
-    // doesn't support `r32` on 64-bit platforms yet, we have to use `r64` for
-    // roots on the Wasm stack instead.
     #[inline]
-    pub fn add_wasm_stack_root(&mut self, ptr_to_root: SendSyncPtr<u64>) {
+    pub unsafe fn add_wasm_stack_root(&mut self, ptr_to_root: SendSyncPtr<u64>) {
+        log::trace!(
+            "Adding Wasm stack root: {:#p}",
+            VMGcRef::from_r64(*ptr_to_root.as_ref()).unwrap().unwrap()
+        );
         self.0.push(RawGcRoot::Stack(ptr_to_root));
     }
 
     /// Add a GC root to this list.
     #[inline]
-    pub fn add_root(&mut self, ptr_to_root: SendSyncPtr<VMGcRef>) {
+    pub unsafe fn add_root(&mut self, ptr_to_root: SendSyncPtr<VMGcRef>) {
+        log::trace!(
+            "Adding non-stack root: {:#p}",
+            ptr_to_root.as_ref().unchecked_copy()
+        );
         self.0.push(RawGcRoot::NonStack(ptr_to_root))
     }
 
diff --git a/crates/runtime/src/gc/host_data.rs b/crates/runtime/src/gc/host_data.rs
index 933935bed..13161b508 100644
--- a/crates/runtime/src/gc/host_data.rs
+++ b/crates/runtime/src/gc/host_data.rs
@@ -39,11 +39,14 @@ impl ExternRefHostDataTable {
     /// Allocate a new `externref` host data value.
     pub fn alloc(&mut self, value: Box<dyn Any + Send + Sync>) -> ExternRefHostDataId {
         let id = self.slab.alloc(value);
-        ExternRefHostDataId(id)
+        let id = ExternRefHostDataId(id);
+        log::trace!("allocated new externref host data: {id:?}");
+        id
     }
 
     /// Deallocate an `externref` host data value.
     pub fn dealloc(&mut self, id: ExternRefHostDataId) -> Box<dyn Any + Send + Sync> {
+        log::trace!("deallocated externref host data: {id:?}");
         self.slab.dealloc(id.0)
     }
 
diff --git a/crates/runtime/src/libcalls.rs b/crates/runtime/src/libcalls.rs
index 1095948ba..c2074eeee 100644
--- a/crates/runtime/src/libcalls.rs
+++ b/crates/runtime/src/libcalls.rs
@@ -380,6 +380,7 @@ unsafe fn drop_gc_ref(instance: &mut Instance, gc_ref: *mut u8) {
     let gc_ref = VMGcRef::from_r64(u64::try_from(gc_ref as usize).unwrap())
         .expect("valid r64")
         .expect("non-null VMGcRef");
+    log::trace!("libcalls::drop_gc_ref({gc_ref:?})");
     (*instance.store()).gc_store().drop_gc_ref(gc_ref);
 }
 
diff --git a/crates/runtime/src/table.rs b/crates/runtime/src/table.rs
index 4a0ea6019..f138d1a37 100644
--- a/crates/runtime/src/table.rs
+++ b/crates/runtime/src/table.rs
@@ -467,16 +467,16 @@ impl Table {
             TableElement::FuncRef(f) => {
                 self.funcrefs_mut()[start..end].fill(TaggedFuncRef::from(f));
             }
-            TableElement::GcRef(e) => {
+            TableElement::GcRef(r) => {
                 // Clone the init GC reference into each table slot.
                 for slot in &mut self.gc_refs_mut()[start..end] {
-                    gc_store.write_gc_ref(slot, e.as_ref());
+                    gc_store.write_gc_ref(slot, r.as_ref());
                 }
 
                 // Drop the init GC reference, since we aren't holding onto this
                 // reference anymore, only the clones in the table.
-                if let Some(e) = e {
-                    gc_store.drop_gc_ref(e);
+                if let Some(r) = r {
+                    gc_store.drop_gc_ref(r);
                 }
             }
             TableElement::UninitFunc => {
diff --git a/crates/wasi-http/src/lib.rs b/crates/wasi-http/src/lib.rs
index 29419d745..da72cecfa 100644
--- a/crates/wasi-http/src/lib.rs
+++ b/crates/wasi-http/src/lib.rs
@@ -20,6 +20,7 @@ pub mod bindings {
         ",
         tracing: true,
         async: false,
+        trappable_imports: true,
         with: {
             // Upstream package dependencies
             "wasi:io": wasmtime_wasi::bindings::io,
diff --git a/crates/wasi-nn/src/wit.rs b/crates/wasi-nn/src/wit.rs
index 95bfd1dac..dd9c9cc08 100644
--- a/crates/wasi-nn/src/wit.rs
+++ b/crates/wasi-nn/src/wit.rs
@@ -20,7 +20,11 @@ use std::{error::Error, fmt, hash::Hash, str::FromStr};
 
 /// Generate the traits and types from the `wasi-nn` WIT specification.
 mod gen_ {
-    wasmtime::component::bindgen!("ml" in "spec/wit/wasi-nn.wit");
+    wasmtime::component::bindgen!({
+        world: "ml",
+        path: "spec/wit/wasi-nn.wit",
+        trappable_imports: true,
+    });
 }
 use gen_::wasi::nn as gen; // Shortcut to the module containing the types we need.
 
diff --git a/crates/wasi/src/bindings.rs b/crates/wasi/src/bindings.rs
index 57c08329a..737301403 100644
--- a/crates/wasi/src/bindings.rs
+++ b/crates/wasi/src/bindings.rs
@@ -14,6 +14,7 @@ pub mod sync {
                 "wasi:io/streams/stream-error" => StreamError,
                 "wasi:filesystem/types/error-code" => FsError,
             },
+            trappable_imports: true,
             with: {
                 // These interfaces come from the outer module, as it's
                 // sync/async agnostic.
@@ -118,6 +119,7 @@ mod async_io {
         path: "wit",
         world: "wasi:cli/command",
         tracing: true,
+        trappable_imports: true,
         async: {
             // Only these functions are `async` and everything else is sync
             // meaning that it basically doesn't need to block. These functions
diff --git a/crates/wasi/src/preview1.rs b/crates/wasi/src/preview1.rs
index 96bab3f88..90475a7c6 100644
--- a/crates/wasi/src/preview1.rs
+++ b/crates/wasi/src/preview1.rs
@@ -83,6 +83,7 @@ use std::ops::{Deref, DerefMut};
 use std::slice;
 use std::sync::atomic::{AtomicU64, Ordering};
 use std::sync::Arc;
+use system_interface::fs::FileIoExt;
 use wasmtime::component::Resource;
 use wiggle::tracing::instrument;
 use wiggle::{GuestError, GuestPtr, GuestStrCow, GuestType};
@@ -276,6 +277,32 @@ impl BlockingMode {
             }
         }
     }
+
+    fn block_write(
+        &self,
+        file: &super::filesystem::File,
+        bytes: GuestPtr<'_, [u8]>,
+        append: bool,
+        mut pos: u64,
+    ) -> Result<usize> {
+        let buf = bytes.as_cow()?;
+        let mut buf = buf.deref();
+
+        let mut total: usize = 0;
+        while !buf.is_empty() {
+            let nwritten = if append {
+                file.file.append(buf)
+            } else {
+                file.file.write_at(buf, pos)
+            };
+            let nwritten = nwritten.or_else(|e| Err(StreamError::LastOperationFailed(e.into())))?;
+            pos = pos.checked_add(nwritten as u64).expect("add overflow");
+            (_, buf) = buf.split_at(nwritten);
+            total = total.checked_add(nwritten).expect("add overflow");
+        }
+
+        Ok(total)
+    }
 }
 
 #[derive(Debug)]
@@ -1550,37 +1577,46 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
     ) -> Result<types::Size, types::Error> {
         let t = self.transact()?;
         let desc = t.get_descriptor(fd)?;
-        let (buf, read) = match desc {
+        match desc {
             Descriptor::File(File {
                 fd,
-                blocking_mode,
+                blocking_mode: _,
                 position,
                 ..
             }) => {
                 let fd = fd.borrowed();
-                let blocking_mode = *blocking_mode;
                 let position = position.clone();
                 drop(t);
-                let Some(buf) = first_non_empty_iovec(iovs)? else {
+                let pos = position.load(Ordering::Relaxed);
+                let file = self.table().get(&fd)?.file()?;
+                let Some(iov) = first_non_empty_iovec(iovs)? else {
                     return Ok(0);
                 };
+                let is_shared_memory = iov.is_shared_memory();
+                let bytes_read = if is_shared_memory {
+                    let mut buf = vec![0; iov.len() as usize];
+                    let bytes_read = file
+                        .file
+                        .read_at(&mut buf[..], pos)
+                        .or_else(|e| Err(StreamError::LastOperationFailed(e.into())))?;
+                    iov.copy_from_slice(&buf[0..bytes_read])?;
+                    bytes_read
+                } else {
+                    let mut buf = iov.as_slice_mut()?.expect("get none");
+                    let buf = buf.deref_mut();
+                    let bytes_read = file
+                        .file
+                        .read_at(buf, pos)
+                        .or_else(|e| Err(StreamError::LastOperationFailed(e.into())))?;
+                    bytes_read
+                };
 
-                let pos = position.load(Ordering::Relaxed);
-                let stream = self.read_via_stream(fd.borrowed(), pos).map_err(|e| {
-                    e.try_into()
-                        .context("failed to call `read-via-stream`")
-                        .unwrap_or_else(types::Error::trap)
-                })?;
-                let read = blocking_mode
-                    .read(self, stream.borrowed(), buf.len().try_into()?)
-                    .await;
-                streams::HostInputStream::drop(self, stream).map_err(|e| types::Error::trap(e))?;
-                let read = read?;
-                let n = read.len().try_into()?;
-                let pos = pos.checked_add(n).ok_or(types::Errno::Overflow)?;
+                let pos = pos
+                    .checked_add(bytes_read.try_into()?)
+                    .ok_or(types::Errno::Overflow)?;
                 position.store(pos, Ordering::Relaxed);
 
-                (buf, read)
+                Ok(bytes_read.try_into()?)
             }
             Descriptor::Stdin { stream, .. } => {
                 let stream = stream.borrowed();
@@ -1591,17 +1627,16 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
                 let read = BlockingMode::Blocking
                     .read(self, stream, buf.len().try_into()?)
                     .await?;
-                (buf, read)
+                if read.len() > buf.len().try_into()? {
+                    return Err(types::Errno::Range.into());
+                }
+                let buf = buf.get_range(0..u32::try_from(read.len())?).unwrap();
+                buf.copy_from_slice(&read)?;
+                let n = read.len().try_into()?;
+                Ok(n)
             }
             _ => return Err(types::Errno::Badf.into()),
-        };
-        if read.len() > buf.len().try_into()? {
-            return Err(types::Errno::Range.into());
         }
-        let buf = buf.get_range(0..u32::try_from(read.len())?).unwrap();
-        buf.copy_from_slice(&read)?;
-        let n = read.len().try_into()?;
-        Ok(n)
     }
 
     /// Read from a file descriptor, without using and updating the file descriptor's offset.
@@ -1670,42 +1705,26 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
                 position,
             }) => {
                 let fd = fd.borrowed();
-                let fd2 = fd.borrowed();
                 let blocking_mode = *blocking_mode;
                 let position = position.clone();
+                let pos = position.load(Ordering::Relaxed);
                 let append = *append;
                 drop(t);
+                let f = self.table().get(&fd)?.file()?;
                 let Some(buf) = first_non_empty_ciovec(ciovs)? else {
                     return Ok(0);
                 };
-                let (stream, pos) = if append {
-                    let stream = self.append_via_stream(fd).map_err(|e| {
-                        e.try_into()
-                            .context("failed to call `append-via-stream`")
-                            .unwrap_or_else(types::Error::trap)
-                    })?;
-                    (stream, 0)
-                } else {
-                    let pos = position.load(Ordering::Relaxed);
-                    let stream = self.write_via_stream(fd, pos).map_err(|e| {
-                        e.try_into()
-                            .context("failed to call `write-via-stream`")
-                            .unwrap_or_else(types::Error::trap)
-                    })?;
-                    (stream, pos)
-                };
-                let n = blocking_mode.write(self, stream.borrowed(), buf).await;
-                streams::HostOutputStream::drop(self, stream).map_err(|e| types::Error::trap(e))?;
-                let n = n?;
+                let nwritten = blocking_mode.block_write(f, buf, append, pos)?;
                 if append {
-                    let len = self.stat(fd2).await?;
+                    let len = self.stat(fd).await?;
                     position.store(len.size, Ordering::Relaxed);
                 } else {
-                    let pos = pos.checked_add(n as u64).ok_or(types::Errno::Overflow)?;
+                    let pos = pos
+                        .checked_add(nwritten as u64)
+                        .ok_or(types::Errno::Overflow)?;
                     position.store(pos, Ordering::Relaxed);
                 }
-                let n = n.try_into()?;
-                Ok(n)
+                Ok(nwritten.try_into()?)
             }
             Descriptor::Stdout { stream, .. } | Descriptor::Stderr { stream, .. } => {
                 let stream = stream.borrowed();
diff --git a/crates/wasmtime/src/runtime/component/mod.rs b/crates/wasmtime/src/runtime/component/mod.rs
index 9418ede3b..43ac2f710 100644
--- a/crates/wasmtime/src/runtime/component/mod.rs
+++ b/crates/wasmtime/src/runtime/component/mod.rs
@@ -209,8 +209,8 @@ pub(crate) use self::store::ComponentStoreData;
 /// impl HelloWorldImports for MyState {
 ///     // Note the `Result` return value here where `Ok` is returned back to
 ///     // the component and `Err` will raise a trap.
-///     fn name(&mut self) -> wasmtime::Result<String> {
-///         Ok(self.name.clone())
+///     fn name(&mut self) -> String {
+///         self.name.clone()
 ///     }
 /// }
 ///
@@ -315,13 +315,13 @@ pub(crate) use self::store::ComponentStoreData;
 ///
 /// // Note that the trait here is per-interface and within a submodule now.
 /// impl Host for MyState {
-///     fn gen_random_integer(&mut self) -> wasmtime::Result<u32> {
+///     fn gen_random_integer(&mut self) -> u32 {
 /// #       panic!();
 /// #       #[cfg(FALSE)]
-///         Ok(rand::thread_rng().gen())
+///         rand::thread_rng().gen()
 ///     }
 ///
-///     fn sha256(&mut self, bytes: Vec<u8>) -> wasmtime::Result<String> {
+///     fn sha256(&mut self, bytes: Vec<u8>) -> String {
 ///         // ...
 /// #       panic!()
 ///     }
@@ -432,6 +432,21 @@ pub(crate) use self::store::ComponentStoreData;
 ///         only_imports: ["foo", "bar"],
 ///     },
 ///
+///     // This option is used to indicate whether imports can trap.
+///     //
+///     // Imports that may trap have their return types wrapped in
+///     // `wasmtime::Result<T>` where the `Err` variant indicates that a
+///     // trap will be raised in the guest.
+///     //
+///     // By default imports cannot trap and the return value is the return
+///     // value from the WIT bindings itself. This value can be set to `true`
+///     // to indicate that any import can trap. This value can also be set to
+///     // an array-of-strings to indicate that only a set list of imports
+///     // can trap.
+///     trappable_imports: false,             // no imports can trap (default)
+///     // trappable_imports: true,           // all imports can trap
+///     // trappable_imports: ["foo", "bar"], // only these can trap
+///
 ///     // This can be used to translate WIT return values of the form
 ///     // `result<T, error-type>` into `Result<T, RustErrorType>` in Rust.
 ///     // Users must define `RustErrorType` and the `Host` trait for the
@@ -440,7 +455,8 @@ pub(crate) use self::store::ComponentStoreData;
 ///     // into `wasmtime::Result<ErrorType>`. This conversion can either
 ///     // return the raw WIT error (`ErrorType` here) or a trap.
 ///     //
-///     // By default this option is not specified.
+///     // By default this option is not specified. This option only takes
+///     // effect when `trappable_imports` is set for some imports.
 ///     trappable_error_type: {
 ///         "wasi:io/streams/stream-error" => RustErrorType,
 ///     },
diff --git a/crates/wasmtime/src/runtime/externals/global.rs b/crates/wasmtime/src/runtime/externals/global.rs
index 8276e6b98..dc58268d2 100644
--- a/crates/wasmtime/src/runtime/externals/global.rs
+++ b/crates/wasmtime/src/runtime/externals/global.rs
@@ -213,7 +213,9 @@ impl Global {
             if let Some(gc_ref) = unsafe { (*store[self.0].definition).as_gc_ref() } {
                 let gc_ref = NonNull::from(gc_ref);
                 let gc_ref = SendSyncPtr::new(gc_ref);
-                gc_roots_list.add_root(gc_ref);
+                unsafe {
+                    gc_roots_list.add_root(gc_ref);
+                }
             }
         }
     }
diff --git a/crates/wasmtime/src/runtime/externals/table.rs b/crates/wasmtime/src/runtime/externals/table.rs
index 145723a36..0adc6ad94 100644
--- a/crates/wasmtime/src/runtime/externals/table.rs
+++ b/crates/wasmtime/src/runtime/externals/table.rs
@@ -387,7 +387,9 @@ impl Table {
             if let Some(gc_ref) = gc_ref {
                 let gc_ref = NonNull::from(gc_ref);
                 let gc_ref = SendSyncPtr::new(gc_ref);
-                gc_roots_list.add_root(gc_ref);
+                unsafe {
+                    gc_roots_list.add_root(gc_ref);
+                }
             }
         }
     }
diff --git a/crates/wasmtime/src/runtime/gc/enabled/rooting.rs b/crates/wasmtime/src/runtime/gc/enabled/rooting.rs
index 06193a529..293765e0a 100644
--- a/crates/wasmtime/src/runtime/gc/enabled/rooting.rs
+++ b/crates/wasmtime/src/runtime/gc/enabled/rooting.rs
@@ -370,12 +370,21 @@ struct LifoRoot {
 
 impl RootSet {
     pub(crate) fn trace_roots(&mut self, gc_roots_list: &mut GcRootsList) {
+        log::trace!("Begin trace user LIFO roots");
         for root in &mut self.lifo_roots {
-            gc_roots_list.add_root((&mut root.gc_ref).into());
+            unsafe {
+                gc_roots_list.add_root((&mut root.gc_ref).into());
+            }
         }
+        log::trace!("End trace user LIFO roots");
+
+        log::trace!("Begin trace user manual roots");
         for (_id, root) in self.manually_rooted.iter_mut() {
-            gc_roots_list.add_root(root.into());
+            unsafe {
+                gc_roots_list.add_root(root.into());
+            }
         }
+        log::trace!("End trace user manual roots");
     }
 
     /// Enter a LIFO rooting scope.
diff --git a/crates/wasmtime/src/runtime/store.rs b/crates/wasmtime/src/runtime/store.rs
index f54c5846f..c21273fab 100644
--- a/crates/wasmtime/src/runtime/store.rs
+++ b/crates/wasmtime/src/runtime/store.rs
@@ -1691,8 +1691,11 @@ impl StoreOpaque {
                     .expect("we should never use the high 32 bits of an r64");
 
                 if gc_ref.is_some() {
-                    gc_roots_list
-                        .add_wasm_stack_root(SendSyncPtr::new(NonNull::new(stack_slot).unwrap()));
+                    unsafe {
+                        gc_roots_list.add_wasm_stack_root(SendSyncPtr::new(
+                            NonNull::new(stack_slot).unwrap(),
+                        ));
+                    }
                 }
             }
 
diff --git a/crates/wit-bindgen/src/lib.rs b/crates/wit-bindgen/src/lib.rs
index b07ac4c4d..5d51c583b 100644
--- a/crates/wit-bindgen/src/lib.rs
+++ b/crates/wit-bindgen/src/lib.rs
@@ -104,6 +104,9 @@ pub struct Opts {
     /// Whether or not to generate code for only the interfaces of this wit file or not.
     pub only_interfaces: bool,
 
+    /// Configuration of which imports are allowed to generate a trap.
+    pub trappable_imports: TrappableImports,
+
     /// Remapping of interface names to rust module names.
     /// TODO: is there a better type to use for the value of this map?
     pub with: HashMap<String, String>,
@@ -154,6 +157,27 @@ impl AsyncConfig {
     }
 }
 
+#[derive(Default, Debug, Clone)]
+pub enum TrappableImports {
+    /// No imports are allowed to trap.
+    #[default]
+    None,
+    /// All imports may trap.
+    All,
+    /// Only the specified set of functions may trap.
+    Only(HashSet<String>),
+}
+
+impl TrappableImports {
+    fn can_trap(&self, f: &Function) -> bool {
+        match self {
+            TrappableImports::None => false,
+            TrappableImports::All => true,
+            TrappableImports::Only(set) => set.contains(&f.name),
+        }
+    }
+}
+
 impl Opts {
     pub fn generate(&self, resolve: &Resolve, world: WorldId) -> String {
         let mut r = Wasmtime::default();
@@ -1853,7 +1877,13 @@ impl<'a> InterfaceGenerator<'a> {
             );
         }
 
-        if let Some((_, err, _)) = self.special_case_trappable_error(&func.results) {
+        if !self.gen.opts.trappable_imports.can_trap(&func) {
+            if func.results.iter_types().len() == 1 {
+                uwrite!(self.src, "Ok((r,))\n");
+            } else {
+                uwrite!(self.src, "Ok(r)\n");
+            }
+        } else if let Some((_, err, _)) = self.special_case_trappable_error(&func.results) {
             let err = &self.resolve.types[resolve_type_definition_id(self.resolve, err)];
             let err_name = err.name.as_ref().unwrap();
             let owner = match err.owner {
@@ -1905,7 +1935,11 @@ impl<'a> InterfaceGenerator<'a> {
         self.push_str(")");
         self.push_str(" -> ");
 
-        if let Some((r, _id, error_typename)) = self.special_case_trappable_error(&func.results) {
+        if !self.gen.opts.trappable_imports.can_trap(func) {
+            self.print_result_ty(&func.results, TypeMode::Owned);
+        } else if let Some((r, _id, error_typename)) =
+            self.special_case_trappable_error(&func.results)
+        {
             // Functions which have a single result `result<ok,err>` get special
             // cased to use the host_wasmtime_rust::Error<err>, making it possible
             // for them to trap or use `?` to propogate their errors
diff --git a/examples/component/main.rs b/examples/component/main.rs
index 459764664..f70fc3819 100644
--- a/examples/component/main.rs
+++ b/examples/component/main.rs
@@ -13,8 +13,8 @@ struct HostComponent;
 
 // Implmentation of the host interface defined in the wit file.
 impl host::Host for HostComponent {
-    fn multiply(&mut self, a: f32, b: f32) -> wasmtime::Result<f32> {
-        Ok(a * b)
+    fn multiply(&mut self, a: f32, b: f32) -> f32 {
+        a * b
     }
 }
 
diff --git a/src/commands/explore.rs b/src/commands/explore.rs
index 465a1d342..6aaa004e3 100644
--- a/src/commands/explore.rs
+++ b/src/commands/explore.rs
@@ -2,7 +2,7 @@
 
 use anyhow::{Context, Result};
 use clap::Parser;
-use std::path::PathBuf;
+use std::{borrow::Cow, path::PathBuf};
 use wasmtime_cli_flags::CommonOptions;
 
 /// Explore the compilation of a WebAssembly module to native code.
@@ -32,8 +32,15 @@ impl ExploreCommand {
 
         let config = self.common.config(self.target.as_deref())?;
 
-        let wasm = std::fs::read(&self.module)
-            .with_context(|| format!("failed to read Wasm module: {}", self.module.display()))?;
+        let bytes =
+            Cow::Owned(std::fs::read(&self.module).with_context(|| {
+                format!("failed to read Wasm module: {}", self.module.display())
+            })?);
+        #[cfg(feature = "wat")]
+        let bytes = wat::parse_bytes(&bytes).map_err(|mut e| {
+            e.set_path(&self.module);
+            e
+        })?;
 
         let output = self
             .output
@@ -43,7 +50,7 @@ impl ExploreCommand {
             .with_context(|| format!("failed to create file: {}", output.display()))?;
         let mut output_file = std::io::BufWriter::new(output_file);
 
-        wasmtime_explorer::generate(&config, self.target.as_deref(), &wasm, &mut output_file)?;
+        wasmtime_explorer::generate(&config, self.target.as_deref(), &bytes, &mut output_file)?;
         println!("Exploration written to {}", output.display());
         Ok(())
     }
diff --git a/winch/codegen/src/codegen/env.rs b/winch/codegen/src/codegen/env.rs
index 63f1d31a2..377bbcb72 100644
--- a/winch/codegen/src/codegen/env.rs
+++ b/winch/codegen/src/codegen/env.rs
@@ -127,6 +127,8 @@ pub struct FuncEnv<'a, 'translation: 'a, 'data: 'translation, P: PtrSize> {
     ptr_type: WasmValType,
     /// Whether or not to enable Spectre mitigation on heap bounds checks.
     heap_access_spectre_mitigation: bool,
+    /// Whether or not to enable Spectre mitigation on table element accesses.
+    table_access_spectre_mitigation: bool,
     name_map: PrimaryMap<UserExternalNameRef, UserExternalName>,
     name_intern: HashMap<UserExternalName, UserExternalNameRef>,
 }
@@ -158,6 +160,7 @@ impl<'a, 'translation, 'data, P: PtrSize> FuncEnv<'a, 'translation, 'data, P> {
             resolved_globals: HashMap::new(),
             ptr_type,
             heap_access_spectre_mitigation: isa.flags().enable_heap_access_spectre_mitigation(),
+            table_access_spectre_mitigation: isa.flags().enable_table_access_spectre_mitigation(),
             builtins,
             name_map: Default::default(),
             name_intern: Default::default(),
@@ -314,6 +317,12 @@ impl<'a, 'translation, 'data, P: PtrSize> FuncEnv<'a, 'translation, 'data, P> {
         self.heap_access_spectre_mitigation
     }
 
+    /// Returns true if Spectre mitigations are enabled for table element
+    /// accesses.
+    pub fn table_access_spectre_mitigation(&self) -> bool {
+        self.table_access_spectre_mitigation
+    }
+
     pub(crate) fn callee_sig<'b, A>(&'b mut self, callee: &'b Callee) -> &'b ABISig
     where
         A: ABI,
diff --git a/winch/codegen/src/codegen/mod.rs b/winch/codegen/src/codegen/mod.rs
index 1ba4e9867..3017dc812 100644
--- a/winch/codegen/src/codegen/mod.rs
+++ b/winch/codegen/src/codegen/mod.rs
@@ -2,8 +2,10 @@ use crate::{
     abi::{vmctx, ABIOperand, ABISig, RetArea, ABI},
     codegen::BlockSig,
     isa::reg::Reg,
-    masm::{ExtendKind, IntCmpKind, MacroAssembler, OperandSize, RegImm, SPOffset, TrapCode},
-    stack::TypedReg,
+    masm::{
+        ExtendKind, IntCmpKind, MacroAssembler, OperandSize, RegImm, SPOffset, ShiftKind, TrapCode,
+    },
+    stack::{TypedReg, Val},
 };
 use anyhow::Result;
 use smallvec::SmallVec;
@@ -12,7 +14,7 @@ use wasmparser::{
 };
 use wasmtime_environ::{
     GlobalIndex, MemoryIndex, PtrSize, TableIndex, TypeIndex, WasmHeapType, WasmValType,
-    FUNCREF_MASK,
+    FUNCREF_MASK, WASM_PAGE_SIZE,
 };
 
 use cranelift_codegen::{
@@ -475,9 +477,7 @@ where
         let index = self.context.pop_to_reg(self.masm, None);
         let base = self.context.any_gpr(self.masm);
 
-        let elem_addr =
-            self.masm
-                .table_elem_address(index.into(), base, &table_data, &mut self.context);
+        let elem_addr = self.emit_compute_table_elem_addr(index.into(), base, &table_data);
         self.masm.load_ptr(elem_addr, elem_value);
         // Free the register used as base, once we have loaded the element
         // address into the element value register.
@@ -745,6 +745,132 @@ where
         }
         self.context.free_reg(src);
     }
+
+    /// Loads the address of the table element at a given index. Returns the
+    /// address of the table element using the provided register as base.
+    pub fn emit_compute_table_elem_addr(
+        &mut self,
+        index: Reg,
+        base: Reg,
+        table_data: &TableData,
+    ) -> M::Address {
+        let scratch = <M::ABI as ABI>::scratch_reg();
+        let bound = self.context.any_gpr(self.masm);
+        let tmp = self.context.any_gpr(self.masm);
+        let ptr_size: OperandSize = self.env.ptr_type().into();
+
+        if let Some(offset) = table_data.import_from {
+            // If the table data declares a particular offset base,
+            // load the address into a register to further use it as
+            // the table address.
+            self.masm.load_ptr(self.masm.address_at_vmctx(offset), base);
+        } else {
+            // Else, simply move the vmctx register into the addr register as
+            // the base to calculate the table address.
+            self.masm.mov(vmctx!(M).into(), base, ptr_size);
+        };
+
+        // OOB check.
+        let bound_addr = self
+            .masm
+            .address_at_reg(base, table_data.current_elems_offset);
+        let bound_size = table_data.current_elements_size;
+        self.masm.load(bound_addr, bound, bound_size.into());
+        self.masm.cmp(bound.into(), index, bound_size);
+        self.masm
+            .trapif(IntCmpKind::GeU, TrapCode::TableOutOfBounds);
+
+        // Move the index into the scratch register to calcualte the table
+        // element address.
+        // Moving the value of the index register to the scratch register
+        // also avoids overwriting the context of the index register.
+        self.masm.mov(index.into(), scratch, bound_size);
+        self.masm.mul(
+            scratch,
+            scratch,
+            RegImm::i32(table_data.element_size.bytes() as i32),
+            table_data.element_size,
+        );
+        self.masm
+            .load_ptr(self.masm.address_at_reg(base, table_data.offset), base);
+        // Copy the value of the table base into a temporary register
+        // so that we can use it later in case of a misspeculation.
+        self.masm.mov(base.into(), tmp, ptr_size);
+        // Calculate the address of the table element.
+        self.masm.add(base, base, scratch.into(), ptr_size);
+        if self.env.table_access_spectre_mitigation() {
+            // Perform a bounds check and override the value of the
+            // table element address in case the index is out of bounds.
+            self.masm.cmp(bound.into(), index, OperandSize::S32);
+            self.masm.cmov(tmp, base, IntCmpKind::GeU, ptr_size);
+        }
+        self.context.free_reg(bound);
+        self.context.free_reg(tmp);
+        self.masm.address_at_reg(base, 0)
+    }
+
+    /// Retrieves the size of the table, pushing the result to the value stack.
+    pub fn emit_compute_table_size(&mut self, table_data: &TableData) {
+        let scratch = <M::ABI as ABI>::scratch_reg();
+        let size = self.context.any_gpr(self.masm);
+        let ptr_size: OperandSize = self.env.ptr_type().into();
+
+        if let Some(offset) = table_data.import_from {
+            self.masm
+                .load_ptr(self.masm.address_at_vmctx(offset), scratch);
+        } else {
+            self.masm.mov(vmctx!(M).into(), scratch, ptr_size);
+        };
+
+        let size_addr = self
+            .masm
+            .address_at_reg(scratch, table_data.current_elems_offset);
+        self.masm
+            .load(size_addr, size, table_data.current_elements_size.into());
+
+        self.context.stack.push(TypedReg::i32(size).into());
+    }
+
+    /// Retrieves the size of the memory, pushing the result to the value stack.
+    pub fn emit_compute_memory_size(&mut self, heap_data: &HeapData) {
+        let size_reg = self.context.any_gpr(self.masm);
+        let scratch = <M::ABI as ABI>::scratch_reg();
+
+        let base = if let Some(offset) = heap_data.import_from {
+            self.masm
+                .load_ptr(self.masm.address_at_vmctx(offset), scratch);
+            scratch
+        } else {
+            vmctx!(M)
+        };
+
+        let size_addr = self
+            .masm
+            .address_at_reg(base, heap_data.current_length_offset);
+        self.masm.load_ptr(size_addr, size_reg);
+        // Prepare the stack to emit a shift to get the size in pages rather
+        // than in bytes.
+        self.context
+            .stack
+            .push(TypedReg::new(heap_data.ty, size_reg).into());
+
+        // Since the page size is a power-of-two, verify that 2^16, equals the
+        // defined constant. This is mostly a safeguard in case the constant
+        // value ever changes.
+        let pow = 16;
+        debug_assert_eq!(2u32.pow(pow), WASM_PAGE_SIZE);
+
+        // Ensure that the constant is correctly typed according to the heap
+        // type to reduce register pressure when emitting the shift operation.
+        match heap_data.ty {
+            WasmValType::I32 => self.context.stack.push(Val::i32(pow as i32)),
+            WasmValType::I64 => self.context.stack.push(Val::i64(pow as i64)),
+            _ => unreachable!(),
+        }
+
+        self.masm
+            .shift(&mut self.context, ShiftKind::ShrU, heap_data.ty.into());
+    }
 }
 
 /// Returns the index of the [`ControlStackFrame`] for the given
diff --git a/winch/codegen/src/isa/aarch64/masm.rs b/winch/codegen/src/isa/aarch64/masm.rs
index 8f2d20513..0a106d582 100644
--- a/winch/codegen/src/isa/aarch64/masm.rs
+++ b/winch/codegen/src/isa/aarch64/masm.rs
@@ -1,7 +1,7 @@
 use super::{abi::Aarch64ABI, address::Address, asm::Assembler, regs};
 use crate::{
     abi::{self, local::LocalSlot},
-    codegen::{ptr_type_from_ptr_size, CodeGenContext, FuncEnv, HeapData, TableData},
+    codegen::{ptr_type_from_ptr_size, CodeGenContext, FuncEnv},
     isa::reg::Reg,
     masm::{
         CalleeKind, DivKind, ExtendKind, FloatCmpKind, Imm as I, IntCmpKind,
@@ -111,24 +111,6 @@ impl Masm for MacroAssembler {
         Address::offset(reg, offset as i64)
     }
 
-    fn table_elem_address(
-        &mut self,
-        _index: Reg,
-        _base: Reg,
-        _table_data: &TableData,
-        _context: &mut CodeGenContext,
-    ) -> Self::Address {
-        todo!()
-    }
-
-    fn table_size(&mut self, _table_data: &TableData, _context: &mut CodeGenContext) {
-        todo!()
-    }
-
-    fn memory_size(&mut self, _heap_data: &HeapData, _context: &mut CodeGenContext) {
-        todo!()
-    }
-
     fn address_from_sp(&self, _offset: SPOffset) -> Self::Address {
         todo!()
     }
diff --git a/winch/codegen/src/isa/x64/masm.rs b/winch/codegen/src/isa/x64/masm.rs
index 4b68dde4a..2300253b7 100644
--- a/winch/codegen/src/isa/x64/masm.rs
+++ b/winch/codegen/src/isa/x64/masm.rs
@@ -11,13 +11,12 @@ use crate::masm::{
 };
 use crate::{
     abi::{self, align_to, calculate_frame_adjustment, LocalSlot},
-    codegen::{ptr_type_from_ptr_size, CodeGenContext, FuncEnv, HeapData, TableData},
+    codegen::{ptr_type_from_ptr_size, CodeGenContext, FuncEnv},
     stack::Val,
 };
 use crate::{
     abi::{vmctx, ABI},
     masm::{SPOffset, StackSlot},
-    stack::TypedReg,
 };
 use crate::{
     isa::reg::{Reg, RegClass},
@@ -34,7 +33,7 @@ use cranelift_codegen::{
     settings, Final, MachBufferFinalized, MachLabel,
 };
 
-use wasmtime_environ::{PtrSize, WasmValType, WASM_PAGE_SIZE};
+use wasmtime_environ::{PtrSize, WasmValType};
 
 /// x64 MacroAssembler.
 pub(crate) struct MacroAssembler {
@@ -180,141 +179,6 @@ impl Masm for MacroAssembler {
         Address::offset(reg, offset)
     }
 
-    fn table_elem_address(
-        &mut self,
-        index: Reg,
-        ptr_base: Reg,
-        table_data: &TableData,
-        context: &mut CodeGenContext,
-    ) -> Self::Address {
-        let scratch = regs::scratch();
-        let bound = context.any_gpr(self);
-        let tmp = context.any_gpr(self);
-
-        if let Some(offset) = table_data.import_from {
-            // If the table data declares a particular offset base,
-            // load the address into a register to further use it as
-            // the table address.
-            self.asm.movzx_mr(
-                &self.address_at_vmctx(offset),
-                ptr_base,
-                self.ptr_size.into(),
-                TRUSTED_FLAGS,
-            );
-        } else {
-            // Else, simply move the vmctx register into the addr register as
-            // the base to calculate the table address.
-            self.asm.mov_rr(vmctx!(Self), ptr_base, self.ptr_size);
-        };
-
-        // OOB check.
-        let bound_addr = self.address_at_reg(ptr_base, table_data.current_elems_offset);
-        let bound_size = table_data.current_elements_size;
-        self.asm
-            .movzx_mr(&bound_addr, bound, bound_size.into(), TRUSTED_FLAGS);
-        self.asm.cmp_rr(bound, index, bound_size);
-        self.asm.trapif(IntCmpKind::GeU, TrapCode::TableOutOfBounds);
-
-        // Move the index into the scratch register to calcualte the table
-        // element address.
-        // Moving the value of the index register to the scratch register
-        // also avoids overwriting the context of the index register.
-        self.asm.mov_rr(index, scratch, bound_size);
-        self.asm.mul_ir(
-            table_data.element_size.bytes() as i32,
-            scratch,
-            table_data.element_size,
-        );
-        self.asm.movzx_mr(
-            &self.address_at_reg(ptr_base, table_data.offset),
-            ptr_base,
-            self.ptr_size.into(),
-            TRUSTED_FLAGS,
-        );
-        // Copy the value of the table base into a temporary register
-        // so that we can use it later in case of a misspeculation.
-        self.asm.mov_rr(ptr_base, tmp, self.ptr_size);
-        // Calculate the address of the table element.
-        self.asm.add_rr(scratch, ptr_base, self.ptr_size);
-        if self.shared_flags.enable_table_access_spectre_mitigation() {
-            // Perform a bounds check and override the value of the
-            // table element address in case the index is out of bounds.
-            self.asm.cmp_rr(bound, index, OperandSize::S32);
-            self.asm.cmov(tmp, ptr_base, IntCmpKind::GeU, self.ptr_size);
-        }
-        context.free_reg(bound);
-        context.free_reg(tmp);
-        self.address_at_reg(ptr_base, 0)
-    }
-
-    fn table_size(&mut self, table_data: &TableData, context: &mut CodeGenContext) {
-        let scratch = regs::scratch();
-        let size = context.any_gpr(self);
-
-        if let Some(offset) = table_data.import_from {
-            self.asm.movzx_mr(
-                &self.address_at_vmctx(offset),
-                scratch,
-                self.ptr_size.into(),
-                TRUSTED_FLAGS,
-            );
-        } else {
-            self.asm.mov_rr(vmctx!(Self), scratch, self.ptr_size);
-        };
-
-        let size_addr = Address::offset(scratch, table_data.current_elems_offset);
-        self.asm.movzx_mr(
-            &size_addr,
-            size,
-            table_data.current_elements_size.into(),
-            TRUSTED_FLAGS,
-        );
-
-        context.stack.push(TypedReg::i32(size).into());
-    }
-
-    fn memory_size(&mut self, heap_data: &HeapData, context: &mut CodeGenContext) {
-        let size_reg = context.any_gpr(self);
-        let scratch = regs::scratch();
-
-        let base = if let Some(offset) = heap_data.import_from {
-            self.asm.movzx_mr(
-                &self.address_at_vmctx(offset),
-                scratch,
-                self.ptr_size.into(),
-                TRUSTED_FLAGS,
-            );
-            scratch
-        } else {
-            vmctx!(Self)
-        };
-
-        let size_addr = Address::offset(base, heap_data.current_length_offset);
-        self.asm
-            .movzx_mr(&size_addr, size_reg, self.ptr_size.into(), TRUSTED_FLAGS);
-        // Prepare the stack to emit a shift to get the size in pages rather
-        // than in bytes.
-        context
-            .stack
-            .push(TypedReg::new(heap_data.ty, size_reg).into());
-
-        // Since the page size is a power-of-two, verify that 2^16, equals the
-        // defined constant. This is mostly a safeguard in case the constant
-        // value ever changes.
-        let pow = 16;
-        debug_assert_eq!(2u32.pow(pow), WASM_PAGE_SIZE);
-
-        // Ensure that the constant is correctly typed according to the heap
-        // type to reduce register pressure when emitting the shift operation.
-        match heap_data.ty {
-            WasmValType::I32 => context.stack.push(Val::i32(pow as i32)),
-            WasmValType::I64 => context.stack.push(Val::i64(pow as i64)),
-            _ => unreachable!(),
-        }
-
-        self.shift(context, ShiftKind::ShrU, heap_data.ty.into());
-    }
-
     fn address_from_sp(&self, offset: SPOffset) -> Self::Address {
         Address::offset(regs::rsp(), self.sp_offset - offset.as_u32())
     }
diff --git a/winch/codegen/src/masm.rs b/winch/codegen/src/masm.rs
index e04cf5cda..5b86dc8fd 100644
--- a/winch/codegen/src/masm.rs
+++ b/winch/codegen/src/masm.rs
@@ -1,5 +1,5 @@
 use crate::abi::{self, align_to, LocalSlot};
-use crate::codegen::{CodeGenContext, FuncEnv, HeapData, TableData};
+use crate::codegen::{CodeGenContext, FuncEnv};
 use crate::isa::reg::Reg;
 use cranelift_codegen::{
     binemit::CodeOffset,
@@ -503,22 +503,6 @@ pub(crate) trait MacroAssembler {
     /// Get the address of a local slot.
     fn local_address(&mut self, local: &LocalSlot) -> Self::Address;
 
-    /// Loads the address of the table element at a given index. Returns the
-    /// address of the table element using the provided register as base.
-    fn table_elem_address(
-        &mut self,
-        index: Reg,
-        base: Reg,
-        table_data: &TableData,
-        context: &mut CodeGenContext,
-    ) -> Self::Address;
-
-    /// Retrieves the size of the table, pushing the result to the value stack.
-    fn table_size(&mut self, table_data: &TableData, context: &mut CodeGenContext);
-
-    /// Retrieves the size of the memory, pushing the result to the value stack.
-    fn memory_size(&mut self, heap_data: &HeapData, context: &mut CodeGenContext);
-
     /// Constructs an address with an offset that is relative to the
     /// current position of the stack pointer (e.g. [sp + (sp_offset -
     /// offset)].
diff --git a/winch/codegen/src/visitor.rs b/winch/codegen/src/visitor.rs
index 688e5b4bf..c02abb763 100644
--- a/winch/codegen/src/visitor.rs
+++ b/winch/codegen/src/visitor.rs
@@ -1469,7 +1469,7 @@ where
     fn visit_table_size(&mut self, table: u32) {
         let table_index = TableIndex::from_u32(table);
         let table_data = self.env.resolve_table_data(table_index);
-        self.masm.table_size(&table_data, &mut self.context);
+        self.emit_compute_table_size(&table_data);
     }
 
     fn visit_table_fill(&mut self, table: u32) {
@@ -1505,13 +1505,8 @@ where
                     let value = self.context.pop_to_reg(self.masm, None);
                     let index = self.context.pop_to_reg(self.masm, None);
                     let base = self.context.any_gpr(self.masm);
-                    let elem_addr = self.masm.table_elem_address(
-                        index.into(),
-                        base,
-                        &table_data,
-                        &mut self.context,
-                    );
-
+                    let elem_addr =
+                        self.emit_compute_table_elem_addr(index.into(), base, &table_data);
                     // Set the initialized bit.
                     self.masm.or(
                         value.into(),
@@ -1605,7 +1600,7 @@ where
 
     fn visit_memory_size(&mut self, mem: u32, _: u8) {
         let heap = self.env.resolve_heap(MemoryIndex::from_u32(mem));
-        self.masm.memory_size(&heap, &mut self.context);
+        self.emit_compute_memory_size(&heap);
     }
 
     fn visit_memory_grow(&mut self, mem: u32, _: u8) {


Test patch:
diff --git a/cranelift/filetests/filetests/egraph/bitcast-ref.clif b/cranelift/filetests/filetests/egraph/bitcast-ref.clif
new file mode 100644
index 000000000..e6bb67818
--- /dev/null
+++ b/cranelift/filetests/filetests/egraph/bitcast-ref.clif
@@ -0,0 +1,71 @@
+test optimize precise-output
+set opt_level=speed
+target x86_64
+
+function %bitcast_from_r64(i64 vmctx, r64) -> i32 fast {
+    sig0 = (i32) fast
+    fn0 = colocated %foo sig0
+
+block0(v0: i64, v1: r64):
+    v2 = bitcast.i64 v1
+    v3 = ireduce.i32 v2
+
+    call fn0(v3)
+
+    ;; We MUST NOT dedupe the bitcast *from* the reference type, as that would
+    ;; make `v1` no longer live across the `call`, which would mean that it
+    ;; would not show up in the `call`'s safepoint's stack map, which could
+    ;; result in the collector reclaiming an object too early, which is basically
+    ;; a use-after-free bug.
+    v4 = bitcast.i64 v1
+    v5 = ireduce.i32 v4
+
+    return v5
+}
+
+; function %bitcast_from_r64(i64 vmctx, r64) -> i32 fast {
+;     sig0 = (i32) fast
+;     fn0 = colocated %foo sig0
+;
+; block0(v0: i64, v1: r64):
+;     v2 = bitcast.i64 v1
+;     v3 = ireduce.i32 v2
+;     call fn0(v3)
+;     v4 = bitcast.i64 v1
+;     v5 = ireduce.i32 v4
+;     return v5
+; }
+
+function %bitcast_to_r64(i64 vmctx, i32) -> r64 fast {
+    sig0 = (r64) fast
+    fn0 = colocated %foo sig0
+
+block0(v0: i64, v1: i32):
+    v2 = uextend.i64 v1
+    v3 = bitcast.r64 v2
+
+    call fn0(v3)
+
+    ;; On the other hand, it is technically fine to dedupe bitcasts *to*
+    ;; reference types. Doing so extends the live range of the GC reference --
+    ;; potentially adding it to more stack maps than it otherwise would have
+    ;; been in, which means it might unnecessarily survive a GC it otherwise
+    ;; wouldn't have -- but that is okay. Shrinking live ranges of GC
+    ;; references, and removing them from stack maps they otherwise would have
+    ;; been in, is the problematic transformation.
+    v4 = uextend.i64 v1
+    v5 = bitcast.r64 v4
+
+    return v5
+}
+
+; function %bitcast_to_r64(i64 vmctx, i32) -> r64 fast {
+;     sig0 = (r64) fast
+;     fn0 = colocated %foo sig0
+;
+; block0(v0: i64, v1: i32):
+;     v2 = uextend.i64 v1
+;     v3 = bitcast.r64 v2
+;     call fn0(v3)
+;     return v3
+; }
diff --git a/cranelift/filetests/filetests/isa/aarch64/nan-canonicalization.clif b/cranelift/filetests/filetests/isa/aarch64/nan-canonicalization.clif
new file mode 100644
index 000000000..8ffffcdd2
--- /dev/null
+++ b/cranelift/filetests/filetests/isa/aarch64/nan-canonicalization.clif
@@ -0,0 +1,140 @@
+test compile precise-output
+set enable_nan_canonicalization=true 
+target x86_64 sse41
+
+function %f0(f32x4, f32x4) -> f32x4 {
+block0(v0: f32x4, v1: f32x4):
+	v2 = fadd v0, v1
+	return v2
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   addps   %xmm0, %xmm1, %xmm0
+;   movl    $2143289344, %r10d
+;   movd    %r10d, %xmm7
+;   shufps  $0, %xmm7, const(0), %xmm7
+;   movdqa  %xmm0, %xmm1
+;   cmpps   $3, %xmm1, %xmm0, %xmm1
+;   movdqa  %xmm0, %xmm2
+;   movdqa  %xmm1, %xmm0
+;   movdqa  %xmm2, %xmm1
+;   pblendvb %xmm1, %xmm7, %xmm1
+;   movdqa  %xmm1, %xmm0
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   addps %xmm1, %xmm0
+;   movl $0x7fc00000, %r10d
+;   movd %r10d, %xmm7
+;   shufps $0, 0x26(%rip), %xmm7
+;   movdqa %xmm0, %xmm1
+;   cmpunordps %xmm0, %xmm1
+;   movdqa %xmm0, %xmm2
+;   movdqa %xmm1, %xmm0
+;   movdqa %xmm2, %xmm1
+;   pblendvb %xmm0, %xmm7, %xmm1
+;   movdqa %xmm1, %xmm0
+;   movq %rbp, %rsp
+;   popq %rbp
+;   retq
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   sarb $0, (%rdi)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+
+function %f1(f64, f64) -> f64 {
+block0(v0: f64, v1: f64):
+	v2 = fadd v0, v1
+	return v2
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   addsd   %xmm0, %xmm1, %xmm0
+;   movabsq $9221120237041090560, %r9
+;   movq    %r9, %xmm1
+;   movdqa  %xmm0, %xmm7
+;   cmppd   $3, %xmm7, %xmm0, %xmm7
+;   movdqa  %xmm0, %xmm5
+;   movdqa  %xmm7, %xmm0
+;   pblendvb %xmm5, %xmm1, %xmm5
+;   movdqa  %xmm5, %xmm0
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   addsd %xmm1, %xmm0
+;   movabsq $0x7ff8000000000000, %r9
+;   movq %r9, %xmm1
+;   movdqa %xmm0, %xmm7
+;   cmpunordpd %xmm0, %xmm7
+;   movdqa %xmm0, %xmm5
+;   movdqa %xmm7, %xmm0
+;   pblendvb %xmm0, %xmm1, %xmm5
+;   movdqa %xmm5, %xmm0
+;   movq %rbp, %rsp
+;   popq %rbp
+;   retq
+
+function %f1(f32, f32) -> f32 {
+block0(v0: f32, v1: f32):
+	v2 = fadd v0, v1
+	return v2
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   addss   %xmm0, %xmm1, %xmm0
+;   movl    $2143289344, %r9d
+;   movd    %r9d, %xmm1
+;   movdqa  %xmm0, %xmm7
+;   cmpps   $3, %xmm7, %xmm0, %xmm7
+;   movdqa  %xmm0, %xmm5
+;   movdqa  %xmm7, %xmm0
+;   pblendvb %xmm5, %xmm1, %xmm5
+;   movdqa  %xmm5, %xmm0
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   addss %xmm1, %xmm0
+;   movl $0x7fc00000, %r9d
+;   movd %r9d, %xmm1
+;   movdqa %xmm0, %xmm7
+;   cmpunordps %xmm0, %xmm7
+;   movdqa %xmm0, %xmm5
+;   movdqa %xmm7, %xmm0
+;   pblendvb %xmm0, %xmm1, %xmm5
+;   movdqa %xmm5, %xmm0
+;   movq %rbp, %rsp
+;   popq %rbp
+;   retq
+
diff --git a/cranelift/filetests/filetests/isa/riscv64/nan-canonicalization-has_v.clif b/cranelift/filetests/filetests/isa/riscv64/nan-canonicalization-has_v.clif
new file mode 100644
index 000000000..0a9a82457
--- /dev/null
+++ b/cranelift/filetests/filetests/isa/riscv64/nan-canonicalization-has_v.clif
@@ -0,0 +1,160 @@
+test compile precise-output
+set enable_nan_canonicalization=true
+target riscv64 has_v
+
+function %f0(f32x4, f32x4) -> f32x4 {
+block0(v0: f32x4, v1: f32x4):
+	v2 = fadd v0, v1
+	return v2
+}
+
+; VCode:
+;   addi sp,sp,-16
+;   sd ra,8(sp)
+;   sd fp,0(sp)
+;   mv fp,sp
+; block0:
+;   vle8.v v9,16(fp) #avl=16, #vtype=(e8, m1, ta, ma)
+;   vle8.v v11,32(fp) #avl=16, #vtype=(e8, m1, ta, ma)
+;   vfadd.vv v13,v9,v11 #avl=4, #vtype=(e32, m1, ta, ma)
+;   lui a1,523264
+;   fmv.w.x fa2,a1
+;   vfmv.v.f v14,fa2 #avl=4, #vtype=(e32, m1, ta, ma)
+;   vmfne.vv v10,v13,v13 #avl=4, #vtype=(e32, m1, ta, ma)
+;   vmfne.vv v12,v13,v13 #avl=4, #vtype=(e32, m1, ta, ma)
+;   vmor.mm v0,v10,v12 #avl=4, #vtype=(e32, m1, ta, ma)
+;   vmerge.vvm v8,v13,v14,v0.t #avl=4, #vtype=(e32, m1, ta, ma)
+;   vse8.v v8,0(a0) #avl=16, #vtype=(e8, m1, ta, ma)
+;   ld ra,8(sp)
+;   ld fp,0(sp)
+;   addi sp,sp,16
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   addi sp, sp, -0x10
+;   sd ra, 8(sp)
+;   sd s0, 0(sp)
+;   mv s0, sp
+; block1: ; offset 0x10
+;   .byte 0x57, 0x70, 0x08, 0xcc
+;   addi t6, s0, 0x10
+;   .byte 0x87, 0x84, 0x0f, 0x02
+;   addi t6, s0, 0x20
+;   .byte 0x87, 0x85, 0x0f, 0x02
+;   .byte 0x57, 0x70, 0x02, 0xcd
+;   .byte 0xd7, 0x96, 0x95, 0x02
+;   lui a1, 0x7fc00
+;   fmv.w.x fa2, a1
+;   .byte 0x57, 0x57, 0x06, 0x5e
+;   .byte 0x57, 0x95, 0xd6, 0x72
+;   .byte 0x57, 0x96, 0xd6, 0x72
+;   .byte 0x57, 0x20, 0xa6, 0x6a
+;   .byte 0x57, 0x04, 0xd7, 0x5c
+;   .byte 0x57, 0x70, 0x08, 0xcc
+;   .byte 0x27, 0x04, 0x05, 0x02
+;   ld ra, 8(sp)
+;   ld s0, 0(sp)
+;   addi sp, sp, 0x10
+;   ret
+
+function %f1(f64, f64) -> f64 {
+block0(v0: f64, v1: f64):
+	v2 = fadd v0, v1
+	return v2
+}
+
+; VCode:
+; block0:
+;   fadd.d fa1,fa0,fa1,rne
+;   lui a5,4095
+;   slli a1,a5,39
+;   fmv.d.x fa3,a1
+;   vmv.v.x v8,zero #avl=2, #vtype=(e64, m1, ta, ma)
+;   vfmv.s.f v10,fa3 #avl=2, #vtype=(e64, m1, ta, ma)
+;   vmv.v.i v0,1 #avl=2, #vtype=(e64, m1, ta, ma)
+;   vmerge.vvm v14,v8,v10,v0.t #avl=2, #vtype=(e64, m1, ta, ma)
+;   vmv.v.x v8,zero #avl=2, #vtype=(e64, m1, ta, ma)
+;   vfmv.s.f v10,fa1 #avl=2, #vtype=(e64, m1, ta, ma)
+;   vmv.v.i v0,1 #avl=2, #vtype=(e64, m1, ta, ma)
+;   vmerge.vvm v15,v8,v10,v0.t #avl=2, #vtype=(e64, m1, ta, ma)
+;   vmfne.vv v8,v15,v15 #avl=2, #vtype=(e64, m1, ta, ma)
+;   vmfne.vv v10,v15,v15 #avl=2, #vtype=(e64, m1, ta, ma)
+;   vmor.mm v0,v8,v10 #avl=2, #vtype=(e64, m1, ta, ma)
+;   vmerge.vvm v8,v15,v14,v0.t #avl=2, #vtype=(e64, m1, ta, ma)
+;   vfmv.f.s fa0,v8 #avl=2, #vtype=(e64, m1, ta, ma)
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   fadd.d fa1, fa0, fa1, rne
+;   lui a5, 0xfff
+;   slli a1, a5, 0x27
+;   fmv.d.x fa3, a1
+;   .byte 0x57, 0x70, 0x81, 0xcd
+;   .byte 0x57, 0x44, 0x00, 0x5e
+;   .byte 0x57, 0xd5, 0x06, 0x42
+;   .byte 0x57, 0xb0, 0x00, 0x5e
+;   .byte 0x57, 0x07, 0x85, 0x5c
+;   .byte 0x57, 0x44, 0x00, 0x5e
+;   .byte 0x57, 0xd5, 0x05, 0x42
+;   .byte 0x57, 0xb0, 0x00, 0x5e
+;   .byte 0xd7, 0x07, 0x85, 0x5c
+;   .byte 0x57, 0x94, 0xf7, 0x72
+;   .byte 0x57, 0x95, 0xf7, 0x72
+;   .byte 0x57, 0x20, 0x85, 0x6a
+;   .byte 0x57, 0x04, 0xf7, 0x5c
+;   .byte 0x57, 0x15, 0x80, 0x42
+;   ret
+
+function %f1(f32, f32) -> f32 {
+block0(v0: f32, v1: f32):
+	v2 = fadd v0, v1
+	return v2
+}
+
+; VCode:
+; block0:
+;   fadd.s fa0,fa0,fa1,rne
+;   lui a5,523264
+;   fmv.w.x fa1,a5
+;   vmv.v.x v15,zero #avl=4, #vtype=(e32, m1, ta, ma)
+;   vfmv.s.f v9,fa1 #avl=4, #vtype=(e32, m1, ta, ma)
+;   vmv.v.i v0,1 #avl=2, #vtype=(e64, m1, ta, ma)
+;   vmerge.vvm v13,v15,v9,v0.t #avl=4, #vtype=(e32, m1, ta, ma)
+;   vmv.v.x v15,zero #avl=4, #vtype=(e32, m1, ta, ma)
+;   vfmv.s.f v9,fa0 #avl=4, #vtype=(e32, m1, ta, ma)
+;   vmv.v.i v0,1 #avl=2, #vtype=(e64, m1, ta, ma)
+;   vmerge.vvm v14,v15,v9,v0.t #avl=4, #vtype=(e32, m1, ta, ma)
+;   vmfne.vv v15,v14,v14 #avl=4, #vtype=(e32, m1, ta, ma)
+;   vmfne.vv v9,v14,v14 #avl=4, #vtype=(e32, m1, ta, ma)
+;   vmor.mm v0,v15,v9 #avl=4, #vtype=(e32, m1, ta, ma)
+;   vmerge.vvm v15,v14,v13,v0.t #avl=4, #vtype=(e32, m1, ta, ma)
+;   vfmv.f.s fa0,v15 #avl=4, #vtype=(e32, m1, ta, ma)
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   fadd.s fa0, fa0, fa1, rne
+;   lui a5, 0x7fc00
+;   fmv.w.x fa1, a5
+;   .byte 0x57, 0x70, 0x02, 0xcd
+;   .byte 0xd7, 0x47, 0x00, 0x5e
+;   .byte 0xd7, 0xd4, 0x05, 0x42
+;   .byte 0x57, 0x70, 0x81, 0xcd
+;   .byte 0x57, 0xb0, 0x00, 0x5e
+;   .byte 0x57, 0x70, 0x02, 0xcd
+;   .byte 0xd7, 0x86, 0xf4, 0x5c
+;   .byte 0xd7, 0x47, 0x00, 0x5e
+;   .byte 0xd7, 0x54, 0x05, 0x42
+;   .byte 0x57, 0x70, 0x81, 0xcd
+;   .byte 0x57, 0xb0, 0x00, 0x5e
+;   .byte 0x57, 0x70, 0x02, 0xcd
+;   .byte 0x57, 0x87, 0xf4, 0x5c
+;   .byte 0xd7, 0x17, 0xe7, 0x72
+;   .byte 0xd7, 0x14, 0xe7, 0x72
+;   .byte 0x57, 0xa0, 0xf4, 0x6a
+;   .byte 0xd7, 0x87, 0xe6, 0x5c
+;   .byte 0x57, 0x15, 0xf0, 0x42
+;   ret
+
diff --git a/cranelift/filetests/filetests/isa/riscv64/nan-canonicalization.clif b/cranelift/filetests/filetests/isa/riscv64/nan-canonicalization.clif
new file mode 100644
index 000000000..8f74386af
--- /dev/null
+++ b/cranelift/filetests/filetests/isa/riscv64/nan-canonicalization.clif
@@ -0,0 +1,66 @@
+test compile precise-output
+set enable_nan_canonicalization=true 
+target riscv64
+
+function %f1(f64, f64) -> f64 {
+block0(v0: f64, v1: f64):
+	v2 = fadd v0, v1
+	return v2
+}
+
+; VCode:
+; block0:
+;   fadd.d fa4,fa0,fa1,rne
+;   lui a2,4095
+;   slli a4,a2,39
+;   fmv.d.x fa0,a4
+;   feq.d a1,fa4,fa4
+;   feq.d a3,fa4,fa4
+;   and a5,a1,a3
+;   select fa0,fa0,fa4##condition=(a5 eq zero)
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   fadd.d fa4, fa0, fa1, rne
+;   lui a2, 0xfff
+;   slli a4, a2, 0x27
+;   fmv.d.x fa0, a4
+;   feq.d a1, fa4, fa4
+;   feq.d a3, fa4, fa4
+;   and a5, a1, a3
+;   beqz a5, 8
+;   fmv.d fa0, fa4
+;   ret
+
+function %f1(f32, f32) -> f32 {
+block0(v0: f32, v1: f32):
+	v2 = fadd v0, v1
+	return v2
+}
+
+; VCode:
+; block0:
+;   fadd.s fa3,fa0,fa1,rne
+;   lui a2,523264
+;   fmv.w.x fa4,a2
+;   feq.s a0,fa3,fa3
+;   feq.s a2,fa3,fa3
+;   and a4,a0,a2
+;   select fa0,fa4,fa3##condition=(a4 eq zero)
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   fadd.s fa3, fa0, fa1, rne
+;   lui a2, 0x7fc00
+;   fmv.w.x fa4, a2
+;   feq.s a0, fa3, fa3
+;   feq.s a2, fa3, fa3
+;   and a4, a0, a2
+;   bnez a4, 0xc
+;   fmv.d fa0, fa4
+;   j 8
+;   fmv.d fa0, fa3
+;   ret
+
diff --git a/cranelift/filetests/filetests/isa/s390x/nan-canonicalization.clif b/cranelift/filetests/filetests/isa/s390x/nan-canonicalization.clif
new file mode 100644
index 000000000..f2619125d
--- /dev/null
+++ b/cranelift/filetests/filetests/isa/s390x/nan-canonicalization.clif
@@ -0,0 +1,112 @@
+test compile precise-output
+set enable_nan_canonicalization=true 
+target s390x
+
+function %f0(f32x4, f32x4) -> f32x4 {
+block0(v0: f32x4, v1: f32x4):
+	v2 = fadd v0, v1
+	return v2
+}
+
+; VCode:
+; block0:
+;   vfasb %v17, %v24, %v25
+;   bras %r1, 8 ; data.f32 NaN ; vlef %v18, 0(%r1), 0
+;   vrepf %v18, %v18, 0
+;   vfchesb %v7, %v17, %v17
+;   vfchesb %v19, %v17, %v17
+;   vno %v19, %v7, %v19
+;   vsel %v24, %v18, %v17, %v19
+;   br %r14
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   vfasb %v17, %v24, %v25
+;   bras %r1, 0xe
+;   su %f12, 0
+;   vlef %v18, 0(%r1), 0
+;   vrepf %v18, %v18, 0
+;   vfchesb %v7, %v17, %v17
+;   vfchesb %v19, %v17, %v17
+;   vno %v19, %v7, %v19
+;   vsel %v24, %v18, %v17, %v19
+;   br %r14
+
+function %f1(f64, f64) -> f64 {
+block0(v0: f64, v1: f64):
+	v2 = fadd v0, v1
+	return v2
+}
+
+; VCode:
+; block0:
+;   wfadb %v21, %f0, %f2
+;   bras %r1, 12 ; data.f64 NaN ; vleg %v22, 0(%r1), 0
+;   vgbm %v20, 0
+;   vpdi %v22, %v22, %v20, 0
+;   vgbm %v20, 0
+;   vpdi %v23, %v21, %v20, 0
+;   vfchedb %v19, %v23, %v23
+;   vfchedb %v21, %v23, %v23
+;   vno %v24, %v19, %v21
+;   vsel %v21, %v22, %v23, %v24
+;   vrepg %v0, %v21, 0
+;   br %r14
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   wfadb %v21, %f0, %f2
+;   bras %r1, 0x12
+;   su %f15, 0(%r8)
+;   .byte 0x00, 0x00
+;   .byte 0x00, 0x00
+;   vleg %v22, 0(%r1), 0
+;   vzero %v20
+;   vpdi %v22, %v22, %v20, 0
+;   vzero %v20
+;   vpdi %v23, %v21, %v20, 0
+;   vfchedb %v19, %v23, %v23
+;   vfchedb %v21, %v23, %v23
+;   vno %v24, %v19, %v21
+;   vsel %v21, %v22, %v23, %v24
+;   vrepg %v0, %v21, 0
+;   br %r14
+
+function %f1(f32, f32) -> f32 {
+block0(v0: f32, v1: f32):
+	v2 = fadd v0, v1
+	return v2
+}
+
+; VCode:
+; block0:
+;   wfasb %v21, %f0, %f2
+;   bras %r1, 8 ; data.f32 NaN ; vlef %v22, 0(%r1), 0
+;   vgbm %v20, 61440
+;   vn %v22, %v22, %v20
+;   vgbm %v20, 61440
+;   vn %v23, %v21, %v20
+;   vfchesb %v19, %v23, %v23
+;   vfchesb %v21, %v23, %v23
+;   vno %v24, %v19, %v21
+;   vsel %v21, %v22, %v23, %v24
+;   vrepf %v0, %v21, 0
+;   br %r14
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   wfasb %v21, %f0, %f2
+;   bras %r1, 0xe
+;   su %f12, 0
+;   vlef %v22, 0(%r1), 0
+;   vgbm %v20, 0xf000
+;   vn %v22, %v22, %v20
+;   vgbm %v20, 0xf000
+;   vn %v23, %v21, %v20
+;   vfchesb %v19, %v23, %v23
+;   vfchesb %v21, %v23, %v23
+;   vno %v24, %v19, %v21
+;   vsel %v21, %v22, %v23, %v24
+;   vrepf %v0, %v21, 0
+;   br %r14
+
diff --git a/cranelift/filetests/filetests/isa/x64/nan-canonicalization-sse41.clif b/cranelift/filetests/filetests/isa/x64/nan-canonicalization-sse41.clif
new file mode 100644
index 000000000..b29003307
--- /dev/null
+++ b/cranelift/filetests/filetests/isa/x64/nan-canonicalization-sse41.clif
@@ -0,0 +1,140 @@
+test compile precise-output
+set enable_nan_canonicalization=true
+target x86_64 sse41
+
+function %f0(f32x4, f32x4) -> f32x4 {
+block0(v0: f32x4, v1: f32x4):
+	v2 = fadd v0, v1
+	return v2
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   addps   %xmm0, %xmm1, %xmm0
+;   movl    $2143289344, %r10d
+;   movd    %r10d, %xmm7
+;   shufps  $0, %xmm7, const(0), %xmm7
+;   movdqa  %xmm0, %xmm1
+;   cmpps   $3, %xmm1, %xmm0, %xmm1
+;   movdqa  %xmm0, %xmm2
+;   movdqa  %xmm1, %xmm0
+;   movdqa  %xmm2, %xmm1
+;   pblendvb %xmm1, %xmm7, %xmm1
+;   movdqa  %xmm1, %xmm0
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   addps %xmm1, %xmm0
+;   movl $0x7fc00000, %r10d
+;   movd %r10d, %xmm7
+;   shufps $0, 0x26(%rip), %xmm7
+;   movdqa %xmm0, %xmm1
+;   cmpunordps %xmm0, %xmm1
+;   movdqa %xmm0, %xmm2
+;   movdqa %xmm1, %xmm0
+;   movdqa %xmm2, %xmm1
+;   pblendvb %xmm0, %xmm7, %xmm1
+;   movdqa %xmm1, %xmm0
+;   movq %rbp, %rsp
+;   popq %rbp
+;   retq
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   sarb $0, (%rdi)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+
+function %f1(f64, f64) -> f64 {
+block0(v0: f64, v1: f64):
+	v2 = fadd v0, v1
+	return v2
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   addsd   %xmm0, %xmm1, %xmm0
+;   movabsq $9221120237041090560, %r9
+;   movq    %r9, %xmm1
+;   movdqa  %xmm0, %xmm7
+;   cmppd   $3, %xmm7, %xmm0, %xmm7
+;   movdqa  %xmm0, %xmm5
+;   movdqa  %xmm7, %xmm0
+;   pblendvb %xmm5, %xmm1, %xmm5
+;   movdqa  %xmm5, %xmm0
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   addsd %xmm1, %xmm0
+;   movabsq $0x7ff8000000000000, %r9
+;   movq %r9, %xmm1
+;   movdqa %xmm0, %xmm7
+;   cmpunordpd %xmm0, %xmm7
+;   movdqa %xmm0, %xmm5
+;   movdqa %xmm7, %xmm0
+;   pblendvb %xmm0, %xmm1, %xmm5
+;   movdqa %xmm5, %xmm0
+;   movq %rbp, %rsp
+;   popq %rbp
+;   retq
+
+function %f1(f32, f32) -> f32 {
+block0(v0: f32, v1: f32):
+	v2 = fadd v0, v1
+	return v2
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   addss   %xmm0, %xmm1, %xmm0
+;   movl    $2143289344, %r9d
+;   movd    %r9d, %xmm1
+;   movdqa  %xmm0, %xmm7
+;   cmpps   $3, %xmm7, %xmm0, %xmm7
+;   movdqa  %xmm0, %xmm5
+;   movdqa  %xmm7, %xmm0
+;   pblendvb %xmm5, %xmm1, %xmm5
+;   movdqa  %xmm5, %xmm0
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   addss %xmm1, %xmm0
+;   movl $0x7fc00000, %r9d
+;   movd %r9d, %xmm1
+;   movdqa %xmm0, %xmm7
+;   cmpunordps %xmm0, %xmm7
+;   movdqa %xmm0, %xmm5
+;   movdqa %xmm7, %xmm0
+;   pblendvb %xmm0, %xmm1, %xmm5
+;   movdqa %xmm5, %xmm0
+;   movq %rbp, %rsp
+;   popq %rbp
+;   retq
+
diff --git a/cranelift/filetests/filetests/isa/x64/nan-canonicalization.clif b/cranelift/filetests/filetests/isa/x64/nan-canonicalization.clif
new file mode 100644
index 000000000..7454b82fa
--- /dev/null
+++ b/cranelift/filetests/filetests/isa/x64/nan-canonicalization.clif
@@ -0,0 +1,131 @@
+test compile precise-output
+set enable_nan_canonicalization=true 
+target x86_64
+
+function %f0(f32x4, f32x4) -> f32x4 {
+block0(v0: f32x4, v1: f32x4):
+	v2 = fadd v0, v1
+	return v2
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   addps   %xmm0, %xmm1, %xmm0
+;   movdqa  %xmm0, %xmm1
+;   movl    $2143289344, %esi
+;   movd    %esi, %xmm5
+;   shufps  $0, %xmm5, const(0), %xmm5
+;   cmpps   $3, %xmm0, %xmm1, %xmm0
+;   andps   %xmm5, %xmm0, %xmm5
+;   andnps  %xmm0, %xmm1, %xmm0
+;   orps    %xmm0, %xmm5, %xmm0
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   addps %xmm1, %xmm0
+;   movdqa %xmm0, %xmm1
+;   movl $0x7fc00000, %esi
+;   movd %esi, %xmm5
+;   shufps $0, 0x14(%rip), %xmm5
+;   cmpunordps %xmm1, %xmm0
+;   andps %xmm0, %xmm5
+;   andnps %xmm1, %xmm0
+;   orps %xmm5, %xmm0
+;   movq %rbp, %rsp
+;   popq %rbp
+;   retq
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   sarb $0, (%rdi)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+
+function %f1(f64, f64) -> f64 {
+block0(v0: f64, v1: f64):
+	v2 = fadd v0, v1
+	return v2
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   addsd   %xmm0, %xmm1, %xmm0
+;   movdqa  %xmm0, %xmm7
+;   movabsq $9221120237041090560, %r11
+;   movq    %r11, %xmm5
+;   cmppd   $3, %xmm0, %xmm7, %xmm0
+;   andpd   %xmm5, %xmm0, %xmm5
+;   andnpd  %xmm0, %xmm7, %xmm0
+;   orpd    %xmm0, %xmm5, %xmm0
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   addsd %xmm1, %xmm0
+;   movdqa %xmm0, %xmm7
+;   movabsq $0x7ff8000000000000, %r11
+;   movq %r11, %xmm5
+;   cmpunordpd %xmm7, %xmm0
+;   andpd %xmm0, %xmm5
+;   andnpd %xmm7, %xmm0
+;   orpd %xmm5, %xmm0
+;   movq %rbp, %rsp
+;   popq %rbp
+;   retq
+
+function %f1(f32, f32) -> f32 {
+block0(v0: f32, v1: f32):
+	v2 = fadd v0, v1
+	return v2
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   addss   %xmm0, %xmm1, %xmm0
+;   movdqa  %xmm0, %xmm7
+;   movl    $2143289344, %r11d
+;   movd    %r11d, %xmm5
+;   cmpps   $3, %xmm0, %xmm7, %xmm0
+;   andps   %xmm5, %xmm0, %xmm5
+;   andnps  %xmm0, %xmm7, %xmm0
+;   orps    %xmm0, %xmm5, %xmm0
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   addss %xmm1, %xmm0
+;   movdqa %xmm0, %xmm7
+;   movl $0x7fc00000, %r11d
+;   movd %r11d, %xmm5
+;   cmpunordps %xmm7, %xmm0
+;   andps %xmm0, %xmm5
+;   andnps %xmm7, %xmm0
+;   orps %xmm5, %xmm0
+;   movq %rbp, %rsp
+;   popq %rbp
+;   retq
+
diff --git a/crates/component-macro/tests/codegen.rs b/crates/component-macro/tests/codegen.rs
index a292d8004..76e228203 100644
--- a/crates/component-macro/tests/codegen.rs
+++ b/crates/component-macro/tests/codegen.rs
@@ -254,7 +254,7 @@ mod with_works_with_hierarchy {
         struct X;
 
         impl FooImports for X {
-            fn x(&mut self) -> wasmtime::Result<super::bindings::foo::foo::a::T> {
+            fn x(&mut self) -> super::bindings::foo::foo::a::T {
                 loop {}
             }
         }
@@ -294,7 +294,7 @@ mod with_works_with_hierarchy {
         struct X;
 
         impl FooImports for X {
-            fn x(&mut self) -> wasmtime::Result<super::bindings::foo::foo::a::T> {
+            fn x(&mut self) -> super::bindings::foo::foo::a::T {
                 loop {}
             }
         }
@@ -334,9 +334,189 @@ mod with_works_with_hierarchy {
         struct X;
 
         impl FooImports for X {
-            fn x(&mut self) -> wasmtime::Result<super::bindings::foo::foo::a::T> {
+            fn x(&mut self) -> super::bindings::foo::foo::a::T {
                 loop {}
             }
         }
     }
 }
+
+mod trappable_imports {
+    mod none {
+        wasmtime::component::bindgen!({
+            inline: "
+                package foo:foo;
+
+                world foo {
+                    import foo: func();
+                }
+            ",
+            trappable_imports: false,
+        });
+        struct X;
+
+        impl FooImports for X {
+            fn foo(&mut self) {}
+        }
+    }
+
+    mod all {
+        wasmtime::component::bindgen!({
+            inline: "
+                package foo:foo;
+
+                world foo {
+                    import foo: func();
+                }
+            ",
+            trappable_imports: true,
+        });
+        struct X;
+
+        impl FooImports for X {
+            fn foo(&mut self) -> wasmtime::Result<()> {
+                Ok(())
+            }
+        }
+    }
+
+    mod some {
+        wasmtime::component::bindgen!({
+            inline: "
+                package foo:foo;
+
+                world foo {
+                    import foo: func();
+                    import bar: func();
+                }
+            ",
+            trappable_imports: ["foo"],
+        });
+        struct X;
+
+        impl FooImports for X {
+            fn foo(&mut self) -> wasmtime::Result<()> {
+                Ok(())
+            }
+            fn bar(&mut self) {}
+        }
+    }
+
+    mod across_interfaces {
+        use wasmtime::component::Resource;
+
+        wasmtime::component::bindgen!({
+            inline: "
+                package foo:foo;
+
+                interface a {
+                    foo: func();
+                    bar: func();
+
+                    resource r {
+                        constructor();
+                        foo: func();
+                        bar: static func();
+                    }
+                }
+
+                world foo {
+                    import a;
+                    import foo: func();
+                    import bar: func();
+                    import i: interface {
+                        foo: func();
+                        bar: func();
+                    }
+
+                }
+            ",
+            trappable_imports: ["foo"],
+            with: { "foo:foo/a/r": R },
+        });
+
+        struct X;
+        pub struct R;
+
+        impl FooImports for X {
+            fn foo(&mut self) -> wasmtime::Result<()> {
+                Ok(())
+            }
+            fn bar(&mut self) {}
+        }
+
+        impl i::Host for X {
+            fn foo(&mut self) -> wasmtime::Result<()> {
+                Ok(())
+            }
+            fn bar(&mut self) {}
+        }
+
+        impl foo::foo::a::Host for X {
+            fn foo(&mut self) -> wasmtime::Result<()> {
+                Ok(())
+            }
+            fn bar(&mut self) {}
+        }
+
+        impl foo::foo::a::HostR for X {
+            fn new(&mut self) -> Resource<R> {
+                loop {}
+            }
+            fn foo(&mut self, _: Resource<R>) {}
+            fn bar(&mut self) {}
+            fn drop(&mut self, _: Resource<R>) -> wasmtime::Result<()> {
+                Ok(())
+            }
+        }
+    }
+
+    mod resources {
+        use wasmtime::component::Resource;
+
+        wasmtime::component::bindgen!({
+            inline: "
+                package foo:foo;
+
+                interface a {
+                    resource r {
+                        constructor();
+                        foo: func();
+                        bar: static func();
+                    }
+                }
+
+                world foo {
+                    import a;
+
+                }
+            ",
+            trappable_imports: [
+                "[constructor]r",
+                "[method]r.foo",
+                "[static]r.bar",
+            ],
+            with: { "foo:foo/a/r": R },
+        });
+
+        struct X;
+        pub struct R;
+
+        impl foo::foo::a::Host for X {}
+
+        impl foo::foo::a::HostR for X {
+            fn new(&mut self) -> wasmtime::Result<Resource<R>> {
+                loop {}
+            }
+            fn foo(&mut self, _: Resource<R>) -> wasmtime::Result<()> {
+                Ok(())
+            }
+            fn bar(&mut self) -> wasmtime::Result<()> {
+                Ok(())
+            }
+            fn drop(&mut self, _: Resource<R>) -> wasmtime::Result<()> {
+                Ok(())
+            }
+        }
+    }
+}
diff --git a/tests/all/component_model/aot.rs b/tests/all/component_model/aot.rs
index b94e8f085..676de39d1 100644
--- a/tests/all/component_model/aot.rs
+++ b/tests/all/component_model/aot.rs
@@ -164,3 +164,46 @@ fn reflect_resource_import() -> Result<()> {
 
     Ok(())
 }
+
+#[test]
+#[cfg_attr(miri, ignore)]
+fn truncated_component_binaries_dont_panic() -> Result<()> {
+    let engine = super::engine();
+
+    let binary = wat::parse_str(
+        r#"
+        (component
+            (import "a" (core module $m0
+                (import "" "" (func))
+            ))
+
+            (core module $m1
+                (func (export ""))
+            )
+            (core instance $i1 (instantiate (module $m1)))
+            (func $f (canon lift (core func $i1 "f")))
+
+            (component $c1
+                (import "f" (func))
+                (core module $m2
+                    (func (export "g"))
+                )
+                (core instance $i2 (instantiate $m2))
+                (func (export "g")
+                    (canon lift (core func $i2 "g"))
+                )
+            )
+            (instance $i3 (instantiate $c1 (with "f" (func $f))))
+            (func (export "g") (alias export $i3 "g"))
+        )
+        "#,
+    )?;
+
+    // Check that if we feed each truncation of the component binary into
+    // `Component::new` we don't get any panics.
+    for i in 1..binary.len() - 1 {
+        let _ = Component::from_binary(&engine, &binary[0..i]);
+    }
+
+    Ok(())
+}
diff --git a/tests/all/component_model/bindgen.rs b/tests/all/component_model/bindgen.rs
index 1c0b713a1..764836491 100644
--- a/tests/all/component_model/bindgen.rs
+++ b/tests/all/component_model/bindgen.rs
@@ -105,9 +105,8 @@ mod one_import {
         }
 
         impl foo::Host for MyImports {
-            fn foo(&mut self) -> Result<()> {
+            fn foo(&mut self) {
                 self.hit = true;
-                Ok(())
             }
         }
 
@@ -181,9 +180,9 @@ mod resources_at_world_level {
         }
 
         impl HostX for MyImports {
-            fn new(&mut self) -> Result<Resource<X>> {
+            fn new(&mut self) -> Resource<X> {
                 self.ctor_hit = true;
-                Ok(Resource::new_own(80))
+                Resource::new_own(80)
             }
 
             fn drop(&mut self, val: Resource<X>) -> Result<()> {
@@ -286,9 +285,9 @@ mod resources_at_interface_level {
         use foo::foo::def::X;
 
         impl foo::foo::def::HostX for MyImports {
-            fn new(&mut self) -> Result<Resource<X>> {
+            fn new(&mut self) -> Resource<X> {
                 self.ctor_hit = true;
-                Ok(Resource::new_own(80))
+                Resource::new_own(80)
             }
 
             fn drop(&mut self, val: Resource<X>) -> Result<()> {
@@ -337,13 +336,9 @@ mod async_config {
 
     #[async_trait::async_trait]
     impl T1Imports for T {
-        async fn x(&mut self) -> Result<()> {
-            Ok(())
-        }
+        async fn x(&mut self) {}
 
-        async fn y(&mut self) -> Result<()> {
-            Ok(())
-        }
+        async fn y(&mut self) {}
     }
 
     async fn _test_t1(t1: &T1, store: &mut Store<()>) {
@@ -367,13 +362,9 @@ mod async_config {
 
     #[async_trait::async_trait]
     impl T2Imports for T {
-        fn x(&mut self) -> Result<()> {
-            Ok(())
-        }
+        fn x(&mut self) {}
 
-        async fn y(&mut self) -> Result<()> {
-            Ok(())
-        }
+        async fn y(&mut self) {}
     }
 
     async fn _test_t2(t2: &T2, store: &mut Store<()>) {
@@ -397,13 +388,9 @@ mod async_config {
 
     #[async_trait::async_trait]
     impl T3Imports for T {
-        async fn x(&mut self) -> Result<()> {
-            Ok(())
-        }
+        async fn x(&mut self) {}
 
-        fn y(&mut self) -> Result<()> {
-            Ok(())
-        }
+        fn y(&mut self) {}
     }
 
     async fn _test_t3(t3: &T3, store: &mut Store<()>) {
@@ -468,11 +455,11 @@ mod exported_resources {
     }
 
     impl a::HostX for MyImports {
-        fn new(&mut self) -> Result<Resource<a::X>> {
+        fn new(&mut self) -> Resource<a::X> {
             let rep = self.next_a_x;
             self.next_a_x += 1;
             self.hostcalls.push(Hostcall::NewA);
-            Ok(Resource::new_own(rep))
+            Resource::new_own(rep)
         }
 
         fn drop(&mut self, val: Resource<a::X>) -> Result<()> {
diff --git a/tests/all/component_model/bindgen/results.rs b/tests/all/component_model/bindgen/results.rs
index 29f60fe8b..dfb361523 100644
--- a/tests/all/component_model/bindgen/results.rs
+++ b/tests/all/component_model/bindgen/results.rs
@@ -17,6 +17,7 @@ mod empty_error {
 
             export empty-error: func(a: float64) -> result<float64>;
         }",
+        trappable_imports: true,
     });
 
     #[test]
@@ -118,6 +119,7 @@ mod string_error {
 
             export string-error: func(a: float64) -> result<float64, string>;
         }",
+        trappable_imports: true,
     });
 
     #[test]
@@ -238,7 +240,8 @@ mod enum_error {
                 enum-error: func(a: float64) -> result<float64, e1>;
             }
         }",
-        trappable_error_type: { "inline:inline/imports/e1" => TrappableE1 }
+        trappable_error_type: { "inline:inline/imports/e1" => TrappableE1 },
+        trappable_imports: true,
     });
 
     // You can create concrete trap types which make it all the way out to the
@@ -418,7 +421,8 @@ mod record_error {
                 record-error: func(a: float64) -> result<float64, e2>;
             }
         }",
-        trappable_error_type: { "inline:inline/imports/e2" => TrappableE2 }
+        trappable_error_type: { "inline:inline/imports/e2" => TrappableE2 },
+        trappable_imports: true,
     });
 
     pub enum TrappableE2 {
@@ -589,7 +593,8 @@ mod variant_error {
                 variant-error: func(a: float64) -> result<float64, e3>;
             }
         }",
-        trappable_error_type: { "inline:inline/imports/e3" => TrappableE3 }
+        trappable_error_type: { "inline:inline/imports/e3" => TrappableE3 },
+        trappable_imports: true,
     });
 
     pub enum TrappableE3 {
@@ -784,7 +789,8 @@ mod multiple_interfaces_error {
                 enum-error: func(a: float64) -> result<float64, e1>;
             }
         }",
-        trappable_error_type: { "inline:inline/types/e1" => TrappableE1 }
+        trappable_error_type: { "inline:inline/types/e1" => TrappableE1 },
+        trappable_imports: true,
     });
 
     pub enum TrappableE1 {
@@ -967,6 +973,7 @@ mod with_remapping {
             import imports: interface {
                 empty-error: func(a: float64) -> result<float64>;
             }",
+            trappable_imports: true,
         });
     }
 
@@ -983,6 +990,7 @@ mod with_remapping {
         with: {
             "imports": interfaces::imports,
         },
+        trappable_imports: true,
     });
 
     #[test]
diff --git a/winch/docs/testing.md b/winch/docs/testing.md
deleted file mode 100644
index 859db45b7..000000000
--- a/winch/docs/testing.md
+++ /dev/null
@@ -1,63 +0,0 @@
-# Testing Winch
-
-Winch is tested through integration testing using the `winch-filetests` crate
-and manual exploratory testing. A CLI is available to run these tests
-conveniently. To add the `winch-tools` binary to your `PATH`, run `cargo install
---path winch` from the root of `wasmtime`. The CLI provides two commands: `test`
-and `compile`. To see the help text for each command, run `winch-tools test
---help` or `winch-tools compile --help`.
-
-## Integration Testing (`winch-tools test`)
-
-The `test` command will run a suite of tests that validates Winch output for a
-WebAssembly module is consistent with our expectations.
-
-### Running `test`
-
-Running `winch-tools test` will run all integration tests in the
-`winch-filetests` crate. All arguments following two dashes (`--`) will be
-passed directly to `cargo test -p winch-filetests`. This will allow you to
-configure the tests to run based on your requirements. All tests in the
-`winch-filetests` crate get named in the following convention:
-`winch_filetests_${filepath}`. This makes it possible to filter if you don't
-want to run the entire suite.
-
-If the output of Winch changes for a test in a run due to code updates, the test
-will fail and the difference between the two outputs will be shown. If the new
-output is expected, the tests can be re-run with an `WINCH_TEST_BLESS`
-environment variable set to `1`.
-
-### Adding a test
-
-To add new tests, create a `.wat` file in the `winch/filetests/filetests` folder
-in the following format:
-
-```wat
-;;! target = "x86_64"
-(module
-  (func (result i32)
-    (i32.const 42)
-  )
-)
-```
-
-It is encouraged to use folders to organize tests. For example, tests targeting
-the x86_64 architecture can be placed in the `winch/filetests/filetests/x64`.
-
-The first block of comments are a TOML compatible configuration passed to Winch
-during compilation with a `!` at the start of each line. The body of the file
-will be the subject of the test. A final block of comments is reserved for the
-output of the compilation, and it will be used to compare the output of the
-current run with the output of previous runs.
-
-## Manual Exploratory Tests (`winch-tools compile`)
-
-The `compile` command will run Winch for particular architecture against
-provided input file, and print the disassembled output to the console. Only
-`.wat` files are supported.
-
-### Running `compile`
-
-```bash
-winch-tools compile $wat_file --target $target_triple
-```