rustc_const_eval/interpret/
stack.rs

1//! Manages the low-level pushing and popping of stack frames and the (de)allocation of local variables.
2//! For handling of argument passing and return values, see the `call` module.
3use std::cell::Cell;
4use std::{fmt, mem};
5
6use either::{Either, Left, Right};
7use rustc_hir as hir;
8use rustc_hir::definitions::DefPathData;
9use rustc_index::IndexVec;
10use rustc_middle::ty::layout::{LayoutOf, TyAndLayout};
11use rustc_middle::ty::{self, Ty, TyCtxt};
12use rustc_middle::{bug, mir};
13use rustc_mir_dataflow::impls::always_storage_live_locals;
14use rustc_span::Span;
15use tracing::{info_span, instrument, trace};
16
17use super::{
18    AllocId, CtfeProvenance, Immediate, InterpCx, InterpResult, Machine, MemPlace, MemPlaceMeta,
19    MemoryKind, Operand, PlaceTy, Pointer, Provenance, ReturnAction, Scalar, from_known_layout,
20    interp_ok, throw_ub, throw_unsup,
21};
22use crate::errors;
23
24// The Phantomdata exists to prevent this type from being `Send`. If it were sent across a thread
25// boundary and dropped in the other thread, it would exit the span in the other thread.
26struct SpanGuard(tracing::Span, std::marker::PhantomData<*const u8>);
27
28impl SpanGuard {
29    /// By default a `SpanGuard` does nothing.
30    fn new() -> Self {
31        Self(tracing::Span::none(), std::marker::PhantomData)
32    }
33
34    /// If a span is entered, we exit the previous span (if any, normally none) and enter the
35    /// new span. This is mainly so we don't have to use `Option` for the `tracing_span` field of
36    /// `Frame` by creating a dummy span to being with and then entering it once the frame has
37    /// been pushed.
38    fn enter(&mut self, span: tracing::Span) {
39        // This executes the destructor on the previous instance of `SpanGuard`, ensuring that
40        // we never enter or exit more spans than vice versa. Unless you `mem::leak`, then we
41        // can't protect the tracing stack, but that'll just lead to weird logging, no actual
42        // problems.
43        *self = Self(span, std::marker::PhantomData);
44        self.0.with_subscriber(|(id, dispatch)| {
45            dispatch.enter(id);
46        });
47    }
48}
49
50impl Drop for SpanGuard {
51    fn drop(&mut self) {
52        self.0.with_subscriber(|(id, dispatch)| {
53            dispatch.exit(id);
54        });
55    }
56}
57
58/// A stack frame.
59pub struct Frame<'tcx, Prov: Provenance = CtfeProvenance, Extra = ()> {
60    ////////////////////////////////////////////////////////////////////////////////
61    // Function and callsite information
62    ////////////////////////////////////////////////////////////////////////////////
63    /// The MIR for the function called on this frame.
64    pub(super) body: &'tcx mir::Body<'tcx>,
65
66    /// The def_id and args of the current function.
67    pub(super) instance: ty::Instance<'tcx>,
68
69    /// Extra data for the machine.
70    pub extra: Extra,
71
72    ////////////////////////////////////////////////////////////////////////////////
73    // Return place and locals
74    ////////////////////////////////////////////////////////////////////////////////
75    /// Work to perform when returning from this function.
76    return_to_block: StackPopCleanup,
77
78    /// The location where the result of the current stack frame should be written to,
79    /// and its layout in the caller. This place is to be interpreted relative to the
80    /// *caller's* stack frame. We use a `PlaceTy` instead of an `MPlaceTy` since this
81    /// avoids having to move *all* return places into Miri's memory.
82    pub return_place: PlaceTy<'tcx, Prov>,
83
84    /// The list of locals for this stack frame, stored in order as
85    /// `[return_ptr, arguments..., variables..., temporaries...]`.
86    /// The locals are stored as `Option<Value>`s.
87    /// `None` represents a local that is currently dead, while a live local
88    /// can either directly contain `Scalar` or refer to some part of an `Allocation`.
89    ///
90    /// Do *not* access this directly; always go through the machine hook!
91    pub locals: IndexVec<mir::Local, LocalState<'tcx, Prov>>,
92
93    /// The span of the `tracing` crate is stored here.
94    /// When the guard is dropped, the span is exited. This gives us
95    /// a full stack trace on all tracing statements.
96    tracing_span: SpanGuard,
97
98    ////////////////////////////////////////////////////////////////////////////////
99    // Current position within the function
100    ////////////////////////////////////////////////////////////////////////////////
101    /// If this is `Right`, we are not currently executing any particular statement in
102    /// this frame (can happen e.g. during frame initialization, and during unwinding on
103    /// frames without cleanup code).
104    ///
105    /// Needs to be public because ConstProp does unspeakable things to it.
106    pub(super) loc: Either<mir::Location, Span>,
107}
108
109#[derive(Clone, Copy, Eq, PartialEq, Debug)] // Miri debug-prints these
110pub enum StackPopCleanup {
111    /// Jump to the next block in the caller, or cause UB if None (that's a function
112    /// that may never return). Also store layout of return place so
113    /// we can validate it at that layout.
114    /// `ret` stores the block we jump to on a normal return, while `unwind`
115    /// stores the block used for cleanup during unwinding.
116    Goto { ret: Option<mir::BasicBlock>, unwind: mir::UnwindAction },
117    /// The root frame of the stack: nowhere else to jump to.
118    /// `cleanup` says whether locals are deallocated. Static computation
119    /// wants them leaked to intern what they need (and just throw away
120    /// the entire `ecx` when it is done).
121    Root { cleanup: bool },
122}
123
124/// Return type of [`InterpCx::pop_stack_frame_raw`].
125pub struct StackPopInfo<'tcx, Prov: Provenance> {
126    /// Additional information about the action to be performed when returning from the popped
127    /// stack frame.
128    pub return_action: ReturnAction,
129
130    /// [`return_to_block`](Frame::return_to_block) of the popped stack frame.
131    pub return_to_block: StackPopCleanup,
132
133    /// [`return_place`](Frame::return_place) of the popped stack frame.
134    pub return_place: PlaceTy<'tcx, Prov>,
135}
136
137/// State of a local variable including a memoized layout
138#[derive(Clone)]
139pub struct LocalState<'tcx, Prov: Provenance = CtfeProvenance> {
140    value: LocalValue<Prov>,
141    /// Don't modify if `Some`, this is only used to prevent computing the layout twice.
142    /// Avoids computing the layout of locals that are never actually initialized.
143    layout: Cell<Option<TyAndLayout<'tcx>>>,
144}
145
146impl<Prov: Provenance> std::fmt::Debug for LocalState<'_, Prov> {
147    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148        f.debug_struct("LocalState")
149            .field("value", &self.value)
150            .field("ty", &self.layout.get().map(|l| l.ty))
151            .finish()
152    }
153}
154
155/// Current value of a local variable
156///
157/// This does not store the type of the local; the type is given by `body.local_decls` and can never
158/// change, so by not storing here we avoid having to maintain that as an invariant.
159#[derive(Copy, Clone, Debug)] // Miri debug-prints these
160pub(super) enum LocalValue<Prov: Provenance = CtfeProvenance> {
161    /// This local is not currently alive, and cannot be used at all.
162    Dead,
163    /// A normal, live local.
164    /// Mostly for convenience, we re-use the `Operand` type here.
165    /// This is an optimization over just always having a pointer here;
166    /// we can thus avoid doing an allocation when the local just stores
167    /// immediate values *and* never has its address taken.
168    Live(Operand<Prov>),
169}
170
171impl<'tcx, Prov: Provenance> LocalState<'tcx, Prov> {
172    pub fn make_live_uninit(&mut self) {
173        self.value = LocalValue::Live(Operand::Immediate(Immediate::Uninit));
174    }
175
176    /// This is a hack because Miri needs a way to visit all the provenance in a `LocalState`
177    /// without having a layout or `TyCtxt` available, and we want to keep the `Operand` type
178    /// private.
179    pub fn as_mplace_or_imm(
180        &self,
181    ) -> Option<Either<(Pointer<Option<Prov>>, MemPlaceMeta<Prov>), Immediate<Prov>>> {
182        match self.value {
183            LocalValue::Dead => None,
184            LocalValue::Live(Operand::Indirect(mplace)) => Some(Left((mplace.ptr, mplace.meta))),
185            LocalValue::Live(Operand::Immediate(imm)) => Some(Right(imm)),
186        }
187    }
188
189    /// Read the local's value or error if the local is not yet live or not live anymore.
190    #[inline(always)]
191    pub(super) fn access(&self) -> InterpResult<'tcx, &Operand<Prov>> {
192        match &self.value {
193            LocalValue::Dead => throw_ub!(DeadLocal), // could even be "invalid program"?
194            LocalValue::Live(val) => interp_ok(val),
195        }
196    }
197
198    /// Overwrite the local. If the local can be overwritten in place, return a reference
199    /// to do so; otherwise return the `MemPlace` to consult instead.
200    #[inline(always)]
201    pub(super) fn access_mut(&mut self) -> InterpResult<'tcx, &mut Operand<Prov>> {
202        match &mut self.value {
203            LocalValue::Dead => throw_ub!(DeadLocal), // could even be "invalid program"?
204            LocalValue::Live(val) => interp_ok(val),
205        }
206    }
207}
208
209/// What we store about a frame in an interpreter backtrace.
210#[derive(Clone, Debug)]
211pub struct FrameInfo<'tcx> {
212    pub instance: ty::Instance<'tcx>,
213    pub span: Span,
214}
215
216// FIXME: only used by miri, should be removed once translatable.
217impl<'tcx> fmt::Display for FrameInfo<'tcx> {
218    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219        ty::tls::with(|tcx| {
220            if tcx.def_key(self.instance.def_id()).disambiguated_data.data == DefPathData::Closure {
221                write!(f, "inside closure")
222            } else {
223                // Note: this triggers a `must_produce_diag` state, which means that if we ever
224                // get here we must emit a diagnostic. We should never display a `FrameInfo` unless
225                // we actually want to emit a warning or error to the user.
226                write!(f, "inside `{}`", self.instance)
227            }
228        })
229    }
230}
231
232impl<'tcx> FrameInfo<'tcx> {
233    pub fn as_note(&self, tcx: TyCtxt<'tcx>) -> errors::FrameNote {
234        let span = self.span;
235        if tcx.def_key(self.instance.def_id()).disambiguated_data.data == DefPathData::Closure {
236            errors::FrameNote {
237                where_: "closure",
238                span,
239                instance: String::new(),
240                times: 0,
241                has_label: false,
242            }
243        } else {
244            let instance = format!("{}", self.instance);
245            // Note: this triggers a `must_produce_diag` state, which means that if we ever get
246            // here we must emit a diagnostic. We should never display a `FrameInfo` unless we
247            // actually want to emit a warning or error to the user.
248            errors::FrameNote { where_: "instance", span, instance, times: 0, has_label: false }
249        }
250    }
251}
252
253impl<'tcx, Prov: Provenance> Frame<'tcx, Prov> {
254    pub fn with_extra<Extra>(self, extra: Extra) -> Frame<'tcx, Prov, Extra> {
255        Frame {
256            body: self.body,
257            instance: self.instance,
258            return_to_block: self.return_to_block,
259            return_place: self.return_place,
260            locals: self.locals,
261            loc: self.loc,
262            extra,
263            tracing_span: self.tracing_span,
264        }
265    }
266}
267
268impl<'tcx, Prov: Provenance, Extra> Frame<'tcx, Prov, Extra> {
269    /// Get the current location within the Frame.
270    ///
271    /// If this is `Right`, we are not currently executing any particular statement in
272    /// this frame (can happen e.g. during frame initialization, and during unwinding on
273    /// frames without cleanup code).
274    ///
275    /// Used by [priroda](https://github.com/oli-obk/priroda).
276    pub fn current_loc(&self) -> Either<mir::Location, Span> {
277        self.loc
278    }
279
280    pub fn body(&self) -> &'tcx mir::Body<'tcx> {
281        self.body
282    }
283
284    pub fn instance(&self) -> ty::Instance<'tcx> {
285        self.instance
286    }
287
288    /// Return the `SourceInfo` of the current instruction.
289    pub fn current_source_info(&self) -> Option<&mir::SourceInfo> {
290        self.loc.left().map(|loc| self.body.source_info(loc))
291    }
292
293    pub fn current_span(&self) -> Span {
294        match self.loc {
295            Left(loc) => self.body.source_info(loc).span,
296            Right(span) => span,
297        }
298    }
299
300    pub fn lint_root(&self, tcx: TyCtxt<'tcx>) -> Option<hir::HirId> {
301        // We first try to get a HirId via the current source scope,
302        // and fall back to `body.source`.
303        self.current_source_info()
304            .and_then(|source_info| match &self.body.source_scopes[source_info.scope].local_data {
305                mir::ClearCrossCrate::Set(data) => Some(data.lint_root),
306                mir::ClearCrossCrate::Clear => None,
307            })
308            .or_else(|| {
309                let def_id = self.body.source.def_id().as_local();
310                def_id.map(|def_id| tcx.local_def_id_to_hir_id(def_id))
311            })
312    }
313
314    /// Returns the address of the buffer where the locals are stored. This is used by `Place` as a
315    /// sanity check to detect bugs where we mix up which stack frame a place refers to.
316    #[inline(always)]
317    pub(super) fn locals_addr(&self) -> usize {
318        self.locals.raw.as_ptr().addr()
319    }
320
321    #[must_use]
322    pub fn generate_stacktrace_from_stack(stack: &[Self]) -> Vec<FrameInfo<'tcx>> {
323        let mut frames = Vec::new();
324        // This deliberately does *not* honor `requires_caller_location` since it is used for much
325        // more than just panics.
326        for frame in stack.iter().rev() {
327            let span = match frame.loc {
328                Left(loc) => {
329                    // If the stacktrace passes through MIR-inlined source scopes, add them.
330                    let mir::SourceInfo { mut span, scope } = *frame.body.source_info(loc);
331                    let mut scope_data = &frame.body.source_scopes[scope];
332                    while let Some((instance, call_span)) = scope_data.inlined {
333                        frames.push(FrameInfo { span, instance });
334                        span = call_span;
335                        scope_data = &frame.body.source_scopes[scope_data.parent_scope.unwrap()];
336                    }
337                    span
338                }
339                Right(span) => span,
340            };
341            frames.push(FrameInfo { span, instance: frame.instance });
342        }
343        trace!("generate stacktrace: {:#?}", frames);
344        frames
345    }
346}
347
348impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
349    /// Very low-level helper that pushes a stack frame without initializing
350    /// the arguments or local variables.
351    ///
352    /// The high-level version of this is `init_stack_frame`.
353    #[instrument(skip(self, body, return_place, return_to_block), level = "debug")]
354    pub(crate) fn push_stack_frame_raw(
355        &mut self,
356        instance: ty::Instance<'tcx>,
357        body: &'tcx mir::Body<'tcx>,
358        return_place: &PlaceTy<'tcx, M::Provenance>,
359        return_to_block: StackPopCleanup,
360    ) -> InterpResult<'tcx> {
361        trace!("body: {:#?}", body);
362
363        // We can push a `Root` frame if and only if the stack is empty.
364        debug_assert_eq!(
365            self.stack().is_empty(),
366            matches!(return_to_block, StackPopCleanup::Root { .. })
367        );
368
369        // First push a stack frame so we have access to `instantiate_from_current_frame` and other
370        // `self.frame()`-based functions.
371        let dead_local = LocalState { value: LocalValue::Dead, layout: Cell::new(None) };
372        let locals = IndexVec::from_elem(dead_local, &body.local_decls);
373        let pre_frame = Frame {
374            body,
375            loc: Right(body.span), // Span used for errors caused during preamble.
376            return_to_block,
377            return_place: return_place.clone(),
378            locals,
379            instance,
380            tracing_span: SpanGuard::new(),
381            extra: (),
382        };
383        let frame = M::init_frame(self, pre_frame)?;
384        self.stack_mut().push(frame);
385
386        // Make sure all the constants required by this frame evaluate successfully (post-monomorphization check).
387        for &const_ in body.required_consts() {
388            let c =
389                self.instantiate_from_current_frame_and_normalize_erasing_regions(const_.const_)?;
390            c.eval(*self.tcx, self.typing_env, const_.span).map_err(|err| {
391                err.emit_note(*self.tcx);
392                err
393            })?;
394        }
395
396        // Finish things up.
397        M::after_stack_push(self)?;
398        self.frame_mut().loc = Left(mir::Location::START);
399        let span = info_span!("frame", "{}", instance);
400        self.frame_mut().tracing_span.enter(span);
401
402        interp_ok(())
403    }
404
405    /// Low-level helper that pops a stack frame from the stack and returns some information about
406    /// it.
407    ///
408    /// This also deallocates locals, if necessary.
409    /// `copy_ret_val` gets called after the frame has been taken from the stack but before the locals have been deallocated.
410    ///
411    /// [`M::before_stack_pop`] and [`M::after_stack_pop`] are called by this function
412    /// automatically.
413    ///
414    /// The high-level version of this is `return_from_current_stack_frame`.
415    ///
416    /// [`M::before_stack_pop`]: Machine::before_stack_pop
417    /// [`M::after_stack_pop`]: Machine::after_stack_pop
418    pub(super) fn pop_stack_frame_raw(
419        &mut self,
420        unwinding: bool,
421        copy_ret_val: impl FnOnce(&mut Self, &PlaceTy<'tcx, M::Provenance>) -> InterpResult<'tcx>,
422    ) -> InterpResult<'tcx, StackPopInfo<'tcx, M::Provenance>> {
423        M::before_stack_pop(self)?;
424        let frame =
425            self.stack_mut().pop().expect("tried to pop a stack frame, but there were none");
426
427        // Copy return value (unless we are unwinding).
428        if !unwinding {
429            copy_ret_val(self, &frame.return_place)?;
430        }
431
432        let return_to_block = frame.return_to_block;
433        let return_place = frame.return_place.clone();
434
435        // Cleanup: deallocate locals.
436        // Usually we want to clean up (deallocate locals), but in a few rare cases we don't.
437        // We do this while the frame is still on the stack, so errors point to the callee.
438        let cleanup = match return_to_block {
439            StackPopCleanup::Goto { .. } => true,
440            StackPopCleanup::Root { cleanup, .. } => cleanup,
441        };
442
443        let return_action = if cleanup {
444            // We need to take the locals out, since we need to mutate while iterating.
445            for local in &frame.locals {
446                self.deallocate_local(local.value)?;
447            }
448
449            // Call the machine hook, which determines the next steps.
450            let return_action = M::after_stack_pop(self, frame, unwinding)?;
451            assert_ne!(return_action, ReturnAction::NoCleanup);
452            return_action
453        } else {
454            // We also skip the machine hook when there's no cleanup. This not a real "pop" anyway.
455            ReturnAction::NoCleanup
456        };
457
458        interp_ok(StackPopInfo { return_action, return_to_block, return_place })
459    }
460
461    /// In the current stack frame, mark all locals as live that are not arguments and don't have
462    /// `Storage*` annotations (this includes the return place).
463    pub(crate) fn storage_live_for_always_live_locals(&mut self) -> InterpResult<'tcx> {
464        self.storage_live(mir::RETURN_PLACE)?;
465
466        let body = self.body();
467        let always_live = always_storage_live_locals(body);
468        for local in body.vars_and_temps_iter() {
469            if always_live.contains(local) {
470                self.storage_live(local)?;
471            }
472        }
473        interp_ok(())
474    }
475
476    pub fn storage_live_dyn(
477        &mut self,
478        local: mir::Local,
479        meta: MemPlaceMeta<M::Provenance>,
480    ) -> InterpResult<'tcx> {
481        trace!("{:?} is now live", local);
482
483        // We avoid `ty.is_trivially_sized` since that does something expensive for ADTs.
484        fn is_very_trivially_sized(ty: Ty<'_>) -> bool {
485            match ty.kind() {
486                ty::Infer(ty::IntVar(_) | ty::FloatVar(_))
487                | ty::Uint(_)
488                | ty::Int(_)
489                | ty::Bool
490                | ty::Float(_)
491                | ty::FnDef(..)
492                | ty::FnPtr(..)
493                | ty::RawPtr(..)
494                | ty::Char
495                | ty::Ref(..)
496                | ty::Coroutine(..)
497                | ty::CoroutineWitness(..)
498                | ty::Array(..)
499                | ty::Closure(..)
500                | ty::CoroutineClosure(..)
501                | ty::Never
502                | ty::Error(_)
503                | ty::Dynamic(_, _, ty::DynStar) => true,
504
505                ty::Str | ty::Slice(_) | ty::Dynamic(_, _, ty::Dyn) | ty::Foreign(..) => false,
506
507                ty::Tuple(tys) => tys.last().is_none_or(|ty| is_very_trivially_sized(*ty)),
508
509                ty::Pat(ty, ..) => is_very_trivially_sized(*ty),
510
511                // We don't want to do any queries, so there is not much we can do with ADTs.
512                ty::Adt(..) => false,
513
514                ty::UnsafeBinder(ty) => is_very_trivially_sized(ty.skip_binder()),
515
516                ty::Alias(..) | ty::Param(_) | ty::Placeholder(..) => false,
517
518                ty::Infer(ty::TyVar(_)) => false,
519
520                ty::Bound(..)
521                | ty::Infer(ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) => {
522                    bug!("`is_very_trivially_sized` applied to unexpected type: {}", ty)
523                }
524            }
525        }
526
527        // This is a hot function, we avoid computing the layout when possible.
528        // `unsized_` will be `None` for sized types and `Some(layout)` for unsized types.
529        let unsized_ = if is_very_trivially_sized(self.body().local_decls[local].ty) {
530            None
531        } else {
532            // We need the layout.
533            let layout = self.layout_of_local(self.frame(), local, None)?;
534            if layout.is_sized() { None } else { Some(layout) }
535        };
536
537        let local_val = LocalValue::Live(if let Some(layout) = unsized_ {
538            if !meta.has_meta() {
539                throw_unsup!(UnsizedLocal);
540            }
541            // Need to allocate some memory, since `Immediate::Uninit` cannot be unsized.
542            let dest_place = self.allocate_dyn(layout, MemoryKind::Stack, meta)?;
543            Operand::Indirect(*dest_place.mplace())
544        } else {
545            // Just make this an efficient immediate.
546            assert!(!meta.has_meta()); // we're dropping the metadata
547            // Make sure the machine knows this "write" is happening. (This is important so that
548            // races involving local variable allocation can be detected by Miri.)
549            M::after_local_write(self, local, /*storage_live*/ true)?;
550            // Note that not calling `layout_of` here does have one real consequence:
551            // if the type is too big, we'll only notice this when the local is actually initialized,
552            // which is a bit too late -- we should ideally notice this already here, when the memory
553            // is conceptually allocated. But given how rare that error is and that this is a hot function,
554            // we accept this downside for now.
555            Operand::Immediate(Immediate::Uninit)
556        });
557
558        // If the local is already live, deallocate its old memory.
559        let old = mem::replace(&mut self.frame_mut().locals[local].value, local_val);
560        self.deallocate_local(old)?;
561        interp_ok(())
562    }
563
564    /// Mark a storage as live, killing the previous content.
565    #[inline(always)]
566    pub fn storage_live(&mut self, local: mir::Local) -> InterpResult<'tcx> {
567        self.storage_live_dyn(local, MemPlaceMeta::None)
568    }
569
570    pub fn storage_dead(&mut self, local: mir::Local) -> InterpResult<'tcx> {
571        assert!(local != mir::RETURN_PLACE, "Cannot make return place dead");
572        trace!("{:?} is now dead", local);
573
574        // If the local is already dead, this is a NOP.
575        let old = mem::replace(&mut self.frame_mut().locals[local].value, LocalValue::Dead);
576        self.deallocate_local(old)?;
577        interp_ok(())
578    }
579
580    fn deallocate_local(&mut self, local: LocalValue<M::Provenance>) -> InterpResult<'tcx> {
581        if let LocalValue::Live(Operand::Indirect(MemPlace { ptr, .. })) = local {
582            // All locals have a backing allocation, even if the allocation is empty
583            // due to the local having ZST type. Hence we can `unwrap`.
584            trace!(
585                "deallocating local {:?}: {:?}",
586                local,
587                // Locals always have a `alloc_id` (they are never the result of a int2ptr).
588                self.dump_alloc(ptr.provenance.unwrap().get_alloc_id().unwrap())
589            );
590            self.deallocate_ptr(ptr, None, MemoryKind::Stack)?;
591        };
592        interp_ok(())
593    }
594
595    /// This is public because it is used by [Aquascope](https://github.com/cognitive-engineering-lab/aquascope/)
596    /// to analyze all the locals in a stack frame.
597    #[inline(always)]
598    pub fn layout_of_local(
599        &self,
600        frame: &Frame<'tcx, M::Provenance, M::FrameExtra>,
601        local: mir::Local,
602        layout: Option<TyAndLayout<'tcx>>,
603    ) -> InterpResult<'tcx, TyAndLayout<'tcx>> {
604        let state = &frame.locals[local];
605        if let Some(layout) = state.layout.get() {
606            return interp_ok(layout);
607        }
608
609        let layout = from_known_layout(self.tcx, self.typing_env, layout, || {
610            let local_ty = frame.body.local_decls[local].ty;
611            let local_ty =
612                self.instantiate_from_frame_and_normalize_erasing_regions(frame, local_ty)?;
613            self.layout_of(local_ty).into()
614        })?;
615
616        // Layouts of locals are requested a lot, so we cache them.
617        state.layout.set(Some(layout));
618        interp_ok(layout)
619    }
620}
621
622impl<'tcx, Prov: Provenance> LocalState<'tcx, Prov> {
623    pub(super) fn print(
624        &self,
625        allocs: &mut Vec<Option<AllocId>>,
626        fmt: &mut std::fmt::Formatter<'_>,
627    ) -> std::fmt::Result {
628        match self.value {
629            LocalValue::Dead => write!(fmt, " is dead")?,
630            LocalValue::Live(Operand::Immediate(Immediate::Uninit)) => {
631                write!(fmt, " is uninitialized")?
632            }
633            LocalValue::Live(Operand::Indirect(mplace)) => {
634                write!(
635                    fmt,
636                    " by {} ref {:?}:",
637                    match mplace.meta {
638                        MemPlaceMeta::Meta(meta) => format!(" meta({meta:?})"),
639                        MemPlaceMeta::None => String::new(),
640                    },
641                    mplace.ptr,
642                )?;
643                allocs.extend(mplace.ptr.provenance.map(Provenance::get_alloc_id));
644            }
645            LocalValue::Live(Operand::Immediate(Immediate::Scalar(val))) => {
646                write!(fmt, " {val:?}")?;
647                if let Scalar::Ptr(ptr, _size) = val {
648                    allocs.push(ptr.provenance.get_alloc_id());
649                }
650            }
651            LocalValue::Live(Operand::Immediate(Immediate::ScalarPair(val1, val2))) => {
652                write!(fmt, " ({val1:?}, {val2:?})")?;
653                if let Scalar::Ptr(ptr, _size) = val1 {
654                    allocs.push(ptr.provenance.get_alloc_id());
655                }
656                if let Scalar::Ptr(ptr, _size) = val2 {
657                    allocs.push(ptr.provenance.get_alloc_id());
658                }
659            }
660        }
661
662        Ok(())
663    }
664}