Skip to content

Commit 2049ee7

Browse files
committed
when an address gets reused, establish a happens-before link in the data race model
1 parent e090ca2 commit 2049ee7

File tree

7 files changed

+125
-36
lines changed

7 files changed

+125
-36
lines changed

src/alloc_addresses/mod.rs

+27-15
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ use rustc_span::Span;
1414
use rustc_target::abi::{Align, HasDataLayout, Size};
1515

1616
use crate::*;
17-
use reuse_pool::ReusePool;
17+
18+
use self::reuse_pool::ReusePool;
1819

1920
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
2021
pub enum ProvenanceMode {
@@ -159,9 +160,12 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
159160
assert!(!matches!(kind, AllocKind::Dead));
160161

161162
// This allocation does not have a base address yet, pick or reuse one.
162-
let base_addr = if let Some(reuse_addr) =
163+
let base_addr = if let Some((reuse_addr, clock)) =
163164
global_state.reuse.take_addr(&mut *rng, size, align)
164165
{
166+
if let Some(data_race) = &ecx.machine.data_race {
167+
data_race.validate_lock_acquire(&clock, ecx.get_active_thread());
168+
}
165169
reuse_addr
166170
} else {
167171
// We have to pick a fresh address.
@@ -328,14 +332,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
328332
}
329333
}
330334

331-
impl GlobalStateInner {
332-
pub fn free_alloc_id(
333-
&mut self,
334-
rng: &mut impl Rng,
335-
dead_id: AllocId,
336-
size: Size,
337-
align: Align,
338-
) {
335+
impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
336+
pub fn free_alloc_id(&mut self, dead_id: AllocId, size: Size, align: Align) {
337+
let global_state = self.alloc_addresses.get_mut();
338+
let rng = self.rng.get_mut();
339+
339340
// We can *not* remove this from `base_addr`, since the interpreter design requires that we
340341
// be able to retrieve an AllocId + offset for any memory access *before* we check if the
341342
// access is valid. Specifically, `ptr_get_alloc` is called on each attempt at a memory
@@ -348,15 +349,26 @@ impl GlobalStateInner {
348349
// returns a dead allocation.
349350
// To avoid a linear scan we first look up the address in `base_addr`, and then find it in
350351
// `int_to_ptr_map`.
351-
let addr = *self.base_addr.get(&dead_id).unwrap();
352-
let pos = self.int_to_ptr_map.binary_search_by_key(&addr, |(addr, _)| *addr).unwrap();
353-
let removed = self.int_to_ptr_map.remove(pos);
352+
let addr = *global_state.base_addr.get(&dead_id).unwrap();
353+
let pos =
354+
global_state.int_to_ptr_map.binary_search_by_key(&addr, |(addr, _)| *addr).unwrap();
355+
let removed = global_state.int_to_ptr_map.remove(pos);
354356
assert_eq!(removed, (addr, dead_id)); // double-check that we removed the right thing
355357
// We can also remove it from `exposed`, since this allocation can anyway not be returned by
356358
// `alloc_id_from_addr` any more.
357-
self.exposed.remove(&dead_id);
359+
global_state.exposed.remove(&dead_id);
358360
// Also remember this address for future reuse.
359-
self.reuse.add_addr(rng, addr, size, align)
361+
global_state.reuse.add_addr(rng, addr, size, align, || {
362+
let mut clock = concurrency::VClock::default();
363+
if let Some(data_race) = &self.data_race {
364+
data_race.validate_lock_release(
365+
&mut clock,
366+
self.threads.get_active_thread_id(),
367+
self.threads.active_thread_ref().current_span(),
368+
);
369+
}
370+
clock
371+
})
360372
}
361373
}
362374

src/alloc_addresses/reuse_pool.rs

+28-11
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ use rand::Rng;
44

55
use rustc_target::abi::{Align, Size};
66

7+
use crate::concurrency::VClock;
8+
79
const MAX_POOL_SIZE: usize = 64;
810

911
// Just use fair coins, until we have evidence that other numbers are better.
@@ -21,42 +23,57 @@ pub struct ReusePool {
2123
///
2224
/// Each of these maps has at most MAX_POOL_SIZE elements, and since alignment is limited to
2325
/// less than 64 different possible value, that bounds the overall size of the pool.
24-
pool: Vec<Vec<(u64, Size)>>,
26+
///
27+
/// We also store the clock from the thread that donated this pool element,
28+
/// to ensure synchronization with the thread that picks up this address.
29+
pool: Vec<Vec<(u64, Size, VClock)>>,
2530
}
2631

2732
impl ReusePool {
2833
pub fn new() -> Self {
2934
ReusePool { pool: vec![] }
3035
}
3136

32-
fn subpool(&mut self, align: Align) -> &mut Vec<(u64, Size)> {
37+
fn subpool(&mut self, align: Align) -> &mut Vec<(u64, Size, VClock)> {
3338
let pool_idx: usize = align.bytes().trailing_zeros().try_into().unwrap();
3439
if self.pool.len() <= pool_idx {
3540
self.pool.resize(pool_idx + 1, Vec::new());
3641
}
3742
&mut self.pool[pool_idx]
3843
}
3944

40-
pub fn add_addr(&mut self, rng: &mut impl Rng, addr: u64, size: Size, align: Align) {
45+
pub fn add_addr(
46+
&mut self,
47+
rng: &mut impl Rng,
48+
addr: u64,
49+
size: Size,
50+
align: Align,
51+
clock: impl FnOnce() -> VClock,
52+
) {
4153
// Let's see if we even want to remember this address.
4254
if !rng.gen_bool(ADDR_REMEMBER_CHANCE) {
4355
return;
4456
}
4557
// Determine the pool to add this to, and where in the pool to put it.
4658
let subpool = self.subpool(align);
47-
let pos = subpool.partition_point(|(_addr, other_size)| *other_size < size);
59+
let pos = subpool.partition_point(|(_addr, other_size, _)| *other_size < size);
4860
// Make sure the pool does not grow too big.
4961
if subpool.len() >= MAX_POOL_SIZE {
5062
// Pool full. Replace existing element, or last one if this would be even bigger.
5163
let clamped_pos = pos.min(subpool.len() - 1);
52-
subpool[clamped_pos] = (addr, size);
64+
subpool[clamped_pos] = (addr, size, clock());
5365
return;
5466
}
5567
// Add address to pool, at the right position.
56-
subpool.insert(pos, (addr, size));
68+
subpool.insert(pos, (addr, size, clock()));
5769
}
5870

59-
pub fn take_addr(&mut self, rng: &mut impl Rng, size: Size, align: Align) -> Option<u64> {
71+
pub fn take_addr(
72+
&mut self,
73+
rng: &mut impl Rng,
74+
size: Size,
75+
align: Align,
76+
) -> Option<(u64, VClock)> {
6077
// Determine whether we'll even attempt a reuse.
6178
if !rng.gen_bool(ADDR_TAKE_CHANCE) {
6279
return None;
@@ -65,9 +82,9 @@ impl ReusePool {
6582
let subpool = self.subpool(align);
6683
// Let's see if we can find something of the right size. We want to find the full range of
6784
// such items, beginning with the first, so we can't use `binary_search_by_key`.
68-
let begin = subpool.partition_point(|(_addr, other_size)| *other_size < size);
85+
let begin = subpool.partition_point(|(_addr, other_size, _)| *other_size < size);
6986
let mut end = begin;
70-
while let Some((_addr, other_size)) = subpool.get(end) {
87+
while let Some((_addr, other_size, _)) = subpool.get(end) {
7188
if *other_size != size {
7289
break;
7390
}
@@ -80,8 +97,8 @@ impl ReusePool {
8097
// Pick a random element with the desired size.
8198
let idx = rng.gen_range(begin..end);
8299
// Remove it from the pool and return.
83-
let (chosen_addr, chosen_size) = subpool.remove(idx);
100+
let (chosen_addr, chosen_size, clock) = subpool.remove(idx);
84101
debug_assert!(chosen_size >= size && chosen_addr % align.bytes() == 0);
85-
Some(chosen_addr)
102+
Some((chosen_addr, clock))
86103
}
87104
}

src/concurrency/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ pub mod init_once;
66
pub mod thread;
77
mod vector_clock;
88
pub mod weak_memory;
9+
10+
pub use vector_clock::VClock;

src/concurrency/thread.rs

+6
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,12 @@ impl<'mir, 'tcx> Thread<'mir, 'tcx> {
223223
// empty stacks.
224224
self.top_user_relevant_frame.or_else(|| self.stack.len().checked_sub(1))
225225
}
226+
227+
pub fn current_span(&self) -> Span {
228+
self.top_user_relevant_frame()
229+
.map(|frame_idx| self.stack[frame_idx].current_span())
230+
.unwrap_or(rustc_span::DUMMY_SP)
231+
}
226232
}
227233

228234
impl<'mir, 'tcx> std::fmt::Debug for Thread<'mir, 'tcx> {

src/helpers.rs

+2-4
Original file line numberDiff line numberDiff line change
@@ -1265,9 +1265,7 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
12651265
/// This function is backed by a cache, and can be assumed to be very fast.
12661266
/// It will work even when the stack is empty.
12671267
pub fn current_span(&self) -> Span {
1268-
self.top_user_relevant_frame()
1269-
.map(|frame_idx| self.stack()[frame_idx].current_span())
1270-
.unwrap_or(rustc_span::DUMMY_SP)
1268+
self.threads.active_thread_ref().current_span()
12711269
}
12721270

12731271
/// Returns the span of the *caller* of the current operation, again
@@ -1279,7 +1277,7 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
12791277
// We need to go down at least to the caller (len - 2), or however
12801278
// far we have to go to find a frame in a local crate which is also not #[track_caller].
12811279
let frame_idx = self.top_user_relevant_frame().unwrap();
1282-
let frame_idx = cmp::min(frame_idx, self.stack().len().checked_sub(2).unwrap());
1280+
let frame_idx = cmp::min(frame_idx, self.stack().len().saturating_sub(2));
12831281
self.stack()[frame_idx].current_span()
12841282
}
12851283

src/machine.rs

+1-6
Original file line numberDiff line numberDiff line change
@@ -1300,12 +1300,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
13001300
{
13011301
*deallocated_at = Some(machine.current_span());
13021302
}
1303-
machine.alloc_addresses.get_mut().free_alloc_id(
1304-
machine.rng.get_mut(),
1305-
alloc_id,
1306-
size,
1307-
align,
1308-
);
1303+
machine.free_alloc_id(alloc_id, size, align);
13091304
Ok(())
13101305
}
13111306

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//! Regression test for <https://github.com/rust-lang/miri/issues/3450>:
2+
//! When the address gets reused, there should be a happens-before relation.
3+
#![feature(strict_provenance)]
4+
#![feature(sync_unsafe_cell)]
5+
6+
use std::cell::SyncUnsafeCell;
7+
use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
8+
use std::thread;
9+
10+
static ADDR: AtomicUsize = AtomicUsize::new(0);
11+
static VAL: SyncUnsafeCell<i32> = SyncUnsafeCell::new(0);
12+
13+
fn addr() -> usize {
14+
let alloc = Box::new(42);
15+
<*const i32>::addr(&*alloc)
16+
}
17+
18+
fn thread1() {
19+
unsafe {
20+
VAL.get().write(42);
21+
}
22+
let alloc = addr();
23+
ADDR.store(alloc, Relaxed);
24+
}
25+
26+
fn thread2() -> bool {
27+
// We try to get an allocation at the same address. If we fail too often, try again with a
28+
// different allocation to ensure it is still in Miri's reuse cache.
29+
for _ in 0..16 {
30+
let alloc = addr();
31+
let addr = ADDR.load(Relaxed);
32+
if alloc == addr {
33+
// If the new allocation is at the same address as the old one, there must be a
34+
// happens-before relationship between them. Therefore, we can read VAL without racing
35+
// and must observe the write above.
36+
let val = unsafe { VAL.get().read() };
37+
assert_eq!(val, 42);
38+
return true;
39+
}
40+
}
41+
42+
false
43+
}
44+
45+
fn main() {
46+
let mut success = false;
47+
while !success {
48+
let t1 = thread::spawn(thread1);
49+
let t2 = thread::spawn(thread2);
50+
t1.join().unwrap();
51+
success = t2.join().unwrap();
52+
53+
// Reset everything.
54+
ADDR.store(0, Relaxed);
55+
unsafe {
56+
VAL.get().write(0);
57+
}
58+
}
59+
}

0 commit comments

Comments
 (0)