Skip to main content

shape_jit/ffi/control/
mod.rs

1// Heap allocation audit (PR-9 V8 Gap Closure):
2//   Category A (NaN-boxed returns): 2 sites
3//     jit_box(HK_ARRAY, ...) — jit_control_map, jit_control_filter
4//   Category B (intermediate/consumed): 3 sites
5//     Vec::with_capacity for args in jit_call_value, jit_call_foreign_impl,
6//       jit_call_foreign_native_args_fixed (consumed within call, not escaped)
7//     Arc::new in error path of jit_call_foreign_impl (returned as ValueWord)
8//   Category C (heap islands): 0 sites (jit_control_map results — fixed via write barrier)
9//!
10//! Control Flow FFI Functions for JIT
11//!
12//! Higher-order functions (fold, reduce, map, filter, forEach) and function call helpers
13//! for JIT-compiled code.
14
15use crate::context::{JITClosure, JITContext};
16use crate::ffi::object::conversion::{jit_bits_to_nanboxed_with_ctx, nanboxed_to_jit_bits};
17use crate::jit_array::JitArray;
18use crate::nan_boxing::*;
19use shape_runtime::module_exports::RawCallableInvoker;
20use shape_value::ValueWord;
21use std::ffi::c_void;
22use std::sync::Arc;
23
24/// Call a function by function_id
25/// Stack reads args from ctx.stack before the call
26pub extern "C" fn jit_call_function(
27    ctx: *mut JITContext,
28    function_id: u16,
29    _args: *const u64, // deprecated, pass null
30    _arg_count: usize,
31) -> u64 {
32    unsafe {
33        if ctx.is_null() {
34            return TAG_NULL;
35        }
36        let ctx_ref = &mut *ctx;
37
38        // Check if we have a function table
39        if ctx_ref.function_table.is_null() || (function_id as usize) >= ctx_ref.function_table_len
40        {
41            return TAG_NULL;
42        }
43
44        // Get the function pointer
45        let fn_ptr = *ctx_ref.function_table.add(function_id as usize);
46
47        // The function reads its args from the stack (already pushed by caller)
48        // and returns result on the stack
49        let _result_code = fn_ptr(ctx);
50
51        // Pop result from stack
52        if ctx_ref.stack_ptr > 0 {
53            ctx_ref.stack_ptr -= 1;
54            ctx_ref.stack[ctx_ref.stack_ptr]
55        } else {
56            TAG_NULL
57        }
58    }
59}
60
61/// Call a closure or function value
62/// Stack layout: [callee, arg1, ..., argN, arg_count]
63/// Returns the result of the call
64pub extern "C" fn jit_call_value(ctx: *mut JITContext) -> u64 {
65    unsafe {
66        if ctx.is_null() {
67            return TAG_NULL;
68        }
69        let ctx_ref = &mut *ctx;
70
71        // Pop arg_count
72        if ctx_ref.stack_ptr == 0 {
73            return TAG_NULL;
74        }
75        ctx_ref.stack_ptr -= 1;
76        let arg_count_bits = ctx_ref.stack[ctx_ref.stack_ptr];
77        let arg_count = if is_number(arg_count_bits) {
78            unbox_number(arg_count_bits) as usize
79        } else {
80            return TAG_NULL;
81        };
82
83        // Pop args (in reverse order, then we'll reverse)
84        let mut args = Vec::with_capacity(arg_count);
85        for _ in 0..arg_count {
86            if ctx_ref.stack_ptr == 0 {
87                return TAG_NULL;
88            }
89            ctx_ref.stack_ptr -= 1;
90            args.push(ctx_ref.stack[ctx_ref.stack_ptr]);
91        }
92        args.reverse();
93
94        // Pop callee
95        if ctx_ref.stack_ptr == 0 {
96            return TAG_NULL;
97        }
98        ctx_ref.stack_ptr -= 1;
99        let callee_bits = ctx_ref.stack[ctx_ref.stack_ptr];
100
101        if is_inline_function(callee_bits) {
102            // Direct function reference (function_id encoded in payload)
103            let function_id = unbox_function_id(callee_bits);
104
105            if ctx_ref.function_table.is_null()
106                || (function_id as usize) >= ctx_ref.function_table_len
107            {
108                return TAG_NULL;
109            }
110
111            // Push args back onto stack for the function
112            for &arg in &args {
113                ctx_ref.stack[ctx_ref.stack_ptr] = arg;
114                ctx_ref.stack_ptr += 1;
115            }
116
117            let fn_ptr = *ctx_ref.function_table.add(function_id as usize);
118            let _result_code = fn_ptr(ctx);
119
120            // Pop result
121            if ctx_ref.stack_ptr > 0 {
122                ctx_ref.stack_ptr -= 1;
123                ctx_ref.stack[ctx_ref.stack_ptr]
124            } else {
125                TAG_NULL
126            }
127        } else if is_heap_kind(callee_bits, HK_CLOSURE) {
128            // Closure with captured values
129            let closure = jit_unbox::<JITClosure>(callee_bits);
130            let function_id = closure.function_id;
131
132            if ctx_ref.function_table.is_null()
133                || (function_id as usize) >= ctx_ref.function_table_len
134            {
135                return TAG_NULL;
136            }
137
138            // Push captured values first, then args
139            for i in 0..closure.captures_count as usize {
140                ctx_ref.stack[ctx_ref.stack_ptr] = *closure.captures_ptr.add(i);
141                ctx_ref.stack_ptr += 1;
142            }
143            for &arg in &args {
144                ctx_ref.stack[ctx_ref.stack_ptr] = arg;
145                ctx_ref.stack_ptr += 1;
146            }
147
148            let fn_ptr = *ctx_ref.function_table.add(function_id as usize);
149            let _result_code = fn_ptr(ctx);
150
151            // Pop result
152            if ctx_ref.stack_ptr > 0 {
153                ctx_ref.stack_ptr -= 1;
154                ctx_ref.stack[ctx_ref.stack_ptr]
155            } else {
156                TAG_NULL
157            }
158        } else {
159            TAG_NULL
160        }
161    }
162}
163
164/// fold(array, initial, fn) - left fold over array
165/// Stack layout: [array, fn, initial, arg_count=3]
166pub extern "C" fn jit_control_fold(ctx: *mut JITContext) -> u64 {
167    unsafe {
168        if ctx.is_null() {
169            return TAG_NULL;
170        }
171        let ctx_ref = &mut *ctx;
172
173        // Pop arg_count
174        if ctx_ref.stack_ptr == 0 {
175            return TAG_NULL;
176        }
177        ctx_ref.stack_ptr -= 1;
178
179        // Pop initial value
180        if ctx_ref.stack_ptr == 0 {
181            return TAG_NULL;
182        }
183        ctx_ref.stack_ptr -= 1;
184        let initial = ctx_ref.stack[ctx_ref.stack_ptr];
185
186        // Pop callback
187        if ctx_ref.stack_ptr == 0 {
188            return TAG_NULL;
189        }
190        ctx_ref.stack_ptr -= 1;
191        let callback = ctx_ref.stack[ctx_ref.stack_ptr];
192
193        // Pop array
194        if ctx_ref.stack_ptr == 0 {
195            return TAG_NULL;
196        }
197        ctx_ref.stack_ptr -= 1;
198        let array_bits = ctx_ref.stack[ctx_ref.stack_ptr];
199
200        if !is_heap_kind(array_bits, HK_ARRAY) {
201            return TAG_NULL;
202        }
203
204        let elements = jit_unbox::<JitArray>(array_bits);
205
206        let mut accumulator = initial;
207        for (index, &value) in elements.iter().enumerate() {
208            // Call: callback(accumulator, value, index)
209            ctx_ref.stack[ctx_ref.stack_ptr] = callback;
210            ctx_ref.stack_ptr += 1;
211            ctx_ref.stack[ctx_ref.stack_ptr] = accumulator;
212            ctx_ref.stack_ptr += 1;
213            ctx_ref.stack[ctx_ref.stack_ptr] = value;
214            ctx_ref.stack_ptr += 1;
215            ctx_ref.stack[ctx_ref.stack_ptr] = box_number(index as f64);
216            ctx_ref.stack_ptr += 1;
217            ctx_ref.stack[ctx_ref.stack_ptr] = box_number(3.0); // arg_count
218            ctx_ref.stack_ptr += 1;
219
220            accumulator = jit_call_value(ctx);
221        }
222
223        accumulator
224    }
225}
226
227/// reduce(array, fn, initial) - reduce array to single value
228/// Stack layout: [array, fn, initial, arg_count=3]
229pub extern "C" fn jit_control_reduce(ctx: *mut JITContext) -> u64 {
230    // reduce is the same as fold
231    jit_control_fold(ctx)
232}
233
234/// map(array, fn) - transform each element
235/// Stack layout: [array, fn, arg_count=2]
236pub extern "C" fn jit_control_map(ctx: *mut JITContext) -> u64 {
237    unsafe {
238        if ctx.is_null() {
239            return TAG_NULL;
240        }
241        let ctx_ref = &mut *ctx;
242
243        // Pop arg_count
244        if ctx_ref.stack_ptr == 0 {
245            return TAG_NULL;
246        }
247        ctx_ref.stack_ptr -= 1;
248
249        // Pop callback
250        if ctx_ref.stack_ptr == 0 {
251            return TAG_NULL;
252        }
253        ctx_ref.stack_ptr -= 1;
254        let callback = ctx_ref.stack[ctx_ref.stack_ptr];
255
256        // Pop array
257        if ctx_ref.stack_ptr == 0 {
258            return TAG_NULL;
259        }
260        ctx_ref.stack_ptr -= 1;
261        let array_bits = ctx_ref.stack[ctx_ref.stack_ptr];
262
263        if !is_heap_kind(array_bits, HK_ARRAY) {
264            return TAG_NULL;
265        }
266
267        let elements = jit_unbox::<JitArray>(array_bits);
268
269        let mut results = Vec::with_capacity(elements.len());
270        for (index, &value) in elements.iter().enumerate() {
271            // Call: callback(value, index)
272            ctx_ref.stack[ctx_ref.stack_ptr] = callback;
273            ctx_ref.stack_ptr += 1;
274            ctx_ref.stack[ctx_ref.stack_ptr] = value;
275            ctx_ref.stack_ptr += 1;
276            ctx_ref.stack[ctx_ref.stack_ptr] = box_number(index as f64);
277            ctx_ref.stack_ptr += 1;
278            ctx_ref.stack[ctx_ref.stack_ptr] = box_number(2.0); // arg_count
279            ctx_ref.stack_ptr += 1;
280
281            let result = jit_call_value(ctx);
282            results.push(result);
283        }
284
285        // Write barrier: notify GC that result array contains callback heap refs
286        for &r in &results {
287            crate::ffi::gc::jit_write_barrier(0, r);
288        }
289        jit_box(HK_ARRAY, JitArray::from_vec(results))
290    }
291}
292
293/// filter(array, predicate) - keep elements where predicate returns true
294/// Stack layout: [array, predicate, arg_count=2]
295pub extern "C" fn jit_control_filter(ctx: *mut JITContext) -> u64 {
296    unsafe {
297        if ctx.is_null() {
298            return TAG_NULL;
299        }
300        let ctx_ref = &mut *ctx;
301
302        // Pop arg_count
303        if ctx_ref.stack_ptr == 0 {
304            return TAG_NULL;
305        }
306        ctx_ref.stack_ptr -= 1;
307
308        // Pop predicate
309        if ctx_ref.stack_ptr == 0 {
310            return TAG_NULL;
311        }
312        ctx_ref.stack_ptr -= 1;
313        let predicate = ctx_ref.stack[ctx_ref.stack_ptr];
314
315        // Pop array
316        if ctx_ref.stack_ptr == 0 {
317            return TAG_NULL;
318        }
319        ctx_ref.stack_ptr -= 1;
320        let array_bits = ctx_ref.stack[ctx_ref.stack_ptr];
321
322        if !is_heap_kind(array_bits, HK_ARRAY) {
323            return TAG_NULL;
324        }
325
326        let elements = jit_unbox::<JitArray>(array_bits);
327
328        let mut results = Vec::new();
329        for (index, &value) in elements.iter().enumerate() {
330            // Call: predicate(value, index)
331            ctx_ref.stack[ctx_ref.stack_ptr] = predicate;
332            ctx_ref.stack_ptr += 1;
333            ctx_ref.stack[ctx_ref.stack_ptr] = value;
334            ctx_ref.stack_ptr += 1;
335            ctx_ref.stack[ctx_ref.stack_ptr] = box_number(index as f64);
336            ctx_ref.stack_ptr += 1;
337            ctx_ref.stack[ctx_ref.stack_ptr] = box_number(2.0); // arg_count
338            ctx_ref.stack_ptr += 1;
339
340            let result = jit_call_value(ctx);
341            if result == TAG_BOOL_TRUE {
342                results.push(value);
343            }
344        }
345
346        jit_box(HK_ARRAY, JitArray::from_vec(results))
347    }
348}
349
350/// forEach(array, fn, count) - execute fn for each element (side effects)
351/// Stack layout: [array, fn, count=2]
352pub extern "C" fn jit_control_foreach(ctx: *mut JITContext, _count: usize) -> u64 {
353    unsafe {
354        if ctx.is_null() {
355            return TAG_NULL;
356        }
357        let ctx_ref = &mut *ctx;
358
359        // Pop callback
360        if ctx_ref.stack_ptr == 0 {
361            return TAG_NULL;
362        }
363        ctx_ref.stack_ptr -= 1;
364        let callback = ctx_ref.stack[ctx_ref.stack_ptr];
365
366        // Pop array
367        if ctx_ref.stack_ptr == 0 {
368            return TAG_NULL;
369        }
370        ctx_ref.stack_ptr -= 1;
371        let array_bits = ctx_ref.stack[ctx_ref.stack_ptr];
372
373        if !is_heap_kind(array_bits, HK_ARRAY) {
374            return TAG_NULL;
375        }
376
377        let elements = jit_unbox::<JitArray>(array_bits);
378
379        for (index, &value) in elements.iter().enumerate() {
380            // Call: callback(value, index)
381            ctx_ref.stack[ctx_ref.stack_ptr] = callback;
382            ctx_ref.stack_ptr += 1;
383            ctx_ref.stack[ctx_ref.stack_ptr] = value;
384            ctx_ref.stack_ptr += 1;
385            ctx_ref.stack[ctx_ref.stack_ptr] = box_number(index as f64);
386            ctx_ref.stack_ptr += 1;
387            ctx_ref.stack[ctx_ref.stack_ptr] = box_number(2.0); // arg_count
388            ctx_ref.stack_ptr += 1;
389
390            let _result = jit_call_value(ctx);
391        }
392
393        TAG_NULL // forEach returns null/unit
394    }
395}
396
397/// find(array, predicate) - find first element matching predicate
398/// Stack layout: [array, predicate, arg_count=2]
399pub extern "C" fn jit_control_find(ctx: *mut JITContext) -> u64 {
400    unsafe {
401        if ctx.is_null() {
402            return TAG_NULL;
403        }
404        let ctx_ref = &mut *ctx;
405
406        // Pop arg_count
407        if ctx_ref.stack_ptr == 0 {
408            return TAG_NULL;
409        }
410        ctx_ref.stack_ptr -= 1;
411
412        // Pop predicate
413        if ctx_ref.stack_ptr == 0 {
414            return TAG_NULL;
415        }
416        ctx_ref.stack_ptr -= 1;
417        let predicate = ctx_ref.stack[ctx_ref.stack_ptr];
418
419        // Pop array
420        if ctx_ref.stack_ptr == 0 {
421            return TAG_NULL;
422        }
423        ctx_ref.stack_ptr -= 1;
424        let array_bits = ctx_ref.stack[ctx_ref.stack_ptr];
425
426        if !is_heap_kind(array_bits, HK_ARRAY) {
427            return TAG_NULL;
428        }
429
430        let elements = jit_unbox::<JitArray>(array_bits);
431
432        for (index, &value) in elements.iter().enumerate() {
433            // Call: predicate(value, index)
434            ctx_ref.stack[ctx_ref.stack_ptr] = predicate;
435            ctx_ref.stack_ptr += 1;
436            ctx_ref.stack[ctx_ref.stack_ptr] = value;
437            ctx_ref.stack_ptr += 1;
438            ctx_ref.stack[ctx_ref.stack_ptr] = box_number(index as f64);
439            ctx_ref.stack_ptr += 1;
440            ctx_ref.stack[ctx_ref.stack_ptr] = box_number(2.0); // arg_count
441            ctx_ref.stack_ptr += 1;
442
443            let result = jit_call_value(ctx);
444            if result == TAG_BOOL_TRUE {
445                return value;
446            }
447        }
448
449        TAG_NULL // Not found
450    }
451}
452
453unsafe fn jit_callable_invoker(
454    ctx: *mut c_void,
455    callable: &ValueWord,
456    args: &[ValueWord],
457) -> Result<ValueWord, String> {
458    if ctx.is_null() {
459        return Err("native callback invoker received null JIT context".to_string());
460    }
461
462    let jit_ctx = unsafe { &mut *(ctx as *mut JITContext) };
463    let base_sp = jit_ctx.stack_ptr;
464    let needed = args.len().saturating_add(2); // callee + args + arg_count
465    if base_sp.saturating_add(needed) > jit_ctx.stack.len() {
466        return Err("native callback exceeded JIT stack capacity".to_string());
467    }
468
469    jit_ctx.stack[jit_ctx.stack_ptr] = nanboxed_to_jit_bits(callable);
470    jit_ctx.stack_ptr += 1;
471    for arg in args {
472        jit_ctx.stack[jit_ctx.stack_ptr] = nanboxed_to_jit_bits(arg);
473        jit_ctx.stack_ptr += 1;
474    }
475    jit_ctx.stack[jit_ctx.stack_ptr] = box_number(args.len() as f64);
476    jit_ctx.stack_ptr += 1;
477
478    let result_bits = jit_call_value(jit_ctx as *mut JITContext);
479    jit_ctx.stack_ptr = base_sp;
480    Ok(jit_bits_to_nanboxed_with_ctx(
481        result_bits,
482        jit_ctx as *const JITContext,
483    ))
484}
485
486/// Invoke a linked foreign function from JIT code.
487///
488/// Args are read from `ctx.stack` (already materialized by lowering):
489/// `[... arg0, arg1, ..., argN-1]` with `arg_count` provided out-of-band.
490enum ForeignInvokeMode {
491    Any,
492    NativeOnly,
493    DynamicOnly,
494}
495
496unsafe fn jit_call_foreign_impl(
497    ctx: *mut JITContext,
498    foreign_idx: u32,
499    arg_count: usize,
500    mode: ForeignInvokeMode,
501) -> u64 {
502    if ctx.is_null() {
503        return TAG_NULL;
504    }
505    let ctx_ref = unsafe { &mut *ctx };
506    if ctx_ref.foreign_bridge_ptr.is_null() {
507        return TAG_NULL;
508    }
509    if ctx_ref.stack_ptr < arg_count {
510        return TAG_NULL;
511    }
512
513    let args_start = ctx_ref.stack_ptr - arg_count;
514    let mut args = Vec::with_capacity(arg_count);
515    for idx in args_start..ctx_ref.stack_ptr {
516        args.push(jit_bits_to_nanboxed_with_ctx(
517            ctx_ref.stack[idx],
518            ctx as *const JITContext,
519        ));
520    }
521    ctx_ref.stack_ptr = args_start;
522
523    let bridge = unsafe {
524        &*(ctx_ref.foreign_bridge_ptr as *const crate::foreign_bridge::JitForeignBridgeState)
525    };
526    let raw_invoker = RawCallableInvoker {
527        ctx: ctx as *mut c_void,
528        invoke: jit_callable_invoker,
529    };
530
531    let result = match mode {
532        ForeignInvokeMode::Any => bridge.invoke(foreign_idx as usize, &args, Some(raw_invoker)),
533        ForeignInvokeMode::NativeOnly => {
534            bridge.invoke_native(foreign_idx as usize, &args, Some(raw_invoker))
535        }
536        ForeignInvokeMode::DynamicOnly => bridge.invoke_dynamic(foreign_idx as usize, &args),
537    };
538
539    match result {
540        Ok(result) => nanboxed_to_jit_bits(&result),
541        Err(err) => {
542            let err_nb = ValueWord::from_err(ValueWord::from_string(Arc::new(err)));
543            nanboxed_to_jit_bits(&err_nb)
544        }
545    }
546}
547
548pub extern "C" fn jit_call_foreign(
549    ctx: *mut JITContext,
550    foreign_idx: u32,
551    arg_count: usize,
552) -> u64 {
553    unsafe { jit_call_foreign_impl(ctx, foreign_idx, arg_count, ForeignInvokeMode::Any) }
554}
555
556pub extern "C" fn jit_call_foreign_native(
557    ctx: *mut JITContext,
558    foreign_idx: u32,
559    arg_count: usize,
560) -> u64 {
561    unsafe { jit_call_foreign_impl(ctx, foreign_idx, arg_count, ForeignInvokeMode::NativeOnly) }
562}
563
564pub extern "C" fn jit_call_foreign_dynamic(
565    ctx: *mut JITContext,
566    foreign_idx: u32,
567    arg_count: usize,
568) -> u64 {
569    unsafe { jit_call_foreign_impl(ctx, foreign_idx, arg_count, ForeignInvokeMode::DynamicOnly) }
570}
571
572unsafe fn jit_call_foreign_native_args_fixed<const N: usize>(
573    ctx: *mut JITContext,
574    foreign_idx: u32,
575    args: [u64; N],
576) -> u64 {
577    if ctx.is_null() {
578        return TAG_NULL;
579    }
580    let ctx_ref = unsafe { &mut *ctx };
581    if ctx_ref.foreign_bridge_ptr.is_null() {
582        return TAG_NULL;
583    }
584
585    let bridge = unsafe {
586        &*(ctx_ref.foreign_bridge_ptr as *const crate::foreign_bridge::JitForeignBridgeState)
587    };
588    let raw_invoker = RawCallableInvoker {
589        ctx: ctx as *mut c_void,
590        invoke: jit_callable_invoker,
591    };
592    let boxed_args: [ValueWord; N] = std::array::from_fn(|idx| {
593        jit_bits_to_nanboxed_with_ctx(args[idx], ctx as *const JITContext)
594    });
595
596    match bridge.invoke_native(foreign_idx as usize, &boxed_args, Some(raw_invoker)) {
597        Ok(result) => nanboxed_to_jit_bits(&result),
598        Err(err) => {
599            let err_nb = ValueWord::from_err(ValueWord::from_string(Arc::new(err)));
600            nanboxed_to_jit_bits(&err_nb)
601        }
602    }
603}
604
605macro_rules! define_jit_call_foreign_native_fixed {
606    ($name:ident, [$($arg:ident),*]) => {
607        pub extern "C" fn $name(
608            ctx: *mut JITContext,
609            foreign_idx: u32,
610            $($arg: u64),*
611        ) -> u64 {
612            unsafe { jit_call_foreign_native_args_fixed(ctx, foreign_idx, [$($arg),*]) }
613        }
614    };
615}
616
617define_jit_call_foreign_native_fixed!(jit_call_foreign_native_0, []);
618define_jit_call_foreign_native_fixed!(jit_call_foreign_native_1, [arg0]);
619define_jit_call_foreign_native_fixed!(jit_call_foreign_native_2, [arg0, arg1]);
620define_jit_call_foreign_native_fixed!(jit_call_foreign_native_3, [arg0, arg1, arg2]);
621define_jit_call_foreign_native_fixed!(jit_call_foreign_native_4, [arg0, arg1, arg2, arg3]);
622define_jit_call_foreign_native_fixed!(jit_call_foreign_native_5, [arg0, arg1, arg2, arg3, arg4]);
623define_jit_call_foreign_native_fixed!(
624    jit_call_foreign_native_6,
625    [arg0, arg1, arg2, arg3, arg4, arg5]
626);
627define_jit_call_foreign_native_fixed!(
628    jit_call_foreign_native_7,
629    [arg0, arg1, arg2, arg3, arg4, arg5, arg6]
630);
631define_jit_call_foreign_native_fixed!(
632    jit_call_foreign_native_8,
633    [arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7]
634);
635
636/// Trampoline placeholder for mixed-table VM fallback paths.
637pub unsafe extern "C" fn jit_vm_fallback_trampoline(
638    _ctx: *mut std::ffi::c_void,
639    _function_id: u32,
640    _args_ptr: *const u64,
641    _args_len: u32,
642) -> u64 {
643    TAG_NULL
644}
645
646/// findIndex(array, predicate) - find index of first element matching predicate
647/// Stack layout: [array, predicate, arg_count=2]
648pub extern "C" fn jit_control_find_index(ctx: *mut JITContext) -> u64 {
649    unsafe {
650        if ctx.is_null() {
651            return box_number(-1.0);
652        }
653        let ctx_ref = &mut *ctx;
654
655        // Pop arg_count
656        if ctx_ref.stack_ptr == 0 {
657            return box_number(-1.0);
658        }
659        ctx_ref.stack_ptr -= 1;
660
661        // Pop predicate
662        if ctx_ref.stack_ptr == 0 {
663            return box_number(-1.0);
664        }
665        ctx_ref.stack_ptr -= 1;
666        let predicate = ctx_ref.stack[ctx_ref.stack_ptr];
667
668        // Pop array
669        if ctx_ref.stack_ptr == 0 {
670            return box_number(-1.0);
671        }
672        ctx_ref.stack_ptr -= 1;
673        let array_bits = ctx_ref.stack[ctx_ref.stack_ptr];
674
675        if !is_heap_kind(array_bits, HK_ARRAY) {
676            return box_number(-1.0);
677        }
678
679        let elements = jit_unbox::<JitArray>(array_bits);
680
681        for (index, &value) in elements.iter().enumerate() {
682            // Call: predicate(value, index)
683            ctx_ref.stack[ctx_ref.stack_ptr] = predicate;
684            ctx_ref.stack_ptr += 1;
685            ctx_ref.stack[ctx_ref.stack_ptr] = value;
686            ctx_ref.stack_ptr += 1;
687            ctx_ref.stack[ctx_ref.stack_ptr] = box_number(index as f64);
688            ctx_ref.stack_ptr += 1;
689            ctx_ref.stack[ctx_ref.stack_ptr] = box_number(2.0); // arg_count
690            ctx_ref.stack_ptr += 1;
691
692            let result = jit_call_value(ctx);
693            if result == TAG_BOOL_TRUE {
694                return box_number(index as f64);
695            }
696        }
697
698        box_number(-1.0) // Not found
699    }
700}
701
702/// some(array, predicate) - true if any element matches predicate
703/// Stack layout: [array, predicate, arg_count=2]
704pub extern "C" fn jit_control_some(ctx: *mut JITContext) -> u64 {
705    unsafe {
706        if ctx.is_null() {
707            return TAG_BOOL_FALSE;
708        }
709        let ctx_ref = &mut *ctx;
710
711        // Pop arg_count
712        if ctx_ref.stack_ptr == 0 {
713            return TAG_BOOL_FALSE;
714        }
715        ctx_ref.stack_ptr -= 1;
716
717        // Pop predicate
718        if ctx_ref.stack_ptr == 0 {
719            return TAG_BOOL_FALSE;
720        }
721        ctx_ref.stack_ptr -= 1;
722        let predicate = ctx_ref.stack[ctx_ref.stack_ptr];
723
724        // Pop array
725        if ctx_ref.stack_ptr == 0 {
726            return TAG_BOOL_FALSE;
727        }
728        ctx_ref.stack_ptr -= 1;
729        let array_bits = ctx_ref.stack[ctx_ref.stack_ptr];
730
731        if !is_heap_kind(array_bits, HK_ARRAY) {
732            return TAG_BOOL_FALSE;
733        }
734
735        let elements = jit_unbox::<JitArray>(array_bits);
736
737        for (index, &value) in elements.iter().enumerate() {
738            // Call: predicate(value, index)
739            ctx_ref.stack[ctx_ref.stack_ptr] = predicate;
740            ctx_ref.stack_ptr += 1;
741            ctx_ref.stack[ctx_ref.stack_ptr] = value;
742            ctx_ref.stack_ptr += 1;
743            ctx_ref.stack[ctx_ref.stack_ptr] = box_number(index as f64);
744            ctx_ref.stack_ptr += 1;
745            ctx_ref.stack[ctx_ref.stack_ptr] = box_number(2.0); // arg_count
746            ctx_ref.stack_ptr += 1;
747
748            let result = jit_call_value(ctx);
749            if result == TAG_BOOL_TRUE {
750                return TAG_BOOL_TRUE;
751            }
752        }
753
754        TAG_BOOL_FALSE
755    }
756}
757
758/// every(array, predicate) - true if all elements match predicate
759/// Stack layout: [array, predicate, arg_count=2]
760pub extern "C" fn jit_control_every(ctx: *mut JITContext) -> u64 {
761    unsafe {
762        if ctx.is_null() {
763            return TAG_BOOL_FALSE;
764        }
765        let ctx_ref = &mut *ctx;
766
767        // Pop arg_count
768        if ctx_ref.stack_ptr == 0 {
769            return TAG_BOOL_FALSE;
770        }
771        ctx_ref.stack_ptr -= 1;
772
773        // Pop predicate
774        if ctx_ref.stack_ptr == 0 {
775            return TAG_BOOL_FALSE;
776        }
777        ctx_ref.stack_ptr -= 1;
778        let predicate = ctx_ref.stack[ctx_ref.stack_ptr];
779
780        // Pop array
781        if ctx_ref.stack_ptr == 0 {
782            return TAG_BOOL_FALSE;
783        }
784        ctx_ref.stack_ptr -= 1;
785        let array_bits = ctx_ref.stack[ctx_ref.stack_ptr];
786
787        if !is_heap_kind(array_bits, HK_ARRAY) {
788            return TAG_BOOL_FALSE;
789        }
790
791        let elements = jit_unbox::<JitArray>(array_bits);
792
793        if elements.is_empty() {
794            return TAG_BOOL_TRUE; // Empty array - vacuous truth
795        }
796
797        for (index, &value) in elements.iter().enumerate() {
798            // Call: predicate(value, index)
799            ctx_ref.stack[ctx_ref.stack_ptr] = predicate;
800            ctx_ref.stack_ptr += 1;
801            ctx_ref.stack[ctx_ref.stack_ptr] = value;
802            ctx_ref.stack_ptr += 1;
803            ctx_ref.stack[ctx_ref.stack_ptr] = box_number(index as f64);
804            ctx_ref.stack_ptr += 1;
805            ctx_ref.stack[ctx_ref.stack_ptr] = box_number(2.0); // arg_count
806            ctx_ref.stack_ptr += 1;
807
808            let result = jit_call_value(ctx);
809            if result != TAG_BOOL_TRUE {
810                return TAG_BOOL_FALSE;
811            }
812        }
813
814        TAG_BOOL_TRUE
815    }
816}
817
818#[cfg(test)]
819mod tests {
820    use super::*;
821
822    #[test]
823    fn native_fixed_arity_helpers_return_null_for_null_context() {
824        assert_eq!(jit_call_foreign_native_0(std::ptr::null_mut(), 0), TAG_NULL);
825        assert_eq!(
826            jit_call_foreign_native_1(std::ptr::null_mut(), 0, TAG_NULL),
827            TAG_NULL
828        );
829        assert_eq!(
830            jit_call_foreign_native_2(std::ptr::null_mut(), 0, TAG_NULL, TAG_NULL),
831            TAG_NULL
832        );
833        assert_eq!(
834            jit_call_foreign_native_3(std::ptr::null_mut(), 0, TAG_NULL, TAG_NULL, TAG_NULL),
835            TAG_NULL
836        );
837        assert_eq!(
838            jit_call_foreign_native_4(
839                std::ptr::null_mut(),
840                0,
841                TAG_NULL,
842                TAG_NULL,
843                TAG_NULL,
844                TAG_NULL
845            ),
846            TAG_NULL
847        );
848        assert_eq!(
849            jit_call_foreign_native_5(
850                std::ptr::null_mut(),
851                0,
852                TAG_NULL,
853                TAG_NULL,
854                TAG_NULL,
855                TAG_NULL,
856                TAG_NULL
857            ),
858            TAG_NULL
859        );
860        assert_eq!(
861            jit_call_foreign_native_6(
862                std::ptr::null_mut(),
863                0,
864                TAG_NULL,
865                TAG_NULL,
866                TAG_NULL,
867                TAG_NULL,
868                TAG_NULL,
869                TAG_NULL
870            ),
871            TAG_NULL
872        );
873        assert_eq!(
874            jit_call_foreign_native_7(
875                std::ptr::null_mut(),
876                0,
877                TAG_NULL,
878                TAG_NULL,
879                TAG_NULL,
880                TAG_NULL,
881                TAG_NULL,
882                TAG_NULL,
883                TAG_NULL
884            ),
885            TAG_NULL
886        );
887        assert_eq!(
888            jit_call_foreign_native_8(
889                std::ptr::null_mut(),
890                0,
891                TAG_NULL,
892                TAG_NULL,
893                TAG_NULL,
894                TAG_NULL,
895                TAG_NULL,
896                TAG_NULL,
897                TAG_NULL,
898                TAG_NULL
899            ),
900            TAG_NULL
901        );
902    }
903}