For teaching purposes the book introduces an eXample relaxed Consistency model — XC xc The book's eXample relaxed Consistency model: a total memory order exists, but only FENCE-related and same-address orders are enforced, with TSO-style bypassing. defined in Chapter 5 — open in glossary — capturing the basic idea and implementation potential of relaxed models. Like SC and TSO (and the largely defunct Alpha and SPARC RMO models it resembles), XC assumes a global memory order exists.
5.2.1 The basic idea
By default, XC leaves loads and stores unordered. A FENCE fence Instruction forcing all program-order-earlier memory operations into memory order before all later ones (a.k.a. memory barrier). defined in Chapter 4 — open in glossary — other models say barrier, memory barrier, membar, or sync — is how a programmer asks for order: if core Ci executes operations Xi, then a FENCE, then operations Yi, memory order puts all Xi before the FENCE and the FENCE before all Yi. Four properties are worth pinning down:
- A FENCE specifies no address.
- Two FENCEs by the same core stay ordered.
- A FENCE does not affect other cores’ operations — which is why “fence” is a better name than “barrier.”
- Real architectures offer multiple FENCE flavors with different ordering properties (§5.6’s RVWMO has six); this chapter uses only full FENCEs.
So XC’s memory order respects program order for:
- Load → FENCE, Store → FENCE, FENCE → FENCE, FENCE → Load, FENCE → Store — the five FENCE rules, and
- Load → Load, Load → Store, Store → Store to the same address only —
TSO’s rules, kept to preserve sequential core semantics and prevent
astonishment: without same-address Store→Store, a critical section
running
A = 1thenA = 2could finish with A = 1; without same-address Load→Load, reading B twice could see B’s value go from new back to old.
Finally, like TSO’s write-buffer 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 , XC ensures loads immediately see updates from their own stores — again preserving single-thread sequentiality.
5.2.2 Examples using FENCEs
Without FENCEs, XC demolishes §5.1’s flag idiom — all four outcomes appear:
Table 5.1's program under raw XC — no FENCEs
| Core C1 | Core C2 |
|---|---|
| S1: data1 = NEW; | L1: r1 = flag; |
| S2: data2 = NEW; | L2: r2 = data1; |
| S3: flag = SET; | L3: r3 = data2; |
Initially data1 = 0, data2 = 0, flag = 0. Table 5.1: what order ensures r2 and r3 always get NEW?
Possible outcomes (r2, r3) under XC (FENCEs only):
90 executions — show one
S1 → S2 → S3 → L1 → L2 → L390 executions — show one
L2 → S1 → S2 → S3 → L1 → L390 executions — show one
S1 → L3 → S2 → S3 → L1 → L290 executions — show one
L3 → L2 → S1 → S2 → S3 → L1XC enumeration reorders each thread freely EXCEPT across a FENCE and for same-address Load→Load / Load→Store / Store→Store; (bypass) = the load read its own thread's program-order-earlier store even though that store enters memory order later (the extended value rule).
Only executions where C2's spin loop has exited (r1 = SET) are shown — the book's L1/B1 loop repeats until flag reads SET.
Table 5.3 repairs it. One FENCE orders the data stores before the flag store; one orders the flag observation before the data loads:
Table 5.3: Table 5.1 with the FENCEs XC needs
| Core C1 | Core C2 |
|---|---|
| S1: data1 = NEW; | L1: r1 = flag; |
| S2: data2 = NEW; | F2: FENCE |
| F1: FENCE | L2: r2 = data1; |
| S3: flag = SET; | L3: r3 = data2; |
Initially data1 = 0, data2 = 0, flag = 0. Table 5.3: Table 5.1's program with the FENCEs XC needs.
Possible outcomes (r2, r3) under XC (FENCEs only):
4 executions — show one
S1 → S2 → F1 → S3 → L1 → F2 → L2 → L3XC enumeration reorders each thread freely EXCEPT across a FENCE and for same-address Load→Load / Load→Store / Store→Store; (bypass) = the load read its own thread's program-order-earlier store even though that store enters memory order later (the extended value rule).
Only executions where C2's spin loop has exited (r1 = SET) are shown — the book's L1/B1 loop repeats until flag reads SET.
The chain is now S1, S2 → F1 → S3 → L1 loads SET → F2 → L2, L3. The store-side FENCE F1 surprises no one; the load-side FENCE F2 does. But allowing loads to execute out of order can make in-order stores look out of order: run L2 early — L2, S1, S2, S3, L1, L3 — and r2 reads 0. The trap is sharpest for a program without B1’s control dependence, where L1 and L2 are just consecutive loads to different addresses and reordering them “seems reasonable, but is not.”
For the critical-section program, Table 5.4 conservatively surrounds every acquire and release with FENCEs (the book notes some are removable — §5.5’s release consistency makes that precise):
| Core C1 | Core C2 | Comments |
|---|---|---|
F11: FENCEA11: acquire(lock)F12: FENCE | ||
| Some loads L1i interleaved with some stores S1j | /* Arbitrary interleaving of L1i's & S1j's */ | |
F13: FENCER11: release(lock)F14: FENCE | F21: FENCEA21: acquire(lock)F22: FENCE | /* Handoff from critical section 1 to 2 */ |
| Some loads L2i interleaved with some stores S2j | /* Arbitrary interleaving of L2i's & S2j's */ | |
F23: FENCER22: release(lock)F24: FENCE |
FENCEs F13 and F22 carry the handoff: All L1i, All S1j → F13 → R11 → A21 → F22 → All L2i, All S2j.
5.2.3 Formalizing XC
Using chapter 3’s notation, an XC execution requires:
1. FENCE rules — all cores insert loads, stores, and FENCEs into respecting:
2. Same-address rules — loads and stores to the same address stay ordered:
3. Value rule — exactly TSO’s extended rule, bypassing included:
Table 5.5 summarizes the rules with one new letter — A, an ordering enforced only if the operations target the same address:
| Op 1 ↓ \ Op 2 → | Load | Store | RMW | FENCE |
|---|---|---|---|---|
| Load | ADifferent addresses reorder freely; same-address loads stay ordered so a value can never appear to go from new back to old. | ALoad → Store: this program ordering is NOT enforced — the two operations may be reordered in memory order. | ALoad → RMW: this program ordering is NOT enforced — the two operations may be reordered in memory order. | XLoad → FENCE: a load before a fence in program order must appear in that order in memory order. |
| Store | BSame as TSO: a later load may enter memory order before an earlier store — but a same-address load must return the just-stored value (bypassing). | ADifferent addresses reorder freely — this is what legalizes non-FIFO coalescing write buffers. Same-address stores stay ordered. | AStore → RMW: this program ordering is NOT enforced — the two operations may be reordered in memory order. | XStore → FENCE: a store before a fence in program order must appear in that order in memory order. |
| RMW | ARMW → Load: this program ordering is NOT enforced — the two operations may be reordered in memory order. | ARMW → Store: this program ordering is NOT enforced — the two operations may be reordered in memory order. | ARMW → RMW: this program ordering is NOT enforced — the two operations may be reordered in memory order. | XRMW → FENCE: a rmw before a fence in program order must appear in that order in memory order. |
| FENCE | XFENCE → Load: a fence before a load in program order must appear in that order in memory order. | XFENCE → Store: a fence before a store in program order must appear in that order in memory order. | XFENCE → RMW: a fence before a rmw in program order must appear in that order in memory order. | XFENCE → FENCE: a fence before a fence in program order must appear in that order in memory order. |
X = ordering enforced · B = bypassing required if same address · A = ordering enforced only if same address · hover any cell
Table 5.5 (recreated): XC ordering rules. The nine A entries are where XC differs from TSO’s Table 4.4 — every X outside the FENCE row and column weakened to same-address-only.
An implementation that allows only XC executions is an XC implementation.
5.2.4 The examples, operating correctly
Why do the FENCEd programs work? Figure 5.1a shows an XC execution of Table 5.3 in which C1’s stores S1/S2 swapped in memory order, as did C2’s loads L2/L3 — watch the program-order arrows cross:
Figure 5.1a (recreated): an XC execution of Table 5.3. S2 enters memory order before S1, and L3 before L2 — legal, and harmless.
Figure 5.1b (recreated): an SC execution of the same program. As far as any programmer can tell, the two executions are equivalent.
The same holds for the critical-section program (the book’s Figure 5.2): within each “arbitrary interleaving” block, XC may shuffle the loads L1i and stores S1j against each other — but the lock handoff chain F13 → R11 → A21 → F22 pins everything a second thread could observe, so the XC execution is again equivalent to an SC execution in which nothing reordered.
These examples demonstrate the chapter’s pivotal claim: with sufficient FENCEs, a relaxed model like XC can appear to programmers as SC — generalized properly in §5.4’s “SC for data-race-free.”
Check yourself
1.Which orderings does XC enforce WITHOUT the programmer asking?
2.In Table 5.3, most readers accept the store-side FENCE F1 — why is the LOAD-side FENCE F2 also necessary?
3.Table 5.5 introduces the entry "A" alongside X and B. What does A mean?
4.In Table 5.4's conservatively FENCEd critical sections, which FENCEs make the handoff from critical section 1 to critical section 2 correct?
5.Figure 5.1a shows an XC execution where C1's stores S1/S2 swapped and C2's loads L2/L3 swapped. Why does the book call it EQUIVALENT to the SC execution of Figure 5.1b?