mirror of
https://github.com/AdaCore/why3.git
synced 2026-02-12 12:34:55 -08:00
189 lines
6.6 KiB
Plaintext
189 lines
6.6 KiB
Plaintext
(**
|
|
|
|
{1 VerifyThis @ ETAPS 2018 competition
|
|
Challenge 3: Array-Based Queuing Lock}
|
|
|
|
Author: Martin Clochard (LRI, Université Paris Sud)
|
|
*)
|
|
|
|
use int.Int
|
|
use int.ComputerDivision
|
|
use import seq.Seq as S
|
|
use array.Array
|
|
use ref.Ref
|
|
|
|
val constant k : int ensures { result > 0 }
|
|
val constant n : int ensures { result > 0 }
|
|
|
|
(* Model of bounded arithmetic.
|
|
Note: bincrement only model the incrementation behind fetch_and_add,
|
|
not the atomic operation itself. *)
|
|
type bounded_int = private { ghost bmodel : int }
|
|
invariant { 0 <= bmodel < k * n } by { bmodel = 0 }
|
|
val constant bzero : bounded_int
|
|
ensures { result.bmodel = 0 }
|
|
val bincrement (b:bounded_int) : bounded_int
|
|
ensures { let v = b.bmodel + 1 in
|
|
result.bmodel = if v = k * n then 0 else v }
|
|
val bmodn (b:bounded_int) : int
|
|
ensures { result = mod b.bmodel n }
|
|
|
|
(* Minor ghost wrapping of the model to get rid of k from the model,
|
|
while keeping the same operational meaning. *)
|
|
type bounded_int2 = {
|
|
value : bounded_int;
|
|
ghost model : int;
|
|
} invariant { 0 <= model < n }
|
|
invariant { model = mod value.bmodel n }
|
|
by { value = bzero; model = 0 }
|
|
type ticket = { tvalue : int } invariant { 0 <= tvalue < n }
|
|
|
|
let zero () : bounded_int2
|
|
ensures { result.model = 0 }
|
|
= { value = bzero; model = 0 }
|
|
let increment (b:bounded_int2) : bounded_int2
|
|
ensures { let v = b.model + 1 in
|
|
result.model = if v = n then 0 else v }
|
|
= let ghost v0 = b.model in
|
|
let ghost v1 = if v0+1 = n then 0 else v0 + 1 in
|
|
let ghost v2 = b.value.bmodel in
|
|
assert { mod (v2+1) n = v1 by exists q.
|
|
v2 = n * q + v0 so q >= 0 so v2 + 1 = n * q + (v0+1)
|
|
so if v0+1 < n then v0+1 = mod (v2+1) n else
|
|
v2+1 = n * (q+1) + 0 so 0 = mod (v2+1) n };
|
|
{ value = bincrement b.value; model = v1 }
|
|
let modn (b:bounded_int2) : ticket
|
|
ensures { result.tvalue = b.model }
|
|
= { tvalue = bmodn b.value }
|
|
let tinc (t:ticket) : ticket
|
|
ensures { let v = t.tvalue + 1 in result.tvalue = if v = n then 0 else v }
|
|
= { tvalue = mod (t.tvalue + 1) n }
|
|
|
|
(* Break down the thread state between each operation that affect or
|
|
read the global state *)
|
|
type thread_state =
|
|
| AcqFetched ticket (* Corresponds to control point right after the fetch. *)
|
|
| Granted ticket (* Corresponds to observation that pass was true.
|
|
If it was false, there may be intermediate steps, but they do not
|
|
rely on global state --> state equivalent to lock granted. *)
|
|
| RelSet ticket (* Corresponds to the pass = false operation. *)
|
|
| Released ticket (* Corresponds to a released state.
|
|
for technical reasons, we keep the last ticket used here
|
|
(which is defaulted to the thread id at the beginning *)
|
|
|
|
function ticket (s:thread_state) : int = match s with
|
|
| AcqFetched t | Granted t | RelSet t | Released t -> t.tvalue
|
|
end
|
|
|
|
predicate released (s:thread_state) = match s with
|
|
| Released _ -> true | _ -> false
|
|
end
|
|
|
|
(* For verification of task 2: log of request/acquire events
|
|
identified by thread id. *)
|
|
type event =
|
|
| Request int
|
|
| Acquire int
|
|
|
|
type hidden = private mutable {}
|
|
val hidden : hidden
|
|
|
|
(* scheduler. *)
|
|
val schedule () : int
|
|
writes { hidden }
|
|
ensures { 0 <= result < n }
|
|
|
|
(* if a thread is in a granted or released state,
|
|
check whether it begins an acquire or release. Otherwise,
|
|
the state stays the same. *)
|
|
val acqrel (id: int) : bool
|
|
writes { hidden }
|
|
|
|
(* Model execution of concurrent program. *)
|
|
let main () : unit
|
|
diverges
|
|
= (* Model of the pass buffer. The begin-end block with post-condition
|
|
hide the fact that the array was technically initialized. *)
|
|
let pass = begin ensures { result.length = n } make n false end in
|
|
(* Model of the next variable. *)
|
|
let next = ref (zero ()) in
|
|
(* Thread state. *)
|
|
let state = make n (Released { tvalue = 0 }) in
|
|
(* Additional reciprocal map for the cyclically allocated tickets. *)
|
|
let tmap = make n 0 in
|
|
for i = 0 to n - 1 do
|
|
invariant { forall j. 0 <= j < i -> match state[j] with
|
|
| Released t -> t.tvalue = j
|
|
| _ -> false end }
|
|
invariant { forall j. 0 <= j < i -> tmap[j] = j }
|
|
state[i] <- Released { tvalue = i };
|
|
tmap[i] <- i
|
|
done;
|
|
(* Extra variable for verification: index of the currently held ticket. *)
|
|
let current = ref (modn (zero ())) in
|
|
(* Event log (for second task). *)
|
|
(* let log = ref empty in *)
|
|
(* Initialisation, done before the threads are executed. *)
|
|
for i = 1 to n - 1 do
|
|
invariant { forall j. 1 <= j < i -> not pass[j] }
|
|
pass[i] <- false;
|
|
done;
|
|
pass[0] <- true;
|
|
while true do
|
|
invariant { forall i. 0 <= i < n /\ pass[i] -> i = !current.tvalue }
|
|
invariant { forall i. 0 <= i < n -> match state[i] with
|
|
| Granted t -> t.tvalue = !current.tvalue /\ pass[t.tvalue]
|
|
| RelSet t -> t.tvalue = !current.tvalue /\ not pass[t.tvalue]
|
|
| _ -> true
|
|
end
|
|
}
|
|
invariant { forall i. 0 <= i < n -> 0 <= tmap[i] < n }
|
|
invariant { forall i. 0 <= i < n -> ticket state[tmap[i]] = i }
|
|
invariant { forall i. 0 <= i < n -> tmap[ticket state[i]] = i }
|
|
invariant { !next.model < !current.tvalue ->
|
|
(forall i. 0 <= i < !next.model \/ !current.tvalue <= i < n ->
|
|
not released state[tmap[i]])
|
|
/\ (forall i. !next.model <= i < !current.tvalue ->
|
|
released state[tmap[i]]) }
|
|
invariant { !next.model > !current.tvalue ->
|
|
(forall i. !current.tvalue <= i < !next.model ->
|
|
not released state[tmap[i]])
|
|
/\ (forall i. 0 <= i < !current.tvalue \/ !next.model <= i < n ->
|
|
released state[tmap[i]]) }
|
|
invariant { !next.model = !current.tvalue ->
|
|
forall i j. 0 <= i < j < n ->
|
|
released state[tmap[i]] <-> released state[tmap[j]] }
|
|
(* Invariant corresponding to Task 1, slightly strengthened. *)
|
|
invariant { forall i j. 0 <= i < j < n ->
|
|
match state[i], state[j] with
|
|
| (Granted _ | RelSet _), (Granted _ | RelSet _) -> false
|
|
| _ -> true end
|
|
}
|
|
let id = schedule () in
|
|
match state[id] with
|
|
| AcqFetched ticket ->
|
|
if pass[ticket.tvalue] then state[id] <- Granted ticket
|
|
| Granted ticket -> if acqrel id then begin
|
|
state[id] <- RelSet ticket;
|
|
pass[ticket.tvalue] <- false;
|
|
end
|
|
| RelSet ticket ->
|
|
state[id] <- Released ticket;
|
|
pass[(tinc ticket).tvalue] <- true;
|
|
current := tinc !current;
|
|
| Released told -> if acqrel id then begin
|
|
let ticket = modn !next in
|
|
let id2 = tmap[ticket.tvalue] in
|
|
match state[id2] with
|
|
| Released _ -> state[id2] <- Released told;
|
|
tmap[ticket.tvalue] <- id;
|
|
tmap[told.tvalue] <- id2
|
|
| _ -> absurd
|
|
end;
|
|
state[id] <- AcqFetched ticket;
|
|
next := increment !next
|
|
end
|
|
end
|
|
done
|
|
|