Skip to content

Commit a888905

Browse files
committed
miri: treat non-memory local variables properly for data race detection
1 parent 304b7f8 commit a888905

16 files changed

+433
-18
lines changed

compiler/rustc_const_eval/src/interpret/machine.rs

+20-1
Original file line numberDiff line numberDiff line change
@@ -540,10 +540,29 @@ pub trait Machine<'tcx>: Sized {
540540
Ok(ReturnAction::Normal)
541541
}
542542

543+
/// Called immediately after an "immediate" local variable is read
544+
/// (i.e., this is called for reads that do not end up accessing addressable memory).
545+
#[inline(always)]
546+
fn after_local_read(_ecx: &InterpCx<'tcx, Self>, _local: mir::Local) -> InterpResult<'tcx> {
547+
Ok(())
548+
}
549+
550+
/// Called immediately after an "immediate" local variable is assigned a new value
551+
/// (i.e., this is called for writes that do not end up in memory).
552+
/// `storage_live` indicates whether this is the initial write upon `StorageLive`.
553+
#[inline(always)]
554+
fn after_local_write(
555+
_ecx: &mut InterpCx<'tcx, Self>,
556+
_local: mir::Local,
557+
_storage_live: bool,
558+
) -> InterpResult<'tcx> {
559+
Ok(())
560+
}
561+
543562
/// Called immediately after actual memory was allocated for a local
544563
/// but before the local's stack frame is updated to point to that memory.
545564
#[inline(always)]
546-
fn after_local_allocated(
565+
fn after_local_moved_to_memory(
547566
_ecx: &mut InterpCx<'tcx, Self>,
548567
_local: mir::Local,
549568
_mplace: &MPlaceTy<'tcx, Self::Provenance>,

compiler/rustc_const_eval/src/interpret/memory.rs

+4
Original file line numberDiff line numberDiff line change
@@ -1030,6 +1030,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
10301030
);
10311031
res
10321032
}
1033+
1034+
pub(super) fn validation_in_progress(&self) -> bool {
1035+
self.memory.validation_in_progress
1036+
}
10331037
}
10341038

10351039
#[doc(hidden)]

compiler/rustc_const_eval/src/interpret/operand.rs

+1
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
719719
if matches!(op, Operand::Immediate(_)) {
720720
assert!(!layout.is_unsized());
721721
}
722+
M::after_local_read(self, local)?;
722723
Ok(OpTy { op, layout })
723724
}
724725

compiler/rustc_const_eval/src/interpret/place.rs

+22-9
Original file line numberDiff line numberDiff line change
@@ -504,15 +504,13 @@ where
504504
&self,
505505
local: mir::Local,
506506
) -> InterpResult<'tcx, PlaceTy<'tcx, M::Provenance>> {
507-
// Other parts of the system rely on `Place::Local` never being unsized.
508-
// So we eagerly check here if this local has an MPlace, and if yes we use it.
509507
let frame = self.frame();
510508
let layout = self.layout_of_local(frame, local, None)?;
511509
let place = if layout.is_sized() {
512510
// We can just always use the `Local` for sized values.
513511
Place::Local { local, offset: None, locals_addr: frame.locals_addr() }
514512
} else {
515-
// Unsized `Local` isn't okay (we cannot store the metadata).
513+
// Other parts of the system rely on `Place::Local` never being unsized.
516514
match frame.locals[local].access()? {
517515
Operand::Immediate(_) => bug!(),
518516
Operand::Indirect(mplace) => Place::Ptr(*mplace),
@@ -565,7 +563,10 @@ where
565563
place: &PlaceTy<'tcx, M::Provenance>,
566564
) -> InterpResult<
567565
'tcx,
568-
Either<MPlaceTy<'tcx, M::Provenance>, (&mut Immediate<M::Provenance>, TyAndLayout<'tcx>)>,
566+
Either<
567+
MPlaceTy<'tcx, M::Provenance>,
568+
(&mut Immediate<M::Provenance>, TyAndLayout<'tcx>, mir::Local),
569+
>,
569570
> {
570571
Ok(match place.to_place().as_mplace_or_local() {
571572
Left(mplace) => Left(mplace),
@@ -584,7 +585,7 @@ where
584585
}
585586
Operand::Immediate(local_val) => {
586587
// The local still has the optimized representation.
587-
Right((local_val, layout))
588+
Right((local_val, layout, local))
588589
}
589590
}
590591
}
@@ -646,9 +647,13 @@ where
646647
assert!(dest.layout().is_sized(), "Cannot write unsized immediate data");
647648

648649
match self.as_mplace_or_mutable_local(&dest.to_place())? {
649-
Right((local_val, local_layout)) => {
650+
Right((local_val, local_layout, local)) => {
650651
// Local can be updated in-place.
651652
*local_val = src;
653+
// Call the machine hook (the data race detector needs to know about this write).
654+
if !self.validation_in_progress() {
655+
M::after_local_write(self, local, /*storage_live*/ false)?;
656+
}
652657
// Double-check that the value we are storing and the local fit to each other.
653658
if cfg!(debug_assertions) {
654659
src.assert_matches_abi(local_layout.abi, self);
@@ -717,8 +722,12 @@ where
717722
dest: &impl Writeable<'tcx, M::Provenance>,
718723
) -> InterpResult<'tcx> {
719724
match self.as_mplace_or_mutable_local(&dest.to_place())? {
720-
Right((local_val, _local_layout)) => {
725+
Right((local_val, _local_layout, local)) => {
721726
*local_val = Immediate::Uninit;
727+
// Call the machine hook (the data race detector needs to know about this write).
728+
if !self.validation_in_progress() {
729+
M::after_local_write(self, local, /*storage_live*/ false)?;
730+
}
722731
}
723732
Left(mplace) => {
724733
let Some(mut alloc) = self.get_place_alloc_mut(&mplace)? else {
@@ -737,8 +746,12 @@ where
737746
dest: &impl Writeable<'tcx, M::Provenance>,
738747
) -> InterpResult<'tcx> {
739748
match self.as_mplace_or_mutable_local(&dest.to_place())? {
740-
Right((local_val, _local_layout)) => {
749+
Right((local_val, _local_layout, local)) => {
741750
local_val.clear_provenance()?;
751+
// Call the machine hook (the data race detector needs to know about this write).
752+
if !self.validation_in_progress() {
753+
M::after_local_write(self, local, /*storage_live*/ false)?;
754+
}
742755
}
743756
Left(mplace) => {
744757
let Some(mut alloc) = self.get_place_alloc_mut(&mplace)? else {
@@ -944,7 +957,7 @@ where
944957
mplace.mplace,
945958
)?;
946959
}
947-
M::after_local_allocated(self, local, &mplace)?;
960+
M::after_local_moved_to_memory(self, local, &mplace)?;
948961
// Now we can call `access_mut` again, asserting it goes well, and actually
949962
// overwrite things. This points to the entire allocation, not just the part
950963
// the place refers to, i.e. we do this before we apply `offset`.

compiler/rustc_const_eval/src/interpret/stack.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -534,8 +534,11 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
534534
let dest_place = self.allocate_dyn(layout, MemoryKind::Stack, meta)?;
535535
Operand::Indirect(*dest_place.mplace())
536536
} else {
537-
assert!(!meta.has_meta()); // we're dropping the metadata
538537
// Just make this an efficient immediate.
538+
assert!(!meta.has_meta()); // we're dropping the metadata
539+
// Make sure the machine knows this "write" is happening. (This is important so that
540+
// races involving local variable allocation can be detected by Miri.)
541+
M::after_local_write(self, local, /*storage_live*/ true)?;
539542
// Note that not calling `layout_of` here does have one real consequence:
540543
// if the type is too big, we'll only notice this when the local is actually initialized,
541544
// which is a bit too late -- we should ideally notice this already here, when the memory

src/tools/miri/src/concurrency/data_race.rs

+98
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ use std::{
4747
};
4848

4949
use rustc_ast::Mutability;
50+
use rustc_data_structures::fx::FxHashMap;
5051
use rustc_data_structures::fx::FxHashSet;
5152
use rustc_index::{Idx, IndexVec};
5253
use rustc_middle::{mir, ty::Ty};
@@ -1121,6 +1122,103 @@ impl VClockAlloc {
11211122
}
11221123
}
11231124

1125+
/// Vector clock state for a stack frame (tracking the local variables
1126+
/// that do not have an allocation yet).
1127+
#[derive(Debug, Default)]
1128+
pub struct FrameState {
1129+
local_clocks: RefCell<FxHashMap<mir::Local, LocalClocks>>,
1130+
}
1131+
1132+
/// Stripped-down version of [`MemoryCellClocks`] for the clocks we need to keep track
1133+
/// of in a local that does not yet have addressable memory -- and hence can only
1134+
/// be accessed from the thread its stack frame belongs to, and cannot be access atomically.
1135+
#[derive(Debug)]
1136+
struct LocalClocks {
1137+
write: VTimestamp,
1138+
write_type: NaWriteType,
1139+
read: VTimestamp,
1140+
}
1141+
1142+
impl Default for LocalClocks {
1143+
fn default() -> Self {
1144+
Self { write: VTimestamp::ZERO, write_type: NaWriteType::Allocate, read: VTimestamp::ZERO }
1145+
}
1146+
}
1147+
1148+
impl FrameState {
1149+
pub fn local_write(&self, local: mir::Local, storage_live: bool, machine: &MiriMachine<'_>) {
1150+
let current_span = machine.current_span();
1151+
let global = machine.data_race.as_ref().unwrap();
1152+
if global.race_detecting() {
1153+
let (index, mut thread_clocks) = global.active_thread_state_mut(&machine.threads);
1154+
// This should do the same things as `MemoryCellClocks::write_race_detect`.
1155+
if !current_span.is_dummy() {
1156+
thread_clocks.clock.index_mut(index).span = current_span;
1157+
}
1158+
let mut clocks = self.local_clocks.borrow_mut();
1159+
if storage_live {
1160+
let new_clocks = LocalClocks {
1161+
write: thread_clocks.clock[index],
1162+
write_type: NaWriteType::Allocate,
1163+
read: VTimestamp::ZERO,
1164+
};
1165+
// There might already be an entry in the map for this, if the local was previously
1166+
// live already.
1167+
clocks.insert(local, new_clocks);
1168+
} else {
1169+
// This can fail to exist if `race_detecting` was false when the allocation
1170+
// occurred, in which case we can backdate this to the beginning of time.
1171+
let clocks = clocks.entry(local).or_insert_with(Default::default);
1172+
clocks.write = thread_clocks.clock[index];
1173+
clocks.write_type = NaWriteType::Write;
1174+
}
1175+
}
1176+
}
1177+
1178+
pub fn local_read(&self, local: mir::Local, machine: &MiriMachine<'_>) {
1179+
let current_span = machine.current_span();
1180+
let global = machine.data_race.as_ref().unwrap();
1181+
if global.race_detecting() {
1182+
let (index, mut thread_clocks) = global.active_thread_state_mut(&machine.threads);
1183+
// This should do the same things as `MemoryCellClocks::read_race_detect`.
1184+
if !current_span.is_dummy() {
1185+
thread_clocks.clock.index_mut(index).span = current_span;
1186+
}
1187+
thread_clocks.clock.index_mut(index).set_read_type(NaReadType::Read);
1188+
// This can fail to exist if `race_detecting` was false when the allocation
1189+
// occurred, in which case we can backdate this to the beginning of time.
1190+
let mut clocks = self.local_clocks.borrow_mut();
1191+
let clocks = clocks.entry(local).or_insert_with(Default::default);
1192+
clocks.read = thread_clocks.clock[index];
1193+
}
1194+
}
1195+
1196+
pub fn local_moved_to_memory(
1197+
&self,
1198+
local: mir::Local,
1199+
alloc: &mut VClockAlloc,
1200+
machine: &MiriMachine<'_>,
1201+
) {
1202+
let global = machine.data_race.as_ref().unwrap();
1203+
if global.race_detecting() {
1204+
let (index, _thread_clocks) = global.active_thread_state_mut(&machine.threads);
1205+
// Get the time the last write actually happened. This can fail to exist if
1206+
// `race_detecting` was false when the write occurred, in that case we can backdate this
1207+
// to the beginning of time.
1208+
let local_clocks = self.local_clocks.borrow_mut().remove(&local).unwrap_or_default();
1209+
for (_mem_clocks_range, mem_clocks) in alloc.alloc_ranges.get_mut().iter_mut_all() {
1210+
// The initialization write for this already happened, just at the wrong timestamp.
1211+
// Check that the thread index matches what we expect.
1212+
assert_eq!(mem_clocks.write.0, index);
1213+
// Convert the local's clocks into memory clocks.
1214+
mem_clocks.write = (index, local_clocks.write);
1215+
mem_clocks.write_type = local_clocks.write_type;
1216+
mem_clocks.read = VClock::new_with_index(index, local_clocks.read);
1217+
}
1218+
}
1219+
}
1220+
}
1221+
11241222
impl<'tcx> EvalContextPrivExt<'tcx> for MiriInterpCx<'tcx> {}
11251223
trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> {
11261224
/// Temporarily allow data-races to occur. This should only be used in

src/tools/miri/src/concurrency/thread.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,9 @@ impl<'tcx> ThreadManager<'tcx> {
530530
}
531531

532532
/// Mutably borrow the stack of the active thread.
533-
fn active_thread_stack_mut(&mut self) -> &mut Vec<Frame<'tcx, Provenance, FrameExtra<'tcx>>> {
533+
pub fn active_thread_stack_mut(
534+
&mut self,
535+
) -> &mut Vec<Frame<'tcx, Provenance, FrameExtra<'tcx>>> {
534536
&mut self.threads[self.active_thread].stack
535537
}
536538
pub fn all_stacks(

src/tools/miri/src/concurrency/vector_clock.rs

+6
Original file line numberDiff line numberDiff line change
@@ -130,13 +130,19 @@ impl Ord for VTimestamp {
130130
/// also this means that there is only one unique valid length
131131
/// for each set of vector clock values and hence the PartialEq
132132
/// and Eq derivations are correct.
133+
///
134+
/// This means we cannot represent a clock where the last entry is a timestamp-0 read that occurs
135+
/// because of a retag. That's fine, all it does is risk wrong diagnostics in a extreme corner case.
133136
#[derive(PartialEq, Eq, Default, Debug)]
134137
pub struct VClock(SmallVec<[VTimestamp; SMALL_VECTOR]>);
135138

136139
impl VClock {
137140
/// Create a new vector-clock containing all zeros except
138141
/// for a value at the given index
139142
pub(super) fn new_with_index(index: VectorIdx, timestamp: VTimestamp) -> VClock {
143+
if timestamp.time() == 0 {
144+
return VClock::default();
145+
}
140146
let len = index.index() + 1;
141147
let mut vec = smallvec::smallvec![VTimestamp::ZERO; len];
142148
vec[index.index()] = timestamp;

src/tools/miri/src/machine.rs

+50-5
Original file line numberDiff line numberDiff line change
@@ -81,24 +81,42 @@ pub struct FrameExtra<'tcx> {
8181
/// an additional bit of "salt" into the cache key. This salt is fixed per-frame
8282
/// so that within a call, a const will have a stable address.
8383
salt: usize,
84+
85+
/// Data race detector per-frame data.
86+
pub data_race: Option<data_race::FrameState>,
8487
}
8588

8689
impl<'tcx> std::fmt::Debug for FrameExtra<'tcx> {
8790
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
8891
// Omitting `timing`, it does not support `Debug`.
89-
let FrameExtra { borrow_tracker, catch_unwind, timing: _, is_user_relevant: _, salt: _ } =
90-
self;
92+
let FrameExtra {
93+
borrow_tracker,
94+
catch_unwind,
95+
timing: _,
96+
is_user_relevant,
97+
salt,
98+
data_race,
99+
} = self;
91100
f.debug_struct("FrameData")
92101
.field("borrow_tracker", borrow_tracker)
93102
.field("catch_unwind", catch_unwind)
103+
.field("is_user_relevant", is_user_relevant)
104+
.field("salt", salt)
105+
.field("data_race", data_race)
94106
.finish()
95107
}
96108
}
97109

98110
impl VisitProvenance for FrameExtra<'_> {
99111
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
100-
let FrameExtra { catch_unwind, borrow_tracker, timing: _, is_user_relevant: _, salt: _ } =
101-
self;
112+
let FrameExtra {
113+
catch_unwind,
114+
borrow_tracker,
115+
timing: _,
116+
is_user_relevant: _,
117+
salt: _,
118+
data_race: _,
119+
} = self;
102120

103121
catch_unwind.visit_provenance(visit);
104122
borrow_tracker.visit_provenance(visit);
@@ -1446,6 +1464,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
14461464
timing,
14471465
is_user_relevant: ecx.machine.is_user_relevant(&frame),
14481466
salt: ecx.machine.rng.borrow_mut().gen::<usize>() % ADDRS_PER_ANON_GLOBAL,
1467+
data_race: ecx.machine.data_race.as_ref().map(|_| data_race::FrameState::default()),
14491468
};
14501469

14511470
Ok(frame.with_extra(extra))
@@ -1551,17 +1570,43 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
15511570
res
15521571
}
15531572

1554-
fn after_local_allocated(
1573+
fn after_local_read(ecx: &InterpCx<'tcx, Self>, local: mir::Local) -> InterpResult<'tcx> {
1574+
if let Some(data_race) = &ecx.frame().extra.data_race {
1575+
data_race.local_read(local, &ecx.machine);
1576+
}
1577+
Ok(())
1578+
}
1579+
1580+
fn after_local_write(
1581+
ecx: &mut InterpCx<'tcx, Self>,
1582+
local: mir::Local,
1583+
storage_live: bool,
1584+
) -> InterpResult<'tcx> {
1585+
if let Some(data_race) = &ecx.frame().extra.data_race {
1586+
data_race.local_write(local, storage_live, &ecx.machine);
1587+
}
1588+
Ok(())
1589+
}
1590+
1591+
fn after_local_moved_to_memory(
15551592
ecx: &mut InterpCx<'tcx, Self>,
15561593
local: mir::Local,
15571594
mplace: &MPlaceTy<'tcx>,
15581595
) -> InterpResult<'tcx> {
15591596
let Some(Provenance::Concrete { alloc_id, .. }) = mplace.ptr().provenance else {
15601597
panic!("after_local_allocated should only be called on fresh allocations");
15611598
};
1599+
// Record the span where this was allocated: the declaration of the local.
15621600
let local_decl = &ecx.frame().body().local_decls[local];
15631601
let span = local_decl.source_info.span;
15641602
ecx.machine.allocation_spans.borrow_mut().insert(alloc_id, (span, None));
1603+
// The data race system has to fix the clocks used for this write.
1604+
let (alloc_info, machine) = ecx.get_alloc_extra_mut(alloc_id)?;
1605+
if let Some(data_race) =
1606+
&machine.threads.active_thread_stack().last().unwrap().extra.data_race
1607+
{
1608+
data_race.local_moved_to_memory(local, alloc_info.data_race.as_mut().unwrap(), machine);
1609+
}
15651610
Ok(())
15661611
}
15671612

0 commit comments

Comments
 (0)