11.1Specification: Operational and Axiomatic

book pp. 251–260 · ~7 min read

  • observable actions
  • operational specification
  • axiomatic specification
  • linearizability
  • composability
  • pipeline ordering axioms

Ten chapters in, you are hopefully convinced that consistency models and coherence protocols are complex and subtle. This final chapter is about taming that subtlety rigorously: how to formally specify consistency models and coherence protocols (this section), how to explore the behaviors a specification allows (§11.2), and how to validate that implementations actually obey their specifications (§11.3).

A specification is the contract between a system’s user and its implementer: it answers what are the allowed behaviors? — leaving how they are enforced to the implementation. A system meets its environment through actions: input actions (user → system), internal actions (invisible inside), and output actions (system → user). Only input and output actions are visible, so a sequence of them is a behavior — and the observable actions are where all specification begins. Two classes of properties constrain behaviors: safety (“bad things don’t happen” — which sequences are legal; concurrency means there are usually many) and liveness (“good things eventually happen” — a system that accepts one input and halts is safe and useless).

Consistency model: what’s observable

The contract is software ↔ hardware. Inputs: loads and stores (with core ID, address, store value). Outputs: the values loads return. Nothing else — in particular, when an operation completes is not observable.

Coherence protocol: what’s observable

The protocol’s user is the pipeline (§2.3’s interface). Inputs: read-request, write-request. Outputs: read-return (a value), write-return (an ack) — the pipeline must know when a write completes, so a write’s completion instant IS observable here.

That last asymmetry — write completion observable for coherence, invisible for consistency — will decide exactly which correctness condition specifies each, in a few paragraphs. There are two major specification styles: operational (describe an abstract reference implementation) and axiomatic (describe behaviors with mathematical axioms).

11.1.1 Operational specification

An operational specification is a reference implementation, usually a state machine: whatever behaviors it can exhibit are, by definition, the legal ones. Internal state and internal actions do the constraining (that’s safety); liveness rides along as separate temporal-logic axioms (“state changes must eventually happen”).

An operational consistency spec typically has two components mirroring real hardware — a pipeline part and a memory system part, the latter presenting exactly the coherence interface above. Here are two operational specs of SC with identical in-order pipelines but different memory systems — a contrast that will formally separate §2.3’s two coherence classes.

Spec1: in-order pipeline + atomic memory

  1. Fetch: a core is picked non-deterministically; it fetches its next instruction into a local queue.
  2. Issue: a core is picked; a memory instruction issues a read-request or write-request and the pipeline blocks.
  3. Atomic memory: a read-request reads memory and responds with the value; a write-request writes memory and responds with an ack.
  4. Return: the pipeline unblocks on the response.

This is §3.6’s switch, formalized. Non-determinism at steps 1–2 generates all the legal interleavings.

Spec2: in-order pipeline + buffered memory

  1. Issue′: a store issues a write-request and blocks; a load first waits for the core’s latest write to be late-acked, then issues a read-request and blocks.
  2. Buffered memory: one global FIFO store queue fronts memory. A write-request is enqueued (with address, value, core ID) and acked immediately; when it drains to memory, a late-ack goes to the issuing core. Reads read memory directly.

Still SC! The single FIFO queue drains stores without violating any core’s program order — and the load rule keeps a core from racing past its own queued store.

In the message-passing program (Table 11.1 — our old friend: S1: St data=NEW; S2: St flag=SET vs a spin on flag then L2: Ld r2=data), both specs can exhibit «S1, S2, L1:r1=SET, L2:r2=NEW» — and neither can exhibit the SC-violating «…, L1:r1=SET, L2:r2=0».

Same SC, different coherence: linearizability

As consistency specs, Spec1 and Spec2 are equivalent. As coherence specs, they differ observably. Spec1’s atomic memory performs writes synchronously — a consistency-agnostic protocol. Spec2’s buffered memory performs them asynchronously — a consistency-directed protocol (Afek et al.’s lazy caching is a more aggressive spec in the same family). One four-event trace tells them apart:

Core C1wr-req(X,1) @t0 · wr-ret @t1
Core C2rd-req(X) @t2 · rd-ret 0 @t3
Global store queue
MemoryX = ?
SC verdict?
Linearizability verdict?

Four observable actions; the internal story is unknown.

1 / 6The trace on trial (Table 11.2)

Core C1: write-request(X,1) at t0, write-return at t1. Core C2: read-request(X) at t2, read-return → 0 at t3. The write RETURNED before the read was even issued — and yet the read saw the old value. Is this behavior legal? Depends on whom you ask.

Refining specs into implementations. The operational style’s great convenience: implementations are just refinements. Add per-core caches and coherence controllers to Spec1 — read/write-requests now check the local cache, misses send GetS/GetM to a directory — and you have a detailed protocol. Read that sentence again with ch. 6–9 in mind: the book’s protocol tables are operational models, refinements of Step 3. Pipelines refine the same way (more stages, more internal actions). Tool support: state-machine languages like Murphi and TLA+ express these models directly — every protocol table in this site could be typed into either almost mechanically.

11.1.2 Axiomatic specification

An axiomatic specification constrains behaviors with mathematics instead of machinery. You have been reading axiomatic specs since chapter 3 — the formalism of §3.5, §4.3, and §5.2 is exactly this method:

  • Observable actions: loads, stores, values returned.
  • Relations: program order (per-core total order) and the global memory order (total order over all cores’ operations).
  • Safety axioms (for SC): the preserved program order axiom — memory order respects each core’s program order; the load value axiom — a load returns the most recent same-address store in memory order; and the atomicity axiom — an RMW ’s load and store sit consecutively in memory order.
  • A liveness axiom: no operation is preceded by infinitely many others — every operation eventually performs.

A behavior is legal iff some global memory order satisfying all the axioms exists. Coherence gets the same treatment: take the four request/return actions, add two internal actions — read-perform and write-perform, the instants a request takes effect — define memory order over the perform events, keep SC’s three safety axioms, and add a fourth: each perform sits between its request and its return. That fourth axiom is precisely the real-time rule, so the result is per-location linearizability — the axiomatic spec of consistency-agnostic coherence. (Consistency-directed protocols are specified like the consistency models they enforce.)

Specifying implementations axiomatically

Axioms can also model hardware faithfully rather than specify correctness — the goal shifts from “what is legal” to “what does this pipeline actually do.” Following Lustig et al.’s PipeCheck, each load or store expands into per-stage sub-actions — a five-stage pipeline gives loads fetch, decode, execute, memory, writeback and stores those five plus exits-store-buffer, memory-write — and pipeline ordering axioms state which orderings the machine preserves, as microarchitectural happens-before (µhb):

  • fetch honors program order: i1 →po i2 ⇒ i1.fetch → i2.fetch;
  • decode/execute/memory/writeback each preserve the previous stage’s order;
  • the store buffer is FIFO: writeback order ⇒ exits-store-buffer order;
  • writes are ordered: exits-store-buffer order ⇒ memory-write order;
  • and a µhb load value axiom: a load’s memory stage reads the latest memory-write before it.

Note the altitude change: ppo specifies correctness; pipeline ordering axioms describe an implementation — whether the implementation’s axioms uphold the model’s ppo is exactly the validation question of §11.3. The analysis style, though, is one you can run by hand — witness a forbidden outcome by trying to order the sub-actions, and watch it die by cycle:

S1.memory-writeS2.memory-writeL1.memoryL2.memory

Each arrow: “must happen before.”

1 / 6The question (Table 11.3)

C1: S1: St data=NEW; S2: St flag=SET. C2: L1: Ld r1=flag; L2: Ld r2=data. Suppose L1 reads SET. Can L2 still read the old 0? Method: model each instruction as pipeline SUB-ACTIONS (fetch, decode, execute, memory, writeback — stores add exits-store-buffer and memory-write), then try to build a global order of sub-actions satisfying the pipeline ordering axioms. If the required orderings form a cycle, no such execution exists.

Manerkar et al.’s CCICheck extends the same µhb machinery across the pipeline–coherence boundary — it is the tool that formalized (and fixed) §9.3’s Peekaboo problem. Tool support for the axiomatic style: Alloy, the Cat language of the herd tool, and the Check suite’s µspec DSL for pipelines and protocols.

Check yourself: specifying consistency and coherence

1.What is observable for a coherence protocol but NOT for a consistency model?

2.In SC Spec2 (buffered memory), why must a load wait until the issuing core's latest write has been late-acked?

3.The trace «write-request(X,1), write-return, read-request(X) by another core, read-return(X,0)» is…

4.Why can consistency-agnostic coherence be enforced per memory location (e.g., by distributed directories), while SC cannot be decomposed that way?

5.In the axiomatic pipeline model, what does a CYCLE among required µhb edges (as in Table 11.3's message-passing test) establish?

5 questions