If C0 reads an uncached line and is its only holder, why make it request permission again before its first store? MESI adds E: exclusive writable permission while the line is still clean. Prerequisite: MSI Protocol. The S, M and I meanings remain the same; E is not a stronger form of M. It distinguishes cleanliness while keeping exclusive permission. E (MESI, MOESI) appears in both larger protocols.
The official gem5 MESI Two Level overview documents the optimisation of returning exclusive permission for a lone reader. Its hierarchy differs from our single-home teaching model; use its E definition, not its controller messages, for this lesson.
State at C0 | Other usable cached copies | Local store? | Memory | Eviction in the course model |
|---|---|---|---|---|
S | May exist; even a lone S is read-only | Needs exclusive permission first | Current in settled MESI | PutS, wait for Put-Ack |
E | None | Yes, silently change to M | Current before that store | PutE (no data), wait for Put-Ack |
M | None | Yes | May be stale; C0 has dirty responsibility | PutM with data, wait for Put-Ack |
The MESI cold read (GetS, H reads memory, Data grants E) followed by a silent local store that turns E into M is traced in Caches, Lines and Write-Back. The fact to carry forward: H records exclusive(C0) at the grant and nothing reaches H at the store, so the same entry covers E / 0 and M / 1. An exclusive record is a reason to consult C0 on a conflicting request, not evidence that memory holds the latest value.
That is also why a forwarded read treats both cases alike. On Fwd-GetS an E or M holder becomes S and always replies with Data, and in this course H writes that Data to memory before settling S/S. For a clean E line the write leaves memory unchanged, but H cannot tell clean from dirty, so it always writes. Part 3 follows that clean-holder case step by step.
Stop the Part 1 trace after its step 2, before any store: C0 = E / 0, C1 = I / -, H = exclusive(C0), memory = 0. C0 now replaces X. Because the line is clean, it sends PutE with no data, enters EI^A (sent PutE, waiting for Put-Ack: local loads and stores stall, and its writeback buffer keeps the line so an Inv or Fwd could still be answered) and waits. H removes C0, records U, writes nothing to memory and sends Put-Ack.
Step | C0 state / value | C1 state / value | H directory | Memory | Newest data |
|---|---|---|---|---|---|
Checkpoint | E / 0 | I / - | exclusive(C0) | 0 | C0 and memory: 0 |
L. C0 evicts X | EI^A / - | I / - | exclusive(C0) | 0 | memory: 0, PutE in flight to H |
1. PutE received | EI^A / - | I / - | exclusive(C0), pend PutE C0 | 0 | memory: 0 |
H records U, sends Put-Ack | EI^A / - | I / - | U | 0 | memory: 0 |
2. Put-Ack received | I / - | I / - | U | 0 | memory: 0 |
H's entry does not change at L: no message has reached H yet. Memory is authoritative after the PutE only because the line was E, not M. Had C0 stored first, the line would be M and leave with PutM carrying the data; H would write memory before sending Put-Ack. PutE therefore has to mean 'clean': a cache must never send PutE for a line it has silently written.
Some protocols drop E silently instead. Then H's exclusive(C0) record outlives the copy, so a later Fwd-GetS or Fwd-GetM to C0 must be able to get a 'no copy' reply and H must fall back to memory. That silent variant is a different protocol, taught later in Part 3, as are requests that meet C0 while it waits in EI^A (Part 4).
E is worthwhile when a read is followed by a local write while exclusivity lasts: it avoids a permission request at that store. It offers no such shortcut to a cache already in S. Nor does it guarantee that every cold read receives E; a protocol may choose S, and a concurrent reader can change the decision.
It treats C0 as the exclusive holder and forwards the request. C0 always replies with Data, whether it is still E or has become M. In this course H always writes that Data to memory before settling S/S, which is correct whether the line was clean or dirty, so H never needs to know which case occurred.