Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
284 changes: 284 additions & 0 deletions lib/pulse/lib/Pulse.Lib.Vector.fst
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
(*
Copyright 2025 Microsoft Research

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*)

module Pulse.Lib.Vector

#lang-pulse

open Pulse.Lib.Pervasives
module Seq = FStar.Seq
module SZ = FStar.SizeT
module A = Pulse.Lib.Array
module Box = Pulse.Lib.Box
open Pulse.Lib.Box

/// Internal representation — one outer box wrapping a plain struct
noeq
type vector_internal (t:Type0) = {
arr: A.array t;
sz: SZ.t;
cap: SZ.t;
default_val: t;
}

let vector t = box (vector_internal t)

/// The is_vector predicate
let is_vector #t (v:vector t) (s:Seq.seq t) (cap:SZ.t) : slprop =
exists* (vi: vector_internal t) (buf:Seq.seq t).
pts_to v vi **
A.pts_to vi.arr buf **
pure (
SZ.v vi.sz == Seq.length s /\
SZ.v vi.cap == A.length vi.arr /\
vi.cap == cap /\
SZ.v vi.sz <= SZ.v cap /\
SZ.v cap > 0 /\
A.is_full_array vi.arr /\
Seq.length buf == SZ.v cap /\
s `Seq.equal` Seq.slice buf 0 (SZ.v vi.sz)
)

/// Create
#push-options "--warn_error -288"
fn create (#t:Type0) (default:t) (n:SZ.t{SZ.v n > 0})
returns v:vector t
ensures is_vector v (Seq.create (SZ.v n) default) n
{
let arr = A.alloc default n;
A.pts_to_len arr;
let vi : vector_internal t = Mkvector_internal #t arr n n default;
let v = alloc vi;
rewrite (A.pts_to arr (Seq.create (SZ.v n) default))
as (A.pts_to vi.arr (Seq.create (SZ.v n) default));
fold (is_vector v (Seq.create (SZ.v n) default) n);
v
}
#pop-options

/// Read element at index
fn at (#t:Type0) (v:vector t) (i:SZ.t)
(#s:erased (Seq.seq t){SZ.v i < Seq.length s}) (#cap:erased SZ.t)
preserves is_vector v s cap
returns x:t
ensures pure (x == Seq.index s (SZ.v i))
{
unfold (is_vector v s cap);
with vi buf. _;
let cur = !v;
rewrite (A.pts_to vi.arr buf) as (A.pts_to cur.arr buf);
A.pts_to_len cur.arr;
let x = A.op_Array_Access cur.arr i;
rewrite (A.pts_to cur.arr buf) as (A.pts_to vi.arr buf);
fold (is_vector v s cap);
x
}

/// Write element at index
fn set (#t:Type0) (v:vector t) (i:SZ.t) (x:t)
(#s:erased (Seq.seq t){SZ.v i < Seq.length s}) (#cap:erased SZ.t)
requires is_vector v s cap
ensures is_vector v (Seq.upd s (SZ.v i) x) cap
{
unfold (is_vector v s cap);
with vi buf. _;
let cur = !v;
rewrite (A.pts_to vi.arr buf) as (A.pts_to cur.arr buf);
A.pts_to_len cur.arr;
A.op_Array_Assignment cur.arr i x;
with buf'. assert (A.pts_to cur.arr buf');
rewrite (A.pts_to cur.arr buf') as (A.pts_to vi.arr buf');
fold (is_vector v (Seq.upd s (SZ.v i) x) cap)
}

/// Get current size
fn size (#t:Type0) (v:vector t) (#s:erased (Seq.seq t)) (#cap:erased SZ.t)
preserves is_vector v s cap
returns n:SZ.t
ensures pure (SZ.v n == Seq.length s)
{
unfold (is_vector v s cap);
with vi buf. _;
let cur = !v;
fold (is_vector v s cap);
cur.sz
}

/// Get current capacity
fn capacity (#t:Type0) (v:vector t) (#s:erased (Seq.seq t)) (#cap:erased SZ.t)
preserves is_vector v s cap
returns n:SZ.t
ensures pure (n == reveal cap)
{
unfold (is_vector v s cap);
with vi buf. _;
let cur = !v;
fold (is_vector v s cap);
cur.cap
}

/// Push back — append element, double capacity if full
#push-options "--warn_error -288 --z3rlimit_factor 4"
fn push_back (#t:Type0) (v:vector t) (x:t)
(#s:erased (Seq.seq t)) (#cap:erased SZ.t)
requires is_vector v s cap **
pure (Seq.length s < SZ.v cap \/ SZ.fits (SZ.v cap + SZ.v cap))
ensures exists* (cap':SZ.t).
is_vector v (Seq.snoc s x) cap' **
pure (SZ.v cap' >= Seq.length s + 1 /\ SZ.v cap' > 0 /\
(Seq.length s < SZ.v cap ==> cap' == cap) /\
(Seq.length s == SZ.v cap ==> SZ.v cap' == SZ.v cap + SZ.v cap))
{
unfold (is_vector v s cap);
with vi buf. _;
let cur = !v;
rewrite (A.pts_to vi.arr buf) as (A.pts_to cur.arr buf);
A.pts_to_len cur.arr;

if SZ.lt cur.sz cur.cap
{
A.op_Array_Assignment cur.arr cur.sz x;
with buf'. assert (A.pts_to cur.arr buf');
let new_vi = Mkvector_internal cur.arr (SZ.add cur.sz 1sz) cur.cap cur.default_val;
( := ) v new_vi;
rewrite (A.pts_to cur.arr buf') as (A.pts_to new_vi.arr buf');
fold (is_vector v (Seq.snoc s x) cap);
()
}
else
{
let new_cap = SZ.add cur.cap cur.cap;
let new_arr = A.alloc cur.default_val new_cap;
A.pts_to_len new_arr;
let _sq = A.memcpy_l cur.cap cur.arr new_arr;
A.op_Array_Assignment new_arr cur.sz x;
with buf'. assert (A.pts_to new_arr buf');
A.free cur.arr;
let new_vi = Mkvector_internal new_arr (SZ.add cur.sz 1sz) new_cap cur.default_val;
( := ) v new_vi;
rewrite (A.pts_to new_arr buf') as (A.pts_to new_vi.arr buf');
fold (is_vector v (Seq.snoc s x) new_cap);
()
}
}
#pop-options

/// Pop back — remove last element, halve capacity when size == floor(cap/2)
#push-options "--warn_error -288 --z3rlimit_factor 4"
fn pop_back (#t:Type0) (v:vector t)
(#s:erased (Seq.seq t){Seq.length s > 0}) (#cap:erased SZ.t)
requires is_vector v s cap
returns x:t
ensures exists* (cap':SZ.t).
is_vector v (Seq.slice s 0 (Seq.length s - 1)) cap' **
pure (x == Seq.index s (Seq.length s - 1) /\
SZ.v cap' >= Seq.length s - 1 /\ SZ.v cap' > 0 /\
(Seq.length s - 1 == SZ.v cap / 2 /\ SZ.v cap / 2 > 0
==> SZ.v cap' == SZ.v cap / 2) /\
(~(Seq.length s - 1 == SZ.v cap / 2 /\ SZ.v cap / 2 > 0)
==> cap' == cap))
{
unfold (is_vector v s cap);
with vi buf. _;
let cur = !v;
rewrite (A.pts_to vi.arr buf) as (A.pts_to cur.arr buf);
A.pts_to_len cur.arr;

let last_idx = SZ.sub cur.sz 1sz;
let x = A.op_Array_Access cur.arr last_idx;

let new_sz = last_idx;
let half_cap = SZ.div cur.cap 2sz;
let should_shrink = SZ.gt half_cap 0sz && SZ.eq new_sz half_cap;

if should_shrink
{
let new_arr = A.alloc cur.default_val half_cap;
A.pts_to_len new_arr;
let _sq = A.memcpy_l new_sz cur.arr new_arr;
A.free cur.arr;
let new_vi = Mkvector_internal new_arr new_sz half_cap cur.default_val;
( := ) v new_vi;
with buf_new. assert (A.pts_to new_arr buf_new);
rewrite (A.pts_to new_arr buf_new) as (A.pts_to new_vi.arr buf_new);
fold (is_vector v (Seq.slice s 0 (Seq.length s - 1)) half_cap);
x
}
else
{
let new_vi = Mkvector_internal cur.arr new_sz cur.cap cur.default_val;
( := ) v new_vi;
rewrite (A.pts_to cur.arr buf) as (A.pts_to new_vi.arr buf);
fold (is_vector v (Seq.slice s 0 (Seq.length s - 1)) cap);
x
}
}
#pop-options

/// Resize
#push-options "--warn_error -288 --z3rlimit_factor 4"
fn resize (#t:Type0) (v:vector t) (new_size:SZ.t{SZ.v new_size > 0})
(#s:erased (Seq.seq t)) (#cap:erased SZ.t)
requires is_vector v s cap
ensures exists* (s':Seq.seq t) (cap':SZ.t).
is_vector v s' cap' **
pure (Seq.length s' == SZ.v new_size /\
SZ.v cap' >= SZ.v new_size /\ SZ.v cap' > 0 /\
(forall (i:nat). i < Seq.length s /\ i < SZ.v new_size ==>
Seq.index s' i == Seq.index s i))
{
unfold (is_vector v s cap);
with vi buf. _;
let cur = !v;
rewrite (A.pts_to vi.arr buf) as (A.pts_to cur.arr buf);
A.pts_to_len cur.arr;

if SZ.lte new_size cur.cap
{
let new_vi = Mkvector_internal cur.arr new_size cur.cap cur.default_val;
( := ) v new_vi;
rewrite (A.pts_to cur.arr buf) as (A.pts_to new_vi.arr buf);
fold (is_vector v (Seq.slice buf 0 (SZ.v new_size)) cap);
()
}
else
{
let new_arr = A.alloc cur.default_val new_size;
A.pts_to_len new_arr;
let _sq = A.memcpy_l cur.sz cur.arr new_arr;
A.free cur.arr;
let new_vi = Mkvector_internal new_arr new_size new_size cur.default_val;
( := ) v new_vi;
with buf'. assert (A.pts_to new_arr buf');
rewrite (A.pts_to new_arr buf') as (A.pts_to new_vi.arr buf');
fold (is_vector v (Seq.slice buf' 0 (SZ.v new_size)) new_size);
()
}
}
#pop-options

/// Free
fn free (#t:Type0) (v:vector t) (#s:erased (Seq.seq t)) (#cap:erased SZ.t)
requires is_vector v s cap
{
unfold (is_vector v s cap);
with vi buf. _;
let cur = !v;
rewrite (A.pts_to vi.arr buf) as (A.pts_to cur.arr buf);
A.free cur.arr;
Box.free v;
()
}
113 changes: 113 additions & 0 deletions lib/pulse/lib/Pulse.Lib.Vector.fsti
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
(*
Copyright 2025 Microsoft Research

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*)

(**
A dynamically-resizable vector for Pulse.

Doubles capacity on push_back when full.
Halves capacity on pop_back when size == floor(capacity / 2).
Backed by a flat array with a stored default value for unused slots.
*)

module Pulse.Lib.Vector

#lang-pulse

open Pulse.Lib.Pervasives
module Seq = FStar.Seq
module SZ = FStar.SizeT

/// Abstract vector type
val vector (t:Type0) : Type0

/// Predicate relating a vector to its logical contents and capacity
val is_vector (#t:Type0) ([@@@mkey]v:vector t) (s:Seq.seq t) (cap:SZ.t) : slprop

/// Create a new vector with n elements all set to default.
/// Capacity is initially n. Requires n > 0.
fn create (#t:Type0) (default:t) (n:SZ.t{SZ.v n > 0})
returns v:vector t
ensures is_vector v (Seq.create (SZ.v n) default) n

/// Read element at index i.
/// Requires: i < size
fn at (#t:Type0) (v:vector t) (i:SZ.t)
(#s:erased (Seq.seq t){SZ.v i < Seq.length s}) (#cap:erased SZ.t)
preserves is_vector v s cap
returns x:t
ensures pure (x == Seq.index s (SZ.v i))

/// Write element at index i.
/// Requires: i < size
fn set (#t:Type0) (v:vector t) (i:SZ.t) (x:t)
(#s:erased (Seq.seq t){SZ.v i < Seq.length s}) (#cap:erased SZ.t)
requires is_vector v s cap
ensures is_vector v (Seq.upd s (SZ.v i) x) cap

/// Get the current number of elements
fn size (#t:Type0) (v:vector t) (#s:erased (Seq.seq t)) (#cap:erased SZ.t)
preserves is_vector v s cap
returns n:SZ.t
ensures pure (SZ.v n == Seq.length s)

/// Get the current capacity
fn capacity (#t:Type0) (v:vector t) (#s:erased (Seq.seq t)) (#cap:erased SZ.t)
preserves is_vector v s cap
returns n:SZ.t
ensures pure (n == cap)

/// Append element to end. Doubles capacity if full.
/// Precondition: either there is room, or doubling is representable.
fn push_back (#t:Type0) (v:vector t) (x:t)
(#s:erased (Seq.seq t)) (#cap:erased SZ.t)
requires is_vector v s cap **
pure (Seq.length s < SZ.v cap \/ SZ.fits (SZ.v cap + SZ.v cap))
ensures exists* (cap':SZ.t).
is_vector v (Seq.snoc s x) cap' **
pure (SZ.v cap' >= Seq.length s + 1 /\ SZ.v cap' > 0 /\
(Seq.length s < SZ.v cap ==> cap' == cap) /\
(Seq.length s == SZ.v cap ==> SZ.v cap' == SZ.v cap + SZ.v cap))

/// Remove and return the last element. Halves capacity when size == floor(cap/2).
/// Requires: vector is non-empty
fn pop_back (#t:Type0) (v:vector t)
(#s:erased (Seq.seq t){Seq.length s > 0}) (#cap:erased SZ.t)
requires is_vector v s cap
returns x:t
ensures exists* (cap':SZ.t).
is_vector v (Seq.slice s 0 (Seq.length s - 1)) cap' **
pure (x == Seq.index s (Seq.length s - 1) /\
SZ.v cap' >= Seq.length s - 1 /\ SZ.v cap' > 0 /\
(Seq.length s - 1 == SZ.v cap / 2 /\ SZ.v cap / 2 > 0
==> SZ.v cap' == SZ.v cap / 2) /\
(~(Seq.length s - 1 == SZ.v cap / 2 /\ SZ.v cap / 2 > 0)
==> cap' == cap))

/// Resize the vector to new_size elements.
/// Preserves the first min(old_size, new_size) elements.
fn resize (#t:Type0) (v:vector t) (new_size:SZ.t{SZ.v new_size > 0})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fn resize (#t:Type0) (v:vector t) (new_size:SZ.t{SZ.v new_size > 0})
fn resize (#t:Type0) (v:vector t) (new_size:SZ.t)

??

(#s:erased (Seq.seq t)) (#cap:erased SZ.t)
requires is_vector v s cap
ensures exists* (s':Seq.seq t) (cap':SZ.t).
is_vector v s' cap' **
pure (Seq.length s' == SZ.v new_size /\
SZ.v cap' >= SZ.v new_size /\ SZ.v cap' > 0 /\
(forall (i:nat). i < Seq.length s /\ i < SZ.v new_size ==>
Seq.index s' i == Seq.index s i))

/// Free the vector and its backing storage
fn free (#t:Type0) (v:vector t) (#s:erased (Seq.seq t)) (#cap:erased SZ.t)
requires is_vector v s cap
Loading