tail calls: fix copying non-scalar arguments to callee
This commit is contained in:
parent
dfbfbf785f
commit
ad5108eaad
9 changed files with 98 additions and 92 deletions
|
|
@ -236,7 +236,7 @@ impl<'tcx> CompileTimeInterpCx<'tcx> {
|
||||||
if self.tcx.is_lang_item(def_id, LangItem::PanicDisplay)
|
if self.tcx.is_lang_item(def_id, LangItem::PanicDisplay)
|
||||||
|| self.tcx.is_lang_item(def_id, LangItem::BeginPanic)
|
|| self.tcx.is_lang_item(def_id, LangItem::BeginPanic)
|
||||||
{
|
{
|
||||||
let args = self.copy_fn_args(args);
|
let args = Self::copy_fn_args(args);
|
||||||
// &str or &&str
|
// &str or &&str
|
||||||
assert!(args.len() == 1);
|
assert!(args.len() == 1);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ use tracing::{info, instrument, trace};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
CtfeProvenance, FnVal, ImmTy, InterpCx, InterpResult, MPlaceTy, Machine, OpTy, PlaceTy,
|
CtfeProvenance, FnVal, ImmTy, InterpCx, InterpResult, MPlaceTy, Machine, OpTy, PlaceTy,
|
||||||
Projectable, Provenance, ReturnAction, ReturnContinuation, Scalar, StackPopInfo, interp_ok,
|
Projectable, Provenance, ReturnAction, ReturnContinuation, Scalar, interp_ok, throw_ub,
|
||||||
throw_ub, throw_ub_custom,
|
throw_ub_custom,
|
||||||
};
|
};
|
||||||
use crate::enter_trace_span;
|
use crate::enter_trace_span;
|
||||||
use crate::interpret::EnteredTraceSpan;
|
use crate::interpret::EnteredTraceSpan;
|
||||||
|
|
@ -43,25 +43,22 @@ impl<'tcx, Prov: Provenance> FnArg<'tcx, Prov> {
|
||||||
FnArg::InPlace(mplace) => &mplace.layout,
|
FnArg::InPlace(mplace) => &mplace.layout,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|
||||||
/// Make a copy of the given fn_arg. Any `InPlace` are degenerated to copies, no protection of the
|
/// Make a copy of the given fn_arg. Any `InPlace` are degenerated to copies, no protection of the
|
||||||
/// original memory occurs.
|
/// original memory occurs.
|
||||||
pub fn copy_fn_arg(&self, arg: &FnArg<'tcx, M::Provenance>) -> OpTy<'tcx, M::Provenance> {
|
pub fn copy_fn_arg(&self) -> OpTy<'tcx, Prov> {
|
||||||
match arg {
|
match self {
|
||||||
FnArg::Copy(op) => op.clone(),
|
FnArg::Copy(op) => op.clone(),
|
||||||
FnArg::InPlace(mplace) => mplace.clone().into(),
|
FnArg::InPlace(mplace) => mplace.clone().into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
/// Make a copy of the given fn_args. Any `InPlace` are degenerated to copies, no protection of the
|
/// Make a copy of the given fn_args. Any `InPlace` are degenerated to copies, no protection of the
|
||||||
/// original memory occurs.
|
/// original memory occurs.
|
||||||
pub fn copy_fn_args(
|
pub fn copy_fn_args(args: &[FnArg<'tcx, M::Provenance>]) -> Vec<OpTy<'tcx, M::Provenance>> {
|
||||||
&self,
|
args.iter().map(|fn_arg| fn_arg.copy_fn_arg()).collect()
|
||||||
args: &[FnArg<'tcx, M::Provenance>],
|
|
||||||
) -> Vec<OpTy<'tcx, M::Provenance>> {
|
|
||||||
args.iter().map(|fn_arg| self.copy_fn_arg(fn_arg)).collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper function for argument untupling.
|
/// Helper function for argument untupling.
|
||||||
|
|
@ -319,7 +316,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
// We work with a copy of the argument for now; if this is in-place argument passing, we
|
// We work with a copy of the argument for now; if this is in-place argument passing, we
|
||||||
// will later protect the source it comes from. This means the callee cannot observe if we
|
// will later protect the source it comes from. This means the callee cannot observe if we
|
||||||
// did in-place of by-copy argument passing, except for pointer equality tests.
|
// did in-place of by-copy argument passing, except for pointer equality tests.
|
||||||
let caller_arg_copy = self.copy_fn_arg(caller_arg);
|
let caller_arg_copy = caller_arg.copy_fn_arg();
|
||||||
if !already_live {
|
if !already_live {
|
||||||
let local = callee_arg.as_local().unwrap();
|
let local = callee_arg.as_local().unwrap();
|
||||||
let meta = caller_arg_copy.meta();
|
let meta = caller_arg_copy.meta();
|
||||||
|
|
@ -616,7 +613,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
if let Some(fallback) = M::call_intrinsic(
|
if let Some(fallback) = M::call_intrinsic(
|
||||||
self,
|
self,
|
||||||
instance,
|
instance,
|
||||||
&self.copy_fn_args(args),
|
&Self::copy_fn_args(args),
|
||||||
destination,
|
destination,
|
||||||
target,
|
target,
|
||||||
unwind,
|
unwind,
|
||||||
|
|
@ -703,7 +700,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
// An `InPlace` does nothing here, we keep the original receiver intact. We can't
|
// An `InPlace` does nothing here, we keep the original receiver intact. We can't
|
||||||
// really pass the argument in-place anyway, and we are constructing a new
|
// really pass the argument in-place anyway, and we are constructing a new
|
||||||
// `Immediate` receiver.
|
// `Immediate` receiver.
|
||||||
let mut receiver = self.copy_fn_arg(&args[0]);
|
let mut receiver = args[0].copy_fn_arg();
|
||||||
let receiver_place = loop {
|
let receiver_place = loop {
|
||||||
match receiver.layout.ty.kind() {
|
match receiver.layout.ty.kind() {
|
||||||
ty::Ref(..) | ty::RawPtr(..) => {
|
ty::Ref(..) | ty::RawPtr(..) => {
|
||||||
|
|
@ -824,41 +821,50 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
with_caller_location: bool,
|
with_caller_location: bool,
|
||||||
) -> InterpResult<'tcx> {
|
) -> InterpResult<'tcx> {
|
||||||
trace!("init_fn_tail_call: {:#?}", fn_val);
|
trace!("init_fn_tail_call: {:#?}", fn_val);
|
||||||
|
|
||||||
// This is the "canonical" implementation of tails calls,
|
// This is the "canonical" implementation of tails calls,
|
||||||
// a pop of the current stack frame, followed by a normal call
|
// a pop of the current stack frame, followed by a normal call
|
||||||
// which pushes a new stack frame, with the return address from
|
// which pushes a new stack frame, with the return address from
|
||||||
// the popped stack frame.
|
// the popped stack frame.
|
||||||
//
|
//
|
||||||
// Note that we are using `pop_stack_frame_raw` and not `return_from_current_stack_frame`,
|
// Note that we cannot use `return_from_current_stack_frame`,
|
||||||
// as the latter "executes" the goto to the return block, but we don't want to,
|
// as that "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.
|
// only the tail called function should return to the current return block.
|
||||||
let StackPopInfo { return_action, return_cont, return_place } =
|
|
||||||
self.pop_stack_frame_raw(false, |_this, _return_place| {
|
|
||||||
// This function's return value is just discarded, the tail-callee will fill in the return place instead.
|
|
||||||
interp_ok(())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
assert_eq!(return_action, ReturnAction::Normal);
|
// The arguments need to all be copied since the current stack frame will be removed
|
||||||
|
// before the callee even starts executing.
|
||||||
// Take the "stack pop cleanup" info, and use that to initiate the next call.
|
// FIXME(explicit_tail_calls,#144855): does this match what codegen does?
|
||||||
let ReturnContinuation::Goto { ret, unwind } = return_cont else {
|
let args = args.iter().map(|fn_arg| FnArg::Copy(fn_arg.copy_fn_arg())).collect::<Vec<_>>();
|
||||||
bug!("can't tailcall as root");
|
// Remove the frame from the stack.
|
||||||
|
let frame = self.pop_stack_frame_raw()?;
|
||||||
|
// Remember where this frame would have returned to.
|
||||||
|
let ReturnContinuation::Goto { ret, unwind } = frame.return_cont() else {
|
||||||
|
bug!("can't tailcall as root of the stack");
|
||||||
};
|
};
|
||||||
|
// There's no return value to deal with! Instead, we forward the old return place
|
||||||
|
// to the new function.
|
||||||
// FIXME(explicit_tail_calls):
|
// FIXME(explicit_tail_calls):
|
||||||
// we should check if both caller&callee can/n't unwind,
|
// we should check if both caller&callee can/n't unwind,
|
||||||
// see <https://github.com/rust-lang/rust/pull/113128#issuecomment-1614979803>
|
// see <https://github.com/rust-lang/rust/pull/113128#issuecomment-1614979803>
|
||||||
|
|
||||||
|
// Now push the new stack frame.
|
||||||
self.init_fn_call(
|
self.init_fn_call(
|
||||||
fn_val,
|
fn_val,
|
||||||
(caller_abi, caller_fn_abi),
|
(caller_abi, caller_fn_abi),
|
||||||
args,
|
&*args,
|
||||||
with_caller_location,
|
with_caller_location,
|
||||||
&return_place,
|
frame.return_place(),
|
||||||
ret,
|
ret,
|
||||||
unwind,
|
unwind,
|
||||||
)
|
)?;
|
||||||
|
|
||||||
|
// Finally, clear the local variables. Has to be done after pushing to support
|
||||||
|
// non-scalar arguments.
|
||||||
|
// FIXME(explicit_tail_calls,#144855): revisit this once codegen supports indirect
|
||||||
|
// arguments, to ensure the semantics are compatible.
|
||||||
|
let return_action = self.cleanup_stack_frame(/* unwinding */ false, frame)?;
|
||||||
|
assert_eq!(return_action, ReturnAction::Normal);
|
||||||
|
|
||||||
|
interp_ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn init_drop_in_place_call(
|
pub(super) fn init_drop_in_place_call(
|
||||||
|
|
@ -953,14 +959,18 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
// local's value out.
|
// local's value out.
|
||||||
let return_op =
|
let return_op =
|
||||||
self.local_to_op(mir::RETURN_PLACE, None).expect("return place should always be live");
|
self.local_to_op(mir::RETURN_PLACE, None).expect("return place should always be live");
|
||||||
// Do the actual pop + copy.
|
// Remove the frame from the stack.
|
||||||
let stack_pop_info = self.pop_stack_frame_raw(unwinding, |this, return_place| {
|
let frame = self.pop_stack_frame_raw()?;
|
||||||
this.copy_op_allow_transmute(&return_op, return_place)?;
|
// Copy the return value and remember the return continuation.
|
||||||
trace!("return value: {:?}", this.dump_place(return_place));
|
if !unwinding {
|
||||||
interp_ok(())
|
self.copy_op_allow_transmute(&return_op, frame.return_place())?;
|
||||||
})?;
|
trace!("return value: {:?}", self.dump_place(frame.return_place()));
|
||||||
|
}
|
||||||
match stack_pop_info.return_action {
|
let return_cont = frame.return_cont();
|
||||||
|
// Finish popping the stack frame.
|
||||||
|
let return_action = self.cleanup_stack_frame(unwinding, frame)?;
|
||||||
|
// Jump to the next block.
|
||||||
|
match return_action {
|
||||||
ReturnAction::Normal => {}
|
ReturnAction::Normal => {}
|
||||||
ReturnAction::NoJump => {
|
ReturnAction::NoJump => {
|
||||||
// The hook already did everything.
|
// The hook already did everything.
|
||||||
|
|
@ -978,7 +988,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
// Normal return, figure out where to jump.
|
// Normal return, figure out where to jump.
|
||||||
if unwinding {
|
if unwinding {
|
||||||
// Follow the unwind edge.
|
// Follow the unwind edge.
|
||||||
match stack_pop_info.return_cont {
|
match return_cont {
|
||||||
ReturnContinuation::Goto { unwind, .. } => {
|
ReturnContinuation::Goto { unwind, .. } => {
|
||||||
// This must be the very last thing that happens, since it can in fact push a new stack frame.
|
// This must be the very last thing that happens, since it can in fact push a new stack frame.
|
||||||
self.unwind_to_block(unwind)
|
self.unwind_to_block(unwind)
|
||||||
|
|
@ -989,7 +999,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Follow the normal return edge.
|
// Follow the normal return edge.
|
||||||
match stack_pop_info.return_cont {
|
match return_cont {
|
||||||
ReturnContinuation::Goto { ret, .. } => self.return_to_block(ret),
|
ReturnContinuation::Goto { ret, .. } => self.return_to_block(ret),
|
||||||
ReturnContinuation::Stop { .. } => {
|
ReturnContinuation::Stop { .. } => {
|
||||||
assert!(
|
assert!(
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ pub use self::operand::{ImmTy, Immediate, OpTy};
|
||||||
pub use self::place::{MPlaceTy, MemPlaceMeta, PlaceTy, Writeable};
|
pub use self::place::{MPlaceTy, MemPlaceMeta, PlaceTy, Writeable};
|
||||||
use self::place::{MemPlace, Place};
|
use self::place::{MemPlace, Place};
|
||||||
pub use self::projection::{OffsetMode, Projectable};
|
pub use self::projection::{OffsetMode, Projectable};
|
||||||
pub use self::stack::{Frame, FrameInfo, LocalState, ReturnContinuation, StackPopInfo};
|
pub use self::stack::{Frame, FrameInfo, LocalState, ReturnContinuation};
|
||||||
pub use self::util::EnteredTraceSpan;
|
pub use self::util::EnteredTraceSpan;
|
||||||
pub(crate) use self::util::create_static_alloc;
|
pub(crate) use self::util::create_static_alloc;
|
||||||
pub use self::validity::{CtfeValidationMode, RangeSet, RefTracking};
|
pub use self::validity::{CtfeValidationMode, RangeSet, RefTracking};
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ pub struct Frame<'tcx, Prov: Provenance = CtfeProvenance, Extra = ()> {
|
||||||
/// and its layout in the caller. This place is to be interpreted relative to the
|
/// and its layout in the caller. This place is to be interpreted relative to the
|
||||||
/// *caller's* stack frame. We use a `PlaceTy` instead of an `MPlaceTy` since this
|
/// *caller's* stack frame. We use a `PlaceTy` instead of an `MPlaceTy` since this
|
||||||
/// avoids having to move *all* return places into Miri's memory.
|
/// avoids having to move *all* return places into Miri's memory.
|
||||||
pub return_place: PlaceTy<'tcx, Prov>,
|
return_place: PlaceTy<'tcx, Prov>,
|
||||||
|
|
||||||
/// The list of locals for this stack frame, stored in order as
|
/// The list of locals for this stack frame, stored in order as
|
||||||
/// `[return_ptr, arguments..., variables..., temporaries...]`.
|
/// `[return_ptr, arguments..., variables..., temporaries...]`.
|
||||||
|
|
@ -127,19 +127,6 @@ pub enum ReturnContinuation {
|
||||||
Stop { cleanup: bool },
|
Stop { cleanup: bool },
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return type of [`InterpCx::pop_stack_frame_raw`].
|
|
||||||
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_cont`](Frame::return_cont) of the popped stack frame.
|
|
||||||
pub return_cont: ReturnContinuation,
|
|
||||||
|
|
||||||
/// [`return_place`](Frame::return_place) of the popped stack frame.
|
|
||||||
pub return_place: PlaceTy<'tcx, Prov>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// State of a local variable including a memoized layout
|
/// State of a local variable including a memoized layout
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct LocalState<'tcx, Prov: Provenance = CtfeProvenance> {
|
pub struct LocalState<'tcx, Prov: Provenance = CtfeProvenance> {
|
||||||
|
|
@ -292,6 +279,14 @@ impl<'tcx, Prov: Provenance, Extra> Frame<'tcx, Prov, Extra> {
|
||||||
self.instance
|
self.instance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn return_place(&self) -> &PlaceTy<'tcx, Prov> {
|
||||||
|
&self.return_place
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn return_cont(&self) -> ReturnContinuation {
|
||||||
|
self.return_cont
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the `SourceInfo` of the current instruction.
|
/// Return the `SourceInfo` of the current instruction.
|
||||||
pub fn current_source_info(&self) -> Option<&mir::SourceInfo> {
|
pub fn current_source_info(&self) -> Option<&mir::SourceInfo> {
|
||||||
self.loc.left().map(|loc| self.body.source_info(loc))
|
self.loc.left().map(|loc| self.body.source_info(loc))
|
||||||
|
|
@ -417,35 +412,26 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
interp_ok(())
|
interp_ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Low-level helper that pops a stack frame from the stack and returns some information about
|
/// Low-level helper that pops a stack frame from the stack without any cleanup.
|
||||||
/// it.
|
/// This invokes `before_stack_pop`.
|
||||||
///
|
/// After calling this function, you need to deal with the return value, and then
|
||||||
/// This also deallocates locals, if necessary.
|
/// invoke `cleanup_stack_frame`.
|
||||||
/// `copy_ret_val` gets called after the frame has been taken from the stack but before the locals have been deallocated.
|
|
||||||
///
|
|
||||||
/// [`M::before_stack_pop`] and [`M::after_stack_pop`] are called by this function
|
|
||||||
/// automatically.
|
|
||||||
///
|
|
||||||
/// The high-level version of this is `return_from_current_stack_frame`.
|
|
||||||
///
|
|
||||||
/// [`M::before_stack_pop`]: Machine::before_stack_pop
|
|
||||||
/// [`M::after_stack_pop`]: Machine::after_stack_pop
|
|
||||||
pub(super) fn pop_stack_frame_raw(
|
pub(super) fn pop_stack_frame_raw(
|
||||||
&mut self,
|
&mut self,
|
||||||
unwinding: bool,
|
) -> InterpResult<'tcx, Frame<'tcx, M::Provenance, M::FrameExtra>> {
|
||||||
copy_ret_val: impl FnOnce(&mut Self, &PlaceTy<'tcx, M::Provenance>) -> InterpResult<'tcx>,
|
|
||||||
) -> InterpResult<'tcx, StackPopInfo<'tcx, M::Provenance>> {
|
|
||||||
M::before_stack_pop(self)?;
|
M::before_stack_pop(self)?;
|
||||||
let frame =
|
let frame =
|
||||||
self.stack_mut().pop().expect("tried to pop a stack frame, but there were none");
|
self.stack_mut().pop().expect("tried to pop a stack frame, but there were none");
|
||||||
|
interp_ok(frame)
|
||||||
|
}
|
||||||
|
|
||||||
// Copy return value (unless we are unwinding).
|
/// Deallocate local variables in the stack frame, and invoke `after_stack_pop`.
|
||||||
if !unwinding {
|
pub(super) fn cleanup_stack_frame(
|
||||||
copy_ret_val(self, &frame.return_place)?;
|
&mut self,
|
||||||
}
|
unwinding: bool,
|
||||||
|
frame: Frame<'tcx, M::Provenance, M::FrameExtra>,
|
||||||
|
) -> InterpResult<'tcx, ReturnAction> {
|
||||||
let return_cont = frame.return_cont;
|
let return_cont = frame.return_cont;
|
||||||
let return_place = frame.return_place.clone();
|
|
||||||
|
|
||||||
// Cleanup: deallocate locals.
|
// Cleanup: deallocate locals.
|
||||||
// Usually we want to clean up (deallocate locals), but in a few rare cases we don't.
|
// Usually we want to clean up (deallocate locals), but in a few rare cases we don't.
|
||||||
|
|
@ -455,7 +441,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
ReturnContinuation::Stop { cleanup, .. } => cleanup,
|
ReturnContinuation::Stop { cleanup, .. } => cleanup,
|
||||||
};
|
};
|
||||||
|
|
||||||
let return_action = if cleanup {
|
if cleanup {
|
||||||
for local in &frame.locals {
|
for local in &frame.locals {
|
||||||
self.deallocate_local(local.value)?;
|
self.deallocate_local(local.value)?;
|
||||||
}
|
}
|
||||||
|
|
@ -466,13 +452,11 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
// Call the machine hook, which determines the next steps.
|
// Call the machine hook, which determines the next steps.
|
||||||
let return_action = M::after_stack_pop(self, frame, unwinding)?;
|
let return_action = M::after_stack_pop(self, frame, unwinding)?;
|
||||||
assert_ne!(return_action, ReturnAction::NoCleanup);
|
assert_ne!(return_action, ReturnAction::NoCleanup);
|
||||||
return_action
|
interp_ok(return_action)
|
||||||
} else {
|
} else {
|
||||||
// We also skip the machine hook when there's no cleanup. This not a real "pop" anyway.
|
// We also skip the machine hook when there's no cleanup. This not a real "pop" anyway.
|
||||||
ReturnAction::NoCleanup
|
interp_ok(ReturnAction::NoCleanup)
|
||||||
};
|
}
|
||||||
|
|
||||||
interp_ok(StackPopInfo { return_action, return_cont, return_place })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// In the current stack frame, mark all locals as live that are not arguments and don't have
|
/// In the current stack frame, mark all locals as live that are not arguments and don't have
|
||||||
|
|
@ -655,7 +639,7 @@ impl<'a, 'tcx: 'a, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
let (_idx, callee_abi) = callee_abis.next().unwrap();
|
let (_idx, callee_abi) = callee_abis.next().unwrap();
|
||||||
assert!(self.check_argument_compat(caller_abi, callee_abi)?);
|
assert!(self.check_argument_compat(caller_abi, callee_abi)?);
|
||||||
// FIXME: do we have to worry about in-place argument passing?
|
// FIXME: do we have to worry about in-place argument passing?
|
||||||
let op = self.copy_fn_arg(fn_arg);
|
let op = fn_arg.copy_fn_arg();
|
||||||
let mplace = self.allocate(op.layout, MemoryKind::Stack)?;
|
let mplace = self.allocate(op.layout, MemoryKind::Stack)?;
|
||||||
self.copy_op(&op, &mplace)?;
|
self.copy_op(&op, &mplace)?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -343,8 +343,8 @@ impl VisitProvenance for Thread<'_> {
|
||||||
|
|
||||||
impl VisitProvenance for Frame<'_, Provenance, FrameExtra<'_>> {
|
impl VisitProvenance for Frame<'_, Provenance, FrameExtra<'_>> {
|
||||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
|
let return_place = self.return_place();
|
||||||
let Frame {
|
let Frame {
|
||||||
return_place,
|
|
||||||
locals,
|
locals,
|
||||||
extra,
|
extra,
|
||||||
// There are some private fields we cannot access; they contain no tags.
|
// There are some private fields we cannot access; they contain no tags.
|
||||||
|
|
|
||||||
|
|
@ -493,7 +493,7 @@ pub fn report_result<'tcx>(
|
||||||
for (i, frame) in ecx.active_thread_stack().iter().enumerate() {
|
for (i, frame) in ecx.active_thread_stack().iter().enumerate() {
|
||||||
trace!("-------------------");
|
trace!("-------------------");
|
||||||
trace!("Frame {}", i);
|
trace!("Frame {}", i);
|
||||||
trace!(" return: {:?}", frame.return_place);
|
trace!(" return: {:?}", frame.return_place());
|
||||||
for (i, local) in frame.locals.iter().enumerate() {
|
for (i, local) in frame.locals.iter().enumerate() {
|
||||||
trace!(" local {}: {:?}", i, local);
|
trace!(" local {}: {:?}", i, local);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1235,7 +1235,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
|
||||||
// to run extra MIR), and Ok(Some(body)) if we found MIR to run for the
|
// to run extra MIR), and Ok(Some(body)) if we found MIR to run for the
|
||||||
// foreign function
|
// foreign function
|
||||||
// Any needed call to `goto_block` will be performed by `emulate_foreign_item`.
|
// Any needed call to `goto_block` will be performed by `emulate_foreign_item`.
|
||||||
let args = ecx.copy_fn_args(args); // FIXME: Should `InPlace` arguments be reset to uninit?
|
let args = MiriInterpCx::copy_fn_args(args); // FIXME: Should `InPlace` arguments be reset to uninit?
|
||||||
let link_name = Symbol::intern(ecx.tcx.symbol_name(instance).name);
|
let link_name = Symbol::intern(ecx.tcx.symbol_name(instance).name);
|
||||||
return ecx.emulate_foreign_item(link_name, abi, &args, dest, ret, unwind);
|
return ecx.emulate_foreign_item(link_name, abi, &args, dest, ret, unwind);
|
||||||
}
|
}
|
||||||
|
|
@ -1262,7 +1262,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
|
||||||
ret: Option<mir::BasicBlock>,
|
ret: Option<mir::BasicBlock>,
|
||||||
unwind: mir::UnwindAction,
|
unwind: mir::UnwindAction,
|
||||||
) -> InterpResult<'tcx> {
|
) -> InterpResult<'tcx> {
|
||||||
let args = ecx.copy_fn_args(args); // FIXME: Should `InPlace` arguments be reset to uninit?
|
let args = MiriInterpCx::copy_fn_args(args); // FIXME: Should `InPlace` arguments be reset to uninit?
|
||||||
ecx.emulate_dyn_sym(fn_val, abi, &args, dest, ret, unwind)
|
ecx.emulate_dyn_sym(fn_val, abi, &args, dest, ret, unwind)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,8 @@ LL | let local = 0;
|
||||||
help: ALLOC was deallocated here:
|
help: ALLOC was deallocated here:
|
||||||
--> tests/fail/tail_calls/dangling-local-var.rs:LL:CC
|
--> tests/fail/tail_calls/dangling-local-var.rs:LL:CC
|
||||||
|
|
|
|
||||||
LL | f(std::ptr::null());
|
LL | let _val = unsafe { *x };
|
||||||
| ^^^^^^^^^^^^^^^^^^^
|
| ^^^^
|
||||||
= note: stack backtrace:
|
= note: stack backtrace:
|
||||||
0: g
|
0: g
|
||||||
at tests/fail/tail_calls/dangling-local-var.rs:LL:CC
|
at tests/fail/tail_calls/dangling-local-var.rs:LL:CC
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
fn main() {
|
fn main() {
|
||||||
assert_eq!(factorial(10), 3_628_800);
|
assert_eq!(factorial(10), 3_628_800);
|
||||||
assert_eq!(mutually_recursive_identity(1000), 1000);
|
assert_eq!(mutually_recursive_identity(1000), 1000);
|
||||||
|
non_scalar();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn factorial(n: u32) -> u32 {
|
fn factorial(n: u32) -> u32 {
|
||||||
|
|
@ -37,3 +38,14 @@ fn mutually_recursive_identity(x: u32) -> u32 {
|
||||||
|
|
||||||
switch(x, 0)
|
switch(x, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn non_scalar() {
|
||||||
|
fn f(x: [usize; 2], i: u32) {
|
||||||
|
if i == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
become f(x, i - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
f([5, 5], 2);
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue