Skip to content

Commit e063dee

Browse files
committed
add release sequence test
1 parent 891fbf7 commit e063dee

File tree

3 files changed

+46
-4
lines changed

3 files changed

+46
-4
lines changed

src/concurrency/data_race.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ struct MemoryCellClocks {
276276
/// zero on each write operation.
277277
read: VClock,
278278

279-
/// Atomic access, acquire, release sequence tracking clocks.
279+
/// Atomic access tracking clocks.
280280
/// For non-atomic memory this value is set to None.
281281
/// For atomic memory, each byte carries this information.
282282
atomic_ops: Option<Box<AtomicMemoryCellClocks>>,
@@ -555,7 +555,8 @@ impl MemoryCellClocks {
555555
// The handling of release sequences was changed in C++20 and so
556556
// the code here is different to the paper since now all relaxed
557557
// stores block release sequences. The exception for same-thread
558-
// relaxed stores has been removed.
558+
// relaxed stores has been removed. We always overwrite the `sync_vector`,
559+
// meaning the previous release sequence is broken.
559560
let atomic = self.atomic_mut_unwrap();
560561
atomic.sync_vector.clone_from(&thread_clocks.fence_release);
561562
Ok(())
@@ -571,6 +572,8 @@ impl MemoryCellClocks {
571572
) -> Result<(), DataRace> {
572573
self.atomic_write_detect(thread_clocks, index, access_size)?;
573574
let atomic = self.atomic_mut_unwrap();
575+
// This *joining* of `sync_vector` implements release sequences: future
576+
// reads of this location will acquire our clock *and* what was here before.
574577
atomic.sync_vector.join(&thread_clocks.clock);
575578
Ok(())
576579
}
@@ -585,6 +588,8 @@ impl MemoryCellClocks {
585588
) -> Result<(), DataRace> {
586589
self.atomic_write_detect(thread_clocks, index, access_size)?;
587590
let atomic = self.atomic_mut_unwrap();
591+
// This *joining* of `sync_vector` implements release sequences: future
592+
// reads of this location will acquire our fence clock *and* what was here before.
588593
atomic.sync_vector.join(&thread_clocks.fence_release);
589594
Ok(())
590595
}

src/concurrency/weak_memory.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,12 @@ struct StoreElement {
159159

160160
#[derive(Debug, Clone, PartialEq, Eq, Default)]
161161
struct LoadInfo {
162-
/// Timestamp of first loads from this store element by each thread
162+
/// Timestamp of first loads from this store element by each thread.
163163
timestamps: FxHashMap<VectorIdx, VTimestamp>,
164-
/// Whether this store element has been read by an SC load
164+
/// Whether this store element has been read by an SC load.
165+
/// This is crucial to ensure we respect coherence-ordered-before. Concretely we use
166+
/// this to ensure that if a store element is seen by an SC load, then all later SC loads
167+
/// cannot see `mo`-earlier store elements.
165168
sc_loaded: bool,
166169
}
167170

@@ -476,6 +479,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
476479
}
477480
let range = alloc_range(base_offset, place.layout.size);
478481
let buffer = alloc_buffers.get_or_create_store_buffer_mut(range, Some(init))?;
482+
// The RMW always reads from the most recent store.
479483
buffer.read_from_last_store(global, threads, atomic == AtomicRwOrd::SeqCst);
480484
buffer.buffered_write(new_val, global, threads, atomic == AtomicRwOrd::SeqCst)?;
481485
}

tests/pass/0weak_memory/weak.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,11 +198,44 @@ fn weaker_release_sequences() {
198198
});
199199
}
200200

201+
/// Ensuring normal release sequences (with RMWs) still work correctly.
202+
fn release_sequence() {
203+
check_all_outcomes([None, Some(1)], || {
204+
let x = static_atomic(0);
205+
let y = static_atomic(0);
206+
207+
let t1 = spawn(move || {
208+
x.store(2, Relaxed);
209+
});
210+
let t2 = spawn(move || {
211+
y.store(1, Relaxed);
212+
x.store(1, Release);
213+
x.swap(3, Relaxed);
214+
});
215+
let t3 = spawn(move || {
216+
if x.load(Acquire) == 3 {
217+
// If we read 3 here, we are seeing the result of the `x.swap` above, which
218+
// was relaxed but forms a release sequence with the `x.store` (since we know
219+
// `t1` will not be scheduled in between). This means there is a release sequence,
220+
// so we acquire the `y.store` and cannot see the original value `0` any more.
221+
Some(y.load(Relaxed))
222+
} else {
223+
None
224+
}
225+
});
226+
227+
t1.join().unwrap();
228+
t2.join().unwrap();
229+
t3.join().unwrap()
230+
});
231+
}
232+
201233
pub fn main() {
202234
relaxed();
203235
seq_cst();
204236
initialization_write(false);
205237
initialization_write(true);
206238
faa_replaced_by_load();
239+
release_sequence();
207240
weaker_release_sequences();
208241
}

0 commit comments

Comments
 (0)