Skip to content

Commit 8866513

Browse files
committed
Auto merge of #2512 - cbeuw:scfix, r=RalfJung
Strengthen C++20 SC accesses `@SabrinaJewson` noted in #2301 that Miri could produce behaviours forbidden under C++20 even without SC fences. Due to the added coherence-ordered before relationship which is created from read from and read before, plus the fact that coherence-ordered before between SC operations must be consistent with the Global Total Order S, in C++20 if there's an SC load that reads from any store, then a later SC load cannot read before that store. This PR adds this restriction
2 parents 74d99ab + 3e97d8e commit 8866513

File tree

2 files changed

+98
-29
lines changed

2 files changed

+98
-29
lines changed

src/concurrency/weak_memory.rs

Lines changed: 68 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,16 @@
66
//! but it is incapable of producing all possible weak behaviours allowed by the model. There are
77
//! certain weak behaviours observable on real hardware but not while using this.
88
//!
9-
//! Note that this implementation does not take into account of C++20's memory model revision to SC accesses
9+
//! Note that this implementation does not fully take into account of C++20's memory model revision to SC accesses
1010
//! and fences introduced by P0668 (<https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0668r5.html>).
1111
//! This implementation is not fully correct under the revised C++20 model and may generate behaviours C++20
1212
//! disallows (<https://github.com/rust-lang/miri/issues/2301>).
1313
//!
14+
//! A modification is made to the paper's model to partially address C++20 changes.
15+
//! Specifically, if an SC load reads from an atomic store of any ordering, then a later SC load cannot read from
16+
//! an earlier store in the location's modification order. This is to prevent creating a backwards S edge from the second
17+
//! load to the first, as a result of C++20's coherence-ordered before rules.
18+
//!
1419
//! Rust follows the C++20 memory model (except for the Consume ordering and some operations not performable through C++'s
1520
//! std::atomic<T> API). It is therefore possible for this implementation to generate behaviours never observable when the
1621
//! same program is compiled and run natively. Unfortunately, no literature exists at the time of writing which proposes
@@ -133,9 +138,17 @@ struct StoreElement {
133138
// (partially) uninitialized data.
134139
val: Scalar<Provenance>,
135140

141+
/// Metadata about loads from this store element,
142+
/// behind a RefCell to keep load op take &self
143+
load_info: RefCell<LoadInfo>,
144+
}
145+
146+
#[derive(Debug, Clone, PartialEq, Eq, Default)]
147+
struct LoadInfo {
136148
/// Timestamp of first loads from this store element by each thread
137-
/// Behind a RefCell to keep load op take &self
138-
loads: RefCell<FxHashMap<VectorIdx, VTimestamp>>,
149+
timestamps: FxHashMap<VectorIdx, VTimestamp>,
150+
/// Whether this store element has been read by an SC load
151+
sc_loaded: bool,
139152
}
140153

141154
impl StoreBufferAlloc {
@@ -235,18 +248,23 @@ impl<'mir, 'tcx: 'mir> StoreBuffer {
235248
timestamp: 0,
236249
val: init,
237250
is_seqcst: false,
238-
loads: RefCell::new(FxHashMap::default()),
251+
load_info: RefCell::new(LoadInfo::default()),
239252
};
240253
ret.buffer.push_back(store_elem);
241254
ret
242255
}
243256

244257
/// Reads from the last store in modification order
245-
fn read_from_last_store(&self, global: &DataRaceState, thread_mgr: &ThreadManager<'_, '_>) {
258+
fn read_from_last_store(
259+
&self,
260+
global: &DataRaceState,
261+
thread_mgr: &ThreadManager<'_, '_>,
262+
is_seqcst: bool,
263+
) {
246264
let store_elem = self.buffer.back();
247265
if let Some(store_elem) = store_elem {
248266
let (index, clocks) = global.current_thread_state(thread_mgr);
249-
store_elem.load_impl(index, &clocks);
267+
store_elem.load_impl(index, &clocks, is_seqcst);
250268
}
251269
}
252270

@@ -276,7 +294,7 @@ impl<'mir, 'tcx: 'mir> StoreBuffer {
276294
validate()?;
277295

278296
let (index, clocks) = global.current_thread_state(thread_mgr);
279-
let loaded = store_elem.load_impl(index, &clocks);
297+
let loaded = store_elem.load_impl(index, &clocks, is_seqcst);
280298
Ok((loaded, recency))
281299
}
282300

@@ -293,6 +311,7 @@ impl<'mir, 'tcx: 'mir> StoreBuffer {
293311
Ok(())
294312
}
295313

314+
#[allow(clippy::if_same_then_else, clippy::needless_bool)]
296315
/// Selects a valid store element in the buffer.
297316
fn fetch_store<R: rand::Rng + ?Sized>(
298317
&self,
@@ -319,34 +338,43 @@ impl<'mir, 'tcx: 'mir> StoreBuffer {
319338
keep_searching = if store_elem.timestamp <= clocks.clock[store_elem.store_index] {
320339
// CoWR: if a store happens-before the current load,
321340
// then we can't read-from anything earlier in modification order.
322-
log::info!("Stopping due to coherent write-read");
341+
// C++20 §6.9.2.2 [intro.races] paragraph 18
323342
false
324-
} else if store_elem.loads.borrow().iter().any(|(&load_index, &load_timestamp)| {
325-
load_timestamp <= clocks.clock[load_index]
326-
}) {
343+
} else if store_elem.load_info.borrow().timestamps.iter().any(
344+
|(&load_index, &load_timestamp)| load_timestamp <= clocks.clock[load_index],
345+
) {
327346
// CoRR: if there was a load from this store which happened-before the current load,
328347
// then we cannot read-from anything earlier in modification order.
329-
log::info!("Stopping due to coherent read-read");
348+
// C++20 §6.9.2.2 [intro.races] paragraph 16
330349
false
331350
} else if store_elem.timestamp <= clocks.fence_seqcst[store_elem.store_index] {
332-
// The current load, which may be sequenced-after an SC fence, can only read-from
333-
// the last store sequenced-before an SC fence in another thread (or any stores
334-
// later than that SC fence)
335-
log::info!("Stopping due to coherent load sequenced after sc fence");
351+
// The current load, which may be sequenced-after an SC fence, cannot read-before
352+
// the last store sequenced-before an SC fence in another thread.
353+
// C++17 §32.4 [atomics.order] paragraph 6
336354
false
337355
} else if store_elem.timestamp <= clocks.write_seqcst[store_elem.store_index]
338356
&& store_elem.is_seqcst
339357
{
340-
// The current non-SC load can only read-from the latest SC store (or any stores later than that
341-
// SC store)
342-
log::info!("Stopping due to needing to load from the last SC store");
358+
// The current non-SC load, which may be sequenced-after an SC fence,
359+
// cannot read-before the last SC store executed before the fence.
360+
// C++17 §32.4 [atomics.order] paragraph 4
361+
false
362+
} else if is_seqcst
363+
&& store_elem.timestamp <= clocks.read_seqcst[store_elem.store_index]
364+
{
365+
// The current SC load cannot read-before the last store sequenced-before
366+
// the last SC fence.
367+
// C++17 §32.4 [atomics.order] paragraph 5
343368
false
344-
} else if is_seqcst && store_elem.timestamp <= clocks.read_seqcst[store_elem.store_index] {
345-
// The current SC load can only read-from the last store sequenced-before
346-
// the last SC fence (or any stores later than the SC fence)
347-
log::info!("Stopping due to sc load needing to load from the last SC store before an SC fence");
369+
} else if is_seqcst && store_elem.load_info.borrow().sc_loaded {
370+
// The current SC load cannot read-before a store that an earlier SC load has observed.
371+
// See https://github.com/rust-lang/miri/issues/2301#issuecomment-1222720427
372+
// Consequences of C++20 §31.4 [atomics.order] paragraph 3.1, 3.3 (coherence-ordered before)
373+
// and 4.1 (coherence-ordered before between SC makes global total order S)
348374
false
349-
} else {true};
375+
} else {
376+
true
377+
};
350378

351379
true
352380
})
@@ -387,7 +415,7 @@ impl<'mir, 'tcx: 'mir> StoreBuffer {
387415
// access
388416
val,
389417
is_seqcst,
390-
loads: RefCell::new(FxHashMap::default()),
418+
load_info: RefCell::new(LoadInfo::default()),
391419
};
392420
self.buffer.push_back(store_elem);
393421
if self.buffer.len() > STORE_BUFFER_LIMIT {
@@ -415,8 +443,15 @@ impl StoreElement {
415443
/// buffer regardless of subsequent loads by the same thread; if the earliest load of another
416444
/// thread doesn't happen before the current one, then no subsequent load by the other thread
417445
/// can happen before the current one.
418-
fn load_impl(&self, index: VectorIdx, clocks: &ThreadClockSet) -> Scalar<Provenance> {
419-
let _ = self.loads.borrow_mut().try_insert(index, clocks.clock[index]);
446+
fn load_impl(
447+
&self,
448+
index: VectorIdx,
449+
clocks: &ThreadClockSet,
450+
is_seqcst: bool,
451+
) -> Scalar<Provenance> {
452+
let mut load_info = self.load_info.borrow_mut();
453+
load_info.sc_loaded |= is_seqcst;
454+
let _ = load_info.timestamps.try_insert(index, clocks.clock[index]);
420455
self.val
421456
}
422457
}
@@ -476,7 +511,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
476511
}
477512
let range = alloc_range(base_offset, place.layout.size);
478513
let buffer = alloc_buffers.get_or_create_store_buffer_mut(range, init)?;
479-
buffer.read_from_last_store(global, threads);
514+
buffer.read_from_last_store(global, threads, atomic == AtomicRwOrd::SeqCst);
480515
buffer.buffered_write(new_val, global, threads, atomic == AtomicRwOrd::SeqCst)?;
481516
}
482517
Ok(())
@@ -583,7 +618,11 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
583618
if let Some(alloc_buffers) = this.get_alloc_extra(alloc_id)?.weak_memory.as_ref() {
584619
let buffer = alloc_buffers
585620
.get_or_create_store_buffer(alloc_range(base_offset, size), init)?;
586-
buffer.read_from_last_store(global, &this.machine.threads);
621+
buffer.read_from_last_store(
622+
global,
623+
&this.machine.threads,
624+
atomic == AtomicReadOrd::SeqCst,
625+
);
587626
}
588627
}
589628
Ok(())

tests/pass/0weak_memory_consistency.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,35 @@ fn test_sync_through_rmw_and_fences() {
257257
assert_ne!((a, b), (0, 0));
258258
}
259259

260+
// Test case by @SabrinaJewson
261+
// https://github.com/rust-lang/miri/issues/2301#issuecomment-1221502757
262+
// Demonstrating C++20 SC access changes
263+
fn test_iriw_sc_rlx() {
264+
let x = static_atomic_bool(false);
265+
let y = static_atomic_bool(false);
266+
267+
x.store(false, Relaxed);
268+
y.store(false, Relaxed);
269+
270+
let a = spawn(move || x.store(true, Relaxed));
271+
let b = spawn(move || y.store(true, Relaxed));
272+
let c = spawn(move || {
273+
while !x.load(SeqCst) {}
274+
y.load(SeqCst)
275+
});
276+
let d = spawn(move || {
277+
while !y.load(SeqCst) {}
278+
x.load(SeqCst)
279+
});
280+
281+
a.join().unwrap();
282+
b.join().unwrap();
283+
let c = c.join().unwrap();
284+
let d = d.join().unwrap();
285+
286+
assert!(c || d);
287+
}
288+
260289
pub fn main() {
261290
for _ in 0..50 {
262291
test_single_thread();
@@ -267,5 +296,6 @@ pub fn main() {
267296
test_corr();
268297
test_sc_store_buffering();
269298
test_sync_through_rmw_and_fences();
299+
test_iriw_sc_rlx();
270300
}
271301
}

0 commit comments

Comments
 (0)