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::JITContext;
16// crate::jit_array::JitArray removed — see jit_array.rs SURFACE comment.
17// Higher-order array-walk FFI functions below now route to surface-and-stop
18// per ADR-006 §2.7.4 / W10 jit-playbook §5; the kinded rebuild reads the
19// receiver as `Arc<TypedArrayData>` per-element-kind arm (§2.7.6/Q8).
20use crate::ffi::value_ffi::*;
21#[allow(unused_imports)]
22use crate::ffi::jit_kinds::*;
23use std::ffi::c_void;
24
25// ============================================================================
26// Trampoline VM — thread-local VirtualMachine for JIT-to-VM fallback
27// ============================================================================
28
29use std::cell::Cell;
30
31thread_local! {
32    /// Pointer to a fully-initialized VirtualMachine for executing bytecode
33    /// functions that weren't JIT-compiled. Set by `execute_with_jit()` before
34    /// JIT execution and cleared after. Valid only on the executor thread.
35    static TRAMPOLINE_VM: Cell<*mut shape_vm::VirtualMachine> = const { Cell::new(std::ptr::null_mut()) };
36
37    /// `r5c-2-bz-b-jit-err-surface`: error message from the most recent
38    /// VM-trampoline FFI call (`jit_call_method`) whose VM-side handler
39    /// returned `Err`.
40    ///
41    /// When a trampoline FFI call hits a VM `Err`, it stores the error message
42    /// here and sets `JITContext.pending_call_error = 1`. The MIR emitter
43    /// loads that flag right after the FFI call and deopts (returns
44    /// `SIGNAL_TRAMPOLINE_ERROR`) — the JIT frame is abandoned before the
45    /// FFI's placeholder return value reaches a heap-kinded refcount-retain
46    /// site. `JITExecutor` then `take`s this message and surfaces it as the
47    /// program's runtime error — identical to the error the VM produces, so
48    /// VM and JIT modes agree.
49    static JIT_RUNTIME_ERROR: std::cell::RefCell<Option<String>> =
50        const { std::cell::RefCell::new(None) };
51}
52
53/// Record a VM-trampoline error message for the surrounding JIT execution to
54/// surface on deopt. Called from `jit_call_method`'s `Err` arm. Overwrites any
55/// prior message (the most recent error is the one that triggers the deopt).
56pub fn set_jit_runtime_error(message: String) {
57    JIT_RUNTIME_ERROR.with(|cell| *cell.borrow_mut() = Some(message));
58}
59
60/// Take (and clear) the recorded VM-trampoline error message. Called by
61/// `JITExecutor` when a JIT-compiled function returns a negative signal, so
62/// the clean VM error can be surfaced in place of a generic JIT error code.
63pub fn take_jit_runtime_error() -> Option<String> {
64    JIT_RUNTIME_ERROR.with(|cell| cell.borrow_mut().take())
65}
66
67/// Register the trampoline VM for use during JIT execution.
68///
69/// # Safety
70/// The pointer must remain valid for the entire duration of JIT execution.
71/// Caller must clear it with `unset_trampoline_vm()` after execution.
72pub unsafe fn set_trampoline_vm(vm: *mut shape_vm::VirtualMachine) {
73    TRAMPOLINE_VM.with(|cell| cell.set(vm));
74}
75
76/// Clear the trampoline VM pointer after JIT execution.
77pub fn unset_trampoline_vm() {
78    TRAMPOLINE_VM.with(|cell| cell.set(std::ptr::null_mut()));
79}
80
81/// Access the trampoline VM for read-only queries (schema lookups, etc.)
82pub fn with_trampoline_vm<F, R>(f: F) -> Option<R>
83where
84    F: FnOnce(&shape_vm::VirtualMachine) -> R,
85{
86    TRAMPOLINE_VM.with(|cell| {
87        let vm_ptr = cell.get();
88        if vm_ptr.is_null() {
89            None
90        } else {
91            Some(f(unsafe { &*vm_ptr }))
92        }
93    })
94}
95
96/// Execute a closure with mutable access to the trampoline VM.
97pub fn with_trampoline_vm_mut<F, R>(f: F) -> Option<R>
98where
99    F: FnOnce(&mut shape_vm::VirtualMachine) -> R,
100{
101    TRAMPOLINE_VM.with(|cell| {
102        let vm_ptr = cell.get();
103        if vm_ptr.is_null() {
104            None
105        } else {
106            Some(f(unsafe { &mut *vm_ptr }))
107        }
108    })
109}
110
111/// Dispatch a function call through the trampoline VM for functions that
112/// aren't JIT-compiled (null entries in the function table).
113///
114/// `upvalue_bits` carries the closure's captures when the callee is a
115/// closure (either VM-format heap or unified-heap `JITClosure`). When the
116/// callee is a bare function (TAG_FUNCTION inline), pass `None` to dispatch
117/// through `call_value_immediate_nb` with a plain function ValueWord.
118///
119/// When captures are present we route through `jit_trampoline_call_closure`
120/// on the interpreter side, which binds them to the callee frame's
121/// upvalues exactly as the `op_call_closure` path does. Without this
122/// path, a closure that fails JIT compilation (null entry in the function
123/// table) would be reconstructed as a bare function, losing its captures
124/// and producing `Null` on return.
125fn dispatch_call_via_trampoline_vm(
126    function_id: u32,
127    upvalue_bits: Option<&[u64]>,
128    jit_args: &[u64],
129    jit_ctx: *mut JITContext,
130) -> u64 {
131    use shape_value::NativeKind;
132
133    // r5c-2-bz-b-jit-err-surface: a VM-trampoline value-call that surfaces an
134    // `Err` must NOT continue with a value-shaped placeholder — the same
135    // SIGSEGV class as `jit_call_method` (`TAG_NULL` flowing into a heap-
136    // kinded refcount-retain). Record the error + raise `pending_call_error`
137    // so the MIR-emitted post-call check deopts the JIT frame. The closure
138    // captures the raw `jit_ctx` pointer; it is non-null on every real call
139    // path (the MIR emitter always passes `self.ctx_ptr`).
140    let raise_trampoline_error = |message: String| {
141        set_jit_runtime_error(message);
142        if !jit_ctx.is_null() {
143            unsafe { (*jit_ctx).pending_call_error = 1 };
144        }
145    };
146
147    // §2.7.5 stable-FFI raw-pair shape: each arg / capture pair is
148    // `(u64, NativeKind)`. The JIT MIR emitter widened every arg to
149    // I64 at terminators.rs:651-671 without an associated kind track;
150    // we stamp `NativeKind::UInt64` here (the §2.7.11 callee-
151    // classification kind for function-id-shaped slots, also used as
152    // the "I64-wide raw bits without further classification" carrier
153    // kind at the §2.7.5 stable-FFI boundary). This is NOT a Bool-
154    // default fallback — it is the documented function-id-class kind
155    // shape per ADR-006 §2.7.11/Q12.
156    //
157    // The kind companion is consumed by `jit_trampoline_call_closure`
158    // which wraps each pair as a `KindedSlot` and threads it into the
159    // new frame's locals via `stack_write_kinded`. The VM-side
160    // runtime-tier per-slot kind track is established by the callee's
161    // own FrameDescriptor when it begins execution; the §2.7.5 stable-
162    // FFI handoff doesn't need per-arg semantic kind, only the slot-
163    // size discipline (I64 here).
164    let arg_pairs: Vec<(u64, NativeKind)> = jit_args
165        .iter()
166        .copied()
167        .map(|bits| (bits, NativeKind::UInt64))
168        .collect();
169
170    with_trampoline_vm_mut(|vm| {
171        let func_id = function_id as u16;
172        match upvalue_bits {
173            Some(caps) => {
174                // Shape 2 / 3: closure-with-captures. Route through
175                // `jit_trampoline_call_closure` which materializes a
176                // fresh `OwnedClosureBlock` from `upvalue_bits` and
177                // dispatches via `call_closure_with_nb_args_keepalive`.
178                let capture_pairs: Vec<(u64, NativeKind)> = caps
179                    .iter()
180                    .copied()
181                    .map(|bits| (bits, NativeKind::UInt64))
182                    .collect();
183                match vm.jit_trampoline_call_closure(func_id, &capture_pairs, &arg_pairs, None) {
184                    Ok(bits) => bits,
185                    Err(e) => {
186                        raise_trampoline_error(e.to_string());
187                        TAG_NULL
188                    }
189                }
190            }
191            None => {
192                // Shape 1: bare function callee (no captures). Use the
193                // VM's `call_value_immediate_nb` with a `NativeKind::
194                // UInt64` callee — the §2.7.11 callee-classification
195                // kind for function-id-shaped callees (per
196                // `call_convention.rs:853-877` UInt64 arm).
197                use shape_value::{KindedSlot, ValueSlot};
198                let callee = KindedSlot::new(
199                    ValueSlot::from_raw(func_id as u64),
200                    NativeKind::UInt64,
201                );
202                let kinded_args: Vec<KindedSlot> = arg_pairs
203                    .iter()
204                    .map(|(bits, kind)| {
205                        KindedSlot::new(ValueSlot::from_raw(*bits), *kind)
206                    })
207                    .collect();
208                match vm.call_value_immediate_nb(&callee, &kinded_args, None) {
209                    Ok(result) => {
210                        let bits = result.slot.raw();
211                        // The result's strong-count share transfers to
212                        // the JIT-side stack slot via the return path.
213                        // `mem::forget` prevents the KindedSlot's Drop
214                        // from retiring the share — the caller's stack
215                        // slot now owns it (same pattern as the runtime
216                        // tier's `dispatch_call_value_immediate` per
217                        // §2.7.11/Q12).
218                        std::mem::forget(result);
219                        // The callee KindedSlot was constructed with
220                        // raw bits (no Arc share); its Drop is a no-op
221                        // for UInt64 kind. Same for the arg
222                        // KindedSlots — JIT pre-incremented each share
223                        // before crossing the FFI boundary, and the VM
224                        // already consumed them by transferring into
225                        // the new frame's locals.
226                        std::mem::forget(callee);
227                        std::mem::forget(kinded_args);
228                        bits
229                    }
230                    Err(e) => {
231                        raise_trampoline_error(e.to_string());
232                        TAG_NULL
233                    }
234                }
235            }
236        }
237    })
238    .unwrap_or_else(|| {
239        // `TRAMPOLINE_VM` is null — the JIT-compiled callee could not be
240        // dispatched. Raise `pending_call_error` so the MIR-emitted check
241        // deopts rather than continuing with a value-shaped placeholder.
242        raise_trampoline_error(format!(
243            "JIT value-call for function {} could not reach the interpreter \
244             trampoline",
245            function_id,
246        ));
247        TAG_NULL
248    })
249}
250
251/// Dispatch a native module function call through the trampoline VM.
252fn dispatch_module_fn_call(
253    _module_fn_id: u32,
254    _jit_args: &[u64],
255    _ctx: *mut JITContext,
256) -> u64 {
257    todo!(
258        "phase-2c §2.7.10/Q11: JIT-side kinded handler ABI rebuild — \
259         dispatch_module_fn_call. ModuleFunction callee construction and \
260         the call_value_immediate_nb dispatch shell now take &KindedSlot \
261         per ADR-006 §2.7.10/Q11; the deleted ValueWord::from_module_function \
262         constructor needs a kinded replacement at the producing call \
263         signature per §2.7.5. See \
264         docs/cluster-audits/wave-10-jit-playbook.md §5."
265    )
266}
267
268/// Call a function by function_id
269/// Stack reads args from ctx.stack before the call
270pub extern "C" fn jit_call_function(
271    ctx: *mut JITContext,
272    function_id: u16,
273    _args: *const u64, // deprecated, pass null
274    _arg_count: usize,
275) -> u64 {
276    unsafe {
277        if ctx.is_null() {
278            return TAG_NULL;
279        }
280        let ctx_ref = &mut *ctx;
281
282        // Check if we have a function table
283        if ctx_ref.function_table.is_null() || (function_id as usize) >= ctx_ref.function_table_len
284        {
285            return TAG_NULL;
286        }
287
288        // Get the function pointer
289        let fn_ptr = *ctx_ref.function_table.add(function_id as usize);
290
291        // The function reads its args from the stack (already pushed by caller)
292        // and returns result on the stack
293        let _result_code = fn_ptr(ctx);
294
295        // Pop result from stack
296        if ctx_ref.stack_ptr > 0 {
297            ctx_ref.stack_ptr -= 1;
298            ctx_ref.stack[ctx_ref.stack_ptr]
299        } else {
300            TAG_NULL
301        }
302    }
303}
304
305/// Call a closure or function value through the trampoline VM.
306///
307/// Stack layout (set by MIR `TerminatorKind::Call` lowering in
308/// `mir_compiler/terminators.rs`):
309/// ```text
310///   [..., callee_bits, arg0_bits, arg1_bits, ..., argN-1_bits, arg_count]
311///                                                                       ^ ctx.stack_ptr
312/// ```
313/// `arg_count` is a raw `i64` (not NaN-boxed) per the MIR-side
314/// `iconst(types::I64, args.len() as i64)` push at terminators.rs:681.
315///
316/// ## Callee classification (JIT-internal NaN-box, NOT deleted ValueWord)
317///
318/// Per ADR-006 §2.7.5 the JIT-internal NaN-box scheme in
319/// `crates/shape-jit/src/ffi/value_ffi.rs` is the JIT's own value
320/// representation — it is NOT the deleted runtime-tier `tag_bits`
321/// dispatch (CLAUDE.md "Forbidden Patterns" #4 enumerates the deleted
322/// ValueWord synthesizer / `is_tagged()` runtime handlers / runtime
323/// return-kind stamp family). The JIT-internal predicates
324/// (`is_inline_function`, `is_heap_kind`) operate on the JIT's own
325/// slot encoding and are intentionally preserved.
326///
327/// Two callee shapes flow through `jit_call_value` today:
328///
329///   1. **Inline function** (`box_function(fn_id)` → `TAG_FUNCTION_BITS`
330///      tag): classified by `is_inline_function(callee_bits)`, function-
331///      id recovered by `unbox_function_id(callee_bits)`. The JIT MIR
332///      emitter pushes this shape when the callee operand is a bare
333///      `FunctionRef` constant.
334///
335///   2. **Deprecated `unified_box(HK_CLOSURE, JITClosure)` callees**:
336///      classified by `is_heap_kind(callee_bits, HK_CLOSURE)`. This is
337///      the legacy `jit_make_closure` FFI return shape. New code goes
338///      through `jit_finalize_heap_closure` which returns a raw
339///      `Arc::into_raw(Arc<HeapValue::ClosureRaw>)` (no NaN-box) — see
340///      "kind-source gap" below.
341///
342/// ## Kind-source gap (§2.7.5 surface)
343///
344/// `jit_finalize_heap_closure` (the current preferred closure path)
345/// returns `Arc::into_raw(Arc::new(HeapValue::ClosureRaw(owned))) as u64`
346/// — a raw Arc pointer, not a NaN-boxed value. There is no tag-bit
347/// signature on the bits themselves; the callee's `NativeKind::Ptr(
348/// HeapKind::Closure)` is supplied by the producing site at JIT compile
349/// time and lives in a separate side-table the MIR emitter would have
350/// to thread through the call signature.
351///
352/// Under the current `extern "C" fn(*mut JITContext)` signature, the
353/// callee kind is NOT recoverable from `callee_bits` alone — and per
354/// §2.7.7 #4 / #7 / CLAUDE.md "Forbidden Patterns" we MUST NOT probe
355/// `is_heap()` / `is_tagged()` on the bits to classify (those predicates
356/// are JIT-internal NaN-box checks, valid for the *NaN-boxed* shapes
357/// above, but NOT for raw Arc pointers — a heap pointer with bit-63=0
358/// reads as "not tagged" and the predicate returns false; a heap pointer
359/// that happens to alias a tag pattern is a wrong-shape match).
360///
361/// Per the §2.7.5 stamp-at-compile-time discipline, the principled fix
362/// is for the JIT MIR emitter to extend the call signature to carry a
363/// parallel kind track (or per-callee kind side-table) — that is an
364/// ADR-006 §2.7.5 follow-up and an architectural extension beyond this
365/// sub-cluster's scope. For raw-Arc closure callees today, we
366/// surface-and-stop: return TAG_NULL after popping the stack frame, so
367/// the calling MIR continues with a null result rather than crashing
368/// via `extern "C" todo!()` SIGABRT. The shape mirrors the W11-round-1
369/// close's `jit_join_init` surface — graceful surface, audible via
370/// `--trace-jit=shape_jit=debug` (cluster-2 closure-wave-F tracing-crate
371/// migration 2026-05-16), no silent leak (the Arc share remains owned by
372/// the stack slot per the §2.7.7 retain-on-read discipline).
373///
374/// ## Argument kind sourcing
375///
376/// JIT MIR widens args to I64 at terminators.rs:651-671 without an
377/// associated kind track — the same §2.7.5 gap. We pass raw `u64` bits
378/// through to `jit_trampoline_call_closure` paired with `NativeKind::
379/// UInt64` companions (the §2.7.11 callee-classification kind for
380/// function-id callees) ONLY when we can prove the callee is a function
381/// (case 1 above). The VM-side trampoline does not currently consume
382/// per-arg kinds beyond function dispatch; per `call_convention.rs:
383/// jit_trampoline_call_closure` the args are wrapped as
384/// `KindedSlot::new(ValueSlot::from_raw(bits), kind)` and threaded into
385/// the new frame's locals without inspecting kind on the read side. For
386/// heap-bearing args, the W11-round-1 retain-on-read discipline on the
387/// runtime tier handles refcount; the JIT side has already retained
388/// each share before pushing per the §2.7.7 retain semantics. No
389/// fabrication: `NativeKind::UInt64` is the documented function-id
390/// classification kind, not a Bool-default fallback.
391///
392/// ## Forbidden alternatives (refuse on sight)
393///
394/// - **Decoding callee kind from `callee_bits` via tag-bit probe** —
395///   §2.7.7 #4 / #7 / CLAUDE.md "Forbidden Patterns" #4.
396/// - **Bool-default kind for args/callee** — §2.7.7 #9 / CLAUDE.md
397///   "Forbidden rationalizations" ("Soft-fail counter for now, harden
398///   later" — the W11 round-1 walk-back precedent).
399/// - **Silent no-op of the function-id call path** — the supervisor
400///   explicitly refused the W11 round-1 walk-back of `jit_arc_retain` /
401///   `jit_arc_release` to silent no-ops; the same discipline applies
402///   here (ADR-006 §2.7.14 "Reopen amendment").
403/// - **Resurrecting `ValueWord::clone_from_bits` /
404///   `value_word_drop::vw_drop`** — CLAUDE.md "Forbidden Patterns" #1.
405pub extern "C" fn jit_call_value(ctx: *mut JITContext) -> u64 {
406    use crate::ffi::jit_kinds::unified_unbox;
407    use crate::ffi::stack_kind_code;
408    use crate::context::JITClosure;
409    use shape_value::{HeapKind, NativeKind, heap_value::HeapValue};
410    use std::sync::Arc;
411
412    unsafe {
413        if ctx.is_null() {
414            return TAG_NULL;
415        }
416        let ctx_ref = &mut *ctx;
417
418        // Pop arg_count (raw i64 per the MIR-side `iconst(I64,
419        // args.len() as i64)` push at terminators.rs). The parallel-kind
420        // track byte at this slot is `NativeKind::UInt64` (the documented
421        // §2.7.11 / §2.7.5 I64-wide raw bits carrier kind for FFI
422        // scalar sentinels) per the producing emit_kind_track_write call.
423        if ctx_ref.stack_ptr == 0 {
424            tracing::debug!(
425                target: "shape_jit",
426                "jit-call-value BAIL: stack_ptr=0 at arg_count pop",
427            );
428            return TAG_NULL;
429        }
430        ctx_ref.stack_ptr -= 1;
431        let arg_count = ctx_ref.stack[ctx_ref.stack_ptr] as usize;
432        // Reset the kind byte sentinel for hygiene (matches the VM
433        // `pop_kinded` "write Bool sentinel on dead slot" discipline at
434        // `vm_impl/stack.rs:706`).
435        ctx_ref.stack_kinds[ctx_ref.stack_ptr] = stack_kind_code::SENTINEL;
436
437        // Pop args together with their parallel-track kinds (reverse
438        // stack order, then reverse to source order). The §2.7.7 / Q9
439        // lockstep invariant: each `(bits, kind)` pair is read from the
440        // same slot index.
441        let mut arg_pairs: Vec<(u64, NativeKind)> = Vec::with_capacity(arg_count);
442        for _ in 0..arg_count {
443            if ctx_ref.stack_ptr == 0 {
444                return TAG_NULL;
445            }
446            ctx_ref.stack_ptr -= 1;
447            let bits = ctx_ref.stack[ctx_ref.stack_ptr];
448            let code = ctx_ref.stack_kinds[ctx_ref.stack_ptr];
449            ctx_ref.stack_kinds[ctx_ref.stack_ptr] = stack_kind_code::SENTINEL;
450            // Decode the kind from the parallel track. `None` is a
451            // kind-source gap (§2.7.7 #9) — surface, do not Bool-default.
452            let kind = match stack_kind_code::decode(code) {
453                Some(k) => k,
454                None => {
455                    tracing::debug!(
456                        target: "shape_jit",
457                        code,
458                        stack_ptr = ctx_ref.stack_ptr,
459                        "jit-call-value SURFACE \u{a7}2.7.7 / Q9: arg \
460                         kind-byte is SENTINEL / reserved. The producing \
461                         call site at `mir_compiler/terminators.rs` must \
462                         stamp a concrete NativeKind for every push (no \
463                         Bool-default fallback per \u{a7}2.7.7 #9).",
464                    );
465                    return TAG_NULL;
466                }
467            };
468            arg_pairs.push((bits, kind));
469        }
470        arg_pairs.reverse();
471
472        // Pop callee together with its parallel-track kind. The kind IS
473        // the §2.7.11/Q12 callee-classification discriminator — no tag-
474        // bit decode on `callee_bits`, no `is_heap()` probe (§2.7.7 #4 /
475        // #7 forbidden).
476        if ctx_ref.stack_ptr == 0 {
477            return TAG_NULL;
478        }
479        ctx_ref.stack_ptr -= 1;
480        let callee_bits = ctx_ref.stack[ctx_ref.stack_ptr];
481        let callee_code = ctx_ref.stack_kinds[ctx_ref.stack_ptr];
482        ctx_ref.stack_kinds[ctx_ref.stack_ptr] = stack_kind_code::SENTINEL;
483        let callee_kind = match stack_kind_code::decode(callee_code) {
484            Some(k) => k,
485            None => {
486                tracing::debug!(
487                    target: "shape_jit",
488                    callee_code,
489                    stack_ptr = ctx_ref.stack_ptr,
490                    "jit-call-value SURFACE \u{a7}2.7.7 / Q9: callee kind-byte \
491                     is SENTINEL / reserved. The producing call site must \
492                     stamp the callee's NativeKind from `operand_slot_kind` \
493                     per ADR-006 \u{a7}2.7.11 / Q12. No Bool-default fallback \
494                     (\u{a7}2.7.7 #9).",
495                );
496                return TAG_NULL;
497            }
498        };
499
500        // ── Dispatch on callee kind (§2.7.11 / Q12) ──────────────────────
501        //
502        // Mirror of the VM-side `dispatch_call_value_immediate` in
503        // `crates/shape-vm/src/executor/control_flow/mod.rs:389`. The
504        // callee kind classifies the dispatch shape:
505        //
506        // - `Ptr(HeapKind::Closure)`: raw `Arc::into_raw(Arc<HeapValue::
507        //   ClosureRaw>)` slot bits (the `jit_finalize_heap_closure`
508        //   return shape). Recover the `OwnedClosureBlock` via the
509        //   `Arc<HeapValue>` slot-tier convention and pass through to
510        //   `jit_trampoline_call_closure`, which decodes the closure
511        //   captures kinded.
512        //
513        // - `UInt64` / `Int64` / `IntSize` / `UIntSize`: function-id
514        //   class kind (the §2.7.5 I64-wide raw bits carrier kind also
515        //   used for inline function refs whose bits hold a NaN-boxed
516        //   `TAG_FUNCTION` value). Pass through to the trampoline VM's
517        //   `call_value_immediate_nb` function-id path.
518        //
519        // - Anything else: surface — the language doesn't have other
520        //   callable kinds at the indirect-call entry yet.
521        //
522        // Cases 1 and 2 below are the legacy bit-shape predicates we
523        // preserved through W11-jit-carrier-conversion. They fire only
524        // when the stamped kind is the generic `UInt64` / `Int64`
525        // carrier kind (so the producing site didn't stamp a specific
526        // closure or function-ref kind), and the bits themselves are a
527        // JIT-internal NaN-box pattern (per `value_ffi.rs`). They're
528        // shrunk to a narrow legacy compatibility surface; the principled
529        // dispatch is by kind.
530        let function_id: u16;
531        let mut vm_captures: Option<Vec<u64>> = None;
532
533        match callee_kind {
534            NativeKind::Ptr(HeapKind::Closure) => {
535                // Case 3 (closed): raw `Arc::into_raw(Arc<HeapValue::
536                // ClosureRaw(OwnedClosureBlock)>)` callee bits. Per the
537                // §2.7.11/Q12 slot-tier convention (W7 Round-2.5 close
538                // `5fa4b19`), `clone_with_kind` / `drop_with_kind` for
539                // `HeapKind::Closure` retain/release at the
540                // `Arc<HeapValue>` shape; recover the `OwnedClosureBlock`
541                // by going through `HeapValue::ClosureRaw`.
542                if callee_bits == 0 {
543                    tracing::debug!(
544                        target: "shape_jit",
545                        "jit-call-value BAIL \u{a7}2.7.11/Q12: callee \
546                         stamped Ptr(HeapKind::Closure) but bits=0 \u{2014} \
547                         producing site emitted a null callee.",
548                    );
549                    return TAG_NULL;
550                }
551                // W15.2-LANG-4 jit-filter-predicate close (2026-05-18).
552                // Function-typed parameter slots (e.g. `apply(p: (int)=>bool,
553                // ...)`'s `p`) are stamped `Ptr(HeapKind::Closure)` per
554                // the declared `ConcreteType::Function` mapping in
555                // `native_kind_from_concrete_type`. The producing call
556                // signature `apply(pred, ...)` may deliver either runtime
557                // carrier shape per the closure-zero-captures
558                // optimization in the bytecode compiler:
559                //
560                //   (a) `Arc::into_raw(Arc<HeapValue::ClosureRaw(block)>)`
561                //       — the §2.7.11/Q12 canonical heap-closure carrier
562                //       (escaping closure with captures OR escaping
563                //       closure without captures routed through
564                //       `emit_heap_closure` + `jit_finalize_heap_closure`).
565                //
566                //   (b) `box_function(fn_id)` — the §2.7.11 NaN-box
567                //       function-ref carrier (the bytecode compiler can
568                //       emit `Operand::Function(fid)` for a `let pred =
569                //       |x| x > 24` shape where `x > 24` has no captures
570                //       AND the binding storage class permits the
571                //       fn-ref-as-callable optimization).
572                //
573                // The carrier shape is determined at producing-site
574                // codegen time but the declared-type-based kind
575                // classification (`ConcreteType::Function` →
576                // `Ptr(HeapKind::Closure)`) can't statically project the
577                // carrier — the kind is the slot's *semantic* type, not
578                // the runtime carrier discriminator. Dispatch on the
579                // bit-shape predicate before falling through to the
580                // `Arc::from_raw` deref to avoid UB on the NaN-box
581                // carrier (which would deref random memory).
582                if is_inline_function(callee_bits) {
583                    function_id = unbox_function_id(callee_bits);
584                    // No captures — bare function ref path.
585                    let args: Vec<u64> = arg_pairs.iter().map(|(b, _)| *b).collect();
586                    if !ctx_ref.function_table.is_null()
587                        && (function_id as usize) < ctx_ref.function_table_len
588                    {
589                        let raw_fn_ptr =
590                            *(ctx_ref.function_table as *const *const u8)
591                                .add(function_id as usize);
592                        if !raw_fn_ptr.is_null() {
593                            ctx_ref.stack_ptr = 0;
594                            let _signal = call_jit_fn_with_args(raw_fn_ptr, ctx, &args);
595                            if ctx_ref.stack_ptr > 0 {
596                                ctx_ref.stack_ptr -= 1;
597                                let ret_bits = ctx_ref.stack[ctx_ref.stack_ptr];
598                                ctx_ref.stack_kinds[ctx_ref.stack_ptr] =
599                                    stack_kind_code::SENTINEL;
600                                return ret_bits;
601                            }
602                            return TAG_NULL;
603                        }
604                    }
605                    // Fall through to trampoline VM for the bare-fn case.
606                    return dispatch_call_via_trampoline_vm(
607                        function_id as u32,
608                        None,
609                        &args,
610                        ctx,
611                    );
612                }
613                // Borrow the `Arc<HeapValue>` (use `from_raw` + `into_raw`
614                // to avoid taking the share — the share stays in the
615                // stack slot per §2.7.11 / Q12 the dispatch shell borrow
616                // contract).
617                let arc = Arc::<HeapValue>::from_raw(callee_bits as *const HeapValue);
618                let extracted: Option<(u16, Vec<u64>)> = match &*arc {
619                    HeapValue::ClosureRaw(block) => {
620                        // §2.7.11/Q12: read the function_id from the
621                        // TypedClosureHeader prefix at offset 8 (per
622                        // `closure_raw.rs` `TypedClosureHeader` layout).
623                        let fid = shape_value::v2::closure_raw::typed_closure_function_id(
624                            block.as_ptr(),
625                        );
626                        let cap_count = block.layout().capture_count();
627                        let mut caps: Vec<u64> = Vec::with_capacity(cap_count);
628                        for idx in 0..cap_count {
629                            // §2.7.8/Q10 read_capture_kinded returns
630                            // `(bits, kind)`. `read_capture_kinded` is a
631                            // RAW bit read — it does NOT bump the
632                            // capture's refcount.
633                            //
634                            // γ-CP5 7b (jit-typedarray-ptr,
635                            // v2-raw-heap-aliasing class): retain each
636                            // heap-typed capture before handing it to
637                            // `jit_trampoline_call_closure`. That
638                            // trampoline builds a FRESH `OwnedClosureBlock`
639                            // from these bits (`write_capture_raw_u64` —
640                            // no bump) and the fresh block's `Drop`
641                            // (`release_typed_closure`) WILL release each
642                            // heap capture via the layout's capture masks.
643                            // Its doc-comment states the contract
644                            // explicitly: "the JIT pre-incremented each
645                            // share before crossing the FFI boundary."
646                            // Without this retain every closure call
647                            // retires one share of each captured heap
648                            // value — the original binding's + the
649                            // closure block's shares are consumed within
650                            // a few calls and the next access
651                            // dereferences freed memory (`malloc():
652                            // unaligned tcache chunk` SIGABRT).
653                            //
654                            // The retain is the §2.7.8/Q10 kind-driven
655                            // bump via `KindedSlot::clone` — same dispatch
656                            // table as the VM's per-capture
657                            // `clone_with_kind` at frame setup
658                            // (`executor/call_convention.rs:776`). Inline
659                            // scalars are a no-op (`KindedSlot::clone`'s
660                            // scalar arms). `mem::forget` hands the bumped
661                            // share to the fresh block; the borrow-view
662                            // `KindedSlot` must NOT run its `Drop` (that
663                            // would cancel the bump) so it is forgotten
664                            // too.
665                            let (cap_bits, cap_kind) = block.read_capture_kinded(idx);
666                            let borrow_view = shape_value::KindedSlot::new(
667                                shape_value::ValueSlot::from_raw(cap_bits),
668                                cap_kind,
669                            );
670                            let retained = borrow_view.clone();
671                            std::mem::forget(borrow_view);
672                            std::mem::forget(retained);
673                            caps.push(cap_bits);
674                        }
675                        Some((fid, caps))
676                    }
677                    other => {
678                        // Wrong HeapValue arm under the stamped kind —
679                        // a producing-site bug, not a tag-decode gap.
680                        // Surface with diagnostic.
681                        tracing::debug!(
682                            target: "shape_jit",
683                            heap_kind = ?other.kind(),
684                            "jit-call-value SURFACE \u{a7}2.7.6/Q8: callee \
685                             stamped Ptr(HeapKind::Closure) but HeapValue \
686                             arm is not ClosureRaw. Producing site \
687                             mislabeled the slot kind.",
688                        );
689                        None
690                    }
691                };
692                // Restore the `Arc` raw pointer — the slot share is
693                // still owned by whoever pushed it (the call signature
694                // borrow contract leaves the share with the producer).
695                let _ = Arc::into_raw(arc);
696                match extracted {
697                    Some((f, c)) => {
698                        function_id = f;
699                        vm_captures = Some(c);
700                    }
701                    None => return TAG_NULL,
702                }
703            }
704            NativeKind::Ptr(HeapKind::ModuleFn) => {
705                // ModuleFn callees flow through the comptime dispatch —
706                // the §2.7.26 path. Not yet supported in the JIT-side
707                // value-call surface; the bytecode compiler shouldn't
708                // emit a top-level module-fn callee through this opcode
709                // at present. Surface.
710                tracing::debug!(
711                    target: "shape_jit",
712                    "jit-call-value SURFACE \u{a7}2.7.26: ModuleFn callee \
713                     not implemented in jit_call_value.",
714                );
715                return TAG_NULL;
716            }
717            NativeKind::UInt64
718            | NativeKind::Int64
719            | NativeKind::IntSize
720            | NativeKind::UIntSize
721            | NativeKind::NullableUInt64
722            | NativeKind::NullableInt64
723            | NativeKind::NullableIntSize
724            | NativeKind::NullableUIntSize => {
725                // Generic I64-wide raw bits carrier kind (§2.7.5 / §2.7.11).
726                // The bits hold either (a) a NaN-boxed inline function
727                // ref (the JIT MIR emitter pushes `box_function(fn_id)`
728                // when the callee is a `FunctionRef` constant), or (b)
729                // a NaN-boxed `HK_CLOSURE` legacy unified-heap
730                // `JITClosure` allocation. The JIT-internal NaN-box
731                // predicates `is_inline_function` and
732                // `is_heap_kind(_, HK_CLOSURE)` are intentionally
733                // preserved here per ADR-006 §2.7.5 — they operate on
734                // the JIT's own value representation, NOT on the
735                // deleted runtime-tier `tag_bits` dispatch (CLAUDE.md
736                // "Forbidden Patterns" #4 enumerates the deleted runtime
737                // synthesizer / `is_tagged()` handlers; the JIT-internal
738                // NaN-box checks in `value_ffi.rs` are a different
739                // surface and remain valid).
740                if is_inline_function(callee_bits) {
741                    function_id = unbox_function_id(callee_bits);
742                } else if is_heap_kind(callee_bits, HK_CLOSURE) {
743                    let closure = unified_unbox::<JITClosure>(callee_bits);
744                    function_id = closure.function_id;
745                    let count = closure.captures_count as usize;
746                    let mut caps: Vec<u64> = Vec::with_capacity(count);
747                    for i in 0..count {
748                        caps.push(*closure.captures_ptr.add(i));
749                    }
750                    vm_captures = Some(caps);
751                } else {
752                    tracing::debug!(
753                        target: "shape_jit",
754                        callee_bits,
755                        "jit-call-value SURFACE \u{a7}2.7.5: callee_bits \
756                         stamped UInt64 but is neither inline function \
757                         (TAG_FUNCTION) nor unified-heap HK_CLOSURE. \
758                         Producing site stamped the carrier kind but \
759                         emitted bits that don't match either UInt64-class \
760                         shape.",
761                    );
762                    return TAG_NULL;
763                }
764            }
765            other => {
766                tracing::debug!(
767                    target: "shape_jit",
768                    kind = ?other,
769                    "jit-call-value SURFACE \u{a7}2.7.11/Q12: callee kind \
770                     is not a recognized callable kind. The \u{a7}2.7.11/Q12 \
771                     callee-classification kinds at the indirect-call entry \
772                     are Ptr(HeapKind::Closure) (raw-Arc closure shape), \
773                     Ptr(HeapKind::ModuleFn) (deferred), and UInt64/Int64-\
774                     family (function-id and JIT-internal NaN-box shapes).",
775                );
776                return TAG_NULL;
777            }
778        }
779
780        // Extract the raw arg bits for dispatch. Per-arg kinds are
781        // already paired into `arg_pairs` and consumed inside the
782        // trampoline VM as `KindedSlot` carriers (see
783        // `dispatch_call_via_trampoline_vm`); we keep raw bits here for
784        // the JIT function-table fast path which uses native Cranelift
785        // call signatures (uniformly I64) and has no kind dependency.
786        let args: Vec<u64> = arg_pairs.iter().map(|(b, _)| *b).collect();
787
788        // ── Dispatch ─────────────────────────────────────────────────────
789
790        // Try the JIT function table fast path first (no trampoline
791        // hop). Only the bare-function shape can use this path —
792        // closures need the trampoline VM for the captures-binding
793        // semantics.
794        if vm_captures.is_none()
795            && !ctx_ref.function_table.is_null()
796            && (function_id as usize) < ctx_ref.function_table_len
797        {
798            let raw_fn_ptr =
799                *(ctx_ref.function_table as *const *const u8).add(function_id as usize);
800            if !raw_fn_ptr.is_null() {
801                // Reset ctx.stack_ptr so the callee starts with a clean
802                // stack frame. The kind track is naturally re-initialized
803                // by the callee's own push sequence — the §2.7.7 / Q9
804                // lockstep invariant only constrains the live region of
805                // the stack (`stack[..stack_ptr]`), not the dead region
806                // beyond.
807                ctx_ref.stack_ptr = 0;
808                let _signal = call_jit_fn_with_args(raw_fn_ptr, ctx, &args);
809                // Result is on ctx.stack[0..sp]; pop the top slot.
810                if ctx_ref.stack_ptr > 0 {
811                    ctx_ref.stack_ptr -= 1;
812                    let ret_bits = ctx_ref.stack[ctx_ref.stack_ptr];
813                    // Return-slot kind is consumed implicitly by the
814                    // executor's RETURN_TAG_* dispatch (see
815                    // `executor.rs::execute_with_jit`); we don't need
816                    // to thread it back through `stack_kinds` because
817                    // the calling MIR slot's kind is set by the
818                    // destination write via `write_place`.
819                    ctx_ref.stack_kinds[ctx_ref.stack_ptr] = stack_kind_code::SENTINEL;
820                    return ret_bits;
821                }
822                return TAG_NULL;
823            }
824        }
825
826        // Fallback: route through the trampoline VM. This handles:
827        //   - JIT-untranslated function bodies (null function-table entry).
828        //   - HK_CLOSURE callees (captures threaded into the new frame).
829        //   - Raw-Arc HeapKind::Closure callees (Case 3 closed via the
830        //     §2.7.11/Q12 kind dispatch above).
831        let upvalues: Option<&[u64]> = vm_captures.as_deref();
832        dispatch_call_via_trampoline_vm(
833            function_id as u32,
834            upvalues,
835            &args,
836            ctx,
837        )
838    }
839}
840
841/// Call a JIT-compiled function pointer with the right number of native arguments.
842/// The function has Cranelift signature: fn(ctx_ptr: i64, arg0: i64, ...) -> i32
843///
844/// `pub(crate)` visibility: shared with `ffi/call_method/mod.rs::try_call_user_method`
845/// per W14.2-E-followup soundness fix (2026-05-19) — trait-method UFCS user-callee
846/// dispatch must invoke the JIT-compiled callee through the same native-ABI path
847/// as `jit_call_value`'s bare-function fast path (line ~717). The prior shape that
848/// called `fn_ptr(ctx)` directly under the `JittedStrategyFn` typedef silently
849/// dropped every receiver/arg slot since the callee's extended Cranelift signature
850/// `fn(ctx_ptr, arg0, ..., argN) -> i32` reads its params via System V register/
851/// stack convention, NOT from `ctx.stack`. Per ADR-006 §2.7.10/Q11 the dispatch
852/// shell sources every kind from the §2.7.7/Q9 parallel-kind track; the data half
853/// flows through this helper's typed-fn transmute selector.
854pub(crate) unsafe fn call_jit_fn_with_args(
855    fn_ptr: *const u8,
856    ctx: *mut JITContext,
857    args: &[u64],
858) -> i32 {
859    type F0 = unsafe extern "C" fn(*mut JITContext) -> i32;
860    type F1 = unsafe extern "C" fn(*mut JITContext, u64) -> i32;
861    type F2 = unsafe extern "C" fn(*mut JITContext, u64, u64) -> i32;
862    type F3 = unsafe extern "C" fn(*mut JITContext, u64, u64, u64) -> i32;
863    type F4 = unsafe extern "C" fn(*mut JITContext, u64, u64, u64, u64) -> i32;
864    type F5 = unsafe extern "C" fn(*mut JITContext, u64, u64, u64, u64, u64) -> i32;
865    type F6 = unsafe extern "C" fn(*mut JITContext, u64, u64, u64, u64, u64, u64) -> i32;
866    type F7 = unsafe extern "C" fn(*mut JITContext, u64, u64, u64, u64, u64, u64, u64) -> i32;
867    type F8 = unsafe extern "C" fn(*mut JITContext, u64, u64, u64, u64, u64, u64, u64, u64) -> i32;
868
869    let result = match args.len() {
870        0 => std::mem::transmute::<_, F0>(fn_ptr)(ctx),
871        1 => std::mem::transmute::<_, F1>(fn_ptr)(ctx, args[0]),
872        2 => std::mem::transmute::<_, F2>(fn_ptr)(ctx, args[0], args[1]),
873        3 => std::mem::transmute::<_, F3>(fn_ptr)(ctx, args[0], args[1], args[2]),
874        4 => std::mem::transmute::<_, F4>(fn_ptr)(ctx, args[0], args[1], args[2], args[3]),
875        5 => std::mem::transmute::<_, F5>(fn_ptr)(ctx, args[0], args[1], args[2], args[3], args[4]),
876        6 => std::mem::transmute::<_, F6>(fn_ptr)(ctx, args[0], args[1], args[2], args[3], args[4], args[5]),
877        7 => std::mem::transmute::<_, F7>(fn_ptr)(ctx, args[0], args[1], args[2], args[3], args[4], args[5], args[6]),
878        8 => std::mem::transmute::<_, F8>(fn_ptr)(ctx, args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]),
879        _ => {
880            // Too many args for direct dispatch — fall back to trampoline
881            -1
882        }
883    };
884    result
885}
886
887/// fold(array, initial, fn) - left fold over array
888///
889/// SURFACE (W10 jit-playbook §5 / ADR-006 §2.7.4): walked the deleted
890/// `JitArray` heap layout (`from_heap_bits`). Kinded rebuild reads
891/// `Arc<TypedArrayData>` per-element-kind arm (§2.7.6/Q8) and threads
892/// the per-element kind into the callback dispatch per §2.7.5.
893pub extern "C" fn jit_control_fold(_ctx: *mut JITContext) -> u64 {
894    todo!(
895        "phase-2c §2.7.4 / W10 jit-playbook §5: JitArray rebuild — \
896         jit_control_fold. The deleted UnifiedArray-walk decoded element \
897         bits without per-element NativeKind tracking; the kinded rebuild \
898         reads Arc<TypedArrayData> per ADR-006 §2.7.6/Q8 and dispatches \
899         the callback through the §2.7.10/Q11 kinded handler ABI."
900    )
901}
902
903/// reduce(array, fn, initial) - reduce array to single value
904pub extern "C" fn jit_control_reduce(ctx: *mut JITContext) -> u64 {
905    // reduce is the same as fold
906    jit_control_fold(ctx)
907}
908
909/// map(array, fn) - transform each element
910///
911/// SURFACE (W10 jit-playbook §5 / ADR-006 §2.7.4): same JitArray
912/// deletion as `jit_control_fold` plus the result allocation goes
913/// through the deleted `JitArray::from_vec(...).heap_box()`. Kinded
914/// rebuild allocates a `TypedArray<T>` for the inferred element kind.
915pub extern "C" fn jit_control_map(_ctx: *mut JITContext) -> u64 {
916    todo!(
917        "phase-2c §2.7.4 / W10 jit-playbook §5: JitArray rebuild — \
918         jit_control_map. Receiver decode + result allocation both \
919         block on the kinded TypedArray<T> rebuild per ADR-006 §2.7.6/Q8."
920    )
921}
922
923/// filter(array, predicate) - keep elements where predicate returns true
924pub extern "C" fn jit_control_filter(_ctx: *mut JITContext) -> u64 {
925    todo!(
926        "phase-2c §2.7.4 / W10 jit-playbook §5: JitArray rebuild — \
927         jit_control_filter. Same kinded-TypedArray<T> rebuild as \
928         jit_control_map."
929    )
930}
931
932/// forEach(array, fn, count) - execute fn for each element (side effects)
933pub extern "C" fn jit_control_foreach(_ctx: *mut JITContext, _count: usize) -> u64 {
934    todo!(
935        "phase-2c §2.7.4 / W10 jit-playbook §5: JitArray rebuild — \
936         jit_control_foreach. Same kinded-TypedArray<T> rebuild as \
937         jit_control_map."
938    )
939}
940
941/// find(array, predicate) - find first element matching predicate
942pub extern "C" fn jit_control_find(_ctx: *mut JITContext) -> u64 {
943    todo!(
944        "phase-2c §2.7.4 / W10 jit-playbook §5: JitArray rebuild — \
945         jit_control_find. Same kinded-TypedArray<T> rebuild as \
946         jit_control_map."
947    )
948}
949
950unsafe fn jit_callable_invoker(
951    _ctx: *mut c_void,
952    _callable: &u64,
953    _args: &[u64],
954) -> Result<u64, String> {
955    // Phase-2c §2.7.10/Q11 + §2.7.11/Q12: the kinded value-call ABI
956    // rebuild applies here too — the native-callback re-entry path
957    // pushes the callable + args back onto the JIT stack and dispatches
958    // through `jit_call_value`. Both ends are now kinded surfaces; the
959    // RawCallableInvoker signature must thread `KindedSlot` through
960    // once the kinded JIT-FFI consumer waves land. See
961    // docs/cluster-audits/wave-10-jit-playbook.md §5.
962    Err(
963        "phase-2c §2.7.10/Q11: jit_callable_invoker is a kinded-ABI \
964         surface awaiting the value-call kind-companion lowering"
965            .to_string(),
966    )
967}
968
969/// Invoke a linked foreign function from JIT code.
970///
971/// Args are read from `ctx.stack` (already materialized by lowering):
972/// `[... arg0, arg1, ..., argN-1]` with `arg_count` provided out-of-band.
973enum ForeignInvokeMode {
974    Any,
975    NativeOnly,
976    DynamicOnly,
977}
978
979unsafe fn jit_call_foreign_impl(
980    _ctx: *mut JITContext,
981    _foreign_idx: u32,
982    _arg_count: usize,
983    _mode: ForeignInvokeMode,
984) -> u64 {
985    todo!(
986        "phase-2c §2.7.10/Q11: JIT-side kinded foreign-call ABI rebuild — \
987         jit_call_foreign_impl. The foreign_bridge invoke / invoke_native / \
988         invoke_dynamic surfaces still take &[ValueWord]; once that crate's \
989         own kinded-ABI migration lands, args flow as &[KindedSlot] per \
990         ADR-006 §2.7.10/Q11 and the Err() arm constructs the Result::Err \
991         carrier through the kinded HeapKind::Err producer per §2.7.6/Q8. \
992         See docs/cluster-audits/wave-10-jit-playbook.md §5."
993    )
994}
995
996pub extern "C" fn jit_call_foreign(
997    ctx: *mut JITContext,
998    foreign_idx: u32,
999    arg_count: usize,
1000) -> u64 {
1001    unsafe { jit_call_foreign_impl(ctx, foreign_idx, arg_count, ForeignInvokeMode::Any) }
1002}
1003
1004pub extern "C" fn jit_call_foreign_native(
1005    ctx: *mut JITContext,
1006    foreign_idx: u32,
1007    arg_count: usize,
1008) -> u64 {
1009    unsafe { jit_call_foreign_impl(ctx, foreign_idx, arg_count, ForeignInvokeMode::NativeOnly) }
1010}
1011
1012pub extern "C" fn jit_call_foreign_dynamic(
1013    ctx: *mut JITContext,
1014    foreign_idx: u32,
1015    arg_count: usize,
1016) -> u64 {
1017    unsafe { jit_call_foreign_impl(ctx, foreign_idx, arg_count, ForeignInvokeMode::DynamicOnly) }
1018}
1019
1020unsafe fn jit_call_foreign_native_args_fixed<const N: usize>(
1021    _ctx: *mut JITContext,
1022    _foreign_idx: u32,
1023    _args: [u64; N],
1024) -> u64 {
1025    todo!(
1026        "phase-2c §2.7.10/Q11: JIT-side kinded foreign-call ABI rebuild — \
1027         jit_call_foreign_native_args_fixed<N>. Same gating as \
1028         jit_call_foreign_impl: foreign_bridge invoke_native still takes \
1029         &[ValueWord]; once that crate's own kinded-ABI migration lands, \
1030         the fixed-arity boxed_args array becomes [KindedSlot; N] per \
1031         ADR-006 §2.7.10/Q11. See \
1032         docs/cluster-audits/wave-10-jit-playbook.md §5."
1033    )
1034}
1035
1036macro_rules! define_jit_call_foreign_native_fixed {
1037    ($name:ident, [$($arg:ident),*]) => {
1038        pub extern "C" fn $name(
1039            ctx: *mut JITContext,
1040            foreign_idx: u32,
1041            $($arg: u64),*
1042        ) -> u64 {
1043            unsafe { jit_call_foreign_native_args_fixed(ctx, foreign_idx, [$($arg),*]) }
1044        }
1045    };
1046}
1047
1048define_jit_call_foreign_native_fixed!(jit_call_foreign_native_0, []);
1049define_jit_call_foreign_native_fixed!(jit_call_foreign_native_1, [arg0]);
1050define_jit_call_foreign_native_fixed!(jit_call_foreign_native_2, [arg0, arg1]);
1051define_jit_call_foreign_native_fixed!(jit_call_foreign_native_3, [arg0, arg1, arg2]);
1052define_jit_call_foreign_native_fixed!(jit_call_foreign_native_4, [arg0, arg1, arg2, arg3]);
1053define_jit_call_foreign_native_fixed!(jit_call_foreign_native_5, [arg0, arg1, arg2, arg3, arg4]);
1054define_jit_call_foreign_native_fixed!(
1055    jit_call_foreign_native_6,
1056    [arg0, arg1, arg2, arg3, arg4, arg5]
1057);
1058define_jit_call_foreign_native_fixed!(
1059    jit_call_foreign_native_7,
1060    [arg0, arg1, arg2, arg3, arg4, arg5, arg6]
1061);
1062define_jit_call_foreign_native_fixed!(
1063    jit_call_foreign_native_8,
1064    [arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7]
1065);
1066
1067/// Trampoline placeholder for mixed-table VM fallback paths.
1068///
1069/// When implemented, this will dispatch to the VM interpreter for functions
1070/// that weren't JIT-compiled. The return value from the VM is in ValueWord
1071/// format, so it must be converted to JIT format via `vm_result_to_jit`.
1072pub unsafe extern "C" fn jit_vm_fallback_trampoline(
1073    _ctx: *mut std::ffi::c_void,
1074    _function_id: u32,
1075    _args_ptr: *const u64,
1076    _args_len: u32,
1077) -> u64 {
1078    // TODO: when implemented, convert result via vm_result_to_jit():
1079    //   let vm_result = /* dispatch to VM interpreter */;
1080    //   crate::ffi::object::conversion::vm_result_to_jit(vm_result)
1081    TAG_NULL
1082}
1083
1084/// findIndex(array, predicate) - find index of first element matching predicate
1085pub extern "C" fn jit_control_find_index(_ctx: *mut JITContext) -> u64 {
1086    todo!(
1087        "phase-2c §2.7.4 / W10 jit-playbook §5: JitArray rebuild — \
1088         jit_control_find_index. Same kinded-TypedArray<T> rebuild as \
1089         jit_control_map."
1090    )
1091}
1092
1093/// some(array, predicate) - true if any element matches predicate
1094pub extern "C" fn jit_control_some(_ctx: *mut JITContext) -> u64 {
1095    todo!(
1096        "phase-2c §2.7.4 / W10 jit-playbook §5: JitArray rebuild — \
1097         jit_control_some. Same kinded-TypedArray<T> rebuild as \
1098         jit_control_map."
1099    )
1100}
1101
1102/// every(array, predicate) - true if all elements match predicate
1103pub extern "C" fn jit_control_every(_ctx: *mut JITContext) -> u64 {
1104    todo!(
1105        "phase-2c §2.7.4 / W10 jit-playbook §5: JitArray rebuild — \
1106         jit_control_every. Same kinded-TypedArray<T> rebuild as \
1107         jit_control_map."
1108    )
1109}
1110
1111#[cfg(test)]
1112mod tests {
1113    use super::*;
1114
1115    // jit_call_value_decodes_arg_count_as_raw_i64 — removed. The
1116    // function under test is now SURFACE per ADR-006 §2.7.11/Q12 (kinded
1117    // value-call ABI rebuild); the behavioural decode-arg_count
1118    // regression test belongs to the kinded ABI rebuild wave (W11 /
1119    // deeper Phase-2c) where the call signature exposes the kind
1120    // companion explicitly.
1121
1122    // ── r5c-2-bz-b-jit-err-surface: VM-trampoline error channel ─────────
1123
1124    /// `set_jit_runtime_error` then `take_jit_runtime_error` round-trips the
1125    /// message exactly once; the take clears it so a later, unrelated JIT
1126    /// execution on the same thread does not inherit a stale error.
1127    #[test]
1128    fn jit_runtime_error_channel_round_trips_and_clears() {
1129        // Clear any residue from a prior test on this thread.
1130        let _ = take_jit_runtime_error();
1131        assert_eq!(take_jit_runtime_error(), None);
1132
1133        set_jit_runtime_error("Set.add(): key must be a string".to_string());
1134        assert_eq!(
1135            take_jit_runtime_error().as_deref(),
1136            Some("Set.add(): key must be a string"),
1137        );
1138        // The take cleared it — a second take sees None.
1139        assert_eq!(take_jit_runtime_error(), None);
1140    }
1141
1142    /// The most recent `set_jit_runtime_error` wins — the message that
1143    /// triggers the deopt is the one surfaced.
1144    #[test]
1145    fn jit_runtime_error_channel_keeps_most_recent() {
1146        let _ = take_jit_runtime_error();
1147        set_jit_runtime_error("first error".to_string());
1148        set_jit_runtime_error("second error".to_string());
1149        assert_eq!(take_jit_runtime_error().as_deref(), Some("second error"));
1150        let _ = take_jit_runtime_error();
1151    }
1152
1153    #[test]
1154    #[ignore = "SURFACE: jit_call_foreign_native_0 is extern \"C\" todo!() pending kinded foreign-call ABI rebuild (ADR-006 §2.7.10/Q11, docs/cluster-audits/wave-10-jit-playbook.md §5); extern C can't unwind, so #[should_panic] aborts the test process. Re-enable via `cargo test -- --ignored` once the underlying SURFACE closes."]
1155    fn native_fixed_arity_helpers_surface_pending_kinded_abi() {
1156        // SURFACE: jit_call_foreign_native_args_fixed routes to todo!()
1157        // pending the kinded foreign-call ABI rebuild (§2.7.10/Q11).
1158        // Can't use #[should_panic] on extern "C" functions: Rust 1.93+
1159        // aborts the process (SIGABRT) on a non-unwinding panic instead of
1160        // reporting a clean test failure. Same constraint as
1161        // ffi/v2/mod.rs:1060 `test_array_get_oob_returns_none_via_typed_array`.
1162        let _ = jit_call_foreign_native_0(std::ptr::null_mut(), 0);
1163    }
1164
1165    // Suppress the unused-helpers lint for the moved `native_fixed_arity_helpers_return_null_for_null_context`.
1166    #[allow(dead_code)]
1167    fn native_fixed_arity_helpers_return_null_for_null_context() {
1168        assert_eq!(jit_call_foreign_native_0(std::ptr::null_mut(), 0), TAG_NULL);
1169        assert_eq!(
1170            jit_call_foreign_native_1(std::ptr::null_mut(), 0, TAG_NULL),
1171            TAG_NULL
1172        );
1173        assert_eq!(
1174            jit_call_foreign_native_2(std::ptr::null_mut(), 0, TAG_NULL, TAG_NULL),
1175            TAG_NULL
1176        );
1177        assert_eq!(
1178            jit_call_foreign_native_3(std::ptr::null_mut(), 0, TAG_NULL, TAG_NULL, TAG_NULL),
1179            TAG_NULL
1180        );
1181        assert_eq!(
1182            jit_call_foreign_native_4(
1183                std::ptr::null_mut(),
1184                0,
1185                TAG_NULL,
1186                TAG_NULL,
1187                TAG_NULL,
1188                TAG_NULL
1189            ),
1190            TAG_NULL
1191        );
1192        assert_eq!(
1193            jit_call_foreign_native_5(
1194                std::ptr::null_mut(),
1195                0,
1196                TAG_NULL,
1197                TAG_NULL,
1198                TAG_NULL,
1199                TAG_NULL,
1200                TAG_NULL
1201            ),
1202            TAG_NULL
1203        );
1204        assert_eq!(
1205            jit_call_foreign_native_6(
1206                std::ptr::null_mut(),
1207                0,
1208                TAG_NULL,
1209                TAG_NULL,
1210                TAG_NULL,
1211                TAG_NULL,
1212                TAG_NULL,
1213                TAG_NULL
1214            ),
1215            TAG_NULL
1216        );
1217        assert_eq!(
1218            jit_call_foreign_native_7(
1219                std::ptr::null_mut(),
1220                0,
1221                TAG_NULL,
1222                TAG_NULL,
1223                TAG_NULL,
1224                TAG_NULL,
1225                TAG_NULL,
1226                TAG_NULL,
1227                TAG_NULL
1228            ),
1229            TAG_NULL
1230        );
1231        assert_eq!(
1232            jit_call_foreign_native_8(
1233                std::ptr::null_mut(),
1234                0,
1235                TAG_NULL,
1236                TAG_NULL,
1237                TAG_NULL,
1238                TAG_NULL,
1239                TAG_NULL,
1240                TAG_NULL,
1241                TAG_NULL,
1242                TAG_NULL
1243            ),
1244            TAG_NULL
1245        );
1246    }
1247}