Rollup merge of #153361 - folkertdev:tail-call-indirect-on-stack-true, r=WaffleLapkin

enable `PassMode::Indirect { on_stack: true, .. }` tail call arguments

tracking issue: https://github.com/rust-lang/rust/issues/112788
fixes https://github.com/rust-lang/rust/issues/144855

And add a bunch of tests for tail call target support.

r? WaffleLapkin
This commit is contained in:
Jonathan Brouwer
2026-03-05 06:31:36 +01:00
committed by GitHub
4 changed files with 341 additions and 46 deletions
+28 -46
View File
@@ -1256,55 +1256,37 @@ fn codegen_call_terminator(
}
}
CallKind::Tail => {
match fn_abi.args[i].mode {
PassMode::Indirect { on_stack: false, .. } => {
let Some(tmp) = tail_call_temporaries[i].take() else {
span_bug!(
fn_span,
"missing temporary for indirect tail call argument #{i}"
)
};
let local = self.mir.args_iter().nth(i).unwrap();
match &self.locals[local] {
LocalRef::Place(arg) => {
bx.typed_place_copy(arg.val, tmp.val, fn_abi.args[i].layout);
op.val = Ref(arg.val);
}
LocalRef::Operand(arg) => {
let Ref(place_value) = arg.val else {
bug!("only `Ref` should use `PassMode::Indirect`");
};
bx.typed_place_copy(
place_value,
tmp.val,
fn_abi.args[i].layout,
);
op.val = arg.val;
}
LocalRef::UnsizedPlace(_) => {
span_bug!(fn_span, "unsized types are not supported")
}
LocalRef::PendingOperand => {
span_bug!(fn_span, "argument local should not be pending")
}
};
bx.lifetime_end(tmp.val.llval, tmp.layout.size);
}
PassMode::Indirect { on_stack: true, .. } => {
// FIXME: some LLVM backends (notably x86) do not correctly pass byval
// arguments to tail calls (as of LLVM 21). See also:
//
// - https://github.com/rust-lang/rust/pull/144232#discussion_r2218543841
// - https://github.com/rust-lang/rust/issues/144855
if let PassMode::Indirect { on_stack: false, .. } = fn_abi.args[i].mode {
let Some(tmp) = tail_call_temporaries[i].take() else {
span_bug!(
fn_span,
"arguments using PassMode::Indirect {{ on_stack: true, .. }} are currently not supported for tail calls"
"missing temporary for indirect tail call argument #{i}"
)
}
_ => (),
};
let local = self.mir.args_iter().nth(i).unwrap();
match &self.locals[local] {
LocalRef::Place(arg) => {
bx.typed_place_copy(arg.val, tmp.val, fn_abi.args[i].layout);
op.val = Ref(arg.val);
}
LocalRef::Operand(arg) => {
let Ref(place_value) = arg.val else {
bug!("only `Ref` should use `PassMode::Indirect`");
};
bx.typed_place_copy(place_value, tmp.val, fn_abi.args[i].layout);
op.val = arg.val;
}
LocalRef::UnsizedPlace(_) => {
span_bug!(fn_span, "unsized types are not supported")
}
LocalRef::PendingOperand => {
span_bug!(fn_span, "argument local should not be pending")
}
};
bx.lifetime_end(tmp.val.llval, tmp.layout.size);
}
}
}
@@ -0,0 +1,97 @@
//@ build-pass
//@ ignore-backends: gcc
//@ add-minicore
//@ min-llvm-version: 22
//
//@ revisions: i686
//@[i686] compile-flags: --target i686-unknown-linux-gnu
//@[i686] needs-llvm-components: x86
//@ revisions: x86-64
//@[x86-64] compile-flags: --target x86_64-unknown-linux-gnu
//@[x86-64] needs-llvm-components: x86
//@ revisions: x86-64-win
//@[x86-64-win] compile-flags: --target x86_64-pc-windows-msvc
//@[x86-64-win] needs-llvm-components: x86
//@ revisions: arm
//@[arm] compile-flags: --target arm-unknown-linux-gnueabi
//@[arm] needs-llvm-components: arm
//@ revisions: thumb
//@[thumb] compile-flags: --target thumbv8m.main-none-eabi
//@[thumb] needs-llvm-components: arm
//@ revisions: aarch64
//@[aarch64] compile-flags: --target aarch64-unknown-linux-gnu
//@[aarch64] needs-llvm-components: aarch64
//@ revisions: s390x
//@[s390x] compile-flags: --target s390x-unknown-linux-gnu
//@[s390x] needs-llvm-components: systemz
//@ revisions: sparc
//@[sparc] compile-flags: --target sparc-unknown-linux-gnu
//@[sparc] needs-llvm-components: sparc
//@ revisions: sparc64
//@[sparc64] compile-flags: --target sparc64-unknown-linux-gnu
//@[sparc64] needs-llvm-components: sparc
//@ revisions: powerpc64
//@[powerpc64] compile-flags: --target powerpc64-unknown-linux-gnu
//@[powerpc64] needs-llvm-components: powerpc
//@ revisions: riscv
//@[riscv] compile-flags: --target riscv64gc-unknown-linux-gnu
//@[riscv] needs-llvm-components: riscv
//@ revisions: loongarch32
//@[loongarch32] compile-flags: --target loongarch32-unknown-none
//@[loongarch32] needs-llvm-components: loongarch
//@ revisions: loongarch64
//@[loongarch64] compile-flags: --target loongarch64-unknown-linux-gnu
//@[loongarch64] needs-llvm-components: loongarch
//@ revisions: bpf
//@[bpf] compile-flags: --target bpfeb-unknown-none
//@[bpf] needs-llvm-components: bpf
//@ revisions: m68k
//@[m68k] compile-flags: --target m68k-unknown-linux-gnu
//@[m68k] needs-llvm-components: m68k
//@ revisions: nvptx64
//@[nvptx64] compile-flags: --target nvptx64-nvidia-cuda
//@[nvptx64] needs-llvm-components: nvptx
//
// Wasm needs a special target feature.
//
//@ revisions: wasm
//@[wasm] compile-flags: --target wasm32-unknown-unknown -Ctarget-feature=+tail-call
//@[wasm] needs-llvm-components: webassembly
//@ revisions: wasip1
//@[wasip1] compile-flags: --target wasm32-wasip1 -Ctarget-feature=+tail-call
//@[wasip1] needs-llvm-components: webassembly
//
// Failing cases (just zero support)
//
// //@ revisions: powerpc
// //@[powerpc] compile-flags: --target powerpc-unknown-linux-gnu
// //@[powerpc] needs-llvm-components: powerpc
// //@ revisions: aix
// //@[aix] compile-flags: --target powerpc64-ibm-aix
// //@[aix] needs-llvm-components: powerpc
// //@ revisions: csky
// //@[csky] compile-flags: --target csky-unknown-linux-gnuabiv2
// //@[csky] needs-llvm-components: csky
// //@ revisions: mips
// //@[mips] compile-flags: --target mips-unknown-linux-gnu
// //@[mips] needs-llvm-components: mips
// //@ revisions: mips64
// //@[mips64] compile-flags: --target mips64-unknown-linux-gnuabi64
// //@[mips64] needs-llvm-components: mips
#![feature(no_core, explicit_tail_calls)]
#![expect(incomplete_features)]
#![no_core]
#![crate_type = "lib"]
extern crate minicore;
use minicore::*;
#[inline(never)]
fn simple1(x: u64) -> u64 {
x
}
#[unsafe(no_mangle)]
fn simple2(x: u64) -> u64 {
become simple1(x);
}
@@ -0,0 +1,108 @@
//@ build-pass
//@ ignore-backends: gcc
//@ add-minicore
//@ min-llvm-version: 22
//
//@ revisions: i686
//@[i686] compile-flags: --target i686-unknown-linux-gnu
//@[i686] needs-llvm-components: x86
//@ revisions: x86-64
//@[x86-64] compile-flags: --target x86_64-unknown-linux-gnu
//@[x86-64] needs-llvm-components: x86
//@ revisions: x86-64-win
//@[x86-64-win] compile-flags: --target x86_64-pc-windows-msvc
//@[x86-64-win] needs-llvm-components: x86
//@ revisions: arm
//@[arm] compile-flags: --target arm-unknown-linux-gnueabi
//@[arm] needs-llvm-components: arm
//@ revisions: thumb
//@[thumb] compile-flags: --target thumbv8m.main-none-eabi
//@[thumb] needs-llvm-components: arm
//@ revisions: aarch64
//@[aarch64] compile-flags: --target aarch64-unknown-linux-gnu
//@[aarch64] needs-llvm-components: aarch64
//@ revisions: s390x
//@[s390x] compile-flags: --target s390x-unknown-linux-gnu
//@[s390x] needs-llvm-components: systemz
//@ revisions: sparc
//@[sparc] compile-flags: --target sparc-unknown-linux-gnu
//@[sparc] needs-llvm-components: sparc
//@ revisions: sparc64
//@[sparc64] compile-flags: --target sparc64-unknown-linux-gnu
//@[sparc64] needs-llvm-components: sparc
//@ revisions: powerpc64
//@[powerpc64] compile-flags: --target powerpc64-unknown-linux-gnu
//@[powerpc64] needs-llvm-components: powerpc
//@ revisions: loongarch32
//@[loongarch32] compile-flags: --target loongarch32-unknown-none
//@[loongarch32] needs-llvm-components: loongarch
//@ revisions: loongarch64
//@[loongarch64] compile-flags: --target loongarch64-unknown-linux-gnu
//@[loongarch64] needs-llvm-components: loongarch
//@ revisions: bpf
//@[bpf] compile-flags: --target bpfeb-unknown-none
//@[bpf] needs-llvm-components: bpf
//@ revisions: m68k
//@[m68k] compile-flags: --target m68k-unknown-linux-gnu
//@[m68k] needs-llvm-components: m68k
//@ revisions: nvptx64
//@[nvptx64] compile-flags: --target nvptx64-nvidia-cuda
//@[nvptx64] needs-llvm-components: nvptx
//
// Riscv does not support byval in LLVM 22 (but wil in LLVM 23)
//
// //@ revisions: riscv
// //@[riscv] compile-flags: --target riscv64gc-unknown-linux-gnu
// //@[riscv] needs-llvm-components: riscv
//
// Wasm needs a special target feature.
//
//@ revisions: wasm
//@[wasm] compile-flags: --target wasm32-unknown-unknown -Ctarget-feature=+tail-call
//@[wasm] needs-llvm-components: webassembly
//@ revisions: wasip1
//@[wasip1] compile-flags: --target wasm32-wasip1 -Ctarget-feature=+tail-call
//@[wasip1] needs-llvm-components: webassembly
//
// Failing cases (just zero support)
//
// //@ revisions: powerpc
// //@[powerpc] compile-flags: --target powerpc-unknown-linux-gnu
// //@[powerpc] needs-llvm-components: powerpc
// //@ revisions: aix
// //@[aix] compile-flags: --target powerpc64-ibm-aix
// //@[aix] needs-llvm-components: powerpc
// //@ revisions: csky
// //@[csky] compile-flags: --target csky-unknown-linux-gnuabiv2
// //@[csky] needs-llvm-components: csky
// //@ revisions: mips
// //@[mips] compile-flags: --target mips-unknown-linux-gnu
// //@[mips] needs-llvm-components: mips
// //@ revisions: mips64
// //@[mips64] compile-flags: --target mips64-unknown-linux-gnuabi64
// //@[mips64] needs-llvm-components: mips
#![feature(no_core, explicit_tail_calls)]
#![expect(incomplete_features)]
#![no_core]
#![crate_type = "lib"]
extern crate minicore;
use minicore::*;
#[repr(C)]
struct PassedByVal {
a: u64,
b: u64,
c: u64,
d: u64,
}
#[inline(never)]
extern "C" fn callee(x: PassedByVal) -> PassedByVal {
x
}
#[unsafe(no_mangle)]
extern "C" fn byval(x: PassedByVal) -> PassedByVal {
become callee(x);
}
@@ -0,0 +1,108 @@
//@ build-pass
//@ ignore-backends: gcc
//@ add-minicore
//@ min-llvm-version: 22
//
//@ revisions: i686
//@[i686] compile-flags: --target i686-unknown-linux-gnu
//@[i686] needs-llvm-components: x86
//@ revisions: x86-64
//@[x86-64] compile-flags: --target x86_64-unknown-linux-gnu
//@[x86-64] needs-llvm-components: x86
//@ revisions: x86-64-win
//@[x86-64-win] compile-flags: --target x86_64-pc-windows-msvc
//@[x86-64-win] needs-llvm-components: x86
//@ revisions: arm
//@[arm] compile-flags: --target arm-unknown-linux-gnueabi
//@[arm] needs-llvm-components: arm
//@ revisions: thumb
//@[thumb] compile-flags: --target thumbv8m.main-none-eabi
//@[thumb] needs-llvm-components: arm
//@ revisions: aarch64
//@[aarch64] compile-flags: --target aarch64-unknown-linux-gnu
//@[aarch64] needs-llvm-components: aarch64
//@ revisions: s390x
//@[s390x] compile-flags: --target s390x-unknown-linux-gnu
//@[s390x] needs-llvm-components: systemz
//@ revisions: sparc
//@[sparc] compile-flags: --target sparc-unknown-linux-gnu
//@[sparc] needs-llvm-components: sparc
//@ revisions: sparc64
//@[sparc64] compile-flags: --target sparc64-unknown-linux-gnu
//@[sparc64] needs-llvm-components: sparc
//@ revisions: powerpc64
//@[powerpc64] compile-flags: --target powerpc64-unknown-linux-gnu
//@[powerpc64] needs-llvm-components: powerpc
//@ revisions: loongarch32
//@[loongarch32] compile-flags: --target loongarch32-unknown-none
//@[loongarch32] needs-llvm-components: loongarch
//@ revisions: loongarch64
//@[loongarch64] compile-flags: --target loongarch64-unknown-linux-gnu
//@[loongarch64] needs-llvm-components: loongarch
//@ revisions: bpf
//@[bpf] compile-flags: --target bpfeb-unknown-none
//@[bpf] needs-llvm-components: bpf
//@ revisions: m68k
//@[m68k] compile-flags: --target m68k-unknown-linux-gnu
//@[m68k] needs-llvm-components: m68k
//@ revisions: nvptx64
//@[nvptx64] compile-flags: --target nvptx64-nvidia-cuda
//@[nvptx64] needs-llvm-components: nvptx
//
// Riscv does not support byval in LLVM 22 (but wil in LLVM 23)
//
// //@ revisions: riscv
// //@[riscv] compile-flags: --target riscv64gc-unknown-linux-gnu
// //@[riscv] needs-llvm-components: riscv
//
// Wasm needs a special target feature.
//
//@ revisions: wasm
//@[wasm] compile-flags: --target wasm32-unknown-unknown -Ctarget-feature=+tail-call
//@[wasm] needs-llvm-components: webassembly
//@ revisions: wasip1
//@[wasip1] compile-flags: --target wasm32-wasip1 -Ctarget-feature=+tail-call
//@[wasip1] needs-llvm-components: webassembly
//
// Failing cases (just zero support)
//
// //@ revisions: powerpc
// //@[powerpc] compile-flags: --target powerpc-unknown-linux-gnu
// //@[powerpc] needs-llvm-components: powerpc
// //@ revisions: aix
// //@[aix] compile-flags: --target powerpc64-ibm-aix
// //@[aix] needs-llvm-components: powerpc
// //@ revisions: csky
// //@[csky] compile-flags: --target csky-unknown-linux-gnuabiv2
// //@[csky] needs-llvm-components: csky
// //@ revisions: mips
// //@[mips] compile-flags: --target mips-unknown-linux-gnu
// //@[mips] needs-llvm-components: mips
// //@ revisions: mips64
// //@[mips64] compile-flags: --target mips64-unknown-linux-gnuabi64
// //@[mips64] needs-llvm-components: mips
#![feature(no_core, explicit_tail_calls)]
#![expect(incomplete_features)]
#![no_core]
#![crate_type = "lib"]
extern crate minicore;
use minicore::*;
// The rust calling convention will pass this by-value.
struct PassedByVal {
a: u64,
b: u64,
c: u64,
d: u64,
}
#[inline(never)]
fn callee(x: PassedByVal) -> PassedByVal {
x
}
#[unsafe(no_mangle)]
fn byval(x: PassedByVal) -> PassedByVal {
become callee(x);
}