Compare commits
17 Commits
86bc33ee26
...
fable-lean
| Author | SHA1 | Date | |
|---|---|---|---|
| 904f6375be | |||
| 8cd053a242 | |||
| 0e6976f9b4 | |||
| 10b8fa97ca | |||
| 8ed48cf444 | |||
| 37d88f070a | |||
| 6c05e401c1 | |||
| fe5098095a | |||
| 59afbdaf71 | |||
| 490c472d22 | |||
| d1a11a9b2c | |||
| 2598df690c | |||
| 47d54f5b4b | |||
| 8fa822b2e6 | |||
| d66a7d0e3e | |||
| 778e974dfb | |||
| 319fa272ac |
@@ -1,5 +1,6 @@
|
|||||||
import Spa.Analysis.Sign
|
import Spa.Analysis.Sign
|
||||||
import Spa.Analysis.Constant
|
import Spa.Analysis.Constant
|
||||||
|
import Spa.Analysis.Reaching
|
||||||
import Spa.Language.Notation
|
import Spa.Language.Notation
|
||||||
|
|
||||||
namespace Spa
|
namespace Spa
|
||||||
@@ -26,10 +27,11 @@ def testCodeCond₂ : Stmt := [obj_stmt|
|
|||||||
if var { x := 1 } else { noop }
|
if var { x := 1 } else { noop }
|
||||||
]
|
]
|
||||||
|
|
||||||
def testProgram : Program := ⟨testCode⟩
|
def testProgram : Program := { rootStmt := testCode }
|
||||||
|
|
||||||
end Spa
|
end Spa
|
||||||
|
|
||||||
def main : IO Unit :=
|
def main : IO Unit :=
|
||||||
IO.println (Spa.ConstAnalysis.output Spa.testProgram ++ "\n" ++
|
IO.println (Spa.ConstAnalysis.output Spa.testProgram ++ "\n" ++
|
||||||
Spa.SignAnalysis.output Spa.testProgram)
|
Spa.SignAnalysis.output Spa.testProgram ++ "\n" ++
|
||||||
|
Spa.ReachingAnalysis.output Spa.testProgram)
|
||||||
|
|||||||
@@ -134,9 +134,16 @@ instance eval_valid : ValidExprEvaluator ConstLattice prog := by
|
|||||||
exact minus_valid h₁ h₂
|
exact minus_valid h₁ h₂
|
||||||
|
|
||||||
theorem analyze_correct {ρ : Env} (hrun : EvalStmt [] prog.rootStmt ρ) :
|
theorem analyze_correct {ρ : Env} (hrun : EvalStmt [] prog.rootStmt ρ) :
|
||||||
⟦ variablesAt prog.finalState (result ConstLattice prog) ⟧ ρ () :=
|
⟦ variablesAt prog.finalState (result ConstLattice prog) ⟧ ρ :=
|
||||||
Forward.analyze_correct ConstLattice prog hrun
|
Forward.analyze_correct ConstLattice prog hrun
|
||||||
|
|
||||||
|
theorem analyze_correct_at {ρf : Env} (hrun : EvalStmt [] prog.rootStmt ρf)
|
||||||
|
{s : prog.State} {ρin ρout : Env}
|
||||||
|
(hr : Reaches (prog.trace hrun) s ρin ρout) :
|
||||||
|
⟦ joinForKey s (result ConstLattice prog) ⟧ ρin
|
||||||
|
∧ ⟦ variablesAt s (result ConstLattice prog) ⟧ ρout :=
|
||||||
|
Forward.analyze_correct_at ConstLattice prog hrun hr
|
||||||
|
|
||||||
end ConstAnalysis
|
end ConstAnalysis
|
||||||
|
|
||||||
end Spa
|
end Spa
|
||||||
|
|||||||
@@ -9,22 +9,12 @@ namespace Forward
|
|||||||
|
|
||||||
variable {L : Type} [FiniteHeightLattice L] {prog : Program} [E : StmtEvaluator L prog]
|
variable {L : Type} [FiniteHeightLattice L] {prog : Program} [E : StmtEvaluator L prog]
|
||||||
|
|
||||||
def evalStmtOrNone (s : prog.State) (o : Option BasicStmt) (hco : prog.code s = o)
|
|
||||||
(vs : VariableValues L prog) : VariableValues L prog :=
|
|
||||||
o.elimEq vs (fun bs h => E.eval s bs (hco.trans h))
|
|
||||||
|
|
||||||
lemma evalStmtOrNone_mono (s : prog.State) (o : Option BasicStmt)
|
|
||||||
(hco : prog.code s = o) : Monotone (evalStmtOrNone (L := L) s o hco) :=
|
|
||||||
elimEq_self_mono o (fun bs h vs => E.eval s bs (hco.trans h) vs)
|
|
||||||
(fun bs h => E.eval_mono s bs (hco.trans h))
|
|
||||||
|
|
||||||
def updateVariablesForState (s : prog.State) (sv : StateVariables L prog) :
|
def updateVariablesForState (s : prog.State) (sv : StateVariables L prog) :
|
||||||
VariableValues L prog :=
|
VariableValues L prog := E.eval s (variablesAt s sv)
|
||||||
evalStmtOrNone s (prog.code s) rfl (variablesAt s sv)
|
|
||||||
|
|
||||||
lemma updateVariablesForState_mono (s : prog.State) :
|
lemma updateVariablesForState_mono (s : prog.State) :
|
||||||
Monotone (updateVariablesForState (L := L) s) := fun _ _ hle =>
|
Monotone (updateVariablesForState (L := L) s) := fun _ _ hle =>
|
||||||
evalStmtOrNone_mono s (prog.code s) rfl (variablesAt_le hle s)
|
E.eval_mono s (variablesAt_le hle s)
|
||||||
|
|
||||||
def updateAll (sv : StateVariables L prog) : StateVariables L prog :=
|
def updateAll (sv : StateVariables L prog) : StateVariables L prog :=
|
||||||
FiniteMap.generalizedUpdate id updateVariablesForState
|
FiniteMap.generalizedUpdate id updateVariablesForState
|
||||||
@@ -64,98 +54,120 @@ lemma joinForKey_initialState :
|
|||||||
rfl
|
rfl
|
||||||
|
|
||||||
class ValidStateEvaluator (L : Type) [FiniteHeightLattice L] (prog : Program)
|
class ValidStateEvaluator (L : Type) [FiniteHeightLattice L] (prog : Program)
|
||||||
[E : StmtEvaluator L prog] [S : StateInterp L prog] where
|
[E : StmtEvaluator L prog] [S : StateInterpretation L prog] where
|
||||||
step : (s : prog.State) → {ρ₁ ρ₂ : Env} → {bs : BasicStmt} →
|
valid : ∀ (s₁ s₂ : prog.State) {ρ₁ ρ₂ ρ₃: Env}
|
||||||
prog.code s = some bs → EvalBasicStmt ρ₁ bs ρ₂ → S.St ρ₁ → S.St ρ₂
|
{vs : VariableValues L prog},
|
||||||
valid : ∀ (s : prog.State) {ρ₁ ρ₂ : Env} {bs : BasicStmt}
|
(tr : Traceₗ prog.cfg s₁ s₂ ρ₁ ρ₂) →
|
||||||
{vs : VariableValues L prog} {st : S.St ρ₁},
|
(hbs : EvalBasicStmtOpt ρ₂ (prog.cfg.nodes s₂) ρ₃) → ⟦ vs ⟧ (S.Pre tr) →
|
||||||
(hcode : prog.code s = some bs) → (hbs : EvalBasicStmt ρ₁ bs ρ₂) → ⟦ vs ⟧ ρ₁ st →
|
⟦ E.eval s₂ vs ⟧ (S.Post (tr ++ hbs))
|
||||||
⟦ E.eval s bs hcode vs ⟧ ρ₂ (step s hcode hbs st)
|
botV_init : ⟦ botV L prog ⟧ (S.Pre (Traceₗ.single prog.cfg prog.initialState []))
|
||||||
botV_init : ⟦ botV L prog ⟧ [] S.init
|
|
||||||
|
|
||||||
instance [LatticeInterpretation L] [ValidStmtEvaluator L prog] :
|
instance [LatticeInterpretation L] [ValidStmtEvaluator L prog] :
|
||||||
ValidStateEvaluator L prog where
|
ValidStateEvaluator L prog where
|
||||||
step := by intro _ _ _ _ _ _ _; exact PUnit.unit
|
valid := by intro _ _ _ _ _ _ tr hbs hvs; exact ValidStmtEvaluator.valid hbs hvs
|
||||||
valid := by intro _ _ _ _ _ _ hcode hbs hvs; exact ValidStmtEvaluator.valid hcode hbs hvs
|
|
||||||
botV_init := by intro k l _ v hmem; cases hmem
|
botV_init := by intro k l _ v hmem; cases hmem
|
||||||
|
|
||||||
section
|
section
|
||||||
variable [S : StateInterp L prog] [V : ValidStateEvaluator L prog]
|
variable [S : StateInterpretation L prog] [V : ValidStateEvaluator L prog]
|
||||||
|
|
||||||
noncomputable def stepStmtOrNone (s : prog.State) {ρ₁ ρ₂ : Env} :
|
|
||||||
(o : Option BasicStmt) → prog.code s = o → EvalBasicStmtOpt ρ₁ o ρ₂ →
|
|
||||||
S.St ρ₁ → S.St ρ₂
|
|
||||||
| none, _, .none, st => st
|
|
||||||
| some _, hco, .some hbs, st => V.step s hco hbs st
|
|
||||||
|
|
||||||
noncomputable def stepNode (s : prog.State) {ρ₁ ρ₂ : Env}
|
|
||||||
(h : EvalBasicStmtOpt ρ₁ (prog.code s) ρ₂) (st : S.St ρ₁) : S.St ρ₂ :=
|
|
||||||
stepStmtOrNone s (prog.code s) rfl h st
|
|
||||||
|
|
||||||
noncomputable def stepTraceState :
|
|
||||||
{s₁ s₂ : prog.State} → {ρ₁ ρ₂ : Env} →
|
|
||||||
Trace prog.cfg s₁ s₂ ρ₁ ρ₂ → S.St ρ₁ → S.St ρ₂
|
|
||||||
| s₁, _, _, _, .single hnode, st => stepNode s₁ hnode st
|
|
||||||
| s₁, _, _, _, .edge hnode _ subtr, st =>
|
|
||||||
stepTraceState subtr (stepNode s₁ hnode st)
|
|
||||||
|
|
||||||
omit [DecidableEq L] in
|
omit [DecidableEq L] in
|
||||||
lemma evalStmtOrNone_valid {s : prog.State} {ρ₁ ρ₂ : Env} {st : S.St ρ₁}
|
lemma updateAll_matches {s₁ s₂ : prog.State} {sv : StateVariables L prog}
|
||||||
{vs : VariableValues L prog} (o : Option BasicStmt) (hco : prog.code s = o)
|
{ρ₁ ρ₂ ρ₃ : Env}
|
||||||
(he : EvalBasicStmtOpt ρ₁ o ρ₂) (hvs : ⟦ vs ⟧ ρ₁ st) :
|
(tr : Traceₗ prog.cfg s₁ s₂ ρ₁ ρ₂)
|
||||||
⟦ evalStmtOrNone s o hco vs ⟧ ρ₂ (stepStmtOrNone s o hco he st) := by
|
(hnode : EvalBasicStmtOpt ρ₂ (prog.code s₂) ρ₃)
|
||||||
cases he with
|
(hvs : ⟦ variablesAt s₂ sv ⟧ (S.Pre tr)) :
|
||||||
| none => exact hvs
|
⟦ variablesAt s₂ (updateAll sv) ⟧ (S.Post (tr ++ hnode)) := by
|
||||||
| some hbs => exact V.valid s hco hbs hvs
|
|
||||||
|
|
||||||
omit [DecidableEq L] in
|
|
||||||
lemma updateAll_matches {s : prog.State} {sv : StateVariables L prog}
|
|
||||||
{ρ₁ ρ₂ : Env} {st : S.St ρ₁}
|
|
||||||
(hnode : EvalBasicStmtOpt ρ₁ (prog.code s) ρ₂)
|
|
||||||
(hvs : ⟦ variablesAt s sv ⟧ ρ₁ st) :
|
|
||||||
⟦ variablesAt s (updateAll sv) ⟧ ρ₂ (stepNode s hnode st) := by
|
|
||||||
rw [variablesAt_updateAll]
|
rw [variablesAt_updateAll]
|
||||||
exact evalStmtOrNone_valid (prog.code s) rfl hnode hvs
|
exact V.valid s₁ s₂ tr hnode hvs
|
||||||
|
|
||||||
lemma stepTrace {s₁ : prog.State} {ρ₁ ρ₂ : Env} {st : S.St ρ₁}
|
lemma stepTrace {s₁ s₂ : prog.State} {ρ₁ ρ₂ : Env}
|
||||||
(hjoin : ⟦ joinForKey s₁ (result L prog) ⟧ ρ₁ st)
|
(tr : Traceₗ prog.cfg s₁ s₂ ρ₁ ρ₂)
|
||||||
(hnode : EvalBasicStmtOpt ρ₁ (prog.code s₁) ρ₂) :
|
(hjoin : ⟦ joinForKey s₂ (result L prog) ⟧ (S.Pre tr))
|
||||||
⟦ variablesAt s₁ (result L prog) ⟧ ρ₂ (stepNode s₁ hnode st) := by
|
(hnode : EvalBasicStmtOpt ρ₂ (prog.code s₂) ρ₃) :
|
||||||
|
⟦ variablesAt s₂ (result L prog) ⟧ (S.Post (tr ++ hnode)) := by
|
||||||
rw [result_eq L prog]
|
rw [result_eq L prog]
|
||||||
refine updateAll_matches hnode ?_
|
refine updateAll_matches tr hnode ?_
|
||||||
rw [variablesAt_joinAll]
|
rw [variablesAt_joinAll]
|
||||||
exact hjoin
|
exact hjoin
|
||||||
|
|
||||||
lemma walkTrace {s₁ s₂ : prog.State} {ρ₁ ρ₂ : Env} {st₁ : S.St ρ₁}
|
/-- Soundness at *every* visited node: if the analysis result over-approximates the
|
||||||
(hjoin : ⟦ joinForKey s₁ (result L prog) ⟧ ρ₁ st₁)
|
incoming environment at the start of the trace, then at each node reached along the
|
||||||
|
way it over-approximates both the environment entering that node (via `joinForKey`)
|
||||||
|
and the environment leaving it (via `variablesAt`). The intermediate `variablesAt`
|
||||||
|
evidence used to be computed and discarded inside `walkTrace`; here it is returned. -/
|
||||||
|
lemma walkTrace_reaches {s₁ s₂ s₃: prog.State} {ρ₁ ρ₂ ρ₃: Env}
|
||||||
|
{s : prog.State} {ρin ρout : Env}
|
||||||
|
{tr : Trace prog.cfg s₂ s₃ ρ₂ ρ₃}
|
||||||
|
(hr : Reaches tr s ρin ρout)
|
||||||
|
(trₗ : Traceₗ prog.cfg s₁ s₂ ρ₁ ρ₂)
|
||||||
|
(hjoin : ⟦ joinForKey s₂ (result L prog) ⟧ (S.Pre trₗ)) :
|
||||||
|
⟦ joinForKey s (result L prog) ⟧ (S.Pre (trₗ ++ hr.pre))
|
||||||
|
∧ ⟦ variablesAt s (result L prog) ⟧ (S.Post (trₗ ++ hr.post)) := by
|
||||||
|
induction hr with
|
||||||
|
| single_here hnode =>
|
||||||
|
simp [Reaches.pre, Reaches.post]
|
||||||
|
refine ⟨?_, ?_⟩ <;> try simpa [HAppend.hAppend]
|
||||||
|
exact stepTrace trₗ hjoin hnode
|
||||||
|
| edge_here hnode hedge rest =>
|
||||||
|
simp [Reaches.pre, Reaches.post]
|
||||||
|
refine ⟨?_, ?_⟩ <;> try simpa [HAppend.hAppend]
|
||||||
|
exact stepTrace trₗ hjoin hnode
|
||||||
|
| edge_there hnode hedge rest hr' ih =>
|
||||||
|
have hstep := stepTrace trₗ hjoin hnode
|
||||||
|
have hmem := FiniteMap.mem_valuesAt prog.states_nodup
|
||||||
|
(prog.mem_incoming_of_edge hedge) (variablesAt_mem _ (result L prog))
|
||||||
|
simpa [Reaches.pre, Reaches.post, HAppend.hAppend] using
|
||||||
|
ih ((trₗ ++ hnode).addEdge hedge)
|
||||||
|
(interp_foldr (S.post_pre (trₗ ++ hnode) hedge hstep) hmem)
|
||||||
|
|
||||||
|
omit [DecidableEq L] in
|
||||||
|
/-- The final node of a trace is always reached, with the environment/state the trace
|
||||||
|
ends in. Used to recover the final-state soundness theorem from `walkTrace_reaches`. -/
|
||||||
|
def reaches_final {s₁ s₂ : prog.State} {ρ₁ ρ₂ : Env}
|
||||||
(tr : Trace prog.cfg s₁ s₂ ρ₁ ρ₂) :
|
(tr : Trace prog.cfg s₁ s₂ ρ₁ ρ₂) :
|
||||||
⟦ variablesAt s₂ (result L prog) ⟧ ρ₂ (stepTraceState tr st₁) := by
|
Σ ρin, Reaches tr s₂ ρin ρ₂ :=
|
||||||
|
match tr with
|
||||||
|
| .single hnode => ⟨_, .single_here hnode⟩
|
||||||
|
| .edge hnode hedge rest =>
|
||||||
|
let ⟨ρin, r'⟩ := reaches_final rest; ⟨ρin, .edge_there hnode hedge _ r'⟩
|
||||||
|
|
||||||
|
omit [DecidableEq L] in
|
||||||
|
/-- Reaching the final node covers the whole trace. -/
|
||||||
|
@[simp] lemma reaches_final_post {s₁ s₂ : prog.State} {ρ₁ ρ₂ : Env}
|
||||||
|
(tr : Trace prog.cfg s₁ s₂ ρ₁ ρ₂) :
|
||||||
|
(reaches_final tr).2.post = tr := by
|
||||||
induction tr with
|
induction tr with
|
||||||
| single hnode => exact stepTrace hjoin hnode
|
| single hnode => rfl
|
||||||
| @edge _ ρ' _ i₁ i₂ _ hnode hedge _ ih =>
|
| edge hnode hedge rest ih => simp [reaches_final, Reaches.post, ih]
|
||||||
have hstep : ⟦ variablesAt i₁ (result L prog) ⟧ ρ' (stepNode i₁ hnode st₁) :=
|
|
||||||
stepTrace hjoin hnode
|
|
||||||
have hmem : variablesAt i₁ (result L prog)
|
|
||||||
∈ (result L prog).valuesAt (prog.incoming i₂) :=
|
|
||||||
FiniteMap.mem_valuesAt prog.states_nodup
|
|
||||||
(prog.mem_incoming_of_edge hedge) (variablesAt_mem i₁ (result L prog))
|
|
||||||
exact ih (interp_foldr hstep hmem)
|
|
||||||
|
|
||||||
variable (L prog) in
|
variable (L prog) in
|
||||||
theorem analyze_correct_state {ρ : Env} (hrun : EvalStmt [] prog.rootStmt ρ) :
|
/-- Soundness at every program point reached during execution: for any node `s` visited
|
||||||
⟦ variablesAt prog.finalState (result L prog) ⟧ ρ
|
by the run `hrun` (witnessed by `hr`), the analysis result over-approximates both the
|
||||||
(stepTraceState (prog.trace hrun) S.init) := by
|
environment entering `s` and the one leaving it. The final-state theorem
|
||||||
refine walkTrace ?_ (prog.trace hrun)
|
`analyze_correct_state` is the special case where `s` is `prog.finalState`. -/
|
||||||
|
theorem analyze_correct_at {ρf : Env} (hrun : EvalStmt [] prog.rootStmt ρf)
|
||||||
|
{s : prog.State} {ρin ρout : Env}
|
||||||
|
(hr : Reaches (prog.trace hrun) s ρin ρout) :
|
||||||
|
⟦ joinForKey s (result L prog) ⟧ (S.Pre hr.pre)
|
||||||
|
∧ ⟦ variablesAt s (result L prog) ⟧ (S.Post hr.post) := by
|
||||||
|
refine walkTrace_reaches hr (Traceₗ.single _ _ []) ?_
|
||||||
rw [joinForKey_initialState]
|
rw [joinForKey_initialState]
|
||||||
exact ValidStateEvaluator.botV_init
|
exact ValidStateEvaluator.botV_init
|
||||||
|
|
||||||
|
variable (L prog) in
|
||||||
|
theorem analyze_correct'
|
||||||
|
{ρ : Env} (hrun : EvalStmt [] prog.rootStmt ρ) :
|
||||||
|
⟦ variablesAt prog.finalState (result L prog) ⟧ (S.Post (prog.trace hrun)) := by
|
||||||
|
have h := (analyze_correct_at L prog hrun (reaches_final (prog.trace hrun)).2).2
|
||||||
|
rwa [reaches_final_post] at h
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
variable (L prog) in
|
variable (L prog) in
|
||||||
theorem analyze_correct [LatticeInterpretation L] [ValidStmtEvaluator L prog]
|
theorem analyze_correct [LatticeInterpretation L] [ValidStmtEvaluator L prog]
|
||||||
{ρ : Env} (hrun : EvalStmt [] prog.rootStmt ρ) :
|
{ρ : Env} (hrun : EvalStmt [] prog.rootStmt ρ) :
|
||||||
⟦ variablesAt prog.finalState (result L prog) ⟧ ρ () :=
|
⟦ variablesAt prog.finalState (result L prog) ⟧ ρ :=
|
||||||
analyze_correct_state L prog hrun
|
analyze_correct' L prog hrun
|
||||||
|
|
||||||
end Forward
|
end Forward
|
||||||
|
|
||||||
|
|||||||
@@ -14,44 +14,50 @@ lemma updateVariablesFromExpression_mono (k : String) (e : Expr) :
|
|||||||
Monotone (updateVariablesFromExpression (L := L) (prog := prog) k e) :=
|
Monotone (updateVariablesFromExpression (L := L) (prog := prog) k e) :=
|
||||||
FiniteMap.generalizedUpdate_monotone monotone_id (fun _ => E.eval_mono e)
|
FiniteMap.generalizedUpdate_monotone monotone_id (fun _ => E.eval_mono e)
|
||||||
|
|
||||||
def evalBasicStmt (s : prog.State) (bs : BasicStmt) (_h : prog.code s = some bs)
|
def evalBasicStmt (bs : BasicStmt)
|
||||||
(vs : VariableValues L prog) : VariableValues L prog :=
|
(vs : VariableValues L prog) : VariableValues L prog :=
|
||||||
match bs with
|
match bs with
|
||||||
| .assign k e => updateVariablesFromExpression k e vs
|
| .assign k e => updateVariablesFromExpression k e vs
|
||||||
| .noop => vs
|
| .noop => vs
|
||||||
|
|
||||||
lemma evalBasicStmt_mono (s : prog.State) (bs : BasicStmt) (h : prog.code s = some bs) :
|
lemma evalBasicStmt_mono (bs : BasicStmt) :
|
||||||
Monotone (evalBasicStmt (L := L) (prog := prog) s bs h) := by
|
Monotone (evalBasicStmt (L := L) (prog := prog) bs) := by
|
||||||
cases bs with
|
cases bs with
|
||||||
| assign k e => exact updateVariablesFromExpression_mono k e
|
| assign k e => exact updateVariablesFromExpression_mono k e
|
||||||
| noop => exact monotone_id
|
| noop => exact monotone_id
|
||||||
|
|
||||||
|
def evalBasicStmtOpt (obs : Option BasicStmt)
|
||||||
|
(vs : VariableValues L prog) : VariableValues L prog :=
|
||||||
|
match obs with
|
||||||
|
| none => vs
|
||||||
|
| some bs => evalBasicStmt bs vs
|
||||||
|
|
||||||
|
lemma evalBasicStmtOpt_mono (obs : Option BasicStmt) :
|
||||||
|
Monotone (evalBasicStmtOpt (L := L) (prog := prog) obs) := by
|
||||||
|
cases obs <;> unfold evalBasicStmtOpt
|
||||||
|
· exact monotone_id
|
||||||
|
· apply evalBasicStmt_mono
|
||||||
|
|
||||||
instance ExprEvaluator.toStmtEvaluator : StmtEvaluator L prog :=
|
instance ExprEvaluator.toStmtEvaluator : StmtEvaluator L prog :=
|
||||||
⟨evalBasicStmt, evalBasicStmt_mono⟩
|
⟨evalBasicStmtOpt ∘ prog.code,
|
||||||
|
by intro s; simp; exact (evalBasicStmtOpt_mono (prog.code s))⟩
|
||||||
|
|
||||||
instance ExprEvaluator.toStmtEvaluator_valid [LatticeInterpretation L]
|
instance ExprEvaluator.toStmtEvaluator_valid [LatticeInterpretation L]
|
||||||
[ValidExprEvaluator L prog] : ValidStmtEvaluator L prog := by
|
[ValidExprEvaluator L prog] : ValidStmtEvaluator L prog := by
|
||||||
constructor
|
constructor
|
||||||
intro s vs ρ₁ ρ₂ bs hcode hbs hvs
|
simp [StmtEvaluator.eval, evalBasicStmtOpt]
|
||||||
cases hbs with
|
intro s vs ρ₁ ρ₂; generalize prog.code s = obs; intro hev hvs
|
||||||
| noop => exact hvs
|
rcases hev with _ | @⟨_,bs,hev⟩ <;> try simpa
|
||||||
| assign k e v hev =>
|
rcases hev with _ | @⟨k, e, v, hev⟩ <;> try simpa
|
||||||
intro k' l hk'l v' hv'
|
intros k' l' hkl' v' hρ
|
||||||
cases hv' with
|
rcases hρ with _ | ⟨_,_,_,_,_,hne,hmem⟩ <;> simp [evalBasicStmt] at hkl'
|
||||||
| here =>
|
· have hl := FiniteMap.generalizedUpdate_mem_eq (f := id)
|
||||||
have hk'l₀ : (k, l) ∈ FiniteMap.generalizedUpdate (ks := prog.vars) id
|
(g := fun _ vs => E.eval e vs) (List.mem_singleton_self k) hkl'
|
||||||
(fun _ vs => E.eval e vs) [k] vs := hk'l
|
rewrite [hl]; simp
|
||||||
have hl := FiniteMap.generalizedUpdate_mem_eq (f := id)
|
exact ValidExprEvaluator.valid hev hvs
|
||||||
(g := fun _ vs => E.eval e vs) (List.mem_singleton_self k) hk'l₀
|
· have hl := FiniteMap.generalizedUpdate_not_mem_backward
|
||||||
rw [hl]
|
(fun hmem => hne (List.mem_singleton.mp hmem)) hkl'
|
||||||
exact ValidExprEvaluator.valid hev hvs
|
apply hvs _ _ hl _ hmem
|
||||||
| there _ _ _ _ _ hne hmem' =>
|
|
||||||
have hk'l₀ : (k', l) ∈ FiniteMap.generalizedUpdate (ks := prog.vars) id
|
|
||||||
(fun _ vs => E.eval e vs) [k] vs := hk'l
|
|
||||||
have hk'l' : (k', l) ∈ (id vs : VariableValues L prog) :=
|
|
||||||
FiniteMap.generalizedUpdate_not_mem_backward
|
|
||||||
(fun hmem => hne (List.mem_singleton.mp hmem)) hk'l₀
|
|
||||||
exact hvs _ _ hk'l' _ hmem'
|
|
||||||
|
|
||||||
end Forward
|
end Forward
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,8 @@ namespace Forward
|
|||||||
variable (L : Type) [Lattice L] (prog : Program)
|
variable (L : Type) [Lattice L] (prog : Program)
|
||||||
|
|
||||||
class StmtEvaluator where
|
class StmtEvaluator where
|
||||||
eval : (s : prog.State) → (bs : BasicStmt) → prog.code s = some bs →
|
eval : prog.State → VariableValues L prog → VariableValues L prog
|
||||||
VariableValues L prog → VariableValues L prog
|
eval_mono : ∀ s, Monotone (eval s)
|
||||||
eval_mono : ∀ s bs h, Monotone (eval s bs h)
|
|
||||||
|
|
||||||
class ExprEvaluator where
|
class ExprEvaluator where
|
||||||
eval : Expr → VariableValues L prog → L
|
eval : Expr → VariableValues L prog → L
|
||||||
@@ -18,13 +17,12 @@ class ExprEvaluator where
|
|||||||
class ValidExprEvaluator [ExprEvaluator L prog] [I : LatticeInterpretation L] :
|
class ValidExprEvaluator [ExprEvaluator L prog] [I : LatticeInterpretation L] :
|
||||||
Prop where
|
Prop where
|
||||||
valid : ∀ {vs : VariableValues L prog} {ρ : Env} {e : Expr} {v : Value},
|
valid : ∀ {vs : VariableValues L prog} {ρ : Env} {e : Expr} {v : Value},
|
||||||
EvalExpr ρ e v → ⟦ vs ⟧ ρ () → I.interp (ExprEvaluator.eval e vs) v
|
EvalExpr ρ e v → ⟦ vs ⟧ ρ → I.interp (ExprEvaluator.eval e vs) v
|
||||||
|
|
||||||
class ValidStmtEvaluator [E : StmtEvaluator L prog] [LatticeInterpretation L] :
|
class ValidStmtEvaluator [E : StmtEvaluator L prog] [LatticeInterpretation L] :
|
||||||
Prop where
|
Prop where
|
||||||
valid : ∀ {s : prog.State} {vs : VariableValues L prog} {ρ₁ ρ₂ : Env}
|
valid : ∀ {s : prog.State} {vs : VariableValues L prog} {ρ₁ ρ₂ : Env},
|
||||||
{bs : BasicStmt} (hcode : prog.code s = some bs),
|
EvalBasicStmtOpt ρ₁ (prog.code s) ρ₂ → ⟦ vs ⟧ ρ₁ → ⟦ E.eval s vs ⟧ ρ₂
|
||||||
EvalBasicStmt ρ₁ bs ρ₂ → ⟦ vs ⟧ ρ₁ () → ⟦ E.eval s bs hcode vs ⟧ ρ₂ ()
|
|
||||||
|
|
||||||
end Forward
|
end Forward
|
||||||
|
|
||||||
|
|||||||
@@ -64,23 +64,29 @@ lemma variablesAt_joinAll (s : prog.State) (sv : StateVariables L prog) :
|
|||||||
variablesAt s (joinAll sv) = joinForKey s sv :=
|
variablesAt s (joinAll sv) = joinForKey s sv :=
|
||||||
joinAll_mem_eq (variablesAt_mem s (joinAll sv))
|
joinAll_mem_eq (variablesAt_mem s (joinAll sv))
|
||||||
|
|
||||||
class StateInterp (L : Type) [Lattice L] (prog : Program) where
|
class StateInterpretation (L : Type) [Lattice L] (prog : Program) where
|
||||||
St : Env → Type
|
Proj : Type
|
||||||
init : St []
|
Pre : ∀ {s₁ s₂ : prog.State} {ρ₁ ρ₂ : Env}, Traceₗ prog.cfg s₁ s₂ ρ₁ ρ₂ → Proj
|
||||||
interp : VariableValues L prog → (ρ : Env) → St ρ → Prop
|
Post : ∀ {s₁ s₂ : prog.State} {ρ₁ ρ₂ : Env}, Trace prog.cfg s₁ s₂ ρ₁ ρ₂ → Proj
|
||||||
interp_sup : ∀ {vs₁ vs₂ : VariableValues L prog} {ρ : Env} {st : St ρ},
|
|
||||||
interp vs₁ ρ st ∨ interp vs₂ ρ st → interp (vs₁ ⊔ vs₂) ρ st
|
|
||||||
interp_inf : ∀ {vs₁ vs₂ : VariableValues L prog} {ρ : Env} {st : St ρ},
|
|
||||||
interp vs₁ ρ st ∧ interp vs₂ ρ st → interp (vs₁ ⊓ vs₂) ρ st
|
|
||||||
|
|
||||||
instance [S : StateInterp L prog] :
|
interp : VariableValues L prog → (p : Proj) → Prop
|
||||||
Interp (VariableValues L prog) ((ρ : Env) → S.St ρ → Prop) :=
|
interp_sup : ∀ {vs₁ vs₂ : VariableValues L prog} {p : Proj},
|
||||||
|
interp vs₁ p ∨ interp vs₂ p → interp (vs₁ ⊔ vs₂) p
|
||||||
|
interp_inf : ∀ {vs₁ vs₂ : VariableValues L prog} {p : Proj},
|
||||||
|
interp vs₁ p ∧ interp vs₂ p → interp (vs₁ ⊓ vs₂) p
|
||||||
|
|
||||||
|
post_pre : ∀ {vs} {s₁ s₂ s₃: prog.State} {ρ₁ ρ₂ : Env}
|
||||||
|
(tr : Trace prog.cfg s₁ s₂ ρ₁ ρ₂) (hedge : (s₂, s₃) ∈ prog.cfg.edges),
|
||||||
|
interp vs (Post tr) → interp vs (Pre (tr.addEdge hedge))
|
||||||
|
|
||||||
|
instance [S : StateInterpretation L prog] :
|
||||||
|
Interp (VariableValues L prog) (S.Proj → Prop) :=
|
||||||
⟨S.interp⟩
|
⟨S.interp⟩
|
||||||
|
|
||||||
lemma interp_foldr [S : StateInterp L prog]
|
lemma interp_foldr [S : StateInterpretation L prog]
|
||||||
{vs : VariableValues L prog} {vss : List (VariableValues L prog)}
|
{vs : VariableValues L prog} {vss : List (VariableValues L prog)}
|
||||||
{ρ : Env} {st : S.St ρ} (hvs : ⟦ vs ⟧ ρ st) (hmem : vs ∈ vss) :
|
{p : S.Proj} (hvs : ⟦ vs ⟧ p) (hmem : vs ∈ vss) :
|
||||||
⟦ vss.foldr (· ⊔ ·) (botV L prog) ⟧ ρ st := by
|
⟦ vss.foldr (· ⊔ ·) (botV L prog) ⟧ p := by
|
||||||
induction vss with
|
induction vss with
|
||||||
| nil => cases hmem
|
| nil => cases hmem
|
||||||
| cons vs' vss' ih =>
|
| cons vs' vss' ih =>
|
||||||
@@ -90,21 +96,25 @@ lemma interp_foldr [S : StateInterp L prog]
|
|||||||
|
|
||||||
variable [I : LatticeInterpretation L]
|
variable [I : LatticeInterpretation L]
|
||||||
|
|
||||||
instance : StateInterp L prog where
|
instance : StateInterpretation L prog where
|
||||||
St := fun _ => PUnit
|
Proj := Env
|
||||||
init := PUnit.unit
|
Pre := fun {_ _ _ ρ₂} _ => ρ₂
|
||||||
interp vs ρ _ := ∀ (k : String) (l : L), (k, l) ∈ vs →
|
Post := fun {_ _ _ ρ₂} _ => ρ₂
|
||||||
|
|
||||||
|
interp vs ρ := ∀ (k : String) (l : L), (k, l) ∈ vs →
|
||||||
∀ (v : Value), Env.Mem (k, v) ρ → I.interp l v
|
∀ (v : Value), Env.Mem (k, v) ρ → I.interp l v
|
||||||
interp_sup := by
|
interp_sup := by
|
||||||
intro vs₁ vs₂ ρ st h k l hmem v hv
|
intro vs₁ vs₂ ρ h k l hmem v hv
|
||||||
obtain ⟨l₁, l₂, rfl, h₁, h₂⟩ := FiniteMap.mem_sup hmem
|
obtain ⟨l₁, l₂, rfl, h₁, h₂⟩ := FiniteMap.mem_sup hmem
|
||||||
rcases h with h | h
|
rcases h with h | h
|
||||||
· exact I.interp_sup v (Or.inl (h _ _ h₁ _ hv))
|
· exact I.interp_sup v (Or.inl (h _ _ h₁ _ hv))
|
||||||
· exact I.interp_sup v (Or.inr (h _ _ h₂ _ hv))
|
· exact I.interp_sup v (Or.inr (h _ _ h₂ _ hv))
|
||||||
interp_inf := by
|
interp_inf := by
|
||||||
intro vs₁ vs₂ ρ st h k l hmem v hv
|
intro vs₁ vs₂ ρ h k l hmem v hv
|
||||||
obtain ⟨l₁, l₂, rfl, h₁, h₂⟩ := FiniteMap.mem_inf hmem
|
obtain ⟨l₁, l₂, rfl, h₁, h₂⟩ := FiniteMap.mem_inf hmem
|
||||||
exact I.interp_inf v ⟨h.1 _ _ h₁ _ hv, h.2 _ _ h₂ _ hv⟩
|
exact I.interp_inf v ⟨h.1 _ _ h₁ _ hv, h.2 _ _ h₂ _ hv⟩
|
||||||
|
post_pre := by simp
|
||||||
|
|
||||||
|
|
||||||
end Forward
|
end Forward
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import Spa.Analysis.Forward
|
import Spa.Analysis.Forward
|
||||||
import Spa.Lattice.Bool
|
import Spa.Lattice.Finset
|
||||||
import Spa.Lattice.Tuple
|
|
||||||
import Spa.Language.Tagged.Graphs
|
import Spa.Language.Tagged.Graphs
|
||||||
import Spa.Showable
|
import Spa.Showable
|
||||||
|
|
||||||
@@ -8,37 +7,35 @@ namespace Spa
|
|||||||
|
|
||||||
open Forward
|
open Forward
|
||||||
|
|
||||||
instance : Showable Bool := ⟨fun b => if b then "true" else "false"⟩
|
instance {n : ℕ} : Showable (Finset (Fin n)) :=
|
||||||
|
⟨fun s =>
|
||||||
instance {n : ℕ} {β : Type*} [Showable β] : Showable (Fin n → β) :=
|
|
||||||
⟨fun f =>
|
|
||||||
"{" ++ (List.finRange n).foldr
|
"{" ++ (List.finRange n).foldr
|
||||||
(fun i rest => show' i ++ " ↦ " ++ show' (f i) ++ ", " ++ rest) ""
|
(fun i rest => if i ∈ s then show' i ++ ", " ++ rest else rest) ""
|
||||||
++ "}"⟩
|
++ "}"⟩
|
||||||
|
|
||||||
abbrev DefSet (prog : Program) : Type := prog.NodeId → Bool
|
abbrev DefSet (prog : Program) : Type := Finset prog.NodeId
|
||||||
|
|
||||||
namespace ReachingAnalysis
|
namespace ReachingAnalysis
|
||||||
|
|
||||||
variable (prog : Program)
|
variable (prog : Program)
|
||||||
|
|
||||||
def genSet (s : prog.State) {bs : BasicStmt} (h : prog.code s = some bs) :
|
def genSet (s : prog.State) : DefSet prog := (prog.nodeIdOf s).elim {} (fun x => {x})
|
||||||
DefSet prog :=
|
|
||||||
Function.update (⊥ : DefSet prog) (prog.nodeIdOfNonempty s h) true
|
|
||||||
|
|
||||||
def eval (s : prog.State) :
|
def eval (s : prog.State) (vs : VariableValues (DefSet prog) prog) : VariableValues (DefSet prog) prog :=
|
||||||
(bs : BasicStmt) → prog.code s = some bs →
|
match prog.code s with
|
||||||
VariableValues (DefSet prog) prog → VariableValues (DefSet prog) prog
|
| none => vs
|
||||||
| .assign k _, h, vs =>
|
| some bs =>
|
||||||
FiniteMap.generalizedUpdate id (fun _ _ => genSet prog s h) [k] vs
|
match bs with
|
||||||
| .noop, _, vs => vs
|
| .assign k _ => FiniteMap.generalizedUpdate id (fun _ _ => genSet prog s) [k] vs
|
||||||
|
| .noop => vs
|
||||||
|
|
||||||
lemma eval_mono (s : prog.State) (bs : BasicStmt) (h : prog.code s = some bs) :
|
lemma eval_mono (s : prog.State) :
|
||||||
Monotone (eval prog s bs h) := by
|
Monotone (eval prog s) := by
|
||||||
cases bs with
|
intros vs₁ vs₂ hle
|
||||||
| assign k e =>
|
unfold eval; split <;> try simpa
|
||||||
exact FiniteMap.generalizedUpdate_monotone monotone_id (fun _ => monotone_const)
|
split <;> try simpa
|
||||||
| noop => exact monotone_id
|
apply FiniteMap.generalizedUpdate_monotone monotone_id (fun _ => monotone_const)
|
||||||
|
assumption
|
||||||
|
|
||||||
instance stmtEvaluator : StmtEvaluator (DefSet prog) prog :=
|
instance stmtEvaluator : StmtEvaluator (DefSet prog) prog :=
|
||||||
⟨eval prog, eval_mono prog⟩
|
⟨eval prog, eval_mono prog⟩
|
||||||
@@ -46,58 +43,92 @@ instance stmtEvaluator : StmtEvaluator (DefSet prog) prog :=
|
|||||||
def output : String :=
|
def output : String :=
|
||||||
show' (result (DefSet prog) prog)
|
show' (result (DefSet prog) prog)
|
||||||
|
|
||||||
inductive Run (prog : Program) where
|
/-- The statements a trace executed, paired with the state each executed at,
|
||||||
| nil : Run prog
|
most recent first (matching `LastAssign`, which scans for the most recent
|
||||||
| cons (s : prog.State) (bs : BasicStmt) (hc : prog.code s = some bs)
|
assignment). This is `Trace.steps` (chronological) reversed, so facts about
|
||||||
(rest : Run prog) : Run prog
|
concatenating traces reduce to mathlib's `List.append`/`List.reverse` lemmas. -/
|
||||||
|
abbrev Run (prog : Program) : Type := List (prog.State × BasicStmt)
|
||||||
|
|
||||||
@[aesop unsafe cases]
|
@[aesop unsafe cases]
|
||||||
inductive LastAssign (prog : Program) (x : String) : Run prog → prog.NodeId → Prop
|
inductive LastAssign (prog : Program) (x : String) : Run prog → prog.NodeId → Prop
|
||||||
| here (s : prog.State) (e : Expr) (hc : prog.code s = some (.assign x e))
|
| here (s : prog.State) (e : Expr) (hc : prog.code s = some (.assign x e))
|
||||||
(rest : Run prog) :
|
(rest : Run prog) :
|
||||||
LastAssign prog x (Run.cons s (.assign x e) hc rest) (prog.nodeIdOfNonempty s hc)
|
LastAssign prog x ((s, .assign x e) :: rest) (prog.nodeIdOfNonempty s hc)
|
||||||
| there (s : prog.State) (bs : BasicStmt) (hc : prog.code s = some bs)
|
| there (s : prog.State) (bs : BasicStmt) (hc : prog.code s = some bs)
|
||||||
(rest : Run prog) {n : prog.NodeId} :
|
(rest : Run prog) {n : prog.NodeId} :
|
||||||
(∀ e, bs ≠ .assign x e) → LastAssign prog x rest n →
|
(∀ e, bs ≠ .assign x e) → LastAssign prog x rest n →
|
||||||
LastAssign prog x (Run.cons s bs hc rest) n
|
LastAssign prog x ((s, bs) :: rest) n
|
||||||
|
|
||||||
instance stateInterp : StateInterp (DefSet prog) prog where
|
def runOfTraceₗ {s₁ s₂ : prog.State} {ρ₁ ρ₂ : Env}
|
||||||
St := fun _ => Run prog
|
(tr : Traceₗ prog.cfg s₁ s₂ ρ₁ ρ₂) : Run prog :=
|
||||||
init := Run.nil
|
tr.steps.reverse
|
||||||
interp vs _ run := ∀ (x : String) (assigners : DefSet prog), (x, assigners) ∈ vs →
|
|
||||||
∀ (n : prog.NodeId), LastAssign prog x run n → assigners n = true
|
def runOfTrace {s₁ s₂ : prog.State} {ρ₁ ρ₂ : Env}
|
||||||
|
(tr : Trace prog.cfg s₁ s₂ ρ₁ ρ₂) : Run prog :=
|
||||||
|
tr.steps.reverse
|
||||||
|
|
||||||
|
instance stateInterp : StateInterpretation (DefSet prog) prog where
|
||||||
|
Proj := Run prog
|
||||||
|
Pre := @runOfTraceₗ prog
|
||||||
|
Post := @runOfTrace prog
|
||||||
|
|
||||||
|
interp vs run := ∀ (x : String) (assigners : DefSet prog), (x, assigners) ∈ vs →
|
||||||
|
∀ (n : prog.NodeId), LastAssign prog x run n → n ∈ assigners
|
||||||
interp_sup := by
|
interp_sup := by
|
||||||
intro vs₁ vs₂ ρ run h x assigners hmem n hla
|
intro vs₁ vs₂ run h x assigners hmem n hla
|
||||||
obtain ⟨a₁, a₂, rfl, h₁, h₂⟩ := FiniteMap.mem_sup hmem
|
obtain ⟨a₁, a₂, rfl, h₁, h₂⟩ := FiniteMap.mem_sup hmem
|
||||||
aesop
|
aesop (add simp Finset.mem_union)
|
||||||
interp_inf := by
|
interp_inf := by
|
||||||
intro vs₁ vs₂ ρ run h x assigners hmem n hla
|
intro vs₁ vs₂ run h x assigners hmem n hla
|
||||||
obtain ⟨a₁, a₂, rfl, h₁, h₂⟩ := FiniteMap.mem_inf hmem
|
obtain ⟨a₁, a₂, rfl, h₁, h₂⟩ := FiniteMap.mem_inf hmem
|
||||||
aesop
|
aesop (add simp Finset.mem_inter)
|
||||||
|
|
||||||
|
post_pre := by
|
||||||
|
intro vs s₁ s₂ s₃ ρ₁ ρ₂ tr hedge hvs
|
||||||
|
simpa [runOfTrace, runOfTraceₗ] using hvs
|
||||||
|
|
||||||
|
private lemma valid_step (s : prog.State) {ρ₁ ρ₂ : Env}
|
||||||
|
{obs : Option BasicStmt} (hcode : prog.code s = obs)
|
||||||
|
(hbs : EvalBasicStmtOpt ρ₁ obs ρ₂)
|
||||||
|
{vs : VariableValues (DefSet prog) prog} {run : Run prog}
|
||||||
|
(hvs : ⟦vs⟧ run) :
|
||||||
|
⟦eval prog s vs⟧ ((hbs.steps s).reverse ++ run) := by
|
||||||
|
cases hbs with
|
||||||
|
| none => simpa [eval, hcode, EvalBasicStmtOpt.steps] using hvs
|
||||||
|
| some hbs =>
|
||||||
|
cases hbs with
|
||||||
|
| noop =>
|
||||||
|
simp [eval, hcode, EvalBasicStmtOpt.steps]
|
||||||
|
intro x assigners hmem n hla; aesop
|
||||||
|
| assign x e v hev =>
|
||||||
|
simp [eval, hcode, EvalBasicStmtOpt.steps]; intro k assigners hmem n hla
|
||||||
|
by_cases hx : k = x
|
||||||
|
· subst hx
|
||||||
|
have hd := FiniteMap.generalizedUpdate_mem_eq (List.mem_singleton.mpr rfl) hmem
|
||||||
|
rcases hla
|
||||||
|
<;> simp [Program.nodeIdOfNonempty, hd, genSet, Option.get] <;> aesop
|
||||||
|
· have hmem' := FiniteMap.generalizedUpdate_not_mem_backward
|
||||||
|
(fun hc => hx (List.mem_singleton.mp hc)) hmem
|
||||||
|
aesop
|
||||||
|
|
||||||
instance validStateEvaluator : ValidStateEvaluator (DefSet prog) prog where
|
instance validStateEvaluator : ValidStateEvaluator (DefSet prog) prog where
|
||||||
step := by intro s _ _ bs hcode _ rest; exact Run.cons s bs hcode rest
|
|
||||||
valid := by
|
valid := by
|
||||||
intro s ρ₁ ρ₂ bs vs st hcode hbs hvs
|
intro s₁ s₂ ρ₁ ρ₂ ρ₃ vs tr hbs hvs
|
||||||
cases hbs with
|
show ⟦eval prog s₂ vs⟧ (runOfTrace prog (tr ++ hbs))
|
||||||
| noop => intro x assigners hmem n hla; aesop
|
simpa [runOfTrace, runOfTraceₗ] using valid_step prog s₂ rfl hbs hvs
|
||||||
| assign x e v hev =>
|
|
||||||
intro k assigners hmem n hla
|
|
||||||
have hmem2 : (k, assigners) ∈
|
|
||||||
FiniteMap.generalizedUpdate id (fun _ _ => genSet prog s hcode) [x] vs := hmem
|
|
||||||
by_cases hx : k = x
|
|
||||||
· subst hx
|
|
||||||
have hd := FiniteMap.generalizedUpdate_mem_eq (List.mem_singleton.mpr rfl) hmem2
|
|
||||||
aesop (add simp genSet)
|
|
||||||
· have hmem' := FiniteMap.generalizedUpdate_not_mem_backward
|
|
||||||
(fun hc => hx (List.mem_singleton.mp hc)) hmem2
|
|
||||||
aesop
|
|
||||||
botV_init := by intro x assigners _ n hla; cases hla
|
botV_init := by intro x assigners _ n hla; cases hla
|
||||||
|
|
||||||
theorem analyze_correct {ρ : Env} (hrun : EvalStmt [] prog.rootStmt ρ) :
|
theorem analyze_correct {ρ : Env} (hrun : EvalStmt [] prog.rootStmt ρ) :
|
||||||
⟦ variablesAt prog.finalState (result (DefSet prog) prog) ⟧ ρ
|
⟦ variablesAt prog.finalState (result (DefSet prog) prog) ⟧
|
||||||
(stepTraceState (prog.trace hrun) (stateInterp prog).init) :=
|
(runOfTrace prog (prog.trace hrun)) :=
|
||||||
Forward.analyze_correct_state (DefSet prog) prog hrun
|
Forward.analyze_correct' (DefSet prog) prog hrun
|
||||||
|
|
||||||
|
theorem analyze_correct_at {ρf : Env} (hrun : EvalStmt [] prog.rootStmt ρf)
|
||||||
|
{s : prog.State} {ρin ρout : Env}
|
||||||
|
(hr : Reaches (prog.trace hrun) s ρin ρout) :
|
||||||
|
⟦ joinForKey s (result (DefSet prog) prog) ⟧ (runOfTraceₗ prog hr.pre)
|
||||||
|
∧ ⟦ variablesAt s (result (DefSet prog) prog) ⟧ (runOfTrace prog hr.post) :=
|
||||||
|
Forward.analyze_correct_at (DefSet prog) prog hrun hr
|
||||||
|
|
||||||
end ReachingAnalysis
|
end ReachingAnalysis
|
||||||
|
|
||||||
|
|||||||
@@ -210,9 +210,16 @@ instance eval_valid : ValidExprEvaluator SignLattice prog := by
|
|||||||
exact minus_valid h₁ h₂
|
exact minus_valid h₁ h₂
|
||||||
|
|
||||||
theorem analyze_correct {ρ : Env} (hrun : EvalStmt [] prog.rootStmt ρ) :
|
theorem analyze_correct {ρ : Env} (hrun : EvalStmt [] prog.rootStmt ρ) :
|
||||||
⟦ variablesAt prog.finalState (result SignLattice prog) ⟧ ρ () :=
|
⟦ variablesAt prog.finalState (result SignLattice prog) ⟧ ρ :=
|
||||||
Forward.analyze_correct SignLattice prog hrun
|
Forward.analyze_correct SignLattice prog hrun
|
||||||
|
|
||||||
|
theorem analyze_correct_at {ρf : Env} (hrun : EvalStmt [] prog.rootStmt ρf)
|
||||||
|
{s : prog.State} {ρin ρout : Env}
|
||||||
|
(hr : Reaches (prog.trace hrun) s ρin ρout) :
|
||||||
|
⟦ joinForKey s (result SignLattice prog) ⟧ ρin
|
||||||
|
∧ ⟦ variablesAt s (result SignLattice prog) ⟧ ρout :=
|
||||||
|
Forward.analyze_correct_at SignLattice prog hrun hr
|
||||||
|
|
||||||
end SignAnalysis
|
end SignAnalysis
|
||||||
|
|
||||||
end Spa
|
end Spa
|
||||||
|
|||||||
@@ -3,56 +3,4 @@ import Spa.Language.Semantics
|
|||||||
import Spa.Language.Graphs
|
import Spa.Language.Graphs
|
||||||
import Spa.Language.Traces
|
import Spa.Language.Traces
|
||||||
import Spa.Language.Properties
|
import Spa.Language.Properties
|
||||||
import Mathlib.Data.Finset.Sort
|
import Spa.Language.Program
|
||||||
import Mathlib.Data.String.Basic
|
|
||||||
|
|
||||||
namespace Spa
|
|
||||||
|
|
||||||
structure Program where
|
|
||||||
rootStmt : Stmt
|
|
||||||
|
|
||||||
namespace Program
|
|
||||||
|
|
||||||
variable (p : Program)
|
|
||||||
|
|
||||||
def cfg : Graph := Graph.wrap p.rootStmt.cfg
|
|
||||||
|
|
||||||
abbrev State : Type := p.cfg.Index
|
|
||||||
|
|
||||||
def initialState : p.State := p.rootStmt.cfg.wrapInput
|
|
||||||
|
|
||||||
def finalState : p.State := p.rootStmt.cfg.wrapOutput
|
|
||||||
|
|
||||||
noncomputable def trace {ρ : Env} (h : EvalStmt [] p.rootStmt ρ) :
|
|
||||||
Trace p.cfg p.initialState p.finalState [] ρ := by
|
|
||||||
obtain ⟨i₁, h₁, i₂, h₂, tr⟩ := EndToEndTrace.wrap (Stmt.cfg_sufficient h)
|
|
||||||
rw [Graph.wrap_inputs, List.mem_singleton] at h₁
|
|
||||||
rw [Graph.wrap_outputs, List.mem_singleton] at h₂
|
|
||||||
subst h₁; subst h₂
|
|
||||||
exact tr
|
|
||||||
|
|
||||||
def vars : List String := p.rootStmt.vars.sort (· ≤ ·)
|
|
||||||
|
|
||||||
lemma vars_nodup : p.vars.Nodup := Finset.sort_nodup _ _
|
|
||||||
|
|
||||||
def states : List p.State := p.cfg.indices
|
|
||||||
|
|
||||||
lemma states_complete (s : p.State) : s ∈ p.states := p.cfg.mem_indices s
|
|
||||||
|
|
||||||
lemma states_nodup : p.states.Nodup := p.cfg.nodup_indices
|
|
||||||
|
|
||||||
def code (st : p.State) : Option BasicStmt := p.cfg.nodes st
|
|
||||||
|
|
||||||
def incoming (s : p.State) : List p.State := p.cfg.predecessors s
|
|
||||||
|
|
||||||
lemma incoming_initialState_eq_nil : p.incoming p.initialState = [] :=
|
|
||||||
Graph.wrap_predecessors_eq_nil p.rootStmt.cfg p.initialState
|
|
||||||
(by rw [Graph.wrap_inputs]; exact List.mem_singleton_self _)
|
|
||||||
|
|
||||||
lemma mem_incoming_of_edge {s₁ s₂ : p.State}
|
|
||||||
(h : (s₁, s₂) ∈ p.cfg.edges) : s₁ ∈ p.incoming s₂ :=
|
|
||||||
p.cfg.mem_predecessors_of_edge h
|
|
||||||
|
|
||||||
end Program
|
|
||||||
|
|
||||||
end Spa
|
|
||||||
|
|||||||
@@ -25,6 +25,15 @@ indexing into a list.
|
|||||||
|
|
||||||
-/
|
-/
|
||||||
|
|
||||||
|
/-- Logically, when combining `Fin`s from two distinct pools,
|
||||||
|
the combination is disjoint. -/
|
||||||
|
lemma Fin.castAdd_ne_natAdd {n m : ℕ} (i : Fin n) (j : Fin m) :
|
||||||
|
Fin.castAdd m i ≠ Fin.natAdd n j := by
|
||||||
|
intro h
|
||||||
|
have := congrArg Fin.val h
|
||||||
|
simp only [Fin.coe_castAdd, Fin.coe_natAdd] at this
|
||||||
|
omega
|
||||||
|
|
||||||
/-- Bump the upper bound of a list of `Fin`s without changing their value. -/
|
/-- Bump the upper bound of a list of `Fin`s without changing their value. -/
|
||||||
def List.finCastAdd {n : ℕ} (l : List (Fin n)) (m : ℕ) : List (Fin (n + m)) :=
|
def List.finCastAdd {n : ℕ} (l : List (Fin n)) (m : ℕ) : List (Fin (n + m)) :=
|
||||||
l.map (Fin.castAdd m)
|
l.map (Fin.castAdd m)
|
||||||
@@ -157,6 +166,22 @@ def singleton (a : α) : GGraph α where
|
|||||||
def wrap (g : GGraph (Option β)) : GGraph (Option β) :=
|
def wrap (g : GGraph (Option β)) : GGraph (Option β) :=
|
||||||
singleton none ⤳ g ⤳ singleton none
|
singleton none ⤳ g ⤳ singleton none
|
||||||
|
|
||||||
|
/-- The input / entry node generated by `GGraph.wrap`. -/
|
||||||
|
def wrapInput (g : GGraph (Option β)) : (wrap g).Index :=
|
||||||
|
(0 : Fin 1).castAdd ((g ⤳ singleton none).size)
|
||||||
|
|
||||||
|
/-- The output / exit node generated by `GGraph.wrap`. -/
|
||||||
|
def wrapOutput (g : GGraph (Option β)) : (wrap g).Index :=
|
||||||
|
Fin.natAdd 1 ((Fin.natAdd g.size (0 : Fin 1)))
|
||||||
|
|
||||||
|
/-- The `wrapInput` is, indeed, the graph's only input after `wrap`. -/
|
||||||
|
lemma wrap_inputs (g : GGraph (Option β)) :
|
||||||
|
(wrap g).inputs = [g.wrapInput] := rfl
|
||||||
|
|
||||||
|
/-- The `wrapInput` is, indeed, the graph's only output after `wrap`. -/
|
||||||
|
lemma wrap_outputs (g : GGraph (Option β)) :
|
||||||
|
(wrap g).outputs = [g.wrapOutput] := rfl
|
||||||
|
|
||||||
@[simp] lemma map_singleton (f : α → β) (a : α) :
|
@[simp] lemma map_singleton (f : α → β) (a : α) :
|
||||||
f <$> singleton a = singleton (f a) := rfl
|
f <$> singleton a = singleton (f a) := rfl
|
||||||
|
|
||||||
@@ -188,6 +213,65 @@ def wrap (g : GGraph (Option β)) : GGraph (Option β) :=
|
|||||||
(Option.map h) <$> wrap g = wrap (Option.map h <$> g) := by
|
(Option.map h) <$> wrap g = wrap (Option.map h <$> g) := by
|
||||||
simp [GGraph.wrap, GGraph.map_sequence, GGraph.map_singleton]
|
simp [GGraph.wrap, GGraph.map_sequence, GGraph.map_singleton]
|
||||||
|
|
||||||
|
/-! ### Embeddings
|
||||||
|
|
||||||
|
Each composition operator includes its operands into the result via an index
|
||||||
|
translation that preserves node payloads and edges. `Embed` captures exactly
|
||||||
|
those two facts, so anything defined from `nodes` and `edges` (traces, node
|
||||||
|
labels, …) can be transported along an embedding once, instead of once per
|
||||||
|
operator.
|
||||||
|
|
||||||
|
`Embed` is deliberately a structure rather than a class: for `g ⤳ g`, both the
|
||||||
|
left and the right inclusion inhabit the same type `Embed g (g ⤳ g)`, so
|
||||||
|
instance resolution could silently pick the wrong copy. Embeddings into a
|
||||||
|
composed graph are non-canonical by design; a named witness says which
|
||||||
|
inclusion is meant. -/
|
||||||
|
|
||||||
|
/-- An embedding of graph `g` into graph `h`: an index translation that
|
||||||
|
preserves node payloads and edges. -/
|
||||||
|
structure Embed (g h : GGraph α) where
|
||||||
|
f : g.Index → h.Index
|
||||||
|
nodes_eq : ∀ i, h.nodes (f i) = g.nodes i
|
||||||
|
edges_mem : ∀ {e : g.Edge}, e ∈ g.edges → (f e.1, f e.2) ∈ h.edges
|
||||||
|
|
||||||
|
/-- Embeddings compose. -/
|
||||||
|
def Embed.trans {g₁ g₂ g₃ : GGraph α} (e₁ : Embed g₁ g₂) (e₂ : Embed g₂ g₃) :
|
||||||
|
Embed g₁ g₃ where
|
||||||
|
f := e₂.f ∘ e₁.f
|
||||||
|
nodes_eq i := (e₂.nodes_eq (e₁.f i)).trans (e₁.nodes_eq i)
|
||||||
|
edges_mem he := e₂.edges_mem (e₁.edges_mem he)
|
||||||
|
|
||||||
|
/-- The left operand's inclusion into a sequenced graph. -/
|
||||||
|
def Embed.sequenceLeft (g₁ g₂ : GGraph α) : Embed g₁ (g₁ ⤳ g₂) where
|
||||||
|
f i := i.castAdd g₂.size
|
||||||
|
nodes_eq i := Fin.append_left g₁.nodes g₂.nodes i
|
||||||
|
edges_mem he := List.mem_append_left _ (List.mem_append_left _ (List.mem_map_of_mem _ he))
|
||||||
|
|
||||||
|
/-- The right operand's inclusion into a sequenced graph. -/
|
||||||
|
def Embed.sequenceRight (g₁ g₂ : GGraph α) : Embed g₂ (g₁ ⤳ g₂) where
|
||||||
|
f i := i.natAdd g₁.size
|
||||||
|
nodes_eq i := Fin.append_right g₁.nodes g₂.nodes i
|
||||||
|
edges_mem he := List.mem_append_left _ (List.mem_append_right _ (List.mem_map_of_mem _ he))
|
||||||
|
|
||||||
|
/-- The left operand's inclusion into an overlaid graph. -/
|
||||||
|
def Embed.overlayLeft (g₁ g₂ : GGraph α) : Embed g₁ (g₁ ∙ g₂) where
|
||||||
|
f i := i.castAdd g₂.size
|
||||||
|
nodes_eq i := Fin.append_left g₁.nodes g₂.nodes i
|
||||||
|
edges_mem he := List.mem_append_left _ (List.mem_map_of_mem _ he)
|
||||||
|
|
||||||
|
/-- The right operand's inclusion into an overlaid graph. -/
|
||||||
|
def Embed.overlayRight (g₁ g₂ : GGraph α) : Embed g₂ (g₁ ∙ g₂) where
|
||||||
|
f i := i.natAdd g₁.size
|
||||||
|
nodes_eq i := Fin.append_right g₁.nodes g₂.nodes i
|
||||||
|
edges_mem he := List.mem_append_right _ (List.mem_map_of_mem _ he)
|
||||||
|
|
||||||
|
/-- The body's inclusion into a `loop` graph. -/
|
||||||
|
def Embed.loop (g : GGraph (Option β)) : Embed g (loop g) where
|
||||||
|
f i := i.natAdd 2
|
||||||
|
nodes_eq i := Fin.append_right (fun _ : Fin 2 => none) g.nodes i
|
||||||
|
edges_mem he := List.mem_append_left _ (List.mem_append_left _
|
||||||
|
(List.mem_append_left _ (List.mem_map_of_mem _ he)))
|
||||||
|
|
||||||
variable (g : GGraph α)
|
variable (g : GGraph α)
|
||||||
|
|
||||||
/-- All the nodes in the graph. -/
|
/-- All the nodes in the graph. -/
|
||||||
@@ -205,6 +289,35 @@ lemma nodup_indices : g.indices.Nodup :=
|
|||||||
def predecessors (idx : g.Index) : List g.Index :=
|
def predecessors (idx : g.Index) : List g.Index :=
|
||||||
g.indices.filter (fun idx' => (idx', idx) ∈ g.edges)
|
g.indices.filter (fun idx' => (idx', idx) ∈ g.edges)
|
||||||
|
|
||||||
|
/-- When sequencing (proven here with `Graph.singleton` on the left), no edges
|
||||||
|
exist from the right-hand graph back to the left. -/
|
||||||
|
private lemma not_mem_edges_castAdd_sequence {g₂ : GGraph (Option β)} (i : Fin 1)
|
||||||
|
(idx : (singleton none ⤳ g₂).Index) :
|
||||||
|
((idx, i.castAdd g₂.size) : (singleton none ⤳ g₂).Edge)
|
||||||
|
∉ (singleton none ⤳ g₂).edges := by
|
||||||
|
intro h
|
||||||
|
rcases List.mem_append.mp h with h' | h'
|
||||||
|
· rcases List.mem_append.mp h' with h'' | h''
|
||||||
|
· -- lifted edges of `singleton []`: there are none
|
||||||
|
simp [singleton, List.finCastAddProd] at h''
|
||||||
|
· -- lifted edges of g₂: targets are natAdd
|
||||||
|
obtain ⟨e, _, heq⟩ := List.mem_map.mp h''
|
||||||
|
exact Fin.castAdd_ne_natAdd i e.2 (congrArg Prod.snd heq).symm
|
||||||
|
· -- product edges: targets are natAdd'd inputs of g₂
|
||||||
|
obtain ⟨-, hb⟩ := List.mem_product.mp h'
|
||||||
|
obtain ⟨j, -, heq⟩ := List.mem_map.mp hb
|
||||||
|
exact Fin.castAdd_ne_natAdd i j heq.symm
|
||||||
|
|
||||||
|
/-- The input node of a graph after `Graph.wrap` has no predecessors. -/
|
||||||
|
lemma wrap_predecessors_eq_nil (g : GGraph (Option β)) (idx : (wrap g).Index)
|
||||||
|
(h : idx ∈ (wrap g).inputs) :
|
||||||
|
(wrap g).predecessors idx = [] := by
|
||||||
|
rw [wrap_inputs, List.mem_singleton] at h
|
||||||
|
subst h
|
||||||
|
rw [GGraph.predecessors, List.filter_eq_nil_iff]
|
||||||
|
intro idx' _
|
||||||
|
simpa using not_mem_edges_castAdd_sequence (g₂ := g ⤳ singleton none) 0 idx'
|
||||||
|
|
||||||
/-- There's there's an edge between two nodes `idx₁` and `idx₂`,
|
/-- There's there's an edge between two nodes `idx₁` and `idx₂`,
|
||||||
then `idx₁` is the predecessor of `idx₂`. -/
|
then `idx₁` is the predecessor of `idx₂`. -/
|
||||||
lemma mem_predecessors_of_edge {idx₁ idx₂ : g.Index}
|
lemma mem_predecessors_of_edge {idx₁ idx₂ : g.Index}
|
||||||
@@ -225,7 +338,7 @@ abbrev Graph : Type := GGraph (Option BasicStmt)
|
|||||||
|
|
||||||
namespace Graph
|
namespace Graph
|
||||||
|
|
||||||
export GGraph (overlay sequence loop singleton wrap loop_inputs loop_outputs)
|
export GGraph (overlay sequence loop singleton wrap loop_inputs loop_outputs wrapInput wrapOutput wrap_inputs wrap_outputs)
|
||||||
|
|
||||||
@[inherit_doc] scoped infixr:70 " ∙ " => GGraph.overlay
|
@[inherit_doc] scoped infixr:70 " ∙ " => GGraph.overlay
|
||||||
@[inherit_doc] scoped infixr:70 " ⤳ " => GGraph.sequence
|
@[inherit_doc] scoped infixr:70 " ⤳ " => GGraph.sequence
|
||||||
|
|||||||
77
lean/Spa/Language/Program.lean
Normal file
77
lean/Spa/Language/Program.lean
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import Spa.Language.Base
|
||||||
|
import Spa.Language.Semantics
|
||||||
|
import Spa.Language.Graphs
|
||||||
|
import Mathlib.Data.Finset.Sort
|
||||||
|
import Mathlib.Data.String.Basic
|
||||||
|
|
||||||
|
namespace Spa
|
||||||
|
|
||||||
|
/-- A self-contained program to be evaluated, analyzed, and transformed. -/
|
||||||
|
structure Program where
|
||||||
|
/-- The statement at the top level of the program. Since `Spa.Stmt` contains
|
||||||
|
sequencing via `Spa.Stmt.andThen`, this can encode any number of
|
||||||
|
statements. -/
|
||||||
|
rootStmt : Stmt
|
||||||
|
/-- A memoized copy of the control-flow graph. This field is an
|
||||||
|
implementation detail to avoid re-computing `Spa.GGraph.wrap` and `Spa.Stmt.cfg`
|
||||||
|
every time the program's control flow graph is needed -/
|
||||||
|
cfgCache : Thunk Graph := Thunk.mk fun _ => Graph.wrap rootStmt.cfg
|
||||||
|
|
||||||
|
namespace Program
|
||||||
|
|
||||||
|
variable (p : Program)
|
||||||
|
|
||||||
|
-- Runtime implementation of `cfg`: read the memoized graph.
|
||||||
|
private def cfgImpl : Graph := p.cfgCache.get
|
||||||
|
|
||||||
|
/-- The control flow graph corresponding to this graph. -/
|
||||||
|
@[implemented_by cfgImpl]
|
||||||
|
def cfg : Graph := Graph.wrap p.rootStmt.cfg
|
||||||
|
|
||||||
|
/-- A state in the control flow `Spa.Graph` of this program. -/
|
||||||
|
abbrev State : Type := p.cfg.Index
|
||||||
|
|
||||||
|
/-- Variables mentioned or defined in this program. -/
|
||||||
|
def vars : List String := p.rootStmt.vars.sort (· ≤ ·)
|
||||||
|
|
||||||
|
/-- `vars` has no duplicates. -/
|
||||||
|
lemma vars_nodup : p.vars.Nodup := Finset.sort_nodup _ _
|
||||||
|
|
||||||
|
/-- All the states in the program's control flow `Spa.Graph`. -/
|
||||||
|
def states : List p.State := p.cfg.indices
|
||||||
|
|
||||||
|
/-- All states in the CFG are contained in `states`. -/
|
||||||
|
lemma states_complete (s : p.State) : s ∈ p.states := p.cfg.mem_indices s
|
||||||
|
|
||||||
|
/-- `states` has no duplicates. -/
|
||||||
|
lemma states_nodup : p.states.Nodup := p.cfg.nodup_indices
|
||||||
|
|
||||||
|
/-- Given a node of the program's CFG, return the code at that node.
|
||||||
|
At this time, for convenience of proofs, the CFGs have at most
|
||||||
|
one basic statement, and multi-statement basic blocks are encoded
|
||||||
|
as chains of blocks. Thus, this returns at most one `Spa.BasicStmt`. -/
|
||||||
|
@[reducible]
|
||||||
|
def code (st : p.State) : Option BasicStmt := p.cfg.nodes st
|
||||||
|
|
||||||
|
/-- Get the predecessors of a particular CFG node / program state. -/
|
||||||
|
def incoming (s : p.State) : List p.State := p.cfg.predecessors s
|
||||||
|
|
||||||
|
/-- The entry point of the program's CFG. -/
|
||||||
|
def initialState : p.State := Graph.wrapInput p.rootStmt.cfg
|
||||||
|
|
||||||
|
/-- The exit point of the program's CFG. -/
|
||||||
|
def finalState : p.State := Graph.wrapOutput p.rootStmt.cfg
|
||||||
|
|
||||||
|
/-- `incoming` is a faithful representation of edges in the CFG. -/
|
||||||
|
lemma mem_incoming_of_edge {s₁ s₂ : p.State}
|
||||||
|
(h : (s₁, s₂) ∈ p.cfg.edges) : s₁ ∈ p.incoming s₂ :=
|
||||||
|
p.cfg.mem_predecessors_of_edge h
|
||||||
|
|
||||||
|
/-- The `initialState` has no incoming edges (it's the program start). -/
|
||||||
|
lemma incoming_initialState_eq_nil : p.incoming p.initialState = [] :=
|
||||||
|
GGraph.wrap_predecessors_eq_nil p.rootStmt.cfg p.initialState
|
||||||
|
(by rw [Graph.wrap_inputs]; exact List.mem_singleton_self _)
|
||||||
|
|
||||||
|
end Program
|
||||||
|
|
||||||
|
end Spa
|
||||||
@@ -23,73 +23,47 @@ namespace Spa
|
|||||||
|
|
||||||
open Graph
|
open Graph
|
||||||
|
|
||||||
lemma Fin.castAdd_ne_natAdd {n m : ℕ} (i : Fin n) (j : Fin m) :
|
|
||||||
Fin.castAdd m i ≠ Fin.natAdd n j := by
|
|
||||||
intro h
|
|
||||||
have := congrArg Fin.val h
|
|
||||||
simp only [Fin.coe_castAdd, Fin.coe_natAdd] at this
|
|
||||||
omega
|
|
||||||
|
|
||||||
section Embeddings
|
section Embeddings
|
||||||
|
|
||||||
variable {g₁ g₂ : Graph} {ρ₁ ρ₂ : Env}
|
variable {g₁ g₂ : Graph} {ρ₁ ρ₂ : Env}
|
||||||
|
|
||||||
|
/-- Transport a trace along a graph embedding: an embedding preserves node
|
||||||
|
payloads and edges, which is everything a trace is made of. This is the
|
||||||
|
single induction behind all the per-operator lifting corollaries below. -/
|
||||||
|
noncomputable def Trace.embed {g h : Graph} (e : GGraph.Embed g h)
|
||||||
|
{idx₁ idx₂ : g.Index} (tr : Trace g idx₁ idx₂ ρ₁ ρ₂) :
|
||||||
|
Trace h (e.f idx₁) (e.f idx₂) ρ₁ ρ₂ := by
|
||||||
|
induction tr with
|
||||||
|
| single hbs => exact Trace.single (by rwa [e.nodes_eq])
|
||||||
|
| edge hbs he _ ih => exact Trace.edge (by rwa [e.nodes_eq]) (e.edges_mem he) ih
|
||||||
|
|
||||||
/-- When two graphs are overlaid, for each trace in the left graph,
|
/-- When two graphs are overlaid, for each trace in the left graph,
|
||||||
a corresponding trace exists in the combined graph. -/
|
a corresponding trace exists in the combined graph. -/
|
||||||
noncomputable def Trace.overlay_left {idx₁ idx₂ : g₁.Index}
|
noncomputable def Trace.overlay_left {idx₁ idx₂ : g₁.Index}
|
||||||
(tr : Trace g₁ idx₁ idx₂ ρ₁ ρ₂) :
|
(tr : Trace g₁ idx₁ idx₂ ρ₁ ρ₂) :
|
||||||
Trace (g₁ ∙ g₂) (idx₁.castAdd g₂.size) (idx₂.castAdd g₂.size) ρ₁ ρ₂ := by
|
Trace (g₁ ∙ g₂) (idx₁.castAdd g₂.size) (idx₂.castAdd g₂.size) ρ₁ ρ₂ :=
|
||||||
induction tr with
|
tr.embed (GGraph.Embed.overlayLeft g₁ g₂)
|
||||||
| single hbs =>
|
|
||||||
exact Trace.single (by rwa [show (g₁ ∙ g₂).nodes = Fin.append g₁.nodes g₂.nodes from rfl,
|
|
||||||
Fin.append_left])
|
|
||||||
| edge hbs he _ ih =>
|
|
||||||
refine Trace.edge ?_ ?_ ih
|
|
||||||
· rwa [show (g₁ ∙ g₂).nodes = Fin.append g₁.nodes g₂.nodes from rfl, Fin.append_left]
|
|
||||||
· exact List.mem_append_left _ (List.mem_map_of_mem _ he)
|
|
||||||
|
|
||||||
/-- When two graphs are overlaid, for each trace in the right graph,
|
/-- When two graphs are overlaid, for each trace in the right graph,
|
||||||
a corresponding trace exists in the combined graph. -/
|
a corresponding trace exists in the combined graph. -/
|
||||||
noncomputable def Trace.overlay_right {idx₁ idx₂ : g₂.Index}
|
noncomputable def Trace.overlay_right {idx₁ idx₂ : g₂.Index}
|
||||||
(tr : Trace g₂ idx₁ idx₂ ρ₁ ρ₂) :
|
(tr : Trace g₂ idx₁ idx₂ ρ₁ ρ₂) :
|
||||||
Trace (g₁ ∙ g₂) (idx₁.natAdd g₁.size) (idx₂.natAdd g₁.size) ρ₁ ρ₂ := by
|
Trace (g₁ ∙ g₂) (idx₁.natAdd g₁.size) (idx₂.natAdd g₁.size) ρ₁ ρ₂ :=
|
||||||
induction tr with
|
tr.embed (GGraph.Embed.overlayRight g₁ g₂)
|
||||||
| single hbs =>
|
|
||||||
exact Trace.single (by rwa [show (g₁ ∙ g₂).nodes = Fin.append g₁.nodes g₂.nodes from rfl,
|
|
||||||
Fin.append_right])
|
|
||||||
| edge hbs he _ ih =>
|
|
||||||
refine Trace.edge ?_ ?_ ih
|
|
||||||
· rwa [show (g₁ ∙ g₂).nodes = Fin.append g₁.nodes g₂.nodes from rfl, Fin.append_right]
|
|
||||||
· exact List.mem_append_right _ (List.mem_map_of_mem _ he)
|
|
||||||
|
|
||||||
/-- When two graphs are sequenced, for each trace in the first graph,
|
/-- When two graphs are sequenced, for each trace in the first graph,
|
||||||
a corresponding trace exists in the combined graph. -/
|
a corresponding trace exists in the combined graph. -/
|
||||||
noncomputable def Trace.sequence_left {idx₁ idx₂ : g₁.Index}
|
noncomputable def Trace.sequence_left {idx₁ idx₂ : g₁.Index}
|
||||||
(tr : Trace g₁ idx₁ idx₂ ρ₁ ρ₂) :
|
(tr : Trace g₁ idx₁ idx₂ ρ₁ ρ₂) :
|
||||||
Trace (g₁ ⤳ g₂) (idx₁.castAdd g₂.size) (idx₂.castAdd g₂.size) ρ₁ ρ₂ := by
|
Trace (g₁ ⤳ g₂) (idx₁.castAdd g₂.size) (idx₂.castAdd g₂.size) ρ₁ ρ₂ :=
|
||||||
induction tr with
|
tr.embed (GGraph.Embed.sequenceLeft g₁ g₂)
|
||||||
| single hbs =>
|
|
||||||
exact Trace.single (by rwa [show (g₁ ⤳ g₂).nodes = Fin.append g₁.nodes g₂.nodes from rfl,
|
|
||||||
Fin.append_left])
|
|
||||||
| edge hbs he _ ih =>
|
|
||||||
refine Trace.edge ?_ ?_ ih
|
|
||||||
· rwa [show (g₁ ⤳ g₂).nodes = Fin.append g₁.nodes g₂.nodes from rfl, Fin.append_left]
|
|
||||||
· exact List.mem_append_left _ (List.mem_append_left _ (List.mem_map_of_mem _ he))
|
|
||||||
|
|
||||||
/-- When two graphs are sequenced, for each trace in the second graph,
|
/-- When two graphs are sequenced, for each trace in the second graph,
|
||||||
a corresponding trace exists in the combined graph. -/
|
a corresponding trace exists in the combined graph. -/
|
||||||
noncomputable def Trace.sequence_right {idx₁ idx₂ : g₂.Index}
|
noncomputable def Trace.sequence_right {idx₁ idx₂ : g₂.Index}
|
||||||
(tr : Trace g₂ idx₁ idx₂ ρ₁ ρ₂) :
|
(tr : Trace g₂ idx₁ idx₂ ρ₁ ρ₂) :
|
||||||
Trace (g₁ ⤳ g₂) (idx₁.natAdd g₁.size) (idx₂.natAdd g₁.size) ρ₁ ρ₂ := by
|
Trace (g₁ ⤳ g₂) (idx₁.natAdd g₁.size) (idx₂.natAdd g₁.size) ρ₁ ρ₂ :=
|
||||||
induction tr with
|
tr.embed (GGraph.Embed.sequenceRight g₁ g₂)
|
||||||
| single hbs =>
|
|
||||||
exact Trace.single (by rwa [show (g₁ ⤳ g₂).nodes = Fin.append g₁.nodes g₂.nodes from rfl,
|
|
||||||
Fin.append_right])
|
|
||||||
| edge hbs he _ ih =>
|
|
||||||
refine Trace.edge ?_ ?_ ih
|
|
||||||
· rwa [show (g₁ ⤳ g₂).nodes = Fin.append g₁.nodes g₂.nodes from rfl, Fin.append_right]
|
|
||||||
· exact List.mem_append_left _
|
|
||||||
(List.mem_append_right _ (List.mem_map_of_mem _ he))
|
|
||||||
|
|
||||||
/-- Equivalent of `Trace.overlay_left` for end-to-end traces. -/
|
/-- Equivalent of `Trace.overlay_left` for end-to-end traces. -/
|
||||||
noncomputable def EndToEndTrace.overlay_left (etr : EndToEndTrace g₁ ρ₁ ρ₂) :
|
noncomputable def EndToEndTrace.overlay_left (etr : EndToEndTrace g₁ ρ₁ ρ₂) :
|
||||||
@@ -132,18 +106,8 @@ variable {g : Graph} {ρ₁ ρ₂ ρ₃ : Env}
|
|||||||
|
|
||||||
/-- A trace through a body CFG still exists (up to reindexing) in a zero-or-more loop CFG. -/
|
/-- A trace through a body CFG still exists (up to reindexing) in a zero-or-more loop CFG. -/
|
||||||
noncomputable def Trace.loop {idx₁ idx₂ : g.Index} (tr : Trace g idx₁ idx₂ ρ₁ ρ₂) :
|
noncomputable def Trace.loop {idx₁ idx₂ : g.Index} (tr : Trace g idx₁ idx₂ ρ₁ ρ₂) :
|
||||||
Trace (Graph.loop g) (idx₁.natAdd 2) (idx₂.natAdd 2) ρ₁ ρ₂ := by
|
Trace (Graph.loop g) (idx₁.natAdd 2) (idx₂.natAdd 2) ρ₁ ρ₂ :=
|
||||||
induction tr with
|
tr.embed (GGraph.Embed.loop g)
|
||||||
| single hbs =>
|
|
||||||
exact Trace.single (by
|
|
||||||
rwa [show (Graph.loop g).nodes = Fin.append (fun _ : Fin 2 => none) g.nodes from rfl,
|
|
||||||
Fin.append_right])
|
|
||||||
| edge hbs he _ ih =>
|
|
||||||
refine Trace.edge ?_ ?_ ih
|
|
||||||
· rwa [show (Graph.loop g).nodes = Fin.append (fun _ : Fin 2 => none) g.nodes from rfl,
|
|
||||||
Fin.append_right]
|
|
||||||
· exact List.mem_append_left _ (List.mem_append_left _
|
|
||||||
(List.mem_append_left _ (List.mem_map_of_mem _ he)))
|
|
||||||
|
|
||||||
/-- The beginning node of a loop graph is empty. -/
|
/-- The beginning node of a loop graph is empty. -/
|
||||||
private lemma loop_nodes_at_in :
|
private lemma loop_nodes_at_in :
|
||||||
@@ -246,49 +210,17 @@ noncomputable def Stmt.cfg_sufficient {s : Stmt} {ρ₁ ρ₂ : Env}
|
|||||||
| whileFalse ρ e s _ =>
|
| whileFalse ρ e s _ =>
|
||||||
exact EndToEndTrace.loop_empty
|
exact EndToEndTrace.loop_empty
|
||||||
|
|
||||||
/-- The input / entry node generated by `Graph.wrap`. -/
|
namespace Program
|
||||||
def Graph.wrapInput (g : Graph) : (Graph.wrap g).Index :=
|
|
||||||
(0 : Fin 1).castAdd ((g ⤳ Graph.singleton none).size)
|
|
||||||
|
|
||||||
/-- The output / exit node generated by `Graph.wrap`. -/
|
noncomputable def trace (p : Program) {ρ : Env} (h : EvalStmt [] p.rootStmt ρ) :
|
||||||
def Graph.wrapOutput (g : Graph) : (Graph.wrap g).Index :=
|
Trace p.cfg p.initialState p.finalState [] ρ := by
|
||||||
Fin.natAdd 1 ((Fin.natAdd g.size (0 : Fin 1)))
|
obtain ⟨i₁, h₁, i₂, h₂, tr⟩ := EndToEndTrace.wrap (Stmt.cfg_sufficient h)
|
||||||
|
rw [Graph.wrap_inputs, List.mem_singleton] at h₁
|
||||||
|
rw [Graph.wrap_outputs, List.mem_singleton] at h₂
|
||||||
|
subst h₁; subst h₂
|
||||||
|
exact tr
|
||||||
|
|
||||||
/-- The `Graph.wrapInput` is, indeed, the graph's only input after `Graph.wrap`. -/
|
end Program
|
||||||
lemma Graph.wrap_inputs (g : Graph) :
|
|
||||||
(Graph.wrap g).inputs = [g.wrapInput] := rfl
|
|
||||||
|
|
||||||
/-- The `Graph.wrapInput` is, indeed, the graph's only output after `Graph.wrap`. -/
|
|
||||||
lemma Graph.wrap_outputs (g : Graph) :
|
|
||||||
(Graph.wrap g).outputs = [g.wrapOutput] := rfl
|
|
||||||
|
|
||||||
/-- When sequencing (proven here with `Graph.singleton` on the left), no edges
|
|
||||||
exist from the right-hand graph back to the left. -/
|
|
||||||
private lemma not_mem_edges_castAdd_sequence {g₂ : Graph} (i : Fin 1)
|
|
||||||
(idx : (Graph.singleton none ⤳ g₂).Index) :
|
|
||||||
((idx, i.castAdd g₂.size) : (Graph.singleton none ⤳ g₂).Edge)
|
|
||||||
∉ (Graph.singleton none ⤳ g₂).edges := by
|
|
||||||
intro h
|
|
||||||
rcases List.mem_append.mp h with h' | h'
|
|
||||||
· rcases List.mem_append.mp h' with h'' | h''
|
|
||||||
· -- lifted edges of `singleton []`: there are none
|
|
||||||
simp [Graph.singleton, List.finCastAddProd] at h''
|
|
||||||
· -- lifted edges of g₂: targets are natAdd
|
|
||||||
obtain ⟨e, _, heq⟩ := List.mem_map.mp h''
|
|
||||||
exact Fin.castAdd_ne_natAdd i e.2 (congrArg Prod.snd heq).symm
|
|
||||||
· -- product edges: targets are natAdd'd inputs of g₂
|
|
||||||
obtain ⟨-, hb⟩ := List.mem_product.mp h'
|
|
||||||
obtain ⟨j, -, heq⟩ := List.mem_map.mp hb
|
|
||||||
exact Fin.castAdd_ne_natAdd i j heq.symm
|
|
||||||
|
|
||||||
/-- The input node of a graph after `Graph.wrap` has no predecessors. -/
|
|
||||||
lemma Graph.wrap_predecessors_eq_nil (g : Graph) (idx : (Graph.wrap g).Index)
|
|
||||||
(h : idx ∈ (Graph.wrap g).inputs) :
|
|
||||||
(Graph.wrap g).predecessors idx = [] := by
|
|
||||||
rw [Graph.wrap_inputs, List.mem_singleton] at h
|
|
||||||
subst h
|
|
||||||
rw [GGraph.predecessors, List.filter_eq_nil_iff]
|
|
||||||
intro idx' _
|
|
||||||
simpa using not_mem_edges_castAdd_sequence (g₂ := g ⤳ Graph.singleton none) 0 idx'
|
|
||||||
|
|
||||||
end Spa
|
end Spa
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import Spa.Language.Semantics
|
import Spa.Language.Semantics
|
||||||
import Spa.Language.Graphs
|
import Spa.Language.Graphs
|
||||||
|
import Spa.Language.Program
|
||||||
|
|
||||||
/-!
|
/-!
|
||||||
|
|
||||||
@@ -36,24 +37,230 @@ inductive Trace (g : Graph) : g.Index → g.Index → Env → Env → Type
|
|||||||
EvalBasicStmtOpt ρ₁ (g.nodes idx₁) ρ₂ → (idx₁, idx₂) ∈ g.edges →
|
EvalBasicStmtOpt ρ₁ (g.nodes idx₁) ρ₂ → (idx₁, idx₂) ∈ g.edges →
|
||||||
Trace g idx₂ idx₃ ρ₂ ρ₃ → Trace g idx₁ idx₃ ρ₁ ρ₃
|
Trace g idx₂ idx₃ ρ₂ ρ₃ → Trace g idx₁ idx₃ ρ₁ ρ₃
|
||||||
|
|
||||||
|
/-!
|
||||||
|
|
||||||
|
## Open Traces
|
||||||
|
|
||||||
|
A normal `Trace` starts right before one state, and ends right after another.
|
||||||
|
This is convenient for inductively proving correctness / sufficience, but
|
||||||
|
awkward because 1) no empty traces exist and 2) concatenation requires an extra
|
||||||
|
edge.
|
||||||
|
|
||||||
|
However, when attempting an "empty" trace, two types are equally possible:
|
||||||
|
traces that end _right before_ executing a state (`Traceₗ`) and
|
||||||
|
traces that begin _right after_ executing a state (`Traceᵣ`). They
|
||||||
|
are symmetric and can be concatenated with full traces on the left
|
||||||
|
and right, respectively. -/
|
||||||
|
|
||||||
|
/-- Left-open trace, representing execution that ends right before `idx₂`. -/
|
||||||
|
inductive Traceₗ (g : Graph) : g.Index → g.Index → Env → Env → Type where
|
||||||
|
| nil {idx : g.Index} {ρ : Env} : Traceₗ g idx idx ρ ρ
|
||||||
|
| cons {idx₁ idx₂ idx₃ : g.Index} {ρ₁ ρ₂ ρ₃ : Env} :
|
||||||
|
EvalBasicStmtOpt ρ₁ (g.nodes idx₁) ρ₂ →
|
||||||
|
(idx₁, idx₂) ∈ g.edges →
|
||||||
|
Traceₗ g idx₂ idx₃ ρ₂ ρ₃ → Traceₗ g idx₁ idx₃ ρ₁ ρ₃
|
||||||
|
|
||||||
|
def Traceₗ.single (g : Graph) (idx : g.Index) (ρ : Env) : Traceₗ g idx idx ρ ρ := .nil
|
||||||
|
|
||||||
|
/-- Right-open trace, representing execution that starts right after `idx₁`. -/
|
||||||
|
inductive Traceᵣ (g : Graph) : g.Index → g.Index → Env → Env → Type where
|
||||||
|
| nil {idx : g.Index} {ρ : Env} : Traceᵣ g idx idx ρ ρ
|
||||||
|
| cons {idx₁ idx₂ idx₃ : g.Index} {ρ₁ ρ₂ ρ₃ : Env} :
|
||||||
|
Traceᵣ g idx₁ idx₂ ρ₁ ρ₂ →
|
||||||
|
(idx₂, idx₃) ∈ g.edges →
|
||||||
|
EvalBasicStmtOpt ρ₂ (g.nodes idx₃) ρ₃ → Traceᵣ g idx₁ idx₃ ρ₁ ρ₃
|
||||||
|
|
||||||
|
def Traceᵣ.single (g : Graph) (idx : g.Index) (ρ : Env) : Traceᵣ g idx idx ρ ρ := .nil
|
||||||
|
|
||||||
/-- Sequence two traces together. Since the endpoint of the first trace
|
/-- Sequence two traces together. Since the endpoint of the first trace
|
||||||
is _after_ its last basic block's execution, and the beginning of
|
is _after_ its last basic block's execution, and the beginning of
|
||||||
the next trace is _before_ its first basic block's execution,
|
the next trace is _before_ its first basic block's execution,
|
||||||
there must be an edge to connect the two. -/
|
there must be an edge to connect the two. -/
|
||||||
noncomputable def Trace.concat {g : Graph} {idx₁ idx₂ idx₃ idx₄ : g.Index}
|
def Trace.concat {g : Graph} {idx₁ idx₂ idx₃ idx₄ : g.Index}
|
||||||
{ρ₁ ρ₂ ρ₃ : Env} (tr₁ : Trace g idx₁ idx₂ ρ₁ ρ₂)
|
{ρ₁ ρ₂ ρ₃ : Env} (tr₁ : Trace g idx₁ idx₂ ρ₁ ρ₂)
|
||||||
(he : (idx₂, idx₃) ∈ g.edges) (tr₂ : Trace g idx₃ idx₄ ρ₂ ρ₃) :
|
(he : (idx₂, idx₃) ∈ g.edges) (tr₂ : Trace g idx₃ idx₄ ρ₂ ρ₃) :
|
||||||
Trace g idx₁ idx₄ ρ₁ ρ₃ := by
|
Trace g idx₁ idx₄ ρ₁ ρ₃ :=
|
||||||
induction tr₁ with
|
match tr₁ with
|
||||||
| single hbs => exact Trace.edge hbs he tr₂
|
| single hbs => edge hbs he tr₂
|
||||||
| edge hbs he' _ ih => exact Trace.edge hbs he' (ih he tr₂)
|
| edge hbs he' tr₁' => edge hbs he' (tr₁'.concat he tr₂)
|
||||||
|
|
||||||
scoped notation:65 tr₁:66 " ++< " he " >++ " tr₂:65 => Trace.concat tr₁ he tr₂
|
scoped notation:65 tr₁:66 " ++< " he " >++ " tr₂:65 => Trace.concat tr₁ he tr₂
|
||||||
|
|
||||||
|
def Trace.addEdge {g : Graph} {idx₁ idx₂ idx₃ : g.Index} {ρ₁ ρ₂ : Env} :
|
||||||
|
Trace g idx₁ idx₂ ρ₁ ρ₂ →
|
||||||
|
(idx₂, idx₃) ∈ g.edges →
|
||||||
|
Traceₗ g idx₁ idx₃ ρ₁ ρ₂
|
||||||
|
| .single hnode, hedge => .cons hnode hedge .nil
|
||||||
|
| .edge hnode hedge' rest, hedge => .cons hnode hedge' (rest.addEdge hedge)
|
||||||
|
|
||||||
|
@[aesop simp]
|
||||||
|
def Traceₗ.append {g : Graph} {idx₁ idx₂ idx₃ : g.Index} {ρ₁ ρ₂ ρ₃ : Env} :
|
||||||
|
Traceₗ g idx₁ idx₂ ρ₁ ρ₂ → Traceₗ g idx₂ idx₃ ρ₂ ρ₃ →
|
||||||
|
Traceₗ g idx₁ idx₃ ρ₁ ρ₃
|
||||||
|
| .nil, rhs => rhs
|
||||||
|
| .cons hnode hedge rest, rhs => .cons hnode hedge (rest.append rhs)
|
||||||
|
|
||||||
|
@[simp] def traceₗ_append_nil {g : Graph} {idx₁ idx₂ : g.Index} {ρ₁ ρ₂ : Env}
|
||||||
|
{trₗ : Traceₗ g idx₁ idx₂ ρ₁ ρ₂} : trₗ.append Traceₗ.nil = trₗ := by
|
||||||
|
induction trₗ <;> aesop
|
||||||
|
|
||||||
|
def Traceₗ.appendTrace {g : Graph} {idx₁ idx₂ idx₃ : g.Index} {ρ₁ ρ₂ ρ₃ : Env} :
|
||||||
|
Traceₗ g idx₁ idx₂ ρ₁ ρ₂ → Trace g idx₂ idx₃ ρ₂ ρ₃ →
|
||||||
|
Trace g idx₁ idx₃ ρ₁ ρ₃
|
||||||
|
| .nil, rhs => rhs
|
||||||
|
| .cons hnode hedge rest, rhs => .edge hnode hedge (rest.appendTrace rhs)
|
||||||
|
|
||||||
|
def Traceₗ.appendStep {g : Graph} {idx₁ idx₂ : g.Index} {ρ₁ ρ₂ ρ₃ : Env} :
|
||||||
|
Traceₗ g idx₁ idx₂ ρ₁ ρ₂ → EvalBasicStmtOpt ρ₂ (g.nodes idx₂) ρ₃ →
|
||||||
|
Trace g idx₁ idx₂ ρ₁ ρ₃ := fun trₗ hbs => trₗ.appendTrace (Trace.single hbs)
|
||||||
|
|
||||||
|
def Trace.appendRight {g : Graph} {idx₁ idx₂ idx₃ : g.Index} {ρ₁ ρ₂ ρ₃ : Env} :
|
||||||
|
Trace g idx₁ idx₂ ρ₁ ρ₂ → Traceᵣ g idx₂ idx₃ ρ₂ ρ₃ →
|
||||||
|
Trace g idx₁ idx₃ ρ₁ ρ₃
|
||||||
|
| lhs, .nil => lhs
|
||||||
|
| lhs, .cons rest hedge hnode => Trace.concat (lhs.appendRight rest) hedge (.single hnode)
|
||||||
|
|
||||||
|
instance instHAppendTraceLTraceL {g : Graph} {idx₁ idx₂ idx₃ : g.Index} {ρ₁ ρ₂ ρ₃ : Env} :
|
||||||
|
HAppend (Traceₗ g idx₁ idx₂ ρ₁ ρ₂) (Traceₗ g idx₂ idx₃ ρ₂ ρ₃) (Traceₗ g idx₁ idx₃ ρ₁ ρ₃) where
|
||||||
|
hAppend := Traceₗ.append
|
||||||
|
|
||||||
|
instance instHAppendTraceLTrace {g : Graph} {idx₁ idx₂ idx₃ : g.Index} {ρ₁ ρ₂ ρ₃ : Env} :
|
||||||
|
HAppend (Traceₗ g idx₁ idx₂ ρ₁ ρ₂) (Trace g idx₂ idx₃ ρ₂ ρ₃) (Trace g idx₁ idx₃ ρ₁ ρ₃) where
|
||||||
|
hAppend := Traceₗ.appendTrace
|
||||||
|
|
||||||
|
instance instHAppendTraceLStep {g : Graph} {idx₁ idx₂ : g.Index} {ρ₁ ρ₂ ρ₃ : Env} :
|
||||||
|
HAppend (Traceₗ g idx₁ idx₂ ρ₁ ρ₂) (EvalBasicStmtOpt ρ₂ (g.nodes idx₂) ρ₃) (Trace g idx₁ idx₂ ρ₁ ρ₃) where
|
||||||
|
hAppend := Traceₗ.appendStep
|
||||||
|
|
||||||
|
instance instHAppendTraceTraceR {g : Graph} {idx₁ idx₂ idx₃ : g.Index} {ρ₁ ρ₂ ρ₃ : Env} :
|
||||||
|
HAppend (Trace g idx₁ idx₂ ρ₁ ρ₂) (Traceᵣ g idx₂ idx₃ ρ₂ ρ₃) (Trace g idx₁ idx₃ ρ₁ ρ₃) where
|
||||||
|
hAppend := Trace.appendRight
|
||||||
|
|
||||||
|
/-!
|
||||||
|
|
||||||
|
## Trace Steps
|
||||||
|
|
||||||
|
Analyses that care about *which statements executed* (e.g. reaching
|
||||||
|
definitions) need to project a trace down to its list of executed statements.
|
||||||
|
Defining that projection here, once, as a chronological mathlib `List` means
|
||||||
|
all the re-association facts about concatenating traces come for free from
|
||||||
|
`List.append_assoc` and friends, instead of being re-proven per analysis. -/
|
||||||
|
|
||||||
|
/-- The (index, statement) pairs executed by a single optional-statement step:
|
||||||
|
none if the node is empty, and the node's statement otherwise. -/
|
||||||
|
def EvalBasicStmtOpt.steps {α : Type*} (idx : α) {ρ₁ ρ₂ : Env} {obs : Option BasicStmt} :
|
||||||
|
EvalBasicStmtOpt ρ₁ obs ρ₂ → List (α × BasicStmt)
|
||||||
|
| .none => []
|
||||||
|
| .some (bs := bs) _ => [(idx, bs)]
|
||||||
|
|
||||||
|
/-- The statements executed by a left-open trace, in chronological order. -/
|
||||||
|
def Traceₗ.steps {g : Graph} {idx₁ idx₂ : g.Index} {ρ₁ ρ₂ : Env} :
|
||||||
|
Traceₗ g idx₁ idx₂ ρ₁ ρ₂ → List (g.Index × BasicStmt)
|
||||||
|
| .nil => []
|
||||||
|
| .cons (idx₁ := idx) hnode _ rest => hnode.steps idx ++ rest.steps
|
||||||
|
|
||||||
|
/-- The statements executed by a trace, in chronological order. -/
|
||||||
|
def Trace.steps {g : Graph} {idx₁ idx₂ : g.Index} {ρ₁ ρ₂ : Env} :
|
||||||
|
Trace g idx₁ idx₂ ρ₁ ρ₂ → List (g.Index × BasicStmt)
|
||||||
|
| .single (idx := idx) hnode => hnode.steps idx
|
||||||
|
| .edge (idx₁ := idx) hnode _ rest => hnode.steps idx ++ rest.steps
|
||||||
|
|
||||||
|
@[simp] lemma Traceₗ.steps_append {g : Graph} {idx₁ idx₂ idx₃ : g.Index}
|
||||||
|
{ρ₁ ρ₂ ρ₃ : Env} (tr₁ : Traceₗ g idx₁ idx₂ ρ₁ ρ₂)
|
||||||
|
(tr₂ : Traceₗ g idx₂ idx₃ ρ₂ ρ₃) :
|
||||||
|
(tr₁ ++ tr₂).steps = tr₁.steps ++ tr₂.steps := by
|
||||||
|
show (tr₁.append tr₂).steps = _
|
||||||
|
induction tr₁ <;> simp [Traceₗ.append, Traceₗ.steps, *]
|
||||||
|
|
||||||
|
@[simp] lemma Traceₗ.steps_appendTrace {g : Graph} {idx₁ idx₂ idx₃ : g.Index}
|
||||||
|
{ρ₁ ρ₂ ρ₃ : Env} (tr₁ : Traceₗ g idx₁ idx₂ ρ₁ ρ₂)
|
||||||
|
(tr₂ : Trace g idx₂ idx₃ ρ₂ ρ₃) :
|
||||||
|
(tr₁ ++ tr₂).steps = tr₁.steps ++ tr₂.steps := by
|
||||||
|
show (tr₁.appendTrace tr₂).steps = _
|
||||||
|
induction tr₁ <;> simp [Traceₗ.appendTrace, Traceₗ.steps, Trace.steps, *]
|
||||||
|
|
||||||
|
@[simp] lemma Traceₗ.steps_appendStep {g : Graph} {idx₁ idx₂ : g.Index}
|
||||||
|
{ρ₁ ρ₂ ρ₃ : Env} (tr : Traceₗ g idx₁ idx₂ ρ₁ ρ₂)
|
||||||
|
(hbs : EvalBasicStmtOpt ρ₂ (g.nodes idx₂) ρ₃) :
|
||||||
|
(tr ++ hbs).steps = tr.steps ++ hbs.steps idx₂ :=
|
||||||
|
Traceₗ.steps_appendTrace tr (Trace.single hbs)
|
||||||
|
|
||||||
|
@[simp] lemma Trace.steps_addEdge {g : Graph} {idx₁ idx₂ idx₃ : g.Index}
|
||||||
|
{ρ₁ ρ₂ : Env} (tr : Trace g idx₁ idx₂ ρ₁ ρ₂)
|
||||||
|
(hedge : (idx₂, idx₃) ∈ g.edges) :
|
||||||
|
(tr.addEdge hedge).steps = tr.steps := by
|
||||||
|
induction tr <;> simp [Trace.addEdge, Trace.steps, Traceₗ.steps, *]
|
||||||
|
|
||||||
|
@[simp] lemma Traceₗ.append_addEdge {g : Graph}
|
||||||
|
{idx₁ idx₂ idx₃ idx₄ : g.Index} {ρ₁ ρ₂ ρ₃ ρ₄ : Env}
|
||||||
|
(trₗ : Traceₗ g idx₁ idx₂ ρ₁ ρ₂)
|
||||||
|
(hnode : EvalBasicStmtOpt ρ₂ (g.nodes idx₂) ρ₃)
|
||||||
|
(hedge : (idx₂, idx₃) ∈ g.edges)
|
||||||
|
(rest : Traceₗ g idx₃ idx₄ ρ₃ ρ₄) :
|
||||||
|
trₗ.append (Traceₗ.cons hnode hedge rest) =
|
||||||
|
(Trace.addEdge (trₗ.appendStep hnode) hedge).append rest := by
|
||||||
|
induction trₗ <;> simp [Traceₗ.append, Traceₗ.appendStep, Traceₗ.appendTrace, Trace.addEdge, *]
|
||||||
|
|
||||||
|
@[simp] lemma Traceₗ.appendTrace_addEdge {g : Graph}
|
||||||
|
{idx₁ idx₂ idx₃ idx₄ : g.Index} {ρ₁ ρ₂ ρ₃ ρ₄ : Env}
|
||||||
|
(trₗ : Traceₗ g idx₁ idx₂ ρ₁ ρ₂)
|
||||||
|
(hnode : EvalBasicStmtOpt ρ₂ (g.nodes idx₂) ρ₃)
|
||||||
|
(hedge : (idx₂, idx₃) ∈ g.edges)
|
||||||
|
(rest : Trace g idx₃ idx₄ ρ₃ ρ₄) :
|
||||||
|
trₗ.appendTrace (Trace.edge hnode hedge rest) =
|
||||||
|
(Trace.addEdge (trₗ.appendStep hnode) hedge).appendTrace rest := by
|
||||||
|
induction trₗ <;> simp [Traceₗ.appendTrace, Traceₗ.appendStep, Trace.addEdge, *]
|
||||||
|
|
||||||
/-- A beginning-to-end trace corresponding to the CFG `g`. -/
|
/-- A beginning-to-end trace corresponding to the CFG `g`. -/
|
||||||
inductive EndToEndTrace (g : Graph) (ρ₁ ρ₂ : Env) : Type
|
inductive EndToEndTrace (g : Graph) (ρ₁ ρ₂ : Env) : Type
|
||||||
| intro (idx₁ : g.Index) (idx₁_mem : idx₁ ∈ g.inputs)
|
| intro (idx₁ : g.Index) (idx₁_mem : idx₁ ∈ g.inputs)
|
||||||
(idx₂ : g.Index) (idx₂_mem : idx₂ ∈ g.outputs)
|
(idx₂ : g.Index) (idx₂_mem : idx₂ ∈ g.outputs)
|
||||||
(trace : Trace g idx₁ idx₂ ρ₁ ρ₂) : EndToEndTrace g ρ₁ ρ₂
|
(trace : Trace g idx₁ idx₂ ρ₁ ρ₂) : EndToEndTrace g ρ₁ ρ₂
|
||||||
|
|
||||||
|
inductive Reaches {prog : Program} : {s₁ s₂ : prog.State} → {ρ₁ ρ₂ : Env} →
|
||||||
|
Trace prog.cfg s₁ s₂ ρ₁ ρ₂ →
|
||||||
|
(s : prog.State) → (ρin ρout : Env) → Type
|
||||||
|
| single_here {s₁ : prog.State} {ρ₁ ρ₂ : Env}
|
||||||
|
(hnode : EvalBasicStmtOpt ρ₁ (prog.code s₁) ρ₂) :
|
||||||
|
Reaches (.single hnode) s₁ ρ₁ ρ₂
|
||||||
|
| edge_here {s₁ s₂ s₃ : prog.State} {ρ₁ ρ₂ ρ₃ : Env}
|
||||||
|
(hnode : EvalBasicStmtOpt ρ₁ (prog.code s₁) ρ₂)
|
||||||
|
(hedge : (s₁, s₂) ∈ prog.cfg.edges) (rest : Trace prog.cfg s₂ s₃ ρ₂ ρ₃) :
|
||||||
|
Reaches (.edge hnode hedge rest) s₁ ρ₁ ρ₂
|
||||||
|
| edge_there {s₁ s₂ s₃ : prog.State} {ρ₁ ρ₂ ρ₃ : Env}
|
||||||
|
(hnode : EvalBasicStmtOpt ρ₁ (prog.code s₁) ρ₂)
|
||||||
|
(hedge : (s₁, s₂) ∈ prog.cfg.edges) (rest : Trace prog.cfg s₂ s₃ ρ₂ ρ₃)
|
||||||
|
{s : prog.State} {ρin ρout : Env} :
|
||||||
|
Reaches rest s ρin ρout →
|
||||||
|
Reaches (.edge hnode hedge rest) s ρin ρout
|
||||||
|
|
||||||
|
def Reaches.pre {prog : Program} {s₁ s₂ s: prog.State}
|
||||||
|
{ρ₁ ρ₂ ρin ρout : Env} {tr : Trace prog.cfg s₁ s₂ ρ₁ ρ₂} :
|
||||||
|
(r : Reaches tr s ρin ρout) → Traceₗ prog.cfg s₁ s ρ₁ ρin
|
||||||
|
| .single_here _ => .nil
|
||||||
|
| .edge_here _ _ _ => .nil
|
||||||
|
| .edge_there hnode hedge _ r => .cons hnode hedge r.pre
|
||||||
|
|
||||||
|
def Reaches.post {prog : Program} {s₁ s₂ s: prog.State}
|
||||||
|
{ρ₁ ρ₂ ρin ρout : Env} {tr : Trace prog.cfg s₁ s₂ ρ₁ ρ₂} :
|
||||||
|
(r : Reaches tr s ρin ρout) → Trace prog.cfg s₁ s ρ₁ ρout
|
||||||
|
| .single_here hnode => .single hnode
|
||||||
|
| .edge_here hnode _ _ => .single hnode
|
||||||
|
| .edge_there hnode hedge _ r => .edge hnode hedge r.post
|
||||||
|
|
||||||
|
def Reaches.first {prog : Program} {s₁ s₂ s: prog.State}
|
||||||
|
{ρ₁ ρ₂ ρin ρout : Env} {tr : Trace prog.cfg s₁ s₂ ρ₁ ρ₂} :
|
||||||
|
(r : Reaches tr s ρin ρout) → Σ ρ₁', Reaches tr s₁ ρ₁ ρ₁'
|
||||||
|
| .single_here hnode => ⟨_, .single_here hnode⟩
|
||||||
|
| .edge_here hnode hedge hrest => ⟨_, .edge_here hnode hedge hrest⟩
|
||||||
|
| .edge_there hnode hedge hrest tmp' => ⟨_, .edge_here hnode hedge hrest⟩
|
||||||
|
|
||||||
|
def Reaches.step {prog : Program} {s₁ s₂ s: prog.State}
|
||||||
|
{ρ₁ ρ₂ ρin ρout : Env} {tr : Trace prog.cfg s₁ s₂ ρ₁ ρ₂} :
|
||||||
|
(r : Reaches tr s ρin ρout) → EvalBasicStmtOpt ρin (prog.code s) ρout
|
||||||
|
| .single_here hnode => hnode
|
||||||
|
| .edge_here hnode hedge hrest => hnode
|
||||||
|
| .edge_there hnode hedge hrest tmp' => tmp'.step
|
||||||
|
|
||||||
|
|
||||||
end Spa
|
end Spa
|
||||||
|
|||||||
@@ -1,7 +1,38 @@
|
|||||||
import Spa.Lattice
|
import Spa.Lattice
|
||||||
|
|
||||||
|
/-!
|
||||||
|
|
||||||
|
# The Above-Below Lattice
|
||||||
|
|
||||||
|
This file defines the `AboveBelow` lattice, which takes a flat domain
|
||||||
|
$a_1, \ldots, a_n \in \alpha$ and lifts it into a lattice bounded
|
||||||
|
above by a synthetic $\top$ element, and below by a synthetic $\bot$
|
||||||
|
element.
|
||||||
|
|
||||||
|
$$
|
||||||
|
\begin{array}{ccccc}
|
||||||
|
&& \top && \\
|
||||||
|
& \swarrow & \downarrow & \searrow & \\
|
||||||
|
a_1 & & … & & a_n \\
|
||||||
|
& \searrow & \downarrow & \swarrow & \\
|
||||||
|
&& \bot &&
|
||||||
|
\end{array}
|
||||||
|
$$
|
||||||
|
|
||||||
|
This lattice is also a `Spa.FiniteHeightLattice`, because no chain can
|
||||||
|
exceed the bottom-to-top chain $\bot < a_i < \top$.
|
||||||
|
|
||||||
|
The above-below lattice is helpful for for analyses such as
|
||||||
|
`Spa/Analysis/Sign.lean` and `Spa/Analysis/Constant.lean`, whose
|
||||||
|
classifications of values (by sign or by exact value) do not have
|
||||||
|
any inherent structure beyond "matching exactly".
|
||||||
|
|
||||||
|
-/
|
||||||
|
|
||||||
namespace Spa
|
namespace Spa
|
||||||
|
|
||||||
|
/-- The above-below lattice, with bottom element `bot` and top element `top`. -/
|
||||||
|
@[aesop safe cases]
|
||||||
inductive AboveBelow (α : Type*) where
|
inductive AboveBelow (α : Type*) where
|
||||||
| bot
|
| bot
|
||||||
| top
|
| top
|
||||||
@@ -10,8 +41,6 @@ inductive AboveBelow (α : Type*) where
|
|||||||
|
|
||||||
namespace AboveBelow
|
namespace AboveBelow
|
||||||
|
|
||||||
attribute [aesop safe cases] AboveBelow
|
|
||||||
|
|
||||||
instance {α : Type*} [ToString α] : ToString (AboveBelow α) where
|
instance {α : Type*} [ToString α] : ToString (AboveBelow α) where
|
||||||
toString
|
toString
|
||||||
| bot => "⊥"
|
| bot => "⊥"
|
||||||
@@ -50,23 +79,12 @@ instance : Min (AboveBelow α) where
|
|||||||
@[simp] lemma mk_inf_mk (x y : α) :
|
@[simp] lemma mk_inf_mk (x y : α) :
|
||||||
(mk x ⊓ mk y : AboveBelow α) = if x = y then mk x else bot := rfl
|
(mk x ⊓ mk y : AboveBelow α) = if x = y then mk x else bot := rfl
|
||||||
|
|
||||||
protected lemma sup_comm (a b : AboveBelow α) : a ⊔ b = b ⊔ a := by
|
protected lemma sup_comm (a b : AboveBelow α) : a ⊔ b = b ⊔ a := by aesop
|
||||||
aesop
|
protected lemma sup_assoc (a b c : AboveBelow α) : a ⊔ b ⊔ c = a ⊔ (b ⊔ c) := by aesop
|
||||||
|
protected lemma inf_comm (a b : AboveBelow α) : a ⊓ b = b ⊓ a := by aesop
|
||||||
protected lemma sup_assoc (a b c : AboveBelow α) : a ⊔ b ⊔ c = a ⊔ (b ⊔ c) := by
|
protected lemma inf_assoc (a b c : AboveBelow α) : a ⊓ b ⊓ c = a ⊓ (b ⊓ c) := by aesop
|
||||||
aesop
|
protected lemma sup_inf_self (a b : AboveBelow α) : a ⊔ a ⊓ b = a := by aesop
|
||||||
|
protected lemma inf_sup_self (a b : AboveBelow α) : a ⊓ (a ⊔ b) = a := by aesop
|
||||||
protected lemma inf_comm (a b : AboveBelow α) : a ⊓ b = b ⊓ a := by
|
|
||||||
aesop
|
|
||||||
|
|
||||||
protected lemma inf_assoc (a b c : AboveBelow α) : a ⊓ b ⊓ c = a ⊓ (b ⊓ c) := by
|
|
||||||
aesop
|
|
||||||
|
|
||||||
protected lemma sup_inf_self (a b : AboveBelow α) : a ⊔ a ⊓ b = a := by
|
|
||||||
aesop
|
|
||||||
|
|
||||||
protected lemma inf_sup_self (a b : AboveBelow α) : a ⊓ (a ⊔ b) = a := by
|
|
||||||
aesop
|
|
||||||
|
|
||||||
instance : Lattice (AboveBelow α) :=
|
instance : Lattice (AboveBelow α) :=
|
||||||
Lattice.mk' AboveBelow.sup_comm AboveBelow.sup_assoc
|
Lattice.mk' AboveBelow.sup_comm AboveBelow.sup_assoc
|
||||||
@@ -89,123 +107,72 @@ instance : OrderTop (AboveBelow α) where
|
|||||||
top := top
|
top := top
|
||||||
le_top := le_top'
|
le_top := le_top'
|
||||||
|
|
||||||
lemma bot_lt_mk (x : α) : (bot : AboveBelow α) < mk x :=
|
lemma bot_lt_mk (x : α) : (bot : AboveBelow α) < mk x := lt_of_le_of_ne (bot_le' _) (by simp)
|
||||||
lt_of_le_of_ne (bot_le' _) (by simp)
|
lemma mk_lt_top (x : α) : (mk x : AboveBelow α) < top := lt_of_le_of_ne (le_top' _) (by simp)
|
||||||
|
lemma bot_lt_top : (bot : AboveBelow α) < top := lt_of_le_of_ne (bot_le' _) (by simp)
|
||||||
lemma mk_lt_top (x : α) : (mk x : AboveBelow α) < top :=
|
|
||||||
lt_of_le_of_ne (le_top' _) (by simp)
|
|
||||||
|
|
||||||
lemma bot_lt_top : (bot : AboveBelow α) < top :=
|
|
||||||
lt_of_le_of_ne (bot_le' _) (by simp)
|
|
||||||
|
|
||||||
lemma le_cases {a b : AboveBelow α} (h : a ≤ b) :
|
lemma le_cases {a b : AboveBelow α} (h : a ≤ b) :
|
||||||
a = bot ∨ b = top ∨ a = b := by
|
a = bot ∨ b = top ∨ a = b := by
|
||||||
have hsup := le_iff.mp h
|
rw [le_iff] at h
|
||||||
rcases a with _ | _ | x <;> rcases b with _ | _ | y
|
rcases a with _ | _ | x <;> rcases b with _ | _ | y <;> simp_all
|
||||||
· exact Or.inl rfl
|
|
||||||
· exact Or.inr (Or.inl rfl)
|
|
||||||
· exact Or.inl rfl
|
|
||||||
· exact absurd hsup (by simp)
|
|
||||||
· exact Or.inr (Or.inl rfl)
|
|
||||||
· exact absurd hsup (by simp)
|
|
||||||
· exact absurd hsup (by simp)
|
|
||||||
· exact Or.inr (Or.inl rfl)
|
|
||||||
· rw [mk_sup_mk] at hsup
|
|
||||||
by_cases hxy : x = y
|
|
||||||
· exact Or.inr (Or.inr (by rw [hxy]))
|
|
||||||
· rw [if_neg hxy] at hsup
|
|
||||||
exact absurd hsup (by simp)
|
|
||||||
|
|
||||||
/-- Monotonicity for *strict* operations on flat lattices: if `f` sends `⊥` to
|
/-- If `f` sends `⊥` to `⊥` (in both arguments) and `⊤` to `⊤`
|
||||||
`⊥` (in either argument) and `⊤` to `⊤` (against any non-`⊥` argument), it is
|
(against any non-`⊥` argument), it is monotone in both arguments.
|
||||||
monotone in both arguments — regardless of its values on plain elements.
|
The values of the the elements in `α` are irrelevant since they
|
||||||
`Analysis/Sign.agda` and `Analysis/Constant.agda` postulated exactly these
|
are always incomparable. This makes it easy to prove monotonicity
|
||||||
monotonicity facts for their `plus`/`minus`, all of which have this shape. -/
|
for operations that "just" combine their flat elements, or give up. -/
|
||||||
lemma monotone₂_of_strict {β γ : Type*} [DecidableEq β] [DecidableEq γ]
|
lemma monotone₂_of_strict {β γ : Type*} [DecidableEq β] [DecidableEq γ]
|
||||||
(f : AboveBelow α → AboveBelow β → AboveBelow γ)
|
(f : AboveBelow α → AboveBelow β → AboveBelow γ)
|
||||||
(hbotl : ∀ y, f bot y = bot) (hbotr : ∀ x, f x bot = bot)
|
(hbotl : ∀ y, f bot y = bot) (hbotr : ∀ x, f x bot = bot)
|
||||||
(htopl : ∀ y, y ≠ bot → f top y = top)
|
(htopl : ∀ y, y ≠ bot → f top y = top)
|
||||||
(htopr : ∀ x, x ≠ bot → f x top = top) : Monotone₂ f := by
|
(htopr : ∀ x, x ≠ bot → f x top = top) : Monotone₂ f := by
|
||||||
constructor
|
constructor <;> intro c a b hab <;>
|
||||||
· intro y a b hab
|
rcases eq_or_ne c bot with rfl | hc <;>
|
||||||
show f a y ≤ f b y
|
rcases le_cases hab with rfl | rfl | rfl <;>
|
||||||
rcases le_cases hab with rfl | rfl | rfl
|
simp [hbotl, hbotr, htopl, htopr, bot_le', le_top', *]
|
||||||
· rw [hbotl]; exact bot_le' _
|
|
||||||
· rcases eq_or_ne y bot with rfl | hy
|
|
||||||
· rw [hbotr, hbotr]
|
|
||||||
· rw [htopl y hy]; exact le_top' _
|
|
||||||
· exact le_rfl
|
|
||||||
· intro x a b hab
|
|
||||||
show f x a ≤ f x b
|
|
||||||
rcases le_cases hab with rfl | rfl | rfl
|
|
||||||
· rw [hbotr]; exact bot_le' _
|
|
||||||
· rcases eq_or_ne x bot with rfl | hx
|
|
||||||
· rw [hbotl, hbotl]
|
|
||||||
· rw [htopr x hx]; exact le_top' _
|
|
||||||
· exact le_rfl
|
|
||||||
|
|
||||||
/-! ### Interpretations of flat lattices -/
|
|
||||||
|
|
||||||
section Interp
|
section Interp
|
||||||
|
|
||||||
variable {V : Type*} {P : AboveBelow α → V → Prop}
|
variable {V : Type*} {P : AboveBelow α → V → Prop}
|
||||||
|
|
||||||
|
/-- As long as the interpretation of a the above-below lattice respects the
|
||||||
|
fact that `bot` means "impossible", interpreting the above-below
|
||||||
|
lattice agrees with its `⊔`. -/
|
||||||
lemma interp_sup_of (hbot : ∀ v, ¬P bot v) (htop : ∀ v, P top v)
|
lemma interp_sup_of (hbot : ∀ v, ¬P bot v) (htop : ∀ v, P top v)
|
||||||
{s₁ s₂ : AboveBelow α} (v : V) (h : P s₁ v ∨ P s₂ v) : P (s₁ ⊔ s₂) v := by
|
{s₁ s₂ : AboveBelow α} (v : V) (h : P s₁ v ∨ P s₂ v) : P (s₁ ⊔ s₂) v := by aesop
|
||||||
rcases s₁ with _ | _ | x
|
|
||||||
· rw [bot_sup]; exact h.resolve_left (hbot v)
|
|
||||||
· rw [top_sup]; exact htop v
|
|
||||||
· rcases s₂ with _ | _ | y
|
|
||||||
· rw [sup_bot]; exact h.resolve_right (hbot v)
|
|
||||||
· rw [sup_top]; exact htop v
|
|
||||||
· rw [mk_sup_mk]
|
|
||||||
split
|
|
||||||
· next heq => subst heq; exact h.elim id id
|
|
||||||
· exact htop v
|
|
||||||
|
|
||||||
|
/-- As long as two distinct values in the flat domain don't overlap,
|
||||||
|
interpreting the above-below lattice agrees with its `⊔` -/
|
||||||
lemma interp_inf_of
|
lemma interp_inf_of
|
||||||
(hdisj : ∀ {x y : α}, x ≠ y → ∀ v, ¬(P (mk x) v ∧ P (mk y) v))
|
(hdisj : ∀ {x y : α}, x ≠ y → ∀ v, ¬(P (mk x) v ∧ P (mk y) v))
|
||||||
{s₁ s₂ : AboveBelow α} (v : V) (h : P s₁ v ∧ P s₂ v) : P (s₁ ⊓ s₂) v := by
|
{s₁ s₂ : AboveBelow α} (v : V) (h : P s₁ v ∧ P s₂ v) : P (s₁ ⊓ s₂) v := by
|
||||||
rcases s₁ with _ | _ | x
|
rcases s₁ with _ | _ | x <;> rcases s₂ with _ | _ | y <;> simp_all
|
||||||
· rw [bot_inf]; exact h.1
|
split
|
||||||
· rw [top_inf]; exact h.2
|
· exact h.2
|
||||||
· rcases s₂ with _ | _ | y
|
· next hne => exact (hdisj hne v h.1 h.2).elim
|
||||||
· rw [inf_bot]; exact h.2
|
|
||||||
· rw [inf_top]; exact h.1
|
|
||||||
· rw [mk_inf_mk]
|
|
||||||
split
|
|
||||||
· next heq => subst heq; exact h.1
|
|
||||||
· next hne => exact absurd h (hdisj hne v)
|
|
||||||
|
|
||||||
end Interp
|
end Interp
|
||||||
|
|
||||||
/-- Rank of an element: `⊥ ↦ 0`, `[x] ↦ 1`, `⊤ ↦ 2`. Used to bound chains
|
/-- synthetic rank of an element, used to prove chain bounds. -/
|
||||||
(Agda's `isLongest` / `x≺[y]⇒x≡⊥` / `[x]≺y⇒y≡⊤` case analysis lives here). -/
|
private def rank : AboveBelow α → ℕ
|
||||||
def rank : AboveBelow α → ℕ
|
|
||||||
| bot => 0
|
| bot => 0
|
||||||
| mk _ => 1
|
| mk _ => 1
|
||||||
| top => 2
|
| top => 2
|
||||||
|
|
||||||
/-- Agda: the impossibility of `[x] ≺ [y]` (combines `x≺[y]⇒x≡⊥` and
|
/-- It's not possible for any two lifted flat-domain elements to be less
|
||||||
`[x]≺y⇒y≡⊤`: the flat middle layer is an antichain). -/
|
than one another. -/
|
||||||
lemma not_mk_lt_mk (x y : α) : ¬(mk x : AboveBelow α) < mk y := by
|
lemma not_mk_lt_mk (x y : α) : ¬(mk x : AboveBelow α) < mk y := by
|
||||||
intro h
|
intro h
|
||||||
obtain ⟨hle, hne⟩ := lt_iff_le_and_ne.mp h
|
obtain ⟨hle, hne⟩ := lt_iff_le_and_ne.mp h
|
||||||
rcases le_cases hle with h | h | h <;> simp_all
|
rcases le_cases hle with h | h | h <;> simp_all
|
||||||
|
|
||||||
|
/-- The rank of elements is strictly monotonic. -/
|
||||||
lemma rank_strictMono : StrictMono (rank : AboveBelow α → ℕ) := by
|
lemma rank_strictMono : StrictMono (rank : AboveBelow α → ℕ) := by
|
||||||
intro a b hab
|
intro a b hab
|
||||||
rcases a with _ | _ | x <;> rcases b with _ | _ | y
|
rcases a with _ | _ | x <;> rcases b with _ | _ | y <;>
|
||||||
· exact absurd hab (lt_irrefl _)
|
simp_all [rank, not_mk_lt_mk, (bot_le' _).not_lt, (le_top' _).not_lt]
|
||||||
· simp [rank]
|
|
||||||
· simp [rank]
|
|
||||||
· exact absurd hab (bot_le' _).not_lt
|
|
||||||
· exact absurd hab (lt_irrefl _)
|
|
||||||
· exact absurd hab (le_top' _).not_lt
|
|
||||||
· exact absurd hab (bot_le' _).not_lt
|
|
||||||
· simp [rank]
|
|
||||||
· exact absurd hab (not_mk_lt_mk x y)
|
|
||||||
|
|
||||||
|
/-- All chains in the above-below lattice have at most 2 comparisons. -/
|
||||||
lemma boundedChains : BoundedChains (AboveBelow α) 2 := fun c => by
|
lemma boundedChains : BoundedChains (AboveBelow α) 2 := fun c => by
|
||||||
have h := LTSeries.head_add_length_le_nat (c.map rank rank_strictMono)
|
have h := LTSeries.head_add_length_le_nat (c.map rank rank_strictMono)
|
||||||
rw [LTSeries.head_map, LTSeries.last_map, LTSeries.map_length] at h
|
rw [LTSeries.head_map, LTSeries.last_map, LTSeries.map_length] at h
|
||||||
|
|||||||
@@ -1,64 +1,79 @@
|
|||||||
import Spa.Lattice.Tuple
|
import Spa.Lattice.Tuple
|
||||||
import Mathlib.Data.List.Nodup
|
import Mathlib.Data.List.Nodup
|
||||||
|
|
||||||
|
/-!
|
||||||
|
|
||||||
|
# Finite Maps
|
||||||
|
|
||||||
|
This file defines _finite maps_, or key-value maps with a finite domain. This
|
||||||
|
is encoded as a map from `Fin` into the value type. Finite maps form a
|
||||||
|
lattice from pointwise composition: $(f \land g) k = f k \land g k$,
|
||||||
|
and, provided the domain `\beta` is of finite height, so is the map
|
||||||
|
lattice as a whole.
|
||||||
|
|
||||||
|
In fact, the isomorphism is described and proven in `Spa/Lattice/Tuple.lean`.
|
||||||
|
|
||||||
|
-/
|
||||||
|
|
||||||
namespace Spa
|
namespace Spa
|
||||||
|
|
||||||
def FiniteMap (A B : Type*) (ks : List A) : Type _ := Fin ks.length → B
|
/-- Key-value map with domain `α` and codomain `β`, with possible keys $\textit{ks} \subseteq \alpha$. -/
|
||||||
|
def FiniteMap (α β : Type*) (ks : List α) : Type _ := Fin ks.length → β
|
||||||
|
|
||||||
namespace FiniteMap
|
namespace FiniteMap
|
||||||
|
|
||||||
variable {A B : Type*} {ks : List A}
|
variable {α β : Type*} {ks : List α}
|
||||||
|
|
||||||
instance [Lattice B] : Lattice (FiniteMap A B ks) :=
|
instance [Lattice β] : Lattice (FiniteMap α β ks) :=
|
||||||
inferInstanceAs (Lattice (Fin ks.length → B))
|
inferInstanceAs (Lattice (Fin ks.length → β))
|
||||||
|
|
||||||
instance [FiniteHeightLattice B] : FiniteHeightLattice (FiniteMap A B ks) :=
|
instance [FiniteHeightLattice β] : FiniteHeightLattice (FiniteMap α β ks) :=
|
||||||
inferInstanceAs (FiniteHeightLattice (Fin ks.length → B))
|
inferInstanceAs (FiniteHeightLattice (Fin ks.length → β))
|
||||||
|
|
||||||
instance [DecidableEq B] : DecidableEq (FiniteMap A B ks) :=
|
instance [DecidableEq β] : DecidableEq (FiniteMap α β ks) :=
|
||||||
inferInstanceAs (DecidableEq (Fin ks.length → B))
|
inferInstanceAs (DecidableEq (Fin ks.length → β))
|
||||||
|
|
||||||
instance : Membership (A × B) (FiniteMap A B ks) :=
|
instance : Membership (α × β) (FiniteMap α β ks) :=
|
||||||
⟨fun fm p => ∃ i : Fin ks.length, ks.get i = p.1 ∧ fm i = p.2⟩
|
⟨fun fm p => ∃ i : Fin ks.length, ks.get i = p.1 ∧ fm i = p.2⟩
|
||||||
|
|
||||||
lemma mem_iff {fm : FiniteMap A B ks} {p : A × B} :
|
lemma mem_iff {fm : FiniteMap α β ks} {p : α × β} :
|
||||||
p ∈ fm ↔ ∃ i : Fin ks.length, ks.get i = p.1 ∧ fm i = p.2 := Iff.rfl
|
p ∈ fm ↔ ∃ i : Fin ks.length, ks.get i = p.1 ∧ fm i = p.2 := Iff.rfl
|
||||||
|
|
||||||
def MemKey (k : A) (_fm : FiniteMap A B ks) : Prop := k ∈ ks
|
def MemKey (k : α) (_fm : FiniteMap α β ks) : Prop := k ∈ ks
|
||||||
|
|
||||||
lemma MemKey_iff {k : A} {fm : FiniteMap A B ks} : MemKey k fm ↔ k ∈ ks := Iff.rfl
|
lemma MemKey_iff {k : α} {fm : FiniteMap α β ks} : MemKey k fm ↔ k ∈ ks := Iff.rfl
|
||||||
|
|
||||||
instance {k : A} {fm : FiniteMap A B ks} [DecidableEq A] : Decidable (MemKey k fm) :=
|
instance {k : α} {fm : FiniteMap α β ks} [DecidableEq α] : Decidable (MemKey k fm) :=
|
||||||
decidable_of_iff _ MemKey_iff.symm
|
decidable_of_iff _ MemKey_iff.symm
|
||||||
|
|
||||||
lemma mem_key_of_mem {k : A} {v : B} {fm : FiniteMap A B ks}
|
lemma mem_key_of_mem {k : α} {v : β} {fm : FiniteMap α β ks}
|
||||||
(h : (k, v) ∈ fm) : MemKey k fm := by
|
(h : (k, v) ∈ fm) : MemKey k fm := by
|
||||||
obtain ⟨i, hi, _⟩ := h
|
obtain ⟨i, hi, _⟩ := h
|
||||||
have hik : ks.get i = k := hi
|
have hik : ks.get i = k := hi
|
||||||
exact hik ▸ ks.get_mem i
|
exact hik ▸ ks.get_mem i
|
||||||
|
|
||||||
def toList (fm : FiniteMap A B ks) : List (A × B) :=
|
def toList (fm : FiniteMap α β ks) : List (α × β) :=
|
||||||
(List.finRange ks.length).map fun i => (ks.get i, fm i)
|
(List.finRange ks.length).map fun i => (ks.get i, fm i)
|
||||||
|
|
||||||
lemma le_def [Lattice B] {fm₁ fm₂ : FiniteMap A B ks} :
|
lemma le_def [Lattice β] {fm₁ fm₂ : FiniteMap α β ks} :
|
||||||
fm₁ ≤ fm₂ ↔ ∀ i, fm₁ i ≤ fm₂ i := Iff.rfl
|
fm₁ ≤ fm₂ ↔ ∀ i, fm₁ i ≤ fm₂ i := Iff.rfl
|
||||||
|
|
||||||
section Locate
|
section Locate
|
||||||
|
|
||||||
variable [DecidableEq A]
|
variable [DecidableEq α]
|
||||||
|
|
||||||
/-- Recover the value stored under a present key. -/
|
/-- Recover the value stored under a present key. -/
|
||||||
def locate {k : A} {fm : FiniteMap A B ks} (h : MemKey k fm) :
|
def locate {k : α} {fm : FiniteMap α β ks} (h : MemKey k fm) :
|
||||||
{v : B // (k, v) ∈ fm} :=
|
{v : β // (k, v) ∈ fm} :=
|
||||||
let i : Fin ks.length := ⟨ks.idxOf k, List.idxOf_lt_length_iff.mpr h⟩
|
let i : Fin ks.length := ⟨ks.idxOf k, List.idxOf_lt_length_iff.mpr h⟩
|
||||||
⟨fm i, i, List.idxOf_get _, rfl⟩
|
⟨fm i, i, List.idxOf_get _, rfl⟩
|
||||||
|
|
||||||
end Locate
|
end Locate
|
||||||
|
|
||||||
variable [Lattice B]
|
variable [Lattice β]
|
||||||
|
|
||||||
lemma le_of_mem_mem (hks : ks.Nodup) {fm₁ fm₂ : FiniteMap A B ks}
|
lemma le_of_mem_mem (hks : ks.Nodup) {fm₁ fm₂ : FiniteMap α β ks}
|
||||||
(hle : fm₁ ≤ fm₂) {k : A} {v₁ v₂ : B}
|
(hle : fm₁ ≤ fm₂) {k : α} {v₁ v₂ : β}
|
||||||
(h₁ : (k, v₁) ∈ fm₁) (h₂ : (k, v₂) ∈ fm₂) : v₁ ≤ v₂ := by
|
(h₁ : (k, v₁) ∈ fm₁) (h₂ : (k, v₂) ∈ fm₂) : v₁ ≤ v₂ := by
|
||||||
obtain ⟨i, hi, rfl⟩ := h₁
|
obtain ⟨i, hi, rfl⟩ := h₁
|
||||||
obtain ⟨j, hj, rfl⟩ := h₂
|
obtain ⟨j, hj, rfl⟩ := h₂
|
||||||
@@ -66,13 +81,13 @@ lemma le_of_mem_mem (hks : ks.Nodup) {fm₁ fm₂ : FiniteMap A B ks}
|
|||||||
subst hij
|
subst hij
|
||||||
exact le_def.mp hle i
|
exact le_def.mp hle i
|
||||||
|
|
||||||
lemma mem_sup {fm₁ fm₂ : FiniteMap A B ks} {k : A} {v : B}
|
lemma mem_sup {fm₁ fm₂ : FiniteMap α β ks} {k : α} {v : β}
|
||||||
(h : (k, v) ∈ fm₁ ⊔ fm₂) :
|
(h : (k, v) ∈ fm₁ ⊔ fm₂) :
|
||||||
∃ v₁ v₂, v = v₁ ⊔ v₂ ∧ (k, v₁) ∈ fm₁ ∧ (k, v₂) ∈ fm₂ := by
|
∃ v₁ v₂, v = v₁ ⊔ v₂ ∧ (k, v₁) ∈ fm₁ ∧ (k, v₂) ∈ fm₂ := by
|
||||||
obtain ⟨i, hi, rfl⟩ := h
|
obtain ⟨i, hi, rfl⟩ := h
|
||||||
exact ⟨fm₁ i, fm₂ i, rfl, ⟨i, hi, rfl⟩, ⟨i, hi, rfl⟩⟩
|
exact ⟨fm₁ i, fm₂ i, rfl, ⟨i, hi, rfl⟩, ⟨i, hi, rfl⟩⟩
|
||||||
|
|
||||||
lemma mem_inf {fm₁ fm₂ : FiniteMap A B ks} {k : A} {v : B}
|
lemma mem_inf {fm₁ fm₂ : FiniteMap α β ks} {k : α} {v : β}
|
||||||
(h : (k, v) ∈ fm₁ ⊓ fm₂) :
|
(h : (k, v) ∈ fm₁ ⊓ fm₂) :
|
||||||
∃ v₁ v₂, v = v₁ ⊓ v₂ ∧ (k, v₁) ∈ fm₁ ∧ (k, v₂) ∈ fm₂ := by
|
∃ v₁ v₂, v = v₁ ⊓ v₂ ∧ (k, v₁) ∈ fm₁ ∧ (k, v₂) ∈ fm₂ := by
|
||||||
obtain ⟨i, hi, rfl⟩ := h
|
obtain ⟨i, hi, rfl⟩ := h
|
||||||
@@ -80,30 +95,30 @@ lemma mem_inf {fm₁ fm₂ : FiniteMap A B ks} {k : A} {v : B}
|
|||||||
|
|
||||||
section Updating
|
section Updating
|
||||||
|
|
||||||
variable [DecidableEq A]
|
variable [DecidableEq α]
|
||||||
|
|
||||||
def updating (fm : FiniteMap A B ks) (ks' : List A) (g : A → B) : FiniteMap A B ks :=
|
def updating (fm : FiniteMap α β ks) (ks' : List α) (g : α → β) : FiniteMap α β ks :=
|
||||||
fun i => if ks.get i ∈ ks' then g (ks.get i) else fm i
|
fun i => if ks.get i ∈ ks' then g (ks.get i) else fm i
|
||||||
|
|
||||||
omit [Lattice B] in
|
omit [Lattice β] in
|
||||||
lemma eq_of_mem_updating {k : A} {v : B} {fm : FiniteMap A B ks}
|
lemma eq_of_mem_updating {k : α} {v : β} {fm : FiniteMap α β ks}
|
||||||
{ks' : List A} {g : A → B} (hk : k ∈ ks')
|
{ks' : List α} {g : α → β} (hk : k ∈ ks')
|
||||||
(h : (k, v) ∈ updating fm ks' g) : v = g k := by
|
(h : (k, v) ∈ updating fm ks' g) : v = g k := by
|
||||||
obtain ⟨i, hi, rfl⟩ := h
|
obtain ⟨i, hi, rfl⟩ := h
|
||||||
show (if ks.get i ∈ ks' then g (ks.get i) else fm i) = g k
|
show (if ks.get i ∈ ks' then g (ks.get i) else fm i) = g k
|
||||||
rw [if_pos (by rw [hi]; exact hk), hi]
|
rw [if_pos (by rw [hi]; exact hk), hi]
|
||||||
|
|
||||||
omit [Lattice B] in
|
omit [Lattice β] in
|
||||||
lemma mem_of_mem_updating {k : A} {v : B} {fm : FiniteMap A B ks}
|
lemma mem_of_mem_updating {k : α} {v : β} {fm : FiniteMap α β ks}
|
||||||
{ks' : List A} {g : A → B} (hk : k ∉ ks')
|
{ks' : List α} {g : α → β} (hk : k ∉ ks')
|
||||||
(h : (k, v) ∈ updating fm ks' g) : (k, v) ∈ fm := by
|
(h : (k, v) ∈ updating fm ks' g) : (k, v) ∈ fm := by
|
||||||
obtain ⟨i, hi, rfl⟩ := h
|
obtain ⟨i, hi, rfl⟩ := h
|
||||||
refine ⟨i, hi, ?_⟩
|
refine ⟨i, hi, ?_⟩
|
||||||
show fm i = (if ks.get i ∈ ks' then g (ks.get i) else fm i)
|
show fm i = (if ks.get i ∈ ks' then g (ks.get i) else fm i)
|
||||||
rw [if_neg (by rw [hi]; exact hk)]
|
rw [if_neg (by rw [hi]; exact hk)]
|
||||||
|
|
||||||
lemma updating_mono {fm₁ fm₂ : FiniteMap A B ks} {ks' : List A}
|
lemma updating_mono {fm₁ fm₂ : FiniteMap α β ks} {ks' : List α}
|
||||||
{g₁ g₂ : A → B} (hfm : fm₁ ≤ fm₂) (hg : ∀ k, g₁ k ≤ g₂ k) :
|
{g₁ g₂ : α → β} (hfm : fm₁ ≤ fm₂) (hg : ∀ k, g₁ k ≤ g₂ k) :
|
||||||
updating fm₁ ks' g₁ ≤ updating fm₂ ks' g₂ := by
|
updating fm₁ ks' g₁ ≤ updating fm₂ ks' g₂ := by
|
||||||
rw [le_def]
|
rw [le_def]
|
||||||
intro i
|
intro i
|
||||||
@@ -117,25 +132,25 @@ end Updating
|
|||||||
|
|
||||||
section GeneralizedUpdate
|
section GeneralizedUpdate
|
||||||
|
|
||||||
variable [DecidableEq A] {L : Type*} [Lattice L]
|
variable [DecidableEq α] {L : Type*} [Lattice L]
|
||||||
|
|
||||||
def generalizedUpdate (f : L → FiniteMap A B ks) (g : A → L → B)
|
def generalizedUpdate (f : L → FiniteMap α β ks) (g : α → L → β)
|
||||||
(ks' : List A) : L → FiniteMap A B ks := fun l =>
|
(ks' : List α) : L → FiniteMap α β ks := fun l =>
|
||||||
(f l).updating ks' (fun k => g k l)
|
(f l).updating ks' (fun k => g k l)
|
||||||
|
|
||||||
variable {f : L → FiniteMap A B ks} {g : A → L → B} {ks' : List A}
|
variable {f : L → FiniteMap α β ks} {g : α → L → β} {ks' : List α}
|
||||||
|
|
||||||
lemma generalizedUpdate_monotone (hf : Monotone f)
|
lemma generalizedUpdate_monotone (hf : Monotone f)
|
||||||
(hg : ∀ k, Monotone (g k)) : Monotone (generalizedUpdate f g ks') :=
|
(hg : ∀ k, Monotone (g k)) : Monotone (generalizedUpdate f g ks') :=
|
||||||
fun _ _ hl => updating_mono (hf hl) (fun k => hg k hl)
|
fun _ _ hl => updating_mono (hf hl) (fun k => hg k hl)
|
||||||
|
|
||||||
omit [Lattice B] [Lattice L] in
|
omit [Lattice β] [Lattice L] in
|
||||||
lemma generalizedUpdate_mem_eq {k : A} {v : B} {l : L} (hk : k ∈ ks')
|
lemma generalizedUpdate_mem_eq {k : α} {v : β} {l : L} (hk : k ∈ ks')
|
||||||
(h : (k, v) ∈ generalizedUpdate f g ks' l) : v = g k l :=
|
(h : (k, v) ∈ generalizedUpdate f g ks' l) : v = g k l :=
|
||||||
eq_of_mem_updating (g := fun k => g k l) hk h
|
eq_of_mem_updating (g := fun k => g k l) hk h
|
||||||
|
|
||||||
omit [Lattice B] [Lattice L] in
|
omit [Lattice β] [Lattice L] in
|
||||||
lemma generalizedUpdate_not_mem_backward {k : A} {v : B} {l : L} (hk : k ∉ ks')
|
lemma generalizedUpdate_not_mem_backward {k : α} {v : β} {l : L} (hk : k ∉ ks')
|
||||||
(h : (k, v) ∈ generalizedUpdate f g ks' l) : (k, v) ∈ f l :=
|
(h : (k, v) ∈ generalizedUpdate f g ks' l) : (k, v) ∈ f l :=
|
||||||
mem_of_mem_updating hk h
|
mem_of_mem_updating hk h
|
||||||
|
|
||||||
@@ -143,19 +158,19 @@ end GeneralizedUpdate
|
|||||||
|
|
||||||
section ValuesAt
|
section ValuesAt
|
||||||
|
|
||||||
variable [DecidableEq A]
|
variable [DecidableEq α]
|
||||||
|
|
||||||
/-- The value stored under `k`, if `k` is a key. -/
|
/-- The value stored under `k`, if `k` is a key. -/
|
||||||
private def lookup (fm : FiniteMap A B ks) (k : A) : Option B :=
|
private def lookup (fm : FiniteMap α β ks) (k : α) : Option β :=
|
||||||
if h : k ∈ ks then some (fm ⟨ks.idxOf k, List.idxOf_lt_length_iff.mpr h⟩) else none
|
if h : k ∈ ks then some (fm ⟨ks.idxOf k, List.idxOf_lt_length_iff.mpr h⟩) else none
|
||||||
|
|
||||||
/-- The values stored under the keys `ks'` (skipping any that are not keys). -/
|
/-- The values stored under the keys `ks'` (skipping any that are not keys). -/
|
||||||
def valuesAt (fm : FiniteMap A B ks) (ks' : List A) : List B :=
|
def valuesAt (fm : FiniteMap α β ks) (ks' : List α) : List β :=
|
||||||
ks'.filterMap fm.lookup
|
ks'.filterMap fm.lookup
|
||||||
|
|
||||||
omit [Lattice B] in
|
omit [Lattice β] in
|
||||||
lemma mem_valuesAt (hks : ks.Nodup) {fm : FiniteMap A B ks} {k : A} {v : B}
|
lemma mem_valuesAt (hks : ks.Nodup) {fm : FiniteMap α β ks} {k : α} {v : β}
|
||||||
{ks' : List A} (hk : k ∈ ks') (h : (k, v) ∈ fm) : v ∈ valuesAt fm ks' := by
|
{ks' : List α} (hk : k ∈ ks') (h : (k, v) ∈ fm) : v ∈ valuesAt fm ks' := by
|
||||||
refine List.mem_filterMap.mpr ⟨k, hk, ?_⟩
|
refine List.mem_filterMap.mpr ⟨k, hk, ?_⟩
|
||||||
obtain ⟨i, hi, rfl⟩ := h
|
obtain ⟨i, hi, rfl⟩ := h
|
||||||
have hik : ks.get i = k := hi
|
have hik : ks.get i = k := hi
|
||||||
@@ -167,7 +182,7 @@ lemma mem_valuesAt (hks : ks.Nodup) {fm : FiniteMap A B ks} {k : A} {v : B}
|
|||||||
hks.get_inj_iff.mp (by rw [List.idxOf_get, hi])
|
hks.get_inj_iff.mp (by rw [List.idxOf_get, hi])
|
||||||
rw [this]
|
rw [this]
|
||||||
|
|
||||||
private lemma lookup_rel {fm₁ fm₂ : FiniteMap A B ks} (hle : fm₁ ≤ fm₂) (k : A) :
|
private lemma lookup_rel {fm₁ fm₂ : FiniteMap α β ks} (hle : fm₁ ≤ fm₂) (k : α) :
|
||||||
Option.Rel (· ≤ ·) (fm₁.lookup k) (fm₂.lookup k) := by
|
Option.Rel (· ≤ ·) (fm₁.lookup k) (fm₂.lookup k) := by
|
||||||
show Option.Rel _
|
show Option.Rel _
|
||||||
(if h : k ∈ ks then some (fm₁ ⟨ks.idxOf k, List.idxOf_lt_length_iff.mpr h⟩) else none)
|
(if h : k ∈ ks then some (fm₁ ⟨ks.idxOf k, List.idxOf_lt_length_iff.mpr h⟩) else none)
|
||||||
@@ -176,8 +191,8 @@ private lemma lookup_rel {fm₁ fm₂ : FiniteMap A B ks} (hle : fm₁ ≤ fm₂
|
|||||||
· rw [dif_pos hk, dif_pos hk]; exact Option.Rel.some (le_def.mp hle _)
|
· rw [dif_pos hk, dif_pos hk]; exact Option.Rel.some (le_def.mp hle _)
|
||||||
· rw [dif_neg hk, dif_neg hk]; exact Option.Rel.none
|
· rw [dif_neg hk, dif_neg hk]; exact Option.Rel.none
|
||||||
|
|
||||||
lemma valuesAt_le {fm₁ fm₂ : FiniteMap A B ks} (hle : fm₁ ≤ fm₂)
|
lemma valuesAt_le {fm₁ fm₂ : FiniteMap α β ks} (hle : fm₁ ≤ fm₂)
|
||||||
(ks' : List A) :
|
(ks' : List α) :
|
||||||
List.Forall₂ (· ≤ ·) (valuesAt fm₁ ks') (valuesAt fm₂ ks') := by
|
List.Forall₂ (· ≤ ·) (valuesAt fm₁ ks') (valuesAt fm₂ ks') := by
|
||||||
induction ks' with
|
induction ks' with
|
||||||
| nil => exact List.Forall₂.nil
|
| nil => exact List.Forall₂.nil
|
||||||
|
|||||||
38
lean/Spa/Lattice/Finset.lean
Normal file
38
lean/Spa/Lattice/Finset.lean
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import Spa.Lattice
|
||||||
|
import Mathlib.Data.Finset.Lattice.Basic
|
||||||
|
import Mathlib.Data.Fintype.Lattice
|
||||||
|
import Mathlib.Data.Fintype.Card
|
||||||
|
|
||||||
|
/-! # Power Sets of Finite Type
|
||||||
|
|
||||||
|
For a `Fintype α`, `Finset α` is the power-set lattice: `⊔` is union, `⊓` is
|
||||||
|
intersection, `⊥ = ∅`, `⊤ = univ`. This lattice also has a finite height.
|
||||||
|
|
||||||
|
The `Finset α` representation s isomorphic to `Fin α → Bool`, but far more
|
||||||
|
efficient because it avoids building up stacks of layered closures. -/
|
||||||
|
|
||||||
|
namespace Spa
|
||||||
|
|
||||||
|
variable {α : Type*} [Fintype α] [DecidableEq α]
|
||||||
|
|
||||||
|
omit [Fintype α] [DecidableEq α] in
|
||||||
|
private lemma finset_card_strictMono : StrictMono (Finset.card : Finset α → ℕ) :=
|
||||||
|
fun _ _ h => Finset.card_lt_card h
|
||||||
|
|
||||||
|
omit [DecidableEq α] in
|
||||||
|
/-- A strictly increasing chain of finsets grows its cardinality by at least one
|
||||||
|
each step, and cardinality is capped by `Fintype.card α`. -/
|
||||||
|
lemma finset_boundedChains : BoundedChains (Finset α) (Fintype.card α) := fun c => by
|
||||||
|
have h := LTSeries.head_add_length_le_nat (c.map Finset.card finset_card_strictMono)
|
||||||
|
rw [LTSeries.head_map, LTSeries.last_map, LTSeries.map_length] at h
|
||||||
|
have h2 : c.last.card ≤ Fintype.card α := Finset.card_le_univ _
|
||||||
|
omega
|
||||||
|
|
||||||
|
instance instFiniteHeightFinset : FiniteHeightLattice (Finset α) where
|
||||||
|
toLattice := inferInstance
|
||||||
|
toOrderBot := inferInstance
|
||||||
|
toOrderTop := inferInstance
|
||||||
|
height := Fintype.card α
|
||||||
|
chains_bounded := finset_boundedChains
|
||||||
|
|
||||||
|
end Spa
|
||||||
@@ -65,10 +65,10 @@ def lookupDef (prog : Program) (vs : VariableValues (DefSet prog) prog)
|
|||||||
(k : String) : DefSet prog :=
|
(k : String) : DefSet prog :=
|
||||||
if h : FiniteMap.MemKey k vs then (FiniteMap.locate h).1 else ⊥
|
if h : FiniteMap.MemKey k vs then (FiniteMap.locate h).1 else ⊥
|
||||||
|
|
||||||
/-- The AST node ids marked as definition sites in a `DefSet` (those mapped to
|
/-- The AST node ids marked as definition sites in a `DefSet`. With the
|
||||||
`true`). With the AST-id-keyed lattice these are recovered directly. -/
|
`Finset`-of-AST-ids lattice these are just the elements of the set. -/
|
||||||
def defSites (prog : Program) (d : DefSet prog) : List prog.NodeId :=
|
def defSites (prog : Program) (d : DefSet prog) : List prog.NodeId :=
|
||||||
(List.finRange prog.size).filter (fun i => d i)
|
(List.finRange prog.size).filter (fun i => decide (i ∈ d))
|
||||||
|
|
||||||
/-- Is the candidate assignment loop-invariant: do all reaching definitions of
|
/-- Is the candidate assignment loop-invariant: do all reaching definitions of
|
||||||
its RHS variables lie outside the loop body? Reaching sets are now keyed by AST
|
its RHS variables lie outside the loop body? Reaching sets are now keyed by AST
|
||||||
|
|||||||
Reference in New Issue
Block a user