-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathcode01_simplified.lean
More file actions
119 lines (103 loc) · 4.53 KB
/
code01_simplified.lean
File metadata and controls
119 lines (103 loc) · 4.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
-- Thanks to Arthur Adjedj for helping with the proofs
-- Datatype for arithmetic expressions
inductive Expr where
| const : Int → Expr -- Constants like 5
| var : String → Expr -- Variables like "x"
| add : Expr → Expr → Expr -- Addition (e₁ + e₂)
deriving Repr
-- Environment type (maps variable names to values)
def Env := String → Int
-- Evaluator function
def evaluate : Expr → Env → Int
| Expr.const n, _ => n -- A constant evaluates to itself
| Expr.var x, env => env x -- A variable evaluates to its value in the environment
| Expr.add e₁ e₂, env => evaluate e₁ env + evaluate e₂ env -- Addition evaluates recursively
-- Optimizer to remove additions by 0 & multiplication by 1, and to simplify multiplication by 0
def optimize : Expr → Expr
| Expr.const n => Expr.const n -- Constants remain unchanged
| Expr.var x => Expr.var x -- Variables remain unchanged
| Expr.add e₁ e₂ =>
let e₁' := optimize e₁
let e₂' := optimize e₂
match e₁', e₂' with
| Expr.const 0, e => e -- 0 + e => e
| e, Expr.const 0 => e -- e + 0 => e
| _, _ => Expr.add e₁' e₂' -- Otherwise keep the optimized addition
-- Theorem: optimizer preserves semantics
set_option grind.warning false
theorem optimize_preserves_semantics (e : Expr) (env : Env) :
evaluate (optimize e) env = evaluate e env := by
induction e <;> simp [optimize, evaluate]
<;> grind +ring [optimize, evaluate]
-- Predicate defining an optimally optimized expression
def optimal : Expr → Bool
| Expr.const _ => true
| Expr.var _ => true
| Expr.add (Expr.const 0) _ => false
| Expr.add _ (Expr.const 0) => false
| Expr.add e₁ e₂ => optimal e₁ && optimal e₂
-- Theorem: optimizer produces optimital expressions
theorem optimize_optimal (e : Expr) :
optimal (optimize e) := by
induction e <;> simp [optimize, optimal] <;> split <;> simp [optimal, *]
-- Compiler correctness proof for a stack machine
-- See also: https://xavierleroy.org/courses/EUTypes-2019/
-- Stack machine instructions
inductive Instr where
| push : Int → Instr -- Push a constant onto the stack
| load : String → Instr -- Load a variable onto the stack
| add : Instr -- Pop two values, push their sum
deriving Repr
-- Compile expression to stack machine code
def compile : Expr → List Instr
| Expr.const n => [Instr.push n]
| Expr.var x => [Instr.load x]
| Expr.add e₁ e₂ => compile e₁ ++ compile e₂ ++ [Instr.add]
-- Execute stack machine code, returning None on stack underflow
def exec : List Instr → List Int → Env → Option (List Int)
| [], stk, _ => some stk
| Instr.push v :: code, stk, env => exec code (v :: stk) env
| Instr.load x :: code, stk, env => exec code (env x :: stk) env
| Instr.add :: code, v₂ :: v₁ :: stk, env => exec code ((v₁ + v₂) :: stk) env
| Instr.add :: _, _, _ => none -- Stack underflow
-- Key lemma: exec distributes over code concatenation
theorem exec_append (code₁ code₂ : List Instr) (stk : List Int) (env : Env) :
exec (code₁ ++ code₂) stk env =
match exec code₁ stk env with
| some stk' => exec code₂ stk' env
| none => none := by
induction code₁ generalizing stk with
| nil => simp [exec]
| cons instr code₁ ih =>
cases instr with
| push v => simp [exec, ih]
| load x => simp [exec, ih]
| add =>
cases stk with
| nil => simp [exec]
| cons v₂ stk' =>
cases stk' with
| nil => simp [exec]
| cons v₁ stk'' => simp [exec, ih]
-- Key insight: compile preserves semantics with any continuation stack
theorem compile_correct (e : Expr) (code : List Instr) (stk : List Int) (env : Env) :
exec (compile e ++ code) stk env = exec code (evaluate e env :: stk) env := by
induction e generalizing code stk with
| const n => simp [compile, exec, evaluate]
| var x => simp [compile, exec, evaluate]
| add e₁ e₂ ih₁ ih₂ =>
simp only [compile, evaluate, List.append_assoc]
rw [ih₁]
rw [ih₂]
simp [exec]
-- Main theorem: compiled code succeeds and evaluates correctly
theorem compiler_correct (e : Expr) (env : Env) :
exec (compile e) [] env = some [evaluate e env] := by
have h := compile_correct e [] [] env
simp at h
exact h
-- Concrete example: 2 + (3 * 4) = 9
#eval
let e := Expr.add (Expr.const 2) (Expr.add (Expr.const 3) (Expr.const 4))
let env : Env := fun _ => 0
(compile e, exec (compile e) [] env, evaluate e env)