luau-vm 0.732.0

Pure-Rust Luau virtual machine, garbage collector, and standard libraries
Documentation
use crate::Table;
use crate::handle::RawHandle;
use crate::metamethod::TmEvent;
use crate::string::TString;
use crate::types::{
    LUA_TBOOLEAN, LUA_TINTEGER, LUA_TLIGHTUSERDATA, LUA_TNIL, LUA_TNUMBER, LUA_TSTRING,
    LUA_TVECTOR, LUA_VECTOR_SIZE,
};
use crate::value::{TValue, nil_object};

use super::{LuaNode, LuaNodeCursor, RawLuaNode};

#[allow(
    clippy::missing_safety_doc,
    reason = "Table's shared raw-handle contract is documented on Table"
)]
impl Table {
    pub fn dummy_node_cursor() -> LuaNodeCursor {
        struct SharedDummyNode(RawLuaNode);

        unsafe impl Sync for SharedDummyNode {}

        static DUMMY_NODE: SharedDummyNode = SharedDummyNode(crate::table::RAW_LUA_NODE_DUMMY);

        LuaNodeCursor::from_ptr((&raw const DUMMY_NODE.0).cast_mut())
    }

    pub fn dummy_node() -> LuaNode {
        unsafe { Self::dummy_node_cursor().node_unchecked() }
    }

    pub fn dummy_node_ptr() -> *mut RawLuaNode {
        Self::dummy_node().as_ptr()
    }

    /// `arrayindex`
    pub fn array_index(key: f64) -> Option<i32> {
        let index = key as i32;
        ((index as f64) == key).then_some(index)
    }

    /// `hashpow2`
    unsafe fn hash_pow2(&self, hash: u32) -> LuaNodeCursor {
        unsafe {
            self.node_cursor()
                .add((hash as usize) & (self.node_count() - 1))
        }
    }

    /// `hashstr`
    unsafe fn hash_str(&self, string: TString) -> LuaNodeCursor {
        unsafe { self.hash_pow2(string.as_ptr().as_ref().unwrap_unchecked().hash) }
    }

    /// `hashboolean`
    unsafe fn hash_boolean(&self, value: i32) -> LuaNodeCursor {
        unsafe { self.hash_pow2(value as u32) }
    }

    /// `hashpointer`
    unsafe fn hash_pointer(&self, pointer: *const ()) -> LuaNodeCursor {
        let mut hash = pointer as usize as u32;
        hash ^= hash >> 16;
        hash = hash.wrapping_mul(0x85ebca6b);
        hash ^= hash >> 13;
        hash = hash.wrapping_mul(0xc2b2ae35);
        hash ^= hash >> 16;
        unsafe { self.hash_pow2(hash) }
    }

    /// `hashnum`
    pub(super) unsafe fn hash_num(&self, number: f64) -> LuaNodeCursor {
        let bits = number.to_bits() & 0x7fff_ffff_ffff_ffff;
        let mut h1 = bits as u32;
        let mut h2 = (bits >> 32) as u32;
        let m = 0x5bd1e995u32;

        h1 ^= h2 >> 18;
        h1 = h1.wrapping_mul(m);
        h2 ^= h1 >> 22;
        h2 = h2.wrapping_mul(m);
        h1 ^= h2 >> 17;
        h1 = h1.wrapping_mul(m);
        h2 ^= h1 >> 19;
        h2 = h2.wrapping_mul(m);

        unsafe { self.hash_pow2(h2) }
    }

    /// `hashint`
    unsafe fn hash_int(&self, integer: i64) -> LuaNodeCursor {
        let bits = integer as u64;
        let mut h1 = bits as u32;
        let mut h2 = (bits >> 32) as u32;
        let m = 0x5bd1e995u32;

        h1 ^= h2 >> 18;
        h1 = h1.wrapping_mul(m);
        h2 ^= h1 >> 22;
        h2 = h2.wrapping_mul(m);
        h1 ^= h2 >> 17;
        h1 = h1.wrapping_mul(m);
        h2 ^= h1 >> 19;
        h2 = h2.wrapping_mul(m);

        unsafe { self.hash_pow2(h2) }
    }

    /// `hashvec`
    unsafe fn hash_vec(&self, vector: [f32; LUA_VECTOR_SIZE]) -> LuaNodeCursor {
        let mut values = [
            vector[0].to_bits(),
            vector[1].to_bits(),
            vector[2].to_bits(),
        ];
        #[cfg(feature = "vector4")]
        let value3 = vector[3].to_bits();
        for value in &mut values {
            if *value == 0x8000_0000 {
                *value = 0;
            }
            *value ^= *value >> 17;
        }

        let hash = values[0].wrapping_mul(73_856_093)
            ^ values[1].wrapping_mul(19_349_663)
            ^ values[2].wrapping_mul(83_492_791);
        #[cfg(feature = "vector4")]
        {
            let mut hash = hash;
            let mut value3 = if value3 == 0x8000_0000 { 0 } else { value3 };
            value3 ^= value3 >> 17;
            hash ^= value3.wrapping_mul(39_916_801);
            unsafe { self.hash_pow2(hash) }
        }
        #[cfg(not(feature = "vector4"))]
        {
            unsafe { self.hash_pow2(hash) }
        }
    }

    pub(super) unsafe fn get_pointer_node(&self, key: *mut (), tag: i32) -> Option<LuaNodeCursor> {
        let mut node = unsafe { self.hash_pointer(key.cast_const()) };

        loop {
            let node_key = unsafe { node.node_unchecked() }.key();
            if node_key.tt() == LUA_TLIGHTUSERDATA
                && node_key.pointer_value() == key
                && node_key.light_userdata_tag() == tag
            {
                return Some(node);
            }

            let next = node_key.next();
            if next == 0 {
                break;
            }

            node = unsafe { node.offset(next as isize) };
        }

        None
    }

    /// `mainposition`
    pub unsafe fn main_position(&self, key: TValue) -> LuaNodeCursor {
        unsafe {
            match key.tt() {
                x if x == LUA_TNUMBER => self.hash_num(key.number_value()),
                x if x == LUA_TINTEGER => self.hash_int(key.integer_value()),
                x if x == LUA_TVECTOR => self.hash_vec(key.vector_value()),
                x if x == LUA_TSTRING => self.hash_str(key.string_value()),
                x if x == LUA_TBOOLEAN => self.hash_boolean(key.boolean_value()),
                x if x == LUA_TLIGHTUSERDATA => self.hash_pointer(key.pointer_value().cast_const()),
                _ => self.hash_pointer(key.gc_value().as_ptr().cast()),
            }
        }
    }

    pub unsafe fn get_hash_node(&self, key: TValue) -> Option<LuaNodeCursor> {
        let mut node = unsafe { self.main_position(key) };

        loop {
            if unsafe { node.node_unchecked() }.key().raw_equal_value(key) {
                return Some(node);
            }

            let next = unsafe { node.node_unchecked() }.next();
            if next == 0 {
                break;
            }

            node = unsafe { node.offset(next as isize) };
        }

        None
    }

    #[inline(always)]
    pub unsafe fn get_str_node(self, key: TString) -> Option<LuaNodeCursor> {
        let mut node = unsafe { self.hash_str(key) };

        loop {
            let current = unsafe { node.node_unchecked() };
            if current.has_string_key(key) {
                return Some(node);
            }

            let next = current.next();
            if next == 0 {
                break;
            }

            node = unsafe { node.offset(next as isize) };
        }

        None
    }
}

#[allow(
    clippy::missing_safety_doc,
    reason = "Table's shared raw-handle contract is documented on Table"
)]
impl Table {
    /// `luaH_getnum`
    pub unsafe fn get_num(&self, key: i32) -> TValue {
        unsafe {
            if let Some(slot) = self.array_slot_for_key(key) {
                slot
            } else if !self.has_dummy_node() {
                let nk = key as f64;
                let mut node_cursor = self.hash_num(nk);

                loop {
                    let node_key = node_cursor.node_unchecked().key();
                    if node_key.tt() == LUA_TNUMBER && node_key.number_value() == nk {
                        return node_cursor.node_unchecked().value_unchecked();
                    }

                    let next = node_key.next();
                    if next == 0 {
                        break;
                    }

                    node_cursor = node_cursor.offset(next as isize);
                }

                nil_object()
            } else {
                nil_object()
            }
        }
    }

    /// `luaH_getstr`
    pub unsafe fn get_str(&self, key: TString) -> TValue {
        unsafe {
            self.get_str_node(key)
                .map(|node_cursor| node_cursor.node_unchecked().value_unchecked())
                .unwrap_or_else(nil_object)
        }
    }

    unsafe fn get_hash(&self, key: TValue) -> TValue {
        unsafe {
            self.get_hash_node(key)
                .map(|node_cursor| node_cursor.node_unchecked().value_unchecked())
                .unwrap_or_else(nil_object)
        }
    }

    /// `luaH_get`
    pub unsafe fn get(&self, key: TValue) -> TValue {
        unsafe {
            match key.tt() {
                x if x == LUA_TNIL => nil_object(),
                x if x == LUA_TSTRING => self.get_str(key.string_value()),
                x if x == LUA_TNUMBER => {
                    if let Some(index) = Self::array_index(key.number_value()) {
                        self.get_num(index)
                    } else {
                        self.get_hash(key)
                    }
                }
                _ => self.get_hash(key),
            }
        }
    }

    /// `luaH_getp`
    pub unsafe fn getp(&self, key: *mut (), tag: i32) -> TValue {
        unsafe {
            self.get_pointer_node(key, tag)
                .map(|node_cursor| node_cursor.node_unchecked().value_unchecked())
                .unwrap_or_else(nil_object)
        }
    }

    /// `luaH_getn`
    #[inline(always)]
    pub unsafe fn getn(self) -> i32 {
        unsafe {
            let mut boundary = self.get_aboundary();
            let size_array = self.as_ptr().as_ref().unwrap_unchecked().size_array;
            let array = self.array_cursor();

            if boundary > 0 {
                let node_is_dummy = self.has_dummy_node();

                if size_array > 0
                    && !array.add((size_array - 1) as usize).is_nil_unchecked()
                    && node_is_dummy
                {
                    return size_array;
                }

                if boundary < size_array
                    && !array.add((boundary - 1) as usize).is_nil_unchecked()
                    && array.add(boundary as usize).is_nil_unchecked()
                {
                    return boundary;
                }

                let found = self.update_aboundary(boundary);
                if found > 0 {
                    return found;
                }
            }

            let last_array_is_nil = if size_array > 0 {
                array.add((size_array - 1) as usize).is_nil_unchecked()
            } else {
                false
            };

            if last_array_is_nil {
                let mut base = array;
                let mut rest = size_array;

                while rest >> 1 != 0 {
                    let half = rest >> 1;
                    let half_slot = base.add(half as usize);
                    let half_is_nil = half_slot.is_nil_unchecked();
                    base = if half_is_nil { base } else { half_slot };
                    rest -= half;
                }

                let base_is_nil = base.is_nil_unchecked();
                boundary = i32::from(!base_is_nil) + base.offset_from(array) as i32;
                self.maybe_set_aboundary(boundary);
                boundary
            } else {
                debug_assert!(self.has_dummy_node() || self.get_num(size_array + 1).is_nil());
                size_array
            }
        }
    }

    /// `luaT_gettm`
    pub unsafe fn get_tm(&self, event: TmEvent, event_name: TString) -> Option<TValue> {
        let tm = unsafe { self.get_str(event_name) };
        if tm.is_nil() {
            unsafe {
                self.as_ptr().as_mut().unwrap_unchecked().tm_cache =
                    self.as_ptr().as_ref().unwrap_unchecked().tm_cache | (1u8 << event as usize)
            };
            None
        } else {
            Some(tm)
        }
    }
}