miri/concurrency/weak_memory.rs
1//! Implementation of C++11-consistent weak memory emulation using store buffers
2//! based on Dynamic Race Detection for C++ ("the paper"):
3//! <https://www.doc.ic.ac.uk/~afd/homepages/papers/pdfs/2017/POPL.pdf>
4//!
5//! This implementation will never generate weak memory behaviours forbidden by the C++11 model,
6//! but it is incapable of producing all possible weak behaviours allowed by the model. There are
7//! certain weak behaviours observable on real hardware but not while using this.
8//!
9//! Note that this implementation does not fully take into account of C++20's memory model revision to SC accesses
10//! and fences introduced by P0668 (<https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0668r5.html>).
11//! This implementation is not fully correct under the revised C++20 model and may generate behaviours C++20
12//! disallows (<https://github.com/rust-lang/miri/issues/2301>).
13//!
14//! Modifications are made to the paper's model to address C++20 changes:
15//! - If an SC load reads from an atomic store of any ordering, then a later SC load cannot read
16//! from an earlier store in the location's modification order. This is to prevent creating a
17//! backwards S edge from the second load to the first, as a result of C++20's coherence-ordered
18//! before rules. (This seems to rule out behaviors that were actually permitted by the RC11 model
19//! that C++20 intended to copy (<https://plv.mpi-sws.org/scfix/paper.pdf>); a change was
20//! introduced when translating the math to English. According to Viktor Vafeiadis, this
21//! difference is harmless. So we stick to what the standard says, and allow fewer behaviors.)
22//! - If an SC store happens after a load (of any ordering), then the existing store (of any ordering)
23//! seen by the load is marked as an SC store. (The paper's model only marks stores that happen-before
24//! an SC store as SC.)
25//! - SC fences are treated like AcqRel RMWs to a global clock, to ensure they induce enough
26//! synchronization with the surrounding accesses. This rules out legal behavior, but it is really
27//! hard to be more precise here.
28//!
29//! Rust follows the C++20 memory model (except for the Consume ordering and some operations not performable through C++'s
30//! `std::atomic<T>` API). It is therefore possible for this implementation to generate behaviours never observable when the
31//! same program is compiled and run natively. Unfortunately, no literature exists at the time of writing which proposes
32//! an implementable and C++20-compatible relaxed memory model that supports all atomic operation existing in Rust. The closest one is
33//! A Promising Semantics for Relaxed-Memory Concurrency by Jeehoon Kang et al. (<https://www.cs.tau.ac.il/~orilahav/papers/popl17.pdf>)
34//! However, this model lacks SC accesses and is therefore unusable by Miri (SC accesses are everywhere in library code).
35//!
36//! If you find anything that proposes a relaxed memory model that is C++20-consistent, supports all orderings Rust's atomic accesses
37//! and fences accept, and is implementable (with operational semantics), please open a GitHub issue!
38//!
39//! One characteristic of this implementation, in contrast to some other notable operational models such as ones proposed in
40//! Taming Release-Acquire Consistency by Ori Lahav et al. (<https://plv.mpi-sws.org/sra/paper.pdf>) or Promising Semantics noted above,
41//! is that this implementation does not require each thread to hold an isolated view of the entire memory. Here, store buffers are per-location
42//! and shared across all threads. This is more memory efficient but does require store elements (representing writes to a location) to record
43//! information about reads, whereas in the other two models it is the other way round: reads points to the write it got its value from.
44//! Additionally, writes in our implementation do not have globally unique timestamps attached. In the other two models this timestamp is
45//! used to make sure a value in a thread's view is not overwritten by a write that occurred earlier than the one in the existing view.
46//! In our implementation, this is detected using read information attached to store elements, as there is no data structure representing reads.
47//!
48//! The C++ memory model is built around the notion of an 'atomic object', so it would be natural
49//! to attach store buffers to atomic objects. However, Rust follows LLVM in that it only has
50//! 'atomic accesses'. Therefore Miri cannot know when and where atomic 'objects' are being
51//! created or destroyed, to manage its store buffers. Instead, we hence lazily create an
52//! atomic object on the first atomic write to a given region, and we destroy that object
53//! on the next non-atomic or imperfectly overlapping atomic write to that region.
54//! These lazy (de)allocations happen in memory_accessed() on non-atomic accesses, and
55//! get_or_create_store_buffer_mut() on atomic writes.
56//!
57//! One consequence of this difference is that safe/sound Rust allows for more operations on atomic locations
58//! than the C++20 atomic API was intended to allow, such as non-atomically accessing
59//! a previously atomically accessed location, or accessing previously atomically accessed locations with a differently sized operation
60//! (such as accessing the top 16 bits of an AtomicU32). These scenarios are generally undiscussed in formalizations of C++ memory model.
61//! In Rust, these operations can only be done through a `&mut AtomicFoo` reference or one derived from it, therefore these operations
62//! can only happen after all previous accesses on the same locations. This implementation is adapted to allow these operations.
63//! A mixed atomicity read that races with writes, or a write that races with reads or writes will still cause UBs to be thrown.
64//! Mixed size atomic accesses must not race with any other atomic access, whether read or write, or a UB will be thrown.
65//! You can refer to test cases in tests/pass/0weak_memory/extra_cpp.rs for examples of these operations.
66
67// Our and the author's own implementation (tsan11) of the paper have some deviations from the provided operational semantics in §5.3:
68// 1. In the operational semantics, loads acquire the vector clock of the atomic location
69// irrespective of which store buffer element is loaded. That's incorrect; the synchronization clock
70// needs to be tracked per-store-buffer-element. (The paper has a field "clocks" for that purpose,
71// but it is not actuallt used.) tsan11 does this correctly
72// (https://github.com/ChrisLidbury/tsan11/blob/ecbd6b81e9b9454e01cba78eb9d88684168132c7/lib/tsan/rtl/tsan_relaxed.cc#L305).
73//
74// 2. In the operational semantics, each store element keeps the timestamp of a thread when it loads from the store.
75// If the same thread loads from the same store element multiple times, then the timestamps at all loads are saved in a list of load elements.
76// This is not necessary as later loads by the same thread will always have greater timestamp values, so we only need to record the timestamp of the first
77// load by each thread. This optimisation is done in tsan11
78// (https://github.com/ChrisLidbury/tsan11/blob/ecbd6b81e9b9454e01cba78eb9d88684168132c7/lib/tsan/rtl/tsan_relaxed.h#L35-L37)
79// and here.
80//
81// 3. §4.5 of the paper wants an SC store to mark all existing stores in the buffer that happens before it
82// as SC. This is not done in the operational semantics but implemented correctly in tsan11
83// (https://github.com/ChrisLidbury/tsan11/blob/ecbd6b81e9b9454e01cba78eb9d88684168132c7/lib/tsan/rtl/tsan_relaxed.cc#L160-L167).
84// On top of this we've added a C++20 change: if the current SC store happens after a load, then the store seen by that load
85// is marked SC.
86//
87// 4. W_SC ; R_SC case requires the SC load to ignore all but last store marked SC (stores not marked SC are not
88// affected). But this rule is applied to all loads in ReadsFromSet from the paper (last two lines of code), not just SC load.
89// This is implemented correctly in tsan11
90// (https://github.com/ChrisLidbury/tsan11/blob/ecbd6b81e9b9454e01cba78eb9d88684168132c7/lib/tsan/rtl/tsan_relaxed.cc#L295)
91// and here.
92
93use std::cell::{Ref, RefCell};
94use std::collections::VecDeque;
95
96use rustc_data_structures::fx::FxHashMap;
97
98use super::AllocDataRaceHandler;
99use super::data_race::{GlobalState as DataRaceState, ThreadClockSet};
100use super::vector_clock::{VClock, VTimestamp, VectorIdx};
101use crate::concurrency::GlobalDataRaceHandler;
102use crate::data_structures::range_object_map::{AccessType, RangeObjectMap};
103use crate::*;
104
105pub type AllocState = StoreBufferAlloc;
106
107// Each store buffer must be bounded otherwise it will grow indefinitely.
108// However, bounding the store buffer means restricting the amount of weak
109// behaviours observable. The author picked 128 as a good tradeoff
110// so we follow them here.
111const STORE_BUFFER_LIMIT: usize = 128;
112
113#[derive(Debug, Clone)]
114pub struct StoreBufferAlloc {
115 /// Store buffer of each atomic object in this allocation
116 // Behind a RefCell because we need to allocate/remove on read access
117 store_buffers: RefCell<RangeObjectMap<StoreBuffer>>,
118}
119
120impl VisitProvenance for StoreBufferAlloc {
121 fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
122 let Self { store_buffers } = self;
123 for val in store_buffers
124 .borrow()
125 .iter()
126 .flat_map(|buf| buf.buffer.iter().map(|element| &element.val))
127 {
128 val.visit_provenance(visit);
129 }
130 }
131}
132
133#[derive(Debug, Clone, PartialEq, Eq)]
134pub(super) struct StoreBuffer {
135 // Stores to this location in modification order
136 buffer: VecDeque<StoreElement>,
137}
138
139/// Whether a load returned the latest value or not.
140#[derive(PartialEq, Eq)]
141enum LoadRecency {
142 Latest,
143 Outdated,
144}
145
146#[derive(Debug, Clone, PartialEq, Eq)]
147struct StoreElement {
148 /// The thread that performed the store.
149 store_thread: VectorIdx,
150 /// The timestamp of the storing thread when it performed the store
151 store_timestamp: VTimestamp,
152
153 /// The vector clock that can be acquired by loading this store.
154 sync_clock: VClock,
155
156 /// Whether this store is SC. If a store happens-before or precedes in `mo` another SC store,
157 /// then it is also marked as SC.
158 is_seqcst: bool,
159
160 /// The value of this store. `None` means uninitialized.
161 // FIXME: Currently, we cannot represent partial initialization.
162 val: Option<Scalar>,
163
164 /// Metadata about loads from this store element,
165 /// behind a RefCell to keep load op take &self
166 load_info: RefCell<LoadInfo>,
167}
168
169#[derive(Debug, Clone, PartialEq, Eq, Default)]
170struct LoadInfo {
171 /// Timestamp of first loads from this store element by each thread.
172 timestamps: FxHashMap<VectorIdx, VTimestamp>,
173 /// Whether this store element has been read by an SC load.
174 /// This is crucial to ensure we respect coherence-ordered-before. Concretely we use
175 /// this to ensure that if a store element is seen by an SC load, then all later SC loads
176 /// cannot see `mo`-earlier store elements.
177 sc_loaded: bool,
178}
179
180impl StoreBufferAlloc {
181 pub fn new_allocation() -> Self {
182 Self { store_buffers: RefCell::new(RangeObjectMap::new()) }
183 }
184
185 /// When a non-atomic write happens on a location that has been atomically accessed
186 /// before without data race, we can determine that the non-atomic write fully happens
187 /// after all the prior atomic writes so the location no longer needs to exhibit
188 /// any weak memory behaviours until further atomic writes.
189 pub fn non_atomic_write(&self, range: AllocRange, global: &DataRaceState) {
190 if !global.ongoing_action_data_race_free() {
191 let mut buffers = self.store_buffers.borrow_mut();
192 let access_type = buffers.access_type(range);
193 match access_type {
194 AccessType::PerfectlyOverlapping(pos) => {
195 buffers.remove_from_pos(pos);
196 }
197 AccessType::ImperfectlyOverlapping(pos_range) => {
198 // We rely on the data-race check making sure this is synchronized.
199 // Therefore we can forget about the old data here.
200 buffers.remove_pos_range(pos_range);
201 }
202 AccessType::Empty(_) => {
203 // The range had no weak behaviours attached, do nothing
204 }
205 }
206 }
207 }
208
209 /// Gets a store buffer associated with an atomic object in this allocation.
210 /// Returns `None` if there is no store buffer.
211 fn get_store_buffer<'tcx>(
212 &self,
213 range: AllocRange,
214 ) -> InterpResult<'tcx, Option<Ref<'_, StoreBuffer>>> {
215 let access_type = self.store_buffers.borrow().access_type(range);
216 let AccessType::PerfectlyOverlapping(pos) = access_type else {
217 // If there is nothing here yet, that means there wasn't an atomic write yet so
218 // we can't return anything outdated.
219 return interp_ok(None);
220 };
221 let store_buffer =
222 Ref::map(self.store_buffers.borrow(), |buffer| buffer.get_pos(pos).unwrap());
223 interp_ok(Some(store_buffer))
224 }
225
226 /// Gets a mutable store buffer associated with an atomic object in this allocation,
227 /// or creates one with the specified initial value if no atomic object exists yet.
228 fn get_or_create_store_buffer_mut<'tcx>(
229 &mut self,
230 range: AllocRange,
231 init: Option<Scalar>,
232 ) -> InterpResult<'tcx, &mut StoreBuffer> {
233 let buffers = self.store_buffers.get_mut();
234 let access_type = buffers.access_type(range);
235 let pos = match access_type {
236 AccessType::PerfectlyOverlapping(pos) => pos,
237 AccessType::Empty(pos) => {
238 // We can use `init` for a new store buffer (with a default `sync_clock` that
239 // acquires nothing) because there was no data race and no previous atomic write
240 // either.
241 buffers.insert_at_pos(pos, range, StoreBuffer::new(init));
242 pos
243 }
244 AccessType::ImperfectlyOverlapping(pos_range) => {
245 // We can use `init` for a new store buffer (with a default `sync_clock` that
246 // acquires nothing) because there was no data race and all previous atomic writes
247 // are fully synchronized (as otherwise the imperfect overlap would be UB).
248 // It is tempting to try to sanity-check this against the data race clock view of
249 // whether there was any overlap, but that is very tricky: because we are skipping
250 // clock tracking until the first thread is spawned, we don't actually have a good
251 // view of whether a given atomic access "creates a new atomic object" or not.
252 // A sanity check would require is to always track clocks for atomic accesses, which
253 // does show up in benchmarks so we don't do it.
254 buffers.remove_pos_range(pos_range.clone());
255 buffers.insert_at_pos(pos_range.start, range, StoreBuffer::new(init));
256 pos_range.start
257 }
258 };
259 interp_ok(buffers.get_mut_pos(pos).unwrap())
260 }
261}
262
263impl<'tcx> StoreBuffer {
264 fn new(init: Option<Scalar>) -> Self {
265 let mut buffer = VecDeque::new();
266 let store_elem = StoreElement {
267 // The thread index and timestamp of the initialisation write
268 // are never meaningfully used, so it's fine to leave them as 0
269 store_thread: VectorIdx::from(0),
270 store_timestamp: VTimestamp::ZERO,
271 // The initialization write is non-atomic so nothing can be acquired.
272 sync_clock: VClock::default(),
273 val: init,
274 is_seqcst: false,
275 load_info: RefCell::new(LoadInfo::default()),
276 };
277 buffer.push_back(store_elem);
278 Self { buffer }
279 }
280
281 /// Reads from the last store in modification order, if any.
282 fn read_from_last_store(
283 &self,
284 global: &DataRaceState,
285 thread_mgr: &ThreadManager<'_>,
286 is_seqcst: bool,
287 ) {
288 let store_elem = self.buffer.back();
289 if let Some(store_elem) = store_elem {
290 let (index, clocks) = global.active_thread_state(thread_mgr);
291 store_elem.load_impl(index, &clocks, is_seqcst);
292 }
293 }
294
295 fn buffered_read(
296 &self,
297 global: &DataRaceState,
298 thread_mgr: &ThreadManager<'_>,
299 is_seqcst: bool,
300 rng: &mut (impl rand::Rng + ?Sized),
301 validate: impl FnOnce(Option<&VClock>) -> InterpResult<'tcx>,
302 ) -> InterpResult<'tcx, (Option<Scalar>, LoadRecency)> {
303 // Having a live borrow to store_buffer while calling validate_atomic_load is fine
304 // because the race detector doesn't touch store_buffer
305
306 let (store_elem, recency) = {
307 // The `clocks` we got here must be dropped before calling validate_atomic_load
308 // as the race detector will update it
309 let (.., clocks) = global.active_thread_state(thread_mgr);
310 // Load from a valid entry in the store buffer
311 self.fetch_store(is_seqcst, &clocks, &mut *rng)
312 };
313
314 // Unlike in buffered_atomic_write, thread clock updates have to be done
315 // after we've picked a store element from the store buffer, as presented
316 // in ATOMIC LOAD rule of the paper. This is because fetch_store
317 // requires access to ThreadClockSet.clock, which is updated by the race detector
318 validate(Some(&store_elem.sync_clock))?;
319
320 let (index, clocks) = global.active_thread_state(thread_mgr);
321 let loaded = store_elem.load_impl(index, &clocks, is_seqcst);
322 interp_ok((loaded, recency))
323 }
324
325 fn buffered_write(
326 &mut self,
327 val: Scalar,
328 global: &DataRaceState,
329 thread_mgr: &ThreadManager<'_>,
330 is_seqcst: bool,
331 sync_clock: VClock,
332 ) -> InterpResult<'tcx> {
333 let (index, clocks) = global.active_thread_state(thread_mgr);
334
335 self.store_impl(val, index, &clocks.clock, is_seqcst, sync_clock);
336 interp_ok(())
337 }
338
339 /// Selects a valid store element in the buffer.
340 fn fetch_store<R: rand::Rng + ?Sized>(
341 &self,
342 is_seqcst: bool,
343 clocks: &ThreadClockSet,
344 rng: &mut R,
345 ) -> (&StoreElement, LoadRecency) {
346 use rand::seq::IteratorRandom;
347 let mut found_sc = false;
348 // FIXME: we want an inclusive take_while (stops after a false predicate, but
349 // includes the element that gave the false), but such function doesn't yet
350 // exist in the standard library https://github.com/rust-lang/rust/issues/62208
351 // so we have to hack around it with keep_searching
352 let mut keep_searching = true;
353 let candidates = self
354 .buffer
355 .iter()
356 .rev()
357 .take_while(move |&store_elem| {
358 if !keep_searching {
359 return false;
360 }
361
362 keep_searching = if store_elem.store_timestamp
363 <= clocks.clock[store_elem.store_thread]
364 {
365 // CoWR: if a store happens-before the current load,
366 // then we can't read-from anything earlier in modification order.
367 // C++20 §6.9.2.2 [intro.races] paragraph 18
368 false
369 } else if store_elem.load_info.borrow().timestamps.iter().any(
370 |(&load_index, &load_timestamp)| load_timestamp <= clocks.clock[load_index],
371 ) {
372 // CoRR: if there was a load from this store which happened-before the current load,
373 // then we cannot read-from anything earlier in modification order.
374 // C++20 §6.9.2.2 [intro.races] paragraph 16
375 false
376 } else if store_elem.store_timestamp <= clocks.write_seqcst[store_elem.store_thread]
377 && store_elem.is_seqcst
378 {
379 // The current non-SC load, which may be sequenced-after an SC fence,
380 // cannot read-before the last SC store executed before the fence.
381 // C++17 §32.4 [atomics.order] paragraph 4
382 false
383 } else if is_seqcst
384 && store_elem.store_timestamp <= clocks.read_seqcst[store_elem.store_thread]
385 {
386 // The current SC load cannot read-from any but the last store sequenced-before
387 // the last SC fence.
388 // C++17 §32.4 [atomics.order] paragraph 5
389 false
390 } else if is_seqcst && store_elem.load_info.borrow().sc_loaded {
391 // The current SC load cannot read-before a store that an earlier SC load has observed.
392 // See https://github.com/rust-lang/miri/issues/2301#issuecomment-1222720427.
393 // Consequences of C++20 §31.4 [atomics.order] paragraph 3.1, 3.3 (coherence-ordered before)
394 // and 4.1 (coherence-ordered before between SC makes global total order S).
395 false
396 } else {
397 true
398 };
399
400 true
401 })
402 .filter(|&store_elem| {
403 if is_seqcst && store_elem.is_seqcst {
404 // An SC load needs to ignore all but last store marked SC (stores not marked SC are not
405 // affected)
406 let include = !found_sc;
407 found_sc = true;
408 include
409 } else {
410 true
411 }
412 });
413
414 let chosen = candidates.choose(rng).expect("store buffer cannot be empty");
415 if std::ptr::eq(chosen, self.buffer.back().expect("store buffer cannot be empty")) {
416 (chosen, LoadRecency::Latest)
417 } else {
418 (chosen, LoadRecency::Outdated)
419 }
420 }
421
422 /// ATOMIC STORE IMPL in the paper
423 fn store_impl(
424 &mut self,
425 val: Scalar,
426 index: VectorIdx,
427 thread_clock: &VClock,
428 is_seqcst: bool,
429 sync_clock: VClock,
430 ) {
431 let store_elem = StoreElement {
432 store_thread: index,
433 store_timestamp: thread_clock[index],
434 sync_clock,
435 // In the language provided in the paper, an atomic store takes the value from a
436 // non-atomic memory location.
437 // But we already have the immediate value here so we don't need to do the memory
438 // access.
439 val: Some(val),
440 is_seqcst,
441 load_info: RefCell::new(LoadInfo::default()),
442 };
443 if self.buffer.len() >= STORE_BUFFER_LIMIT {
444 self.buffer.pop_front();
445 }
446 self.buffer.push_back(store_elem);
447 if is_seqcst {
448 // Every store that happens-before or is coherence-ordered before the ongoing SC store
449 // needs to be marked as SC, so that in a later SC load, only the latest SC-marked store
450 // or unmarked stores can be picked.
451 self.buffer.iter_mut().rev().for_each(|elem| {
452 if elem.store_timestamp <= thread_clock[elem.store_thread] {
453 // This store happens-before the ongoing SC store.
454 elem.is_seqcst = true;
455 } else if elem
456 .load_info
457 .borrow()
458 .timestamps
459 .iter()
460 .any(|(&idx, &load_ts)| load_ts <= thread_clock[idx])
461 {
462 // This store has a load which happens before the ongoing store.
463 // This store must precede the onging store in modification order,
464 // and is therefore coherence-ordered before the ongoing SC store.
465 elem.is_seqcst = true;
466 }
467 })
468 }
469 }
470}
471
472impl StoreElement {
473 /// ATOMIC LOAD IMPL in the paper
474 /// Unlike the operational semantics in the paper, we don't need to keep track
475 /// of the thread timestamp for every single load. Keeping track of the first (smallest)
476 /// timestamp of each thread that has loaded from a store is sufficient: if the earliest
477 /// load of another thread happens before the current one, then we must stop searching the store
478 /// buffer regardless of subsequent loads by the same thread; if the earliest load of another
479 /// thread doesn't happen before the current one, then no subsequent load by the other thread
480 /// can happen before the current one.
481 fn load_impl(
482 &self,
483 index: VectorIdx,
484 clocks: &ThreadClockSet,
485 is_seqcst: bool,
486 ) -> Option<Scalar> {
487 let mut load_info = self.load_info.borrow_mut();
488 load_info.sc_loaded |= is_seqcst;
489 let _ = load_info.timestamps.try_insert(index, clocks.clock[index]);
490 self.val
491 }
492}
493
494impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
495pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
496 /// Update the store buffer after an RMW.
497 /// Does nothing if we are currently not detecting races (e.g. single-threaded mode).
498 fn buffered_atomic_rmw(
499 &mut self,
500 new_val: Scalar,
501 place: &MPlaceTy<'tcx>,
502 atomic: AtomicRwOrd,
503 init: Scalar,
504 ) -> InterpResult<'tcx> {
505 let ecx = self.eval_context_mut();
506 let (alloc_id, base_offset, ..) = ecx.ptr_get_alloc_id(place.ptr(), 0)?;
507 if let (
508 crate::AllocExtra {
509 data_race: AllocDataRaceHandler::Vclocks(data_race_clocks, Some(alloc_buffers)),
510 ..
511 },
512 crate::MiriMachine {
513 data_race: GlobalDataRaceHandler::Vclocks(global), threads, ..
514 },
515 ) = ecx.get_alloc_extra_mut(alloc_id)?
516 && global.race_detecting()
517 {
518 if atomic == AtomicRwOrd::SeqCst {
519 global.sc_read(threads);
520 global.sc_write(threads);
521 }
522 let range = alloc_range(base_offset, place.layout.size);
523 let sync_clock = data_race_clocks.sync_clock(range);
524 let buffer = alloc_buffers.get_or_create_store_buffer_mut(range, Some(init))?;
525 // The RMW always reads from the most recent store.
526 buffer.read_from_last_store(global, threads, atomic == AtomicRwOrd::SeqCst);
527 buffer.buffered_write(
528 new_val,
529 global,
530 threads,
531 atomic == AtomicRwOrd::SeqCst,
532 sync_clock,
533 )?;
534 }
535 interp_ok(())
536 }
537
538 /// Perform a store buffer load. Returning `None` indicates a read of uninitialized memory.
539 /// Does nothing if we are currently not detecting races (e.g. single-threaded mode).
540 ///
541 /// `latest_in_mo` must be the most recent value according to the mo relation.
542 /// The argument to `validate` is the synchronization clock of the memory that is being read,
543 /// if we are reading from a store buffer element.
544 fn buffered_atomic_read(
545 &self,
546 place: &MPlaceTy<'tcx>,
547 atomic: AtomicReadOrd,
548 latest_in_mo: Scalar,
549 validate: impl FnOnce(Option<&VClock>) -> InterpResult<'tcx>,
550 ) -> InterpResult<'tcx, Option<Scalar>> {
551 let ecx = self.eval_context_ref();
552 'fallback: {
553 if let Some(global) = ecx.machine.data_race.as_vclocks_ref()
554 && global.race_detecting()
555 {
556 let (alloc_id, base_offset, ..) = ecx.ptr_get_alloc_id(place.ptr(), 0)?;
557 if let Some(alloc_buffers) =
558 ecx.get_alloc_extra(alloc_id)?.data_race.as_weak_memory_ref()
559 {
560 if atomic == AtomicReadOrd::SeqCst {
561 global.sc_read(&ecx.machine.threads);
562 }
563 let mut rng = ecx.machine.rng.borrow_mut();
564 let Some(buffer) = alloc_buffers
565 .get_store_buffer(alloc_range(base_offset, place.layout.size))?
566 else {
567 // No old writes available, fall back to base case.
568 break 'fallback;
569 };
570 let (loaded, recency) = buffer.buffered_read(
571 global,
572 &ecx.machine.threads,
573 atomic == AtomicReadOrd::SeqCst,
574 &mut *rng,
575 validate,
576 )?;
577 if global.track_outdated_loads && recency == LoadRecency::Outdated {
578 ecx.emit_diagnostic(NonHaltingDiagnostic::WeakMemoryOutdatedLoad {
579 ptr: place.ptr(),
580 });
581 }
582
583 return interp_ok(loaded);
584 }
585 }
586 }
587
588 // Race detector or weak memory disabled, simply read the latest value
589 validate(None)?;
590 interp_ok(Some(latest_in_mo))
591 }
592
593 /// Add the given write to the store buffer. (Does not change machine memory.)
594 /// Must only be called after we determined that there is no data race or mixed-size race.
595 /// Does nothing if we are currently not detecting races (e.g. single-threaded mode).
596 ///
597 /// `init` says with which value to initialize the store buffer in case there wasn't a store
598 /// buffer for this memory range before. `None` means the memory does not contain a valid
599 /// scalar.
600 ///
601 /// Must be called *after* `validate_atomic_store` to ensure that `sync_clock` is up-to-date.
602 fn buffered_atomic_write(
603 &mut self,
604 val: Scalar,
605 dest: &MPlaceTy<'tcx>,
606 atomic: AtomicWriteOrd,
607 init: Option<Scalar>,
608 ) -> InterpResult<'tcx> {
609 let ecx = self.eval_context_mut();
610 let (alloc_id, base_offset, ..) = ecx.ptr_get_alloc_id(dest.ptr(), 0)?;
611 if let (
612 crate::AllocExtra {
613 data_race: AllocDataRaceHandler::Vclocks(data_race_clocks, Some(alloc_buffers)),
614 ..
615 },
616 crate::MiriMachine {
617 data_race: GlobalDataRaceHandler::Vclocks(global), threads, ..
618 },
619 ) = ecx.get_alloc_extra_mut(alloc_id)?
620 && global.race_detecting()
621 {
622 if atomic == AtomicWriteOrd::SeqCst {
623 global.sc_write(threads);
624 }
625
626 let range = alloc_range(base_offset, dest.layout.size);
627 // It's a bit annoying that we have to go back to the data race part to get the clock...
628 // but it does make things a lot simpler.
629 let sync_clock = data_race_clocks.sync_clock(range);
630 let buffer = alloc_buffers.get_or_create_store_buffer_mut(range, init)?;
631 buffer.buffered_write(
632 val,
633 global,
634 threads,
635 atomic == AtomicWriteOrd::SeqCst,
636 sync_clock,
637 )?;
638 }
639
640 // Caller should've written to dest with the vanilla scalar write, we do nothing here
641 interp_ok(())
642 }
643
644 /// Caller should never need to consult the store buffer for the latest value.
645 /// This function is used exclusively for failed atomic_compare_exchange_scalar
646 /// to perform load_impl on the latest store element
647 fn perform_read_on_buffered_latest(
648 &self,
649 place: &MPlaceTy<'tcx>,
650 atomic: AtomicReadOrd,
651 ) -> InterpResult<'tcx> {
652 let ecx = self.eval_context_ref();
653
654 if let Some(global) = ecx.machine.data_race.as_vclocks_ref() {
655 if atomic == AtomicReadOrd::SeqCst {
656 global.sc_read(&ecx.machine.threads);
657 }
658 let size = place.layout.size;
659 let (alloc_id, base_offset, ..) = ecx.ptr_get_alloc_id(place.ptr(), 0)?;
660 if let Some(alloc_buffers) =
661 ecx.get_alloc_extra(alloc_id)?.data_race.as_weak_memory_ref()
662 {
663 let Some(buffer) =
664 alloc_buffers.get_store_buffer(alloc_range(base_offset, size))?
665 else {
666 // No store buffer, nothing to do.
667 return interp_ok(());
668 };
669 buffer.read_from_last_store(
670 global,
671 &ecx.machine.threads,
672 atomic == AtomicReadOrd::SeqCst,
673 );
674 }
675 }
676 interp_ok(())
677 }
678}