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}