luna-jit 2.16.0

A Lua runtime in pure Rust — full 5.1/5.2/5.3/5.4/5.5 support. Equivalent to `luna-core` plus a Cranelift-backed JIT (method + trace) and the lua.h-compatible C ABI. (The `luna` crate name on crates.io is taken by an unrelated utility library; this crate is the JIT-equipped variant of the goliajp/luna Lua runtime.)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
//! mlua-style `Lua` facade (B12, Phase 2 P2-D).
//!
//! A thin wrapper around [`luna_core::vm::Vm`] that exposes the same
//! API in a shape familiar to embedders coming from `rlua` / `mlua`:
//!
//! ```
//! use luna_jit::Lua;
//!
//! let mut lua = Lua::new();
//! lua.open_base();
//! lua.open_math();
//! let r: i64 = lua.eval("return 1 + 2").unwrap();
//! assert_eq!(r, 3);
//!
//! let add = lua.create_function(|a: i64, b: i64| -> i64 { a + b });
//! lua.set_global("add", add).unwrap();
//! let r: i64 = lua.eval("return add(40, 2)").unwrap();
//! assert_eq!(r, 42);
//! ```
//!
//! ## Handles
//!
//! [`LuaFunction`] / [`LuaTable`] / [`LuaRoot`] are `Copy` wrappers
//! around a [`HostRootTicket`] returned by [`Vm::pin_host`]. They
//! keep their referenced `Gc<T>` alive across calls (so a `LuaTable`
//! survives a GC cycle even when no Lua-side reference exists).
//!
//! v1.3 Phase SR added slot recycling — a single handle can be
//! released via [`Lua::unpin`]; the whole batch via
//! [`Lua::unpin_all`]. Both operations bump the slot's generation,
//! invalidating any further use of `LuaFunction` / `LuaTable` /
//! `LuaRoot` `Copy` values that referenced the released slot
//! (subsequent reads / calls panic on the stale ticket).
//!
//! ## Threading
//!
//! `Lua` inherits `Vm`'s `!Send + !Sync` contract. See
//! [`docs/threading.md`](../../../../docs/threading.md) for canonical
//! embedding patterns.

use luna_core::runtime::Value;
use luna_core::version::LuaVersion;
use luna_core::vm::{
    FromLuaValue, HostRootStale, HostRootTicket, IntoValue, LuaError, NativeTypedSig,
    SandboxBuilder, Vm,
};

/// `mlua`-style front door for embedders. Wraps a [`Vm`] with JIT
/// installed by default (`Vm::new_minimal_with_jit`).
pub struct Lua(Vm);

impl Lua {
    /// Create a Lua VM with JIT installed + Lua 5.5 dialect.
    pub fn new() -> Lua {
        Lua(crate::new_minimal_with_jit(LuaVersion::Lua55))
    }

    /// Pick a specific dialect (5.1-5.5).
    pub fn with_version(v: LuaVersion) -> Lua {
        Lua(crate::new_minimal_with_jit(v))
    }

    /// Sandbox-mode builder — same as [`Vm::sandbox`] but doesn't
    /// install JIT by default. `.build_lua()` finalizes to a `Lua`
    /// wrapping the sandboxed `Vm`.
    pub fn sandbox(v: LuaVersion) -> LuaSandboxBuilder {
        LuaSandboxBuilder {
            inner: Vm::sandbox(v),
        }
    }

    /// Borrow the underlying `Vm` for direct access (escape hatch
    /// for cases the facade doesn't cover).
    pub fn vm(&mut self) -> &mut Vm {
        &mut self.0
    }

    /// Open the base library (`print`, `type`, `pcall`, etc.).
    pub fn open_base(&mut self) {
        self.0.open_base();
    }

    /// Open the math library.
    pub fn open_math(&mut self) {
        self.0.open_math();
    }

    /// Open the string library.
    pub fn open_string(&mut self) {
        self.0.open_string();
    }

    /// Open the table library.
    pub fn open_table(&mut self) {
        self.0.open_table();
    }

    /// Open the coroutine library.
    pub fn open_coroutine(&mut self) {
        self.0.open_coroutine();
    }

    /// Compile and run `src`; extract the first return value as `T`.
    /// Use [`Lua::eval_multi`] to retrieve all returns.
    pub fn eval<T: FromLuaValue>(&mut self, src: &str) -> Result<T, LuaError> {
        let mut r = self.0.eval(src)?;
        if r.is_empty() {
            T::from_lua_value(Value::Nil)
        } else {
            T::from_lua_value(r.remove(0))
        }
    }

    /// Compile and run `src`; return all results.
    pub fn eval_multi(&mut self, src: &str) -> Result<Vec<Value>, LuaError> {
        self.0.eval(src)
    }

    /// Async variant of [`Lua::eval`]. Returns an `!Send` future that
    /// drives the dispatcher with cooperative yields on instruction
    /// budget exhaustion. Pin this to a `current_thread` Tokio
    /// runtime (or a `LocalSet` inside multi-thread Tokio) — see
    /// `docs/threading.md` and `examples/async_host.rs`.
    pub async fn eval_async<T: FromLuaValue>(&mut self, src: &str) -> Result<T, LuaError> {
        let mut r = self.0.eval_async(src).await?;
        if r.is_empty() {
            T::from_lua_value(Value::Nil)
        } else {
            T::from_lua_value(r.remove(0))
        }
    }

    /// Async variant of [`Lua::eval_multi`].
    pub async fn eval_async_multi(&mut self, src: &str) -> Result<Vec<Value>, LuaError> {
        self.0.eval_async(src).await
    }

    /// Register an async native function callable from Lua. The
    /// raw fn pointer ABI takes `(*mut Vm, func_slot, nargs)` and
    /// returns a boxed future — see [`luna_core::vm::AsyncNativeFn`]
    /// for the safety contract.
    ///
    /// Calling an async native from inside `vm.eval()` (sync mode)
    /// errors with a typed `LuaError`; embedders must drive the call
    /// through `eval_async`.
    pub fn set_async_native(
        &mut self,
        name: &str,
        f: luna_core::vm::AsyncNativeFn,
    ) -> Result<(), LuaError> {
        self.0.set_async_native(name, f)
    }

    /// Set a global by name. Accepts any [`IntoValue`] including
    /// `LuaFunction` / `LuaTable` / `LuaRoot` (the handle types impl
    /// `IntoValue` so they fan in alongside primitives + `Value`).
    pub fn set_global<V: IntoValue>(&mut self, name: &str, v: V) -> Result<(), LuaError> {
        self.0.set_global(name, v)
    }

    /// Borrow the globals table as a [`LuaTable`] handle.
    pub fn globals(&mut self) -> LuaTable {
        let g = self.0.globals();
        let ticket = self.0.pin_host(Value::Table(g));
        LuaTable { ticket }
    }

    /// Allocate a fresh empty table; return a handle that keeps it alive.
    pub fn create_table(&mut self) -> LuaTable {
        let t = self.0.new_table().build();
        let ticket = self.0.pin_host(Value::Table(t));
        LuaTable { ticket }
    }

    /// Wrap a typed Rust function as a Lua callable. See
    /// [`Vm::native_typed`] for the supported callable shapes.
    pub fn create_function<F, Marker>(&mut self, f: F) -> LuaFunction
    where
        F: NativeTypedSig<Marker>,
    {
        let v = self.0.native_typed(f);
        let ticket = self.0.pin_host(v);
        LuaFunction { ticket }
    }

    /// Pin an arbitrary value as a host root; the returned [`LuaRoot`]
    /// keeps it alive until [`Lua::unpin`] or [`Lua::unpin_all`].
    pub fn pin<V: IntoValue>(&mut self, v: V) -> LuaRoot {
        let v = v.into_value(&mut self.0);
        let ticket = self.0.pin_host(v);
        LuaRoot { ticket }
    }

    /// Release a single pinned handle (v1.3 Phase SR). The handle's
    /// slot is recycled; the supplied `LuaFunction` / `LuaTable` /
    /// `LuaRoot` value (and any `Copy`-cloned aliases) becomes stale
    /// and will panic on subsequent reads / calls.
    ///
    /// Returns `Err(HostRootStale)` if the handle was already
    /// released — pool is unchanged in that case, so embedders can
    /// safely ignore the error if double-unpin is acceptable.
    pub fn unpin<H: PinnedHandle>(&mut self, h: H) -> Result<(), HostRootStale> {
        self.0.unpin(h.ticket())
    }

    /// Drop every pinned handle. `LuaFunction` / `LuaTable` /
    /// `LuaRoot` created before this call become invalid (panic on
    /// use). Bumps every slot's generation; underlying `Vec` capacity
    /// is retained for amortized future allocations.
    pub fn unpin_all(&mut self) {
        self.0.unpin_all();
    }

    /// Number of currently-pinned handles (diagnostic). v1.3 Phase
    /// SR: counts live (non-free) slots, so a steady `pin → unpin`
    /// loop holds at 1 instead of growing monotonically.
    pub fn pinned_count(&self) -> usize {
        self.0.host_root_count()
    }
}

/// v1.3 Phase SR — common trait for handle types that wrap a
/// [`HostRootTicket`]. Lets [`Lua::unpin`] accept `LuaFunction` /
/// `LuaTable` / `LuaRoot` uniformly.
pub trait PinnedHandle {
    /// The ticket this handle wraps.
    fn ticket(&self) -> HostRootTicket;
}

impl Default for Lua {
    fn default() -> Self {
        Lua::new()
    }
}

/// Sandbox builder that finalizes to a `Lua` (instead of a bare `Vm`).
pub struct LuaSandboxBuilder {
    inner: SandboxBuilder,
}

impl LuaSandboxBuilder {
    /// Whitelist the `base` standard library.
    pub fn open_base(mut self) -> Self {
        self.inner = self.inner.open_base();
        self
    }
    /// Whitelist the `math` standard library.
    pub fn open_math(mut self) -> Self {
        self.inner = self.inner.open_math();
        self
    }
    /// Whitelist the `string` standard library.
    pub fn open_string(mut self) -> Self {
        self.inner = self.inner.open_string();
        self
    }
    /// Whitelist the `table` standard library.
    pub fn open_table(mut self) -> Self {
        self.inner = self.inner.open_table();
        self
    }
    /// Whitelist the `coroutine` standard library.
    pub fn open_coroutine(mut self) -> Self {
        self.inner = self.inner.open_coroutine();
        self
    }
    /// Cap interpreter instruction count per call (fires once, then trips).
    pub fn with_instr_budget(mut self, n: i64) -> Self {
        self.inner = self.inner.with_instr_budget(n);
        self
    }
    /// Cap heap memory (approximate; see [`crate::vm::Vm::set_memory_cap`]).
    pub fn with_memory_cap(mut self, n: usize) -> Self {
        self.inner = self.inner.with_memory_cap(n);
        self
    }
    /// Re-enable precompiled-bytecode loading (off by default in sandbox
    /// mode for safety).
    pub fn allow_bytecode_loading(mut self) -> Self {
        self.inner = self.inner.allow_bytecode_loading();
        self
    }
    /// Finalize the builder and return a configured [`Lua`].
    pub fn build(self) -> Lua {
        Lua(self.inner.build())
    }
}

// ─────────────────────────────────────────────────────────────────────
// Handle types
// ─────────────────────────────────────────────────────────────────────

/// Handle to a Lua-callable value (`Value::Closure` or
/// `Value::Native`) pinned in the host root pool. `Copy`-able —
/// clones share the same [`HostRootTicket`].
///
/// Becomes stale (panics on call) after [`Lua::unpin`] /
/// [`Lua::unpin_all`].
#[derive(Copy, Clone, Debug)]
pub struct LuaFunction {
    ticket: HostRootTicket,
}

impl LuaFunction {
    /// The underlying [`HostRootTicket`]. Facade-author use only.
    pub fn ticket(self) -> HostRootTicket {
        self.ticket
    }

    /// Call this function with the given typed args; decode the
    /// (first) return as `R`. Use [`LuaFunction::call_multi`] for
    /// the full result vector.
    pub fn call<A, R>(self, lua: &mut Lua, args: A) -> Result<R, LuaError>
    where
        A: IntoLuaArgs,
        R: FromLuaValue,
    {
        let f = lua
            .0
            .read_host(self.ticket)
            .expect("LuaFunction used after unpin / unpin_all");
        let args = args.into_lua_args(&mut lua.0);
        let mut r = lua.0.call_value(f, &args)?;
        if r.is_empty() {
            R::from_lua_value(Value::Nil)
        } else {
            R::from_lua_value(r.remove(0))
        }
    }

    /// Call this function; return all results.
    pub fn call_multi<A>(self, lua: &mut Lua, args: A) -> Result<Vec<Value>, LuaError>
    where
        A: IntoLuaArgs,
    {
        let f = lua
            .0
            .read_host(self.ticket)
            .expect("LuaFunction used after unpin / unpin_all");
        let args = args.into_lua_args(&mut lua.0);
        lua.0.call_value(f, &args)
    }
}

impl IntoValue for LuaFunction {
    fn into_value(self, vm: &mut Vm) -> Value {
        vm.read_host(self.ticket)
            .expect("LuaFunction used after unpin / unpin_all")
    }
}

impl PinnedHandle for LuaFunction {
    fn ticket(&self) -> HostRootTicket {
        self.ticket
    }
}

/// Handle to a `Value::Table` pinned in the host root pool.
#[derive(Copy, Clone, Debug)]
pub struct LuaTable {
    ticket: HostRootTicket,
}

impl LuaTable {
    /// The underlying [`HostRootTicket`]. Facade-author use only.
    pub fn ticket(self) -> HostRootTicket {
        self.ticket
    }

    /// Set `t[k] = v`. Both `k` and `v` may be any [`IntoValue`].
    pub fn set<K: IntoValue, V: IntoValue>(
        self,
        lua: &mut Lua,
        k: K,
        v: V,
    ) -> Result<(), LuaError> {
        let t = match lua
            .0
            .read_host(self.ticket)
            .expect("LuaTable used after unpin / unpin_all")
        {
            Value::Table(t) => t,
            _ => return Err(LuaError(Value::Nil)),
        };
        let k = k.into_value(&mut lua.0);
        let v = v.into_value(&mut lua.0);
        // SAFETY: Gc<T> is NonNull<T> over the GC heap; the heap is
        // single-threaded (see heap.rs:5-7).
        unsafe { t.as_mut() }.set(&mut lua.0.heap, k, v)?;
        lua.0
            .heap
            .barrier_back(t.as_ptr() as *mut luna_core::runtime::heap::GcHeader);
        Ok(())
    }

    /// Read `t[k]`; decode as `V`. Returns `Err` if the key is
    /// missing OR the value's type doesn't match `V`. Use
    /// `t.raw_get(k)` (returning `Value`) for runtime branching.
    pub fn get<K: IntoValue, V: FromLuaValue>(self, lua: &mut Lua, k: K) -> Result<V, LuaError> {
        let v = self.raw_get(lua, k)?;
        V::from_lua_value(v)
    }

    /// Read `t[k]` as a raw [`Value`] (no type coercion).
    pub fn raw_get<K: IntoValue>(self, lua: &mut Lua, k: K) -> Result<Value, LuaError> {
        let t = match lua
            .0
            .read_host(self.ticket)
            .expect("LuaTable used after unpin / unpin_all")
        {
            Value::Table(t) => t,
            _ => return Err(LuaError(Value::Nil)),
        };
        let k = k.into_value(&mut lua.0);
        // SAFETY: see set() — same single-threaded GC contract.
        Ok(unsafe { t.as_mut() }.get(k))
    }
}

impl IntoValue for LuaTable {
    fn into_value(self, vm: &mut Vm) -> Value {
        vm.read_host(self.ticket)
            .expect("LuaTable used after unpin / unpin_all")
    }
}

impl PinnedHandle for LuaTable {
    fn ticket(&self) -> HostRootTicket {
        self.ticket
    }
}

/// Generic pinned root. Use for arbitrary `Value`s the embedder
/// wants to keep alive without wrapping in `LuaFunction` / `LuaTable`.
#[derive(Copy, Clone, Debug)]
pub struct LuaRoot {
    ticket: HostRootTicket,
}

impl LuaRoot {
    /// The underlying [`HostRootTicket`]. Facade-author use only.
    pub fn ticket(self) -> HostRootTicket {
        self.ticket
    }

    /// Read the pinned value. Panics if the handle was released.
    pub fn get(self, lua: &Lua) -> Value {
        lua.0
            .read_host(self.ticket)
            .expect("LuaRoot used after unpin / unpin_all")
    }
}

impl IntoValue for LuaRoot {
    fn into_value(self, vm: &mut Vm) -> Value {
        vm.read_host(self.ticket)
            .expect("LuaRoot used after unpin / unpin_all")
    }
}

impl PinnedHandle for LuaRoot {
    fn ticket(&self) -> HostRootTicket {
        self.ticket
    }
}

// ─────────────────────────────────────────────────────────────────────
// IntoLuaArgs — tuple to &[Value] conversion for LuaFunction::call
// ─────────────────────────────────────────────────────────────────────

/// Convert a tuple of typed values into the `&[Value]` shape
/// [`Vm::call_value`] expects. Implemented for `()` + tuples of
/// [`IntoValue`] up to arity 6.
pub trait IntoLuaArgs {
    /// Encode `self` (a tuple of [`IntoValue`] implementors) as a flat
    /// argument list ready for [`LuaFunction::call`].
    fn into_lua_args(self, vm: &mut Vm) -> Vec<Value>;
}

impl IntoLuaArgs for () {
    fn into_lua_args(self, _vm: &mut Vm) -> Vec<Value> {
        Vec::new()
    }
}

macro_rules! impl_into_lua_args_tuple {
    ( $( ($($name:ident: $idx:tt),+) ),+ $(,)? ) => {
        $(
            impl<$($name: IntoValue),+> IntoLuaArgs for ($($name,)+) {
                fn into_lua_args(self, vm: &mut Vm) -> Vec<Value> {
                    vec![ $( self.$idx.into_value(vm), )+ ]
                }
            }
        )+
    };
}
impl_into_lua_args_tuple! {
    (T0: 0),
    (T0: 0, T1: 1),
    (T0: 0, T1: 1, T2: 2),
    (T0: 0, T1: 1, T2: 2, T3: 3),
    (T0: 0, T1: 1, T2: 2, T3: 3, T4: 4),
    (T0: 0, T1: 1, T2: 2, T3: 3, T4: 4, T5: 5),
}