Skip to main content

luau_vm/table/
mod.rs

1use core::mem;
2use core::ptr::{self, NonNull};
3
4mod layout;
5mod lookup;
6
7pub use layout::{
8    LuaNode, LuaNodeCursor, RAW_LUA_NODE_DUMMY, RAW_TKEY_DEAD_KEY, RAW_TKEY_NIL, RawLuaNode,
9    RawTKey, TKey,
10};
11
12use crate::VmErrorResult;
13use crate::debug::DebugRuntime;
14use crate::gc::GcBarrier;
15use crate::gc::{GcObject, RawGcObject};
16use crate::handle::RawHandle;
17use crate::handle::sealed::Sealed;
18use crate::memory::{LuaPage, MemoryRuntime};
19use crate::string::TString;
20use crate::thread::Thread;
21use crate::types::{LUA_TNUMBER, LUA_TTABLE};
22use crate::value::{RAW_TVALUE_NIL, RawTValue, TValue, TValueCursor, nil_object};
23
24#[repr(C)]
25pub struct RawLuaTable {
26    pub tt: u8,
27    pub marked: u8,
28    pub memcat: u8,
29    pub tm_cache: u8,
30    pub readonly: u8,
31    pub safe_env: u8,
32    pub lsize_node: u8,
33    pub node_mask_8: u8,
34    pub size_array: i32,
35    pub free: RawLuaTableFree,
36    pub metatable: *mut RawLuaTable,
37    pub array: *mut RawTValue,
38    pub node: *mut RawLuaNode,
39    pub gc_list: *mut RawGcObject,
40}
41
42#[repr(C)]
43pub union RawLuaTableFree {
44    pub last_free: i32,
45    pub aboundary: i32,
46}
47
48const LOG_2: [u8; 256] = [
49    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,
50    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,
51    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,
52    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,
53    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,
54    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,
55    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,
56    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,
57];
58
59/// `luaO_log2`
60fn log_2(mut value: u32) -> i32 {
61    let mut log = -1;
62    while value >= 256 {
63        log += 8;
64        value >>= 8;
65    }
66    log + i32::from(LOG_2[value as usize])
67}
68
69/// `ceillog2`
70pub(super) fn ceil_log_2(value: u32) -> i32 {
71    log_2(value - 1) + 1
72}
73
74#[derive(Clone, Copy, PartialEq, Eq)]
75#[repr(transparent)]
76/// Non-owning identity of a VM table record.
77///
78/// # Safety model for unsafe methods
79///
80/// The table and referenced storage must remain live in its owning VM. Keys,
81/// values, nodes, and indices must match the current allocation; resize or
82/// free operations invalidate derived cursors and views. Mutation must
83/// preserve hashing, readonly, metatable-cache, and GC barrier invariants.
84pub struct Table {
85    pub(crate) raw: NonNull<RawLuaTable>,
86}
87
88/// Unstable table allocation and mutation capability.
89///
90/// # Safety
91///
92/// Tables, keys, values, cursors, and pages must be live records from this
93/// thread's VM. Stack cursors and storage indices must be in bounds, and all
94/// resize, hash, readonly, metatable, and GC barrier invariants must hold.
95#[allow(
96    clippy::missing_safety_doc,
97    reason = "all methods share the capability-level safety contract"
98)]
99pub trait TableRuntime: Sealed {
100    unsafe fn set_num(&self, table: Table, key: i32) -> VmErrorResult<TValue>;
101    unsafe fn set_str(&self, table: Table, key: TString) -> VmErrorResult<LuaNodeCursor>;
102    unsafe fn setp(&self, table: Table, key: *mut (), tag: i32) -> VmErrorResult<TValue>;
103    unsafe fn set(&self, table: Table, key: TValue) -> VmErrorResult<TValue>;
104    unsafe fn set_slot(&self, table: Table, slot: TValue, key: TValue) -> VmErrorResult<TValue>;
105    unsafe fn new_key(&self, table: Table, key: TValue) -> VmErrorResult<TValue>;
106    unsafe fn next_internal(&self, table: Table, key: TValueCursor) -> VmErrorResult<i32>;
107    unsafe fn new_table_internal(&self, n_array: i32, n_hash: i32) -> VmErrorResult<Table>;
108    unsafe fn resize_array(&self, table: Table, n_array: i32) -> VmErrorResult;
109    unsafe fn resize_hash(&self, table: Table, n_hash: i32) -> VmErrorResult;
110    unsafe fn free_table(&self, table: Table, page: LuaPage);
111    unsafe fn clone_table_internal(&self, table: Table) -> VmErrorResult<Table>;
112}
113
114impl crate::handle::sealed::Sealed for Table {}
115
116impl RawHandle for Table {
117    type Raw = RawLuaTable;
118
119    fn as_ptr(&self) -> *mut Self::Raw {
120        self.raw.as_ptr()
121    }
122}
123
124impl AsRef<Table> for Table {
125    fn as_ref(&self) -> &Table {
126        self
127    }
128}
129
130const MAX_BITS: i32 = 26;
131const MAX_SIZE: i32 = 1 << MAX_BITS;
132
133/// `twoto`
134fn twoto(value: i32) -> i32 {
135    1 << value
136}
137
138/// `countint`
139fn count_int(key: f64, nums: &mut [i32; (MAX_BITS + 1) as usize]) -> i32 {
140    match Table::array_index(key) {
141        Some(index) if index > 0 && index <= MAX_SIZE => {
142            nums[ceil_log_2(index as u32) as usize] += 1;
143            1
144        }
145        _ => 0,
146    }
147}
148
149/// `computesizes`
150fn compute_sizes(nums: &[i32; (MAX_BITS + 1) as usize], n_array: &mut i32) -> i32 {
151    let mut a = 0;
152    let mut na = 0;
153    let mut n = 0;
154    let mut twotoi = 1;
155    let mut index = 0;
156
157    while twotoi / 2 < *n_array {
158        if nums[index] > 0 {
159            a += nums[index];
160            if a > twotoi / 2 {
161                n = twotoi;
162                na = a;
163            }
164        }
165
166        if a == *n_array {
167            break;
168        }
169
170        index += 1;
171        twotoi *= 2;
172    }
173
174    *n_array = n;
175    debug_assert!(*n_array / 2 <= na && na <= *n_array);
176    na
177}
178
179#[allow(
180    clippy::missing_safety_doc,
181    reason = "Table's shared raw-handle contract is documented on Table"
182)]
183impl Table {
184    /// `numusearray`
185    fn num_use_array(&self, nums: &mut [i32; (MAX_BITS + 1) as usize]) -> i32 {
186        let mut ause = 0;
187        let mut index = 1;
188        let mut ttlg = 1;
189        let size_array = unsafe { self.as_ptr().as_ref().unwrap_unchecked().size_array };
190        let array = unsafe { self.array_cursor() };
191
192        for lg in 0..=MAX_BITS {
193            let mut lc = 0;
194            let mut limit = ttlg;
195            if limit > size_array {
196                limit = size_array;
197                if index > limit {
198                    break;
199                }
200            }
201
202            while index <= limit {
203                if unsafe { !array.add((index - 1) as usize).is_nil_unchecked() } {
204                    lc += 1;
205                }
206                index += 1;
207            }
208
209            nums[lg as usize] += lc;
210            ause += lc;
211            ttlg *= 2;
212        }
213
214        ause
215    }
216
217    /// `numusehash`
218    fn num_use_hash(&self, nums: &mut [i32; (MAX_BITS + 1) as usize], n_array: &mut i32) -> i32 {
219        let mut total_use = 0;
220        let mut ause = 0;
221        let mut index = unsafe { self.node_count() };
222
223        while index > 0 {
224            index -= 1;
225            let node = unsafe { self.node(index as i32) };
226            if !node.value_unchecked().is_nil() {
227                let key = node.key();
228                if key.tt() == LUA_TNUMBER {
229                    ause += count_int(key.number_value(), nums);
230                }
231                total_use += 1;
232            }
233        }
234
235        *n_array += ause;
236        total_use
237    }
238
239    /// `setarrayvector`
240    unsafe fn set_array_vector(&self, thread: &Thread, size: i32) -> VmErrorResult {
241        if size > MAX_SIZE {
242            return unsafe { crate::run_error!(thread, "table overflow") };
243        }
244
245        unsafe {
246            let old_size = self.as_ptr().as_ref().unwrap_unchecked().size_array as usize;
247            let new_size = size as usize;
248            let memcat = self.as_ptr().as_ref().unwrap_unchecked().memcat;
249            let array = TValueCursor::from_ptr(thread.realloc_array(
250                self.as_ptr().as_ref().unwrap_unchecked().array,
251                old_size,
252                new_size,
253                memcat,
254            )?);
255
256            for index in old_size..new_size {
257                array.add(index).value_unchecked().set_nil();
258            }
259            self.set_array(array);
260            self.as_ptr().as_mut().unwrap_unchecked().size_array = size;
261        }
262        Ok(())
263    }
264
265    /// `setnodevector`
266    unsafe fn set_node_vector(&self, thread: &Thread, size: i32) -> VmErrorResult {
267        unsafe {
268            let (node, lsize, count) = if size == 0 {
269                (Table::dummy_node_cursor(), 0, 0usize)
270            } else {
271                let lsize = ceil_log_2(size as u32);
272                if lsize > MAX_BITS {
273                    return crate::run_error!(thread, "table overflow");
274                }
275
276                let count = twoto(lsize) as usize;
277                let node = LuaNodeCursor::from_ptr(thread.new_array::<RawLuaNode>(
278                    count,
279                    self.as_ptr().as_ref().unwrap_unchecked().memcat,
280                )?);
281
282                for index in 0..count {
283                    let current_cursor = node.add(index);
284                    let current_node = current_cursor.node_unchecked();
285                    current_node.key().set_nil();
286                    current_node.value_unchecked().set_nil();
287                }
288
289                (node, lsize, count)
290            };
291
292            self.set_node(node);
293            self.as_ptr().as_mut().unwrap_unchecked().lsize_node = lsize as u8;
294            self.as_ptr().as_mut().unwrap_unchecked().node_mask_8 = ((1 << lsize) - 1) as u8;
295            self.as_ptr().as_mut().unwrap_unchecked().free = RawLuaTableFree {
296                last_free: count as i32,
297            };
298        }
299        Ok(())
300    }
301
302    /// `arrayornewkey`
303    unsafe fn array_or_new_key(&self, thread: &Thread, key: TValue) -> VmErrorResult<TValue> {
304        unsafe {
305            if key.is_number() {
306                let Some(index) = Table::array_index(key.number_value()) else {
307                    return self.new_key(thread, key);
308                };
309                if let Some(slot) = self.array_slot_for_key(index) {
310                    return Ok(slot);
311                }
312            }
313
314            self.new_key(thread, key)
315        }
316    }
317
318    /// `resize`
319    unsafe fn resize(&self, thread: &Thread, n_array: i32, n_hash: i32) -> VmErrorResult {
320        unsafe {
321            if n_array > MAX_SIZE || n_hash > MAX_SIZE {
322                return crate::run_error!(thread, "table overflow");
323            }
324
325            let old_array_size = self.as_ptr().as_ref().unwrap_unchecked().size_array;
326            let old_hash_lsize = self.as_ptr().as_ref().unwrap_unchecked().lsize_node as i32;
327            let old_nodes = self.node_cursor();
328
329            if n_array > old_array_size {
330                self.set_array_vector(thread, n_array)?;
331            }
332
333            self.set_node_vector(thread, n_hash)?;
334            let new_nodes = self.node_cursor();
335
336            if n_array < old_array_size {
337                self.as_ptr().as_mut().unwrap_unchecked().size_array = n_array;
338
339                for index in n_array..old_array_size {
340                    let value = self.array_slot(index as usize);
341                    if !value.is_nil() {
342                        let mut key_storage = RawTValue::number((index + 1) as f64);
343                        let key = TValue::from_mut(&mut key_storage);
344                        self.array_or_new_key(thread, key)?.set_obj(value);
345                    }
346                }
347
348                let array = TValueCursor::from_ptr(thread.realloc_array(
349                    self.as_ptr().as_ref().unwrap_unchecked().array,
350                    old_array_size as usize,
351                    n_array as usize,
352                    self.as_ptr().as_ref().unwrap_unchecked().memcat,
353                )?);
354                self.set_array(array);
355            }
356
357            let new_array = self.array_cursor();
358            for index in (0..twoto(old_hash_lsize)).rev() {
359                let old_cursor = old_nodes.add(index as usize);
360                let old_node = old_cursor.node_unchecked();
361                if !old_node.value_unchecked().is_nil() {
362                    let mut key_storage = RawTValue::nil();
363                    let key = TValue::from_mut(&mut key_storage);
364                    old_node.write_key_to_value(key);
365                    self.array_or_new_key(thread, key)?
366                        .set_obj(old_node.value_unchecked());
367                }
368            }
369
370            debug_assert!(new_nodes == self.node_cursor());
371            debug_assert!(new_array == self.array_cursor());
372
373            if old_nodes != Table::dummy_node_cursor() {
374                thread.free_array(
375                    old_nodes.as_ptr(),
376                    twoto(old_hash_lsize) as usize,
377                    self.as_ptr().as_ref().unwrap_unchecked().memcat,
378                );
379            }
380        }
381        Ok(())
382    }
383
384    /// `adjustasize`
385    unsafe fn adjust_array_size(&self, mut size: i32, extra_key: Option<TValue>) -> i32 {
386        let table_bound = unsafe {
387            !self.has_dummy_node() || size < self.as_ptr().as_ref().unwrap_unchecked().size_array
388        };
389        let extra_key_index = extra_key
390            .and_then(|key| {
391                if key.is_number() {
392                    Table::array_index(key.number_value())
393                } else {
394                    None
395                }
396            })
397            .unwrap_or(-1);
398
399        while size + 1 == extra_key_index
400            || (table_bound && !unsafe { self.get_num(size + 1) }.is_nil())
401        {
402            size += 1;
403        }
404
405        size
406    }
407
408    /// `rehash`
409    unsafe fn rehash(&self, thread: &Thread, extra_key: TValue) -> VmErrorResult {
410        let mut nums = [0; (MAX_BITS + 1) as usize];
411        let mut n_array = self.num_use_array(&mut nums);
412        let mut total_use = n_array;
413        total_use += self.num_use_hash(&mut nums, &mut n_array);
414
415        if extra_key.is_number() {
416            n_array += count_int(extra_key.number_value(), &mut nums);
417        }
418        total_use += 1;
419
420        let na = compute_sizes(&nums, &mut n_array);
421        let mut n_hash = total_use - na;
422
423        unsafe {
424            let mut adjusted = self.adjust_array_size(n_array, Some(extra_key));
425            let extra_array = adjusted - n_array;
426            if extra_array != 0 {
427                n_hash -= extra_array;
428                n_array = adjusted + extra_array;
429                adjusted = self.adjust_array_size(n_array, Some(extra_key));
430            }
431
432            self.resize(thread, adjusted, n_hash)?;
433        }
434        Ok(())
435    }
436
437    /// `getfreepos`
438    unsafe fn free_position(&self) -> Option<LuaNodeCursor> {
439        let mut last_free = unsafe { self.as_ptr().as_ref().unwrap_unchecked().free.last_free };
440        while last_free > 0 {
441            last_free -= 1;
442            unsafe {
443                self.as_ptr().as_mut().unwrap_unchecked().free = RawLuaTableFree { last_free };
444                let node_cursor = self.node_cursor().add(last_free as usize);
445                if node_cursor.node_unchecked().key().is_nil() {
446                    return Some(node_cursor);
447                }
448            }
449        }
450
451        None
452    }
453
454    unsafe fn new_hash_key(&self, thread: &Thread, key: TValue) -> VmErrorResult<TValue> {
455        unsafe {
456            let mut main_cursor = self.main_position(key);
457            if !main_cursor.node_unchecked().value_unchecked().is_nil()
458                || main_cursor == Table::dummy_node_cursor()
459            {
460                let Some(free_cursor) = self.free_position() else {
461                    self.rehash(thread, key)?;
462                    return self.array_or_new_key(thread, key);
463                };
464
465                debug_assert!(free_cursor != Table::dummy_node_cursor());
466
467                let mut main_key_storage = RAW_TVALUE_NIL;
468                let main_key = TValue::from_mut(&mut main_key_storage);
469                main_cursor.node_unchecked().write_key_to_value(main_key);
470
471                let mut other_cursor = self.main_position(main_key);
472                if other_cursor != main_cursor {
473                    let mut next_cursor =
474                        other_cursor.offset(other_cursor.node_unchecked().next() as isize);
475                    while next_cursor != main_cursor {
476                        other_cursor =
477                            other_cursor.offset(other_cursor.node_unchecked().next() as isize);
478                        next_cursor =
479                            other_cursor.offset(other_cursor.node_unchecked().next() as isize);
480                    }
481
482                    other_cursor
483                        .node_unchecked()
484                        .key()
485                        .set_next(free_cursor.offset_from(other_cursor) as i32);
486                    ptr::copy_nonoverlapping(main_cursor.as_ptr(), free_cursor.as_ptr(), 1);
487                    let main_next = main_cursor.node_unchecked().next();
488                    if main_next != 0 {
489                        free_cursor.node_unchecked().key().set_next(
490                            free_cursor.node_unchecked().next()
491                                + main_cursor.offset_from(free_cursor) as i32,
492                        );
493                        main_cursor.node_unchecked().key().set_next(0);
494                    }
495                    main_cursor.node_unchecked().value_unchecked().set_nil();
496                } else {
497                    let main_next = main_cursor.node_unchecked().next();
498                    if main_next != 0 {
499                        free_cursor.node_unchecked().key().set_next(
500                            main_cursor
501                                .offset(main_next as isize)
502                                .offset_from(free_cursor) as i32,
503                        );
504                    } else {
505                        debug_assert!(free_cursor.node_unchecked().next() == 0);
506                    }
507
508                    main_cursor
509                        .node_unchecked()
510                        .key()
511                        .set_next(free_cursor.offset_from(main_cursor) as i32);
512                    main_cursor = free_cursor;
513                }
514            }
515
516            let main_node = main_cursor.node_unchecked();
517            main_node.set_key_from_value(key);
518            if key.is_collectable() {
519                let object = key.gc_value();
520                let table_object: GcObject = (*self).into();
521                if table_object.is_black() && object.is_white() {
522                    thread.barrier_table(*self, object);
523                }
524            }
525
526            debug_assert!(main_node.value_unchecked().is_nil());
527            Ok(main_node.value())
528        }
529    }
530
531    /// `newkey`
532    unsafe fn new_key(&self, thread: &Thread, key: TValue) -> VmErrorResult<TValue> {
533        unsafe {
534            let size_array = self.as_ptr().as_ref().unwrap_unchecked().size_array;
535            if key.is_number() && key.number_value() == (size_array + 1) as f64 {
536                self.rehash(thread, key)?;
537                return self.array_or_new_key(thread, key);
538            }
539
540            self.new_hash_key(thread, key)
541        }
542    }
543
544    /// `findindex`
545    unsafe fn find_index(&self, thread: &Thread, key: TValue) -> VmErrorResult<i32> {
546        if key.is_nil() {
547            return Ok(-1);
548        }
549
550        unsafe {
551            if key.is_number()
552                && matches!(
553                    Table::array_index(key.number_value()),
554                    Some(index) if index > 0 && index <= self.as_ptr().as_ref().unwrap_unchecked().size_array
555                )
556            {
557                return Ok(Table::array_index(key.number_value()).unwrap_unchecked() - 1);
558            }
559
560            let mut node_cursor = self.main_position(key);
561            loop {
562                let node_key = node_cursor.node_unchecked().key();
563                if node_key.raw_equal_value(key)
564                    || (node_key.is_dead_key()
565                        && key.is_collectable()
566                        && node_key.gc_value() == key.gc_value())
567                {
568                    let index = self.node_index(node_cursor);
569                    return Ok(index + self.as_ptr().as_ref().unwrap_unchecked().size_array);
570                }
571
572                let next = node_key.next();
573                if next == 0 {
574                    break;
575                }
576                node_cursor = node_cursor.offset(next as isize);
577            }
578
579            crate::run_error!(thread, "invalid key to 'next'")
580        }
581    }
582}
583
584impl TableRuntime for Thread {
585    /// `luaH_setnum`
586    #[inline(always)]
587    unsafe fn set_num(&self, table: Table, key: i32) -> VmErrorResult<TValue> {
588        unsafe {
589            if let Some(slot) = table.array_slot_for_key(key) {
590                Ok(slot)
591            } else {
592                let slot = table.get_num(key);
593                if slot != nil_object() {
594                    Ok(slot)
595                } else {
596                    let mut value_storage = RawTValue::number(key as f64);
597                    let value = TValue::from_mut(&mut value_storage);
598                    table.new_key(self, value)
599                }
600            }
601        }
602    }
603
604    /// `luaH_setstr`
605    #[inline(always)]
606    unsafe fn set_str(&self, table: Table, key: TString) -> VmErrorResult<LuaNodeCursor> {
607        unsafe {
608            table.invalidate_tm_cache();
609
610            if let Some(node_cursor) = table.get_str_node(key) {
611                Ok(node_cursor)
612            } else {
613                let mut value_storage = RawTValue::string(key);
614                let value = TValue::from_mut(&mut value_storage);
615                table.new_hash_key(self, value)?;
616                Ok(table.get_str_node(key).unwrap_unchecked())
617            }
618        }
619    }
620
621    /// `luaH_setp`
622    unsafe fn setp(&self, table: Table, key: *mut (), tag: i32) -> VmErrorResult<TValue> {
623        unsafe {
624            let slot = table.getp(key, tag);
625            if slot != nil_object() {
626                Ok(slot)
627            } else {
628                let mut value_storage = RawTValue::light_userdata(key, tag);
629                let value = TValue::from_mut(&mut value_storage);
630                table.new_key(self, value)
631            }
632        }
633    }
634
635    /// `luaH_set`
636    unsafe fn set(&self, table: Table, key: TValue) -> VmErrorResult<TValue> {
637        unsafe {
638            let slot = table.get(key);
639            self.set_slot(table, slot, key)
640        }
641    }
642
643    /// `luaH_setslot`
644    unsafe fn set_slot(&self, table: Table, slot: TValue, key: TValue) -> VmErrorResult<TValue> {
645        unsafe {
646            table.invalidate_tm_cache();
647            if slot != nil_object() {
648                Ok(slot)
649            } else {
650                self.new_key(table, key)
651            }
652        }
653    }
654
655    /// `luaH_newkey`
656    unsafe fn new_key(&self, table: Table, key: TValue) -> VmErrorResult<TValue> {
657        unsafe {
658            if key.is_nil() {
659                return crate::run_error!(self, "table index is nil");
660            } else if key.is_number() && key.number_value().is_nan() {
661                return crate::run_error!(self, "table index is NaN");
662            } else if key.is_vector() && crate::number::vec_is_nan(&key.vector_value()) {
663                return crate::run_error!(self, "table index contains NaN");
664            }
665
666            table.new_key(self, key)
667        }
668    }
669
670    /// `luaH_next`
671    unsafe fn next_internal(&self, table: Table, key: TValueCursor) -> VmErrorResult<i32> {
672        unsafe {
673            let mut index = table.find_index(self, key.value_unchecked())?;
674
675            index += 1;
676            while index < table.as_ptr().as_ref().unwrap_unchecked().size_array {
677                let value = table.array_slot(index as usize);
678                if !value.is_nil() {
679                    key.value_unchecked().set_number((index + 1) as f64);
680                    key.add(1).value_unchecked().set_obj(value);
681                    return Ok(1);
682                }
683                index += 1;
684            }
685
686            index -= table.as_ptr().as_ref().unwrap_unchecked().size_array;
687            while index < table.node_count() as i32 {
688                let node = table.node(index);
689                if !node.value_unchecked().is_nil() {
690                    node.write_key_to_value(key.value_unchecked());
691                    key.add(1).value_unchecked().set_obj(node.value_unchecked());
692                    return Ok(1);
693                }
694                index += 1;
695            }
696
697            Ok(0)
698        }
699    }
700
701    /// `luaH_new`
702    unsafe fn new_table_internal(&self, n_array: i32, n_hash: i32) -> VmErrorResult<Table> {
703        let table = unsafe {
704            let table = self.new_gco::<Table>(
705                mem::size_of::<RawLuaTable>(),
706                self.as_ptr().as_ref().unwrap_unchecked().active_memcat,
707            )?;
708            GcObject::from(table).init_header(self, LUA_TTABLE as u8);
709            let table_ref = table.as_ptr().as_mut().unwrap_unchecked();
710            table_ref.metatable = ptr::null_mut();
711            table_ref.tm_cache = !0;
712            table.init_empty_storage();
713            if n_array > 0 {
714                table.set_array_vector(self, n_array)?;
715            }
716            if n_hash > 0 {
717                table.set_node_vector(self, n_hash)?;
718            }
719            table
720        };
721
722        Ok(table)
723    }
724
725    /// `luaH_resizearray`
726    unsafe fn resize_array(&self, table: Table, n_array: i32) -> VmErrorResult {
727        unsafe {
728            let n_hash = if table.has_dummy_node() {
729                0
730            } else {
731                table.node_count() as i32
732            };
733            let adjusted = table.adjust_array_size(n_array, None);
734            table.resize(self, adjusted, n_hash)
735        }
736    }
737
738    /// `luaH_resizehash`
739    unsafe fn resize_hash(&self, table: Table, n_hash: i32) -> VmErrorResult {
740        unsafe {
741            table.resize(
742                self,
743                table.as_ptr().as_ref().unwrap_unchecked().size_array,
744                n_hash,
745            )
746        }
747    }
748
749    /// `luaH_free`
750    unsafe fn free_table(&self, table: Table, page: LuaPage) {
751        unsafe {
752            let table_ref = table.as_ptr().as_ref().unwrap_unchecked();
753            let memcat = table_ref.memcat;
754            table.free_storage(self);
755            self.free_gco(
756                table.into(),
757                core::mem::size_of::<RawLuaTable>(),
758                memcat,
759                page,
760            );
761        }
762    }
763
764    /// `luaH_clone`
765    unsafe fn clone_table_internal(&self, table: Table) -> VmErrorResult<Table> {
766        unsafe {
767            let active_memcat = self.as_ptr().as_ref().unwrap_unchecked().active_memcat;
768            let cloned = self.new_gco::<Table>(mem::size_of::<RawLuaTable>(), active_memcat)?;
769            GcObject::from(cloned).init_header(self, LUA_TTABLE as u8);
770
771            let cloned_ref = cloned.as_ptr().as_mut().unwrap_unchecked();
772            cloned_ref.metatable = table
773                .metatable()
774                .map_or(ptr::null_mut(), |table| table.as_ptr());
775            cloned_ref.tm_cache = table.as_ptr().as_ref().unwrap_unchecked().tm_cache;
776            cloned.init_empty_storage();
777
778            let table_ref = table.as_ptr().as_ref().unwrap_unchecked();
779            let cloned_memcat = cloned.as_ptr().as_ref().unwrap_unchecked().memcat;
780
781            if table_ref.size_array > 0 {
782                let size_array = table_ref.size_array as usize;
783                let array =
784                    TValueCursor::from_ptr(self.new_array::<RawTValue>(size_array, cloned_memcat)?);
785
786                cloned.set_array(array);
787                cloned.as_ptr().as_mut().unwrap_unchecked().size_array = table_ref.size_array;
788                cloned.maybe_set_aboundary(table.get_aboundary());
789                ptr::copy_nonoverlapping(table.array_cursor().as_ptr(), array.as_ptr(), size_array);
790            }
791
792            if !table.has_dummy_node() {
793                let size = table.node_count();
794                let node = self.new_array::<RawLuaNode>(size, cloned_memcat)?;
795
796                cloned.set_node(LuaNodeCursor::from_ptr(node));
797                cloned.as_ptr().as_mut().unwrap_unchecked().lsize_node = table_ref.lsize_node;
798                cloned.as_ptr().as_mut().unwrap_unchecked().node_mask_8 = table_ref.node_mask_8;
799                cloned.as_ptr().as_mut().unwrap_unchecked().free = RawLuaTableFree {
800                    last_free: table_ref.free.last_free,
801                };
802                ptr::copy_nonoverlapping(table_ref.node, node, size);
803            }
804
805            Ok(cloned)
806        }
807    }
808}
809
810impl Table {
811    /// `luaH_clear`
812    ///
813    /// # Safety
814    ///
815    /// The table must be a live record owned by the active VM and exclusively
816    /// mutable for the duration of the call. Any outstanding element or node
817    /// cursors must not be used after their slots are cleared.
818    pub unsafe fn clear(&self) {
819        unsafe {
820            for index in 0..self.as_ptr().as_ref().unwrap_unchecked().size_array as usize {
821                self.array_slot(index).set_nil();
822            }
823
824            self.maybe_set_aboundary(0);
825
826            if !self.has_dummy_node() {
827                let size = self.node_count();
828                self.as_ptr().as_mut().unwrap_unchecked().free = RawLuaTableFree {
829                    last_free: size as i32,
830                };
831
832                for index in 0..size {
833                    let node = self.node(index as i32);
834                    node.set_key_from_value(nil_object());
835                    node.value_unchecked().set_nil();
836                    node.key().set_next(0);
837                }
838            }
839
840            self.as_ptr().as_mut().unwrap_unchecked().tm_cache = !0;
841        }
842    }
843}