Skip to content

Commit c034362

Browse files
committed
Support tail calls in mir via TerminatorKind::TailCall
1 parent 8882507 commit c034362

File tree

35 files changed

+263
-29
lines changed

35 files changed

+263
-29
lines changed

compiler/rustc_borrowck/src/invalidation.rs

+6
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,12 @@ impl<'cx, 'tcx> Visitor<'tcx> for InvalidationGenerator<'cx, 'tcx> {
137137
}
138138
self.mutate_place(location, *destination, Deep);
139139
}
140+
TerminatorKind::TailCall { func, args, .. } => {
141+
self.consume_operand(location, func);
142+
for arg in args {
143+
self.consume_operand(location, arg);
144+
}
145+
}
140146
TerminatorKind::Assert { cond, expected: _, msg, target: _, unwind: _ } => {
141147
self.consume_operand(location, cond);
142148
use rustc_middle::mir::AssertKind;

compiler/rustc_borrowck/src/lib.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,12 @@ impl<'cx, 'tcx, R> rustc_mir_dataflow::ResultsVisitor<'cx, 'tcx, R> for MirBorro
719719
}
720720
self.mutate_place(loc, (*destination, span), Deep, flow_state);
721721
}
722+
TerminatorKind::TailCall { func, args, .. } => {
723+
self.consume_operand(loc, (func, span), flow_state);
724+
for arg in args {
725+
self.consume_operand(loc, (arg, span), flow_state);
726+
}
727+
}
722728
TerminatorKind::Assert { cond, expected: _, msg, target: _, unwind: _ } => {
723729
self.consume_operand(loc, (cond, span), flow_state);
724730
use rustc_middle::mir::AssertKind;
@@ -803,7 +809,11 @@ impl<'cx, 'tcx, R> rustc_mir_dataflow::ResultsVisitor<'cx, 'tcx, R> for MirBorro
803809
}
804810
}
805811

806-
TerminatorKind::Resume | TerminatorKind::Return | TerminatorKind::GeneratorDrop => {
812+
// FIXME(explicit_tail_calls): do we need to do something similar before the tail call?
813+
TerminatorKind::Resume
814+
| TerminatorKind::Return
815+
| TerminatorKind::TailCall { .. }
816+
| TerminatorKind::GeneratorDrop => {
807817
// Returning from the function implicitly kills storage for all locals and statics.
808818
// Often, the storage will already have been killed by an explicit
809819
// StorageDead, but we don't always emit those (notably on unwind paths),

compiler/rustc_borrowck/src/type_check/mod.rs

+18-3
Original file line numberDiff line numberDiff line change
@@ -1370,7 +1370,14 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
13701370
}
13711371
// FIXME: check the values
13721372
}
1373-
TerminatorKind::Call { func, args, destination, call_source, target, .. } => {
1373+
TerminatorKind::Call { func, args, .. }
1374+
| TerminatorKind::TailCall { func, args, .. } => {
1375+
let call_source = match term.kind {
1376+
TerminatorKind::Call { call_source, .. } => call_source,
1377+
TerminatorKind::TailCall { .. } => CallSource::Normal,
1378+
_ => unreachable!(),
1379+
};
1380+
13741381
self.check_operand(func, term_location);
13751382
for arg in args {
13761383
self.check_operand(arg, term_location);
@@ -1428,7 +1435,10 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
14281435
ConstraintCategory::Boring,
14291436
);
14301437
let sig = self.normalize(sig, term_location);
1431-
self.check_call_dest(body, term, &sig, *destination, *target, term_location);
1438+
1439+
if let TerminatorKind::Call { destination, target, .. } = term.kind {
1440+
self.check_call_dest(body, term, &sig, destination, target, term_location);
1441+
}
14321442

14331443
// The ordinary liveness rules will ensure that all
14341444
// regions in the type of the callee are live here. We
@@ -1446,7 +1456,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
14461456
.add_element(region_vid, term_location);
14471457
}
14481458

1449-
self.check_call_inputs(body, term, &sig, args, term_location, *call_source);
1459+
self.check_call_inputs(body, term, &sig, args, term_location, call_source);
14501460
}
14511461
TerminatorKind::Assert { cond, msg, .. } => {
14521462
self.check_operand(cond, term_location);
@@ -1639,6 +1649,11 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
16391649
span_mirbug!(self, block_data, "return on cleanup block")
16401650
}
16411651
}
1652+
TerminatorKind::TailCall { .. } => {
1653+
if is_cleanup {
1654+
span_mirbug!(self, block_data, "tailcall on cleanup block")
1655+
}
1656+
}
16421657
TerminatorKind::GeneratorDrop { .. } => {
16431658
if is_cleanup {
16441659
span_mirbug!(self, block_data, "generator_drop in cleanup block")

compiler/rustc_const_eval/src/interpret/terminator.rs

+2
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
115115
}
116116
}
117117

118+
TailCall { func: _, args: _, fn_span: _ } => todo!(),
119+
118120
Drop { place, target, unwind, replace: _ } => {
119121
let frame = self.frame();
120122
let ty = place.ty(&frame.body.local_decls, *self.tcx).ty;

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

+15-6
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ impl<'mir, 'tcx> Qualifs<'mir, 'tcx> {
129129
ccx: &'mir ConstCx<'mir, 'tcx>,
130130
tainted_by_errors: Option<ErrorGuaranteed>,
131131
) -> ConstQualifs {
132+
// FIXME(explicit_tail_calls): uhhhh I think we can return without return now, does it change anything
133+
132134
// Find the `Return` terminator if one exists.
133135
//
134136
// If no `Return` terminator exists, this MIR is divergent. Just return the conservative
@@ -695,7 +697,14 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
695697
self.super_terminator(terminator, location);
696698

697699
match &terminator.kind {
698-
TerminatorKind::Call { func, args, fn_span, call_source, .. } => {
700+
TerminatorKind::Call { func, args, fn_span, .. }
701+
| TerminatorKind::TailCall { func, args, fn_span, .. } => {
702+
let call_source = match terminator.kind {
703+
TerminatorKind::Call { call_source, .. } => call_source,
704+
TerminatorKind::TailCall { .. } => CallSource::Normal,
705+
_ => unreachable!(),
706+
};
707+
699708
let ConstCx { tcx, body, param_env, .. } = *self.ccx;
700709
let caller = self.def_id();
701710

@@ -748,7 +757,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
748757
callee,
749758
substs,
750759
span: *fn_span,
751-
call_source: *call_source,
760+
call_source,
752761
feature: Some(sym::const_trait_impl),
753762
});
754763
return;
@@ -790,7 +799,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
790799
callee,
791800
substs,
792801
span: *fn_span,
793-
call_source: *call_source,
802+
call_source,
794803
feature: None,
795804
});
796805

@@ -816,7 +825,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
816825
callee,
817826
substs,
818827
span: *fn_span,
819-
call_source: *call_source,
828+
call_source,
820829
feature: None,
821830
});
822831
return;
@@ -859,7 +868,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
859868
callee,
860869
substs,
861870
span: *fn_span,
862-
call_source: *call_source,
871+
call_source,
863872
feature: None,
864873
});
865874
return;
@@ -919,7 +928,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
919928
callee,
920929
substs,
921930
span: *fn_span,
922-
call_source: *call_source,
931+
call_source,
923932
feature: None,
924933
});
925934
return;

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

+1
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ impl<'tcx> Visitor<'tcx> for CheckLiveDrops<'_, 'tcx> {
106106

107107
mir::TerminatorKind::Terminate
108108
| mir::TerminatorKind::Call { .. }
109+
| mir::TerminatorKind::TailCall { .. }
109110
| mir::TerminatorKind::Assert { .. }
110111
| mir::TerminatorKind::FalseEdge { .. }
111112
| mir::TerminatorKind::FalseUnwind { .. }

compiler/rustc_const_eval/src/transform/validate.rs

+14-6
Original file line numberDiff line numberDiff line change
@@ -969,7 +969,9 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
969969
self.check_edge(location, *target, EdgeKind::Normal);
970970
self.check_unwind_edge(location, *unwind);
971971
}
972-
TerminatorKind::Call { func, args, destination, target, unwind, .. } => {
972+
973+
TerminatorKind::Call { func, args, .. }
974+
| TerminatorKind::TailCall { func, args, .. } => {
973975
let func_ty = func.ty(&self.body.local_decls, self.tcx);
974976
match func_ty.kind() {
975977
ty::FnPtr(..) | ty::FnDef(..) => {}
@@ -978,16 +980,21 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
978980
format!("encountered non-callable type {} in `Call` terminator", func_ty),
979981
),
980982
}
981-
if let Some(target) = target {
982-
self.check_edge(location, *target, EdgeKind::Normal);
983+
984+
if let TerminatorKind::Call { target, unwind, .. } = terminator.kind {
985+
if let Some(target) = target {
986+
self.check_edge(location, target, EdgeKind::Normal);
987+
}
988+
self.check_unwind_edge(location, unwind);
983989
}
984-
self.check_unwind_edge(location, *unwind);
985990

986991
// The call destination place and Operand::Move place used as an argument might be
987992
// passed by a reference to the callee. Consequently they must be non-overlapping.
988993
// Currently this simply checks for duplicate places.
989994
self.place_cache.clear();
990-
self.place_cache.push(destination.as_ref());
995+
if let TerminatorKind::Call { destination, .. } = terminator.kind {
996+
self.place_cache.push(destination.as_ref());
997+
}
991998
for arg in args {
992999
if let Operand::Move(place) = arg {
9931000
self.place_cache.push(place.as_ref());
@@ -1001,7 +1008,8 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
10011008
self.fail(
10021009
location,
10031010
format!(
1004-
"encountered overlapping memory in `Call` terminator: {:?}",
1011+
"encountered overlapping memory in `{}` terminator: {:?}",
1012+
terminator.kind.name(),
10051013
terminator.kind,
10061014
),
10071015
);

compiler/rustc_middle/src/mir/spanview.rs

+1
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ pub fn terminator_kind_name(term: &Terminator<'_>) -> &'static str {
270270
Call { .. } => "Call",
271271
Assert { .. } => "Assert",
272272
Yield { .. } => "Yield",
273+
TailCall { .. } => "TailCall",
273274
GeneratorDrop => "GeneratorDrop",
274275
FalseEdge { .. } => "FalseEdge",
275276
FalseUnwind { .. } => "FalseUnwind",

compiler/rustc_middle/src/mir/syntax.rs

+31
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,36 @@ pub enum TerminatorKind<'tcx> {
671671
fn_span: Span,
672672
},
673673

674+
/// Tail call.
675+
///
676+
/// Roughly speaking this is a chimera of [`Call`] and [`Return`], with some caveats.
677+
/// Semantically tail calls consists of two actions:
678+
/// - pop of the current stack frame
679+
/// - a call to the `func`, with the return address of the **current** caller
680+
/// - so that a `return` inside `func` returns to the caller of the caller
681+
/// of the function that is currently being executed
682+
///
683+
/// Note that in difference with [`Call`] this is missing
684+
/// - `destination` (because it's always the return place)
685+
/// - `target` (because it's always taken from the current stack frame)
686+
/// - `unwind` (because it's always taken from the current stack frame)
687+
///
688+
/// [`Call`]: TerminatorKind::Call
689+
/// [`Return`]: TerminatorKind::Return
690+
TailCall {
691+
/// The function that’s being called.
692+
func: Operand<'tcx>,
693+
/// Arguments the function is called with.
694+
/// These are owned by the callee, which is free to modify them.
695+
/// This allows the memory occupied by "by-value" arguments to be
696+
/// reused across function calls without duplicating the contents.
697+
args: Vec<Operand<'tcx>>,
698+
// FIXME(explicit_tail_calls): should we have the span for `become`? is this span accurate? do we need it?
699+
/// This `Span` is the span of the function, without the dot and receiver
700+
/// (e.g. `foo(a, b)` in `x.foo(a, b)`
701+
fn_span: Span,
702+
},
703+
674704
/// Evaluates the operand, which must have type `bool`. If it is not equal to `expected`,
675705
/// initiates a panic. Initiating a panic corresponds to a `Call` terminator with some
676706
/// unspecified constant as the function to call, all the operands stored in the `AssertMessage`
@@ -796,6 +826,7 @@ impl TerminatorKind<'_> {
796826
TerminatorKind::Unreachable => "Unreachable",
797827
TerminatorKind::Drop { .. } => "Drop",
798828
TerminatorKind::Call { .. } => "Call",
829+
TerminatorKind::TailCall { .. } => "TailCall",
799830
TerminatorKind::Assert { .. } => "Assert",
800831
TerminatorKind::Yield { .. } => "Yield",
801832
TerminatorKind::GeneratorDrop => "GeneratorDrop",

compiler/rustc_middle/src/mir/terminator.rs

+15-1
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ impl<'tcx> TerminatorKind<'tcx> {
158158
| Terminate
159159
| GeneratorDrop
160160
| Return
161+
| TailCall { .. }
161162
| Unreachable
162163
| Call { target: None, unwind: _, .. }
163164
| InlineAsm { destination: None, unwind: _, .. } => {
@@ -200,6 +201,7 @@ impl<'tcx> TerminatorKind<'tcx> {
200201
| Terminate
201202
| GeneratorDrop
202203
| Return
204+
| TailCall { .. }
203205
| Unreachable
204206
| Call { target: None, unwind: _, .. }
205207
| InlineAsm { destination: None, unwind: _, .. } => None.into_iter().chain(&mut []),
@@ -216,6 +218,7 @@ impl<'tcx> TerminatorKind<'tcx> {
216218
| TerminatorKind::Resume
217219
| TerminatorKind::Terminate
218220
| TerminatorKind::Return
221+
| TerminatorKind::TailCall { .. }
219222
| TerminatorKind::Unreachable
220223
| TerminatorKind::GeneratorDrop
221224
| TerminatorKind::Yield { .. }
@@ -235,6 +238,7 @@ impl<'tcx> TerminatorKind<'tcx> {
235238
| TerminatorKind::Resume
236239
| TerminatorKind::Terminate
237240
| TerminatorKind::Return
241+
| TerminatorKind::TailCall { .. }
238242
| TerminatorKind::Unreachable
239243
| TerminatorKind::GeneratorDrop
240244
| TerminatorKind::Yield { .. }
@@ -326,6 +330,16 @@ impl<'tcx> TerminatorKind<'tcx> {
326330
}
327331
write!(fmt, ")")
328332
}
333+
TailCall { func, args, .. } => {
334+
write!(fmt, "tailcall {func:?}(")?;
335+
for (index, arg) in args.iter().enumerate() {
336+
if index > 0 {
337+
write!(fmt, ", ")?;
338+
}
339+
write!(fmt, "{:?}", arg)?;
340+
}
341+
write!(fmt, ")")
342+
}
329343
Assert { cond, expected, msg, .. } => {
330344
write!(fmt, "assert(")?;
331345
if !expected {
@@ -390,7 +404,7 @@ impl<'tcx> TerminatorKind<'tcx> {
390404
pub fn fmt_successor_labels(&self) -> Vec<Cow<'static, str>> {
391405
use self::TerminatorKind::*;
392406
match *self {
393-
Return | Resume | Terminate | Unreachable | GeneratorDrop => vec![],
407+
Return | TailCall { .. } | Resume | Terminate | Unreachable | GeneratorDrop => vec![],
394408
Goto { .. } => vec!["".into()],
395409
SwitchInt { ref targets, .. } => targets
396410
.values

compiler/rustc_middle/src/mir/visit.rs

+11
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,17 @@ macro_rules! make_mir_visitor {
533533
);
534534
}
535535

536+
TerminatorKind::TailCall {
537+
func,
538+
args,
539+
fn_span: _,
540+
} => {
541+
self.visit_operand(func, location);
542+
for arg in args {
543+
self.visit_operand(arg, location);
544+
}
545+
},
546+
536547
TerminatorKind::Assert {
537548
cond,
538549
expected: _,

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

+28-7
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,34 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
9999
BreakableTarget::Return,
100100
source_info,
101101
),
102-
// FIXME(explicit_tail_calls): properly lower tail calls here
103-
ExprKind::Become { value } => this.break_scope(
104-
block,
105-
Some(&this.thir[value]),
106-
BreakableTarget::Return,
107-
source_info,
108-
),
102+
ExprKind::Become { value } => {
103+
let v = &this.thir[value];
104+
let ExprKind::Scope { value, .. } = v.kind
105+
else { span_bug!(v.span, "`thir_check_tail_calls` should have disallowed this {v:?}") };
106+
107+
let v = &this.thir[value];
108+
let ExprKind::Call { ref args, fun, fn_span, .. } = v.kind
109+
else { span_bug!(v.span, "`thir_check_tail_calls` should have disallowed this {v:?}") };
110+
111+
let fun = unpack!(block = this.as_local_operand(block, &this.thir[fun]));
112+
let args: Vec<_> = args
113+
.into_iter()
114+
.copied()
115+
.map(|arg| unpack!(block = this.as_local_call_operand(block, &this.thir[arg])))
116+
.collect();
117+
118+
this.record_operands_moved(&args);
119+
120+
debug!("expr_into_dest: fn_span={:?}", fn_span);
121+
122+
this.cfg.terminate(
123+
block,
124+
source_info,
125+
TerminatorKind::TailCall { func: fun, args, fn_span },
126+
);
127+
128+
this.cfg.start_new_block().unit()
129+
}
109130
_ => {
110131
assert!(
111132
statement_scope.is_some(),

0 commit comments

Comments
 (0)