Mark stores SC if it must precede the ongoing SC store in modification order
diff --git a/src/tools/miri/src/concurrency/weak_memory.rs b/src/tools/miri/src/concurrency/weak_memory.rs
index 3aded9a..b3124a3 100644
--- a/src/tools/miri/src/concurrency/weak_memory.rs
+++ b/src/tools/miri/src/concurrency/weak_memory.rs
@@ -19,6 +19,9 @@
 //!   that C++20 intended to copy (<https://plv.mpi-sws.org/scfix/paper.pdf>); a change was
 //!   introduced when translating the math to English. According to Viktor Vafeiadis, this
 //!   difference is harmless. So we stick to what the standard says, and allow fewer behaviors.)
+//! - If an SC store happens after a load (of any ordering), then the existing store (of any ordering)
+//!   seen by the load is marked as an SC store. (The paper's model only marks stores that happen-before
+//!   an SC store as SC.)
 //! - SC fences are treated like AcqRel RMWs to a global clock, to ensure they induce enough
 //!   synchronization with the surrounding accesses. This rules out legal behavior, but it is really
 //!   hard to be more precise here.
@@ -77,8 +80,9 @@
 //
 // 3. §4.5 of the paper wants an SC store to mark all existing stores in the buffer that happens before it
 // as SC. This is not done in the operational semantics but implemented correctly in tsan11
-// (https://github.com/ChrisLidbury/tsan11/blob/ecbd6b81e9b9454e01cba78eb9d88684168132c7/lib/tsan/rtl/tsan_relaxed.cc#L160-L167)
-// and here.
+// (https://github.com/ChrisLidbury/tsan11/blob/ecbd6b81e9b9454e01cba78eb9d88684168132c7/lib/tsan/rtl/tsan_relaxed.cc#L160-L167).
+// 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
+// is marked SC.
 //
 // 4. W_SC ; R_SC case requires the SC load to ignore all but last store marked SC (stores not marked SC are not
 // affected). But this rule is applied to all loads in ReadsFromSet from the paper (last two lines of code), not just SC load.
@@ -149,7 +153,8 @@ struct StoreElement {
     /// The vector clock that can be acquired by loading this store.
     sync_clock: VClock,
 
-    /// Whether this store is SC.
+    /// Whether this store is SC. If a store happens-before or precedes in `mo` another SC store,
+    /// then it is also marked as SC.
     is_seqcst: bool,
 
     /// The value of this store. `None` means uninitialized.
@@ -371,7 +376,7 @@ fn fetch_store<R: rand::Rng + ?Sized>(
                 } else if is_seqcst
                     && store_elem.store_timestamp <= clocks.read_seqcst[store_elem.store_thread]
                 {
-                    // The current SC load cannot read-before the last store sequenced-before
+                    // The current SC load cannot read-from any but the last store sequenced-before
                     // the last SC fence.
                     // C++17 §32.4 [atomics.order] paragraph 5
                     false
@@ -433,11 +438,23 @@ fn store_impl(
         }
         self.buffer.push_back(store_elem);
         if is_seqcst {
-            // Every store that happens before this needs to be marked as SC
-            // so that in a later SC load, only the last SC store (i.e. this one) or stores that
-            // aren't ordered by hb with the last SC is picked.
+            // Every store that happens-before or is coherence-ordered before the ongoing SC store
+            // needs to be marked as SC, so that in a later SC load, only the latest SC-marked store
+            // or unmarked stores can be picked.
             self.buffer.iter_mut().rev().for_each(|elem| {
                 if elem.store_timestamp <= thread_clock[elem.store_thread] {
+                    // This store happens-before the ongoing SC store.
+                    elem.is_seqcst = true;
+                } else if elem
+                    .load_info
+                    .borrow()
+                    .timestamps
+                    .iter()
+                    .any(|(&idx, &load_ts)| load_ts <= thread_clock[idx])
+                {
+                    // This store has a load which happens before the ongoing store.
+                    // This store must precede the onging store in modification order,
+                    // and is therefore coherence-ordered before the ongoing SC store.
                     elem.is_seqcst = true;
                 }
             })
diff --git a/src/tools/miri/tests/pass/0weak_memory/consistency_sc.rs b/src/tools/miri/tests/pass/0weak_memory/consistency_sc.rs
index d92c0d1..74d3ea3 100644
--- a/src/tools/miri/tests/pass/0weak_memory/consistency_sc.rs
+++ b/src/tools/miri/tests/pass/0weak_memory/consistency_sc.rs
@@ -348,6 +348,85 @@ fn test_sc_relaxed() {
     assert!(!bad);
 }
 
+/// Test that coherence-ordered-before established by modification order prevents
+/// an SC load from reading before the last-in-mo store.
+/// Test by Joseph Perez (https://github.com/rust-lang/miri/issues/5104#issue-4644401606)
+fn test_cob_due_to_mo() {
+    let x = static_atomic(0);
+    let y = static_atomic(0);
+    x.store(0, Relaxed);
+    y.store(0, Relaxed);
+
+    let t1 = spawn(move || x.store(1, Release));
+    let t2 = spawn(move || {
+        let r0 = x.load(Relaxed);
+        x.store(2, SeqCst);
+        let r1 = y.load(SeqCst);
+        (r0, r1)
+    });
+    let t3 = spawn(move || {
+        y.store(1, SeqCst);
+        let r2 = x.load(SeqCst);
+        r2
+    });
+    let (r0, r1) = t2.join().unwrap();
+    let r2 = t3.join().unwrap();
+    t1.join().unwrap();
+    let bad = r0 == 1 && r1 == 0 && r2 == 1;
+    // This is bad because: T2's SC load of Y read 0, which means that
+    // T2's SC operations all precede T3's SC operations in the SC order.
+    // But in X's modification order, we must have T1's store before T2's store
+    // (since T2 observed a 1). As a result, T3's X.load reading from T1's store
+    // means it is coherence-ordered before and precedes in SC T2's X.store.
+    // This causes a cycle in SC.
+    assert!(!bad);
+}
+
+/// Variant of the test above, where the modification order is established by
+/// happens-before across three threads (t1, t2, and t3).
+fn test_cob_due_to_mo_hb() {
+    let x = static_atomic(0);
+    let y = static_atomic(0);
+    let gate = static_atomic(0);
+    x.store(0, Relaxed);
+    y.store(0, Relaxed);
+    gate.store(0, Relaxed);
+
+    let t1 = spawn(move || x.store(1, Release));
+    let t2 = spawn(move || {
+        let r0 = x.load(Acquire);
+        gate.store(1, Release);
+        r0
+    });
+
+    let t3 = spawn(move || {
+        while gate.load(Acquire) == 0 {}
+        x.store(2, SeqCst);
+        let r1 = y.load(SeqCst);
+        r1
+    });
+    let t4 = spawn(move || {
+        y.store(1, SeqCst);
+        let r2 = x.load(SeqCst);
+        r2
+    });
+
+    let r0 = t2.join().unwrap();
+    let r1 = t3.join().unwrap();
+    let r2 = t4.join().unwrap();
+    t1.join().unwrap();
+    let bad = r0 == 1 && r1 == 0 && r2 == 1;
+    // This is bad because: T3's SC load of Y read 0 means that
+    // T3's SC operations all precede T4's SC operations in the SC order.
+    // But in X's modification order, we must have T1's store before T3's store
+    // (since T1's store happens-before T3's store).
+    // As a result, T4's X.load reading from T1's store means it is
+    // coherence-ordered before and precedes in SC T3's X.store.
+    // This causes a cycle in SC.
+
+    assert!(!bad);
+}
+
 fn main() {
     for _ in 0..32 {
         test_sc_store_buffering();
@@ -359,5 +438,7 @@ fn main() {
         test_sc_fence_access_relacq();
         test_sc_multi_fence();
         test_sc_relaxed();
+        test_cob_due_to_mo();
+        test_cob_due_to_mo_hb();
     }
 }