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 postcondition
What 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 predicates
Dijkstra 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) -> r
Take 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 a
WPs 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) a
You 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) -> prop
For 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_pre
Reasoning 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 loc
For 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.