Skip to main content

plugmem_arena/
arena.rs

1//! Sharded sorted arena over one flat byte pool.
2//!
3//! Full v2 mechanics of: each shard owns a **chain of
4//! range-partitioned pages** — every page holds a sorted run of keys, pages
5//! in a chain follow each other in ascending key ranges (a B+-tree leaf
6//! level without interior nodes; chains stay short because shard hashing
7//! spreads the load). A full page **splits** in half instead of failing, and
8//! a page emptied by removals is unlinked into a **free-list** for reuse, so
9//! capacity is bounded only by [`ArenaCfg::max_bytes`].
10//!
11//! The structure is tuned for **wasm environments first**: `no_std + alloc`,
12//! a single linear byte pool (snapshot = memcpy), no threads, and the one
13//! measured `unsafe` (uninitialized page allocation) whose entire benefit is
14//! on the wasm allocation path — see [`Arena::insert`] internals and the
15//! crate-level documentation for the numbers.
16//!
17//! # Overlay opens
18//!
19//! [`Arena::load_overlay`] opens an arena whose existing pages are **borrowed**
20//! from a longer-lived buffer — typically a memory-mapped file — while the
21//! arena stays fully mutable. Because an arena mutates pages *in place* (slot
22//! shifts, page splits), it cannot use the append-only tail of
23//! [`BlobHeap`](crate::BlobHeap): instead the first write to a borrowed page
24//! copies just *that* page into an owned overlay, and pages grown after open
25//! live in an owned tail (per-page copy-on-write). The borrowed base is never
26//! mutated and never cloned as a whole, so a multi-gigabyte arena can be
27//! *written to* while resident only in the pages it actually touches. Staying
28//! true to the crate's flat philosophy, the overlay is two flat `Vec`s (a
29//! dense redirect and one contiguous copy pool) — no `Box`, no map. See
30//! `examples/overlay.rs`.
31
32use alloc::vec::Vec;
33use core::fmt;
34use core::marker::PhantomData;
35
36#[cfg(feature = "counters")]
37use core::cell::Cell;
38
39use crate::error::Error;
40use crate::paged::Paged;
41use crate::slot::Slot;
42
43/// Size of one arena page in bytes.
44///
45/// Fixed, not per-slot-count: whatever the slot size, a page is one
46/// L1-friendly unit of work, and every operation touches O(1) pages.
47pub const PAGE_BYTES: usize = 4096;
48
49/// Sentinel for "no page": an empty chain head, the end of a chain, or an
50/// empty free-list.
51const NONE: u32 = u32::MAX;
52
53/// Fibonacci hashing multiplier (2^64 / phi), used by [`ShardMode::Uniform`].
54const FIB: u64 = 0x9E37_79B9_7F4A_7C15;
55
56/// Fixed prefix of the serialized metadata section (see
57/// [`Arena::dump_meta`]).
58const IMAGE_HEADER: usize = 24;
59
60/// Serialized width of one page index entry (`heads`, `next` — little-endian
61/// `u32`), used to size and offset those metadata arrays.
62const PAGE_IDX_BYTES: usize = core::mem::size_of::<u32>();
63
64/// Serialized width of one page's slot count (`counts` — little-endian `u16`).
65const COUNT_BYTES: usize = core::mem::size_of::<u16>();
66
67/// Serialized bytes of per-page metadata: one `next` index plus one `counts`
68/// entry.
69const PAGE_META_BYTES: usize = PAGE_IDX_BYTES + COUNT_BYTES;
70
71/// `true` when the `counters` feature is enabled; lets hot loops keep their
72/// counting code in one expression that constant-folds away otherwise.
73const COUNT: bool = cfg!(feature = "counters");
74
75/// Adds `$n` to counter field `$field` when the `counters` feature is on;
76/// expands to nothing otherwise (zero cost, and the counter expression is
77/// not even evaluated).
78macro_rules! bump {
79    ($self:ident, $field:ident, $n:expr) => {
80        #[cfg(feature = "counters")]
81        {
82            let mut c = $self.counters.get();
83            c.$field += $n as u64;
84            $self.counters.set(c);
85        }
86    };
87}
88
89/// How keys are mapped to shards.
90#[derive(Clone, Copy, Debug, PartialEq, Eq)]
91#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
92pub enum ShardMode {
93    /// Shard = Fibonacci hash of the key's leading bytes. Spreads any key
94    /// distribution (including sequential ids) evenly across shards. Global
95    /// iteration order is *not* the key order. Use for lookup tables.
96    Uniform,
97    /// Shard = top bits of the key's leading bytes. Shard index order equals
98    /// key order, so [`Arena::iter`] yields globally ascending keys and
99    /// [`Arena::range`] scans work. Keys arriving in a narrow value range
100    /// will concentrate in few shards — their chains simply grow longer;
101    /// that is the trade-off for ordering.
102    Ordered,
103}
104
105/// Arena configuration.
106///
107/// Stored verbatim in snapshots later, so everything that
108/// affects byte interpretation lives here.
109#[derive(Clone, Copy, Debug, PartialEq, Eq)]
110#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
111pub struct ArenaCfg {
112    /// Number of shards; must be a non-zero power of two.
113    pub shards: usize,
114    /// Hard ceiling for the byte pool; exceeding it fails with
115    /// [`Error::CapacityExceeded`] instead of growing.
116    pub max_bytes: usize,
117    /// Key-to-shard mapping mode.
118    pub mode: ShardMode,
119}
120
121impl ArenaCfg {
122    /// Creates a config with the given shard count and mode, and no byte
123    /// limit (`max_bytes = usize::MAX`).
124    pub const fn new(shards: usize, mode: ShardMode) -> Self {
125        Self {
126            shards,
127            max_bytes: usize::MAX,
128            mode,
129        }
130    }
131
132    /// Returns the config with `max_bytes` replaced.
133    pub const fn with_max_bytes(mut self, max_bytes: usize) -> Self {
134        self.max_bytes = max_bytes;
135        self
136    }
137}
138
139impl Default for ArenaCfg {
140    /// 1024 shards, [`ShardMode::Uniform`], unlimited bytes.
141    fn default() -> Self {
142        Self::new(1024, ShardMode::Uniform)
143    }
144}
145
146/// Deterministic work counters (feature `counters`).
147///
148/// These are the basis of the project's CI performance gates: unlike
149/// wall-clock time they are identical on every machine, so a complexity
150/// regression fails the same way everywhere.
151#[cfg(feature = "counters")]
152#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
153pub struct Counters {
154    /// Key comparisons performed by binary searches.
155    pub cmp_ops: u64,
156    /// Bytes moved by insert/remove shifts and page splits.
157    pub bytes_shifted: u64,
158    /// Pages taken from the pool or the free-list.
159    pub pages_allocated: u64,
160    /// Steps taken walking page chains (each step peeks one page's first
161    /// key — one cache line).
162    pub chain_steps: u64,
163    /// Page splits performed by inserts into full pages.
164    pub splits: u64,
165}
166
167/// A sharded collection of fixed-size records in one contiguous byte pool,
168/// sorted by key prefix within each shard.
169///
170/// See the [crate-level documentation](crate) for the philosophy and the
171/// module documentation for the chain/split mechanics. Highlights:
172///
173/// - all state is `pool` + small metadata arrays — persisting the arena is a
174///   `memcpy` of defined bytes;
175/// - every operation touches O(1) pages: a short chain walk (one first-key
176///   peek per step), then binary search and shifts inside one 4 KiB page;
177/// - capacity is bounded only by [`ArenaCfg::max_bytes`] — full pages split,
178///   emptied pages are recycled through a free-list;
179/// - `Arena` deliberately implements neither `Clone` nor `PartialEq`: pages
180///   are allocated **uninitialized** (the measured wasm optimization), and a
181///   byte-wise clone/compare would read the uninitialized tails.
182pub struct Arena<'a, T: Slot> {
183    /// Page pool; length is always a multiple of [`PAGE_BYTES`]. Bytes of a
184    /// page beyond `counts[page] * T::SIZE` are uninitialized and must
185    /// never be read.
186    ///
187    /// A [`Paged`] backing so the arena can either own the pool (`new`/`load` —
188    /// the writable default, and the only shape on wasm32) or borrow a base
189    /// from a longer-lived buffer such as a memory-mapped snapshot
190    /// (`load_borrowed`/`load_overlay` —). The overlay open borrows
191    /// the base read-only and copies just the touched pages up on first write
192    /// (per-page copy-on-write), so a mapped database is mutated without
193    /// cloning it. The backing is `alloc`-only (no `std`/`Arc`), so the crate
194    /// stays `no_std`, and the lifetime `'a` lets the compiler prove the arena
195    /// never outlives its base. The uninitialized-tail invariant is untouched:
196    /// base pages are a fully-initialized dumped image, overlay copies are
197    /// taken from them, and freshly grown pages keep the same
198    /// read-only-up-to-`count` contract as before.
199    pool: Paged<'a, PAGE_BYTES>,
200    /// Shard -> first page of its chain (`NONE` = empty shard).
201    heads: Vec<u32>,
202    /// Page -> successor: the next page in a shard chain, or the next free
203    /// page when the page sits in the free-list.
204    next: Vec<u32>,
205    /// Page -> predecessor in a shard chain. Rebuilt from `next` on load and
206    /// kept in memory so ordered arenas can scan a range newest-first without
207    /// changing the on-disk metadata format.
208    prev: Vec<u32>,
209    /// Page -> number of occupied slots.
210    counts: Vec<u16>,
211    /// Shard -> last page of its chain (`NONE` when empty). Runtime-only
212    /// metadata paired with `heads` for reverse ordered scans.
213    tails: Vec<u32>,
214    /// Head of the free-list of recycled pages (linked through `next`).
215    free_head: u32,
216    /// Total records across all shards.
217    total: usize,
218    /// Reusable serialization buffer for [`Arena::insert`] (`T::SIZE` bytes,
219    /// allocated once) — inserts do not allocate after the first call except
220    /// when growing the pool itself.
221    scratch: Vec<u8>,
222    /// Reusable buffer for the cross-page slot moves in [`Arena::split`]. A
223    /// split reads a run of slots out of one page and writes it into another;
224    /// under the overlay backing those may be distinct owned pages, so the
225    /// bytes route through this buffer instead of a single in-pool
226    /// `copy_within`. Grown once; splits are amortized and off the recall path.
227    split_buf: Vec<u8>,
228    cfg: ArenaCfg,
229    #[cfg(feature = "counters")]
230    counters: Cell<Counters>,
231    /// `fn() -> T` keeps the arena `Send`/`Sync`-neutral and avoids bounding
232    /// auto-traits on `T` itself.
233    _marker: PhantomData<fn() -> T>,
234}
235
236/// Where a key's record lives (or would live).
237struct Target {
238    /// Page preceding `page` in its chain, `NONE` when `page` is the head.
239    prev: u32,
240    /// The chain page whose key range covers the key.
241    page: u32,
242}
243
244impl<'a, T: Slot> Arena<'a, T> {
245    /// Creates an empty arena.
246    ///
247    /// # Errors
248    ///
249    /// - [`Error::BadSlot`] unless `1 <= T::KEY_LEN <= T::SIZE <= PAGE_BYTES`;
250    /// - [`Error::BadShardCount`] unless `cfg.shards` is a non-zero power of
251    ///   two.
252    pub fn new(cfg: ArenaCfg) -> Result<Self, Error> {
253        if T::SIZE == 0 || T::SIZE > PAGE_BYTES || T::KEY_LEN == 0 || T::KEY_LEN > T::SIZE {
254            return Err(Error::BadSlot {
255                size: T::SIZE,
256                key_len: T::KEY_LEN,
257            });
258        }
259        if cfg.shards == 0 || !cfg.shards.is_power_of_two() {
260            return Err(Error::BadShardCount { got: cfg.shards });
261        }
262        Ok(Self {
263            pool: Paged::owned_empty(),
264            heads: alloc::vec![NONE; cfg.shards],
265            next: Vec::new(),
266            prev: Vec::new(),
267            counts: Vec::new(),
268            tails: alloc::vec![NONE; cfg.shards],
269            free_head: NONE,
270            total: 0,
271            scratch: Vec::new(),
272            split_buf: Vec::new(),
273            cfg,
274            #[cfg(feature = "counters")]
275            counters: Cell::new(Counters::default()),
276            _marker: PhantomData,
277        })
278    }
279
280    /// Number of slots one page holds for this slot size (at least 1).
281    pub const fn slots_per_page() -> usize {
282        PAGE_BYTES / T::SIZE
283    }
284
285    /// Total number of records.
286    pub fn len(&self) -> usize {
287        self.total
288    }
289
290    /// `true` when the arena holds no records.
291    pub fn is_empty(&self) -> bool {
292        self.total == 0
293    }
294
295    /// Bytes currently allocated in the page pool (including recycled
296    /// free-list pages — the pool never shrinks; emptied pages are reused).
297    pub fn pool_bytes(&self) -> usize {
298        self.pool.len()
299    }
300
301    /// The configuration this arena was created with.
302    pub fn cfg(&self) -> &ArenaCfg {
303        &self.cfg
304    }
305
306    /// Inserts a record, keeping its shard's chain sorted by key prefix.
307    ///
308    /// Returns `Ok(false)` if a record with the same key prefix already
309    /// exists (the arena is a map keyed by prefix; existing payload is left
310    /// untouched — use [`Arena::payload_mut`] to update in place). A full
311    /// target page splits in half — inserts never fail on page capacity.
312    ///
313    /// # Errors
314    ///
315    /// [`Error::CapacityExceeded`] if a needed page allocation would cross
316    /// [`ArenaCfg::max_bytes`].
317    pub fn insert(&mut self, value: &T) -> Result<bool, Error> {
318        // Take the scratch buffer out of `self` to sidestep aliasing between
319        // `&self.scratch` and `&mut self.pool` below.
320        let mut buf = core::mem::take(&mut self.scratch);
321        buf.resize(T::SIZE, 0);
322        value.write(&mut buf);
323        let result = self.insert_bytes(&buf);
324        self.scratch = buf;
325        result
326    }
327
328    /// Insert path operating on the serialized slot.
329    fn insert_bytes(&mut self, slot: &[u8]) -> Result<bool, Error> {
330        let key = &slot[..T::KEY_LEN];
331        let shard = self.shard_of(key);
332
333        let target = match self.find_page(shard, key) {
334            Some(t) => t,
335            None => {
336                // Empty shard: allocate its first chain page.
337                let page = self.alloc_page()?;
338                self.heads[shard] = page;
339                self.tails[shard] = page;
340                Target { prev: NONE, page }
341            }
342        };
343
344        let mut page = target.page;
345        let mut count = self.counts[page as usize] as usize;
346        let mut pos = {
347            let mut cmps = 0u64;
348            let found = self.search_in(page, count, key, &mut cmps);
349            bump!(self, cmp_ops, cmps);
350            match found {
351                Ok(_) => return Ok(false),
352                Err(pos) => pos,
353            }
354        };
355
356        if count == Self::slots_per_page() {
357            // Split the full page; afterwards (page, pos, count) address the
358            // half that must receive the new record.
359            (page, pos, count) = self.split(page, pos)?;
360        }
361
362        let slot_start = pos * T::SIZE;
363        let used_end = count * T::SIZE;
364        let shifted = used_end - slot_start;
365        let bytes = self.pool.page_mut(page);
366        if shifted > 0 {
367            // Shift the sorted tail right by one slot; stays within the page.
368            bytes.copy_within(slot_start..used_end, slot_start + T::SIZE);
369        }
370        bytes[slot_start..slot_start + T::SIZE].copy_from_slice(slot);
371        bump!(self, bytes_shifted, shifted);
372
373        self.counts[page as usize] += 1;
374        self.total += 1;
375        Ok(true)
376    }
377
378    /// Splits full `page`, given the insert position `pos` inside it.
379    /// Returns `(target_page, target_pos, target_count)` for the record that
380    /// triggered the split.
381    fn split(&mut self, page: u32, pos: usize) -> Result<(u32, usize, usize), Error> {
382        let spp = Self::slots_per_page();
383        let fresh = self.alloc_page()?;
384        bump!(self, splits, 1);
385
386        // Link the fresh page right after the split one.
387        let successor = self.next[page as usize];
388        self.next[fresh as usize] = successor;
389        self.prev[fresh as usize] = page;
390        self.next[page as usize] = fresh;
391        if successor == NONE {
392            let shard = self.shard_of(self.first_key(page));
393            self.tails[shard] = fresh;
394        } else {
395            self.prev[successor as usize] = fresh;
396        }
397
398        if spp == 1 {
399            // Degenerate single-slot pages (T::SIZE > PAGE_BYTES / 2): the
400            // "upper half" is the whole record when the new key precedes it,
401            // otherwise the fresh page simply receives the new record.
402            return Ok(if pos == 0 {
403                self.move_slots(page, 0, fresh, T::SIZE);
404                bump!(self, bytes_shifted, T::SIZE);
405                self.counts[page as usize] = 0;
406                self.counts[fresh as usize] = 1;
407                (page, 0, 0)
408            } else {
409                (fresh, 0, 0)
410            });
411        }
412
413        // Move the upper half [half..spp) into the fresh page.
414        let half = spp / 2;
415        let moved = spp - half;
416        self.move_slots(page, half * T::SIZE, fresh, moved * T::SIZE);
417        bump!(self, bytes_shifted, moved * T::SIZE);
418        self.counts[page as usize] = half as u16;
419        self.counts[fresh as usize] = moved as u16;
420
421        // `pos` was computed against the full page; place the new record in
422        // whichever half now owns that position (pos == half belongs at the
423        // end of the lower page: the key sorts before the fresh page's first).
424        Ok(if pos <= half {
425            (page, pos, half)
426        } else {
427            (fresh, pos - half, moved)
428        })
429    }
430
431    /// Copies `len` bytes from `src_page` (starting at in-page offset
432    /// `src_off`) to the start of `dst_page`. The two pages may be distinct
433    /// owned pages under the overlay backing, so the bytes route through the
434    /// reusable [`Arena::split_buf`] rather than a single in-pool
435    /// `copy_within` — one immutable read of the source, then one write of the
436    /// destination (which copies-up if it is still a borrowed base page).
437    fn move_slots(&mut self, src_page: u32, src_off: usize, dst_page: u32, len: usize) {
438        let mut buf = core::mem::take(&mut self.split_buf);
439        buf.clear();
440        buf.extend_from_slice(&self.pool.page(src_page)[src_off..src_off + len]);
441        self.pool.page_mut(dst_page)[..len].copy_from_slice(&buf);
442        self.split_buf = buf;
443    }
444
445    /// Returns the record with the given key prefix, if present.
446    ///
447    /// # Panics
448    ///
449    /// Panics if `key.len() != T::KEY_LEN` (a caller bug, not a data
450    /// condition — mirrors slice indexing).
451    pub fn get(&self, key: &[u8]) -> Option<T> {
452        self.locate(key).map(|off| {
453            let (page, rel) = (off / PAGE_BYTES, off % PAGE_BYTES);
454            T::read(&self.pool.page(page as u32)[rel..rel + T::SIZE])
455        })
456    }
457
458    /// `true` if a record with the given key prefix exists.
459    ///
460    /// # Panics
461    ///
462    /// Panics if `key.len() != T::KEY_LEN`.
463    pub fn contains(&self, key: &[u8]) -> bool {
464        self.locate(key).is_some()
465    }
466
467    /// Returns the raw bytes of the record with the given key prefix.
468    ///
469    /// # Panics
470    ///
471    /// Panics if `key.len() != T::KEY_LEN`.
472    pub fn get_slot(&self, key: &[u8]) -> Option<&[u8]> {
473        self.locate(key).map(|off| {
474            let (page, rel) = (off / PAGE_BYTES, off % PAGE_BYTES);
475            &self.pool.page(page as u32)[rel..rel + T::SIZE]
476        })
477    }
478
479    /// Returns a mutable view of the record's **payload** (the bytes after
480    /// the key prefix), for in-place updates.
481    ///
482    /// The key prefix itself is not reachable through this method — sorted
483    /// order cannot be corrupted by construction, which is why this is safe
484    /// to expose at all.
485    ///
486    /// # Panics
487    ///
488    /// Panics if `key.len() != T::KEY_LEN`.
489    pub fn payload_mut(&mut self, key: &[u8]) -> Option<&mut [u8]> {
490        let off = self.locate(key)?;
491        let (page, rel) = (off / PAGE_BYTES, off % PAGE_BYTES);
492        Some(&mut self.pool.page_mut(page as u32)[rel + T::KEY_LEN..rel + T::SIZE])
493    }
494
495    /// Removes the record with the given key prefix. Returns `true` if it
496    /// existed. A page emptied by the removal is unlinked from its chain and
497    /// recycled through the free-list.
498    ///
499    /// # Panics
500    ///
501    /// Panics if `key.len() != T::KEY_LEN`.
502    pub fn remove(&mut self, key: &[u8]) -> bool {
503        assert_eq!(key.len(), T::KEY_LEN, "key length must equal Slot::KEY_LEN");
504        let shard = self.shard_of(key);
505        let Some(Target { prev, page }) = self.find_page(shard, key) else {
506            return false;
507        };
508        let count = self.counts[page as usize] as usize;
509        let pos = {
510            let mut cmps = 0u64;
511            let found = self.search_in(page, count, key, &mut cmps);
512            bump!(self, cmp_ops, cmps);
513            match found {
514                Ok(pos) => pos,
515                Err(_) => return false,
516            }
517        };
518
519        let slot_start = pos * T::SIZE;
520        let used_end = count * T::SIZE;
521        let tail = used_end - (slot_start + T::SIZE);
522        if tail > 0 {
523            // Shift the tail left over the removed slot; stays within the page.
524            self.pool
525                .page_mut(page)
526                .copy_within(slot_start + T::SIZE..used_end, slot_start);
527        }
528        bump!(self, bytes_shifted, tail);
529        self.counts[page as usize] -= 1;
530        self.total -= 1;
531
532        if self.counts[page as usize] == 0 {
533            // Unlink the emptied page and recycle it.
534            let successor = self.next[page as usize];
535            if prev == NONE {
536                self.heads[shard] = successor;
537            } else {
538                self.next[prev as usize] = successor;
539            }
540            if successor == NONE {
541                self.tails[shard] = prev;
542            } else {
543                self.prev[successor as usize] = prev;
544            }
545            self.next[page as usize] = self.free_head;
546            self.prev[page as usize] = NONE;
547            self.free_head = page;
548        }
549        true
550    }
551
552    /// Iterates all records shard by shard, following each shard's chain.
553    ///
554    /// In [`ShardMode::Ordered`] the shard index order equals key order and
555    /// chains are range-partitioned, so this yields **globally ascending
556    /// keys**. In [`ShardMode::Uniform`] the global order is unspecified
557    /// (each shard is still internally sorted).
558    pub fn iter(&self) -> Iter<'_, T> {
559        Iter {
560            arena: self,
561            shard: 0,
562            page: NONE,
563            idx: 0,
564            remaining: self.total,
565        }
566    }
567
568    /// Iterates records whose key prefix lies in `[from, to)` — `from`
569    /// inclusive, `to` exclusive — in ascending key order.
570    ///
571    /// Only meaningful when shard order equals key order, hence restricted
572    /// to [`ShardMode::Ordered`].
573    ///
574    /// # Panics
575    ///
576    /// Panics if the arena is in [`ShardMode::Uniform`], or if either bound's
577    /// length differs from `T::KEY_LEN`.
578    pub fn range<'s>(&'s self, from: &[u8], to: &'s [u8]) -> Range<'s, T> {
579        assert_eq!(
580            self.cfg.mode,
581            ShardMode::Ordered,
582            "range scans require ShardMode::Ordered"
583        );
584        assert_eq!(
585            from.len(),
586            T::KEY_LEN,
587            "key length must equal Slot::KEY_LEN"
588        );
589        assert_eq!(to.len(), T::KEY_LEN, "key length must equal Slot::KEY_LEN");
590
591        // Position on the first record with key >= `from`.
592        let shard = self.shard_of(from);
593        let mut page = NONE;
594        let mut idx = 0usize;
595        if let Some(t) = self.find_page(shard, from) {
596            let count = self.counts[t.page as usize] as usize;
597            let mut cmps = 0u64;
598            let pos = match self.search_in(t.page, count, from, &mut cmps) {
599                Ok(p) | Err(p) => p,
600            };
601            bump!(self, cmp_ops, cmps);
602            if pos < count {
603                page = t.page;
604                idx = pos;
605            } else {
606                // Past the last record of the covering page: continue with
607                // the next page in this chain, or fall through to the next
608                // shard.
609                page = self.next[t.page as usize];
610            }
611        }
612        Range {
613            arena: self,
614            // The shard the iterator moves to once the current chain (if
615            // any) is exhausted.
616            shard: shard + 1,
617            page,
618            idx,
619            to,
620        }
621    }
622
623    /// Iterates records whose key prefix lies in `[from, to)` — `from`
624    /// inclusive, `to` exclusive — in descending key order.
625    ///
626    /// This is the bounded newest-first counterpart to [`Arena::range`]. It
627    /// is restricted to ordered arenas and keeps the predecessor links in
628    /// runtime metadata, so the on-disk arena format remains unchanged.
629    pub fn range_rev<'s>(&'s self, from: &'s [u8], to: &'s [u8]) -> RangeRev<'s, T> {
630        assert_eq!(
631            self.cfg.mode,
632            ShardMode::Ordered,
633            "reverse range scans require ShardMode::Ordered"
634        );
635        assert_eq!(
636            from.len(),
637            T::KEY_LEN,
638            "key length must equal Slot::KEY_LEN"
639        );
640        assert_eq!(to.len(), T::KEY_LEN, "key length must equal Slot::KEY_LEN");
641
642        if from >= to {
643            return RangeRev {
644                arena: self,
645                shard: 0,
646                page: NONE,
647                idx: 0,
648                from,
649            };
650        }
651
652        // Position immediately before `to`: an exact key is excluded, while
653        // an insertion point after the page's last key starts at that last
654        // key. If the covering page has no key below `to`, walk to its
655        // predecessor.
656        let shard = self.shard_of(to);
657        let mut page = NONE;
658        let mut idx = 0usize;
659        if let Some(t) = self.find_page(shard, to) {
660            let count = self.counts[t.page as usize] as usize;
661            let mut cmps = 0u64;
662            let pos = match self.search_in(t.page, count, to, &mut cmps) {
663                Ok(p) | Err(p) => p,
664            };
665            bump!(self, cmp_ops, cmps);
666            if pos > 0 {
667                page = t.page;
668                idx = pos;
669            } else {
670                page = self.prev[t.page as usize];
671                if page != NONE {
672                    idx = self.counts[page as usize] as usize;
673                }
674            }
675        }
676
677        RangeRev {
678            arena: self,
679            // The shard the iterator moves to once the current chain (if
680            // any) is exhausted.
681            shard,
682            page,
683            idx,
684            from,
685        }
686    }
687
688    /// A snapshot of the work counters (feature `counters`).
689    #[cfg(feature = "counters")]
690    pub fn counters(&self) -> Counters {
691        self.counters.get()
692    }
693
694    /// Resets all work counters to zero (feature `counters`).
695    #[cfg(feature = "counters")]
696    pub fn reset_counters(&self) {
697        self.counters.set(Counters::default());
698    }
699
700    /// Appends the arena's metadata section to `out`.
701    ///
702    /// Layout (all little-endian): `[shards u32][pages u32][free_head u32]
703    /// [total u64][mode u8][reserved 3]`, then `heads` (`shards × u32`),
704    /// `next` (`pages × u32`), `counts` (`pages × u16`). Together with
705    /// [`Arena::dump_pool`] this is the complete state; both dumps are
706    /// canonical — dump → [`Arena::load`] → dump reproduces identical
707    /// bytes.
708    pub fn dump_meta(&self, out: &mut Vec<u8>) {
709        let pages = self.pool.len() / PAGE_BYTES;
710        debug_assert!(self.cfg.shards <= u32::MAX as usize && pages < u32::MAX as usize);
711        out.reserve(IMAGE_HEADER + self.heads.len() * PAGE_IDX_BYTES + pages * PAGE_META_BYTES);
712        out.extend_from_slice(&(self.cfg.shards as u32).to_le_bytes());
713        out.extend_from_slice(&(pages as u32).to_le_bytes());
714        out.extend_from_slice(&self.free_head.to_le_bytes());
715        out.extend_from_slice(&(self.total as u64).to_le_bytes());
716        out.push(match self.cfg.mode {
717            ShardMode::Uniform => 0,
718            ShardMode::Ordered => 1,
719        });
720        out.extend_from_slice(&[0u8; 3]);
721        for &head in &self.heads {
722            out.extend_from_slice(&head.to_le_bytes());
723        }
724        for &next in &self.next {
725            out.extend_from_slice(&next.to_le_bytes());
726        }
727        for &count in &self.counts {
728            out.extend_from_slice(&count.to_le_bytes());
729        }
730    }
731
732    /// Appends the arena's pool section to `out`.
733    ///
734    /// Each page contributes its initialized prefix (`counts[page] ×
735    /// SIZE` bytes) followed by zero padding to [`PAGE_BYTES`]: the
736    /// uninitialized page tails (see [`Arena::insert`] internals) are
737    /// never read, and zero-filling them makes the image canonical and
738    /// leak-free.
739    pub fn dump_pool(&self, out: &mut Vec<u8>) {
740        out.reserve(self.pool.len());
741        for (page, &count) in self.counts.iter().enumerate() {
742            let used = count as usize * T::SIZE;
743            out.extend_from_slice(&self.pool.page(page as u32)[..used]);
744            out.resize(out.len() + (PAGE_BYTES - used), 0);
745        }
746    }
747
748    /// Rebuilds an arena from its two dumped sections.
749    ///
750    /// The input is **untrusted**: every metadata invariant is checked
751    /// before adoption and any inconsistency returns
752    /// [`Error::Corrupt`] — this method never panics on arbitrary bytes.
753    /// Cost is O(pages) on top of the pool copy:
754    ///
755    /// - exact section lengths; `cfg` agreement (shards, mode);
756    /// - per-page slot counts within `PAGE_BYTES / SIZE`;
757    /// - every page reachable exactly once — shard chains and the
758    ///   free-list are walked with a visited bitmap (no cycles, no shared
759    ///   or orphan pages); chain pages are non-empty, free pages empty;
760    /// - chain pages ascend by key range and sit in the shard their first
761    ///   key maps to; the record total matches the page counts.
762    ///
763    /// Slot *content* is not validated beyond the per-page first/last key
764    /// reads — record payloads are semantically validated lazily by the
765    /// owning engine.
766    ///
767    /// # Errors
768    ///
769    /// [`Error::BadSlot`] / [`Error::BadShardCount`] for an invalid `cfg`
770    /// (same gates as [`Arena::new`]); [`Error::Corrupt`] for any image
771    /// inconsistency.
772    pub fn load(cfg: ArenaCfg, meta: &[u8], pool: &[u8]) -> Result<Self, Error> {
773        Self::load_impl(cfg, meta, pool, Paged::owned_from(pool.to_vec()))
774    }
775
776    /// Rebuilds an arena that **borrows** its page pool from a longer-lived
777    /// buffer (a memory-mapped snapshot) instead of copying it.
778    /// Validation is identical to [`Arena::load`] — it reads each page's
779    /// first and last keys, so it touches one OS page per arena page (the
780    /// key-order check needs them), but no pool byte is copied.
781    ///
782    /// # Errors
783    ///
784    /// Same as [`Arena::load`].
785    pub fn load_borrowed(cfg: ArenaCfg, meta: &[u8], pool: &'a [u8]) -> Result<Self, Error> {
786        Self::load_impl(cfg, meta, pool, Paged::borrowed(pool))
787    }
788
789    /// Opens an arena over a borrowed base for the **overlay** write path: the
790    /// base pages are mapped read-only, and the first write to any base page
791    /// copies just that page into owned storage (per-page copy-on-write, see
792    /// [`Paged`](crate::paged)), while pages grown after open live in an owned
793    /// tail. Unlike [`Arena::load_borrowed`] the returned arena is fully
794    /// mutable — inserts, removals and splits work — yet the borrowed base is
795    /// never cloned as a whole and never mutated, so a memory-mapped database
796    /// can be written to while resident only in the pages it actually touches.
797    /// Validation is identical to [`Arena::load`].
798    ///
799    /// # Errors
800    ///
801    /// Same as [`Arena::load`].
802    pub fn load_overlay(cfg: ArenaCfg, meta: &[u8], pool: &'a [u8]) -> Result<Self, Error> {
803        Self::load_impl(cfg, meta, pool, Paged::borrowed(pool))
804    }
805
806    /// Shared body of [`Arena::load`] / [`Arena::load_borrowed`] /
807    /// [`Arena::load_overlay`]: validates the untrusted `meta`/`pool` image and
808    /// adopts `backing` as the pool.
809    fn load_impl(
810        cfg: ArenaCfg,
811        meta: &[u8],
812        pool: &[u8],
813        backing: Paged<'a, PAGE_BYTES>,
814    ) -> Result<Self, Error> {
815        let mut arena = Self::new(cfg)?;
816        if meta.len() < IMAGE_HEADER {
817            return Err(Error::Corrupt("arena meta shorter than its header"));
818        }
819        let shards = u32::from_le_bytes(meta[0..4].try_into().unwrap()) as usize;
820        let pages = u32::from_le_bytes(meta[4..8].try_into().unwrap()) as usize;
821        let free_head = u32::from_le_bytes(meta[8..12].try_into().unwrap());
822        let total = u64::from_le_bytes(meta[12..20].try_into().unwrap());
823        let mode = meta[20];
824        if meta[21..24] != [0u8; 3] {
825            return Err(Error::Corrupt("arena meta reserved bytes must be zero"));
826        }
827        if shards != cfg.shards {
828            return Err(Error::Corrupt("arena meta shard count disagrees with cfg"));
829        }
830        let want_mode = match cfg.mode {
831            ShardMode::Uniform => 0u8,
832            ShardMode::Ordered => 1,
833        };
834        if mode != want_mode {
835            return Err(Error::Corrupt("arena meta shard mode disagrees with cfg"));
836        }
837        if pages as u64 >= u64::from(NONE) {
838            return Err(Error::Corrupt("arena page count overflows the index space"));
839        }
840        let want_meta = IMAGE_HEADER as u64
841            + shards as u64 * PAGE_IDX_BYTES as u64
842            + pages as u64 * PAGE_META_BYTES as u64;
843        if meta.len() as u64 != want_meta {
844            return Err(Error::Corrupt("arena meta length mismatch"));
845        }
846        if pool.len() as u64 != pages as u64 * PAGE_BYTES as u64 {
847            return Err(Error::Corrupt("arena pool length mismatch"));
848        }
849
850        let mut heads = Vec::with_capacity(shards);
851        for i in 0..shards {
852            let at = IMAGE_HEADER + i * PAGE_IDX_BYTES;
853            heads.push(u32::from_le_bytes(
854                meta[at..at + PAGE_IDX_BYTES].try_into().unwrap(),
855            ));
856        }
857        let next_base = IMAGE_HEADER + shards * PAGE_IDX_BYTES;
858        let mut next = Vec::with_capacity(pages);
859        for i in 0..pages {
860            let at = next_base + i * PAGE_IDX_BYTES;
861            next.push(u32::from_le_bytes(
862                meta[at..at + PAGE_IDX_BYTES].try_into().unwrap(),
863            ));
864        }
865        let counts_base = next_base + pages * PAGE_IDX_BYTES;
866        let mut counts = Vec::with_capacity(pages);
867        for i in 0..pages {
868            let at = counts_base + i * COUNT_BYTES;
869            counts.push(u16::from_le_bytes(
870                meta[at..at + COUNT_BYTES].try_into().unwrap(),
871            ));
872        }
873        let mut prev_links = alloc::vec![NONE; pages];
874        let mut tails = alloc::vec![NONE; shards];
875
876        let spp = Self::slots_per_page();
877        if counts.iter().any(|&c| c as usize > spp) {
878            return Err(Error::Corrupt("arena page count exceeds slots per page"));
879        }
880
881        // Reachability walk: every page must appear exactly once across all
882        // shard chains and the free-list; the bitmap catches cycles, shared
883        // pages and (after the walks) orphans.
884        let mut seen = alloc::vec![false; pages];
885        let mut live = 0u64;
886        for (shard, &head) in heads.iter().enumerate() {
887            let mut page = head;
888            let mut predecessor = NONE;
889            while page != NONE {
890                let p = page as usize;
891                if p >= pages {
892                    return Err(Error::Corrupt("arena chain page out of bounds"));
893                }
894                if core::mem::replace(&mut seen[p], true) {
895                    return Err(Error::Corrupt("arena page linked more than once"));
896                }
897                let count = counts[p] as usize;
898                if count == 0 {
899                    return Err(Error::Corrupt("arena chain contains an empty page"));
900                }
901                live += count as u64;
902                let first = &pool[p * PAGE_BYTES..p * PAGE_BYTES + T::KEY_LEN];
903                if arena.shard_of(first) != shard {
904                    return Err(Error::Corrupt("arena page sits in the wrong shard"));
905                }
906                if predecessor != NONE {
907                    let pr = predecessor as usize;
908                    let last_at = pr * PAGE_BYTES + (counts[pr] as usize - 1) * T::SIZE;
909                    if pool[last_at..last_at + T::KEY_LEN] >= *first {
910                        return Err(Error::Corrupt("arena chain pages out of key order"));
911                    }
912                }
913                prev_links[p] = predecessor;
914                if next[p] == NONE {
915                    tails[shard] = page;
916                }
917                predecessor = page;
918                page = next[p];
919            }
920        }
921        let mut page = free_head;
922        while page != NONE {
923            let p = page as usize;
924            if p >= pages {
925                return Err(Error::Corrupt("arena free page out of bounds"));
926            }
927            if core::mem::replace(&mut seen[p], true) {
928                return Err(Error::Corrupt("arena page linked more than once"));
929            }
930            if counts[p] != 0 {
931                return Err(Error::Corrupt("arena free page has a nonzero count"));
932            }
933            page = next[p];
934        }
935        if seen.iter().any(|&s| !s) {
936            return Err(Error::Corrupt("arena has an orphan page"));
937        }
938        if live != total {
939            return Err(Error::Corrupt(
940                "arena record total disagrees with page counts",
941            ));
942        }
943
944        arena.pool = backing;
945        arena.heads = heads;
946        arena.next = next;
947        arena.prev = prev_links;
948        arena.counts = counts;
949        arena.tails = tails;
950        arena.free_head = free_head;
951        arena.total = total as usize;
952        Ok(arena)
953    }
954
955    /// Walks the shard's chain to the page whose key range covers `key`.
956    /// `None` when the shard is empty.
957    fn find_page(&self, shard: usize, key: &[u8]) -> Option<Target> {
958        let head = self.heads[shard];
959        if head == NONE {
960            return None;
961        }
962        let mut prev = NONE;
963        let mut page = head;
964        let mut steps = 0u64;
965        loop {
966            let nxt = self.next[page as usize];
967            // Advance while the next page's range can still contain the key
968            // (its first key is <= key). Peeking a first key touches one
969            // cache line.
970            if nxt == NONE || self.first_key(nxt) > key {
971                break;
972            }
973            steps += COUNT as u64;
974            prev = page;
975            page = nxt;
976        }
977        bump!(self, chain_steps, steps);
978        let _ = steps; // read only by the counters feature
979        Some(Target { prev, page })
980    }
981
982    /// First key of a page. Caller guarantees the page is non-empty (every
983    /// page in a chain holds at least one record — emptied pages are
984    /// unlinked immediately).
985    fn first_key(&self, page: u32) -> &[u8] {
986        &self.pool.page(page)[..T::KEY_LEN]
987    }
988
989    /// Binary search inside a page; wraps the free-function search with the
990    /// page slice resolution.
991    fn search_in(
992        &self,
993        page: u32,
994        count: usize,
995        key: &[u8],
996        cmps: &mut u64,
997    ) -> Result<usize, usize> {
998        search::<T>(self.pool.page(page), count, key, cmps)
999    }
1000
1001    /// Byte offset of the slot with the given key, if present.
1002    fn locate(&self, key: &[u8]) -> Option<usize> {
1003        assert_eq!(key.len(), T::KEY_LEN, "key length must equal Slot::KEY_LEN");
1004        let shard = self.shard_of(key);
1005        let Target { page, .. } = self.find_page(shard, key)?;
1006        let count = self.counts[page as usize] as usize;
1007        let mut cmps = 0u64;
1008        let found = self.search_in(page, count, key, &mut cmps).ok();
1009        bump!(self, cmp_ops, cmps);
1010        found.map(|pos| page as usize * PAGE_BYTES + pos * T::SIZE)
1011    }
1012
1013    /// Maps a key to its shard. Only the first 8 key bytes participate;
1014    /// longer keys sharing an 8-byte prefix land in the same shard (harmless
1015    /// for `Ordered`: order across shards is still by prefix).
1016    fn shard_of(&self, key: &[u8]) -> usize {
1017        let mut pad = [0u8; 8];
1018        let n = key.len().min(8);
1019        pad[..n].copy_from_slice(&key[..n]);
1020        let v = u64::from_be_bytes(pad);
1021        let bits = self.cfg.shards.trailing_zeros();
1022        if bits == 0 {
1023            return 0; // single shard; also avoids the undefined `v >> 64`
1024        }
1025        let h = match self.cfg.mode {
1026            ShardMode::Ordered => v,
1027            ShardMode::Uniform => v.wrapping_mul(FIB),
1028        };
1029        (h >> (64 - bits)) as usize
1030    }
1031
1032    /// Takes a page from the free-list, or grows the pool by one page.
1033    fn alloc_page(&mut self) -> Result<u32, Error> {
1034        if self.free_head != NONE {
1035            let page = self.free_head;
1036            self.free_head = self.next[page as usize];
1037            self.next[page as usize] = NONE;
1038            self.prev[page as usize] = NONE;
1039            self.counts[page as usize] = 0;
1040            bump!(self, pages_allocated, 1);
1041            return Ok(page);
1042        }
1043
1044        let old_len = self.pool.len();
1045        let new_len = old_len + PAGE_BYTES;
1046        if new_len > self.cfg.max_bytes {
1047            return Err(Error::CapacityExceeded {
1048                max_bytes: self.cfg.max_bytes,
1049            });
1050        }
1051        let page = (old_len / PAGE_BYTES) as u32;
1052        // Grow the owned tail by one page: the whole vector when owned, the
1053        // overlay's grown tail when borrowing (the borrowed base is never
1054        // resized). `grown_tail_mut` hands back that owned `Vec` directly — no
1055        // clone of the base — and the new page's global index is `old_len /
1056        // PAGE_BYTES` regardless of which tail holds it.
1057        let tail = self.pool.grown_tail_mut();
1058        let tail_len = tail.len() + PAGE_BYTES;
1059        tail.reserve(PAGE_BYTES);
1060        // SAFETY: the new page is left uninitialized on purpose — this is the
1061        // one measured unsafe of the crate. Zeroing fresh pages was benched
1062        // at 12x slower on the wasm allocation path (wasmtime, 32k pages:
1063        // 3889 us zeroed vs 316 us uninit; native: noise) — and wasm is this
1064        // structure's primary environment. The invariant making it sound:
1065        // *no byte of a page beyond `counts[page] * T::SIZE` is ever read*.
1066        // Every read (search, get, iter, range, first_key, shifts) is
1067        // bounded by the page count, and a slot's bytes are fully written
1068        // before the count is incremented. Consequently `Arena` exposes no
1069        // whole-pool reads (no `Clone`/`PartialEq`/`as_bytes`); the snapshot
1070        // writer emits only the initialized prefixes of pages.
1071        // Routing the same reserve+set_len at the grown tail (not the base)
1072        // keeps this the crate's sole `unsafe` — the overlay adds none.
1073        #[allow(clippy::uninit_vec)]
1074        unsafe {
1075            tail.set_len(tail_len);
1076        }
1077        self.next.push(NONE);
1078        self.prev.push(NONE);
1079        self.counts.push(0);
1080        bump!(self, pages_allocated, 1);
1081        Ok(page)
1082    }
1083}
1084
1085/// Binary search over a page's occupied slots, comparing key prefixes.
1086///
1087/// Free function (not a method) so the borrow of the page slice stays
1088/// independent from `&mut self` at call sites. Counting compiles away when
1089/// the `counters` feature is off (`COUNT` is a const `false`).
1090fn search<T: Slot>(page: &[u8], count: usize, key: &[u8], cmps: &mut u64) -> Result<usize, usize> {
1091    let mut lo = 0usize;
1092    let mut hi = count;
1093    while lo < hi {
1094        let mid = lo + (hi - lo) / 2;
1095        let off = mid * T::SIZE;
1096        // Branchless: adds 0 when the `counters` feature is off, which the
1097        // compiler folds away entirely.
1098        *cmps += COUNT as u64;
1099        match page[off..off + T::KEY_LEN].cmp(key) {
1100            core::cmp::Ordering::Less => lo = mid + 1,
1101            core::cmp::Ordering::Greater => hi = mid,
1102            core::cmp::Ordering::Equal => return Ok(mid),
1103        }
1104    }
1105    Err(lo)
1106}
1107
1108impl<T: Slot> fmt::Debug for Arena<'_, T> {
1109    /// Summary only — dumping the pool would both flood output and read
1110    /// uninitialized page tails.
1111    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1112        f.debug_struct("Arena")
1113            .field("len", &self.total)
1114            .field("shards", &self.cfg.shards)
1115            .field("mode", &self.cfg.mode)
1116            .field("pages", &(self.pool.len() / PAGE_BYTES))
1117            .field("slot_size", &T::SIZE)
1118            .finish()
1119    }
1120}
1121
1122/// Iterator over all records of an [`Arena`]; see [`Arena::iter`] for
1123/// ordering guarantees.
1124pub struct Iter<'a, T: Slot> {
1125    arena: &'a Arena<'a, T>,
1126    shard: usize,
1127    /// Current chain page; `NONE` means "advance to the next shard's head".
1128    page: u32,
1129    idx: usize,
1130    remaining: usize,
1131}
1132
1133impl<T: Slot> Iterator for Iter<'_, T> {
1134    type Item = T;
1135
1136    fn next(&mut self) -> Option<T> {
1137        loop {
1138            if self.page == NONE {
1139                if self.shard >= self.arena.cfg.shards {
1140                    return None;
1141                }
1142                self.page = self.arena.heads[self.shard];
1143                self.idx = 0;
1144                self.shard += 1;
1145                continue;
1146            }
1147            if self.idx < self.arena.counts[self.page as usize] as usize {
1148                let rel = self.idx * T::SIZE;
1149                self.idx += 1;
1150                self.remaining -= 1;
1151                return Some(T::read(
1152                    &self.arena.pool.page(self.page)[rel..rel + T::SIZE],
1153                ));
1154            }
1155            self.page = self.arena.next[self.page as usize];
1156            self.idx = 0;
1157        }
1158    }
1159
1160    fn size_hint(&self) -> (usize, Option<usize>) {
1161        (self.remaining, Some(self.remaining))
1162    }
1163}
1164
1165impl<T: Slot> ExactSizeIterator for Iter<'_, T> {}
1166
1167impl<'a, T: Slot> IntoIterator for &'a Arena<'a, T> {
1168    type Item = T;
1169    type IntoIter = Iter<'a, T>;
1170
1171    fn into_iter(self) -> Self::IntoIter {
1172        self.iter()
1173    }
1174}
1175
1176/// Iterator over a key range of an [`Arena`]; see [`Arena::range`].
1177pub struct Range<'a, T: Slot> {
1178    arena: &'a Arena<'a, T>,
1179    shard: usize,
1180    /// Current chain page; `NONE` means "advance to the next shard's head".
1181    page: u32,
1182    idx: usize,
1183    /// Exclusive upper bound on key prefixes.
1184    to: &'a [u8],
1185}
1186
1187impl<T: Slot> Iterator for Range<'_, T> {
1188    type Item = T;
1189
1190    fn next(&mut self) -> Option<T> {
1191        loop {
1192            if self.page == NONE {
1193                if self.shard >= self.arena.cfg.shards {
1194                    return None;
1195                }
1196                self.page = self.arena.heads[self.shard];
1197                self.idx = 0;
1198                self.shard += 1;
1199                continue;
1200            }
1201            if self.idx < self.arena.counts[self.page as usize] as usize {
1202                let rel = self.idx * T::SIZE;
1203                let page = self.arena.pool.page(self.page);
1204                if page[rel..rel + T::KEY_LEN] >= *self.to {
1205                    // Keys only grow from here on — the scan is complete.
1206                    return None;
1207                }
1208                self.idx += 1;
1209                return Some(T::read(&page[rel..rel + T::SIZE]));
1210            }
1211            self.page = self.arena.next[self.page as usize];
1212            self.idx = 0;
1213        }
1214    }
1215}
1216
1217/// Iterator over a key range of an [`Arena`] in descending order; see
1218/// [`Arena::range_rev`].
1219pub struct RangeRev<'a, T: Slot> {
1220    arena: &'a Arena<'a, T>,
1221    /// The current shard. Lower shards are visited after the current chain.
1222    shard: usize,
1223    /// Current chain page; `NONE` means "advance to the previous shard's
1224    /// tail".
1225    page: u32,
1226    /// Exclusive upper slot index in the current page.
1227    idx: usize,
1228    /// Inclusive lower key bound.
1229    from: &'a [u8],
1230}
1231
1232impl<T: Slot> Iterator for RangeRev<'_, T> {
1233    type Item = T;
1234
1235    fn next(&mut self) -> Option<T> {
1236        loop {
1237            if self.page == NONE {
1238                if self.shard == 0 {
1239                    return None;
1240                }
1241                self.shard -= 1;
1242                self.page = self.arena.tails[self.shard];
1243                self.idx = if self.page == NONE {
1244                    0
1245                } else {
1246                    self.arena.counts[self.page as usize] as usize
1247                };
1248                continue;
1249            }
1250
1251            if self.idx == 0 {
1252                self.page = self.arena.prev[self.page as usize];
1253                self.idx = if self.page == NONE {
1254                    0
1255                } else {
1256                    self.arena.counts[self.page as usize] as usize
1257                };
1258                continue;
1259            }
1260
1261            self.idx -= 1;
1262            let rel = self.idx * T::SIZE;
1263            let page = self.arena.pool.page(self.page);
1264            if page[rel..rel + T::KEY_LEN] < *self.from {
1265                return None;
1266            }
1267            return Some(T::read(&page[rel..rel + T::SIZE]));
1268        }
1269    }
1270}