Skip to main content

shape_jit/ffi/
conversion.rs

1// Heap allocation audit (PR-9 V8 Gap Closure):
2//   Category A (NaN-boxed returns): 3 sites
3//     jit_box(HK_STRING, ...) — jit_typeof, jit_to_string, jit_type_check
4//   Category B (intermediate/consumed): 0 sites
5//   Category C (heap islands): 0 sites
6//!
7//! Type Conversion FFI Functions for JIT
8//!
9//! Functions for type checking and conversion in JIT-compiled code.
10
11use super::super::jit_array::JitArray;
12use super::super::nan_boxing::*;
13
14// ============================================================================
15// Type Checking
16// ============================================================================
17
18/// Get typeof a value as a string
19pub extern "C" fn jit_typeof(value_bits: u64) -> u64 {
20    let type_str = if is_number(value_bits) {
21        "number"
22    } else if value_bits == TAG_NULL {
23        "null"
24    } else if value_bits == TAG_BOOL_TRUE || value_bits == TAG_BOOL_FALSE {
25        "boolean"
26    } else if is_ok_tag(value_bits) || is_err_tag(value_bits) {
27        "result"
28    } else if is_inline_function(value_bits) {
29        "function"
30    } else {
31        match heap_kind(value_bits) {
32            Some(HK_STRING) => "string",
33            Some(HK_ARRAY) => "array",
34            Some(HK_JIT_OBJECT) | Some(HK_TYPED_OBJECT) => "object",
35            Some(HK_CLOSURE) => "function",
36            Some(HK_RANGE) => "range",
37            Some(HK_COLUMN_REF) => "series",
38            Some(HK_JIT_TABLE_REF) => "series_ref",
39            Some(HK_DURATION) => "duration",
40            Some(HK_TIME) => "time",
41            Some(HK_TIMEFRAME) => "timeframe",
42            _ => "unknown",
43        }
44    };
45    jit_box(HK_STRING, type_str.to_string())
46}
47
48// ============================================================================
49// Type Conversion
50// ============================================================================
51
52/// Convert value to string
53pub extern "C" fn jit_to_string(value_bits: u64) -> u64 {
54    let s = if is_number(value_bits) {
55        format!("{}", unbox_number(value_bits))
56    } else if value_bits == TAG_NULL {
57        "null".to_string()
58    } else if value_bits == TAG_BOOL_TRUE {
59        "true".to_string()
60    } else if value_bits == TAG_BOOL_FALSE {
61        "false".to_string()
62    } else {
63        match heap_kind(value_bits) {
64            Some(HK_STRING) => {
65                let s = unsafe { jit_unbox::<String>(value_bits) };
66                s.clone()
67            }
68            Some(HK_ARRAY) => "[array]".to_string(),
69            Some(HK_JIT_OBJECT) | Some(HK_TYPED_OBJECT) => "[object]".to_string(),
70            _ => "[unknown]".to_string(),
71        }
72    };
73    jit_box(HK_STRING, s)
74}
75
76/// Check if a value matches a type (returns TAG_BOOL_TRUE or TAG_BOOL_FALSE)
77/// type_name_bits should be a boxed string pointer with encoded type info
78pub extern "C" fn jit_type_check(value_bits: u64, type_name_bits: u64) -> u64 {
79    // Get type name string
80    let type_name = unsafe {
81        if !is_heap_kind(type_name_bits, HK_STRING) {
82            return TAG_BOOL_FALSE;
83        }
84        jit_unbox::<String>(type_name_bits).clone()
85    };
86
87    let matches = check_type_recursive(value_bits, &type_name);
88
89    if matches {
90        TAG_BOOL_TRUE
91    } else {
92        TAG_BOOL_FALSE
93    }
94}
95
96/// Recursive helper to check encoded type strings
97fn check_type_recursive(value_bits: u64, type_spec: &str) -> bool {
98    // Parse type spec: "prefix:content" or just "typename"
99    if let Some((prefix, rest)) = type_spec.split_once(':') {
100        match prefix {
101            "basic" => check_basic_type(value_bits, rest),
102            "optional" => {
103                // Optional: null matches, or inner type matches
104                value_bits == TAG_NULL || check_type_recursive(value_bits, rest)
105            }
106            "array" => {
107                // Array type: check if value is array, then optionally check element types
108                if !is_heap_kind(value_bits, HK_ARRAY) {
109                    return false;
110                }
111                let arr = unsafe { jit_unbox::<JitArray>(value_bits) };
112                arr.iter().all(|elem| check_type_recursive(*elem, rest))
113            }
114            "tuple" => {
115                // Tuple type: check array with specific element types
116                if !is_heap_kind(value_bits, HK_ARRAY) {
117                    return false;
118                }
119                let types: Vec<&str> = rest.split(',').collect();
120                let arr = unsafe { jit_unbox::<JitArray>(value_bits) };
121                if arr.len() != types.len() {
122                    return false;
123                }
124                arr.iter()
125                    .zip(types.iter())
126                    .all(|(elem, ty)| check_type_recursive(*elem, ty))
127            }
128            "generic" => {
129                // Generic like Array<T> - check base type only (don't verify element types)
130                match rest {
131                    "Array" => is_heap_kind(value_bits, HK_ARRAY),
132                    "Series" => is_heap_kind(value_bits, HK_COLUMN_REF),
133                    _ => false,
134                }
135            }
136            "ref" => {
137                // Reference types - not fully supported in JIT yet
138                false
139            }
140            "dyn" => {
141                // Dyn trait types - not fully supported in JIT yet
142                false
143            }
144            _ => false,
145        }
146    } else {
147        // No prefix, treat as direct type match
148        match type_spec {
149            "function" => is_inline_function(value_bits) || is_heap_kind(value_bits, HK_CLOSURE),
150            "object" => is_heap_kind(value_bits, HK_TYPED_OBJECT),
151            "any" => true,
152            "void" => value_bits == TAG_UNIT,
153            "never" => false,
154            "null" => value_bits == TAG_NULL,
155            "undefined" => value_bits == TAG_NULL || value_bits == TAG_UNIT,
156            "unknown" => false,
157            _ => check_basic_type(value_bits, type_spec),
158        }
159    }
160}
161
162/// Check a basic type name against a value
163fn check_basic_type(value_bits: u64, type_name: &str) -> bool {
164    if is_number(value_bits) {
165        return type_name == "number";
166    }
167    if value_bits == TAG_NULL {
168        return type_name == "null";
169    }
170    if value_bits == TAG_BOOL_TRUE || value_bits == TAG_BOOL_FALSE {
171        return type_name == "boolean" || type_name == "bool";
172    }
173    if value_bits == TAG_UNIT {
174        return type_name == "void" || type_name == "unit";
175    }
176    if is_inline_function(value_bits) {
177        return type_name == "function";
178    }
179    if is_data_row(value_bits) {
180        return type_name == "data_row";
181    }
182    if is_ok_tag(value_bits) || is_err_tag(value_bits) {
183        return type_name == "result";
184    }
185
186    match heap_kind(value_bits) {
187        Some(HK_STRING) => type_name == "string",
188        Some(HK_ARRAY) => type_name == "array",
189        Some(HK_JIT_OBJECT) | Some(HK_TYPED_OBJECT) => type_name == "object",
190        Some(HK_CLOSURE) => type_name == "function",
191        Some(HK_COLUMN_REF) => type_name == "series",
192        Some(HK_TIME) => type_name == "time",
193        Some(HK_DURATION) => type_name == "duration",
194        Some(HK_TIMEFRAME) => type_name == "timeframe",
195        Some(HK_RANGE) => type_name == "range",
196        _ => false,
197    }
198}
199
200/// Format a NaN-boxed value as a string for display
201fn format_nan_boxed(value_bits: u64) -> String {
202    use shape_value::tags::{TAG_INT, get_payload, get_tag, is_tagged, sign_extend_i48};
203
204    if is_number(value_bits) {
205        let n = unbox_number(value_bits);
206        if n.is_finite() && n == n.trunc() && n.abs() < 1e15 {
207            format!("{}", n as i64)
208        } else {
209            format!("{}", n)
210        }
211    } else if value_bits == TAG_BOOL_TRUE {
212        "true".to_string()
213    } else if value_bits == TAG_BOOL_FALSE {
214        "false".to_string()
215    } else if value_bits == TAG_NULL {
216        "null".to_string()
217    } else if is_tagged(value_bits) && get_tag(value_bits) == TAG_INT {
218        let int_val = sign_extend_i48(get_payload(value_bits));
219        format!("{}", int_val)
220    } else {
221        match heap_kind(value_bits) {
222            Some(HK_STRING) => {
223                let s = unsafe { jit_unbox::<String>(value_bits) };
224                s.clone()
225            }
226            Some(HK_ARRAY) => {
227                let arr = unsafe { jit_unbox::<JitArray>(value_bits) };
228                let elems: Vec<String> = arr.iter().map(|&bits| format_nan_boxed(bits)).collect();
229                format!("[{}]", elems.join(", "))
230            }
231            Some(HK_OK) => {
232                let inner = unsafe { *jit_unbox::<u64>(value_bits) };
233                format!("Ok({})", format_nan_boxed(inner))
234            }
235            Some(HK_ERR) => {
236                let inner = unsafe { *jit_unbox::<u64>(value_bits) };
237                format!("Err({})", format_nan_boxed(inner))
238            }
239            Some(HK_SOME) => {
240                let inner = unsafe { *jit_unbox::<u64>(value_bits) };
241                format!("Some({})", format_nan_boxed(inner))
242            }
243            _ => "[object]".to_string(),
244        }
245    }
246}
247
248/// Print a NaN-boxed value to stdout with a newline
249pub extern "C" fn jit_print(value_bits: u64) {
250    println!("{}", format_nan_boxed(value_bits));
251}
252
253/// Convert value to number
254pub extern "C" fn jit_to_number(value_bits: u64) -> u64 {
255    if is_number(value_bits) {
256        return value_bits;
257    }
258
259    if value_bits == TAG_NULL {
260        return box_number(0.0);
261    }
262    if value_bits == TAG_BOOL_TRUE {
263        return box_number(1.0);
264    }
265    if value_bits == TAG_BOOL_FALSE {
266        return box_number(0.0);
267    }
268
269    let num = match heap_kind(value_bits) {
270        Some(HK_STRING) => {
271            let s = unsafe { jit_unbox::<String>(value_bits) };
272            s.parse::<f64>().unwrap_or(f64::NAN)
273        }
274        _ => f64::NAN,
275    };
276    box_number(num)
277}