Llqueue1
A model of a queue using a linked list
Properties:
1) Progress -- if we infinitely often call receive, then every sent messages is received 2) No memory leaks -- when the queue is empty there is always a free cell
include order
include collections
instance nat : unbounded_sequence
module signal(data) = {
action raise(val:data)
specification {
relation now
var value : data
after init { now := false; }
before raise {
value := val;
now := true;
now := false;
}
invariant ~now
}
}
type id
type cell
isolate bar = {
action send(x:id)
action recv
specification {
var head : nat
var tail : nat
var queue(X:nat) : id
var head_cell : cell # Pointer to head cell if any
var tail_cell : cell # Pointer to tail cell if any
var full(X:cell):bool # Full bit for each cell
var val(X:cell):id # Message contents of cell, if any
instance link : partial_function(cell,cell) # Links
var empty : bool # The queue is empty
var index(X:cell) : nat
after init {
head := 0;
tail := 0;
full(C) := false;
empty := true;
}
instance sending : signal(id)
instance receiving : signal(id)
before send {
require exists X. ~full(X);
if some c:cell. ~full(c) {
queue(tail) := x;
full(c) := true;
val(c) := x;
if ~empty {
link.remap(tail_cell,c);
} else {
head_cell := c;
}
tail_cell := c;
empty := false;
index(c) := tail;
tail := tail.next;
sending.raise(x);
}
}
before recv {
require ~empty;
call receiving.raise(val(head_cell));
head := head.next;
full(head_cell) := false;
if head_cell = tail_cell {
empty := true;
} else {
var c := link.get(head_cell);
link.remove(head_cell);
head_cell := c;
}
assert index(C) = index(C);
}
temporal property
forall X. ((globally eventually receiving.now)
& (eventually sending.now & sending.value = X) ->
(eventually receiving.now & receiving.value = X))
proof {
tactic skolemize;
tactic l2s_auto with {
definition work_created(X) = (X < tail)
definition work_needed(X) = ~exists Y. Y < X & queue(Y) = _X
definition work_done(X) = (X < head)
definition work_start = (sending.now & sending.value = _X)
definition work_progress = receiving.now
invariant empty <-> head = tail
invariant head <= tail
invariant full(C) -> index(C) < tail
invariant full(C) -> head <= index(C)
invariant ~empty -> full(head_cell) & index(head_cell) = head
invariant ~empty -> full(tail_cell) & nat.succ(index(tail_cell),tail)
invariant link.pre(C) <-> full(C) & C ~= tail_cell
invariant link.map(C,D) <-> full(C) & full(D) & nat.succ(index(C),index(D))
invariant full(C) & full(D) & index(C) = index(D) -> C = D
invariant full(C) -> val(C) = queue(index(C))
}
showgoals
}
invariant empty -> exists X. ~full(X)
invariant empty <-> head = tail
invariant head <= tail
invariant full(C) -> index(C) < tail
invariant full(C) -> head <= index(C)
invariant ~empty -> full(head_cell) & index(head_cell) = head
invariant ~empty -> full(tail_cell) & nat.succ(index(tail_cell),tail)
invariant link.pre(C) <-> full(C) & C ~= tail_cell
invariant link.map(C,D) <-> full(C) & full(D) & nat.succ(index(C),index(D))
invariant full(C) & full(D) & index(C) = index(D) -> C = D
}
} with nat
export bar.send
export bar.recv