blob: 2bc1743a9bafd4d8a9fba4ada67bc21a4772a5bc [file] [edit]
//@ add-minicore
//@ min-llvm-version: 22
//@ assembly-output: emit-asm
//@ needs-llvm-components: x86
//@ compile-flags: --target=x86_64-unknown-linux-gnu
//@ compile-flags: -Copt-level=3 -C llvm-args=-x86-asm-syntax=intel
#![feature(no_core, explicit_tail_calls)]
#![expect(incomplete_features)]
#![no_core]
#![crate_type = "lib"]
// Test tail calls with `PassMode::Indirect { on_stack: false, .. }` arguments.
//
// Normally an indirect argument with `on_stack: false` would be passed as a pointer to the
// caller's stack frame. For tail calls, that would be unsound, because the caller's stack
// frame is overwritten by the callee's stack frame.
//
// The solution is to write the argument into the caller's argument place (stored somewhere further
// up the stack), and forward that place.
extern crate minicore;
use minicore::*;
#[repr(C)]
struct S {
x: u64,
y: u64,
z: u64,
}
unsafe extern "C" {
safe fn force_usage(_: u64, _: u64, _: u64) -> u64;
}
// CHECK-LABEL: callee:
// CHECK-NEXT: .cfi_startproc
//
// CHECK-NEXT: mov rax, qword ptr [rdi]
// CHECK-NEXT: mov rsi, qword ptr [rdi + 8]
// CHECK-NEXT: mov rdx, qword ptr [rdi + 16]
// CHECK-NEXT: mov rdi, rax
//
// CHECK-NEXT: jmp qword ptr [rip + force_usage@GOTPCREL]
#[inline(never)]
#[unsafe(no_mangle)]
fn callee(s: S) -> u64 {
force_usage(s.x, s.y, s.z)
}
// CHECK-LABEL: caller1:
// CHECK-NEXT: .cfi_startproc
//
// Just forward the argument:
//
// CHECK-NEXT: jmp qword ptr [rip + callee@GOTPCREL]
#[unsafe(no_mangle)]
fn caller1(s: S) -> u64 {
become callee(s);
}
// CHECK-LABEL: caller2:
// CHECK-NEXT: .cfi_startproc
//
// Construct the S value directly into the argument slot:
//
// CHECK-NEXT: mov qword ptr [rdi], 1
// CHECK-NEXT: mov qword ptr [rdi + 8], 2
// CHECK-NEXT: mov qword ptr [rdi + 16], 3
//
// CHECK-NEXT: jmp qword ptr [rip + callee@GOTPCREL]
#[unsafe(no_mangle)]
fn caller2(_: S) -> u64 {
let s = S { x: 1, y: 2, z: 3 };
become callee(s);
}