| use std::cell::RefCell; | |
| use std::collections::hash_map::Entry; | |
| use either::{Left, Right}; | |
| use rustc_abi::{Align, HasDataLayout, Size, TargetDataLayout}; | |
| use rustc_data_structures::fx::FxHashMap; | |
| use rustc_hir::def_id::DefId; | |
| use rustc_middle::mir::interpret::{ErrorHandled, InvalidMetaKind, ReportedErrorInfo}; | |
| use rustc_middle::query::TyCtxtAt; | |
| use rustc_middle::ty::layout::{ | |
| self, FnAbiError, FnAbiOf, FnAbiOfHelpers, FnAbiRequest, LayoutError, LayoutOf, | |
| LayoutOfHelpers, TyAndLayout, | |
| }; | |
| use rustc_middle::ty::{ | |
| self, GenericArgsRef, Ty, TyCtxt, TypeFoldable, TypeVisitableExt, TypingEnv, Variance, | |
| }; | |
| use rustc_middle::{mir, span_bug}; | |
| use rustc_span::Span; | |
| use rustc_structures::Limit; | |
| use rustc_target::callconv::FnAbi; | |
| use tracing::{debug, trace}; | |
| use super::{ | |
| Frame, FrameInfo, GlobalId, InterpErrorKind, InterpResult, MPlaceTy, Machine, MemPlaceMeta, | |
| Memory, OpTy, Place, PlaceTy, PointerArithmetic, Projectable, Provenance, err_inval, interp_ok, | |
| throw_inval, throw_ub, throw_ub_format, | |
| }; | |
| use crate::{enter_trace_span, util}; | |
| pub struct InterpCx<'tcx, M: Machine<'tcx>> { | |
| /// Stores the `Machine` instance. | |
| /// | |
| /// Note: the stack is provided by the machine. | |
| pub machine: M, | |
| /// The results of the type checker, from rustc. | |
| /// The span in this is the "root" of the evaluation, i.e., the const | |
| /// we are evaluating (if this is CTFE). | |
| pub tcx: TyCtxtAt<'tcx>, | |
| /// The current context in case we're evaluating in a | |
| /// polymorphic context. This always uses `ty::TypingMode::PostAnalysis`. | |
| pub(super) typing_env: ty::TypingEnv<'tcx>, | |
| /// The query cache is slow so we have our own cache in front of it. | |
| pub(super) layout_cache: RefCell<FxHashMap<Ty<'tcx>, rustc_abi::Layout<'tcx>>>, | |
| /// The virtual memory system. | |
| pub memory: Memory<'tcx, M>, | |
| /// The recursion limit (cached from `tcx.recursion_limit(())`) | |
| pub recursion_limit: Limit, | |
| } | |
| impl<'tcx, M: Machine<'tcx>> HasDataLayout for InterpCx<'tcx, M> { | |
| #[inline] | |
| fn data_layout(&self) -> &TargetDataLayout { | |
| &self.tcx.data_layout | |
| } | |
| } | |
| impl<'tcx, M> layout::HasTyCtxt<'tcx> for InterpCx<'tcx, M> | |
| where | |
| M: Machine<'tcx>, | |
| { | |
| #[inline] | |
| fn tcx(&self) -> TyCtxt<'tcx> { | |
| *self.tcx | |
| } | |
| } | |
| impl<'tcx, M> layout::HasTypingEnv<'tcx> for InterpCx<'tcx, M> | |
| where | |
| M: Machine<'tcx>, | |
| { | |
| fn typing_env(&self) -> ty::TypingEnv<'tcx> { | |
| self.typing_env | |
| } | |
| } | |
| impl<'tcx, M: Machine<'tcx>> LayoutOfHelpers<'tcx> for InterpCx<'tcx, M> { | |
| type LayoutOfResult = Result<TyAndLayout<'tcx>, InterpErrorKind<'tcx>>; | |
| #[inline] | |
| fn layout_tcx_at_span(&self) -> Span { | |
| // Using the cheap root span for performance. | |
| self.tcx.span | |
| } | |
| #[inline] | |
| fn handle_layout_err( | |
| &self, | |
| mut err: LayoutError<'tcx>, | |
| _: Span, | |
| _: Ty<'tcx>, | |
| ) -> InterpErrorKind<'tcx> { | |
| // FIXME(#149283): This is really hacky and is only used to hide type | |
| // system bugs. We use it as a temporary fix for #149081. | |
| // | |
| // While it's expected that we sometimes get ambiguity errors when | |
| // entering another generic environment while the current environment | |
| // itself is still generic, we should never fail to entirely prove | |
| // something. | |
| match err { | |
| LayoutError::NormalizationFailure(ty, _) => { | |
| if ty.has_non_region_param() { | |
| err = LayoutError::TooGeneric(ty); | |
| } | |
| } | |
| LayoutError::Unknown(_) | |
| | LayoutError::SizeOverflow(_) | |
| | LayoutError::InvalidSimd { .. } | |
| | LayoutError::TooGeneric(_) | |
| | LayoutError::ReferencesError(_) => {} | |
| } | |
| err_inval!(Layout(err)) | |
| } | |
| } | |
| impl<'tcx, M: Machine<'tcx>> FnAbiOfHelpers<'tcx> for InterpCx<'tcx, M> { | |
| type FnAbiOfResult = Result<&'tcx FnAbi<'tcx, Ty<'tcx>>, InterpErrorKind<'tcx>>; | |
| fn handle_fn_abi_err( | |
| &self, | |
| err: FnAbiError<'tcx>, | |
| _span: Span, | |
| _fn_abi_request: FnAbiRequest<'tcx>, | |
| ) -> InterpErrorKind<'tcx> { | |
| match err { | |
| FnAbiError::Layout(err) => err_inval!(Layout(err)), | |
| } | |
| } | |
| } | |
| impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { | |
| /// This inherent method takes priority over the trait method with the same name in LayoutOf, | |
| /// and allows wrapping the actual [LayoutOf::layout_of] with a tracing span. | |
| /// See [LayoutOf::layout_of] for the original documentation. | |
| #[inline] | |
| pub fn layout_of(&self, ty: Ty<'tcx>) -> Result<TyAndLayout<'tcx>, InterpErrorKind<'tcx>> { | |
| match self.layout_cache.borrow_mut().entry(ty) { | |
| Entry::Occupied(occupied_entry) => { | |
| Ok(TyAndLayout { ty, layout: *occupied_entry.get() }) | |
| } | |
| Entry::Vacant(vacant_entry) => { | |
| let _trace = enter_trace_span!(M, layouting::layout_of, ty = ?ty.kind()); | |
| let layout = LayoutOf::layout_of(self, ty)?; | |
| vacant_entry.insert(layout.layout); | |
| Ok(layout) | |
| } | |
| } | |
| } | |
| /// This inherent method takes priority over the trait method with the same name in FnAbiOf, | |
| /// and allows wrapping the actual [FnAbiOf::fn_abi_of_fn_ptr] with a tracing span. | |
| /// See [FnAbiOf::fn_abi_of_fn_ptr] for the original documentation. | |
| #[inline(always)] | |
| pub fn fn_abi_of_fn_ptr( | |
| &self, | |
| sig: ty::PolyFnSig<'tcx>, | |
| extra_args: &'tcx ty::List<Ty<'tcx>>, | |
| ) -> <Self as FnAbiOfHelpers<'tcx>>::FnAbiOfResult { | |
| let _trace = enter_trace_span!(M, layouting::fn_abi_of_fn_ptr, ?sig, ?extra_args); | |
| FnAbiOf::fn_abi_of_fn_ptr(self, sig, extra_args) | |
| } | |
| /// This inherent method takes priority over the trait method with the same name in FnAbiOf, | |
| /// and allows wrapping the actual [FnAbiOf::fn_abi_of_instance_no_deduced_attrs] with a tracing span. | |
| /// See [FnAbiOf::fn_abi_of_instance_no_deduced_attrs] for the original documentation. | |
| #[inline(always)] | |
| pub fn fn_abi_of_instance_no_deduced_attrs( | |
| &self, | |
| instance: ty::Instance<'tcx>, | |
| extra_args: &'tcx ty::List<Ty<'tcx>>, | |
| ) -> <Self as FnAbiOfHelpers<'tcx>>::FnAbiOfResult { | |
| let _trace = enter_trace_span!(M, layouting::fn_abi_of_instance, ?instance, ?extra_args); | |
| FnAbiOf::fn_abi_of_instance_no_deduced_attrs(self, instance, extra_args) | |
| } | |
| } | |
| /// Test if it is valid for a MIR assignment to assign `src`-typed place to `dest`-typed value. | |
| pub(super) fn mir_assign_valid_types<'tcx>( | |
| tcx: TyCtxt<'tcx>, | |
| typing_env: TypingEnv<'tcx>, | |
| src: TyAndLayout<'tcx>, | |
| dest: TyAndLayout<'tcx>, | |
| ) -> bool { | |
| // We *could* check `Invariant` here since all subtyping must be explicit post-borrowck. | |
| // However, this check is also used by the interpreter to figure out if a transmute can be | |
| // turned into a regular assignment (which has a more efficient codepath), so we want the check | |
| // to consider as many assignments as possible to be valid. Therefore we are happy to accept | |
| // one-way subtyping. | |
| if util::relate_types(tcx, typing_env, Variance::Covariant, src.ty, dest.ty) { | |
| // Make sure the layout is equal, too -- just to be safe. Miri really needs layout equality. | |
| // For performance reason we skip this check when the types are equal. Equal types *can* | |
| // have different layouts when enum downcast is involved (as enum variants carry the type of | |
| // the enum), but those should never occur in assignments. | |
| if cfg!(debug_assertions) || src.ty != dest.ty { | |
| assert_eq!( | |
| src.layout, | |
| dest.layout, | |
| "{src} is a subtype of {dest} but they have different layout", | |
| src = src.ty, | |
| dest = dest.ty, | |
| ); | |
| } | |
| true | |
| } else { | |
| false | |
| } | |
| } | |
| /// Use the already known layout if given (but sanity check in debug mode), | |
| /// or compute the layout. | |
| #[cfg_attr(not(debug_assertions), inline(always))] | |
| pub(super) fn from_known_layout<'tcx>( | |
| tcx: TyCtxtAt<'tcx>, | |
| typing_env: TypingEnv<'tcx>, | |
| known_layout: Option<TyAndLayout<'tcx>>, | |
| compute: impl FnOnce() -> InterpResult<'tcx, TyAndLayout<'tcx>>, | |
| ) -> InterpResult<'tcx, TyAndLayout<'tcx>> { | |
| match known_layout { | |
| None => compute(), | |
| Some(known_layout) => { | |
| if cfg!(debug_assertions) { | |
| let check_layout = compute()?; | |
| if !mir_assign_valid_types(tcx.tcx, typing_env, check_layout, known_layout) { | |
| span_bug!( | |
| tcx.span, | |
| "expected type differs from actual type.\nexpected: {}\nactual: {}", | |
| known_layout.ty, | |
| check_layout.ty, | |
| ); | |
| } | |
| } | |
| interp_ok(known_layout) | |
| } | |
| } | |
| } | |
| impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { | |
| pub fn new( | |
| tcx: TyCtxt<'tcx>, | |
| root_span: Span, | |
| typing_env: ty::TypingEnv<'tcx>, | |
| machine: M, | |
| ) -> Self { | |
| crate::assert_typing_mode(typing_env.typing_mode()); | |
| InterpCx { | |
| machine, | |
| tcx: tcx.at(root_span), | |
| typing_env, | |
| layout_cache: RefCell::new(FxHashMap::default()), | |
| memory: Memory::new(), | |
| recursion_limit: tcx.recursion_limit(), | |
| } | |
| } | |
| /// Returns the span of the currently executed statement/terminator. | |
| /// This is the span typically used for error reporting. | |
| #[inline(always)] | |
| pub fn cur_span(&self) -> Span { | |
| // This deliberately does *not* honor `requires_caller_location` since it is used for much | |
| // more than just panics. | |
| self.stack().last().map_or(self.tcx.span, |f| f.current_span()) | |
| } | |
| pub(crate) fn stack(&self) -> &[Frame<'tcx, M::Provenance, M::FrameExtra>] { | |
| M::stack(self) | |
| } | |
| #[inline(always)] | |
| pub(crate) fn stack_mut(&mut self) -> &mut Vec<Frame<'tcx, M::Provenance, M::FrameExtra>> { | |
| M::stack_mut(self) | |
| } | |
| #[inline(always)] | |
| pub fn frame_idx(&self) -> usize { | |
| let stack = self.stack(); | |
| assert!(!stack.is_empty()); | |
| stack.len() - 1 | |
| } | |
| #[inline(always)] | |
| pub fn frame(&self) -> &Frame<'tcx, M::Provenance, M::FrameExtra> { | |
| self.stack().last().expect("no call frames exist") | |
| } | |
| #[inline(always)] | |
| pub fn frame_mut(&mut self) -> &mut Frame<'tcx, M::Provenance, M::FrameExtra> { | |
| self.stack_mut().last_mut().expect("no call frames exist") | |
| } | |
| #[inline(always)] | |
| pub fn body(&self) -> &'tcx mir::Body<'tcx> { | |
| self.frame().body | |
| } | |
| #[inline] | |
| pub fn type_is_freeze(&self, ty: Ty<'tcx>) -> bool { | |
| ty.is_freeze(*self.tcx, self.typing_env) | |
| } | |
| pub fn load_mir( | |
| &self, | |
| instance: ty::InstanceKind<'tcx>, | |
| promoted: Option<mir::Promoted>, | |
| ) -> InterpResult<'tcx, &'tcx mir::Body<'tcx>> { | |
| trace!("load mir(instance={:?}, promoted={:?})", instance, promoted); | |
| let body = if let Some(promoted) = promoted { | |
| let def = instance.def_id(); | |
| &self.tcx.promoted_mir(def)[promoted] | |
| } else { | |
| M::load_mir(self, instance) | |
| }; | |
| // do not continue if typeck errors occurred (can only occur in local crate) | |
| if let Some(err) = body.tainted_by_errors { | |
| throw_inval!(AlreadyReported(ReportedErrorInfo::non_const_eval_error(err))); | |
| } | |
| interp_ok(body) | |
| } | |
| /// Call this on things you got out of the MIR (so it is as generic as the current | |
| /// stack frame), to bring it into the proper environment for this interpreter. | |
| pub fn instantiate_from_current_frame_and_normalize_erasing_regions< | |
| T: TypeFoldable<TyCtxt<'tcx>>, | |
| >( | |
| &self, | |
| value: T, | |
| ) -> Result<T, ErrorHandled> { | |
| self.instantiate_from_frame_and_normalize_erasing_regions(self.frame(), value) | |
| } | |
| /// Call this on things you got out of the MIR (so it is as generic as the provided | |
| /// stack frame), to bring it into the proper environment for this interpreter. | |
| pub fn instantiate_from_frame_and_normalize_erasing_regions<T: TypeFoldable<TyCtxt<'tcx>>>( | |
| &self, | |
| frame: &Frame<'tcx, M::Provenance, M::FrameExtra>, | |
| value: T, | |
| ) -> Result<T, ErrorHandled> { | |
| let _trace = enter_trace_span!( | |
| M, | |
| "instantiate_from_frame_and_normalize_erasing_regions", | |
| %frame.instance | |
| ); | |
| frame | |
| .instance | |
| .try_instantiate_mir_and_normalize_erasing_regions( | |
| *self.tcx, | |
| self.typing_env, | |
| ty::EarlyBinder::bind(self.tcx.tcx, value), | |
| ) | |
| .map_err(|_| ErrorHandled::TooGeneric(self.cur_span())) | |
| } | |
| /// The `args` are assumed to already be in our interpreter "universe". | |
| pub(super) fn resolve( | |
| &self, | |
| def: DefId, | |
| args: GenericArgsRef<'tcx>, | |
| ) -> InterpResult<'tcx, ty::Instance<'tcx>> { | |
| let _trace = enter_trace_span!(M, resolve::try_resolve, def = ?def); | |
| trace!("resolve: {:?}, {:#?}", def, args); | |
| trace!("typing_env: {:#?}", self.typing_env); | |
| trace!("args: {:#?}", args); | |
| match ty::Instance::try_resolve(*self.tcx, self.typing_env, def, args) { | |
| Ok(Some(instance)) => interp_ok(instance), | |
| Ok(None) => throw_inval!(TooGeneric), | |
| // FIXME(eddyb) this could be a bit more specific than `AlreadyReported`. | |
| Err(error_guaranteed) => throw_inval!(AlreadyReported( | |
| ReportedErrorInfo::non_const_eval_error(error_guaranteed) | |
| )), | |
| } | |
| } | |
| /// Walks up the callstack from the intrinsic's callsite, searching for the first callsite in a | |
| /// frame which is not `#[track_caller]`. This matches the `caller_location` intrinsic, | |
| /// and is primarily intended for the panic machinery. | |
| pub(crate) fn find_closest_untracked_caller_location(&self) -> Span { | |
| for frame in self.stack().iter().rev() { | |
| debug!("find_closest_untracked_caller_location: checking frame {:?}", frame.instance); | |
| // Assert that the frame we look at is actually executing code currently | |
| // (`loc` is `Right` when we are unwinding and the frame does not require cleanup). | |
| let loc = frame.loc.left().unwrap(); | |
| // This could be a non-`Call` terminator (such as `Drop`), or not a terminator at all | |
| // (such as `box`). Use the normal span by default. | |
| let mut source_info = *frame.body.source_info(loc); | |
| // If this is a `Call` terminator, use the `fn_span` instead. | |
| let block = &frame.body.basic_blocks[loc.block]; | |
| if loc.statement_index == block.statements.len() { | |
| debug!( | |
| "find_closest_untracked_caller_location: got terminator {:?} ({:?})", | |
| block.terminator(), | |
| block.terminator().kind, | |
| ); | |
| if let mir::TerminatorKind::Call { fn_span, .. } = block.terminator().kind { | |
| source_info.span = fn_span; | |
| } | |
| } | |
| let caller_location = if frame.instance.def.requires_caller_location(*self.tcx) { | |
| // We use `Err(())` as indication that we should continue up the call stack since | |
| // this is a `#[track_caller]` function. | |
| Some(Err(())) | |
| } else { | |
| None | |
| }; | |
| if let Ok(span) = | |
| frame.body.caller_location_span(source_info, caller_location, *self.tcx, Ok) | |
| { | |
| return span; | |
| } | |
| } | |
| span_bug!(self.cur_span(), "no non-`#[track_caller]` frame found") | |
| } | |
| /// 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. | |
| pub(super) fn size_and_align_from_meta( | |
| &self, | |
| metadata: &MemPlaceMeta<M::Provenance>, | |
| layout: &TyAndLayout<'tcx>, | |
| ) -> InterpResult<'tcx, Option<(Size, Align)>> { | |
| if layout.is_sized() { | |
| return interp_ok(Some((layout.size, layout.align.abi))); | |
| } | |
| match layout.ty.kind() { | |
| ty::Adt(..) | ty::Tuple(..) => { | |
| // First get the size of all statically known fields. | |
| // Don't use type_of::sizing_type_of because that expects t to be sized, | |
| // and it also rounds up to alignment, which we want to avoid, | |
| // as the unsized field's alignment could be smaller. | |
| assert!(!layout.ty.is_simd()); | |
| assert!(layout.fields.count() > 0); | |
| trace!("DST layout: {:?}", layout); | |
| let unsized_offset_unadjusted = layout.fields.offset(layout.fields.count() - 1); | |
| let sized_align = layout.align.abi; | |
| // Recurse to get the size of the dynamically sized field (must be | |
| // the last field). Can't have foreign types here, how would we | |
| // adjust alignment and size for them? | |
| let field = layout.field(self, layout.fields.count() - 1); | |
| let Some((unsized_size, mut unsized_align)) = | |
| self.size_and_align_from_meta(metadata, &field)? | |
| else { | |
| // A field with an extern type. We don't know the actual dynamic size | |
| // or the alignment. | |
| return interp_ok(None); | |
| }; | |
| // # First compute the dynamic alignment | |
| // Packed type alignment needs to be capped. | |
| if let ty::Adt(def, _) = layout.ty.kind() | |
| && let Some(packed) = def.repr().pack | |
| { | |
| unsized_align = unsized_align.min(packed); | |
| } | |
| // Choose max of two known alignments (combined value must | |
| // be aligned according to more restrictive of the two). | |
| let full_align = sized_align.max(unsized_align); | |
| // # Then compute the dynamic size | |
| let unsized_offset_adjusted = unsized_offset_unadjusted.align_to(unsized_align); | |
| let full_size = (unsized_offset_adjusted + unsized_size).align_to(full_align); | |
| // Just for our sanitiy's sake, assert that this is equal to what codegen would compute. | |
| assert_eq!( | |
| full_size, | |
| (unsized_offset_unadjusted + unsized_size).align_to(full_align) | |
| ); | |
| // Check if this brought us over the size limit. | |
| if full_size > self.max_size_of_val() { | |
| throw_ub!(InvalidMeta(InvalidMetaKind::TooBig)); | |
| } | |
| interp_ok(Some((full_size, full_align))) | |
| } | |
| ty::Dynamic(expected_trait, _) => { | |
| let vtable = metadata.unwrap_meta().to_pointer(self); | |
| // Read size and align from vtable (already checks size). | |
| interp_ok(Some(self.get_vtable_size_and_align(vtable, Some(expected_trait))?)) | |
| } | |
| ty::Slice(_) | ty::Str => { | |
| let len = metadata.unwrap_meta().to_target_usize(self)?; | |
| let elem = layout.field(self, 0); | |
| // Make sure the slice is not too big. | |
| let size = elem.size.bytes().saturating_mul(len); // we rely on `max_size_of_val` being smaller than `u64::MAX`. | |
| let size = Size::from_bytes(size); | |
| if size > self.max_size_of_val() { | |
| throw_ub!(InvalidMeta(InvalidMetaKind::SliceTooBig)); | |
| } | |
| interp_ok(Some((size, elem.align.abi))) | |
| } | |
| ty::Foreign(_) => interp_ok(None), | |
| _ => span_bug!(self.cur_span(), "size_and_align_of::<{}> not supported", layout.ty), | |
| } | |
| } | |
| #[inline] | |
| pub fn size_and_align_of_val( | |
| &self, | |
| val: &impl Projectable<'tcx, M::Provenance>, | |
| ) -> InterpResult<'tcx, Option<(Size, Align)>> { | |
| self.size_and_align_from_meta(&val.meta(), &val.layout()) | |
| } | |
| /// Jump to the given block. | |
| #[inline] | |
| pub fn go_to_block(&mut self, target: mir::BasicBlock) { | |
| self.frame_mut().loc = Left(mir::Location { block: target, statement_index: 0 }); | |
| } | |
| /// *Return* to the given `target` basic block. | |
| /// Do *not* use for unwinding! Use `unwind_to_block` instead. | |
| /// | |
| /// If `target` is `None`, that indicates the function cannot return, so we raise UB. | |
| pub fn return_to_block(&mut self, target: Option<mir::BasicBlock>) -> InterpResult<'tcx> { | |
| if let Some(target) = target { | |
| self.go_to_block(target); | |
| interp_ok(()) | |
| } else { | |
| throw_ub!(Unreachable) | |
| } | |
| } | |
| /// *Unwind* to the given `target` basic block. | |
| /// Do *not* use for returning! Use `return_to_block` instead. | |
| /// | |
| /// If `target` is `UnwindAction::Continue`, that indicates the function does not need cleanup | |
| /// during unwinding, and we will just keep propagating that upwards. | |
| /// | |
| /// If `target` is `UnwindAction::Unreachable`, that indicates the function does not allow | |
| /// unwinding, and doing so is UB. | |
| #[cold] // usually we have normal returns, not unwinding | |
| pub fn unwind_to_block(&mut self, target: mir::UnwindAction) -> InterpResult<'tcx> { | |
| self.frame_mut().loc = match target { | |
| mir::UnwindAction::Cleanup(block) => Left(mir::Location { block, statement_index: 0 }), | |
| mir::UnwindAction::Continue => Right(self.frame_mut().body.span), | |
| mir::UnwindAction::Unreachable => { | |
| throw_ub_format!("unwinding past a stack frame that does not allow unwinding"); | |
| } | |
| mir::UnwindAction::Terminate(reason) => { | |
| self.frame_mut().loc = Right(self.frame_mut().body.span); | |
| M::unwind_terminate(self, reason)?; | |
| // This might have pushed a new stack frame, or it terminated execution. | |
| // Either way, `loc` will not be updated. | |
| return interp_ok(()); | |
| } | |
| }; | |
| interp_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>( | |
| &self, | |
| query: impl FnOnce(TyCtxtAt<'tcx>) -> Result<T, ErrorHandled>, | |
| ) -> Result<T, ErrorHandled> { | |
| // Use a precise span for better cycle errors. | |
| query(self.tcx.at(self.cur_span())).map_err(|err| { | |
| err.emit_note(*self.tcx); | |
| err | |
| }) | |
| } | |
| pub fn eval_global( | |
| &self, | |
| instance: ty::Instance<'tcx>, | |
| ) -> InterpResult<'tcx, MPlaceTy<'tcx, M::Provenance>> { | |
| let gid = GlobalId { instance, promoted: None }; | |
| let val = if self.tcx.is_static(gid.instance.def_id()) { | |
| let alloc_id = self.tcx.reserve_and_set_static_alloc(gid.instance.def_id()); | |
| let ty = instance.ty(self.tcx.tcx, self.typing_env); | |
| mir::ConstAlloc { alloc_id, ty } | |
| } else { | |
| self.ctfe_query(|tcx| tcx.eval_to_allocation_raw(self.typing_env.as_query_input(gid)))? | |
| }; | |
| self.raw_const_to_mplace(val) | |
| } | |
| pub fn eval_mir_constant( | |
| &self, | |
| val: &mir::Const<'tcx>, | |
| span: Span, | |
| layout: Option<TyAndLayout<'tcx>>, | |
| ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { | |
| let _trace = enter_trace_span!(M, const_eval::eval_mir_constant, ?val); | |
| let const_val = val.eval(*self.tcx, self.typing_env, span).map_err(|err| { | |
| if M::ALL_CONSTS_ARE_PRECHECKED { | |
| match err { | |
| ErrorHandled::TooGeneric(..) => {}, | |
| ErrorHandled::Reported(reported, span) => { | |
| if reported.is_allowed_in_infallible() { | |
| // These errors can just sometimes happen, even when the expression | |
| // is nominally "infallible", e.g. when running out of memory | |
| // or when some layout could not be computed. | |
| } else { | |
| // Looks like the const is not captured by `required_consts`, that's bad. | |
| span_bug!(span, "interpret const eval failure of {val:?} which is not in required_consts"); | |
| } | |
| } | |
| } | |
| } | |
| err.emit_note(*self.tcx); | |
| err | |
| })?; | |
| self.const_val_to_op(const_val, val.ty(), layout) | |
| } | |
| #[must_use] | |
| pub fn dump_place(&self, place: &PlaceTy<'tcx, M::Provenance>) -> PlacePrinter<'_, 'tcx, M> { | |
| PlacePrinter { ecx: self, place: *place.place() } | |
| } | |
| #[must_use] | |
| pub fn generate_stacktrace(&self) -> Vec<FrameInfo<'tcx>> { | |
| Frame::generate_stacktrace_from_stack(self.stack(), *self.tcx) | |
| } | |
| pub fn adjust_nan<F1, F2>(&self, f: F2, inputs: &[F1]) -> F2 | |
| where | |
| F1: rustc_apfloat::Float + rustc_apfloat::FloatConvert<F2>, | |
| F2: rustc_apfloat::Float, | |
| { | |
| if f.is_nan() { M::generate_nan(self, inputs) } else { f } | |
| } | |
| } | |
| #[doc(hidden)] | |
| /// Helper struct for the `dump_place` function. | |
| pub struct PlacePrinter<'a, 'tcx, M: Machine<'tcx>> { | |
| ecx: &'a InterpCx<'tcx, M>, | |
| place: Place<M::Provenance>, | |
| } | |
| impl<'a, 'tcx, M: Machine<'tcx>> std::fmt::Debug for PlacePrinter<'a, 'tcx, M> { | |
| fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
| match self.place { | |
| Place::Local { local, offset, locals_addr } => { | |
| debug_assert_eq!(locals_addr, self.ecx.frame().locals_addr()); | |
| let mut allocs = Vec::new(); | |
| write!(fmt, "{local:?}")?; | |
| if let Some(offset) = offset { | |
| write!(fmt, "+{:#x}", offset.bytes())?; | |
| } | |
| write!(fmt, ":")?; | |
| self.ecx.frame().locals[local].print(&mut allocs, fmt)?; | |
| write!(fmt, ": {:?}", self.ecx.dump_allocs(allocs.into_iter().flatten().collect())) | |
| } | |
| Place::Ptr(mplace) => match mplace.ptr.provenance.and_then(Provenance::get_alloc_id) { | |
| Some(alloc_id) => { | |
| write!(fmt, "by ref {:?}: {:?}", mplace.ptr, self.ecx.dump_alloc(alloc_id)) | |
| } | |
| ptr => write!(fmt, " integral by ref: {ptr:?}"), | |
| }, | |
| } | |
| } | |
| } |