Skip to main content

luau_vm/table/
layout.rs

1use core::mem::{self, offset_of};
2use core::ptr::{self, NonNull};
3
4use crate::Table;
5use crate::gc::GcObject;
6use crate::handle::RawHandle;
7use crate::memory::MemoryRuntime;
8use crate::string::TString;
9use crate::thread::Thread;
10use crate::types::{
11    LUA_EXTRA_SIZE, LUA_TBOOLEAN, LUA_TDEADKEY, LUA_TINTEGER, LUA_TLIGHTUSERDATA, LUA_TNIL,
12    LUA_TNUMBER, LUA_TSTRING, LUA_TVECTOR, LUA_VECTOR_SIZE,
13};
14use crate::value::{RAW_TVALUE_NIL, RawTValue, RawValue, TValue, TValueCursor};
15
16use super::{RawLuaTable, RawLuaTableFree};
17
18#[derive(Clone, Copy)]
19#[repr(C)]
20pub struct RawTKey {
21    pub value: RawValue,
22    pub extra: [i32; LUA_EXTRA_SIZE],
23    pub tt_next: u32,
24}
25
26pub const RAW_TKEY_NIL: RawTKey = RawTKey {
27    value: RawValue {
28        pointer: core::ptr::null_mut(),
29    },
30    extra: [0; LUA_EXTRA_SIZE],
31    tt_next: LUA_TNIL as u32,
32};
33
34pub const RAW_TKEY_DEAD_KEY: RawTKey = RawTKey {
35    value: RawValue {
36        pointer: core::ptr::null_mut(),
37    },
38    extra: [0; LUA_EXTRA_SIZE],
39    tt_next: LUA_TDEADKEY as u32,
40};
41
42#[derive(Clone, Copy, PartialEq, Eq)]
43#[repr(transparent)]
44/// Non-owning view of a live table-key record.
45///
46/// Unsafe operations require the owning table storage to remain live and the
47/// key's tag/payload and collision-chain encoding to remain valid.
48pub struct TKey {
49    raw: NonNull<RawTKey>,
50}
51
52#[allow(
53    clippy::missing_safety_doc,
54    reason = "TKey is a non-owning table-key view governed by internal's raw-view contract"
55)]
56impl TKey {
57    const TT_BITS: u32 = 4;
58    const TT_MASK: u32 = (1 << Self::TT_BITS) - 1;
59    const NEXT_SHIFT: u32 = Self::TT_BITS;
60    const NEXT_BITS: u32 = 32 - Self::NEXT_SHIFT;
61    const NEXT_MASK: u32 = !Self::TT_MASK;
62
63    pub const unsafe fn from_raw(raw: NonNull<RawTKey>) -> Self {
64        Self { raw }
65    }
66
67    pub fn set_nil(&self) {
68        unsafe {
69            *self.as_ptr().as_mut().unwrap_unchecked() = RAW_TKEY_NIL;
70        }
71    }
72
73    pub fn tt(&self) -> i32 {
74        (unsafe { self.as_ptr().as_ref().unwrap_unchecked().tt_next } & Self::TT_MASK) as i32
75    }
76
77    /// `setttype`
78    pub fn set_tt(&self, tt: i32) {
79        unsafe {
80            let raw = self.as_ptr().as_mut().unwrap_unchecked();
81            raw.tt_next = (raw.tt_next & Self::NEXT_MASK) | (tt as u32 & Self::TT_MASK);
82        }
83    }
84
85    pub fn next(&self) -> i32 {
86        let raw = unsafe { self.as_ptr().as_ref().unwrap_unchecked().tt_next } >> Self::NEXT_SHIFT;
87        let shift = 32 - Self::NEXT_BITS;
88        ((raw << shift) as i32) >> shift
89    }
90
91    /// `gnext`
92    pub fn set_next(&self, next: i32) {
93        let next_bits = ((next as u32) << Self::NEXT_SHIFT) & Self::NEXT_MASK;
94        unsafe {
95            let raw = self.as_ptr().as_mut().unwrap_unchecked();
96            raw.tt_next = (raw.tt_next & Self::TT_MASK) | next_bits;
97        }
98    }
99
100    pub fn is_nil(&self) -> bool {
101        self.tt() == LUA_TNIL
102    }
103
104    pub fn is_dead_key(&self) -> bool {
105        self.tt() == LUA_TDEADKEY
106    }
107
108    /// `iscollectable`
109    pub fn is_collectable(&self) -> bool {
110        self.tt() >= LUA_TSTRING
111    }
112
113    /// `gcvalue`
114    pub fn gc_value(&self) -> GcObject {
115        debug_assert!(self.is_collectable());
116        unsafe {
117            GcObject::from_raw(NonNull::new_unchecked(
118                self.as_ptr().as_ref().unwrap_unchecked().value.gc,
119            ))
120        }
121    }
122
123    /// `pvalue`
124    pub fn pointer_value(&self) -> *mut () {
125        debug_assert!(self.tt() == LUA_TLIGHTUSERDATA);
126        unsafe { self.as_ptr().as_ref().unwrap_unchecked().value.pointer }
127    }
128
129    /// `nvalue`
130    pub fn number_value(&self) -> f64 {
131        debug_assert!(self.tt() == LUA_TNUMBER);
132        unsafe { self.as_ptr().as_ref().unwrap_unchecked().value.number }
133    }
134
135    /// `lvalue`
136    pub fn integer_value(&self) -> i64 {
137        debug_assert!(self.tt() == LUA_TINTEGER);
138        unsafe { self.as_ptr().as_ref().unwrap_unchecked().value.integer }
139    }
140
141    /// `bvalue`
142    pub fn boolean_value(&self) -> i32 {
143        debug_assert!(self.tt() == LUA_TBOOLEAN);
144        unsafe { self.as_ptr().as_ref().unwrap_unchecked().value.boolean }
145    }
146
147    /// `lightuserdatatag`
148    pub fn light_userdata_tag(&self) -> i32 {
149        debug_assert!(self.tt() == LUA_TLIGHTUSERDATA);
150        unsafe { self.as_ptr().as_ref().unwrap_unchecked().extra[0] }
151    }
152
153    /// `vvalue`
154    pub fn vector_value(&self) -> [f32; LUA_VECTOR_SIZE] {
155        debug_assert!(self.tt() == LUA_TVECTOR);
156        let vector = self.as_ptr().cast::<f32>();
157        #[cfg(not(feature = "vector4"))]
158        unsafe {
159            [*vector, *vector.add(1), *vector.add(2)]
160        }
161        #[cfg(feature = "vector4")]
162        unsafe {
163            [*vector, *vector.add(1), *vector.add(2), *vector.add(3)]
164        }
165    }
166
167    /// `luaO_rawequalKey`
168    pub fn raw_equal_value(&self, other: impl Into<TValue>) -> bool {
169        let other = other.into();
170        if self.tt() != unsafe { other.as_ptr().as_ref().unwrap_unchecked().tt } {
171            return false;
172        }
173
174        match self.tt() {
175            x if x == LUA_TNIL => true,
176            x if x == LUA_TNUMBER => self.number_value() == other.number_value(),
177            x if x == LUA_TINTEGER => self.integer_value() == other.integer_value(),
178            x if x == LUA_TVECTOR => self.vector_value() == other.vector_value(),
179            x if x == LUA_TBOOLEAN => self.boolean_value() == other.boolean_value(),
180            x if x == LUA_TLIGHTUSERDATA => {
181                self.pointer_value() == other.pointer_value()
182                    && self.light_userdata_tag() == other.light_userdata_tag()
183            }
184            _ => {
185                debug_assert!(self.is_collectable());
186                self.gc_value() == other.gc_value()
187            }
188        }
189    }
190}
191impl crate::handle::sealed::Sealed for TKey {}
192impl RawHandle for TKey {
193    type Raw = RawTKey;
194
195    fn as_ptr(&self) -> *mut Self::Raw {
196        self.raw.as_ptr()
197    }
198}
199
200impl AsRef<TKey> for TKey {
201    fn as_ref(&self) -> &TKey {
202        self
203    }
204}
205
206#[repr(C)]
207pub struct RawLuaNode {
208    pub value: RawTValue,
209    pub key: RawTKey,
210}
211
212pub const RAW_LUA_NODE_DUMMY: RawLuaNode = RawLuaNode {
213    value: RAW_TVALUE_NIL,
214    key: RAW_TKEY_NIL,
215};
216
217#[derive(Clone, Copy, PartialEq, Eq)]
218#[repr(transparent)]
219/// Non-owning view of a live table hash node.
220///
221/// Unsafe operations require the owning table's node array to remain live and
222/// the node's key/value records to remain valid.
223pub struct LuaNode {
224    raw: NonNull<RawLuaNode>,
225}
226
227#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
228#[repr(transparent)]
229/// Nullable traversal position in a table's hash-node array.
230///
231/// Unsafe navigation requires cursors from the same live node allocation and
232/// an in-bounds non-null position where a node is accessed. Resizing the table
233/// invalidates every cursor.
234pub struct LuaNodeCursor(*mut RawLuaNode);
235
236#[allow(
237    clippy::missing_safety_doc,
238    reason = "LuaNode is a non-owning table-node view governed by Table's contract"
239)]
240impl LuaNode {
241    pub const unsafe fn from_raw(raw: NonNull<RawLuaNode>) -> Self {
242        Self { raw }
243    }
244
245    pub fn value(&self) -> TValue {
246        unsafe { TValue::from_raw(NonNull::new_unchecked(&raw mut (*self.as_ptr()).value)) }
247    }
248
249    pub fn value_unchecked(&self) -> TValue {
250        self.value()
251    }
252
253    pub fn key(&self) -> TKey {
254        unsafe { TKey::from_raw(NonNull::new_unchecked(&raw mut (*self.as_ptr()).key)) }
255    }
256
257    pub fn has_string_key(&self, key: TString) -> bool {
258        unsafe {
259            const TT_MASK: u32 = (1 << 4) - 1;
260
261            let raw = self.as_ptr();
262            let key_raw = &raw const (*raw).key;
263            ((*key_raw).tt_next & TT_MASK) as i32 == LUA_TSTRING
264                && (*key_raw).value.gc.cast() == key.as_ptr()
265        }
266    }
267
268    pub fn value_is_nil(&self) -> bool {
269        unsafe { (*self.as_ptr()).value.tt == LUA_TNIL }
270    }
271
272    /// `gnext`
273    pub fn next(&self) -> i32 {
274        self.key().next()
275    }
276
277    /// `setnodekey`
278    pub fn set_key_from_value(&self, value: impl Into<TValue>) {
279        let value = value.into();
280        unsafe {
281            ptr::copy_nonoverlapping(
282                (&raw const (*value.as_ptr()).value).cast::<u8>(),
283                (&raw mut (*self.as_ptr()).key.value).cast::<u8>(),
284                core::mem::size_of::<RawValue>(),
285            );
286            ptr::copy_nonoverlapping(
287                (&raw const (*value.as_ptr()).extra).cast::<u8>(),
288                (&raw mut (*self.as_ptr()).key.extra).cast::<u8>(),
289                core::mem::size_of::<[i32; LUA_EXTRA_SIZE]>(),
290            );
291            self.key().set_tt((*value.as_ptr()).tt);
292        }
293    }
294
295    /// `getnodekey`
296    pub fn write_key_to_value(&self, value: impl Into<TValue>) {
297        let value = value.into();
298        unsafe {
299            ptr::copy_nonoverlapping(
300                (&raw const (*self.as_ptr()).key.value).cast::<u8>(),
301                (&raw mut (*value.as_ptr()).value).cast::<u8>(),
302                core::mem::size_of::<RawValue>(),
303            );
304            ptr::copy_nonoverlapping(
305                (&raw const (*self.as_ptr()).key.extra).cast::<u8>(),
306                (&raw mut (*value.as_ptr()).extra).cast::<u8>(),
307                core::mem::size_of::<[i32; LUA_EXTRA_SIZE]>(),
308            );
309            (*value.as_ptr()).tt = self.key().tt();
310        }
311    }
312}
313
314#[allow(
315    clippy::missing_safety_doc,
316    reason = "LuaNodeCursor navigation is governed by Table's storage contract"
317)]
318impl LuaNodeCursor {
319    /// Returns the current traversal address, which may be null.
320    ///
321    /// Table resize and rehash invalidate cursors into the old node array.
322    pub const fn as_ptr(&self) -> *mut RawLuaNode {
323        self.0
324    }
325
326    pub const fn from_ptr(raw: *mut RawLuaNode) -> Self {
327        Self(raw)
328    }
329
330    pub const fn is_null(&self) -> bool {
331        self.0.is_null()
332    }
333
334    pub unsafe fn node_unchecked(&self) -> LuaNode {
335        debug_assert!(!self.is_null());
336        unsafe { LuaNode::from_raw(NonNull::new_unchecked(self.0)) }
337    }
338
339    pub fn node(&self) -> Option<LuaNode> {
340        NonNull::new(self.0).map(|raw| unsafe { LuaNode::from_raw(raw) })
341    }
342
343    pub unsafe fn add(self, count: usize) -> Self {
344        unsafe { Self::from_ptr(self.0.add(count)) }
345    }
346
347    pub unsafe fn sub(self, count: usize) -> Self {
348        unsafe { Self::from_ptr(self.0.sub(count)) }
349    }
350
351    pub unsafe fn offset(self, count: isize) -> Self {
352        unsafe { Self::from_ptr(self.0.offset(count)) }
353    }
354
355    pub unsafe fn offset_from(self, other: Self) -> isize {
356        unsafe { self.0.offset_from(other.0) }
357    }
358}
359impl crate::handle::sealed::Sealed for LuaNode {}
360impl RawHandle for LuaNode {
361    type Raw = RawLuaNode;
362
363    fn as_ptr(&self) -> *mut Self::Raw {
364        self.raw.as_ptr()
365    }
366}
367
368impl AsRef<LuaNode> for LuaNode {
369    fn as_ref(&self) -> &LuaNode {
370        self
371    }
372}
373
374impl AsRef<LuaNodeCursor> for LuaNodeCursor {
375    fn as_ref(&self) -> &LuaNodeCursor {
376        self
377    }
378}
379
380const _: () = assert!(offset_of!(RawLuaNode, value) == 0);
381
382#[allow(
383    clippy::missing_safety_doc,
384    reason = "Table's shared raw-handle contract is documented on Table"
385)]
386impl Table {
387    pub const unsafe fn from_raw(raw: NonNull<RawLuaTable>) -> Self {
388        Self { raw }
389    }
390
391    pub unsafe fn node(&self, index: i32) -> LuaNode {
392        unsafe { self.node_cursor().add(index as usize).node_unchecked() }
393    }
394
395    pub unsafe fn node_mut(&mut self, index: i32) -> LuaNode {
396        unsafe {
397            LuaNode::from_raw(NonNull::new_unchecked(
398                self.as_ptr()
399                    .as_mut()
400                    .unwrap_unchecked()
401                    .node
402                    .add(index as usize),
403            ))
404        }
405    }
406
407    pub unsafe fn metatable(&self) -> Option<Table> {
408        unsafe {
409            NonNull::new(self.as_ptr().as_ref().unwrap_unchecked().metatable)
410                .map(|raw| Table::from_raw(raw))
411        }
412    }
413
414    pub unsafe fn set_metatable(&self, metatable: Option<Table>) {
415        unsafe {
416            self.as_ptr().as_mut().unwrap_unchecked().metatable =
417                metatable.map_or(core::ptr::null_mut(), |table| table.as_ptr())
418        };
419    }
420
421    pub unsafe fn gc_list(&self) -> Option<GcObject> {
422        unsafe {
423            NonNull::new(self.as_ptr().as_ref().unwrap_unchecked().gc_list)
424                .map(|raw| GcObject::from_raw(raw))
425        }
426    }
427
428    pub unsafe fn set_gc_list(&self, gc_list: Option<GcObject>) {
429        unsafe {
430            self.as_ptr().as_mut().unwrap_unchecked().gc_list =
431                gc_list.map_or(ptr::null_mut(), |object| object.as_ptr());
432        }
433    }
434
435    pub unsafe fn invalidate_tm_cache(&self) {
436        unsafe { self.as_ptr().as_mut().unwrap_unchecked().tm_cache = 0 };
437    }
438
439    pub unsafe fn set_node(&self, node_cursor: LuaNodeCursor) {
440        unsafe { self.as_ptr().as_mut().unwrap_unchecked().node = node_cursor.as_ptr() };
441    }
442
443    pub unsafe fn set_array(&self, array_cursor: TValueCursor) {
444        unsafe { self.as_ptr().as_mut().unwrap_unchecked().array = array_cursor.as_ptr() };
445    }
446
447    pub unsafe fn has_dummy_node(&self) -> bool {
448        unsafe { self.node_cursor() == Self::dummy_node_cursor() }
449    }
450
451    pub unsafe fn node_index(&self, node_cursor: LuaNodeCursor) -> i32 {
452        unsafe { node_cursor.offset_from(self.node_cursor()) as i32 }
453    }
454
455    /// `gval2slot`
456    ///
457    /// # Safety
458    /// `value` must be a value pointer produced by `luaH_get` or `luaH_setslot` for this table.
459    pub unsafe fn value_slot_unchecked(&self, value: TValue) -> i32 {
460        unsafe {
461            let value_addr = value.as_ptr() as usize;
462            let node_addr = self.as_ptr().as_ref().unwrap_unchecked().node as usize;
463            (value_addr.wrapping_sub(node_addr) / mem::size_of::<RawLuaNode>()) as i32
464        }
465    }
466
467    pub unsafe fn node_cursor(&self) -> LuaNodeCursor {
468        unsafe { LuaNodeCursor::from_ptr(self.as_ptr().as_ref().unwrap_unchecked().node) }
469    }
470
471    pub unsafe fn array_cursor(&self) -> TValueCursor {
472        unsafe { TValueCursor::from_ptr(self.as_ptr().as_ref().unwrap_unchecked().array) }
473    }
474
475    pub unsafe fn array_slot(&self, index: usize) -> TValue {
476        unsafe { self.array_cursor().add(index).value_unchecked() }
477    }
478
479    pub unsafe fn array_slot_for_key(&self, key: i32) -> Option<TValue> {
480        unsafe {
481            ((key as u32).wrapping_sub(1)
482                < self.as_ptr().as_ref().unwrap_unchecked().size_array as u32)
483                .then(|| self.array_slot((key - 1) as usize))
484        }
485    }
486
487    pub unsafe fn node_count(&self) -> usize {
488        unsafe { 1usize << self.as_ptr().as_ref().unwrap_unchecked().lsize_node }
489    }
490
491    pub unsafe fn hash_mask(&self) -> usize {
492        unsafe { self.node_count() - 1 }
493    }
494
495    pub unsafe fn node_mask_8(&self) -> usize {
496        unsafe { self.as_ptr().as_ref().unwrap_unchecked().node_mask_8 as usize }
497    }
498
499    pub unsafe fn array_storage_size(&self) -> usize {
500        unsafe {
501            self.as_ptr().as_ref().unwrap_unchecked().size_array as usize
502                * mem::size_of::<RawTValue>()
503        }
504    }
505
506    pub unsafe fn node_storage_size(&self) -> usize {
507        unsafe {
508            if self.has_dummy_node() {
509                0
510            } else {
511                self.node_count() * mem::size_of::<RawLuaNode>()
512            }
513        }
514    }
515
516    pub unsafe fn allocation_size(&self) -> usize {
517        unsafe {
518            mem::size_of::<RawLuaTable>() + self.array_storage_size() + self.node_storage_size()
519        }
520    }
521
522    pub unsafe fn gc_work_size(&self, count_dummy_node: bool) -> usize {
523        unsafe {
524            let node_size = if count_dummy_node {
525                self.node_count() * mem::size_of::<RawLuaNode>()
526            } else {
527                self.node_storage_size()
528            };
529            mem::size_of::<RawLuaTable>() + self.array_storage_size() + node_size
530        }
531    }
532
533    pub unsafe fn init_empty_storage(&self) {
534        unsafe {
535            let table_ref = self.as_ptr().as_mut().unwrap_unchecked();
536            table_ref.array = core::ptr::null_mut();
537            table_ref.size_array = 0;
538            table_ref.lsize_node = 0;
539            table_ref.readonly = 0;
540            table_ref.safe_env = 0;
541            table_ref.node_mask_8 = 0;
542            table_ref.node = Self::dummy_node_ptr();
543            table_ref.gc_list = core::ptr::null_mut();
544            table_ref.free = RawLuaTableFree { last_free: 0 };
545        }
546    }
547
548    pub unsafe fn free_storage(&self, thread: &Thread) {
549        unsafe {
550            let table_ref = self.as_ptr().as_ref().unwrap_unchecked();
551            if !self.has_dummy_node() {
552                thread.free_array(
553                    self.node_cursor().as_ptr(),
554                    self.node_count(),
555                    table_ref.memcat,
556                );
557            }
558
559            if !table_ref.array.is_null() {
560                thread.free_array(
561                    table_ref.array,
562                    table_ref.size_array as usize,
563                    table_ref.memcat,
564                );
565            }
566        }
567    }
568
569    pub unsafe fn maybe_set_aboundary(&self, value: i32) {
570        if unsafe { self.as_ptr().as_ref().unwrap_unchecked().free.aboundary } <= 0 {
571            unsafe {
572                self.as_ptr().as_mut().unwrap_unchecked().free =
573                    RawLuaTableFree { aboundary: -value }
574            };
575        }
576    }
577
578    /// `updateaboundary`
579    pub(super) fn update_aboundary(&self, boundary: i32) -> i32 {
580        let size_array = unsafe { self.as_ptr().as_ref().unwrap_unchecked().size_array };
581        let array = unsafe { self.array_cursor() };
582        let boundary_slot_is_nil = unsafe { array.add((boundary - 1) as usize).is_nil_unchecked() };
583        if boundary < size_array && boundary_slot_is_nil {
584            let previous_slot_is_set = if boundary >= 2 {
585                unsafe { !array.add((boundary - 2) as usize).is_nil_unchecked() }
586            } else {
587                false
588            };
589            if previous_slot_is_set {
590                unsafe {
591                    self.maybe_set_aboundary(boundary - 1);
592                }
593                return boundary - 1;
594            }
595        } else {
596            let next_slot_is_set = if boundary + 1 < size_array {
597                unsafe { !array.add(boundary as usize).is_nil_unchecked() }
598            } else {
599                false
600            };
601            let slot_after_next_is_nil = if boundary + 1 < size_array {
602                unsafe { array.add((boundary + 1) as usize).is_nil_unchecked() }
603            } else {
604                false
605            };
606            if next_slot_is_set && slot_after_next_is_nil {
607                unsafe {
608                    self.maybe_set_aboundary(boundary + 1);
609                }
610                return boundary + 1;
611            }
612        }
613
614        0
615    }
616
617    pub unsafe fn get_aboundary(&self) -> i32 {
618        let aboundary = unsafe { self.as_ptr().as_ref().unwrap_unchecked().free.aboundary };
619        if aboundary < 0 {
620            -aboundary
621        } else {
622            unsafe { self.as_ptr().as_ref().unwrap_unchecked().size_array }
623        }
624    }
625}