Skip to content

Commit 5fd979f

Browse files
committed
detect mixed-size atomic accesses
1 parent c05bb69 commit 5fd979f

File tree

6 files changed

+196
-72
lines changed

6 files changed

+196
-72
lines changed

src/concurrency/data_race.rs

+106-32
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
//! on the data-race detection code.
4242
4343
use std::{
44+
borrow::Cow,
4445
cell::{Cell, Ref, RefCell, RefMut},
4546
fmt::Debug,
4647
mem,
@@ -167,7 +168,7 @@ pub struct DataRace;
167168
/// explicitly to reduce memory usage for the
168169
/// common case where no atomic operations
169170
/// exists on the memory cell.
170-
#[derive(Clone, PartialEq, Eq, Default, Debug)]
171+
#[derive(Clone, PartialEq, Eq, Debug)]
171172
struct AtomicMemoryCellClocks {
172173
/// The clock-vector of the timestamp of the last atomic
173174
/// read operation performed by each thread.
@@ -186,6 +187,11 @@ struct AtomicMemoryCellClocks {
186187
/// happen-before a thread if an acquire-load is
187188
/// performed on the data.
188189
sync_vector: VClock,
190+
191+
/// The size of accesses to this atomic location.
192+
/// We use this to detect non-synchronized mixed-size accesses. Since all accesses must be
193+
/// aligned to their size, this is sufficient to detect imperfectly overlapping accesses.
194+
size: Size,
189195
}
190196

191197
/// Type of write operation: allocating memory
@@ -241,6 +247,17 @@ struct MemoryCellClocks {
241247
atomic_ops: Option<Box<AtomicMemoryCellClocks>>,
242248
}
243249

250+
impl AtomicMemoryCellClocks {
251+
fn new(size: Size) -> Self {
252+
AtomicMemoryCellClocks {
253+
read_vector: Default::default(),
254+
write_vector: Default::default(),
255+
sync_vector: Default::default(),
256+
size,
257+
}
258+
}
259+
}
260+
244261
impl MemoryCellClocks {
245262
/// Create a new set of clocks representing memory allocated
246263
/// at a given vector timestamp and index.
@@ -271,11 +288,39 @@ impl MemoryCellClocks {
271288
self.atomic_ops.as_deref()
272289
}
273290

274-
/// Load or create the internal atomic memory metadata
275-
/// if it does not exist.
291+
/// Load the internal atomic memory cells if they exist.
276292
#[inline]
277-
fn atomic_mut(&mut self) -> &mut AtomicMemoryCellClocks {
278-
self.atomic_ops.get_or_insert_with(Default::default)
293+
fn atomic_mut_unwrap(&mut self) -> &mut AtomicMemoryCellClocks {
294+
self.atomic_ops.as_deref_mut().unwrap()
295+
}
296+
297+
/// Load or create the internal atomic memory metadata if it does not exist. Also ensures we do
298+
/// not do mixed-size atomic accesses, and updates the recorded atomic access size.
299+
fn atomic_access(
300+
&mut self,
301+
thread_clocks: &ThreadClockSet,
302+
size: Size,
303+
) -> Result<&mut AtomicMemoryCellClocks, DataRace> {
304+
match self.atomic_ops {
305+
Some(ref mut atomic) => {
306+
// We are good if the size is the same or all atomic accesses are before our current time.
307+
if atomic.size == size {
308+
Ok(atomic)
309+
} else if atomic.read_vector <= thread_clocks.clock
310+
&& atomic.write_vector <= thread_clocks.clock
311+
{
312+
// This is now the new size that must be used for accesses here.
313+
atomic.size = size;
314+
Ok(atomic)
315+
} else {
316+
Err(DataRace)
317+
}
318+
}
319+
None => {
320+
self.atomic_ops = Some(Box::new(AtomicMemoryCellClocks::new(size)));
321+
Ok(self.atomic_ops.as_mut().unwrap())
322+
}
323+
}
279324
}
280325

281326
/// Update memory cell data-race tracking for atomic
@@ -285,8 +330,9 @@ impl MemoryCellClocks {
285330
&mut self,
286331
thread_clocks: &mut ThreadClockSet,
287332
index: VectorIdx,
333+
access_size: Size,
288334
) -> Result<(), DataRace> {
289-
self.atomic_read_detect(thread_clocks, index)?;
335+
self.atomic_read_detect(thread_clocks, index, access_size)?;
290336
if let Some(atomic) = self.atomic() {
291337
thread_clocks.clock.join(&atomic.sync_vector);
292338
}
@@ -309,8 +355,9 @@ impl MemoryCellClocks {
309355
&mut self,
310356
thread_clocks: &mut ThreadClockSet,
311357
index: VectorIdx,
358+
access_size: Size,
312359
) -> Result<(), DataRace> {
313-
self.atomic_read_detect(thread_clocks, index)?;
360+
self.atomic_read_detect(thread_clocks, index, access_size)?;
314361
if let Some(atomic) = self.atomic() {
315362
thread_clocks.fence_acquire.join(&atomic.sync_vector);
316363
}
@@ -323,9 +370,10 @@ impl MemoryCellClocks {
323370
&mut self,
324371
thread_clocks: &ThreadClockSet,
325372
index: VectorIdx,
373+
access_size: Size,
326374
) -> Result<(), DataRace> {
327-
self.atomic_write_detect(thread_clocks, index)?;
328-
let atomic = self.atomic_mut();
375+
self.atomic_write_detect(thread_clocks, index, access_size)?;
376+
let atomic = self.atomic_mut_unwrap(); // initialized by `atomic_write_detect`
329377
atomic.sync_vector.clone_from(&thread_clocks.clock);
330378
Ok(())
331379
}
@@ -336,14 +384,15 @@ impl MemoryCellClocks {
336384
&mut self,
337385
thread_clocks: &ThreadClockSet,
338386
index: VectorIdx,
387+
access_size: Size,
339388
) -> Result<(), DataRace> {
340-
self.atomic_write_detect(thread_clocks, index)?;
389+
self.atomic_write_detect(thread_clocks, index, access_size)?;
341390

342391
// The handling of release sequences was changed in C++20 and so
343392
// the code here is different to the paper since now all relaxed
344393
// stores block release sequences. The exception for same-thread
345394
// relaxed stores has been removed.
346-
let atomic = self.atomic_mut();
395+
let atomic = self.atomic_mut_unwrap();
347396
atomic.sync_vector.clone_from(&thread_clocks.fence_release);
348397
Ok(())
349398
}
@@ -354,9 +403,10 @@ impl MemoryCellClocks {
354403
&mut self,
355404
thread_clocks: &ThreadClockSet,
356405
index: VectorIdx,
406+
access_size: Size,
357407
) -> Result<(), DataRace> {
358-
self.atomic_write_detect(thread_clocks, index)?;
359-
let atomic = self.atomic_mut();
408+
self.atomic_write_detect(thread_clocks, index, access_size)?;
409+
let atomic = self.atomic_mut_unwrap();
360410
atomic.sync_vector.join(&thread_clocks.clock);
361411
Ok(())
362412
}
@@ -367,9 +417,10 @@ impl MemoryCellClocks {
367417
&mut self,
368418
thread_clocks: &ThreadClockSet,
369419
index: VectorIdx,
420+
access_size: Size,
370421
) -> Result<(), DataRace> {
371-
self.atomic_write_detect(thread_clocks, index)?;
372-
let atomic = self.atomic_mut();
422+
self.atomic_write_detect(thread_clocks, index, access_size)?;
423+
let atomic = self.atomic_mut_unwrap();
373424
atomic.sync_vector.join(&thread_clocks.fence_release);
374425
Ok(())
375426
}
@@ -380,9 +431,10 @@ impl MemoryCellClocks {
380431
&mut self,
381432
thread_clocks: &ThreadClockSet,
382433
index: VectorIdx,
434+
access_size: Size,
383435
) -> Result<(), DataRace> {
384436
log::trace!("Atomic read with vectors: {:#?} :: {:#?}", self, thread_clocks);
385-
let atomic = self.atomic_mut();
437+
let atomic = self.atomic_access(thread_clocks, access_size)?;
386438
atomic.read_vector.set_at_index(&thread_clocks.clock, index);
387439
// Make sure the last non-atomic write and all non-atomic reads were before this access.
388440
if self.write_was_before(&thread_clocks.clock) && self.read <= thread_clocks.clock {
@@ -398,9 +450,10 @@ impl MemoryCellClocks {
398450
&mut self,
399451
thread_clocks: &ThreadClockSet,
400452
index: VectorIdx,
453+
access_size: Size,
401454
) -> Result<(), DataRace> {
402455
log::trace!("Atomic write with vectors: {:#?} :: {:#?}", self, thread_clocks);
403-
let atomic = self.atomic_mut();
456+
let atomic = self.atomic_access(thread_clocks, access_size)?;
404457
atomic.write_vector.set_at_index(&thread_clocks.clock, index);
405458
// Make sure the last non-atomic write and all non-atomic reads were before this access.
406459
if self.write_was_before(&thread_clocks.clock) && self.read <= thread_clocks.clock {
@@ -802,26 +855,44 @@ impl VClockAlloc {
802855
mem_clocks: &MemoryCellClocks,
803856
action: &str,
804857
is_atomic: bool,
858+
access_size: Size,
805859
ptr_dbg: Pointer<AllocId>,
806860
) -> InterpResult<'tcx> {
807861
let (current_index, current_clocks) = global.current_thread_state(thread_mgr);
862+
let mut action = Cow::Borrowed(action);
808863
let write_clock;
809864
#[rustfmt::skip]
810865
let (other_action, other_thread, other_clock) =
811866
if mem_clocks.write.1 > current_clocks.clock[mem_clocks.write.0] {
812867
write_clock = mem_clocks.write();
813-
(mem_clocks.write_type.get_descriptor(), mem_clocks.write.0, &write_clock)
868+
(mem_clocks.write_type.get_descriptor().to_owned(), mem_clocks.write.0, &write_clock)
814869
} else if let Some(idx) = Self::find_gt_index(&mem_clocks.read, &current_clocks.clock) {
815-
("Read", idx, &mem_clocks.read)
870+
(format!("Read"), idx, &mem_clocks.read)
871+
} else if is_atomic && let Some(atomic) = mem_clocks.atomic() && atomic.size != access_size {
872+
// This is only a race if we are not synchronized with all atomic accesses, so find
873+
// the one we are not synchronized with.
874+
action = format!("{}-byte (different-size) {action}", access_size.bytes()).into();
875+
if let Some(idx) = Self::find_gt_index(&atomic.write_vector, &current_clocks.clock)
876+
{
877+
(format!("{}-byte Atomic Store", atomic.size.bytes()), idx, &atomic.write_vector)
878+
} else if let Some(idx) =
879+
Self::find_gt_index(&atomic.read_vector, &current_clocks.clock)
880+
{
881+
(format!("{}-byte Atomic Load", atomic.size.bytes()), idx, &atomic.read_vector)
882+
} else {
883+
unreachable!(
884+
"Failed to report data-race for mixed-size access: no race found"
885+
)
886+
}
816887
} else if !is_atomic {
817888
if let Some(atomic) = mem_clocks.atomic() {
818889
if let Some(idx) = Self::find_gt_index(&atomic.write_vector, &current_clocks.clock)
819890
{
820-
("Atomic Store", idx, &atomic.write_vector)
891+
(format!("Atomic Store"), idx, &atomic.write_vector)
821892
} else if let Some(idx) =
822893
Self::find_gt_index(&atomic.read_vector, &current_clocks.clock)
823894
{
824-
("Atomic Load", idx, &atomic.read_vector)
895+
(format!("Atomic Load"), idx, &atomic.read_vector)
825896
} else {
826897
unreachable!(
827898
"Failed to report data-race for non-atomic operation: no race found"
@@ -905,7 +976,8 @@ impl VClockAlloc {
905976
&machine.threads,
906977
mem_clocks,
907978
"Read",
908-
false,
979+
/* is_atomic */ false,
980+
access_range.size,
909981
Pointer::new(alloc_id, Size::from_bytes(mem_clocks_range.start)),
910982
);
911983
}
@@ -944,7 +1016,8 @@ impl VClockAlloc {
9441016
&machine.threads,
9451017
mem_clocks,
9461018
write_type.get_descriptor(),
947-
false,
1019+
/* is_atomic */ false,
1020+
access_range.size,
9481021
Pointer::new(alloc_id, Size::from_bytes(mem_clocks_range.start)),
9491022
);
9501023
}
@@ -1072,9 +1145,9 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
10721145
"Atomic Load",
10731146
move |memory, clocks, index, atomic| {
10741147
if atomic == AtomicReadOrd::Relaxed {
1075-
memory.load_relaxed(&mut *clocks, index)
1148+
memory.load_relaxed(&mut *clocks, index, place.layout.size)
10761149
} else {
1077-
memory.load_acquire(&mut *clocks, index)
1150+
memory.load_acquire(&mut *clocks, index, place.layout.size)
10781151
}
10791152
},
10801153
)
@@ -1095,9 +1168,9 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
10951168
"Atomic Store",
10961169
move |memory, clocks, index, atomic| {
10971170
if atomic == AtomicWriteOrd::Relaxed {
1098-
memory.store_relaxed(clocks, index)
1171+
memory.store_relaxed(clocks, index, place.layout.size)
10991172
} else {
1100-
memory.store_release(clocks, index)
1173+
memory.store_release(clocks, index, place.layout.size)
11011174
}
11021175
},
11031176
)
@@ -1117,14 +1190,14 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
11171190
this.validate_overlapping_atomic(place)?;
11181191
this.validate_atomic_op(place, atomic, "Atomic RMW", move |memory, clocks, index, _| {
11191192
if acquire {
1120-
memory.load_acquire(clocks, index)?;
1193+
memory.load_acquire(clocks, index, place.layout.size)?;
11211194
} else {
1122-
memory.load_relaxed(clocks, index)?;
1195+
memory.load_relaxed(clocks, index, place.layout.size)?;
11231196
}
11241197
if release {
1125-
memory.rmw_release(clocks, index)
1198+
memory.rmw_release(clocks, index, place.layout.size)
11261199
} else {
1127-
memory.rmw_relaxed(clocks, index)
1200+
memory.rmw_relaxed(clocks, index, place.layout.size)
11281201
}
11291202
})
11301203
}
@@ -1175,7 +1248,8 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
11751248
&this.machine.threads,
11761249
mem_clocks,
11771250
description,
1178-
true,
1251+
/* is_atomic */ true,
1252+
place.layout.size,
11791253
Pointer::new(
11801254
alloc_id,
11811255
Size::from_bytes(mem_clocks_range.start),
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//@compile-flags: -Zmiri-preemption-rate=0.0 -Zmiri-disable-weak-memory-emulation
2+
use std::sync::atomic::{AtomicU16, AtomicU8, Ordering};
3+
use std::thread;
4+
5+
fn convert(a: &AtomicU16) -> &[AtomicU8; 2] {
6+
unsafe { std::mem::transmute(a) }
7+
}
8+
9+
// We can't allow mixed-size accesses; they are not possible in C++ and even
10+
// Intel says you shouldn't do it.
11+
fn main() {
12+
let a = AtomicU16::new(0);
13+
let a16 = &a;
14+
let a8 = convert(a16);
15+
16+
thread::scope(|s| {
17+
s.spawn(|| {
18+
a16.load(Ordering::SeqCst);
19+
});
20+
s.spawn(|| {
21+
a8[0].load(Ordering::SeqCst);
22+
//~^ ERROR: Data race detected between (1) 2-byte Atomic Load on thread `<unnamed>` and (2) 1-byte (different-size) Atomic Load on thread `<unnamed>`
23+
});
24+
});
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: Undefined Behavior: Data race detected between (1) 2-byte Atomic Load on thread `<unnamed>` and (2) 1-byte (different-size) Atomic Load on thread `<unnamed>` at ALLOC. (2) just happened here
2+
--> $DIR/mixed_size_read.rs:LL:CC
3+
|
4+
LL | a8[0].load(Ordering::SeqCst);
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Data race detected between (1) 2-byte Atomic Load on thread `<unnamed>` and (2) 1-byte (different-size) Atomic Load on thread `<unnamed>` at ALLOC. (2) just happened here
6+
|
7+
help: and (1) occurred earlier here
8+
--> $DIR/mixed_size_read.rs:LL:CC
9+
|
10+
LL | a16.load(Ordering::SeqCst);
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
12+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
13+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
14+
= note: BACKTRACE (of the first span):
15+
= note: inside closure at $DIR/mixed_size_read.rs:LL:CC
16+
17+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
18+
19+
error: aborting due to previous error
20+
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//@compile-flags: -Zmiri-preemption-rate=0.0 -Zmiri-disable-weak-memory-emulation
2+
use std::sync::atomic::{AtomicU16, AtomicU8, Ordering};
3+
use std::thread;
4+
5+
fn convert(a: &AtomicU16) -> &[AtomicU8; 2] {
6+
unsafe { std::mem::transmute(a) }
7+
}
8+
9+
// We can't allow mixed-size accesses; they are not possible in C++ and even
10+
// Intel says you shouldn't do it.
11+
fn main() {
12+
let a = AtomicU16::new(0);
13+
let a16 = &a;
14+
let a8 = convert(a16);
15+
16+
thread::scope(|s| {
17+
s.spawn(|| {
18+
a16.store(1, Ordering::SeqCst);
19+
});
20+
s.spawn(|| {
21+
a8[0].store(1, Ordering::SeqCst);
22+
//~^ ERROR: Data race detected between (1) 2-byte Atomic Store on thread `<unnamed>` and (2) 1-byte (different-size) Atomic Store on thread `<unnamed>`
23+
});
24+
});
25+
}

0 commit comments

Comments
 (0)