Skip to main content

triblespace_core/patch/
branch.rs

1use super::*;
2use core::sync::atomic;
3use core::sync::atomic::Ordering::Acquire;
4use core::sync::atomic::Ordering::Relaxed;
5use core::sync::atomic::Ordering::Release;
6use std::alloc::alloc_zeroed;
7use std::alloc::dealloc;
8use std::alloc::handle_alloc_error;
9use std::alloc::Layout;
10use std::ops::Deref;
11use std::ops::DerefMut;
12use std::ptr::addr_of;
13use std::ptr::addr_of_mut;
14use std::sync::Arc;
15
16const BRANCH_ALIGN: usize = 16;
17const BRANCH_BASE_SIZE: usize = 64;
18const TABLE_ENTRY_SIZE: usize = 8;
19
20/// Marker trait for opaque owners of bytes referenced by archive-backed
21/// PATCH nodes. An `Option<Arc<dyn ArchiveOwner>>` lives on each
22/// [`Branch`]; when `Some(arc)`, the Arc keeps the underlying bytes
23/// (typically a memory-mapped archive blob) alive so that any
24/// `LocalLeaf` children — which are thin pointers into those bytes —
25/// remain valid for the Branch's lifetime. The trait is intentionally
26/// empty: the owner's only job is to drop the bytes when its refcount
27/// hits zero.
28pub trait ArchiveOwner: Send + Sync + 'static {}
29
30impl<T: Send + Sync + 'static + ?Sized> ArchiveOwner for T {}
31
32#[inline]
33pub(crate) fn dst_len<T>(ptr: *const [T]) -> usize {
34    let ptr: *const [()] = ptr as _;
35    // SAFETY: There is no aliasing as () is zero-sized
36    let slice: &[()] = unsafe { &*ptr };
37    slice.len()
38}
39
40// Mutable editor for a Branch body. This lives in the branch module and
41// encapsulates NonNull/pointer handling for mutating operations. When the
42// editor is dropped it automatically writes the final pointer back into the
43// owning Head via Head::set_body.
44pub(crate) type BranchNN<const KEY_LEN: usize, O, V> =
45    NonNull<Branch<KEY_LEN, O, [Option<Head<KEY_LEN, O, V>>], V>>;
46
47pub(crate) struct BranchMut<'a, const KEY_LEN: usize, O: KeySchema<KEY_LEN>, V> {
48    head: &'a mut Head<KEY_LEN, O, V>,
49    branch_nn: BranchNN<KEY_LEN, O, V>,
50}
51
52impl<'a, const KEY_LEN: usize, O: KeySchema<KEY_LEN>, V> BranchMut<'a, KEY_LEN, O, V> {
53    pub(crate) fn from_head(head: &'a mut Head<KEY_LEN, O, V>) -> Self {
54        match head.body_mut() {
55            BodyMut::Branch(branch_ref) => {
56                let nn = unsafe { NonNull::new_unchecked(branch_ref as *mut _) };
57                Self {
58                    head,
59                    branch_nn: nn,
60                }
61            }
62            BodyMut::Leaf(_) | BodyMut::LocalLeaf(_) => {
63                panic!("BranchMut requires a Branch body")
64            }
65        }
66    }
67
68    #[allow(dead_code)]
69    pub(crate) fn from_slot(slot: &'a mut Option<Head<KEY_LEN, O, V>>) -> Self {
70        let head = slot.as_mut().expect("slot should not be empty");
71        Self::from_head(head)
72    }
73
74    pub fn modify_child<F>(&mut self, key: u8, f: F)
75    where
76        F: FnOnce(Option<Head<KEY_LEN, O, V>>) -> Option<Head<KEY_LEN, O, V>>,
77    {
78        // Delegate to the low-level NonNull based primitive which may grow and
79        // update the pointer in-place.
80        Branch::modify_child(&mut self.branch_nn, key, f);
81    }
82
83    /// Like [`modify_child`] but uses the supplied `inserted_hash`
84    /// for the empty-slot insertion case instead of calling
85    /// `inserted.hash()`. Lets archive ingest avoid recomputing
86    /// the LocalLeaf siphash24 once per index — the caller already
87    /// has it from `ArchiveEntry::hash`.
88    ///
89    /// The hint MUST equal the hash of whatever `f(None)` returns.
90    /// When the slot is non-empty and `f(Some(_))` runs, the result
91    /// is hashed normally (recursion result, hash already cached on
92    /// the Branch).
93    pub fn modify_child_with_inserted_hint<F>(&mut self, key: u8, inserted_hash: u128, f: F)
94    where
95        F: FnOnce(Option<Head<KEY_LEN, O, V>>) -> Option<Head<KEY_LEN, O, V>>,
96    {
97        Branch::modify_child_with_inserted_hint(&mut self.branch_nn, key, inserted_hash, f);
98    }
99
100    /// Insert `head` into the child table, growing the allocation if cuckoo
101    /// placement fails. Does *not* update the branch's aggregates —
102    /// pair with [`Self::recompute_aggregates`] for bulk rewrites.
103    #[cfg_attr(not(feature = "parallel"), allow(dead_code))]
104    pub fn install_child_growing(&mut self, head: Head<KEY_LEN, O, V>) {
105        unsafe {
106            Branch::install_child_growing(&mut self.branch_nn, head);
107        }
108    }
109
110    /// Rebuild aggregates (hash/leaf_count/segment_count/childleaf) in one
111    /// linear pass over `child_table`. Call once after a batch of
112    /// [`Self::install_child_growing`] mutations.
113    #[cfg_attr(not(feature = "parallel"), allow(dead_code))]
114    pub fn recompute_aggregates(&mut self) {
115        unsafe {
116            Branch::recompute_aggregates(&mut self.branch_nn);
117        }
118    }
119}
120
121impl<'a, const KEY_LEN: usize, O: KeySchema<KEY_LEN>, V> Deref for BranchMut<'a, KEY_LEN, O, V> {
122    type Target = Branch<KEY_LEN, O, [Option<Head<KEY_LEN, O, V>>], V>;
123
124    fn deref(&self) -> &Self::Target {
125        unsafe { self.branch_nn.as_ref() }
126    }
127}
128
129impl<'a, const KEY_LEN: usize, O: KeySchema<KEY_LEN>, V> DerefMut for BranchMut<'a, KEY_LEN, O, V> {
130    fn deref_mut(&mut self) -> &mut Self::Target {
131        unsafe { self.branch_nn.as_mut() }
132    }
133}
134
135impl<'a, const KEY_LEN: usize, O: KeySchema<KEY_LEN>, V> Drop for BranchMut<'a, KEY_LEN, O, V> {
136    fn drop(&mut self) {
137        // Commit the final branch pointer into the owning Head.
138        self.head.set_body(self.branch_nn);
139    }
140}
141
142#[repr(C, align(16))]
143pub(crate) struct Branch<const KEY_LEN: usize, O: KeySchema<KEY_LEN>, Table: ?Sized, V> {
144    key_ordering: PhantomData<O>,
145    key_segments: PhantomData<O::Segmentation>,
146    /// Phantom `V`: the value type is no longer stored on the branch
147    /// itself (the childleaf is just `*const [u8; KEY_LEN]`), but it
148    /// stays carried so child `Head<KEY_LEN, O, V>` slots in
149    /// `child_table` and the `Body` impl for the concrete child-table
150    /// shape stay generic in `V`.
151    _value: PhantomData<fn() -> V>,
152
153    rc: atomic::AtomicU32,
154    pub end_depth: u32,
155    /// Thin pointer to the key bytes of a representative descendant
156    /// leaf, used for prefix-matching shortcuts. Points either into a
157    /// heap [`Leaf`]'s inline `key` field (offset 0 thanks to
158    /// `#[repr(C)]`) or into archive memory referenced by a
159    /// `LocalLeaf`. The unified `*const [u8; KEY_LEN]` representation
160    /// lets both leaf flavors serve as the childleaf.
161    pub childleaf: *const [u8; KEY_LEN],
162    pub leaf_count: u64,
163    pub segment_count: u64,
164    pub hash: u128,
165    /// Owner reference keeping `LocalLeaf` children's underlying bytes alive.
166    /// `None` for pure-memory branches; `Some(arc)` for archive-backed
167    /// branches. Niche-optimized to 16 bytes via the inner Arc's `NonNull`
168    /// data pointer — no discriminator byte. See [`ArchiveOwner`].
169    pub owner: Option<Arc<dyn ArchiveOwner>>,
170    pub child_table: Table,
171}
172
173// Manual Debug since `Option<Arc<dyn ArchiveOwner>>` doesn't impl Debug
174// (the trait is intentionally minimal — no Debug bound).
175impl<const KEY_LEN: usize, O: KeySchema<KEY_LEN>, Table: ?Sized + core::fmt::Debug, V: core::fmt::Debug>
176    core::fmt::Debug for Branch<KEY_LEN, O, Table, V>
177{
178    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
179        f.debug_struct("Branch")
180            .field("rc", &self.rc)
181            .field("end_depth", &self.end_depth)
182            .field("childleaf", &self.childleaf)
183            .field("leaf_count", &self.leaf_count)
184            .field("segment_count", &self.segment_count)
185            .field("hash", &self.hash)
186            .field("owner", &self.owner.as_ref().map(|_| "<archive owner>"))
187            .field("child_table", &&self.child_table)
188            .finish()
189    }
190}
191
192impl<const KEY_LEN: usize, O: KeySchema<KEY_LEN>, Table: ?Sized, V> Branch<KEY_LEN, O, Table, V> {
193    /// Returns the key bytes of the representative child leaf. The
194    /// pointer is set to a heap `Leaf`'s `key` field (offset 0) or to
195    /// a `LocalLeaf`'s archive-resident bytes; both yield the same
196    /// reference shape.
197    pub fn childleaf_key(&self) -> &[u8; KEY_LEN] {
198        unsafe { &*self.childleaf }
199    }
200
201    /// Returns the raw key-bytes pointer of the representative child
202    /// leaf. Used for pointer-identity comparisons during invariant
203    /// checks and for propagating the representative through
204    /// branch-construction paths.
205    pub fn childleaf_ptr(&self) -> *const [u8; KEY_LEN] {
206        self.childleaf
207    }
208}
209
210impl<const KEY_LEN: usize, O: KeySchema<KEY_LEN>, V> Body
211    for Branch<KEY_LEN, O, [Option<Head<KEY_LEN, O, V>>], V>
212{
213    fn tag(body: NonNull<Self>) -> HeadTag {
214        unsafe {
215            let ptr = addr_of!((*body.as_ptr()).child_table);
216            let exp = dst_len(ptr).ilog2() as u8;
217            debug_assert!((1..=8).contains(&exp));
218            HeadTag::from_raw(exp)
219        }
220    }
221}
222
223impl<const KEY_LEN: usize, O: KeySchema<KEY_LEN>, V>
224    Branch<KEY_LEN, O, [Option<Head<KEY_LEN, O, V>>], V>
225{
226    pub(super) fn new(
227        end_depth: usize,
228        lchild: Head<KEY_LEN, O, V>,
229        rchild: Head<KEY_LEN, O, V>,
230    ) -> NonNull<Self> {
231        Self::new_with_owner(end_depth, lchild, rchild, None)
232    }
233
234    /// Like [`Self::new`] but sets the branch's `owner` field — used by
235    /// the archive-leaf-elimination path so that a Branch created when
236    /// inserting a `LocalLeaf` adopts the entry's archive owner.
237    pub(super) fn new_with_owner(
238        end_depth: usize,
239        lchild: Head<KEY_LEN, O, V>,
240        rchild: Head<KEY_LEN, O, V>,
241        owner: Option<Arc<dyn ArchiveOwner>>,
242    ) -> NonNull<Self> {
243        // Compute rchild's hash via the normal path. For LocalLeaf
244        // this triggers siphash24; the
245        // [`new_with_owner_and_rchild_hash`] variant skips it when
246        // the caller has the hash already.
247        let rchild_hash = rchild.hash();
248        Self::new_with_owner_and_rchild_hash(end_depth, lchild, rchild, owner, rchild_hash)
249    }
250
251    /// Variant of [`Self::new_with_owner`] that takes a precomputed
252    /// `rchild_hash` and uses it instead of calling `rchild.hash()`.
253    /// Lets archive-ingest divergence paths reuse the
254    /// `ArchiveEntry::hash` they already have instead of recomputing
255    /// siphash24 over the LocalLeaf bytes.
256    ///
257    /// `rchild_hash` MUST equal `rchild.hash()`. The lchild hash
258    /// still goes through the normal path — it's typically a Branch
259    /// (cached) or heap Leaf (cached), so the only LocalLeaf hash
260    /// recompute that matters is on the freshly inserted side.
261    pub(super) fn new_with_owner_and_rchild_hash(
262        end_depth: usize,
263        lchild: Head<KEY_LEN, O, V>,
264        rchild: Head<KEY_LEN, O, V>,
265        owner: Option<Arc<dyn ArchiveOwner>>,
266        rchild_hash: u128,
267    ) -> NonNull<Self> {
268        unsafe {
269            let size = 2;
270            // SAFETY: `BRANCH_ALIGN` is a power of two and `size` is small enough
271            // that the computed layout size is valid.
272            let layout = Layout::from_size_align_unchecked(
273                BRANCH_BASE_SIZE + (TABLE_ENTRY_SIZE * size),
274                BRANCH_ALIGN,
275            );
276            let Some(ptr) =
277                NonNull::new(std::ptr::slice_from_raw_parts(alloc_zeroed(layout), size)
278                    as *mut Branch<KEY_LEN, O, [Option<Head<KEY_LEN, O, V>>], V>)
279            else {
280                handle_alloc_error(layout);
281            };
282            addr_of_mut!((*ptr.as_ptr()).rc).write(atomic::AtomicU32::new(1));
283            addr_of_mut!((*ptr.as_ptr()).end_depth).write(end_depth as u32);
284            addr_of_mut!((*ptr.as_ptr()).childleaf).write(lchild.childleaf_ptr());
285            addr_of_mut!((*ptr.as_ptr()).leaf_count).write(lchild.count() + rchild.count());
286            addr_of_mut!((*ptr.as_ptr()).segment_count)
287                .write(lchild.count_segment(end_depth) + rchild.count_segment(end_depth));
288            addr_of_mut!((*ptr.as_ptr()).hash).write(lchild.hash() ^ rchild_hash);
289            addr_of_mut!((*ptr.as_ptr()).owner).write(owner);
290            (*ptr.as_ptr()).child_table[0] = Some(lchild);
291            (*ptr.as_ptr()).child_table[1] = Some(rchild);
292
293            ptr
294        }
295    }
296
297    pub(super) unsafe fn rc_inc(branch: NonNull<Self>) -> NonNull<Self> {
298        unsafe {
299            let branch = branch.as_ptr();
300            let mut current = (*branch).rc.load(Relaxed);
301            loop {
302                if current == u32::MAX {
303                    panic!("max refcount exceeded");
304                }
305                match (*branch)
306                    .rc
307                    .compare_exchange(current, current + 1, Relaxed, Relaxed)
308                {
309                    Ok(_) => return NonNull::new_unchecked(branch),
310                    Err(v) => current = v,
311                }
312            }
313        }
314    }
315
316    pub(super) unsafe fn rc_dec(branch: NonNull<Self>) {
317        unsafe {
318            let branch = branch.as_ptr();
319            if (*branch).rc.fetch_sub(1, Release) != 1 {
320                return;
321            }
322            (*branch).rc.load(Acquire);
323
324            let size = dst_len(addr_of!((*branch).child_table));
325
326            std::ptr::drop_in_place(branch);
327
328            // SAFETY: layout parameters are constructed from constants and a
329            // runtime `size` that ensures alignment and size validity.
330            let layout = Layout::from_size_align_unchecked(
331                BRANCH_BASE_SIZE + (TABLE_ENTRY_SIZE * size),
332                BRANCH_ALIGN,
333            );
334            let ptr = branch as *mut u8;
335            dealloc(ptr, layout);
336        }
337    }
338
339    /// Ensure the branch is uniquely owned. If it is shared (rc > 1) a
340    /// copy is allocated and `*branch_nn` is updated to point to the new unique
341    /// allocation. Returns `Some(())` if a copy was made, or `None` if the
342    /// branch was already unique.
343    pub(super) unsafe fn rc_cow(branch_nn: &mut NonNull<Self>) -> Option<()> {
344        unsafe {
345            let branch = branch_nn.as_ptr();
346            if (*branch).rc.load(Acquire) == 1 {
347                None
348            } else {
349                let size = dst_len(addr_of!((*branch).child_table));
350                // SAFETY: `size` preserves alignment requirements and the size
351                // calculation cannot overflow for the allowed range.
352                let layout = Layout::from_size_align_unchecked(
353                    BRANCH_BASE_SIZE + (TABLE_ENTRY_SIZE * size),
354                    BRANCH_ALIGN,
355                );
356                if let Some(ptr) =
357                    NonNull::new(std::ptr::slice_from_raw_parts(alloc_zeroed(layout), size)
358                        as *mut Branch<KEY_LEN, O, [Option<Head<KEY_LEN, O, V>>], V>)
359                {
360                    addr_of_mut!((*ptr.as_ptr()).rc).write(atomic::AtomicU32::new(1));
361                    addr_of_mut!((*ptr.as_ptr()).end_depth).write((*branch).end_depth);
362                    addr_of_mut!((*ptr.as_ptr()).childleaf).write((*branch).childleaf);
363                    addr_of_mut!((*ptr.as_ptr()).leaf_count).write((*branch).leaf_count);
364                    addr_of_mut!((*ptr.as_ptr()).segment_count).write((*branch).segment_count);
365                    addr_of_mut!((*ptr.as_ptr()).hash).write((*branch).hash);
366                    addr_of_mut!((*ptr.as_ptr()).owner).write((*branch).owner.clone());
367                    (*ptr.as_ptr())
368                        .child_table
369                        .clone_from_slice(&(*branch).child_table);
370
371                    Self::rc_dec(NonNull::new_unchecked(branch));
372                    *branch_nn = ptr;
373                    Some(())
374                } else {
375                    handle_alloc_error(layout);
376                }
377            }
378        }
379    }
380
381    /// Grow the branch's allocation in-place by updating the provided
382    /// `branch_nn` to point to a larger allocation. The caller must provide a
383    /// mutable reference to the owned pointer; this function updates it when a
384    /// new allocation is made.
385    pub(crate) fn grow(branch_nn: &mut NonNull<Self>) {
386        unsafe {
387            let branch = branch_nn.as_ptr();
388            let old_size = dst_len(addr_of!((*branch).child_table));
389            let new_size = old_size * 2;
390            assert!(new_size <= 256);
391
392            // SAFETY: `new_size` is bounded and alignment is constant, so the
393            // resulting layout is valid for allocation.
394            let layout = Layout::from_size_align_unchecked(
395                BRANCH_BASE_SIZE + (TABLE_ENTRY_SIZE * new_size),
396                BRANCH_ALIGN,
397            );
398            if let Some(ptr) = NonNull::new(std::ptr::slice_from_raw_parts(
399                alloc_zeroed(layout),
400                new_size,
401            )
402                as *mut Branch<KEY_LEN, O, [Option<Head<KEY_LEN, O, V>>], V>)
403            {
404                addr_of_mut!((*ptr.as_ptr()).rc).write(atomic::AtomicU32::new(1));
405                addr_of_mut!((*ptr.as_ptr()).end_depth).write((*branch).end_depth);
406                addr_of_mut!((*ptr.as_ptr()).leaf_count).write((*branch).leaf_count);
407                addr_of_mut!((*ptr.as_ptr()).segment_count).write((*branch).segment_count);
408                addr_of_mut!((*ptr.as_ptr()).childleaf).write((*branch).childleaf);
409                addr_of_mut!((*ptr.as_ptr()).hash).write((*branch).hash);
410                addr_of_mut!((*ptr.as_ptr()).owner).write((*branch).owner.clone());
411                // Note that the child_table is already zeroed by the allocator and therefore None initialized.
412
413                (*branch)
414                    .child_table
415                    .table_grow(&mut (*ptr.as_ptr()).child_table);
416
417                Branch::<KEY_LEN, O, [Option<Head<KEY_LEN, O, V>>], V>::rc_dec(
418                    NonNull::new_unchecked(branch),
419                );
420
421                *branch_nn = ptr;
422            } else {
423                handle_alloc_error(layout);
424            }
425        }
426    }
427
428    // Insert-child helper removed — use `modify_child` which consolidates
429    // insert/update/remove logic and handles potential growth in-place.
430
431    /// Generalized modify/insert/remove primitive for a child slot.
432    ///
433    /// The closure receives the current child if present (Some) or None when
434    /// the slot is empty and should return the new child to place into the
435    /// slot (Some) or None to remove/leave empty. This consolidates the
436    /// insert/update/remove logic in one place and updates branch aggregates
437    /// and `childleaf` as needed. The `branch_nn` pointer may be updated in
438    /// place when the underlying allocation grows.
439    pub(super) fn modify_child<F>(branch_nn: &mut NonNull<Self>, key: u8, f: F)
440    where
441        F: FnOnce(Option<Head<KEY_LEN, O, V>>) -> Option<Head<KEY_LEN, O, V>>,
442    {
443        unsafe {
444            let branch = branch_nn.as_ptr();
445            let end_depth = (*branch).end_depth as usize;
446
447            // If a slot exists, operate on the existing child in-place.
448            if let Some(slot) = (*branch).child_table.table_get_slot(key) {
449                let child = slot.take().unwrap();
450                let old_child_hash = child.hash();
451                let old_child_segment_count = child.count_segment(end_depth);
452                let old_child_leaf_count = child.count();
453
454                let replaced_childleaf = child.childleaf_ptr() == (*branch).childleaf;
455
456                if let Some(new_child) = f(Some(child)) {
457                    // Replace existing child
458                    (*branch).hash = ((*branch).hash ^ old_child_hash) ^ new_child.hash();
459                    (*branch).segment_count = ((*branch).segment_count - old_child_segment_count)
460                        + new_child.count_segment(end_depth);
461                    (*branch).leaf_count =
462                        ((*branch).leaf_count - old_child_leaf_count) + new_child.count();
463
464                    if replaced_childleaf {
465                        (*branch).childleaf = new_child.childleaf_ptr();
466                    }
467
468                    if slot.replace(new_child.with_key(key)).is_some() {
469                        unreachable!();
470                    }
471                } else {
472                    // Remove existing child
473                    (*branch).hash ^= old_child_hash;
474                    (*branch).segment_count -= old_child_segment_count;
475                    (*branch).leaf_count -= old_child_leaf_count;
476
477                    if replaced_childleaf {
478                        if let Some(other) = (*branch).child_table.iter().find_map(|s| s.as_ref()) {
479                            (*branch).childleaf = other.childleaf_ptr();
480                        }
481                    }
482                }
483            } else {
484                // No current slot — the closure can choose to insert a child.
485                if let Some(mut inserted) = f(None) {
486                    // The caller is expected to pass an inserted Head that is
487                    // already prepared (with_start set to the appropriate depth).
488                    // Update aggregates before attempting insertion.
489                    (*branch).leaf_count += inserted.count();
490                    (*branch).segment_count += inserted.count_segment(end_depth);
491                    (*branch).hash ^= inserted.hash();
492
493                    // Cuckoo insert loop, growing the table when necessary.
494                    let mut branch_ptr = branch_nn.as_ptr();
495                    while let Some(new_displaced) = (*branch_ptr).child_table.table_insert(inserted)
496                    {
497                        inserted = new_displaced;
498                        Self::grow(branch_nn);
499                        // Refresh local pointer after potential reallocation.
500                        branch_ptr = branch_nn.as_ptr();
501                    }
502                }
503            }
504            // Debug invariant check (no-op in release builds).
505            #[cfg(debug_assertions)]
506            branch_nn.as_ref().debug_check_invariants();
507        }
508    }
509
510    /// Variant of [`Self::modify_child`] that takes a precomputed
511    /// `inserted_hash` and uses it for the empty-slot insertion path
512    /// instead of calling `inserted.hash()`. The hint MUST equal the
513    /// hash of whatever `f(None)` returns. The non-empty path uses
514    /// `new_child.hash()` as normal (the recursive result is a Branch
515    /// whose hash is already cached, so the call is O(1)).
516    pub(super) fn modify_child_with_inserted_hint<F>(
517        branch_nn: &mut NonNull<Self>,
518        key: u8,
519        inserted_hash: u128,
520        f: F,
521    )
522    where
523        F: FnOnce(Option<Head<KEY_LEN, O, V>>) -> Option<Head<KEY_LEN, O, V>>,
524    {
525        unsafe {
526            let branch = branch_nn.as_ptr();
527            let end_depth = (*branch).end_depth as usize;
528
529            if let Some(slot) = (*branch).child_table.table_get_slot(key) {
530                let child = slot.take().unwrap();
531                let old_child_hash = child.hash();
532                let old_child_segment_count = child.count_segment(end_depth);
533                let old_child_leaf_count = child.count();
534
535                let replaced_childleaf = child.childleaf_ptr() == (*branch).childleaf;
536
537                if let Some(new_child) = f(Some(child)) {
538                    // Recursion result — its hash is cached on the
539                    // returned Head (Branch.hash field), so calling
540                    // .hash() is cheap.
541                    (*branch).hash = ((*branch).hash ^ old_child_hash) ^ new_child.hash();
542                    (*branch).segment_count = ((*branch).segment_count - old_child_segment_count)
543                        + new_child.count_segment(end_depth);
544                    (*branch).leaf_count =
545                        ((*branch).leaf_count - old_child_leaf_count) + new_child.count();
546
547                    if replaced_childleaf {
548                        (*branch).childleaf = new_child.childleaf_ptr();
549                    }
550
551                    if slot.replace(new_child.with_key(key)).is_some() {
552                        unreachable!();
553                    }
554                } else {
555                    (*branch).hash ^= old_child_hash;
556                    (*branch).segment_count -= old_child_segment_count;
557                    (*branch).leaf_count -= old_child_leaf_count;
558
559                    if replaced_childleaf {
560                        if let Some(other) = (*branch).child_table.iter().find_map(|s| s.as_ref()) {
561                            (*branch).childleaf = other.childleaf_ptr();
562                        }
563                    }
564                }
565            } else {
566                if let Some(mut inserted) = f(None) {
567                    // Use the caller-supplied hint instead of
568                    // recomputing siphash24 over the LocalLeaf bytes.
569                    (*branch).leaf_count += inserted.count();
570                    (*branch).segment_count += inserted.count_segment(end_depth);
571                    (*branch).hash ^= inserted_hash;
572
573                    let mut branch_ptr = branch_nn.as_ptr();
574                    while let Some(new_displaced) = (*branch_ptr).child_table.table_insert(inserted)
575                    {
576                        inserted = new_displaced;
577                        Self::grow(branch_nn);
578                        branch_ptr = branch_nn.as_ptr();
579                    }
580                }
581            }
582            #[cfg(debug_assertions)]
583            branch_nn.as_ref().debug_check_invariants();
584        }
585    }
586
587    // Note: upsert_child removed in favor of explicit insert_child / update_child
588
589    // The old in-place `update_child` helper has been superseded by
590    // `modify_child` which accepts an Option<Head> and handles insert/update/remove
591    // uniformly. The thin adapter was removed to centralize behavior; callers
592    // should use `modify_child` or BranchMut::modify_child.
593
594    /// Insert `head` into the child table, growing if cuckoo placement
595    /// fails. Does NOT touch aggregates — used by bulk-rewrite paths
596    /// that recompute aggregates in one pass at the end via
597    /// [`recompute_aggregates`](Self::recompute_aggregates).
598    #[cfg_attr(not(feature = "parallel"), allow(dead_code))]
599    pub(crate) unsafe fn install_child_growing(
600        branch_nn: &mut NonNull<Self>,
601        head: Head<KEY_LEN, O, V>,
602    ) {
603        let mut to_insert = head;
604        let mut branch_ptr = branch_nn.as_ptr();
605        while let Some(displaced) = (*branch_ptr).child_table.table_insert(to_insert) {
606            to_insert = displaced;
607            Self::grow(branch_nn);
608            branch_ptr = branch_nn.as_ptr();
609        }
610    }
611
612    /// Rebuild aggregate fields (`hash`, `leaf_count`, `segment_count`,
613    /// `childleaf`) from the current child table in one linear pass.
614    /// Cheaper than paying `modify_child`'s per-call accounting when
615    /// many children are being installed in bulk.
616    #[cfg_attr(not(feature = "parallel"), allow(dead_code))]
617    pub(crate) unsafe fn recompute_aggregates(branch_nn: &mut NonNull<Self>) {
618        let branch = branch_nn.as_ptr();
619        let end_depth = (*branch).end_depth as usize;
620        let mut agg_leaf_count: u64 = 0;
621        let mut agg_segment_count: u64 = 0;
622        let mut agg_hash: u128 = 0;
623        let mut first_childleaf: *const [u8; KEY_LEN] = std::ptr::null();
624
625        for child in (*branch).child_table.iter().flatten() {
626            agg_leaf_count += child.count();
627            agg_segment_count += child.count_segment(end_depth);
628            agg_hash ^= child.hash();
629            if first_childleaf.is_null() {
630                first_childleaf = child.childleaf_ptr();
631            }
632        }
633
634        (*branch).leaf_count = agg_leaf_count;
635        (*branch).segment_count = agg_segment_count;
636        (*branch).hash = agg_hash;
637        if !first_childleaf.is_null() {
638            (*branch).childleaf = first_childleaf;
639        }
640
641        #[cfg(debug_assertions)]
642        branch_nn.as_ref().debug_check_invariants();
643    }
644
645    pub fn count_segment(&self, at_depth: usize) -> u64 {
646        let node_end = self.end_depth as usize;
647        if !O::same_segment_tree(at_depth, node_end) {
648            1
649        } else {
650            self.segment_count
651        }
652    }
653
654    /// Debug-only invariant checker. Validates that the aggregate fields
655    /// (leaf_count, segment_count, hash, childleaf) are consistent with the
656    /// current child table. Exists only in debug builds so it adds zero
657    /// overhead in release binaries.
658    #[cfg(debug_assertions)]
659    pub fn debug_check_invariants(&self) {
660        let end_depth: usize = self.end_depth as usize;
661        let mut agg_leaf_count: u64 = 0;
662        let mut agg_segment_count: u64 = 0;
663        let mut agg_hash: u128 = 0;
664        let mut match_found = false;
665
666        for child in self.child_table.iter().flatten() {
667            agg_leaf_count = agg_leaf_count.saturating_add(child.count());
668            agg_segment_count = agg_segment_count.saturating_add(child.count_segment(end_depth));
669            agg_hash ^= child.hash();
670            if child.childleaf_ptr() == self.childleaf {
671                match_found = true;
672            }
673        }
674
675        debug_assert_eq!(
676            agg_leaf_count, self.leaf_count,
677            "branch.leaf_count mismatch"
678        );
679        debug_assert_eq!(
680            agg_segment_count, self.segment_count,
681            "branch.segment_count mismatch"
682        );
683        debug_assert_eq!(agg_hash, self.hash, "branch.hash mismatch");
684
685        // If there are any leaves aggregated in this branch then the
686        // `childleaf` pointer must match one of the children. When the
687        // aggregate count is zero the equality check above already guarantees
688        // `self.leaf_count == 0`, so the explicit empty-branch assertion is
689        // redundant and can be omitted.
690        if agg_leaf_count > 0 {
691            debug_assert!(match_found, "branch.childleaf pointer mismatch");
692        }
693    }
694
695    /// Return true if this branch's childleaf key matches the provided
696    /// `prefix` for all tree-ordered bytes in [at_depth, PREFIX_LEN).
697    pub fn infixes<const PREFIX_LEN: usize, const INFIX_LEN: usize, F>(
698        &self,
699        prefix: &[u8; PREFIX_LEN],
700        at_depth: usize,
701        f: &mut F,
702    ) where
703        F: FnMut(&[u8; INFIX_LEN]),
704    {
705        // Early-prune: if the branch's representative childleaf doesn't match
706        // the prefix then no child in this branch can match.
707        let node_end_depth = self.end_depth as usize;
708        let limit = std::cmp::min(PREFIX_LEN, node_end_depth);
709        // If the branch's representative childleaf does NOT match the
710        // provided prefix then no child in this branch can match and we can
711        // early-return. The previous logic inverted this check which caused
712        // branches to be pruned incorrectly.
713        if !super::leaf::key_ops::has_prefix::<KEY_LEN, O>(self.childleaf_key(), at_depth, &prefix[..limit]) {
714            return;
715        }
716
717        // The infix ends within the current node.
718        if PREFIX_LEN + INFIX_LEN <= node_end_depth {
719            let infix: [u8; INFIX_LEN] =
720                core::array::from_fn(|i| self.childleaf_key()[O::TREE_TO_KEY[PREFIX_LEN + i]]);
721            f(&infix);
722            return;
723        }
724        // The prefix ends in a child of this node.
725        if PREFIX_LEN > node_end_depth {
726            if let Some(child) = self.child_table.table_get(prefix[node_end_depth]) {
727                child.infixes(prefix, node_end_depth, f);
728            }
729            return;
730        }
731
732        // The prefix ends in this node, but the infix ends in a child.
733        for entry in self.child_table.iter().flatten() {
734            entry.infixes(prefix, node_end_depth, f);
735        }
736    }
737
738    /// Like [`infixes`](Self::infixes) but only yields infixes in the
739    /// byte range `[min_infix, max_infix]` (inclusive).
740    ///
741    /// In Case 3 (prefix ends in this node, infix in children), filters
742    /// children by their byte key against the range bounds at the current
743    /// depth, pruning entire subtrees outside the range.
744    pub fn infixes_range<const PREFIX_LEN: usize, const INFIX_LEN: usize, F>(
745        &self,
746        prefix: &[u8; PREFIX_LEN],
747        at_depth: usize,
748        min_infix: &[u8; INFIX_LEN],
749        max_infix: &[u8; INFIX_LEN],
750        f: &mut F,
751    ) where
752        F: FnMut(&[u8; INFIX_LEN]),
753    {
754        let node_end_depth = self.end_depth as usize;
755        let limit = std::cmp::min(PREFIX_LEN, node_end_depth);
756        if !super::leaf::key_ops::has_prefix::<KEY_LEN, O>(self.childleaf_key(), at_depth, &prefix[..limit]) {
757            return;
758        }
759
760        // Case 1: infix ends within this node — extract and range-check.
761        if PREFIX_LEN + INFIX_LEN <= node_end_depth {
762            let infix: [u8; INFIX_LEN] =
763                core::array::from_fn(|i| self.childleaf_key()[O::TREE_TO_KEY[PREFIX_LEN + i]]);
764            if &infix >= min_infix && &infix <= max_infix {
765                f(&infix);
766            }
767            return;
768        }
769
770        // Case 2: prefix extends into a specific child.
771        if PREFIX_LEN > node_end_depth {
772            if let Some(child) = self.child_table.table_get(prefix[node_end_depth]) {
773                child.infixes_range(prefix, node_end_depth, min_infix, max_infix, f);
774            }
775            return;
776        }
777
778        // Case 3: prefix ends here, infix spans children.
779        // First check the compressed path (bytes PREFIX_LEN..node_end_depth)
780        // against the range. All children share these bytes (path compression).
781        let infix_byte_idx = node_end_depth - PREFIX_LEN;
782        let mut min_tight = true; // still on the min boundary
783        let mut max_tight = true; // still on the max boundary
784        for i in 0..infix_byte_idx {
785            let path_byte = self.childleaf_key()[O::TREE_TO_KEY[PREFIX_LEN + i]];
786            if min_tight {
787                if path_byte < min_infix[i] {
788                    return;
789                } // whole branch below min
790                if path_byte > min_infix[i] {
791                    min_tight = false;
792                } // safely above min
793            }
794            if max_tight {
795                if path_byte > max_infix[i] {
796                    return;
797                } // whole branch above max
798                if path_byte < max_infix[i] {
799                    max_tight = false;
800                } // safely below max
801            }
802        }
803
804        // Now iterate children, filtering by their byte at infix_byte_idx
805        // only when we're still tight on that boundary.
806        for entry in self.child_table.iter().flatten() {
807            let child_byte = entry.key();
808            if min_tight && infix_byte_idx < INFIX_LEN && child_byte < min_infix[infix_byte_idx] {
809                continue;
810            }
811            if max_tight && infix_byte_idx < INFIX_LEN && child_byte > max_infix[infix_byte_idx] {
812                continue;
813            }
814            entry.infixes_range(prefix, node_end_depth, min_infix, max_infix, f);
815        }
816    }
817
818    /// Count leaves whose infix falls within [min_infix, max_infix].
819    ///
820    /// Counts **distinct first-segment values** under this branch whose
821    /// infix falls within `[min_infix, max_infix]` — matching the
822    /// cardinality that `infixes_range` would yield for the same range.
823    ///
824    /// Interior children (strictly inside the range at the current byte)
825    /// contribute their cached `segment_count` via [`count_segment`]
826    /// without recursion. Only the min- and max-boundary children recurse
827    /// deeper.
828    pub fn count_range<const PREFIX_LEN: usize, const INFIX_LEN: usize>(
829        &self,
830        prefix: &[u8; PREFIX_LEN],
831        at_depth: usize,
832        min_infix: &[u8; INFIX_LEN],
833        max_infix: &[u8; INFIX_LEN],
834    ) -> u64 {
835        let node_end_depth = self.end_depth as usize;
836        let limit = std::cmp::min(PREFIX_LEN, node_end_depth);
837        if !super::leaf::key_ops::has_prefix::<KEY_LEN, O>(self.childleaf_key(), at_depth, &prefix[..limit]) {
838            return 0;
839        }
840
841        // Case 1: infix ends within this node's compressed path. The full
842        // infix is determined by this branch's path, so every leaf below
843        // shares it — exactly one distinct infix value exists under self.
844        if PREFIX_LEN + INFIX_LEN <= node_end_depth {
845            let infix: [u8; INFIX_LEN] =
846                core::array::from_fn(|i| self.childleaf_key()[O::TREE_TO_KEY[PREFIX_LEN + i]]);
847            return if &infix >= min_infix && &infix <= max_infix {
848                1
849            } else {
850                0
851            };
852        }
853
854        // Case 2: prefix extends into a specific child.
855        if PREFIX_LEN > node_end_depth {
856            if let Some(child) = self.child_table.table_get(prefix[node_end_depth]) {
857                return child.count_range(prefix, node_end_depth, min_infix, max_infix);
858            }
859            return 0;
860        }
861
862        // Case 3: prefix ends here, infix spans children.
863        // Check compressed path against range (same logic as infixes_range).
864        let infix_byte_idx = node_end_depth - PREFIX_LEN;
865        let mut min_tight = true;
866        let mut max_tight = true;
867        for i in 0..infix_byte_idx {
868            let path_byte = self.childleaf_key()[O::TREE_TO_KEY[PREFIX_LEN + i]];
869            if min_tight {
870                if path_byte < min_infix[i] {
871                    return 0;
872                }
873                if path_byte > min_infix[i] {
874                    min_tight = false;
875                }
876            }
877            if max_tight {
878                if path_byte > max_infix[i] {
879                    return 0;
880                }
881                if path_byte < max_infix[i] {
882                    max_tight = false;
883                }
884            }
885        }
886
887        let mut total = 0u64;
888        for entry in self.child_table.iter().flatten() {
889            let child_byte = entry.key();
890            let below_min = min_tight && child_byte < min_infix[infix_byte_idx];
891            let above_max = max_tight && child_byte > max_infix[infix_byte_idx];
892            if below_min || above_max {
893                continue;
894            }
895            let on_min = min_tight && child_byte == min_infix[infix_byte_idx];
896            let on_max = max_tight && child_byte == max_infix[infix_byte_idx];
897            if on_min || on_max {
898                total += entry.count_range(prefix, node_end_depth, min_infix, max_infix);
899            } else {
900                total += entry.count_segment(node_end_depth);
901            }
902        }
903        total
904    }
905
906    pub fn has_prefix<const PREFIX_LEN: usize>(
907        &self,
908        at_depth: usize,
909        prefix: &[u8; PREFIX_LEN],
910    ) -> bool {
911        const {
912            assert!(PREFIX_LEN <= KEY_LEN);
913        }
914        let node_end_depth = self.end_depth as usize;
915        let limit = std::cmp::min(PREFIX_LEN, node_end_depth);
916        if !super::leaf::key_ops::has_prefix::<KEY_LEN, O>(self.childleaf_key(), at_depth, &prefix[..limit]) {
917            return false;
918        }
919
920        if PREFIX_LEN <= node_end_depth {
921            return true;
922        }
923
924        if let Some(child) = self.child_table.table_get(prefix[node_end_depth]) {
925            return child.has_prefix::<PREFIX_LEN>(node_end_depth, prefix);
926        }
927
928        false
929    }
930
931    pub fn get<'a>(&'a self, at_depth: usize, key: &[u8; KEY_LEN]) -> Option<&'a V>
932    where
933        O: 'a,
934    {
935        let node_end_depth = self.end_depth as usize;
936        let limit = std::cmp::min(KEY_LEN, node_end_depth);
937        if !super::leaf::key_ops::has_prefix::<KEY_LEN, O>(self.childleaf_key(), at_depth, &key[..limit]) {
938            return None;
939        }
940        if node_end_depth >= KEY_LEN {
941            // Childleaf prefix matched and end_depth == KEY_LEN means the
942            // representative IS the lookup target. For ZST `V` (the only
943            // shape compatible with `LocalLeaf`-backed childleaves) we
944            // synthesize a reference from a dangling pointer; otherwise
945            // the childleaf points at a heap `Leaf<KEY_LEN, V>` whose
946            // `key` field is at offset 0, so casting recovers the Leaf.
947            if std::mem::size_of::<V>() == 0 {
948                return Some(unsafe { std::ptr::NonNull::<V>::dangling().as_ref() });
949            }
950            let leaf_ptr = self.childleaf as *const Leaf<KEY_LEN, V>;
951            return Some(unsafe { &(*leaf_ptr).value });
952        }
953
954        if let Some(child) = self.child_table.table_get(key[node_end_depth]) {
955            return child.get(node_end_depth, key);
956        }
957        None
958    }
959
960    pub fn segmented_len<const PREFIX_LEN: usize>(
961        &self,
962        at_depth: usize,
963        prefix: &[u8; PREFIX_LEN],
964    ) -> u64 {
965        let node_end_depth = self.end_depth as usize;
966        let limit = std::cmp::min(PREFIX_LEN, node_end_depth);
967        if !super::leaf::key_ops::has_prefix::<KEY_LEN, O>(self.childleaf_key(), at_depth, &prefix[..limit]) {
968            return 0;
969        }
970        if PREFIX_LEN <= node_end_depth {
971            if !O::same_segment_tree(PREFIX_LEN, node_end_depth) {
972                return 1;
973            } else {
974                return self.segment_count;
975            }
976        }
977        if let Some(child) = self.child_table.table_get(prefix[node_end_depth]) {
978            child.segmented_len::<PREFIX_LEN>(node_end_depth, prefix)
979        } else {
980            0
981        }
982    }
983
984    // Instance methods implemented directly on &Branch — these contain any
985    // required unsafe access (childleaf deref) locally and avoid forwarding
986    // through more wrappers. This keeps the call graph minimal and makes the
987    // logic easier to maintain.
988}
989
990#[cfg(test)]
991mod tests {
992    use super::*;
993
994    /// The whole archive-leaf-elimination design depends on
995    /// `Option<Arc<dyn ArchiveOwner>>` niche-optimizing to exactly 16
996    /// bytes (no discriminator byte added). The inner `Arc<dyn Trait>`
997    /// is a fat pointer (data + vtable) whose data pointer is `NonNull`,
998    /// so `None` is represented by a null data pointer — same width as
999    /// `Some`. If this size ever increases, the Branch struct grows
1000    /// silently and the design's cost analysis no longer holds; surface
1001    /// the regression here.
1002    #[test]
1003    fn option_arc_dyn_archive_owner_is_sixteen_bytes() {
1004        assert_eq!(
1005            std::mem::size_of::<Option<Arc<dyn ArchiveOwner>>>(),
1006            16,
1007            "Option<Arc<dyn ArchiveOwner>> must niche-optimize to 16 bytes"
1008        );
1009    }
1010}