Skip to content

Commit 14ec2fc

Browse files
committed
Support tail calls in mir via TerminatorKind::TailCall
1 parent ee9c7c9 commit 14ec2fc

File tree

39 files changed

+313
-102
lines changed

39 files changed

+313
-102
lines changed

compiler/rustc_borrowck/src/lib.rs

Lines changed: 8 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 {
@@ -788,11 +794,11 @@ impl<'cx, 'tcx, R> rustc_mir_dataflow::ResultsVisitor<'cx, 'tcx, R> for MirBorro
788794
}
789795
}
790796

797+
// FIXME(explicit_tail_calls): do we need to do something similar before the tail call?
791798
TerminatorKind::UnwindResume
792799
| TerminatorKind::Return
800+
| TerminatorKind::TailCall { .. }
793801
| 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
796802
// StorageDead, but we don't always emit those (notably on unwind paths),
797803
// so this "extra check" serves as a kind of backup.
798804
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
@@ -1416,7 +1416,14 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
14161416
}
14171417
// FIXME: check the values
14181418
}
1419-
TerminatorKind::Call { func, args, destination, call_source, target, .. } => {
1419+
TerminatorKind::Call { func, args, .. }
1420+
| TerminatorKind::TailCall { func, args, .. } => {
1421+
let call_source = match term.kind {
1422+
TerminatorKind::Call { call_source, .. } => call_source,
1423+
TerminatorKind::TailCall { .. } => CallSource::Normal,
1424+
_ => unreachable!(),
1425+
};
1426+
14201427
self.check_operand(func, term_location);
14211428
for arg in args {
14221429
self.check_operand(&arg.node, term_location);
@@ -1489,7 +1496,9 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
14891496
);
14901497
}
14911498

1492-
self.check_call_dest(body, term, &sig, *destination, *target, term_location);
1499+
if let TerminatorKind::Call { destination, target, .. } = term.kind {
1500+
self.check_call_dest(body, term, &sig, destination, target, term_location);
1501+
}
14931502

14941503
// The ordinary liveness rules will ensure that all
14951504
// regions in the type of the callee are live here. We
@@ -1507,7 +1516,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
15071516
.add_location(region_vid, term_location);
15081517
}
15091518

1510-
self.check_call_inputs(body, term, func, &sig, args, term_location, *call_source);
1519+
self.check_call_inputs(body, term, func, &sig, args, term_location, call_source);
15111520
}
15121521
TerminatorKind::Assert { cond, msg, .. } => {
15131522
self.check_operand(cond, term_location);
@@ -1733,6 +1742,11 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
17331742
span_mirbug!(self, block_data, "return on cleanup block")
17341743
}
17351744
}
1745+
TerminatorKind::TailCall { .. } => {
1746+
if is_cleanup {
1747+
span_mirbug!(self, block_data, "tailcall on cleanup block")
1748+
}
1749+
}
17361750
TerminatorKind::CoroutineDrop { .. } => {
17371751
if is_cleanup {
17381752
span_mirbug!(self, block_data, "coroutine_drop in cleanup block")

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
@@ -1269,6 +1269,13 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
12691269
fn_span,
12701270
mergeable_succ(),
12711271
),
1272+
mir::TerminatorKind::TailCall { .. } => {
1273+
// FIXME(explicit_tail_calls): implement tail calls in ssa backend
1274+
span_bug!(
1275+
terminator.source_info.span,
1276+
"`TailCall` terminator is not yet supported by `rustc_codegen_ssa`"
1277+
)
1278+
}
12721279
mir::TerminatorKind::CoroutineDrop | mir::TerminatorKind::Yield { .. } => {
12731280
bug!("coroutine ops in codegen")
12741281
}

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
@@ -683,7 +685,14 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
683685
self.super_terminator(terminator, location);
684686

685687
match &terminator.kind {
686-
TerminatorKind::Call { func, args, fn_span, call_source, .. } => {
688+
TerminatorKind::Call { func, args, fn_span, .. }
689+
| TerminatorKind::TailCall { func, args, fn_span, .. } => {
690+
let call_source = match terminator.kind {
691+
TerminatorKind::Call { call_source, .. } => call_source,
692+
TerminatorKind::TailCall { .. } => CallSource::Normal,
693+
_ => unreachable!(),
694+
};
695+
687696
let ConstCx { tcx, body, param_env, .. } = *self.ccx;
688697
let caller = self.def_id();
689698

@@ -755,7 +764,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
755764
callee,
756765
args: fn_args,
757766
span: *fn_span,
758-
call_source: *call_source,
767+
call_source,
759768
feature: Some(if tcx.features().const_trait_impl {
760769
sym::effects
761770
} else {
@@ -802,7 +811,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
802811
callee,
803812
args: fn_args,
804813
span: *fn_span,
805-
call_source: *call_source,
814+
call_source,
806815
feature: None,
807816
});
808817
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: 40 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -366,40 +366,44 @@ impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> {
366366
self.check_edge(location, *target, EdgeKind::Normal);
367367
self.check_unwind_edge(location, *unwind);
368368
}
369-
TerminatorKind::Call { args, destination, target, unwind, .. } => {
370-
if let Some(target) = target {
371-
self.check_edge(location, *target, EdgeKind::Normal);
372-
}
373-
self.check_unwind_edge(location, *unwind);
369+
TerminatorKind::Call { args, .. } | TerminatorKind::TailCall { args, .. } => {
370+
// FIXME(explicit_tail_calls): refactor this & add tail-call specific checks
371+
if let TerminatorKind::Call { target, unwind, destination, .. } = terminator.kind {
372+
if let Some(target) = target {
373+
self.check_edge(location, target, EdgeKind::Normal);
374+
}
375+
self.check_unwind_edge(location, unwind);
376+
377+
// The code generation assumes that there are no critical call edges. The assumption
378+
// is used to simplify inserting code that should be executed along the return edge
379+
// from the call. FIXME(tmiasko): Since this is a strictly code generation concern,
380+
// the code generation should be responsible for handling it.
381+
if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Optimized)
382+
&& self.is_critical_call_edge(target, unwind)
383+
{
384+
self.fail(
385+
location,
386+
format!(
387+
"encountered critical edge in `Call` terminator {:?}",
388+
terminator.kind,
389+
),
390+
);
391+
}
374392

375-
// The code generation assumes that there are no critical call edges. The assumption
376-
// is used to simplify inserting code that should be executed along the return edge
377-
// from the call. FIXME(tmiasko): Since this is a strictly code generation concern,
378-
// the code generation should be responsible for handling it.
379-
if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Optimized)
380-
&& self.is_critical_call_edge(*target, *unwind)
381-
{
382-
self.fail(
383-
location,
384-
format!(
385-
"encountered critical edge in `Call` terminator {:?}",
386-
terminator.kind,
387-
),
388-
);
393+
// The call destination place and Operand::Move place used as an argument might be
394+
// passed by a reference to the callee. Consequently they cannot be packed.
395+
if is_within_packed(self.tcx, &self.body.local_decls, destination).is_some() {
396+
// This is bad! The callee will expect the memory to be aligned.
397+
self.fail(
398+
location,
399+
format!(
400+
"encountered packed place in `Call` terminator destination: {:?}",
401+
terminator.kind,
402+
),
403+
);
404+
}
389405
}
390406

391-
// The call destination place and Operand::Move place used as an argument might be
392-
// passed by a reference to the callee. Consequently they cannot be packed.
393-
if is_within_packed(self.tcx, &self.body.local_decls, *destination).is_some() {
394-
// This is bad! The callee will expect the memory to be aligned.
395-
self.fail(
396-
location,
397-
format!(
398-
"encountered packed place in `Call` terminator destination: {:?}",
399-
terminator.kind,
400-
),
401-
);
402-
}
403407
for arg in args {
404408
if let Operand::Move(place) = &arg.node {
405409
if is_within_packed(self.tcx, &self.body.local_decls, *place).is_some() {
@@ -1291,13 +1295,16 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
12911295
}
12921296
}
12931297
}
1294-
TerminatorKind::Call { func, .. } => {
1298+
TerminatorKind::Call { func, .. } | TerminatorKind::TailCall { func, .. } => {
12951299
let func_ty = func.ty(&self.body.local_decls, self.tcx);
12961300
match func_ty.kind() {
12971301
ty::FnPtr(..) | ty::FnDef(..) => {}
12981302
_ => self.fail(
12991303
location,
1300-
format!("encountered non-callable type {func_ty} in `Call` terminator"),
1304+
format!(
1305+
"encountered non-callable type {func_ty} in `{}` terminator",
1306+
terminator.kind.name()
1307+
),
13011308
),
13021309
}
13031310
}

compiler/rustc_middle/src/mir/pretty.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,16 @@ impl<'tcx> TerminatorKind<'tcx> {
775775
}
776776
write!(fmt, ")")
777777
}
778+
TailCall { func, args, .. } => {
779+
write!(fmt, "tailcall {func:?}(")?;
780+
for (index, arg) in args.iter().enumerate() {
781+
if index > 0 {
782+
write!(fmt, ", ")?;
783+
}
784+
write!(fmt, "{:?}", arg)?;
785+
}
786+
write!(fmt, ")")
787+
}
778788
Assert { cond, expected, msg, .. } => {
779789
write!(fmt, "assert(")?;
780790
if !expected {
@@ -839,7 +849,12 @@ impl<'tcx> TerminatorKind<'tcx> {
839849
pub fn fmt_successor_labels(&self) -> Vec<Cow<'static, str>> {
840850
use self::TerminatorKind::*;
841851
match *self {
842-
Return | UnwindResume | UnwindTerminate(_) | Unreachable | CoroutineDrop => vec![],
852+
Return
853+
| TailCall { .. }
854+
| UnwindResume
855+
| UnwindTerminate(_)
856+
| Unreachable
857+
| CoroutineDrop => vec![],
843858
Goto { .. } => vec!["".into()],
844859
SwitchInt { ref targets, .. } => targets
845860
.values

0 commit comments

Comments
 (0)