This file contains the processed and commented output of HOL4 when run on the ring-buffer example. The unprocessed output can be found here. The original output was produced by holfoot-full -f ring_buffer.hol.
First HOL4 processes the setup of the framework and the definition of the ring-buffer predicate. For reasons of readability, the following contains just a few selected outputs.
val EXTRACT_SUBLIST_def = |- ∀first len data. EXTRACT_SUBLIST first len data = TAKE len (DROP first (data ++ data)): thm val EXTRACT_SUBLIST_ALT_DEF = |- first < LENGTH data ∧ len ≤ LENGTH data ⇒ (EXTRACT_SUBLIST first len data = SEG len first (data ++ data)): thm val holfoot_ap_ring_buffer_REWRITE2 = |- ∀dta b s data. holfoot_ap_ring_buffer dta b s data = b ⟼ [size:s, len:LENGTH data, first: #_f, data: #_d] ★ (s = #_sv) ★ data_array(#_d, s, dta:_full_data) ★ pure(_f < _sv ∧ LENGTH data ≤ _sv ∧ (_sv = LENGTH _full_data) ∧ (data = EXTRACT_SUBLIST _f (LENGTH data) _full_data)): thm
Next, the auxiliary lemmata are shown.
val EXTRACT_SUBLIST___len_0 = |- EXTRACT_SUBLIST first 0 data = []: thm val EXTRACT_SUBLIST___data_Nil = |- EXTRACT_SUBLIST first l [] = []: thm val EL_EXTRACT_SUBLIST = |- n < len ∧ len ≤ LENGTH data ∧ first < LENGTH data ⇒ (EL n (EXTRACT_SUBLIST first len data) = EL (if n + first < LENGTH data then n + first else (n + first − LENGTH data)) data): thm val EXTRACT_SUBLIST___head = |- (EXTRACT_SUBLIST first len data = x::xs) ∧ first < LENGTH data ∧ len ≤ LENGTH data ⇒ (EL first data = x): thm val SEG_SUC_CONS_TL = |- ∀m n l. m + SUC n ≤ LENGTH l ⇒ (SEG m (SUC n) l = TL (SEG (SUC m) n l)): thm val SEG_SUC_CONS_SNOC = |- ∀m n l. SUC m + n ≤ LENGTH l ⇒ (SEG (SUC m) n l = SNOC (EL (m + n) l) (SEG m n l)): thm val SEG_FIRSTN = |- ∀m n l. m + n < LENGTH l ⇒ (SEG m n l = SEG m n (TAKE (m + n) l)): thm val EXTRACT_SUBLIST___pop = |- (EXTRACT_SUBLIST first len data = x::xs) ∧ first < LENGTH data ∧ len ≤ LENGTH data ⇒ (EXTRACT_SUBLIST ((first + 1) MOD LENGTH data) (len − 1) data = xs): thm val EXTRACT_SUBLIST___push = |- first < LENGTH data ∧ len < LENGTH data ⇒ (EXTRACT_SUBLIST first len data ++ [x] = EXTRACT_SUBLIST first (len + 1) (REPLACE_ELEMENT x ((first + len) MOD LENGTH data) data)): thm
Then the specialiased tactics to verify the procedures are defined.
val clear_TAC = fn: tactic val create_TAC = fn: tactic val head_TAC = fn: tactic val pop_TAC = fn: tactic val push_TAC = fn: tactic val test_TAC = fn: tactic
Finally the input file ring_buffer.dfs is processed using these tactics.
parsing ... 0.066 s - OK HOLFOOT_SPECIFICATION ( create(r; n_const) [w/r: r; | (0 < #n_const )] { local (n = n_const), d; r = new() ; d = new-block (n) [dta]; r→size = n; r→len = 0; r→first = 0; r→data = d; } [w/r: r; | ring_buffer(r, #n_const, dta: [])] clear(; b_const) [w/r: ; | ring_buffer(#b_const, #s, dta: data)] { local (b = b_const); b→len = 0 } [w/r: ; | ring_buffer(#b_const, #s, dta: [])] head(r; b_const) [w/r: r; | ring_buffer(#b_const, #s, dta: x::xs)] { local (b = b_const), v_f, v_d; v_f = b→first; v_d = b→data; r = (v_d + v_f)→dta; } [w/r: r; | (r = #x) ★ ring_buffer(#b_const, #s, dta: x::xs)] push(; b_const, x_const) [w/r: ; | ring_buffer(#b_const, #s, dta: xs) ★ pure(LENGTH xs < s)] { local (b = b_const), (x = x_const), v_s, v_f, v_l, v_d; v_f = b→first; v_l = b→len; v_s = b→size; v_d = b→data; (v_d + ((v_f + v_l) MOD v_s) )→dta = x; b→len = (v_l + 1); } [w/r: ; | ring_buffer(#b_const, #s, dta: xs ++ [x_const])] pop(r; b_const) [w/r: r; | ring_buffer(#b_const, #s, dta: x::xs)] { local (b = b_const), v_f, v_d, v_l, v_s; v_f = b→first; v_d = b→data; v_l = b→len; v_s = b→size; r = (v_d + v_f)→dta; b→len = (v_l − 1); b→first = ((v_f + 1) MOD v_s); } [w/r: r; | (r = #x) ★ ring_buffer(#b_const, #s, dta: xs)] test(b; x_const, y_const, z_const) [w/r: b; | pure(T)] { local (x = x_const), (y = y_const), (z = z_const), h; create(b; 2); push(; b, x); push(; b, y); pop(h; b); assert [(h = x)]; push(; b, z); pop(h; b); assert [(h = y)]; pop(h; b); assert [(h = z)]; } [w/r: b; | ring_buffer(b, 2, dta: [])] ) preprocessing ... 0.179 s - OK verifying specification ... * create ... 0.252 s - OK * clear ... 0.223 s - OK * head ... 0.539 s - OK * push ... 1.529 s - OK * pop ... 0.648 s - OK * test ... 0.340 s - OK done time needed: 5.031 s
This processing proves a HOL4 theorem that states that the specification of the program is indeed correct. The pretty-printed version of this HOL4-theorem looks very similar. Here the theorem is presented next to the input file ring_buffer.dsf.
val final_thm = |- HOLFOOT_SPECIFICATION ( create(r; n_const) [w/r: r; | (0 < #n_const )] { local (n = n_const), d; r = new() ; d = new-block (n) [dta]; r→size = n; r→len = 0; r→first = 0; r→data = d; } [w/r: r; | ring_buffer(r, #n_const, dta: [])] clear(; b_const) [w/r: ; | ring_buffer(#b_const, #s, dta: data)] { local (b = b_const); b→len = 0 } [w/r: ; | ring_buffer(#b_const, #s, dta: [])] head(r; b_const) [w/r: r; | ring_buffer(#b_const, #s, dta: x::xs)] { local (b = b_const), v_f, v_d; v_f = b→first; v_d = b→data; r = (v_d + v_f)→dta; } [w/r: r; | (r = #x) ★ ring_buffer(#b_const, #s, dta: x::xs)] push(; b_const, x_const) [w/r: ; | ring_buffer(#b_const, #s, dta: xs) ★ pure(LENGTH xs < s)] { local (b = b_const), (x = x_const), v_s, v_f, v_l, v_d; v_f = b→first; v_l = b→len; v_s = b→size; v_d = b→data; (v_d + ((v_f + v_l) MOD v_s) )→dta = x; b→len = (v_l + 1); } [w/r: ; | ring_buffer(#b_const, #s, dta: xs ++ [x_const])] pop(r; b_const) [w/r: r; | ring_buffer(#b_const, #s, dta: x::xs)] { local (b = b_const), v_f, v_d, v_l, v_s; v_f = b→first; v_d = b→data; v_l = b→len; v_s = b→size; r = (v_d + v_f)→dta; b→len = (v_l − 1); b→first = ((v_f + 1) MOD v_s); } [w/r: r; | (r = #x) ★ ring_buffer(#b_const, #s, dta: xs)] test(b; x_const, y_const, z_const) [w/r: b; | pure(T)] { local (x = x_const), (y = y_const), (z = z_const), h; create(b; 2); push(; b, x); push(; b, y); pop(h; b); assert [(h = x)]; push(; b, z); pop(h; b); assert [(h = y)]; pop(h; b); assert [(h = z)]; } [w/r: b; | ring_buffer(b, 2, dta: [])] ): thm |
/* Holfoot is easily extensible with new predicates. In the corresponding proof-script-file "ring_buffer.hol" a new predicate "ring_buffer" is defined. A detailed explanation can be found there. Here is just a high level one: ring_buffer (b, s, d) denotes that a heap location "b" a record with the entries "data", "size", "first" and "len" is stored. The entries "size", "first", "len" have the obvious meaning. The ring-buffer predicate "ring_buffer (b, s, d)" demands that the value "s" is stored in "size" and the size of the data in "len". "data" however is in this case not the array itself, but a pointer to an array of the right size "s". Doing the described mapping operation and using "first" and "len", the list "d" is a stored in this array. This list "d" is the logical content of the buffer. */ /* The function "create" gets a call-by-reference argument "r" and a call-by-value one "n". "r" is used as the return value. "n" is the size of the created buffer. The pre-condition is "True", the procedure allocates a ring buffer and guarantees that there is an empty ring-buffer of the right size is there at the end. */ create(r;n) [0 < n] { local d; r = new(); /* Allocate memory for "r" */ d = new(n) [dta]; /* Allocate memory for the array */ /* initialise the record in "r" */ r -> size = n; r -> len = 0; r -> first = 0; r -> data = d; } [ring_buffer(r,n,``[]``)] /* "clear" gets a call-by-value argument "b" that has to point to some ring-buffer containing some arbitrary data of and arbitrary length. It guarantees that there is in the end an empty ring-buffer of the right size at "b". Moreover, the semantics of separation logic guarantee, that there is no memory leak. */ clear(b) [ring_buffer(b,#s,data)] { b -> len = 0; } [ring_buffer(b,#s,``[]``)] /* "head" gets two arguments. A call-by-reference one "r" that is used a the return value and a call-by-value one "b". The precondition demands that "b" points to a ring-buffer of some size "#s". Notice that "#" denotes that "s" is a meta variable and not a program variable. The buffer logically contains the list starting with "x" and containing with a list "xs". Therefore the length of the buffer is implicitly given as "LENGTH (xs :: xs)". The post-condition guarantees that there is still the same ring-buffer with the same content and that "r" has been updated to now contain the value "x". */ head(r;b) [ring_buffer(b, #s, ``x :: xs``)] { local v_f, v_d; /* read "first" and "data" from memory */ v_f = b -> first; v_d = b -> data; /* lookup the right value */ r = (v_d + v_f) -> dta; } [r == #x * ring_buffer(b,#s,``x :: xs``)] /* "push" gets two call-by-value arguments. "b" points to a ring-buffer. "x" should be inserted into this buffer. The precondition demands that "b" to point to a ring-buffer of some size "#s" containing logically the list "xs". Moreover, "LENGTH xs < #s" has to hold, i.e. the buffer is not full. The post-condition guarantees that "x" has been inserted at the end of the list and the length of the buffer has been incremented. */ push(b,x) [ring_buffer(b,#s,``xs``) * ``LENGTH xs < #s``] { local v_s, v_f, v_l, v_d; /* read the record */ v_f = b -> first; v_l = b -> len; v_s = b -> size; v_d = b -> data; /* store x, the operation means a [(first + len) MOD size] <- x */ (v_d + ``(v_f + v_l) MOD v_s``) -> dta = x; /* update length */ b -> len = v_l + 1; } [ring_buffer(b,#s,``xs ++ [x]``)] /* "pop" gets two arguments. A call-by-reference one "r" that is used a the return value and a call-by-value one "b". The precondition demands that "b" points to a ring-buffer of some size "#s". The buffer contains the list starting with "x" and containing with a list "xs". The precondition of "pop" is the same as the precondition of "head". However, in contrast to "head", "x" is removed from the ring-buffer. The post-condition guarantees that there is the ring-buffer without "x" and that "r" has been updated to now contain the value "x". */ pop(r;b) [ring_buffer(b,#s,``x :: xs``)] { local v_f, v_d, v_l, v_s; /* read the record */ v_f = b -> first; v_d = b -> data; v_l = b -> len; v_s = b -> size; /* lookup the right value */ r = (v_d + v_f) -> dta; /* decrement length */ b->len = v_l - 1; /* increment first */ b->first = ``(v_f + 1) MOD v_s``; } [r == #x * ring_buffer(b,#s,``xs``)] /* The test code. Since Holfoot looks also for garbage left in memory an extra call-by-reference argument "b" is passed. This argument is used to point at the newly created buffer. The benefit compared to a local variable is that it can be referred to in the post-condition. */ test(b; x, y, z) [] { local h; create(b;2); push(b, x); push(b, y); pop (h; b); assert [h == x]; push(b, z); pop (h; b); assert [h == y]; pop (h; b); assert [h == z]; } [ring_buffer(b, 2, ``[]``)] |