Repository: bytecodealliance/wasmtime
Pull request #7798 (2024-01-19 22:48:21)

winch: Emit unwind info in the x64 backend
Reuse cranelift's implementation of unwinding info for windows, allowing for traps to work properly in winch. Some `cranelift-codegen` details were necessary to expose to allow for this, most notably the `RealReg` type needed to be exported. Additionally, I opted to reuse functionality from cranelift by adding public functions for ingesting the unwind info present, which greatly simplified the implementation in winch.

<!--
Please make sure you include the following information:

- If this work has been discussed elsewhere, please include a link to that
  conversation. If it was discussed in an issue, just mention "issue #...".

- Explain why this change is needed. If the details are in an issue already,
  this can be brief.

Our development process is documented in the Wasmtime book:
https://docs.wasmtime.dev/contributing-development-process.html

Please ensure all communication follows the code of conduct:
https://github.com/bytecodealliance/wasmtime/blob/main/CODE_OF_CONDUCT.md
-->


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

cc @saulecabrera

<details>
This issue or pull request has been labeled: "cranelift", "cranelift:area:x64", "winch"

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

* saulecabrera: winch

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

[Learn more.](https://github.com/bytecodealliance/subscribe-to-label-action)
</details>
elliottt: There are a few open questions for me at this point:

* Do we have a guiding principle for what goes in `cranelift-shared` vs making things public in `cranelift-codegen`?
* Should we reuse the `FrameLayout` type in cranelift instead of using constants when constructing `UnwindInfo` values?

With answers to those questions, I think this PR will be in a good state.

Follow-on work that needs to be done is:

* Add the probe stack step to stack overflow checking
* Check if we need the same 32k stack overflow check that cranelift does
* Move the stack overflow check into the prologue, as the stack overflow check happening outside of the prologue has consequences on windows
alexcrichton: > Do we have a guiding principle for what goes in cranelift-shared vs making things public in cranelift-codegen?

AFAIK it's "whatever goes". There's probably better places to draw these abstractions and/or perhaps use other abstractions already in Cranelift instead of these abstractions. In general though I think it's ok to not think too too hard about this as it's all internal and we can refactor it in the future pretty easily.


> Should we reuse the FrameLayout type in cranelift instead of using constants when constructing UnwindInfo values?

I haven't used this type much but reading over this to prologue is pretty neatly defined within a single function so I think it's reasonable to leave the constants as-is, although perhaps with a comment explaining a bit (e.g. 16 instead of 8 to account for the original `call` instruction's return address)

Patch:
diff --git a/RELEASES.md b/RELEASES.md
index 3b209b29f..3618765bc 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -12,12 +12,79 @@ Unreleased.
 
 ## 17.0.0
 
-Unreleased.
+Released 2024-01-25
+
+The major feature of this release is that the WASI support in Wasmtime is now
+considered stable and flagged at an 0.2.0 version approved by the WASI subgroup.
+The release was delayed a few days to hold off until the WASI subgroup voted to
+approve the CLI and HTTP worlds and they're now on-by-default! Additionally the
+component model is now enabled by default in Wasmtime, for example an opt-in
+flag is no longer required on the CLI. Note that embeddings still must opt-in to
+using the component model by using the `wasmtime::component` module.
 
 ### Added
 
+* Cranelift optimizations have been added for "3-way comparisons", or `Ord::cmp`
+  in Rust or `<=>` in C++.
+  [#7636](https://github.com/bytecodealliance/wasmtime/pull/7636)
+  [#7702](https://github.com/bytecodealliance/wasmtime/pull/7702)
+
+* Components now use Wasmtime's compilation cache used for core wasm modules by
+  default.
+  [#7649](https://github.com/bytecodealliance/wasmtime/pull/7649)
+
+* The `Resource<T>` and `ResourceAny` types can now be converted between each
+  other.
+  [#7649](https://github.com/bytecodealliance/wasmtime/pull/7649)
+  [#7712](https://github.com/bytecodealliance/wasmtime/pull/7712)
+
 ### Changed
 
+* Minor changes have been made to a number of WITs as they progressed to their
+  official 0.2.0 status.
+  [#7625](https://github.com/bytecodealliance/wasmtime/pull/7625)
+  [#7640](https://github.com/bytecodealliance/wasmtime/pull/7640)
+  [#7690](https://github.com/bytecodealliance/wasmtime/pull/7690)
+  [#7781](https://github.com/bytecodealliance/wasmtime/pull/7781)
+  [#7817](https://github.com/bytecodealliance/wasmtime/pull/7817)
+
+* The component model is now enabled by default.
+  [#7821](https://github.com/bytecodealliance/wasmtime/pull/7821)
+
+* The implementation of `memory.atomic.{wait,notify}` has been rewritten.
+  [#7629](https://github.com/bytecodealliance/wasmtime/pull/7629)
+
+* The `wasmtime_wasi::preview2::Table` type has been moved to
+  `wasmtime::component::ResourceTable`.
+  [#7655](https://github.com/bytecodealliance/wasmtime/pull/7655)
+
+* Creating a UDP stream now validates the address being sent to.
+  [#7648](https://github.com/bytecodealliance/wasmtime/pull/7648)
+
+* Defining resource types in `Linker<T>` now takes the type to define as a
+  runtime parameter.
+  [#7680](https://github.com/bytecodealliance/wasmtime/pull/7680)
+
+* Socket address checks can now be performed dynamically at runtime.
+  [#7662](https://github.com/bytecodealliance/wasmtime/pull/7662)
+
+* Wasmtime and Cranelift's MSRV is now 1.73.0.
+  [#7739](https://github.com/bytecodealliance/wasmtime/pull/7739)
+
+### Fixed
+
+* Bindings for WIT APIs where interfaces have multiple versions are now fixed by
+  putting the version in the generated binding names.
+  [#7656](https://github.com/bytecodealliance/wasmtime/pull/7656)
+
+* The preview1 `fd_{read,write}` APIs are now fixed when a shared memory is
+  used.
+  [#7755](https://github.com/bytecodealliance/wasmtime/pull/7755)
+
+* The preview1 `fd_{read,write}` APIs no longer leak an intermediate stream
+  created.
+  [#7819](https://github.com/bytecodealliance/wasmtime/pull/7819)
+
 --------------------------------------------------------------------------------
 
 ## 16.0.0
diff --git a/build.rs b/build.rs
index 0826f0a86..0421313e7 100644
--- a/build.rs
+++ b/build.rs
@@ -204,30 +204,7 @@ fn ignore(testsuite: &str, testname: &str, strategy: &str) -> bool {
     assert!(strategy == "Cranelift" || strategy == "Winch");
 
     // Ignore everything except the winch misc test suite.
-    // We ignore tests that assert for traps on windows, given
-    // that Winch doesn't encode unwind information for Windows, yet.
     if strategy == "Winch" {
-        let assert_trap = [
-            "i32",
-            "i64",
-            "call",
-            "call_indirect",
-            "conversions",
-            "table_fill",
-            "table_init",
-            "table_copy",
-            "table_set",
-            "table_get",
-            "memory_grow",
-            "memory_init",
-            "memory_fill",
-        ]
-        .contains(&testname);
-
-        if assert_trap && env::var("CARGO_CFG_TARGET_OS").unwrap().as_str() == "windows" {
-            return true;
-        }
-
         if testsuite == "misc_testsuite" {
             // The misc/call_indirect is fully supported by Winch.
             if testname != "call_indirect" {
diff --git a/cranelift/codegen/src/isa/x64/mod.rs b/cranelift/codegen/src/isa/x64/mod.rs
index 38a9c668c..5aaa664fc 100644
--- a/cranelift/codegen/src/isa/x64/mod.rs
+++ b/cranelift/codegen/src/isa/x64/mod.rs
@@ -15,6 +15,7 @@ use crate::machinst::{
 };
 use crate::result::CodegenResult;
 use crate::settings::{self as shared_settings, Flags};
+use crate::{Final, MachBufferFinalized};
 use alloc::{boxed::Box, vec::Vec};
 use core::fmt;
 use cranelift_control::ControlPlane;
@@ -27,6 +28,8 @@ mod lower;
 mod pcc;
 pub mod settings;
 
+pub use inst::unwind::systemv::create_cie;
+
 /// An X64 backend.
 pub(crate) struct X64Backend {
     triple: Triple,
@@ -118,26 +121,7 @@ impl TargetIsa for X64Backend {
         result: &CompiledCode,
         kind: crate::isa::unwind::UnwindInfoKind,
     ) -> CodegenResult<Option<crate::isa::unwind::UnwindInfo>> {
-        use crate::isa::unwind::UnwindInfo;
-        use crate::isa::unwind::UnwindInfoKind;
-        Ok(match kind {
-            UnwindInfoKind::SystemV => {
-                let mapper = self::inst::unwind::systemv::RegisterMapper;
-                Some(UnwindInfo::SystemV(
-                    crate::isa::unwind::systemv::create_unwind_info_from_insts(
-                        &result.buffer.unwind_info[..],
-                        result.buffer.data().len(),
-                        &mapper,
-                    )?,
-                ))
-            }
-            UnwindInfoKind::Windows => Some(UnwindInfo::WindowsX64(
-                crate::isa::unwind::winx64::create_unwind_info_from_insts::<
-                    self::inst::unwind::winx64::RegisterMapper,
-                >(&result.buffer.unwind_info[..])?,
-            )),
-            _ => None,
-        })
+        emit_unwind_info(&result.buffer, kind)
     }
 
     #[cfg(feature = "unwind")]
@@ -193,6 +177,32 @@ impl TargetIsa for X64Backend {
     }
 }
 
+/// Emit unwind info for an x86 target.
+pub fn emit_unwind_info(
+    buffer: &MachBufferFinalized<Final>,
+    kind: crate::isa::unwind::UnwindInfoKind,
+) -> CodegenResult<Option<crate::isa::unwind::UnwindInfo>> {
+    use crate::isa::unwind::{UnwindInfo, UnwindInfoKind};
+    Ok(match kind {
+        UnwindInfoKind::SystemV => {
+            let mapper = self::inst::unwind::systemv::RegisterMapper;
+            Some(UnwindInfo::SystemV(
+                crate::isa::unwind::systemv::create_unwind_info_from_insts(
+                    &buffer.unwind_info[..],
+                    buffer.data().len(),
+                    &mapper,
+                )?,
+            ))
+        }
+        UnwindInfoKind::Windows => Some(UnwindInfo::WindowsX64(
+            crate::isa::unwind::winx64::create_unwind_info_from_insts::<
+                self::inst::unwind::winx64::RegisterMapper,
+            >(&buffer.unwind_info[..])?,
+        )),
+        _ => None,
+    })
+}
+
 impl fmt::Display for X64Backend {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         f.debug_struct("MachBackend")
diff --git a/cranelift/codegen/src/lib.rs b/cranelift/codegen/src/lib.rs
index 5d9922bf7..1908bdd3d 100644
--- a/cranelift/codegen/src/lib.rs
+++ b/cranelift/codegen/src/lib.rs
@@ -59,8 +59,8 @@ pub use crate::machinst::buffer::{
 };
 pub use crate::machinst::{
     CompiledCode, Final, MachBuffer, MachBufferFinalized, MachInst, MachInstEmit,
-    MachInstEmitState, MachLabel, Reg, TextSectionBuilder, VCodeConstantData, VCodeConstants,
-    Writable,
+    MachInstEmitState, MachLabel, RealReg, Reg, TextSectionBuilder, VCodeConstantData,
+    VCodeConstants, Writable,
 };
 
 mod alias_analysis;
diff --git a/cranelift/codegen/src/machinst/reg.rs b/cranelift/codegen/src/machinst/reg.rs
index 8aadc11b5..4e21377d2 100644
--- a/cranelift/codegen/src/machinst/reg.rs
+++ b/cranelift/codegen/src/machinst/reg.rs
@@ -112,6 +112,7 @@ impl RealReg {
         self.0.class()
     }
 
+    /// The physical register number.
     pub fn hw_enc(self) -> u8 {
         PReg::from(self).hw_enc() as u8
     }
diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs
index fd44bd443..221c45470 100644
--- a/crates/wasmtime/src/config.rs
+++ b/crates/wasmtime/src/config.rs
@@ -228,6 +228,8 @@ impl Config {
         ret.wasm_multi_value(true);
         ret.wasm_bulk_memory(true);
         ret.wasm_simd(true);
+        #[cfg(feature = "component-model")]
+        ret.wasm_component_model(true);
         ret.wasm_backtrace_details(WasmBacktraceDetails::Environment);
 
         // This is on-by-default in `wasmparser` since it's a stage 4+ proposal
diff --git a/crates/winch/src/compiler.rs b/crates/winch/src/compiler.rs
index bc1191887..c58318eb8 100644
--- a/crates/winch/src/compiler.rs
+++ b/crates/winch/src/compiler.rs
@@ -1,4 +1,5 @@
 use anyhow::Result;
+use cranelift_codegen::isa::unwind::UnwindInfoKind;
 use object::write::{Object, SymbolId};
 use std::any::Any;
 use std::mem;
@@ -63,6 +64,27 @@ impl Compiler {
         context.allocations = allocs;
         self.contexts.lock().unwrap().push(context);
     }
+
+    /// Emit unwind info into the [`CompiledFunction`].
+    fn emit_unwind_info(
+        &self,
+        compiled_function: &mut CompiledFunction<CompiledFuncEnv>,
+    ) -> Result<(), CompileError> {
+        let kind = match self.isa.triple().operating_system {
+            target_lexicon::OperatingSystem::Windows => UnwindInfoKind::Windows,
+            _ => UnwindInfoKind::SystemV,
+        };
+
+        if let Some(info) = self
+            .isa
+            .emit_unwind_info(&compiled_function.buffer, kind)
+            .map_err(|e| CompileError::Codegen(format!("{e:?}")))?
+        {
+            compiled_function.set_unwind_info(info);
+        }
+
+        Ok(())
+    }
 }
 
 impl wasmtime_environ::Compiler for Compiler {
@@ -98,9 +120,14 @@ impl wasmtime_environ::Compiler for Compiler {
             .map_err(|e| CompileError::Codegen(format!("{e:?}")));
         self.save_context(context, validator.into_allocations());
         let buffer = buffer?;
-        let compiled_function =
+
+        let mut compiled_function =
             CompiledFunction::new(buffer, CompiledFuncEnv {}, self.isa.function_alignment());
 
+        if self.isa.flags().unwind_info() {
+            self.emit_unwind_info(&mut compiled_function)?;
+        }
+
         Ok((
             WasmFunctionInfo {
                 start_srcloc,
@@ -123,9 +150,14 @@ impl wasmtime_environ::Compiler for Compiler {
             .isa
             .compile_trampoline(&ty, TrampolineKind::ArrayToWasm(func_index))
             .map_err(|e| CompileError::Codegen(format!("{:?}", e)))?;
-        let compiled_function =
+
+        let mut compiled_function =
             CompiledFunction::new(buffer, CompiledFuncEnv {}, self.isa.function_alignment());
 
+        if self.isa.flags().unwind_info() {
+            self.emit_unwind_info(&mut compiled_function)?;
+        }
+
         Ok(Box::new(compiled_function))
     }
 
@@ -144,9 +176,13 @@ impl wasmtime_environ::Compiler for Compiler {
             .compile_trampoline(ty, TrampolineKind::NativeToWasm(func_index))
             .map_err(|e| CompileError::Codegen(format!("{:?}", e)))?;
 
-        let compiled_function =
+        let mut compiled_function =
             CompiledFunction::new(buffer, CompiledFuncEnv {}, self.isa.function_alignment());
 
+        if self.isa.flags().unwind_info() {
+            self.emit_unwind_info(&mut compiled_function)?;
+        }
+
         Ok(Box::new(compiled_function))
     }
 
@@ -159,9 +195,13 @@ impl wasmtime_environ::Compiler for Compiler {
             .compile_trampoline(wasm_func_ty, TrampolineKind::WasmToNative)
             .map_err(|e| CompileError::Codegen(format!("{:?}", e)))?;
 
-        let compiled_function =
+        let mut compiled_function =
             CompiledFunction::new(buffer, CompiledFuncEnv {}, self.isa.function_alignment());
 
+        if self.isa.flags().unwind_info() {
+            self.emit_unwind_info(&mut compiled_function)?;
+        }
+
         Ok(Box::new(compiled_function))
     }
 
diff --git a/src/common.rs b/src/common.rs
index 70782c0b3..2850639d0 100644
--- a/src/common.rs
+++ b/src/common.rs
@@ -107,7 +107,7 @@ impl RunCommon {
 
     #[cfg(feature = "component-model")]
     fn ensure_allow_components(&self) -> Result<()> {
-        if self.common.wasm.component_model != Some(true) {
+        if self.common.wasm.component_model == Some(false) {
             bail!("cannot execute a component without `--wasm component-model`");
         }
 
diff --git a/winch/codegen/Cargo.toml b/winch/codegen/Cargo.toml
index cc2b942ff..90c52fa5c 100644
--- a/winch/codegen/Cargo.toml
+++ b/winch/codegen/Cargo.toml
@@ -19,7 +19,7 @@ target-lexicon = { workspace = true, features = ["std"] }
 # mostly to have access to `PReg`s and the calling convention.
 # In the next iteration we'll factor out the common bits so that they can be consumed
 # by Cranelift and Winch.
-cranelift-codegen = { workspace = true }
+cranelift-codegen = { workspace = true, features = ["unwind"] }
 regalloc2 = { workspace = true }
 gimli = { workspace = true }
 wasmtime-environ = { workspace = true }
diff --git a/winch/codegen/src/isa/aarch64/mod.rs b/winch/codegen/src/isa/aarch64/mod.rs
index 15d6b2188..a17e8fbdd 100644
--- a/winch/codegen/src/isa/aarch64/mod.rs
+++ b/winch/codegen/src/isa/aarch64/mod.rs
@@ -134,4 +134,12 @@ impl TargetIsa for Aarch64 {
     ) -> Result<MachBufferFinalized<Final>> {
         todo!()
     }
+
+    fn emit_unwind_info(
+        &self,
+        _result: &MachBufferFinalized<Final>,
+        _kind: cranelift_codegen::isa::unwind::UnwindInfoKind,
+    ) -> Result<Option<cranelift_codegen::isa::unwind::UnwindInfo>> {
+        todo!()
+    }
 }
diff --git a/winch/codegen/src/isa/mod.rs b/winch/codegen/src/isa/mod.rs
index 7ccf621f5..2579840d9 100644
--- a/winch/codegen/src/isa/mod.rs
+++ b/winch/codegen/src/isa/mod.rs
@@ -1,6 +1,7 @@
 use crate::{BuiltinFunctions, TrampolineKind};
 use anyhow::{anyhow, Result};
 use core::fmt::Formatter;
+use cranelift_codegen::isa::unwind::{UnwindInfo, UnwindInfoKind};
 use cranelift_codegen::isa::{CallConv, IsaBuilder};
 use cranelift_codegen::settings;
 use cranelift_codegen::{Final, MachBufferFinalized, TextSectionBuilder};
@@ -184,6 +185,12 @@ pub trait TargetIsa: Send + Sync {
         self.triple().endianness().unwrap()
     }
 
+    fn emit_unwind_info(
+        &self,
+        _result: &MachBufferFinalized<Final>,
+        _kind: UnwindInfoKind,
+    ) -> Result<Option<UnwindInfo>>;
+
     /// See `cranelift_codegen::isa::TargetIsa::create_systemv_cie`.
     fn create_systemv_cie(&self) -> Option<gimli::write::CommonInformationEntry> {
         // By default, an ISA cannot create a System V CIE.
diff --git a/winch/codegen/src/isa/reg.rs b/winch/codegen/src/isa/reg.rs
index 0a96df329..bfdcdb8fc 100644
--- a/winch/codegen/src/isa/reg.rs
+++ b/winch/codegen/src/isa/reg.rs
@@ -1,5 +1,6 @@
-use regalloc2::PReg;
+use cranelift_codegen::RealReg;
 pub use regalloc2::RegClass;
+use regalloc2::{PReg, VReg};
 
 /// A newtype abstraction on top of a physical register.
 //
@@ -71,3 +72,15 @@ impl std::fmt::Debug for Reg {
         write!(f, "{}", self.0)
     }
 }
+
+impl Into<VReg> for Reg {
+    fn into(self) -> VReg {
+        VReg::new(self.inner().index(), self.class())
+    }
+}
+
+impl Into<RealReg> for Reg {
+    fn into(self) -> RealReg {
+        Into::<VReg>::into(self).into()
+    }
+}
diff --git a/winch/codegen/src/isa/x64/asm.rs b/winch/codegen/src/isa/x64/asm.rs
index 256d4fb9f..b956f82bd 100644
--- a/winch/codegen/src/isa/x64/asm.rs
+++ b/winch/codegen/src/isa/x64/asm.rs
@@ -8,6 +8,7 @@ use cranelift_codegen::{
     entity::EntityRef,
     ir::{types, ConstantPool, ExternalName, LibCall, Opcode, TrapCode, UserExternalNameRef},
     isa::{
+        unwind::UnwindInst,
         x64::{
             args::{
                 self, AluRmiROpcode, Amode, CmpOpcode, DivSignedness, ExtMode, FromWritableReg,
@@ -229,6 +230,11 @@ impl Assembler {
         }
     }
 
+    /// Emit an unwind instruction.
+    pub fn emit_unwind_inst(&mut self, inst: UnwindInst) {
+        self.emit(Inst::Unwind { inst })
+    }
+
     /// Push register.
     pub fn push_r(&mut self, reg: Reg) {
         self.emit(Inst::Push64 { src: reg.into() });
diff --git a/winch/codegen/src/isa/x64/masm.rs b/winch/codegen/src/isa/x64/masm.rs
index 7000dacdd..680adb481 100644
--- a/winch/codegen/src/isa/x64/masm.rs
+++ b/winch/codegen/src/isa/x64/masm.rs
@@ -24,7 +24,8 @@ use crate::{
     masm::CalleeKind,
 };
 use cranelift_codegen::{
-    isa::x64::settings as x64_settings, settings, Final, MachBufferFinalized, MachLabel,
+    isa::unwind::UnwindInst, isa::x64::settings as x64_settings, settings, Final,
+    MachBufferFinalized, MachLabel,
 };
 
 use wasmtime_environ::{PtrSize, WasmType, WASM_PAGE_SIZE};
@@ -53,8 +54,25 @@ impl Masm for MacroAssembler {
         let stack_pointer = rsp();
 
         self.asm.push_r(frame_pointer);
+
+        if self.shared_flags.unwind_info() {
+            self.asm.emit_unwind_inst(UnwindInst::PushFrameRegs {
+                offset_upward_to_caller_sp: Self::ABI::arg_base_offset().try_into().unwrap(),
+            })
+        }
+
         self.asm
             .mov_rr(stack_pointer, frame_pointer, OperandSize::S64);
+
+        if self.shared_flags.unwind_info() {
+            self.asm.emit_unwind_inst(UnwindInst::DefineNewFrame {
+                offset_upward_to_caller_sp: Self::ABI::arg_base_offset().try_into().unwrap(),
+
+                // Clobbers appear directly after the RET and FP if they're present. As we just
+                // pushed the frame pointer, the offset to the clobbers will be `0`.
+                offset_downward_to_clobbers: 0,
+            })
+        }
     }
 
     fn check_stack(&mut self) {
@@ -107,6 +125,19 @@ impl Masm for MacroAssembler {
         }
     }
 
+    fn save(&mut self, clobber_offset: u32, reg: Reg, size: OperandSize) -> StackSlot {
+        let slot = self.push(reg, size);
+
+        if self.shared_flags.unwind_info() {
+            self.asm.emit_unwind_inst(UnwindInst::SaveReg {
+                clobber_offset,
+                reg: reg.into(),
+            });
+        }
+
+        slot
+    }
+
     fn reserve_stack(&mut self, bytes: u32) {
         if bytes == 0 {
             return;
diff --git a/winch/codegen/src/isa/x64/mod.rs b/winch/codegen/src/isa/x64/mod.rs
index 7e56d933f..e14baae00 100644
--- a/winch/codegen/src/isa/x64/mod.rs
+++ b/winch/codegen/src/isa/x64/mod.rs
@@ -169,4 +169,16 @@ impl TargetIsa for X64 {
 
         Ok(masm.finalize())
     }
+
+    fn emit_unwind_info(
+        &self,
+        buffer: &MachBufferFinalized<Final>,
+        kind: cranelift_codegen::isa::unwind::UnwindInfoKind,
+    ) -> Result<Option<cranelift_codegen::isa::unwind::UnwindInfo>> {
+        Ok(cranelift_codegen::isa::x64::emit_unwind_info(buffer, kind)?)
+    }
+
+    fn create_systemv_cie(&self) -> Option<gimli::write::CommonInformationEntry> {
+        Some(cranelift_codegen::isa::x64::create_cie())
+    }
 }
diff --git a/winch/codegen/src/masm.rs b/winch/codegen/src/masm.rs
index f12d284ec..3ea75a40e 100644
--- a/winch/codegen/src/masm.rs
+++ b/winch/codegen/src/masm.rs
@@ -766,4 +766,11 @@ pub(crate) trait MacroAssembler {
             self.free_stack(bytes);
         }
     }
+
+    /// Save the value of this register to the stack. By default this is the same as pushing the
+    /// register, however it's present in the [`MacroAssembler`] trait to ensure that it's possible
+    /// to add unwind info for register saves in backends.
+    fn save(&mut self, _off: u32, src: Reg, size: OperandSize) -> StackSlot {
+        self.push(src, size)
+    }
 }
diff --git a/winch/codegen/src/trampoline.rs b/winch/codegen/src/trampoline.rs
index 6d3e4bcfd..bd651ad63 100644
--- a/winch/codegen/src/trampoline.rs
+++ b/winch/codegen/src/trampoline.rs
@@ -705,8 +705,10 @@ where
     fn prologue_with_callee_saved(&mut self) {
         self.masm.prologue();
         // Save any callee-saved registers.
+        let mut off = 0;
         for (r, s) in &self.callee_saved_regs {
-            self.masm.push(*r, *s);
+            let slot = self.masm.save(off, *r, *s);
+            off += slot.size;
         }
     }
 


Test patch:
diff --git a/tests/all/cli_tests.rs b/tests/all/cli_tests.rs
index 478c25830..f5fcc6a5e 100644
--- a/tests/all/cli_tests.rs
+++ b/tests/all/cli_tests.rs
@@ -760,6 +760,7 @@ fn component_missing_feature() -> Result<()> {
     let wasm = build_wasm(path)?;
     let output = get_wasmtime_command()?
         .arg("-Ccache=n")
+        .arg("-Wcomponent-model=n")
         .arg(wasm.path())
         .output()?;
     assert!(!output.status.success());
@@ -772,6 +773,7 @@ fn component_missing_feature() -> Result<()> {
     // also tests with raw *.wat input
     let output = get_wasmtime_command()?
         .arg("-Ccache=n")
+        .arg("-Wcomponent-model=n")
         .arg(path)
         .output()?;
     assert!(!output.status.success());
@@ -784,6 +786,27 @@ fn component_missing_feature() -> Result<()> {
     Ok(())
 }
 
+#[test]
+#[cfg_attr(not(feature = "component-model"), ignore)]
+fn component_enabled_by_default() -> Result<()> {
+    let path = "tests/all/cli_tests/component-basic.wat";
+    let wasm = build_wasm(path)?;
+    let output = get_wasmtime_command()?
+        .arg("-Ccache=n")
+        .arg(wasm.path())
+        .output()?;
+    assert!(output.status.success());
+
+    // also tests with raw *.wat input
+    let output = get_wasmtime_command()?
+        .arg("-Ccache=n")
+        .arg(path)
+        .output()?;
+    assert!(output.status.success());
+
+    Ok(())
+}
+
 // If the text format is invalid then the filename should be mentioned in the
 // error message.
 #[test]