Skip to content

Commit cee3373

Browse files
authored
Rollup merge of rust-lang#144232 - xacrimon:explicit-tail-call, r=oli-obk
Implement support for `become` and explicit tail call codegen for the LLVM backend This PR implements codegen of explicit tail calls via `become` in `rustc_codegen_ssa` and support within the LLVM backend. Completes a task on (rust-lang#112788). This PR implements all the necessary bits to make explicit tail calls usable, other backends have received stubs for now and will ICE if you use `become` on them. I suspect there is some bikeshedding to be done on how we should go about implementing this for other backends, but it should be relatively straightforward for GCC after this is merged. During development I also put together a POC bytecode VM based on tail call dispatch to test these changes out and analyze the codegen to make sure it generates expected assembly. That is available [here](https://github.com/xacrimon/tcvm).
2 parents 477c1ee + ecf34e1 commit cee3373

File tree

9 files changed

+191
-12
lines changed

9 files changed

+191
-12
lines changed

compiler/rustc_codegen_gcc/src/builder.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1742,6 +1742,21 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> {
17421742
call
17431743
}
17441744

1745+
fn tail_call(
1746+
&mut self,
1747+
_llty: Self::Type,
1748+
_fn_attrs: Option<&CodegenFnAttrs>,
1749+
_fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
1750+
_llfn: Self::Value,
1751+
_args: &[Self::Value],
1752+
_funclet: Option<&Self::Funclet>,
1753+
_instance: Option<Instance<'tcx>>,
1754+
) {
1755+
bug!(
1756+
"Guaranteed tail calls with the 'become' keyword are not implemented in the GCC backend"
1757+
);
1758+
}
1759+
17451760
fn zext(&mut self, value: RValue<'gcc>, dest_typ: Type<'gcc>) -> RValue<'gcc> {
17461761
// FIXME(antoyo): this does not zero-extend.
17471762
self.gcc_int_cast(value, dest_typ)

compiler/rustc_codegen_llvm/src/builder.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use rustc_codegen_ssa::mir::place::PlaceRef;
1515
use rustc_codegen_ssa::traits::*;
1616
use rustc_data_structures::small_c_str::SmallCStr;
1717
use rustc_hir::def_id::DefId;
18+
use rustc_middle::bug;
1819
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrs;
1920
use rustc_middle::ty::layout::{
2021
FnAbiError, FnAbiOfHelpers, FnAbiRequest, HasTypingEnv, LayoutError, LayoutOfHelpers,
@@ -24,7 +25,7 @@ use rustc_middle::ty::{self, Instance, Ty, TyCtxt};
2425
use rustc_sanitizers::{cfi, kcfi};
2526
use rustc_session::config::OptLevel;
2627
use rustc_span::Span;
27-
use rustc_target::callconv::FnAbi;
28+
use rustc_target::callconv::{FnAbi, PassMode};
2829
use rustc_target::spec::{HasTargetSpec, SanitizerSet, Target};
2930
use smallvec::SmallVec;
3031
use tracing::{debug, instrument};
@@ -1431,6 +1432,29 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
14311432
call
14321433
}
14331434

1435+
fn tail_call(
1436+
&mut self,
1437+
llty: Self::Type,
1438+
fn_attrs: Option<&CodegenFnAttrs>,
1439+
fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
1440+
llfn: Self::Value,
1441+
args: &[Self::Value],
1442+
funclet: Option<&Self::Funclet>,
1443+
instance: Option<Instance<'tcx>>,
1444+
) {
1445+
let call = self.call(llty, fn_attrs, Some(fn_abi), llfn, args, funclet, instance);
1446+
1447+
match &fn_abi.ret.mode {
1448+
PassMode::Ignore | PassMode::Indirect { .. } => self.ret_void(),
1449+
PassMode::Direct(_) | PassMode::Pair { .. } => self.ret(call),
1450+
mode @ PassMode::Cast { .. } => {
1451+
bug!("Encountered `PassMode::{mode:?}` during codegen")
1452+
}
1453+
}
1454+
1455+
llvm::LLVMRustSetTailCallKind(call, llvm::TailCallKind::MustTail);
1456+
}
1457+
14341458
fn zext(&mut self, val: &'ll Value, dest_ty: &'ll Type) -> &'ll Value {
14351459
unsafe { llvm::LLVMBuildZExt(self.llbuilder, val, dest_ty, UNNAMED) }
14361460
}

compiler/rustc_codegen_llvm/src/llvm/ffi.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,16 @@ pub(crate) enum ModuleFlagMergeBehavior {
9797

9898
// Consts for the LLVM CallConv type, pre-cast to usize.
9999

100+
#[derive(Copy, Clone, PartialEq, Debug)]
101+
#[repr(C)]
102+
#[allow(dead_code)]
103+
pub(crate) enum TailCallKind {
104+
None = 0,
105+
Tail = 1,
106+
MustTail = 2,
107+
NoTail = 3,
108+
}
109+
100110
/// LLVM CallingConv::ID. Should we wrap this?
101111
///
102112
/// See <https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/IR/CallingConv.h>
@@ -1186,6 +1196,7 @@ unsafe extern "C" {
11861196
pub(crate) safe fn LLVMIsGlobalConstant(GlobalVar: &Value) -> Bool;
11871197
pub(crate) safe fn LLVMSetGlobalConstant(GlobalVar: &Value, IsConstant: Bool);
11881198
pub(crate) safe fn LLVMSetTailCall(CallInst: &Value, IsTailCall: Bool);
1199+
pub(crate) safe fn LLVMRustSetTailCallKind(CallInst: &Value, Kind: TailCallKind);
11891200

11901201
// Operations on attributes
11911202
pub(crate) fn LLVMCreateStringAttribute(

compiler/rustc_codegen_ssa/src/mir/block.rs

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ impl<'a, 'tcx> TerminatorCodegenHelper<'tcx> {
160160
mut unwind: mir::UnwindAction,
161161
lifetime_ends_after_call: &[(Bx::Value, Size)],
162162
instance: Option<Instance<'tcx>>,
163+
tail: bool,
163164
mergeable_succ: bool,
164165
) -> MergingSucc {
165166
let tcx = bx.tcx();
@@ -221,6 +222,11 @@ impl<'a, 'tcx> TerminatorCodegenHelper<'tcx> {
221222
}
222223
};
223224

225+
if tail {
226+
bx.tail_call(fn_ty, fn_attrs, fn_abi, fn_ptr, llargs, self.funclet(fx), instance);
227+
return MergingSucc::False;
228+
}
229+
224230
if let Some(unwind_block) = unwind_block {
225231
let ret_llbb = if let Some((_, target)) = destination {
226232
fx.llbb(target)
@@ -659,6 +665,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
659665
unwind,
660666
&[],
661667
Some(drop_instance),
668+
false,
662669
!maybe_null && mergeable_succ,
663670
)
664671
}
@@ -747,8 +754,19 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
747754
let (fn_abi, llfn, instance) = common::build_langcall(bx, span, lang_item);
748755

749756
// Codegen the actual panic invoke/call.
750-
let merging_succ =
751-
helper.do_call(self, bx, fn_abi, llfn, &args, None, unwind, &[], Some(instance), false);
757+
let merging_succ = helper.do_call(
758+
self,
759+
bx,
760+
fn_abi,
761+
llfn,
762+
&args,
763+
None,
764+
unwind,
765+
&[],
766+
Some(instance),
767+
false,
768+
false,
769+
);
752770
assert_eq!(merging_succ, MergingSucc::False);
753771
MergingSucc::False
754772
}
@@ -778,6 +796,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
778796
&[],
779797
Some(instance),
780798
false,
799+
false,
781800
);
782801
assert_eq!(merging_succ, MergingSucc::False);
783802
}
@@ -845,6 +864,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
845864
unwind,
846865
&[],
847866
Some(instance),
867+
false,
848868
mergeable_succ,
849869
))
850870
}
@@ -860,6 +880,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
860880
target: Option<mir::BasicBlock>,
861881
unwind: mir::UnwindAction,
862882
fn_span: Span,
883+
tail: bool,
863884
mergeable_succ: bool,
864885
) -> MergingSucc {
865886
let source_info = mir::SourceInfo { span: fn_span, ..terminator.source_info };
@@ -1003,8 +1024,12 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
10031024
// We still need to call `make_return_dest` even if there's no `target`, since
10041025
// `fn_abi.ret` could be `PassMode::Indirect`, even if it is uninhabited,
10051026
// and `make_return_dest` adds the return-place indirect pointer to `llargs`.
1006-
let return_dest = self.make_return_dest(bx, destination, &fn_abi.ret, &mut llargs);
1007-
let destination = target.map(|target| (return_dest, target));
1027+
let destination = if !tail {
1028+
let return_dest = self.make_return_dest(bx, destination, &fn_abi.ret, &mut llargs);
1029+
target.map(|target| (return_dest, target))
1030+
} else {
1031+
None
1032+
};
10081033

10091034
// Split the rust-call tupled arguments off.
10101035
let (first_args, untuple) = if sig.abi() == ExternAbi::RustCall
@@ -1020,6 +1045,13 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
10201045
// to generate `lifetime_end` when the call returns.
10211046
let mut lifetime_ends_after_call: Vec<(Bx::Value, Size)> = Vec::new();
10221047
'make_args: for (i, arg) in first_args.iter().enumerate() {
1048+
if tail && matches!(fn_abi.args[i].mode, PassMode::Indirect { .. }) {
1049+
span_bug!(
1050+
fn_span,
1051+
"arguments using PassMode::Indirect are currently not supported for tail calls"
1052+
);
1053+
}
1054+
10231055
let mut op = self.codegen_operand(bx, &arg.node);
10241056

10251057
if let (0, Some(ty::InstanceKind::Virtual(_, idx))) = (i, instance.map(|i| i.def)) {
@@ -1147,6 +1179,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
11471179
unwind,
11481180
&lifetime_ends_after_call,
11491181
instance,
1182+
tail,
11501183
mergeable_succ,
11511184
)
11521185
}
@@ -1388,15 +1421,23 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
13881421
target,
13891422
unwind,
13901423
fn_span,
1424+
false,
13911425
mergeable_succ(),
13921426
),
1393-
mir::TerminatorKind::TailCall { .. } => {
1394-
// FIXME(explicit_tail_calls): implement tail calls in ssa backend
1395-
span_bug!(
1396-
terminator.source_info.span,
1397-
"`TailCall` terminator is not yet supported by `rustc_codegen_ssa`"
1398-
)
1399-
}
1427+
mir::TerminatorKind::TailCall { ref func, ref args, fn_span } => self
1428+
.codegen_call_terminator(
1429+
helper,
1430+
bx,
1431+
terminator,
1432+
func,
1433+
args,
1434+
mir::Place::from(mir::RETURN_PLACE),
1435+
None,
1436+
mir::UnwindAction::Unreachable,
1437+
fn_span,
1438+
true,
1439+
mergeable_succ(),
1440+
),
14001441
mir::TerminatorKind::CoroutineDrop | mir::TerminatorKind::Yield { .. } => {
14011442
bug!("coroutine ops in codegen")
14021443
}

compiler/rustc_codegen_ssa/src/traits/builder.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,18 @@ pub trait BuilderMethods<'a, 'tcx>:
595595
funclet: Option<&Self::Funclet>,
596596
instance: Option<Instance<'tcx>>,
597597
) -> Self::Value;
598+
599+
fn tail_call(
600+
&mut self,
601+
llty: Self::Type,
602+
fn_attrs: Option<&CodegenFnAttrs>,
603+
fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
604+
llfn: Self::Value,
605+
args: &[Self::Value],
606+
funclet: Option<&Self::Funclet>,
607+
instance: Option<Instance<'tcx>>,
608+
);
609+
598610
fn zext(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value;
599611

600612
fn apply_attrs_to_cleanup_callsite(&mut self, llret: Self::Value);

compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1986,3 +1986,29 @@ extern "C" void LLVMRustSetNoSanitizeHWAddress(LLVMValueRef Global) {
19861986
MD.NoHWAddress = true;
19871987
GV.setSanitizerMetadata(MD);
19881988
}
1989+
1990+
enum LLVMRustTailCallKind {
1991+
LLVMRustTailCallKindNone = 0,
1992+
LLVMRustTailCallKindTail = 1,
1993+
LLVMRustTailCallKindMustTail = 2,
1994+
LLVMRustTailCallKindNoTail = 3
1995+
};
1996+
1997+
extern "C" void LLVMRustSetTailCallKind(LLVMValueRef Call,
1998+
LLVMRustTailCallKind Kind) {
1999+
CallInst *CI = unwrap<CallInst>(Call);
2000+
switch (Kind) {
2001+
case LLVMRustTailCallKindNone:
2002+
CI->setTailCallKind(CallInst::TCK_None);
2003+
break;
2004+
case LLVMRustTailCallKindTail:
2005+
CI->setTailCallKind(CallInst::TCK_Tail);
2006+
break;
2007+
case LLVMRustTailCallKindMustTail:
2008+
CI->setTailCallKind(CallInst::TCK_MustTail);
2009+
break;
2010+
case LLVMRustTailCallKindNoTail:
2011+
CI->setTailCallKind(CallInst::TCK_NoTail);
2012+
break;
2013+
}
2014+
}

tests/codegen/become-musttail.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//@ compile-flags: -C opt-level=0 -Cpanic=abort -C no-prepopulate-passes
2+
//@ needs-unwind
3+
4+
#![crate_type = "lib"]
5+
#![feature(explicit_tail_calls)]
6+
7+
// CHECK-LABEL: define {{.*}}@fibonacci(
8+
#[no_mangle]
9+
#[inline(never)]
10+
pub fn fibonacci(n: u64, a: u64, b: u64) -> u64 {
11+
// CHECK: musttail call {{.*}}@fibonacci(
12+
// CHECK-NEXT: ret i64
13+
match n {
14+
0 => a,
15+
1 => b,
16+
_ => become fibonacci(n - 1, b, a + b),
17+
}
18+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//@ run-crash
2+
3+
use std::hint::black_box;
4+
5+
pub fn count(curr: u64, top: u64) -> u64 {
6+
if black_box(curr) >= top {
7+
curr
8+
} else {
9+
count(curr + 1, top)
10+
}
11+
}
12+
13+
fn main() {
14+
println!("{}", count(0, black_box(1000000)));
15+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//@ run-pass
2+
#![expect(incomplete_features)]
3+
#![feature(explicit_tail_calls)]
4+
5+
use std::hint::black_box;
6+
7+
pub fn count(curr: u64, top: u64) -> u64 {
8+
if black_box(curr) >= top {
9+
curr
10+
} else {
11+
become count(curr + 1, top)
12+
}
13+
}
14+
15+
fn main() {
16+
println!("{}", count(0, black_box(1000000)));
17+
}

0 commit comments

Comments
 (0)