File: partial.lisp

package info (click to toggle)
acl2 8.6%2Bdfsg-3
  • links: PTS
  • area: main
  • in suites: forky, sid
  • size: 1,138,276 kB
  • sloc: lisp: 17,818,294; java: 125,359; python: 28,122; javascript: 23,458; cpp: 18,851; ansic: 11,569; perl: 7,678; xml: 5,591; sh: 3,978; makefile: 3,840; ruby: 2,633; yacc: 1,126; ml: 763; awk: 295; csh: 233; lex: 197; php: 178; tcl: 49; asm: 23; haskell: 17
file content (818 lines) | stat: -rw-r--r-- 29,753 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
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
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
; Use of Tail-Recursion to Propagate Inductive Assertions
; J Strother Moore
; February 26, 2003

; cd /u/moore/m5/tolquhon
; (ld "partial.lisp" :ld-pre-eval-print t)

; Certification:
; (include-book "utilities")
; (certify-book "partial" 1)

; ---------------------------------------------------------------------------
; Summary

; In this file I show how to use the classic inductive invariant
; approach to prove a partial correctness property of m5 programs.
; We deal with both iterative and recursive methods.

; To use the inductive invariant method with an operational semantics
; you define the invariant characterizing the reachable states of
; interest (i.e., given whatever pre-condition you wish to impose).
; Suppose the invariant is (inv s n0), where n0 is the ``initial
; values'' of the locals.  Then the next step is to prove that
; it is, indeed, an invariant, by proving that

; (implies (inv s n0) (inv (step s) n0)).    [1]

; From this it follows that

; (implies (inv s n0) (inv (run k s) n0)).

; Note that if you then plug in an initial state satisfying the
; invariant, and you suppose that after some arbitrary run the pc is
; at some location, you can read out of the invariant what you must
; know about that state.

; I'll get more specific below.

; In the past when I've used this technique, e.g., to do the
; Apprentice problem, I had to supply an explicit assertion for every
; pc.  Whether you do it explicitly or not, the invariant must
; characterize every pc or else you won't be able to prove [1].

; But the classic inductive assertion method supplies an assertion
; only for selected cut points (one for each loop, plus the pre- and
; post-conditions).

; In this file I demonstrate how to be explicit only at the cut
; points.  I use recursion by step in inv to define the invariant for
; all the states between cut points.  Even this idea is not new for
; me.  But in my previous uses of this idea -- the idea of using
; recursion by step to define the invariant at pcs other than the cut
; points -- you get the problem of how to ADMIT the inv function
; itself.  To admit such a recursive inv you have to prove that every
; step reduces the distance to some cut point.  That is, ACL2 imposes
; a proof burden not incurred by an ordinary user of inductive
; assertions.  (Such a user had to supply enough cut points but you
; knew you had done so if the verification condition generator
; terminated.)  My ``insight'' is that I could use defpun to define
; the invariant, using tail-recursion by step.

; A technique similar in spirit to the one I'm describing was used by
; Pete Manolios to attack the 2-Job version of the Apprentice problem.
; There, he defined the reachable states of the Apprentice problem as
; all the states that could be reached from certain states by the
; execution of a fixed maximum number of steps (25?).

; The contribution I see for this little note is the use of defpun to
; avoid counting.

; Now I'll do it...

; ---------------------------------------------------------------------------
; Preliminaries

; This first part is just ``prelude''.  It has nothing to do with the
; specific programs we will verify.

(in-package "M5")

(include-book "misc/defpun" :dir :system)

(defmacro defpun (g args &rest tail)
  `(acl2::defpun ,g ,args ,@tail))

(defthm update-nth-opener
  (and (equal (update-nth 0 x a) (cons x (cdr a)))
       (implies (not (zp n))
                (equal (update-nth n x a)
                       (cons (car a) (update-nth (- n 1) x (cdr a)))))))

; ---------------------------------------------------------------------------
; Some Preliminaries for Our First Program

(defthm int-evenp-inv-a
  (implies (intp i)
           (iff (evenp (int-fix (- i 2)))
                (evenp i)))
  :hints
  (("Goal" :in-theory (e/d (intp int-fix)
                           (floor)))))

(defthm int-evenp-inv-b
  (implies (intp i)
           (iff (evenp (- i 2))
                (evenp i)))
  :hints
  (("Goal" :in-theory (e/d (intp int-fix)
                           (floor)))))

(in-theory (disable evenp))

(defthm int-lemma2a
  (implies (and (intp x)
                (<= 0 x))
           (equal (int-fix (+ -2 x))
                  (+ -2 x)))
  :hints (("Goal" :in-theory (e/d (intp) nil))))

(defthm int-lemma2b
  (implies (and (intp x)
                (<= 0 x))
           (intp (+ -2 x)))
  :hints (("Goal" :in-theory (e/d (intp) nil))))

; ---------------------------------------------------------------------------
; Our First Program

; Below is an m5 program that decrements its first local, n, by 2 and
; iterates until the result is 0.  On each iteration it adds 1 to a
; local variable, a, which is initialized to 0.  Thus, the method
; computes n/2, when n is even.  It does not terminate when n is odd.

; To make the program slightly simpler to deal with, I only consider
; the case where n is a non-negative int.  Note that the program still
; may loop forever under this pre-condition.  In fact, if n is a
; negative int, the iteration eventually wraps around and n becomes
; positive.  I could therefore eliminate the restriction on the sign
; of n and still get both occasional termination and occasional
; non-termination.  If n is not an int, it becomes an int after one
; iteration because of m5's use of int-fix on every operation.  Thus,
; I believe I could eliminate the pre-conditions on n altogether.
; However, that only involves me in int-ring arithmetic.  The
; non-negative int case exhibits all the interesting behavior.

(defconst *half-prog*
  '((iconst_0)      ; 0
    (istore_1)      ; 1               a := 0;
    (iload_0)       ; 2  top of loop:
    (ifeq 14)       ; 3               if n=0, goto 17;
    (iload_1)       ; 6
    (iconst_1)      ; 7
    (iadd)          ; 8
    (istore_1)      ; 9               a := a+1;
    (iload_0)       ;10
    (iconst_2)      ;11
    (isub)          ;12
    (istore_0)      ;13               n := n-2;
    (goto -12)      ;14               goto top of loop
    (iload_1)       ;17
    (halt)))        ;18

; Here is the ``semantics'' of the loop, in the case in interest.

(defun halfa (n a)
  (declare (xargs :measure (nfix n)))
  (if (zp n)
      a
    (halfa (- n 2) (int-fix (+ a 1)))))

; ---------------------------------------------------------------------------
; The Assertions at the Three Cut Points

; We will use a classic ``inductive assertion'' method.  The following
; function takes a state, s, and the ``initial'' value of n, n0, and
; states the assertions we wish to attach to pcs 0, 2, and 18.  These
; are the so-called ``cut points'' of my choice: the entry to the
; program, the top of the loop, and exit from the program.

; The particular assertions are not my main interest in this paper.
; You can read them if you want.  The real nugget in this paper is not
; the assertions but the fact that I use tail recursion by step to
; propagate assertions from the cut points to all the pcs.

; That said, let me note that the assertions are complicated because
; they have to handle the fact that halfa tracks the program only as
; long as n stays non-negative.  Things would be simpler if I assumed
; that n0 was even.  But I like illustrating the capability of
; establishing conditions that hold for n0 in the event of
; termination.

(defun half-assertion (th s n0)
  (let ((n (nth 0 (locals (top-frame th s))))
        (a (nth 1 (locals (top-frame th s))))
        (stack (stack (top-frame th s))))
    (and (equal (program (top-frame th s)) *half-prog*)
         (case (pc (top-frame th s))
           (0 (and (equal n n0)
                   (intp n0)
                   (<= 0 n0)))
           (2 (and (intp n0)
                   (<= 0 n0)
                   (intp n)
                   (if (and (<= 0 n)
                            (evenp n))
                       (equal (halfa n a)
                              (halfa n0 0))
                     (not (evenp n)))
                   (iff (evenp n0) (evenp n))))
           (18 (and (evenp n0)
                    (equal (top stack) (halfa n0 0))))
           (otherwise nil)))))

; Observe that the output condition is that n0 is even and that the
; top of the stack contains the semantic expression (halfa n0 0).
; We will later convert this to n0/2.

; ---------------------------------------------------------------------------
; The Invariant -- The Only New Idea in this Note

; Here is the new idea.  I define the invariant for the program by
; using defpun.  The assertions are attached at the three cut points
; and all other statements inherit the invariant of the next
; statement.

(defpun half-inv (th s n0)
  (if (or (equal (pc (top-frame th s)) 0)
          (equal (pc (top-frame th s)) 2)
          (equal (pc (top-frame th s)) 18))
      (half-assertion th s n0)
    (half-inv th (step th s) n0)))

; In one sense, the next lemma is just a technical lemma to force
; half-inv to keep opening if it hasn't reached a cut point yet.  But
; in another sense, this lemma highlights the nice feature of this
; approach.  Suppose that in our function half-assertion we had failed
; to supply a cut point for some loop.  Then we'll get a stack
; overflow from the repeated indefinite application of this rewrite
; rule.  But we do not have to prove we've cut every loop, because the
; half-inv function is tail recursive and so was admitted by defpun.

; In the past when I've used the classic inductive invariant approach
; and used recursion in half-inv to avoid an assertion at every pc, I
; had to invent some kind of measure (``distance to the next cut
; point'') to prove that I had cut every loop.  That annoyed me
; because in the classic inductive invariant approach that burden is
; merely pragmatic -- you had to cut every loop or you couldn't
; generate verification conditions.  But you didn't have to prove you
; had cut every loop.  In my past attempts to mimic this, I had to
; prove more stuff!

(defthm half-inv-make-state-opener
  (implies (and (equal s (make-state tt hp ct))
                (equal pc (pc (top-frame th s)))
                (syntaxp (quotep pc))
                (not (equal pc 0))
                (not (equal pc 2))
                (not (equal pc 18)))
           (equal (half-inv th (make-state tt hp ct) n0)
                  (half-inv th (step th (make-state tt hp ct)) n0))))

; ---------------------------------------------------------------------------
; Proofs

; So here is the key theorem of the inductive invariant approach, showing
; that inv is an invariant.

(defthm half-inv-step
  (implies (half-inv th s n0)
           (half-inv th (step th s) n0)))

; We can immediately conclude that half-inv is an invariant under run,
; as long as the only thread we step is th.

(defun mono-threadedp (th sched)
  (if (endp sched)
      t
    (and (equal th (car sched))
         (mono-threadedp th (cdr sched)))))

(defthm half-inv-run
  (implies (and (mono-threadedp th sched)
                (half-inv th s n0))
           (half-inv th (run sched s) n0))
  :rule-classes nil
  :hints (("Goal" :in-theory (e/d (run)(half-inv-def)))))

; And so we're done.  If we plug in an initial state satisfying the
; invariant we get a final state satisfying it.  If the final state is
; supposed to have pc 18, then we can read out what the invariant
; tells us about that cut point.

(defthm half-main
  (let ((s1 (run sched (modify th s0
                               :pc 0
                               :locals (list n0 any)
                               :stack stk
                               :program *half-prog*))))
    (implies (and (intp n0)
                  (<= 0 n0)
                  (mono-threadedp th sched)
                  (equal (pc (top-frame th s1)) 18))
             (and (evenp n0)
                  (equal (top (stack (top-frame th s1)))
                         (halfa n0 0)))))

  :hints (("Goal" :use
           (:instance half-inv-run
                      (s (modify th s0
                                 :pc 0
                                 :locals (list n0 any)
                                 :stack stk
                                 :program *half-prog*))
                      (th th)
                      (sched sched)
                      (n0 n0))))
  :rule-classes nil)

; ---------------------------------------------------------------------------
; Getting Rid of the Semantic Function

; Now, following our standard paradigm, we get rid of halfa and
; introduce n/2 instead.  There is nothing new here, but I have to
; fight intp and int-fix.

(defthm int-back
  (implies (and (intp (+ a x))
                (integerp a)
                (<= 0 a)
                (integerp x)
                (<= 0 x)
                (integerp y)
                (<= 0 y)
                (<= y x))
           (intp (+ y a)))
  :hints (("Goal" :in-theory (enable intp))))

(defthm halfa-is-half
  (implies (and (intp n)
                (<= 0 n)
                (evenp n)
                (integerp a)
                (<= 0 a)
                (intp (+ (/ n 2) a)))
           (equal (halfa n a)
                  (+ (/ n 2) a)))
  :hints (("Goal" :in-theory (enable evenp))))

(defthm intp-half-n
  (implies (and (intp n)
                (<= 0 n)
                (evenp n))
           (intp (* 1/2 n)))
  :hints (("Goal" :in-theory (enable evenp intp))))

; ---------------------------------------------------------------------------
; The (Partial) Correctness Theorem for Half

; The following theorem summarizes what we now know.  Start with a a
; state running *half-prog* from pc 0 with initial n=n0 and run it
; under an arbitrary mono-threaded schedule to get to s1.  Suppose n0
; is a non-negative int and the pc of s1 is 18.

; Then we conclude that n0 is even and that the top of the stack is
; n0/2.

(defthm half-is-partially-correct
  (let ((s1 (run sched (modify th s0
                               :pc 0
                               :locals (list n0 any)
                               :stack stk
                               :program *half-prog*))))
    (implies (and (intp n0)
                  (<= 0 n0)
                  (mono-threadedp th sched)
                  (equal (pc (top-frame th s1)) 18))
             (and (evenp n0)
                  (equal (top (stack (top-frame th s1))) (/ n0 2)))))
  :hints (("Goal"
           :use ((:instance half-main)))))

; Note that at no point in this exercise have we counted instructions
; or defined a clock or schedule generator.

; ---------------------------------------------------------------------------
; Doing a Sum Program

; To re-illustrate the same methodology, without worrying about
; demonstrating that we can conclude things about the input if we're
; told we terminate, here is a program that sums the ints from n0 down
; to 0.

(defconst *sum-prog*
                    ; We name local[0] n and local[1] a.
  '((iconst_0)      ; 0
    (istore_1)      ; 1               a := 0;
    (iload_0)       ; 2  top of loop:
    (ifeq 14)       ; 3               if n=0, goto 17;
    (iload_0)       ; 6
    (iload_1)       ; 7
    (iadd)          ; 8
    (istore_1)      ; 9               a := n+a;
    (iload_0)       ;10
    (iconst_m1)     ;11
    (iadd)          ;12
    (istore_0)      ;13               n := n-1;
    (goto -12)      ;14               goto top of loop
    (iload_1)       ;17
    (halt)))        ;18               halt with a on top of stack;

(defun suma (n a)
  (if (zp n)
      a
    (suma (- n 1) (int-fix (+ n a)))))

(defun sum-assertion (th s n0)
  (let ((n (nth 0 (locals (top-frame th s))))
        (a (nth 1 (locals (top-frame th s))))
        (stack (stack (top-frame th s))))
    (and (equal (program (top-frame th s)) *sum-prog*)
         (case (pc (top-frame th s))
           (0 (and (equal n n0)
                   (intp n0)
                   (<= 0 n0)))
           (2 (and (intp n0)
                   (intp n)
                   (<= 0 n)
                   (<= n n0)
                   (equal (suma n a)
                          (suma n0 0))))
           (18 (equal (top stack) (suma n0 0)))
           (otherwise nil)))))

(defpun sum-inv (th s n0)
  (if (or (equal (pc (top-frame th s)) 0)
          (equal (pc (top-frame th s)) 2)
          (equal (pc (top-frame th s)) 18))
      (sum-assertion th s n0)
    (sum-inv th (step th s) n0)))

(defthm sum-inv-make-state-opener
  (implies (and (equal s (make-state tt hp ct))
                (equal pc (pc (top-frame th s)))
                (syntaxp (quotep pc))
                (not (equal pc 0))
                (not (equal pc 2))
                (not (equal pc 18)))
           (equal (sum-inv th (make-state tt hp ct) n0)
                  (sum-inv th (step th (make-state tt hp ct)) n0))))

(defthm sum-inv-step
  (implies (sum-inv th s n0)
           (sum-inv th (step th s) n0)))

(defthm sum-inv-run
  (implies (and (mono-threadedp th sched)
                (sum-inv th s n0))
           (sum-inv th (run sched s) n0))
  :rule-classes nil
  :hints (("Goal" :in-theory (e/d (run)(sum-inv-def)))))

(defthm sum-main
  (let ((s1 (run sched (modify th s0
                               :pc 0
                               :locals (list n0 any)
                               :stack stk
                               :program *sum-prog*))))
    (implies (and (intp n0)
                  (<= 0 n0)
                  (mono-threadedp th sched)
                  (equal (pc (top-frame th s1)) 18))
             (equal (top (stack (top-frame th s1))) (suma n0 0))))

  :hints (("Goal" :use
           (:instance sum-inv-run
                      (s (modify th s0
                                 :pc 0
                                 :locals (list n0 any)
                                 :stack stk
                                 :program *sum-prog*))
                      (th th)
                      (sched sched)
                      (n0 n0))))
  :rule-classes nil)

; We don't bother to eliminate suma, though we could if we hacked around
; with intp long enough!

; ---------------------------------------------------------------------------
; A Recursive Method

; Now let's do recursive factorial.   We'll bring in the clocked work
; we have already done, just to have the *demo-state* etc.

(include-book "demo")

; Here is the (redundant) definition of the program.

(defconst *fact-def*
  '("fact" (INT) NIL
    (ILOAD_0)                         ;;;  0
    (IFLE 12)                         ;;;  1
    (ILOAD_0)                         ;;;  4
    (ILOAD_0)                         ;;;  5
    (ICONST_1)                        ;;;  6
    (ISUB)                            ;;;  7
    (INVOKESTATIC "Demo" "fact" 1)    ;;;  8
    (IMUL)                            ;;; 11
    (IRETURN)                         ;;; 12
    (ICONST_1)                        ;;; 13
    (IRETURN)))                       ;;; 14

; The following function recognizes the call stack (cs) of a call of
; the "fact" method on n0.  The function is not applied to the
; top-most frame, because the constraints on the frame are so
; pc-sensitive and the top-most frame may have "any" pc.  So the
; function actually recognizes the rest of the "fact" call stack.
; Here is a picture of the entire call stack.

; -------------------   top-most frame
; pc:       any
; locals:   (n)          5  <- suppose n=5
; stack:    any
; program:  fact prog
; -------------------   caller-frame
; pc:       11
; locals:   (n+1)        6     this is caller-frame 3
; stack:    (n+1)
; program:  fact prog
; -------------------   caller-frame
; pc:       11
; locals:   (n+2)        7     this is caller-frame 2
; stack:    (n+2)
; program:  fact prog
; -------------------   caller-frame
; ...
; -------------------   caller-frame
; pc:       11
; locals:   (n0)         8  <- suppose n0 = 8 ; this is caller frame 1
; stack:    (n0)
; program:  fact prog
; -------------------   the frame below called fact on n0
; ...                   this is caller frame 0

; Note that there are n0-n fact caller frames.  We number them from
; n0-n down to 1.  Caller frame 0 is actually the ``external'' entry
; into fact on n0.  We don't know (or care) whether fact or some other
; program is running there.  Let k be the number of the caller frame.
; Then note that the value of n in that frame is n0-k+1.

(defun fact-caller-framesp (cs n0 k)
  (declare (xargs :measure (acl2-count k)))
  (cond ((zp k) t)
        ((and (equal (pc (top cs)) 11)
              (equal (program (top cs)) (cdddr *fact-def*))
              (equal (sync-flg (top cs)) 'UNLOCKED)
              (intp (nth 0 (locals (top cs))))
              (equal (+ n0 (- k)) (- (nth 0 (locals (top cs))) 1))
              (equal (nth 0 (locals (top cs)))
                     (top (stack (top cs)))))
         (fact-caller-framesp (pop cs) n0 (- k 1)))
        (t nil)))

; We want to compute n0*...*(n+1).  Here is the function we use.

(defun stack-product (n0 k)
  (cond ((zp k) 1)
        (t (* (+ n0 (- k) 1) (stack-product n0 (- k 1))))))

(defthm integerp-stack-product
  (implies (and (integerp n0)
                (integerp k))
           (integerp (stack-product n0 k)))
  :rule-classes ((:rewrite) (:type-prescription)))

(defun sdepth (stk)
  (declare (xargs :hints (("Goal" :in-theory (enable pop)))))
  (if (endp stk)
      0
    (+ 1 (sdepth (pop stk)))))

; This assertion is a little different from the iterative version
; because we have to cope with RETURN.  The two iterative programs
; concluded with HALT instructions.  Now we have to deal with a return
; and so cannot deal with arbitrary schedules -- because we might
; return right out of the program of interest, execute arbitrary code,
; and get back into the program of interest with some weird
; environment.  So we have to run until we do a return from the
; initial environment.  We make an assertion there -- the top of the
; stack contains (int-fix (factorial n0)) -- without even knowing what
; the pc there is.

(defun fact-assertion (th s n0 d0)
  (cond
   ((< (sdepth (call-stack th s)) d0)
    (equal (top (stack (top-frame th s)))
           (int-fix (factorial n0))))
   (t
    (let ((n (nth 0 (locals (top-frame th s)))))
      (and (equal (program (top-frame th s)) (cdddr *fact-def*))
           (equal (lookup-method "fact" "Demo" (class-table s))
                  *fact-def*)
           (equal (sync-flg (top-frame th s)) 'UNLOCKED)
           (intp n0)
           (intp n)
           (<= 0 n)
           (<= n n0)
           (equal (sdepth (call-stack th s)) (+ d0 (- n0 n)))
           (fact-caller-framesp (pop (call-stack th s)) n0 (- n0 n))
           (equal (int-fix (factorial n0))
                  (int-fix (* (factorial n)
                              (stack-product n0 (- n0 n)))))
           (case (pc (top-frame th s))
             (0 t)
             ((12 14) (equal (top (stack (top-frame th s)))
                             (int-fix (factorial n))))
             (otherwise nil)))))))

(defpun fact-inv (th s n0 d0)
  (if (or (< (sdepth (call-stack th s)) d0)
          (equal (pc (top-frame th s)) 0)
          (equal (pc (top-frame th s)) 12)
          (equal (pc (top-frame th s)) 14))
      (fact-assertion th s n0 d0)
    (fact-inv th (step th s) n0 d0)))

(defthm fact-inv-make-state-opener
  (implies (and (equal pc (pc (top-frame th s)))
                (syntaxp (quotep pc))
                (not (< (sdepth (call-stack th s)) d0))
                (not (equal pc 0))
                (not (equal pc 12))
                (not (equal pc 14)))
           (equal (fact-inv th s n0 d0)
                  (fact-inv th (step th s) n0 d0))))

; These next two theorems are technical lemmas to force certain
; substitutions.

(DEFTHM KB-HACK1
  (IMPLIES
   (AND
    (FACT-CALLER-FRAMESP
     (POP (POP (CADR (ASSOC-EQUAL TH (THREAD-TABLE S)))))
     N0
     (+ -1 N0 (- NNN)))
    (EQUAL
     NNN
     (+ -1
        (CAR (LOCALS (TOP (POP (CADR (ASSOC-EQUAL TH (THREAD-TABLE S))))))))))
   (FACT-CALLER-FRAMESP
    (POP (POP (CADR (ASSOC-EQUAL TH (THREAD-TABLE S)))))
    N0
    (+
     N0
     (-
      (CAR (LOCALS (TOP (POP (CADR (ASSOC-EQUAL TH (THREAD-TABLE S))))))))))))

(defthm kb-hack2
  (implies (and (intp aaa)
                (intp bbb)
                (EQUAL aaa (+ -1 bbb)))
           (equal (INT-FIX
                   (*
                    fff
                    (+ 1 aaa)
                    (STACK-PRODUCT N0 (+ -1 N0 (- aaa)))))
                  (INT-FIX
                   (*
                    bbb
                    fff
                    (STACK-PRODUCT N0 (+ N0 (- bbb))))))))

(defthm kb-hack3
  (implies (and (equal xxx (int-fix bbb))
                (integerp aaa)
                (integerp bbb))
           (equal (equal (int-fix (* xxx aaa))
                         (int-fix (* aaa bbb)))
                  t)))

; The deepest fact caller frame has n0 as its local and occurs at
; a cs len of d0.  We are at least that deep.  But what if that is
; the topmost frame?  There must be a frame under it, that called it.
; so we require that 1<d0.

; Here we prove the invariance of fact-inv under step, but we limit
; ourselves to starting states with sufficiently deep stacks.  This
; way we don't have to run past the return the that pops us out of the
; program of interest.

(defthm fact-inv-step
  (implies (and (integerp d0)
                (< 1 d0)
                (<= d0 (sdepth (call-stack th s)))
                (fact-inv th s n0 d0))
           (fact-inv th (step th s) n0 d0))
  :otf-flg t
  :hints
  (("Subgoal 8"
    :expand
    (FACT-CALLER-FRAMESP
     (POP (CADR (ASSOC-EQUAL TH (THREAD-TABLE S))))
     N0
     (+ N0
        (- (CAR (LOCALS (TOP (CADR (ASSOC-EQUAL TH (THREAD-TABLE S))))))))))
   ("Subgoal 6"
    :expand
    (FACT-CALLER-FRAMESP
     (POP (CADR (ASSOC-EQUAL TH (THREAD-TABLE S))))
     N0
     (+ N0
        (- (CAR (LOCALS (TOP (CADR (ASSOC-EQUAL TH (THREAD-TABLE S))))))))))
   ("Subgoal 4'"
    :expand
      (FACT-CALLER-FRAMESP
       (POP (CADR (ASSOC-EQUAL TH (THREAD-TABLE S))))
       N0
       (+ N0
          (- (CAR (LOCALS (TOP (CADR (ASSOC-EQUAL TH (THREAD-TABLE S))))))))))
   ("Subgoal 2"
    :expand
      (FACT-CALLER-FRAMESP
       (POP (CADR (ASSOC-EQUAL TH (THREAD-TABLE S))))
       N0
       (+ N0
          (- (CAR (LOCALS (TOP (CADR (ASSOC-EQUAL TH (THREAD-TABLE S))))))))))
   ))

; Of course, having limited the invariance, we have to define a version of
; run that runs only until the first return that pops out of a given
; call depth.

(defun run-to-return (sched th d0 s)
  (cond ((endp sched) s)
        ((<= d0 (sdepth (call-stack th s)))
         (run-to-return (cdr sched) th d0 (step (car sched) s)))
        (t s)))

; Now we carry on with our standard method...

(defthm fact-inv-run
  (implies (and (mono-threadedp th sched)
                (integerp d0)
                (< 1 d0)
                (fact-inv th s n0 d0))
           (fact-inv th (run-to-return sched th d0 s) n0 d0))
  :rule-classes nil
  :hints (("Goal" :in-theory (e/d (run)(fact-inv-def)))))

; Here is the main theorem.  It opens by letting s1 be a run-to-return
; of s0.  That particular call runs s0 with an abitrarily long
; schedule, sched.  Note that run-to-return does not always return a
; state that has returned to a shorter call-stack depth -- if the
; schedule is exhausted before that happens, the final state may still
; be as deep or deeper than the initial state.  In any case, s0 is the
; initial state and s1 is the final state.

; Now let's read the hypotheses of the implication.  There are five
; blocks of hypotheses.  The first says that n0 is a positive intp.
; The second says that the top-frame of thread th of s0 is a call of
; our "fact" method on n0.  The third says that the depth of the
; call-stack of thread th is greater than 1.  That means there is a
; frame under the call of "fact".  We will call that frame the
; ``caller's frame.''  Of course, if s1 has a shorter call-stack than
; s0, then the caller's frame will be its top-frame, since
; run-to-return stops as soon as we've returned to that depth.  The
; fourth says the schedule consists of nothing but th steps.  Note
; that otherwise we say nothing about the schedule -- it may be
; arbitrarily long.  The fifth block says that the depth of the call
; stack of s1 is less than that of s0, so we know the initial state
; did run long enough to return and hence, the caller's frame is the
; top-frame of s1.

; Then the conclusion is that (int-fix (factorial n0)) is on top of
; the stack of the caller's frame.

(defthm fact-main
  (let ((s1 (run-to-return sched th (sdepth (call-stack th s0)) s0)))
    (implies (and (intp n0)
                  (<= 0 n0)

                  (equal (pc (top-frame th s0)) 0)
                  (equal (locals (top-frame th s0)) (list n0))
                  (equal (program (top-frame th s0))
                         (cdddr *fact-def*))
                  (equal (sync-flg (top-frame th s0)) 'unlocked)
                  (equal (lookup-method "fact" "Demo" (class-table s0))
                         *fact-def*)

                  (< 1 (sdepth (call-stack th s0)))

                  (mono-threadedp th sched)

                  (< (sdepth (call-stack th s1))
                     (sdepth (call-stack th s0))))
             (equal (top (stack (top-frame th s1)))
                    (int-fix (factorial n0)))))

  :hints (("Goal"
           :do-not-induct t
           :use
           (:instance fact-inv-run
                      (s s0)
                      (th th)
                      (sched sched)
                      (n0 n0)
                      (d0 (sdepth (call-stack th s0))))))
  :rule-classes nil)