mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-25 00:03:43 +00:00
Support tail calls in mir via TerminatorKind::TailCall
This commit is contained in:
parent
e2cf31a614
commit
484152d562
@ -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);
|
||||
}
|
||||
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: _ } => {
|
||||
self.consume_operand(loc, (cond, span), flow_state);
|
||||
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::Return
|
||||
| TerminatorKind::TailCall { .. }
|
||||
| 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),
|
||||
// so this "extra check" serves as a kind of backup.
|
||||
let borrow_set = self.borrow_set.clone();
|
||||
|
@ -125,6 +125,12 @@ impl<'cx, 'tcx> Visitor<'tcx> for LoanInvalidationsGenerator<'cx, 'tcx> {
|
||||
}
|
||||
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: _ } => {
|
||||
self.consume_operand(location, cond);
|
||||
use rustc_middle::mir::AssertKind;
|
||||
|
@ -1352,7 +1352,14 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
|
||||
}
|
||||
// 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);
|
||||
for arg in args {
|
||||
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
|
||||
// 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);
|
||||
}
|
||||
|
||||
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, .. } => {
|
||||
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")
|
||||
}
|
||||
}
|
||||
TerminatorKind::TailCall { .. } => {
|
||||
if is_cleanup {
|
||||
span_mirbug!(self, block_data, "tailcall on cleanup block")
|
||||
}
|
||||
}
|
||||
TerminatorKind::CoroutineDrop { .. } => {
|
||||
if is_cleanup {
|
||||
span_mirbug!(self, block_data, "coroutine_drop in cleanup block")
|
||||
|
@ -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 {
|
||||
template,
|
||||
operands,
|
||||
|
@ -565,6 +565,7 @@ pub(crate) fn mir_operand_get_const_val<'tcx>(
|
||||
{
|
||||
return None;
|
||||
}
|
||||
TerminatorKind::TailCall { .. } => return None,
|
||||
TerminatorKind::Call { .. } => {}
|
||||
}
|
||||
}
|
||||
|
@ -281,6 +281,7 @@ pub fn cleanup_kinds(mir: &mir::Body<'_>) -> IndexVec<mir::BasicBlock, CleanupKi
|
||||
| TerminatorKind::UnwindResume
|
||||
| TerminatorKind::UnwindTerminate(_)
|
||||
| TerminatorKind::Return
|
||||
| TerminatorKind::TailCall { .. }
|
||||
| TerminatorKind::CoroutineDrop
|
||||
| TerminatorKind::Unreachable
|
||||
| TerminatorKind::SwitchInt { .. }
|
||||
|
@ -1389,6 +1389,13 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||
fn_span,
|
||||
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 { .. } => {
|
||||
bug!("coroutine ops in codegen")
|
||||
}
|
||||
|
@ -135,6 +135,8 @@ impl<'mir, 'tcx> Qualifs<'mir, 'tcx> {
|
||||
ccx: &'mir ConstCx<'mir, 'tcx>,
|
||||
tainted_by_errors: Option<ErrorGuaranteed>,
|
||||
) -> 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.
|
||||
//
|
||||
// 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);
|
||||
|
||||
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 caller = self.def_id();
|
||||
|
||||
@ -783,7 +792,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
|
||||
callee,
|
||||
args: fn_args,
|
||||
span: *fn_span,
|
||||
call_source: *call_source,
|
||||
call_source,
|
||||
feature: Some(if tcx.features().const_trait_impl {
|
||||
sym::effects
|
||||
} else {
|
||||
@ -830,7 +839,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
|
||||
callee,
|
||||
args: fn_args,
|
||||
span: *fn_span,
|
||||
call_source: *call_source,
|
||||
call_source,
|
||||
feature: None,
|
||||
});
|
||||
return;
|
||||
|
@ -108,6 +108,7 @@ impl<'tcx> Visitor<'tcx> for CheckLiveDrops<'_, 'tcx> {
|
||||
|
||||
mir::TerminatorKind::UnwindTerminate(_)
|
||||
| mir::TerminatorKind::Call { .. }
|
||||
| mir::TerminatorKind::TailCall { .. }
|
||||
| mir::TerminatorKind::Assert { .. }
|
||||
| mir::TerminatorKind::FalseEdge { .. }
|
||||
| mir::TerminatorKind::FalseUnwind { .. }
|
||||
|
@ -172,6 +172,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||
}
|
||||
}
|
||||
|
||||
TailCall { func: _, args: _, fn_span: _ } => todo!(),
|
||||
|
||||
Drop { place, target, unwind, replace: _ } => {
|
||||
let place = self.eval_place(place)?;
|
||||
let instance = Instance::resolve_drop_in_place(*self.tcx, place.layout.ty);
|
||||
|
@ -845,6 +845,16 @@ impl<'tcx> TerminatorKind<'tcx> {
|
||||
}
|
||||
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, .. } => {
|
||||
write!(fmt, "assert(")?;
|
||||
if !expected {
|
||||
@ -912,7 +922,12 @@ impl<'tcx> TerminatorKind<'tcx> {
|
||||
pub fn fmt_successor_labels(&self) -> Vec<Cow<'static, str>> {
|
||||
use self::TerminatorKind::*;
|
||||
match *self {
|
||||
Return | UnwindResume | UnwindTerminate(_) | Unreachable | CoroutineDrop => vec![],
|
||||
Return
|
||||
| TailCall { .. }
|
||||
| UnwindResume
|
||||
| UnwindTerminate(_)
|
||||
| Unreachable
|
||||
| CoroutineDrop => vec![],
|
||||
Goto { .. } => vec!["".into()],
|
||||
SwitchInt { ref targets, .. } => targets
|
||||
.values
|
||||
|
@ -744,6 +744,36 @@ pub enum TerminatorKind<'tcx> {
|
||||
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 that’s 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`,
|
||||
/// 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`
|
||||
@ -870,6 +900,7 @@ impl TerminatorKind<'_> {
|
||||
TerminatorKind::Unreachable => "Unreachable",
|
||||
TerminatorKind::Drop { .. } => "Drop",
|
||||
TerminatorKind::Call { .. } => "Call",
|
||||
TerminatorKind::TailCall { .. } => "TailCall",
|
||||
TerminatorKind::Assert { .. } => "Assert",
|
||||
TerminatorKind::Yield { .. } => "Yield",
|
||||
TerminatorKind::CoroutineDrop => "CoroutineDrop",
|
||||
|
@ -439,6 +439,7 @@ mod helper {
|
||||
| CoroutineDrop
|
||||
| Return
|
||||
| Unreachable
|
||||
| TailCall { .. }
|
||||
| Call { target: None, unwind: _, .. } => (&[]).into_iter().copied().chain(None),
|
||||
InlineAsm { ref targets, unwind: UnwindAction::Cleanup(u), .. } => {
|
||||
targets.iter().copied().chain(Some(u))
|
||||
@ -479,6 +480,7 @@ mod helper {
|
||||
| CoroutineDrop
|
||||
| Return
|
||||
| Unreachable
|
||||
| TailCall { .. }
|
||||
| Call { target: None, unwind: _, .. } => (&mut []).into_iter().chain(None),
|
||||
InlineAsm { ref mut targets, unwind: UnwindAction::Cleanup(ref mut u), .. } => {
|
||||
targets.iter_mut().chain(Some(u))
|
||||
@ -501,6 +503,7 @@ impl<'tcx> TerminatorKind<'tcx> {
|
||||
| TerminatorKind::UnwindResume
|
||||
| TerminatorKind::UnwindTerminate(_)
|
||||
| TerminatorKind::Return
|
||||
| TerminatorKind::TailCall { .. }
|
||||
| TerminatorKind::Unreachable
|
||||
| TerminatorKind::CoroutineDrop
|
||||
| TerminatorKind::Yield { .. }
|
||||
@ -521,6 +524,7 @@ impl<'tcx> TerminatorKind<'tcx> {
|
||||
| TerminatorKind::UnwindResume
|
||||
| TerminatorKind::UnwindTerminate(_)
|
||||
| TerminatorKind::Return
|
||||
| TerminatorKind::TailCall { .. }
|
||||
| TerminatorKind::Unreachable
|
||||
| TerminatorKind::CoroutineDrop
|
||||
| TerminatorKind::Yield { .. }
|
||||
@ -606,9 +610,12 @@ impl<'tcx> TerminatorKind<'tcx> {
|
||||
pub fn edges(&self) -> TerminatorEdges<'_, 'tcx> {
|
||||
use TerminatorKind::*;
|
||||
match *self {
|
||||
Return | UnwindResume | UnwindTerminate(_) | CoroutineDrop | Unreachable => {
|
||||
TerminatorEdges::None
|
||||
}
|
||||
Return
|
||||
| TailCall { .. }
|
||||
| UnwindResume
|
||||
| UnwindTerminate(_)
|
||||
| CoroutineDrop
|
||||
| Unreachable => TerminatorEdges::None,
|
||||
|
||||
Goto { target } => TerminatorEdges::Single(target),
|
||||
|
||||
|
@ -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 {
|
||||
cond,
|
||||
expected: _,
|
||||
|
@ -2,7 +2,9 @@ use crate::build::scope::BreakableTarget;
|
||||
use crate::build::{BlockAnd, BlockAndExtension, BlockFrame, Builder};
|
||||
use rustc_middle::middle::region;
|
||||
use rustc_middle::mir::*;
|
||||
use rustc_middle::span_bug;
|
||||
use rustc_middle::thir::*;
|
||||
use rustc_span::source_map::Spanned;
|
||||
use tracing::debug;
|
||||
|
||||
impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
@ -91,9 +93,38 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
ExprKind::Return { value } => {
|
||||
this.break_scope(block, value, BreakableTarget::Return, source_info)
|
||||
}
|
||||
// FIXME(explicit_tail_calls): properly lower tail calls here
|
||||
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!(
|
||||
|
@ -1523,6 +1523,7 @@ impl<'tcx> DropTreeBuilder<'tcx> for Unwind {
|
||||
| TerminatorKind::UnwindResume
|
||||
| TerminatorKind::UnwindTerminate(_)
|
||||
| TerminatorKind::Return
|
||||
| TerminatorKind::TailCall { .. }
|
||||
| TerminatorKind::Unreachable
|
||||
| TerminatorKind::Yield { .. }
|
||||
| TerminatorKind::CoroutineDrop
|
||||
|
@ -196,6 +196,8 @@ impl<'mir, 'tcx, C: TerminatorClassifier<'tcx>> TriColorVisitor<BasicBlocks<'tcx
|
||||
| TerminatorKind::CoroutineDrop
|
||||
| TerminatorKind::UnwindResume
|
||||
| TerminatorKind::Return
|
||||
// FIXME(explicit_tail_calls) Is this right??
|
||||
| TerminatorKind::TailCall { .. }
|
||||
| TerminatorKind::Unreachable
|
||||
| TerminatorKind::Yield { .. } => ControlFlow::Break(NonRecursive),
|
||||
|
||||
|
@ -145,6 +145,7 @@ where
|
||||
| TerminatorKind::InlineAsm { .. }
|
||||
| TerminatorKind::UnwindResume
|
||||
| TerminatorKind::Return
|
||||
| TerminatorKind::TailCall { .. }
|
||||
| TerminatorKind::SwitchInt { .. }
|
||||
| TerminatorKind::Unreachable
|
||||
| TerminatorKind::Yield { .. } => {}
|
||||
|
@ -288,6 +288,7 @@ impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'_, 'tcx> {
|
||||
| TerminatorKind::Goto { .. }
|
||||
| TerminatorKind::UnwindResume
|
||||
| TerminatorKind::Return
|
||||
| TerminatorKind::TailCall { .. }
|
||||
| TerminatorKind::SwitchInt { .. }
|
||||
| TerminatorKind::Unreachable => {}
|
||||
}
|
||||
@ -325,6 +326,7 @@ impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'_, 'tcx> {
|
||||
| TerminatorKind::Goto { .. }
|
||||
| TerminatorKind::UnwindResume
|
||||
| TerminatorKind::Return
|
||||
| TerminatorKind::TailCall { .. }
|
||||
| TerminatorKind::SwitchInt { .. }
|
||||
| TerminatorKind::Unreachable => {}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
TerminatorKind::TailCall { ref func, ref args, .. } => {
|
||||
self.gather_operand(func);
|
||||
for arg in args {
|
||||
self.gather_operand(&arg.node);
|
||||
}
|
||||
}
|
||||
TerminatorKind::InlineAsm {
|
||||
template: _,
|
||||
ref operands,
|
||||
|
@ -269,6 +269,9 @@ pub trait ValueAnalysis<'tcx> {
|
||||
TerminatorKind::SwitchInt { discr, targets } => {
|
||||
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::UnwindResume
|
||||
| TerminatorKind::UnwindTerminate(_)
|
||||
|
@ -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 { .. }
|
||||
|
@ -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(&[]),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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 { .. }
|
||||
|
@ -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() });
|
||||
|
@ -75,6 +75,7 @@ impl RemoveNoopLandingPads {
|
||||
| TerminatorKind::UnwindTerminate(_)
|
||||
| TerminatorKind::Unreachable
|
||||
| TerminatorKind::Call { .. }
|
||||
| TerminatorKind::TailCall { .. }
|
||||
| TerminatorKind::Assert { .. }
|
||||
| TerminatorKind::Drop { .. }
|
||||
| TerminatorKind::InlineAsm { .. } => false,
|
||||
|
@ -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);
|
||||
|
@ -755,7 +755,8 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MirUsedCollector<'a, 'tcx> {
|
||||
};
|
||||
|
||||
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);
|
||||
// *Before* monomorphizing, record that we already handled this mention.
|
||||
self.used_mentioned_items.insert(MentionedItem::Fn(callee_ty));
|
||||
|
@ -644,6 +644,7 @@ impl<'tcx> Stable<'tcx> for mir::TerminatorKind<'tcx> {
|
||||
target: target.map(|t| t.as_usize()),
|
||||
unwind: unwind.stable(tables),
|
||||
},
|
||||
mir::TerminatorKind::TailCall { func: _, args: _, fn_span: _ } => todo!(),
|
||||
mir::TerminatorKind::Assert { cond, expected, msg, target, unwind } => {
|
||||
TerminatorKind::Assert {
|
||||
cond: cond.stable(tables),
|
||||
|
22
tests/ui/explicit-tail-calls/constck.rs
Normal file
22
tests/ui/explicit-tail-calls/constck.rs
Normal 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() {}
|
19
tests/ui/explicit-tail-calls/constck.stderr
Normal file
19
tests/ui/explicit-tail-calls/constck.stderr
Normal 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`.
|
@ -13,7 +13,7 @@ fn _f1() {
|
||||
become _g1(); //~ error: mismatched types
|
||||
}
|
||||
|
||||
fn _g1() -> ! { //~ WARN: cannot return without recursing
|
||||
fn _g1() -> ! {
|
||||
become _g1();
|
||||
}
|
||||
|
||||
|
@ -22,17 +22,6 @@ error[E0308]: mismatched types
|
||||
LL | become _g2();
|
||||
| ^^^^^^^^^^^^ expected `u32`, found `u16`
|
||||
|
||||
warning: function cannot return without recursing
|
||||
--> $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
|
||||
error: aborting due to 3 previous errors
|
||||
|
||||
For more information about this error, try `rustc --explain E0308`.
|
||||
|
11
tests/ui/explicit-tail-calls/unsafeck.rs
Normal file
11
tests/ui/explicit-tail-calls/unsafeck.rs
Normal 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() {}
|
11
tests/ui/explicit-tail-calls/unsafeck.stderr
Normal file
11
tests/ui/explicit-tail-calls/unsafeck.stderr
Normal 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`.
|
@ -147,14 +147,14 @@ fn o() -> Result<(), ()> {
|
||||
};
|
||||
}
|
||||
|
||||
fn p() {
|
||||
let 0 = become {
|
||||
()
|
||||
} else {
|
||||
//~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed
|
||||
return;
|
||||
};
|
||||
}
|
||||
// fn p() { // FIXME(explicit_tail_calls): this currently trips an assertion...
|
||||
// let 0 = become {
|
||||
// ()
|
||||
// } else {
|
||||
// // ~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed
|
||||
// return;
|
||||
// };
|
||||
// }
|
||||
|
||||
fn q() {
|
||||
let foo = |x: i32| {
|
||||
|
@ -203,19 +203,6 @@ LL | ()
|
||||
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
|
||||
--> $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
|
||||
= 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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user