1
Fork 0

Support tail calls in mir via TerminatorKind::TailCall

This commit is contained in:
Maybe Waffle 2024-02-15 19:54:37 +00:00 committed by Maybe Lapkin
parent e2cf31a614
commit 484152d562
41 changed files with 328 additions and 88 deletions

View file

@ -1367,6 +1367,10 @@ fn can_unwind<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) -> bool {
| TerminatorKind::Call { .. }
| TerminatorKind::InlineAsm { .. }
| TerminatorKind::Assert { .. } => return true,
TerminatorKind::TailCall { .. } => {
unreachable!("tail calls can't be present in generators")
}
}
}
@ -1916,6 +1920,7 @@ impl<'tcx> Visitor<'tcx> for EnsureCoroutineFieldAssignmentsNeverAlias<'_> {
| TerminatorKind::UnwindResume
| TerminatorKind::UnwindTerminate(_)
| TerminatorKind::Return
| TerminatorKind::TailCall { .. }
| TerminatorKind::Unreachable
| TerminatorKind::Drop { .. }
| TerminatorKind::Assert { .. }

View file

@ -358,9 +358,12 @@ fn bcb_filtered_successors<'a, 'tcx>(terminator: &'a Terminator<'tcx>) -> Covera
}
// These terminators have no coverage-relevant successors.
CoroutineDrop | Return | Unreachable | UnwindResume | UnwindTerminate(_) => {
CoverageSuccessors::NotChainable(&[])
}
CoroutineDrop
| Return
| TailCall { .. }
| Unreachable
| UnwindResume
| UnwindTerminate(_) => CoverageSuccessors::NotChainable(&[]),
}
}

View file

@ -193,7 +193,8 @@ fn filtered_terminator_span(terminator: &Terminator<'_>) -> Option<Span> {
| TerminatorKind::Goto { .. } => None,
// Call `func` operand can have a more specific span when part of a chain of calls
| TerminatorKind::Call { ref func, .. } => {
TerminatorKind::Call { ref func, .. }
| TerminatorKind::TailCall { ref func, .. } => {
let mut span = terminator.source_info.span;
if let mir::Operand::Constant(box constant) = func {
if constant.span.lo() > span.lo() {

View file

@ -628,6 +628,12 @@ impl WriteInfo {
self.add_operand(&arg.node);
}
}
TerminatorKind::TailCall { func, args, .. } => {
self.add_operand(func);
for arg in args {
self.add_operand(&arg.node);
}
}
TerminatorKind::InlineAsm { operands, .. } => {
for asm_operand in operands {
match asm_operand {

View file

@ -383,6 +383,8 @@ impl<'tcx> Inliner<'tcx> {
) -> Option<CallSite<'tcx>> {
// Only consider direct calls to functions
let terminator = bb_data.terminator();
// FIXME(explicit_tail_calls): figure out if we can inline tail calls
if let TerminatorKind::Call { ref func, fn_span, .. } = terminator.kind {
let func_ty = func.ty(caller_body, self.tcx);
if let ty::FnDef(def_id, args) = *func_ty.kind() {
@ -550,6 +552,9 @@ impl<'tcx> Inliner<'tcx> {
// inline-asm is detected. LLVM will still possibly do an inline later on
// if the no-attribute function ends up with the same instruction set anyway.
return Err("Cannot move inline-asm across instruction sets");
} else if let TerminatorKind::TailCall { .. } = term.kind {
// FIXME(explicit_tail_calls): figure out how exactly functions containing tail calls can be inlined (and if they even should)
return Err("can't inline functions with tail calls");
} else {
work_list.extend(term.successors())
}
@ -1038,6 +1043,10 @@ impl<'tcx> MutVisitor<'tcx> for Integrator<'_, 'tcx> {
*target = self.map_block(*target);
*unwind = self.map_unwind(*unwind);
}
TerminatorKind::TailCall { .. } => {
// check_mir_body forbids tail calls
unreachable!()
}
TerminatorKind::Call { ref mut target, ref mut unwind, .. } => {
if let Some(ref mut tgt) = *target {
*tgt = self.map_block(*tgt);

View file

@ -596,6 +596,7 @@ impl<'tcx, 'a> TOFinder<'tcx, 'a> {
TerminatorKind::UnwindResume
| TerminatorKind::UnwindTerminate(_)
| TerminatorKind::Return
| TerminatorKind::TailCall { .. }
| TerminatorKind::Unreachable
| TerminatorKind::CoroutineDrop => bug!("{term:?} has no terminators"),
// Disallowed during optimizations.

View file

@ -799,6 +799,7 @@ impl<'tcx> Visitor<'tcx> for ConstPropagator<'_, 'tcx> {
| TerminatorKind::UnwindResume
| TerminatorKind::UnwindTerminate(_)
| TerminatorKind::Return
| TerminatorKind::TailCall { .. }
| TerminatorKind::Unreachable
| TerminatorKind::Drop { .. }
| TerminatorKind::Yield { .. }

View file

@ -38,7 +38,7 @@ impl<'tcx> Visitor<'tcx> for MentionedItemsVisitor<'_, 'tcx> {
self.super_terminator(terminator, location);
let span = || self.body.source_info(location).span;
match &terminator.kind {
mir::TerminatorKind::Call { func, .. } => {
mir::TerminatorKind::Call { func, .. } | mir::TerminatorKind::TailCall { func, .. } => {
let callee_ty = func.ty(self.body, self.tcx);
self.mentioned_items
.push(Spanned { node: MentionedItem::Fn(callee_ty), span: span() });

View file

@ -75,6 +75,7 @@ impl RemoveNoopLandingPads {
| TerminatorKind::UnwindTerminate(_)
| TerminatorKind::Unreachable
| TerminatorKind::Call { .. }
| TerminatorKind::TailCall { .. }
| TerminatorKind::Assert { .. }
| TerminatorKind::Drop { .. }
| TerminatorKind::InlineAsm { .. } => false,

View file

@ -400,40 +400,44 @@ impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> {
self.check_edge(location, *target, EdgeKind::Normal);
self.check_unwind_edge(location, *unwind);
}
TerminatorKind::Call { args, destination, target, unwind, .. } => {
if let Some(target) = target {
self.check_edge(location, *target, EdgeKind::Normal);
}
self.check_unwind_edge(location, *unwind);
TerminatorKind::Call { args, .. } | TerminatorKind::TailCall { args, .. } => {
// FIXME(explicit_tail_calls): refactor this & add tail-call specific checks
if let TerminatorKind::Call { target, unwind, destination, .. } = terminator.kind {
if let Some(target) = target {
self.check_edge(location, target, EdgeKind::Normal);
}
self.check_unwind_edge(location, unwind);
// The code generation assumes that there are no critical call edges. The assumption
// is used to simplify inserting code that should be executed along the return edge
// from the call. FIXME(tmiasko): Since this is a strictly code generation concern,
// the code generation should be responsible for handling it.
if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Optimized)
&& self.is_critical_call_edge(*target, *unwind)
{
self.fail(
location,
format!(
"encountered critical edge in `Call` terminator {:?}",
terminator.kind,
),
);
// The code generation assumes that there are no critical call edges. The assumption
// is used to simplify inserting code that should be executed along the return edge
// from the call. FIXME(tmiasko): Since this is a strictly code generation concern,
// the code generation should be responsible for handling it.
if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Optimized)
&& self.is_critical_call_edge(target, unwind)
{
self.fail(
location,
format!(
"encountered critical edge in `Call` terminator {:?}",
terminator.kind,
),
);
}
// The call destination place and Operand::Move place used as an argument might be
// passed by a reference to the callee. Consequently they cannot be packed.
if is_within_packed(self.tcx, &self.body.local_decls, destination).is_some() {
// This is bad! The callee will expect the memory to be aligned.
self.fail(
location,
format!(
"encountered packed place in `Call` terminator destination: {:?}",
terminator.kind,
),
);
}
}
// The call destination place and Operand::Move place used as an argument might be
// passed by a reference to the callee. Consequently they cannot be packed.
if is_within_packed(self.tcx, &self.body.local_decls, *destination).is_some() {
// This is bad! The callee will expect the memory to be aligned.
self.fail(
location,
format!(
"encountered packed place in `Call` terminator destination: {:?}",
terminator.kind,
),
);
}
for arg in args {
if let Operand::Move(place) = &arg.node {
if is_within_packed(self.tcx, &self.body.local_decls, *place).is_some() {
@ -1498,15 +1502,22 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
}
}
}
TerminatorKind::Call { func, .. } => {
TerminatorKind::Call { func, .. } | TerminatorKind::TailCall { func, .. } => {
let func_ty = func.ty(&self.body.local_decls, self.tcx);
match func_ty.kind() {
ty::FnPtr(..) | ty::FnDef(..) => {}
_ => self.fail(
location,
format!("encountered non-callable type {func_ty} in `Call` terminator"),
format!(
"encountered non-callable type {func_ty} in `{}` terminator",
terminator.kind.name()
),
),
}
if let TerminatorKind::TailCall { .. } = terminator.kind {
// FIXME(explicit_tail_calls): implement tail-call specific checks here (such as signature matching, forbidding closures, etc)
}
}
TerminatorKind::Assert { cond, .. } => {
let cond_ty = cond.ty(&self.body.local_decls, self.tcx);