Skip to main content

miri/concurrency/
data_race.rs

1//! Implementation of a data-race detector using Lamport Timestamps / Vector clocks
2//! based on the Dynamic Race Detection for C++:
3//! <https://www.doc.ic.ac.uk/~afd/homepages/papers/pdfs/2017/POPL.pdf>
4//! which does not report false-positives when fences are used, and gives better
5//! accuracy in presence of read-modify-write operations.
6//!
7//! The implementation contains modifications to correctly model the changes to the memory model in C++20
8//! regarding the weakening of release sequences: <http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0982r1.html>.
9//! Relaxed stores now unconditionally block all currently active release sequences and so per-thread tracking of release
10//! sequences is not needed.
11//!
12//! The implementation also models races with memory allocation and deallocation via treating allocation and
13//! deallocation as a type of write internally for detecting data-races.
14//!
15//! Weak memory orders are explored but not all weak behaviours are exhibited, so it can still miss data-races
16//! but should not report false-positives
17//!
18//! Data-race definition from(<https://en.cppreference.com/w/cpp/language/memory_model#Threads_and_data_races>):
19//! a data race occurs between two memory accesses if they are on different threads, at least one operation
20//! is non-atomic, at least one operation is a write and neither access happens-before the other. Read the link
21//! for full definition.
22//!
23//! This re-uses vector indexes for threads that are known to be unable to report data-races, this is valid
24//! because it only re-uses vector indexes once all currently-active (not-terminated) threads have an internal
25//! vector clock that happens-after the join operation of the candidate thread. Threads that have not been joined
26//! on are not considered. Since the thread's vector clock will only increase and a data-race implies that
27//! there is some index x where `clock[x] > thread_clock`, when this is true `clock[candidate-idx] > thread_clock`
28//! can never hold and hence a data-race can never be reported in that vector index again.
29//! This means that the thread-index can be safely re-used, starting on the next timestamp for the newly created
30//! thread.
31//!
32//! The timestamps used in the data-race detector assign each sequence of non-atomic operations
33//! followed by a single atomic or concurrent operation a single timestamp.
34//! Write, Read, Write, ThreadJoin will be represented by a single timestamp value on a thread.
35//! This is because extra increment operations between the operations in the sequence are not
36//! required for accurate reporting of data-race values.
37//!
38//! As per the paper a threads timestamp is only incremented after a release operation is performed
39//! so some atomic operations that only perform acquires do not increment the timestamp. Due to shared
40//! code some atomic operations may increment the timestamp when not necessary but this has no effect
41//! on the data-race detection code.
42
43use std::cell::{Cell, Ref, RefCell, RefMut};
44use std::fmt::Debug;
45use std::mem;
46
47use rand::RngExt;
48use rustc_abi::{Align, HasDataLayout, Size};
49use rustc_ast::Mutability;
50use rustc_data_structures::fx::{FxHashMap, FxHashSet};
51use rustc_index::{Idx, IndexVec};
52use rustc_log::tracing;
53use rustc_middle::mir;
54use rustc_middle::ty::Ty;
55use rustc_span::Span;
56
57use super::vector_clock::{VClock, VTimestamp, VectorIdx};
58use super::weak_memory::EvalContextExt as _;
59use crate::concurrency::GlobalDataRaceHandler;
60use crate::diagnostics::RacingOp;
61use crate::intrinsics::AtomicRmwOp;
62use crate::*;
63
64pub type AllocState = VClockAlloc;
65
66/// Valid atomic read-write orderings, alias of atomic::Ordering (not non-exhaustive).
67#[derive(Copy, Clone, PartialEq, Eq, Debug)]
68pub enum AtomicRwOrd {
69    Relaxed,
70    Acquire,
71    Release,
72    AcqRel,
73    SeqCst,
74}
75
76/// Valid atomic read orderings, subset of atomic::Ordering.
77#[derive(Copy, Clone, PartialEq, Eq, Debug)]
78pub enum AtomicReadOrd {
79    Relaxed,
80    Acquire,
81    SeqCst,
82}
83
84/// Valid atomic write orderings, subset of atomic::Ordering.
85#[derive(Copy, Clone, PartialEq, Eq, Debug)]
86pub enum AtomicWriteOrd {
87    Relaxed,
88    Release,
89    SeqCst,
90}
91
92/// Valid atomic fence orderings, subset of atomic::Ordering.
93#[derive(Copy, Clone, PartialEq, Eq, Debug)]
94pub enum AtomicFenceOrd {
95    Acquire,
96    Release,
97    AcqRel,
98    SeqCst,
99}
100
101/// The current set of vector clocks describing the state
102/// of a thread, contains the happens-before clock and
103/// additional metadata to model atomic fence operations.
104#[derive(Clone, Default, Debug)]
105pub(super) struct ThreadClockSet {
106    /// The increasing clock representing timestamps
107    /// that happen-before this thread.
108    pub(super) clock: VClock,
109
110    /// The set of timestamps that will happen-before this
111    /// thread once it performs an acquire fence.
112    fence_acquire: VClock,
113
114    /// The last timestamp of happens-before relations that
115    /// have been released by this thread by a release fence.
116    fence_release: VClock,
117
118    /// Timestamps of the last SC write performed by each
119    /// thread, updated when this thread performs an SC fence.
120    /// This is never acquired into the thread's clock, it
121    /// just limits which old writes can be seen in weak memory emulation.
122    pub(super) write_seqcst: VClock,
123
124    /// Timestamps of the last SC fence performed by each
125    /// thread, updated when this thread performs an SC read.
126    /// This is never acquired into the thread's clock, it
127    /// just limits which old writes can be seen in weak memory emulation.
128    pub(super) read_seqcst: VClock,
129}
130
131impl ThreadClockSet {
132    /// Apply the effects of a release fence to this
133    /// set of thread vector clocks.
134    #[inline]
135    fn apply_release_fence(&mut self) {
136        self.fence_release.clone_from(&self.clock);
137    }
138
139    /// Apply the effects of an acquire fence to this
140    /// set of thread vector clocks.
141    #[inline]
142    fn apply_acquire_fence(&mut self) {
143        self.clock.join(&self.fence_acquire);
144    }
145
146    /// Increment the happens-before clock at a
147    /// known index.
148    #[inline]
149    fn increment_clock(&mut self, index: VectorIdx, current_span: Span) {
150        self.clock.increment_index(index, current_span);
151    }
152
153    /// Join the happens-before clock with that of
154    /// another thread, used to model thread join
155    /// operations.
156    fn join_with(&mut self, other: &ThreadClockSet) {
157        self.clock.join(&other.clock);
158    }
159}
160
161/// Error returned by finding a data race
162/// should be elaborated upon.
163#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
164pub struct DataRace;
165
166/// Externally stored memory cell clocks
167/// explicitly to reduce memory usage for the
168/// common case where no atomic operations
169/// exists on the memory cell.
170#[derive(Clone, PartialEq, Eq, Debug)]
171struct AtomicMemoryCellClocks {
172    /// The clock-vector of the timestamp of the last atomic
173    /// read operation performed by each thread.
174    /// This detects potential data-races between atomic read
175    /// and non-atomic write operations.
176    read_vector: VClock,
177
178    /// The clock-vector of the timestamp of the last atomic
179    /// write operation performed by each thread.
180    /// This detects potential data-races between atomic write
181    /// and non-atomic read or write operations.
182    write_vector: VClock,
183
184    /// Synchronization vector for acquire-release semantics
185    /// contains the vector of timestamps that will
186    /// happen-before a thread if an acquire-load is
187    /// performed on the data.
188    ///
189    /// With weak memory emulation, this is the clock of the most recent write. It is then only used
190    /// for release sequences, to integrate the most recent clock into the next one for RMWs.
191    sync_vector: VClock,
192
193    /// The size of accesses to this atomic location.
194    /// We use this to detect non-synchronized mixed-size accesses. Since all accesses must be
195    /// aligned to their size, this is sufficient to detect imperfectly overlapping accesses.
196    /// `None` indicates that we saw multiple different sizes, which is okay as long as all accesses are reads.
197    size: Option<Size>,
198}
199
200#[derive(Copy, Clone, PartialEq, Eq, Debug)]
201enum AtomicAccessType {
202    Load(AtomicReadOrd),
203    Store,
204    Rmw,
205}
206
207/// Type of a non-atomic read operation.
208#[derive(Copy, Clone, PartialEq, Eq, Debug)]
209pub enum NaReadType {
210    /// Standard unsynchronized write.
211    Read,
212
213    // An implicit read generated by a retag.
214    Retag,
215}
216
217impl NaReadType {
218    fn description(self) -> &'static str {
219        match self {
220            NaReadType::Read => "non-atomic read",
221            NaReadType::Retag => "retag read",
222        }
223    }
224}
225
226/// Type of a non-atomic write operation: allocating memory, non-atomic writes, and
227/// deallocating memory are all treated as writes for the purpose of the data-race detector.
228#[derive(Copy, Clone, PartialEq, Eq, Debug)]
229pub enum NaWriteType {
230    /// Allocate memory.
231    Allocate,
232
233    /// Standard unsynchronized write.
234    Write,
235
236    // An implicit write generated by a retag.
237    Retag,
238
239    /// Deallocate memory.
240    /// Note that when memory is deallocated first, later non-atomic accesses
241    /// will be reported as use-after-free, not as data races.
242    /// (Same for `Allocate` above.)
243    Deallocate,
244}
245
246impl NaWriteType {
247    fn description(self) -> &'static str {
248        match self {
249            NaWriteType::Allocate => "creating a new allocation",
250            NaWriteType::Write => "non-atomic write",
251            NaWriteType::Retag => "retag write",
252            NaWriteType::Deallocate => "deallocation",
253        }
254    }
255}
256
257#[derive(Copy, Clone, PartialEq, Eq, Debug)]
258enum AccessType {
259    NaRead(NaReadType),
260    NaWrite(NaWriteType),
261    AtomicLoad,
262    AtomicStore,
263    AtomicRmw,
264}
265
266/// Per-byte vector clock metadata for data-race detection.
267#[derive(Clone, PartialEq, Eq, Debug)]
268struct MemoryCellClocks {
269    /// The vector clock timestamp and the thread that did the last non-atomic write. We don't need
270    /// a full `VClock` here, it's always a single thread and nothing synchronizes, so the effective
271    /// clock is all-0 except for the thread that did the write.
272    write: (VectorIdx, VTimestamp),
273
274    /// The type of operation that the write index represents,
275    /// either newly allocated memory, a non-atomic write or
276    /// a deallocation of memory.
277    write_type: NaWriteType,
278
279    /// The vector clock of all non-atomic reads that happened since the last non-atomic write
280    /// (i.e., we join together the "singleton" clocks corresponding to each read). It is reset to
281    /// zero on each write operation.
282    read: VClock,
283
284    /// Atomic access tracking clocks.
285    /// For non-atomic memory this value is set to None.
286    /// For atomic memory, each byte carries this information.
287    atomic_ops: Option<Box<AtomicMemoryCellClocks>>,
288}
289
290/// Extra metadata associated with a thread.
291#[derive(Debug, Clone, Default)]
292struct ThreadExtraState {
293    /// The current vector index in use by the
294    /// thread currently, this is set to None
295    /// after the vector index has been re-used
296    /// and hence the value will never need to be
297    /// read during data-race reporting.
298    vector_index: Option<VectorIdx>,
299
300    /// Thread termination vector clock, this
301    /// is set on thread termination and is used
302    /// for joining on threads since the vector_index
303    /// may be re-used when the join operation occurs.
304    termination_vector_clock: Option<VClock>,
305}
306
307/// Global data-race detection state, contains the currently
308/// executing thread as well as the vector clocks associated
309/// with each of the threads.
310// FIXME: it is probably better to have one large RefCell, than to have so many small ones.
311#[derive(Debug, Clone)]
312pub struct GlobalState {
313    /// Set to true once the first additional
314    /// thread has launched, due to the dependency
315    /// between before and after a thread launch.
316    /// Any data-races must be recorded after this
317    /// so concurrent execution can ignore recording
318    /// any data-races.
319    multi_threaded: Cell<bool>,
320
321    /// A flag to mark we are currently performing
322    /// a data race free action (such as atomic access)
323    /// to suppress the race detector
324    ongoing_action_data_race_free: Cell<bool>,
325
326    /// Mapping of a vector index to a known set of thread
327    /// clocks, this is not directly mapping from a thread id
328    /// since it may refer to multiple threads.
329    vector_clocks: RefCell<IndexVec<VectorIdx, ThreadClockSet>>,
330
331    /// Mapping of a given vector index to the current thread
332    /// that the execution is representing, this may change
333    /// if a vector index is re-assigned to a new thread.
334    vector_info: RefCell<IndexVec<VectorIdx, ThreadId>>,
335
336    /// The mapping of a given thread to associated thread metadata.
337    thread_info: RefCell<IndexVec<ThreadId, ThreadExtraState>>,
338
339    /// Potential vector indices that could be re-used on thread creation
340    /// values are inserted here on after the thread has terminated and
341    /// been joined with, and hence may potentially become free
342    /// for use as the index for a new thread.
343    /// Elements in this set may still require the vector index to
344    /// report data-races, and can only be re-used after all
345    /// active vector clocks catch up with the threads timestamp.
346    reuse_candidates: RefCell<FxHashSet<VectorIdx>>,
347
348    /// We make SC fences act like RMWs on a global location.
349    /// To implement that, they all release and acquire into this clock.
350    last_sc_fence: RefCell<VClock>,
351
352    /// The timestamp of last SC write performed by each thread.
353    /// Threads only update their own index here!
354    last_sc_write_per_thread: RefCell<VClock>,
355
356    /// Track when an outdated (weak memory) load happens.
357    pub track_outdated_loads: bool,
358
359    /// Whether weak memory emulation is enabled
360    pub weak_memory: bool,
361}
362
363impl VisitProvenance for GlobalState {
364    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
365        // We don't have any tags.
366    }
367}
368
369impl AccessType {
370    fn description(self, ty: Option<Ty<'_>>, size: Option<Size>) -> String {
371        let mut msg = String::new();
372
373        if let Some(size) = size {
374            if size == Size::ZERO {
375                // In this case there were multiple read accesses with different sizes and then a write.
376                // We will be reporting *one* of the other reads, but we don't have enough information
377                // to determine which one had which size.
378                assert!(self == AccessType::AtomicLoad);
379                assert!(ty.is_none());
380                return format!("multiple differently-sized atomic loads, including one load");
381            }
382            msg.push_str(&format!("{}-byte {}", size.bytes(), msg))
383        }
384
385        msg.push_str(match self {
386            AccessType::NaRead(w) => w.description(),
387            AccessType::NaWrite(w) => w.description(),
388            AccessType::AtomicLoad => "atomic load",
389            AccessType::AtomicStore => "atomic store",
390            AccessType::AtomicRmw => "atomic read-modify-write",
391        });
392
393        if let Some(ty) = ty {
394            msg.push_str(&format!(" of type `{ty}`"));
395        }
396
397        msg
398    }
399
400    fn is_atomic(self) -> bool {
401        match self {
402            AccessType::AtomicLoad | AccessType::AtomicStore | AccessType::AtomicRmw => true,
403            AccessType::NaRead(_) | AccessType::NaWrite(_) => false,
404        }
405    }
406
407    fn is_read(self) -> bool {
408        match self {
409            AccessType::AtomicLoad | AccessType::NaRead(_) => true,
410            AccessType::NaWrite(_) | AccessType::AtomicStore | AccessType::AtomicRmw => false,
411        }
412    }
413
414    fn is_retag(self) -> bool {
415        matches!(
416            self,
417            AccessType::NaRead(NaReadType::Retag) | AccessType::NaWrite(NaWriteType::Retag)
418        )
419    }
420}
421
422impl AtomicMemoryCellClocks {
423    fn new(size: Size) -> Self {
424        AtomicMemoryCellClocks {
425            read_vector: Default::default(),
426            write_vector: Default::default(),
427            sync_vector: Default::default(),
428            size: Some(size),
429        }
430    }
431}
432
433impl MemoryCellClocks {
434    /// Create a new set of clocks representing memory allocated
435    ///  at a given vector timestamp and index.
436    fn new(alloc: VTimestamp, alloc_index: VectorIdx) -> Self {
437        MemoryCellClocks {
438            read: VClock::default(),
439            write: (alloc_index, alloc),
440            write_type: NaWriteType::Allocate,
441            atomic_ops: None,
442        }
443    }
444
445    #[inline]
446    fn write_was_before(&self, other: &VClock) -> bool {
447        // This is the same as `self.write() <= other` but
448        // without actually manifesting a clock for `self.write`.
449        self.write.1 <= other[self.write.0]
450    }
451
452    #[inline]
453    fn write(&self) -> VClock {
454        VClock::new_with_index(self.write.0, self.write.1)
455    }
456
457    /// Load the internal atomic memory cells if they exist.
458    #[inline]
459    fn atomic(&self) -> Option<&AtomicMemoryCellClocks> {
460        self.atomic_ops.as_deref()
461    }
462
463    /// Load the internal atomic memory cells if they exist.
464    #[inline]
465    fn atomic_mut_unwrap(&mut self) -> &mut AtomicMemoryCellClocks {
466        self.atomic_ops.as_deref_mut().unwrap()
467    }
468
469    /// Load or create the internal atomic memory metadata if it does not exist. Also ensures we do
470    /// not do mixed-size atomic accesses, and updates the recorded atomic access size.
471    fn atomic_access(
472        &mut self,
473        thread_clocks: &ThreadClockSet,
474        size: Size,
475        write: bool,
476    ) -> Result<&mut AtomicMemoryCellClocks, DataRace> {
477        match self.atomic_ops {
478            Some(ref mut atomic) => {
479                // We are good if the size is the same or all atomic accesses are before our current time.
480                if atomic.size == Some(size) {
481                    Ok(atomic)
482                } else if atomic.read_vector <= thread_clocks.clock
483                    && atomic.write_vector <= thread_clocks.clock
484                {
485                    // We are fully ordered after all previous accesses, so we can change the size.
486                    atomic.size = Some(size);
487                    Ok(atomic)
488                } else if !write && atomic.write_vector <= thread_clocks.clock {
489                    // This is a read, and it is ordered after the last write. It's okay for the
490                    // sizes to mismatch, as long as no writes with a different size occur later.
491                    atomic.size = None;
492                    Ok(atomic)
493                } else {
494                    Err(DataRace)
495                }
496            }
497            None => {
498                self.atomic_ops = Some(Box::new(AtomicMemoryCellClocks::new(size)));
499                Ok(self.atomic_ops.as_mut().unwrap())
500            }
501        }
502    }
503
504    /// Update memory cell data-race tracking for atomic
505    /// load acquire semantics, is a no-op if this memory was
506    /// not used previously as atomic memory.
507    fn load_acquire(
508        &mut self,
509        thread_clocks: &mut ThreadClockSet,
510        index: VectorIdx,
511        access_size: Size,
512        sync_clock: Option<&VClock>,
513    ) -> Result<(), DataRace> {
514        self.atomic_read_detect(thread_clocks, index, access_size)?;
515        if let Some(sync_clock) = sync_clock.or_else(|| self.atomic().map(|a| &a.sync_vector)) {
516            thread_clocks.clock.join(sync_clock);
517        }
518        Ok(())
519    }
520
521    /// Update memory cell data-race tracking for atomic
522    /// load relaxed semantics, is a no-op if this memory was
523    /// not used previously as atomic memory.
524    fn load_relaxed(
525        &mut self,
526        thread_clocks: &mut ThreadClockSet,
527        index: VectorIdx,
528        access_size: Size,
529        sync_clock: Option<&VClock>,
530    ) -> Result<(), DataRace> {
531        self.atomic_read_detect(thread_clocks, index, access_size)?;
532        if let Some(sync_clock) = sync_clock.or_else(|| self.atomic().map(|a| &a.sync_vector)) {
533            thread_clocks.fence_acquire.join(sync_clock);
534        }
535        Ok(())
536    }
537
538    /// Update the memory cell data-race tracking for atomic
539    /// store release semantics.
540    fn store_release(
541        &mut self,
542        thread_clocks: &ThreadClockSet,
543        index: VectorIdx,
544        access_size: Size,
545    ) -> Result<(), DataRace> {
546        self.atomic_write_detect(thread_clocks, index, access_size)?;
547        let atomic = self.atomic_mut_unwrap(); // initialized by `atomic_write_detect`
548        atomic.sync_vector.clone_from(&thread_clocks.clock);
549        Ok(())
550    }
551
552    /// Update the memory cell data-race tracking for atomic
553    /// store relaxed semantics.
554    fn store_relaxed(
555        &mut self,
556        thread_clocks: &ThreadClockSet,
557        index: VectorIdx,
558        access_size: Size,
559    ) -> Result<(), DataRace> {
560        self.atomic_write_detect(thread_clocks, index, access_size)?;
561
562        // The handling of release sequences was changed in C++20 and so
563        // the code here is different to the paper since now all relaxed
564        // stores block release sequences. The exception for same-thread
565        // relaxed stores has been removed. We always overwrite the `sync_vector`,
566        // meaning the previous release sequence is broken.
567        let atomic = self.atomic_mut_unwrap();
568        atomic.sync_vector.clone_from(&thread_clocks.fence_release);
569        Ok(())
570    }
571
572    /// Update the memory cell data-race tracking for atomic
573    /// store release semantics for RMW operations.
574    fn rmw_release(
575        &mut self,
576        thread_clocks: &ThreadClockSet,
577        index: VectorIdx,
578        access_size: Size,
579    ) -> Result<(), DataRace> {
580        self.atomic_write_detect(thread_clocks, index, access_size)?;
581        let atomic = self.atomic_mut_unwrap();
582        // This *joining* of `sync_vector` implements release sequences: future
583        // reads of this location will acquire our clock *and* what was here before.
584        atomic.sync_vector.join(&thread_clocks.clock);
585        Ok(())
586    }
587
588    /// Update the memory cell data-race tracking for atomic
589    /// store relaxed semantics for RMW operations.
590    fn rmw_relaxed(
591        &mut self,
592        thread_clocks: &ThreadClockSet,
593        index: VectorIdx,
594        access_size: Size,
595    ) -> Result<(), DataRace> {
596        self.atomic_write_detect(thread_clocks, index, access_size)?;
597        let atomic = self.atomic_mut_unwrap();
598        // This *joining* of `sync_vector` implements release sequences: future
599        // reads of this location will acquire our fence clock *and* what was here before.
600        atomic.sync_vector.join(&thread_clocks.fence_release);
601        Ok(())
602    }
603
604    /// Detect data-races with an atomic read, caused by a non-atomic write that does
605    /// not happen-before the atomic-read.
606    fn atomic_read_detect(
607        &mut self,
608        thread_clocks: &ThreadClockSet,
609        index: VectorIdx,
610        access_size: Size,
611    ) -> Result<(), DataRace> {
612        trace!("Atomic read with vectors: {:#?} :: {:#?}", self, thread_clocks);
613        let atomic = self.atomic_access(thread_clocks, access_size, /*write*/ false)?;
614        atomic.read_vector.set_at_index(&thread_clocks.clock, index);
615        // Make sure the last non-atomic write was before this access.
616        if self.write_was_before(&thread_clocks.clock) { Ok(()) } else { Err(DataRace) }
617    }
618
619    /// Detect data-races with an atomic write, either with a non-atomic read or with
620    /// a non-atomic write.
621    fn atomic_write_detect(
622        &mut self,
623        thread_clocks: &ThreadClockSet,
624        index: VectorIdx,
625        access_size: Size,
626    ) -> Result<(), DataRace> {
627        trace!("Atomic write with vectors: {:#?} :: {:#?}", self, thread_clocks);
628        let atomic = self.atomic_access(thread_clocks, access_size, /*write*/ true)?;
629        atomic.write_vector.set_at_index(&thread_clocks.clock, index);
630        // Make sure the last non-atomic write and all non-atomic reads were before this access.
631        if self.write_was_before(&thread_clocks.clock) && self.read <= thread_clocks.clock {
632            Ok(())
633        } else {
634            Err(DataRace)
635        }
636    }
637
638    /// Detect races for non-atomic read operations at the current memory cell
639    /// returns true if a data-race is detected.
640    fn non_atomic_read_detect(
641        &mut self,
642        thread_clocks: &mut ThreadClockSet,
643        index: VectorIdx,
644        read_type: NaReadType,
645        current_span: Span,
646    ) -> Result<(), DataRace> {
647        trace!("Unsynchronized read with vectors: {:#?} :: {:#?}", self, thread_clocks);
648        if !current_span.is_dummy() {
649            thread_clocks.clock.index_mut(index).span = current_span;
650        }
651        thread_clocks.clock.index_mut(index).set_read_type(read_type);
652        // Check synchronization with non-atomic writes.
653        if !self.write_was_before(&thread_clocks.clock) {
654            return Err(DataRace);
655        }
656        // Check synchronization with atomic writes.
657        if !self.atomic().is_none_or(|atomic| atomic.write_vector <= thread_clocks.clock) {
658            return Err(DataRace);
659        }
660        // Record this access.
661        self.read.set_at_index(&thread_clocks.clock, index);
662        Ok(())
663    }
664
665    /// Detect races for non-atomic write operations at the current memory cell
666    /// returns true if a data-race is detected.
667    fn non_atomic_write_detect(
668        &mut self,
669        thread_clocks: &mut ThreadClockSet,
670        index: VectorIdx,
671        write_type: NaWriteType,
672        current_span: Span,
673    ) -> Result<(), DataRace> {
674        trace!("Unsynchronized write with vectors: {:#?} :: {:#?}", self, thread_clocks);
675        if !current_span.is_dummy() {
676            thread_clocks.clock.index_mut(index).span = current_span;
677        }
678        // Check synchronization with non-atomic accesses.
679        if !(self.write_was_before(&thread_clocks.clock) && self.read <= thread_clocks.clock) {
680            return Err(DataRace);
681        }
682        // Check synchronization with atomic accesses.
683        if !self.atomic().is_none_or(|atomic| {
684            atomic.write_vector <= thread_clocks.clock && atomic.read_vector <= thread_clocks.clock
685        }) {
686            return Err(DataRace);
687        }
688        // Record this access.
689        self.write = (index, thread_clocks.clock[index]);
690        self.write_type = write_type;
691        self.read.set_zero_vector();
692        // This is not an atomic location any more.
693        self.atomic_ops = None;
694        Ok(())
695    }
696}
697
698impl GlobalDataRaceHandler {
699    /// Select whether data race checking is disabled. This is solely an
700    /// implementation detail of `allow_data_races_*` and must not be used anywhere else!
701    fn set_ongoing_action_data_race_free(&self, enable: bool) {
702        match self {
703            GlobalDataRaceHandler::None => {}
704            GlobalDataRaceHandler::Vclocks(data_race) => {
705                let old = data_race.ongoing_action_data_race_free.replace(enable);
706                assert_ne!(old, enable, "cannot nest allow_data_races");
707            }
708            GlobalDataRaceHandler::Genmc(genmc_ctx) => {
709                genmc_ctx.set_ongoing_action_data_race_free(enable);
710            }
711        }
712    }
713}
714
715/// Evaluation context extensions.
716impl<'tcx> EvalContextExt<'tcx> for MiriInterpCx<'tcx> {}
717pub trait EvalContextExt<'tcx>: MiriInterpCxExt<'tcx> {
718    /// Perform an atomic read operation at the memory location.
719    fn read_scalar_atomic(
720        &self,
721        place: &MPlaceTy<'tcx>,
722        atomic: AtomicReadOrd,
723    ) -> InterpResult<'tcx, Scalar> {
724        let this = self.eval_context_ref();
725        this.atomic_access_check(place, AtomicAccessType::Load(atomic))?;
726        // This will read from the last store in the modification order of this location. In case
727        // weak memory emulation is enabled, this may not be the store we will pick to actually read from and return.
728        // This is fine with StackedBorrow and race checks because they don't concern metadata on
729        // the *value* (including the associated provenance if this is an AtomicPtr) at this location.
730        // Only metadata on the location itself is used.
731
732        if let Some(genmc_ctx) = this.machine.data_race.as_genmc_ref() {
733            let old_val = this.run_for_validation_ref(|this| this.read_scalar(place)).discard_err();
734            return genmc_ctx.atomic_load(
735                this,
736                place.ptr().addr(),
737                place.layout.size,
738                atomic,
739                old_val,
740            );
741        }
742
743        trace!("read_scalar_atomic({:?}, {} bytes)", place.ptr(), place.layout.size.bytes());
744
745        let scalar = this.allow_data_races_ref(move |this| this.read_scalar(place))?;
746        let buffered_scalar = this.buffered_atomic_read(place, atomic, scalar, |sync_clock| {
747            this.validate_atomic_load(place, atomic, sync_clock)
748        })?;
749        interp_ok(buffered_scalar.ok_or_else(|| err_ub!(InvalidUninitBytes(None)))?)
750    }
751
752    /// Perform an atomic write operation at the memory location.
753    fn write_scalar_atomic(
754        &mut self,
755        val: Scalar,
756        dest: &MPlaceTy<'tcx>,
757        atomic: AtomicWriteOrd,
758    ) -> InterpResult<'tcx> {
759        let this = self.eval_context_mut();
760        this.atomic_access_check(dest, AtomicAccessType::Store)?;
761
762        // Read the previous value so we can put it in the store buffer later.
763        // Both GenMC and Miri need this. This value is nonsense if there are concurrent writes
764        // but the code consuming the value is aware of that.
765        let old_val = this.run_for_validation_ref(|this| this.read_scalar(dest)).discard_err();
766
767        // Inform GenMC about the atomic store.
768        if let Some(genmc_ctx) = this.machine.data_race.as_genmc_ref() {
769            if genmc_ctx.atomic_store(
770                this,
771                dest.ptr().addr(),
772                dest.layout.size,
773                val,
774                old_val,
775                atomic,
776            )? {
777                // The store might be the latest store in coherence order (determined by GenMC).
778                // If it is, we need to update the value in Miri's memory:
779                this.allow_data_races_mut(|this| this.write_scalar(val, dest))?;
780            }
781            return interp_ok(());
782        }
783
784        trace!("write_scalar_atomic({:?}, {} bytes)", dest.ptr(), dest.layout.size.bytes());
785
786        this.allow_data_races_mut(move |this| this.write_scalar(val, dest))?;
787        this.validate_atomic_store(dest, atomic)?;
788        this.buffered_atomic_write(val, dest, atomic, old_val)
789    }
790
791    /// Perform an atomic RMW operation on a memory location.
792    fn atomic_rmw_op_immediate(
793        &mut self,
794        place: &MPlaceTy<'tcx>,
795        rhs: &ImmTy<'tcx>,
796        atomic_op: AtomicRmwOp,
797        ord: AtomicRwOrd,
798    ) -> InterpResult<'tcx, ImmTy<'tcx>> {
799        let this = self.eval_context_mut();
800        this.atomic_access_check(place, AtomicAccessType::Rmw)?;
801
802        let old = this.allow_data_races_mut(|this| this.read_immediate(place))?;
803
804        // Inform GenMC about the atomic rmw operation.
805        if let Some(genmc_ctx) = this.machine.data_race.as_genmc_ref() {
806            let (old_val, new_val) = genmc_ctx.atomic_rmw_op(
807                this,
808                place.ptr().addr(),
809                place.layout.size,
810                atomic_op,
811                place.layout.backend_repr.is_signed(),
812                ord,
813                rhs.to_scalar(),
814                old.to_scalar(),
815            )?;
816            if let Some(new_val) = new_val {
817                this.allow_data_races_mut(|this| this.write_scalar(new_val, place))?;
818            }
819            return interp_ok(ImmTy::from_scalar(old_val, old.layout));
820        }
821
822        trace!("atomic_rmw({:?}, {} bytes)", place.ptr(), place.layout.size.bytes());
823
824        let val = match atomic_op {
825            AtomicRmwOp::MirOp { op, neg } => {
826                let val = this.binary_op(op, &old, rhs)?;
827                if neg { this.unary_op(mir::UnOp::Not, &val)? } else { val }
828            }
829            AtomicRmwOp::Max => {
830                let lt = this.binary_op(mir::BinOp::Lt, &old, rhs)?.to_scalar().to_bool()?;
831                if lt { rhs } else { &old }.clone()
832            }
833            AtomicRmwOp::Min => {
834                let lt = this.binary_op(mir::BinOp::Lt, &old, rhs)?.to_scalar().to_bool()?;
835                if lt { &old } else { rhs }.clone()
836            }
837        };
838
839        this.allow_data_races_mut(|this| this.write_immediate(*val, place))?;
840        this.validate_atomic_rmw(place, ord)?;
841        this.buffered_atomic_rmw(val.to_scalar(), place, ord, old.to_scalar())?;
842        interp_ok(old)
843    }
844
845    /// Perform an atomic exchange with a memory place and a new
846    /// scalar value, the old value is returned.
847    fn atomic_exchange_scalar(
848        &mut self,
849        place: &MPlaceTy<'tcx>,
850        new: Scalar,
851        atomic: AtomicRwOrd,
852    ) -> InterpResult<'tcx, Scalar> {
853        let this = self.eval_context_mut();
854        this.atomic_access_check(place, AtomicAccessType::Rmw)?;
855
856        let old = this.allow_data_races_mut(|this| this.read_scalar(place))?;
857
858        // Inform GenMC about the atomic atomic exchange.
859        if let Some(genmc_ctx) = this.machine.data_race.as_genmc_ref() {
860            let (old_val, new_val) = genmc_ctx.atomic_exchange(
861                this,
862                place.ptr().addr(),
863                place.layout.size,
864                new,
865                atomic,
866                old,
867            )?;
868            // The store might be the latest store in coherence order (determined by GenMC).
869            // If it is, we need to update the value in Miri's memory:
870            if let Some(new_val) = new_val {
871                this.allow_data_races_mut(|this| this.write_scalar(new_val, place))?;
872            }
873            return interp_ok(old_val);
874        }
875
876        trace!("atomic_exchange_scalar({:?}, {} bytes)", place.ptr(), place.layout.size.bytes());
877
878        this.allow_data_races_mut(|this| this.write_scalar(new, place))?;
879        this.validate_atomic_rmw(place, atomic)?;
880        this.buffered_atomic_rmw(new, place, atomic, old)?;
881        interp_ok(old)
882    }
883
884    /// Perform an atomic compare and exchange at a given memory location.
885    /// On success an atomic RMW operation is performed and on failure
886    /// only an atomic read occurs. If `can_fail_spuriously` is true,
887    /// then we treat it as a "compare_exchange_weak" operation, and
888    /// some portion of the time fail even when the values are actually
889    /// identical.
890    fn atomic_compare_exchange_scalar(
891        &mut self,
892        place: &MPlaceTy<'tcx>,
893        expect_old: &ImmTy<'tcx>,
894        new: Scalar,
895        success: AtomicRwOrd,
896        fail: AtomicReadOrd,
897        can_fail_spuriously: bool,
898    ) -> InterpResult<'tcx, Immediate<Provenance>> {
899        let this = self.eval_context_mut();
900        this.atomic_access_check(place, AtomicAccessType::Rmw)?;
901
902        // Read as immediate for the sake of `binary_op()`
903        let old = this.allow_data_races_mut(|this| this.read_immediate(place))?;
904
905        // Inform GenMC about the atomic atomic compare exchange.
906        if let Some(genmc_ctx) = this.machine.data_race.as_genmc_ref() {
907            let (old_value, new_value, cmpxchg_success) = genmc_ctx.atomic_compare_exchange(
908                this,
909                place.ptr().addr(),
910                place.layout.size,
911                this.read_scalar(expect_old)?,
912                new,
913                success,
914                fail,
915                can_fail_spuriously,
916                old.to_scalar(),
917            )?;
918            // The store might be the latest store in coherence order (determined by GenMC).
919            // If it is, we need to update the value in Miri's memory:
920            if let Some(new_value) = new_value {
921                this.allow_data_races_mut(|this| this.write_scalar(new_value, place))?;
922            }
923            return interp_ok(Immediate::ScalarPair(old_value, Scalar::from_bool(cmpxchg_success)));
924        }
925
926        // `binary_op` will bail if either of them is not a scalar.
927        let eq = this.binary_op(mir::BinOp::Eq, &old, expect_old)?;
928        // If the operation would succeed, but is "weak", fail some portion
929        // of the time, based on `success_rate`.
930        let success_rate = 1.0 - this.machine.cmpxchg_weak_failure_rate;
931        let cmpxchg_success = eq.to_scalar().to_bool()?
932            && if can_fail_spuriously {
933                this.machine.rng.get_mut().random_bool(success_rate)
934            } else {
935                true
936            };
937        let res = Immediate::ScalarPair(old.to_scalar(), Scalar::from_bool(cmpxchg_success));
938
939        trace!(
940            "atomic_compare_exchange_scalar({:?}, {} bytes, success = {})",
941            place.ptr(),
942            place.layout.size.bytes(),
943            cmpxchg_success,
944        );
945
946        // Update ptr depending on comparison.
947        // if successful, perform a full rw-atomic validation
948        // otherwise treat this as an atomic load with the fail ordering.
949        if cmpxchg_success {
950            this.allow_data_races_mut(|this| this.write_scalar(new, place))?;
951            this.validate_atomic_rmw(place, success)?;
952            this.buffered_atomic_rmw(new, place, success, old.to_scalar())?;
953        } else {
954            this.validate_atomic_load(place, fail, /* can use latest sync clock */ None)?;
955            // A failed compare exchange is equivalent to a load, reading from the latest store
956            // in the modification order.
957            // Since `old` is only a value and not the store element, we need to separately
958            // find it in our store buffer and perform load_impl on it.
959            this.perform_read_on_buffered_latest(place, fail)?;
960        }
961
962        // Return the old value.
963        interp_ok(res)
964    }
965
966    /// Update the data-race detector for an atomic fence on the current thread.
967    fn atomic_fence(&mut self, atomic: AtomicFenceOrd) -> InterpResult<'tcx> {
968        let this = self.eval_context_mut();
969        let machine = &this.machine;
970        match &this.machine.data_race {
971            GlobalDataRaceHandler::None => interp_ok(()),
972            GlobalDataRaceHandler::Vclocks(data_race) => data_race.atomic_fence(machine, atomic),
973            GlobalDataRaceHandler::Genmc(genmc_ctx) => genmc_ctx.atomic_fence(machine, atomic),
974        }
975    }
976
977    /// Calls the callback with the "release" clock of the current thread.
978    /// Other threads can acquire this clock in the future to establish synchronization
979    /// with this program point.
980    ///
981    /// The closure will only be invoked if data race handling is on.
982    fn release_clock<R>(
983        &self,
984        callback: impl FnOnce(&VClock) -> R,
985    ) -> InterpResult<'tcx, Option<R>> {
986        let this = self.eval_context_ref();
987        interp_ok(match &this.machine.data_race {
988            GlobalDataRaceHandler::None => None,
989            GlobalDataRaceHandler::Genmc(_genmc_ctx) =>
990                throw_unsup_format!(
991                    "this operation performs synchronization that is not supported in GenMC mode"
992                ),
993            GlobalDataRaceHandler::Vclocks(data_race) =>
994                Some(data_race.release_clock(&this.machine.threads, callback)),
995        })
996    }
997
998    /// Acquire the given clock into the current thread, establishing synchronization with
999    /// the moment when that clock snapshot was taken via `release_clock`.
1000    fn acquire_clock(&self, clock: &VClock) -> InterpResult<'tcx> {
1001        let this = self.eval_context_ref();
1002        match &this.machine.data_race {
1003            GlobalDataRaceHandler::None => {}
1004            GlobalDataRaceHandler::Genmc(_genmc_ctx) =>
1005                throw_unsup_format!(
1006                    "this operation performs synchronization that is not supported in GenMC mode"
1007                ),
1008            GlobalDataRaceHandler::Vclocks(data_race) =>
1009                data_race.acquire_clock(clock, &this.machine.threads),
1010        }
1011        interp_ok(())
1012    }
1013}
1014
1015/// Vector clock metadata for a logical memory allocation.
1016#[derive(Debug, Clone)]
1017pub struct VClockAlloc {
1018    /// Assigning each byte a MemoryCellClocks.
1019    alloc_ranges: RefCell<DedupRangeMap<MemoryCellClocks>>,
1020}
1021
1022impl VisitProvenance for VClockAlloc {
1023    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
1024        // No tags or allocIds here.
1025    }
1026}
1027
1028impl VClockAlloc {
1029    /// Create a new data-race detector for newly allocated memory.
1030    pub fn new_allocation(
1031        global: &GlobalState,
1032        thread_mgr: &ThreadManager<'_>,
1033        len: Size,
1034        kind: MemoryKind,
1035        current_span: Span,
1036    ) -> VClockAlloc {
1037        // Determine the thread that did the allocation, and when it did it.
1038        let (alloc_timestamp, alloc_index) = match kind {
1039            // User allocated and stack memory should track allocation.
1040            MemoryKind::Machine(
1041                MiriMemoryKind::Rust
1042                | MiriMemoryKind::Miri
1043                | MiriMemoryKind::C
1044                | MiriMemoryKind::WinHeap
1045                | MiriMemoryKind::WinLocal
1046                | MiriMemoryKind::Mmap
1047                | MiriMemoryKind::SocketAddress,
1048            )
1049            | MemoryKind::Stack => {
1050                let (alloc_index, clocks) = global.active_thread_state(thread_mgr);
1051                let mut alloc_timestamp = clocks.clock[alloc_index];
1052                alloc_timestamp.span = current_span;
1053                (alloc_timestamp, alloc_index)
1054            }
1055            // Other global memory should trace races but be allocated at the 0 timestamp
1056            // (conceptually they are allocated on the main thread before everything).
1057            MemoryKind::Machine(
1058                MiriMemoryKind::Global
1059                | MiriMemoryKind::Machine
1060                | MiriMemoryKind::Runtime
1061                | MiriMemoryKind::ExternStatic
1062                | MiriMemoryKind::Tls,
1063            )
1064            | MemoryKind::CallerLocation =>
1065                (VTimestamp::ZERO, global.thread_index(ThreadId::MAIN_THREAD)),
1066        };
1067        VClockAlloc {
1068            alloc_ranges: RefCell::new(DedupRangeMap::new(
1069                len,
1070                MemoryCellClocks::new(alloc_timestamp, alloc_index),
1071            )),
1072        }
1073    }
1074
1075    // Find an index, if one exists where the value
1076    // in `l` is greater than the value in `r`.
1077    fn find_gt_index(l: &VClock, r: &VClock) -> Option<VectorIdx> {
1078        trace!("Find index where not {:?} <= {:?}", l, r);
1079        let l_slice = l.as_slice();
1080        let r_slice = r.as_slice();
1081        l_slice
1082            .iter()
1083            .zip(r_slice.iter())
1084            .enumerate()
1085            .find_map(|(idx, (&l, &r))| if l > r { Some(idx) } else { None })
1086            .or_else(|| {
1087                if l_slice.len() > r_slice.len() {
1088                    // By invariant, if l_slice is longer
1089                    // then one element must be larger.
1090                    // This just validates that this is true
1091                    // and reports earlier elements first.
1092                    let l_remainder_slice = &l_slice[r_slice.len()..];
1093                    let idx = l_remainder_slice
1094                        .iter()
1095                        .enumerate()
1096                        .find_map(|(idx, &r)| if r == VTimestamp::ZERO { None } else { Some(idx) })
1097                        .expect("Invalid VClock Invariant");
1098                    Some(idx + r_slice.len())
1099                } else {
1100                    None
1101                }
1102            })
1103            .map(VectorIdx::new)
1104    }
1105
1106    /// Report a data-race found in the program.
1107    /// This finds the two racing threads and the type
1108    /// of data-race that occurred. This will also
1109    /// return info about the memory location the data-race
1110    /// occurred in. The `ty` parameter is used for diagnostics, letting
1111    /// the user know which type was involved in the access.
1112    #[cold]
1113    #[inline(never)]
1114    fn report_data_race<'tcx>(
1115        global: &GlobalState,
1116        thread_mgr: &ThreadManager<'_>,
1117        mem_clocks: &MemoryCellClocks,
1118        access: AccessType,
1119        access_size: Size,
1120        ptr_dbg: interpret::Pointer<AllocId>,
1121        ty: Option<Ty<'_>>,
1122    ) -> InterpResult<'tcx> {
1123        let (active_index, active_clocks) = global.active_thread_state(thread_mgr);
1124        let mut other_size = None; // if `Some`, this was a size-mismatch race
1125        let write_clock;
1126        let (other_access, other_thread, other_clock) =
1127            // First check the atomic-nonatomic cases.
1128            if !access.is_atomic() &&
1129                let Some(atomic) = mem_clocks.atomic() &&
1130                let Some(idx) = Self::find_gt_index(&atomic.write_vector, &active_clocks.clock)
1131            {
1132                (AccessType::AtomicStore, idx, &atomic.write_vector)
1133            } else if !access.is_atomic() &&
1134                !access.is_read() &&
1135                let Some(atomic) = mem_clocks.atomic() &&
1136                let Some(idx) = Self::find_gt_index(&atomic.read_vector, &active_clocks.clock)
1137            {
1138                (AccessType::AtomicLoad, idx, &atomic.read_vector)
1139            // Then check races with non-atomic writes/reads.
1140            } else if mem_clocks.write.1 > active_clocks.clock[mem_clocks.write.0] {
1141                write_clock = mem_clocks.write();
1142                (AccessType::NaWrite(mem_clocks.write_type), mem_clocks.write.0, &write_clock)
1143            } else if !access.is_read() && let Some(idx) = Self::find_gt_index(&mem_clocks.read, &active_clocks.clock) {
1144                (AccessType::NaRead(mem_clocks.read[idx].read_type()), idx, &mem_clocks.read)
1145            // Finally, mixed-size races.
1146            } else if access.is_atomic() && let Some(atomic) = mem_clocks.atomic() && atomic.size != Some(access_size) {
1147                // This is only a race if we are not synchronized with all atomic accesses, so find
1148                // the one we are not synchronized with.
1149                other_size = Some(atomic.size.unwrap_or(Size::ZERO));
1150                if let Some(idx) = Self::find_gt_index(&atomic.write_vector, &active_clocks.clock)
1151                    {
1152                        (AccessType::AtomicStore, idx, &atomic.write_vector)
1153                    } else if let Some(idx) =
1154                        Self::find_gt_index(&atomic.read_vector, &active_clocks.clock)
1155                    {
1156                        (AccessType::AtomicLoad, idx, &atomic.read_vector)
1157                    } else {
1158                        unreachable!(
1159                            "Failed to report data-race for mixed-size access: no race found"
1160                        )
1161                    }
1162            } else {
1163                unreachable!("Failed to report data-race")
1164            };
1165
1166        // Load elaborated thread information about the racing thread actions.
1167        let active_thread_info = global.print_thread_metadata(thread_mgr, active_index);
1168        let other_thread_info = global.print_thread_metadata(thread_mgr, other_thread);
1169        let involves_non_atomic = !access.is_atomic() || !other_access.is_atomic();
1170
1171        // Throw the data-race detection.
1172        let extra = if other_size.is_some() {
1173            assert!(!involves_non_atomic);
1174            Some("overlapping unsynchronized atomic accesses must use the same access size")
1175        } else if access.is_read() && other_access.is_read() {
1176            panic!(
1177                "there should be no same-size read-read races\naccess: {access:?}\nother_access: {other_access:?}"
1178            )
1179        } else {
1180            None
1181        };
1182        Err(err_machine_stop!(TerminationInfo::DataRace {
1183            involves_non_atomic,
1184            extra,
1185            retag_explain: access.is_retag() || other_access.is_retag(),
1186            ptr: ptr_dbg,
1187            op1: RacingOp {
1188                action: other_access.description(None, other_size),
1189                thread_info: other_thread_info,
1190                span: other_clock.as_slice()[other_thread.index()].span_data(),
1191            },
1192            op2: RacingOp {
1193                action: access.description(ty, other_size.map(|_| access_size)),
1194                thread_info: active_thread_info,
1195                span: active_clocks.clock.as_slice()[active_index.index()].span_data(),
1196            },
1197        }))?
1198    }
1199
1200    /// Return the release/acquire synchronization clock for the given memory range.
1201    pub(super) fn sync_clock(&self, access_range: AllocRange) -> VClock {
1202        let alloc_ranges = self.alloc_ranges.borrow();
1203        let mut clock = VClock::default();
1204        for (_, mem_clocks) in alloc_ranges.iter(access_range.start, access_range.size) {
1205            if let Some(atomic) = mem_clocks.atomic() {
1206                clock.join(&atomic.sync_vector);
1207            }
1208        }
1209        clock
1210    }
1211
1212    /// Detect data-races for an unsynchronized read operation. It will not perform
1213    /// data-race detection if `race_detecting()` is false, either due to no threads
1214    /// being created or if it is temporarily disabled during a racy read or write
1215    /// operation for which data-race detection is handled separately, for example
1216    /// atomic read operations. The `ty` parameter is used for diagnostics, letting
1217    /// the user know which type was read.
1218    pub fn read_non_atomic<'tcx>(
1219        &self,
1220        alloc_id: AllocId,
1221        access_range: AllocRange,
1222        read_type: NaReadType,
1223        ty: Option<Ty<'_>>,
1224        machine: &MiriMachine<'_>,
1225    ) -> InterpResult<'tcx> {
1226        let current_span = machine.current_user_relevant_span();
1227        let global = machine.data_race.as_vclocks_ref().unwrap();
1228        if !global.race_detecting() {
1229            return interp_ok(());
1230        }
1231        let (index, mut thread_clocks) = global.active_thread_state_mut(&machine.threads);
1232        let mut alloc_ranges = self.alloc_ranges.borrow_mut();
1233        for (mem_clocks_range, mem_clocks) in
1234            alloc_ranges.iter_mut(access_range.start, access_range.size)
1235        {
1236            if let Err(DataRace) = mem_clocks.non_atomic_read_detect(
1237                &mut thread_clocks,
1238                index,
1239                read_type,
1240                current_span,
1241            ) {
1242                drop(thread_clocks);
1243                // Report data-race.
1244                return Self::report_data_race(
1245                    global,
1246                    &machine.threads,
1247                    mem_clocks,
1248                    AccessType::NaRead(read_type),
1249                    access_range.size,
1250                    interpret::Pointer::new(alloc_id, Size::from_bytes(mem_clocks_range.start)),
1251                    ty,
1252                );
1253            }
1254        }
1255        interp_ok(())
1256    }
1257
1258    /// Detect data-races for an unsynchronized write operation. It will not perform
1259    /// data-race detection if `race_detecting()` is false, either due to no threads
1260    /// being created or if it is temporarily disabled during a racy read or write
1261    /// operation. The `ty` parameter is used for diagnostics, letting
1262    /// the user know which type was written.
1263    pub fn write_non_atomic<'tcx>(
1264        &self,
1265        alloc_id: AllocId,
1266        access_range: AllocRange,
1267        write_type: NaWriteType,
1268        ty: Option<Ty<'_>>,
1269        machine: &MiriMachine<'_>,
1270    ) -> InterpResult<'tcx> {
1271        let current_span = machine.current_user_relevant_span();
1272        let global = machine.data_race.as_vclocks_ref().unwrap();
1273        if !global.race_detecting() {
1274            return interp_ok(());
1275        }
1276        let (index, mut thread_clocks) = global.active_thread_state_mut(&machine.threads);
1277        for (mem_clocks_range, mem_clocks) in
1278            self.alloc_ranges.borrow_mut().iter_mut(access_range.start, access_range.size)
1279        {
1280            if let Err(DataRace) = mem_clocks.non_atomic_write_detect(
1281                &mut thread_clocks,
1282                index,
1283                write_type,
1284                current_span,
1285            ) {
1286                drop(thread_clocks);
1287                // Report data-race
1288                return Self::report_data_race(
1289                    global,
1290                    &machine.threads,
1291                    mem_clocks,
1292                    AccessType::NaWrite(write_type),
1293                    access_range.size,
1294                    interpret::Pointer::new(alloc_id, Size::from_bytes(mem_clocks_range.start)),
1295                    ty,
1296                );
1297            }
1298        }
1299        interp_ok(())
1300    }
1301}
1302
1303/// Vector clock state for a stack frame (tracking the local variables
1304/// that do not have an allocation yet).
1305#[derive(Debug, Default)]
1306pub struct FrameState {
1307    local_clocks: RefCell<FxHashMap<mir::Local, LocalClocks>>,
1308}
1309
1310/// Stripped-down version of [`MemoryCellClocks`] for the clocks we need to keep track
1311/// of in a local that does not yet have addressable memory -- and hence can only
1312/// be accessed from the thread its stack frame belongs to, and cannot be access atomically.
1313#[derive(Debug)]
1314struct LocalClocks {
1315    write: VTimestamp,
1316    write_type: NaWriteType,
1317    read: VTimestamp,
1318}
1319
1320impl Default for LocalClocks {
1321    fn default() -> Self {
1322        Self { write: VTimestamp::ZERO, write_type: NaWriteType::Allocate, read: VTimestamp::ZERO }
1323    }
1324}
1325
1326impl FrameState {
1327    pub fn local_write(&self, local: mir::Local, storage_live: bool, machine: &MiriMachine<'_>) {
1328        let current_span = machine.current_user_relevant_span();
1329        let global = machine.data_race.as_vclocks_ref().unwrap();
1330        if !global.race_detecting() {
1331            return;
1332        }
1333        let (index, mut thread_clocks) = global.active_thread_state_mut(&machine.threads);
1334        // This should do the same things as `MemoryCellClocks::write_race_detect`.
1335        if !current_span.is_dummy() {
1336            thread_clocks.clock.index_mut(index).span = current_span;
1337        }
1338        let mut clocks = self.local_clocks.borrow_mut();
1339        if storage_live {
1340            let new_clocks = LocalClocks {
1341                write: thread_clocks.clock[index],
1342                write_type: NaWriteType::Allocate,
1343                read: VTimestamp::ZERO,
1344            };
1345            // There might already be an entry in the map for this, if the local was previously
1346            // live already.
1347            clocks.insert(local, new_clocks);
1348        } else {
1349            // This can fail to exist if `race_detecting` was false when the allocation
1350            // occurred, in which case we can backdate this to the beginning of time.
1351            let clocks = clocks.entry(local).or_default();
1352            clocks.write = thread_clocks.clock[index];
1353            clocks.write_type = NaWriteType::Write;
1354        }
1355    }
1356
1357    pub fn local_read(&self, local: mir::Local, machine: &MiriMachine<'_>) {
1358        let current_span = machine.current_user_relevant_span();
1359        let global = machine.data_race.as_vclocks_ref().unwrap();
1360        if !global.race_detecting() {
1361            return;
1362        }
1363        let (index, mut thread_clocks) = global.active_thread_state_mut(&machine.threads);
1364        // This should do the same things as `MemoryCellClocks::read_race_detect`.
1365        if !current_span.is_dummy() {
1366            thread_clocks.clock.index_mut(index).span = current_span;
1367        }
1368        thread_clocks.clock.index_mut(index).set_read_type(NaReadType::Read);
1369        // This can fail to exist if `race_detecting` was false when the allocation
1370        // occurred, in which case we can backdate this to the beginning of time.
1371        let mut clocks = self.local_clocks.borrow_mut();
1372        let clocks = clocks.entry(local).or_default();
1373        clocks.read = thread_clocks.clock[index];
1374    }
1375
1376    pub fn local_moved_to_memory(
1377        &self,
1378        local: mir::Local,
1379        alloc: &mut VClockAlloc,
1380        machine: &MiriMachine<'_>,
1381    ) {
1382        let global = machine.data_race.as_vclocks_ref().unwrap();
1383        if !global.race_detecting() {
1384            return;
1385        }
1386        let (index, _thread_clocks) = global.active_thread_state_mut(&machine.threads);
1387        // Get the time the last write actually happened. This can fail to exist if
1388        // `race_detecting` was false when the write occurred, in that case we can backdate this
1389        // to the beginning of time.
1390        let local_clocks = self.local_clocks.borrow_mut().remove(&local).unwrap_or_default();
1391        for (_mem_clocks_range, mem_clocks) in alloc.alloc_ranges.get_mut().iter_mut_all() {
1392            // The initialization write for this already happened, just at the wrong timestamp.
1393            // Check that the thread index matches what we expect.
1394            assert_eq!(mem_clocks.write.0, index);
1395            // Convert the local's clocks into memory clocks.
1396            mem_clocks.write = (index, local_clocks.write);
1397            mem_clocks.write_type = local_clocks.write_type;
1398            mem_clocks.read = VClock::new_with_index(index, local_clocks.read);
1399        }
1400    }
1401}
1402
1403impl<'tcx> EvalContextPrivExt<'tcx> for MiriInterpCx<'tcx> {}
1404trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> {
1405    /// Temporarily allow data-races to occur. This should only be used in
1406    /// one of these cases:
1407    /// - One of the appropriate `validate_atomic` functions will be called to
1408    ///   treat a memory access as atomic.
1409    /// - The memory being accessed should be treated as internal state, that
1410    ///   cannot be accessed by the interpreted program.
1411    /// - Execution of the interpreted program execution has halted.
1412    #[inline]
1413    fn allow_data_races_ref<R>(&self, op: impl FnOnce(&MiriInterpCx<'tcx>) -> R) -> R {
1414        let this = self.eval_context_ref();
1415        this.machine.data_race.set_ongoing_action_data_race_free(true);
1416        let result = op(this);
1417        this.machine.data_race.set_ongoing_action_data_race_free(false);
1418        result
1419    }
1420
1421    /// Same as `allow_data_races_ref`, this temporarily disables any data-race detection and
1422    /// so should only be used for atomic operations or internal state that the program cannot
1423    /// access.
1424    #[inline]
1425    fn allow_data_races_mut<R>(&mut self, op: impl FnOnce(&mut MiriInterpCx<'tcx>) -> R) -> R {
1426        let this = self.eval_context_mut();
1427        this.machine.data_race.set_ongoing_action_data_race_free(true);
1428        let result = op(this);
1429        this.machine.data_race.set_ongoing_action_data_race_free(false);
1430        result
1431    }
1432
1433    /// Checks that an atomic access is legal at the given place.
1434    fn atomic_access_check(
1435        &self,
1436        place: &MPlaceTy<'tcx>,
1437        access_type: AtomicAccessType,
1438    ) -> InterpResult<'tcx> {
1439        let this = self.eval_context_ref();
1440        // Check alignment requirements. Atomics must always be aligned to their size,
1441        // even if the type they wrap would be less aligned (e.g. AtomicU64 on 32bit must
1442        // be 8-aligned).
1443        let align = Align::from_bytes(place.layout.size.bytes()).unwrap();
1444        this.check_ptr_align(place.ptr(), align)?;
1445        // Ensure the allocation is mutable. Even failing (read-only) compare_exchange need mutable
1446        // memory on many targets (i.e., they segfault if that memory is mapped read-only), and
1447        // atomic loads can be implemented via compare_exchange on some targets. There could
1448        // possibly be some very specific exceptions to this, see
1449        // <https://github.com/rust-lang/miri/pull/2464#discussion_r939636130> for details.
1450        // We avoid `get_ptr_alloc` since we do *not* want to run the access hooks -- the actual
1451        // access will happen later.
1452        let (alloc_id, _offset, _prov) = this
1453            .ptr_try_get_alloc_id(place.ptr(), 0)
1454            .expect("there are no zero-sized atomic accesses");
1455        if this.get_alloc_mutability(alloc_id)? == Mutability::Not {
1456            // See if this is fine.
1457            match access_type {
1458                AtomicAccessType::Rmw | AtomicAccessType::Store => {
1459                    throw_ub_format!(
1460                        "atomic store and read-modify-write operations cannot be performed on read-only memory\n\
1461                        see <https://doc.rust-lang.org/nightly/std/sync/atomic/index.html#atomic-accesses-to-read-only-memory> for more information"
1462                    );
1463                }
1464                AtomicAccessType::Load(_)
1465                    if place.layout.size > this.tcx.data_layout().pointer_size() =>
1466                {
1467                    throw_ub_format!(
1468                        "large atomic load operations cannot be performed on read-only memory\n\
1469                        these operations often have to be implemented using read-modify-write operations, which require writeable memory\n\
1470                        see <https://doc.rust-lang.org/nightly/std/sync/atomic/index.html#atomic-accesses-to-read-only-memory> for more information"
1471                    );
1472                }
1473                AtomicAccessType::Load(o) if o != AtomicReadOrd::Relaxed => {
1474                    throw_ub_format!(
1475                        "non-relaxed atomic load operations cannot be performed on read-only memory\n\
1476                        these operations sometimes have to be implemented using read-modify-write operations, which require writeable memory\n\
1477                        see <https://doc.rust-lang.org/nightly/std/sync/atomic/index.html#atomic-accesses-to-read-only-memory> for more information"
1478                    );
1479                }
1480                _ => {
1481                    // Large relaxed loads are fine!
1482                }
1483            }
1484        }
1485        interp_ok(())
1486    }
1487
1488    /// Update the data-race detector for an atomic read occurring at the
1489    /// associated memory-place and on the current thread.
1490    fn validate_atomic_load(
1491        &self,
1492        place: &MPlaceTy<'tcx>,
1493        atomic: AtomicReadOrd,
1494        sync_clock: Option<&VClock>,
1495    ) -> InterpResult<'tcx> {
1496        let this = self.eval_context_ref();
1497        this.validate_atomic_op(
1498            place,
1499            atomic,
1500            AccessType::AtomicLoad,
1501            move |memory, clocks, index, atomic| {
1502                if atomic == AtomicReadOrd::Relaxed {
1503                    memory.load_relaxed(&mut *clocks, index, place.layout.size, sync_clock)
1504                } else {
1505                    memory.load_acquire(&mut *clocks, index, place.layout.size, sync_clock)
1506                }
1507            },
1508        )
1509    }
1510
1511    /// Update the data-race detector for an atomic write occurring at the
1512    /// associated memory-place and on the current thread.
1513    fn validate_atomic_store(
1514        &mut self,
1515        place: &MPlaceTy<'tcx>,
1516        atomic: AtomicWriteOrd,
1517    ) -> InterpResult<'tcx> {
1518        let this = self.eval_context_mut();
1519        this.validate_atomic_op(
1520            place,
1521            atomic,
1522            AccessType::AtomicStore,
1523            move |memory, clocks, index, atomic| {
1524                if atomic == AtomicWriteOrd::Relaxed {
1525                    memory.store_relaxed(clocks, index, place.layout.size)
1526                } else {
1527                    memory.store_release(clocks, index, place.layout.size)
1528                }
1529            },
1530        )
1531    }
1532
1533    /// Update the data-race detector for an atomic read-modify-write occurring
1534    /// at the associated memory place and on the current thread.
1535    fn validate_atomic_rmw(
1536        &mut self,
1537        place: &MPlaceTy<'tcx>,
1538        atomic: AtomicRwOrd,
1539    ) -> InterpResult<'tcx> {
1540        use AtomicRwOrd::*;
1541        let acquire = matches!(atomic, Acquire | AcqRel | SeqCst);
1542        let release = matches!(atomic, Release | AcqRel | SeqCst);
1543        let this = self.eval_context_mut();
1544        this.validate_atomic_op(
1545            place,
1546            atomic,
1547            AccessType::AtomicRmw,
1548            move |memory, clocks, index, _| {
1549                if acquire {
1550                    memory.load_acquire(clocks, index, place.layout.size, None)?;
1551                } else {
1552                    memory.load_relaxed(clocks, index, place.layout.size, None)?;
1553                }
1554                if release {
1555                    memory.rmw_release(clocks, index, place.layout.size)
1556                } else {
1557                    memory.rmw_relaxed(clocks, index, place.layout.size)
1558                }
1559            },
1560        )
1561    }
1562
1563    /// Generic atomic operation implementation
1564    fn validate_atomic_op<A: Debug + Copy>(
1565        &self,
1566        place: &MPlaceTy<'tcx>,
1567        atomic: A,
1568        access: AccessType,
1569        mut op: impl FnMut(
1570            &mut MemoryCellClocks,
1571            &mut ThreadClockSet,
1572            VectorIdx,
1573            A,
1574        ) -> Result<(), DataRace>,
1575    ) -> InterpResult<'tcx> {
1576        let this = self.eval_context_ref();
1577        assert!(access.is_atomic());
1578        let Some(data_race) = this.machine.data_race.as_vclocks_ref() else {
1579            return interp_ok(());
1580        };
1581        if !data_race.race_detecting() {
1582            return interp_ok(());
1583        }
1584        let size = place.layout.size;
1585        let (alloc_id, base_offset, _prov) = this.ptr_get_alloc_id(place.ptr(), 0)?;
1586        // Load and log the atomic operation.
1587        // Note that atomic loads are possible even from read-only allocations, so `get_alloc_extra_mut` is not an option.
1588        let alloc_meta = this.get_alloc_extra(alloc_id)?.data_race.as_vclocks_ref().unwrap();
1589        trace!(
1590            "Atomic op({}) with ordering {:?} on {:?} (size={})",
1591            access.description(None, None),
1592            &atomic,
1593            place.ptr(),
1594            size.bytes()
1595        );
1596
1597        let current_span = this.machine.current_user_relevant_span();
1598        // Perform the atomic operation.
1599        data_race.maybe_perform_sync_operation(
1600            &this.machine.threads,
1601            current_span,
1602            |index, mut thread_clocks| {
1603                for (mem_clocks_range, mem_clocks) in
1604                    alloc_meta.alloc_ranges.borrow_mut().iter_mut(base_offset, size)
1605                {
1606                    if let Err(DataRace) = op(mem_clocks, &mut thread_clocks, index, atomic) {
1607                        mem::drop(thread_clocks);
1608                        return VClockAlloc::report_data_race(
1609                            data_race,
1610                            &this.machine.threads,
1611                            mem_clocks,
1612                            access,
1613                            place.layout.size,
1614                            interpret::Pointer::new(
1615                                alloc_id,
1616                                Size::from_bytes(mem_clocks_range.start),
1617                            ),
1618                            None,
1619                        )
1620                        .map(|_| true);
1621                    }
1622                }
1623
1624                // This conservatively assumes all operations have release semantics
1625                interp_ok(true)
1626            },
1627        )?;
1628
1629        // Log changes to atomic memory.
1630        if tracing::enabled!(tracing::Level::TRACE) {
1631            for (_offset, mem_clocks) in alloc_meta.alloc_ranges.borrow().iter(base_offset, size) {
1632                trace!(
1633                    "Updated atomic memory({:?}, size={}) to {:#?}",
1634                    place.ptr(),
1635                    size.bytes(),
1636                    mem_clocks.atomic_ops
1637                );
1638            }
1639        }
1640
1641        interp_ok(())
1642    }
1643}
1644
1645impl GlobalState {
1646    /// Create a new global state, setup with just thread-id=0
1647    /// advanced to timestamp = 1.
1648    pub fn new(config: &MiriConfig) -> Self {
1649        let mut global_state = GlobalState {
1650            multi_threaded: Cell::new(false),
1651            ongoing_action_data_race_free: Cell::new(false),
1652            vector_clocks: RefCell::new(IndexVec::new()),
1653            vector_info: RefCell::new(IndexVec::new()),
1654            thread_info: RefCell::new(IndexVec::new()),
1655            reuse_candidates: RefCell::new(FxHashSet::default()),
1656            last_sc_fence: RefCell::new(VClock::default()),
1657            last_sc_write_per_thread: RefCell::new(VClock::default()),
1658            track_outdated_loads: config.track_outdated_loads,
1659            weak_memory: config.weak_memory_emulation,
1660        };
1661
1662        // Setup the main-thread since it is not explicitly created:
1663        // uses vector index and thread-id 0.
1664        let index = global_state.vector_clocks.get_mut().push(ThreadClockSet::default());
1665        global_state.vector_info.get_mut().push(ThreadId::MAIN_THREAD);
1666        global_state
1667            .thread_info
1668            .get_mut()
1669            .push(ThreadExtraState { vector_index: Some(index), termination_vector_clock: None });
1670
1671        global_state
1672    }
1673
1674    // We perform data race detection when there are more than 1 active thread
1675    // and we have not temporarily disabled race detection to perform something
1676    // data race free
1677    fn race_detecting(&self) -> bool {
1678        self.multi_threaded.get() && !self.ongoing_action_data_race_free.get()
1679    }
1680
1681    pub fn ongoing_action_data_race_free(&self) -> bool {
1682        self.ongoing_action_data_race_free.get()
1683    }
1684
1685    // Try to find vector index values that can potentially be re-used
1686    // by a new thread instead of a new vector index being created.
1687    fn find_vector_index_reuse_candidate(&self) -> Option<VectorIdx> {
1688        let mut reuse = self.reuse_candidates.borrow_mut();
1689        let vector_clocks = self.vector_clocks.borrow();
1690        for &candidate in reuse.iter() {
1691            let target_timestamp = vector_clocks[candidate].clock[candidate];
1692            if vector_clocks.iter_enumerated().all(|(clock_idx, clock)| {
1693                // The thread happens before the clock, and hence cannot report
1694                // a data-race with this the candidate index.
1695                let no_data_race = clock.clock[candidate] >= target_timestamp;
1696
1697                // The vector represents a thread that has terminated and hence cannot
1698                // report a data-race with the candidate index.
1699                let vector_terminated = reuse.contains(&clock_idx);
1700
1701                // The vector index cannot report a race with the candidate index
1702                // and hence allows the candidate index to be re-used.
1703                no_data_race || vector_terminated
1704            }) {
1705                // All vector clocks for each vector index are equal to
1706                // the target timestamp, and the thread is known to have
1707                // terminated, therefore this vector clock index cannot
1708                // report any more data-races.
1709                assert!(reuse.remove(&candidate));
1710                return Some(candidate);
1711            }
1712        }
1713        None
1714    }
1715
1716    // Hook for thread creation, enabled multi-threaded execution and marks
1717    // the current thread timestamp as happening-before the current thread.
1718    #[inline]
1719    pub fn thread_created(
1720        &mut self,
1721        thread_mgr: &ThreadManager<'_>,
1722        thread: ThreadId,
1723        current_span: Span,
1724    ) {
1725        let current_index = self.active_thread_index(thread_mgr);
1726
1727        // Enable multi-threaded execution, there are now at least two threads
1728        // so data-races are now possible.
1729        self.multi_threaded.set(true);
1730
1731        // Load and setup the associated thread metadata
1732        let mut thread_info = self.thread_info.borrow_mut();
1733        thread_info.ensure_contains_elem(thread, Default::default);
1734
1735        // Assign a vector index for the thread, attempting to re-use an old
1736        // vector index that can no longer report any data-races if possible.
1737        let created_index = if let Some(reuse_index) = self.find_vector_index_reuse_candidate() {
1738            // Now re-configure the re-use candidate, increment the clock
1739            // for the new sync use of the vector.
1740            let vector_clocks = self.vector_clocks.get_mut();
1741            vector_clocks[reuse_index].increment_clock(reuse_index, current_span);
1742
1743            // Locate the old thread the vector was associated with and update
1744            // it to represent the new thread instead.
1745            let vector_info = self.vector_info.get_mut();
1746            let old_thread = vector_info[reuse_index];
1747            vector_info[reuse_index] = thread;
1748
1749            // Mark the thread the vector index was associated with as no longer
1750            // representing a thread index.
1751            thread_info[old_thread].vector_index = None;
1752
1753            reuse_index
1754        } else {
1755            // No vector re-use candidates available, instead create
1756            // a new vector index.
1757            let vector_info = self.vector_info.get_mut();
1758            vector_info.push(thread)
1759        };
1760
1761        trace!("Creating thread = {:?} with vector index = {:?}", thread, created_index);
1762
1763        // Mark the chosen vector index as in use by the thread.
1764        thread_info[thread].vector_index = Some(created_index);
1765
1766        // Create a thread clock set if applicable.
1767        let vector_clocks = self.vector_clocks.get_mut();
1768        if created_index == vector_clocks.next_index() {
1769            vector_clocks.push(ThreadClockSet::default());
1770        }
1771
1772        // Now load the two clocks and configure the initial state.
1773        let (current, created) = vector_clocks.pick2_mut(current_index, created_index);
1774
1775        // Join the created with current, since the current threads
1776        // previous actions happen-before the created thread.
1777        created.join_with(current);
1778
1779        // Advance both threads after the synchronized operation.
1780        // Both operations are considered to have release semantics.
1781        current.increment_clock(current_index, current_span);
1782        created.increment_clock(created_index, current_span);
1783    }
1784
1785    /// Hook on a thread join to update the implicit happens-before relation between the joined
1786    /// thread (the joinee, the thread that someone waited on) and the current thread (the joiner,
1787    /// the thread who was waiting).
1788    #[inline]
1789    pub fn thread_joined(&mut self, threads: &ThreadManager<'_>, joinee: ThreadId) {
1790        let thread_info = self.thread_info.borrow();
1791        let thread_info = &thread_info[joinee];
1792
1793        // Load the associated vector clock for the terminated thread.
1794        let join_clock = thread_info
1795            .termination_vector_clock
1796            .as_ref()
1797            .expect("joined with thread but thread has not terminated");
1798        // Acquire that into the current thread.
1799        self.acquire_clock(join_clock, threads);
1800
1801        // Check the number of live threads, if the value is 1
1802        // then test for potentially disabling multi-threaded execution.
1803        // This has to happen after `acquire_clock`, otherwise there'll always
1804        // be some thread that has not synchronized yet.
1805        if let Some(current_index) = thread_info.vector_index {
1806            if threads.get_live_thread_count() == 1 {
1807                let vector_clocks = self.vector_clocks.get_mut();
1808                // May potentially be able to disable multi-threaded execution.
1809                let current_clock = &vector_clocks[current_index];
1810                if vector_clocks
1811                    .iter_enumerated()
1812                    .all(|(idx, clocks)| clocks.clock[idx] <= current_clock.clock[idx])
1813                {
1814                    // All thread terminations happen-before the current clock
1815                    // therefore no data-races can be reported until a new thread
1816                    // is created, so disable multi-threaded execution.
1817                    self.multi_threaded.set(false);
1818                }
1819            }
1820        }
1821    }
1822
1823    /// On thread termination, the vector clock may be re-used
1824    /// in the future once all remaining thread-clocks catch
1825    /// up with the time index of the terminated thread.
1826    /// This assigns thread termination with a unique index
1827    /// which will be used to join the thread
1828    /// This should be called strictly before any calls to
1829    /// `thread_joined`.
1830    #[inline]
1831    pub fn thread_terminated(&mut self, thread_mgr: &ThreadManager<'_>) {
1832        let current_thread = thread_mgr.active_thread();
1833        let current_index = self.active_thread_index(thread_mgr);
1834
1835        // Store the terminaion clock.
1836        let terminaion_clock = self.release_clock(thread_mgr, |clock| clock.clone());
1837        self.thread_info.get_mut()[current_thread].termination_vector_clock =
1838            Some(terminaion_clock);
1839
1840        // Add this thread's clock index as a candidate for re-use.
1841        let reuse = self.reuse_candidates.get_mut();
1842        reuse.insert(current_index);
1843    }
1844
1845    /// Update the data-race detector for an atomic fence on the current thread.
1846    fn atomic_fence<'tcx>(
1847        &self,
1848        machine: &MiriMachine<'tcx>,
1849        atomic: AtomicFenceOrd,
1850    ) -> InterpResult<'tcx> {
1851        let current_span = machine.current_user_relevant_span();
1852        self.maybe_perform_sync_operation(&machine.threads, current_span, |index, mut clocks| {
1853            trace!("Atomic fence on {:?} with ordering {:?}", index, atomic);
1854
1855            // Apply data-race detection for the current fences
1856            // this treats AcqRel and SeqCst as the same as an acquire
1857            // and release fence applied in the same timestamp.
1858            if atomic != AtomicFenceOrd::Release {
1859                // Either Acquire | AcqRel | SeqCst
1860                clocks.apply_acquire_fence();
1861            }
1862            if atomic == AtomicFenceOrd::SeqCst {
1863                // Behave like an RMW on the global fence location. This takes full care of
1864                // all the SC fence requirements, including C++17 ยง32.4 [atomics.order]
1865                // paragraph 6 (which would limit what future reads can see). It also rules
1866                // out many legal behaviors, but we don't currently have a model that would
1867                // be more precise.
1868                // Also see the second bullet on page 10 of
1869                // <https://www.cs.tau.ac.il/~orilahav/papers/popl21_robustness.pdf>.
1870                let mut sc_fence_clock = self.last_sc_fence.borrow_mut();
1871                sc_fence_clock.join(&clocks.clock);
1872                clocks.clock.join(&sc_fence_clock);
1873                // Also establish some sort of order with the last SC write that happened, globally
1874                // (but this is only respected by future reads).
1875                clocks.write_seqcst.join(&self.last_sc_write_per_thread.borrow());
1876            }
1877            // The release fence is last, since both of the above could alter our clock,
1878            // which should be part of what is being released.
1879            if atomic != AtomicFenceOrd::Acquire {
1880                // Either Release | AcqRel | SeqCst
1881                clocks.apply_release_fence();
1882            }
1883
1884            // Increment timestamp in case of release semantics.
1885            interp_ok(atomic != AtomicFenceOrd::Acquire)
1886        })
1887    }
1888
1889    /// Attempt to perform a synchronized operation, this
1890    /// will perform no operation if multi-threading is
1891    /// not currently enabled.
1892    /// Otherwise it will increment the clock for the current
1893    /// vector before and after the operation for data-race
1894    /// detection between any happens-before edges the
1895    /// operation may create.
1896    fn maybe_perform_sync_operation<'tcx>(
1897        &self,
1898        thread_mgr: &ThreadManager<'_>,
1899        current_span: Span,
1900        op: impl FnOnce(VectorIdx, RefMut<'_, ThreadClockSet>) -> InterpResult<'tcx, bool>,
1901    ) -> InterpResult<'tcx> {
1902        if self.multi_threaded.get() {
1903            let (index, clocks) = self.active_thread_state_mut(thread_mgr);
1904            if op(index, clocks)? {
1905                let (_, mut clocks) = self.active_thread_state_mut(thread_mgr);
1906                clocks.increment_clock(index, current_span);
1907            }
1908        }
1909        interp_ok(())
1910    }
1911
1912    /// Internal utility to identify a thread stored internally
1913    /// returns the id and the name for better diagnostics.
1914    fn print_thread_metadata(&self, thread_mgr: &ThreadManager<'_>, vector: VectorIdx) -> String {
1915        let thread = self.vector_info.borrow()[vector];
1916        let thread_name = thread_mgr.get_thread_display_name(thread);
1917        format!("thread `{thread_name}`")
1918    }
1919
1920    /// Acquire the given clock into the current thread, establishing synchronization with
1921    /// the moment when that clock snapshot was taken via `release_clock`.
1922    /// As this is an acquire operation, the thread timestamp is not
1923    /// incremented.
1924    pub fn acquire_clock<'tcx>(&self, clock: &VClock, threads: &ThreadManager<'tcx>) {
1925        let thread = threads.active_thread();
1926        let (_, mut clocks) = self.thread_state_mut(thread);
1927        clocks.clock.join(clock);
1928    }
1929
1930    /// Calls the given closure with the "release" clock of the current thread.
1931    /// Other threads can acquire this clock in the future to establish synchronization
1932    /// with this program point.
1933    pub fn release_clock<'tcx, R>(
1934        &self,
1935        threads: &ThreadManager<'tcx>,
1936        callback: impl FnOnce(&VClock) -> R,
1937    ) -> R {
1938        let thread = threads.active_thread();
1939        let span = threads.active_thread_ref().current_user_relevant_span();
1940        let (index, mut clocks) = self.thread_state_mut(thread);
1941        let r = callback(&clocks.clock);
1942        // Increment the clock, so that all following events cannot be confused with anything that
1943        // occurred before the release. Crucially, the callback is invoked on the *old* clock!
1944        clocks.increment_clock(index, span);
1945
1946        r
1947    }
1948
1949    fn thread_index(&self, thread: ThreadId) -> VectorIdx {
1950        self.thread_info.borrow()[thread].vector_index.expect("thread has no assigned vector")
1951    }
1952
1953    /// Load the vector index used by the given thread as well as the set of vector clocks
1954    /// used by the thread.
1955    #[inline]
1956    fn thread_state_mut(&self, thread: ThreadId) -> (VectorIdx, RefMut<'_, ThreadClockSet>) {
1957        let index = self.thread_index(thread);
1958        let ref_vector = self.vector_clocks.borrow_mut();
1959        let clocks = RefMut::map(ref_vector, |vec| &mut vec[index]);
1960        (index, clocks)
1961    }
1962
1963    /// Load the vector index used by the given thread as well as the set of vector clocks
1964    /// used by the thread.
1965    #[inline]
1966    fn thread_state(&self, thread: ThreadId) -> (VectorIdx, Ref<'_, ThreadClockSet>) {
1967        let index = self.thread_index(thread);
1968        let ref_vector = self.vector_clocks.borrow();
1969        let clocks = Ref::map(ref_vector, |vec| &vec[index]);
1970        (index, clocks)
1971    }
1972
1973    /// Load the current vector clock in use and the current set of thread clocks
1974    /// in use for the vector.
1975    #[inline]
1976    pub(super) fn active_thread_state(
1977        &self,
1978        thread_mgr: &ThreadManager<'_>,
1979    ) -> (VectorIdx, Ref<'_, ThreadClockSet>) {
1980        self.thread_state(thread_mgr.active_thread())
1981    }
1982
1983    /// Load the current vector clock in use and the current set of thread clocks
1984    /// in use for the vector mutably for modification.
1985    #[inline]
1986    pub(super) fn active_thread_state_mut(
1987        &self,
1988        thread_mgr: &ThreadManager<'_>,
1989    ) -> (VectorIdx, RefMut<'_, ThreadClockSet>) {
1990        self.thread_state_mut(thread_mgr.active_thread())
1991    }
1992
1993    /// Return the current thread, should be the same
1994    /// as the data-race active thread.
1995    #[inline]
1996    fn active_thread_index(&self, thread_mgr: &ThreadManager<'_>) -> VectorIdx {
1997        let active_thread_id = thread_mgr.active_thread();
1998        self.thread_index(active_thread_id)
1999    }
2000
2001    // SC ATOMIC STORE rule in the paper.
2002    pub(super) fn sc_write(&self, thread_mgr: &ThreadManager<'_>) {
2003        let (index, clocks) = self.active_thread_state(thread_mgr);
2004        self.last_sc_write_per_thread.borrow_mut().set_at_index(&clocks.clock, index);
2005    }
2006
2007    // SC ATOMIC READ rule in the paper.
2008    pub(super) fn sc_read(&self, thread_mgr: &ThreadManager<'_>) {
2009        let (.., mut clocks) = self.active_thread_state_mut(thread_mgr);
2010        clocks.read_seqcst.join(&self.last_sc_fence.borrow());
2011    }
2012}