interpret: refactor function call handling to be better-abstracted
This commit is contained in:
parent
8c7e0e1608
commit
522af10ccc
22 changed files with 1337 additions and 1316 deletions
|
@ -1,40 +1,29 @@
|
|||
use std::cell::Cell;
|
||||
use std::{fmt, mem};
|
||||
|
||||
use either::{Either, Left, Right};
|
||||
use either::{Left, Right};
|
||||
use rustc_errors::DiagCtxtHandle;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::definitions::DefPathData;
|
||||
use rustc_hir::{self as hir};
|
||||
use rustc_index::IndexVec;
|
||||
use rustc_infer::infer::at::ToTrace;
|
||||
use rustc_infer::infer::TyCtxtInferExt;
|
||||
use rustc_infer::traits::ObligationCause;
|
||||
use rustc_middle::mir::interpret::{
|
||||
CtfeProvenance, ErrorHandled, InvalidMetaKind, ReportedErrorInfo,
|
||||
};
|
||||
use rustc_middle::mir::interpret::{ErrorHandled, InvalidMetaKind, ReportedErrorInfo};
|
||||
use rustc_middle::query::TyCtxtAt;
|
||||
use rustc_middle::ty::layout::{
|
||||
self, FnAbiError, FnAbiOfHelpers, FnAbiRequest, LayoutError, LayoutOf, LayoutOfHelpers,
|
||||
TyAndLayout,
|
||||
self, FnAbiError, FnAbiOfHelpers, FnAbiRequest, LayoutError, LayoutOfHelpers, TyAndLayout,
|
||||
};
|
||||
use rustc_middle::ty::{self, GenericArgsRef, ParamEnv, Ty, TyCtxt, TypeFoldable, Variance};
|
||||
use rustc_middle::{bug, mir, span_bug};
|
||||
use rustc_mir_dataflow::storage::always_storage_live_locals;
|
||||
use rustc_middle::{mir, span_bug};
|
||||
use rustc_session::Limit;
|
||||
use rustc_span::Span;
|
||||
use rustc_target::abi::call::FnAbi;
|
||||
use rustc_target::abi::{Align, HasDataLayout, Size, TargetDataLayout};
|
||||
use rustc_trait_selection::traits::ObligationCtxt;
|
||||
use tracing::{debug, info, info_span, instrument, trace};
|
||||
use tracing::{debug, trace};
|
||||
|
||||
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,
|
||||
ReturnAction, Scalar,
|
||||
err_inval, throw_inval, throw_ub, throw_ub_custom, Frame, FrameInfo, GlobalId, InterpErrorInfo,
|
||||
InterpResult, MPlaceTy, Machine, MemPlaceMeta, Memory, OpTy, Place, PlaceTy, PointerArithmetic,
|
||||
Projectable, Provenance,
|
||||
};
|
||||
use crate::{errors, fluent_generated as fluent, util, ReportErrorExt};
|
||||
use crate::{fluent_generated as fluent, util, ReportErrorExt};
|
||||
|
||||
pub struct InterpCx<'tcx, M: Machine<'tcx>> {
|
||||
/// Stores the `Machine` instance.
|
||||
|
@ -57,314 +46,6 @@ pub struct InterpCx<'tcx, M: Machine<'tcx>> {
|
|||
pub recursion_limit: Limit,
|
||||
}
|
||||
|
||||
// The Phantomdata exists to prevent this type from being `Send`. If it were sent across a thread
|
||||
// boundary and dropped in the other thread, it would exit the span in the other thread.
|
||||
struct SpanGuard(tracing::Span, std::marker::PhantomData<*const u8>);
|
||||
|
||||
impl SpanGuard {
|
||||
/// By default a `SpanGuard` does nothing.
|
||||
fn new() -> Self {
|
||||
Self(tracing::Span::none(), std::marker::PhantomData)
|
||||
}
|
||||
|
||||
/// If a span is entered, we exit the previous span (if any, normally none) and enter the
|
||||
/// new span. This is mainly so we don't have to use `Option` for the `tracing_span` field of
|
||||
/// `Frame` by creating a dummy span to being with and then entering it once the frame has
|
||||
/// been pushed.
|
||||
fn enter(&mut self, span: tracing::Span) {
|
||||
// This executes the destructor on the previous instance of `SpanGuard`, ensuring that
|
||||
// we never enter or exit more spans than vice versa. Unless you `mem::leak`, then we
|
||||
// can't protect the tracing stack, but that'll just lead to weird logging, no actual
|
||||
// problems.
|
||||
*self = Self(span, std::marker::PhantomData);
|
||||
self.0.with_subscriber(|(id, dispatch)| {
|
||||
dispatch.enter(id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SpanGuard {
|
||||
fn drop(&mut self) {
|
||||
self.0.with_subscriber(|(id, dispatch)| {
|
||||
dispatch.exit(id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// A stack frame.
|
||||
pub struct Frame<'tcx, Prov: Provenance = CtfeProvenance, Extra = ()> {
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Function and callsite information
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// The MIR for the function called on this frame.
|
||||
pub body: &'tcx mir::Body<'tcx>,
|
||||
|
||||
/// The def_id and args of the current function.
|
||||
pub instance: ty::Instance<'tcx>,
|
||||
|
||||
/// Extra data for the machine.
|
||||
pub extra: Extra,
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Return place and locals
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// Work to perform when returning from this function.
|
||||
pub 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>,
|
||||
|
||||
/// The list of locals for this stack frame, stored in order as
|
||||
/// `[return_ptr, arguments..., variables..., temporaries...]`.
|
||||
/// The locals are stored as `Option<Value>`s.
|
||||
/// `None` represents a local that is currently dead, while a live local
|
||||
/// can either directly contain `Scalar` or refer to some part of an `Allocation`.
|
||||
///
|
||||
/// Do *not* access this directly; always go through the machine hook!
|
||||
pub locals: IndexVec<mir::Local, LocalState<'tcx, Prov>>,
|
||||
|
||||
/// The span of the `tracing` crate is stored here.
|
||||
/// When the guard is dropped, the span is exited. This gives us
|
||||
/// a full stack trace on all tracing statements.
|
||||
tracing_span: SpanGuard,
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Current position within the function
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// If this is `Right`, we are not currently executing any particular statement in
|
||||
/// this frame (can happen e.g. during frame initialization, and during unwinding on
|
||||
/// frames without cleanup code).
|
||||
///
|
||||
/// Needs to be public because ConstProp does unspeakable things to it.
|
||||
pub loc: Either<mir::Location, Span>,
|
||||
}
|
||||
|
||||
/// What we store about a frame in an interpreter backtrace.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FrameInfo<'tcx> {
|
||||
pub instance: ty::Instance<'tcx>,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)] // Miri debug-prints these
|
||||
pub enum StackPopCleanup {
|
||||
/// Jump to the next block in the caller, or cause UB if None (that's a function
|
||||
/// that may never return). Also store layout of return place so
|
||||
/// we can validate it at that layout.
|
||||
/// `ret` stores the block we jump to on a normal return, while `unwind`
|
||||
/// stores the block used for cleanup during unwinding.
|
||||
Goto { ret: Option<mir::BasicBlock>, unwind: mir::UnwindAction },
|
||||
/// The root frame of the stack: nowhere else to jump to.
|
||||
/// `cleanup` says whether locals are deallocated. Static computation
|
||||
/// wants them leaked to intern what they need (and just throw away
|
||||
/// the entire `ecx` when it is done).
|
||||
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> {
|
||||
value: LocalValue<Prov>,
|
||||
/// Don't modify if `Some`, this is only used to prevent computing the layout twice.
|
||||
/// Avoids computing the layout of locals that are never actually initialized.
|
||||
layout: Cell<Option<TyAndLayout<'tcx>>>,
|
||||
}
|
||||
|
||||
impl<Prov: Provenance> std::fmt::Debug for LocalState<'_, Prov> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("LocalState")
|
||||
.field("value", &self.value)
|
||||
.field("ty", &self.layout.get().map(|l| l.ty))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Current value of a local variable
|
||||
///
|
||||
/// This does not store the type of the local; the type is given by `body.local_decls` and can never
|
||||
/// change, so by not storing here we avoid having to maintain that as an invariant.
|
||||
#[derive(Copy, Clone, Debug)] // Miri debug-prints these
|
||||
pub(super) enum LocalValue<Prov: Provenance = CtfeProvenance> {
|
||||
/// This local is not currently alive, and cannot be used at all.
|
||||
Dead,
|
||||
/// A normal, live local.
|
||||
/// Mostly for convenience, we re-use the `Operand` type here.
|
||||
/// This is an optimization over just always having a pointer here;
|
||||
/// we can thus avoid doing an allocation when the local just stores
|
||||
/// immediate values *and* never has its address taken.
|
||||
Live(Operand<Prov>),
|
||||
}
|
||||
|
||||
impl<'tcx, Prov: Provenance> LocalState<'tcx, Prov> {
|
||||
pub fn make_live_uninit(&mut self) {
|
||||
self.value = LocalValue::Live(Operand::Immediate(Immediate::Uninit));
|
||||
}
|
||||
|
||||
/// This is a hack because Miri needs a way to visit all the provenance in a `LocalState`
|
||||
/// without having a layout or `TyCtxt` available, and we want to keep the `Operand` type
|
||||
/// private.
|
||||
pub fn as_mplace_or_imm(
|
||||
&self,
|
||||
) -> Option<Either<(Pointer<Option<Prov>>, MemPlaceMeta<Prov>), Immediate<Prov>>> {
|
||||
match self.value {
|
||||
LocalValue::Dead => None,
|
||||
LocalValue::Live(Operand::Indirect(mplace)) => Some(Left((mplace.ptr, mplace.meta))),
|
||||
LocalValue::Live(Operand::Immediate(imm)) => Some(Right(imm)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Read the local's value or error if the local is not yet live or not live anymore.
|
||||
#[inline(always)]
|
||||
pub(super) fn access(&self) -> InterpResult<'tcx, &Operand<Prov>> {
|
||||
match &self.value {
|
||||
LocalValue::Dead => throw_ub!(DeadLocal), // could even be "invalid program"?
|
||||
LocalValue::Live(val) => Ok(val),
|
||||
}
|
||||
}
|
||||
|
||||
/// Overwrite the local. If the local can be overwritten in place, return a reference
|
||||
/// to do so; otherwise return the `MemPlace` to consult instead.
|
||||
#[inline(always)]
|
||||
pub(super) fn access_mut(&mut self) -> InterpResult<'tcx, &mut Operand<Prov>> {
|
||||
match &mut self.value {
|
||||
LocalValue::Dead => throw_ub!(DeadLocal), // could even be "invalid program"?
|
||||
LocalValue::Live(val) => Ok(val),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx, Prov: Provenance> Frame<'tcx, Prov> {
|
||||
pub fn with_extra<Extra>(self, extra: Extra) -> Frame<'tcx, Prov, Extra> {
|
||||
Frame {
|
||||
body: self.body,
|
||||
instance: self.instance,
|
||||
return_to_block: self.return_to_block,
|
||||
return_place: self.return_place,
|
||||
locals: self.locals,
|
||||
loc: self.loc,
|
||||
extra,
|
||||
tracing_span: self.tracing_span,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx, Prov: Provenance, Extra> Frame<'tcx, Prov, Extra> {
|
||||
/// Get the current location within the Frame.
|
||||
///
|
||||
/// If this is `Right`, we are not currently executing any particular statement in
|
||||
/// this frame (can happen e.g. during frame initialization, and during unwinding on
|
||||
/// frames without cleanup code).
|
||||
///
|
||||
/// Used by priroda.
|
||||
pub fn current_loc(&self) -> Either<mir::Location, Span> {
|
||||
self.loc
|
||||
}
|
||||
|
||||
/// Return the `SourceInfo` of the current instruction.
|
||||
pub fn current_source_info(&self) -> Option<&mir::SourceInfo> {
|
||||
self.loc.left().map(|loc| self.body.source_info(loc))
|
||||
}
|
||||
|
||||
pub fn current_span(&self) -> Span {
|
||||
match self.loc {
|
||||
Left(loc) => self.body.source_info(loc).span,
|
||||
Right(span) => span,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lint_root(&self, tcx: TyCtxt<'tcx>) -> Option<hir::HirId> {
|
||||
// We first try to get a HirId via the current source scope,
|
||||
// and fall back to `body.source`.
|
||||
self.current_source_info()
|
||||
.and_then(|source_info| match &self.body.source_scopes[source_info.scope].local_data {
|
||||
mir::ClearCrossCrate::Set(data) => Some(data.lint_root),
|
||||
mir::ClearCrossCrate::Clear => None,
|
||||
})
|
||||
.or_else(|| {
|
||||
let def_id = self.body.source.def_id().as_local();
|
||||
def_id.map(|def_id| tcx.local_def_id_to_hir_id(def_id))
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the address of the buffer where the locals are stored. This is used by `Place` as a
|
||||
/// sanity check to detect bugs where we mix up which stack frame a place refers to.
|
||||
#[inline(always)]
|
||||
pub(super) fn locals_addr(&self) -> usize {
|
||||
self.locals.raw.as_ptr().addr()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn generate_stacktrace_from_stack(stack: &[Self]) -> Vec<FrameInfo<'tcx>> {
|
||||
let mut frames = Vec::new();
|
||||
// This deliberately does *not* honor `requires_caller_location` since it is used for much
|
||||
// more than just panics.
|
||||
for frame in stack.iter().rev() {
|
||||
let span = match frame.loc {
|
||||
Left(loc) => {
|
||||
// If the stacktrace passes through MIR-inlined source scopes, add them.
|
||||
let mir::SourceInfo { mut span, scope } = *frame.body.source_info(loc);
|
||||
let mut scope_data = &frame.body.source_scopes[scope];
|
||||
while let Some((instance, call_span)) = scope_data.inlined {
|
||||
frames.push(FrameInfo { span, instance });
|
||||
span = call_span;
|
||||
scope_data = &frame.body.source_scopes[scope_data.parent_scope.unwrap()];
|
||||
}
|
||||
span
|
||||
}
|
||||
Right(span) => span,
|
||||
};
|
||||
frames.push(FrameInfo { span, instance: frame.instance });
|
||||
}
|
||||
trace!("generate stacktrace: {:#?}", frames);
|
||||
frames
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: only used by miri, should be removed once translatable.
|
||||
impl<'tcx> fmt::Display for FrameInfo<'tcx> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
ty::tls::with(|tcx| {
|
||||
if tcx.def_key(self.instance.def_id()).disambiguated_data.data == DefPathData::Closure {
|
||||
write!(f, "inside closure")
|
||||
} else {
|
||||
// Note: this triggers a `must_produce_diag` state, which means that if we ever
|
||||
// get here we must emit a diagnostic. We should never display a `FrameInfo` unless
|
||||
// we actually want to emit a warning or error to the user.
|
||||
write!(f, "inside `{}`", self.instance)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> FrameInfo<'tcx> {
|
||||
pub fn as_note(&self, tcx: TyCtxt<'tcx>) -> errors::FrameNote {
|
||||
let span = self.span;
|
||||
if tcx.def_key(self.instance.def_id()).disambiguated_data.data == DefPathData::Closure {
|
||||
errors::FrameNote { where_: "closure", span, instance: String::new(), times: 0 }
|
||||
} else {
|
||||
let instance = format!("{}", self.instance);
|
||||
// Note: this triggers a `must_produce_diag` state, which means that if we ever get
|
||||
// here we must emit a diagnostic. We should never display a `FrameInfo` unless we
|
||||
// actually want to emit a warning or error to the user.
|
||||
errors::FrameNote { where_: "instance", span, instance, times: 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx, M: Machine<'tcx>> HasDataLayout for InterpCx<'tcx, M> {
|
||||
#[inline]
|
||||
fn data_layout(&self) -> &TargetDataLayout {
|
||||
|
@ -703,30 +384,6 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||
span_bug!(self.cur_span(), "no non-`#[track_caller]` frame found")
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(super) fn layout_of_local(
|
||||
&self,
|
||||
frame: &Frame<'tcx, M::Provenance, M::FrameExtra>,
|
||||
local: mir::Local,
|
||||
layout: Option<TyAndLayout<'tcx>>,
|
||||
) -> InterpResult<'tcx, TyAndLayout<'tcx>> {
|
||||
let state = &frame.locals[local];
|
||||
if let Some(layout) = state.layout.get() {
|
||||
return Ok(layout);
|
||||
}
|
||||
|
||||
let layout = from_known_layout(self.tcx, self.param_env, layout, || {
|
||||
let local_ty = frame.body.local_decls[local].ty;
|
||||
let local_ty =
|
||||
self.instantiate_from_frame_and_normalize_erasing_regions(frame, local_ty)?;
|
||||
self.layout_of(local_ty)
|
||||
})?;
|
||||
|
||||
// Layouts of locals are requested a lot, so we cache them.
|
||||
state.layout.set(Some(layout));
|
||||
Ok(layout)
|
||||
}
|
||||
|
||||
/// Returns the actual dynamic size and alignment of the place at the given type.
|
||||
/// Only the "meta" (metadata) part of the place matters.
|
||||
/// This can fail to provide an answer for extern types.
|
||||
|
@ -825,132 +482,6 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||
self.size_and_align_of(&mplace.meta(), &mplace.layout)
|
||||
}
|
||||
|
||||
#[instrument(skip(self, body, return_place, return_to_block), level = "debug")]
|
||||
pub fn push_stack_frame(
|
||||
&mut self,
|
||||
instance: ty::Instance<'tcx>,
|
||||
body: &'tcx mir::Body<'tcx>,
|
||||
return_place: &MPlaceTy<'tcx, M::Provenance>,
|
||||
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);
|
||||
let pre_frame = Frame {
|
||||
body,
|
||||
loc: Right(body.span), // Span used for errors caused during preamble.
|
||||
return_to_block,
|
||||
return_place,
|
||||
locals,
|
||||
instance,
|
||||
tracing_span: SpanGuard::new(),
|
||||
extra: (),
|
||||
};
|
||||
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 =
|
||||
self.instantiate_from_current_frame_and_normalize_erasing_regions(const_.const_)?;
|
||||
c.eval(*self.tcx, self.param_env, const_.span).map_err(|err| {
|
||||
err.emit_note(*self.tcx);
|
||||
err
|
||||
})?;
|
||||
}
|
||||
|
||||
// done
|
||||
M::after_stack_push(self)?;
|
||||
self.frame_mut().loc = Left(mir::Location::START);
|
||||
|
||||
let span = info_span!("frame", "{}", instance);
|
||||
self.frame_mut().tracing_span.enter(span);
|
||||
|
||||
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) {
|
||||
|
@ -997,248 +528,6 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Pops the current frame from the stack, deallocating the
|
||||
/// 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,
|
||||
/// and continue execution as normal.
|
||||
///
|
||||
/// If `unwinding` is `true`, then we are in the middle of a panic,
|
||||
/// and need to unwind this frame. In this case, we jump to the
|
||||
/// `cleanup` block for the function, which is responsible for running
|
||||
/// `Drop` impls for any locals that have been initialized at this point.
|
||||
/// The cleanup block ends with a special `Resume` terminator, which will
|
||||
/// cause us to continue unwinding.
|
||||
#[instrument(skip(self), level = "debug")]
|
||||
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" }
|
||||
);
|
||||
|
||||
// Check `unwinding`.
|
||||
assert_eq!(
|
||||
unwinding,
|
||||
match self.frame().loc {
|
||||
Left(loc) => self.body().basic_blocks[loc.block].is_cleanup,
|
||||
Right(_) => true,
|
||||
}
|
||||
);
|
||||
if unwinding && self.frame_idx() == 0 {
|
||||
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.
|
||||
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 err = if self.stack().len() == 1 {
|
||||
// The initializer of constants and statics will get validated separately
|
||||
// after the constant has been fully evaluated. While we could fall back to the default
|
||||
// code path, that will cause -Zenforce-validity to cycle on static initializers.
|
||||
// Reading from a static's memory is not allowed during its evaluation, and will always
|
||||
// trigger a cycle error. Validation must read from the memory of the current item.
|
||||
// For Miri this means we do not validate the root frame return value,
|
||||
// but Miri anyway calls `read_target_isize` on that so separate validation
|
||||
// is not needed.
|
||||
self.copy_op_no_dest_validation(&op, &dest)
|
||||
} else {
|
||||
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.
|
||||
err
|
||||
} else {
|
||||
Ok(())
|
||||
};
|
||||
|
||||
// All right, now it is time to actually pop the frame.
|
||||
let stack_pop_info = self.pop_stack_frame(unwinding)?;
|
||||
|
||||
// Report error from return value copy, if any.
|
||||
copy_ret_result?;
|
||||
|
||||
match stack_pop_info.return_action {
|
||||
ReturnAction::Normal => {}
|
||||
ReturnAction::NoJump => {
|
||||
// The hook already did everything.
|
||||
return Ok(());
|
||||
}
|
||||
ReturnAction::NoCleanup => {
|
||||
// If we are not doing cleanup, also skip everything else.
|
||||
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 stack_pop_info.return_to_block {
|
||||
StackPopCleanup::Goto { unwind, .. } => unwind,
|
||||
StackPopCleanup::Root { .. } => {
|
||||
panic!("encountered StackPopCleanup::Root when unwinding!")
|
||||
}
|
||||
};
|
||||
// This must be the very last thing that happens, since it can in fact push a new stack frame.
|
||||
self.unwind_to_block(unwind)
|
||||
} else {
|
||||
// Follow the normal return edge.
|
||||
match stack_pop_info.return_to_block {
|
||||
StackPopCleanup::Goto { ret, .. } => self.return_to_block(ret),
|
||||
StackPopCleanup::Root { .. } => {
|
||||
assert!(
|
||||
self.stack().is_empty(),
|
||||
"only the topmost frame can have StackPopCleanup::Root"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// In the current stack frame, mark all locals as live that are not arguments and don't have
|
||||
/// `Storage*` annotations (this includes the return place).
|
||||
pub fn storage_live_for_always_live_locals(&mut self) -> InterpResult<'tcx> {
|
||||
self.storage_live(mir::RETURN_PLACE)?;
|
||||
|
||||
let body = self.body();
|
||||
let always_live = always_storage_live_locals(body);
|
||||
for local in body.vars_and_temps_iter() {
|
||||
if always_live.contains(local) {
|
||||
self.storage_live(local)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn storage_live_dyn(
|
||||
&mut self,
|
||||
local: mir::Local,
|
||||
meta: MemPlaceMeta<M::Provenance>,
|
||||
) -> InterpResult<'tcx> {
|
||||
trace!("{:?} is now live", local);
|
||||
|
||||
// We avoid `ty.is_trivially_sized` since that does something expensive for ADTs.
|
||||
fn is_very_trivially_sized(ty: Ty<'_>) -> bool {
|
||||
match ty.kind() {
|
||||
ty::Infer(ty::IntVar(_) | ty::FloatVar(_))
|
||||
| ty::Uint(_)
|
||||
| ty::Int(_)
|
||||
| ty::Bool
|
||||
| ty::Float(_)
|
||||
| ty::FnDef(..)
|
||||
| ty::FnPtr(_)
|
||||
| ty::RawPtr(..)
|
||||
| ty::Char
|
||||
| ty::Ref(..)
|
||||
| ty::Coroutine(..)
|
||||
| ty::CoroutineWitness(..)
|
||||
| ty::Array(..)
|
||||
| ty::Closure(..)
|
||||
| ty::CoroutineClosure(..)
|
||||
| ty::Never
|
||||
| ty::Error(_)
|
||||
| ty::Dynamic(_, _, ty::DynStar) => true,
|
||||
|
||||
ty::Str | ty::Slice(_) | ty::Dynamic(_, _, ty::Dyn) | ty::Foreign(..) => false,
|
||||
|
||||
ty::Tuple(tys) => tys.last().is_none_or(|ty| is_very_trivially_sized(*ty)),
|
||||
|
||||
ty::Pat(ty, ..) => is_very_trivially_sized(*ty),
|
||||
|
||||
// We don't want to do any queries, so there is not much we can do with ADTs.
|
||||
ty::Adt(..) => false,
|
||||
|
||||
ty::Alias(..) | ty::Param(_) | ty::Placeholder(..) => false,
|
||||
|
||||
ty::Infer(ty::TyVar(_)) => false,
|
||||
|
||||
ty::Bound(..)
|
||||
| ty::Infer(ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) => {
|
||||
bug!("`is_very_trivially_sized` applied to unexpected type: {}", ty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is a hot function, we avoid computing the layout when possible.
|
||||
// `unsized_` will be `None` for sized types and `Some(layout)` for unsized types.
|
||||
let unsized_ = if is_very_trivially_sized(self.body().local_decls[local].ty) {
|
||||
None
|
||||
} else {
|
||||
// We need the layout.
|
||||
let layout = self.layout_of_local(self.frame(), local, None)?;
|
||||
if layout.is_sized() { None } else { Some(layout) }
|
||||
};
|
||||
|
||||
let local_val = LocalValue::Live(if let Some(layout) = unsized_ {
|
||||
if !meta.has_meta() {
|
||||
throw_unsup!(UnsizedLocal);
|
||||
}
|
||||
// Need to allocate some memory, since `Immediate::Uninit` cannot be unsized.
|
||||
let dest_place = self.allocate_dyn(layout, MemoryKind::Stack, meta)?;
|
||||
Operand::Indirect(*dest_place.mplace())
|
||||
} else {
|
||||
assert!(!meta.has_meta()); // we're dropping the metadata
|
||||
// Just make this an efficient immediate.
|
||||
// Note that not calling `layout_of` here does have one real consequence:
|
||||
// if the type is too big, we'll only notice this when the local is actually initialized,
|
||||
// which is a bit too late -- we should ideally notice this already here, when the memory
|
||||
// is conceptually allocated. But given how rare that error is and that this is a hot function,
|
||||
// we accept this downside for now.
|
||||
Operand::Immediate(Immediate::Uninit)
|
||||
});
|
||||
|
||||
// If the local is already live, deallocate its old memory.
|
||||
let old = mem::replace(&mut self.frame_mut().locals[local].value, local_val);
|
||||
self.deallocate_local(old)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Mark a storage as live, killing the previous content.
|
||||
#[inline(always)]
|
||||
pub fn storage_live(&mut self, local: mir::Local) -> InterpResult<'tcx> {
|
||||
self.storage_live_dyn(local, MemPlaceMeta::None)
|
||||
}
|
||||
|
||||
pub fn storage_dead(&mut self, local: mir::Local) -> InterpResult<'tcx> {
|
||||
assert!(local != mir::RETURN_PLACE, "Cannot make return place dead");
|
||||
trace!("{:?} is now dead", local);
|
||||
|
||||
// If the local is already dead, this is a NOP.
|
||||
let old = mem::replace(&mut self.frame_mut().locals[local].value, LocalValue::Dead);
|
||||
self.deallocate_local(old)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip(self), level = "debug")]
|
||||
fn deallocate_local(&mut self, local: LocalValue<M::Provenance>) -> InterpResult<'tcx> {
|
||||
if let LocalValue::Live(Operand::Indirect(MemPlace { ptr, .. })) = local {
|
||||
// All locals have a backing allocation, even if the allocation is empty
|
||||
// due to the local having ZST type. Hence we can `unwrap`.
|
||||
trace!(
|
||||
"deallocating local {:?}: {:?}",
|
||||
local,
|
||||
// Locals always have a `alloc_id` (they are never the result of a int2ptr).
|
||||
self.dump_alloc(ptr.provenance.unwrap().get_alloc_id().unwrap())
|
||||
);
|
||||
self.deallocate_ptr(ptr, None, MemoryKind::Stack)?;
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Call a query that can return `ErrorHandled`. Should be used for statics and other globals.
|
||||
/// (`mir::Const`/`ty::Const` have `eval` methods that can be used directly instead.)
|
||||
pub fn ctfe_query<T>(
|
||||
|
@ -1328,39 +617,7 @@ impl<'a, 'tcx, M: Machine<'tcx>> std::fmt::Debug for PlacePrinter<'a, 'tcx, M> {
|
|||
}
|
||||
write!(fmt, ":")?;
|
||||
|
||||
match self.ecx.frame().locals[local].value {
|
||||
LocalValue::Dead => write!(fmt, " is dead")?,
|
||||
LocalValue::Live(Operand::Immediate(Immediate::Uninit)) => {
|
||||
write!(fmt, " is uninitialized")?
|
||||
}
|
||||
LocalValue::Live(Operand::Indirect(mplace)) => {
|
||||
write!(
|
||||
fmt,
|
||||
" by {} ref {:?}:",
|
||||
match mplace.meta {
|
||||
MemPlaceMeta::Meta(meta) => format!(" meta({meta:?})"),
|
||||
MemPlaceMeta::None => String::new(),
|
||||
},
|
||||
mplace.ptr,
|
||||
)?;
|
||||
allocs.extend(mplace.ptr.provenance.map(Provenance::get_alloc_id));
|
||||
}
|
||||
LocalValue::Live(Operand::Immediate(Immediate::Scalar(val))) => {
|
||||
write!(fmt, " {val:?}")?;
|
||||
if let Scalar::Ptr(ptr, _size) = val {
|
||||
allocs.push(ptr.provenance.get_alloc_id());
|
||||
}
|
||||
}
|
||||
LocalValue::Live(Operand::Immediate(Immediate::ScalarPair(val1, val2))) => {
|
||||
write!(fmt, " ({val1:?}, {val2:?})")?;
|
||||
if let Scalar::Ptr(ptr, _size) = val1 {
|
||||
allocs.push(ptr.provenance.get_alloc_id());
|
||||
}
|
||||
if let Scalar::Ptr(ptr, _size) = val2 {
|
||||
allocs.push(ptr.provenance.get_alloc_id());
|
||||
}
|
||||
}
|
||||
}
|
||||
self.ecx.frame().locals[local].print(&mut allocs, fmt)?;
|
||||
|
||||
write!(fmt, ": {:?}", self.ecx.dump_allocs(allocs.into_iter().flatten().collect()))
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue