File: verifythis_2017_tree_buffer.mlw

package info (click to toggle)
why3 1.8.2-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 45,028 kB
  • sloc: xml: 185,443; ml: 111,224; ansic: 3,998; sh: 2,578; makefile: 2,568; java: 865; python: 720; javascript: 290; lisp: 205; pascal: 173
file content (178 lines) | stat: -rw-r--r-- 4,759 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
(**

{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