luau-vm 0.732.0

Pure-Rust Luau virtual machine, garbage collector, and standard libraries
Documentation
use core::ptr;

use luau_common::{ByteSlice, flags};

use crate::VmErrorResult;
use crate::gc::GcObject;
use crate::gc::{GcBarrier, GcRuntime};
use crate::handle::RawHandle;
use crate::metamethod::{MetamethodRuntime, TmEvent};
use crate::state::{EmbedderGc, ThreadState};
use crate::string::LuaString;
use crate::string::StringRuntime;
use crate::table::TableRuntime;
use crate::userdata::UserdataRuntime;
use crate::userdata::{
    LIGHT_USERDATA_TAG_LIMIT, LuaDestructor, LuaInlineDestructor, LuaUserdataDirectAccess,
    LuaUserdataDirectFieldGet, LuaUserdataDirectNamecall, LuaUserdataMark, USERDATA_TAG_LIMIT,
};
use crate::value::{TValue, nil_object};

use super::Thread;
use super::stack::RawStackAccess;

impl Thread {
    /// `lua_newuserdatatagged`
    pub unsafe fn new_userdata_tagged(&self, size: usize, tag: i32) -> VmErrorResult<*mut ()> {
        assert!((tag as u32) < crate::userdata::USERDATA_TAG_LIMIT as u32);
        unsafe { self.new_userdata_tagged_internal(size, tag) }
    }

    /// `lua_newuserdatataggedwithmetatable`
    pub unsafe fn new_userdata_tagged_with_metatable(
        &self,
        size: usize,
        tag: i32,
    ) -> VmErrorResult<*mut ()> {
        assert!((tag as u32) < crate::userdata::USERDATA_TAG_LIMIT as u32);
        unsafe {
            self.check_gc()?;
            self.thread_barrier();
            self.ensure_stack(self, 1)?;

            let userdata = self.new_userdata_internal(size, tag)?;
            debug_assert!(!GcObject::from(userdata).is_black());

            let metatable = self.global().userdata_metatable(tag as usize);
            userdata.set_metatable(metatable);

            let data = userdata.data_mut_ptr();
            let top = self.stack_top();
            top.value_unchecked().set_userdata_value(userdata);
            debug_assert!(top < self.current_call_info().top());
            self.set_stack_top(top.add(1));

            Ok(data.cast())
        }
    }

    /// `lua_newuserdatadtor`
    pub unsafe fn new_userdata_dtor(
        &self,
        size: usize,
        destructor: LuaInlineDestructor,
    ) -> VmErrorResult<*mut ()> {
        let payload_len = size.saturating_add(core::mem::size_of::<LuaInlineDestructor>());
        unsafe {
            self.check_gc()?;
            self.thread_barrier();
            self.ensure_stack(self, 1)?;

            let userdata = self
                .new_userdata_internal(payload_len, crate::userdata::USERDATA_TAG_IDTOR as i32)?;
            userdata.set_inline_destructor(size, destructor);

            let data = userdata.data_mut_ptr();
            let top = self.stack_top();
            top.value_unchecked().set_userdata_value(userdata);
            debug_assert!(top < self.current_call_info().top());
            self.set_stack_top(top.add(1));

            Ok(data.cast())
        }
    }

    /// `lua_newuserdata`
    pub unsafe fn new_userdata(&self, size: usize) -> VmErrorResult<*mut ()> {
        unsafe { self.new_userdata_tagged_internal(size, 0) }
    }

    /// `lua_tolightuserdata`
    pub unsafe fn to_light_userdata(&self, index: i32) -> *mut () {
        let object = unsafe { self.index_to_addr(index) };
        if object == nil_object() || !object.is_light_userdata() {
            ptr::null_mut()
        } else {
            object.pointer_value()
        }
    }

    /// `lua_tolightuserdatatagged`
    pub unsafe fn to_light_userdata_tagged(&self, index: i32, tag: i32) -> *mut () {
        let object = unsafe { self.index_to_addr(index) };
        if object == nil_object()
            || !object.is_light_userdata()
            || object.light_userdata_tag() != tag
        {
            ptr::null_mut()
        } else {
            object.pointer_value()
        }
    }

    /// `lua_touserdata`
    pub unsafe fn to_userdata(&self, index: i32) -> *mut () {
        let object = unsafe { self.index_to_addr(index) };
        if object == nil_object() {
            return ptr::null_mut();
        }

        if object.is_userdata() {
            let userdata = object.userdata_value();
            unsafe { userdata.data_mut_ptr().cast() }
        } else if object.is_light_userdata() {
            object.pointer_value()
        } else {
            ptr::null_mut()
        }
    }

    /// `lua_touserdatatagged`
    pub unsafe fn to_userdata_tagged(&self, index: i32, tag: i32) -> *mut () {
        let object = unsafe { self.index_to_addr(index) };
        if object == nil_object() || !object.is_userdata() {
            return ptr::null_mut();
        }

        let userdata = object.userdata_value();
        if unsafe { userdata.as_ptr().as_ref().unwrap_unchecked().tag as i32 } == tag {
            unsafe { userdata.data_mut_ptr().cast() }
        } else {
            ptr::null_mut()
        }
    }

    /// `lua_userdatatag`
    pub unsafe fn userdata_tag(&self, index: i32) -> i32 {
        let object = unsafe { self.index_to_addr(index) };
        if object == nil_object() || !object.is_userdata() {
            -1
        } else {
            unsafe {
                object
                    .userdata_value()
                    .as_ptr()
                    .as_ref()
                    .unwrap_unchecked()
                    .tag as i32
            }
        }
    }

    /// `lua_lightuserdatatag`
    pub unsafe fn light_userdata_tag(&self, index: i32) -> i32 {
        let object = unsafe { self.index_to_addr(index) };
        if object == nil_object() || !object.is_light_userdata() {
            -1
        } else {
            object.light_userdata_tag()
        }
    }

    /// `lua_setuserdatatag`
    pub unsafe fn set_userdata_tag(&self, index: i32, tag: i32) {
        debug_assert!((tag as u32) < USERDATA_TAG_LIMIT as u32);

        let object = unsafe { self.to_object(index).unwrap_unchecked() };
        debug_assert!(object.is_userdata());

        unsafe {
            object
                .userdata_value()
                .as_ptr()
                .as_mut()
                .unwrap_unchecked()
                .tag = tag as u8
        };
    }

    /// `lua_setuserdatadtor`
    pub unsafe fn set_userdata_dtor(&self, tag: i32, destructor: LuaDestructor) {
        debug_assert!((tag as u32) < USERDATA_TAG_LIMIT as u32);

        unsafe {
            self.global()
                .set_userdata_dtor(tag as usize, Some(destructor))
        };
    }

    /// `lua_getuserdatadtor`
    pub unsafe fn get_userdata_dtor(&self, tag: i32) -> Option<LuaDestructor> {
        debug_assert!((tag as u32) < USERDATA_TAG_LIMIT as u32);

        unsafe { self.global().userdata_dtor(tag as usize) }
    }

    /// `lua_setuserdatamark`
    ///
    /// The callback runs during garbage collection and must not perform
    /// reentrant VM operations. Read-only host state access is permitted.
    pub unsafe fn set_userdata_mark(&self, tag: i32, mark: Option<LuaUserdataMark>) {
        debug_assert!(flags::LuauGcTraceUdata.get());
        debug_assert!((tag as u32) < USERDATA_TAG_LIMIT as u32);

        unsafe { self.global().set_userdata_mark(tag as usize, mark) };
    }

    /// `lua_setembeddergc`
    ///
    /// The callback runs during garbage collection and must not perform
    /// reentrant VM operations. When `mark` is present, the callback may use
    /// it to keep embedder-owned weak references alive for this cycle.
    pub unsafe fn set_embedder_gc(&self, callback: Option<EmbedderGc>) {
        debug_assert!(flags::LuauGcTraceUdata.get());
        unsafe { self.global().set_embedder_gc(callback) };
    }

    /// `lua_setuserdatametatable`
    pub unsafe fn set_userdata_metatable(&self, tag: i32) {
        debug_assert!((tag as u32) < USERDATA_TAG_LIMIT as u32);
        unsafe {
            let top_cursor = self.stack_top();
            debug_assert!(top_cursor.offset_from(self.stack_base()) > 0);
            debug_assert!(self.global().userdata_metatable(tag as usize).is_none());
            let top = top_cursor.sub(1).value_unchecked();
            debug_assert!(top.is_table());

            self.global()
                .set_userdata_metatable(tag as usize, Some(top.table_value()));
            self.set_stack_top(top_cursor.sub(1));
        }
    }

    /// `lua_getuserdatametatable`
    pub unsafe fn get_userdata_metatable(&self, tag: i32) -> VmErrorResult {
        unsafe {
            debug_assert!((tag as u32) < USERDATA_TAG_LIMIT as u32);

            self.thread_barrier();
            self.ensure_stack(self, 1)?;

            let top = self.stack_top();

            if let Some(metatable) = self.global().userdata_metatable(tag as usize) {
                top.value_unchecked().set_table_value(metatable);
            } else {
                top.value_unchecked().set_nil();
            }

            debug_assert!(top < self.current_call_info().top());
            self.set_stack_top(top.add(1));
        }
        Ok(())
    }

    /// `lua_getuserdataname`
    pub unsafe fn get_userdata_name(&self, tag: i32) -> LuaString {
        debug_assert!((tag as u32) < USERDATA_TAG_LIMIT as u32);

        unsafe {
            let global = self.global();
            if let Some(metatable) = global.userdata_metatable(tag as usize) {
                let value = metatable.get_str(global.tm_name(TmEvent::Type as usize));
                if value.is_string() {
                    return LuaString::from_interned(value.string_value());
                }
            }

            LuaString::from_interned(global.type_name(crate::types::LUA_TUSERDATA as usize))
        }
    }

    /// `lua_getlightuserdataname`
    pub unsafe fn get_light_userdata_name(&self, tag: i32) -> Option<LuaString> {
        debug_assert!((tag as u32) < LIGHT_USERDATA_TAG_LIMIT as u32);

        unsafe {
            self.global()
                .light_userdata_name(tag as usize)
                .map(LuaString::from_interned)
        }
    }

    /// `lua_registeruserdatadirectaccess`
    pub unsafe fn register_userdata_direct_access(
        &self,
        tag: i32,
        get: LuaUserdataDirectAccess,
        set: LuaUserdataDirectAccess,
        namecall: Option<LuaUserdataDirectNamecall>,
    ) -> i32 {
        debug_assert!((tag as u32) < USERDATA_TAG_LIMIT as u32);

        unsafe {
            self.thread_barrier();

            let global_handle = self.global();
            let Some(table) = global_handle.userdata_metatable(tag as usize) else {
                return 0;
            };

            let global = global_handle.as_ptr().as_mut().unwrap_unchecked();

            let data = &mut global.userdata_direct[tag as usize];
            TValue::from_mut(&mut data.index_tm).set_nil();
            TValue::from_mut(&mut data.new_index_tm).set_nil();
            TValue::from_mut(&mut data.name_call_tm).set_nil();
            data.index = None;
            data.new_index = None;
            data.name_call = None;

            if let Some(index_tm) = self.fast_tm(Some(table), TmEvent::Index) {
                TValue::from_mut(&mut data.index_tm).set_obj(index_tm);
                data.index = Some(get);
            }

            if let Some(new_index_tm) = self.fast_tm(Some(table), TmEvent::NewIndex) {
                TValue::from_mut(&mut data.new_index_tm).set_obj(new_index_tm);
                data.new_index = Some(set);
            }

            if let Some(name_call_tm) = self.fast_tm(Some(table), TmEvent::NameCall) {
                TValue::from_mut(&mut data.name_call_tm).set_obj(name_call_tm);
                data.name_call = namecall;
            }

            1
        }
    }

    /// `lua_registeruserdatadirectfieldget`
    pub unsafe fn register_userdata_direct_field_get(
        &self,
        tag: i32,
        field: &[u8],
        get: LuaUserdataDirectFieldGet,
    ) -> VmErrorResult {
        unsafe {
            if !flags::LuauDirectFieldGet.get() {
                return Ok(());
            }

            debug_assert!((tag as u32) < USERDATA_TAG_LIMIT as u32);

            let global = self.global();
            if global.userdata_direct_field(tag as usize).is_none() {
                let table = self.new_table_internal(0, 1)?;
                global.set_userdata_direct_field(tag as usize, Some(table));
            }

            let string = self.intern_string(field.as_bstr())?;
            string.fix();

            let table = global
                .userdata_direct_field(tag as usize)
                .unwrap_unchecked();
            let node_cursor = self.set_str(table, string)?;
            node_cursor
                .node_unchecked()
                .value_unchecked()
                .set_light_userdata(get as *const () as *mut (), 0);
        }
        Ok(())
    }

    /// `lua_setlightuserdataname`
    pub unsafe fn set_light_userdata_name(&self, tag: i32, name: &str) -> VmErrorResult {
        unsafe {
            debug_assert!((tag as u32) < LIGHT_USERDATA_TAG_LIMIT as u32);
            debug_assert!(self.global().light_userdata_name(tag as usize).is_none());

            let global = self.global();
            if global.light_userdata_name(tag as usize).is_none() {
                let string = self.intern_string(name.as_bytes().as_bstr())?;
                string.fix();
                global.set_light_userdata_name(tag as usize, Some(string));
            }
        }
        Ok(())
    }
}