Skip to main content

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}