The most important model after SC
Total store order (TSO) is a widely implemented memory model: first introduced by SPARC, and — more importantly — it appears to match the memory model of x86. RISC-V supports a TSO extension (RVTSO), partly to ease porting of x86/SPARC code. This chapter follows the same pattern as chapter 3: motivation here, intuition (§4.2), formalism (§4.3), implementation including atomics and FENCEs (§4.4), further reading (§4.5), and a head-to-head comparison with SC (§4.6).
Write buffers: too valuable to lose
Cores have long used write (store) buffers write buffer Per-core FIFO holding committed stores until the cache can accept them; hides store-miss latency. defined in Chapter 4 — open in glossary to hold committed stores until the rest of the memory system can process them. A store enters the buffer when it commits and exits when the block is in the cache in a read-write coherence state — meaning a store can enter before the cache has write permission. The buffer thus hides store-miss latency, and since stores are common, avoiding those stalls matters. It also just seems sensible: the core isn’t waiting for anything — a store updates memory, not core state.
On a single-core processor, the buffer can be made architecturally invisible: ensure a load to A returns the most recent (program-order) store to A — either by bypassing bypassing A load returning the value of its own core's latest program-order-earlier buffered store, overriding memory order. defined in Chapter 4 — open in glossary the buffered value to the load or by stalling the load until the store leaves the buffer.
When building a multicore, it seems natural to give each core its own bypassing write buffer and assume the buffers remain invisible.
That assumption is wrong
Take the Dekker program once more, on a multicore with in-order cores and single-entry write buffers, and watch the buffers betray you:
1 / 7Initial state
Memory starts as x = 0, y = 0; both write buffers are empty.
The net result is (r1, r2) = (0, 0) — an execution result forbidden by SC (§3.4 showed the constraint cycle). Without write buffers this hardware is SC; with them it is not. On a multicore, write buffers are architecturally visible.
Confirm it with the litmus explorer — under pure SC three outcomes are possible; enabling Store→Load reordering (which is precisely what a write buffer does) reveals the fourth:
Table 4.1: can both r1 and r2 be set to 0?
| Core C1 | Core C2 |
|---|---|
| S1: x = NEW; | S2: y = NEW; |
| L1: r1 = y; | L2: r2 = x; |
Initially x = 0, y = 0. Table 3.3 (Dekker-inspired): can both r1 and r2 be set to 0?
Possible outcomes (r1, r2) under Custom reorderings:
1 execution — show one
S1 → L1 → S2 → L24 executions — show one
S1 → S2 → L1 → L21 execution — show one
S2 → L2 → S1 → L1Three ways out
- Turn the buffers off. Vendors have been loath to do this — the performance cost is real.
- Hide them again with aggressive, speculative SC implementations (§3.8-style). Possible, but it adds complexity and can waste power detecting violations and handling mis-speculations.
- Change the model. SPARC, and later x86, abandoned SC for a model that allows straightforward use of a FIFO write buffer per core: TSO total store order (tso) SC minus the Store→Load ordering rule: legalizes per-core FIFO write buffers; the SPARC/x86 memory model. defined in Chapter 4 — open in glossary , which simply allows (r1, r2) = (0, 0).
The third choice astonishes some people. But TSO turns out to behave like SC for most programming idioms — and it is well defined in all cases. The next section shows exactly what changed.
Check yourself
1.When does a store enter the write buffer, and when does it leave?
2.How is a write buffer made architecturally invisible on a SINGLE-core processor?
3.In the single-entry-buffer execution of Table 4.1 (step through the widget!), why do both loads return 0?
4.Faced with architecturally visible write buffers, what were the three options — and which did SPARC/x86 choose?