Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 989d72f

Browse files
committedMar 16, 2025
Implement guaranteed tail calls with the become keyword in the LLVM backend
1 parent 66678e6 commit 989d72f

File tree

10 files changed

+1395
-6
lines changed

10 files changed

+1395
-6
lines changed
 

‎compiler/rustc_codegen_llvm/src/builder.rs

+5
Original file line numberDiff line numberDiff line change
@@ -1419,6 +1419,11 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
14191419
let cold_inline = llvm::AttributeKind::Cold.create_attr(self.llcx);
14201420
attributes::apply_to_callsite(llret, llvm::AttributePlace::Function, &[cold_inline]);
14211421
}
1422+
1423+
fn set_tail_call(&mut self, call_inst: &'ll Value) {
1424+
// LLVMSetTailCall is marked as safe in the FFI definition
1425+
llvm::LLVMSetTailCall(call_inst, llvm::True);
1426+
}
14221427
}
14231428

14241429
impl<'ll> StaticBuilderMethods for Builder<'_, 'll, '_> {

‎compiler/rustc_codegen_ssa/src/mir/block.rs

+87-6
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,90 @@ impl<'a, 'tcx> TerminatorCodegenHelper<'tcx> {
342342

343343
/// Codegen implementations for some terminator variants.
344344
impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
345+
fn codegen_tail_call_terminator(
346+
&mut self,
347+
bx: &mut Bx,
348+
func: &mir::Operand<'tcx>,
349+
args: &[Spanned<mir::Operand<'tcx>>],
350+
fn_span: Span,
351+
) {
352+
// We don't need source_info as we already have fn_span for diagnostics
353+
let func = self.codegen_operand(bx, func);
354+
let fn_ty = func.layout.ty;
355+
356+
// Create the callee. This is a fn ptr or zero-sized and hence a kind of scalar.
357+
let (fn_ptr, fn_abi, instance) = match *fn_ty.kind() {
358+
ty::FnDef(def_id, substs) => {
359+
let instance = ty::Instance::expect_resolve(
360+
bx.tcx(),
361+
bx.typing_env(),
362+
def_id,
363+
substs,
364+
fn_span,
365+
);
366+
let fn_ptr = bx.get_fn_addr(instance);
367+
let fn_abi = bx.fn_abi_of_instance(instance, ty::List::empty());
368+
(fn_ptr, fn_abi, Some(instance))
369+
}
370+
ty::FnPtr(..) => {
371+
let sig = fn_ty.fn_sig(bx.tcx());
372+
let extra_args = bx.tcx().mk_type_list(&[]);
373+
let fn_ptr = func.immediate();
374+
let fn_abi = bx.fn_abi_of_fn_ptr(sig, extra_args);
375+
(fn_ptr, fn_abi, None)
376+
}
377+
_ => bug!("{} is not callable", func.layout.ty),
378+
};
379+
380+
let mut llargs = Vec::with_capacity(args.len());
381+
382+
// Process arguments
383+
for arg in args {
384+
let op = self.codegen_operand(bx, &arg.node);
385+
let arg_idx = llargs.len();
386+
387+
if arg_idx < fn_abi.args.len() {
388+
self.codegen_argument(bx, op, &mut llargs, &fn_abi.args[arg_idx]);
389+
} else {
390+
// This can happen in case of C-variadic functions
391+
let is_immediate = match op.val {
392+
Immediate(_) => true,
393+
_ => false,
394+
};
395+
396+
if is_immediate {
397+
llargs.push(op.immediate());
398+
} else {
399+
let temp = PlaceRef::alloca(bx, op.layout);
400+
op.val.store(bx, temp);
401+
llargs.push(bx.load(
402+
bx.backend_type(op.layout),
403+
temp.val.llval,
404+
temp.val.align,
405+
));
406+
}
407+
}
408+
}
409+
410+
// Call the function
411+
let fn_ty = bx.fn_decl_backend_type(fn_abi);
412+
let fn_attrs = if let Some(instance) = instance
413+
&& bx.tcx().def_kind(instance.def_id()).has_codegen_attrs()
414+
{
415+
Some(bx.tcx().codegen_fn_attrs(instance.def_id()))
416+
} else {
417+
None
418+
};
419+
420+
// Perform the actual function call
421+
let llret = bx.call(fn_ty, fn_attrs, Some(fn_abi), fn_ptr, &llargs, None, instance);
422+
423+
// Mark as tail call - this is the critical part
424+
bx.set_tail_call(llret);
425+
426+
// Return the result
427+
bx.ret(llret);
428+
}
345429
/// Generates code for a `Resume` terminator.
346430
fn codegen_resume_terminator(&mut self, helper: TerminatorCodegenHelper<'tcx>, bx: &mut Bx) {
347431
if let Some(funclet) = helper.funclet(self) {
@@ -1430,12 +1514,9 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
14301514
fn_span,
14311515
mergeable_succ(),
14321516
),
1433-
mir::TerminatorKind::TailCall { .. } => {
1434-
// FIXME(explicit_tail_calls): implement tail calls in ssa backend
1435-
span_bug!(
1436-
terminator.source_info.span,
1437-
"`TailCall` terminator is not yet supported by `rustc_codegen_ssa`"
1438-
)
1517+
mir::TerminatorKind::TailCall { ref func, ref args, fn_span } => {
1518+
self.codegen_tail_call_terminator(bx, func, args, fn_span);
1519+
MergingSucc::False
14391520
}
14401521
mir::TerminatorKind::CoroutineDrop | mir::TerminatorKind::Yield { .. } => {
14411522
bug!("coroutine ops in codegen")

‎compiler/rustc_codegen_ssa/src/traits/builder.rs

+4
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,10 @@ pub trait BuilderMethods<'a, 'tcx>:
555555
funclet: Option<&Self::Funclet>,
556556
instance: Option<Instance<'tcx>>,
557557
) -> Self::Value;
558+
559+
/// Mark a call instruction as a tail call (guaranteed tail call optimization)
560+
/// Used for implementing the `become` expression
561+
fn set_tail_call(&mut self, call_inst: Self::Value);
558562
fn zext(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value;
559563

560564
fn apply_attrs_to_cleanup_callsite(&mut self, llret: Self::Value);
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# This test verifies that the `become` keyword generates tail call instructions in LLVM IR
2+
3+
# Use the Rust compiler from the build directory
4+
RUSTC ?= ../../../build/aarch64-apple-darwin/stage1/bin/rustc
5+
6+
all: verify-tail-call
7+
8+
verify-tail-call:
9+
# Create test file with debuginfo to ensure function names are preserved
10+
$(RUSTC) tail-call-test.rs --emit=llvm-ir -g -C opt-level=0
11+
12+
# Check that `become` generates 'tail call' instructions for with_tail function
13+
grep -q "tail call.*@_ZN14tail_call_test9with_tail" tail-call-test.ll || (echo "ERROR: 'tail call' instruction not found for with_tail"; exit 1)
14+
15+
# Check that regular recursive calls don't use tail call optimization by default
16+
grep -q "@_ZN14tail_call_test7no_tail" tail-call-test.ll || (echo "ERROR: no_tail function not found"; exit 1)
17+
! grep -q "tail call.*@_ZN14tail_call_test7no_tail" tail-call-test.ll || (echo "ERROR: Regular function call incorrectly marked as tail call"; exit 1)
18+
19+
# Check mutual recursion with 'become'
20+
grep -q "tail call.*@_ZN14tail_call_test14even_with_tail" tail-call-test.ll || (echo "ERROR: 'tail call' instruction not found for even_with_tail"; exit 1)
21+
grep -q "tail call.*@_ZN14tail_call_test13odd_with_tail" tail-call-test.ll || (echo "ERROR: 'tail call' instruction not found for odd_with_tail"; exit 1)
22+
23+
# The test passes only if all checks above confirm that:
24+
# 1. Functions using 'become' generate LLVM tail call instructions
25+
# 2. Regular recursive functions don't generate tail call instructions
26+
@echo "LLVM IR verification successful: 'become' correctly generates 'tail call' instructions"
There was a problem loading the remainder of the diff.

0 commit comments

Comments
 (0)
Failed to load comments.