Skip to main content

lua_vm/
api.rs

1//
2// PORT NOTE: This is the Rust-native translation of lapi.c.
3// The C-API surface (lua_State *, int stack-index protocol) is replaced by
4// methods on LuaState.  `lua_lock` / `lua_unlock` are dropped (no-op in the
5// single-threaded default build).  `api_incr_top` is dropped; `state.push()`
6// already increments.  Stack pointers (StkId) become StackIdx (u32).
7
8#![allow(dead_code)]
9
10use std::convert::Infallible;
11#[allow(unused_imports)] use crate::prelude::*;
12
13use crate::state::{LuaState, LuaCFunction, GlobalState, CallInfo, CallInfoIdx, StackIdx,
14    LuaValueExt, LuaTypeExt, StackIdxExt,
15    LuaTableRefExt, LuaUserDataRefExt, LuaStringRefExt,
16    LuaLClosureRefExt, LuaClosureExt, LuaProtoExt};
17use lua_types::{
18    LuaValue, LuaType, LuaError, LuaString, LuaUserData, LuaClosure, UpVal,
19    GcRef, LuaStatus,
20};
21use lua_types::value::LuaTable;
22
23pub const LUA_IDENT: &[u8] =
24    b"$LuaVersion: Lua 5.4.7  Copyright (C) 1994-2024 Lua.org, PUC-Rio $\
25      $LuaAuthors: R. Ierusalimschy, L. H. Figueiredo, W. Celes $";
26
27const LUA_REGISTRYINDEX: i32 = -(1_000_000) - 1000;
28
29const LUA_MULTRET: i32 = -1;
30
31const LUA_RIDX_GLOBALS: i64 = 2;
32
33const MAX_UPVAL: u8 = 255;
34
35#[inline]
36fn is_pseudo(idx: i32) -> bool {
37    idx <= LUA_REGISTRYINDEX
38}
39
40#[inline]
41fn is_upvalue(idx: i32) -> bool {
42    idx < LUA_REGISTRYINDEX
43}
44
45// PORT NOTE: In C, the only "invalid" TValue is the global nilvalue singleton
46// pointer returned by index2value when the index is out of range. In Rust we
47// cannot do pointer-equality on a singleton, so validity is decided by whether
48// the index resolves to a real stack/upvalue slot — see `is_valid_index`.
49#[inline]
50fn is_valid_index(state: &LuaState, idx: i32) -> bool {
51    if idx == 0 {
52        return false;
53    }
54    let ci = state.current_call_info();
55    if idx > 0 {
56        let slot = ci.func + idx;
57        slot.0 < state.top_idx().0
58    } else if !is_pseudo(idx) {
59        (-idx) as u32 <= state.top_idx().0.saturating_sub(ci.func.0 + 1)
60    } else if idx == LUA_REGISTRYINDEX {
61        true
62    } else {
63        let upval_n = (LUA_REGISTRYINDEX - idx) as usize;
64        let func_val = state.get_at(ci.func);
65        if let LuaValue::Function(LuaClosure::C(ref ccl)) = func_val {
66            upval_n >= 1 && upval_n <= ccl.upvalues.len()
67        } else {
68            false
69        }
70    }
71}
72
73// ── index helpers ─────────────────────────────────────────────────────────────
74
75// PORT NOTE: In Rust we cannot return a pointer; we return a cloned LuaValue.
76// Writers use a companion index_to_stack_idx() for actual stack slots.
77fn index_to_value(state: &LuaState, idx: i32) -> LuaValue {
78    let ci = state.current_call_info();
79    if idx > 0 {
80        let func_idx = ci.func;
81        let slot = func_idx + idx;
82        debug_assert!(
83            idx as u32 <= ci.top.saturating_sub(func_idx + 1),
84            "unacceptable index"
85        );
86        if slot.0 >= state.top_idx().0 {
87            LuaValue::Nil
88        } else {
89            state.get_at(slot)
90        }
91    } else if !is_pseudo(idx) {
92        // negative index
93        debug_assert!(
94            idx != 0,
95            "invalid index"
96        );
97        let top = state.top_idx();
98        let slot = (top.0 as i32 + idx) as u32;
99        state.get_at(slot)
100    } else if idx == LUA_REGISTRYINDEX {
101        state.registry_value()
102    } else {
103        // upvalues: idx = LUA_REGISTRYINDEX - idx  (idx < LUA_REGISTRYINDEX)
104        let upval_n = (LUA_REGISTRYINDEX - idx) as usize;
105        debug_assert!(upval_n <= MAX_UPVAL as usize + 1, "upvalue index too large");
106        let func_val = state.get_at(ci.func);
107        if let LuaValue::Function(LuaClosure::C(ref ccl)) = func_val {
108            // C closure upvalue
109            if upval_n >= 1 && upval_n <= ccl.upvalues.len() {
110                ccl.upvalues[upval_n - 1].clone()
111            } else {
112                LuaValue::Nil
113            }
114        } else {
115            LuaValue::Nil
116        }
117    }
118}
119
120// Returns a StackIdx for a valid (non-pseudo) actual stack slot.
121#[inline]
122fn index_to_stack_idx(state: &LuaState, idx: i32) -> StackIdx {
123    let ci = state.current_call_info();
124    if idx > 0 {
125        let slot = ci.func + idx;
126        debug_assert!(slot.0 < state.top_idx().0, "invalid index");
127        slot
128    } else {
129        debug_assert!(idx != 0 && !is_pseudo(idx), "invalid index");
130        StackIdx((state.top_idx().0 as i32 + idx) as u32)
131    }
132}
133
134// ── stack manipulation ────────────────────────────────────────────────────────
135
136pub fn check_stack(state: &mut LuaState, n: i32) -> bool {
137    debug_assert!(n >= 0, "negative 'n'");
138    let available = state.stack_available();
139    let res = if available > n as usize {
140        true
141    } else {
142        crate::do_::grow_stack(state, n, false).unwrap_or(false)
143    };
144    if res {
145        let needed_top = state.top_idx() + n as i32;
146        let ci_idx = state.current_ci_idx();
147        if state.get_ci(ci_idx).top.0 < needed_top.0 {
148            let live_top = state.top_idx();
149            state.get_ci_mut(ci_idx).top = needed_top;
150            state.clear_stack_range(live_top, needed_top);
151        }
152    }
153    res
154}
155
156/// Move the top `n` values from `from`'s stack onto `to`'s stack.
157///
158/// Both threads must share the same `GlobalState` (i.e. one is a
159/// coroutine the other created via `coroutine.create`). Calling with
160/// `from` == `to` is a no-op. Equivalent to:
161///
162/// ```text
163/// args = from.stack[top-n..top].clone();
164/// from.set_top(top - n);
165/// for v in args { to.push(v); }
166/// ```
167///
168///
169/// Phase E-3: implemented for the same-`GlobalState` case (the only one
170/// `lua-stdlib` uses today). `lua-vm` callers should prefer this helper
171/// over hand-rolling the snapshot/push dance.
172pub fn xmove(from: &mut LuaState, to: &mut LuaState, n: i32) {
173    if n <= 0 {
174        return;
175    }
176    if std::ptr::eq(from as *const LuaState, to as *const LuaState) {
177        return;
178    }
179    let abs_top = from.top_idx().0 as i32;
180    debug_assert!(abs_top >= n, "lua_xmove: from stack underflow");
181    let first_abs = abs_top - n;
182    let mut buf: Vec<lua_types::LuaValue> = Vec::with_capacity(n as usize);
183    for i in 0..n {
184        let idx = StackIdx((first_abs + i) as u32);
185        buf.push(from.get_at(idx));
186    }
187    from.set_top(StackIdx(first_abs as u32));
188    for v in buf {
189        to.push(v);
190    }
191}
192
193pub fn at_panic(
194    state: &mut LuaState,
195    panicf: Option<fn(&mut LuaState) -> Result<usize, LuaError>>,
196) -> Option<fn(&mut LuaState) -> Result<usize, LuaError>> {
197    let old = state.global_mut().panic;
198    state.global_mut().panic = panicf;
199    old
200}
201
202pub fn version(_state: &LuaState) -> f64 {
203    504.0
204}
205
206pub fn abs_index(state: &LuaState, idx: i32) -> i32 {
207    //          : cast_int(L->top.p - L->ci->func.p) + idx;
208    if idx > 0 || is_pseudo(idx) {
209        idx
210    } else {
211        let ci = state.current_call_info();
212        (state.top_idx().0 as i32 - ci.func.0 as i32) + idx
213    }
214}
215
216pub fn get_top(state: &LuaState) -> i32 {
217    let ci = state.current_call_info();
218    (state.top_idx().0 as i32) - (ci.func.0 as i32 + 1)
219}
220
221pub fn set_top(state: &mut LuaState, idx: i32) -> Result<(), LuaError> {
222    let func = state.current_call_info().func;
223    let ci_top = state.current_call_info().top;
224    if idx >= 0 {
225        debug_assert!(
226            idx as u32 <= ci_top.saturating_sub(func + 1),
227            "new top too large"
228        );
229        let new_top = func + 1 + idx as i32;
230        let old_top = state.top_idx();
231        if new_top.0 > old_top.0 {
232            for i in old_top.0..new_top.0 {
233                state.set_at(i, LuaValue::Nil);
234            }
235        }
236        // TODO(port): to-be-closed variable closing on stack shrink;
237        // luaF_close not yet translated. Skipping close logic for Phase A.
238        state.set_top_idx(new_top);
239    } else {
240        debug_assert!(
241            -(idx + 1) <= (state.top_idx().0 as i32 - (func.0 as i32 + 1)),
242            "invalid new top"
243        );
244        let new_top = (state.top_idx().0 as i32 + idx + 1) as u32;
245        // TODO(port): to-be-closed variable closing on stack shrink (same as above)
246        state.set_top_idx(new_top);
247    }
248    Ok(())
249}
250
251pub fn close_slot(state: &mut LuaState, idx: i32) -> Result<(), LuaError> {
252    let level = index_to_stack_idx(state, idx);
253    // TODO(port): tbc-list check and luaF_close not yet translated.
254    state.set_at(level, LuaValue::Nil);
255    Ok(())
256}
257
258#[inline]
259fn reverse_segment(state: &mut LuaState, from: StackIdx, to: StackIdx) {
260    let mut lo = from.0;
261    let mut hi = to.0;
262    while lo < hi {
263        let temp = state.get_at(StackIdx(lo));
264        let hi_val = state.get_at(StackIdx(hi));
265        state.set_at(StackIdx(lo), hi_val);
266        state.set_at(StackIdx(hi), temp);
267        lo += 1;
268        hi -= 1;
269    }
270}
271
272pub fn rotate(state: &mut LuaState, idx: i32, n: i32) {
273    let t = state.top_idx() - 1;
274    let p = index_to_stack_idx(state, idx);
275    debug_assert!((n.unsigned_abs() as i32) <= ((t.0 as i32) - (p.0 as i32) + 1), "invalid 'n'");
276    let m = if n >= 0 {
277        t - n
278    } else {
279        StackIdx((p.0 as i32 - n - 1) as u32)
280    };
281    reverse_segment(state, p, m);
282    reverse_segment(state, m + 1, t);
283    reverse_segment(state, p, t);
284}
285
286pub fn copy(state: &mut LuaState, fromidx: i32, toidx: i32) {
287    let fr = index_to_value(state, fromidx);
288    if is_upvalue(toidx) {
289        // Writing to a function upvalue pseudo-index
290        let upval_n = (LUA_REGISTRYINDEX - toidx) as usize;
291        let func_val = state.get_at(state.current_call_info().func);
292        if let LuaValue::Function(LuaClosure::C(ref ccl)) = func_val {
293            // TODO(port): CClosure upvalue write requires interior mutability on GcRef<CClosure>
294            // state.gc().barrier(ccl, &fr);
295            let _ = (upval_n, ccl);
296        }
297        // TODO(port): implement upvalue write for copy() to C closure upvalues
298    } else if toidx == LUA_REGISTRYINDEX {
299        // TODO(port): write to registry — needs GlobalState::set_registry(fr)
300    } else {
301        let to_slot = index_to_stack_idx(state, toidx);
302        state.set_at(to_slot, fr);
303    }
304}
305
306pub fn push_value(state: &mut LuaState, idx: i32) {
307    let v = index_to_value(state, idx);
308    state.push(v);
309}
310
311/// Inherent `push_copy` so the `LuaStateStubExt::push_copy` default
312/// `todo!()` no longer fires. Phase-A `state.push_copy(idx)` call-sites
313/// (base.rs, etc.) duplicate the value at `idx` onto the top of the stack —
314/// the same semantics as `lua_pushvalue`.
315impl LuaState {
316    pub fn push_copy(&mut self, idx: i32) -> Result<(), LuaError> {
317        push_value(self, idx);
318        Ok(())
319    }
320
321    pub fn push_value_at(&mut self, idx: i32) -> Result<(), LuaError> {
322        push_value(self, idx);
323        Ok(())
324    }
325
326    pub fn insert(&mut self, idx: i32) -> Result<(), LuaError> {
327        rotate(self, idx, 1);
328        Ok(())
329    }
330
331    /// Inherent `length_at` mirroring `luaL_len` from `lauxlib.c`: push the
332    /// value's length onto the stack (honouring `__len`), pop it as an
333    /// integer, and error if the result is not an integer. Defined on
334    /// `LuaState` so it overrides the `LuaStateStubExt::length_at` trait
335    /// default `todo!()`.
336    pub fn length_at(&mut self, idx: i32) -> Result<i64, LuaError> {
337        len(self, idx)?;
338        let l = match to_integer_x(self, -1) {
339            Some(n) => n,
340            None => {
341                return Err(LuaError::runtime(format_args!(
342                    "object length is not an integer"
343                )));
344            }
345        };
346        self.pop_n(1);
347        Ok(l)
348    }
349
350    /// Write `msg` bytes verbatim to standard output. Mirrors the C macro
351    /// `lua_writestring(s, l) = fwrite(s, 1, l, stdout)` from `lauxlib.h`,
352    /// used by `print` and friends. A failed write is propagated as a
353    /// `LuaError::runtime`; this matches C-Lua's behaviour where an I/O
354    /// error during `lua_writestring` would surface through the host's
355    /// error handling.
356    pub fn write_output(&mut self, msg: &[u8]) -> Result<(), LuaError> {
357        use std::io::Write;
358        let stdout = std::io::stdout();
359        let mut handle = stdout.lock();
360        handle
361            .write_all(msg)
362            .map_err(|e| LuaError::runtime(format_args!("{}", e)))?;
363        handle
364            .flush()
365            .map_err(|e| LuaError::runtime(format_args!("{}", e)))?;
366        Ok(())
367    }
368
369    /// Convert the value at `idx` to a display string, push the result onto
370    /// the stack, and return a copy of its bytes. Mirrors `luaL_tolstring`
371    /// from `lauxlib.c`. The default Lua formatting is used for primitives
372    /// (`"true"`/`"false"`/`"nil"`, `%I` integers, `%.14g` floats); for other
373    /// reference types the result is `"<typename>: 0x<hex pointer>"`.
374    ///
375    /// If the value has a `__tostring` metamethod, it is invoked first and its
376    /// (string) result is used in place of the default formatting (matching
377    pub fn to_display_string(&mut self, idx: i32) -> Result<Vec<u8>, LuaError> {
378        let abs = abs_index(self, idx);
379        let v = index_to_value(self, abs);
380        let mt: Option<GcRef<LuaTable>> = match &v {
381            LuaValue::Table(t) => t.metatable(),
382            LuaValue::UserData(u) => u.metatable(),
383            _ => self.global().mt[v.base_type() as usize].clone(),
384        };
385        if let Some(mt_ref) = mt {
386            let key = self.intern_str(b"__tostring")?;
387            let f = mt_ref.get_short_str(&key);
388            if !matches!(f, LuaValue::Nil) {
389                let func_idx = self.top_idx();
390                self.push(f);
391                self.push(v.clone());
392                if self.current_ci().is_lua_code() {
393                    self.do_call(func_idx, 1)?;
394                } else {
395                    self.do_call_no_yield(func_idx, 1)?;
396                }
397                let top = self.top_idx();
398                let result = self.get_at(StackIdx(top.0 - 1));
399                if let LuaValue::Str(s) = result {
400                    return Ok(s.as_bytes().to_vec());
401                }
402                return Err(LuaError::runtime(format_args!(
403                    "'__tostring' must return a string"
404                )));
405            }
406        }
407        let bytes: Vec<u8> = match &v {
408            LuaValue::Str(s) => {
409                let out = s.as_bytes().to_vec();
410                self.push(LuaValue::Str(s.clone()));
411                out
412            }
413            LuaValue::Int(_) | LuaValue::Float(_) => {
414                let s = crate::object::num_to_string(self, &v)?;
415                let out = s.as_bytes().to_vec();
416                self.push(LuaValue::Str(s));
417                out
418            }
419            LuaValue::Bool(b) => {
420                let lit: &[u8] = if *b { b"true" } else { b"false" };
421                let s = self.intern_str(lit)?;
422                self.push(LuaValue::Str(s));
423                lit.to_vec()
424            }
425            LuaValue::Nil => {
426                let s = self.intern_str(b"nil")?;
427                self.push(LuaValue::Str(s));
428                b"nil".to_vec()
429            }
430            _ => {
431                let kind = crate::tagmethods::obj_type_name(self, &v)?;
432                let ptr = to_pointer(self, abs).unwrap_or(0);
433                let mut buf = kind;
434                buf.extend_from_slice(b": 0x");
435                buf.extend_from_slice(format!("{:x}", ptr).as_bytes());
436                let s = self.intern_str(&buf)?;
437                self.push(LuaValue::Str(s));
438                buf
439            }
440        };
441        Ok(bytes)
442    }
443
444    /// (stack top minus the slot just after the frame's `func`).
445    ///
446    /// Receiver is `&mut self` to match the `LuaStateStubExt::top` trait
447    /// signature exactly; with a different receiver shape (`&self`), Rust's
448    /// method-resolution picks the trait default and the program panics on
449    /// `todo!("phase-b-reconcile: top")`.
450    pub fn top(&mut self) -> i32 {
451        get_top(self)
452    }
453
454    /// `LuaStateStubExt::get_top` trait method. Inherent method shadows the
455    /// trait default so the `todo!("phase-b-reconcile: get_top")` shim never
456    /// fires.
457    pub fn get_top(&mut self) -> i32 {
458        get_top(self)
459    }
460
461    /// stack index `idx`, or `LuaType::None` if `idx` falls outside the
462    /// active call frame. Inherent method shadows the
463    /// `LuaStateStubExt::type_at` trait default so the `todo!()` shim
464    /// never fires.
465    pub fn type_at(&mut self, idx: i32) -> LuaType {
466        lua_type_at(self, idx)
467    }
468
469    /// #N (value expected)` error if the slot at `arg` is `LUA_TNONE`
470    /// (i.e. beyond the active call frame's top). Otherwise a no-op.
471    ///
472    /// Inherent method on LuaState shadows the `LuaStateStubExt::check_arg_any`
473    /// trait default so the `todo!()` shim never fires.
474    pub fn check_arg_any(&mut self, arg: i32) -> Result<(), LuaError> {
475        if lua_type_at(self, arg) == LuaType::None {
476            return Err(LuaError::arg_error(arg, "value expected"));
477        }
478        Ok(())
479    }
480
481    /// at `arg` to a string via `lua_tolstring` (which coerces numbers to
482    /// their string form) and returns the bytes. Raises
483    /// `bad argument #N (string expected, got <type>)` if the value is not a
484    /// string and not number-coercible.
485    ///
486    /// Inherent method on LuaState shadows the `LuaStateStubExt::check_arg_string`
487    /// trait default so the `todo!()` shim never fires. Uses the free `to_lua_string`
488    /// helper here rather than `auxlib::check_lstring`, which routes through
489    /// `state.to_lua_string` / `state.type_name` — both still trait stubs.
490    pub fn check_arg_string(&mut self, arg: i32) -> Result<Vec<u8>, LuaError> {
491        match to_lua_string(self, arg)? {
492            Some(s) => Ok(s.as_bytes().to_vec()),
493            None => {
494                let got = index_to_value(self, arg);
495                let got_name = crate::tagmethods::obj_type_name(self, &got)?;
496                let extramsg = format!(
497                    "string expected, got {}",
498                    String::from_utf8_lossy(&got_name)
499                );
500                Err(crate::debug::arg_error_impl(self, arg, extramsg.as_bytes()))
501            }
502        }
503    }
504
505    /// `arg` to a `lua_Integer` (i64) via `lua_tointegerx` (which accepts
506    /// ints, floats with exact integer value, and string-form integers).
507    /// Raises `bad argument #N (number has no integer representation)` if
508    /// the value is a number but not representable as an integer, or
509    /// `bad argument #N (number expected, got <type>)` otherwise.
510    ///
511    /// Inherent method on LuaState shadows the `LuaStateStubExt::check_arg_integer`
512    /// trait default so the `todo!()` shim never fires. Uses the free
513    /// `to_integer_x` / `is_number` helpers in this file rather than
514    /// `auxlib::check_integer`, which routes through `state.to_integer_x`
515    /// and `state.type_name` — both still trait stubs.
516    pub fn check_arg_integer(&mut self, arg: i32) -> Result<i64, LuaError> {
517        match to_integer_x(self, arg) {
518            Some(d) => Ok(d),
519            None => {
520                if is_number(self, arg) {
521                    Err(LuaError::arg_error(
522                        arg,
523                        "number has no integer representation",
524                    ))
525                } else {
526                    let got = index_to_value(self, arg);
527                    let got_name = crate::tagmethods::obj_type_name(self, &got)?;
528                    let extramsg = format!(
529                        "number expected, got {}",
530                        String::from_utf8_lossy(&got_name)
531                    );
532                    Err(crate::debug::arg_error_impl(self, arg, extramsg.as_bytes()))
533                }
534            }
535        }
536    }
537
538    /// `arg` to an `f64` via `lua_tonumberx` (which accepts ints, floats,
539    /// and number-shaped strings) and raises `bad argument #N (number
540    /// expected, got <type>)` if the value is not number-coercible.
541    ///
542    /// Inherent method on LuaState shadows the `LuaStateStubExt::check_number`
543    /// trait default so the `todo!()` shim never fires. Uses the free
544    /// `to_number_x` helper here rather than `auxlib::check_number`, which
545    /// routes through `state.to_number_x` and `state.type_name` — both still
546    /// trait stubs.
547    pub fn check_number(&mut self, arg: i32) -> Result<f64, LuaError> {
548        match to_number_x(self, arg) {
549            Some(d) => Ok(d),
550            None => {
551                let got = index_to_value(self, arg);
552                let got_name = crate::tagmethods::obj_type_name(self, &got)?;
553                let extramsg = format!(
554                    "number expected, got {}",
555                    String::from_utf8_lossy(&got_name)
556                );
557                Err(crate::debug::arg_error_impl(self, arg, extramsg.as_bytes()))
558            }
559        }
560    }
561
562    /// `arg` is absent (`LUA_TNONE`) or `nil`, return `def`; otherwise
563    /// convert it to an integer (with the same string-to-number coercion
564    /// `lua_tointegerx` applies) and raise on failure.
565    ///
566    /// Inherent method on LuaState shadows the `LuaStateStubExt::opt_arg_integer`
567    /// trait default so the `todo!()` shim never fires. Implemented with the
568    /// free-function helpers in this file rather than `auxlib::opt_integer`
569    /// because the latter routes through `state.is_none_or_nil` and
570    /// `state.to_integer_x`, which are themselves stubbed.
571    pub fn opt_arg_integer(&mut self, arg: i32, def: i64) -> Result<i64, LuaError> {
572        match lua_type_at(self, arg) {
573            LuaType::None | LuaType::Nil => Ok(def),
574            _ => match to_integer_x(self, arg) {
575                Some(d) => Ok(d),
576                None => {
577                    if is_number(self, arg) {
578                        Err(LuaError::arg_error(
579                            arg,
580                            "number has no integer representation",
581                        ))
582                    } else {
583                        let got = index_to_value(self, arg);
584                        Err(LuaError::type_arg_error(arg, "number", &got))
585                    }
586                }
587            },
588        }
589    }
590
591    /// `lua_pcallk` with no continuation. Defers to the existing `pcall_k`
592    /// free function, which routes through `protected_call_raw` and
593    /// surfaces any runtime / syntax error as `Err(LuaError::Runtime|Syntax)`.
594    ///
595    /// Inherent method on LuaState shadows the `LuaStateStubExt::protected_call`
596    /// trait default so the `todo!()` shim never fires.
597    pub fn protected_call(&mut self, nargs: i32, nresults: i32, msgh: i32) -> Result<(), LuaError> {
598        pcall_k(self, nargs, nresults, msgh, 0, None).map(|_| ())
599    }
600
601    /// protected call. When `k` is set and the thread is yieldable, an
602    /// inner yield propagates as `LuaError::Yield` and the continuation
603    /// fires on resume via `finishCcall` → `finishpcallk`.
604    pub fn protected_call_k(
605        &mut self,
606        nargs: i32,
607        nresults: i32,
608        msgh: i32,
609        ctx: isize,
610        k: Option<crate::state::LuaKFunction>,
611    ) -> Result<(), LuaError> {
612        pcall_k(self, nargs, nresults, msgh, ctx, k).map(|_| ())
613    }
614
615    pub fn push_string(&mut self, s: &[u8]) -> Result<(), LuaError> {
616        push_lstring(self, s)?;
617        Ok(())
618    }
619
620    pub fn push_c_closure(
621        &mut self,
622        f: fn(&mut LuaState) -> Result<usize, LuaError>,
623        n: i32,
624    ) -> Result<(), LuaError> {
625        push_cclosure(self, f, n)
626    }
627
628    pub fn raw_seti(&mut self, idx: i32, n: i64) -> Result<(), LuaError> {
629        raw_set_i(self, idx, n)
630    }
631
632    pub fn table_set_i(&mut self, idx: i32, n: i64) -> Result<(), LuaError> {
633        set_i(self, idx, n)
634    }
635
636    /// Get `t[n]` where `t` is a pre-resolved `LuaValue`, bypassing stack-index
637    /// resolution. Use this in tight loops that operate on the same table
638    /// repeatedly to avoid the `index_to_value` call per iteration.
639    pub fn table_get_i_value(&mut self, t: &LuaValue, n: i64) -> Result<LuaType, LuaError> {
640        get_i_value(self, t, n)
641    }
642
643    /// Set `t[n] = stack_top` (then pop) where `t` is a pre-resolved `LuaValue`,
644    /// bypassing stack-index resolution. Use this in tight loops that operate on
645    /// the same table repeatedly to avoid the `index_to_value` call per iteration.
646    pub fn table_set_i_value(&mut self, t: &LuaValue, n: i64) -> Result<(), LuaError> {
647        set_i_value(self, t, n)
648    }
649
650    pub fn create_table(&mut self, narr: i32, nrec: i32) -> Result<(), LuaError> {
651        create_table(self, narr, nrec)
652    }
653
654    /// Pop the value on top of the stack and store it in the registry under
655    /// the string `key`.
656    ///
657    pub fn registry_set(&mut self, key: &[u8]) -> Result<(), LuaError> {
658        set_field(self, LUA_REGISTRYINDEX, key)
659    }
660
661    /// Create a new metatable in the registry under key `tname`. Leaves the
662    /// new metatable on top of the stack and returns `true` when newly
663    /// created. If `registry[tname]` already exists, leaves it on top of the
664    /// stack and returns `false`.
665    ///
666    pub fn new_metatable(&mut self, tname: &[u8]) -> Result<bool, LuaError> {
667        if get_field(self, LUA_REGISTRYINDEX, tname)? != LuaType::Nil {
668            return Ok(false);
669        }
670        self.pop_n(1);
671        create_table(self, 0, 2)?;
672        push_lstring(self, tname)?;
673        set_field(self, -2, b"__name")?;
674        push_value(self, -1);
675        set_field(self, LUA_REGISTRYINDEX, tname)?;
676        Ok(true)
677    }
678
679    /// Create a new library table sized for `funcs` and register each entry as
680    /// a closure field on it. Leaves the table on the top of the stack.
681    ///
682    ///   `luaL_checkversion(L), luaL_newlibtable(L,l), luaL_setfuncs(L,l,0)`.
683    /// `luaL_checkversion` is a no-op here (no ABI-version mismatch is
684    /// possible inside the Rust port).
685    pub fn new_lib(
686        &mut self,
687        funcs: &[(&[u8], LuaCFunction)],
688    ) -> Result<(), LuaError> {
689        create_table(self, 0, funcs.len() as i32)?;
690        for (name, f) in funcs {
691            push_cclosure(self, *f, 0)?;
692            set_field(self, -2, name)?;
693        }
694        Ok(())
695    }
696
697    /// Create and populate a library table for `funcs`, leaving it on top of
698    /// the stack. The `_name` argument is informational and matches the
699    /// `luaL_register`-style call sites in the Phase-A stdlib; the actual
700    /// global binding for the library happens later via `luaL_requiref`.
701    ///
702    pub fn register_lib(
703        &mut self,
704        _name: &[u8],
705        funcs: &[(&[u8], LuaCFunction)],
706    ) -> Result<(), LuaError> {
707        self.new_lib(funcs)
708    }
709
710    /// Create a new empty table presized to hold every entry in `funcs`, and
711    /// leave it on top of the stack. No registration is performed — callers
712    /// typically follow up with `set_funcs` / `set_funcs_with_upvalues` to
713    /// populate the table.
714    ///
715    ///   `lua_createtable(L, 0, sizeof(l)/sizeof((l)[0]) - 1)`. The C macro's
716    /// `- 1` discounts the sentinel `{NULL, NULL}` entry; the Rust slice has
717    /// no sentinel, so we use `funcs.len()` directly.
718    pub fn new_lib_table(
719        &mut self,
720        funcs: &[(&[u8], LuaCFunction)],
721    ) -> Result<(), LuaError> {
722        create_table(self, 0, funcs.len() as i32)
723    }
724
725    /// Register each entry in `funcs` as a C closure on the table at index
726    /// `-(nup + 2)`, sharing the `nup` values currently on top of the stack
727    /// as upvalues. The upvalues are popped at the end.
728    ///
729    pub fn set_funcs_with_upvalues(
730        &mut self,
731        funcs: &[(&[u8], LuaCFunction)],
732        nup: i32,
733    ) -> Result<(), LuaError> {
734        check_stack(self, nup);
735        for (name, f) in funcs {
736            for _ in 0..nup {
737                push_value(self, -nup);
738            }
739            push_cclosure(self, *f, nup)?;
740            set_field(self, -(nup + 2), name)?;
741        }
742        self.pop_n(nup as usize);
743        Ok(())
744    }
745
746    pub fn set_metatable(&mut self, objindex: i32) -> Result<(), LuaError> {
747        set_metatable(self, objindex)?;
748        Ok(())
749    }
750
751    /// Fetch the metatable registered under `name` in the registry and assign
752    /// it as the metatable of the value currently on top of the stack. The
753    /// fetched metatable is popped after assignment, leaving the original top
754    /// value in place.
755    ///
756    pub fn set_metatable_by_name(&mut self, name: &[u8]) -> Result<(), LuaError> {
757        get_field(self, LUA_REGISTRYINDEX, name)?;
758        set_metatable(self, -2)?;
759        Ok(())
760    }
761
762    /// Ensure `registry[name]` is a table; push it onto the stack.
763    /// Returns `true` if the table already existed, `false` if newly created.
764    ///
765    pub fn get_subtable_registry(&mut self, name: &[u8]) -> Result<bool, LuaError> {
766        if get_field(self, LUA_REGISTRYINDEX, name)? == LuaType::Table {
767            return Ok(true);
768        }
769        self.pop_n(1);
770        let idx = abs_index(self, LUA_REGISTRYINDEX);
771        let new_tbl = self.new_table();
772        self.push(LuaValue::Table(new_tbl));
773        push_value(self, -1);
774        set_field(self, idx, name)?;
775        Ok(false)
776    }
777
778    /// Allocate a fresh full-userdata block of `size` bytes with `nuvalue`
779    /// nil-initialised user-value slots, push it on the stack, and return a
780    /// `GcRef` to it. The `_name` parameter is advisory — callers typically
781    /// follow up with `set_metatable_by_name(name)` to attach the registered
782    /// metatable.
783    ///
784    /// C-correspondent: `lua_newuserdatauv(L, size, nuvalue)` (no name
785    /// parameter on the C side; the Rust signature carries it for callers'
786    /// convenience).
787    pub fn new_userdata_typed(
788        &mut self,
789        _name: &[u8],
790        size: usize,
791        nuvalue: i32,
792    ) -> Result<GcRef<LuaUserData>, LuaError> {
793        debug_assert!(nuvalue >= 0 && nuvalue < u16::MAX as i32, "invalid value");
794        // TODO(D-1c-bridge): state.new_userdata is still todo!(); keep direct alloc
795        let u = GcRef::new(LuaUserData {
796            data: vec![0u8; size].into_boxed_slice(),
797            uv: vec![LuaValue::Nil; nuvalue as usize],
798            metatable: std::cell::RefCell::new(None),
799        });
800        self.push(LuaValue::UserData(u.clone()));
801        self.gc().check_step();
802        Ok(u)
803    }
804}
805
806// ── access functions (stack → Rust) ──────────────────────────────────────────
807
808pub fn lua_type_at(state: &LuaState, idx: i32) -> LuaType {
809    if !is_valid_index(state, idx) {
810        return LuaType::None;
811    }
812    index_to_value(state, idx).base_type()
813}
814
815pub fn type_name(_state: &LuaState, t: LuaType) -> &'static [u8] {
816    t.type_name()
817}
818
819pub fn is_cfunction(state: &LuaState, idx: i32) -> bool {
820    let o = index_to_value(state, idx);
821    matches!(o, LuaValue::Function(LuaClosure::LightC(_)) | LuaValue::Function(LuaClosure::C(_)))
822}
823
824pub fn is_integer(state: &LuaState, idx: i32) -> bool {
825    let o = index_to_value(state, idx);
826    matches!(o, LuaValue::Int(_))
827}
828
829pub fn is_number(state: &LuaState, idx: i32) -> bool {
830    let o = index_to_value(state, idx);
831    o.to_number_with_strconv().is_some()
832}
833
834pub fn is_string(state: &LuaState, idx: i32) -> bool {
835    let o = index_to_value(state, idx);
836    matches!(o, LuaValue::Str(_) | LuaValue::Int(_) | LuaValue::Float(_))
837}
838
839pub fn is_userdata(state: &LuaState, idx: i32) -> bool {
840    let o = index_to_value(state, idx);
841    matches!(o, LuaValue::UserData(_) | LuaValue::LightUserData(_))
842}
843
844pub fn raw_equal(state: &LuaState, index1: i32, index2: i32) -> bool {
845    if !is_valid_index(state, index1) || !is_valid_index(state, index2) {
846        return false;
847    }
848    let o1 = index_to_value(state, index1);
849    let o2 = index_to_value(state, index2);
850    state.equal_obj(None, &o1, &o2)
851}
852
853// PORT NOTE: LUA_OPUNM / LUA_OPBNOT are unary; all others are binary.
854pub fn arith(state: &mut LuaState, op: i32) -> Result<(), LuaError> {
855    // TODO(port): LUA_OPUNM and LUA_OPBNOT constant values not yet defined in
856    // Rust; using raw i32 comparison for now.
857    const LUA_OPUNM: i32 = 12;
858    const LUA_OPBNOT: i32 = 14;
859    if op == LUA_OPUNM || op == LUA_OPBNOT {
860        // unary — duplicate top as fake second operand
861        let top_val = state.get_at(state.top_idx() - 1);
862        state.push(top_val);
863    }
864    let top = state.top_idx();
865    let a = state.get_at(top - 2);
866    let b = state.get_at(top - 1);
867    let result = state.arith_op(op, &a, &b)?;
868    state.set_at(top - 2, result);
869    state.pop();
870    Ok(())
871}
872
873pub fn compare(state: &mut LuaState, index1: i32, index2: i32, op: i32) -> Result<bool, LuaError> {
874    let valid = is_valid_index(state, index1) && is_valid_index(state, index2);
875    let o1 = index_to_value(state, index1);
876    let o2 = index_to_value(state, index2);
877    if valid {
878        match op {
879            0 => Ok(state.equal_obj_with_tm(&o1, &o2)?),
880            1 => state.less_than(&o1, &o2),
881            2 => state.less_equal(&o1, &o2),
882            _ => {
883                debug_assert!(false, "invalid option");
884                Ok(false)
885            }
886        }
887    } else {
888        Ok(false)
889    }
890}
891
892pub fn string_to_number(state: &mut LuaState, s: &[u8]) -> usize {
893    // TODO(port): luaO_str2num not yet translated; push result if successful.
894    match state.str_to_num(s) {
895        Some((val, consumed)) => {
896            state.push(val);
897            consumed
898        }
899        None => 0,
900    }
901}
902
903pub fn to_number_x(state: &LuaState, idx: i32) -> Option<f64> {
904    let o = index_to_value(state, idx);
905    o.to_number_with_strconv()
906}
907
908pub fn to_integer_x(state: &LuaState, idx: i32) -> Option<i64> {
909    let o = index_to_value(state, idx);
910    o.to_integer_with_strconv()
911}
912
913pub fn to_boolean(state: &LuaState, idx: i32) -> bool {
914    let o = index_to_value(state, idx);
915    !matches!(o, LuaValue::Nil | LuaValue::Bool(false))
916}
917
918// PORT NOTE: returns Option<GcRef<LuaString>> instead of raw C pointer+len.
919pub fn to_lua_string(
920    state: &mut LuaState,
921    idx: i32,
922) -> Result<Option<GcRef<LuaString>>, LuaError> {
923    let o = index_to_value(state, idx);
924    if let LuaValue::Str(s) = &o {
925        return Ok(Some(s.clone()));
926    }
927    if !matches!(o, LuaValue::Int(_) | LuaValue::Float(_)) {
928        return Ok(None);
929    }
930    state.obj_to_string(idx)?;
931    state.gc().check_step();
932    let updated = index_to_value(state, idx);
933    if let LuaValue::Str(s) = updated {
934        Ok(Some(s))
935    } else {
936        Ok(None)
937    }
938}
939
940pub fn raw_len(state: &LuaState, idx: i32) -> u64 {
941    let o = index_to_value(state, idx);
942    match &o {
943        LuaValue::Str(s) => s.len() as u64,
944        LuaValue::UserData(u) => u.len() as u64,
945        LuaValue::Table(t) => state.table_getn(t) as u64,
946        _ => 0,
947    }
948}
949
950pub fn to_cfunction(
951    state: &LuaState,
952    idx: i32,
953) -> Option<fn(&mut LuaState) -> Result<usize, LuaError>> {
954    let o = index_to_value(state, idx);
955    match o {
956        // TODO(phase-b): lua-types `LuaClosure::LightC` carries a placeholder
957        // `fn() -> i32` until it can reference `LuaState`. The real cast
958        // happens once lua-types absorbs the LuaState-aware signature.
959        LuaValue::Function(LuaClosure::LightC(_f)) => None,
960        LuaValue::Function(LuaClosure::C(_ccl)) => None,
961        _ => None,
962    }
963}
964
965#[inline]
966fn to_userdata_ptr(o: &LuaValue) -> Option<*mut core::ffi::c_void> {
967    match o {
968        LuaValue::UserData(u) => {
969            // TODO(port): getudatamem returns a pointer to the raw byte payload of Udata.
970            // In Rust, LuaUserData carries a Box<[u8]>; we'd need to return a raw ptr.
971            // This is only safe inside lua-gc; stubbing with None for Phase A.
972            let _ = u;
973            None
974        }
975        LuaValue::LightUserData(p) => Some(*p),
976        _ => None,
977    }
978}
979
980pub fn to_userdata(state: &LuaState, idx: i32) -> Option<*mut core::ffi::c_void> {
981    let o = index_to_value(state, idx);
982    to_userdata_ptr(&o)
983}
984
985pub fn to_thread(state: &LuaState, idx: i32) -> Option<GcRef<lua_types::value::LuaThread>> {
986    // TODO(phase-b): lua-vm's rich LuaState is not the same type as
987    // lua_types::value::LuaThread; the latter is a placeholder. Resolve in
988    // Phase B by unifying thread types.
989    let o = index_to_value(state, idx);
990    if let LuaValue::Thread(t) = o {
991        Some(t)
992    } else {
993        None
994    }
995}
996
997// PORT NOTE: returns a usize (opaque identity) rather than a raw void*.
998// Raw pointers are only allowed in lua-gc / lua-coro.
999pub fn to_pointer(state: &LuaState, idx: i32) -> Option<usize> {
1000    let o = index_to_value(state, idx);
1001    // TODO(port): returning a raw pointer here is not safe outside lua-gc.
1002    // Returning the GC identity as a usize for opaque pointer identity purposes.
1003    match &o {
1004        LuaValue::Function(LuaClosure::LightC(f)) => Some(*f as usize),
1005        LuaValue::LightUserData(p) => Some(*p as usize),
1006        LuaValue::Str(s) => Some(GcRef::identity(s)),
1007        LuaValue::Table(t) => Some(GcRef::identity(t)),
1008        LuaValue::Function(LuaClosure::Lua(f)) => Some(GcRef::identity(f)),
1009        LuaValue::Function(LuaClosure::C(f)) => Some(GcRef::identity(f)),
1010        LuaValue::UserData(u) => Some(GcRef::identity(u)),
1011        LuaValue::Thread(t) => Some(GcRef::identity(t)),
1012        _ => None,
1013    }
1014}
1015
1016// ── push functions (Rust → stack) ────────────────────────────────────────────
1017
1018pub fn push_nil(state: &mut LuaState) {
1019    state.push(LuaValue::Nil);
1020}
1021
1022pub fn push_number(state: &mut LuaState, n: f64) {
1023    state.push(LuaValue::Float(n));
1024}
1025
1026pub fn push_integer(state: &mut LuaState, n: i64) {
1027    state.push(LuaValue::Int(n));
1028}
1029
1030// PORT NOTE: returns the interned LuaString instead of a raw C pointer.
1031pub fn push_lstring(state: &mut LuaState, s: &[u8]) -> Result<GcRef<LuaString>, LuaError> {
1032    let ts = state.intern_str(s)?;
1033    state.push(LuaValue::Str(ts.clone()));
1034    state.gc().check_step();
1035    Ok(ts)
1036}
1037
1038pub fn push_string(state: &mut LuaState, s: Option<&[u8]>) -> Result<Option<GcRef<LuaString>>, LuaError> {
1039    match s {
1040        None => {
1041            state.push(LuaValue::Nil);
1042            state.gc().check_step();
1043            Ok(None)
1044        }
1045        Some(bytes) => {
1046            let ts = state.intern_str(bytes)?;
1047            state.push(LuaValue::Str(ts.clone()));
1048            state.gc().check_step();
1049            Ok(Some(ts))
1050        }
1051    }
1052}
1053
1054// PORT NOTE: va_list is not representable in safe Rust; callers pass a pre-formatted &[u8].
1055// TODO(port): lua_pushvfstring uses C varargs (va_list); no direct Rust equivalent.
1056// The Rust API uses state.push_fstring(format_args!(...)) instead.
1057pub fn push_vfstring(state: &mut LuaState, formatted: &[u8]) -> Result<GcRef<LuaString>, LuaError> {
1058    let ts = state.intern_str(formatted)?;
1059    state.push(LuaValue::Str(ts.clone()));
1060    state.gc().check_step();
1061    Ok(ts)
1062}
1063
1064// PORT NOTE: C varargs not used; callers use format_args! and push_fstring.
1065pub fn push_fstring(state: &mut LuaState, formatted: &[u8]) -> Result<GcRef<LuaString>, LuaError> {
1066    push_vfstring(state, formatted)
1067}
1068
1069pub fn push_cclosure(
1070    state: &mut LuaState,
1071    f: fn(&mut LuaState) -> Result<usize, LuaError>,
1072    n: i32,
1073) -> Result<(), LuaError> {
1074    //    if (n == 0) { setfvalue(s2v(L->top.p), fn); api_incr_top(L); }
1075    //    else { api_checknelems(L, n); api_check(L, n <= MAXUPVAL, ...);
1076    //           cl = luaF_newCclosure(L, n); cl->f = fn;
1077    //           L->top.p -= n;
1078    //           while (n--) setobj2n(L, &cl->upvalue[n], s2v(L->top.p + n));
1079    //           setclCvalue(L, s2v(L->top.p), cl); api_incr_top(L);
1080    //           luaC_checkGC(L); }
1081    //    lua_unlock(L);
1082    //
1083    // PORT NOTE: `LuaClosure::LightC` and `LuaCClosure` carry a `LuaCFnPtr`
1084    // (a `usize` index into `GlobalState.c_functions`) rather than the raw
1085    // function pointer, because lua-types cannot reference `LuaState`. We
1086    // register `f` in the per-state registry and store the resulting index.
1087    let idx: lua_types::closure::LuaCFnPtr = {
1088        let mut g = state.global_mut();
1089        if n == 0 {
1090            match g.c_functions.iter().position(|&existing| existing == f) {
1091                Some(i) => i,
1092                None => {
1093                    let i = g.c_functions.len();
1094                    g.c_functions.push(f);
1095                    i
1096                }
1097            }
1098        } else {
1099            let i = g.c_functions.len();
1100            g.c_functions.push(f);
1101            i
1102        }
1103    };
1104    if n == 0 {
1105        state.push(LuaValue::Function(LuaClosure::LightC(idx)));
1106    } else {
1107        debug_assert!(n > 0 && (n as u32) <= MAX_UPVAL as u32, "upvalue index too large");
1108        let n_usize = n as usize;
1109        let top = state.top_idx();
1110        debug_assert!((top.0 as usize) >= n_usize, "not enough elements on stack");
1111        let base = top.0 as usize - n_usize;
1112        let mut upvalues: Vec<LuaValue> = Vec::with_capacity(n_usize);
1113        for i in 0..n_usize {
1114            upvalues.push(state.get_at(crate::state::StackIdx((base + i) as u32)));
1115        }
1116        state.pop_n(n_usize);
1117        // TODO(D-1c-bridge): state.new_c_closure is still todo!(); keep direct alloc
1118        let cl = LuaClosure::C(GcRef::new(lua_types::closure::LuaCClosure {
1119            func: idx,
1120            upvalues,
1121        }));
1122        state.push(LuaValue::Function(cl));
1123        state.gc().check_step();
1124    }
1125    Ok(())
1126}
1127
1128pub fn push_boolean(state: &mut LuaState, b: bool) {
1129    state.push(LuaValue::Bool(b));
1130}
1131
1132pub fn push_light_userdata(state: &mut LuaState, p: *mut core::ffi::c_void) {
1133    state.push(LuaValue::LightUserData(p));
1134}
1135
1136// Returns true if pushed thread is the main thread.
1137pub fn push_thread(state: &mut LuaState) -> bool {
1138    let (value, is_main) = {
1139        let g = state.global();
1140        let id = g.current_thread_id;
1141        let v = g
1142            .thread_value_for(id)
1143            .expect("current_thread_id must always resolve to a registered thread");
1144        (v, id == g.main_thread_id)
1145    };
1146    state.push(LuaValue::Thread(value));
1147    is_main
1148}
1149
1150// ── get functions (Lua → stack) ───────────────────────────────────────────────
1151
1152fn aux_get_str(state: &mut LuaState, t: LuaValue, k: &[u8]) -> Result<LuaType, LuaError> {
1153    let str_val = {
1154        let ts = state.intern_str(k)?;
1155        LuaValue::Str(ts)
1156    };
1157    // TODO(port): luaV_fastget / luaV_finishget not yet translated; using
1158    // a simplified table_get that may miss metamethod chains.
1159    let result = state.table_get_with_tm(&t, &str_val)?;
1160    state.push(result);
1161    let top = state.top_idx();
1162    Ok(state.get_at(top - 1).base_type())
1163}
1164
1165fn get_global_table(state: &LuaState) -> LuaValue {
1166    // PORT NOTE (phase-b-reconcile): The lua-types LuaTable placeholder has
1167    // no storage, so we cannot fetch the globals table from the registry's
1168    // array slot. init_registry now stashes globals in a direct
1169    // GlobalState field; read it from there until the LuaTable placeholder
1170    // reconciles with lua-vm::table::LuaTable.
1171    state.global().globals.clone()
1172}
1173
1174pub fn get_global(state: &mut LuaState, name: &[u8]) -> Result<LuaType, LuaError> {
1175    let g = get_global_table(state);
1176    aux_get_str(state, g, name)
1177}
1178
1179pub fn get_table(state: &mut LuaState, idx: i32) -> Result<LuaType, LuaError> {
1180    let t = index_to_value(state, idx);
1181    let top = state.top_idx();
1182    let key = state.get_at(top - 1);
1183    let result = state.table_get_with_tm(&t, &key)?;
1184    state.set_at(top - 1, result);
1185    let val = state.get_at(top - 1);
1186    Ok(val.base_type())
1187}
1188
1189pub fn get_field(state: &mut LuaState, idx: i32, k: &[u8]) -> Result<LuaType, LuaError> {
1190    let t = index_to_value(state, idx);
1191    aux_get_str(state, t, k)
1192}
1193
1194pub fn get_i(state: &mut LuaState, idx: i32, n: i64) -> Result<LuaType, LuaError> {
1195    let t = index_to_value(state, idx);
1196    let key = LuaValue::Int(n);
1197    let result = state.table_get_with_tm(&t, &key)?;
1198    state.push(result);
1199    let top = state.top_idx();
1200    Ok(state.get_at(top - 1).base_type())
1201}
1202
1203/// Variant of `get_i` that accepts a pre-resolved table value instead of a
1204/// stack index. Callers that invoke `get_i` repeatedly on the same table
1205/// (e.g. the shift loops in `table.remove` / `table.insert`) should resolve
1206/// the table once and use this function to avoid calling `index_to_value`
1207/// on every iteration.
1208pub fn get_i_value(state: &mut LuaState, t: &LuaValue, n: i64) -> Result<LuaType, LuaError> {
1209    let key = LuaValue::Int(n);
1210    let result = state.table_get_with_tm(t, &key)?;
1211    state.push(result);
1212    let top = state.top_idx();
1213    Ok(state.get_at(top - 1).base_type())
1214}
1215
1216fn finish_raw_get(state: &mut LuaState, val: Option<LuaValue>) -> LuaType {
1217    let v = val.unwrap_or(LuaValue::Nil);
1218    state.push(v);
1219    let top = state.top_idx();
1220    state.get_at(top - 1).base_type()
1221}
1222
1223fn get_table_value(state: &LuaState, idx: i32) -> Option<GcRef<LuaTable>> {
1224    let t = index_to_value(state, idx);
1225    debug_assert!(matches!(t, LuaValue::Table(_)), "table expected");
1226    if let LuaValue::Table(tbl) = t {
1227        Some(tbl)
1228    } else {
1229        None
1230    }
1231}
1232
1233pub fn raw_get(state: &mut LuaState, idx: i32) -> LuaType {
1234    let t = get_table_value(state, idx);
1235    let top = state.top_idx();
1236    let key = state.get_at(top - 1);
1237    let val = t.as_ref().map(|tbl| tbl.get(&key));
1238    state.set_top_idx(top - 1);
1239    finish_raw_get(state, val)
1240}
1241
1242pub fn raw_get_i(state: &mut LuaState, idx: i32, n: i64) -> LuaType {
1243    let t = get_table_value(state, idx);
1244    let val = t.as_ref().map(|tbl| tbl.get_int(n));
1245    finish_raw_get(state, val)
1246}
1247
1248pub fn raw_get_p(state: &mut LuaState, idx: i32, p: *const core::ffi::c_void) -> LuaType {
1249    let t = get_table_value(state, idx);
1250    let key = LuaValue::LightUserData(p as *mut core::ffi::c_void);
1251    let val = t.as_ref().map(|tbl| tbl.get(&key));
1252    finish_raw_get(state, val)
1253}
1254
1255pub fn create_table(state: &mut LuaState, narray: i32, nrec: i32) -> Result<(), LuaError> {
1256    let t = state.new_table();
1257    if narray > 0 || nrec > 0 {
1258        t.resize(state, narray as usize, nrec as usize)?;
1259    }
1260    state.push(LuaValue::Table(t));
1261    state.gc().check_step();
1262    Ok(())
1263}
1264
1265pub fn get_metatable(state: &mut LuaState, objindex: i32) -> bool {
1266    let obj = index_to_value(state, objindex);
1267    let mt: Option<GcRef<LuaTable>> = match &obj {
1268        LuaValue::Table(t) => t.metatable(),
1269        LuaValue::UserData(u) => u.metatable(),
1270        other => {
1271            let idx = other.base_type() as usize;
1272            state.global().mt[idx].clone()
1273        }
1274    };
1275    if let Some(mt_table) = mt {
1276        state.push(LuaValue::Table(mt_table));
1277        true
1278    } else {
1279        false
1280    }
1281}
1282
1283pub fn get_i_uservalue(state: &mut LuaState, idx: i32, n: i32) -> LuaType {
1284    let o = index_to_value(state, idx);
1285    debug_assert!(matches!(o, LuaValue::UserData(_)), "full userdata expected");
1286    if let LuaValue::UserData(ref u) = o {
1287        let uv_count = u.uv.len() as i32;
1288        if n <= 0 || n > uv_count {
1289            state.push(LuaValue::Nil);
1290            LuaType::None
1291        } else {
1292            let val = u.uv[(n - 1) as usize].clone();
1293            let t = val.base_type();
1294            state.push(val);
1295            t
1296        }
1297    } else {
1298        state.push(LuaValue::Nil);
1299        LuaType::None
1300    }
1301}
1302
1303// ── set functions (stack → Lua) ───────────────────────────────────────────────
1304
1305fn aux_set_str(state: &mut LuaState, t: LuaValue, k: &[u8]) -> Result<(), LuaError> {
1306    let str_val = {
1307        let ts = state.intern_str(k)?;
1308        LuaValue::Str(ts)
1309    };
1310    //       luaV_finishfastset(L, t, slot, s2v(L->top.p - 1)); L->top.p--;
1311    //    else { setsvalue2s L->top.p str; api_incr_top;
1312    //           luaV_finishset(L, t, s2v(L->top.p-1), s2v(L->top.p-2), slot);
1313    //           L->top.p -= 2; }
1314    let top = state.top_idx();
1315    let val = state.get_at(top - 1);
1316    state.table_set_with_tm(&t, str_val, val)?;
1317    state.pop();
1318    Ok(())
1319}
1320
1321pub fn set_global(state: &mut LuaState, name: &[u8]) -> Result<(), LuaError> {
1322    let g = get_global_table(state);
1323    aux_set_str(state, g, name)
1324}
1325
1326pub fn set_table(state: &mut LuaState, idx: i32) -> Result<(), LuaError> {
1327    let t = index_to_value(state, idx);
1328    let top = state.top_idx();
1329    let key = state.get_at(top - 2);
1330    let val = state.get_at(top - 1);
1331    state.table_set_with_tm(&t, key, val)?;
1332    state.set_top_idx(top - 2);
1333    Ok(())
1334}
1335
1336pub fn set_field(state: &mut LuaState, idx: i32, k: &[u8]) -> Result<(), LuaError> {
1337    let t = index_to_value(state, idx);
1338    aux_set_str(state, t, k)
1339}
1340
1341pub fn set_i(state: &mut LuaState, idx: i32, n: i64) -> Result<(), LuaError> {
1342    let t = index_to_value(state, idx);
1343    let top = state.top_idx();
1344    let val = state.get_at(top - 1);
1345    let key = LuaValue::Int(n);
1346    state.table_set_with_tm(&t, key, val)?;
1347    state.pop();
1348    Ok(())
1349}
1350
1351/// Variant of `set_i` that accepts a pre-resolved table value instead of a
1352/// stack index. Callers that invoke `set_i` repeatedly on the same table
1353/// (e.g. the shift loops in `table.remove` / `table.insert`) should resolve
1354/// the table once and use this function to avoid calling `index_to_value`
1355/// on every iteration.
1356pub fn set_i_value(state: &mut LuaState, t: &LuaValue, n: i64) -> Result<(), LuaError> {
1357    let top = state.top_idx();
1358    let val = state.get_at(top - 1);
1359    let key = LuaValue::Int(n);
1360    state.table_set_with_tm(t, key, val)?;
1361    state.pop();
1362    Ok(())
1363}
1364
1365fn aux_raw_set(state: &mut LuaState, idx: i32, key: LuaValue, n: u32) -> Result<(), LuaError> {
1366    let t = get_table_value(state, idx)
1367        .ok_or_else(|| LuaError::runtime(format_args!("table expected")))?;
1368    let top = state.top_idx();
1369    let val = state.get_at(top - 1);
1370    t.raw_set(state, key, val)?;
1371    t.invalidate_tm_cache();
1372    let top_val = state.get_at(top - 1);
1373    state.gc().barrier_back(&t, &top_val);
1374    state.set_top_idx(top - n as i32);
1375    Ok(())
1376}
1377
1378pub fn raw_set(state: &mut LuaState, idx: i32) -> Result<(), LuaError> {
1379    let top = state.top_idx();
1380    let key = state.get_at(top - 2);
1381    aux_raw_set(state, idx, key, 2)
1382}
1383
1384pub fn raw_set_p(state: &mut LuaState, idx: i32, p: *const core::ffi::c_void) -> Result<(), LuaError> {
1385    let key = LuaValue::LightUserData(p as *mut core::ffi::c_void);
1386    aux_raw_set(state, idx, key, 1)
1387}
1388
1389pub fn raw_set_i(state: &mut LuaState, idx: i32, n: i64) -> Result<(), LuaError> {
1390    let t = get_table_value(state, idx)
1391        .ok_or_else(|| LuaError::runtime(format_args!("table expected")))?;
1392    let top = state.top_idx();
1393    let val = state.get_at(top - 1);
1394    t.raw_set_int(state, n, val)?;
1395    let top_val = state.get_at(top - 1);
1396    state.gc().barrier_back(&t, &top_val);
1397    state.pop();
1398    Ok(())
1399}
1400
1401/// Returns true if `mt` (a metatable) holds a non-nil `__gc` entry.
1402///
1403/// PORT NOTE: Mirrors the body of C's `tofinalize` in `lgc.c` minus the bits
1404/// that consult per-object GC bits (irrelevant in Phase B's Rc world).
1405fn metatable_has_gc(state: &LuaState, mt: &GcRef<LuaTable>) -> bool {
1406    let name = state.global().tmname[crate::tagmethods::TagMethod::Gc as usize].clone();
1407    !matches!(mt.get_short_str(&name), LuaValue::Nil)
1408}
1409
1410/// Pin `tbl` in `pending_finalizers` if not already present.
1411fn register_finalizable_table(state: &mut LuaState, tbl: &GcRef<LuaTable>) {
1412    let already = state
1413        .global()
1414        .pending_finalizers
1415        .iter()
1416        .any(|t| GcRef::ptr_eq(t, tbl));
1417    if !already {
1418        state.global_mut().pending_finalizers.push(tbl.clone());
1419    }
1420}
1421
1422/// Phase-B `__gc` driver.
1423///
1424/// Scans `pending_finalizers` for tables whose only strong ref is the list
1425/// itself (`Rc::strong_count == 1`), runs their `__gc` metamethod in a
1426/// protected call, then drops the list's pin so the table can be freed.
1427/// Iterates in reverse so the most-recently registered finalizers run first,
1428/// matching C-Lua's order (`finobj` is a LIFO stack).
1429///
1430/// PORT NOTE: This stands in for C-Lua's `GCSatomic` finalizer-promotion step
1431/// plus `GCTM`. The real GC walks the heap to decide which `finobj` entries
1432/// are unreachable; in Phase B we use the `Rc` strong-count as the proxy.
1433/// Replaced by `lua_gc::run_pending_finalizers` when Phase D's incremental
1434/// GC lands.
1435pub fn run_pending_finalizers(state: &mut LuaState) {
1436    let mut did_run = false;
1437    loop {
1438        // `to_be_finalized` was populated by the most recent
1439        // `collect_via_heap` mark phase. Drain in LIFO order so the most
1440        // recently dead object runs its `__gc` first — matches C-Lua's
1441        // `finobj` stack ordering.
1442        let target_idx = {
1443            let to_fin = &state.global().to_be_finalized;
1444            if to_fin.is_empty() { None } else { Some(to_fin.len() - 1) }
1445        };
1446        let Some(i) = target_idx else { break; };
1447        // The Phase-A pre-finalizer weak-value sweep (mirroring C-Lua's
1448        // `clearbyvalues(g, g->weak, NULL)` from `atomic()`) is no longer
1449        // needed: under D-2, weak-table sweeping runs inside the post-mark
1450        // hook of `Heap::full_collect_with_post_mark`, which uses
1451        // reachability instead of strong_count and therefore clears such
1452        // entries BEFORE this finalizer pass runs. The full "bug-in-5.1"
1453        // ordering (finalizer-visible state) still requires reachability-
1454        // based detection of which finalizable tables are about to die — a
1455        // gap tracked under D-2 ephemeron/finalizer follow-up.
1456        let tbl = state.global_mut().to_be_finalized.swap_remove(i);
1457        let mt = tbl.metatable();
1458        let gc_fn = match mt {
1459            Some(ref m) => {
1460                let name = state.global().tmname[crate::tagmethods::TagMethod::Gc as usize].clone();
1461                m.get_short_str(&name)
1462            }
1463            None => LuaValue::Nil,
1464        };
1465        if !matches!(gc_fn, LuaValue::Function(_)) {
1466            continue;
1467        }
1468        did_run = true;
1469        let saved_top = state.top_idx();
1470        let ci_top = state.current_call_info().top;
1471        if saved_top.0 < ci_top.0 {
1472            state.clear_stack_range(saved_top, ci_top);
1473            state.set_top(ci_top);
1474        }
1475        state.push(gc_fn);
1476        state.push(LuaValue::Table(tbl));
1477        let func_idx = state.top_idx() - 2;
1478        let _heap_guard = {
1479            let g = state.global.borrow();
1480            lua_gc::HeapGuard::push(&g.heap)
1481        };
1482        let old_allowhook = state.allowhook;
1483        let old_gcstp = state.global_mut().stop_gc_internal();
1484        state.allowhook = false;
1485        let caller_ci = state.ci;
1486        let caller_status = state.get_ci(caller_ci).callstatus;
1487        state.get_ci_mut(caller_ci).callstatus = caller_status | crate::state::CIST_FIN;
1488        let _ = crate::do_::pcall(
1489            state,
1490            |s| s.call_no_yield(func_idx, 0),
1491            func_idx,
1492            0,
1493        );
1494        state.get_ci_mut(caller_ci).callstatus = caller_status;
1495        state.allowhook = old_allowhook;
1496        state.global_mut().set_gc_stop_flags(old_gcstp);
1497        state.set_top(saved_top);
1498    }
1499    // Post-finalizer weak sweep is also obsolete: any weak entries newly
1500    // exposed by the finalizer pass will be cleared on the NEXT
1501    // `Heap::full_collect_with_post_mark`. We accept the one-cycle lag.
1502    let _ = did_run;
1503}
1504
1505/// Snapshot the currently-live weak tables from
1506/// `GlobalState.weak_tables_registry`, deduplicating by Rc pointer and
1507/// dropping any whose backing storage has been freed. Used by both the
1508/// pre-finalizer and post-finalizer sweeps in [`run_pending_finalizers`]
1509/// and by the explicit `collectgarbage("collect")` path.
1510fn collect_live_weak_tables(state: &mut LuaState) -> Vec<GcRef<lua_types::value::LuaTable>> {
1511    let mut g = state.global_mut();
1512    g.weak_tables_registry.retain(|w| w.strong_count() > 0);
1513    let mut seen = std::collections::HashSet::<usize>::new();
1514    g.weak_tables_registry
1515        .iter()
1516        .filter_map(|w| w.upgrade())
1517        .filter_map(|rc| {
1518            let id = rc.identity();
1519            if seen.insert(id) {
1520                Some(rc)
1521            } else {
1522                None
1523            }
1524        })
1525        .collect()
1526}
1527
1528pub fn set_metatable(state: &mut LuaState, objindex: i32) -> Result<bool, LuaError> {
1529    let top = state.top_idx();
1530    let mt_val = state.get_at(top - 1);
1531    let mt: Option<GcRef<LuaTable>> = if matches!(mt_val, LuaValue::Nil) {
1532        None
1533    } else {
1534        debug_assert!(matches!(mt_val, LuaValue::Table(_)), "table expected");
1535        if let LuaValue::Table(t) = mt_val {
1536            Some(t)
1537        } else {
1538            None
1539        }
1540    };
1541
1542    let obj = index_to_value(state, objindex);
1543    match obj {
1544        LuaValue::Table(ref tbl) => {
1545            if mt.is_some() {
1546                state.gc().obj_barrier(tbl, mt.as_ref().unwrap());
1547            }
1548            tbl.set_metatable(mt.clone());
1549            if tbl.weak_mode() != 0 {
1550                state
1551                    .global_mut()
1552                    .weak_tables_registry
1553                    .push(tbl.downgrade());
1554            }
1555            // Phase-B finalizer registration: if the new metatable carries
1556            // `__gc` and `obj` was not already registered, pin `obj` in the
1557            // pending-finalizers list so that `run_pending_finalizers` can
1558            // invoke the finalizer before the object is freed.
1559            if let Some(ref mt_table) = mt {
1560                if metatable_has_gc(state, mt_table) {
1561                    register_finalizable_table(state, tbl);
1562                }
1563            }
1564        }
1565        LuaValue::UserData(ref ud) => {
1566            if let Some(ref mt_table) = mt {
1567                state.gc().obj_barrier(ud, mt_table);
1568                // TODO(port): luaC_checkfinalizer
1569            }
1570            ud.set_metatable(mt);
1571        }
1572        ref other => {
1573            let idx = other.base_type() as usize;
1574            state.global_mut().mt[idx] = mt;
1575        }
1576    }
1577    state.pop();
1578    Ok(true)
1579}
1580
1581pub fn set_i_uservalue(state: &mut LuaState, idx: i32, n: i32) -> Result<bool, LuaError> {
1582    let o = index_to_value(state, idx);
1583    debug_assert!(matches!(o, LuaValue::UserData(_)), "full userdata expected");
1584    let top = state.top_idx();
1585    let val = state.get_at(top - 1);
1586    let res = if let LuaValue::UserData(ref ud) = o {
1587        let nuvalue = ud.uv.len() as i32;
1588        if n < 1 || n > nuvalue {
1589            false
1590        } else {
1591            // TODO(port): LuaUserData uv field needs interior mutability for write
1592            // ud.uv[(n - 1) as usize] = val.clone();
1593            state.gc().barrier_back(ud, &val);
1594            let _ = (n, ud);
1595            true
1596        }
1597    } else {
1598        false
1599    };
1600    state.pop();
1601    Ok(res)
1602}
1603
1604// ── load/call functions ───────────────────────────────────────────────────────
1605
1606//                            lua_KContext ctx, lua_KFunction k)
1607pub fn call_k(
1608    state: &mut LuaState,
1609    nargs: i32,
1610    nresults: i32,
1611    ctx: isize,
1612    k: Option<fn(&mut LuaState, i32, isize) -> Result<usize, LuaError>>,
1613) -> Result<(), LuaError> {
1614    let top = state.top_idx();
1615    let func_idx = top - (nargs + 1);
1616    //      L->ci->u.c.k = k; L->ci->u.c.ctx = ctx;
1617    //      luaD_call(L, func, nresults);
1618    //    } else {
1619    //      luaD_callnoyield(L, func, nresults);
1620    //    }
1621    if k.is_some() && state.is_yieldable() {
1622        let ci_idx = state.ci;
1623        {
1624            let ci = state.get_ci_mut(ci_idx);
1625            ci.set_u_c_k(k);
1626            ci.set_u_c_ctx(ctx);
1627        }
1628        state.call_at(func_idx, nresults)?;
1629    } else {
1630        state.call_no_yield(func_idx, nresults)?;
1631    }
1632    state.adjust_results(nresults);
1633    Ok(())
1634}
1635
1636//                            lua_KContext ctx, lua_KFunction k)
1637pub fn pcall_k(
1638    state: &mut LuaState,
1639    nargs: i32,
1640    nresults: i32,
1641    errfunc: i32,
1642    ctx: isize,
1643    k: Option<fn(&mut LuaState, i32, isize) -> Result<usize, LuaError>>,
1644) -> Result<LuaStatus, LuaError> {
1645    // Phase D-1c: activate the heap for the duration of this protected call.
1646    // GcRef::new (post D-1e) and any future allocator-aware code will route
1647    // through state.global.heap via with_current_heap(...). Stacked so nested
1648    // pcalls inside the same thread don't clobber each other.
1649    let _heap_guard = {
1650        let g = state.global.borrow();
1651        // The HeapGuard borrows &Heap; we let it live for the function scope.
1652        // The borrow of `g` is dropped immediately; the guard's NonNull
1653        // outlives it (the heap field is pinned inside GlobalState which
1654        // is Rc-managed and won't move).
1655        lua_gc::HeapGuard::push(&g.heap)
1656    };
1657    let err_handler_idx: isize = if errfunc == 0 {
1658        0
1659    } else {
1660        let o = index_to_stack_idx(state, errfunc);
1661        debug_assert!(
1662            matches!(state.get_at(o), LuaValue::Function(_)),
1663            "error handler must be a function"
1664        );
1665        o.0 as isize
1666    };
1667    let top = state.top_idx();
1668    let func_idx = top - (nargs + 1);
1669    if k.is_none() || !state.is_yieldable() {
1670        state.protected_call_raw(func_idx, nresults, StackIdx(err_handler_idx as u32))?;
1671        state.adjust_results(nresults);
1672        return Ok(LuaStatus::Ok);
1673    }
1674    // Yieldable continuation path: arrange for an interrupted call (yield or
1675    // recoverable error) to be resumable. The call is already protected by
1676    // `lua_resume`; real errors must propagate with CIST_YPCALL still set so
1677    // `precover` can run `finish_pcallk`.
1678    //
1679    let ci_idx = state.ci;
1680    let allow = state.allowhook;
1681    let saved_errfunc = state.errfunc;
1682    {
1683        let ci = state.get_ci_mut(ci_idx);
1684        ci.set_u_c_k(k);
1685        ci.set_u_c_ctx(ctx);
1686        ci.set_u2_funcidx(func_idx.0 as i32);
1687        ci.set_u_c_old_errfunc(saved_errfunc);
1688        ci.set_oah(allow);
1689        ci.callstatus |= crate::state::CIST_YPCALL;
1690    }
1691    state.errfunc = err_handler_idx;
1692    let call_result = crate::do_::call(state, func_idx, nresults);
1693    match call_result {
1694        Ok(()) => {
1695            //    L->errfunc = ci->u.c.old_errfunc;
1696            //    status = LUA_OK;
1697            state.get_ci_mut(ci_idx).callstatus &= !crate::state::CIST_YPCALL;
1698            state.errfunc = saved_errfunc;
1699            state.adjust_results(nresults);
1700            Ok(LuaStatus::Ok)
1701        }
1702        Err(crate::state::LuaError::Yield) => {
1703            // Yield must propagate up to lua_resume. The recovery prep stays
1704            // on `ci_idx` so that on resume, `finishCcall` will call
1705            // `finishpcallk` followed by the continuation `k`.
1706            Err(crate::state::LuaError::Yield)
1707        }
1708        Err(e) => {
1709            // Real errors take the same path as C longjmp: they unwind to
1710            // lua_resume's protected runner, which calls precover and then
1711            // finish_pcallk while this C frame still advertises CIST_YPCALL.
1712            Err(e)
1713        }
1714    }
1715}
1716
1717//                          const char *chunkname, const char *mode)
1718// PORT NOTE: lua_Reader (void* callback) is replaced by Box<dyn FnMut>; mode
1719// is &[u8].
1720pub fn load(
1721    state: &mut LuaState,
1722    reader: Box<dyn FnMut() -> Option<Vec<u8>>>,
1723    chunkname: Option<&[u8]>,
1724    mode: Option<&[u8]>,
1725) -> Result<LuaStatus, LuaError> {
1726    let name = chunkname.unwrap_or(b"?");
1727    let z = crate::zio::ZIO::new(reader);
1728    let status = state.protected_parser(z, name, mode);
1729    if status == LuaStatus::Ok {
1730        let top = state.top_idx();
1731        let func_val = state.get_at(top - 1);
1732        if let LuaValue::Function(LuaClosure::Lua(lcl)) = func_val {
1733            if !lcl.upvals.is_empty() {
1734                let gt = get_global_table(state);
1735                let uv = state.new_upval_closed(gt);
1736                lcl.set_upval(0, uv);
1737            }
1738        }
1739    }
1740    Ok(status)
1741}
1742
1743pub fn dump(
1744    state: &LuaState,
1745    writer: &mut dyn FnMut(&[u8]) -> Result<(), LuaError>,
1746    strip: bool,
1747) -> Result<bool, LuaError> {
1748    let top = state.top_idx();
1749    let o = state.get_at(top - 1);
1750    if let LuaValue::Function(LuaClosure::Lua(ref lcl)) = o {
1751        crate::dump::dump(state, &lcl.proto, writer, strip)?;
1752        Ok(true)
1753    } else {
1754        Ok(false)
1755    }
1756}
1757
1758pub fn status(state: &LuaState) -> LuaStatus {
1759    LuaStatus::from_raw(state.status as i32)
1760}
1761
1762// ── garbage collection ────────────────────────────────────────────────────────
1763
1764/// GC operation codes (C: LUA_GC* constants)
1765#[repr(i32)]
1766#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1767pub enum GcWhat {
1768    Stop = 0,
1769    Restart = 1,
1770    Collect = 2,
1771    Count = 3,
1772    CountB = 4,
1773    Step = 5,
1774    SetPause = 6,
1775    SetStepMul = 7,
1776    IsRunning = 9,
1777    Gen = 10,
1778    Inc = 11,
1779}
1780
1781// PORT NOTE: C varargs replaced by explicit GcArgs enum; callers supply parameters directly.
1782pub enum GcArgs {
1783    Stop,
1784    Restart,
1785    Collect,
1786    Count,
1787    CountB,
1788    Step { data: i32 },
1789    SetPause { value: i32 },
1790    SetStepMul { value: i32 },
1791    IsRunning,
1792    Gen { minormul: i32, majormul: i32 },
1793    Inc { pause: i32, stepmul: i32, stepsize: i32 },
1794}
1795
1796pub fn gc(state: &mut LuaState, args: GcArgs) -> i32 {
1797    if state.global().is_gc_stopped_internally() {
1798        return -1;
1799    }
1800    match args {
1801        GcArgs::Stop => {
1802            state.global_mut().set_gc_stop_user();
1803        }
1804        GcArgs::Restart => {
1805            {
1806                let mut g = state.global_mut();
1807                crate::state::set_debt(&mut *g, 0);
1808            }
1809            state.global_mut().clear_gc_stop();
1810        }
1811        GcArgs::Collect => {
1812            if !state.allowhook {
1813                return 0;
1814            }
1815            // Under D-2, weak-table sweep happens INSIDE the heap's
1816            // post-mark hook (see GcHandle::full_collect), driven by
1817            // reachability rather than strong_count. The standalone weak
1818            // sweep that used to run here would now be a no-op against an
1819            // already-clean state and is removed.
1820            state.gc().full_collect();
1821            // Phase-B: drain pending __gc finalizers for tables whose user
1822            // refs have all been dropped. Kept for legacy compat; runs
1823            // after the heap's collect so weak entries have been cleared.
1824            run_pending_finalizers(state);
1825            // PORT NOTE: Phase-B long-string accounting. Reclaim `gc_debt`
1826            // for any tracked long-string Rc whose strong count has dropped
1827            // to zero (either because the weak-table sweep above released
1828            // the last reference, or because the user dropped it directly).
1829            // Without this, `collectgarbage("count")` would report peak
1830            // allocation rather than live bytes — gc.lua's weak-string-key
1831            // block depends on the post-collect count being lower than the
1832            // pre-collect count.
1833            {
1834                let mut g = state.global_mut();
1835                crate::state::reclaim_dead_long_strings(&mut *g);
1836            }
1837            // PORT NOTE: Phase B has no per-allocation totalbytes tracking,
1838            // so total_bytes() only ever shrinks (each `Step` simulates
1839            // freed memory). Refill to a baseline here so subsequent Step
1840            // calls have headroom to actually drop count*1024 — the test
1841            // pattern `collectgarbage(); local x = gcinfo(); collectgarbage('step'); assert(gcinfo()<x)`
1842            // needs gcinfo to be high enough that decrementing by 1 KB is
1843            // observable. Removed in Phase D when real GC tracks bytes.
1844            {
1845                let mut g = state.global_mut();
1846                let target_tb = 32_768_isize;
1847                let cur_tb = g.totalbytes + g.gc_debt;
1848                if cur_tb < target_tb {
1849                    g.totalbytes += target_tb - cur_tb;
1850                }
1851            }
1852        }
1853        GcArgs::Count => {
1854            {
1855                let mut g = state.global_mut();
1856                crate::state::reclaim_dead_long_strings(&mut *g);
1857            }
1858            let g = state.global();
1859            let long_string_bytes: usize = g.gc_tracked_long_strings.iter().map(|(_, sz)| sz).sum();
1860            let total = g.heap.bytes_used() + long_string_bytes;
1861            return (total >> 10) as i32;
1862        }
1863        GcArgs::CountB => {
1864            {
1865                let mut g = state.global_mut();
1866                crate::state::reclaim_dead_long_strings(&mut *g);
1867            }
1868            let g = state.global();
1869            let long_string_bytes: usize = g.gc_tracked_long_strings.iter().map(|(_, sz)| sz).sum();
1870            let total = g.heap.bytes_used() + long_string_bytes;
1871            return (total & 0x3ff) as i32;
1872        }
1873        GcArgs::Step { data } => {
1874            let old_stp = {
1875                let mut g = state.global_mut();
1876                let old = g.gc_stop_flags();
1877                g.clear_gc_stop();
1878                old
1879            };
1880            // C-Lua converts `data` KiB of added debt into work units via
1881            // `stepmul`. We use a simpler mapping: the work-unit count is
1882            // `data * stepmul / 4` (stepmul is the user-tunable speed,
1883            // /4-encoded in `gcstepmul`), with a floor of 1 unit. When
1884            // `data == 0` the call still performs one basic step (matching
1885            // C-Lua's `luaC_step(L)` after `setdebt(g, 0)`).
1886            let stepmul = (state.global().gc_stepmul_param() as isize | 1).max(1);
1887            let work_units = if data == 0 {
1888                stepmul
1889            } else {
1890                let raw = (data as isize).saturating_mul(stepmul);
1891                raw.max(1)
1892            };
1893            if data == 0 {
1894                let mut g = state.global_mut();
1895                crate::state::set_debt(&mut *g, 0);
1896            } else {
1897                let debt = data as isize * 1024 + state.global().gc_debt();
1898                let mut g = state.global_mut();
1899                crate::state::set_debt(&mut *g, debt);
1900            }
1901            let cycle_complete = state.gc().incremental_step(work_units);
1902            if state.global().is_gen_mode() {
1903                state.gc().prune_weak_tables_mark_only();
1904            }
1905            state.global_mut().set_gc_stop_flags(old_stp);
1906            // Phase-B byte accounting: real allocation isn't tracked, so
1907            // simulate C-Lua's post-sweep totalbytes drop here. Halving
1908            // the current `tb` makes `gcinfo() < x` hold across a step
1909            // that completes a cycle (gc.lua `dosteps()` line 194), while
1910            // the floor at 1 KB preserves `set_debt`'s `tb > 0` invariant
1911            // across many back-to-back step calls.
1912            if cycle_complete {
1913                let mut g = state.global_mut();
1914                let floor: isize = 1024;
1915                let cur_tb = g.totalbytes + g.gc_debt;
1916                let new_tb = (cur_tb / 2).max(floor);
1917                if new_tb < cur_tb {
1918                    g.totalbytes -= cur_tb - new_tb;
1919                }
1920            }
1921            // Sync the global gcstate byte for `gc_at_pause()` callers.
1922            {
1923                let heap_state = state.global().heap.gc_state();
1924                let mut g = state.global_mut();
1925                g.gcstate = if heap_state.is_pause() { 0 } else { 1 };
1926            }
1927            return if cycle_complete { 1 } else { 0 };
1928        }
1929        GcArgs::SetPause { value } => {
1930            let old = state.global().gc_pause_param() as i32;
1931            state.global_mut().set_gc_pause_param(value as u8);
1932            return old;
1933        }
1934        GcArgs::SetStepMul { value } => {
1935            let old = state.global().gc_stepmul_param() as i32;
1936            state.global_mut().set_gc_stepmul_param(value as u8);
1937            return old;
1938        }
1939        GcArgs::IsRunning => {
1940            return state.global().gc_running() as i32;
1941        }
1942        GcArgs::Gen { minormul, majormul } => {
1943            let old_mode = if state.global().is_gen_mode() { 10i32 } else { 11i32 };
1944            if minormul != 0 {
1945                state.global_mut().genminormul = minormul as u8;
1946            }
1947            if majormul != 0 {
1948                state.global_mut().set_gc_genmajormul(majormul as u8);
1949            }
1950            state.gc().change_mode(crate::state::GcKind::Generational);
1951            return old_mode;
1952        }
1953        GcArgs::Inc { pause, stepmul, stepsize } => {
1954            let old_mode = if state.global().is_gen_mode() { 10i32 } else { 11i32 };
1955            if pause != 0 {
1956                state.global_mut().set_gc_pause_param(pause as u8);
1957            }
1958            if stepmul != 0 {
1959                state.global_mut().set_gc_stepmul_param(stepmul as u8);
1960            }
1961            if stepsize != 0 {
1962                state.global_mut().gcstepsize = stepsize as u8;
1963            }
1964            state.gc().change_mode(crate::state::GcKind::Incremental);
1965            return old_mode;
1966        }
1967    }
1968    0
1969}
1970
1971// ── miscellaneous functions ───────────────────────────────────────────────────
1972
1973// PORT NOTE: returns Result<Infallible, _> — semantically "always Err". The
1974// translator originally wrote `Result<!, _>` but the `!` type in a return
1975// position is still nightly-only as of Rust 1.93; Infallible is the stable
1976// stand-in. Callsites just pattern-match on Err.
1977pub fn lua_error(state: &mut LuaState) -> Result<Infallible, LuaError> {
1978    //      luaM_error(L);  /* memory error */
1979    //    else
1980    //      luaG_errormsg(L);  /* regular error */
1981    let top = state.top_idx();
1982    let errobj = state.get_at(top - 1);
1983    let is_mem_err = if let LuaValue::Str(ref s) = errobj {
1984        let memerr = state.global().memerrmsg.clone();
1985        GcRef::ptr_eq(s, &memerr)
1986    } else {
1987        false
1988    };
1989    if is_mem_err {
1990        Err(LuaError::Memory)
1991    } else {
1992        Err(LuaError::from_value(errobj))
1993    }
1994}
1995
1996pub fn next(state: &mut LuaState, idx: i32) -> Result<bool, LuaError> {
1997    let t = get_table_value(state, idx)
1998        .ok_or_else(|| LuaError::runtime(format_args!("table expected")))?;
1999    let top = state.top_idx();
2000    let key = state.get_at(top - 1);
2001    match t.next(key)? {
2002        Some((next_key, next_val)) => {
2003            state.set_at(top - 1, next_key);
2004            state.push(next_val);
2005            Ok(true)
2006        }
2007        None => {
2008            state.set_top_idx(top - 1);
2009            Ok(false)
2010        }
2011    }
2012}
2013
2014pub fn to_close(state: &mut LuaState, idx: i32) -> Result<(), LuaError> {
2015    let _level = index_to_stack_idx(state, idx);
2016    // TODO(port): luaF_newtbcupval and to-be-closed variable infrastructure
2017    // not yet translated. Stubbing for Phase A.
2018    Ok(())
2019}
2020
2021pub fn concat(state: &mut LuaState, n: i32) -> Result<(), LuaError> {
2022    if n > 0 {
2023        state.concat(n)?;
2024    } else {
2025        let empty = state.intern_str(b"")?;
2026        state.push(LuaValue::Str(empty));
2027    }
2028    state.gc().check_step();
2029    Ok(())
2030}
2031
2032pub fn len(state: &mut LuaState, idx: i32) -> Result<(), LuaError> {
2033    let t = index_to_value(state, idx);
2034    let result = state.obj_len(&t)?;
2035    state.push(result);
2036    Ok(())
2037}
2038
2039// PORT NOTE: The custom allocator hook is not exposed in the Rust-native API.
2040// Rust's allocator handles all allocation.
2041// These are intentionally omitted.
2042
2043pub fn set_warn_f(
2044    state: &mut LuaState,
2045    f: Option<Box<dyn FnMut(&[u8], bool)>>,
2046) {
2047    // PORT NOTE: ud_warn userdata is folded into the closure per types.tsv.
2048    state.global_mut().warnf = f;
2049}
2050
2051pub fn warning(state: &mut LuaState, msg: &[u8], tocont: bool) {
2052    state.emit_warning(msg, tocont);
2053}
2054
2055pub fn new_userdata_uv(
2056    state: &mut LuaState,
2057    size: usize,
2058    nuvalue: i32,
2059) -> Result<GcRef<LuaUserData>, LuaError> {
2060    debug_assert!(nuvalue >= 0 && nuvalue < u16::MAX as i32, "invalid value");
2061    let u = state.new_userdata(size, nuvalue as usize)?;
2062    state.push(LuaValue::UserData(u.clone()));
2063    state.gc().check_step();
2064    Ok(u)
2065}
2066
2067// ── upvalue access ────────────────────────────────────────────────────────────
2068
2069// PORT NOTE: Returns (name, value) instead of mutating output pointers. The name
2070// is returned as an owned Vec<u8> because Lua upvalue names live in the proto's
2071// LuaString table (GC heap), not in static storage.
2072fn aux_upvalue(
2073    state: &LuaState,
2074    fi: &LuaValue,
2075    n: i32,
2076) -> Option<(Vec<u8>, LuaValue)> {
2077    match fi {
2078        LuaValue::Function(LuaClosure::C(ccl)) => {
2079            let nupvalues = ccl.upvalues.len() as i32;
2080            if n < 1 || n > nupvalues {
2081                return None;
2082            }
2083            Some((Vec::new(), ccl.upvalues[(n - 1) as usize].clone()))
2084        }
2085        LuaValue::Function(LuaClosure::Lua(lcl)) => {
2086            let nupvalues = lcl.upvals.len() as i32;
2087            if n < 1 || n > nupvalues {
2088                return None;
2089            }
2090            let val = state.upvalue_get(lcl, (n - 1) as usize);
2091            // The proto records the static name of each upvalue (e.g. "_ENV"
2092            // for the main chunk's environment upvalue). Stripped chunks have
2093            // no upvalue-name debug info; Lua reports those as "(no name)".
2094            let name: Vec<u8> = lcl
2095                .proto
2096                .upvalues
2097                .get((n - 1) as usize)
2098                .and_then(|ud| ud.name.as_ref())
2099                .map(|s| s.as_bytes().to_vec())
2100                .unwrap_or_else(|| b"(no name)".to_vec());
2101            Some((name, val))
2102        }
2103        _ => None,
2104    }
2105}
2106
2107pub fn get_upvalue(state: &mut LuaState, funcindex: i32, n: i32) -> Option<Vec<u8>> {
2108    let fi = index_to_value(state, funcindex);
2109    if let Some((name, val)) = aux_upvalue(state, &fi, n) {
2110        state.push(val);
2111        Some(name)
2112    } else {
2113        None
2114    }
2115}
2116
2117pub fn setup_value(state: &mut LuaState, funcindex: i32, n: i32) -> Option<Vec<u8>> {
2118    let fi = index_to_value(state, funcindex);
2119    let (name, _) = aux_upvalue(state, &fi, n)?;
2120    let new_val = state.pop();
2121    match &fi {
2122        LuaValue::Function(LuaClosure::Lua(lcl)) => {
2123            state.upvalue_set(lcl, (n - 1) as usize, new_val).ok()?;
2124        }
2125        LuaValue::Function(LuaClosure::C(_ccl)) => {
2126            // TODO(port): C-closure upvalue writes need interior mutability on
2127            // LuaCClosure.upvalues. Not exercised by current tests.
2128            let _ = new_val;
2129        }
2130        _ => return None,
2131    }
2132    Some(name)
2133}
2134
2135// PORT NOTE: returns an index into the upvals vec rather than a pointer-to-pointer.
2136// Returns None if n is out of range.
2137fn get_upval_ref_idx(state: &LuaState, fidx: i32, n: i32) -> Option<usize> {
2138    let fi = index_to_value(state, fidx);
2139    debug_assert!(matches!(fi, LuaValue::Function(LuaClosure::Lua(_))), "Lua function expected");
2140    if let LuaValue::Function(LuaClosure::Lua(ref lcl)) = fi {
2141        let sizeupvalues = lcl.upvals.len() as i32;
2142        if n >= 1 && n <= sizeupvalues {
2143            Some((n - 1) as usize)
2144        } else {
2145            None
2146        }
2147    } else {
2148        None
2149    }
2150}
2151
2152// PORT NOTE: Returns Option<usize> identity instead of raw void*.
2153pub fn upvalue_id(state: &LuaState, fidx: i32, n: i32) -> Option<usize> {
2154    let fi = index_to_value(state, fidx);
2155    match &fi {
2156        LuaValue::Function(LuaClosure::Lua(lcl)) => {
2157            let idx = get_upval_ref_idx(state, fidx, n)?;
2158            // Return the identity of the UpVal GcRef
2159            Some(GcRef::identity(&lcl.upval(idx)))
2160        }
2161        LuaValue::Function(LuaClosure::C(ccl)) => {
2162            if n >= 1 && n <= ccl.upvalues.len() as i32 {
2163                // TODO(port): returning address of upvalue slot not possible without raw ptr.
2164                // Return a synthetic identity based on the closure's identity + n.
2165                Some(GcRef::identity(ccl) ^ (n as usize))
2166            } else {
2167                None
2168            }
2169        }
2170        LuaValue::Function(LuaClosure::LightC(_)) => None,
2171        _ => {
2172            debug_assert!(false, "function expected");
2173            None
2174        }
2175    }
2176}
2177
2178//                                               int fidx2, int n2)
2179pub fn upvalue_join(state: &mut LuaState, fidx1: i32, n1: i32, fidx2: i32, n2: i32) {
2180    let idx1 = match get_upval_ref_idx(state, fidx1, n1) {
2181        Some(i) => i,
2182        None => return,
2183    };
2184    let idx2 = match get_upval_ref_idx(state, fidx2, n2) {
2185        Some(i) => i,
2186        None => return,
2187    };
2188    let f1 = index_to_value(state, fidx1);
2189    let f2 = index_to_value(state, fidx2);
2190    if let (
2191        LuaValue::Function(LuaClosure::Lua(lcl1)),
2192        LuaValue::Function(LuaClosure::Lua(lcl2)),
2193    ) = (&f1, &f2)
2194    {
2195        let shared = lcl2.upval(idx2);
2196        lcl1.set_upval(idx1, shared);
2197    }
2198}
2199
2200// ──────────────────────────────────────────────────────────────────────────
2201// PORT STATUS
2202//   source:        src/lapi.c  (1464 lines, ~47 functions)
2203//   target_crate:  lua-vm
2204//   confidence:    low
2205//   todos:         18
2206//   port_notes:    8
2207//   unsafe_blocks: 0   (must be 0 outside explicit unsafe-budget crates)
2208//   notes:         Heavy use of interior mutability TODOs (GcRef writes for
2209//                  metatables, upvalue writes, userdata uv writes). The
2210//                  index2value helper returns cloned LuaValue not a pointer,
2211//                  so write-back paths that C achieves with TValue* are
2212//                  stubbed. Stack pointer arithmetic faithfully translated to
2213//                  StackIdx (u32) arithmetic. va_list functions (pushvfstring,
2214//                  pushfstring) replaced by &[u8] forwarders. lua_gc varargs
2215//                  replaced by explicit GcArgs enum. Raw pointer returns
2216//                  (topointer, touserdata, upvalueid) return Option<usize>
2217//                  identity values; actual *mut void only legal in lua-gc.
2218//                  lua_pushthread stubbed (needs self_gcref()), lua_xmove
2219//                  stubbed (split-borrow), upvalue_join stubbed (GcRef write).
2220//                  Phase B must wire up: state.grow_stack, state.call_no_yield,
2221//                  state.protected_call_raw, state.adjust_results,
2222//                  state.table_get_with_tm, state.table_set_with_tm,
2223//                  state.arith_op, state.concat, state.obj_len,
2224//                  state.obj_to_string, state.str_to_num, state.table_getn,
2225//                  state.registry_value, state.registry_get,
2226//                  GcRef::identity, GcRef::ptr_eq, GlobalState GC accessors.
2227// ──────────────────────────────────────────────────────────────────────────