luau-vm 0.732.0

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

use crate::function::RawProto;
use crate::function::{Closure, Proto};
use crate::handle::RawHandle;
use crate::handle::sealed::Sealed;
use crate::value::{RawTValue, TValueCursor};

#[repr(C)]
pub struct RawCallInfo {
    pub base: *mut RawTValue,
    pub function: *mut RawTValue,
    pub top: *mut RawTValue,
    pub proto: *mut RawProto,
    pub saved: RawCallInfoSaved,
    pub n_results: i32,
    pub flags: u32,
}

#[repr(C)]
pub union RawCallInfoSaved {
    pub saved_pc: *const u32,
    pub errfunc: i32,
}

#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(transparent)]
/// Non-owning view of a live VM call-frame record.
///
/// # Safety model for unsafe methods
///
/// The owning thread and call-info array must remain live. Stack pointers,
/// saved PCs, flags, and function variants must match the active frame, and
/// call-info or stack relocation invalidates derived views.
pub struct CallInfo {
    raw: NonNull<RawCallInfo>,
}

#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
/// Nullable traversal position in a thread's call-info array.
///
/// Unsafe navigation requires both cursors to belong to the same live array;
/// reallocation or thread destruction invalidates every cursor.
pub struct CallInfoCursor(*mut RawCallInfo);

#[allow(
    clippy::missing_safety_doc,
    reason = "CallInfo's shared raw-view contract is documented on CallInfo"
)]
impl CallInfo {
    pub const unsafe fn from_raw(raw: NonNull<RawCallInfo>) -> Self {
        Self { raw }
    }

    pub fn base(&self) -> TValueCursor {
        unsafe { TValueCursor::from_ptr(self.as_ptr().as_ref().unwrap_unchecked().base) }
    }

    pub fn function(&self) -> TValueCursor {
        unsafe { TValueCursor::from_ptr(self.as_ptr().as_ref().unwrap_unchecked().function) }
    }

    pub fn top(&self) -> TValueCursor {
        unsafe { TValueCursor::from_ptr(self.as_ptr().as_ref().unwrap_unchecked().top) }
    }

    pub fn set_base(&self, base: TValueCursor) {
        unsafe {
            self.as_ptr().as_mut().unwrap_unchecked().base = base.as_ptr();
        }
    }

    pub fn set_function(&self, function: TValueCursor) {
        unsafe {
            self.as_ptr().as_mut().unwrap_unchecked().function = function.as_ptr();
        }
    }

    pub fn set_top(&self, top: TValueCursor) {
        unsafe {
            self.as_ptr().as_mut().unwrap_unchecked().top = top.as_ptr();
        }
    }

    pub fn proto(&self) -> Option<Proto> {
        let proto = unsafe { self.as_ptr().as_ref().unwrap_unchecked().proto };
        NonNull::new(proto).map(|raw| unsafe { Proto::from_raw(raw) })
    }

    pub fn set_proto(&self, proto: Option<Proto>) {
        unsafe {
            self.as_ptr().as_mut().unwrap_unchecked().proto =
                proto.map_or(core::ptr::null_mut(), |proto| proto.as_ptr());
        }
    }

    pub unsafe fn saved_pc(&self) -> *const u32 {
        unsafe { self.as_ptr().as_ref().unwrap_unchecked().saved.saved_pc }
    }

    pub unsafe fn set_saved_pc(&self, saved_pc: *const u32) {
        unsafe {
            self.as_ptr().as_mut().unwrap_unchecked().saved.saved_pc = saved_pc;
        }
    }

    pub unsafe fn errfunc(&self) -> i32 {
        unsafe { self.as_ptr().as_ref().unwrap_unchecked().saved.errfunc }
    }

    pub unsafe fn set_errfunc(&self, errfunc: i32) {
        unsafe {
            self.as_ptr().as_mut().unwrap_unchecked().saved.errfunc = errfunc;
        }
    }

    pub unsafe fn init_call(
        &self,
        function: TValueCursor,
        top: TValueCursor,
        n_results: i32,
        proto: Option<Proto>,
    ) {
        unsafe {
            self.set_function(function);
            self.set_base(function.add(1));
            self.set_top(top);
            self.set_proto(proto);
            self.set_saved_pc(core::ptr::null());
            self.as_ptr().as_mut().unwrap_unchecked().flags = 0;
            self.as_ptr().as_mut().unwrap_unchecked().n_results = n_results;
        }
    }

    pub unsafe fn rebase_stack(&self, old_stack: TValueCursor, new_stack: TValueCursor) {
        unsafe {
            self.set_top(new_stack.add(self.top().addr_offset_from(old_stack) as usize));
            self.set_base(new_stack.add(self.base().addr_offset_from(old_stack) as usize));
            self.set_function(new_stack.add(self.function().addr_offset_from(old_stack) as usize));
        }
    }

    /// `ci_func`
    pub unsafe fn function_closure(&self) -> Closure {
        unsafe { self.function().value_unchecked().closure_value() }
    }

    /// `f_isLua`
    pub unsafe fn is_lua_function(&self) -> bool {
        unsafe { self.function_closure().is_lua() }
    }

    /// `isLua`
    pub unsafe fn is_lua(&self) -> bool {
        unsafe { self.function().value_unchecked().is_function() && self.is_lua_function() }
    }
}

#[allow(
    clippy::missing_safety_doc,
    reason = "CallInfoCursor's navigation contract is documented on CallInfoCursor"
)]
impl CallInfoCursor {
    /// Returns the current traversal address, which may be null.
    ///
    /// CallInfo array growth invalidates cursors into the old allocation.
    pub const fn as_ptr(&self) -> *mut RawCallInfo {
        self.0
    }

    pub const fn from_ptr(raw: *mut RawCallInfo) -> Self {
        Self(raw)
    }

    pub const fn is_null(&self) -> bool {
        self.0.is_null()
    }

    pub unsafe fn call_info_unchecked(&self) -> CallInfo {
        debug_assert!(!self.is_null());
        unsafe { CallInfo::from_raw(NonNull::new_unchecked(self.0)) }
    }

    pub fn call_info(&self) -> Option<CallInfo> {
        NonNull::new(self.0).map(|raw| unsafe { CallInfo::from_raw(raw) })
    }

    pub unsafe fn add(self, count: usize) -> Self {
        unsafe { Self::from_ptr(self.0.add(count)) }
    }

    pub unsafe fn sub(self, count: usize) -> Self {
        unsafe { Self::from_ptr(self.0.sub(count)) }
    }

    pub unsafe fn offset(self, count: isize) -> Self {
        unsafe { Self::from_ptr(self.0.offset(count)) }
    }

    pub unsafe fn offset_from(self, other: Self) -> isize {
        unsafe { self.0.offset_from(other.0) }
    }
}

impl Sealed for CallInfo {}

impl RawHandle for CallInfo {
    type Raw = RawCallInfo;

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

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

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