Repository: bytecodealliance/wasmtime
Pull request #8566 (2024-05-07 08:30:14)

cranelift: Remove `next_fixed_nonallocatable` and `with_allocs` methods
The `next_fixed_nonallocatable` method doesn't do anything any more and doesn't return anything so calls to it can just be deleted.

The `with_allocs`, `allocate`, and `to_string_with_alloc` methods are all trivial at this point, so inline them. The bulk of this change was performed this way:

`git grep -lF '.with_allocs(' | xargs sed -i 's/\.with_allocs([^)]*)/.clone()/g'`

In a couple cases, this makes the `AllocationConsumer` unused at these methods' call sites. Rather than changing function signatures in this PR, just mark those arguments as deliberately unused.

The number of structures being cloned here is unfortunate, and unnecessary now that we don't need to mutate any of them. But switching to borrowing them is a bigger change than I want to include here.


Patch:
diff --git a/cranelift/codegen/src/isa/aarch64/inst/args.rs b/cranelift/codegen/src/isa/aarch64/inst/args.rs
index 2893c9e0d..28a1c3405 100644
--- a/cranelift/codegen/src/isa/aarch64/inst/args.rs
+++ b/cranelift/codegen/src/isa/aarch64/inst/args.rs
@@ -145,20 +145,10 @@ impl AMode {
             extendop: op,
         }
     }
-
-    pub(crate) fn with_allocs(&self, _allocs: &mut AllocationConsumer) -> Self {
-        self.clone()
-    }
 }
 
 pub use crate::isa::aarch64::lower::isle::generated_code::PairAMode;
 
-impl PairAMode {
-    pub(crate) fn with_allocs(&self, _allocs: &mut AllocationConsumer) -> Self {
-        self.clone()
-    }
-}
-
 //=============================================================================
 // Instruction sub-components (conditions, branches and branch targets):
 // definitions
diff --git a/cranelift/codegen/src/isa/aarch64/inst/emit.rs b/cranelift/codegen/src/isa/aarch64/inst/emit.rs
index 66dd4242e..9dc389d11 100644
--- a/cranelift/codegen/src/isa/aarch64/inst/emit.rs
+++ b/cranelift/codegen/src/isa/aarch64/inst/emit.rs
@@ -997,7 +997,7 @@ impl MachInstEmit for Inst {
             | &Inst::FpuLoad64 { rd, ref mem, flags }
             | &Inst::FpuLoad128 { rd, ref mem, flags } => {
                 let rd = allocs.next_writable(rd);
-                let mem = mem.with_allocs(&mut allocs);
+                let mem = mem.clone();
                 let access_ty = self.mem_type().unwrap();
                 let (mem_insts, mem) = mem_finalize(Some(sink), &mem, access_ty, state);
 
@@ -1138,7 +1138,7 @@ impl MachInstEmit for Inst {
             | &Inst::FpuStore64 { rd, ref mem, flags }
             | &Inst::FpuStore128 { rd, ref mem, flags } => {
                 let rd = allocs.next(rd);
-                let mem = mem.with_allocs(&mut allocs);
+                let mem = mem.clone();
                 let access_ty = self.mem_type().unwrap();
                 let (mem_insts, mem) = mem_finalize(Some(sink), &mem, access_ty, state);
 
@@ -1233,7 +1233,7 @@ impl MachInstEmit for Inst {
             } => {
                 let rt = allocs.next(rt);
                 let rt2 = allocs.next(rt2);
-                let mem = mem.with_allocs(&mut allocs);
+                let mem = mem.clone();
                 if let Some(trap_code) = flags.trap_code() {
                     // Register the offset at which the actual store instruction starts.
                     sink.add_trap(trap_code);
@@ -1264,7 +1264,7 @@ impl MachInstEmit for Inst {
             } => {
                 let rt = allocs.next(rt.to_reg());
                 let rt2 = allocs.next(rt2.to_reg());
-                let mem = mem.with_allocs(&mut allocs);
+                let mem = mem.clone();
                 if let Some(trap_code) = flags.trap_code() {
                     // Register the offset at which the actual load instruction starts.
                     sink.add_trap(trap_code);
@@ -1302,7 +1302,7 @@ impl MachInstEmit for Inst {
             } => {
                 let rt = allocs.next(rt.to_reg());
                 let rt2 = allocs.next(rt2.to_reg());
-                let mem = mem.with_allocs(&mut allocs);
+                let mem = mem.clone();
 
                 if let Some(trap_code) = flags.trap_code() {
                     // Register the offset at which the actual load instruction starts.
@@ -1347,7 +1347,7 @@ impl MachInstEmit for Inst {
             } => {
                 let rt = allocs.next(rt);
                 let rt2 = allocs.next(rt2);
-                let mem = mem.with_allocs(&mut allocs);
+                let mem = mem.clone();
 
                 if let Some(trap_code) = flags.trap_code() {
                     // Register the offset at which the actual store instruction starts.
@@ -1416,7 +1416,6 @@ impl MachInstEmit for Inst {
             }
             &Inst::MovFromPReg { rd, rm } => {
                 let rd = allocs.next_writable(rd);
-                allocs.next_fixed_nonallocatable(rm);
                 let rm: Reg = rm.into();
                 debug_assert!([
                     regs::fp_reg(),
@@ -1431,7 +1430,6 @@ impl MachInstEmit for Inst {
                 Inst::Mov { size, rd, rm }.emit(&[], sink, emit_info, state);
             }
             &Inst::MovToPReg { rd, rm } => {
-                allocs.next_fixed_nonallocatable(rd);
                 let rd: Writable<Reg> = Writable::from_reg(rd.into());
                 let rm = allocs.next(rm);
                 debug_assert!([
@@ -3454,7 +3452,7 @@ impl MachInstEmit for Inst {
             }
             &Inst::LoadAddr { rd, ref mem } => {
                 let rd = allocs.next_writable(rd);
-                let mem = mem.with_allocs(&mut allocs);
+                let mem = mem.clone();
                 let (mem_insts, mem) = mem_finalize(Some(sink), &mem, I8, state);
                 for inst in mem_insts.into_iter() {
                     inst.emit(&[], sink, emit_info, state);
diff --git a/cranelift/codegen/src/isa/aarch64/inst/mod.rs b/cranelift/codegen/src/isa/aarch64/inst/mod.rs
index 7cffbce30..7bed4c982 100644
--- a/cranelift/codegen/src/isa/aarch64/inst/mod.rs
+++ b/cranelift/codegen/src/isa/aarch64/inst/mod.rs
@@ -397,7 +397,6 @@ impl Inst {
 // Instructions: get_regs
 
 fn memarg_operands(memarg: &mut AMode, collector: &mut impl OperandVisitor) {
-    // This should match `AMode::with_allocs()`.
     match memarg {
         AMode::Unscaled { rn, .. } | AMode::UnsignedOffset { rn, .. } => {
             collector.reg_use(rn);
@@ -421,7 +420,6 @@ fn memarg_operands(memarg: &mut AMode, collector: &mut impl OperandVisitor) {
 }
 
 fn pairmemarg_operands(pairmemarg: &mut PairAMode, collector: &mut impl OperandVisitor) {
-    // This should match `PairAMode::with_allocs()`.
     match pairmemarg {
         PairAMode::SignedOffset { reg, .. } => {
             collector.reg_use(reg);
@@ -1265,9 +1263,6 @@ impl Inst {
             }
         }
 
-        // N.B.: order of `allocs` consumption (via register
-        // pretty-printing or memarg.with_allocs()) needs to match the
-        // order in `aarch64_get_operands` above.
         match self {
             &Inst::Nop0 => "nop-zero-len".to_string(),
             &Inst::Nop4 => "nop".to_string(),
@@ -1416,7 +1411,7 @@ impl Inst {
                 };
 
                 let rd = pretty_print_ireg(rd.to_reg(), size, allocs);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
                 let access_ty = self.mem_type().unwrap();
                 let (mem_str, mem) = mem_finalize_for_show(&mem, access_ty, state);
 
@@ -1443,7 +1438,7 @@ impl Inst {
                 };
 
                 let rd = pretty_print_ireg(rd, size, allocs);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
                 let access_ty = self.mem_type().unwrap();
                 let (mem_str, mem) = mem_finalize_for_show(&mem, access_ty, state);
 
@@ -1454,7 +1449,7 @@ impl Inst {
             } => {
                 let rt = pretty_print_ireg(rt, OperandSize::Size64, allocs);
                 let rt2 = pretty_print_ireg(rt2, OperandSize::Size64, allocs);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
                 let mem = mem.pretty_print_default();
                 format!("stp {}, {}, {}", rt, rt2, mem)
             }
@@ -1463,7 +1458,7 @@ impl Inst {
             } => {
                 let rt = pretty_print_ireg(rt.to_reg(), OperandSize::Size64, allocs);
                 let rt2 = pretty_print_ireg(rt2.to_reg(), OperandSize::Size64, allocs);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
                 let mem = mem.pretty_print_default();
                 format!("ldp {}, {}, {}", rt, rt2, mem)
             }
@@ -1474,12 +1469,10 @@ impl Inst {
             }
             &Inst::MovFromPReg { rd, rm } => {
                 let rd = pretty_print_ireg(rd.to_reg(), OperandSize::Size64, allocs);
-                allocs.next_fixed_nonallocatable(rm);
                 let rm = show_ireg_sized(rm.into(), OperandSize::Size64);
                 format!("mov {}, {}", rd, rm)
             }
             &Inst::MovToPReg { rd, rm } => {
-                allocs.next_fixed_nonallocatable(rd);
                 let rd = show_ireg_sized(rd.into(), OperandSize::Size64);
                 let rm = pretty_print_ireg(rm, OperandSize::Size64, allocs);
                 format!("mov {}, {}", rd, rm)
@@ -1832,14 +1825,14 @@ impl Inst {
             }
             &Inst::FpuLoad32 { rd, ref mem, .. } => {
                 let rd = pretty_print_vreg_scalar(rd.to_reg(), ScalarSize::Size32, allocs);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
                 let access_ty = self.mem_type().unwrap();
                 let (mem_str, mem) = mem_finalize_for_show(&mem, access_ty, state);
                 format!("{}ldr {}, {}", mem_str, rd, mem)
             }
             &Inst::FpuLoad64 { rd, ref mem, .. } => {
                 let rd = pretty_print_vreg_scalar(rd.to_reg(), ScalarSize::Size64, allocs);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
                 let access_ty = self.mem_type().unwrap();
                 let (mem_str, mem) = mem_finalize_for_show(&mem, access_ty, state);
                 format!("{}ldr {}, {}", mem_str, rd, mem)
@@ -1847,21 +1840,21 @@ impl Inst {
             &Inst::FpuLoad128 { rd, ref mem, .. } => {
                 let rd = pretty_print_reg(rd.to_reg(), allocs);
                 let rd = "q".to_string() + &rd[1..];
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
                 let access_ty = self.mem_type().unwrap();
                 let (mem_str, mem) = mem_finalize_for_show(&mem, access_ty, state);
                 format!("{}ldr {}, {}", mem_str, rd, mem)
             }
             &Inst::FpuStore32 { rd, ref mem, .. } => {
                 let rd = pretty_print_vreg_scalar(rd, ScalarSize::Size32, allocs);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
                 let access_ty = self.mem_type().unwrap();
                 let (mem_str, mem) = mem_finalize_for_show(&mem, access_ty, state);
                 format!("{}str {}, {}", mem_str, rd, mem)
             }
             &Inst::FpuStore64 { rd, ref mem, .. } => {
                 let rd = pretty_print_vreg_scalar(rd, ScalarSize::Size64, allocs);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
                 let access_ty = self.mem_type().unwrap();
                 let (mem_str, mem) = mem_finalize_for_show(&mem, access_ty, state);
                 format!("{}str {}, {}", mem_str, rd, mem)
@@ -1869,7 +1862,7 @@ impl Inst {
             &Inst::FpuStore128 { rd, ref mem, .. } => {
                 let rd = pretty_print_reg(rd, allocs);
                 let rd = "q".to_string() + &rd[1..];
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
                 let access_ty = self.mem_type().unwrap();
                 let (mem_str, mem) = mem_finalize_for_show(&mem, access_ty, state);
                 format!("{}str {}, {}", mem_str, rd, mem)
@@ -1879,7 +1872,7 @@ impl Inst {
             } => {
                 let rt = pretty_print_vreg_scalar(rt.to_reg(), ScalarSize::Size64, allocs);
                 let rt2 = pretty_print_vreg_scalar(rt2.to_reg(), ScalarSize::Size64, allocs);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
                 let mem = mem.pretty_print_default();
 
                 format!("ldp {}, {}, {}", rt, rt2, mem)
@@ -1889,7 +1882,7 @@ impl Inst {
             } => {
                 let rt = pretty_print_vreg_scalar(rt, ScalarSize::Size64, allocs);
                 let rt2 = pretty_print_vreg_scalar(rt2, ScalarSize::Size64, allocs);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
                 let mem = mem.pretty_print_default();
 
                 format!("stp {}, {}, {}", rt, rt2, mem)
@@ -1899,7 +1892,7 @@ impl Inst {
             } => {
                 let rt = pretty_print_vreg_scalar(rt.to_reg(), ScalarSize::Size128, allocs);
                 let rt2 = pretty_print_vreg_scalar(rt2.to_reg(), ScalarSize::Size128, allocs);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
                 let mem = mem.pretty_print_default();
 
                 format!("ldp {}, {}, {}", rt, rt2, mem)
@@ -1909,7 +1902,7 @@ impl Inst {
             } => {
                 let rt = pretty_print_vreg_scalar(rt, ScalarSize::Size128, allocs);
                 let rt2 = pretty_print_vreg_scalar(rt2, ScalarSize::Size128, allocs);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
                 let mem = mem.pretty_print_default();
 
                 format!("stp {}, {}, {}", rt, rt2, mem)
@@ -2781,7 +2774,7 @@ impl Inst {
                 // expansion stage (i.e., legalization, but without the slow edit-in-place
                 // of the existing legalization framework).
                 let rd = allocs.next_writable(rd);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
                 let (mem_insts, mem) = mem_finalize(None, &mem, I8, state);
                 let mut ret = String::new();
                 for inst in mem_insts.into_iter() {
diff --git a/cranelift/codegen/src/isa/riscv64/inst/args.rs b/cranelift/codegen/src/isa/riscv64/inst/args.rs
index 15bb09f0a..561d18da8 100644
--- a/cranelift/codegen/src/isa/riscv64/inst/args.rs
+++ b/cranelift/codegen/src/isa/riscv64/inst/args.rs
@@ -113,10 +113,6 @@ pub enum AMode {
 }
 
 impl AMode {
-    pub(crate) fn with_allocs(self, _allocs: &mut AllocationConsumer) -> Self {
-        self
-    }
-
     /// Add the registers referenced by this AMode to `collector`.
     pub(crate) fn get_operands(&mut self, collector: &mut impl OperandVisitor) {
         match self {
@@ -176,10 +172,6 @@ impl AMode {
             | &AMode::NominalSPOffset(..) => None,
         }
     }
-
-    pub(crate) fn to_string_with_alloc(&self, allocs: &mut AllocationConsumer) -> String {
-        format!("{}", self.clone().with_allocs(allocs))
-    }
 }
 
 impl Display for AMode {
diff --git a/cranelift/codegen/src/isa/riscv64/inst/emit.rs b/cranelift/codegen/src/isa/riscv64/inst/emit.rs
index e0ceb87a2..f7318af99 100644
--- a/cranelift/codegen/src/isa/riscv64/inst/emit.rs
+++ b/cranelift/codegen/src/isa/riscv64/inst/emit.rs
@@ -223,17 +223,13 @@ impl MachInstEmit for Inst {
 
     fn emit(
         &self,
-        allocs: &[Allocation],
+        _allocs: &[Allocation],
         sink: &mut MachBuffer<Inst>,
         emit_info: &Self::Info,
         state: &mut EmitState,
     ) {
-        // Transform this into a instruction with all the physical regs
-        let mut allocs = AllocationConsumer::new(allocs);
-        let inst = self.clone().allocate(&mut allocs);
-
         // Check if we need to update the vector state before emitting this instruction
-        if let Some(expected) = inst.expected_vstate() {
+        if let Some(expected) = self.expected_vstate() {
             if state.vstate != EmitVState::Known(expected.clone()) {
                 // Update the vector state.
                 Inst::VecSetState {
@@ -252,10 +248,10 @@ impl MachInstEmit for Inst {
         let mut start_off = sink.cur_offset();
 
         // First try to emit this as a compressed instruction
-        let res = inst.try_emit_compressed(sink, emit_info, state, &mut start_off);
+        let res = self.try_emit_compressed(sink, emit_info, state, &mut start_off);
         if res.is_none() {
             // If we can't lets emit it as a normal instruction
-            inst.emit_uncompressed(sink, emit_info, state, &mut start_off);
+            self.emit_uncompressed(sink, emit_info, state, &mut start_off);
         }
 
         let end_off = sink.cur_offset();
@@ -2605,10 +2601,6 @@ impl Inst {
             }
         };
     }
-
-    fn allocate(self, _allocs: &mut AllocationConsumer) -> Self {
-        self
-    }
 }
 
 fn emit_return_call_common_sequence(
diff --git a/cranelift/codegen/src/isa/riscv64/inst/mod.rs b/cranelift/codegen/src/isa/riscv64/inst/mod.rs
index 376a1cb5a..33fd8c04c 100644
--- a/cranelift/codegen/src/isa/riscv64/inst/mod.rs
+++ b/cranelift/codegen/src/isa/riscv64/inst/mod.rs
@@ -934,9 +934,9 @@ impl Inst {
             reg_name(reg)
         };
 
-        let format_vec_amode = |amode: &VecAMode, allocs: &mut AllocationConsumer| -> String {
+        let format_vec_amode = |amode: &VecAMode, _allocs: &mut AllocationConsumer| -> String {
             match amode {
-                VecAMode::UnitStride { base } => base.to_string_with_alloc(allocs),
+                VecAMode::UnitStride { base } => base.to_string(),
             }
         };
 
@@ -1346,7 +1346,7 @@ impl Inst {
                 from,
                 flags: _flags,
             } => {
-                let base = from.to_string_with_alloc(allocs);
+                let base = from.to_string();
                 let rd = format_reg(rd.to_reg(), allocs);
                 format!("{} {},{}", op.op_name(), rd, base,)
             }
@@ -1356,7 +1356,7 @@ impl Inst {
                 op,
                 flags: _flags,
             } => {
-                let base = to.to_string_with_alloc(allocs);
+                let base = to.to_string();
                 let src = format_reg(src, allocs);
                 format!("{} {},{}", op.op_name(), src, base,)
             }
@@ -1497,7 +1497,7 @@ impl Inst {
                 format!("elf_tls_get_addr {rd},{}", name.display(None))
             }
             &MInst::LoadAddr { ref rd, ref mem } => {
-                let rs = mem.to_string_with_alloc(allocs);
+                let rs = mem.to_string();
                 let rd = format_reg(rd.to_reg(), allocs);
                 format!("load_addr {},{}", rd, rs)
             }
diff --git a/cranelift/codegen/src/isa/s390x/inst/args.rs b/cranelift/codegen/src/isa/s390x/inst/args.rs
index f14333bfb..294ac2113 100644
--- a/cranelift/codegen/src/isa/s390x/inst/args.rs
+++ b/cranelift/codegen/src/isa/s390x/inst/args.rs
@@ -101,11 +101,6 @@ impl MemArg {
             MemArg::NominalSPOffset { .. } => MemFlags::trusted(),
         }
     }
-
-    /// Edit registers with allocations.
-    pub fn with_allocs(&self, _allocs: &mut AllocationConsumer) -> Self {
-        self.clone()
-    }
 }
 
 /// A memory argument for an instruction with two memory operands.
@@ -153,11 +148,6 @@ impl MemArgPair {
             _ => None,
         }
     }
-
-    /// Edit registers with allocations.
-    pub fn with_allocs(&self, _allocs: &mut AllocationConsumer) -> Self {
-        self.clone()
-    }
 }
 
 //=============================================================================
diff --git a/cranelift/codegen/src/isa/s390x/inst/emit.rs b/cranelift/codegen/src/isa/s390x/inst/emit.rs
index bf43ebd9e..cee096d92 100644
--- a/cranelift/codegen/src/isa/s390x/inst/emit.rs
+++ b/cranelift/codegen/src/isa/s390x/inst/emit.rs
@@ -1533,7 +1533,7 @@ impl Inst {
                 let rd = allocs.next_writable(rd);
                 let ri = allocs.next(ri);
                 debug_assert_eq!(rd.to_reg(), ri);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
 
                 let (opcode_rx, opcode_rxy) = match alu_op {
                     ALUOp::Add32 => (Some(0x5a), Some(0xe35a)),        // A(Y)
@@ -1952,7 +1952,7 @@ impl Inst {
             }
             &Inst::CmpRX { op, rn, ref mem } => {
                 let rn = allocs.next(rn);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
 
                 let (opcode_rx, opcode_rxy, opcode_ril) = match op {
                     CmpOp::CmpS32 => (Some(0x59), Some(0xe359), Some(0xc6d)), // C(Y), CRL
@@ -2068,7 +2068,7 @@ impl Inst {
             } => {
                 let rd = allocs.next_writable(rd);
                 let rn = allocs.next(rn);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
 
                 let opcode = match alu_op {
                     ALUOp::Add32 => 0xebf8,        // LAA
@@ -2146,7 +2146,7 @@ impl Inst {
                 let ri = allocs.next(ri);
                 debug_assert_eq!(rd.to_reg(), ri);
                 let rn = allocs.next(rn);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
 
                 let (opcode_rs, opcode_rsy) = match self {
                     &Inst::AtomicCas32 { .. } => (Some(0xba), Some(0xeb14)), // CS(Y)
@@ -2179,7 +2179,7 @@ impl Inst {
             | &Inst::LoadRev32 { rd, ref mem }
             | &Inst::LoadRev64 { rd, ref mem } => {
                 let rd = allocs.next_writable(rd);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
 
                 let (opcode_rx, opcode_rxy, opcode_ril) = match self {
                     &Inst::Load32 { .. } => (Some(0x58), Some(0xe358), Some(0xc4d)), // L(Y), LRL
@@ -2213,7 +2213,7 @@ impl Inst {
             | &Inst::StoreRev32 { rd, ref mem }
             | &Inst::StoreRev64 { rd, ref mem } => {
                 let rd = allocs.next(rd);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
 
                 let (opcode_rx, opcode_rxy, opcode_ril) = match self {
                     &Inst::Store8 { .. } => (Some(0x42), Some(0xe372), None), // STC(Y)
@@ -2230,7 +2230,7 @@ impl Inst {
                 );
             }
             &Inst::StoreImm8 { imm, ref mem } => {
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
 
                 let opcode_si = 0x92; // MVI
                 let opcode_siy = 0xeb52; // MVIY
@@ -2241,7 +2241,7 @@ impl Inst {
             &Inst::StoreImm16 { imm, ref mem }
             | &Inst::StoreImm32SExt16 { imm, ref mem }
             | &Inst::StoreImm64SExt16 { imm, ref mem } => {
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
 
                 let opcode = match self {
                     &Inst::StoreImm16 { .. } => 0xe544,       // MVHHI
@@ -2256,14 +2256,14 @@ impl Inst {
                 ref src,
                 len_minus_one,
             } => {
-                let dst = dst.with_allocs(allocs);
-                let src = src.with_allocs(allocs);
+                let dst = dst.clone();
+                let src = src.clone();
                 let opcode = 0xd2; // MVC
                 mem_mem_emit(&dst, &src, len_minus_one, opcode, true, sink, state);
             }
 
             &Inst::LoadMultiple64 { rt, rt2, ref mem } => {
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
 
                 let opcode = 0xeb04; // LMG
                 let rt = rt.to_reg();
@@ -2281,7 +2281,7 @@ impl Inst {
                 );
             }
             &Inst::StoreMultiple64 { rt, rt2, ref mem } => {
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
 
                 let opcode = 0xeb24; // STMG
                 mem_rs_emit(
@@ -2299,7 +2299,7 @@ impl Inst {
 
             &Inst::LoadAddr { rd, ref mem } => {
                 let rd = allocs.next_writable(rd);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
 
                 let opcode_rx = Some(0x41); // LA
                 let opcode_rxy = Some(0xe371); // LAY
@@ -3045,7 +3045,7 @@ impl Inst {
             | &Inst::VecLoadElt32Rev { rd, ref mem }
             | &Inst::VecLoadElt64Rev { rd, ref mem } => {
                 let rd = allocs.next_writable(rd);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
 
                 let (opcode, m3) = match self {
                     &Inst::VecLoad { .. } => (0xe706, 0),          // VL
@@ -3069,7 +3069,7 @@ impl Inst {
             | &Inst::VecStoreElt32Rev { rd, ref mem }
             | &Inst::VecStoreElt64Rev { rd, ref mem } => {
                 let rd = allocs.next(rd);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
 
                 let (opcode, m3) = match self {
                     &Inst::VecStore { .. } => (0xe70e, 0),          // VST
@@ -3087,7 +3087,7 @@ impl Inst {
             &Inst::VecLoadReplicate { size, rd, ref mem }
             | &Inst::VecLoadReplicateRev { size, rd, ref mem } => {
                 let rd = allocs.next_writable(rd);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
 
                 let (opcode, m3) = match (self, size) {
                     (&Inst::VecLoadReplicate { .. }, 8) => (0xe705, 0), // VLREPB
@@ -3215,7 +3215,7 @@ impl Inst {
                 let rd = allocs.next_writable(rd);
                 let ri = allocs.next(ri);
                 debug_assert_eq!(rd.to_reg(), ri);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
 
                 let opcode_vrx = match (self, size) {
                     (&Inst::VecLoadLane { .. }, 8) => 0xe700,     // VLEB
@@ -3253,7 +3253,7 @@ impl Inst {
                 lane_imm,
             } => {
                 let rd = allocs.next_writable(rd);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
 
                 let (opcode_vrx, opcode_rx, opcode_rxy) = match (self, size) {
                     (&Inst::VecLoadLaneUndef { .. }, 8) => (0xe700, None, None), // VLEB
@@ -3297,7 +3297,7 @@ impl Inst {
                 lane_imm,
             } => {
                 let rd = allocs.next(rd);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
 
                 let (opcode_vrx, opcode_rx, opcode_rxy) = match (self, size) {
                     (&Inst::VecStoreLane { .. }, 8) => (0xe708, None, None), // VSTEB
diff --git a/cranelift/codegen/src/isa/s390x/inst/mod.rs b/cranelift/codegen/src/isa/s390x/inst/mod.rs
index 834be5eb0..8fb823cc8 100644
--- a/cranelift/codegen/src/isa/s390x/inst/mod.rs
+++ b/cranelift/codegen/src/isa/s390x/inst/mod.rs
@@ -1333,7 +1333,7 @@ impl Inst {
                 };
 
                 let rd = pretty_print_reg_mod(rd, ri, allocs);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
                 let (mem_str, mem) = mem_finalize_for_show(
                     &mem,
                     state,
@@ -1615,7 +1615,7 @@ impl Inst {
                 };
 
                 let rn = pretty_print_reg(rn, allocs);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
                 let (mem_str, mem) = mem_finalize_for_show(
                     &mem,
                     state,
@@ -1725,7 +1725,7 @@ impl Inst {
 
                 let rd = pretty_print_reg(rd.to_reg(), allocs);
                 let rn = pretty_print_reg(rn, allocs);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
                 let (mem_str, mem) = mem_finalize_for_show(
                     &mem,
                     state,
@@ -1760,7 +1760,7 @@ impl Inst {
 
                 let rd = pretty_print_reg_mod(rd, ri, allocs);
                 let rn = pretty_print_reg(rn, allocs);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
                 let (mem_str, mem) = mem_finalize_for_show(
                     &mem,
                     state,
@@ -1817,7 +1817,7 @@ impl Inst {
                 };
 
                 let rd = pretty_print_reg(rd.to_reg(), allocs);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
                 let (mem_str, mem) = mem_finalize_for_show(
                     &mem,
                     state,
@@ -1857,7 +1857,7 @@ impl Inst {
                 };
 
                 let rd = pretty_print_reg(rd, allocs);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
                 let (mem_str, mem) = mem_finalize_for_show(
                     &mem,
                     state,
@@ -1880,7 +1880,7 @@ impl Inst {
                 format!("{}{} {}, {}", mem_str, op.unwrap(), rd, mem)
             }
             &Inst::StoreImm8 { imm, ref mem } => {
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
                 let (mem_str, mem) = mem_finalize_for_show(
                     &mem,
                     state,
@@ -1904,7 +1904,7 @@ impl Inst {
             &Inst::StoreImm16 { imm, ref mem }
             | &Inst::StoreImm32SExt16 { imm, ref mem }
             | &Inst::StoreImm64SExt16 { imm, ref mem } => {
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
                 let (mem_str, mem) = mem_finalize_for_show(
                     &mem,
                     state,
@@ -1931,8 +1931,8 @@ impl Inst {
                 ref src,
                 len_minus_one,
             } => {
-                let dst = dst.with_allocs(allocs);
-                let src = src.with_allocs(allocs);
+                let dst = dst.clone();
+                let src = src.clone();
                 format!(
                     "mvc {}({},{}), {}({})",
                     dst.disp.pretty_print_default(),
@@ -1943,7 +1943,7 @@ impl Inst {
                 )
             }
             &Inst::LoadMultiple64 { rt, rt2, ref mem } => {
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
                 let (mem_str, mem) = mem_finalize_for_show(
                     &mem,
                     state,
@@ -1961,7 +1961,7 @@ impl Inst {
                 format!("{}lmg {}, {}, {}", mem_str, rt, rt2, mem)
             }
             &Inst::StoreMultiple64 { rt, rt2, ref mem } => {
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
                 let (mem_str, mem) = mem_finalize_for_show(
                     &mem,
                     state,
@@ -2671,7 +2671,7 @@ impl Inst {
                 };
 
                 let rd = pretty_print_reg(rd.to_reg(), allocs);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
                 let (mem_str, mem) = mem_finalize_for_show(
                     &mem,
                     state,
@@ -2707,7 +2707,7 @@ impl Inst {
                 };
 
                 let rd = pretty_print_reg(rd, allocs);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
                 let (mem_str, mem) = mem_finalize_for_show(
                     &mem,
                     state,
@@ -2736,7 +2736,7 @@ impl Inst {
                 };
 
                 let rd = pretty_print_reg(rd.to_reg(), allocs);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
                 let (mem_str, mem) = mem_finalize_for_show(
                     &mem,
                     state,
@@ -2857,7 +2857,7 @@ impl Inst {
 
                 let (rd, _) = pretty_print_fpr(rd.to_reg(), allocs);
                 let _ri = allocs.next(ri);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
                 let (mem_str, mem) = mem_finalize_for_show(
                     &mem,
                     state,
@@ -2896,7 +2896,7 @@ impl Inst {
                 };
 
                 let (rd, rd_fpr) = pretty_print_fpr(rd.to_reg(), allocs);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
                 if lane_imm == 0 && rd_fpr.is_some() && opcode_rx.is_some() {
                     let (mem_str, mem) = mem_finalize_for_show(
                         &mem,
@@ -2956,7 +2956,7 @@ impl Inst {
                 };
 
                 let (rd, rd_fpr) = pretty_print_fpr(rd, allocs);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
                 if lane_imm == 0 && rd_fpr.is_some() && opcode_rx.is_some() {
                     let (mem_str, mem) = mem_finalize_for_show(
                         &mem,
@@ -3246,7 +3246,7 @@ impl Inst {
             }
             &Inst::LoadAddr { rd, ref mem } => {
                 let rd = pretty_print_reg(rd.to_reg(), allocs);
-                let mem = mem.with_allocs(allocs);
+                let mem = mem.clone();
                 let (mem_str, mem) = mem_finalize_for_show(
                     &mem,
                     state,
diff --git a/cranelift/codegen/src/isa/x64/inst/args.rs b/cranelift/codegen/src/isa/x64/inst/args.rs
index 975a33e76..72acd7171 100644
--- a/cranelift/codegen/src/isa/x64/inst/args.rs
+++ b/cranelift/codegen/src/isa/x64/inst/args.rs
@@ -407,10 +407,6 @@ impl Amode {
         }
     }
 
-    pub(crate) fn with_allocs(&self, _allocs: &mut AllocationConsumer) -> Self {
-        self.clone()
-    }
-
     /// Offset the amode by a fixed offset.
     pub(crate) fn offset(&self, offset: i32) -> Self {
         let mut ret = self.clone();
@@ -539,10 +535,6 @@ impl SyntheticAmode {
         }
     }
 
-    pub(crate) fn with_allocs(&self, _allocs: &mut AllocationConsumer) -> Self {
-        self.clone()
-    }
-
     pub(crate) fn aligned(&self) -> bool {
         match self {
             SyntheticAmode::Real(addr) => addr.aligned(),
@@ -636,10 +628,6 @@ impl RegMemImm {
             Self::Imm { .. } => {}
         }
     }
-
-    pub(crate) fn with_allocs(&self, _allocs: &mut AllocationConsumer) -> Self {
-        self.clone()
-    }
 }
 
 impl From<RegMem> for RegMemImm {
@@ -734,10 +722,6 @@ impl RegMem {
             RegMem::Mem { addr, .. } => addr.get_operands(collector),
         }
     }
-
-    pub(crate) fn with_allocs(&self, _allocs: &mut AllocationConsumer) -> Self {
-        self.clone()
-    }
 }
 
 impl From<Reg> for RegMem {
diff --git a/cranelift/codegen/src/isa/x64/inst/emit.rs b/cranelift/codegen/src/isa/x64/inst/emit.rs
index f1efd2151..3305e231f 100644
--- a/cranelift/codegen/src/isa/x64/inst/emit.rs
+++ b/cranelift/codegen/src/isa/x64/inst/emit.rs
@@ -28,10 +28,11 @@ fn emit_signed_cvt(
     } else {
         SseOpcode::Cvtsi2ss
     };
+    let dst = WritableXmm::from_writable_reg(dst).unwrap();
     Inst::CvtIntToFloat {
         op,
-        dst: Writable::from_reg(Xmm::new(dst.to_reg()).unwrap()),
-        src1: Xmm::new(dst.to_reg()).unwrap(),
+        dst,
+        src1: dst.to_reg(),
         src2: GprMem::new(RegMem::reg(src)).unwrap(),
         src2_size: OperandSize::Size64,
     }
@@ -155,7 +156,7 @@ pub(crate) fn emit(
             let src1 = allocs.next(src1.to_reg());
             let reg_g = allocs.next(reg_g.to_reg().to_reg());
             debug_assert_eq!(src1, reg_g);
-            let src2 = src2.clone().to_reg_mem_imm().with_allocs(allocs);
+            let src2 = src2.clone().to_reg_mem_imm().clone();
 
             let prefix = if *size == OperandSize::Size16 {
                 LegacyPrefixes::_66
@@ -235,15 +236,16 @@ pub(crate) fn emit(
             }
         }
 
-        Inst::AluConstOp { op, size, dst } => {
-            let dst = allocs.next(dst.to_reg().to_reg());
+        &Inst::AluConstOp { op, size, dst } => {
+            let dst = WritableGpr::from_writable_reg(allocs.next_writable(dst.to_writable_reg()))
+                .unwrap();
             emit(
                 &Inst::AluRmiR {
-                    size: *size,
-                    op: *op,
-                    dst: Writable::from_reg(Gpr::new(dst).unwrap()),
-                    src1: Gpr::new(dst).unwrap(),
-                    src2: Gpr::new(dst).unwrap().into(),
+                    size,
+                    op,
+                    dst,
+                    src1: dst.to_reg(),
+                    src2: dst.to_reg().into(),
                 },
                 allocs,
                 sink,
@@ -259,7 +261,7 @@ pub(crate) fn emit(
             op,
         } => {
             let src2 = allocs.next(src2.to_reg());
-            let src1_dst = src1_dst.finalize(state, sink).with_allocs(allocs);
+            let src1_dst = src1_dst.finalize(state, sink).clone();
 
             let opcode = match op {
                 AluRmiROpcode::Add => 0x01,
@@ -303,7 +305,7 @@ pub(crate) fn emit(
 
             let dst = allocs.next(dst.to_reg().to_reg());
             let src1 = allocs.next(src1.to_reg());
-            let src2 = match src2.clone().to_reg_mem().with_allocs(allocs) {
+            let src2 = match src2.clone().to_reg_mem().clone() {
                 RegMem::Reg { reg } => {
                     RegisterOrAmode::Register(reg.to_real_reg().unwrap().hw_enc().into())
                 }
@@ -367,7 +369,7 @@ pub(crate) fn emit(
                     emit_std_reg_reg(sink, prefix, opcode, num_opcodes, dst, src, rex_flags);
                 }
                 RegMem::Mem { addr: src } => {
-                    let amode = src.finalize(state, sink).with_allocs(allocs);
+                    let amode = src.finalize(state, sink).clone();
                     emit_std_reg_mem(sink, prefix, opcode, num_opcodes, dst, &amode, rex_flags, 0);
                 }
             }
@@ -375,7 +377,7 @@ pub(crate) fn emit(
 
         Inst::UnaryRmRVex { size, op, src, dst } => {
             let dst = allocs.next(dst.to_reg().to_reg());
-            let src = match src.clone().to_reg_mem().with_allocs(allocs) {
+            let src = match src.clone().to_reg_mem().clone() {
                 RegMem::Reg { reg } => {
                     RegisterOrAmode::Register(reg.to_real_reg().unwrap().hw_enc().into())
                 }
@@ -406,7 +408,7 @@ pub(crate) fn emit(
             imm,
         } => {
             let dst = allocs.next(dst.to_reg().to_reg());
-            let src = match src.clone().to_reg_mem().with_allocs(allocs) {
+            let src = match src.clone().to_reg_mem().clone() {
                 RegMem::Reg { reg } => {
                     RegisterOrAmode::Register(reg.to_real_reg().unwrap().hw_enc().into())
                 }
@@ -474,7 +476,7 @@ pub(crate) fn emit(
             divisor,
             ..
         } => {
-            let divisor = divisor.clone().to_reg_mem().with_allocs(allocs);
+            let divisor = divisor.clone().to_reg_mem().clone();
             let size = match inst {
                 Inst::Div {
                     size,
@@ -560,7 +562,7 @@ pub(crate) fn emit(
             debug_assert_eq!(src1, regs::rax());
             debug_assert_eq!(dst_lo, regs::rax());
             debug_assert_eq!(dst_hi, regs::rdx());
-            let src2 = src2.clone().to_reg_mem().with_allocs(allocs);
+            let src2 = src2.clone().to_reg_mem().clone();
 
             let rex_flags = RexFlags::from(*size);
             let prefix = match size {
@@ -592,7 +594,7 @@ pub(crate) fn emit(
             let dst = allocs.next(dst.to_reg().to_reg());
             debug_assert_eq!(src1, regs::rax());
             debug_assert_eq!(dst, regs::rax());
-            let src2 = src2.clone().to_reg_mem().with_allocs(allocs);
+            let src2 = src2.clone().to_reg_mem().clone();
 
             let mut rex_flags = RexFlags::from(OperandSize::Size8);
             let prefix = LegacyPrefixes::None;
@@ -631,7 +633,7 @@ pub(crate) fn emit(
             let src1 = allocs.next(src1.to_reg());
             let dst = allocs.next(dst.to_reg().to_reg());
             debug_assert_eq!(src1, dst);
-            let src2 = src2.clone().to_reg_mem().with_allocs(allocs);
+            let src2 = src2.clone().to_reg_mem().clone();
 
             let rex = RexFlags::from(*size);
             let prefix = LegacyPrefixes::None;
@@ -654,7 +656,7 @@ pub(crate) fn emit(
             dst,
         } => {
             let dst = allocs.next(dst.to_reg().to_reg());
-            let src1 = src1.clone().to_reg_mem().with_allocs(allocs);
+            let src1 = src1.clone().to_reg_mem().clone();
 
             let rex = RexFlags::from(*size);
             let prefix = match size {
@@ -845,7 +847,7 @@ pub(crate) fn emit(
         }
 
         Inst::MovImmM { size, simm32, dst } => {
-            let dst = &dst.finalize(state, sink).with_allocs(allocs);
+            let dst = &dst.finalize(state, sink).clone();
             let default_rex = RexFlags::clear_w();
             let default_opcode = 0xC7;
             let bytes = size.to_bytes();
@@ -885,20 +887,18 @@ pub(crate) fn emit(
         }
 
         Inst::MovFromPReg { src, dst } => {
-            allocs.next_fixed_nonallocatable(*src);
             let src: Reg = (*src).into();
             debug_assert!([regs::rsp(), regs::rbp(), regs::pinned_reg()].contains(&src));
             let src = Gpr::new(src).unwrap();
             let size = OperandSize::Size64;
-            let dst = allocs.next(dst.to_reg().to_reg());
-            let dst = WritableGpr::from_writable_reg(Writable::from_reg(dst)).unwrap();
+            let dst = WritableGpr::from_writable_reg(allocs.next_writable(dst.to_writable_reg()))
+                .unwrap();
             Inst::MovRR { size, src, dst }.emit(&[], sink, info, state);
         }
 
         Inst::MovToPReg { src, dst } => {
             let src = allocs.next(src.to_reg());
             let src = Gpr::new(src).unwrap();
-            allocs.next_fixed_nonallocatable(*dst);
             let dst: Reg = (*dst).into();
             debug_assert!([regs::rsp(), regs::rbp(), regs::pinned_reg()].contains(&dst));
             let dst = WritableGpr::from_writable_reg(Writable::from_reg(dst)).unwrap();
@@ -961,7 +961,7 @@ pub(crate) fn emit(
                 }
 
                 RegMem::Mem { addr: src } => {
-                    let src = &src.finalize(state, sink).with_allocs(allocs);
+                    let src = &src.finalize(state, sink).clone();
 
                     emit_std_reg_mem(
                         sink,
@@ -979,7 +979,7 @@ pub(crate) fn emit(
 
         Inst::Mov64MR { src, dst } => {
             let dst = allocs.next(dst.to_reg().to_reg());
-            let src = &src.finalize(state, sink).with_allocs(allocs);
+            let src = &src.finalize(state, sink).clone();
 
             emit_std_reg_mem(
                 sink,
@@ -995,7 +995,7 @@ pub(crate) fn emit(
 
         Inst::LoadEffectiveAddress { addr, dst, size } => {
             let dst = allocs.next(dst.to_reg().to_reg());
-            let amode = addr.finalize(state, sink).with_allocs(allocs);
+            let amode = addr.finalize(state, sink).clone();
 
             // If this `lea` can actually get encoded as an `add` then do that
             // instead. Currently all candidate `iadd`s become an `lea`
@@ -1120,7 +1120,7 @@ pub(crate) fn emit(
                 }
 
                 RegMem::Mem { addr: src } => {
-                    let src = &src.finalize(state, sink).with_allocs(allocs);
+                    let src = &src.finalize(state, sink).clone();
 
                     emit_std_reg_mem(
                         sink,
@@ -1138,7 +1138,7 @@ pub(crate) fn emit(
 
         Inst::MovRM { size, src, dst } => {
             let src = allocs.next(src.to_reg());
-            let dst = &dst.finalize(state, sink).with_allocs(allocs);
+            let dst = &dst.finalize(state, sink).clone();
 
             let prefix = match size {
                 OperandSize::Size16 => LegacyPrefixes::_66,
@@ -1268,7 +1268,7 @@ pub(crate) fn emit(
                         emit_std_reg_reg(sink, prefix, opcode_bytes, 2, dst, reg, rex);
                     }
                     RegMemImm::Mem { addr } => {
-                        let addr = &addr.finalize(state, sink).with_allocs(allocs);
+                        let addr = &addr.finalize(state, sink).clone();
                         emit_std_reg_mem(sink, prefix, opcode_bytes, 2, dst, addr, rex, 0);
                     }
                     RegMemImm::Imm { .. } => unreachable!(),
@@ -1316,7 +1316,7 @@ pub(crate) fn emit(
                 }
 
                 RegMemImm::Mem { addr } => {
-                    let addr = &addr.finalize(state, sink).with_allocs(allocs);
+                    let addr = &addr.finalize(state, sink).clone();
                     // Whereas here we revert to the "normal" G-E ordering for CMP.
                     let opcode = match (*size, is_cmp) {
                         (OperandSize::Size8, true) => 0x3A,
@@ -1412,7 +1412,7 @@ pub(crate) fn emit(
                     emit_std_reg_reg(sink, prefix, opcode, 2, dst, reg, rex_flags);
                 }
                 RegMem::Mem { addr } => {
-                    let addr = &addr.finalize(state, sink).with_allocs(allocs);
+                    let addr = &addr.finalize(state, sink).clone();
                     emit_std_reg_mem(sink, prefix, opcode, 2, dst, addr, rex_flags, 0);
                 }
             }
@@ -1426,8 +1426,8 @@ pub(crate) fn emit(
             dst,
         } => {
             let alternative = allocs.next(alternative.to_reg());
-            let dst = allocs.next(dst.to_reg().to_reg());
-            debug_assert_eq!(alternative, dst);
+            let dst = allocs.next_writable(dst.to_writable_reg());
+            debug_assert_eq!(alternative, dst.to_reg());
             let consequent = allocs.next(consequent.clone().to_reg());
 
             // Lowering of the Select IR opcode when the input is an fcmp relies on the fact that
@@ -1447,14 +1447,14 @@ pub(crate) fn emit(
                     SseOpcode::Movdqa
                 }
             };
-            let inst = Inst::xmm_unary_rm_r(op, consequent.into(), Writable::from_reg(dst));
+            let inst = Inst::xmm_unary_rm_r(op, consequent.into(), dst);
             inst.emit(&[], sink, info, state);
 
             sink.bind_label(next, state.ctrl_plane_mut());
         }
 
         Inst::Push64 { src } => {
-            let src = src.clone().to_reg_mem_imm().with_allocs(allocs);
+            let src = src.clone().to_reg_mem_imm().clone();
 
             match src {
                 RegMemImm::Reg { reg } => {
@@ -1659,7 +1659,7 @@ pub(crate) fn emit(
         }
 
         Inst::CallUnknown { dest, opcode, info } => {
-            let dest = dest.with_allocs(allocs);
+            let dest = dest.clone();
 
             let start_offset = sink.cur_offset();
             match dest {
@@ -1774,7 +1774,7 @@ pub(crate) fn emit(
         }
 
         Inst::JmpUnknown { target } => {
-            let target = target.with_allocs(allocs);
+            let target = target.clone();
 
             match target {
                 RegMem::Reg { reg } => {
@@ -1806,17 +1806,17 @@ pub(crate) fn emit(
             }
         }
 
-        Inst::JmpTableSeq {
+        &Inst::JmpTableSeq {
             idx,
             tmp1,
             tmp2,
             ref targets,
-            default_target,
+            ref default_target,
             ..
         } => {
-            let idx = allocs.next(*idx);
-            let tmp1 = Writable::from_reg(allocs.next(tmp1.to_reg()));
-            let tmp2 = Writable::from_reg(allocs.next(tmp2.to_reg()));
+            let idx = allocs.next(idx);
+            let tmp1 = allocs.next_writable(tmp1);
+            let tmp2 = allocs.next_writable(tmp2);
 
             // This sequence is *one* instruction in the vcode, and is expanded only here at
             // emission time, because we cannot allow the regalloc to insert spills/reloads in
@@ -1936,7 +1936,7 @@ pub(crate) fn emit(
             dst: reg_g,
         } => {
             let reg_g = allocs.next(reg_g.to_reg().to_reg());
-            let src_e = src_e.clone().to_reg_mem().with_allocs(allocs);
+            let src_e = src_e.clone().to_reg_mem().clone();
 
             let rex = RexFlags::clear_w();
 
@@ -1989,7 +1989,7 @@ pub(crate) fn emit(
 
         Inst::XmmUnaryRmRImm { op, src, dst, imm } => {
             let dst = allocs.next(dst.to_reg().to_reg());
-            let src = src.clone().to_reg_mem().with_allocs(allocs);
+            let src = src.clone().to_reg_mem().clone();
             let rex = RexFlags::clear_w();
 
             let (prefix, opcode, len) = match op {
@@ -2017,7 +2017,7 @@ pub(crate) fn emit(
 
         Inst::XmmUnaryRmREvex { op, src, dst } => {
             let dst = allocs.next(dst.to_reg().to_reg());
-            let src = match src.clone().to_reg_mem().with_allocs(allocs) {
+            let src = match src.clone().to_reg_mem().clone() {
                 RegMem::Reg { reg } => {
                     RegisterOrAmode::Register(reg.to_real_reg().unwrap().hw_enc().into())
                 }
@@ -2044,7 +2044,7 @@ pub(crate) fn emit(
 
         Inst::XmmUnaryRmRImmEvex { op, src, dst, imm } => {
             let dst = allocs.next(dst.to_reg().to_reg());
-            let src = match src.clone().to_reg_mem().with_allocs(allocs) {
+            let src = match src.clone().to_reg_mem().clone() {
                 RegMem::Reg { reg } => {
                     RegisterOrAmode::Register(reg.to_real_reg().unwrap().hw_enc().into())
                 }
@@ -2095,7 +2095,7 @@ pub(crate) fn emit(
         } => {
             let src1 = allocs.next(src1.to_reg());
             let reg_g = allocs.next(reg_g.to_reg().to_reg());
-            let src_e = src_e.clone().to_reg_mem().with_allocs(allocs);
+            let src_e = src_e.clone().to_reg_mem().clone();
             debug_assert_eq!(src1, reg_g);
 
             let rex = RexFlags::clear_w();
@@ -2234,7 +2234,7 @@ pub(crate) fn emit(
             debug_assert_eq!(mask, regs::xmm0());
             let reg_g = allocs.next(dst.to_reg().to_reg());
             debug_assert_eq!(src1, reg_g);
-            let src_e = src2.clone().to_reg_mem().with_allocs(allocs);
+            let src_e = src2.clone().to_reg_mem().clone();
 
             let rex = RexFlags::clear_w();
             let (prefix, opcode, length) = match op {
@@ -2266,7 +2266,7 @@ pub(crate) fn emit(
 
             let dst = allocs.next(dst.to_reg().to_reg());
             let src1 = allocs.next(src1.to_reg());
-            let src2 = src2.clone().to_reg_mem_imm().with_allocs(allocs);
+            let src2 = src2.clone().to_reg_mem_imm().clone();
 
             // When the opcode is commutative, src1 is xmm{0..7}, and src2 is
             // xmm{8..15}, then we can swap the operands to save one byte on the
@@ -2456,7 +2456,7 @@ pub(crate) fn emit(
         } => {
             let dst = allocs.next(dst.to_reg().to_reg());
             let src1 = allocs.next(src1.to_reg());
-            let src2 = match src2.clone().to_reg_mem().with_allocs(allocs) {
+            let src2 = match src2.clone().to_reg_mem().clone() {
                 RegMem::Reg { reg } => {
                     RegisterOrAmode::Register(reg.to_real_reg().unwrap().hw_enc().into())
                 }
@@ -2495,7 +2495,7 @@ pub(crate) fn emit(
         } => {
             let dst = allocs.next(dst.to_reg().to_reg());
             let src1 = allocs.next(src1.to_reg());
-            let src2 = match src2.clone().to_reg_mem().with_allocs(allocs) {
+            let src2 = match src2.clone().to_reg_mem().clone() {
                 RegMem::Reg { reg } => {
                     RegisterOrAmode::Register(reg.to_real_reg().unwrap().hw_enc().into())
                 }
@@ -2534,7 +2534,7 @@ pub(crate) fn emit(
             let dst = allocs.next(dst.to_reg().to_reg());
             debug_assert_eq!(src1, dst);
             let src2 = allocs.next(src2.to_reg());
-            let src3 = match src3.clone().to_reg_mem().with_allocs(allocs) {
+            let src3 = match src3.clone().to_reg_mem().clone() {
                 RegMem::Reg { reg } => {
                     RegisterOrAmode::Register(reg.to_real_reg().unwrap().hw_enc().into())
                 }
@@ -2585,7 +2585,7 @@ pub(crate) fn emit(
         } => {
             let dst = allocs.next(dst.to_reg().to_reg());
             let src1 = allocs.next(src1.to_reg());
-            let src2 = match src2.clone().to_reg_mem().with_allocs(allocs) {
+            let src2 = match src2.clone().to_reg_mem().clone() {
                 RegMem::Reg { reg } => {
                     RegisterOrAmode::Register(reg.to_real_reg().unwrap().hw_enc().into())
                 }
@@ -2614,7 +2614,7 @@ pub(crate) fn emit(
 
         Inst::XmmUnaryRmRVex { op, src, dst } => {
             let dst = allocs.next(dst.to_reg().to_reg());
-            let src = match src.clone().to_reg_mem().with_allocs(allocs) {
+            let src = match src.clone().to_reg_mem().clone() {
                 RegMem::Reg { reg } => {
                     RegisterOrAmode::Register(reg.to_real_reg().unwrap().hw_enc().into())
                 }
@@ -2678,7 +2678,7 @@ pub(crate) fn emit(
 
         Inst::XmmUnaryRmRImmVex { op, src, dst, imm } => {
             let dst = allocs.next(dst.to_reg().to_reg());
-            let src = match src.clone().to_reg_mem().with_allocs(allocs) {
+            let src = match src.clone().to_reg_mem().clone() {
                 RegMem::Reg { reg } => {
                     RegisterOrAmode::Register(reg.to_real_reg().unwrap().hw_enc().into())
                 }
@@ -2718,7 +2718,7 @@ pub(crate) fn emit(
 
         Inst::XmmMovRMVex { op, src, dst } => {
             let src = allocs.next(src.to_reg());
-            let dst = dst.with_allocs(allocs).finalize(state, sink);
+            let dst = dst.clone().finalize(state, sink);
 
             let (prefix, map, opcode) = match op {
                 AvxOpcode::Vmovdqu => (LegacyPrefixes::_F3, OpcodeMap::_0F, 0x7F),
@@ -2740,7 +2740,7 @@ pub(crate) fn emit(
 
         Inst::XmmMovRMImmVex { op, src, dst, imm } => {
             let src = allocs.next(src.to_reg());
-            let dst = dst.with_allocs(allocs).finalize(state, sink);
+            let dst = dst.clone().finalize(state, sink);
 
             let (w, prefix, map, opcode) = match op {
                 AvxOpcode::Vpextrb => (false, LegacyPrefixes::_66, OpcodeMap::_0F3A, 0x14),
@@ -2831,7 +2831,7 @@ pub(crate) fn emit(
             src_size,
         } => {
             let dst = allocs.next(dst.to_reg().to_reg());
-            let src = match src.clone().to_reg_mem().with_allocs(allocs) {
+            let src = match src.clone().to_reg_mem().clone() {
                 RegMem::Reg { reg } => {
                     RegisterOrAmode::Register(reg.to_real_reg().unwrap().hw_enc().into())
                 }
@@ -2860,7 +2860,7 @@ pub(crate) fn emit(
 
         Inst::XmmCmpRmRVex { op, src1, src2 } => {
             let src1 = allocs.next(src1.to_reg());
-            let src2 = match src2.clone().to_reg_mem().with_allocs(allocs) {
+            let src2 = match src2.clone().to_reg_mem().clone() {
                 RegMem::Reg { reg } => {
                     RegisterOrAmode::Register(reg.to_real_reg().unwrap().hw_enc().into())
                 }
@@ -2902,7 +2902,7 @@ pub(crate) fn emit(
                 _ => None,
             };
             let src1 = allocs.next(src1.to_reg());
-            let src2 = match src2.clone().to_reg_mem().with_allocs(allocs) {
+            let src2 = match src2.clone().to_reg_mem().clone() {
                 RegMem::Reg { reg } => {
                     RegisterOrAmode::Register(reg.to_real_reg().unwrap().hw_enc().into())
                 }
@@ -2941,8 +2941,8 @@ pub(crate) fn emit(
         } => {
             let rhs = allocs.next(rhs.to_reg());
             let lhs = allocs.next(lhs.to_reg());
-            let dst = allocs.next(dst.to_reg().to_reg());
-            debug_assert_eq!(rhs, dst);
+            let dst = allocs.next_writable(dst.to_writable_reg());
+            debug_assert_eq!(rhs, dst.to_reg());
 
             // Generates the following sequence:
             // cmpss/cmpsd %lhs, %rhs_dst
@@ -2993,7 +2993,7 @@ pub(crate) fn emit(
                 _ => unreachable!(),
             };
 
-            let inst = Inst::xmm_cmp_rm_r(cmp_op, dst, RegMem::reg(lhs));
+            let inst = Inst::xmm_cmp_rm_r(cmp_op, dst.to_reg(), RegMem::reg(lhs));
             inst.emit(&[], sink, info, state);
 
             one_way_jmp(sink, CC::NZ, do_min_max);
@@ -3003,7 +3003,7 @@ pub(crate) fn emit(
             // and negative zero. These instructions merge the sign bits in that
             // case, and are no-ops otherwise.
             let op = if *is_min { or_op } else { and_op };
-            let inst = Inst::xmm_rm_r(op, RegMem::reg(lhs), Writable::from_reg(dst));
+            let inst = Inst::xmm_rm_r(op, RegMem::reg(lhs), dst);
             inst.emit(&[], sink, info, state);
 
             let inst = Inst::jmp_known(done);
@@ -3013,14 +3013,14 @@ pub(crate) fn emit(
             // read-only operand: perform an addition between the two operands, which has the
             // desired NaN propagation effects.
             sink.bind_label(propagate_nan, state.ctrl_plane_mut());
-            let inst = Inst::xmm_rm_r(add_op, RegMem::reg(lhs), Writable::from_reg(dst));
+            let inst = Inst::xmm_rm_r(add_op, RegMem::reg(lhs), dst);
             inst.emit(&[], sink, info, state);
 
             one_way_jmp(sink, CC::P, done);
 
             sink.bind_label(do_min_max, state.ctrl_plane_mut());
 
-            let inst = Inst::xmm_rm_r(min_max_op, RegMem::reg(lhs), Writable::from_reg(dst));
+            let inst = Inst::xmm_rm_r(min_max_op, RegMem::reg(lhs), dst);
             inst.emit(&[], sink, info, state);
 
             sink.bind_label(done, state.ctrl_plane_mut());
@@ -3036,7 +3036,7 @@ pub(crate) fn emit(
         } => {
             let src1 = allocs.next(*src1);
             let dst = allocs.next(dst.to_reg());
-            let src2 = src2.with_allocs(allocs);
+            let src2 = src2.clone();
             debug_assert_eq!(src1, dst);
 
             let (prefix, opcode, len) = match op {
@@ -3090,7 +3090,7 @@ pub(crate) fn emit(
 
         Inst::XmmMovRM { op, src, dst } => {
             let src = allocs.next(src.to_reg());
-            let dst = dst.with_allocs(allocs);
+            let dst = dst.clone();
 
             let (prefix, opcode) = match op {
                 SseOpcode::Movaps => (LegacyPrefixes::None, 0x0F29),
@@ -3108,7 +3108,7 @@ pub(crate) fn emit(
 
         Inst::XmmMovRMImm { op, src, dst, imm } => {
             let src = allocs.next(src.to_reg());
-            let dst = dst.with_allocs(allocs);
+            let dst = dst.clone();
 
             let (w, prefix, opcode) = match op {
                 SseOpcode::Pextrb => (false, LegacyPrefixes::_66, 0x0F3A14),
@@ -3180,7 +3180,7 @@ pub(crate) fn emit(
             src_size,
         } => {
             let reg_g = allocs.next(reg_g.to_reg().to_reg());
-            let src_e = src_e.clone().to_reg_mem().with_allocs(allocs);
+            let src_e = src_e.clone().to_reg_mem().clone();
 
             let (prefix, opcode) = match op {
                 // Movd and movq use the same opcode; the presence of the REX prefix (set below)
@@ -3202,7 +3202,7 @@ pub(crate) fn emit(
 
         Inst::XmmCmpRmR { op, src1, src2 } => {
             let src1 = allocs.next(src1.to_reg());
-            let src2 = src2.clone().to_reg_mem().with_allocs(allocs);
+            let src2 = src2.clone().to_reg_mem().clone();
 
             let rex = RexFlags::clear_w();
             let (prefix, opcode, len) = match op {
@@ -3233,7 +3233,7 @@ pub(crate) fn emit(
             let src1 = allocs.next(src1.to_reg());
             let dst = allocs.next(dst.to_reg().to_reg());
             assert_eq!(src1, dst);
-            let src2 = src2.clone().to_reg_mem().with_allocs(allocs);
+            let src2 = src2.clone().to_reg_mem().clone();
 
             let (prefix, opcode) = match op {
                 SseOpcode::Cvtsi2ss => (LegacyPrefixes::_F3, 0x0F2A),
@@ -3261,7 +3261,7 @@ pub(crate) fn emit(
         } => {
             let dst = allocs.next(dst.to_reg().to_reg());
             let src1 = allocs.next(src1.to_reg());
-            let src2 = match src2.clone().to_reg_mem().with_allocs(allocs) {
+            let src2 = match src2.clone().to_reg_mem().clone() {
                 RegMem::Reg { reg } => {
                     RegisterOrAmode::Register(reg.to_real_reg().unwrap().hw_enc().into())
                 }
@@ -3297,9 +3297,9 @@ pub(crate) fn emit(
             tmp_gpr2,
         } => {
             let src = allocs.next(src.to_reg());
-            let dst = allocs.next(dst.to_reg().to_reg());
-            let tmp_gpr1 = allocs.next(tmp_gpr1.to_reg().to_reg());
-            let tmp_gpr2 = allocs.next(tmp_gpr2.to_reg().to_reg());
+            let dst = allocs.next_writable(dst.to_writable_reg());
+            let tmp_gpr1 = allocs.next_writable(tmp_gpr1.to_writable_reg());
+            let tmp_gpr2 = allocs.next_writable(tmp_gpr2.to_writable_reg());
 
             // Note: this sequence is specific to 64-bit mode; a 32-bit mode would require a
             // different sequence.
@@ -3325,9 +3325,8 @@ pub(crate) fn emit(
             //
             //  done:
 
-            assert_ne!(src, tmp_gpr1);
-            assert_ne!(src, tmp_gpr2);
-            assert_ne!(tmp_gpr1, tmp_gpr2);
+            assert_ne!(src, tmp_gpr1.to_reg());
+            assert_ne!(src, tmp_gpr2.to_reg());
 
             let handle_negative = sink.get_label();
             let done = sink.get_label();
@@ -3347,7 +3346,7 @@ pub(crate) fn emit(
                 info,
                 state,
                 src,
-                Writable::from_reg(dst),
+                dst,
                 *dst_size == OperandSize::Size64,
             );
 
@@ -3358,7 +3357,7 @@ pub(crate) fn emit(
 
             // Divide x by two to get it in range for the signed conversion, keep the LSB, and
             // scale it back up on the FP side.
-            let inst = Inst::gen_move(Writable::from_reg(tmp_gpr1), src, types::I64);
+            let inst = Inst::gen_move(tmp_gpr1, src, types::I64);
             inst.emit(&[], sink, info, state);
 
             // tmp_gpr1 := src >> 1
@@ -3366,27 +3365,27 @@ pub(crate) fn emit(
                 OperandSize::Size64,
                 ShiftKind::ShiftRightLogical,
                 Imm8Gpr::new(Imm8Reg::Imm8 { imm: 1 }).unwrap(),
+                tmp_gpr1.to_reg(),
                 tmp_gpr1,
-                Writable::from_reg(tmp_gpr1),
             );
             inst.emit(&[], sink, info, state);
 
-            let inst = Inst::gen_move(Writable::from_reg(tmp_gpr2), src, types::I64);
+            let inst = Inst::gen_move(tmp_gpr2, src, types::I64);
             inst.emit(&[], sink, info, state);
 
             let inst = Inst::alu_rmi_r(
                 OperandSize::Size64,
                 AluRmiROpcode::And,
                 RegMemImm::imm(1),
-                Writable::from_reg(tmp_gpr2),
+                tmp_gpr2,
             );
             inst.emit(&[], sink, info, state);
 
             let inst = Inst::alu_rmi_r(
                 OperandSize::Size64,
                 AluRmiROpcode::Or,
-                RegMemImm::reg(tmp_gpr1),
-                Writable::from_reg(tmp_gpr2),
+                RegMemImm::reg(tmp_gpr1.to_reg()),
+                tmp_gpr2,
             );
             inst.emit(&[], sink, info, state);
 
@@ -3394,8 +3393,8 @@ pub(crate) fn emit(
                 sink,
                 info,
                 state,
-                tmp_gpr2,
-                Writable::from_reg(dst),
+                tmp_gpr2.to_reg(),
+                dst,
                 *dst_size == OperandSize::Size64,
             );
 
@@ -3404,7 +3403,7 @@ pub(crate) fn emit(
             } else {
                 SseOpcode::Addss
             };
-            let inst = Inst::xmm_rm_r(add_op, RegMem::reg(dst), Writable::from_reg(dst));
+            let inst = Inst::xmm_rm_r(add_op, RegMem::reg(dst.to_reg()), dst);
             inst.emit(&[], sink, info, state);
 
             sink.bind_label(done, state.ctrl_plane_mut());
@@ -3420,9 +3419,9 @@ pub(crate) fn emit(
             tmp_xmm,
         } => {
             let src = allocs.next(src.to_reg());
-            let dst = allocs.next(dst.to_reg().to_reg());
-            let tmp_gpr = allocs.next(tmp_gpr.to_reg().to_reg());
-            let tmp_xmm = allocs.next(tmp_xmm.to_reg().to_reg());
+            let dst = allocs.next_writable(dst.to_writable_reg());
+            let tmp_gpr = allocs.next_writable(tmp_gpr.to_writable_reg());
+            let tmp_xmm = allocs.next_writable(tmp_xmm.to_writable_reg());
 
             // Emits the following common sequence:
             //
@@ -3479,11 +3478,11 @@ pub(crate) fn emit(
             let done = sink.get_label();
 
             // The truncation.
-            let inst = Inst::xmm_to_gpr(trunc_op, src, Writable::from_reg(dst), *dst_size);
+            let inst = Inst::xmm_to_gpr(trunc_op, src, dst, *dst_size);
             inst.emit(&[], sink, info, state);
 
             // Compare against 1, in case of overflow the dst operand was INT_MIN.
-            let inst = Inst::cmp_rmi_r(*dst_size, dst, RegMemImm::imm(1));
+            let inst = Inst::cmp_rmi_r(*dst_size, dst.to_reg(), RegMemImm::imm(1));
             inst.emit(&[], sink, info, state);
 
             one_way_jmp(sink, CC::NO, done); // no overflow => done
@@ -3501,8 +3500,8 @@ pub(crate) fn emit(
                 let inst = Inst::alu_rmi_r(
                     *dst_size,
                     AluRmiROpcode::Xor,
-                    RegMemImm::reg(dst),
-                    Writable::from_reg(dst),
+                    RegMemImm::reg(dst.to_reg()),
+                    dst,
                 );
                 inst.emit(&[], sink, info, state);
 
@@ -3514,14 +3513,10 @@ pub(crate) fn emit(
                 // If the input was positive, saturate to INT_MAX.
 
                 // Zero out tmp_xmm.
-                let inst = Inst::xmm_rm_r(
-                    SseOpcode::Xorpd,
-                    RegMem::reg(tmp_xmm),
-                    Writable::from_reg(tmp_xmm),
-                );
+                let inst = Inst::xmm_rm_r(SseOpcode::Xorpd, RegMem::reg(tmp_xmm.to_reg()), tmp_xmm);
                 inst.emit(&[], sink, info, state);
 
-                let inst = Inst::xmm_cmp_rm_r(cmp_op, tmp_xmm, RegMem::reg(src));
+                let inst = Inst::xmm_cmp_rm_r(cmp_op, tmp_xmm.to_reg(), RegMem::reg(src));
                 inst.emit(&[], sink, info, state);
 
                 // Jump if >= to done.
@@ -3529,14 +3524,10 @@ pub(crate) fn emit(
 
                 // Otherwise, put INT_MAX.
                 if *dst_size == OperandSize::Size64 {
-                    let inst = Inst::imm(
-                        OperandSize::Size64,
-                        0x7fffffffffffffff,
-                        Writable::from_reg(dst),
-                    );
+                    let inst = Inst::imm(OperandSize::Size64, 0x7fffffffffffffff, dst);
                     inst.emit(&[], sink, info, state);
                 } else {
-                    let inst = Inst::imm(OperandSize::Size32, 0x7fffffff, Writable::from_reg(dst));
+                    let inst = Inst::imm(OperandSize::Size32, 0x7fffffff, dst);
                     inst.emit(&[], sink, info, state);
                 }
             } else {
@@ -3554,8 +3545,7 @@ pub(crate) fn emit(
                 match *src_size {
                     OperandSize::Size32 => {
                         let cst = Ieee32::pow2(output_bits - 1).neg().bits();
-                        let inst =
-                            Inst::imm(OperandSize::Size32, cst as u64, Writable::from_reg(tmp_gpr));
+                        let inst = Inst::imm(OperandSize::Size32, cst as u64, tmp_gpr);
                         inst.emit(&[], sink, info, state);
                     }
                     OperandSize::Size64 => {
@@ -3567,22 +3557,17 @@ pub(crate) fn emit(
                         } else {
                             Ieee64::pow2(output_bits - 1).neg()
                         };
-                        let inst =
-                            Inst::imm(OperandSize::Size64, cst.bits(), Writable::from_reg(tmp_gpr));
+                        let inst = Inst::imm(OperandSize::Size64, cst.bits(), tmp_gpr);
                         inst.emit(&[], sink, info, state);
                     }
                     _ => unreachable!(),
                 }
 
-                let inst = Inst::gpr_to_xmm(
-                    cast_op,
-                    RegMem::reg(tmp_gpr),
-                    *src_size,
-                    Writable::from_reg(tmp_xmm),
-                );
+                let inst =
+                    Inst::gpr_to_xmm(cast_op, RegMem::reg(tmp_gpr.to_reg()), *src_size, tmp_xmm);
                 inst.emit(&[], sink, info, state);
 
-                let inst = Inst::xmm_cmp_rm_r(cmp_op, src, RegMem::reg(tmp_xmm));
+                let inst = Inst::xmm_cmp_rm_r(cmp_op, src, RegMem::reg(tmp_xmm.to_reg()));
                 inst.emit(&[], sink, info, state);
 
                 // no trap if src >= or > threshold
@@ -3592,14 +3577,10 @@ pub(crate) fn emit(
                 // If positive, it was a real overflow.
 
                 // Zero out the tmp_xmm register.
-                let inst = Inst::xmm_rm_r(
-                    SseOpcode::Xorpd,
-                    RegMem::reg(tmp_xmm),
-                    Writable::from_reg(tmp_xmm),
-                );
+                let inst = Inst::xmm_rm_r(SseOpcode::Xorpd, RegMem::reg(tmp_xmm.to_reg()), tmp_xmm);
                 inst.emit(&[], sink, info, state);
 
-                let inst = Inst::xmm_cmp_rm_r(cmp_op, tmp_xmm, RegMem::reg(src));
+                let inst = Inst::xmm_cmp_rm_r(cmp_op, tmp_xmm.to_reg(), RegMem::reg(src));
                 inst.emit(&[], sink, info, state);
 
                 // no trap if 0 >= src
@@ -3621,10 +3602,10 @@ pub(crate) fn emit(
             tmp_xmm2,
         } => {
             let src = allocs.next(src.to_reg());
-            let dst = allocs.next(dst.to_reg().to_reg());
-            let tmp_gpr = allocs.next(tmp_gpr.to_reg().to_reg());
-            let tmp_xmm = allocs.next(tmp_xmm.to_reg().to_reg());
-            let tmp_xmm2 = allocs.next(tmp_xmm2.to_reg().to_reg());
+            let dst = allocs.next_writable(dst.to_writable_reg());
+            let tmp_gpr = allocs.next_writable(tmp_gpr.to_writable_reg());
+            let tmp_xmm = allocs.next_writable(tmp_xmm.to_writable_reg());
+            let tmp_xmm2 = allocs.next_writable(tmp_xmm2.to_writable_reg());
 
             // The only difference in behavior between saturating and non-saturating is how we
             // handle errors. Emits the following sequence:
@@ -3660,7 +3641,7 @@ pub(crate) fn emit(
             //
             // done:
 
-            assert_ne!(tmp_xmm, src, "tmp_xmm clobbers src!");
+            assert_ne!(tmp_xmm.to_reg(), src, "tmp_xmm clobbers src!");
 
             let (sub_op, cast_op, cmp_op, trunc_op) = match src_size {
                 OperandSize::Size32 => (
@@ -3686,18 +3667,13 @@ pub(crate) fn emit(
                 _ => unreachable!(),
             };
 
-            let inst = Inst::imm(*src_size, cst, Writable::from_reg(tmp_gpr));
+            let inst = Inst::imm(*src_size, cst, tmp_gpr);
             inst.emit(&[], sink, info, state);
 
-            let inst = Inst::gpr_to_xmm(
-                cast_op,
-                RegMem::reg(tmp_gpr),
-                *src_size,
-                Writable::from_reg(tmp_xmm),
-            );
+            let inst = Inst::gpr_to_xmm(cast_op, RegMem::reg(tmp_gpr.to_reg()), *src_size, tmp_xmm);
             inst.emit(&[], sink, info, state);
 
-            let inst = Inst::xmm_cmp_rm_r(cmp_op, src, RegMem::reg(tmp_xmm));
+            let inst = Inst::xmm_cmp_rm_r(cmp_op, src, RegMem::reg(tmp_xmm.to_reg()));
             inst.emit(&[], sink, info, state);
 
             let handle_large = sink.get_label();
@@ -3710,8 +3686,8 @@ pub(crate) fn emit(
                 let inst = Inst::alu_rmi_r(
                     *dst_size,
                     AluRmiROpcode::Xor,
-                    RegMemImm::reg(dst),
-                    Writable::from_reg(dst),
+                    RegMemImm::reg(dst.to_reg()),
+                    dst,
                 );
                 inst.emit(&[], sink, info, state);
 
@@ -3727,10 +3703,10 @@ pub(crate) fn emit(
             // Actual truncation for small inputs: if the result is not positive, then we had an
             // overflow.
 
-            let inst = Inst::xmm_to_gpr(trunc_op, src, Writable::from_reg(dst), *dst_size);
+            let inst = Inst::xmm_to_gpr(trunc_op, src, dst, *dst_size);
             inst.emit(&[], sink, info, state);
 
-            let inst = Inst::cmp_rmi_r(*dst_size, dst, RegMemImm::imm(0));
+            let inst = Inst::cmp_rmi_r(*dst_size, dst.to_reg(), RegMemImm::imm(0));
             inst.emit(&[], sink, info, state);
 
             one_way_jmp(sink, CC::NL, done); // if dst >= 0, jump to done
@@ -3741,8 +3717,8 @@ pub(crate) fn emit(
                 let inst = Inst::alu_rmi_r(
                     *dst_size,
                     AluRmiROpcode::Xor,
-                    RegMemImm::reg(dst),
-                    Writable::from_reg(dst),
+                    RegMemImm::reg(dst.to_reg()),
+                    dst,
                 );
                 inst.emit(&[], sink, info, state);
 
@@ -3758,16 +3734,16 @@ pub(crate) fn emit(
 
             sink.bind_label(handle_large, state.ctrl_plane_mut());
 
-            let inst = Inst::gen_move(Writable::from_reg(tmp_xmm2), src, types::F64);
+            let inst = Inst::gen_move(tmp_xmm2, src, types::F64);
             inst.emit(&[], sink, info, state);
 
-            let inst = Inst::xmm_rm_r(sub_op, RegMem::reg(tmp_xmm), Writable::from_reg(tmp_xmm2));
+            let inst = Inst::xmm_rm_r(sub_op, RegMem::reg(tmp_xmm.to_reg()), tmp_xmm2);
             inst.emit(&[], sink, info, state);
 
-            let inst = Inst::xmm_to_gpr(trunc_op, tmp_xmm2, Writable::from_reg(dst), *dst_size);
+            let inst = Inst::xmm_to_gpr(trunc_op, tmp_xmm2.to_reg(), dst, *dst_size);
             inst.emit(&[], sink, info, state);
 
-            let inst = Inst::cmp_rmi_r(*dst_size, dst, RegMemImm::imm(0));
+            let inst = Inst::cmp_rmi_r(*dst_size, dst.to_reg(), RegMemImm::imm(0));
             inst.emit(&[], sink, info, state);
 
             if *is_saturating {
@@ -3783,7 +3759,7 @@ pub(crate) fn emit(
                     } else {
                         u32::max_value() as u64
                     },
-                    Writable::from_reg(dst),
+                    dst,
                 );
                 inst.emit(&[], sink, info, state);
 
@@ -3796,14 +3772,14 @@ pub(crate) fn emit(
             }
 
             if *dst_size == OperandSize::Size64 {
-                let inst = Inst::imm(OperandSize::Size64, 1 << 63, Writable::from_reg(tmp_gpr));
+                let inst = Inst::imm(OperandSize::Size64, 1 << 63, tmp_gpr);
                 inst.emit(&[], sink, info, state);
 
                 let inst = Inst::alu_rmi_r(
                     OperandSize::Size64,
                     AluRmiROpcode::Add,
-                    RegMemImm::reg(tmp_gpr),
-                    Writable::from_reg(dst),
+                    RegMemImm::reg(tmp_gpr.to_reg()),
+                    dst,
                 );
                 inst.emit(&[], sink, info, state);
             } else {
@@ -3811,7 +3787,7 @@ pub(crate) fn emit(
                     OperandSize::Size32,
                     AluRmiROpcode::Add,
                     RegMemImm::imm(1 << 31),
-                    Writable::from_reg(dst),
+                    dst,
                 );
                 inst.emit(&[], sink, info, state);
             }
@@ -3881,7 +3857,7 @@ pub(crate) fn emit(
             let replacement = allocs.next(*replacement);
             let expected = allocs.next(*expected);
             let dst_old = allocs.next(dst_old.to_reg());
-            let mem = mem.with_allocs(allocs);
+            let mem = mem.clone();
 
             debug_assert_eq!(expected, regs::rax());
             debug_assert_eq!(dst_old, regs::rax());
@@ -3912,7 +3888,7 @@ pub(crate) fn emit(
             let temp = allocs.next_writable(*temp);
             let dst_old = allocs.next_writable(*dst_old);
             debug_assert_eq!(dst_old.to_reg(), regs::rax());
-            let mem = mem.finalize(state, sink).with_allocs(allocs);
+            let mem = mem.finalize(state, sink).clone();
 
             // Emit this:
             //    mov{zbq,zwq,zlq,q}     (%r_address), %rax    // rax = old value
diff --git a/cranelift/codegen/src/isa/x64/inst/mod.rs b/cranelift/codegen/src/isa/x64/inst/mod.rs
index 45790ad5b..593f1620c 100644
--- a/cranelift/codegen/src/isa/x64/inst/mod.rs
+++ b/cranelift/codegen/src/isa/x64/inst/mod.rs
@@ -1465,7 +1465,6 @@ impl PrettyPrint for Inst {
             }
 
             Inst::MovFromPReg { src, dst } => {
-                allocs.next_fixed_nonallocatable(*src);
                 let src: Reg = (*src).into();
                 let src = regs::show_ireg_sized(src, 8);
                 let dst = pretty_print_reg(dst.to_reg().to_reg(), 8, allocs);
@@ -1475,7 +1474,6 @@ impl PrettyPrint for Inst {
 
             Inst::MovToPReg { src, dst } => {
                 let src = pretty_print_reg(src.to_reg(), 8, allocs);
-                allocs.next_fixed_nonallocatable(*dst);
                 let dst: Reg = (*dst).into();
                 let dst = regs::show_ireg_sized(dst, 8);
                 let op = ljustify("movq".to_string());
diff --git a/cranelift/codegen/src/machinst/reg.rs b/cranelift/codegen/src/machinst/reg.rs
index 8dbcd32fa..269c95c8a 100644
--- a/cranelift/codegen/src/machinst/reg.rs
+++ b/cranelift/codegen/src/machinst/reg.rs
@@ -504,8 +504,6 @@ impl AllocationConsumer {
         Self
     }
 
-    pub fn next_fixed_nonallocatable(&mut self, _preg: PReg) {}
-
     pub fn next(&mut self, pre_regalloc_reg: Reg) -> Reg {
         pre_regalloc_reg
     }
diff --git a/crates/wasi/src/preview1.rs b/crates/wasi/src/preview1.rs
index 36075e8a4..05155d23e 100644
--- a/crates/wasi/src/preview1.rs
+++ b/crates/wasi/src/preview1.rs
@@ -5,7 +5,7 @@
 //! support for WASIp2 available at [the crate root](crate), but that's just an
 //! internal implementation detail.
 //!
-//! Unlike the crate root support for WASIp1 centers around two APIs:
+//! Unlike the crate root, support for WASIp1 centers around two APIs:
 //!
 //! * [`WasiP1Ctx`]
 //! * [`add_to_linker_sync`] (or [`add_to_linker_async`])
diff --git a/crates/wasmtime/src/runtime/externals/global.rs b/crates/wasmtime/src/runtime/externals/global.rs
index 4623bce68..ae2b1e460 100644
--- a/crates/wasmtime/src/runtime/externals/global.rs
+++ b/crates/wasmtime/src/runtime/externals/global.rs
@@ -216,7 +216,7 @@ impl Global {
 
     pub(crate) fn trace_root(&self, store: &mut StoreOpaque, gc_roots_list: &mut GcRootsList) {
         if let Some(ref_ty) = self._ty(store).content().as_ref() {
-            if !ref_ty.is_gc_heap_type() {
+            if !ref_ty.is_vmgcref_type_and_points_to_object() {
                 return;
             }
 
diff --git a/crates/wasmtime/src/runtime/externals/table.rs b/crates/wasmtime/src/runtime/externals/table.rs
index db63ca8e6..67d1aec8c 100644
--- a/crates/wasmtime/src/runtime/externals/table.rs
+++ b/crates/wasmtime/src/runtime/externals/table.rs
@@ -375,7 +375,11 @@ impl Table {
     }
 
     pub(crate) fn trace_roots(&self, store: &mut StoreOpaque, gc_roots_list: &mut GcRootsList) {
-        if !self._ty(store).element().is_gc_heap_type() {
+        if !self
+            ._ty(store)
+            .element()
+            .is_vmgcref_type_and_points_to_object()
+        {
             return;
         }
 
diff --git a/crates/wasmtime/src/runtime/func.rs b/crates/wasmtime/src/runtime/func.rs
index 3e0611766..3579a2049 100644
--- a/crates/wasmtime/src/runtime/func.rs
+++ b/crates/wasmtime/src/runtime/func.rs
@@ -90,12 +90,14 @@ impl NoFunc {
 
     /// Get the null `(ref null nofunc)` (aka `nullfuncref`) reference as a
     /// [`Ref`].
+    #[inline]
     pub fn null_ref() -> Ref {
         Ref::Func(None)
     }
 
     /// Get the null `(ref null nofunc)` (aka `nullfuncref`) reference as a
     /// [`Val`].
+    #[inline]
     pub fn null_val() -> Val {
         Val::FuncRef(None)
     }
@@ -620,25 +622,27 @@ impl Func {
     /// function being called is known statically so the type signature can
     /// be inferred. Rust types will map to WebAssembly types as follows:
     ///
-    /// | Rust Argument Type                | WebAssembly Type                      |
-    /// |-----------------------------------|---------------------------------------|
-    /// | `i32`                             | `i32`                                 |
-    /// | `u32`                             | `i32`                                 |
-    /// | `i64`                             | `i64`                                 |
-    /// | `u64`                             | `i64`                                 |
-    /// | `f32`                             | `f32`                                 |
-    /// | `f64`                             | `f64`                                 |
-    /// | `V128` on x86-64 and aarch64 only | `v128`                                |
-    /// | `Option<Func>`                    | `funcref` aka `(ref null func)`       |
-    /// | `Func`                            | `(ref func)`                          |
-    /// | `Option<Nofunc>`                  | `nullfuncref` aka `(ref null nofunc)` |
-    /// | `NoFunc`                          | `(ref nofunc)`                        |
-    /// | `Option<ExternRef>`               | `externref` aka `(ref null extern)`   |
-    /// | `ExternRef`                       | `(ref extern)`                        |
-    /// | `Option<AnyRef>`                  | `anyref` aka `(ref null any)`         |
-    /// | `AnyRef`                          | `(ref any)`                           |
-    /// | `Option<I31>`                     | `i31ref` aka `(ref null i31)`         |
-    /// | `I31`                             | `(ref i31)`                           |
+    /// | Rust Argument Type                | WebAssembly Type                          |
+    /// |-----------------------------------|-------------------------------------------|
+    /// | `i32`                             | `i32`                                     |
+    /// | `u32`                             | `i32`                                     |
+    /// | `i64`                             | `i64`                                     |
+    /// | `u64`                             | `i64`                                     |
+    /// | `f32`                             | `f32`                                     |
+    /// | `f64`                             | `f64`                                     |
+    /// | `V128` on x86-64 and aarch64 only | `v128`                                    |
+    /// | `Option<Func>`                    | `funcref` aka `(ref null func)`           |
+    /// | `Func`                            | `(ref func)`                              |
+    /// | `Option<Nofunc>`                  | `nullfuncref` aka `(ref null nofunc)`     |
+    /// | `NoFunc`                          | `(ref nofunc)`                            |
+    /// | `Option<ExternRef>`               | `externref` aka `(ref null extern)`       |
+    /// | `ExternRef`                       | `(ref extern)`                            |
+    /// | `Option<NoExtern>`                | `nullexternref` aka `(ref null noextern)` |
+    /// | `NoExtern`                        | `(ref noextern)`                          |
+    /// | `Option<AnyRef>`                  | `anyref` aka `(ref null any)`             |
+    /// | `AnyRef`                          | `(ref any)`                               |
+    /// | `Option<I31>`                     | `i31ref` aka `(ref null i31)`             |
+    /// | `I31`                             | `(ref i31)`                               |
     ///
     /// Any of the Rust types can be returned from the closure as well, in
     /// addition to some extra types
@@ -1391,25 +1395,27 @@ impl Func {
     ///
     /// Translation between Rust types and WebAssembly types looks like:
     ///
-    /// | WebAssembly                           | Rust                                  |
-    /// |---------------------------------------|---------------------------------------|
-    /// | `i32`                                 | `i32` or `u32`                        |
-    /// | `i64`                                 | `i64` or `u64`                        |
-    /// | `f32`                                 | `f32`                                 |
-    /// | `f64`                                 | `f64`                                 |
-    /// | `externref` aka `(ref null extern)`   | `Option<ExternRef>`                   |
-    /// | `(ref extern)`                        | `ExternRef`                           |
-    /// | `anyref` aka `(ref null any)`         | `Option<AnyRef>`                      |
-    /// | `(ref any)`                           | `AnyRef`                              |
-    /// | `i31ref` aka `(ref null i31)`         | `Option<I31>`                         |
-    /// | `(ref i31)`                           | `I31`                                 |
-    /// | `funcref` aka `(ref null func)`       | `Option<Func>`                        |
-    /// | `(ref func)`                          | `Func`                                |
-    /// | `(ref null <func type index>)`        | `Option<Func>`                        |
-    /// | `(ref <func type index>)`             | `Func`                                |
-    /// | `nullfuncref` aka `(ref null nofunc)` | `Option<NoFunc>`                      |
-    /// | `(ref nofunc)`                        | `NoFunc`                              |
-    /// | `v128`                                | `V128` on `x86-64` and `aarch64` only |
+    /// | WebAssembly                               | Rust                                  |
+    /// |-------------------------------------------|---------------------------------------|
+    /// | `i32`                                     | `i32` or `u32`                        |
+    /// | `i64`                                     | `i64` or `u64`                        |
+    /// | `f32`                                     | `f32`                                 |
+    /// | `f64`                                     | `f64`                                 |
+    /// | `externref` aka `(ref null extern)`       | `Option<ExternRef>`                   |
+    /// | `(ref extern)`                            | `ExternRef`                           |
+    /// | `(ref noextern)`                          | `NoExtern`                            |
+    /// | `nullexternref` aka `(ref null noextern)` | `Option<NoExtern>`                    |
+    /// | `anyref` aka `(ref null any)`             | `Option<AnyRef>`                      |
+    /// | `(ref any)`                               | `AnyRef`                              |
+    /// | `i31ref` aka `(ref null i31)`             | `Option<I31>`                         |
+    /// | `(ref i31)`                               | `I31`                                 |
+    /// | `funcref` aka `(ref null func)`           | `Option<Func>`                        |
+    /// | `(ref func)`                              | `Func`                                |
+    /// | `(ref null <func type index>)`            | `Option<Func>`                        |
+    /// | `(ref <func type index>)`                 | `Func`                                |
+    /// | `nullfuncref` aka `(ref null nofunc)`     | `Option<NoFunc>`                      |
+    /// | `(ref nofunc)`                            | `NoFunc`                              |
+    /// | `v128`                                    | `V128` on `x86-64` and `aarch64` only |
     ///
     /// (Note that this mapping is the same as that of [`Func::wrap`]).
     ///
diff --git a/crates/wasmtime/src/runtime/func/typed.rs b/crates/wasmtime/src/runtime/func/typed.rs
index 0fa6f01bd..79777dea4 100644
--- a/crates/wasmtime/src/runtime/func/typed.rs
+++ b/crates/wasmtime/src/runtime/func/typed.rs
@@ -154,7 +154,7 @@ where
         #[cfg(feature = "gc")]
         {
             // See the comment in `Func::call_impl_check_args`.
-            let num_gc_refs = _params.non_i31_gc_refs_count();
+            let num_gc_refs = _params.vmgcref_pointing_to_object_count();
             if let Some(num_gc_refs) = NonZeroUsize::new(num_gc_refs) {
                 return _store
                     .unwrap_gc_store()
@@ -329,9 +329,18 @@ pub unsafe trait WasmTy: Send {
         actual: &HeapType,
     ) -> Result<()>;
 
-    // Is this an externref?
+    // Is this a GC-managed reference that actually points to a GC object? That
+    // is, `self` is *not* an `i31`, null reference, or uninhabited type.
+    //
+    // Note that it is okay if this returns false positives (i.e. `true` for
+    // `Rooted<AnyRef>` without actually looking up the rooted `anyref` in the
+    // store and reflecting on it to determine whether it is actually an
+    // `i31`). However, it is not okay if this returns false negatives.
     #[doc(hidden)]
-    fn is_non_i31_gc_ref(&self) -> bool;
+    #[inline]
+    fn is_vmgcref_and_points_to_object(&self) -> bool {
+        Self::valtype().is_vmgcref_type_and_points_to_object()
+    }
 
     // Construct a `Self::Abi` from the given `ValRaw`.
     #[doc(hidden)]
@@ -394,10 +403,6 @@ macro_rules! integers {
                 unreachable!()
             }
             #[inline]
-            fn is_non_i31_gc_ref(&self) -> bool {
-                false
-            }
-            #[inline]
             unsafe fn abi_from_raw(raw: *mut ValRaw) -> $primitive {
                 (*raw).$get_primitive()
             }
@@ -442,10 +447,6 @@ macro_rules! floats {
                 unreachable!()
             }
             #[inline]
-            fn is_non_i31_gc_ref(&self) -> bool {
-                false
-            }
-            #[inline]
             unsafe fn abi_from_raw(raw: *mut ValRaw) -> $float {
                 $float::from_bits((*raw).$get_float())
             }
@@ -490,7 +491,7 @@ unsafe impl WasmTy for NoFunc {
     }
 
     #[inline]
-    fn is_non_i31_gc_ref(&self) -> bool {
+    fn is_vmgcref_and_points_to_object(&self) -> bool {
         match self._inner {}
     }
 
@@ -543,11 +544,6 @@ unsafe impl WasmTy for Option<NoFunc> {
         }
     }
 
-    #[inline]
-    fn is_non_i31_gc_ref(&self) -> bool {
-        false
-    }
-
     #[inline]
     unsafe fn abi_from_raw(_raw: *mut ValRaw) -> Self::Abi {
         ptr::null_mut()
@@ -594,11 +590,6 @@ unsafe impl WasmTy for Func {
             .context("argument type mismatch for reference to concrete type")
     }
 
-    #[inline]
-    fn is_non_i31_gc_ref(&self) -> bool {
-        false
-    }
-
     #[inline]
     unsafe fn abi_from_raw(raw: *mut ValRaw) -> Self::Abi {
         let p = (*raw).get_funcref();
@@ -656,11 +647,6 @@ unsafe impl WasmTy for Option<Func> {
         }
     }
 
-    #[inline]
-    fn is_non_i31_gc_ref(&self) -> bool {
-        false
-    }
-
     #[inline]
     unsafe fn abi_from_raw(raw: *mut ValRaw) -> Self::Abi {
         (*raw).get_funcref() as Self::Abi
@@ -703,7 +689,7 @@ pub unsafe trait WasmParams: Send {
     ) -> Result<()>;
 
     #[doc(hidden)]
-    fn non_i31_gc_refs_count(&self) -> usize;
+    fn vmgcref_pointing_to_object_count(&self) -> usize;
 
     #[doc(hidden)]
     fn into_abi(self, store: &mut AutoAssertNoGc<'_>, func_ty: &FuncType) -> Result<Self::Abi>;
@@ -734,8 +720,8 @@ where
     }
 
     #[inline]
-    fn non_i31_gc_refs_count(&self) -> usize {
-        T::is_non_i31_gc_ref(self) as usize
+    fn vmgcref_pointing_to_object_count(&self) -> usize {
+        T::is_vmgcref_and_points_to_object(self) as usize
     }
 
     #[inline]
@@ -786,10 +772,10 @@ macro_rules! impl_wasm_params {
             }
 
             #[inline]
-            fn non_i31_gc_refs_count(&self) -> usize {
+            fn vmgcref_pointing_to_object_count(&self) -> usize {
                 let ($(ref $t,)*) = self;
                 0 $(
-                    + $t.is_non_i31_gc_ref() as usize
+                    + $t.is_vmgcref_and_points_to_object() as usize
                 )*
             }
 
diff --git a/crates/wasmtime/src/runtime/gc.rs b/crates/wasmtime/src/runtime/gc.rs
index 61ed99d8a..f6697b105 100644
--- a/crates/wasmtime/src/runtime/gc.rs
+++ b/crates/wasmtime/src/runtime/gc.rs
@@ -8,6 +8,9 @@ mod disabled;
 #[cfg(not(feature = "gc"))]
 pub use disabled::*;
 
+mod noextern;
+pub use noextern::NoExtern;
+
 use core::fmt;
 use core::ops::Deref;
 
diff --git a/crates/wasmtime/src/runtime/gc/enabled/anyref.rs b/crates/wasmtime/src/runtime/gc/enabled/anyref.rs
index 946293add..49344a97c 100644
--- a/crates/wasmtime/src/runtime/gc/enabled/anyref.rs
+++ b/crates/wasmtime/src/runtime/gc/enabled/anyref.rs
@@ -264,11 +264,6 @@ unsafe impl WasmTy for Rooted<AnyRef> {
         unreachable!()
     }
 
-    #[inline]
-    fn is_non_i31_gc_ref(&self) -> bool {
-        true
-    }
-
     #[inline]
     unsafe fn abi_from_raw(raw: *mut ValRaw) -> Self::Abi {
         let raw = (*raw).get_externref();
@@ -320,8 +315,8 @@ unsafe impl WasmTy for Option<Rooted<AnyRef>> {
     }
 
     #[inline]
-    fn is_non_i31_gc_ref(&self) -> bool {
-        true
+    fn is_vmgcref_and_points_to_object(&self) -> bool {
+        self.is_some()
     }
 
     #[inline]
@@ -371,11 +366,6 @@ unsafe impl WasmTy for ManuallyRooted<AnyRef> {
         unreachable!()
     }
 
-    #[inline]
-    fn is_non_i31_gc_ref(&self) -> bool {
-        true
-    }
-
     #[inline]
     unsafe fn abi_from_raw(raw: *mut ValRaw) -> Self::Abi {
         let externref = (*raw).get_externref();
@@ -432,8 +422,8 @@ unsafe impl WasmTy for Option<ManuallyRooted<AnyRef>> {
     }
 
     #[inline]
-    fn is_non_i31_gc_ref(&self) -> bool {
-        true
+    fn is_vmgcref_and_points_to_object(&self) -> bool {
+        self.is_some()
     }
 
     #[inline]
diff --git a/crates/wasmtime/src/runtime/gc/enabled/externref.rs b/crates/wasmtime/src/runtime/gc/enabled/externref.rs
index 7e389d4cb..a57382f9e 100644
--- a/crates/wasmtime/src/runtime/gc/enabled/externref.rs
+++ b/crates/wasmtime/src/runtime/gc/enabled/externref.rs
@@ -441,11 +441,6 @@ unsafe impl WasmTy for Rooted<ExternRef> {
         unreachable!()
     }
 
-    #[inline]
-    fn is_non_i31_gc_ref(&self) -> bool {
-        true
-    }
-
     #[inline]
     unsafe fn abi_from_raw(raw: *mut ValRaw) -> Self::Abi {
         let raw = (*raw).get_externref();
@@ -497,8 +492,8 @@ unsafe impl WasmTy for Option<Rooted<ExternRef>> {
     }
 
     #[inline]
-    fn is_non_i31_gc_ref(&self) -> bool {
-        true
+    fn is_vmgcref_and_points_to_object(&self) -> bool {
+        self.is_some()
     }
 
     #[inline]
@@ -549,7 +544,7 @@ unsafe impl WasmTy for ManuallyRooted<ExternRef> {
     }
 
     #[inline]
-    fn is_non_i31_gc_ref(&self) -> bool {
+    fn is_vmgcref_and_points_to_object(&self) -> bool {
         true
     }
 
@@ -609,8 +604,8 @@ unsafe impl WasmTy for Option<ManuallyRooted<ExternRef>> {
     }
 
     #[inline]
-    fn is_non_i31_gc_ref(&self) -> bool {
-        true
+    fn is_vmgcref_and_points_to_object(&self) -> bool {
+        self.is_some()
     }
 
     #[inline]
diff --git a/crates/wasmtime/src/runtime/gc/enabled/i31.rs b/crates/wasmtime/src/runtime/gc/enabled/i31.rs
index 9d632a6f2..ddf7f1b39 100644
--- a/crates/wasmtime/src/runtime/gc/enabled/i31.rs
+++ b/crates/wasmtime/src/runtime/gc/enabled/i31.rs
@@ -243,11 +243,6 @@ unsafe impl WasmTy for I31 {
         unreachable!()
     }
 
-    #[inline]
-    fn is_non_i31_gc_ref(&self) -> bool {
-        false
-    }
-
     #[inline]
     unsafe fn abi_from_raw(raw: *mut ValRaw) -> Self::Abi {
         let raw = (*raw).get_anyref();
@@ -306,11 +301,6 @@ unsafe impl WasmTy for Option<I31> {
         unreachable!()
     }
 
-    #[inline]
-    fn is_non_i31_gc_ref(&self) -> bool {
-        false
-    }
-
     #[inline]
     unsafe fn abi_from_raw(raw: *mut ValRaw) -> Self::Abi {
         let raw = (*raw).get_anyref();
diff --git a/crates/wasmtime/src/runtime/gc/noextern.rs b/crates/wasmtime/src/runtime/gc/noextern.rs
new file mode 100644
index 000000000..d7d4a0b3d
--- /dev/null
+++ b/crates/wasmtime/src/runtime/gc/noextern.rs
@@ -0,0 +1,179 @@
+use crate::{
+    store::{AutoAssertNoGc, StoreOpaque},
+    HeapType, Ref, RefType, Result, Uninhabited, Val, ValRaw, ValType, WasmTy,
+};
+
+/// A reference to the abstract `noextern` heap value.
+///
+/// The are no instances of `(ref noextern)`: it is an uninhabited type.
+///
+/// There is precisely one instance of `(ref null noextern)`, aka `nullexternref`:
+/// the null reference.
+///
+/// This `NoExtern` Rust type's sole purpose is for use with
+/// [`Func::wrap`][crate::Func::wrap]- and
+/// [`Func::typed`][crate::Func::typed]-style APIs for statically typing a
+/// function as taking or returning a `(ref null noextern)` (aka
+/// `Option<NoExtern>`) which is always `None`.
+///
+/// # Example
+///
+/// ```
+/// # use wasmtime::*;
+/// # fn _foo() -> Result<()> {
+/// let mut config = Config::new();
+/// config.wasm_function_references(true);
+/// config.wasm_gc(true);
+/// let engine = Engine::new(&config)?;
+///
+/// let module = Module::new(
+///     &engine,
+///     r#"
+///         (module
+///             (func (export "f") (param (ref null noextern))
+///                 ;; If the reference is null, return.
+///                 local.get 0
+///                 ref.is_null noextern
+///                 br_if 0
+///
+///                 ;; If the reference was not null (which is impossible)
+///                 ;; then raise a trap.
+///                 unreachable
+///             )
+///         )
+///     "#,
+/// )?;
+///
+/// let mut store = Store::new(&engine, ());
+/// let instance = Instance::new(&mut store, &module, &[])?;
+/// let f = instance.get_func(&mut store, "f").unwrap();
+///
+/// // We can cast a `(ref null noextern)`-taking function into a typed function that
+/// // takes an `Option<NoExtern>` via the `Func::typed` method.
+/// let f = f.typed::<Option<NoExtern>, ()>(&store)?;
+///
+/// // We can call the typed function, passing the null `noextern` reference.
+/// let result = f.call(&mut store, NoExtern::null());
+///
+/// // The function should not have trapped, because the reference we gave it was
+/// // null (as it had to be, since `NoExtern` is uninhabited).
+/// assert!(result.is_ok());
+/// # Ok(())
+/// # }
+/// ```
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub struct NoExtern {
+    _inner: Uninhabited,
+}
+
+impl NoExtern {
+    /// Get the null `(ref null noextern)` (aka `nullexternref`) reference.
+    #[inline]
+    pub fn null() -> Option<Self> {
+        None
+    }
+
+    /// Get the null `(ref null noextern)` (aka `nullexternref`) reference as a
+    /// [`Ref`].
+    #[inline]
+    pub fn null_ref() -> Ref {
+        Ref::Extern(None)
+    }
+
+    /// Get the null `(ref null noextern)` (aka `nullexternref`) reference as a
+    /// [`Val`].
+    #[inline]
+    pub fn null_val() -> Val {
+        Val::ExternRef(None)
+    }
+}
+
+unsafe impl WasmTy for NoExtern {
+    type Abi = NoExtern;
+
+    #[inline]
+    fn valtype() -> ValType {
+        ValType::Ref(RefType::new(false, HeapType::NoExtern))
+    }
+
+    #[inline]
+    fn compatible_with_store(&self, _store: &StoreOpaque) -> bool {
+        match self._inner {}
+    }
+
+    #[inline]
+    fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &HeapType) -> Result<()> {
+        match self._inner {}
+    }
+
+    #[inline]
+    fn is_vmgcref_and_points_to_object(&self) -> bool {
+        match self._inner {}
+    }
+
+    #[inline]
+    unsafe fn abi_from_raw(_raw: *mut ValRaw) -> Self::Abi {
+        unreachable!("NoExtern is uninhabited")
+    }
+
+    #[inline]
+    unsafe fn abi_into_raw(abi: Self::Abi, _raw: *mut ValRaw) {
+        match abi._inner {}
+    }
+
+    #[inline]
+    fn into_abi(self, _store: &mut AutoAssertNoGc<'_>) -> Result<Self::Abi> {
+        unreachable!("NoExtern is uninhabited")
+    }
+
+    #[inline]
+    unsafe fn from_abi(abi: Self::Abi, _store: &mut AutoAssertNoGc<'_>) -> Self {
+        match abi._inner {}
+    }
+}
+
+unsafe impl WasmTy for Option<NoExtern> {
+    type Abi = u32;
+
+    #[inline]
+    fn valtype() -> ValType {
+        ValType::Ref(RefType::new(true, HeapType::NoExtern))
+    }
+
+    #[inline]
+    fn compatible_with_store(&self, _store: &StoreOpaque) -> bool {
+        true
+    }
+
+    #[inline]
+    fn dynamic_concrete_type_check(
+        &self,
+        _store: &StoreOpaque,
+        _nullable: bool,
+        _ty: &HeapType,
+    ) -> Result<()> {
+        unreachable!()
+    }
+
+    #[inline]
+    unsafe fn abi_from_raw(_raw: *mut ValRaw) -> Self::Abi {
+        0
+    }
+
+    #[inline]
+    unsafe fn abi_into_raw(abi: Self::Abi, raw: *mut ValRaw) {
+        debug_assert_eq!(abi, 0);
+        *raw = ValRaw::externref(0);
+    }
+
+    #[inline]
+    fn into_abi(self, _store: &mut AutoAssertNoGc<'_>) -> Result<Self::Abi> {
+        Ok(0)
+    }
+
+    #[inline]
+    unsafe fn from_abi(abi: Self::Abi, _store: &mut AutoAssertNoGc<'_>) -> Self {
+        debug_assert_eq!(abi, 0);
+        None
+    }
+}
diff --git a/crates/wasmtime/src/runtime/types.rs b/crates/wasmtime/src/runtime/types.rs
index a23782883..eb62df488 100644
--- a/crates/wasmtime/src/runtime/types.rs
+++ b/crates/wasmtime/src/runtime/types.rs
@@ -238,6 +238,16 @@ impl ValType {
         a.matches(b) && b.matches(a)
     }
 
+    /// Is this a `VMGcRef` type that is not i31 and is not an uninhabited
+    /// bottom type?
+    #[inline]
+    pub(crate) fn is_vmgcref_type_and_points_to_object(&self) -> bool {
+        match self {
+            ValType::Ref(r) => r.is_vmgcref_type_and_points_to_object(),
+            ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 | ValType::V128 => false,
+        }
+    }
+
     pub(crate) fn ensure_matches(&self, engine: &Engine, other: &ValType) -> Result<()> {
         if !self.comes_from_same_engine(engine) || !other.comes_from_same_engine(engine) {
             bail!("type used with wrong engine");
@@ -425,7 +435,7 @@ impl RefType {
         }
     }
 
-    pub(crate) fn is_gc_heap_type(&self) -> bool {
+    pub(crate) fn is_vmgcref_type_and_points_to_object(&self) -> bool {
         self.heap_type().is_vmgcref_type_and_points_to_object()
     }
 }
@@ -1032,7 +1042,11 @@ impl HeapType {
     /// bottom type?
     #[inline]
     pub(crate) fn is_vmgcref_type_and_points_to_object(&self) -> bool {
-        self.is_vmgcref_type() && !matches!(self, HeapType::I31 | HeapType::NoFunc | HeapType::None)
+        self.is_vmgcref_type()
+            && !matches!(
+                self,
+                HeapType::I31 | HeapType::NoExtern | HeapType::NoFunc | HeapType::None
+            )
     }
 }
 
diff --git a/crates/wasmtime/src/runtime/v128.rs b/crates/wasmtime/src/runtime/v128.rs
index 287958c30..2e6e5affa 100644
--- a/crates/wasmtime/src/runtime/v128.rs
+++ b/crates/wasmtime/src/runtime/v128.rs
@@ -104,11 +104,6 @@ unsafe impl WasmTy for V128 {
         unreachable!()
     }
 
-    #[inline]
-    fn is_non_i31_gc_ref(&self) -> bool {
-        false
-    }
-
     #[inline]
     unsafe fn abi_from_raw(raw: *mut ValRaw) -> Self::Abi {
         V128::from((*raw).get_v128()).0


Test patch:
diff --git a/tests/all/func.rs b/tests/all/func.rs
index eafff2bf3..ecf8feb52 100644
--- a/tests/all/func.rs
+++ b/tests/all/func.rs
@@ -400,6 +400,8 @@ fn func_constructors() {
     Func::wrap(&mut store, || -> Option<Func> { None });
     Func::wrap(&mut store, || -> NoFunc { loop {} });
     Func::wrap(&mut store, || -> Option<NoFunc> { None });
+    Func::wrap(&mut store, || -> NoExtern { loop {} });
+    Func::wrap(&mut store, || -> Option<NoExtern> { None });
 
     Func::wrap(&mut store, || -> Result<()> { loop {} });
     Func::wrap(&mut store, || -> Result<i32> { loop {} });
@@ -429,6 +431,8 @@ fn func_constructors() {
     Func::wrap(&mut store, || -> Result<Option<Func>> { loop {} });
     Func::wrap(&mut store, || -> Result<NoFunc> { loop {} });
     Func::wrap(&mut store, || -> Result<Option<NoFunc>> { loop {} });
+    Func::wrap(&mut store, || -> Result<NoExtern> { loop {} });
+    Func::wrap(&mut store, || -> Result<Option<NoExtern>> { loop {} });
 }
 
 #[test]
diff --git a/tests/all/main.rs b/tests/all/main.rs
index bdfed81d4..fcab06d35 100644
--- a/tests/all/main.rs
+++ b/tests/all/main.rs
@@ -28,6 +28,7 @@ mod memory_creator;
 mod module;
 mod module_serialize;
 mod name;
+mod noextern;
 mod piped_tests;
 mod pooling_allocator;
 mod relocs;
diff --git a/tests/all/noextern.rs b/tests/all/noextern.rs
new file mode 100644
index 000000000..59394d4f1
--- /dev/null
+++ b/tests/all/noextern.rs
@@ -0,0 +1,71 @@
+use std::sync::atomic::{AtomicU32, Ordering};
+use wasmtime::*;
+
+#[test]
+#[cfg_attr(miri, ignore)]
+fn func_wrap_no_extern() -> Result<()> {
+    let mut config = Config::default();
+    config.wasm_function_references(true);
+    config.wasm_gc(true);
+
+    let engine = Engine::new(&config)?;
+
+    let module = Module::new(
+        &engine,
+        r#"
+            (module
+                (import "" "" (func (param nullexternref) (result nullexternref)))
+                (func $main
+                    ref.null noextern
+                    call 0
+                    drop
+                )
+                (start $main)
+            )
+        "#,
+    )?;
+
+    let mut store = Store::new(&engine, ());
+
+    static HITS: AtomicU32 = AtomicU32::new(0);
+
+    let f = Func::wrap(&mut store, |x: Option<NoExtern>| -> Option<NoExtern> {
+        assert!(x.is_none());
+        HITS.fetch_add(1, Ordering::SeqCst);
+        x
+    });
+
+    let _ = Instance::new(&mut store, &module, &[f.into()])?;
+    assert_eq!(HITS.load(Ordering::SeqCst), 1);
+
+    Ok(())
+}
+
+#[test]
+#[cfg_attr(miri, ignore)]
+fn func_typed_no_extern() -> Result<()> {
+    let mut config = Config::default();
+    config.wasm_function_references(true);
+    config.wasm_gc(true);
+
+    let engine = Engine::new(&config)?;
+
+    let module = Module::new(
+        &engine,
+        r#"
+            (module
+                (func (export "f") (param nullexternref) (result nullexternref)
+                    local.get 0
+                )
+            )
+        "#,
+    )?;
+
+    let mut store = Store::new(&engine, ());
+    let instance = Instance::new(&mut store, &module, &[])?;
+    let f = instance.get_typed_func::<Option<NoExtern>, Option<NoExtern>>(&mut store, "f")?;
+    let result = f.call(&mut store, None)?;
+    assert!(result.is_none());
+
+    Ok(())
+}