File: wormhole-abstraction.lisp

package info (click to toggle)
acl2 8.5dfsg-5
  • links: PTS
  • area: main
  • in suites: bookworm
  • size: 991,452 kB
  • sloc: lisp: 15,567,759; javascript: 22,820; cpp: 13,929; ansic: 12,092; perl: 7,150; java: 4,405; xml: 3,884; makefile: 3,507; sh: 3,187; ruby: 2,633; ml: 763; python: 746; yacc: 723; awk: 295; csh: 186; php: 171; lex: 154; tcl: 49; asm: 23; haskell: 17
file content (351 lines) | stat: -rw-r--r-- 15,062 bytes parent folder | download | duplicates (6)
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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
; Correctness of Fact via Wormhole Abstraction

; Problem: Define an M1 program to compute the factorial of its natural number
; input.  Prove your program correct without specifying what happens to some
; variables.

; Acknowledgement: This approach to code proofs was first developed by Dave
; Greve at Rockwell Collins Inc, who named the technique ``wormhole
; abstraction'' because the expressions defining the values of the variables we
; don't care about are hidden from sight.  In essence, wormhole abstraction
; characterizes the value of the uninteresting variables by saying ``they are
; what they are'' or ``their values are whatever the machine computes'' instead
; of characterizing them algebraically or extensionally.

; This file started as fact.lisp.  But I modified the program to be verified so
; that it unnecessarily changes local variable 2 (in addition to locals 0 and
; 1).  Then I verified the program without explicitly characterizing how local
; variable 2 -- or any other local -- changes.  If you attempt to verify this
; program by the method used to verify fact.lisp, you'll see that you must
; specify what is happening to local 2 even though it is not needed to express
; the final answer.  I have put comments beginning with the tag "Wormhole
; Abstraction" everywhere I changed fact.lisp below.

; (0) Start ACL2
; (include-book "m1")

(in-package "M1")

; (1) Write your specification, i.e., define the expected inputs and the
; desired output, theta.

(defun ok-inputs (n)
  (natp n))

(defun ! (n)
  (if (zp n)
      1
      (* n (! (- n 1)))))

(defun theta (n)
  (! n))

; (2) Write your algorithm.  This will consist of a tail-recursive helper
; function and a wrapper, fn.

(defun helper (n a)
  (if (zp n)
      a
      (helper (- n 1) (* n a))))

(defun fn (n) (helper n 1))

; (3) Prove that the algorithm satisfies the spec, by proving first that the
; helper is appropriately related to theta and then that fn is theta on ok
; inputs.

; Important Note: When you verify your helper function, you must consider the
; most general case.  For example, if helper is defined with formal parameters
; n and a and fn calls helper initializing a to 0, your helper theorem must
; be about (helper n a), not just about the special case (helper n 0).

(defthm helper-is-theta
  (implies (and (ok-inputs n)
                (natp a))
           (equal (helper n a)
                  (* a (theta n)))))

(defthm fn-is-theta
  (implies (ok-inputs n)
           (equal (fn n)
                  (theta n))))

; Disable these two lemmas because they confuse the theorem prover when it is
; dealing with the code versus fn.

(in-theory (disable helper-is-theta fn-is-theta))

; (4) Write your M1 program with the intention of implementing your algorithm.

; Wormhole Abstraction: This program implements fact a little oddly: it uses a
; temporary variable, local 2, that is otherwise unimportant.  This change is not
; illustrative of wormhole abstraction per se.  It just makes this program more
; typical of the situations in which wormhole abstraction is useful:  when a program
; side-effects many machine resources on the way to computing what really matters and
; you don't want to have to specify all those side-effects explicitly.

(defconst *pi*
  '((iconst 1)  ;  0
    (istore 1)  ;  1
    (iload 0)   ;  2
    (ifeq 12)   ;  3
    (iload 0)   ;  4
    (istore 2)  ;  5  ; Wormhole Abstraction: Note that local 2 takes on
    (iload 0)   ;  6  ; successive values of local variable 0.
    (iconst 1)  ;  7
    (isub)      ;  8
    (istore 0)  ;  9
    (iload 2)   ; 10  ; Wormhole Abstraction: And local 2 is used in the
    (iload 1)   ; 11  ; calculation of the answer, local 1.
    (imul)      ; 12
    (istore 1)  ; 13
    (goto -12)  ; 14
    (iload 1)   ; 15
    (halt))     ; 16
  )

; (5) Define the ACL2 function that clocks your program, starting with the
; loop clock and then using it to clock the whole program.  The clock
; should take the program from pc 0 to a HALT statement.  (Sometimes your
; clocks will require multiple inputs or other locals, but our example only
; requires the first local.)

(defun loop-clk (n)
  (if (zp n)
      3
      (clk+ 13  ; Wormhole Abstraction: The clock is a little different for
                ; this version of the program since we execute two extra
                ; instructions.  the program in fact.lisp took 11 steps here
                ; instead of 13.  Again, this is not illustrative of wormhole
                ; abstraction but is a change from fact.lisp.

            (loop-clk (- n 1)))))

(defun clk (n)
  (clk+ 2
        (loop-clk n)))

; (6) Prove that the code implements your algorithm, starting with the lemma
; that the loop implements the helper.  Each time you prove a lemma relating
; code to algorithm, disable the corresponding clock function so the theorem
; prover doesn't look any deeper into subsequent code.

; Important Note: Your lemma about the loop must consider the general case.
; For example, if the loop uses the locals n and a, you must characterize
; its behavior for arbitrary, legal n and a, not just a special case (e.g.,
; where a is 0).

; Wormhole Abstraction: We now illustrate wormhole abstraction.  The key step
; in any code proof about a program with a single loop is the lemma that
; characterizes how the loop affects the machine's resources.  (In fact.lisp we
; called that lemma ``loop-is-helper'' and it is given that name below too.)
; The utility of wormhole abstraction is most apparent in the statement and
; proof of this crucial lemma because it is during the proof of this lemma that
; we spend the most time ``walking through the code'' and tracking
; side-effects.

; The key idea in wormhole abstraction is that we are do not want to specify
; EXPLICITLY how some of the machine resources variables are changed as the
; program executes or what their final values are.  In this example, we'll
; focus on the changes to the local variables.  We're just going to say ``the
; final locals are whatever M1 computes them to be.''  For succinctness we
; define this concept:

(defun fact-loop-locals (n s)
  (locals (m1 s (loop-clk n))))

; Assume, above, that n is the natural number on which we're to compute
; factorial and s is a state poised at the top of the loop (pc = 2) of program
; *pi*.  Then (fact-loop-locals n s) is the final value of the local variables
; after the M1 machine has run as long as the clock says to run.  Notice that
; this definition does not explicitly characterize the values of any of the
; locals!  They are whatever the code tells M1 to compute.

; Wormhole Abstraction: It is convenient to wrap up the conditions, above, on n
; and s as a named concept.

(defun poised-to-loop (n s)
  (and (natp n)
       (equal (pc s) 2)
       (equal (program s) *pi*)
       (equal n (nth 0 (locals s)))))

; Wormhole Abstraction: In fact.lisp we assumed we were dealing with a state
; whose locals were (list n a) and we characterized the final answer in a to be
; (helper n a).  This had a powerful but very subtle effect on ACL2's behavior:
; it suggested to ACL2 that it should induct on n and a in the way that (helper
; n a) recurs.  That suggestion will no longer be part of our statement of the
; theorem.  So we have to tell ACL2 what to induct on.  But we're going to
; employ wormhole abstraction here too and say ``induct on the way M1 is driven
; around the loop by whatever code it is running.''

(defun loop-hint (n s)
  (if (zp n)
      (list n s)
      (loop-hint (- n 1)
                 (M1 s 13))))

; The value of the function loop-hint is unimportant.  What is important is how
; loop-hint recurs.  Notice that it tests whether (zp n) is true or not.  If
; true, it terminates in a base case (no recursive calls).  Otherwise, it
; replaces n by (- n 1) and s by (M1 s 13).  We will be assuming that n and s
; satisfy (poised-to-loop n s).

; When we ask ACL2 to prove the crucial lemma we will give the
;   :hints (("Goal" :induct (loop-hint n s)))

; This tells ACL2 to induct the way (loop-hint n s) recurs.  If the goal is to
; prove (psi n s), then ``induct the way (loop-hint n s) recurs'' means"

; Base Case:        (zp n) --> (psi n s)

; Induction Step:   [(not (zp n)) & (psi (- n 1) (M1 s 13))] --> (psi n s)

; Note that we haven't told ACL2 to replace local 1 by the product of local 0
; and local 1, and we haven't told it to replace local 2 by local 1.  (We have
; told it, implicitly to replace local 0 by the result of decrementing local 0
; by 1, by virtue of telling it to replace n by n-1 and requiring that n be
; local 0.)  Instead of being explicit, we just tell the induction mechanism to
; symbolically run the code 13 steps from the top of the loop and use whatever
; new state is produced.  That state is just the state you get by going once
; around the loop.

; Wormhole Abstraction: We also make a minor technical adjustment to the rule
; configuration used in fact.lisp: we disable the functions NTH and UPDATE-NTH
; so that terms like (NTH 1 (locals s)) and (UPDATE-NTH 2 v (locals s)) just
; stay in that form.  We also disable NTH-ADD1! which is a :rewrite rule that
; would otherwise expand (NTH 1 (locals s)) to (NTH 0 (cdr (locals s))) and
; thence to (cadr (locals s)).  We will be exploiting the standard rules like
; NTH-UPDATE-NTH to simplify such terms as:

; (NTH 1 (UPDATE-NTH 2 v (locals s))) = (NTH 1 (locals s)).

(in-theory (disable nth update-nth nth-add1!))

; Why are we doing this for Wormhole Abstraction and not for our other proofs?
; Because in our other proofs we describe the locals as (list n a) or other
; [semi-] explicit cons structures and so we wanted our rules to reduce NTH and
; UPDATE-NTH on such structures.  Now we don't.  Note that in this setting we
; do not even know how many locals are allocated (i.e., what is the length of
; (locals s)).

; Wormhole Abstraction: Here is the crucial lemma: If s is a state poised to
; execute at the top of the loop of the factorial program on input n, and we
; run s (loop-clk n) steps, we get a state where the final pc is 16, the locals
; are whatever they are, and the stack has (helper (nth 0 (locals s)) (nth 1
; (locals s))) on top of whatever was there before.  Note that we do not say --
; explicitly -- what the final locals are.  They are what they are.

(defthm loop-is-helper
  (implies (poised-to-loop n s)
           (equal (m1 s (loop-clk n))
                  (make-state 16
                              (fact-loop-locals n s)
                              (push (helper (nth 0 (locals s))
                                            (nth 1 (locals s)))
                                    (stack s))
                              *pi*)))
  :hints (("Goal" :induct (loop-hint n s))))

; Contrast this to the original version of the lemma in fact.lisp:

; (defthm loop-is-helper          ; [from fact.lisp]
;   (implies (and (ok-inputs n)
;                 (natp a))
;           (equal (m1 (make-state 2
;                                  (list n a)
;                                  nil
;                                  *pi*)
;                      (loop-clk n))
;                  (make-state 14
;                              (list 0 (helper n a))
;                              (push (helper n a) nil)
;                              *pi*))))

; Note that in the original, which did not involve local 2, we characterized
; explicitly every component of the final state, e.g., that local 0 is 0, that
; local 1 is given by (helper n a), etc.  Had the original program involved
; changes to local 2 we would have had to give them explicitly.  Wormhole
; abstraction is a way to write and prove such lemmas without being explicit
; about the final values of some variables.  ``They are what they are.''

; Wormhole Abstraction:  Now following the original pattern, we disable the clock
; for the loop but we also disable the function that computes the final value of
; the locals.  The idea is simply not to concern ourselves any more with those
; concepts.

(in-theory (disable loop-clk fact-loop-locals))

; We apply the same wormhole abstraction techniques to the next step, from pc 0 to
; to the top of the loop and then on to the HALT:

(defun poised-for-fact (n s)
  (and (natp n)
       (equal (pc s) 0)
       (equal (program s) *pi*)
       (equal n (nth 0 (locals s)))))

(defun fact-locals (n s)
  (locals (m1 s (clk n))))

(defthm fact-thm
  (implies (poised-for-fact n s)
           (equal (m1 s (clk n))
                  (make-state 16
                              (fact-locals n s)
                              (push (fn (nth 0 (locals s)))
                                    (stack s))
                              *pi*))))

(in-theory (disable clk fact-locals))

; (7) Put the two steps together to get correctness.

(in-theory (enable fn-is-theta))

; Wormhole Abstraction: In the original fact.lisp we proved PROGRAM-CORRECT
; before finishing with the same TOTAL-CORRECTNESS theorem shown below.  Recall
; PROGRAM-CORRECT just replaced our operational model of the final value, (fn
; n) by its specification value (! n).  It also explicated all the other state
; changes; we then used TOTAL-CORRECTNESS just to exhibit the ones we care
; about.  We could do something similar here but don't bother.  Instead we just
; exhibit the desired total correctness theorem.  This is the same
; TOTAL-CORRECTNESS formula proved before, without ever characterizing the
; local variables beyond ``they are what they are.''

; Note however that we have to enable NTH below.  Why?  To answer that,
; consider the state to which we apply M1 below.  It contains the list
; structure (list n) as its locals.  We need to know that this state satisfies
; the poised-for-fact predicate.  That means we need to be able to show that
; (nth 0 (list n)) is n.  That can be arranged by enabling NTH.

(defthm total-correctness
  (implies (and (natp n)
                (equal sf (m1 (make-state 0
                                          (list n)
                                          nil
                                          *pi*)
                              (clk n))))
           (and (equal (next-inst sf) '(HALT))
                (equal (top (stack sf))
                       (! n))))
  :hints (("Goal" :in-theory (enable nth)))
  :rule-classes nil)

; An alternative version, more in keeping with the style we've adopted here, is
; to refrain from describing the locals with a cons structure and just
; hypothesize that the 0th local is n:

(defthm alternative-total-correctness
  (implies (and (natp n)
                (equal n (nth 0 locals))
                (equal sf (m1 (make-state 0
                                          locals
                                          nil
                                          *pi*)
                              (clk n))))
           (and (equal (next-inst sf) '(HALT))
                (equal (top (stack sf))
                       (! n))))
  :rule-classes nil)