Description
In a non-coherent hierarchy (coherence_protocol=none, the Incoherent / Incoherent_L1 coherence managers) above a noninclusive level, a cache that evicts a dirty (M) line sends the PutM writeback fire-and-forget and immediately deallocates the line without tracking the in-flight writeback (no mshr_->insertWriteback). Nothing serializes a subsequent same-line request behind that writeback. A read that misses can therefore race the writeback down to the next level and read the pre-writeback (stale) value; because incoherent caches never invalidate, the stale value can then be cached and returned to the CPU — silent data corruption.
The coherent managers (MESI) don't hit this: they already use the insertWriteback / handleAckPut / AckPut MSHR-blocking machinery, and inclusive-L2 configs serialize the read at L2. The incoherent managers never wire that machinery up, and the MemController never acknowledges a PutM (it is unconditionally flagged F_NORESPONSE).
Root cause
Paths on devel @ f782290 (identical on v15.0.0):
coherencemgr/Incoherent_L1.cc — handleEviction case M: sendWriteback(PutM) then immediate deallocate, no insertWriteback; getInitCoherenceEvent advertises recvWBAck=false.
coherencemgr/Incoherent.cc — same on eviction; getInitCoherenceEvent advertises sendWBAck=false.
memoryController.cc — handleEvent case PutM unconditionally sets F_NORESPONSE; init() advertises sendWBAck=false, so memory never acks a writeback that a noninclusive level would need to release a blocked read.
How to reproduce
Build sst-core + sst-elements devel with --enable-debug, then run the repro.py below (standardCPU → L1 none → L2 none, noninclusive → MemController, small caches to force dirty-line eviction under concurrent access). Debug is already enabled on line 0x0. In the trace, look for a GetS/GetSResp fill of line 0x0 being serviced from the level below while a PutM writeback for that same line is in flight — an ordering the MESI managers structurally prevent.
Note: the stale value is timing-dependent and does not surface as a wrong number under stock traffic generators, because they write data-independent payloads (identical bytes on every write) — which is why the defect is latent. It manifests as returned-stale data only under a data-dependent workload; the ordering hazard above and the code path are the direct evidence.
import sst
DBG = "[0]" # watch line 0x0
cpu = sst.Component("core", "memHierarchy.standardCPU")
cpu.addParams({"clock":"2GHz","memFreq":"2","rngseed":"101","memSize":"32KiB","verbose":0,
"maxOutstanding":16,"opCount":20000,"reqsPerIssue":4,"write_freq":70,"read_freq":30})
iface = cpu.setSubComponent("memory", "memHierarchy.standardInterface")
l1 = sst.Component("l1cache", "memHierarchy.Cache")
l1.addParams({"access_latency_cycles":"4","cache_frequency":"2 Ghz","replacement_policy":"lru",
"coherence_protocol":"none","associativity":"2","cache_line_size":"64","cache_size":"2 KB","L1":"1",
"debug":1,"debug_level":10,"debug_addr":DBG})
l2 = sst.Component("l2cache", "memHierarchy.Cache")
l2.addParams({"access_latency_cycles":"10","mshr_latency_cycles":2,"cache_frequency":"2 Ghz",
"replacement_policy":"lru","coherence_protocol":"none","associativity":"4","cache_line_size":"64",
"cache_size":"16 KB","cache_type":"noninclusive",
"debug":1,"debug_level":10,"debug_addr":DBG})
mc = sst.Component("memory", "memHierarchy.MemController")
mc.addParams({"clock":"1GHz","addr_range_end":512*1024*1024-1,"backing":"malloc","backing_init_zero":"true",
"debug":1,"debug_level":10,"debug_addr":DBG})
# the fix adds an opt-in "writeback_acks":1 to the MemController params above
be = mc.setSubComponent("backend", "memHierarchy.simpleMem")
be.addParams({"access_time":"100 ns","mem_size":"512MiB"})
sst.Link("a").connect((iface,"lowlink","1000ps"),(l1,"highlink","1000ps"))
sst.Link("b").connect((l1,"lowlink","10000ps"),(l2,"highlink","1000ps"))
sst.Link("c").connect((l2,"lowlink","10000ps"),(mc,"highlink","10000ps"))
Environment
- OS: Ubuntu 22.04.5 LTS, kernel 6.8.0
- Compiler: gcc 11.4.0
- MPI: Open MPI 4.1.6
- sst-core: 15.0.0 (
87e9e3b)
- sst-elements: v15.0.0 (
d713776) and devel (f782290) — the vulnerable code is unchanged across the v16.0.0 release
Proposed fix (PR to follow)
Wire up the existing writeback-ack serialization on the incoherent path — insert an MSHR writeback entry on M eviction (when recvWritebackAck_), advertise/consume WB acks in getInitCoherenceEvent, add handleAckPut, accept Command::AckPut — and gate the MemController ack behind a new default-off writeback_acks parameter so all existing (coherent) configurations remain byte-identical.
Related, separate defect (not fixed by the above)
Under sustained eviction pressure the same incoherent path also segfaults deterministically in MSHR::removeEvictPointer via IncoherentL1::handleNULLCMD (reproducible on a stock build by shrinking the caches/working set in repro.py). It reproduces independently of the writeback fix and is noted because it lives in the same eviction/MSHR bookkeeping.
Description
In a non-coherent hierarchy (
coherence_protocol=none, theIncoherent/Incoherent_L1coherence managers) above a noninclusive level, a cache that evicts a dirty (M) line sends thePutMwriteback fire-and-forget and immediately deallocates the line without tracking the in-flight writeback (nomshr_->insertWriteback). Nothing serializes a subsequent same-line request behind that writeback. A read that misses can therefore race the writeback down to the next level and read the pre-writeback (stale) value; because incoherent caches never invalidate, the stale value can then be cached and returned to the CPU — silent data corruption.The coherent managers (MESI) don't hit this: they already use the
insertWriteback/handleAckPut/AckPutMSHR-blocking machinery, and inclusive-L2 configs serialize the read at L2. The incoherent managers never wire that machinery up, and theMemControllernever acknowledges aPutM(it is unconditionally flaggedF_NORESPONSE).Root cause
Paths on
devel@f782290(identical on v15.0.0):coherencemgr/Incoherent_L1.cc—handleEvictioncase M:sendWriteback(PutM)then immediatedeallocate, noinsertWriteback;getInitCoherenceEventadvertisesrecvWBAck=false.coherencemgr/Incoherent.cc— same on eviction;getInitCoherenceEventadvertisessendWBAck=false.memoryController.cc—handleEventcasePutMunconditionally setsF_NORESPONSE;init()advertisessendWBAck=false, so memory never acks a writeback that a noninclusive level would need to release a blocked read.How to reproduce
Build sst-core + sst-elements
develwith--enable-debug, then run therepro.pybelow (standardCPU→ L1none→ L2none, noninclusive→MemController, small caches to force dirty-line eviction under concurrent access). Debug is already enabled on line0x0. In the trace, look for aGetS/GetSRespfill of line0x0being serviced from the level below while aPutMwriteback for that same line is in flight — an ordering the MESI managers structurally prevent.Note: the stale value is timing-dependent and does not surface as a wrong number under stock traffic generators, because they write data-independent payloads (identical bytes on every write) — which is why the defect is latent. It manifests as returned-stale data only under a data-dependent workload; the ordering hazard above and the code path are the direct evidence.
Environment
87e9e3b)d713776) and devel (f782290) — the vulnerable code is unchanged across the v16.0.0 releaseProposed fix (PR to follow)
Wire up the existing writeback-ack serialization on the incoherent path — insert an MSHR writeback entry on M eviction (when
recvWritebackAck_), advertise/consume WB acks ingetInitCoherenceEvent, addhandleAckPut, acceptCommand::AckPut— and gate theMemControllerack behind a new default-offwriteback_acksparameter so all existing (coherent) configurations remain byte-identical.Related, separate defect (not fixed by the above)
Under sustained eviction pressure the same incoherent path also segfaults deterministically in
MSHR::removeEvictPointerviaIncoherentL1::handleNULLCMD(reproducible on a stock build by shrinking the caches/working set inrepro.py). It reproduces independently of the writeback fix and is noted because it lives in the same eviction/MSHR bookkeeping.