lua_types/closure.rs
1//! `LuaClosure` — the function variant of `LuaValue`. Three sub-kinds:
2//! Lua closure (compiled Proto + upvalues), C closure (function pointer +
3//! upvalues), light C function (function pointer, no upvalues).
4
5use std::cell::{Cell, RefCell};
6
7use crate::gc::GcRef;
8use crate::proto::LuaProto;
9use crate::upval::UpVal;
10use crate::value::LuaValue;
11
12/// Opaque registry index into `GlobalState.c_functions`, where the real
13/// `lua_CFunction` (`fn(&mut LuaState) -> Result<usize, LuaError>`) is stored.
14/// Lua-types can't reference `LuaState` without a circular dep, so we keep
15/// the closure variant type-erased here and resolve through the registry at
16/// call time.
17pub type LuaCFnPtr = usize;
18
19#[derive(Debug, Clone, Copy)]
20pub enum LuaClosure {
21 Lua(GcRef<LuaLClosure>),
22 C(GcRef<LuaCClosure>),
23 LightC(LuaCFnPtr),
24}
25
26#[derive(Debug)]
27pub struct LuaLClosure {
28 pub proto: GcRef<LuaProto>,
29 /// Each upvalue slot is held in a `Cell` so that `debug.upvaluejoin`
30 /// can replace an entry with another closure's slot without rebuilding
31 /// the (shared) closure. `GcRef<UpVal>` is `Copy` (thin wrapper over
32 /// `Gc<UpVal>`), so a plain `Cell` is sufficient and skips RefCell
33 /// borrow tracking on every upvalue read — critical for the
34 /// `upvalue_get` hot path.
35 ///
36 /// The slice is `Box<[_]>`, not `Vec<_>`: the upvalue count is fixed at
37 /// closure creation (`proto.upvalues.len()`) and never grows or shrinks
38 /// afterwards (`set_upval` is a `Cell::set` into an existing slot, not a
39 /// resize). Dropping the `Vec` capacity word shrinks `LuaLClosure` by one
40 /// pointer and makes `buffer_bytes` an exact GC-accounting figure.
41 pub upvals: Box<[Cell<GcRef<UpVal>>]>,
42}
43
44/// `LuaLClosure` is a GC-traced heap object; every byte multiplies across the
45/// live closure population. Switching `upvals` from `Vec` (ptr+len+cap) to
46/// `Box<[_]>` (ptr+len) drops the capacity word, taking the struct from 32 to
47/// 24 bytes on a 64-bit target. Gated to 64-bit because the byte count is a
48/// pointer-width claim (the wasm32 CI build has a 32-bit layout).
49#[cfg(target_pointer_width = "64")]
50const _: () = assert!(std::mem::size_of::<LuaLClosure>() == 24);
51
52#[derive(Debug)]
53pub struct LuaCClosure {
54 pub func: LuaCFnPtr,
55 pub upvalues: RefCell<Vec<LuaValue>>,
56}
57
58impl LuaLClosure {
59 pub fn placeholder() -> Self {
60 LuaLClosure {
61 proto: GcRef::new(LuaProto::placeholder()),
62 upvals: Box::new([]),
63 }
64 }
65
66 /// Returns the upvalue slot at index `i`. Cheap (Copy of a one-pointer
67 /// `GcRef<UpVal>`).
68 #[inline(always)]
69 pub fn upval(&self, i: usize) -> GcRef<UpVal> {
70 self.upvals[i].get()
71 }
72
73 /// Replaces the upvalue slot at index `i` with `new`. Used by
74 /// `debug.upvaluejoin` to share an upvalue between two closures.
75 pub fn set_upval(&self, i: usize, new: GcRef<UpVal>) {
76 self.upvals[i].set(new);
77 }
78
79 /// Bytes owned outside the `GcBox` header/object allocation.
80 ///
81 /// `Box<[_]>` has no separate capacity; `len` is the exact slot count,
82 /// which is also the faithful GC-accounting figure.
83 pub fn buffer_bytes(&self) -> usize {
84 self.upvals.len() * std::mem::size_of::<Cell<GcRef<UpVal>>>()
85 }
86}
87
88impl LuaCClosure {
89 /// Bytes owned outside the `GcBox` header/object allocation.
90 pub fn buffer_bytes(&self) -> usize {
91 self.upvalues.borrow().capacity() * std::mem::size_of::<LuaValue>()
92 }
93}
94
95// ──────────────────────────────────────────────────────────────────────────────
96// PORT STATUS
97// source: src/lobject.h (CClosure / LClosure / Closure union)
98// target_crate: lua-types
99// confidence: high
100// todos: 0
101// port_notes: 0
102// unsafe_blocks: 0
103// notes: LuaClosure enum covering the C-Lua C/LightC/Lua closure variants.
104// C uses a union with a common header; we use a tagged enum.
105// LuaLClosure.upvals uses Cell<GcRef<UpVal>> (not RefCell) so per-
106// upvalue reads avoid borrow-tracking; GcRef<UpVal> is Copy. The
107// upvals slice is Box<[_]> (fixed count, never resized after
108// construction), making LuaLClosure 24 bytes on 64-bit.
109// ──────────────────────────────────────────────────────────────────────────────