Add tracing to various miscellaneous functions
This PR adds tracing to:
- `ty.fn_sig()`. There is only one place where `fn_sig` is called for real within `rustc_const_eval`. There are three other places where it's called, but one is inside `ConstCx::fn_sig` (which does not seem to be used anywhere), another is under `if cfg!(debug_assertions)`, and the last is within `call_main` and thus gets called only once.
- the two possible things `find_mir_or_eval_fn` can do: "emulate_foreign_item" and "load_mir"
- all calls to `Const.eval()` within the Miri or the `rustc_const_eval` codebase.
- a separate commit also fixes the style of some tracing macros
Those are all quite long-lived operations, that in total make up for 6-7% of the total time spent in the program. I found out about them by looking for long periods of time that were previously not traced at all, using this SQL query in ui.perfetto.dev:
```sql
with ordered as (select s1.*, row_number() over (order by s1.ts) as rn from slices as s1 where s1.parent_id is null and s1.dur > 0 and s1.name != "frame" and s1.name != "step" and s1.name != "backtrace") select a.ts+a.dur as ts, b.ts-a.ts-a.dur as dur, a.id, a.track_id, a.category, a.depth, a.stack_id, a.parent_stack_id, a.parent_id, a.arg_set_id, a.thread_ts, a.thread_instruction_count, a.thread_instruction_delta, a.cat, a.slice_id, "empty" as name from ordered as a inner join ordered as b on a.rn=b.rn-1 /*where b.ts-a.ts-a.dur > 5000*/ order by b.ts-a.ts-a.dur desc
```
<details>
<summary>How the table was obtained</summary>
The above image was obtained in ui.perfetto.dev with the following SQL query after obtaining a trace file by running Miri on the following Rust code with `n=100`.
```sql
select "TOTAL PROGRAM DURATION" as name, count(*), max(ts + dur) as "sum(dur)", 100.0 as "%", null as "min(dur)", null as "max(dur)", null as "avg(dur)", null as "stddev(dur)" from slices union select "TOTAL OVER ALL SPANS (excluding events)" as name, count(*), sum(dur), cast(cast(sum(dur) as float) / (select max(ts + dur) from slices) * 1000 as int) / 10.0 as "%", min(dur), max(dur), cast(avg(dur) as int) as "avg(dur)", cast(sqrt(avg(dur*dur)-avg(dur)*avg(dur)) as int) as "stddev(dur)" from slices where parent_id is null and name != "frame" and name != "step" and dur > 0 union select name, count(*), sum(dur), cast(cast(sum(dur) as float) / (select max(ts + dur) from slices) * 1000 as int) / 10.0 as "%", min(dur), max(dur), cast(avg(dur) as int) as "avg(dur)", cast(sqrt(avg(dur*dur)-avg(dur)*avg(dur)) as int) as "stddev(dur)" from slices where parent_id is null and name != "frame" and name != "step" group by name order by sum(dur) desc, count(*) desc
```
```rust
fn main() {
let n: usize = std::env::args().nth(1).unwrap().parse().unwrap();
let mut v = (0..n).into_iter().collect::<Vec<_>>();
for i in &mut v {
*i += 1;
}
}
```
</details>
<img width="1689" height="317" alt="image" src="https://github.com/user-attachments/assets/ee2c81f5-d74a-4da5-b4b6-ab2770175b14" />
659 lines
26 KiB
Rust
659 lines
26 KiB
Rust
use std::assert_matches::debug_assert_matches;
|
|
|
|
use either::{Left, Right};
|
|
use rustc_abi::{Align, HasDataLayout, Size, TargetDataLayout};
|
|
use rustc_errors::DiagCtxtHandle;
|
|
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, TypingEnv, Variance};
|
|
use rustc_middle::{mir, span_bug};
|
|
use rustc_session::Limit;
|
|
use rustc_span::Span;
|
|
use rustc_target::callconv::FnAbi;
|
|
use tracing::{debug, trace};
|
|
|
|
use super::{
|
|
Frame, FrameInfo, GlobalId, InterpErrorInfo, InterpErrorKind, InterpResult, MPlaceTy, Machine,
|
|
MemPlaceMeta, Memory, OpTy, Place, PlaceTy, PointerArithmetic, Projectable, Provenance,
|
|
err_inval, interp_ok, throw_inval, throw_ub, throw_ub_custom,
|
|
};
|
|
use crate::{ReportErrorExt, enter_trace_span, fluent_generated as fluent, 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 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,
|
|
err: LayoutError<'tcx>,
|
|
_: Span,
|
|
_: Ty<'tcx>,
|
|
) -> InterpErrorKind<'tcx> {
|
|
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(always)]
|
|
pub fn layout_of(&self, ty: Ty<'tcx>) -> <Self as LayoutOfHelpers<'tcx>>::LayoutOfResult {
|
|
let _trace = enter_trace_span!(M, layouting::layout_of, ty = ?ty.kind());
|
|
LayoutOf::layout_of(self, ty)
|
|
}
|
|
|
|
/// 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] with a tracing span.
|
|
/// See [FnAbiOf::fn_abi_of_instance] for the original documentation.
|
|
#[inline(always)]
|
|
pub fn fn_abi_of_instance(
|
|
&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(self, instance, extra_args)
|
|
}
|
|
}
|
|
|
|
/// Test if it is valid for a MIR assignment to assign `src`-typed place to `dest`-typed value.
|
|
/// This test should be symmetric, as it is primarily about layout compatibility.
|
|
pub(super) fn mir_assign_valid_types<'tcx>(
|
|
tcx: TyCtxt<'tcx>,
|
|
typing_env: TypingEnv<'tcx>,
|
|
src: TyAndLayout<'tcx>,
|
|
dest: TyAndLayout<'tcx>,
|
|
) -> bool {
|
|
// Type-changing assignments can happen when subtyping is used. While
|
|
// all normal lifetimes are erased, higher-ranked types with their
|
|
// late-bound lifetimes are still around and can lead to type
|
|
// differences.
|
|
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);
|
|
}
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Turn the given error into a human-readable string. Expects the string to be printed, so if
|
|
/// `RUSTC_CTFE_BACKTRACE` is set this will show a backtrace of the rustc internals that
|
|
/// triggered the error.
|
|
///
|
|
/// This is NOT the preferred way to render an error; use `report` from `const_eval` instead.
|
|
/// However, this is useful when error messages appear in ICEs.
|
|
pub fn format_interp_error<'tcx>(dcx: DiagCtxtHandle<'_>, e: InterpErrorInfo<'tcx>) -> String {
|
|
let (e, backtrace) = e.into_parts();
|
|
backtrace.print_backtrace();
|
|
// FIXME(fee1-dead), HACK: we want to use the error as title therefore we can just extract the
|
|
// label and arguments from the InterpError.
|
|
#[allow(rustc::untranslatable_diagnostic)]
|
|
let mut diag = dcx.struct_allow("");
|
|
let msg = e.diagnostic_message();
|
|
e.add_args(&mut diag);
|
|
let s = dcx.eagerly_translate_to_string(msg, diag.args.iter());
|
|
diag.cancel();
|
|
s
|
|
}
|
|
|
|
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 {
|
|
// Const eval always happens in post analysis mode in order to be able to use the hidden types of
|
|
// opaque types. This is needed for trivial things like `size_of`, but also for using associated
|
|
// types that are not specified in the opaque type. We also use MIR bodies whose opaque types have
|
|
// already been revealed, so we'd be able to at least partially observe the hidden types anyways.
|
|
debug_assert_matches!(typing_env.typing_mode, ty::TypingMode::PostAnalysis);
|
|
InterpCx {
|
|
machine,
|
|
tcx: tcx.at(root_span),
|
|
typing_env,
|
|
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(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, _, ty::Dyn) => {
|
|
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_custom!(fluent::const_eval_unreachable_unwind);
|
|
}
|
|
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())
|
|
}
|
|
|
|
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:?}"),
|
|
},
|
|
}
|
|
}
|
|
}
|