Sometimes return the same AllocId for a ConstAllocation
This commit is contained in:
parent
2b399b5275
commit
245afd7896
4 changed files with 117 additions and 9 deletions
|
@ -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]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
38
src/tools/miri/tests/pass/const-addrs.rs
Normal file
38
src/tools/miri/tests/pass/const-addrs.rs
Normal 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()
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue