12
12
//! The implementation also models races with memory allocation and deallocation via treating allocation and
13
13
//! deallocation as a type of write internally for detecting data-races.
14
14
//!
15
- //! This does not explore weak memory orders and so can still miss data-races
15
+ //! Weak memory orders are explored but not all weak behaviours are exhibited, so it can still miss data-races
16
16
//! but should not report false-positives
17
17
//!
18
18
//! Data-race definition from(<https://en.cppreference.com/w/cpp/language/memory_model#Threads_and_data_races>):
29
29
//! This means that the thread-index can be safely re-used, starting on the next timestamp for the newly created
30
30
//! thread.
31
31
//!
32
- //! The sequentially consistent ordering corresponds to the ordering that the threads
33
- //! are currently scheduled, this means that the data-race detector has no additional
34
- //! logic for sequentially consistent accesses at the moment since they are indistinguishable
35
- //! from acquire/release operations. If weak memory orderings are explored then this
36
- //! may need to change or be updated accordingly.
37
- //!
38
- //! Per the C++ spec for the memory model a sequentially consistent operation:
39
- //! "A load operation with this memory order performs an acquire operation,
40
- //! a store performs a release operation, and read-modify-write performs
41
- //! both an acquire operation and a release operation, plus a single total
42
- //! order exists in which all threads observe all modifications in the same
43
- //! order (see Sequentially-consistent ordering below) "
44
- //! So in the absence of weak memory effects a seq-cst load & a seq-cst store is identical
45
- //! to an acquire load and a release store given the global sequentially consistent order
46
- //! of the schedule.
47
- //!
48
32
//! The timestamps used in the data-race detector assign each sequence of non-atomic operations
49
33
//! followed by a single atomic or concurrent operation a single timestamp.
50
34
//! Write, Read, Write, ThreadJoin will be represented by a single timestamp value on a thread.
@@ -67,6 +51,7 @@ use std::{
67
51
mem,
68
52
} ;
69
53
54
+ use rustc_const_eval:: interpret:: alloc_range;
70
55
use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
71
56
use rustc_index:: vec:: { Idx , IndexVec } ;
72
57
use rustc_middle:: { mir, ty:: layout:: TyAndLayout } ;
@@ -115,10 +100,10 @@ pub enum AtomicFenceOp {
115
100
/// of a thread, contains the happens-before clock and
116
101
/// additional metadata to model atomic fence operations.
117
102
#[ derive( Clone , Default , Debug ) ]
118
- struct ThreadClockSet {
103
+ pub struct ThreadClockSet {
119
104
/// The increasing clock representing timestamps
120
105
/// that happen-before this thread.
121
- clock : VClock ,
106
+ pub clock : VClock ,
122
107
123
108
/// The set of timestamps that will happen-before this
124
109
/// thread once it performs an acquire fence.
@@ -127,6 +112,12 @@ struct ThreadClockSet {
127
112
/// The last timestamp of happens-before relations that
128
113
/// have been released by this thread by a fence.
129
114
fence_release : VClock ,
115
+
116
+ pub fence_seqcst : VClock ,
117
+
118
+ pub write_seqcst : VClock ,
119
+
120
+ pub read_seqcst : VClock ,
130
121
}
131
122
132
123
impl ThreadClockSet {
@@ -169,7 +160,7 @@ pub struct DataRace;
169
160
/// common case where no atomic operations
170
161
/// exists on the memory cell.
171
162
#[ derive( Clone , PartialEq , Eq , Default , Debug ) ]
172
- struct AtomicMemoryCellClocks {
163
+ pub struct AtomicMemoryCellClocks {
173
164
/// The clock-vector of the timestamp of the last atomic
174
165
/// read operation performed by each thread.
175
166
/// This detects potential data-races between atomic read
@@ -514,7 +505,32 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: MiriEvalContextExt<'mir, 'tcx> {
514
505
atomic : AtomicReadOp ,
515
506
) -> InterpResult < ' tcx , ScalarMaybeUninit < Tag > > {
516
507
let this = self . eval_context_ref ( ) ;
508
+ // This will read from the last store in the modification order of this location. In case
509
+ // weak memory emulation is enabled, this may not be the store we will pick to actually read from and return.
510
+ // This is fine with StackedBorrow and race checks because they don't concern metadata on
511
+ // the *value* (including the associated provenance if this is an AtomicPtr) at this location.
512
+ // Only metadata on the location itself is used.
517
513
let scalar = this. allow_data_races_ref ( move |this| this. read_scalar ( & place. into ( ) ) ) ?;
514
+
515
+ if let Some ( global) = & this. machine . data_race {
516
+ let ( alloc_id, base_offset, ..) = this. ptr_get_alloc_id ( place. ptr ) ?;
517
+ if let Some ( alloc_buffers) = this. get_alloc_extra ( alloc_id) ?. weak_memory . as_ref ( ) {
518
+ if atomic == AtomicReadOp :: SeqCst {
519
+ global. sc_read ( ) ;
520
+ }
521
+ let mut rng = this. machine . rng . borrow_mut ( ) ;
522
+ let loaded = alloc_buffers. buffered_read (
523
+ alloc_range ( base_offset, place. layout . size ) ,
524
+ global,
525
+ atomic == AtomicReadOp :: SeqCst ,
526
+ & mut * rng,
527
+ || this. validate_atomic_load ( place, atomic) ,
528
+ ) ?;
529
+
530
+ return Ok ( loaded. unwrap_or ( scalar) ) ;
531
+ }
532
+ }
533
+
518
534
this. validate_atomic_load ( place, atomic) ?;
519
535
Ok ( scalar)
520
536
}
@@ -528,7 +544,27 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: MiriEvalContextExt<'mir, 'tcx> {
528
544
) -> InterpResult < ' tcx > {
529
545
let this = self . eval_context_mut ( ) ;
530
546
this. allow_data_races_mut ( move |this| this. write_scalar ( val, & ( * dest) . into ( ) ) ) ?;
531
- this. validate_atomic_store ( dest, atomic)
547
+
548
+ this. validate_atomic_store ( dest, atomic) ?;
549
+ let ( alloc_id, base_offset, ..) = this. ptr_get_alloc_id ( dest. ptr ) ?;
550
+ if let (
551
+ crate :: AllocExtra { weak_memory : Some ( alloc_buffers) , .. } ,
552
+ crate :: Evaluator { data_race : Some ( global) , .. } ,
553
+ ) = this. get_alloc_extra_mut ( alloc_id) ?
554
+ {
555
+ if atomic == AtomicWriteOp :: SeqCst {
556
+ global. sc_write ( ) ;
557
+ }
558
+ let size = dest. layout . size ;
559
+ alloc_buffers. buffered_write (
560
+ val,
561
+ alloc_range ( base_offset, size) ,
562
+ global,
563
+ atomic == AtomicWriteOp :: SeqCst ,
564
+ ) ?;
565
+ }
566
+
567
+ Ok ( ( ) )
532
568
}
533
569
534
570
/// Perform an atomic operation on a memory location.
@@ -550,6 +586,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: MiriEvalContextExt<'mir, 'tcx> {
550
586
this. allow_data_races_mut ( |this| this. write_immediate ( * val, & ( * place) . into ( ) ) ) ?;
551
587
552
588
this. validate_atomic_rmw ( place, atomic) ?;
589
+
590
+ this. buffered_atomic_rmw ( val. to_scalar_or_uninit ( ) , place, atomic) ?;
553
591
Ok ( old)
554
592
}
555
593
@@ -565,7 +603,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: MiriEvalContextExt<'mir, 'tcx> {
565
603
566
604
let old = this. allow_data_races_mut ( |this| this. read_scalar ( & place. into ( ) ) ) ?;
567
605
this. allow_data_races_mut ( |this| this. write_scalar ( new, & ( * place) . into ( ) ) ) ?;
606
+
568
607
this. validate_atomic_rmw ( place, atomic) ?;
608
+
609
+ this. buffered_atomic_rmw ( new, place, atomic) ?;
569
610
Ok ( old)
570
611
}
571
612
@@ -584,15 +625,25 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: MiriEvalContextExt<'mir, 'tcx> {
584
625
let lt = this. binary_op ( mir:: BinOp :: Lt , & old, & rhs) ?. to_scalar ( ) ?. to_bool ( ) ?;
585
626
586
627
let new_val = if min {
587
- if lt { & old } else { & rhs }
628
+ if lt {
629
+ & old
630
+ } else {
631
+ & rhs
632
+ }
588
633
} else {
589
- if lt { & rhs } else { & old }
634
+ if lt {
635
+ & rhs
636
+ } else {
637
+ & old
638
+ }
590
639
} ;
591
640
592
641
this. allow_data_races_mut ( |this| this. write_immediate ( * * new_val, & ( * place) . into ( ) ) ) ?;
593
642
594
643
this. validate_atomic_rmw ( place, atomic) ?;
595
644
645
+ this. buffered_atomic_rmw ( new_val. to_scalar_or_uninit ( ) , place, atomic) ?;
646
+
596
647
// Return the old value.
597
648
Ok ( old)
598
649
}
@@ -642,14 +693,56 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: MiriEvalContextExt<'mir, 'tcx> {
642
693
if cmpxchg_success {
643
694
this. allow_data_races_mut ( |this| this. write_scalar ( new, & ( * place) . into ( ) ) ) ?;
644
695
this. validate_atomic_rmw ( place, success) ?;
696
+ this. buffered_atomic_rmw ( new, place, success) ?;
645
697
} else {
646
698
this. validate_atomic_load ( place, fail) ?;
699
+ // A failed compare exchange is equivalent to a load, reading from the latest store
700
+ // in the modification order.
701
+ // Since `old` is only a value and not the store element, we need to separately
702
+ // find it in our store buffer and perform load_impl on it.
703
+ if let Some ( global) = & this. machine . data_race {
704
+ if fail == AtomicReadOp :: SeqCst {
705
+ global. sc_read ( ) ;
706
+ }
707
+ let size = place. layout . size ;
708
+ let ( alloc_id, base_offset, ..) = this. ptr_get_alloc_id ( place. ptr ) ?;
709
+ if let Some ( alloc_buffers) = this. get_alloc_extra ( alloc_id) ?. weak_memory . as_ref ( ) {
710
+ if global. multi_threaded . get ( ) {
711
+ alloc_buffers. read_from_last_store ( alloc_range ( base_offset, size) , global) ;
712
+ }
713
+ }
714
+ }
647
715
}
648
716
649
717
// Return the old value.
650
718
Ok ( res)
651
719
}
652
720
721
+ fn buffered_atomic_rmw (
722
+ & mut self ,
723
+ new_val : ScalarMaybeUninit < Tag > ,
724
+ place : & MPlaceTy < ' tcx , Tag > ,
725
+ atomic : AtomicRwOp ,
726
+ ) -> InterpResult < ' tcx > {
727
+ let this = self . eval_context_mut ( ) ;
728
+ let ( alloc_id, base_offset, ..) = this. ptr_get_alloc_id ( place. ptr ) ?;
729
+ if let (
730
+ crate :: AllocExtra { weak_memory : Some ( alloc_buffers) , .. } ,
731
+ crate :: Evaluator { data_race : Some ( global) , .. } ,
732
+ ) = this. get_alloc_extra_mut ( alloc_id) ?
733
+ {
734
+ if atomic == AtomicRwOp :: SeqCst {
735
+ global. sc_read ( ) ;
736
+ global. sc_write ( ) ;
737
+ }
738
+ let size = place. layout . size ;
739
+ let range = alloc_range ( base_offset, size) ;
740
+ alloc_buffers. read_from_last_store ( range, global) ;
741
+ alloc_buffers. buffered_write ( new_val, range, global, atomic == AtomicRwOp :: SeqCst ) ?;
742
+ }
743
+ Ok ( ( ) )
744
+ }
745
+
653
746
/// Update the data-race detector for an atomic read occurring at the
654
747
/// associated memory-place and on the current thread.
655
748
fn validate_atomic_load (
@@ -723,7 +816,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: MiriEvalContextExt<'mir, 'tcx> {
723
816
fn validate_atomic_fence ( & mut self , atomic : AtomicFenceOp ) -> InterpResult < ' tcx > {
724
817
let this = self . eval_context_mut ( ) ;
725
818
if let Some ( data_race) = & mut this. machine . data_race {
726
- data_race. maybe_perform_sync_operation ( move |index, mut clocks| {
819
+ data_race. maybe_perform_sync_operation ( |index, mut clocks| {
727
820
log:: trace!( "Atomic fence on {:?} with ordering {:?}" , index, atomic) ;
728
821
729
822
// Apply data-race detection for the current fences
@@ -737,6 +830,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: MiriEvalContextExt<'mir, 'tcx> {
737
830
// Either Release | AcqRel | SeqCst
738
831
clocks. apply_release_fence ( ) ;
739
832
}
833
+ if atomic == AtomicFenceOp :: SeqCst {
834
+ data_race. last_sc_fence . borrow_mut ( ) . set_at_index ( & clocks. clock , index) ;
835
+ clocks. fence_seqcst . join ( & data_race. last_sc_fence . borrow ( ) ) ;
836
+ clocks. write_seqcst . join ( & data_race. last_sc_write . borrow ( ) ) ;
837
+ }
740
838
741
839
// Increment timestamp in case of release semantics.
742
840
Ok ( atomic != AtomicFenceOp :: Acquire )
@@ -1116,6 +1214,12 @@ pub struct GlobalState {
1116
1214
/// The associated vector index will be moved into re-use candidates
1117
1215
/// after the join operation occurs.
1118
1216
terminated_threads : RefCell < FxHashMap < ThreadId , VectorIdx > > ,
1217
+
1218
+ /// The timestamp of last SC fence performed by each thread
1219
+ last_sc_fence : RefCell < VClock > ,
1220
+
1221
+ /// The timestamp of last SC write performed by each thread
1222
+ last_sc_write : RefCell < VClock > ,
1119
1223
}
1120
1224
1121
1225
impl GlobalState {
@@ -1131,6 +1235,8 @@ impl GlobalState {
1131
1235
active_thread_count : Cell :: new ( 1 ) ,
1132
1236
reuse_candidates : RefCell :: new ( FxHashSet :: default ( ) ) ,
1133
1237
terminated_threads : RefCell :: new ( FxHashMap :: default ( ) ) ,
1238
+ last_sc_fence : RefCell :: new ( VClock :: default ( ) ) ,
1239
+ last_sc_write : RefCell :: new ( VClock :: default ( ) ) ,
1134
1240
} ;
1135
1241
1136
1242
// Setup the main-thread since it is not explicitly created:
@@ -1445,7 +1551,7 @@ impl GlobalState {
1445
1551
/// Load the current vector clock in use and the current set of thread clocks
1446
1552
/// in use for the vector.
1447
1553
#[ inline]
1448
- fn current_thread_state ( & self ) -> ( VectorIdx , Ref < ' _ , ThreadClockSet > ) {
1554
+ pub fn current_thread_state ( & self ) -> ( VectorIdx , Ref < ' _ , ThreadClockSet > ) {
1449
1555
let index = self . current_index ( ) ;
1450
1556
let ref_vector = self . vector_clocks . borrow ( ) ;
1451
1557
let clocks = Ref :: map ( ref_vector, |vec| & vec[ index] ) ;
@@ -1455,7 +1561,7 @@ impl GlobalState {
1455
1561
/// Load the current vector clock in use and the current set of thread clocks
1456
1562
/// in use for the vector mutably for modification.
1457
1563
#[ inline]
1458
- fn current_thread_state_mut ( & self ) -> ( VectorIdx , RefMut < ' _ , ThreadClockSet > ) {
1564
+ pub fn current_thread_state_mut ( & self ) -> ( VectorIdx , RefMut < ' _ , ThreadClockSet > ) {
1459
1565
let index = self . current_index ( ) ;
1460
1566
let ref_vector = self . vector_clocks . borrow_mut ( ) ;
1461
1567
let clocks = RefMut :: map ( ref_vector, |vec| & mut vec[ index] ) ;
@@ -1468,4 +1574,16 @@ impl GlobalState {
1468
1574
fn current_index ( & self ) -> VectorIdx {
1469
1575
self . current_index . get ( )
1470
1576
}
1577
+
1578
+ // SC ATOMIC STORE rule in the paper.
1579
+ fn sc_write ( & self ) {
1580
+ let ( index, clocks) = self . current_thread_state ( ) ;
1581
+ self . last_sc_write . borrow_mut ( ) . set_at_index ( & clocks. clock , index) ;
1582
+ }
1583
+
1584
+ // SC ATOMIC READ rule in the paper.
1585
+ fn sc_read ( & self ) {
1586
+ let ( .., mut clocks) = self . current_thread_state_mut ( ) ;
1587
+ clocks. read_seqcst . join ( & self . last_sc_fence . borrow ( ) ) ;
1588
+ }
1471
1589
}
0 commit comments