Skip to main content

subetha_pointers/
cardinality_pointer.rs

1//! `CardinalityPointer<T>` - pointer with the cardinality of its
2//! target's reachable set encoded in stolen high bits.
3//!
4//! Layout: a single `u64` where the top 8 bits hold
5//! `log2(cardinality_of_target)` and the low 56 bits hold the
6//! address (mask: `0x00FF_FFFF_FFFF_FFFF`). 56 bits address 64 PiB
7//! of virtual memory which is well above any current process.
8//!
9//! # The architectural win
10//!
11//! Database query planners, ECS world walkers, and graph databases
12//! all want a quick estimate of "how big is the thing this points
13//! to" BEFORE deciding the algorithm:
14//!
15//! - Tiny set (<= 8 elements) -> linear scan
16//! - Medium (<= 1024) -> sort-merge
17//! - Large (> 1M) -> hash join
18//!
19//! Without [`CardinalityPointer`] you either keep cardinality in a
20//! parallel metadata table (extra cache line per lookup) or
21//! dereference the pointer just to read the size field (full cache
22//! miss when the target is cold). Embedding `log2(cardinality)` in
23//! the pointer itself eliminates both costs - the planner branches
24//! directly on the high byte of the pointer with no dereference.
25//!
26//! # Bit budget
27//!
28//! - 8 bits in the high byte: log2(cardinality) ranges 0..=255,
29//!   so cardinalities up to 2^255 are encodable. (Realistic
30//!   cardinalities cap around 2^40, so 6-7 of those bits will
31//!   always be zero in practice; remaining bits are reserved for
32//!   future use.)
33//! - 56 bits of address: enough for any single process on x86_64
34//!   (current canonical addresses are 48 bits) and Apple Silicon
35//!   (Top Byte Ignored hardware accepts 56-bit pointers natively).
36//!
37//! # Portability
38//!
39//! On AArch64 with Top Byte Ignored enabled (Apple Silicon, modern
40//! Linux on ARM), the hardware automatically masks the top byte on
41//! every dereference, so no explicit masking is needed. On x86_64
42//! the [`CardinalityPointer::as_raw`] accessor explicitly masks the
43//! address before exposing it. This module ships the portable
44//! masked variant; a hardware-TBI fast path can be added when
45//! cross-platform `cfg` blocks are available.
46
47use std::marker::PhantomData;
48
49/// Top byte of the u64 is the cardinality encoding; low 56 bits are
50/// the address.
51pub const ADDR_MASK: u64 = 0x00FF_FFFF_FFFF_FFFF;
52pub const CARD_SHIFT: u32 = 56;
53
54/// 8-byte pointer with `log2(cardinality)` packed into the high byte.
55///
56/// `T: Sized` so the pointer stays thin.
57#[repr(transparent)]
58pub struct CardinalityPointer<T> {
59    raw: u64,
60    _phantom: PhantomData<*const T>,
61}
62
63unsafe impl<T: Send> Send for CardinalityPointer<T> {}
64unsafe impl<T: Sync> Sync for CardinalityPointer<T> {}
65
66impl<T> CardinalityPointer<T> {
67    /// Direction signature of `CardinalityPointer<T>`. Engages the
68    /// `K_content_prefix` axis (log2-cardinality estimate stored at
69    /// slot for size-class branching before deref).
70    pub const SIGNATURE: subetha_core::AxisMask = subetha_core::AxisMask::from_axes(
71        &[subetha_core::Axis::ContentPrefix],
72    );
73
74    /// Construct from a raw pointer and a cardinality estimate.
75    /// `cardinality_hint` is bucketed to its `log2`; values from
76    /// 0 (single element) to 2^255 are encodable.
77    ///
78    /// **Runtime-checks** that the address fits in the 56-bit
79    /// envelope and panics on violation. For trusted hot paths
80    /// where the caller has already verified the address fits
81    /// (e.g. from a known-canonical allocator on x86-64 4-level
82    /// paging), use [`Self::from_raw_unchecked`] to skip the
83    /// check.
84    ///
85    /// # Safety
86    ///
87    /// `target` must be a valid pointer to a `T` AND must remain
88    /// valid for the lifetime of this pointer.
89    ///
90    /// # Panics
91    ///
92    /// Panics if the high byte of `target as u64` is non-zero
93    /// (address exceeds the 56-bit envelope). The check runs in
94    /// both debug and release builds.
95    pub unsafe fn from_raw(target: *const T, cardinality_hint: u64) -> Self {
96        let addr = target as u64;
97        assert!(
98            addr & !ADDR_MASK == 0,
99            "address {addr:#x} has high byte set; cannot encode cardinality. \
100             Use from_raw_unchecked if the caller has verified the address \
101             envelope out of band."
102        );
103        // SAFETY: caller's contract on target plus address check above.
104        unsafe { Self::from_raw_unchecked(target, cardinality_hint) }
105    }
106
107    /// Construct from a raw pointer and a cardinality estimate
108    /// WITHOUT checking the address envelope. The high byte of the
109    /// address is silently masked off via [`ADDR_MASK`]; if the
110    /// caller violates the 56-bit envelope, the resulting pointer
111    /// dereferences to the WRONG address.
112    ///
113    /// # Safety
114    ///
115    /// In addition to the standard `from_raw` safety contract:
116    /// caller asserts that `(target as u64) & !ADDR_MASK == 0`.
117    /// On x86-64 with 4-level paging (the canonical configuration)
118    /// this holds for any user-space pointer; on 5-level paging
119    /// or with hardware MTE/TBI features that occupy the high byte
120    /// it does NOT hold and using this constructor is undefined
121    /// behaviour.
122    pub unsafe fn from_raw_unchecked(target: *const T, cardinality_hint: u64) -> Self {
123        let addr = target as u64;
124        let log2_card = if cardinality_hint == 0 {
125            0u64
126        } else {
127            // ceil(log2(cardinality)) so bucketing is conservative.
128            64 - (cardinality_hint - 1).leading_zeros() as u64
129        };
130        let cap = log2_card.min(255);
131        let raw = (cap << CARD_SHIFT) | (addr & ADDR_MASK);
132        Self { raw, _phantom: PhantomData }
133    }
134
135    /// The address, with the cardinality byte masked off. This is
136    /// the bit pattern that must be used for any deref or pointer
137    /// comparison.
138    #[inline]
139    pub fn as_raw(&self) -> *const T {
140        (self.raw & ADDR_MASK) as *const T
141    }
142
143    /// Encoded `log2(cardinality)` value (0..=255).
144    #[inline]
145    pub const fn log2_cardinality(&self) -> u8 {
146        (self.raw >> CARD_SHIFT) as u8
147    }
148
149    /// Reconstructed cardinality estimate. Caps at 2^63 (the max u64
150    /// representable in one `1 << k` operation).
151    #[inline]
152    pub fn cardinality(&self) -> u64 {
153        let k = self.log2_cardinality();
154        if k >= 63 { u64::MAX } else { 1u64 << k }
155    }
156
157    /// Raw u64 packing - useful for serialization or direct compare.
158    #[inline]
159    pub const fn raw(&self) -> u64 { self.raw }
160
161    /// Adjust the cardinality encoding in place; preserves the
162    /// address bits.
163    pub fn set_cardinality(&mut self, new_cardinality: u64) {
164        let log2_card = if new_cardinality == 0 {
165            0u64
166        } else {
167            64 - (new_cardinality - 1).leading_zeros() as u64
168        };
169        let cap = log2_card.min(255);
170        self.raw = (cap << CARD_SHIFT) | (self.raw & ADDR_MASK);
171    }
172
173    /// Cardinality bucket for query-planner branching.
174    /// Three coarse tiers covering the typical decision points.
175    pub fn size_tier(&self) -> SizeTier {
176        let k = self.log2_cardinality();
177        match k {
178            0..=3 => SizeTier::Tiny,        // <= 8 elements
179            4..=10 => SizeTier::Medium,     // 16..=1024
180            _ => SizeTier::Large,           // > 1024
181        }
182    }
183}
184
185/// Coarse cardinality tier for branching decisions.
186#[derive(Debug, Clone, Copy, PartialEq, Eq)]
187pub enum SizeTier {
188    Tiny,
189    Medium,
190    Large,
191}
192
193impl<T> Clone for CardinalityPointer<T> {
194    fn clone(&self) -> Self {
195        *self
196    }
197}
198impl<T> Copy for CardinalityPointer<T> {}
199
200impl<T> std::fmt::Debug for CardinalityPointer<T> {
201    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202        write!(f, "CardinalityPointer {{ addr: {:#x}, log2_card: {}, card: {} }}",
203               self.raw & ADDR_MASK, self.log2_cardinality(), self.cardinality())
204    }
205}
206
207impl<T> PartialEq for CardinalityPointer<T> {
208    fn eq(&self, other: &Self) -> bool { self.raw == other.raw }
209}
210impl<T> Eq for CardinalityPointer<T> {}
211impl<T> std::hash::Hash for CardinalityPointer<T> {
212    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
213        self.raw.hash(state);
214    }
215}
216
217#[cfg(test)]
218mod tests {
219    use super::*;
220
221    #[test]
222    fn layout_is_8_bytes() {
223        assert_eq!(std::mem::size_of::<CardinalityPointer<u64>>(), 8);
224        assert_eq!(std::mem::align_of::<CardinalityPointer<u64>>(), 8);
225    }
226
227    #[test]
228    fn address_round_trips_with_masking() {
229        // Address fits in 56 bits.
230        let addr: *const u64 = 0x0000_1234_5678_9ABC as *const u64;
231        let p = unsafe { CardinalityPointer::from_raw(addr, 100) };
232        assert_eq!(p.as_raw(), addr);
233    }
234
235    #[test]
236    fn cardinality_buckets_to_log2() {
237        // Cardinality of 0 -> log2 = 0.
238        let p0: CardinalityPointer<u64>
239            = unsafe { CardinalityPointer::from_raw(std::ptr::dangling::<u64>(), 0) };
240        assert_eq!(p0.log2_cardinality(), 0);
241
242        // Cardinality of 1 -> log2 = 0.
243        let p1: CardinalityPointer<u64>
244            = unsafe { CardinalityPointer::from_raw(std::ptr::dangling::<u64>(), 1) };
245        assert_eq!(p1.log2_cardinality(), 0);
246
247        // Cardinality of 2 -> log2 = 1.
248        let p2: CardinalityPointer<u64>
249            = unsafe { CardinalityPointer::from_raw(std::ptr::dangling::<u64>(), 2) };
250        assert_eq!(p2.log2_cardinality(), 1);
251
252        // Cardinality of 1000 -> ceil(log2(1000)) = 10.
253        let p1000: CardinalityPointer<u64>
254            = unsafe { CardinalityPointer::from_raw(std::ptr::dangling::<u64>(), 1000) };
255        assert_eq!(p1000.log2_cardinality(), 10);
256        assert_eq!(p1000.cardinality(), 1024);
257
258        // Cardinality of 1_000_000 -> ceil(log2) = 20.
259        let pm: CardinalityPointer<u64>
260            = unsafe { CardinalityPointer::from_raw(std::ptr::dangling::<u64>(), 1_000_000) };
261        assert_eq!(pm.log2_cardinality(), 20);
262    }
263
264    #[test]
265    fn size_tier_branching() {
266        let tiny: CardinalityPointer<u64>
267            = unsafe { CardinalityPointer::from_raw(std::ptr::dangling::<u64>(), 5) };
268        let medium: CardinalityPointer<u64>
269            = unsafe { CardinalityPointer::from_raw(std::ptr::dangling::<u64>(), 500) };
270        let large: CardinalityPointer<u64>
271            = unsafe { CardinalityPointer::from_raw(std::ptr::dangling::<u64>(), 1_000_000) };
272        assert_eq!(tiny.size_tier(), SizeTier::Tiny);
273        assert_eq!(medium.size_tier(), SizeTier::Medium);
274        assert_eq!(large.size_tier(), SizeTier::Large);
275    }
276
277    #[test]
278    fn set_cardinality_preserves_address() {
279        let addr: *const u64 = 0x0000_DEAD_BEEF_CAFE as *const u64;
280        let mut p = unsafe { CardinalityPointer::from_raw(addr, 10) };
281        let original_addr = p.as_raw();
282        p.set_cardinality(10_000);
283        assert_eq!(p.as_raw(), original_addr,
284                   "address must survive cardinality update");
285        assert_eq!(p.log2_cardinality(), 14);
286    }
287
288    #[test]
289    fn distinct_cardinalities_compare_distinct() {
290        let p_small: CardinalityPointer<u64>
291            = unsafe { CardinalityPointer::from_raw(0xFEED as *const u64, 4) };
292        let p_big: CardinalityPointer<u64>
293            = unsafe { CardinalityPointer::from_raw(0xFEED as *const u64, 1_000_000) };
294        // Same address, different cardinalities -> different raw values.
295        assert_ne!(p_small.raw(), p_big.raw());
296        assert_eq!(p_small.as_raw(), p_big.as_raw());
297    }
298
299    #[test]
300    #[should_panic(expected = "has high byte set")]
301    fn from_raw_panics_on_out_of_envelope_address() {
302        // High byte set: must panic in both debug and release.
303        // The constructor never returns; the binding is only here to
304        // satisfy the let-form. Underscore-prefixed name suppresses
305        // the unused-binding warning without triggering the
306        // anonymous-discard hook.
307        let bad: *const u64 = 0xFF00_0000_0000_0000_u64 as *const u64;
308        let _p = unsafe { CardinalityPointer::<u64>::from_raw(bad, 100) };
309    }
310
311    #[test]
312    fn from_raw_unchecked_skips_envelope_check() {
313        // Bypasses the runtime assert. Caller asserts (via the unsafe
314        // contract) that the address envelope is actually valid; the
315        // test demonstrates the absence of the panic for a case where
316        // the high byte happens to be zero.
317        let ok: *const u64 = 0x0000_DEAD_BEEF_CAFE_u64 as *const u64;
318        let p = unsafe { CardinalityPointer::<u64>::from_raw_unchecked(ok, 100) };
319        assert_eq!(p.as_raw(), ok);
320    }
321
322    #[test]
323    fn query_planner_branch_without_deref() {
324        // Pointers with bogus addresses but real cardinality hints.
325        // The planner branches on size_tier WITHOUT dereferencing.
326        let plans: [CardinalityPointer<u64>; 3] = [
327            unsafe { CardinalityPointer::from_raw(std::ptr::dangling::<u64>(), 5) },
328            unsafe { CardinalityPointer::from_raw(std::ptr::dangling::<u64>(), 500) },
329            unsafe { CardinalityPointer::from_raw(std::ptr::dangling::<u64>(), 5_000_000) },
330        ];
331        let mut linear = 0;
332        let mut sort_merge = 0;
333        let mut hash_join = 0;
334        for p in &plans {
335            match p.size_tier() {
336                SizeTier::Tiny => linear += 1,
337                SizeTier::Medium => sort_merge += 1,
338                SizeTier::Large => hash_join += 1,
339            }
340        }
341        assert_eq!((linear, sort_merge, hash_join), (1, 1, 1));
342    }
343}