pub extern "C" fn jit_call_value(ctx: *mut JITContext) -> u64Expand 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_ptrarg_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:
-
Inline function (
box_function(fn_id)→TAG_FUNCTION_BITStag): classified byis_inline_function(callee_bits), function- id recovered byunbox_function_id(callee_bits). The JIT MIR emitter pushes this shape when the callee operand is a bareFunctionRefconstant. -
Deprecated
unified_box(HK_CLOSURE, JITClosure)callees: classified byis_heap_kind(callee_bits, HK_CLOSURE). This is the legacyjit_make_closureFFI return shape. New code goes throughjit_finalize_heap_closurewhich returns a rawArc::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_bitsvia 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_releaseto 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.