Skip to content

Commit 4005890

Browse files
committed
Support tail calls in mir via TerminatorKind::TailCall
1 parent cffd2df commit 4005890

Some content is hidden

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

49 files changed

+581
-32
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

+19-3
Original file line numberDiff line numberDiff line change
@@ -1370,7 +1370,15 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
13701370
}
13711371
// FIXME: check the values
13721372
}
1373-
TerminatorKind::Call { func, args, destination, from_hir_call, target, .. } => {
1373+
TerminatorKind::Call { func, args, .. }
1374+
| TerminatorKind::TailCall { func, args, .. } => {
1375+
let from_hir_call = matches!(
1376+
&term.kind,
1377+
TerminatorKind::Call { from_hir_call: true, .. }
1378+
// tail calls don't support overloaded operators
1379+
| TerminatorKind::TailCall { .. }
1380+
);
1381+
13741382
self.check_operand(func, term_location);
13751383
for arg in args {
13761384
self.check_operand(arg, term_location);
@@ -1426,7 +1434,10 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
14261434
ConstraintCategory::Boring,
14271435
);
14281436
let sig = self.normalize(sig, term_location);
1429-
self.check_call_dest(body, term, &sig, *destination, *target, term_location);
1437+
1438+
if let TerminatorKind::Call { destination, target, .. } = term.kind {
1439+
self.check_call_dest(body, term, &sig, destination, target, term_location);
1440+
}
14301441

14311442
// The ordinary liveness rules will ensure that all
14321443
// regions in the type of the callee are live here. We
@@ -1444,7 +1455,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
14441455
.add_element(region_vid, term_location);
14451456
}
14461457

1447-
self.check_call_inputs(body, term, &sig, args, term_location, *from_hir_call);
1458+
self.check_call_inputs(body, term, &sig, args, term_location, from_hir_call);
14481459
}
14491460
TerminatorKind::Assert { cond, msg, .. } => {
14501461
self.check_operand(cond, term_location);
@@ -1637,6 +1648,11 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
16371648
span_mirbug!(self, block_data, "return on cleanup block")
16381649
}
16391650
}
1651+
TerminatorKind::TailCall { .. } => {
1652+
if is_cleanup {
1653+
span_mirbug!(self, block_data, "tailcall on cleanup block")
1654+
}
1655+
}
16401656
TerminatorKind::GeneratorDrop { .. } => {
16411657
if is_cleanup {
16421658
span_mirbug!(self, block_data, "generator_drop in cleanup block")

compiler/rustc_codegen_ssa/src/mir/analyze.rs

+1
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ pub fn cleanup_kinds(mir: &mir::Body<'_>) -> IndexVec<mir::BasicBlock, CleanupKi
292292
| TerminatorKind::SwitchInt { .. }
293293
| TerminatorKind::Yield { .. }
294294
| TerminatorKind::FalseEdge { .. }
295+
| TerminatorKind::TailCall { .. }
295296
| TerminatorKind::FalseUnwind { .. } => { /* nothing to do */ }
296297
TerminatorKind::Call { unwind, .. }
297298
| TerminatorKind::InlineAsm { unwind, .. }

compiler/rustc_codegen_ssa/src/mir/block.rs

+9
Original file line numberDiff line numberDiff line change
@@ -1294,6 +1294,15 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
12941294
fn_span,
12951295
mergeable_succ(),
12961296
),
1297+
1298+
mir::TerminatorKind::TailCall { .. } => {
1299+
// FIXME(explicit_tail_calls): implement tail calls in ssa backend
1300+
span_bug!(
1301+
terminator.source_info.span,
1302+
"`TailCall` terminator is not yet supported by `rustc_codegen_ssa`"
1303+
)
1304+
}
1305+
12971306
mir::TerminatorKind::GeneratorDrop | mir::TerminatorKind::Yield { .. } => {
12981307
bug!("generator ops in codegen")
12991308
}

compiler/rustc_const_eval/src/interpret/terminator.rs

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

118+
TailCall { ref func, ref args, fn_span: _ } => {
119+
// FIXME: a lot of code here is duplicated with normal calls, can we refactor this?
120+
let old_frame_idx = self.frame_idx();
121+
let func = self.eval_operand(func, None)?;
122+
let args = self.eval_operands(args)?;
123+
124+
let fn_sig_binder = func.layout.ty.fn_sig(*self.tcx);
125+
let fn_sig =
126+
self.tcx.normalize_erasing_late_bound_regions(self.param_env, fn_sig_binder);
127+
let extra_args = &args[fn_sig.inputs().len()..];
128+
let extra_args =
129+
self.tcx.mk_type_list_from_iter(extra_args.iter().map(|arg| arg.layout.ty));
130+
131+
let (fn_val, fn_abi, with_caller_location) = match *func.layout.ty.kind() {
132+
ty::FnPtr(_sig) => {
133+
let fn_ptr = self.read_pointer(&func)?;
134+
let fn_val = self.get_ptr_fn(fn_ptr)?;
135+
(fn_val, self.fn_abi_of_fn_ptr(fn_sig_binder, extra_args)?, false)
136+
}
137+
ty::FnDef(def_id, substs) => {
138+
let instance = self.resolve(def_id, substs)?;
139+
(
140+
FnVal::Instance(instance),
141+
self.fn_abi_of_instance(instance, extra_args)?,
142+
instance.def.requires_caller_location(*self.tcx),
143+
)
144+
}
145+
_ => span_bug!(
146+
terminator.source_info.span,
147+
"invalid callee of type {:?}",
148+
func.layout.ty
149+
),
150+
};
151+
152+
// FIXME(explicit_tail_calls): maybe we need the drop here?...
153+
154+
// This is the "canonical" implementation of tails calls,
155+
// a pop of the current stack frame, followed by a normal call
156+
// which pushes a new stack frame, with the return address from
157+
// the popped stack frame.
158+
//
159+
// Note that we can't use `pop_stack_frame` as it "executes"
160+
// the goto to the return block, but we don't want to,
161+
// only the tail called function should return to the current
162+
// return block.
163+
let Some(prev_frame) = self.stack_mut().pop()
164+
else { span_bug!(terminator.source_info.span, "empty stack while evaluating this tail call") };
165+
166+
let StackPopCleanup::Goto { ret, unwind } = prev_frame.return_to_block
167+
else { span_bug!(terminator.source_info.span, "tail call with the root stack frame") };
168+
169+
self.eval_fn_call(
170+
fn_val,
171+
(fn_sig.abi, fn_abi),
172+
&args,
173+
with_caller_location,
174+
&prev_frame.return_place,
175+
ret,
176+
unwind,
177+
)?;
178+
179+
if self.frame_idx() != old_frame_idx {
180+
span_bug!(
181+
terminator.source_info.span,
182+
"evaluating this tail call pushed a new stack frame"
183+
);
184+
}
185+
}
186+
118187
Drop { place, target, unwind, replace: _ } => {
119188
let frame = self.frame();
120189
let ty = place.ty(&frame.body.local_decls, *self.tcx).ty;

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

+16-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
@@ -702,7 +704,15 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
702704
self.super_terminator(terminator, location);
703705

704706
match &terminator.kind {
705-
TerminatorKind::Call { func, args, fn_span, from_hir_call, .. } => {
707+
TerminatorKind::Call { func, args, fn_span, .. }
708+
| TerminatorKind::TailCall { func, args, fn_span, .. } => {
709+
let from_hir_call = matches!(
710+
&terminator.kind,
711+
TerminatorKind::Call { from_hir_call: true, .. }
712+
// tail calls don't support overloaded operators
713+
| TerminatorKind::TailCall { .. }
714+
);
715+
706716
let ConstCx { tcx, body, param_env, .. } = *self.ccx;
707717
let caller = self.def_id();
708718

@@ -755,7 +765,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
755765
callee,
756766
substs,
757767
span: *fn_span,
758-
from_hir_call: *from_hir_call,
768+
from_hir_call,
759769
feature: Some(sym::const_trait_impl),
760770
});
761771
return;
@@ -788,7 +798,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
788798
callee,
789799
substs,
790800
span: *fn_span,
791-
from_hir_call: *from_hir_call,
801+
from_hir_call,
792802
feature: None,
793803
});
794804

@@ -814,7 +824,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
814824
callee,
815825
substs,
816826
span: *fn_span,
817-
from_hir_call: *from_hir_call,
827+
from_hir_call,
818828
feature: None,
819829
});
820830
return;
@@ -857,7 +867,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
857867
callee,
858868
substs,
859869
span: *fn_span,
860-
from_hir_call: *from_hir_call,
870+
from_hir_call,
861871
feature: None,
862872
});
863873
return;
@@ -917,7 +927,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
917927
callee,
918928
substs,
919929
span: *fn_span,
920-
from_hir_call: *from_hir_call,
930+
from_hir_call,
921931
feature: None,
922932
});
923933
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
@@ -976,7 +976,9 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
976976
self.check_edge(location, *target, EdgeKind::Normal);
977977
self.check_unwind_edge(location, *unwind);
978978
}
979-
TerminatorKind::Call { func, args, destination, target, unwind, .. } => {
979+
980+
TerminatorKind::Call { func, args, .. }
981+
| TerminatorKind::TailCall { func, args, .. } => {
980982
let func_ty = func.ty(&self.body.local_decls, self.tcx);
981983
match func_ty.kind() {
982984
ty::FnPtr(..) | ty::FnDef(..) => {}
@@ -985,16 +987,21 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
985987
format!("encountered non-callable type {} in `Call` terminator", func_ty),
986988
),
987989
}
988-
if let Some(target) = target {
989-
self.check_edge(location, *target, EdgeKind::Normal);
990+
991+
if let TerminatorKind::Call { target, unwind, .. } = terminator.kind {
992+
if let Some(target) = target {
993+
self.check_edge(location, target, EdgeKind::Normal);
994+
}
995+
self.check_unwind_edge(location, unwind);
990996
}
991-
self.check_unwind_edge(location, *unwind);
992997

993998
// The call destination place and Operand::Move place used as an argument might be
994999
// passed by a reference to the callee. Consequently they must be non-overlapping.
9951000
// Currently this simply checks for duplicate places.
9961001
self.place_cache.clear();
997-
self.place_cache.push(destination.as_ref());
1002+
if let TerminatorKind::Call { destination, .. } = terminator.kind {
1003+
self.place_cache.push(destination.as_ref());
1004+
}
9981005
for arg in args {
9991006
if let Operand::Move(place) = arg {
10001007
self.place_cache.push(place.as_ref());
@@ -1008,7 +1015,8 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
10081015
self.fail(
10091016
location,
10101017
format!(
1011-
"encountered overlapping memory in `Call` terminator: {:?}",
1018+
"encountered overlapping memory in `{}` terminator: {:?}",
1019+
terminator.kind.name(),
10121020
terminator.kind,
10131021
),
10141022
);

compiler/rustc_middle/src/mir/spanview.rs

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

compiler/rustc_middle/src/mir/syntax.rs

+31
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,36 @@ pub enum TerminatorKind<'tcx> {
646646
fn_span: Span,
647647
},
648648

649+
/// Tail call.
650+
///
651+
/// Roughly speaking this is a chimera of [`Call`] and [`Return`], with some caveats.
652+
/// Semantically tail calls consists of two actions:
653+
/// - pop of the current stack frame
654+
/// - a call to the `func`, with the return address of the **current** caller
655+
/// - so that a `return` inside `func` returns to the caller of the caller
656+
/// of the function that is currently being executed
657+
///
658+
/// Note that in difference with [`Call`] this is missing
659+
/// - `destination` (because it's always the return place)
660+
/// - `target` (because it's always taken from the current stack frame)
661+
/// - `unwind` (because it's always taken from the current stack frame)
662+
///
663+
/// [`Call`]: TerminatorKind::Call
664+
/// [`Return`]: TerminatorKind::Return
665+
TailCall {
666+
/// The function that’s being called.
667+
func: Operand<'tcx>,
668+
/// Arguments the function is called with.
669+
/// These are owned by the callee, which is free to modify them.
670+
/// This allows the memory occupied by "by-value" arguments to be
671+
/// reused across function calls without duplicating the contents.
672+
args: Vec<Operand<'tcx>>,
673+
// FIXME(explicit_tail_calls): should we have the span for `become`? is this span accurate? do we need it?
674+
/// This `Span` is the span of the function, without the dot and receiver
675+
/// (e.g. `foo(a, b)` in `x.foo(a, b)`
676+
fn_span: Span,
677+
},
678+
649679
/// Evaluates the operand, which must have type `bool`. If it is not equal to `expected`,
650680
/// initiates a panic. Initiating a panic corresponds to a `Call` terminator with some
651681
/// unspecified constant as the function to call, all the operands stored in the `AssertMessage`
@@ -771,6 +801,7 @@ impl TerminatorKind<'_> {
771801
TerminatorKind::Unreachable => "Unreachable",
772802
TerminatorKind::Drop { .. } => "Drop",
773803
TerminatorKind::Call { .. } => "Call",
804+
TerminatorKind::TailCall { .. } => "TailCall",
774805
TerminatorKind::Assert { .. } => "Assert",
775806
TerminatorKind::Yield { .. } => "Yield",
776807
TerminatorKind::GeneratorDrop => "GeneratorDrop",

0 commit comments

Comments
 (0)