Auto merge of #113128 - WaffleLapkin:become_trully_unuwuable, r=oli-obk,RalfJung
Support tail calls in mir via `TerminatorKind::TailCall` This is one of the interesting bits in tail call implementation — MIR support. This adds a new `TerminatorKind` which represents a tail call: ```rust TailCall { func: Operand<'tcx>, args: Vec<Operand<'tcx>>, fn_span: Span, }, ``` *Structurally* this is very similar to a normal `Call` but is missing a few fields: - `destination` — tail calls don't write to destination, instead they pass caller's destination to the callee (such that eventual `return` will write to the caller of the function that used tail call) - `target` — similarly to `destination` tail calls pass the caller's return address to the callee, so there is nothing to do - `unwind` — I _think_ this is applicable too, although it's a bit confusing - `call_source` — `become` forbids operators and is not created as a lowering of something else; tail calls always come from HIR (at least for now) It might be helpful to read the interpreter implementation to understand what `TailCall` means exactly, although I've tried documenting it too. ----- There are a few `FIXME`-questions still left, ideally we'd be able to answer them during review ':) ----- r? `@oli-obk` cc `@scottmcm` `@DrMeepster` `@JakobDegen`
This commit is contained in:
commit
9af6fee87d
75 changed files with 2385 additions and 173 deletions
|
@ -135,6 +135,8 @@ impl<'mir, 'tcx> Qualifs<'mir, 'tcx> {
|
|||
ccx: &'mir ConstCx<'mir, 'tcx>,
|
||||
tainted_by_errors: Option<ErrorGuaranteed>,
|
||||
) -> ConstQualifs {
|
||||
// FIXME(explicit_tail_calls): uhhhh I think we can return without return now, does it change anything
|
||||
|
||||
// Find the `Return` terminator if one exists.
|
||||
//
|
||||
// If no `Return` terminator exists, this MIR is divergent. Just return the conservative
|
||||
|
@ -711,7 +713,14 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
|
|||
self.super_terminator(terminator, location);
|
||||
|
||||
match &terminator.kind {
|
||||
TerminatorKind::Call { func, args, fn_span, call_source, .. } => {
|
||||
TerminatorKind::Call { func, args, fn_span, .. }
|
||||
| TerminatorKind::TailCall { func, args, fn_span, .. } => {
|
||||
let call_source = match terminator.kind {
|
||||
TerminatorKind::Call { call_source, .. } => call_source,
|
||||
TerminatorKind::TailCall { .. } => CallSource::Normal,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let ConstCx { tcx, body, param_env, .. } = *self.ccx;
|
||||
let caller = self.def_id();
|
||||
|
||||
|
@ -783,7 +792,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
|
|||
callee,
|
||||
args: fn_args,
|
||||
span: *fn_span,
|
||||
call_source: *call_source,
|
||||
call_source,
|
||||
feature: Some(if tcx.features().const_trait_impl {
|
||||
sym::effects
|
||||
} else {
|
||||
|
@ -830,7 +839,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
|
|||
callee,
|
||||
args: fn_args,
|
||||
span: *fn_span,
|
||||
call_source: *call_source,
|
||||
call_source,
|
||||
feature: None,
|
||||
});
|
||||
return;
|
||||
|
|
|
@ -108,6 +108,7 @@ impl<'tcx> Visitor<'tcx> for CheckLiveDrops<'_, 'tcx> {
|
|||
|
||||
mir::TerminatorKind::UnwindTerminate(_)
|
||||
| mir::TerminatorKind::Call { .. }
|
||||
| mir::TerminatorKind::TailCall { .. }
|
||||
| mir::TerminatorKind::Assert { .. }
|
||||
| mir::TerminatorKind::FalseEdge { .. }
|
||||
| mir::TerminatorKind::FalseUnwind { .. }
|
||||
|
|
|
@ -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;
|
||||
|
@ -159,6 +159,19 @@ pub enum StackPopCleanup {
|
|||
Root { cleanup: bool },
|
||||
}
|
||||
|
||||
/// Return type of [`InterpCx::pop_stack_frame`].
|
||||
pub struct StackPopInfo<'tcx, Prov: Provenance> {
|
||||
/// Additional information about the action to be performed when returning from the popped
|
||||
/// stack frame.
|
||||
pub return_action: ReturnAction,
|
||||
|
||||
/// [`return_to_block`](Frame::return_to_block) of the popped stack frame.
|
||||
pub return_to_block: StackPopCleanup,
|
||||
|
||||
/// [`return_place`](Frame::return_place) of the popped stack frame.
|
||||
pub return_place: MPlaceTy<'tcx, Prov>,
|
||||
}
|
||||
|
||||
/// State of a local variable including a memoized layout
|
||||
#[derive(Clone)]
|
||||
pub struct LocalState<'tcx, Prov: Provenance = CtfeProvenance> {
|
||||
|
@ -803,14 +816,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 onto the stack.
|
||||
/// A private helper for [`push_stack_frame`](InterpCx::push_stack_frame).
|
||||
fn push_new_stack_frame(
|
||||
&mut self,
|
||||
instance: ty::Instance<'tcx>,
|
||||
body: &'tcx mir::Body<'tcx>,
|
||||
return_to_block: StackPopCleanup,
|
||||
return_place: MPlaceTy<'tcx, M::Provenance>,
|
||||
) -> InterpResult<'tcx> {
|
||||
let dead_local = LocalState { value: LocalValue::Dead, layout: Cell::new(None) };
|
||||
let 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 +849,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 +878,61 @@ 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, StackPopInfo<'tcx, M::Provenance>> {
|
||||
let cleanup = self.cleanup_current_frame_locals()?;
|
||||
|
||||
let frame =
|
||||
self.stack_mut().pop().expect("tried to pop a stack frame, but there were none");
|
||||
|
||||
let return_to_block = frame.return_to_block;
|
||||
let return_place = frame.return_place.clone();
|
||||
|
||||
let return_action;
|
||||
if cleanup {
|
||||
return_action = M::after_stack_pop(self, frame, unwinding)?;
|
||||
assert_ne!(return_action, ReturnAction::NoCleanup);
|
||||
} else {
|
||||
return_action = ReturnAction::NoCleanup;
|
||||
};
|
||||
|
||||
Ok(StackPopInfo { return_action, return_to_block, return_place })
|
||||
}
|
||||
|
||||
/// A private helper for [`pop_stack_frame`](InterpCx::pop_stack_frame).
|
||||
/// Returns `true` if cleanup has been done, `false` otherwise.
|
||||
fn cleanup_current_frame_locals(&mut self) -> InterpResult<'tcx, bool> {
|
||||
// Cleanup: deallocate locals.
|
||||
// Usually we want to clean up (deallocate locals), but in a few rare cases we don't.
|
||||
// We do this while the frame is still on the stack, so errors point to the callee.
|
||||
let return_to_block = self.frame().return_to_block;
|
||||
let cleanup = match return_to_block {
|
||||
StackPopCleanup::Goto { .. } => true,
|
||||
StackPopCleanup::Root { cleanup, .. } => cleanup,
|
||||
};
|
||||
|
||||
if cleanup {
|
||||
// We need to take the locals out, since we need to mutate while iterating.
|
||||
let locals = mem::take(&mut self.frame_mut().locals);
|
||||
for local in &locals {
|
||||
self.deallocate_local(local.value)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(cleanup)
|
||||
}
|
||||
|
||||
/// Jump to the given block.
|
||||
#[inline]
|
||||
pub fn go_to_block(&mut self, target: mir::BasicBlock) {
|
||||
|
@ -886,7 +980,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||
}
|
||||
|
||||
/// Pops the current frame from the stack, deallocating the
|
||||
/// 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 +993,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 +1044,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 stack_pop_info = 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 stack_pop_info.return_action {
|
||||
ReturnAction::Normal => {}
|
||||
ReturnAction::NoJump => {
|
||||
// The hook already did everything.
|
||||
return Ok(());
|
||||
}
|
||||
ReturnAction::NoCleanup => {
|
||||
// If we are not doing cleanup, also skip everything else.
|
||||
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 stack_pop_info.return_to_block {
|
||||
StackPopCleanup::Goto { unwind, .. } => unwind,
|
||||
StackPopCleanup::Root { .. } => {
|
||||
panic!("encountered StackPopCleanup::Root when unwinding!")
|
||||
|
@ -995,7 +1078,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 stack_pop_info.return_to_block {
|
||||
StackPopCleanup::Goto { ret, .. } => self.return_to_block(ret),
|
||||
StackPopCleanup::Root { .. } => {
|
||||
assert!(
|
||||
|
|
|
@ -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
|
||||
|
@ -36,6 +37,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
|
||||
|
@ -522,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
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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::StackPopInfo, ReturnAction},
|
||||
};
|
||||
|
||||
/// An argment passed to a function.
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -47,6 +49,15 @@ impl<'tcx, Prov: Provenance> FnArg<'tcx, Prov> {
|
|||
}
|
||||
}
|
||||
|
||||
struct EvaluatedCalleeAndArgs<'tcx, M: Machine<'tcx>> {
|
||||
callee: FnVal<'tcx, M::ExtraFnVal>,
|
||||
args: Vec<FnArg<'tcx, M::Provenance>>,
|
||||
fn_sig: ty::FnSig<'tcx>,
|
||||
fn_abi: &'tcx FnAbi<'tcx, Ty<'tcx>>,
|
||||
/// True if the function is marked as `#[track_caller]` ([`ty::InstanceKind::requires_caller_location`])
|
||||
with_caller_location: bool,
|
||||
}
|
||||
|
||||
impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||
/// Make a copy of the given fn_arg. Any `InPlace` are degenerated to copies, no protection of the
|
||||
/// original memory occurs.
|
||||
|
@ -84,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),
|
||||
|
@ -124,40 +135,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,
|
||||
|
@ -172,6 +156,22 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||
}
|
||||
}
|
||||
|
||||
TailCall { ref func, ref args, fn_span: _ } => {
|
||||
let old_frame_idx = self.frame_idx();
|
||||
|
||||
let EvaluatedCalleeAndArgs { callee, args, fn_sig, fn_abi, with_caller_location } =
|
||||
self.eval_callee_and_args(terminator, func, args)?;
|
||||
|
||||
self.eval_fn_tail_call(callee, (fn_sig.abi, fn_abi), &args, with_caller_location)?;
|
||||
|
||||
if self.frame_idx() != old_frame_idx {
|
||||
span_bug!(
|
||||
terminator.source_info.span,
|
||||
"evaluating this tail call pushed a new stack frame"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Drop { place, target, unwind, replace: _ } => {
|
||||
let place = self.eval_place(place)?;
|
||||
let instance = Instance::resolve_drop_in_place(*self.tcx, place.layout.ty);
|
||||
|
@ -209,7 +209,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||
trace!("unwinding: resuming from cleanup");
|
||||
// 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(());
|
||||
}
|
||||
|
||||
|
@ -514,6 +514,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<mir::Operand<'tcx>>],
|
||||
) -> InterpResult<'tcx, EvaluatedCalleeAndArgs<'tcx, M>> {
|
||||
let func = self.eval_operand(func, None)?;
|
||||
let args = self.eval_fn_call_arguments(args)?;
|
||||
|
||||
let fn_sig_binder = func.layout.ty.fn_sig(*self.tcx);
|
||||
let fn_sig = self.tcx.normalize_erasing_late_bound_regions(self.param_env, fn_sig_binder);
|
||||
let extra_args = &args[fn_sig.inputs().len()..];
|
||||
let extra_args =
|
||||
self.tcx.mk_type_list_from_iter(extra_args.iter().map(|arg| arg.layout().ty));
|
||||
|
||||
let (callee, fn_abi, with_caller_location) = match *func.layout.ty.kind() {
|
||||
ty::FnPtr(_sig) => {
|
||||
let fn_ptr = self.read_pointer(&func)?;
|
||||
let fn_val = self.get_ptr_fn(fn_ptr)?;
|
||||
(fn_val, self.fn_abi_of_fn_ptr(fn_sig_binder, extra_args)?, false)
|
||||
}
|
||||
ty::FnDef(def_id, args) => {
|
||||
let instance = self.resolve(def_id, args)?;
|
||||
(
|
||||
FnVal::Instance(instance),
|
||||
self.fn_abi_of_instance(instance, extra_args)?,
|
||||
instance.def.requires_caller_location(*self.tcx),
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
span_bug!(terminator.source_info.span, "invalid callee of type {}", func.layout.ty)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(EvaluatedCalleeAndArgs { callee, args, fn_sig, fn_abi, with_caller_location })
|
||||
}
|
||||
|
||||
/// Call this function -- pushing the stack frame and initializing the arguments.
|
||||
///
|
||||
/// `caller_fn_abi` is used to determine if all the arguments are passed the proper way.
|
||||
|
@ -924,6 +963,49 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn eval_fn_tail_call(
|
||||
&mut self,
|
||||
fn_val: FnVal<'tcx, M::ExtraFnVal>,
|
||||
(caller_abi, caller_fn_abi): (Abi, &FnAbi<'tcx, Ty<'tcx>>),
|
||||
args: &[FnArg<'tcx, M::Provenance>],
|
||||
with_caller_location: bool,
|
||||
) -> InterpResult<'tcx> {
|
||||
trace!("eval_fn_call: {:#?}", fn_val);
|
||||
|
||||
// This is the "canonical" implementation of tails calls,
|
||||
// a pop of the current stack frame, followed by a normal call
|
||||
// which pushes a new stack frame, with the return address from
|
||||
// the popped stack frame.
|
||||
//
|
||||
// Note that we are using `pop_stack_frame` and not `return_from_current_stack_frame`,
|
||||
// as the latter "executes" the goto to the return block, but we don't want to,
|
||||
// only the tail called function should return to the current return block.
|
||||
M::before_stack_pop(self, self.frame())?;
|
||||
|
||||
let StackPopInfo { return_action, return_to_block, return_place } =
|
||||
self.pop_stack_frame(false)?;
|
||||
|
||||
assert_eq!(return_action, ReturnAction::Normal);
|
||||
|
||||
let StackPopCleanup::Goto { ret, unwind } = return_to_block else {
|
||||
bug!("can't tailcall as root");
|
||||
};
|
||||
|
||||
// FIXME(explicit_tail_calls):
|
||||
// we should check if both caller&callee can/n't unwind,
|
||||
// see <https://github.com/rust-lang/rust/pull/113128#issuecomment-1614979803>
|
||||
|
||||
self.eval_fn_call(
|
||||
fn_val,
|
||||
(caller_abi, caller_fn_abi),
|
||||
args,
|
||||
with_caller_location,
|
||||
&return_place,
|
||||
ret,
|
||||
unwind,
|
||||
)
|
||||
}
|
||||
|
||||
fn check_fn_target_features(&self, instance: ty::Instance<'tcx>) -> InterpResult<'tcx, ()> {
|
||||
// Calling functions with `#[target_feature]` is not unsafe on WASM, see #84988
|
||||
let attrs = self.tcx.codegen_fn_attrs(instance.def_id());
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue