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

@ -728,6 +728,12 @@ impl<'a, 'mir, 'tcx, R> rustc_mir_dataflow::ResultsVisitor<'mir, 'tcx, R>
} }
self.mutate_place(loc, (*destination, span), Deep, flow_state); self.mutate_place(loc, (*destination, span), Deep, flow_state);
} }
TerminatorKind::TailCall { func, args, fn_span: _ } => {
self.consume_operand(loc, (func, span), flow_state);
for arg in args {
self.consume_operand(loc, (&arg.node, arg.span), flow_state);
}
}
TerminatorKind::Assert { cond, expected: _, msg, target: _, unwind: _ } => { TerminatorKind::Assert { cond, expected: _, msg, target: _, unwind: _ } => {
self.consume_operand(loc, (cond, span), flow_state); self.consume_operand(loc, (cond, span), flow_state);
if let AssertKind::BoundsCheck { len, index } = &**msg { if let AssertKind::BoundsCheck { len, index } = &**msg {
@ -814,9 +820,8 @@ impl<'a, 'mir, 'tcx, R> rustc_mir_dataflow::ResultsVisitor<'mir, 'tcx, R>
TerminatorKind::UnwindResume TerminatorKind::UnwindResume
| TerminatorKind::Return | TerminatorKind::Return
| TerminatorKind::TailCall { .. }
| TerminatorKind::CoroutineDrop => { | TerminatorKind::CoroutineDrop => {
// Returning from the function implicitly kills storage for all locals and statics.
// Often, the storage will already have been killed by an explicit
// StorageDead, but we don't always emit those (notably on unwind paths), // StorageDead, but we don't always emit those (notably on unwind paths),
// so this "extra check" serves as a kind of backup. // so this "extra check" serves as a kind of backup.
let borrow_set = self.borrow_set.clone(); let borrow_set = self.borrow_set.clone();

View File

@ -125,6 +125,12 @@ impl<'cx, 'tcx> Visitor<'tcx> for LoanInvalidationsGenerator<'cx, 'tcx> {
} }
self.mutate_place(location, *destination, Deep); self.mutate_place(location, *destination, Deep);
} }
TerminatorKind::TailCall { func, args, .. } => {
self.consume_operand(location, func);
for arg in args {
self.consume_operand(location, &arg.node);
}
}
TerminatorKind::Assert { cond, expected: _, msg, target: _, unwind: _ } => { TerminatorKind::Assert { cond, expected: _, msg, target: _, unwind: _ } => {
self.consume_operand(location, cond); self.consume_operand(location, cond);
use rustc_middle::mir::AssertKind; use rustc_middle::mir::AssertKind;

View File

@ -1352,7 +1352,14 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
} }
// FIXME: check the values // FIXME: check the values
} }
TerminatorKind::Call { func, args, destination, call_source, target, .. } => { TerminatorKind::Call { func, args, .. }
| TerminatorKind::TailCall { func, args, .. } => {
let call_source = match term.kind {
TerminatorKind::Call { call_source, .. } => call_source,
TerminatorKind::TailCall { .. } => CallSource::Normal,
_ => unreachable!(),
};
self.check_operand(func, term_location); self.check_operand(func, term_location);
for arg in args { for arg in args {
self.check_operand(&arg.node, term_location); self.check_operand(&arg.node, term_location);
@ -1425,7 +1432,9 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
); );
} }
self.check_call_dest(body, term, &sig, *destination, *target, term_location); if let TerminatorKind::Call { destination, target, .. } = term.kind {
self.check_call_dest(body, term, &sig, destination, target, term_location);
}
// The ordinary liveness rules will ensure that all // The ordinary liveness rules will ensure that all
// regions in the type of the callee are live here. We // regions in the type of the callee are live here. We
@ -1443,7 +1452,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
.add_location(region_vid, term_location); .add_location(region_vid, term_location);
} }
self.check_call_inputs(body, term, func, &sig, args, term_location, *call_source); self.check_call_inputs(body, term, func, &sig, args, term_location, call_source);
} }
TerminatorKind::Assert { cond, msg, .. } => { TerminatorKind::Assert { cond, msg, .. } => {
self.check_operand(cond, term_location); self.check_operand(cond, term_location);
@ -1675,6 +1684,11 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
span_mirbug!(self, block_data, "return on cleanup block") span_mirbug!(self, block_data, "return on cleanup block")
} }
} }
TerminatorKind::TailCall { .. } => {
if is_cleanup {
span_mirbug!(self, block_data, "tailcall on cleanup block")
}
}
TerminatorKind::CoroutineDrop { .. } => { TerminatorKind::CoroutineDrop { .. } => {
if is_cleanup { if is_cleanup {
span_mirbug!(self, block_data, "coroutine_drop in cleanup block") span_mirbug!(self, block_data, "coroutine_drop in cleanup block")

View File

@ -491,6 +491,11 @@ fn codegen_fn_body(fx: &mut FunctionCx<'_, '_, '_>, start_block: Block) {
) )
}); });
} }
// FIXME(explicit_tail_calls): add support for tail calls to the cranelift backend, once cranelift supports tail calls
TerminatorKind::TailCall { fn_span, .. } => span_bug!(
*fn_span,
"tail calls are not yet supported in `rustc_codegen_cranelift` backend"
),
TerminatorKind::InlineAsm { TerminatorKind::InlineAsm {
template, template,
operands, operands,

View File

@ -565,6 +565,7 @@ pub(crate) fn mir_operand_get_const_val<'tcx>(
{ {
return None; return None;
} }
TerminatorKind::TailCall { .. } => return None,
TerminatorKind::Call { .. } => {} TerminatorKind::Call { .. } => {}
} }
} }

View File

@ -281,6 +281,7 @@ pub fn cleanup_kinds(mir: &mir::Body<'_>) -> IndexVec<mir::BasicBlock, CleanupKi
| TerminatorKind::UnwindResume | TerminatorKind::UnwindResume
| TerminatorKind::UnwindTerminate(_) | TerminatorKind::UnwindTerminate(_)
| TerminatorKind::Return | TerminatorKind::Return
| TerminatorKind::TailCall { .. }
| TerminatorKind::CoroutineDrop | TerminatorKind::CoroutineDrop
| TerminatorKind::Unreachable | TerminatorKind::Unreachable
| TerminatorKind::SwitchInt { .. } | TerminatorKind::SwitchInt { .. }

View File

@ -1389,6 +1389,13 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
fn_span, fn_span,
mergeable_succ(), mergeable_succ(),
), ),
mir::TerminatorKind::TailCall { .. } => {
// FIXME(explicit_tail_calls): implement tail calls in ssa backend
span_bug!(
terminator.source_info.span,
"`TailCall` terminator is not yet supported by `rustc_codegen_ssa`"
)
}
mir::TerminatorKind::CoroutineDrop | mir::TerminatorKind::Yield { .. } => { mir::TerminatorKind::CoroutineDrop | mir::TerminatorKind::Yield { .. } => {
bug!("coroutine ops in codegen") bug!("coroutine ops in codegen")
} }

View File

@ -135,6 +135,8 @@ impl<'mir, 'tcx> Qualifs<'mir, 'tcx> {
ccx: &'mir ConstCx<'mir, 'tcx>, ccx: &'mir ConstCx<'mir, 'tcx>,
tainted_by_errors: Option<ErrorGuaranteed>, tainted_by_errors: Option<ErrorGuaranteed>,
) -> ConstQualifs { ) -> ConstQualifs {
// FIXME(explicit_tail_calls): uhhhh I think we can return without return now, does it change anything
// Find the `Return` terminator if one exists. // Find the `Return` terminator if one exists.
// //
// If no `Return` terminator exists, this MIR is divergent. Just return the conservative // If no `Return` terminator exists, this MIR is divergent. Just return the conservative
@ -711,7 +713,14 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
self.super_terminator(terminator, location); self.super_terminator(terminator, location);
match &terminator.kind { match &terminator.kind {
TerminatorKind::Call { func, args, fn_span, call_source, .. } => { TerminatorKind::Call { func, args, fn_span, .. }
| TerminatorKind::TailCall { func, args, fn_span, .. } => {
let call_source = match terminator.kind {
TerminatorKind::Call { call_source, .. } => call_source,
TerminatorKind::TailCall { .. } => CallSource::Normal,
_ => unreachable!(),
};
let ConstCx { tcx, body, param_env, .. } = *self.ccx; let ConstCx { tcx, body, param_env, .. } = *self.ccx;
let caller = self.def_id(); let caller = self.def_id();
@ -783,7 +792,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
callee, callee,
args: fn_args, args: fn_args,
span: *fn_span, span: *fn_span,
call_source: *call_source, call_source,
feature: Some(if tcx.features().const_trait_impl { feature: Some(if tcx.features().const_trait_impl {
sym::effects sym::effects
} else { } else {
@ -830,7 +839,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
callee, callee,
args: fn_args, args: fn_args,
span: *fn_span, span: *fn_span,
call_source: *call_source, call_source,
feature: None, feature: None,
}); });
return; return;

View File

@ -108,6 +108,7 @@ impl<'tcx> Visitor<'tcx> for CheckLiveDrops<'_, 'tcx> {
mir::TerminatorKind::UnwindTerminate(_) mir::TerminatorKind::UnwindTerminate(_)
| mir::TerminatorKind::Call { .. } | mir::TerminatorKind::Call { .. }
| mir::TerminatorKind::TailCall { .. }
| mir::TerminatorKind::Assert { .. } | mir::TerminatorKind::Assert { .. }
| mir::TerminatorKind::FalseEdge { .. } | mir::TerminatorKind::FalseEdge { .. }
| mir::TerminatorKind::FalseUnwind { .. } | mir::TerminatorKind::FalseUnwind { .. }

View File

@ -172,6 +172,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
} }
} }
TailCall { func: _, args: _, fn_span: _ } => todo!(),
Drop { place, target, unwind, replace: _ } => { Drop { place, target, unwind, replace: _ } => {
let place = self.eval_place(place)?; let place = self.eval_place(place)?;
let instance = Instance::resolve_drop_in_place(*self.tcx, place.layout.ty); let instance = Instance::resolve_drop_in_place(*self.tcx, place.layout.ty);

View File

@ -845,6 +845,16 @@ impl<'tcx> TerminatorKind<'tcx> {
} }
write!(fmt, ")") write!(fmt, ")")
} }
TailCall { func, args, .. } => {
write!(fmt, "tailcall {func:?}(")?;
for (index, arg) in args.iter().enumerate() {
if index > 0 {
write!(fmt, ", ")?;
}
write!(fmt, "{:?}", arg)?;
}
write!(fmt, ")")
}
Assert { cond, expected, msg, .. } => { Assert { cond, expected, msg, .. } => {
write!(fmt, "assert(")?; write!(fmt, "assert(")?;
if !expected { if !expected {
@ -912,7 +922,12 @@ impl<'tcx> TerminatorKind<'tcx> {
pub fn fmt_successor_labels(&self) -> Vec<Cow<'static, str>> { pub fn fmt_successor_labels(&self) -> Vec<Cow<'static, str>> {
use self::TerminatorKind::*; use self::TerminatorKind::*;
match *self { match *self {
Return | UnwindResume | UnwindTerminate(_) | Unreachable | CoroutineDrop => vec![], Return
| TailCall { .. }
| UnwindResume
| UnwindTerminate(_)
| Unreachable
| CoroutineDrop => vec![],
Goto { .. } => vec!["".into()], Goto { .. } => vec!["".into()],
SwitchInt { ref targets, .. } => targets SwitchInt { ref targets, .. } => targets
.values .values

View File

@ -744,6 +744,36 @@ pub enum TerminatorKind<'tcx> {
fn_span: Span, fn_span: Span,
}, },
/// Tail call.
///
/// Roughly speaking this is a chimera of [`Call`] and [`Return`], with some caveats.
/// Semantically tail calls consists of two actions:
/// - pop of the current stack frame
/// - a call to the `func`, with the return address of the **current** caller
/// - so that a `return` inside `func` returns to the caller of the caller
/// of the function that is currently being executed
///
/// Note that in difference with [`Call`] this is missing
/// - `destination` (because it's always the return place)
/// - `target` (because it's always taken from the current stack frame)
/// - `unwind` (because it's always taken from the current stack frame)
///
/// [`Call`]: TerminatorKind::Call
/// [`Return`]: TerminatorKind::Return
TailCall {
/// The function thats being called.
func: Operand<'tcx>,
/// Arguments the function is called with.
/// These are owned by the callee, which is free to modify them.
/// This allows the memory occupied by "by-value" arguments to be
/// reused across function calls without duplicating the contents.
args: Vec<Spanned<Operand<'tcx>>>,
// FIXME(explicit_tail_calls): should we have the span for `become`? is this span accurate? do we need it?
/// This `Span` is the span of the function, without the dot and receiver
/// (e.g. `foo(a, b)` in `x.foo(a, b)`
fn_span: Span,
},
/// Evaluates the operand, which must have type `bool`. If it is not equal to `expected`, /// Evaluates the operand, which must have type `bool`. If it is not equal to `expected`,
/// initiates a panic. Initiating a panic corresponds to a `Call` terminator with some /// initiates a panic. Initiating a panic corresponds to a `Call` terminator with some
/// unspecified constant as the function to call, all the operands stored in the `AssertMessage` /// unspecified constant as the function to call, all the operands stored in the `AssertMessage`
@ -870,6 +900,7 @@ impl TerminatorKind<'_> {
TerminatorKind::Unreachable => "Unreachable", TerminatorKind::Unreachable => "Unreachable",
TerminatorKind::Drop { .. } => "Drop", TerminatorKind::Drop { .. } => "Drop",
TerminatorKind::Call { .. } => "Call", TerminatorKind::Call { .. } => "Call",
TerminatorKind::TailCall { .. } => "TailCall",
TerminatorKind::Assert { .. } => "Assert", TerminatorKind::Assert { .. } => "Assert",
TerminatorKind::Yield { .. } => "Yield", TerminatorKind::Yield { .. } => "Yield",
TerminatorKind::CoroutineDrop => "CoroutineDrop", TerminatorKind::CoroutineDrop => "CoroutineDrop",

View File

@ -439,6 +439,7 @@ mod helper {
| CoroutineDrop | CoroutineDrop
| Return | Return
| Unreachable | Unreachable
| TailCall { .. }
| Call { target: None, unwind: _, .. } => (&[]).into_iter().copied().chain(None), | Call { target: None, unwind: _, .. } => (&[]).into_iter().copied().chain(None),
InlineAsm { ref targets, unwind: UnwindAction::Cleanup(u), .. } => { InlineAsm { ref targets, unwind: UnwindAction::Cleanup(u), .. } => {
targets.iter().copied().chain(Some(u)) targets.iter().copied().chain(Some(u))
@ -479,6 +480,7 @@ mod helper {
| CoroutineDrop | CoroutineDrop
| Return | Return
| Unreachable | Unreachable
| TailCall { .. }
| Call { target: None, unwind: _, .. } => (&mut []).into_iter().chain(None), | Call { target: None, unwind: _, .. } => (&mut []).into_iter().chain(None),
InlineAsm { ref mut targets, unwind: UnwindAction::Cleanup(ref mut u), .. } => { InlineAsm { ref mut targets, unwind: UnwindAction::Cleanup(ref mut u), .. } => {
targets.iter_mut().chain(Some(u)) targets.iter_mut().chain(Some(u))
@ -501,6 +503,7 @@ impl<'tcx> TerminatorKind<'tcx> {
| TerminatorKind::UnwindResume | TerminatorKind::UnwindResume
| TerminatorKind::UnwindTerminate(_) | TerminatorKind::UnwindTerminate(_)
| TerminatorKind::Return | TerminatorKind::Return
| TerminatorKind::TailCall { .. }
| TerminatorKind::Unreachable | TerminatorKind::Unreachable
| TerminatorKind::CoroutineDrop | TerminatorKind::CoroutineDrop
| TerminatorKind::Yield { .. } | TerminatorKind::Yield { .. }
@ -521,6 +524,7 @@ impl<'tcx> TerminatorKind<'tcx> {
| TerminatorKind::UnwindResume | TerminatorKind::UnwindResume
| TerminatorKind::UnwindTerminate(_) | TerminatorKind::UnwindTerminate(_)
| TerminatorKind::Return | TerminatorKind::Return
| TerminatorKind::TailCall { .. }
| TerminatorKind::Unreachable | TerminatorKind::Unreachable
| TerminatorKind::CoroutineDrop | TerminatorKind::CoroutineDrop
| TerminatorKind::Yield { .. } | TerminatorKind::Yield { .. }
@ -606,9 +610,12 @@ impl<'tcx> TerminatorKind<'tcx> {
pub fn edges(&self) -> TerminatorEdges<'_, 'tcx> { pub fn edges(&self) -> TerminatorEdges<'_, 'tcx> {
use TerminatorKind::*; use TerminatorKind::*;
match *self { match *self {
Return | UnwindResume | UnwindTerminate(_) | CoroutineDrop | Unreachable => { Return
TerminatorEdges::None | TailCall { .. }
} | UnwindResume
| UnwindTerminate(_)
| CoroutineDrop
| Unreachable => TerminatorEdges::None,
Goto { target } => TerminatorEdges::Single(target), Goto { target } => TerminatorEdges::Single(target),

View File

@ -540,6 +540,17 @@ macro_rules! make_mir_visitor {
); );
} }
TerminatorKind::TailCall {
func,
args,
fn_span: _,
} => {
self.visit_operand(func, location);
for arg in args {
self.visit_operand(&$($mutability)? arg.node, location);
}
},
TerminatorKind::Assert { TerminatorKind::Assert {
cond, cond,
expected: _, expected: _,

View File

@ -2,7 +2,9 @@ use crate::build::scope::BreakableTarget;
use crate::build::{BlockAnd, BlockAndExtension, BlockFrame, Builder}; use crate::build::{BlockAnd, BlockAndExtension, BlockFrame, Builder};
use rustc_middle::middle::region; use rustc_middle::middle::region;
use rustc_middle::mir::*; use rustc_middle::mir::*;
use rustc_middle::span_bug;
use rustc_middle::thir::*; use rustc_middle::thir::*;
use rustc_span::source_map::Spanned;
use tracing::debug; use tracing::debug;
impl<'a, 'tcx> Builder<'a, 'tcx> { impl<'a, 'tcx> Builder<'a, 'tcx> {
@ -91,9 +93,38 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
ExprKind::Return { value } => { ExprKind::Return { value } => {
this.break_scope(block, value, BreakableTarget::Return, source_info) this.break_scope(block, value, BreakableTarget::Return, source_info)
} }
// FIXME(explicit_tail_calls): properly lower tail calls here
ExprKind::Become { value } => { ExprKind::Become { value } => {
this.break_scope(block, Some(value), BreakableTarget::Return, source_info) let v = &this.thir[value];
let ExprKind::Scope { value, .. } = v.kind else {
span_bug!(v.span, "`thir_check_tail_calls` should have disallowed this {v:?}")
};
let v = &this.thir[value];
let ExprKind::Call { ref args, fun, fn_span, .. } = v.kind else {
span_bug!(v.span, "`thir_check_tail_calls` should have disallowed this {v:?}")
};
let fun = unpack!(block = this.as_local_operand(block, fun));
let args: Vec<_> = args
.into_iter()
.copied()
.map(|arg| Spanned {
node: unpack!(block = this.as_local_call_operand(block, arg)),
span: this.thir.exprs[arg].span,
})
.collect();
this.record_operands_moved(&args);
debug!("expr_into_dest: fn_span={:?}", fn_span);
this.cfg.terminate(
block,
source_info,
TerminatorKind::TailCall { func: fun, args, fn_span },
);
this.cfg.start_new_block().unit()
} }
_ => { _ => {
assert!( assert!(

View File

@ -1523,6 +1523,7 @@ impl<'tcx> DropTreeBuilder<'tcx> for Unwind {
| TerminatorKind::UnwindResume | TerminatorKind::UnwindResume
| TerminatorKind::UnwindTerminate(_) | TerminatorKind::UnwindTerminate(_)
| TerminatorKind::Return | TerminatorKind::Return
| TerminatorKind::TailCall { .. }
| TerminatorKind::Unreachable | TerminatorKind::Unreachable
| TerminatorKind::Yield { .. } | TerminatorKind::Yield { .. }
| TerminatorKind::CoroutineDrop | TerminatorKind::CoroutineDrop

View File

@ -196,6 +196,8 @@ impl<'mir, 'tcx, C: TerminatorClassifier<'tcx>> TriColorVisitor<BasicBlocks<'tcx
| TerminatorKind::CoroutineDrop | TerminatorKind::CoroutineDrop
| TerminatorKind::UnwindResume | TerminatorKind::UnwindResume
| TerminatorKind::Return | TerminatorKind::Return
// FIXME(explicit_tail_calls) Is this right??
| TerminatorKind::TailCall { .. }
| TerminatorKind::Unreachable | TerminatorKind::Unreachable
| TerminatorKind::Yield { .. } => ControlFlow::Break(NonRecursive), | TerminatorKind::Yield { .. } => ControlFlow::Break(NonRecursive),

View File

@ -145,6 +145,7 @@ where
| TerminatorKind::InlineAsm { .. } | TerminatorKind::InlineAsm { .. }
| TerminatorKind::UnwindResume | TerminatorKind::UnwindResume
| TerminatorKind::Return | TerminatorKind::Return
| TerminatorKind::TailCall { .. }
| TerminatorKind::SwitchInt { .. } | TerminatorKind::SwitchInt { .. }
| TerminatorKind::Unreachable | TerminatorKind::Unreachable
| TerminatorKind::Yield { .. } => {} | TerminatorKind::Yield { .. } => {}

View File

@ -288,6 +288,7 @@ impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'_, 'tcx> {
| TerminatorKind::Goto { .. } | TerminatorKind::Goto { .. }
| TerminatorKind::UnwindResume | TerminatorKind::UnwindResume
| TerminatorKind::Return | TerminatorKind::Return
| TerminatorKind::TailCall { .. }
| TerminatorKind::SwitchInt { .. } | TerminatorKind::SwitchInt { .. }
| TerminatorKind::Unreachable => {} | TerminatorKind::Unreachable => {}
} }
@ -325,6 +326,7 @@ impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'_, 'tcx> {
| TerminatorKind::Goto { .. } | TerminatorKind::Goto { .. }
| TerminatorKind::UnwindResume | TerminatorKind::UnwindResume
| TerminatorKind::Return | TerminatorKind::Return
| TerminatorKind::TailCall { .. }
| TerminatorKind::SwitchInt { .. } | TerminatorKind::SwitchInt { .. }
| TerminatorKind::Unreachable => {} | TerminatorKind::Unreachable => {}
} }

View File

@ -489,6 +489,12 @@ impl<'b, 'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> Gatherer<'b, 'a, 'tcx, F> {
self.gather_init(destination.as_ref(), InitKind::NonPanicPathOnly); self.gather_init(destination.as_ref(), InitKind::NonPanicPathOnly);
} }
} }
TerminatorKind::TailCall { ref func, ref args, .. } => {
self.gather_operand(func);
for arg in args {
self.gather_operand(&arg.node);
}
}
TerminatorKind::InlineAsm { TerminatorKind::InlineAsm {
template: _, template: _,
ref operands, ref operands,

View File

@ -269,6 +269,9 @@ pub trait ValueAnalysis<'tcx> {
TerminatorKind::SwitchInt { discr, targets } => { TerminatorKind::SwitchInt { discr, targets } => {
return self.handle_switch_int(discr, targets, state); return self.handle_switch_int(discr, targets, state);
} }
TerminatorKind::TailCall { .. } => {
// FIXME(explicit_tail_calls): determine if we need to do something here (probably not)
}
TerminatorKind::Goto { .. } TerminatorKind::Goto { .. }
| TerminatorKind::UnwindResume | TerminatorKind::UnwindResume
| TerminatorKind::UnwindTerminate(_) | TerminatorKind::UnwindTerminate(_)

View File

@ -1367,6 +1367,10 @@ fn can_unwind<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) -> bool {
| TerminatorKind::Call { .. } | TerminatorKind::Call { .. }
| TerminatorKind::InlineAsm { .. } | TerminatorKind::InlineAsm { .. }
| TerminatorKind::Assert { .. } => return true, | 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::UnwindResume
| TerminatorKind::UnwindTerminate(_) | TerminatorKind::UnwindTerminate(_)
| TerminatorKind::Return | TerminatorKind::Return
| TerminatorKind::TailCall { .. }
| TerminatorKind::Unreachable | TerminatorKind::Unreachable
| TerminatorKind::Drop { .. } | TerminatorKind::Drop { .. }
| TerminatorKind::Assert { .. } | 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. // These terminators have no coverage-relevant successors.
CoroutineDrop | Return | Unreachable | UnwindResume | UnwindTerminate(_) => { CoroutineDrop
CoverageSuccessors::NotChainable(&[]) | Return
} | TailCall { .. }
| Unreachable
| UnwindResume
| UnwindTerminate(_) => CoverageSuccessors::NotChainable(&[]),
} }
} }

View File

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

View File

@ -628,6 +628,12 @@ impl WriteInfo {
self.add_operand(&arg.node); 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, .. } => { TerminatorKind::InlineAsm { operands, .. } => {
for asm_operand in operands { for asm_operand in operands {
match asm_operand { match asm_operand {

View File

@ -383,6 +383,8 @@ impl<'tcx> Inliner<'tcx> {
) -> Option<CallSite<'tcx>> { ) -> Option<CallSite<'tcx>> {
// Only consider direct calls to functions // Only consider direct calls to functions
let terminator = bb_data.terminator(); 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 { if let TerminatorKind::Call { ref func, fn_span, .. } = terminator.kind {
let func_ty = func.ty(caller_body, self.tcx); let func_ty = func.ty(caller_body, self.tcx);
if let ty::FnDef(def_id, args) = *func_ty.kind() { 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 // 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. // if the no-attribute function ends up with the same instruction set anyway.
return Err("Cannot move inline-asm across instruction sets"); 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 { } else {
work_list.extend(term.successors()) work_list.extend(term.successors())
} }
@ -1038,6 +1043,10 @@ impl<'tcx> MutVisitor<'tcx> for Integrator<'_, 'tcx> {
*target = self.map_block(*target); *target = self.map_block(*target);
*unwind = self.map_unwind(*unwind); *unwind = self.map_unwind(*unwind);
} }
TerminatorKind::TailCall { .. } => {
// check_mir_body forbids tail calls
unreachable!()
}
TerminatorKind::Call { ref mut target, ref mut unwind, .. } => { TerminatorKind::Call { ref mut target, ref mut unwind, .. } => {
if let Some(ref mut tgt) = *target { if let Some(ref mut tgt) = *target {
*tgt = self.map_block(*tgt); *tgt = self.map_block(*tgt);

View File

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

View File

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

View File

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

View File

@ -75,6 +75,7 @@ impl RemoveNoopLandingPads {
| TerminatorKind::UnwindTerminate(_) | TerminatorKind::UnwindTerminate(_)
| TerminatorKind::Unreachable | TerminatorKind::Unreachable
| TerminatorKind::Call { .. } | TerminatorKind::Call { .. }
| TerminatorKind::TailCall { .. }
| TerminatorKind::Assert { .. } | TerminatorKind::Assert { .. }
| TerminatorKind::Drop { .. } | TerminatorKind::Drop { .. }
| TerminatorKind::InlineAsm { .. } => false, | 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_edge(location, *target, EdgeKind::Normal);
self.check_unwind_edge(location, *unwind); self.check_unwind_edge(location, *unwind);
} }
TerminatorKind::Call { args, destination, target, unwind, .. } => { TerminatorKind::Call { args, .. } | TerminatorKind::TailCall { args, .. } => {
if let Some(target) = target { // FIXME(explicit_tail_calls): refactor this & add tail-call specific checks
self.check_edge(location, *target, EdgeKind::Normal); if let TerminatorKind::Call { target, unwind, destination, .. } = terminator.kind {
} if let Some(target) = target {
self.check_unwind_edge(location, *unwind); 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 // 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 // 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, // from the call. FIXME(tmiasko): Since this is a strictly code generation concern,
// the code generation should be responsible for handling it. // the code generation should be responsible for handling it.
if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Optimized) if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Optimized)
&& self.is_critical_call_edge(*target, *unwind) && self.is_critical_call_edge(target, unwind)
{ {
self.fail( self.fail(
location, location,
format!( format!(
"encountered critical edge in `Call` terminator {:?}", "encountered critical edge in `Call` terminator {:?}",
terminator.kind, 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 { for arg in args {
if let Operand::Move(place) = &arg.node { if let Operand::Move(place) = &arg.node {
if is_within_packed(self.tcx, &self.body.local_decls, *place).is_some() { 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); let func_ty = func.ty(&self.body.local_decls, self.tcx);
match func_ty.kind() { match func_ty.kind() {
ty::FnPtr(..) | ty::FnDef(..) => {} ty::FnPtr(..) | ty::FnDef(..) => {}
_ => self.fail( _ => self.fail(
location, 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, .. } => { TerminatorKind::Assert { cond, .. } => {
let cond_ty = cond.ty(&self.body.local_decls, self.tcx); let cond_ty = cond.ty(&self.body.local_decls, self.tcx);

View File

@ -755,7 +755,8 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MirUsedCollector<'a, 'tcx> {
}; };
match terminator.kind { match terminator.kind {
mir::TerminatorKind::Call { ref func, ref args, ref fn_span, .. } => { mir::TerminatorKind::Call { ref func, ref args, ref fn_span, .. }
| mir::TerminatorKind::TailCall { ref func, ref args, ref fn_span } => {
let callee_ty = func.ty(self.body, tcx); let callee_ty = func.ty(self.body, tcx);
// *Before* monomorphizing, record that we already handled this mention. // *Before* monomorphizing, record that we already handled this mention.
self.used_mentioned_items.insert(MentionedItem::Fn(callee_ty)); self.used_mentioned_items.insert(MentionedItem::Fn(callee_ty));

View File

@ -644,6 +644,7 @@ impl<'tcx> Stable<'tcx> for mir::TerminatorKind<'tcx> {
target: target.map(|t| t.as_usize()), target: target.map(|t| t.as_usize()),
unwind: unwind.stable(tables), unwind: unwind.stable(tables),
}, },
mir::TerminatorKind::TailCall { func: _, args: _, fn_span: _ } => todo!(),
mir::TerminatorKind::Assert { cond, expected, msg, target, unwind } => { mir::TerminatorKind::Assert { cond, expected, msg, target, unwind } => {
TerminatorKind::Assert { TerminatorKind::Assert {
cond: cond.stable(tables), cond: cond.stable(tables),

View File

@ -0,0 +1,22 @@
#![allow(incomplete_features)]
#![feature(explicit_tail_calls)]
const fn f() {
if false {
become not_const();
//~^ error: cannot call non-const fn `not_const` in constant functions
}
}
const fn g((): ()) {
if false {
become yes_const(not_const());
//~^ error: cannot call non-const fn `not_const` in constant functions
}
}
fn not_const() {}
const fn yes_const((): ()) {}
fn main() {}

View File

@ -0,0 +1,19 @@
error[E0015]: cannot call non-const fn `not_const` in constant functions
--> $DIR/constck.rs:6:16
|
LL | become not_const();
| ^^^^^^^^^^^
|
= note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
error[E0015]: cannot call non-const fn `not_const` in constant functions
--> $DIR/constck.rs:13:26
|
LL | become yes_const(not_const());
| ^^^^^^^^^^^
|
= note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0015`.

View File

@ -13,7 +13,7 @@ fn _f1() {
become _g1(); //~ error: mismatched types become _g1(); //~ error: mismatched types
} }
fn _g1() -> ! { //~ WARN: cannot return without recursing fn _g1() -> ! {
become _g1(); become _g1();
} }

View File

@ -22,17 +22,6 @@ error[E0308]: mismatched types
LL | become _g2(); LL | become _g2();
| ^^^^^^^^^^^^ expected `u32`, found `u16` | ^^^^^^^^^^^^ expected `u32`, found `u16`
warning: function cannot return without recursing error: aborting due to 3 previous errors
--> $DIR/return-mismatches.rs:16:1
|
LL | fn _g1() -> ! {
| ^^^^^^^^^^^^^ cannot return without recursing
LL | become _g1();
| ----- recursive call site
|
= help: a `loop` may express intention better if this is on purpose
= note: `#[warn(unconditional_recursion)]` on by default
error: aborting due to 3 previous errors; 1 warning emitted
For more information about this error, try `rustc --explain E0308`. For more information about this error, try `rustc --explain E0308`.

View File

@ -0,0 +1,11 @@
#![allow(incomplete_features)]
#![feature(explicit_tail_calls)]
const fn f() {
become dangerous();
//~^ error: call to unsafe function `dangerous` is unsafe and requires unsafe function or block
}
const unsafe fn dangerous() {}
fn main() {}

View File

@ -0,0 +1,11 @@
error[E0133]: call to unsafe function `dangerous` is unsafe and requires unsafe function or block
--> $DIR/unsafeck.rs:5:12
|
LL | become dangerous();
| ^^^^^^^^^^^ call to unsafe function
|
= note: consult the function's documentation for information on how to avoid undefined behavior
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0133`.

View File

@ -147,14 +147,14 @@ fn o() -> Result<(), ()> {
}; };
} }
fn p() { // fn p() { // FIXME(explicit_tail_calls): this currently trips an assertion...
let 0 = become { // let 0 = become {
() // ()
} else { // } else {
//~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed // // ~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed
return; // return;
}; // };
} // }
fn q() { fn q() {
let foo = |x: i32| { let foo = |x: i32| {

View File

@ -203,19 +203,6 @@ LL | ()
LL ~ }) else { LL ~ }) else {
| |
error: right curly brace `}` before `else` in a `let...else` statement not allowed
--> $DIR/bad-let-else-statement.rs:153:5
|
LL | } else {
| ^
|
help: wrap the expression in parentheses
|
LL ~ let 0 = become ({
LL | ()
LL ~ }) else {
|
error: right curly brace `}` before `else` in a `let...else` statement not allowed error: right curly brace `}` before `else` in a `let...else` statement not allowed
--> $DIR/bad-let-else-statement.rs:163:5 --> $DIR/bad-let-else-statement.rs:163:5
| |
@ -325,5 +312,5 @@ LL | | } else {
= note: this pattern will always match, so the `else` clause is useless = note: this pattern will always match, so the `else` clause is useless
= help: consider removing the `else` clause = help: consider removing the `else` clause
error: aborting due to 20 previous errors; 5 warnings emitted error: aborting due to 19 previous errors; 5 warnings emitted