4.1Motivation for TSO/x86

book pp. 39–40 · ~3 min read

  • write buffer
  • architecturally visible buffering
  • total store order

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 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 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:

Core C1(registers empty)FIFO write buffer(empty)Core C2(registers empty)FIFO write buffer(empty)Memoryx=0 y=0

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 C1Core 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?

Model:

Possible outcomes (r1, r2) under Custom reorderings:

(0, NEW)SC
1 execution — show oneS1 → L1 → S2 → L2
(NEW, NEW)SC
4 executions — show oneS1 → S2 → L1 → L2
(NEW, 0)SC
1 execution — show oneS2 → L2 → S1 → L1

Three ways out

  1. Turn the buffers off. Vendors have been loath to do this — the performance cost is real.
  2. 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.
  3. Change the model. SPARC, and later x86, abandoned SC for a model that allows straightforward use of a FIFO write buffer per core: TSO , 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?

4 questions