41
41
//! on the data-race detection code.
42
42
43
43
use std:: {
44
- borrow:: Cow ,
45
44
cell:: { Cell , Ref , RefCell , RefMut } ,
46
45
fmt:: Debug ,
47
46
mem,
@@ -206,7 +205,7 @@ enum AtomicAccessType {
206
205
/// are all treated as writes for the purpose
207
206
/// of the data-race detector.
208
207
#[ derive( Copy , Clone , PartialEq , Eq , Debug ) ]
209
- enum WriteType {
208
+ enum NaWriteType {
210
209
/// Allocate memory.
211
210
Allocate ,
212
211
@@ -219,12 +218,48 @@ enum WriteType {
219
218
/// (Same for `Allocate` above.)
220
219
Deallocate ,
221
220
}
222
- impl WriteType {
223
- fn get_descriptor ( self ) -> & ' static str {
221
+
222
+ impl NaWriteType {
223
+ fn description ( self ) -> & ' static str {
224
224
match self {
225
- WriteType :: Allocate => "Allocate" ,
226
- WriteType :: Write => "Write" ,
227
- WriteType :: Deallocate => "Deallocate" ,
225
+ NaWriteType :: Allocate => "creating a new allocation" ,
226
+ NaWriteType :: Write => "non-atomic write" ,
227
+ NaWriteType :: Deallocate => "deallocation" ,
228
+ }
229
+ }
230
+ }
231
+
232
+ #[ derive( Copy , Clone , PartialEq , Eq , Debug ) ]
233
+ enum AccessType {
234
+ NaRead ,
235
+ NaWrite ( NaWriteType ) ,
236
+ AtomicLoad ,
237
+ AtomicStore ,
238
+ AtomicRmw ,
239
+ }
240
+
241
+ impl AccessType {
242
+ fn description ( self ) -> & ' static str {
243
+ match self {
244
+ AccessType :: NaRead => "non-atomic read" ,
245
+ AccessType :: NaWrite ( w) => w. description ( ) ,
246
+ AccessType :: AtomicLoad => "atomic load" ,
247
+ AccessType :: AtomicStore => "atomic store" ,
248
+ AccessType :: AtomicRmw => "atomic read-modify-write" ,
249
+ }
250
+ }
251
+
252
+ fn is_atomic ( self ) -> bool {
253
+ match self {
254
+ AccessType :: AtomicLoad | AccessType :: AtomicStore | AccessType :: AtomicRmw => true ,
255
+ AccessType :: NaRead | AccessType :: NaWrite ( _) => false ,
256
+ }
257
+ }
258
+
259
+ fn is_read ( self ) -> bool {
260
+ match self {
261
+ AccessType :: AtomicLoad | AccessType :: NaRead => true ,
262
+ AccessType :: NaWrite ( _) | AccessType :: AtomicStore | AccessType :: AtomicRmw => false ,
228
263
}
229
264
}
230
265
}
@@ -241,7 +276,7 @@ struct MemoryCellClocks {
241
276
/// The type of operation that the write index represents,
242
277
/// either newly allocated memory, a non-atomic write or
243
278
/// a deallocation of memory.
244
- write_type : WriteType ,
279
+ write_type : NaWriteType ,
245
280
246
281
/// The vector-clock of all non-atomic reads that happened since the last non-atomic write
247
282
/// (i.e., we join together the "singleton" clocks corresponding to each read). It is reset to
@@ -272,7 +307,7 @@ impl MemoryCellClocks {
272
307
MemoryCellClocks {
273
308
read : VClock :: default ( ) ,
274
309
write : ( alloc_index, alloc) ,
275
- write_type : WriteType :: Allocate ,
310
+ write_type : NaWriteType :: Allocate ,
276
311
atomic_ops : None ,
277
312
}
278
313
}
@@ -495,7 +530,7 @@ impl MemoryCellClocks {
495
530
& mut self ,
496
531
thread_clocks : & mut ThreadClockSet ,
497
532
index : VectorIdx ,
498
- write_type : WriteType ,
533
+ write_type : NaWriteType ,
499
534
current_span : Span ,
500
535
) -> Result < ( ) , DataRace > {
501
536
log:: trace!( "Unsynchronized write with vectors: {:#?} :: {:#?}" , self , thread_clocks) ;
@@ -845,48 +880,45 @@ impl VClockAlloc {
845
880
global : & GlobalState ,
846
881
thread_mgr : & ThreadManager < ' _ , ' _ > ,
847
882
mem_clocks : & MemoryCellClocks ,
848
- action : & str ,
849
- is_atomic : bool ,
883
+ access : AccessType ,
850
884
access_size : Size ,
851
885
ptr_dbg : Pointer < AllocId > ,
852
886
) -> InterpResult < ' tcx > {
853
887
let ( current_index, current_clocks) = global. current_thread_state ( thread_mgr) ;
854
- let mut action = Cow :: Borrowed ( action) ;
855
- let mut involves_non_atomic = true ;
888
+ let mut other_size = None ; // if `Some`, this was a size-mismatch race
856
889
let write_clock;
857
- let ( other_action , other_thread, other_clock) =
890
+ let ( other_access , other_thread, other_clock) =
858
891
// First check the atomic-nonatomic cases. If it looks like multiple
859
892
// cases apply, this one should take precedence, else it might look like
860
893
// we are reporting races between two non-atomic reads.
861
- if !is_atomic &&
894
+ if !access . is_atomic ( ) &&
862
895
let Some ( atomic) = mem_clocks. atomic ( ) &&
863
896
let Some ( idx) = Self :: find_gt_index ( & atomic. write_vector , & current_clocks. clock )
864
897
{
865
- ( format ! ( "Atomic Store" ) , idx, & atomic. write_vector )
866
- } else if !is_atomic &&
898
+ ( AccessType :: AtomicStore , idx, & atomic. write_vector )
899
+ } else if !access . is_atomic ( ) &&
867
900
let Some ( atomic) = mem_clocks. atomic ( ) &&
868
901
let Some ( idx) = Self :: find_gt_index ( & atomic. read_vector , & current_clocks. clock )
869
902
{
870
- ( format ! ( "Atomic Load" ) , idx, & atomic. read_vector )
903
+ ( AccessType :: AtomicLoad , idx, & atomic. read_vector )
871
904
// Then check races with non-atomic writes/reads.
872
905
} else if mem_clocks. write . 1 > current_clocks. clock [ mem_clocks. write . 0 ] {
873
906
write_clock = mem_clocks. write ( ) ;
874
- ( mem_clocks. write_type . get_descriptor ( ) . to_owned ( ) , mem_clocks. write . 0 , & write_clock)
907
+ ( AccessType :: NaWrite ( mem_clocks. write_type ) , mem_clocks. write . 0 , & write_clock)
875
908
} else if let Some ( idx) = Self :: find_gt_index ( & mem_clocks. read , & current_clocks. clock ) {
876
- ( format ! ( "Read" ) , idx, & mem_clocks. read )
909
+ ( AccessType :: NaRead , idx, & mem_clocks. read )
877
910
// Finally, mixed-size races.
878
- } else if is_atomic && let Some ( atomic) = mem_clocks. atomic ( ) && atomic. size != access_size {
911
+ } else if access . is_atomic ( ) && let Some ( atomic) = mem_clocks. atomic ( ) && atomic. size != access_size {
879
912
// This is only a race if we are not synchronized with all atomic accesses, so find
880
913
// the one we are not synchronized with.
881
- involves_non_atomic = false ;
882
- action = format ! ( "{}-byte (different-size) {action}" , access_size. bytes( ) ) . into ( ) ;
914
+ other_size = Some ( atomic. size ) ;
883
915
if let Some ( idx) = Self :: find_gt_index ( & atomic. write_vector , & current_clocks. clock )
884
916
{
885
- ( format ! ( "{}-byte Atomic Store" , atomic . size . bytes ( ) ) , idx, & atomic. write_vector )
917
+ ( AccessType :: AtomicStore , idx, & atomic. write_vector )
886
918
} else if let Some ( idx) =
887
919
Self :: find_gt_index ( & atomic. read_vector , & current_clocks. clock )
888
920
{
889
- ( format ! ( "{}-byte Atomic Load" , atomic . size . bytes ( ) ) , idx, & atomic. read_vector )
921
+ ( AccessType :: AtomicLoad , idx, & atomic. read_vector )
890
922
} else {
891
923
unreachable ! (
892
924
"Failed to report data-race for mixed-size access: no race found"
@@ -899,18 +931,39 @@ impl VClockAlloc {
899
931
// Load elaborated thread information about the racing thread actions.
900
932
let current_thread_info = global. print_thread_metadata ( thread_mgr, current_index) ;
901
933
let other_thread_info = global. print_thread_metadata ( thread_mgr, other_thread) ;
934
+ let involves_non_atomic = !access. is_atomic ( ) || !other_access. is_atomic ( ) ;
902
935
903
936
// Throw the data-race detection.
937
+ let extra = if other_size. is_some ( ) {
938
+ assert ! ( !involves_non_atomic) ;
939
+ Some ( "overlapping unsynchronized atomic accesses must use the same access size" )
940
+ } else if access. is_read ( ) && other_access. is_read ( ) {
941
+ assert ! ( involves_non_atomic) ;
942
+ Some (
943
+ "overlapping atomic and non-atomic accesses must be synchronized, even if both are read-only" ,
944
+ )
945
+ } else {
946
+ None
947
+ } ;
904
948
Err ( err_machine_stop ! ( TerminationInfo :: DataRace {
905
949
involves_non_atomic,
950
+ extra,
906
951
ptr: ptr_dbg,
907
952
op1: RacingOp {
908
- action: other_action. to_string( ) ,
953
+ action: if let Some ( other_size) = other_size {
954
+ format!( "{}-byte {}" , other_size. bytes( ) , other_access. description( ) )
955
+ } else {
956
+ other_access. description( ) . to_owned( )
957
+ } ,
909
958
thread_info: other_thread_info,
910
959
span: other_clock. as_slice( ) [ other_thread. index( ) ] . span_data( ) ,
911
960
} ,
912
961
op2: RacingOp {
913
- action: action. to_string( ) ,
962
+ action: if other_size. is_some( ) {
963
+ format!( "{}-byte {}" , access_size. bytes( ) , access. description( ) )
964
+ } else {
965
+ access. description( ) . to_owned( )
966
+ } ,
914
967
thread_info: current_thread_info,
915
968
span: current_clocks. clock. as_slice( ) [ current_index. index( ) ] . span_data( ) ,
916
969
} ,
@@ -945,8 +998,7 @@ impl VClockAlloc {
945
998
global,
946
999
& machine. threads ,
947
1000
mem_clocks,
948
- "Read" ,
949
- /* is_atomic */ false ,
1001
+ AccessType :: NaRead ,
950
1002
access_range. size ,
951
1003
Pointer :: new ( alloc_id, Size :: from_bytes ( mem_clocks_range. start ) ) ,
952
1004
) ;
@@ -963,7 +1015,7 @@ impl VClockAlloc {
963
1015
& mut self ,
964
1016
alloc_id : AllocId ,
965
1017
access_range : AllocRange ,
966
- write_type : WriteType ,
1018
+ write_type : NaWriteType ,
967
1019
machine : & mut MiriMachine < ' _ , ' _ > ,
968
1020
) -> InterpResult < ' tcx > {
969
1021
let current_span = machine. current_span ( ) ;
@@ -985,8 +1037,7 @@ impl VClockAlloc {
985
1037
global,
986
1038
& machine. threads ,
987
1039
mem_clocks,
988
- write_type. get_descriptor ( ) ,
989
- /* is_atomic */ false ,
1040
+ AccessType :: NaWrite ( write_type) ,
990
1041
access_range. size ,
991
1042
Pointer :: new ( alloc_id, Size :: from_bytes ( mem_clocks_range. start ) ) ,
992
1043
) ;
@@ -1008,7 +1059,7 @@ impl VClockAlloc {
1008
1059
range : AllocRange ,
1009
1060
machine : & mut MiriMachine < ' _ , ' _ > ,
1010
1061
) -> InterpResult < ' tcx > {
1011
- self . unique_access ( alloc_id, range, WriteType :: Write , machine)
1062
+ self . unique_access ( alloc_id, range, NaWriteType :: Write , machine)
1012
1063
}
1013
1064
1014
1065
/// Detect data-races for an unsynchronized deallocate operation, will not perform
@@ -1021,7 +1072,7 @@ impl VClockAlloc {
1021
1072
range : AllocRange ,
1022
1073
machine : & mut MiriMachine < ' _ , ' _ > ,
1023
1074
) -> InterpResult < ' tcx > {
1024
- self . unique_access ( alloc_id, range, WriteType :: Deallocate , machine)
1075
+ self . unique_access ( alloc_id, range, NaWriteType :: Deallocate , machine)
1025
1076
}
1026
1077
}
1027
1078
@@ -1134,7 +1185,7 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
1134
1185
this. validate_atomic_op (
1135
1186
place,
1136
1187
atomic,
1137
- "Atomic Load" ,
1188
+ AccessType :: AtomicLoad ,
1138
1189
move |memory, clocks, index, atomic| {
1139
1190
if atomic == AtomicReadOrd :: Relaxed {
1140
1191
memory. load_relaxed ( & mut * clocks, index, place. layout . size )
@@ -1156,7 +1207,7 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
1156
1207
this. validate_atomic_op (
1157
1208
place,
1158
1209
atomic,
1159
- "Atomic Store" ,
1210
+ AccessType :: AtomicStore ,
1160
1211
move |memory, clocks, index, atomic| {
1161
1212
if atomic == AtomicWriteOrd :: Relaxed {
1162
1213
memory. store_relaxed ( clocks, index, place. layout . size )
@@ -1178,26 +1229,31 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
1178
1229
let acquire = matches ! ( atomic, Acquire | AcqRel | SeqCst ) ;
1179
1230
let release = matches ! ( atomic, Release | AcqRel | SeqCst ) ;
1180
1231
let this = self . eval_context_mut ( ) ;
1181
- this. validate_atomic_op ( place, atomic, "Atomic RMW" , move |memory, clocks, index, _| {
1182
- if acquire {
1183
- memory. load_acquire ( clocks, index, place. layout . size ) ?;
1184
- } else {
1185
- memory. load_relaxed ( clocks, index, place. layout . size ) ?;
1186
- }
1187
- if release {
1188
- memory. rmw_release ( clocks, index, place. layout . size )
1189
- } else {
1190
- memory. rmw_relaxed ( clocks, index, place. layout . size )
1191
- }
1192
- } )
1232
+ this. validate_atomic_op (
1233
+ place,
1234
+ atomic,
1235
+ AccessType :: AtomicRmw ,
1236
+ move |memory, clocks, index, _| {
1237
+ if acquire {
1238
+ memory. load_acquire ( clocks, index, place. layout . size ) ?;
1239
+ } else {
1240
+ memory. load_relaxed ( clocks, index, place. layout . size ) ?;
1241
+ }
1242
+ if release {
1243
+ memory. rmw_release ( clocks, index, place. layout . size )
1244
+ } else {
1245
+ memory. rmw_relaxed ( clocks, index, place. layout . size )
1246
+ }
1247
+ } ,
1248
+ )
1193
1249
}
1194
1250
1195
1251
/// Generic atomic operation implementation
1196
1252
fn validate_atomic_op < A : Debug + Copy > (
1197
1253
& self ,
1198
1254
place : & MPlaceTy < ' tcx , Provenance > ,
1199
1255
atomic : A ,
1200
- description : & str ,
1256
+ access : AccessType ,
1201
1257
mut op : impl FnMut (
1202
1258
& mut MemoryCellClocks ,
1203
1259
& mut ThreadClockSet ,
@@ -1206,6 +1262,7 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
1206
1262
) -> Result < ( ) , DataRace > ,
1207
1263
) -> InterpResult < ' tcx > {
1208
1264
let this = self . eval_context_ref ( ) ;
1265
+ assert ! ( access. is_atomic( ) ) ;
1209
1266
if let Some ( data_race) = & this. machine . data_race {
1210
1267
if data_race. race_detecting ( ) {
1211
1268
let size = place. layout . size ;
@@ -1215,7 +1272,7 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
1215
1272
let alloc_meta = this. get_alloc_extra ( alloc_id) ?. data_race . as_ref ( ) . unwrap ( ) ;
1216
1273
log:: trace!(
1217
1274
"Atomic op({}) with ordering {:?} on {:?} (size={})" ,
1218
- description,
1275
+ access . description( ) ,
1219
1276
& atomic,
1220
1277
place. ptr( ) ,
1221
1278
size. bytes( )
@@ -1237,8 +1294,7 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
1237
1294
data_race,
1238
1295
& this. machine . threads ,
1239
1296
mem_clocks,
1240
- description,
1241
- /* is_atomic */ true ,
1297
+ access,
1242
1298
place. layout . size ,
1243
1299
Pointer :: new (
1244
1300
alloc_id,
0 commit comments