luau-vm 0.732.0

Pure-Rust Luau virtual machine, garbage collector, and standard libraries
Documentation
mod coroutine;
mod protected;
mod stack;

pub(crate) use coroutine::{resume as resume_protected, resume_handle};
pub use protected::{ErrorRuntime, LuaProtectedErrorFrame, Pfunc, ProtectedCall};
pub use stack::ThreadStack;

use crate::gc::{GcBarrier, GcRuntime};
use crate::handle::RawHandle;
use crate::handle::sealed::Sealed;
use crate::state::ThreadState;
use crate::state::{THREAD_STATUS_BREAK, THREAD_STATUS_SCHEDULED_REENTRY, THREAD_STATUS_YIELD};
use crate::thread::{LUA_MULTRET, Thread};
use crate::value::TValueCursor;
use crate::vm::{PreCallResult, VmCallFrame, VmExecution};
use crate::{VmControl, VmExit, VmResult};

/// Unstable callable-invocation capability.
///
/// # Safety
///
/// Function cursors must be live positions in this thread's current stack,
/// the argument/result stack shape must match the requested call, and callers
/// must account for execution, yielding, stack relocation, and collection.
#[allow(
    clippy::missing_safety_doc,
    reason = "all methods share the capability-level safety contract"
)]
pub trait CallRuntime: Sealed {
    unsafe fn perform_cally(&self, function: TValueCursor, n_results: i32) -> VmResult;
    unsafe fn call_int(
        &self,
        function: TValueCursor,
        n_results: i32,
        prepare_reentry: bool,
    ) -> VmResult;
    unsafe fn call_internal(&self, function: TValueCursor, n_results: i32) -> VmResult;
    unsafe fn call_no_yield(&self, function: TValueCursor, n_results: i32) -> VmResult;
}

/// `performcall`
unsafe fn perform_call(
    thread: &Thread,
    function: TValueCursor,
    n_results: i32,
    prepare_reentry: bool,
) -> VmResult {
    if unsafe { thread.pre_call(function, n_results)? } == PreCallResult::Lua {
        unsafe {
            let call_info = thread.current_call_info();
            call_info.as_ptr().as_mut().unwrap_unchecked().flags |=
                crate::state::LUA_CALLINFO_RETURN;

            let old_active = thread.as_ptr().as_ref().unwrap_unchecked().is_active;
            thread.as_ptr().as_mut().unwrap_unchecked().is_active = true;
            thread.thread_barrier();

            if prepare_reentry {
                thread.as_ptr().as_mut().unwrap_unchecked().status =
                    THREAD_STATUS_SCHEDULED_REENTRY;
                if !old_active {
                    thread.as_ptr().as_mut().unwrap_unchecked().is_active = false;
                }
                Err(VmExit::Control(VmControl::Yield))
            } else {
                let result = thread.execute();
                if !old_active {
                    thread.as_ptr().as_mut().unwrap_unchecked().is_active = false;
                }
                result
            }
        }
    } else {
        Ok(())
    }
}

impl CallRuntime for Thread {
    /// `luaD_performcally`
    unsafe fn perform_cally(&self, function: TValueCursor, n_results: i32) -> VmResult {
        unsafe {
            self.increment_native_call_depth();
            if self.as_ptr().as_ref().unwrap_unchecked().native_call_depth
                >= crate::thread::LUAI_MAX_NATIVE_CALLS
            {
                self.check_c_stack()?;
            }
            self.increment_base_native_call_depth();
            let ci_offset = self.save_ci(self.current_call_info_cursor());

            match perform_call(self, function, n_results, false) {
                Ok(()) => {}
                Err(VmExit::Control(control)) => {
                    let caller = self.restore_ci(ci_offset).call_info_unchecked();

                    caller.as_ptr().as_mut().unwrap_unchecked().flags |=
                        crate::state::LUA_CALLINFO_OP_YIELD;

                    return Err(VmExit::Control(control));
                }
                Err(exit) => return Err(exit),
            }

            self.decrement_base_native_call_depth();
            self.decrement_native_call_depth();
            self.check_gc()?;
            Ok(())
        }
    }

    /// `luaD_callint`
    unsafe fn call_int(
        &self,
        function: TValueCursor,
        n_results: i32,
        prepare_reentry: bool,
    ) -> VmResult {
        let (from_yieldable_native_call, function_offset, ci_offset) = unsafe {
            self.increment_native_call_depth();
            if self.as_ptr().as_ref().unwrap_unchecked().native_call_depth
                >= crate::thread::LUAI_MAX_NATIVE_CALLS
            {
                self.check_c_stack()?;
            }

            let mut from_yieldable_native_call = false;
            if self.current_call_info() != self.base_call_info() {
                let current = self.current_call_info().function_closure();
                if current.is_native() && current.native_data().continuation.is_some() {
                    from_yieldable_native_call = true;
                    self.increment_base_native_call_depth();
                }
            }

            let function_offset = self.save_stack(function);
            let ci_offset = self.save_ci(self.current_call_info_cursor());
            (from_yieldable_native_call, function_offset, ci_offset)
        };

        unsafe {
            let result = perform_call(self, function, n_results, prepare_reentry);
            let control = match result {
                Ok(()) => None,
                Err(VmExit::Control(control)) => Some(control),
                Err(exit) => return Err(exit),
            };
            let suspended = control.is_some();

            if from_yieldable_native_call {
                self.decrement_base_native_call_depth();

                if suspended {
                    let caller = self.restore_ci(ci_offset).call_info_unchecked();
                    let caller_top =
                        self.restore_stack(function_offset)
                            .add(if n_results != LUA_MULTRET {
                                n_results as usize
                            } else {
                                0
                            });

                    caller.set_top(caller_top);
                }
            }

            if n_results != LUA_MULTRET && !suspended {
                self.set_stack_top(self.restore_stack(function_offset).add(n_results as usize))
            }

            self.decrement_native_call_depth();
            self.check_gc()?;

            if let Some(control) = control {
                return Err(VmExit::Control(control));
            }
        }
        Ok(())
    }

    /// `luaD_call`
    unsafe fn call_internal(&self, function: TValueCursor, n_results: i32) -> VmResult {
        unsafe { self.call_int(function, n_results, false) }
    }

    /// `luaD_callny`
    unsafe fn call_no_yield(&self, function: TValueCursor, n_results: i32) -> VmResult {
        unsafe {
            self.increment_native_call_depth();
            if self.as_ptr().as_ref().unwrap_unchecked().native_call_depth
                >= crate::thread::LUAI_MAX_NATIVE_CALLS
            {
                self.check_c_stack()?;
            }
            debug_assert!(
                self.as_ptr().as_ref().unwrap_unchecked().native_call_depth
                    > self
                        .as_ptr()
                        .as_ref()
                        .unwrap_unchecked()
                        .base_native_call_depth
            );
            let function_offset = self.save_stack(function);
            perform_call(self, function, n_results, false)?;

            debug_assert!(!matches!(
                self.as_ptr().as_ref().unwrap_unchecked().status,
                THREAD_STATUS_YIELD | THREAD_STATUS_BREAK | THREAD_STATUS_SCHEDULED_REENTRY
            ));
            if n_results != LUA_MULTRET {
                self.set_stack_top(self.restore_stack(function_offset).add(n_results as usize))
            }

            self.decrement_native_call_depth();
            self.check_gc()?;
        }
        Ok(())
    }
}