From 484152d562f6babaacb3fae08cc5f70ee550e9ee Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Thu, 15 Feb 2024 19:54:37 +0000 Subject: [PATCH 01/15] Support tail calls in mir via `TerminatorKind::TailCall` --- compiler/rustc_borrowck/src/lib.rs | 9 ++- .../src/polonius/loan_invalidations.rs | 6 ++ compiler/rustc_borrowck/src/type_check/mod.rs | 20 ++++- compiler/rustc_codegen_cranelift/src/base.rs | 5 ++ .../rustc_codegen_cranelift/src/constant.rs | 1 + compiler/rustc_codegen_ssa/src/mir/analyze.rs | 1 + compiler/rustc_codegen_ssa/src/mir/block.rs | 7 ++ .../src/check_consts/check.rs | 15 +++- .../src/check_consts/post_drop_elaboration.rs | 1 + .../src/interpret/terminator.rs | 2 + compiler/rustc_middle/src/mir/pretty.rs | 17 +++- compiler/rustc_middle/src/mir/syntax.rs | 31 ++++++++ compiler/rustc_middle/src/mir/terminator.rs | 13 +++- compiler/rustc_middle/src/mir/visit.rs | 11 +++ .../rustc_mir_build/src/build/expr/stmt.rs | 35 ++++++++- compiler/rustc_mir_build/src/build/scope.rs | 1 + compiler/rustc_mir_build/src/lints.rs | 2 + .../src/impls/borrowed_locals.rs | 1 + .../src/impls/storage_liveness.rs | 2 + .../src/move_paths/builder.rs | 6 ++ .../rustc_mir_dataflow/src/value_analysis.rs | 3 + compiler/rustc_mir_transform/src/coroutine.rs | 5 ++ .../rustc_mir_transform/src/coverage/graph.rs | 9 ++- .../src/coverage/spans/from_mir.rs | 3 +- compiler/rustc_mir_transform/src/dest_prop.rs | 6 ++ compiler/rustc_mir_transform/src/inline.rs | 9 +++ .../rustc_mir_transform/src/jump_threading.rs | 1 + .../src/known_panics_lint.rs | 1 + .../src/mentioned_items.rs | 2 +- .../src/remove_noop_landing_pads.rs | 1 + compiler/rustc_mir_transform/src/validate.rs | 77 +++++++++++-------- compiler/rustc_monomorphize/src/collector.rs | 3 +- .../rustc_smir/src/rustc_smir/convert/mir.rs | 1 + tests/ui/explicit-tail-calls/constck.rs | 22 ++++++ tests/ui/explicit-tail-calls/constck.stderr | 19 +++++ .../explicit-tail-calls/return-mismatches.rs | 2 +- .../return-mismatches.stderr | 13 +--- tests/ui/explicit-tail-calls/unsafeck.rs | 11 +++ tests/ui/explicit-tail-calls/unsafeck.stderr | 11 +++ tests/ui/parser/bad-let-else-statement.rs | 16 ++-- tests/ui/parser/bad-let-else-statement.stderr | 15 +--- 41 files changed, 328 insertions(+), 88 deletions(-) create mode 100644 tests/ui/explicit-tail-calls/constck.rs create mode 100644 tests/ui/explicit-tail-calls/constck.stderr create mode 100644 tests/ui/explicit-tail-calls/unsafeck.rs create mode 100644 tests/ui/explicit-tail-calls/unsafeck.stderr diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs index e6f8ffd428d06..df532c739509e 100644 --- a/compiler/rustc_borrowck/src/lib.rs +++ b/compiler/rustc_borrowck/src/lib.rs @@ -728,6 +728,12 @@ impl<'a, 'mir, 'tcx, R> rustc_mir_dataflow::ResultsVisitor<'mir, 'tcx, R> } self.mutate_place(loc, (*destination, span), Deep, flow_state); } + TerminatorKind::TailCall { func, args, fn_span: _ } => { + self.consume_operand(loc, (func, span), flow_state); + for arg in args { + self.consume_operand(loc, (&arg.node, arg.span), flow_state); + } + } TerminatorKind::Assert { cond, expected: _, msg, target: _, unwind: _ } => { self.consume_operand(loc, (cond, span), flow_state); if let AssertKind::BoundsCheck { len, index } = &**msg { @@ -814,9 +820,8 @@ impl<'a, 'mir, 'tcx, R> rustc_mir_dataflow::ResultsVisitor<'mir, 'tcx, R> TerminatorKind::UnwindResume | TerminatorKind::Return + | TerminatorKind::TailCall { .. } | TerminatorKind::CoroutineDrop => { - // Returning from the function implicitly kills storage for all locals and statics. - // Often, the storage will already have been killed by an explicit // StorageDead, but we don't always emit those (notably on unwind paths), // so this "extra check" serves as a kind of backup. let borrow_set = self.borrow_set.clone(); diff --git a/compiler/rustc_borrowck/src/polonius/loan_invalidations.rs b/compiler/rustc_borrowck/src/polonius/loan_invalidations.rs index 6979910a02d73..30dfc4c21b06a 100644 --- a/compiler/rustc_borrowck/src/polonius/loan_invalidations.rs +++ b/compiler/rustc_borrowck/src/polonius/loan_invalidations.rs @@ -125,6 +125,12 @@ impl<'cx, 'tcx> Visitor<'tcx> for LoanInvalidationsGenerator<'cx, 'tcx> { } self.mutate_place(location, *destination, Deep); } + TerminatorKind::TailCall { func, args, .. } => { + self.consume_operand(location, func); + for arg in args { + self.consume_operand(location, &arg.node); + } + } TerminatorKind::Assert { cond, expected: _, msg, target: _, unwind: _ } => { self.consume_operand(location, cond); use rustc_middle::mir::AssertKind; diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index aa25e3adf28af..8bba7ef425520 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -1352,7 +1352,14 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { } // FIXME: check the values } - TerminatorKind::Call { func, args, destination, call_source, target, .. } => { + TerminatorKind::Call { func, args, .. } + | TerminatorKind::TailCall { func, args, .. } => { + let call_source = match term.kind { + TerminatorKind::Call { call_source, .. } => call_source, + TerminatorKind::TailCall { .. } => CallSource::Normal, + _ => unreachable!(), + }; + self.check_operand(func, term_location); for arg in args { self.check_operand(&arg.node, term_location); @@ -1425,7 +1432,9 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { ); } - self.check_call_dest(body, term, &sig, *destination, *target, term_location); + if let TerminatorKind::Call { destination, target, .. } = term.kind { + self.check_call_dest(body, term, &sig, destination, target, term_location); + } // The ordinary liveness rules will ensure that all // regions in the type of the callee are live here. We @@ -1443,7 +1452,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { .add_location(region_vid, term_location); } - self.check_call_inputs(body, term, func, &sig, args, term_location, *call_source); + self.check_call_inputs(body, term, func, &sig, args, term_location, call_source); } TerminatorKind::Assert { cond, msg, .. } => { self.check_operand(cond, term_location); @@ -1675,6 +1684,11 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { span_mirbug!(self, block_data, "return on cleanup block") } } + TerminatorKind::TailCall { .. } => { + if is_cleanup { + span_mirbug!(self, block_data, "tailcall on cleanup block") + } + } TerminatorKind::CoroutineDrop { .. } => { if is_cleanup { span_mirbug!(self, block_data, "coroutine_drop in cleanup block") diff --git a/compiler/rustc_codegen_cranelift/src/base.rs b/compiler/rustc_codegen_cranelift/src/base.rs index c5b4277015a9e..5adbbb09ac85f 100644 --- a/compiler/rustc_codegen_cranelift/src/base.rs +++ b/compiler/rustc_codegen_cranelift/src/base.rs @@ -491,6 +491,11 @@ fn codegen_fn_body(fx: &mut FunctionCx<'_, '_, '_>, start_block: Block) { ) }); } + // FIXME(explicit_tail_calls): add support for tail calls to the cranelift backend, once cranelift supports tail calls + TerminatorKind::TailCall { fn_span, .. } => span_bug!( + *fn_span, + "tail calls are not yet supported in `rustc_codegen_cranelift` backend" + ), TerminatorKind::InlineAsm { template, operands, diff --git a/compiler/rustc_codegen_cranelift/src/constant.rs b/compiler/rustc_codegen_cranelift/src/constant.rs index 87c5da3b7c3ed..fc12f0ff738ba 100644 --- a/compiler/rustc_codegen_cranelift/src/constant.rs +++ b/compiler/rustc_codegen_cranelift/src/constant.rs @@ -565,6 +565,7 @@ pub(crate) fn mir_operand_get_const_val<'tcx>( { return None; } + TerminatorKind::TailCall { .. } => return None, TerminatorKind::Call { .. } => {} } } diff --git a/compiler/rustc_codegen_ssa/src/mir/analyze.rs b/compiler/rustc_codegen_ssa/src/mir/analyze.rs index 0577ba32ffdcc..ac2b6ca4e9520 100644 --- a/compiler/rustc_codegen_ssa/src/mir/analyze.rs +++ b/compiler/rustc_codegen_ssa/src/mir/analyze.rs @@ -281,6 +281,7 @@ pub fn cleanup_kinds(mir: &mir::Body<'_>) -> IndexVec> FunctionCx<'a, 'tcx, Bx> { fn_span, mergeable_succ(), ), + mir::TerminatorKind::TailCall { .. } => { + // FIXME(explicit_tail_calls): implement tail calls in ssa backend + span_bug!( + terminator.source_info.span, + "`TailCall` terminator is not yet supported by `rustc_codegen_ssa`" + ) + } mir::TerminatorKind::CoroutineDrop | mir::TerminatorKind::Yield { .. } => { bug!("coroutine ops in codegen") } diff --git a/compiler/rustc_const_eval/src/check_consts/check.rs b/compiler/rustc_const_eval/src/check_consts/check.rs index 0d8a17775dd3d..412effba32dc6 100644 --- a/compiler/rustc_const_eval/src/check_consts/check.rs +++ b/compiler/rustc_const_eval/src/check_consts/check.rs @@ -135,6 +135,8 @@ impl<'mir, 'tcx> Qualifs<'mir, 'tcx> { ccx: &'mir ConstCx<'mir, 'tcx>, tainted_by_errors: Option, ) -> ConstQualifs { + // FIXME(explicit_tail_calls): uhhhh I think we can return without return now, does it change anything + // Find the `Return` terminator if one exists. // // If no `Return` terminator exists, this MIR is divergent. Just return the conservative @@ -711,7 +713,14 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { self.super_terminator(terminator, location); match &terminator.kind { - TerminatorKind::Call { func, args, fn_span, call_source, .. } => { + TerminatorKind::Call { func, args, fn_span, .. } + | TerminatorKind::TailCall { func, args, fn_span, .. } => { + let call_source = match terminator.kind { + TerminatorKind::Call { call_source, .. } => call_source, + TerminatorKind::TailCall { .. } => CallSource::Normal, + _ => unreachable!(), + }; + let ConstCx { tcx, body, param_env, .. } = *self.ccx; let caller = self.def_id(); @@ -783,7 +792,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { callee, args: fn_args, span: *fn_span, - call_source: *call_source, + call_source, feature: Some(if tcx.features().const_trait_impl { sym::effects } else { @@ -830,7 +839,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { callee, args: fn_args, span: *fn_span, - call_source: *call_source, + call_source, feature: None, }); return; diff --git a/compiler/rustc_const_eval/src/check_consts/post_drop_elaboration.rs b/compiler/rustc_const_eval/src/check_consts/post_drop_elaboration.rs index f0763f1e490ff..f5e745454ab84 100644 --- a/compiler/rustc_const_eval/src/check_consts/post_drop_elaboration.rs +++ b/compiler/rustc_const_eval/src/check_consts/post_drop_elaboration.rs @@ -108,6 +108,7 @@ impl<'tcx> Visitor<'tcx> for CheckLiveDrops<'_, 'tcx> { mir::TerminatorKind::UnwindTerminate(_) | mir::TerminatorKind::Call { .. } + | mir::TerminatorKind::TailCall { .. } | mir::TerminatorKind::Assert { .. } | mir::TerminatorKind::FalseEdge { .. } | mir::TerminatorKind::FalseUnwind { .. } diff --git a/compiler/rustc_const_eval/src/interpret/terminator.rs b/compiler/rustc_const_eval/src/interpret/terminator.rs index 68acddf63d86e..7d1a48d6ded6f 100644 --- a/compiler/rustc_const_eval/src/interpret/terminator.rs +++ b/compiler/rustc_const_eval/src/interpret/terminator.rs @@ -172,6 +172,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } } + TailCall { func: _, args: _, fn_span: _ } => todo!(), + Drop { place, target, unwind, replace: _ } => { let place = self.eval_place(place)?; let instance = Instance::resolve_drop_in_place(*self.tcx, place.layout.ty); diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs index 4657f4dcf8132..23251d7b7bf61 100644 --- a/compiler/rustc_middle/src/mir/pretty.rs +++ b/compiler/rustc_middle/src/mir/pretty.rs @@ -845,6 +845,16 @@ impl<'tcx> TerminatorKind<'tcx> { } write!(fmt, ")") } + TailCall { func, args, .. } => { + write!(fmt, "tailcall {func:?}(")?; + for (index, arg) in args.iter().enumerate() { + if index > 0 { + write!(fmt, ", ")?; + } + write!(fmt, "{:?}", arg)?; + } + write!(fmt, ")") + } Assert { cond, expected, msg, .. } => { write!(fmt, "assert(")?; if !expected { @@ -912,7 +922,12 @@ impl<'tcx> TerminatorKind<'tcx> { pub fn fmt_successor_labels(&self) -> Vec> { use self::TerminatorKind::*; match *self { - Return | UnwindResume | UnwindTerminate(_) | Unreachable | CoroutineDrop => vec![], + Return + | TailCall { .. } + | UnwindResume + | UnwindTerminate(_) + | Unreachable + | CoroutineDrop => vec![], Goto { .. } => vec!["".into()], SwitchInt { ref targets, .. } => targets .values diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index 2c2884f189743..deaa1259f6b73 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -744,6 +744,36 @@ pub enum TerminatorKind<'tcx> { fn_span: Span, }, + /// Tail call. + /// + /// Roughly speaking this is a chimera of [`Call`] and [`Return`], with some caveats. + /// Semantically tail calls consists of two actions: + /// - pop of the current stack frame + /// - a call to the `func`, with the return address of the **current** caller + /// - so that a `return` inside `func` returns to the caller of the caller + /// of the function that is currently being executed + /// + /// Note that in difference with [`Call`] this is missing + /// - `destination` (because it's always the return place) + /// - `target` (because it's always taken from the current stack frame) + /// - `unwind` (because it's always taken from the current stack frame) + /// + /// [`Call`]: TerminatorKind::Call + /// [`Return`]: TerminatorKind::Return + TailCall { + /// The function that’s being called. + func: Operand<'tcx>, + /// Arguments the function is called with. + /// These are owned by the callee, which is free to modify them. + /// This allows the memory occupied by "by-value" arguments to be + /// reused across function calls without duplicating the contents. + args: Vec>>, + // FIXME(explicit_tail_calls): should we have the span for `become`? is this span accurate? do we need it? + /// This `Span` is the span of the function, without the dot and receiver + /// (e.g. `foo(a, b)` in `x.foo(a, b)` + fn_span: Span, + }, + /// Evaluates the operand, which must have type `bool`. If it is not equal to `expected`, /// initiates a panic. Initiating a panic corresponds to a `Call` terminator with some /// unspecified constant as the function to call, all the operands stored in the `AssertMessage` @@ -870,6 +900,7 @@ impl TerminatorKind<'_> { TerminatorKind::Unreachable => "Unreachable", TerminatorKind::Drop { .. } => "Drop", TerminatorKind::Call { .. } => "Call", + TerminatorKind::TailCall { .. } => "TailCall", TerminatorKind::Assert { .. } => "Assert", TerminatorKind::Yield { .. } => "Yield", TerminatorKind::CoroutineDrop => "CoroutineDrop", diff --git a/compiler/rustc_middle/src/mir/terminator.rs b/compiler/rustc_middle/src/mir/terminator.rs index ed592612358be..5b035d9579d76 100644 --- a/compiler/rustc_middle/src/mir/terminator.rs +++ b/compiler/rustc_middle/src/mir/terminator.rs @@ -439,6 +439,7 @@ mod helper { | CoroutineDrop | Return | Unreachable + | TailCall { .. } | Call { target: None, unwind: _, .. } => (&[]).into_iter().copied().chain(None), InlineAsm { ref targets, unwind: UnwindAction::Cleanup(u), .. } => { targets.iter().copied().chain(Some(u)) @@ -479,6 +480,7 @@ mod helper { | CoroutineDrop | Return | Unreachable + | TailCall { .. } | Call { target: None, unwind: _, .. } => (&mut []).into_iter().chain(None), InlineAsm { ref mut targets, unwind: UnwindAction::Cleanup(ref mut u), .. } => { targets.iter_mut().chain(Some(u)) @@ -501,6 +503,7 @@ impl<'tcx> TerminatorKind<'tcx> { | TerminatorKind::UnwindResume | TerminatorKind::UnwindTerminate(_) | TerminatorKind::Return + | TerminatorKind::TailCall { .. } | TerminatorKind::Unreachable | TerminatorKind::CoroutineDrop | TerminatorKind::Yield { .. } @@ -521,6 +524,7 @@ impl<'tcx> TerminatorKind<'tcx> { | TerminatorKind::UnwindResume | TerminatorKind::UnwindTerminate(_) | TerminatorKind::Return + | TerminatorKind::TailCall { .. } | TerminatorKind::Unreachable | TerminatorKind::CoroutineDrop | TerminatorKind::Yield { .. } @@ -606,9 +610,12 @@ impl<'tcx> TerminatorKind<'tcx> { pub fn edges(&self) -> TerminatorEdges<'_, 'tcx> { use TerminatorKind::*; match *self { - Return | UnwindResume | UnwindTerminate(_) | CoroutineDrop | Unreachable => { - TerminatorEdges::None - } + Return + | TailCall { .. } + | UnwindResume + | UnwindTerminate(_) + | CoroutineDrop + | Unreachable => TerminatorEdges::None, Goto { target } => TerminatorEdges::Single(target), diff --git a/compiler/rustc_middle/src/mir/visit.rs b/compiler/rustc_middle/src/mir/visit.rs index 7628a1ed2fe2b..0d3c419748b7a 100644 --- a/compiler/rustc_middle/src/mir/visit.rs +++ b/compiler/rustc_middle/src/mir/visit.rs @@ -540,6 +540,17 @@ macro_rules! make_mir_visitor { ); } + TerminatorKind::TailCall { + func, + args, + fn_span: _, + } => { + self.visit_operand(func, location); + for arg in args { + self.visit_operand(&$($mutability)? arg.node, location); + } + }, + TerminatorKind::Assert { cond, expected: _, diff --git a/compiler/rustc_mir_build/src/build/expr/stmt.rs b/compiler/rustc_mir_build/src/build/expr/stmt.rs index 2bdeb579a02de..60ab843257d5d 100644 --- a/compiler/rustc_mir_build/src/build/expr/stmt.rs +++ b/compiler/rustc_mir_build/src/build/expr/stmt.rs @@ -2,7 +2,9 @@ use crate::build::scope::BreakableTarget; use crate::build::{BlockAnd, BlockAndExtension, BlockFrame, Builder}; use rustc_middle::middle::region; use rustc_middle::mir::*; +use rustc_middle::span_bug; use rustc_middle::thir::*; +use rustc_span::source_map::Spanned; use tracing::debug; impl<'a, 'tcx> Builder<'a, 'tcx> { @@ -91,9 +93,38 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { ExprKind::Return { value } => { this.break_scope(block, value, BreakableTarget::Return, source_info) } - // FIXME(explicit_tail_calls): properly lower tail calls here ExprKind::Become { value } => { - this.break_scope(block, Some(value), BreakableTarget::Return, source_info) + let v = &this.thir[value]; + let ExprKind::Scope { value, .. } = v.kind else { + span_bug!(v.span, "`thir_check_tail_calls` should have disallowed this {v:?}") + }; + + let v = &this.thir[value]; + let ExprKind::Call { ref args, fun, fn_span, .. } = v.kind else { + span_bug!(v.span, "`thir_check_tail_calls` should have disallowed this {v:?}") + }; + + let fun = unpack!(block = this.as_local_operand(block, fun)); + let args: Vec<_> = args + .into_iter() + .copied() + .map(|arg| Spanned { + node: unpack!(block = this.as_local_call_operand(block, arg)), + span: this.thir.exprs[arg].span, + }) + .collect(); + + this.record_operands_moved(&args); + + debug!("expr_into_dest: fn_span={:?}", fn_span); + + this.cfg.terminate( + block, + source_info, + TerminatorKind::TailCall { func: fun, args, fn_span }, + ); + + this.cfg.start_new_block().unit() } _ => { assert!( diff --git a/compiler/rustc_mir_build/src/build/scope.rs b/compiler/rustc_mir_build/src/build/scope.rs index 5b6de39bb2e78..9e7534a283d9d 100644 --- a/compiler/rustc_mir_build/src/build/scope.rs +++ b/compiler/rustc_mir_build/src/build/scope.rs @@ -1523,6 +1523,7 @@ impl<'tcx> DropTreeBuilder<'tcx> for Unwind { | TerminatorKind::UnwindResume | TerminatorKind::UnwindTerminate(_) | TerminatorKind::Return + | TerminatorKind::TailCall { .. } | TerminatorKind::Unreachable | TerminatorKind::Yield { .. } | TerminatorKind::CoroutineDrop diff --git a/compiler/rustc_mir_build/src/lints.rs b/compiler/rustc_mir_build/src/lints.rs index 1c7aa9f9ed095..3cb83a48ffe1b 100644 --- a/compiler/rustc_mir_build/src/lints.rs +++ b/compiler/rustc_mir_build/src/lints.rs @@ -196,6 +196,8 @@ impl<'mir, 'tcx, C: TerminatorClassifier<'tcx>> TriColorVisitor ControlFlow::Break(NonRecursive), diff --git a/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs b/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs index 706bb796349fb..574da949b0ed3 100644 --- a/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs +++ b/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs @@ -145,6 +145,7 @@ where | TerminatorKind::InlineAsm { .. } | TerminatorKind::UnwindResume | TerminatorKind::Return + | TerminatorKind::TailCall { .. } | TerminatorKind::SwitchInt { .. } | TerminatorKind::Unreachable | TerminatorKind::Yield { .. } => {} diff --git a/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs b/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs index 29169c31263bd..f850a71027739 100644 --- a/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs +++ b/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs @@ -288,6 +288,7 @@ impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'_, 'tcx> { | TerminatorKind::Goto { .. } | TerminatorKind::UnwindResume | TerminatorKind::Return + | TerminatorKind::TailCall { .. } | TerminatorKind::SwitchInt { .. } | TerminatorKind::Unreachable => {} } @@ -325,6 +326,7 @@ impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'_, 'tcx> { | TerminatorKind::Goto { .. } | TerminatorKind::UnwindResume | TerminatorKind::Return + | TerminatorKind::TailCall { .. } | TerminatorKind::SwitchInt { .. } | TerminatorKind::Unreachable => {} } diff --git a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs index 1fb77bef3d410..7b39db821d839 100644 --- a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs +++ b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs @@ -489,6 +489,12 @@ impl<'b, 'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> Gatherer<'b, 'a, 'tcx, F> { self.gather_init(destination.as_ref(), InitKind::NonPanicPathOnly); } } + TerminatorKind::TailCall { ref func, ref args, .. } => { + self.gather_operand(func); + for arg in args { + self.gather_operand(&arg.node); + } + } TerminatorKind::InlineAsm { template: _, ref operands, diff --git a/compiler/rustc_mir_dataflow/src/value_analysis.rs b/compiler/rustc_mir_dataflow/src/value_analysis.rs index 7c1ff6fda53f7..1582c2e8a9061 100644 --- a/compiler/rustc_mir_dataflow/src/value_analysis.rs +++ b/compiler/rustc_mir_dataflow/src/value_analysis.rs @@ -269,6 +269,9 @@ pub trait ValueAnalysis<'tcx> { TerminatorKind::SwitchInt { discr, targets } => { return self.handle_switch_int(discr, targets, state); } + TerminatorKind::TailCall { .. } => { + // FIXME(explicit_tail_calls): determine if we need to do something here (probably not) + } TerminatorKind::Goto { .. } | TerminatorKind::UnwindResume | TerminatorKind::UnwindTerminate(_) diff --git a/compiler/rustc_mir_transform/src/coroutine.rs b/compiler/rustc_mir_transform/src/coroutine.rs index 9d44001f91512..fa5da89e8ba56 100644 --- a/compiler/rustc_mir_transform/src/coroutine.rs +++ b/compiler/rustc_mir_transform/src/coroutine.rs @@ -1367,6 +1367,10 @@ fn can_unwind<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) -> bool { | TerminatorKind::Call { .. } | TerminatorKind::InlineAsm { .. } | TerminatorKind::Assert { .. } => return true, + + TerminatorKind::TailCall { .. } => { + unreachable!("tail calls can't be present in generators") + } } } @@ -1916,6 +1920,7 @@ impl<'tcx> Visitor<'tcx> for EnsureCoroutineFieldAssignmentsNeverAlias<'_> { | TerminatorKind::UnwindResume | TerminatorKind::UnwindTerminate(_) | TerminatorKind::Return + | TerminatorKind::TailCall { .. } | TerminatorKind::Unreachable | TerminatorKind::Drop { .. } | TerminatorKind::Assert { .. } diff --git a/compiler/rustc_mir_transform/src/coverage/graph.rs b/compiler/rustc_mir_transform/src/coverage/graph.rs index 360dccb240dc6..83fb9ff974369 100644 --- a/compiler/rustc_mir_transform/src/coverage/graph.rs +++ b/compiler/rustc_mir_transform/src/coverage/graph.rs @@ -358,9 +358,12 @@ fn bcb_filtered_successors<'a, 'tcx>(terminator: &'a Terminator<'tcx>) -> Covera } // These terminators have no coverage-relevant successors. - CoroutineDrop | Return | Unreachable | UnwindResume | UnwindTerminate(_) => { - CoverageSuccessors::NotChainable(&[]) - } + CoroutineDrop + | Return + | TailCall { .. } + | Unreachable + | UnwindResume + | UnwindTerminate(_) => CoverageSuccessors::NotChainable(&[]), } } diff --git a/compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs b/compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs index 2ca166929eec8..a0f8f580b1d38 100644 --- a/compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs +++ b/compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs @@ -193,7 +193,8 @@ fn filtered_terminator_span(terminator: &Terminator<'_>) -> Option { | TerminatorKind::Goto { .. } => None, // Call `func` operand can have a more specific span when part of a chain of calls - | TerminatorKind::Call { ref func, .. } => { + TerminatorKind::Call { ref func, .. } + | TerminatorKind::TailCall { ref func, .. } => { let mut span = terminator.source_info.span; if let mir::Operand::Constant(box constant) = func { if constant.span.lo() > span.lo() { diff --git a/compiler/rustc_mir_transform/src/dest_prop.rs b/compiler/rustc_mir_transform/src/dest_prop.rs index b1016c0867c62..ab73a8af317a7 100644 --- a/compiler/rustc_mir_transform/src/dest_prop.rs +++ b/compiler/rustc_mir_transform/src/dest_prop.rs @@ -628,6 +628,12 @@ impl WriteInfo { self.add_operand(&arg.node); } } + TerminatorKind::TailCall { func, args, .. } => { + self.add_operand(func); + for arg in args { + self.add_operand(&arg.node); + } + } TerminatorKind::InlineAsm { operands, .. } => { for asm_operand in operands { match asm_operand { diff --git a/compiler/rustc_mir_transform/src/inline.rs b/compiler/rustc_mir_transform/src/inline.rs index 5075e0727548f..fe73715480f8b 100644 --- a/compiler/rustc_mir_transform/src/inline.rs +++ b/compiler/rustc_mir_transform/src/inline.rs @@ -383,6 +383,8 @@ impl<'tcx> Inliner<'tcx> { ) -> Option> { // Only consider direct calls to functions let terminator = bb_data.terminator(); + + // FIXME(explicit_tail_calls): figure out if we can inline tail calls if let TerminatorKind::Call { ref func, fn_span, .. } = terminator.kind { let func_ty = func.ty(caller_body, self.tcx); if let ty::FnDef(def_id, args) = *func_ty.kind() { @@ -550,6 +552,9 @@ impl<'tcx> Inliner<'tcx> { // inline-asm is detected. LLVM will still possibly do an inline later on // if the no-attribute function ends up with the same instruction set anyway. return Err("Cannot move inline-asm across instruction sets"); + } else if let TerminatorKind::TailCall { .. } = term.kind { + // FIXME(explicit_tail_calls): figure out how exactly functions containing tail calls can be inlined (and if they even should) + return Err("can't inline functions with tail calls"); } else { work_list.extend(term.successors()) } @@ -1038,6 +1043,10 @@ impl<'tcx> MutVisitor<'tcx> for Integrator<'_, 'tcx> { *target = self.map_block(*target); *unwind = self.map_unwind(*unwind); } + TerminatorKind::TailCall { .. } => { + // check_mir_body forbids tail calls + unreachable!() + } TerminatorKind::Call { ref mut target, ref mut unwind, .. } => { if let Some(ref mut tgt) = *target { *tgt = self.map_block(*tgt); diff --git a/compiler/rustc_mir_transform/src/jump_threading.rs b/compiler/rustc_mir_transform/src/jump_threading.rs index 85a61f95ca36f..97ec0cb39ded3 100644 --- a/compiler/rustc_mir_transform/src/jump_threading.rs +++ b/compiler/rustc_mir_transform/src/jump_threading.rs @@ -596,6 +596,7 @@ impl<'tcx, 'a> TOFinder<'tcx, 'a> { TerminatorKind::UnwindResume | TerminatorKind::UnwindTerminate(_) | TerminatorKind::Return + | TerminatorKind::TailCall { .. } | TerminatorKind::Unreachable | TerminatorKind::CoroutineDrop => bug!("{term:?} has no terminators"), // Disallowed during optimizations. diff --git a/compiler/rustc_mir_transform/src/known_panics_lint.rs b/compiler/rustc_mir_transform/src/known_panics_lint.rs index 8d6c00bbedbae..82ad8879d17b3 100644 --- a/compiler/rustc_mir_transform/src/known_panics_lint.rs +++ b/compiler/rustc_mir_transform/src/known_panics_lint.rs @@ -799,6 +799,7 @@ impl<'tcx> Visitor<'tcx> for ConstPropagator<'_, 'tcx> { | TerminatorKind::UnwindResume | TerminatorKind::UnwindTerminate(_) | TerminatorKind::Return + | TerminatorKind::TailCall { .. } | TerminatorKind::Unreachable | TerminatorKind::Drop { .. } | TerminatorKind::Yield { .. } diff --git a/compiler/rustc_mir_transform/src/mentioned_items.rs b/compiler/rustc_mir_transform/src/mentioned_items.rs index db2bb60bdac34..d928d7cf76445 100644 --- a/compiler/rustc_mir_transform/src/mentioned_items.rs +++ b/compiler/rustc_mir_transform/src/mentioned_items.rs @@ -38,7 +38,7 @@ impl<'tcx> Visitor<'tcx> for MentionedItemsVisitor<'_, 'tcx> { self.super_terminator(terminator, location); let span = || self.body.source_info(location).span; match &terminator.kind { - mir::TerminatorKind::Call { func, .. } => { + mir::TerminatorKind::Call { func, .. } | mir::TerminatorKind::TailCall { func, .. } => { let callee_ty = func.ty(self.body, self.tcx); self.mentioned_items .push(Spanned { node: MentionedItem::Fn(callee_ty), span: span() }); diff --git a/compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs b/compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs index fb52bfa468a01..1df5737e85974 100644 --- a/compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs +++ b/compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs @@ -75,6 +75,7 @@ impl RemoveNoopLandingPads { | TerminatorKind::UnwindTerminate(_) | TerminatorKind::Unreachable | TerminatorKind::Call { .. } + | TerminatorKind::TailCall { .. } | TerminatorKind::Assert { .. } | TerminatorKind::Drop { .. } | TerminatorKind::InlineAsm { .. } => false, diff --git a/compiler/rustc_mir_transform/src/validate.rs b/compiler/rustc_mir_transform/src/validate.rs index 2018a8fe667d7..ab5c25c493773 100644 --- a/compiler/rustc_mir_transform/src/validate.rs +++ b/compiler/rustc_mir_transform/src/validate.rs @@ -400,40 +400,44 @@ impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> { self.check_edge(location, *target, EdgeKind::Normal); self.check_unwind_edge(location, *unwind); } - TerminatorKind::Call { args, destination, target, unwind, .. } => { - if let Some(target) = target { - self.check_edge(location, *target, EdgeKind::Normal); - } - self.check_unwind_edge(location, *unwind); + TerminatorKind::Call { args, .. } | TerminatorKind::TailCall { args, .. } => { + // FIXME(explicit_tail_calls): refactor this & add tail-call specific checks + if let TerminatorKind::Call { target, unwind, destination, .. } = terminator.kind { + if let Some(target) = target { + self.check_edge(location, target, EdgeKind::Normal); + } + self.check_unwind_edge(location, unwind); + + // The code generation assumes that there are no critical call edges. The assumption + // is used to simplify inserting code that should be executed along the return edge + // from the call. FIXME(tmiasko): Since this is a strictly code generation concern, + // the code generation should be responsible for handling it. + if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Optimized) + && self.is_critical_call_edge(target, unwind) + { + self.fail( + location, + format!( + "encountered critical edge in `Call` terminator {:?}", + terminator.kind, + ), + ); + } - // The code generation assumes that there are no critical call edges. The assumption - // is used to simplify inserting code that should be executed along the return edge - // from the call. FIXME(tmiasko): Since this is a strictly code generation concern, - // the code generation should be responsible for handling it. - if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Optimized) - && self.is_critical_call_edge(*target, *unwind) - { - self.fail( - location, - format!( - "encountered critical edge in `Call` terminator {:?}", - terminator.kind, - ), - ); + // The call destination place and Operand::Move place used as an argument might be + // passed by a reference to the callee. Consequently they cannot be packed. + if is_within_packed(self.tcx, &self.body.local_decls, destination).is_some() { + // This is bad! The callee will expect the memory to be aligned. + self.fail( + location, + format!( + "encountered packed place in `Call` terminator destination: {:?}", + terminator.kind, + ), + ); + } } - // The call destination place and Operand::Move place used as an argument might be - // passed by a reference to the callee. Consequently they cannot be packed. - if is_within_packed(self.tcx, &self.body.local_decls, *destination).is_some() { - // This is bad! The callee will expect the memory to be aligned. - self.fail( - location, - format!( - "encountered packed place in `Call` terminator destination: {:?}", - terminator.kind, - ), - ); - } for arg in args { if let Operand::Move(place) = &arg.node { if is_within_packed(self.tcx, &self.body.local_decls, *place).is_some() { @@ -1498,15 +1502,22 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { } } } - TerminatorKind::Call { func, .. } => { + TerminatorKind::Call { func, .. } | TerminatorKind::TailCall { func, .. } => { let func_ty = func.ty(&self.body.local_decls, self.tcx); match func_ty.kind() { ty::FnPtr(..) | ty::FnDef(..) => {} _ => self.fail( location, - format!("encountered non-callable type {func_ty} in `Call` terminator"), + format!( + "encountered non-callable type {func_ty} in `{}` terminator", + terminator.kind.name() + ), ), } + + if let TerminatorKind::TailCall { .. } = terminator.kind { + // FIXME(explicit_tail_calls): implement tail-call specific checks here (such as signature matching, forbidding closures, etc) + } } TerminatorKind::Assert { cond, .. } => { let cond_ty = cond.ty(&self.body.local_decls, self.tcx); diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs index 342c01ff69731..3ef8da4a9c51c 100644 --- a/compiler/rustc_monomorphize/src/collector.rs +++ b/compiler/rustc_monomorphize/src/collector.rs @@ -755,7 +755,8 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MirUsedCollector<'a, 'tcx> { }; match terminator.kind { - mir::TerminatorKind::Call { ref func, ref args, ref fn_span, .. } => { + mir::TerminatorKind::Call { ref func, ref args, ref fn_span, .. } + | mir::TerminatorKind::TailCall { ref func, ref args, ref fn_span } => { let callee_ty = func.ty(self.body, tcx); // *Before* monomorphizing, record that we already handled this mention. self.used_mentioned_items.insert(MentionedItem::Fn(callee_ty)); diff --git a/compiler/rustc_smir/src/rustc_smir/convert/mir.rs b/compiler/rustc_smir/src/rustc_smir/convert/mir.rs index f15b82d0c031f..3367cea9897ae 100644 --- a/compiler/rustc_smir/src/rustc_smir/convert/mir.rs +++ b/compiler/rustc_smir/src/rustc_smir/convert/mir.rs @@ -644,6 +644,7 @@ impl<'tcx> Stable<'tcx> for mir::TerminatorKind<'tcx> { target: target.map(|t| t.as_usize()), unwind: unwind.stable(tables), }, + mir::TerminatorKind::TailCall { func: _, args: _, fn_span: _ } => todo!(), mir::TerminatorKind::Assert { cond, expected, msg, target, unwind } => { TerminatorKind::Assert { cond: cond.stable(tables), diff --git a/tests/ui/explicit-tail-calls/constck.rs b/tests/ui/explicit-tail-calls/constck.rs new file mode 100644 index 0000000000000..938f15f12c091 --- /dev/null +++ b/tests/ui/explicit-tail-calls/constck.rs @@ -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() {} diff --git a/tests/ui/explicit-tail-calls/constck.stderr b/tests/ui/explicit-tail-calls/constck.stderr new file mode 100644 index 0000000000000..d9967c45fa03c --- /dev/null +++ b/tests/ui/explicit-tail-calls/constck.stderr @@ -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`. diff --git a/tests/ui/explicit-tail-calls/return-mismatches.rs b/tests/ui/explicit-tail-calls/return-mismatches.rs index 8094a192913bc..935a1a1d28b02 100644 --- a/tests/ui/explicit-tail-calls/return-mismatches.rs +++ b/tests/ui/explicit-tail-calls/return-mismatches.rs @@ -13,7 +13,7 @@ fn _f1() { become _g1(); //~ error: mismatched types } -fn _g1() -> ! { //~ WARN: cannot return without recursing +fn _g1() -> ! { become _g1(); } diff --git a/tests/ui/explicit-tail-calls/return-mismatches.stderr b/tests/ui/explicit-tail-calls/return-mismatches.stderr index 31c7a46ded911..1dcc35797c130 100644 --- a/tests/ui/explicit-tail-calls/return-mismatches.stderr +++ b/tests/ui/explicit-tail-calls/return-mismatches.stderr @@ -22,17 +22,6 @@ error[E0308]: mismatched types LL | become _g2(); | ^^^^^^^^^^^^ expected `u32`, found `u16` -warning: function cannot return without recursing - --> $DIR/return-mismatches.rs:16:1 - | -LL | fn _g1() -> ! { - | ^^^^^^^^^^^^^ cannot return without recursing -LL | become _g1(); - | ----- recursive call site - | - = help: a `loop` may express intention better if this is on purpose - = note: `#[warn(unconditional_recursion)]` on by default - -error: aborting due to 3 previous errors; 1 warning emitted +error: aborting due to 3 previous errors For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/explicit-tail-calls/unsafeck.rs b/tests/ui/explicit-tail-calls/unsafeck.rs new file mode 100644 index 0000000000000..872a70ca3a0a7 --- /dev/null +++ b/tests/ui/explicit-tail-calls/unsafeck.rs @@ -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() {} diff --git a/tests/ui/explicit-tail-calls/unsafeck.stderr b/tests/ui/explicit-tail-calls/unsafeck.stderr new file mode 100644 index 0000000000000..25b8967e17b6d --- /dev/null +++ b/tests/ui/explicit-tail-calls/unsafeck.stderr @@ -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`. diff --git a/tests/ui/parser/bad-let-else-statement.rs b/tests/ui/parser/bad-let-else-statement.rs index ff6619cbc9864..3ede26dbcd06c 100644 --- a/tests/ui/parser/bad-let-else-statement.rs +++ b/tests/ui/parser/bad-let-else-statement.rs @@ -147,14 +147,14 @@ fn o() -> Result<(), ()> { }; } -fn p() { - let 0 = become { - () - } else { - //~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed - return; - }; -} +// fn p() { // FIXME(explicit_tail_calls): this currently trips an assertion... +// let 0 = become { +// () +// } else { +// // ~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed +// return; +// }; +// } fn q() { let foo = |x: i32| { diff --git a/tests/ui/parser/bad-let-else-statement.stderr b/tests/ui/parser/bad-let-else-statement.stderr index 0bf6a346dfb17..79d722bb7ac44 100644 --- a/tests/ui/parser/bad-let-else-statement.stderr +++ b/tests/ui/parser/bad-let-else-statement.stderr @@ -203,19 +203,6 @@ LL | () LL ~ }) else { | -error: right curly brace `}` before `else` in a `let...else` statement not allowed - --> $DIR/bad-let-else-statement.rs:153:5 - | -LL | } else { - | ^ - | -help: wrap the expression in parentheses - | -LL ~ let 0 = become ({ -LL | () -LL ~ }) else { - | - error: right curly brace `}` before `else` in a `let...else` statement not allowed --> $DIR/bad-let-else-statement.rs:163:5 | @@ -325,5 +312,5 @@ LL | | } else { = note: this pattern will always match, so the `else` clause is useless = help: consider removing the `else` clause -error: aborting due to 20 previous errors; 5 warnings emitted +error: aborting due to 19 previous errors; 5 warnings emitted From 4187cdc0135ee05802733632d4cbf6467da22847 Mon Sep 17 00:00:00 2001 From: DrMeepster <19316085+DrMeepster@users.noreply.github.com> Date: Wed, 14 Jun 2023 01:12:25 -0700 Subject: [PATCH 02/15] Properly handle drops for tail calls --- .../rustc_mir_build/src/build/expr/stmt.rs | 40 ++-- compiler/rustc_mir_build/src/build/scope.rs | 85 ++++++++ ...ll_drops.f.ElaborateDrops.panic-abort.diff | 108 ++++++++++ ...l_drops.f.ElaborateDrops.panic-unwind.diff | 109 ++++++++++ ...l_call_drops.f.built.after.panic-abort.mir | 118 ++++++++++ ..._call_drops.f.built.after.panic-unwind.mir | 118 ++++++++++ ...f_with_arg.ElaborateDrops.panic-abort.diff | 184 ++++++++++++++++ ..._with_arg.ElaborateDrops.panic-unwind.diff | 184 ++++++++++++++++ ...ops.f_with_arg.built.after.panic-abort.mir | 202 ++++++++++++++++++ ...ps.f_with_arg.built.after.panic-unwind.mir | 202 ++++++++++++++++++ tests/mir-opt/tail_call_drops.rs | 41 ++++ tests/ui/explicit-tail-calls/drop-order.rs | 70 ++++++ 12 files changed, 1443 insertions(+), 18 deletions(-) create mode 100644 tests/mir-opt/tail_call_drops.f.ElaborateDrops.panic-abort.diff create mode 100644 tests/mir-opt/tail_call_drops.f.ElaborateDrops.panic-unwind.diff create mode 100644 tests/mir-opt/tail_call_drops.f.built.after.panic-abort.mir create mode 100644 tests/mir-opt/tail_call_drops.f.built.after.panic-unwind.mir create mode 100644 tests/mir-opt/tail_call_drops.f_with_arg.ElaborateDrops.panic-abort.diff create mode 100644 tests/mir-opt/tail_call_drops.f_with_arg.ElaborateDrops.panic-unwind.diff create mode 100644 tests/mir-opt/tail_call_drops.f_with_arg.built.after.panic-abort.mir create mode 100644 tests/mir-opt/tail_call_drops.f_with_arg.built.after.panic-unwind.mir create mode 100644 tests/mir-opt/tail_call_drops.rs create mode 100644 tests/ui/explicit-tail-calls/drop-order.rs diff --git a/compiler/rustc_mir_build/src/build/expr/stmt.rs b/compiler/rustc_mir_build/src/build/expr/stmt.rs index 60ab843257d5d..7d2c32e000b90 100644 --- a/compiler/rustc_mir_build/src/build/expr/stmt.rs +++ b/compiler/rustc_mir_build/src/build/expr/stmt.rs @@ -95,7 +95,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { } ExprKind::Become { value } => { let v = &this.thir[value]; - let ExprKind::Scope { value, .. } = v.kind else { + let ExprKind::Scope { value, lint_level, region_scope } = v.kind else { span_bug!(v.span, "`thir_check_tail_calls` should have disallowed this {v:?}") }; @@ -104,27 +104,31 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { span_bug!(v.span, "`thir_check_tail_calls` should have disallowed this {v:?}") }; - let fun = unpack!(block = this.as_local_operand(block, fun)); - let args: Vec<_> = args - .into_iter() - .copied() - .map(|arg| Spanned { - node: unpack!(block = this.as_local_call_operand(block, arg)), - span: this.thir.exprs[arg].span, - }) - .collect(); + this.in_scope((region_scope, source_info), lint_level, |this| { + let fun = unpack!(block = this.as_local_operand(block, fun)); + let args: Vec<_> = args + .into_iter() + .copied() + .map(|arg| Spanned { + node: unpack!(block = this.as_local_call_operand(block, arg)), + span: this.thir.exprs[arg].span, + }) + .collect(); - this.record_operands_moved(&args); + this.record_operands_moved(&args); - debug!("expr_into_dest: fn_span={:?}", fn_span); + debug!("expr_into_dest: fn_span={:?}", fn_span); - this.cfg.terminate( - block, - source_info, - TerminatorKind::TailCall { func: fun, args, fn_span }, - ); + unpack!(block = this.break_for_tail_call(block, &args, source_info)); - this.cfg.start_new_block().unit() + this.cfg.terminate( + block, + source_info, + TerminatorKind::TailCall { func: fun, args, fn_span }, + ); + + this.cfg.start_new_block().unit() + }) } _ => { assert!( diff --git a/compiler/rustc_mir_build/src/build/scope.rs b/compiler/rustc_mir_build/src/build/scope.rs index 9e7534a283d9d..948301e2ece4d 100644 --- a/compiler/rustc_mir_build/src/build/scope.rs +++ b/compiler/rustc_mir_build/src/build/scope.rs @@ -745,6 +745,91 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { 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>], + 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 { // If we are emitting a `drop` statement, we need to have the cached // diverge cleanup pads ready in case that drop panics. diff --git a/tests/mir-opt/tail_call_drops.f.ElaborateDrops.panic-abort.diff b/tests/mir-opt/tail_call_drops.f.ElaborateDrops.panic-abort.diff new file mode 100644 index 0000000000000..44673ea00a966 --- /dev/null +++ b/tests/mir-opt/tail_call_drops.f.ElaborateDrops.panic-abort.diff @@ -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::(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]; + } + } + diff --git a/tests/mir-opt/tail_call_drops.f.ElaborateDrops.panic-unwind.diff b/tests/mir-opt/tail_call_drops.f.ElaborateDrops.panic-unwind.diff new file mode 100644 index 0000000000000..a6d33a2459543 --- /dev/null +++ b/tests/mir-opt/tail_call_drops.f.ElaborateDrops.panic-unwind.diff @@ -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::(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]; + } + } + diff --git a/tests/mir-opt/tail_call_drops.f.built.after.panic-abort.mir b/tests/mir-opt/tail_call_drops.f.built.after.panic-abort.mir new file mode 100644 index 0000000000000..2c3d62491d715 --- /dev/null +++ b/tests/mir-opt/tail_call_drops.f.built.after.panic-abort.mir @@ -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::(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; + } +} diff --git a/tests/mir-opt/tail_call_drops.f.built.after.panic-unwind.mir b/tests/mir-opt/tail_call_drops.f.built.after.panic-unwind.mir new file mode 100644 index 0000000000000..2c3d62491d715 --- /dev/null +++ b/tests/mir-opt/tail_call_drops.f.built.after.panic-unwind.mir @@ -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::(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; + } +} diff --git a/tests/mir-opt/tail_call_drops.f_with_arg.ElaborateDrops.panic-abort.diff b/tests/mir-opt/tail_call_drops.f_with_arg.ElaborateDrops.panic-abort.diff new file mode 100644 index 0000000000000..c7df2bb2207fb --- /dev/null +++ b/tests/mir-opt/tail_call_drops.f_with_arg.ElaborateDrops.panic-abort.diff @@ -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::(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]; + } + } + diff --git a/tests/mir-opt/tail_call_drops.f_with_arg.ElaborateDrops.panic-unwind.diff b/tests/mir-opt/tail_call_drops.f_with_arg.ElaborateDrops.panic-unwind.diff new file mode 100644 index 0000000000000..c7df2bb2207fb --- /dev/null +++ b/tests/mir-opt/tail_call_drops.f_with_arg.ElaborateDrops.panic-unwind.diff @@ -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::(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]; + } + } + diff --git a/tests/mir-opt/tail_call_drops.f_with_arg.built.after.panic-abort.mir b/tests/mir-opt/tail_call_drops.f_with_arg.built.after.panic-abort.mir new file mode 100644 index 0000000000000..744f1989acc92 --- /dev/null +++ b/tests/mir-opt/tail_call_drops.f_with_arg.built.after.panic-abort.mir @@ -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::(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; + } +} diff --git a/tests/mir-opt/tail_call_drops.f_with_arg.built.after.panic-unwind.mir b/tests/mir-opt/tail_call_drops.f_with_arg.built.after.panic-unwind.mir new file mode 100644 index 0000000000000..744f1989acc92 --- /dev/null +++ b/tests/mir-opt/tail_call_drops.f_with_arg.built.after.panic-unwind.mir @@ -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::(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; + } +} diff --git a/tests/mir-opt/tail_call_drops.rs b/tests/mir-opt/tail_call_drops.rs new file mode 100644 index 0000000000000..56f4852a95f4b --- /dev/null +++ b/tests/mir-opt/tail_call_drops.rs @@ -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() {} diff --git a/tests/ui/explicit-tail-calls/drop-order.rs b/tests/ui/explicit-tail-calls/drop-order.rs new file mode 100644 index 0000000000000..e20730446ec55 --- /dev/null +++ b/tests/ui/explicit-tail-calls/drop-order.rs @@ -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::>()); + + let simply_counter = Default::default(); + simply_recursive(0, &simply_counter); + assert_eq!(simply_counter.into_inner(), (0..128).rev().collect::>()); + + let scope_counter = Default::default(); + out_of_inner_scope(&scope_counter); + assert_eq!(scope_counter.into_inner(), (0..8).collect::>()); +} + +fn tail_recursive(n: u8, order: &RefCell>) { + if n >= 128 { + return; + } + + let _local = DropCounter(n, order); + + become tail_recursive(n + 1, order) +} + +fn simply_recursive(n: u8, order: &RefCell>) { + if n >= 128 { + return; + } + + let _local = DropCounter(n, order); + + return simply_recursive(n + 1, order); +} + +fn out_of_inner_scope(order: &RefCell>) { + fn inner(order: &RefCell>) { + 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>); + +impl Drop for DropCounter<'_> { + #[track_caller] + fn drop(&mut self) { + self.1.borrow_mut().push(self.0); + } +} From 3b5a5ee6c8b2b055e772de2163379d99e0d463df Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Wed, 28 Jun 2023 15:34:10 +0000 Subject: [PATCH 03/15] Support tail calls in the interpreter --- .../src/interpret/terminator.rs | 72 ++++++++++++++++++- .../ctfe-arg-bad-borrow.rs | 14 ++++ .../ctfe-arg-bad-borrow.stderr | 14 ++++ .../ctfe-arg-good-borrow.rs | 13 ++++ tests/ui/explicit-tail-calls/ctfe-arg-move.rs | 15 ++++ .../ctfe-collatz-multi-rec.rs | 43 +++++++++++ .../ctfe-id-unlimited.return.stderr | 36 ++++++++++ .../explicit-tail-calls/ctfe-id-unlimited.rs | 35 +++++++++ .../ctfe-tail-call-panic.rs | 19 +++++ .../ctfe-tail-call-panic.stderr | 21 ++++++ 10 files changed, 281 insertions(+), 1 deletion(-) create mode 100644 tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.rs create mode 100644 tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.stderr create mode 100644 tests/ui/explicit-tail-calls/ctfe-arg-good-borrow.rs create mode 100644 tests/ui/explicit-tail-calls/ctfe-arg-move.rs create mode 100644 tests/ui/explicit-tail-calls/ctfe-collatz-multi-rec.rs create mode 100644 tests/ui/explicit-tail-calls/ctfe-id-unlimited.return.stderr create mode 100644 tests/ui/explicit-tail-calls/ctfe-id-unlimited.rs create mode 100644 tests/ui/explicit-tail-calls/ctfe-tail-call-panic.rs create mode 100644 tests/ui/explicit-tail-calls/ctfe-tail-call-panic.stderr diff --git a/compiler/rustc_const_eval/src/interpret/terminator.rs b/compiler/rustc_const_eval/src/interpret/terminator.rs index 7d1a48d6ded6f..d77bcdd709731 100644 --- a/compiler/rustc_const_eval/src/interpret/terminator.rs +++ b/compiler/rustc_const_eval/src/interpret/terminator.rs @@ -172,7 +172,77 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } } - TailCall { func: _, args: _, fn_span: _ } => todo!(), + TailCall { ref func, ref args, fn_span: _ } => { + // FIXME(explicit_tail_calls): a lot of code here is duplicated with normal calls, can we refactor this? + let old_frame_idx = self.frame_idx(); + 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 (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, substs) => { + let instance = self.resolve(def_id, substs)?; + ( + 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 + ), + }; + + // 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 can't use `pop_stack_frame` as it "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. + let Some(prev_frame) = self.stack_mut().pop() else { + span_bug!( + terminator.source_info.span, + "empty stack while evaluating this tail call" + ) + }; + + let StackPopCleanup::Goto { ret, unwind } = prev_frame.return_to_block else { + span_bug!(terminator.source_info.span, "tail call with the root stack frame") + }; + + self.eval_fn_call( + fn_val, + (fn_sig.abi, fn_abi), + &args, + with_caller_location, + &prev_frame.return_place, + ret, + unwind, + )?; + + 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: _ } => { let place = self.eval_place(place)?; diff --git a/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.rs b/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.rs new file mode 100644 index 0000000000000..5a105ee4eb59d --- /dev/null +++ b/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.rs @@ -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() {} diff --git a/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.stderr b/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.stderr new file mode 100644 index 0000000000000..75fb13c378c24 --- /dev/null +++ b/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.stderr @@ -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`. diff --git a/tests/ui/explicit-tail-calls/ctfe-arg-good-borrow.rs b/tests/ui/explicit-tail-calls/ctfe-arg-good-borrow.rs new file mode 100644 index 0000000000000..50bf6c946ca8f --- /dev/null +++ b/tests/ui/explicit-tail-calls/ctfe-arg-good-borrow.rs @@ -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() {} diff --git a/tests/ui/explicit-tail-calls/ctfe-arg-move.rs b/tests/ui/explicit-tail-calls/ctfe-arg-move.rs new file mode 100644 index 0000000000000..88ff3a4a5ad13 --- /dev/null +++ b/tests/ui/explicit-tail-calls/ctfe-arg-move.rs @@ -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() {} diff --git a/tests/ui/explicit-tail-calls/ctfe-collatz-multi-rec.rs b/tests/ui/explicit-tail-calls/ctfe-collatz-multi-rec.rs new file mode 100644 index 0000000000000..2e6bb0e1ac5b7 --- /dev/null +++ b/tests/ui/explicit-tail-calls/ctfe-collatz-multi-rec.rs @@ -0,0 +1,43 @@ +//@ run-pass +#![allow(incomplete_features)] +#![feature(explicit_tail_calls)] + +/// A very unnecessarily complicated "implementation" of the callatz 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; +} diff --git a/tests/ui/explicit-tail-calls/ctfe-id-unlimited.return.stderr b/tests/ui/explicit-tail-calls/ctfe-id-unlimited.return.stderr new file mode 100644 index 0000000000000..4a1e50b4111e3 --- /dev/null +++ b/tests/ui/explicit-tail-calls/ctfe-id-unlimited.return.stderr @@ -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`. diff --git a/tests/ui/explicit-tail-calls/ctfe-id-unlimited.rs b/tests/ui/explicit-tail-calls/ctfe-id-unlimited.rs new file mode 100644 index 0000000000000..54e68b2b7f793 --- /dev/null +++ b/tests/ui/explicit-tail-calls/ctfe-id-unlimited.rs @@ -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; +} diff --git a/tests/ui/explicit-tail-calls/ctfe-tail-call-panic.rs b/tests/ui/explicit-tail-calls/ctfe-tail-call-panic.rs new file mode 100644 index 0000000000000..3d69cde29895a --- /dev/null +++ b/tests/ui/explicit-tail-calls/ctfe-tail-call-panic.rs @@ -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() {} diff --git a/tests/ui/explicit-tail-calls/ctfe-tail-call-panic.stderr b/tests/ui/explicit-tail-calls/ctfe-tail-call-panic.stderr new file mode 100644 index 0000000000000..8c07051210578 --- /dev/null +++ b/tests/ui/explicit-tail-calls/ctfe-tail-call-panic.stderr @@ -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`. From 45c70318f796cb20f47120ebe9de66919f948f91 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Fri, 30 Jun 2023 18:27:43 +0000 Subject: [PATCH 04/15] Refactor common part of evaluating `Call`&`TailCall` in the interpreter --- .../src/interpret/terminator.rs | 119 +++++++++--------- 1 file changed, 56 insertions(+), 63 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/terminator.rs b/compiler/rustc_const_eval/src/interpret/terminator.rs index d77bcdd709731..6644e70e039a8 100644 --- a/compiler/rustc_const_eval/src/interpret/terminator.rs +++ b/compiler/rustc_const_eval/src/interpret/terminator.rs @@ -47,6 +47,15 @@ impl<'tcx, Prov: Provenance> FnArg<'tcx, Prov> { } } +struct EvaluatedCalleeAndArgs<'tcx, M: Machine<'tcx>> { + callee: FnVal<'tcx, M::ExtraFnVal>, + args: Vec>, + fn_sig: ty::FnSig<'tcx>, + fn_abi: &'tcx FnAbi<'tcx, Ty<'tcx>>, + /// True if the function is marked as `#[track_caller]` ([`ty::InstanceDef::requires_caller_location`]) + with_caller_location: bool, +} + 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 /// original memory occurs. @@ -124,40 +133,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } => { let old_stack = self.frame_idx(); 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 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 (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 EvaluatedCalleeAndArgs { callee, args, fn_sig, fn_abi, with_caller_location } = + self.eval_callee_and_args(terminator, func, args)?; let destination = self.force_allocation(&self.eval_place(destination)?)?; self.eval_fn_call( - fn_val, + callee, (fn_sig.abi, fn_abi), &args, with_caller_location, @@ -173,38 +155,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } TailCall { ref func, ref args, fn_span: _ } => { - // FIXME(explicit_tail_calls): a lot of code here is duplicated with normal calls, can we refactor this? let old_frame_idx = self.frame_idx(); - 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 (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, substs) => { - let instance = self.resolve(def_id, substs)?; - ( - 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 EvaluatedCalleeAndArgs { callee, args, fn_sig, fn_abi, with_caller_location } = + self.eval_callee_and_args(terminator, func, args)?; // This is the "canonical" implementation of tails calls, // a pop of the current stack frame, followed by a normal call @@ -227,7 +181,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { }; self.eval_fn_call( - fn_val, + callee, (fn_sig.abi, fn_abi), &args, with_caller_location, @@ -586,6 +540,45 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { 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>], + ) -> 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. /// /// `caller_fn_abi` is used to determine if all the arguments are passed the proper way. From 5f4caae11c7a640350fbe90ddc465d8e2fa0156e Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Tue, 18 Jul 2023 16:25:59 +0000 Subject: [PATCH 05/15] Fix unconditional recursion lint wrt tail calls --- compiler/rustc_mir_build/src/lints.rs | 18 ++++++++++++-- ...lint-unconditional-recursion-tail-calls.rs | 24 +++++++++++++++++++ ...-unconditional-recursion-tail-calls.stderr | 18 ++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 tests/ui/lint/lint-unconditional-recursion-tail-calls.rs create mode 100644 tests/ui/lint/lint-unconditional-recursion-tail-calls.stderr diff --git a/compiler/rustc_mir_build/src/lints.rs b/compiler/rustc_mir_build/src/lints.rs index 3cb83a48ffe1b..263e777d03ae1 100644 --- a/compiler/rustc_mir_build/src/lints.rs +++ b/compiler/rustc_mir_build/src/lints.rs @@ -196,8 +196,6 @@ impl<'mir, 'tcx, C: TerminatorClassifier<'tcx>> TriColorVisitor ControlFlow::Break(NonRecursive), @@ -219,12 +217,28 @@ impl<'mir, 'tcx, C: TerminatorClassifier<'tcx>> TriColorVisitor 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 { // When we examine a node for the last time, remember it if it is a recursive call. 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) { self.reachable_recursive_calls.push(terminator.source_info.span); } diff --git a/tests/ui/lint/lint-unconditional-recursion-tail-calls.rs b/tests/ui/lint/lint-unconditional-recursion-tail-calls.rs new file mode 100644 index 0000000000000..c94bf03257972 --- /dev/null +++ b/tests/ui/lint/lint-unconditional-recursion-tail-calls.rs @@ -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() {} diff --git a/tests/ui/lint/lint-unconditional-recursion-tail-calls.stderr b/tests/ui/lint/lint-unconditional-recursion-tail-calls.stderr new file mode 100644 index 0000000000000..52f9740d027e4 --- /dev/null +++ b/tests/ui/lint/lint-unconditional-recursion-tail-calls.stderr @@ -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 + From 30b18d7c3621e4b6c2cb84b7f84e72c8efd3ff3f Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Tue, 9 May 2023 21:30:28 +0000 Subject: [PATCH 06/15] Add support for `mir::TerminatorKind::TailCall` in clippy --- src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs index 42b10f69c0cdf..f206b2ceebcb1 100644 --- a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs +++ b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs @@ -330,7 +330,8 @@ fn check_terminator<'tcx>( target: _, unwind: _, fn_span: _, - } => { + } + | TerminatorKind::TailCall { func, args, fn_span: _ } => { let fn_ty = func.ty(body, tcx); if let ty::FnDef(fn_def_id, _) = *fn_ty.kind() { if !is_const_fn(tcx, fn_def_id, msrv) { From cda25e56c8d4f810d19034ebf5d0e4fdb155a264 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Fri, 15 Mar 2024 15:08:20 +0000 Subject: [PATCH 07/15] Refactor & fixup interpreter implementation of tail calls --- .../src/interpret/eval_context.rs | 145 +++++++++++++----- .../rustc_const_eval/src/interpret/machine.rs | 3 + .../rustc_const_eval/src/interpret/step.rs | 2 +- .../src/interpret/terminator.rs | 82 ++++++---- 4 files changed, 162 insertions(+), 70 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/eval_context.rs b/compiler/rustc_const_eval/src/interpret/eval_context.rs index 830c4bd3e26e0..fd063575e5d98 100644 --- a/compiler/rustc_const_eval/src/interpret/eval_context.rs +++ b/compiler/rustc_const_eval/src/interpret/eval_context.rs @@ -159,6 +159,13 @@ pub enum StackPopCleanup { Root { cleanup: bool }, } +/// Return type of [`InterpCx::pop_stack_frame`]. +pub struct StackPop<'tcx, Prov: Provenance> { + pub jump: StackPopJump, + pub target: StackPopCleanup, + pub destination: MPlaceTy<'tcx, Prov>, +} + /// State of a local variable including a memoized layout #[derive(Clone)] pub struct LocalState<'tcx, Prov: Provenance = CtfeProvenance> { @@ -803,14 +810,31 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { return_to_block: StackPopCleanup, ) -> InterpResult<'tcx> { 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 unto 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 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 { body, loc: Right(body.span), // Span used for errors caused during preamble. return_to_block, - return_place: return_place.clone(), + return_place, locals, instance, tracing_span: SpanGuard::new(), @@ -819,6 +843,15 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let frame = M::init_frame(self, pre_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). for &const_ in &body.required_consts { let c = @@ -839,6 +872,59 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { 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, StackPop<'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 target = frame.return_to_block; + let destination = frame.return_place.clone(); + + let jump = if cleanup { + M::after_stack_pop(self, frame, unwinding)? + } else { + StackPopJump::NoCleanup + }; + + Ok(StackPop { jump, target, destination }) + } + + /// A private helper for [`push_stack_frame`](InterpCx::push_stack_frame). + /// Returns whatever the cleanup was done. + 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. #[inline] pub fn go_to_block(&mut self, target: mir::BasicBlock) { @@ -886,7 +972,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } /// 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 /// from a function. In this case, we jump back into the frame of the caller, @@ -899,7 +985,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { /// The cleanup block ends with a special `Resume` terminator, which will /// cause us to continue unwinding. #[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!( "popping stack frame ({})", if unwinding { "during unwinding" } else { "returning from function" } @@ -947,45 +1036,31 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { 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. - // Note that its locals are gone already, but that's fine. - let frame = - self.stack_mut().pop().expect("tried to pop a stack frame, but there were none"); + let frame = self.pop_stack_frame(unwinding)?; + // Report error from return value copy, if any. copy_ret_result?; - // 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!(!unwinding, "tried to skip cleanup during unwinding"); - // Skip machine hook. - return Ok(()); - } - if M::after_stack_pop(self, frame, unwinding)? == StackPopJump::NoJump { - // The hook already did everything. - return Ok(()); + match frame.jump { + StackPopJump::Normal => {} + StackPopJump::NoJump => { + // The hook already did everything. + return Ok(()); + } + StackPopJump::NoCleanup => { + // If we are not doing cleanup, also skip everything else. + assert!(self.stack().is_empty(), "only the topmost frame should ever be leaked"); + assert!(!unwinding, "tried to skip cleanup during unwinding"); + // Skip machine hook. + return Ok(()); + } } // Normal return, figure out where to jump. if unwinding { // Follow the unwind edge. - let unwind = match return_to_block { + let unwind = match frame.target { StackPopCleanup::Goto { unwind, .. } => unwind, StackPopCleanup::Root { .. } => { panic!("encountered StackPopCleanup::Root when unwinding!") @@ -995,7 +1070,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { self.unwind_to_block(unwind) } else { // Follow the normal return edge. - match return_to_block { + match frame.target { StackPopCleanup::Goto { ret, .. } => self.return_to_block(ret), StackPopCleanup::Root { .. } => { assert!( diff --git a/compiler/rustc_const_eval/src/interpret/machine.rs b/compiler/rustc_const_eval/src/interpret/machine.rs index e91ab7c179163..b8a651a744aa6 100644 --- a/compiler/rustc_const_eval/src/interpret/machine.rs +++ b/compiler/rustc_const_eval/src/interpret/machine.rs @@ -36,6 +36,9 @@ pub enum StackPopJump { /// Indicates that we should *not* jump to the return/unwind address, as the callback already /// took care of everything. NoJump, + + /// Returned by [`InterpCx::pop_stack_frame`] when no cleanup should be done. + NoCleanup, } /// Whether this kind of memory is allowed to leak diff --git a/compiler/rustc_const_eval/src/interpret/step.rs b/compiler/rustc_const_eval/src/interpret/step.rs index 1baf62baa816f..b3124dfdfbc01 100644 --- a/compiler/rustc_const_eval/src/interpret/step.rs +++ b/compiler/rustc_const_eval/src/interpret/step.rs @@ -32,7 +32,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // We are unwinding and this fn has no cleanup code. // Just go on unwinding. trace!("unwinding: skipping frame"); - self.pop_stack_frame(/* unwinding */ true)?; + self.return_from_current_stack_frame(/* unwinding */ true)?; return Ok(true); }; let basic_block = &self.body().basic_blocks[loc.block]; diff --git a/compiler/rustc_const_eval/src/interpret/terminator.rs b/compiler/rustc_const_eval/src/interpret/terminator.rs index 6644e70e039a8..a8d18405b6e56 100644 --- a/compiler/rustc_const_eval/src/interpret/terminator.rs +++ b/compiler/rustc_const_eval/src/interpret/terminator.rs @@ -4,9 +4,8 @@ use either::Either; use rustc_middle::ty::TyCtxt; use tracing::trace; -use rustc_middle::span_bug; use rustc_middle::{ - mir, + bug, mir, span_bug, ty::{ self, layout::{FnAbiOf, IntegerExt, LayoutOf, TyAndLayout}, @@ -26,7 +25,10 @@ use super::{ InterpResult, MPlaceTy, Machine, OpTy, PlaceTy, Projectable, Provenance, Scalar, StackPopCleanup, }; -use crate::fluent_generated as fluent; +use crate::{ + fluent_generated as fluent, + interpret::{eval_context::StackPop, StackPopJump}, +}; /// An argment passed to a function. #[derive(Clone, Debug)] @@ -93,7 +95,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { use rustc_middle::mir::TerminatorKind::*; match terminator.kind { Return => { - self.pop_stack_frame(/* unwinding */ false)? + self.return_from_current_stack_frame(/* unwinding */ false)? } Goto { target } => self.go_to_block(target), @@ -160,35 +162,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let EvaluatedCalleeAndArgs { callee, args, fn_sig, fn_abi, with_caller_location } = self.eval_callee_and_args(terminator, func, args)?; - // 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 can't use `pop_stack_frame` as it "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. - let Some(prev_frame) = self.stack_mut().pop() else { - span_bug!( - terminator.source_info.span, - "empty stack while evaluating this tail call" - ) - }; - - let StackPopCleanup::Goto { ret, unwind } = prev_frame.return_to_block else { - span_bug!(terminator.source_info.span, "tail call with the root stack frame") - }; - - self.eval_fn_call( - callee, - (fn_sig.abi, fn_abi), - &args, - with_caller_location, - &prev_frame.return_place, - ret, - unwind, - )?; + self.eval_fn_tail_call(callee, (fn_sig.abi, fn_abi), &args, with_caller_location)?; if self.frame_idx() != old_frame_idx { span_bug!( @@ -235,7 +209,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { trace!("unwinding: resuming from cleanup"); // By definition, a Resume terminator means // that we're unwinding - self.pop_stack_frame(/* unwinding */ true)?; + self.return_from_current_stack_frame(/* unwinding */ true)?; return Ok(()); } @@ -989,6 +963,46 @@ 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 can't use `pop_stack_frame` as it "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 StackPop { jump, target, destination } = self.pop_stack_frame(false)?; + + assert_eq!(jump, StackPopJump::Normal); + + let StackPopCleanup::Goto { ret, unwind } = target else { + bug!("can't tailcall as root"); + }; + + self.eval_fn_call( + fn_val, + (caller_abi, caller_fn_abi), + args, + with_caller_location, + &destination, + ret, + unwind, + ) + } + fn check_fn_target_features(&self, instance: ty::Instance<'tcx>) -> InterpResult<'tcx, ()> { // Calling functions with `#[target_feature]` is not unsafe on WASM, see #84988 let attrs = self.tcx.codegen_fn_attrs(instance.def_id()); From 6d4995f4e6d3f4fe6ac54943a5b01c3cf752dd5d Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 17 Jun 2024 15:37:33 +0000 Subject: [PATCH 08/15] add miri tests and a fixme --- .../src/interpret/terminator.rs | 4 ++ .../miri/tests/fail/tail_calls/cc-mismatch.rs | 10 +++++ .../tests/fail/tail_calls/cc-mismatch.stderr | 25 ++++++++++++ .../fail/tail_calls/signature-mismatch-arg.rs | 17 ++++++++ .../tail_calls/signature-mismatch-arg.stderr | 17 ++++++++ src/tools/miri/tests/pass/tail_call.rs | 39 +++++++++++++++++++ 6 files changed, 112 insertions(+) create mode 100644 src/tools/miri/tests/fail/tail_calls/cc-mismatch.rs create mode 100644 src/tools/miri/tests/fail/tail_calls/cc-mismatch.stderr create mode 100644 src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.rs create mode 100644 src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.stderr create mode 100644 src/tools/miri/tests/pass/tail_call.rs diff --git a/compiler/rustc_const_eval/src/interpret/terminator.rs b/compiler/rustc_const_eval/src/interpret/terminator.rs index a8d18405b6e56..9f4ca93a3e8d6 100644 --- a/compiler/rustc_const_eval/src/interpret/terminator.rs +++ b/compiler/rustc_const_eval/src/interpret/terminator.rs @@ -992,6 +992,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { bug!("can't tailcall as root"); }; + // FIXME(explicit_tail_calls): + // we should check if both caller&callee can/n't unwind, + // see + self.eval_fn_call( fn_val, (caller_abi, caller_fn_abi), diff --git a/src/tools/miri/tests/fail/tail_calls/cc-mismatch.rs b/src/tools/miri/tests/fail/tail_calls/cc-mismatch.rs new file mode 100644 index 0000000000000..5f00dbf257366 --- /dev/null +++ b/src/tools/miri/tests/fail/tail_calls/cc-mismatch.rs @@ -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::(f) }; + become f(); +} + +extern "C" fn f() {} diff --git a/src/tools/miri/tests/fail/tail_calls/cc-mismatch.stderr b/src/tools/miri/tests/fail/tail_calls/cc-mismatch.stderr new file mode 100644 index 0000000000000..708972e6eff13 --- /dev/null +++ b/src/tools/miri/tests/fail/tail_calls/cc-mismatch.stderr @@ -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 `>::call_once - shim(fn())` at RUSTLIB/core/src/ops/function.rs:LL:CC + = note: inside `std::sys_common::backtrace::__rust_begin_short_backtrace::` at RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::ops::function::impls:: 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 + 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::` 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 + diff --git a/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.rs b/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.rs new file mode 100644 index 0000000000000..3264a74d159e3 --- /dev/null +++ b/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.rs @@ -0,0 +1,17 @@ +#![feature(explicit_tail_calls)] +#![allow(incomplete_features)] + +fn main() { + // FIXME(explicit_tail_calls): + // the error should point to `become f(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::(g) }; + become g(x); +} + +fn g(_: i32) {} diff --git a/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.stderr b/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.stderr new file mode 100644 index 0000000000000..2ecc5674c6537 --- /dev/null +++ b/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.stderr @@ -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 + = 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 + diff --git a/src/tools/miri/tests/pass/tail_call.rs b/src/tools/miri/tests/pass/tail_call.rs new file mode 100644 index 0000000000000..f620070639801 --- /dev/null +++ b/src/tools/miri/tests/pass/tail_call.rs @@ -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) +} From cda6f0c25d6421fd6814bd55a25701205da23585 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 17 Jun 2024 17:04:28 +0000 Subject: [PATCH 09/15] doc fixups from review --- compiler/rustc_const_eval/src/interpret/eval_context.rs | 6 +++--- compiler/rustc_const_eval/src/interpret/terminator.rs | 8 +++----- tests/ui/explicit-tail-calls/ctfe-collatz-multi-rec.rs | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/eval_context.rs b/compiler/rustc_const_eval/src/interpret/eval_context.rs index fd063575e5d98..9517e3d359744 100644 --- a/compiler/rustc_const_eval/src/interpret/eval_context.rs +++ b/compiler/rustc_const_eval/src/interpret/eval_context.rs @@ -819,7 +819,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { Ok(()) } - /// Creates a new stack frame, initializes it and pushes it unto the stack. + /// 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, @@ -902,8 +902,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { Ok(StackPop { jump, target, destination }) } - /// A private helper for [`push_stack_frame`](InterpCx::push_stack_frame). - /// Returns whatever the cleanup was done. + /// 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. diff --git a/compiler/rustc_const_eval/src/interpret/terminator.rs b/compiler/rustc_const_eval/src/interpret/terminator.rs index 9f4ca93a3e8d6..589ab6029f9bc 100644 --- a/compiler/rustc_const_eval/src/interpret/terminator.rs +++ b/compiler/rustc_const_eval/src/interpret/terminator.rs @@ -977,11 +977,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // which pushes a new stack frame, with the return address from // the popped stack frame. // - // Note that we can't use `pop_stack_frame` as it "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. - + // 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 StackPop { jump, target, destination } = self.pop_stack_frame(false)?; diff --git a/tests/ui/explicit-tail-calls/ctfe-collatz-multi-rec.rs b/tests/ui/explicit-tail-calls/ctfe-collatz-multi-rec.rs index 2e6bb0e1ac5b7..86041b669601d 100644 --- a/tests/ui/explicit-tail-calls/ctfe-collatz-multi-rec.rs +++ b/tests/ui/explicit-tail-calls/ctfe-collatz-multi-rec.rs @@ -2,7 +2,7 @@ #![allow(incomplete_features)] #![feature(explicit_tail_calls)] -/// A very unnecessarily complicated "implementation" of the callatz conjecture. +/// 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. From 236352024b25c2e834c7a73fc9219ddc84c0bcf9 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 17 Jun 2024 17:25:14 +0000 Subject: [PATCH 10/15] make `StackPop` field names less confusing --- .../src/interpret/eval_context.rs | 38 +++++++++++-------- .../rustc_const_eval/src/interpret/machine.rs | 11 +++--- .../rustc_const_eval/src/interpret/mod.rs | 2 +- .../src/interpret/terminator.rs | 11 +++--- src/tools/miri/src/machine.rs | 2 +- src/tools/miri/src/shims/panic.rs | 6 +-- 6 files changed, 39 insertions(+), 31 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/eval_context.rs b/compiler/rustc_const_eval/src/interpret/eval_context.rs index 9517e3d359744..bc15e41343fcd 100644 --- a/compiler/rustc_const_eval/src/interpret/eval_context.rs +++ b/compiler/rustc_const_eval/src/interpret/eval_context.rs @@ -26,8 +26,8 @@ use rustc_target::abi::{call::FnAbi, Align, HasDataLayout, Size, TargetDataLayou use super::{ err_inval, throw_inval, throw_ub, throw_ub_custom, throw_unsup, GlobalId, Immediate, InterpErrorInfo, InterpResult, MPlaceTy, Machine, MemPlace, MemPlaceMeta, Memory, MemoryKind, - OpTy, Operand, Place, PlaceTy, Pointer, PointerArithmetic, Projectable, Provenance, Scalar, - StackPopJump, + OpTy, Operand, Place, PlaceTy, Pointer, PointerArithmetic, Projectable, Provenance, + ReturnAction, Scalar, }; use crate::errors; use crate::util; @@ -161,9 +161,15 @@ pub enum StackPopCleanup { /// Return type of [`InterpCx::pop_stack_frame`]. pub struct StackPop<'tcx, Prov: Provenance> { - pub jump: StackPopJump, - pub target: StackPopCleanup, - pub destination: MPlaceTy<'tcx, Prov>, + /// 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 @@ -890,16 +896,16 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let frame = self.stack_mut().pop().expect("tried to pop a stack frame, but there were none"); - let target = frame.return_to_block; - let destination = frame.return_place.clone(); + let return_to_block = frame.return_to_block; + let return_place = frame.return_place.clone(); - let jump = if cleanup { + let return_action = if cleanup { M::after_stack_pop(self, frame, unwinding)? } else { - StackPopJump::NoCleanup + ReturnAction::NoCleanup }; - Ok(StackPop { jump, target, destination }) + Ok(StackPop { return_action, return_to_block, return_place }) } /// A private helper for [`pop_stack_frame`](InterpCx::pop_stack_frame). @@ -1042,13 +1048,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // Report error from return value copy, if any. copy_ret_result?; - match frame.jump { - StackPopJump::Normal => {} - StackPopJump::NoJump => { + match frame.return_action { + ReturnAction::Normal => {} + ReturnAction::NoJump => { // The hook already did everything. return Ok(()); } - StackPopJump::NoCleanup => { + ReturnAction::NoCleanup => { // If we are not doing cleanup, also skip everything else. assert!(self.stack().is_empty(), "only the topmost frame should ever be leaked"); assert!(!unwinding, "tried to skip cleanup during unwinding"); @@ -1060,7 +1066,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // Normal return, figure out where to jump. if unwinding { // Follow the unwind edge. - let unwind = match frame.target { + let unwind = match frame.return_to_block { StackPopCleanup::Goto { unwind, .. } => unwind, StackPopCleanup::Root { .. } => { panic!("encountered StackPopCleanup::Root when unwinding!") @@ -1070,7 +1076,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { self.unwind_to_block(unwind) } else { // Follow the normal return edge. - match frame.target { + match frame.return_to_block { StackPopCleanup::Goto { ret, .. } => self.return_to_block(ret), StackPopCleanup::Root { .. } => { assert!( diff --git a/compiler/rustc_const_eval/src/interpret/machine.rs b/compiler/rustc_const_eval/src/interpret/machine.rs index b8a651a744aa6..7f2e9ce06a5a0 100644 --- a/compiler/rustc_const_eval/src/interpret/machine.rs +++ b/compiler/rustc_const_eval/src/interpret/machine.rs @@ -23,10 +23,11 @@ use super::{ MemoryKind, Misalignment, OpTy, PlaceTy, Pointer, Provenance, }; -/// Data returned by Machine::stack_pop, -/// to provide further control over the popping of the stack frame +/// Data returned by [`Machine::after_stack_pop`], and consumed by +/// [`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)] -pub enum StackPopJump { +pub enum ReturnAction { /// Indicates that no special handling should be /// done - we'll either return normally or unwind /// based on the terminator for the function @@ -525,10 +526,10 @@ pub trait Machine<'tcx>: Sized { _ecx: &mut InterpCx<'tcx, Self>, _frame: Frame<'tcx, Self::Provenance, Self::FrameExtra>, unwinding: bool, - ) -> InterpResult<'tcx, StackPopJump> { + ) -> InterpResult<'tcx, ReturnAction> { // By default, we do not support unwinding from panics assert!(!unwinding); - Ok(StackPopJump::Normal) + Ok(ReturnAction::Normal) } /// Called immediately after actual memory was allocated for a local diff --git a/compiler/rustc_const_eval/src/interpret/mod.rs b/compiler/rustc_const_eval/src/interpret/mod.rs index 7d7b421f86935..f703c6fbe3ea3 100644 --- a/compiler/rustc_const_eval/src/interpret/mod.rs +++ b/compiler/rustc_const_eval/src/interpret/mod.rs @@ -26,7 +26,7 @@ pub use self::intern::{ intern_const_alloc_for_constprop, intern_const_alloc_recursive, HasStaticRootDefId, InternKind, 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::operand::{ImmTy, Immediate, OpTy, Readable}; pub use self::place::{MPlaceTy, MemPlaceMeta, PlaceTy, Writeable}; diff --git a/compiler/rustc_const_eval/src/interpret/terminator.rs b/compiler/rustc_const_eval/src/interpret/terminator.rs index 589ab6029f9bc..8886d7d8af571 100644 --- a/compiler/rustc_const_eval/src/interpret/terminator.rs +++ b/compiler/rustc_const_eval/src/interpret/terminator.rs @@ -27,7 +27,7 @@ use super::{ }; use crate::{ fluent_generated as fluent, - interpret::{eval_context::StackPop, StackPopJump}, + interpret::{eval_context::StackPop, ReturnAction}, }; /// An argment passed to a function. @@ -982,11 +982,12 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // only the tail called function should return to the current return block. M::before_stack_pop(self, self.frame())?; - let StackPop { jump, target, destination } = self.pop_stack_frame(false)?; + let StackPop { return_action, return_to_block, return_place } = + self.pop_stack_frame(false)?; - assert_eq!(jump, StackPopJump::Normal); + assert_eq!(return_action, ReturnAction::Normal); - let StackPopCleanup::Goto { ret, unwind } = target else { + let StackPopCleanup::Goto { ret, unwind } = return_to_block else { bug!("can't tailcall as root"); }; @@ -999,7 +1000,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { (caller_abi, caller_fn_abi), args, with_caller_location, - &destination, + &return_place, ret, unwind, ) diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index 0d91279f9f4c4..d4d50ebdd14a9 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -1434,7 +1434,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> { ecx: &mut InterpCx<'tcx, Self>, frame: Frame<'tcx, Provenance, FrameExtra<'tcx>>, unwinding: bool, - ) -> InterpResult<'tcx, StackPopJump> { + ) -> InterpResult<'tcx, ReturnAction> { if frame.extra.is_user_relevant { // 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. diff --git a/src/tools/miri/src/shims/panic.rs b/src/tools/miri/src/shims/panic.rs index ef832f5bbbd15..306dce5edcd3d 100644 --- a/src/tools/miri/src/shims/panic.rs +++ b/src/tools/miri/src/shims/panic.rs @@ -113,7 +113,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { &mut self, mut extra: FrameExtra<'tcx>, unwinding: bool, - ) -> InterpResult<'tcx, StackPopJump> { + ) -> InterpResult<'tcx, ReturnAction> { let this = self.eval_context_mut(); 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! - Ok(StackPopJump::NoJump) + Ok(ReturnAction::NoJump) } else { - Ok(StackPopJump::Normal) + Ok(ReturnAction::Normal) } } From dd5a447b5abaabcefd1b8e6e03470aa345226e1e Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 17 Jun 2024 17:36:26 +0000 Subject: [PATCH 11/15] Do renames proposed by review --- .../rustc_const_eval/src/interpret/eval_context.rs | 14 +++++++------- .../rustc_const_eval/src/interpret/terminator.rs | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/eval_context.rs b/compiler/rustc_const_eval/src/interpret/eval_context.rs index bc15e41343fcd..23ade4b0106dd 100644 --- a/compiler/rustc_const_eval/src/interpret/eval_context.rs +++ b/compiler/rustc_const_eval/src/interpret/eval_context.rs @@ -160,7 +160,7 @@ pub enum StackPopCleanup { } /// Return type of [`InterpCx::pop_stack_frame`]. -pub struct StackPop<'tcx, Prov: Provenance> { +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, @@ -890,7 +890,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { pub fn pop_stack_frame( &mut self, unwinding: bool, - ) -> InterpResult<'tcx, StackPop<'tcx, M::Provenance>> { + ) -> InterpResult<'tcx, StackPopInfo<'tcx, M::Provenance>> { let cleanup = self.cleanup_current_frame_locals()?; let frame = @@ -905,7 +905,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { ReturnAction::NoCleanup }; - Ok(StackPop { return_action, return_to_block, return_place }) + Ok(StackPopInfo { return_action, return_to_block, return_place }) } /// A private helper for [`pop_stack_frame`](InterpCx::pop_stack_frame). @@ -1043,12 +1043,12 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { }; // All right, now it is time to actually pop the frame. - let frame = self.pop_stack_frame(unwinding)?; + let stack_pop_info = self.pop_stack_frame(unwinding)?; // Report error from return value copy, if any. copy_ret_result?; - match frame.return_action { + match stack_pop_info.return_action { ReturnAction::Normal => {} ReturnAction::NoJump => { // The hook already did everything. @@ -1066,7 +1066,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // Normal return, figure out where to jump. if unwinding { // Follow the unwind edge. - let unwind = match frame.return_to_block { + let unwind = match stack_pop_info.return_to_block { StackPopCleanup::Goto { unwind, .. } => unwind, StackPopCleanup::Root { .. } => { panic!("encountered StackPopCleanup::Root when unwinding!") @@ -1076,7 +1076,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { self.unwind_to_block(unwind) } else { // Follow the normal return edge. - match frame.return_to_block { + match stack_pop_info.return_to_block { StackPopCleanup::Goto { ret, .. } => self.return_to_block(ret), StackPopCleanup::Root { .. } => { assert!( diff --git a/compiler/rustc_const_eval/src/interpret/terminator.rs b/compiler/rustc_const_eval/src/interpret/terminator.rs index 8886d7d8af571..76051f4651f56 100644 --- a/compiler/rustc_const_eval/src/interpret/terminator.rs +++ b/compiler/rustc_const_eval/src/interpret/terminator.rs @@ -27,7 +27,7 @@ use super::{ }; use crate::{ fluent_generated as fluent, - interpret::{eval_context::StackPop, ReturnAction}, + interpret::{eval_context::StackPopInfo, ReturnAction}, }; /// An argment passed to a function. @@ -982,7 +982,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // only the tail called function should return to the current return block. M::before_stack_pop(self, self.frame())?; - let StackPop { return_action, return_to_block, return_place } = + let StackPopInfo { return_action, return_to_block, return_place } = self.pop_stack_frame(false)?; assert_eq!(return_action, ReturnAction::Normal); From 54e11cf378d5f3a423d98b9a8bcbc6c25078995a Mon Sep 17 00:00:00 2001 From: Maybe Lapkin Date: Sun, 7 Jul 2024 17:01:28 +0200 Subject: [PATCH 12/15] add an assertion that machine hook doesn't return NoCleanup --- compiler/rustc_const_eval/src/interpret/eval_context.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/eval_context.rs b/compiler/rustc_const_eval/src/interpret/eval_context.rs index 23ade4b0106dd..6d3e5ea103148 100644 --- a/compiler/rustc_const_eval/src/interpret/eval_context.rs +++ b/compiler/rustc_const_eval/src/interpret/eval_context.rs @@ -899,10 +899,12 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let return_to_block = frame.return_to_block; let return_place = frame.return_place.clone(); - let return_action = if cleanup { - M::after_stack_pop(self, frame, unwinding)? + let return_action; + if cleanup { + return_action = M::after_stack_pop(self, frame, unwinding)?; + assert_ne!(return_action, ReturnAction::NoCleanup); } else { - ReturnAction::NoCleanup + return_action = ReturnAction::NoCleanup; }; Ok(StackPopInfo { return_action, return_to_block, return_place }) From 7fd0c55a1a85b42b02c378ba5472243cc17211dd Mon Sep 17 00:00:00 2001 From: Maybe Lapkin Date: Sun, 7 Jul 2024 18:04:29 +0200 Subject: [PATCH 13/15] Fix conflicts after rebase - r-l/r 126784 - r-l/r 127113 - r-l/miri 3562 --- compiler/rustc_middle/src/mir/syntax.rs | 2 +- compiler/rustc_mir_build/src/build/expr/stmt.rs | 2 +- compiler/rustc_mir_transform/src/cost_checker.rs | 7 ++++++- src/tools/miri/tests/fail/tail_calls/cc-mismatch.stderr | 2 +- .../tests/fail/tail_calls/signature-mismatch-arg.stderr | 2 +- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index deaa1259f6b73..620fa962d791f 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -767,7 +767,7 @@ pub enum TerminatorKind<'tcx> { /// These are owned by the callee, which is free to modify them. /// This allows the memory occupied by "by-value" arguments to be /// reused across function calls without duplicating the contents. - args: Vec>>, + args: Box<[Spanned>]>, // 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)` diff --git a/compiler/rustc_mir_build/src/build/expr/stmt.rs b/compiler/rustc_mir_build/src/build/expr/stmt.rs index 7d2c32e000b90..88b76c46c90bf 100644 --- a/compiler/rustc_mir_build/src/build/expr/stmt.rs +++ b/compiler/rustc_mir_build/src/build/expr/stmt.rs @@ -106,7 +106,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { this.in_scope((region_scope, source_info), lint_level, |this| { let fun = unpack!(block = this.as_local_operand(block, fun)); - let args: Vec<_> = args + let args: Box<[_]> = args .into_iter() .copied() .map(|arg| Spanned { diff --git a/compiler/rustc_mir_transform/src/cost_checker.rs b/compiler/rustc_mir_transform/src/cost_checker.rs index 3333bebff3a6e..a1c1422912ee3 100644 --- a/compiler/rustc_mir_transform/src/cost_checker.rs +++ b/compiler/rustc_mir_transform/src/cost_checker.rs @@ -40,7 +40,9 @@ impl<'b, 'tcx> CostChecker<'b, 'tcx> { fn is_call_like(bbd: &BasicBlockData<'_>) -> bool { use TerminatorKind::*; match bbd.terminator().kind { - Call { .. } | Drop { .. } | Assert { .. } | InlineAsm { .. } => true, + Call { .. } | TailCall { .. } | Drop { .. } | Assert { .. } | InlineAsm { .. } => { + true + } Goto { .. } | SwitchInt { .. } @@ -137,6 +139,9 @@ impl<'tcx> Visitor<'tcx> for CostChecker<'_, 'tcx> { self.penalty += LANDINGPAD_PENALTY; } } + TerminatorKind::TailCall { .. } => { + self.penalty += CALL_PENALTY; + } TerminatorKind::SwitchInt { discr, targets } => { if discr.constant().is_some() { // Not only will this become a `Goto`, but likely other diff --git a/src/tools/miri/tests/fail/tail_calls/cc-mismatch.stderr b/src/tools/miri/tests/fail/tail_calls/cc-mismatch.stderr index 708972e6eff13..b157e9f0b211a 100644 --- a/src/tools/miri/tests/fail/tail_calls/cc-mismatch.stderr +++ b/src/tools/miri/tests/fail/tail_calls/cc-mismatch.stderr @@ -8,7 +8,7 @@ LL | extern "rust-call" fn call_once(self, args: Args) -> Self::Output; = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information = note: BACKTRACE: = note: inside `>::call_once - shim(fn())` at RUSTLIB/core/src/ops/function.rs:LL:CC - = note: inside `std::sys_common::backtrace::__rust_begin_short_backtrace::` at RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC + = note: inside `std::sys::backtrace::__rust_begin_short_backtrace::` 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:: 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 diff --git a/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.stderr b/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.stderr index 2ecc5674c6537..8823ab9b97059 100644 --- a/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.stderr +++ b/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.stderr @@ -7,7 +7,7 @@ LL | f(0); = 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 + = 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 From 39eaefc15db286dc295134f0c65704e90eae1d0e Mon Sep 17 00:00:00 2001 From: Maybe Lapkin Date: Sun, 7 Jul 2024 20:16:48 +0200 Subject: [PATCH 14/15] Fixup conflict with r-l/r/126567 --- compiler/rustc_const_eval/src/interpret/terminator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_const_eval/src/interpret/terminator.rs b/compiler/rustc_const_eval/src/interpret/terminator.rs index 76051f4651f56..25f6bd640554e 100644 --- a/compiler/rustc_const_eval/src/interpret/terminator.rs +++ b/compiler/rustc_const_eval/src/interpret/terminator.rs @@ -54,7 +54,7 @@ struct EvaluatedCalleeAndArgs<'tcx, M: Machine<'tcx>> { args: Vec>, fn_sig: ty::FnSig<'tcx>, fn_abi: &'tcx FnAbi<'tcx, Ty<'tcx>>, - /// True if the function is marked as `#[track_caller]` ([`ty::InstanceDef::requires_caller_location`]) + /// True if the function is marked as `#[track_caller]` ([`ty::InstanceKind::requires_caller_location`]) with_caller_location: bool, } From 14e5d5fbee637af09759f924e14f97685e5c24ea Mon Sep 17 00:00:00 2001 From: Maybe Lapkin Date: Sun, 7 Jul 2024 20:18:42 +0200 Subject: [PATCH 15/15] Fixup a typo in a comment in a test --- src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.rs b/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.rs index 3264a74d159e3..6df132d325591 100644 --- a/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.rs +++ b/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.rs @@ -3,7 +3,7 @@ fn main() { // FIXME(explicit_tail_calls): - // the error should point to `become f(x)`, + // 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