luna_core/jit/abi.rs
1//! JIT-backend trait boundary (luna-core side).
2//!
3//! Defines the [`IntChunkCompiler`] + [`TraceCompiler`] traits that
4//! the interpreter dispatcher in [`crate::vm::exec`] routes through
5//! to invoke the JIT codegen. Two implementations exist:
6//!
7//! - [`NullJitBackend`] (in this file) — does nothing; the default
8//! for [`crate::vm::Vm::new_minimal`] in luna-core.
9//! - `CraneliftBackend` (in the `luna` crate, `luna::jit_backend`) —
10//! delegates to Cranelift. Installed by `luna::install_default_jit`
11//! and `luna::Vm::new_minimal_with_jit`.
12//!
13//! v1.1 A1 Session C moved this file from `src/jit/abi.rs` (single
14//! crate) to `crates/luna-core/src/jit/abi.rs` (workspace). The trait
15//! surface itself is unchanged from Session A.
16//!
17//! RFC: `.dev/rfcs/v1.1-rfc-crate-split.md` §D1 + Migration step 6.
18
19use crate::jit::JitVmGuard;
20use crate::jit::trace_types::{CompileOptions, CompiledTrace, TraceRecord};
21use crate::runtime::Gc;
22use crate::runtime::LuaClosure;
23use crate::runtime::function::Proto;
24
25/// Native entry-point produced by `try_compile_int_chunk` for a chunk
26/// with zero params. Returns the chunk's `Return1` value as i64;
27/// `Return0` chunks return 0 (the caller knows by inspecting
28/// `JitHandle::returns_one`).
29// SAFETY: offset is hand-computed against the `repr(C)` layout of the target struct in this same module; the Cranelift lowerer relies on it staying in sync.
30pub type IntChunkFn = unsafe extern "C" fn() -> i64;
31
32/// One-arg JIT entry signature. See [`IntChunkFn`] for the zero-arg
33/// shape and [`MAX_JIT_ARITY`] for the cap.
34// SAFETY: offset is hand-computed against the `repr(C)` layout of the target struct in this same module; the Cranelift lowerer relies on it staying in sync.
35pub type IntFn1 = unsafe extern "C" fn(i64) -> i64;
36/// Two-arg JIT entry signature.
37// SAFETY: offset is hand-computed against the `repr(C)` layout of the target struct in this same module; the Cranelift lowerer relies on it staying in sync.
38pub type IntFn2 = unsafe extern "C" fn(i64, i64) -> i64;
39/// Three-arg JIT entry signature.
40// SAFETY: offset is hand-computed against the `repr(C)` layout of the target struct in this same module; the Cranelift lowerer relies on it staying in sync.
41pub type IntFn3 = unsafe extern "C" fn(i64, i64, i64) -> i64;
42/// Four-arg JIT entry signature.
43// SAFETY: offset is hand-computed against the `repr(C)` layout of the target struct in this same module; the Cranelift lowerer relies on it staying in sync.
44pub type IntFn4 = unsafe extern "C" fn(i64, i64, i64, i64) -> i64;
45
46/// Max arity the int-chunk compiler accepts before bailing back to
47/// the interpreter. Tuned against the 5-arg-and-down distribution in
48/// the official PUC test suites — extending past 4 buys very little
49/// hit-rate and complicates the dispatch table in [`crate::vm::exec`].
50pub const MAX_JIT_ARITY: u8 = 4;
51
52/// Outcome of a closure-compilation attempt. Kept ABI-compatible with
53/// the legacy `crate::jit::cache_lookup_or_compile` return tuple so the
54/// `Vm::populate_jit_cache` call site doesn't have to re-shape its
55/// destructure.
56#[derive(Clone, Copy, Debug)]
57pub enum CompileResult {
58 /// The proto was lowered (or served from cache); the fields mirror
59 /// the legacy 7-tuple returned by `cache_lookup_or_compile`.
60 Compiled {
61 /// Mmap'd entry address; transmute to the matching `unsafe extern
62 /// "C" fn` shape at the call site.
63 entry: *const u8,
64 /// Argument count (0..=`MAX_JIT_ARITY`).
65 num_args: u8,
66 /// True when the compiled chunk produces a single observable
67 /// result (`Op::Return1`); false when it only side-effects.
68 returns_one: bool,
69 /// Bit `i` set ↔ argument slot `i` is `Value::Float`.
70 arg_float_mask: u8,
71 /// Bit `i` set ↔ argument slot `i` is `Value::Table`.
72 arg_table_mask: u8,
73 /// True when the chunk's return value is float (only meaningful
74 /// when `returns_one` is true).
75 ret_is_float: bool,
76 /// True when the chunk's return value is `Value::Table`.
77 ret_is_table: bool,
78 },
79 /// The proto fell outside the whitelist or its compile pass bailed.
80 /// The interpreter handles it unchanged.
81 Skipped,
82}
83
84/// Closure-compilation backend. The interpreter dispatcher (and the
85/// `Op::Call` JIT fast path inside `vm/exec.rs`) calls
86/// `chunk_compiler.try_compile(proto, pre53, float_only)` once per
87/// Proto on the cold path; subsequent hits read the result back from
88/// `Proto.jit: Cell<JitProtoState>` directly, so the vtable cost is
89/// bounded by Proto count, not by call count.
90///
91/// `enter` is the per-JIT-entry RAII guard that pins the active `Vm`
92/// pointer (and optional `LuaClosure`) into the thread-locals the JIT
93/// helpers read. Taking a raw `*mut Vm` keeps the trait object-safe
94/// and lets the dispatcher pass `self as *mut Vm` without holding a
95/// mutable borrow on `self` while reading `self.chunk_compiler`.
96pub trait IntChunkCompiler {
97 /// Attempt to compile `proto`. Returns [`CompileResult::Compiled`] on
98 /// a whitelist hit or [`CompileResult::Skipped`] when the body is
99 /// outside the JIT's supported shape.
100 ///
101 /// v2.0 Track J sub-step J-B — `storage` is the per-`Vm` JIT cache
102 /// + handle holder. The Cranelift backend downcasts it to its
103 /// concrete `CraneliftJitStorage`; the no-op [`NullJitBackend`]
104 /// ignores it (returns `Skipped` without touching storage).
105 fn try_compile(
106 &self,
107 storage: &mut dyn crate::jit::JitStorage,
108 proto: Gc<Proto>,
109 pre53: bool,
110 float_only: bool,
111 ) -> CompileResult;
112
113 /// Install the active `Vm` + closure pointer into the JIT
114 /// helpers' thread-local slots; the returned guard restores
115 /// state on drop. For [`NullJitBackend`] this is a no-op
116 /// (helpers never fire because nothing compiled).
117 ///
118 /// SAFETY: the caller must ensure `vm` is a live, exclusively
119 /// borrowed `Vm` for the duration of the returned guard.
120 fn enter(&self, vm: *mut crate::vm::Vm, cl: Option<Gc<LuaClosure>>) -> JitVmGuard;
121}
122
123/// Trace-JIT backend. Receives a closed [`TraceRecord`] from the
124/// interpreter's recorder; returns `Some(CompiledTrace)` on success or
125/// `None` when the lowerer bailed. `last_compile_checkpoint` exposes
126/// the lowerer's per-thread last-phase marker used by
127/// `Vm.trace_compile_failed_reasons`.
128pub trait TraceCompiler {
129 /// Attempt to lower `record` into native code under `opts`. Returns
130 /// `None` if the lowerer bailed at any checkpoint.
131 ///
132 /// v2.0 Track J sub-step J-B — `storage` is the per-`Vm` JIT cache
133 /// + handle holder; the Cranelift backend uses it to park each
134 /// compiled trace's `JITModule`. [`NullJitBackend`] ignores it.
135 fn try_compile_trace(
136 &self,
137 storage: &mut dyn crate::jit::JitStorage,
138 record: &TraceRecord,
139 opts: CompileOptions,
140 ) -> Option<CompiledTrace>;
141
142 /// Name of the lowerer's last-reached checkpoint (diagnostic; lets
143 /// the recorder bucket failures by phase).
144 fn last_compile_checkpoint(&self) -> &'static str;
145}
146
147/// No-op backend installed by [`crate::vm::Vm::new_minimal`] in
148/// luna-core. Both calls report "nothing compiled" so the interp
149/// dispatcher always takes the standard path. Embedders who want a
150/// real JIT either depend on the `luna` crate (whose
151/// `Vm::new_minimal_with_jit` swaps in `CraneliftBackend`) or write
152/// their own `IntChunkCompiler` / `TraceCompiler` implementation.
153#[derive(Clone, Copy, Debug, Default)]
154pub struct NullJitBackend;
155
156impl IntChunkCompiler for NullJitBackend {
157 fn try_compile(
158 &self,
159 _: &mut dyn crate::jit::JitStorage,
160 _: Gc<Proto>,
161 _: bool,
162 _: bool,
163 ) -> CompileResult {
164 CompileResult::Skipped
165 }
166
167 fn enter(&self, _: *mut crate::vm::Vm, _: Option<Gc<LuaClosure>>) -> JitVmGuard {
168 // The TLS slots stay whatever they were — no JIT mcode will
169 // execute under a NullJitBackend (try_compile returned
170 // Skipped, so no Proto reaches JitProtoState::Compiled, and
171 // try_compile_trace returns None so no CompiledTrace ever
172 // dispatches), so the helpers never read those TLS values.
173 // JitVmGuard's drop is already a no-op (see the comment on
174 // `JitVmGuard::drop` in `mod.rs`).
175 crate::jit::noop_jit_guard()
176 }
177}
178
179impl TraceCompiler for NullJitBackend {
180 fn try_compile_trace(
181 &self,
182 _: &mut dyn crate::jit::JitStorage,
183 _: &TraceRecord,
184 _: CompileOptions,
185 ) -> Option<CompiledTrace> {
186 None
187 }
188
189 fn last_compile_checkpoint(&self) -> &'static str {
190 "null-backend"
191 }
192}