4 {1 VerifyThis @ ETAPS 2015 competition, Challenge 2: Parallel GCD}
8 The following is the original description of the verification task,
9 reproduced verbatim from
10 <a href="http://etaps2015.verifythis.org/challenges">the competition web site</a>.
13 PARALLEL GCD (60 minutes)
14 =========================
19 Various parallel GCD algorithms exist. In this challenge, we consider a
20 simple Euclid-like algorithm with two parallel threads. One thread
21 subtracts in one direction, the other thread subtracts in the other
22 direction, and eventually this procedure converges on GCD.
28 In pseudocode, the algorithm is described as follows:
32 IF a>b THEN a:=a-b ELSE SKIP FI
36 IF b>a THEN b:=b-a ELSE SKIP FI
45 Specify and verify the following behaviour of this parallel GCD algorithm:
47 Input: two positive integers a and b
48 Output: a positive number that is the greatest common divisor of a and b
50 Feel free to add synchronisation where appropriate, but try to avoid
51 blocking of the parallel threads.
57 If your tool does not support reasoning about parallel threads, you may
58 verify the following pseudocode algorithm:
62 IF a > b THEN a := a - b ELSE SKIP FI,
63 IF b > a THEN b := b - a ELSE SKIP FI
70 The following is the solution by Jean-Christophe Filliâtre (CNRS)
71 and Guillaume Melquiond (Inria) who entered the competition as "team Why3".
73 Since Why3 has no support for threads, we prove the sequential
74 implementation. We do not prove termination, which would require some
75 fairness hypothesis (in that case, you can prove that the code
76 terminates with probability 1).
86 (** the following lemma is easily derived from a more general
87 lemma in library [number.Gcd], namely [gcd_euclid].*)
88 lemma gcd_sub: forall a b: int. gcd a b = gcd a (b - a)
90 let parallel_gcd (a0 b0: int) : int
91 requires { 0 < a0 /\ 0 < b0 }
93 ensures { result = gcd a0 b0 }
98 invariant { 0 < !a <= a0 /\ 0 < !b <= b0 }
99 invariant { gcd !a !b = gcd a0 b0 }
101 if !a > !b then a := !a - !b else ()
103 if !b > !a then b := !b - !a else ()
109 (** Threads interleaving.
110 Code and invariants by Rustan Leino.
111 Termination argument by Martin Clochard and Léon Gondelman.
112 Proof by Martin Clochard and Léon Gondelman.
120 lemma gcd_sub: forall a b: int. gcd a b = gcd a (b - a)
122 (* Representation of a thread: two local variables
123 (register copies of the globals) and a program counter:
130 if local_a = local_b goto Halt;
131 if local_a > local_b a := local_a - local_b;
135 For the sake of simplicity, every section is considered atomic.
136 (strictly speaking, section Compare is not, but it interacts
137 atomically with memory so it would be equivalent)
139 type state = ReadA | ReadB | Compare | Halt
142 mutable local_a: int; (* local copy of a *)
143 mutable local_b: int; (* local copy of b *)
144 mutable state : state;
147 (* Thread invariant. *)
148 predicate inv (th: thread) (d a b: int) =
149 0 < a /\ 0 < b /\ gcd a b = d /\
152 | ReadB -> th.local_a = a (* No other thread can write in a. *)
153 | Compare -> th.local_a = a && b <= th.local_b &&
154 (th.local_b <= th.local_a -> th.local_b = b)
155 (* Other thread may have overwritten b, but only in a decreasing
156 decreasing fashion, and only once under a. *)
157 | Halt -> th.local_a = a = b (* Final state is stable. *)
160 (* Does running this thread make any progress toward the result? *)
161 predicate progress_thread (th: thread) (a b: int) =
162 a > b \/ (a = b /\ th.state <> Halt)
164 (* Decreasing ordering on program counter *)
165 function state_index (s: state) : int = match s with
172 (* Synchronisation status. *)
173 predicate sync (th: thread) (b: int) =
174 match th.state with Compare -> th.local_b = b | _ -> true end
176 (* Convert status into an index. *)
177 function sync_index (th: thread) (b: int) : int =
178 if sync th b then 0 else 42
180 (* Thread progression index: if running this thread should make any
181 progression toward the result, then it will have the following shape:
182 - A first (optional) loop run for synchronization.
183 - A second synchronized run until effective progress *)
184 function prog_index (th: thread) (b: int) : int =
185 sync_index th b + state_index th.state
187 val create_thread () : thread
188 ensures { result.state = ReadA }
190 (* Fair scheduler modelisation: Each time it switches between threads,
191 it also writes down the maximal time remaining before it
192 will switch to the other.
193 If it does not switch, this timeout decreases. *)
194 val ghost scheduled : ref bool
195 val ghost timer : ref int
197 val schedule () : bool
198 writes { scheduled, timer }
199 ensures { !scheduled = old !scheduled -> 0 <= !timer < old !timer }
200 ensures { result = !scheduled }
202 (* Execution of one thread step. *)
203 let step (th: thread) (ghost d: int) (a b: ref int)
204 requires { inv th d !a !b }
206 ensures { inv th d !a !b }
207 ensures { 0 < !a <= old !a }
208 ensures { old !a > !a -> old !a >= !a + !b }
209 ensures { progress_thread th !a !b ->
210 prog_index (old th) !b > prog_index th !b \/ !a < old !a }
214 th.local_a <- !a; th.state <- ReadB
216 th.local_b <- !b; th.state <- Compare
218 if th.local_a = th.local_b then
221 if th.local_a > th.local_b then a := th.local_a - th.local_b;
228 let can_progress (s : state)
229 ensures { result = True <-> s <> Halt }
230 = match s with Halt -> False | _ -> True end
232 let parallel_gcd (a0 b0: int) : int
233 requires { 0 < a0 /\ 0 < b0 }
234 ensures { result = gcd a0 b0 }
236 (* shared variables *)
239 let ghost d = gcd a0 b0 in
241 let th1 = create_thread () in
242 let th2 = create_thread () in
243 while can_progress th1.state || can_progress th2.state do
244 invariant { inv th1 d !a !b /\ inv th2 d !b !a }
245 variant { (* global progress in the algorithm *)
248 (* progress in one of the two threads *)
250 then prog_index th2 !a + prog_index th1 !b
252 then prog_index th2 !a
253 else prog_index th1 !b end
255 (* no progress in both threads, but the scheduler
256 switches to the non-progressing thread *)
257 begin if progress_thread th1 !a !b
258 then if !scheduled then 1 else 0
259 else if progress_thread th2 !b !a
260 then if !scheduled then 0 else 1
263 (* the scheduler is still running the non-progressing thread *)
265 if schedule () then step th1 d a b else step th2 d b a