1use crate::shared_region::OffsetPtr;
52
53pub const BOXED_MASK: u64 = 0xFFF8_0000_0000_0000;
55pub const BOXED_PREFIX: u64 = 0xFFF8_0000_0000_0000;
57pub const TAG_SHIFT: u64 = 48;
59pub const TAG_MASK: u64 = 0x7;
61pub const PAYLOAD_MASK: u64 = 0x0000_FFFF_FFFF_FFFF;
63
64pub const CANONICAL_QNAN: u64 = 0x7FF8_0000_0000_0000;
68
69pub const TAG_NIL: u64 = 0;
71pub const TAG_I32: u64 = 1;
72pub const TAG_U32: u64 = 2;
73pub const TAG_BOOL: u64 = 3;
74pub const TAG_OFFSET_PTR: u64 = 4;
75pub const TAG_TAGGED_OFFSET_PTR: u64 = 5; #[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub enum NaNValueType {
80 F64,
81 Nil,
82 I32,
83 U32,
84 Bool,
85 OffsetPtr,
86 Reserved(u64),
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
92#[repr(C)]
93pub struct SharedNaNValue {
94 raw: u64,
95}
96
97#[inline]
98const fn pack(tag: u64, payload: u64) -> u64 {
99 BOXED_PREFIX | (tag << TAG_SHIFT) | (payload & PAYLOAD_MASK)
100}
101
102impl SharedNaNValue {
103 pub const NIL: Self = Self { raw: pack(TAG_NIL, 0) };
105
106 pub fn from_f64(v: f64) -> Self {
111 if v.is_nan() {
112 Self { raw: CANONICAL_QNAN }
114 } else {
115 Self { raw: v.to_bits() }
116 }
117 }
118
119 pub const fn from_i32(v: i32) -> Self {
120 Self { raw: pack(TAG_I32, (v as u32) as u64) }
123 }
124
125 pub const fn from_u32(v: u32) -> Self {
126 Self { raw: pack(TAG_U32, v as u64) }
127 }
128
129 pub const fn from_bool(v: bool) -> Self {
130 Self { raw: pack(TAG_BOOL, v as u64) }
131 }
132
133 pub fn from_offset_ptr<T>(p: OffsetPtr<T>) -> Self {
136 Self { raw: pack(TAG_OFFSET_PTR, p.index as u64) }
137 }
138
139 #[inline]
142 pub const fn from_raw(raw: u64) -> Self { Self { raw } }
143
144 #[inline]
146 pub const fn raw(self) -> u64 { self.raw }
147
148 #[inline]
151 fn is_boxed(self) -> bool {
152 (self.raw & BOXED_MASK) == BOXED_PREFIX
153 }
154
155 #[inline]
156 fn tag(self) -> u64 {
157 (self.raw >> TAG_SHIFT) & TAG_MASK
158 }
159
160 pub fn type_tag(self) -> NaNValueType {
161 if !self.is_boxed() { return NaNValueType::F64; }
162 match self.tag() {
163 TAG_NIL => NaNValueType::Nil,
164 TAG_I32 => NaNValueType::I32,
165 TAG_U32 => NaNValueType::U32,
166 TAG_BOOL => NaNValueType::Bool,
167 TAG_OFFSET_PTR => NaNValueType::OffsetPtr,
168 other => NaNValueType::Reserved(other),
169 }
170 }
171
172 pub fn is_f64(self) -> bool { !self.is_boxed() }
173 pub fn is_nil(self) -> bool { self.is_boxed() && self.tag() == TAG_NIL }
174 pub fn is_i32(self) -> bool { self.is_boxed() && self.tag() == TAG_I32 }
175 pub fn is_u32(self) -> bool { self.is_boxed() && self.tag() == TAG_U32 }
176 pub fn is_bool(self) -> bool { self.is_boxed() && self.tag() == TAG_BOOL }
177 pub fn is_offset_ptr(self) -> bool {
178 self.is_boxed() && self.tag() == TAG_OFFSET_PTR
179 }
180
181 pub fn as_f64(self) -> Option<f64> {
184 if self.is_f64() { Some(f64::from_bits(self.raw)) } else { None }
185 }
186
187 pub fn as_i32(self) -> Option<i32> {
188 if self.is_i32() {
189 Some((self.raw & 0xFFFF_FFFF) as u32 as i32)
190 } else { None }
191 }
192
193 pub fn as_u32(self) -> Option<u32> {
194 if self.is_u32() {
195 Some((self.raw & 0xFFFF_FFFF) as u32)
196 } else { None }
197 }
198
199 pub fn as_bool(self) -> Option<bool> {
200 if self.is_bool() { Some((self.raw & 1) != 0) } else { None }
201 }
202
203 pub fn as_offset_ptr<T>(self) -> Option<OffsetPtr<T>> {
204 if self.is_offset_ptr() {
205 Some(OffsetPtr::new((self.raw & 0xFFFF_FFFF) as u32))
206 } else { None }
207 }
208}
209
210impl Default for SharedNaNValue {
211 fn default() -> Self { Self::NIL }
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217 use crate::shared_region::OffsetPtr;
218
219 #[test]
220 fn nil_round_trip() {
221 let v = SharedNaNValue::NIL;
222 assert!(v.is_nil());
223 assert_eq!(v.type_tag(), NaNValueType::Nil);
224 assert_eq!(SharedNaNValue::default(), SharedNaNValue::NIL);
225 }
226
227 #[test]
228 fn i32_round_trip_positive_and_negative() {
229 for v in [0i32, 1, -1, 42, -42, i32::MAX, i32::MIN] {
230 let n = SharedNaNValue::from_i32(v);
231 assert!(n.is_i32(), "{v} should be i32");
232 assert_eq!(n.as_i32(), Some(v));
233 assert_eq!(n.type_tag(), NaNValueType::I32);
234 }
235 }
236
237 #[test]
238 fn u32_round_trip() {
239 for v in [0u32, 1, 42, u32::MAX, u32::MAX / 2] {
240 let n = SharedNaNValue::from_u32(v);
241 assert!(n.is_u32());
242 assert_eq!(n.as_u32(), Some(v));
243 assert_eq!(n.type_tag(), NaNValueType::U32);
244 }
245 }
246
247 #[test]
248 fn bool_round_trip() {
249 let t = SharedNaNValue::from_bool(true);
250 let f = SharedNaNValue::from_bool(false);
251 assert!(t.is_bool() && f.is_bool());
252 assert_eq!(t.as_bool(), Some(true));
253 assert_eq!(f.as_bool(), Some(false));
254 }
255
256 #[test]
257 fn f64_round_trip_normal_values() {
258 for v in [0.0f64, 1.0, -1.0, std::f64::consts::PI, 1e100, -1e-100,
259 f64::INFINITY, f64::NEG_INFINITY] {
260 let n = SharedNaNValue::from_f64(v);
261 assert!(n.is_f64(), "{v} should be f64");
262 assert_eq!(n.as_f64(), Some(v));
263 assert_eq!(n.type_tag(), NaNValueType::F64);
264 }
265 }
266
267 #[test]
268 fn f64_nan_canonicalised() {
269 let n = SharedNaNValue::from_f64(f64::NAN);
272 assert!(n.is_f64());
273 let extracted = n.as_f64().unwrap();
274 assert!(extracted.is_nan());
275 assert_eq!(n.raw(), CANONICAL_QNAN);
276 }
277
278 #[test]
279 fn f64_with_sign_1_nan_doesnt_collide_with_boxed() {
280 let evil = f64::from_bits(0xFFF8_FFFF_FFFF_FFFF);
283 assert!(evil.is_nan());
284 let n = SharedNaNValue::from_f64(evil);
285 assert!(n.is_f64());
286 assert_eq!(n.raw(), CANONICAL_QNAN);
288 }
289
290 #[test]
291 fn offset_ptr_round_trip() {
292 let p: OffsetPtr<u64> = OffsetPtr::new(42);
293 let n = SharedNaNValue::from_offset_ptr(p);
294 assert!(n.is_offset_ptr());
295 let p2: OffsetPtr<u64> = n.as_offset_ptr().unwrap();
296 assert_eq!(p, p2);
297 assert_eq!(p2.index, 42);
298 }
299
300 #[test]
301 fn offset_ptr_phantom_type_erased_then_reconstructed() {
302 let p: OffsetPtr<u64> = OffsetPtr::new(0xABCD);
305 let n = SharedNaNValue::from_offset_ptr(p);
306 #[derive(Clone, Copy, Debug, PartialEq)]
308 struct Foo { x: u32 }
309 let p2: OffsetPtr<Foo> = n.as_offset_ptr().unwrap();
310 assert_eq!(p2.index, 0xABCD);
311 }
312
313 #[test]
314 fn raw_round_trip_preserves_value() {
315 let n = SharedNaNValue::from_i32(-7);
316 let raw = n.raw();
317 let restored = SharedNaNValue::from_raw(raw);
318 assert_eq!(n, restored);
319 assert_eq!(restored.as_i32(), Some(-7));
320 }
321
322 #[test]
323 fn type_queries_are_mutually_exclusive() {
324 let i = SharedNaNValue::from_i32(42);
325 assert!(i.is_i32());
326 assert!(!i.is_u32());
327 assert!(!i.is_bool());
328 assert!(!i.is_f64());
329 assert!(!i.is_nil());
330 assert!(!i.is_offset_ptr());
331 }
332
333 #[test]
334 fn wrong_type_extractor_returns_none() {
335 let i = SharedNaNValue::from_i32(42);
336 assert_eq!(i.as_f64(), None);
337 assert_eq!(i.as_u32(), None);
338 assert_eq!(i.as_bool(), None);
339 assert_eq!(i.as_offset_ptr::<u64>(), None);
340 }
341
342 #[test]
343 fn equality_and_hash() {
344 use std::collections::HashSet;
345 let a = SharedNaNValue::from_i32(42);
346 let b = SharedNaNValue::from_i32(42);
347 let c = SharedNaNValue::from_i32(43);
348 assert_eq!(a, b);
349 assert_ne!(a, c);
350 let mut s = HashSet::new();
351 s.insert(a);
352 assert!(s.contains(&b));
353 assert!(!s.contains(&c));
354 }
355
356 #[test]
357 fn cross_process_via_shared_hash_map() {
358 use crate::SharedHashMap;
360 let mut p = std::env::temp_dir();
361 p.push(format!("subetha-nan-shm-{}.bin", std::process::id()));
362 let m: SharedHashMap<u32, SharedNaNValue>
363 = SharedHashMap::create(&p, 16).unwrap();
364 m.insert(0, SharedNaNValue::from_i32(42)).unwrap();
365 m.insert(1, SharedNaNValue::from_f64(2.5)).unwrap();
366 m.insert(2, SharedNaNValue::from_bool(true)).unwrap();
367 m.insert(3, SharedNaNValue::NIL).unwrap();
368 m.insert(4, SharedNaNValue::from_offset_ptr::<u64>(OffsetPtr::new(99))).unwrap();
369 assert_eq!(m.get(&0).unwrap().as_i32(), Some(42));
370 assert_eq!(m.get(&1).unwrap().as_f64(), Some(2.5));
371 assert_eq!(m.get(&2).unwrap().as_bool(), Some(true));
372 assert!(m.get(&3).unwrap().is_nil());
373 let ptr: OffsetPtr<u64> = m.get(&4).unwrap().as_offset_ptr().unwrap();
374 assert_eq!(ptr.index, 99);
375 std::fs::remove_file(&p).ok();
376 }
377
378 #[test]
379 fn size_is_8_bytes() {
380 assert_eq!(std::mem::size_of::<SharedNaNValue>(), 8);
381 }
382
383 #[test]
384 fn reserved_tags_decode_as_reserved() {
385 let raw = pack(6, 0); let n = SharedNaNValue::from_raw(raw);
387 assert_eq!(n.type_tag(), NaNValueType::Reserved(6));
388 }
389}