Skip to content
REFERENCE / ZLIB × LEAN

inflate.c.lookup-loop.lean

Source snapshot · 7e23412343bf53ac · Source at the matching commit ↗

Lean source with line numbers. Follow a line number to locate a statement, or an import to read its dependency.

1import ZlibF.Proof.«inflate.c.input-cursor»
2import ZlibF.Proof.«inflate.c.xloop-rank»
3
4namespace Zlib.Inflate.Proofs
5open C Zlib.Inflate
6set_option autoImplicit false
7
8def codeLookupValue (ca : Array Code) (offset root : Nat) (l : IL) : Code :=
9  ca.getD (offset + l.hold.toNat % 2 ^ root) default
10
11/-- Root lookup changes input and bit registers and the selected entry only. -/
12def LookupRegShape (before after : IL) : Prop :=
13  after = { before with next := after.next, have_ := after.have_, hold := after.hold, bits := after.bits, here := after.here }
14
15theorem LookupRegShape.refl (l : IL) : LookupRegShape l l := rfl
16
17theorem LookupRegShape.trans {a b c : IL} (first : LookupRegShape a b) (second : LookupRegShape b c) :
18    LookupRegShape a c := by
19  unfold LookupRegShape at *
20  rw [second, first]
21
22/-- A checked table-lookup body either stops on sufficient bits or refills.
23This loop rule retains exact input consumption, the selected entry and the
24bit stream. Actual lookup bodies must establish the execution equality. -/
25theorem lookupLoop_run_cursor {body : X Bool} {h : Heap} {l : IL} {bs : List UInt8}
26    (query : IL → Code) (need : Code → UInt32)
27    (stable : ∀ current, query { current with here := query current } = query current)
28    (bound : ∀ current, (need (query current)).toNat ≤ 15)
29    (bodyRun : ∀ current, LookupRegShape l current →
30      runX body current h =
31        if need (query current) ≤ current.bits then
32          .ok ((.ok true, { current with here := query current }), h)
33        else runX (do PULLBYTE; pure false : X Bool) { current with here := query current } h)
34    (B : BitInv h l bs) :
35    ∃ result regs used,
36      runX (xloopBrk body) l h = .ok ((result, regs), h) ∧
37      used ≤ 2 ∧ used ≤ bs.length ∧ BitInv h regs (bs.drop used) ∧
38      stream regs (bs.drop used) = stream l bs ∧ LookupRegShape l regs ∧
39      regs.bits.toNat = l.bits.toNat + 8 * used ∧
40      regs.bits.toNat ≤ max l.bits.toNat 22 ∧
41      regs.here = query regs ∧ (need regs.here).toNat ≤ 15 ∧
42      (result = .ok () ∨ result = .error Ex.leave) ∧
43      (result = .ok () → (need regs.here).toNat ≤ regs.bits.toNat) ∧
44      (result = .error Ex.leave → regs.have_ = 0) ∧ regs.next = inputAdvance l.next used := by
45  let P : IL → Heap → Prop := fun regs heap =>
46    heap = h ∧ ∃ used, used ≤ bs.length ∧ BitInv h regs (bs.drop used) ∧
47      stream regs (bs.drop used) = stream l bs ∧ LookupRegShape l regs ∧
48      regs.bits.toNat = l.bits.toNat + 8 * used ∧ regs.bits.toNat ≤ max l.bits.toNat 22 ∧
49      regs.next = inputAdvance l.next used
50  let Q : Except Ex Unit → IL → Heap → Prop := fun result regs heap =>
51    P regs heap ∧ regs.here = query regs ∧
52      (need regs.here).toNat ≤ 15 ∧ (result = .ok () ∨ result = .error Ex.leave) ∧
53      (result = .ok () → (need regs.here).toNat ≤ regs.bits.toNat) ∧
54      (result = .error Ex.leave → regs.have_ = 0)
55  have step : ∀ current heap, P current heap → ∃ test regs heap1,
56      runX (pure true : X Bool) current heap = .ok ((test, regs), heap1) ∧
57      (match test with
58       | .error fault => Q (.error fault) regs heap1
59       | .ok false => Q (.ok ()) regs heap1
60       | .ok true => ∃ result final heap2,
61           runX body regs heap1 = .ok ((result, final), heap2) ∧
62           (match result with
63            | .error fault => Q (.error fault) final heap2
64            | .ok true => Q (.ok ()) final heap2
65            | .ok false => P final heap2 ∧ 15 - final.bits.toNat < 15 - current.bits.toNat)) := by
66    rintro current heap ⟨heapEq, used, usedBound, inv, sameStream, shape, count, bitBound, cursor⟩
67    subst heap
68    refine ⟨.ok true, current, h, rfl, ?_⟩
69    let entry := query current
70    let read := { current with here := entry }
71    have entryBound : (need entry).toNat ≤ 15 := bound current
72    have readInv : BitInv h read (bs.drop used) := ⟨inv.have_eq, inv.next, inv.hold_lt⟩
73    have readShape : LookupRegShape l read := shape.trans (by rfl)
74    have actual := bodyRun current shape
75    change runX body current h =
76      if need entry ≤ current.bits then .ok ((.ok true, read), h)
77      else runX (do PULLBYTE; pure false : X Bool) read h at actual
78    by_cases fits : need entry ≤ current.bits
79    · rw [ite_eq_left fits] at actual
80      refine ⟨.ok true, read, h, actual, ⟨rfl, used, usedBound, readInv, sameStream, readShape, count, bitBound, cursor⟩,
81        (stable current).symm, entryBound, Or.inl rfl, ?_, ?_⟩
82      · intro _
83        have bound := UInt32.le_iff_toNat_le.mp fits
84        exact bound
85      · intro impossible
86        cases impossible
87    · rw [ite_eq_right fits] at actual
88      have short : current.bits.toNat < (need entry).toNat := by
89        have := (not_congr UInt32.le_iff_toNat_le).mp fits
90        omega
91      cases remaining : bs.drop used with
92      | nil =>
93        have empty : read.have_ = 0 := by
94          apply UInt32.toNat_inj.mp
95          have len := inv.have_eq
96          rw [remaining] at len
97          exact len
98        rw [runX_bind, PULLBYTE_zero empty] at actual
99        refine ⟨.error Ex.leave, read, h, actual,
100          ⟨rfl, used, usedBound, readInv, sameStream, readShape, count, bitBound, cursor⟩,
101          (stable current).symm, entryBound, Or.inr rfl, ?_, fun _ => empty⟩
102        intro impossible
103        cases impossible
104      | cons byte rest =>
105        have readCons : BitInv h read (byte :: rest) := by rwa [remaining] at readInv
106        obtain ⟨next, pull, nextInv, nextStream, nextBits, nextCursor⟩ := PULLBYTE_succ_cursor readCons
107          (by change current.bits.toNat ≤ 56; omega)
108        let final := pulled read byte next
109        have restEq : bs.drop (used + 1) = rest := by
110          have eq : (bs.drop used).drop 1 = rest := by rw [remaining]; rfl
111          simpa only [List.drop_drop, Nat.add_comm] using eq
112        have usedNext : used + 1 ≤ bs.length := by
113          have len := congrArg List.length remaining
114          simp only [List.length_drop, List.length_cons] at len
115          omega
116        have finalInv : BitInv h final (bs.drop (used + 1)) := by rwa [restEq]
117        have finalStream : stream final (bs.drop (used + 1)) = stream l bs := by
118          rw [restEq, nextStream, ← remaining]
119          exact sameStream
120        have finalCount : final.bits.toNat = l.bits.toNat + 8 * (used + 1) := by
121          rw [nextBits]
122          change current.bits.toNat + 8 = _
123          rw [count, Nat.mul_add]
124          omega
125        have finalBound : final.bits.toNat ≤ max l.bits.toNat 22 := by
126          rw [nextBits]
127          change current.bits.toNat + 8 ≤ _
128          omega
129        rw [runX_bind, pull] at actual
130        simp only [runX_pure] at actual
131        refine ⟨.ok false, final, h, actual,
132          ⟨rfl, used + 1, usedNext, finalInv, finalStream, readShape.trans (by rfl), finalCount, finalBound, ?_⟩, ?_⟩
133        · change next = inputAdvance l.next (used + 1)
134          rw [nextCursor]
135          change inputAdvance current.next 1 = _
136          rw [cursor, inputAdvance_add]
137        · rw [nextBits]
138          change 15 - (current.bits.toNat + 8) < 15 - current.bits.toNat
139          omega
140  have initial : P l h := ⟨rfl, 0, by omega, by simpa using B, rfl, LookupRegShape.refl l, by simp, by omega, (inputAdvance_zero l.next).symm⟩
141  have fuelBound : 17 < fuel := by decide
142  obtain ⟨result, regs, after, run, post, selected, entryBits, kinds, enough, empty⟩ :=
143    xwhileBrk_rank P (fun regs _ => 15 - regs.bits.toNat) Q step initial (by change 15 - l.bits.toNat + 2 < fuel; omega)
144  obtain ⟨heapEq, used, usedBound, inv, sameStream, shape, count, bitBound, cursor⟩ := post
145  subst after
146  have atMostTwo : used ≤ 2 := by omega
147  exact ⟨result, regs, used, run, atMostTwo, usedBound, inv, sameStream, shape, count, bitBound,
148    selected, entryBits, kinds, enough, empty, cursor⟩
149
150/-- Projection retaining the original checked lookup-loop contract. -/
151theorem lookupLoop_run {body : X Bool} {h : Heap} {l : IL} {bs : List UInt8}
152    (query : IL → Code) (need : Code → UInt32)
153    (stable : ∀ current, query { current with here := query current } = query current)
154    (bound : ∀ current, (need (query current)).toNat ≤ 15)
155    (bodyRun : ∀ current, LookupRegShape l current →
156      runX body current h =
157        if need (query current) ≤ current.bits then
158          .ok ((.ok true, { current with here := query current }), h)
159        else runX (do PULLBYTE; pure false : X Bool) { current with here := query current } h)
160    (B : BitInv h l bs) :
161    ∃ result regs used,
162      runX (xloopBrk body) l h = .ok ((result, regs), h) ∧
163      used ≤ 2 ∧ used ≤ bs.length ∧ BitInv h regs (bs.drop used) ∧
164      stream regs (bs.drop used) = stream l bs ∧ LookupRegShape l regs ∧
165      regs.bits.toNat = l.bits.toNat + 8 * used ∧
166      regs.bits.toNat ≤ max l.bits.toNat 22 ∧
167      regs.here = query regs ∧ (need regs.here).toNat ≤ 15 ∧
168      (result = .ok () ∨ result = .error Ex.leave) ∧
169      (result = .ok () → (need regs.here).toNat ≤ regs.bits.toNat) ∧
170      (result = .error Ex.leave → regs.have_ = 0) := by
171  obtain ⟨result, regs, used, run, two, usedBound, inv, bits, shape, count, bitBound,
172    selected, width, kinds, enough, empty, _⟩ := lookupLoop_run_cursor query need stable bound bodyRun B
173  exact ⟨result, regs, used, run, two, usedBound, inv, bits, shape, count, bitBound,
174    selected, width, kinds, enough, empty⟩
175
176end Zlib.Inflate.Proofs
ZLIB × LEAN

Global contents

READING NOWinflate.c.lookup-loop.lean