Skip to content

Commit 3a90354

Browse files
committed
Sometimes return the same AllocId for a ConstAllocation
1 parent 525b2e0 commit 3a90354

File tree

2 files changed

+88
-2
lines changed

2 files changed

+88
-2
lines changed

src/machine.rs

+50-2
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
44
use std::borrow::Cow;
55
use std::cell::{Cell, RefCell};
6+
use std::collections::hash_map::Entry;
67
use std::fmt;
78
use std::path::Path;
89
use std::process;
910

1011
use either::Either;
1112
use rand::rngs::StdRng;
1213
use rand::SeedableRng;
14+
use rand::Rng;
1315

1416
use rustc_ast::ast::Mutability;
1517
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
@@ -45,6 +47,11 @@ pub const SIGRTMIN: i32 = 34;
4547
/// `SIGRTMAX` - `SIGRTMIN` >= 8 (which is the value of `_POSIX_RTSIG_MAX`)
4648
pub const SIGRTMAX: i32 = 42;
4749

50+
/// Each const has multiple addresses, but only this many. Since const allocations are never
51+
/// deallocated, choosing a new [`AllocId`] and thus base address for each evaluation would
52+
/// produce unbounded memory usage.
53+
const ADDRS_PER_CONST: usize = 16;
54+
4855
/// Extra data stored with each stack frame
4956
pub struct FrameExtra<'tcx> {
5057
/// Extra data for the Borrow Tracker.
@@ -65,12 +72,18 @@ pub struct FrameExtra<'tcx> {
6572
/// optimization.
6673
/// This is used by `MiriMachine::current_span` and `MiriMachine::caller_span`
6774
pub is_user_relevant: bool,
75+
76+
/// We have a cache for the mapping from [`mir::Const`] to resulting [`AllocId`].
77+
/// However, we don't want all frames to always get the same result, so we insert
78+
/// an additional bit of "salt" into the cache key. This salt is fixed per-frame
79+
/// so that within a call, a const will have a stable address.
80+
salt: usize,
6881
}
6982

7083
impl<'tcx> std::fmt::Debug for FrameExtra<'tcx> {
7184
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7285
// Omitting `timing`, it does not support `Debug`.
73-
let FrameExtra { borrow_tracker, catch_unwind, timing: _, is_user_relevant: _ } = self;
86+
let FrameExtra { borrow_tracker, catch_unwind, timing: _, is_user_relevant: _, salt: _ } = self;
7487
f.debug_struct("FrameData")
7588
.field("borrow_tracker", borrow_tracker)
7689
.field("catch_unwind", catch_unwind)
@@ -80,7 +93,7 @@ impl<'tcx> std::fmt::Debug for FrameExtra<'tcx> {
8093

8194
impl VisitProvenance for FrameExtra<'_> {
8295
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
83-
let FrameExtra { catch_unwind, borrow_tracker, timing: _, is_user_relevant: _ } = self;
96+
let FrameExtra { catch_unwind, borrow_tracker, timing: _, is_user_relevant: _, salt: _ } = self;
8497

8598
catch_unwind.visit_provenance(visit);
8699
borrow_tracker.visit_provenance(visit);
@@ -552,6 +565,11 @@ pub struct MiriMachine<'mir, 'tcx> {
552565
/// The spans we will use to report where an allocation was created and deallocated in
553566
/// diagnostics.
554567
pub(crate) allocation_spans: RefCell<FxHashMap<AllocId, (Span, Option<Span>)>>,
568+
569+
/// Maps MIR consts to their evaluated result. We combine the const with a "salt" (`usize`)
570+
/// that is fixed per stack frame; this lets us have sometimes different results for the
571+
/// same const while ensuring consistent results within a single call.
572+
const_cache: RefCell<FxHashMap<(mir::Const<'tcx>, usize), OpTy<'tcx, Provenance>>>,
555573
}
556574

557575
impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
@@ -677,6 +695,7 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
677695
stack_size,
678696
collect_leak_backtraces: config.collect_leak_backtraces,
679697
allocation_spans: RefCell::new(FxHashMap::default()),
698+
const_cache: RefCell::new(FxHashMap::default()),
680699
}
681700
}
682701

@@ -857,6 +876,7 @@ impl VisitProvenance for MiriMachine<'_, '_> {
857876
stack_size: _,
858877
collect_leak_backtraces: _,
859878
allocation_spans: _,
879+
const_cache: _,
860880
} = self;
861881

862882
threads.visit_provenance(visit);
@@ -1400,6 +1420,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
14001420
catch_unwind: None,
14011421
timing,
14021422
is_user_relevant: ecx.machine.is_user_relevant(&frame),
1423+
salt: ecx.machine.rng.borrow_mut().gen::<usize>() % ADDRS_PER_CONST,
14031424
};
14041425

14051426
Ok(frame.with_extra(extra))
@@ -1505,4 +1526,31 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
15051526
ecx.machine.allocation_spans.borrow_mut().insert(alloc_id, (span, None));
15061527
Ok(())
15071528
}
1529+
1530+
fn eval_mir_constant<F>(
1531+
ecx: &InterpCx<'mir, 'tcx, Self>,
1532+
val: mir::Const<'tcx>,
1533+
span: Option<Span>,
1534+
layout: Option<TyAndLayout<'tcx>>,
1535+
eval: F,
1536+
) -> InterpResult<'tcx, OpTy<'tcx, Self::Provenance>>
1537+
where
1538+
F: Fn(
1539+
&InterpCx<'mir, 'tcx, Self>,
1540+
mir::Const<'tcx>,
1541+
Option<Span>,
1542+
Option<TyAndLayout<'tcx>>,
1543+
) -> InterpResult<'tcx, OpTy<'tcx, Self::Provenance>>,
1544+
{
1545+
let frame = ecx.active_thread_stack().last().unwrap();
1546+
let mut cache = ecx.machine.const_cache.borrow_mut();
1547+
match cache.entry((val, frame.extra.salt)) {
1548+
Entry::Vacant(ve) => {
1549+
let op = eval(ecx, val, span, layout)?;
1550+
ve.insert(op.clone());
1551+
Ok(op)
1552+
}
1553+
Entry::Occupied(oe) => Ok(oe.get().clone()),
1554+
}
1555+
}
15081556
}

tests/pass/const-addrs.rs

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// The const fn interpreter creates a new AllocId every time it evaluates any const.
2+
// If we do that in Miri, repeatedly evaluating a const causes unbounded memory use
3+
// we need to keep track of the base address for that AllocId, and the allocation is never
4+
// deallocated.
5+
// In Miri we explicitly store previously-assigned AllocIds for each const and ensure
6+
// that we only hand out a finite number of AllocIds per const.
7+
// MIR inlining will put every evaluation of the const we're repeatedly evaluting into the same
8+
// stack frame, breaking this test.
9+
//@compile-flags: -Zinline-mir=no
10+
#![feature(strict_provenance)]
11+
12+
const EVALS: usize = 256;
13+
14+
use std::collections::HashSet;
15+
fn main() {
16+
let mut addrs = HashSet::new();
17+
for _ in 0..EVALS {
18+
addrs.insert(const_addr());
19+
}
20+
// Check that the const allocation has multiple base addresses
21+
assert!(addrs.len() > 1);
22+
// But also that we get a limited number of unique base addresses
23+
assert!(addrs.len() < EVALS);
24+
25+
// Check that within a call we always produce the same address
26+
let mut prev = 0;
27+
for iter in 0..EVALS {
28+
let addr = "test".as_bytes().as_ptr().addr();
29+
if iter > 0 {
30+
assert_eq!(prev, addr);
31+
}
32+
prev = addr;
33+
}
34+
}
35+
36+
fn const_addr() -> usize {
37+
"test".as_bytes().as_ptr().addr()
38+
}

0 commit comments

Comments
 (0)