Effectful Programming in F*

fstar-logo

Nik Swamy

Microsoft Research

Oregon Programming Languages Summer School (OPLSS), 2021

Shallow embeddings of effectful languages

  • 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*?

General approach to effectful programming

  • 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 and Computations

  • “Values” aka unconditionally total terms

  • Two classes of types

    • Value types (t): int, list int,
    • Computation types (C): pure, divergent, stateful,
  • Dependent function types of the form: x:t -> C

    • F* is call-by-value
    • argument can't have side-effects, so value type
  • Two forms of refinement types

    • Refined value types: x:t{p}
    • Refined computation types:
      • Stateful computations that can read and write the heap: ST t pre post

Monadic effects in F*

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

    • It is an executable model, and you can choose to run your programs in the model
  • But, state can be primitively implemented under the hood if you like

    • for instance by C stack+heap

Effectful Programs, Directly

  • Can now program effectful computations directly:

    let double () : ST unit = put (get () + get ())
  • But, we want to prove properties about them too!

Specifying Effectful Programs

  • Choose an abstraction in which to reason about effectful programs

    • E.g., A Hoare logic for stateful programs
  • Encode the abstraction in an indexed monad

    • E.g., A Hoare monad: STATE a (pre:state -> prop) (post:a -> state -> prop)
    • Or, a Dijktra monad: STATE a (w: wp a)
  • Rig F*'s effect system to infer a type for effectful programs in your chosen abstraction

Weakest preconditions for stateful programs

  • A WP predicate transformer 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

WP inference $\sim$ CPS tranform

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
  • Hey, wait a minute isn't that like the continuation monad?
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

Deriving WP calculi for monadic effects

  • You can define a weakest precondition calculus for m-effectful computations whose WPs have the form

    m_wp a = mT (cont prop) a
    • the 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

Derived Specifications: Hoare triples

  • 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)

Now we can prove double in our chosen abstraction

let double ()
  : ST unit
    (requires fun _ -> True)
    (ensures fun s0 _ s1 -> s1 = 2*s0)
  = put (get () + get ())

Demo

Pick your abstraction for program reasoning

  • For some applications, you may only be interested in enforcing certain security properties

    • E.g., lattice-based information flow control

A Primer on Information Flow Control

  • Given a lattice of “security labels”:

    • Labels: L

      • lo, hi, etc.
      • Sets of principals: {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

Noninterference

  • 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.

Enforcing Information Flow Control with an Indexed Effect