Skip to main content

luau_vm/state/
callbacks.rs

1use core::sync::atomic::{AtomicBool, Ordering};
2
3use luau_common::BStr;
4
5use crate::debug::{LuaDebugInterruptHook, LuaHook};
6use crate::function::{Closure, Proto};
7use crate::gc::{GCS_ATOMIC, GCS_PAUSE, GCS_PROPAGATE, GCS_PROPAGATE_AGAIN, GCS_SWEEP};
8use crate::handle::RawHandle;
9use crate::thread::Thread;
10use crate::{VmErrorResult, VmResult};
11
12use super::{GlobalState, LUA_EXECUTION_CALLBACK_STORAGE};
13
14#[derive(Clone, Copy, Debug, Eq, PartialEq)]
15pub enum InterruptKind {
16    Execution,
17    Pattern,
18    Gc(GcInterrupt),
19}
20
21/// Atomic request state for VM interrupt callbacks.
22#[derive(Debug, Default)]
23pub struct InterruptRequest {
24    pending: AtomicBool,
25}
26
27impl InterruptRequest {
28    /// Creates an idle interrupt request.
29    pub const fn new() -> Self {
30        Self {
31            pending: AtomicBool::new(false),
32        }
33    }
34
35    /// Requests delivery at the next interrupt point.
36    pub fn request(&self) {
37        self.pending.store(true, Ordering::Release);
38    }
39
40    /// Clears the pending interrupt request.
41    pub fn clear(&self) {
42        self.pending.store(false, Ordering::Relaxed);
43    }
44
45    fn take(&self) -> bool {
46        if !self.pending.load(Ordering::Relaxed) {
47            return false;
48        }
49        self.pending.swap(false, Ordering::Acquire)
50    }
51}
52
53#[derive(Clone, Copy, Debug, Eq, PartialEq)]
54pub enum GcPhase {
55    Pause,
56    Propagate,
57    PropagateAgain,
58    Atomic,
59    Sweep,
60}
61
62impl GcPhase {
63    pub(crate) fn from_state(state: u8) -> Self {
64        match state {
65            GCS_PAUSE => Self::Pause,
66            GCS_PROPAGATE => Self::Propagate,
67            GCS_PROPAGATE_AGAIN => Self::PropagateAgain,
68            GCS_ATOMIC => Self::Atomic,
69            GCS_SWEEP => Self::Sweep,
70            _ => unreachable!("unexpected gc state {state}"),
71        }
72    }
73}
74
75#[derive(Clone, Copy, Debug, Eq, PartialEq)]
76pub enum GcInterrupt {
77    BeforeStep,
78    AfterStep { previous_phase: GcPhase },
79}
80
81pub type ExecutionInterrupt = fn(&Thread) -> VmResult;
82pub type PatternInterrupt = fn(&Thread) -> VmErrorResult;
83pub type GcInterruptCallback = fn(&Thread, GcInterrupt) -> VmErrorResult;
84pub type UserThreadCallback = fn(Option<&Thread>, &Thread);
85pub type UserAtomCallback = fn(&Thread, &BStr) -> i16;
86pub type ProtectedErrorCallback = fn(&Thread) -> ProtectedErrorAction;
87pub type AllocateCallback = fn(&Thread, usize, usize);
88pub type EmbedderMark = fn(&Thread, i32);
89pub type EmbedderGc = fn(&Thread, Option<EmbedderMark>);
90pub type ExecutionClose = unsafe fn(&Thread);
91pub type ExecutionDestroy = unsafe fn(&Thread, Proto);
92pub type ExecutionEnter = unsafe fn(&Thread, Proto) -> i32;
93pub type ExecutionDisable = unsafe fn(&Thread, Proto);
94pub type ExecutionMemorySize = unsafe fn(&Thread, Proto) -> usize;
95pub type ExecutionTypeMapping = unsafe fn(&Thread, &BStr) -> u8;
96pub type ExecutionCounterData = unsafe fn(&Thread, Proto, *mut usize) -> *mut u8;
97pub type ExecutionInlineFunction = unsafe fn(&Thread, Closure, Closure, u32) -> Option<Proto>;
98
99#[derive(Clone, Copy, Default)]
100#[repr(C)]
101/// VM callback record installed by an embedder.
102///
103/// The VM owns the record storage. Embedders may update it only while the VM
104/// is quiescent; execution must never observe a partial update. The callback
105/// fields are not atomic and their raw address is not a cross-thread
106/// capability. When `interrupt_request` is non-null, it must remain valid while
107/// installed. The VM atomically consumes a request before delivering an
108/// interrupt.
109pub struct LuaCallbacks {
110    pub userdata: *mut (),
111    pub execution_interrupt: Option<ExecutionInterrupt>,
112    pub pattern_interrupt: Option<PatternInterrupt>,
113    pub gc_interrupt: Option<GcInterruptCallback>,
114    pub interrupt_request: *const InterruptRequest,
115    pub user_thread: Option<UserThreadCallback>,
116    pub user_atom: Option<UserAtomCallback>,
117    pub debug_break: Option<LuaHook>,
118    pub debug_step: Option<LuaHook>,
119    pub debug_interrupt: Option<LuaDebugInterruptHook>,
120    pub debug_protected_error: Option<ProtectedErrorCallback>,
121    pub on_allocate: Option<AllocateCallback>,
122}
123
124#[derive(Clone, Copy, Default)]
125#[repr(C)]
126/// Execution-engine callback record installed by an advanced embedder.
127///
128/// The record must be installed or updated as one coherent operation while
129/// the VM is quiescent. Its fields are plain, non-atomic storage and must not
130/// be mutated concurrently with VM execution.
131pub struct LuaExecutionCallbacks {
132    pub context: *mut (),
133    pub close: Option<ExecutionClose>,
134    pub destroy: Option<ExecutionDestroy>,
135    pub enter: Option<ExecutionEnter>,
136    pub disable: Option<ExecutionDisable>,
137    pub get_memory_size: Option<ExecutionMemorySize>,
138    pub get_type_mapping: Option<ExecutionTypeMapping>,
139    pub get_counter_data: Option<ExecutionCounterData>,
140    pub inline_function: Option<ExecutionInlineFunction>,
141}
142
143#[repr(C, align(16))]
144pub struct ExecutionCallbackStorage {
145    pub bytes: [u8; LUA_EXECUTION_CALLBACK_STORAGE],
146}
147
148impl GlobalState {
149    /// Returns the VM-owned public callback record address.
150    ///
151    /// The pointer is for quiescent installation and focused VM reads. It
152    /// does not establish a Rust borrow and must not be used to mutate the
153    /// record while the VM is executing.
154    pub fn callbacks(&self) -> *mut LuaCallbacks {
155        unsafe { &raw mut (*self.as_ptr()).cb }
156    }
157
158    /// Returns the VM-owned execution callback record address.
159    ///
160    /// The pointer is for quiescent installation and focused VM reads. It
161    /// does not establish a Rust borrow and must not be used to mutate the
162    /// record while the VM is executing.
163    pub fn execution_callbacks(&self) -> *mut LuaExecutionCallbacks {
164        unsafe { &raw mut (*self.as_ptr()).ecb }
165    }
166
167    pub(crate) fn user_thread_callback(&self) -> Option<UserThreadCallback> {
168        unsafe { (*self.callbacks()).user_thread }
169    }
170
171    pub(crate) fn user_atom_callback(&self) -> Option<UserAtomCallback> {
172        unsafe { (*self.callbacks()).user_atom }
173    }
174
175    pub(crate) fn debug_break_callback(&self) -> Option<LuaHook> {
176        unsafe { (*self.callbacks()).debug_break }
177    }
178
179    pub(crate) fn debug_step_callback(&self) -> Option<LuaHook> {
180        unsafe { (*self.callbacks()).debug_step }
181    }
182
183    pub(crate) fn debug_interrupt_callback(&self) -> Option<LuaDebugInterruptHook> {
184        unsafe { (*self.callbacks()).debug_interrupt }
185    }
186
187    pub(crate) fn protected_error_callback(&self) -> Option<ProtectedErrorCallback> {
188        unsafe { (*self.callbacks()).debug_protected_error }
189    }
190
191    pub(crate) fn take_execution_interrupt_callback(&self) -> Option<ExecutionInterrupt> {
192        let callback = unsafe { (*self.callbacks()).execution_interrupt };
193        callback.filter(|_| self.should_call_interrupt())
194    }
195
196    pub(crate) fn take_pattern_interrupt_callback(&self) -> Option<PatternInterrupt> {
197        let callback = unsafe { (*self.callbacks()).pattern_interrupt };
198        callback.filter(|_| self.should_call_interrupt())
199    }
200
201    pub(crate) fn take_gc_interrupt_callback(&self) -> Option<GcInterruptCallback> {
202        let callback = unsafe { (*self.callbacks()).gc_interrupt };
203        callback.filter(|_| self.should_call_interrupt())
204    }
205
206    fn should_call_interrupt(&self) -> bool {
207        let request = unsafe { (*self.callbacks()).interrupt_request };
208        request.is_null() || unsafe { (*request).take() }
209    }
210
211    pub(crate) fn execution_close(&self) -> Option<ExecutionClose> {
212        unsafe { (*self.execution_callbacks()).close }
213    }
214
215    pub(crate) fn execution_destroy(&self) -> Option<ExecutionDestroy> {
216        unsafe { (*self.execution_callbacks()).destroy }
217    }
218
219    pub(crate) fn execution_enter(&self) -> Option<ExecutionEnter> {
220        unsafe { (*self.execution_callbacks()).enter }
221    }
222
223    pub(crate) fn execution_disable(&self) -> Option<ExecutionDisable> {
224        unsafe { (*self.execution_callbacks()).disable }
225    }
226
227    pub(crate) fn execution_memory_size(&self) -> Option<ExecutionMemorySize> {
228        unsafe { (*self.execution_callbacks()).get_memory_size }
229    }
230
231    pub(crate) fn execution_type_mapping(&self) -> Option<ExecutionTypeMapping> {
232        unsafe { (*self.execution_callbacks()).get_type_mapping }
233    }
234
235    pub(crate) fn execution_counter_data(&self) -> Option<ExecutionCounterData> {
236        unsafe { (*self.execution_callbacks()).get_counter_data }
237    }
238
239    pub(crate) fn execution_inline_function(&self) -> Option<ExecutionInlineFunction> {
240        unsafe { (*self.execution_callbacks()).inline_function }
241    }
242}
243
244#[derive(Clone, Copy, Debug, Eq, PartialEq)]
245pub enum ProtectedErrorAction {
246    Continue,
247    Break,
248}