mirror of
https://github.com/AdaCore/why3.git
synced 2026-02-12 12:34:55 -08:00
179 lines
4.6 KiB
Plaintext
179 lines
4.6 KiB
Plaintext
(**
|
|
|
|
{1 VerifyThis @ ETAPS 2017 competition
|
|
Challenge 4: Tree Buffer}
|
|
|
|
See https://formal.iti.kit.edu/ulbrich/verifythis2017/
|
|
|
|
Author: Jean-Christophe Filliâtre (CNRS)
|
|
*)
|
|
|
|
(* default implementation *)
|
|
module Spec
|
|
|
|
use export int.Int
|
|
use export list.List
|
|
|
|
type buf 'a = { h: int; xs: list 'a }
|
|
|
|
let rec function take (n: int) (l: list 'a) : list 'a =
|
|
match l with
|
|
| Nil -> Nil
|
|
| Cons x xs -> if n = 0 then Nil
|
|
else Cons x (take (n-1) xs) end
|
|
|
|
let function empty (h: int) : buf 'a =
|
|
{ h = h; xs = Nil }
|
|
|
|
let function add (x: 'a) (b: buf 'a) : buf 'a =
|
|
{ b with xs = Cons x b.xs }
|
|
|
|
let function get (b: buf 'a) : list 'a =
|
|
take b.h b.xs
|
|
|
|
(* the following lemma is useful to verify both Caterpillar and
|
|
RealTime implementations below *)
|
|
|
|
use list.Append
|
|
use list.Length
|
|
|
|
let rec lemma take_lemma (l1 l2 l3: list 'a) (n: int)
|
|
requires { 0 <= n <= length l1 }
|
|
ensures { take n (l1 ++ l2) = take n (l1 ++ l3) }
|
|
variant { l1 }
|
|
= match l1 with Nil -> ()
|
|
| Cons _ ll1 -> if n > 0 then take_lemma ll1 l2 l3 (n-1) end
|
|
|
|
end
|
|
|
|
(* task 1 *)
|
|
module Caterpillar
|
|
|
|
use Spec
|
|
use list.Append
|
|
use list.Length
|
|
|
|
type cat 'a = {
|
|
ch: int;
|
|
xs: list 'a;
|
|
xs_len: int;
|
|
ys: list 'a;
|
|
ghost b: buf 'a; (* the model is the default implementation *)
|
|
} invariant {
|
|
b.h = ch /\
|
|
xs_len = length xs < ch /\
|
|
forall len. 0 <= len <= ch -> take len (xs ++ ys) = take len b.xs
|
|
} by {
|
|
ch = 1; xs = Nil; xs_len = 0; ys = Nil; b = empty 1
|
|
}
|
|
|
|
(* for the three operations, the postcondition uses the default
|
|
implementation *)
|
|
|
|
let cat_empty (h: int) : cat 'a
|
|
requires { 0 < h }
|
|
ensures { result.b = empty h }
|
|
= { ch = h; xs = Nil; xs_len = 0; ys = Nil;
|
|
b = empty h }
|
|
|
|
let cat_add (x: 'a) (c: cat 'a) : cat 'a
|
|
ensures { result.b = add x c.b }
|
|
= if c.xs_len = c.ch - 1 then
|
|
{ c with xs = Nil; xs_len = 0; ys = Cons x c.xs;
|
|
b = add x c.b }
|
|
else
|
|
{ c with xs = Cons x c.xs; xs_len = 1 + c.xs_len;
|
|
b = add x c.b }
|
|
|
|
let cat_get (c: cat 'a) : list 'a
|
|
ensures { result = get c.b }
|
|
= take c.ch (c.xs ++ c.ys)
|
|
|
|
end
|
|
|
|
(* task 2 *)
|
|
|
|
(* important note: Why3 assumes a garbage collector and so it makes
|
|
little sense to implement the real time solution in Why3.
|
|
|
|
Yet I stayed close to the C++ code, with a queue to_delete where
|
|
lists are added when discarded and then destroyed progressively
|
|
(at most two conses at a time) in process_queue.
|
|
|
|
The C++ code seems to be missing the insertion into to_delete,
|
|
which I added to rt_add; see my comment below.
|
|
*)
|
|
|
|
module RealTime
|
|
|
|
use Spec
|
|
use list.Append
|
|
use list.Length
|
|
|
|
(* For technical reasons, the global queue cannot contain
|
|
polymorphic values, to we assume values to be of some
|
|
abstract type "elt". Anyway, the C++ code assumes integer
|
|
elements. *)
|
|
type elt
|
|
|
|
(* not different from the Caterpillar implementation
|
|
replacing 'a with elt everywhere *)
|
|
type rt = {
|
|
ch: int;
|
|
xs: list elt;
|
|
xs_len: int;
|
|
ys: list elt;
|
|
ghost b: buf elt; (* the model is the default implementation *)
|
|
} invariant {
|
|
b.h = ch /\
|
|
xs_len = length xs < ch /\
|
|
forall len. 0 <= len <= ch -> take len (xs ++ ys) = take len b.xs
|
|
} by {
|
|
ch = 1; xs = Nil; xs_len = 0; ys = Nil; b = empty 1
|
|
}
|
|
|
|
(* garbage collection *)
|
|
use queue.Queue as Q
|
|
(* note: when translating Why3 to OCaml, this module is mapped
|
|
to OCaml's Queue module, where push and pop are O(1) *)
|
|
|
|
val to_delete: Q.t (list elt)
|
|
|
|
let de_allocate (l: list elt)
|
|
= match l with Nil -> () | Cons _ xs -> Q.push xs to_delete end
|
|
|
|
let process_queue ()
|
|
= try
|
|
if not (Q.is_empty to_delete) then de_allocate (Q.pop to_delete);
|
|
if not (Q.is_empty to_delete) then de_allocate (Q.pop to_delete)
|
|
with Q.Empty -> absurd end
|
|
|
|
(* no difference wrt Caterpillar *)
|
|
let rt_empty (h: int) : rt
|
|
requires { 0 < h }
|
|
ensures { result.b = empty h }
|
|
= { ch = h; xs = Nil; xs_len = 0; ys = Nil;
|
|
b = empty h }
|
|
|
|
(* no difference wrt Caterpillar *)
|
|
let rt_get (c: rt) : list elt
|
|
ensures { result = get c.b }
|
|
= take c.ch (c.xs ++ c.ys)
|
|
|
|
(* this is where we introduce explicit garbage collection
|
|
1. process_queue is called first (as in the C++ code)
|
|
2. when ys is discarded, it is added to the queue (which
|
|
seems to be missing in the C++ code) *)
|
|
let rt_add (x: elt) (c: rt) : rt
|
|
ensures { result.b = add x c.b }
|
|
= process_queue ();
|
|
if c.xs_len = c.ch - 1 then begin
|
|
Q.push c.ys to_delete;
|
|
{ c with xs = Nil; xs_len = 0; ys = Cons x c.xs;
|
|
b = add x c.b }
|
|
end else
|
|
{ c with xs = Cons x c.xs; xs_len = 1 + c.xs_len;
|
|
b = add x c.b }
|
|
|
|
end
|