Repository: bytecodealliance/wasmtime
Pull request #8198 (2024-03-20 21:43:19)

cranelift: Fix return value handling with the winch calling convention
Cranelift's Winch calling convention implementation was incorrect in its handling of return values in three ways:

* Winch returns the last return value in a register instead of the first,
* Winch doesn't align entries returned through the stack to 8-byte boundaries,
* and finally, Winch places the first returned value at the largest offset from the return location, instead of the smallest.

This PR addresses these three issues, enabling progress towards removing the trampoline implementations that are defined in terms of Winch's `MacroAssembler` trait.

Additionally, this PR disables the Winch calling convention for Cranelift's `aarch64` backend, as Winch support for `aarch64` is currently minimal.

<!--
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
-->


elliottt: > Would it perhaps also make sense to add a runtest exercising this to ensure at runtime the result works in Cranelift at least?

This seems reasonable to me! I'll investigate adding one.
saulecabrera: Oops, I didn't realize that Alex had already approved. 

Patch:
diff --git a/RELEASES.md b/RELEASES.md
index 52b80b5b5..c4cf5b2e8 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -114,6 +114,16 @@ Released 2024-03-20
 
 --------------------------------------------------------------------------------
 
+## 18.0.3
+
+Released 2024-03-12
+
+### Fixed
+* Fix inferring native flags when a compilation target is specified.
+  [#7991](https://github.com/bytecodealliance/wasmtime/pull/7991)
+
+--------------------------------------------------------------------------------
+
 ## 18.0.2
 
 Released 2024-02-28.
diff --git a/cranelift/codegen/build.rs b/cranelift/codegen/build.rs
index d5c5f7a57..70b62f580 100644
--- a/cranelift/codegen/build.rs
+++ b/cranelift/codegen/build.rs
@@ -231,8 +231,9 @@ fn get_isle_compilations(
                     src_opts.join("icmp.isle"),
                     src_opts.join("remat.isle"),
                     src_opts.join("selects.isle"),
-                    src_opts.join("spaceship.isle"),
                     src_opts.join("shifts.isle"),
+                    src_opts.join("spaceship.isle"),
+                    src_opts.join("spectre.isle"),
                     src_opts.join("vector.isle"),
                 ],
                 untracked_inputs: vec![clif_opt_isle],
diff --git a/cranelift/codegen/meta/src/shared/instructions.rs b/cranelift/codegen/meta/src/shared/instructions.rs
index 5e75ac3a4..909f80160 100644
--- a/cranelift/codegen/meta/src/shared/instructions.rs
+++ b/cranelift/codegen/meta/src/shared/instructions.rs
@@ -1449,19 +1449,36 @@ pub(crate) fn define(
             Conditional select intended for Spectre guards.
 
             This operation is semantically equivalent to a select instruction.
-            However, it is guaranteed to not be removed or otherwise altered by any
-            optimization pass, and is guaranteed to result in a conditional-move
-            instruction, not a branch-based lowering.  As such, it is suitable
-            for use when producing Spectre guards. For example, a bounds-check
-            may guard against unsafe speculation past a bounds-check conditional
-            branch by passing the address or index to be accessed through a
-            conditional move, also gated on the same condition. Because no
-            Spectre-vulnerable processors are known to perform speculation on
-            conditional move instructions, this is guaranteed to pick the
-            correct input. If the selected input in case of overflow is a "safe"
-            value, for example a null pointer that causes an exception in the
-            speculative path, this ensures that no Spectre vulnerability will
-            exist.
+            However, this instruction prohibits all speculation on the
+            controlling value when determining which input to use as the result.
+            As such, it is suitable for use in Spectre guards.
+
+            For example, on a target which may speculatively execute branches,
+            the lowering of this instruction is guaranteed to not conditionally
+            branch. Instead it will typically lower to a conditional move
+            instruction. (No Spectre-vulnerable processors are known to perform
+            value speculation on conditional move instructions.)
+
+            Ensure that the instruction you're trying to protect from Spectre
+            attacks has a data dependency on the result of this instruction.
+            That prevents an out-of-order CPU from evaluating that instruction
+            until the result of this one is known, which in turn will be blocked
+            until the controlling value is known.
+
+            Typical usage is to use a bounds-check as the controlling value,
+            and select between either a null pointer if the bounds-check
+            fails, or an in-bounds address otherwise, so that dereferencing
+            the resulting address with a load or store instruction will trap if
+            the bounds-check failed. When this instruction is used in this way,
+            any microarchitectural side effects of the memory access will only
+            occur after the bounds-check finishes, which ensures that no Spectre
+            vulnerability will exist.
+
+            Optimization opportunities for this instruction are limited compared
+            to a normal select instruction, but it is allowed to be replaced
+            by other values which are functionally equivalent as long as doing
+            so does not introduce any new opportunities to speculate on the
+            controlling value.
             "#,
             &formats.ternary,
         )
@@ -1470,11 +1487,7 @@ pub(crate) fn define(
             Operand::new("x", Any).with_doc("Value to use when `c` is true"),
             Operand::new("y", Any).with_doc("Value to use when `c` is false"),
         ])
-        .operands_out(vec![Operand::new("a", Any)])
-        .other_side_effects()
-        // We can de-duplicate spectre selects since the side effect is
-        // idempotent.
-        .side_effects_idempotent(),
+        .operands_out(vec![Operand::new("a", Any)]),
     );
 
     ig.push(
diff --git a/cranelift/codegen/src/isa/aarch64/abi.rs b/cranelift/codegen/src/isa/aarch64/abi.rs
index 31a5c25c3..0841cea2c 100644
--- a/cranelift/codegen/src/isa/aarch64/abi.rs
+++ b/cranelift/codegen/src/isa/aarch64/abi.rs
@@ -107,7 +107,13 @@ impl ABIMachineSpec for AArch64MachineDeps {
         add_ret_area_ptr: bool,
         mut args: ArgsAccumulator,
     ) -> CodegenResult<(u32, Option<usize>)> {
-        if matches!(call_conv, isa::CallConv::Tail | isa::CallConv::Winch) {
+        assert_ne!(
+            call_conv,
+            isa::CallConv::Winch,
+            "aarch64 does not support the 'winch' calling convention yet"
+        );
+
+        if matches!(call_conv, isa::CallConv::Tail) {
             return compute_arg_locs_tail(params, add_ret_area_ptr, args);
         }
 
@@ -1105,7 +1111,6 @@ impl ABIMachineSpec for AArch64MachineDeps {
     fn get_regs_clobbered_by_call(call_conv_of_callee: isa::CallConv) -> PRegSet {
         match call_conv_of_callee {
             isa::CallConv::Tail => ALL_CLOBBERS,
-            isa::CallConv::Winch => ALL_CLOBBERS,
             _ => DEFAULT_AAPCS_CLOBBERS,
         }
     }
@@ -1423,7 +1428,7 @@ fn is_reg_saved_in_prologue(
     sig: &Signature,
     r: RealReg,
 ) -> bool {
-    if call_conv == isa::CallConv::Tail || call_conv == isa::CallConv::Winch {
+    if call_conv == isa::CallConv::Tail {
         return false;
     }
 
diff --git a/cranelift/codegen/src/isa/aarch64/inst/mod.rs b/cranelift/codegen/src/isa/aarch64/inst/mod.rs
index af7e42fc5..82fdccab8 100644
--- a/cranelift/codegen/src/isa/aarch64/inst/mod.rs
+++ b/cranelift/codegen/src/isa/aarch64/inst/mod.rs
@@ -873,11 +873,6 @@ fn aarch64_get_operands<F: Fn(VReg) -> VReg>(inst: &Inst, collector: &mut Operan
                     // This shouldn't be a fixed register constraint.
                     collector.reg_fixed_use(info.rn, xreg(1))
                 }
-                CallConv::Winch => {
-                    // TODO(https://github.com/bytecodealliance/regalloc2/issues/145):
-                    // This shouldn't be a fixed register constraint.
-                    collector.reg_fixed_use(info.rn, xreg(1))
-                }
                 _ => collector.reg_use(info.rn),
             }
             for u in &info.uses {
diff --git a/cranelift/codegen/src/isa/x64/abi.rs b/cranelift/codegen/src/isa/x64/abi.rs
index 9b8eb0764..5b33950d5 100644
--- a/cranelift/codegen/src/isa/x64/abi.rs
+++ b/cranelift/codegen/src/isa/x64/abi.rs
@@ -125,7 +125,9 @@ impl ABIMachineSpec for X64ABIMachineSpec {
             next_stack = 32;
         }
 
-        for param in params {
+        for (ix, param) in params.iter().enumerate() {
+            let last_param = ix == params.len() - 1;
+
             if let ir::ArgumentPurpose::StructArgument(size) = param.purpose {
                 let offset = next_stack as i64;
                 let size = size;
@@ -210,6 +212,11 @@ impl ABIMachineSpec for X64ABIMachineSpec {
                 continue;
             }
 
+            debug_assert!(
+                call_conv != CallConv::Winch || rcs.len() == 1,
+                "Winch is unable to handle values wider than 64-bits"
+            );
+
             let mut slots = ABIArgSlotVec::new();
             for (rc, reg_ty) in rcs.iter().zip(reg_tys.iter()) {
                 let intreg = *rc == RegClass::Int;
@@ -218,14 +225,18 @@ impl ABIMachineSpec for X64ABIMachineSpec {
                         ArgsOrRets::Args => {
                             get_intreg_for_arg(&call_conv, next_gpr, next_param_idx)
                         }
-                        ArgsOrRets::Rets => get_intreg_for_retval(&call_conv, flags, next_gpr),
+                        ArgsOrRets::Rets => {
+                            get_intreg_for_retval(&call_conv, flags, next_gpr, last_param)
+                        }
                     }
                 } else {
                     match args_or_rets {
                         ArgsOrRets::Args => {
                             get_fltreg_for_arg(&call_conv, next_vreg, next_param_idx)
                         }
-                        ArgsOrRets::Rets => get_fltreg_for_retval(&call_conv, next_vreg),
+                        ArgsOrRets::Rets => {
+                            get_fltreg_for_retval(&call_conv, next_vreg, last_param)
+                        }
                     }
                 };
                 next_param_idx += 1;
@@ -241,11 +252,18 @@ impl ABIMachineSpec for X64ABIMachineSpec {
                         extension: param.extension,
                     });
                 } else {
-                    let size = reg_ty.bits() / 8;
-                    let size = std::cmp::max(size, 8);
-                    // Align.
-                    debug_assert!(size.is_power_of_two());
-                    next_stack = align_to(next_stack, size);
+                    let size = reg_ty.bytes();
+                    let size = if call_conv == CallConv::Winch && args_or_rets == ArgsOrRets::Rets {
+                        size
+                    } else {
+                        let size = std::cmp::max(size, 8);
+
+                        // Align.
+                        debug_assert!(size.is_power_of_two());
+                        next_stack = align_to(next_stack, size);
+                        size
+                    };
+
                     slots.push(ABIArgSlot::Stack {
                         offset: next_stack as i64,
                         ty: *reg_ty,
@@ -298,6 +316,23 @@ impl ABIMachineSpec for X64ABIMachineSpec {
             None
         };
 
+        // Winch writes the first result to the highest offset, so we need to iterate through the
+        // args and adjust the offsets down.
+        if call_conv == CallConv::Winch && args_or_rets == ArgsOrRets::Rets {
+            for arg in args.args_mut() {
+                if let ABIArg::Slots { slots, .. } = arg {
+                    for slot in slots.iter_mut() {
+                        if let ABIArgSlot::Stack { offset, ty, .. } = slot {
+                            let size = i64::from(ty.bytes());
+                            *offset = i64::from(next_stack) - *offset - size;
+                        }
+                    }
+                } else {
+                    unreachable!("Winch cannot handle {arg:?}");
+                }
+            }
+        }
+
         next_stack = align_to(next_stack, 16);
 
         // To avoid overflow issues, limit the arg/return size to something reasonable.
@@ -1037,6 +1072,7 @@ fn get_intreg_for_retval(
     call_conv: &CallConv,
     flags: &settings::Flags,
     intreg_idx: usize,
+    is_last: bool,
 ) -> Option<Reg> {
     match call_conv {
         CallConv::Tail => match intreg_idx {
@@ -1067,16 +1103,13 @@ fn get_intreg_for_retval(
             1 => Some(regs::rdx()), // The Rust ABI for i128s needs this.
             _ => None,
         },
-        CallConv::Winch => match intreg_idx {
-            0 => Some(regs::rax()),
-            _ => None,
-        },
+        CallConv::Winch => is_last.then(|| regs::rax()),
         CallConv::Probestack => todo!(),
         CallConv::WasmtimeSystemV | CallConv::AppleAarch64 => unreachable!(),
     }
 }
 
-fn get_fltreg_for_retval(call_conv: &CallConv, fltreg_idx: usize) -> Option<Reg> {
+fn get_fltreg_for_retval(call_conv: &CallConv, fltreg_idx: usize, is_last: bool) -> Option<Reg> {
     match call_conv {
         CallConv::Tail => match fltreg_idx {
             0 => Some(regs::xmm0()),
@@ -1094,10 +1127,11 @@ fn get_fltreg_for_retval(call_conv: &CallConv, fltreg_idx: usize) -> Option<Reg>
             1 => Some(regs::xmm1()),
             _ => None,
         },
-        CallConv::WindowsFastcall | CallConv::Winch => match fltreg_idx {
+        CallConv::WindowsFastcall => match fltreg_idx {
             0 => Some(regs::xmm0()),
             _ => None,
         },
+        CallConv::Winch => is_last.then(|| regs::xmm0()),
         CallConv::Probestack => todo!(),
         CallConv::WasmtimeSystemV | CallConv::AppleAarch64 => unreachable!(),
     }
diff --git a/cranelift/codegen/src/opts/spectre.isle b/cranelift/codegen/src/opts/spectre.isle
new file mode 100644
index 000000000..c58e29eaa
--- /dev/null
+++ b/cranelift/codegen/src/opts/spectre.isle
@@ -0,0 +1,14 @@
+;; Rewrites for `select_spectre_guard`. Check these rules carefully! This
+;; instruction prohibits all speculation on the controlling value when
+;; determining which input to use as the result. Rewrites must respect that
+;; requirement.
+
+;; If we statically know which value will be the result, it's safe to just use
+;; that value. No speculation on the controlling value is possible if we simply
+;; don't depend on that value at all.
+(rule (simplify (select_spectre_guard _ _ x x))
+      (subsume x))
+(rule (simplify (select_spectre_guard _ (iconst_u _ (u64_nonzero _)) x _))
+      (subsume x))
+(rule (simplify (select_spectre_guard _ (iconst_u _ 0) _ y))
+      (subsume y))
diff --git a/crates/cranelift/src/lib.rs b/crates/cranelift/src/lib.rs
index 013d40b52..fc4b0cd39 100644
--- a/crates/cranelift/src/lib.rs
+++ b/crates/cranelift/src/lib.rs
@@ -156,8 +156,8 @@ fn wasm_call_signature(
         // The winch calling convention is only implemented for x64 and aarch64
         arch if tunables.winch_callable => {
             assert!(
-                matches!(arch, Architecture::X86_64 | Architecture::Aarch64(_)),
-                "https://github.com/bytecodealliance/wasmtime/issues/6530"
+                matches!(arch, Architecture::X86_64),
+                "The Winch calling convention is only implemented for x86_64"
             );
             CallConv::Winch
         }


Test patch:
diff --git a/cranelift/filetests/filetests/egraph/spectre.clif b/cranelift/filetests/filetests/egraph/spectre.clif
new file mode 100644
index 000000000..ca3432f61
--- /dev/null
+++ b/cranelift/filetests/filetests/egraph/spectre.clif
@@ -0,0 +1,26 @@
+test optimize
+set opt_level=speed
+target x86_64
+
+function %same_value(i8, i64) -> i64 {
+block0(v0: i8, v1: i64):
+    v2 = select_spectre_guard v0, v1, v1
+    return v2
+}
+; check: return v1
+
+function %const_true(i64, i64) -> i64 {
+block0(v0: i64, v1: i64):
+    v2 = iconst.i8 42
+    v3 = select_spectre_guard v2, v0, v1
+    return v3
+}
+; check: return v0
+
+function %const_false(i64, i64) -> i64 {
+block0(v0: i64, v1: i64):
+    v2 = iconst.i8 0
+    v3 = select_spectre_guard v2, v0, v1
+    return v3
+}
+; check: return v1
diff --git a/cranelift/filetests/filetests/isa/aarch64/winch.clif b/cranelift/filetests/filetests/isa/aarch64/winch.clif
deleted file mode 100644
index 4168e3a7d..000000000
--- a/cranelift/filetests/filetests/isa/aarch64/winch.clif
+++ /dev/null
@@ -1,329 +0,0 @@
-test compile precise-output
-target aarch64
-
-function %f1() winch {
-block0:
-    return
-}
-
-; VCode:
-; block0:
-;   ret
-;
-; Disassembled:
-; block0: ; offset 0x0
-;   ret
-
-function %f2(i64, i64, i64, i64, i64, i64) -> i64 winch {
-  sig0 = () winch
-  fn0 = %g sig0
-
-block0(v0:i64, v1:i64, v2:i64, v3:i64, v4:i64, v5:i64):
-  call fn0()
-  return v0
-}
-
-; VCode:
-;   stp fp, lr, [sp, #-16]!
-;   mov fp, sp
-;   sub sp, sp, #16
-; block0:
-;   str x2, [sp]
-;   load_ext_name x1, TestCase(%g)+0
-;   blr x1
-;   ldr x2, [sp]
-;   add sp, sp, #16
-;   ldp fp, lr, [sp], #16
-;   ret
-;
-; Disassembled:
-; block0: ; offset 0x0
-;   stp x29, x30, [sp, #-0x10]!
-;   mov x29, sp
-;   sub sp, sp, #0x10
-; block1: ; offset 0xc
-;   stur x2, [sp]
-;   ldr x1, #0x18
-;   b #0x20
-;   .byte 0x00, 0x00, 0x00, 0x00 ; reloc_external Abs8 %g 0
-;   .byte 0x00, 0x00, 0x00, 0x00
-;   blr x1
-;   ldur x2, [sp]
-;   add sp, sp, #0x10
-;   ldp x29, x30, [sp], #0x10
-;   ret
-
-function %f3(i64, i64, i64, i64, i64, i64) -> i64 {
-  sig0 = () winch
-  fn0 = %g sig0
-
-block0(v0:i64, v1:i64, v2:i64, v3:i64, v4:i64, v5:i64):
-  call fn0()
-  return v0
-}
-
-; VCode:
-;   stp fp, lr, [sp, #-16]!
-;   mov fp, sp
-;   stp x27, x28, [sp, #-16]!
-;   stp x25, x26, [sp, #-16]!
-;   stp x23, x24, [sp, #-16]!
-;   stp x21, x22, [sp, #-16]!
-;   stp x19, x20, [sp, #-16]!
-;   stp d14, d15, [sp, #-16]!
-;   stp d12, d13, [sp, #-16]!
-;   stp d10, d11, [sp, #-16]!
-;   stp d8, d9, [sp, #-16]!
-;   sub sp, sp, #16
-; block0:
-;   str x0, [sp]
-;   load_ext_name x1, TestCase(%g)+0
-;   blr x1
-;   ldr x0, [sp]
-;   add sp, sp, #16
-;   ldp d8, d9, [sp], #16
-;   ldp d10, d11, [sp], #16
-;   ldp d12, d13, [sp], #16
-;   ldp d14, d15, [sp], #16
-;   ldp x19, x20, [sp], #16
-;   ldp x21, x22, [sp], #16
-;   ldp x23, x24, [sp], #16
-;   ldp x25, x26, [sp], #16
-;   ldp x27, x28, [sp], #16
-;   ldp fp, lr, [sp], #16
-;   ret
-;
-; Disassembled:
-; block0: ; offset 0x0
-;   stp x29, x30, [sp, #-0x10]!
-;   mov x29, sp
-;   stp x27, x28, [sp, #-0x10]!
-;   stp x25, x26, [sp, #-0x10]!
-;   stp x23, x24, [sp, #-0x10]!
-;   stp x21, x22, [sp, #-0x10]!
-;   stp x19, x20, [sp, #-0x10]!
-;   stp d14, d15, [sp, #-0x10]!
-;   stp d12, d13, [sp, #-0x10]!
-;   stp d10, d11, [sp, #-0x10]!
-;   stp d8, d9, [sp, #-0x10]!
-;   sub sp, sp, #0x10
-; block1: ; offset 0x30
-;   stur x0, [sp]
-;   ldr x1, #0x3c
-;   b #0x44
-;   .byte 0x00, 0x00, 0x00, 0x00 ; reloc_external Abs8 %g 0
-;   .byte 0x00, 0x00, 0x00, 0x00
-;   blr x1
-;   ldur x0, [sp]
-;   add sp, sp, #0x10
-;   ldp d8, d9, [sp], #0x10
-;   ldp d10, d11, [sp], #0x10
-;   ldp d12, d13, [sp], #0x10
-;   ldp d14, d15, [sp], #0x10
-;   ldp x19, x20, [sp], #0x10
-;   ldp x21, x22, [sp], #0x10
-;   ldp x23, x24, [sp], #0x10
-;   ldp x25, x26, [sp], #0x10
-;   ldp x27, x28, [sp], #0x10
-;   ldp x29, x30, [sp], #0x10
-;   ret
-
-function %f4(i64, i64, i64, i64, i64, i64) -> i64 winch {
-  sig0 = (i64, i64, i64, i64, i64, i64) -> i64 winch
-  fn0 = %g sig0
-
-block0(v0:i64, v1:i64, v2:i64, v3:i64, v4:i64, v5:i64):
-  v6 = call fn0(v5, v1, v2, v3, v4, v0)
-  return v6
-}
-
-; VCode:
-;   stp fp, lr, [sp, #-16]!
-;   mov fp, sp
-; block0:
-;   mov x0, x2
-;   mov x2, x7
-;   mov x7, x0
-;   load_ext_name x1, TestCase(%g)+0
-;   blr x1
-;   ldp fp, lr, [sp], #16
-;   ret
-;
-; Disassembled:
-; block0: ; offset 0x0
-;   stp x29, x30, [sp, #-0x10]!
-;   mov x29, sp
-; block1: ; offset 0x8
-;   mov x0, x2
-;   mov x2, x7
-;   mov x7, x0
-;   ldr x1, #0x1c
-;   b #0x24
-;   .byte 0x00, 0x00, 0x00, 0x00 ; reloc_external Abs8 %g 0
-;   .byte 0x00, 0x00, 0x00, 0x00
-;   blr x1
-;   ldp x29, x30, [sp], #0x10
-;   ret
-
-function %f5(i64, i64, i64, i64, i64, i64) -> i64 {
-  sig0 = (i64, i64, i64, i64, i64, i64) -> i64 winch
-  fn0 = %g sig0
-
-block0(v0:i64, v1:i64, v2:i64, v3:i64, v4:i64, v5:i64):
-  v6 = call fn0(v5, v1, v2, v3, v4, v0)
-  return v6
-}
-
-; VCode:
-;   stp fp, lr, [sp, #-16]!
-;   mov fp, sp
-;   stp x27, x28, [sp, #-16]!
-;   stp x25, x26, [sp, #-16]!
-;   stp x23, x24, [sp, #-16]!
-;   stp x21, x22, [sp, #-16]!
-;   stp x19, x20, [sp, #-16]!
-;   stp d14, d15, [sp, #-16]!
-;   stp d12, d13, [sp, #-16]!
-;   stp d10, d11, [sp, #-16]!
-;   stp d8, d9, [sp, #-16]!
-; block0:
-;   mov x7, x0
-;   mov x6, x4
-;   mov x4, x2
-;   mov x2, x5
-;   mov x5, x3
-;   mov x3, x1
-;   load_ext_name x1, TestCase(%g)+0
-;   blr x1
-;   mov x0, x2
-;   ldp d8, d9, [sp], #16
-;   ldp d10, d11, [sp], #16
-;   ldp d12, d13, [sp], #16
-;   ldp d14, d15, [sp], #16
-;   ldp x19, x20, [sp], #16
-;   ldp x21, x22, [sp], #16
-;   ldp x23, x24, [sp], #16
-;   ldp x25, x26, [sp], #16
-;   ldp x27, x28, [sp], #16
-;   ldp fp, lr, [sp], #16
-;   ret
-;
-; Disassembled:
-; block0: ; offset 0x0
-;   stp x29, x30, [sp, #-0x10]!
-;   mov x29, sp
-;   stp x27, x28, [sp, #-0x10]!
-;   stp x25, x26, [sp, #-0x10]!
-;   stp x23, x24, [sp, #-0x10]!
-;   stp x21, x22, [sp, #-0x10]!
-;   stp x19, x20, [sp, #-0x10]!
-;   stp d14, d15, [sp, #-0x10]!
-;   stp d12, d13, [sp, #-0x10]!
-;   stp d10, d11, [sp, #-0x10]!
-;   stp d8, d9, [sp, #-0x10]!
-; block1: ; offset 0x2c
-;   mov x7, x0
-;   mov x6, x4
-;   mov x4, x2
-;   mov x2, x5
-;   mov x5, x3
-;   mov x3, x1
-;   ldr x1, #0x4c
-;   b #0x54
-;   .byte 0x00, 0x00, 0x00, 0x00 ; reloc_external Abs8 %g 0
-;   .byte 0x00, 0x00, 0x00, 0x00
-;   blr x1
-;   mov x0, x2
-;   ldp d8, d9, [sp], #0x10
-;   ldp d10, d11, [sp], #0x10
-;   ldp d12, d13, [sp], #0x10
-;   ldp d14, d15, [sp], #0x10
-;   ldp x19, x20, [sp], #0x10
-;   ldp x21, x22, [sp], #0x10
-;   ldp x23, x24, [sp], #0x10
-;   ldp x25, x26, [sp], #0x10
-;   ldp x27, x28, [sp], #0x10
-;   ldp x29, x30, [sp], #0x10
-;   ret
-
-function u1:0() system_v {
-    sig0 = () winch
-    fn0 = u2:0 sig0
-
-block0:
-    v5 = func_addr.i64 fn0
-    call_indirect sig0, v5()
-    call_indirect sig0, v5()
-    return
-}
-
-; VCode:
-;   stp fp, lr, [sp, #-16]!
-;   mov fp, sp
-;   stp x27, x28, [sp, #-16]!
-;   stp x25, x26, [sp, #-16]!
-;   stp x23, x24, [sp, #-16]!
-;   stp x21, x22, [sp, #-16]!
-;   stp x19, x20, [sp, #-16]!
-;   stp d14, d15, [sp, #-16]!
-;   stp d12, d13, [sp, #-16]!
-;   stp d10, d11, [sp, #-16]!
-;   stp d8, d9, [sp, #-16]!
-;   sub sp, sp, #16
-; block0:
-;   load_ext_name x1, User(userextname0)+0
-;   str x1, [sp]
-;   ldr x1, [sp]
-;   blr x1
-;   ldr x1, [sp]
-;   blr x1
-;   add sp, sp, #16
-;   ldp d8, d9, [sp], #16
-;   ldp d10, d11, [sp], #16
-;   ldp d12, d13, [sp], #16
-;   ldp d14, d15, [sp], #16
-;   ldp x19, x20, [sp], #16
-;   ldp x21, x22, [sp], #16
-;   ldp x23, x24, [sp], #16
-;   ldp x25, x26, [sp], #16
-;   ldp x27, x28, [sp], #16
-;   ldp fp, lr, [sp], #16
-;   ret
-;
-; Disassembled:
-; block0: ; offset 0x0
-;   stp x29, x30, [sp, #-0x10]!
-;   mov x29, sp
-;   stp x27, x28, [sp, #-0x10]!
-;   stp x25, x26, [sp, #-0x10]!
-;   stp x23, x24, [sp, #-0x10]!
-;   stp x21, x22, [sp, #-0x10]!
-;   stp x19, x20, [sp, #-0x10]!
-;   stp d14, d15, [sp, #-0x10]!
-;   stp d12, d13, [sp, #-0x10]!
-;   stp d10, d11, [sp, #-0x10]!
-;   stp d8, d9, [sp, #-0x10]!
-;   sub sp, sp, #0x10
-; block1: ; offset 0x30
-;   ldr x1, #0x38
-;   b #0x40
-;   .byte 0x00, 0x00, 0x00, 0x00 ; reloc_external Abs8 u2:0 0
-;   .byte 0x00, 0x00, 0x00, 0x00
-;   stur x1, [sp]
-;   ldur x1, [sp]
-;   blr x1
-;   ldur x1, [sp]
-;   blr x1
-;   add sp, sp, #0x10
-;   ldp d8, d9, [sp], #0x10
-;   ldp d10, d11, [sp], #0x10
-;   ldp d12, d13, [sp], #0x10
-;   ldp d14, d15, [sp], #0x10
-;   ldp x19, x20, [sp], #0x10
-;   ldp x21, x22, [sp], #0x10
-;   ldp x23, x24, [sp], #0x10
-;   ldp x25, x26, [sp], #0x10
-;   ldp x27, x28, [sp], #0x10
-;   ldp x29, x30, [sp], #0x10
-;   ret
-
diff --git a/cranelift/filetests/filetests/isa/x64/winch.clif b/cranelift/filetests/filetests/isa/x64/winch.clif
index e1845d9f9..82cf18e39 100644
--- a/cranelift/filetests/filetests/isa/x64/winch.clif
+++ b/cranelift/filetests/filetests/isa/x64/winch.clif
@@ -279,3 +279,102 @@ block0:
 ;   popq %rbp
 ;   retq
 
+function %f6(i64) -> i32 {
+  sig0 = () -> i32, i32, f64 winch
+  fn0 = %g sig0
+
+block0(v0:i64):
+  v1, v2, v3 = call fn0()
+  v4 = band.i32 v1, v2
+  return v4
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+;   subq    %rsp, $48, %rsp
+;   movq    %rbx, 0(%rsp)
+;   movq    %r12, 8(%rsp)
+;   movq    %r13, 16(%rsp)
+;   movq    %r14, 24(%rsp)
+;   movq    %r15, 32(%rsp)
+; block0:
+;   subq    %rsp, $16, %rsp
+;   virtual_sp_offset_adjust 16
+;   lea     0(%rsp), %rdi
+;   load_ext_name %g+0, %r15
+;   call    *%r15
+;   movq    4(%rsp), %rax
+;   movq    0(%rsp), %rdi
+;   addq    %rsp, $16, %rsp
+;   virtual_sp_offset_adjust -16
+;   andl    %eax, %edi, %eax
+;   movq    0(%rsp), %rbx
+;   movq    8(%rsp), %r12
+;   movq    16(%rsp), %r13
+;   movq    24(%rsp), %r14
+;   movq    32(%rsp), %r15
+;   addq    %rsp, $48, %rsp
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+;   subq $0x30, %rsp
+;   movq %rbx, (%rsp)
+;   movq %r12, 8(%rsp)
+;   movq %r13, 0x10(%rsp)
+;   movq %r14, 0x18(%rsp)
+;   movq %r15, 0x20(%rsp)
+; block1: ; offset 0x20
+;   subq $0x10, %rsp
+;   leaq (%rsp), %rdi
+;   movabsq $0, %r15 ; reloc_external Abs8 %g 0
+;   callq *%r15
+;   movq 4(%rsp), %rax
+;   movq (%rsp), %rdi
+;   addq $0x10, %rsp
+;   andl %edi, %eax
+;   movq (%rsp), %rbx
+;   movq 8(%rsp), %r12
+;   movq 0x10(%rsp), %r13
+;   movq 0x18(%rsp), %r14
+;   movq 0x20(%rsp), %r15
+;   addq $0x30, %rsp
+;   movq %rbp, %rsp
+;   popq %rbp
+;   retq
+
+function %reverse_args(i32, i64, i32, i64) -> i64, i32, i64, i32 winch {
+block0(v0: i32, v1: i64, v2: i32, v3: i64):
+    return v3, v2, v1, v0
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   movq    %rdi, %rax
+;   movq    %rcx, 12(%r8)
+;   movl    %edx, 8(%r8)
+;   movq    %rsi, 0(%r8)
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   movq %rdi, %rax
+;   movq %rcx, 0xc(%r8)
+;   movl %edx, 8(%r8)
+;   movq %rsi, (%r8)
+;   movq %rbp, %rsp
+;   popq %rbp
+;   retq
+
diff --git a/cranelift/filetests/filetests/runtests/winch.clif b/cranelift/filetests/filetests/runtests/winch.clif
new file mode 100644
index 000000000..9985ff62b
--- /dev/null
+++ b/cranelift/filetests/filetests/runtests/winch.clif
@@ -0,0 +1,21 @@
+test run
+target x86_64
+
+function %reverse_args(i32, i64, i32, i64) -> i64, i32, i64, i32 winch {
+block0(v0: i32, v1: i64, v2: i32, v3: i64):
+    return v3, v2, v1, v0
+}
+
+function %call_winch() -> i64, i32, i64, i32 {
+    fn0 = %reverse_args(i32, i64, i32, i64) -> i64, i32, i64, i32 winch
+
+block0:
+    v0 = iconst.i32 0
+    v1 = iconst.i64 1
+    v2 = iconst.i32 2
+    v3 = iconst.i64 3
+    v4, v5, v6, v7 = call fn0(v0, v1, v2, v3)
+    return v4, v5, v6, v7
+}
+
+; run: %call_winch() == [3, 2, 1, 0]
diff --git a/tests/disas/typed-funcrefs.wat b/tests/disas/typed-funcrefs.wat
index 10ba6a955..8bf6beb37 100644
--- a/tests/disas/typed-funcrefs.wat
+++ b/tests/disas/typed-funcrefs.wat
@@ -151,12 +151,9 @@
 ;;                                     v32 -> v4
 ;;                                     v33 -> v5
 ;; @0048                               v12 = load.i64 notrap aligned v0+72
-;;                                     v62 = iconst.i8 0
-;; @0048                               v15 = iconst.i64 0
 ;;                                     v70 = iconst.i64 8
 ;; @0048                               v14 = iadd v12, v70  ; v70 = 8
-;; @0048                               v16 = select_spectre_guard v62, v15, v14  ; v62 = 0, v15 = 0
-;; @0048                               v17 = load.i64 table_oob aligned table v16
+;; @0048                               v17 = load.i64 table_oob aligned table v14
 ;;                                     v58 = iconst.i64 -2
 ;; @0048                               v18 = band v17, v58  ; v58 = -2
 ;; @0048                               brif v17, block3(v18), block2
@@ -175,22 +172,19 @@
 ;; @004a                               v26 = load.i64 notrap aligned readonly v19+32
 ;; @004a                               v27 = call_indirect sig1, v25(v26, v0, v2, v3, v4, v5)
 ;; @005b                               v38 = load.i64 notrap aligned v0+72
-;;                                     v79 = iconst.i8 0
-;;                                     v80 = iconst.i64 0
 ;;                                     v78 = iconst.i64 16
 ;; @005b                               v40 = iadd v38, v78  ; v78 = 16
-;; @005b                               v42 = select_spectre_guard v79, v80, v40  ; v79 = 0, v80 = 0
-;; @005b                               v43 = load.i64 table_oob aligned table v42
-;;                                     v81 = iconst.i64 -2
-;;                                     v82 = band v43, v81  ; v81 = -2
-;; @005b                               brif v43, block5(v82), block4
+;; @005b                               v43 = load.i64 table_oob aligned table v40
+;;                                     v79 = iconst.i64 -2
+;;                                     v80 = band v43, v79  ; v79 = -2
+;; @005b                               brif v43, block5(v80), block4
 ;;
 ;;                                 block4 cold:
-;;                                     v83 = load.i64 notrap aligned readonly v0+56
-;;                                     v84 = load.i64 notrap aligned readonly v83+72
-;;                                     v85 = iconst.i32 0
+;;                                     v81 = load.i64 notrap aligned readonly v0+56
+;;                                     v82 = load.i64 notrap aligned readonly v81+72
+;;                                     v83 = iconst.i32 0
 ;; @0059                               v34 = iconst.i32 2
-;; @005b                               v50 = call_indirect sig0, v84(v0, v85, v34)  ; v85 = 0, v34 = 2
+;; @005b                               v50 = call_indirect sig0, v82(v0, v83, v34)  ; v83 = 0, v34 = 2
 ;; @005b                               jump block5(v50)
 ;;
 ;;                                 block5(v45: i64):
@@ -227,12 +221,9 @@
 ;;                                     v32 -> v4
 ;;                                     v33 -> v5
 ;; @0075                               v12 = load.i64 notrap aligned v0+72
-;;                                     v62 = iconst.i8 0
-;; @0075                               v15 = iconst.i64 0
 ;;                                     v70 = iconst.i64 8
 ;; @0075                               v14 = iadd v12, v70  ; v70 = 8
-;; @0075                               v16 = select_spectre_guard v62, v15, v14  ; v62 = 0, v15 = 0
-;; @0075                               v17 = load.i64 table_oob aligned table v16
+;; @0075                               v17 = load.i64 table_oob aligned table v14
 ;;                                     v58 = iconst.i64 -2
 ;; @0075                               v18 = band v17, v58  ; v58 = -2
 ;; @0075                               brif v17, block3(v18), block2
@@ -251,22 +242,19 @@
 ;; @0075                               v26 = load.i64 notrap aligned readonly v19+32
 ;; @0075                               v27 = call_indirect sig0, v25(v26, v0, v2, v3, v4, v5)
 ;; @0087                               v38 = load.i64 notrap aligned v0+72
-;;                                     v79 = iconst.i8 0
-;;                                     v80 = iconst.i64 0
 ;;                                     v78 = iconst.i64 16
 ;; @0087                               v40 = iadd v38, v78  ; v78 = 16
-;; @0087                               v42 = select_spectre_guard v79, v80, v40  ; v79 = 0, v80 = 0
-;; @0087                               v43 = load.i64 table_oob aligned table v42
-;;                                     v81 = iconst.i64 -2
-;;                                     v82 = band v43, v81  ; v81 = -2
-;; @0087                               brif v43, block5(v82), block4
+;; @0087                               v43 = load.i64 table_oob aligned table v40
+;;                                     v79 = iconst.i64 -2
+;;                                     v80 = band v43, v79  ; v79 = -2
+;; @0087                               brif v43, block5(v80), block4
 ;;
 ;;                                 block4 cold:
-;;                                     v83 = load.i64 notrap aligned readonly v0+56
-;;                                     v84 = load.i64 notrap aligned readonly v83+72
-;;                                     v85 = iconst.i32 0
+;;                                     v81 = load.i64 notrap aligned readonly v0+56
+;;                                     v82 = load.i64 notrap aligned readonly v81+72
+;;                                     v83 = iconst.i32 0
 ;; @0085                               v34 = iconst.i32 2
-;; @0087                               v50 = call_indirect sig1, v84(v0, v85, v34)  ; v85 = 0, v34 = 2
+;; @0087                               v50 = call_indirect sig1, v82(v0, v83, v34)  ; v83 = 0, v34 = 2
 ;; @0087                               jump block5(v50)
 ;;
 ;;                                 block5(v45: i64):