Skip to main content

shape_jit/ffi/object/
conversion.rs

1// Heap allocation audit (PR-9 V8 Gap Closure):
2//   Category A (NaN-boxed returns): 14 sites
3//     nanboxed_to_jit_bits: jit_box(HK_STRING/HK_ARRAY/HK_TIME/HK_DURATION/
4//       HK_TIMEFRAME/HK_TASK_GROUP/HK_FUTURE), box_ok/box_err/box_some
5//   Category B (intermediate/consumed): 3 sites
6//     jit_bits_to_nanboxed: Arc::new in ValueWord::from_string/from_array (returned
7//       as runtime values, not JIT heap objects)
8//   Category C (heap islands): 1 site (nanboxed_to_jit_bits Array conversion)
9//!
10//! Conversion Between NaN-Boxed Bits and Runtime Values / ValueWord
11//!
12//! Functions for bidirectional conversion between the JIT's NaN-boxed u64
13//! representation and the runtime's value types (ValueWord and Value).
14
15use std::sync::Arc;
16
17use super::super::super::context::JITDuration;
18use super::super::super::jit_array::{ArrayElementKind, JitArray};
19use super::super::super::nan_boxing::*;
20
21/// JIT-side representation of a TaskGroup for heap boxing.
22#[derive(Clone)]
23pub struct JitTaskGroup {
24    pub kind: u8,
25    pub task_ids: Vec<u64>,
26}
27
28/// Bridge a width-specific typed array (`Vec<T>`) to a JitArray.
29///
30/// NaN-boxes each element as f64, sets typed_data to the raw buffer
31/// pointer, and tags with the appropriate element kind.
32fn typed_array_to_jit<T: Copy + CastToF64>(
33    data: &[T],
34    hk: u16,
35    kind: ArrayElementKind,
36) -> u64 {
37    let boxed_arr: Vec<u64> = data.iter().map(|&v| box_number(v.cast_f64())).collect();
38    let mut jit_arr = JitArray::from_vec(boxed_arr);
39    jit_arr.typed_data = data.as_ptr() as *mut u64;
40    jit_arr.element_kind = kind.as_byte();
41    jit_arr.typed_storage_kind = kind.as_byte();
42    jit_box(hk, jit_arr)
43}
44
45/// Reconstruct a width-specific typed array from a JitArray's NaN-boxed elements.
46fn jit_to_typed_array<T, F>(bits: u64, from_fn: F) -> shape_value::ValueWord
47where
48    T: Default + Copy,
49    f64: IntoTyped<T>,
50    F: FnOnce(Arc<shape_value::typed_buffer::TypedBuffer<T>>) -> shape_value::ValueWord,
51{
52    let arr = unsafe { jit_unbox::<JitArray>(bits) };
53    let data: Vec<T> = arr
54        .iter()
55        .map(|&b| {
56            if is_number(b) {
57                <f64 as IntoTyped<T>>::into_typed(unbox_number(b))
58            } else {
59                T::default()
60            }
61        })
62        .collect();
63    let buf = shape_value::typed_buffer::TypedBuffer {
64        data,
65        validity: None,
66    };
67    from_fn(Arc::new(buf))
68}
69
70/// Helper trait for T → f64 conversion (entry path).
71trait CastToF64 {
72    fn cast_f64(self) -> f64;
73}
74impl CastToF64 for i8 { fn cast_f64(self) -> f64 { self as f64 } }
75impl CastToF64 for i16 { fn cast_f64(self) -> f64 { self as f64 } }
76impl CastToF64 for i32 { fn cast_f64(self) -> f64 { self as f64 } }
77impl CastToF64 for i64 { fn cast_f64(self) -> f64 { self as f64 } }
78impl CastToF64 for u8 { fn cast_f64(self) -> f64 { self as f64 } }
79impl CastToF64 for u16 { fn cast_f64(self) -> f64 { self as f64 } }
80impl CastToF64 for u32 { fn cast_f64(self) -> f64 { self as f64 } }
81impl CastToF64 for u64 { fn cast_f64(self) -> f64 { self as f64 } }
82impl CastToF64 for f32 { fn cast_f64(self) -> f64 { self as f64 } }
83impl CastToF64 for f64 { fn cast_f64(self) -> f64 { self } }
84
85/// Helper trait for f64 → typed element conversion (exit path).
86trait IntoTyped<T> {
87    fn into_typed(self) -> T;
88}
89impl IntoTyped<i8> for f64 { fn into_typed(self) -> i8 { self as i8 } }
90impl IntoTyped<i16> for f64 { fn into_typed(self) -> i16 { self as i16 } }
91impl IntoTyped<i32> for f64 { fn into_typed(self) -> i32 { self as i32 } }
92impl IntoTyped<i64> for f64 { fn into_typed(self) -> i64 { self as i64 } }
93impl IntoTyped<u8> for f64 { fn into_typed(self) -> u8 { self as u8 } }
94impl IntoTyped<u16> for f64 { fn into_typed(self) -> u16 { self as u16 } }
95impl IntoTyped<u32> for f64 { fn into_typed(self) -> u32 { self as u32 } }
96impl IntoTyped<u64> for f64 { fn into_typed(self) -> u64 { self as u64 } }
97impl IntoTyped<f32> for f64 { fn into_typed(self) -> f32 { self as f32 } }
98impl IntoTyped<f64> for f64 { fn into_typed(self) -> f64 { self } }
99
100// ============================================================================
101// Direct ValueWord <-> JIT Bits Conversion
102// ============================================================================
103
104/// Convert JIT NaN-boxed bits directly to ValueWord (no intermediate Value/VMValue).
105pub fn jit_bits_to_nanboxed(bits: u64) -> shape_value::ValueWord {
106    use shape_value::ValueWord;
107
108    if is_number(bits) {
109        return ValueWord::from_f64(unbox_number(bits));
110    }
111    if bits == TAG_NULL {
112        return ValueWord::none();
113    }
114    if bits == TAG_BOOL_TRUE {
115        return ValueWord::from_bool(true);
116    }
117    if bits == TAG_BOOL_FALSE {
118        return ValueWord::from_bool(false);
119    }
120    if bits == TAG_UNIT {
121        return ValueWord::unit();
122    }
123    if is_inline_function(bits) {
124        let func_id = unbox_function_id(bits);
125        return ValueWord::from_function_ref(format!("__func_{}", func_id), None);
126    }
127    if is_ok_tag(bits) {
128        let inner_bits = unsafe { unbox_result_inner(bits) };
129        let inner = jit_bits_to_nanboxed(inner_bits);
130        return ValueWord::from_ok(inner);
131    }
132    if is_err_tag(bits) {
133        let inner_bits = unsafe { unbox_result_inner(bits) };
134        let inner = jit_bits_to_nanboxed(inner_bits);
135        return ValueWord::from_err(inner);
136    }
137    if is_some_tag(bits) {
138        let inner_bits = unsafe { unbox_some_inner(bits) };
139        let inner = jit_bits_to_nanboxed(inner_bits);
140        return ValueWord::from_some(inner);
141    }
142
143    match heap_kind(bits) {
144        Some(HK_STRING) => {
145            let s = unsafe { jit_unbox::<String>(bits) };
146            ValueWord::from_string(Arc::new(s.clone()))
147        }
148        Some(HK_ARRAY) => {
149            let arr = unsafe { jit_unbox::<JitArray>(bits) };
150            let values: Vec<ValueWord> = arr.iter().map(|&b| jit_bits_to_nanboxed(b)).collect();
151            ValueWord::from_array(Arc::new(values))
152        }
153        Some(HK_CLOSURE) => {
154            let closure = unsafe { jit_unbox::<super::super::super::context::JITClosure>(bits) };
155            ValueWord::from_function_ref(format!("__func_{}", closure.function_id), None)
156        }
157        Some(HK_TASK_GROUP) => {
158            let tg = unsafe { jit_unbox::<JitTaskGroup>(bits) };
159            ValueWord::from_task_group(tg.kind, tg.task_ids.clone())
160        }
161        Some(HK_FUTURE) => {
162            let id = unsafe { jit_unbox::<u64>(bits) };
163            ValueWord::from_future(*id)
164        }
165        Some(HK_FLOAT_ARRAY) => {
166            // Reconstruct FloatArray from JitArray's NaN-boxed element buffer.
167            let arr = unsafe { jit_unbox::<JitArray>(bits) };
168            let floats: Vec<f64> = arr
169                .iter()
170                .map(|&b| {
171                    if is_number(b) {
172                        unbox_number(b)
173                    } else {
174                        0.0
175                    }
176                })
177                .collect();
178            let aligned = shape_value::aligned_vec::AlignedVec::from_vec(floats);
179            let buf = shape_value::typed_buffer::AlignedTypedBuffer::from_aligned(aligned);
180            ValueWord::from_float_array(Arc::new(buf))
181        }
182        Some(HK_INT_ARRAY) => {
183            // Reconstruct IntArray from JitArray's NaN-boxed element buffer.
184            let arr = unsafe { jit_unbox::<JitArray>(bits) };
185            let ints: Vec<i64> = arr
186                .iter()
187                .map(|&b| {
188                    if is_number(b) {
189                        unbox_number(b) as i64
190                    } else {
191                        0
192                    }
193                })
194                .collect();
195            let buf = shape_value::typed_buffer::TypedBuffer { data: ints, validity: None };
196            ValueWord::from_int_array(Arc::new(buf))
197        }
198        Some(HK_FLOAT_ARRAY_SLICE) => {
199            // Reconstruct FloatArraySlice with original parent Arc linkage.
200            let arr = unsafe { jit_unbox::<JitArray>(bits) };
201            if arr.slice_parent_arc.is_null() {
202                // Fallback: parent was lost, materialize as owned FloatArray.
203                let floats: Vec<f64> = arr
204                    .iter()
205                    .map(|&b| if is_number(b) { unbox_number(b) } else { 0.0 })
206                    .collect();
207                let aligned = shape_value::aligned_vec::AlignedVec::from_vec(floats);
208                let buf =
209                    shape_value::typed_buffer::AlignedTypedBuffer::from_aligned(aligned);
210                ValueWord::from_float_array(Arc::new(buf))
211            } else {
212                // Reconstitute the Arc without dropping it — the JitArray's Drop
213                // will handle the Arc::from_raw when the JitArray is freed.
214                let parent = unsafe {
215                    Arc::from_raw(
216                        arr.slice_parent_arc
217                            as *const shape_value::heap_value::MatrixData,
218                    )
219                };
220                // Clone to get our own reference, then leak the original back
221                // so the JitArray Drop doesn't double-free.
222                let parent_clone = Arc::clone(&parent);
223                std::mem::forget(parent);
224                ValueWord::from_float_array_slice(
225                    parent_clone,
226                    arr.slice_offset,
227                    arr.slice_len,
228                )
229            }
230        }
231        Some(HK_MATRIX) => {
232            // Reconstruct Matrix with original Arc<MatrixData>.
233            let jm = unsafe {
234                jit_unbox::<crate::jit_matrix::JitMatrix>(bits)
235            };
236            let mat_arc = jm.to_arc();
237            ValueWord::from_matrix(mat_arc)
238        }
239        // Width-specific typed arrays
240        Some(HK_BOOL_ARRAY) => jit_to_typed_array::<u8, _>(bits, ValueWord::from_bool_array),
241        Some(HK_I8_ARRAY) => jit_to_typed_array::<i8, _>(bits, ValueWord::from_i8_array),
242        Some(HK_I16_ARRAY) => jit_to_typed_array::<i16, _>(bits, ValueWord::from_i16_array),
243        Some(HK_I32_ARRAY) => jit_to_typed_array::<i32, _>(bits, ValueWord::from_i32_array),
244        Some(HK_U8_ARRAY) => jit_to_typed_array::<u8, _>(bits, ValueWord::from_u8_array),
245        Some(HK_U16_ARRAY) => jit_to_typed_array::<u16, _>(bits, ValueWord::from_u16_array),
246        Some(HK_U32_ARRAY) => jit_to_typed_array::<u32, _>(bits, ValueWord::from_u32_array),
247        Some(HK_U64_ARRAY) => jit_to_typed_array::<u64, _>(bits, ValueWord::from_u64_array),
248        Some(HK_F32_ARRAY) => jit_to_typed_array::<f32, _>(bits, ValueWord::from_f32_array),
249        _ => ValueWord::none(),
250    }
251}
252
253/// Convert JIT NaN-boxed bits to ValueWord with JITContext for function name lookup.
254pub fn jit_bits_to_nanboxed_with_ctx(
255    bits: u64,
256    ctx: *const super::super::super::context::JITContext,
257) -> shape_value::ValueWord {
258    use shape_value::ValueWord;
259
260    if is_number(bits) {
261        return ValueWord::from_f64(unbox_number(bits));
262    }
263
264    // Handle inline function refs and closures with name lookup
265    if is_inline_function(bits) {
266        let func_id = unbox_function_id(bits);
267        let name = lookup_function_name(ctx, func_id);
268        return ValueWord::from_function_ref(name, None);
269    }
270
271    if is_heap_kind(bits, HK_CLOSURE) {
272        let closure = unsafe { jit_unbox::<super::super::super::context::JITClosure>(bits) };
273        let name = lookup_function_name(ctx, closure.function_id);
274        return ValueWord::from_function_ref(name, None);
275    }
276
277    if is_heap_kind(bits, HK_ARRAY) {
278        let arr = unsafe { jit_unbox::<JitArray>(bits) };
279        let values: Vec<ValueWord> = arr
280            .iter()
281            .map(|&b| jit_bits_to_nanboxed_with_ctx(b, ctx))
282            .collect();
283        return ValueWord::from_array(Arc::new(values));
284    }
285
286    // For other types, delegate to the basic converter
287    jit_bits_to_nanboxed(bits)
288}
289
290/// Helper: look up function name from JITContext
291fn lookup_function_name(
292    ctx: *const super::super::super::context::JITContext,
293    func_id: u16,
294) -> String {
295    if !ctx.is_null() {
296        unsafe {
297            let ctx_ref = &*ctx;
298            if !ctx_ref.function_names_ptr.is_null()
299                && (func_id as usize) < ctx_ref.function_names_len
300            {
301                return (*ctx_ref.function_names_ptr.add(func_id as usize)).clone();
302            }
303        }
304    }
305    format!("__func_{}", func_id)
306}
307
308// ============================================================================
309// TypedScalar <-> JIT Bits Conversion
310// ============================================================================
311
312/// Convert JIT NaN-boxed bits to a TypedScalar with an optional type hint.
313///
314/// When `hint` is provided (e.g., from a FrameDescriptor's last slot), integer-hinted
315/// numbers are decoded as `ScalarKind::I64` instead of `ScalarKind::F64`, preserving
316/// type identity across the boundary.
317pub fn jit_bits_to_typed_scalar(
318    bits: u64,
319    hint: Option<shape_vm::SlotKind>,
320) -> shape_value::TypedScalar {
321    use shape_value::TypedScalar;
322    use shape_vm::SlotKind;
323
324    if is_number(bits) {
325        let f = unbox_number(bits);
326        // Check if the hint says this should be an integer
327        if let Some(h) = hint {
328            match h {
329                SlotKind::Int8 | SlotKind::NullableInt8 => {
330                    return TypedScalar::i8(f as i8);
331                }
332                SlotKind::UInt8 | SlotKind::NullableUInt8 => {
333                    return TypedScalar::u8(f as u8);
334                }
335                SlotKind::Int16 | SlotKind::NullableInt16 => {
336                    return TypedScalar::i16(f as i16);
337                }
338                SlotKind::UInt16 | SlotKind::NullableUInt16 => {
339                    return TypedScalar::u16(f as u16);
340                }
341                SlotKind::Int32 | SlotKind::NullableInt32 => {
342                    return TypedScalar::i32(f as i32);
343                }
344                SlotKind::UInt32 | SlotKind::NullableUInt32 => {
345                    return TypedScalar::u32(f as u32);
346                }
347                SlotKind::Int64 | SlotKind::NullableInt64 => {
348                    return TypedScalar::i64(f as i64);
349                }
350                SlotKind::UInt64 | SlotKind::NullableUInt64 => {
351                    return TypedScalar::u64(f as u64);
352                }
353                SlotKind::Float64 | SlotKind::NullableFloat64 => {
354                    return TypedScalar::f64_from_bits(bits);
355                }
356                _ => {
357                    // Bool, String, Boxed, etc. — no special numeric treatment
358                }
359            }
360        }
361        // Default: treat as f64
362        return TypedScalar::f64_from_bits(bits);
363    }
364
365    if bits == TAG_BOOL_TRUE {
366        return TypedScalar::bool(true);
367    }
368    if bits == TAG_BOOL_FALSE {
369        return TypedScalar::bool(false);
370    }
371    if bits == TAG_NULL || bits == TAG_NONE {
372        return TypedScalar::none();
373    }
374    if bits == TAG_UNIT {
375        return TypedScalar::unit();
376    }
377
378    // Non-scalar (heap pointer, function, etc.) — return None sentinel
379    TypedScalar::none()
380}
381
382/// Convert a TypedScalar to JIT NaN-boxed bits.
383///
384/// Integer kinds are stored as `box_number(value as f64)` since the JIT's
385/// Cranelift IR uses f64 for all numeric operations internally.
386pub fn typed_scalar_to_jit_bits(ts: &shape_value::TypedScalar) -> u64 {
387    use shape_value::ScalarKind;
388
389    match ts.kind {
390        ScalarKind::I8 | ScalarKind::I16 | ScalarKind::I32 | ScalarKind::I64 => {
391            box_number(ts.payload_lo as i64 as f64)
392        }
393        ScalarKind::U8 | ScalarKind::U16 | ScalarKind::U32 | ScalarKind::U64 => {
394            box_number(ts.payload_lo as f64)
395        }
396        ScalarKind::I128 | ScalarKind::U128 => box_number(ts.payload_lo as i64 as f64),
397        ScalarKind::F64 | ScalarKind::F32 => ts.payload_lo, // already f64 bits
398        ScalarKind::Bool => {
399            if ts.payload_lo != 0 {
400                TAG_BOOL_TRUE
401            } else {
402                TAG_BOOL_FALSE
403            }
404        }
405        ScalarKind::None => TAG_NULL,
406        ScalarKind::Unit => TAG_UNIT,
407    }
408}
409
410/// Convert a ValueWord value directly to JIT NaN-boxed bits (no intermediate VMValue).
411pub fn nanboxed_to_jit_bits(nb: &shape_value::ValueWord) -> u64 {
412    use shape_value::NanTag;
413    use shape_value::heap_value::HeapValue;
414
415    match nb.tag() {
416        NanTag::F64 => box_number(unsafe { nb.as_f64_unchecked() }),
417        NanTag::I48 => box_number(unsafe { nb.as_i64_unchecked() } as f64),
418        NanTag::Bool => {
419            if unsafe { nb.as_bool_unchecked() } {
420                TAG_BOOL_TRUE
421            } else {
422                TAG_BOOL_FALSE
423            }
424        }
425        NanTag::None => TAG_NULL,
426        NanTag::Unit => TAG_UNIT,
427        NanTag::Function => {
428            let func_id = unsafe { nb.as_function_unchecked() };
429            box_function(func_id)
430        }
431        NanTag::ModuleFunction => TAG_NULL,
432        NanTag::Ref => TAG_NULL,
433        NanTag::Heap => match nb.as_heap_ref() {
434            Some(HeapValue::String(s)) => jit_box(HK_STRING, s.clone()),
435            Some(HeapValue::Array(arr)) => {
436                // AUDIT(C4): heap island — each element converted via nanboxed_to_jit_bits
437                // may itself call jit_box (for strings, nested arrays, etc.), producing
438                // JitAlloc pointers stored as raw u64 in the Vec. These inner allocations
439                // escape into the outer JitArray element buffer without GC tracking.
440                // When GC feature enabled, route through gc_allocator.
441                let boxed_arr: Vec<u64> = arr.iter().map(|v| nanboxed_to_jit_bits(v)).collect();
442                jit_box(HK_ARRAY, JitArray::from_vec(boxed_arr))
443            }
444            Some(HeapValue::Time(dt)) => jit_box(HK_TIME, dt.timestamp()),
445            Some(HeapValue::Duration(dur)) => {
446                let unit_code = match dur.unit {
447                    crate::ast::DurationUnit::Seconds => 0,
448                    crate::ast::DurationUnit::Minutes => 1,
449                    crate::ast::DurationUnit::Hours => 2,
450                    crate::ast::DurationUnit::Days => 3,
451                    crate::ast::DurationUnit::Weeks => 4,
452                    crate::ast::DurationUnit::Months => 5,
453                    crate::ast::DurationUnit::Years => 6,
454                    crate::ast::DurationUnit::Samples => 7,
455                };
456                jit_box(
457                    HK_DURATION,
458                    JITDuration {
459                        value: dur.value,
460                        unit: unit_code,
461                    },
462                )
463            }
464            Some(HeapValue::Timeframe(tf)) => {
465                let internal_tf = crate::ast::data::Timeframe::new(
466                    tf.value,
467                    match tf.unit {
468                        crate::ast::TimeframeUnit::Second => {
469                            crate::ast::data::TimeframeUnit::Second
470                        }
471                        crate::ast::TimeframeUnit::Minute => {
472                            crate::ast::data::TimeframeUnit::Minute
473                        }
474                        crate::ast::TimeframeUnit::Hour => crate::ast::data::TimeframeUnit::Hour,
475                        crate::ast::TimeframeUnit::Day => crate::ast::data::TimeframeUnit::Day,
476                        crate::ast::TimeframeUnit::Week => crate::ast::data::TimeframeUnit::Week,
477                        crate::ast::TimeframeUnit::Month => crate::ast::data::TimeframeUnit::Month,
478                        crate::ast::TimeframeUnit::Year => crate::ast::data::TimeframeUnit::Year,
479                    },
480                );
481                jit_box(HK_TIMEFRAME, internal_tf)
482            }
483            Some(HeapValue::Ok(inner)) => {
484                let inner_bits = nanboxed_to_jit_bits(inner);
485                box_ok(inner_bits)
486            }
487            Some(HeapValue::Err(inner)) => {
488                let inner_bits = nanboxed_to_jit_bits(inner);
489                box_err(inner_bits)
490            }
491            Some(HeapValue::Some(inner)) => {
492                let inner_bits = nanboxed_to_jit_bits(inner);
493                box_some(inner_bits)
494            }
495            // BigInt → f64: precision loss for |i| > 2^53
496            Some(HeapValue::BigInt(i)) => box_number(*i as f64),
497            Some(HeapValue::TaskGroup { kind, task_ids }) => jit_box(
498                HK_TASK_GROUP,
499                JitTaskGroup {
500                    kind: *kind,
501                    task_ids: task_ids.clone(),
502                },
503            ),
504            Some(HeapValue::Future(id)) => jit_box(HK_FUTURE, *id),
505            Some(HeapValue::FloatArray(buf)) => {
506                // Bridge FloatArray → JitArray with typed_data pointing to
507                // the AlignedTypedBuffer's f64 data for direct numeric access.
508                let len = buf.data.len();
509                let boxed_arr: Vec<u64> = buf
510                    .data
511                    .as_slice()
512                    .iter()
513                    .map(|&v| box_number(v))
514                    .collect();
515                let mut jit_arr = JitArray::from_vec(boxed_arr);
516                // Point typed_data at the source AlignedVec's f64 buffer.
517                // This is safe because the Arc keeps the buffer alive as long
518                // as the HeapValue exists, and the JitArray only lives for the
519                // duration of the JIT call.
520                jit_arr.typed_data = buf.data.as_slice().as_ptr() as *mut u64;
521                jit_arr.element_kind =
522                    crate::jit_array::ArrayElementKind::Float64.as_byte();
523                jit_arr.typed_storage_kind =
524                    crate::jit_array::ArrayElementKind::Float64.as_byte();
525                let _ = len; // suppress unused warning
526                jit_box(HK_FLOAT_ARRAY, jit_arr)
527            }
528            Some(HeapValue::IntArray(buf)) => {
529                // Bridge IntArray → JitArray with typed_data pointing to
530                // the TypedBuffer<i64>'s data for direct integer access.
531                let boxed_arr: Vec<u64> = buf
532                    .data
533                    .iter()
534                    .map(|&v| box_number(v as f64))
535                    .collect();
536                let mut jit_arr = JitArray::from_vec(boxed_arr);
537                jit_arr.typed_data = buf.data.as_ptr() as *mut u64;
538                jit_arr.element_kind =
539                    crate::jit_array::ArrayElementKind::Int64.as_byte();
540                jit_arr.typed_storage_kind =
541                    crate::jit_array::ArrayElementKind::Int64.as_byte();
542                jit_box(HK_INT_ARRAY, jit_arr)
543            }
544            Some(HeapValue::FloatArraySlice {
545                parent,
546                offset,
547                len,
548            }) => {
549                // Bridge FloatArraySlice → JitArray with typed_data pointing
550                // to the parent MatrixData's AlignedVec at the given offset.
551                // Preserves parent Arc linkage for clean round-trip on deopt.
552                let off = *offset as usize;
553                let slice_len = *len as usize;
554                let parent_slice = parent.data.as_slice();
555                let end = (off + slice_len).min(parent_slice.len());
556                let actual_slice = &parent_slice[off..end];
557                let boxed_arr: Vec<u64> = actual_slice
558                    .iter()
559                    .map(|&v| box_number(v))
560                    .collect();
561                let mut jit_arr = JitArray::from_vec(boxed_arr);
562                // Point typed_data at the parent's data + offset for zero-copy reads.
563                if !parent_slice.is_empty() && off < parent_slice.len() {
564                    jit_arr.typed_data =
565                        unsafe { parent_slice.as_ptr().add(off) } as *mut u64;
566                }
567                jit_arr.element_kind =
568                    crate::jit_array::ArrayElementKind::Float64.as_byte();
569                jit_arr.typed_storage_kind =
570                    crate::jit_array::ArrayElementKind::Float64.as_byte();
571                // Stash parent Arc for round-trip reconstruction.
572                // Arc::into_raw increments the strong count; the JitArray Drop
573                // impl calls Arc::from_raw to release it.
574                jit_arr.slice_parent_arc =
575                    Arc::into_raw(Arc::clone(parent)) as *const ();
576                jit_arr.slice_offset = *offset;
577                jit_arr.slice_len = *len;
578                jit_box(HK_FLOAT_ARRAY_SLICE, jit_arr)
579            }
580            Some(HeapValue::Matrix(mat_arc)) => {
581                // Bridge Matrix → JitMatrix with direct f64 data pointer.
582                let jm = crate::jit_matrix::JitMatrix::from_arc(mat_arc);
583                jit_box(HK_MATRIX, jm)
584            }
585            // Width-specific typed arrays
586            Some(HeapValue::BoolArray(buf)) => typed_array_to_jit(&buf.data, HK_BOOL_ARRAY, ArrayElementKind::Bool),
587            Some(HeapValue::I8Array(buf)) => typed_array_to_jit(&buf.data, HK_I8_ARRAY, ArrayElementKind::I8),
588            Some(HeapValue::I16Array(buf)) => typed_array_to_jit(&buf.data, HK_I16_ARRAY, ArrayElementKind::I16),
589            Some(HeapValue::I32Array(buf)) => typed_array_to_jit(&buf.data, HK_I32_ARRAY, ArrayElementKind::I32),
590            Some(HeapValue::U8Array(buf)) => typed_array_to_jit(&buf.data, HK_U8_ARRAY, ArrayElementKind::U8),
591            Some(HeapValue::U16Array(buf)) => typed_array_to_jit(&buf.data, HK_U16_ARRAY, ArrayElementKind::U16),
592            Some(HeapValue::U32Array(buf)) => typed_array_to_jit(&buf.data, HK_U32_ARRAY, ArrayElementKind::U32),
593            Some(HeapValue::U64Array(buf)) => typed_array_to_jit(&buf.data, HK_U64_ARRAY, ArrayElementKind::U64),
594            Some(HeapValue::F32Array(buf)) => typed_array_to_jit(&buf.data, HK_F32_ARRAY, ArrayElementKind::F32),
595            _ => TAG_NULL,
596        },
597    }
598}