luau-vm 0.732.0

Pure-Rust Luau virtual machine, garbage collector, and standard libraries
Documentation
use core::sync::atomic::{AtomicBool, Ordering};

use luau_common::BStr;

use crate::debug::{LuaDebugInterruptHook, LuaHook};
use crate::function::{Closure, Proto};
use crate::gc::{GCS_ATOMIC, GCS_PAUSE, GCS_PROPAGATE, GCS_PROPAGATE_AGAIN, GCS_SWEEP};
use crate::handle::RawHandle;
use crate::thread::Thread;
use crate::{VmErrorResult, VmResult};

use super::{GlobalState, LUA_EXECUTION_CALLBACK_STORAGE};

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum InterruptKind {
    Execution,
    Pattern,
    Gc(GcInterrupt),
}

/// Atomic request state for VM interrupt callbacks.
#[derive(Debug, Default)]
pub struct InterruptRequest {
    pending: AtomicBool,
}

impl InterruptRequest {
    /// Creates an idle interrupt request.
    pub const fn new() -> Self {
        Self {
            pending: AtomicBool::new(false),
        }
    }

    /// Requests delivery at the next interrupt point.
    pub fn request(&self) {
        self.pending.store(true, Ordering::Release);
    }

    /// Clears the pending interrupt request.
    pub fn clear(&self) {
        self.pending.store(false, Ordering::Relaxed);
    }

    fn take(&self) -> bool {
        if !self.pending.load(Ordering::Relaxed) {
            return false;
        }
        self.pending.swap(false, Ordering::Acquire)
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum GcPhase {
    Pause,
    Propagate,
    PropagateAgain,
    Atomic,
    Sweep,
}

impl GcPhase {
    pub(crate) fn from_state(state: u8) -> Self {
        match state {
            GCS_PAUSE => Self::Pause,
            GCS_PROPAGATE => Self::Propagate,
            GCS_PROPAGATE_AGAIN => Self::PropagateAgain,
            GCS_ATOMIC => Self::Atomic,
            GCS_SWEEP => Self::Sweep,
            _ => unreachable!("unexpected gc state {state}"),
        }
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum GcInterrupt {
    BeforeStep,
    AfterStep { previous_phase: GcPhase },
}

pub type ExecutionInterrupt = fn(&Thread) -> VmResult;
pub type PatternInterrupt = fn(&Thread) -> VmErrorResult;
pub type GcInterruptCallback = fn(&Thread, GcInterrupt) -> VmErrorResult;
pub type UserThreadCallback = fn(Option<&Thread>, &Thread);
pub type UserAtomCallback = fn(&Thread, &BStr) -> i16;
pub type ProtectedErrorCallback = fn(&Thread) -> ProtectedErrorAction;
pub type AllocateCallback = fn(&Thread, usize, usize);
pub type EmbedderMark = fn(&Thread, i32);
pub type EmbedderGc = fn(&Thread, Option<EmbedderMark>);
pub type ExecutionClose = unsafe fn(&Thread);
pub type ExecutionDestroy = unsafe fn(&Thread, Proto);
pub type ExecutionEnter = unsafe fn(&Thread, Proto) -> i32;
pub type ExecutionDisable = unsafe fn(&Thread, Proto);
pub type ExecutionMemorySize = unsafe fn(&Thread, Proto) -> usize;
pub type ExecutionTypeMapping = unsafe fn(&Thread, &BStr) -> u8;
pub type ExecutionCounterData = unsafe fn(&Thread, Proto, *mut usize) -> *mut u8;
pub type ExecutionInlineFunction = unsafe fn(&Thread, Closure, Closure, u32) -> Option<Proto>;

#[derive(Clone, Copy, Default)]
#[repr(C)]
/// VM callback record installed by an embedder.
///
/// The VM owns the record storage. Embedders may update it only while the VM
/// is quiescent; execution must never observe a partial update. The callback
/// fields are not atomic and their raw address is not a cross-thread
/// capability. When `interrupt_request` is non-null, it must remain valid while
/// installed. The VM atomically consumes a request before delivering an
/// interrupt.
pub struct LuaCallbacks {
    pub userdata: *mut (),
    pub execution_interrupt: Option<ExecutionInterrupt>,
    pub pattern_interrupt: Option<PatternInterrupt>,
    pub gc_interrupt: Option<GcInterruptCallback>,
    pub interrupt_request: *const InterruptRequest,
    pub user_thread: Option<UserThreadCallback>,
    pub user_atom: Option<UserAtomCallback>,
    pub debug_break: Option<LuaHook>,
    pub debug_step: Option<LuaHook>,
    pub debug_interrupt: Option<LuaDebugInterruptHook>,
    pub debug_protected_error: Option<ProtectedErrorCallback>,
    pub on_allocate: Option<AllocateCallback>,
}

#[derive(Clone, Copy, Default)]
#[repr(C)]
/// Execution-engine callback record installed by an advanced embedder.
///
/// The record must be installed or updated as one coherent operation while
/// the VM is quiescent. Its fields are plain, non-atomic storage and must not
/// be mutated concurrently with VM execution.
pub struct LuaExecutionCallbacks {
    pub context: *mut (),
    pub close: Option<ExecutionClose>,
    pub destroy: Option<ExecutionDestroy>,
    pub enter: Option<ExecutionEnter>,
    pub disable: Option<ExecutionDisable>,
    pub get_memory_size: Option<ExecutionMemorySize>,
    pub get_type_mapping: Option<ExecutionTypeMapping>,
    pub get_counter_data: Option<ExecutionCounterData>,
    pub inline_function: Option<ExecutionInlineFunction>,
}

#[repr(C, align(16))]
pub struct ExecutionCallbackStorage {
    pub bytes: [u8; LUA_EXECUTION_CALLBACK_STORAGE],
}

impl GlobalState {
    /// Returns the VM-owned public callback record address.
    ///
    /// The pointer is for quiescent installation and focused VM reads. It
    /// does not establish a Rust borrow and must not be used to mutate the
    /// record while the VM is executing.
    pub fn callbacks(&self) -> *mut LuaCallbacks {
        unsafe { &raw mut (*self.as_ptr()).cb }
    }

    /// Returns the VM-owned execution callback record address.
    ///
    /// The pointer is for quiescent installation and focused VM reads. It
    /// does not establish a Rust borrow and must not be used to mutate the
    /// record while the VM is executing.
    pub fn execution_callbacks(&self) -> *mut LuaExecutionCallbacks {
        unsafe { &raw mut (*self.as_ptr()).ecb }
    }

    pub(crate) fn user_thread_callback(&self) -> Option<UserThreadCallback> {
        unsafe { (*self.callbacks()).user_thread }
    }

    pub(crate) fn user_atom_callback(&self) -> Option<UserAtomCallback> {
        unsafe { (*self.callbacks()).user_atom }
    }

    pub(crate) fn debug_break_callback(&self) -> Option<LuaHook> {
        unsafe { (*self.callbacks()).debug_break }
    }

    pub(crate) fn debug_step_callback(&self) -> Option<LuaHook> {
        unsafe { (*self.callbacks()).debug_step }
    }

    pub(crate) fn debug_interrupt_callback(&self) -> Option<LuaDebugInterruptHook> {
        unsafe { (*self.callbacks()).debug_interrupt }
    }

    pub(crate) fn protected_error_callback(&self) -> Option<ProtectedErrorCallback> {
        unsafe { (*self.callbacks()).debug_protected_error }
    }

    pub(crate) fn take_execution_interrupt_callback(&self) -> Option<ExecutionInterrupt> {
        let callback = unsafe { (*self.callbacks()).execution_interrupt };
        callback.filter(|_| self.should_call_interrupt())
    }

    pub(crate) fn take_pattern_interrupt_callback(&self) -> Option<PatternInterrupt> {
        let callback = unsafe { (*self.callbacks()).pattern_interrupt };
        callback.filter(|_| self.should_call_interrupt())
    }

    pub(crate) fn take_gc_interrupt_callback(&self) -> Option<GcInterruptCallback> {
        let callback = unsafe { (*self.callbacks()).gc_interrupt };
        callback.filter(|_| self.should_call_interrupt())
    }

    fn should_call_interrupt(&self) -> bool {
        let request = unsafe { (*self.callbacks()).interrupt_request };
        request.is_null() || unsafe { (*request).take() }
    }

    pub(crate) fn execution_close(&self) -> Option<ExecutionClose> {
        unsafe { (*self.execution_callbacks()).close }
    }

    pub(crate) fn execution_destroy(&self) -> Option<ExecutionDestroy> {
        unsafe { (*self.execution_callbacks()).destroy }
    }

    pub(crate) fn execution_enter(&self) -> Option<ExecutionEnter> {
        unsafe { (*self.execution_callbacks()).enter }
    }

    pub(crate) fn execution_disable(&self) -> Option<ExecutionDisable> {
        unsafe { (*self.execution_callbacks()).disable }
    }

    pub(crate) fn execution_memory_size(&self) -> Option<ExecutionMemorySize> {
        unsafe { (*self.execution_callbacks()).get_memory_size }
    }

    pub(crate) fn execution_type_mapping(&self) -> Option<ExecutionTypeMapping> {
        unsafe { (*self.execution_callbacks()).get_type_mapping }
    }

    pub(crate) fn execution_counter_data(&self) -> Option<ExecutionCounterData> {
        unsafe { (*self.execution_callbacks()).get_counter_data }
    }

    pub(crate) fn execution_inline_function(&self) -> Option<ExecutionInlineFunction> {
        unsafe { (*self.execution_callbacks()).inline_function }
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ProtectedErrorAction {
    Continue,
    Break,
}