spec/containers/bitlist.bend source
spec/containers/bitlist.bend on the hub · documented module
import Baseimport ../lib/common.bend as Cimport ./bitset.bend as BSimport ../../src/containers/types/bitlist.bend as Eimport ../lib/sequence.bend as V# Independent model of a bit list: a sequence of Booleans (bit 0 first), an# optional maximum length and the capacity exponent of the storage (a bit# list holds at most 32 * 2^cexp bits). Nothing here refers to words,# shifts, masks or arrays.## Shape of the specification, following verified bit-sequence developments:# * SSZ Bitlist[N] as in the ConsenSys eth2.0-dafny SSZ proofs# (src/dafny/ssz/BitListSeDes.dfy, wiki/ssz-notes.md): a bitlist is a# sequence of booleans whose length is bounded by N; appending past N# is not allowed.# * Lean 4 core `List Bool` / `BitVec` lemmas (Init.Data.BitVec.Lemmas,# getLsbD_concat / getLsbD_cons, List.getElem_set): reading bit i after# writing bit j is the written bit when i = j and the old bit otherwise;# appending one bit extends the sequence by exactly that bit.# * SPARK Ada formal vectors (Formal_Vectors: Length / Element /# Replace_Element / Append postconditions): every operation is stated by# its effect on the whole model plus frame conditions (proofs/containers# /bitlist/proof.bend).type Model is Data: M{limit: Maybe<&2, Nat>, cexp: Nat, bits: List<&2, Bool>}def new() -> Model: M{None{}, 31n, Nil{}}def with_limit(+n: Nat) -> Model: M{Some{n}, 31n, Nil{}}# One more bit fits under the limit and in the storage.def below(lim: Maybe<&2, Nat>, +n: Nat) -> Bool: match lim: case None{}: True{} case Some{k}: Nat.is_lt(n, k)def room(lim: Maybe<&2, Nat>, +c: Nat, +n: Nat) -> Bool: Bool.and(below(lim, n), Nat.is_lt(n, Nat.mul(C.pow2(c), 32n)))# Number of True bits (the popcount of the bitset specification).def count(xs: List<&2, Bool>) -> Nat: BS.count(xs)def bit(x: Maybe<&2, Bool>) -> Result<&2, &2, E.Error, Bool>: match x: case None{}: Fail{E.IndexOutOfRange{}} case Some{b}: Done{b}def last_bit(x: Maybe<&2, Bool>) -> Result<&2, &2, E.Error, Bool>: match x: case None{}: Fail{E.Empty{}} case Some{b}: Done{b}# Assign bit i; out of range leaves the list unchanged and fails.def assign_at(x: Maybe<&2, Bool>, +l: Maybe<&2, Nat>, +c: Nat, +xs: List<&2, Bool>, i: Nat, v: Bool) -> Model & E.Obs: match x: case None{}: (M{l, c, xs}, E.OUnit{Fail{E.IndexOutOfRange{}}}) case Some{b}: (M{l, c, C.update(Bool, xs, i, v)}, E.OUnit{Done{Unit{}}})def push_if(ok: Bool, +l: Maybe<&2, Nat>, +c: Nat, +xs: List<&2, Bool>, v: Bool) -> Model & E.Obs: match ok: case True{}: (M{l, c, C.snoc(Bool, xs, v)}, E.OUnit{Done{Unit{}}}) case False{}: (M{l, c, xs}, E.OUnit{Fail{E.Full{}}})def pop(+l: Maybe<&2, Nat>, +c: Nat, xs: List<&2, Bool>) -> Model & E.Obs: match xs: case Nil{}: (M{l, c, Nil{}}, E.OBit{Fail{E.Empty{}}}) case Con{+h, +t}: (M{l, c, C.init(Bool, Con{h, t})}, E.OBit{last_bit(C.last(Bool, Con{h, t}))})def step_parts(+l: Maybe<&2, Nat>, +c: Nat, +xs: List<&2, Bool>, op: E.Op) -> Model & E.Obs: match op: case E.Length{}: (M{l, c, xs}, E.ONat{C.length(Bool, xs)}) case E.Limit{}: (M{l, c, xs}, E.OLimit{l}) case E.Get{+i}: (M{l, c, xs}, E.OBit{bit(C.nth(Bool, xs, i))}) case E.Assign{+i, +v}: assign_at(C.nth(Bool, xs, i), l, c, xs, i, v) case E.Push{+v}: push_if(room(l, c, C.length(Bool, xs)), l, c, xs, v) case E.Pop{}: pop(l, c, xs) case E.Clear{}: (M{l, c, Nil{}}, E.OUnit{Done{Unit{}}}) case E.Count{}: (M{l, c, xs}, E.ONat{count(xs)}) case E.ToList{}: (M{l, c, xs}, E.OBits{xs})# Every operation: the next model and the observation.def step(m: Model, op: E.Op) -> Model & E.Obs: M{+l, +c, +xs} = m step_parts(l, c, xs, op)def cons_obs(o: E.Obs, r: Model & List<&2, E.Obs>) -> Model & List<&2, E.Obs>: (m, os) = r (m, Con{o, os})def run(ops: List<&2, E.Op>, +m: Model) -> Model & List<&2, E.Obs>: match ops: case Nil{}: (m, Nil{}) case Con{+op, rest}: cons_obs(Pair.snd(Model, E.Obs, step(m, op)), run(rest, Pair.fst(Model, E.Obs, step(m, op))))# from_bools: the bits pushed one by one onto the unbounded list.def push_all(bs: List<&2, Bool>, +m: Model) -> Model: match bs: case Nil{}: m case Con{+b, t}: push_all(t, Pair.fst(Model, E.Obs, step(m, E.Push{b})))def from_bools(+bs: List<&2, Bool>) -> Model: push_all(bs, new())# ---- contract (SPARK formal containers) ----# Each `<Subprogram>.<clause>` definition below states one Post clause of# that SPARK subprogram, as a proposition on this model; the table names the# clauses. proofs/containers/bitlist/ proves every clause under its clause name,# and its `impl` lemma carries them to the implementation.## Contracts of the specification, in the style of the SPARK formal vector# postconditions (Replace_Element: Element(Container, I) = New_Item and every# other element unchanged; Append: Length + 1 and Last_Element = New_Item)# and of the Lean 4 List lemmas (List.getElem_set_self / getElem_set_ne,# List.length_concat, List.dropLast_concat): each law is about the model# step, and proofs/containers/bitlist/steps.bend makes the implementation# step equal to step on every good bitlist, so each law holds for the# implementation.## SPARK subprogram (.ads line) ours clauses# Replace_Element (384) assign get_assign_same, get_assign_other, length_assign# Append (706) push length_push, get_push_last, count_push# Delete_Last (866) pop pop_pushdef bits(m: Model) -> List<&2, Bool>: match m: case M{l, c, xs}: xs# Replace_Element (384)def Replace_Element.get_assign_same(+l: Maybe<&2, Nat>, +c: Nat, +xs: List<&2, Bool>, +i: Nat, +v: Bool, +h: {Nat.is_lt(i, C.length(Bool, xs)) == True{} : Bool}) -> Type: {Pair.snd(Model, E.Obs, step(Pair.fst(Model, E.Obs, step(M{l, c, xs}, E.Assign{i, v})), E.Get{i})) == E.OBit{Done{v}} : E.Obs}# Replace_Element (384)def Replace_Element.get_assign_other(+l: Maybe<&2, Nat>, +c: Nat, +xs: List<&2, Bool>, +i: Nat, +j: Nat, +v: Bool, +h: {Nat.is_lt(i, C.length(Bool, xs)) == True{} : Bool}, +ne: {Nat.is_eq(i, j) == False{} : Bool}) -> Type: {Pair.snd(Model, E.Obs, step(Pair.fst(Model, E.Obs, step(M{l, c, xs}, E.Assign{i, v})), E.Get{j})) == Pair.snd(Model, E.Obs, step(M{l, c, xs}, E.Get{j})) : E.Obs}# Replace_Element (384)def Replace_Element.length_assign(+l: Maybe<&2, Nat>, +c: Nat, +xs: List<&2, Bool>, +i: Nat, +v: Bool, +h: {Nat.is_lt(i, C.length(Bool, xs)) == True{} : Bool}) -> Type: {C.length(Bool, bits(Pair.fst(Model, E.Obs, step(M{l, c, xs}, E.Assign{i, v})))) == C.length(Bool, xs) : Nat}# Append (706)def Append.length_push(+l: Maybe<&2, Nat>, +c: Nat, +xs: List<&2, Bool>, +v: Bool, +h: {room(l, c, C.length(Bool, xs)) == True{} : Bool}) -> Type: {C.length(Bool, bits(Pair.fst(Model, E.Obs, step(M{l, c, xs}, E.Push{v})))) == 1n+C.length(Bool, xs) : Nat}# Append (706)def Append.get_push_last(+l: Maybe<&2, Nat>, +c: Nat, +xs: List<&2, Bool>, +v: Bool, +h: {room(l, c, C.length(Bool, xs)) == True{} : Bool}) -> Type: {Pair.snd(Model, E.Obs, step(Pair.fst(Model, E.Obs, step(M{l, c, xs}, E.Push{v})), E.Get{C.length(Bool, xs)})) == E.OBit{Done{v}} : E.Obs}# Delete_Last (866)def Delete_Last.pop_push(+l: Maybe<&2, Nat>, +c: Nat, +xs: List<&2, Bool>, +v: Bool, +h: {room(l, c, C.length(Bool, xs)) == True{} : Bool}) -> Type: {step(Pair.fst(Model, E.Obs, step(M{l, c, xs}, E.Push{v})), E.Pop{}) == (M{l, c, xs}, E.OBit{Done{v}}) : Model & E.Obs}# Append (706)def Append.count_push(+l: Maybe<&2, Nat>, +c: Nat, +xs: List<&2, Bool>, +v: Bool, +h: {room(l, c, C.length(Bool, xs)) == True{} : Bool}) -> Type: {count(bits(Pair.fst(Model, E.Obs, step(M{l, c, xs}, E.Push{v})))) == Nat.add(count(xs), count(Con{v, Nil{}})) : Nat}# ---- contract (SPARK formal containers) ----# Each `<Subprogram>.<clause>` definition below states one Post clause of# that SPARK subprogram, as a proposition on this model; the table names the# clauses. proofs/containers/bitlist/ proves every clause under its clause name,# and its `impl` lemma carries them to the implementation.## The bit list against the complete SPARK formal-vector contract set# (SPARKlib src/spark-containers-formal-vectors.ads, AdaCore/SPARKlib# 46ec319; model predicates in spec/lib/sequence.bend), on top of the# read-back laws above. Each lemma is one Post clause of# step; `impl` (via P.step_ok) carries every clause to the# implementation: BLI.step on the bit list of a good shadow lands on the# bit list of a good shadow whose model satisfies it.## SPARK subprogram (.ads line) ours clauses# Length (284) length length_result, length_frame# Capacity (322) limit limit_result, limit_frame# Empty_Vector (292) new, with_limit new_empty, with_limit_empty, new_impl# Clear (341) clear clear_length, clear_limit# Element (373) get get_element, get_frame, get_outside# Replace_Element (384) assign/set/unset assign_length, assign_element,# assign_except, assign_outside# Append (706) push push_length, push_prefix,# push_element, push_full# Delete_Last (866) pop pop_length, pop_prefix, pop_result, pop_empty# Last_Element (923) pop's result pop_result# iteration (Iter_Model, 1193) to_list to_list_model, to_list_frame# To_Vector (308) from_bools from_bools_model (the model spells the input)# implementation BLI.step impl# count has no SPARK counterpart (count_result: the number of set bits).# Not in this API: Is_Empty, "=", Assign/Copy/Move, Reserve_Capacity,# Reference, Insert*, Prepend*, Append_Vector/Count, Delete (at an index),# Delete_First, Delete_Last with Count, First_Element, Reverse_Elements,# Swap, Find_Index, Reverse_Find_Index, Contains, Has_Element.def nx(+m: Model, +op: E.Op) -> Model: Pair.fst(Model, E.Obs, step(m, op))def ob(+m: Model, +op: E.Op) -> E.Obs: Pair.snd(Model, E.Obs, step(m, op))def limit(m: Model) -> Maybe<&2, Nat>: match m: case M{l, c, xs}: l# Length (284)def Length.length_result(+l: Maybe<&2, Nat>, +c: Nat, +xs: List<&2, Bool>) -> Type: {ob(M{l, c, xs}, E.Length{}) == E.ONat{C.length(Bool, xs)} : E.Obs}# Length (284)def Length.length_frame(+l: Maybe<&2, Nat>, +c: Nat, +xs: List<&2, Bool>) -> Type: {nx(M{l, c, xs}, E.Length{}) == M{l, c, xs} : Model}# Capacity (322)def Capacity.limit_result(+l: Maybe<&2, Nat>, +c: Nat, +xs: List<&2, Bool>) -> Type: {ob(M{l, c, xs}, E.Limit{}) == E.OLimit{l} : E.Obs}# Capacity (322)def Capacity.limit_frame(+l: Maybe<&2, Nat>, +c: Nat, +xs: List<&2, Bool>) -> Type: {nx(M{l, c, xs}, E.Limit{}) == M{l, c, xs} : Model}# implementationdef Implementation.count_result(+l: Maybe<&2, Nat>, +c: Nat, +xs: List<&2, Bool>) -> Type: {ob(M{l, c, xs}, E.Count{}) == E.ONat{count(xs)} : E.Obs}# iteration (Iter_Model, 1193)def Iteration.to_list_model(+l: Maybe<&2, Nat>, +c: Nat, +xs: List<&2, Bool>) -> Type: {ob(M{l, c, xs}, E.ToList{}) == E.OBits{xs} : E.Obs}# iteration (Iter_Model, 1193)def Iteration.to_list_frame(+l: Maybe<&2, Nat>, +c: Nat, +xs: List<&2, Bool>) -> Type: {nx(M{l, c, xs}, E.ToList{}) == M{l, c, xs} : Model}# Empty_Vector (292)def Empty_Vector.new_empty() -> Type: {C.length(Bool, bits(new())) == 0n : Nat}# Empty_Vector (292)def Empty_Vector.with_limit_empty(+n: Nat) -> Type: {C.length(Bool, bits(with_limit(n))) == 0n : Nat}# To_Vector (308)def To_Vector.from_bools_model(+bs: List<&2, Bool>, +c: Nat, +hc: {c == 31n : Nat}, +h: {Nat.is_le(C.length(Bool, bs), Nat.mul(C.pow2(c), 32n)) == True{} : Bool}) -> Type: {push_all(bs, M{None{}, c, Nil{}}) == M{None{}, c, bs} : Model}# Clear (341)def Clear.clear_length(+l: Maybe<&2, Nat>, +c: Nat, +xs: List<&2, Bool>) -> Type: {C.length(Bool, bits(nx(M{l, c, xs}, E.Clear{}))) == 0n : Nat}# Clear (341)def Clear.clear_limit(+l: Maybe<&2, Nat>, +c: Nat, +xs: List<&2, Bool>) -> Type: {limit(nx(M{l, c, xs}, E.Clear{})) == l : Maybe<&2, Nat>}# Element (373)def Element.get_element(+l: Maybe<&2, Nat>, +c: Nat, +xs: List<&2, Bool>, +i: Nat, +v: Bool, +h: {C.nth(Bool, xs, i) == Some{v} : Maybe<&2, Bool>}) -> Type: {ob(M{l, c, xs}, E.Get{i}) == E.OBit{Done{v}} : E.Obs}# Element (373)def Element.get_frame(+l: Maybe<&2, Nat>, +c: Nat, +xs: List<&2, Bool>, +i: Nat) -> Type: {nx(M{l, c, xs}, E.Get{i}) == M{l, c, xs} : Model}# Element (373)def Element.get_outside(+l: Maybe<&2, Nat>, +c: Nat, +xs: List<&2, Bool>, +i: Nat, +h: {Nat.is_le(C.length(Bool, xs), i) == True{} : Bool}) -> Type: {ob(M{l, c, xs}, E.Get{i}) == E.OBit{Fail{E.IndexOutOfRange{}}} : E.Obs}# Replace_Element (384)def Replace_Element.assign_length(+l: Maybe<&2, Nat>, +c: Nat, +xs: List<&2, Bool>, +i: Nat, +v: Bool, +h: {Nat.is_lt(i, C.length(Bool, xs)) == True{} : Bool}) -> Type: {C.length(Bool, bits(nx(M{l, c, xs}, E.Assign{i, v}))) == C.length(Bool, xs) : Nat}# Replace_Element (384)def Replace_Element.assign_element(+l: Maybe<&2, Nat>, +c: Nat, +xs: List<&2, Bool>, +i: Nat, +v: Bool, +h: {Nat.is_lt(i, C.length(Bool, xs)) == True{} : Bool}) -> Type: {C.nth(Bool, bits(nx(M{l, c, xs}, E.Assign{i, v})), i) == Some{v} : Maybe<&2, Bool>}# Replace_Element (384)def Replace_Element.assign_except(+l: Maybe<&2, Nat>, +c: Nat, +xs: List<&2, Bool>, +i: Nat, +v: Bool, +h: {Nat.is_lt(i, C.length(Bool, xs)) == True{} : Bool}) -> Type: V.EqualExcept(Bool, xs, bits(nx(M{l, c, xs}, E.Assign{i, v})), i)# Replace_Element (384)def Replace_Element.assign_outside(+l: Maybe<&2, Nat>, +c: Nat, +xs: List<&2, Bool>, +i: Nat, +v: Bool, +h: {Nat.is_le(C.length(Bool, xs), i) == True{} : Bool}) -> Type: {step(M{l, c, xs}, E.Assign{i, v}) == (M{l, c, xs}, E.OUnit{Fail{E.IndexOutOfRange{}}}) : Model & E.Obs}# Append (706)def Append.push_length(+l: Maybe<&2, Nat>, +c: Nat, +xs: List<&2, Bool>, +v: Bool, +h: {room(l, c, C.length(Bool, xs)) == True{} : Bool}) -> Type: {C.length(Bool, bits(nx(M{l, c, xs}, E.Push{v}))) == 1n+C.length(Bool, xs) : Nat}# Append (706)def Append.push_prefix(+l: Maybe<&2, Nat>, +c: Nat, +xs: List<&2, Bool>, +v: Bool, +h: {room(l, c, C.length(Bool, xs)) == True{} : Bool}) -> Type: V.EqualPrefix(Bool, xs, bits(nx(M{l, c, xs}, E.Push{v})))# Append (706)def Append.push_element(+l: Maybe<&2, Nat>, +c: Nat, +xs: List<&2, Bool>, +v: Bool, +h: {room(l, c, C.length(Bool, xs)) == True{} : Bool}) -> Type: {C.nth(Bool, bits(nx(M{l, c, xs}, E.Push{v})), C.length(Bool, xs)) == Some{v} : Maybe<&2, Bool>}# Append (706)def Append.push_full(+l: Maybe<&2, Nat>, +c: Nat, +xs: List<&2, Bool>, +v: Bool, +h: {room(l, c, C.length(Bool, xs)) == False{} : Bool}) -> Type: {step(M{l, c, xs}, E.Push{v}) == (M{l, c, xs}, E.OUnit{Fail{E.Full{}}}) : Model & E.Obs}# Delete_Last (866)def Delete_Last.pop_length(+l: Maybe<&2, Nat>, +c: Nat, +h: Bool, +t: List<&2, Bool>) -> Type: {C.length(Bool, bits(nx(M{l, c, Con{h, t}}, E.Pop{}))) == C.length(Bool, t) : Nat}# Delete_Last (866)def Delete_Last.pop_prefix(+l: Maybe<&2, Nat>, +c: Nat, +h: Bool, +t: List<&2, Bool>) -> Type: V.EqualPrefix(Bool, bits(nx(M{l, c, Con{h, t}}, E.Pop{})), Con{h, t})# Delete_Last (866)def Delete_Last.pop_result(+l: Maybe<&2, Nat>, +c: Nat, +h: Bool, +t: List<&2, Bool>) -> Type: {ob(M{l, c, Con{h, t}}, E.Pop{}) == E.OBit{last_bit(V.last_elem(Bool, Con{h, t}))} : E.Obs}# Delete_Last (866)def Delete_Last.pop_empty(+l: Maybe<&2, Nat>, +c: Nat) -> Type: {step(M{l, c, Nil{}}, E.Pop{}) == (M{l, c, Nil{}}, E.OBit{Fail{E.Empty{}}}) : Model & E.Obs}