File: m1-with-stobj.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 (443 lines) | stat: -rw-r--r-- 13,011 bytes parent folder | download | duplicates (8)
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
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
#||
Like m1 but uses a stobj for the state.

I totally ignore the guard question.

(defpkg "M1"
  (set-difference-eq (union-eq *acl2-exports*
                               *common-lisp-symbols-from-main-lisp-package*)
                     '(push pop pc program step
                            ; nth update-nth nth-update-nth  ; <--- stobjs use built-ins
                            )))
(certify-book "m1-with-stobj" 1)
||#

(in-package "M1")

; -----------------------------------------------------------------
; Stack Manipulation

(defun push (x y)
  (cons x y))

(defun top (stack)
  (car stack))

(defun pop (stack)
  (cdr stack))

; -----------------------------------------------------------------

; State Manipulation:

(set-verify-guards-eagerness 0)

; I name the components with the prefix "ugly-" because I don't want to see
; them in proofs!  I'm going to define a more uniform interface.  If I see
; ``ugly-'' in a proof, something is wrong!

(defstobj s ugly-pc ugly-locals ugly-stack ugly-program)

; Basic read/write operations on state: for each field f of a stobj, defstobj
; provides two functions, f and update-f, for accessing and updating the field
; value.  Now think about how messy it is to normalize a random mix of update
; expressions, e.g., (update-h a (update-g b (update-h c (update-f d st)))).
; One needs a quadradic number of rules to commute the updates into some canonical order:

; (update-g a (update-f b s)) = (update-f b (update-g a s))
; (update-h a (update-f b s)) = (update-f b (update-h a s))
; (update-i a (update-f b s)) = (update-f b (update-i a s))
; ...
; (update-h a (update-g b s)) = (update-g b (update-h a s))
; (update-i a (update-g b s)) = (update-g b (update-i a s))
; ...

; So I define a pair of read/write functions that can hit any field of the
; stobj, then I prove the normalization rules, then I disable these basic
; functions.  Finally, I use the basic functions to define the field-specific
; update-f versions.  I define how to write to the cd field even though it is
; never needed.  I did this to provide a uniform interface: to access or change
; a field, use rd or wr.

(defun wr (key v s)
  (declare (xargs :stobjs (s)))
  (case key
    (:pc (update-ugly-pc v s))
    (:locals (update-ugly-locals v s))
    (:stack (update-ugly-stack v s))
    (:program (update-ugly-program v s))
    (otherwise s)))

(defun rd (key s)
  (declare (xargs :stobjs (s)))
  (case key
    (:pc (ugly-pc s))
    (:locals (ugly-locals s))
    (:stack (ugly-stack s))
    (:program (ugly-program s))
    (otherwise 0)))

(defthm sp-wr
  (implies (sp s)
           (sp (wr loc val s))))

(in-theory (disable sp))

(defun keyp (k) (member k '(:pc :locals :stack :program)))

(defthm rd-wr
  (equal (rd key1 (wr key2 v s))
         (if (and (equal key1 key2)
                  (keyp key1))
             v
             (rd key1 s))))

(defthm update-nth-update-nth-diff
  (implies (and (natp i) (natp j) (not (equal i j)))
           (equal (update-nth i v (update-nth j w list))
                  (update-nth j w (update-nth i v list))))
  :rule-classes ((:rewrite :loop-stopper ((i j update-nth)))))

(defthm update-nth-update-nth-same
  (equal (update-nth i v (update-nth i w list))
         (update-nth i v list)))

(defthm update-nth-redundant
  (implies (and (natp i)
                (< i (len x))
                (equal (nth i x) v))
           (equal (update-nth i v x) x)))

(defthm wr-wr-diff
  (implies (and (keyp key1)
                (keyp key2)
                (not (equal key1 key2)))
           (equal (wr key1 v1 (wr key2 v2 s))
                  (wr key2 v2 (wr key1 v1 s))))
  :hints (("Goal" :in-theory (disable nth update-nth)))
  :rule-classes ((:rewrite :loop-stopper ((key1 key2 wr)))))

(defthm wr-wr-same
  (implies (keyp key1)
           (equal (wr key1 v1 (wr key1 v2 s))
                  (wr key1 v1 s))))

(defthm wr-redundant
  (implies (and (sp s)
                (equal (rd key s) v))
           (equal (wr key v s) s))
  :hints (("Goal" :in-theory (enable sp))))

(in-theory (disable rd wr))

; The following functions are just handy abbreviations to use in the semantics
; of the instructions.  I expect them always to be expanded into rd/wr terms.
; They could be macros.

(defun pc (s)
  (declare (xargs :stobjs (s)))
  (rd :pc s))

(defun !pc (v s)
  (declare (xargs :stobjs (s)))
  (wr :pc v s))

; There is no need to define lo and !lo since we always change indexed
; positions, not the whole field.

(defun loi (i s)
  (declare (xargs :stobjs (s)))
  (nth i (rd :locals s)))

(defun !loi (i v s)
  (declare (xargs :stobjs (s)))
  (wr :locals (update-nth i v (rd :locals s)) s))

(defun stack (s)
  (declare (xargs :stobjs (s)))
  (rd :stack s))

(defun !stack (v s)
  (declare (xargs :stobjs (s)))
  (wr :stack v s))

; There is no need to define !program.

(defun program (s)
  (declare (xargs :stobjs (s)))
  (rd :program s))

(defun next-inst (s)
  (declare (xargs :stobjs (s)))
  (nth (pc s) (program s)))

; -----------------------------------------------------------------
; Instruction Accessors

(defun op-code (inst)
  (nth 0 inst))

(defun arg1 (inst)
  (nth 1 inst))

; -----------------------------------------------------------------

; The M1 Machine

; Now we define the semantics of each instruction.  These functions are called
; ``semantic functions.''

; Each opcode will be given semantics with an ACL2 function that takes an
; instruction (with the given opcode) and a state and returns the state
; produced by executing the instruction.  For example, the ILOAD instruction,
; which looks like this (ILOAD k), where k is the index of the local variable
; to be loaded, is given semantics by the function execute-ILOAD.  Execute-ILOAD is
; the ``semantic function'' for ILOAD.

; Our expectation when (execute-ILOAD inst s) is called is that s is a ``good''
; M1 state, that inst is the next instruction in s, and that inst is an ILOAD
; instruction.  If all that is true, we can run execute-ILOAD without error,
; i.e., without worrying whether the instruction is well-formed, whether the
; local variable of that index exists, whether incrementing the pc pushes it
; outside the bounds of the program, etc.  Because we have analogous
; expectations on the semantic function for each opcode, we wrap up these
; expectations into a single predicate:

; Semantics of (ILOAD k): increment the pc and push onto the stack the value of
; the kth local.  Aside: We will know, by guard verification, that the new pc
; is legal and that the value pushed is a rational number.  As a rule, I will
; not comment here on everything we know by guard verification, I'm just trying
; to give you a sense of the implications of our expectations.

(defun execute-ILOAD (inst s)
  (declare (xargs :stobjs (s)))
  (let* ((s (!stack (push (loi (arg1 inst) s) (stack s))
                    s))
         (s (!pc (+ 1 (pc s)) s)))
    s))

; Semantics of (ICONST k):  increment the pc and push k onto the stack.

(defun execute-ICONST (inst s)
  (declare (xargs :stobjs (s)))
  (let* ((s (!stack (push (arg1 inst) (stack s))
                    s))
         (s (!pc (+ 1 (pc s)) s)))
    s))

; Semantics of (IADD): increment the pc, pop two items off the stack and push
; their sum.  Aside: We will know, by guard verification, that there are at
; least two items on the stack and that they are both rational numbers.

(defun execute-IADD (inst s)
  (declare (xargs :stobjs (s))
           (ignore inst))
  (let ((u (top (stack s)))
        (v (top (pop (stack s))))
        (stack1 (pop (pop (stack s)))))
    (let* ((s (!stack (push (+ v u) stack1) s))
           (s (!pc (+ 1 (pc s)) s)))
      s)))

; Semantics of (ISUB): increment the pc, pop two items off the stack and push
; their difference.

(defun execute-ISUB (inst s)
  (declare (xargs :stobjs (s))
           (ignore inst))
  (let ((u (top (stack s)))
        (v (top (pop (stack s))))
        (stack1 (pop (pop (stack s)))))
    (let* ((s (!stack (push (- v u) stack1) s))
           (s (!pc (+ 1 (pc s)) s)))
      s)))

; Semantics of (IMUL): increment the ic, pop two items off the stack and push
; their product.

(defun execute-IMUL (inst s)
  (declare (xargs :stobjs (s))
           (ignore inst))
  (let ((u (top (stack s)))
        (v (top (pop (stack s))))
        (stack1 (pop (pop (stack s)))))
    (let* ((s (!stack (push (* v u) stack1) s))
           (s (!pc (+ 1 (pc s)) s)))
      s)))

; Semantics of (ISTORE k): increment the ic, pop one item off the stack, and
; store it as the value of local variable k.

(defun execute-ISTORE (inst s)
  (declare (xargs :stobjs (s)))
  (let ((u (top (stack s)))
        (stack1 (pop (stack s))))
    (let* ((s (!stack stack1 s))
           (s (!loi (arg1 inst) u s))
           (s (!pc (+ 1 (pc s)) s)))
      s)))

; Semantics of (GOTO k): increment the pc by k.  Aside: We will know, by guard
; verification, that the new pc is legal.

(defun execute-GOTO (inst s)
  (declare (xargs :stobjs (s)))
  (let* ((s (!pc (+ (arg1 inst) (pc s)) s)))
    s))

; Semantics of (IFEQ k): pop one item off the stack and increment the pc k if
; that item is 0 and by 1 if if is non-0.  Aside: We will know, by guard
; verification, that the new pc is legal.

(defun execute-IFEQ (inst s)
  (declare (xargs :stobjs (s)))
  (let ((u (top (stack s)))
        (stack1 (pop (stack s))))
    (let* ((s (!stack stack1 s))
           (s (!pc (if (equal u 0)
                       (+ (arg1 inst) (pc s))
                       (+ 1 (pc s)))
                   s)))
      s)))

(defun do-inst (inst s)
  (declare (xargs :stobjs (s)))
  (if (equal (op-code inst) 'ILOAD)
      (execute-ILOAD  inst s)
      (if (equal (op-code inst) 'ICONST)
          (execute-ICONST  inst s)
          (if (equal (op-code inst) 'IADD)
              (execute-IADD   inst s)
              (if (equal (op-code inst) 'ISUB)
                  (execute-ISUB   inst s)
                  (if (equal (op-code inst) 'IMUL)
                      (execute-IMUL   inst s)
                      (if (equal (op-code inst) 'ISTORE)
                          (execute-ISTORE  inst s)
                          (if (equal (op-code inst) 'GOTO)
                              (execute-GOTO   inst s)
                              (if (equal (op-code inst) 'IFEQ)
                                  (execute-IFEQ   inst s)
                                  s)))))))))

; This is the single-step function: it executes the next instruction in a
; state.  Aside: We will know, by guard verification, that the resulting state
; is a good state.

(defun step (s)
  (declare (xargs :stobjs (s)))
  (do-inst (next-inst s) s))

(defun m1 (s n)
  (declare (xargs :stobjs (s)))
  (if (zp n)
      s
      (let* ((s (step s)))
        (m1 s (- n 1)))))

(defun haltedp (s)
  (declare (xargs :stobjs (s)))
  (equal (next-inst s) '(HALT)))

; =================================================================
; Lemmas for Proving M1 Code

; Arithmetic

(include-book "arithmetic-5/top" :dir :system)

; Stacks

(defthm top-push
  (equal (top (push x y)) x))

(defthm pop-push
  (equal (pop (push x y)) y))

(defun popn (n stk)
  (if (zp n)
      stk
    (popn (- n 1) (pop stk))))

(defmacro push* (&rest lst)
  (if (endp lst)
      nil
      (if (endp (cdr lst))
          (car lst)
          `(push ,(car lst) (push* ,@(cdr lst))))))

(defthm len-push
  (equal (len (push x y))
         (+ 1 (len y))))

; Abstract Data Type Stuff

(defthm constant-stacks
  (and

; These next two are needed because some push expressions evaluate to
; list constants, e.g., (push 1 (push 2 nil)) becomes '(1 2) and '(1
; 2) pattern-matches with (cons x s) but not with (push x s).

       (equal (top (cons x s)) x)
       (equal (pop (cons x s)) s))
  :hints (("Goal" :in-theory (enable top pop))))


(in-theory (disable push (:executable-counterpart push) top pop))

; Step Stuff

(defthm step-opener
  (implies (consp (next-inst s))
           (equal (step s)
                  (do-inst (next-inst s) s)))

  :hints (("Goal" :in-theory (enable step))))

(in-theory (disable step))

; Schedules and Run

(defun binary-clk+ (i j)
  (+ (nfix i) (nfix j)))

(defthm clk+-associative
  (equal (binary-clk+ (binary-clk+ i j) k)
         (binary-clk+ i (binary-clk+ j k))))

(defmacro clk+ (&rest argst)
  (if (endp argst)
      0
      (if (endp (cdr argst))
          (car argst)
          `(binary-clk+ ,(car argst)
                         (clk+ ,@(cdr argst))))))

(defthm m1-clk+
  (equal (m1 s (clk+ i j))
         (m1 (m1 s i) j)))

(in-theory (disable binary-clk+))

(defthm m1-opener
  (and (equal (m1 s 0) s)
       (implies (natp i)
                (equal (m1 s (+ 1 i))
                       (m1 (step s) i)))))

(in-theory (disable m1))

; Nth and update-nth




; Len and the Locals

; In our code proofs we need this theorem to prove that certain initial states
; satisfy the good-statep predicate.

(defthm equal-len-0
  (equal (equal (len x) 0)
         (not (consp x))))