6
6
//! but it is incapable of producing all possible weak behaviours allowed by the model. There are
7
7
//! certain weak behaviours observable on real hardware but not while using this.
8
8
//!
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
10
10
//! and fences introduced by P0668 (<https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0668r5.html>).
11
11
//! This implementation is not fully correct under the revised C++20 model and may generate behaviours C++20
12
12
//! disallows (<https://github.com/rust-lang/miri/issues/2301>).
13
13
//!
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
+ //!
14
19
//! Rust follows the C++20 memory model (except for the Consume ordering and some operations not performable through C++'s
15
20
//! std::atomic<T> API). It is therefore possible for this implementation to generate behaviours never observable when the
16
21
//! same program is compiled and run natively. Unfortunately, no literature exists at the time of writing which proposes
@@ -133,9 +138,17 @@ struct StoreElement {
133
138
// (partially) uninitialized data.
134
139
val : Scalar < Provenance > ,
135
140
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 {
136
148
/// 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 ,
139
152
}
140
153
141
154
impl StoreBufferAlloc {
@@ -235,18 +248,23 @@ impl<'mir, 'tcx: 'mir> StoreBuffer {
235
248
timestamp : 0 ,
236
249
val : init,
237
250
is_seqcst : false ,
238
- loads : RefCell :: new ( FxHashMap :: default ( ) ) ,
251
+ load_info : RefCell :: new ( LoadInfo :: default ( ) ) ,
239
252
} ;
240
253
ret. buffer . push_back ( store_elem) ;
241
254
ret
242
255
}
243
256
244
257
/// 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
+ ) {
246
264
let store_elem = self . buffer . back ( ) ;
247
265
if let Some ( store_elem) = store_elem {
248
266
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 ) ;
250
268
}
251
269
}
252
270
@@ -276,7 +294,7 @@ impl<'mir, 'tcx: 'mir> StoreBuffer {
276
294
validate ( ) ?;
277
295
278
296
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 ) ;
280
298
Ok ( ( loaded, recency) )
281
299
}
282
300
@@ -293,6 +311,7 @@ impl<'mir, 'tcx: 'mir> StoreBuffer {
293
311
Ok ( ( ) )
294
312
}
295
313
314
+ #[ allow( clippy:: if_same_then_else, clippy:: needless_bool) ]
296
315
/// Selects a valid store element in the buffer.
297
316
fn fetch_store < R : rand:: Rng + ?Sized > (
298
317
& self ,
@@ -319,34 +338,43 @@ impl<'mir, 'tcx: 'mir> StoreBuffer {
319
338
keep_searching = if store_elem. timestamp <= clocks. clock [ store_elem. store_index ] {
320
339
// CoWR: if a store happens-before the current load,
321
340
// 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
323
342
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
+ ) {
327
346
// CoRR: if there was a load from this store which happened-before the current load,
328
347
// 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
330
349
false
331
350
} 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
336
354
false
337
355
} else if store_elem. timestamp <= clocks. write_seqcst [ store_elem. store_index ]
338
356
&& store_elem. is_seqcst
339
357
{
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
343
368
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)
348
374
false
349
- } else { true } ;
375
+ } else {
376
+ true
377
+ } ;
350
378
351
379
true
352
380
} )
@@ -387,7 +415,7 @@ impl<'mir, 'tcx: 'mir> StoreBuffer {
387
415
// access
388
416
val,
389
417
is_seqcst,
390
- loads : RefCell :: new ( FxHashMap :: default ( ) ) ,
418
+ load_info : RefCell :: new ( LoadInfo :: default ( ) ) ,
391
419
} ;
392
420
self . buffer . push_back ( store_elem) ;
393
421
if self . buffer . len ( ) > STORE_BUFFER_LIMIT {
@@ -415,8 +443,15 @@ impl StoreElement {
415
443
/// buffer regardless of subsequent loads by the same thread; if the earliest load of another
416
444
/// thread doesn't happen before the current one, then no subsequent load by the other thread
417
445
/// 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] ) ;
420
455
self . val
421
456
}
422
457
}
@@ -476,7 +511,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
476
511
}
477
512
let range = alloc_range ( base_offset, place. layout . size ) ;
478
513
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 ) ;
480
515
buffer. buffered_write ( new_val, global, threads, atomic == AtomicRwOrd :: SeqCst ) ?;
481
516
}
482
517
Ok ( ( ) )
@@ -583,7 +618,11 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
583
618
if let Some ( alloc_buffers) = this. get_alloc_extra ( alloc_id) ?. weak_memory . as_ref ( ) {
584
619
let buffer = alloc_buffers
585
620
. 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
+ ) ;
587
626
}
588
627
}
589
628
Ok ( ( ) )
0 commit comments