Skip to content

Commit 65f6df5

Browse files
committed
Support tail calls in mir via TerminatorKind::TailCall
1 parent bdde2a8 commit 65f6df5

File tree

41 files changed

+328
-102
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+328
-102
lines changed

compiler/rustc_borrowck/src/lib.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,12 @@ impl<'cx, 'tcx, R> rustc_mir_dataflow::ResultsVisitor<'cx, 'tcx, R> for MirBorro
705705
}
706706
self.mutate_place(loc, (*destination, span), Deep, flow_state);
707707
}
708+
TerminatorKind::TailCall { func, args, fn_span: _ } => {
709+
self.consume_operand(loc, (func, span), flow_state);
710+
for arg in args {
711+
self.consume_operand(loc, (&arg.node, arg.span), flow_state);
712+
}
713+
}
708714
TerminatorKind::Assert { cond, expected: _, msg, target: _, unwind: _ } => {
709715
self.consume_operand(loc, (cond, span), flow_state);
710716
if let AssertKind::BoundsCheck { len, index } = &**msg {
@@ -790,9 +796,8 @@ impl<'cx, 'tcx, R> rustc_mir_dataflow::ResultsVisitor<'cx, 'tcx, R> for MirBorro
790796

791797
TerminatorKind::UnwindResume
792798
| TerminatorKind::Return
799+
| TerminatorKind::TailCall { .. }
793800
| TerminatorKind::CoroutineDrop => {
794-
// Returning from the function implicitly kills storage for all locals and statics.
795-
// Often, the storage will already have been killed by an explicit
796801
// StorageDead, but we don't always emit those (notably on unwind paths),
797802
// so this "extra check" serves as a kind of backup.
798803
let borrow_set = self.borrow_set.clone();

compiler/rustc_borrowck/src/polonius/loan_invalidations.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,12 @@ impl<'cx, 'tcx> Visitor<'tcx> for LoanInvalidationsGenerator<'cx, 'tcx> {
122122
}
123123
self.mutate_place(location, *destination, Deep);
124124
}
125+
TerminatorKind::TailCall { func, args, .. } => {
126+
self.consume_operand(location, func);
127+
for arg in args {
128+
self.consume_operand(location, &arg.node);
129+
}
130+
}
125131
TerminatorKind::Assert { cond, expected: _, msg, target: _, unwind: _ } => {
126132
self.consume_operand(location, cond);
127133
use rustc_middle::mir::AssertKind;

compiler/rustc_borrowck/src/type_check/mod.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1413,7 +1413,14 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
14131413
}
14141414
// FIXME: check the values
14151415
}
1416-
TerminatorKind::Call { func, args, destination, call_source, target, .. } => {
1416+
TerminatorKind::Call { func, args, .. }
1417+
| TerminatorKind::TailCall { func, args, .. } => {
1418+
let call_source = match term.kind {
1419+
TerminatorKind::Call { call_source, .. } => call_source,
1420+
TerminatorKind::TailCall { .. } => CallSource::Normal,
1421+
_ => unreachable!(),
1422+
};
1423+
14171424
self.check_operand(func, term_location);
14181425
for arg in args {
14191426
self.check_operand(&arg.node, term_location);
@@ -1486,7 +1493,9 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
14861493
);
14871494
}
14881495

1489-
self.check_call_dest(body, term, &sig, *destination, *target, term_location);
1496+
if let TerminatorKind::Call { destination, target, .. } = term.kind {
1497+
self.check_call_dest(body, term, &sig, destination, target, term_location);
1498+
}
14901499

14911500
// The ordinary liveness rules will ensure that all
14921501
// regions in the type of the callee are live here. We
@@ -1504,7 +1513,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
15041513
.add_location(region_vid, term_location);
15051514
}
15061515

1507-
self.check_call_inputs(body, term, func, &sig, args, term_location, *call_source);
1516+
self.check_call_inputs(body, term, func, &sig, args, term_location, call_source);
15081517
}
15091518
TerminatorKind::Assert { cond, msg, .. } => {
15101519
self.check_operand(cond, term_location);
@@ -1736,6 +1745,11 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
17361745
span_mirbug!(self, block_data, "return on cleanup block")
17371746
}
17381747
}
1748+
TerminatorKind::TailCall { .. } => {
1749+
if is_cleanup {
1750+
span_mirbug!(self, block_data, "tailcall on cleanup block")
1751+
}
1752+
}
17391753
TerminatorKind::CoroutineDrop { .. } => {
17401754
if is_cleanup {
17411755
span_mirbug!(self, block_data, "coroutine_drop in cleanup block")

compiler/rustc_codegen_cranelift/src/base.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,11 @@ fn codegen_fn_body(fx: &mut FunctionCx<'_, '_, '_>, start_block: Block) {
441441
)
442442
});
443443
}
444+
// FIXME(explicit_tail_calls): add support for tail calls to the cranelift backend, once cranelift supports tail calls
445+
TerminatorKind::TailCall { fn_span, .. } => span_bug!(
446+
*fn_span,
447+
"tail calls are not yet supported in `rustc_codegen_cranelift` backend"
448+
),
444449
TerminatorKind::InlineAsm {
445450
template,
446451
operands,

compiler/rustc_codegen_cranelift/src/constant.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,7 @@ pub(crate) fn mir_operand_get_const_val<'tcx>(
539539
{
540540
return None;
541541
}
542+
TerminatorKind::TailCall { .. } => return None,
542543
TerminatorKind::Call { .. } => {}
543544
}
544545
}

compiler/rustc_codegen_ssa/src/mir/analyze.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@ pub fn cleanup_kinds(mir: &mir::Body<'_>) -> IndexVec<mir::BasicBlock, CleanupKi
279279
| TerminatorKind::UnwindResume
280280
| TerminatorKind::UnwindTerminate(_)
281281
| TerminatorKind::Return
282+
| TerminatorKind::TailCall { .. }
282283
| TerminatorKind::CoroutineDrop
283284
| TerminatorKind::Unreachable
284285
| TerminatorKind::SwitchInt { .. }

compiler/rustc_codegen_ssa/src/mir/block.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,6 +1306,13 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
13061306
fn_span,
13071307
mergeable_succ(),
13081308
),
1309+
mir::TerminatorKind::TailCall { .. } => {
1310+
// FIXME(explicit_tail_calls): implement tail calls in ssa backend
1311+
span_bug!(
1312+
terminator.source_info.span,
1313+
"`TailCall` terminator is not yet supported by `rustc_codegen_ssa`"
1314+
)
1315+
}
13091316
mir::TerminatorKind::CoroutineDrop | mir::TerminatorKind::Yield { .. } => {
13101317
bug!("coroutine ops in codegen")
13111318
}

compiler/rustc_const_eval/src/interpret/terminator.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
170170
}
171171
}
172172

173+
TailCall { func: _, args: _, fn_span: _ } => todo!(),
174+
173175
Drop { place, target, unwind, replace: _ } => {
174176
let frame = self.frame();
175177
let ty = place.ty(&frame.body.local_decls, *self.tcx).ty;

compiler/rustc_const_eval/src/transform/check_consts/check.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ impl<'mir, 'tcx> Qualifs<'mir, 'tcx> {
132132
ccx: &'mir ConstCx<'mir, 'tcx>,
133133
tainted_by_errors: Option<ErrorGuaranteed>,
134134
) -> ConstQualifs {
135+
// FIXME(explicit_tail_calls): uhhhh I think we can return without return now, does it change anything
136+
135137
// Find the `Return` terminator if one exists.
136138
//
137139
// If no `Return` terminator exists, this MIR is divergent. Just return the conservative
@@ -704,7 +706,14 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
704706
self.super_terminator(terminator, location);
705707

706708
match &terminator.kind {
707-
TerminatorKind::Call { func, args, fn_span, call_source, .. } => {
709+
TerminatorKind::Call { func, args, fn_span, .. }
710+
| TerminatorKind::TailCall { func, args, fn_span, .. } => {
711+
let call_source = match terminator.kind {
712+
TerminatorKind::Call { call_source, .. } => call_source,
713+
TerminatorKind::TailCall { .. } => CallSource::Normal,
714+
_ => unreachable!(),
715+
};
716+
708717
let ConstCx { tcx, body, param_env, .. } = *self.ccx;
709718
let caller = self.def_id();
710719

@@ -776,7 +785,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
776785
callee,
777786
args: fn_args,
778787
span: *fn_span,
779-
call_source: *call_source,
788+
call_source,
780789
feature: Some(if tcx.features().const_trait_impl {
781790
sym::effects
782791
} else {
@@ -823,7 +832,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
823832
callee,
824833
args: fn_args,
825834
span: *fn_span,
826-
call_source: *call_source,
835+
call_source,
827836
feature: None,
828837
});
829838
return;

compiler/rustc_const_eval/src/transform/check_consts/post_drop_elaboration.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ impl<'tcx> Visitor<'tcx> for CheckLiveDrops<'_, 'tcx> {
107107

108108
mir::TerminatorKind::UnwindTerminate(_)
109109
| mir::TerminatorKind::Call { .. }
110+
| mir::TerminatorKind::TailCall { .. }
110111
| mir::TerminatorKind::Assert { .. }
111112
| mir::TerminatorKind::FalseEdge { .. }
112113
| mir::TerminatorKind::FalseUnwind { .. }

compiler/rustc_const_eval/src/transform/validate.rs

Lines changed: 44 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -386,40 +386,44 @@ impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> {
386386
self.check_edge(location, *target, EdgeKind::Normal);
387387
self.check_unwind_edge(location, *unwind);
388388
}
389-
TerminatorKind::Call { args, destination, target, unwind, .. } => {
390-
if let Some(target) = target {
391-
self.check_edge(location, *target, EdgeKind::Normal);
392-
}
393-
self.check_unwind_edge(location, *unwind);
389+
TerminatorKind::Call { args, .. } | TerminatorKind::TailCall { args, .. } => {
390+
// FIXME(explicit_tail_calls): refactor this & add tail-call specific checks
391+
if let TerminatorKind::Call { target, unwind, destination, .. } = terminator.kind {
392+
if let Some(target) = target {
393+
self.check_edge(location, target, EdgeKind::Normal);
394+
}
395+
self.check_unwind_edge(location, unwind);
396+
397+
// The code generation assumes that there are no critical call edges. The assumption
398+
// is used to simplify inserting code that should be executed along the return edge
399+
// from the call. FIXME(tmiasko): Since this is a strictly code generation concern,
400+
// the code generation should be responsible for handling it.
401+
if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Optimized)
402+
&& self.is_critical_call_edge(target, unwind)
403+
{
404+
self.fail(
405+
location,
406+
format!(
407+
"encountered critical edge in `Call` terminator {:?}",
408+
terminator.kind,
409+
),
410+
);
411+
}
394412

395-
// The code generation assumes that there are no critical call edges. The assumption
396-
// is used to simplify inserting code that should be executed along the return edge
397-
// from the call. FIXME(tmiasko): Since this is a strictly code generation concern,
398-
// the code generation should be responsible for handling it.
399-
if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Optimized)
400-
&& self.is_critical_call_edge(*target, *unwind)
401-
{
402-
self.fail(
403-
location,
404-
format!(
405-
"encountered critical edge in `Call` terminator {:?}",
406-
terminator.kind,
407-
),
408-
);
413+
// The call destination place and Operand::Move place used as an argument might be
414+
// passed by a reference to the callee. Consequently they cannot be packed.
415+
if is_within_packed(self.tcx, &self.body.local_decls, destination).is_some() {
416+
// This is bad! The callee will expect the memory to be aligned.
417+
self.fail(
418+
location,
419+
format!(
420+
"encountered packed place in `Call` terminator destination: {:?}",
421+
terminator.kind,
422+
),
423+
);
424+
}
409425
}
410426

411-
// The call destination place and Operand::Move place used as an argument might be
412-
// passed by a reference to the callee. Consequently they cannot be packed.
413-
if is_within_packed(self.tcx, &self.body.local_decls, *destination).is_some() {
414-
// This is bad! The callee will expect the memory to be aligned.
415-
self.fail(
416-
location,
417-
format!(
418-
"encountered packed place in `Call` terminator destination: {:?}",
419-
terminator.kind,
420-
),
421-
);
422-
}
423427
for arg in args {
424428
if let Operand::Move(place) = &arg.node {
425429
if is_within_packed(self.tcx, &self.body.local_decls, *place).is_some() {
@@ -1311,15 +1315,22 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
13111315
}
13121316
}
13131317
}
1314-
TerminatorKind::Call { func, .. } => {
1318+
TerminatorKind::Call { func, .. } | TerminatorKind::TailCall { func, .. } => {
13151319
let func_ty = func.ty(&self.body.local_decls, self.tcx);
13161320
match func_ty.kind() {
13171321
ty::FnPtr(..) | ty::FnDef(..) => {}
13181322
_ => self.fail(
13191323
location,
1320-
format!("encountered non-callable type {func_ty} in `Call` terminator"),
1324+
format!(
1325+
"encountered non-callable type {func_ty} in `{}` terminator",
1326+
terminator.kind.name()
1327+
),
13211328
),
13221329
}
1330+
1331+
if let TerminatorKind::TailCall { .. } = terminator.kind {
1332+
// FIXME(explicit_tail_calls): implement tail-call specific checks here (such as signature matching, forbidding closures, etc)
1333+
}
13231334
}
13241335
TerminatorKind::Assert { cond, .. } => {
13251336
let cond_ty = cond.ty(&self.body.local_decls, self.tcx);

compiler/rustc_middle/src/mir/pretty.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,16 @@ impl<'tcx> TerminatorKind<'tcx> {
777777
}
778778
write!(fmt, ")")
779779
}
780+
TailCall { func, args, .. } => {
781+
write!(fmt, "tailcall {func:?}(")?;
782+
for (index, arg) in args.iter().enumerate() {
783+
if index > 0 {
784+
write!(fmt, ", ")?;
785+
}
786+
write!(fmt, "{:?}", arg)?;
787+
}
788+
write!(fmt, ")")
789+
}
780790
Assert { cond, expected, msg, .. } => {
781791
write!(fmt, "assert(")?;
782792
if !expected {
@@ -841,7 +851,12 @@ impl<'tcx> TerminatorKind<'tcx> {
841851
pub fn fmt_successor_labels(&self) -> Vec<Cow<'static, str>> {
842852
use self::TerminatorKind::*;
843853
match *self {
844-
Return | UnwindResume | UnwindTerminate(_) | Unreachable | CoroutineDrop => vec![],
854+
Return
855+
| TailCall { .. }
856+
| UnwindResume
857+
| UnwindTerminate(_)
858+
| Unreachable
859+
| CoroutineDrop => vec![],
845860
Goto { .. } => vec!["".into()],
846861
SwitchInt { ref targets, .. } => targets
847862
.values

compiler/rustc_middle/src/mir/syntax.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,36 @@ pub enum TerminatorKind<'tcx> {
691691
fn_span: Span,
692692
},
693693

694+
/// Tail call.
695+
///
696+
/// Roughly speaking this is a chimera of [`Call`] and [`Return`], with some caveats.
697+
/// Semantically tail calls consists of two actions:
698+
/// - pop of the current stack frame
699+
/// - a call to the `func`, with the return address of the **current** caller
700+
/// - so that a `return` inside `func` returns to the caller of the caller
701+
/// of the function that is currently being executed
702+
///
703+
/// Note that in difference with [`Call`] this is missing
704+
/// - `destination` (because it's always the return place)
705+
/// - `target` (because it's always taken from the current stack frame)
706+
/// - `unwind` (because it's always taken from the current stack frame)
707+
///
708+
/// [`Call`]: TerminatorKind::Call
709+
/// [`Return`]: TerminatorKind::Return
710+
TailCall {
711+
/// The function that’s being called.
712+
func: Operand<'tcx>,
713+
/// Arguments the function is called with.
714+
/// These are owned by the callee, which is free to modify them.
715+
/// This allows the memory occupied by "by-value" arguments to be
716+
/// reused across function calls without duplicating the contents.
717+
args: Vec<Spanned<Operand<'tcx>>>,
718+
// FIXME(explicit_tail_calls): should we have the span for `become`? is this span accurate? do we need it?
719+
/// This `Span` is the span of the function, without the dot and receiver
720+
/// (e.g. `foo(a, b)` in `x.foo(a, b)`
721+
fn_span: Span,
722+
},
723+
694724
/// Evaluates the operand, which must have type `bool`. If it is not equal to `expected`,
695725
/// initiates a panic. Initiating a panic corresponds to a `Call` terminator with some
696726
/// unspecified constant as the function to call, all the operands stored in the `AssertMessage`
@@ -816,6 +846,7 @@ impl TerminatorKind<'_> {
816846
TerminatorKind::Unreachable => "Unreachable",
817847
TerminatorKind::Drop { .. } => "Drop",
818848
TerminatorKind::Call { .. } => "Call",
849+
TerminatorKind::TailCall { .. } => "TailCall",
819850
TerminatorKind::Assert { .. } => "Assert",
820851
TerminatorKind::Yield { .. } => "Yield",
821852
TerminatorKind::CoroutineDrop => "CoroutineDrop",

0 commit comments

Comments
 (0)