Auto merge of #141406 - RalfJung:less-force-allocate, r=oli-obk
interpret: do not force_allocate all return places A while ago I cleaned up our `PlaceTy` a little, but as a side-effect of that, return places had to always be force-allocated. That turns out to cause quite a few extra allocations, and for a project we are doing where we marry Miri with a model checker, that means a lot of extra work -- local variables are just so much easier to reason about than allocations. So, this PR brings back the ability to have the return place be just a local of the caller. To make this work cleanly I had to rework stack pop handling a bit, which also changes the output of Miri in some cases as the span for errors occurring during a particular phase of stack pop changed. With these changes, a no-std binary with a function of functions that just take and return scalar types and that uses no pointers now does not move *any* local variables into memory. :) r? `@oli-obk`
This commit is contained in:
commit
b5eb9893f4
18 changed files with 138 additions and 142 deletions
|
|
@ -90,7 +90,7 @@ impl<'tcx> interpret::Machine<'tcx> for DummyMachine {
|
|||
_instance: ty::Instance<'tcx>,
|
||||
_abi: &FnAbi<'tcx, Ty<'tcx>>,
|
||||
_args: &[interpret::FnArg<'tcx, Self::Provenance>],
|
||||
_destination: &interpret::MPlaceTy<'tcx, Self::Provenance>,
|
||||
_destination: &interpret::PlaceTy<'tcx, Self::Provenance>,
|
||||
_target: Option<BasicBlock>,
|
||||
_unwind: UnwindAction,
|
||||
) -> interpret::InterpResult<'tcx, Option<(&'tcx Body<'tcx>, ty::Instance<'tcx>)>> {
|
||||
|
|
@ -108,7 +108,7 @@ impl<'tcx> interpret::Machine<'tcx> for DummyMachine {
|
|||
_ecx: &mut InterpCx<'tcx, Self>,
|
||||
_instance: ty::Instance<'tcx>,
|
||||
_args: &[interpret::OpTy<'tcx, Self::Provenance>],
|
||||
_destination: &interpret::MPlaceTy<'tcx, Self::Provenance>,
|
||||
_destination: &interpret::PlaceTy<'tcx, Self::Provenance>,
|
||||
_target: Option<BasicBlock>,
|
||||
_unwind: UnwindAction,
|
||||
) -> interpret::InterpResult<'tcx, Option<ty::Instance<'tcx>>> {
|
||||
|
|
|
|||
|
|
@ -71,7 +71,12 @@ fn eval_body_using_ecx<'tcx, R: InterpretationResult<'tcx>>(
|
|||
|
||||
// This can't use `init_stack_frame` since `body` is not a function,
|
||||
// so computing its ABI would fail. It's also not worth it since there are no arguments to pass.
|
||||
ecx.push_stack_frame_raw(cid.instance, body, &ret, StackPopCleanup::Root { cleanup: false })?;
|
||||
ecx.push_stack_frame_raw(
|
||||
cid.instance,
|
||||
body,
|
||||
&ret.clone().into(),
|
||||
StackPopCleanup::Root { cleanup: false },
|
||||
)?;
|
||||
ecx.storage_live_for_always_live_locals()?;
|
||||
|
||||
// The main interpreter loop.
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ use crate::errors::{LongRunning, LongRunningWarn};
|
|||
use crate::fluent_generated as fluent;
|
||||
use crate::interpret::{
|
||||
self, AllocId, AllocInit, AllocRange, ConstAllocation, CtfeProvenance, FnArg, Frame,
|
||||
GlobalAlloc, ImmTy, InterpCx, InterpResult, MPlaceTy, OpTy, Pointer, RangeSet, Scalar,
|
||||
GlobalAlloc, ImmTy, InterpCx, InterpResult, OpTy, PlaceTy, Pointer, RangeSet, Scalar,
|
||||
compile_time_machine, interp_ok, throw_exhaust, throw_inval, throw_ub, throw_ub_custom,
|
||||
throw_unsup, throw_unsup_format,
|
||||
};
|
||||
|
|
@ -226,7 +226,7 @@ impl<'tcx> CompileTimeInterpCx<'tcx> {
|
|||
&mut self,
|
||||
instance: ty::Instance<'tcx>,
|
||||
args: &[FnArg<'tcx>],
|
||||
_dest: &MPlaceTy<'tcx>,
|
||||
_dest: &PlaceTy<'tcx>,
|
||||
_ret: Option<mir::BasicBlock>,
|
||||
) -> InterpResult<'tcx, Option<ty::Instance<'tcx>>> {
|
||||
let def_id = instance.def_id();
|
||||
|
|
@ -343,7 +343,7 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
|
|||
orig_instance: ty::Instance<'tcx>,
|
||||
_abi: &FnAbi<'tcx, Ty<'tcx>>,
|
||||
args: &[FnArg<'tcx>],
|
||||
dest: &MPlaceTy<'tcx>,
|
||||
dest: &PlaceTy<'tcx>,
|
||||
ret: Option<mir::BasicBlock>,
|
||||
_unwind: mir::UnwindAction, // unwinding is not supported in consts
|
||||
) -> InterpResult<'tcx, Option<(&'tcx mir::Body<'tcx>, ty::Instance<'tcx>)>> {
|
||||
|
|
@ -385,7 +385,7 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
|
|||
ecx: &mut InterpCx<'tcx, Self>,
|
||||
instance: ty::Instance<'tcx>,
|
||||
args: &[OpTy<'tcx>],
|
||||
dest: &MPlaceTy<'tcx, Self::Provenance>,
|
||||
dest: &PlaceTy<'tcx, Self::Provenance>,
|
||||
target: Option<mir::BasicBlock>,
|
||||
_unwind: mir::UnwindAction,
|
||||
) -> InterpResult<'tcx, Option<ty::Instance<'tcx>>> {
|
||||
|
|
|
|||
|
|
@ -339,7 +339,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||
caller_fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
|
||||
args: &[FnArg<'tcx, M::Provenance>],
|
||||
with_caller_location: bool,
|
||||
destination: &MPlaceTy<'tcx, M::Provenance>,
|
||||
destination: &PlaceTy<'tcx, M::Provenance>,
|
||||
mut stack_pop: StackPopCleanup,
|
||||
) -> InterpResult<'tcx> {
|
||||
// Compute callee information.
|
||||
|
|
@ -487,7 +487,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||
}
|
||||
|
||||
// Protect return place for in-place return value passing.
|
||||
M::protect_in_place_function_argument(self, &destination)?;
|
||||
// We only need to protect anything if this is actually an in-memory place.
|
||||
if let Left(mplace) = destination.as_mplace_or_local() {
|
||||
M::protect_in_place_function_argument(self, &mplace)?;
|
||||
}
|
||||
|
||||
// Don't forget to mark "initially live" locals as live.
|
||||
self.storage_live_for_always_live_locals()?;
|
||||
|
|
@ -512,7 +515,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||
(caller_abi, caller_fn_abi): (ExternAbi, &FnAbi<'tcx, Ty<'tcx>>),
|
||||
args: &[FnArg<'tcx, M::Provenance>],
|
||||
with_caller_location: bool,
|
||||
destination: &MPlaceTy<'tcx, M::Provenance>,
|
||||
destination: &PlaceTy<'tcx, M::Provenance>,
|
||||
target: Option<mir::BasicBlock>,
|
||||
unwind: mir::UnwindAction,
|
||||
) -> InterpResult<'tcx> {
|
||||
|
|
@ -776,10 +779,11 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||
// Note that we are using `pop_stack_frame_raw` 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_raw(false)?;
|
||||
let StackPopInfo { return_action, return_to_block, 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);
|
||||
|
||||
|
|
@ -850,7 +854,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||
(ExternAbi::Rust, fn_abi),
|
||||
&[FnArg::Copy(arg.into())],
|
||||
false,
|
||||
&ret,
|
||||
&ret.into(),
|
||||
Some(target),
|
||||
unwind,
|
||||
)
|
||||
|
|
@ -891,28 +895,16 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||
throw_ub_custom!(fluent::const_eval_unwind_past_top);
|
||||
}
|
||||
|
||||
M::before_stack_pop(self, self.frame())?;
|
||||
|
||||
// Copy return value. Must of course happen *before* we deallocate the locals.
|
||||
// Must be *after* `before_stack_pop` as otherwise the return place might still be protected.
|
||||
let copy_ret_result = if !unwinding {
|
||||
let op = self
|
||||
.local_to_op(mir::RETURN_PLACE, None)
|
||||
.expect("return place should always be live");
|
||||
let dest = self.frame().return_place.clone();
|
||||
let res = self.copy_op_allow_transmute(&op, &dest);
|
||||
trace!("return value: {:?}", self.dump_place(&dest.into()));
|
||||
// We delay actually short-circuiting on this error until *after* the stack frame is
|
||||
// popped, since we want this error to be attributed to the caller, whose type defines
|
||||
// this transmute.
|
||||
res
|
||||
} else {
|
||||
// Get out the return value. Must happen *before* the frame is popped as we have to get the
|
||||
// local's value out.
|
||||
let return_op =
|
||||
self.local_to_op(mir::RETURN_PLACE, None).expect("return place should always be live");
|
||||
// Do the actual pop + copy.
|
||||
let stack_pop_info = self.pop_stack_frame_raw(unwinding, |this, return_place| {
|
||||
this.copy_op_allow_transmute(&return_op, return_place)?;
|
||||
trace!("return value: {:?}", this.dump_place(return_place));
|
||||
interp_ok(())
|
||||
};
|
||||
|
||||
// All right, now it is time to actually pop the frame.
|
||||
// An error here takes precedence over the copy error.
|
||||
let (stack_pop_info, ()) = self.pop_stack_frame_raw(unwinding).and(copy_ret_result)?;
|
||||
})?;
|
||||
|
||||
match stack_pop_info.return_action {
|
||||
ReturnAction::Normal => {}
|
||||
|
|
@ -924,7 +916,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||
// 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.
|
||||
// Don't jump anywhere.
|
||||
return interp_ok(());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,9 +17,9 @@ use tracing::trace;
|
|||
use super::memory::MemoryKind;
|
||||
use super::util::ensure_monomorphic_enough;
|
||||
use super::{
|
||||
Allocation, CheckInAllocMsg, ConstAllocation, GlobalId, ImmTy, InterpCx, InterpResult,
|
||||
MPlaceTy, Machine, OpTy, Pointer, PointerArithmetic, Provenance, Scalar, err_inval,
|
||||
err_ub_custom, err_unsup_format, interp_ok, throw_inval, throw_ub_custom, throw_ub_format,
|
||||
Allocation, CheckInAllocMsg, ConstAllocation, GlobalId, ImmTy, InterpCx, InterpResult, Machine,
|
||||
OpTy, PlaceTy, Pointer, PointerArithmetic, Provenance, Scalar, err_inval, err_ub_custom,
|
||||
err_unsup_format, interp_ok, throw_inval, throw_ub_custom, throw_ub_format,
|
||||
};
|
||||
use crate::fluent_generated as fluent;
|
||||
|
||||
|
|
@ -112,7 +112,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||
&mut self,
|
||||
instance: ty::Instance<'tcx>,
|
||||
args: &[OpTy<'tcx, M::Provenance>],
|
||||
dest: &MPlaceTy<'tcx, M::Provenance>,
|
||||
dest: &PlaceTy<'tcx, M::Provenance>,
|
||||
ret: Option<mir::BasicBlock>,
|
||||
) -> InterpResult<'tcx, bool> {
|
||||
let instance_args = instance.args;
|
||||
|
|
@ -587,7 +587,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||
&mut self,
|
||||
a: &ImmTy<'tcx, M::Provenance>,
|
||||
b: &ImmTy<'tcx, M::Provenance>,
|
||||
dest: &MPlaceTy<'tcx, M::Provenance>,
|
||||
dest: &PlaceTy<'tcx, M::Provenance>,
|
||||
) -> InterpResult<'tcx> {
|
||||
assert_eq!(a.layout.ty, b.layout.ty);
|
||||
assert_matches!(a.layout.ty.kind(), ty::Int(..) | ty::Uint(..));
|
||||
|
|
@ -801,7 +801,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||
fn float_min_intrinsic<F>(
|
||||
&mut self,
|
||||
args: &[OpTy<'tcx, M::Provenance>],
|
||||
dest: &MPlaceTy<'tcx, M::Provenance>,
|
||||
dest: &PlaceTy<'tcx, M::Provenance>,
|
||||
) -> InterpResult<'tcx, ()>
|
||||
where
|
||||
F: rustc_apfloat::Float + rustc_apfloat::FloatConvert<F> + Into<Scalar<M::Provenance>>,
|
||||
|
|
@ -822,7 +822,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||
fn float_max_intrinsic<F>(
|
||||
&mut self,
|
||||
args: &[OpTy<'tcx, M::Provenance>],
|
||||
dest: &MPlaceTy<'tcx, M::Provenance>,
|
||||
dest: &PlaceTy<'tcx, M::Provenance>,
|
||||
) -> InterpResult<'tcx, ()>
|
||||
where
|
||||
F: rustc_apfloat::Float + rustc_apfloat::FloatConvert<F> + Into<Scalar<M::Provenance>>,
|
||||
|
|
@ -843,7 +843,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||
fn float_minimum_intrinsic<F>(
|
||||
&mut self,
|
||||
args: &[OpTy<'tcx, M::Provenance>],
|
||||
dest: &MPlaceTy<'tcx, M::Provenance>,
|
||||
dest: &PlaceTy<'tcx, M::Provenance>,
|
||||
) -> InterpResult<'tcx, ()>
|
||||
where
|
||||
F: rustc_apfloat::Float + rustc_apfloat::FloatConvert<F> + Into<Scalar<M::Provenance>>,
|
||||
|
|
@ -859,7 +859,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||
fn float_maximum_intrinsic<F>(
|
||||
&mut self,
|
||||
args: &[OpTy<'tcx, M::Provenance>],
|
||||
dest: &MPlaceTy<'tcx, M::Provenance>,
|
||||
dest: &PlaceTy<'tcx, M::Provenance>,
|
||||
) -> InterpResult<'tcx, ()>
|
||||
where
|
||||
F: rustc_apfloat::Float + rustc_apfloat::FloatConvert<F> + Into<Scalar<M::Provenance>>,
|
||||
|
|
@ -875,7 +875,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||
fn float_copysign_intrinsic<F>(
|
||||
&mut self,
|
||||
args: &[OpTy<'tcx, M::Provenance>],
|
||||
dest: &MPlaceTy<'tcx, M::Provenance>,
|
||||
dest: &PlaceTy<'tcx, M::Provenance>,
|
||||
) -> InterpResult<'tcx, ()>
|
||||
where
|
||||
F: rustc_apfloat::Float + rustc_apfloat::FloatConvert<F> + Into<Scalar<M::Provenance>>,
|
||||
|
|
@ -890,7 +890,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||
fn float_abs_intrinsic<F>(
|
||||
&mut self,
|
||||
args: &[OpTy<'tcx, M::Provenance>],
|
||||
dest: &MPlaceTy<'tcx, M::Provenance>,
|
||||
dest: &PlaceTy<'tcx, M::Provenance>,
|
||||
) -> InterpResult<'tcx, ()>
|
||||
where
|
||||
F: rustc_apfloat::Float + rustc_apfloat::FloatConvert<F> + Into<Scalar<M::Provenance>>,
|
||||
|
|
|
|||
|
|
@ -208,7 +208,7 @@ pub trait Machine<'tcx>: Sized {
|
|||
instance: ty::Instance<'tcx>,
|
||||
abi: &FnAbi<'tcx, Ty<'tcx>>,
|
||||
args: &[FnArg<'tcx, Self::Provenance>],
|
||||
destination: &MPlaceTy<'tcx, Self::Provenance>,
|
||||
destination: &PlaceTy<'tcx, Self::Provenance>,
|
||||
target: Option<mir::BasicBlock>,
|
||||
unwind: mir::UnwindAction,
|
||||
) -> InterpResult<'tcx, Option<(&'tcx mir::Body<'tcx>, ty::Instance<'tcx>)>>;
|
||||
|
|
@ -220,7 +220,7 @@ pub trait Machine<'tcx>: Sized {
|
|||
fn_val: Self::ExtraFnVal,
|
||||
abi: &FnAbi<'tcx, Ty<'tcx>>,
|
||||
args: &[FnArg<'tcx, Self::Provenance>],
|
||||
destination: &MPlaceTy<'tcx, Self::Provenance>,
|
||||
destination: &PlaceTy<'tcx, Self::Provenance>,
|
||||
target: Option<mir::BasicBlock>,
|
||||
unwind: mir::UnwindAction,
|
||||
) -> InterpResult<'tcx>;
|
||||
|
|
@ -234,7 +234,7 @@ pub trait Machine<'tcx>: Sized {
|
|||
ecx: &mut InterpCx<'tcx, Self>,
|
||||
instance: ty::Instance<'tcx>,
|
||||
args: &[OpTy<'tcx, Self::Provenance>],
|
||||
destination: &MPlaceTy<'tcx, Self::Provenance>,
|
||||
destination: &PlaceTy<'tcx, Self::Provenance>,
|
||||
target: Option<mir::BasicBlock>,
|
||||
unwind: mir::UnwindAction,
|
||||
) -> InterpResult<'tcx, Option<ty::Instance<'tcx>>>;
|
||||
|
|
@ -536,11 +536,9 @@ pub trait Machine<'tcx>: Sized {
|
|||
interp_ok(())
|
||||
}
|
||||
|
||||
/// Called just before the return value is copied to the caller-provided return place.
|
||||
fn before_stack_pop(
|
||||
_ecx: &InterpCx<'tcx, Self>,
|
||||
_frame: &Frame<'tcx, Self::Provenance, Self::FrameExtra>,
|
||||
) -> InterpResult<'tcx> {
|
||||
/// Called just before the frame is removed from the stack (followed by return value copy and
|
||||
/// local cleanup).
|
||||
fn before_stack_pop(_ecx: &mut InterpCx<'tcx, Self>) -> InterpResult<'tcx> {
|
||||
interp_ok(())
|
||||
}
|
||||
|
||||
|
|
@ -675,7 +673,7 @@ pub macro compile_time_machine(<$tcx: lifetime>) {
|
|||
fn_val: !,
|
||||
_abi: &FnAbi<$tcx, Ty<$tcx>>,
|
||||
_args: &[FnArg<$tcx>],
|
||||
_destination: &MPlaceTy<$tcx, Self::Provenance>,
|
||||
_destination: &PlaceTy<$tcx, Self::Provenance>,
|
||||
_target: Option<mir::BasicBlock>,
|
||||
_unwind: mir::UnwindAction,
|
||||
) -> InterpResult<$tcx> {
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@ use rustc_span::Span;
|
|||
use tracing::{info_span, instrument, trace};
|
||||
|
||||
use super::{
|
||||
AllocId, CtfeProvenance, Immediate, InterpCx, InterpResult, MPlaceTy, Machine, MemPlace,
|
||||
MemPlaceMeta, MemoryKind, Operand, Pointer, Provenance, ReturnAction, Scalar,
|
||||
from_known_layout, interp_ok, throw_ub, throw_unsup,
|
||||
AllocId, CtfeProvenance, Immediate, InterpCx, InterpResult, Machine, MemPlace, MemPlaceMeta,
|
||||
MemoryKind, Operand, PlaceTy, Pointer, Provenance, ReturnAction, Scalar, from_known_layout,
|
||||
interp_ok, throw_ub, throw_unsup,
|
||||
};
|
||||
use crate::errors;
|
||||
|
||||
|
|
@ -76,8 +76,10 @@ pub struct Frame<'tcx, Prov: Provenance = CtfeProvenance, Extra = ()> {
|
|||
return_to_block: StackPopCleanup,
|
||||
|
||||
/// The location where the result of the current stack frame should be written to,
|
||||
/// and its layout in the caller.
|
||||
pub return_place: MPlaceTy<'tcx, Prov>,
|
||||
/// 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
|
||||
/// avoids having to move *all* return places into Miri's memory.
|
||||
pub return_place: PlaceTy<'tcx, Prov>,
|
||||
|
||||
/// The list of locals for this stack frame, stored in order as
|
||||
/// `[return_ptr, arguments..., variables..., temporaries...]`.
|
||||
|
|
@ -129,7 +131,7 @@ pub struct StackPopInfo<'tcx, Prov: Provenance> {
|
|||
pub return_to_block: StackPopCleanup,
|
||||
|
||||
/// [`return_place`](Frame::return_place) of the popped stack frame.
|
||||
pub return_place: MPlaceTy<'tcx, Prov>,
|
||||
pub return_place: PlaceTy<'tcx, Prov>,
|
||||
}
|
||||
|
||||
/// State of a local variable including a memoized layout
|
||||
|
|
@ -353,7 +355,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||
&mut self,
|
||||
instance: ty::Instance<'tcx>,
|
||||
body: &'tcx mir::Body<'tcx>,
|
||||
return_place: &MPlaceTy<'tcx, M::Provenance>,
|
||||
return_place: &PlaceTy<'tcx, M::Provenance>,
|
||||
return_to_block: StackPopCleanup,
|
||||
) -> InterpResult<'tcx> {
|
||||
trace!("body: {:#?}", body);
|
||||
|
|
@ -404,9 +406,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||
/// it.
|
||||
///
|
||||
/// This also deallocates locals, if necessary.
|
||||
/// `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`] should be called before calling this function.
|
||||
/// [`M::after_stack_pop`] is called by this function automatically.
|
||||
/// [`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`.
|
||||
///
|
||||
|
|
@ -415,47 +418,44 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||
pub(super) fn pop_stack_frame_raw(
|
||||
&mut self,
|
||||
unwinding: bool,
|
||||
copy_ret_val: impl FnOnce(&mut Self, &PlaceTy<'tcx, M::Provenance>) -> InterpResult<'tcx>,
|
||||
) -> InterpResult<'tcx, StackPopInfo<'tcx, M::Provenance>> {
|
||||
let cleanup = self.cleanup_current_frame_locals()?;
|
||||
|
||||
M::before_stack_pop(self)?;
|
||||
let frame =
|
||||
self.stack_mut().pop().expect("tried to pop a stack frame, but there were none");
|
||||
|
||||
// Copy return value (unless we are unwinding).
|
||||
if !unwinding {
|
||||
copy_ret_val(self, &frame.return_place)?;
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
interp_ok(StackPopInfo { return_action, return_to_block, return_place })
|
||||
}
|
||||
|
||||
/// A private helper for [`pop_stack_frame_raw`](InterpCx::pop_stack_frame_raw).
|
||||
/// 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 {
|
||||
let return_action = 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 {
|
||||
for local in &frame.locals {
|
||||
self.deallocate_local(local.value)?;
|
||||
}
|
||||
}
|
||||
|
||||
interp_ok(cleanup)
|
||||
// Call the machine hook, which determines the next steps.
|
||||
let return_action = M::after_stack_pop(self, frame, unwinding)?;
|
||||
assert_ne!(return_action, ReturnAction::NoCleanup);
|
||||
return_action
|
||||
} else {
|
||||
// We also skip the machine hook when there's no cleanup. This not a real "pop" anyway.
|
||||
ReturnAction::NoCleanup
|
||||
};
|
||||
|
||||
interp_ok(StackPopInfo { return_action, return_to_block, return_place })
|
||||
}
|
||||
|
||||
/// In the current stack frame, mark all locals as live that are not arguments and don't have
|
||||
|
|
|
|||
|
|
@ -506,7 +506,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)?;
|
||||
|
||||
let destination = self.force_allocation(&self.eval_place(destination)?)?;
|
||||
let destination = self.eval_place(destination)?;
|
||||
self.init_fn_call(
|
||||
callee,
|
||||
(fn_sig.abi, fn_abi),
|
||||
|
|
|
|||
|
|
@ -218,34 +218,37 @@ impl<'tcx> Thread<'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Return the top user-relevant frame, if there is one.
|
||||
/// Return the top user-relevant frame, if there is one. `skip` indicates how many top frames
|
||||
/// should be skipped.
|
||||
/// Note that the choice to return `None` here when there is no user-relevant frame is part of
|
||||
/// justifying the optimization that only pushes of user-relevant frames require updating the
|
||||
/// `top_user_relevant_frame` field.
|
||||
fn compute_top_user_relevant_frame(&self) -> Option<usize> {
|
||||
fn compute_top_user_relevant_frame(&self, skip: usize) -> Option<usize> {
|
||||
self.stack
|
||||
.iter()
|
||||
.enumerate()
|
||||
.rev()
|
||||
.skip(skip)
|
||||
.find_map(|(idx, frame)| if frame.extra.is_user_relevant { Some(idx) } else { None })
|
||||
}
|
||||
|
||||
/// Re-compute the top user-relevant frame from scratch.
|
||||
pub fn recompute_top_user_relevant_frame(&mut self) {
|
||||
self.top_user_relevant_frame = self.compute_top_user_relevant_frame();
|
||||
/// Re-compute the top user-relevant frame from scratch. `skip` indicates how many top frames
|
||||
/// should be skipped.
|
||||
pub fn recompute_top_user_relevant_frame(&mut self, skip: usize) {
|
||||
self.top_user_relevant_frame = self.compute_top_user_relevant_frame(skip);
|
||||
}
|
||||
|
||||
/// Set the top user-relevant frame to the given value. Must be equal to what
|
||||
/// `get_top_user_relevant_frame` would return!
|
||||
pub fn set_top_user_relevant_frame(&mut self, frame_idx: usize) {
|
||||
debug_assert_eq!(Some(frame_idx), self.compute_top_user_relevant_frame());
|
||||
debug_assert_eq!(Some(frame_idx), self.compute_top_user_relevant_frame(0));
|
||||
self.top_user_relevant_frame = Some(frame_idx);
|
||||
}
|
||||
|
||||
/// Returns the topmost frame that is considered user-relevant, or the
|
||||
/// top of the stack if there is no such frame, or `None` if the stack is empty.
|
||||
pub fn top_user_relevant_frame(&self) -> Option<usize> {
|
||||
debug_assert_eq!(self.top_user_relevant_frame, self.compute_top_user_relevant_frame());
|
||||
debug_assert_eq!(self.top_user_relevant_frame, self.compute_top_user_relevant_frame(0));
|
||||
// This can be called upon creation of an allocation. We create allocations while setting up
|
||||
// parts of the Rust runtime when we do not have any stack frames yet, so we need to handle
|
||||
// empty stacks.
|
||||
|
|
|
|||
|
|
@ -354,11 +354,10 @@ pub fn create_ecx<'tcx>(
|
|||
argvs.push(arg_place.to_ref(&ecx));
|
||||
}
|
||||
// Make an array with all these pointers, in the Miri memory.
|
||||
let argvs_layout = ecx.layout_of(Ty::new_array(
|
||||
tcx,
|
||||
Ty::new_imm_ptr(tcx, tcx.types.u8),
|
||||
u64::try_from(argvs.len()).unwrap(),
|
||||
))?;
|
||||
let u8_ptr_type = Ty::new_imm_ptr(tcx, tcx.types.u8);
|
||||
let u8_ptr_ptr_type = Ty::new_imm_ptr(tcx, u8_ptr_type);
|
||||
let argvs_layout =
|
||||
ecx.layout_of(Ty::new_array(tcx, u8_ptr_type, u64::try_from(argvs.len()).unwrap()))?;
|
||||
let argvs_place = ecx.allocate(argvs_layout, MiriMemoryKind::Machine.into())?;
|
||||
for (idx, arg) in argvs.into_iter().enumerate() {
|
||||
let place = ecx.project_field(&argvs_place, idx)?;
|
||||
|
|
@ -373,10 +372,8 @@ pub fn create_ecx<'tcx>(
|
|||
ecx.mark_immutable(&argc_place);
|
||||
ecx.machine.argc = Some(argc_place.ptr());
|
||||
|
||||
let argv_place = ecx.allocate(
|
||||
ecx.layout_of(Ty::new_imm_ptr(tcx, tcx.types.unit))?,
|
||||
MiriMemoryKind::Machine.into(),
|
||||
)?;
|
||||
let argv_place =
|
||||
ecx.allocate(ecx.layout_of(u8_ptr_ptr_type)?, MiriMemoryKind::Machine.into())?;
|
||||
ecx.write_pointer(argvs_place.ptr(), &argv_place)?;
|
||||
ecx.mark_immutable(&argv_place);
|
||||
ecx.machine.argv = Some(argv_place.ptr());
|
||||
|
|
@ -398,7 +395,9 @@ pub fn create_ecx<'tcx>(
|
|||
}
|
||||
ecx.mark_immutable(&cmd_place);
|
||||
}
|
||||
ecx.mplace_to_ref(&argvs_place)?
|
||||
let imm = argvs_place.to_ref(&ecx);
|
||||
let layout = ecx.layout_of(u8_ptr_ptr_type)?;
|
||||
ImmTy::from_immediate(imm, layout)
|
||||
};
|
||||
|
||||
// Return place (in static memory so that it does not count as leak).
|
||||
|
|
|
|||
|
|
@ -470,7 +470,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
caller_fn_abi,
|
||||
&args.iter().map(|a| FnArg::Copy(a.clone().into())).collect::<Vec<_>>(),
|
||||
/*with_caller_location*/ false,
|
||||
&dest,
|
||||
&dest.into(),
|
||||
stack_pop,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
&mut self,
|
||||
instance: ty::Instance<'tcx>,
|
||||
args: &[OpTy<'tcx>],
|
||||
dest: &MPlaceTy<'tcx>,
|
||||
dest: &PlaceTy<'tcx>,
|
||||
ret: Option<mir::BasicBlock>,
|
||||
unwind: mir::UnwindAction,
|
||||
) -> InterpResult<'tcx, Option<ty::Instance<'tcx>>> {
|
||||
|
|
@ -45,7 +45,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
let intrinsic_name = this.tcx.item_name(instance.def_id());
|
||||
let intrinsic_name = intrinsic_name.as_str();
|
||||
|
||||
match this.emulate_intrinsic_by_name(intrinsic_name, instance.args, args, dest, ret)? {
|
||||
// FIXME: avoid allocating memory
|
||||
let dest = this.force_allocation(dest)?;
|
||||
|
||||
match this.emulate_intrinsic_by_name(intrinsic_name, instance.args, args, &dest, ret)? {
|
||||
EmulateItemResult::NotSupported => {
|
||||
// We haven't handled the intrinsic, let's see if we can use a fallback body.
|
||||
if this.tcx.intrinsic(instance.def_id()).unwrap().must_be_overridden {
|
||||
|
|
|
|||
|
|
@ -1115,7 +1115,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
|
|||
instance: ty::Instance<'tcx>,
|
||||
abi: &FnAbi<'tcx, Ty<'tcx>>,
|
||||
args: &[FnArg<'tcx, Provenance>],
|
||||
dest: &MPlaceTy<'tcx>,
|
||||
dest: &PlaceTy<'tcx>,
|
||||
ret: Option<mir::BasicBlock>,
|
||||
unwind: mir::UnwindAction,
|
||||
) -> InterpResult<'tcx, Option<(&'tcx mir::Body<'tcx>, ty::Instance<'tcx>)>> {
|
||||
|
|
@ -1142,7 +1142,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
|
|||
fn_val: DynSym,
|
||||
abi: &FnAbi<'tcx, Ty<'tcx>>,
|
||||
args: &[FnArg<'tcx, Provenance>],
|
||||
dest: &MPlaceTy<'tcx>,
|
||||
dest: &PlaceTy<'tcx>,
|
||||
ret: Option<mir::BasicBlock>,
|
||||
unwind: mir::UnwindAction,
|
||||
) -> InterpResult<'tcx> {
|
||||
|
|
@ -1155,7 +1155,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
|
|||
ecx: &mut MiriInterpCx<'tcx>,
|
||||
instance: ty::Instance<'tcx>,
|
||||
args: &[OpTy<'tcx>],
|
||||
dest: &MPlaceTy<'tcx>,
|
||||
dest: &PlaceTy<'tcx>,
|
||||
ret: Option<mir::BasicBlock>,
|
||||
unwind: mir::UnwindAction,
|
||||
) -> InterpResult<'tcx, Option<ty::Instance<'tcx>>> {
|
||||
|
|
@ -1634,15 +1634,21 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
|
|||
interp_ok(())
|
||||
}
|
||||
|
||||
fn before_stack_pop(
|
||||
ecx: &InterpCx<'tcx, Self>,
|
||||
frame: &Frame<'tcx, Self::Provenance, Self::FrameExtra>,
|
||||
) -> InterpResult<'tcx> {
|
||||
fn before_stack_pop(ecx: &mut InterpCx<'tcx, Self>) -> InterpResult<'tcx> {
|
||||
let frame = ecx.frame();
|
||||
// We want this *before* the return value copy, because the return place itself is protected
|
||||
// until we do `end_call` here.
|
||||
if ecx.machine.borrow_tracker.is_some() {
|
||||
ecx.on_stack_pop(frame)?;
|
||||
}
|
||||
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.
|
||||
// (If this ever becomes a bottleneck, we could have `push` store the previous
|
||||
// user-relevant frame and restore that here.)
|
||||
// We have to skip the frame that is just being popped.
|
||||
ecx.active_thread_mut().recompute_top_user_relevant_frame(/* skip */ 1);
|
||||
}
|
||||
// tracing-tree can autoamtically annotate scope changes, but it gets very confused by our
|
||||
// concurrency and what it prints is just plain wrong. So we print our own information
|
||||
// instead. (Cc https://github.com/rust-lang/miri/issues/2266)
|
||||
|
|
@ -1656,15 +1662,8 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
|
|||
frame: Frame<'tcx, Provenance, FrameExtra<'tcx>>,
|
||||
unwinding: bool,
|
||||
) -> 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.
|
||||
// (If this ever becomes a bottleneck, we could have `push` store the previous
|
||||
// user-relevant frame and restore that here.)
|
||||
ecx.active_thread_mut().recompute_top_user_relevant_frame();
|
||||
}
|
||||
let res = {
|
||||
// Move `frame`` into a sub-scope so we control when it will be dropped.
|
||||
// Move `frame` into a sub-scope so we control when it will be dropped.
|
||||
let mut frame = frame;
|
||||
let timing = frame.extra.timing.take();
|
||||
let res = ecx.handle_stack_pop_unwind(frame.extra, unwinding);
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
link_name: Symbol,
|
||||
abi: &FnAbi<'tcx, Ty<'tcx>>,
|
||||
args: &[OpTy<'tcx>],
|
||||
dest: &MPlaceTy<'tcx>,
|
||||
dest: &PlaceTy<'tcx>,
|
||||
ret: Option<mir::BasicBlock>,
|
||||
unwind: mir::UnwindAction,
|
||||
) -> InterpResult<'tcx, Option<(&'tcx mir::Body<'tcx>, ty::Instance<'tcx>)>> {
|
||||
|
|
@ -69,8 +69,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
_ => {}
|
||||
}
|
||||
|
||||
// FIXME: avoid allocating memory
|
||||
let dest = this.force_allocation(dest)?;
|
||||
|
||||
// The rest either implements the logic, or falls back to `lookup_exported_symbol`.
|
||||
match this.emulate_foreign_item_inner(link_name, abi, args, dest)? {
|
||||
match this.emulate_foreign_item_inner(link_name, abi, args, &dest)? {
|
||||
EmulateItemResult::NeedsReturn => {
|
||||
trace!("{:?}", this.dump_place(&dest.clone().into()));
|
||||
this.return_to_block(ret)?;
|
||||
|
|
@ -111,7 +114,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
sym: DynSym,
|
||||
abi: &FnAbi<'tcx, Ty<'tcx>>,
|
||||
args: &[OpTy<'tcx>],
|
||||
dest: &MPlaceTy<'tcx>,
|
||||
dest: &PlaceTy<'tcx>,
|
||||
ret: Option<mir::BasicBlock>,
|
||||
unwind: mir::UnwindAction,
|
||||
) -> InterpResult<'tcx> {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ struct MakeSend(*const i32);
|
|||
unsafe impl Send for MakeSend {}
|
||||
|
||||
fn main() {
|
||||
race(0);
|
||||
race(0); //~ERROR: Data race detected between (1) non-atomic read on thread `unnamed-1` and (2) deallocation on thread `main`
|
||||
}
|
||||
|
||||
// Using an argument for the ptr to point to, since those do not get StorageDead.
|
||||
|
|
@ -22,5 +22,4 @@ fn race(local: i32) {
|
|||
thread::yield_now();
|
||||
// Deallocating the local (when `main` returns)
|
||||
// races with the read in the other thread.
|
||||
// Make sure the error points at this function's end, not just the call site.
|
||||
} //~ERROR: Data race detected between (1) non-atomic read on thread `unnamed-1` and (2) deallocation on thread `main`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
error: Undefined Behavior: Data race detected between (1) non-atomic read on thread `unnamed-ID` and (2) deallocation on thread `main` at ALLOC. (2) just happened here
|
||||
--> tests/fail/data_race/stack_pop_race.rs:LL:CC
|
||||
|
|
||||
LL | }
|
||||
| ^ Data race detected between (1) non-atomic read on thread `unnamed-ID` and (2) deallocation on thread `main` at ALLOC. (2) just happened here
|
||||
LL | race(0);
|
||||
| ^^^^^^^ Data race detected between (1) non-atomic read on thread `unnamed-ID` and (2) deallocation on thread `main` at ALLOC. (2) just happened here
|
||||
|
|
||||
help: and (1) occurred earlier here
|
||||
--> tests/fail/data_race/stack_pop_race.rs:LL:CC
|
||||
|
|
@ -12,12 +12,7 @@ LL | let _val = unsafe { *ptr.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
|
||||
= note: BACKTRACE (of the first span):
|
||||
= note: inside `race` at tests/fail/data_race/stack_pop_race.rs:LL:CC
|
||||
note: inside `main`
|
||||
--> tests/fail/data_race/stack_pop_race.rs:LL:CC
|
||||
|
|
||||
LL | race(0);
|
||||
| ^^^^^^^
|
||||
= note: inside `main` at tests/fail/data_race/stack_pop_race.rs:LL:CC
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ LL | let local = 0;
|
|||
help: ALLOC was deallocated here:
|
||||
--> tests/fail/tail_calls/dangling-local-var.rs:LL:CC
|
||||
|
|
||||
LL | become g(ptr)
|
||||
| ^^^^^^^^^^^^^
|
||||
LL | f(std::ptr::null());
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
= note: BACKTRACE (of the first span):
|
||||
= note: inside `g` at tests/fail/tail_calls/dangling-local-var.rs:LL:CC
|
||||
note: inside `main`
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#![no_std]
|
||||
#![no_main]
|
||||
//@compile-flags: -Zmiri-track-alloc-id=21 -Zmiri-track-alloc-accesses -Cpanic=abort
|
||||
//@normalize-stderr-test: "id 21" -> "id $$ALLOC"
|
||||
//@compile-flags: -Zmiri-track-alloc-id=20 -Zmiri-track-alloc-accesses -Cpanic=abort
|
||||
//@normalize-stderr-test: "id 20" -> "id $$ALLOC"
|
||||
//@only-target: linux # alloc IDs differ between OSes (due to extern static allocations)
|
||||
|
||||
extern "Rust" {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue