//@compile-flags: -Zmiri-ignore-leaks -Zmiri-disable-stacked-borrows -Zmiri-disable-validation // This test's runtime explodes if the GC interval is set to 1 (which we do in CI), so we // override it internally back to the default frequency. //@compile-flags: -Zmiri-provenance-gc=10000 // The following tests check whether our weak memory emulation produces // any inconsistent execution outcomes // This file here focuses on SC accesses and fences. use std::sync::atomic::Ordering::*; use std::sync::atomic::{AtomicBool, AtomicI32, Ordering, fence}; use std::thread::spawn; // We can't create static items because we need to run each test // multiple times fn static_atomic(val: i32) -> &'static AtomicI32 { Box::leak(Box::new(AtomicI32::new(val))) } fn static_atomic_bool(val: bool) -> &'static AtomicBool { Box::leak(Box::new(AtomicBool::new(val))) } /// Spins until it acquires a pre-determined value. fn spin_until_i32(loc: &AtomicI32, ord: Ordering, val: i32) -> i32 { while loc.load(ord) != val { std::hint::spin_loop(); } val } /// Spins until it acquires a pre-determined boolean. fn spin_until_bool(loc: &AtomicBool, ord: Ordering, val: bool) -> bool { while loc.load(ord) != val { std::hint::spin_loop(); } val } // Test case SB taken from Repairing Sequential Consistency in C/C++11 // by Lahav et al. // https://plv.mpi-sws.org/scfix/paper.pdf fn test_sc_store_buffering() { let x = static_atomic(0); let y = static_atomic(0); let j1 = spawn(move || { x.store(1, SeqCst); y.load(SeqCst) }); let j2 = spawn(move || { y.store(1, SeqCst); x.load(SeqCst) }); let a = j1.join().unwrap(); let b = j2.join().unwrap(); assert_ne!((a, b), (0, 0)); } // Test case by @SabrinaJewson // https://github.com/rust-lang/miri/issues/2301#issuecomment-1221502757 // Demonstrating C++20 SC access changes fn test_iriw_sc_rlx() { let x = static_atomic_bool(false); let y = static_atomic_bool(false); let a = spawn(move || x.store(true, Relaxed)); let b = spawn(move || y.store(true, Relaxed)); let c = spawn(move || { spin_until_bool(x, SeqCst, true); y.load(SeqCst) }); let d = spawn(move || { spin_until_bool(y, SeqCst, true); x.load(SeqCst) }); a.join().unwrap(); b.join().unwrap(); let c = c.join().unwrap(); let d = d.join().unwrap(); assert!(c || d); } // Similar to `test_iriw_sc_rlx` but with fences instead of SC accesses. fn test_cpp20_sc_fence_fix() { let x = static_atomic_bool(false); let y = static_atomic_bool(false); let thread1 = spawn(|| { let a = x.load(Relaxed); fence(SeqCst); let b = y.load(Relaxed); (a, b) }); let thread2 = spawn(|| { x.store(true, Relaxed); }); let thread3 = spawn(|| { y.store(true, Relaxed); }); let thread4 = spawn(|| { let c = y.load(Relaxed); fence(SeqCst); let d = x.load(Relaxed); (c, d) }); let (a, b) = thread1.join().unwrap(); thread2.join().unwrap(); thread3.join().unwrap(); let (c, d) = thread4.join().unwrap(); let bad = a == true && b == false && c == true && d == false; assert!(!bad); } // https://plv.mpi-sws.org/scfix/paper.pdf // 2.2 Second Problem: SC Fences are Too Weak fn test_cpp20_rwc_syncs() { /* int main() { atomic_int x = 0; atomic_int y = 0; {{{ x.store(1,mo_relaxed); ||| { r1=x.load(mo_relaxed).readsvalue(1); fence(mo_seq_cst); r2=y.load(mo_relaxed); } ||| { y.store(1,mo_relaxed); fence(mo_seq_cst); r3=x.load(mo_relaxed); } }}} return 0; } */ let x = static_atomic(0); let y = static_atomic(0); let j1 = spawn(move || { x.store(1, Relaxed); }); let j2 = spawn(move || { spin_until_i32(&x, Relaxed, 1); fence(SeqCst); y.load(Relaxed) }); let j3 = spawn(move || { y.store(1, Relaxed); fence(SeqCst); x.load(Relaxed) }); j1.join().unwrap(); let b = j2.join().unwrap(); let c = j3.join().unwrap(); assert!((b, c) != (0, 0)); } /// This checks that the *last* thing the SC fence does is act like a release fence. /// See . /// Test by Ori Lahav. fn test_sc_fence_release() { let x = static_atomic(0); let y = static_atomic(0); let z = static_atomic(0); let k = static_atomic(0); let j1 = spawn(move || { x.store(1, Relaxed); fence(SeqCst); k.store(1, Relaxed); }); let j2 = spawn(move || { y.store(1, Relaxed); fence(SeqCst); z.store(1, Relaxed); }); let j3 = spawn(move || { let kval = k.load(Acquire); // bad case: loads 1 let yval = y.load(Relaxed); // bad case: loads 0 (kval, yval) }); let j4 = spawn(move || { let zval = z.load(Acquire); // bad case: loads 1 let xval = x.load(Relaxed); // bad case: loads 0 (zval, xval) }); j1.join().unwrap(); j2.join().unwrap(); let (kval, yval) = j3.join().unwrap(); let (zval, xval) = j4.join().unwrap(); let bad = kval == 1 && yval == 0 && zval == 1 && xval == 0; assert!(!bad); } /// Test that SC fences and accesses sync correctly with each other. /// Test by Ori Lahav. fn test_sc_fence_access() { /* Wx1 sc Ry0 sc || Wy1 rlx SC-fence Rx0 rlx */ let x = static_atomic(0); let y = static_atomic(0); let j1 = spawn(move || { x.store(1, SeqCst); y.load(SeqCst) }); let j2 = spawn(move || { y.store(1, Relaxed); fence(SeqCst); // If this sees a 0, the fence must have been *before* the x.store(1). x.load(Relaxed) }); let yval = j1.join().unwrap(); let xval = j2.join().unwrap(); let bad = yval == 0 && xval == 0; assert!(!bad); } /// Test that SC fences and accesses sync correctly with each other /// when mediated by a release-acquire pair. /// Test by Ori Lahav (https://github.com/rust-lang/miri/pull/4057#issuecomment-2525268730). fn test_sc_fence_access_relacq() { let x = static_atomic(0); let y = static_atomic(0); let z = static_atomic(0); let j1 = spawn(move || { x.store(1, SeqCst); y.load(SeqCst) // bad case: loads 0 }); let j2 = spawn(move || { y.store(1, Relaxed); z.store(1, Release) }); let j3 = spawn(move || { let zval = z.load(Acquire); // bad case: loads 1 // If we see 1 here, the rel-acq pair makes the fence happen after the z.store(1). fence(SeqCst); // If this sees a 0, the fence must have been *before* the x.store(1). let xval = x.load(Relaxed); // bad case: loads 0 (zval, xval) }); let yval = j1.join().unwrap(); j2.join().unwrap(); let (zval, xval) = j3.join().unwrap(); let bad = yval == 0 && zval == 1 && xval == 0; assert!(!bad); } /// A test that involves multiple SC fences and accesses. /// Test by Ori Lahav (https://github.com/rust-lang/miri/pull/4057#issuecomment-2525268730). fn test_sc_multi_fence() { let x = static_atomic(0); let y = static_atomic(0); let z = static_atomic(0); let j1 = spawn(move || { x.store(1, SeqCst); y.load(SeqCst) // bad case: loads 0 }); let j2 = spawn(move || { y.store(1, Relaxed); // In the bad case this fence is *after* the j1 y.load, since // otherwise that load would pick up the 1 we just stored. fence(SeqCst); z.load(Relaxed) // bad case: loads 0 }); let j3 = spawn(move || { z.store(1, Relaxed); }); let j4 = spawn(move || { let zval = z.load(Relaxed); // bad case: loads 1 // In the bad case this fence is *after* the one above since // otherwise, the j2 load of z would load 1. fence(SeqCst); // Since that fence is in turn after the j1 y.load, our fence is // after the j1 x.store, which means we must pick up that store. let xval = x.load(Relaxed); // bad case: loads 0 (zval, xval) }); let yval = j1.join().unwrap(); let zval1 = j2.join().unwrap(); j3.join().unwrap(); let (zval2, xval) = j4.join().unwrap(); let bad = yval == 0 && zval1 == 0 && zval2 == 1 && xval == 0; assert!(!bad); } fn test_sc_relaxed() { /* y:=1 rlx Fence sc a:=x rlx Fence acq b:=z rlx // 0 || z:=1 rlx x:=1 sc c:=y sc // 0 */ let x = static_atomic(0); let y = static_atomic(0); let z = static_atomic(0); let j1 = spawn(move || { y.store(1, Relaxed); fence(SeqCst); // If the relaxed load here is removed, then the "bad" behavior becomes allowed // by C++20 (and by RC11 / scfix as well). let _a = x.load(Relaxed); fence(Acquire); // If we see 0 here this means in some sense we are "before" the store to z below. let b = z.load(Relaxed); b }); let j2 = spawn(move || { z.store(1, Relaxed); x.store(1, SeqCst); // If we see 0 here, this means in some sense we are "before" the store to y above. let c = y.load(SeqCst); c }); let b = j1.join().unwrap(); let c = j2.join().unwrap(); let bad = b == 0 && c == 0; assert!(!bad); } pub fn main() { for _ in 0..32 { test_sc_store_buffering(); test_iriw_sc_rlx(); test_cpp20_sc_fence_fix(); test_cpp20_rwc_syncs(); test_sc_fence_release(); test_sc_fence_access(); test_sc_fence_access_relacq(); test_sc_multi_fence(); test_sc_relaxed(); } }