Skip to content

Commit d8e512a

Browse files
eleftheiCopilot
andcommitted
Fix CircularBuffer for range_vec_add precondition
- Drain now calls range_vec_drain to remove/trim consumed intervals, keeping repr bounded and first.low >= bo invariant - All 3 write fold sites prove |repr'| < max via: add_range_first_low (first.low >= bo preserved) + repr_count_bound_gap (2*n <= al + 1) + al <= pow2_63 → n <= pow2_62 < pow2_62 + 1 = max - Added first.low >= bo conjunct to is_circular_buffer predicate - Fixed max_range_vec_entries to n == pow2 62 + 1 (exact bound needed) - RangeMap.Spec: added add_range_first_low inductive lemma Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 5c4212e commit d8e512a

3 files changed

Lines changed: 89 additions & 27 deletions

File tree

lib/pulse/lib/Pulse.Lib.CircularBuffer.fst

Lines changed: 67 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ let is_circular_buffer
155155
Spec.ranges_match_contents repr st.contents (SZ.v cbi.bo) /\
156156
RMSpec.range_map_wf repr /\
157157
Spec.repr_well_positioned repr (SZ.v cbi.bo) /\
158+
(Seq.length repr = 0 \/ (Seq.index repr 0).low >= SZ.v cbi.bo) /\
158159
Seq.length repr < RM.max_range_vec_entries
159160
)
160161

@@ -559,6 +560,17 @@ fn write_buffer_core
559560
// cf == cpl after write
560561
Spec.write_preserves_cf_eq_cpl repr st.contents (SZ.v cb_val.bo) (SZ.v offset) (reveal src_data);
561562

563+
// Bounded: add_range preserves boundedness
564+
RMSpec.add_range_bounded repr (SZ.v abs_offset) (SZ.v write_len) (SZ.v cb_val.bo + SZ.v cb_val.al);
565+
566+
// Count bound: first.low >= bo preserved by add_range
567+
RMSpec.add_range_first_low repr (SZ.v abs_offset) (SZ.v write_len) (SZ.v cb_val.bo);
568+
// now: |add_range repr ...| > 0 /\ first'.low >= bo
569+
// so repr_count_bound_gap applies
570+
Spec.repr_count_bound_gap (RMSpec.add_range repr (SZ.v abs_offset) (SZ.v write_len))
571+
(SZ.v cb_val.bo) (SZ.v cb_val.al);
572+
// gives: 2 * |repr'| <= al + 1 <= pow2_63 + 1, so |repr'| <= pow2_62 < max
573+
562574
fold (is_circular_buffer cb rm
563575
{ st with contents =
564576
GT.write_range_contents_t st.contents (SZ.v offset) (reveal src_data) });
@@ -780,6 +792,14 @@ fn write_buffer
780792
Spec.write_preserves_rwp repr2 (SZ.v cb_val2.bo) (SZ.v rel_offset) (SZ.v trimmed_len);
781793
Spec.write_preserves_cf_eq_cpl repr2 (reveal rs_st).contents (SZ.v cb_val2.bo) (SZ.v rel_offset) (reveal trimmed_data);
782794

795+
// Bounded: add_range preserves boundedness
796+
RMSpec.add_range_bounded repr2 (SZ.v rm_abs) (SZ.v trimmed_len) (SZ.v cb_val2.bo + SZ.v cb_val2.al);
797+
798+
// Count bound: first.low >= bo preserved, then derive count < max
799+
RMSpec.add_range_first_low repr2 (SZ.v rm_abs) (SZ.v trimmed_len) (SZ.v cb_val2.bo);
800+
Spec.repr_count_bound_gap (RMSpec.add_range repr2 (SZ.v rm_abs) (SZ.v trimmed_len))
801+
(SZ.v cb_val2.bo) (SZ.v cb_val2.al);
802+
783803
fold (is_circular_buffer cb rm
784804
{ Spec.resize_result st (SZ.v new_al) with contents = reveal new_st_contents });
785805
{ wrote = true; new_data_ready = ndr; resize_failed = false }
@@ -863,6 +883,14 @@ fn write_buffer
863883
Spec.write_preserves_rwp repr (SZ.v cb_val.bo) (SZ.v rel_offset) (SZ.v trimmed_len);
864884
Spec.write_preserves_cf_eq_cpl repr st.contents (SZ.v cb_val.bo) (SZ.v rel_offset) (reveal trimmed_data);
865885

886+
// Bounded: add_range preserves boundedness
887+
RMSpec.add_range_bounded repr (SZ.v rm_abs) (SZ.v trimmed_len) (SZ.v cb_val.bo + SZ.v cb_val.al);
888+
889+
// Count bound: first.low >= bo preserved, then derive count < max
890+
RMSpec.add_range_first_low repr (SZ.v rm_abs) (SZ.v trimmed_len) (SZ.v cb_val.bo);
891+
Spec.repr_count_bound_gap (RMSpec.add_range repr (SZ.v rm_abs) (SZ.v trimmed_len))
892+
(SZ.v cb_val.bo) (SZ.v cb_val.al);
893+
866894
fold (is_circular_buffer cb rm
867895
{ st with contents =
868896
GT.write_range_contents_t st.contents (SZ.v rel_offset) (reveal trimmed_data) });
@@ -897,32 +925,45 @@ fn drain
897925
let new_rs = SZ.rem temp cb_val.al;
898926
let new_bo = SZ.add cb_val.bo n;
899927

900-
// Compute new prefix length from range map with new base_offset
901-
let new_pl = RM.range_vec_contiguous_from rm new_bo;
902-
903-
let new_cbi = Mkcb_internal cb_val.buf new_rs cb_val.al new_pl cb_val.vl new_bo;
904-
( := ) cb new_cbi;
905-
rewrite (Vec.pts_to cb_val.buf buf_data) as (Vec.pts_to new_cbi.buf buf_data);
906-
907-
// Physical coherence preserved under drain
908-
Spec.drain_preserves_coherence st.alloc_length buf_data st.contents st.read_start (SZ.v n);
909-
910-
// Bridge preserved by index substitution with padding (trivial!)
911-
Spec.ranges_match_drain_padded repr st.contents (SZ.v cb_val.bo) (SZ.v n);
912-
913-
// repr_well_positioned preserved
914-
Spec.drain_preserves_rwp repr (SZ.v cb_val.bo) (SZ.v n);
915-
916-
// cf == cpl after drain
917-
Spec.rwp_cf_eq_cpl repr
918-
(Spec.drained_contents st.alloc_length st.contents (SZ.v n))
919-
(SZ.v new_bo);
920-
921-
// Drain prefix: new_cpl = old_cpl - n, so new_cpl == 0 iff old_cpl == n
922-
Spec.drain_prefix_length st.alloc_length st.contents (SZ.v n);
923-
924-
fold (is_circular_buffer cb rm (Spec.drain_result st (SZ.v n)));
925-
SZ.eq new_pl 0sz
928+
if (SZ.gt n 0sz) {
929+
// n > 0: drain range vec + fold with drain_repr
930+
RM.range_vec_drain rm new_bo;
931+
932+
let new_pl = RM.range_vec_contiguous_from rm new_bo;
933+
let new_cbi = Mkcb_internal cb_val.buf new_rs cb_val.al new_pl cb_val.vl new_bo;
934+
( := ) cb new_cbi;
935+
rewrite (Vec.pts_to cb_val.buf buf_data) as (Vec.pts_to new_cbi.buf buf_data);
936+
937+
Spec.drain_preserves_coherence st.alloc_length buf_data st.contents st.read_start (SZ.v n);
938+
Spec.ranges_match_drain_repr repr st.contents (SZ.v cb_val.bo) (SZ.v n);
939+
RMSpec.drain_repr_wf repr (SZ.v new_bo);
940+
Spec.drain_repr_preserves_rwp repr (SZ.v cb_val.bo) (SZ.v n);
941+
Spec.rwp_cf_eq_cpl (RMSpec.drain_repr repr (SZ.v new_bo))
942+
(Spec.drained_contents st.alloc_length st.contents (SZ.v n))
943+
(SZ.v new_bo);
944+
Spec.drain_prefix_length st.alloc_length st.contents (SZ.v n);
945+
RMSpec.drain_repr_length repr (SZ.v new_bo);
946+
947+
fold (is_circular_buffer cb rm (Spec.drain_result st (SZ.v n)));
948+
SZ.eq new_pl 0sz
949+
} else {
950+
// n = 0: no drain, fold with original repr
951+
let new_pl = RM.range_vec_contiguous_from rm new_bo;
952+
let new_cbi = Mkcb_internal cb_val.buf new_rs cb_val.al new_pl cb_val.vl new_bo;
953+
( := ) cb new_cbi;
954+
rewrite (Vec.pts_to cb_val.buf buf_data) as (Vec.pts_to new_cbi.buf buf_data);
955+
956+
Spec.drain_preserves_coherence st.alloc_length buf_data st.contents st.read_start (SZ.v n);
957+
Spec.ranges_match_drain_padded repr st.contents (SZ.v cb_val.bo) (SZ.v n);
958+
Spec.drain_preserves_rwp repr (SZ.v cb_val.bo) (SZ.v n);
959+
Spec.rwp_cf_eq_cpl repr
960+
(Spec.drained_contents st.alloc_length st.contents (SZ.v n))
961+
(SZ.v new_bo);
962+
Spec.drain_prefix_length st.alloc_length st.contents (SZ.v n);
963+
964+
fold (is_circular_buffer cb rm (Spec.drain_result st (SZ.v n)));
965+
SZ.eq new_pl 0sz
966+
}
926967
}
927968
#pop-options
928969

lib/pulse/lib/Pulse.Lib.RangeMap.Spec.fst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1648,3 +1648,24 @@ let drain_repr_mem_above (s: Seq.seq interval) (new_bo: nat) (x: nat)
16481648
let tl = Seq.tail s in
16491649
mem_cons trimmed tl x
16501650
end else ()
1651+
1652+
/// add_range preserves first.low >= lo when offset >= lo
1653+
let rec add_range_first_low (s: Seq.seq interval) (offset: nat) (len: pos) (lo: nat)
1654+
: Lemma (requires range_map_wf s /\
1655+
(Seq.length s = 0 \/ (Seq.index s 0).low >= lo) /\ offset >= lo)
1656+
(ensures Seq.length (add_range s offset len) > 0 /\
1657+
(Seq.index (add_range s offset len) 0).low >= lo)
1658+
(decreases Seq.length s) =
1659+
if Seq.length s = 0 then ()
1660+
else
1661+
let hd = Seq.index s 0 in
1662+
let tl = Seq.tail s in
1663+
if offset + len < hd.low then ()
1664+
else if high hd < offset then
1665+
// add_range returns cons hd (add_range tl offset len)
1666+
()
1667+
else
1668+
let merged_low = if offset < hd.low then offset else hd.low in
1669+
let merged_high = if offset + len > high hd then offset + len else high hd in
1670+
range_map_wf_tail s;
1671+
add_range_first_low tl merged_low (merged_high - merged_low) lo

lib/pulse/lib/Pulse.Lib.RangeVec.fst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ assume val platform_is_64bit : squash SZ.fits_u64
2525
/// Strictly greater than the maximum number of separated intervals that can
2626
/// fit in a CircularBuffer with alloc_length ≤ pow2_63 (which is ≤ pow2_62).
2727
/// The bound ensures vector capacity doubling is always representable.
28-
assume val max_range_vec_entries : n:pos{n <= pow2 62 + 1}
28+
assume val max_range_vec_entries : n:pos{n == pow2 62 + 1}
2929

3030
(*** Types ***)
3131

0 commit comments

Comments
 (0)