Skip to main content

shape_jit/
nan_boxing.rs

1//! JIT NaN-boxing layer — unified tag scheme.
2//!
3//! All values fit in a single u64. This module uses the **shared** tag scheme from
4//! `shape_value::tags` for both inline AND heap types:
5//!
6//! - **Inline types** (int, bool, none, unit, function, module_fn, ref) use the 3-bit
7//!   shared tags from `shape_value::tags` in negative NaN space (TAG_BASE = 0xFFF8).
8//! - **Heap types** use TAG_HEAP (tag = 0b000) with a pointer to a `JitAlloc<T>`
9//!   struct. The `JitAlloc` prefix stores a `u16` kind at offset 0 (matching
10//!   `HEAP_KIND_*` constants from `shape_value::tags`), enabling O(1) type
11//!   discrimination with a single memory load.
12//! - **Data rows** use the shared TAG_INT (0b001) encoding with the row index stored
13//!   in the 48-bit payload. Row indices are always small positive integers that fit
14//!   easily within the i48 range.
15//!
16//! ## Number detection
17//!
18//! `is_number()` uses the shared `shape_value::tags::is_number()` check. Plain f64
19//! values have sign=0 and are not detected as tagged.
20
21// ============================================================================
22// Re-exports from shared tags
23// ============================================================================
24
25pub use shape_value::tags::{
26    // Bit layout constants
27    CANONICAL_NAN,
28    // HeapKind constants
29    HEAP_KIND_ARRAY,
30    HEAP_KIND_BIG_INT,
31    HEAP_KIND_BOOL,
32    HEAP_KIND_CLOSURE,
33    HEAP_KIND_COLUMN_REF,
34    HEAP_KIND_DATA_DATETIME_REF,
35    HEAP_KIND_DATA_REFERENCE,
36    HEAP_KIND_DATATABLE,
37    HEAP_KIND_DATETIME_EXPR,
38    HEAP_KIND_DECIMAL,
39    HEAP_KIND_DURATION,
40    HEAP_KIND_ENUM,
41    HEAP_KIND_ERR,
42    HEAP_KIND_EXPR_PROXY,
43    HEAP_KIND_FILTER_EXPR,
44    HEAP_KIND_FUNCTION,
45    HEAP_KIND_FUNCTION_REF,
46    HEAP_KIND_FUTURE,
47    HEAP_KIND_HASHMAP,
48    HEAP_KIND_HOST_CLOSURE,
49    HEAP_KIND_INDEXED_TABLE,
50    HEAP_KIND_MODULE_FUNCTION,
51    HEAP_KIND_NONE,
52    HEAP_KIND_NUMBER,
53    HEAP_KIND_OK,
54    HEAP_KIND_PRINT_RESULT,
55    HEAP_KIND_RANGE,
56    HEAP_KIND_ROW_VIEW,
57    HEAP_KIND_SIMULATION_CALL,
58    HEAP_KIND_SOME,
59    HEAP_KIND_STRING,
60    HEAP_KIND_TASK_GROUP,
61    HEAP_KIND_TIME,
62    HEAP_KIND_TIME_REFERENCE,
63    HEAP_KIND_TIMEFRAME,
64    HEAP_KIND_TIMESPAN,
65    HEAP_KIND_TRAIT_OBJECT,
66    HEAP_KIND_TYPE_ANNOTATED_VALUE,
67    HEAP_KIND_TYPE_ANNOTATION,
68    HEAP_KIND_TYPED_OBJECT,
69    HEAP_KIND_TYPED_TABLE,
70    HEAP_KIND_UNIT,
71    I48_MAX,
72    I48_MIN,
73    PAYLOAD_MASK,
74    TAG_BASE,
75    TAG_SHIFT,
76    // Shared tag helpers
77    make_tagged,
78    sign_extend_i48,
79};
80
81// ============================================================================
82// NaN-space detection
83// ============================================================================
84
85/// NaN base: all 1s in exponent (bits 62-52). Used for number detection.
86pub const NAN_BASE: u64 = 0x7FF0_0000_0000_0000;
87
88/// 16-bit tag mask — used for legacy positive-NaN tag discrimination in translator IR.
89pub const TAG_MASK: u64 = 0xFFFF_0000_0000_0000;
90
91// ============================================================================
92// Inline types — shared scheme (TAG_BASE space, sign=1, negative NaN)
93// ============================================================================
94
95/// Null/None value. Uses shared TAG_NONE (0b011).
96pub const TAG_NULL: u64 =
97    shape_value::tags::TAG_BASE | (shape_value::tags::TAG_NONE << shape_value::tags::TAG_SHIFT);
98
99/// Boolean false. Uses shared TAG_BOOL (0b010) with payload 0.
100pub const TAG_BOOL_FALSE: u64 =
101    shape_value::tags::TAG_BASE | (shape_value::tags::TAG_BOOL << shape_value::tags::TAG_SHIFT);
102
103/// Boolean true. Uses shared TAG_BOOL (0b010) with payload 1.
104pub const TAG_BOOL_TRUE: u64 =
105    shape_value::tags::TAG_BASE | (shape_value::tags::TAG_BOOL << shape_value::tags::TAG_SHIFT) | 1;
106
107/// Unit (void return). Uses shared TAG_UNIT (0b100).
108pub const TAG_UNIT: u64 =
109    shape_value::tags::TAG_BASE | (shape_value::tags::TAG_UNIT << shape_value::tags::TAG_SHIFT);
110
111/// None (alias for TAG_NULL, Option::None).
112pub const TAG_NONE: u64 = TAG_NULL;
113
114/// Number tag sentinel (not a real tag — numbers are plain f64).
115pub const TAG_NUMBER: u64 = 0x0000_0000_0000_0000;
116
117// ============================================================================
118// Data row encoding — uses shared TAG_INT (negative NaN space)
119// ============================================================================
120
121/// Data row tag: uses the shared TAG_INT (0b001) encoding in negative NaN space.
122/// Row indices are stored as i48 in the 48-bit payload.
123/// Full tagged value: TAG_BASE | (TAG_INT << TAG_SHIFT) | row_index
124pub const TAG_DATA_ROW: u64 = TAG_BASE | (shape_value::tags::TAG_INT << TAG_SHIFT);
125
126// ============================================================================
127// Heap Kind shortcuts (HK_* = HEAP_KIND_* as u16)
128//
129// Use these in match arms: `match heap_kind(bits) { Some(HK_STRING) => ... }`
130// ============================================================================
131
132pub const HK_STRING: u16 = HEAP_KIND_STRING as u16;
133pub const HK_ARRAY: u16 = HEAP_KIND_ARRAY as u16;
134pub const HK_TYPED_OBJECT: u16 = HEAP_KIND_TYPED_OBJECT as u16;
135pub const HK_CLOSURE: u16 = HEAP_KIND_CLOSURE as u16;
136pub const HK_DECIMAL: u16 = HEAP_KIND_DECIMAL as u16;
137pub const HK_BIG_INT: u16 = HEAP_KIND_BIG_INT as u16;
138pub const HK_HOST_CLOSURE: u16 = HEAP_KIND_HOST_CLOSURE as u16;
139pub const HK_DATATABLE: u16 = HEAP_KIND_DATATABLE as u16;
140pub const HK_HASHMAP: u16 = HEAP_KIND_HASHMAP as u16;
141pub const HK_TYPED_TABLE: u16 = HEAP_KIND_TYPED_TABLE as u16;
142pub const HK_ROW_VIEW: u16 = HEAP_KIND_ROW_VIEW as u16;
143pub const HK_COLUMN_REF: u16 = HEAP_KIND_COLUMN_REF as u16;
144pub const HK_INDEXED_TABLE: u16 = HEAP_KIND_INDEXED_TABLE as u16;
145pub const HK_RANGE: u16 = HEAP_KIND_RANGE as u16;
146pub const HK_ENUM: u16 = HEAP_KIND_ENUM as u16;
147pub const HK_SOME: u16 = HEAP_KIND_SOME as u16;
148pub const HK_OK: u16 = HEAP_KIND_OK as u16;
149pub const HK_ERR: u16 = HEAP_KIND_ERR as u16;
150pub const HK_FUTURE: u16 = HEAP_KIND_FUTURE as u16;
151pub const HK_TASK_GROUP: u16 = HEAP_KIND_TASK_GROUP as u16;
152pub const HK_TRAIT_OBJECT: u16 = HEAP_KIND_TRAIT_OBJECT as u16;
153pub const HK_EXPR_PROXY: u16 = HEAP_KIND_EXPR_PROXY as u16;
154pub const HK_FILTER_EXPR: u16 = HEAP_KIND_FILTER_EXPR as u16;
155pub const HK_TIME: u16 = HEAP_KIND_TIME as u16;
156pub const HK_DURATION: u16 = HEAP_KIND_DURATION as u16;
157pub const HK_TIMESPAN: u16 = HEAP_KIND_TIMESPAN as u16;
158pub const HK_TIMEFRAME: u16 = HEAP_KIND_TIMEFRAME as u16;
159pub const HK_TIME_REFERENCE: u16 = HEAP_KIND_TIME_REFERENCE as u16;
160pub const HK_DATETIME_EXPR: u16 = HEAP_KIND_DATETIME_EXPR as u16;
161pub const HK_DATA_DATETIME_REF: u16 = HEAP_KIND_DATA_DATETIME_REF as u16;
162pub const HK_TYPE_ANNOTATION: u16 = HEAP_KIND_TYPE_ANNOTATION as u16;
163pub const HK_TYPE_ANNOTATED_VALUE: u16 = HEAP_KIND_TYPE_ANNOTATED_VALUE as u16;
164pub const HK_PRINT_RESULT: u16 = HEAP_KIND_PRINT_RESULT as u16;
165pub const HK_SIMULATION_CALL: u16 = HEAP_KIND_SIMULATION_CALL as u16;
166pub const HK_FUNCTION_REF: u16 = HEAP_KIND_FUNCTION_REF as u16;
167pub const HK_DATA_REFERENCE: u16 = HEAP_KIND_DATA_REFERENCE as u16;
168
169// JIT-specific heap kinds (values >= 128, outside VM's HeapKind enum range)
170pub const HK_JIT_FUNCTION: u16 = 128;
171pub const HK_JIT_SIGNAL_BUILDER: u16 = 129;
172pub const HK_JIT_TABLE_REF: u16 = 130;
173/// Plain HashMap<String, u64> objects (JIT-only, distinct from TypedObject).
174pub const HK_JIT_OBJECT: u16 = 131;
175
176// ============================================================================
177// JIT Heap Allocation Infrastructure
178// ============================================================================
179
180/// Prefix for JIT heap allocations. Stored at offset 0 of every JIT-owned
181/// heap value, enabling type discrimination via `read_heap_kind()`.
182///
183/// Layout: `[kind: u16][_pad: 6 bytes][data: T]` — data starts at offset 8.
184#[repr(C)]
185pub struct JitAlloc<T> {
186    /// HeapKind discriminator (matches HK_* / HEAP_KIND_* constants).
187    pub kind: u16,
188    _pad: [u8; 6],
189    /// The actual value.
190    pub data: T,
191}
192
193/// Byte offset of `data` within a `JitAlloc<T>`.
194pub const JIT_ALLOC_DATA_OFFSET: usize = 8;
195
196// Compile-time layout verification
197const _: () = {
198    // Verify inline types use the shared scheme (negative NaN, sign bit = 1)
199    assert!(
200        TAG_NULL & 0x8000_0000_0000_0000 != 0,
201        "TAG_NULL must be in negative NaN space"
202    );
203    assert!(
204        TAG_BOOL_FALSE & 0x8000_0000_0000_0000 != 0,
205        "TAG_BOOL must be in negative NaN space"
206    );
207    assert!(
208        TAG_UNIT & 0x8000_0000_0000_0000 != 0,
209        "TAG_UNIT must be in negative NaN space"
210    );
211    // TAG_DATA_ROW now uses shared TAG_INT in negative NaN space
212    assert!(
213        TAG_DATA_ROW & 0x8000_0000_0000_0000 != 0,
214        "TAG_DATA_ROW must be in negative NaN space"
215    );
216};
217
218// ============================================================================
219// Core Helper Functions
220// ============================================================================
221
222/// Check if a value is a plain f64 number (not NaN-boxed with any tag).
223/// All tags live in negative NaN space (sign bit = 1).
224#[inline]
225pub fn is_number(bits: u64) -> bool {
226    !shape_value::tags::is_tagged(bits)
227}
228
229/// Unbox a number (assumes value is a number — check with `is_number()` first).
230#[inline]
231pub fn unbox_number(bits: u64) -> f64 {
232    f64::from_bits(bits)
233}
234
235/// Box a number into a NaN-boxed u64.
236#[inline]
237pub const fn box_number(n: f64) -> u64 {
238    f64::to_bits(n)
239}
240
241/// Box a boolean into a NaN-boxed u64 (shared scheme).
242#[inline]
243pub const fn box_bool(b: bool) -> u64 {
244    if b { TAG_BOOL_TRUE } else { TAG_BOOL_FALSE }
245}
246
247/// Box an inline function reference (shared TAG_FUNCTION, payload = function_id).
248#[inline]
249pub fn box_function(fn_id: u16) -> u64 {
250    make_tagged(shape_value::tags::TAG_FUNCTION, fn_id as u64)
251}
252
253/// Check if a value is an inline function reference.
254#[inline]
255pub fn is_inline_function(bits: u64) -> bool {
256    shape_value::tags::is_tagged(bits)
257        && shape_value::tags::get_tag(bits) == shape_value::tags::TAG_FUNCTION
258}
259
260/// Extract function_id from an inline function reference.
261#[inline]
262pub fn unbox_function_id(bits: u64) -> u16 {
263    (bits & PAYLOAD_MASK) as u16
264}
265
266// ============================================================================
267// TAG_HEAP helpers — unified heap value management
268// ============================================================================
269
270/// Allocate a heap value with kind prefix, returning a TAG_HEAP-tagged u64.
271///
272/// The returned value has tag bits = TAG_HEAP (0b000) and payload = pointer to
273/// `JitAlloc<T>` whose first 2 bytes are the `kind` discriminator.
274#[inline]
275pub fn jit_box<T>(kind: u16, data: T) -> u64 {
276    let alloc = Box::new(JitAlloc {
277        kind,
278        _pad: [0; 6],
279        data,
280    });
281    let ptr = Box::into_raw(alloc);
282    // TAG_HEAP = 0b000, so TAG_BASE | (0 << TAG_SHIFT) | ptr = TAG_BASE | ptr
283    TAG_BASE | ((ptr as u64) & PAYLOAD_MASK)
284}
285
286/// Read the heap kind (u16) from a TAG_HEAP-tagged value.
287///
288/// # Safety
289/// `bits` must be a valid TAG_HEAP value whose payload pointer is non-null and
290/// points to a `JitAlloc`-prefixed allocation (kind at offset 0).
291#[inline]
292pub unsafe fn read_heap_kind(bits: u64) -> u16 {
293    let ptr = (bits & PAYLOAD_MASK) as *const u16;
294    unsafe { *ptr }
295}
296
297/// Get a reference to the data within a JIT heap allocation.
298///
299/// # Safety
300/// `bits` must be a TAG_HEAP value pointing to `JitAlloc<T>`.
301#[inline]
302pub unsafe fn jit_unbox<T>(bits: u64) -> &'static T {
303    let ptr = (bits & PAYLOAD_MASK) as *const JitAlloc<T>;
304    unsafe { &(*ptr).data }
305}
306
307/// Get a mutable reference to the data within a JIT heap allocation.
308///
309/// # Safety
310/// `bits` must be a TAG_HEAP value pointing to `JitAlloc<T>`.
311#[inline]
312pub unsafe fn jit_unbox_mut<T>(bits: u64) -> &'static mut T {
313    let ptr = (bits & PAYLOAD_MASK) as *mut JitAlloc<T>;
314    unsafe { &mut (*ptr).data }
315}
316
317/// Deallocate a JIT heap value.
318///
319/// # Safety
320/// Must only be called once per allocation. `bits` must be a TAG_HEAP value
321/// pointing to `JitAlloc<T>`.
322#[inline]
323pub unsafe fn jit_drop<T>(bits: u64) {
324    let ptr = (bits & PAYLOAD_MASK) as *mut JitAlloc<T>;
325    unsafe { drop(Box::from_raw(ptr)) };
326}
327
328/// Check if a value has TAG_HEAP (tag bits 50-48 == 0, in negative NaN space).
329#[inline]
330pub fn is_heap(bits: u64) -> bool {
331    shape_value::tags::is_tagged(bits)
332        && shape_value::tags::get_tag(bits) == shape_value::tags::TAG_HEAP
333}
334
335/// Get the heap kind of a value, or None if not a heap value.
336#[inline]
337pub fn heap_kind(bits: u64) -> Option<u16> {
338    if is_heap(bits) {
339        Some(unsafe { read_heap_kind(bits) })
340    } else {
341        None
342    }
343}
344
345/// Check if a value is a heap value with a specific kind.
346#[inline]
347pub fn is_heap_kind(bits: u64, expected_kind: u16) -> bool {
348    is_heap(bits) && unsafe { read_heap_kind(bits) } == expected_kind
349}
350
351/// Extract the raw pointer from a TAG_HEAP value (points to JitAlloc header).
352#[inline]
353pub fn unbox_heap_pointer(bits: u64) -> *const u8 {
354    (bits & PAYLOAD_MASK) as *const u8
355}
356
357// ============================================================================
358// Result Type (Ok/Err) Helper Functions
359// ============================================================================
360
361#[inline]
362pub fn is_ok_tag(bits: u64) -> bool {
363    is_heap_kind(bits, HK_OK)
364}
365
366#[inline]
367pub fn is_err_tag(bits: u64) -> bool {
368    is_heap_kind(bits, HK_ERR)
369}
370
371#[inline]
372pub fn is_result_tag(bits: u64) -> bool {
373    is_ok_tag(bits) || is_err_tag(bits)
374}
375
376#[inline]
377pub fn box_ok(inner_bits: u64) -> u64 {
378    jit_box(HK_OK, inner_bits)
379}
380
381#[inline]
382pub fn box_err(inner_bits: u64) -> u64 {
383    jit_box(HK_ERR, inner_bits)
384}
385
386#[inline]
387pub unsafe fn unbox_result_inner(bits: u64) -> u64 {
388    *unsafe { jit_unbox::<u64>(bits) }
389}
390
391#[inline]
392pub fn unbox_result_pointer(bits: u64) -> *const u64 {
393    let ptr = (bits & PAYLOAD_MASK) as *const JitAlloc<u64>;
394    if ptr.is_null() {
395        std::ptr::null()
396    } else {
397        unsafe { &(*ptr).data as *const u64 }
398    }
399}
400
401// ============================================================================
402// Option Type (Some/None) Helper Functions
403// ============================================================================
404
405#[inline]
406pub fn is_some_tag(bits: u64) -> bool {
407    is_heap_kind(bits, HK_SOME)
408}
409
410#[inline]
411pub fn is_none_tag(bits: u64) -> bool {
412    bits == TAG_NONE
413}
414
415#[inline]
416pub fn is_option_tag(bits: u64) -> bool {
417    is_some_tag(bits) || is_none_tag(bits)
418}
419
420#[inline]
421pub fn box_some(inner_bits: u64) -> u64 {
422    jit_box(HK_SOME, inner_bits)
423}
424
425#[inline]
426pub unsafe fn unbox_some_inner(bits: u64) -> u64 {
427    *unsafe { jit_unbox::<u64>(bits) }
428}
429
430// ============================================================================
431// Data Row Helper Functions
432// ============================================================================
433
434/// Box a row index as a data row reference using shared TAG_INT encoding.
435#[inline]
436pub const fn box_data_row(row_index: usize) -> u64 {
437    TAG_DATA_ROW | ((row_index as u64) & PAYLOAD_MASK)
438}
439
440/// Extract the row index from a data row reference (TAG_INT payload).
441#[inline]
442pub const fn unbox_data_row(bits: u64) -> usize {
443    (bits & PAYLOAD_MASK) as usize
444}
445
446/// Check if a value is a data row reference.
447/// Data rows use the shared TAG_INT encoding (tag bits 50-48 == 0b001).
448#[inline]
449pub fn is_data_row(bits: u64) -> bool {
450    shape_value::tags::is_tagged(bits)
451        && shape_value::tags::get_tag(bits) == shape_value::tags::TAG_INT
452}
453
454// ============================================================================
455// Column Reference Helper Functions
456// ============================================================================
457
458#[inline]
459pub fn box_column_ref(ptr: *const f64, len: usize) -> u64 {
460    jit_box(HK_COLUMN_REF, (ptr, len))
461}
462
463#[inline]
464pub unsafe fn unbox_column_ref(bits: u64) -> (*const f64, usize) {
465    *unsafe { jit_unbox::<(*const f64, usize)>(bits) }
466}
467
468#[inline]
469pub fn is_column_ref(bits: u64) -> bool {
470    is_heap_kind(bits, HK_COLUMN_REF)
471}
472
473// ============================================================================
474// Typed Object Helper Functions
475// ============================================================================
476
477#[inline]
478pub fn box_typed_object(ptr: *const u8) -> u64 {
479    jit_box(HK_TYPED_OBJECT, ptr)
480}
481
482#[inline]
483pub fn unbox_typed_object(bits: u64) -> *const u8 {
484    *unsafe { jit_unbox::<*const u8>(bits) }
485}
486
487#[inline]
488pub fn is_typed_object(bits: u64) -> bool {
489    is_heap_kind(bits, HK_TYPED_OBJECT)
490}
491
492// ============================================================================
493// Tests
494// ============================================================================
495
496#[cfg(test)]
497mod tests {
498    use super::*;
499
500    #[test]
501    fn test_inline_types_in_negative_nan_space() {
502        assert!(TAG_NULL & 0x8000_0000_0000_0000 != 0);
503        assert!(TAG_BOOL_FALSE & 0x8000_0000_0000_0000 != 0);
504        assert!(TAG_BOOL_TRUE & 0x8000_0000_0000_0000 != 0);
505        assert!(TAG_UNIT & 0x8000_0000_0000_0000 != 0);
506    }
507
508    #[test]
509    fn test_data_row_in_negative_nan_space() {
510        assert!(TAG_DATA_ROW & 0x8000_0000_0000_0000 != 0);
511        assert!(!is_number(TAG_DATA_ROW));
512    }
513
514    #[test]
515    fn test_nan_base_detects_all_tags() {
516        assert!(!is_number(TAG_NULL), "TAG_NULL should not be a number");
517        assert!(
518            !is_number(TAG_DATA_ROW),
519            "TAG_DATA_ROW should not be a number"
520        );
521        assert!(
522            !is_number(TAG_BOOL_TRUE),
523            "TAG_BOOL_TRUE should not be a number"
524        );
525
526        // Plain f64 values should be detected as numbers
527        assert!(is_number(box_number(3.14)));
528        assert!(is_number(box_number(0.0)));
529        assert!(is_number(box_number(-1.0)));
530        assert!(is_number(box_number(f64::MAX)));
531        assert!(is_number(box_number(f64::MIN)));
532    }
533
534    #[test]
535    fn test_inline_constants_match_shared_scheme() {
536        assert_eq!(TAG_NULL, make_tagged(shape_value::tags::TAG_NONE, 0));
537        assert_eq!(TAG_BOOL_FALSE, make_tagged(shape_value::tags::TAG_BOOL, 0));
538        assert_eq!(TAG_BOOL_TRUE, make_tagged(shape_value::tags::TAG_BOOL, 1));
539        assert_eq!(TAG_UNIT, make_tagged(shape_value::tags::TAG_UNIT, 0));
540    }
541
542    #[test]
543    fn test_box_unbox_number() {
544        let n = 3.14f64;
545        let boxed = box_number(n);
546        assert!(is_number(boxed));
547        assert_eq!(unbox_number(boxed), n);
548    }
549
550    #[test]
551    fn test_box_unbox_bool() {
552        assert_eq!(box_bool(true), TAG_BOOL_TRUE);
553        assert_eq!(box_bool(false), TAG_BOOL_FALSE);
554    }
555
556    #[test]
557    fn test_box_function() {
558        let bits = box_function(42);
559        assert!(is_inline_function(bits));
560        assert_eq!(unbox_function_id(bits), 42);
561        assert!(!is_number(bits));
562        assert!(!is_heap(bits));
563    }
564
565    #[test]
566    fn test_jit_alloc_string() {
567        let bits = jit_box(HK_STRING, "hello".to_string());
568        assert!(is_heap(bits));
569        assert!(is_heap_kind(bits, HK_STRING));
570        assert!(!is_number(bits));
571        assert_eq!(heap_kind(bits), Some(HK_STRING));
572        let s = unsafe { jit_unbox::<String>(bits) };
573        assert_eq!(s, "hello");
574        unsafe { jit_drop::<String>(bits) };
575    }
576
577    #[test]
578    fn test_jit_alloc_array() {
579        let bits = jit_box(HK_ARRAY, vec![1u64, 2, 3]);
580        assert!(is_heap(bits));
581        assert!(is_heap_kind(bits, HK_ARRAY));
582        assert_eq!(heap_kind(bits), Some(HK_ARRAY));
583        let arr = unsafe { jit_unbox::<Vec<u64>>(bits) };
584        assert_eq!(arr.len(), 3);
585        unsafe { jit_drop::<Vec<u64>>(bits) };
586    }
587
588    #[test]
589    fn test_heap_kind_none_for_non_heap() {
590        assert_eq!(heap_kind(TAG_NULL), None);
591        assert_eq!(heap_kind(TAG_BOOL_TRUE), None);
592        assert_eq!(heap_kind(box_number(42.0)), None);
593        assert_eq!(heap_kind(TAG_DATA_ROW | 5), None);
594    }
595
596    #[test]
597    fn test_data_row_round_trip() {
598        let bits = box_data_row(999);
599        assert!(is_data_row(bits));
600        assert_eq!(unbox_data_row(bits), 999);
601        assert!(!is_number(bits));
602        assert!(!is_heap(bits));
603    }
604
605    #[test]
606    fn test_result_tag_discrimination() {
607        assert!(!is_ok_tag(TAG_NULL));
608        assert!(!is_err_tag(TAG_NULL));
609        assert!(!is_result_tag(box_number(1.0)));
610
611        let ok_val = box_ok(box_number(1.0));
612        assert!(is_ok_tag(ok_val));
613        assert!(!is_err_tag(ok_val));
614        assert!(is_result_tag(ok_val));
615
616        let err_val = box_err(box_number(42.0));
617        assert!(is_err_tag(err_val));
618        assert!(!is_ok_tag(err_val));
619        assert!(is_result_tag(err_val));
620
621        // TAG_BOOL values must not be detected as ERR
622        assert!(!is_err_tag(TAG_BOOL_FALSE));
623        assert!(!is_err_tag(TAG_BOOL_TRUE));
624
625        // Clean up
626        unsafe { jit_drop::<u64>(ok_val) };
627        unsafe { jit_drop::<u64>(err_val) };
628    }
629
630    #[test]
631    fn test_result_round_trip() {
632        let inner = box_number(99.5);
633        let ok_val = box_ok(inner);
634        assert!(is_ok_tag(ok_val));
635        let recovered = unsafe { unbox_result_inner(ok_val) };
636        assert_eq!(unbox_number(recovered), 99.5);
637        unsafe { jit_drop::<u64>(ok_val) };
638    }
639
640    #[test]
641    fn test_option_tag_discrimination() {
642        assert!(is_none_tag(TAG_NONE));
643        assert!(is_option_tag(TAG_NONE));
644        assert!(!is_some_tag(TAG_NONE));
645
646        let some_val = box_some(box_number(3.14));
647        assert!(is_some_tag(some_val));
648        assert!(is_option_tag(some_val));
649        assert!(!is_none_tag(some_val));
650
651        // Round-trip
652        let inner = unsafe { unbox_some_inner(some_val) };
653        assert_eq!(unbox_number(inner), 3.14);
654
655        // Clean up
656        unsafe { jit_drop::<u64>(some_val) };
657    }
658
659    #[test]
660    fn test_typed_object_encoding() {
661        let fake_ptr = 0x0000_1234_5678_0000u64 as *const u8;
662        let boxed = box_typed_object(fake_ptr);
663        assert!(is_typed_object(boxed));
664        assert!(!is_number(boxed));
665
666        // Round-trip: recover the pointer
667        let recovered = unbox_typed_object(boxed);
668        assert_eq!(recovered, fake_ptr);
669
670        // Non-typed-object values should not match
671        assert!(!is_typed_object(TAG_NULL));
672        assert!(!is_typed_object(box_number(42.0)));
673
674        // Clean up
675        unsafe { jit_drop::<*const u8>(boxed) };
676    }
677
678    #[test]
679    fn test_column_ref_round_trip() {
680        let data = vec![1.0f64, 2.0, 3.0];
681        let bits = box_column_ref(data.as_ptr(), data.len());
682        assert!(is_column_ref(bits));
683        assert!(!is_number(bits));
684
685        let (ptr, len) = unsafe { unbox_column_ref(bits) };
686        assert_eq!(ptr, data.as_ptr());
687        assert_eq!(len, 3);
688
689        // Clean up
690        unsafe { jit_drop::<(*const f64, usize)>(bits) };
691    }
692}