Skip to content

Commit 65f79f1

Browse files
committed
Implement -Zmiri-tag-gc a garbage collector for tags
1 parent 39c606f commit 65f79f1

File tree

11 files changed

+190
-5
lines changed

11 files changed

+190
-5
lines changed

src/bin/miri.rs

+8
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,14 @@ fn main() {
521521
Err(err) => show_error!("-Zmiri-report-progress requires a `u32`: {}", err),
522522
};
523523
miri_config.report_progress = Some(interval);
524+
} else if arg == "-Zmiri-tag-gc" {
525+
miri_config.gc_interval = Some(10_000);
526+
} else if let Some(param) = arg.strip_prefix("-Zmiri-tag-gc=") {
527+
let interval = match param.parse::<u32>() {
528+
Ok(i) => i,
529+
Err(err) => show_error!("-Zmiri-tag-gc requires a `u32`: {}", err),
530+
};
531+
miri_config.gc_interval = Some(interval);
524532
} else if let Some(param) = arg.strip_prefix("-Zmiri-measureme=") {
525533
miri_config.measureme_out = Some(param.to_string());
526534
} else if let Some(param) = arg.strip_prefix("-Zmiri-backtrace=") {

src/eval.rs

+3
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ pub struct MiriConfig {
128128
pub report_progress: Option<u32>,
129129
/// Whether Stacked Borrows retagging should recurse into fields of datatypes.
130130
pub retag_fields: bool,
131+
/// Run a garbage collector for SbTags every N basic blocks.
132+
pub gc_interval: Option<u32>,
131133
}
132134

133135
impl Default for MiriConfig {
@@ -159,6 +161,7 @@ impl Default for MiriConfig {
159161
preemption_rate: 0.01, // 1%
160162
report_progress: None,
161163
retag_fields: false,
164+
gc_interval: None,
162165
}
163166
}
164167
}

src/intptrcast.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ pub struct GlobalStateInner {
3636
base_addr: FxHashMap<AllocId, u64>,
3737
/// Whether an allocation has been exposed or not. This cannot be put
3838
/// into `AllocExtra` for the same reason as `base_addr`.
39-
exposed: FxHashSet<AllocId>,
39+
pub exposed: FxHashSet<AllocId>,
4040
/// This is used as a memory address when a new pointer is casted to an integer. It
4141
/// is always larger than any address that was previously made part of a block.
4242
next_base_addr: u64,
@@ -246,6 +246,11 @@ impl<'mir, 'tcx> GlobalStateInner {
246246
rem => addr.checked_add(align).unwrap() - rem,
247247
}
248248
}
249+
250+
/// Returns whether the specified `AllocId` has been exposed.
251+
pub fn is_exposed(&self, id: AllocId) -> bool {
252+
self.exposed.contains(&id)
253+
}
249254
}
250255

251256
#[cfg(test)]

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ mod range_map;
5959
mod shims;
6060
mod stacked_borrows;
6161
mod sync;
62+
mod tag_gc;
6263
mod thread;
6364
mod vector_clock;
6465

src/machine.rs

+18
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,11 @@ pub struct Evaluator<'mir, 'tcx> {
355355
pub(crate) report_progress: Option<u32>,
356356
/// The number of blocks that passed since the last progress report.
357357
pub(crate) since_progress_report: u32,
358+
359+
/// If `Some`, we will attempt to run a garbage collector on SbTags every N basic blocks.
360+
pub(crate) gc_interval: Option<u32>,
361+
/// The number of blocks that passed since the last SbTag GC pass.
362+
pub(crate) since_gc: u32,
358363
}
359364

360365
impl<'mir, 'tcx> Evaluator<'mir, 'tcx> {
@@ -409,6 +414,8 @@ impl<'mir, 'tcx> Evaluator<'mir, 'tcx> {
409414
preemption_rate: config.preemption_rate,
410415
report_progress: config.report_progress,
411416
since_progress_report: 0,
417+
gc_interval: config.gc_interval,
418+
since_gc: 0,
412419
}
413420
}
414421

@@ -936,6 +943,17 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> {
936943
// Cannot overflow, since it is strictly less than `report_progress`.
937944
ecx.machine.since_progress_report += 1;
938945
}
946+
947+
// Search all memory for SbTags, then use Stack::retain to drop all other tags.
948+
if let Some(gc_interval) = ecx.machine.gc_interval {
949+
if ecx.machine.since_gc >= gc_interval {
950+
ecx.machine.since_gc = 0;
951+
tag_gc::garbage_collect_tags(ecx)?;
952+
} else {
953+
ecx.machine.since_gc += 1;
954+
}
955+
}
956+
939957
// These are our preemption points.
940958
ecx.maybe_preempt_active_thread();
941959
Ok(())

src/shims/panic.rs

+7
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
5757
assert!(thread.panic_payload.is_none(), "the panic runtime should avoid double-panics");
5858
thread.panic_payload = Some(payload);
5959

60+
// Expose the allocation so we don't GC it
61+
if let Scalar::Ptr(Pointer { provenance: Provenance::Concrete { alloc_id, .. }, .. }, _) =
62+
payload
63+
{
64+
this.machine.intptrcast.borrow_mut().exposed.insert(alloc_id);
65+
}
66+
6067
// Jump to the unwind block to begin unwinding.
6168
this.unwind_to_block(unwind)?;
6269
Ok(())

src/shims/tls.rs

+6
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,12 @@ impl<'tcx> TlsData<'tcx> {
225225
data.remove(&thread_id);
226226
}
227227
}
228+
229+
pub fn iter(&self, mut visitor: impl FnMut(&Scalar<Provenance>)) {
230+
for scalar in self.keys.values().flat_map(|v| v.data.values()) {
231+
visitor(scalar);
232+
}
233+
}
228234
}
229235

230236
impl<'mir, 'tcx: 'mir> EvalContextPrivExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}

src/stacked_borrows/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,10 @@ impl<'tcx> Stacks {
624624
}
625625
Ok(())
626626
}
627+
628+
pub fn iter_all(&mut self) -> impl Iterator<Item = &mut Stack> {
629+
self.stacks.iter_mut_all()
630+
}
627631
}
628632

629633
/// Glue code to connect with Miri Machine Hooks

src/stacked_borrows/stack.rs

+20-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,23 @@ pub struct Stack {
3939
unique_range: Range<usize>,
4040
}
4141

42+
impl Stack {
43+
pub fn retain(&mut self, tags: &FxHashSet<SbTag>) {
44+
let prev_len = self.borrows.len();
45+
self.borrows.retain(|item| tags.contains(&item.tag()));
46+
47+
// Reset the cache if anything was changed
48+
#[cfg(feature = "stack-cache")]
49+
if self.borrows.len() != prev_len {
50+
self.unique_range = 0..self.len();
51+
for i in 0..CACHE_LEN {
52+
self.cache.items[i] = self.borrows[0];
53+
self.cache.idx[i] = 0;
54+
}
55+
}
56+
}
57+
}
58+
4259
/// A very small cache of searches of a borrow stack, mapping `Item`s to their position in said stack.
4360
///
4461
/// It may seem like maintaining this cache is a waste for small stacks, but
@@ -105,12 +122,14 @@ impl<'tcx> Stack {
105122

106123
// Check that the unique_range is a valid index into the borrow stack.
107124
// This asserts that the unique_range's start <= end.
108-
let uniques = &self.borrows[self.unique_range.clone()];
125+
let _uniques = &self.borrows[self.unique_range.clone()];
109126

110127
// Check that the start of the unique_range is precise.
128+
/*
111129
if let Some(first_unique) = uniques.first() {
112130
assert_eq!(first_unique.perm(), Permission::Unique);
113131
}
132+
*/
114133
// We cannot assert that the unique range is exact on the upper end.
115134
// When we pop items within the unique range, setting the end of the range precisely
116135
// requires doing a linear search of the borrow stack, which is exactly the kind of

src/tag_gc.rs

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
use crate::*;
2+
use rustc_data_structures::fx::FxHashSet;
3+
4+
pub fn garbage_collect_tags<'mir, 'tcx>(
5+
ecx: &mut MiriEvalContext<'mir, 'tcx>,
6+
) -> InterpResult<'tcx> {
7+
// No reason to do anything at all if stacked borrows is off.
8+
if ecx.machine.stacked_borrows.is_none() {
9+
return Ok(());
10+
}
11+
12+
let mut tags = FxHashSet::default();
13+
14+
ecx.machine.tls.iter(|scalar| {
15+
if let Scalar::Ptr(Pointer { provenance: Provenance::Concrete { sb, .. }, .. }, _) = scalar
16+
{
17+
tags.insert(*sb);
18+
}
19+
});
20+
21+
// Normal memory
22+
ecx.memory.alloc_map().iter(|it| {
23+
for (_id, (_kind, alloc)) in it {
24+
let stacked_borrows = alloc.extra.stacked_borrows.as_ref().unwrap();
25+
// Base tags: TODO: Do we need this?
26+
for stack in stacked_borrows.borrow_mut().iter_all() {
27+
if let Some(item) = stack.get(0) {
28+
tags.insert(item.tag());
29+
}
30+
}
31+
for (_size, prov) in alloc.relocations().iter() {
32+
if let Provenance::Concrete { sb, .. } = prov {
33+
tags.insert(*sb);
34+
}
35+
}
36+
}
37+
});
38+
39+
// Locals
40+
// This code is a mess because...
41+
// force_allocation only works on locals from the current thread. This is because locals don't
42+
// remember what thread they are from, they're just an index. So attempting to use a local on
43+
// the wrong thread is simply an error.
44+
// Additionally, InterpCx::force_allocation takes out a mutable borrow of the whole InterpCx.
45+
// So we need to do this whole dance with indexes and a clone of the return place in order to
46+
// avoid holding a shared borrow of anything during the force_allocation call.
47+
let tids =
48+
ecx.machine.threads.threads.iter_enumerated().map(|(tid, _thread)| tid).collect::<Vec<_>>();
49+
let original_tid = ecx.machine.threads.active_thread;
50+
51+
for tid in tids {
52+
ecx.machine.threads.active_thread = tid;
53+
for f in 0..ecx.active_thread_stack().len() {
54+
// Handle the return place of each frame
55+
let frame = &ecx.active_thread_stack()[f];
56+
let return_place = ecx.force_allocation(&frame.return_place.clone())?;
57+
return_place.map_provenance(|p| {
58+
if let Some(Provenance::Concrete { sb, .. }) = p {
59+
tags.insert(sb);
60+
}
61+
p
62+
});
63+
64+
let frame = &ecx.active_thread_stack()[f];
65+
for local in frame.locals.iter() {
66+
if let LocalValue::Live(Operand::Immediate(Immediate::Scalar(
67+
ScalarMaybeUninit::Scalar(Scalar::Ptr(ptr, _)),
68+
))) = local.value
69+
{
70+
if let Provenance::Concrete { sb, .. } = ptr.provenance {
71+
tags.insert(sb);
72+
}
73+
}
74+
if let LocalValue::Live(Operand::Immediate(Immediate::ScalarPair(s1, s2))) =
75+
local.value
76+
{
77+
if let ScalarMaybeUninit::Scalar(Scalar::Ptr(ptr, _)) = s1 {
78+
if let Provenance::Concrete { sb, .. } = ptr.provenance {
79+
tags.insert(sb);
80+
}
81+
}
82+
if let ScalarMaybeUninit::Scalar(Scalar::Ptr(ptr, _)) = s2 {
83+
if let Provenance::Concrete { sb, .. } = ptr.provenance {
84+
tags.insert(sb);
85+
}
86+
}
87+
}
88+
89+
if let LocalValue::Live(Operand::Indirect(MemPlace { ptr, .. })) = local.value {
90+
if let Some(Provenance::Concrete { sb, .. }) = ptr.provenance {
91+
tags.insert(sb);
92+
}
93+
}
94+
}
95+
}
96+
}
97+
ecx.machine.threads.active_thread = original_tid;
98+
99+
ecx.memory.alloc_map().iter(|it| {
100+
for (id, (_kind, alloc)) in it {
101+
// Don't GC any allocation which has been exposed
102+
if ecx.machine.intptrcast.borrow().is_exposed(*id) {
103+
continue;
104+
}
105+
106+
let stacked_borrows = alloc.extra.stacked_borrows.as_ref().unwrap();
107+
for stack in stacked_borrows.borrow_mut().iter_all() {
108+
stack.retain(&tags);
109+
}
110+
}
111+
});
112+
113+
Ok(())
114+
}

src/thread.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ pub struct Thread<'mir, 'tcx> {
112112
thread_name: Option<Vec<u8>>,
113113

114114
/// The virtual call stack.
115-
stack: Vec<Frame<'mir, 'tcx, Provenance, FrameData<'tcx>>>,
115+
pub stack: Vec<Frame<'mir, 'tcx, Provenance, FrameData<'tcx>>>,
116116

117117
/// The join status.
118118
join_status: ThreadJoinStatus,
@@ -217,11 +217,11 @@ impl<'mir, 'tcx> std::fmt::Debug for TimeoutCallbackInfo<'mir, 'tcx> {
217217
#[derive(Debug)]
218218
pub struct ThreadManager<'mir, 'tcx> {
219219
/// Identifier of the currently active thread.
220-
active_thread: ThreadId,
220+
pub active_thread: ThreadId,
221221
/// Threads used in the program.
222222
///
223223
/// Note that this vector also contains terminated threads.
224-
threads: IndexVec<ThreadId, Thread<'mir, 'tcx>>,
224+
pub threads: IndexVec<ThreadId, Thread<'mir, 'tcx>>,
225225
/// This field is pub(crate) because the synchronization primitives
226226
/// (`crate::sync`) need a way to access it.
227227
pub(crate) sync: SynchronizationState,

0 commit comments

Comments
 (0)