Variant of dependent type theory
Recursion and semantic termination check
Refinements
x:t{p}
Pure t pre post
Subtyping and sub-effecting
Extensional equality
Monadic effects in F*
Verifying effectful programs extrinsically (monadic reification)
Tactics as a user-defined, non-primitive effect (experimental)
Under the hood: Weakest pre-conditions and Dijkstra monads (for free)
type st (mem:Type) (a:Type) = mem -> Tot (a * mem)
total reifiable new_effect {
STATE_m (mem:Type) : a:Type -> Effect
with repr = st mem;
return = fun (a:Type) (x:a) (m:mem) -> x, m;
bind = fun (a b:Type) (f:st mem a) (g:a -> st mem b) (m:mem) ->
let z, m' = f m in g z m';
get = fun () (m:mem) -> m, m;
put = fun (m:mem) _ -> (), m }
total reifiable new_effect STATE = STATE_m heap
State and exception monad
let stexn a = nat -> Tot ((either a string) * nat))
new_effect {
STEXN: a:Type -> Effect with
repr = stexn;
return = fun (a:Type) (x:a) s -> Inl x, s;
bind = fun (a b:Type) (f:stexn a) (g:a -> stexn b) s0 ->
let (r,s1) = f s0 in
match r with
| Inl ret -> Inl (g ret s1), s1
| Inr m -> Inr m, s1
raise = fun (a:Type) (msg:string) s -> Inr msg, s
}
sub_effect STATE ~> STEXN {
lift = fun (a:Type) (e:st nat a) -> (fun s -> let (x,s') = e s in Inl x, s')
}
In F*, the programmer writes:
let incr () =
let x = STATE.get() in
STATE.put (x + 1);
let y = STATE.get() in
assert (y > x)
Made explicitly monadic via type and effect inference
let incr () =
STATE.bind (STATE.get ()) (fun x ->
STATE.bind (STATE.put (x + 1)) (fun _ ->
STATE.bind (STATE.get ()) (fun y ->
STATE.return (assert (y > x)))))
Programmer writes:
( / ) : int -> x:int{x<>0} -> Tot int
let divide_by (x:int) : STEXN unit ...
= if x <> 0 then put (get () / x) else raise "Divide by zero"
Elaborated to:
let divide_by x =
if x <> 0 then STATE_STEXN.lift (STATE.bind (STATE.get())
(fun n -> STATE.put (n / x)))
else STEXN.raise "Divide by zero"
F* infers the least effect of each sub-term
STATE.reify : (St a) -> Ghost (nat -> Tot (a * nat))
Allows us to give weak specification to an effectful function
let incr (r:ref nat) : St unit = (r := (!r + 1))
and prove lemmas about reification of effectful computation
let incr_works (r:ref nat) (h:heap) :
Lemma (sel (snd (STATE.reify (incr r) h)) r = sel h r + 1) = ()
Reducing effectful verification to pure verification
Recent experiments using this for “relational verification”
F* tactics written as effectful F* code (inspired by Lean, Idris)
have access to F*'s proof state (and can efficiently roll it back)
can introspect on F* terms (deep embedding, simply typed)
can be interpreted by F*'s normalizer or compiled to OCaml
user-defined, non-primitive effect: proof state + exceptions monad
noeq type __result a =
| Success of a * proofstate
| Failed of string //error message
* proofstate //the proofstate at time of failure
let __tac (a:Type) = proofstate -> Tot (__result a)
reifiable reflectable new_effect {
TAC : a:Type -> Effect
with repr = __tac ... }
let tactic (a:Type) = unit -> Tac a
Arithmetic expression canonizer (proof automation)
Bitvectors in Vale (proof automation)
Separation logic (proof automation, ongoing, ask Aseem)
Pattern matcher (proof automation, ongoing)
Generate code for inductive types (metaprogramming, ongoing)
Turn F* to Low* buffer code (metaprogramming, upcoming)
Metatheory of subsets of F* (interactive proofs, upcoming)
Pre- and post- conditions are just syntactic sugar:
Pure t (pre : Type0) (post : t->Type0)
= PURE t (fun k -> pre /\ forall y. post y ==> k y)
val factorial : x:int -> Pure int (requires (x >= 0)) (ensures (fun y -> y >= 0))
val factorial : x:int -> Pure (fun k -> x >= 0 /\ forall y. y >= 0 ==> k y)
Same for user-defined effects, like state:
ST t (pre : nat -> Type0) (post : nat -> t -> nat -> Type0)
= STATE t (fun n0 k -> pre n0 /\ forall x n1. post n0 x n1 ==> k x n1)
val incr : unit -> St unit (requires (fun n0 -> True))
(ensures (fun n0 _ n1 -> n1 = n0 + 1))
val incr : unit -> STATE unit (fun n0 k -> k () (n0 + 1))
let incr () = STATE.bind (STATE.get()) (fun x -> STATE.put (x + 1))
incr
against following interface:
STATE.get : unit -> STATE nat (STATE.get_wp())
STATE.put : n:nat -> STATE unit (STATE.put_wp n)
STATE.bind : STATE 'a 'wa ->
(x:'a -> STATE 'b ('wb x)) ->
STATE 'b (STATE.bind_wp 'wa 'wb)
… we compute the weakest precondition for incr
val incr : unit -> STATE unit
(STATE.bind_wp (STATE.get_wp()) (fun x -> STATE.put_wp (x + 1)))
= (fun n0 k -> k () (n0 + 1))
let STATE.wp t = (t -> nat -> Type0) -> (nat -> Type0)
val STATE.return_wp : 'a -> Tot (STATE.wp 'a)
val STATE.bind_wp : (STATE.wp 'a) ->
('a -> Tot (STATE.wp 'b)) ->
Tot (STATE.wp 'b)
val STATE.get_wp : unit -> Tot (STATE.wp nat)
val STATE.put_wp : nat -> Tot (STATE.wp unit)
let STATE.return_wp v = fun p -> p v
let STATE.bind_wp wp f = fun p -> wp (fun v -> f v p)
let STATE.get_wp () = fun p n0 -> p n0 n0
let STATE.put_wp n = fun p _ -> p () n
STATE.wp t = (t -> nat -> Type0) -> (nat -> Type0)
~= nat -> (t * nat -> Type0) -> Type0
This can be automatically derived from the state monad
STATE.repr t = nat -> t * nat
by selective continuation-passing style (CPS) returning Type0
This works well for large class of monadic effects:
state, exceptions, continuations, etc.
From monadic effect definition we can derive a correct-by-construction weakest-precondition calculus for this effect.
Two calculi
Two translations from well-typed DMF terms to EMF*
*
-translation: gives specification (selective CPS)
*
-trans gives correct Dijkstra monad for elaborated terms
PURE
is the only primitive EMF* effect (F* also has DIV
)
A WP for PURE
is of type
PURE.wp t = (t -> Type0) -> Type0
PURE
is exactly the continuation monad
Total Correctness of PURE
:
If ⊢ e : PURE t wp
and ⊢ wp p
then e ↝* v
s.t. ⊨ p v
Say we have a term e : nat -> t × nat
From logical relation, we get
s₀ : nat -> PURE (t × nat) (e* s₀)
From previous and correctness of PURE
, we get
Correctness of STATE
If ⊢ e : nat -> t × nat
and ⊢ e* s₀ p
then e s₀ ↝* (v,s)
s.t. ⊨ p (v,s)
*
-translation preserves equality
e*
is monotonic: maps weaker post's to weaker pre's
(∀x. p₁ ⇒ p₂) ⇒ e* p₁ ⇒ e* p₂
e*
is conjunctive: distributes over ∧ and ∀
e* (fun x -> p₁ x ∧ p₂ x) ⇔ e* p₁ ∧ e* p₂
so for any DMF monad we produce correct Dijkstra monad,
that's usable within the F* verification system
Improve tactics, balance automation and control
Better treatment of effects: Dijkstra monads for free v2
Studying more effects: probabilities, concurrency, …
Effect masking: hiding exceptions, state, divergence(!?), …
Verified interoperability: F* (OCaml) + Low* (C) + Vale (ASM)
Further work out metatheory and self-certify core type-checker
Join the team at Inria Paris and Microsoft Research