Skip to content

Commit abd5a3a

Browse files
hyperpolymathclaude
andcommitted
feat(absolute-zero): complete loadStore_preserves_memory proof — no sorry
Replaces the DEFERRED proof gap with a complete, sorry-free mechanisation using the rewrite-lemma layer approach recommended in the DEFERRED comment. Three private helper lemmas are added above the theorem: - `setReg_cons_zero`: `setReg (r :: rs) 0 val = val :: rs` (unfold + rfl) - `getReg_cons_zero`: `getReg (val :: rs) 0 = some val` (unfold + rfl) - `Memory.update_same_pointwise`: `Memory.update m addr (m addr) a = m a` (by_cases on `a = addr`, closed with `simp` in both branches) The theorem proof: 1. Uses `show` to restate the goal at the definitionally-equal reduced form `step (step s (.load addr 0)) (.store addr 0)).memory`, bypassing the opaque `let s'` binding that blocked the earlier attempt. 2. `unfold step; simp only []` reduces struct-literal field projections (ι-reductions) so the goal is expressed in terms of `setReg`/`getReg`. 3. Case-splits on `s.registers`: - nil → `unfold setReg getReg; simp only []` reduces to the reflexive `Memory.eq s.memory s.memory`; closed by `intro a; rfl`. - cons → `rw [setReg_cons_zero, getReg_cons_zero]; simp only []` resolves the round-trip to `some (s.memory addr)`, reduces the match, and leaves `Memory.eq s.memory (Memory.update s.memory addr (s.memory addr))`; closed by `Memory.update_same_pointwise`. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 8c1fa85 commit abd5a3a

1 file changed

Lines changed: 109 additions & 42 deletions

File tree

proofs/lean4/CNO.lean

Lines changed: 109 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -382,56 +382,123 @@ theorem triple_rotation_identity (n : Nat) :
382382
def loadStoreSame (addr : Nat) : Program :=
383383
[.load addr 0, .store addr 0]
384384

385+
/- ── Helper lemmas for loadStore_preserves_memory ──────────────────────────
386+
387+
These three private lemmas establish the round-trip property of
388+
`setReg`/`getReg` for register index 0 and the no-op character of
389+
`Memory.update m addr (m addr)`. They are the rewrite-lemma layer
390+
mentioned in the DEFERRED comment below.
391+
392+
`setReg_cons_zero` and `getReg_cons_zero` make `simp` able to compute
393+
through the match chains in `setReg`/`getReg` (which are `def`, not
394+
`abbrev`, so are otherwise opaque to the simp normal-form engine).
395+
396+
`Memory.update_same_pointwise` is the key identity-update fact: writing
397+
the value already stored at an address is a no-op, point-wise. -/
398+
399+
private lemma setReg_cons_zero (r val : Nat) (rs : List Nat) :
400+
setReg (r :: rs) 0 val = val :: rs := by
401+
unfold setReg
402+
rfl
403+
404+
private lemma getReg_cons_zero (val : Nat) (rs : List Nat) :
405+
getReg (val :: rs) 0 = some val := by
406+
unfold getReg
407+
rfl
408+
409+
/-- Writing the value already at `addr` back to `addr` is a pointwise no-op. -/
410+
private lemma Memory.update_same_pointwise (m : Memory) (addr a : Nat) :
411+
Memory.update m addr (m addr) a = m a := by
412+
unfold Memory.update
413+
-- The branch condition is `a == addr : Bool`. We case-split on whether
414+
-- `a` and `addr` are propositionally equal, then close each sub-goal.
415+
by_cases h : a = addr
416+
· -- Equal branch: write m addr back → still m addr = m a (since a = addr).
417+
subst h
418+
-- After substitution: `(if addr == addr then m addr else m addr) = m addr`.
419+
simp
420+
· -- Unequal branch: the if-branch is skipped, result is m a.
421+
have hne : (a == addr) = false := by simp [h]
422+
simp [hne]
423+
385424
/-- This preserves memory.
386425
387-
DEFERRED — pre-existing proof gap (not part of the 18-sorry
388-
sorry-debt brief). The mathematics is clear:
389-
- `load addr 0` puts `s.memory addr` into register 0 (no-op if
390-
registers were `[]`).
391-
- `store addr 0` reads register 0 back; if it was set, writes
392-
`Memory.update s.memory addr (s.memory addr)` — the identity
393-
update; if `getReg` returned `none`, the state is unchanged.
394-
- Either way, every memory address `a` is equal in source and
395-
result, satisfying `Memory.eq`.
396-
The mechanisation runs into `cases s.registers` not substituting
397-
inside the deeply-nested `eval`/`step`/`setReg`/`getReg` match
398-
chain. A clean proof needs either an `eval_load`/`eval_store`
399-
rewrite-lemma layer, a strengthened `setReg_getReg` round-trip
400-
lemma, or a switch from `def` to `abbrev` for `setReg`/`getReg`
401-
so simp can compute through them. Tracking in
402-
`~/Desktop/proof-debt-plan.md` under "absolute-zero pre-existing
403-
drift". -/
426+
Proof strategy (rewrite-lemma layer):
427+
- `load addr 0` puts `s.memory addr` into register 0; because
428+
`loadStoreSame` only executes on a concrete two-instruction list,
429+
we can `show` the definitionally-equal fully-reduced form of
430+
`eval (loadStoreSame addr) s` and work on that directly.
431+
- We then case-split on `s.registers`:
432+
nil → `setReg [] 0 _ = []`, `getReg [] 0 = none`, so the
433+
store instruction takes the `none` branch and leaves
434+
memory untouched.
435+
cons → `setReg_cons_zero` + `getReg_cons_zero` give the round-trip
436+
`getReg (setReg (r :: rs) 0 v) 0 = some v`; the store
437+
then writes `Memory.update s.memory addr (s.memory addr)`,
438+
which equals `s.memory` pointwise by
439+
`Memory.update_same_pointwise`.
440+
- No `sorry`. -/
404441
theorem loadStore_preserves_memory (addr : Nat) (s : ProgramState) :
405442
let s' := eval (loadStoreSame addr) s
406443
Memory.eq s.memory s'.memory := by
407-
unfold loadStoreSame eval step
408-
simp
409-
-- s' = eval [.store addr 0] (step s (.load addr 0))
410-
-- = step (step s (.load addr 0)) (.store addr 0)
411-
-- The intermediate state after load:
412-
let s_mid := step s (.load addr 0)
413-
show Memory.eq s.memory (step s_mid (.store addr 0)).memory
444+
-- Reduce `eval (loadStoreSame addr) s` to its fully-computed form.
445+
-- `loadStoreSame addr = [.load addr 0, .store addr 0]` and `eval` on a
446+
-- concrete two-instruction list is definitionally:
447+
-- eval [l, st] s = step (step s l) st
448+
-- so `show` restates the goal at that fully-reduced type.
449+
show Memory.eq s.memory
450+
(step (step s (.load addr 0)) (.store addr 0)).memory
451+
-- Unfold both steps explicitly.
452+
-- step s (.load addr 0)
453+
-- = { s with registers := setReg s.registers 0 (s.memory addr),
454+
-- pc := s.pc + 1 } [call this s_mid]
455+
-- step s_mid (.store addr 0)
456+
-- = match getReg s_mid.registers 0 with
457+
-- | some v => { s_mid with memory := Memory.update s_mid.memory addr v,
458+
-- pc := s_mid.pc + 1 }
459+
-- | none => s_mid
460+
-- s_mid.registers = setReg s.registers 0 (s.memory addr)
461+
-- s_mid.memory = s.memory
462+
-- Unfolding `step` leaves struct-literal field projections.
463+
-- `simp only []` applies pure definitional (ι) reductions — specifically,
464+
-- it reduces struct-literal field accesses like
465+
-- `{ s with registers := X }.registers ↝ X`
466+
-- without unfolding any user-defined `def`.
414467
unfold step
415-
-- By unfolding s_mid, we see setReg.
416-
simp [s_mid, step]
417-
cases h_reg : s.registers with
468+
simp only []
469+
-- Now case-split on s.registers to concretise setReg/getReg.
470+
cases s.registers with
418471
| nil =>
419-
unfold setReg getReg
420-
simp [h_reg]
421-
unfold Memory.eq; intro a; rfl
472+
-- s.registers = []
473+
-- setReg [] 0 _ = [] (nil case of setReg's definition, reduces by ι)
474+
-- getReg [] 0 = [].get? 0 = none (idem, plus List.get? on nil)
475+
-- ⟹ the store instruction takes the `none` error branch → leaves
476+
-- memory and registers unchanged.
477+
-- `unfold setReg getReg` expands both defs by their match clauses.
478+
-- `simp only []` then applies ι-reductions: `[].get? 0 ↝ none`,
479+
-- `match none with ... | none => X ↝ X`, and `.memory` projection;
480+
-- the goal collapses to `Memory.eq s.memory s.memory`.
481+
unfold setReg getReg
482+
simp only []
483+
-- Goal: Memory.eq s.memory s.memory
484+
intro a
485+
rfl
422486
| cons r rs =>
423-
unfold setReg getReg
424-
simp [h_reg]
425-
unfold Memory.eq Memory.update
426-
intro a
427-
split
428-
· -- Case a == addr
429-
have h_eq : a = addr := by
430-
apply beq_iff_eq.mp
431-
assumption
432-
rw [h_eq]
433-
· -- Case a != addr
434-
rfl
487+
-- s.registers = r :: rs
488+
-- setReg (r :: rs) 0 v = v :: rs (setReg_cons_zero)
489+
-- getReg (v :: rs) 0 = some v (getReg_cons_zero)
490+
-- ⟹ store writes Memory.update s.memory addr (s.memory addr),
491+
-- which equals s.memory pointwise (Memory.update_same_pointwise).
492+
--
493+
-- After the two rewrites the match scrutinee is `some (s.memory addr)`.
494+
-- `simp only []` applies the ι (constructor-match) reduction to resolve
495+
-- it to the `some` arm, and then reduces the resulting struct-literal
496+
-- `.memory` field access. The goal is then:
497+
-- Memory.eq s.memory (Memory.update s.memory addr (s.memory addr))
498+
rw [setReg_cons_zero, getReg_cons_zero]
499+
simp only []
500+
intro a
501+
exact (Memory.update_same_pointwise s.memory addr a).symm
435502

436503
/-! ## Decidability and Complexity
437504

0 commit comments

Comments
 (0)