Skip to content

Commit 484152d

Browse files
committed
Support tail calls in mir via TerminatorKind::TailCall
1 parent e2cf31a commit 484152d

File tree

41 files changed

+328
-88
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
-88
lines changed

compiler/rustc_borrowck/src/lib.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,12 @@ impl<'a, 'mir, 'tcx, R> rustc_mir_dataflow::ResultsVisitor<'mir, 'tcx, R>
728728
}
729729
self.mutate_place(loc, (*destination, span), Deep, flow_state);
730730
}
731+
TerminatorKind::TailCall { func, args, fn_span: _ } => {
732+
self.consume_operand(loc, (func, span), flow_state);
733+
for arg in args {
734+
self.consume_operand(loc, (&arg.node, arg.span), flow_state);
735+
}
736+
}
731737
TerminatorKind::Assert { cond, expected: _, msg, target: _, unwind: _ } => {
732738
self.consume_operand(loc, (cond, span), flow_state);
733739
if let AssertKind::BoundsCheck { len, index } = &**msg {
@@ -814,9 +820,8 @@ impl<'a, 'mir, 'tcx, R> rustc_mir_dataflow::ResultsVisitor<'mir, 'tcx, R>
814820

815821
TerminatorKind::UnwindResume
816822
| TerminatorKind::Return
823+
| TerminatorKind::TailCall { .. }
817824
| TerminatorKind::CoroutineDrop => {
818-
// Returning from the function implicitly kills storage for all locals and statics.
819-
// Often, the storage will already have been killed by an explicit
820825
// StorageDead, but we don't always emit those (notably on unwind paths),
821826
// so this "extra check" serves as a kind of backup.
822827
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
@@ -125,6 +125,12 @@ impl<'cx, 'tcx> Visitor<'tcx> for LoanInvalidationsGenerator<'cx, 'tcx> {
125125
}
126126
self.mutate_place(location, *destination, Deep);
127127
}
128+
TerminatorKind::TailCall { func, args, .. } => {
129+
self.consume_operand(location, func);
130+
for arg in args {
131+
self.consume_operand(location, &arg.node);
132+
}
133+
}
128134
TerminatorKind::Assert { cond, expected: _, msg, target: _, unwind: _ } => {
129135
self.consume_operand(location, cond);
130136
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
@@ -1352,7 +1352,14 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
13521352
}
13531353
// FIXME: check the values
13541354
}
1355-
TerminatorKind::Call { func, args, destination, call_source, target, .. } => {
1355+
TerminatorKind::Call { func, args, .. }
1356+
| TerminatorKind::TailCall { func, args, .. } => {
1357+
let call_source = match term.kind {
1358+
TerminatorKind::Call { call_source, .. } => call_source,
1359+
TerminatorKind::TailCall { .. } => CallSource::Normal,
1360+
_ => unreachable!(),
1361+
};
1362+
13561363
self.check_operand(func, term_location);
13571364
for arg in args {
13581365
self.check_operand(&arg.node, term_location);
@@ -1425,7 +1432,9 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
14251432
);
14261433
}
14271434

1428-
self.check_call_dest(body, term, &sig, *destination, *target, term_location);
1435+
if let TerminatorKind::Call { destination, target, .. } = term.kind {
1436+
self.check_call_dest(body, term, &sig, destination, target, term_location);
1437+
}
14291438

14301439
// The ordinary liveness rules will ensure that all
14311440
// regions in the type of the callee are live here. We
@@ -1443,7 +1452,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
14431452
.add_location(region_vid, term_location);
14441453
}
14451454

1446-
self.check_call_inputs(body, term, func, &sig, args, term_location, *call_source);
1455+
self.check_call_inputs(body, term, func, &sig, args, term_location, call_source);
14471456
}
14481457
TerminatorKind::Assert { cond, msg, .. } => {
14491458
self.check_operand(cond, term_location);
@@ -1675,6 +1684,11 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
16751684
span_mirbug!(self, block_data, "return on cleanup block")
16761685
}
16771686
}
1687+
TerminatorKind::TailCall { .. } => {
1688+
if is_cleanup {
1689+
span_mirbug!(self, block_data, "tailcall on cleanup block")
1690+
}
1691+
}
16781692
TerminatorKind::CoroutineDrop { .. } => {
16791693
if is_cleanup {
16801694
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
@@ -491,6 +491,11 @@ fn codegen_fn_body(fx: &mut FunctionCx<'_, '_, '_>, start_block: Block) {
491491
)
492492
});
493493
}
494+
// FIXME(explicit_tail_calls): add support for tail calls to the cranelift backend, once cranelift supports tail calls
495+
TerminatorKind::TailCall { fn_span, .. } => span_bug!(
496+
*fn_span,
497+
"tail calls are not yet supported in `rustc_codegen_cranelift` backend"
498+
),
494499
TerminatorKind::InlineAsm {
495500
template,
496501
operands,

compiler/rustc_codegen_cranelift/src/constant.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,7 @@ pub(crate) fn mir_operand_get_const_val<'tcx>(
565565
{
566566
return None;
567567
}
568+
TerminatorKind::TailCall { .. } => return None,
568569
TerminatorKind::Call { .. } => {}
569570
}
570571
}

compiler/rustc_codegen_ssa/src/mir/analyze.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ pub fn cleanup_kinds(mir: &mir::Body<'_>) -> IndexVec<mir::BasicBlock, CleanupKi
281281
| TerminatorKind::UnwindResume
282282
| TerminatorKind::UnwindTerminate(_)
283283
| TerminatorKind::Return
284+
| TerminatorKind::TailCall { .. }
284285
| TerminatorKind::CoroutineDrop
285286
| TerminatorKind::Unreachable
286287
| TerminatorKind::SwitchInt { .. }

compiler/rustc_codegen_ssa/src/mir/block.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1389,6 +1389,13 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
13891389
fn_span,
13901390
mergeable_succ(),
13911391
),
1392+
mir::TerminatorKind::TailCall { .. } => {
1393+
// FIXME(explicit_tail_calls): implement tail calls in ssa backend
1394+
span_bug!(
1395+
terminator.source_info.span,
1396+
"`TailCall` terminator is not yet supported by `rustc_codegen_ssa`"
1397+
)
1398+
}
13921399
mir::TerminatorKind::CoroutineDrop | mir::TerminatorKind::Yield { .. } => {
13931400
bug!("coroutine ops in codegen")
13941401
}

compiler/rustc_const_eval/src/check_consts/check.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ impl<'mir, 'tcx> Qualifs<'mir, 'tcx> {
135135
ccx: &'mir ConstCx<'mir, 'tcx>,
136136
tainted_by_errors: Option<ErrorGuaranteed>,
137137
) -> ConstQualifs {
138+
// FIXME(explicit_tail_calls): uhhhh I think we can return without return now, does it change anything
139+
138140
// Find the `Return` terminator if one exists.
139141
//
140142
// If no `Return` terminator exists, this MIR is divergent. Just return the conservative
@@ -711,7 +713,14 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
711713
self.super_terminator(terminator, location);
712714

713715
match &terminator.kind {
714-
TerminatorKind::Call { func, args, fn_span, call_source, .. } => {
716+
TerminatorKind::Call { func, args, fn_span, .. }
717+
| TerminatorKind::TailCall { func, args, fn_span, .. } => {
718+
let call_source = match terminator.kind {
719+
TerminatorKind::Call { call_source, .. } => call_source,
720+
TerminatorKind::TailCall { .. } => CallSource::Normal,
721+
_ => unreachable!(),
722+
};
723+
715724
let ConstCx { tcx, body, param_env, .. } = *self.ccx;
716725
let caller = self.def_id();
717726

@@ -783,7 +792,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
783792
callee,
784793
args: fn_args,
785794
span: *fn_span,
786-
call_source: *call_source,
795+
call_source,
787796
feature: Some(if tcx.features().const_trait_impl {
788797
sym::effects
789798
} else {
@@ -830,7 +839,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
830839
callee,
831840
args: fn_args,
832841
span: *fn_span,
833-
call_source: *call_source,
842+
call_source,
834843
feature: None,
835844
});
836845
return;

compiler/rustc_const_eval/src/check_consts/post_drop_elaboration.rs

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

109109
mir::TerminatorKind::UnwindTerminate(_)
110110
| mir::TerminatorKind::Call { .. }
111+
| mir::TerminatorKind::TailCall { .. }
111112
| mir::TerminatorKind::Assert { .. }
112113
| mir::TerminatorKind::FalseEdge { .. }
113114
| mir::TerminatorKind::FalseUnwind { .. }

compiler/rustc_const_eval/src/interpret/terminator.rs

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

175+
TailCall { func: _, args: _, fn_span: _ } => todo!(),
176+
175177
Drop { place, target, unwind, replace: _ } => {
176178
let place = self.eval_place(place)?;
177179
let instance = Instance::resolve_drop_in_place(*self.tcx, place.layout.ty);

compiler/rustc_middle/src/mir/pretty.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -845,6 +845,16 @@ impl<'tcx> TerminatorKind<'tcx> {
845845
}
846846
write!(fmt, ")")
847847
}
848+
TailCall { func, args, .. } => {
849+
write!(fmt, "tailcall {func:?}(")?;
850+
for (index, arg) in args.iter().enumerate() {
851+
if index > 0 {
852+
write!(fmt, ", ")?;
853+
}
854+
write!(fmt, "{:?}", arg)?;
855+
}
856+
write!(fmt, ")")
857+
}
848858
Assert { cond, expected, msg, .. } => {
849859
write!(fmt, "assert(")?;
850860
if !expected {
@@ -912,7 +922,12 @@ impl<'tcx> TerminatorKind<'tcx> {
912922
pub fn fmt_successor_labels(&self) -> Vec<Cow<'static, str>> {
913923
use self::TerminatorKind::*;
914924
match *self {
915-
Return | UnwindResume | UnwindTerminate(_) | Unreachable | CoroutineDrop => vec![],
925+
Return
926+
| TailCall { .. }
927+
| UnwindResume
928+
| UnwindTerminate(_)
929+
| Unreachable
930+
| CoroutineDrop => vec![],
916931
Goto { .. } => vec!["".into()],
917932
SwitchInt { ref targets, .. } => targets
918933
.values

compiler/rustc_middle/src/mir/syntax.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,36 @@ pub enum TerminatorKind<'tcx> {
744744
fn_span: Span,
745745
},
746746

747+
/// Tail call.
748+
///
749+
/// Roughly speaking this is a chimera of [`Call`] and [`Return`], with some caveats.
750+
/// Semantically tail calls consists of two actions:
751+
/// - pop of the current stack frame
752+
/// - a call to the `func`, with the return address of the **current** caller
753+
/// - so that a `return` inside `func` returns to the caller of the caller
754+
/// of the function that is currently being executed
755+
///
756+
/// Note that in difference with [`Call`] this is missing
757+
/// - `destination` (because it's always the return place)
758+
/// - `target` (because it's always taken from the current stack frame)
759+
/// - `unwind` (because it's always taken from the current stack frame)
760+
///
761+
/// [`Call`]: TerminatorKind::Call
762+
/// [`Return`]: TerminatorKind::Return
763+
TailCall {
764+
/// The function that’s being called.
765+
func: Operand<'tcx>,
766+
/// Arguments the function is called with.
767+
/// These are owned by the callee, which is free to modify them.
768+
/// This allows the memory occupied by "by-value" arguments to be
769+
/// reused across function calls without duplicating the contents.
770+
args: Vec<Spanned<Operand<'tcx>>>,
771+
// FIXME(explicit_tail_calls): should we have the span for `become`? is this span accurate? do we need it?
772+
/// This `Span` is the span of the function, without the dot and receiver
773+
/// (e.g. `foo(a, b)` in `x.foo(a, b)`
774+
fn_span: Span,
775+
},
776+
747777
/// Evaluates the operand, which must have type `bool`. If it is not equal to `expected`,
748778
/// initiates a panic. Initiating a panic corresponds to a `Call` terminator with some
749779
/// unspecified constant as the function to call, all the operands stored in the `AssertMessage`
@@ -870,6 +900,7 @@ impl TerminatorKind<'_> {
870900
TerminatorKind::Unreachable => "Unreachable",
871901
TerminatorKind::Drop { .. } => "Drop",
872902
TerminatorKind::Call { .. } => "Call",
903+
TerminatorKind::TailCall { .. } => "TailCall",
873904
TerminatorKind::Assert { .. } => "Assert",
874905
TerminatorKind::Yield { .. } => "Yield",
875906
TerminatorKind::CoroutineDrop => "CoroutineDrop",

compiler/rustc_middle/src/mir/terminator.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,7 @@ mod helper {
439439
| CoroutineDrop
440440
| Return
441441
| Unreachable
442+
| TailCall { .. }
442443
| Call { target: None, unwind: _, .. } => (&[]).into_iter().copied().chain(None),
443444
InlineAsm { ref targets, unwind: UnwindAction::Cleanup(u), .. } => {
444445
targets.iter().copied().chain(Some(u))
@@ -479,6 +480,7 @@ mod helper {
479480
| CoroutineDrop
480481
| Return
481482
| Unreachable
483+
| TailCall { .. }
482484
| Call { target: None, unwind: _, .. } => (&mut []).into_iter().chain(None),
483485
InlineAsm { ref mut targets, unwind: UnwindAction::Cleanup(ref mut u), .. } => {
484486
targets.iter_mut().chain(Some(u))
@@ -501,6 +503,7 @@ impl<'tcx> TerminatorKind<'tcx> {
501503
| TerminatorKind::UnwindResume
502504
| TerminatorKind::UnwindTerminate(_)
503505
| TerminatorKind::Return
506+
| TerminatorKind::TailCall { .. }
504507
| TerminatorKind::Unreachable
505508
| TerminatorKind::CoroutineDrop
506509
| TerminatorKind::Yield { .. }
@@ -521,6 +524,7 @@ impl<'tcx> TerminatorKind<'tcx> {
521524
| TerminatorKind::UnwindResume
522525
| TerminatorKind::UnwindTerminate(_)
523526
| TerminatorKind::Return
527+
| TerminatorKind::TailCall { .. }
524528
| TerminatorKind::Unreachable
525529
| TerminatorKind::CoroutineDrop
526530
| TerminatorKind::Yield { .. }
@@ -606,9 +610,12 @@ impl<'tcx> TerminatorKind<'tcx> {
606610
pub fn edges(&self) -> TerminatorEdges<'_, 'tcx> {
607611
use TerminatorKind::*;
608612
match *self {
609-
Return | UnwindResume | UnwindTerminate(_) | CoroutineDrop | Unreachable => {
610-
TerminatorEdges::None
611-
}
613+
Return
614+
| TailCall { .. }
615+
| UnwindResume
616+
| UnwindTerminate(_)
617+
| CoroutineDrop
618+
| Unreachable => TerminatorEdges::None,
612619

613620
Goto { target } => TerminatorEdges::Single(target),
614621

compiler/rustc_middle/src/mir/visit.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,17 @@ macro_rules! make_mir_visitor {
540540
);
541541
}
542542

543+
TerminatorKind::TailCall {
544+
func,
545+
args,
546+
fn_span: _,
547+
} => {
548+
self.visit_operand(func, location);
549+
for arg in args {
550+
self.visit_operand(&$($mutability)? arg.node, location);
551+
}
552+
},
553+
543554
TerminatorKind::Assert {
544555
cond,
545556
expected: _,

compiler/rustc_mir_build/src/build/expr/stmt.rs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ use crate::build::scope::BreakableTarget;
22
use crate::build::{BlockAnd, BlockAndExtension, BlockFrame, Builder};
33
use rustc_middle::middle::region;
44
use rustc_middle::mir::*;
5+
use rustc_middle::span_bug;
56
use rustc_middle::thir::*;
7+
use rustc_span::source_map::Spanned;
68
use tracing::debug;
79

810
impl<'a, 'tcx> Builder<'a, 'tcx> {
@@ -91,9 +93,38 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
9193
ExprKind::Return { value } => {
9294
this.break_scope(block, value, BreakableTarget::Return, source_info)
9395
}
94-
// FIXME(explicit_tail_calls): properly lower tail calls here
9596
ExprKind::Become { value } => {
96-
this.break_scope(block, Some(value), BreakableTarget::Return, source_info)
97+
let v = &this.thir[value];
98+
let ExprKind::Scope { value, .. } = v.kind else {
99+
span_bug!(v.span, "`thir_check_tail_calls` should have disallowed this {v:?}")
100+
};
101+
102+
let v = &this.thir[value];
103+
let ExprKind::Call { ref args, fun, fn_span, .. } = v.kind else {
104+
span_bug!(v.span, "`thir_check_tail_calls` should have disallowed this {v:?}")
105+
};
106+
107+
let fun = unpack!(block = this.as_local_operand(block, fun));
108+
let args: Vec<_> = args
109+
.into_iter()
110+
.copied()
111+
.map(|arg| Spanned {
112+
node: unpack!(block = this.as_local_call_operand(block, arg)),
113+
span: this.thir.exprs[arg].span,
114+
})
115+
.collect();
116+
117+
this.record_operands_moved(&args);
118+
119+
debug!("expr_into_dest: fn_span={:?}", fn_span);
120+
121+
this.cfg.terminate(
122+
block,
123+
source_info,
124+
TerminatorKind::TailCall { func: fun, args, fn_span },
125+
);
126+
127+
this.cfg.start_new_block().unit()
97128
}
98129
_ => {
99130
assert!(

compiler/rustc_mir_build/src/build/scope.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1523,6 +1523,7 @@ impl<'tcx> DropTreeBuilder<'tcx> for Unwind {
15231523
| TerminatorKind::UnwindResume
15241524
| TerminatorKind::UnwindTerminate(_)
15251525
| TerminatorKind::Return
1526+
| TerminatorKind::TailCall { .. }
15261527
| TerminatorKind::Unreachable
15271528
| TerminatorKind::Yield { .. }
15281529
| TerminatorKind::CoroutineDrop

compiler/rustc_mir_build/src/lints.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,8 @@ impl<'mir, 'tcx, C: TerminatorClassifier<'tcx>> TriColorVisitor<BasicBlocks<'tcx
196196
| TerminatorKind::CoroutineDrop
197197
| TerminatorKind::UnwindResume
198198
| TerminatorKind::Return
199+
// FIXME(explicit_tail_calls) Is this right??
200+
| TerminatorKind::TailCall { .. }
199201
| TerminatorKind::Unreachable
200202
| TerminatorKind::Yield { .. } => ControlFlow::Break(NonRecursive),
201203

0 commit comments

Comments
 (0)