1use std::marker::PhantomData;
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum TaggedPtrError {
51 TagOutOfRange,
52 IndexOutOfRange,
53}
54
55#[derive(Debug)]
60#[repr(C)]
61pub struct TaggedOffsetPtr<T, const TAG_BITS: u32> {
62 packed: u32,
63 _phantom: PhantomData<T>,
64}
65
66impl<T, const TAG_BITS: u32> Clone for TaggedOffsetPtr<T, TAG_BITS> {
67 fn clone(&self) -> Self { *self }
68}
69impl<T, const TAG_BITS: u32> Copy for TaggedOffsetPtr<T, TAG_BITS> {}
70impl<T, const TAG_BITS: u32> PartialEq for TaggedOffsetPtr<T, TAG_BITS> {
71 fn eq(&self, other: &Self) -> bool { self.packed == other.packed }
72}
73impl<T, const TAG_BITS: u32> Eq for TaggedOffsetPtr<T, TAG_BITS> {}
74impl<T, const TAG_BITS: u32> std::hash::Hash for TaggedOffsetPtr<T, TAG_BITS> {
75 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
76 self.packed.hash(state);
77 }
78}
79
80impl<T, const TAG_BITS: u32> TaggedOffsetPtr<T, TAG_BITS> {
81 const _ASSERT_TAG_BITS: () = assert!(
87 TAG_BITS <= 31,
88 "TAG_BITS must be in 0..=31 (32 would leave no index bits)",
89 );
90
91 pub const NIL: Self = Self { packed: u32::MAX, _phantom: PhantomData };
93
94 pub const fn max_tag() -> u32 {
97 if TAG_BITS == 0 { 0 } else { (1u32 << TAG_BITS) - 1 }
98 }
99
100 pub const fn max_index() -> u32 {
103 let idx_bits = 32 - TAG_BITS;
104 if idx_bits == 32 { u32::MAX } else { (1u32 << idx_bits) - 1 }
105 }
106
107 #[inline]
109 pub const fn index_mask() -> u32 { Self::max_index() }
110
111 #[inline]
113 pub const fn tag_shift() -> u32 { 32 - TAG_BITS }
114
115 pub fn new(index: u32, tag: u32) -> Self {
119 let _: () = Self::_ASSERT_TAG_BITS;
121 assert!(
122 tag <= Self::max_tag(),
123 "tag {tag} exceeds MAX_TAG {} (TAG_BITS={TAG_BITS})",
124 Self::max_tag(),
125 );
126 assert!(
127 index <= Self::max_index(),
128 "index {index} exceeds MAX_INDEX {} (TAG_BITS={TAG_BITS})",
129 Self::max_index(),
130 );
131 let packed = if TAG_BITS == 0 {
132 index
135 } else {
136 (tag << Self::tag_shift()) | index
137 };
138 Self { packed, _phantom: PhantomData }
139 }
140
141 pub fn try_new(index: u32, tag: u32) -> Result<Self, TaggedPtrError> {
144 if tag > Self::max_tag() { return Err(TaggedPtrError::TagOutOfRange); }
145 if index > Self::max_index() { return Err(TaggedPtrError::IndexOutOfRange); }
146 let packed = if TAG_BITS == 0 {
147 index
148 } else {
149 (tag << Self::tag_shift()) | index
150 };
151 Ok(Self { packed, _phantom: PhantomData })
152 }
153
154 #[inline]
158 pub const fn from_raw(packed: u32) -> Self {
159 Self { packed, _phantom: PhantomData }
160 }
161
162 #[inline]
164 pub const fn raw(self) -> u32 { self.packed }
165
166 #[inline]
168 pub fn index(self) -> u32 { self.packed & Self::index_mask() }
169
170 #[inline]
172 pub fn tag(self) -> u32 {
173 if TAG_BITS == 0 { 0 } else { self.packed >> Self::tag_shift() }
174 }
175
176 pub fn with_tag(self, new_tag: u32) -> Self {
178 Self::new(self.index(), new_tag)
179 }
180
181 pub fn with_index(self, new_index: u32) -> Self {
183 Self::new(new_index, self.tag())
184 }
185
186 #[inline]
188 pub fn is_nil(self) -> bool { self.packed == u32::MAX }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194
195 #[test]
196 fn tag_bits_4_max_tag_and_index() {
197 type P = TaggedOffsetPtr<u64, 4>;
198 assert_eq!(P::max_tag(), 15); assert_eq!(P::max_index(), (1u32 << 28) - 1); assert_eq!(P::tag_shift(), 28);
201 }
202
203 #[test]
204 fn tag_bits_0_degenerates_to_offset_ptr() {
205 type P = TaggedOffsetPtr<u64, 0>;
206 assert_eq!(P::max_tag(), 0);
207 assert_eq!(P::max_index(), u32::MAX);
208 let p = P::new(123, 0);
210 assert_eq!(p.index(), 123);
211 assert_eq!(p.tag(), 0);
212 }
213
214 #[test]
215 fn pack_unpack_round_trip_tag_bits_4() {
216 type P = TaggedOffsetPtr<u64, 4>;
217 let p = P::new(42, 7);
218 assert_eq!(p.index(), 42);
219 assert_eq!(p.tag(), 7);
220 }
221
222 #[test]
223 fn pack_unpack_round_trip_tag_bits_8() {
224 type P = TaggedOffsetPtr<u64, 8>;
225 assert_eq!(P::max_tag(), 255);
226 assert_eq!(P::max_index(), (1u32 << 24) - 1);
227 let p = P::new(99_999, 200);
228 assert_eq!(p.index(), 99_999);
229 assert_eq!(p.tag(), 200);
230 }
231
232 #[test]
233 fn raw_round_trip() {
234 type P = TaggedOffsetPtr<u64, 4>;
235 let p = P::new(42, 7);
236 let raw = p.raw();
237 let q = P::from_raw(raw);
238 assert_eq!(p, q);
239 assert_eq!(q.index(), 42);
240 assert_eq!(q.tag(), 7);
241 }
242
243 #[test]
244 fn with_tag_keeps_index() {
245 type P = TaggedOffsetPtr<u64, 4>;
246 let p = P::new(42, 7);
247 let q = p.with_tag(3);
248 assert_eq!(q.index(), 42);
249 assert_eq!(q.tag(), 3);
250 }
251
252 #[test]
253 fn with_index_keeps_tag() {
254 type P = TaggedOffsetPtr<u64, 4>;
255 let p = P::new(42, 7);
256 let q = p.with_index(100);
257 assert_eq!(q.index(), 100);
258 assert_eq!(q.tag(), 7);
259 }
260
261 #[test]
262 fn try_new_rejects_oversized_tag() {
263 type P = TaggedOffsetPtr<u64, 4>;
264 assert_eq!(P::try_new(0, 16).err(), Some(TaggedPtrError::TagOutOfRange));
265 assert_eq!(P::try_new(0, 999).err(), Some(TaggedPtrError::TagOutOfRange));
266 }
267
268 #[test]
269 fn try_new_rejects_oversized_index() {
270 type P = TaggedOffsetPtr<u64, 4>;
271 let max = P::max_index();
272 assert!(P::try_new(max, 0).is_ok());
273 assert_eq!(P::try_new(max + 1, 0).err(), Some(TaggedPtrError::IndexOutOfRange));
274 }
275
276 #[test]
277 #[should_panic(expected = "tag")]
278 fn new_panics_on_oversized_tag() {
279 type P = TaggedOffsetPtr<u64, 4>;
280 let _p = P::new(0, 999);
281 }
282
283 #[test]
284 #[should_panic(expected = "index")]
285 fn new_panics_on_oversized_index() {
286 type P = TaggedOffsetPtr<u64, 4>;
287 let _p = P::new(u32::MAX, 0);
288 }
289
290 #[test]
291 fn nil_is_all_ones_and_detectable() {
292 type P = TaggedOffsetPtr<u64, 4>;
293 let n = P::NIL;
294 assert!(n.is_nil());
295 assert_eq!(n.raw(), u32::MAX);
296 let p = P::new(0, 0);
297 assert!(!p.is_nil());
298 }
299
300 #[test]
301 fn equality_and_hash() {
302 use std::collections::HashSet;
303 type P = TaggedOffsetPtr<u64, 4>;
304 let a = P::new(5, 1);
305 let b = P::new(5, 1);
306 let c = P::new(5, 2);
307 let d = P::new(6, 1);
308 assert_eq!(a, b);
309 assert_ne!(a, c);
310 assert_ne!(a, d);
311 let mut s = HashSet::new();
312 s.insert(a);
313 assert!(s.contains(&b));
314 assert!(!s.contains(&c));
315 assert!(!s.contains(&d));
316 }
317
318 #[test]
319 fn boundary_index_at_max_for_tag_bits_4() {
320 type P = TaggedOffsetPtr<u64, 4>;
321 let max_idx = P::max_index();
322 let p = P::new(max_idx, 0);
323 assert_eq!(p.index(), max_idx);
324 assert_eq!(p.tag(), 0);
325 let max_tag = P::max_tag();
327 let p2 = P::new(max_idx, max_tag);
328 assert_eq!(p2.index(), max_idx);
329 assert_eq!(p2.tag(), max_tag);
330 }
331
332 #[test]
333 fn integration_with_shared_region_via_index_extraction() {
334 use crate::SharedRegion;
335 use std::path::PathBuf;
336
337 let mut p: PathBuf = std::env::temp_dir();
338 let pid = std::process::id();
339 p.push(format!("subetha-tagged-region-{pid}.bin"));
340
341 #[derive(Clone, Copy, Debug, PartialEq)]
344 #[repr(C)]
345 struct Node { key: u64, value: u64 }
346
347 let r: SharedRegion<Node> = SharedRegion::create(&p, 16).unwrap();
348 let inner = r.allocate(Node { key: 42, value: 100 }).unwrap();
351 type P = TaggedOffsetPtr<Node, 2>;
352 let tagged = P::new(inner.index, 1);
353 assert_eq!(tagged.tag(), 1);
354 let n = r.get(crate::OffsetPtr::new(tagged.index())).unwrap();
356 assert_eq!(n, Node { key: 42, value: 100 });
357 std::fs::remove_file(&p).ok();
358 }
359
360 #[test]
361 fn cross_process_position_independence_via_raw_bits() {
362 type P = TaggedOffsetPtr<u64, 4>;
366 let producer = P::new(1234, 9);
367 let raw = producer.raw();
368 let consumer = P::from_raw(raw);
370 assert_eq!(consumer.index(), 1234);
371 assert_eq!(consumer.tag(), 9);
372 }
373
374 #[test]
375 fn tag_bits_1_dirty_bit_pattern() {
376 type P = TaggedOffsetPtr<u64, 1>;
378 assert_eq!(P::max_tag(), 1);
379 assert_eq!(P::max_index(), i32::MAX as u32); let clean = P::new(42, 0);
381 let dirty = clean.with_tag(1);
382 assert_eq!(dirty.index(), clean.index());
383 assert_ne!(dirty, clean);
384 assert_eq!(dirty.tag(), 1);
385 }
386}