Skip to main content

shape_jit/ffi/
array.rs

1// Heap allocation audit (PR-9 V8 Gap Closure):
2//   Category A (NaN-boxed returns): 20 sites
3//   Category B (intermediate/consumed): 0 sites
4//   Category C (heap islands): 0 sites (jit_array_zip inner pairs — fixed via write barrier)
5//!
6//! Array FFI Functions for JIT
7//!
8//! Functions for creating and manipulating arrays in JIT-compiled code.
9//! All arrays are stored as `JitAlloc<JitArray>` via `jit_box(HK_ARRAY, ...)`.
10
11use super::super::context::JITContext;
12use super::super::jit_array::JitArray;
13use super::super::nan_boxing::*;
14
15// ============================================================================
16// Helper Functions
17// ============================================================================
18
19/// Helper: Extract elements from a NaN-boxed array (returns a clone as Vec)
20pub fn get_array_elements(array_bits: u64) -> Vec<u64> {
21    if !is_heap_kind(array_bits, HK_ARRAY) {
22        return Vec::new();
23    }
24    let arr = unsafe { jit_unbox::<JitArray>(array_bits) };
25    arr.as_slice().to_vec()
26}
27
28/// Helper: Create a new array from elements
29pub fn create_array_from_elements(_ctx: *mut JITContext, elements: &[u64]) -> u64 {
30    let arr = JitArray::from_slice(elements);
31    jit_box(HK_ARRAY, arr)
32}
33
34// ============================================================================
35// FFI Functions
36// ============================================================================
37
38/// Extract array data pointer and length from a NaN-boxed array value.
39///
40/// Returns (data_ptr, length) packed into a `#[repr(C)]` struct.
41/// With JitArray's guaranteed layout, this can now be inlined by the JIT
42/// as direct memory loads (offset 0 = data, offset 8 = len).
43///
44/// This FFI version is kept as a fallback for non-inlined paths.
45#[repr(C)]
46pub struct ArrayInfo {
47    pub data_ptr: u64,
48    pub length: u64,
49}
50
51#[unsafe(no_mangle)]
52pub extern "C" fn jit_array_info(array_bits: u64) -> ArrayInfo {
53    if !is_heap_kind(array_bits, HK_ARRAY) {
54        return ArrayInfo {
55            data_ptr: 0,
56            length: 0,
57        };
58    }
59
60    let arr = unsafe { jit_unbox::<JitArray>(array_bits) };
61    ArrayInfo {
62        data_ptr: arr.data as u64,
63        length: arr.len as u64,
64    }
65}
66
67/// Create a new array from values on stack
68pub extern "C" fn jit_new_array(ctx: *mut JITContext, count: usize) -> u64 {
69    unsafe {
70        if ctx.is_null() || count > 512 {
71            return TAG_NULL;
72        }
73
74        let ctx_ref = &mut *ctx;
75        if ctx_ref.stack_ptr < count || ctx_ref.stack_ptr > 512 {
76            return TAG_NULL;
77        }
78
79        let mut elements = Vec::with_capacity(count);
80        for _ in 0..count {
81            ctx_ref.stack_ptr -= 1;
82            elements.push(ctx_ref.stack[ctx_ref.stack_ptr]);
83        }
84        elements.reverse();
85
86        jit_box(HK_ARRAY, JitArray::from_vec(elements))
87    }
88}
89
90/// Get element from array by index (supports negative indexing)
91pub extern "C" fn jit_array_get(array_bits: u64, index_bits: u64) -> u64 {
92    unsafe {
93        if !is_heap_kind(array_bits, HK_ARRAY) || !is_number(index_bits) {
94            return TAG_NULL;
95        }
96
97        let arr = jit_unbox::<JitArray>(array_bits);
98        let index = unbox_number(index_bits) as i64;
99
100        let actual_index = if index < 0 {
101            (arr.len() as i64 + index) as usize
102        } else {
103            index as usize
104        };
105
106        arr.get(actual_index).copied().unwrap_or(TAG_NULL)
107    }
108}
109
110/// Push multiple values onto array (returns new array)
111pub extern "C" fn jit_array_push(ctx: *mut JITContext, count: i64) -> u64 {
112    unsafe {
113        if ctx.is_null() {
114            return TAG_NULL;
115        }
116
117        let ctx_ref = &mut *ctx;
118        let count = count as usize;
119
120        if ctx_ref.stack_ptr == 0 {
121            return TAG_NULL;
122        }
123        ctx_ref.stack_ptr -= 1;
124        let arg_count_val = ctx_ref.stack[ctx_ref.stack_ptr];
125        let arg_count = if is_number(arg_count_val) {
126            unbox_number(arg_count_val) as usize
127        } else {
128            count.saturating_sub(1)
129        };
130
131        let values_to_push = arg_count.saturating_sub(1);
132
133        let mut values = Vec::with_capacity(values_to_push);
134        for _ in 0..values_to_push {
135            if ctx_ref.stack_ptr == 0 {
136                break;
137            }
138            ctx_ref.stack_ptr -= 1;
139            values.push(ctx_ref.stack[ctx_ref.stack_ptr]);
140        }
141        values.reverse();
142
143        if ctx_ref.stack_ptr == 0 {
144            return TAG_NULL;
145        }
146        ctx_ref.stack_ptr -= 1;
147        let array_bits = ctx_ref.stack[ctx_ref.stack_ptr];
148
149        let mut elements = get_array_elements(array_bits);
150        elements.extend(values);
151
152        create_array_from_elements(ctx, &elements)
153    }
154}
155
156/// Pop last element from array (returns new array without last element)
157pub extern "C" fn jit_array_pop(array_bits: u64) -> u64 {
158    let mut elements = get_array_elements(array_bits);
159    if elements.is_empty() {
160        return array_bits;
161    }
162    elements.pop();
163    create_array_from_elements(std::ptr::null_mut(), &elements)
164}
165
166/// Push single element onto array (returns new array with element appended)
167/// Used by ArrayPush opcode in list comprehensions
168pub extern "C" fn jit_array_push_elem(array_bits: u64, value_bits: u64) -> u64 {
169    let mut elements = get_array_elements(array_bits);
170    elements.push(value_bits);
171    create_array_from_elements(std::ptr::null_mut(), &elements)
172}
173
174/// Push a value into an array in-place, mutating the existing JitArray.
175/// Returns the same array bits (the JitAlloc pointer doesn't move; JitArray handles realloc internally).
176/// This is O(1) amortized vs O(n) for jit_array_push_elem which copies all elements.
177/// Used by ArrayPushLocal opcode for `x = x.push(val)` optimization.
178#[unsafe(no_mangle)]
179pub extern "C" fn jit_array_push_local(array_bits: u64, value_bits: u64) -> u64 {
180    if !is_heap_kind(array_bits, HK_ARRAY) {
181        return array_bits;
182    }
183    let arr = unsafe { jit_unbox_mut::<JitArray>(array_bits) };
184    arr.push(value_bits);
185    array_bits
186}
187
188/// Ensure array capacity is at least `min_capacity` elements.
189/// Returns original array bits.
190#[unsafe(no_mangle)]
191pub extern "C" fn jit_array_reserve_local(array_bits: u64, min_capacity: i64) -> u64 {
192    if !is_heap_kind(array_bits, HK_ARRAY) {
193        return array_bits;
194    }
195    if min_capacity <= 0 {
196        return array_bits;
197    }
198    let arr = unsafe { jit_unbox_mut::<JitArray>(array_bits) };
199    arr.reserve(min_capacity as usize);
200    array_bits
201}
202
203/// Zip two arrays into array of pairs
204pub extern "C" fn jit_array_zip(arr1: u64, arr2: u64) -> u64 {
205    let elements1 = get_array_elements(arr1);
206    let elements2 = get_array_elements(arr2);
207
208    let min_len = elements1.len().min(elements2.len());
209    let mut pairs = Vec::with_capacity(min_len);
210
211    for i in 0..min_len {
212        let pair = JitArray::from_slice(&[elements1[i], elements2[i]]);
213        let pair_bits = jit_box(HK_ARRAY, pair);
214        pairs.push(pair_bits);
215    }
216
217    let result = create_array_from_elements(std::ptr::null_mut(), &pairs);
218    // Write barrier: notify GC that result array contains inner heap refs
219    for &pair_bits in &pairs {
220        super::gc::jit_write_barrier(0, pair_bits);
221    }
222    result
223}
224
225/// Get first element of array
226pub extern "C" fn jit_array_first(arr_bits: u64) -> u64 {
227    if !is_heap_kind(arr_bits, HK_ARRAY) {
228        return TAG_NULL;
229    }
230    let arr = unsafe { jit_unbox::<JitArray>(arr_bits) };
231    arr.first().copied().unwrap_or(TAG_NULL)
232}
233
234/// Get last element of array
235pub extern "C" fn jit_array_last(arr_bits: u64) -> u64 {
236    if !is_heap_kind(arr_bits, HK_ARRAY) {
237        return TAG_NULL;
238    }
239    let arr = unsafe { jit_unbox::<JitArray>(arr_bits) };
240    arr.last().copied().unwrap_or(TAG_NULL)
241}
242
243/// Get minimum element of numeric array
244pub extern "C" fn jit_array_min(arr_bits: u64) -> u64 {
245    if !is_heap_kind(arr_bits, HK_ARRAY) {
246        return TAG_NULL;
247    }
248    let arr = unsafe { jit_unbox::<JitArray>(arr_bits) };
249    if arr.is_empty() {
250        return TAG_NULL;
251    }
252    let mut min_val = f64::INFINITY;
253    for &bits in arr.iter() {
254        if is_number(bits) {
255            let n = unbox_number(bits);
256            if n < min_val {
257                min_val = n;
258            }
259        }
260    }
261    if min_val.is_infinite() {
262        TAG_NULL
263    } else {
264        box_number(min_val)
265    }
266}
267
268/// Get maximum element of numeric array
269pub extern "C" fn jit_array_max(arr_bits: u64) -> u64 {
270    if !is_heap_kind(arr_bits, HK_ARRAY) {
271        return TAG_NULL;
272    }
273    let arr = unsafe { jit_unbox::<JitArray>(arr_bits) };
274    if arr.is_empty() {
275        return TAG_NULL;
276    }
277    let mut max_val = f64::NEG_INFINITY;
278    for &bits in arr.iter() {
279        if is_number(bits) {
280            let n = unbox_number(bits);
281            if n > max_val {
282                max_val = n;
283            }
284        }
285    }
286    if max_val.is_infinite() {
287        TAG_NULL
288    } else {
289        box_number(max_val)
290    }
291}
292
293/// Slice an array or string
294pub extern "C" fn jit_slice(arr_bits: u64, start_bits: u64, end_bits: u64) -> u64 {
295    unsafe {
296        // Handle string slicing
297        if is_heap_kind(arr_bits, HK_STRING) {
298            let s = jit_unbox::<String>(arr_bits);
299
300            let start = if is_number(start_bits) {
301                unbox_number(start_bits) as usize
302            } else {
303                0
304            };
305            let end = if is_number(end_bits) {
306                unbox_number(end_bits) as usize
307            } else {
308                s.len()
309            };
310
311            let start = start.min(s.len());
312            let end = end.min(s.len());
313
314            if start > end {
315                return jit_box(HK_STRING, String::new());
316            }
317
318            let sliced = s[start..end].to_string();
319            return jit_box(HK_STRING, sliced);
320        }
321
322        // Handle array slicing
323        if !is_heap_kind(arr_bits, HK_ARRAY) {
324            return TAG_NULL;
325        }
326        let arr = jit_unbox::<JitArray>(arr_bits);
327
328        let start = if is_number(start_bits) {
329            unbox_number(start_bits) as usize
330        } else {
331            0
332        };
333        let end = if is_number(end_bits) {
334            unbox_number(end_bits) as usize
335        } else {
336            arr.len()
337        };
338
339        let start = start.min(arr.len());
340        let end = end.min(arr.len());
341
342        if start > end {
343            return jit_box(HK_ARRAY, JitArray::new());
344        }
345
346        let sliced = JitArray::from_slice(&arr.as_slice()[start..end]);
347        jit_box(HK_ARRAY, sliced)
348    }
349}
350
351/// Create a range array [start, start+1, ..., end-1]
352pub extern "C" fn jit_range(start_bits: u64, end_bits: u64) -> u64 {
353    let start = if is_number(start_bits) {
354        unbox_number(start_bits) as i64
355    } else {
356        0
357    };
358    let end = if is_number(end_bits) {
359        unbox_number(end_bits) as i64
360    } else {
361        0
362    };
363
364    if end > start && (end - start) <= 10000 {
365        let range: Vec<u64> = (start..end).map(|i| box_number(i as f64)).collect();
366        jit_box(HK_ARRAY, JitArray::from_vec(range))
367    } else {
368        jit_box(HK_ARRAY, JitArray::new())
369    }
370}
371
372/// Create a pre-allocated array filled with a given value.
373/// `Array.filled(size, value)` — equivalent to `vec![value; size]`.
374#[unsafe(no_mangle)]
375pub extern "C" fn jit_array_filled(size_bits: u64, value_bits: u64) -> u64 {
376    if !is_number(size_bits) {
377        return TAG_NULL;
378    }
379
380    let size = unbox_number(size_bits) as usize;
381    // Safety limit
382    if size > 100_000_000 {
383        return TAG_NULL;
384    }
385
386    let mut elements = vec![value_bits; size];
387    let len = elements.len();
388    let cap = elements.len();
389    let data = elements.as_mut_ptr();
390    std::mem::forget(elements);
391
392    let mut typed_data: *mut u64 = std::ptr::null_mut();
393    let mut element_kind = crate::jit_array::ArrayElementKind::Untyped as u8;
394    let mut typed_storage_kind = crate::jit_array::ArrayElementKind::Untyped as u8;
395
396    if value_bits == TAG_BOOL_TRUE || value_bits == TAG_BOOL_FALSE {
397        let byte_len = size.div_ceil(8);
398        let mut bytes = if value_bits == TAG_BOOL_TRUE {
399            vec![0xFFu8; byte_len]
400        } else {
401            vec![0u8; byte_len]
402        };
403        if value_bits == TAG_BOOL_TRUE && !bytes.is_empty() {
404            let rem = size & 7;
405            if rem != 0 {
406                let tail_mask = (1u8 << rem) - 1;
407                if let Some(last) = bytes.last_mut() {
408                    *last = tail_mask;
409                }
410            }
411        }
412        typed_data = bytes.as_mut_ptr() as *mut u64;
413        std::mem::forget(bytes);
414        element_kind = crate::jit_array::ArrayElementKind::Bool as u8;
415        typed_storage_kind = crate::jit_array::ArrayElementKind::Bool as u8;
416    }
417
418    // Build directly to avoid O(n) kind inference + typed-mirror initialization.
419    let arr = JitArray {
420        data,
421        len: len as u64,
422        cap: cap as u64,
423        typed_data,
424        typed_storage_kind,
425        element_kind,
426        _padding: [0; 6],
427    };
428    jit_box(HK_ARRAY, arr)
429}
430
431/// Reverse an array, returning a new reversed array.
432/// Used by JIT inline path for `.reverse()`.
433#[unsafe(no_mangle)]
434pub extern "C" fn jit_array_reverse(arr_bits: u64) -> u64 {
435    if !is_heap_kind(arr_bits, HK_ARRAY) {
436        return TAG_NULL;
437    }
438    let arr = unsafe { jit_unbox::<JitArray>(arr_bits) };
439    let mut reversed = arr.as_slice().to_vec();
440    reversed.reverse();
441    jit_box(HK_ARRAY, JitArray::from_vec(reversed))
442}
443
444/// Push a single element onto an array (method call style).
445/// Returns new array with element appended.
446/// Used by JIT inline path for `.push(element)`.
447#[unsafe(no_mangle)]
448pub extern "C" fn jit_array_push_element(arr_bits: u64, element_bits: u64) -> u64 {
449    if !is_heap_kind(arr_bits, HK_ARRAY) {
450        return TAG_NULL;
451    }
452    let arr = unsafe { jit_unbox::<JitArray>(arr_bits) };
453    let mut elements = arr.as_slice().to_vec();
454    elements.push(element_bits);
455    jit_box(HK_ARRAY, JitArray::from_vec(elements))
456}
457
458/// Allocate a new empty array with pre-allocated capacity.
459/// Returns a NaN-boxed HK_ARRAY. Used by HOF inlining to pre-allocate result arrays.
460#[unsafe(no_mangle)]
461pub extern "C" fn jit_hof_array_alloc(capacity: u64) -> u64 {
462    let cap = capacity as usize;
463    if cap > 100_000_000 {
464        return jit_box(HK_ARRAY, JitArray::new());
465    }
466    jit_box(HK_ARRAY, JitArray::with_capacity(cap))
467}
468
469/// Push a single element into an in-place array (used by HOF inlining loops).
470/// Mutates the JitArray, returns the same boxed array bits.
471/// Identical to jit_array_push_local but with a different name for clarity.
472#[unsafe(no_mangle)]
473pub extern "C" fn jit_hof_array_push(array_bits: u64, value_bits: u64) -> u64 {
474    if !is_heap_kind(array_bits, HK_ARRAY) {
475        return array_bits;
476    }
477    let arr = unsafe { jit_unbox_mut::<JitArray>(array_bits) };
478    arr.push(value_bits);
479    array_bits
480}
481
482/// Create a Range object from start and end values
483/// This creates a proper Range object (not an array), used by MakeRange opcode
484pub extern "C" fn jit_make_range(start_bits: u64, end_bits: u64) -> u64 {
485    use super::super::context::JITRange;
486
487    let range = JITRange::new(start_bits, end_bits);
488    JITRange::box_range(range)
489}