Skip to main content

arctic/
topology.rs

1//! Pointer-free import and export of Arctic's adaptive radix-tree topology.
2//!
3//! This module deliberately exposes a typed interchange representation rather
4//! than a byte codec. Durable framing, checksums, generations, and write-ahead
5//! logging remain the caller's responsibility.
6
7use core::fmt;
8use core::ptr::NonNull;
9use core::sync::atomic::Ordering;
10
11use ribbit::Unpack as _;
12
13use crate::concurrent;
14use crate::raw::Edge as RawEdge;
15use crate::raw::edge;
16use crate::raw::edge::Meta as _;
17use crate::raw::node;
18use crate::sequential;
19use crate::sync::Atomic;
20
21/// Version of the typed topology interchange contract.
22pub const VERSION: u16 = 1;
23
24/// An exported, pointer-free Arctic topology.
25#[derive(Clone, Debug, PartialEq, Eq)]
26pub struct Topology<V> {
27    /// Interchange contract version. Must equal [`VERSION`].
28    pub version: u16,
29    /// Root edge, or `None` for an empty map.
30    pub root: Option<Edge<V>>,
31}
32
33/// A compressed edge and its child.
34#[derive(Clone, Debug, PartialEq, Eq)]
35pub struct Edge<V> {
36    /// Arctic compressed-edge bits with transient flags cleared.
37    pub metadata: u64,
38    /// Value or adaptive node reached by this edge.
39    pub child: Child<V>,
40}
41
42/// Child reached by a compressed edge.
43#[derive(Clone, Debug, PartialEq, Eq)]
44pub enum Child<V> {
45    /// A caller-encoded value. This is never Arctic's raw in-memory value word.
46    Value(V),
47    /// An adaptive radix-tree node.
48    Node(Node<V>),
49}
50
51/// An Arctic adaptive node and its physical branches.
52#[derive(Clone, Debug, PartialEq, Eq)]
53pub struct Node<V> {
54    /// Exact adaptive node representation.
55    pub kind: NodeKind,
56    /// Number of physical slots initialized in the node header.
57    ///
58    /// This can exceed the number of live branches after removals.
59    pub slot_count: u16,
60    /// Live branches, including their physical edge slots.
61    pub branches: Vec<Branch<V>>,
62}
63
64/// A byte branch stored in a physical node edge slot.
65#[derive(Clone, Debug, PartialEq, Eq)]
66pub struct Branch<V> {
67    /// Radix byte selecting this branch.
68    pub key: u8,
69    /// Physical slot in the node's edge array.
70    pub slot: u16,
71    /// Compressed child edge.
72    pub edge: Edge<V>,
73}
74
75/// Arctic's adaptive node representations.
76#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
77pub enum NodeKind {
78    /// Up to three branches in one cache line.
79    Node3,
80    /// Up to fifteen branches.
81    Node15,
82    /// Up to forty-seven branches.
83    Node47,
84    /// Directly addressed 256-way node.
85    Node256,
86}
87
88impl NodeKind {
89    fn capacity(self) -> usize {
90        match self {
91            Self::Node3 => 3,
92            Self::Node15 => 15,
93            Self::Node47 => 47,
94            Self::Node256 => 256,
95        }
96    }
97
98    fn from_raw(kind: ribbit::Packed<node::Type>) -> Self {
99        match kind.unpack() {
100            node::Type::Node3 => Self::Node3,
101            node::Type::Node15 => Self::Node15,
102            node::Type::Node47 => Self::Node47,
103            node::Type::Node256 => Self::Node256,
104        }
105    }
106
107    fn into_raw(self) -> node::Type {
108        match self {
109            Self::Node3 => node::Type::Node3,
110            Self::Node15 => node::Type::Node15,
111            Self::Node47 => node::Type::Node47,
112            Self::Node256 => node::Type::Node256,
113        }
114    }
115}
116
117/// A malformed or unsupported topology.
118#[derive(Clone, Debug, PartialEq, Eq)]
119pub enum Error {
120    /// The interchange version is not supported by this crate.
121    UnsupportedVersion {
122        /// Version found in the topology.
123        found: u16,
124    },
125    /// Compressed-edge metadata contains invalid or transient bits.
126    InvalidMetadata {
127        /// Rejected metadata word.
128        metadata: u64,
129    },
130    /// A value does not terminate at the unsigned key's exact byte length.
131    InvalidKeyLength {
132        /// Number of key bytes represented by the path.
133        found: usize,
134        /// Required number of key bytes.
135        expected: usize,
136    },
137    /// A node has no live branches.
138    EmptyNode,
139    /// A node contains more branches than its recorded representation permits.
140    NodeCapacity {
141        /// Recorded node representation.
142        kind: NodeKind,
143        /// Number of branches found.
144        found: usize,
145    },
146    /// Two branches in one node use the same radix byte.
147    DuplicateKey {
148        /// Duplicated radix byte.
149        key: u8,
150    },
151    /// A physical edge slot is invalid or duplicated.
152    InvalidSlot {
153        /// Recorded node representation.
154        kind: NodeKind,
155        /// Invalid physical slot.
156        slot: u16,
157    },
158}
159
160impl fmt::Display for Error {
161    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
162        match self {
163            Self::UnsupportedVersion { found } => {
164                write!(formatter, "unsupported Arctic topology version {found}")
165            }
166            Self::InvalidMetadata { metadata } => {
167                write!(formatter, "invalid Arctic edge metadata {metadata:#018x}")
168            }
169            Self::InvalidKeyLength { found, expected } => write!(
170                formatter,
171                "Arctic path has {found} key bytes but key type requires {expected}",
172            ),
173            Self::EmptyNode => formatter.write_str("Arctic topology contains an empty node"),
174            Self::NodeCapacity { kind, found } => {
175                write!(formatter, "{kind:?} cannot contain {found} branches")
176            }
177            Self::DuplicateKey { key } => {
178                write!(formatter, "Arctic node contains duplicate byte {key}")
179            }
180            Self::InvalidSlot { kind, slot } => {
181                write!(formatter, "invalid or duplicate {kind:?} slot {slot}")
182            }
183        }
184    }
185}
186
187impl std::error::Error for Error {}
188
189mod private {
190    use super::*;
191
192    pub trait Sealed: crate::Key {
193        const BYTES: usize;
194
195        fn metadata_to_raw(metadata: ribbit::Packed<Self::Edge>) -> u64;
196
197        unsafe fn metadata_from_raw(raw: u64) -> ribbit::Packed<Self::Edge>;
198
199        fn validate_metadata(raw: u64) -> Result<usize, Error>;
200    }
201
202    macro_rules! impl_be_key {
203        ($($key:ty),+ $(,)?) => {
204            $(
205                impl Sealed for $key {
206                    const BYTES: usize = core::mem::size_of::<Self>();
207
208                    fn metadata_to_raw(metadata: ribbit::Packed<Self::Edge>) -> u64 {
209                        metadata.with_frozen(false).with_value(false).into_raw()
210                    }
211
212                    unsafe fn metadata_from_raw(raw: u64) -> ribbit::Packed<Self::Edge> {
213                        unsafe { ribbit::Packed::<edge::Be>::from_raw_unchecked(raw) }
214                    }
215
216                    fn validate_metadata(raw: u64) -> Result<usize, Error> {
217                        validate_be_metadata(raw)
218                    }
219                }
220            )+
221        };
222    }
223
224    impl_be_key!(u16, u32, u128);
225    #[cfg(not(feature = "opt-no-int"))]
226    impl_be_key!(u64);
227
228    #[cfg(feature = "opt-no-int")]
229    impl Sealed for u64 {
230        const BYTES: usize = core::mem::size_of::<Self>();
231
232        fn metadata_to_raw(metadata: ribbit::Packed<Self::Edge>) -> u64 {
233            metadata.with_frozen(false).with_value(false).into_raw()
234        }
235
236        unsafe fn metadata_from_raw(raw: u64) -> ribbit::Packed<Self::Edge> {
237            unsafe { ribbit::Packed::<edge::Le>::from_raw_unchecked(raw) }
238        }
239
240        fn validate_metadata(raw: u64) -> Result<usize, Error> {
241            validate_le_metadata(raw)
242        }
243    }
244}
245
246/// Unsigned key types supported by pointer-free topology snapshots.
247///
248/// This trait is sealed. Version 1 intentionally matches the unsigned-key
249/// restriction in WorkTable's Arctic backend.
250pub trait Key: crate::Key + private::Sealed {}
251
252impl Key for u16 {}
253impl Key for u32 {}
254impl Key for u64 {}
255impl Key for u128 {}
256
257impl<V> Topology<V> {
258    /// Validate all structural, slot, metadata, and key-length invariants.
259    pub fn validate<K: Key>(&self) -> Result<(), Error> {
260        if self.version != VERSION {
261            return Err(Error::UnsupportedVersion {
262                found: self.version,
263            });
264        }
265
266        if let Some(root) = &self.root {
267            validate_edge::<K, V>(root, 0)?;
268        }
269        Ok(())
270    }
271}
272
273impl<K, V> sequential::Map<K, V>
274where
275    K: Key,
276    V: sequential::Value,
277{
278    /// Export the exact quiescent Arctic topology without process pointers.
279    ///
280    /// `encode` must copy or otherwise encode the logical value; the raw value
281    /// word stored in Arctic is deliberately never exposed.
282    pub fn export_topology<T>(
283        &self,
284        mut encode: impl FnMut(&V) -> T,
285    ) -> Result<Topology<T>, Error> {
286        let root =
287            unsafe { export_edge::<K, V, T, _>(NonNull::from(self.raw.root()), &mut encode) };
288        let topology = Topology {
289            version: VERSION,
290            root,
291        };
292        topology.validate::<K>()?;
293        Ok(topology)
294    }
295
296    /// Restore a validated topology and reconstruct its exact adaptive node kinds.
297    pub fn from_topology<T>(
298        topology: Topology<T>,
299        mut decode: impl FnMut(T) -> V,
300    ) -> Result<Self, Error> {
301        topology.validate::<K>()?;
302
303        let mut map = Self::new();
304        if let Some(root) = topology.root {
305            let root = unsafe { import_edge::<K, V, T, _>(root, &mut decode) };
306            map.raw.set_empty_root(root);
307        }
308        Ok(map)
309    }
310}
311
312impl<K, V, S> concurrent::Map<K, V, S>
313where
314    K: Key,
315    V: concurrent::Value,
316    S: concurrent::Smr<K, V>,
317{
318    /// Export an exact topology through an exclusive sequential view.
319    ///
320    /// Requiring `&mut self` prevents concurrent mutation while the snapshot is
321    /// captured and adds no synchronization to point operations.
322    pub fn export_topology<T>(
323        &mut self,
324        encode: impl FnMut(&V) -> T,
325    ) -> Result<Topology<T>, Error> {
326        self.as_sequential().export_topology(encode)
327    }
328}
329
330impl<K, V, S> concurrent::Map<K, V, S>
331where
332    K: Key,
333    V: concurrent::Value,
334    S: concurrent::Smr<K, V> + Default,
335{
336    /// Restore a concurrent map from a validated pointer-free topology.
337    pub fn from_topology<T>(
338        topology: Topology<T>,
339        decode: impl FnMut(T) -> V,
340    ) -> Result<Self, Error> {
341        sequential::Map::from_topology(topology, decode).map(Into::into)
342    }
343}
344
345fn validate_edge<K: Key, V>(edge: &Edge<V>, path_bytes: usize) -> Result<(), Error> {
346    let prefix_bytes = K::validate_metadata(edge.metadata)?;
347    let path_bytes = path_bytes
348        .checked_add(prefix_bytes)
349        .ok_or(Error::InvalidKeyLength {
350            found: usize::MAX,
351            expected: K::BYTES,
352        })?;
353
354    match &edge.child {
355        Child::Value(_) if path_bytes == K::BYTES => Ok(()),
356        Child::Value(_) => Err(Error::InvalidKeyLength {
357            found: path_bytes,
358            expected: K::BYTES,
359        }),
360        Child::Node(node) => {
361            if path_bytes >= K::BYTES {
362                return Err(Error::InvalidKeyLength {
363                    found: path_bytes + 1,
364                    expected: K::BYTES,
365                });
366            }
367            if node.branches.is_empty() {
368                return Err(Error::EmptyNode);
369            }
370            let slot_count = node.slot_count as usize;
371            if node.branches.len() > node.kind.capacity()
372                || slot_count > node.kind.capacity()
373                || slot_count < node.branches.len()
374                || (node.kind == NodeKind::Node256 && slot_count != 256)
375            {
376                return Err(Error::NodeCapacity {
377                    kind: node.kind,
378                    found: slot_count.max(node.branches.len()),
379                });
380            }
381
382            let mut keys = [false; 256];
383            let mut slots = [false; 256];
384            for branch in &node.branches {
385                if core::mem::replace(&mut keys[branch.key as usize], true) {
386                    return Err(Error::DuplicateKey { key: branch.key });
387                }
388
389                let slot = branch.slot as usize;
390                if slot >= slot_count
391                    || core::mem::replace(&mut slots[slot], true)
392                    || (node.kind == NodeKind::Node256 && slot != branch.key as usize)
393                {
394                    return Err(Error::InvalidSlot {
395                        kind: node.kind,
396                        slot: branch.slot,
397                    });
398                }
399
400                validate_edge::<K, V>(&branch.edge, path_bytes + 1)?;
401            }
402            Ok(())
403        }
404    }
405}
406
407fn validate_be_metadata(metadata: u64) -> Result<usize, Error> {
408    const FLAGS: u64 = 0b111;
409    const LENGTH: u64 = 0b11_1000;
410
411    let bits = (metadata & LENGTH) as usize;
412    let prefix_mask = if bits == 0 {
413        0
414    } else {
415        u64::MAX << (64 - bits)
416    };
417    if metadata & FLAGS != 0 || metadata & !(prefix_mask | LENGTH) != 0 {
418        return Err(Error::InvalidMetadata { metadata });
419    }
420    Ok(bits / 8)
421}
422
423#[cfg(feature = "opt-no-int")]
424fn validate_le_metadata(metadata: u64) -> Result<usize, Error> {
425    const FLAGS: u64 = 0b111 << 56;
426    const LENGTH: u64 = 0b11_1000 << 56;
427
428    let bits = ((metadata & LENGTH) >> 56) as usize;
429    let prefix_mask = if bits == 0 {
430        0
431    } else {
432        u64::MAX >> (64 - bits)
433    };
434    if metadata & FLAGS != 0 || metadata & !(prefix_mask | LENGTH) != 0 {
435        return Err(Error::InvalidMetadata { metadata });
436    }
437    Ok(bits / 8)
438}
439
440unsafe fn export_edge<K, V, T, F>(
441    pointer: NonNull<Atomic<RawEdge<K::Edge>>>,
442    encode: &mut F,
443) -> Option<Edge<T>>
444where
445    K: Key,
446    V: sequential::Value,
447    F: FnMut(&V) -> T,
448{
449    let edge = unsafe { pointer.as_ref() }.load_packed(Ordering::Acquire);
450    let child = edge.child()?;
451    let metadata = K::metadata_to_raw(edge.meta());
452
453    let child = match child {
454        edge::Child::Value(_) => {
455            let value = unsafe { RawEdge::as_value_unchecked(pointer).cast::<V>().as_ref() };
456            Child::Value(encode(value))
457        }
458        edge::Child::Node(node) => {
459            let kind = NodeKind::from_raw(node.r#type());
460            let entries = unsafe { node.topology_entries() };
461            let slot_count = entries.len() as u16;
462            let mut branches = Vec::new();
463            for (key, slot, pointer) in entries {
464                if let Some(edge) = unsafe { export_edge::<K, V, T, F>(pointer.cast(), encode) } {
465                    branches.push(Branch { key, slot, edge });
466                }
467            }
468            branches.sort_unstable_by_key(|branch| branch.slot);
469            Child::Node(Node {
470                kind,
471                slot_count,
472                branches,
473            })
474        }
475    };
476
477    Some(Edge { metadata, child })
478}
479
480unsafe fn import_edge<K, V, T, F>(edge: Edge<T>, decode: &mut F) -> ribbit::Packed<RawEdge<K::Edge>>
481where
482    K: Key,
483    V: sequential::Value,
484    F: FnMut(T) -> V,
485{
486    let metadata = unsafe { K::metadata_from_raw(edge.metadata) };
487    match edge.child {
488        Child::Value(value) => RawEdge::new_value(metadata, decode(value).into_raw()),
489        Child::Node(mut node) => {
490            node.branches.sort_unstable_by_key(|branch| branch.slot);
491            let kind = node.kind;
492            let slot_count = node.slot_count as usize;
493            let (keys, edges) = if kind == NodeKind::Node256 {
494                let mut keys = Vec::with_capacity(node.branches.len());
495                let mut edges = Vec::with_capacity(node.branches.len());
496                for branch in node.branches {
497                    keys.push(branch.key);
498                    edges.push(unsafe { import_edge::<K, V, T, F>(branch.edge, decode) }.erase());
499                }
500                (keys, edges)
501            } else {
502                let mut used_keys = [false; 256];
503                for branch in &node.branches {
504                    used_keys[branch.key as usize] = true;
505                }
506
507                let mut filler_keys = (u8::MIN..=u8::MAX).filter(|key| !used_keys[*key as usize]);
508                let mut keys = Vec::with_capacity(slot_count);
509                for _ in 0..slot_count {
510                    keys.push(
511                        filler_keys
512                            .next()
513                            .expect("validated node has spare key bytes"),
514                    );
515                }
516                let mut edges = vec![RawEdge::<K::Edge>::NULL.erase(); slot_count];
517
518                for branch in node.branches {
519                    let slot = branch.slot as usize;
520                    keys[slot] = branch.key;
521                    edges[slot] = unsafe { import_edge::<K, V, T, F>(branch.edge, decode) }.erase();
522                }
523                (keys, edges)
524            };
525            let pointer = unsafe { node::Ptr::new_exact(kind.into_raw(), &keys, &edges) };
526            RawEdge::new_node(metadata, pointer)
527        }
528    }
529}
530
531#[cfg(test)]
532mod tests {
533    use super::*;
534
535    fn contains_kind<V>(edge: &Edge<V>, expected: NodeKind) -> bool {
536        match &edge.child {
537            Child::Value(_) => false,
538            Child::Node(node) => {
539                node.kind == expected
540                    || node
541                        .branches
542                        .iter()
543                        .any(|branch| contains_kind(&branch.edge, expected))
544            }
545        }
546    }
547
548    fn assert_round_trip_for_keys(keys: impl IntoIterator<Item = u64>, expected: NodeKind) {
549        let mut map = sequential::Map::<u64, u64>::new();
550        for key in keys {
551            map.insert(key, key.rotate_left(17)).unwrap();
552        }
553
554        let before = map.export_topology(|value| *value).unwrap();
555        assert!(contains_kind(before.root.as_ref().unwrap(), expected));
556        let restored =
557            sequential::Map::<u64, u64>::from_topology(before.clone(), |value| value).unwrap();
558        let after = restored.export_topology(|value| *value).unwrap();
559        assert_eq!(after, before);
560    }
561
562    #[test]
563    fn sequential_round_trip_preserves_topology_and_values() {
564        let mut map = sequential::Map::<u64, Box<u64>>::new();
565        for key in (0..4_096).map(|key| key * 17) {
566            map.insert(key, Box::new(key ^ 0xA5A5)).unwrap();
567        }
568        for key in (0..4_096).step_by(7).map(|key| key * 17) {
569            map.remove(&key).unwrap();
570        }
571
572        let before = map.export_topology(|value| **value).unwrap();
573        let restored =
574            sequential::Map::<u64, Box<u64>>::from_topology(before.clone(), Box::new).unwrap();
575        let after = restored.export_topology(|value| **value).unwrap();
576
577        assert_eq!(after, before);
578        for key in (0..4_096).map(|key| key * 17) {
579            let expected = (key / 17 % 7 != 0).then_some(key ^ 0xA5A5);
580            assert_eq!(restored.get(&key).map(|value| **value), expected);
581        }
582    }
583
584    #[test]
585    fn concurrent_round_trip_requires_exclusive_snapshot() {
586        let mut map = concurrent::Map::<u64, Box<u64>>::default();
587        for key in 0..1_024 {
588            map.insert(key, Box::new(key + 1)).unwrap();
589        }
590
591        let topology = map.export_topology(|value| **value).unwrap();
592        let restored = concurrent::Map::<u64, Box<u64>>::from_topology(topology, Box::new).unwrap();
593
594        for key in 0..1_024 {
595            assert_eq!(restored.get(&key).as_deref(), Some(&(key + 1)));
596        }
597    }
598
599    #[test]
600    fn rejects_transient_metadata_flags() {
601        let topology = Topology::<u64> {
602            version: VERSION,
603            root: Some(Edge {
604                metadata: 1,
605                child: Child::Value(42),
606            }),
607        };
608        assert_eq!(
609            topology.validate::<u64>(),
610            Err(Error::InvalidMetadata { metadata: 1 }),
611        );
612    }
613
614    #[test]
615    fn preserves_every_adaptive_node_kind() {
616        assert_round_trip_for_keys(0..2, NodeKind::Node3);
617        assert_round_trip_for_keys(0..10, NodeKind::Node15);
618        assert_round_trip_for_keys(0..32, NodeKind::Node47);
619        assert_round_trip_for_keys(0..256, NodeKind::Node256);
620    }
621}