Skip to main content

luna_jit_helpers/
lib.rs

1//! v2.1 Phase 1K.D.1 — shared `luna_jit_*` extern-C runtime helpers
2//! and the per-thread `JIT_VM` / `JIT_CL` TLS slots plus the
3//! `enter_jit` RAII rebind. Extracted verbatim from
4//! `luna-jit/src/jit_backend/mod.rs` so both `luna-jit` (Cranelift)
5//! and `luna-jit-llvm` (v2.1 alt backend) share one symbol table
6//! and one TLS discipline.
7//!
8//! luna-jit re-exports everything in this crate via
9//! `pub use luna_jit_helpers::*;` from `jit_backend/mod.rs`, so all
10//! historical `crate::jit_backend::luna_jit_*` / `super::luna_jit_*`
11//! paths resolve unchanged. luna-jit-llvm depends on this crate
12//! directly without pulling Cranelift.
13//!
14//! See `.dev/rfcs/v2.1-phase-1k-c-trait-audit.md` § 3.5 + § 5.1 for
15//! the extraction rationale.
16//!
17//! # Invariants
18//!
19//! - Symbol names are `#[unsafe(no_mangle)] pub unsafe extern "C" fn
20//!   luna_jit_*` — Cranelift's `Linkage::Import` resolves them by
21//!   linker symbol; LLVM's `Module::add_function` resolves them via
22//!   JIT execution-engine `add_global_mapping`.
23//! - Every helper is called only under an active `enter_jit` guard
24//!   (which pins `JIT_VM` / `JIT_CL` for the dispatch window) and
25//!   reads the Vm/closure pointer via `current_jit_vm()` /
26//!   `current_jit_closure()`.
27
28// All helpers use fully-qualified `luna_core::*` paths internally
29// (preserved verbatim from the original `luna-jit/src/jit_backend/mod.rs`
30// site). Only the `JitVmGuard` re-export is needed by the `enter_jit`
31// signature below.
32use luna_core::jit::JitVmGuard;
33
34thread_local! {
35    /// v2.0 Track J sub-step J-B — `JIT_CACHE` (Phase D) and
36    /// `JIT_CACHE_HANDLES` (Phase E) both migrated to
37    /// `Vm.jit.storage.{cache,cache_handles}`. The JIT_VM / JIT_CL
38    /// per-dispatch slots below stay TLS until J-D's
39    /// `scoped_jit_vm_rebind` RAII lift.
40
41    /// P11-S5c — current `Vm` pointer for Rust helpers called from
42    /// JIT'd code. Set by [`enter_jit`] just before invoking the
43    /// entry fn; cleared (RAII via [`JitVmGuard`]) on return. Helpers
44    /// (`luna_jit_new_table`, `luna_jit_table_set_int`, etc.) read
45    /// this to reach `Vm.heap`. Null when no JIT call is in flight.
46    static JIT_VM: std::cell::Cell<*mut luna_core::vm::Vm> =
47        const { std::cell::Cell::new(std::ptr::null_mut()) };
48    /// P11-S5d.J — current `LuaClosure` pointer for `Op::GetUpval`
49    /// value-read helpers. Set alongside `JIT_VM` by [`enter_jit`].
50    /// Null when no JIT call is in flight, or when the active call
51    /// has no upvalues (zero-upval Protos never reach
52    /// `luna_jit_upval_get`).
53    static JIT_CL: std::cell::Cell<*const luna_core::runtime::LuaClosure> =
54        const { std::cell::Cell::new(std::ptr::null()) };
55}
56
57/// P11-S5c — install `vm` as the current JIT Vm pointer. Returns a
58/// [`JitVmGuard`] whose drop restores the prior `(JIT_VM, JIT_CL)`
59/// values (J-D RAII rebind). Must be held across the JIT entry-fn
60/// call so any helper can pick the pointer up.
61///
62/// The guard type itself lives in `luna_core::jit` so the trait
63/// signature in `IntChunkCompiler::enter` doesn't drag Cranelift into
64/// luna-core.
65///
66/// # v2.0 Track J sub-step J-D — capture-and-restore
67///
68/// Before J-D the body just overwrote the TLS slots and returned an
69/// inert guard (a historical `noop_jit_guard` helper, since removed);
70/// the "next `enter_jit` overwrites anyway" invariant made the
71/// elision harmless under single-thread, single-level dispatch.
72/// Cross-thread Vm move plus nested JIT entry (e.g. JIT'd op →
73/// metamethod → Lua-from-Rust → JIT entry again) makes the no-op-drop
74/// variant unsafe: the outer entry would resume holding the inner
75/// Vm's slot. J-D therefore delegates to a crate-private
76/// `scoped_rebind::scoped_jit_vm_rebind`, which snapshots the
77/// previous values into the guard and restores them on drop.
78///
79/// P11-S5d.J — the `cl` parameter is the closure being invoked. The
80/// guard also pins it in `JIT_CL` so `luna_jit_upval_get` can fetch
81/// `cl.upvals[idx]` at runtime. Callers that don't need upvalues (the
82/// zero-arg host-call path before `Op::GetUpval` was JIT'd) can pass
83/// `None`; helpers will hit the debug-assert if they fire.
84pub fn enter_jit(
85    vm: &mut luna_core::vm::Vm,
86    cl: Option<luna_core::runtime::Gc<luna_core::runtime::LuaClosure>>,
87) -> JitVmGuard {
88    scoped_rebind::scoped_jit_vm_rebind(vm, cl)
89}
90
91/// v2.0 Track J sub-step J-D — test-only inspector of the active
92/// `(JIT_VM, JIT_CL)` TLS pointers. Used by the J-D regression test
93/// (`tests/j_d_scoped_rebind_and_sleeve.rs`) to assert RAII install +
94/// restore semantics across nested [`enter_jit`] calls. Not part of
95/// the embedder API.
96#[doc(hidden)]
97pub fn __j_d_tls_ptrs() -> (
98    *mut luna_core::vm::Vm,
99    *const luna_core::runtime::LuaClosure,
100) {
101    let vm = JIT_VM.with(|c| c.get());
102    let cl = JIT_CL.with(|c| c.get());
103    (vm, cl)
104}
105
106/// P11-S5c — read the active Vm pointer. SAFETY: the caller (always
107/// a Rust helper invoked from inside JIT'd code) must be running
108/// under an active [`enter_jit`] guard.
109#[inline]
110unsafe fn current_jit_vm<'a>() -> &'a mut luna_core::vm::Vm {
111    let p = JIT_VM.with(|cell| cell.get());
112    debug_assert!(!p.is_null(), "JIT helper called outside enter_jit scope");
113    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
114    unsafe { &mut *p }
115}
116
117/// P11-S5d.J — read the active LuaClosure pointer. SAFETY: caller is
118/// a JIT helper running under an `enter_jit` guard whose closure
119/// argument was non-None.
120#[inline]
121unsafe fn current_jit_closure() -> luna_core::runtime::Gc<luna_core::runtime::LuaClosure> {
122    let p = JIT_CL.with(|cell| cell.get());
123    debug_assert!(
124        !p.is_null(),
125        "luna_jit_upval_get called outside an upval-aware enter_jit scope"
126    );
127    luna_core::runtime::Gc::from_ptr(p as *mut luna_core::runtime::LuaClosure)
128}
129
130/// P11-S5c — allocate an empty `Gc<Table>` on the active Vm's heap.
131/// Returns the Gc pointer pun'd to `i64`. The fresh table is rooted
132/// only through the Cranelift Variable the JIT writes it into; no
133/// `maybe_collect_garbage` runs inside the helper so the SSA-only
134/// rooting suffices for the duration of the JIT entry.
135// SAFETY: `no_mangle` is required for Cranelift's `Linkage::Import` to resolve this symbol from the JIT'd code; this crate is the sole producer of `luna_jit_*` symbols.
136#[unsafe(no_mangle)]
137pub unsafe extern "C" fn luna_jit_new_table() -> i64 {
138    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
139    let vm = unsafe { current_jit_vm() };
140    // P11-S5d.E' — a prior helper in this JIT entry parked a deopt
141    // request; short-circuit so we don't touch the heap unnecessarily.
142    // Returning a NULL ptr is safe because subsequent helpers also
143    // early-return on `jit_pending_err`, and the dispatcher will deopt
144    // to the interpreter as soon as the JIT entry returns.
145    if vm.jit.pending_err.is_some() {
146        return 0;
147    }
148    let g = vm.heap.new_table();
149    g.as_ptr() as i64
150}
151
152/// P11-S5c.B — `Heap::new_table_sized(n)` variant. JIT emit reaches
153/// for this when the `NewTable` window is immediately followed by a
154/// counted `for i = 1, N do … end` with a compile-time-known
155/// `N` — pre-allocating the array part skips ~13 intermediate
156/// `rehash` rounds for N=10000, which dominates the hot loop's
157/// wall-clock on `table_alloc_10k`. Negative or zero hints
158/// degrade to an empty table (matches `new_table`).
159// SAFETY: `no_mangle` is required for Cranelift's `Linkage::Import` to resolve this symbol from the JIT'd code; this crate is the sole producer of `luna_jit_*` symbols.
160#[unsafe(no_mangle)]
161pub unsafe extern "C" fn luna_jit_new_table_sized(asize: i64) -> i64 {
162    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
163    let vm = unsafe { current_jit_vm() };
164    if vm.jit.pending_err.is_some() {
165        return 0;
166    }
167    let n = if asize > 0 { asize as usize } else { 0 };
168    let g = vm.heap.new_table_sized(n);
169    g.as_ptr() as i64
170}
171
172/// P12-S5-C — materialize a Sinkable site's virtual array slots into
173/// a heap `Gc<Table>` at a side-exit emit point. The JIT emit lays
174/// out two parallel stack buffers per site per exit (`raws_ptr` of
175/// `cap` × u64 and `kinds_ptr` of `cap` × u8, one entry per virt
176/// slot) and calls this helper. The caller writes the returned
177/// `Value::Table` raw bits into the slot's `reg_state` cell + sets
178/// the per-exit-tags entry to `ExitTag::Table` so the dispatcher
179/// repacks correctly on deopt.
180///
181/// `kind` byte uses the same `luna_core::runtime::value::raw::*` tag
182/// space as `Value::pack`. Unset slots in `virt_kinds` map to
183/// `raw::NIL` at emit time so the table sees a NIL fill — matches
184/// Lua's "table created with array part, slot unwritten" semantics.
185// SAFETY: `no_mangle` is required for Cranelift's `Linkage::Import` to resolve this symbol from the JIT'd code; this crate is the sole producer of `luna_jit_*` symbols.
186#[unsafe(no_mangle)]
187// SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
188pub unsafe extern "C" fn luna_jit_materialize_sunk_table(
189    cap: i64,
190    raws_ptr: *const u64,
191    kinds_ptr: *const u8,
192    n_hash: i64,
193    hash_keys_ptr: *const u64,
194    hash_raws_ptr: *const u64,
195    hash_kinds_ptr: *const u8,
196) -> i64 {
197    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
198    let vm = unsafe { current_jit_vm() };
199    if vm.jit.pending_err.is_some() {
200        return 0;
201    }
202    let cap_u = if cap > 0 { cap as usize } else { 0 };
203    let n_hash_u = if n_hash > 0 { n_hash as usize } else { 0 };
204    let g = vm.heap.new_table_sized(cap_u);
205    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
206    let table = unsafe { g.as_mut() };
207    // Array slots.
208    if cap_u > 0 {
209        for i in 0..cap_u {
210            // SAFETY: the index is bounded by the buffer length passed as an argument by Cranelift-emitted code, which computes it from the IR's compile-time-known site shape (`n_array_slots` / `n_hash_pairs`).
211            let raw_bits = unsafe { *raws_ptr.add(i) };
212            // SAFETY: the index is bounded by the buffer length passed as an argument by Cranelift-emitted code, which computes it from the IR's compile-time-known site shape (`n_array_slots` / `n_hash_pairs`).
213            let kind = unsafe { *kinds_ptr.add(i) };
214            let raw = luna_core::runtime::value::RawVal { zero: raw_bits };
215            // SAFETY: `kind` was loaded from the IR-emitted `kinds` buffer in lockstep with the matching raw payload, so the tag byte agrees with the `RawVal` discriminator (see `runtime::value::raw`).
216            let v = unsafe { luna_core::runtime::Value::pack(kind, raw) };
217            let _ = table.set_int(&mut vm.heap, (i + 1) as i64, v);
218        }
219    }
220    // P12-S11-B-v2 — hash slots. Each entry is a
221    // (key_ptr: *const LuaStr, raw_bits, kind_byte) triple from
222    // the trace IR's stack-allocated buffers. The IR baked the
223    // const-string ptr at compile time from head_proto.consts.
224    if n_hash_u > 0 {
225        for i in 0..n_hash_u {
226            // SAFETY: the index is bounded by the buffer length passed as an argument by Cranelift-emitted code, which computes it from the IR's compile-time-known site shape (`n_array_slots` / `n_hash_pairs`).
227            let key_ptr_bits = unsafe { *hash_keys_ptr.add(i) };
228            // SAFETY: the index is bounded by the buffer length passed as an argument by Cranelift-emitted code, which computes it from the IR's compile-time-known site shape (`n_array_slots` / `n_hash_pairs`).
229            let raw_bits = unsafe { *hash_raws_ptr.add(i) };
230            // SAFETY: the index is bounded by the buffer length passed as an argument by Cranelift-emitted code, which computes it from the IR's compile-time-known site shape (`n_array_slots` / `n_hash_pairs`).
231            let kind = unsafe { *hash_kinds_ptr.add(i) };
232            let key_gc: luna_core::runtime::Gc<luna_core::runtime::LuaStr> =
233                luna_core::runtime::Gc::from_ptr(key_ptr_bits as *mut luna_core::runtime::LuaStr);
234            let raw = luna_core::runtime::value::RawVal { zero: raw_bits };
235            // SAFETY: `kind` was loaded from the IR-emitted `kinds` buffer in lockstep with the matching raw payload, so the tag byte agrees with the `RawVal` discriminator (see `runtime::value::raw`).
236            let v = unsafe { luna_core::runtime::Value::pack(kind, raw) };
237            let _ = table.set(&mut vm.heap, luna_core::runtime::Value::Str(key_gc), v);
238        }
239    }
240    g.as_ptr() as i64
241}
242
243/// P11-S5c — `t[key] = val` where `t` is a Table Gc (i64 pun), `key`
244/// is an Int and `val` is an Int. Wraps `Table::set_int(&mut Heap,
245/// i64, Value)`. Returns nothing (errors swallowed — luna's
246/// `set_int` only returns `Err` on table-size pathology that the
247/// interpreter would also surface; JIT'd workloads bounded by N=10k
248/// don't reach it). Future caller-visible error reporting would
249/// route through a deopt return path.
250// SAFETY: `no_mangle` is required for Cranelift's `Linkage::Import` to resolve this symbol from the JIT'd code; this crate is the sole producer of `luna_jit_*` symbols.
251#[unsafe(no_mangle)]
252pub unsafe extern "C" fn luna_jit_table_set_int(t: i64, key: i64, val: i64) {
253    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
254    let vm = unsafe { current_jit_vm() };
255    if vm.jit.pending_err.is_some() {
256        return;
257    }
258    let g: luna_core::runtime::Gc<luna_core::runtime::Table> =
259        luna_core::runtime::Gc::from_ptr(t as *mut luna_core::runtime::Table);
260    // P11-S5d.E' — a metatable on the target table means PUC would route
261    // this write through __newindex; the JIT helper would bypass it. Park
262    // a deopt request and let the dispatcher re-run the call through the
263    // interpreter so __newindex / raw-set semantics are honoured.
264    if g.metatable().is_some() {
265        vm.jit.pending_err = Some(vm.rt_err("JIT deopt: table has metatable"));
266        return;
267    }
268    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
269    let table = unsafe { g.as_mut() };
270    let _ = table.set_int(&mut vm.heap, key, luna_core::runtime::Value::Int(val));
271}
272
273/// P12-S7-C — write an arbitrary `Value::pack(tag, raw_bits)` to
274/// `t[key]` (Int key). Generalises `_table_set_int` / `_table_set_nil`:
275/// trace JIT emit dispatches Int/Nil to their specialized helpers
276/// (slightly less overhead) and Closure/Table/Float/etc. to this
277/// helper. Without it, a SetTable whose src is a Closure (post-S7
278/// Op::Closure trace JIT) silently wraps the closure pointer as
279/// `Value::Int(ptr_bits)` — a number that later calls fail with
280/// "attempt to call a number value".
281// SAFETY: `no_mangle` is required for Cranelift's `Linkage::Import` to resolve this symbol from the JIT'd code; this crate is the sole producer of `luna_jit_*` symbols.
282#[unsafe(no_mangle)]
283// SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
284pub unsafe extern "C" fn luna_jit_table_set_raw(t: i64, key: i64, raw_bits: i64, tag: i64) {
285    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
286    let vm = unsafe { current_jit_vm() };
287    if vm.jit.pending_err.is_some() {
288        return;
289    }
290    let g: luna_core::runtime::Gc<luna_core::runtime::Table> =
291        luna_core::runtime::Gc::from_ptr(t as *mut luna_core::runtime::Table);
292    if g.metatable().is_some() {
293        vm.jit.pending_err = Some(vm.rt_err("JIT deopt: table has metatable"));
294        return;
295    }
296    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
297    let table = unsafe { g.as_mut() };
298    // SAFETY: `kind` was loaded from the IR-emitted `kinds` buffer in lockstep with the matching raw payload, so the tag byte agrees with the `RawVal` discriminator (see `runtime::value::raw`).
299    let v = unsafe {
300        luna_core::runtime::Value::pack(
301            tag as u8,
302            luna_core::runtime::value::RawVal {
303                zero: raw_bits as u64,
304            },
305        )
306    };
307    let _ = table.set_int(&mut vm.heap, key, v);
308}
309
310/// P12-S11-A — write `Value::pack(tag, raw)` to `t[key_ptr_as_str]`.
311/// String key is a `Gc<LuaStr>` raw pointer (baked into IR at
312/// emit time from `head_proto.consts[ins.b()]`); value goes
313/// through the standard tag/raw round-trip. Used for Op::SetField
314/// trace JIT support (helper path; sunk emit is S11-B).
315///
316/// Same metatable / pending_err short-circuit as the other table
317/// helpers — `__newindex` cases deopt to interp.
318// SAFETY: `no_mangle` is required for Cranelift's `Linkage::Import` to resolve this symbol from the JIT'd code; this crate is the sole producer of `luna_jit_*` symbols.
319#[unsafe(no_mangle)]
320// SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
321pub unsafe extern "C" fn luna_jit_table_set_field(
322    t: i64,
323    key_ptr: i64,
324    val_raw: i64,
325    val_tag: i64,
326) {
327    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
328    let vm = unsafe { current_jit_vm() };
329    if vm.jit.pending_err.is_some() {
330        return;
331    }
332    let g: luna_core::runtime::Gc<luna_core::runtime::Table> =
333        luna_core::runtime::Gc::from_ptr(t as *mut luna_core::runtime::Table);
334    if g.metatable().is_some() {
335        vm.jit.pending_err = Some(vm.rt_err("JIT deopt: table has metatable"));
336        return;
337    }
338    let key_gc: luna_core::runtime::Gc<luna_core::runtime::LuaStr> =
339        luna_core::runtime::Gc::from_ptr(key_ptr as *mut luna_core::runtime::LuaStr);
340    let key = luna_core::runtime::Value::Str(key_gc);
341    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
342    let table = unsafe { g.as_mut() };
343    // SAFETY: `kind` was loaded from the IR-emitted `kinds` buffer in lockstep with the matching raw payload, so the tag byte agrees with the `RawVal` discriminator (see `runtime::value::raw`).
344    let v = unsafe {
345        luna_core::runtime::Value::pack(
346            val_tag as u8,
347            luna_core::runtime::value::RawVal {
348                zero: val_raw as u64,
349            },
350        )
351    };
352    let _ = table.set(&mut vm.heap, key, v);
353}
354
355/// P12-S11-A — read `t[key_ptr_as_str]` and return raw payload bits.
356/// String key is a `Gc<LuaStr>` raw pointer baked into IR. Caller
357/// (trace JIT GetField emit) infers exit_tag for the dst slot via
358/// `infer_getx_exit`; absent inference, dispatchable=false.
359// SAFETY: `no_mangle` is required for Cranelift's `Linkage::Import` to resolve this symbol from the JIT'd code; this crate is the sole producer of `luna_jit_*` symbols.
360#[unsafe(no_mangle)]
361// SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
362pub unsafe extern "C" fn luna_jit_table_get_field(t: i64, key_ptr: i64) -> i64 {
363    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
364    let vm = unsafe { current_jit_vm() };
365    if vm.jit.pending_err.is_some() {
366        return 0;
367    }
368    let g: luna_core::runtime::Gc<luna_core::runtime::Table> =
369        luna_core::runtime::Gc::from_ptr(t as *mut luna_core::runtime::Table);
370    if g.metatable().is_some() {
371        vm.jit.pending_err = Some(vm.rt_err("JIT deopt: table has metatable"));
372        return 0;
373    }
374    let key_gc: luna_core::runtime::Gc<luna_core::runtime::LuaStr> =
375        luna_core::runtime::Gc::from_ptr(key_ptr as *mut luna_core::runtime::LuaStr);
376    let v = g.get(luna_core::runtime::Value::Str(key_gc));
377    let (_tag, raw) = v.unpack();
378    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
379    unsafe { raw.zero as i64 }
380}
381
382/// v1.2 D3 Path B — read `upvals[upval_idx][key_str]` and return raw
383/// payload bits. Mirrors `luna_jit_table_get_field` but resolves the
384/// table via the trace head closure's upvalue list first (the trace
385/// dispatcher's `enter_jit(vm, Some(cl))` pins `JIT_CL`).
386///
387/// Used by the trace JIT lowerer's `Op::GetTabUp` arm for upvalue-
388/// table accesses outside the recognised math-fold pattern. The
389/// canonical case is `math.min(a, b)` whose 2-arg shape doesn't
390/// match `try_match_trace_math_fold`'s single-arg libm catalog;
391/// without this helper the entire trace bails at the `cmp-dirs`
392/// pre-emit pass and the workload runs interp-only (P3a diag finding
393/// 2026-06-24: `bail:cmp-dirs-GetTabUp` × 200/200 on `token_bucket_1k`).
394///
395/// Deopt cases: upval isn't a Table (corrupted upval list) or has
396/// a metatable (`__index` could shadow the lookup — interp-only).
397// SAFETY: `no_mangle` is required for Cranelift's `Linkage::Import` to resolve this symbol from the JIT'd code; this crate is the sole producer of `luna_jit_*` symbols.
398#[unsafe(no_mangle)]
399// SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
400pub unsafe extern "C" fn luna_jit_op_get_tab_up(upval_idx: i64, key_ptr: i64) -> i64 {
401    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard.
402    let vm = unsafe { current_jit_vm() };
403    if vm.jit.pending_err.is_some() {
404        return 0;
405    }
406    // SAFETY: called only from Cranelift-emitted JIT code; `enter_jit(vm, Some(cl))` pinned JIT_CL for the dispatch window.
407    let cl = unsafe { current_jit_closure() };
408    let env = vm.upval_get(cl, upval_idx as u32);
409    let g: luna_core::runtime::Gc<luna_core::runtime::Table> = match env {
410        luna_core::runtime::Value::Table(t) => t,
411        _ => {
412            vm.jit.pending_err = Some(vm.rt_err("JIT deopt: GetTabUp upval not Table"));
413            return 0;
414        }
415    };
416    if g.metatable().is_some() {
417        vm.jit.pending_err = Some(vm.rt_err("JIT deopt: GetTabUp env has metatable"));
418        return 0;
419    }
420    let key_gc: luna_core::runtime::Gc<luna_core::runtime::LuaStr> =
421        luna_core::runtime::Gc::from_ptr(key_ptr as *mut luna_core::runtime::LuaStr);
422    let v = g.get(luna_core::runtime::Value::Str(key_gc));
423    let (_tag, raw) = v.unpack();
424    // SAFETY: pulled from `RawVal` of a freshly unpacked Value above.
425    unsafe { raw.zero as i64 }
426}
427
428/// P12-S6-A2 — write `Value::Nil` to `t[key]` (Int key). Used by
429/// trace JIT when a SetList/SetI/SetTable's source register is a
430/// `RegKind::Nil` (e.g. Lua's `local t = {nil, nil}` table
431/// constructor expands to `NewTable; LoadNil×N; SetList` and
432/// without a Nil-specific helper the existing `_table_set_int`
433/// would silently coerce the Nil to `Value::Int(0)`).
434///
435/// Same metatable / `jit_pending_err` short-circuit as the other
436/// `_table_set_*` helpers — caller deopts on `pending_err` and
437/// the interpreter re-runs the op to honour `__newindex`.
438// SAFETY: `no_mangle` is required for Cranelift's `Linkage::Import` to resolve this symbol from the JIT'd code; this crate is the sole producer of `luna_jit_*` symbols.
439#[unsafe(no_mangle)]
440pub unsafe extern "C" fn luna_jit_table_set_nil(t: i64, key: i64) {
441    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
442    let vm = unsafe { current_jit_vm() };
443    if vm.jit.pending_err.is_some() {
444        return;
445    }
446    let g: luna_core::runtime::Gc<luna_core::runtime::Table> =
447        luna_core::runtime::Gc::from_ptr(t as *mut luna_core::runtime::Table);
448    if g.metatable().is_some() {
449        vm.jit.pending_err = Some(vm.rt_err("JIT deopt: table has metatable"));
450        return;
451    }
452    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
453    let table = unsafe { g.as_mut() };
454    let _ = table.set_int(&mut vm.heap, key, luna_core::runtime::Value::Nil);
455}
456
457/// P11-S5c — Float-key, Float-value variant. luna 5.1 / 5.2 lower
458/// `for i = 1, N do t[i] = i end` with a Float loop var (no Int
459/// subtype in those dialects), so the SetTable's key and value
460/// arguments arrive as f64 bit-patterns. `Table::set` normalizes
461/// integral Float keys back to Int slots so `#t` still reports the
462/// array length we'd expect — same shape PUC produces.
463// SAFETY: `no_mangle` is required for Cranelift's `Linkage::Import` to resolve this symbol from the JIT'd code; this crate is the sole producer of `luna_jit_*` symbols.
464#[unsafe(no_mangle)]
465pub unsafe extern "C" fn luna_jit_table_set_float_float(t: i64, key_bits: i64, val_bits: i64) {
466    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
467    let vm = unsafe { current_jit_vm() };
468    if vm.jit.pending_err.is_some() {
469        return;
470    }
471    let g: luna_core::runtime::Gc<luna_core::runtime::Table> =
472        luna_core::runtime::Gc::from_ptr(t as *mut luna_core::runtime::Table);
473    if g.metatable().is_some() {
474        vm.jit.pending_err = Some(vm.rt_err("JIT deopt: table has metatable"));
475        return;
476    }
477    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
478    let table = unsafe { g.as_mut() };
479    let k = luna_core::runtime::Value::Float(f64::from_bits(key_bits as u64));
480    let v = luna_core::runtime::Value::Float(f64::from_bits(val_bits as u64));
481    let _ = table.set(&mut vm.heap, k, v);
482}
483
484/// P11-S5c — `t[key]` where the JIT statically expects an Int
485/// result. Pulls the raw `Value` from the table and unpacks
486/// the Int payload. If the slot is anything but Int (Nil, Float,
487/// Str, …) the helper returns 0 — the JIT scan only admits
488/// chunks that store Ints, so the divergence is observable only
489/// when the user-facing semantics violate the static expectation.
490// SAFETY: `no_mangle` is required for Cranelift's `Linkage::Import` to resolve this symbol from the JIT'd code; this crate is the sole producer of `luna_jit_*` symbols.
491#[unsafe(no_mangle)]
492pub unsafe extern "C" fn luna_jit_table_get_int(t: i64, key: i64) -> i64 {
493    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
494    let vm = unsafe { current_jit_vm() };
495    if vm.jit.pending_err.is_some() {
496        return 0;
497    }
498    let g: luna_core::runtime::Gc<luna_core::runtime::Table> =
499        luna_core::runtime::Gc::from_ptr(t as *mut luna_core::runtime::Table);
500    // P11-S5d.E' — metatable on the source table means PUC would route
501    // a missing entry through __index; the helper bypasses that. Park a
502    // deopt request and bail; the dispatcher re-runs the call through
503    // the interpreter, which walks __index correctly (including the
504    // infinite-loop error events.lua relies on).
505    if g.metatable().is_some() {
506        vm.jit.pending_err = Some(vm.rt_err("JIT deopt: table has metatable"));
507        return 0;
508    }
509    // P11-S5d.B — return the raw 8-byte payload of the stored
510    // Value, regardless of tag. The JIT-emitted caller interprets
511    // the bit pattern according to the GetI result's RegKind:
512    // Int → i64, Float → f64::from_bits, Table → Gc<Table>::from_ptr.
513    // A previous variant unconditionally returned 0 on non-Int /
514    // non-Float — that fed NULL into subsequent helpers when the
515    // table actually stored Gc objects (binary_trees' `check`
516    // chain calling itself on `t[1]`).
517    let v = g.get_int(key);
518    let (_tag, raw) = v.unpack();
519    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
520    unsafe { raw.zero as i64 }
521}
522
523/// P11-S5d.E' — `t[k]` where `k` is a Float key. luna 5.1 / 5.2's
524/// `OP_GETTABLE` typically loads the key via `LoadF` (no Int subtype
525/// in those dialects); the emit hands `k` as `f64::to_bits` so the
526/// helper can reconstruct the Float value before calling `Table::get`.
527/// `Table::get` normalises integral Floats back to the Int slot, so
528/// `t[1.0]` lands on `t[1]` exactly like PUC does. Returns the raw
529/// 8-byte payload (same convention as `luna_jit_table_get_int`).
530// SAFETY: `no_mangle` is required for Cranelift's `Linkage::Import` to resolve this symbol from the JIT'd code; this crate is the sole producer of `luna_jit_*` symbols.
531#[unsafe(no_mangle)]
532pub unsafe extern "C" fn luna_jit_table_get_float(t: i64, key_bits: i64) -> i64 {
533    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
534    let vm = unsafe { current_jit_vm() };
535    if vm.jit.pending_err.is_some() {
536        return 0;
537    }
538    let g: luna_core::runtime::Gc<luna_core::runtime::Table> =
539        luna_core::runtime::Gc::from_ptr(t as *mut luna_core::runtime::Table);
540    if g.metatable().is_some() {
541        vm.jit.pending_err = Some(vm.rt_err("JIT deopt: table has metatable"));
542        return 0;
543    }
544    let k = luna_core::runtime::Value::Float(f64::from_bits(key_bits as u64));
545    let v = g.get(k);
546    let (_tag, raw) = v.unpack();
547    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
548    unsafe { raw.zero as i64 }
549}
550
551/// P11-S5d.J — `R[A] = upvals[idx]` value-read variant. Reads the
552/// active closure's upvalue cell, dispatching open/closed via the
553/// interpreter's `Vm::upval_get` (so an open upvalue resolves to its
554/// current stack slot — matters when a closure is called from inside
555/// an enclosing function whose upvalues are still open). Returns the
556/// raw 8-byte payload (same convention as the table helpers): the
557/// JIT-emitted caller bitcasts to F64 if the slot's declared kind is
558/// Float, leaves as I64 otherwise.
559///
560/// Scope: only invoked for `Op::GetUpval` PCs the scan classified as
561/// `ValueRead` (not the self-recursion call-target marker). The
562/// dispatcher pins `JIT_CL` at entry; helper safety relies on that.
563#[unsafe(no_mangle)]
564pub unsafe extern "C" fn luna_jit_upval_get(idx: i64) -> i64 {
565    let vm = unsafe { current_jit_vm() };
566    if vm.jit.pending_err.is_some() {
567        return 0;
568    }
569    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
570    let cl = unsafe { current_jit_closure() };
571    let v = vm.upval_get(cl, idx as u32);
572    let (_tag, raw) = v.unpack();
573    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
574    unsafe { raw.zero as i64 }
575}
576
577/// P12-S7-C — trace JIT helper for `Op::Close A`. Wraps
578/// `Vm::jit_op_close` which does the predict-and-deopt logic:
579/// returns 0 to continue the trace, 1 to deopt (handler would run
580/// or pre-existing pending_err).
581// SAFETY: `no_mangle` is required for Cranelift's `Linkage::Import` to resolve this symbol from the JIT'd code; this crate is the sole producer of `luna_jit_*` symbols.
582#[unsafe(no_mangle)]
583pub unsafe extern "C" fn luna_jit_op_close(start_offset: i64) -> i64 {
584    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
585    let vm = unsafe { current_jit_vm() };
586    vm.jit_op_close(start_offset as u32)
587}
588
589/// P12-S12-C v1 — update only the raw payload of
590/// `vm.stack[base + slot_offset]`, preserving its existing tag.
591/// Used by `Op::Concat` body emit to spill trace-IR Variables
592/// back to vm.stack for operands whose `current_kinds` is
593/// `Unset` (e.g. Str slots that round-trip as pointer raw bits
594/// but have no `RegKind::Str` variant). The interp's previous
595/// execution of the same op already wrote the right `tag` to
596/// that slot — the trace just needs to refresh the raw bits.
597// SAFETY: `no_mangle` is required for Cranelift's `Linkage::Import` to resolve this symbol from the JIT'd code; this crate is the sole producer of `luna_jit_*` symbols.
598#[unsafe(no_mangle)]
599// SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
600pub unsafe extern "C" fn luna_jit_stack_update_raw(slot_offset: i64, raw_bits: i64) {
601    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
602    let vm = unsafe { current_jit_vm() };
603    if vm.jit.pending_err.is_some() {
604        return;
605    }
606    vm.jit_stack_update_raw(slot_offset as u32, raw_bits as u64);
607}
608
609/// P12-S12-C v1 — trace JIT helper for `Op::Concat A B`.
610///
611/// Wraps `Vm::jit_op_concat` which mirrors the interp arm: sets
612/// `self.top = base + a + n`, then runs `concat_run(base + a)`.
613/// Detects metamethod-path (which would push a Lua frame mid-trace)
614/// via pre/post `frames.len()` comparison and deopts cleanly via
615/// `pending_err` + frame unwind.
616///
617/// Returns `0` on success (result lives at `vm.stack[base + a]`),
618/// `-1` on deopt (pending_err set; metamethod path, type error,
619/// length overflow, or pre-existing pending_err).
620// SAFETY: `no_mangle` is required for Cranelift's `Linkage::Import` to resolve this symbol from the JIT'd code; this crate is the sole producer of `luna_jit_*` symbols.
621#[unsafe(no_mangle)]
622// SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
623pub unsafe extern "C" fn luna_jit_op_concat(slot_offset: i64, n: i64) -> i64 {
624    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
625    let vm = unsafe { current_jit_vm() };
626    vm.jit_op_concat(slot_offset as u32, n as i32)
627}
628
629/// P14-S14-B v2 — trace JIT helper:acquire a fresh accumulator
630/// buffer from the Vm's pool. Returns a `*mut Vec<u8>` boxed-leaked
631/// pointer that the trace fn keeps in a stack slot through the loop.
632///
633/// Safety: caller must be inside `enter_jit` and must eventually call
634/// `luna_jit_str_buf_release` with the returned pointer.
635#[unsafe(no_mangle)]
636pub unsafe extern "C" fn luna_jit_str_buf_acquire() -> i64 {
637    let vm = unsafe { current_jit_vm() };
638    vm.jit_str_buf_acquire() as i64
639}
640
641/// P14-S14-B v2 — trace JIT helper:release a buffer back to the
642/// Vm's pool.
643///
644/// Safety: `buf` must have been returned by a prior
645/// `luna_jit_str_buf_acquire` on the same Vm.
646#[unsafe(no_mangle)]
647pub unsafe extern "C" fn luna_jit_str_buf_release(buf: i64) {
648    let vm = unsafe { current_jit_vm() };
649    vm.jit_str_buf_release(buf as *mut Vec<u8>);
650}
651
652/// P14-S14-B v2 — trace JIT helper:append a LuaStr's bytes to a
653/// previously-acquired accumulator buffer. The trace IR calls this
654/// at each loop iter inside the `s = s .. v` idiom.
655///
656/// Returns 0 on success, -1 if `str_ptr` isn't a valid LuaStr (deopt
657/// to interp, which will hit the __concat metamethod path).
658///
659/// Safety: `buf` from prior `acquire`; `str_ptr` from the piece slot.
660#[unsafe(no_mangle)]
661pub unsafe extern "C" fn luna_jit_str_buf_extend(buf: i64, str_ptr: i64) -> i64 {
662    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
663    let vm = unsafe { current_jit_vm() };
664    vm.jit_str_buf_extend(buf as *mut Vec<u8>, str_ptr)
665}
666
667/// P14-S14-B v2 — trace JIT helper:drain the accumulator buffer
668/// into a fresh `LuaStr` via `heap.intern`, returning the raw ptr
669/// bits for the trace to write into the accumulator slot.
670///
671/// Returns the LuaStr ptr as i64 on success, 0 on overflow (the v2
672/// hard cap = 256KB; trace deopts).
673///
674/// Safety: `buf` from prior `acquire`. The buffer is drained and
675/// ready for `release`.
676#[unsafe(no_mangle)]
677pub unsafe extern "C" fn luna_jit_str_buf_intern(buf: i64) -> i64 {
678    let vm = unsafe { current_jit_vm() };
679    vm.jit_str_buf_intern(buf as *mut Vec<u8>)
680}
681
682/// P12-S12-B-v2 — trace JIT helper for `Op::TForCall A 0 C`.
683///
684/// Mirrors `exec.rs:5316` Op::TForCall semantics:
685/// - copies `R[A..=A+2]` (iter / state / control) to `R[A+4..=A+6]`,
686///   resizing `vm.stack` if needed
687/// - calls `vm.begin_call(abs+4, Some(2), nvars, false)` to dispatch
688///   the iterator function
689///
690/// v2 restriction: the iterator at `R[A]` must be `Value::Native`. A
691/// Lua-closure iter would push a Lua frame mid-trace, breaking the
692/// trace head's `recording_frame_base` invariant; we deopt instead
693/// (sets `jit_pending_err`, returns sentinel). The expected v3
694/// follow-up inlines `inext` directly so the helper path is gone.
695///
696/// Returns `0` on success, `-1` on deopt (pending_err set OR
697/// pre-existing pending_err).
698///
699/// Safety: caller (trace JIT IR) runs under `enter_jit` so
700/// `current_jit_vm()` is live.
701#[unsafe(no_mangle)]
702pub unsafe extern "C" fn luna_jit_op_tforcall(
703    abs_offset: i64,
704    nvars: i64,
705    ctrl_out: *mut i64,
706    key_out: *mut i64,
707    val_out: *mut i64,
708) -> i64 {
709    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
710    let vm = unsafe { current_jit_vm() };
711    vm.jit_op_tforcall(abs_offset as u32, nvars as i32, ctrl_out, key_out, val_out)
712}
713
714/// P12-S12-B-v2 — load the raw `i64` payload of `vm.stack[base + slot_offset]`
715/// for the active trace's head frame. Used to reload trace IR
716/// `Variable`s after a helper (e.g. `luna_jit_op_tforcall`) has
717/// mutated `vm.stack` directly.
718///
719/// Safety: caller (trace JIT IR) runs under `enter_jit` so
720/// `current_jit_vm()` is live. Returns `0` if the slot is out of
721/// stack range (defensive — emit-time bounds check should make this
722/// unreachable).
723#[unsafe(no_mangle)]
724pub unsafe extern "C" fn luna_jit_stack_load(slot_offset: i64) -> i64 {
725    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
726    let vm = unsafe { current_jit_vm() };
727    vm.jit_stack_load(slot_offset as u32)
728}
729
730/// P12-S12-B-v2 — read the tag byte of `vm.stack[base + slot_offset]`
731/// for the active trace's head frame. Used by `Op::TForLoop` emit
732/// to dispatch on the iterator's return-key tag (Nil → loop end,
733/// Int → continue for ipairs, other → deopt for v2).
734///
735/// Safety: caller (trace JIT IR) runs under `enter_jit`. Returns
736/// `raw::NIL` (0) if slot out of range.
737#[unsafe(no_mangle)]
738pub unsafe extern "C" fn luna_jit_stack_tag(slot_offset: i64) -> i64 {
739    let vm = unsafe { current_jit_vm() };
740    vm.jit_stack_tag(slot_offset as u32) as i64
741}
742
743/// P12-S7-B — spill a trace's per-register live value into the
744/// caller frame's `vm.stack[base + slot_offset]`. Always called
745/// just before `luna_jit_op_closure` for each `in_stack: true`
746/// upval in the inner proto, so the open upval the helper creates
747/// points to a slot holding the right value.
748///
749/// Parameters: `slot_offset` is the caller-frame register index
750/// (`u32`, depth=0 only — S7-B doesn't support depth>0 Closure).
751/// `tag` is the `raw::*` byte for the register's RegKind at this
752/// emit point (Int / Float / Table / Closure / Nil). `raw_bits` is
753/// the trace IR's i64 payload for the register (Float held as
754/// `f64::to_bits`, Table/Closure as raw `Gc::as_ptr` cast).
755///
756/// Safety: caller (trace JIT IR) runs under `enter_jit` so
757/// `current_jit_vm()` is live; the (tag, raw_bits) pair is
758/// generated by the same emit path that proves the kind, so
759/// `Value::pack` round-trips correctly.
760#[unsafe(no_mangle)]
761pub unsafe extern "C" fn luna_jit_spill_to_stack(slot_offset: i64, tag: i64, raw_bits: i64) {
762    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
763    let vm = unsafe { current_jit_vm() };
764    if vm.jit.pending_err.is_some() {
765        return;
766    }
767    vm.jit_spill_stack(slot_offset as u32, tag as u8, raw_bits as u64);
768}
769
770/// P12-S7-A — trace JIT helper for `Op::Closure A Bx`.
771///
772/// Looks up `cl.proto.protos[bx]` (the inner Proto) and builds a
773/// new `Gc<LuaClosure>` for it. Each upval is captured either from
774/// the trace head closure's `upvals()` slice (`in_stack=false`)
775/// or from the caller frame's stack via `find_or_create_upval`
776/// (`in_stack=true`, P12-S7-B). v51 dialect clones the `_ENV` cell
777/// to match interp semantics (per-closure `_ENV`). v52+ honours
778/// the Proto cache.
779///
780/// **Pre-condition for in_stack upvals**: the trace IR has already
781/// emitted `luna_jit_spill_to_stack(d.index, tag, raw)` for every
782/// `d.in_stack == true` upval BEFORE this call, so the underlying
783/// `vm.stack[base + d.index]` holds the trace's current value at
784/// helper time. Without that spill the open upval would point at
785/// a stale entry-tag value.
786///
787/// Returns the raw `Gc<LuaClosure>` ptr as i64 (Value::Closure's
788/// payload). On error (`pending_err` already set) returns 0
789/// sentinel so the dispatcher deopts.
790///
791/// Safety: caller runs under `enter_jit(vm, Some(cl))` guard so
792/// `current_jit_vm()` / `current_jit_closure()` return live
793/// references. `proto_idx` is in-bounds by the emit pre-check.
794#[unsafe(no_mangle)]
795pub unsafe extern "C" fn luna_jit_op_closure(proto_idx: i64) -> i64 {
796    use luna_core::runtime::function::{INLINE_UPVALS_N, UpvalState, Upvalue};
797    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
798    let vm = unsafe { current_jit_vm() };
799    if vm.jit.pending_err.is_some() {
800        return 0;
801    }
802    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
803    let cl = unsafe { current_jit_closure() };
804    let inner = cl.proto.protos[proto_idx as usize];
805    let n_ups = inner.upvals.len();
806    // Determine the caller frame's base for in_stack captures. The
807    // helper runs MID-trace, before any frame writeback — the trace
808    // head's frame is the topmost Lua frame here (S7-B restricts
809    // Op::Closure emit to inline_depth=0 only, so no deeper frame
810    // exists).
811    let base = match vm.jit_last_lua_frame() {
812        Some(f) => f.base,
813        None => {
814            vm.jit.pending_err = Some(vm.rt_err("JIT op_closure: no Lua frame"));
815            return 0;
816        }
817    };
818    // Build the upval slice — small (0..2 typical) so use a stack
819    // array up to INLINE_UPVALS_N like the interp does, else heap.
820    let mut stack_buf: [std::mem::MaybeUninit<luna_core::runtime::Gc<Upvalue>>; INLINE_UPVALS_N] =
821        [std::mem::MaybeUninit::uninit(); INLINE_UPVALS_N];
822    let mut heap_buf: Vec<luna_core::runtime::Gc<Upvalue>> = Vec::new();
823    let use_inline = n_ups <= INLINE_UPVALS_N;
824    if !use_inline {
825        heap_buf.reserve_exact(n_ups);
826    }
827    for (i, d) in inner.upvals.iter().enumerate() {
828        let uv = if d.in_stack {
829            // P12-S7-B — `find_or_create_upval` points the open
830            // upval at vm.stack[base + d.index]. The trace IR
831            // emitted a spill before this call, so the slot holds
832            // the right value at capture time.
833            vm.find_or_create_upval(base + d.index as u32)
834        } else {
835            cl.upvals()[d.index as usize]
836        };
837        if use_inline {
838            stack_buf[i] = std::mem::MaybeUninit::new(uv);
839        } else {
840            heap_buf.push(uv);
841        }
842    }
843    let ups: &mut [luna_core::runtime::Gc<Upvalue>] = if use_inline {
844        // SAFETY: first n_ups slots of stack_buf were initialised
845        // by the loop above; we expose exactly that range.
846        unsafe {
847            std::slice::from_raw_parts_mut(
848                stack_buf.as_mut_ptr() as *mut luna_core::runtime::Gc<Upvalue>,
849                n_ups,
850            )
851        }
852    } else {
853        &mut heap_buf[..]
854    };
855    // v51 per-closure `_ENV` clone — matches interp Op::Closure.
856    let v51 = vm.version() <= luna_core::version::LuaVersion::Lua51;
857    if v51 && inner.env_upval_idx != u8::MAX {
858        let i = inner.env_upval_idx as usize;
859        let cur = match ups[i].state() {
860            UpvalState::Open { slot, thread } => vm.read_slot(slot, thread),
861            UpvalState::Closed(v) => v,
862        };
863        ups[i] = vm.heap.new_upvalue(UpvalState::Closed(cur));
864    }
865    let ups_slice: &[luna_core::runtime::Gc<Upvalue>] = ups;
866    let nc = if v51 {
867        vm.heap.new_closure_inline(inner, ups_slice)
868    } else {
869        // PUC 5.2+ getcached: reuse the last LuaClosure built for
870        // this Proto if every upval slot points to the same
871        // Upvalue object (typical for `function() return outer end`
872        // captured inside a hot loop).
873        let cached = inner.cache.get().filter(|c| {
874            c.upvals().len() == ups_slice.len()
875                && c.upvals()
876                    .iter()
877                    .zip(ups_slice.iter())
878                    .all(|(a, b)| std::ptr::eq(a.as_ptr(), b.as_ptr()))
879        });
880        match cached {
881            Some(c) => c,
882            None => {
883                let n = vm.heap.new_closure_inline(inner, ups_slice);
884                inner.cache.set(Some(n));
885                n
886            }
887        }
888    };
889    let (_tag, raw) = luna_core::runtime::Value::Closure(nc).unpack();
890    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
891    unsafe { raw.zero as i64 }
892}
893
894/// v2.0 Phase 5 Track AO sub-track AO-PF — runtime fire counter for
895/// the Stage 7 polish 6 inline-chain reloc path. Every call to
896/// [`luna_jit_trace_materialize_frames`] from trace mcode (JIT-baked
897/// OR AOT polish-6 slot-loaded) increments this counter. In an AOT-
898/// only run (no in-process JIT compilation of traces that carry
899/// inline cmp@d>0 side-exits) any non-zero value is direct evidence
900/// that the polish-6 chain reloc path actually fires at runtime — the
901/// resolver-side probe (`aot_inline_chains_resolved`) only confirms
902/// the slot was populated, not that any AOT mcode dispatch ever
903/// loaded it. See `.dev/rfcs/v2.0-ao-pf-verdict.md`.
904pub static TRACE_MATERIALIZE_FRAMES_FIRES: std::sync::atomic::AtomicU64 =
905    std::sync::atomic::AtomicU64::new(0);
906
907/// Reader for [`TRACE_MATERIALIZE_FRAMES_FIRES`]. Relaxed load is fine
908/// — the counter is diagnostic, not a synchronisation point.
909pub fn trace_materialize_frames_fires() -> u64 {
910    TRACE_MATERIALIZE_FRAMES_FIRES.load(std::sync::atomic::Ordering::Relaxed)
911}
912
913/// P12-S4-step4b — frame materialization helper.
914///
915/// step4b-B body: walks `metas[0..n]` and pushes one
916/// `CallFrame::Lua` per entry onto `vm.frames` so the interp can
917/// resume at a depth>0 continuation PC after the trace side-exits.
918/// Returns `0` on success, non-zero to force the dispatcher into
919/// the deopt path. The lowerer (step4b-C) will emit the call site
920/// from cmp@d>0 side-exit blocks.
921///
922/// Invariants the caller (lowerer) enforces at compile time:
923/// - All inlined frames are the same `LuaClosure` (self-recursion
924///   only), so `current_jit_closure()` matches every frame's
925///   closure pointer.
926/// - The chain is non-vararg (`!cl.proto.is_vararg`) — helper does
927///   NOT reconstruct the vararg rotation that `push_frame` does.
928/// - Every inlined `Op::Call` has `C == 2` (one return value);
929///   `m.nresults` is therefore always 1. The helper writes whatever
930///   the metadata says, no validation.
931///
932/// Safety:
933/// - Caller runs under an `enter_jit(vm, Some(cl))` guard so
934///   `current_jit_vm()` / `current_jit_closure()` return live
935///   references.
936/// - `metas` points to a valid array of length `n` of
937///   `FrameMaterializeInfo`, alive for the duration of the call —
938///   today it's a pointer into the owning `CompiledTrace.frame_metas`
939///   `Box`, which lives at least as long as the trace's mmap.
940// SAFETY: `no_mangle` is required for Cranelift's `Linkage::Import` to resolve this symbol from the JIT'd code; this crate is the sole producer of `luna_jit_*` symbols.
941#[unsafe(no_mangle)]
942// SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
943pub unsafe extern "C" fn luna_jit_trace_materialize_frames(
944    n: u64,
945    metas: *const luna_core::jit::trace::FrameMaterializeInfo,
946) -> i64 {
947    // AO-PF — count every entry to this helper from trace mcode.
948    // Relaxed ordering: the counter is purely diagnostic; the read
949    // side runs after process work has quiesced.
950    TRACE_MATERIALIZE_FRAMES_FIRES.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
951    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
952    let vm = unsafe { current_jit_vm() };
953    // Honour the existing deopt protocol: if any earlier helper in
954    // this JIT entry parked a deopt, don't push frames — the
955    // dispatcher will unwind via the deopt path.
956    if vm.jit.pending_err.is_some() {
957        return -1;
958    }
959    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
960    let cl = unsafe { current_jit_closure() };
961    let head_frame = match vm.jit_last_lua_frame() {
962        Some(f) => f,
963        // No live Lua frame at trace head — shouldn't happen under
964        // any current dispatcher path, but treat as deopt rather
965        // than panic from the JIT.
966        None => return -1,
967    };
968    let max_stack = cl.proto.max_stack as u32;
969    for i in 0..n as usize {
970        // SAFETY: caller-supplied `metas` points to a valid array of
971        // length `n` per the contract above.
972        let m = unsafe { *metas.add(i) };
973        let new_base = head_frame.base + m.base_offset;
974        vm.jit_ensure_stack((new_base + max_stack) as usize);
975        vm.jit_push_inlined_frame(cl, new_base, m.pc, m.nresults);
976    }
977    0
978}
979
980/// P11-S5c — `#t` (table length).
981// SAFETY: `no_mangle` is required for Cranelift's `Linkage::Import` to resolve this symbol from the JIT'd code; this crate is the sole producer of `luna_jit_*` symbols.
982#[unsafe(no_mangle)]
983pub unsafe extern "C" fn luna_jit_table_len(t: i64) -> i64 {
984    // SAFETY: called only from Cranelift-emitted JIT code under an active JitVmGuard; the guard guarantees JIT_VM TLS holds a live &mut Vm for the dispatch window.
985    let vm = unsafe { current_jit_vm() };
986    if vm.jit.pending_err.is_some() {
987        return 0;
988    }
989    let g: luna_core::runtime::Gc<luna_core::runtime::Table> =
990        luna_core::runtime::Gc::from_ptr(t as *mut luna_core::runtime::Table);
991    // P11-S5d.E' — 5.4+ honours __len on tables; the helper bypasses it.
992    // Park a deopt request and let the interpreter compute the length.
993    if g.metatable().is_some() {
994        vm.jit.pending_err = Some(vm.rt_err("JIT deopt: table has metatable"));
995        return 0;
996    }
997    g.len()
998}
999
1000// scoped_rebind submodule (formerly luna-jit/src/jit_backend/scoped_rebind.rs).
1001mod scoped_rebind;