luau-vm 0.732.0

Pure-Rust Luau virtual machine, garbage collector, and standard libraries
Documentation
use crate::debug::DebugRuntime;
use crate::handle::RawHandle;
use crate::handle::sealed::Sealed;
use crate::memory::MemoryRuntime;
use crate::state::ThreadState;
use crate::state::{CallInfoCursor, RawCallInfo};
use crate::thread::{LUAI_MAX_CALLS, LUAI_MAX_NATIVE_CALLS, Thread};
use crate::value::{RawTValue, TValueCursor};
use crate::{VmError, VmErrorResult};

pub(super) const MAX_STACK_SIZE: i32 =
    ((1024 / core::mem::size_of::<RawTValue>()) * 1024 * 1024) as i32;
pub(super) const MAX_CALLS_HARD: usize = LUAI_MAX_CALLS + (LUAI_MAX_CALLS >> 3);

/// Unstable stack and call-frame relocation capability.
///
/// # Safety
///
/// All pointers and cursors must belong to this thread's current stack or
/// call-info arrays. Saved offsets must originate from the matching array.
/// Growth and reallocation invalidate outstanding cursors and record views.
#[allow(
    clippy::missing_safety_doc,
    reason = "all methods share the capability-level safety contract"
)]
pub trait ThreadStack: Sealed {
    unsafe fn check_stack_internal(&self, needed: i32) -> VmErrorResult;
    unsafe fn check_stack_for_new_ci(&self, needed: i32) -> VmErrorResult;
    unsafe fn get_grown_stack_size(&self, needed: i32) -> i32;
    unsafe fn stack_limit_reached(&self, needed: i32) -> bool;
    unsafe fn save_stack(&self, pointer: TValueCursor) -> isize;
    unsafe fn restore_stack(&self, offset: isize) -> TValueCursor;
    unsafe fn save_ci(&self, pointer: CallInfoCursor) -> isize;
    unsafe fn restore_ci(&self, offset: isize) -> CallInfoCursor;
    unsafe fn expand_stack_limit(&self, pointer: TValueCursor);
    unsafe fn realloc_stack(&self, new_size: i32, for_new_ci: bool) -> VmErrorResult;
    unsafe fn realloc_ci(&self, new_size: i32) -> VmErrorResult;
    unsafe fn grow_stack(&self, needed: i32) -> VmErrorResult;
    unsafe fn grow_ci(&self) -> VmErrorResult<CallInfoCursor>;
    unsafe fn incr_ci(&self) -> VmErrorResult<CallInfoCursor>;
    unsafe fn check_c_stack(&self) -> VmErrorResult;
}

impl Thread {
    /// `correctstack`
    unsafe fn correct_stack(&self, old_stack: TValueCursor) {
        unsafe {
            let new_stack_cursor = self.stack();
            let top_offset = self.stack_top().addr_offset_from(old_stack) as usize;
            self.set_stack_top(new_stack_cursor.add(top_offset));

            let mut upvalue = self.open_upvalue();
            while let Some(current_upvalue) = upvalue {
                current_upvalue.rebase_value(old_stack, new_stack_cursor);
                upvalue = current_upvalue.open_data().thread_next();
            }

            let mut call_info_cursor = self.base_call_info_cursor();
            let current_call_info_cursor = self.current_call_info_cursor();
            while call_info_cursor <= current_call_info_cursor {
                call_info_cursor
                    .call_info_unchecked()
                    .rebase_stack(old_stack, new_stack_cursor);
                call_info_cursor = call_info_cursor.add(1);
            }

            let base_offset = self.stack_base().addr_offset_from(old_stack) as usize;
            self.set_stack_base(new_stack_cursor.add(base_offset));
        }
    }
}

impl ThreadStack for Thread {
    /// `luaD_checkstack`
    unsafe fn check_stack_internal(&self, needed: i32) -> VmErrorResult {
        if unsafe { self.stack_limit_reached(needed) } {
            unsafe { self.grow_stack(needed)? };
        }
        Ok(())
    }

    /// `luaD_checkstackfornewci`
    unsafe fn check_stack_for_new_ci(&self, needed: i32) -> VmErrorResult {
        if unsafe { self.stack_limit_reached(needed) } {
            unsafe { self.realloc_stack(self.get_grown_stack_size(needed), true)? };
        }
        Ok(())
    }

    /// `getgrownstacksize`
    unsafe fn get_grown_stack_size(&self, needed: i32) -> i32 {
        let stack_size = unsafe { self.as_ptr().as_ref().unwrap_unchecked().stack_size };
        if needed <= stack_size {
            2 * stack_size
        } else {
            stack_size + needed
        }
    }

    /// `stacklimitreached`
    unsafe fn stack_limit_reached(&self, needed: i32) -> bool {
        let thread = unsafe { self.as_ptr().as_ref().unwrap_unchecked() };
        let available_bytes = (thread.stack_last as usize).wrapping_sub(thread.top as usize);
        available_bytes <= needed as usize * core::mem::size_of::<RawTValue>()
    }

    /// `savestack`
    unsafe fn save_stack(&self, pointer: TValueCursor) -> isize {
        let stack = unsafe { self.stack() };
        unsafe {
            pointer
                .as_ptr()
                .cast::<u8>()
                .offset_from(stack.as_ptr().cast::<u8>())
        }
    }

    /// `restorestack`
    unsafe fn restore_stack(&self, offset: isize) -> TValueCursor {
        unsafe { TValueCursor::from_ptr(self.stack().as_ptr().cast::<u8>().offset(offset).cast()) }
    }

    /// `saveci`
    unsafe fn save_ci(&self, pointer: CallInfoCursor) -> isize {
        let base_ci = unsafe { self.as_ptr().as_ref().unwrap_unchecked().base_ci };
        unsafe {
            pointer
                .as_ptr()
                .cast::<u8>()
                .offset_from(base_ci.cast::<u8>())
        }
    }

    /// `restoreci`
    unsafe fn restore_ci(&self, offset: isize) -> CallInfoCursor {
        unsafe {
            CallInfoCursor::from_ptr(
                self.as_ptr()
                    .as_ref()
                    .unwrap_unchecked()
                    .base_ci
                    .cast::<u8>()
                    .offset(offset)
                    .cast::<RawCallInfo>(),
            )
        }
    }

    /// `expandstacklimit`
    unsafe fn expand_stack_limit(&self, pointer: TValueCursor) {
        debug_assert!(pointer <= unsafe { self.stack_last() });

        unsafe {
            let current_call_info = self.current_call_info();
            if current_call_info.top() < pointer {
                current_call_info.set_top(pointer);
            }
        }
    }

    /// `luaD_reallocstack`
    unsafe fn realloc_stack(&self, new_size: i32, for_new_ci: bool) -> VmErrorResult {
        unsafe {
            if new_size > MAX_STACK_SIZE {
                if for_new_ci {
                    let parent_cursor = self.current_call_info_cursor().sub(1);
                    self.restore_call_frame(
                        parent_cursor,
                        parent_cursor.call_info_unchecked().top(),
                    );
                }

                return Err(VmError::Memory);
            }

            let old_stack = self.stack();
            let real_size = new_size + crate::state::EXTRA_STACK as i32;
            debug_assert!(
                self.as_ptr()
                    .as_ref()
                    .unwrap_unchecked()
                    .stack_last
                    .offset_from(self.as_ptr().as_ref().unwrap_unchecked().stack)
                    == self.as_ptr().as_ref().unwrap_unchecked().stack_size as isize
                        - crate::state::EXTRA_STACK as isize
            );

            let old_stack_size = self.as_ptr().as_ref().unwrap_unchecked().stack_size as usize;
            let new_stack_size = real_size as usize;
            let memcat = self.as_ptr().as_ref().unwrap_unchecked().active_memcat;
            let new_stack = TValueCursor::from_ptr(self.realloc_array(
                old_stack.as_ptr(),
                old_stack_size,
                new_stack_size,
                memcat,
            )?);

            self.set_stack(new_stack);

            for index in old_stack_size..new_stack_size {
                new_stack.add(index).value_unchecked().set_nil();
            }

            let raw = self.as_ptr().as_mut().unwrap_unchecked();
            raw.stack_size = real_size;
            raw.stack_last = new_stack.add(new_size as usize).as_ptr();
            self.correct_stack(old_stack);
        }
        Ok(())
    }

    /// `luaD_reallocCI`
    unsafe fn realloc_ci(&self, new_size: i32) -> VmErrorResult {
        unsafe {
            let old_ci = self.as_ptr().as_ref().unwrap_unchecked().base_ci;
            let old_size = self.as_ptr().as_ref().unwrap_unchecked().size_ci as usize;
            let new_size_usize = new_size as usize;
            let memcat = self.as_ptr().as_ref().unwrap_unchecked().active_memcat;
            let new_ci = self.realloc_array(old_ci, old_size, new_size_usize, memcat)?;
            let current_offset =
                self.current_call_info_cursor()
                    .offset_from(self.base_call_info_cursor()) as usize;

            let raw = self.as_ptr().as_mut().unwrap_unchecked();
            raw.base_ci = new_ci;
            raw.size_ci = new_size;
            raw.end_ci = new_ci.add(new_size_usize - 1);
            self.set_current_call_info(self.base_call_info_cursor().add(current_offset));
        }
        Ok(())
    }

    /// `luaD_growstack`
    unsafe fn grow_stack(&self, needed: i32) -> VmErrorResult {
        unsafe { self.realloc_stack(self.get_grown_stack_size(needed), false) }
    }

    /// `luaD_growCI`
    unsafe fn grow_ci(&self) -> VmErrorResult<CallInfoCursor> {
        unsafe {
            if self.as_ptr().as_ref().unwrap_unchecked().size_ci as usize >= MAX_CALLS_HARD {
                return Err(VmError::ErrorHandler);
            }

            let current_size = self.as_ptr().as_ref().unwrap_unchecked().size_ci;
            let request = current_size * 2;
            let new_size = if current_size as usize >= LUAI_MAX_CALLS {
                MAX_CALLS_HARD as i32
            } else if request as usize >= LUAI_MAX_CALLS {
                LUAI_MAX_CALLS as i32
            } else {
                request
            };

            self.realloc_ci(new_size)?;

            if self.as_ptr().as_ref().unwrap_unchecked().size_ci as usize > LUAI_MAX_CALLS {
                return crate::run_error!(self, "stack overflow");
            }

            let call_info = self.current_call_info_cursor().add(1);
            self.set_current_call_info(call_info);
            Ok(call_info)
        }
    }

    /// `incr_ci`
    unsafe fn incr_ci(&self) -> VmErrorResult<CallInfoCursor> {
        unsafe {
            if self.current_call_info_cursor()
                == self
                    .base_call_info_cursor()
                    .add(self.as_ptr().as_ref().unwrap_unchecked().size_ci as usize - 1)
            {
                self.grow_ci()
            } else {
                let call_info = self.current_call_info_cursor().add(1);
                self.set_current_call_info(call_info);
                Ok(call_info)
            }
        }
    }

    /// `luaD_checkCstack`
    unsafe fn check_c_stack(&self) -> VmErrorResult {
        unsafe {
            let native_call_depth = self.as_ptr().as_ref().unwrap_unchecked().native_call_depth;
            if native_call_depth == LUAI_MAX_NATIVE_CALLS {
                return crate::run_error!(self, "C stack overflow");
            } else if native_call_depth >= crate::state::MAX_NATIVE_CALLS_HARD {
                return Err(VmError::ErrorHandler);
            }
        }
        Ok(())
    }
}