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