2 (** Schorr-Waite algorithm for general-size records.
4 Author: Martin Clochard (Université Paris Sud)
6 Here the proof is carried out by proving an equivalent
7 recursive program. The recursive program can be justified
8 to be equivalent to the looping one:
9 all side-effects/exception throwing are done by running the loop body
10 (which features a minor modification to exit by exception),
11 so the recursive programs amounts to execute an arbitrary number
12 of time the loop body. It is immediately followed by an absurd statement
13 to enforce the equivalence with the loop which runs its body an
14 infinite number of times.
15 Although the added recursive structure can be seen to be computationally
16 irrelevant, it allows to discharge details about Schorr-Waite
17 stack management through a recursive procedure proof. The method
18 basically make explicit the derecursification used to obtain
19 Schorr-Waite algorithm.
21 See example verifythis_2016_tree_traversal for details about
22 the technique, applied to a similar algorithm for trees.
26 (** Component-as-array memory model, with null pointers
27 and arbitrary-sized memory blocks. *)
33 (** Memory locations *)
37 (** Marks used by Schorr-Waite *)
41 type pmem = map loc (map int loc)
42 (** Memory blocks have two parts: a marking part containing in particular
43 Schorr-Waite internal management data, and a sequence of pointers
44 to other memory blocks. *)
45 type memory = abstract {
46 (** Associate block size to location. *)
47 block_size : map loc int;
48 (** Pointers to other memory blocks. *)
49 mutable accessor : pmem;
51 mutable colors : map loc color;
53 (** Global instance for memory *)
56 val null () : loc ensures { result = null }
58 val is_null (l:loc) : bool ensures { result <-> l = null }
59 (** Get block size associated to a given location *)
60 val get_block_size (l:loc) : int
61 requires { l <> null }
63 ensures { result = memo.block_size[l] /\ result >= 0 }
64 (** Access to a mark *)
65 val get_color (l:loc) : color
66 requires { l <> null }
68 ensures { result = memo.colors[l] }
69 (** Set a mark. We also impose the restriction that when a block is
70 marked black, the given index must be coherent with the block size.
71 This impose special treatment for 0-sized memory blocks. *)
72 val set_color (l:loc) (c:color) : unit
73 requires { l <> null }
74 requires { match c with
76 | Black None -> memo.block_size[l] = 0
77 | Black (Some ind) -> 0 <= ind < memo.block_size[l]
79 writes { memo.colors }
80 ensures { memo.colors = old (memo.colors[l <- c]) }
81 (** Getter/Setter for pointer buffer *)
82 val get_acc (l:loc) (k:int) : loc
83 requires { l <> null /\ 0 <= k < memo.block_size[l] }
85 ensures { result = memo.accessor[l][k] }
86 val set_acc (l:loc) (k:int) (d:loc) : unit
87 requires { l <> null /\ 0 <= k < memo.block_size[l] }
88 writes { memo.accessor }
89 ensures { memo.accessor =
90 old (memo.accessor[l <- memo.accessor[l][k <- d]]) }
91 (** Take ghost snapshots of memory. *)
92 val ghost snapshot_acc () : pmem
94 ensures { result = memo.accessor }
95 val ghost snapshot_colors () : map loc color
97 ensures { result = memo.colors }
101 (** Define notions about the memory graph *)
109 predicate black (c:color) = c <> White
112 predicate edge (m:memory) (x y:loc) =
113 x <> null /\ (exists n. 0 <= n < m.block_size[x] /\ m.accessor[x][n] = y)
115 inductive path memory (x y:loc) =
116 | path_nil : forall m x. path m x x
117 | path_cons : forall m x y z. edge m x y /\ path m y z -> path m x z
118 (** DFS invariant. For technical reason, it must refer to different parts
119 of the memory at different time. The graph structure is given
120 via the initial memory, but the coloring is given via the current one. *)
121 predicate well_colored_on (graph gray:fset loc) (m:memory) (cl:map loc color) =
123 (forall x y. mem x graph /\ edge m x y /\ y <> null /\ black cl[x] ->
124 mem x gray \/ black cl[y]) /\
125 (forall x. mem x gray -> black cl[x])
126 (** Unchanged only concerns the graph shape, not the marks *)
127 predicate unchanged (m1 m2:memory) =
128 forall x n. x <> null /\ 0 <= n < m1.block_size[x] ->
129 m2.accessor[x][n] = m1.accessor[x][n]
133 (** Proof of Schorr-Waite algorithm *)
145 let black (l: loc) : bool
146 requires { l <> null }
148 ensures { result <-> black memo.colors[l] }
149 = match get_color l with White -> false | _ -> true end
153 let schorr_waite (root: loc) (ghost graph: fset loc) : unit
154 (** Root belong to graph (note: for simplicity, the graph set
155 may (and likely does) contain the null pointer. *)
156 requires { mem root graph }
157 (** Graph is closed by following edges *)
158 requires { forall l n.
159 mem l graph /\ l <> null /\ 0 <= n < memo.block_size[l] ->
160 mem memo.accessor[l][n] graph }
161 writes { memo.accessor, memo.colors }
162 (** The graph starts fully unmarked. *)
163 requires { forall x. mem x graph -> not black memo.colors[x] }
164 (** The graph structure is left unchanged. *)
165 ensures { unchanged (old memo) memo }
166 (** Every non-null location reachable from the root is marked black. *)
167 ensures { forall x. path (old memo) root x /\ x <> null ->
168 black memo.colors[x] }
169 (** Every other location is left with its previous color. *)
170 ensures { forall x. not path (old memo) root x /\ x <> null ->
171 memo.colors[x] = (old memo).colors[x] }
175 let p = ref (null ()) in
176 (** Schorr-Waite loop body. *)
178 (** Loop body specification: mindlessly repeat the underlying code. *)
179 requires { !p <> null /\ (!t = null \/ black memo.colors[!t]) ->
180 match memo.colors[!p] with
181 | Black (Some m) -> 0 <= m < memo.block_size[!p]
184 ensures { old (!p <> null \/ (!t <> null /\ not black memo.colors[!t])) }
185 ensures { old (!t <> null /\ not black memo.colors[!t] /\
186 memo.block_size[!t] = 0) ->
187 memo.colors = old memo.colors[!t <- Black None] /\
188 !t = old !t /\ !p = old (!p) /\ memo.accessor = old memo.accessor
190 ensures { old (!t <> null /\ not black memo.colors[!t] /\
191 memo.block_size[!t] > 0) ->
192 memo.colors = old memo.colors[!t <- Black (Some 0)] /\
193 !t = old memo.accessor[!t][0] /\ !p = old (!t) /\
194 memo.accessor = old memo.accessor[!t <- memo.accessor[!t][0 <- !p]] }
195 ensures { old (!t = null \/ black memo.colors[!t]) ->
196 match old (memo.colors[!p]) with
199 if n = old (memo.block_size[!p])
201 !t = old (!p) /\ !p = (old memo.accessor[!p])[m] /\
202 memo.colors = old (memo.colors) /\
204 (old memo.accessor)[old !p <-
205 (old memo.accessor[!p])[m <- old !t]]
206 else !p = old !p /\ !t = (old memo.accessor[!p])[n] /\
207 memo.colors = (old memo.colors)[old !p <- Black (Some n)] /\
208 let pi = (old memo.accessor[!p])[m] in
210 (old memo.accessor)[old !p <-
211 (old memo.accessor[!p])[n <- pi][m <- old !t]]
214 raises { Stop -> old(!p = null /\ (!t = null \/ black memo.colors[!t])) /\
215 memo.colors = old memo.colors /\ memo.accessor = old memo.accessor }
216 = (** Minor modification to the loop: it exits by exception. *)
217 if is_null !p && (is_null !t || black !t) then raise Stop;
218 if is_null !t || black !t then begin
219 match get_color !p with
221 let s = get_block_size !p in
223 if n = s then begin (* Pop *)
228 end else begin (* Swing *)
231 set_acc !p n (get_acc !p m);
233 set_color !p (Black (Some n))
238 let s = get_block_size !t in
239 if s = 0 then (* Mark & continue *) set_color !t (Black None)
240 else begin (* Mark & Push *)
245 set_color !p (Black (Some 0))
248 let rec aux (ghost gray:fset loc) : unit
250 requires { well_colored_on graph gray (memo at I) memo.colors }
251 requires { mem !t graph }
252 (* Non-marked nodes have unchanged structure with
253 respect to the initial one. *)
254 requires { forall x n.
255 x <> null /\ not black memo.colors[x] /\ 0 <= n < memo.block_size[x] ->
256 memo.accessor[x][n] = (memo.accessor at I)[x][n] }
257 (* 'stack frames' are maintained correctly *)
258 ensures { !t = old !t /\ !p = old !p }
259 (* Pointer buffer is left unchanged *)
260 ensures { unchanged (old memo) memo }
261 (* Maintain DFS invariant *)
262 ensures { well_colored_on graph gray (memo at I) memo.colors }
263 (* The top node get marked *)
264 ensures { black memo.colors[!t] \/ !t = null }
265 (* May not mark unreachable node, neither change marked node. *)
267 x <> null -> not path (memo at I) !t x \/ black (old memo.colors)[x] ->
268 memo.colors[x] = (old memo.colors)[x] }
269 (* Never 'exit' the loop during the recursive calls *)
270 raises { Stop -> false }
271 (* Terminates because there is a limited number of 'grayable' nodes. *)
272 variant { cardinal graph - cardinal gray }
274 if is_null !t || black !t then () else begin
275 let s = get_block_size !t in
276 let ghost g2 = add !t gray in
277 assert { path (memo at I) !t !t };
278 body (); (* Either push or mark & continue. *)
280 for i = 0 to s - 2 do (* Over all sub-blocs... *)
282 invariant { well_colored_on graph g2 (memo at I) memo.colors }
283 (* Current stack frame invariants *)
284 invariant { !p = !t at J }
285 invariant { !t = (memo.accessor at I)[!p][i] }
286 invariant { memo.colors[!p] = Black (Some i) }
287 invariant { forall j. 0 <= j < s /\ j <> i ->
288 memo.accessor[!p][j] = (memo.accessor at J)[!p][j] }
289 invariant { memo.accessor[!p][i] = !p at J }
290 (* Outside structure is unchanged. *)
291 invariant { forall l n.
292 l <> null /\ l <> !p /\ 0 <= n < memo.block_size[l] ->
293 memo.accessor[l][n] = (memo.accessor at J)[l][n] }
294 (* All nodes under !p & before i are either null or marked black. *)
295 invariant { forall j. 0 <= j < i ->
296 let l = memo.accessor[!p][j] in l = null \/ black memo.colors[l] }
297 (* Unreachable/pre-marked blocks do not change. *)
298 invariant { forall l. l <> null ->
299 not path (memo at I) !p l \/ black (memo.colors at J)[l] ->
300 memo.colors[l] = (memo.colors at J)[l] }
302 aux g2; (* Explore sub-bloc. *)
303 body (); (* Swing to next sub-bloc. *)
304 assert { !t = (memo.accessor at K)[!p][i+1]
305 = (memo.accessor at J)[!p][i+1] }
307 aux g2; (* Explore last sub-bloc. *)
311 try aux (ghost empty); (* Explore main bloc *)
315 (* Need induction to recover path-based specification. *)
316 assert { forall x y m. m = memo at I /\ x <> null /\ y <> null /\
317 mem x graph /\ black memo.colors[x] ->
318 ([@induction] path m x y) -> black memo.colors[y] }