shape-jit 0.3.2

Tiered JIT compiler (Cranelift) for the Shape virtual machine
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
//! JIT-side value-encoding helpers (NaN-box layout used by JIT-emitted code).
//!
//! Per ADR-006 §2.7.5, the JIT FFI boundary carries raw `u64` plus a parallel
//! `NativeKind` companion stamped at JIT compile time from the call signature.
//! The constants and helpers in this module are JIT-internal: they encode the
//! sentinel u64 layout that JIT-emitted Cranelift code uses for inline scalars
//! (`TAG_NULL`, `TAG_BOOL_*`, `TAG_UNIT`, `TAG_DATA_ROW`) and the JitAlloc /
//! `UnifiedValue` pointer shape for heap values.
//!
//! The deleted `shape_value::tag_bits::*`, `shape_value::ValueWord*`,
//! `shape_value::ValueBits`, `shape_value::unified_string`, and
//! `shape_value::unified_wrapper` references that this file previously
//! relied on were retired by the strict-typing bulldozer (Phase 2). The
//! tag constants below are defined locally with the exact u64 layout the
//! JIT-emitted code already targets — they are not a "tag_bits restoration
//! shim" (forbidden per W10 playbook §3) but the JIT-internal sentinel
//! encoding that survives §2.7.5's stable-FFI rule (raw u64 ABI, no
//! runtime kind discrimination from the bits themselves; consumers that
//! need a runtime-tier carrier wrap the bits as
//! `KindedSlot::new(ValueSlot::from_raw(bits), kind)` per §2.7.5/Q7).
//!
//! Heap-pointer values produced by `box_string` / `box_ok` / `box_err` /
//! `box_some` / `box_typed_object` / `box_column_ref` use the
//! `jit_kinds::unified_box` shape: a `UnifiedValue<T>` heap allocation with
//! a `kind: u16` prefix at offset 0, readable via
//! `jit_kinds::read_heap_kind` (per §2.7.5: this is *not* tag-bit dispatch —
//! it reads a field from a heap-resident struct). The `HK_*` constants
//! mirror `HeapKind` ordinals (cast to `u16`) for use as the prefix.

use shape_value::HeapKind;
use std::sync::Arc;

use super::jit_kinds::{read_heap_kind, unified_box, unified_unbox};

// ============================================================================
// JIT-internal NaN-box sentinel layout
// ============================================================================
//
// Inline scalars (null, bool, unit, data-row, function-id) ride in negative
// NaN space (sign bit = 1). The 3-bit tag at bits 50-48 selects the inline
// shape; the low 48 bits carry the payload. This layout is local to the JIT
// (no `shape_value::tag_bits` import) — it is the shape JIT-emitted Cranelift
// code references via `iconst(types::I64, TAG_NULL as i64)` etc., kept stable
// so existing JIT-emitted code keeps working through the W10 consumer
// migration cascade.

/// NaN base: all 1s in exponent (bits 62-52). Used for number detection.
pub const NAN_BASE: u64 = 0x7FF0_0000_0000_0000;

/// 16-bit tag mask -- used for legacy positive-NaN tag discrimination in translator IR.
pub const TAG_MASK: u64 = 0xFFFF_0000_0000_0000;

/// Tagged-value base: negative-NaN exponent + sign bit.
pub const TAG_BASE: u64 = 0xFFF8_0000_0000_0000;

/// Bit shift for the 3-bit inline-tag field at bits 50-48.
pub const TAG_SHIFT: u32 = 48;

/// 48-bit payload mask.
pub const PAYLOAD_MASK: u64 = 0x0000_FFFF_FFFF_FFFF;

/// IEEE-754 canonical quiet NaN (positive sign).
pub const CANONICAL_NAN: u64 = 0x7FF8_0000_0000_0000;

/// `i48` payload range — JIT inline-int encoding fits in 48 bits.
pub const I48_MAX: i64 = (1_i64 << 47) - 1;
pub const I48_MIN: i64 = -(1_i64 << 47);

/// Bit-47 marker for unified-heap pointers; legacy bit retained for the
/// JIT consumer migration window where some helpers still discriminate the
/// pointer shape. Per Band 1 close (§2.7.5): the discriminator no longer
/// gates kind decode — both shapes are raw `Box::into_raw` pointers and
/// the kind flows through the parallel `NativeKind` companion.
pub const UNIFIED_HEAP_FLAG: u64 = 1 << 47;
pub const UNIFIED_PTR_MASK: u64 = PAYLOAD_MASK & !UNIFIED_HEAP_FLAG;

/// Low ownership bit cleared on heap-pointer reads.
const HEAP_OWNED_BIT: u64 = 1;
pub const HEAP_PTR_MASK: u64 = !HEAP_OWNED_BIT;

// 3-bit inline tags at bits 50-48 (private — JIT-internal naming carries
// `_BITS` suffix to free the unsuffixed names for the public sentinel values
// callers reference, e.g. `TAG_NULL` / `TAG_NONE` / `TAG_UNIT`).
const TAG_HEAP_BITS: u64 = 0b000;
const TAG_INT_BITS: u64 = 0b001;
const TAG_BOOL_BITS: u64 = 0b010;
const TAG_NONE_BITS: u64 = 0b011;
const TAG_UNIT_BITS: u64 = 0b100;
const TAG_FUNCTION_BITS: u64 = 0b101;

#[inline]
const fn make_tagged(tag: u64, payload: u64) -> u64 {
    TAG_BASE | (tag << TAG_SHIFT) | (payload & PAYLOAD_MASK)
}

#[inline]
fn is_tagged(bits: u64) -> bool {
    bits & TAG_BASE == TAG_BASE
}

#[inline]
fn get_tag(bits: u64) -> u64 {
    (bits >> TAG_SHIFT) & 0b111
}

// ============================================================================
// Inline types -- shared scheme (TAG_BASE space, sign=1, negative NaN)
// ============================================================================

/// Null/None value. Uses shared TAG_NONE (0b011).
pub const TAG_NULL: u64 = make_tagged(TAG_NONE_BITS, 0);

/// Boolean false. Uses shared TAG_BOOL (0b010) with payload 0.
pub const TAG_BOOL_FALSE: u64 = make_tagged(TAG_BOOL_BITS, 0);

/// Boolean true. Uses shared TAG_BOOL (0b010) with payload 1.
pub const TAG_BOOL_TRUE: u64 = make_tagged(TAG_BOOL_BITS, 1);

/// Unit (void return). Uses shared TAG_UNIT (0b100).
pub const TAG_UNIT: u64 = make_tagged(TAG_UNIT_BITS, 0);

/// None sentinel — alias for `TAG_NULL` (`Option::None` JIT representation).
/// Re-exported under `TAG_NONE` for legacy callers.
pub const TAG_NONE: u64 = TAG_NULL;

/// Number tag sentinel (not a real tag -- numbers are plain f64).
pub const TAG_NUMBER: u64 = 0x0000_0000_0000_0000;

// ============================================================================
// Data row encoding -- uses shared TAG_INT (negative NaN space)
// ============================================================================

/// Data row tag: uses the shared TAG_INT (0b001) encoding in negative NaN space.
/// Row indices are stored as i48 in the 48-bit payload.
pub const TAG_DATA_ROW: u64 = TAG_BASE | (TAG_INT_BITS << TAG_SHIFT);

// ============================================================================
// Heap Kind shortcuts (HK_*)
//
// Use these as the `kind: u16` prefix on `unified_box` / `jit_box`
// allocations: `unified_box(HK_STRING, Arc::new(s))`. Match arms read the
// prefix back via `jit_kinds::read_heap_kind(bits)`.
// ============================================================================
//
// Two-tier layout (W17-jit-legacy-ordinal-disambiguation, 2026-05-12,
// phase-2d-hardening item (i)):
//
//   Tier 1 — canonical kinds aliased to `HeapKind as u16` (ordinal range 0..127):
//     HK_STRING, HK_TYPED_OBJECT, HK_CLOSURE, HK_DECIMAL, HK_BIG_INT,
//     HK_DATATABLE, HK_HASHMAP, HK_FUTURE, HK_TASK_GROUP, HK_FILTER_EXPR.
//     These ARE the runtime `HeapKind` discriminator; producers / consumers
//     using these constants speak the same kind label as the runtime tier.
//
//   Tier 2 — JIT-private ordinals (range 256..511):
//     Every other HK_* constant. These label `JitAlloc<T>` / `UnifiedValue<T>`
//     heap prefixes for JIT-internal values whose `T` payload type is
//     determined by the producing call and consumed by sibling JIT arms
//     pattern-matching the same `HK_*` prefix. They do NOT cross the JIT FFI
//     boundary as runtime `HeapKind` labels; they MUST stay outside the
//     `HeapKind as u16` range so a stray runtime-tier slot that does cross
//     the boundary (e.g. via the `jit_bits_to_nanboxed` / `nanboxed_to_jit_bits`
//     carrier when those land per W11) cannot collide with a JIT-internal
//     `JitAlloc<T>` prefix.
//
// JIT-private base. Chosen so that the entire JIT-private block sits above
// the `HeapKind as u16` representable range *and* above the existing
// `jit_kinds::HK_JIT_*` block (128..132) / `v2_struct::HK_V2_TYPED_STRUCT`
// (132). HeapKind can grow to 255 variants before the boundary needs to
// move; ample headroom.
pub const JIT_LEGACY_HK_BASE: u16 = 256;

// ----------------------------------------------------------------------------
// Tier 1 — canonical HeapKind-aliased
// ----------------------------------------------------------------------------
pub const HK_STRING: u16 = HeapKind::String as u16;
pub const HK_TYPED_OBJECT: u16 = HeapKind::TypedObject as u16;
pub const HK_CLOSURE: u16 = HeapKind::Closure as u16;
pub const HK_DECIMAL: u16 = HeapKind::Decimal as u16;
pub const HK_BIG_INT: u16 = HeapKind::BigInt as u16;
pub const HK_DATATABLE: u16 = HeapKind::DataTable as u16;
pub const HK_HASHMAP: u16 = HeapKind::HashMap as u16;
pub const HK_FUTURE: u16 = HeapKind::Future as u16;
pub const HK_TASK_GROUP: u16 = HeapKind::TaskGroup as u16;
pub const HK_FILTER_EXPR: u16 = HeapKind::FilterExpr as u16;

// ----------------------------------------------------------------------------
// Tier 2 — JIT-private kinds (no surviving HeapValue arm; JIT-emitted-and-
// JIT-consumed only). Contiguous block starting at JIT_LEGACY_HK_BASE so the
// CHECK 12 grep guard in verify-merge.sh can assert every JIT-private HK_*
// constant sits at or above the base.
// ----------------------------------------------------------------------------
pub const HK_ARRAY: u16 = JIT_LEGACY_HK_BASE; // 256 — was 1 (collided HeapKind::TypedObject)
pub const HK_HOST_CLOSURE: u16 = JIT_LEGACY_HK_BASE + 1; // 257 — was 6
pub const HK_TYPED_TABLE: u16 = JIT_LEGACY_HK_BASE + 2; // 258 — was 8 (collided HeapKind::TypedArray)
pub const HK_ROW_VIEW: u16 = JIT_LEGACY_HK_BASE + 3; // 259 — was 9 (collided HeapKind::Temporal)
pub const HK_COLUMN_REF: u16 = JIT_LEGACY_HK_BASE + 4; // 260 — was 10 (collided HeapKind::TableView)
pub const HK_INDEXED_TABLE: u16 = JIT_LEGACY_HK_BASE + 5; // 261 — was 11 (collided HeapKind::Content)
pub const HK_RANGE: u16 = JIT_LEGACY_HK_BASE + 6; // 262 — was 12 (collided HeapKind::Instant)
pub const HK_ENUM: u16 = JIT_LEGACY_HK_BASE + 7; // 263 — was 13 (collided HeapKind::IoHandle)
pub const HK_SOME: u16 = JIT_LEGACY_HK_BASE + 8; // 264 — was 14 (collided HeapKind::NativeScalar)
pub const HK_OK: u16 = JIT_LEGACY_HK_BASE + 9; // 265 — was 15 (collided HeapKind::NativeView)
pub const HK_ERR: u16 = JIT_LEGACY_HK_BASE + 10; // 266 — was 16 (collided HeapKind::Char)
pub const HK_TRAIT_OBJECT: u16 = JIT_LEGACY_HK_BASE + 11; // 267 — was 19 (collided HeapKind::Reference)
pub const HK_EXPR_PROXY: u16 = JIT_LEGACY_HK_BASE + 12; // 268 — was 20 (collided HeapKind::SharedCell)
pub const HK_TIME: u16 = JIT_LEGACY_HK_BASE + 13; // 269 — was 22 (collided HeapKind::Iterator)
pub const HK_DURATION: u16 = JIT_LEGACY_HK_BASE + 14; // 270 — was 23 (collided HeapKind::Deque)
pub const HK_TIMESPAN: u16 = JIT_LEGACY_HK_BASE + 15; // 271 — was 24 (collided HeapKind::Channel)
pub const HK_TIMEFRAME: u16 = JIT_LEGACY_HK_BASE + 16; // 272 — was 25 (collided HeapKind::PriorityQueue)
pub const HK_TIME_REFERENCE: u16 = JIT_LEGACY_HK_BASE + 17; // 273 — was 26 (collided HeapKind::Range)
pub const HK_DATETIME_EXPR: u16 = JIT_LEGACY_HK_BASE + 18; // 274 — was 27 (collided HeapKind::Result)
pub const HK_DATA_DATETIME_REF: u16 = JIT_LEGACY_HK_BASE + 19; // 275 — was 28 (collided HeapKind::Option)
pub const HK_TYPE_ANNOTATION: u16 = JIT_LEGACY_HK_BASE + 20; // 276 — was 29 (collided HeapKind::TraitObject)
pub const HK_TYPE_ANNOTATED_VALUE: u16 = JIT_LEGACY_HK_BASE + 21; // 277 — was 30 (collided HeapKind::Mutex)
pub const HK_PRINT_RESULT: u16 = JIT_LEGACY_HK_BASE + 22; // 278 — was 31 (collided HeapKind::Atomic)
pub const HK_SIMULATION_CALL: u16 = JIT_LEGACY_HK_BASE + 23; // 279 — was 32 (collided HeapKind::Lazy)
pub const HK_FUNCTION_REF: u16 = JIT_LEGACY_HK_BASE + 24; // 280 — was 33 (collided HeapKind::ModuleFn)
pub const HK_DATA_REFERENCE: u16 = JIT_LEGACY_HK_BASE + 25; // 281 — was 34 (one above current HeapKind tail; bumped pre-emptively)
pub const HK_INT_ARRAY: u16 = JIT_LEGACY_HK_BASE + 26; // 282 — was 48 (above current HeapKind range; bumped to preserve invariant)
pub const HK_FLOAT_ARRAY: u16 = JIT_LEGACY_HK_BASE + 27; // 283 — was 49
pub const HK_BOOL_ARRAY: u16 = JIT_LEGACY_HK_BASE + 28; // 284 — was 50
pub const HK_MATRIX: u16 = JIT_LEGACY_HK_BASE + 29; // 285 — was 51
pub const HK_I8_ARRAY: u16 = JIT_LEGACY_HK_BASE + 30; // 286 — was 57
pub const HK_I16_ARRAY: u16 = JIT_LEGACY_HK_BASE + 31; // 287 — was 58
pub const HK_I32_ARRAY: u16 = JIT_LEGACY_HK_BASE + 32; // 288 — was 59
pub const HK_U8_ARRAY: u16 = JIT_LEGACY_HK_BASE + 33; // 289 — was 60
pub const HK_U16_ARRAY: u16 = JIT_LEGACY_HK_BASE + 34; // 290 — was 61
pub const HK_U32_ARRAY: u16 = JIT_LEGACY_HK_BASE + 35; // 291 — was 62
pub const HK_U64_ARRAY: u16 = JIT_LEGACY_HK_BASE + 36; // 292 — was 63
pub const HK_F32_ARRAY: u16 = JIT_LEGACY_HK_BASE + 37; // 293 — was 64
pub const HK_FLOAT_ARRAY_SLICE: u16 = JIT_LEGACY_HK_BASE + 38; // 294 — was 71

// Compile-time invariants for the JIT-private block:
//   * base sits strictly above the `HeapKind as u16` representable range;
//   * base sits strictly above the existing JIT-private blocks in
//     `jit_kinds.rs` (128..132) and `v2_struct.rs` (132).
const _: () = {
    // 192 = current HeapKind tail (33) plus headroom for the existing
    // 128..132 JIT-private block; if HeapKind grows past 127 a future
    // sub-cluster must move JIT_LEGACY_HK_BASE up and renumber.
    assert!(
        JIT_LEGACY_HK_BASE >= 192,
        "JIT_LEGACY_HK_BASE must sit above the HeapKind / jit_kinds.rs / v2_struct.rs blocks"
    );
};

// Compile-time layout verification
const _: () = {
    // Verify inline types use the shared scheme (negative NaN, sign bit = 1)
    assert!(
        TAG_NULL & 0x8000_0000_0000_0000 != 0,
        "TAG_NULL must be in negative NaN space"
    );
    assert!(
        TAG_BOOL_FALSE & 0x8000_0000_0000_0000 != 0,
        "TAG_BOOL must be in negative NaN space"
    );
    assert!(
        TAG_UNIT & 0x8000_0000_0000_0000 != 0,
        "TAG_UNIT must be in negative NaN space"
    );
    assert!(
        TAG_DATA_ROW & 0x8000_0000_0000_0000 != 0,
        "TAG_DATA_ROW must be in negative NaN space"
    );
};

// ============================================================================
// Core Helper Functions
// ============================================================================

/// Check if a value is a plain f64 number (not NaN-boxed with any tag).
/// All tags live in negative NaN space (sign bit = 1).
#[inline]
pub fn is_number(bits: u64) -> bool {
    !is_tagged(bits)
}

/// Unbox a number (assumes value is a number -- check with `is_number()` first).
#[inline]
pub fn unbox_number(bits: u64) -> f64 {
    f64::from_bits(bits)
}

/// Box a number into a NaN-boxed u64.
#[inline]
pub const fn box_number(n: f64) -> u64 {
    f64::to_bits(n)
}

/// Box a boolean into a NaN-boxed u64 (shared scheme).
#[inline]
pub const fn box_bool(b: bool) -> u64 {
    if b { TAG_BOOL_TRUE } else { TAG_BOOL_FALSE }
}

/// Box an inline function reference (shared TAG_FUNCTION, payload = function_id).
#[inline]
pub fn box_function(fn_id: u16) -> u64 {
    make_tagged(TAG_FUNCTION_BITS, fn_id as u64)
}

/// Check if a value is an inline function reference.
#[inline]
pub fn is_inline_function(bits: u64) -> bool {
    is_tagged(bits) && get_tag(bits) == TAG_FUNCTION_BITS
}

/// Extract function_id from an inline function reference.
#[inline]
pub fn unbox_function_id(bits: u64) -> u16 {
    (bits & PAYLOAD_MASK) as u16
}

// ============================================================================
// TAG_HEAP helpers -- unified heap value management
// ============================================================================

/// Check if a value has TAG_HEAP (tag bits 50-48 == 0, in negative NaN space).
#[inline]
pub fn is_heap(bits: u64) -> bool {
    is_tagged(bits) && get_tag(bits) == TAG_HEAP_BITS
}

/// Get the heap kind of a value, or None if not a heap value.
///
/// Reads the `kind: u16` prefix at offset 0 of the underlying `JitAlloc` /
/// `UnifiedValue` allocation per ADR-006 §2.7.5 (this is *not* tag-bit
/// dispatch — it reads a field from a heap-resident struct that the
/// producing call placed there).
#[inline]
pub fn heap_kind(bits: u64) -> Option<u16> {
    if !is_heap(bits) {
        return None;
    }
    Some(unsafe { read_heap_kind(unbox_heap_pointer(bits) as u64) })
}

/// Check if a value is a heap value with a specific kind.
#[inline]
pub fn is_heap_kind(bits: u64, expected_kind: u16) -> bool {
    heap_kind(bits) == Some(expected_kind)
}

/// Extract the raw pointer from a TAG_HEAP value (points to JitAlloc header).
#[inline]
pub fn unbox_heap_pointer(bits: u64) -> *const u8 {
    // Mask off the ownership bit (bit 0): owned Box-backed values have bit 0
    // set, which would offset the pointer by 1 byte. Per Band 1 close
    // (§2.7.5), the bit-47 unified-heap discriminator no longer gates kind
    // decode — both shapes are raw `Box::into_raw` pointers, so we strip
    // the unified flag too to recover the canonical pointer.
    (bits & PAYLOAD_MASK & HEAP_PTR_MASK & !UNIFIED_HEAP_FLAG) as *const u8
}

// ============================================================================
// Result Type (Ok/Err) Helper Functions
// ============================================================================
//
// JIT-internal Ok/Err carriers. Each wraps a single u64 inner-bits payload
// in a `UnifiedValue<u64>` heap allocation with prefix kind=HK_OK/HK_ERR.
// The strict-typed `HeapValue::Reference` / typed-Result rebuild is in a
// later W10/Phase-2c sub-cluster; until then, JIT-emitted code stays on
// the raw-u64 wrapper shape per §2.7.5 stable-FFI rule.

#[inline]
pub fn is_ok_tag(bits: u64) -> bool {
    is_heap_kind(bits, HK_OK)
}

#[inline]
pub fn is_err_tag(bits: u64) -> bool {
    is_heap_kind(bits, HK_ERR)
}

#[inline]
pub fn is_result_tag(bits: u64) -> bool {
    is_ok_tag(bits) || is_err_tag(bits)
}

#[inline]
pub fn box_ok(inner_bits: u64) -> u64 {
    unified_box(HK_OK, inner_bits)
}

#[inline]
pub fn box_err(inner_bits: u64) -> u64 {
    unified_box(HK_ERR, inner_bits)
}

#[inline]
pub unsafe fn unbox_result_inner(bits: u64) -> u64 {
    *unsafe { unified_unbox::<u64>(bits) }
}

#[inline]
pub fn unbox_result_pointer(bits: u64) -> *const u64 {
    let ptr = unbox_heap_pointer(bits);
    if ptr.is_null() {
        std::ptr::null()
    } else {
        // Inner u64 sits at the `data` offset of the `UnifiedValue<u64>`
        // allocation per `jit_kinds::JIT_ALLOC_DATA_OFFSET`.
        unsafe { (ptr.add(super::jit_kinds::JIT_ALLOC_DATA_OFFSET)) as *const u64 }
    }
}

// ============================================================================
// Option Type (Some/None) Helper Functions
// ============================================================================

#[inline]
pub fn is_some_tag(bits: u64) -> bool {
    is_heap_kind(bits, HK_SOME)
}

#[inline]
pub fn is_none_tag(bits: u64) -> bool {
    bits == TAG_NULL
}

#[inline]
pub fn is_option_tag(bits: u64) -> bool {
    is_some_tag(bits) || is_none_tag(bits)
}

#[inline]
pub fn box_some(inner_bits: u64) -> u64 {
    unified_box(HK_SOME, inner_bits)
}

#[inline]
pub unsafe fn unbox_some_inner(bits: u64) -> u64 {
    *unsafe { unified_unbox::<u64>(bits) }
}

// ============================================================================
// Data Row Helper Functions
// ============================================================================

/// Box a row index as a data row reference using shared TAG_INT encoding.
#[inline]
pub const fn box_data_row(row_index: usize) -> u64 {
    TAG_DATA_ROW | ((row_index as u64) & PAYLOAD_MASK)
}

/// Extract the row index from a data row reference (TAG_INT payload).
#[inline]
pub const fn unbox_data_row(bits: u64) -> usize {
    (bits & PAYLOAD_MASK) as usize
}

/// Check if a value is a data row reference.
/// Data rows use the shared TAG_INT encoding (tag bits 50-48 == 0b001).
#[inline]
pub fn is_data_row(bits: u64) -> bool {
    is_tagged(bits) && get_tag(bits) == TAG_INT_BITS
}

// ============================================================================
// Column Reference Helper Functions
// ============================================================================

#[inline]
pub fn box_column_ref(ptr: *const f64, len: usize) -> u64 {
    unified_box(HK_COLUMN_REF, (ptr, len))
}

#[inline]
pub unsafe fn unbox_column_ref(bits: u64) -> (*const f64, usize) {
    *unsafe { unified_unbox::<(*const f64, usize)>(bits) }
}

#[inline]
pub fn is_column_ref(bits: u64) -> bool {
    is_heap_kind(bits, HK_COLUMN_REF)
}

/// Extract a `&[f64]` slice from a NaN-boxed column reference.
///
/// Returns `None` if `bits` is not a valid column reference, or if the
/// underlying pointer is null or the length is zero.
///
/// # Safety
/// `bits` must be a TAG_HEAP value whose payload points to a live
/// `UnifiedValue<(*const f64, usize)>`. The returned slice borrows from
/// the column data and must not outlive the column allocation.
#[inline]
pub unsafe fn extract_column(bits: u64) -> Option<&'static [f64]> {
    if !is_column_ref(bits) {
        return None;
    }
    let (ptr, len) = unsafe { unbox_column_ref(bits) };
    if ptr.is_null() || len == 0 {
        return None;
    }
    Some(unsafe { std::slice::from_raw_parts(ptr, len) })
}

/// Box a `Vec<f64>` as a new column reference.
///
/// Leaks the vector into a heap-allocated boxed slice and returns a
/// NaN-boxed column reference pointing to it. The caller is responsible
/// for eventually freeing the column.
#[inline]
pub fn box_column_result(data: Vec<f64>) -> u64 {
    let len = data.len();
    let leaked = Box::leak(data.into_boxed_slice());
    box_column_ref(leaked.as_ptr(), len)
}

// ============================================================================
// Typed Object Helper Functions
// ============================================================================

#[inline]
pub fn box_typed_object(ptr: *const u8) -> u64 {
    unified_box(HK_TYPED_OBJECT, ptr)
}

#[inline]
pub fn unbox_typed_object(bits: u64) -> *const u8 {
    *unsafe { unified_unbox::<*const u8>(bits) }
}

#[inline]
pub fn is_typed_object(bits: u64) -> bool {
    is_heap_kind(bits, HK_TYPED_OBJECT)
}

// ============================================================================
// String Helper Functions
// ============================================================================
//
// Per ADR-006 §2.2 / §2.3, strings live as `Arc<String>` in the v2 heap.
// JIT-side `box_string` wraps an `Arc<String>` in a `UnifiedValue<Arc<String>>`
// allocation with prefix kind=HK_STRING. `unbox_string` reads the prefix
// to recover the inner `Arc<String>` and borrows its `&str`.

/// Box a String as a unified heap string value.
#[inline]
pub fn box_string(s: String) -> u64 {
    unified_box(HK_STRING, Arc::new(s))
}

/// Box a &str as a unified heap string value.
#[inline]
pub fn box_str(s: &str) -> u64 {
    unified_box(HK_STRING, Arc::new(s.to_string()))
}

/// Read a string from a NaN-boxed heap value.
///
/// # Safety
/// `bits` must be a TAG_HEAP value pointing to a live
/// `UnifiedValue<Arc<String>>` allocation produced by `box_string` /
/// `box_str`, or a legacy `JitAlloc<String>` allocation.
#[inline]
pub unsafe fn unbox_string(bits: u64) -> &'static str {
    // The strict-typed JIT-FFI carries `Arc<String>` for HK_STRING-kinded
    // bits per §2.7.5 stable-FFI rule; the legacy `JitAlloc<String>` shape
    // remains for already-emitted JIT code that hasn't migrated to the
    // unified shape. Distinguish on the `kind: u16` prefix at offset 0
    // (which both shapes share — see `jit_kinds::read_heap_kind`).
    let arc: &Arc<String> = unsafe { unified_unbox::<Arc<String>>(bits) };
    arc.as_str()
}

// ============================================================================
// Tests
// ============================================================================

#[cfg(test)]
mod tests {
    use super::*;
    use super::super::jit_kinds::UnifiedValue;

    #[test]
    fn test_inline_types_in_negative_nan_space() {
        assert!(TAG_NULL & 0x8000_0000_0000_0000 != 0);
        assert!(TAG_BOOL_FALSE & 0x8000_0000_0000_0000 != 0);
        assert!(TAG_BOOL_TRUE & 0x8000_0000_0000_0000 != 0);
        assert!(TAG_UNIT & 0x8000_0000_0000_0000 != 0);
    }

    #[test]
    fn test_data_row_in_negative_nan_space() {
        assert!(TAG_DATA_ROW & 0x8000_0000_0000_0000 != 0);
        assert!(!is_number(TAG_DATA_ROW));
    }

    #[test]
    fn test_nan_base_detects_all_tags() {
        assert!(!is_number(TAG_NULL), "TAG_NULL should not be a number");
        assert!(
            !is_number(TAG_DATA_ROW),
            "TAG_DATA_ROW should not be a number"
        );
        assert!(
            !is_number(TAG_BOOL_TRUE),
            "TAG_BOOL_TRUE should not be a number"
        );

        // Plain f64 values should be detected as numbers
        assert!(is_number(box_number(3.14)));
        assert!(is_number(box_number(0.0)));
        assert!(is_number(box_number(-1.0)));
        assert!(is_number(box_number(f64::MAX)));
        assert!(is_number(box_number(f64::MIN)));
    }

    #[test]
    fn test_box_unbox_number() {
        let n = 3.14f64;
        let boxed = box_number(n);
        assert!(is_number(boxed));
        assert_eq!(unbox_number(boxed), n);
    }

    #[test]
    fn test_box_unbox_bool() {
        assert_eq!(box_bool(true), TAG_BOOL_TRUE);
        assert_eq!(box_bool(false), TAG_BOOL_FALSE);
    }

    #[test]
    fn test_box_function() {
        let bits = box_function(42);
        assert!(is_inline_function(bits));
        assert_eq!(unbox_function_id(bits), 42);
        assert!(!is_number(bits));
        assert!(!is_heap(bits));
    }

    #[test]
    fn test_data_row_round_trip() {
        let bits = box_data_row(999);
        assert!(is_data_row(bits));
        assert_eq!(unbox_data_row(bits), 999);
        assert!(!is_number(bits));
        assert!(!is_heap(bits));
    }

    /// `box_typed_object` produces a `UnifiedValue<*const u8>` heap
    /// allocation tagged with `HK_TYPED_OBJECT` at offset 0. Strict-typed
    /// rewrite of `test_typed_object_encoding` (W12-deleted-valuewordshape-
    /// tests-rewrite, 2026-05-12).
    ///
    /// Pre-rewrite the test asserted the deleted ValueWord-shape invariant
    /// `is_number(box_typed_object(p)) == false` and `is_typed_object(boxed)
    /// == true`. Under ADR-006 §2.7.5 the JIT-FFI carrier is
    /// `(bits, NativeKind)`: producers return raw `Box::into_raw(...) as u64`
    /// without NaN-box tag bits, so `is_number(boxed)` is true (raw pointer
    /// bits look like a plain f64) and `is_typed_object(boxed)` is false
    /// (`is_heap_kind` gates on `is_tagged` first). Discrimination flows
    /// through the parallel `NativeKind` companion stamped at JIT compile
    /// time — or, where the test needs to probe the JIT-internal heap
    /// allocation, via `read_heap_kind(bits)` which reads the `kind: u16`
    /// prefix at offset 0 of the allocation directly (per §2.7.5 "*not*
    /// tag-bit dispatch — it reads a field from a heap-resident struct
    /// that the producing call placed there").
    ///
    /// Same construction-side semantics expressed through the strict-typed
    /// predicate.
    #[test]
    fn test_typed_object_encoding_via_heap_kind_prefix() {
        let fake_ptr = 0x0000_1234_5678_0000u64 as *const u8;
        let boxed = box_typed_object(fake_ptr);
        // Construction-side contract: `box_typed_object` produces a
        // `UnifiedValue<*const u8>` allocation. The kind prefix at offset 0
        // is the strict-typed §2.7.5 discriminator.
        assert_ne!(boxed, 0, "allocation pointer is non-null");
        assert_eq!(
            unsafe { super::super::jit_kinds::read_heap_kind(boxed) },
            HK_TYPED_OBJECT,
            "heap-kind prefix at offset 0 discriminates the allocation"
        );

        // Round-trip via direct `unbox_typed_object`: reads the `data`
        // field of the `UnifiedValue<*const u8>` without gating on tag
        // bits, recovering the pointer the producer stored.
        let recovered = unbox_typed_object(boxed);
        assert_eq!(recovered, fake_ptr);

        // Clean up the UnifiedValue allocation directly. The deleted
        // ValueWord-shape clean-up went through `jit_typed_object_dec_ref`,
        // which itself gates on `is_typed_object(bits)` and is broken on
        // raw `Box::into_raw` pointers; using `heap_drop` is the §2.7.5
        // direct-path cleanup.
        unsafe { UnifiedValue::<*const u8>::heap_drop(boxed) };
    }

    /// Pairing a `KindedSlot` with a typed-object pointer is the
    /// runtime-tier `(slot, NativeKind)` carrier per ADR-006 §2.7.6 / Q8.
    /// Reflects the same construction-side contract `box_typed_object`
    /// expresses at the JIT-FFI tier, but using the bounded carrier API
    /// from `shape-value`. Same test semantics as the deleted
    /// `is_typed_object(boxed) == true` invariant, expressed at the
    /// strict-typed carrier layer where the discriminator IS the kind
    /// label (no tag-bit probe).
    #[test]
    fn test_typed_object_kinded_slot_discriminates_via_kind_label() {
        use shape_value::{HeapKind, KindedSlot, NativeKind, TypedObjectStorage, ValueSlot};
        use std::sync::Arc;

        // W5 v0.3 fix (2026-05-17): migrated to the v2-raw `_new` carrier
        // per `executor/objects/property_access.rs::length_typed_object_empty`
        // rationale. The post-Wave-2-D1 `KindedSlot::Drop` for
        // `Ptr(HeapKind::TypedObject)` uses `release_elem` →
        // `std::alloc::dealloc(ptr, Layout::new::<TypedObjectStorage>())`
        // — incompatible with `Arc::new` allocator provenance.
        //
        // Build a minimal `TypedObjectStorage` via the v2-raw allocator —
        // the canonical production carrier shape per ADR-006 §2.3 +
        // §2.7.24. The slot's Drop now runs through `release_elem` →
        // `v2_release` → `_drop` cleanly when the test's `slot` falls out
        // of scope at the closing brace.
        let ptr = TypedObjectStorage::_new(
            0,
            Vec::<ValueSlot>::new().into_boxed_slice(),
            0,
            Arc::from(Vec::<NativeKind>::new().into_boxed_slice()),
        );
        let slot = KindedSlot::from_typed_object_raw(ptr);

        // §2.7.6 / Q8: the kind label discriminates the slot — no tag-bit
        // probe required (and tag-bit probes don't exist post-strict-
        // typing). Construction-side contract holds.
        assert_eq!(slot.kind(), NativeKind::Ptr(HeapKind::TypedObject));
        // The matching heap discriminator for a v2-raw
        // `*const TypedObjectStorage` slot is `HeapKind::TypedObject` —
        // the §2.7.5/Q8 bounded-carrier API exposes one constructor per
        // kind variant, mirror-matching the heap arm.
    }
}