Contents
1. Introduction
MoRDor is a tool for exploring weak memory. You give it a concurrent program as a litmus test, and it computes the program's justified executions. Each one is a way the program's reads and writes can resolve when memory is relaxed. It is backed by justifications that say what every write in it depends on. A memory model then decides which of these executions are coherent.
Every execution carries the justifications it was built from. MoRDor can therefore tell you why an outcome is allowed, not just whether it is: which reads a write really depends on, which accesses a compiler could reorder or forward, and which synchronisation rules out the rest.
MoRDor implements Symbolic MRD (Symbolic MRD: Dynamic Memory, Undefined Behaviour, and Extrinsic Choice, Richards, Wright, Cooksey and Batty, OOPSLA 2025). It adds a check for episodic loops and a use-after-free analysis. Values are symbolic, so one execution stands for every run that resolves the same way, whatever the concrete values.
This manual covers the concepts behind MoRDor, the web UI, and the litmus test language.
2. Concepts
Events and event structures
MoRDor interprets a program as an event structure: one event for every memory access and control point the program can perform, however it runs. An event is written label:type location value, as in 3:R x α or 4:W y 1.
| Type | Event |
|---|---|
R | Read (load) of a location |
W | Write (store) to a location |
A | Allocation (malloc) |
D | Deallocation (free) |
L, U | Lock and unlock |
F | Fence (a synchronisation point) |
B | Branch on a condition |
I | The initial event, which gives every location its initial value |
T | Terminal event, one per thread's end |
Values are symbolic. A read does not read a number: it reads a fresh symbol, written as a Greek letter (α, β, …). An allocation returns a symbolic address, written as a CJK numeral (一, 二, …). Constraints on those symbols decide which values they take.
Events are ordered by program order (po), the order the source imposes within a thread. Where a program branches, the events on the two sides are in conflict: no execution contains both.
Justifications
A justification for a write says which reads the write depends on, and under what condition. Initially a write depends on every read its value or location mentions. Elaborations then derive further justifications that depend on less:
- Value assignment replaces a symbol by the value the condition forces, so the write no longer depends on the read that produced it.
- Forwarding lets a read take its value directly from an earlier write in the same thread, so the read is no longer performed. Write elision removes a write that a later write overwrites before anything reads it. Neither applies to a volatile access, or to the read or write of a
casorfadd: those are always performed, and an update's read reads from memory, which is what keeps it atomic. - Lifting merges justifications that hold on both sides of a branch, so the write no longer depends on what the branch tested.
- Weakening drops the parts of a justification's condition that the program's de facto guarantees already imply.
Together these capture what an optimising compiler may do to a program. A dependency that survives every elaboration is a real one, and the justification set is everything the elaborations derive for the program. The web UI shows the set with the elaboration step behind each justification (see Graph controls).
Justified executions
An execution chooses one side of every branch, one justification for every write, and for every read the write it reads from. It records:
| Relation | Meaning |
|---|---|
rf | Reads-from: which write each read takes its value from |
dp | Dependencies: the reads each write's chosen justification depends on |
ppo | Preserved program order: the part of program order that must hold in any run, such as accesses that may share a location and orders that synchronisation imposes |
fwd, we | The forwarding context: reads forwarded from a write, and writes elided, in the justifications the execution uses |
rmw | Read-modify-write: the read and write of one cas or fadd |
co | Coherence order: the order of writes to each location under which the memory model admitted the execution |
An execution also carries predicates: the conditions of the branches it takes, and the equalities its rf edges impose on symbols. It must be complete, meaning every read reads from some write, and free of thin air: dp ∪ ppo ∪ rf may not have a cycle. A cycle would mean a value justified only by itself.
x := 0; before the threads.
Memory models and coherence
Justified executions describe what relaxed memory permits. A memory model says which of them a platform or language standard admits. MoRDor admits an execution under a model when some coherence order co satisfies the model's axioms.
| Model | In brief |
|---|---|
smrd | Symbolic MRD. Only dependencies, preserved program order and release/acquire synchronisation order events. This is the model a test that names none runs under. |
rc11 | Repaired C11 (Lahav et al., PLDI 2017). All of program order counts, and accesses in mode sc are sequentially consistent. |
rc17 | RC11 with C++17's release sequences, which later same-thread relaxed stores no longer continue. |
rc11z | RC11 with allocations and frees ordered as writes to the memory they allocate or free. |
c11, c17, c20 | The C11, C++17 and C++20 standards, also named cpp11, cpp17 and cpp20. C11 and C++17 take the zoo's release sequences with the standard's own SC order, so they allow IRIW with SC fences as the standard did before P0668; C++20 has C++17's release sequence with RC11's SC order. None has a thin-air axiom, and all three allow out-of-thin-air executions. |
orc11 | Operational RC11 (Dang et al., POPL 2020), the model RustBelt verifies Rust's libraries against: RC11 without SC accesses or SC fences. Programs with either are refused. |
wasm | WebAssembly's memory model (Watt et al., OOPSLA 2019). Accesses in mode sc are seqcst, every other access unordered. Unordered accesses need not be coherent, a race is defined rather than undefined, and out-of-thin-air executions are allowed. Programs with fences or release/acquire modes are refused. |
drfx, denovosync | Sequential consistency for data-race-free programs, and an exception (DRFx, Marino et al., PLDI 2010) or undefined behaviour (DeNovoSync, Sung and Adve, ASPLOS 2015) otherwise: SC, with a race between accesses of which one is na reported as undefined behaviour. Programs with locks are refused. |
bmm | The buffered memory model for Java (Demange et al., POPL 2013): TSO, where a store and a later load stay in order when either is volatile, written as mode sc. Programs with locks are refused. |
crc | The C11 fragment of Dodds, Batty and Gotsman (ESOP 2018): release-acquire accesses, non-atomic ones with catch-fire semantics, and SC fences. Programs with SC accesses, other fences or locks are refused. |
ocaml | The OCaml memory model (Dolan et al., PLDI 2018). An access in mode sc is to an atomic location, any other access non-atomic. Load buffering is forbidden, a race is defined, and non-atomic reads need not be coherent. Programs with fences, locks or release/acquire modes are refused. |
jam | Java Access Modes (Bender and Palsberg, OOPSLA 2019), their herd model: na is Java's plain, rlx opaque, acq/rel release-acquire and sc volatile. Coherence is derived from visibility; plain accesses are coherent but may be out of thin air. Programs with locks are refused. |
rar | RC11's release-acquire/relaxed fragment (Doherty et al., PPoPP 2019): relaxed, release and acquire accesses and updates only. Programs with fences, non-atomic or SC accesses are refused. |
od-lso | Ou and Demsky's load-store ordering, acyclic(sb ∪ rf), over the C/C++11 model. |
mrd | Modular Relaxed Dependencies. sMRD's axioms on programs that access only named globals and allocate nothing. Other programs are refused, because there sMRD admits executions MRD does not. |
sc, vbd | Sequential consistency, and Volatile-by-Default, which admits the same outcomes. |
tso, x86-tso, clighttso | Total store order. Only store-to-load reordering is relaxed. Fences and atomic instructions such as CAS flush the store buffer. |
ra, sra, wra | The release-acquire fragment of C/C++11, its strong variant, and its weak variant (Lahav and Boker). Only release stores synchronise with acquire loads. |
cc | Weak causal consistency (Bouajjani et al.): WRA's axioms with every access synchronising, and no RMW axiom. |
coherence | Per-location cache coherence: writes to one location are seen in one order. |
pc, pram, causal, slow, local | Steinke and Nutt's lattice. Each thread must have a view of all writes and its own reads in which every read sees the latest write. The models differ in what the views respect: a writer's order per location (Slow), a writer's order (PRAM), the causal order (Causal), or a writer's order and one order per location (PC). |
pocausal | Per-object causal consistency (Burckhardt et al.), with visibility read off rf and arbitration from co. |
ryw, mr, mw, wfr | Terry et al.'s session guarantees, with a thread as a session and an arbitration order per reader. On its own, mw rejects nothing: a read sees only the write it read, so it never has two writes of one session to put in order. |
rc11c | RC11 with consume, by name only: MoRDor does not yet give consume reads a semantics of their own, so rc11c answers as rc11. A test may name it; the settings dialog does not offer it. |
imm | The Intermediate Memory Model (Podkopaev et al., POPL 2019), between the language models and hardware. Its address dependencies are not computed, so it allows some shapes IMM forbids. |
ps1, ps2 | Promising semantics 1.0 (Kang et al., POPL 2017) and 2.0 (Lee et al., PLDI 2020). Not a check on sMRD's executions: see below. |
Every model is a check on the executions MoRDor generates. Those are free of thin air unless a model of the run allows it, as only c11, c17, c20, wasm and jam do; then the other models reject the thin-air executions themselves. The names follow the Relaxed Memory Model Zoo; a name with a hyphen, such as [x86-TSO], is written as the zoo spells it. Each model name in the table links to its page in the zoo.
Promising semantics is operational rather than axiomatic. A thread may promise a write before it reaches it, other threads may read the promise, and the promise stands only if the thread can be certified to fulfil it running alone. So ps1 and ps2 compute their own executions, one per distinct outcome, instead of checking sMRD's. An execution's relations are those promising defines: rf, which message each read took, and co, the order of timestamps per location. dp and ppo are sMRD's and have no counterpart. Loops need the step counter. A test annotated [PS1] or [PS2] runs under that version; [Promising] names none and needs one chosen. On the command line, --semantics ps1 or ps2 chooses it for a whole run.
A test names its model in its assertion, as in forbid (…) [RC11], or several, as in forbid (…) [RA, SRA]. Some names map onto a model: [Power] onto IMM, and [UB11], [RC11UB] and [IMMUB] also allow the undefined-behaviour fold e / !r → e. An unknown model name is an error. [_] means any model.
Assertions
allow C holds when some admitted execution satisfies C. forbid C holds when none does. Conditions can mention registers, final values of memory (@x is the value finally stored at x), and an execution's relations, as in (3,2) ∈ .co or (1,4) ∈ .rf. A refinement chain ~~> compares the observable behaviours of two programs.
MoRDor reports assertion instances that explain the verdict:
| Instance | Meaning |
|---|---|
| Witnessed | An execution that satisfies an allow condition |
| Contradicted | An execution that satisfies a forbid condition, and so violates it |
| Confirmed | An allow condition that holds for every execution |
| Refuted | A forbid condition that no execution satisfies |
Loops and episodicity
A loop can be explored in two ways. Under step-counter semantics MoRDor unrolls each loop a fixed number of times, and the executions are those of the bounded program. Under symbolic loop semantics one iteration stands for all of them.
The episodicity analysis checks whether a loop is episodic: whether each iteration behaves as an independent episode, so that analysing one iteration is enough for any number of them. It checks four conditions, and reports which fail and where:
- Register condition: how the iteration accesses registers
- Write condition: which writes a read in the loop may read from
- Branching condition: what the loop's branching conditions pin down
- Events condition: how events are ordered across iterations
The analysis always interprets loops symbolically, whichever loop semantics the settings choose.
Use-after-free
An execution exhibits a use-after-free when it accesses a location that a deallocation frees, and nothing orders the access before the deallocation: no chain of dp, ppo and rf edges leads from the access to the free. Program order alone does not count, since relaxed memory does not preserve it. Which executions exist depends on the memory model, so a program can be free of use-after-free under RC11 and exhibit one under sMRD.
An unbounded pointer dereference is the counterpart for allocations: an access through a pointer to allocated memory that nothing orders after the allocation.
Both are sMRD's. Under promising semantics there is no use-after-free; what counts as undefined behaviour there is a dereference of something that is not an address, or a division by zero. The run aborts at that point, is reported as undefined behaviour, and has no final state for a condition to hold of.
3. Getting started
- Pick a program from Examples… above the editor, such as Classics › MP, or type your own.
- Press Visualize. MoRDor parses and interprets the program, derives its justifications, and computes and checks its executions. Results stream in as each stage finishes.
- The graph panel first shows the event structure. Step through the executions with ‹ and ›.
- Read the execution's predicates, final registers and justifications in the box at the top left of the graph.
- Open Assertions in the Analysis sidebar for the verdict, and click an instance to jump to its execution.
x := 0; y := 0;
{
r1 := x;
y := 1
} ||| {
r2 := y;
x := 1
}
%%
allow (r1 = 0 && r2 = 0) [RC11]
Two threads each read a variable and then write the other. The assertion asks whether both reads can see the initial 0.
4. The web UI
Layout
| Area | What it holds |
|---|---|
| Top bar | From left to right: the action button, Settings with a summary of what the next run uses, and the run's status; then Share and Tests; then Manual and the theme toggle |
| Litmus Source (left) | The editor, with the example library and Open |
| Event Structure & Executions (centre) | The graph of the event structure or the current execution, its information box, statistics and controls |
| Analysis (right) | Episodicity, Assertions and Use-After-Free panels |
| Log (bottom) | What the UI and the server have done, newest last. Clear empties it. |
Drag the gaps between the three columns to resize them. The theme choice is remembered in the browser.
Actions
The action button runs the program in the editor. The arrow beside it chooses which action the button runs, and marks the current one:
| Action | Runs |
|---|---|
| Visualize | Everything: parsing, interpretation, episodicity, justifications, executions, coherence, assertions and use-after-free |
| Parse | Parsing only. The Episodicity panel lists the program's loops. |
| Interpret | Parsing and interpretation. The graph shows the event structure. |
| Episodicity | Interpretation and the episodicity analysis, without executions |
| Assertions | The same stages as Visualize. The test runner selects it when it loads a litmus test. |
The status in the top bar reads Processing... while an action runs, then Complete, or Error if the run fails. The error itself appears in the log and in the graph panel. For a parse error the log quotes the offending line with a caret under the token, and the editor marks the token in red until you edit the program or run it again.
The editor
- Examples… offers programs from MoRDor's test corpus, grouped by what they illustrate: classic weak-memory tests, coherence, fences and access modes, dependencies and thin air, and programs only MoRDor can express.
- Open loads a
.litfile from your computer. - ? next to the panel title opens this manual at the language reference.
Share, in the top bar, copies a link that carries the program and the selected action. Opening the link restores both, but does not run the action. The link does not carry the settings.
Syntax is highlighted as you type.
Event structure and executions
The graph panel holds a carousel. Its first graph is the event structure. The graphs after it are the admitted executions, labelled Execution i of n at the top. Move through them with ‹ and ›.
Hover over an event to highlight the source code it came from. The log also names the event. You can drag events to rearrange the graph.
The event structure shows program order (po), plus lock order (lo) and fork-join (fj) edges where the program has them. Conflict (cf), between events on different sides of a branch, is drawn as a red zig-zag with an arrowhead at each end, since it is symmetric. Only the po-minimal pairs are drawn: those where no event before either one in program order conflicts with the other. Every other conflict follows from these, as the events after two conflicting events conflict too, up to where the threads join. An execution graph shows the execution's events and relations:
| Edge | Colour | Meaning |
|---|---|---|
po | teal | Program order, transitively reduced |
ppo | purple | Preserved program order, transitively reduced |
dp | orange | Dependencies, transitively reduced |
rf | blue | Reads-from, from the write to the read |
rmw | red | Read-modify-write pairs |
fwd | turquoise | Forwarding: a read forwarded from a write |
we | violet | Write elision |
uaf | bright red | A use-after-free, drawn out of the deallocation |
A fwd or we edge points at an event the execution does not contain: elision is what removed it. That event is drawn with a dashed outline. Fences are not drawn in executions.
Execution information
The box at the top left of the graph describes what is on screen:
| Field | Shows |
|---|---|
| Predicates | The execution's predicates: the branch conditions it takes and the value equalities its rf edges impose. ⊤ for the event structure. |
| Final registers | The value each register ends with. A register stays symbolic where the execution does not pin its value. |
| Also allowed by | The compared memory models that also admit this execution, or none when only the primary model admits it. Only shown when models are compared (see Settings). The filter button beside it, a funnel, restricts the carousel to the executions the primary model admits and no compared model does; press it again to see all of them. While it is on, the label at the top counts them, as in Execution 3 of 25 · 4 only sMRD. |
| Justifications | How many justifications the execution was built from. Press the button to list them. |
| Use-after-free | The use-after-free pairs in this execution, or No |
| Unbounded Deref | Whether the execution has an unbounded pointer dereference (see Use-after-free) |
Graph controls and export
Below the graph:
- Layout arranges the graph: Hierarchical (the default), fCoSE (fast, force-directed), COSE (slower, often tidier) or Circle.
- Relations chooses which kinds of edge to draw, with All and None shortcuts. The choice carries over from one execution to the next.
- Fit zooms the whole graph into view.
- Export saves the graph as you see it: the current layout, including events you dragged, and only the relations you chose to draw.
- PNG: an image at twice screen resolution.
- DOT: Graphviz with node positions.
neato -n -Tpdf graph.dot -o graph.pdfreproduces the layout. - TikZ: a
tikzpicturefor LaTeX, with Greek letters in math mode. The file's header lists the packages it needs. Labels with allocation names needxelatexorlualatex.
- Save JSON saves the whole run: the program, every graph, and the analysis results. Load JSON (above the graph) restores a saved run without contacting the server, at the execution that was on screen.
At the bottom right, the statistics box counts the nodes and edges on screen and the executions of the run. Expand Justification set there for the program's whole justification set, each with the elaboration step that derived it.
Analysis panels
Episodicity
One entry per loop, with its source range and a verdict: ✓ Episodic, ✗ Non-episodic, ⋯ checking while the analysis runs, or — not analysed when the analysis could not evaluate the loop's conditions. Verdicts appear loop by loop as each finishes. Expand a loop to see its four conditions, and expand a failed condition for the violations and where in the source they occur. Hovering over a loop highlights it in the editor.
Assertions
The overall verdict (Valid or Invalid), then the assertion instances behind it, each with its execution, the instantiated condition, its result and any set-membership tests it made. Click an instance to show its execution.
Use-After-Free
Every execution with a use-after-free, and the pairs of events that make it one. Click an entry to show its execution, where the pairs are drawn as uaf edges.
Settings
Settings in the top bar opens the settings; beside it, a summary names the models and loop semantics the next run uses. Apply & Regenerate applies them and reruns the last program you ran.
Loop Semantics
- Step-counter semantics unrolls every loop Loop unravellings times (2 by default).
- Symbolic loop semantics interprets loops symbolically.
Memory Model
- Primary model is the model executions are shown for, and the model the assertion is checked under. Default is the model the test's assertion names, or sMRD if it names none. Choosing any other model replaces the test's own model, for every assertion the test has.
- Also compute executions under checks every execution against further models, any of those in the table above except RC11c. The primary model is greyed out here, as there is nothing to compare it with; under Default that is the model the test's first assertion names, or sMRD. The log reports how many executions each model admits. The information box lists, for the execution on screen, the compared models that also admit it.
- PS1.0 and PS2.0, under Promising (operational), compute the executions under promising semantics instead of sMRD's. As the primary model, each execution's graph shows its events with their
rfandcoedges; there are no justifications to show. Symbolic loops are unavailable while either is the primary or a compared model.
[RC11UB], for example, happens before any model is consulted, so it follows the primary model.
Promising semantics enumerates executions of its own, so between it and the other models it is the outcome that is compared: a model allows an execution when it allows the execution's final values of registers and globals. An sMRD execution is symbolic, and allows an outcome when its values can take those of the outcome. A promising run that aborted has no outcome, and is allowed by none.
Execution Filtering
Show executions with use-after-free and Show executions with unbounded pointer dereference are sent with each request, but the server does not act on them yet. Every admitted execution is shown whichever way they are set.
Test runner
Tests opens the test runner over MoRDor's corpus. Tests are listed in three trees: Litmus Tests (litmus-tests/), Episodicity Tests (programs/episodicity/) and Programs (programs/).
- Filter tests… narrows the trees by path.
- ▶ next to a test runs it. ▶ Run All Tests runs every litmus and episodicity test in turn.
- A litmus test's result shows whether its assertion is valid, how many executions it has, and whether it has undefined behaviour. An episodicity test's shows how many loops were analysed and whether all are episodic. A directory is marked ✓ when every result in it passed.
- Click a test for its output and source, each with 📋 Copy. 📝 Load in Editor puts the source in the editor and selects the matching action.
- 🔄 Refresh reloads the lists, 🗑️ Clear Results forgets results, and 📊 View Summary returns to the overview of results.
The test runner runs each test under the model its assertion names, not the settings.
Log
The log at the bottom of the page records each step of a run as it arrives: the stages, the executions received, per-model counts when models are compared, and settings changes. Errors are shown in red: a program that does not parse, a model name MoRDor does not know, a failure anywhere in the pipeline, or a run that ended without completing.
5. Common tasks
Is an outcome allowed?
Write the outcome as an assertion: allow (r1 = 1 && r2 = 0) [RC11]. Run Visualize and open Assertions. For allow, a Witnessed instance is an execution that produces the outcome. For forbid, a Contradicted instance is one that should not exist. Click it to see the execution.
Why is an execution allowed?
Show the execution, then:
- Read its Predicates and Final registers to see which outcome it is.
- Use Relations to draw only
rf,dpandppo. A read that sees an old value is one whoserfedge crosses a later write that nothing indporppoorders before it. - Press the Justifications button for what each write depends on. A write with fewer dependencies than its source suggests got them removed by an elaboration. The Justification set in the statistics box names the step.
- Turn on
fwdandweto see reads that were forwarded and writes that were elided.
Which models allow an execution?
In Settings, choose a primary model and tick the models to compare. Executions of the primary model the others also admit say so under Also allowed by. An execution that lists no other model is one only the primary admits: on load buffering with sMRD as primary and RC11 compared, those are exactly the load-buffering witnesses RC11 rules out. To step through just those, press the funnel button beside Also allowed by.
Is a loop episodic?
Choose the Episodicity action, or run Visualize, and open Episodicity. A failed condition lists its violations with source positions. Hover over the loop to find it in the editor.
Does a program use memory after freeing it?
Run Visualize and open Use-After-Free. Each entry is an execution and its offending pairs. In the graph they are uaf edges. An access that synchronisation should order before the free, but does not, typically lacks a release or acquire annotation. Compare MoRDor only › Use after free with Use after free (fixed) in the examples.
A figure for a paper
Show the execution, choose a layout, drag events where you want them, and use Relations to draw only what the figure needs. Then Export › TikZ. The figure keeps the positions and relations on screen.
Keeping a result
Share gives a link to the program, to rerun later. Save JSON keeps the computed run itself, which Load JSON reopens without recomputing.
6. Command line
The pipeline behind the web UI is also available from the mordor command. mordor --help lists its commands and options.
| Command | Does |
|---|---|
run | Runs the whole pipeline and reports whether the assertion holds |
parse | Parses a test, optionally as JSON or for Isabelle (--output-mode json|isa) |
interpret | Builds the event structure |
justifications | Lists the justification set and the elaboration behind each justification |
executions | Exports the executions with their events and relations as JSON |
dependencies | Reports po, dp and ppo for each execution |
visual-es | Draws the event structure as DOT, JSON or HTML |
episodicity | Checks the program's loops for episodicity |
futures | Computes the program's futures |
mordor run --single test.lit
mordor run --all-litmus-tests litmus-tests -r
The web server's HTTP API is described in web/README.md in the MoRDor repository.
7. Language reference
The rest of this manual describes the litmus test language.
Overview
A litmus test states a concurrent program, whose justified executions MoRDor explores, and an assertion about the outcomes it may have. The program consists of threads performing memory operations.
// syntax. Comments extend from // to the end of the line.
Litmus Test Structure
A complete litmus test has three optional parts:
// Optional configuration
name = TestName
model = rc11
values = {0, 1, 2}
[de facto guarantees]
[[constraints]]
%%
// Program (required), optionally initialising memory first
x := 0;
{ thread1_statements } ||| { thread2_statements }
// Optional assertion
%%
allow final_condition [model]
Configuration Section
The configuration section appears before the program and is terminated by %%.
Name Declaration
name = StorBuffering
name = "Message Passing Test"
Model Selection
model = rc11
model = smrd
model = imm
Values Domain
Specify the set of possible values for variables:
values = {0, 1, 2, 3}
values = {0x0, 0x10, 0xFF}
De Facto Guarantees
Facts that hold in every run of the program, as expressions in square brackets. Weakening may drop the part of a justification's condition these imply. They do not initialise memory: to give a location an initial value, write it before the threads, as in x := 0;.
[x = 0]
[y = 0]
[ptr = 0]
Constraints
Add constraints using double square brackets:
[[x != y]]
[[ptr != 0]]
Variables and Identifiers
Registers
Registers start with r followed by alphanumeric characters:
r0, r1, r2, rax, rtmp, rResult
Global Variables
Global variables start with a letter and contain alphanumeric characters:
x, y, flag, data, pointer
A program touches a global only through a load or a store, and each is an event of its own. A global cannot appear in an expression: load it into a register first, as in r0 := x; r1 := r0 + 1, not r1 := x + 1. Its address is taken only by a reference, r0 := &x, and a pointer held in a global is loaded into a register before it is used. A program that breaks these rules is refused with a parse error.
AtLoc Variables
Special variables prefixed with @, used in assertions to name the value at a location:
@x, @thread1, @location
Labels
Labels are enclosed in backticks and used to mark statements:
`label1` r0 := x;
`critical` *rp := 1;
Statements
Register Assignment
// Register to register
r0 := r1;
// Register from an expression over registers
r0 := r1 + 1;
// Reference: the address of global x
r0 := &x;
A reference is the only way to take an address, and it is not an event. Store it, dereference it, or pass it to cas or fadd through the register.
Skip Statement
skip;
Parallel Threads
Use ||| to separate parallel threads:
{ r0 := x; y := 1 } ||| { r1 := y; x := 1 }
Memory Operations
Loads
From Global Variables
| Syntax | Description |
|---|---|
r0 := x |
Relaxed load from global x |
r0 := x.load() |
Explicit relaxed load |
r0 :rlx= x |
Relaxed load |
r0 :acq= x |
Acquire load |
r0 :sc= x |
Sequentially consistent load |
r0 :v= x |
Volatile relaxed load |
r0 :vacq= x |
Volatile acquire load |
From Pointers
The address is an expression over registers: a reference, a pointer from malloc, or a pointer loaded from memory, optionally with an offset, as in *(rp + 1).
| Syntax | Description |
|---|---|
r0 := *rp |
Relaxed pointer dereference |
r0 :rlx= *rp |
Relaxed pointer load |
r0 :acq= *rp |
Acquire pointer load |
r0 :v= *rp |
Volatile pointer load |
Stores
To Global Variables
| Syntax | Description |
|---|---|
x := 1 |
Relaxed store to global x |
x.store(1) |
Explicit relaxed store |
x :rlx= 1 |
Relaxed store |
x :rel= 1 |
Release store |
x :sc= 1 |
Sequentially consistent store |
x :v= 1 |
Volatile relaxed store |
x :vrel= 1 |
Volatile release store |
To Pointers
| Syntax | Description |
|---|---|
*rp := 1 |
Relaxed pointer store |
*rp :rlx= 1 |
Relaxed pointer store |
*rp :rel= 1 |
Release pointer store |
*rp :v= 1 |
Volatile pointer store |
Allocation
| Syntax | Description |
|---|---|
r0 := malloc(n) |
Allocate, and put the address in r0 |
x := malloc(n) |
Allocate, then store the address to global x: two events |
free(r0) |
Free the allocation r0 points to |
The size is not used, and offsets into an allocation are assumed not to reach another object. free takes a register: to free a pointer held in a global, load it first, r0 := x; free(r0).
Memory Orders
| Keyword | Aliases | Description |
|---|---|---|
nonatomic |
na |
Non-atomic access |
relaxed |
rlx |
Relaxed memory order |
acquire |
acq |
Acquire memory order |
release |
rel |
Release memory order |
relacq |
Release-acquire combined | |
sequentially_consistent |
sc |
Sequential consistency |
Atomic Operations
Compare-and-Swap (CAS)
Atomically compares the value at an address with an expected value and, if they are equal, stores the desired value. The register is set to 1 if the swap happened and 0 if it did not. As for every expression in a program, the address, expected and desired values are over registers: to swap a global, pass a reference to it.
r0 := cas(load_order, store_order, address, expected, desired)
// Basic CAS on global x
rx := &x;
r0 := cas(relaxed, relaxed, rx, 0, 1);
// With acquire/release, through a pointer
r1 := cas(acquire, release, rp, r2, r3);
// Sequential consistency
r0 := cas(sc, sc, rx, 0, 1);
Fetch-and-Add (FADD)
Atomically adds the operand to the value at an address. The register is set to the sum, the value written. The address and operand are over registers, as for cas:
r0 := fadd(rmw_mode, load_order, store_order, address, operand)
r0 := fadd(load_order, store_order, address, operand)
// Basic FADD on global counter
rc := &counter;
r0 := fadd(relaxed, relaxed, rc, 1);
// With strong semantics
r1 := fadd(strong, acquire, release, rc, 5);
// Normal semantics
r2 := fadd(normal, relaxed, relaxed, rc, -1);
Fences
Memory fence/barrier operations:
fence(relaxed);
fence(acquire);
fence(release);
fence(sc);
Locks
// Global lock operations
lock mutex;
unlock mutex;
// Default unnamed lock
lock;
unlock;
Control Flow
If-Else Statements
// Single statement
if (r0 = 1) r1 := 2;
// Block
if (r0 != 0) {
r1 := x;
y := r1
}
// With else
if (r0 > 0) {
x := 1
} else {
x := 0
}
While Loops
// Single statement
while (r0 < 10) r0 := r0 + 1;
// Block: a condition over a global needs a load into a register
r0 := flag;
while (r0 = 0) {
r0 := flag
}
Do-While Loops
do {
r0 := r0 + 1;
x := r0
} while (r0 < 10)
Expressions
In a program, an expression is over registers and constants: a global is loaded into a register first, and an address is taken only by r0 := &x. Assertions are not programs, and their expressions may also name globals, @ locations and sets.
Arithmetic Operators
| Operator | Description | Example |
|---|---|---|
+ |
Addition | r0 + 1 |
- |
Subtraction / Negation | r0 - 1, -r0 |
* |
Multiplication | r0 * 2 |
/ |
Division | r0 / 2 |
% |
Modulo | r0 % 4 |
Comparison Operators
| Operator | Description | Example |
|---|---|---|
= |
Equality | r0 = 1 |
!= |
Inequality | r0 != 0 |
< |
Less than | r0 < 10 |
> |
Greater than | r0 > 5 |
<= |
Less than or equal | r0 <= 10 |
>= |
Greater than or equal | r0 >= 0 |
Logical Operators
| Operator | Description | Example |
|---|---|---|
&& |
Logical AND | r0 = 1 && r1 = 1 |
|| |
Logical OR | r0 = 0 || r1 = 0 |
! |
Logical NOT | !(r0 = 0) |
Bitwise Operators
| Operator | Description | Example |
|---|---|---|
& |
Bitwise AND | r0 & 0xFF |
| |
Bitwise OR | r0 | 0x01 |
^ |
Bitwise XOR | r0 ^ r1 |
~ |
Bitwise NOT | ~r0 |
Set Operators
| Operator | Aliases | Description |
|---|---|---|
in |
\in, ∈ |
Set membership |
notin |
\notin, ∉ |
Not in set |
Special Operators
| Operator | Description | Example |
|---|---|---|
forall |
Universal quantifier | forall (r0 in {0,1,2}) |
. |
Dot operator | x.load() |
, |
Tuple constructor | (r0, r1) |
Literals
// Decimal integers
0, 1, 42, 255
// Hexadecimal integers
0x0, 0x10, 0xFF, 0xDEADBEEF
// Booleans: allow (true) asks whether the model admits any execution
true, false
// Strings
"message", "test name"
// Set literals (with dot or quote prefix)
.'eventset'
'eventset'
.name
Assertions
Assertions specify expected outcomes of litmus tests. They appear after the %% delimiter.
Simple Outcome Assertions
%%
allow r0 = 0 && r1 = 0
%%
forbid r0 = 1 && r1 = 1
Model-Specific Assertions
%%
allow r0 = 0 && r1 = 0 [rc11]
%%
forbid r0 = 1 && r1 = 1 [smrd]
Several Models
An assertion can name several models, and a test can have several assertions, one per line. Each assertion is checked under each model it names, and the test is valid when every one holds. Executions are enumerated once and each model checks them, so the models of one test must agree on the undefined-behaviour fold. PS1 and PS2 compute executions of their own: naming one beside a coherence model, as in allow (…) [RC11, PS1], runs both, and each verdict is its own model's. --semantics chooses one semantics for every assertion. The first model named is the one executions are shown for.
%%
allow (r0 = 1 && r1 = 2 && r2 = 1) [WRA, CC]
forbid (r0 = 1 && r1 = 2 && r2 = 1) [RA, SRA, SC, RC11]
Chained Assertions
Test the same program under different models:
%%
~~> allow [rc11] %%
// Another test definition here
Model-Only Assertions
%%
[rc11]
%%
[smrd]
Which model an assertion is checked under in the web UI, and how to compare models, is described under Settings.
Complete Examples
Store Buffering (SB)
name = SB
model = rc11
%%
x := 0; y := 0;
{ x := 1; r0 := y } ||| { y := 1; r1 := x }
%%
allow r0 = 0 && r1 = 0
Message Passing (MP)
name = MP
model = rc11
%%
data := 0; flag := 0;
{
data := 1;
flag :rel= 1
} ||| {
r0 :acq= flag;
r1 := data
}
%%
forbid r0 = 1 && r1 = 0
Load Buffering (LB)
name = LB
model = rc11
%%
// RC11 forbids load buffering; under model = smrd it is allowed
x := 0; y := 0;
{ r0 := x; y := 1 } ||| { r1 := y; x := 1 }
%%
forbid r0 = 1 && r1 = 1
CAS Example
name = CAS_Test
model = rc11
%%
m := 0;
rm := &m;
{
// r0 is 1 if this thread's swap happened, 0 if not
r0 := cas(acquire, release, rm, 0, 1)
} ||| {
r1 := cas(acquire, release, rm, 0, 1)
}
%%
forbid r0 = 1 && r1 = 1
Pointer Operations
name = PointerTest
model = rc11
%%
x := 0; ptr := 0;
{
rp := malloc(4);
*rp := 42;
ptr := rp;
x := 1
} ||| {
r0 := x;
if (r0 = 1) {
rq := ptr;
r1 := *rq
}
}
%%
allow r0 = 1 && r1 = 42
Loop Example
name = SpinLock
model = rc11
%%
flag := 0;
{
flag :rel= 1
} ||| {
r0 :acq= flag;
while (r0 = 0) {
r0 :acq= flag
};
r1 := flag
}
%%
allow r1 = 1
Quick Reference Card
| Category | Syntax |
|---|---|
| Comments | // comment |
| Config delimiter | %% |
| Thread separator | ||| |
| Statement separator | ; |
| Block delimiters | { ... } |
| Initialisation | x := 0; before the threads |
| De facto guarantee | [x = 0] |
| Constraint | [[x != y]] |
| Label | `label` |
| Register | r0, r1, rax |
| Global variable | x, flag, data |
| AtLoc variable | @location |
| Assignment | := |
| Memory orders | :rlx=, :acq=, :rel=, :sc= |
| Volatile | :v=, :vrlx=, :vacq=, :vrel=, :vsc= |
| Pointer deref | *rp |
| Reference | r0 := &x |
| Allocation | r0 := malloc(n), free(r0) |
Additional Resources
For more information about weak memory models and litmus testing:
- RC11: The C11 Relaxed Memory Model
- C11 Standard: ISO/IEC 9899:2011
- Memory Model Papers and Research