luau-vm 0.732.0

Pure-Rust Luau virtual machine, garbage collector, and standard libraries
Documentation
use core::alloc::Layout;
use core::cell::UnsafeCell;
use core::mem::{ManuallyDrop, size_of};
use core::ptr::{self, NonNull};

use crate::call::ProtectedCall;
use crate::function::FunctionRuntime;
use crate::function::{RawUpVal, RawUpValData, RawUpValOpen};
use crate::gc::{FIXED_BIT, GcRuntime, GcStats, WHITE0_BIT, bit_mask};
use crate::handle::RawHandle;
use crate::handle::sealed::Sealed;
use crate::memory::MemoryRuntime;
use crate::memory::{LUA_MEMORY_CATEGORIES, LUA_SIZE_CLASSES};
use crate::metamethod::TM_N;
use crate::state::ThreadState;
use crate::state::{
    ExecutionCallbackStorage, LUA_EXECUTION_CALLBACK_STORAGE, LuaCallbacks, LuaExecutionCallbacks,
    RawGlobalState, RawLuaState, RawMainState, THREAD_STATUS_OK, open_main_state,
};
use crate::state::{LuaAllocator, VmAllocator};
use crate::string::StringTable;
use crate::types::{LUA_T_COUNT, LUA_TTHREAD};
use crate::userdata::{LuaUserdataDirectAccessData, UserdataTypeRegistry};
use crate::value::RAW_TVALUE_NIL;

use crate::thread::Thread;

pub struct Lua {
    thread: Thread,
    allocator: NonNull<VmAllocator>,
}

impl Lua {
    /// `luaL_newstate`
    pub fn new() -> Option<Self> {
        Self::new_with_vm_allocator(VmAllocator::system())
    }

    pub fn new_with_allocator<A: LuaAllocator + 'static>(allocator: A) -> Option<Self> {
        Self::new_with_vm_allocator(VmAllocator::custom(allocator))
    }

    fn new_with_vm_allocator(allocator: VmAllocator) -> Option<Self> {
        let allocator = Box::new(allocator);
        let main_state =
            unsafe { allocator.allocate(Layout::new::<RawMainState>()) }?.cast::<RawMainState>();
        let allocator = unsafe { NonNull::new_unchecked(Box::into_raw(allocator)) };

        unsafe {
            let main_state = main_state.as_ptr();
            let current_white = bit_mask(WHITE0_BIT) | bit_mask(FIXED_BIT);
            let state_ptr = &raw mut (*main_state).state;
            let global_ptr = &raw mut (*main_state).global;
            let uv_head_ptr = &raw mut (*global_ptr).uv_head;

            let mut memcat_bytes = [0; LUA_MEMORY_CATEGORIES];
            memcat_bytes[0] = size_of::<RawMainState>();

            ptr::write(
                state_ptr,
                RawLuaState {
                    tt: LUA_TTHREAD as u8,
                    marked: current_white,
                    memcat: 0,
                    status: THREAD_STATUS_OK,
                    active_memcat: 0,
                    is_active: false,
                    single_step: false,
                    top: ptr::null_mut(),
                    base: ptr::null_mut(),
                    global: global_ptr,
                    ci: ptr::null_mut(),
                    stack_last: ptr::null_mut(),
                    stack: ptr::null_mut(),
                    end_ci: ptr::null_mut(),
                    base_ci: ptr::null_mut(),
                    stack_size: 0,
                    size_ci: 0,
                    native_call_depth: 0,
                    base_native_call_depth: 0,
                    cached_slot: 0,
                    gt: ptr::null_mut(),
                    open_upval: ptr::null_mut(),
                    gc_list: ptr::null_mut(),
                    name_call: ptr::null_mut(),
                    userdata: ptr::null_mut(),
                },
            );

            ptr::write(
                global_ptr,
                RawGlobalState {
                    string_table: StringTable {
                        hash: ptr::null_mut(),
                        n_use: 0,
                        size: 0,
                    },
                    allocator,
                    current_white,
                    gc_state: crate::gc::GCS_PAUSE,
                    gray: ptr::null_mut(),
                    gray_again: ptr::null_mut(),
                    weak: ptr::null_mut(),
                    gc_threshold: 0,
                    total_bytes: size_of::<RawMainState>(),
                    gc_goal: 200,
                    gc_step_mul: 200,
                    gc_step_size: 1024,
                    free_pages: [ptr::null_mut(); LUA_SIZE_CLASSES],
                    free_gco_pages: [ptr::null_mut(); LUA_SIZE_CLASSES],
                    all_pages: ptr::null_mut(),
                    all_gco_pages: ptr::null_mut(),
                    sweep_gco_page: ptr::null_mut(),
                    main_thread: state_ptr,
                    uv_head: RawUpVal {
                        tt: 0,
                        marked: 0,
                        memcat: 0,
                        marked_open: 0,
                        value: ptr::null_mut(),
                        data: RawUpValData {
                            open: ManuallyDrop::new(RawUpValOpen {
                                prev: uv_head_ptr,
                                next: uv_head_ptr,
                                thread_next: ptr::null_mut(),
                            }),
                        },
                    },
                    mt: [ptr::null_mut(); LUA_T_COUNT],
                    tt_name: [ptr::null_mut(); LUA_T_COUNT],
                    tm_name: [ptr::null_mut(); TM_N],
                    pseudo_temp: RAW_TVALUE_NIL,
                    registry: RAW_TVALUE_NIL,
                    registry_free: 0,
                    protected_error: ptr::null_mut(),
                    rng_state: 0,
                    ptr_enc_key: [1, 0, 0, 0],
                    cb: LuaCallbacks::default(),
                    ecb: LuaExecutionCallbacks::default(),
                    ecb_data: ExecutionCallbackStorage {
                        bytes: [0; LUA_EXECUTION_CALLBACK_STORAGE],
                    },
                    userdata_type_registry: UnsafeCell::new(UserdataTypeRegistry::new()),
                    userdata_direct: core::array::from_fn(|_| LuaUserdataDirectAccessData {
                        index_tm: RAW_TVALUE_NIL,
                        new_index_tm: RAW_TVALUE_NIL,
                        name_call_tm: RAW_TVALUE_NIL,
                        index: None,
                        new_index: None,
                        name_call: None,
                    }),
                    memcat_bytes,
                    userdata_gc: [None; crate::userdata::USERDATA_TAG_LIMIT],
                    userdata_mark: [None; crate::userdata::USERDATA_TAG_LIMIT],
                    userdata_mt: [ptr::null_mut(); crate::userdata::USERDATA_TAG_LIMIT],
                    weak_registry: RAW_TVALUE_NIL,
                    weak_registry_free: 0,
                    embedder_gc: None,
                    light_userdata_name: [ptr::null_mut();
                        crate::userdata::LIGHT_USERDATA_TAG_LIMIT],
                    userdata_direct_fields: [ptr::null_mut();
                        crate::userdata::USERDATA_INTERNAL_LIMIT],
                    gc_stats: GcStats::default(),
                    last_proto_id: 1,
                },
            );

            let raw = NonNull::new_unchecked(state_ptr);
            let thread = Thread::from_raw(raw);
            let lua = Lua { thread, allocator };
            let thread = lua.main_thread();

            let mut unit = ();
            thread
                .raw_run_protected(open_main_state, &mut unit)
                .is_ok()
                .then_some(lua)
        }
    }

    /// `lua_mainthread`
    pub fn main_thread(&self) -> &Thread {
        &self.thread
    }
}

impl Sealed for Lua {}

impl RawHandle for Lua {
    type Raw = RawLuaState;

    fn as_ptr(&self) -> *mut Self::Raw {
        self.thread.as_ptr()
    }
}

impl AsRef<Lua> for Lua {
    fn as_ref(&self) -> &Lua {
        self
    }
}

impl Drop for Lua {
    fn drop(&mut self) {
        unsafe {
            let thread = self.main_thread();
            let global = thread.global();
            if let Some(stack) = thread.stack().value() {
                thread.close(stack);
            }

            thread.free_all();
            UserdataTypeRegistry::free(global.userdata_type_registry_ptr(), thread);

            assert_eq!(
                global
                    .as_ptr()
                    .as_ref()
                    .unwrap_unchecked()
                    .string_table
                    .n_use,
                0
            );
            if !global
                .as_ptr()
                .as_ref()
                .unwrap_unchecked()
                .string_table
                .hash
                .is_null()
            {
                thread.free_array(
                    global
                        .as_ptr()
                        .as_ref()
                        .unwrap_unchecked()
                        .string_table
                        .hash,
                    global
                        .as_ptr()
                        .as_ref()
                        .unwrap_unchecked()
                        .string_table
                        .size
                        .max(0) as usize,
                    0,
                );
            }

            thread.free_stack(thread);

            for index in 0..LUA_SIZE_CLASSES {
                assert!(global.as_ptr().as_ref().unwrap_unchecked().free_pages[index].is_null());
                assert!(
                    global.as_ptr().as_ref().unwrap_unchecked().free_gco_pages[index].is_null()
                );
            }

            assert!(
                global
                    .as_ptr()
                    .as_ref()
                    .unwrap_unchecked()
                    .all_gco_pages
                    .is_null()
            );
            assert_eq!(
                global.as_ptr().as_ref().unwrap_unchecked().total_bytes,
                size_of::<RawMainState>()
            );
            assert_eq!(
                global.as_ptr().as_ref().unwrap_unchecked().memcat_bytes[0],
                size_of::<RawMainState>()
            );
            for index in 1..LUA_MEMORY_CATEGORIES {
                assert_eq!(
                    global.as_ptr().as_ref().unwrap_unchecked().memcat_bytes[index],
                    0
                );
            }

            if let Some(close) = global.execution_close() {
                close(thread);
            }

            let allocator = Box::from_raw(self.allocator.as_ptr());
            allocator.deallocate(
                NonNull::new_unchecked(thread.as_ptr().cast()),
                Layout::new::<RawMainState>(),
            );
        }
    }
}