I’ve been playing with Alloy (Electrum version) and added a ‘PlusCal’ like feature to Alloy. I finally got the basics to work and I am looking for some expert feedback. I know PlusCal is for us, the deplorable developer class, because it gives the impression of procedural code. However, that might actually convince some other developers to take a look. Many concurrent problems become a lot easier. However, so often in this world I find that my intuition is totally off so some feedback about this would be highly appreciated.
The first example I’ve got is the algorithm for multiplying as an addition:
let N = { n : Int | n >= 0 }
algorithm mult[ a, b : N, var r, n : Int ] {
r := 0
n := b
while (n>0) {
r := r.plus[a]
n := n.minus[1]
}
assert valid r = a.mul[b]
}
During compilation, I translate this to predicates
and sigs
. I.e. the algorithm generates behind the scenes plain Alloy code.
The first is an enum
specifying the labels. Since the example uses a ‘while’, a label is necessary and automatically generated. (You can also specify your own labels.)
enum multEnum {Done, _L100}
The next part is a sig that holds the state. This uses the Electrum model of vars.
one sig mult {
var pc : multEnum,
a, b : N,
var r, n : Int,
}
The next part are the predicates that act as event methods:
pred mult.Done{
this.pc = Done
this.r' = this.r
this.pc' = this.pc
this.n' = this.n
}
As you can see, I any variables not changed in a predicate are kept constant. This was quite tricky when I started to use other sigs
with vars. This is further discussed later with multiple processes.
pred mult._L100{
this.pc = _L100
this.n > 0 implies {
this.r' = this.r.plus[this.a]
this.n' = this.n.minus[1]
this.pc' = _L100
} else {
this.pc' = Done
this.r' = this.r
this.n' = this.n
}
}
My reasoning became that if you have an IF statement, you must ensure that the ifTrue and ifFalse parts assign the same variables. So they are automatically balanced. This works for the local variables and I think I devised a scheme for the non-local relations. Later more about that but this is a shady area for me.
pred mult.mult{
this.r = 0
this.n = this.b
this.pc = _L100
always (
this.Done
or this._L100
)
eventually (
this.pc = Done
)
}
And then an overall predicate that runs the Init section and, executes the event predicates in always
, and expects that the program counter pc ends in Done.
So run { mult.mult }
gives us
------State 0-------
┌─────────┬──────┬─┬─┬─┬─┐
│this/mult│pc │a│b│r│n│
├─────────┼──────┼─┼─┼─┼─┤
│mult⁰ │_L100⁰│2│2│0│2│
└─────────┴──────┴─┴─┴─┴─┘
------State 1-------
┌─────────┬──────┬─┬─┬─┬─┐
│this/mult│pc │a│b│r│n│
├─────────┼──────┼─┼─┼─┼─┤
│mult⁰ │_L100⁰│2│2│2│1│
└─────────┴──────┴─┴─┴─┴─┘
------State 2-------
┌─────────┬──────┬─┬─┬─┬─┐
│this/mult│pc │a│b│r│n│
├─────────┼──────┼─┼─┼─┼─┤
│mult⁰ │_L100⁰│2│2│4│0│
└─────────┴──────┴─┴─┴─┴─┘
------State 3-------
┌─────────┬─────┬─┬─┬─┬─┐
│this/mult│pc │a│b│r│n│
├─────────┼─────┼─┼─┼─┼─┤
│mult⁰ │Done⁰│2│2│4│0│
└─────────┴─────┴─┴─┴─┴─┘
There is an assert in the algorithm:
assert valid r = a.mul[b]
This automatically generates a predicate and an assert:
pred mult.valid_101{
this.mult => eventually (
this.pc = Done
this.r = this.a.mul[this.b]
)
}
assert valid { mult.valid }
So we can check the model with:
check valid for 10 but 6 int
And this gives no counterexample, indicating that our algorithm always has the same result as the Alloy mul
operator.
Processes
The cool part is, of course, the multi-process part. So I created the following example. It is my simplest concurrent example where an increment gives a false result when run with multiple concurrent processes. In PlusCal, each label generates its own event predicate so processes are running ‘concurrent’.
some sig Process { var reg : Int }
algorithm incr[ var total : Int] : Process {
total := 0
lda: p.reg := total
inc: p.reg := p.reg.plus[1]
sta: total := p.reg
assert valid total = # Process
}
This algorithm will run in parallel for each process the sequence of the lda
(Load register), inc
(Increment the register), and sta
(Store the register). Since it runs the processes in parallel, there are situations where the processes will overwrite each others values. So the assert must fail. It is the the simples concurrent problem I know.
I initially wanted to allow an arbitrary number of processes like things but the process is really something special since it controls the program counter. I also could not think of a use case where you need more than one type of ‘process’. So I used the return type syntax of a function to specify an arbitrary expression that is the domain of the program counter, aka pc
. I.e. the pc in expr -> labels
. The end condition is then that eventually, the pc must be pc = expr -> Done
. (I believe this is much more powerful than the limited concept of PlusCal processes?) You can declare arbitrary sigs with the var
keyword. This makes the code extremely readable.
enum incrEnum {sta, Done, lda, inc}
one sig incr {
var pc : Process -> incrEnum,
var p : Process,
var total : Int,
}
As you can see, the pc is now a relation from the Algorithm return type (which is not a real return type, it is just used to specify the domain of p
, to the labels. And an automatic variable p
is introduced, that is the ‘current’ process for an event predicate. This variable is not automatically kept constant and can this vary over its domain in the different states.
This is the generated Alloy code:
sig Process {
var reg : Int,
}
enum incrEnum {sta, Done, lda, inc}
one sig incr {
var pc : Process -> incrEnum,
var p : Process,
var total : Int,
}
pred incr.sta{
this.p -> sta in this.pc
this.total' = this.p.reg
this.pc' = this.pc ++ this.p -> Done
reg' = reg
}
pred incr.Done{
this.p -> Done in this.pc
this.total' = this.total
this.pc' = this.pc
reg' = reg
}
pred incr.lda{
this.p -> lda in this.pc
this.p.reg' = this.total
this.pc' = this.pc ++ this.p -> inc
this.total' = this.total
all x : (Process - this.p) | x.reg' = x.reg
}
Note the partial update. Since the lda
event predicate updates p.reg, I need to keep all other processes constant. I am using the algorithm process expression and then subtract the assignments. I think this is ok but I could use your help to get some confidence in this idea. I also spoke to Daniel Jackson and I understand this is a classic complicated problem.
From Daniel, I understand this is called frame conditions. I implemented the analysis of the code flow in the algorithm code. However, Daniel indicated that this is a very useful feature to have on predicates. I think I can use the code analysis & generation of code also on the Alloy AST directly. So I will experiment with this. This would then look like a modifier on the pred
keyword. E.e. pred auto
or so. I think Electrum also has a variant with an act
keyword that does something similar?
pred incr.inc{
this.p -> inc in this.pc
this.p.reg' = this.p.reg.plus[1]
this.pc' = this.pc ++ this.p -> sta
this.total' = this.total
all x : (Process - this.p) | x.reg' = x.reg
}
pred incr.incr{
this.total = 0
this.pc = Process -> lda
always (
this.sta
or this.Done
or this.lda
or this.inc
)
eventually (
this.pc = Process -> Done
)
}
So running it to find a case where we get the wrong answer:
check valid for 10 but exactly 2 Process
This gives a counter example:
------State 0-------
┌─────────┬─────────────┬────────┬─────┐
│this/incr│pc │p │total│
├─────────┼────────┬────┼────────┼─────┤
│incr⁰ │Process⁰│lda⁰│Process¹│0 │
│ ├────────┼────┼────────┼─────┤
│ │Process¹│lda⁰│ │ │
└─────────┴────────┴────┴────────┴─────┘
┌────────────┬───┐
│this/Process│reg│
├────────────┼───┤
│Process⁰ │2 │
├────────────┼───┤
│Process¹ │0 │
└────────────┴───┘
------State 1-------
┌─────────┬─────────────┬────────┬─────┐
│this/incr│pc │p │total│
├─────────┼────────┬────┼────────┼─────┤
│incr⁰ │Process⁰│lda⁰│Process¹│0 │
│ ├────────┼────┼────────┼─────┤
│ │Process¹│inc⁰│ │ │
└─────────┴────────┴────┴────────┴─────┘
┌────────────┬───┐
│this/Process│reg│
├────────────┼───┤
│Process⁰ │2 │
├────────────┼───┤
│Process¹ │0 │
└────────────┴───┘
------State 2-------
┌─────────┬─────────────┬────────┬─────┐
│this/incr│pc │p │total│
├─────────┼────────┬────┼────────┼─────┤
│incr⁰ │Process⁰│lda⁰│Process⁰│0 │
│ ├────────┼────┼────────┼─────┤
│ │Process¹│sta⁰│ │ │
└─────────┴────────┴────┴────────┴─────┘
┌────────────┬───┐
│this/Process│reg│
├────────────┼───┤
│Process⁰ │2 │
├────────────┼───┤
│Process¹ │1 │
└────────────┴───┘
------State 3-------
┌─────────┬─────────────┬────────┬─────┐
│this/incr│pc │p │total│
├─────────┼────────┬────┼────────┼─────┤
│incr⁰ │Process⁰│inc⁰│Process⁰│0 │
│ ├────────┼────┼────────┼─────┤
│ │Process¹│sta⁰│ │ │
└─────────┴────────┴────┴────────┴─────┘
┌────────────┬───┐
│this/Process│reg│
├────────────┼───┤
│Process⁰ │0 │
├────────────┼───┤
│Process¹ │1 │
└────────────┴───┘
------State 4-------
┌─────────┬─────────────┬────────┬─────┐
│this/incr│pc │p │total│
├─────────┼────────┬────┼────────┼─────┤
│incr⁰ │Process⁰│sta⁰│Process⁰│0 │
│ ├────────┼────┼────────┼─────┤
│ │Process¹│sta⁰│ │ │
└─────────┴────────┴────┴────────┴─────┘
┌────────────┬───┐
│this/Process│reg│
├────────────┼───┤
│Process⁰ │1 │
├────────────┼───┤
│Process¹ │1 │
└────────────┴───┘
------State 5-------
┌─────────┬──────────────┬────────┬─────┐
│this/incr│pc │p │total│
├─────────┼────────┬─────┼────────┼─────┤
│incr⁰ │Process⁰│Done⁰│Process¹│1 │
│ ├────────┼─────┼────────┼─────┤
│ │Process¹│sta⁰ │ │ │
└─────────┴────────┴─────┴────────┴─────┘
┌────────────┬───┐
│this/Process│reg│
├────────────┼───┤
│Process⁰ │1 │
├────────────┼───┤
│Process¹ │1 │
└────────────┴───┘
------State 6-------
┌─────────┬──────────────┬────────┬─────┐
│this/incr│pc │p │total│
├─────────┼────────┬─────┼────────┼─────┤
│incr⁰ │Process⁰│Done⁰│Process¹│1 │
│ ├────────┼─────┼────────┼─────┤
│ │Process¹│Done⁰│ │ │
└─────────┴────────┴─────┴────────┴─────┘
┌────────────┬───┐
│this/Process│reg│
├────────────┼───┤
│Process⁰ │1 │
├────────────┼───┤
│Process¹ │1 │
└────────────┴───┘
The order is
p0 p1 total
p1.lda x 0 0
p1.inc x 1 0
p0.lda 0 1 0
p0.inc 1 1 0
p0.sta 1 1 1
p1.sta 1 1 1
Perfectly demonstrating the problem of concurrent updates!
GCD
One of the first examples in PlusCal is Euclid
let N = { n : Int | n >= 1}
fun divisors[ a : N ] : set N {
{ r : N | a.rem[r] = 0 }
}
fun gcd[ a, b : N ] : lone Int {
(divisors[a] & divisors[b]).max
}
algorithm euclid[ a, b : N, var u, v : Int ] {
u := a
v := b
while( u != 0 ) {
if ( u < v ) {
u := v
v := u
}
assign: u := u.minus[v]
}
assert valid gcd[a,b] = v
}
check valid
This does not find any counterexamples.
Syntax
The syntax (a bit informal) is so far:
algorithm ID ('[' decls ']')? (':' expr ) compound
decls ::= decl (',' decl)*
compound ::= '{' statement * '}'
statement ::= while | if | assignment | label | compound | skip
while ::= 'while' '(' expr ')' compound
skip ::= 'skip'
if ::= 'if' '(' expr ')' compound ( 'else' compound )?
label ::= ID ':'
assignment::= lvar ':=' expr
assert ::= 'assert' ID expr
lvar ::= ID | lvar '.' ID | lvar '[' lvar ] ']'
expr ::= <alloy expression>
Status
I’ve got this implemented in the Electrum 2.0.1 codebase. I’ve been able to keep it really separate from the main codebase. Of course, I needed to change the parser but that went ok. However, so far it is a prototype and not a lot of error handling is going on. If you’re interested I can share the codebase on Github. As said, not a lot of error handling and running it in the debugger gives you access to the generated code. Haven’t found the time yet to show the generated code in the GUI.
There is currently a limitation that I generate the predicates and sigs before the module is resolved. This feels wrong and I am afraid this is going to bite me. It also forces me to do some resolving myself to find the proper scope, which is ugly. It also requires any sigs to be defined above the algorithm, also ugly. However, there is a chicken-egg problem since the generated predicates, especially the overall predicate, needs to be there to be resolved.
I did not implement ‘with’. I’ve not spent a lot of time on it and I am not sure we need it, but this might be my lack of understanding.
Preliminary Conclusion
Interestingly, I like Alloy mostly for its specification aspects. I see it potentially as a great tool to specify the behaviour and not as much as verifying the behaviour. However, the Time
concept in Alloy always felt too complicated. I like the var
keyword because it makes handling and reasoning about state so much easier. However, writing all those event predicates, and especially making sure that unused variables are not changing between states, is a royal pain in the ass. A procedural approach solves many of those problems because the majority of the state is local and implicit. For me, it is also important that I can talk to my fellow developers in a language that has a much lower impedance mismatch than Alloy’s core language, while still having Alloy as the incredibly powerful expression language. And of course the solvers.
So I looked at PlusCal and found that it seems to map very well to Alloy. Actually, I think Alloy is far superior as the host language. PlusCal makes a very ad hoc impression, this variation leverages a lot of the more powerful Alloy/Electrum features. I’ve not tested it, but it would not surprise me if we could execute the verification a lot faster than TLC.
As usual, I’ve spent way too much time on it. So I am at a decision point to burn more time on this or give up. I like the feature a lot but there have been many times my lack of intuition in this area has burnt me. So please let me know what you think.