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