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
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
//! C ABI surface — a minimal viable `lua.h`-equivalent that lets existing
//! C/C++ hosts link against luna. Not a full PUC reimplementation; the
//! subset here is the one needed to drive a Vm from a C caller:
//! create/close state, load + pcall, push/to integer/string/boolean/nil,
//! get/setglobal, stack height, type queries, plus C-side callbacks via
//! `lua_pushcfunction` / `lua_register`.
//!
//! `L` argument names follow PUC convention; suppress non_snake_case warnings
//! for the whole module so the surface reads like lua.h.
//!
//! Aliasing safety: `LuaState` is a `#[repr(transparent)]` wrapper around
//! `Vm`. A `*mut LuaState` is bit-identical to a `*mut Vm`, so any function
//! that holds `&mut Vm` can cast it to `*mut LuaState` and hand it to a C
//! callback without a second active reference colliding. The trampoline
//! that bridges to a `LuaCFunction` drops its `&mut Vm` to a raw pointer
//! exactly across the `cf(L)` call, re-borrowing afterward — see
//! `capi_trampoline` for the prose.
#![allow(non_snake_case)]

use luna_core::runtime::Value;
use luna_core::version::LuaVersion;
use luna_core::vm::{LuaError, Vm};
use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_int};

/// `lua_State` analogue — a transparent wrapper around `Vm`. C callers see
/// it as an opaque pointer; Rust glue casts between `*mut LuaState` and
/// `*mut Vm` freely because `#[repr(transparent)]` guarantees identical
/// layout.
#[repr(transparent)]
pub struct LuaState {
    vm: Vm,
}

/// PUC status codes that fit luna's surface.
pub const LUA_OK: c_int = 0;
/// PUC `LUA_ERRRUN` — runtime error while executing a chunk.
pub const LUA_ERRRUN: c_int = 2;
/// PUC `LUA_ERRSYNTAX` — parse / compile error in `luaL_loadbufferx`.
pub const LUA_ERRSYNTAX: c_int = 3;
/// PUC `LUA_ERRMEM` — memory-allocation failure.
pub const LUA_ERRMEM: c_int = 4;

/// PUC `lua_type` constants — match the values PUC uses so a C header
/// shared with PUC code resolves to the same tags.
pub const LUA_TNONE: c_int = -1;
/// PUC `LUA_TNIL`.
pub const LUA_TNIL: c_int = 0;
/// PUC `LUA_TBOOLEAN`.
pub const LUA_TBOOLEAN: c_int = 1;
/// PUC `LUA_TLIGHTUSERDATA`.
pub const LUA_TLIGHTUSERDATA: c_int = 2;
/// PUC `LUA_TNUMBER`.
pub const LUA_TNUMBER: c_int = 3;
/// PUC `LUA_TSTRING`.
pub const LUA_TSTRING: c_int = 4;
/// PUC `LUA_TTABLE`.
pub const LUA_TTABLE: c_int = 5;
/// PUC `LUA_TFUNCTION`.
pub const LUA_TFUNCTION: c_int = 6;
/// PUC `LUA_TUSERDATA`.
pub const LUA_TUSERDATA: c_int = 7;
/// PUC `LUA_TTHREAD`.
pub const LUA_TTHREAD: c_int = 8;

/// C function ABI used by `lua_pushcfunction` / `lua_register`.
pub type LuaCFunction = extern "C" fn(*mut LuaState) -> c_int;

/// Resolve a (possibly negative) PUC-style index into a Vm `capi_stack`
/// slot. Returns None when the index is out of bounds.
fn abs_index(vm: &Vm, idx: c_int) -> Option<usize> {
    let len = vm.capi_stack.len() as c_int;
    let abs = if idx > 0 {
        idx
    } else if idx < 0 {
        len + idx + 1
    } else {
        return None; // 0 is invalid for PUC indices
    };
    if abs < 1 || abs > len {
        None
    } else {
        Some((abs - 1) as usize)
    }
}

fn get_at(vm: &Vm, idx: c_int) -> Option<Value> {
    abs_index(vm, idx).map(|i| vm.capi_stack[i])
}

unsafe fn vm_mut<'a>(L: *mut LuaState) -> &'a mut Vm {
    debug_assert!(!L.is_null(), "null lua_State*");
    // SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
    unsafe { &mut (*L).vm }
}

fn type_tag(v: Value) -> c_int {
    match v {
        Value::Nil => LUA_TNIL,
        Value::Bool(_) => LUA_TBOOLEAN,
        Value::Int(_) | Value::Float(_) => LUA_TNUMBER,
        Value::Str(_) => LUA_TSTRING,
        Value::Table(_) => LUA_TTABLE,
        Value::Closure(_) | Value::Native(_) => LUA_TFUNCTION,
        Value::Userdata(_) => LUA_TUSERDATA,
        Value::Coro(_) => LUA_TTHREAD,
        Value::LightUserdata(_) => LUA_TLIGHTUSERDATA,
    }
}

// ─── state lifecycle ─────────────────────────────────────────────────────

/// Allocate a new Lua state with the 5.5 dialect (PUC `luaL_newstate`).
/// The state is empty — call `luaL_openlibs` to load the standard library.
///
/// v1.1 A1 Session C — the C ABI is a `luna`-crate surface, so we
/// install the Cranelift backend here (matching v1.0 behavior where
/// JIT was on by default for C callers). luna-core's
/// `Vm::new_minimal` itself defaults to `NullJitBackend`.
///
/// v2.0 J-B follow-up — route through `crate::install_default_jit`
/// so the `CraneliftBackend` install is paired with a fresh
/// `CraneliftJitStorage`. Calling `install_jit_backend` alone leaves
/// `Vm.jit.storage` at the default `NullJitStorage`; the trait-pair
/// invariant means the first JIT compile path that downcasts storage
/// (via `jit_backend::storage::from_storage`) would observe a type
/// mismatch. Pre-fix that downcast was an `expect` panic which
/// became SIGABRT across the C-ABI boundary (panic-can't-unwind);
/// the `Result` conversion in `storage::from_storage` makes mismatch
/// gracefully skip JIT, but the right thing here is still to install
/// both halves so the JIT actually runs for capi callers.
// SAFETY: `no_mangle` is required for the C ABI symbol to be linkable as `lua_*` by external C/C++ callers; this crate is the sole producer of these symbols within any final binary that links it.
#[unsafe(no_mangle)]
pub extern "C" fn luaL_newstate() -> *mut LuaState {
    let mut vm = Vm::new_minimal(LuaVersion::Lua55);
    crate::install_default_jit(&mut vm);
    let l = Box::new(LuaState { vm });
    Box::into_raw(l)
}

/// Free the state and its Vm (PUC `lua_close`). Safe to call with a null
/// pointer (no-op); calling with a previously-closed pointer is UB just
/// like in PUC.
// SAFETY: `no_mangle` is required for the C ABI symbol to be linkable as `lua_*` by external C/C++ callers; this crate is the sole producer of these symbols within any final binary that links it.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_close(L: *mut LuaState) {
    if L.is_null() {
        return;
    }
    // SAFETY: `L` was originally produced by `Box::into_raw` in `lua_newstate` / `lua_open`; the caller hasn't freed it via another `lua_close`, so reclaiming ownership here is sound.
    let _ = unsafe { Box::from_raw(L) };
}

/// Open all 5.5 standard libraries (PUC `luaL_openlibs`).
// SAFETY: `no_mangle` is required for the C ABI symbol to be linkable as `lua_*` by external C/C++ callers; this crate is the sole producer of these symbols within any final binary that links it.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaL_openlibs(L: *mut LuaState) {
    // SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
    let vm = unsafe { vm_mut(L) };
    vm.open_all_libs();
}

// ─── load + call ─────────────────────────────────────────────────────────

/// Compile `src` (NUL-terminated C string) under `chunkname`; push the
/// resulting function on the stack and return LUA_OK, or push the error
/// string and return LUA_ERRSYNTAX (PUC `luaL_loadstring`). `chunkname`
/// may be null — in that case the compiler uses `"=?"`.
// SAFETY: `no_mangle` is required for the C ABI symbol to be linkable as `lua_*` by external C/C++ callers; this crate is the sole producer of these symbols within any final binary that links it.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaL_loadstring(L: *mut LuaState, src: *const c_char) -> c_int {
    if L.is_null() || src.is_null() {
        return LUA_ERRSYNTAX;
    }
    // SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
    let vm = unsafe { vm_mut(L) };
    // SAFETY: Lua C API contract — the caller guarantees the passed `*const c_char` points to a NUL-terminated byte string that stays valid for the duration of this call.
    let src_bytes = unsafe { CStr::from_ptr(src).to_bytes() };
    match vm.load(src_bytes, b"=(load)") {
        Ok(cl) => {
            vm.capi_stack.push(Value::Closure(cl));
            LUA_OK
        }
        Err(e) => {
            let msg = format!("{e}");
            let v = Value::Str(vm.heap.intern(msg.as_bytes()));
            vm.capi_stack.push(v);
            LUA_ERRSYNTAX
        }
    }
}

/// Call `stack[-(nargs + 1)]` with the top `nargs` values as arguments,
/// expecting `nresults` results (use -1 to mean "all"). Pops the function
/// + arguments and pushes the results; on error pushes the error message
/// and returns LUA_ERRRUN. `msgh` (message handler) is accepted for ABI
/// compatibility but currently ignored — the error object is forwarded
/// raw (PUC `lua_pcall` with `msgh=0` is the same).
// SAFETY: `no_mangle` is required for the C ABI symbol to be linkable as `lua_*` by external C/C++ callers; this crate is the sole producer of these symbols within any final binary that links it.
#[unsafe(no_mangle)]
// SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
pub unsafe extern "C" fn lua_pcall(
    L: *mut LuaState,
    nargs: c_int,
    nresults: c_int,
    _msgh: c_int,
) -> c_int {
    // SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
    let vm = unsafe { vm_mut(L) };
    let needed = (nargs + 1) as usize;
    if vm.capi_stack.len() < needed {
        let v = Value::Str(vm.heap.intern(b"not enough values on stack"));
        vm.capi_stack.push(v);
        return LUA_ERRRUN;
    }
    let func_idx = vm.capi_stack.len() - needed;
    let args: Vec<Value> = vm.capi_stack[func_idx + 1..].to_vec();
    let f = vm.capi_stack[func_idx];
    vm.capi_stack.truncate(func_idx);
    match vm.call_value(f, &args) {
        Ok(mut results) => {
            if nresults >= 0 {
                results.resize(nresults as usize, Value::Nil);
            }
            for v in results {
                vm.capi_stack.push(v);
            }
            LUA_OK
        }
        Err(e) => {
            let err_val = match e.0 {
                Value::Str(_) => e.0,
                _ => {
                    let rendered = vm.error_text(&e);
                    Value::Str(vm.heap.intern(rendered.as_bytes()))
                }
            };
            vm.capi_stack.push(err_val);
            LUA_ERRRUN
        }
    }
}

// ─── globals ─────────────────────────────────────────────────────────────

/// Push the global named by `name` on the stack and return its type
/// (`LUA_T*`). `LUA_TNIL` if the global is unset (PUC `lua_getglobal`).
// SAFETY: `no_mangle` is required for the C ABI symbol to be linkable as `lua_*` by external C/C++ callers; this crate is the sole producer of these symbols within any final binary that links it.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_getglobal(L: *mut LuaState, name: *const c_char) -> c_int {
    if name.is_null() {
        return LUA_TNONE;
    }
    // SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
    let vm = unsafe { vm_mut(L) };
    // SAFETY: Lua C API contract — the caller guarantees the passed `*const c_char` points to a NUL-terminated byte string that stays valid for the duration of this call.
    let name_bytes = unsafe { CStr::from_ptr(name).to_bytes() };
    let key = Value::Str(vm.heap.intern(name_bytes));
    let v = vm.globals().get(key);
    vm.capi_stack.push(v);
    type_tag(v)
}

/// Pop the top of the stack and set it as the global named by `name`
/// (PUC `lua_setglobal`).
// SAFETY: `no_mangle` is required for the C ABI symbol to be linkable as `lua_*` by external C/C++ callers; this crate is the sole producer of these symbols within any final binary that links it.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_setglobal(L: *mut LuaState, name: *const c_char) {
    if name.is_null() {
        return;
    }
    // SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
    let vm = unsafe { vm_mut(L) };
    let v = vm.capi_stack.pop().unwrap_or(Value::Nil);
    // SAFETY: Lua C API contract — the caller guarantees the passed `*const c_char` points to a NUL-terminated byte string that stays valid for the duration of this call.
    let name_str = unsafe { CStr::from_ptr(name).to_str().unwrap_or("?") };
    let _ = vm.set_global(name_str, v); // capi swallows: lua_setglobal is void in C ABI
}

// ─── stack push ──────────────────────────────────────────────────────────

/// PUC `lua_pushnil` — push `nil` onto the C API stack.
// SAFETY: `no_mangle` is required for the C ABI symbol to be linkable as `lua_*` by external C/C++ callers; this crate is the sole producer of these symbols within any final binary that links it.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_pushnil(L: *mut LuaState) {
    // SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
    let vm = unsafe { vm_mut(L) };
    vm.capi_stack.push(Value::Nil);
}

/// PUC `lua_pushboolean` — push a boolean (`0` is false, anything else true).
// SAFETY: `no_mangle` is required for the C ABI symbol to be linkable as `lua_*` by external C/C++ callers; this crate is the sole producer of these symbols within any final binary that links it.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_pushboolean(L: *mut LuaState, b: c_int) {
    // SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
    let vm = unsafe { vm_mut(L) };
    vm.capi_stack.push(Value::Bool(b != 0));
}

/// PUC `lua_pushinteger` — push a 64-bit signed integer.
// SAFETY: `no_mangle` is required for the C ABI symbol to be linkable as `lua_*` by external C/C++ callers; this crate is the sole producer of these symbols within any final binary that links it.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_pushinteger(L: *mut LuaState, n: i64) {
    // SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
    let vm = unsafe { vm_mut(L) };
    vm.capi_stack.push(Value::Int(n));
}

/// PUC `lua_pushnumber` — push an IEEE-754 double.
// SAFETY: `no_mangle` is required for the C ABI symbol to be linkable as `lua_*` by external C/C++ callers; this crate is the sole producer of these symbols within any final binary that links it.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_pushnumber(L: *mut LuaState, n: f64) {
    // SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
    let vm = unsafe { vm_mut(L) };
    vm.capi_stack.push(Value::Float(n));
}

/// Push `str` (NUL-terminated) on the stack and return a borrowed pointer
/// to its interned bytes. Lifetime: until the pushed string falls off the
/// stack (or the state is closed).
// SAFETY: `no_mangle` is required for the C ABI symbol to be linkable as `lua_*` by external C/C++ callers; this crate is the sole producer of these symbols within any final binary that links it.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_pushstring(L: *mut LuaState, str: *const c_char) -> *const c_char {
    if str.is_null() {
        // SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
        unsafe { lua_pushnil(L) };
        return std::ptr::null();
    }
    // SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
    let vm = unsafe { vm_mut(L) };
    // SAFETY: Lua C API contract — the caller guarantees the passed `*const c_char` points to a NUL-terminated byte string that stays valid for the duration of this call.
    let bytes = unsafe { CStr::from_ptr(str).to_bytes() };
    let interned = vm.heap.intern(bytes);
    vm.capi_stack.push(Value::Str(interned));
    // SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
    unsafe { lua_tostring(L, -1) }
}

// ─── stack read ──────────────────────────────────────────────────────────

/// PUC `lua_tointeger` — convert the value at `idx` to `i64` (PUC's lossy
/// coercion: floats truncate, booleans become 0/1, parsable strings parse,
/// others return 0).
// SAFETY: `no_mangle` is required for the C ABI symbol to be linkable as `lua_*` by external C/C++ callers; this crate is the sole producer of these symbols within any final binary that links it.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_tointeger(L: *mut LuaState, idx: c_int) -> i64 {
    // SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
    let vm = unsafe { vm_mut(L) };
    match get_at(vm, idx) {
        Some(Value::Int(i)) => i,
        Some(Value::Float(f)) => f as i64,
        Some(Value::Bool(true)) => 1,
        Some(Value::Bool(false)) => 0,
        Some(Value::Str(st)) => std::str::from_utf8(st.as_bytes())
            .ok()
            .and_then(|s| s.trim().parse::<i64>().ok())
            .unwrap_or(0),
        _ => 0,
    }
}

/// PUC `lua_tonumber` — convert the value at `idx` to `f64` with PUC's
/// lossy coercion (see [`lua_tointeger`]).
// SAFETY: `no_mangle` is required for the C ABI symbol to be linkable as `lua_*` by external C/C++ callers; this crate is the sole producer of these symbols within any final binary that links it.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_tonumber(L: *mut LuaState, idx: c_int) -> f64 {
    // SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
    let vm = unsafe { vm_mut(L) };
    match get_at(vm, idx) {
        Some(Value::Int(i)) => i as f64,
        Some(Value::Float(f)) => f,
        Some(Value::Bool(true)) => 1.0,
        Some(Value::Bool(false)) => 0.0,
        Some(Value::Str(st)) => std::str::from_utf8(st.as_bytes())
            .ok()
            .and_then(|s| s.trim().parse::<f64>().ok())
            .unwrap_or(0.0),
        _ => 0.0,
    }
}

/// PUC `lua_toboolean` — Lua truth at `idx` (`nil` / `false` → 0; else 1).
// SAFETY: `no_mangle` is required for the C ABI symbol to be linkable as `lua_*` by external C/C++ callers; this crate is the sole producer of these symbols within any final binary that links it.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_toboolean(L: *mut LuaState, idx: c_int) -> c_int {
    // SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
    let vm = unsafe { vm_mut(L) };
    match get_at(vm, idx) {
        Some(Value::Nil) | None => 0,
        Some(Value::Bool(false)) => 0,
        _ => 1,
    }
}

/// Return a pointer to the i-th stack slot as a NUL-terminated string.
/// Numeric values are stringified the same way `tostring()` would. The
/// pointer is valid until the next `lua_tostring` on this state with a
/// different value, or `lua_close`.
// SAFETY: `no_mangle` is required for the C ABI symbol to be linkable as `lua_*` by external C/C++ callers; this crate is the sole producer of these symbols within any final binary that links it.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_tostring(L: *mut LuaState, idx: c_int) -> *const c_char {
    // SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
    let vm = unsafe { vm_mut(L) };
    let bytes: Vec<u8> = match get_at(vm, idx) {
        Some(Value::Str(st)) => st.as_bytes().to_vec(),
        Some(Value::Int(i)) => i.to_string().into_bytes(),
        Some(Value::Float(f)) => f.to_string().into_bytes(),
        Some(Value::Nil) | None => return std::ptr::null(),
        Some(Value::Bool(true)) => b"true".to_vec(),
        Some(Value::Bool(false)) => b"false".to_vec(),
        _ => return std::ptr::null(),
    };
    let c = CString::new(bytes).unwrap_or_else(|_| CString::new("?").unwrap());
    let p = c.as_ptr();
    vm.capi_cstr_pin = Some(c);
    p
}

// ─── type queries ────────────────────────────────────────────────────────

/// PUC `lua_type` — discriminator tag at `idx` (`LUA_T*`); `LUA_TNONE`
/// if `idx` is out of bounds.
// SAFETY: `no_mangle` is required for the C ABI symbol to be linkable as `lua_*` by external C/C++ callers; this crate is the sole producer of these symbols within any final binary that links it.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_type(L: *mut LuaState, idx: c_int) -> c_int {
    // SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
    let vm = unsafe { vm_mut(L) };
    get_at(vm, idx).map_or(LUA_TNONE, type_tag)
}

/// PUC `lua_isnil` — true iff the value at `idx` is `nil`.
// SAFETY: `no_mangle` is required for the C ABI symbol to be linkable as `lua_*` by external C/C++ callers; this crate is the sole producer of these symbols within any final binary that links it.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_isnil(L: *mut LuaState, idx: c_int) -> c_int {
    // SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
    (unsafe { lua_type(L, idx) } == LUA_TNIL) as c_int
}

/// PUC `lua_isnumber` — true iff the value at `idx` is `Int` or `Float`.
// SAFETY: `no_mangle` is required for the C ABI symbol to be linkable as `lua_*` by external C/C++ callers; this crate is the sole producer of these symbols within any final binary that links it.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_isnumber(L: *mut LuaState, idx: c_int) -> c_int {
    // SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
    let vm = unsafe { vm_mut(L) };
    matches!(get_at(vm, idx), Some(Value::Int(_)) | Some(Value::Float(_))) as c_int
}

/// PUC `lua_isinteger` (5.3+) — true iff the value at `idx` is exactly `Int`.
// SAFETY: `no_mangle` is required for the C ABI symbol to be linkable as `lua_*` by external C/C++ callers; this crate is the sole producer of these symbols within any final binary that links it.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_isinteger(L: *mut LuaState, idx: c_int) -> c_int {
    // SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
    let vm = unsafe { vm_mut(L) };
    matches!(get_at(vm, idx), Some(Value::Int(_))) as c_int
}

/// PUC `lua_isstring` — true iff the value at `idx` is a string or a
/// number (numbers coerce to strings in PUC).
// SAFETY: `no_mangle` is required for the C ABI symbol to be linkable as `lua_*` by external C/C++ callers; this crate is the sole producer of these symbols within any final binary that links it.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_isstring(L: *mut LuaState, idx: c_int) -> c_int {
    // SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
    let vm = unsafe { vm_mut(L) };
    matches!(
        get_at(vm, idx),
        Some(Value::Str(_)) | Some(Value::Int(_)) | Some(Value::Float(_))
    ) as c_int
}

/// PUC `lua_isboolean` — true iff the value at `idx` is a boolean.
// SAFETY: `no_mangle` is required for the C ABI symbol to be linkable as `lua_*` by external C/C++ callers; this crate is the sole producer of these symbols within any final binary that links it.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_isboolean(L: *mut LuaState, idx: c_int) -> c_int {
    // SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
    let vm = unsafe { vm_mut(L) };
    matches!(get_at(vm, idx), Some(Value::Bool(_))) as c_int
}

/// PUC `lua_isfunction` — true iff the value at `idx` is a Lua closure
/// or a native function.
// SAFETY: `no_mangle` is required for the C ABI symbol to be linkable as `lua_*` by external C/C++ callers; this crate is the sole producer of these symbols within any final binary that links it.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_isfunction(L: *mut LuaState, idx: c_int) -> c_int {
    // SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
    let vm = unsafe { vm_mut(L) };
    matches!(
        get_at(vm, idx),
        Some(Value::Closure(_)) | Some(Value::Native(_))
    ) as c_int
}

// ─── stack manipulation ──────────────────────────────────────────────────

/// PUC `lua_gettop` — current stack height (number of pushed values).
// SAFETY: `no_mangle` is required for the C ABI symbol to be linkable as `lua_*` by external C/C++ callers; this crate is the sole producer of these symbols within any final binary that links it.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_gettop(L: *mut LuaState) -> c_int {
    // SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
    let vm = unsafe { vm_mut(L) };
    vm.capi_stack.len() as c_int
}

/// PUC `lua_settop` — set the stack height to `idx`, padding with `nil`
/// or truncating as needed (negative indices count from the top).
// SAFETY: `no_mangle` is required for the C ABI symbol to be linkable as `lua_*` by external C/C++ callers; this crate is the sole producer of these symbols within any final binary that links it.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_settop(L: *mut LuaState, idx: c_int) {
    // SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
    let vm = unsafe { vm_mut(L) };
    let new_len = if idx >= 0 {
        idx as usize
    } else {
        (vm.capi_stack.len() as c_int + idx + 1).max(0) as usize
    };
    if new_len < vm.capi_stack.len() {
        vm.capi_stack.truncate(new_len);
    } else {
        vm.capi_stack.resize(new_len, Value::Nil);
    }
}

/// PUC `lua_pop` — drop the top `n` stack values.
// SAFETY: `no_mangle` is required for the C ABI symbol to be linkable as `lua_*` by external C/C++ callers; this crate is the sole producer of these symbols within any final binary that links it.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_pop(L: *mut LuaState, n: c_int) {
    // SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
    unsafe { lua_settop(L, -n - 1) };
}

/// PUC `lua_pushvalue` — duplicate the value at `idx` onto the top.
// SAFETY: `no_mangle` is required for the C ABI symbol to be linkable as `lua_*` by external C/C++ callers; this crate is the sole producer of these symbols within any final binary that links it.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_pushvalue(L: *mut LuaState, idx: c_int) {
    // SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
    let vm = unsafe { vm_mut(L) };
    if let Some(v) = get_at(vm, idx) {
        vm.capi_stack.push(v);
    } else {
        vm.capi_stack.push(Value::Nil);
    }
}

// ─── C callbacks ─────────────────────────────────────────────────────────

/// Rust-side trampoline that any `lua_pushcfunction`-registered C function
/// is wrapped in. Its upvalue slot 0 holds the C function pointer as a
/// `LightUserdata`. The bridge:
///   1. mirrors the Vm dispatch frame's args into `vm.capi_stack` (so the
///      C callback's `lua_tointeger(L, 1)` etc. resolve to its arguments)
///   2. demotes `&mut Vm` to a raw `*mut Vm`, casts that to `*mut LuaState`
///      (sound because `LuaState` is `#[repr(transparent)] Vm`), and calls
///      the C function
///   3. takes the top `nret` values back off `capi_stack` and returns them
///      via `nat_return`
///
/// Aliasing note: the `&mut Vm` reference is held until just before
/// `cf(L_ptr)`; the raw pointer cast drops the unique-reference invariant.
/// During the C call we do NOT touch `vm` through the (stale) reference —
/// only via the raw pointer the C side now exclusively owns. After the C
/// callback returns we re-borrow from the raw pointer, which is sound
/// because no other live reference exists at that point.
fn capi_trampoline(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
    let cf_value = vm.running_native_upvalue(0);
    let cf: LuaCFunction = match cf_value {
        // SAFETY: source and destination types share the same in-memory representation; see the C ABI typedef this function implements.
        Value::LightUserdata(p) => unsafe { std::mem::transmute::<*const (), LuaCFunction>(p) },
        _ => {
            let s = Value::Str(vm.heap.intern(b"missing C function pointer upvalue"));
            return Err(LuaError(s));
        }
    };
    // Mirror args from the Vm dispatch frame to the C-visible capi_stack
    // (via the public `nat_arg` accessor — works for the missing-arg-is-nil
    // contract too).
    let baseline = vm.capi_stack.len();
    for i in 0..nargs {
        let v = vm.nat_arg(fs, nargs, i);
        vm.capi_stack.push(v);
    }
    // Demote to raw pointer; the &mut Vm is no longer live across the cf call.
    let vm_ptr: *mut Vm = vm as *mut Vm;
    let nret = cf(vm_ptr as *mut LuaState) as usize;
    // Re-borrow.
    // SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
    let vm = unsafe { &mut *vm_ptr };
    let stack_len = vm.capi_stack.len();
    if stack_len < baseline + nret {
        // C function lied about its return count.
        let s = Value::Str(
            vm.heap
                .intern(b"C function returned more values than were pushed"),
        );
        return Err(LuaError(s));
    }
    let results_start = stack_len - nret;
    let results: Vec<Value> = vm.capi_stack[results_start..].to_vec();
    vm.capi_stack.truncate(baseline);
    Ok(vm.nat_return(fs, &results))
}

/// Push a C function as a Lua callable on the stack. The C function receives
/// the calling `LuaState*` and reads its args from positions 1..N on the
/// stack; it must push its results and return the result count (PUC's
/// `lua_pushcfunction`).
// SAFETY: `no_mangle` is required for the C ABI symbol to be linkable as `lua_*` by external C/C++ callers; this crate is the sole producer of these symbols within any final binary that links it.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_pushcfunction(L: *mut LuaState, f: LuaCFunction) {
    // SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
    let vm = unsafe { vm_mut(L) };
    let cf_ptr = f as *const ();
    let trampoline: luna_core::runtime::value::NativeFn = capi_trampoline;
    let f_val = vm.native_with(trampoline, Box::new([Value::LightUserdata(cf_ptr)]));
    vm.capi_stack.push(f_val);
}

/// `lua_register(L, name, f)`: install `f` as the global named `name`
/// (PUC `lua_register`, defined in lua.h as a macro over pushcfunction
/// + setglobal).
// SAFETY: `no_mangle` is required for the C ABI symbol to be linkable as `lua_*` by external C/C++ callers; this crate is the sole producer of these symbols within any final binary that links it.
#[unsafe(no_mangle)]
// SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
pub unsafe extern "C" fn lua_register(L: *mut LuaState, name: *const c_char, f: LuaCFunction) {
    // SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
    unsafe { lua_pushcfunction(L, f) };
    // SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
    unsafe { lua_setglobal(L, name) };
}

// ─── version probe ───────────────────────────────────────────────────────

/// Return the Lua version this state targets (e.g. 505 for 5.5), matching
/// PUC's `LUA_VERSION_NUM` shape.
// SAFETY: `no_mangle` is required for the C ABI symbol to be linkable as `lua_*` by external C/C++ callers; this crate is the sole producer of these symbols within any final binary that links it.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_version(L: *mut LuaState) -> c_int {
    // SAFETY: Lua C API contract — the caller guarantees `L` is a valid `lua_State` pointer that this thread currently owns; pointer/index arguments follow the documented Lua API requirements.
    let vm = unsafe { vm_mut(L) };
    match vm.version() {
        LuaVersion::Lua51 => 501,
        LuaVersion::Lua52 => 502,
        LuaVersion::Lua53 => 503,
        LuaVersion::Lua54 => 504,
        // MacroLua reports the 5.4 base it inherits from (audit-locked).
        LuaVersion::MacroLua => 504,
        LuaVersion::Lua55 => 505,
    }
}