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