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::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// ============================================================================
29// Direct ValueWord <-> JIT Bits Conversion
30// ============================================================================
31
32/// Convert JIT NaN-boxed bits directly to ValueWord (no intermediate Value/VMValue).
33pub fn jit_bits_to_nanboxed(bits: u64) -> shape_value::ValueWord {
34    use shape_value::ValueWord;
35
36    if is_number(bits) {
37        return ValueWord::from_f64(unbox_number(bits));
38    }
39    if bits == TAG_NULL {
40        return ValueWord::none();
41    }
42    if bits == TAG_BOOL_TRUE {
43        return ValueWord::from_bool(true);
44    }
45    if bits == TAG_BOOL_FALSE {
46        return ValueWord::from_bool(false);
47    }
48    if bits == TAG_UNIT {
49        return ValueWord::unit();
50    }
51    if is_inline_function(bits) {
52        let func_id = unbox_function_id(bits);
53        return ValueWord::from_function_ref(format!("__func_{}", func_id), None);
54    }
55    if is_ok_tag(bits) {
56        let inner_bits = unsafe { unbox_result_inner(bits) };
57        let inner = jit_bits_to_nanboxed(inner_bits);
58        return ValueWord::from_ok(inner);
59    }
60    if is_err_tag(bits) {
61        let inner_bits = unsafe { unbox_result_inner(bits) };
62        let inner = jit_bits_to_nanboxed(inner_bits);
63        return ValueWord::from_err(inner);
64    }
65    if is_some_tag(bits) {
66        let inner_bits = unsafe { unbox_some_inner(bits) };
67        let inner = jit_bits_to_nanboxed(inner_bits);
68        return ValueWord::from_some(inner);
69    }
70
71    match heap_kind(bits) {
72        Some(HK_STRING) => {
73            let s = unsafe { jit_unbox::<String>(bits) };
74            ValueWord::from_string(Arc::new(s.clone()))
75        }
76        Some(HK_ARRAY) => {
77            let arr = unsafe { jit_unbox::<JitArray>(bits) };
78            let values: Vec<ValueWord> = arr.iter().map(|&b| jit_bits_to_nanboxed(b)).collect();
79            ValueWord::from_array(Arc::new(values))
80        }
81        Some(HK_CLOSURE) => {
82            let closure = unsafe { jit_unbox::<super::super::super::context::JITClosure>(bits) };
83            ValueWord::from_function_ref(format!("__func_{}", closure.function_id), None)
84        }
85        Some(HK_TASK_GROUP) => {
86            let tg = unsafe { jit_unbox::<JitTaskGroup>(bits) };
87            ValueWord::from_task_group(tg.kind, tg.task_ids.clone())
88        }
89        Some(HK_FUTURE) => {
90            let id = unsafe { jit_unbox::<u64>(bits) };
91            ValueWord::from_future(*id)
92        }
93        _ => ValueWord::none(),
94    }
95}
96
97/// Convert JIT NaN-boxed bits to ValueWord with JITContext for function name lookup.
98pub fn jit_bits_to_nanboxed_with_ctx(
99    bits: u64,
100    ctx: *const super::super::super::context::JITContext,
101) -> shape_value::ValueWord {
102    use shape_value::ValueWord;
103
104    if is_number(bits) {
105        return ValueWord::from_f64(unbox_number(bits));
106    }
107
108    // Handle inline function refs and closures with name lookup
109    if is_inline_function(bits) {
110        let func_id = unbox_function_id(bits);
111        let name = lookup_function_name(ctx, func_id);
112        return ValueWord::from_function_ref(name, None);
113    }
114
115    if is_heap_kind(bits, HK_CLOSURE) {
116        let closure = unsafe { jit_unbox::<super::super::super::context::JITClosure>(bits) };
117        let name = lookup_function_name(ctx, closure.function_id);
118        return ValueWord::from_function_ref(name, None);
119    }
120
121    if is_heap_kind(bits, HK_ARRAY) {
122        let arr = unsafe { jit_unbox::<JitArray>(bits) };
123        let values: Vec<ValueWord> = arr
124            .iter()
125            .map(|&b| jit_bits_to_nanboxed_with_ctx(b, ctx))
126            .collect();
127        return ValueWord::from_array(Arc::new(values));
128    }
129
130    // For other types, delegate to the basic converter
131    jit_bits_to_nanboxed(bits)
132}
133
134/// Helper: look up function name from JITContext
135fn lookup_function_name(
136    ctx: *const super::super::super::context::JITContext,
137    func_id: u16,
138) -> String {
139    if !ctx.is_null() {
140        unsafe {
141            let ctx_ref = &*ctx;
142            if !ctx_ref.function_names_ptr.is_null()
143                && (func_id as usize) < ctx_ref.function_names_len
144            {
145                return (*ctx_ref.function_names_ptr.add(func_id as usize)).clone();
146            }
147        }
148    }
149    format!("__func_{}", func_id)
150}
151
152// ============================================================================
153// TypedScalar <-> JIT Bits Conversion
154// ============================================================================
155
156/// Convert JIT NaN-boxed bits to a TypedScalar with an optional type hint.
157///
158/// When `hint` is provided (e.g., from a FrameDescriptor's last slot), integer-hinted
159/// numbers are decoded as `ScalarKind::I64` instead of `ScalarKind::F64`, preserving
160/// type identity across the boundary.
161pub fn jit_bits_to_typed_scalar(
162    bits: u64,
163    hint: Option<shape_vm::SlotKind>,
164) -> shape_value::TypedScalar {
165    use shape_value::{ScalarKind, TypedScalar};
166    use shape_vm::SlotKind;
167
168    if is_number(bits) {
169        let f = unbox_number(bits);
170        // Check if the hint says this should be an integer
171        if let Some(h) = hint {
172            match h {
173                SlotKind::Int8 | SlotKind::NullableInt8 => {
174                    return TypedScalar::i8(f as i8);
175                }
176                SlotKind::UInt8 | SlotKind::NullableUInt8 => {
177                    return TypedScalar::u8(f as u8);
178                }
179                SlotKind::Int16 | SlotKind::NullableInt16 => {
180                    return TypedScalar::i16(f as i16);
181                }
182                SlotKind::UInt16 | SlotKind::NullableUInt16 => {
183                    return TypedScalar::u16(f as u16);
184                }
185                SlotKind::Int32 | SlotKind::NullableInt32 => {
186                    return TypedScalar::i32(f as i32);
187                }
188                SlotKind::UInt32 | SlotKind::NullableUInt32 => {
189                    return TypedScalar::u32(f as u32);
190                }
191                SlotKind::Int64 | SlotKind::NullableInt64 => {
192                    return TypedScalar::i64(f as i64);
193                }
194                SlotKind::UInt64 | SlotKind::NullableUInt64 => {
195                    return TypedScalar::u64(f as u64);
196                }
197                SlotKind::Float64 | SlotKind::NullableFloat64 => {
198                    return TypedScalar::f64_from_bits(bits);
199                }
200                _ => {
201                    // Bool, String, Boxed, etc. — no special numeric treatment
202                }
203            }
204        }
205        // Default: treat as f64
206        return TypedScalar::f64_from_bits(bits);
207    }
208
209    if bits == TAG_BOOL_TRUE {
210        return TypedScalar::bool(true);
211    }
212    if bits == TAG_BOOL_FALSE {
213        return TypedScalar::bool(false);
214    }
215    if bits == TAG_NULL || bits == TAG_NONE {
216        return TypedScalar::none();
217    }
218    if bits == TAG_UNIT {
219        return TypedScalar::unit();
220    }
221
222    // Non-scalar (heap pointer, function, etc.) — return None sentinel
223    TypedScalar::none()
224}
225
226/// Convert a TypedScalar to JIT NaN-boxed bits.
227///
228/// Integer kinds are stored as `box_number(value as f64)` since the JIT's
229/// Cranelift IR uses f64 for all numeric operations internally.
230pub fn typed_scalar_to_jit_bits(ts: &shape_value::TypedScalar) -> u64 {
231    use shape_value::ScalarKind;
232
233    match ts.kind {
234        ScalarKind::I8 | ScalarKind::I16 | ScalarKind::I32 | ScalarKind::I64 => {
235            box_number(ts.payload_lo as i64 as f64)
236        }
237        ScalarKind::U8 | ScalarKind::U16 | ScalarKind::U32 | ScalarKind::U64 => {
238            box_number(ts.payload_lo as f64)
239        }
240        ScalarKind::I128 | ScalarKind::U128 => box_number(ts.payload_lo as i64 as f64),
241        ScalarKind::F64 | ScalarKind::F32 => ts.payload_lo, // already f64 bits
242        ScalarKind::Bool => {
243            if ts.payload_lo != 0 {
244                TAG_BOOL_TRUE
245            } else {
246                TAG_BOOL_FALSE
247            }
248        }
249        ScalarKind::None => TAG_NULL,
250        ScalarKind::Unit => TAG_UNIT,
251    }
252}
253
254/// Convert a ValueWord value directly to JIT NaN-boxed bits (no intermediate VMValue).
255pub fn nanboxed_to_jit_bits(nb: &shape_value::ValueWord) -> u64 {
256    use shape_value::NanTag;
257    use shape_value::heap_value::HeapValue;
258
259    match nb.tag() {
260        NanTag::F64 => box_number(unsafe { nb.as_f64_unchecked() }),
261        NanTag::I48 => box_number(unsafe { nb.as_i64_unchecked() } as f64),
262        NanTag::Bool => {
263            if unsafe { nb.as_bool_unchecked() } {
264                TAG_BOOL_TRUE
265            } else {
266                TAG_BOOL_FALSE
267            }
268        }
269        NanTag::None => TAG_NULL,
270        NanTag::Unit => TAG_UNIT,
271        NanTag::Function => {
272            let func_id = unsafe { nb.as_function_unchecked() };
273            box_function(func_id)
274        }
275        NanTag::ModuleFunction => TAG_NULL,
276        NanTag::Ref => TAG_NULL,
277        NanTag::Heap => match nb.as_heap_ref() {
278            Some(HeapValue::String(s)) => jit_box(HK_STRING, s.clone()),
279            Some(HeapValue::Array(arr)) => {
280                // AUDIT(C4): heap island — each element converted via nanboxed_to_jit_bits
281                // may itself call jit_box (for strings, nested arrays, etc.), producing
282                // JitAlloc pointers stored as raw u64 in the Vec. These inner allocations
283                // escape into the outer JitArray element buffer without GC tracking.
284                // When GC feature enabled, route through gc_allocator.
285                let boxed_arr: Vec<u64> = arr.iter().map(|v| nanboxed_to_jit_bits(v)).collect();
286                jit_box(HK_ARRAY, JitArray::from_vec(boxed_arr))
287            }
288            Some(HeapValue::Time(dt)) => jit_box(HK_TIME, dt.timestamp()),
289            Some(HeapValue::Duration(dur)) => {
290                let unit_code = match dur.unit {
291                    crate::ast::DurationUnit::Seconds => 0,
292                    crate::ast::DurationUnit::Minutes => 1,
293                    crate::ast::DurationUnit::Hours => 2,
294                    crate::ast::DurationUnit::Days => 3,
295                    crate::ast::DurationUnit::Weeks => 4,
296                    crate::ast::DurationUnit::Months => 5,
297                    crate::ast::DurationUnit::Years => 6,
298                    crate::ast::DurationUnit::Samples => 7,
299                };
300                jit_box(
301                    HK_DURATION,
302                    JITDuration {
303                        value: dur.value,
304                        unit: unit_code,
305                    },
306                )
307            }
308            Some(HeapValue::Timeframe(tf)) => {
309                let internal_tf = crate::ast::data::Timeframe::new(
310                    tf.value,
311                    match tf.unit {
312                        crate::ast::TimeframeUnit::Second => {
313                            crate::ast::data::TimeframeUnit::Second
314                        }
315                        crate::ast::TimeframeUnit::Minute => {
316                            crate::ast::data::TimeframeUnit::Minute
317                        }
318                        crate::ast::TimeframeUnit::Hour => crate::ast::data::TimeframeUnit::Hour,
319                        crate::ast::TimeframeUnit::Day => crate::ast::data::TimeframeUnit::Day,
320                        crate::ast::TimeframeUnit::Week => crate::ast::data::TimeframeUnit::Week,
321                        crate::ast::TimeframeUnit::Month => crate::ast::data::TimeframeUnit::Month,
322                        crate::ast::TimeframeUnit::Year => crate::ast::data::TimeframeUnit::Year,
323                    },
324                );
325                jit_box(HK_TIMEFRAME, internal_tf)
326            }
327            Some(HeapValue::Ok(inner)) => {
328                let inner_bits = nanboxed_to_jit_bits(inner);
329                box_ok(inner_bits)
330            }
331            Some(HeapValue::Err(inner)) => {
332                let inner_bits = nanboxed_to_jit_bits(inner);
333                box_err(inner_bits)
334            }
335            Some(HeapValue::Some(inner)) => {
336                let inner_bits = nanboxed_to_jit_bits(inner);
337                box_some(inner_bits)
338            }
339            // BigInt → f64: precision loss for |i| > 2^53
340            Some(HeapValue::BigInt(i)) => box_number(*i as f64),
341            Some(HeapValue::TaskGroup { kind, task_ids }) => jit_box(
342                HK_TASK_GROUP,
343                JitTaskGroup {
344                    kind: *kind,
345                    task_ids: task_ids.clone(),
346                },
347            ),
348            Some(HeapValue::Future(id)) => jit_box(HK_FUTURE, *id),
349            _ => TAG_NULL,
350        },
351    }
352}