Files
lean4/tests/elab/scopeCacheProofs.lean
Joachim Breitner 2e06fb5008 perf: fuse fvar substitution into instantiateMVars (#12233)
This PR replaces the default `instantiateMVars` implementation with a
two-pass variant that fuses fvar substitution into the traversal,
avoiding separate `replace_fvars` calls for delayed-assigned MVars and
preserving sharing. The old single-pass implementation is removed
entirely.

The previous implementation had quadratic complexity when instantiating
expressions with long chains of nested delayed-assigned MVars. Such
chains arise naturally from repeated `intro`/`apply` tactic sequences,
where each step creates a new delayed assignment wrapping the previous
one. The new two-pass approach resolves the entire chain in a single
traversal with a fused fvar substitution, reducing this to linear
complexity.

### Terminology (used in this PR and in the source)

* **Direct MVar**: an MVar that is not delayed-assigned.
* **Pending MVar**: the direct MVar stored in a
`DelayedMetavarAssignment`.
* **Assigned MVar**: a direct MVar with an assignment, or a
delayed-assigned MVar with an assigned pending MVar.
* **MVar DAG**: the directed acyclic graph of MVars reachable from the
expression.
* **Resolvable MVar**: an MVar where all MVars reachable from it
(including itself) are assigned.
* **Updateable MVar**: an assigned direct MVar, or a delayed-assigned
MVar that is resolvable but not reachable from any other resolvable
delayed-assigned MVar.

In the MVar DAG, the updateable delayed-assigned MVars form a cut (the
**updateable-MVar cut**) with only assigned MVars behind it and no
resolvable delayed-assigned MVars before it.

### Two-pass architecture

**Pass 1** (`instantiate_direct_fn`): Traverses all MVars and
expressions reachable from the initial expression and instantiates all
updateable direct MVars (updating their assignment with the result),
instantiates all level MVars, and determines if there are any updateable
delayed-assigned MVars.

**Pass 2** (`instantiate_delayed_fn`): Only run if pass 1 found
updateable delayed-assigned MVars. Has an **outer** and an **inner**
mode, depending on whether it has crossed the updateable-MVar cut.

In outer mode (empty fvar substitution), all MVars are either unassigned
direct MVars (left alone), non-updateable delayed-assigned MVars
(pending MVar traversed in outer mode and updated with the result), or
updateable delayed-assigned MVars. When a delayed-assigned MVar is
encountered, its MVar DAG is explored (via `is_resolvable_pending`) to
determine if it is resolvable (and thus updateable). Results are cached
across invocations.

If it is updateable, the substitution is initialized from its arguments
and traversal continues with the value of its pending MVar in inner
mode. In inner mode (non-empty substitution), all encountered
delayed-assigned MVars are, by construction, resolvable but not
updateable. The substitution is carried along and extended as we cross
such MVars. Pending MVars of these delayed-assigned MVars are NOT
updated with the result (as the result is valid only for this
substitution, not in general).

Applying the substitution in one go, rather than instantiating each
delayed-assigned MVar on its own from inside out, avoids the quadratic
overhead of that approach when there are long chains of delayed-assigned
MVars.

**Write-back behavior**: Pass 2 writes back the normalized pending MVar
values of delayed-assigned MVars above the updateable-MVar cut (the
non-resolvable ones whose children may have been resolved). This is
exactly the right set: these MVars are visited in outer mode, so their
normalized values are suitable for storing in the mctx. MVars below the
cut are visited in inner mode, so their intermediate values cannot be
written back.

### Pass 2 scope-tracked caching

A `scope_cache` data structure ensures that sharing is preserved even
across different delayed-assigned MVars (and hence with different
substitutions), when possible. Each `visit_delayed` call pushes a new
scope with fresh fvar bindings. The cache correctly handles cross-scope
reuse, fvar shadowing, and late-binding via generation counters and
scope-level tracking.

The `scope_cache` has been formally verified:
`tests/elab/scopeCacheProofs.lean` contains a complete Lean proof that
the lazy generation-based implementation refines the eager
specification, covering all operations (push, pop, lookup, insert)
including the rewind lazy cleanup with scope re-entry and degradation.
The key correctness invariant is inter-entry gen list consistency
(GensConsistent), which, unlike per-entry alignment with `currentGens`,
survives pop+push cycles.

### Behavioral differences from original `instantiateMVars`

The implementation matches the original single-pass `instantiateMVars`
behavior with one cosmetic difference: the new implementation
substitutes fvars inline during traversal rather than constructing
intermediate beta-redexes, producing more beta-reduced terms in some
edge cases. This changes the pretty-printed output for two elab tests
(`1179b`, `depElim1`) but all terms remain definitionally equal.

### Tests

Correctness and performance tests for the new implementation were added
in #12808.

### Files

- `src/library/instantiate_mvars.cpp` — C++ implementation of both
passes (replaces `src/kernel/instantiate_mvars.cpp`)
- `src/library/scope_cache.h` — scope-aware cache data structure
- `src/Lean/MetavarContext.lean` — exported accessors for
`DelayedMetavarAssignment` fields
- `tests/elab/scopeCacheProofs.lean` — formal verification of
`scope_cache` correctness
- `tests/elab/1179b.lean.out.expected`,
`tests/elab/depElim1.lean.out.expected` — updated expected output

Co-authored-by: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 17:05:21 +00:00

1691 lines
76 KiB
Lean4
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
module
/-!
# Scope Cache: Specification and Verification
This file was mostly generated by Claude and verified by Lean. If the proofs
here break due to upstream changes and fixing them is a hassle, it is fine to
just delete this test file.
This file models the `scope_cache` data structure from `src/library/scope_cache.h`
and verifies that the optimized implementation refines the simple specification.
## Structure
- `Spec`: The specification — an eager entry-list model where stale entries are
cleaned immediately on pop. No generation counters needed.
- `Imp`: The implementation — a lazy entry-list model with generation-based
staleness detection (matching the C++ `scope_cache`).
- `Tests`: Exhaustive testing of both models on all bounded operation sequences.
- `Proofs`: Formal proof that Imp refines Spec via a simulation invariant.
-/
namespace ScopeCache
/-! ## Specification: Eager entry-list model
Each key independently maintains a list of entries. Each entry has:
- `result`: the cached value
- `scopeLevel`: the scope at which it was inserted
- `resultScope`: the outermost scope the result depends on
The entry covers scope levels `resultScope` through `scopeLevel`.
Entries are non-overlapping and sorted by `scopeLevel`.
On pop, entries whose `resultScope` exceeds the new scope are discarded.
This is the "eager" cleanup — no lazy staleness detection needed.
-/
namespace Spec
structure Entry where
result : Nat
scopeLevel : Nat
resultScope : Nat
deriving Repr, DecidableEq, BEq
structure State where
entries : List Entry
scopeN : Nat
deriving Repr, DecidableEq, BEq
def empty : State := { entries := [], scopeN := 0 }
def push (s : State) : State :=
{ s with scopeN := s.scopeN + 1 }
def pop (s : State) : State :=
let newScope := s.scopeN - 1
{ entries := (s.entries.filter fun e => e.resultScope newScope).map fun e =>
if e.scopeLevel > newScope then { e with scopeLevel := newScope } else e
scopeN := newScope }
def lookup (s : State) : Option (Nat × Nat) :=
match s.entries.getLast? with
| some top => if top.scopeLevel == s.scopeN then some (top.result, top.resultScope) else none
| none => none
def insert (s : State) (value : Nat) (resultScope : Nat) : State × Nat :=
let shared := match s.entries.getLast? with
| some top => if top.resultScope == resultScope then top.result else value
| none => value
let entries' := s.entries.filter fun e => e.scopeLevel < resultScope
let newEntry : Entry := { result := shared, scopeLevel := s.scopeN, resultScope }
({ s with entries := entries' ++ [newEntry] }, shared)
end Spec
/-! ## Implementation: Lazy entry-list model with generation counters
Models the C++ `scope_cache` for a single key. Entries are NOT cleaned on pop;
instead, they are lazily validated via generation counters when accessed (rewind).
-/
namespace Imp
abbrev GenList := List Nat
structure Entry where
result : Nat
scopeLevel : Nat
scopeGens : GenList -- snapshot of gen list at insert time
resultScope : Nat
deriving Repr, DecidableEq, BEq, Inhabited
structure State where
entries : List Entry
scopeN : Nat
genCounter : Nat
currentGens : GenList -- head = gen for scopeN
deriving Repr
def empty : State :=
{ entries := [], scopeN := 0, genCounter := 0, currentGens := [0] }
def push (s : State) : State :=
{ s with
scopeN := s.scopeN + 1
genCounter := s.genCounter + 1
currentGens := (s.genCounter + 1) :: s.currentGens }
def pop (s : State) : State :=
{ s with
scopeN := s.scopeN - 1
currentGens := s.currentGens.tail }
/-- Search from `level` down to `resultScope` for a level where entry and current gens match.
Returns the matching level and the entry's remaining gen list at that level. -/
def findValidLevel (eGens cGens : GenList) (level resultScope : Nat) :
Option (Nat × GenList) :=
match eGens.head?, cGens.head? with
| some eg, some cg =>
if eg == cg then some (level, eGens)
else if h : level resultScope then none
else findValidLevel eGens.tail cGens.tail (level - 1) resultScope
| _, _ => none
termination_by level - resultScope
/-- Rewind: clean up the entry stack from the back. -/
def rewind (entries : List Entry) (scopeN : Nat) (currentGens : GenList) : List Entry :=
go entries.reverse []
where
go : List Entry List Entry List Entry
| [], acc => acc
| top :: rest, acc =>
if top.resultScope > scopeN then
go rest acc
else
let degradedLevel := min top.scopeLevel scopeN
let degradedGens := top.scopeGens.drop (top.scopeLevel - degradedLevel)
let currentGensAligned := currentGens.drop (scopeN - degradedLevel)
match findValidLevel degradedGens currentGensAligned degradedLevel top.resultScope with
| some (lvl, eGens) =>
rest.reverse ++ [{ top with scopeLevel := lvl, scopeGens := eGens }] ++ acc
| none => go rest acc
def lookup (s : State) : Option (Nat × Nat) :=
let entries' := rewind s.entries s.scopeN s.currentGens
match entries'.getLast? with
| some top => if top.scopeLevel == s.scopeN then some (top.result, top.resultScope) else none
| none => none
def insert (s : State) (value : Nat) (resultScope : Nat) : State × Nat :=
let entries' := rewind s.entries s.scopeN s.currentGens
let shared := match entries'.getLast? with
| some top => if top.resultScope == resultScope then top.result else value
| none => value
let entries'' := entries'.filter fun e => e.scopeLevel < resultScope
let newEntry : Entry := {
result := shared, scopeLevel := s.scopeN, scopeGens := s.currentGens, resultScope
}
({ s with entries := entries'' ++ [newEntry] }, shared)
/-- Erase generation info from an Imp entry to get a Spec entry. -/
def Entry.toSpec (e : Entry) : Spec.Entry :=
{ result := e.result, scopeLevel := e.scopeLevel, resultScope := e.resultScope }
end Imp
/-! ## Tests -/
namespace Tests
-- Basic Spec tests
#eval do
let s := Spec.empty
assert! Spec.lookup s == none
let (s, _) := Spec.insert s 100 0
assert! Spec.lookup s == some (100, 0)
let s := Spec.push s
assert! Spec.lookup s == none -- entry at scope 0, not at scope 1
let (s, v) := Spec.insert s 200 0
assert! v == 100 -- sharing
assert! Spec.lookup s == some (100, 0)
let s := Spec.pop s
assert! Spec.lookup s == some (100, 0)
return ()
-- Basic Imp tests
#eval do
let s := Imp.empty
assert! Imp.lookup s == none
let (s, _) := Imp.insert s 100 0
assert! Imp.lookup s == some (100, 0)
let s := Imp.push s
assert! Imp.lookup s == none
let (s, v) := Imp.insert s 200 0
assert! v == 100 -- sharing
assert! Imp.lookup s == some (100, 0)
let s := Imp.pop s
assert! Imp.lookup s == some (100, 0)
return ()
-- Re-entry test
#eval do
let s := Imp.empty
let s := Imp.push s
let (s, _) := Imp.insert s 100 1
assert! Imp.lookup s == some (100, 1)
let s := Imp.pop s
let s := Imp.push s -- re-enter scope 1
assert! Imp.lookup s == none -- stale!
return ()
-- Degradation test
#eval do
let s := Imp.empty
let s := Imp.push s
let s := Imp.push s
let s := Imp.push s -- scope 3
let (s, _) := Imp.insert s 100 1
assert! Imp.lookup s == some (100, 1)
let s := Imp.pop s -- scope 2
assert! Imp.lookup s == some (100, 1)
let s := Imp.pop s -- scope 1
assert! Imp.lookup s == some (100, 1)
return ()
-- WalkDown test: all scopes re-entered, walkDown must iterate to scope 0
#eval do
let s := Imp.empty
let s := Imp.push s -- scope 1
let s := Imp.push s -- scope 2
let s := Imp.push s -- scope 3
let (s, _) := Imp.insert s 100 0
let s := Imp.pop s -- scope 2
let s := Imp.pop s -- scope 1
let s := Imp.pop s -- scope 0
let s := Imp.push s -- scope 1 (re-enter)
let s := Imp.push s -- scope 2 (re-enter)
let s := Imp.push s -- scope 3 (re-enter)
-- Entry should survive at level 0 (only scope 0 unchanged)
let (s, v) := Imp.insert s 200 0
assert! v == 100 -- sharing: rewind found entry at level 0
assert! Imp.lookup s == some (100, 0)
return ()
/-! ### Exhaustive verification
Verify that Spec and Imp produce identical observable results for ALL
operation sequences up to a bounded depth and length. -/
partial def verifySpecImp (maxOps maxScope : Nat) : IO Unit :=
go maxOps Spec.empty Imp.empty 0 []
where
go (fuel : Nat) (spec : Spec.State) (imp : Imp.State) (depth : Nat)
(trace : List String) : IO Unit := do
if fuel == 0 then return
-- push
if depth < maxScope then
let spec' := Spec.push spec
let imp' := Imp.push imp
if Spec.lookup spec' != Imp.lookup imp' then
throw <| IO.userError s!"push lookup mismatch after {trace}"
go (fuel - 1) spec' imp' (depth + 1) (trace ++ ["push"])
-- pop
if depth > 0 then
let spec' := Spec.pop spec
let imp' := Imp.pop imp
if Spec.lookup spec' != Imp.lookup imp' then
throw <| IO.userError s!"pop lookup mismatch after {trace}"
go (fuel - 1) spec' imp' (depth - 1) (trace ++ ["pop"])
-- inserts
for v in [0, 1, 2] do
for rs in List.range (depth + 1) do
let (spec', sv) := Spec.insert spec v rs
let (imp', iv) := Imp.insert imp v rs
if sv != iv then
throw <| IO.userError s!"insert sharing mismatch after {trace}: insert {v} {rs}"
if Spec.lookup spec' != Imp.lookup imp' then
throw <| IO.userError s!"insert lookup mismatch after {trace}: insert {v} {rs}"
go (fuel - 1) spec' imp' depth (trace ++ [s!"ins({v},{rs})"])
#eval do
verifySpecImp 4 3
return ()
end Tests
/-! ## Formal Proofs
We prove that for any sequence of operations starting from empty,
Spec and Imp produce identical lookup results and insert-sharing values.
### Approach
We define a simulation invariant `SimInv` relating Spec and Imp states:
the Imp entries, after rewinding, match the Spec entries (modulo generation info).
We prove:
1. `SimInv` holds for empty states
2. Each operation preserves `SimInv`
3. `SimInv` implies lookup equivalence and insert-sharing equivalence
Key rewind properties are stated as separate lemmas.
-/
namespace Proofs
/-! ### Rewind helper lemmas
These lemmas capture the essential properties of the `rewind` function needed
for the simulation proof. They express how rewind behaves under push, pop,
and when applied to freshly-inserted entries. -/
/-- After Imp.insert, the entries are `filtered ++ [newEntry]` where newEntry
has current scope and gens. Rewind at the same scope is identity because
rewind processes from the back and immediately finds the valid last entry. -/
theorem rewind_after_insert (filtered : List Imp.Entry) (newEntry : Imp.Entry)
(scopeN : Nat) (gens : Imp.GenList)
(hSL : newEntry.scopeLevel = scopeN)
(hGens : newEntry.scopeGens = gens)
(hRS : newEntry.resultScope scopeN)
(hGensPos : gens.length > 0) :
Imp.rewind (filtered ++ [newEntry]) scopeN gens = filtered ++ [newEntry] := by
subst hSL; subst hGens
simp only [Imp.rewind]
rw [List.reverse_append, List.reverse_cons, List.reverse_nil, List.nil_append,
List.singleton_append]
rw [Imp.rewind.go]
-- resultScope ≤ scopeLevel, so not discarded
simp only [show (newEntry.resultScope > newEntry.scopeLevel) = False from
eq_false (Nat.not_lt.mpr hRS), reduceIte]
-- degradedLevel = scopeLevel, drops are zero
simp only [Nat.min_self, Nat.sub_self, List.drop_zero]
-- findValidLevel with same eGens and cGens: head? values are equal
obtain g, gl, hgl := List.exists_cons_of_length_pos hGensPos
conv => lhs; rw [hgl, Imp.findValidLevel]; simp
rw [ hgl]
/-- When the first gen check fails due to a fresh gen g, findValidLevel at (n+1)
with g::cGens reduces to findValidLevel at n with eGens.tail and cGens. -/
theorem findValidLevel_skip_fresh (eGens cGens : Imp.GenList) (n g resultScope : Nat)
(hFreshHead : k < eGens.length, eGens[k]! < g) (hRS : resultScope n) :
Imp.findValidLevel eGens (g :: cGens) (n + 1) resultScope =
Imp.findValidLevel eGens.tail cGens n resultScope := by
cases eGens with
| nil =>
-- eGens empty → both sides none
simp [Imp.findValidLevel]
| cons v vs =>
-- eGens = v :: vs, head? = some v, tail = vs
have hvg : v < g := by have := hFreshHead 0 (by simp); simpa using this
rw [Imp.findValidLevel]
simp [show (v == g) = false from by simp [Nat.ne_of_lt hvg],
show ¬(n + 1 resultScope) from Nat.not_le.mpr (Nat.lt_succ_of_le hRS)]
/-- Push rewind equivalence: rewind at (n+1, g::gens) equals rewind at (n, gens)
when g is fresh (strictly greater than all gens in all entries). -/
theorem rewind_push_equiv (entries : List Imp.Entry) (n g : Nat) (gens : Imp.GenList)
(hFresh : e entries, k < e.scopeGens.length, e.scopeGens[k]! < g)
(hRSSL : e entries, e.resultScope e.scopeLevel) :
Imp.rewind entries (n + 1) (g :: gens) = Imp.rewind entries n gens := by
simp only [Imp.rewind]
suffices revEntries acc,
( e revEntries, k < e.scopeGens.length, e.scopeGens[k]! < g)
( e revEntries, e.resultScope e.scopeLevel)
Imp.rewind.go (n + 1) (g :: gens) revEntries acc =
Imp.rewind.go n gens revEntries acc by
exact this entries.reverse [] (fun e he => hFresh e (List.mem_reverse.mp he))
(fun e he => hRSSL e (List.mem_reverse.mp he))
intro revEntries acc hFreshRev hRSRev
induction revEntries generalizing acc with
| nil => simp [Imp.rewind.go]
| cons top rest ih =>
have hTF := hFreshRev top (List.mem_cons_self ..)
have hTR := hRSRev top (List.mem_cons_self ..)
have hRF := fun e he => hFreshRev e (List.mem_cons_of_mem _ he)
have hRR := fun e he => hRSRev e (List.mem_cons_of_mem _ he)
simp only [Imp.rewind.go]
-- Helper: degraded gens freshness (all gens in dropped list are still < g)
have hDGFresh : d, k < (top.scopeGens.drop d).length,
(top.scopeGens.drop d)[k]! < g := by
intro d k hk
simp only [List.length_drop] at hk
have hkd : k + d < top.scopeGens.length := Nat.add_lt_of_lt_sub hk
have h1 := hTF (k + d) hkd
rw [show top.scopeGens[k + d]! = (top.scopeGens.drop d)[k]! from by
simp [List.getElem!_eq_getElem?_getD, List.getElem?_drop, Nat.add_comm]] at h1
exact h1
-- Main case split: first handle LHS (scope n+1), then RHS (scope n)
by_cases hGN1 : top.resultScope > n + 1
· -- resultScope > n+1: both discard (also > n)
have hGN : top.resultScope > n := Nat.lt_of_le_of_lt (Nat.le_succ n) hGN1
simp only [hGN1, hGN, reduceIte]; exact ih acc hRF hRR
· simp only [show ¬(top.resultScope > n + 1) from hGN1, reduceIte]
by_cases hGN : top.resultScope > n
· -- resultScope = n+1: discarded at (n), findValidLevel returns none at (n+1)
simp only [hGN, reduceIte]
have hEq : top.resultScope = n + 1 := Nat.le_antisymm (Nat.not_lt.mp hGN1) hGN
have hSL : top.scopeLevel n + 1 := hEq hTR
simp only [show min top.scopeLevel (n + 1) = n + 1 from Nat.min_eq_right hSL,
Nat.sub_self, List.drop_zero]
rw [hEq]; cases hDG : top.scopeGens.drop (top.scopeLevel - (n + 1)) with
| nil => simp [Imp.findValidLevel]; exact ih acc hRF hRR
| cons v vs =>
rw [Imp.findValidLevel]; simp only [List.head?_cons]
have hvg : v < g := by
have := hDGFresh (top.scopeLevel - (n + 1)) 0 (by simp [hDG])
simpa [hDG] using this
simp [show (v == g) = false from by simp [Nat.ne_of_lt hvg]]
exact ih acc hRF hRR
· -- resultScope ≤ n: both not discarded
simp only [show ¬(top.resultScope > n) from hGN, reduceIte]
have hRSN : top.resultScope n := Nat.not_lt.mp hGN
by_cases hSL : top.scopeLevel n
· -- scopeLevel ≤ n: identical processing
simp only [show min top.scopeLevel (n + 1) = top.scopeLevel from
Nat.min_eq_left (Nat.le_succ_of_le hSL),
show min top.scopeLevel n = top.scopeLevel from Nat.min_eq_left hSL,
Nat.sub_self, List.drop_zero]
have hDrop : List.drop (n + 1 - top.scopeLevel) (g :: gens) =
List.drop (n - top.scopeLevel) gens := by
rw [show n + 1 - top.scopeLevel = (n - top.scopeLevel) + 1 from by grind]; rfl
rw [hDrop]
-- Both sides now match on the same findValidLevel call
split
· rfl -- some case: identical
· exact ih acc hRF hRR -- none case: induction hypothesis
· -- scopeLevel > n: use findValidLevel_skip_fresh
have hSLgt : top.scopeLevel > n := Nat.not_le.mp hSL
simp only [show min top.scopeLevel (n + 1) = n + 1 from
Nat.min_eq_right (Nat.succ_le_of_lt hSLgt),
show min top.scopeLevel n = n from Nat.min_eq_right (Nat.le_of_lt hSLgt),
Nat.sub_self, List.drop_zero]
have hTailEq : (List.drop (top.scopeLevel - (n + 1)) top.scopeGens).tail =
List.drop (top.scopeLevel - n) top.scopeGens := by
rw [List.tail_drop]; congr 1; grind
rw [findValidLevel_skip_fresh _ _ _ _ _ (hDGFresh _) hRSN, hTailEq]
split
· rfl
· exact ih acc hRF hRR
/-- findValidLevel returns gens that are a suffix (via drop) of the input eGens. -/
theorem findValidLevel_gens_suffix {eGens cGens : Imp.GenList} {level resultScope lvl : Nat}
{gs : Imp.GenList}
(h : Imp.findValidLevel eGens cGens level resultScope = some (lvl, gs)) :
d, gs = eGens.drop d := by
induction eGens generalizing level cGens with
| nil => simp [Imp.findValidLevel] at h
| cons v vs ih =>
cases cGens with
| nil => simp [Imp.findValidLevel] at h
| cons w ws =>
rw [Imp.findValidLevel] at h
simp only [List.head?_cons] at h
split at h
· simp at h; exact 0, by simp [h.2]
· split at h
· simp at h
· obtain d, hd := ih h
exact d + 1, by rw [hd]; rfl
/-- findValidLevel returns a level ≤ the input level. -/
theorem findValidLevel_lvl_le {eGens cGens : Imp.GenList} {level resultScope lvl : Nat}
{gs : Imp.GenList}
(h : Imp.findValidLevel eGens cGens level resultScope = some (lvl, gs)) :
lvl level := by
induction eGens generalizing level cGens with
| nil => simp [Imp.findValidLevel] at h
| cons v vs ih =>
cases cGens with
| nil => simp [Imp.findValidLevel] at h
| cons w ws =>
rw [Imp.findValidLevel] at h
simp only [List.head?_cons] at h
split at h
· simp at h; exact h.1 Nat.le_refl _
· split at h
· simp at h
· exact Nat.le_trans (ih h) (Nat.sub_le level 1)
/-- findValidLevel returns a level ≥ resultScope. -/
theorem findValidLevel_rs_le {eGens cGens : Imp.GenList} {level resultScope lvl : Nat}
{gs : Imp.GenList}
(hLR : resultScope level)
(h : Imp.findValidLevel eGens cGens level resultScope = some (lvl, gs)) :
resultScope lvl := by
induction eGens generalizing level cGens with
| nil => simp [Imp.findValidLevel] at h
| cons v vs ih =>
cases cGens with
| nil => simp [Imp.findValidLevel] at h
| cons w ws =>
rw [Imp.findValidLevel] at h
simp only [List.head?_cons] at h
split at h
· simp at h; exact h.1 hLR
· split at h
· simp at h
· exact ih (by grind) h
/-- When findValidLevel matches at the starting level, it returns the input gens unchanged. -/
theorem findValidLevel_match_at_level {eGens cGens : Imp.GenList} {level rs : Nat}
{gs : Imp.GenList}
(h : Imp.findValidLevel eGens cGens level rs = some (level, gs)) :
gs = eGens := by
cases eGens with
| nil => simp [Imp.findValidLevel] at h
| cons v vs =>
cases cGens with
| nil => simp [Imp.findValidLevel] at h
| cons w ws =>
rw [Imp.findValidLevel] at h
simp only [List.head?_cons] at h
split at h
· simp at h; exact h.symm
· split at h
· simp at h
· -- Recursive case: fvl(vs, ws, level-1, rs) returns (level, gs).
-- But findValidLevel_lvl_le gives level ≤ level - 1, contradicting level ≥ 1.
rename_i _ hlrs
have := findValidLevel_lvl_le h
omega
/-- The output gens of findValidLevel is a drop-suffix of the input eGens. -/
theorem findValidLevel_output_eq_input_drop
{eGens cGens : Imp.GenList} {level rs lvl : Nat} {gs : Imp.GenList}
(h : Imp.findValidLevel eGens cGens level rs = some (lvl, gs)) :
gs = eGens.drop (level - lvl) := by
induction eGens generalizing cGens level with
| nil => simp [Imp.findValidLevel] at h
| cons v vs ih =>
cases cGens with
| nil => simp [Imp.findValidLevel] at h
| cons w ws =>
rw [Imp.findValidLevel] at h
simp only [List.head?_cons] at h
split at h
· simp at h; obtain hlvl, hgs := h
subst hlvl; subst hgs; simp
· split at h
· simp at h
· have hrec := ih h
have hlvl_lt : lvl < level := by
have := findValidLevel_lvl_le h; omega
rw [hrec, show level - lvl = (level - 1 - lvl) + 1 from by omega,
List.drop_succ_cons]
/-- When findValidLevel succeeds and gensSuffix holds between the original entry gens
and the current gens list, the output gens are aligned at the correct offset. -/
theorem findValidLevel_aligned
{eGens cGens : Imp.GenList} {level rs lvl : Nat} {gs : Imp.GenList}
(hfvl : Imp.findValidLevel eGens cGens level rs = some (lvl, gs))
{origGens gens : Imp.GenList} {d offset : Nat}
(hE : eGens = origGens.drop d) (hC : cGens = gens.drop offset)
(hGensSuffix : i < origGens.length, j < gens.length,
origGens[i]! = gens[j]! origGens.drop i = gens.drop j) :
gs = gens.drop (offset + (level - lvl)) := by
induction eGens generalizing cGens level d offset with
| nil => simp [Imp.findValidLevel] at hfvl
| cons v vs ih =>
cases cGens with
| nil => simp [Imp.findValidLevel] at hfvl
| cons w ws =>
rw [Imp.findValidLevel] at hfvl
simp only [List.head?_cons] at hfvl
split at hfvl
· -- Match: v == w
rename_i hvw
simp at hfvl
obtain hlvl, hgs := hfvl
subst hlvl; subst hgs; simp
-- From hE: v :: vs = origGens.drop d, so origGens[d]! = v
have hd : d < origGens.length := by
have : (origGens.drop d).length > 0 := by rw [ hE]; simp
simp [List.length_drop] at this; omega
have ho : offset < gens.length := by
have : (gens.drop offset).length > 0 := by rw [ hC]; simp
simp [List.length_drop] at this; omega
have hv : origGens[d]! = v := by
have : (origGens.drop d)[0]! = v := by rw [ hE]; rfl
rwa [List.getElem!_eq_getElem?_getD, List.getElem?_drop, Nat.add_zero,
List.getElem!_eq_getElem?_getD] at this
have hw : gens[offset]! = w := by
have : (gens.drop offset)[0]! = w := by rw [ hC]; rfl
rwa [List.getElem!_eq_getElem?_getD, List.getElem?_drop, Nat.add_zero,
List.getElem!_eq_getElem?_getD] at this
have heq : origGens[d]! = gens[offset]! := by
rw [hv, hw]; exact beq_iff_eq.mp hvw
rw [ hGensSuffix d hd offset ho heq, hE]
· split at hfvl
· simp at hfvl
· -- Recurse: fvl vs ws (level - 1) rs
rename_i hne hlrs
have hE' : vs = origGens.drop (d + 1) := by
have h := congrArg List.tail hE
simp only [List.tail_cons, List.tail_drop] at h
exact h
have hC' : ws = gens.drop (offset + 1) := by
have h := congrArg List.tail hC
simp only [List.tail_cons, List.tail_drop] at h
exact h
have hlvlle := findValidLevel_lvl_le hfvl
have hih := ih hfvl hE' hC'
rw [hih]; congr 1
omega
/-- Every entry in rewind output either comes from the input unchanged or has its
scopeGens as a suffix (via drop) of some input entry's scopeGens. -/
theorem rewind_mem {entries : List Imp.Entry} {n : Nat} {gens : Imp.GenList}
{e : Imp.Entry} (he : e Imp.rewind entries n gens) :
e' entries, e.result = e'.result e.resultScope = e'.resultScope
( d, e.scopeGens = e'.scopeGens.drop d) := by
simp only [Imp.rewind] at he
suffices revEntries acc,
( e revEntries, e entries)
( e acc, e' entries, e.result = e'.result e.resultScope = e'.resultScope
d, e.scopeGens = e'.scopeGens.drop d)
e Imp.rewind.go n gens revEntries acc
e' entries, e.result = e'.result e.resultScope = e'.resultScope
d, e.scopeGens = e'.scopeGens.drop d by
exact this entries.reverse []
(fun e he => List.mem_reverse.mp he) (fun _ h => nomatch h) he
intro revEntries acc hRev hAcc
induction revEntries generalizing acc with
| nil =>
simp [Imp.rewind.go]; intro hmem; exact hAcc e hmem
| cons top rest ih =>
intro he'
have hTopMem := hRev top (List.mem_cons_self ..)
have hRestMem := fun e he => hRev e (List.mem_cons_of_mem _ he)
simp only [Imp.rewind.go] at he'
split at he'
· exact ih acc hRestMem hAcc he'
· split at he'
· -- Found valid: rest.reverse ++ [modified_top] ++ acc
rename_i _ lvl eGens hfvl
rcases List.mem_append.mp he' with h1 | h2
· rcases List.mem_append.mp h1 with hr | hm
· exact e, hRev e (List.mem_cons_of_mem _ (List.mem_reverse.mp hr)),
rfl, rfl, 0, by simp
· simp only [List.mem_cons, List.mem_nil_iff, or_false] at hm
subst hm
obtain d, hd := findValidLevel_gens_suffix hfvl
exact top, hTopMem, rfl, rfl,
top.scopeLevel - min top.scopeLevel n + d, by rw [hd, List.drop_drop]
· exact hAcc e h2
· exact ih acc hRestMem hAcc he'
/-- Entries in rewind output have their gen values bounded by the input entries' gen values. -/
theorem rewind_genBound {entries : List Imp.Entry} {n : Nat} {gens : Imp.GenList}
{bound : Nat}
(hBound : e entries, k < e.scopeGens.length, e.scopeGens[k]! bound)
{e : Imp.Entry} (he : e Imp.rewind entries n gens) :
k < e.scopeGens.length, e.scopeGens[k]! bound := by
obtain e', he', _, _, d, hd := rewind_mem he
intro k hk; rw [hd] at hk
have hkd : k + d < e'.scopeGens.length := by
simp [List.length_drop] at hk; grind
have h1 := hBound e' he' (k + d) hkd
rwa [show e'.scopeGens[k + d]! = (e'.scopeGens.drop d)[k]! from by
simp [List.getElem!_eq_getElem?_getD, List.getElem?_drop, Nat.add_comm]] at h1
/-- Entries in rewind output have resultScope ≤ scopeLevel. -/
theorem rewind_rsLeSl {entries : List Imp.Entry} {n : Nat} {gens : Imp.GenList}
(hRSSL : e entries, e.resultScope e.scopeLevel)
{e : Imp.Entry} (he : e Imp.rewind entries n gens) :
e.resultScope e.scopeLevel := by
simp only [Imp.rewind] at he
suffices revEntries acc,
( e revEntries, e.resultScope e.scopeLevel)
( e acc, e.resultScope e.scopeLevel)
e Imp.rewind.go n gens revEntries acc e.resultScope e.scopeLevel by
exact this entries.reverse [] (fun e he => hRSSL e (List.mem_reverse.mp he))
(fun _ h => nomatch h) he
intro revEntries acc hRev hAcc
induction revEntries generalizing acc with
| nil => simp [Imp.rewind.go]; intro hmem; exact hAcc e hmem
| cons top rest ih =>
intro he'
have hRestRev := fun e he => hRev e (List.mem_cons_of_mem _ he)
simp only [Imp.rewind.go] at he'
split at he'
· exact ih acc hRestRev hAcc he'
· split at he'
· rename_i _ lvl eGens hfvl
rcases List.mem_append.mp he' with h1 | h2
· rcases List.mem_append.mp h1 with hr | hm
· exact hRev _ (List.mem_cons_of_mem _ (List.mem_reverse.mp hr))
· simp only [List.mem_cons, List.mem_nil_iff, or_false] at hm
subst hm; simp only
exact findValidLevel_rs_le (by simp [Nat.min_def]; split <;> grind) hfvl
· exact hAcc e h2
· exact ih acc hRestRev hAcc he'
/-- Entries are well-ordered: each entry's scopeLevel < every later entry's resultScope.
This holds for entries produced by Imp.insert (which filters scopeLevel < rs). -/
def Imp.EntryOrdered (entries : List Imp.Entry) : Prop :=
entries.Pairwise (fun a b => a.scopeLevel < b.resultScope)
/-- Rewind preserves entry ordering. -/
theorem rewind_entryOrdered {entries : List Imp.Entry} {n : Nat} {gens : Imp.GenList}
(hOrd : Imp.EntryOrdered entries) :
Imp.EntryOrdered (Imp.rewind entries n gens) := by
simp only [Imp.rewind, Imp.EntryOrdered]
suffices revEntries acc,
(revEntries.reverse ++ acc).Pairwise (fun a b => a.scopeLevel < b.resultScope)
acc.Pairwise (fun a b => a.scopeLevel < b.resultScope)
(Imp.rewind.go n gens revEntries acc).Pairwise
(fun a b => a.scopeLevel < b.resultScope) by
exact this entries.reverse []
(by simpa using hOrd) List.Pairwise.nil
intro revEntries acc hAll hAcc
induction revEntries generalizing acc with
| nil => simpa [Imp.rewind.go]
| cons top rest ih =>
simp only [Imp.rewind.go]
-- Extract ordering facts from hAll
rw [List.reverse_cons, List.append_assoc, List.singleton_append] at hAll
have hP := List.pairwise_append.mp hAll
have hOrdPre := hP.1
have hOrdTA := hP.2.1
have hCross := hP.2.2
have hTopAcc := List.pairwise_cons.mp hOrdTA
split
· -- top.resultScope > n: skip top
apply ih acc
· rw [List.pairwise_append]
exact hOrdPre, hTopAcc.2,
fun a ha b hb => hCross a ha b (List.mem_cons_of_mem _ hb)
· exact hAcc
· -- top.resultScope ≤ n
split
· -- findValidLevel returns some (lvl, eGens)
rename_i _ lvl eGens hfvl
-- Result is rest.reverse ++ [modified_top] ++ acc
-- modified_top has same resultScope, scopeLevel ≤ top.scopeLevel
have hLvlLe : lvl min top.scopeLevel n :=
findValidLevel_lvl_le hfvl
have hLvlLeSL : lvl top.scopeLevel :=
Nat.le_trans hLvlLe (Nat.min_le_left ..)
rw [List.append_assoc, List.pairwise_append]
refine hOrdPre, ?_, ?_
· -- Pairwise for [modified_top] ++ acc
rw [List.singleton_append, List.pairwise_cons]
exact fun b hb => Nat.lt_of_le_of_lt hLvlLeSL (hTopAcc.1 b hb), hTopAcc.2
· -- ∀ a ∈ rest.reverse, ∀ b ∈ [modified_top] ++ acc, a.sl < b.rs
intro a ha b hb
rw [List.singleton_append, List.mem_cons] at hb
cases hb with
| inl heq =>
subst heq; simp only
exact hCross a ha top (List.mem_cons_self ..)
| inr hb' => exact hCross a ha b (List.mem_cons_of_mem _ hb')
· -- findValidLevel returns none: skip top
apply ih acc
· rw [List.pairwise_append]
exact hOrdPre, hTopAcc.2,
fun a ha b hb => hCross a ha b (List.mem_cons_of_mem _ hb)
· exact hAcc
/-- When all entries in revEntries are trivially valid at the given scope
(findValidLevel matches immediately at their scopeLevel), rewind.go is identity. -/
theorem rewind_go_all_valid (revEntries acc : List Imp.Entry)
(scope : Nat) (gens : Imp.GenList)
(hRS : e revEntries, e.resultScope scope)
(hSL : e revEntries, e.scopeLevel scope)
(hRSSL : e revEntries, e.resultScope e.scopeLevel)
(hValid : e revEntries,
Imp.findValidLevel e.scopeGens (gens.drop (scope - e.scopeLevel))
e.scopeLevel e.resultScope = some (e.scopeLevel, e.scopeGens)) :
Imp.rewind.go scope gens revEntries acc = revEntries.reverse ++ acc := by
induction revEntries generalizing acc with
| nil => simp [Imp.rewind.go]
| cons top rest ih =>
simp only [Imp.rewind.go]
have hTopRS := hRS top (List.mem_cons_self ..)
have hTopSL := hSL top (List.mem_cons_self ..)
have hTopRSSL := hRSSL top (List.mem_cons_self ..)
have hTopValid := hValid top (List.mem_cons_self ..)
simp only [show ¬(top.resultScope > scope) from Nat.not_lt.mpr hTopRS, reduceIte]
simp only [show min top.scopeLevel scope = top.scopeLevel from Nat.min_eq_left hTopSL,
Nat.sub_self, List.drop_zero] at hTopValid
rw [hTopValid]
simp [List.reverse_cons]
/-! ### Simulation invariant -/
/-- The simulation invariant relates Spec and Imp states. -/
structure SimInv (spec : Spec.State) (imp : Imp.State) : Prop where
scopeEq : spec.scopeN = imp.scopeN
entriesEq : spec.entries =
(Imp.rewind imp.entries imp.scopeN imp.currentGens).map Imp.Entry.toSpec
gensLen : imp.currentGens.length = imp.scopeN + 1
-- All generation values in entries and currentGens are ≤ genCounter
genBound : e imp.entries, k < e.scopeGens.length, e.scopeGens[k]! imp.genCounter
curGenBound : k < imp.currentGens.length, imp.currentGens[k]! imp.genCounter
-- Entries have resultScope ≤ scopeLevel (from insert semantics)
rsLeSl : e imp.entries, e.resultScope e.scopeLevel
-- Entries are well-ordered: each entry's scopeLevel < next entry's resultScope
entryOrdered : Imp.EntryOrdered imp.entries
-- Current gens are all distinct (each push creates a unique gen value)
gensNodup : imp.currentGens.Nodup
-- Gen values act as unique scope identifiers: matching gens imply matching suffixes
gensSuffix : e imp.entries, i < e.scopeGens.length,
j < imp.currentGens.length, e.scopeGens[i]! = imp.currentGens[j]!
e.scopeGens.drop i = imp.currentGens.drop j
/-- SimInv holds for empty states. -/
theorem simInv_empty : SimInv Spec.empty Imp.empty where
scopeEq := by simp [Spec.empty, Imp.empty]
entriesEq := by simp [Spec.empty, Imp.empty, Imp.rewind, Imp.rewind.go]
gensLen := by simp [Imp.empty]
genBound := by simp [Imp.empty]
curGenBound := by simp [Imp.empty]
rsLeSl := by simp [Imp.empty]
entryOrdered := List.Pairwise.nil
gensNodup := by simp [Imp.empty, List.Nodup]
gensSuffix := by simp [Imp.empty]
/-! ### Observational equivalence from SimInv -/
/-- Lookup equivalence given SimInv. -/
theorem lookup_equiv (h : SimInv spec imp) :
Spec.lookup spec = Imp.lookup imp := by
simp only [Spec.lookup, Imp.lookup]
rw [h.entriesEq, h.scopeEq]
cases hrw : (Imp.rewind imp.entries imp.scopeN imp.currentGens).getLast? with
| none => simp [List.getLast?_map, hrw]
| some e => simp [List.getLast?_map, hrw, Imp.Entry.toSpec]
/-- Insert-sharing equivalence given SimInv. -/
theorem insert_sharing_equiv (h : SimInv spec imp) :
(Spec.insert spec v rs).2 = (Imp.insert imp v rs).2 := by
simp only [Spec.insert, Imp.insert]
rw [h.entriesEq]
cases hrw : (Imp.rewind imp.entries imp.scopeN imp.currentGens).getLast? with
| none => simp [List.getLast?_map, hrw]
| some e => simp [List.getLast?_map, hrw, Imp.Entry.toSpec]
/-! ### SimInv preservation -/
/-- Push preserves SimInv. -/
theorem push_simInv (h : SimInv spec imp) :
SimInv (Spec.push spec) (Imp.push imp) where
scopeEq := by simp [Spec.push, Imp.push, h.scopeEq]
entriesEq := by
simp only [Spec.push, Imp.push]
have hFresh : e imp.entries,
k < e.scopeGens.length, e.scopeGens[k]! < imp.genCounter + 1 := by
intro e he k hk
exact Nat.lt_succ_of_le (h.genBound e he k hk)
rw [rewind_push_equiv imp.entries imp.scopeN
(imp.genCounter + 1) imp.currentGens hFresh h.rsLeSl]
exact h.entriesEq
gensLen := by simp [Imp.push, h.gensLen]
genBound := by
intro e he k hk
simp only [Imp.push] at he
exact Nat.le_succ_of_le (h.genBound e he k hk)
curGenBound := by
intro k hk
simp only [Imp.push] at hk
cases k with
| zero => simp
| succ k =>
simp only [List.getElem!_cons_succ]
exact Nat.le_succ_of_le (h.curGenBound k (by
simp only [List.length_cons] at hk; exact Nat.lt_of_succ_lt_succ hk))
rsLeSl := by
intro e he; simp only [Imp.push] at he; exact h.rsLeSl e he
entryOrdered := by simp only [Imp.push]; exact h.entryOrdered
gensNodup := by
simp only [Imp.push, List.nodup_cons]
refine ?_, h.gensNodup
intro hmem
have k, hk, heq := List.mem_iff_getElem.mp hmem
have hle := h.curGenBound k hk
simp only [List.getElem!_eq_getElem?_getD, List.getElem?_eq_getElem hk,
Option.getD_some] at hle
-- hle : currentGens[k] ≤ genCounter, heq : currentGens[k] = genCounter + 1
grind
gensSuffix := by
intro e he i hi j hj heq
simp only [Imp.push] at he hj heq
cases j with
| zero =>
-- gens[0] is the fresh gen (genCounter + 1), can't match any entry gen
simp only [List.getElem!_cons_zero] at heq
exact absurd heq.symm (Nat.ne_of_gt (Nat.lt_succ_of_le (h.genBound e he i hi)))
| succ j =>
simp only [List.getElem!_cons_succ] at heq
exact h.gensSuffix e he i hi j (by
simp only [List.length_cons] at hj; exact Nat.lt_of_succ_lt_succ hj) heq
/-- Key lemma: an entry with scopeGens aligned to gens (via suffix property) is
trivially valid at its scopeLevel. -/
theorem fvl_self_match (e : Imp.Entry) (gens : Imp.GenList) (scope : Nat)
(_hSL : e.scopeLevel scope)
(hSuffix : e.scopeGens = gens.drop (scope - e.scopeLevel))
(hLen : e.scopeGens.length > 0) :
Imp.findValidLevel e.scopeGens (gens.drop (scope - e.scopeLevel))
e.scopeLevel e.resultScope = some (e.scopeLevel, e.scopeGens) := by
obtain g, gs, hgs := List.exists_cons_of_length_pos hLen
have hgs' : gens.drop (scope - e.scopeLevel) = g :: gs := hSuffix hgs
rw [hgs, hgs', Imp.findValidLevel]
simp
/-- For entries with sl ≤ n-1, the fvl calls at scope n and scope n-1 are identical
because gens.drop(n - sl) = gens.tail.drop(n-1 - sl). -/
theorem fvl_drop_shift (gens : Imp.GenList) (n sl : Nat) (hSL : sl n - 1) (hn : n > 0) :
gens.drop (n - sl) = gens.tail.drop (n - 1 - sl) := by
have h1 : n - sl = (n - 1 - sl) + 1 := by grind
rw [h1, List.drop_tail]
/-- When fvl succeeds at scope n with level = n (first gens match),
and dg = gens (from gensSuffix), fvl at scope n-1 matches at n-1. -/
theorem fvl_self_tail (gens : Imp.GenList) (n rs : Nat) (hn : n > 0)
(_hRS : rs n - 1) (hGensLen : gens.length = n + 1) :
Imp.findValidLevel gens.tail gens.tail (n - 1) rs = some (n - 1, gens.tail) := by
have hTailLen : gens.tail.length = n := by simp [List.length_tail, hGensLen]
obtain g, gs, hgs := List.exists_cons_of_length_pos (by rw [hTailLen]; exact hn : gens.tail.length > 0)
rw [hgs, Imp.findValidLevel]; simp
/-- When fvl at scope n succeeds for an entry, fvl at scope n-1 produces
the degraded version. Takes fvl success as a premise. -/
theorem fvl_pop_entry (e : Imp.Entry) (n : Nat) (gens : Imp.GenList)
(hn : n > 0) (hRS : e.resultScope n - 1)
(hGensSuffix : i < e.scopeGens.length, j < gens.length,
e.scopeGens[i]! = gens[j]! e.scopeGens.drop i = gens.drop j)
(hGensLen : gens.length = n + 1)
(lvl : Nat) (eGens : Imp.GenList)
(hfvl : Imp.findValidLevel (e.scopeGens.drop (e.scopeLevel - min e.scopeLevel n))
(gens.drop (n - min e.scopeLevel n)) (min e.scopeLevel n) e.resultScope =
some (lvl, eGens)) :
Imp.findValidLevel (e.scopeGens.drop (e.scopeLevel - min e.scopeLevel (n - 1)))
(gens.tail.drop ((n - 1) - min e.scopeLevel (n - 1)))
(min e.scopeLevel (n - 1)) e.resultScope =
some (min lvl (n - 1),
if lvl n - 1 then eGens
else eGens.tail) := by
by_cases hSL : e.scopeLevel n - 1
· -- Case: sl ≤ n-1. Both fvl calls are identical.
have hSLn : e.scopeLevel n := Nat.le_trans hSL (Nat.pred_le n)
rw [Nat.min_eq_left hSLn, Nat.sub_self, List.drop_zero] at hfvl
rw [Nat.min_eq_left hSL, Nat.sub_self, List.drop_zero,
fvl_drop_shift gens n e.scopeLevel hSL hn]
have hlvl_le : lvl n - 1 := Nat.le_trans (findValidLevel_lvl_le hfvl) hSL
simp [Nat.min_eq_left hlvl_le, hlvl_le, hfvl]
· -- Case: sl ≥ n. Unfold one step of fvl at scope n.
have hSLge : e.scopeLevel n := by omega
rw [Nat.min_eq_right hSLge, show n - n = 0 from Nat.sub_self n,
List.drop_zero] at hfvl
rw [Nat.min_eq_right (by omega : e.scopeLevel n - 1),
show (n - 1) - (n - 1) = 0 from Nat.sub_self (n - 1), List.drop_zero]
-- Relate the two degraded gens lists
have hdrop_shift : e.scopeGens.drop (e.scopeLevel - (n - 1)) =
(e.scopeGens.drop (e.scopeLevel - n)).tail := by
rw [List.tail_drop]; congr 1; omega
rw [hdrop_shift]
-- dg := e.scopeGens.drop(sl - n) must be non-empty (fvl succeeded on it)
obtain v, vs, hvvs : v vs, e.scopeGens.drop (e.scopeLevel - n) = v :: vs := by
cases hdg : e.scopeGens.drop (e.scopeLevel - n) with
| nil => simp [hdg, Imp.findValidLevel] at hfvl
| cons v vs => exact v, vs, rfl
-- gens must be non-empty
obtain w, ws, hwws : w ws, gens = w :: ws := by
cases gens with
| nil => simp at hGensLen
| cons w ws => exact w, ws, rfl
rw [hvvs, hwws] at hfvl
simp only [List.tail_cons]
-- Unfold fvl at scope n: fvl(v :: vs, w :: ws, n, rs)
rw [Imp.findValidLevel] at hfvl
simp only [List.head?_cons] at hfvl
split at hfvl
· -- Sub-case: v == w (match at level n)
rename_i heq
simp at hfvl
obtain hlvl_eq, hout_eq := hfvl
subst hlvl_eq; subst hout_eq
-- lvl = n, eGens = v :: vs
simp only [show ¬(n n - 1) from by omega, reduceIte,
show min n (n - 1) = n - 1 from Nat.min_eq_right (Nat.pred_le n)]
-- By gensSuffix: e.scopeGens.drop(sl-n) = gens, so v :: vs = w :: ws
have hveq : v = w := by simpa using heq
have hi : e.scopeLevel - n < e.scopeGens.length := by
have : (e.scopeGens.drop (e.scopeLevel - n)).length > 0 := by rw [hvvs]; simp
simp [List.length_drop] at this; omega
have hval : e.scopeGens[e.scopeLevel - n]! = gens[0]! := by
have h1 : (e.scopeGens.drop (e.scopeLevel - n))[0]! = v := by
rw [hvvs]; simp
rw [show (e.scopeGens.drop (e.scopeLevel - n))[0]! = e.scopeGens[e.scopeLevel - n]! from by
simp [List.getElem!_eq_getElem?_getD, List.getElem?_drop]] at h1
have h2 : gens[0]! = w := by rw [hwws]; simp
rw [h1, h2, hveq]
have hdg_gens : e.scopeGens.drop (e.scopeLevel - n) = gens :=
hGensSuffix (e.scopeLevel - n) hi 0 (by rw [hGensLen]; omega) hval
-- Goal: fvl vs ws (n-1) rs = some(n-1, (v :: vs).tail)
-- Since drop(sl-n) = gens, and drop(sl-n) = v :: vs, and gens = w :: ws:
-- v :: vs = w :: ws, so vs = ws and v = w
-- Also (v :: vs).tail = vs = ws = gens.tail
have hvs_ws : vs = ws := by
have h := hdg_gens; rw [hvvs, hwws] at h; exact List.cons.inj h |>.2
simp only [List.tail_cons, hvs_ws]
-- Goal: fvl ws ws (n-1) rs = some(n-1, ws)
have hws_eq : ws = gens.tail := by rw [hwws]; rfl
rw [hws_eq]
exact fvl_self_tail gens n e.resultScope hn hRS hGensLen
· -- Sub-case: v ≠ w
rename_i hne
split at hfvl
· -- n ≤ rs: impossible since rs ≤ n-1
omega
· -- n > rs: fvl recurses to fvl(vs, ws, n-1, rs) = some(lvl, eGens)
-- This IS the scope n-1 call, and lvl ≤ n-1
simp only [List.tail_cons] at hfvl
have hlvl_le : lvl n - 1 := findValidLevel_lvl_le hfvl
simp [Nat.min_eq_left hlvl_le, hlvl_le, hfvl]
/-- If fvl at scope n returns none, fvl at scope n-1 also returns none. -/
theorem fvl_pop_none (e : Imp.Entry) (n : Nat) (gens : Imp.GenList)
(hn : n > 0) (hRS : e.resultScope n - 1)
(hfvl : Imp.findValidLevel (e.scopeGens.drop (e.scopeLevel - min e.scopeLevel n))
(gens.drop (n - min e.scopeLevel n)) (min e.scopeLevel n) e.resultScope = none) :
Imp.findValidLevel (e.scopeGens.drop (e.scopeLevel - min e.scopeLevel (n - 1)))
(gens.tail.drop ((n - 1) - min e.scopeLevel (n - 1)))
(min e.scopeLevel (n - 1)) e.resultScope = none := by
by_cases hSL : e.scopeLevel n - 1
· -- sl ≤ n-1: both calls are identical
have hSLn : e.scopeLevel n := Nat.le_trans hSL (Nat.pred_le n)
rw [Nat.min_eq_left hSLn, Nat.sub_self, List.drop_zero] at hfvl
rw [Nat.min_eq_left hSL, Nat.sub_self, List.drop_zero,
fvl_drop_shift gens n e.scopeLevel hSL hn]
exact hfvl
· -- sl ≥ n: unfold one step
have hSLge : e.scopeLevel n := by omega
rw [Nat.min_eq_right hSLge, show n - n = 0 from Nat.sub_self n,
List.drop_zero] at hfvl
rw [Nat.min_eq_right (by omega : e.scopeLevel n - 1),
show (n - 1) - (n - 1) = 0 from Nat.sub_self (n - 1), List.drop_zero]
have hdrop_shift : e.scopeGens.drop (e.scopeLevel - (n - 1)) =
(e.scopeGens.drop (e.scopeLevel - n)).tail := by
rw [List.tail_drop]; congr 1; omega
rw [hdrop_shift]
cases hdg : e.scopeGens.drop (e.scopeLevel - n) with
| nil => simp [Imp.findValidLevel]
| cons v vs =>
simp only [List.tail_cons]
-- Goal: fvl(vs, gens.tail, n-1, rs) = none
-- hfvl: fvl(dg, gens, n, rs) = none where dg = v :: vs
rw [hdg] at hfvl
cases hg : gens with
| nil =>
-- fvl with empty cGens always returns none
simp only [List.tail_nil]
cases vs with
| nil => rw [Imp.findValidLevel]; rfl
| cons a as => rw [Imp.findValidLevel]; simp
| cons w ws =>
simp only [List.tail_cons]
rw [hg] at hfvl; rw [Imp.findValidLevel] at hfvl
simp only [List.head?_cons] at hfvl
split at hfvl
· -- v == w: fvl returns some, contradicts hfvl = none
exact absurd hfvl (by simp)
· split at hfvl
· -- n ≤ rs: impossible since rs ≤ n-1 and n > 0
omega
· -- Recursive: fvl(vs, ws, n-1, rs) = none
simp only [List.tail_cons] at hfvl
exact hfvl
/-- When all entries have rs ≤ k and sl ≤ k, filter+degrade is the identity. -/
private theorem filter_degrade_spec_id (l : List Spec.Entry) (k : Nat)
(hRS : e l, e.resultScope k)
(hSL : e l, e.scopeLevel k) :
List.map (fun e => if e.scopeLevel > k then
{ result := e.result, scopeLevel := k, resultScope := e.resultScope } else e)
(l.filter (fun e => decide (e.resultScope k))) = l := by
induction l with
| nil => simp
| cons hd tl ih =>
simp only [List.filter_cons,
show decide (hd.resultScope k) = true from decide_eq_true_eq.mpr (hRS hd (List.mem_cons_self ..)),
reduceIte, List.map_cons,
show ¬(hd.scopeLevel > k) from Nat.not_lt.mpr (hSL hd (List.mem_cons_self ..)),
reduceIte]
exact congrArg (hd :: ·) (ih (fun e he => hRS e (List.mem_cons_of_mem _ he))
(fun e he => hSL e (List.mem_cons_of_mem _ he)))
/-- Entries' gen lists are mutually consistent: earlier entries' gens are drop-suffixes
of later entries' gens. This is preserved trivially by push/pop (which don't touch entries)
and established by insert (where all surviving entries are aligned with currentGens). -/
private def GensConsistent (entries : List Imp.Entry) : Prop :=
entries.Pairwise (fun a b => a.scopeGens = b.scopeGens.drop (b.scopeLevel - a.scopeLevel))
/-- Pop rewind equivalence: rewind at (n-1, gens.tail) relates to
filter+degrade of rewind at (n, gens). -/
theorem rewind_pop_equiv (entries : List Imp.Entry) (n : Nat) (gens : Imp.GenList)
(hn : n > 0)
(hGensLen : gens.length = n + 1)
(hRSSL : e entries, e.resultScope e.scopeLevel)
(hOrd : Imp.EntryOrdered entries)
(hGensSuffix : e entries, i < e.scopeGens.length,
j < gens.length, e.scopeGens[i]! = gens[j]!
e.scopeGens.drop i = gens.drop j)
(hGC : GensConsistent entries) :
(Imp.rewind entries (n - 1) gens.tail).map Imp.Entry.toSpec =
List.map
(fun e => if e.scopeLevel > n - 1 then
{ e with scopeLevel := n - 1 : Spec.Entry } else e)
((Imp.rewind entries n gens).map Imp.Entry.toSpec |>.filter
fun e => decide (e.resultScope n - 1)) := by
simp only [Imp.rewind]
-- GensConsistent is a Pairwise property on entries. In the suffices, we track
-- GensConsistent for revEntries.reverse (sub-list of entries) plus EntryOrdered.
-- In the early-stop case, we derive alignment for rest entries from GensConsistent
-- (inter-entry gens relationship) + gensSuffix (entry-to-currentGens relationship).
suffices revEntries acc1 acc2,
( e revEntries, e entries)
( e revEntries, e.resultScope e.scopeLevel)
(revEntries.reverse ++ acc1).Pairwise (fun a b => a.scopeLevel < b.resultScope)
( e revEntries, i < e.scopeGens.length,
j < gens.length, e.scopeGens[i]! = gens[j]!
e.scopeGens.drop i = gens.drop j)
revEntries.reverse.Pairwise
(fun a b => a.scopeGens = b.scopeGens.drop (b.scopeLevel - a.scopeLevel))
acc2.map Imp.Entry.toSpec =
List.map (fun e => if e.scopeLevel > n - 1 then
{ e with scopeLevel := n - 1 : Spec.Entry } else e)
(acc1.map Imp.Entry.toSpec |>.filter fun e => decide (e.resultScope n - 1))
(Imp.rewind.go (n - 1) gens.tail revEntries acc2).map Imp.Entry.toSpec =
List.map (fun e => if e.scopeLevel > n - 1 then
{ e with scopeLevel := n - 1 : Spec.Entry } else e)
((Imp.rewind.go n gens revEntries acc1).map Imp.Entry.toSpec |>.filter
fun e => decide (e.resultScope n - 1)) by
exact this entries.reverse [] []
(fun e he => List.mem_reverse.mp he)
(fun e he => hRSSL e (List.mem_reverse.mp he))
(by simpa using hOrd)
(fun e he => hGensSuffix e (List.mem_reverse.mp he))
(by simpa using hGC)
(by simp)
intro revEntries acc1 acc2 hMem hRSSLrev hOrdRev hGSrev hGCrev hAccEq
induction revEntries generalizing acc1 acc2 with
| nil => simp [Imp.rewind.go]; exact hAccEq
| cons top rest ih =>
have hTopMem := hMem top (List.mem_cons_self ..)
have hTopRSSL := hRSSLrev top (List.mem_cons_self ..)
have hRestMem := fun e he => hMem e (List.mem_cons_of_mem _ he)
have hRestRSSL := fun e he => hRSSLrev e (List.mem_cons_of_mem _ he)
have hRestGS := fun e he => hGSrev e (List.mem_cons_of_mem _ he)
have hTopGS := hGSrev top (List.mem_cons_self ..)
-- GensConsistent for rest.reverse: sub-list of (top :: rest).reverse = rest.reverse ++ [top]
rw [List.reverse_cons] at hGCrev
have hRestGC := (List.pairwise_append.mp hGCrev).1
-- Cross from GensConsistent: ∀ e ∈ rest, e.gens = top.gens.drop(top.sl - e.sl)
have hGCcross := (List.pairwise_append.mp hGCrev).2.2
-- Extract ordering
rw [List.reverse_cons, List.append_assoc, List.singleton_append] at hOrdRev
have hP := List.pairwise_append.mp hOrdRev
have hOrdPre := hP.1
have hOrdTA := hP.2.1
have hCross := hP.2.2
have hTopAcc := List.pairwise_cons.mp hOrdTA
simp only [Imp.rewind.go]
-- Case 1: top.resultScope > n
by_cases hGN : top.resultScope > n
· -- Both scopes skip (rs > n implies rs > n-1)
have hGN1 : top.resultScope > n - 1 := by omega
simp only [hGN, hGN1, reduceIte]
exact ih acc1 acc2 hRestMem hRestRSSL
(by rw [List.pairwise_append]; exact hOrdPre, hTopAcc.2,
fun a ha b hb => hCross a ha b (List.mem_cons_of_mem _ hb))
hRestGS hRestGC hAccEq
· simp only [show ¬(top.resultScope > n) from hGN, reduceIte]
-- Case 2: top.resultScope = n (rs > n-1 but rs ≤ n)
by_cases hGN1 : top.resultScope > n - 1
· -- Scope n processes, scope n-1 skips
simp only [hGN1, reduceIte]
-- At scope n, fvl is called
split
· -- fvl at scope n succeeds: result has rs = n which gets filtered
rename_i lvl eGens hfvl_n
-- The entry has rs > n-1, so it gets filtered out
have hRSn : top.resultScope = n := by omega
-- rest.reverse ++ [modified_top] ++ acc1
-- After map toSpec and filter (rs ≤ n-1): modified_top has rs = n, filtered out
-- Prefix entries (rest.reverse) have scopeLevel < top.resultScope = n
-- so scopeLevel ≤ n-1, so their toSpec entries don't get capped
-- modified_top has rs = n, so it gets filtered out on RHS.
-- All entries in rest have sl < top.rs = n (by EntryOrdered/hCross),
-- and rs ≤ sl < n (by hRSSL). Use IH.
-- Key: need acc equivalence for the new accumulators.
-- New acc1' = modified_top :: acc1.
-- modified_top.toSpec.resultScope = n > n-1, so filtered out.
-- modified_top entry
have hNewAccEq : acc2.map Imp.Entry.toSpec =
List.map (fun e => if e.scopeLevel > n - 1 then
{ e with scopeLevel := n - 1 : Spec.Entry } else e)
(({ result := top.result, scopeLevel := lvl,
scopeGens := eGens, resultScope := top.resultScope } ::
acc1).map Imp.Entry.toSpec |>.filter
fun e => decide (e.resultScope n - 1)) := by
simp only [List.map_cons, List.filter_cons, Imp.Entry.toSpec, hRSn]
have : ¬(n n - 1) := by omega
simp only [this, decide_false]
exact hAccEq
-- Ordering: entries in rest have sl < top.rs = n.
-- mt.resultScope = n, and lvl ≤ n (from fvl_lvl_le). So sl_rest < n = mt.rs.
-- Ordering: entries in rest have sl < top.rs = n = mt.rs
have hOrdNew : (rest.reverse ++
{ result := top.result, scopeLevel := lvl,
scopeGens := eGens, resultScope := top.resultScope } :: acc1).Pairwise
(fun a b => a.scopeLevel < b.resultScope) := by
rw [List.pairwise_append]; refine hOrdPre, ?_, ?_
· have hlvl : lvl top.scopeLevel :=
Nat.le_trans (findValidLevel_lvl_le hfvl_n) (Nat.min_le_left ..)
exact List.pairwise_cons.mpr
fun a ha => by
exact Nat.lt_of_le_of_lt hlvl (hTopAcc.1 a ha),
hTopAcc.2
· intro a ha b hb
rcases List.mem_cons.mp hb with rfl | hb'
· simp only; exact hRSn hCross a ha top (List.mem_cons_self ..)
· exact hCross a ha b (List.mem_cons_of_mem _ hb')
-- The IH gives rewind.go(n, gens, rest, mt::acc1) on the RHS.
-- The goal has rest.reverse ++ [mt] ++ acc1 (early stop result).
-- Show they're equal via rewind_go_all_valid.
-- Derive alignment for rest entries from GensConsistent + top's fvl + gensSuffix
-- Step 1: top.gens.drop(top.sl - n) = gens
have hTopSLgeN : n top.scopeLevel := by
have := hTopRSSL; rw [hRSn] at this; exact this
have hMinEq : min top.scopeLevel n = n := Nat.min_eq_right hTopSLgeN
-- fvl at (n, n) can only match at level n
have hLvlN : lvl = n := by
have h1 := findValidLevel_lvl_le hfvl_n
have h2 := findValidLevel_rs_le
(show top.resultScope min top.scopeLevel n by
simp [hRSn, hMinEq]) hfvl_n
rw [hMinEq] at h1; rw [hRSn] at h2; omega
-- top.gens.drop(top.sl - n) = gens
-- eGens = top.gens.drop(top.sl - n) since fvl matched at level n
have hfvl_n' : Imp.findValidLevel
(top.scopeGens.drop (top.scopeLevel - n))
(gens.drop (n - n)) n top.resultScope = some (n, eGens) := by
rwa [hMinEq, hLvlN] at hfvl_n
have hEGens : eGens = top.scopeGens.drop (top.scopeLevel - n) :=
findValidLevel_match_at_level hfvl_n'
-- eGens = gens (from findValidLevel_aligned)
have hEGensGens : eGens = gens := by
have := findValidLevel_aligned hfvl_n rfl rfl hTopGS
rw [hMinEq, Nat.sub_self, hLvlN, Nat.sub_self, Nat.zero_add, List.drop_zero] at this
exact this
-- So top.gens.drop(top.sl - n) = gens
have hTopAligned : top.scopeGens.drop (top.scopeLevel - n) = gens := by
rw [ hEGensGens, hEGens]
-- Step 2: derive alignment for each rest entry
have hRestValid : e rest,
Imp.findValidLevel e.scopeGens (gens.drop (n - e.scopeLevel))
e.scopeLevel e.resultScope = some (e.scopeLevel, e.scopeGens) := by
intro e he
have heSL : e.scopeLevel n := by
have := hCross e (List.mem_reverse.mpr he) top (List.mem_cons_self ..)
have := hRSSLrev e (List.mem_cons_of_mem _ he)
omega
-- From GensConsistent: e.gens = top.gens.drop(top.sl - e.sl)
have heGC := hGCcross e (List.mem_reverse.mpr he) top (List.mem_cons_self ..)
-- Derive e.gens = gens.drop(n - e.sl) using List.drop_drop
have heAligned : e.scopeGens = gens.drop (n - e.scopeLevel) := by
rw [heGC, show top.scopeLevel - e.scopeLevel =
(top.scopeLevel - n) + (n - e.scopeLevel) from by omega,
List.drop_drop, hTopAligned]
exact fvl_self_match e gens n heSL heAligned
(by rw [heAligned, List.length_drop, hGensLen]; omega)
-- Use IH + rewind_go_all_valid to close the goal
have hRestRS : e rest, e.resultScope n :=
fun e he => by
have := hCross e (List.mem_reverse.mpr he) top (List.mem_cons_self ..)
have := hRestRSSL e he; omega
have hRestSL : e rest, e.scopeLevel n :=
fun e he => by
have := hCross e (List.mem_reverse.mpr he) top (List.mem_cons_self ..)
omega
have hRGAV := rewind_go_all_valid rest
({ result := top.result, scopeLevel := lvl,
scopeGens := eGens, resultScope := top.resultScope } :: acc1)
n gens hRestRS hRestSL hRestRSSL hRestValid
have hIH := ih
({ result := top.result, scopeLevel := lvl,
scopeGens := eGens, resultScope := top.resultScope } :: acc1)
acc2 hRestMem hRestRSSL hOrdNew hRestGS hRestGC hNewAccEq
rw [hRGAV] at hIH
simp only [List.append_assoc, List.singleton_append] at hIH
exact hIH
· -- fvl at scope n fails: both skip
exact ih acc1 acc2 hRestMem hRestRSSL
(by rw [List.pairwise_append]; exact hOrdPre, hTopAcc.2,
fun a ha b hb => hCross a ha b (List.mem_cons_of_mem _ hb))
hRestGS hRestGC hAccEq
· -- Case 3: top.resultScope ≤ n-1 (both scopes process)
have hRS : top.resultScope n - 1 := by omega
simp only [show ¬(top.resultScope > n - 1) from hGN1, reduceIte]
-- `split` splits the first match (LHS = scope n-1 fvl)
split
· -- scope n-1 fvl succeeds
rename_i lvl1 eGens1 hfvl_n1
-- Now split on scope n fvl (RHS)
split
· -- scope n fvl also succeeds
rename_i lvl eGens hfvl_n
-- Relate (lvl1, eGens1) to (lvl, eGens) via fvl_pop_entry
have hRel := fvl_pop_entry top n gens hn hRS hTopGS hGensLen lvl eGens hfvl_n
rw [hRel] at hfvl_n1
simp only [Option.some.injEq, Prod.mk.injEq] at hfvl_n1
obtain hLvl1, hEGens1 := hfvl_n1
subst hLvl1; subst hEGens1
-- Simplify filter/map on empty list and singleton
simp only [List.map_append, List.map_cons, List.map_nil,
List.filter_append, List.filter_cons,
Imp.Entry.toSpec, List.filter_nil]
-- rest entries: filter+degrade is identity (sl < top.rs ≤ n-1)
have hRestRS : e rest.reverse.map Imp.Entry.toSpec,
e.resultScope n - 1 := by
intro e he
obtain e', he', rfl := List.mem_map.mp he
have := hCross e' he' top (List.mem_cons_self ..)
have := hRSSLrev e' (List.mem_cons_of_mem _ (List.mem_reverse.mp he'))
simp [Imp.Entry.toSpec]; omega
have hRestSL : e rest.reverse.map Imp.Entry.toSpec,
e.scopeLevel n - 1 := by
intro e he
obtain e', he', rfl := List.mem_map.mp he
have := hCross e' he' top (List.mem_cons_self ..)
simp [Imp.Entry.toSpec]; omega
rw [filter_degrade_spec_id _ (n - 1) hRestRS hRestSL,
show decide (top.resultScope n - 1) = true from
decide_eq_true_eq.mpr hRS]
simp only [reduceIte, List.map_cons, List.map_nil]
congr 1
· congr 1
-- [{sl = min lvl (n-1)}] = [degrade {sl = lvl}]
by_cases hlvl : lvl > n - 1
· simp [hlvl, show min lvl (n - 1) = n - 1 from Nat.min_eq_right (by omega)]
· simp [hlvl, show min lvl (n - 1) = lvl from Nat.min_eq_left (by omega)]
· -- scope n fails but scope n-1 succeeds: impossible
rename_i hfvl_n_none
exact absurd hfvl_n1 (by rw [fvl_pop_none top n gens hn hRS hfvl_n_none]; simp)
· -- scope n-1 fvl fails
rename_i hfvl_n1_none
-- Now split on scope n fvl (RHS)
split
· -- scope n succeeds but scope n-1 fails: impossible
rename_i lvl eGens hfvl_n
exfalso
rw [fvl_pop_entry top n gens hn hRS hTopGS hGensLen lvl eGens hfvl_n]
at hfvl_n1_none
exact absurd hfvl_n1_none (by simp)
· -- Both fail: both skip, use IH
exact ih acc1 acc2 hRestMem hRestRSSL
(by rw [List.pairwise_append]; exact hOrdPre, hTopAcc.2,
fun a ha b hb => hCross a ha b (List.mem_cons_of_mem _ hb))
hRestGS hRestGC hAccEq
/-- Pop preserves SimInv. -/
theorem pop_simInv (h : SimInv spec imp) (hpos : spec.scopeN > 0)
(hgc : GensConsistent imp.entries) :
SimInv (Spec.pop spec) (Imp.pop imp) where
scopeEq := by simp [Spec.pop, Imp.pop, h.scopeEq]
entriesEq := by
simp only [Spec.pop, Imp.pop]
rw [h.entriesEq, h.scopeEq]
exact (rewind_pop_equiv imp.entries imp.scopeN imp.currentGens
(h.scopeEq hpos) h.gensLen h.rsLeSl h.entryOrdered h.gensSuffix hgc).symm
gensLen := by
simp only [Imp.pop, List.length_tail, h.gensLen]
have : imp.scopeN 1 := h.scopeEq hpos
rw [Nat.succ_sub_one, Nat.sub_add_cancel this]
genBound := by
intro e he k hk
simp only [Imp.pop] at he
exact h.genBound e he k hk
curGenBound := by
intro k hk
simp only [Imp.pop] at hk
have : k + 1 < imp.currentGens.length := by
rw [List.length_tail] at hk; grind
rw [show imp.currentGens.tail[k]! = imp.currentGens[k + 1]! from by
simp [List.getElem!_eq_getElem?_getD, List.getElem?_tail]]
exact h.curGenBound (k + 1) this
rsLeSl := by
intro e he; simp only [Imp.pop] at he; exact h.rsLeSl e he
entryOrdered := by simp only [Imp.pop]; exact h.entryOrdered
gensNodup := by
simp only [Imp.pop]
exact h.gensNodup.sublist (List.tail_sublist _)
gensSuffix := by
intro e he i hi j hj heq
simp only [Imp.pop] at he hj heq
have hj1 : j + 1 < imp.currentGens.length := by
rw [List.length_tail] at hj; grind
rw [show imp.currentGens.tail[j]! = imp.currentGens[j + 1]! from by
simp [List.getElem!_eq_getElem?_getD, List.getElem?_tail]] at heq
rw [show imp.currentGens.tail.drop j = imp.currentGens.drop (j + 1) from by
rw [List.drop_tail]]
exact h.gensSuffix e he i hi (j + 1) hj1 heq
private theorem filter_map_toSpec (p : Nat Bool) (entries : List Imp.Entry) :
(entries.map Imp.Entry.toSpec).filter (fun e => p e.scopeLevel) =
(entries.filter (fun e => p e.scopeLevel)).map Imp.Entry.toSpec := by
induction entries with
| nil => simp
| cons hd tl ih =>
simp only [List.map_cons, List.filter_cons, Imp.Entry.toSpec]
split <;> simp_all [Imp.Entry.toSpec]
/-- Insert preserves SimInv. -/
theorem insert_simInv (h : SimInv spec imp) (hrs : rs spec.scopeN) :
SimInv (Spec.insert spec v rs).1 (Imp.insert imp v rs).1 where
scopeEq := by simp [Spec.insert, Imp.insert, h.scopeEq]
entriesEq := by
simp only [Spec.insert, Imp.insert]
rw [h.entriesEq, h.scopeEq]
have hGensPos : imp.currentGens.length > 0 := by
rw [h.gensLen]; exact Nat.zero_lt_succ _
rw [rewind_after_insert _ _ imp.scopeN imp.currentGens rfl rfl
(h.scopeEq hrs) hGensPos]
rw [List.map_append, List.map_cons, List.map_nil]
congr 1
· exact filter_map_toSpec (· < rs) _
· simp only [List.cons_eq_cons, and_true, Imp.Entry.toSpec]
congr 1
cases hL : (Imp.rewind imp.entries imp.scopeN imp.currentGens).getLast? with
| none => simp [List.getLast?_map, hL]
| some e => simp [List.getLast?_map, hL, Imp.Entry.toSpec]
gensLen := by simp [Imp.insert, h.gensLen]
genBound := by
intro e he k hk
simp only [Imp.insert, List.mem_append, List.mem_cons, List.mem_nil_iff, or_false] at he
rcases he with hmem | heq
· -- From filtered rewind entries — these come from imp.entries via rewind
exact rewind_genBound h.genBound (List.mem_filter.mp hmem).1 k hk
· -- The new entry has scopeGens = currentGens
subst heq
exact h.curGenBound k hk
curGenBound := by
intro k hk
simp only [Imp.insert] at hk
exact h.curGenBound k hk
rsLeSl := by
intro e he
simp only [Imp.insert, List.mem_append, List.mem_cons, List.mem_nil_iff, or_false] at he
rcases he with hmem | heq
· exact rewind_rsLeSl h.rsLeSl (List.mem_filter.mp hmem).1
· subst heq; exact h.scopeEq hrs
entryOrdered := by
simp only [Imp.insert, Imp.EntryOrdered]
apply List.pairwise_append.mpr
refine (rewind_entryOrdered h.entryOrdered).filter _, ?_, ?_
· -- [newEntry].Pairwise R — trivially true for singleton
exact List.pairwise_cons.mpr fun b hb => (List.not_mem_nil hb).elim, List.Pairwise.nil
· -- ∀ a ∈ filtered, ∀ b ∈ [newEntry], R a b
intro a ha b hb
simp only [List.mem_singleton] at hb
subst hb; simp only
exact of_decide_eq_true (List.mem_filter.mp ha).2
gensNodup := by simp only [Imp.insert]; exact h.gensNodup
gensSuffix := by
intro e he i hi j hj heq
simp only [Imp.insert, List.mem_append, List.mem_cons, List.mem_nil_iff, or_false] at he
simp only [Imp.insert] at hj heq
rcases he with hmem | heq_e
· -- Entry from filtered rewind: its gens come from some original entry
have he_rw := (List.mem_filter.mp hmem).1
obtain e', he', _, _, d, hd := rewind_mem he_rw
-- Convert to facts about e'.scopeGens
have hi' : i + d < e'.scopeGens.length := by
rw [hd] at hi; simp only [List.length_drop] at hi; grind
have heq' : e'.scopeGens[i + d]! = imp.currentGens[j]! := by
rw [hd] at heq
simp only [List.getElem!_eq_getElem?_getD, List.getElem?_drop] at heq
rw [Nat.add_comm] at heq
simp only [List.getElem!_eq_getElem?_getD]
exact heq
have hsuf := h.gensSuffix e' he' (i + d) hi' j hj heq'
-- hsuf : e'.scopeGens.drop (i + d) = imp.currentGens.drop j
-- Goal: e.scopeGens.drop i = imp.currentGens.drop j
rw [hd, List.drop_drop, Nat.add_comm]
exact hsuf
· -- The new entry: scopeGens = imp.currentGens
subst heq_e; simp only at hi heq
-- heq : currentGens[i]! = currentGens[j]!
-- Use Nodup to conclude i = j, then drop i = drop j
have heqi : imp.currentGens[i]? = imp.currentGens[j]? := by
simp only [List.getElem!_eq_getElem?_getD,
List.getElem?_eq_getElem hi, List.getElem?_eq_getElem hj,
Option.getD_some] at heq
simp only [List.getElem?_eq_getElem hi, List.getElem?_eq_getElem hj]
exact congrArg some heq
have hij := (List.getElem?_inj hi h.gensNodup).mp heqi
subst hij; rfl
/-! ### Reachable states and main theorems -/
/-- States reachable from empty via matching operations. -/
inductive Reachable : Spec.State Imp.State Prop where
| empty : Reachable Spec.empty Imp.empty
| push : Reachable s i Reachable (Spec.push s) (Imp.push i)
| pop : Reachable s i s.scopeN > 0
Reachable (Spec.pop s) (Imp.pop i)
| insert : Reachable s i rs s.scopeN
Reachable (Spec.insert s v rs).1 (Imp.insert i v rs).1
private theorem push_gensConsistent (hgc : GensConsistent imp.entries) :
GensConsistent (Imp.push imp).entries := by
simp only [Imp.push]; exact hgc
private theorem pop_gensConsistent (hgc : GensConsistent imp.entries) :
GensConsistent (Imp.pop imp).entries := by
simp only [Imp.pop]; exact hgc
/-- When all entries are aligned with a common gens list and ordered, GensConsistent holds. -/
private theorem aligned_to_gensConsistent
{entries : List Imp.Entry} {g : Imp.GenList} {N : Nat}
(hAlign : e entries, e.scopeGens = g.drop (N - e.scopeLevel))
(hOrd : entries.Pairwise (fun a b => a.scopeLevel < b.resultScope))
(hRSSL : e entries, e.resultScope e.scopeLevel)
(hSLle : e entries, e.scopeLevel N) :
GensConsistent entries := by
unfold GensConsistent
induction entries with
| nil => exact List.Pairwise.nil
| cons hd tl ih =>
apply List.pairwise_cons.mpr
constructor
· intro b hb
rw [hAlign hd (List.mem_cons_self ..), hAlign b (List.mem_cons_of_mem _ hb),
List.drop_drop]; congr 1
have := (List.pairwise_cons.mp hOrd).1 b hb
have := hRSSL b (List.mem_cons_of_mem _ hb)
have := hSLle b (List.mem_cons_of_mem _ hb)
omega
· exact ih (fun e he => hAlign e (List.mem_cons_of_mem _ he))
(List.pairwise_cons.mp hOrd).2
(fun e he => hRSSL e (List.mem_cons_of_mem _ he))
(fun e he => hSLle e (List.mem_cons_of_mem _ he))
/-- After rewind, all surviving entries have gens aligned with currentGens. -/
private theorem rewind_gens_aligned
(hGC : GensConsistent entries)
(hGS : e entries, i < e.scopeGens.length, j < gens.length,
e.scopeGens[i]! = gens[j]! e.scopeGens.drop i = gens.drop j)
(hRSSL : e entries, e.resultScope e.scopeLevel)
(hOrd : Imp.EntryOrdered entries) :
e Imp.rewind entries n gens, e.scopeGens = gens.drop (n - e.scopeLevel) := by
simp only [Imp.rewind]
suffices revEntries acc,
( e revEntries, e entries)
revEntries.reverse.Pairwise
(fun a b => a.scopeGens = b.scopeGens.drop (b.scopeLevel - a.scopeLevel))
revEntries.reverse.Pairwise (fun a b => a.scopeLevel < b.resultScope)
( e acc, e.scopeGens = gens.drop (n - e.scopeLevel))
e Imp.rewind.go n gens revEntries acc,
e.scopeGens = gens.drop (n - e.scopeLevel) by
exact this entries.reverse []
(fun e he => List.mem_reverse.mp he)
(by simpa using hGC)
(by simpa using hOrd)
(by simp)
intro revEntries acc hMem hGCrev hOrdRev hAccAligned
induction revEntries generalizing acc with
| nil => simp [Imp.rewind.go]; exact hAccAligned
| cons top rest ih =>
have hTopMem := hMem top (List.mem_cons_self ..)
have hRestMem := fun e he => hMem e (List.mem_cons_of_mem _ he)
-- Decompose Pairwise on rest.reverse ++ [top]
rw [List.reverse_cons] at hGCrev hOrdRev
have hGCparts := List.pairwise_append.mp hGCrev
have hRestGC := hGCparts.1
have hGCcross := hGCparts.2.2
have hOrdParts := List.pairwise_append.mp hOrdRev
have hRestOrd := hOrdParts.1
have hOrdCross := hOrdParts.2.2
simp only [Imp.rewind.go]
by_cases hGN : top.resultScope > n
· -- rs > n: discard
simp only [hGN, reduceIte]
exact ih acc hRestMem hRestGC hRestOrd hAccAligned
· simp only [show ¬(top.resultScope > n) from hGN, reduceIte]
split
· -- fvl succeeds: early stop, result = rest.reverse ++ [modified_top] ++ acc
rename_i lvl eGens hfvl
-- Establish key facts about the fvl result before intro
have hTopRSSL := hRSSL top hTopMem
have hRSleMin : top.resultScope min top.scopeLevel n :=
Nat.le_min.mpr hTopRSSL, Nat.not_lt.mp hGN
have hRSle := findValidLevel_rs_le hRSleMin hfvl
have hEGensDrop := findValidLevel_output_eq_input_drop hfvl
have hFVLle := findValidLevel_lvl_le hfvl
have hEGensTop : eGens = top.scopeGens.drop (top.scopeLevel - lvl) := by
rw [hEGensDrop, List.drop_drop]; congr 1; omega
have hEGensGens := findValidLevel_aligned hfvl rfl rfl (hGS top hTopMem)
have hEGensGens' : eGens = gens.drop (n - lvl) := by
rw [hEGensGens]; congr 1; omega
have hTopDrop : top.scopeGens.drop (top.scopeLevel - lvl) =
gens.drop (n - lvl) := by
rw [ hEGensGens', hEGensTop]
intro e he
-- result = (rest.reverse ++ [modified_top]) ++ acc
rcases List.mem_append.mp he with hLeft | hAcc
· rcases List.mem_append.mp hLeft with hRest | hSingle
· -- e ∈ rest.reverse: derive alignment from GensConsistent + fvl result
have heGC := hGCcross e hRest top (List.mem_singleton.mpr rfl)
have heOrd := hOrdCross e hRest top (List.mem_singleton.mpr rfl)
-- e.gens = gens.drop(n - e.sl) via List.drop_drop
rw [heGC, show top.scopeLevel - e.scopeLevel =
(top.scopeLevel - lvl) + (lvl - e.scopeLevel) from by omega,
List.drop_drop, hTopDrop, List.drop_drop]
congr 1; omega
· -- e ∈ [modified_top]: from findValidLevel_aligned
rw [List.mem_singleton.mp hSingle]
simp only [show (n - min top.scopeLevel n) + (min top.scopeLevel n - lvl) =
n - lvl from by omega] at hEGensGens
exact hEGensGens
· -- e ∈ acc: from hypothesis
exact hAccAligned e hAcc
· -- fvl fails: continue with rest
exact ih acc hRestMem hRestGC hRestOrd hAccAligned
private theorem insert_gensConsistent (h : SimInv spec imp)
(hgc : GensConsistent imp.entries) (hrs : rs spec.scopeN) :
GensConsistent (Imp.insert imp v rs).1.entries := by
have hRA := rewind_gens_aligned (n := imp.scopeN) (gens := imp.currentGens)
hgc h.gensSuffix h.rsLeSl h.entryOrdered
-- After insert, entries = filter(sl < rs, rewind) ++ [newEntry]
-- All are aligned with currentGens and GensConsistent follows from aligned_to_gensConsistent
change GensConsistent (Imp.insert imp v rs).1.entries
unfold Imp.insert
simp only
apply aligned_to_gensConsistent (N := imp.scopeN) (g := imp.currentGens)
· -- alignment: all entries have gens = currentGens.drop(scopeN - sl)
intro e he
rcases List.mem_append.mp he with hmem | hSingle
· exact hRA e (List.mem_filter.mp hmem).1
· rw [List.mem_singleton.mp hSingle]; simp [Nat.sub_self]
· -- ordering: Pairwise (fun a b => a.sl < b.rs)
apply List.pairwise_append.mpr
refine (rewind_entryOrdered h.entryOrdered).filter _, ?_, ?_
· exact List.pairwise_cons.mpr fun b hb => by simp at hb, List.Pairwise.nil
· intro a ha b hb
rw [List.mem_singleton.mp hb]
exact of_decide_eq_true (List.mem_filter.mp ha).2
· -- rsLeSl
intro e he
rcases List.mem_append.mp he with hmem | hSingle
· exact rewind_rsLeSl h.rsLeSl (List.mem_filter.mp hmem).1
· rw [List.mem_singleton.mp hSingle]; exact h.scopeEq hrs
· -- sl ≤ scopeN
intro e he
rcases List.mem_append.mp he with hmem | hSingle
· have := of_decide_eq_true (List.mem_filter.mp hmem).2
have := h.scopeEq hrs; omega
· rw [List.mem_singleton.mp hSingle]; simp
/-- All reachable states satisfy the simulation invariant and gens consistency. -/
private theorem reachable_simInv_gc : Reachable s i SimInv s i GensConsistent i.entries := by
intro h
induction h with
| empty => exact simInv_empty, List.Pairwise.nil
| push _ ih =>
exact push_simInv ih.1, push_gensConsistent ih.2
| pop _ hpos ih =>
exact pop_simInv ih.1 hpos ih.2, pop_gensConsistent ih.2
| insert _ hrs ih =>
exact insert_simInv ih.1 hrs, insert_gensConsistent ih.1 ih.2 hrs
/-- All reachable states satisfy the simulation invariant. -/
theorem reachable_simInv : Reachable s i SimInv s i :=
fun h => (reachable_simInv_gc h).1
/-- Main theorem: lookup is equivalent for all reachable states. -/
theorem reachable_lookup_equiv (h : Reachable s i) :
Spec.lookup s = Imp.lookup i :=
lookup_equiv (reachable_simInv h)
/-- Main theorem: insert sharing is equivalent for all reachable states. -/
theorem reachable_insert_equiv (h : Reachable s i) :
(Spec.insert s v rs).2 = (Imp.insert i v rs).2 :=
insert_sharing_equiv (reachable_simInv h)
end Proofs
end ScopeCache