Auto merge of #113128 - WaffleLapkin:become_trully_unuwuable, r=oli-obk,RalfJung
Support tail calls in mir via `TerminatorKind::TailCall` This is one of the interesting bits in tail call implementation — MIR support. This adds a new `TerminatorKind` which represents a tail call: ```rust TailCall { func: Operand<'tcx>, args: Vec<Operand<'tcx>>, fn_span: Span, }, ``` *Structurally* this is very similar to a normal `Call` but is missing a few fields: - `destination` — tail calls don't write to destination, instead they pass caller's destination to the callee (such that eventual `return` will write to the caller of the function that used tail call) - `target` — similarly to `destination` tail calls pass the caller's return address to the callee, so there is nothing to do - `unwind` — I _think_ this is applicable too, although it's a bit confusing - `call_source` — `become` forbids operators and is not created as a lowering of something else; tail calls always come from HIR (at least for now) It might be helpful to read the interpreter implementation to understand what `TailCall` means exactly, although I've tried documenting it too. ----- There are a few `FIXME`-questions still left, ideally we'd be able to answer them during review ':) ----- r? `@oli-obk` cc `@scottmcm` `@DrMeepster` `@JakobDegen`
This commit is contained in:
commit
9af6fee87d
75 changed files with 2385 additions and 173 deletions
|
@ -727,6 +727,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 {
|
||||||
|
@ -813,9 +819,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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -567,6 +567,7 @@ pub(crate) fn mir_operand_get_const_val<'tcx>(
|
||||||
{
|
{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
TerminatorKind::TailCall { .. } => return None,
|
||||||
TerminatorKind::Call { .. } => {}
|
TerminatorKind::Call { .. } => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 { .. }
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 { .. }
|
||||||
|
|
|
@ -26,8 +26,8 @@ use rustc_target::abi::{call::FnAbi, Align, HasDataLayout, Size, TargetDataLayou
|
||||||
use super::{
|
use super::{
|
||||||
err_inval, throw_inval, throw_ub, throw_ub_custom, throw_unsup, GlobalId, Immediate,
|
err_inval, throw_inval, throw_ub, throw_ub_custom, throw_unsup, GlobalId, Immediate,
|
||||||
InterpErrorInfo, InterpResult, MPlaceTy, Machine, MemPlace, MemPlaceMeta, Memory, MemoryKind,
|
InterpErrorInfo, InterpResult, MPlaceTy, Machine, MemPlace, MemPlaceMeta, Memory, MemoryKind,
|
||||||
OpTy, Operand, Place, PlaceTy, Pointer, PointerArithmetic, Projectable, Provenance, Scalar,
|
OpTy, Operand, Place, PlaceTy, Pointer, PointerArithmetic, Projectable, Provenance,
|
||||||
StackPopJump,
|
ReturnAction, Scalar,
|
||||||
};
|
};
|
||||||
use crate::errors;
|
use crate::errors;
|
||||||
use crate::util;
|
use crate::util;
|
||||||
|
@ -159,6 +159,19 @@ pub enum StackPopCleanup {
|
||||||
Root { cleanup: bool },
|
Root { cleanup: bool },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return type of [`InterpCx::pop_stack_frame`].
|
||||||
|
pub struct StackPopInfo<'tcx, Prov: Provenance> {
|
||||||
|
/// Additional information about the action to be performed when returning from the popped
|
||||||
|
/// stack frame.
|
||||||
|
pub return_action: ReturnAction,
|
||||||
|
|
||||||
|
/// [`return_to_block`](Frame::return_to_block) of the popped stack frame.
|
||||||
|
pub return_to_block: StackPopCleanup,
|
||||||
|
|
||||||
|
/// [`return_place`](Frame::return_place) of the popped stack frame.
|
||||||
|
pub return_place: MPlaceTy<'tcx, Prov>,
|
||||||
|
}
|
||||||
|
|
||||||
/// State of a local variable including a memoized layout
|
/// State of a local variable including a memoized layout
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct LocalState<'tcx, Prov: Provenance = CtfeProvenance> {
|
pub struct LocalState<'tcx, Prov: Provenance = CtfeProvenance> {
|
||||||
|
@ -803,14 +816,31 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
return_to_block: StackPopCleanup,
|
return_to_block: StackPopCleanup,
|
||||||
) -> InterpResult<'tcx> {
|
) -> InterpResult<'tcx> {
|
||||||
trace!("body: {:#?}", body);
|
trace!("body: {:#?}", body);
|
||||||
|
|
||||||
|
// First push a stack frame so we have access to the local args
|
||||||
|
self.push_new_stack_frame(instance, body, return_to_block, return_place.clone())?;
|
||||||
|
|
||||||
|
self.after_stack_frame_push(instance, body)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new stack frame, initializes it and pushes it onto the stack.
|
||||||
|
/// A private helper for [`push_stack_frame`](InterpCx::push_stack_frame).
|
||||||
|
fn push_new_stack_frame(
|
||||||
|
&mut self,
|
||||||
|
instance: ty::Instance<'tcx>,
|
||||||
|
body: &'tcx mir::Body<'tcx>,
|
||||||
|
return_to_block: StackPopCleanup,
|
||||||
|
return_place: MPlaceTy<'tcx, M::Provenance>,
|
||||||
|
) -> InterpResult<'tcx> {
|
||||||
let dead_local = LocalState { value: LocalValue::Dead, layout: Cell::new(None) };
|
let dead_local = LocalState { value: LocalValue::Dead, layout: Cell::new(None) };
|
||||||
let locals = IndexVec::from_elem(dead_local, &body.local_decls);
|
let locals = IndexVec::from_elem(dead_local, &body.local_decls);
|
||||||
// First push a stack frame so we have access to the local args
|
|
||||||
let pre_frame = Frame {
|
let pre_frame = Frame {
|
||||||
body,
|
body,
|
||||||
loc: Right(body.span), // Span used for errors caused during preamble.
|
loc: Right(body.span), // Span used for errors caused during preamble.
|
||||||
return_to_block,
|
return_to_block,
|
||||||
return_place: return_place.clone(),
|
return_place,
|
||||||
locals,
|
locals,
|
||||||
instance,
|
instance,
|
||||||
tracing_span: SpanGuard::new(),
|
tracing_span: SpanGuard::new(),
|
||||||
|
@ -819,6 +849,15 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
let frame = M::init_frame(self, pre_frame)?;
|
let frame = M::init_frame(self, pre_frame)?;
|
||||||
self.stack_mut().push(frame);
|
self.stack_mut().push(frame);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A private helper for [`push_stack_frame`](InterpCx::push_stack_frame).
|
||||||
|
fn after_stack_frame_push(
|
||||||
|
&mut self,
|
||||||
|
instance: ty::Instance<'tcx>,
|
||||||
|
body: &'tcx mir::Body<'tcx>,
|
||||||
|
) -> InterpResult<'tcx> {
|
||||||
// Make sure all the constants required by this frame evaluate successfully (post-monomorphization check).
|
// Make sure all the constants required by this frame evaluate successfully (post-monomorphization check).
|
||||||
for &const_ in &body.required_consts {
|
for &const_ in &body.required_consts {
|
||||||
let c =
|
let c =
|
||||||
|
@ -839,6 +878,61 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Pops a stack frame from the stack and returns some information about it.
|
||||||
|
///
|
||||||
|
/// This also deallocates locals, if necessary.
|
||||||
|
///
|
||||||
|
/// [`M::before_stack_pop`] should be called before calling this function.
|
||||||
|
/// [`M::after_stack_pop`] is called by this function automatically.
|
||||||
|
///
|
||||||
|
/// [`M::before_stack_pop`]: Machine::before_stack_pop
|
||||||
|
/// [`M::after_stack_pop`]: Machine::after_stack_pop
|
||||||
|
pub fn pop_stack_frame(
|
||||||
|
&mut self,
|
||||||
|
unwinding: bool,
|
||||||
|
) -> InterpResult<'tcx, StackPopInfo<'tcx, M::Provenance>> {
|
||||||
|
let cleanup = self.cleanup_current_frame_locals()?;
|
||||||
|
|
||||||
|
let frame =
|
||||||
|
self.stack_mut().pop().expect("tried to pop a stack frame, but there were none");
|
||||||
|
|
||||||
|
let return_to_block = frame.return_to_block;
|
||||||
|
let return_place = frame.return_place.clone();
|
||||||
|
|
||||||
|
let return_action;
|
||||||
|
if cleanup {
|
||||||
|
return_action = M::after_stack_pop(self, frame, unwinding)?;
|
||||||
|
assert_ne!(return_action, ReturnAction::NoCleanup);
|
||||||
|
} else {
|
||||||
|
return_action = ReturnAction::NoCleanup;
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(StackPopInfo { return_action, return_to_block, return_place })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A private helper for [`pop_stack_frame`](InterpCx::pop_stack_frame).
|
||||||
|
/// Returns `true` if cleanup has been done, `false` otherwise.
|
||||||
|
fn cleanup_current_frame_locals(&mut self) -> InterpResult<'tcx, bool> {
|
||||||
|
// Cleanup: deallocate locals.
|
||||||
|
// Usually we want to clean up (deallocate locals), but in a few rare cases we don't.
|
||||||
|
// We do this while the frame is still on the stack, so errors point to the callee.
|
||||||
|
let return_to_block = self.frame().return_to_block;
|
||||||
|
let cleanup = match return_to_block {
|
||||||
|
StackPopCleanup::Goto { .. } => true,
|
||||||
|
StackPopCleanup::Root { cleanup, .. } => cleanup,
|
||||||
|
};
|
||||||
|
|
||||||
|
if cleanup {
|
||||||
|
// We need to take the locals out, since we need to mutate while iterating.
|
||||||
|
let locals = mem::take(&mut self.frame_mut().locals);
|
||||||
|
for local in &locals {
|
||||||
|
self.deallocate_local(local.value)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(cleanup)
|
||||||
|
}
|
||||||
|
|
||||||
/// Jump to the given block.
|
/// Jump to the given block.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn go_to_block(&mut self, target: mir::BasicBlock) {
|
pub fn go_to_block(&mut self, target: mir::BasicBlock) {
|
||||||
|
@ -886,7 +980,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pops the current frame from the stack, deallocating the
|
/// Pops the current frame from the stack, deallocating the
|
||||||
/// memory for allocated locals.
|
/// memory for allocated locals, and jumps to an appropriate place.
|
||||||
///
|
///
|
||||||
/// If `unwinding` is `false`, then we are performing a normal return
|
/// If `unwinding` is `false`, then we are performing a normal return
|
||||||
/// from a function. In this case, we jump back into the frame of the caller,
|
/// from a function. In this case, we jump back into the frame of the caller,
|
||||||
|
@ -899,7 +993,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
/// The cleanup block ends with a special `Resume` terminator, which will
|
/// The cleanup block ends with a special `Resume` terminator, which will
|
||||||
/// cause us to continue unwinding.
|
/// cause us to continue unwinding.
|
||||||
#[instrument(skip(self), level = "debug")]
|
#[instrument(skip(self), level = "debug")]
|
||||||
pub(super) fn pop_stack_frame(&mut self, unwinding: bool) -> InterpResult<'tcx> {
|
pub(super) fn return_from_current_stack_frame(
|
||||||
|
&mut self,
|
||||||
|
unwinding: bool,
|
||||||
|
) -> InterpResult<'tcx> {
|
||||||
info!(
|
info!(
|
||||||
"popping stack frame ({})",
|
"popping stack frame ({})",
|
||||||
if unwinding { "during unwinding" } else { "returning from function" }
|
if unwinding { "during unwinding" } else { "returning from function" }
|
||||||
|
@ -947,45 +1044,31 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
|
|
||||||
// Cleanup: deallocate locals.
|
|
||||||
// Usually we want to clean up (deallocate locals), but in a few rare cases we don't.
|
|
||||||
// We do this while the frame is still on the stack, so errors point to the callee.
|
|
||||||
let return_to_block = self.frame().return_to_block;
|
|
||||||
let cleanup = match return_to_block {
|
|
||||||
StackPopCleanup::Goto { .. } => true,
|
|
||||||
StackPopCleanup::Root { cleanup, .. } => cleanup,
|
|
||||||
};
|
|
||||||
if cleanup {
|
|
||||||
// We need to take the locals out, since we need to mutate while iterating.
|
|
||||||
let locals = mem::take(&mut self.frame_mut().locals);
|
|
||||||
for local in &locals {
|
|
||||||
self.deallocate_local(local.value)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// All right, now it is time to actually pop the frame.
|
// All right, now it is time to actually pop the frame.
|
||||||
// Note that its locals are gone already, but that's fine.
|
let stack_pop_info = self.pop_stack_frame(unwinding)?;
|
||||||
let frame =
|
|
||||||
self.stack_mut().pop().expect("tried to pop a stack frame, but there were none");
|
|
||||||
// Report error from return value copy, if any.
|
// Report error from return value copy, if any.
|
||||||
copy_ret_result?;
|
copy_ret_result?;
|
||||||
|
|
||||||
|
match stack_pop_info.return_action {
|
||||||
|
ReturnAction::Normal => {}
|
||||||
|
ReturnAction::NoJump => {
|
||||||
|
// The hook already did everything.
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
ReturnAction::NoCleanup => {
|
||||||
// If we are not doing cleanup, also skip everything else.
|
// If we are not doing cleanup, also skip everything else.
|
||||||
if !cleanup {
|
|
||||||
assert!(self.stack().is_empty(), "only the topmost frame should ever be leaked");
|
assert!(self.stack().is_empty(), "only the topmost frame should ever be leaked");
|
||||||
assert!(!unwinding, "tried to skip cleanup during unwinding");
|
assert!(!unwinding, "tried to skip cleanup during unwinding");
|
||||||
// Skip machine hook.
|
// Skip machine hook.
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
if M::after_stack_pop(self, frame, unwinding)? == StackPopJump::NoJump {
|
|
||||||
// The hook already did everything.
|
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normal return, figure out where to jump.
|
// Normal return, figure out where to jump.
|
||||||
if unwinding {
|
if unwinding {
|
||||||
// Follow the unwind edge.
|
// Follow the unwind edge.
|
||||||
let unwind = match return_to_block {
|
let unwind = match stack_pop_info.return_to_block {
|
||||||
StackPopCleanup::Goto { unwind, .. } => unwind,
|
StackPopCleanup::Goto { unwind, .. } => unwind,
|
||||||
StackPopCleanup::Root { .. } => {
|
StackPopCleanup::Root { .. } => {
|
||||||
panic!("encountered StackPopCleanup::Root when unwinding!")
|
panic!("encountered StackPopCleanup::Root when unwinding!")
|
||||||
|
@ -995,7 +1078,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
self.unwind_to_block(unwind)
|
self.unwind_to_block(unwind)
|
||||||
} else {
|
} else {
|
||||||
// Follow the normal return edge.
|
// Follow the normal return edge.
|
||||||
match return_to_block {
|
match stack_pop_info.return_to_block {
|
||||||
StackPopCleanup::Goto { ret, .. } => self.return_to_block(ret),
|
StackPopCleanup::Goto { ret, .. } => self.return_to_block(ret),
|
||||||
StackPopCleanup::Root { .. } => {
|
StackPopCleanup::Root { .. } => {
|
||||||
assert!(
|
assert!(
|
||||||
|
|
|
@ -23,10 +23,11 @@ use super::{
|
||||||
MemoryKind, Misalignment, OpTy, PlaceTy, Pointer, Provenance,
|
MemoryKind, Misalignment, OpTy, PlaceTy, Pointer, Provenance,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Data returned by Machine::stack_pop,
|
/// Data returned by [`Machine::after_stack_pop`], and consumed by
|
||||||
/// to provide further control over the popping of the stack frame
|
/// [`InterpCx::return_from_current_stack_frame`] to determine what actions should be done when
|
||||||
|
/// returning from a stack frame.
|
||||||
#[derive(Eq, PartialEq, Debug, Copy, Clone)]
|
#[derive(Eq, PartialEq, Debug, Copy, Clone)]
|
||||||
pub enum StackPopJump {
|
pub enum ReturnAction {
|
||||||
/// Indicates that no special handling should be
|
/// Indicates that no special handling should be
|
||||||
/// done - we'll either return normally or unwind
|
/// done - we'll either return normally or unwind
|
||||||
/// based on the terminator for the function
|
/// based on the terminator for the function
|
||||||
|
@ -36,6 +37,9 @@ pub enum StackPopJump {
|
||||||
/// Indicates that we should *not* jump to the return/unwind address, as the callback already
|
/// Indicates that we should *not* jump to the return/unwind address, as the callback already
|
||||||
/// took care of everything.
|
/// took care of everything.
|
||||||
NoJump,
|
NoJump,
|
||||||
|
|
||||||
|
/// Returned by [`InterpCx::pop_stack_frame`] when no cleanup should be done.
|
||||||
|
NoCleanup,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this kind of memory is allowed to leak
|
/// Whether this kind of memory is allowed to leak
|
||||||
|
@ -522,10 +526,10 @@ pub trait Machine<'tcx>: Sized {
|
||||||
_ecx: &mut InterpCx<'tcx, Self>,
|
_ecx: &mut InterpCx<'tcx, Self>,
|
||||||
_frame: Frame<'tcx, Self::Provenance, Self::FrameExtra>,
|
_frame: Frame<'tcx, Self::Provenance, Self::FrameExtra>,
|
||||||
unwinding: bool,
|
unwinding: bool,
|
||||||
) -> InterpResult<'tcx, StackPopJump> {
|
) -> InterpResult<'tcx, ReturnAction> {
|
||||||
// By default, we do not support unwinding from panics
|
// By default, we do not support unwinding from panics
|
||||||
assert!(!unwinding);
|
assert!(!unwinding);
|
||||||
Ok(StackPopJump::Normal)
|
Ok(ReturnAction::Normal)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called immediately after actual memory was allocated for a local
|
/// Called immediately after actual memory was allocated for a local
|
||||||
|
|
|
@ -26,7 +26,7 @@ pub use self::intern::{
|
||||||
intern_const_alloc_for_constprop, intern_const_alloc_recursive, HasStaticRootDefId, InternKind,
|
intern_const_alloc_for_constprop, intern_const_alloc_recursive, HasStaticRootDefId, InternKind,
|
||||||
InternResult,
|
InternResult,
|
||||||
};
|
};
|
||||||
pub use self::machine::{compile_time_machine, AllocMap, Machine, MayLeak, StackPopJump};
|
pub use self::machine::{compile_time_machine, AllocMap, Machine, MayLeak, ReturnAction};
|
||||||
pub use self::memory::{AllocKind, AllocRef, AllocRefMut, FnVal, Memory, MemoryKind};
|
pub use self::memory::{AllocKind, AllocRef, AllocRefMut, FnVal, Memory, MemoryKind};
|
||||||
pub use self::operand::{ImmTy, Immediate, OpTy, Readable};
|
pub use self::operand::{ImmTy, Immediate, OpTy, Readable};
|
||||||
pub use self::place::{MPlaceTy, MemPlaceMeta, PlaceTy, Writeable};
|
pub use self::place::{MPlaceTy, MemPlaceMeta, PlaceTy, Writeable};
|
||||||
|
|
|
@ -32,7 +32,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
// We are unwinding and this fn has no cleanup code.
|
// We are unwinding and this fn has no cleanup code.
|
||||||
// Just go on unwinding.
|
// Just go on unwinding.
|
||||||
trace!("unwinding: skipping frame");
|
trace!("unwinding: skipping frame");
|
||||||
self.pop_stack_frame(/* unwinding */ true)?;
|
self.return_from_current_stack_frame(/* unwinding */ true)?;
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
};
|
};
|
||||||
let basic_block = &self.body().basic_blocks[loc.block];
|
let basic_block = &self.body().basic_blocks[loc.block];
|
||||||
|
|
|
@ -4,9 +4,8 @@ use either::Either;
|
||||||
use rustc_middle::ty::TyCtxt;
|
use rustc_middle::ty::TyCtxt;
|
||||||
use tracing::trace;
|
use tracing::trace;
|
||||||
|
|
||||||
use rustc_middle::span_bug;
|
|
||||||
use rustc_middle::{
|
use rustc_middle::{
|
||||||
mir,
|
bug, mir, span_bug,
|
||||||
ty::{
|
ty::{
|
||||||
self,
|
self,
|
||||||
layout::{FnAbiOf, IntegerExt, LayoutOf, TyAndLayout},
|
layout::{FnAbiOf, IntegerExt, LayoutOf, TyAndLayout},
|
||||||
|
@ -26,7 +25,10 @@ use super::{
|
||||||
InterpResult, MPlaceTy, Machine, OpTy, PlaceTy, Projectable, Provenance, Scalar,
|
InterpResult, MPlaceTy, Machine, OpTy, PlaceTy, Projectable, Provenance, Scalar,
|
||||||
StackPopCleanup,
|
StackPopCleanup,
|
||||||
};
|
};
|
||||||
use crate::fluent_generated as fluent;
|
use crate::{
|
||||||
|
fluent_generated as fluent,
|
||||||
|
interpret::{eval_context::StackPopInfo, ReturnAction},
|
||||||
|
};
|
||||||
|
|
||||||
/// An argment passed to a function.
|
/// An argment passed to a function.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -47,6 +49,15 @@ impl<'tcx, Prov: Provenance> FnArg<'tcx, Prov> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct EvaluatedCalleeAndArgs<'tcx, M: Machine<'tcx>> {
|
||||||
|
callee: FnVal<'tcx, M::ExtraFnVal>,
|
||||||
|
args: Vec<FnArg<'tcx, M::Provenance>>,
|
||||||
|
fn_sig: ty::FnSig<'tcx>,
|
||||||
|
fn_abi: &'tcx FnAbi<'tcx, Ty<'tcx>>,
|
||||||
|
/// True if the function is marked as `#[track_caller]` ([`ty::InstanceKind::requires_caller_location`])
|
||||||
|
with_caller_location: bool,
|
||||||
|
}
|
||||||
|
|
||||||
impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
/// Make a copy of the given fn_arg. Any `InPlace` are degenerated to copies, no protection of the
|
/// Make a copy of the given fn_arg. Any `InPlace` are degenerated to copies, no protection of the
|
||||||
/// original memory occurs.
|
/// original memory occurs.
|
||||||
|
@ -84,7 +95,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
use rustc_middle::mir::TerminatorKind::*;
|
use rustc_middle::mir::TerminatorKind::*;
|
||||||
match terminator.kind {
|
match terminator.kind {
|
||||||
Return => {
|
Return => {
|
||||||
self.pop_stack_frame(/* unwinding */ false)?
|
self.return_from_current_stack_frame(/* unwinding */ false)?
|
||||||
}
|
}
|
||||||
|
|
||||||
Goto { target } => self.go_to_block(target),
|
Goto { target } => self.go_to_block(target),
|
||||||
|
@ -124,40 +135,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
} => {
|
} => {
|
||||||
let old_stack = self.frame_idx();
|
let old_stack = self.frame_idx();
|
||||||
let old_loc = self.frame().loc;
|
let old_loc = self.frame().loc;
|
||||||
let func = self.eval_operand(func, None)?;
|
|
||||||
let args = self.eval_fn_call_arguments(args)?;
|
|
||||||
|
|
||||||
let fn_sig_binder = func.layout.ty.fn_sig(*self.tcx);
|
let EvaluatedCalleeAndArgs { callee, args, fn_sig, fn_abi, with_caller_location } =
|
||||||
let fn_sig =
|
self.eval_callee_and_args(terminator, func, args)?;
|
||||||
self.tcx.normalize_erasing_late_bound_regions(self.param_env, fn_sig_binder);
|
|
||||||
let extra_args = &args[fn_sig.inputs().len()..];
|
|
||||||
let extra_args =
|
|
||||||
self.tcx.mk_type_list_from_iter(extra_args.iter().map(|arg| arg.layout().ty));
|
|
||||||
|
|
||||||
let (fn_val, fn_abi, with_caller_location) = match *func.layout.ty.kind() {
|
|
||||||
ty::FnPtr(_sig) => {
|
|
||||||
let fn_ptr = self.read_pointer(&func)?;
|
|
||||||
let fn_val = self.get_ptr_fn(fn_ptr)?;
|
|
||||||
(fn_val, self.fn_abi_of_fn_ptr(fn_sig_binder, extra_args)?, false)
|
|
||||||
}
|
|
||||||
ty::FnDef(def_id, args) => {
|
|
||||||
let instance = self.resolve(def_id, args)?;
|
|
||||||
(
|
|
||||||
FnVal::Instance(instance),
|
|
||||||
self.fn_abi_of_instance(instance, extra_args)?,
|
|
||||||
instance.def.requires_caller_location(*self.tcx),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
_ => span_bug!(
|
|
||||||
terminator.source_info.span,
|
|
||||||
"invalid callee of type {}",
|
|
||||||
func.layout.ty
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
let destination = self.force_allocation(&self.eval_place(destination)?)?;
|
let destination = self.force_allocation(&self.eval_place(destination)?)?;
|
||||||
self.eval_fn_call(
|
self.eval_fn_call(
|
||||||
fn_val,
|
callee,
|
||||||
(fn_sig.abi, fn_abi),
|
(fn_sig.abi, fn_abi),
|
||||||
&args,
|
&args,
|
||||||
with_caller_location,
|
with_caller_location,
|
||||||
|
@ -172,6 +156,22 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TailCall { ref func, ref args, fn_span: _ } => {
|
||||||
|
let old_frame_idx = self.frame_idx();
|
||||||
|
|
||||||
|
let EvaluatedCalleeAndArgs { callee, args, fn_sig, fn_abi, with_caller_location } =
|
||||||
|
self.eval_callee_and_args(terminator, func, args)?;
|
||||||
|
|
||||||
|
self.eval_fn_tail_call(callee, (fn_sig.abi, fn_abi), &args, with_caller_location)?;
|
||||||
|
|
||||||
|
if self.frame_idx() != old_frame_idx {
|
||||||
|
span_bug!(
|
||||||
|
terminator.source_info.span,
|
||||||
|
"evaluating this tail call pushed a new stack frame"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
@ -209,7 +209,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
trace!("unwinding: resuming from cleanup");
|
trace!("unwinding: resuming from cleanup");
|
||||||
// By definition, a Resume terminator means
|
// By definition, a Resume terminator means
|
||||||
// that we're unwinding
|
// that we're unwinding
|
||||||
self.pop_stack_frame(/* unwinding */ true)?;
|
self.return_from_current_stack_frame(/* unwinding */ true)?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -514,6 +514,45 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shared part of `Call` and `TailCall` implementation — finding and evaluating all the
|
||||||
|
/// necessary information about callee and arguments to make a call.
|
||||||
|
fn eval_callee_and_args(
|
||||||
|
&self,
|
||||||
|
terminator: &mir::Terminator<'tcx>,
|
||||||
|
func: &mir::Operand<'tcx>,
|
||||||
|
args: &[Spanned<mir::Operand<'tcx>>],
|
||||||
|
) -> InterpResult<'tcx, EvaluatedCalleeAndArgs<'tcx, M>> {
|
||||||
|
let func = self.eval_operand(func, None)?;
|
||||||
|
let args = self.eval_fn_call_arguments(args)?;
|
||||||
|
|
||||||
|
let fn_sig_binder = func.layout.ty.fn_sig(*self.tcx);
|
||||||
|
let fn_sig = self.tcx.normalize_erasing_late_bound_regions(self.param_env, fn_sig_binder);
|
||||||
|
let extra_args = &args[fn_sig.inputs().len()..];
|
||||||
|
let extra_args =
|
||||||
|
self.tcx.mk_type_list_from_iter(extra_args.iter().map(|arg| arg.layout().ty));
|
||||||
|
|
||||||
|
let (callee, fn_abi, with_caller_location) = match *func.layout.ty.kind() {
|
||||||
|
ty::FnPtr(_sig) => {
|
||||||
|
let fn_ptr = self.read_pointer(&func)?;
|
||||||
|
let fn_val = self.get_ptr_fn(fn_ptr)?;
|
||||||
|
(fn_val, self.fn_abi_of_fn_ptr(fn_sig_binder, extra_args)?, false)
|
||||||
|
}
|
||||||
|
ty::FnDef(def_id, args) => {
|
||||||
|
let instance = self.resolve(def_id, args)?;
|
||||||
|
(
|
||||||
|
FnVal::Instance(instance),
|
||||||
|
self.fn_abi_of_instance(instance, extra_args)?,
|
||||||
|
instance.def.requires_caller_location(*self.tcx),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
span_bug!(terminator.source_info.span, "invalid callee of type {}", func.layout.ty)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(EvaluatedCalleeAndArgs { callee, args, fn_sig, fn_abi, with_caller_location })
|
||||||
|
}
|
||||||
|
|
||||||
/// Call this function -- pushing the stack frame and initializing the arguments.
|
/// Call this function -- pushing the stack frame and initializing the arguments.
|
||||||
///
|
///
|
||||||
/// `caller_fn_abi` is used to determine if all the arguments are passed the proper way.
|
/// `caller_fn_abi` is used to determine if all the arguments are passed the proper way.
|
||||||
|
@ -924,6 +963,49 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn eval_fn_tail_call(
|
||||||
|
&mut self,
|
||||||
|
fn_val: FnVal<'tcx, M::ExtraFnVal>,
|
||||||
|
(caller_abi, caller_fn_abi): (Abi, &FnAbi<'tcx, Ty<'tcx>>),
|
||||||
|
args: &[FnArg<'tcx, M::Provenance>],
|
||||||
|
with_caller_location: bool,
|
||||||
|
) -> InterpResult<'tcx> {
|
||||||
|
trace!("eval_fn_call: {:#?}", fn_val);
|
||||||
|
|
||||||
|
// This is the "canonical" implementation of tails calls,
|
||||||
|
// a pop of the current stack frame, followed by a normal call
|
||||||
|
// which pushes a new stack frame, with the return address from
|
||||||
|
// the popped stack frame.
|
||||||
|
//
|
||||||
|
// Note that we are using `pop_stack_frame` and not `return_from_current_stack_frame`,
|
||||||
|
// as the latter "executes" the goto to the return block, but we don't want to,
|
||||||
|
// only the tail called function should return to the current return block.
|
||||||
|
M::before_stack_pop(self, self.frame())?;
|
||||||
|
|
||||||
|
let StackPopInfo { return_action, return_to_block, return_place } =
|
||||||
|
self.pop_stack_frame(false)?;
|
||||||
|
|
||||||
|
assert_eq!(return_action, ReturnAction::Normal);
|
||||||
|
|
||||||
|
let StackPopCleanup::Goto { ret, unwind } = return_to_block else {
|
||||||
|
bug!("can't tailcall as root");
|
||||||
|
};
|
||||||
|
|
||||||
|
// FIXME(explicit_tail_calls):
|
||||||
|
// we should check if both caller&callee can/n't unwind,
|
||||||
|
// see <https://github.com/rust-lang/rust/pull/113128#issuecomment-1614979803>
|
||||||
|
|
||||||
|
self.eval_fn_call(
|
||||||
|
fn_val,
|
||||||
|
(caller_abi, caller_fn_abi),
|
||||||
|
args,
|
||||||
|
with_caller_location,
|
||||||
|
&return_place,
|
||||||
|
ret,
|
||||||
|
unwind,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn check_fn_target_features(&self, instance: ty::Instance<'tcx>) -> InterpResult<'tcx, ()> {
|
fn check_fn_target_features(&self, instance: ty::Instance<'tcx>) -> InterpResult<'tcx, ()> {
|
||||||
// Calling functions with `#[target_feature]` is not unsafe on WASM, see #84988
|
// Calling functions with `#[target_feature]` is not unsafe on WASM, see #84988
|
||||||
let attrs = self.tcx.codegen_fn_attrs(instance.def_id());
|
let attrs = self.tcx.codegen_fn_attrs(instance.def_id());
|
||||||
|
|
|
@ -854,6 +854,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 {
|
||||||
|
@ -921,7 +931,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
|
||||||
|
|
|
@ -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 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: Box<[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",
|
||||||
|
|
|
@ -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),
|
||||||
|
|
||||||
|
|
|
@ -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: _,
|
||||||
|
|
|
@ -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,42 @@ 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, lint_level, region_scope } = 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:?}")
|
||||||
|
};
|
||||||
|
|
||||||
|
this.in_scope((region_scope, source_info), lint_level, |this| {
|
||||||
|
let fun = unpack!(block = this.as_local_operand(block, fun));
|
||||||
|
let args: Box<[_]> = 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);
|
||||||
|
|
||||||
|
unpack!(block = this.break_for_tail_call(block, &args, source_info));
|
||||||
|
|
||||||
|
this.cfg.terminate(
|
||||||
|
block,
|
||||||
|
source_info,
|
||||||
|
TerminatorKind::TailCall { func: fun, args, fn_span },
|
||||||
|
);
|
||||||
|
|
||||||
|
this.cfg.start_new_block().unit()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
assert!(
|
assert!(
|
||||||
|
|
|
@ -745,6 +745,91 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||||
self.cfg.terminate(block, source_info, TerminatorKind::UnwindResume);
|
self.cfg.terminate(block, source_info, TerminatorKind::UnwindResume);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets up the drops for explict tail calls.
|
||||||
|
///
|
||||||
|
/// Unlike other kinds of early exits, tail calls do not go through the drop tree.
|
||||||
|
/// Instead, all scheduled drops are immediately added to the CFG.
|
||||||
|
pub(crate) fn break_for_tail_call(
|
||||||
|
&mut self,
|
||||||
|
mut block: BasicBlock,
|
||||||
|
args: &[Spanned<Operand<'tcx>>],
|
||||||
|
source_info: SourceInfo,
|
||||||
|
) -> BlockAnd<()> {
|
||||||
|
let arg_drops: Vec<_> = args
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.filter_map(|arg| match &arg.node {
|
||||||
|
Operand::Copy(_) => bug!("copy op in tail call args"),
|
||||||
|
Operand::Move(place) => {
|
||||||
|
let local =
|
||||||
|
place.as_local().unwrap_or_else(|| bug!("projection in tail call args"));
|
||||||
|
|
||||||
|
Some(DropData { source_info, local, kind: DropKind::Value })
|
||||||
|
}
|
||||||
|
Operand::Constant(_) => None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut unwind_to = self.diverge_cleanup_target(
|
||||||
|
self.scopes.scopes.iter().rev().nth(1).unwrap().region_scope,
|
||||||
|
DUMMY_SP,
|
||||||
|
);
|
||||||
|
let unwind_drops = &mut self.scopes.unwind_drops;
|
||||||
|
|
||||||
|
// the innermost scope contains only the destructors for the tail call arguments
|
||||||
|
// we only want to drop these in case of a panic, so we skip it
|
||||||
|
for scope in self.scopes.scopes[1..].iter().rev().skip(1) {
|
||||||
|
// FIXME(explicit_tail_calls) code duplication with `build_scope_drops`
|
||||||
|
for drop_data in scope.drops.iter().rev() {
|
||||||
|
let source_info = drop_data.source_info;
|
||||||
|
let local = drop_data.local;
|
||||||
|
|
||||||
|
match drop_data.kind {
|
||||||
|
DropKind::Value => {
|
||||||
|
// `unwind_to` should drop the value that we're about to
|
||||||
|
// schedule. If dropping this value panics, then we continue
|
||||||
|
// with the *next* value on the unwind path.
|
||||||
|
debug_assert_eq!(unwind_drops.drops[unwind_to].data.local, drop_data.local);
|
||||||
|
debug_assert_eq!(unwind_drops.drops[unwind_to].data.kind, drop_data.kind);
|
||||||
|
unwind_to = unwind_drops.drops[unwind_to].next;
|
||||||
|
|
||||||
|
let mut unwind_entry_point = unwind_to;
|
||||||
|
|
||||||
|
// the tail call arguments must be dropped if any of these drops panic
|
||||||
|
for drop in arg_drops.iter().copied() {
|
||||||
|
unwind_entry_point = unwind_drops.add_drop(drop, unwind_entry_point);
|
||||||
|
}
|
||||||
|
|
||||||
|
unwind_drops.add_entry_point(block, unwind_entry_point);
|
||||||
|
|
||||||
|
let next = self.cfg.start_new_block();
|
||||||
|
self.cfg.terminate(
|
||||||
|
block,
|
||||||
|
source_info,
|
||||||
|
TerminatorKind::Drop {
|
||||||
|
place: local.into(),
|
||||||
|
target: next,
|
||||||
|
unwind: UnwindAction::Continue,
|
||||||
|
replace: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
block = next;
|
||||||
|
}
|
||||||
|
DropKind::Storage => {
|
||||||
|
// Only temps and vars need their storage dead.
|
||||||
|
assert!(local.index() > self.arg_count);
|
||||||
|
self.cfg.push(
|
||||||
|
block,
|
||||||
|
Statement { source_info, kind: StatementKind::StorageDead(local) },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
block.unit()
|
||||||
|
}
|
||||||
|
|
||||||
fn leave_top_scope(&mut self, block: BasicBlock) -> BasicBlock {
|
fn leave_top_scope(&mut self, block: BasicBlock) -> BasicBlock {
|
||||||
// If we are emitting a `drop` statement, we need to have the cached
|
// If we are emitting a `drop` statement, we need to have the cached
|
||||||
// diverge cleanup pads ready in case that drop panics.
|
// diverge cleanup pads ready in case that drop panics.
|
||||||
|
@ -1523,6 +1608,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
|
||||||
|
|
|
@ -217,12 +217,28 @@ impl<'mir, 'tcx, C: TerminatorClassifier<'tcx>> TriColorVisitor<BasicBlocks<'tcx
|
||||||
| TerminatorKind::FalseUnwind { .. }
|
| TerminatorKind::FalseUnwind { .. }
|
||||||
| TerminatorKind::Goto { .. }
|
| TerminatorKind::Goto { .. }
|
||||||
| TerminatorKind::SwitchInt { .. } => ControlFlow::Continue(()),
|
| TerminatorKind::SwitchInt { .. } => ControlFlow::Continue(()),
|
||||||
|
|
||||||
|
// Note that tail call terminator technically returns to the caller,
|
||||||
|
// but for purposes of this lint it makes sense to count it as possibly recursive,
|
||||||
|
// since it's still a call.
|
||||||
|
//
|
||||||
|
// If this'll be repurposed for something else, this might need to be changed.
|
||||||
|
TerminatorKind::TailCall { .. } => ControlFlow::Continue(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_settled(&mut self, bb: BasicBlock) -> ControlFlow<Self::BreakVal> {
|
fn node_settled(&mut self, bb: BasicBlock) -> ControlFlow<Self::BreakVal> {
|
||||||
// When we examine a node for the last time, remember it if it is a recursive call.
|
// When we examine a node for the last time, remember it if it is a recursive call.
|
||||||
let terminator = self.body[bb].terminator();
|
let terminator = self.body[bb].terminator();
|
||||||
|
|
||||||
|
// FIXME(explicit_tail_calls): highlight tail calls as "recursive call site"
|
||||||
|
//
|
||||||
|
// We don't want to lint functions that recurse only through tail calls
|
||||||
|
// (such as `fn g() { become () }`), so just adding `| TailCall { ... }`
|
||||||
|
// here won't work.
|
||||||
|
//
|
||||||
|
// But at the same time we would like to highlight both calls in a function like
|
||||||
|
// `fn f() { if false { become f() } else { f() } }`, so we need to figure something out.
|
||||||
if self.classifier.is_recursive_terminator(self.tcx, self.body, terminator) {
|
if self.classifier.is_recursive_terminator(self.tcx, self.body, terminator) {
|
||||||
self.reachable_recursive_calls.push(terminator.source_info.span);
|
self.reachable_recursive_calls.push(terminator.source_info.span);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 { .. } => {}
|
||||||
|
|
|
@ -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 => {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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(_)
|
||||||
|
|
|
@ -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 { .. }
|
||||||
|
|
|
@ -40,7 +40,9 @@ impl<'b, 'tcx> CostChecker<'b, 'tcx> {
|
||||||
fn is_call_like(bbd: &BasicBlockData<'_>) -> bool {
|
fn is_call_like(bbd: &BasicBlockData<'_>) -> bool {
|
||||||
use TerminatorKind::*;
|
use TerminatorKind::*;
|
||||||
match bbd.terminator().kind {
|
match bbd.terminator().kind {
|
||||||
Call { .. } | Drop { .. } | Assert { .. } | InlineAsm { .. } => true,
|
Call { .. } | TailCall { .. } | Drop { .. } | Assert { .. } | InlineAsm { .. } => {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
Goto { .. }
|
Goto { .. }
|
||||||
| SwitchInt { .. }
|
| SwitchInt { .. }
|
||||||
|
@ -137,6 +139,9 @@ impl<'tcx> Visitor<'tcx> for CostChecker<'_, 'tcx> {
|
||||||
self.penalty += LANDINGPAD_PENALTY;
|
self.penalty += LANDINGPAD_PENALTY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
TerminatorKind::TailCall { .. } => {
|
||||||
|
self.penalty += CALL_PENALTY;
|
||||||
|
}
|
||||||
TerminatorKind::SwitchInt { discr, targets } => {
|
TerminatorKind::SwitchInt { discr, targets } => {
|
||||||
if discr.constant().is_some() {
|
if discr.constant().is_some() {
|
||||||
// Not only will this become a `Goto`, but likely other
|
// Not only will this become a `Goto`, but likely other
|
||||||
|
|
|
@ -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(&[]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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 { .. }
|
||||||
|
|
|
@ -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() });
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -400,18 +400,20 @@ 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, .. } => {
|
||||||
|
// 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 {
|
if let Some(target) = target {
|
||||||
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);
|
||||||
|
|
||||||
// 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,
|
||||||
|
@ -424,7 +426,7 @@ impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> {
|
||||||
|
|
||||||
// The call destination place and Operand::Move place used as an argument might be
|
// 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.
|
// 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() {
|
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.
|
// This is bad! The callee will expect the memory to be aligned.
|
||||||
self.fail(
|
self.fail(
|
||||||
location,
|
location,
|
||||||
|
@ -434,6 +436,8 @@ impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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,14 +1502,21 @@ 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, .. } => {
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -330,7 +330,8 @@ fn check_terminator<'tcx>(
|
||||||
target: _,
|
target: _,
|
||||||
unwind: _,
|
unwind: _,
|
||||||
fn_span: _,
|
fn_span: _,
|
||||||
} => {
|
}
|
||||||
|
| TerminatorKind::TailCall { func, args, fn_span: _ } => {
|
||||||
let fn_ty = func.ty(body, tcx);
|
let fn_ty = func.ty(body, tcx);
|
||||||
if let ty::FnDef(fn_def_id, _) = *fn_ty.kind() {
|
if let ty::FnDef(fn_def_id, _) = *fn_ty.kind() {
|
||||||
if !is_const_fn(tcx, fn_def_id, msrv) {
|
if !is_const_fn(tcx, fn_def_id, msrv) {
|
||||||
|
|
|
@ -1434,7 +1434,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
|
||||||
ecx: &mut InterpCx<'tcx, Self>,
|
ecx: &mut InterpCx<'tcx, Self>,
|
||||||
frame: Frame<'tcx, Provenance, FrameExtra<'tcx>>,
|
frame: Frame<'tcx, Provenance, FrameExtra<'tcx>>,
|
||||||
unwinding: bool,
|
unwinding: bool,
|
||||||
) -> InterpResult<'tcx, StackPopJump> {
|
) -> InterpResult<'tcx, ReturnAction> {
|
||||||
if frame.extra.is_user_relevant {
|
if frame.extra.is_user_relevant {
|
||||||
// All that we store is whether or not the frame we just removed is local, so now we
|
// All that we store is whether or not the frame we just removed is local, so now we
|
||||||
// have no idea where the next topmost local frame is. So we recompute it.
|
// have no idea where the next topmost local frame is. So we recompute it.
|
||||||
|
|
|
@ -113,7 +113,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||||
&mut self,
|
&mut self,
|
||||||
mut extra: FrameExtra<'tcx>,
|
mut extra: FrameExtra<'tcx>,
|
||||||
unwinding: bool,
|
unwinding: bool,
|
||||||
) -> InterpResult<'tcx, StackPopJump> {
|
) -> InterpResult<'tcx, ReturnAction> {
|
||||||
let this = self.eval_context_mut();
|
let this = self.eval_context_mut();
|
||||||
trace!("handle_stack_pop_unwind(extra = {:?}, unwinding = {})", extra, unwinding);
|
trace!("handle_stack_pop_unwind(extra = {:?}, unwinding = {})", extra, unwinding);
|
||||||
|
|
||||||
|
@ -150,9 +150,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// We pushed a new stack frame, the engine should not do any jumping now!
|
// We pushed a new stack frame, the engine should not do any jumping now!
|
||||||
Ok(StackPopJump::NoJump)
|
Ok(ReturnAction::NoJump)
|
||||||
} else {
|
} else {
|
||||||
Ok(StackPopJump::Normal)
|
Ok(ReturnAction::Normal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
10
src/tools/miri/tests/fail/tail_calls/cc-mismatch.rs
Normal file
10
src/tools/miri/tests/fail/tail_calls/cc-mismatch.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
//@error-in-other-file: Undefined Behavior: calling a function with calling convention C using calling convention Rust
|
||||||
|
#![feature(explicit_tail_calls)]
|
||||||
|
#![allow(incomplete_features)]
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let f = unsafe { std::mem::transmute::<extern "C" fn(), fn()>(f) };
|
||||||
|
become f();
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn f() {}
|
25
src/tools/miri/tests/fail/tail_calls/cc-mismatch.stderr
Normal file
25
src/tools/miri/tests/fail/tail_calls/cc-mismatch.stderr
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
error: Undefined Behavior: calling a function with calling convention C using calling convention Rust
|
||||||
|
--> RUSTLIB/core/src/ops/function.rs:LL:CC
|
||||||
|
|
|
||||||
|
LL | extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ calling a function with calling convention C using calling convention Rust
|
||||||
|
|
|
||||||
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
|
||||||
|
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
|
||||||
|
= note: BACKTRACE:
|
||||||
|
= note: inside `<fn() as std::ops::FnOnce<()>>::call_once - shim(fn())` at RUSTLIB/core/src/ops/function.rs:LL:CC
|
||||||
|
= note: inside `std::sys::backtrace::__rust_begin_short_backtrace::<fn(), ()>` at RUSTLIB/std/src/sys/backtrace.rs:LL:CC
|
||||||
|
= note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC
|
||||||
|
= note: inside `std::ops::function::impls::<impl std::ops::FnOnce<()> for &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>::call_once` at RUSTLIB/core/src/ops/function.rs:LL:CC
|
||||||
|
= note: inside `std::panicking::r#try::do_call::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32>` at RUSTLIB/std/src/panicking.rs:LL:CC
|
||||||
|
= note: inside `std::panicking::r#try::<i32, &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>` at RUSTLIB/std/src/panicking.rs:LL:CC
|
||||||
|
= note: inside `std::panic::catch_unwind::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32>` at RUSTLIB/std/src/panic.rs:LL:CC
|
||||||
|
= note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC
|
||||||
|
= note: inside `std::panicking::r#try::do_call::<{closure@std::rt::lang_start_internal::{closure#2}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC
|
||||||
|
= note: inside `std::panicking::r#try::<isize, {closure@std::rt::lang_start_internal::{closure#2}}>` at RUSTLIB/std/src/panicking.rs:LL:CC
|
||||||
|
= note: inside `std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#2}}, isize>` at RUSTLIB/std/src/panic.rs:LL:CC
|
||||||
|
= note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC
|
||||||
|
= note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC
|
||||||
|
|
||||||
|
error: aborting due to 1 previous error
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
#![feature(explicit_tail_calls)]
|
||||||
|
#![allow(incomplete_features)]
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// FIXME(explicit_tail_calls):
|
||||||
|
// the error should point to `become g(x)`,
|
||||||
|
// but tail calls mess up the backtrace it seems like...
|
||||||
|
f(0);
|
||||||
|
//~^ error: Undefined Behavior: calling a function with argument of type i32 passing data of type u32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn f(x: u32) {
|
||||||
|
let g = unsafe { std::mem::transmute::<fn(i32), fn(u32)>(g) };
|
||||||
|
become g(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn g(_: i32) {}
|
|
@ -0,0 +1,17 @@
|
||||||
|
error: Undefined Behavior: calling a function with argument of type i32 passing data of type u32
|
||||||
|
--> $DIR/signature-mismatch-arg.rs:LL:CC
|
||||||
|
|
|
||||||
|
LL | f(0);
|
||||||
|
| ^^^^ calling a function with argument of type i32 passing data of type u32
|
||||||
|
|
|
||||||
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
|
||||||
|
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
|
||||||
|
= help: this means these two types are not *guaranteed* to be ABI-compatible across all targets
|
||||||
|
= help: if you think this code should be accepted anyway, please report an issue with Miri
|
||||||
|
= note: BACKTRACE:
|
||||||
|
= note: inside `main` at $DIR/signature-mismatch-arg.rs:LL:CC
|
||||||
|
|
||||||
|
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||||
|
|
||||||
|
error: aborting due to 1 previous error
|
||||||
|
|
39
src/tools/miri/tests/pass/tail_call.rs
Normal file
39
src/tools/miri/tests/pass/tail_call.rs
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
#![allow(incomplete_features)]
|
||||||
|
#![feature(explicit_tail_calls)]
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
assert_eq!(factorial(10), 3_628_800);
|
||||||
|
assert_eq!(mutually_recursive_identity(1000), 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn factorial(n: u32) -> u32 {
|
||||||
|
fn factorial_acc(n: u32, acc: u32) -> u32 {
|
||||||
|
match n {
|
||||||
|
0 => acc,
|
||||||
|
_ => become factorial_acc(n - 1, acc * n),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
factorial_acc(n, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is of course very silly, but we need to demonstrate mutual recursion somehow so...
|
||||||
|
fn mutually_recursive_identity(x: u32) -> u32 {
|
||||||
|
fn switch(src: u32, tgt: u32) -> u32 {
|
||||||
|
match src {
|
||||||
|
0 => tgt,
|
||||||
|
_ if src % 7 == 0 => become advance_with_extra_steps(src, tgt),
|
||||||
|
_ => become advance(src, tgt),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn advance(src: u32, tgt: u32) -> u32 {
|
||||||
|
become switch(src - 1, tgt + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn advance_with_extra_steps(src: u32, tgt: u32) -> u32 {
|
||||||
|
become advance(src, tgt)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(x, 0)
|
||||||
|
}
|
108
tests/mir-opt/tail_call_drops.f.ElaborateDrops.panic-abort.diff
Normal file
108
tests/mir-opt/tail_call_drops.f.ElaborateDrops.panic-abort.diff
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
- // MIR for `f` before ElaborateDrops
|
||||||
|
+ // MIR for `f` after ElaborateDrops
|
||||||
|
|
||||||
|
fn f() -> () {
|
||||||
|
let mut _0: ();
|
||||||
|
let mut _1: !;
|
||||||
|
let _2: std::string::String;
|
||||||
|
let _6: ();
|
||||||
|
let mut _7: std::string::String;
|
||||||
|
+ let mut _8: bool;
|
||||||
|
scope 1 {
|
||||||
|
debug _a => _2;
|
||||||
|
let _3: i32;
|
||||||
|
scope 2 {
|
||||||
|
debug _b => _3;
|
||||||
|
let _4: std::string::String;
|
||||||
|
scope 3 {
|
||||||
|
debug _c => _4;
|
||||||
|
let _5: std::string::String;
|
||||||
|
scope 4 {
|
||||||
|
debug _d => _5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bb0: {
|
||||||
|
+ _8 = const false;
|
||||||
|
StorageLive(_2);
|
||||||
|
_2 = String::new() -> [return: bb1, unwind: bb12];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb1: {
|
||||||
|
StorageLive(_3);
|
||||||
|
_3 = const 12_i32;
|
||||||
|
StorageLive(_4);
|
||||||
|
_4 = String::new() -> [return: bb2, unwind: bb11];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb2: {
|
||||||
|
+ _8 = const true;
|
||||||
|
StorageLive(_5);
|
||||||
|
_5 = String::new() -> [return: bb3, unwind: bb10];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb3: {
|
||||||
|
StorageLive(_6);
|
||||||
|
StorageLive(_7);
|
||||||
|
+ _8 = const false;
|
||||||
|
_7 = move _4;
|
||||||
|
_6 = std::mem::drop::<String>(move _7) -> [return: bb4, unwind: bb8];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb4: {
|
||||||
|
StorageDead(_7);
|
||||||
|
StorageDead(_6);
|
||||||
|
drop(_5) -> [return: bb5, unwind: bb10];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb5: {
|
||||||
|
StorageDead(_5);
|
||||||
|
- drop(_4) -> [return: bb6, unwind: bb11];
|
||||||
|
+ goto -> bb6;
|
||||||
|
}
|
||||||
|
|
||||||
|
bb6: {
|
||||||
|
+ _8 = const false;
|
||||||
|
StorageDead(_4);
|
||||||
|
StorageDead(_3);
|
||||||
|
drop(_2) -> [return: bb7, unwind: bb12];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb7: {
|
||||||
|
StorageDead(_2);
|
||||||
|
tailcall g();
|
||||||
|
}
|
||||||
|
|
||||||
|
bb8 (cleanup): {
|
||||||
|
- drop(_7) -> [return: bb9, unwind terminate(cleanup)];
|
||||||
|
+ goto -> bb9;
|
||||||
|
}
|
||||||
|
|
||||||
|
bb9 (cleanup): {
|
||||||
|
drop(_5) -> [return: bb10, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb10 (cleanup): {
|
||||||
|
- drop(_4) -> [return: bb11, unwind terminate(cleanup)];
|
||||||
|
+ goto -> bb14;
|
||||||
|
}
|
||||||
|
|
||||||
|
bb11 (cleanup): {
|
||||||
|
drop(_2) -> [return: bb12, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb12 (cleanup): {
|
||||||
|
resume;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ bb13 (cleanup): {
|
||||||
|
+ drop(_4) -> [return: bb11, unwind terminate(cleanup)];
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ bb14 (cleanup): {
|
||||||
|
+ switchInt(_8) -> [0: bb11, otherwise: bb13];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
109
tests/mir-opt/tail_call_drops.f.ElaborateDrops.panic-unwind.diff
Normal file
109
tests/mir-opt/tail_call_drops.f.ElaborateDrops.panic-unwind.diff
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
- // MIR for `f` before ElaborateDrops
|
||||||
|
+ // MIR for `f` after ElaborateDrops
|
||||||
|
|
||||||
|
fn f() -> () {
|
||||||
|
let mut _0: ();
|
||||||
|
let mut _1: !;
|
||||||
|
let _2: std::string::String;
|
||||||
|
let _6: ();
|
||||||
|
let mut _7: std::string::String;
|
||||||
|
+ let mut _8: bool;
|
||||||
|
scope 1 {
|
||||||
|
debug _a => _2;
|
||||||
|
let _3: i32;
|
||||||
|
scope 2 {
|
||||||
|
debug _b => _3;
|
||||||
|
let _4: std::string::String;
|
||||||
|
scope 3 {
|
||||||
|
debug _c => _4;
|
||||||
|
let _5: std::string::String;
|
||||||
|
scope 4 {
|
||||||
|
debug _d => _5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bb0: {
|
||||||
|
+ _8 = const false;
|
||||||
|
StorageLive(_2);
|
||||||
|
_2 = String::new() -> [return: bb1, unwind continue];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb1: {
|
||||||
|
StorageLive(_3);
|
||||||
|
_3 = const 12_i32;
|
||||||
|
StorageLive(_4);
|
||||||
|
_4 = String::new() -> [return: bb2, unwind: bb11];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb2: {
|
||||||
|
+ _8 = const true;
|
||||||
|
StorageLive(_5);
|
||||||
|
_5 = String::new() -> [return: bb3, unwind: bb10];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb3: {
|
||||||
|
StorageLive(_6);
|
||||||
|
StorageLive(_7);
|
||||||
|
+ _8 = const false;
|
||||||
|
_7 = move _4;
|
||||||
|
_6 = std::mem::drop::<String>(move _7) -> [return: bb4, unwind: bb8];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb4: {
|
||||||
|
StorageDead(_7);
|
||||||
|
StorageDead(_6);
|
||||||
|
drop(_5) -> [return: bb5, unwind: bb10];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb5: {
|
||||||
|
StorageDead(_5);
|
||||||
|
- drop(_4) -> [return: bb6, unwind: bb11];
|
||||||
|
+ goto -> bb6;
|
||||||
|
}
|
||||||
|
|
||||||
|
bb6: {
|
||||||
|
+ _8 = const false;
|
||||||
|
StorageDead(_4);
|
||||||
|
StorageDead(_3);
|
||||||
|
- drop(_2) -> [return: bb7, unwind continue];
|
||||||
|
+ drop(_2) -> [return: bb7, unwind: bb12];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb7: {
|
||||||
|
StorageDead(_2);
|
||||||
|
tailcall g();
|
||||||
|
}
|
||||||
|
|
||||||
|
bb8 (cleanup): {
|
||||||
|
- drop(_7) -> [return: bb9, unwind terminate(cleanup)];
|
||||||
|
+ goto -> bb9;
|
||||||
|
}
|
||||||
|
|
||||||
|
bb9 (cleanup): {
|
||||||
|
drop(_5) -> [return: bb10, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb10 (cleanup): {
|
||||||
|
- drop(_4) -> [return: bb11, unwind terminate(cleanup)];
|
||||||
|
+ goto -> bb14;
|
||||||
|
}
|
||||||
|
|
||||||
|
bb11 (cleanup): {
|
||||||
|
drop(_2) -> [return: bb12, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb12 (cleanup): {
|
||||||
|
resume;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ bb13 (cleanup): {
|
||||||
|
+ drop(_4) -> [return: bb11, unwind terminate(cleanup)];
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ bb14 (cleanup): {
|
||||||
|
+ switchInt(_8) -> [0: bb11, otherwise: bb13];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
118
tests/mir-opt/tail_call_drops.f.built.after.panic-abort.mir
Normal file
118
tests/mir-opt/tail_call_drops.f.built.after.panic-abort.mir
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
// MIR for `f` after built
|
||||||
|
|
||||||
|
fn f() -> () {
|
||||||
|
let mut _0: ();
|
||||||
|
let mut _1: !;
|
||||||
|
let _2: std::string::String;
|
||||||
|
let _6: ();
|
||||||
|
let mut _7: std::string::String;
|
||||||
|
scope 1 {
|
||||||
|
debug _a => _2;
|
||||||
|
let _3: i32;
|
||||||
|
scope 2 {
|
||||||
|
debug _b => _3;
|
||||||
|
let _4: std::string::String;
|
||||||
|
scope 3 {
|
||||||
|
debug _c => _4;
|
||||||
|
let _5: std::string::String;
|
||||||
|
scope 4 {
|
||||||
|
debug _d => _5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bb0: {
|
||||||
|
StorageLive(_2);
|
||||||
|
_2 = String::new() -> [return: bb1, unwind: bb17];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb1: {
|
||||||
|
FakeRead(ForLet(None), _2);
|
||||||
|
StorageLive(_3);
|
||||||
|
_3 = const 12_i32;
|
||||||
|
FakeRead(ForLet(None), _3);
|
||||||
|
StorageLive(_4);
|
||||||
|
_4 = String::new() -> [return: bb2, unwind: bb16];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb2: {
|
||||||
|
FakeRead(ForLet(None), _4);
|
||||||
|
StorageLive(_5);
|
||||||
|
_5 = String::new() -> [return: bb3, unwind: bb15];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb3: {
|
||||||
|
FakeRead(ForLet(None), _5);
|
||||||
|
StorageLive(_6);
|
||||||
|
StorageLive(_7);
|
||||||
|
_7 = move _4;
|
||||||
|
_6 = std::mem::drop::<String>(move _7) -> [return: bb4, unwind: bb13];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb4: {
|
||||||
|
StorageDead(_7);
|
||||||
|
StorageDead(_6);
|
||||||
|
drop(_5) -> [return: bb5, unwind: bb15];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb5: {
|
||||||
|
StorageDead(_5);
|
||||||
|
drop(_4) -> [return: bb6, unwind: bb16];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb6: {
|
||||||
|
StorageDead(_4);
|
||||||
|
StorageDead(_3);
|
||||||
|
drop(_2) -> [return: bb7, unwind: bb17];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb7: {
|
||||||
|
StorageDead(_2);
|
||||||
|
tailcall g();
|
||||||
|
}
|
||||||
|
|
||||||
|
bb8: {
|
||||||
|
drop(_5) -> [return: bb9, unwind: bb15];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb9: {
|
||||||
|
StorageDead(_5);
|
||||||
|
drop(_4) -> [return: bb10, unwind: bb16];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb10: {
|
||||||
|
StorageDead(_4);
|
||||||
|
StorageDead(_3);
|
||||||
|
drop(_2) -> [return: bb11, unwind: bb17];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb11: {
|
||||||
|
StorageDead(_2);
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
bb12: {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bb13 (cleanup): {
|
||||||
|
drop(_7) -> [return: bb14, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb14 (cleanup): {
|
||||||
|
drop(_5) -> [return: bb15, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb15 (cleanup): {
|
||||||
|
drop(_4) -> [return: bb16, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb16 (cleanup): {
|
||||||
|
drop(_2) -> [return: bb17, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb17 (cleanup): {
|
||||||
|
resume;
|
||||||
|
}
|
||||||
|
}
|
118
tests/mir-opt/tail_call_drops.f.built.after.panic-unwind.mir
Normal file
118
tests/mir-opt/tail_call_drops.f.built.after.panic-unwind.mir
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
// MIR for `f` after built
|
||||||
|
|
||||||
|
fn f() -> () {
|
||||||
|
let mut _0: ();
|
||||||
|
let mut _1: !;
|
||||||
|
let _2: std::string::String;
|
||||||
|
let _6: ();
|
||||||
|
let mut _7: std::string::String;
|
||||||
|
scope 1 {
|
||||||
|
debug _a => _2;
|
||||||
|
let _3: i32;
|
||||||
|
scope 2 {
|
||||||
|
debug _b => _3;
|
||||||
|
let _4: std::string::String;
|
||||||
|
scope 3 {
|
||||||
|
debug _c => _4;
|
||||||
|
let _5: std::string::String;
|
||||||
|
scope 4 {
|
||||||
|
debug _d => _5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bb0: {
|
||||||
|
StorageLive(_2);
|
||||||
|
_2 = String::new() -> [return: bb1, unwind: bb17];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb1: {
|
||||||
|
FakeRead(ForLet(None), _2);
|
||||||
|
StorageLive(_3);
|
||||||
|
_3 = const 12_i32;
|
||||||
|
FakeRead(ForLet(None), _3);
|
||||||
|
StorageLive(_4);
|
||||||
|
_4 = String::new() -> [return: bb2, unwind: bb16];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb2: {
|
||||||
|
FakeRead(ForLet(None), _4);
|
||||||
|
StorageLive(_5);
|
||||||
|
_5 = String::new() -> [return: bb3, unwind: bb15];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb3: {
|
||||||
|
FakeRead(ForLet(None), _5);
|
||||||
|
StorageLive(_6);
|
||||||
|
StorageLive(_7);
|
||||||
|
_7 = move _4;
|
||||||
|
_6 = std::mem::drop::<String>(move _7) -> [return: bb4, unwind: bb13];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb4: {
|
||||||
|
StorageDead(_7);
|
||||||
|
StorageDead(_6);
|
||||||
|
drop(_5) -> [return: bb5, unwind: bb15];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb5: {
|
||||||
|
StorageDead(_5);
|
||||||
|
drop(_4) -> [return: bb6, unwind: bb16];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb6: {
|
||||||
|
StorageDead(_4);
|
||||||
|
StorageDead(_3);
|
||||||
|
drop(_2) -> [return: bb7, unwind: bb17];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb7: {
|
||||||
|
StorageDead(_2);
|
||||||
|
tailcall g();
|
||||||
|
}
|
||||||
|
|
||||||
|
bb8: {
|
||||||
|
drop(_5) -> [return: bb9, unwind: bb15];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb9: {
|
||||||
|
StorageDead(_5);
|
||||||
|
drop(_4) -> [return: bb10, unwind: bb16];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb10: {
|
||||||
|
StorageDead(_4);
|
||||||
|
StorageDead(_3);
|
||||||
|
drop(_2) -> [return: bb11, unwind: bb17];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb11: {
|
||||||
|
StorageDead(_2);
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
bb12: {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bb13 (cleanup): {
|
||||||
|
drop(_7) -> [return: bb14, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb14 (cleanup): {
|
||||||
|
drop(_5) -> [return: bb15, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb15 (cleanup): {
|
||||||
|
drop(_4) -> [return: bb16, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb16 (cleanup): {
|
||||||
|
drop(_2) -> [return: bb17, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb17 (cleanup): {
|
||||||
|
resume;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,184 @@
|
||||||
|
- // MIR for `f_with_arg` before ElaborateDrops
|
||||||
|
+ // MIR for `f_with_arg` after ElaborateDrops
|
||||||
|
|
||||||
|
fn f_with_arg(_1: String, _2: String) -> () {
|
||||||
|
debug _arg1 => _1;
|
||||||
|
debug _arg2 => _2;
|
||||||
|
let mut _0: ();
|
||||||
|
let mut _3: !;
|
||||||
|
let _4: std::string::String;
|
||||||
|
let _8: ();
|
||||||
|
let mut _9: std::string::String;
|
||||||
|
let mut _10: std::string::String;
|
||||||
|
let mut _11: std::string::String;
|
||||||
|
+ let mut _12: bool;
|
||||||
|
scope 1 {
|
||||||
|
debug _a => _4;
|
||||||
|
let _5: i32;
|
||||||
|
scope 2 {
|
||||||
|
debug _b => _5;
|
||||||
|
let _6: std::string::String;
|
||||||
|
scope 3 {
|
||||||
|
debug _c => _6;
|
||||||
|
let _7: std::string::String;
|
||||||
|
scope 4 {
|
||||||
|
debug _d => _7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bb0: {
|
||||||
|
+ _12 = const false;
|
||||||
|
StorageLive(_4);
|
||||||
|
_4 = String::new() -> [return: bb1, unwind: bb27];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb1: {
|
||||||
|
StorageLive(_5);
|
||||||
|
_5 = const 12_i32;
|
||||||
|
StorageLive(_6);
|
||||||
|
_6 = String::new() -> [return: bb2, unwind: bb26];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb2: {
|
||||||
|
+ _12 = const true;
|
||||||
|
StorageLive(_7);
|
||||||
|
_7 = String::new() -> [return: bb3, unwind: bb25];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb3: {
|
||||||
|
StorageLive(_8);
|
||||||
|
StorageLive(_9);
|
||||||
|
+ _12 = const false;
|
||||||
|
_9 = move _6;
|
||||||
|
_8 = std::mem::drop::<String>(move _9) -> [return: bb4, unwind: bb23];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb4: {
|
||||||
|
StorageDead(_9);
|
||||||
|
StorageDead(_8);
|
||||||
|
StorageLive(_10);
|
||||||
|
_10 = String::new() -> [return: bb5, unwind: bb24];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb5: {
|
||||||
|
StorageLive(_11);
|
||||||
|
_11 = String::new() -> [return: bb6, unwind: bb22];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb6: {
|
||||||
|
drop(_7) -> [return: bb7, unwind: bb20];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb7: {
|
||||||
|
StorageDead(_7);
|
||||||
|
- drop(_6) -> [return: bb8, unwind: bb18];
|
||||||
|
+ goto -> bb8;
|
||||||
|
}
|
||||||
|
|
||||||
|
bb8: {
|
||||||
|
+ _12 = const false;
|
||||||
|
StorageDead(_6);
|
||||||
|
StorageDead(_5);
|
||||||
|
drop(_4) -> [return: bb9, unwind: bb16];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb9: {
|
||||||
|
StorageDead(_4);
|
||||||
|
drop(_2) -> [return: bb10, unwind: bb14];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb10: {
|
||||||
|
drop(_1) -> [return: bb11, unwind: bb12];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb11: {
|
||||||
|
tailcall g_with_arg(Spanned { node: move _10, span: $DIR/tail_call_drops.rs:36:23: 36:36 (#0) }, Spanned { node: move _11, span: $DIR/tail_call_drops.rs:36:38: 36:51 (#0) });
|
||||||
|
}
|
||||||
|
|
||||||
|
bb12 (cleanup): {
|
||||||
|
drop(_10) -> [return: bb13, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb13 (cleanup): {
|
||||||
|
drop(_11) -> [return: bb29, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb14 (cleanup): {
|
||||||
|
drop(_10) -> [return: bb15, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb15 (cleanup): {
|
||||||
|
drop(_11) -> [return: bb28, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb16 (cleanup): {
|
||||||
|
drop(_10) -> [return: bb17, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb17 (cleanup): {
|
||||||
|
drop(_11) -> [return: bb27, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb18 (cleanup): {
|
||||||
|
- drop(_10) -> [return: bb19, unwind terminate(cleanup)];
|
||||||
|
+ goto -> bb19;
|
||||||
|
}
|
||||||
|
|
||||||
|
bb19 (cleanup): {
|
||||||
|
- drop(_11) -> [return: bb26, unwind terminate(cleanup)];
|
||||||
|
+ goto -> bb26;
|
||||||
|
}
|
||||||
|
|
||||||
|
bb20 (cleanup): {
|
||||||
|
drop(_10) -> [return: bb21, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb21 (cleanup): {
|
||||||
|
drop(_11) -> [return: bb25, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb22 (cleanup): {
|
||||||
|
drop(_10) -> [return: bb24, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb23 (cleanup): {
|
||||||
|
- drop(_9) -> [return: bb24, unwind terminate(cleanup)];
|
||||||
|
+ goto -> bb24;
|
||||||
|
}
|
||||||
|
|
||||||
|
bb24 (cleanup): {
|
||||||
|
drop(_7) -> [return: bb25, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb25 (cleanup): {
|
||||||
|
- drop(_6) -> [return: bb26, unwind terminate(cleanup)];
|
||||||
|
+ goto -> bb31;
|
||||||
|
}
|
||||||
|
|
||||||
|
bb26 (cleanup): {
|
||||||
|
drop(_4) -> [return: bb27, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb27 (cleanup): {
|
||||||
|
drop(_2) -> [return: bb28, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb28 (cleanup): {
|
||||||
|
drop(_1) -> [return: bb29, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb29 (cleanup): {
|
||||||
|
resume;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ bb30 (cleanup): {
|
||||||
|
+ drop(_6) -> [return: bb26, unwind terminate(cleanup)];
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ bb31 (cleanup): {
|
||||||
|
+ switchInt(_12) -> [0: bb26, otherwise: bb30];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,184 @@
|
||||||
|
- // MIR for `f_with_arg` before ElaborateDrops
|
||||||
|
+ // MIR for `f_with_arg` after ElaborateDrops
|
||||||
|
|
||||||
|
fn f_with_arg(_1: String, _2: String) -> () {
|
||||||
|
debug _arg1 => _1;
|
||||||
|
debug _arg2 => _2;
|
||||||
|
let mut _0: ();
|
||||||
|
let mut _3: !;
|
||||||
|
let _4: std::string::String;
|
||||||
|
let _8: ();
|
||||||
|
let mut _9: std::string::String;
|
||||||
|
let mut _10: std::string::String;
|
||||||
|
let mut _11: std::string::String;
|
||||||
|
+ let mut _12: bool;
|
||||||
|
scope 1 {
|
||||||
|
debug _a => _4;
|
||||||
|
let _5: i32;
|
||||||
|
scope 2 {
|
||||||
|
debug _b => _5;
|
||||||
|
let _6: std::string::String;
|
||||||
|
scope 3 {
|
||||||
|
debug _c => _6;
|
||||||
|
let _7: std::string::String;
|
||||||
|
scope 4 {
|
||||||
|
debug _d => _7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bb0: {
|
||||||
|
+ _12 = const false;
|
||||||
|
StorageLive(_4);
|
||||||
|
_4 = String::new() -> [return: bb1, unwind: bb27];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb1: {
|
||||||
|
StorageLive(_5);
|
||||||
|
_5 = const 12_i32;
|
||||||
|
StorageLive(_6);
|
||||||
|
_6 = String::new() -> [return: bb2, unwind: bb26];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb2: {
|
||||||
|
+ _12 = const true;
|
||||||
|
StorageLive(_7);
|
||||||
|
_7 = String::new() -> [return: bb3, unwind: bb25];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb3: {
|
||||||
|
StorageLive(_8);
|
||||||
|
StorageLive(_9);
|
||||||
|
+ _12 = const false;
|
||||||
|
_9 = move _6;
|
||||||
|
_8 = std::mem::drop::<String>(move _9) -> [return: bb4, unwind: bb23];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb4: {
|
||||||
|
StorageDead(_9);
|
||||||
|
StorageDead(_8);
|
||||||
|
StorageLive(_10);
|
||||||
|
_10 = String::new() -> [return: bb5, unwind: bb24];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb5: {
|
||||||
|
StorageLive(_11);
|
||||||
|
_11 = String::new() -> [return: bb6, unwind: bb22];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb6: {
|
||||||
|
drop(_7) -> [return: bb7, unwind: bb20];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb7: {
|
||||||
|
StorageDead(_7);
|
||||||
|
- drop(_6) -> [return: bb8, unwind: bb18];
|
||||||
|
+ goto -> bb8;
|
||||||
|
}
|
||||||
|
|
||||||
|
bb8: {
|
||||||
|
+ _12 = const false;
|
||||||
|
StorageDead(_6);
|
||||||
|
StorageDead(_5);
|
||||||
|
drop(_4) -> [return: bb9, unwind: bb16];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb9: {
|
||||||
|
StorageDead(_4);
|
||||||
|
drop(_2) -> [return: bb10, unwind: bb14];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb10: {
|
||||||
|
drop(_1) -> [return: bb11, unwind: bb12];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb11: {
|
||||||
|
tailcall g_with_arg(Spanned { node: move _10, span: $DIR/tail_call_drops.rs:36:23: 36:36 (#0) }, Spanned { node: move _11, span: $DIR/tail_call_drops.rs:36:38: 36:51 (#0) });
|
||||||
|
}
|
||||||
|
|
||||||
|
bb12 (cleanup): {
|
||||||
|
drop(_10) -> [return: bb13, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb13 (cleanup): {
|
||||||
|
drop(_11) -> [return: bb29, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb14 (cleanup): {
|
||||||
|
drop(_10) -> [return: bb15, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb15 (cleanup): {
|
||||||
|
drop(_11) -> [return: bb28, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb16 (cleanup): {
|
||||||
|
drop(_10) -> [return: bb17, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb17 (cleanup): {
|
||||||
|
drop(_11) -> [return: bb27, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb18 (cleanup): {
|
||||||
|
- drop(_10) -> [return: bb19, unwind terminate(cleanup)];
|
||||||
|
+ goto -> bb19;
|
||||||
|
}
|
||||||
|
|
||||||
|
bb19 (cleanup): {
|
||||||
|
- drop(_11) -> [return: bb26, unwind terminate(cleanup)];
|
||||||
|
+ goto -> bb26;
|
||||||
|
}
|
||||||
|
|
||||||
|
bb20 (cleanup): {
|
||||||
|
drop(_10) -> [return: bb21, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb21 (cleanup): {
|
||||||
|
drop(_11) -> [return: bb25, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb22 (cleanup): {
|
||||||
|
drop(_10) -> [return: bb24, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb23 (cleanup): {
|
||||||
|
- drop(_9) -> [return: bb24, unwind terminate(cleanup)];
|
||||||
|
+ goto -> bb24;
|
||||||
|
}
|
||||||
|
|
||||||
|
bb24 (cleanup): {
|
||||||
|
drop(_7) -> [return: bb25, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb25 (cleanup): {
|
||||||
|
- drop(_6) -> [return: bb26, unwind terminate(cleanup)];
|
||||||
|
+ goto -> bb31;
|
||||||
|
}
|
||||||
|
|
||||||
|
bb26 (cleanup): {
|
||||||
|
drop(_4) -> [return: bb27, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb27 (cleanup): {
|
||||||
|
drop(_2) -> [return: bb28, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb28 (cleanup): {
|
||||||
|
drop(_1) -> [return: bb29, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb29 (cleanup): {
|
||||||
|
resume;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ bb30 (cleanup): {
|
||||||
|
+ drop(_6) -> [return: bb26, unwind terminate(cleanup)];
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ bb31 (cleanup): {
|
||||||
|
+ switchInt(_12) -> [0: bb26, otherwise: bb30];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,202 @@
|
||||||
|
// MIR for `f_with_arg` after built
|
||||||
|
|
||||||
|
fn f_with_arg(_1: String, _2: String) -> () {
|
||||||
|
debug _arg1 => _1;
|
||||||
|
debug _arg2 => _2;
|
||||||
|
let mut _0: ();
|
||||||
|
let mut _3: !;
|
||||||
|
let _4: std::string::String;
|
||||||
|
let _8: ();
|
||||||
|
let mut _9: std::string::String;
|
||||||
|
let mut _10: std::string::String;
|
||||||
|
let mut _11: std::string::String;
|
||||||
|
scope 1 {
|
||||||
|
debug _a => _4;
|
||||||
|
let _5: i32;
|
||||||
|
scope 2 {
|
||||||
|
debug _b => _5;
|
||||||
|
let _6: std::string::String;
|
||||||
|
scope 3 {
|
||||||
|
debug _c => _6;
|
||||||
|
let _7: std::string::String;
|
||||||
|
scope 4 {
|
||||||
|
debug _d => _7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bb0: {
|
||||||
|
StorageLive(_4);
|
||||||
|
_4 = String::new() -> [return: bb1, unwind: bb34];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb1: {
|
||||||
|
FakeRead(ForLet(None), _4);
|
||||||
|
StorageLive(_5);
|
||||||
|
_5 = const 12_i32;
|
||||||
|
FakeRead(ForLet(None), _5);
|
||||||
|
StorageLive(_6);
|
||||||
|
_6 = String::new() -> [return: bb2, unwind: bb33];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb2: {
|
||||||
|
FakeRead(ForLet(None), _6);
|
||||||
|
StorageLive(_7);
|
||||||
|
_7 = String::new() -> [return: bb3, unwind: bb32];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb3: {
|
||||||
|
FakeRead(ForLet(None), _7);
|
||||||
|
StorageLive(_8);
|
||||||
|
StorageLive(_9);
|
||||||
|
_9 = move _6;
|
||||||
|
_8 = std::mem::drop::<String>(move _9) -> [return: bb4, unwind: bb30];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb4: {
|
||||||
|
StorageDead(_9);
|
||||||
|
StorageDead(_8);
|
||||||
|
StorageLive(_10);
|
||||||
|
_10 = String::new() -> [return: bb5, unwind: bb31];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb5: {
|
||||||
|
StorageLive(_11);
|
||||||
|
_11 = String::new() -> [return: bb6, unwind: bb29];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb6: {
|
||||||
|
drop(_7) -> [return: bb7, unwind: bb27];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb7: {
|
||||||
|
StorageDead(_7);
|
||||||
|
drop(_6) -> [return: bb8, unwind: bb25];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb8: {
|
||||||
|
StorageDead(_6);
|
||||||
|
StorageDead(_5);
|
||||||
|
drop(_4) -> [return: bb9, unwind: bb23];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb9: {
|
||||||
|
StorageDead(_4);
|
||||||
|
drop(_2) -> [return: bb10, unwind: bb21];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb10: {
|
||||||
|
drop(_1) -> [return: bb11, unwind: bb19];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb11: {
|
||||||
|
tailcall g_with_arg(Spanned { node: move _10, span: $DIR/tail_call_drops.rs:36:23: 36:36 (#0) }, Spanned { node: move _11, span: $DIR/tail_call_drops.rs:36:38: 36:51 (#0) });
|
||||||
|
}
|
||||||
|
|
||||||
|
bb12: {
|
||||||
|
StorageDead(_11);
|
||||||
|
StorageDead(_10);
|
||||||
|
drop(_7) -> [return: bb13, unwind: bb32];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb13: {
|
||||||
|
StorageDead(_7);
|
||||||
|
drop(_6) -> [return: bb14, unwind: bb33];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb14: {
|
||||||
|
StorageDead(_6);
|
||||||
|
StorageDead(_5);
|
||||||
|
drop(_4) -> [return: bb15, unwind: bb34];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb15: {
|
||||||
|
StorageDead(_4);
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
bb16: {
|
||||||
|
drop(_2) -> [return: bb17, unwind: bb35];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb17: {
|
||||||
|
drop(_1) -> [return: bb18, unwind: bb36];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb18: {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bb19 (cleanup): {
|
||||||
|
drop(_10) -> [return: bb20, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb20 (cleanup): {
|
||||||
|
drop(_11) -> [return: bb36, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb21 (cleanup): {
|
||||||
|
drop(_10) -> [return: bb22, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb22 (cleanup): {
|
||||||
|
drop(_11) -> [return: bb35, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb23 (cleanup): {
|
||||||
|
drop(_10) -> [return: bb24, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb24 (cleanup): {
|
||||||
|
drop(_11) -> [return: bb34, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb25 (cleanup): {
|
||||||
|
drop(_10) -> [return: bb26, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb26 (cleanup): {
|
||||||
|
drop(_11) -> [return: bb33, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb27 (cleanup): {
|
||||||
|
drop(_10) -> [return: bb28, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb28 (cleanup): {
|
||||||
|
drop(_11) -> [return: bb32, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb29 (cleanup): {
|
||||||
|
drop(_10) -> [return: bb31, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb30 (cleanup): {
|
||||||
|
drop(_9) -> [return: bb31, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb31 (cleanup): {
|
||||||
|
drop(_7) -> [return: bb32, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb32 (cleanup): {
|
||||||
|
drop(_6) -> [return: bb33, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb33 (cleanup): {
|
||||||
|
drop(_4) -> [return: bb34, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb34 (cleanup): {
|
||||||
|
drop(_2) -> [return: bb35, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb35 (cleanup): {
|
||||||
|
drop(_1) -> [return: bb36, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb36 (cleanup): {
|
||||||
|
resume;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,202 @@
|
||||||
|
// MIR for `f_with_arg` after built
|
||||||
|
|
||||||
|
fn f_with_arg(_1: String, _2: String) -> () {
|
||||||
|
debug _arg1 => _1;
|
||||||
|
debug _arg2 => _2;
|
||||||
|
let mut _0: ();
|
||||||
|
let mut _3: !;
|
||||||
|
let _4: std::string::String;
|
||||||
|
let _8: ();
|
||||||
|
let mut _9: std::string::String;
|
||||||
|
let mut _10: std::string::String;
|
||||||
|
let mut _11: std::string::String;
|
||||||
|
scope 1 {
|
||||||
|
debug _a => _4;
|
||||||
|
let _5: i32;
|
||||||
|
scope 2 {
|
||||||
|
debug _b => _5;
|
||||||
|
let _6: std::string::String;
|
||||||
|
scope 3 {
|
||||||
|
debug _c => _6;
|
||||||
|
let _7: std::string::String;
|
||||||
|
scope 4 {
|
||||||
|
debug _d => _7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bb0: {
|
||||||
|
StorageLive(_4);
|
||||||
|
_4 = String::new() -> [return: bb1, unwind: bb34];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb1: {
|
||||||
|
FakeRead(ForLet(None), _4);
|
||||||
|
StorageLive(_5);
|
||||||
|
_5 = const 12_i32;
|
||||||
|
FakeRead(ForLet(None), _5);
|
||||||
|
StorageLive(_6);
|
||||||
|
_6 = String::new() -> [return: bb2, unwind: bb33];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb2: {
|
||||||
|
FakeRead(ForLet(None), _6);
|
||||||
|
StorageLive(_7);
|
||||||
|
_7 = String::new() -> [return: bb3, unwind: bb32];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb3: {
|
||||||
|
FakeRead(ForLet(None), _7);
|
||||||
|
StorageLive(_8);
|
||||||
|
StorageLive(_9);
|
||||||
|
_9 = move _6;
|
||||||
|
_8 = std::mem::drop::<String>(move _9) -> [return: bb4, unwind: bb30];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb4: {
|
||||||
|
StorageDead(_9);
|
||||||
|
StorageDead(_8);
|
||||||
|
StorageLive(_10);
|
||||||
|
_10 = String::new() -> [return: bb5, unwind: bb31];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb5: {
|
||||||
|
StorageLive(_11);
|
||||||
|
_11 = String::new() -> [return: bb6, unwind: bb29];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb6: {
|
||||||
|
drop(_7) -> [return: bb7, unwind: bb27];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb7: {
|
||||||
|
StorageDead(_7);
|
||||||
|
drop(_6) -> [return: bb8, unwind: bb25];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb8: {
|
||||||
|
StorageDead(_6);
|
||||||
|
StorageDead(_5);
|
||||||
|
drop(_4) -> [return: bb9, unwind: bb23];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb9: {
|
||||||
|
StorageDead(_4);
|
||||||
|
drop(_2) -> [return: bb10, unwind: bb21];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb10: {
|
||||||
|
drop(_1) -> [return: bb11, unwind: bb19];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb11: {
|
||||||
|
tailcall g_with_arg(Spanned { node: move _10, span: $DIR/tail_call_drops.rs:36:23: 36:36 (#0) }, Spanned { node: move _11, span: $DIR/tail_call_drops.rs:36:38: 36:51 (#0) });
|
||||||
|
}
|
||||||
|
|
||||||
|
bb12: {
|
||||||
|
StorageDead(_11);
|
||||||
|
StorageDead(_10);
|
||||||
|
drop(_7) -> [return: bb13, unwind: bb32];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb13: {
|
||||||
|
StorageDead(_7);
|
||||||
|
drop(_6) -> [return: bb14, unwind: bb33];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb14: {
|
||||||
|
StorageDead(_6);
|
||||||
|
StorageDead(_5);
|
||||||
|
drop(_4) -> [return: bb15, unwind: bb34];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb15: {
|
||||||
|
StorageDead(_4);
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
bb16: {
|
||||||
|
drop(_2) -> [return: bb17, unwind: bb35];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb17: {
|
||||||
|
drop(_1) -> [return: bb18, unwind: bb36];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb18: {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bb19 (cleanup): {
|
||||||
|
drop(_10) -> [return: bb20, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb20 (cleanup): {
|
||||||
|
drop(_11) -> [return: bb36, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb21 (cleanup): {
|
||||||
|
drop(_10) -> [return: bb22, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb22 (cleanup): {
|
||||||
|
drop(_11) -> [return: bb35, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb23 (cleanup): {
|
||||||
|
drop(_10) -> [return: bb24, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb24 (cleanup): {
|
||||||
|
drop(_11) -> [return: bb34, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb25 (cleanup): {
|
||||||
|
drop(_10) -> [return: bb26, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb26 (cleanup): {
|
||||||
|
drop(_11) -> [return: bb33, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb27 (cleanup): {
|
||||||
|
drop(_10) -> [return: bb28, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb28 (cleanup): {
|
||||||
|
drop(_11) -> [return: bb32, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb29 (cleanup): {
|
||||||
|
drop(_10) -> [return: bb31, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb30 (cleanup): {
|
||||||
|
drop(_9) -> [return: bb31, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb31 (cleanup): {
|
||||||
|
drop(_7) -> [return: bb32, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb32 (cleanup): {
|
||||||
|
drop(_6) -> [return: bb33, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb33 (cleanup): {
|
||||||
|
drop(_4) -> [return: bb34, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb34 (cleanup): {
|
||||||
|
drop(_2) -> [return: bb35, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb35 (cleanup): {
|
||||||
|
drop(_1) -> [return: bb36, unwind terminate(cleanup)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bb36 (cleanup): {
|
||||||
|
resume;
|
||||||
|
}
|
||||||
|
}
|
41
tests/mir-opt/tail_call_drops.rs
Normal file
41
tests/mir-opt/tail_call_drops.rs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
// skip-filecheck
|
||||||
|
// EMIT_MIR_FOR_EACH_PANIC_STRATEGY
|
||||||
|
#![allow(incomplete_features)]
|
||||||
|
#![feature(explicit_tail_calls)]
|
||||||
|
|
||||||
|
// EMIT_MIR tail_call_drops.f.built.after.mir
|
||||||
|
// Expected result:
|
||||||
|
// drop(_d) -> drop(_c) -> drop(_a) -> tailcall g()
|
||||||
|
//
|
||||||
|
// EMIT_MIR tail_call_drops.f.ElaborateDrops.diff
|
||||||
|
// Expected result:
|
||||||
|
// drop(_d) -> drop(_a) -> tailcall g()
|
||||||
|
fn f() {
|
||||||
|
let _a = String::new();
|
||||||
|
let _b = 12;
|
||||||
|
let _c = String::new();
|
||||||
|
let _d = String::new();
|
||||||
|
|
||||||
|
drop(_c);
|
||||||
|
|
||||||
|
become g();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn g() {}
|
||||||
|
|
||||||
|
// EMIT_MIR tail_call_drops.f_with_arg.built.after.mir
|
||||||
|
// EMIT_MIR tail_call_drops.f_with_arg.ElaborateDrops.diff
|
||||||
|
fn f_with_arg(_arg1: String, _arg2: String) {
|
||||||
|
let _a = String::new();
|
||||||
|
let _b = 12;
|
||||||
|
let _c = String::new();
|
||||||
|
let _d = String::new();
|
||||||
|
|
||||||
|
drop(_c);
|
||||||
|
|
||||||
|
become g_with_arg(String::new(), String::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn g_with_arg(_arg1: String, _arg2: String) {}
|
||||||
|
|
||||||
|
fn main() {}
|
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`.
|
14
tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.rs
Normal file
14
tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.rs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
#![allow(incomplete_features)]
|
||||||
|
#![feature(explicit_tail_calls)]
|
||||||
|
|
||||||
|
pub const fn test(_: &Type) {
|
||||||
|
const fn takes_borrow(_: &Type) {}
|
||||||
|
|
||||||
|
let local = Type;
|
||||||
|
become takes_borrow(&local);
|
||||||
|
//~^ error: `local` does not live long enough
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Type;
|
||||||
|
|
||||||
|
fn main() {}
|
14
tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.stderr
Normal file
14
tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.stderr
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
error[E0597]: `local` does not live long enough
|
||||||
|
--> $DIR/ctfe-arg-bad-borrow.rs:8:25
|
||||||
|
|
|
||||||
|
LL | let local = Type;
|
||||||
|
| ----- binding `local` declared here
|
||||||
|
LL | become takes_borrow(&local);
|
||||||
|
| ^^^^^^ borrowed value does not live long enough
|
||||||
|
LL |
|
||||||
|
LL | }
|
||||||
|
| - `local` dropped here while still borrowed
|
||||||
|
|
||||||
|
error: aborting due to 1 previous error
|
||||||
|
|
||||||
|
For more information about this error, try `rustc --explain E0597`.
|
13
tests/ui/explicit-tail-calls/ctfe-arg-good-borrow.rs
Normal file
13
tests/ui/explicit-tail-calls/ctfe-arg-good-borrow.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
//@ check-pass
|
||||||
|
#![allow(incomplete_features)]
|
||||||
|
#![feature(explicit_tail_calls)]
|
||||||
|
|
||||||
|
pub const fn test(x: &Type) {
|
||||||
|
const fn takes_borrow(_: &Type) {}
|
||||||
|
|
||||||
|
become takes_borrow(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Type;
|
||||||
|
|
||||||
|
fn main() {}
|
15
tests/ui/explicit-tail-calls/ctfe-arg-move.rs
Normal file
15
tests/ui/explicit-tail-calls/ctfe-arg-move.rs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
//@ check-pass
|
||||||
|
#![allow(incomplete_features)]
|
||||||
|
#![feature(explicit_tail_calls)]
|
||||||
|
|
||||||
|
pub const fn test(s: String) -> String {
|
||||||
|
const fn takes_string(s: String) -> String {
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
become takes_string(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Type;
|
||||||
|
|
||||||
|
fn main() {}
|
43
tests/ui/explicit-tail-calls/ctfe-collatz-multi-rec.rs
Normal file
43
tests/ui/explicit-tail-calls/ctfe-collatz-multi-rec.rs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
//@ run-pass
|
||||||
|
#![allow(incomplete_features)]
|
||||||
|
#![feature(explicit_tail_calls)]
|
||||||
|
|
||||||
|
/// A very unnecessarily complicated "implementation" of the Collatz conjecture.
|
||||||
|
/// Returns the number of steps to reach `1`.
|
||||||
|
///
|
||||||
|
/// This is just a test for tail calls, which involves multiple functions calling each other.
|
||||||
|
///
|
||||||
|
/// Panics if `x == 0`.
|
||||||
|
const fn collatz(x: u32) -> u32 {
|
||||||
|
assert!(x > 0);
|
||||||
|
|
||||||
|
const fn switch(x: u32, steps: u32) -> u32 {
|
||||||
|
match x {
|
||||||
|
1 => steps,
|
||||||
|
_ if x & 1 == 0 => become div2(x, steps + 1),
|
||||||
|
_ => become mul3plus1(x, steps + 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn div2(x: u32, steps: u32) -> u32 {
|
||||||
|
become switch(x >> 1, steps)
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn mul3plus1(x: u32, steps: u32) -> u32 {
|
||||||
|
become switch(3 * x + 1, steps)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(x, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ASSERTS: () = {
|
||||||
|
assert!(collatz(1) == 0);
|
||||||
|
assert!(collatz(2) == 1);
|
||||||
|
assert!(collatz(3) == 7);
|
||||||
|
assert!(collatz(4) == 2);
|
||||||
|
assert!(collatz(6171) == 261);
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let _ = ASSERTS;
|
||||||
|
}
|
36
tests/ui/explicit-tail-calls/ctfe-id-unlimited.return.stderr
Normal file
36
tests/ui/explicit-tail-calls/ctfe-id-unlimited.return.stderr
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
error[E0080]: evaluation of constant value failed
|
||||||
|
--> $DIR/ctfe-id-unlimited.rs:17:42
|
||||||
|
|
|
||||||
|
LL | #[cfg(r#return)] _ => return inner(acc + 1, n - 1),
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^ reached the configured maximum number of stack frames
|
||||||
|
|
|
||||||
|
note: inside `inner`
|
||||||
|
--> $DIR/ctfe-id-unlimited.rs:17:42
|
||||||
|
|
|
||||||
|
LL | #[cfg(r#return)] _ => return inner(acc + 1, n - 1),
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
note: [... 125 additional calls inside `inner` ...]
|
||||||
|
--> $DIR/ctfe-id-unlimited.rs:17:42
|
||||||
|
|
|
||||||
|
LL | #[cfg(r#return)] _ => return inner(acc + 1, n - 1),
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
note: inside `rec_id`
|
||||||
|
--> $DIR/ctfe-id-unlimited.rs:22:5
|
||||||
|
|
|
||||||
|
LL | inner(0, n)
|
||||||
|
| ^^^^^^^^^^^
|
||||||
|
note: inside `ID_ED`
|
||||||
|
--> $DIR/ctfe-id-unlimited.rs:29:20
|
||||||
|
|
|
||||||
|
LL | const ID_ED: u32 = rec_id(ORIGINAL);
|
||||||
|
| ^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
note: erroneous constant encountered
|
||||||
|
--> $DIR/ctfe-id-unlimited.rs:31:40
|
||||||
|
|
|
||||||
|
LL | const ASSERT: () = assert!(ORIGINAL == ID_ED);
|
||||||
|
| ^^^^^
|
||||||
|
|
||||||
|
error: aborting due to 1 previous error
|
||||||
|
|
||||||
|
For more information about this error, try `rustc --explain E0080`.
|
35
tests/ui/explicit-tail-calls/ctfe-id-unlimited.rs
Normal file
35
tests/ui/explicit-tail-calls/ctfe-id-unlimited.rs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
//@ revisions: become return
|
||||||
|
//@ [become] run-pass
|
||||||
|
#![allow(incomplete_features)]
|
||||||
|
#![feature(explicit_tail_calls)]
|
||||||
|
|
||||||
|
// This is an identity function (`|x| x`), but implemented using recursion.
|
||||||
|
// Each step we increment accumulator and decrement the number.
|
||||||
|
//
|
||||||
|
// With normal calls this fails compilation because of the recursion limit,
|
||||||
|
// but with tail calls/`become` we don't grow the stack/spend recursion limit
|
||||||
|
// so this should compile.
|
||||||
|
const fn rec_id(n: u32) -> u32 {
|
||||||
|
const fn inner(acc: u32, n: u32) -> u32 {
|
||||||
|
match n {
|
||||||
|
0 => acc,
|
||||||
|
#[cfg(r#become)] _ => become inner(acc + 1, n - 1),
|
||||||
|
#[cfg(r#return)] _ => return inner(acc + 1, n - 1),
|
||||||
|
//[return]~^ error: evaluation of constant value failed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner(0, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some relatively big number that is higher than recursion limit
|
||||||
|
const ORIGINAL: u32 = 12345;
|
||||||
|
// Original number, but with identity function applied
|
||||||
|
// (this is the same, but requires execution of the recursion)
|
||||||
|
const ID_ED: u32 = rec_id(ORIGINAL);
|
||||||
|
// Assert to make absolutely sure the computation actually happens
|
||||||
|
const ASSERT: () = assert!(ORIGINAL == ID_ED);
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let _ = ASSERT;
|
||||||
|
}
|
19
tests/ui/explicit-tail-calls/ctfe-tail-call-panic.rs
Normal file
19
tests/ui/explicit-tail-calls/ctfe-tail-call-panic.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
#![allow(incomplete_features)]
|
||||||
|
#![feature(explicit_tail_calls)]
|
||||||
|
|
||||||
|
pub const fn f() {
|
||||||
|
become g();
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn g() {
|
||||||
|
panic!()
|
||||||
|
//~^ error: evaluation of constant value failed
|
||||||
|
//~| note: in this expansion of panic!
|
||||||
|
//~| note: inside `g`
|
||||||
|
//~| note: in this expansion of panic!
|
||||||
|
}
|
||||||
|
|
||||||
|
const _: () = f();
|
||||||
|
//~^ note: inside `_`
|
||||||
|
|
||||||
|
fn main() {}
|
21
tests/ui/explicit-tail-calls/ctfe-tail-call-panic.stderr
Normal file
21
tests/ui/explicit-tail-calls/ctfe-tail-call-panic.stderr
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
error[E0080]: evaluation of constant value failed
|
||||||
|
--> $DIR/ctfe-tail-call-panic.rs:9:5
|
||||||
|
|
|
||||||
|
LL | panic!()
|
||||||
|
| ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/ctfe-tail-call-panic.rs:9:5
|
||||||
|
|
|
||||||
|
note: inside `g`
|
||||||
|
--> $DIR/ctfe-tail-call-panic.rs:9:5
|
||||||
|
|
|
||||||
|
LL | panic!()
|
||||||
|
| ^^^^^^^^
|
||||||
|
note: inside `_`
|
||||||
|
--> $DIR/ctfe-tail-call-panic.rs:16:15
|
||||||
|
|
|
||||||
|
LL | const _: () = f();
|
||||||
|
| ^^^
|
||||||
|
= note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
|
||||||
|
error: aborting due to 1 previous error
|
||||||
|
|
||||||
|
For more information about this error, try `rustc --explain E0080`.
|
70
tests/ui/explicit-tail-calls/drop-order.rs
Normal file
70
tests/ui/explicit-tail-calls/drop-order.rs
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
// FIXME(explicit_tail_calls): enable this test once rustc_codegen_ssa supports tail calls
|
||||||
|
//@ ignore-test: tail calls are not implemented in rustc_codegen_ssa yet, so this causes 🧊
|
||||||
|
//@ run-pass
|
||||||
|
#![allow(incomplete_features)]
|
||||||
|
#![feature(explicit_tail_calls)]
|
||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let tail_counter = Default::default();
|
||||||
|
tail_recursive(0, &tail_counter);
|
||||||
|
assert_eq!(tail_counter.into_inner(), (0..128).collect::<Vec<u8>>());
|
||||||
|
|
||||||
|
let simply_counter = Default::default();
|
||||||
|
simply_recursive(0, &simply_counter);
|
||||||
|
assert_eq!(simply_counter.into_inner(), (0..128).rev().collect::<Vec<u8>>());
|
||||||
|
|
||||||
|
let scope_counter = Default::default();
|
||||||
|
out_of_inner_scope(&scope_counter);
|
||||||
|
assert_eq!(scope_counter.into_inner(), (0..8).collect::<Vec<u8>>());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tail_recursive(n: u8, order: &RefCell<Vec<u8>>) {
|
||||||
|
if n >= 128 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _local = DropCounter(n, order);
|
||||||
|
|
||||||
|
become tail_recursive(n + 1, order)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn simply_recursive(n: u8, order: &RefCell<Vec<u8>>) {
|
||||||
|
if n >= 128 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _local = DropCounter(n, order);
|
||||||
|
|
||||||
|
return simply_recursive(n + 1, order);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn out_of_inner_scope(order: &RefCell<Vec<u8>>) {
|
||||||
|
fn inner(order: &RefCell<Vec<u8>>) {
|
||||||
|
let _7 = DropCounter(7, order);
|
||||||
|
let _6 = DropCounter(6, order);
|
||||||
|
}
|
||||||
|
|
||||||
|
let _5 = DropCounter(5, order);
|
||||||
|
let _4 = DropCounter(4, order);
|
||||||
|
|
||||||
|
if true {
|
||||||
|
let _3 = DropCounter(3, order);
|
||||||
|
let _2 = DropCounter(2, order);
|
||||||
|
loop {
|
||||||
|
let _1 = DropCounter(1, order);
|
||||||
|
let _0 = DropCounter(0, order);
|
||||||
|
|
||||||
|
become inner(order);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DropCounter<'a>(u8, &'a RefCell<Vec<u8>>);
|
||||||
|
|
||||||
|
impl Drop for DropCounter<'_> {
|
||||||
|
#[track_caller]
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.1.borrow_mut().push(self.0);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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`.
|
||||||
|
|
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`.
|
24
tests/ui/lint/lint-unconditional-recursion-tail-calls.rs
Normal file
24
tests/ui/lint/lint-unconditional-recursion-tail-calls.rs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
#![allow(incomplete_features, dead_code)]
|
||||||
|
#![deny(unconditional_recursion)] //~ note: the lint level is defined here
|
||||||
|
#![feature(explicit_tail_calls)]
|
||||||
|
|
||||||
|
fn f(x: bool) {
|
||||||
|
//~^ error: function cannot return without recursing
|
||||||
|
//~| note: cannot return without recursing
|
||||||
|
if x {
|
||||||
|
become f(!x)
|
||||||
|
} else {
|
||||||
|
f(!x) //~ note: recursive call site
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should *not* lint, tail-recursive functions which never return is a reasonable thing
|
||||||
|
fn g(x: bool) {
|
||||||
|
if x {
|
||||||
|
become g(!x)
|
||||||
|
} else {
|
||||||
|
become g(!x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
18
tests/ui/lint/lint-unconditional-recursion-tail-calls.stderr
Normal file
18
tests/ui/lint/lint-unconditional-recursion-tail-calls.stderr
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
error: function cannot return without recursing
|
||||||
|
--> $DIR/lint-unconditional-recursion-tail-calls.rs:5:1
|
||||||
|
|
|
||||||
|
LL | fn f(x: bool) {
|
||||||
|
| ^^^^^^^^^^^^^ cannot return without recursing
|
||||||
|
...
|
||||||
|
LL | f(!x)
|
||||||
|
| ----- recursive call site
|
||||||
|
|
|
||||||
|
= help: a `loop` may express intention better if this is on purpose
|
||||||
|
note: the lint level is defined here
|
||||||
|
--> $DIR/lint-unconditional-recursion-tail-calls.rs:2:9
|
||||||
|
|
|
||||||
|
LL | #![deny(unconditional_recursion)]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: aborting due to 1 previous error
|
||||||
|
|
|
@ -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| {
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue