luau-vm 0.732.0

Pure-Rust Luau virtual machine, garbage collector, and standard libraries
Documentation
use crate::Table;
use crate::call::ProtectedCall;
use crate::function::FunctionRuntime;
use crate::function::UpVal;
use crate::gc::GcObject;
use crate::handle::RawHandle;
use crate::metamethod::TmEvent;
use crate::table::LuaNode;
use crate::table::TableRuntime;
use crate::thread::Thread;
use crate::types::{LUA_TDEADKEY, LUA_TSTRING};
use crate::value::{RAW_TVALUE_NIL, TValue};
use crate::{VmError, VmExit, VmResult};
use luau_common::flags;

fn embedder_mark_ref(thread: &Thread, reference: i32) {
    if reference <= crate::thread::LUA_REFNIL {
        return;
    }

    unsafe {
        let global = thread.global();
        let registry = global.weak_registry().table_value();
        let value = registry.get_num(reference);
        if value.is_collectable() && value.gc_value().is_white() {
            global.really_mark_object(value.gc_value());
        }
    }
}

impl LuaNode {
    /// `removeentry`
    fn remove_entry(&self) {
        debug_assert!(self.value_unchecked().is_nil());
        if self.key().is_collectable() {
            self.key().set_tt(LUA_TDEADKEY);
        }
    }
}

impl Table {
    unsafe fn mode_contains(&self, thread: &Thread, byte: u8) -> bool {
        unsafe {
            let global = thread.global();
            let event_name = global.tm_name(TmEvent::Mode as usize);

            let Some(mode) = self.get_tm(TmEvent::Mode, event_name) else {
                return false;
            };
            if mode.is_string() {
                mode.string_value().as_bytes().contains(&byte)
            } else {
                false
            }
        }
    }

    /// `table_resize_protected`
    unsafe fn resize_hash_protected(&self, thread: &Thread, hash_size: i32) {
        #[repr(C)]
        struct ResizeHashContext {
            table: Table,
            hash_size: i32,
        }

        unsafe fn resize_hash_trampoline(
            thread: &Thread,
            context: &mut ResizeHashContext,
        ) -> VmResult {
            unsafe { thread.resize_hash(context.table, context.hash_size) }?;
            Ok(())
        }

        let mut context = ResizeHashContext {
            table: *self,
            hash_size,
        };
        let result = unsafe { thread.raw_run_protected(resize_hash_trampoline, &mut context) };
        debug_assert!(matches!(
            result,
            Ok(()) | Err(VmExit::Error(VmError::Memory))
        ));
    }
}

impl TValue {
    /// `iscleared`
    unsafe fn is_cleared(&self) -> bool {
        if !self.is_collectable() {
            return false;
        }

        let object_ref = self.gc_value();
        unsafe {
            if object_ref.as_ptr().as_ref().unwrap_unchecked().tt == LUA_TSTRING as u8 {
                object_ref.to_string().mark_gc();
                return false;
            }

            object_ref.is_white()
        }
    }
}

impl Thread {
    /// `remarkupvals`
    unsafe fn remark_upvalues(&self) -> usize {
        unsafe {
            let global = self.global();
            let uv_head = global.uv_head();
            let mut work = 0;
            let mut upvalue = uv_head.open_data().next();

            while upvalue != uv_head {
                work += UpVal::allocation_size();

                let open = upvalue.open_data();
                let object: GcObject = upvalue.into();

                debug_assert!(upvalue.is_open());
                debug_assert!(open.next().open_data().prev() == upvalue);
                debug_assert!(open.prev().open_data().next() == upvalue);
                debug_assert!(!object.is_black());

                if object.is_gray() {
                    global.mark_value(upvalue.value());
                }

                upvalue = open.next();
            }

            work
        }
    }

    /// `cleartable`
    unsafe fn clear_tables(&self, mut list: Option<GcObject>) -> usize {
        let mut work = 0;

        unsafe {
            while let Some(object) = list {
                let table = object.to_table();
                let table_ref = table.as_ptr().as_ref().unwrap_unchecked();
                let size_node = table.node_count();

                work += table.gc_work_size(!flags::LuauGcTableStepFix.get());

                for index in (0..table_ref.size_array as usize).rev() {
                    let value = table.array_slot(index);
                    if value.is_cleared() {
                        value.set_nil();
                    }
                }

                let mut active_values = 0usize;
                for index in (0..size_node).rev() {
                    let node = table.node(index as i32);

                    if !node.value_unchecked().is_nil() {
                        let mut key = RAW_TVALUE_NIL;
                        let key = TValue::from_mut(&mut key);
                        node.write_key_to_value(key);

                        if key.is_cleared() || node.value_unchecked().is_cleared() {
                            node.value_unchecked().set_nil();
                            node.remove_entry();
                        } else {
                            active_values += 1;
                        }
                    }
                }

                if table.mode_contains(self, b's') && active_values < size_node * 3 / 8 {
                    table.resize_hash_protected(self, active_values as i32);
                }

                list = table.gc_list();
            }
        }

        work
    }

    /// `clearupvals`
    unsafe fn clear_upvalues(&self) -> usize {
        unsafe {
            let global = self.global();
            let uv_head = global.uv_head();
            let mut work = 0;
            let mut upvalue = uv_head.open_data().next();

            while upvalue != uv_head {
                work += UpVal::allocation_size();

                let current_upvalue = upvalue;
                let open = current_upvalue.open_data();
                let object: GcObject = current_upvalue.into();

                debug_assert!(current_upvalue.is_open());
                debug_assert!(open.next().open_data().prev() == current_upvalue);
                debug_assert!(open.prev().open_data().next() == current_upvalue);
                debug_assert!(!object.is_black());
                let value = current_upvalue.value();
                debug_assert!(
                    object.is_white() || !value.is_collectable() || !value.gc_value().is_white()
                );

                if current_upvalue
                    .as_ptr()
                    .as_ref()
                    .unwrap_unchecked()
                    .marked_open
                    != 0
                {
                    debug_assert!(object.is_gray());
                    current_upvalue
                        .as_ptr()
                        .as_mut()
                        .unwrap_unchecked()
                        .marked_open = 0;
                    upvalue = open.next();
                } else {
                    let next = open.next();
                    self.close_upvalue(current_upvalue, object.is_white());
                    upvalue = next;
                }
            }

            work
        }
    }

    /// `atomic`
    pub(super) unsafe fn atomic(&self) -> usize {
        unsafe {
            let global = self.global();
            debug_assert_eq!(global.gc_state(), crate::gc::GCS_ATOMIC);

            let mut work = 0;

            work += self.remark_upvalues();
            work += global.propagate_all();

            global.set_gray(global.weak());
            global.set_weak(None);

            debug_assert!(!GcObject::from(global.main_thread()).is_white());
            global.mark_object(self.into());
            global.mark_mt();
            global.mark_userdata_type_metatables();
            if flags::LuauUdataMetatablePinned.get() {
                global.mark_tagged_userdata_metatables();
            }
            if flags::LuauGcMarkUdataAccess.get() {
                global.mark_userdata_direct_access();
            }
            if flags::LuauDirectFieldGet.get() {
                global.mark_userdata_direct_fields();
            }
            work += global.propagate_all();

            global.set_gray(global.gray_again());
            global.set_gray_again(None);
            work += global.propagate_all();

            if flags::LuauGcTraceUdata.get()
                && let Some(embedder_gc) = global.embedder_gc()
            {
                let main_thread = global.main_thread();
                embedder_gc(&main_thread, Some(embedder_mark_ref));
                while global.gray().is_some() {
                    work += global.propagate_all();
                    embedder_gc(&main_thread, Some(embedder_mark_ref));
                }
            }

            work += self.clear_tables(global.weak());
            global.set_weak(None);

            work += self.clear_upvalues();

            (&mut *global.userdata_type_registry_ptr()).remove_unreachable_metatables();

            let global_mut = global.as_ptr().as_mut().unwrap_unchecked();
            global_mut.current_white ^= crate::gc::WHITE_BITS;
            global.set_sweep_gco_page(global.all_gco_pages());
            global.set_gc_state(crate::gc::GCS_SWEEP);

            work
        }
    }
}