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;