11.2Exploring the Behavior of Memory Consistency Models

book pp. 260–261 · ~3 min read

  • litmus test
  • exhaustive exploration
  • herd
  • RMEM
  • diy
  • MemSynth

Consistency models define shared-memory behavior — so programmers and middleware writers (compiler authors, kernel developers) must be able to tell which behaviors a model permits and which it forbids. Vendors, however, traditionally specify their models in prose, which has a track record of ambiguity (a famous Linux kernel mailing-list thread spent weeks arguing about a spin-unlock optimization the manual couldn’t settle). A formal specification is unambiguous — and, better still, it makes exploration automatic.

11.2.1 Litmus tests

A memory model barely matters to a high-level programmer whose synchronization comes from libraries. It matters enormously to low-level programmers: the compiler writer implementing language-level synchronization, the kernel developer, the author of a lock-free data structure. For them, litmus tests — small multithreaded programs abstracting a synchronization situation — are the lingua franca: the outcomes a model allows on a litmus test tell the low-level programmer exactly what their hand-crafted synchronization can rely on, and whether a particular FENCE is needed. Processor vendors use the same tests in their manuals to explain their models.

You have been reading litmus tests since chapter 3: Table 3.3 is Dekker’s litmus test (it abstracts the mutual-exclusion handshake of Dekker’s algorithm), and Table 3.1 is the message-passing test. One more classic completes the set — the “coherence” litmus test (book Table 11.4): one core stores x = NEW while another loads x twice. May the first load see NEW and the second see the old 0?

Table 11.4: the coherence litmus test — enumerate and look for (r1, r2) = (NEW, 0)

Core C1Core C2
S1: x = NEW;L1: r1 = x;
L2: r2 = x;

Initially x = 0. Table 11.4, the coherence litmus test: can r1 be NEW while r2 is 0?

Model:

Possible outcomes (r1, r2) under Custom reorderings:

(NEW, NEW)SC
1 execution — show oneS1 → L1 → L2
(0, NEW)SC
1 execution — show oneL1 → S1 → L2
(0, 0)SC
1 execution — show oneL1 → L2 → S1

Enumerate under SC, then under XC: the outcome (NEW, 0) never appears. A later same-address load reading an older value than an earlier load would violate the data-value invariant — it is exactly the per-location SC “coherence axiom” that even the weakest commercial models mandate (XC’s same-address Load→Load rule is this test’s guardian).

11.2.2 Exploration

Given a formal memory model and a litmus test, tools can compute every behavior the model allows on that test. The recipe is blunt-force and beautiful: exhaustively enumerate the test’s possible schedules; for each candidate execution, either check the mathematical axioms ( axiomatic models) or run the reference state machine ( operational models) and record the values the loads return.

ToolStyleWhat it does
herd (diy.inria.fr/herd)axiomaticThe Cat language for expressing models mathematically + a built-in simulator for exploring litmus tests.
ppcmemaxiomatic/operationalLitmus exploration specialized for the POWER and ARM memory models.
CppMemaxiomaticExploration for the C/C++ language-level memory model (§5.9).
RMEMoperationalBuilt-in operational models for ARM (several variants), POWER (several), x86-TSO, and RISC-V; explores their behaviors.
diy (diy.inria.fr)generationGenerates a litmus test from its shape — the program-order relations and shared-memory dependencies you want probed.
litmustestgengenerationAutomatically produces a comprehensive litmus suite from an axiomatic model specification (NVIDIA labs).
MemSynth (memsynth.uwplse.org)synthesisGiven a litmus suite and a syntactic model template with holes, synthesizes a complete memory model satisfying the tests.

Test generation matters as much as exploration: tests can be written by hand for any interesting corner, generated randomly (the TSOtool/McVersi lineage, §11.3.2), or derived systematically by shape (diy) or from the model itself (litmustestgen). One caveat keeps everyone honest: a litmus suite conveys intuition but normally cannot be a complete specification — behaviors the tests don’t cover stay unspecified. MemSynth closes that loop by synthesizing the full model that the tests imply.

Check yourself: exploring memory-model behavior

1.Who is the primary audience for litmus tests?

2.The 'coherence' litmus test (Table 11.4): C1 stores x=NEW while C2 loads x twice. What does the forbidden outcome r1=NEW, r2=0 correspond to?

3.How do exploration tools (herd, RMEM, ppcmem, CppMem) compute all behaviors of a litmus test?

4.Why can't a suite of litmus tests normally serve as a complete memory-model specification — and what does MemSynth do about it?

4 questions