Skip to content

Commit 5ac282c

Browse files
committed
add store buffer initialization sanity check that would have caught the bug
1 parent 0a32bfd commit 5ac282c

File tree

3 files changed

+52
-10
lines changed

3 files changed

+52
-10
lines changed

src/concurrency/data_race.rs

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -688,6 +688,8 @@ impl MemoryCellClocks {
688688
self.write = (index, thread_clocks.clock[index]);
689689
self.write_type = write_type;
690690
self.read.set_zero_vector();
691+
// This is not an atomic location any more.
692+
self.atomic_ops = None;
691693
Ok(())
692694
}
693695
}
@@ -754,14 +756,9 @@ pub trait EvalContextExt<'tcx>: MiriInterpCxExt<'tcx> {
754756
let this = self.eval_context_mut();
755757
this.atomic_access_check(dest, AtomicAccessType::Store)?;
756758

757-
// Read the previous value so we can put it in the store buffer later.
758-
// The program didn't actually do a read, so suppress the memory access hooks.
759-
// This is also a very special exception where we just ignore an error -- if this read
760-
// was UB e.g. because the memory is uninitialized, we don't want to know!
761-
let old_val = this.run_for_validation_ref(|this| this.read_scalar(dest)).discard_err();
762-
763759
// Inform GenMC about the atomic store.
764760
if let Some(genmc_ctx) = this.machine.data_race.as_genmc_ref() {
761+
let old_val = this.run_for_validation_ref(|this| this.read_scalar(dest)).discard_err();
765762
if genmc_ctx.atomic_store(
766763
this,
767764
dest.ptr().addr(),
@@ -776,6 +773,9 @@ pub trait EvalContextExt<'tcx>: MiriInterpCxExt<'tcx> {
776773
}
777774
return interp_ok(());
778775
}
776+
777+
// Read the previous value so we can put it in the store buffer later.
778+
let old_val = this.get_latest_nonatomic_val(dest);
779779
this.allow_data_races_mut(move |this| this.write_scalar(val, dest))?;
780780
this.validate_atomic_store(dest, atomic)?;
781781
this.buffered_atomic_write(val, dest, atomic, old_val)
@@ -1536,6 +1536,35 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> {
15361536
)
15371537
}
15381538

1539+
/// Returns the most recent *non-atomic* value stored in the given place.
1540+
/// Errors if we don't need that (because we don't do store buffering) or if
1541+
/// the most recent value is in fact atomic.
1542+
fn get_latest_nonatomic_val(&self, place: &MPlaceTy<'tcx>) -> Result<Option<Scalar>, ()> {
1543+
let this = self.eval_context_ref();
1544+
// These cannot fail because `atomic_access_check` was done first.
1545+
let (alloc_id, offset, _prov) = this.ptr_get_alloc_id(place.ptr(), 0).unwrap();
1546+
let alloc_meta = &this.get_alloc_extra(alloc_id).unwrap().data_race;
1547+
if alloc_meta.as_weak_memory_ref().is_none() {
1548+
// No reason to read old value if we don't track store buffers.
1549+
return Err(());
1550+
}
1551+
let data_race = alloc_meta.as_vclocks_ref().unwrap();
1552+
// Only read old value if this is currently a non-atomic location.
1553+
for (_range, clocks) in data_race.alloc_ranges.borrow_mut().iter(offset, place.layout.size)
1554+
{
1555+
// If this had an atomic write that's not before the non-atomic write, that should
1556+
// already be in the store buffer. Initializing the store buffer now would use the
1557+
// wrong `sync_clock` so we better make sure that does not happen.
1558+
if clocks.atomic().is_some_and(|atomic| !(atomic.write_vector <= clocks.write())) {
1559+
return Err(());
1560+
}
1561+
}
1562+
// The program didn't actually do a read, so suppress the memory access hooks.
1563+
// This is also a very special exception where we just ignore an error -- if this read
1564+
// was UB e.g. because the memory is uninitialized, we don't want to know!
1565+
Ok(this.run_for_validation_ref(|this| this.read_scalar(place)).discard_err())
1566+
}
1567+
15391568
/// Generic atomic operation implementation
15401569
fn validate_atomic_op<A: Debug + Copy>(
15411570
&self,

src/concurrency/weak_memory.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -223,18 +223,23 @@ impl StoreBufferAlloc {
223223
fn get_or_create_store_buffer_mut<'tcx>(
224224
&mut self,
225225
range: AllocRange,
226-
init: Option<Scalar>,
226+
init: Result<Option<Scalar>, ()>,
227227
) -> InterpResult<'tcx, &mut StoreBuffer> {
228228
let buffers = self.store_buffers.get_mut();
229229
let access_type = buffers.access_type(range);
230230
let pos = match access_type {
231231
AccessType::PerfectlyOverlapping(pos) => pos,
232232
AccessType::Empty(pos) => {
233+
let init =
234+
init.expect("cannot have empty store buffer when previous write was atomic");
233235
buffers.insert_at_pos(pos, range, StoreBuffer::new(init));
234236
pos
235237
}
236238
AccessType::ImperfectlyOverlapping(pos_range) => {
237239
// Once we reach here we would've already checked that this access is not racy.
240+
let init = init.expect(
241+
"cannot have partially overlapping store buffer when previous write was atomic",
242+
);
238243
buffers.remove_pos_range(pos_range.clone());
239244
buffers.insert_at_pos(pos_range.start, range, StoreBuffer::new(init));
240245
pos_range.start
@@ -490,7 +495,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
490495
}
491496
let range = alloc_range(base_offset, place.layout.size);
492497
let sync_clock = data_race_clocks.sync_clock(range);
493-
let buffer = alloc_buffers.get_or_create_store_buffer_mut(range, Some(init))?;
498+
let buffer = alloc_buffers.get_or_create_store_buffer_mut(range, Ok(Some(init)))?;
494499
// The RMW always reads from the most recent store.
495500
buffer.read_from_last_store(global, threads, atomic == AtomicRwOrd::SeqCst);
496501
buffer.buffered_write(
@@ -556,15 +561,16 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
556561
/// Add the given write to the store buffer. (Does not change machine memory.)
557562
///
558563
/// `init` says with which value to initialize the store buffer in case there wasn't a store
559-
/// buffer for this memory range before.
564+
/// buffer for this memory range before. `Err(())` means the value is not available;
565+
/// `Ok(None)` means the memory does not contain a valid scalar.
560566
///
561567
/// Must be called *after* `validate_atomic_store` to ensure that `sync_clock` is up-to-date.
562568
fn buffered_atomic_write(
563569
&mut self,
564570
val: Scalar,
565571
dest: &MPlaceTy<'tcx>,
566572
atomic: AtomicWriteOrd,
567-
init: Option<Scalar>,
573+
init: Result<Option<Scalar>, ()>,
568574
) -> InterpResult<'tcx> {
569575
let this = self.eval_context_mut();
570576
let (alloc_id, base_offset, ..) = this.ptr_get_alloc_id(dest.ptr(), 0)?;

tests/pass/0weak_memory/weak.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,13 @@ fn initialization_write(add_fence: bool) {
8888
check_all_outcomes([11, 22], || {
8989
let x = static_atomic(11);
9090

91+
if add_fence {
92+
// For the fun of it, let's make this location atomic and non-atomic again,
93+
// to ensure Miri updates the state properly for that.
94+
x.store(99, Relaxed);
95+
unsafe { std::ptr::from_ref(x).cast_mut().write(11.into()) };
96+
}
97+
9198
let wait = static_atomic(0);
9299

93100
let j1 = spawn(move || {

0 commit comments

Comments
 (0)