Skip to content

Commit 0f9e009

Browse files
committed
Fix C++20 SC access unsoundness
1 parent a2467c9 commit 0f9e009

File tree

1 file changed

+51
-17
lines changed

1 file changed

+51
-17
lines changed

src/concurrency/weak_memory.rs

+51-17
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
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>).
@@ -133,9 +133,17 @@ struct StoreElement {
133133
// (partially) uninitialized data.
134134
val: Scalar<Provenance>,
135135

136+
/// Metadata about loads from this store element,
137+
/// behind a RefCell to keep load op take &self
138+
load_info: RefCell<LoadInfo>,
139+
}
140+
141+
#[derive(Debug, Clone, PartialEq, Eq, Default)]
142+
struct LoadInfo {
136143
/// 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>>,
144+
timestamps: FxHashMap<VectorIdx, VTimestamp>,
145+
/// Whether this store element has been read by an SC load
146+
sc_loaded: bool,
139147
}
140148

141149
impl StoreBufferAlloc {
@@ -235,18 +243,23 @@ impl<'mir, 'tcx: 'mir> StoreBuffer {
235243
timestamp: 0,
236244
val: init,
237245
is_seqcst: false,
238-
loads: RefCell::new(FxHashMap::default()),
246+
load_info: RefCell::new(LoadInfo::default()),
239247
};
240248
ret.buffer.push_back(store_elem);
241249
ret
242250
}
243251

244252
/// Reads from the last store in modification order
245-
fn read_from_last_store(&self, global: &DataRaceState, thread_mgr: &ThreadManager<'_, '_>) {
253+
fn read_from_last_store(
254+
&self,
255+
global: &DataRaceState,
256+
thread_mgr: &ThreadManager<'_, '_>,
257+
is_seqcst: bool,
258+
) {
246259
let store_elem = self.buffer.back();
247260
if let Some(store_elem) = store_elem {
248261
let (index, clocks) = global.current_thread_state(thread_mgr);
249-
store_elem.load_impl(index, &clocks);
262+
store_elem.load_impl(index, &clocks, is_seqcst);
250263
}
251264
}
252265

@@ -276,7 +289,7 @@ impl<'mir, 'tcx: 'mir> StoreBuffer {
276289
validate()?;
277290

278291
let (index, clocks) = global.current_thread_state(thread_mgr);
279-
let loaded = store_elem.load_impl(index, &clocks);
292+
let loaded = store_elem.load_impl(index, &clocks, is_seqcst);
280293
Ok((loaded, recency))
281294
}
282295

@@ -321,9 +334,9 @@ impl<'mir, 'tcx: 'mir> StoreBuffer {
321334
// then we can't read-from anything earlier in modification order.
322335
// C++20 §6.9.2.2 [intro.races] paragraph 18
323336
false
324-
} else if store_elem.loads.borrow().iter().any(|(&load_index, &load_timestamp)| {
325-
load_timestamp <= clocks.clock[load_index]
326-
}) {
337+
} else if store_elem.load_info.borrow().timestamps.iter().any(
338+
|(&load_index, &load_timestamp)| load_timestamp <= clocks.clock[load_index],
339+
) {
327340
// CoRR: if there was a load from this store which happened-before the current load,
328341
// then we cannot read-from anything earlier in modification order.
329342
// C++20 §6.9.2.2 [intro.races] paragraph 16
@@ -340,12 +353,22 @@ impl<'mir, 'tcx: 'mir> StoreBuffer {
340353
// cannot read-before the last SC store executed before the fence.
341354
// C++17 §32.4 [atomics.order] paragraph 4
342355
false
343-
} else if is_seqcst && store_elem.timestamp <= clocks.read_seqcst[store_elem.store_index] {
356+
} else if is_seqcst
357+
&& store_elem.timestamp <= clocks.read_seqcst[store_elem.store_index]
358+
{
344359
// The current SC load cannot read-before the last store sequenced-before
345360
// the last SC fence.
346361
// C++17 §32.4 [atomics.order] paragraph 5
347362
false
348-
} else {true};
363+
} else if is_seqcst && store_elem.load_info.borrow().sc_loaded {
364+
// The current SC load cannot read-before a store that an earlier SC load has observed.
365+
// See https://github.com/rust-lang/miri/issues/2301#issuecomment-1222720427
366+
// Consequences of C++20 §31.4 [atomics.order] paragraph 3.1, 3.3 (coherence-ordered before)
367+
// and 4.1 (coherence-ordered before between SC makes global total order S)
368+
false
369+
} else {
370+
true
371+
};
349372

350373
true
351374
})
@@ -386,7 +409,7 @@ impl<'mir, 'tcx: 'mir> StoreBuffer {
386409
// access
387410
val,
388411
is_seqcst,
389-
loads: RefCell::new(FxHashMap::default()),
412+
load_info: RefCell::new(LoadInfo::default()),
390413
};
391414
self.buffer.push_back(store_elem);
392415
if self.buffer.len() > STORE_BUFFER_LIMIT {
@@ -414,8 +437,15 @@ impl StoreElement {
414437
/// buffer regardless of subsequent loads by the same thread; if the earliest load of another
415438
/// thread doesn't happen before the current one, then no subsequent load by the other thread
416439
/// can happen before the current one.
417-
fn load_impl(&self, index: VectorIdx, clocks: &ThreadClockSet) -> Scalar<Provenance> {
418-
let _ = self.loads.borrow_mut().try_insert(index, clocks.clock[index]);
440+
fn load_impl(
441+
&self,
442+
index: VectorIdx,
443+
clocks: &ThreadClockSet,
444+
is_seqcst: bool,
445+
) -> Scalar<Provenance> {
446+
let mut load_info = self.load_info.borrow_mut();
447+
load_info.sc_loaded |= is_seqcst;
448+
let _ = load_info.timestamps.try_insert(index, clocks.clock[index]);
419449
self.val
420450
}
421451
}
@@ -475,7 +505,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
475505
}
476506
let range = alloc_range(base_offset, place.layout.size);
477507
let buffer = alloc_buffers.get_or_create_store_buffer_mut(range, init)?;
478-
buffer.read_from_last_store(global, threads);
508+
buffer.read_from_last_store(global, threads, atomic == AtomicRwOrd::SeqCst);
479509
buffer.buffered_write(new_val, global, threads, atomic == AtomicRwOrd::SeqCst)?;
480510
}
481511
Ok(())
@@ -582,7 +612,11 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
582612
if let Some(alloc_buffers) = this.get_alloc_extra(alloc_id)?.weak_memory.as_ref() {
583613
let buffer = alloc_buffers
584614
.get_or_create_store_buffer(alloc_range(base_offset, size), init)?;
585-
buffer.read_from_last_store(global, &this.machine.threads);
615+
buffer.read_from_last_store(
616+
global,
617+
&this.machine.threads,
618+
atomic == AtomicReadOrd::SeqCst,
619+
);
586620
}
587621
}
588622
Ok(())

0 commit comments

Comments
 (0)