-
Notifications
You must be signed in to change notification settings - Fork 13.6k
Implement explicit tail calls in the LLVM backend #138555
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -342,6 +342,97 @@ impl<'a, 'tcx> TerminatorCodegenHelper<'tcx> { | |
|
||
/// Codegen implementations for some terminator variants. | ||
impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { | ||
fn codegen_tail_call_terminator( | ||
&mut self, | ||
bx: &mut Bx, | ||
func: &mir::Operand<'tcx>, | ||
args: &[Spanned<mir::Operand<'tcx>>], | ||
fn_span: Span, | ||
) { | ||
// We don't need source_info as we already have fn_span for diagnostics | ||
let func = self.codegen_operand(bx, func); | ||
let fn_ty = func.layout.ty; | ||
|
||
// Create the callee. This is a fn ptr or zero-sized and hence a kind of scalar. | ||
let (fn_ptr, fn_abi, instance) = match *fn_ty.kind() { | ||
ty::FnDef(def_id, substs) => { | ||
let instance = ty::Instance::expect_resolve( | ||
bx.tcx(), | ||
bx.typing_env(), | ||
def_id, | ||
substs, | ||
fn_span, | ||
); | ||
let fn_ptr = bx.get_fn_addr(instance); | ||
let fn_abi = bx.fn_abi_of_instance(instance, ty::List::empty()); | ||
(fn_ptr, fn_abi, Some(instance)) | ||
} | ||
ty::FnPtr(..) => { | ||
let sig = fn_ty.fn_sig(bx.tcx()); | ||
let extra_args = bx.tcx().mk_type_list(&[]); | ||
let fn_ptr = func.immediate(); | ||
let fn_abi = bx.fn_abi_of_fn_ptr(sig, extra_args); | ||
(fn_ptr, fn_abi, None) | ||
} | ||
_ => bug!("{} is not callable", func.layout.ty), | ||
}; | ||
|
||
let mut llargs = Vec::with_capacity(args.len()); | ||
let mut lifetime_ends_after_call = Vec::new(); | ||
|
||
// Process arguments | ||
for arg in args { | ||
let op = self.codegen_operand(bx, &arg.node); | ||
let arg_idx = llargs.len(); | ||
|
||
if arg_idx < fn_abi.args.len() { | ||
self.codegen_argument( | ||
bx, | ||
op, | ||
&mut llargs, | ||
&fn_abi.args[arg_idx], | ||
&mut lifetime_ends_after_call, | ||
); | ||
} else { | ||
// This can happen in case of C-variadic functions | ||
let is_immediate = match op.val { | ||
Immediate(_) => true, | ||
_ => false, | ||
}; | ||
|
||
if is_immediate { | ||
llargs.push(op.immediate()); | ||
} else { | ||
let temp = PlaceRef::alloca(bx, op.layout); | ||
op.val.store(bx, temp); | ||
llargs.push(bx.load( | ||
bx.backend_type(op.layout), | ||
temp.val.llval, | ||
temp.val.align, | ||
)); | ||
} | ||
} | ||
} | ||
|
||
// Call the function | ||
let fn_ty = bx.fn_decl_backend_type(fn_abi); | ||
let fn_attrs = if let Some(instance) = instance | ||
&& bx.tcx().def_kind(instance.def_id()).has_codegen_attrs() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When would we ever have a tail call for something that doesn't have codegen attrs? |
||
{ | ||
Some(bx.tcx().codegen_fn_attrs(instance.def_id())) | ||
} else { | ||
None | ||
}; | ||
|
||
// Perform the actual function call | ||
let llret = bx.call(fn_ty, fn_attrs, Some(fn_abi), fn_ptr, &llargs, None, instance); | ||
|
||
// Mark as tail call - this is the critical part | ||
bx.set_tail_call(llret); | ||
Comment on lines
+427
to
+431
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why can't we use normal function codegen and just Can we at least share some of the argument processing code and instance resolution code? |
||
|
||
// Return the result - musttail requires ret immediately after the call | ||
bx.ret(llret); | ||
} | ||
/// Generates code for a `Resume` terminator. | ||
fn codegen_resume_terminator(&mut self, helper: TerminatorCodegenHelper<'tcx>, bx: &mut Bx) { | ||
if let Some(funclet) = helper.funclet(self) { | ||
|
@@ -1390,12 +1481,9 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { | |
fn_span, | ||
mergeable_succ(), | ||
), | ||
mir::TerminatorKind::TailCall { .. } => { | ||
// FIXME(explicit_tail_calls): implement tail calls in ssa backend | ||
span_bug!( | ||
terminator.source_info.span, | ||
"`TailCall` terminator is not yet supported by `rustc_codegen_ssa`" | ||
) | ||
mir::TerminatorKind::TailCall { ref func, ref args, fn_span } => { | ||
self.codegen_tail_call_terminator(bx, func, args, fn_span); | ||
MergingSucc::False | ||
} | ||
mir::TerminatorKind::CoroutineDrop | mir::TerminatorKind::Yield { .. } => { | ||
bug!("coroutine ops in codegen") | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -595,6 +595,10 @@ pub trait BuilderMethods<'a, 'tcx>: | |
funclet: Option<&Self::Funclet>, | ||
instance: Option<Instance<'tcx>>, | ||
) -> Self::Value; | ||
|
||
/// Mark a call instruction as a tail call (guaranteed tail call optimization) | ||
/// Used for implementing the `become` expression | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All those references to being used to implement |
||
fn set_tail_call(&mut self, call_inst: Self::Value); | ||
fn zext(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value; | ||
|
||
fn apply_attrs_to_cleanup_callsite(&mut self, llret: Self::Value); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
//@ compile-flags: -C opt-level=0 -Cpanic=abort -C no-prepopulate-passes | ||
//@ needs-llvm-components: x86 | ||
|
||
#![feature(explicit_tail_calls)] | ||
#![crate_type = "lib"] | ||
|
||
// CHECK-LABEL: define {{.*}}@with_tail( | ||
#[no_mangle] | ||
#[inline(never)] | ||
pub fn with_tail(n: u32) -> u32 { | ||
// CHECK: tail call {{.*}}@with_tail( | ||
if n == 0 { 0 } else { become with_tail(n - 1) } | ||
} | ||
|
||
// CHECK-LABEL: define {{.*}}@no_tail( | ||
#[no_mangle] | ||
#[inline(never)] | ||
pub fn no_tail(n: u32) -> u32 { | ||
// CHECK-NOT: tail call | ||
// CHECK: call {{.*}}@no_tail( | ||
if n == 0 { 0 } else { no_tail(n - 1) } | ||
} | ||
|
||
// CHECK-LABEL: define {{.*}}@even_with_tail( | ||
#[no_mangle] | ||
#[inline(never)] | ||
pub fn even_with_tail(n: u32) -> bool { | ||
// CHECK: tail call {{.*}}@odd_with_tail( | ||
match n { | ||
0 => true, | ||
_ => become odd_with_tail(n - 1), | ||
} | ||
} | ||
|
||
// CHECK-LABEL: define {{.*}}@odd_with_tail( | ||
#[no_mangle] | ||
#[inline(never)] | ||
pub fn odd_with_tail(n: u32) -> bool { | ||
// CHECK: tail call {{.*}}@even_with_tail( | ||
match n { | ||
0 => false, | ||
_ => become even_with_tail(n - 1), | ||
} | ||
} | ||
|
||
// CHECK-LABEL: define {{.*}}@even_no_tail( | ||
#[no_mangle] | ||
#[inline(never)] | ||
pub fn even_no_tail(n: u32) -> bool { | ||
// CHECK-NOT: tail call | ||
// CHECK: call {{.*}}@odd_no_tail( | ||
match n { | ||
0 => true, | ||
_ => odd_no_tail(n - 1), | ||
} | ||
} | ||
|
||
// CHECK-LABEL: define {{.*}}@odd_no_tail( | ||
#[no_mangle] | ||
#[inline(never)] | ||
pub fn odd_no_tail(n: u32) -> bool { | ||
// CHECK-NOT: tail call | ||
// CHECK: call {{.*}}@even_no_tail( | ||
match n { | ||
0 => false, | ||
_ => even_no_tail(n - 1), | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
//@ compile-flags: -C opt-level=0 -Cpanic=abort -C no-prepopulate-passes | ||
//@ needs-unwind | ||
|
||
#![crate_type = "lib"] | ||
#![feature(explicit_tail_calls)] | ||
|
||
// Ensure that explicit tail calls use musttail in LLVM | ||
|
||
// CHECK-LABEL: define {{.*}}@simple_tail_call( | ||
#[no_mangle] | ||
#[inline(never)] | ||
pub fn simple_tail_call(n: i32) -> i32 { | ||
// CHECK: musttail call {{.*}}@simple_tail_call( | ||
// CHECK-NEXT: ret i32 | ||
if n <= 0 { | ||
0 | ||
} else { | ||
become simple_tail_call(n - 1) | ||
} | ||
} | ||
|
||
// CHECK-LABEL: define {{.*}}@tail_call_with_args( | ||
#[no_mangle] | ||
#[inline(never)] | ||
pub fn tail_call_with_args(a: i32, b: i32, c: i32) -> i32 { | ||
// CHECK: musttail call {{.*}}@tail_call_with_args( | ||
// CHECK-NEXT: ret i32 | ||
if a == 0 { | ||
b + c | ||
} else { | ||
become tail_call_with_args(a - 1, b + 1, c) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
//@ compile-flags: -O | ||
//@ run-pass | ||
#![expect(incomplete_features)] | ||
#![feature(explicit_tail_calls)] | ||
|
||
// A deep recursive function that uses explicit tail calls | ||
// This will cause stack overflow without tail call optimization | ||
fn deep_recursion(n: u32) -> u32 { | ||
match n { | ||
0 => 0, | ||
_ => become deep_recursion(n - 1) | ||
} | ||
} | ||
|
||
// A deep recursive function without explicit tail calls | ||
// This will overflow the stack for large values | ||
fn deep_recursion_no_tail(n: u32) -> u32 { | ||
match n { | ||
0 => 0, | ||
_ => deep_recursion_no_tail(n - 1) | ||
} | ||
} | ||
|
||
fn main() { | ||
// Verify correctness for small values | ||
assert_eq!(deep_recursion(10), 0); | ||
assert_eq!(deep_recursion_no_tail(10), 0); | ||
|
||
// This will succeed only if tail call optimization is working | ||
// It would overflow the stack otherwise | ||
println!("Starting deep recursion with 50,000 calls"); | ||
let result = deep_recursion(50_000); | ||
assert_eq!(result, 0); | ||
println!("Successfully completed 50,000 recursive calls with tail call optimization"); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is missing an sret argument when
PassMode::Indirect
is used. Also please deduplicate this code with regular calls where possible to prevent divergence.