Example: memcpy
The snippet below implements a classic memcpy function, copying len
elements of type a from src into dst.
module MemCpy
open FStar.HyperStack.ST
module S = FStar.Seq
module B = LowStar.Buffer
module M = LowStar.Modifies
module U32 = FStar.UInt32
module ST = FStar.HyperStack.ST
let slice_plus_one #a (s1 s2: S.seq a) (i: nat): Lemma
(requires (
i < S.length s1 /\
i < S.length s2 /\
S.equal (S.slice s1 0 i) (S.slice s2 0 i) /\
S.index s1 i == S.index s2 i))
(ensures (
S.equal (S.slice s1 0 (i + 1)) (S.slice s2 0 (i + 1))))
[ SMTPat (S.slice s1 0 (i + 1)); SMTPat (S.slice s2 0 (i + 1)) ]
=
let x = S.index s1 i in
let s1' = S.append (S.slice s1 0 i) (S.cons x S.empty) in
let s2' = S.append (S.slice s2 0 i) (S.cons x S.empty) in
assert (S.equal s1' (S.slice s1 0 (i + 1)));
assert (S.equal s2' (S.slice s2 0 (i + 1)))
open LowStar.BufferOps
#set-options "--max_fuel 0 --max_ifuel 0"
val memcpy: #a:eqtype -> src:B.buffer a -> dst:B.buffer a -> len:U32.t -> Stack unit
(requires fun h0 ->
let l_src = M.loc_buffer src in
let l_dst = M.loc_buffer dst in
B.live h0 src /\ B.live h0 dst /\
B.length src = U32.v len /\
B.length dst = U32.v len /\
M.loc_disjoint l_src l_dst)
(ensures fun h0 () h1 ->
let l_src = M.loc_buffer src in
let l_dst = M.loc_buffer dst in
B.live h1 src /\
B.live h1 dst /\
M.(modifies l_dst h0 h1) /\
S.equal (B.as_seq h1 dst) (B.as_seq h0 src))
let memcpy #a src dst len =
let h0 = ST.get () in
let inv h (i: nat) =
B.live h src /\ B.live h dst /\
M.(modifies (loc_buffer dst) h0 h) /\
i <= U32.v len /\
S.equal (Seq.slice (B.as_seq h src) 0 i) (Seq.slice (B.as_seq h dst) 0 i)
in
let body (i: U32.t{ 0 <= U32.v i /\ U32.v i < U32.v len }): Stack unit
(requires (fun h -> inv h (U32.v i)))
(ensures (fun h0 () h1 -> inv h0 (U32.v i) /\ inv h1 (U32.v i + 1)))
=
let open B in
dst.(i) <- src.(i)
in
C.Loops.for 0ul len inv body
let main (): Stack C.exit_code
(requires fun _ -> True)
(ensures fun h _ h' -> M.modifies M.loc_none h h')
=
push_frame ();
let src = B.alloca_of_list [ 1UL; 2UL ] in
let dst = B.alloca 0UL 2ul in
memcpy src dst 2ul;
pop_frame ();
C.EXIT_SUCCESS
This example showcases several features of Low*. We only present the code from a high-level point of view.
The code starts by opening several modules that are part of the "Low* standard library".
Bufferis our model of stack- and heap- allocated C arrays (described in The buffer library)Seqis the sequence abstraction from the F* standard library, whichBufferuses to reflect the contents of a given buffer in a given heap at the proof levelModifiesprovides a universal modifies clause over buffers, references and regions (described in The modifies clauses library)UInt32is a model of the C11uint32_ttype, reflected at the proof level using natural numbers (described in Machine integers)HyperStackis our model of the C memory layout (described in The memory model)CandC.Loopsexpose some C concepts to F* (described in Low* system libraries)
The first definition is a lemma over sequences: if two sequences are equal over
the slice [0; i) and their i-th element is equal, then they are equal
over the slice [0; i + 1). This lemma is required to prove the functional
correctness of memcpy. Lemmas are erased and do not appear in the generated
code, so it is safe to mix lemmas with Low* code.
Next, then memcpy function is defined and annotated with pre- and
post-conditions, using liveness and disjointness predicates. The post-condition
states that after calling memcpy src dst len, the destination and the source
have the same contents up to index len.
The implementation of memcpy uses a C-style for loop with a loop
invariant and a loop body. Alternatively, one could write a recursive function,
relying on the C compiler to hopefully perform tail-call optimization.
Finally, the main function uses push_frame and pop_frame, two
combinators from the memory model that indicate that this code conceptually
executes in a new stack frame. In this new stack frame, two test arrays are
allocated on the stack. These are arrays of 64-bit unsigned integers, as denoted
by the UL Low* suffix for machine integers. The memcpy function is
called over these two arrays. From a verification perspective, since the stack
frame is freed after calling main, we can successfully state that main
modifies no buffer.