(* Copyright 2008-2019 Microsoft Research Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Authors: C. Hawblitzel, N. Swamy *) module OPLSS2021.Vale open FStar.FunctionalExtensionality open FStar.Mul //suppress a benign warning in this program #push-options "--warn_error -290" (* This is a highly-simplified model of Vale/F*, based on Section 3.1-3.3 of the paper of the POPL '19 paper. It is derived from the QuickRegs1 code in the popl-artifact-submit branch of Vale. *) /// 2^64 let pow2_64 = 0x10000000000000000 /// Natural numbers representable in 64 bits type nat64 = i:int{0 <= i /\ i < pow2_64} /// We have 4 registers type reg = | Rax | Rbx | Rcx | Rdx /// An operand is either a register or a constant type operand = | OReg: r:reg -> operand | OConst: n:nat64 -> operand /// Only 2 instructions here: /// A move or an add type ins = | Mov64: dst:operand -> src:operand -> ins | Add64: dst:operand -> src:operand -> ins /// A program is /// - a single instruction /// - a block of instructions /// - or a while loop type code = | Ins: ins:ins -> code | Block: block:list code -> code | WhileLessThan: src1:operand -> src2:operand -> whileBody:code -> code /// The state of a program is the register file /// holiding a 64-bit value for each register type state = reg -> nat64 /// fuel: To prove the termination of while loops, we're going to /// instrument while loops with fuel type fuel = nat /// Evaluating an operand: /// -- marked for reduction /// -- Registers evaluated by state lookup let eval_operand (o:operand) (s:state) : nat64 = match o with | OReg r -> s r | OConst n -> n /// updating a register state `s` at `r` with `v` let update_reg (s:state) (r:reg) (v:nat64) : state = fun r' -> if r = r' then v else s r' /// updating a register state `s` at `r` with `s' r` let update_state (r:reg) (s' s:state) : state = update_reg s r (s' r) /// We don't have an "ok" flag, so errors just result an arbitrary state: assume val unknown_state (s:state) : state (*** A basic semantics using a big-step interpreter ***) /// Instruction evaluation: /// only some operands are valid let eval_ins (ins:ins) (s:state) : state = match ins with | Mov64 (OConst _) _ -> unknown_state s | Mov64 (OReg dst) src -> update_reg s dst (eval_operand src s) | Add64 (OConst _) _ -> unknown_state s | Add64 (OReg dst) src -> update_reg s dst ((s dst + eval_operand src s) % 0x10000000000000000) /// eval_code: /// A fueled big-step interpreter /// While lops return None when we're out of fuel let rec eval_code (c:code) (f:fuel) (s:state) : option state = match c with | Ins ins -> Some (eval_ins ins s) | Block cs -> eval_codes cs f s | WhileLessThan src1 src2 body -> if f = 0 then None else if eval_operand src1 s < eval_operand src2 s then match eval_code body f s with | None -> None | Some s -> eval_code c (f - 1) s else Some s and eval_codes (cs:list code) (f:fuel) (s:state) : option state = match cs with | [] -> Some s | c::cs -> match eval_code c f s with | None -> None | Some s -> eval_codes cs f s (*** END OF TRUSTED SEMANTICS ***) //////////////////////////////////////////////////////////////////////////////// /// 1. We prove that incrasing the fuel is irrelevant to terminating executions let rec increase_fuel (c:code) (s0:state) (f0:fuel) (sN:state) (fN:fuel) : Lemma (requires eval_code c f0 s0 == Some sN /\ f0 <= fN) (ensures eval_code c fN s0 == Some sN) (decreases %[f0; c]) = match c with | Ins ins -> () | Block l -> increase_fuels l s0 f0 sN fN | WhileLessThan src1 src2 body -> if eval_operand src1 s0 < eval_operand src2 s0 then match eval_code body f0 s0 with | None -> () | Some s1 -> increase_fuel body s0 f0 s1 fN; increase_fuel c s1 (f0 - 1) sN (fN - 1) else () and increase_fuels (c:list code) (s0:state) (f0:fuel) (sN:state) (fN:fuel) : Lemma (requires eval_code (Block c) f0 s0 == Some sN /\ f0 <= fN) (ensures eval_code (Block c) fN s0 == Some sN) (decreases %[f0; c]) = match c with | [] -> () | h::t -> let Some s1 = eval_code h f0 s0 in increase_fuel h s0 f0 s1 fN; increase_fuels t s1 f0 sN fN (* t -> Pure t' pre post is (roughly) sugar for x:t{pre} -> y:t'{post y} *) /// 2. We can compute the fuel needed to run a sequential composition /// as the max of the fuel to compute each piece of code in it let lemma_merge (c:code) (cs:list code) (s0:state) (f0:fuel) (sM:state) (fM:fuel) (sN:state) : Pure fuel (requires eval_code c f0 s0 == Some sM /\ eval_code (Block cs) fM sM == Some sN) (ensures fun fN -> eval_code (Block (c::cs)) fN s0 == Some sN) = let f = if f0 > fM then f0 else fM in increase_fuel c s0 f0 sM f; increase_fuel (Block cs) sM fM sN f; f //////////////////////////////////////////////////////////////////////////////// let lemma_Move (s0:state) (dst:operand) (src:operand) : Pure (state & fuel) (requires OReg? dst) (ensures fun (sM, fM) -> eval_code (Ins (Mov64 dst src)) fM s0 == Some sM /\ eval_operand dst sM == eval_operand src s0 /\ sM == update_state (OReg?.r dst) sM s0 ) = let Some sM = eval_code (Ins (Mov64 dst src)) 0 s0 in (sM, 0) let lemma_Add (s0:state) (dst:operand) (src:operand) : Pure (state & fuel) (requires OReg? dst /\ eval_operand dst s0 + eval_operand src s0 < pow2_64) (ensures fun (sM, fM) -> eval_code (Ins (Add64 dst src)) fM s0 == Some sM /\ eval_operand dst sM == eval_operand dst s0 + eval_operand src s0 /\ sM == update_state (OReg?.r dst) sM s0 ) = let Some sM = eval_code (Ins (Add64 dst src)) 0 s0 in (sM, 0) let codes_Triple : list code = [ Ins (Mov64 (OReg Rbx) (OReg Rax)); //mov rbx rax; Ins (Add64 (OReg Rax) (OReg Rbx)); //add rax rbx; Ins (Add64 (OReg Rbx) (OReg Rax)) //add rbx rax; ] let lemma_Triple (s0:state) : Pure (state & fuel) (requires s0 Rax < 100) (ensures fun (sM, f0) -> eval_code (Block codes_Triple) f0 s0 == Some sM /\ sM Rbx == 3 * s0 Rax /\ sM `feq` update_state Rax sM (update_state Rbx sM s0)) = // Naive proof: let b1 = codes_Triple in let (s2, fc2) = lemma_Move s0 (OReg Rbx) (OReg Rax) in let b2 = Cons?.tl b1 in let (s3, fc3) = lemma_Add s2 (OReg Rax) (OReg Rbx) in let b3 = Cons?.tl b2 in let (s4, fc4) = lemma_Add s3 (OReg Rbx) (OReg Rax) in let b4 = Cons?.tl b3 in let (sM, f4) = (s4, 0) in let f3 = lemma_merge (Cons?.hd b3) b4 s3 fc4 s4 f4 sM in let f2 = lemma_merge (Cons?.hd b2) b3 s2 fc3 s3 f3 sM in let fM = lemma_merge (Cons?.hd b1) b2 s0 fc2 s2 f2 sM in (sM, fM)