Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
d313d3a
feat: Add lemmas about `setArgumentValues?` and the equation lemmas
math-fehr Jun 12, 2026
7a8a64b
math-fehr Jun 19, 2026
4048468
math-fehr Jun 20, 2026
f27237c
math-fehr Jun 20, 2026
63a3d0e
feat(soundness): bridge fromLocalRewrite to RewrittenAt (keystone + b…
math-fehr Jun 21, 2026
820a35f
feat(soundness): prove survives/opErased/blocksInBounds/regionsInBoun…
math-fehr Jun 21, 2026
e291149
feat(soundness): prove newOpsFresh and mappingFixesNonResults fields
math-fehr Jun 21, 2026
a927f6a
feat(soundness): prove mapNonResultsInBounds field
math-fehr Jun 21, 2026
d06bc74
docs(soundness): categorize remaining RewrittenAt sorries
math-fehr Jun 21, 2026
223395f
math-fehr Jun 25, 2026
2377b3c
math-fehr Jun 25, 2026
72ab4f7
math-fehr Jun 26, 2026
4eed63d
math-fehr Jun 28, 2026
622174d
Done refactoring with isRefinedByAt
math-fehr Jun 28, 2026
5146978
A few less fields
math-fehr Jun 29, 2026
5073f77
Remove two sorries from lifting lemma
math-fehr Jun 29, 2026
9db3068
remove bad constraint
math-fehr Jun 30, 2026
70d0b62
One less sorry
math-fehr Jun 30, 2026
3e1d631
One less sorry
math-fehr Jun 30, 2026
5af4029
One less sorry
math-fehr Jun 30, 2026
a5a8d45
math-fehr Jun 30, 2026
f45dc66
Last sorry
math-fehr Jun 30, 2026
8990654
Removing sorries from fromLocalRewrite
math-fehr Jun 30, 2026
2c86168
One less argument in fromLocalRewrite soundness
math-fehr Jun 30, 2026
810badb
Check for parent in rewriter
math-fehr Jun 30, 2026
ff25aa4
Preservation of parent
math-fehr Jun 30, 2026
6dc7068
math-fehr Jun 30, 2026
bef7d1a
Thread EquationLemmaAt
math-fehr Jul 1, 2026
d48a8cd
Step 2 of last proof
math-fehr Jul 1, 2026
5c0a8d5
Magic
math-fehr Jul 1, 2026
f9991b8
Done modulus 3 axioms
math-fehr Jul 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Veir.lean
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Veir.Rewriter.WfRewriter
import Veir.Printer
import Veir.PatternRewriter.Basic
import Veir.Passes.RISCVCombines.Proofs
import Veir.PatternRewriter.Soundness
import Veir.Benchmarks
import Veir.Parser.Lexer
import Veir.Interpreter
Expand Down
98 changes: 98 additions & 0 deletions Veir/Dominance.lean
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,101 @@ it dominates the program point before the operation, or it is a result of the op
axiom WfIRContext.Dom.value_dominatesIp_after_iff (ctxDom : ctx.Dom) :
value.dominatesIp (InsertPoint.after op ctx.raw block blockIsParent opInBounds) ctx ↔
value.dominatesIp (InsertPoint.before op) ctx ∨ value ∈ op.getResults! ctx.raw

/-- A value dominating the entry of a successor block either already dominates the predecessor's
end, or it is one of the successor's own block arguments. -/
axiom WfIRContext.Dom.value_dominatesIp_successor_entry (ctxDom : ctx.Dom)
{block : BlockPtr} (blockInBounds : block.InBounds ctx.raw)
(hsucc : succ ∈ block.getSuccessors! ctx.raw) :
value.dominatesIp (InsertPoint.atStart! succ ctx.raw) ctx →
value.dominatesIp (InsertPoint.atEnd block) ctx ∨
value ∈ succ.getArguments! ctx.raw

/-- An operation dominating the entry of a successor already dominates the predecessor's end. -/
axiom WfIRContext.Dom.op_dominatesIp_successor_entry (ctxDom : ctx.Dom)
{block : BlockPtr} (blockInBounds : block.InBounds ctx.raw)
(hsucc : succ ∈ block.getSuccessors! ctx.raw) :
op.dominatesIp (InsertPoint.atStart! succ ctx.raw) ctx →
op.dominatesIp (InsertPoint.atEnd block) ctx

/-- An argument of a block dominates the block's start. -/
axiom WfIRContext.Dom.blockArgument_dominatesIp_entry (ctxDom : ctx.Dom)
{block : BlockPtr} (blockInBounds : block.InBounds ctx.raw)
(hMem : value ∈ block.getArguments! ctx.raw) :
value.dominatesIp (InsertPoint.atStart! block ctx.raw) ctx

/-- An argument of a block cannot dominate a program point that dominates the block start. -/
axiom WfIRContext.Dom.blockArgument_not_dominatesIp_before_of_dominatesIp_firstOp
(ctxDom : ctx.Dom) {op : OperationPtr} (opInBounds : op.InBounds ctx.raw)
(opDom : op.dominatesIp (InsertPoint.atStart! block ctx.raw) ctx)
(hMem : value ∈ block.getArguments! ctx.raw) :
¬ value.dominatesIp (InsertPoint.before op) ctx

/-- A block argument dominates the end of its own block: it is in scope throughout the block body. -/
axiom WfIRContext.Dom.blockArgument_dominatesIp_atEnd
(ctxDom : ctx.Dom) {block : BlockPtr} (blockIn : block.InBounds ctx.raw)
(hMem : value ∈ block.getArguments! ctx.raw) :
value.dominatesIp (InsertPoint.atEnd block) ctx

/-- A result of an operation in a block body does not dominate that block's entry (the SSA
property: results are defined strictly inside the block, not before it). -/
axiom WfIRContext.Dom.opResult_not_dominatesIp_atStart!
(ctxDom : ctx.Dom) {op : OperationPtr} (opIn : op.InBounds ctx.raw)
{block : BlockPtr} (blockIn : block.InBounds ctx.raw)
(opInBlock : op ∈ block.operationList ctx.raw ctx.wellFormed blockIn)
{r : ValuePtr} (rResult : r ∈ op.getResults! ctx.raw) :
¬ r.dominatesIp (InsertPoint.atStart! block ctx.raw) ctx

/-- SSA antisymmetry of the definition order, used to justify op-result *forwarding* in the pattern
rewriter. Two distinct operations cannot each be defined before the other: if a result of `op₁`
dominates the point before `op₂`, then a result of `op₂` cannot dominate the point before `op₁`
(that would mean `op₁` strictly dominates `op₂` and `op₂` strictly dominates `op₁`). This is what
rules out the only would-be `ReflectsResults o o` collision when a rewrite redirects `op`'s result
onto a result of a surviving operation `o`: `op`'s own result cannot dominate `.before o` while
`o`'s forwarded result dominates `.before op`. -/
axiom WfIRContext.Dom.not_opResult_dominatesIp_before_cycle
(ctxDom : ctx.Dom) {op₁ op₂ : OperationPtr} (hne : op₁ ≠ op₂)
{r₁ : ValuePtr} (r₁Res : r₁ ∈ op₁.getResults! ctx.raw)
(r₁Dom : r₁.dominatesIp (InsertPoint.before op₂) ctx)
{r₂ : ValuePtr} (r₂Res : r₂ ∈ op₂.getResults! ctx.raw)
(r₂Dom : r₂.dominatesIp (InsertPoint.before op₁) ctx) :
False

/-!
## Block-level dominance

Dominance between blocks (the entry of `b₁` dominates every point of `b₂`). Used to discharge the
cross-block antisymmetry argument in the pattern-rewriter soundness proof: a value forwarded by a
rewrite cannot become an argument of a *different* block.
-/

variable {b₁ b₂ block bl : BlockPtr}

/-- The dominance relation between two blocks: `b₁` dominates `b₂` when `b₁`'s entry dominates every
program point of `b₂`. A block dominates itself. -/
axiom BlockPtr.dominates (b₁ b₂ : BlockPtr) (ctx : WfIRContext OpInfo) : Prop

/-- Block dominance is antisymmetric: two blocks that dominate each other are equal. -/
axiom BlockPtr.dominates_antisymm :
b₁.dominates b₂ ctx → b₂.dominates b₁ ctx → b₁ = b₂

/-- If a result `r` of an operation `op` living in `block` dominates the entry of a block `bl`, then
`block` dominates `bl` (the definition site of `r` is in `block`, and `r` reaches all of `bl`). -/
axiom WfIRContext.Dom.block_dominates_of_opResult_dominatesIp_atStart!
(ctxDom : ctx.Dom) {op : OperationPtr} (opIn : op.InBounds ctx.raw)
(blockIn : block.InBounds ctx.raw) (blIn : bl.InBounds ctx.raw)
(opInBlock : op ∈ block.operationList ctx.raw ctx.wellFormed blockIn)
{r : ValuePtr} (rResult : r ∈ op.getResults! ctx.raw)
(rDom : r.dominatesIp (InsertPoint.atStart! bl ctx.raw) ctx) :
block.dominates bl ctx

/-- If an argument `w` of a block `bl` dominates a program point inside `block` (the end of a list of
`block`'s operations, started from `block`'s entry), then `bl` dominates `block`: `w` is in scope
from `bl`'s entry, so `bl`'s entry dominates that point, hence all of `block`. -/
axiom WfIRContext.Dom.block_dominates_of_arg_dominatesIp_afterLast
(ctxDom : ctx.Dom) (blIn : bl.InBounds ctx.raw) (blockIn : block.InBounds ctx.raw)
{w : ValuePtr} (wArg : w ∈ bl.getArguments! ctx.raw)
{ops : List OperationPtr}
(hops : ∀ o ∈ ops, o ∈ block.operationList ctx.raw ctx.wellFormed blockIn)
(wDom : w.dominatesIp (InsertPoint.afterLast ops ctx.raw (.atStart! block ctx.raw)) ctx) :
bl.dominates block ctx
8 changes: 8 additions & 0 deletions Veir/IR/Basic.lean
Original file line number Diff line number Diff line change
Expand Up @@ -2557,6 +2557,14 @@ theorem cons_iff {ctx : IRContext OpInfo} {parent : BlockPtr} {head : OperationP

end BlockPtr.OpChainSlice

/-- Get the successors of a block. This is defined as the successors of the terminator operation.
In case the block has no terminator, this function returns an empty array. -/
def BlockPtr.getSuccessors! (block : BlockPtr) (ctx : IRContext OpInfo) : Array BlockPtr :=
let term := (block.get! ctx).lastOp
match term with
| none => #[]
| some term => term.getSuccessors! ctx

def IRContext.empty (OpInfo : Type) [HasOpInfo OpInfo] : IRContext OpInfo := {
nextID := 0,
operations := Std.HashMap.emptyWithCapacity,
Expand Down
6 changes: 6 additions & 0 deletions Veir/Interpreter/Basic.lean
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,12 @@ instance : MonadLift Option Interp where

instance : Inhabited (Interp α) := ⟨(none : Option (UBOr α))⟩

/-- The interpreter monad is a chain-complete partial order with `none` as bottom (the flat order on
`Option`). This steers `partial_fixpoint` (used for `interpretBlockCFG`) to take its least fixpoint in
the `none`-bottom order, so that `interpretBlockCFG.fixpoint_induct`'s admissibility base case is the
trivially-true `none` outcome — exactly what refinement/correctness proofs need. -/
instance instCCPOInterp {α : Type} : Lean.Order.CCPO (Interp α) := Lean.Order.instCCPOOption

/-- Signal undefined behaviour from inside the interpreter monad. -/
@[inline] def Interp.ub : Interp α := some .ub

Expand Down
45 changes: 45 additions & 0 deletions Veir/Interpreter/EquationLemma.lean
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,51 @@ theorem interpretOp_DefinesDominating {ctx : WfIRContext OpCode} {opInBounds}
case inr =>
grind [OperationPtr.getResults!.mem_iff_exists_index]

/-- Setting a successor's block arguments preserves `DefinesDominating` in a state satisfying it at
the predecessor's exit. -/
theorem InterpreterState.DefinesDominating.setArgumentValues?_succ_entry
(ctxDom : ctx.Dom) {exitState : InterpreterState ctx}
{block : BlockPtr} (blockInBounds : block.InBounds ctx.raw)
(hsucc : succ ∈ block.getSuccessors! ctx.raw)
(hExit : exitState.DefinesDominating (InsertPoint.atEnd block))
(hArgs : exitState.variables.setArgumentValues? succ res succInBounds = some newVars) :
InterpreterState.DefinesDominating ⟨newVars, exitState.memory⟩ (InsertPoint.atStart! succ ctx.raw) := by
intro value valueInBounds valueDom
cases WfIRContext.Dom.value_dominatesIp_successor_entry ctxDom blockInBounds hsucc valueDom
· grind [InterpreterState.DefinesDominating]
· grind [BlockPtr.getArguments!.mem_iff_exists_index]

/-- `EquationHolds` for an `op` dominating `succ`'s entry is preserved when setting `succ`'s block
arguments. -/
theorem InterpreterState.EquationHolds.setArgumentValues?_of_dominatesIp (ctxDom : ctx.Dom)
(opDom : op.dominatesIp (InsertPoint.atStart! succ ctx.raw) ctx)
{exitState : InterpreterState ctx} (hEq : exitState.EquationHolds op opIn)
(hArgs : exitState.variables.setArgumentValues? succ res succInBounds = some newVars) :
InterpreterState.EquationHolds ⟨newVars, exitState.memory⟩ op := by
simp only [InterpreterState.EquationHolds] at hEq ⊢
obtain ⟨cf, hinterp⟩ := hEq
simp only [interpretOp_some_iff] at hinterp ⊢
grind [VariableState.getOperandValues_eq_of_getVar?_eq,
VariableState.getVar?_setArgumentValues?_of_notMem_getArguments!,
WfIRContext.Dom.blockArgument_not_dominatesIp_before_of_dominatesIp_firstOp,
→ VariableState.setResultValues?_setArgumentValues?_comm]

/-- Setting a successor's block arguments preserves `EquationLemmaAt` in a state satisfying it at
the predecessor's exit. -/
theorem InterpreterState.EquationLemmaAt.setArgumentValues?_succ_entry (ctxDom : ctx.Dom)
{block : BlockPtr} (blockInBounds : block.InBounds ctx.raw)
(hsucc : succ ∈ block.getSuccessors! ctx.raw)
{exitState : InterpreterState ctx}
(hExit : exitState.EquationLemmaAt (InsertPoint.atEnd block))
(hArgs : exitState.variables.setArgumentValues? succ res succInBounds = some newVars) :
InterpreterState.EquationLemmaAt ⟨newVars, exitState.memory⟩
(InsertPoint.atStart! succ ctx.raw) := by
intro op opIn hPure hDom
have opDomAtEnd : op.dominatesIp (InsertPoint.atEnd block) ctx := by
grind [WfIRContext.Dom.op_dominatesIp_successor_entry]
have := hExit op opIn hPure opDomAtEnd
grind [InterpreterState.EquationHolds.setArgumentValues?_of_dominatesIp]

/-- Interpreting a verified operation never fails on a state satisfying `DefinesDominating` at the
operation's location. -/
theorem InterpreterState.DefinesDominating.interpretOp_ne_none
Expand Down
146 changes: 146 additions & 0 deletions Veir/Interpreter/Lemmas.lean
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,90 @@ theorem VariableState.getVar?_setResultValues?_of_value_inBounds
simp only [getVar?_setResultValues? h]
cases value <;> grind

theorem VariableState.getVar?_setArgumentValues?_loop {block : BlockPtr}
{values : Array RuntimeValue} {i : Nat} {iInBounds blockInBounds} :
VariableState.setArgumentValues?.loop block values blockInBounds varState i iInBounds = some varState' →
varState'.getVar? value =
match value with
| .blockArgument ⟨block', index⟩ =>
if block' = block ∧ index < i then
some values[index]!
else
varState.getVar? value
| _ =>
varState.getVar? value := by
fun_induction VariableState.setArgumentValues?.loop
next => grind
next =>
simp only [Option.bind_eq_bind, Nat.succ_eq_add_one, Option.bind]
grind [cases BlockArgumentPtr, OperationPtr.getResult_def, cases ValuePtr]

@[grind =>]
theorem VariableState.getVar?_setArgumentValues? {block : BlockPtr} {values : Array RuntimeValue}
{blockInBounds} :
varState.setArgumentValues? block values blockInBounds = some varState' →
varState'.getVar? value =
match value with
| .blockArgument ⟨block', index⟩ =>
if block' = block ∧ index < block.getNumArguments! ctx.raw then
some values[index]!
else
varState.getVar? value
| _ =>
varState.getVar? value := by
grind [VariableState.setArgumentValues?, VariableState.getVar?_setArgumentValues?_loop]

theorem VariableState.getVar?_setArgumentValues?_of_notMem_getArguments!
{block : BlockPtr} {value values blockInBounds} :
value ∉ block.getArguments! ctx.raw →
varState.setArgumentValues? block values blockInBounds = some varState' →
varState'.getVar? value = varState.getVar? value := by
intro hNotMem hSet
simp only [VariableState.getVar?_setArgumentValues? hSet]
rcases value with _ | ⟨block', index⟩
· grind
· simp only [BlockPtr.getArguments!.mem_iff_exists_index, not_exists, not_and] at hNotMem
grind [BlockPtr.getArgument_def]

/-- `block` arguments are exactly the values set by `setArgumentValues? block` in the new state. -/
theorem VariableState.getVar?_getArgument_of_setArgumentValues?
{block : BlockPtr} {values blockInBounds} :
i < block.getNumArguments! ctx.raw →
varState.setArgumentValues? block values blockInBounds = some varState' →
varState'.getVar? (block.getArgument i) = some values[i]! := by
intro hi hSet
simp only [VariableState.getVar?_setArgumentValues? hSet]
grind [BlockPtr.getArgument_def]

/-- `setArgumentValues?.loop` succeeds iff every argument value it binds conforms to its argument
type. -/
theorem VariableState.setArgumentValues?_loop_isSome_iff {block : BlockPtr}
{values : Array RuntimeValue} {i : Nat} {iInBounds blockInBounds} :
(∀ j, j < i → (values[j]!).Conforms ((block.getArgument j : ValuePtr).getType! ctx.raw)) ↔
(∃ v, VariableState.setArgumentValues?.loop block values blockInBounds varState i iInBounds = some v) := by
fun_induction VariableState.setArgumentValues?.loop
next => grind
next varState hin k hk arg value ih =>
simp only [Option.bind_eq_bind, Option.bind]
constructor
· intro hconform
rw [VariableState.setVar?_eq_some_setVar (hconform k (by grind))]
simp only [← ih]
grind
· rintro ⟨v, hv⟩ j hj
rcases hsv : hin.setVar? (ValuePtr.blockArgument arg) value with _ | varState'
· grind
· grind [ih varState']

/-- `setArgumentValues?` succeeds iff every argument value conforms to its argument type. -/
theorem VariableState.setArgumentValues?_isSome_iff_conforms (varState : VariableState ctx)
{block : BlockPtr} {values : Array RuntimeValue} {blockInBounds} :
(∀ j, j < block.getNumArguments! ctx.raw →
(values[j]!).Conforms ((block.getArgument j : ValuePtr).getType! ctx.raw)) ↔
(∃ v, varState.setArgumentValues? block values blockInBounds = some v) := by
simp only [VariableState.setArgumentValues?]
exact VariableState.setArgumentValues?_loop_isSome_iff

/--
Assert equality between two `interpretOp'` calls that have the same operation type and properties.
This lemma is useful to avoid introducing extra casts on the `interpretOp'` arguments.
Expand Down Expand Up @@ -282,6 +366,48 @@ theorem VariableState.setResultValues?_comm
ext val value
cases val <;> grind [getVar?_setResultValues?]

/-- Success of `setArgumentValues?` only depends on the values conforming to the argument types,
not on the contents of the variable state, so it can be transferred to any other state. -/
theorem VariableState.setArgumentValues?_eq_some_of_varState
(varState₂ : VariableState ctx) :
varState.setArgumentValues? block values blockInBounds = some varState' →
∃ varState₂', varState₂.setArgumentValues? block values blockInBounds = some varState₂' := by
intro h
simp only [← VariableState.setArgumentValues?_isSome_iff_conforms]
grind [(VariableState.setArgumentValues?_isSome_iff_conforms varState).mpr ⟨_, h⟩]

/-- Setting block arguments and operation results commute: block arguments and operation results
are distinct `ValuePtr`s, so they never alias. -/
theorem VariableState.setArgumentValues?_setResultValues?_comm :
varState.setArgumentValues? block argValues blockInBounds = some varState' →
varState'.setResultValues? op resValues opInBounds = some varState'' →
∃ varState₂, varState.setResultValues? op resValues opInBounds = some varState₂ ∧
varState₂.setArgumentValues? block argValues blockInBounds = some varState'' := by
intros h₁ h₂
have ⟨varState₂, hvs₂⟩ := setResultValues?_eq_some_of_varState varState h₂
have ⟨varState₂', hvs₂'⟩ := setArgumentValues?_eq_some_of_varState varState₂ h₁
exists varState₂
constructor; grind
simp only [hvs₂', Option.some.injEq]
ext val value
grind [getVar?_setResultValues?, getVar?_setArgumentValues?]

/-- Setting operation results and block arguments commute: block arguments and operation results
are distinct `ValuePtr`s, so they never alias. -/
theorem VariableState.setResultValues?_setArgumentValues?_comm :
varState.setResultValues? op resValues opInBounds = some varState' →
varState'.setArgumentValues? block argValues blockInBounds = some varState'' →
∃ varState₂, varState.setArgumentValues? block argValues blockInBounds = some varState₂ ∧
varState₂.setResultValues? op resValues opInBounds = some varState'' := by
intros h₁ h₂
have ⟨varState₂, hvs₂⟩ := setArgumentValues?_eq_some_of_varState varState h₂
have ⟨varState₂', hvs₂'⟩ := setResultValues?_eq_some_of_varState varState₂ h₁
exists varState₂
constructor; grind
simp only [hvs₂', Option.some.injEq]
ext val value
grind [getVar?_setResultValues?, getVar?_setArgumentValues?]

theorem VariableState.getVar?_setResultValues?_operand_of_dominates
(ctxDom : ctx.Dom) (hdom : op'.dominates op ctx) :
value ∈ op'.getOperands! ctx.raw →
Expand Down Expand Up @@ -518,3 +644,23 @@ theorem interpretOp'_results_conform {ctx : WfIRContext OpCode}
(h : op.interpret ctx.raw operands mem = some (.ok (vals, mem', act))) :
RuntimeValue.ArrayConforms vals (op.getResultTypes! ctx.raw) := by
sorry

set_option warn.sorry false in
/-- A branch control-flow action returned by interpreting an operation always targets one of the
provided successor blocks. -/
theorem interpretOp'_branch_dest_mem
(h : interpretOp' opType properties resultTypes operands blockOperands mem
= some (.ok (vals, mem', some (.branch res dest)))) :
dest ∈ blockOperands := by
sorry

/-- A branch control-flow action returned by interpreting an operation always targets one of the
operation successors. -/
theorem interpretOp_branch_dest_mem_getSuccessors!
{ctx : WfIRContext OpCode} {op : OperationPtr} {state state' : InterpreterState ctx}
{inBounds : op.InBounds ctx.raw} {res : Array RuntimeValue} {dest : BlockPtr}
(h : interpretOp op state inBounds = some (.ok (state', some (.branch res dest)))) :
dest ∈ op.getSuccessors! ctx.raw := by
obtain ⟨operandValues, resValues, mem', varState', hOperand, hInterp', hSetRes, hStateEq⟩ :=
interpretOp_some_iff.mp h
exact interpretOp'_branch_dest_mem hInterp'
Loading
Loading