This project implements a couple interpreters for a simple programming language that are capable of running under different memory models.
It has the following instructions set:
r = 1
put constant into register.r1 = r2 # r3
binary operation on two registers.if r goto L
conditional jump on labelL
.load m #x r
load value from memory by address stored in locationx
into registerr
.store m #x r
store value from registerr
into memory by locationx
.r1 = cas m #x r2 r3
compare-and-swap value in memory by location namedx
, expected value is stored inr2
, desired value is stored inr3
, shoud return the actually read value in registerr1
.r1 = fai m #x r2
fetch-and-increment value in memory by locationx
, the value to increment by is stored inr2
, should return the read value prior increment in registerr1
.fence m
memory fence instruction.thread_goto L
create a new thread with start instruction at labelL
.
Notes on instructions:
- all values are assumed to be (fixed-width) integers.
- binary operation
#
can be one of the following:+
,-
,*
,/
. - each instruction can be prefixed with label, e.g.
L: r = 1
. m
denotes access mode:SEQ_CST
,REL
,ACQ
,REL_ACQ
,RLX
.
Program supports 4 memory models:
- SC (sequencially consistent, more info)
- TSO (total store ordering, more info)
- PSO (partial store ordering, more info)
- Strong RA (strong release acquire, more info): does not yet support fences.
Program supports 4 modes of executions:
- Non-deterministic: chooses one random execution on each run.
- Tracing: same as non-determinstic, but it logs the intermediate results and thread interleavings.
- Model-checking: enumerates all possible executions of a given program.
- Interactive: allows user to make a decision on what execution to explore at each choice point.
Requirements:
C++17
or higherCMake v3.15
or higher
Local setup:
git clone https://github.com/dmitrii-artuhov/weak-memory-models-simulator.git && cd ./weak-memory-models-simulator
mkdir build && cd build
cmake .. && make
After building the project there will be 2 targets generated: tests
and simulator
.
To run the simulator
you have to pass some arguments:
--file
: path to file containing program code.--mm
: memory model, one ofsc
(default),tso
,pso
,sra
.--it
: execution mode, one ofnd
(non-deterministic, default),tr
(tracing),mc
(model-checking),i
(interactive).
-
Store buffering:
thread_goto T1 // create new thread with start instruction at location `T1` thread_goto T2 // same for `T2` goto END T1: r = 1 store RLX #x r // write 1 to `x` load RLX #y a // read from `y` goto END T2: r = 1 store RLX #y r // write 1 to `y` load RLX #x b // read from `x` goto END END: // just an empty label
Running program with TSO memory model in model-checking mode:
> ./simulator --file ../tests/test-data/store-buffering.txt --mm tso --it mc =========== Memory state =========== Memory: // final state of memory x: 1 y: 1 Store buffers: [empty] ==================================== =========== Thread states ========== Thread 0 // this is a main thread (that created the rest) [empty] Thread 1 a: 0 // value read in register `a` r: 1 Thread 2 b: 0 // value read in register `b` r: 1 ==================================== =========== Memory state =========== Memory: y: 1 x: 1 Store buffers: [empty] ==================================== =========== Thread states ========== Thread 0 [empty] Thread 1 a: 0 r: 1 Thread 2 b: 1 r: 1 ==================================== =========== Memory state =========== Memory: x: 1 y: 1 Store buffers: [empty] ==================================== =========== Thread states ========== Thread 0 [empty] Thread 1 a: 1 r: 1 Thread 2 b: 0 r: 1 ==================================== =========== Memory state =========== Memory: y: 1 x: 1 Store buffers: [empty] ==================================== =========== Thread states ========== Thread 0 [empty] Thread 1 a: 1 r: 1 Thread 2 b: 1 r: 1 ==================================== Total states generated: 4297 // number of program states explored Final states count (unique in terms of thread subsystems states): 4
Above we can see 4 different outcomes of the store-buffering example under TSO. Model checking mode explores all interesting executions of a program in order to cover all possible outcomes (by outcomes I mean all final unique thread states).
-
Message passing:
thread_goto T1 thread_goto T2 goto END T1: r = 1 store RLX #x r load RLX #y a goto END T2: r = 1 store RLX #y r load RLX #x b goto END END:
Running program with Strong RA memory model in model-checking mode:
> ./simulator --file ../tests/test-data/message-passing.txt --mm sra --it mc =========== Memory state =========== Memory: <RLX; x: 1 @ 1> <RLX; y: 1 @ 1> Global timestamps: x @ 1 y @ 1 Thread views: Thread 0: Thread 1: x @ 1 y @ 1 Thread 2: ==================================== =========== Thread states ========== Thread 0 [empty] Thread 1 r: 1 Thread 2 a: 0 b: 0 ==================================== =========== Memory state =========== Memory: <RLX; x: 1 @ 1> <RLX; y: 1 @ 1> Global timestamps: x @ 1 y @ 1 Thread views: Thread 0: Thread 1: x @ 1 y @ 1 Thread 2: x @ 1 ==================================== =========== Thread states ========== Thread 0 [empty] Thread 1 r: 1 Thread 2 a: 0 b: 1 ==================================== =========== Memory state =========== Memory: <RLX; x: 1 @ 1> <RLX; y: 1 @ 1> Global timestamps: x @ 1 y @ 1 Thread views: Thread 0: Thread 1: x @ 1 y @ 1 Thread 2: y @ 1 ==================================== =========== Thread states ========== Thread 0 [empty] Thread 1 r: 1 Thread 2 a: 1 b: 0 ==================================== =========== Memory state =========== Memory: <RLX; x: 1 @ 1> <RLX; y: 1 @ 1> Global timestamps: x @ 1 y @ 1 Thread views: Thread 0: Thread 1: x @ 1 y @ 1 Thread 2: x @ 1 y @ 1 ==================================== =========== Thread states ========== Thread 0 [empty] Thread 1 r: 1 Thread 2 a: 1 b: 1 ==================================== Total states generated: 2716 Final states count (unique in terms of thread subsystems states): 4
Let's modify the code by adding release memory order for all writes and acquire for all reads:
thread_goto T1 thread_goto T2 goto END T1: r = 1 store REL #x r store REL #y r goto END T2: load ACQ #y a load ACQ #x b goto END END:
And run again with the same arguments:
... Total states generated: 2642 Final states count (unique in terms of thread subsystems states): 3
I did not put the whole output, but we see that one outcome became impossible. To be certain
{ a: 1, b: 0 }
became impossible (and it is expected result).