Skip to main content

lua_types/
table.rs

1//! Lua table implementation (array + hash hybrid).
2//!
3//! Canonical port of `reference/lua-5.4.7/src/ltable.c`. Lives in
4//! `lua-types` because `LuaValue::Table(GcRef<LuaTable>)` is defined here
5//! and the table storage must be reachable without depending on
6//! `lua-vm`. The crate `lua_unsafe = "forbid"` lint is preserved.
7//!
8//! # Interior mutability
9//!
10//! `GcRef<T>` only yields `&T` on deref, so the mutable algorithms in
11//! C-Lua's `ltable.c` (which write through `Table *`) must operate
12//! through a `RefCell`. The split is:
13//!
14//! * `LuaTable` — outer handle. All public methods take `&self`.
15//! * `TableInner` — storage + algorithms. All mutating methods are
16//!   `&mut TableInner` and are reached via `inner.borrow_mut()`.
17//!
18//! The hash part uses Brent's variation of chained scatter tables.
19//! The key invariant: if an element is not in its *main position*
20//! (the slot its hash maps to), the colliding element *is* in its
21//! own main position.
22
23use std::cell::{Cell, RefCell};
24
25use crate::closure::LuaClosure;
26use crate::error::LuaError;
27use crate::gc::GcRef;
28use crate::string::LuaString;
29use crate::value::LuaValue;
30
31// ── Constants ─────────────────────────────────────────────────────────────────
32
33/// Largest `k` such that `2^k` fits in a signed `i32`.
34/// C: `#define MAXABITS cast_int(sizeof(int) * CHAR_BIT - 1)`
35const MAXABITS: u32 = (std::mem::size_of::<i32>() as u32) * 8 - 1;
36
37/// Maximum size of the array part.
38/// C: `#define MAXASIZE luaM_limitN(1u << MAXABITS, TValue)`
39pub const MAXASIZE: u32 = 1u32 << MAXABITS;
40
41/// Largest `k` such that `2^k` fits in a signed `i32` minus one (hash part).
42/// C: `#define MAXHBITS (MAXABITS - 1)`
43pub const MAXHBITS: u32 = MAXABITS - 1;
44
45/// Maximum size of the hash part (power-of-2 count of nodes).
46/// C: `#define MAXHSIZE luaM_limitN(1u << MAXHBITS, Node)`
47const MAXHSIZE: u32 = 1u32 << MAXHBITS;
48
49/// Bit 7 of `TableFlags`: when set, `alimit` is NOT the real array size.
50/// C: `#define BITRAS (1 << 7)`.
51const BIT_RAS: u8 = 1 << 7;
52
53/// Soft cap on array growth in a single `raw_set` call.
54///
55/// Prevents pathological inserts of a far-away integer key (e.g.
56/// `t[1<<30] = 1`) from allocating gigabytes when an array slot would
57/// be the wrong choice; falls back to the hash part for keys above the
58/// cap. Matches C-Lua's behavioural bound, which is governed by
59/// `MAXASIZE` and the rehash density heuristic.
60pub const ARRAY_GROW_CAP: u32 = 1u32 << 20;
61
62/// Soft cap on total entries (array + hash) used to emulate C-Lua's
63/// `malloc`-NULL termination of unbounded `for i = 1, math.huge do a[i] = ... end`
64/// loops. C-Lua hits real malloc failure at multi-gigabyte allocations; we
65/// raise `LuaError::Memory` (which pcall catches as `"not enough memory"`)
66/// once the table exceeds this size. Sized well above any realistic test
67/// workload (`big.lua` uses ~264K entries) while bounded enough that
68/// `heavy.lua` terminates within the harness timeout.
69pub const TOTAL_GROW_CAP: usize = 1usize << 20;
70
71const WEAK_KEYS: u8 = 1 << 0;
72const WEAK_VALUES: u8 = 1 << 1;
73
74// ── TableFlags ─────────────────────────────────────────────────────────────────
75
76/// Bitfield for a [`LuaTable`]: lower bits record absent fast-access
77/// metamethods; bit 7 encodes whether `alimit` is the real array size.
78#[derive(Clone, Copy, Debug, Default)]
79pub struct TableFlags(pub u8);
80
81impl TableFlags {
82    /// `isrealasize(t)` — bit 7 clear means alimit IS the real array size.
83    #[inline]
84    pub fn is_real_asize(self) -> bool {
85        (self.0 & BIT_RAS) == 0
86    }
87
88    /// `setrealasize(t)` — clear bit 7 so alimit becomes the canonical size.
89    #[inline]
90    pub fn set_real_asize(&mut self) {
91        self.0 &= !BIT_RAS;
92    }
93
94    /// `setnorealasize(t)` — set bit 7 so alimit is only a hint.
95    #[inline]
96    pub fn set_no_real_asize(&mut self) {
97        self.0 |= BIT_RAS;
98    }
99
100    /// `invalidateTMcache(t)` — clear all fast-access metamethod bits.
101    #[inline]
102    pub fn invalidate_tm_cache(&mut self) {
103        const MASK_FLAGS: u8 = 0x7F;
104        self.0 &= !MASK_FLAGS;
105    }
106}
107
108// ── TableNode ──────────────────────────────────────────────────────────────────
109
110/// One node in a table's hash part.
111///
112/// C: `Node` = `NodeKey` + `TValue i_val`. The collision chain is a
113/// signed offset into the same node vector.
114pub struct TableNode {
115    /// Value stored at this key.  C: `gval(n)`.
116    pub value: LuaValue,
117    /// Key stored in this node.  C: `n->u.key_val` + `n->u.key_tt`.
118    pub key: LuaValue,
119    /// Collision-chain offset (positive or negative; zero means end of chain).
120    /// C: `n->u.next` (`int`).
121    pub next: i32,
122}
123
124impl TableNode {
125    fn empty() -> Self {
126        TableNode { value: LuaValue::Nil, key: LuaValue::Nil, next: 0 }
127    }
128
129    fn key_is_nil(&self) -> bool { matches!(self.key, LuaValue::Nil) }
130    fn key_is_int(&self) -> bool { matches!(self.key, LuaValue::Int(_)) }
131    fn key_int(&self) -> i64 {
132        if let LuaValue::Int(i) = self.key { i }
133        else { panic!("TableNode::key_int: key is not int") }
134    }
135    fn key_is_short_str(&self) -> bool {
136        if let LuaValue::Str(s) = &self.key { s.is_short() }
137        else { false }
138    }
139    fn key_string(&self) -> &GcRef<LuaString> {
140        if let LuaValue::Str(s) = &self.key { s }
141        else { panic!("TableNode::key_string: key is not a string") }
142    }
143    fn key_value(&self) -> LuaValue { self.key.clone() }
144    fn set_key(&mut self, k: &LuaValue) { self.key = k.clone(); }
145}
146
147// ── TableSlotRef ───────────────────────────────────────────────────────────────
148
149/// Internal slot reference returned by the "get" family of functions.
150///
151/// Replaces C's `const TValue *` pattern, which may point into either
152/// the array part, the hash part, or the static `absentkey` sentinel.
153#[derive(Debug, Clone, Copy)]
154pub enum TableSlotRef {
155    /// Key lives in the array part at this 0-based index.
156    Array(usize),
157    /// Key lives in the hash part at this 0-based node index.
158    Hash(usize),
159    /// Key is absent from the table (C: `&absentkey`).
160    Absent,
161}
162
163// ── ceil_log2 ─────────────────────────────────────────────────────────────────
164
165/// Computes `ceil(log2(x))`; returns the minimum `k` such that `2^k >= x`.
166/// C: `int luaO_ceillog2 (unsigned int x)`.
167fn ceil_log2(x: u32) -> i32 {
168    static LOG_2: [u8; 256] = [
169        0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
170        6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
171        7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
172        7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
173        8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
174        8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
175        8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
176        8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
177    ];
178    let mut l: i32 = 0;
179    let mut x = x.wrapping_sub(1);
180    while x >= 256 { l += 8; x >>= 8; }
181    l + LOG_2[x as usize] as i32
182}
183
184// ── float hash (frexp-based) ──────────────────────────────────────────────────
185
186/// Hash a `f64` to an `i32` bucket index.
187///
188/// Uses `frexp` decomposition to produce a well-distributed integer hash.
189/// Handles inf/NaN by returning 0.
190///
191/// C: `static int l_hashfloat (lua_Number n)`.
192fn hash_float(n: f64) -> i32 {
193    if n.is_nan() || n.is_infinite() {
194        return 0;
195    }
196    let (mantissa, exp) = frexp(n);
197    let scaled = mantissa * -(i32::MIN as f64);
198    let ni = scaled as i64;
199    if ni as f64 != scaled {
200        return 0;
201    }
202    let u = (exp as u32).wrapping_add(ni as u32);
203    if u <= i32::MAX as u32 { u as i32 } else { !(u as i32) }
204}
205
206/// Decompose `x` into mantissa ∈ `[0.5, 1)` and integer exponent.
207fn frexp(x: f64) -> (f64, i32) {
208    if x == 0.0 || x.is_nan() || x.is_infinite() {
209        return (x, 0);
210    }
211    let bits = x.to_bits();
212    let exp_bits = ((bits >> 52) & 0x7FFu64) as i32;
213    if exp_bits == 0 {
214        let scaled = x * (2.0f64.powi(64));
215        let (m, e) = frexp(scaled);
216        return (m, e - 64);
217    }
218    let exp = exp_bits - 1022;
219    let mantissa_bits = (bits & !(0x7FFu64 << 52)) | (0x3FEu64 << 52);
220    (f64::from_bits(mantissa_bits), exp)
221}
222
223// ── TableInner ─────────────────────────────────────────────────────────────────
224
225/// Hybrid array + hash storage backing a [`LuaTable`].
226///
227/// All mutating algorithms live as `&mut TableInner` methods so they
228/// can be called from the outer `&self` API via `RefCell::borrow_mut`.
229pub struct TableInner {
230    pub flags: TableFlags,
231    pub lsizenode: u8,
232    pub alimit: u32,
233    pub array: Vec<LuaValue>,
234    pub node: Vec<TableNode>,
235    pub lastfree: Option<usize>,
236}
237
238impl TableInner {
239    fn new() -> Self {
240        TableInner {
241            flags: TableFlags(0x7F),
242            lsizenode: 0,
243            alimit: 0,
244            array: Vec::new(),
245            node: Vec::new(),
246            lastfree: None,
247        }
248    }
249
250    /// `isdummy(t)` — true when the table has no allocated hash part.
251    #[inline]
252    fn is_dummy(&self) -> bool { self.lastfree.is_none() }
253
254    /// `sizenode(t)` — nominal hash-part capacity (`1 << lsizenode`).
255    #[inline]
256    fn sizenode(&self) -> u32 { 1u32 << self.lsizenode }
257
258    /// `allocsizenode(t)` — 0 when dummy, else `1 << lsizenode`.
259    #[inline]
260    fn alloc_sizenode(&self) -> u32 {
261        if self.is_dummy() { 0 } else { self.sizenode() }
262    }
263
264    /// `isrealasize(t)` accessor.
265    #[inline]
266    fn is_real_asize(&self) -> bool { self.flags.is_real_asize() }
267
268    /// `ispow2(x)` — C treats 0 as a power of two.
269    #[inline]
270    fn is_pow2(x: u32) -> bool { x == 0 || x.is_power_of_two() }
271
272    /// Returns the real size of the array part. C: `luaH_realasize`.
273    fn real_asize(&self) -> u32 {
274        if self.limit_equals_asize() {
275            return self.alimit;
276        }
277        let mut size = self.alimit;
278        size |= size >> 1;
279        size |= size >> 2;
280        size |= size >> 4;
281        size |= size >> 8;
282        size |= size >> 16;
283        size = size.wrapping_add(1);
284        debug_assert!(
285            Self::is_pow2(size) && size / 2 < self.alimit && self.alimit < size
286        );
287        size
288    }
289
290    #[inline]
291    fn limit_equals_asize(&self) -> bool {
292        self.is_real_asize() || Self::is_pow2(self.alimit)
293    }
294
295    fn is_pow2_real_asize(&self) -> bool {
296        !self.is_real_asize() || Self::is_pow2(self.alimit)
297    }
298
299    fn set_limit_to_size(&mut self) -> u32 {
300        self.alimit = self.real_asize();
301        self.flags.set_real_asize();
302        self.alimit
303    }
304
305    // ── Hash helper functions ──────────────────────────────────────────────
306
307    fn hash_idx_for_int(&self, i: i64) -> usize {
308        let ui = i as u64;
309        let sn = self.sizenode() as usize;
310        let modulo = (sn - 1) | 1;
311        if ui <= i32::MAX as u64 {
312            (ui as usize) % modulo
313        } else {
314            (ui as usize) % modulo
315        }
316    }
317
318    #[inline]
319    fn hashpow2_idx(&self, h: u32) -> usize {
320        (h & (self.sizenode() - 1)) as usize
321    }
322
323    #[inline]
324    fn hashmod_idx(&self, h: usize) -> usize {
325        let sn = self.sizenode() as usize;
326        let modulo = (sn - 1) | 1;
327        h % modulo
328    }
329
330    fn main_position(&self, key: &LuaValue) -> usize {
331        match key {
332            LuaValue::Int(i) => self.hash_idx_for_int(*i),
333            LuaValue::Float(f) => {
334                let h = hash_float(*f);
335                self.hashmod_idx(h as usize)
336            }
337            LuaValue::Str(s) if s.is_short() => self.hashpow2_idx(s.hash()),
338            LuaValue::Str(s) => self.hashpow2_idx(s.hash()),
339            LuaValue::Bool(false) => self.hashpow2_idx(0),
340            LuaValue::Bool(true) => self.hashpow2_idx(1),
341            LuaValue::LightUserData(p) => {
342                let h = (*p as usize as u32) as usize;
343                self.hashmod_idx(h)
344            }
345            LuaValue::Function(LuaClosure::LightC(f)) => {
346                let h = (*f as u32) as usize;
347                self.hashmod_idx(h)
348            }
349            LuaValue::Table(t) => {
350                let h = (GcRef::identity(t) as u32) as usize;
351                self.hashmod_idx(h)
352            }
353            LuaValue::Function(LuaClosure::Lua(cl)) => {
354                let h = (GcRef::identity(cl) as u32) as usize;
355                self.hashmod_idx(h)
356            }
357            LuaValue::Function(LuaClosure::C(cl)) => {
358                let h = (GcRef::identity(cl) as u32) as usize;
359                self.hashmod_idx(h)
360            }
361            LuaValue::UserData(u) => {
362                let h = (GcRef::identity(u) as u32) as usize;
363                self.hashmod_idx(h)
364            }
365            LuaValue::Thread(th) => {
366                let h = (GcRef::identity(th) as u32) as usize;
367                self.hashmod_idx(h)
368            }
369            LuaValue::Nil => 0,
370        }
371    }
372
373    fn main_position_from_node(&self, nd: usize) -> usize {
374        let key = self.node[nd].key_value();
375        self.main_position(&key)
376    }
377
378    // ── Key equality ───────────────────────────────────────────────────────
379
380    fn equal_key(k1: &LuaValue, n2: &TableNode) -> bool {
381        let types_match = std::mem::discriminant(k1) == std::mem::discriminant(&n2.key);
382        if !types_match {
383            return false;
384        }
385        match &n2.key {
386            LuaValue::Nil => true,
387            LuaValue::Bool(b) => matches!(k1, LuaValue::Bool(b2) if b == b2),
388            LuaValue::Int(ni) => matches!(k1, LuaValue::Int(ki) if ki == ni),
389            LuaValue::Float(nf) => matches!(k1, LuaValue::Float(kf) if kf == nf),
390            LuaValue::LightUserData(np) => matches!(k1, LuaValue::LightUserData(kp) if kp == np),
391            LuaValue::Function(LuaClosure::LightC(nf)) => {
392                matches!(k1, LuaValue::Function(LuaClosure::LightC(kf)) if kf == nf)
393            }
394            LuaValue::Str(ns) if ns.is_long() => {
395                if let LuaValue::Str(ks) = k1 {
396                    ks.as_bytes() == ns.as_bytes()
397                } else { false }
398            }
399            _ => Self::gc_ptr_eq(k1, &n2.key),
400        }
401    }
402
403    fn gc_ptr_eq(a: &LuaValue, b: &LuaValue) -> bool {
404        match (a, b) {
405            (LuaValue::Str(sa), LuaValue::Str(sb)) => GcRef::ptr_eq(sa, sb),
406            (LuaValue::Table(ta), LuaValue::Table(tb)) => GcRef::ptr_eq(ta, tb),
407            (LuaValue::Function(LuaClosure::Lua(fa)), LuaValue::Function(LuaClosure::Lua(fb))) => {
408                GcRef::ptr_eq(fa, fb)
409            }
410            (LuaValue::Function(LuaClosure::C(fa)), LuaValue::Function(LuaClosure::C(fb))) => {
411                GcRef::ptr_eq(fa, fb)
412            }
413            (LuaValue::UserData(ua), LuaValue::UserData(ub)) => GcRef::ptr_eq(ua, ub),
414            (LuaValue::Thread(ta), LuaValue::Thread(tb)) => GcRef::ptr_eq(ta, tb),
415            _ => false,
416        }
417    }
418
419    // ── Generic hash-part lookup ───────────────────────────────────────────
420
421    fn get_generic_slot(&self, key: &LuaValue) -> TableSlotRef {
422        if self.is_dummy() { return TableSlotRef::Absent; }
423        let mut n = self.main_position(key);
424        loop {
425            if Self::equal_key(key, &self.node[n]) {
426                return TableSlotRef::Hash(n);
427            }
428            let nx = self.node[n].next;
429            if nx == 0 { return TableSlotRef::Absent; }
430            n = (n as isize + nx as isize) as usize;
431        }
432    }
433
434    // ── arrayindex / findindex ─────────────────────────────────────────────
435
436    fn array_index(k: i64) -> u32 {
437        let uk = k as u64;
438        if uk.wrapping_sub(1) < MAXASIZE as u64 { k as u32 } else { 0 }
439    }
440
441    /// Find the linear traversal position of `key`. Returns 0 for `Nil`
442    /// (first iteration). Errors with `"invalid key to 'next'"` when
443    /// the key is non-nil and not present in the table.
444    fn find_index(&self, key: &LuaValue, asize: u32) -> Result<u32, LuaError> {
445        if matches!(key, LuaValue::Nil) { return Ok(0); }
446        let i = if let LuaValue::Int(k) = key { Self::array_index(*k) } else { 0 };
447        if i.wrapping_sub(1) < asize { return Ok(i); }
448        let slot = self.get_generic_slot(key);
449        match slot {
450            TableSlotRef::Absent => {
451                Err(LuaError::runtime(format_args!("invalid key to 'next'")))
452            }
453            TableSlotRef::Hash(node_idx) => Ok((node_idx as u32 + 1) + asize),
454            TableSlotRef::Array(_) => unreachable!("getgeneric returned Array slot"),
455        }
456    }
457
458    /// Iteration step: given a key (`Nil` for first call), return the
459    /// next `(key, value)` pair in C-Lua's array-then-hash order.
460    fn next_pair(&self, key: &LuaValue) -> Result<Option<(LuaValue, LuaValue)>, LuaError> {
461        let asize = self.real_asize();
462        let i = self.find_index(key, asize)?;
463        let mut i = i as usize;
464        while i < asize as usize {
465            if !matches!(self.array[i], LuaValue::Nil) {
466                return Ok(Some((LuaValue::Int((i + 1) as i64), self.array[i].clone())));
467            }
468            i += 1;
469        }
470        let mut hi = i.saturating_sub(asize as usize);
471        while hi < self.node.len() {
472            if !matches!(self.node[hi].value, LuaValue::Nil) {
473                return Ok(Some((self.node[hi].key_value(), self.node[hi].value.clone())));
474            }
475            hi += 1;
476        }
477        Ok(None)
478    }
479
480    // ── Rehash helpers ─────────────────────────────────────────────────────
481
482    fn compute_sizes(nums: &[u32], pna: &mut u32) -> u32 {
483        let mut twotoi: u32 = 1;
484        let mut a: u32 = 0;
485        let mut na: u32 = 0;
486        let mut optimal: u32 = 0;
487        for i in 0..nums.len() {
488            if twotoi == 0 || *pna <= twotoi / 2 { break; }
489            a += nums[i];
490            if a > twotoi / 2 {
491                optimal = twotoi;
492                na = a;
493            }
494            twotoi = twotoi.wrapping_mul(2);
495        }
496        debug_assert!(optimal == 0 || optimal / 2 < na && na <= optimal);
497        *pna = na;
498        optimal
499    }
500
501    fn count_int(key: i64, nums: &mut [u32]) -> bool {
502        let k = Self::array_index(key);
503        if k != 0 {
504            nums[ceil_log2(k) as usize] += 1;
505            true
506        } else { false }
507    }
508
509    fn num_use_array(&self, nums: &mut [u32]) -> u32 {
510        debug_assert!(self.is_real_asize(), "numusearray: alimit must be real size");
511        let asize = self.alimit as usize;
512        let mut ause: u32 = 0;
513        let mut i: usize = 1;
514        let mut ttlg: usize = 1;
515        for lg in 0..=(MAXABITS as usize) {
516            let mut lc: u32 = 0;
517            let lim = if ttlg > asize { asize } else { ttlg };
518            if i > lim { break; }
519            while i <= lim {
520                if !matches!(self.array[i - 1], LuaValue::Nil) { lc += 1; }
521                i += 1;
522            }
523            nums[lg] += lc;
524            ause += lc;
525            ttlg = ttlg.saturating_mul(2);
526        }
527        ause
528    }
529
530    fn num_use_hash(&self, nums: &mut [u32], pna: &mut u32) -> i32 {
531        let mut totaluse: i32 = 0;
532        let mut ause: u32 = 0;
533        let mut i = self.node.len();
534        while i > 0 {
535            i -= 1;
536            let n = &self.node[i];
537            if !matches!(n.value, LuaValue::Nil) {
538                if n.key_is_int() {
539                    if Self::count_int(n.key_int(), nums) { ause += 1; }
540                }
541                totaluse += 1;
542            }
543        }
544        *pna += ause;
545        totaluse
546    }
547
548    fn set_node_vector(&mut self, size: u32) -> Result<(), LuaError> {
549        if size == 0 {
550            self.node = Vec::new();
551            self.lsizenode = 0;
552            self.lastfree = None;
553        } else {
554            let lsize = ceil_log2(size);
555            if lsize as u32 > MAXHBITS || (1u32 << lsize) > MAXHSIZE {
556                return Err(LuaError::runtime(format_args!("table overflow")));
557            }
558            let actual_size = 1u32 << lsize;
559            let mut nodes = Vec::with_capacity(actual_size as usize);
560            for _ in 0..actual_size { nodes.push(TableNode::empty()); }
561            self.node = nodes;
562            self.lsizenode = lsize as u8;
563            self.lastfree = Some(actual_size as usize);
564        }
565        Ok(())
566    }
567
568    fn reinsert(&mut self, old_nodes: Vec<(LuaValue, LuaValue)>) -> Result<(), LuaError> {
569        for (k, v) in old_nodes {
570            self.set(&k, v)?;
571        }
572        Ok(())
573    }
574
575    /// Resize the table to new array and hash sizes.
576    /// C: `void luaH_resize`.
577    fn resize(&mut self, new_asize: u32, nhsize: u32) -> Result<(), LuaError> {
578        let old_asize = self.set_limit_to_size();
579
580        let (mut new_hash_node, mut new_hash_lsize, mut new_hash_lastfree) = {
581            let mut tmp = TableInner::new();
582            tmp.set_node_vector(nhsize)?;
583            (tmp.node, tmp.lsizenode, tmp.lastfree)
584        };
585
586        if new_asize < old_asize {
587            self.alimit = new_asize;
588            std::mem::swap(&mut self.node, &mut new_hash_node);
589            std::mem::swap(&mut self.lsizenode, &mut new_hash_lsize);
590            std::mem::swap(&mut self.lastfree, &mut new_hash_lastfree);
591
592            for i in (new_asize as usize)..(old_asize as usize) {
593                if !matches!(self.array[i], LuaValue::Nil) {
594                    let v = self.array[i].clone();
595                    self.set_int((i + 1) as i64, v)?;
596                }
597            }
598
599            self.alimit = old_asize;
600            std::mem::swap(&mut self.node, &mut new_hash_node);
601            std::mem::swap(&mut self.lsizenode, &mut new_hash_lsize);
602            std::mem::swap(&mut self.lastfree, &mut new_hash_lastfree);
603        }
604
605        self.array.resize_with(new_asize as usize, || LuaValue::Nil);
606
607        std::mem::swap(&mut self.node, &mut new_hash_node);
608        std::mem::swap(&mut self.lsizenode, &mut new_hash_lsize);
609        std::mem::swap(&mut self.lastfree, &mut new_hash_lastfree);
610        self.alimit = new_asize;
611
612        let old_hash_entries: Vec<(LuaValue, LuaValue)> = new_hash_node
613            .iter()
614            .filter(|n| !matches!(n.value, LuaValue::Nil))
615            .map(|n| (n.key_value(), n.value.clone()))
616            .collect();
617        drop(new_hash_node);
618        self.reinsert(old_hash_entries)?;
619
620        Ok(())
621    }
622
623    fn rehash(&mut self, extra_key: &LuaValue) -> Result<(), LuaError> {
624        let mut nums = [0u32; MAXABITS as usize + 1];
625        self.set_limit_to_size();
626
627        let na = self.num_use_array(&mut nums);
628        let mut na = na;
629        let mut totaluse = na as i32;
630
631        totaluse += self.num_use_hash(&mut nums, &mut na);
632
633        if let LuaValue::Int(ek) = extra_key {
634            if Self::count_int(*ek, &mut nums) { na += 1; }
635        }
636        totaluse += 1;
637
638        let asize = Self::compute_sizes(&nums, &mut na);
639
640        let nh = (totaluse - na as i32).max(0) as u32;
641        self.resize(asize, nh)
642    }
643
644    fn get_free_pos(&mut self) -> Option<usize> {
645        if self.is_dummy() { return None; }
646        loop {
647            let lf = self.lastfree?;
648            if lf == 0 {
649                self.lastfree = None;
650                return None;
651            }
652            let idx = lf - 1;
653            self.lastfree = Some(idx);
654            if self.node[idx].key_is_nil() && !self.node_has_chain_link(idx) {
655                return Some(idx);
656            }
657        }
658    }
659
660    fn node_has_chain_link(&self, idx: usize) -> bool {
661        if self.node[idx].next != 0 {
662            return true;
663        }
664        self.find_chain_predecessor(idx).is_some()
665    }
666
667    fn find_chain_predecessor(&self, idx: usize) -> Option<usize> {
668        self.node.iter().enumerate().find(|(prev, node)| {
669            node.next != 0 && (*prev as isize + node.next as isize) == idx as isize
670        }).map(|(prev, _)| prev)
671    }
672
673    fn clear_node(&mut self, idx: usize) {
674        self.node[idx].key = LuaValue::Nil;
675        self.node[idx].value = LuaValue::Nil;
676        self.node[idx].next = 0;
677    }
678
679    fn remove_hash_node(&mut self, idx: usize) {
680        if let Some(prev) = self.find_chain_predecessor(idx) {
681            let next = self.node[idx].next;
682            self.node[prev].next = if next == 0 {
683                0
684            } else {
685                let target = idx as isize + next as isize;
686                (target - prev as isize) as i32
687            };
688            self.clear_node(idx);
689            return;
690        }
691
692        let next = self.node[idx].next;
693        if next == 0 {
694            self.clear_node(idx);
695            return;
696        }
697
698        let next_idx = (idx as isize + next as isize) as usize;
699        let moved_next = self.node[next_idx].next;
700        let moved_key = self.node[next_idx].key_value();
701        let moved_value = self.node[next_idx].value.clone();
702        self.node[idx].key = moved_key;
703        self.node[idx].value = moved_value;
704        self.node[idx].next = if moved_next == 0 {
705            0
706        } else {
707            let target = next_idx as isize + moved_next as isize;
708            (target - idx as isize) as i32
709        };
710        self.clear_node(next_idx);
711    }
712
713    fn clear_dead_hash_node(&mut self, idx: usize) {
714        self.remove_hash_node(idx);
715    }
716
717    fn new_key(&mut self, key: &LuaValue, value: LuaValue) -> Result<(), LuaError> {
718        if matches!(key, LuaValue::Nil) {
719            return Err(LuaError::runtime(format_args!("table index is nil")));
720        }
721        let normalised_key;
722        let key = if let LuaValue::Float(f) = key {
723            let f = *f;
724            if f.is_nan() {
725                return Err(LuaError::runtime(format_args!("table index is NaN")));
726            }
727            let k = f as i64;
728            if k as f64 == f {
729                normalised_key = LuaValue::Int(k);
730                &normalised_key
731            } else { key }
732        } else { key };
733
734        if matches!(value, LuaValue::Nil) { return Ok(()); }
735
736        let mp = self.main_position(key);
737        let mp_occupied = self.is_dummy() || !matches!(self.node[mp].value, LuaValue::Nil);
738        if mp_occupied {
739            let f = self.get_free_pos();
740            let f = match f {
741                None => {
742                    self.rehash(key)?;
743                    return self.set(key, value);
744                }
745                Some(idx) => idx,
746            };
747
748            debug_assert!(!self.is_dummy());
749            let othern = self.main_position_from_node(mp);
750
751            if othern != mp {
752                let mut prev = othern;
753                while (prev as isize + self.node[prev].next as isize) as usize != mp {
754                    prev = (prev as isize + self.node[prev].next as isize) as usize;
755                }
756                self.node[prev].next = (f as isize - prev as isize) as i32;
757                let mp_key = self.node[mp].key_value();
758                let mp_val = self.node[mp].value.clone();
759                let mp_next = self.node[mp].next;
760                self.node[f].key = mp_key;
761                self.node[f].value = mp_val;
762                if mp_next != 0 {
763                    self.node[f].next = mp_next + (mp as isize - f as isize) as i32;
764                    self.node[mp].next = 0;
765                } else {
766                    self.node[f].next = 0;
767                }
768                self.node[mp].value = LuaValue::Nil;
769            } else {
770                if self.node[mp].next != 0 {
771                    let target = (mp as isize + self.node[mp].next as isize) as usize;
772                    self.node[f].next = (target as isize - f as isize) as i32;
773                } else {
774                    debug_assert!(self.node[f].next == 0);
775                }
776                self.node[mp].next = (f as isize - mp as isize) as i32;
777                self.node[f].set_key(key);
778                debug_assert!(matches!(self.node[f].value, LuaValue::Nil));
779                self.node[f].value = value;
780                return Ok(());
781            }
782        }
783        self.node[mp].set_key(key);
784        debug_assert!(matches!(self.node[mp].value, LuaValue::Nil));
785        self.node[mp].value = value;
786        Ok(())
787    }
788
789    fn get_int_slot(&self, key: i64) -> TableSlotRef {
790        let alimit = self.alimit as u64;
791        let uk = key as u64;
792        if uk.wrapping_sub(1) < alimit {
793            return TableSlotRef::Array((key - 1) as usize);
794        }
795        if !self.is_real_asize() && alimit > 0 {
796            let masked = (uk.wrapping_sub(1)) & !(alimit.wrapping_sub(1));
797            if masked < alimit {
798                return TableSlotRef::Array((key - 1) as usize);
799            }
800        }
801        if self.is_dummy() { return TableSlotRef::Absent; }
802        let mut n = self.hash_idx_for_int(key);
803        loop {
804            if self.node[n].key_is_int() && self.node[n].key_int() == key {
805                return TableSlotRef::Hash(n);
806            }
807            let nx = self.node[n].next;
808            if nx == 0 { break; }
809            n = (n as isize + nx as isize) as usize;
810        }
811        TableSlotRef::Absent
812    }
813
814    /// Read an integer key directly to a [`LuaValue`], mirroring C's
815    /// `luaH_getint`. The array-part fast path returns the slot in a
816    /// single bounds-checked load without constructing an intermediate
817    /// [`TableSlotRef`] enum; only when the key falls through to the
818    /// hash part do we walk the chain. Equivalent in observable
819    /// behaviour to `slot_value(get_int_slot(key))`.
820    #[inline]
821    fn get_int_value(&self, key: i64) -> LuaValue {
822        let alimit = self.alimit as u64;
823        let uk = key as u64;
824        if uk.wrapping_sub(1) < alimit {
825            return self.array[(key - 1) as usize].clone();
826        }
827        self.get_int_value_cold(key)
828    }
829
830    #[cold]
831    #[inline(never)]
832    fn get_int_value_cold(&self, key: i64) -> LuaValue {
833        let alimit = self.alimit as u64;
834        let uk = key as u64;
835        if !self.is_real_asize() && alimit > 0 {
836            let masked = (uk.wrapping_sub(1)) & !(alimit.wrapping_sub(1));
837            if masked < alimit {
838                return self.array[(key - 1) as usize].clone();
839            }
840        }
841        if self.is_dummy() { return LuaValue::Nil; }
842        let mut n = self.hash_idx_for_int(key);
843        loop {
844            if self.node[n].key_is_int() && self.node[n].key_int() == key {
845                return self.node[n].value.clone();
846            }
847            let nx = self.node[n].next;
848            if nx == 0 { break; }
849            n = (n as isize + nx as isize) as usize;
850        }
851        LuaValue::Nil
852    }
853
854    fn get_short_str_slot(&self, key: &GcRef<LuaString>) -> TableSlotRef {
855        debug_assert!(key.is_short());
856        if self.is_dummy() { return TableSlotRef::Absent; }
857        let mut n = self.hashpow2_idx(key.hash());
858        loop {
859            if self.node[n].key_is_short_str() {
860                let ks = self.node[n].key_string();
861                if GcRef::ptr_eq(ks, key) || ks.as_bytes() == key.as_bytes() {
862                    return TableSlotRef::Hash(n);
863                }
864            }
865            let nx = self.node[n].next;
866            if nx == 0 { return TableSlotRef::Absent; }
867            n = (n as isize + nx as isize) as usize;
868        }
869    }
870
871    /// Read a short-string key directly to a [`LuaValue`], mirroring the
872    /// shape of [`Self::get_int_value`]: a single hash-chain walk that
873    /// produces the slot's value without constructing an intermediate
874    /// [`TableSlotRef`] enum. Short strings are interned, so pointer
875    /// equality wins almost every comparison; the byte-equality fallback
876    /// handles the rare cross-interning-table path. Callers must ensure
877    /// `key.is_short()` before dispatching here.
878    #[inline]
879    fn get_str_value(&self, key: &GcRef<LuaString>) -> LuaValue {
880        debug_assert!(key.is_short());
881        if self.is_dummy() { return LuaValue::Nil; }
882        let mut n = self.hashpow2_idx(key.hash());
883        loop {
884            if self.node[n].key_is_short_str() {
885                let ks = self.node[n].key_string();
886                if GcRef::ptr_eq(ks, key) || ks.as_bytes() == key.as_bytes() {
887                    return self.node[n].value.clone();
888                }
889            }
890            let nx = self.node[n].next;
891            if nx == 0 { return LuaValue::Nil; }
892            n = (n as isize + nx as isize) as usize;
893        }
894    }
895
896    /// Cold fallback for keys that miss the integer- and short-string
897    /// fast paths in [`LuaTable::get`] (long strings, booleans,
898    /// non-integer floats, table / function keys, light userdata, …).
899    /// Routes through the existing `get_slot` + `slot_value` pair.
900    #[cold]
901    #[inline(never)]
902    fn get_generic_value(&self, key: &LuaValue) -> LuaValue {
903        let slot = self.get_slot(key);
904        self.slot_value(slot)
905    }
906
907    fn get_str_slot(&self, key: &GcRef<LuaString>) -> TableSlotRef {
908        if key.is_short() {
909            self.get_short_str_slot(key)
910        } else {
911            let ko = LuaValue::Str(key.clone());
912            self.get_generic_slot(&ko)
913        }
914    }
915
916    fn get_slot(&self, key: &LuaValue) -> TableSlotRef {
917        match key {
918            LuaValue::Str(s) if s.is_short() => self.get_short_str_slot(s),
919            LuaValue::Int(i) => self.get_int_slot(*i),
920            LuaValue::Nil => TableSlotRef::Absent,
921            LuaValue::Float(f) => {
922                let f = *f;
923                let k = f as i64;
924                if k as f64 == f { self.get_int_slot(k) }
925                else { self.get_generic_slot(key) }
926            }
927            _ => self.get_generic_slot(key),
928        }
929    }
930
931    fn slot_value(&self, slot: TableSlotRef) -> LuaValue {
932        match slot {
933            TableSlotRef::Array(i) => self.array[i].clone(),
934            TableSlotRef::Hash(i) => self.node[i].value.clone(),
935            TableSlotRef::Absent => LuaValue::Nil,
936        }
937    }
938
939    fn finish_set(&mut self, key: &LuaValue, slot: TableSlotRef, value: LuaValue) -> Result<(), LuaError> {
940        match slot {
941            TableSlotRef::Absent => self.new_key(key, value),
942            TableSlotRef::Array(i) => { self.array[i] = value; Ok(()) }
943            TableSlotRef::Hash(i) => { self.node[i].value = value; Ok(()) }
944        }
945    }
946
947    fn set(&mut self, key: &LuaValue, value: LuaValue) -> Result<(), LuaError> {
948        let slot = self.get_slot(key);
949        self.finish_set(key, slot, value)
950    }
951
952    /// Set by integer key. May grow the array part up to
953    /// [`ARRAY_GROW_CAP`] for keys just past `alimit` to amortise the
954    /// common `t[#t+1] = v` pattern.
955    fn set_int(&mut self, key: i64, value: LuaValue) -> Result<(), LuaError> {
956        let slot = self.get_int_slot(key);
957        if matches!(slot, TableSlotRef::Absent) {
958            if key > 0 && (key as u64) <= ARRAY_GROW_CAP as u64 {
959                let cur = self.alimit as i64;
960                if key == cur + 1 && !matches!(value, LuaValue::Nil) {
961                    let new_size = (key as u32).next_power_of_two().max(4);
962                    let capped = new_size.min(ARRAY_GROW_CAP);
963                    if capped > self.alimit {
964                        let nsize = self.alloc_sizenode();
965                        self.resize(capped, nsize)?;
966                        let new_slot = self.get_int_slot(key);
967                        return self.finish_set(&LuaValue::Int(key), new_slot, value);
968                    }
969                }
970            }
971        }
972        match slot {
973            TableSlotRef::Absent => {
974                let k = LuaValue::Int(key);
975                self.new_key(&k, value)
976            }
977            TableSlotRef::Array(i) => { self.array[i] = value; Ok(()) }
978            TableSlotRef::Hash(i) => { self.node[i].value = value; Ok(()) }
979        }
980    }
981
982    /// Write an integer key directly, mirroring C's `luaH_setint`. The
983    /// array-part fast path writes the slot in a single bounds-checked
984    /// store with no intermediate [`TableSlotRef`] enum and no second
985    /// lookup; only when the key falls outside `alimit` do we route
986    /// through the cold helper which handles the alimit-aliased slot,
987    /// hash-part walk, array-grow-on-`#t+1`, and `new_key` insertion.
988    /// Caller is responsible for rejecting `Nil` / `NaN` keys and for
989    /// firing the GC write barrier on the value.
990    #[inline]
991    fn set_int_value(&mut self, key: i64, value: LuaValue) -> Result<(), LuaError> {
992        let alimit = self.alimit as u64;
993        let uk = key as u64;
994        if uk.wrapping_sub(1) < alimit {
995            self.array[(key - 1) as usize] = value;
996            return Ok(());
997        }
998        self.set_int_value_cold(key, value)
999    }
1000
1001    /// Integer-key entry used by [`LuaTable::try_raw_set`] /
1002    /// [`LuaTable::try_raw_set_int`]. The array fast path writes
1003    /// directly into a slot that is by definition already allocated
1004    /// (it lives inside the `Vec` at offset `key-1 < alimit`), so the
1005    /// `TOTAL_GROW_CAP` guard cannot apply. Only the cold path can
1006    /// allocate; the guard runs there.
1007    #[inline]
1008    fn try_raw_set_int_fast(&mut self, key: i64, value: LuaValue) -> Result<(), LuaError> {
1009        let alimit = self.alimit as u64;
1010        let uk = key as u64;
1011        if uk.wrapping_sub(1) < alimit {
1012            self.array[(key - 1) as usize] = value;
1013            return Ok(());
1014        }
1015        self.try_raw_set_int_cold(key, value)
1016    }
1017
1018    #[cold]
1019    #[inline(never)]
1020    fn try_raw_set_int_cold(&mut self, key: i64, value: LuaValue) -> Result<(), LuaError> {
1021        if self.array.len() + self.node.len() >= TOTAL_GROW_CAP
1022            && matches!(self.get_int_slot(key), TableSlotRef::Absent)
1023        {
1024            return Err(LuaError::Memory);
1025        }
1026        self.set_int_value_cold(key, value)
1027    }
1028
1029    /// Cold fallback for [`Self::set_int_value`]: handles the
1030    /// alimit-aliased slot (non-real-`asize` tables), hash-part lookup
1031    /// + in-place store, the array-grow-on-`#t+1` heuristic, and
1032    /// `new_key` insertion. Split into a `#[cold] #[inline(never)]`
1033    /// helper so LLVM lays out the array fast path as straight-line
1034    /// code in the inlined caller.
1035    #[cold]
1036    #[inline(never)]
1037    fn set_int_value_cold(&mut self, key: i64, value: LuaValue) -> Result<(), LuaError> {
1038        let alimit = self.alimit as u64;
1039        let uk = key as u64;
1040        if !self.is_real_asize() && alimit > 0 {
1041            let masked = (uk.wrapping_sub(1)) & !(alimit.wrapping_sub(1));
1042            if masked < alimit {
1043                self.array[(key - 1) as usize] = value;
1044                return Ok(());
1045            }
1046        }
1047        if !self.is_dummy() {
1048            let mut n = self.hash_idx_for_int(key);
1049            loop {
1050                if self.node[n].key_is_int() && self.node[n].key_int() == key {
1051                    self.node[n].value = value;
1052                    return Ok(());
1053                }
1054                let nx = self.node[n].next;
1055                if nx == 0 { break; }
1056                n = (n as isize + nx as isize) as usize;
1057            }
1058        }
1059        if key > 0 && (key as u64) <= ARRAY_GROW_CAP as u64 {
1060            let cur = self.alimit as i64;
1061            if key == cur + 1 && !matches!(value, LuaValue::Nil) {
1062                let new_size = (key as u32).next_power_of_two().max(4);
1063                let capped = new_size.min(ARRAY_GROW_CAP);
1064                if capped > self.alimit {
1065                    let nsize = self.alloc_sizenode();
1066                    self.resize(capped, nsize)?;
1067                    let new_slot = self.get_int_slot(key);
1068                    return self.finish_set(&LuaValue::Int(key), new_slot, value);
1069                }
1070            }
1071        }
1072        let k = LuaValue::Int(key);
1073        self.new_key(&k, value)
1074    }
1075
1076    // ── boundary search ────────────────────────────────────────────────────
1077
1078    fn hash_search(&self, mut j: u64) -> u64 {
1079        let mut i: u64;
1080        if j == 0 { j = 1; }
1081        loop {
1082            i = j;
1083            if j <= (i64::MAX as u64) / 2 {
1084                j *= 2;
1085            } else {
1086                j = i64::MAX as u64;
1087                let s = self.get_int_slot(j as i64);
1088                if matches!(s, TableSlotRef::Absent)
1089                    || matches!(self.slot_value(s), LuaValue::Nil)
1090                {
1091                    break;
1092                } else { return j; }
1093            }
1094            let s = self.get_int_slot(j as i64);
1095            if matches!(s, TableSlotRef::Absent) { break; }
1096            if matches!(self.slot_value(s), LuaValue::Nil) { break; }
1097        }
1098        while j - i > 1 {
1099            let m = i / 2 + j / 2;
1100            let s = self.get_int_slot(m as i64);
1101            let empty = matches!(s, TableSlotRef::Absent)
1102                || matches!(self.slot_value(s), LuaValue::Nil);
1103            if empty { j = m; } else { i = m; }
1104        }
1105        i
1106    }
1107
1108    fn bin_search(array: &[LuaValue], mut i: u32, mut j: u32) -> u32 {
1109        while j - i > 1 {
1110            let m = (i + j) / 2;
1111            if matches!(array[(m - 1) as usize], LuaValue::Nil) { j = m; }
1112            else { i = m; }
1113        }
1114        i
1115    }
1116
1117    /// Find a boundary `i` such that `t[i]` is present and `t[i+1]` is absent,
1118    /// or 0 if `t[1]` is absent. C: `luaH_getn`.
1119    fn getn(&mut self) -> u64 {
1120        let limit = self.alimit;
1121        if limit > 0 && matches!(self.array[(limit - 1) as usize], LuaValue::Nil) {
1122            if limit >= 2 && !matches!(self.array[(limit - 2) as usize], LuaValue::Nil) {
1123                if self.is_pow2_real_asize() && !Self::is_pow2(limit - 1) {
1124                    self.alimit = limit - 1;
1125                    self.flags.set_no_real_asize();
1126                }
1127                return (limit - 1) as u64;
1128            } else {
1129                let boundary = Self::bin_search(&self.array, 0, limit);
1130                if self.is_pow2_real_asize() && boundary > self.real_asize() / 2 {
1131                    self.alimit = boundary;
1132                    self.flags.set_no_real_asize();
1133                }
1134                return boundary as u64;
1135            }
1136        }
1137        if !self.limit_equals_asize() {
1138            if matches!(self.array[limit as usize], LuaValue::Nil) {
1139                return limit as u64;
1140            }
1141            let real = self.real_asize();
1142            if matches!(self.array[(real - 1) as usize], LuaValue::Nil) {
1143                let old_alimit = self.alimit;
1144                let boundary = Self::bin_search(&self.array, old_alimit, real);
1145                self.alimit = boundary;
1146                return boundary as u64;
1147            }
1148        }
1149        let limit = self.real_asize();
1150        debug_assert!(
1151            limit == self.real_asize()
1152                && (limit == 0 || !matches!(self.array[(limit - 1) as usize], LuaValue::Nil))
1153        );
1154        let next_key = (limit as i64).saturating_add(1);
1155        let next_slot = self.get_int_slot(next_key);
1156        let next_empty = matches!(next_slot, TableSlotRef::Absent)
1157            || matches!(self.slot_value(next_slot), LuaValue::Nil);
1158        if self.is_dummy() || next_empty {
1159            return limit as u64;
1160        }
1161        self.hash_search(limit as u64)
1162    }
1163}
1164
1165// ── LuaTable (outer handle) ────────────────────────────────────────────────────
1166
1167/// A Lua table: hybrid array + hash map.
1168///
1169/// All public methods take `&self` so the type works through
1170/// `GcRef<LuaTable>` (which only dereferences to a shared borrow).
1171/// Mutations are routed through an internal [`RefCell`].
1172#[derive(Debug)]
1173pub struct LuaTable {
1174    inner: RefCell<TableInner>,
1175    metatable: RefCell<Option<GcRef<LuaTable>>>,
1176    weak_mode: Cell<u8>,
1177}
1178
1179impl std::fmt::Debug for TableInner {
1180    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1181        f.debug_struct("TableInner")
1182            .field("alimit", &self.alimit)
1183            .field("array_len", &self.array.len())
1184            .field("node_len", &self.node.len())
1185            .finish()
1186    }
1187}
1188
1189impl Default for LuaTable {
1190    fn default() -> Self {
1191        LuaTable {
1192            inner: RefCell::new(TableInner::new()),
1193            metatable: RefCell::new(None),
1194            weak_mode: Cell::new(0),
1195        }
1196    }
1197}
1198
1199impl LuaTable {
1200    /// Construct an empty table. Used as a placeholder by callers that
1201    /// will populate it via the normal API.
1202    pub fn placeholder() -> Self { Self::default() }
1203
1204    /// Borrow inner storage for read access. Intended for advanced
1205    /// callers (e.g. the GC trace impl); prefer the typed methods.
1206    pub fn with_inner<R>(&self, f: impl FnOnce(&TableInner) -> R) -> R {
1207        f(&self.inner.borrow())
1208    }
1209
1210    /// Read a key. Returns `LuaValue::Nil` if absent or if `k` is nil.
1211    /// Integer keys take the direct array-part fast path used by
1212    /// [`LuaTable::get_int`]; short-string keys take the analogous
1213    /// hash-chain fast path used by [`LuaTable::get_short_str`]; every
1214    /// other key shape falls through to the cold generic slot lookup.
1215    /// Marked `#[inline(always)]` so the dispatch folds into the
1216    /// caller (the hot `state::fast_get` / `state::table_get_with_tm`
1217    /// frames in the VM); profiling at #[inline] showed LLVM was still
1218    /// emitting a cross-crate function call here.
1219    #[inline(always)]
1220    pub fn get(&self, k: &LuaValue) -> LuaValue {
1221        let inner = self.inner.borrow();
1222        match k {
1223            LuaValue::Nil => LuaValue::Nil,
1224            LuaValue::Int(i) => inner.get_int_value(*i),
1225            LuaValue::Str(s) if s.is_short() => inner.get_str_value(s),
1226            _ => inner.get_generic_value(k),
1227        }
1228    }
1229
1230    /// Read by integer key. Hot path: callers like `state.fast_get_int`
1231    /// and `state.table_get_with_tm` dispatch here on every integer-key
1232    /// access in user code (`t[1]`, `OP_GETI`, ipairs loops, etc.). The
1233    /// array-part lookup folds into a single bounds-checked load,
1234    /// matching C's `luaH_getint`.
1235    #[inline(always)]
1236    pub fn get_int(&self, key: i64) -> LuaValue {
1237        let inner = self.inner.borrow();
1238        inner.get_int_value(key)
1239    }
1240
1241    /// Read by string key. Despite the name (kept for compatibility
1242    /// with the old API), this dispatches internally to either the
1243    /// short- or long-string path; passing a long string is safe. The
1244    /// short-string branch (the common case — all interned identifiers
1245    /// and most table-field keys are short) takes the folded hash-walk
1246    /// in [`TableInner::get_str_value`]; long strings still go through
1247    /// the slot indirection.
1248    #[inline(always)]
1249    pub fn get_short_str(&self, k: &GcRef<LuaString>) -> LuaValue {
1250        let inner = self.inner.borrow();
1251        if k.is_short() {
1252            inner.get_str_value(k)
1253        } else {
1254            let slot = inner.get_str_slot(k);
1255            inner.slot_value(slot)
1256        }
1257    }
1258
1259    /// Read by raw byte-string key. Linear scan over the hash part —
1260    /// rarely-used helper for callers that don't have a `GcRef<LuaString>`
1261    /// handle.
1262    pub fn get_str_bytes(&self, key_bytes: &[u8]) -> LuaValue {
1263        let mut found = LuaValue::Nil;
1264        self.for_each_entry(|k, v| {
1265            if !matches!(found, LuaValue::Nil) { return; }
1266            if let LuaValue::Str(s) = k {
1267                if s.as_bytes() == key_bytes {
1268                    found = v.clone();
1269                }
1270            }
1271        });
1272        found
1273    }
1274
1275    /// Raw set without metamethod dispatch. Nil keys are an error;
1276    /// NaN-float keys are an error. Setting `v == Nil` clears the slot.
1277    pub fn raw_set(&self, k: LuaValue, v: LuaValue) {
1278        if matches!(k, LuaValue::Nil) { return; }
1279        if let LuaValue::Float(f) = &k {
1280            if f.is_nan() { return; }
1281        }
1282        let mut inner = self.inner.borrow_mut();
1283        let _ = inner.set(&k, v);
1284    }
1285
1286    /// Raw set with explicit error returns; preferred path used by
1287    /// `LuaTableRefExt::raw_set` in `lua-vm`. Integer keys (and floats
1288    /// that are exact integers) take the same direct array-part fast
1289    /// path used by [`LuaTable::try_raw_set_int`]; other key shapes
1290    /// fall through to the generic slot lookup.
1291    #[inline]
1292    pub fn try_raw_set(&self, k: LuaValue, v: LuaValue) -> Result<(), LuaError> {
1293        match &k {
1294            LuaValue::Nil => {
1295                Err(LuaError::runtime(format_args!("table index is nil")))
1296            }
1297            LuaValue::Float(f) if f.is_nan() => {
1298                Err(LuaError::runtime(format_args!("table index is NaN")))
1299            }
1300            LuaValue::Int(i) => {
1301                let key = *i;
1302                let mut inner = self.inner.borrow_mut();
1303                inner.try_raw_set_int_fast(key, v)
1304            }
1305            LuaValue::Float(f) => {
1306                let f = *f;
1307                let k_int = f as i64;
1308                if k_int as f64 == f {
1309                    let mut inner = self.inner.borrow_mut();
1310                    inner.try_raw_set_int_fast(k_int, v)
1311                } else {
1312                    self.try_raw_set_generic(k, v)
1313                }
1314            }
1315            _ => self.try_raw_set_generic(k, v),
1316        }
1317    }
1318
1319    /// Generic-key path for [`Self::try_raw_set`]. Split out so the
1320    /// integer fast path stays branch-light and inlineable.
1321    #[cold]
1322    #[inline(never)]
1323    fn try_raw_set_generic(&self, k: LuaValue, v: LuaValue) -> Result<(), LuaError> {
1324        let mut inner = self.inner.borrow_mut();
1325        if inner.array.len() + inner.node.len() >= TOTAL_GROW_CAP
1326            && matches!(inner.get_slot(&k), TableSlotRef::Absent)
1327        {
1328            return Err(LuaError::Memory);
1329        }
1330        inner.set(&k, v)
1331    }
1332
1333    /// Raw set by integer key with explicit error returns. Routes the
1334    /// array-part fast path through [`TableInner::set_int_value`] — a
1335    /// single bounds-checked store with no intermediate
1336    /// [`TableSlotRef`] indirection — and only consults the
1337    /// `TOTAL_GROW_CAP` allocation guard when the key would create a
1338    /// new slot.
1339    #[inline]
1340    pub fn try_raw_set_int(&self, k: i64, v: LuaValue) -> Result<(), LuaError> {
1341        let mut inner = self.inner.borrow_mut();
1342        inner.try_raw_set_int_fast(k, v)
1343    }
1344
1345    /// Resize the table to new array and hash sizes (sizing hint from
1346    /// the bytecode's `OP_NEWTABLE`).
1347    pub fn resize(&self, new_asize: u32, new_hsize: u32) -> Result<(), LuaError> {
1348        let mut inner = self.inner.borrow_mut();
1349        inner.resize(new_asize, new_hsize)
1350    }
1351
1352    /// Number of array-part slots currently allocated. Cheap counter
1353    /// for sizing decisions; NOT the Lua `#t` length operator.
1354    pub fn array_len(&self) -> usize { self.inner.borrow().array.len() }
1355
1356    /// Total occupied slots (array + hash) — used for legacy
1357    /// `len()` callers; prefer `getn` for the Lua `#` operator.
1358    pub fn len(&self) -> usize {
1359        let inner = self.inner.borrow();
1360        let mut n = 0usize;
1361        for v in inner.array.iter() {
1362            if !matches!(v, LuaValue::Nil) { n += 1; }
1363        }
1364        for node in inner.node.iter() {
1365            if !matches!(node.value, LuaValue::Nil) { n += 1; }
1366        }
1367        n
1368    }
1369    pub fn is_empty(&self) -> bool { self.len() == 0 }
1370
1371    /// `#t` boundary (C: `luaH_getn`). Mutates internal caching state.
1372    pub fn getn(&self) -> u64 {
1373        let mut inner = self.inner.borrow_mut();
1374        inner.getn()
1375    }
1376
1377    /// Returns true iff `k` resolves to a slot in this table (array or
1378    /// hash). Used by `next` to validate the resumption key.
1379    pub fn contains_key(&self, k: &LuaValue) -> bool {
1380        if matches!(k, LuaValue::Nil) { return false; }
1381        let inner = self.inner.borrow();
1382        let slot = inner.get_slot(k);
1383        !matches!(slot, TableSlotRef::Absent)
1384    }
1385
1386    pub fn metatable(&self) -> Option<GcRef<LuaTable>> {
1387        self.metatable.borrow().clone()
1388    }
1389
1390    /// Install a metatable. Inspects its `__mode` field eagerly so the
1391    /// GC trace impl can read [`weak_mode`] without re-entering the
1392    /// metatable RefCell.
1393    pub fn set_metatable(&self, mt: Option<GcRef<LuaTable>>) {
1394        let mode = mt.as_ref().map(|t| extract_weak_mode(t)).unwrap_or(0);
1395        self.weak_mode.set(mode);
1396        *self.metatable.borrow_mut() = mt;
1397    }
1398
1399    pub fn weak_mode(&self) -> u8 { self.weak_mode.get() }
1400
1401    /// Implements Lua's `next(t, k)`.
1402    pub fn next_pair(&self, k: &LuaValue) -> Option<(LuaValue, LuaValue)> {
1403        let inner = self.inner.borrow();
1404        inner.next_pair(k).ok().flatten()
1405    }
1406
1407    /// Like [`next_pair`] but reports the `"invalid key to 'next'"`
1408    /// error when `k` is non-nil and not present.
1409    pub fn try_next_pair(&self, k: &LuaValue) -> Result<Option<(LuaValue, LuaValue)>, LuaError> {
1410        let inner = self.inner.borrow();
1411        inner.next_pair(k)
1412    }
1413
1414    /// Walk every live (key, value) pair via the given closure.
1415    /// Used by the GC trace impl to avoid the overhead of repeatedly
1416    /// re-entering `find_index` from `next_pair`.
1417    pub fn for_each_entry(&self, mut f: impl FnMut(&LuaValue, &LuaValue)) {
1418        let inner = self.inner.borrow();
1419        for (i, v) in inner.array.iter().enumerate() {
1420            if !matches!(v, LuaValue::Nil) {
1421                let k = LuaValue::Int((i + 1) as i64);
1422                f(&k, v);
1423            }
1424        }
1425        for node in inner.node.iter() {
1426            if !matches!(node.value, LuaValue::Nil) {
1427                f(&node.key, &node.value);
1428            }
1429        }
1430    }
1431
1432    /// Drop weak entries whose weakly-tracked target is unreachable,
1433    /// and return the list of values whose strings must still be
1434    /// marked by the caller.
1435    pub fn prune_weak_dead(&self, is_reachable: &dyn Fn(usize) -> bool) -> Vec<LuaValue> {
1436        let mode = self.weak_mode.get();
1437        if mode == 0 { return Vec::new(); }
1438        let weak_k = (mode & WEAK_KEYS) != 0;
1439        let weak_v = (mode & WEAK_VALUES) != 0;
1440        let mut to_mark: Vec<LuaValue> = Vec::new();
1441        let mut inner = self.inner.borrow_mut();
1442        for i in 0..inner.array.len() {
1443            let v = inner.array[i].clone();
1444            if matches!(v, LuaValue::Nil) { continue; }
1445            if weak_v && value_is_dead_collectable(&v, is_reachable) {
1446                inner.array[i] = LuaValue::Nil;
1447                continue;
1448            }
1449            if weak_v {
1450                if matches!(v, LuaValue::Str(_)) { to_mark.push(v); }
1451            }
1452        }
1453        let mut i = 0;
1454        while i < inner.node.len() {
1455            let v = inner.node[i].value.clone();
1456            if matches!(v, LuaValue::Nil) {
1457                i += 1;
1458                continue;
1459            }
1460            let k = inner.node[i].key.clone();
1461            if weak_v && value_is_dead_collectable(&v, is_reachable) {
1462                inner.clear_dead_hash_node(i);
1463                continue;
1464            }
1465            if weak_k && value_is_dead_collectable(&k, is_reachable) {
1466                inner.clear_dead_hash_node(i);
1467                continue;
1468            }
1469            if weak_k {
1470                if matches!(k, LuaValue::Str(_)) { to_mark.push(k); }
1471            }
1472            if weak_v {
1473                if matches!(v, LuaValue::Str(_)) { to_mark.push(v); }
1474            }
1475            i += 1;
1476        }
1477        to_mark
1478    }
1479
1480    /// Ephemeron-convergence helper for pure `__mode = "k"` tables.
1481    pub fn ephemeron_values_to_mark(&self, is_reachable: &dyn Fn(usize) -> bool) -> Vec<LuaValue> {
1482        let mode = self.weak_mode.get();
1483        if (mode & WEAK_KEYS) == 0 || (mode & WEAK_VALUES) != 0 {
1484            return Vec::new();
1485        }
1486        let inner = self.inner.borrow();
1487        let mut out = Vec::new();
1488        for node in inner.node.iter() {
1489            if matches!(node.value, LuaValue::Nil) { continue; }
1490            if !value_is_dead_collectable(&node.key, is_reachable) {
1491                out.push(node.value.clone());
1492            }
1493        }
1494        for (i, v) in inner.array.iter().enumerate() {
1495            if matches!(v, LuaValue::Nil) { continue; }
1496            let k = LuaValue::Int((i + 1) as i64);
1497            if !value_is_dead_collectable(&k, is_reachable) {
1498                out.push(v.clone());
1499            }
1500        }
1501        out
1502    }
1503}
1504
1505// ── Free helpers ──────────────────────────────────────────────────────────────
1506
1507/// True iff `v` is a collectable non-string LuaValue whose target was
1508/// unreached during the mark phase. Strings are explicitly excluded.
1509fn value_is_dead_collectable(v: &LuaValue, is_reachable: &dyn Fn(usize) -> bool) -> bool {
1510    match v {
1511        LuaValue::Table(t) => !is_reachable(t.identity()),
1512        LuaValue::UserData(u) => !is_reachable(u.identity()),
1513        LuaValue::Thread(th) => !is_reachable(th.identity()),
1514        LuaValue::Function(c) => match c {
1515            LuaClosure::Lua(x) => !is_reachable(x.identity()),
1516            LuaClosure::C(x) => !is_reachable(x.identity()),
1517            LuaClosure::LightC(_) => false,
1518        },
1519        LuaValue::Str(_)
1520        | LuaValue::Nil
1521        | LuaValue::Bool(_)
1522        | LuaValue::Int(_)
1523        | LuaValue::Float(_)
1524        | LuaValue::LightUserData(_) => false,
1525    }
1526}
1527
1528/// Inspect a metatable's `__mode` field and produce the corresponding
1529/// `WEAK_KEYS | WEAK_VALUES` bitmask. Returns 0 when no `__mode` is
1530/// set or it is not a string.
1531fn extract_weak_mode(mt: &LuaTable) -> u8 {
1532    let inner = mt.inner.borrow();
1533    for node in inner.node.iter() {
1534        if let LuaValue::Str(ks) = &node.key {
1535            if ks.as_bytes() == b"__mode" {
1536                if let LuaValue::Str(vs) = &node.value {
1537                    let bytes = vs.as_bytes();
1538                    let mut mode = 0u8;
1539                    if bytes.iter().any(|b| *b == b'k') { mode |= WEAK_KEYS; }
1540                    if bytes.iter().any(|b| *b == b'v') { mode |= WEAK_VALUES; }
1541                    return mode;
1542                }
1543                return 0;
1544            }
1545        }
1546    }
1547    0
1548}
1549
1550// ──────────────────────────────────────────────────────────────────────────────
1551// PORT STATUS
1552//   source:        src/ltable.c (~995 lines, 28 functions), src/ltable.h
1553//   target_crate:  lua-types
1554//   confidence:    high
1555//   todos:         0
1556//   port_notes:    0
1557//   unsafe_blocks: 0
1558//   notes:         Canonical LuaTable: hybrid array + hash. Mirrors C's Table
1559//                  struct (flags, lsizenode, alimit, array, node, lastfree) with
1560//                  Vec<LuaValue> + Vec<TableNode> in place of raw C pointers, and
1561//                  Option<usize> indexing in place of Node*. The luaH_getn
1562//                  boundary search + alimit-aware integer-key fast path are
1563//                  ported faithfully (see getn() and get_int_slot()). The
1564//                  integer-key read path also exposes get_int_value, which
1565//                  mirrors C's luaH_getint by returning the array slot directly
1566//                  in one bounds-checked load (no TableSlotRef indirection) and
1567//                  splitting the rare alimit-aliased / hash-part path into a
1568//                  cold helper. The short-string read path mirrors that shape
1569//                  via get_str_value (single hash-chain walk, no TableSlotRef
1570//                  round-trip); LuaTable::get dispatches on integer/short-string
1571//                  keys inline and routes everything else through a #[cold]
1572//                  get_generic_value, matching C's luaH_get fast-path structure.
1573//                  LuaTable::get / get_int / get_short_str are #[inline(always)]
1574//                  so the dispatch folds into the cross-crate VM hot frames
1575//                  (state::fast_get / state::table_get_with_tm). Weak-table mode
1576//                  flags + the prune_weak_dead / ephemeron_values_to_mark
1577//                  helpers integrate with the lua-gc Trace impl.
1578// ──────────────────────────────────────────────────────────────────────────────