Last time, we saw how to deeply embed small languages within F*, develop their metatheory, and proof tools to use on them
This time: How can we embed full-fledged, effectful programming languages within F*?
Program libraries to model effects
Derive effectful actions for primitive operations
read, write, alloc, throw, catch, fork, join, etc.
Write programs against these models and verify them with refined computation types
Extract them to OCaml, F#, C, Wasm, … with primitive effects
F*:
let incr (r:ref int) : ST unit
(ensures fun h0 _ h1 -> modifies {r} h0 h1 /\ h1.[r] = h0.[r] + 1)
= r := !r + 1
ML:
let incr (r:ref int) : unit = r := !r + 1
C:
void incr (int *r) { *r = *r + 1; }“Values” aka unconditionally total terms
Two classes of types
t): int, list int, …
C): pure, divergent, stateful, …
Dependent function types of the form: x:t -> C
Two forms of refinement types
x:t{p}
ST t pre post
type st (a:Type) = int -> (a * int)
new_effect {
STATE : a:Type -> Effect
with repr = st;
return = fun (a:Type) (x:a) (h:int) -> x, h;
bind = fun (a b:Type) (f:st a) (g:a -> st b) (h:int) ->
let z, h' = f h in g z h';
get = fun () (h:int) -> h,h;
put = fun (h:int) _ -> (),h
}this monadic definition is the model F* uses to verify stateful code
But, state can be primitively implemented under the hood if you like
Can now program effectful computations directly:
let double () : ST unit = put (get () + get ())But, we want to prove properties about them too!
Choose an abstraction in which to reason about effectful programs
Encode the abstraction in an indexed monad
STATE a (pre:state -> prop) (post:a -> state -> prop)
STATE a (w: wp a)
Rig F*'s effect system to infer a type for effectful programs in your chosen abstraction
w for a program e means
forall post h0. //for all postconditions and initial states
w post h0 ==> //given the weakest precondition is valid for post and h0
e h0 ~>* (v, h1) /\ //the computation reduces to a value and final state
post (v, h1) //that validate the postconditionWhat is the type of a WP predicate transformer?
let st_wp a : st_post a -> st_pre //transforms postconditions to preconditions
where st_post a : (a * s) -> prop //postconditions relate results to memories
and st_pre : s -> prop //preconditions are memory predicatesDijkstra monad for state
let st_wp a : st_post a -> st_pre //transforms postconditions to preconditions
where st_post a : (a * s) -> prop //postconditions relate results to memories
and st_pre : s -> prop //preconditions are memory predicates
cont r a = (a -> r) -> rTake the continuation monad with result type prop
s -> cont prop (a * s)
= s -> ((a * s) -> prop) -> prop
~ ((a * s) -> prop) -> s -> prop
= st_post a -> st_pre
= st_wp aWPs for state are just the state monad transformer
applied to the continuation monad with result type prop
stateT m a = s -> m (a * s)
st_wp a = stateT (cont prop) aYou can define a weakest precondition calculus for m-effectful computations whose WPs have the form
m_wp a = mT (cont prop) a
m transformer on the continuation monad with result type prop
And design an effect so that F* infers computations types of the
form M a (wp : m_wp a)
E.g., for exceptions
ex_wp a = ex_t (cont prop) a
= (option a -> prop) -> propFor state + exceptions
st_ex_wp a = state_t (ex_t (cont prop)) a
= s -> (ex_t (cont prop) a * s)
= s -> ((option a * state) -> prop) -> prop
~ ((option a * state) -> prop) -> s -> prop
= st_ex_post a -> st_ex_preReasoning about continuations: great for a core logic / not for a human
Hoare logic-style pre-conditions and post-conditions
ST a (pre: s -> prop) (post: s -> a -> s -> prop) =
STATE a (fun k s0 -> pre s0 /\ (forall x s1. post s0 x s1 ==> k (x, s1)))stateful pre-condition is predicate on initial states
post-condition is relation on initial states, results, and final states
val get ()
: ST s
(requires fun s -> True)
(ensures fun s0 result s1 -> s0 == result /\ result == s1)
val put s
: ST unit
(requires fun _ -> True)
(ensures fun _ _ s1 -> s1 == s0)double in our chosen abstractionlet double ()
: ST unit
(requires fun _ -> True)
(ensures fun s0 _ s1 -> s1 = 2*s0)
= put (get () + get ())For some applications, you may only be interested in enforcing certain security properties
Given a lattice of “security labels”:
Labels: L
lo, hi, etc.
{alice}, {bob}, {alice,bob}, …
With some partial order <=
And a least upper bound, lub, where l <= lub l m /\ m <= lub l m
For a notion of program memories mem = loc -> value,
And given a labelling of locations, e.g., label_of: loc -> label
Define a label-indexed equivalence relation on memories:
m0 ~_l m1 = forall loc. label_of loc <= l ==> m0 loc == m1 locFor a program P : mem -> mem,
P is noninterferent at label l, if and only if
forall m0 m1. m0 ~_l m1 ==> P m0 ~_l P m1
i.e., P does not leak information from locations labeled greater
than l to locations labeled l or below.