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
141154impl 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 ( ( ) )
0 commit comments