Must a remote reader force dirty data all the way to memory? MOESI allows a different settled result: C0 keeps dirty responsibility in O, C1 reads in S, and memory can remain stale. O stands for Owned, but it is a data obligation, not permission to store: C0 is the dirty-responsible holder. Prerequisites are the MSI dirty-read trace and the MESI distinction between cache state and H's exclusive record.
The Primer, second edition, section on MOESI and the Owned state explains dirty sharing. The comparison below classifies legal settled snapshots on our model; it is not an extra transaction. In all rows C0 previously wrote 1 after X initially held 0.
Protocol and settled snapshot | C0 | C1 | Memory | Who may store? |
|---|---|---|---|---|
MSI or MESI after dirty read | S / 1 | S / 1 | 1 | Neither cache |
MOESI after dirty read | O / 1 | S / 1 | 0 | Neither cache |
MOESI exclusive dirty checkpoint | M / 1 | I / - | 0 | C0 |
In O/S a new reader's data comes from C0, never from memory: H forwards each further GetS to the dirty-responsible holder, which supplies the current value and keeps its responsibility (the O self-loop).
Start from the M / 1, I / - checkpoint established by the cold-write trace in SWMR Precisely: memory = 0, a completed GetM transaction, then C0's local store of 1. That prefix also applies to MOESI. Now compare only the remote-read transaction. H forwards the request to C0; C0 stops writing, takes O, and sends the current value to H while retaining dirty responsibility. H then sends Data with S permission to C1 and does not write memory. The introductory version of this trace is in Dirty Responsibility; this one is the protocol reference with the same rows.
Step | C0 state / value | C1 state / value | H directory | Memory | Newest data |
|---|---|---|---|---|---|
Checkpoint | M / 1 | I / - | exclusive(C0) | 0 | C0: 1 |
1. GetS received | M / 1 | IS^D / - | exclusive(C0), pend GetS C1 | 0 | C0: 1 |
2. Fwd-GetS received | O / 1 | IS^D / - | exclusive(C0), pend GetS C1 | 0 | C0: 1, Data(1) in flight to H |
3. Data received at H | O / 1 | IS^D / - | O: C0, pend GetS C1 | 0 | C0 and H buffer: 1, Data(1) in flight to C1 |
4. Data received at C1 | O / 1 | S / 1 | O: C0, pend GetS C1 | 0 | C0 and C1: 1, C0 dirty-responsible |
(assumed) grant delivered | O / 1 | S / 1 | O: C0, sharers {C1} | 0 | C0 and C1: 1, C0 dirty-responsible |
The directory entry O: C0, sharers {C1} names C0 as the dirty-responsible holder and C1 as a reader. C1's S copy is current even though memory is stale: S describes C1's permission and does not itself imply clean memory in MOESI. H must use its dirty-responsibility record when choosing a source. In contrast, the MSI and MESI settled shared snapshot requires memory to be current.
Dirty responsibility cannot vanish when an O line is replaced. In this course an O or M copy leaves with PutM carrying the data and waits for Put-Ack, which means H has accepted responsibility; it is not automatically evidence that the bytes have reached DRAM. PutS and PutE release clean copies and also wait for Put-Ack.
This step model replays the dirty read in each protocol and the MESI silent store from Part 1, then asserts the settled snapshots. It models only these traces, not a controller. Simplifications against the course protocol: messages are applied in program order with no other traffic, the pending directory entries and the (assumed) grant-delivery row are collapsed so H's settled entry is written directly, and the MSI/MESI memory write happens in the same step as H's receipt of Data.
def dirty_read(protocol):
# Checkpoint: C0 = M / 1 after its store, C1 = I / -, memory = 0.
c0, c1, mem, dirx = ("M", 1), ("I", None), 0, "exclusive(C0)"
# 1. GetS from C1, 2. Fwd-GetS to C0, 3. Data(1) from C0 to H
data_at_h = c0[1]
if protocol == "MOESI":
c0 = ("O", c0[1]) # keep dirty responsibility
dirx = "O: C0, sharers {C1}"
else:
c0 = ("S", c0[1])
mem = data_at_h # H writes memory before settling S/S
dirx = "S{C0,C1}"
c1 = ("S", data_at_h) # 4. Data(1, S permission) to C1
return c0, c1, mem, dirx
def mesi_cold_read_then_store():
mem = 0
c0, dirx = ("E", mem), "exclusive(C0)" # 1. GetS, H reads memory, 2. Data grants E
record_before = dirx
c0 = ("M", 1) # L. silent local store, no message
return c0, mem, record_before, dirx
for proto in ("MSI", "MESI"):
assert dirty_read(proto) == (("S", 1), ("S", 1), 1, "S{C0,C1}")
assert dirty_read("MOESI") == (("O", 1), ("S", 1), 0, "O: C0, sharers {C1}")
for proto in ("MSI", "MESI", "MOESI"):
c0, c1, _, _ = dirty_read(proto)
assert c0[0] not in {"M", "E"} and c1[0] not in {"M", "E"} # no writer
assert c0[1] == c1[1] == 1 # value kept
c0, mem, before, after = mesi_cold_read_then_store()
assert c0 == ("M", 1) and mem == 0
assert before == after == "exclusive(C0)" # H's record unchanged by E -> M
print("PASS: MSI/MESI settle S/S with memory = 1, MOESI O/S with memory = 0, E -> M is silent")C1's 1 is current; stale memory is legal because C0 retains dirty responsibility. C0 cannot store 2 in O because C1 still has read permission. A new exclusive transaction must revoke other readers before granting write permission.