1queuelive prophecy3
Example of using a prophecy variable to avoid a quantifier alternation in a liveness proof.
include order
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
isolate bar = {
action send(x:id)
action recv
specification {
var head : nat
var tail : nat
var queue(X:nat) : id
after init {
head := 0;
tail := 0;
}
instance sending : signal(id)
instance receiving : signal(id)
before send {
sending.raise(x);
queue(tail) := x;
tail := tail.next;
}
before recv {
require head < tail;
call receiving.raise(queue(head));
head := head.next;
}
We use the notation (t whenfirst p) which returns the value of term t at the first time that predicate p is true.
In this case, we want to identifiy the value of the tail pointer at the first time the message X is sent, since this is the location in the queue that will hold messge X. The 'work_needed' predicate is tus defined to be the set of queue positions up to the one that will hold X. We do this to avoid using a quantifier to define work_needed as the set of positions up to the least one currently holding X.
To do this proof, we use tactic l2s_auto2. This differs from l2s_auto in that we require that there is always work left to be done only after the work_start event. This work must also satisfy the 'work_end' predicate. The 'work_end' predicate represents the work that actually causes the the desired eventuality to occur (as opposed to work that needs to be done before this, but does not cause the eventuality). In this case work_done is true for queue cells that contain X. The invariant the there is always work to be done satisfying work_end means in this case that there is always a queue cell above the head pointer that contans X, so we can guarantee that the head pointer never skips over X without X being received.
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_auto2 with {
definition work_created(X) = (X < tail)
definition work_needed(X) = X <= (tail whenfirst (sending.now & sending.value = _X))
definition work_done(X) = (X < head)
definition work_end(X) = queue(X) = _X
definition work_start = (sending.now & sending.value = _X)
definition work_progress = receiving.now
}
showgoals
}
}
} with nat
export bar.send
export bar.recv