spg_storage/persistent.rs
1//! Persistent (structural-sharing) vector — the v4.38 building block for the
2//! v4.39 cheap-`Catalog::clone` migration.
3//!
4//! `PersistentVec<T>` is a Bitmapped Vector Trie (Clojure persistent vector
5//! shape): 32-way branching trie with a `tail` buffer at the open end. Every
6//! mutating operation produces a new handle that shares interior nodes with
7//! the old handle via `Arc`. `Clone` is `O(1)`; `push` and `get` are
8//! `O(log₃₂ N)`; a `CoW` path touches only the spine of the affected leaf.
9//!
10//! Hard rules (do not relax in later milestones):
11//! - `no_std` compatible (`alloc::sync::Arc`, `alloc::vec::Vec`).
12//! - Zero `unsafe`. Workspace lint `unsafe_code = "deny"` stays in force here.
13//! - Zero external deps. Pure std + `alloc`.
14//!
15//! Layout:
16//! - `root: Arc<Node<T>>` — the persistent trie. `Node::Internal(Vec<Arc<Node>>)`
17//! for non-leaf levels, `Node::Leaf(Vec<T>)` for the bottom.
18//! - `tail: Arc<Vec<T>>` — the open-end buffer (≤ 32 elements). Lives outside
19//! the trie so `push` to a non-full tail avoids walking the spine.
20//! - `len: usize` — total element count (`trie_size + tail.len()`).
21//! - `shift: u32` — distance from the root to the leaf level, in bits, in
22//! multiples of `SHIFT`. An empty PV has `shift = SHIFT` and an empty root
23//! so the first incorporate doesn't have to special-case the root type.
24//!
25//! Invariants (debug-asserted in hot paths):
26//! - `tail.len() ≤ BRANCH`.
27//! - When `tail.len() == BRANCH` we incorporate it into the trie before the
28//! next push (so post-condition is `tail.len() < BRANCH`, except briefly in
29//! the middle of `push`).
30//! - `shift` is always a multiple of `SHIFT` and ≥ `SHIFT`.
31//! - `trie_size = len - tail.len()` always fits in `1 << (shift + SHIFT)`.
32
33use alloc::sync::Arc;
34use alloc::vec::Vec;
35use core::ops::Index;
36
37const SHIFT: u32 = 5;
38const BRANCH: usize = 1 << SHIFT; // 32
39const MASK: usize = BRANCH - 1; // 0x1F
40
41// `Clone` (v5.5.0) backs `Arc::make_mut` in `get_mut_in_trie`: cloning an
42// `Internal` only bumps its children's `Arc`s (shallow), cloning a `Leaf`
43// copies its ≤ BRANCH elements — exactly the path-copy a shared spine needs,
44// matching what `set_in_trie` does by hand.
45#[derive(Debug, Clone)]
46enum Node<T> {
47 Internal(Vec<Arc<Node<T>>>),
48 Leaf(Vec<T>),
49}
50
51/// A persistent vector with structural sharing. `Clone` is O(1) (bumps the
52/// root `Arc`); `push` is amortised O(log₃₂ N) and only allocates fresh nodes
53/// along the spine from the root to the affected leaf.
54#[derive(Debug)]
55pub struct PersistentVec<T> {
56 root: Arc<Node<T>>,
57 tail: Arc<Vec<T>>,
58 len: usize,
59 shift: u32,
60}
61
62impl<T> Default for PersistentVec<T> {
63 fn default() -> Self {
64 Self::new()
65 }
66}
67
68impl<T> Clone for PersistentVec<T> {
69 /// O(1) — only `Arc` bumps, no element copy. This is the whole reason PV
70 /// exists in v4.38; `Catalog::clone` in v4.39 inherits the property.
71 fn clone(&self) -> Self {
72 Self {
73 root: self.root.clone(),
74 tail: self.tail.clone(),
75 len: self.len,
76 shift: self.shift,
77 }
78 }
79}
80
81/// Element-wise equality: two PVs are equal iff they yield the same elements
82/// in the same order. Independent of internal trie shape — two PVs built via
83/// different push / set sequences with the same end state still compare
84/// equal. Used by `Catalog::serialize` round-trip tests in v4.39+.
85impl<T: PartialEq> PartialEq for PersistentVec<T> {
86 fn eq(&self, other: &Self) -> bool {
87 self.len == other.len && self.iter().eq(other.iter())
88 }
89}
90
91impl<T: Eq> Eq for PersistentVec<T> {}
92
93impl<T> PersistentVec<T> {
94 /// Empty vector. Allocates one empty `Internal` root and one empty `tail`
95 /// `Vec`; both are shared across every empty PV via `Arc::clone` once the
96 /// first one is built. The shape matches a `shift = SHIFT` trie so the
97 /// incorporate path never has to grow the root type.
98 #[must_use]
99 pub fn new() -> Self {
100 Self {
101 root: Arc::new(Node::Internal(Vec::new())),
102 tail: Arc::new(Vec::new()),
103 len: 0,
104 shift: SHIFT,
105 }
106 }
107
108 #[must_use]
109 pub const fn len(&self) -> usize {
110 self.len
111 }
112
113 #[must_use]
114 pub const fn is_empty(&self) -> bool {
115 self.len == 0
116 }
117
118 /// O(log₃₂ N). `None` for out-of-bounds. Returned reference is valid for
119 /// the lifetime of `&self`; structural sharing means the borrow is
120 /// independent of any other handle that shares the same spine.
121 pub fn get(&self, i: usize) -> Option<&T> {
122 let (run, off) = self.run_at(i)?;
123 run.get(off)
124 }
125
126 /// v7.39 (round 562) — the contiguous run holding `i`, with the index
127 /// `i` sits at inside it, so a caller reading ascending indices can
128 /// keep the run and descend once per leaf instead of once per element.
129 ///
130 /// This is what `iter` already does; `run_at`'s own comment says so.
131 /// It was private, so a caller that reads BY INDEX — an index-only
132 /// scan checking one header per matching row — had no way to say it,
133 /// and paid a descent per row for elements 32 to a leaf.
134 ///
135 /// Returns `(start, run)`: `run[i - start]` is element `i`, and the
136 /// run covers `start .. start + run.len()`.
137 pub fn run_containing(&self, i: usize) -> Option<(usize, &[T])> {
138 let (run, off) = self.run_at(i)?;
139 Some((i - off, run))
140 }
141
142 /// v7.39 (round 567) — a cursor that holds the run it last descended
143 /// to, for a caller reading many elements by ascending index.
144 ///
145 /// Indexing is `O(log₃₂ N)` — four dependent loads over 500k
146 /// elements — and a scan that reads every row pays it every row. A
147 /// leaf holds 32, so keeping it between reads makes that one descent
148 /// per 32. Ask for a scattered index and it descends, exactly as
149 /// `get` would.
150 pub const fn run_cursor(&self) -> RunCursor<'_, T> {
151 RunCursor {
152 vec: self,
153 run: None,
154 }
155 }
156
157 /// The contiguous run of elements holding index `i`, plus `i`'s offset
158 /// inside it. One trie descent serves the whole run, which is what lets
159 /// `iter` walk a leaf at a time instead of descending per element.
160 ///
161 /// This is `get`'s arithmetic, factored out: the trie region indexes a
162 /// leaf by `i & MASK`, the tail by `i - trie_size`.
163 fn run_at(&self, i: usize) -> Option<(&[T], usize)> {
164 if i >= self.len {
165 return None;
166 }
167 let trie_size = self.len - self.tail.len();
168 if i >= trie_size {
169 return Some((&self.tail, i - trie_size));
170 }
171 let mut node: &Arc<Node<T>> = &self.root;
172 let mut shift = self.shift;
173 loop {
174 match &**node {
175 Node::Leaf(elems) => return Some((elems, i & MASK)),
176 Node::Internal(children) => {
177 let sub_idx = (i >> shift) & MASK;
178 node = children.get(sub_idx)?;
179 shift = shift.saturating_sub(SHIFT);
180 }
181 }
182 }
183 }
184
185 /// Sequential iterator, walking a leaf at a time.
186 ///
187 /// v7.39 (round 486) — this used to call `get` per element, so every
188 /// scan in the engine paid a full trie descent (a chain of `Arc`
189 /// dereferences) for each row it read. The v4.38 comment here said
190 /// "v4.39 / v4.40 will profile and upgrade if iter shows up as the
191 /// bottleneck"; it showed up — `is_row_visible` plus the scan's own
192 /// row reads were 17 % of `big_in`'s profile, both of them descents.
193 /// One descent now serves up to `BRANCH` elements.
194 pub fn iter(&self) -> Iter<'_, T> {
195 Iter {
196 pv: self,
197 pos: 0,
198 run: &[],
199 off: 0,
200 }
201 }
202}
203
204impl<'a, T> IntoIterator for &'a PersistentVec<T> {
205 type Item = &'a T;
206 type IntoIter = Iter<'a, T>;
207 fn into_iter(self) -> Self::IntoIter {
208 self.iter()
209 }
210}
211
212/// `pv[i]` indexing, matching `Vec<T>::index`'s contract: panics on
213/// out-of-bounds. v4.39 lets `table.rows[i]` work unchanged on the new
214/// PV-backed `Table` for the price of one extra `O(log₃₂ N)` walk per
215/// lookup (vs Vec's O(1)). Callers in a hot loop should hoist the trie
216/// walk where possible (`let row = pv.get(i)?;`) instead of re-indexing.
217impl<T> Index<usize> for PersistentVec<T> {
218 type Output = T;
219 fn index(&self, i: usize) -> &T {
220 self.get(i).expect("PersistentVec index out of bounds")
221 }
222}
223
224impl<T: Clone> PersistentVec<T> {
225 /// `O(log₃₂ N)` path-copy push. Returns a new handle; `self` is untouched
226 /// (structural sharing means the old handle and the new one share every
227 /// internal node except the spine to the newly written tail / leaf).
228 #[must_use]
229 pub fn push(&self, x: T) -> Self {
230 // Fast path: tail still has room.
231 if self.tail.len() < BRANCH {
232 let mut new_tail = (*self.tail).clone();
233 new_tail.push(x);
234 return Self {
235 root: self.root.clone(),
236 tail: Arc::new(new_tail),
237 len: self.len + 1,
238 shift: self.shift,
239 };
240 }
241 // Slow path: tail is full → incorporate it into the trie as a new
242 // Leaf, then start a fresh tail with `x`.
243 let leaf: Arc<Node<T>> = Arc::new(Node::Leaf((*self.tail).clone()));
244 let old_trie_size = self.len - BRANCH; // tail.len() == BRANCH here
245 let trie_capacity: usize = 1usize << (self.shift + SHIFT);
246 let needs_grow = old_trie_size + BRANCH > trie_capacity;
247 let (new_root, new_shift) = if needs_grow {
248 // Root overflow: wrap the old root and a brand-new branch (carrying
249 // the new leaf) under a fresh top-level Internal. The new branch
250 // sits at the same depth the old root sat at, so it needs
251 // `old_shift / SHIFT` layers of `Internal` above the leaf.
252 let internal_levels_above_leaf = self.shift / SHIFT;
253 let new_branch = new_path(internal_levels_above_leaf, leaf);
254 let new_root = Arc::new(Node::Internal(alloc::vec![self.root.clone(), new_branch]));
255 (new_root, self.shift + SHIFT)
256 } else {
257 (
258 push_leaf_into_node(&self.root, self.shift, old_trie_size, leaf),
259 self.shift,
260 )
261 };
262 Self {
263 root: new_root,
264 tail: Arc::new(alloc::vec![x]),
265 len: self.len + 1,
266 shift: new_shift,
267 }
268 }
269
270 /// `O(1)` amortized — transient in-place push. v4.39.1 perf path for the
271 /// `Table::insert` hot loop (and any other streaming caller that holds a
272 /// `&mut PersistentVec`). Uses `Arc::make_mut` on the tail buffer: when
273 /// the tail's `Arc` is uniquely owned (the common case), this mutates
274 /// in place — same cost as `Vec::push`. If a cloned handle is outstanding
275 /// (e.g. inside a TX wrap holding a Catalog snapshot), the tail is path-
276 /// copied just like `push` and the snapshot is unaffected. Either way,
277 /// callers observe the same end state as `self = self.push(x)`.
278 pub fn push_mut(&mut self, x: T) {
279 if self.tail.len() < BRANCH {
280 // Fast path: room in tail, mutate in place when uniquely owned.
281 let tail = Arc::make_mut(&mut self.tail);
282 tail.push(x);
283 self.len += 1;
284 return;
285 }
286 // Slow path: tail full → incorporate into trie, then start a fresh
287 // tail with [x]. Take ownership of the tail Arc to reuse its Vec
288 // when uniquely owned; the placeholder replacement makes self.tail
289 // the fresh `[x]` buffer.
290 let old_tail_arc = core::mem::replace(&mut self.tail, Arc::new(alloc::vec![x]));
291 let old_tail_vec: Vec<T> =
292 Arc::try_unwrap(old_tail_arc).unwrap_or_else(|arc| (*arc).clone());
293 let leaf: Arc<Node<T>> = Arc::new(Node::Leaf(old_tail_vec));
294 let old_trie_size = self.len - BRANCH;
295 let trie_capacity: usize = 1usize << (self.shift + SHIFT);
296 let needs_grow = old_trie_size + BRANCH > trie_capacity;
297 if needs_grow {
298 let internal_levels = self.shift / SHIFT;
299 let new_branch = new_path(internal_levels, leaf);
300 self.root = Arc::new(Node::Internal(alloc::vec![self.root.clone(), new_branch]));
301 self.shift += SHIFT;
302 } else {
303 self.root = push_leaf_into_node(&self.root, self.shift, old_trie_size, leaf);
304 }
305 self.len += 1;
306 }
307
308 /// `O(log₃₂ N)` path-copy set. `None` for out-of-bounds (matches `get`).
309 /// Result shares every node except the spine to the rewritten cell.
310 #[must_use]
311 pub fn set(&self, i: usize, x: T) -> Option<Self> {
312 if i >= self.len {
313 return None;
314 }
315 let trie_size = self.len - self.tail.len();
316 if i >= trie_size {
317 let mut new_tail: Vec<T> = (*self.tail).clone();
318 new_tail[i - trie_size] = x;
319 return Some(Self {
320 root: self.root.clone(),
321 tail: Arc::new(new_tail),
322 len: self.len,
323 shift: self.shift,
324 });
325 }
326 let new_root = set_in_trie(&self.root, self.shift, i, x);
327 Some(Self {
328 root: new_root,
329 tail: self.tail.clone(),
330 len: self.len,
331 shift: self.shift,
332 })
333 }
334
335 /// `O(log₃₂ N)` transient-mut access — the read-side analogue of
336 /// `push_mut` (v5.5.0). Walks the spine with `Arc::make_mut`: when every
337 /// node along the path is uniquely owned (the common streaming case) the
338 /// walk mutates in place at the same cost as `Vec::get_mut`. If a cloned
339 /// handle shares the spine (e.g. a `Catalog` snapshot held by an open TX),
340 /// the touched nodes are path-copied — the snapshot keeps its old value
341 /// and only this handle observes the mutation, exactly like `set`. `None`
342 /// for out-of-bounds (matches `get` / `set`).
343 ///
344 /// Introduced for the v5.5 HNSW `NswGraph` switch to PV-backed layers: the
345 /// insert path needs in-place edits to a node's neighbour list
346 /// (`layers[l].get_mut(node)`) without the `set`-then-write-back round trip
347 /// and its extra path-copy.
348 pub fn get_mut(&mut self, i: usize) -> Option<&mut T> {
349 if i >= self.len {
350 return None;
351 }
352 let trie_size = self.len - self.tail.len();
353 if i >= trie_size {
354 let tail = Arc::make_mut(&mut self.tail);
355 return tail.get_mut(i - trie_size);
356 }
357 get_mut_in_trie(&mut self.root, self.shift, i)
358 }
359}
360
361/// Push a freshly-built `Leaf` into the trie at trie-position `trie_index`.
362/// Assumes the caller has already verified `trie_index < trie_capacity` (i.e.
363/// `needs_grow == false`). `shift` is the shift at `node`; recursion drops
364/// it by `SHIFT` per layer.
365fn push_leaf_into_node<T: Clone>(
366 node: &Arc<Node<T>>,
367 shift: u32,
368 trie_index: usize,
369 leaf: Arc<Node<T>>,
370) -> Arc<Node<T>> {
371 let sub_idx = (trie_index >> shift) & MASK;
372 let Node::Internal(children) = &**node else {
373 // Bottom-of-trie is `Leaf`; we never recurse below `shift == SHIFT`.
374 // Reaching a `Leaf` here would be a shift-bookkeeping bug.
375 debug_assert!(false, "push_leaf_into_node hit a Leaf — shift bug");
376 return node.clone();
377 };
378 let mut new_children: Vec<Arc<Node<T>>> = children.clone();
379 if shift == SHIFT {
380 // Next layer down is the Leaf layer — drop the new leaf in at the
381 // open slot. Leaves are inserted in trie-index order, so `sub_idx`
382 // is always either an existing index (replace — shouldn't happen
383 // during push, only during set) or one past the end (append).
384 debug_assert!(
385 sub_idx == new_children.len(),
386 "leaves are pushed sequentially; sub_idx {} != next slot {}",
387 sub_idx,
388 new_children.len()
389 );
390 new_children.push(leaf);
391 } else {
392 let child: Arc<Node<T>> = if sub_idx < new_children.len() {
393 push_leaf_into_node(&new_children[sub_idx], shift - SHIFT, trie_index, leaf)
394 } else {
395 // Fresh branch: wrap the leaf in enough Internal layers to land
396 // at the leaf level under this node's child.
397 let internal_levels_above_leaf = (shift / SHIFT) - 1;
398 new_path(internal_levels_above_leaf, leaf)
399 };
400 if sub_idx < new_children.len() {
401 new_children[sub_idx] = child;
402 } else {
403 new_children.push(child);
404 }
405 }
406 Arc::new(Node::Internal(new_children))
407}
408
409/// Build a chain of `internal_levels` `Internal` nodes wrapping `leaf`. With
410/// `internal_levels == 0` the leaf is returned as-is.
411fn new_path<T>(internal_levels: u32, leaf: Arc<Node<T>>) -> Arc<Node<T>> {
412 let mut node = leaf;
413 for _ in 0..internal_levels {
414 node = Arc::new(Node::Internal(alloc::vec![node]));
415 }
416 node
417}
418
419/// Path-copy `set` walk. Returns a fresh `Arc<Node>` along the spine; every
420/// other node is shared via `Arc::clone`.
421fn set_in_trie<T: Clone>(node: &Arc<Node<T>>, shift: u32, i: usize, x: T) -> Arc<Node<T>> {
422 match &**node {
423 Node::Leaf(elems) => {
424 let mut new_elems = elems.clone();
425 new_elems[i & MASK] = x;
426 Arc::new(Node::Leaf(new_elems))
427 }
428 Node::Internal(children) => {
429 let sub_idx = (i >> shift) & MASK;
430 let new_child = set_in_trie(&children[sub_idx], shift - SHIFT, i, x);
431 let mut new_children = children.clone();
432 new_children[sub_idx] = new_child;
433 Arc::new(Node::Internal(new_children))
434 }
435 }
436}
437
438/// Copy-on-write `get_mut` walk (v5.5.0). `Arc::make_mut` clones a node only
439/// when it's shared; a uniquely-owned spine is walked in place. Mirrors
440/// `set_in_trie` but hands back a `&mut` to the located cell instead of
441/// rewriting it, so the caller can mutate the element directly.
442fn get_mut_in_trie<T: Clone>(node: &mut Arc<Node<T>>, shift: u32, i: usize) -> Option<&mut T> {
443 match Arc::make_mut(node) {
444 Node::Leaf(elems) => elems.get_mut(i & MASK),
445 Node::Internal(children) => {
446 let sub_idx = (i >> shift) & MASK;
447 let child = children.get_mut(sub_idx)?;
448 get_mut_in_trie(child, shift - SHIFT, i)
449 }
450 }
451}
452
453/// Sequential `&T` iterator. v4.38 implementation is `get(i)`-driven — simple
454/// and correct, but O(N log N) over the whole vector. Profile in v4.39 /
455/// v4.40 and upgrade if it shows up in flamegraphs.
456#[derive(Debug)]
457pub struct Iter<'a, T> {
458 pv: &'a PersistentVec<T>,
459 pos: usize,
460 /// The run `pos` currently sits in, and how far into it we are.
461 /// Empty (with `off == 0`) means "descend on the next call".
462 run: &'a [T],
463 off: usize,
464}
465
466impl<'a, T> Iterator for Iter<'a, T> {
467 type Item = &'a T;
468 fn next(&mut self) -> Option<&'a T> {
469 if self.off == self.run.len() {
470 let (run, off) = self.pv.run_at(self.pos)?;
471 self.run = run;
472 self.off = off;
473 }
474 let v = self.run.get(self.off)?;
475 self.off += 1;
476 self.pos += 1;
477 Some(v)
478 }
479
480 fn size_hint(&self) -> (usize, Option<usize>) {
481 let remaining = self.pv.len.saturating_sub(self.pos);
482 (remaining, Some(remaining))
483 }
484}
485
486impl<T> ExactSizeIterator for Iter<'_, T> {}
487
488#[cfg(test)]
489impl<T> PersistentVec<T> {
490 /// Test-only: do two handles share the same root + tail `Arc` — i.e. did
491 /// `clone` bump pointers rather than copy elements? Used by v5.5.0's
492 /// `nsw_clone_is_o1` to prove `NswGraph::clone` is O(1) structural sharing,
493 /// not an O(N) element copy.
494 pub(crate) fn shares_storage_with(&self, other: &Self) -> bool {
495 Arc::ptr_eq(&self.root, &other.root) && Arc::ptr_eq(&self.tail, &other.tail)
496 }
497}
498
499#[cfg(test)]
500#[allow(
501 clippy::cast_possible_truncation,
502 clippy::cast_possible_wrap,
503 clippy::cast_sign_loss,
504 clippy::cast_lossless,
505 clippy::needless_range_loop,
506 clippy::items_after_statements,
507 clippy::manual_range_patterns,
508 clippy::unreadable_literal,
509 clippy::similar_names
510)]
511mod tests {
512 use super::*;
513
514 /// v7.39 (round 486) — `iter` walks a leaf at a time now instead of
515 /// calling `get` per element. The two must stay indistinguishable, so
516 /// this checks them against each other at every length that puts a
517 /// boundary somewhere interesting: inside the tail, exactly on a leaf
518 /// edge, one past it, and deep enough to need a second trie level.
519 #[test]
520 fn iter_agrees_with_get_at_every_boundary() {
521 for n in [
522 0usize, 1, 2, 31, 32, 33, 63, 64, 65, 1023, 1024, 1025, 1057, 2000,
523 ] {
524 let mut pv: PersistentVec<usize> = PersistentVec::new();
525 for i in 0..n {
526 pv = pv.push(i * 7 + 1);
527 }
528 let by_index: Vec<usize> = (0..n).map(|i| *pv.get(i).unwrap()).collect();
529 let by_iter: Vec<usize> = pv.iter().copied().collect();
530 assert_eq!(by_iter, by_index, "n = {n}");
531 assert_eq!(pv.iter().count(), n, "n = {n}");
532 assert_eq!(pv.iter().len(), n, "ExactSizeIterator, n = {n}");
533 }
534 }
535
536 /// A partially-consumed walk must keep going from where it stopped,
537 /// including across the leaf boundary it is sitting on.
538 #[test]
539 fn iter_resumes_across_a_leaf_boundary() {
540 let mut pv: PersistentVec<usize> = PersistentVec::new();
541 for i in 0..200 {
542 pv = pv.push(i);
543 }
544 let mut it = pv.iter();
545 let head: Vec<usize> = it.by_ref().take(32).copied().collect();
546 assert_eq!(head, (0..32).collect::<Vec<_>>());
547 assert_eq!(it.len(), 168);
548 let tail: Vec<usize> = it.copied().collect();
549 assert_eq!(tail, (32..200).collect::<Vec<_>>());
550 }
551
552 /// The walk reads the handle it was made from, not whatever the
553 /// structural sharing produced later.
554 #[test]
555 fn iter_sees_its_own_handles_contents() {
556 let mut pv: PersistentVec<usize> = PersistentVec::new();
557 for i in 0..40 {
558 pv = pv.push(i);
559 }
560 let older = pv.clone();
561 let newer = pv.push(999).set(0, 111).unwrap();
562 assert_eq!(
563 older.iter().copied().collect::<Vec<_>>(),
564 (0..40).collect::<Vec<_>>()
565 );
566 let seen: Vec<usize> = newer.iter().copied().collect();
567 assert_eq!(seen.len(), 41);
568 assert_eq!(seen[0], 111);
569 assert_eq!(seen[40], 999);
570 }
571
572 #[test]
573 fn empty_vec_is_empty() {
574 let pv: PersistentVec<u64> = PersistentVec::new();
575 assert_eq!(pv.len(), 0);
576 assert!(pv.is_empty());
577 assert!(pv.get(0).is_none());
578 }
579
580 #[test]
581 fn push_single_fits_in_tail() {
582 let pv: PersistentVec<u64> = PersistentVec::new().push(42);
583 assert_eq!(pv.len(), 1);
584 assert_eq!(pv.get(0), Some(&42));
585 assert!(pv.get(1).is_none());
586 }
587
588 #[test]
589 fn push_fills_tail_then_incorporates() {
590 // 32 elements all sit in tail; 33rd triggers the first incorporate.
591 let mut pv: PersistentVec<u64> = PersistentVec::new();
592 for i in 0..40_u64 {
593 pv = pv.push(i);
594 }
595 for i in 0..40_u64 {
596 assert_eq!(pv.get(i as usize), Some(&i), "mismatch at {i}");
597 }
598 assert!(pv.get(40).is_none());
599 }
600
601 #[test]
602 fn push_crosses_root_overflow_boundary() {
603 // Crossing 1024 forces the first root grow (`shift` 5 → 10).
604 let mut pv: PersistentVec<u64> = PersistentVec::new();
605 for i in 0..1100_u64 {
606 pv = pv.push(i);
607 }
608 for i in 0..1100_u64 {
609 assert_eq!(pv.get(i as usize), Some(&i), "mismatch at {i}");
610 }
611 }
612
613 #[test]
614 fn push_crosses_second_grow_boundary() {
615 // 32_768 forces the second root grow (`shift` 10 → 15). Verifies the
616 // recursion in `push_leaf_into_node` handles a 3-deep trie.
617 let mut pv: PersistentVec<u64> = PersistentVec::new();
618 for i in 0..33_000_u64 {
619 pv = pv.push(i);
620 }
621 // Spot-check a handful — the full 33k loop is too slow under cargo
622 // test default mode; the 100K fuzz oracle covers thorough coverage.
623 let probes = [0_usize, 1, 31, 32, 1023, 1024, 1056, 32_767, 32_768, 32_999];
624 for &p in &probes {
625 assert_eq!(pv.get(p), Some(&(p as u64)), "mismatch at {p}");
626 }
627 assert!(pv.get(33_000).is_none());
628 }
629
630 #[test]
631 fn clone_then_push_preserves_original() {
632 // The whole point of PV: pushing onto a clone must not mutate the
633 // original handle.
634 let mut a: PersistentVec<u64> = PersistentVec::new();
635 for i in 0..50_u64 {
636 a = a.push(i);
637 }
638 let b = a.clone();
639 let b = b.push(999);
640 assert_eq!(a.len(), 50);
641 assert_eq!(b.len(), 51);
642 assert_eq!(a.get(50), None);
643 assert_eq!(b.get(50), Some(&999));
644 // First 50 elements are visible from both handles.
645 for i in 0..50_usize {
646 assert_eq!(a.get(i), Some(&(i as u64)));
647 assert_eq!(b.get(i), Some(&(i as u64)));
648 }
649 }
650
651 #[test]
652 fn set_rewrites_element_in_tail() {
653 let pv: PersistentVec<u64> = PersistentVec::new()
654 .push(10)
655 .push(20)
656 .push(30)
657 .set(1, 200)
658 .unwrap();
659 assert_eq!(pv.get(0), Some(&10));
660 assert_eq!(pv.get(1), Some(&200));
661 assert_eq!(pv.get(2), Some(&30));
662 }
663
664 #[test]
665 fn set_rewrites_element_in_trie() {
666 // Need ≥ 33 elements so that position 0 lives in the trie, not tail.
667 let mut pv: PersistentVec<u64> = PersistentVec::new();
668 for i in 0..40_u64 {
669 pv = pv.push(i);
670 }
671 let pv2 = pv.set(0, 9999).unwrap();
672 assert_eq!(pv2.get(0), Some(&9999));
673 assert_eq!(pv.get(0), Some(&0), "set must not mutate original");
674 assert_eq!(pv2.get(39), Some(&39));
675 }
676
677 #[test]
678 fn set_out_of_bounds_is_none() {
679 let pv: PersistentVec<u64> = PersistentVec::new().push(1);
680 assert!(pv.set(5, 99).is_none());
681 }
682
683 #[test]
684 fn iter_matches_get_for_full_walk() {
685 let mut pv: PersistentVec<u64> = PersistentVec::new();
686 for i in 0..200_u64 {
687 pv = pv.push(i * 7);
688 }
689 let via_iter: Vec<u64> = pv.iter().copied().collect();
690 let via_get: Vec<u64> = (0..pv.len()).map(|i| *pv.get(i).unwrap()).collect();
691 assert_eq!(via_iter, via_get);
692 assert_eq!(via_iter.len(), 200);
693 assert_eq!(via_iter[199], 199 * 7);
694 }
695
696 #[test]
697 fn iter_size_hint_exact() {
698 let mut pv: PersistentVec<u64> = PersistentVec::new();
699 for i in 0..15_u64 {
700 pv = pv.push(i);
701 }
702 let it = pv.iter();
703 assert_eq!(it.size_hint(), (15, Some(15)));
704 assert_eq!(it.count(), 15);
705 }
706
707 /// SplitMix-style PRNG so the fuzz oracle is reproducible without pulling
708 /// `rand` in. Same mixer the NSW level assignment uses upstream.
709 struct Splitmix(u64);
710 impl Splitmix {
711 fn new(seed: u64) -> Self {
712 Self(seed)
713 }
714 fn next(&mut self) -> u64 {
715 self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
716 let mut x = self.0;
717 x = (x ^ (x >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
718 x = (x ^ (x >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
719 x ^ (x >> 31)
720 }
721 }
722
723 /// Random `push` / `set` / `get` operation sequence ≥ 100K steps mirrored
724 /// against the std `Vec<u64>`. Confirms PV's mutating ops match the
725 /// canonical ground-truth semantics across every BVT branch / tail
726 /// boundary / root overflow.
727 #[test]
728 fn fuzz_oracle_against_vec_u64() {
729 let mut pv: PersistentVec<u64> = PersistentVec::new();
730 let mut oracle: Vec<u64> = Vec::new();
731 let mut rng = Splitmix::new(0xC0FFEE_u64);
732 const STEPS: usize = 100_000;
733 for step in 0..STEPS {
734 let r = rng.next();
735 // Bias toward push so we actually grow the trie past the second
736 // boundary (33k+ in ~80k pushes).
737 let op = r % 4; // 0..2 push, 3 set
738 match (op, oracle.len()) {
739 (0 | 1 | 2, _) | (_, 0) => {
740 let val = rng.next();
741 pv = pv.push(val);
742 oracle.push(val);
743 }
744 (3, n) => {
745 let idx = (rng.next() as usize) % n;
746 let val = rng.next();
747 pv = pv.set(idx, val).expect("in-bounds set");
748 oracle[idx] = val;
749 }
750 _ => unreachable!(),
751 }
752 // Cheap step-end check: head + tail + a sampled interior cell.
753 assert_eq!(pv.len(), oracle.len(), "len drift @ step {step}");
754 if !oracle.is_empty() {
755 assert_eq!(pv.get(0), oracle.first(), "head drift @ step {step}");
756 assert_eq!(
757 pv.get(oracle.len() - 1),
758 oracle.last(),
759 "tail drift @ step {step}"
760 );
761 let probe = (rng.next() as usize) % oracle.len();
762 assert_eq!(
763 pv.get(probe),
764 Some(&oracle[probe]),
765 "interior drift @ step {step}, probe {probe}"
766 );
767 }
768 }
769 // Final exhaustive sweep — every element must match.
770 for i in 0..oracle.len() {
771 assert_eq!(pv.get(i), Some(&oracle[i]), "final drift at {i}");
772 }
773 // And `iter` must traverse them in order.
774 let via_iter: Vec<u64> = pv.iter().copied().collect();
775 assert_eq!(via_iter, oracle, "iter drift");
776 }
777
778 /// Clone-isolation: build PV A, branch into B and C from a midpoint, mutate
779 /// each independently, and verify each handle reads back its own mutations
780 /// without leaking into the others.
781 #[test]
782 fn fuzz_oracle_clone_isolation() {
783 let mut a: PersistentVec<u64> = PersistentVec::new();
784 let mut oracle_a: Vec<u64> = Vec::new();
785 let mut rng = Splitmix::new(0xDECAFBAD_u64);
786 for _ in 0..2_000 {
787 let v = rng.next();
788 a = a.push(v);
789 oracle_a.push(v);
790 }
791 // Branch.
792 let mut b = a.clone();
793 let mut oracle_b = oracle_a.clone();
794 let mut c = a.clone();
795 let mut oracle_c = oracle_a.clone();
796 // Mutate B and C independently.
797 for _ in 0..500 {
798 let v = rng.next();
799 b = b.push(v);
800 oracle_b.push(v);
801 }
802 for _ in 0..300 {
803 let idx = (rng.next() as usize) % oracle_c.len();
804 let v = rng.next();
805 c = c.set(idx, v).expect("in-bounds");
806 oracle_c[idx] = v;
807 }
808 // Each handle must match its own oracle, end to end.
809 for (i, &want) in oracle_a.iter().enumerate() {
810 assert_eq!(a.get(i), Some(&want), "A drift at {i}");
811 }
812 for (i, &want) in oracle_b.iter().enumerate() {
813 assert_eq!(b.get(i), Some(&want), "B drift at {i}");
814 }
815 for (i, &want) in oracle_c.iter().enumerate() {
816 assert_eq!(c.get(i), Some(&want), "C drift at {i}");
817 }
818 assert_eq!(a.len(), oracle_a.len());
819 assert_eq!(b.len(), oracle_b.len());
820 assert_eq!(c.len(), oracle_c.len());
821 }
822
823 /// v4.39.1: `push_mut` fuzz oracle. Same shape as the `push` oracle but
824 /// drives the in-place transient path so every BVT branch / tail boundary
825 /// / root overflow is hit under `Arc::make_mut`. Confirms the optimization
826 /// preserves the canonical `Vec<u64>` ground-truth.
827 #[test]
828 fn fuzz_oracle_push_mut_against_vec_u64() {
829 let mut pv: PersistentVec<u64> = PersistentVec::new();
830 let mut oracle: Vec<u64> = Vec::new();
831 let mut rng = Splitmix::new(0xFEEDFACE_u64);
832 const STEPS: usize = 100_000;
833 for step in 0..STEPS {
834 let val = rng.next();
835 pv.push_mut(val);
836 oracle.push(val);
837 assert_eq!(pv.len(), oracle.len(), "len drift @ step {step}");
838 if step % 1024 == 0 {
839 // Cheap spot-check; full sweep at end.
840 let probe = (rng.next() as usize) % oracle.len();
841 assert_eq!(
842 pv.get(probe),
843 Some(&oracle[probe]),
844 "interior drift @ step {step}, probe {probe}"
845 );
846 }
847 }
848 for i in 0..oracle.len() {
849 assert_eq!(pv.get(i), Some(&oracle[i]), "final drift at {i}");
850 }
851 }
852
853 /// v4.39.1: critical invariant — when a `Clone`d handle B exists and the
854 /// original A calls `push_mut(x)`, B's view is **not** affected (the
855 /// `Arc::make_mut` tail-clone keeps the immutable contract). Without
856 /// this guarantee the v4.34 BEGIN..COMMIT wrap (which holds a Catalog
857 /// snapshot) would see writes leak across the snapshot boundary.
858 #[test]
859 fn push_mut_does_not_disturb_cloned_handle() {
860 let mut a: PersistentVec<u64> = PersistentVec::new();
861 for i in 0..200_u64 {
862 a.push_mut(i);
863 }
864 let b = a.clone();
865 // A pushes through the tail boundary multiple times.
866 for i in 200_u64..500 {
867 a.push_mut(i);
868 }
869 assert_eq!(b.len(), 200);
870 for i in 0..200_u64 {
871 assert_eq!(b.get(i as usize), Some(&i), "B drift at {i}");
872 }
873 assert!(b.get(200).is_none());
874 assert_eq!(a.len(), 500);
875 for i in 0..500_u64 {
876 assert_eq!(a.get(i as usize), Some(&i), "A drift at {i}");
877 }
878 }
879
880 #[test]
881 fn push_clone_arc_count_stays_constant_in_old_handle() {
882 // Smoke check that v4.38's O(1) clone really is Arc bumps: push 200
883 // elements, take 5 clones, drop them all, verify the original is
884 // unchanged. (No way to assert Arc strong_count here without exposing
885 // internals — we just verify the original reads back correctly,
886 // which is the property that actually matters.)
887 let mut a: PersistentVec<u64> = PersistentVec::new();
888 for i in 0..200_u64 {
889 a = a.push(i);
890 }
891 let snapshots: Vec<PersistentVec<u64>> = (0..5).map(|_| a.clone()).collect();
892 drop(snapshots);
893 for i in 0..200_u64 {
894 assert_eq!(a.get(i as usize), Some(&i));
895 }
896 assert_eq!(a.len(), 200);
897 }
898}
899
900/// v7.39 (round 567) — see [`PersistentVec::run_cursor`].
901#[derive(Debug)]
902pub struct RunCursor<'a, T> {
903 vec: &'a PersistentVec<T>,
904 /// `(start, run)` — `run[i - start]` is element `i`.
905 run: Option<(usize, &'a [T])>,
906}
907
908impl<'a, T> RunCursor<'a, T> {
909 /// Element `i`, descending only when it falls outside the held run.
910 pub fn get(&mut self, i: usize) -> Option<&'a T> {
911 if let Some((start, run)) = self.run
912 && i >= start
913 && i - start < run.len()
914 {
915 return run.get(i - start);
916 }
917 let (start, run) = self.vec.run_containing(i)?;
918 self.run = Some((start, run));
919 run.get(i - start)
920 }
921}