7.1 KiB
State machines in TLA+
Converting plain text to TLA+ notation
An advantage of using TLA+ notation instead of whatever language teams write in is that it forces them to reason in a more abstract way.
For the example program, we need to describe:
-
possible initial values of variables:
i = 0andpc = "start"(i = 0) ⋏ (pc = "start") -
the relation between their values in the current and next states
if the current value of pc is "start", then next value of i is in {0, 1, ... 1000} next value of pc is "middle" else if current value of pc is "middle" then next value of i equals current value of i + 1. /* It is in `{1, 2, ... 1001}`*/ next value of pc is "done" else /* pc is "done" */ no next values-
let's get rid of "the current value of X", by notating it just "X"
if pc is "start", then next value of i is in {0, 1, ... 1000} next value of pc is "middle" else if pc is "middle" then next value of i equals i + 1. /* It is in `{1, 2, ... 1001}` $/ next value of pc is "done" else /* pc is "done" */ no next values -
let's get rid of "next value of X", by notating it just "X'"
if pc is "start", then i' is in {0, 1, ... 1000} pc' is "middle" else if pc is "middle" then i' equals i + 1. /* It is in `{1, 2, ... 1001}` */ pc' is "done" else /* pc is "done" */ no next values -
let's replace "equals" by "=", "is in" by "∈" and drop the comments
if pc = "start" then i' ∈ {0, 1, ... 1000} pc' = "middle" else if pc = "middle" then i' = i + 1 pc' = "done" else no next values -
TLA+ notates integer ranges as
low..highwith both limits includedif pc = "start" then i' ∈ 0..1000 pc' = "middle" else if pc = "middle" then i' = i + 1 pc' = "done" else no next values -
The "if/then" contain an implicit "and"
if pc = "start" then i' ∈ 0..1000 ⋏ pc' = "middle" else if pc = "middle" then i' = i + 1 ⋏ pc' = "done" else no next values -
Important point:
- these are not instructions for computing, but a formula
relating the values of
i,pc,i', andpc'. - this does NOT mean
if pc == "start" then do the then part otherwise do the else part - if means that
if pc == "start" then the formula EQUALS the then formula, otherwise it EQUALS the else formula - The formule equals TRUE for: [i = 17, pc = "start", i' = 534, pc' = 'middle']
- The formule equals FALSE for: [i = 534, pc = "middle", i' = 77, pc' = 'done']
- these are not instructions for computing, but a formula
relating the values of
-
The "no next values" replacement should be a formula that does not equal true for any value of
i,pc,i', andpc'. -
The simplest formula for that is
FALSE. Most TLA+ keywords are uppercase.IF pc = "start" THEN (i' ∈ 0..1000) ⋏ (pc' = "middle") ELSE IF pc = "middle" THEN (i' = i + 1) ⋏ (pc' = "done") ELSE FALSE -
This is a pretty-printed TLA+ formula.
-
The TLA+ source code is in ASCII so it doesn't have these Unicode glyphs
IF pc = "start" THEN (i' \in 0..1000) /\ (pc' = "middle") ELSE IF pc = "middle" THEN (i' = i + 1) /\ (pc' = "done") ELSE FALSE
-
The complete program is now a set of two formulas:
- Initial state formula:
(i = 0) /\ (pc = "start") - Next state formula:
IF pc = "start"
THEN (i' \in 0..1000) /\ (pc' = "middle")
ELSE IF pc = "middle"
THEN (i' = i + 1) /\ (pc' = "done")
ELSE FALSE
But there is a nicer way to write the next-state formula
IF pc = "start"
THEN A
ELSE IF pc = "middle"
THEN B
ELSE FALSE
The formula is true in two cases:
- either
pc = "start" /\ A - or
pc != "middle" /\ B
So we can rewrite it:
(pc = "start" /\ A) \/ (pc = "middle" /\ B)
putting the values back in:
((pc = "start") /\ A)
\/ ((pc = "middle" /\ B)
((pc = "start") /\ (i' \in 0..1000) /\ (pc' = "middle"))
\/ ((pc = "middle") /\ (i' = i + 1) /\ (pc' = "done"))
( (pc = "start")
/\ (i' \in 0..1000)
/\ (pc' = "middle") )
\/ ( (pc = "middle")
/\ (i' = i + 1)
/\ (pc' = "done") )
( pc = "start")
/\ i' \in 0..1000
/\ pc' = "middle" )
\/ ( pc = "middle"
/\ i' = i + 1
/\ pc' = "done" )
The far away parentheses are hard to read and will be harder on larger formula, so TLA+ adds a prefix notation:
\/ /\ pc = "start"
/\ i' \in 0..1000
/\ pc' = "middle"
\/ /\ pc = "middle"
/\ i' = i + 1
/\ pc' = "done"
The initial state formula could also be written the same way:
(i = 0) /\ (pc = "start")
/\ i = 0
/\ pc = "start"
The "bulleted" list of "/" is ended by any symbol to the left, here the \/,
meaning white space is significant.
Compare with the original C:
int i;
void main() {
i = someNumberFrom0to1000();
i = i + 1;
}
- TLA+ is actually simpler
- The equal sign is an actual equality
someNumberFrom0to1000();is non-deterministic. We need this because we cannot predict in what order things happen- Remember that this is a formula not a sequence of commands:
/\and\/are commutative (no shortcut eval)
More syntax
- TLA specs appear in modules, like
MODULE SimpleProgram - Arithmetic like
+and..can be imported from moduleIntegers:EXTENDS Integers - Identifiers need to be declared before use:
VARIABLES i, pc - The initial formula is defined by
Init ≜ (i = 0) /\ (pc = "start") - The next formula is defined similarly by
Next ≜ (the next formula) - Names
InitandNextare conventional, but any name can be used - ASCII version
------------- MODULE SimpleProgram -------------------
EXTENDS Integers
VARIABLES i, pc
Init == (i = 0) /\ (pc = "start")`
Next == \/ /\ pc = "start"
/\ i' \in 0..1000
/\ pc' = "middle"
\/ /\ pc = "middle"
/\ i' = i + 1
/\ pc' = "done"
=======================================================
Decomposing large specs
For real code, the next state formula can be thousands of lines.
The math way to decompose is using definitions. Our spec is too small to be worth it, but let's do it to show syntax.
Next == \/ /\ pc = "start"
/\ i' \in 0..1000
/\ pc' = "middle"
\/ /\ pc = "middle"
/\ i' = i + 1
/\ pc' = "done"
====== Is equivalent to this =======
Pick == /\ pc = "start"
/\ i' \in 0..1000
/\ pc' = "middle"
Add1 == /\ pc = "middle"
/\ i' = i + 1
/\ pc' = "done"
Next == Pick \/ Add1