Skip to main content

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}