Skip to main content

jit_call_value

Function jit_call_value 

Source
pub extern "C" fn jit_call_value(ctx: *mut JITContext) -> u64
Expand description

Call a closure or function value through the trampoline VM.

Stack layout (set by MIR TerminatorKind::Call lowering in mir_compiler/terminators.rs):

  [..., callee_bits, arg0_bits, arg1_bits, ..., argN-1_bits, arg_count]
                                                                      ^ ctx.stack_ptr

arg_count is a raw i64 (not NaN-boxed) per the MIR-side iconst(types::I64, args.len() as i64) push at terminators.rs:681.

§Callee classification (JIT-internal NaN-box, NOT deleted ValueWord)

Per ADR-006 §2.7.5 the JIT-internal NaN-box scheme in crates/shape-jit/src/ffi/value_ffi.rs is the JIT’s own value representation — it is NOT the deleted runtime-tier tag_bits dispatch (CLAUDE.md “Forbidden Patterns” #4 enumerates the deleted ValueWord synthesizer / is_tagged() runtime handlers / runtime return-kind stamp family). The JIT-internal predicates (is_inline_function, is_heap_kind) operate on the JIT’s own slot encoding and are intentionally preserved.

Two callee shapes flow through jit_call_value today:

  1. Inline function (box_function(fn_id) → TAG_FUNCTION_BITS tag): classified by is_inline_function(callee_bits), function- id recovered by unbox_function_id(callee_bits). The JIT MIR emitter pushes this shape when the callee operand is a bare FunctionRef constant.

  2. Deprecated unified_box(HK_CLOSURE, JITClosure) callees: classified by is_heap_kind(callee_bits, HK_CLOSURE). This is the legacy jit_make_closure FFI return shape. New code goes through jit_finalize_heap_closure which returns a raw Arc::into_raw(Arc<HeapValue::ClosureRaw>) (no NaN-box) — see “kind-source gap” below.

§Kind-source gap (§2.7.5 surface)

jit_finalize_heap_closure (the current preferred closure path) returns Arc::into_raw(Arc::new(HeapValue::ClosureRaw(owned))) as u64 — a raw Arc pointer, not a NaN-boxed value. There is no tag-bit signature on the bits themselves; the callee’s NativeKind::Ptr( HeapKind::Closure) is supplied by the producing site at JIT compile time and lives in a separate side-table the MIR emitter would have to thread through the call signature.

Under the current extern "C" fn(*mut JITContext) signature, the callee kind is NOT recoverable from callee_bits alone — and per §2.7.7 #4 / #7 / CLAUDE.md “Forbidden Patterns” we MUST NOT probe is_heap() / is_tagged() on the bits to classify (those predicates are JIT-internal NaN-box checks, valid for the NaN-boxed shapes above, but NOT for raw Arc pointers — a heap pointer with bit-63=0 reads as “not tagged” and the predicate returns false; a heap pointer that happens to alias a tag pattern is a wrong-shape match).

Per the §2.7.5 stamp-at-compile-time discipline, the principled fix is for the JIT MIR emitter to extend the call signature to carry a parallel kind track (or per-callee kind side-table) — that is an ADR-006 §2.7.5 follow-up and an architectural extension beyond this sub-cluster’s scope. For raw-Arc closure callees today, we surface-and-stop: return TAG_NULL after popping the stack frame, so the calling MIR continues with a null result rather than crashing via extern "C" todo!() SIGABRT. The shape mirrors the W11-round-1 close’s jit_join_init surface — graceful surface, audible via --trace-jit=shape_jit=debug (cluster-2 closure-wave-F tracing-crate migration 2026-05-16), no silent leak (the Arc share remains owned by the stack slot per the §2.7.7 retain-on-read discipline).

§Argument kind sourcing

JIT MIR widens args to I64 at terminators.rs:651-671 without an associated kind track — the same §2.7.5 gap. We pass raw u64 bits through to jit_trampoline_call_closure paired with NativeKind:: UInt64 companions (the §2.7.11 callee-classification kind for function-id callees) ONLY when we can prove the callee is a function (case 1 above). The VM-side trampoline does not currently consume per-arg kinds beyond function dispatch; per call_convention.rs: jit_trampoline_call_closure the args are wrapped as KindedSlot::new(ValueSlot::from_raw(bits), kind) and threaded into the new frame’s locals without inspecting kind on the read side. For heap-bearing args, the W11-round-1 retain-on-read discipline on the runtime tier handles refcount; the JIT side has already retained each share before pushing per the §2.7.7 retain semantics. No fabrication: NativeKind::UInt64 is the documented function-id classification kind, not a Bool-default fallback.

§Forbidden alternatives (refuse on sight)

  • Decoding callee kind from callee_bits via tag-bit probe — §2.7.7 #4 / #7 / CLAUDE.md “Forbidden Patterns” #4.
  • Bool-default kind for args/callee — §2.7.7 #9 / CLAUDE.md “Forbidden rationalizations” (“Soft-fail counter for now, harden later” — the W11 round-1 walk-back precedent).
  • Silent no-op of the function-id call path — the supervisor explicitly refused the W11 round-1 walk-back of jit_arc_retain / jit_arc_release to silent no-ops; the same discipline applies here (ADR-006 §2.7.14 “Reopen amendment”).
  • Resurrecting ValueWord::clone_from_bits / value_word_drop::vw_drop — CLAUDE.md “Forbidden Patterns” #1.