| //! Manages the low-level pushing and popping of stack frames and the (de)allocation of local variables. | |
| //! For handling of argument passing and return values, see the `call` module. | |
| use std::cell::Cell; | |
| use std::{fmt, mem}; | |
| use either::{Either, Left, Right}; | |
| use rustc_hir as hir; | |
| use rustc_hir::definitions::DefPathData; | |
| use rustc_index::IndexVec; | |
| use rustc_middle::ty::layout::TyAndLayout; | |
| use rustc_middle::ty::{self, Ty, TyCtxt}; | |
| use rustc_middle::{bug, mir}; | |
| use rustc_mir_dataflow::impls::always_storage_live_locals; | |
| use rustc_span::Span; | |
| use rustc_target::callconv::ArgAbi; | |
| use tracing::field::Empty; | |
| use tracing::{info_span, instrument, trace}; | |
| use super::{ | |
| AllocId, CtfeProvenance, FnArg, Immediate, InterpCx, InterpResult, MPlaceTy, Machine, MemPlace, | |
| MemPlaceMeta, MemoryKind, Operand, PlaceTy, Pointer, Provenance, ReturnAction, Scalar, | |
| from_known_layout, interp_ok, throw_ub, throw_unsup, | |
| }; | |
| use crate::{enter_trace_span, errors}; | |
| // 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(super) body: &'tcx mir::Body<'tcx>, | |
| /// The def_id and args of the current function. | |
| pub(super) instance: ty::Instance<'tcx>, | |
| /// Extra data for the machine. | |
| pub extra: Extra, | |
| //////////////////////////////////////////////////////////////////////////////// | |
| // Return place and locals | |
| //////////////////////////////////////////////////////////////////////////////// | |
| /// Where to continue when returning from this function. | |
| return_cont: ReturnContinuation, | |
| /// The location where the result of the current stack frame should be written to, | |
| /// 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. | |
| return_place: PlaceTy<'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 complete variable argument list of this frame. Its elements must be dropped when the | |
| /// frame is popped. | |
| pub(super) va_list: Vec<MPlaceTy<'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(super) loc: Either<mir::Location, Span>, | |
| } | |
| /// Where and how to continue when returning/unwinding from the current function. | |
| #[derive(Clone, Copy, Eq, PartialEq, Debug)] // Miri debug-prints these | |
| pub enum ReturnContinuation { | |
| /// Jump to the next block in the caller, or cause UB if None (that's a function | |
| /// that may never return). | |
| /// `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, so we stop. | |
| /// `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). | |
| Stop { cleanup: bool }, | |
| } | |
| /// 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) => interp_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) => interp_ok(val), | |
| } | |
| } | |
| } | |
| /// 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, | |
| } | |
| // 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(crate) 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, | |
| has_label: false, | |
| } | |
| } 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, has_label: false } | |
| } | |
| } | |
| } | |
| 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_cont: self.return_cont, | |
| return_place: self.return_place, | |
| locals: self.locals, | |
| va_list: self.va_list, | |
| 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](https://github.com/oli-obk/priroda). | |
| pub fn current_loc(&self) -> Either<mir::Location, Span> { | |
| self.loc | |
| } | |
| pub fn body(&self) -> &'tcx mir::Body<'tcx> { | |
| self.body | |
| } | |
| pub fn instance(&self) -> ty::Instance<'tcx> { | |
| 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. | |
| 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], | |
| tcx: TyCtxt<'tcx>, | |
| ) -> 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 mut 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, | |
| }; | |
| if span.is_dummy() { | |
| // Some statements lack a proper span; point at the function instead. | |
| span = tcx.def_span(frame.instance.def_id()); | |
| } | |
| frames.push(FrameInfo { span, instance: frame.instance }); | |
| } | |
| trace!("generate stacktrace: {:#?}", frames); | |
| frames | |
| } | |
| } | |
| impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { | |
| /// Very low-level helper that pushes a stack frame without initializing | |
| /// the arguments or local variables. | |
| /// | |
| /// The high-level version of this is `init_stack_frame`. | |
| #[instrument(skip(self, body, return_place, return_cont), level = "debug")] | |
| pub(crate) fn push_stack_frame_raw( | |
| &mut self, | |
| instance: ty::Instance<'tcx>, | |
| body: &'tcx mir::Body<'tcx>, | |
| return_place: &PlaceTy<'tcx, M::Provenance>, | |
| return_cont: ReturnContinuation, | |
| ) -> InterpResult<'tcx> { | |
| trace!("body: {:#?}", body); | |
| // We can push a `Root` frame if and only if the stack is empty. | |
| debug_assert_eq!( | |
| self.stack().is_empty(), | |
| matches!(return_cont, ReturnContinuation::Stop { .. }) | |
| ); | |
| // First push a stack frame so we have access to `instantiate_from_current_frame` and other | |
| // `self.frame()`-based functions. | |
| 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(self.tcx.def_span(body.source.def_id())), // Span used for errors caused during preamble. | |
| return_cont, | |
| return_place: return_place.clone(), | |
| locals, | |
| va_list: vec![], | |
| instance, | |
| tracing_span: SpanGuard::new(), | |
| extra: (), | |
| }; | |
| let frame = M::init_frame(self, pre_frame)?; | |
| self.stack_mut().push(frame); | |
| // Make sure all the constants required by this frame evaluate successfully (post-monomorphization check). | |
| for &const_ in body.required_consts() { | |
| // We can't use `eval_mir_constant` here as that assumes that all required consts have | |
| // already been checked, so we need a separate tracing call. | |
| let _trace = enter_trace_span!(M, const_eval::required_consts, ?const_.const_); | |
| let c = | |
| self.instantiate_from_current_frame_and_normalize_erasing_regions(const_.const_)?; | |
| c.eval(*self.tcx, self.typing_env, const_.span).map_err(|err| { | |
| err.emit_note(*self.tcx); | |
| err | |
| })?; | |
| } | |
| // Finish things up. | |
| M::after_stack_push(self)?; | |
| // `tracing_separate_thread` is used to instruct the tracing_chrome [tracing::Layer] in Miri | |
| // to put the "frame" span on a separate trace thread/line than other spans, to make the | |
| // visualization in <https://ui.perfetto.dev> easier to interpret. It is set to a value of | |
| // [tracing::field::Empty] so that other tracing layers (e.g. the logger) will ignore it. | |
| let span = info_span!("frame", tracing_separate_thread = Empty, frame = %instance); | |
| self.frame_mut().tracing_span.enter(span); | |
| interp_ok(()) | |
| } | |
| /// Low-level helper that pops a stack frame from the stack without any cleanup. | |
| /// This invokes `before_stack_pop`. | |
| /// After calling this function, you need to deal with the return value, and then | |
| /// invoke `cleanup_stack_frame`. | |
| pub(super) fn pop_stack_frame_raw( | |
| &mut self, | |
| ) -> InterpResult<'tcx, Frame<'tcx, M::Provenance, M::FrameExtra>> { | |
| M::before_stack_pop(self)?; | |
| let frame = | |
| self.stack_mut().pop().expect("tried to pop a stack frame, but there were none"); | |
| interp_ok(frame) | |
| } | |
| /// Deallocate local variables in the stack frame, and invoke `after_stack_pop`. | |
| pub(super) fn cleanup_stack_frame( | |
| &mut self, | |
| unwinding: bool, | |
| frame: Frame<'tcx, M::Provenance, M::FrameExtra>, | |
| ) -> InterpResult<'tcx, ReturnAction> { | |
| let return_cont = frame.return_cont; | |
| // 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 cleanup = match return_cont { | |
| ReturnContinuation::Goto { .. } => true, | |
| ReturnContinuation::Stop { cleanup, .. } => cleanup, | |
| }; | |
| if cleanup { | |
| for local in &frame.locals { | |
| self.deallocate_local(local.value)?; | |
| } | |
| // Deallocate any c-variadic arguments. | |
| self.deallocate_varargs(&frame.va_list)?; | |
| // 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); | |
| interp_ok(return_action) | |
| } else { | |
| // We also skip the machine hook when there's no cleanup. This not a real "pop" anyway. | |
| interp_ok(ReturnAction::NoCleanup) | |
| } | |
| } | |
| /// Call this after `push_stack_frame_raw` and when all the other setup that needs to be done | |
| /// is completed. | |
| pub(crate) fn push_stack_frame_done(&mut self) -> InterpResult<'tcx> { | |
| // Mark all locals as live that are not arguments and don't have `Storage*` annotations | |
| // (this includes the return place, but not the arguments). | |
| 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)?; | |
| } | |
| } | |
| // Get ready to execute the first instruction in the stack frame. | |
| self.frame_mut().loc = Left(mir::Location::START); | |
| interp_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(_) => true, | |
| ty::Str | ty::Slice(_) | ty::Dynamic(_, _) | 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::UnsafeBinder(ty) => is_very_trivially_sized(ty.skip_binder()), | |
| 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 { | |
| // Just make this an efficient immediate. | |
| assert!(!meta.has_meta()); // we're dropping the metadata | |
| // Make sure the machine knows this "write" is happening. (This is important so that | |
| // races involving local variable allocation can be detected by Miri.) | |
| M::after_local_write(self, local, /*storage_live*/ true)?; | |
| // 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)?; | |
| interp_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)?; | |
| interp_ok(()) | |
| } | |
| 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)?; | |
| }; | |
| interp_ok(()) | |
| } | |
| /// This is public because it is used by [Aquascope](https://github.com/cognitive-engineering-lab/aquascope/) | |
| /// to analyze all the locals in a stack frame. | |
| #[inline(always)] | |
| pub 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 interp_ok(layout); | |
| } | |
| let layout = from_known_layout(self.tcx, self.typing_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).into() | |
| })?; | |
| // Layouts of locals are requested a lot, so we cache them. | |
| state.layout.set(Some(layout)); | |
| interp_ok(layout) | |
| } | |
| } | |
| impl<'a, 'tcx: 'a, M: Machine<'tcx>> InterpCx<'tcx, M> { | |
| /// Consume the arguments provided by the iterator and store them as a list | |
| /// of variadic arguments. Return a list of the places that hold those arguments. | |
| pub(crate) fn allocate_varargs<I, J>( | |
| &mut self, | |
| caller_args: I, | |
| mut callee_abis: J, | |
| ) -> InterpResult<'tcx, Vec<MPlaceTy<'tcx, M::Provenance>>> | |
| where | |
| I: Iterator<Item = (&'a FnArg<'tcx, M::Provenance>, &'a ArgAbi<'tcx, Ty<'tcx>>)>, | |
| J: Iterator<Item = (usize, &'a ArgAbi<'tcx, Ty<'tcx>>)>, | |
| { | |
| // Consume the remaining arguments and store them in fresh allocations. | |
| let mut varargs = Vec::new(); | |
| for (fn_arg, caller_abi) in caller_args { | |
| // The callee ABI is entirely computed based on which arguments the caller has | |
| // provided so it should not be possible to get a mismatch here. | |
| let (_idx, callee_abi) = callee_abis.next().unwrap(); | |
| assert!(self.check_argument_compat(caller_abi, callee_abi)?); | |
| // FIXME: do we have to worry about in-place argument passing? | |
| let op = fn_arg.copy_fn_arg(); | |
| let mplace = self.allocate(op.layout, MemoryKind::Stack)?; | |
| self.copy_op(&op, &mplace)?; | |
| varargs.push(mplace); | |
| } | |
| assert!(callee_abis.next().is_none()); | |
| interp_ok(varargs) | |
| } | |
| /// Deallocate the variadic arguments in the list (that must have been created with `allocate_varargs`). | |
| fn deallocate_varargs( | |
| &mut self, | |
| varargs: &[MPlaceTy<'tcx, M::Provenance>], | |
| ) -> InterpResult<'tcx> { | |
| for vararg in varargs { | |
| let ptr = vararg.ptr(); | |
| trace!( | |
| "deallocating vararg {:?}: {:?}", | |
| vararg, | |
| // 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)?; | |
| } | |
| interp_ok(()) | |
| } | |
| } | |
| impl<'tcx, Prov: Provenance> LocalState<'tcx, Prov> { | |
| pub(super) fn print( | |
| &self, | |
| allocs: &mut Vec<Option<AllocId>>, | |
| fmt: &mut std::fmt::Formatter<'_>, | |
| ) -> std::fmt::Result { | |
| match self.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()); | |
| } | |
| } | |
| } | |
| Ok(()) | |
| } | |
| } |