Repository: bytecodealliance/wasmtime
Pull request #6498 (2023-06-01 20:25:13)

Wasi: preview2 poll-oneoff returns list<bool>
In the early days of the preview2 prototype, there was some trouble with wit-bindgen that made it impossible to return a `list<bool>` from the `poll-oneoff` function.

This bug has since been resolved. The poll-oneoff definition in wit has been modified to return `list<bool>` and minor tweaks have been made at use sites to use the boolean.

<!--
Please make sure you include the following information:

- If this work has been discussed elsewhere, please include a link to that
  conversation. If it was discussed in an issue, just mention "issue #...".

- Explain why this change is needed. If the details are in an issue already,
  this can be brief.

Our development process is documented in the Wasmtime book:
https://docs.wasmtime.dev/contributing-development-process.html

Please ensure all communication follows the code of conduct:
https://github.com/bytecodealliance/wasmtime/blob/main/CODE_OF_CONDUCT.md
-->


pchickey: This will conflict with #6486. We can upstream the wit fix to the standards repo, rebase on main after 6486 lands, and then merge this, since this is a very minor change.
alexcrichton: I don't believe this was supposed to be closed by that PR, so reopening

Patch:
diff --git a/cranelift/codegen/src/isa/aarch64/abi.rs b/cranelift/codegen/src/isa/aarch64/abi.rs
index 5c786eced..d542ac06f 100644
--- a/cranelift/codegen/src/isa/aarch64/abi.rs
+++ b/cranelift/codegen/src/isa/aarch64/abi.rs
@@ -657,7 +657,12 @@ impl ABIMachineSpec for AArch64MachineDeps {
         unimplemented!("Stack probing is unimplemented on AArch64");
     }
 
-    fn gen_inline_probestack(insts: &mut SmallInstVec<Self::I>, frame_size: u32, guard_size: u32) {
+    fn gen_inline_probestack(
+        insts: &mut SmallInstVec<Self::I>,
+        _call_conv: isa::CallConv,
+        frame_size: u32,
+        guard_size: u32,
+    ) {
         // The stack probe loop currently takes 6 instructions and each inline
         // probe takes 2 (ish, these numbers sort of depend on the constants).
         // Set this to 3 to keep the max size of the probe to 6 instructions.
@@ -1047,6 +1052,19 @@ impl ABIMachineSpec for AArch64MachineDeps {
         insts
     }
 
+    fn gen_return_call(
+        _callee: CallDest,
+        _new_stack_arg_size: u32,
+        _old_stack_arg_size: u32,
+        _ret_addr: Option<Reg>,
+        _fp: Reg,
+        _tmp: Writable<Reg>,
+        _tmp2: Writable<Reg>,
+        _uses: abi::CallArgList,
+    ) -> SmallVec<[Self::I; 2]> {
+        todo!();
+    }
+
     fn gen_memcpy<F: FnMut(Type) -> Writable<Reg>>(
         call_conv: isa::CallConv,
         dst: Reg,
diff --git a/cranelift/codegen/src/isa/aarch64/lower/isle.rs b/cranelift/codegen/src/isa/aarch64/lower/isle.rs
index 729f1ad23..1ad1de7ab 100644
--- a/cranelift/codegen/src/isa/aarch64/lower/isle.rs
+++ b/cranelift/codegen/src/isa/aarch64/lower/isle.rs
@@ -86,6 +86,27 @@ impl Context for IsleContext<'_, '_, MInst, AArch64Backend> {
         AArch64CallSite
     );
 
+    fn gen_return_call(
+        &mut self,
+        callee_sig: SigRef,
+        callee: ExternalName,
+        distance: RelocDistance,
+        args: ValueSlice,
+    ) -> InstOutput {
+        let _ = (callee_sig, callee, distance, args);
+        todo!()
+    }
+
+    fn gen_return_call_indirect(
+        &mut self,
+        callee_sig: SigRef,
+        callee: Value,
+        args: ValueSlice,
+    ) -> InstOutput {
+        let _ = (callee_sig, callee, args);
+        todo!()
+    }
+
     fn sign_return_address_disabled(&mut self) -> Option<()> {
         if self.backend.isa_flags.sign_return_address() {
             None
diff --git a/cranelift/codegen/src/isa/riscv64/abi.rs b/cranelift/codegen/src/isa/riscv64/abi.rs
index 5eb0145cd..caa18b749 100644
--- a/cranelift/codegen/src/isa/riscv64/abi.rs
+++ b/cranelift/codegen/src/isa/riscv64/abi.rs
@@ -584,6 +584,19 @@ impl ABIMachineSpec for Riscv64MachineDeps {
         insts
     }
 
+    fn gen_return_call(
+        _callee: CallDest,
+        _new_stack_arg_size: u32,
+        _old_stack_arg_size: u32,
+        _ret_addr: Option<Reg>,
+        _fp: Reg,
+        _tmp: Writable<Reg>,
+        _tmp2: Writable<Reg>,
+        _uses: abi::CallArgList,
+    ) -> SmallVec<[Self::I; 2]> {
+        todo!();
+    }
+
     fn gen_memcpy<F: FnMut(Type) -> Writable<Reg>>(
         call_conv: isa::CallConv,
         dst: Reg,
@@ -686,7 +699,12 @@ impl ABIMachineSpec for Riscv64MachineDeps {
         || fixed_frame_storage_size > 0
     }
 
-    fn gen_inline_probestack(insts: &mut SmallInstVec<Self::I>, frame_size: u32, guard_size: u32) {
+    fn gen_inline_probestack(
+        insts: &mut SmallInstVec<Self::I>,
+        call_conv: isa::CallConv,
+        frame_size: u32,
+        guard_size: u32,
+    ) {
         // Unroll at most n consecutive probes, before falling back to using a loop
         const PROBE_MAX_UNROLL: u32 = 3;
         // Number of probes that we need to perform
@@ -695,7 +713,7 @@ impl ABIMachineSpec for Riscv64MachineDeps {
         if probe_count <= PROBE_MAX_UNROLL {
             Self::gen_probestack_unroll(insts, guard_size, probe_count)
         } else {
-            Self::gen_probestack_loop(insts, guard_size, probe_count)
+            Self::gen_probestack_loop(insts, call_conv, guard_size, probe_count)
         }
     }
 }
@@ -940,11 +958,21 @@ impl Riscv64MachineDeps {
         }
     }
 
-    fn gen_probestack_loop(insts: &mut SmallInstVec<Inst>, guard_size: u32, probe_count: u32) {
+    fn gen_probestack_loop(
+        insts: &mut SmallInstVec<Inst>,
+        call_conv: isa::CallConv,
+        guard_size: u32,
+        probe_count: u32,
+    ) {
+        // Must be a caller-saved register that is not an argument.
+        let tmp = match call_conv {
+            isa::CallConv::Tail => Writable::from_reg(x_reg(1)),
+            _ => Writable::from_reg(x_reg(28)), // t3
+        };
         insts.push(Inst::StackProbeLoop {
             guard_size,
             probe_count,
-            tmp: Writable::from_reg(x_reg(28)), // t3
+            tmp,
         });
     }
 }
diff --git a/cranelift/codegen/src/isa/riscv64/inst/emit.rs b/cranelift/codegen/src/isa/riscv64/inst/emit.rs
index bcbf85f7a..feadde261 100644
--- a/cranelift/codegen/src/isa/riscv64/inst/emit.rs
+++ b/cranelift/codegen/src/isa/riscv64/inst/emit.rs
@@ -1159,7 +1159,7 @@ impl MachInstEmit for Inst {
             }
 
             &Inst::VirtualSPOffsetAdj { amount } => {
-                log::trace!(
+                crate::trace!(
                     "virtual sp offset adjusted by {} -> {}",
                     amount,
                     state.virtual_sp_offset + amount
diff --git a/cranelift/codegen/src/isa/riscv64/lower.isle b/cranelift/codegen/src/isa/riscv64/lower.isle
index bc46b8279..dfd67eb5e 100644
--- a/cranelift/codegen/src/isa/riscv64/lower.isle
+++ b/cranelift/codegen/src/isa/riscv64/lower.isle
@@ -1566,7 +1566,6 @@
 (rule (lower (return args))
       (lower_return args))
 
-
 ;;; Rules for `get_{frame,stack}_pointer` and `get_return_address` ;;;;;;;;;;;;;
 
 (rule (lower (get_frame_pointer))
diff --git a/cranelift/codegen/src/isa/riscv64/lower/isle.rs b/cranelift/codegen/src/isa/riscv64/lower/isle.rs
index c03a93640..c030f532b 100644
--- a/cranelift/codegen/src/isa/riscv64/lower/isle.rs
+++ b/cranelift/codegen/src/isa/riscv64/lower/isle.rs
@@ -72,6 +72,27 @@ impl generated_code::Context for RV64IsleContext<'_, '_, MInst, Riscv64Backend>
     isle_lower_prelude_methods!();
     isle_prelude_caller_methods!(Riscv64MachineDeps, Riscv64ABICallSite);
 
+    fn gen_return_call(
+        &mut self,
+        callee_sig: SigRef,
+        callee: ExternalName,
+        distance: RelocDistance,
+        args: ValueSlice,
+    ) -> InstOutput {
+        let _ = (callee_sig, callee, distance, args);
+        todo!()
+    }
+
+    fn gen_return_call_indirect(
+        &mut self,
+        callee_sig: SigRef,
+        callee: Value,
+        args: ValueSlice,
+    ) -> InstOutput {
+        let _ = (callee_sig, callee, args);
+        todo!()
+    }
+
     fn vreg_new(&mut self, r: Reg) -> VReg {
         VReg::new(r).unwrap()
     }
diff --git a/cranelift/codegen/src/isa/s390x/abi.rs b/cranelift/codegen/src/isa/s390x/abi.rs
index 1482e29c9..9b29dab16 100644
--- a/cranelift/codegen/src/isa/s390x/abi.rs
+++ b/cranelift/codegen/src/isa/s390x/abi.rs
@@ -588,6 +588,7 @@ impl ABIMachineSpec for S390xMachineDeps {
 
     fn gen_inline_probestack(
         _insts: &mut SmallInstVec<Self::I>,
+        _call_conv: isa::CallConv,
         _frame_size: u32,
         _guard_size: u32,
     ) {
@@ -764,6 +765,19 @@ impl ABIMachineSpec for S390xMachineDeps {
         unreachable!();
     }
 
+    fn gen_return_call(
+        _callee: CallDest,
+        _new_stack_arg_size: u32,
+        _old_stack_arg_size: u32,
+        _ret_addr: Option<Reg>,
+        _fp: Reg,
+        _tmp: Writable<Reg>,
+        _tmp2: Writable<Reg>,
+        _uses: abi::CallArgList,
+    ) -> SmallVec<[Self::I; 2]> {
+        todo!();
+    }
+
     fn gen_memcpy<F: FnMut(Type) -> Writable<Reg>>(
         _call_conv: isa::CallConv,
         _dst: Reg,
diff --git a/cranelift/codegen/src/isa/s390x/inst.isle b/cranelift/codegen/src/isa/s390x/inst.isle
index acea782ab..30e65266e 100644
--- a/cranelift/codegen/src/isa/s390x/inst.isle
+++ b/cranelift/codegen/src/isa/s390x/inst.isle
@@ -3518,9 +3518,6 @@
 
 ;; Helpers for generating `call` instructions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
-;; List of argument registers for a call instruction.
-(type CallArgList extern (enum))
-
 ;; Partial (mutable) argument list in the process of being created.
 (type CallArgListBuilder extern (enum))
 
diff --git a/cranelift/codegen/src/isa/s390x/lower/isle.rs b/cranelift/codegen/src/isa/s390x/lower/isle.rs
index f32d38b79..2eff24234 100644
--- a/cranelift/codegen/src/isa/s390x/lower/isle.rs
+++ b/cranelift/codegen/src/isa/s390x/lower/isle.rs
@@ -81,6 +81,27 @@ pub(crate) fn lower_branch(
 impl generated_code::Context for IsleContext<'_, '_, MInst, S390xBackend> {
     isle_lower_prelude_methods!();
 
+    fn gen_return_call(
+        &mut self,
+        callee_sig: SigRef,
+        callee: ExternalName,
+        distance: RelocDistance,
+        args: ValueSlice,
+    ) -> InstOutput {
+        let _ = (callee_sig, callee, distance, args);
+        todo!()
+    }
+
+    fn gen_return_call_indirect(
+        &mut self,
+        callee_sig: SigRef,
+        callee: Value,
+        args: ValueSlice,
+    ) -> InstOutput {
+        let _ = (callee_sig, callee, args);
+        todo!()
+    }
+
     #[inline]
     fn args_builder_new(&mut self) -> CallArgListBuilder {
         Cell::new(CallArgList::new())
diff --git a/cranelift/codegen/src/isa/x64/abi.rs b/cranelift/codegen/src/isa/x64/abi.rs
index 1ae2de4b7..cf838318c 100644
--- a/cranelift/codegen/src/isa/x64/abi.rs
+++ b/cranelift/codegen/src/isa/x64/abi.rs
@@ -44,17 +44,32 @@ impl X64ABIMachineSpec {
             ));
         }
     }
-    fn gen_probestack_loop(insts: &mut SmallInstVec<Inst>, frame_size: u32, guard_size: u32) {
-        // We have to use a caller saved register since clobbering only happens
-        // after stack probing.
-        //
-        // R11 is caller saved on both Fastcall and SystemV, and not used for argument
-        // passing, so it's pretty much free. It is also not used by the stacklimit mechanism.
-        let tmp = regs::r11();
-        debug_assert!({
-            let real_reg = tmp.to_real_reg().unwrap();
-            !is_callee_save_systemv(real_reg, false) && !is_callee_save_fastcall(real_reg, false)
-        });
+
+    fn gen_probestack_loop(
+        insts: &mut SmallInstVec<Inst>,
+        call_conv: isa::CallConv,
+        frame_size: u32,
+        guard_size: u32,
+    ) {
+        // We have to use a caller-saved register since clobbering only
+        // happens after stack probing.
+        let tmp = match call_conv {
+            // All registers are caller-saved on the `tail` calling convention,
+            // and `r15` is not used to pass arguments.
+            isa::CallConv::Tail => regs::r15(),
+            // `r11` is caller saved on both Fastcall and SystemV, and not used
+            // for argument passing, so it's pretty much free. It is also not
+            // used by the stacklimit mechanism.
+            _ => {
+                let tmp = regs::r11();
+                debug_assert!({
+                    let real_reg = tmp.to_real_reg().unwrap();
+                    !is_callee_save_systemv(real_reg, false)
+                        && !is_callee_save_fastcall(real_reg, false)
+                });
+                tmp
+            }
+        };
 
         insts.push(Inst::StackProbeLoop {
             tmp: Writable::from_reg(tmp),
@@ -494,7 +509,12 @@ impl ABIMachineSpec for X64ABIMachineSpec {
         });
     }
 
-    fn gen_inline_probestack(insts: &mut SmallInstVec<Self::I>, frame_size: u32, guard_size: u32) {
+    fn gen_inline_probestack(
+        insts: &mut SmallInstVec<Self::I>,
+        call_conv: isa::CallConv,
+        frame_size: u32,
+        guard_size: u32,
+    ) {
         // Unroll at most n consecutive probes, before falling back to using a loop
         //
         // This was number was picked because the loop version is 38 bytes long. We can fit
@@ -507,7 +527,7 @@ impl ABIMachineSpec for X64ABIMachineSpec {
         if probe_count <= PROBE_MAX_UNROLL {
             Self::gen_probestack_unroll(insts, guard_size, probe_count)
         } else {
-            Self::gen_probestack_loop(insts, frame_size, guard_size)
+            Self::gen_probestack_loop(insts, call_conv, frame_size, guard_size)
         }
     }
 
@@ -700,6 +720,52 @@ impl ABIMachineSpec for X64ABIMachineSpec {
         insts
     }
 
+    fn gen_return_call(
+        callee: CallDest,
+        new_stack_arg_size: u32,
+        old_stack_arg_size: u32,
+        ret_addr: Option<Reg>,
+        fp: Reg,
+        tmp: Writable<Reg>,
+        tmp2: Writable<Reg>,
+        uses: abi::CallArgList,
+    ) -> SmallVec<[Self::I; 2]> {
+        let ret_addr = ret_addr.map(|r| Gpr::new(r).unwrap());
+        let fp = Gpr::new(fp).unwrap();
+        let tmp = WritableGpr::from_writable_reg(tmp).unwrap();
+        let info = Box::new(ReturnCallInfo {
+            new_stack_arg_size,
+            old_stack_arg_size,
+            ret_addr,
+            fp,
+            tmp,
+            uses,
+        });
+        match callee {
+            CallDest::ExtName(callee, RelocDistance::Near) => {
+                smallvec![Inst::ReturnCallKnown { callee, info }]
+            }
+            CallDest::ExtName(callee, RelocDistance::Far) => {
+                smallvec![
+                    Inst::LoadExtName {
+                        dst: tmp2,
+                        name: Box::new(callee.clone()),
+                        offset: 0,
+                        distance: RelocDistance::Far,
+                    },
+                    Inst::ReturnCallUnknown {
+                        callee: tmp2.into(),
+                        info,
+                    }
+                ]
+            }
+            CallDest::Reg(callee) => smallvec![Inst::ReturnCallUnknown {
+                callee: callee.into(),
+                info,
+            }],
+        }
+    }
+
     fn gen_memcpy<F: FnMut(Type) -> Writable<Reg>>(
         call_conv: isa::CallConv,
         dst: Reg,
@@ -878,10 +944,9 @@ fn get_intreg_for_arg(call_conv: &CallConv, idx: usize, arg_idx: usize) -> Optio
             7 => Some(regs::r9()),
             8 => Some(regs::r10()),
             9 => Some(regs::r11()),
-            10 => Some(regs::r12()),
-            11 => Some(regs::r13()),
-            12 => Some(regs::r14()),
-            // NB: `r15` is reserved as a scratch register.
+            // NB: `r12`, `r13`, `r14` and `r15` are reserved for indirect
+            // callee addresses and temporaries required for our tail call
+            // sequence (fp, ret_addr, tmp).
             _ => None,
         };
     }
diff --git a/cranelift/codegen/src/isa/x64/encoding/rex.rs b/cranelift/codegen/src/isa/x64/encoding/rex.rs
index 3586b8fc6..70a61425c 100644
--- a/cranelift/codegen/src/isa/x64/encoding/rex.rs
+++ b/cranelift/codegen/src/isa/x64/encoding/rex.rs
@@ -49,7 +49,7 @@ pub(crate) fn encode_sib(shift: u8, enc_index: u8, enc_base: u8) -> u8 {
 #[inline(always)]
 pub(crate) fn int_reg_enc(reg: impl Into<Reg>) -> u8 {
     let reg = reg.into();
-    debug_assert!(reg.is_real());
+    debug_assert!(reg.is_real(), "reg = {reg:?}");
     debug_assert_eq!(reg.class(), RegClass::Int);
     reg.to_real_reg().unwrap().hw_enc()
 }
diff --git a/cranelift/codegen/src/isa/x64/inst.isle b/cranelift/codegen/src/isa/x64/inst.isle
index 2d2a87ed4..1398caa84 100644
--- a/cranelift/codegen/src/isa/x64/inst.isle
+++ b/cranelift/codegen/src/isa/x64/inst.isle
@@ -493,6 +493,14 @@
        (CallUnknown (dest RegMem)
                     (info BoxCallInfo))
 
+       ;; Tail call to a direct destination.
+       (ReturnCallKnown (callee ExternalName)
+                        (info BoxReturnCallInfo))
+
+       ;; Tail call to an indirect destination.
+       (ReturnCallUnknown (callee RegMem)
+                          (info BoxReturnCallInfo))
+
        ;; A pseudo-instruction that captures register arguments in vregs.
        (Args
         (args VecArgPair))
@@ -691,6 +699,7 @@
             SFence))
 
 (type BoxCallInfo extern (enum))
+(type BoxReturnCallInfo extern (enum))
 
 (type BoxVecMachLabel extern (enum))
 
diff --git a/cranelift/codegen/src/isa/x64/inst/emit.rs b/cranelift/codegen/src/isa/x64/inst/emit.rs
index 37d1e258e..5676fe6b1 100644
--- a/cranelift/codegen/src/isa/x64/inst/emit.rs
+++ b/cranelift/codegen/src/isa/x64/inst/emit.rs
@@ -1,7 +1,8 @@
 use crate::binemit::{Addend, Reloc};
+use crate::ir;
 use crate::ir::immediates::{Ieee32, Ieee64};
 use crate::ir::TrapCode;
-use crate::ir::{KnownSymbol, LibCall};
+use crate::ir::{KnownSymbol, LibCall, MemFlags};
 use crate::isa::x64::encoding::evex::{EvexInstruction, EvexVectorLength, RegisterOrAmode};
 use crate::isa::x64::encoding::rex::{
     emit_simm, emit_std_enc_enc, emit_std_enc_mem, emit_std_reg_mem, emit_std_reg_reg, int_reg_enc,
@@ -1584,6 +1585,59 @@ pub(crate) fn emit(
             state.adjust_virtual_sp_offset(-callee_pop_size);
         }
 
+        Inst::ReturnCallKnown {
+            callee,
+            info: call_info,
+        } => {
+            emit_return_call_common_sequence(
+                allocs,
+                sink,
+                info,
+                state,
+                call_info.new_stack_arg_size,
+                call_info.old_stack_arg_size,
+                call_info.ret_addr,
+                call_info.fp,
+                call_info.tmp,
+                &call_info.uses,
+            );
+
+            // Finally, jump to the callee!
+            //
+            // Note: this is not `Inst::Jmp { .. }.emit(..)` because we have
+            // different metadata in this case: we don't have a label for the
+            // target, but rather a function relocation.
+            sink.put1(0xE9);
+            // The addend adjusts for the difference between the end of the instruction and the
+            // beginning of the immediate field.
+            emit_reloc(sink, Reloc::X86CallPCRel4, &callee, -4);
+            sink.put4(0);
+            sink.add_call_site(ir::Opcode::ReturnCall);
+        }
+
+        Inst::ReturnCallUnknown {
+            callee,
+            info: call_info,
+        } => {
+            let callee = callee.with_allocs(allocs);
+
+            emit_return_call_common_sequence(
+                allocs,
+                sink,
+                info,
+                state,
+                call_info.new_stack_arg_size,
+                call_info.old_stack_arg_size,
+                call_info.ret_addr,
+                call_info.fp,
+                call_info.tmp,
+                &call_info.uses,
+            );
+
+            Inst::JmpUnknown { target: callee }.emit(&[], sink, info, state);
+            sink.add_call_site(ir::Opcode::ReturnCallIndirect);
+        }
+
         Inst::CallUnknown {
             dest,
             info: call_info,
@@ -4089,3 +4143,156 @@ pub(crate) fn emit(
 
     state.clear_post_insn();
 }
+
+/// Emit the common sequence used for both direct and indirect tail calls:
+///
+/// * Copy the new frame's stack arguments over the top of our current frame.
+///
+/// * Restore the old frame pointer.
+///
+/// * Initialize the tail callee's stack pointer (simultaneously deallocating
+///   the temporary stack space we allocated when creating the new frame's stack
+///   arguments).
+///
+/// * Move the return address into its stack slot.
+fn emit_return_call_common_sequence(
+    allocs: &mut AllocationConsumer<'_>,
+    sink: &mut MachBuffer<Inst>,
+    info: &EmitInfo,
+    state: &mut EmitState,
+    new_stack_arg_size: u32,
+    old_stack_arg_size: u32,
+    ret_addr: Option<Gpr>,
+    fp: Gpr,
+    tmp: WritableGpr,
+    uses: &CallArgList,
+) {
+    assert!(
+        info.flags.preserve_frame_pointers(),
+        "frame pointers aren't fundamentally required for tail calls, \
+                 but the current implementation relies on them being present"
+    );
+
+    for u in uses {
+        let _ = allocs.next(u.vreg);
+    }
+
+    let ret_addr = ret_addr.map(|r| Gpr::new(allocs.next(*r)).unwrap());
+
+    let fp = allocs.next(*fp);
+
+    let tmp = allocs.next(tmp.to_reg().to_reg());
+    let tmp = Gpr::new(tmp).unwrap();
+    let tmp_w = WritableGpr::from_reg(tmp);
+
+    // Copy the new frame (which is `frame_size` bytes above the SP)
+    // onto our current frame, using only volatile, non-argument
+    // registers.
+    //
+    //
+    // The current stack layout is the following:
+    //
+    //            | ...                 |
+    //            +---------------------+
+    //            | ...                 |
+    //            | stack arguments     |
+    //            | ...                 |
+    //    current | return address      |
+    //    frame   | old FP              | <-- FP
+    //            | ...                 |
+    //            | old stack slots     |
+    //            | ...                 |
+    //            +---------------------+
+    //            | ...                 |
+    //    new     | new stack arguments |
+    //    frame   | ...                 | <-- SP
+    //            +---------------------+
+    //
+    // We need to restore the old FP, copy the new stack arguments over the old
+    // stack arguments, write the return address into the correct slot just
+    // after the new stack arguments, adjust SP to point to the new return
+    // address, and then jump to the callee (which will push the old FP again).
+
+    // Restore the old FP into `rbp`.
+    Inst::Mov64MR {
+        src: SyntheticAmode::Real(Amode::ImmReg {
+            simm32: 0,
+            base: fp,
+            flags: MemFlags::trusted(),
+        }),
+        dst: Writable::from_reg(Gpr::new(regs::rbp()).unwrap()),
+    }
+    .emit(&[], sink, info, state);
+
+    // The new lowest address (top of stack) -- relative to FP -- for
+    // our tail callee. We compute this now so that we can move our
+    // stack arguments into place.
+    let callee_sp_relative_to_fp = old_stack_arg_size.wrapping_sub(new_stack_arg_size);
+
+    // Copy over each word, using `tmp` as a temporary register.
+    //
+    // Note that we have to do this from stack slots with the highest
+    // address to lowest address because in the case of when the tail
+    // callee has more stack arguments than we do, we might otherwise
+    // overwrite some of our stack arguments before they've been copied
+    // into place.
+    assert_eq!(
+        new_stack_arg_size % 8,
+        0,
+        "stack argument space sizes should always be 8-byte aligned"
+    );
+    for i in (0..new_stack_arg_size / 8).rev() {
+        Inst::Mov64MR {
+            src: SyntheticAmode::Real(Amode::ImmReg {
+                simm32: i * 8,
+                base: regs::rsp(),
+                flags: MemFlags::trusted(),
+            }),
+            dst: tmp_w,
+        }
+        .emit(&[], sink, info, state);
+        Inst::MovRM {
+            size: OperandSize::Size64,
+            src: tmp,
+            dst: SyntheticAmode::Real(Amode::ImmReg {
+                // Add 2 because we need to skip over the old FP and the
+                // return address.
+                simm32: callee_sp_relative_to_fp.wrapping_add((i + 2) * 8),
+                base: fp,
+                flags: MemFlags::trusted(),
+            }),
+        }
+        .emit(&[], sink, info, state);
+    }
+
+    // Initialize SP for the tail callee, deallocating the temporary
+    // stack arguments space at the same time.
+    Inst::LoadEffectiveAddress {
+        size: OperandSize::Size64,
+        addr: SyntheticAmode::Real(Amode::ImmReg {
+            // NB: We add a word to `callee_sp_relative_to_fp` here because the
+            // callee will push FP, not us.
+            simm32: callee_sp_relative_to_fp.wrapping_add(8),
+            base: fp,
+            flags: MemFlags::trusted(),
+        }),
+        dst: Writable::from_reg(Gpr::new(regs::rsp()).unwrap()),
+    }
+    .emit(&[], sink, info, state);
+
+    state.adjust_virtual_sp_offset(-i64::from(new_stack_arg_size));
+
+    // Write the return address into the correct stack slot.
+    if let Some(ret_addr) = ret_addr {
+        Inst::MovRM {
+            size: OperandSize::Size64,
+            src: ret_addr,
+            dst: SyntheticAmode::Real(Amode::ImmReg {
+                simm32: 0,
+                base: regs::rsp(),
+                flags: MemFlags::trusted(),
+            }),
+        }
+        .emit(&[], sink, info, state);
+    }
+}
diff --git a/cranelift/codegen/src/isa/x64/inst/mod.rs b/cranelift/codegen/src/isa/x64/inst/mod.rs
index 88164567f..a1047d670 100644
--- a/cranelift/codegen/src/isa/x64/inst/mod.rs
+++ b/cranelift/codegen/src/isa/x64/inst/mod.rs
@@ -52,6 +52,27 @@ pub struct CallInfo {
     pub callee_conv: CallConv,
 }
 
+/// Out-of-line data for return-calls, to keep the size of `Inst` down.
+#[derive(Clone, Debug)]
+pub struct ReturnCallInfo {
+    /// The size of the new stack frame's stack arguments. This is necessary
+    /// for copying the frame over our current frame. It must already be
+    /// allocated on the stack.
+    pub new_stack_arg_size: u32,
+    /// The size of the current/old stack frame's stack arguments.
+    pub old_stack_arg_size: u32,
+    /// The return address. Needs to be written into the correct stack slot
+    /// after the new stack frame is copied into place.
+    pub ret_addr: Option<Gpr>,
+    /// A copy of the frame pointer, because we will overwrite the current
+    /// `rbp`.
+    pub fp: Gpr,
+    /// A temporary register.
+    pub tmp: WritableGpr,
+    /// The in-register arguments and their constraints.
+    pub uses: CallArgList,
+}
+
 #[test]
 #[cfg(target_pointer_width = "64")]
 fn inst_size_test() {
@@ -80,6 +101,8 @@ impl Inst {
             | Inst::Bswap { .. }
             | Inst::CallKnown { .. }
             | Inst::CallUnknown { .. }
+            | Inst::ReturnCallKnown { .. }
+            | Inst::ReturnCallUnknown { .. }
             | Inst::CheckedSRemSeq { .. }
             | Inst::CheckedSRemSeq8 { .. }
             | Inst::Cmove { .. }
@@ -1576,6 +1599,65 @@ impl PrettyPrint for Inst {
                 format!("{op} *{dest}")
             }
 
+            Inst::ReturnCallKnown { callee, info } => {
+                let ReturnCallInfo {
+                    new_stack_arg_size,
+                    old_stack_arg_size,
+                    ret_addr,
+                    fp,
+                    tmp,
+                    uses,
+                } = &**info;
+                let ret_addr = ret_addr.map(|r| regs::show_reg(*r));
+                let fp = regs::show_reg(fp.to_reg());
+                let tmp = regs::show_reg(tmp.to_reg().to_reg());
+                let mut s = format!(
+                    "return_call_known \
+                     {callee:?} \
+                     new_stack_arg_size:{new_stack_arg_size} \
+                     old_stack_arg_size:{old_stack_arg_size} \
+                     ret_addr:{ret_addr:?} \
+                     fp:{fp} \
+                     tmp:{tmp}"
+                );
+                for ret in uses {
+                    let preg = regs::show_reg(ret.preg);
+                    let vreg = pretty_print_reg(ret.vreg, 8, allocs);
+                    write!(&mut s, " {vreg}={preg}").unwrap();
+                }
+                s
+            }
+
+            Inst::ReturnCallUnknown { callee, info } => {
+                let ReturnCallInfo {
+                    new_stack_arg_size,
+                    old_stack_arg_size,
+                    ret_addr,
+                    fp,
+                    tmp,
+                    uses,
+                } = &**info;
+                let callee = callee.pretty_print(8, allocs);
+                let ret_addr = ret_addr.map(|r| regs::show_reg(*r));
+                let fp = regs::show_reg(fp.to_reg());
+                let tmp = regs::show_reg(tmp.to_reg().to_reg());
+                let mut s = format!(
+                    "return_call_unknown \
+                     {callee} \
+                     new_stack_arg_size:{new_stack_arg_size} \
+                     old_stack_arg_size:{old_stack_arg_size} \
+                     ret_addr:{ret_addr:?} \
+                     fp:{fp} \
+                     tmp:{tmp}"
+                );
+                for ret in uses {
+                    let preg = regs::show_reg(ret.preg);
+                    let vreg = pretty_print_reg(ret.vreg, 8, allocs);
+                    write!(&mut s, " {vreg}={preg}").unwrap();
+                }
+                s
+            }
+
             Inst::Args { args } => {
                 let mut s = "args".to_string();
                 for arg in args {
@@ -2212,6 +2294,45 @@ fn x64_get_operands<F: Fn(VReg) -> VReg>(inst: &Inst, collector: &mut OperandCol
             collector.reg_clobbers(info.clobbers);
         }
 
+        Inst::ReturnCallKnown { callee, info } => {
+            let ReturnCallInfo {
+                ret_addr,
+                fp,
+                tmp,
+                uses,
+                ..
+            } = &**info;
+            // Same as in the `Inst::CallKnown` branch.
+            debug_assert_ne!(*callee, ExternalName::LibCall(LibCall::Probestack));
+            for u in uses {
+                collector.reg_fixed_use(u.vreg, u.preg);
+            }
+            if let Some(ret_addr) = ret_addr {
+                collector.reg_use(**ret_addr);
+            }
+            collector.reg_use(**fp);
+            collector.reg_early_def(tmp.to_writable_reg());
+        }
+
+        Inst::ReturnCallUnknown { callee, info } => {
+            let ReturnCallInfo {
+                ret_addr,
+                fp,
+                tmp,
+                uses,
+                ..
+            } = &**info;
+            callee.get_operands(collector);
+            for u in uses {
+                collector.reg_fixed_use(u.vreg, u.preg);
+            }
+            if let Some(ret_addr) = ret_addr {
+                collector.reg_use(**ret_addr);
+            }
+            collector.reg_use(**fp);
+            collector.reg_early_def(tmp.to_writable_reg());
+        }
+
         Inst::JmpTableSeq {
             ref idx,
             ref tmp1,
@@ -2393,6 +2514,9 @@ impl MachInst for Inst {
         match self {
             // Interesting cases.
             &Self::Ret { .. } => MachTerminator::Ret,
+            &Self::ReturnCallKnown { .. } | &Self::ReturnCallUnknown { .. } => {
+                MachTerminator::RetCall
+            }
             &Self::JmpKnown { .. } => MachTerminator::Uncond,
             &Self::JmpCond { .. } => MachTerminator::Cond,
             &Self::JmpTableSeq { .. } => MachTerminator::Indirect,
diff --git a/cranelift/codegen/src/isa/x64/lower.isle b/cranelift/codegen/src/isa/x64/lower.isle
index 788927e7f..250397f9a 100644
--- a/cranelift/codegen/src/isa/x64/lower.isle
+++ b/cranelift/codegen/src/isa/x64/lower.isle
@@ -3188,6 +3188,14 @@
 (rule (lower (call_indirect sig_ref val inputs))
       (gen_call_indirect sig_ref val inputs))
 
+;;;; Rules for `return_call` and `return_call_indirect` ;;;;;;;;;;;;;;;;;;;;;;;;
+
+(rule (lower (return_call (func_ref_data sig_ref extname dist) args))
+      (gen_return_call sig_ref extname dist args))
+
+(rule (lower (return_call_indirect sig_ref callee args))
+      (gen_return_call_indirect sig_ref callee args))
+
 ;;;; Rules for `get_{frame,stack}_pointer` and `get_return_address` ;;;;;;;;;;;;
 
 (rule (lower (get_frame_pointer))
diff --git a/cranelift/codegen/src/isa/x64/lower/isle.rs b/cranelift/codegen/src/isa/x64/lower/isle.rs
index 3935b1869..bb7eb5c8f 100644
--- a/cranelift/codegen/src/isa/x64/lower/isle.rs
+++ b/cranelift/codegen/src/isa/x64/lower/isle.rs
@@ -5,9 +5,9 @@ pub(crate) mod generated_code;
 use crate::{
     ir::types,
     ir::AtomicRmwOp,
+    isa, isle_common_prelude_methods, isle_lower_prelude_methods,
     machinst::{InputSourceInst, Reg, Writable},
 };
-use crate::{isle_common_prelude_methods, isle_lower_prelude_methods};
 use generated_code::{Context, MInst, RegisterClass};
 
 // Types that the generated ISLE code uses via `use super::*`.
@@ -26,7 +26,7 @@ use crate::{
         unwind::UnwindInst,
         x64::{
             abi::X64CallSite,
-            inst::{args::*, regs, CallInfo},
+            inst::{args::*, regs, CallInfo, ReturnCallInfo},
         },
     },
     machinst::{
@@ -41,6 +41,7 @@ use std::boxed::Box;
 use std::convert::TryFrom;
 
 type BoxCallInfo = Box<CallInfo>;
+type BoxReturnCallInfo = Box<ReturnCallInfo>;
 type BoxVecMachLabel = Box<SmallVec<[MachLabel; 4]>>;
 type MachLabelSlice = [MachLabel];
 type VecArgPair = Vec<ArgPair>;
@@ -79,6 +80,165 @@ impl Context for IsleContext<'_, '_, MInst, X64Backend> {
     isle_lower_prelude_methods!();
     isle_prelude_caller_methods!(X64ABIMachineSpec, X64CallSite);
 
+    fn gen_return_call_indirect(
+        &mut self,
+        callee_sig: SigRef,
+        callee: Value,
+        args: ValueSlice,
+    ) -> InstOutput {
+        let caller_conv = isa::CallConv::Tail;
+        debug_assert_eq!(
+            self.lower_ctx.abi().call_conv(self.lower_ctx.sigs()),
+            caller_conv,
+            "Can only do `return_call`s from within a `tail` calling convention function"
+        );
+
+        let callee = self.put_in_reg(callee);
+
+        let mut call_site = X64CallSite::from_ptr(
+            self.lower_ctx.sigs(),
+            callee_sig,
+            callee,
+            Opcode::ReturnCallIndirect,
+            caller_conv,
+            self.backend.flags().clone(),
+        )
+        .unwrap();
+
+        // Allocate additional stack space for the new stack frame. We will
+        // build it in the newly allocated space, but then copy it over our
+        // current frame at the last moment.
+        let new_stack_arg_size = call_site.emit_allocate_tail_call_frame(self.lower_ctx);
+        let old_stack_arg_size = self.lower_ctx.abi().stack_args_size(self.lower_ctx.sigs());
+
+        // Make a copy of the frame pointer, since we use it when copying down
+        // the new stack frame.
+        let fp = self.temp_writable_gpr();
+        let rbp = self.preg_rbp();
+        self.lower_ctx
+            .emit(MInst::MovFromPReg { src: rbp, dst: fp });
+
+        // Load the return address, because copying our new stack frame
+        // over our current stack frame might overwrite it, and we'll need to
+        // place it in the correct location after we do that copy.
+        //
+        // But we only need to actually move the return address if the size of
+        // stack arguments changes.
+        let ret_addr = if new_stack_arg_size != old_stack_arg_size {
+            let ret_addr = self.temp_writable_gpr();
+            self.lower_ctx.emit(MInst::Mov64MR {
+                src: SyntheticAmode::Real(Amode::ImmReg {
+                    simm32: 8,
+                    base: fp.to_reg().to_reg(),
+                    flags: MemFlags::trusted(),
+                }),
+                dst: ret_addr,
+            });
+            Some(ret_addr)
+        } else {
+            None
+        };
+
+        // Put all arguments in registers and stack slots (within that newly
+        // allocated stack space).
+        self.gen_call_common_args(&mut call_site, args);
+
+        // Finally, emit the macro instruction to copy the new stack frame over
+        // our current one and do the actual tail call!
+        let tmp = self.temp_writable_gpr();
+        let tmp2 = self.temp_writable_gpr();
+        call_site.emit_return_call(
+            self.lower_ctx,
+            new_stack_arg_size,
+            old_stack_arg_size,
+            ret_addr.map(|r| r.to_reg().to_reg()),
+            fp.to_reg().to_reg(),
+            tmp.to_writable_reg(),
+            tmp2.to_writable_reg(),
+        );
+
+        InstOutput::new()
+    }
+
+    fn gen_return_call(
+        &mut self,
+        callee_sig: SigRef,
+        callee: ExternalName,
+        distance: RelocDistance,
+        args: ValueSlice,
+    ) -> InstOutput {
+        let caller_conv = isa::CallConv::Tail;
+        debug_assert_eq!(
+            self.lower_ctx.abi().call_conv(self.lower_ctx.sigs()),
+            caller_conv,
+            "Can only do `return_call`s from within a `tail` calling convention function"
+        );
+
+        let mut call_site = X64CallSite::from_func(
+            self.lower_ctx.sigs(),
+            callee_sig,
+            &callee,
+            distance,
+            caller_conv,
+            self.backend.flags().clone(),
+        )
+        .unwrap();
+
+        // Allocate additional stack space for the new stack frame. We will
+        // build it in the newly allocated space, but then copy it over our
+        // current frame at the last moment.
+        let new_stack_arg_size = call_site.emit_allocate_tail_call_frame(self.lower_ctx);
+        let old_stack_arg_size = self.lower_ctx.abi().stack_args_size(self.lower_ctx.sigs());
+
+        // Make a copy of the frame pointer, since we use it when copying down
+        // the new stack frame.
+        let fp = self.temp_writable_gpr();
+        let rbp = self.preg_rbp();
+        self.lower_ctx
+            .emit(MInst::MovFromPReg { src: rbp, dst: fp });
+
+        // Load the return address, because copying our new stack frame
+        // over our current stack frame might overwrite it, and we'll need to
+        // place it in the correct location after we do that copy.
+        //
+        // But we only need to actually move the return address if the size of
+        // stack arguments changes.
+        let ret_addr = if new_stack_arg_size != old_stack_arg_size {
+            let ret_addr = self.temp_writable_gpr();
+            self.lower_ctx.emit(MInst::Mov64MR {
+                src: SyntheticAmode::Real(Amode::ImmReg {
+                    simm32: 8,
+                    base: fp.to_reg().to_reg(),
+                    flags: MemFlags::trusted(),
+                }),
+                dst: ret_addr,
+            });
+            Some(ret_addr)
+        } else {
+            None
+        };
+
+        // Put all arguments in registers and stack slots (within that newly
+        // allocated stack space).
+        self.gen_call_common_args(&mut call_site, args);
+
+        // Finally, emit the macro instruction to copy the new stack frame over
+        // our current one and do the actual tail call!
+        let tmp = self.temp_writable_gpr();
+        let tmp2 = self.temp_writable_gpr();
+        call_site.emit_return_call(
+            self.lower_ctx,
+            new_stack_arg_size,
+            old_stack_arg_size,
+            ret_addr.map(|r| r.to_reg().to_reg()),
+            fp.to_reg().to_reg(),
+            tmp.to_writable_reg(),
+            tmp2.to_writable_reg(),
+        );
+
+        InstOutput::new()
+    }
+
     #[inline]
     fn operand_size_of_type_32_64(&mut self, ty: Type) -> OperandSize {
         if ty.bits() == 64 {
diff --git a/cranelift/codegen/src/isa/x64/mod.rs b/cranelift/codegen/src/isa/x64/mod.rs
index 514dbe41e..3429b26e1 100644
--- a/cranelift/codegen/src/isa/x64/mod.rs
+++ b/cranelift/codegen/src/isa/x64/mod.rs
@@ -80,7 +80,7 @@ impl TargetIsa for X64Backend {
         let dynamic_stackslot_offsets = emit_result.dynamic_stackslot_offsets;
 
         if let Some(disasm) = emit_result.disasm.as_ref() {
-            log::trace!("disassembly:\n{}", disasm);
+            crate::trace!("disassembly:\n{}", disasm);
         }
 
         Ok(CompiledCodeStencil {
diff --git a/cranelift/codegen/src/machinst/abi.rs b/cranelift/codegen/src/machinst/abi.rs
index 54dc09c2c..ed25f6724 100644
--- a/cranelift/codegen/src/machinst/abi.rs
+++ b/cranelift/codegen/src/machinst/abi.rs
@@ -525,7 +525,12 @@ pub trait ABIMachineSpec {
     fn gen_probestack(insts: &mut SmallInstVec<Self::I>, frame_size: u32);
 
     /// Generate a inline stack probe.
-    fn gen_inline_probestack(insts: &mut SmallInstVec<Self::I>, frame_size: u32, guard_size: u32);
+    fn gen_inline_probestack(
+        insts: &mut SmallInstVec<Self::I>,
+        call_conv: isa::CallConv,
+        frame_size: u32,
+        guard_size: u32,
+    );
 
     /// Get all clobbered registers that are callee-saved according to the ABI; the result
     /// contains the registers in a sorted order.
@@ -589,6 +594,17 @@ pub trait ABIMachineSpec {
         callee_pop_size: u32,
     ) -> SmallVec<[Self::I; 2]>;
 
+    fn gen_return_call(
+        callee: CallDest,
+        new_stack_arg_size: u32,
+        old_stack_arg_size: u32,
+        ret_addr: Option<Reg>,
+        fp: Reg,
+        tmp: Writable<Reg>,
+        tmp2: Writable<Reg>,
+        uses: abi::CallArgList,
+    ) -> SmallVec<[Self::I; 2]>;
+
     /// Generate a memcpy invocation. Used to set up struct
     /// args. Takes `src`, `dst` as read-only inputs and passes a temporary
     /// allocator.
@@ -940,7 +956,7 @@ impl SigSet {
                 for slot in slots {
                     match slot {
                         &ABIArgSlot::Reg { reg, .. } => {
-                            log::trace!("call_clobbers: retval reg {:?}", reg);
+                            crate::trace!("call_clobbers: retval reg {:?}", reg);
                             clobbers.remove(PReg::from(reg));
                         }
                         _ => {}
@@ -1893,7 +1909,12 @@ impl<M: ABIMachineSpec> Callee<M> {
                 match self.flags.probestack_strategy() {
                     ProbestackStrategy::Inline => {
                         let guard_size = 1 << self.flags.probestack_size_log2();
-                        M::gen_inline_probestack(&mut insts, total_stacksize, guard_size)
+                        M::gen_inline_probestack(
+                            &mut insts,
+                            self.call_conv,
+                            total_stacksize,
+                            guard_size,
+                        )
                     }
                     ProbestackStrategy::Outline => M::gen_probestack(&mut insts, total_stacksize),
                 }
@@ -2167,6 +2188,27 @@ impl<M: ABIMachineSpec> CallSite<M> {
         sigs.num_args(self.sig)
     }
 
+    /// Allocate space for building a `return_call`'s temporary frame before we
+    /// copy it over the current frame.
+    pub fn emit_allocate_tail_call_frame(&self, ctx: &mut Lower<M::I>) -> u32 {
+        // The necessary stack space is:
+        //
+        //     sizeof(callee_sig.stack_args)
+        //
+        // Note that any stack return space conceptually belongs to our caller
+        // and the function we are tail calling to has the same return type and
+        // will reuse that stack return space.
+        //
+        // The return address is pushed later on, after the stack arguments are
+        // filled in.
+        let frame_size = ctx.sigs()[self.sig].sized_stack_arg_space;
+
+        let adjustment = -i32::try_from(frame_size).unwrap();
+        adjust_stack_and_nominal_sp::<M>(ctx, adjustment);
+
+        frame_size
+    }
+
     /// Emit code to pre-adjust the stack, prior to argument copies and call.
     pub fn emit_stack_pre_adjust(&self, ctx: &mut Lower<M::I>) {
         let sig = &ctx.sigs()[self.sig];
@@ -2192,11 +2234,12 @@ impl<M: ABIMachineSpec> CallSite<M> {
         adjust_stack_and_nominal_sp::<M>(ctx, stack_space);
     }
 
-    /// Emit a copy of a large argument into its associated stack buffer, if any.
-    /// We must be careful to perform all these copies (as necessary) before setting
-    /// up the argument registers, since we may have to invoke memcpy(), which could
-    /// clobber any registers already set up.  The back-end should call this routine
-    /// for all arguments before calling emit_copy_regs_to_arg for all arguments.
+    /// Emit a copy of a large argument into its associated stack buffer, if
+    /// any.  We must be careful to perform all these copies (as necessary)
+    /// before setting up the argument registers, since we may have to invoke
+    /// memcpy(), which could clobber any registers already set up.  The
+    /// back-end should call this routine for all arguments before calling
+    /// `gen_arg` for all arguments.
     pub fn emit_copy_regs_to_buffer(
         &self,
         ctx: &mut Lower<M::I>,
@@ -2510,6 +2553,45 @@ impl<M: ABIMachineSpec> CallSite<M> {
             ctx.emit(inst);
         }
     }
+
+    /// Emit a tail call sequence.
+    ///
+    /// The returned instruction should have a proper use-set (arg registers are
+    /// uses) according to the argument registers this function signature in
+    /// this ABI.
+    pub fn emit_return_call(
+        mut self,
+        ctx: &mut Lower<M::I>,
+        new_stack_arg_size: u32,
+        old_stack_arg_size: u32,
+        ret_addr: Option<Reg>,
+        fp: Reg,
+        tmp: Writable<Reg>,
+        tmp2: Writable<Reg>,
+    ) {
+        if let Some(i) = ctx.sigs()[self.sig].stack_ret_arg {
+            let ret_area_ptr = ctx.abi().ret_area_ptr.expect(
+                "if the tail callee has a return pointer, then the tail caller \
+                 must as well",
+            );
+            for inst in self.gen_arg(ctx, i.into(), ValueRegs::one(ret_area_ptr.to_reg())) {
+                ctx.emit(inst);
+            }
+        }
+
+        for inst in M::gen_return_call(
+            self.dest,
+            new_stack_arg_size,
+            old_stack_arg_size,
+            ret_addr,
+            fp,
+            tmp,
+            tmp2,
+            self.uses,
+        ) {
+            ctx.emit(inst);
+        }
+    }
 }
 
 #[cfg(test)]
diff --git a/cranelift/codegen/src/machinst/buffer.rs b/cranelift/codegen/src/machinst/buffer.rs
index 147cb82e4..08ef25f0b 100644
--- a/cranelift/codegen/src/machinst/buffer.rs
+++ b/cranelift/codegen/src/machinst/buffer.rs
@@ -406,7 +406,6 @@ impl<I: VCodeInst> MachBuffer<I> {
 
     /// Add a byte.
     pub fn put1(&mut self, value: u8) {
-        trace!("MachBuffer: put byte @ {}: {:x}", self.cur_offset(), value);
         self.data.push(value);
 
         // Post-invariant: conceptual-labels_at_tail contains a complete and
@@ -421,11 +420,6 @@ impl<I: VCodeInst> MachBuffer<I> {
 
     /// Add 2 bytes.
     pub fn put2(&mut self, value: u16) {
-        trace!(
-            "MachBuffer: put 16-bit word @ {}: {:x}",
-            self.cur_offset(),
-            value
-        );
         let bytes = value.to_le_bytes();
         self.data.extend_from_slice(&bytes[..]);
 
@@ -434,11 +428,6 @@ impl<I: VCodeInst> MachBuffer<I> {
 
     /// Add 4 bytes.
     pub fn put4(&mut self, value: u32) {
-        trace!(
-            "MachBuffer: put 32-bit word @ {}: {:x}",
-            self.cur_offset(),
-            value
-        );
         let bytes = value.to_le_bytes();
         self.data.extend_from_slice(&bytes[..]);
 
@@ -447,11 +436,6 @@ impl<I: VCodeInst> MachBuffer<I> {
 
     /// Add 8 bytes.
     pub fn put8(&mut self, value: u64) {
-        trace!(
-            "MachBuffer: put 64-bit word @ {}: {:x}",
-            self.cur_offset(),
-            value
-        );
         let bytes = value.to_le_bytes();
         self.data.extend_from_slice(&bytes[..]);
 
@@ -460,11 +444,6 @@ impl<I: VCodeInst> MachBuffer<I> {
 
     /// Add a slice of bytes.
     pub fn put_data(&mut self, data: &[u8]) {
-        trace!(
-            "MachBuffer: put data @ {}: len {}",
-            self.cur_offset(),
-            data.len()
-        );
         self.data.extend_from_slice(data);
 
         // Post-invariant: as for `put1()`.
@@ -472,7 +451,6 @@ impl<I: VCodeInst> MachBuffer<I> {
 
     /// Reserve appended space and return a mutable slice referring to it.
     pub fn get_appended_space(&mut self, len: usize) -> &mut [u8] {
-        trace!("MachBuffer: put data @ {}: len {}", self.cur_offset(), len);
         let off = self.data.len();
         let new_len = self.data.len() + len;
         self.data.resize(new_len, 0);
diff --git a/cranelift/codegen/src/machinst/isle.rs b/cranelift/codegen/src/machinst/isle.rs
index 088c04a8d..d97a54c9f 100644
--- a/cranelift/codegen/src/machinst/isle.rs
+++ b/cranelift/codegen/src/machinst/isle.rs
@@ -737,16 +737,8 @@ macro_rules! isle_prelude_caller_methods {
 #[doc(hidden)]
 macro_rules! isle_prelude_method_helpers {
     ($abicaller:ty) => {
-        fn gen_call_common(
-            &mut self,
-            abi: Sig,
-            num_rets: usize,
-            mut caller: $abicaller,
-            (inputs, off): ValueSlice,
-        ) -> InstOutput {
-            caller.emit_stack_pre_adjust(self.lower_ctx);
-
-            let num_args = self.lower_ctx.sigs().num_args(abi);
+        fn gen_call_common_args(&mut self, call_site: &mut $abicaller, (inputs, off): ValueSlice) {
+            let num_args = call_site.num_args(self.lower_ctx.sigs());
 
             assert_eq!(
                 inputs.len(&self.lower_ctx.dfg().value_lists) - off,
@@ -760,13 +752,25 @@ macro_rules! isle_prelude_method_helpers {
                 arg_regs.push(self.put_in_regs(input));
             }
             for (i, arg_regs) in arg_regs.iter().enumerate() {
-                caller.emit_copy_regs_to_buffer(self.lower_ctx, i, *arg_regs);
+                call_site.emit_copy_regs_to_buffer(self.lower_ctx, i, *arg_regs);
             }
             for (i, arg_regs) in arg_regs.iter().enumerate() {
-                for inst in caller.gen_arg(self.lower_ctx, i, *arg_regs) {
+                for inst in call_site.gen_arg(self.lower_ctx, i, *arg_regs) {
                     self.lower_ctx.emit(inst);
                 }
             }
+        }
+
+        fn gen_call_common(
+            &mut self,
+            abi: Sig,
+            num_rets: usize,
+            mut caller: $abicaller,
+            args: ValueSlice,
+        ) -> InstOutput {
+            caller.emit_stack_pre_adjust(self.lower_ctx);
+
+            self.gen_call_common_args(&mut caller, args);
 
             // Handle retvals prior to emitting call, so the
             // constraints are on the call instruction; but buffer the
diff --git a/cranelift/codegen/src/machinst/mod.rs b/cranelift/codegen/src/machinst/mod.rs
index ba0a5484c..e08384c30 100644
--- a/cranelift/codegen/src/machinst/mod.rs
+++ b/cranelift/codegen/src/machinst/mod.rs
@@ -263,6 +263,8 @@ pub enum MachTerminator {
     None,
     /// A return instruction.
     Ret,
+    /// A tail call.
+    RetCall,
     /// An unconditional branch to another block.
     Uncond,
     /// A conditional branch to one of two other blocks.
diff --git a/cranelift/codegen/src/machinst/vcode.rs b/cranelift/codegen/src/machinst/vcode.rs
index 5fd6255e9..a727ad537 100644
--- a/cranelift/codegen/src/machinst/vcode.rs
+++ b/cranelift/codegen/src/machinst/vcode.rs
@@ -1255,8 +1255,8 @@ impl<I: VCodeInst> RegallocFunction for VCode<I> {
         match self.insts[insn.index()].is_term() {
             // We treat blocks terminated by an unconditional trap like a return for regalloc.
             MachTerminator::None => self.insts[insn.index()].is_trap(),
-            MachTerminator::Ret => true,
-            _ => false,
+            MachTerminator::Ret | MachTerminator::RetCall => true,
+            MachTerminator::Uncond | MachTerminator::Cond | MachTerminator::Indirect => false,
         }
     }
 
diff --git a/cranelift/codegen/src/prelude_lower.isle b/cranelift/codegen/src/prelude_lower.isle
index 4d4474561..82737edf6 100644
--- a/cranelift/codegen/src/prelude_lower.isle
+++ b/cranelift/codegen/src/prelude_lower.isle
@@ -180,6 +180,7 @@
 (type RelocDistance (primitive RelocDistance))
 (type VecArgPair extern (enum))
 (type VecRetPair extern (enum))
+(type CallArgList extern (enum))
 
 ;;;; Helper Clif Extractors ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
@@ -999,6 +1000,12 @@
 (decl gen_return (ValueSlice) Unit)
 (extern constructor gen_return gen_return)
 
+(decl gen_return_call (SigRef ExternalName RelocDistance ValueSlice) InstOutput)
+(extern constructor gen_return_call gen_return_call)
+
+(decl gen_return_call_indirect (SigRef Value ValueSlice) InstOutput)
+(extern constructor gen_return_call_indirect gen_return_call_indirect)
+
 ;; Helper for extracting an immediate that's not 0 and not -1 from an imm64.
 (decl pure partial safe_divisor_from_imm64 (Type Imm64) u64)
 (extern constructor safe_divisor_from_imm64 safe_divisor_from_imm64)
diff --git a/cranelift/codegen/src/timing.rs b/cranelift/codegen/src/timing.rs
index 50a0b66ad..573b82c0c 100644
--- a/cranelift/codegen/src/timing.rs
+++ b/cranelift/codegen/src/timing.rs
@@ -238,7 +238,7 @@ impl Profiler for DefaultProfiler {
 impl Drop for DefaultTimingToken {
     fn drop(&mut self) {
         let duration = self.start.elapsed();
-        log::debug!("timing: Ending {}", self.pass);
+        log::debug!("timing: Ending {}: {}ms", self.pass, duration.as_millis());
         let old_cur = CURRENT_PASS.with(|p| p.replace(self.prev));
         debug_assert_eq!(self.pass, old_cur, "Timing tokens dropped out of order");
         PASS_TIME.with(|rc| {
diff --git a/crates/component-macro/src/bindgen.rs b/crates/component-macro/src/bindgen.rs
index e41e0fa2a..51241a848 100644
--- a/crates/component-macro/src/bindgen.rs
+++ b/crates/component-macro/src/bindgen.rs
@@ -4,7 +4,7 @@ use std::path::{Path, PathBuf};
 use syn::parse::{Error, Parse, ParseStream, Result};
 use syn::punctuated::Punctuated;
 use syn::{braced, token, Ident, Token};
-use wasmtime_wit_bindgen::{Opts, TrappableError};
+use wasmtime_wit_bindgen::{Opts, Ownership, TrappableError};
 use wit_parser::{PackageId, Resolve, UnresolvedPackage, WorldId};
 
 pub struct Config {
@@ -73,7 +73,7 @@ impl Parse for Config {
                     Opt::Tracing(val) => opts.tracing = val,
                     Opt::Async(val) => opts.async_ = val,
                     Opt::TrappableErrorType(val) => opts.trappable_error_type = val,
-                    Opt::DuplicateIfNecessary(val) => opts.duplicate_if_necessary = val,
+                    Opt::Ownership(val) => opts.ownership = val,
                     Opt::Interfaces(s) => {
                         if inline.is_some() {
                             return Err(Error::new(s.span(), "cannot specify a second source"));
@@ -168,7 +168,7 @@ mod kw {
     syn::custom_keyword!(tracing);
     syn::custom_keyword!(trappable_error_type);
     syn::custom_keyword!(world);
-    syn::custom_keyword!(duplicate_if_necessary);
+    syn::custom_keyword!(ownership);
     syn::custom_keyword!(interfaces);
     syn::custom_keyword!(with);
 }
@@ -180,7 +180,7 @@ enum Opt {
     Tracing(bool),
     Async(bool),
     TrappableErrorType(Vec<TrappableError>),
-    DuplicateIfNecessary(bool),
+    Ownership(Ownership),
     Interfaces(syn::LitStr),
     With(HashMap<String, String>),
 }
@@ -208,12 +208,44 @@ impl Parse for Opt {
             input.parse::<Token![async]>()?;
             input.parse::<Token![:]>()?;
             Ok(Opt::Async(input.parse::<syn::LitBool>()?.value))
-        } else if l.peek(kw::duplicate_if_necessary) {
-            input.parse::<kw::duplicate_if_necessary>()?;
+        } else if l.peek(kw::ownership) {
+            input.parse::<kw::ownership>()?;
             input.parse::<Token![:]>()?;
-            Ok(Opt::DuplicateIfNecessary(
-                input.parse::<syn::LitBool>()?.value,
-            ))
+            let ownership = input.parse::<syn::Ident>()?;
+            Ok(Opt::Ownership(match ownership.to_string().as_str() {
+                "Owning" => Ownership::Owning,
+                "Borrowing" => Ownership::Borrowing {
+                    duplicate_if_necessary: {
+                        let contents;
+                        braced!(contents in input);
+                        let field = contents.parse::<syn::Ident>()?;
+                        match field.to_string().as_str() {
+                            "duplicate_if_necessary" => {
+                                contents.parse::<Token![:]>()?;
+                                contents.parse::<syn::LitBool>()?.value
+                            }
+                            name => {
+                                return Err(Error::new(
+                                    field.span(),
+                                    format!(
+                                        "unrecognized `Ownership::Borrowing` field: `{name}`; \
+                                         expected `duplicate_if_necessary`"
+                                    ),
+                                ));
+                            }
+                        }
+                    },
+                },
+                name => {
+                    return Err(Error::new(
+                        ownership.span(),
+                        format!(
+                            "unrecognized ownership: `{name}`; \
+                             expected `Owning` or `Borrowing`"
+                        ),
+                    ));
+                }
+            }))
         } else if l.peek(kw::trappable_error_type) {
             input.parse::<kw::trappable_error_type>()?;
             input.parse::<Token![:]>()?;
diff --git a/crates/cranelift/src/compiler/component.rs b/crates/cranelift/src/compiler/component.rs
index 76c3dfc7e..55c6018d0 100644
--- a/crates/cranelift/src/compiler/component.rs
+++ b/crates/cranelift/src/compiler/component.rs
@@ -12,6 +12,7 @@ use wasmtime_environ::component::{
 };
 use wasmtime_environ::{PtrSize, WasmFuncType};
 
+#[derive(Copy, Clone)]
 enum Abi {
     Wasm,
     Native,
@@ -33,14 +34,7 @@ impl Compiler {
 
         let mut compiler = self.function_compiler();
 
-        let func = ir::Function::with_name_signature(
-            ir::UserFuncName::user(0, 0),
-            match abi {
-                Abi::Wasm => crate::wasm_call_signature(isa, wasm_func_ty),
-                Abi::Native => crate::native_call_signature(isa, wasm_func_ty),
-                Abi::Array => crate::array_call_signature(isa),
-            },
-        );
+        let func = self.func(wasm_func_ty, abi);
         let (mut builder, block0) = compiler.builder(func);
 
         // Start off by spilling all the wasm arguments into a stack slot to be
@@ -59,26 +53,7 @@ impl Compiler {
         };
         let vmctx = builder.func.dfg.block_params(block0)[0];
 
-        // If we are crossing the Wasm-to-native boundary, we need to save the
-        // exit FP and return address for stack walking purposes. However, we
-        // always debug assert that our vmctx is a component context, regardless
-        // whether we are actually crossing that boundary because it should
-        // always hold.
-        super::debug_assert_vmctx_kind(
-            isa,
-            &mut builder,
-            vmctx,
-            wasmtime_environ::component::VMCOMPONENT_MAGIC,
-        );
-        if let Abi::Wasm = abi {
-            let limits = builder.ins().load(
-                pointer_type,
-                MemFlags::trusted(),
-                vmctx,
-                i32::try_from(offsets.limits()).unwrap(),
-            );
-            super::save_last_wasm_exit_fp_and_pc(&mut builder, pointer_type, &offsets.ptr, limits);
-        }
+        self.abi_preamble(&mut builder, &offsets, vmctx, abi);
 
         // Below this will incrementally build both the signature of the host
         // function we're calling as well as the list of arguments since the
@@ -197,16 +172,8 @@ impl Compiler {
         ty: &WasmFuncType,
         abi: Abi,
     ) -> Result<Box<dyn Any + Send>> {
-        let isa = &*self.isa;
         let mut compiler = self.function_compiler();
-        let func = ir::Function::with_name_signature(
-            ir::UserFuncName::user(0, 0),
-            match abi {
-                Abi::Wasm => crate::wasm_call_signature(isa, ty),
-                Abi::Native => crate::native_call_signature(isa, ty),
-                Abi::Array => crate::array_call_signature(isa),
-            },
-        );
+        let func = self.func(ty, abi);
         let (mut builder, _block0) = compiler.builder(func);
         builder.ins().trap(ir::TrapCode::User(ALWAYS_TRAP_CODE));
         builder.finalize();
@@ -225,14 +192,7 @@ impl Compiler {
         let isa = &*self.isa;
         let offsets = VMComponentOffsets::new(isa.pointer_bytes(), component);
         let mut compiler = self.function_compiler();
-        let func = ir::Function::with_name_signature(
-            ir::UserFuncName::user(0, 0),
-            match abi {
-                Abi::Wasm => crate::wasm_call_signature(isa, ty),
-                Abi::Native => crate::native_call_signature(isa, ty),
-                Abi::Array => crate::array_call_signature(isa),
-            },
-        );
+        let func = self.func(ty, abi);
         let (mut builder, block0) = compiler.builder(func);
 
         match abi {
@@ -251,6 +211,59 @@ impl Compiler {
         builder.finalize();
         Ok(Box::new(compiler.finish()?))
     }
+
+    fn func(&self, ty: &WasmFuncType, abi: Abi) -> ir::Function {
+        let isa = &*self.isa;
+        ir::Function::with_name_signature(
+            ir::UserFuncName::user(0, 0),
+            match abi {
+                Abi::Wasm => crate::wasm_call_signature(isa, ty),
+                Abi::Native => crate::native_call_signature(isa, ty),
+                Abi::Array => crate::array_call_signature(isa),
+            },
+        )
+    }
+
+    fn compile_func_ref(
+        &self,
+        compile: impl Fn(Abi) -> Result<Box<dyn Any + Send>>,
+    ) -> Result<AllCallFunc<Box<dyn Any + Send>>> {
+        Ok(AllCallFunc {
+            wasm_call: compile(Abi::Wasm)?,
+            array_call: compile(Abi::Array)?,
+            native_call: compile(Abi::Native)?,
+        })
+    }
+
+    fn abi_preamble(
+        &self,
+        builder: &mut FunctionBuilder<'_>,
+        offsets: &VMComponentOffsets<u8>,
+        vmctx: ir::Value,
+        abi: Abi,
+    ) {
+        let pointer_type = self.isa.pointer_type();
+        // If we are crossing the Wasm-to-native boundary, we need to save the
+        // exit FP and return address for stack walking purposes. However, we
+        // always debug assert that our vmctx is a component context, regardless
+        // whether we are actually crossing that boundary because it should
+        // always hold.
+        super::debug_assert_vmctx_kind(
+            &*self.isa,
+            builder,
+            vmctx,
+            wasmtime_environ::component::VMCOMPONENT_MAGIC,
+        );
+        if let Abi::Wasm = abi {
+            let limits = builder.ins().load(
+                pointer_type,
+                MemFlags::trusted(),
+                vmctx,
+                i32::try_from(offsets.limits()).unwrap(),
+            );
+            super::save_last_wasm_exit_fp_and_pc(builder, pointer_type, &offsets.ptr, limits);
+        }
+    }
 }
 
 impl ComponentCompiler for Compiler {
@@ -260,34 +273,13 @@ impl ComponentCompiler for Compiler {
         lowering: &LowerImport,
         types: &ComponentTypes,
     ) -> Result<AllCallFunc<Box<dyn Any + Send>>> {
-        Ok(AllCallFunc {
-            wasm_call: self.compile_lowered_trampoline_for_abi(
-                component,
-                lowering,
-                types,
-                Abi::Wasm,
-            )?,
-            array_call: self.compile_lowered_trampoline_for_abi(
-                component,
-                lowering,
-                types,
-                Abi::Array,
-            )?,
-            native_call: self.compile_lowered_trampoline_for_abi(
-                component,
-                lowering,
-                types,
-                Abi::Native,
-            )?,
+        self.compile_func_ref(|abi| {
+            self.compile_lowered_trampoline_for_abi(component, lowering, types, abi)
         })
     }
 
     fn compile_always_trap(&self, ty: &WasmFuncType) -> Result<AllCallFunc<Box<dyn Any + Send>>> {
-        Ok(AllCallFunc {
-            wasm_call: self.compile_always_trap_for_abi(ty, Abi::Wasm)?,
-            array_call: self.compile_always_trap_for_abi(ty, Abi::Array)?,
-            native_call: self.compile_always_trap_for_abi(ty, Abi::Native)?,
-        })
+        self.compile_func_ref(|abi| self.compile_always_trap_for_abi(ty, abi))
     }
 
     fn compile_transcoder(
@@ -296,20 +288,8 @@ impl ComponentCompiler for Compiler {
         transcoder: &Transcoder,
         types: &ComponentTypes,
     ) -> Result<AllCallFunc<Box<dyn Any + Send>>> {
-        Ok(AllCallFunc {
-            wasm_call: self.compile_transcoder_for_abi(component, transcoder, types, Abi::Wasm)?,
-            array_call: self.compile_transcoder_for_abi(
-                component,
-                transcoder,
-                types,
-                Abi::Array,
-            )?,
-            native_call: self.compile_transcoder_for_abi(
-                component,
-                transcoder,
-                types,
-                Abi::Native,
-            )?,
+        self.compile_func_ref(|abi| {
+            self.compile_transcoder_for_abi(component, transcoder, types, abi)
         })
     }
 }
@@ -325,15 +305,7 @@ impl Compiler {
         let pointer_type = self.isa.pointer_type();
         let vmctx = builder.func.dfg.block_params(block)[0];
 
-        // Save the exit FP and return address for stack walking purposes. This
-        // is used when an invalid encoding is encountered and a trap is raised.
-        let limits = builder.ins().load(
-            pointer_type,
-            MemFlags::trusted(),
-            vmctx,
-            i32::try_from(offsets.limits()).unwrap(),
-        );
-        super::save_last_wasm_exit_fp_and_pc(builder, pointer_type, &offsets.ptr, limits);
+        self.abi_preamble(builder, offsets, vmctx, Abi::Wasm);
 
         // Determine the static signature of the host libcall for this transcode
         // operation and additionally calculate the static offset within the
diff --git a/crates/environ/examples/factc.rs b/crates/environ/examples/factc.rs
index cc14a4ae6..473168d9a 100644
--- a/crates/environ/examples/factc.rs
+++ b/crates/environ/examples/factc.rs
@@ -2,7 +2,7 @@ use anyhow::{bail, Context, Result};
 use clap::Parser;
 use std::io::Write;
 use std::path::PathBuf;
-use wasmparser::{Payload, Validator, WasmFeatures};
+use wasmparser::{Validator, WasmFeatures};
 use wasmtime_environ::component::*;
 use wasmtime_environ::fact::Module;
 
@@ -120,59 +120,56 @@ impl Factc {
 
         let mut adapters = Vec::new();
         let input = wat::parse_file(&self.input)?;
-        types.push_type_scope();
         let mut validator = Validator::new_with_features(WasmFeatures {
             component_model: true,
             ..Default::default()
         });
-        for payload in wasmparser::Parser::new(0).parse_all(&input) {
-            let payload = payload?;
-            validator.payload(&payload)?;
-            let section = match payload {
-                Payload::ComponentTypeSection(s) => s,
-                _ => continue,
+        let wasm_types = validator
+            .validate_all(&input)
+            .context("failed to validate input wasm")?;
+        let wasm_types = wasm_types.as_ref();
+        for i in 0.. {
+            let ty = match wasm_types.id_from_type_index(i, false) {
+                Some(ty) => ty,
+                None => break,
             };
-            for ty in section {
-                let ty = types.intern_component_type(&ty?)?;
-                types.push_component_typedef(ty);
-                let ty = match ty {
-                    TypeDef::ComponentFunc(ty) => ty,
-                    _ => continue,
-                };
-                adapters.push(Adapter {
-                    lift_ty: ty,
-                    lower_ty: ty,
-                    lower_options: AdapterOptions {
-                        instance: RuntimeComponentInstanceIndex::from_u32(0),
-                        string_encoding: self.lower_str,
-                        memory64: self.lower64,
-                        // Pessimistically assume that memory/realloc are going to be
-                        // required for this trampoline and provide it. Avoids doing
-                        // calculations to figure out whether they're necessary and
-                        // simplifies the fuzzer here without reducing coverage within FACT
-                        // itself.
-                        memory: Some(dummy_memory(self.lower64)),
-                        realloc: Some(dummy_def()),
-                        // Lowering never allows `post-return`
-                        post_return: None,
-                    },
-                    lift_options: AdapterOptions {
-                        instance: RuntimeComponentInstanceIndex::from_u32(1),
-                        string_encoding: self.lift_str,
-                        memory64: self.lift64,
-                        memory: Some(dummy_memory(self.lift64)),
-                        realloc: Some(dummy_def()),
-                        post_return: if self.post_return {
-                            Some(dummy_def())
-                        } else {
-                            None
-                        },
+            let wasm_ty = wasm_types.type_from_id(ty).unwrap();
+            let ty = match wasm_ty.as_component_func_type() {
+                Some(ty) => types.convert_component_func_type(wasm_types, ty)?,
+                None => continue,
+            };
+            adapters.push(Adapter {
+                lift_ty: ty,
+                lower_ty: ty,
+                lower_options: AdapterOptions {
+                    instance: RuntimeComponentInstanceIndex::from_u32(0),
+                    string_encoding: self.lower_str,
+                    memory64: self.lower64,
+                    // Pessimistically assume that memory/realloc are going to be
+                    // required for this trampoline and provide it. Avoids doing
+                    // calculations to figure out whether they're necessary and
+                    // simplifies the fuzzer here without reducing coverage within FACT
+                    // itself.
+                    memory: Some(dummy_memory(self.lower64)),
+                    realloc: Some(dummy_def()),
+                    // Lowering never allows `post-return`
+                    post_return: None,
+                },
+                lift_options: AdapterOptions {
+                    instance: RuntimeComponentInstanceIndex::from_u32(1),
+                    string_encoding: self.lift_str,
+                    memory64: self.lift64,
+                    memory: Some(dummy_memory(self.lift64)),
+                    realloc: Some(dummy_def()),
+                    post_return: if self.post_return {
+                        Some(dummy_def())
+                    } else {
+                        None
                     },
-                    func: dummy_def(),
-                });
-            }
+                },
+                func: dummy_def(),
+            });
         }
-        types.pop_type_scope();
 
         let mut fact_module = Module::new(&types, self.debug);
         for (i, adapter) in adapters.iter().enumerate() {
diff --git a/crates/environ/src/component/translate.rs b/crates/environ/src/component/translate.rs
index cb2306e25..84c3ad63b 100644
--- a/crates/environ/src/component/translate.rs
+++ b/crates/environ/src/component/translate.rs
@@ -62,11 +62,6 @@ pub struct Translator<'a, 'data> {
     /// As frames are popped from `lexical_scopes` their completed component
     /// will be pushed onto this list.
     static_components: PrimaryMap<StaticComponentIndex, Translation<'data>>,
-
-    /// Storage for type information used by `ComponentInstanceType::Synthetic`
-    /// which is thrown away after compilation is finished.
-    synthetic_instance_types:
-        PrimaryMap<SyntheticInstanceTypeIndex, HashMap<&'data str, ComponentItemType>>,
 }
 
 /// Representation of the syntactic scope of a component meaning where it is
@@ -153,12 +148,9 @@ struct Translation<'data> {
     /// index into an index space of what's being exported.
     exports: IndexMap<&'data str, ComponentItem>,
 
-    /// Type information (in Wasmtime's representation) for various
-    /// component-model index spaces.
-    funcs: PrimaryMap<FuncIndex, SignatureIndex>,
-    components: PrimaryMap<ComponentIndex, ComponentType>,
-    component_funcs: PrimaryMap<ComponentFuncIndex, TypeFuncIndex>,
-    component_instances: PrimaryMap<ComponentInstanceIndex, ComponentInstanceType>,
+    /// The next core function index that's going to be defined, used to lookup
+    /// type information when lowering.
+    core_func_index: u32,
 }
 
 #[allow(missing_docs)]
@@ -167,7 +159,12 @@ enum LocalInitializer<'data> {
     Import(ComponentExternName<'data>, TypeDef),
 
     // canonical function sections
-    Lower(ComponentFuncIndex, LocalCanonicalOptions),
+    Lower {
+        func: ComponentFuncIndex,
+        lower_ty: TypeFuncIndex,
+        canonical_abi: SignatureIndex,
+        options: LocalCanonicalOptions,
+    },
     Lift(TypeFuncIndex, FuncIndex, LocalCanonicalOptions),
 
     // core wasm modules
@@ -235,45 +232,6 @@ struct LocalCanonicalOptions {
     post_return: Option<FuncIndex>,
 }
 
-#[derive(Copy, Clone)]
-enum ComponentType {
-    /// A component was imported from so its type is listed by an index.
-    Index(TypeComponentIndex),
-    /// A component was defined statically inline so its type information can be
-    /// found in the `Translation` itself.
-    Static(StaticComponentIndex),
-}
-
-#[derive(Copy, Clone)]
-enum ComponentInstanceType {
-    /// An instance was imported so its type information is listed by index.
-    Index(TypeComponentInstanceIndex),
-    /// An instance was created through instantiating an imported component, so
-    /// the type of the index is inferred from the type of the component.
-    InstantiatedIndex(TypeComponentIndex),
-    /// An instance was created through instantiation of a statically defined
-    /// component, so the type information is inferred from a `Translation`.
-    InstantiatedStatic(StaticComponentIndex),
-    /// An instance was created from a list of other items, and the list can be
-    /// found in the `synthetic_instance_types` map.
-    Synthetic(SyntheticInstanceTypeIndex),
-}
-
-/// Only used as part of `synthetic_instance_types` which is used to infer type
-/// information for component-model items. This intentionally doesn't cover
-/// everything (e.g. modules are missing at this time since they aren't needed).
-#[derive(Copy, Clone)]
-enum ComponentItemType {
-    Func(TypeFuncIndex),
-    Component(ComponentType),
-    Instance(ComponentInstanceType),
-    Type(TypeDef),
-}
-
-#[derive(Copy, Clone, PartialEq, Eq)]
-struct SyntheticInstanceTypeIndex(u32);
-cranelift_entity::entity_impl!(SyntheticInstanceTypeIndex);
-
 enum Action {
     KeepGoing,
     Skip(usize),
@@ -297,7 +255,6 @@ impl<'a, 'data> Translator<'a, 'data> {
             lexical_scopes: Vec::new(),
             static_components: Default::default(),
             static_modules: Default::default(),
-            synthetic_instance_types: Default::default(),
             scope_vec,
         }
     }
@@ -395,39 +352,10 @@ impl<'a, 'data> Translator<'a, 'data> {
                         bail!("attempted to parse a wasm module with a component parser");
                     }
                 }
-
-                // Push a new scope for component types so outer aliases know
-                // that the 0th level is this new component.
-                self.types.push_type_scope();
             }
 
             Payload::End(offset) => {
-                let types = self.validator.end(offset)?;
-
-                // Record type information for this component now that we'll
-                // have it from wasmparser.
-                //
-                // Note that this uses type information from `wasmparser` to
-                // lookup signature of all core wasm functions in this
-                // component.  This avoids us having to reimplement the
-                // translate-interface-types-to-the-canonical-abi logic. The
-                // type of the function is then intern'd to get a
-                // `SignatureIndex` which is later used at runtime for a
-                // `VMSharedSignatureIndex`.
-                for idx in 0.. {
-                    let lowered_function_type = match types.function_at(idx) {
-                        Some(ty) => ty,
-                        None => break,
-                    };
-                    let ty = self.types.convert_func_type(lowered_function_type);
-                    let ty = self.types.module_types_builder().wasm_func_type(ty);
-                    self.result.funcs.push(ty);
-                }
-
-                // When leaving a module be sure to pop the types scope to
-                // ensure that when we go back to the previous module outer
-                // type alias indices work correctly again.
-                self.types.pop_type_scope();
+                self.validator.end(offset)?;
 
                 // Exit the current lexical scope. If there is no parent (no
                 // frame currently on the stack) then translation is finished.
@@ -447,9 +375,6 @@ impl<'a, 'data> Translator<'a, 'data> {
                 self.result
                     .initializers
                     .push(LocalInitializer::ComponentStatic(static_idx, closure_args));
-                self.result
-                    .components
-                    .push(ComponentType::Static(static_idx));
             }
 
             // When we see a type section the types are validated and then
@@ -462,17 +387,9 @@ impl<'a, 'data> Translator<'a, 'data> {
             // within a component.
             Payload::ComponentTypeSection(s) => {
                 self.validator.component_type_section(&s)?;
-                for ty in s {
-                    let ty = self.types.intern_component_type(&ty?)?;
-                    self.types.push_component_typedef(ty);
-                }
             }
             Payload::CoreTypeSection(s) => {
                 self.validator.core_type_section(&s)?;
-                for ty in s {
-                    let ty = self.types.intern_core_type(&ty?)?;
-                    self.types.push_core_typedef(ty);
-                }
             }
 
             // Processing the import section at this point is relatively simple
@@ -482,8 +399,11 @@ impl<'a, 'data> Translator<'a, 'data> {
                 self.validator.component_import_section(&s)?;
                 for import in s {
                     let import = import?;
-                    let ty = self.types.component_type_ref(&import.ty);
-                    self.push_typedef(ty);
+                    let types = self.validator.types(0).unwrap();
+                    let ty = types
+                        .component_entity_type_of_import(import.name.as_str())
+                        .unwrap();
+                    let ty = self.types.convert_component_entity_type(types, ty)?;
                     self.result
                         .initializers
                         .push(LocalInitializer::Import(import.name, ty));
@@ -494,6 +414,7 @@ impl<'a, 'data> Translator<'a, 'data> {
             // with the listed options for lifting/lowering.
             Payload::ComponentCanonicalSection(s) => {
                 self.validator.component_canonical_section(&s)?;
+                let types = self.validator.types(0).unwrap();
                 for func in s {
                     match func? {
                         wasmparser::CanonicalFunction::Lift {
@@ -501,28 +422,44 @@ impl<'a, 'data> Translator<'a, 'data> {
                             core_func_index,
                             options,
                         } => {
-                            let ty = ComponentTypeIndex::from_u32(type_index);
-                            let ty = match self.types.component_outer_type(0, ty) {
-                                TypeDef::ComponentFunc(ty) => ty,
-                                // should not be possible after validation
-                                _ => unreachable!(),
-                            };
+                            let ty = types
+                                .type_at(type_index, false)
+                                .unwrap()
+                                .as_component_func_type()
+                                .unwrap();
+                            let ty = self.types.convert_component_func_type(types, ty)?;
                             let func = FuncIndex::from_u32(core_func_index);
                             let options = self.canonical_options(&options);
                             self.result
                                 .initializers
                                 .push(LocalInitializer::Lift(ty, func, options));
-                            self.result.component_funcs.push(ty);
                         }
                         wasmparser::CanonicalFunction::Lower {
                             func_index,
                             options,
                         } => {
+                            let lower_ty = types.component_function_at(func_index).unwrap();
+                            let lower_ty =
+                                self.types.convert_component_func_type(types, lower_ty)?;
+
                             let func = ComponentFuncIndex::from_u32(func_index);
                             let options = self.canonical_options(&options);
-                            self.result
-                                .initializers
-                                .push(LocalInitializer::Lower(func, options));
+
+                            let canonical_abi =
+                                types.function_at(self.result.core_func_index).unwrap();
+                            let canonical_abi = self.types.convert_func_type(canonical_abi);
+                            let canonical_abi = self
+                                .types
+                                .module_types_builder()
+                                .wasm_func_type(canonical_abi);
+
+                            self.result.initializers.push(LocalInitializer::Lower {
+                                func,
+                                options,
+                                canonical_abi,
+                                lower_ty,
+                            });
+                            self.result.core_func_index += 1;
                         }
 
                         wasmparser::CanonicalFunction::ResourceNew { .. }
@@ -602,10 +539,10 @@ impl<'a, 'data> Translator<'a, 'data> {
                             args,
                         } => {
                             let index = ComponentIndex::from_u32(component_index);
-                            self.instantiate_component(index, &args)
+                            self.instantiate_component(index, &args)?
                         }
                         wasmparser::ComponentInstance::FromExports(exports) => {
-                            self.instantiate_component_from_exports(&exports)
+                            self.instantiate_component_from_exports(&exports)?
                         }
                     };
                     self.result.initializers.push(init);
@@ -621,38 +558,12 @@ impl<'a, 'data> Translator<'a, 'data> {
                 self.validator.component_export_section(&s)?;
                 for export in s {
                     let export = export?;
-                    let item = self.kind_to_item(export.kind, export.index);
+                    let item = self.kind_to_item(export.kind, export.index)?;
                     let prev = self.result.exports.insert(export.name.as_str(), item);
                     assert!(prev.is_none());
                     self.result
                         .initializers
                         .push(LocalInitializer::Export(item));
-
-                    // Exports create a new index, so push the item onto the
-                    // appropriate list.
-                    match item {
-                        ComponentItem::Func(idx) => {
-                            self.result
-                                .component_funcs
-                                .push(self.result.component_funcs[idx]);
-                        }
-                        ComponentItem::Module(_idx) => {
-                            // We don't need to do anything here for modules as
-                            // the type information isn't tracked in the initial
-                            // translation pass.
-                        }
-                        ComponentItem::Component(idx) => {
-                            self.result.components.push(self.result.components[idx]);
-                        }
-                        ComponentItem::ComponentInstance(idx) => {
-                            self.result
-                                .component_instances
-                                .push(self.result.component_instances[idx]);
-                        }
-                        ComponentItem::Type(ty) => {
-                            self.types.push_component_typedef(ty);
-                        }
-                    }
                 }
             }
 
@@ -674,7 +585,6 @@ impl<'a, 'data> Translator<'a, 'data> {
                             name,
                         } => {
                             let instance = ComponentInstanceIndex::from_u32(instance_index);
-                            self.alias_component_instance_export(instance, name);
                             LocalInitializer::AliasComponentExport(instance, name)
                         }
                         wasmparser::ComponentAlias::Outer { kind, count, index } => {
@@ -714,28 +624,6 @@ impl<'a, 'data> Translator<'a, 'data> {
         Ok(Action::KeepGoing)
     }
 
-    fn push_typedef(&mut self, ty: TypeDef) {
-        match ty {
-            TypeDef::ComponentInstance(idx) => {
-                self.result
-                    .component_instances
-                    .push(ComponentInstanceType::Index(idx));
-            }
-            TypeDef::ComponentFunc(idx) => {
-                self.result.component_funcs.push(idx);
-            }
-            TypeDef::Component(idx) => {
-                self.result.components.push(ComponentType::Index(idx));
-            }
-            TypeDef::Interface(_) => {
-                self.types.push_component_typedef(ty);
-            }
-
-            // not processed here
-            TypeDef::CoreFunc(_) | TypeDef::Module(_) => {}
-        }
-    }
-
     fn instantiate_module(
         &mut self,
         module: ModuleIndex,
@@ -791,21 +679,14 @@ impl<'a, 'data> Translator<'a, 'data> {
         &mut self,
         component: ComponentIndex,
         raw_args: &[wasmparser::ComponentInstantiationArg<'data>],
-    ) -> LocalInitializer<'data> {
+    ) -> Result<LocalInitializer<'data>> {
         let mut args = HashMap::with_capacity(raw_args.len());
         for arg in raw_args {
-            let idx = self.kind_to_item(arg.kind, arg.index);
+            let idx = self.kind_to_item(arg.kind, arg.index)?;
             args.insert(arg.name, idx);
         }
 
-        self.result
-            .component_instances
-            .push(match self.result.components[component] {
-                ComponentType::Index(i) => ComponentInstanceType::InstantiatedIndex(i),
-                ComponentType::Static(i) => ComponentInstanceType::InstantiatedStatic(i),
-            });
-
-        LocalInitializer::ComponentInstantiate(component, args)
+        Ok(LocalInitializer::ComponentInstantiate(component, args))
     }
 
     /// Creates a synthetic module from the list of items currently in the
@@ -813,40 +694,22 @@ impl<'a, 'data> Translator<'a, 'data> {
     fn instantiate_component_from_exports(
         &mut self,
         exports: &[wasmparser::ComponentExport<'data>],
-    ) -> LocalInitializer<'data> {
+    ) -> Result<LocalInitializer<'data>> {
         let mut map = HashMap::with_capacity(exports.len());
-        let mut types = HashMap::with_capacity(exports.len());
         for export in exports {
-            let idx = self.kind_to_item(export.kind, export.index);
-            let ty = match idx {
-                ComponentItem::Func(i) => {
-                    Some(ComponentItemType::Func(self.result.component_funcs[i]))
-                }
-                ComponentItem::Component(i) => {
-                    Some(ComponentItemType::Component(self.result.components[i]))
-                }
-                ComponentItem::ComponentInstance(i) => Some(ComponentItemType::Instance(
-                    self.result.component_instances[i],
-                )),
-                ComponentItem::Type(ty) => Some(ComponentItemType::Type(ty)),
-                ComponentItem::Module(_) => None,
-            };
+            let idx = self.kind_to_item(export.kind, export.index)?;
             map.insert(export.name.as_str(), idx);
-            if let Some(ty) = ty {
-                types.insert(export.name.as_str(), ty);
-            }
         }
 
-        let index = self.synthetic_instance_types.push(types);
-        self.result
-            .component_instances
-            .push(ComponentInstanceType::Synthetic(index));
-
-        LocalInitializer::ComponentSynthetic(map)
+        Ok(LocalInitializer::ComponentSynthetic(map))
     }
 
-    fn kind_to_item(&self, kind: wasmparser::ComponentExternalKind, index: u32) -> ComponentItem {
-        match kind {
+    fn kind_to_item(
+        &mut self,
+        kind: wasmparser::ComponentExternalKind,
+        index: u32,
+    ) -> Result<ComponentItem> {
+        Ok(match kind {
             wasmparser::ComponentExternalKind::Func => {
                 let index = ComponentFuncIndex::from_u32(index);
                 ComponentItem::Func(index)
@@ -867,11 +730,12 @@ impl<'a, 'data> Translator<'a, 'data> {
                 unimplemented!("component values");
             }
             wasmparser::ComponentExternalKind::Type => {
-                let index = ComponentTypeIndex::from_u32(index);
-                let ty = self.types.component_outer_type(0, index);
+                let types = self.validator.types(0).unwrap();
+                let ty = types.id_from_type_index(index, false).unwrap();
+                let ty = self.types.convert_type(types, ty)?;
                 ComponentItem::Type(ty)
             }
-        }
+        })
     }
 
     fn alias_module_instance_export(
@@ -881,7 +745,10 @@ impl<'a, 'data> Translator<'a, 'data> {
         name: &'data str,
     ) -> LocalInitializer<'data> {
         match kind {
-            wasmparser::ExternalKind::Func => LocalInitializer::AliasExportFunc(instance, name),
+            wasmparser::ExternalKind::Func => {
+                self.result.core_func_index += 1;
+                LocalInitializer::AliasExportFunc(instance, name)
+            }
             wasmparser::ExternalKind::Memory => LocalInitializer::AliasExportMemory(instance, name),
             wasmparser::ExternalKind::Table => LocalInitializer::AliasExportTable(instance, name),
             wasmparser::ExternalKind::Global => LocalInitializer::AliasExportGlobal(instance, name),
@@ -891,79 +758,6 @@ impl<'a, 'data> Translator<'a, 'data> {
         }
     }
 
-    fn alias_component_instance_export(
-        &mut self,
-        instance: ComponentInstanceIndex,
-        name: &'data str,
-    ) {
-        match self.result.component_instances[instance] {
-            // An imported component instance is being aliased, so the type of
-            // the aliased item is directly available from the instance type.
-            ComponentInstanceType::Index(ty) => {
-                let ty = self.types[ty].exports[name];
-                self.push_typedef(ty);
-            }
-
-            // An imported component was instantiated so the type of the aliased
-            // export is available through the type of the export on the
-            // original component.
-            ComponentInstanceType::InstantiatedIndex(ty) => {
-                let ty = self.types[ty].exports[name];
-                self.push_typedef(ty);
-            }
-
-            // A static nested component was instantiated which means that the
-            // type of the export can be looked up directly from the translation
-            // that was finished prior.
-            ComponentInstanceType::InstantiatedStatic(idx) => {
-                let translation = &self.static_components[idx];
-
-                match translation.exports[name] {
-                    ComponentItem::Func(idx) => {
-                        self.result
-                            .component_funcs
-                            .push(translation.component_funcs[idx]);
-                    }
-                    ComponentItem::Component(idx) => {
-                        self.result.components.push(translation.components[idx]);
-                    }
-                    ComponentItem::ComponentInstance(idx) => {
-                        self.result
-                            .component_instances
-                            .push(translation.component_instances[idx]);
-                    }
-                    ComponentItem::Type(ty) => {
-                        self.types.push_component_typedef(ty);
-                    }
-
-                    // ignored during this type pass
-                    ComponentItem::Module(_) => {}
-                }
-            }
-
-            // A synthetic instance is being aliased which means the global list
-            // of synthetic instance types can be consulted for the type
-            // information.
-            ComponentInstanceType::Synthetic(index) => {
-                let map = &self.synthetic_instance_types[index];
-                match map[name] {
-                    ComponentItemType::Func(ty) => {
-                        self.result.component_funcs.push(ty);
-                    }
-                    ComponentItemType::Component(ty) => {
-                        self.result.components.push(ty);
-                    }
-                    ComponentItemType::Instance(ty) => {
-                        self.result.component_instances.push(ty);
-                    }
-                    ComponentItemType::Type(ty) => {
-                        self.types.push_component_typedef(ty);
-                    }
-                }
-            }
-        }
-    }
-
     fn alias_component_outer(
         &mut self,
         kind: wasmparser::ComponentOuterAliasKind,
@@ -971,24 +765,8 @@ impl<'a, 'data> Translator<'a, 'data> {
         index: u32,
     ) {
         match kind {
-            // When aliasing a type the `ComponentTypesBuilder` is used to
-            // resolve the outer `count` plus the index, and then once it's
-            // resolved we push the type information into our local index
-            // space.
-            //
-            // Note that this is just copying indices around as all type
-            // information is basically a pointer back into the `TypesBuilder`
-            // structure (and the eventual `TypeTables` that it produces).
-            wasmparser::ComponentOuterAliasKind::CoreType => {
-                let index = TypeIndex::from_u32(index);
-                let ty = self.types.core_outer_type(count, index);
-                self.types.push_core_typedef(ty);
-            }
-            wasmparser::ComponentOuterAliasKind::Type => {
-                let index = ComponentTypeIndex::from_u32(index);
-                let ty = self.types.component_outer_type(count, index);
-                self.types.push_component_typedef(ty);
-            }
+            wasmparser::ComponentOuterAliasKind::CoreType => {}
+            wasmparser::ComponentOuterAliasKind::Type => {}
 
             // For more information about the implementation of outer aliases
             // see the documentation of `LexicalScope`. Otherwise though the
@@ -1020,20 +798,15 @@ impl<'a, 'data> Translator<'a, 'data> {
                     component =
                         ClosedOverComponent::Upvar(frame.closure_args.components.push(component));
                 }
-                let component_ty = match self.lexical_scopes.get(depth) {
-                    Some(frame) => frame.translation.components[index],
-                    None => self.result.components[index],
-                };
 
                 self.result
                     .initializers
                     .push(LocalInitializer::AliasComponent(component));
-                self.result.components.push(component_ty);
             }
         }
     }
 
-    fn canonical_options(&mut self, opts: &[wasmparser::CanonicalOption]) -> LocalCanonicalOptions {
+    fn canonical_options(&self, opts: &[wasmparser::CanonicalOption]) -> LocalCanonicalOptions {
         let mut ret = LocalCanonicalOptions {
             string_encoding: StringEncoding::Utf8,
             memory: None,
diff --git a/crates/environ/src/component/translate/inline.rs b/crates/environ/src/component/translate/inline.rs
index 62a7d007a..06348c975 100644
--- a/crates/environ/src/component/translate/inline.rs
+++ b/crates/environ/src/component/translate/inline.rs
@@ -401,10 +401,12 @@ impl<'a> Inliner<'a> {
             // recording the lowering.
             //
             // NB: at this time only lowered imported functions are supported.
-            Lower(func, options) => {
-                let canonical_abi = frame.translation.funcs[frame.funcs.next_key()];
-                let lower_ty = frame.translation.component_funcs[*func];
-
+            Lower {
+                func,
+                options,
+                canonical_abi,
+                lower_ty,
+            } => {
                 let options_lower = self.adapter_options(frame, options);
                 let func = match &frame.component_funcs[*func] {
                     // If this component function was originally a host import
@@ -415,7 +417,7 @@ impl<'a> Inliner<'a> {
                         let import = self.runtime_import(path);
                         let options = self.canonical_options(options_lower);
                         let index = self.result.lowerings.push_uniq(dfg::LowerImport {
-                            canonical_abi,
+                            canonical_abi: *canonical_abi,
                             import,
                             options,
                         });
@@ -451,7 +453,7 @@ impl<'a> Inliner<'a> {
                         options: options_lift,
                         ..
                     } if options_lift.instance == options_lower.instance => {
-                        let index = self.result.always_trap.push_uniq(canonical_abi);
+                        let index = self.result.always_trap.push_uniq(*canonical_abi);
                         dfg::CoreDef::AlwaysTrap(index)
                     }
 
@@ -491,7 +493,7 @@ impl<'a> Inliner<'a> {
                         let adapter_idx = self.result.adapters.push_uniq(Adapter {
                             lift_ty: *lift_ty,
                             lift_options: options_lift.clone(),
-                            lower_ty,
+                            lower_ty: *lower_ty,
                             lower_options: options_lower,
                             func: func.clone(),
                         });
diff --git a/crates/environ/src/component/types.rs b/crates/environ/src/component/types.rs
index e98271b4c..5efd1d9a7 100644
--- a/crates/environ/src/component/types.rs
+++ b/crates/environ/src/component/types.rs
@@ -5,14 +5,13 @@ use crate::{
 };
 use anyhow::{bail, Result};
 use cranelift_entity::EntityRef;
-use indexmap::IndexMap;
+use indexmap::{IndexMap, IndexSet};
 use serde::{Deserialize, Serialize};
 use std::collections::HashMap;
 use std::hash::Hash;
 use std::ops::Index;
-use wasmparser::{
-    ComponentAlias, ComponentOuterAliasKind, ComponentTypeDeclaration, InstanceTypeDeclaration,
-};
+use wasmparser::names::KebabString;
+use wasmparser::types;
 use wasmtime_component_util::{DiscriminantSize, FlagsSize};
 
 pub use wasmtime_types::StaticModuleIndex;
@@ -307,7 +306,6 @@ where
 /// managing building up core wasm [`ModuleTypes`] as well.
 #[derive(Default)]
 pub struct ComponentTypesBuilder {
-    type_scopes: Vec<TypeScope>,
     functions: HashMap<TypeFunc, TypeFuncIndex>,
     lists: HashMap<TypeList, TypeListIndex>,
     records: HashMap<TypeRecord, TypeRecordIndex>,
@@ -326,13 +324,11 @@ pub struct ComponentTypesBuilder {
     // used at compile-time and not used at runtime, hence the location here
     // as opposed to `ComponentTypes`.
     type_info: TypeInformationCache,
-}
 
-#[derive(Default)]
-struct TypeScope {
-    core: PrimaryMap<TypeIndex, TypeDef>,
-    component: PrimaryMap<ComponentTypeIndex, TypeDef>,
-    instances: PrimaryMap<ComponentInstanceIndex, TypeComponentInstanceIndex>,
+    defined_types_cache: HashMap<types::TypeId, InterfaceType>,
+    module_types_cache: HashMap<types::TypeId, TypeModuleIndex>,
+    component_types_cache: HashMap<types::TypeId, TypeComponentIndex>,
+    instance_types_cache: HashMap<types::TypeId, TypeComponentInstanceIndex>,
 }
 
 macro_rules! intern_and_fill_flat_types {
@@ -371,419 +367,311 @@ impl ComponentTypesBuilder {
         &mut self.module_types
     }
 
-    /// Pushes a new scope when entering a new index space for types in the
-    /// component model.
-    ///
-    /// This happens when a component is recursed into or a module/instance
-    /// type is recursed into.
-    pub fn push_type_scope(&mut self) {
-        self.type_scopes.push(Default::default());
-    }
-
-    /// Adds a new `TypeDef` definition within the current component types
-    /// scope.
-    ///
-    /// Returns the `ComponentTypeIndex` associated with the type being pushed.
-    ///
-    /// # Panics
-    ///
-    /// Requires that `push_type_scope` was called previously.
-    pub fn push_component_typedef(&mut self, ty: TypeDef) -> ComponentTypeIndex {
-        debug_assert!(!matches!(ty, TypeDef::Module(_) | TypeDef::CoreFunc(_)));
-        self.type_scopes.last_mut().unwrap().component.push(ty)
-    }
-
-    /// Adds a new `TypeDef` definition within the current core types
-    /// scope.
-    ///
-    /// Returns the `TypeIndex` associated with the type being pushed. Note that
-    /// this should only be used with core-wasm-related `TypeDef` instances such
-    /// as `TypeDef::Module` and `TypeDef::CoreFunc`.
-    ///
-    /// # Panics
-    ///
-    /// Requires that `push_type_scope` was called previously.
-    pub fn push_core_typedef(&mut self, ty: TypeDef) -> TypeIndex {
-        debug_assert!(matches!(ty, TypeDef::Module(_) | TypeDef::CoreFunc(_)));
-        self.type_scopes.last_mut().unwrap().core.push(ty)
-    }
-
-    /// Looks up an "outer" type in this builder to handle outer aliases.
-    ///
-    /// The `count` parameter and `ty` are taken from the binary format itself,
-    /// and the `TypeDef` returned is what the outer type refers to.
-    ///
-    /// # Panics
-    ///
-    /// Assumes that `count` and `ty` are valid.
-    pub fn component_outer_type(&self, count: u32, ty: ComponentTypeIndex) -> TypeDef {
-        // Reverse the index and 0 means the "current scope"
-        let idx = self.type_scopes.len() - (count as usize) - 1;
-        self.type_scopes[idx].component[ty]
-    }
-
-    /// Same as `component_outer_type` but for core wasm types instead.
-    pub fn core_outer_type(&self, count: u32, ty: TypeIndex) -> TypeDef {
-        // Reverse the index and 0 means the "current scope"
-        let idx = self.type_scopes.len() - (count as usize) - 1;
-        self.type_scopes[idx].core[ty]
-    }
-
-    /// Pops a scope pushed by `push_type_scope`.
-    pub fn pop_type_scope(&mut self) {
-        self.type_scopes.pop().unwrap();
-    }
-
-    /// Translates a wasmparser `TypeComponent` into a Wasmtime `TypeDef`,
-    /// interning types along the way.
-    pub fn intern_component_type(&mut self, ty: &wasmparser::ComponentType<'_>) -> Result<TypeDef> {
-        Ok(match ty {
-            wasmparser::ComponentType::Defined(ty) => TypeDef::Interface(self.defined_type(ty)?),
-            wasmparser::ComponentType::Func(ty) => TypeDef::ComponentFunc(self.func_type(ty)),
-            wasmparser::ComponentType::Component(ty) => {
-                TypeDef::Component(self.component_type(ty)?)
-            }
-            wasmparser::ComponentType::Instance(ty) => {
-                TypeDef::ComponentInstance(self.component_instance_type(ty)?)
-            }
-            wasmparser::ComponentType::Resource { .. } => {
-                unimplemented!("resource types")
-            }
-        })
+    /// Converts a wasmparser `ComponentFuncType` into Wasmtime's type
+    /// representation.
+    pub fn convert_component_func_type(
+        &mut self,
+        types: types::TypesRef<'_>,
+        ty: &types::ComponentFuncType,
+    ) -> Result<TypeFuncIndex> {
+        let ty = TypeFunc {
+            params: ty
+                .params
+                .iter()
+                .map(|(_name, ty)| self.valtype(types, ty))
+                .collect::<Result<_>>()?,
+            results: ty
+                .results
+                .iter()
+                .map(|(_name, ty)| self.valtype(types, ty))
+                .collect::<Result<_>>()?,
+        };
+        Ok(self.add_func_type(ty))
     }
 
-    /// Translates a wasmparser `CoreType` into a Wasmtime `TypeDef`,
-    /// interning types along the way.
-    pub fn intern_core_type(&mut self, ty: &wasmparser::CoreType<'_>) -> Result<TypeDef> {
+    /// Converts a wasmparser `ComponentEntityType` into Wasmtime's type
+    /// representation.
+    pub fn convert_component_entity_type(
+        &mut self,
+        types: types::TypesRef<'_>,
+        ty: types::ComponentEntityType,
+    ) -> Result<TypeDef> {
         Ok(match ty {
-            wasmparser::CoreType::Func(ty) => {
-                let ty = self.convert_func_type(ty);
-                TypeDef::CoreFunc(self.module_types.wasm_func_type(ty))
+            types::ComponentEntityType::Module(id) => {
+                TypeDef::Module(self.convert_module(types, id)?)
             }
-            wasmparser::CoreType::Module(ty) => TypeDef::Module(self.module_type(ty)?),
-        })
-    }
-
-    /// Translates a wasmparser `ComponentTypeRef` into a Wasmtime `TypeDef`.
-    pub fn component_type_ref(&self, ty: &wasmparser::ComponentTypeRef) -> TypeDef {
-        match ty {
-            wasmparser::ComponentTypeRef::Module(ty) => {
-                self.core_outer_type(0, TypeIndex::from_u32(*ty))
+            types::ComponentEntityType::Component(id) => {
+                TypeDef::Component(self.convert_component(types, id)?)
             }
-            wasmparser::ComponentTypeRef::Func(ty)
-            | wasmparser::ComponentTypeRef::Type(wasmparser::TypeBounds::Eq(ty))
-            | wasmparser::ComponentTypeRef::Instance(ty)
-            | wasmparser::ComponentTypeRef::Component(ty) => {
-                self.component_outer_type(0, ComponentTypeIndex::from_u32(*ty))
+            types::ComponentEntityType::Instance(id) => {
+                TypeDef::ComponentInstance(self.convert_instance(types, id)?)
             }
-            wasmparser::ComponentTypeRef::Value(..) => {
-                unimplemented!("references to value types");
+            types::ComponentEntityType::Func(id) => {
+                let id = types
+                    .type_from_id(id)
+                    .unwrap()
+                    .as_component_func_type()
+                    .unwrap();
+                let idx = self.convert_component_func_type(types, id)?;
+                TypeDef::ComponentFunc(idx)
             }
-            wasmparser::ComponentTypeRef::Type(wasmparser::TypeBounds::SubResource) => {
-                unimplemented!("resource types");
+            types::ComponentEntityType::Type { created, .. } => {
+                match types.type_from_id(created).unwrap() {
+                    types::Type::Defined(_) => {
+                        TypeDef::Interface(self.defined_type(types, created)?)
+                    }
+                    _ => bail!("unsupported type export"),
+                }
             }
-        }
+            types::ComponentEntityType::Value(_) => bail!("values not supported"),
+        })
     }
 
-    fn module_type(
+    /// Converts a wasmparser `Type` into Wasmtime's type representation.
+    pub fn convert_type(
         &mut self,
-        ty: &[wasmparser::ModuleTypeDeclaration<'_>],
-    ) -> Result<TypeModuleIndex> {
-        let mut result = TypeModule::default();
-        self.push_type_scope();
-
-        for item in ty {
-            match item {
-                wasmparser::ModuleTypeDeclaration::Type(wasmparser::Type::Func(f)) => {
-                    let f = self.convert_func_type(f);
-                    let ty = TypeDef::CoreFunc(self.module_types.wasm_func_type(f));
-                    self.push_core_typedef(ty);
-                }
-                wasmparser::ModuleTypeDeclaration::Type(wasmparser::Type::Array(_)) => {
-                    unimplemented!("gc types");
-                }
-                wasmparser::ModuleTypeDeclaration::Export { name, ty } => {
-                    let prev = result
-                        .exports
-                        .insert(name.to_string(), self.entity_type(ty)?);
-                    assert!(prev.is_none());
-                }
-                wasmparser::ModuleTypeDeclaration::Import(import) => {
-                    let prev = result.imports.insert(
-                        (import.module.to_string(), import.name.to_string()),
-                        self.entity_type(&import.ty)?,
-                    );
-                    assert!(prev.is_none());
-                }
-                wasmparser::ModuleTypeDeclaration::OuterAlias {
-                    kind: wasmparser::OuterAliasKind::Type,
-                    count,
-                    index,
-                } => {
-                    let ty = self.core_outer_type(*count, TypeIndex::from_u32(*index));
-                    self.push_core_typedef(ty);
-                }
+        types: types::TypesRef<'_>,
+        id: types::TypeId,
+    ) -> Result<TypeDef> {
+        Ok(match types.type_from_id(id).unwrap() {
+            types::Type::Defined(_) => TypeDef::Interface(self.defined_type(types, id)?),
+            types::Type::Module(_) => TypeDef::Module(self.convert_module(types, id)?),
+            types::Type::Component(_) => TypeDef::Component(self.convert_component(types, id)?),
+            types::Type::ComponentInstance(_) => {
+                TypeDef::ComponentInstance(self.convert_instance(types, id)?)
             }
-        }
-
-        self.pop_type_scope();
-
-        Ok(self.component_types.modules.push(result))
-    }
-
-    fn entity_type(&self, ty: &wasmparser::TypeRef) -> Result<EntityType> {
-        Ok(match ty {
-            wasmparser::TypeRef::Func(idx) => {
-                let idx = TypeIndex::from_u32(*idx);
-                match self.core_outer_type(0, idx) {
-                    TypeDef::CoreFunc(idx) => EntityType::Function(idx),
-                    _ => unreachable!(), // not possible with valid components
-                }
+            types::Type::ComponentFunc(f) => {
+                TypeDef::ComponentFunc(self.convert_component_func_type(types, f)?)
+            }
+            types::Type::Instance(_) | types::Type::Func(_) | types::Type::Array(_) => {
+                unreachable!()
             }
-            wasmparser::TypeRef::Table(ty) => EntityType::Table(self.convert_table_type(ty)),
-            wasmparser::TypeRef::Memory(ty) => EntityType::Memory(ty.clone().into()),
-            wasmparser::TypeRef::Global(ty) => EntityType::Global(self.convert_global_type(ty)),
-            wasmparser::TypeRef::Tag(_) => bail!("exceptions proposal not implemented"),
+            types::Type::Resource(_) => unimplemented!(),
         })
     }
 
-    fn component_type(
+    fn convert_component(
         &mut self,
-        ty: &[ComponentTypeDeclaration<'_>],
+        types: types::TypesRef<'_>,
+        id: types::TypeId,
     ) -> Result<TypeComponentIndex> {
+        if let Some(ret) = self.component_types_cache.get(&id) {
+            return Ok(*ret);
+        }
+        let ty = &types.type_from_id(id).unwrap().as_component_type().unwrap();
         let mut result = TypeComponent::default();
-        self.push_type_scope();
-
-        for item in ty {
-            match item {
-                ComponentTypeDeclaration::Type(ty) => self.type_declaration_type(ty)?,
-                ComponentTypeDeclaration::CoreType(ty) => self.type_declaration_core_type(ty)?,
-                ComponentTypeDeclaration::Alias(alias) => self.type_declaration_alias(alias)?,
-                ComponentTypeDeclaration::Export { name, ty } => {
-                    let ty = self.type_declaration_define(ty);
-                    result.exports.insert(name.as_str().to_string(), ty);
-                }
-                ComponentTypeDeclaration::Import(import) => {
-                    let ty = self.type_declaration_define(&import.ty);
-                    result.imports.insert(import.name.as_str().to_string(), ty);
-                }
-            }
+        for (name, ty) in ty.imports.iter() {
+            result.imports.insert(
+                name.clone(),
+                self.convert_component_entity_type(types, *ty)?,
+            );
         }
-
-        self.pop_type_scope();
-
-        Ok(self.component_types.components.push(result))
+        for (name, ty) in ty.exports.iter() {
+            result.exports.insert(
+                name.clone(),
+                self.convert_component_entity_type(types, *ty)?,
+            );
+        }
+        let ret = self.component_types.components.push(result);
+        self.component_types_cache.insert(id, ret);
+        Ok(ret)
     }
 
-    fn component_instance_type(
+    fn convert_instance(
         &mut self,
-        ty: &[InstanceTypeDeclaration<'_>],
+        types: types::TypesRef<'_>,
+        id: types::TypeId,
     ) -> Result<TypeComponentInstanceIndex> {
+        if let Some(ret) = self.instance_types_cache.get(&id) {
+            return Ok(*ret);
+        }
+        let ty = &types
+            .type_from_id(id)
+            .unwrap()
+            .as_component_instance_type()
+            .unwrap();
         let mut result = TypeComponentInstance::default();
-        self.push_type_scope();
-
-        for item in ty {
-            match item {
-                InstanceTypeDeclaration::Type(ty) => self.type_declaration_type(ty)?,
-                InstanceTypeDeclaration::CoreType(ty) => self.type_declaration_core_type(ty)?,
-                InstanceTypeDeclaration::Alias(alias) => self.type_declaration_alias(alias)?,
-                InstanceTypeDeclaration::Export { name, ty } => {
-                    let ty = self.type_declaration_define(ty);
-                    result.exports.insert(name.as_str().to_string(), ty);
-                }
-            }
+        for (name, ty) in ty.exports.iter() {
+            result.exports.insert(
+                name.clone(),
+                self.convert_component_entity_type(types, *ty)?,
+            );
         }
-
-        self.pop_type_scope();
-
-        Ok(self.component_types.component_instances.push(result))
+        let ret = self.component_types.component_instances.push(result);
+        self.instance_types_cache.insert(id, ret);
+        Ok(ret)
     }
 
-    fn type_declaration_type(&mut self, ty: &wasmparser::ComponentType<'_>) -> Result<()> {
-        let ty = self.intern_component_type(ty)?;
-        self.push_component_typedef(ty);
-        Ok(())
-    }
-
-    fn type_declaration_core_type(&mut self, ty: &wasmparser::CoreType<'_>) -> Result<()> {
-        let ty = self.intern_core_type(ty)?;
-        self.push_core_typedef(ty);
-        Ok(())
+    fn convert_module(
+        &mut self,
+        types: types::TypesRef<'_>,
+        id: types::TypeId,
+    ) -> Result<TypeModuleIndex> {
+        if let Some(ret) = self.module_types_cache.get(&id) {
+            return Ok(*ret);
+        }
+        let ty = &types.type_from_id(id).unwrap().as_module_type().unwrap();
+        let mut result = TypeModule::default();
+        for ((module, field), ty) in ty.imports.iter() {
+            result.imports.insert(
+                (module.clone(), field.clone()),
+                self.entity_type(types, ty)?,
+            );
+        }
+        for (name, ty) in ty.exports.iter() {
+            result
+                .exports
+                .insert(name.clone(), self.entity_type(types, ty)?);
+        }
+        let ret = self.component_types.modules.push(result);
+        self.module_types_cache.insert(id, ret);
+        Ok(ret)
     }
 
-    fn type_declaration_alias(&mut self, alias: &wasmparser::ComponentAlias<'_>) -> Result<()> {
-        match alias {
-            ComponentAlias::Outer {
-                kind: ComponentOuterAliasKind::CoreType,
-                count,
-                index,
-            } => {
-                let ty = self.core_outer_type(*count, TypeIndex::from_u32(*index));
-                self.push_core_typedef(ty);
-            }
-            ComponentAlias::Outer {
-                kind: ComponentOuterAliasKind::Type,
-                count,
-                index,
-            } => {
-                let ty = self.component_outer_type(*count, ComponentTypeIndex::from_u32(*index));
-                self.push_component_typedef(ty);
-            }
-            ComponentAlias::InstanceExport {
-                kind: _,
-                instance_index,
-                name,
-            } => {
-                let ty = self.type_scopes.last().unwrap().instances
-                    [ComponentInstanceIndex::from_u32(*instance_index)];
-                let ty = self.component_types[ty].exports[*name];
-                self.push_component_typedef(ty);
+    fn entity_type(
+        &mut self,
+        types: types::TypesRef<'_>,
+        ty: &types::EntityType,
+    ) -> Result<EntityType> {
+        Ok(match ty {
+            types::EntityType::Func(idx) => {
+                let ty = types.type_from_id(*idx).unwrap().as_func_type().unwrap();
+                let ty = self.convert_func_type(ty);
+                EntityType::Function(self.module_types_builder().wasm_func_type(ty))
             }
-            a => unreachable!("invalid alias {a:?}"),
-        }
-        Ok(())
+            types::EntityType::Table(ty) => EntityType::Table(self.convert_table_type(ty)),
+            types::EntityType::Memory(ty) => EntityType::Memory(ty.clone().into()),
+            types::EntityType::Global(ty) => EntityType::Global(self.convert_global_type(ty)),
+            types::EntityType::Tag(_) => bail!("exceptions proposal not implemented"),
+        })
     }
 
-    fn type_declaration_define(&mut self, ty: &wasmparser::ComponentTypeRef) -> TypeDef {
-        let ty = self.component_type_ref(ty);
-        let scope = self.type_scopes.last_mut().unwrap();
-        match ty {
-            // If an import or an export within a component or instance type
-            // references an interface type itself then that creates a new type
-            // which is effectively an alias, so push the type information here.
-            TypeDef::Interface(_) => {
-                self.push_component_typedef(ty);
+    fn defined_type(
+        &mut self,
+        types: types::TypesRef<'_>,
+        id: types::TypeId,
+    ) -> Result<InterfaceType> {
+        if let Some(ty) = self.defined_types_cache.get(&id) {
+            return Ok(*ty);
+        }
+        let ret = match types.type_from_id(id).unwrap().as_defined_type().unwrap() {
+            types::ComponentDefinedType::Primitive(ty) => ty.into(),
+            types::ComponentDefinedType::Record(e) => {
+                InterfaceType::Record(self.record_type(types, e)?)
             }
-
-            // When an import or an export references a component instance then
-            // that creates a "pseudo-instance" which type information is
-            // maintained about. This is later used during the `InstanceExport`
-            // alias within a type declaration.
-            TypeDef::ComponentInstance(ty) => {
-                scope.instances.push(ty);
+            types::ComponentDefinedType::Variant(e) => {
+                InterfaceType::Variant(self.variant_type(types, e)?)
             }
-
-            // All other valid types are ignored since we don't need to maintain
-            // metadata about them here as index spaces are modified that we're
-            // not interested in.
-            _ => {}
-        }
-
-        ty
-    }
-
-    fn func_type(&mut self, ty: &wasmparser::ComponentFuncType<'_>) -> TypeFuncIndex {
-        let ty = TypeFunc {
-            params: ty
-                .params
-                .iter()
-                .map(|(_name, ty)| self.valtype(ty))
-                .collect(),
-            results: ty
-                .results
-                .iter()
-                .map(|(_name, ty)| self.valtype(ty))
-                .collect(),
-        };
-        self.add_func_type(ty)
-    }
-
-    fn defined_type(&mut self, ty: &wasmparser::ComponentDefinedType<'_>) -> Result<InterfaceType> {
-        let result = match ty {
-            wasmparser::ComponentDefinedType::Primitive(ty) => ty.into(),
-            wasmparser::ComponentDefinedType::Record(e) => {
-                InterfaceType::Record(self.record_type(e))
+            types::ComponentDefinedType::List(e) => InterfaceType::List(self.list_type(types, e)?),
+            types::ComponentDefinedType::Tuple(e) => {
+                InterfaceType::Tuple(self.tuple_type(types, e)?)
             }
-            wasmparser::ComponentDefinedType::Variant(e) => {
-                InterfaceType::Variant(self.variant_type(e))
+            types::ComponentDefinedType::Flags(e) => InterfaceType::Flags(self.flags_type(e)),
+            types::ComponentDefinedType::Enum(e) => InterfaceType::Enum(self.enum_type(e)),
+            types::ComponentDefinedType::Union(e) => {
+                InterfaceType::Union(self.union_type(types, e)?)
             }
-            wasmparser::ComponentDefinedType::List(e) => InterfaceType::List(self.list_type(e)),
-            wasmparser::ComponentDefinedType::Tuple(e) => InterfaceType::Tuple(self.tuple_type(e)),
-            wasmparser::ComponentDefinedType::Flags(e) => InterfaceType::Flags(self.flags_type(e)),
-            wasmparser::ComponentDefinedType::Enum(e) => InterfaceType::Enum(self.enum_type(e)),
-            wasmparser::ComponentDefinedType::Union(e) => InterfaceType::Union(self.union_type(e)),
-            wasmparser::ComponentDefinedType::Option(e) => {
-                InterfaceType::Option(self.option_type(e))
+            types::ComponentDefinedType::Option(e) => {
+                InterfaceType::Option(self.option_type(types, e)?)
             }
-            wasmparser::ComponentDefinedType::Result { ok, err } => {
-                InterfaceType::Result(self.result_type(ok, err))
+            types::ComponentDefinedType::Result { ok, err } => {
+                InterfaceType::Result(self.result_type(types, ok, err)?)
             }
-            wasmparser::ComponentDefinedType::Own(_)
-            | wasmparser::ComponentDefinedType::Borrow(_) => {
+            types::ComponentDefinedType::Own(_) | types::ComponentDefinedType::Borrow(_) => {
                 unimplemented!("resource types")
             }
         };
-        let info = self.type_information(&result);
+        let info = self.type_information(&ret);
         if info.depth > MAX_TYPE_DEPTH {
             bail!("type nesting is too deep");
         }
-        Ok(result)
+        self.defined_types_cache.insert(id, ret);
+        Ok(ret)
     }
 
-    fn valtype(&mut self, ty: &wasmparser::ComponentValType) -> InterfaceType {
+    fn valtype(
+        &mut self,
+        types: types::TypesRef<'_>,
+        ty: &types::ComponentValType,
+    ) -> Result<InterfaceType> {
         match ty {
-            wasmparser::ComponentValType::Primitive(p) => p.into(),
-            wasmparser::ComponentValType::Type(idx) => {
-                let idx = ComponentTypeIndex::from_u32(*idx);
-                match self.component_outer_type(0, idx) {
-                    TypeDef::Interface(ty) => ty,
-                    // this should not be possible if the module validated
-                    _ => unreachable!(),
-                }
-            }
+            types::ComponentValType::Primitive(p) => Ok(p.into()),
+            types::ComponentValType::Type(id) => self.defined_type(types, *id),
         }
     }
 
-    fn record_type(&mut self, record: &[(&str, wasmparser::ComponentValType)]) -> TypeRecordIndex {
-        let fields = record
+    fn record_type(
+        &mut self,
+        types: types::TypesRef<'_>,
+        ty: &types::RecordType,
+    ) -> Result<TypeRecordIndex> {
+        let fields = ty
+            .fields
             .iter()
-            .map(|(name, ty)| RecordField {
-                name: name.to_string(),
-                ty: self.valtype(ty),
+            .map(|(name, ty)| {
+                Ok(RecordField {
+                    name: name.to_string(),
+                    ty: self.valtype(types, ty)?,
+                })
             })
-            .collect::<Box<[_]>>();
+            .collect::<Result<Box<[_]>>>()?;
         let abi = CanonicalAbiInfo::record(
             fields
                 .iter()
                 .map(|field| self.component_types.canonical_abi(&field.ty)),
         );
-        self.add_record_type(TypeRecord { fields, abi })
+        Ok(self.add_record_type(TypeRecord { fields, abi }))
     }
 
-    fn variant_type(&mut self, cases: &[wasmparser::VariantCase<'_>]) -> TypeVariantIndex {
-        let cases = cases
+    fn variant_type(
+        &mut self,
+        types: types::TypesRef<'_>,
+        ty: &types::VariantType,
+    ) -> Result<TypeVariantIndex> {
+        let cases = ty
+            .cases
             .iter()
-            .map(|case| {
+            .map(|(name, case)| {
                 // FIXME: need to implement `refines`, not sure what that
                 // is at this time.
-                assert!(case.refines.is_none());
-                VariantCase {
-                    name: case.name.to_string(),
-                    ty: case.ty.as_ref().map(|ty| self.valtype(ty)),
+                if case.refines.is_some() {
+                    bail!("refines is not supported at this time");
                 }
+                Ok(VariantCase {
+                    name: name.to_string(),
+                    ty: match &case.ty.as_ref() {
+                        Some(ty) => Some(self.valtype(types, ty)?),
+                        None => None,
+                    },
+                })
             })
-            .collect::<Box<[_]>>();
+            .collect::<Result<Box<[_]>>>()?;
         let (info, abi) = VariantInfo::new(cases.iter().map(|c| {
             c.ty.as_ref()
                 .map(|ty| self.component_types.canonical_abi(ty))
         }));
-        self.add_variant_type(TypeVariant { cases, abi, info })
+        Ok(self.add_variant_type(TypeVariant { cases, abi, info }))
     }
 
-    fn tuple_type(&mut self, types: &[wasmparser::ComponentValType]) -> TypeTupleIndex {
-        let types = types
+    fn tuple_type(
+        &mut self,
+        types: types::TypesRef<'_>,
+        ty: &types::TupleType,
+    ) -> Result<TypeTupleIndex> {
+        let types = ty
+            .types
             .iter()
-            .map(|ty| self.valtype(ty))
-            .collect::<Box<[_]>>();
+            .map(|ty| self.valtype(types, ty))
+            .collect::<Result<Box<[_]>>>()?;
         let abi = CanonicalAbiInfo::record(
             types
                 .iter()
                 .map(|ty| self.component_types.canonical_abi(ty)),
         );
-        self.add_tuple_type(TypeTuple { types, abi })
+        Ok(self.add_tuple_type(TypeTuple { types, abi }))
     }
 
-    fn flags_type(&mut self, flags: &[&str]) -> TypeFlagsIndex {
+    fn flags_type(&mut self, flags: &IndexSet<KebabString>) -> TypeFlagsIndex {
         let flags = TypeFlags {
             names: flags.iter().map(|s| s.to_string()).collect(),
             abi: CanonicalAbiInfo::flags(flags.len()),
@@ -791,48 +679,68 @@ impl ComponentTypesBuilder {
         self.add_flags_type(flags)
     }
 
-    fn enum_type(&mut self, variants: &[&str]) -> TypeEnumIndex {
+    fn enum_type(&mut self, variants: &IndexSet<KebabString>) -> TypeEnumIndex {
         let names = variants.iter().map(|s| s.to_string()).collect::<Box<[_]>>();
         let (info, abi) = VariantInfo::new(names.iter().map(|_| None));
         self.add_enum_type(TypeEnum { names, abi, info })
     }
 
-    fn union_type(&mut self, types: &[wasmparser::ComponentValType]) -> TypeUnionIndex {
-        let types = types
+    fn union_type(
+        &mut self,
+        types: types::TypesRef<'_>,
+        ty: &types::UnionType,
+    ) -> Result<TypeUnionIndex> {
+        let types = ty
+            .types
             .iter()
-            .map(|ty| self.valtype(ty))
-            .collect::<Box<[_]>>();
+            .map(|ty| self.valtype(types, ty))
+            .collect::<Result<Box<[_]>>>()?;
         let (info, abi) = VariantInfo::new(
             types
                 .iter()
                 .map(|t| Some(self.component_types.canonical_abi(t))),
         );
-        self.add_union_type(TypeUnion { types, abi, info })
+        Ok(self.add_union_type(TypeUnion { types, abi, info }))
     }
 
-    fn option_type(&mut self, ty: &wasmparser::ComponentValType) -> TypeOptionIndex {
-        let ty = self.valtype(ty);
+    fn option_type(
+        &mut self,
+        types: types::TypesRef<'_>,
+        ty: &types::ComponentValType,
+    ) -> Result<TypeOptionIndex> {
+        let ty = self.valtype(types, ty)?;
         let (info, abi) = VariantInfo::new([None, Some(self.component_types.canonical_abi(&ty))]);
-        self.add_option_type(TypeOption { ty, abi, info })
+        Ok(self.add_option_type(TypeOption { ty, abi, info }))
     }
 
     fn result_type(
         &mut self,
-        ok: &Option<wasmparser::ComponentValType>,
-        err: &Option<wasmparser::ComponentValType>,
-    ) -> TypeResultIndex {
-        let ok = ok.as_ref().map(|ty| self.valtype(ty));
-        let err = err.as_ref().map(|ty| self.valtype(ty));
+        types: types::TypesRef<'_>,
+        ok: &Option<types::ComponentValType>,
+        err: &Option<types::ComponentValType>,
+    ) -> Result<TypeResultIndex> {
+        let ok = match ok {
+            Some(ty) => Some(self.valtype(types, ty)?),
+            None => None,
+        };
+        let err = match err {
+            Some(ty) => Some(self.valtype(types, ty)?),
+            None => None,
+        };
         let (info, abi) = VariantInfo::new([
             ok.as_ref().map(|t| self.component_types.canonical_abi(t)),
             err.as_ref().map(|t| self.component_types.canonical_abi(t)),
         ]);
-        self.add_result_type(TypeResult { ok, err, abi, info })
+        Ok(self.add_result_type(TypeResult { ok, err, abi, info }))
     }
 
-    fn list_type(&mut self, ty: &wasmparser::ComponentValType) -> TypeListIndex {
-        let element = self.valtype(ty);
-        self.add_list_type(TypeList { element })
+    fn list_type(
+        &mut self,
+        types: types::TypesRef<'_>,
+        ty: &types::ComponentValType,
+    ) -> Result<TypeListIndex> {
+        let element = self.valtype(types, ty)?;
+        Ok(self.add_list_type(TypeList { element }))
     }
 
     /// Interns a new function type within this type information.
@@ -943,11 +851,8 @@ impl ComponentTypesBuilder {
 }
 
 impl TypeConvert for ComponentTypesBuilder {
-    fn lookup_heap_type(&self, index: TypeIndex) -> WasmHeapType {
-        match self.type_scopes.last().unwrap().core[index] {
-            TypeDef::CoreFunc(i) => WasmHeapType::TypedFunc(i),
-            _ => unreachable!(),
-        }
+    fn lookup_heap_type(&self, _index: TypeIndex) -> WasmHeapType {
+        panic!("heap types are not supported yet")
     }
 }
 
diff --git a/crates/wasi-preview1-component-adapter/src/lib.rs b/crates/wasi-preview1-component-adapter/src/lib.rs
index d10fc48cd..d3f1ffa77 100644
--- a/crates/wasi-preview1-component-adapter/src/lib.rs
+++ b/crates/wasi-preview1-component-adapter/src/lib.rs
@@ -34,9 +34,13 @@ pub mod bindings {
         world: "wasi:preview/command",
         std_feature,
         raw_strings,
-        // The generated definition of command will pull in std, so we are defining it
-        // manually below instead
-        skip: ["run", "get-directories", "get-environment"],
+        // Automatically generated bindings for these functions will allocate
+        // Vecs, which in turn pulls in the panic machinery from std, which
+        // creates vtables that end up in the wasm elem section, which we
+        // can't support in these special core-wasm adapters.
+        // Instead, we manually define the bindings for these functions in
+        // terms of raw pointers.
+        skip: ["run", "get-environment", "poll-oneoff"],
     });
 
     #[cfg(feature = "reactor")]
@@ -45,7 +49,13 @@ pub mod bindings {
         world: "wasi:preview/reactor",
         std_feature,
         raw_strings,
-        skip: ["get-directories", "get-environment"],
+        // Automatically generated bindings for these functions will allocate
+        // Vecs, which in turn pulls in the panic machinery from std, which
+        // creates vtables that end up in the wasm elem section, which we
+        // can't support in these special core-wasm adapters.
+        // Instead, we manually define the bindings for these functions in
+        // terms of raw pointers.
+        skip: ["get-environment", "poll-oneoff"],
     });
 }
 
@@ -1686,24 +1696,39 @@ pub unsafe extern "C" fn poll_oneoff(
             });
         }
 
-        let vec = state.import_alloc.with_buffer(
+        #[link(wasm_import_module = "wasi:poll/poll")]
+        extern "C" {
+            #[link_name = "poll-oneoff"]
+            fn poll_oneoff_import(pollables: *const Pollable, len: usize, rval: *mut BoolList);
+        }
+        let mut ready_list = BoolList {
+            base: std::ptr::null(),
+            len: 0,
+        };
+
+        state.import_alloc.with_buffer(
             results,
             nsubscriptions
                 .checked_mul(size_of::<bool>())
                 .trapping_unwrap(),
-            || poll::poll_oneoff(slice::from_raw_parts(pollables.pointer, pollables.length)),
+            || {
+                poll_oneoff_import(
+                    pollables.pointer,
+                    pollables.length,
+                    &mut ready_list as *mut _,
+                )
+            },
         );
 
-        assert_eq!(vec.len(), nsubscriptions);
-        assert_eq!(vec.as_ptr(), results);
-        forget(vec);
+        assert_eq!(ready_list.len, nsubscriptions);
+        assert_eq!(ready_list.base, results as *const bool);
 
         drop(pollables);
 
         let ready = subscriptions
             .iter()
             .enumerate()
-            .filter_map(|(i, s)| (*results.add(i) != 0).then_some(s));
+            .filter_map(|(i, s)| (*ready_list.base.add(i)).then_some(s));
 
         let mut count = 0;
 
@@ -2176,6 +2201,13 @@ pub struct StrTupleList {
     len: usize,
 }
 
+#[derive(Copy, Clone)]
+#[repr(C)]
+pub struct BoolList {
+    base: *const bool,
+    len: usize,
+}
+
 const fn bump_arena_size() -> usize {
     // The total size of the struct should be a page, so start there
     let mut start = PAGE_SIZE;
diff --git a/crates/wasi/src/preview2/ctx.rs b/crates/wasi/src/preview2/ctx.rs
index ab397dc73..681d4860b 100644
--- a/crates/wasi/src/preview2/ctx.rs
+++ b/crates/wasi/src/preview2/ctx.rs
@@ -7,26 +7,25 @@ use crate::preview2::{
 };
 use cap_rand::{Rng, RngCore, SeedableRng};
 
-#[derive(Default)]
 pub struct WasiCtxBuilder {
-    stdin: Option<Box<dyn InputStream>>,
-    stdout: Option<Box<dyn OutputStream>>,
-    stderr: Option<Box<dyn OutputStream>>,
+    stdin: Box<dyn InputStream>,
+    stdout: Box<dyn OutputStream>,
+    stderr: Box<dyn OutputStream>,
     env: Vec<(String, String)>,
     args: Vec<String>,
     preopens: Vec<(Dir, String)>,
 
-    random: Option<Box<dyn RngCore + Send + Sync>>,
-    insecure_random: Option<Box<dyn RngCore + Send + Sync>>,
-    insecure_random_seed: Option<u128>,
-    clocks: Option<WasiClocks>,
+    random: Box<dyn RngCore + Send + Sync>,
+    insecure_random: Box<dyn RngCore + Send + Sync>,
+    insecure_random_seed: u128,
+    clocks: WasiClocks,
 }
 
 impl WasiCtxBuilder {
     pub fn new() -> Self {
         // For the insecure random API, use `SmallRng`, which is fast. It's
         // also insecure, but that's the deal here.
-        let insecure_random = cap_rand::rngs::SmallRng::from_entropy();
+        let insecure_random = Box::new(cap_rand::rngs::SmallRng::from_entropy());
 
         // For the insecure random seed, use a `u128` generated from
         // `thread_rng()`, so that it's not guessable from the insecure_random
@@ -34,29 +33,32 @@ impl WasiCtxBuilder {
         let insecure_random_seed =
             cap_rand::thread_rng(cap_rand::ambient_authority()).gen::<u128>();
 
-        let result = Self::default()
-            .set_clocks(clocks::host::clocks_ctx())
-            .set_insecure_random(insecure_random)
-            .set_insecure_random_seed(insecure_random_seed)
-            .set_stdin(pipe::ReadPipe::new(std::io::empty()))
-            .set_stdout(pipe::WritePipe::new(std::io::sink()))
-            .set_stderr(pipe::WritePipe::new(std::io::sink()))
-            .set_secure_random();
-        result
+        Self {
+            stdin: Box::new(pipe::ReadPipe::new(std::io::empty())),
+            stdout: Box::new(pipe::WritePipe::new(std::io::sink())),
+            stderr: Box::new(pipe::WritePipe::new(std::io::sink())),
+            env: Vec::new(),
+            args: Vec::new(),
+            preopens: Vec::new(),
+            random: random::thread_rng(),
+            insecure_random,
+            insecure_random_seed,
+            clocks: clocks::host::clocks_ctx(),
+        }
     }
 
     pub fn set_stdin(mut self, stdin: impl InputStream + 'static) -> Self {
-        self.stdin = Some(Box::new(stdin));
+        self.stdin = Box::new(stdin);
         self
     }
 
     pub fn set_stdout(mut self, stdout: impl OutputStream + 'static) -> Self {
-        self.stdout = Some(Box::new(stdout));
+        self.stdout = Box::new(stdout);
         self
     }
 
     pub fn set_stderr(mut self, stderr: impl OutputStream + 'static) -> Self {
-        self.stderr = Some(Box::new(stderr));
+        self.stderr = Box::new(stderr);
         self
     }
 
@@ -100,7 +102,6 @@ impl WasiCtxBuilder {
         self
     }
 
-    // TODO: optionally read-only
     pub fn push_preopened_dir(
         mut self,
         dir: cap_std::fs::Dir,
@@ -118,7 +119,7 @@ impl WasiCtxBuilder {
     /// This initializes the random number generator using
     /// [`cap_rand::thread_rng`].
     pub fn set_secure_random(mut self) -> Self {
-        self.random = Some(random::thread_rng());
+        self.random = random::thread_rng();
         self
     }
 
@@ -139,7 +140,7 @@ impl WasiCtxBuilder {
         mut self,
         random: impl RngCore + Send + Sync + 'static,
     ) -> Self {
-        self.random = Some(Box::new(random));
+        self.random = Box::new(random);
         self
     }
 
@@ -147,54 +148,58 @@ impl WasiCtxBuilder {
         mut self,
         insecure_random: impl RngCore + Send + Sync + 'static,
     ) -> Self {
-        self.insecure_random = Some(Box::new(insecure_random));
+        self.insecure_random = Box::new(insecure_random);
         self
     }
     pub fn set_insecure_random_seed(mut self, insecure_random_seed: u128) -> Self {
-        self.insecure_random_seed = Some(insecure_random_seed);
+        self.insecure_random_seed = insecure_random_seed;
         self
     }
     pub fn set_clocks(mut self, clocks: WasiClocks) -> Self {
-        self.clocks = Some(clocks);
+        self.clocks = clocks;
         self
     }
 
     pub fn build(self, table: &mut Table) -> Result<WasiCtx, anyhow::Error> {
         use anyhow::Context;
-
-        let stdin = table
-            .push_input_stream(self.stdin.context("required member stdin")?)
-            .context("stdin")?;
-        let stdout = table
-            .push_output_stream(self.stdout.context("required member stdout")?)
-            .context("stdout")?;
-        let stderr = table
-            .push_output_stream(self.stderr.context("required member stderr")?)
-            .context("stderr")?;
-
-        let mut preopens = Vec::new();
-        for (dir, path) in self.preopens {
-            let dirfd = table
-                .push_dir(dir)
-                .with_context(|| format!("preopen {path:?}"))?;
-            preopens.push((dirfd, path));
-        }
+        let Self {
+            stdin,
+            stdout,
+            stderr,
+            env,
+            args,
+            preopens,
+            random,
+            insecure_random,
+            insecure_random_seed,
+            clocks,
+        } = self;
+
+        let stdin = table.push_input_stream(stdin).context("stdin")?;
+        let stdout = table.push_output_stream(stdout).context("stdout")?;
+        let stderr = table.push_output_stream(stderr).context("stderr")?;
+
+        let preopens = preopens
+            .into_iter()
+            .map(|(dir, path)| {
+                let dirfd = table
+                    .push_dir(dir)
+                    .with_context(|| format!("preopen {path:?}"))?;
+                Ok((dirfd, path))
+            })
+            .collect::<anyhow::Result<Vec<_>>>()?;
 
         Ok(WasiCtx {
-            random: self.random.context("required member random")?,
-            insecure_random: self
-                .insecure_random
-                .context("required member insecure_random")?,
-            insecure_random_seed: self
-                .insecure_random_seed
-                .context("required member insecure_random_seed")?,
-            clocks: self.clocks.context("required member clocks")?,
-            env: self.env,
-            args: self.args,
-            preopens,
             stdin,
             stdout,
             stderr,
+            env,
+            args,
+            preopens,
+            random,
+            insecure_random,
+            insecure_random_seed,
+            clocks,
         })
     }
 }
@@ -208,20 +213,13 @@ pub trait WasiView: Send {
 
 pub struct WasiCtx {
     pub(crate) random: Box<dyn RngCore + Send + Sync>,
-    pub insecure_random: Box<dyn RngCore + Send + Sync>,
-    pub insecure_random_seed: u128,
-    pub clocks: WasiClocks,
-    pub env: Vec<(String, String)>,
-    pub args: Vec<String>,
-    pub preopens: Vec<(u32, String)>,
-    pub stdin: u32,
-    pub stdout: u32,
-    pub stderr: u32,
-}
-
-impl WasiCtx {
-    /// Create an empty `WasiCtxBuilder`
-    pub fn builder() -> WasiCtxBuilder {
-        WasiCtxBuilder::default()
-    }
+    pub(crate) insecure_random: Box<dyn RngCore + Send + Sync>,
+    pub(crate) insecure_random_seed: u128,
+    pub(crate) clocks: WasiClocks,
+    pub(crate) env: Vec<(String, String)>,
+    pub(crate) args: Vec<String>,
+    pub(crate) preopens: Vec<(u32, String)>,
+    pub(crate) stdin: u32,
+    pub(crate) stdout: u32,
+    pub(crate) stderr: u32,
 }
diff --git a/crates/wasi/src/preview2/pipe.rs b/crates/wasi/src/preview2/pipe.rs
index a29aada93..2b7b1fdb3 100644
--- a/crates/wasi/src/preview2/pipe.rs
+++ b/crates/wasi/src/preview2/pipe.rs
@@ -26,9 +26,9 @@ use system_interface::io::ReadReady;
 /// easy to create. For example:
 ///
 /// ```
-/// use wasmtime_wasi::preview2::{pipe::ReadPipe, WasiCtx};
+/// use wasmtime_wasi::preview2::{pipe::ReadPipe, WasiCtxBuilder};
 /// let stdin = ReadPipe::from("hello from stdin!");
-/// let builder = WasiCtx::builder().set_stdin(stdin);
+/// let builder = WasiCtxBuilder::new().set_stdin(stdin);
 /// ```
 #[derive(Debug)]
 pub struct ReadPipe<R: Read + ReadReady> {
@@ -134,10 +134,10 @@ impl<R: Read + ReadReady + Any + Send + Sync> InputStream for ReadPipe<R> {
 /// A virtual pipe write end.
 ///
 /// ```no_run
-/// use wasmtime_wasi::preview2::{pipe::WritePipe, WasiCtx, Table};
+/// use wasmtime_wasi::preview2::{pipe::WritePipe, WasiCtxBuilder, Table};
 /// let mut table = Table::new();
 /// let stdout = WritePipe::new_in_memory();
-/// let mut ctx = WasiCtx::builder().set_stdout(stdout.clone()).build(&mut table).unwrap();
+/// let mut ctx = WasiCtxBuilder::new().set_stdout(stdout.clone()).build(&mut table).unwrap();
 /// // use ctx and table in an instance, then make sure it is dropped:
 /// drop(ctx);
 /// drop(table);
diff --git a/crates/wasi/src/preview2/preview2/poll.rs b/crates/wasi/src/preview2/preview2/poll.rs
index d3b9380c7..338b83518 100644
--- a/crates/wasi/src/preview2/preview2/poll.rs
+++ b/crates/wasi/src/preview2/preview2/poll.rs
@@ -34,7 +34,7 @@ impl<T: WasiView> poll::Host for T {
         Ok(())
     }
 
-    async fn poll_oneoff(&mut self, futures: Vec<Pollable>) -> anyhow::Result<Vec<u8>> {
+    async fn poll_oneoff(&mut self, futures: Vec<Pollable>) -> anyhow::Result<Vec<bool>> {
         use crate::preview2::sched::{sync::SyncSched, Poll, Userdata, WasiSched};
 
         // Convert `futures` into `Poll` subscriptions.
@@ -74,10 +74,9 @@ impl<T: WasiView> poll::Host for T {
         // Do the poll.
         SyncSched.poll_oneoff(&mut poll).await?;
 
-        // Convert the results into a list of `u8` to return.
-        let mut results = vec![0_u8; len];
+        let mut results = vec![false; len];
         for (_result, data) in poll.results() {
-            results[u64::from(data) as usize] = u8::from(true);
+            results[u64::from(data) as usize] = true;
         }
         Ok(results)
     }
diff --git a/crates/wasi/wit/deps/poll/poll.wit b/crates/wasi/wit/deps/poll/poll.wit
index cf5d8779c..a6334c557 100644
--- a/crates/wasi/wit/deps/poll/poll.wit
+++ b/crates/wasi/wit/deps/poll/poll.wit
@@ -32,10 +32,8 @@ interface poll {
     /// component model async proposal, which will include a scalable waiting
     /// facility.
     ///
-    /// Note that the return type would ideally be `list<bool>`, but that would
-    /// be more difficult to polyfill given the current state of `wit-bindgen`.
-    /// See <https://github.com/bytecodealliance/preview2-prototyping/pull/11#issuecomment-1329873061>
-    /// for details.  For now, we use zero to mean "not ready" and non-zero to
-    /// mean "ready".
-    poll-oneoff: func(in: list<pollable>) -> list<u8>
+    /// The result list<bool> is the same length as the argument
+    /// list<pollable>, and indicates the readiness of each corresponding
+    /// element in that / list, with true indicating ready.
+    poll-oneoff: func(in: list<pollable>) -> list<bool>
 }
diff --git a/crates/wit-bindgen/src/lib.rs b/crates/wit-bindgen/src/lib.rs
index 26ce89f86..f4812254e 100644
--- a/crates/wit-bindgen/src/lib.rs
+++ b/crates/wit-bindgen/src/lib.rs
@@ -63,6 +63,25 @@ struct Exports {
     funcs: Vec<String>,
 }
 
+#[derive(Default, Debug, Clone, Copy)]
+pub enum Ownership {
+    /// Generated types will be composed entirely of owning fields, regardless
+    /// of whether they are used as parameters to guest exports or not.
+    #[default]
+    Owning,
+
+    /// Generated types used as parameters to guest exports will be "deeply
+    /// borrowing", i.e. contain references rather than owned values when
+    /// applicable.
+    Borrowing {
+        /// Whether or not to generate "duplicate" type definitions for a single
+        /// WIT type if necessary, for example if it's used as both an import
+        /// and an export, or if it's used both as a parameter to an export and
+        /// a return value from an export.
+        duplicate_if_necessary: bool,
+    },
+}
+
 #[derive(Default, Debug, Clone)]
 pub struct Opts {
     /// Whether or not `rustfmt` is executed to format generated code.
@@ -78,10 +97,8 @@ pub struct Opts {
     /// `result<T, E>` found in WIT.
     pub trappable_error_type: Vec<TrappableError>,
 
-    /// Whether or not to generate "duplicate" type definitions for a single
-    /// WIT type if necessary, for example if it's used as both an import and an
-    /// export.
-    pub duplicate_if_necessary: bool,
+    /// Whether to generate owning or borrowing type definitions.
+    pub ownership: Ownership,
 
     /// Whether or not to generate code for only the interfaces of this wit file or not.
     pub only_interfaces: bool,
@@ -1582,8 +1599,8 @@ impl<'a> RustGenerator<'a> for InterfaceGenerator<'a> {
         self.resolve
     }
 
-    fn duplicate_if_necessary(&self) -> bool {
-        self.gen.opts.duplicate_if_necessary
+    fn ownership(&self) -> Ownership {
+        self.gen.opts.ownership
     }
 
     fn path_to_interface(&self, interface: InterfaceId) -> Option<String> {
diff --git a/crates/wit-bindgen/src/rust.rs b/crates/wit-bindgen/src/rust.rs
index 38df7d6de..4bbc12ff5 100644
--- a/crates/wit-bindgen/src/rust.rs
+++ b/crates/wit-bindgen/src/rust.rs
@@ -1,4 +1,4 @@
-use crate::types::TypeInfo;
+use crate::{types::TypeInfo, Ownership};
 use heck::*;
 use std::collections::HashMap;
 use std::fmt::Write;
@@ -17,15 +17,16 @@ pub trait RustGenerator<'a> {
     fn info(&self, ty: TypeId) -> TypeInfo;
     fn path_to_interface(&self, interface: InterfaceId) -> Option<String>;
 
-    /// This, if enabled, will possibly cause types to get duplicate copies to
-    /// get generated of each other. For example a record containing a string
-    /// used both in the import and export context would get one variant
-    /// generated for both.
+    /// This determines whether we generate owning types or (where appopriate)
+    /// borrowing types.
     ///
-    /// If this is disabled then the import context would require the same type
-    /// used for the export context, which has an owned string that might not
-    /// otherwise be necessary.
-    fn duplicate_if_necessary(&self) -> bool;
+    /// For example, when generating a type which is only used as a parameter to
+    /// a guest-exported function, there is no need for it to own its fields.
+    /// However, constructing deeply-nested borrows (e.g. `&[&[&[&str]]]]` for
+    /// `list<list<list<string>>>`) can be very awkward, so by default we
+    /// generate owning types and use only shallow borrowing at the top level
+    /// inside function signatures.
+    fn ownership(&self) -> Ownership;
 
     fn print_ty(&mut self, ty: &Type, mode: TypeMode) {
         match ty {
@@ -188,6 +189,11 @@ pub trait RustGenerator<'a> {
     }
 
     fn print_list(&mut self, ty: &Type, mode: TypeMode) {
+        let next_mode = if matches!(self.ownership(), Ownership::Owning) {
+            TypeMode::Owned
+        } else {
+            mode
+        };
         match mode {
             TypeMode::AllBorrowed(lt) => {
                 self.push_str("&");
@@ -196,12 +202,12 @@ pub trait RustGenerator<'a> {
                     self.push_str(" ");
                 }
                 self.push_str("[");
-                self.print_ty(ty, mode);
+                self.print_ty(ty, next_mode);
                 self.push_str("]");
             }
             TypeMode::Owned => {
                 self.push_str("Vec<");
-                self.print_ty(ty, mode);
+                self.print_ty(ty, next_mode);
                 self.push_str(">");
             }
         }
@@ -225,12 +231,13 @@ pub trait RustGenerator<'a> {
             return Vec::new();
         }
         let mut result = Vec::new();
-        let first_mode = if info.owned || !info.borrowed {
-            TypeMode::Owned
-        } else {
-            assert!(!self.uses_two_names(&info));
-            TypeMode::AllBorrowed("'a")
-        };
+        let first_mode =
+            if info.owned || !info.borrowed || matches!(self.ownership(), Ownership::Owning) {
+                TypeMode::Owned
+            } else {
+                assert!(!self.uses_two_names(&info));
+                TypeMode::AllBorrowed("'a")
+            };
         result.push((self.result_name(ty), first_mode));
         if self.uses_two_names(&info) {
             result.push((self.param_name(ty), TypeMode::AllBorrowed("'a")));
@@ -378,10 +385,21 @@ pub trait RustGenerator<'a> {
     }
 
     fn uses_two_names(&self, info: &TypeInfo) -> bool {
-        info.has_list && info.borrowed && info.owned && self.duplicate_if_necessary()
+        info.has_list
+            && info.borrowed
+            && info.owned
+            && matches!(
+                self.ownership(),
+                Ownership::Borrowing {
+                    duplicate_if_necessary: true
+                }
+            )
     }
 
     fn lifetime_for(&self, info: &TypeInfo, mode: TypeMode) -> Option<&'static str> {
+        if matches!(self.ownership(), Ownership::Owning) {
+            return None;
+        }
         let lt = match mode {
             TypeMode::AllBorrowed(s) => s,
             _ => return None,
diff --git a/crates/wit-bindgen/src/types.rs b/crates/wit-bindgen/src/types.rs
index 251a52ea7..223bfcc91 100644
--- a/crates/wit-bindgen/src/types.rs
+++ b/crates/wit-bindgen/src/types.rs
@@ -69,11 +69,13 @@ impl Types {
             live.add_type(resolve, ty);
         }
         for id in live.iter() {
-            let info = self.type_info.get_mut(&id).unwrap();
-            if import {
-                info.owned = true;
-            } else {
-                info.borrowed = true;
+            if resolve.types[id].name.is_some() {
+                let info = self.type_info.get_mut(&id).unwrap();
+                if import {
+                    info.owned = true;
+                } else {
+                    info.borrowed = true;
+                }
             }
         }
         let mut live = LiveTypes::default();
@@ -82,7 +84,9 @@ impl Types {
             live.add_type(resolve, ty);
         }
         for id in live.iter() {
-            self.type_info.get_mut(&id).unwrap().owned = true;
+            if resolve.types[id].name.is_some() {
+                self.type_info.get_mut(&id).unwrap().owned = true;
+            }
         }
 
         for ty in func.results.iter_types() {


Test patch:
diff --git a/cranelift/filetests/filetests/isa/x64/return-call-indirect.clif b/cranelift/filetests/filetests/isa/x64/return-call-indirect.clif
new file mode 100644
index 000000000..aaed42342
--- /dev/null
+++ b/cranelift/filetests/filetests/isa/x64/return-call-indirect.clif
@@ -0,0 +1,218 @@
+test compile precise-output
+set preserve_frame_pointers=true
+target x86_64
+
+;;;; Test passing `i64`s ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+function %callee_i64(i64) -> i64 tail {
+block0(v0: i64):
+    v1 = iadd_imm.i64 v0, 10
+    return v1
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   lea     10(%rax), %rax
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   addq $0xa, %rax
+;   movq %rbp, %rsp
+;   popq %rbp
+;   retq
+
+function %call_i64(i64) -> i64 tail {
+    sig0 = (i64) -> i64 tail
+    fn0 = %callee_i64(i64) -> i64 tail
+
+block0(v0: i64):
+    v1 = func_addr.i64 fn0
+    return_call_indirect sig0, v1(v0)
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   load_ext_name %callee_i64+0, %rdx
+;   movq    %rbp, %rcx
+;   return_call_unknown %rdx new_stack_arg_size:0 old_stack_arg_size:0 ret_addr:None fp:%v194 tmp:%v195 %rax=%rax
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   movabsq $0, %rdx ; reloc_external Abs8 %callee_i64 0
+;   movq %rbp, %rcx
+;   movq (%rcx), %rbp
+;   leaq 8(%rcx), %rsp
+;   jmpq *%rdx
+
+;;;; Test colocated tail calls ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+function %colocated_i64(i64) -> i64 tail {
+    sig0 = (i64) -> i64 tail
+    fn0 = colocated %callee_i64(i64) -> i64 tail
+
+block0(v0: i64):
+    v1 = func_addr.i64 fn0
+    return_call_indirect sig0, v1(v0)
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   load_ext_name %callee_i64+0, %rdx
+;   movq    %rbp, %rcx
+;   return_call_unknown %rdx new_stack_arg_size:0 old_stack_arg_size:0 ret_addr:None fp:%v194 tmp:%v195 %rax=%rax
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   leaq (%rip), %rdx ; reloc_external CallPCRel4 %callee_i64 -4
+;   movq %rbp, %rcx
+;   movq (%rcx), %rbp
+;   leaq 8(%rcx), %rsp
+;   jmpq *%rdx
+
+;;;; Test passing `f64`s ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+function %callee_f64(f64) -> f64 tail {
+block0(v0: f64):
+    v1 = f64const 0x10.0
+    v2 = fadd.f64 v0, v1
+    return v2
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   addsd   %xmm0, const(0), %xmm0
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   addsd 0x14(%rip), %xmm0
+;   movq %rbp, %rsp
+;   popq %rbp
+;   retq
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   addb %dh, (%rax)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+
+function %call_f64(f64) -> f64 tail {
+    sig0 = (f64) -> f64 tail
+    fn0 = %callee_f64(f64) -> f64 tail
+
+block0(v0: f64):
+    v1 = func_addr.i64 fn0
+    return_call_indirect sig0, v1(v0)
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   load_ext_name %callee_f64+0, %rdx
+;   movq    %rbp, %rcx
+;   return_call_unknown %rdx new_stack_arg_size:0 old_stack_arg_size:0 ret_addr:None fp:%v194 tmp:%v195 %xmm0=%xmm0
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   movabsq $0, %rdx ; reloc_external Abs8 %callee_f64 0
+;   movq %rbp, %rcx
+;   movq (%rcx), %rbp
+;   leaq 8(%rcx), %rsp
+;   jmpq *%rdx
+
+;;;; Test passing `i8`s ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+function %callee_i8(i8) -> i8 tail {
+block0(v0: i8):
+    v1 = iconst.i8 0
+    v2 = icmp eq v0, v1
+    return v2
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   testb   %al, %al
+;   setz    %al
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   testb %al, %al
+;   sete %al
+;   movq %rbp, %rsp
+;   popq %rbp
+;   retq
+
+function %call_i8(i8) -> i8 tail {
+    sig0 = (i8) -> i8 tail
+    fn0 = %callee_i8(i8) -> i8 tail
+
+block0(v0: i8):
+    v1 = func_addr.i64 fn0
+    return_call_indirect sig0, v1(v0)
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   load_ext_name %callee_i8+0, %rdx
+;   movq    %rbp, %rcx
+;   return_call_unknown %rdx new_stack_arg_size:0 old_stack_arg_size:0 ret_addr:None fp:%v194 tmp:%v195 %rax=%rax
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   movabsq $0, %rdx ; reloc_external Abs8 %callee_i8 0
+;   movq %rbp, %rcx
+;   movq (%rcx), %rbp
+;   leaq 8(%rcx), %rsp
+;   jmpq *%rdx
+
diff --git a/cranelift/filetests/filetests/isa/x64/return-call.clif b/cranelift/filetests/filetests/isa/x64/return-call.clif
new file mode 100644
index 000000000..fc3f2c48e
--- /dev/null
+++ b/cranelift/filetests/filetests/isa/x64/return-call.clif
@@ -0,0 +1,500 @@
+test compile precise-output
+set preserve_frame_pointers=true
+target x86_64
+
+;;;; Test passing `i64`s ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+function %callee_i64(i64) -> i64 tail {
+block0(v0: i64):
+    v1 = iadd_imm.i64 v0, 10
+    return v1
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   lea     10(%rax), %rax
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   addq $0xa, %rax
+;   movq %rbp, %rsp
+;   popq %rbp
+;   retq
+
+function %call_i64(i64) -> i64 tail {
+    fn0 = %callee_i64(i64) -> i64 tail
+
+block0(v0: i64):
+    return_call fn0(v0)
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   movq    %rbp, %rcx
+;   load_ext_name %callee_i64+0, %r8
+;   return_call_unknown %r8 new_stack_arg_size:0 old_stack_arg_size:0 ret_addr:None fp:%v193 tmp:%v194 %rax=%rax
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   movq %rbp, %rcx
+;   movabsq $0, %r8 ; reloc_external Abs8 %callee_i64 0
+;   movq (%rcx), %rbp
+;   leaq 8(%rcx), %rsp
+;   jmpq *%r8
+
+;;;; Test colocated tail calls ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+function %colocated_i64(i64) -> i64 tail {
+    fn0 = colocated %callee_i64(i64) -> i64 tail
+
+block0(v0: i64):
+    return_call fn0(v0)
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   movq    %rbp, %rcx
+;   return_call_known TestCase(%callee_i64) new_stack_arg_size:0 old_stack_arg_size:0 ret_addr:None fp:%v193 tmp:%v194 %rax=%rax
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   movq %rbp, %rcx
+;   movq (%rcx), %rbp
+;   leaq 8(%rcx), %rsp
+;   jmp 0x13 ; reloc_external CallPCRel4 %callee_i64 -4
+
+;;;; Test passing `f64`s ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+function %callee_f64(f64) -> f64 tail {
+block0(v0: f64):
+    v1 = f64const 0x10.0
+    v2 = fadd.f64 v0, v1
+    return v2
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   addsd   %xmm0, const(0), %xmm0
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   addsd 0x14(%rip), %xmm0
+;   movq %rbp, %rsp
+;   popq %rbp
+;   retq
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   addb %dh, (%rax)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+;   addb %al, (%rax)
+
+function %call_f64(f64) -> f64 tail {
+    fn0 = %callee_f64(f64) -> f64 tail
+
+block0(v0: f64):
+    return_call fn0(v0)
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   movq    %rbp, %rax
+;   load_ext_name %callee_f64+0, %r8
+;   return_call_unknown %r8 new_stack_arg_size:0 old_stack_arg_size:0 ret_addr:None fp:%v193 tmp:%v194 %xmm0=%xmm0
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   movq %rbp, %rax
+;   movabsq $0, %r8 ; reloc_external Abs8 %callee_f64 0
+;   movq (%rax), %rbp
+;   leaq 8(%rax), %rsp
+;   jmpq *%r8
+
+;;;; Test passing `i8`s ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+function %callee_i8(i8) -> i8 tail {
+block0(v0: i8):
+    v1 = iconst.i8 0
+    v2 = icmp eq v0, v1
+    return v2
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   testb   %al, %al
+;   setz    %al
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   testb %al, %al
+;   sete %al
+;   movq %rbp, %rsp
+;   popq %rbp
+;   retq
+
+function %call_i8(i8) -> i8 tail {
+    fn0 = %callee_i8(i8) -> i8 tail
+
+block0(v0: i8):
+    return_call fn0(v0)
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   movq    %rbp, %rcx
+;   load_ext_name %callee_i8+0, %r8
+;   return_call_unknown %r8 new_stack_arg_size:0 old_stack_arg_size:0 ret_addr:None fp:%v193 tmp:%v194 %rax=%rax
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   movq %rbp, %rcx
+;   movabsq $0, %r8 ; reloc_external Abs8 %callee_i8 0
+;   movq (%rcx), %rbp
+;   leaq 8(%rcx), %rsp
+;   jmpq *%r8
+
+;;;; Test passing many arguments on stack ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+function %tail_callee_stack_args(i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64) -> i64 tail {
+block0(v0: i64, v1: i64, v2: i64, v3: i64, v4: i64, v5: i64, v6: i64, v7: i64, v8: i64, v9: i64, v10: i64, v11: i64, v12: i64, v13: i64, v14: i64, v15: i64, v16: i64, v17: i64, v18: i64, v19: i64, v20: i64, v21: i64, v22: i64, v23: i64, v24: i64, v25: i64):
+    return v25
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   movq    16(%rbp), %rax
+;   movq    24(%rbp), %rdx
+;   movq    32(%rbp), %r9
+;   movq    40(%rbp), %r11
+;   movq    48(%rbp), %rdi
+;   movq    56(%rbp), %rcx
+;   movq    64(%rbp), %r8
+;   movq    72(%rbp), %r10
+;   movq    80(%rbp), %rsi
+;   movq    88(%rbp), %rax
+;   movq    96(%rbp), %rdx
+;   movq    104(%rbp), %r9
+;   movq    112(%rbp), %r11
+;   movq    120(%rbp), %rdi
+;   movq    128(%rbp), %rcx
+;   movq    136(%rbp), %rax
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret 128
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   movq 0x10(%rbp), %rax
+;   movq 0x18(%rbp), %rdx
+;   movq 0x20(%rbp), %r9
+;   movq 0x28(%rbp), %r11
+;   movq 0x30(%rbp), %rdi
+;   movq 0x38(%rbp), %rcx
+;   movq 0x40(%rbp), %r8
+;   movq 0x48(%rbp), %r10
+;   movq 0x50(%rbp), %rsi
+;   movq 0x58(%rbp), %rax
+;   movq 0x60(%rbp), %rdx
+;   movq 0x68(%rbp), %r9
+;   movq 0x70(%rbp), %r11
+;   movq 0x78(%rbp), %rdi
+;   movq 0x80(%rbp), %rcx
+;   movq 0x88(%rbp), %rax
+;   movq %rbp, %rsp
+;   popq %rbp
+;   retq $0x80
+
+function %tail_caller_stack_args() -> i64 tail {
+    fn0 = %tail_callee_stack_args(i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64) -> i64 tail
+
+block0:
+    v0 = iconst.i64 10
+    v1 = iconst.i64 15
+    v2 = iconst.i64 20
+    v3 = iconst.i64 25
+    v4 = iconst.i64 30
+    v5 = iconst.i64 35
+    v6 = iconst.i64 40
+    v7 = iconst.i64 45
+    v8 = iconst.i64 50
+    v9 = iconst.i64 55
+    v10 = iconst.i64 60
+    v11 = iconst.i64 65
+    v12 = iconst.i64 70
+    v13 = iconst.i64 75
+    v14 = iconst.i64 80
+    v15 = iconst.i64 85
+    v16 = iconst.i64 90
+    v17 = iconst.i64 95
+    v18 = iconst.i64 100
+    v19 = iconst.i64 105
+    v20 = iconst.i64 110
+    v21 = iconst.i64 115
+    v22 = iconst.i64 120
+    v23 = iconst.i64 125
+    v24 = iconst.i64 130
+    v25 = iconst.i64 135
+    return_call fn0(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25)
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+;   subq    %rsp, $128, %rsp
+; block0:
+;   movl    $10, %eax
+;   movq    %rax, rsp(112 + virtual offset)
+;   movl    $15, %ecx
+;   movq    %rcx, rsp(104 + virtual offset)
+;   movl    $20, %edx
+;   movq    %rdx, rsp(96 + virtual offset)
+;   movl    $25, %ebx
+;   movq    %rbx, rsp(88 + virtual offset)
+;   movl    $30, %esi
+;   movq    %rsi, rsp(80 + virtual offset)
+;   movl    $35, %edi
+;   movq    %rdi, rsp(72 + virtual offset)
+;   movl    $40, %r8d
+;   movq    %r8, rsp(64 + virtual offset)
+;   movl    $45, %r9d
+;   movq    %r9, rsp(56 + virtual offset)
+;   movl    $50, %r10d
+;   movq    %r10, rsp(48 + virtual offset)
+;   movl    $55, %r11d
+;   movq    %r11, rsp(40 + virtual offset)
+;   movl    $60, %r15d
+;   movl    $65, %eax
+;   movq    %rax, rsp(32 + virtual offset)
+;   movl    $70, %r13d
+;   movl    $75, %edi
+;   movl    $80, %eax
+;   movl    $85, %ecx
+;   movq    %rcx, rsp(24 + virtual offset)
+;   movl    $90, %edx
+;   movl    $95, %ebx
+;   movl    $100, %esi
+;   movl    $105, %ecx
+;   movq    %rcx, rsp(16 + virtual offset)
+;   movl    $110, %r8d
+;   movl    $115, %r9d
+;   movl    $120, %r10d
+;   movl    $125, %r11d
+;   movl    $130, %ecx
+;   movq    %rcx, rsp(8 + virtual offset)
+;   movl    $135, %ecx
+;   movq    %rcx, rsp(0 + virtual offset)
+;   subq    %rsp, $128, %rsp
+;   virtual_sp_offset_adjust 128
+;   movq    %rbp, %r14
+;   movq    8(%r14), %r12
+;   movq    %r15, 0(%rsp)
+;   movq    rsp(32 + virtual offset), %rcx
+;   movq    %rcx, 8(%rsp)
+;   movq    %r13, 16(%rsp)
+;   movq    %rdi, 24(%rsp)
+;   movq    %rax, 32(%rsp)
+;   movq    rsp(24 + virtual offset), %rax
+;   movq    %rax, 40(%rsp)
+;   movq    %rdx, 48(%rsp)
+;   movq    %rbx, 56(%rsp)
+;   movq    %rsi, 64(%rsp)
+;   movq    rsp(16 + virtual offset), %rax
+;   movq    %rax, 72(%rsp)
+;   movq    %r8, 80(%rsp)
+;   movq    %r9, 88(%rsp)
+;   movq    %r10, 96(%rsp)
+;   movq    %r11, 104(%rsp)
+;   movq    rsp(8 + virtual offset), %rax
+;   movq    %rax, 112(%rsp)
+;   movq    rsp(0 + virtual offset), %rax
+;   movq    %rax, 120(%rsp)
+;   load_ext_name %tail_callee_stack_args+0, %r15
+;   movq    rsp(40 + virtual offset), %r11
+;   movq    rsp(48 + virtual offset), %r10
+;   movq    rsp(56 + virtual offset), %r9
+;   movq    rsp(64 + virtual offset), %r8
+;   movq    rsp(72 + virtual offset), %rdi
+;   movq    rsp(80 + virtual offset), %rsi
+;   movq    rsp(88 + virtual offset), %rbx
+;   movq    rsp(96 + virtual offset), %rdx
+;   movq    rsp(104 + virtual offset), %rcx
+;   movq    rsp(112 + virtual offset), %rax
+;   return_call_unknown %r15 new_stack_arg_size:128 old_stack_arg_size:0 ret_addr:Some("%v219") fp:%v218 tmp:%v220 %rax=%rax %rcx=%rcx %rdx=%rdx %rbx=%rbx %rsi=%rsi %rdi=%rdi %r8=%r8 %r9=%r9 %r10=%r10 %r11=%r11
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+;   subq $0x80, %rsp
+; block1: ; offset 0xb
+;   movl $0xa, %eax
+;   movq %rax, 0x70(%rsp)
+;   movl $0xf, %ecx
+;   movq %rcx, 0x68(%rsp)
+;   movl $0x14, %edx
+;   movq %rdx, 0x60(%rsp)
+;   movl $0x19, %ebx
+;   movq %rbx, 0x58(%rsp)
+;   movl $0x1e, %esi
+;   movq %rsi, 0x50(%rsp)
+;   movl $0x23, %edi
+;   movq %rdi, 0x48(%rsp)
+;   movl $0x28, %r8d
+;   movq %r8, 0x40(%rsp)
+;   movl $0x2d, %r9d
+;   movq %r9, 0x38(%rsp)
+;   movl $0x32, %r10d
+;   movq %r10, 0x30(%rsp)
+;   movl $0x37, %r11d
+;   movq %r11, 0x28(%rsp)
+;   movl $0x3c, %r15d
+;   movl $0x41, %eax
+;   movq %rax, 0x20(%rsp)
+;   movl $0x46, %r13d
+;   movl $0x4b, %edi
+;   movl $0x50, %eax
+;   movl $0x55, %ecx
+;   movq %rcx, 0x18(%rsp)
+;   movl $0x5a, %edx
+;   movl $0x5f, %ebx
+;   movl $0x64, %esi
+;   movl $0x69, %ecx
+;   movq %rcx, 0x10(%rsp)
+;   movl $0x6e, %r8d
+;   movl $0x73, %r9d
+;   movl $0x78, %r10d
+;   movl $0x7d, %r11d
+;   movl $0x82, %ecx
+;   movq %rcx, 8(%rsp)
+;   movl $0x87, %ecx
+;   movq %rcx, (%rsp)
+;   subq $0x80, %rsp
+;   movq %rbp, %r14
+;   movq 8(%r14), %r12
+;   movq %r15, (%rsp)
+;   movq 0xa0(%rsp), %rcx
+;   movq %rcx, 8(%rsp)
+;   movq %r13, 0x10(%rsp)
+;   movq %rdi, 0x18(%rsp)
+;   movq %rax, 0x20(%rsp)
+;   movq 0x98(%rsp), %rax
+;   movq %rax, 0x28(%rsp)
+;   movq %rdx, 0x30(%rsp)
+;   movq %rbx, 0x38(%rsp)
+;   movq %rsi, 0x40(%rsp)
+;   movq 0x90(%rsp), %rax
+;   movq %rax, 0x48(%rsp)
+;   movq %r8, 0x50(%rsp)
+;   movq %r9, 0x58(%rsp)
+;   movq %r10, 0x60(%rsp)
+;   movq %r11, 0x68(%rsp)
+;   movq 0x88(%rsp), %rax
+;   movq %rax, 0x70(%rsp)
+;   movq 0x80(%rsp), %rax
+;   movq %rax, 0x78(%rsp)
+;   movabsq $0, %r15 ; reloc_external Abs8 %tail_callee_stack_args 0
+;   movq 0xa8(%rsp), %r11
+;   movq 0xb0(%rsp), %r10
+;   movq 0xb8(%rsp), %r9
+;   movq 0xc0(%rsp), %r8
+;   movq 0xc8(%rsp), %rdi
+;   movq 0xd0(%rsp), %rsi
+;   movq 0xd8(%rsp), %rbx
+;   movq 0xe0(%rsp), %rdx
+;   movq 0xe8(%rsp), %rcx
+;   movq 0xf0(%rsp), %rax
+;   movq (%r14), %rbp
+;   movq 0x78(%rsp), %r13
+;   movq %r13, 8(%r14)
+;   movq 0x70(%rsp), %r13
+;   movq %r13, (%r14)
+;   movq 0x68(%rsp), %r13
+;   movq %r13, -8(%r14)
+;   movq 0x60(%rsp), %r13
+;   movq %r13, -0x10(%r14)
+;   movq 0x58(%rsp), %r13
+;   movq %r13, -0x18(%r14)
+;   movq 0x50(%rsp), %r13
+;   movq %r13, -0x20(%r14)
+;   movq 0x48(%rsp), %r13
+;   movq %r13, -0x28(%r14)
+;   movq 0x40(%rsp), %r13
+;   movq %r13, -0x30(%r14)
+;   movq 0x38(%rsp), %r13
+;   movq %r13, -0x38(%r14)
+;   movq 0x30(%rsp), %r13
+;   movq %r13, -0x40(%r14)
+;   movq 0x28(%rsp), %r13
+;   movq %r13, -0x48(%r14)
+;   movq 0x20(%rsp), %r13
+;   movq %r13, -0x50(%r14)
+;   movq 0x18(%rsp), %r13
+;   movq %r13, -0x58(%r14)
+;   movq 0x10(%rsp), %r13
+;   movq %r13, -0x60(%r14)
+;   movq 8(%rsp), %r13
+;   movq %r13, -0x68(%r14)
+;   movq (%rsp), %r13
+;   movq %r13, -0x70(%r14)
+;   leaq -0x78(%r14), %rsp
+;   movq %r12, (%rsp)
+;   jmpq *%r15
+
diff --git a/cranelift/filetests/filetests/isa/x64/tail-call-conv.clif b/cranelift/filetests/filetests/isa/x64/tail-call-conv.clif
index 03c04601f..04012299b 100644
--- a/cranelift/filetests/filetests/isa/x64/tail-call-conv.clif
+++ b/cranelift/filetests/filetests/isa/x64/tail-call-conv.clif
@@ -12,22 +12,28 @@ block0(v0: i64, v1: i64, v2: i64, v3: i64, v4: i64, v5: i64, v6: i64, v7: i64, v
 ;   pushq   %rbp
 ;   movq    %rsp, %rbp
 ; block0:
-;   movq    16(%rbp), %r8
-;   movq    24(%rbp), %rax
+;   movq    16(%rbp), %rax
+;   movq    24(%rbp), %rdx
+;   movq    32(%rbp), %r9
+;   movq    40(%rbp), %r11
+;   movq    48(%rbp), %rax
 ;   movq    %rbp, %rsp
 ;   popq    %rbp
-;   ret 16
+;   ret 48
 ;
 ; Disassembled:
 ; block0: ; offset 0x0
 ;   pushq %rbp
 ;   movq %rsp, %rbp
 ; block1: ; offset 0x4
-;   movq 0x10(%rbp), %r8
-;   movq 0x18(%rbp), %rax
+;   movq 0x10(%rbp), %rax
+;   movq 0x18(%rbp), %rdx
+;   movq 0x20(%rbp), %r9
+;   movq 0x28(%rbp), %r11
+;   movq 0x30(%rbp), %rax
 ;   movq %rbp, %rsp
 ;   popq %rbp
-;   retq $0x10
+;   retq $0x30
 
 function %tail_caller_stack_args() -> i64 {
     fn0 = %tail_callee_stack_args(i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64) -> i64 tail
@@ -78,10 +84,13 @@ block0:
 ;   movl    $70, %r14d
 ;   movl    $75, %r15d
 ;   movl    $80, %eax
-;   subq    %rsp, $16, %rsp
-;   virtual_sp_offset_adjust 16
-;   movq    %r15, 0(%rsp)
-;   movq    %rax, 8(%rsp)
+;   subq    %rsp, $48, %rsp
+;   virtual_sp_offset_adjust 48
+;   movq    %r12, 0(%rsp)
+;   movq    %r13, 8(%rsp)
+;   movq    %r14, 16(%rsp)
+;   movq    %r15, 24(%rsp)
+;   movq    %rax, 32(%rsp)
 ;   load_ext_name %tail_callee_stack_args+0, %r15
 ;   movq    rsp(0 + virtual offset), %rax
 ;   call    *%r15
@@ -122,11 +131,14 @@ block0:
 ;   movl $0x46, %r14d
 ;   movl $0x4b, %r15d
 ;   movl $0x50, %eax
-;   subq $0x10, %rsp
-;   movq %r15, (%rsp)
-;   movq %rax, 8(%rsp)
+;   subq $0x30, %rsp
+;   movq %r12, (%rsp)
+;   movq %r13, 8(%rsp)
+;   movq %r14, 0x10(%rsp)
+;   movq %r15, 0x18(%rsp)
+;   movq %rax, 0x20(%rsp)
 ;   movabsq $0, %r15 ; reloc_external Abs8 %tail_callee_stack_args 0
-;   movq 0x10(%rsp), %rax
+;   movq 0x30(%rsp), %rax
 ;   callq *%r15
 ;   movq 0x10(%rsp), %rbx
 ;   movq 0x18(%rsp), %r12
@@ -406,36 +418,39 @@ block0(v0: i64, v1: i64, v2: i64, v3: i64, v4: i64, v5: i64, v6: i64, v7: i64, v
 ;   movq    %r9, rsp(56 + virtual offset)
 ;   movq    %r10, rsp(64 + virtual offset)
 ;   movq    %r11, rsp(72 + virtual offset)
+;   movq    16(%rbp), %r12
 ;   movq    %r12, rsp(80 + virtual offset)
+;   movq    24(%rbp), %r13
 ;   movq    %r13, rsp(88 + virtual offset)
-;   movq    %r14, rsp(96 + virtual offset)
-;   movq    16(%rbp), %rbx
-;   movq    24(%rbp), %r12
 ;   movq    32(%rbp), %r14
-;   movq    40(%rbp), %rax
-;   movq    48(%rbp), %rdx
-;   movq    56(%rbp), %r9
-;   movq    64(%rbp), %r11
-;   movq    72(%rbp), %rdi
-;   movq    80(%rbp), %rcx
-;   movq    88(%rbp), %r8
-;   movq    96(%rbp), %r10
-;   movq    104(%rbp), %rsi
-;   movq    112(%rbp), %r15
-;   movq    120(%rbp), %r13
-;   movq    %rbx, 0(%r13)
-;   movq    %r12, 8(%r13)
-;   movq    %r14, 16(%r13)
-;   movq    %rax, 24(%r13)
-;   movq    %rdx, 32(%r13)
-;   movq    %r9, 40(%r13)
-;   movq    %r11, 48(%r13)
-;   movq    %rdi, 56(%r13)
-;   movq    %rcx, 64(%r13)
-;   movq    %r8, 72(%r13)
-;   movq    %r10, 80(%r13)
-;   movq    %rsi, 88(%r13)
-;   movq    %r15, 96(%r13)
+;   movq    %r14, rsp(96 + virtual offset)
+;   movq    40(%rbp), %r13
+;   movq    48(%rbp), %r12
+;   movq    56(%rbp), %rbx
+;   movq    64(%rbp), %r8
+;   movq    72(%rbp), %r10
+;   movq    80(%rbp), %rsi
+;   movq    88(%rbp), %rax
+;   movq    96(%rbp), %rdx
+;   movq    104(%rbp), %r9
+;   movq    112(%rbp), %r11
+;   movq    120(%rbp), %rdi
+;   movq    128(%rbp), %rcx
+;   movq    136(%rbp), %r15
+;   movq    144(%rbp), %r14
+;   movq    %r13, 0(%r14)
+;   movq    %r12, 8(%r14)
+;   movq    %rbx, 16(%r14)
+;   movq    %r8, 24(%r14)
+;   movq    %r10, 32(%r14)
+;   movq    %rsi, 40(%r14)
+;   movq    %rax, 48(%r14)
+;   movq    %rdx, 56(%r14)
+;   movq    %r9, 64(%r14)
+;   movq    %r11, 72(%r14)
+;   movq    %rdi, 80(%r14)
+;   movq    %rcx, 88(%r14)
+;   movq    %r15, 96(%r14)
 ;   movq    rsp(0 + virtual offset), %rax
 ;   movq    rsp(8 + virtual offset), %rcx
 ;   movq    rsp(16 + virtual offset), %rdx
@@ -452,7 +467,7 @@ block0(v0: i64, v1: i64, v2: i64, v3: i64, v4: i64, v5: i64, v6: i64, v7: i64, v
 ;   addq    %rsp, $112, %rsp
 ;   movq    %rbp, %rsp
 ;   popq    %rbp
-;   ret 112
+;   ret 144
 ;
 ; Disassembled:
 ; block0: ; offset 0x0
@@ -470,36 +485,39 @@ block0(v0: i64, v1: i64, v2: i64, v3: i64, v4: i64, v5: i64, v6: i64, v7: i64, v
 ;   movq %r9, 0x38(%rsp)
 ;   movq %r10, 0x40(%rsp)
 ;   movq %r11, 0x48(%rsp)
+;   movq 0x10(%rbp), %r12
 ;   movq %r12, 0x50(%rsp)
+;   movq 0x18(%rbp), %r13
 ;   movq %r13, 0x58(%rsp)
-;   movq %r14, 0x60(%rsp)
-;   movq 0x10(%rbp), %rbx
-;   movq 0x18(%rbp), %r12
 ;   movq 0x20(%rbp), %r14
-;   movq 0x28(%rbp), %rax
-;   movq 0x30(%rbp), %rdx
-;   movq 0x38(%rbp), %r9
-;   movq 0x40(%rbp), %r11
-;   movq 0x48(%rbp), %rdi
-;   movq 0x50(%rbp), %rcx
-;   movq 0x58(%rbp), %r8
-;   movq 0x60(%rbp), %r10
-;   movq 0x68(%rbp), %rsi
-;   movq 0x70(%rbp), %r15
-;   movq 0x78(%rbp), %r13
-;   movq %rbx, (%r13)
-;   movq %r12, 8(%r13)
-;   movq %r14, 0x10(%r13)
-;   movq %rax, 0x18(%r13)
-;   movq %rdx, 0x20(%r13)
-;   movq %r9, 0x28(%r13)
-;   movq %r11, 0x30(%r13)
-;   movq %rdi, 0x38(%r13)
-;   movq %rcx, 0x40(%r13)
-;   movq %r8, 0x48(%r13)
-;   movq %r10, 0x50(%r13)
-;   movq %rsi, 0x58(%r13)
-;   movq %r15, 0x60(%r13)
+;   movq %r14, 0x60(%rsp)
+;   movq 0x28(%rbp), %r13
+;   movq 0x30(%rbp), %r12
+;   movq 0x38(%rbp), %rbx
+;   movq 0x40(%rbp), %r8
+;   movq 0x48(%rbp), %r10
+;   movq 0x50(%rbp), %rsi
+;   movq 0x58(%rbp), %rax
+;   movq 0x60(%rbp), %rdx
+;   movq 0x68(%rbp), %r9
+;   movq 0x70(%rbp), %r11
+;   movq 0x78(%rbp), %rdi
+;   movq 0x80(%rbp), %rcx
+;   movq 0x88(%rbp), %r15
+;   movq 0x90(%rbp), %r14
+;   movq %r13, (%r14)
+;   movq %r12, 8(%r14)
+;   movq %rbx, 0x10(%r14)
+;   movq %r8, 0x18(%r14)
+;   movq %r10, 0x20(%r14)
+;   movq %rsi, 0x28(%r14)
+;   movq %rax, 0x30(%r14)
+;   movq %rdx, 0x38(%r14)
+;   movq %r9, 0x40(%r14)
+;   movq %r11, 0x48(%r14)
+;   movq %rdi, 0x50(%r14)
+;   movq %rcx, 0x58(%r14)
+;   movq %r15, 0x60(%r14)
 ;   movq (%rsp), %rax
 ;   movq 8(%rsp), %rcx
 ;   movq 0x10(%rsp), %rdx
@@ -516,7 +534,7 @@ block0(v0: i64, v1: i64, v2: i64, v3: i64, v4: i64, v5: i64, v6: i64, v7: i64, v
 ;   addq $0x70, %rsp
 ;   movq %rbp, %rsp
 ;   popq %rbp
-;   retq $0x70
+;   retq $0x90
 
 function %tail_caller_stack_args_and_rets() -> i64 tail {
     fn0 = %tail_callee_stack_args_and_rets(i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64) -> i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64 tail
@@ -555,34 +573,32 @@ block0:
 ; VCode:
 ;   pushq   %rbp
 ;   movq    %rsp, %rbp
-;   subq    %rsp, $96, %rsp
+;   subq    %rsp, $112, %rsp
 ; block0:
 ;   movl    $10, %eax
-;   movq    %rax, rsp(88 + virtual offset)
+;   movq    %rax, rsp(96 + virtual offset)
 ;   movl    $15, %ecx
-;   movq    %rcx, rsp(80 + virtual offset)
+;   movq    %rcx, rsp(88 + virtual offset)
 ;   movl    $20, %edx
-;   movq    %rdx, rsp(72 + virtual offset)
+;   movq    %rdx, rsp(80 + virtual offset)
 ;   movl    $25, %ebx
-;   movq    %rbx, rsp(64 + virtual offset)
+;   movq    %rbx, rsp(72 + virtual offset)
 ;   movl    $30, %esi
-;   movq    %rsi, rsp(56 + virtual offset)
+;   movq    %rsi, rsp(64 + virtual offset)
 ;   movl    $35, %edi
-;   movq    %rdi, rsp(48 + virtual offset)
+;   movq    %rdi, rsp(56 + virtual offset)
 ;   movl    $40, %r8d
-;   movq    %r8, rsp(40 + virtual offset)
+;   movq    %r8, rsp(48 + virtual offset)
 ;   movl    $45, %r9d
-;   movq    %r9, rsp(32 + virtual offset)
+;   movq    %r9, rsp(40 + virtual offset)
 ;   movl    $50, %r10d
-;   movq    %r10, rsp(24 + virtual offset)
+;   movq    %r10, rsp(32 + virtual offset)
 ;   movl    $55, %r11d
-;   movq    %r11, rsp(16 + virtual offset)
-;   movl    $60, %r12d
-;   movq    %r12, rsp(8 + virtual offset)
-;   movl    $65, %r13d
-;   movq    %r13, rsp(0 + virtual offset)
-;   movl    $70, %r14d
-;   movl    $75, %r15d
+;   movq    %r11, rsp(24 + virtual offset)
+;   movl    $60, %r14d
+;   movl    $65, %r15d
+;   movl    $70, %r12d
+;   movl    $75, %r13d
 ;   movl    $80, %eax
 ;   movl    $85, %ecx
 ;   movl    $90, %edx
@@ -593,55 +609,62 @@ block0:
 ;   movl    $115, %r9d
 ;   movl    $120, %r10d
 ;   movl    $125, %r11d
-;   movl    $130, %r12d
-;   movl    $135, %r13d
-;   subq    %rsp, $224, %rsp
-;   virtual_sp_offset_adjust 224
-;   movq    %r15, 0(%rsp)
-;   movq    %rax, 8(%rsp)
-;   movq    %rcx, 16(%rsp)
-;   movq    %rdx, 24(%rsp)
-;   movq    %rbx, 32(%rsp)
-;   movq    %rsi, 40(%rsp)
-;   movq    %rdi, 48(%rsp)
-;   movq    %r8, 56(%rsp)
-;   movq    %r9, 64(%rsp)
-;   movq    %r10, 72(%rsp)
-;   movq    %r11, 80(%rsp)
-;   movq    %r12, 88(%rsp)
-;   movq    %r13, 96(%rsp)
-;   lea     112(%rsp), %rdx
-;   movq    %rdx, 104(%rsp)
+;   movq    %r11, rsp(16 + virtual offset)
+;   movl    $130, %r11d
+;   movq    %r11, rsp(8 + virtual offset)
+;   movl    $135, %r11d
+;   movq    %r11, rsp(0 + virtual offset)
+;   subq    %rsp, $256, %rsp
+;   virtual_sp_offset_adjust 256
+;   movq    %r14, 0(%rsp)
+;   movq    %r15, 8(%rsp)
+;   movq    %r12, 16(%rsp)
+;   movq    %r13, 24(%rsp)
+;   movq    %rax, 32(%rsp)
+;   movq    %rcx, 40(%rsp)
+;   movq    %rdx, 48(%rsp)
+;   movq    %rbx, 56(%rsp)
+;   movq    %rsi, 64(%rsp)
+;   movq    %rdi, 72(%rsp)
+;   movq    %r8, 80(%rsp)
+;   movq    %r9, 88(%rsp)
+;   movq    %r10, 96(%rsp)
+;   movq    rsp(16 + virtual offset), %rsi
+;   movq    %rsi, 104(%rsp)
+;   movq    rsp(8 + virtual offset), %r11
+;   movq    %r11, 112(%rsp)
+;   movq    rsp(0 + virtual offset), %r11
+;   movq    %r11, 120(%rsp)
+;   lea     144(%rsp), %r10
+;   movq    %r10, 128(%rsp)
 ;   load_ext_name %tail_callee_stack_args_and_rets+0, %r15
-;   movq    rsp(0 + virtual offset), %r13
-;   movq    rsp(8 + virtual offset), %r12
-;   movq    rsp(16 + virtual offset), %r11
-;   movq    rsp(24 + virtual offset), %r10
-;   movq    rsp(32 + virtual offset), %r9
-;   movq    rsp(40 + virtual offset), %r8
-;   movq    rsp(48 + virtual offset), %rdi
-;   movq    rsp(56 + virtual offset), %rsi
-;   movq    rsp(64 + virtual offset), %rbx
-;   movq    rsp(72 + virtual offset), %rdx
-;   movq    rsp(80 + virtual offset), %rcx
-;   movq    rsp(88 + virtual offset), %rax
+;   movq    rsp(24 + virtual offset), %r11
+;   movq    rsp(32 + virtual offset), %r10
+;   movq    rsp(40 + virtual offset), %r9
+;   movq    rsp(48 + virtual offset), %r8
+;   movq    rsp(56 + virtual offset), %rdi
+;   movq    rsp(64 + virtual offset), %rsi
+;   movq    rsp(72 + virtual offset), %rbx
+;   movq    rsp(80 + virtual offset), %rdx
+;   movq    rsp(88 + virtual offset), %rcx
+;   movq    rsp(96 + virtual offset), %rax
 ;   call    *%r15
-;   movq    0(%rsp), %rdx
-;   movq    8(%rsp), %r9
-;   movq    16(%rsp), %r11
-;   movq    24(%rsp), %rdi
-;   movq    32(%rsp), %rcx
-;   movq    40(%rsp), %r8
-;   movq    48(%rsp), %r10
-;   movq    56(%rsp), %rsi
-;   movq    64(%rsp), %rax
-;   movq    72(%rsp), %rdx
-;   movq    80(%rsp), %r9
-;   movq    88(%rsp), %r11
+;   movq    0(%rsp), %r10
+;   movq    8(%rsp), %rsi
+;   movq    16(%rsp), %rax
+;   movq    24(%rsp), %rdx
+;   movq    32(%rsp), %r9
+;   movq    40(%rsp), %r11
+;   movq    48(%rsp), %rdi
+;   movq    56(%rsp), %rcx
+;   movq    64(%rsp), %r8
+;   movq    72(%rsp), %r10
+;   movq    80(%rsp), %rsi
+;   movq    88(%rsp), %rax
 ;   movq    96(%rsp), %rax
 ;   addq    %rsp, $112, %rsp
 ;   virtual_sp_offset_adjust -112
-;   addq    %rsp, $96, %rsp
+;   addq    %rsp, $112, %rsp
 ;   movq    %rbp, %rsp
 ;   popq    %rbp
 ;   ret
@@ -650,34 +673,32 @@ block0:
 ; block0: ; offset 0x0
 ;   pushq %rbp
 ;   movq %rsp, %rbp
-;   subq $0x60, %rsp
+;   subq $0x70, %rsp
 ; block1: ; offset 0x8
 ;   movl $0xa, %eax
-;   movq %rax, 0x58(%rsp)
+;   movq %rax, 0x60(%rsp)
 ;   movl $0xf, %ecx
-;   movq %rcx, 0x50(%rsp)
+;   movq %rcx, 0x58(%rsp)
 ;   movl $0x14, %edx
-;   movq %rdx, 0x48(%rsp)
+;   movq %rdx, 0x50(%rsp)
 ;   movl $0x19, %ebx
-;   movq %rbx, 0x40(%rsp)
+;   movq %rbx, 0x48(%rsp)
 ;   movl $0x1e, %esi
-;   movq %rsi, 0x38(%rsp)
+;   movq %rsi, 0x40(%rsp)
 ;   movl $0x23, %edi
-;   movq %rdi, 0x30(%rsp)
+;   movq %rdi, 0x38(%rsp)
 ;   movl $0x28, %r8d
-;   movq %r8, 0x28(%rsp)
+;   movq %r8, 0x30(%rsp)
 ;   movl $0x2d, %r9d
-;   movq %r9, 0x20(%rsp)
+;   movq %r9, 0x28(%rsp)
 ;   movl $0x32, %r10d
-;   movq %r10, 0x18(%rsp)
+;   movq %r10, 0x20(%rsp)
 ;   movl $0x37, %r11d
-;   movq %r11, 0x10(%rsp)
-;   movl $0x3c, %r12d
-;   movq %r12, 8(%rsp)
-;   movl $0x41, %r13d
-;   movq %r13, (%rsp)
-;   movl $0x46, %r14d
-;   movl $0x4b, %r15d
+;   movq %r11, 0x18(%rsp)
+;   movl $0x3c, %r14d
+;   movl $0x41, %r15d
+;   movl $0x46, %r12d
+;   movl $0x4b, %r13d
 ;   movl $0x50, %eax
 ;   movl $0x55, %ecx
 ;   movl $0x5a, %edx
@@ -688,53 +709,60 @@ block0:
 ;   movl $0x73, %r9d
 ;   movl $0x78, %r10d
 ;   movl $0x7d, %r11d
-;   movl $0x82, %r12d
-;   movl $0x87, %r13d
-;   subq $0xe0, %rsp
-;   movq %r15, (%rsp)
-;   movq %rax, 8(%rsp)
-;   movq %rcx, 0x10(%rsp)
-;   movq %rdx, 0x18(%rsp)
-;   movq %rbx, 0x20(%rsp)
-;   movq %rsi, 0x28(%rsp)
-;   movq %rdi, 0x30(%rsp)
-;   movq %r8, 0x38(%rsp)
-;   movq %r9, 0x40(%rsp)
-;   movq %r10, 0x48(%rsp)
-;   movq %r11, 0x50(%rsp)
-;   movq %r12, 0x58(%rsp)
-;   movq %r13, 0x60(%rsp)
-;   leaq 0x70(%rsp), %rdx
-;   movq %rdx, 0x68(%rsp)
+;   movq %r11, 0x10(%rsp)
+;   movl $0x82, %r11d
+;   movq %r11, 8(%rsp)
+;   movl $0x87, %r11d
+;   movq %r11, (%rsp)
+;   subq $0x100, %rsp
+;   movq %r14, (%rsp)
+;   movq %r15, 8(%rsp)
+;   movq %r12, 0x10(%rsp)
+;   movq %r13, 0x18(%rsp)
+;   movq %rax, 0x20(%rsp)
+;   movq %rcx, 0x28(%rsp)
+;   movq %rdx, 0x30(%rsp)
+;   movq %rbx, 0x38(%rsp)
+;   movq %rsi, 0x40(%rsp)
+;   movq %rdi, 0x48(%rsp)
+;   movq %r8, 0x50(%rsp)
+;   movq %r9, 0x58(%rsp)
+;   movq %r10, 0x60(%rsp)
+;   movq 0x110(%rsp), %rsi
+;   movq %rsi, 0x68(%rsp)
+;   movq 0x108(%rsp), %r11
+;   movq %r11, 0x70(%rsp)
+;   movq 0x100(%rsp), %r11
+;   movq %r11, 0x78(%rsp)
+;   leaq 0x90(%rsp), %r10
+;   movq %r10, 0x80(%rsp)
 ;   movabsq $0, %r15 ; reloc_external Abs8 %tail_callee_stack_args_and_rets 0
-;   movq 0xe0(%rsp), %r13
-;   movq 0xe8(%rsp), %r12
-;   movq 0xf0(%rsp), %r11
-;   movq 0xf8(%rsp), %r10
-;   movq 0x100(%rsp), %r9
-;   movq 0x108(%rsp), %r8
-;   movq 0x110(%rsp), %rdi
-;   movq 0x118(%rsp), %rsi
-;   movq 0x120(%rsp), %rbx
-;   movq 0x128(%rsp), %rdx
-;   movq 0x130(%rsp), %rcx
-;   movq 0x138(%rsp), %rax
+;   movq 0x118(%rsp), %r11
+;   movq 0x120(%rsp), %r10
+;   movq 0x128(%rsp), %r9
+;   movq 0x130(%rsp), %r8
+;   movq 0x138(%rsp), %rdi
+;   movq 0x140(%rsp), %rsi
+;   movq 0x148(%rsp), %rbx
+;   movq 0x150(%rsp), %rdx
+;   movq 0x158(%rsp), %rcx
+;   movq 0x160(%rsp), %rax
 ;   callq *%r15
-;   movq (%rsp), %rdx
-;   movq 8(%rsp), %r9
-;   movq 0x10(%rsp), %r11
-;   movq 0x18(%rsp), %rdi
-;   movq 0x20(%rsp), %rcx
-;   movq 0x28(%rsp), %r8
-;   movq 0x30(%rsp), %r10
-;   movq 0x38(%rsp), %rsi
-;   movq 0x40(%rsp), %rax
-;   movq 0x48(%rsp), %rdx
-;   movq 0x50(%rsp), %r9
-;   movq 0x58(%rsp), %r11
+;   movq (%rsp), %r10
+;   movq 8(%rsp), %rsi
+;   movq 0x10(%rsp), %rax
+;   movq 0x18(%rsp), %rdx
+;   movq 0x20(%rsp), %r9
+;   movq 0x28(%rsp), %r11
+;   movq 0x30(%rsp), %rdi
+;   movq 0x38(%rsp), %rcx
+;   movq 0x40(%rsp), %r8
+;   movq 0x48(%rsp), %r10
+;   movq 0x50(%rsp), %rsi
+;   movq 0x58(%rsp), %rax
 ;   movq 0x60(%rsp), %rax
 ;   addq $0x70, %rsp
-;   addq $0x60, %rsp
+;   addq $0x70, %rsp
 ;   movq %rbp, %rsp
 ;   popq %rbp
 ;   retq
diff --git a/cranelift/filetests/filetests/runtests/issue-6635.clif b/cranelift/filetests/filetests/runtests/issue-6635.clif
new file mode 100644
index 000000000..cc930f8e9
--- /dev/null
+++ b/cranelift/filetests/filetests/runtests/issue-6635.clif
@@ -0,0 +1,18 @@
+test run
+set preserve_frame_pointers=true
+target x86_64
+
+function %b(i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8) -> i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8 tail {
+block0(v0: i8, v1: i8, v2: i8, v3: i8, v4: i8, v5: i8, v6: i8, v7: i8, v8: i8, v9: i8, v10: i8, v11: i8, v12: i8, v13: i8, v14: i8, v15: i8):
+    return v0, v0, v0, v0, v0, v0, v0, v0, v0, v0, v9, v9, v9, v9, v9, v9
+}
+
+
+function %a(i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8) -> i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8 tail {
+    sig0 = (i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8) -> i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8 tail
+    fn0 = %b sig0
+block0(v0: i8, v1: i8, v2: i8, v3: i8, v4: i8, v5: i8, v6: i8, v7: i8, v8: i8, v9: i8, v10: i8, v11: i8, v12: i8, v13: i8, v14: i8, v15: i8):
+    return_call fn0(v0, v0, v0, v0, v0, v0, v0, v0, v0, v0, v0, v0, v0, v0, v0, v0)
+}
+
+; run: %a(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) == [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
diff --git a/cranelift/filetests/filetests/runtests/issue-6640.clif b/cranelift/filetests/filetests/runtests/issue-6640.clif
new file mode 100644
index 000000000..2d0936e36
--- /dev/null
+++ b/cranelift/filetests/filetests/runtests/issue-6640.clif
@@ -0,0 +1,18 @@
+test interpret
+test run
+set enable_llvm_abi_extensions=true
+set probestack_size_log2=6
+set probestack_strategy=inline
+set enable_probestack=true
+target x86_64
+target aarch64
+target riscv64gc has_v
+
+function %a(i8, i8, i8, i8, i8, i8, i8, i8, i128) -> i128 tail {
+    ss0 = explicit_slot 321
+
+block0(v0: i8, v1: i8, v2: i8, v3: i8, v4: i8, v5: i8, v6: i8, v7: i8, v8: i128):
+    return v8
+}
+
+; run: %a(0, 1, 2, 3, 4, 5, 6, 7, 8) == 8
diff --git a/cranelift/filetests/filetests/runtests/return-call-indirect.clif b/cranelift/filetests/filetests/runtests/return-call-indirect.clif
index 4277aaa28..698ead772 100644
--- a/cranelift/filetests/filetests/runtests/return-call-indirect.clif
+++ b/cranelift/filetests/filetests/runtests/return-call-indirect.clif
@@ -1,6 +1,9 @@
 test interpret
-;; test run
-;; target x86_64
+test run
+
+set preserve_frame_pointers=true
+
+target x86_64
 ;; target aarch64
 ;; target aarch64 sign_return_address
 ;; target aarch64 has_pauth sign_return_address
diff --git a/cranelift/filetests/filetests/runtests/return-call-loop.clif b/cranelift/filetests/filetests/runtests/return-call-loop.clif
new file mode 100644
index 000000000..7778b61fa
--- /dev/null
+++ b/cranelift/filetests/filetests/runtests/return-call-loop.clif
@@ -0,0 +1,142 @@
+test run
+set preserve_frame_pointers=true
+target x86_64
+;; target aarch64
+;; target aarch64 sign_return_address
+;; target aarch64 has_pauth sign_return_address
+;; target s390x
+
+;;;; Tail-Recursive Loop ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+function %loop(i64, i64) -> i8 tail {
+    fn0 = colocated %loop(i64, i64) -> i8 tail
+
+block0(v0: i64, v1: i64):
+    brif v0, block2, block1
+
+;; Loop counter reached zero, we're done iterating. Return a comparison of the
+;; current SP and the first loop iteration's SP.
+block1:
+    v2 = get_stack_pointer.i64
+    v3 = icmp eq v1, v2
+    return v3
+
+;; Another iteration of the loop. Decrement the loop counter and, if this was
+;; our first iteration, grab the initial SP.
+block2:
+    v4 = iadd_imm v0, -1
+    brif v1, block4(v1), block3
+
+;; Grab the initial SP.
+block3:
+    v5 = get_stack_pointer.i64
+    jump block4(v5)
+
+;; Continue the loop.
+block4(v6: i64):
+    return_call fn0(v4, v6)
+}
+
+; run: %loop(1_000_000, 0) == 1
+
+;;;; Tail-Recursive Loop With Stack Arguments ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+function %loop_stack_args(i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64) -> i8 tail {
+    fn0 = %loop_stack_args(i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64) -> i8 tail
+
+block0(v0: i64, v1: i64, v2: i64, v3: i64, v4: i64, v5: i64, v6: i64, v7: i64, v8: i64, v9: i64, v10: i64, v11: i64, v12: i64, v13: i64, v14: i64, v15: i64, v16: i64, v17: i64, v18: i64, v19: i64, v20: i64, v21: i64, v22: i64, v23: i64, v24: i64, v25: i64):
+    brif v0, block2, block1
+
+;; Loop counter reached zero, we're done iterating. Return a comparison of the
+;; current SP and the first loop iteration's SP.
+block1:
+    v26 = get_stack_pointer.i64
+    v27 = icmp eq v25, v26
+    return v27
+
+;; Another iteration of the loop. Decrement the loop counter and, if this was
+;; our first iteration, grab the initial SP.
+block2:
+    v28 = iadd_imm v0, -1
+    brif v25, block4(v25), block3
+
+;; Grab the initial SP.
+block3:
+    v29 = get_stack_pointer.i64
+    jump block4(v29)
+
+;; Continue the loop.
+block4(v30: i64):
+    return_call fn0(v28, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v30)
+}
+
+; run: %loop_stack_args(1_000_000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) == 1
+
+;;;; Mutually-Recursive Loop With Different #s of Stack Arguments ;;;;;;;;;;;;;;
+
+function %mutual_loop_1(i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64) -> i8 tail {
+    fn0 = %mutual_loop_2(i64, i64) -> i8 tail
+
+block0(v0: i64, v1: i64, v2: i64, v3: i64, v4: i64, v5: i64, v6: i64, v7: i64, v8: i64, v9: i64, v10: i64, v11: i64, v12: i64, v13: i64, v14: i64, v15: i64, v16: i64, v17: i64, v18: i64, v19: i64, v20: i64, v21: i64, v22: i64, v23: i64, v24: i64, v25: i64):
+    brif v0, block2, block1
+
+;; Loop counter reached zero, we're done iterating. Return a comparison of the
+;; current SP and the first loop iteration's SP.
+block1:
+    v26 = get_stack_pointer.i64
+    v27 = icmp eq v1, v26
+    return v27
+
+;; Another iteration of the loop. If this was our first iteration, grab the
+;; initial SP.
+block2:
+    brif v1, block4(v1), block3
+
+;; Grab the initial SP.
+block3:
+    v29 = get_stack_pointer.i64
+    jump block4(v29)
+
+;; Continue the loop.
+block4(v30: i64):
+    return_call fn0(v0, v30)
+}
+
+function %mutual_loop_2(i64, i64) -> i8 tail {
+    fn0 = %mutual_loop_1(i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64) -> i8 tail
+
+block0(v0: i64, v1: i64):
+    v2 = iadd_imm v0, -1
+    v3 = iconst.i64 42
+    return_call fn0(v2, v1, v3, v3, v3, v3, v3, v3, v3, v3, v3, v3, v3, v3, v3, v3, v3, v3, v3, v3, v3, v3, v3, v3, v3, v3)
+}
+
+; run: %mutual_loop_2(1_000_000, 0) == 1
+
+;;;; Indirect Return-Call Loop ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+function %indirect_main(i64) -> i8 tail {
+    sig0 = (i64) -> i8 tail
+    fn0 = %indirect_break(i64) -> i8 tail
+    fn1 = %indirect_continue(i64) -> i8 tail
+block0(v0: i64):
+    v1 = func_addr.i64 fn0
+    v2 = func_addr.i64 fn1
+    v3 = select v0, v2, v1
+    return_call_indirect sig0, v3(v0)
+}
+
+function %indirect_break(i64) -> i8 tail {
+block0(v0: i64):
+    v1 = iconst.i8 42
+    return v1
+}
+
+function %indirect_continue(i64) -> i8 tail {
+    fn0 = %indirect_main(i64) -> i8 tail
+block0(v0: i64):
+    v1 = iadd_imm v0, -1
+    return_call fn0(v1)
+}
+
+; run: %indirect_main(1_000_000) == 42
diff --git a/cranelift/filetests/filetests/runtests/return-call.clif b/cranelift/filetests/filetests/runtests/return-call.clif
index 4f317a41e..3ecea5a92 100644
--- a/cranelift/filetests/filetests/runtests/return-call.clif
+++ b/cranelift/filetests/filetests/runtests/return-call.clif
@@ -1,6 +1,9 @@
 test interpret
-;; test run
-;; target x86_64
+test run
+
+set preserve_frame_pointers=true
+
+target x86_64
 ;; target aarch64
 ;; target aarch64 sign_return_address
 ;; target aarch64 has_pauth sign_return_address
@@ -66,3 +69,101 @@ block0(v0: i8):
 }
 ; run: %call_i8(1) == 0
 ; run: %call_i8(0) == 1
+
+;;;; Test passing many arguments on stack ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+function %tail_callee_stack_args(i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64) -> i64 tail {
+block0(v0: i64, v1: i64, v2: i64, v3: i64, v4: i64, v5: i64, v6: i64, v7: i64, v8: i64, v9: i64, v10: i64, v11: i64, v12: i64, v13: i64, v14: i64, v15: i64, v16: i64, v17: i64, v18: i64, v19: i64, v20: i64, v21: i64, v22: i64, v23: i64, v24: i64, v25: i64):
+    return v25
+}
+
+function %tail_caller_stack_args() -> i64 tail {
+    fn0 = %tail_callee_stack_args(i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64) -> i64 tail
+
+block0:
+    v0 = iconst.i64 10
+    v1 = iconst.i64 15
+    v2 = iconst.i64 20
+    v3 = iconst.i64 25
+    v4 = iconst.i64 30
+    v5 = iconst.i64 35
+    v6 = iconst.i64 40
+    v7 = iconst.i64 45
+    v8 = iconst.i64 50
+    v9 = iconst.i64 55
+    v10 = iconst.i64 60
+    v11 = iconst.i64 65
+    v12 = iconst.i64 70
+    v13 = iconst.i64 75
+    v14 = iconst.i64 80
+    v15 = iconst.i64 85
+    v16 = iconst.i64 90
+    v17 = iconst.i64 95
+    v18 = iconst.i64 100
+    v19 = iconst.i64 105
+    v20 = iconst.i64 110
+    v21 = iconst.i64 115
+    v22 = iconst.i64 120
+    v23 = iconst.i64 125
+    v24 = iconst.i64 130
+    v25 = iconst.i64 135
+    return_call fn0(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25)
+}
+
+; run: %tail_caller_stack_args() == 135
+
+;;;; Test diff blocks with diff return calls with diff # of stack args ;;;;;;;;;
+
+function %different_callee1(i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64) -> i64 tail {
+block0(v0: i64, v1: i64, v2: i64, v3: i64, v4: i64, v5: i64, v6: i64, v7: i64, v8: i64, v9: i64, v10: i64, v11: i64, v12: i64, v13: i64, v14: i64, v15: i64, v16: i64, v17: i64, v18: i64, v19: i64, v20: i64, v21: i64, v22: i64, v23: i64, v24: i64, v25: i64):
+    return v25
+}
+
+function %different_callee2(i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64) -> i64 tail {
+block0(v0: i64, v1: i64, v2: i64, v3: i64, v4: i64, v5: i64, v6: i64, v7: i64, v8: i64, v9: i64, v10: i64, v11: i64, v12: i64, v13: i64, v14: i64, v15: i64, v16: i64, v17: i64, v18: i64, v19: i64, v20: i64, v21: i64, v22: i64, v23: i64, v24: i64, v25: i64, v26: i64):
+    return v26
+}
+
+function %caller_of_different_callees(i64) -> i64 tail {
+    fn0 = %different_callee1(i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64) -> i64 tail
+    fn1 = %different_callee2(i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64) -> i64 tail
+
+block0(v99: i64):
+    v0 = iconst.i64 10
+    v1 = iconst.i64 15
+    v2 = iconst.i64 20
+    v3 = iconst.i64 25
+    v4 = iconst.i64 30
+    v5 = iconst.i64 35
+    v6 = iconst.i64 40
+    v7 = iconst.i64 45
+    v8 = iconst.i64 50
+    v9 = iconst.i64 55
+    v10 = iconst.i64 60
+    v11 = iconst.i64 65
+    v12 = iconst.i64 70
+    v13 = iconst.i64 75
+    v14 = iconst.i64 80
+    v15 = iconst.i64 85
+    v16 = iconst.i64 90
+    v17 = iconst.i64 95
+    v18 = iconst.i64 100
+    v19 = iconst.i64 105
+    v20 = iconst.i64 110
+    v21 = iconst.i64 115
+    v22 = iconst.i64 120
+    v23 = iconst.i64 125
+    v24 = iconst.i64 130
+    v25 = iconst.i64 135
+    brif v99, block1, block2
+
+block1:
+    return_call fn0(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25)
+
+block2:
+    v26 = iconst.i64 140
+    return_call fn1(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26)
+}
+
+; run: %caller_of_different_callees(1) == 135
+; run: %caller_of_different_callees(0) == 140
diff --git a/cranelift/filetests/filetests/runtests/tail-call-conv.clif b/cranelift/filetests/filetests/runtests/tail-call-conv.clif
index efa9431d6..84965b3a3 100644
--- a/cranelift/filetests/filetests/runtests/tail-call-conv.clif
+++ b/cranelift/filetests/filetests/runtests/tail-call-conv.clif
@@ -124,45 +124,3 @@ block0:
 }
 
 ; run: %tail_caller_stack_rets() == 135
-
-;; Test the `tail` calling convention with non-tail calls and both stack
-;; arguments and stack returns.
-
-function %tail_callee_stack_args_and_rets(i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64) -> i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64 tail {
-block0(v0: i64, v1: i64, v2: i64, v3: i64, v4: i64, v5: i64, v6: i64, v7: i64, v8: i64, v9: i64, v10: i64, v11: i64, v12: i64, v13: i64, v14: i64, v15: i64, v16: i64, v17: i64, v18: i64, v19: i64, v20: i64, v21: i64, v22: i64, v23: i64, v24: i64, v25: i64):
-    return v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25
-}
-
-function %tail_caller_stack_args_and_rets() -> i64 tail {
-    fn0 = %tail_callee_stack_args_and_rets(i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64) -> i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64 tail
-
-block0:
-    v0 = iconst.i64 10
-    v1 = iconst.i64 15
-    v2 = iconst.i64 20
-    v3 = iconst.i64 25
-    v4 = iconst.i64 30
-    v5 = iconst.i64 35
-    v6 = iconst.i64 40
-    v7 = iconst.i64 45
-    v8 = iconst.i64 50
-    v9 = iconst.i64 55
-    v10 = iconst.i64 60
-    v11 = iconst.i64 65
-    v12 = iconst.i64 70
-    v13 = iconst.i64 75
-    v14 = iconst.i64 80
-    v15 = iconst.i64 85
-    v16 = iconst.i64 90
-    v17 = iconst.i64 95
-    v18 = iconst.i64 100
-    v19 = iconst.i64 105
-    v20 = iconst.i64 110
-    v21 = iconst.i64 115
-    v22 = iconst.i64 120
-    v23 = iconst.i64 125
-    v24 = iconst.i64 130
-    v25 = iconst.i64 135
-    v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51 = call fn0(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25)
-    return v51
-}
diff --git a/crates/component-macro/tests/codegen.rs b/crates/component-macro/tests/codegen.rs
index 616f3253c..216d03e52 100644
--- a/crates/component-macro/tests/codegen.rs
+++ b/crates/component-macro/tests/codegen.rs
@@ -14,7 +14,9 @@ macro_rules! gentest {
                 wasmtime::component::bindgen!({
                     path: $path,
                     tracing: true,
-                    duplicate_if_necessary: true,
+                    ownership: Borrowing {
+                        duplicate_if_necessary: true
+                    }
                 });
             }
         }
diff --git a/crates/test-programs/tests/command.rs b/crates/test-programs/tests/command.rs
index 3f03dc3cd..f16f14405 100644
--- a/crates/test-programs/tests/command.rs
+++ b/crates/test-programs/tests/command.rs
@@ -13,7 +13,7 @@ use wasmtime_wasi::preview2::{
     pipe::ReadPipe,
     wasi::command::add_to_linker,
     wasi::command::Command,
-    DirPerms, FilePerms, Table, WasiCtx, WasiCtxBuilder, WasiView,
+    DirPerms, FilePerms, Table, WasiClocks, WasiCtx, WasiCtxBuilder, WasiView,
 };
 
 lazy_static::lazy_static! {
@@ -159,9 +159,12 @@ async fn time() -> Result<()> {
     }
 
     let mut table = Table::new();
-    let mut wasi = WasiCtxBuilder::new().build(&mut table)?;
-    wasi.clocks.wall = Box::new(FakeWallClock);
-    wasi.clocks.monotonic = Box::new(FakeMonotonicClock { now: Mutex::new(0) });
+    let wasi = WasiCtxBuilder::new()
+        .set_clocks(WasiClocks {
+            wall: Box::new(FakeWallClock),
+            monotonic: Box::new(FakeMonotonicClock { now: Mutex::new(0) }),
+        })
+        .build(&mut table)?;
 
     let (mut store, command) =
         instantiate(get_component("time"), CommandCtx { table, wasi }).await?;
diff --git a/crates/test-programs/tests/reactor.rs b/crates/test-programs/tests/reactor.rs
index 9cd6ececb..7091aafc0 100644
--- a/crates/test-programs/tests/reactor.rs
+++ b/crates/test-programs/tests/reactor.rs
@@ -37,6 +37,9 @@ wasmtime::component::bindgen!({
        "wasi:cli-base/stdout": preview2::wasi::cli_base::stdout,
        "wasi:cli-base/stderr": preview2::wasi::cli_base::stderr,
     },
+    ownership: Borrowing {
+        duplicate_if_necessary: false
+    }
 });
 
 struct ReactorCtx {
@@ -85,38 +88,22 @@ async fn instantiate(
 #[test_log::test(tokio::test)]
 async fn reactor_tests() -> Result<()> {
     let mut table = Table::new();
-    let wasi = WasiCtxBuilder::new().build(&mut table)?;
+    let wasi = WasiCtxBuilder::new()
+        .push_env("GOOD_DOG", "gussie")
+        .build(&mut table)?;
 
     let (mut store, reactor) =
         instantiate(get_component("reactor_tests"), ReactorCtx { table, wasi }).await?;
 
-    store
-        .data_mut()
-        .wasi
-        .env
-        .push(("GOOD_DOG".to_owned(), "gussie".to_owned()));
-
+    // Show that integration with the WASI context is working - the guest will
+    // interpolate $GOOD_DOG to gussie here using the environment:
     let r = reactor
         .call_add_strings(&mut store, &["hello", "$GOOD_DOG"])
         .await?;
     assert_eq!(r, 2);
 
-    // Redefine the env, show that the adapter only fetches it once
-    // even if the libc ctors copy it in multiple times:
-    store.data_mut().wasi.env.clear();
-    store
-        .data_mut()
-        .wasi
-        .env
-        .push(("GOOD_DOG".to_owned(), "cody".to_owned()));
-    // Cody is indeed good but this should be "hello again" "gussie"
-    let r = reactor
-        .call_add_strings(&mut store, &["hello again", "$GOOD_DOG"])
-        .await?;
-    assert_eq!(r, 4);
-
     let contents = reactor.call_get_strings(&mut store).await?;
-    assert_eq!(contents, &["hello", "gussie", "hello again", "gussie"]);
+    assert_eq!(contents, &["hello", "gussie"]);
 
     // Show that we can pass in a resource type whose impls are defined in the
     // `host` and `wasi-common` crate.
@@ -129,7 +116,7 @@ async fn reactor_tests() -> Result<()> {
     let r = reactor.call_write_strings_to(&mut store, table_ix).await?;
     assert_eq!(r, Ok(()));
 
-    assert_eq!(*write_dest.read().unwrap(), b"hellogussiehello againgussie");
+    assert_eq!(*write_dest.read().unwrap(), b"hellogussie");
 
     // Show that the `with` invocation in the macro means we get to re-use the
     // type definitions from inside the `host` crate for these structures:
diff --git a/tests/all/component_model/bindgen.rs b/tests/all/component_model/bindgen.rs
index c377f08a6..5d3d00135 100644
--- a/tests/all/component_model/bindgen.rs
+++ b/tests/all/component_model/bindgen.rs
@@ -7,6 +7,7 @@ use wasmtime::{
     Store,
 };
 
+mod ownership;
 mod results;
 
 mod no_imports {
diff --git a/tests/all/component_model/bindgen/ownership.rs b/tests/all/component_model/bindgen/ownership.rs
new file mode 100644
index 000000000..fd0dd89da
--- /dev/null
+++ b/tests/all/component_model/bindgen/ownership.rs
@@ -0,0 +1,329 @@
+use super::{super::REALLOC_AND_FREE, engine};
+use anyhow::Result;
+use wasmtime::{
+    component::{Component, Linker},
+    Store,
+};
+
+fn component() -> String {
+    format!(
+        r#"
+        (component
+            (core module $libc
+                (memory (export "memory") 1)
+                {REALLOC_AND_FREE}
+            )
+            (core instance $libc (instantiate $libc))
+            (core module $m
+                (import "libc" "memory" (memory 0))
+                (import "libc" "realloc" (func $realloc (param i32 i32 i32 i32) (result i32)))
+                (func (export "core_foo_export") (param i32 i32) (result i32)
+                    (local $retptr i32)
+                    (local.set $retptr
+                        (call $realloc
+                            (i32.const 0)
+                            (i32.const 0)
+                            (i32.const 4)
+                            (i32.const 8)))
+                    (i32.store offset=0 (local.get $retptr) (local.get 0))
+                    (i32.store offset=4 (local.get $retptr) (local.get 1))
+                    (local.get $retptr)
+                )
+                (func (export "core_bar_export") (param i32 i32 i32 i32))
+                (func (export "core_baz_export") (param i32 i32 i32 i32) (result i32)
+                    (local $retptr i32)
+                    (local.set $retptr
+                        (call $realloc
+                            (i32.const 0)
+                            (i32.const 0)
+                            (i32.const 4)
+                            (i32.const 16)))
+                    (i32.store offset=0 (local.get $retptr) (local.get 0))
+                    (i32.store offset=4 (local.get $retptr) (local.get 1))
+                    (i32.store offset=8 (local.get $retptr) (local.get 2))
+                    (i32.store offset=12 (local.get $retptr) (local.get 3))
+                    (local.get $retptr)
+                )
+            )
+            (core instance $i (instantiate $m
+                (with "libc" (instance $libc))
+            ))
+
+            (func $f_foo
+                (param "a" (list (list string)))
+                (result (list (list string)))
+                (canon lift (core func $i "core_foo_export") (memory $libc "memory")
+                    (realloc (func $libc "realloc"))
+                )
+            )
+
+            (type $thing (record (field "name" string) (field "value" (list string))))
+
+            (func $f_bar
+                (param "a" $thing)
+                (canon lift (core func $i "core_bar_export") (memory $libc "memory")
+                    (realloc (func $libc "realloc"))
+                )
+            )
+
+            (func $f_baz
+                (param "a" $thing)
+                (result $thing)
+                (canon lift (core func $i "core_baz_export") (memory $libc "memory")
+                    (realloc (func $libc "realloc"))
+                )
+            )
+
+            (component $c_lists
+                (import "import-foo" (func $f
+                    (param "a" (list (list string)))
+                    (result (list (list string)))
+                ))
+                (export "foo" (func $f))
+            )
+            (instance $i_lists (instantiate $c_lists
+                (with "import-foo" (func $f_foo))
+            ))
+            (export "lists" (instance $i_lists))
+
+            (component $c_thing_in
+                (import "import-thing" (type $import-thing (eq $thing)))
+                (import "import-bar" (func $f (param "a" $import-thing)))
+                (export $export-thing "thing" (type $thing))
+                (export "bar" (func $f) (func (param "a" $export-thing)))
+            )
+            (instance $i_thing_in (instantiate $c_thing_in
+                (with "import-thing" (type $thing))
+                (with "import-bar" (func $f_bar))
+            ))
+            (export "thing-in" (instance $i_thing_in))
+
+            (component $c_thing_in_and_out
+                (import "import-thing" (type $import-thing (eq $thing)))
+                (import "import-baz" (func $f (param "a" $import-thing) (result $import-thing)))
+                (export $export-thing "thing" (type $thing))
+                (export "baz" (func $f) (func (param "a" $export-thing) (result $export-thing)))
+            )
+            (instance $i_thing_in_and_out (instantiate $c_thing_in_and_out
+                (with "import-thing" (type $thing))
+                (with "import-baz" (func $f_baz))
+            ))
+            (export "thing-in-and-out" (instance $i_thing_in_and_out))
+        )
+        "#
+    )
+}
+
+#[test]
+fn owning() -> Result<()> {
+    wasmtime::component::bindgen!({
+        inline: "
+        package inline:inline
+        world test {
+            export lists: interface {
+                foo: func(a: list<list<string>>) -> list<list<string>>
+            }
+
+            export thing-in: interface {
+                record thing {
+                    name: string,
+                    value: list<string>
+                }
+
+                bar: func(a: thing)
+            }
+
+            export thing-in-and-out: interface {
+                record thing {
+                    name: string,
+                    value: list<string>
+                }
+
+                baz: func(a: thing) -> thing
+            }
+        }",
+        ownership: Owning
+    });
+
+    impl PartialEq for exports::thing_in::Thing {
+        fn eq(&self, other: &Self) -> bool {
+            self.name == other.name && self.value == other.value
+        }
+    }
+
+    impl PartialEq for exports::thing_in_and_out::Thing {
+        fn eq(&self, other: &Self) -> bool {
+            self.name == other.name && self.value == other.value
+        }
+    }
+
+    let engine = engine();
+    let component = Component::new(&engine, component())?;
+
+    let linker = Linker::new(&engine);
+    let mut store = Store::new(&engine, ());
+    let (test, _) = Test::instantiate(&mut store, &component, &linker)?;
+
+    let value = vec![vec!["a".to_owned(), "b".to_owned()]];
+    assert_eq!(value, test.lists().call_foo(&mut store, &value)?);
+
+    let value = exports::thing_in::Thing {
+        name: "thing 1".to_owned(),
+        value: vec!["some value".to_owned(), "another value".to_owned()],
+    };
+    test.thing_in().call_bar(&mut store, &value)?;
+
+    let value = exports::thing_in_and_out::Thing {
+        name: "thing 1".to_owned(),
+        value: vec!["some value".to_owned(), "another value".to_owned()],
+    };
+    assert_eq!(value, test.thing_in_and_out().call_baz(&mut store, &value)?);
+
+    Ok(())
+}
+
+#[test]
+fn borrowing_no_duplication() -> Result<()> {
+    wasmtime::component::bindgen!({
+        inline: "
+        package inline:inline
+        world test {
+            export lists: interface {
+                foo: func(a: list<list<string>>) -> list<list<string>>
+            }
+
+            export thing-in: interface {
+                record thing {
+                    name: string,
+                    value: list<string>
+                }
+
+                bar: func(a: thing)
+            }
+
+            export thing-in-and-out: interface {
+                record thing {
+                    name: string,
+                    value: list<string>
+                }
+
+                baz: func(a: thing) -> thing
+            }
+        }",
+        ownership: Borrowing {
+            duplicate_if_necessary: false
+        }
+    });
+
+    impl PartialEq for exports::thing_in::Thing<'_> {
+        fn eq(&self, other: &Self) -> bool {
+            self.name == other.name && self.value == other.value
+        }
+    }
+
+    impl PartialEq for exports::thing_in_and_out::Thing {
+        fn eq(&self, other: &Self) -> bool {
+            self.name == other.name && self.value == other.value
+        }
+    }
+
+    let engine = engine();
+    let component = Component::new(&engine, component())?;
+
+    let linker = Linker::new(&engine);
+    let mut store = Store::new(&engine, ());
+    let (test, _) = Test::instantiate(&mut store, &component, &linker)?;
+
+    let value = &[&["a", "b"] as &[_]] as &[_];
+    assert_eq!(value, test.lists().call_foo(&mut store, value)?);
+
+    let value = exports::thing_in::Thing {
+        name: "thing 1",
+        value: &["some value", "another value"],
+    };
+    test.thing_in().call_bar(&mut store, value)?;
+
+    let value = exports::thing_in_and_out::Thing {
+        name: "thing 1".to_owned(),
+        value: vec!["some value".to_owned(), "another value".to_owned()],
+    };
+    assert_eq!(value, test.thing_in_and_out().call_baz(&mut store, &value)?);
+
+    Ok(())
+}
+
+#[test]
+fn borrowing_with_duplication() -> Result<()> {
+    wasmtime::component::bindgen!({
+        inline: "
+        package inline:inline
+        world test {
+            export lists: interface {
+                foo: func(a: list<list<string>>) -> list<list<string>>
+            }
+
+            export thing-in: interface {
+                record thing {
+                    name: string,
+                    value: list<string>
+                }
+
+                bar: func(a: thing)
+            }
+
+            export thing-in-and-out: interface {
+                record thing {
+                    name: string,
+                    value: list<string>
+                }
+
+                baz: func(a: thing) -> thing
+            }
+        }",
+        ownership: Borrowing {
+            duplicate_if_necessary: true
+        }
+    });
+
+    impl PartialEq for exports::thing_in::Thing<'_> {
+        fn eq(&self, other: &Self) -> bool {
+            self.name == other.name && self.value == other.value
+        }
+    }
+
+    impl PartialEq for exports::thing_in_and_out::ThingResult {
+        fn eq(&self, other: &Self) -> bool {
+            self.name == other.name && self.value == other.value
+        }
+    }
+
+    let engine = engine();
+    let component = Component::new(&engine, component())?;
+
+    let linker = Linker::new(&engine);
+    let mut store = Store::new(&engine, ());
+    let (test, _) = Test::instantiate(&mut store, &component, &linker)?;
+
+    let value = &[&["a", "b"] as &[_]] as &[_];
+    assert_eq!(value, test.lists().call_foo(&mut store, value)?);
+
+    let value = exports::thing_in::Thing {
+        name: "thing 1",
+        value: &["some value", "another value"],
+    };
+    test.thing_in().call_bar(&mut store, value)?;
+
+    let value = exports::thing_in_and_out::ThingParam {
+        name: "thing 1",
+        value: &["some value", "another value"],
+    };
+    assert_eq!(
+        exports::thing_in_and_out::ThingResult {
+            name: "thing 1".to_owned(),
+            value: vec!["some value".to_owned(), "another value".to_owned()],
+        },
+        test.thing_in_and_out().call_baz(&mut store, value)?
+    );
+
+    Ok(())
+}
diff --git a/tests/misc_testsuite/component-model/types.wast b/tests/misc_testsuite/component-model/types.wast
index 4c44764ae..25f103022 100644
--- a/tests/misc_testsuite/component-model/types.wast
+++ b/tests/misc_testsuite/component-model/types.wast
@@ -244,6 +244,7 @@
     (type $t99 (list $t98))
     (type $t100 (list $t99))
     (type $t101 (list $t100))
+    (export "t" (type $t101))
   )
   "type nesting is too deep")