@@ -382,56 +382,123 @@ theorem triple_rotation_identity (n : Nat) :
382382def 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`. -/
404441theorem 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