Declarative Semantics

Declarative semantics define a program execution. They map a program to a set of candidate executions, by defining a consistency predicate.

1. Candidate Execution Graphs

We can represent candidate executions as graphs, where nodes represent events or memory instructions (reads, writes, updates, fences). Relations correspond to program order and reads-from . This is not EVERY POSSIBLE EXECUTION, but a CANDIDATE ONE.

DeclarativeSemantics InitX W x 0 WriteX W x 1 InitX->WriteX ReadX1 R x 1 InitX->ReadX1 ReadY1 R y 1 InitX->ReadY1 WriteY W y 1 InitX->WriteY ReadX2 R x 0 InitX->ReadX2 InitY W y 0 InitY->WriteX InitY->ReadX1 InitY->ReadY1 InitY->WriteY ReadY2 R y 0 InitY->ReadY2 rf WriteX->ReadX1 rf ReadX1->ReadY2 ReadY1->ReadX2 WriteY->ReadY1 rf

In the graph above:

Example 1

To map our store buffering example to a graph, we can do:

And here three valid candidatae execution graphs:

DeclarativeSemantics AInitX W x 0 AWriteX W x 1 AInitX->AWriteX AWriteY W y 1 AInitX->AWriteY AReadX R x 0 AInitX->AReadX rf AInitY W y 0 AInitY->AWriteX AInitY->AWriteY AReadY R y 0 AInitY->AReadY rf AWriteX->AReadY AWriteY->AReadX BInitX W x 0 BWriteX W x 1 BInitX->BWriteX BWriteY W y 1 BInitX->BWriteY BInitY W y 0 BInitY->BWriteX BInitY->BWriteY BReadY R y 1 BWriteX->BReadY BReadX R x 1 BWriteX->BReadX rf BWriteY->BReadX CInitX W x 0 CWriteX W x 1 CInitX->CWriteX CWriteY W y 1 CInitX->CWriteY CInitY W y 0 CInitY->CWriteX CInitY->CWriteY CReadY R y 0 CWriteX->CReadY CReadX R x 67 CWriteY->CReadX

We allow anything to be read unless it is constrained by a reads-from edge.

2. Formalising Executions

An event is a triple where:

In the graphs, for consiceness, we omit and , only showing . Labels are defined below, where and :

We can define some helper functions:

2.1 Relational Notation

Given a set and relations , we write:

Given an event set , a relation , a location and a thread , we write:

2.2 Defining Executions

A (candidate) execution graph is a tuple where:

Let be an execution graph. Then:

2.3 Consistency Predicates

A consistency predicate defines the set of allowed executions for a given memory model.

The most basic consistency predicate is completeness, which requires that every read event reads from some write event. Formally, an execution graph is complete if:

2.4. Sequential Consistency

Let be a strict total order on . is SC-consistent wrt if:

Then is SC consistent if it is complete and SC consistent wrt some on .

Example 2

To map our store buffering example to a graph under SC, we can do:

DeclarativeSemantics cluster_valid ALLOWED cluster_invalid FORBIDDEN AInitX W x 0 AWriteX W x 1 AInitX->AWriteX AWriteY W y 1 AInitX->AWriteY AInitY W y 0 AInitY->AWriteX AInitY->AWriteY AReadY R y 0 AInitY->AReadY rf AWriteX->AReadY AReadX R x 1 AWriteX->AReadX rf AWriteY->AReadX BInitX W x 0 BWriteX W x 1 BInitX->BWriteX BWriteY W y 1 BInitX->BWriteY BReadX R x 0 BInitX->BReadX BInitY W y 0 BInitY->BWriteX BInitY->BWriteY BReadY R y 0 BInitY->BReadY BWriteX->BReadY BWriteY->BReadX

We allow anything to be read unless it is constrained by a reads-from edge.

2.5 Alternative SC

This alternative definition of SC, which is easier to reason about. is the modification order of , where is a strict total order on . Then is SC consistent if:

Intuitively, is the order in which writes reach memory.

Example 3

To map our store buffering example to a graph under alternative SC, we can do:

DeclarativeSemantics cluster_valid ALLOWED cluster_invalid FORBIDDEN AInitX W x 0 AWriteX W x 1 AInitX->AWriteX AInitX->AWriteX mo AWriteY W y 1 AInitX->AWriteY AInitY W y 0 AInitY->AWriteX AInitY->AWriteY AInitY->AWriteY mo AReadY R y 0 AInitY->AReadY AWriteX->AReadY AReadX R x 1 AWriteX->AReadX AWriteY->AReadX AReadY->AWriteY rb BInitX W x 0 BWriteX W x 1 BInitX->BWriteX BInitX->BWriteX mo BWriteY W y 1 BInitX->BWriteY BReadX R x 0 BInitX->BReadX BInitY W y 0 BInitY->BWriteX BInitY->BWriteY BInitY->BWriteY mo BReadY R y 0 BInitY->BReadY BWriteX->BReadY BWriteY->BReadX BReadY->BWriteY BReadX->BWriteX rb

Much simpler to verify, as we can just check for cycles in the union of relations.

2.6 Total Store Order (TSO)

Let be a strict partial order on . is TSO-consistent wrt if:

is all the external reads-from edges in . This means reads from other threads.

is TSO consistent if it is complete and TSO consistent wrt some on .

Example 4

To map our store buffering example to a graph under TSO, we can do:

DeclarativeSemantics cluster_valid ALLOWED cluster_invalid ALLOWED AInitX W x 0 AWriteX W x 1 AInitX->AWriteX AWriteY W y 1 AInitX->AWriteY AInitY W y 0 AInitY->AWriteX AInitY->AWriteY AReadY R y 0 AInitY->AReadY rf AWriteX->AReadY AReadX R x 1 AWriteX->AReadX rf AWriteY->AReadX BInitX W x 0 BWriteX W x 1 BInitX->BWriteX BWriteY W y 1 BInitX->BWriteY BReadX R x 0 BInitX->BReadX BInitY W y 0 BInitY->BWriteX BInitY->BWriteY BReadY R y 0 BInitY->BReadY BWriteX->BReadY BWriteY->BReadX

2.7 Alternative TSO

This alternative definition of TSO, which is easier to reason about. is the modification order of , where is a strict total order on . Then is TSO consistent if it is complete and such that:

3. COH & RA

3.1 Coherence (COH)

SC is very expensive to implement in hardware, and forbids optimisations that are sound for sequential code. Hardware and compilers preserve SC "per-location" (coherence).

An execution graph is coherent if is complete and, there exists a strict total order on such that:

3.2 Alternative COH

Let be the modification order of . Then is coherent wrt if where . RF, RB are already on the same location, so we don't need to write for them.

is coherent if it is complete and coherent wrt some on .

Coherence Examples

Here are some examples:

3.3 Problems with COH

Under COH, spinlocks do not guarantee mutual exclusion:

Moreover COH does not allow message passing, so we can't really write any concurrent code. To solve this, we can strengthen COH with "observed" writes - change an -cycles to - cycles.

3.4 Release Acquire (RA)

Let be the modification order of . Then is RA consistent wrt if holds where and . Here, is the happens-before relation under RA, which relates events that are ordered by program order or reads-from on the same location.

This is much like saying every read is a "release" and every write is an "acquire" in C.

A execution graph is RA consistent if it is complete and RA consistent wrt some on .

3.5 Ordering Models

Given two memory models and , we can say that if :

4. C11 Semantics

We want to be careful not to over-synchronise our code, as that can lead to performance degredation. C11 allows us to specify memory access modes. Each memory access has mode :

Given an event , we write to get the mode of . We can order mode strength: . Given a set of events and mode , we can write:

4.1 C11 Definition

We can define happens-before with:

Let be the modification order of . Then is C11 consistent wrt if holds where . Then is C11 consistent if it is complete and C11 consistent wrt some on .

The synchronises-with relation is defined as:

This can be expanded to 4 different cases:

Release Sequences

We allow multiple updates between a release and acquire, hence the instead of just .

4.2 Catch Fire Semantics

Given a C11-execution graph we say that two events are in C11-race, , if:

is C11-racy if .

A program has undefined behaviour under C11 if it has a racy C11-consistent execution graph. An execution of program is a C11-outcome if is C11-consistent or has undefined behaviour under C11.

Back to Home