seq_runtime/
nanbox.rs

1//! NaN-Boxing Implementation
2//!
3//! Encodes Seq values into 8 bytes using IEEE 754 NaN-boxing.
4//! This reduces Value size from 40 bytes to 8 bytes, improving cache
5//! utilization and reducing memory bandwidth.
6//!
7//! ## Encoding Scheme
8//!
9//! IEEE 754 doubles use specific bit patterns for NaN. We encode non-float
10//! values in the negative quiet NaN space (>= 0xFFF8_0000_0000_0000):
11//!
12//! ```text
13//! Float (normal):    [any valid IEEE 754 double below 0xFFF8_0000_0000_0000]
14//! Boxed values:      0xFFF8 + (tag << 47) + payload
15//!                    Bits 63:51 = 0x1FFF (negative quiet NaN)
16//!                    Bits 50:47 = 4-bit type tag (0-15)
17//!                    Bits 46:0  = 47-bit payload
18//! ```
19//!
20//! ## Type Tags
21//!
22//! - 0x0: Int (47-bit signed integer, range ~±70 trillion)
23//! - 0x1: Bool (0 or 1 in low bit)
24//! - 0x2: String (47-bit pointer to SeqString)
25//! - 0x3: Symbol (47-bit pointer to SeqString)
26//! - 0x4: Variant (47-bit pointer to Arc<VariantData>)
27//! - 0x5: Map (47-bit pointer to Box<HashMap>)
28//! - 0x6: Quotation (47-bit pointer to QuotationData)
29//! - 0x7: Closure (47-bit pointer to ClosureData)
30//! - 0x8: Channel (47-bit pointer to Arc<ChannelData>)
31//! - 0x9: WeaveCtx (47-bit pointer to WeaveCtxData)
32//!
33//! ## Float Handling
34//!
35//! Real float values are stored directly as IEEE 754 doubles. To distinguish
36//! them from boxed values, we canonicalize NaN results to a specific pattern
37//! that doesn't collide with our tagged encoding.
38
39use std::collections::HashMap;
40use std::sync::Arc;
41
42use crate::seqstring::SeqString;
43use crate::value::{ChannelData, MapKey, Value, VariantData, WeaveChannelData};
44
45// =============================================================================
46// Constants
47// =============================================================================
48
49/// We use negative quiet NaNs (sign bit + exponent all 1s + quiet bit) for boxing.
50/// Any value >= this threshold is a boxed value, not a float.
51/// This leaves all positive floats (including +inf, +NaN) untouched.
52///
53/// Bit layout for negative quiet NaN:
54/// - Bit 63: 1 (sign, negative)
55/// - Bits 62:52: 0x7FF (exponent, all 1s)
56/// - Bit 51: 1 (quiet bit)
57/// - Bits 50:0: 51 bits available for tag + payload
58///
59/// We use: bits 50:47 for 4-bit tag, bits 46:0 for 47-bit payload
60const NANBOX_THRESHOLD: u64 = 0xFFF8_0000_0000_0000;
61
62/// Base value for boxed types (negative quiet NaN with quiet bit set)
63const NANBOX_BASE: u64 = 0xFFF8_0000_0000_0000;
64
65/// Mask for the 4-bit type tag (stored in bits 50:47)
66const TAG_MASK: u64 = 0x0007_8000_0000_0000;
67
68/// Shift amount for the type tag (47 bits)
69const TAG_SHIFT: u32 = 47;
70
71/// Mask for the 47-bit payload (stored in bits 46:0)
72/// 47 bits is enough for x86_64 user-space pointers (48-bit virtual address space,
73/// but user space is limited to 47 bits with the high bit being 0)
74const PAYLOAD_MASK: u64 = 0x0000_7FFF_FFFF_FFFF;
75
76/// Canonical NaN value (used when float operations produce NaN)
77/// This is a positive quiet NaN that doesn't collide with our boxed encoding
78pub const CANONICAL_NAN: u64 = 0x7FF8_0000_0000_0000;
79
80/// Maximum 47-bit signed integer: 2^46 - 1 = 70,368,744,177,663 (~70 trillion)
81pub const MAX_NANBOX_INT: i64 = (1i64 << 46) - 1;
82
83/// Minimum 47-bit signed integer: -2^46 = -70,368,744,177,664
84pub const MIN_NANBOX_INT: i64 = -(1i64 << 46);
85
86// =============================================================================
87// Type Tags
88// =============================================================================
89
90/// Type tags for NaN-boxed values
91#[repr(u8)]
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub enum NanBoxTag {
94    Int = 0,
95    Bool = 1,
96    String = 2,
97    Symbol = 3,
98    Variant = 4,
99    Map = 5,
100    Quotation = 6,
101    Closure = 7,
102    Channel = 8,
103    WeaveCtx = 9,
104}
105
106// =============================================================================
107// Heap-Allocated Data Types
108// =============================================================================
109
110/// Quotation data stored on the heap for NaN-boxing
111///
112/// In the 40-byte Value, Quotation stored two pointers inline.
113/// With NaN-boxing, we need to heap-allocate this struct.
114#[repr(C)]
115#[derive(Debug, Clone)]
116pub struct QuotationData {
117    /// C-convention wrapper function pointer (for runtime calls)
118    pub wrapper: usize,
119    /// tailcc implementation function pointer (for musttail from compiled code)
120    pub impl_: usize,
121}
122
123/// Closure data stored on the heap for NaN-boxing
124///
125/// In the 40-byte Value, Closure stored fn_ptr and env pointer inline.
126/// With NaN-boxing, we heap-allocate this struct.
127#[repr(C)]
128#[derive(Debug, Clone)]
129pub struct ClosureData {
130    /// Function pointer
131    pub fn_ptr: usize,
132    /// Captured environment (Arc for shared ownership)
133    pub env: Arc<[Value]>,
134}
135
136/// Weave context data stored on the heap for NaN-boxing
137///
138/// In the 40-byte Value, WeaveCtx stored two Arc pointers inline.
139/// With NaN-boxing, we heap-allocate this struct.
140#[repr(C)]
141#[derive(Debug, Clone)]
142pub struct WeaveCtxData {
143    /// Channel for yielding values from weave to consumer
144    pub yield_chan: Arc<WeaveChannelData>,
145    /// Channel for resuming with values from consumer to weave
146    pub resume_chan: Arc<WeaveChannelData>,
147}
148
149// =============================================================================
150// NanBoxedValue
151// =============================================================================
152
153/// An 8-byte NaN-boxed value
154///
155/// This is the core type for the NaN-boxing optimization. All Seq values
156/// are encoded into exactly 8 bytes.
157#[repr(transparent)]
158#[derive(Clone, Copy)]
159pub struct NanBoxedValue(u64);
160
161impl NanBoxedValue {
162    // =========================================================================
163    // Type Checking
164    // =========================================================================
165
166    /// Check if this value is a float (not in the NaN-box range)
167    #[inline(always)]
168    pub fn is_float(self) -> bool {
169        // A value is a float if it's below our boxed threshold
170        // All positive floats (including +inf, +NaN) are below 0xFFFC...
171        self.0 < NANBOX_THRESHOLD
172    }
173
174    /// Check if this value is a boxed (non-float) value
175    #[inline(always)]
176    pub fn is_boxed(self) -> bool {
177        self.0 >= NANBOX_THRESHOLD
178    }
179
180    /// Get the type tag (only valid if is_boxed() is true)
181    #[inline(always)]
182    pub fn tag(self) -> u8 {
183        debug_assert!(self.is_boxed(), "tag() called on float value");
184        // Tag is in bits 50:47
185        ((self.0 & TAG_MASK) >> TAG_SHIFT) as u8
186    }
187
188    /// Get the 47-bit payload (only valid if is_boxed() is true)
189    #[inline(always)]
190    pub fn payload(self) -> u64 {
191        debug_assert!(self.is_boxed(), "payload() called on float value");
192        self.0 & PAYLOAD_MASK
193    }
194
195    /// Check if this is an Int
196    #[inline(always)]
197    pub fn is_int(self) -> bool {
198        self.is_boxed() && self.tag() == NanBoxTag::Int as u8
199    }
200
201    /// Check if this is a Bool
202    #[inline(always)]
203    pub fn is_bool(self) -> bool {
204        self.is_boxed() && self.tag() == NanBoxTag::Bool as u8
205    }
206
207    /// Check if this is a String
208    #[inline(always)]
209    pub fn is_string(self) -> bool {
210        self.is_boxed() && self.tag() == NanBoxTag::String as u8
211    }
212
213    /// Check if this is a Symbol
214    #[inline(always)]
215    pub fn is_symbol(self) -> bool {
216        self.is_boxed() && self.tag() == NanBoxTag::Symbol as u8
217    }
218
219    /// Check if this is a Variant
220    #[inline(always)]
221    pub fn is_variant(self) -> bool {
222        self.is_boxed() && self.tag() == NanBoxTag::Variant as u8
223    }
224
225    /// Check if this is a Map
226    #[inline(always)]
227    pub fn is_map(self) -> bool {
228        self.is_boxed() && self.tag() == NanBoxTag::Map as u8
229    }
230
231    /// Check if this is a Quotation
232    #[inline(always)]
233    pub fn is_quotation(self) -> bool {
234        self.is_boxed() && self.tag() == NanBoxTag::Quotation as u8
235    }
236
237    /// Check if this is a Closure
238    #[inline(always)]
239    pub fn is_closure(self) -> bool {
240        self.is_boxed() && self.tag() == NanBoxTag::Closure as u8
241    }
242
243    /// Check if this is a Channel
244    #[inline(always)]
245    pub fn is_channel(self) -> bool {
246        self.is_boxed() && self.tag() == NanBoxTag::Channel as u8
247    }
248
249    /// Check if this is a WeaveCtx
250    #[inline(always)]
251    pub fn is_weave_ctx(self) -> bool {
252        self.is_boxed() && self.tag() == NanBoxTag::WeaveCtx as u8
253    }
254
255    // =========================================================================
256    // Encoding (Creating NanBoxedValue)
257    // =========================================================================
258
259    /// Helper to create a boxed value from tag and payload
260    #[inline(always)]
261    fn make_boxed(tag: NanBoxTag, payload: u64) -> Self {
262        // Encoding: 0xFFF8 in bits 63:51, tag in bits 50:47, payload in bits 46:0
263        debug_assert!(
264            payload <= PAYLOAD_MASK,
265            "Payload 0x{:x} exceeds 47-bit limit",
266            payload
267        );
268        NanBoxedValue(NANBOX_BASE | ((tag as u64) << TAG_SHIFT) | payload)
269    }
270
271    /// Create a NaN-boxed float
272    ///
273    /// If the float is NaN, it's canonicalized to avoid collision with boxed values.
274    #[inline(always)]
275    pub fn from_float(f: f64) -> Self {
276        let bits = f.to_bits();
277        // Check if this is a NaN that could collide with our boxed range
278        if bits >= NANBOX_THRESHOLD {
279            // Canonicalize to a safe NaN
280            NanBoxedValue(CANONICAL_NAN)
281        } else {
282            NanBoxedValue(bits)
283        }
284    }
285
286    /// Create a NaN-boxed integer
287    ///
288    /// # Panics
289    /// Panics if the integer is outside the 47-bit signed range.
290    #[inline(always)]
291    pub fn from_int(n: i64) -> Self {
292        debug_assert!(
293            (MIN_NANBOX_INT..=MAX_NANBOX_INT).contains(&n),
294            "Integer {} outside NaN-boxing range [{}, {}]",
295            n,
296            MIN_NANBOX_INT,
297            MAX_NANBOX_INT
298        );
299        // Sign-extend handling: mask to 48 bits
300        let payload = (n as u64) & PAYLOAD_MASK;
301        Self::make_boxed(NanBoxTag::Int, payload)
302    }
303
304    /// Create a NaN-boxed integer, returning None if out of range
305    #[inline(always)]
306    pub fn try_from_int(n: i64) -> Option<Self> {
307        if (MIN_NANBOX_INT..=MAX_NANBOX_INT).contains(&n) {
308            Some(Self::from_int(n))
309        } else {
310            None
311        }
312    }
313
314    /// Create a NaN-boxed boolean
315    #[inline(always)]
316    pub fn from_bool(b: bool) -> Self {
317        let payload = if b { 1 } else { 0 };
318        Self::make_boxed(NanBoxTag::Bool, payload)
319    }
320
321    /// Create a NaN-boxed string pointer
322    ///
323    /// # Safety
324    /// The pointer must be valid and properly aligned.
325    #[inline(always)]
326    pub fn from_string_ptr(ptr: *const SeqString) -> Self {
327        let payload = (ptr as u64) & PAYLOAD_MASK;
328        debug_assert_eq!(
329            payload, ptr as u64,
330            "String pointer exceeds 47-bit address space"
331        );
332        Self::make_boxed(NanBoxTag::String, payload)
333    }
334
335    /// Create a NaN-boxed symbol pointer
336    ///
337    /// # Safety
338    /// The pointer must be valid and properly aligned.
339    #[inline(always)]
340    pub fn from_symbol_ptr(ptr: *const SeqString) -> Self {
341        let payload = (ptr as u64) & PAYLOAD_MASK;
342        debug_assert_eq!(
343            payload, ptr as u64,
344            "Symbol pointer exceeds 47-bit address space"
345        );
346        Self::make_boxed(NanBoxTag::Symbol, payload)
347    }
348
349    /// Create a NaN-boxed variant pointer
350    #[inline(always)]
351    pub fn from_variant_ptr(ptr: *const Arc<VariantData>) -> Self {
352        let payload = (ptr as u64) & PAYLOAD_MASK;
353        debug_assert_eq!(
354            payload, ptr as u64,
355            "Variant pointer exceeds 47-bit address space"
356        );
357        Self::make_boxed(NanBoxTag::Variant, payload)
358    }
359
360    /// Create a NaN-boxed map pointer
361    #[inline(always)]
362    pub fn from_map_ptr(ptr: *const Box<HashMap<MapKey, Value>>) -> Self {
363        let payload = (ptr as u64) & PAYLOAD_MASK;
364        debug_assert_eq!(
365            payload, ptr as u64,
366            "Map pointer exceeds 47-bit address space"
367        );
368        Self::make_boxed(NanBoxTag::Map, payload)
369    }
370
371    /// Create a NaN-boxed quotation pointer
372    #[inline(always)]
373    pub fn from_quotation_ptr(ptr: *const QuotationData) -> Self {
374        let payload = (ptr as u64) & PAYLOAD_MASK;
375        debug_assert_eq!(
376            payload, ptr as u64,
377            "Quotation pointer exceeds 47-bit address space"
378        );
379        Self::make_boxed(NanBoxTag::Quotation, payload)
380    }
381
382    /// Create a NaN-boxed closure pointer
383    #[inline(always)]
384    pub fn from_closure_ptr(ptr: *const ClosureData) -> Self {
385        let payload = (ptr as u64) & PAYLOAD_MASK;
386        debug_assert_eq!(
387            payload, ptr as u64,
388            "Closure pointer exceeds 47-bit address space"
389        );
390        Self::make_boxed(NanBoxTag::Closure, payload)
391    }
392
393    /// Create a NaN-boxed channel pointer
394    #[inline(always)]
395    pub fn from_channel_ptr(ptr: *const Arc<ChannelData>) -> Self {
396        let payload = (ptr as u64) & PAYLOAD_MASK;
397        debug_assert_eq!(
398            payload, ptr as u64,
399            "Channel pointer exceeds 47-bit address space"
400        );
401        Self::make_boxed(NanBoxTag::Channel, payload)
402    }
403
404    /// Create a NaN-boxed weave context pointer
405    #[inline(always)]
406    pub fn from_weave_ctx_ptr(ptr: *const WeaveCtxData) -> Self {
407        let payload = (ptr as u64) & PAYLOAD_MASK;
408        debug_assert_eq!(
409            payload, ptr as u64,
410            "WeaveCtx pointer exceeds 47-bit address space"
411        );
412        Self::make_boxed(NanBoxTag::WeaveCtx, payload)
413    }
414
415    // =========================================================================
416    // Decoding (Extracting values)
417    // =========================================================================
418
419    /// Extract a float value
420    ///
421    /// # Panics
422    /// Panics in debug mode if this is not a float.
423    #[inline(always)]
424    pub fn as_float(self) -> f64 {
425        debug_assert!(self.is_float(), "as_float() called on non-float value");
426        f64::from_bits(self.0)
427    }
428
429    /// Extract an integer value
430    ///
431    /// # Panics
432    /// Panics in debug mode if this is not an integer.
433    #[inline(always)]
434    pub fn as_int(self) -> i64 {
435        debug_assert!(self.is_int(), "as_int() called on non-int value");
436        // Sign-extend from 47 bits to 64 bits
437        let payload = self.payload();
438        // Check if the sign bit (bit 46) is set
439        if payload & (1 << 46) != 0 {
440            // Negative: sign-extend by setting upper 17 bits
441            (payload | 0xFFFF_8000_0000_0000) as i64
442        } else {
443            payload as i64
444        }
445    }
446
447    /// Extract a boolean value
448    ///
449    /// # Panics
450    /// Panics in debug mode if this is not a boolean.
451    #[inline(always)]
452    pub fn as_bool(self) -> bool {
453        debug_assert!(self.is_bool(), "as_bool() called on non-bool value");
454        self.payload() != 0
455    }
456
457    /// Extract a string pointer
458    ///
459    /// # Safety
460    /// The caller must ensure this is a String value.
461    #[inline(always)]
462    pub unsafe fn as_string_ptr(self) -> *const SeqString {
463        debug_assert!(
464            self.is_string(),
465            "as_string_ptr() called on non-string value"
466        );
467        self.payload() as *const SeqString
468    }
469
470    /// Extract a symbol pointer
471    ///
472    /// # Safety
473    /// The caller must ensure this is a Symbol value.
474    #[inline(always)]
475    pub unsafe fn as_symbol_ptr(self) -> *const SeqString {
476        debug_assert!(
477            self.is_symbol(),
478            "as_symbol_ptr() called on non-symbol value"
479        );
480        self.payload() as *const SeqString
481    }
482
483    /// Extract a variant pointer
484    ///
485    /// # Safety
486    /// The caller must ensure this is a Variant value.
487    #[inline(always)]
488    pub unsafe fn as_variant_ptr(self) -> *const Arc<VariantData> {
489        debug_assert!(
490            self.is_variant(),
491            "as_variant_ptr() called on non-variant value"
492        );
493        self.payload() as *const Arc<VariantData>
494    }
495
496    /// Extract a map pointer
497    ///
498    /// # Safety
499    /// The caller must ensure this is a Map value.
500    #[inline(always)]
501    pub unsafe fn as_map_ptr(self) -> *const Box<HashMap<MapKey, Value>> {
502        debug_assert!(self.is_map(), "as_map_ptr() called on non-map value");
503        self.payload() as *const Box<HashMap<MapKey, Value>>
504    }
505
506    /// Extract a quotation pointer
507    ///
508    /// # Safety
509    /// The caller must ensure this is a Quotation value.
510    #[inline(always)]
511    pub unsafe fn as_quotation_ptr(self) -> *const QuotationData {
512        debug_assert!(
513            self.is_quotation(),
514            "as_quotation_ptr() called on non-quotation value"
515        );
516        self.payload() as *const QuotationData
517    }
518
519    /// Extract a closure pointer
520    ///
521    /// # Safety
522    /// The caller must ensure this is a Closure value.
523    #[inline(always)]
524    pub unsafe fn as_closure_ptr(self) -> *const ClosureData {
525        debug_assert!(
526            self.is_closure(),
527            "as_closure_ptr() called on non-closure value"
528        );
529        self.payload() as *const ClosureData
530    }
531
532    /// Extract a channel pointer
533    ///
534    /// # Safety
535    /// The caller must ensure this is a Channel value.
536    #[inline(always)]
537    pub unsafe fn as_channel_ptr(self) -> *const Arc<ChannelData> {
538        debug_assert!(
539            self.is_channel(),
540            "as_channel_ptr() called on non-channel value"
541        );
542        self.payload() as *const Arc<ChannelData>
543    }
544
545    /// Extract a weave context pointer
546    ///
547    /// # Safety
548    /// The caller must ensure this is a WeaveCtx value.
549    #[inline(always)]
550    pub unsafe fn as_weave_ctx_ptr(self) -> *const WeaveCtxData {
551        debug_assert!(
552            self.is_weave_ctx(),
553            "as_weave_ctx_ptr() called on non-weave_ctx value"
554        );
555        self.payload() as *const WeaveCtxData
556    }
557
558    // =========================================================================
559    // Raw Access
560    // =========================================================================
561
562    /// Get the raw 64-bit representation
563    #[inline(always)]
564    pub fn to_bits(self) -> u64 {
565        self.0
566    }
567
568    /// Create from raw 64-bit representation
569    ///
570    /// # Safety
571    /// The caller must ensure the bits represent a valid NanBoxedValue.
572    #[inline(always)]
573    pub unsafe fn from_bits(bits: u64) -> Self {
574        NanBoxedValue(bits)
575    }
576}
577
578impl std::fmt::Debug for NanBoxedValue {
579    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
580        if self.is_float() {
581            write!(f, "Float({})", self.as_float())
582        } else {
583            match self.tag() {
584                t if t == NanBoxTag::Int as u8 => write!(f, "Int({})", self.as_int()),
585                t if t == NanBoxTag::Bool as u8 => write!(f, "Bool({})", self.as_bool()),
586                t if t == NanBoxTag::String as u8 => {
587                    write!(f, "String(0x{:012x})", self.payload())
588                }
589                t if t == NanBoxTag::Symbol as u8 => {
590                    write!(f, "Symbol(0x{:012x})", self.payload())
591                }
592                t if t == NanBoxTag::Variant as u8 => {
593                    write!(f, "Variant(0x{:012x})", self.payload())
594                }
595                t if t == NanBoxTag::Map as u8 => write!(f, "Map(0x{:012x})", self.payload()),
596                t if t == NanBoxTag::Quotation as u8 => {
597                    write!(f, "Quotation(0x{:012x})", self.payload())
598                }
599                t if t == NanBoxTag::Closure as u8 => {
600                    write!(f, "Closure(0x{:012x})", self.payload())
601                }
602                t if t == NanBoxTag::Channel as u8 => {
603                    write!(f, "Channel(0x{:012x})", self.payload())
604                }
605                t if t == NanBoxTag::WeaveCtx as u8 => {
606                    write!(f, "WeaveCtx(0x{:012x})", self.payload())
607                }
608                _ => write!(
609                    f,
610                    "Unknown(tag={}, payload=0x{:012x})",
611                    self.tag(),
612                    self.payload()
613                ),
614            }
615        }
616    }
617}
618
619impl Default for NanBoxedValue {
620    fn default() -> Self {
621        // Default to integer 0
622        Self::from_int(0)
623    }
624}
625
626// Safety: NanBoxedValue is just a u64, which is Send + Sync
627unsafe impl Send for NanBoxedValue {}
628unsafe impl Sync for NanBoxedValue {}
629
630// =============================================================================
631// Value <-> NanBoxedValue Conversions
632// =============================================================================
633
634impl NanBoxedValue {
635    /// Convert a Value to a NanBoxedValue
636    ///
637    /// This clones heap-allocated data and stores pointers in the NaN-boxed format.
638    /// The caller is responsible for ensuring the returned NanBoxedValue is properly
639    /// dropped (via `drop_nanboxed`) to avoid memory leaks.
640    ///
641    /// # Panics
642    /// Panics if an integer value is outside the 47-bit range.
643    pub fn from_value(value: &Value) -> Self {
644        match value {
645            Value::Int(n) => {
646                // Check range at runtime - in production, compiler should reject out-of-range literals
647                if *n < MIN_NANBOX_INT || *n > MAX_NANBOX_INT {
648                    panic!(
649                        "Integer {} outside NaN-boxing range [{}, {}]",
650                        n, MIN_NANBOX_INT, MAX_NANBOX_INT
651                    );
652                }
653                Self::from_int(*n)
654            }
655            Value::Float(f) => Self::from_float(*f),
656            Value::Bool(b) => Self::from_bool(*b),
657            Value::String(s) => {
658                // Clone the SeqString and leak it
659                let boxed = Box::new(s.clone());
660                let ptr = Box::into_raw(boxed);
661                Self::from_string_ptr(ptr)
662            }
663            Value::Symbol(s) => {
664                let boxed = Box::new(s.clone());
665                let ptr = Box::into_raw(boxed);
666                Self::from_symbol_ptr(ptr)
667            }
668            Value::Variant(arc) => {
669                // Clone the Arc and leak it
670                let arc_clone = arc.clone();
671                let boxed = Box::new(arc_clone);
672                let ptr = Box::into_raw(boxed);
673                Self::from_variant_ptr(ptr)
674            }
675            Value::Map(map) => {
676                // Clone the map and leak it
677                let map_clone = map.clone();
678                let boxed = Box::new(map_clone);
679                let ptr = Box::into_raw(boxed);
680                Self::from_map_ptr(ptr)
681            }
682            Value::Quotation { wrapper, impl_ } => {
683                let data = Box::new(QuotationData {
684                    wrapper: *wrapper,
685                    impl_: *impl_,
686                });
687                let ptr = Box::into_raw(data);
688                Self::from_quotation_ptr(ptr)
689            }
690            Value::Closure { fn_ptr, env } => {
691                let data = Box::new(ClosureData {
692                    fn_ptr: *fn_ptr,
693                    env: env.clone(),
694                });
695                let ptr = Box::into_raw(data);
696                Self::from_closure_ptr(ptr)
697            }
698            Value::Channel(arc) => {
699                let arc_clone = arc.clone();
700                let boxed = Box::new(arc_clone);
701                let ptr = Box::into_raw(boxed);
702                Self::from_channel_ptr(ptr)
703            }
704            Value::WeaveCtx {
705                yield_chan,
706                resume_chan,
707            } => {
708                let data = Box::new(WeaveCtxData {
709                    yield_chan: yield_chan.clone(),
710                    resume_chan: resume_chan.clone(),
711                });
712                let ptr = Box::into_raw(data);
713                Self::from_weave_ctx_ptr(ptr)
714            }
715        }
716    }
717
718    /// Convert a NanBoxedValue back to a Value
719    ///
720    /// This reconstructs the Value from the NaN-boxed representation.
721    /// For heap-allocated types, this takes ownership of the underlying memory.
722    ///
723    /// # Safety
724    /// The NanBoxedValue must have been created by `from_value` and not yet
725    /// converted back or dropped. Each NanBoxedValue should only be converted
726    /// to Value once.
727    pub unsafe fn to_value(self) -> Value {
728        if self.is_float() {
729            return Value::Float(self.as_float());
730        }
731
732        match self.tag() {
733            t if t == NanBoxTag::Int as u8 => Value::Int(self.as_int()),
734            t if t == NanBoxTag::Bool as u8 => Value::Bool(self.as_bool()),
735            t if t == NanBoxTag::String as u8 => unsafe {
736                let ptr = self.as_string_ptr() as *mut SeqString;
737                let boxed = Box::from_raw(ptr);
738                Value::String(*boxed)
739            },
740            t if t == NanBoxTag::Symbol as u8 => unsafe {
741                let ptr = self.as_symbol_ptr() as *mut SeqString;
742                let boxed = Box::from_raw(ptr);
743                Value::Symbol(*boxed)
744            },
745            t if t == NanBoxTag::Variant as u8 => unsafe {
746                let ptr = self.as_variant_ptr() as *mut Arc<VariantData>;
747                let boxed = Box::from_raw(ptr);
748                Value::Variant(*boxed)
749            },
750            t if t == NanBoxTag::Map as u8 => unsafe {
751                let ptr = self.as_map_ptr() as *mut Box<HashMap<MapKey, Value>>;
752                let boxed = Box::from_raw(ptr);
753                Value::Map(*boxed)
754            },
755            t if t == NanBoxTag::Quotation as u8 => unsafe {
756                let ptr = self.as_quotation_ptr() as *mut QuotationData;
757                let data = Box::from_raw(ptr);
758                Value::Quotation {
759                    wrapper: data.wrapper,
760                    impl_: data.impl_,
761                }
762            },
763            t if t == NanBoxTag::Closure as u8 => unsafe {
764                let ptr = self.as_closure_ptr() as *mut ClosureData;
765                let data = Box::from_raw(ptr);
766                Value::Closure {
767                    fn_ptr: data.fn_ptr,
768                    env: data.env,
769                }
770            },
771            t if t == NanBoxTag::Channel as u8 => unsafe {
772                let ptr = self.as_channel_ptr() as *mut Arc<ChannelData>;
773                let boxed = Box::from_raw(ptr);
774                Value::Channel(*boxed)
775            },
776            t if t == NanBoxTag::WeaveCtx as u8 => unsafe {
777                let ptr = self.as_weave_ctx_ptr() as *mut WeaveCtxData;
778                let data = Box::from_raw(ptr);
779                Value::WeaveCtx {
780                    yield_chan: data.yield_chan,
781                    resume_chan: data.resume_chan,
782                }
783            },
784            _ => panic!("Unknown NanBoxedValue tag: {}", self.tag()),
785        }
786    }
787
788    /// Clone a NanBoxedValue, properly cloning any heap-allocated data
789    ///
790    /// For pointer types, this creates new heap allocations.
791    pub fn clone_nanboxed(&self) -> Self {
792        if self.is_float() {
793            return *self;
794        }
795
796        match self.tag() {
797            t if t == NanBoxTag::Int as u8 => *self,
798            t if t == NanBoxTag::Bool as u8 => *self,
799            t if t == NanBoxTag::String as u8 => {
800                let ptr = unsafe { self.as_string_ptr() };
801                let s = unsafe { &*ptr };
802                let boxed = Box::new(s.clone());
803                Self::from_string_ptr(Box::into_raw(boxed))
804            }
805            t if t == NanBoxTag::Symbol as u8 => {
806                let ptr = unsafe { self.as_symbol_ptr() };
807                let s = unsafe { &*ptr };
808                let boxed = Box::new(s.clone());
809                Self::from_symbol_ptr(Box::into_raw(boxed))
810            }
811            t if t == NanBoxTag::Variant as u8 => {
812                let ptr = unsafe { self.as_variant_ptr() };
813                let arc = unsafe { &*ptr };
814                let boxed = Box::new(arc.clone());
815                Self::from_variant_ptr(Box::into_raw(boxed))
816            }
817            t if t == NanBoxTag::Map as u8 => {
818                let ptr = unsafe { self.as_map_ptr() };
819                let map = unsafe { &*ptr };
820                let boxed = Box::new(map.clone());
821                Self::from_map_ptr(Box::into_raw(boxed))
822            }
823            t if t == NanBoxTag::Quotation as u8 => {
824                let ptr = unsafe { self.as_quotation_ptr() };
825                let data = unsafe { &*ptr };
826                let boxed = Box::new(data.clone());
827                Self::from_quotation_ptr(Box::into_raw(boxed))
828            }
829            t if t == NanBoxTag::Closure as u8 => {
830                let ptr = unsafe { self.as_closure_ptr() };
831                let data = unsafe { &*ptr };
832                let boxed = Box::new(data.clone());
833                Self::from_closure_ptr(Box::into_raw(boxed))
834            }
835            t if t == NanBoxTag::Channel as u8 => {
836                let ptr = unsafe { self.as_channel_ptr() };
837                let arc = unsafe { &*ptr };
838                let boxed = Box::new(arc.clone());
839                Self::from_channel_ptr(Box::into_raw(boxed))
840            }
841            t if t == NanBoxTag::WeaveCtx as u8 => {
842                let ptr = unsafe { self.as_weave_ctx_ptr() };
843                let data = unsafe { &*ptr };
844                let boxed = Box::new(data.clone());
845                Self::from_weave_ctx_ptr(Box::into_raw(boxed))
846            }
847            _ => *self, // Unknown tag, just copy bits
848        }
849    }
850
851    /// Drop a NanBoxedValue, freeing any heap-allocated data
852    ///
853    /// This must be called for NanBoxedValues that hold pointer types
854    /// to avoid memory leaks.
855    ///
856    /// # Safety
857    /// The NanBoxedValue must have been created by `from_value` or `clone_nanboxed`
858    /// and not yet dropped or converted to Value.
859    pub unsafe fn drop_nanboxed(self) {
860        if self.is_float() {
861            return;
862        }
863
864        match self.tag() {
865            t if t == NanBoxTag::Int as u8 => {}
866            t if t == NanBoxTag::Bool as u8 => {}
867            t if t == NanBoxTag::String as u8 => unsafe {
868                let ptr = self.as_string_ptr() as *mut SeqString;
869                drop(Box::from_raw(ptr));
870            },
871            t if t == NanBoxTag::Symbol as u8 => unsafe {
872                let ptr = self.as_symbol_ptr() as *mut SeqString;
873                drop(Box::from_raw(ptr));
874            },
875            t if t == NanBoxTag::Variant as u8 => unsafe {
876                let ptr = self.as_variant_ptr() as *mut Arc<VariantData>;
877                drop(Box::from_raw(ptr));
878            },
879            t if t == NanBoxTag::Map as u8 => unsafe {
880                let ptr = self.as_map_ptr() as *mut Box<HashMap<MapKey, Value>>;
881                drop(Box::from_raw(ptr));
882            },
883            t if t == NanBoxTag::Quotation as u8 => unsafe {
884                let ptr = self.as_quotation_ptr() as *mut QuotationData;
885                drop(Box::from_raw(ptr));
886            },
887            t if t == NanBoxTag::Closure as u8 => unsafe {
888                let ptr = self.as_closure_ptr() as *mut ClosureData;
889                drop(Box::from_raw(ptr));
890            },
891            t if t == NanBoxTag::Channel as u8 => unsafe {
892                let ptr = self.as_channel_ptr() as *mut Arc<ChannelData>;
893                drop(Box::from_raw(ptr));
894            },
895            t if t == NanBoxTag::WeaveCtx as u8 => unsafe {
896                let ptr = self.as_weave_ctx_ptr() as *mut WeaveCtxData;
897                drop(Box::from_raw(ptr));
898            },
899            _ => {} // Unknown tag, nothing to drop
900        }
901    }
902}
903
904// =============================================================================
905// Tests
906// =============================================================================
907
908#[cfg(test)]
909mod tests {
910    use super::*;
911
912    #[test]
913    fn test_nanboxed_value_size() {
914        assert_eq!(std::mem::size_of::<NanBoxedValue>(), 8);
915        assert_eq!(std::mem::align_of::<NanBoxedValue>(), 8);
916    }
917
918    #[test]
919    fn test_float_encoding() {
920        // Normal floats
921        let v = NanBoxedValue::from_float(2.5);
922        assert!(v.is_float());
923        assert!(!v.is_boxed());
924        assert_eq!(v.as_float(), 2.5);
925
926        // Zero
927        let v = NanBoxedValue::from_float(0.0);
928        assert!(v.is_float());
929        assert_eq!(v.as_float(), 0.0);
930
931        // Negative
932        let v = NanBoxedValue::from_float(-123.456);
933        assert!(v.is_float());
934        assert_eq!(v.as_float(), -123.456);
935
936        // Infinity
937        let v = NanBoxedValue::from_float(f64::INFINITY);
938        assert!(v.is_float());
939        assert!(v.as_float().is_infinite());
940
941        // Negative infinity
942        let v = NanBoxedValue::from_float(f64::NEG_INFINITY);
943        assert!(v.is_float());
944        assert!(v.as_float().is_infinite());
945    }
946
947    #[test]
948    fn test_nan_canonicalization() {
949        // Standard NaN should be preserved (it's outside our boxed range)
950        let v = NanBoxedValue::from_float(f64::NAN);
951        assert!(v.is_float());
952        assert!(v.as_float().is_nan());
953    }
954
955    #[test]
956    fn test_int_encoding() {
957        // Zero
958        let v = NanBoxedValue::from_int(0);
959        assert!(v.is_int());
960        assert_eq!(v.as_int(), 0);
961
962        // Positive
963        let v = NanBoxedValue::from_int(42);
964        assert!(v.is_int());
965        assert_eq!(v.as_int(), 42);
966
967        // Negative
968        let v = NanBoxedValue::from_int(-42);
969        assert!(v.is_int());
970        assert_eq!(v.as_int(), -42);
971
972        // Large positive (within 47-bit range)
973        let v = NanBoxedValue::from_int(MAX_NANBOX_INT);
974        assert!(v.is_int());
975        assert_eq!(v.as_int(), MAX_NANBOX_INT);
976
977        // Large negative (within 47-bit range)
978        let v = NanBoxedValue::from_int(MIN_NANBOX_INT);
979        assert!(v.is_int());
980        assert_eq!(v.as_int(), MIN_NANBOX_INT);
981
982        // Common values
983        let v = NanBoxedValue::from_int(1000000);
984        assert_eq!(v.as_int(), 1000000);
985
986        let v = NanBoxedValue::from_int(-1);
987        assert_eq!(v.as_int(), -1);
988    }
989
990    #[test]
991    fn test_try_from_int() {
992        // Within range
993        assert!(NanBoxedValue::try_from_int(0).is_some());
994        assert!(NanBoxedValue::try_from_int(MAX_NANBOX_INT).is_some());
995        assert!(NanBoxedValue::try_from_int(MIN_NANBOX_INT).is_some());
996
997        // Outside range
998        assert!(NanBoxedValue::try_from_int(MAX_NANBOX_INT + 1).is_none());
999        assert!(NanBoxedValue::try_from_int(MIN_NANBOX_INT - 1).is_none());
1000        assert!(NanBoxedValue::try_from_int(i64::MAX).is_none());
1001        assert!(NanBoxedValue::try_from_int(i64::MIN).is_none());
1002    }
1003
1004    #[test]
1005    fn test_bool_encoding() {
1006        let v_true = NanBoxedValue::from_bool(true);
1007        assert!(v_true.is_bool());
1008        assert!(v_true.as_bool());
1009
1010        let v_false = NanBoxedValue::from_bool(false);
1011        assert!(v_false.is_bool());
1012        assert!(!v_false.as_bool());
1013    }
1014
1015    #[test]
1016    fn test_type_discrimination() {
1017        let float = NanBoxedValue::from_float(1.0);
1018        let int = NanBoxedValue::from_int(1);
1019        let bool_val = NanBoxedValue::from_bool(true);
1020
1021        // Float checks
1022        assert!(float.is_float());
1023        assert!(!float.is_int());
1024        assert!(!float.is_bool());
1025
1026        // Int checks
1027        assert!(!int.is_float());
1028        assert!(int.is_int());
1029        assert!(!int.is_bool());
1030
1031        // Bool checks
1032        assert!(!bool_val.is_float());
1033        assert!(!bool_val.is_int());
1034        assert!(bool_val.is_bool());
1035    }
1036
1037    #[test]
1038    fn test_int_range_boundaries() {
1039        // Test values near the boundaries
1040        let near_max = MAX_NANBOX_INT - 1;
1041        let near_min = MIN_NANBOX_INT + 1;
1042
1043        let v = NanBoxedValue::from_int(near_max);
1044        assert_eq!(v.as_int(), near_max);
1045
1046        let v = NanBoxedValue::from_int(near_min);
1047        assert_eq!(v.as_int(), near_min);
1048    }
1049
1050    #[test]
1051    fn test_pointer_encoding() {
1052        // Create a test value on the heap to get a valid pointer
1053        let boxed: Box<u64> = Box::new(42);
1054        let ptr = Box::into_raw(boxed);
1055
1056        // The pointer should fit in 47 bits on x86-64 (user space is 47 bits) and ARM64
1057        let ptr_val = ptr as u64;
1058        assert!(
1059            ptr_val <= PAYLOAD_MASK,
1060            "Pointer 0x{:x} exceeds 47-bit range",
1061            ptr_val
1062        );
1063
1064        // Clean up
1065        unsafe {
1066            drop(Box::from_raw(ptr));
1067        }
1068    }
1069
1070    #[test]
1071    fn test_quotation_data_size() {
1072        // QuotationData should be 16 bytes (2 x usize on 64-bit)
1073        assert_eq!(std::mem::size_of::<QuotationData>(), 16);
1074    }
1075
1076    #[test]
1077    fn test_debug_format() {
1078        let float = NanBoxedValue::from_float(2.5);
1079        let int = NanBoxedValue::from_int(42);
1080        let bool_val = NanBoxedValue::from_bool(true);
1081
1082        assert!(format!("{:?}", float).contains("Float"));
1083        assert!(format!("{:?}", int).contains("Int(42)"));
1084        assert!(format!("{:?}", bool_val).contains("Bool(true)"));
1085    }
1086
1087    #[test]
1088    fn test_default() {
1089        let v = NanBoxedValue::default();
1090        assert!(v.is_int());
1091        assert_eq!(v.as_int(), 0);
1092    }
1093
1094    #[test]
1095    fn test_raw_bits_roundtrip() {
1096        let original = NanBoxedValue::from_int(12345);
1097        let bits = original.to_bits();
1098        let restored = unsafe { NanBoxedValue::from_bits(bits) };
1099        assert_eq!(restored.as_int(), 12345);
1100    }
1101
1102    // =========================================================================
1103    // Additional Edge Case Tests
1104    // =========================================================================
1105
1106    #[test]
1107    fn test_negative_zero() {
1108        let v = NanBoxedValue::from_float(-0.0);
1109        assert!(v.is_float());
1110        // -0.0 and 0.0 compare equal in IEEE 754
1111        assert_eq!(v.as_float(), 0.0);
1112        // But the bits are different
1113        assert_eq!(v.to_bits(), (-0.0_f64).to_bits());
1114    }
1115
1116    #[test]
1117    fn test_subnormal_floats() {
1118        // Smallest positive subnormal
1119        let smallest = f64::from_bits(1);
1120        let v = NanBoxedValue::from_float(smallest);
1121        assert!(v.is_float());
1122        assert_eq!(v.as_float().to_bits(), 1);
1123
1124        // Largest subnormal (just below smallest normal)
1125        let largest_subnormal = f64::from_bits(0x000F_FFFF_FFFF_FFFF);
1126        let v = NanBoxedValue::from_float(largest_subnormal);
1127        assert!(v.is_float());
1128        assert_eq!(v.as_float().to_bits(), 0x000F_FFFF_FFFF_FFFF);
1129    }
1130
1131    #[test]
1132    fn test_special_float_values() {
1133        // f64::MIN (largest negative)
1134        let v = NanBoxedValue::from_float(f64::MIN);
1135        assert!(v.is_float());
1136        assert_eq!(v.as_float(), f64::MIN);
1137
1138        // f64::MAX (largest positive)
1139        let v = NanBoxedValue::from_float(f64::MAX);
1140        assert!(v.is_float());
1141        assert_eq!(v.as_float(), f64::MAX);
1142
1143        // f64::MIN_POSITIVE (smallest positive normal)
1144        let v = NanBoxedValue::from_float(f64::MIN_POSITIVE);
1145        assert!(v.is_float());
1146        assert_eq!(v.as_float(), f64::MIN_POSITIVE);
1147
1148        // f64::EPSILON
1149        let v = NanBoxedValue::from_float(f64::EPSILON);
1150        assert!(v.is_float());
1151        assert_eq!(v.as_float(), f64::EPSILON);
1152    }
1153
1154    #[test]
1155    fn test_negative_infinity() {
1156        let v = NanBoxedValue::from_float(f64::NEG_INFINITY);
1157        assert!(v.is_float());
1158        assert!(!v.is_boxed());
1159        assert!(v.as_float().is_infinite());
1160        assert!(v.as_float().is_sign_negative());
1161    }
1162
1163    #[test]
1164    fn test_large_negative_floats_not_boxed() {
1165        // Large negative floats should NOT be treated as boxed values
1166        // even though their bit patterns have the sign bit set
1167        let values = [-1.0e308, -1.0e100, -1.0e50, -1.0, -0.5, -f64::MIN_POSITIVE];
1168
1169        for &f in &values {
1170            let v = NanBoxedValue::from_float(f);
1171            assert!(
1172                v.is_float(),
1173                "Float {} should be recognized as float, not boxed (bits: 0x{:016x})",
1174                f,
1175                f.to_bits()
1176            );
1177            assert_eq!(v.as_float(), f);
1178        }
1179    }
1180
1181    #[test]
1182    fn test_no_float_boxed_collision() {
1183        // Verify that normal float values don't collide with our boxed range.
1184        // The threshold is 0xFFF8_0000_0000_0000, so negative quiet NaNs
1185        // (which start at 0xFFF8...) are canonicalized by from_float().
1186
1187        // Negative infinity: 0xFFF0_0000_0000_0000 - below threshold
1188        assert!(f64::NEG_INFINITY.to_bits() < NANBOX_THRESHOLD);
1189
1190        // Largest negative normal: 0xFFEF_FFFF_FFFF_FFFF - below threshold
1191        assert!(f64::MIN.to_bits() < NANBOX_THRESHOLD);
1192
1193        // -1.0: 0xBFF0_0000_0000_0000 - way below threshold
1194        assert!((-1.0_f64).to_bits() < NANBOX_THRESHOLD);
1195
1196        // Negative quiet NaN starts at 0xFFF8... which IS our threshold.
1197        // Such NaNs get canonicalized by from_float() to avoid collision.
1198        let neg_qnan = f64::from_bits(0xFFF8_0000_0000_0000);
1199        assert!(neg_qnan.to_bits() >= NANBOX_THRESHOLD);
1200
1201        // Verify canonicalization works
1202        let boxed = NanBoxedValue::from_float(neg_qnan);
1203        assert!(boxed.is_float());
1204        assert_eq!(boxed.to_bits(), CANONICAL_NAN);
1205    }
1206
1207    #[test]
1208    fn test_all_tags_discriminate() {
1209        // Create one value of each boxed type and verify they're correctly discriminated
1210        let int = NanBoxedValue::from_int(0);
1211        let bool_v = NanBoxedValue::from_bool(false);
1212
1213        // Verify each has correct tag
1214        assert_eq!(int.tag(), NanBoxTag::Int as u8);
1215        assert_eq!(bool_v.tag(), NanBoxTag::Bool as u8);
1216
1217        // Verify they don't match other types
1218        assert!(int.is_int());
1219        assert!(!int.is_bool());
1220        assert!(!int.is_string());
1221
1222        assert!(bool_v.is_bool());
1223        assert!(!bool_v.is_int());
1224        assert!(!bool_v.is_string());
1225    }
1226
1227    #[test]
1228    fn test_encoding_bit_patterns() {
1229        // Verify specific bit patterns for debugging
1230        // Encoding: 0xFFF8 base (bits 63:51), tag in bits 50:47, payload in bits 46:0
1231
1232        // Int 0: tag=0, payload=0
1233        let int_zero = NanBoxedValue::from_int(0);
1234        let bits = int_zero.to_bits();
1235        assert_eq!(
1236            bits >> 51,
1237            0x1FFF,
1238            "All boxed values have 0x1FFF in high 13 bits (negative quiet NaN)"
1239        );
1240        assert_eq!(int_zero.tag(), 0, "Int should have tag 0");
1241        assert_eq!(bits & PAYLOAD_MASK, 0, "Int(0) should have payload 0");
1242
1243        // Bool true: tag=1, payload=1
1244        let bool_true = NanBoxedValue::from_bool(true);
1245        let bits = bool_true.to_bits();
1246        assert_eq!(
1247            bits >> 51,
1248            0x1FFF,
1249            "All boxed values have 0x1FFF in high 13 bits (negative quiet NaN)"
1250        );
1251        assert_eq!(bool_true.tag(), 1, "Bool should have tag 1");
1252        assert_eq!(bits & PAYLOAD_MASK, 1, "Bool(true) should have payload 1");
1253
1254        // Int 42: tag=0, payload=42
1255        let int_42 = NanBoxedValue::from_int(42);
1256        let bits = int_42.to_bits();
1257        assert_eq!(bits >> 51, 0x1FFF);
1258        assert_eq!(int_42.tag(), 0);
1259        assert_eq!(bits & PAYLOAD_MASK, 42);
1260
1261        // Verify tag is in correct position (bits 50:47)
1262        // Bool(true) should be: 0xFFF8_0000_0000_0000 | (1 << 47) | 1 = 0xFFF8_8000_0000_0001
1263        let expected_bool_bits = 0xFFF8_0000_0000_0000_u64 | (1_u64 << 47) | 1;
1264        assert_eq!(
1265            bool_true.to_bits(),
1266            expected_bool_bits,
1267            "Bool(true) bit pattern should be 0xFFF8_8000_0000_0001"
1268        );
1269    }
1270
1271    #[test]
1272    fn test_negative_int_sign_extension() {
1273        // Verify negative integers are properly sign-extended when decoded
1274
1275        let v = NanBoxedValue::from_int(-1);
1276        assert_eq!(v.as_int(), -1);
1277
1278        let v = NanBoxedValue::from_int(-100);
1279        assert_eq!(v.as_int(), -100);
1280
1281        let v = NanBoxedValue::from_int(-1000000);
1282        assert_eq!(v.as_int(), -1000000);
1283
1284        // Test the boundary values
1285        let v = NanBoxedValue::from_int(MIN_NANBOX_INT);
1286        assert_eq!(v.as_int(), MIN_NANBOX_INT);
1287        assert!(v.as_int() < 0);
1288    }
1289
1290    #[test]
1291    fn test_closure_and_weave_data_sizes() {
1292        // Verify our heap-allocated structs have expected sizes
1293        assert_eq!(std::mem::size_of::<QuotationData>(), 16); // 2 x usize
1294        // ClosureData has fn_ptr (usize) + Arc<[Value]> (2 x usize for fat pointer)
1295        assert!(std::mem::size_of::<ClosureData>() >= 24);
1296        // WeaveCtxData has 2 x Arc<WeaveChannelData>
1297        assert!(std::mem::size_of::<WeaveCtxData>() >= 16);
1298    }
1299
1300    // =========================================================================
1301    // Value <-> NanBoxedValue Conversion Tests
1302    // =========================================================================
1303
1304    #[test]
1305    fn test_value_int_roundtrip() {
1306        let original = Value::Int(42);
1307        let nb = NanBoxedValue::from_value(&original);
1308        let restored = unsafe { nb.to_value() };
1309        assert_eq!(restored, original);
1310
1311        // Negative
1312        let original = Value::Int(-12345);
1313        let nb = NanBoxedValue::from_value(&original);
1314        let restored = unsafe { nb.to_value() };
1315        assert_eq!(restored, original);
1316    }
1317
1318    #[test]
1319    fn test_value_float_roundtrip() {
1320        let original = Value::Float(std::f64::consts::PI);
1321        let nb = NanBoxedValue::from_value(&original);
1322        let restored = unsafe { nb.to_value() };
1323        assert_eq!(restored, original);
1324
1325        // Negative
1326        let original = Value::Float(-std::f64::consts::E);
1327        let nb = NanBoxedValue::from_value(&original);
1328        let restored = unsafe { nb.to_value() };
1329        assert_eq!(restored, original);
1330    }
1331
1332    #[test]
1333    fn test_value_bool_roundtrip() {
1334        let original = Value::Bool(true);
1335        let nb = NanBoxedValue::from_value(&original);
1336        let restored = unsafe { nb.to_value() };
1337        assert_eq!(restored, original);
1338
1339        let original = Value::Bool(false);
1340        let nb = NanBoxedValue::from_value(&original);
1341        let restored = unsafe { nb.to_value() };
1342        assert_eq!(restored, original);
1343    }
1344
1345    #[test]
1346    fn test_value_string_roundtrip() {
1347        let original = Value::String(SeqString::from("hello world"));
1348        let nb = NanBoxedValue::from_value(&original);
1349        let restored = unsafe { nb.to_value() };
1350        assert_eq!(restored, original);
1351    }
1352
1353    #[test]
1354    fn test_value_symbol_roundtrip() {
1355        let original = Value::Symbol(SeqString::from("my-symbol"));
1356        let nb = NanBoxedValue::from_value(&original);
1357        let restored = unsafe { nb.to_value() };
1358        assert_eq!(restored, original);
1359    }
1360
1361    #[test]
1362    fn test_value_quotation_roundtrip() {
1363        let original = Value::Quotation {
1364            wrapper: 0x1234,
1365            impl_: 0x5678,
1366        };
1367        let nb = NanBoxedValue::from_value(&original);
1368        let restored = unsafe { nb.to_value() };
1369        assert_eq!(restored, original);
1370    }
1371
1372    #[test]
1373    fn test_clone_nanboxed_int() {
1374        let original = NanBoxedValue::from_int(42);
1375        let cloned = original.clone_nanboxed();
1376        assert_eq!(cloned.as_int(), 42);
1377        // Both should be valid (no double-free for primitives)
1378        assert_eq!(original.as_int(), 42);
1379    }
1380
1381    #[test]
1382    fn test_clone_nanboxed_string() {
1383        let value = Value::String(SeqString::from("test"));
1384        let nb = NanBoxedValue::from_value(&value);
1385        let cloned = nb.clone_nanboxed();
1386
1387        // Both should be valid and contain the same string
1388        let restored1 = unsafe { nb.to_value() };
1389        let restored2 = unsafe { cloned.to_value() };
1390
1391        assert_eq!(restored1, value);
1392        assert_eq!(restored2, value);
1393    }
1394
1395    #[test]
1396    fn test_drop_nanboxed_string() {
1397        let value = Value::String(SeqString::from("test"));
1398        let nb = NanBoxedValue::from_value(&value);
1399        // This should not leak memory
1400        unsafe { nb.drop_nanboxed() };
1401        // Test passes if no memory issues (detected by miri or valgrind)
1402    }
1403
1404    #[test]
1405    fn test_drop_nanboxed_primitive() {
1406        let nb = NanBoxedValue::from_int(42);
1407        // Dropping a primitive should be a no-op
1408        unsafe { nb.drop_nanboxed() };
1409        // The value is still valid (Copy type)
1410        assert_eq!(nb.as_int(), 42);
1411    }
1412}