Library compver
Require Import Arith.
Require Import List.
Import ListNotations.
Ltac inv H := inversion H; subst; clear H.
Inductive maybe (T:Type) : Type :=
| OK : T -> maybe T
| Error : maybe T.
Arguments OK {T} _.
Arguments Error {T}.
Inductive id : Type :=
Id : nat -> id.
Lemma eqdec_id: forall (x1 x2:id) , {x1 = x2} + {x1 <> x2}.
Proof.
repeat (decide equality).
Qed.
Definition beq_id x1 x2 :=
match (x1, x2) with
(Id n1, Id n2) => beq_nat n1 n2
end.
A few specific identifiers for use in examples.
Definition X : id := Id 0.
Definition Y : id := Id 1.
Definition Z : id := Id 2.
Definition state := id -> nat.
Definition initial_state : state := fun _ => 0.
Definition update (st : state) (x : id) (n : nat) : state :=
fun x' => if beq_id x x' then n else st x'.
A randomly chosen example state: X = 100 Y = 20 Z = 2
Definition st1 := update (update (update initial_state X 100) Y 20) Z 2.
Definition trace := list nat.
Definition initial_trace : trace := [].
Inductive aexp : Type :=
| Const : nat -> aexp
| Name : id -> aexp
| Plus : aexp -> aexp -> aexp
| Minus : aexp -> aexp -> aexp
.
Inductive stmt : Type :=
| Assign: id -> aexp -> stmt
| PrintInt: aexp -> stmt
.
Definition prog := list stmt.
Semantics of the source language, given in a "denotational" style,
in this case a tree-walking interpreter.
Fixpoint aeval (st : state) (a : aexp) : nat :=
match a with
| Const n => n
| Name x => st x
| Plus a1 a2 => (aeval st a1) + (aeval st a2)
| Minus a1 a2 => (aeval st a1) - (aeval st a2)
end.
Definition seval (st: state) (tr: trace) (s: stmt) : state*trace :=
match s with
| PrintInt a => (st, tr ++ [aeval st a])
| Assign x a => (update st x (aeval st a), tr)
end.
Fixpoint sseval (st: state) (tr: trace) (ss: list stmt) : state*trace :=
match ss with
| [] => (st,tr)
| s::ss' =>
let '(st',tr') := seval st tr s in
sseval st' tr' ss'
end.
Definition peval (p: prog) : trace :=
snd (sseval initial_state initial_trace p).
An example program
Definition ex1 :=
[ Assign X (Plus (Const 2) (Const 2));
Assign Y (Minus (Const 44) (Name X));
Assign Z (Plus (Name Y) (Const 2));
PrintInt (Name Z) ].
We can evaluate the denotation of a program directly inside Coq.
Eval compute in (peval ex1).
===> :nat
The target language: stack machine instructions
(2*3)+(3*(4-2))would be entered as
2 3 * 3 4 2 - * +and evaluated like this:
[] | 2 3 * 3 4 2 - * + [2] | 3 * 3 4 2 - * + [3; 2] | * 3 4 2 - * + [6] | 3 4 2 - * + [3; 6] | 4 2 - * + [4; 3; 6] | 2 - * + [2; 4; 3; 6] | - * + [2; 3; 6] | * + [6; 6] | + [12] |
- SPush n: Push the number n on the stack.
- SLoad x: Load the identifier x from the store and push it on the stack
- SStore x : Pop the top number from the stack, and save it as the value of identifier x in the store.
- SPlus: Pop the two top numbers from the stack, add them, and push the result onto the stack.
- SMinus: Similar, but subtract.
- SPrint: Pop the top number from the stack and print it.
Inductive instr : Type :=
| SPush : nat -> instr
| SLoad : id -> instr
| SStore: id -> instr
| SPlus : instr
| SMinus : instr
| SPrint : instr
.
Definition iprog := list instr.
Semantics of the target machine
Fixpoint i_execute (st : state) (tr: trace) (stack : list nat) (prog : iprog)
: list nat * list nat :=
match (prog, stack) with
| (nil, _ ) => (stack, tr)
| (SPush n::prog', _ ) => i_execute st tr (n::stack) prog'
| (SLoad x::prog', _ ) => i_execute st tr (st x::stack) prog'
| (SStore x::prog', n::stack' ) => i_execute (update st x n) tr stack' prog'
| (SPlus::prog', n::m::stack') => i_execute st tr ((m+n)::stack') prog'
| (SMinus::prog', n::m::stack') => i_execute st tr ((m-n)::stack') prog'
| (SPrint::prog', n::stack' ) => i_execute st (tr ++ [n]) stack' prog'
| (_::prog', _ ) => i_execute st tr stack prog'
end.
Definition ip_execute (prog: iprog) : list nat :=
snd (i_execute initial_state initial_trace [] prog).
Compilers and Correctness
Definition compiler := prog -> maybe iprog.
If the compiler succeeds, then the observed behavior of
target should match that of source.
In this case, all we can "observe" are the traces of printed values.
Definition compiler_ok (comp : compiler) :=
forall (p:prog) (st:state) (ip: iprog),
comp p = OK ip ->
ip_execute ip = peval p.
Fixpoint e_compile_r (e : aexp) : iprog :=
match e with
| Const n => [SPush n]
| Name x => [SLoad x]
| Plus a1 a2 => e_compile_r a1 ++ e_compile_r a2 ++ [SPlus]
| Minus a1 a2 => e_compile_r a1 ++ e_compile_r a2 ++ [SMinus]
end.
Definition s_compile (s : stmt) : iprog :=
match s with
| Assign x a => e_compile_r a ++ [SStore x]
| PrintInt a => e_compile_r a ++ [SPrint]
end.
Fixpoint p_compile_r (p : prog) : iprog :=
match p with
| [] => []
| s::ss => s_compile s ++ p_compile_r ss
end.
Definition p_compile (p : prog) : maybe iprog :=
OK (p_compile_r p).
Again, we can evaluate the result of compilation directly inside Coq.
Definition p1 := p_compile ex1.
Eval compute in p1.
===>
OK [SPush 2; SPush 2; SPlus; SStore (Id 0); SPush 44; SLoad (Id 0); SMinus; SStore (Id 1);
SLoad (Id 1); SPush 2; SPlus; SStore (Id 2); SLoad (Id 2); SPrint]
Coq also supports "extraction" of functions and datatypes to
a more efficiently executable language such as OCaml.
Require Extraction.
Recursive Extraction p_compile.
(see compver_extract.ml)
So properties we prove about p_compile apply directly to
the "real" compiler generated by extraction (assuming we
trust the extraction process, of course).
Proof of Correctness
Lemma execute_aeval_comm : forall st e rest stack tr,
i_execute st tr stack (e_compile_r e ++ rest) =
i_execute st tr (aeval st e :: stack) rest.
Proof.
induction e;
try reflexivity;
intros; simpl; rewrite <- app_assoc; rewrite IHe1;
rewrite <- app_assoc; rewrite IHe2; reflexivity.
Qed.
Then we prove a lemma about statement sequences, showing that
the generate stack code produces the same trace and store changes
as for the source program, and leaves the stack unchanged.
This proof is by induction on the statement sequence, and
again requires generalization for arbitrary code sequences.
Lemma execute_sseval_comm: forall ss st tr stack rest,
i_execute st tr stack (p_compile_r ss ++ rest) =
(let (st', tr') := sseval st tr ss in
i_execute st' tr' stack rest).
Proof.
induction ss; intros; try reflexivity;
destruct a; simpl; repeat rewrite <- app_assoc; rewrite execute_aeval_comm;
simpl; rewrite IHss; reflexivity.
Qed.
The top level theorem justs instantiates the statement sequence lemma.
Theorem p_compile_ok: compiler_ok p_compile.
Proof.
unfold compiler_ok. intros.
unfold p_compile in H. inv H.
replace (p_compile_r p) with (p_compile_r p ++ []) by auto with datatypes.
unfold ip_execute.
rewrite execute_sseval_comm. unfold peval.
destruct (sseval initial_state initial_trace p).
reflexivity.
Qed.