17 Commits

Author SHA1 Message Date
904f6375be Consolidate per-operator trace lifting into GGraph.Embed + Trace.embed
Each graph-composition operator includes its operands via an index
translation preserving node payloads and edges. Capture that once as
GGraph.Embed (a structure, not a class: for g ; g both inclusions share
the type Embed g (g <~> g), so instance resolution could pick the wrong
copy) with five named witnesses, and replace the five structurally
identical trace-lifting inductions in Properties.lean with a single
generic Trace.embed plus one-line corollaries.

The same witnesses' nodes_eq fields will back the upcoming AST-id/CFG
label bijection, so the per-operator content is stated exactly once.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 15:06:40 -05:00
8cd053a242 Migrate Reaching.lean to projections via a generic Trace.steps
Finish the projection migration for reaching definitions by replacing the
accumulator-style runOfTrace*From definitions and their hand-rolled
re-association lemmas with a single analysis-agnostic projection:
Trace.steps / Traceₗ.steps, the chronological List of executed
(index, statement) pairs. Its four simp lemmas are one-line inductions,
with all re-association falling out of mathlib's List.append_assoc and
List.reverse_append.

Run is now an abbrev for List (State × BasicStmt) (latest-first, so
LastAssign keeps its first-match structure) and runOfTrace is just
steps.reverse.

Also hoist the generic reaches_final_post into Forward.lean, letting
analyze_correct' be stated directly about S.Post (prog.trace hrun).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 09:01:09 -05:00
0e6976f9b4 Migrate most of the codebase (sans Reaching.lean / LICM left) to projections 2026-07-01 22:56:29 -05:00
10b8fa97ca Add left-and-right open traces to help formalization
Co-Authored-By: OpenAI Codex <codex@openai.com>
2026-07-01 19:27:06 -05:00
8ed48cf444 Add non-state parameterized 'Reaches' relation 2026-07-01 13:02:39 -05:00
37d88f070a Remove 'prog.code s = some bs' argument to eval 2026-06-30 23:21:00 -05:00
6c05e401c1 Document Program.lean 2026-06-29 10:42:01 -05:00
fe5098095a Reorganize proofs to make 'Program' accessible to files in Language/ 2026-06-29 10:41:40 -05:00
59afbdaf71 Rename StateInterp to match the style of the rest of the codebase 2026-06-29 10:00:01 -05:00
490c472d22 Start documenting FiniteMap.lean 2026-06-29 08:59:51 -05:00
d1a11a9b2c Use alpha and beta for FiniteMap type variables 2026-06-29 08:59:37 -05:00
2598df690c Slightly tweak AboveBelow proofs 2026-06-29 08:16:55 -05:00
47d54f5b4b Further simplify proofs in AboveBelow
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 15:01:24 -05:00
8fa822b2e6 Improve performance by caching CFG building
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 14:39:14 -05:00
d66a7d0e3e Clean up and document AboveBelow 2026-06-28 14:38:12 -05:00
778e974dfb Prove that analysis results apply to all states, not just the final one
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 14:24:46 -05:00
319fa272ac Switch Reaching analysis to use Finset for more efficiency
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 09:46:54 -05:00
17 changed files with 870 additions and 500 deletions

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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