1
Fork 0

Sometimes return the same AllocId for a ConstAllocation

This commit is contained in:
Ben Kimock 2023-11-26 17:06:13 -05:00
parent 2b399b5275
commit 245afd7896
4 changed files with 117 additions and 9 deletions

View file

@ -1138,13 +1138,15 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
span: Option<Span>, span: Option<Span>,
layout: Option<TyAndLayout<'tcx>>, layout: Option<TyAndLayout<'tcx>>,
) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> {
let const_val = val.eval(*self.tcx, self.param_env, span).map_err(|err| { M::eval_mir_constant(self, *val, span, layout, |ecx, val, span, layout| {
let const_val = val.eval(*ecx.tcx, ecx.param_env, span).map_err(|err| {
// FIXME: somehow this is reachable even when POST_MONO_CHECKS is on. // FIXME: somehow this is reachable even when POST_MONO_CHECKS is on.
// Are we not always populating `required_consts`? // Are we not always populating `required_consts`?
err.emit_note(*self.tcx); err.emit_note(*ecx.tcx);
err err
})?; })?;
self.const_val_to_op(const_val, val.ty(), layout) ecx.const_val_to_op(const_val, val.ty(), layout)
})
} }
#[must_use] #[must_use]

View file

@ -13,6 +13,7 @@ use rustc_middle::query::TyCtxtAt;
use rustc_middle::ty; use rustc_middle::ty;
use rustc_middle::ty::layout::TyAndLayout; use rustc_middle::ty::layout::TyAndLayout;
use rustc_span::def_id::DefId; use rustc_span::def_id::DefId;
use rustc_span::Span;
use rustc_target::abi::{Align, Size}; use rustc_target::abi::{Align, Size};
use rustc_target::spec::abi::Abi as CallAbi; use rustc_target::spec::abi::Abi as CallAbi;
@ -510,6 +511,25 @@ pub trait Machine<'mir, 'tcx: 'mir>: Sized {
) -> InterpResult<'tcx> { ) -> InterpResult<'tcx> {
Ok(()) Ok(())
} }
#[inline(always)]
fn eval_mir_constant<F>(
ecx: &InterpCx<'mir, 'tcx, Self>,
val: mir::Const<'tcx>,
span: Option<Span>,
layout: Option<TyAndLayout<'tcx>>,
eval: F,
) -> InterpResult<'tcx, OpTy<'tcx, Self::Provenance>>
where
F: Fn(
&InterpCx<'mir, 'tcx, Self>,
mir::Const<'tcx>,
Option<Span>,
Option<TyAndLayout<'tcx>>,
) -> InterpResult<'tcx, OpTy<'tcx, Self::Provenance>>,
{
eval(ecx, val, span, layout)
}
} }
/// A lot of the flexibility above is just needed for `Miri`, but all "compile-time" machines /// A lot of the flexibility above is just needed for `Miri`, but all "compile-time" machines

View file

@ -3,6 +3,7 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::collections::hash_map::Entry;
use std::fmt; use std::fmt;
use std::path::Path; use std::path::Path;
use std::process; use std::process;
@ -10,6 +11,7 @@ use std::process;
use either::Either; use either::Either;
use rand::rngs::StdRng; use rand::rngs::StdRng;
use rand::SeedableRng; use rand::SeedableRng;
use rand::Rng;
use rustc_ast::ast::Mutability; use rustc_ast::ast::Mutability;
use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_data_structures::fx::{FxHashMap, FxHashSet};
@ -45,6 +47,11 @@ pub const SIGRTMIN: i32 = 34;
/// `SIGRTMAX` - `SIGRTMIN` >= 8 (which is the value of `_POSIX_RTSIG_MAX`) /// `SIGRTMAX` - `SIGRTMIN` >= 8 (which is the value of `_POSIX_RTSIG_MAX`)
pub const SIGRTMAX: i32 = 42; pub const SIGRTMAX: i32 = 42;
/// Each const has multiple addresses, but only this many. Since const allocations are never
/// deallocated, choosing a new [`AllocId`] and thus base address for each evaluation would
/// produce unbounded memory usage.
const ADDRS_PER_CONST: usize = 16;
/// Extra data stored with each stack frame /// Extra data stored with each stack frame
pub struct FrameExtra<'tcx> { pub struct FrameExtra<'tcx> {
/// Extra data for the Borrow Tracker. /// Extra data for the Borrow Tracker.
@ -65,12 +72,18 @@ pub struct FrameExtra<'tcx> {
/// optimization. /// optimization.
/// This is used by `MiriMachine::current_span` and `MiriMachine::caller_span` /// This is used by `MiriMachine::current_span` and `MiriMachine::caller_span`
pub is_user_relevant: bool, pub is_user_relevant: bool,
/// We have a cache for the mapping from [`mir::Const`] to resulting [`AllocId`].
/// However, we don't want all frames to always get the same result, so we insert
/// an additional bit of "salt" into the cache key. This salt is fixed per-frame
/// so that within a call, a const will have a stable address.
salt: usize,
} }
impl<'tcx> std::fmt::Debug for FrameExtra<'tcx> { impl<'tcx> std::fmt::Debug for FrameExtra<'tcx> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// Omitting `timing`, it does not support `Debug`. // Omitting `timing`, it does not support `Debug`.
let FrameExtra { borrow_tracker, catch_unwind, timing: _, is_user_relevant: _ } = self; let FrameExtra { borrow_tracker, catch_unwind, timing: _, is_user_relevant: _, salt: _ } = self;
f.debug_struct("FrameData") f.debug_struct("FrameData")
.field("borrow_tracker", borrow_tracker) .field("borrow_tracker", borrow_tracker)
.field("catch_unwind", catch_unwind) .field("catch_unwind", catch_unwind)
@ -80,7 +93,7 @@ impl<'tcx> std::fmt::Debug for FrameExtra<'tcx> {
impl VisitProvenance for FrameExtra<'_> { impl VisitProvenance for FrameExtra<'_> {
fn visit_provenance(&self, visit: &mut VisitWith<'_>) { fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
let FrameExtra { catch_unwind, borrow_tracker, timing: _, is_user_relevant: _ } = self; let FrameExtra { catch_unwind, borrow_tracker, timing: _, is_user_relevant: _, salt: _ } = self;
catch_unwind.visit_provenance(visit); catch_unwind.visit_provenance(visit);
borrow_tracker.visit_provenance(visit); borrow_tracker.visit_provenance(visit);
@ -552,6 +565,11 @@ pub struct MiriMachine<'mir, 'tcx> {
/// The spans we will use to report where an allocation was created and deallocated in /// The spans we will use to report where an allocation was created and deallocated in
/// diagnostics. /// diagnostics.
pub(crate) allocation_spans: RefCell<FxHashMap<AllocId, (Span, Option<Span>)>>, pub(crate) allocation_spans: RefCell<FxHashMap<AllocId, (Span, Option<Span>)>>,
/// Maps MIR consts to their evaluated result. We combine the const with a "salt" (`usize`)
/// that is fixed per stack frame; this lets us have sometimes different results for the
/// same const while ensuring consistent results within a single call.
const_cache: RefCell<FxHashMap<(mir::Const<'tcx>, usize), OpTy<'tcx, Provenance>>>,
} }
impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
@ -677,6 +695,7 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
stack_size, stack_size,
collect_leak_backtraces: config.collect_leak_backtraces, collect_leak_backtraces: config.collect_leak_backtraces,
allocation_spans: RefCell::new(FxHashMap::default()), allocation_spans: RefCell::new(FxHashMap::default()),
const_cache: RefCell::new(FxHashMap::default()),
} }
} }
@ -857,6 +876,7 @@ impl VisitProvenance for MiriMachine<'_, '_> {
stack_size: _, stack_size: _,
collect_leak_backtraces: _, collect_leak_backtraces: _,
allocation_spans: _, allocation_spans: _,
const_cache: _,
} = self; } = self;
threads.visit_provenance(visit); threads.visit_provenance(visit);
@ -1400,6 +1420,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
catch_unwind: None, catch_unwind: None,
timing, timing,
is_user_relevant: ecx.machine.is_user_relevant(&frame), is_user_relevant: ecx.machine.is_user_relevant(&frame),
salt: ecx.machine.rng.borrow_mut().gen::<usize>() % ADDRS_PER_CONST,
}; };
Ok(frame.with_extra(extra)) Ok(frame.with_extra(extra))
@ -1505,4 +1526,31 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
ecx.machine.allocation_spans.borrow_mut().insert(alloc_id, (span, None)); ecx.machine.allocation_spans.borrow_mut().insert(alloc_id, (span, None));
Ok(()) Ok(())
} }
fn eval_mir_constant<F>(
ecx: &InterpCx<'mir, 'tcx, Self>,
val: mir::Const<'tcx>,
span: Option<Span>,
layout: Option<TyAndLayout<'tcx>>,
eval: F,
) -> InterpResult<'tcx, OpTy<'tcx, Self::Provenance>>
where
F: Fn(
&InterpCx<'mir, 'tcx, Self>,
mir::Const<'tcx>,
Option<Span>,
Option<TyAndLayout<'tcx>>,
) -> InterpResult<'tcx, OpTy<'tcx, Self::Provenance>>,
{
let frame = ecx.active_thread_stack().last().unwrap();
let mut cache = ecx.machine.const_cache.borrow_mut();
match cache.entry((val, frame.extra.salt)) {
Entry::Vacant(ve) => {
let op = eval(ecx, val, span, layout)?;
ve.insert(op.clone());
Ok(op)
}
Entry::Occupied(oe) => Ok(oe.get().clone()),
}
}
} }

View file

@ -0,0 +1,38 @@
// The const fn interpreter creates a new AllocId every time it evaluates any const.
// If we do that in Miri, repeatedly evaluating a const causes unbounded memory use
// we need to keep track of the base address for that AllocId, and the allocation is never
// deallocated.
// In Miri we explicitly store previously-assigned AllocIds for each const and ensure
// that we only hand out a finite number of AllocIds per const.
// MIR inlining will put every evaluation of the const we're repeatedly evaluting into the same
// stack frame, breaking this test.
//@compile-flags: -Zinline-mir=no
#![feature(strict_provenance)]
const EVALS: usize = 256;
use std::collections::HashSet;
fn main() {
let mut addrs = HashSet::new();
for _ in 0..EVALS {
addrs.insert(const_addr());
}
// Check that the const allocation has multiple base addresses
assert!(addrs.len() > 1);
// But also that we get a limited number of unique base addresses
assert!(addrs.len() < EVALS);
// Check that within a call we always produce the same address
let mut prev = 0;
for iter in 0..EVALS {
let addr = "test".as_bytes().as_ptr().addr();
if iter > 0 {
assert_eq!(prev, addr);
}
prev = addr;
}
}
fn const_addr() -> usize {
"test".as_bytes().as_ptr().addr()
}