shape_jit/ffi/value_ffi.rs
1//! JIT-side value-encoding helpers (NaN-box layout used by JIT-emitted code).
2//!
3//! Per ADR-006 §2.7.5, the JIT FFI boundary carries raw `u64` plus a parallel
4//! `NativeKind` companion stamped at JIT compile time from the call signature.
5//! The constants and helpers in this module are JIT-internal: they encode the
6//! sentinel u64 layout that JIT-emitted Cranelift code uses for inline scalars
7//! (`TAG_NULL`, `TAG_BOOL_*`, `TAG_UNIT`, `TAG_DATA_ROW`) and the JitAlloc /
8//! `UnifiedValue` pointer shape for heap values.
9//!
10//! The deleted `shape_value::tag_bits::*`, `shape_value::ValueWord*`,
11//! `shape_value::ValueBits`, `shape_value::unified_string`, and
12//! `shape_value::unified_wrapper` references that this file previously
13//! relied on were retired by the strict-typing bulldozer (Phase 2). The
14//! tag constants below are defined locally with the exact u64 layout the
15//! JIT-emitted code already targets — they are not a "tag_bits restoration
16//! shim" (forbidden per W10 playbook §3) but the JIT-internal sentinel
17//! encoding that survives §2.7.5's stable-FFI rule (raw u64 ABI, no
18//! runtime kind discrimination from the bits themselves; consumers that
19//! need a runtime-tier carrier wrap the bits as
20//! `KindedSlot::new(ValueSlot::from_raw(bits), kind)` per §2.7.5/Q7).
21//!
22//! Heap-pointer values produced by `box_string` / `box_ok` / `box_err` /
23//! `box_some` / `box_typed_object` / `box_column_ref` use the
24//! `jit_kinds::unified_box` shape: a `UnifiedValue<T>` heap allocation with
25//! a `kind: u16` prefix at offset 0, readable via
26//! `jit_kinds::read_heap_kind` (per §2.7.5: this is *not* tag-bit dispatch —
27//! it reads a field from a heap-resident struct). The `HK_*` constants
28//! mirror `HeapKind` ordinals (cast to `u16`) for use as the prefix.
29
30use shape_value::HeapKind;
31use std::sync::Arc;
32
33use super::jit_kinds::{read_heap_kind, unified_box, unified_unbox};
34
35// ============================================================================
36// JIT-internal NaN-box sentinel layout
37// ============================================================================
38//
39// Inline scalars (null, bool, unit, data-row, function-id) ride in negative
40// NaN space (sign bit = 1). The 3-bit tag at bits 50-48 selects the inline
41// shape; the low 48 bits carry the payload. This layout is local to the JIT
42// (no `shape_value::tag_bits` import) — it is the shape JIT-emitted Cranelift
43// code references via `iconst(types::I64, TAG_NULL as i64)` etc., kept stable
44// so existing JIT-emitted code keeps working through the W10 consumer
45// migration cascade.
46
47/// NaN base: all 1s in exponent (bits 62-52). Used for number detection.
48pub const NAN_BASE: u64 = 0x7FF0_0000_0000_0000;
49
50/// 16-bit tag mask -- used for legacy positive-NaN tag discrimination in translator IR.
51pub const TAG_MASK: u64 = 0xFFFF_0000_0000_0000;
52
53/// Tagged-value base: negative-NaN exponent + sign bit.
54pub const TAG_BASE: u64 = 0xFFF8_0000_0000_0000;
55
56/// Bit shift for the 3-bit inline-tag field at bits 50-48.
57pub const TAG_SHIFT: u32 = 48;
58
59/// 48-bit payload mask.
60pub const PAYLOAD_MASK: u64 = 0x0000_FFFF_FFFF_FFFF;
61
62/// IEEE-754 canonical quiet NaN (positive sign).
63pub const CANONICAL_NAN: u64 = 0x7FF8_0000_0000_0000;
64
65/// `i48` payload range — JIT inline-int encoding fits in 48 bits.
66pub const I48_MAX: i64 = (1_i64 << 47) - 1;
67pub const I48_MIN: i64 = -(1_i64 << 47);
68
69/// Bit-47 marker for unified-heap pointers; legacy bit retained for the
70/// JIT consumer migration window where some helpers still discriminate the
71/// pointer shape. Per Band 1 close (§2.7.5): the discriminator no longer
72/// gates kind decode — both shapes are raw `Box::into_raw` pointers and
73/// the kind flows through the parallel `NativeKind` companion.
74pub const UNIFIED_HEAP_FLAG: u64 = 1 << 47;
75pub const UNIFIED_PTR_MASK: u64 = PAYLOAD_MASK & !UNIFIED_HEAP_FLAG;
76
77/// Low ownership bit cleared on heap-pointer reads.
78const HEAP_OWNED_BIT: u64 = 1;
79pub const HEAP_PTR_MASK: u64 = !HEAP_OWNED_BIT;
80
81// 3-bit inline tags at bits 50-48 (private — JIT-internal naming carries
82// `_BITS` suffix to free the unsuffixed names for the public sentinel values
83// callers reference, e.g. `TAG_NULL` / `TAG_NONE` / `TAG_UNIT`).
84const TAG_HEAP_BITS: u64 = 0b000;
85const TAG_INT_BITS: u64 = 0b001;
86const TAG_BOOL_BITS: u64 = 0b010;
87const TAG_NONE_BITS: u64 = 0b011;
88const TAG_UNIT_BITS: u64 = 0b100;
89const TAG_FUNCTION_BITS: u64 = 0b101;
90
91#[inline]
92const fn make_tagged(tag: u64, payload: u64) -> u64 {
93 TAG_BASE | (tag << TAG_SHIFT) | (payload & PAYLOAD_MASK)
94}
95
96#[inline]
97fn is_tagged(bits: u64) -> bool {
98 bits & TAG_BASE == TAG_BASE
99}
100
101#[inline]
102fn get_tag(bits: u64) -> u64 {
103 (bits >> TAG_SHIFT) & 0b111
104}
105
106// ============================================================================
107// Inline types -- shared scheme (TAG_BASE space, sign=1, negative NaN)
108// ============================================================================
109
110/// Null/None value. Uses shared TAG_NONE (0b011).
111pub const TAG_NULL: u64 = make_tagged(TAG_NONE_BITS, 0);
112
113/// Boolean false. Uses shared TAG_BOOL (0b010) with payload 0.
114pub const TAG_BOOL_FALSE: u64 = make_tagged(TAG_BOOL_BITS, 0);
115
116/// Boolean true. Uses shared TAG_BOOL (0b010) with payload 1.
117pub const TAG_BOOL_TRUE: u64 = make_tagged(TAG_BOOL_BITS, 1);
118
119/// Unit (void return). Uses shared TAG_UNIT (0b100).
120pub const TAG_UNIT: u64 = make_tagged(TAG_UNIT_BITS, 0);
121
122/// None sentinel — alias for `TAG_NULL` (`Option::None` JIT representation).
123/// Re-exported under `TAG_NONE` for legacy callers.
124pub const TAG_NONE: u64 = TAG_NULL;
125
126/// Number tag sentinel (not a real tag -- numbers are plain f64).
127pub const TAG_NUMBER: u64 = 0x0000_0000_0000_0000;
128
129// ============================================================================
130// Data row encoding -- uses shared TAG_INT (negative NaN space)
131// ============================================================================
132
133/// Data row tag: uses the shared TAG_INT (0b001) encoding in negative NaN space.
134/// Row indices are stored as i48 in the 48-bit payload.
135pub const TAG_DATA_ROW: u64 = TAG_BASE | (TAG_INT_BITS << TAG_SHIFT);
136
137// ============================================================================
138// Heap Kind shortcuts (HK_*)
139//
140// Use these as the `kind: u16` prefix on `unified_box` / `jit_box`
141// allocations: `unified_box(HK_STRING, Arc::new(s))`. Match arms read the
142// prefix back via `jit_kinds::read_heap_kind(bits)`.
143// ============================================================================
144//
145// Two-tier layout (W17-jit-legacy-ordinal-disambiguation, 2026-05-12,
146// phase-2d-hardening item (i)):
147//
148// Tier 1 — canonical kinds aliased to `HeapKind as u16` (ordinal range 0..127):
149// HK_STRING, HK_TYPED_OBJECT, HK_CLOSURE, HK_DECIMAL, HK_BIG_INT,
150// HK_DATATABLE, HK_HASHMAP, HK_FUTURE, HK_TASK_GROUP, HK_FILTER_EXPR.
151// These ARE the runtime `HeapKind` discriminator; producers / consumers
152// using these constants speak the same kind label as the runtime tier.
153//
154// Tier 2 — JIT-private ordinals (range 256..511):
155// Every other HK_* constant. These label `JitAlloc<T>` / `UnifiedValue<T>`
156// heap prefixes for JIT-internal values whose `T` payload type is
157// determined by the producing call and consumed by sibling JIT arms
158// pattern-matching the same `HK_*` prefix. They do NOT cross the JIT FFI
159// boundary as runtime `HeapKind` labels; they MUST stay outside the
160// `HeapKind as u16` range so a stray runtime-tier slot that does cross
161// the boundary (e.g. via the `jit_bits_to_nanboxed` / `nanboxed_to_jit_bits`
162// carrier when those land per W11) cannot collide with a JIT-internal
163// `JitAlloc<T>` prefix.
164//
165// JIT-private base. Chosen so that the entire JIT-private block sits above
166// the `HeapKind as u16` representable range *and* above the existing
167// `jit_kinds::HK_JIT_*` block (128..132) / `v2_struct::HK_V2_TYPED_STRUCT`
168// (132). HeapKind can grow to 255 variants before the boundary needs to
169// move; ample headroom.
170pub const JIT_LEGACY_HK_BASE: u16 = 256;
171
172// ----------------------------------------------------------------------------
173// Tier 1 — canonical HeapKind-aliased
174// ----------------------------------------------------------------------------
175pub const HK_STRING: u16 = HeapKind::String as u16;
176pub const HK_TYPED_OBJECT: u16 = HeapKind::TypedObject as u16;
177pub const HK_CLOSURE: u16 = HeapKind::Closure as u16;
178pub const HK_DECIMAL: u16 = HeapKind::Decimal as u16;
179pub const HK_BIG_INT: u16 = HeapKind::BigInt as u16;
180pub const HK_DATATABLE: u16 = HeapKind::DataTable as u16;
181pub const HK_HASHMAP: u16 = HeapKind::HashMap as u16;
182pub const HK_FUTURE: u16 = HeapKind::Future as u16;
183pub const HK_TASK_GROUP: u16 = HeapKind::TaskGroup as u16;
184pub const HK_FILTER_EXPR: u16 = HeapKind::FilterExpr as u16;
185
186// ----------------------------------------------------------------------------
187// Tier 2 — JIT-private kinds (no surviving HeapValue arm; JIT-emitted-and-
188// JIT-consumed only). Contiguous block starting at JIT_LEGACY_HK_BASE so the
189// CHECK 12 grep guard in verify-merge.sh can assert every JIT-private HK_*
190// constant sits at or above the base.
191// ----------------------------------------------------------------------------
192pub const HK_ARRAY: u16 = JIT_LEGACY_HK_BASE; // 256 — was 1 (collided HeapKind::TypedObject)
193pub const HK_HOST_CLOSURE: u16 = JIT_LEGACY_HK_BASE + 1; // 257 — was 6
194pub const HK_TYPED_TABLE: u16 = JIT_LEGACY_HK_BASE + 2; // 258 — was 8 (collided HeapKind::TypedArray)
195pub const HK_ROW_VIEW: u16 = JIT_LEGACY_HK_BASE + 3; // 259 — was 9 (collided HeapKind::Temporal)
196pub const HK_COLUMN_REF: u16 = JIT_LEGACY_HK_BASE + 4; // 260 — was 10 (collided HeapKind::TableView)
197pub const HK_INDEXED_TABLE: u16 = JIT_LEGACY_HK_BASE + 5; // 261 — was 11 (collided HeapKind::Content)
198pub const HK_RANGE: u16 = JIT_LEGACY_HK_BASE + 6; // 262 — was 12 (collided HeapKind::Instant)
199pub const HK_ENUM: u16 = JIT_LEGACY_HK_BASE + 7; // 263 — was 13 (collided HeapKind::IoHandle)
200pub const HK_SOME: u16 = JIT_LEGACY_HK_BASE + 8; // 264 — was 14 (collided HeapKind::NativeScalar)
201pub const HK_OK: u16 = JIT_LEGACY_HK_BASE + 9; // 265 — was 15 (collided HeapKind::NativeView)
202pub const HK_ERR: u16 = JIT_LEGACY_HK_BASE + 10; // 266 — was 16 (collided HeapKind::Char)
203pub const HK_TRAIT_OBJECT: u16 = JIT_LEGACY_HK_BASE + 11; // 267 — was 19 (collided HeapKind::Reference)
204pub const HK_EXPR_PROXY: u16 = JIT_LEGACY_HK_BASE + 12; // 268 — was 20 (collided HeapKind::SharedCell)
205pub const HK_TIME: u16 = JIT_LEGACY_HK_BASE + 13; // 269 — was 22 (collided HeapKind::Iterator)
206pub const HK_DURATION: u16 = JIT_LEGACY_HK_BASE + 14; // 270 — was 23 (collided HeapKind::Deque)
207pub const HK_TIMESPAN: u16 = JIT_LEGACY_HK_BASE + 15; // 271 — was 24 (collided HeapKind::Channel)
208pub const HK_TIMEFRAME: u16 = JIT_LEGACY_HK_BASE + 16; // 272 — was 25 (collided HeapKind::PriorityQueue)
209pub const HK_TIME_REFERENCE: u16 = JIT_LEGACY_HK_BASE + 17; // 273 — was 26 (collided HeapKind::Range)
210pub const HK_DATETIME_EXPR: u16 = JIT_LEGACY_HK_BASE + 18; // 274 — was 27 (collided HeapKind::Result)
211pub const HK_DATA_DATETIME_REF: u16 = JIT_LEGACY_HK_BASE + 19; // 275 — was 28 (collided HeapKind::Option)
212pub const HK_TYPE_ANNOTATION: u16 = JIT_LEGACY_HK_BASE + 20; // 276 — was 29 (collided HeapKind::TraitObject)
213pub const HK_TYPE_ANNOTATED_VALUE: u16 = JIT_LEGACY_HK_BASE + 21; // 277 — was 30 (collided HeapKind::Mutex)
214pub const HK_PRINT_RESULT: u16 = JIT_LEGACY_HK_BASE + 22; // 278 — was 31 (collided HeapKind::Atomic)
215pub const HK_SIMULATION_CALL: u16 = JIT_LEGACY_HK_BASE + 23; // 279 — was 32 (collided HeapKind::Lazy)
216pub const HK_FUNCTION_REF: u16 = JIT_LEGACY_HK_BASE + 24; // 280 — was 33 (collided HeapKind::ModuleFn)
217pub const HK_DATA_REFERENCE: u16 = JIT_LEGACY_HK_BASE + 25; // 281 — was 34 (one above current HeapKind tail; bumped pre-emptively)
218pub const HK_INT_ARRAY: u16 = JIT_LEGACY_HK_BASE + 26; // 282 — was 48 (above current HeapKind range; bumped to preserve invariant)
219pub const HK_FLOAT_ARRAY: u16 = JIT_LEGACY_HK_BASE + 27; // 283 — was 49
220pub const HK_BOOL_ARRAY: u16 = JIT_LEGACY_HK_BASE + 28; // 284 — was 50
221pub const HK_MATRIX: u16 = JIT_LEGACY_HK_BASE + 29; // 285 — was 51
222pub const HK_I8_ARRAY: u16 = JIT_LEGACY_HK_BASE + 30; // 286 — was 57
223pub const HK_I16_ARRAY: u16 = JIT_LEGACY_HK_BASE + 31; // 287 — was 58
224pub const HK_I32_ARRAY: u16 = JIT_LEGACY_HK_BASE + 32; // 288 — was 59
225pub const HK_U8_ARRAY: u16 = JIT_LEGACY_HK_BASE + 33; // 289 — was 60
226pub const HK_U16_ARRAY: u16 = JIT_LEGACY_HK_BASE + 34; // 290 — was 61
227pub const HK_U32_ARRAY: u16 = JIT_LEGACY_HK_BASE + 35; // 291 — was 62
228pub const HK_U64_ARRAY: u16 = JIT_LEGACY_HK_BASE + 36; // 292 — was 63
229pub const HK_F32_ARRAY: u16 = JIT_LEGACY_HK_BASE + 37; // 293 — was 64
230pub const HK_FLOAT_ARRAY_SLICE: u16 = JIT_LEGACY_HK_BASE + 38; // 294 — was 71
231
232// Compile-time invariants for the JIT-private block:
233// * base sits strictly above the `HeapKind as u16` representable range;
234// * base sits strictly above the existing JIT-private blocks in
235// `jit_kinds.rs` (128..132) and `v2_struct.rs` (132).
236const _: () = {
237 // 192 = current HeapKind tail (33) plus headroom for the existing
238 // 128..132 JIT-private block; if HeapKind grows past 127 a future
239 // sub-cluster must move JIT_LEGACY_HK_BASE up and renumber.
240 assert!(
241 JIT_LEGACY_HK_BASE >= 192,
242 "JIT_LEGACY_HK_BASE must sit above the HeapKind / jit_kinds.rs / v2_struct.rs blocks"
243 );
244};
245
246// Compile-time layout verification
247const _: () = {
248 // Verify inline types use the shared scheme (negative NaN, sign bit = 1)
249 assert!(
250 TAG_NULL & 0x8000_0000_0000_0000 != 0,
251 "TAG_NULL must be in negative NaN space"
252 );
253 assert!(
254 TAG_BOOL_FALSE & 0x8000_0000_0000_0000 != 0,
255 "TAG_BOOL must be in negative NaN space"
256 );
257 assert!(
258 TAG_UNIT & 0x8000_0000_0000_0000 != 0,
259 "TAG_UNIT must be in negative NaN space"
260 );
261 assert!(
262 TAG_DATA_ROW & 0x8000_0000_0000_0000 != 0,
263 "TAG_DATA_ROW must be in negative NaN space"
264 );
265};
266
267// ============================================================================
268// Core Helper Functions
269// ============================================================================
270
271/// Check if a value is a plain f64 number (not NaN-boxed with any tag).
272/// All tags live in negative NaN space (sign bit = 1).
273#[inline]
274pub fn is_number(bits: u64) -> bool {
275 !is_tagged(bits)
276}
277
278/// Unbox a number (assumes value is a number -- check with `is_number()` first).
279#[inline]
280pub fn unbox_number(bits: u64) -> f64 {
281 f64::from_bits(bits)
282}
283
284/// Box a number into a NaN-boxed u64.
285#[inline]
286pub const fn box_number(n: f64) -> u64 {
287 f64::to_bits(n)
288}
289
290/// Box a boolean into a NaN-boxed u64 (shared scheme).
291#[inline]
292pub const fn box_bool(b: bool) -> u64 {
293 if b { TAG_BOOL_TRUE } else { TAG_BOOL_FALSE }
294}
295
296/// Box an inline function reference (shared TAG_FUNCTION, payload = function_id).
297#[inline]
298pub fn box_function(fn_id: u16) -> u64 {
299 make_tagged(TAG_FUNCTION_BITS, fn_id as u64)
300}
301
302/// Check if a value is an inline function reference.
303#[inline]
304pub fn is_inline_function(bits: u64) -> bool {
305 is_tagged(bits) && get_tag(bits) == TAG_FUNCTION_BITS
306}
307
308/// Extract function_id from an inline function reference.
309#[inline]
310pub fn unbox_function_id(bits: u64) -> u16 {
311 (bits & PAYLOAD_MASK) as u16
312}
313
314// ============================================================================
315// TAG_HEAP helpers -- unified heap value management
316// ============================================================================
317
318/// Check if a value has TAG_HEAP (tag bits 50-48 == 0, in negative NaN space).
319#[inline]
320pub fn is_heap(bits: u64) -> bool {
321 is_tagged(bits) && get_tag(bits) == TAG_HEAP_BITS
322}
323
324/// Get the heap kind of a value, or None if not a heap value.
325///
326/// Reads the `kind: u16` prefix at offset 0 of the underlying `JitAlloc` /
327/// `UnifiedValue` allocation per ADR-006 §2.7.5 (this is *not* tag-bit
328/// dispatch — it reads a field from a heap-resident struct that the
329/// producing call placed there).
330#[inline]
331pub fn heap_kind(bits: u64) -> Option<u16> {
332 if !is_heap(bits) {
333 return None;
334 }
335 Some(unsafe { read_heap_kind(unbox_heap_pointer(bits) as u64) })
336}
337
338/// Check if a value is a heap value with a specific kind.
339#[inline]
340pub fn is_heap_kind(bits: u64, expected_kind: u16) -> bool {
341 heap_kind(bits) == Some(expected_kind)
342}
343
344/// Extract the raw pointer from a TAG_HEAP value (points to JitAlloc header).
345#[inline]
346pub fn unbox_heap_pointer(bits: u64) -> *const u8 {
347 // Mask off the ownership bit (bit 0): owned Box-backed values have bit 0
348 // set, which would offset the pointer by 1 byte. Per Band 1 close
349 // (§2.7.5), the bit-47 unified-heap discriminator no longer gates kind
350 // decode — both shapes are raw `Box::into_raw` pointers, so we strip
351 // the unified flag too to recover the canonical pointer.
352 (bits & PAYLOAD_MASK & HEAP_PTR_MASK & !UNIFIED_HEAP_FLAG) as *const u8
353}
354
355// ============================================================================
356// Result Type (Ok/Err) Helper Functions
357// ============================================================================
358//
359// JIT-internal Ok/Err carriers. Each wraps a single u64 inner-bits payload
360// in a `UnifiedValue<u64>` heap allocation with prefix kind=HK_OK/HK_ERR.
361// The strict-typed `HeapValue::Reference` / typed-Result rebuild is in a
362// later W10/Phase-2c sub-cluster; until then, JIT-emitted code stays on
363// the raw-u64 wrapper shape per §2.7.5 stable-FFI rule.
364
365#[inline]
366pub fn is_ok_tag(bits: u64) -> bool {
367 is_heap_kind(bits, HK_OK)
368}
369
370#[inline]
371pub fn is_err_tag(bits: u64) -> bool {
372 is_heap_kind(bits, HK_ERR)
373}
374
375#[inline]
376pub fn is_result_tag(bits: u64) -> bool {
377 is_ok_tag(bits) || is_err_tag(bits)
378}
379
380#[inline]
381pub fn box_ok(inner_bits: u64) -> u64 {
382 unified_box(HK_OK, inner_bits)
383}
384
385#[inline]
386pub fn box_err(inner_bits: u64) -> u64 {
387 unified_box(HK_ERR, inner_bits)
388}
389
390#[inline]
391pub unsafe fn unbox_result_inner(bits: u64) -> u64 {
392 *unsafe { unified_unbox::<u64>(bits) }
393}
394
395#[inline]
396pub fn unbox_result_pointer(bits: u64) -> *const u64 {
397 let ptr = unbox_heap_pointer(bits);
398 if ptr.is_null() {
399 std::ptr::null()
400 } else {
401 // Inner u64 sits at the `data` offset of the `UnifiedValue<u64>`
402 // allocation per `jit_kinds::JIT_ALLOC_DATA_OFFSET`.
403 unsafe { (ptr.add(super::jit_kinds::JIT_ALLOC_DATA_OFFSET)) as *const u64 }
404 }
405}
406
407// ============================================================================
408// Option Type (Some/None) Helper Functions
409// ============================================================================
410
411#[inline]
412pub fn is_some_tag(bits: u64) -> bool {
413 is_heap_kind(bits, HK_SOME)
414}
415
416#[inline]
417pub fn is_none_tag(bits: u64) -> bool {
418 bits == TAG_NULL
419}
420
421#[inline]
422pub fn is_option_tag(bits: u64) -> bool {
423 is_some_tag(bits) || is_none_tag(bits)
424}
425
426#[inline]
427pub fn box_some(inner_bits: u64) -> u64 {
428 unified_box(HK_SOME, inner_bits)
429}
430
431#[inline]
432pub unsafe fn unbox_some_inner(bits: u64) -> u64 {
433 *unsafe { unified_unbox::<u64>(bits) }
434}
435
436// ============================================================================
437// Data Row Helper Functions
438// ============================================================================
439
440/// Box a row index as a data row reference using shared TAG_INT encoding.
441#[inline]
442pub const fn box_data_row(row_index: usize) -> u64 {
443 TAG_DATA_ROW | ((row_index as u64) & PAYLOAD_MASK)
444}
445
446/// Extract the row index from a data row reference (TAG_INT payload).
447#[inline]
448pub const fn unbox_data_row(bits: u64) -> usize {
449 (bits & PAYLOAD_MASK) as usize
450}
451
452/// Check if a value is a data row reference.
453/// Data rows use the shared TAG_INT encoding (tag bits 50-48 == 0b001).
454#[inline]
455pub fn is_data_row(bits: u64) -> bool {
456 is_tagged(bits) && get_tag(bits) == TAG_INT_BITS
457}
458
459// ============================================================================
460// Column Reference Helper Functions
461// ============================================================================
462
463#[inline]
464pub fn box_column_ref(ptr: *const f64, len: usize) -> u64 {
465 unified_box(HK_COLUMN_REF, (ptr, len))
466}
467
468#[inline]
469pub unsafe fn unbox_column_ref(bits: u64) -> (*const f64, usize) {
470 *unsafe { unified_unbox::<(*const f64, usize)>(bits) }
471}
472
473#[inline]
474pub fn is_column_ref(bits: u64) -> bool {
475 is_heap_kind(bits, HK_COLUMN_REF)
476}
477
478/// Extract a `&[f64]` slice from a NaN-boxed column reference.
479///
480/// Returns `None` if `bits` is not a valid column reference, or if the
481/// underlying pointer is null or the length is zero.
482///
483/// # Safety
484/// `bits` must be a TAG_HEAP value whose payload points to a live
485/// `UnifiedValue<(*const f64, usize)>`. The returned slice borrows from
486/// the column data and must not outlive the column allocation.
487#[inline]
488pub unsafe fn extract_column(bits: u64) -> Option<&'static [f64]> {
489 if !is_column_ref(bits) {
490 return None;
491 }
492 let (ptr, len) = unsafe { unbox_column_ref(bits) };
493 if ptr.is_null() || len == 0 {
494 return None;
495 }
496 Some(unsafe { std::slice::from_raw_parts(ptr, len) })
497}
498
499/// Box a `Vec<f64>` as a new column reference.
500///
501/// Leaks the vector into a heap-allocated boxed slice and returns a
502/// NaN-boxed column reference pointing to it. The caller is responsible
503/// for eventually freeing the column.
504#[inline]
505pub fn box_column_result(data: Vec<f64>) -> u64 {
506 let len = data.len();
507 let leaked = Box::leak(data.into_boxed_slice());
508 box_column_ref(leaked.as_ptr(), len)
509}
510
511// ============================================================================
512// Typed Object Helper Functions
513// ============================================================================
514
515#[inline]
516pub fn box_typed_object(ptr: *const u8) -> u64 {
517 unified_box(HK_TYPED_OBJECT, ptr)
518}
519
520#[inline]
521pub fn unbox_typed_object(bits: u64) -> *const u8 {
522 *unsafe { unified_unbox::<*const u8>(bits) }
523}
524
525#[inline]
526pub fn is_typed_object(bits: u64) -> bool {
527 is_heap_kind(bits, HK_TYPED_OBJECT)
528}
529
530// ============================================================================
531// String Helper Functions
532// ============================================================================
533//
534// Per ADR-006 §2.2 / §2.3, strings live as `Arc<String>` in the v2 heap.
535// JIT-side `box_string` wraps an `Arc<String>` in a `UnifiedValue<Arc<String>>`
536// allocation with prefix kind=HK_STRING. `unbox_string` reads the prefix
537// to recover the inner `Arc<String>` and borrows its `&str`.
538
539/// Box a String as a unified heap string value.
540#[inline]
541pub fn box_string(s: String) -> u64 {
542 unified_box(HK_STRING, Arc::new(s))
543}
544
545/// Box a &str as a unified heap string value.
546#[inline]
547pub fn box_str(s: &str) -> u64 {
548 unified_box(HK_STRING, Arc::new(s.to_string()))
549}
550
551/// Read a string from a NaN-boxed heap value.
552///
553/// # Safety
554/// `bits` must be a TAG_HEAP value pointing to a live
555/// `UnifiedValue<Arc<String>>` allocation produced by `box_string` /
556/// `box_str`, or a legacy `JitAlloc<String>` allocation.
557#[inline]
558pub unsafe fn unbox_string(bits: u64) -> &'static str {
559 // The strict-typed JIT-FFI carries `Arc<String>` for HK_STRING-kinded
560 // bits per §2.7.5 stable-FFI rule; the legacy `JitAlloc<String>` shape
561 // remains for already-emitted JIT code that hasn't migrated to the
562 // unified shape. Distinguish on the `kind: u16` prefix at offset 0
563 // (which both shapes share — see `jit_kinds::read_heap_kind`).
564 let arc: &Arc<String> = unsafe { unified_unbox::<Arc<String>>(bits) };
565 arc.as_str()
566}
567
568// ============================================================================
569// Tests
570// ============================================================================
571
572#[cfg(test)]
573mod tests {
574 use super::*;
575 use super::super::jit_kinds::UnifiedValue;
576
577 #[test]
578 fn test_inline_types_in_negative_nan_space() {
579 assert!(TAG_NULL & 0x8000_0000_0000_0000 != 0);
580 assert!(TAG_BOOL_FALSE & 0x8000_0000_0000_0000 != 0);
581 assert!(TAG_BOOL_TRUE & 0x8000_0000_0000_0000 != 0);
582 assert!(TAG_UNIT & 0x8000_0000_0000_0000 != 0);
583 }
584
585 #[test]
586 fn test_data_row_in_negative_nan_space() {
587 assert!(TAG_DATA_ROW & 0x8000_0000_0000_0000 != 0);
588 assert!(!is_number(TAG_DATA_ROW));
589 }
590
591 #[test]
592 fn test_nan_base_detects_all_tags() {
593 assert!(!is_number(TAG_NULL), "TAG_NULL should not be a number");
594 assert!(
595 !is_number(TAG_DATA_ROW),
596 "TAG_DATA_ROW should not be a number"
597 );
598 assert!(
599 !is_number(TAG_BOOL_TRUE),
600 "TAG_BOOL_TRUE should not be a number"
601 );
602
603 // Plain f64 values should be detected as numbers
604 assert!(is_number(box_number(3.14)));
605 assert!(is_number(box_number(0.0)));
606 assert!(is_number(box_number(-1.0)));
607 assert!(is_number(box_number(f64::MAX)));
608 assert!(is_number(box_number(f64::MIN)));
609 }
610
611 #[test]
612 fn test_box_unbox_number() {
613 let n = 3.14f64;
614 let boxed = box_number(n);
615 assert!(is_number(boxed));
616 assert_eq!(unbox_number(boxed), n);
617 }
618
619 #[test]
620 fn test_box_unbox_bool() {
621 assert_eq!(box_bool(true), TAG_BOOL_TRUE);
622 assert_eq!(box_bool(false), TAG_BOOL_FALSE);
623 }
624
625 #[test]
626 fn test_box_function() {
627 let bits = box_function(42);
628 assert!(is_inline_function(bits));
629 assert_eq!(unbox_function_id(bits), 42);
630 assert!(!is_number(bits));
631 assert!(!is_heap(bits));
632 }
633
634 #[test]
635 fn test_data_row_round_trip() {
636 let bits = box_data_row(999);
637 assert!(is_data_row(bits));
638 assert_eq!(unbox_data_row(bits), 999);
639 assert!(!is_number(bits));
640 assert!(!is_heap(bits));
641 }
642
643 /// `box_typed_object` produces a `UnifiedValue<*const u8>` heap
644 /// allocation tagged with `HK_TYPED_OBJECT` at offset 0. Strict-typed
645 /// rewrite of `test_typed_object_encoding` (W12-deleted-valuewordshape-
646 /// tests-rewrite, 2026-05-12).
647 ///
648 /// Pre-rewrite the test asserted the deleted ValueWord-shape invariant
649 /// `is_number(box_typed_object(p)) == false` and `is_typed_object(boxed)
650 /// == true`. Under ADR-006 §2.7.5 the JIT-FFI carrier is
651 /// `(bits, NativeKind)`: producers return raw `Box::into_raw(...) as u64`
652 /// without NaN-box tag bits, so `is_number(boxed)` is true (raw pointer
653 /// bits look like a plain f64) and `is_typed_object(boxed)` is false
654 /// (`is_heap_kind` gates on `is_tagged` first). Discrimination flows
655 /// through the parallel `NativeKind` companion stamped at JIT compile
656 /// time — or, where the test needs to probe the JIT-internal heap
657 /// allocation, via `read_heap_kind(bits)` which reads the `kind: u16`
658 /// prefix at offset 0 of the allocation directly (per §2.7.5 "*not*
659 /// tag-bit dispatch — it reads a field from a heap-resident struct
660 /// that the producing call placed there").
661 ///
662 /// Same construction-side semantics expressed through the strict-typed
663 /// predicate.
664 #[test]
665 fn test_typed_object_encoding_via_heap_kind_prefix() {
666 let fake_ptr = 0x0000_1234_5678_0000u64 as *const u8;
667 let boxed = box_typed_object(fake_ptr);
668 // Construction-side contract: `box_typed_object` produces a
669 // `UnifiedValue<*const u8>` allocation. The kind prefix at offset 0
670 // is the strict-typed §2.7.5 discriminator.
671 assert_ne!(boxed, 0, "allocation pointer is non-null");
672 assert_eq!(
673 unsafe { super::super::jit_kinds::read_heap_kind(boxed) },
674 HK_TYPED_OBJECT,
675 "heap-kind prefix at offset 0 discriminates the allocation"
676 );
677
678 // Round-trip via direct `unbox_typed_object`: reads the `data`
679 // field of the `UnifiedValue<*const u8>` without gating on tag
680 // bits, recovering the pointer the producer stored.
681 let recovered = unbox_typed_object(boxed);
682 assert_eq!(recovered, fake_ptr);
683
684 // Clean up the UnifiedValue allocation directly. The deleted
685 // ValueWord-shape clean-up went through `jit_typed_object_dec_ref`,
686 // which itself gates on `is_typed_object(bits)` and is broken on
687 // raw `Box::into_raw` pointers; using `heap_drop` is the §2.7.5
688 // direct-path cleanup.
689 unsafe { UnifiedValue::<*const u8>::heap_drop(boxed) };
690 }
691
692 /// Pairing a `KindedSlot` with a typed-object pointer is the
693 /// runtime-tier `(slot, NativeKind)` carrier per ADR-006 §2.7.6 / Q8.
694 /// Reflects the same construction-side contract `box_typed_object`
695 /// expresses at the JIT-FFI tier, but using the bounded carrier API
696 /// from `shape-value`. Same test semantics as the deleted
697 /// `is_typed_object(boxed) == true` invariant, expressed at the
698 /// strict-typed carrier layer where the discriminator IS the kind
699 /// label (no tag-bit probe).
700 #[test]
701 fn test_typed_object_kinded_slot_discriminates_via_kind_label() {
702 use shape_value::{HeapKind, KindedSlot, NativeKind, TypedObjectStorage, ValueSlot};
703 use std::sync::Arc;
704
705 // W5 v0.3 fix (2026-05-17): migrated to the v2-raw `_new` carrier
706 // per `executor/objects/property_access.rs::length_typed_object_empty`
707 // rationale. The post-Wave-2-D1 `KindedSlot::Drop` for
708 // `Ptr(HeapKind::TypedObject)` uses `release_elem` →
709 // `std::alloc::dealloc(ptr, Layout::new::<TypedObjectStorage>())`
710 // — incompatible with `Arc::new` allocator provenance.
711 //
712 // Build a minimal `TypedObjectStorage` via the v2-raw allocator —
713 // the canonical production carrier shape per ADR-006 §2.3 +
714 // §2.7.24. The slot's Drop now runs through `release_elem` →
715 // `v2_release` → `_drop` cleanly when the test's `slot` falls out
716 // of scope at the closing brace.
717 let ptr = TypedObjectStorage::_new(
718 0,
719 Vec::<ValueSlot>::new().into_boxed_slice(),
720 0,
721 Arc::from(Vec::<NativeKind>::new().into_boxed_slice()),
722 );
723 let slot = KindedSlot::from_typed_object_raw(ptr);
724
725 // §2.7.6 / Q8: the kind label discriminates the slot — no tag-bit
726 // probe required (and tag-bit probes don't exist post-strict-
727 // typing). Construction-side contract holds.
728 assert_eq!(slot.kind(), NativeKind::Ptr(HeapKind::TypedObject));
729 // The matching heap discriminator for a v2-raw
730 // `*const TypedObjectStorage` slot is `HeapKind::TypedObject` —
731 // the §2.7.5/Q8 bounded-carrier API exposes one constructor per
732 // kind variant, mirror-matching the heap arm.
733 }
734}