Files
why3/examples/double_wp/compiler.mlw
2018-06-15 16:45:58 +02:00

227 lines
8.5 KiB
Plaintext

(*Imp to Vm compiler *)
(**************************************************************************)
(* Compiler for arithmetic expressions *)
module Compile_aexpr
use int.Int
use list.List
use list.Length
use list.Append
use imp.Imp
use vm.Vm
use state.State
use logic.Compiler_logic
use specs.VM_instr_spec
(* Compilation scheme: the generated code for arithmetic expressions
put the result of the expression on the stack. *)
function aexpr_post (a:aexpr) (len:pos) : post 'a =
fun _ p ms ms' -> let VMS _ s m = ms in ms' = VMS (p+len) (push (aeval m a) s) m
meta rewrite_def function aexpr_post
let rec compile_aexpr (a:aexpr) : hl 'a
ensures { result.pre --> trivial_pre }
ensures { result.post --> aexpr_post a result.code.length }
variant { a }
= let c = match a with
| Anum n -> $ iconstf n
| Avar x -> $ ivarf x
| Aadd a1 a2 -> $ compile_aexpr a1 -- $ compile_aexpr a2 -- $ iaddf ()
| Asub a1 a2 -> $ compile_aexpr a1 -- $ compile_aexpr a2 -- $ isubf ()
| Amul a1 a2 -> $ compile_aexpr a1 -- $ compile_aexpr a2 -- $ imulf ()
end in
hoare trivial_pre c (aexpr_post a c.wcode.length)
(* Check that the above specification indeed implies the
natural one. *)
let compile_aexpr_natural (a:aexpr) : code
ensures { forall c p s m. codeseq_at c p result ->
transition_star c (VMS p s m)
(VMS (p + length result) (push (aeval m a) s) m) }
= let res = compile_aexpr a : hl unit in
assert { forall p s m. res.pre () p (VMS p s m) }; res.code
end
(* Compiler for boolean expressions. *)
module Compile_bexpr
use int.Int
use list.List
use list.Length
use list.Append
use imp.Imp
use vm.Vm
use state.State
use logic.Compiler_logic
use specs.VM_instr_spec
use Compile_aexpr
(* Compilation scheme: the generated code perform a jump
iff the boolean expression evaluate to cond. *)
function bexpr_post (b:bexpr) (cond: bool) (out_t:ofs) (out_f:ofs) : post 'a =
fun _ p ms ms' -> let VMS _ s m = ms in if beval m b = cond
then ms' = VMS (p + out_t) s m
else ms' = VMS (p + out_f) s m
meta rewrite_def function bexpr_post
function exec_cond (b1:bexpr) (cond:bool) : pre 'a =
fun _ _ ms -> let VMS _ _ m = ms in beval m b1 = cond
meta rewrite_def function exec_cond
let rec compile_bexpr (b:bexpr) (cond:bool) (ofs:ofs) : hl 'a
ensures { result.pre --> trivial_pre }
ensures { result.post --> let len = result.code.length in
bexpr_post b cond (len + ofs) len }
variant { b }
= let c = match b with
| Btrue -> $ if cond then ibranchf ofs else inil ()
| Bfalse -> $ if cond then inil () else ibranchf ofs
| Bnot b1 -> $ compile_bexpr b1 (not cond) ofs
| Band b1 b2 ->
let c2 = $ compile_bexpr b2 cond ofs % exec_cond b1 true in
let ofs = if cond then length c2.wcode else ofs + length c2.wcode in
$ compile_bexpr b1 false ofs -- c2
| Beq a1 a2 -> $ compile_aexpr a1 -- $ compile_aexpr a2 --
$ if cond then ibeqf ofs else ibnef ofs
| Ble a1 a2 -> $ compile_aexpr a1 -- $ compile_aexpr a2 --
$ if cond then iblef ofs else ibgtf ofs
end in
let ghost post = bexpr_post b cond (c.wcode.length + ofs) c.wcode.length in
hoare trivial_pre c post
(* Check that the above specification implies the natural one. *)
let compile_bexpr_natural (b:bexpr) (cond:bool) (ofs:ofs) : code
ensures { forall c p s m. codeseq_at c p result ->
transition_star c (VMS p s m)
(VMS (p + length result + if beval m b = cond then ofs else 0) s m) }
= let res = compile_bexpr b cond ofs : hl unit in
assert { forall p s m. res.pre () p (VMS p s m) }; res.code
end
module Compile_com
use int.Int
use list.List
use list.Length
use list.Append
use imp.Imp
use vm.Vm
use state.State
use logic.Compiler_logic
use specs.VM_instr_spec
use Compile_aexpr
use Compile_bexpr
(* Compilation scheme: the generated code for a command
simulates the command on the memory part of the machine state. *)
(* As we specify only terminating behavior, we have to require
that the source program terminates in the initial conditions. *)
function com_pre (cmd:com) : pre 'a =
fun _ p ms -> let VMS p' _ m = ms in p = p' /\ exists m'. ceval m cmd m'
meta rewrite_def function com_pre
function com_post (cmd:com) (len:pos) : post 'a =
fun _ _ ms ms' -> let VMS p s m = ms in let VMS p' s' m' = ms' in
p' = p + len /\ s' = s /\ ceval m cmd m'
meta rewrite_def function com_post
function exec_cond_old (b1:bexpr) (cond:bool) : pre ('a,machine_state) =
fun x _ _ -> let VMS _ _ m = snd x in beval m b1 = cond
meta rewrite_def function exec_cond_old
(* Invariant for loop compilation: any intermediate state
would evaluate to the same final state as the initial state. *)
function loop_invariant (c:com) : pre ('a,machine_state) =
fun x p msi -> let VMS _ s0 m0 = snd x in let VMS pi si mi = msi in
pi = p /\ s0 = si /\ exists mf. ceval m0 c mf /\ ceval mi c mf
meta rewrite_def function loop_invariant
function loop_variant (c:com) (test:bexpr) : post 'a =
fun _ _ msj msi -> let VMS _pj _sj mj = msj in let VMS _pi _si mi = msi in
ceval mi c mj /\ beval mi test
lemma loop_variant_lemma : forall c test,x:'a,p msj msi.
loop_variant c test x p msj msi =
let VMS _pj _sj mj = msj in let VMS _pi _si mi = msi in
ceval mi c mj /\ beval mi test
meta rewrite lemma loop_variant_lemma
(* Well-foundedness of the loop variant. *)
lemma loop_variant_acc : forall c test,x:'a,p mi mj.
let wh = Cwhile test c in let var = (loop_variant c test x p) in
(ceval mi wh mj -> forall pi si. acc var (VMS pi si mi))
by forall pi si mi mj mf. ceval mi c mj /\ beval mi test ->
ceval mj wh mf /\ (forall pj sj. acc var (VMS pj sj mj)) ->
acc var (VMS pi si mi) by
(forall pk sk mk. var (VMS pk sk mk) (VMS pi si mi) -> mk = mj)
let rec compile_com (cmd: com) : hl 'a
ensures { result.pre --> com_pre cmd }
ensures { result.post --> let len = result.code.length in com_post cmd len }
variant { cmd }
= let res = match cmd with
| Cskip -> $ inil ()
| Cassign x a -> $ compile_aexpr a -- $ isetvarf x
| Cseq cmd1 cmd2 -> $ compile_com cmd1 -- $ compile_com cmd2
| Cif cond cmd1 cmd2 -> let code_false = compile_com cmd2 in
let code_true = $ compile_com cmd1 -- $ ibranchf code_false.code.length in
$ compile_bexpr cond false code_true.wcode.length --
(code_true % exec_cond cond true) --
($ code_false % exec_cond_old cond false)
| Cwhile test body ->
let code_body = compile_com body in
let body_length = length code_body.code + 1 in
let code_test = compile_bexpr test false body_length in
let ofs = length code_test.code + body_length in
let wp_while = $ code_test --
($ code_body -- $ ibranchf (- ofs)) % exec_cond test true in
let ghost inv = loop_invariant cmd in
let ghost var = loop_variant body test in
$ inil () -- make_loop wp_while inv (exec_cond test true) var
end in
hoare (com_pre cmd) res (com_post cmd res.wcode.length)
(* Get back to natural specification for the compiler. *)
let compile_com_natural (com: com) : code
ensures { forall c p s m m'. ceval m com m' -> codeseq_at c p result ->
transition_star c (VMS p s m) (VMS (p + length result) s m') }
= let res = compile_com com : hl unit in
assert { forall c p s m m'. ceval m com m' -> codeseq_at c p res.code ->
res.pre () p (VMS p s m) && (forall ms'. res.post () p (VMS p s m) ms' ->
ms' = VMS (p + length res.code) s m') };
res.code
(* Insert the final halting instruction. *)
let compile_program (prog : com) : code
ensures { forall mi mf: state.
ceval mi prog mf -> vm_terminates result mi mf }
= compile_com_natural prog ++ ihalt
(* Execution test: compile a simple factorial program, e.g
X := 1; WHILE NOT (Y <= 0) DO X := X * Y; Y := Y - 1 DONE
(why3 execute -L . compiler.mlw Compile_com.test) *)
let test () : code =
let x = Id 0 in
let y = Id 1 in
let cond = Bnot (Ble (Avar y) (Anum 0)) in
let body1 = Cassign x (Amul (Avar x) (Avar y)) in
let body2 = Cassign y (Asub (Avar y) (Anum 1)) in
let lp = Cwhile cond (Cseq body1 body2) in
let code = Cseq (Cassign x (Anum 1)) lp in
compile_program code
let test2 () : code =
compile_program (Cwhile Btrue Cskip)
end