Skip to main content

luau_vm/thread/
mod.rs

1use core::ptr::NonNull;
2
3use crate::VmErrorResult;
4use crate::call::ThreadStack;
5use crate::function::FunctionRuntime;
6use crate::gc::{GcBarrier, GcRuntime};
7use crate::handle::RawHandle;
8use crate::state::{
9    BASIC_CI_SIZE, BASIC_STACK_SIZE, GlobalState, INITIAL_STACK_SIZE, LuaCallbacks, RawLuaState,
10    THREAD_STATUS_BREAK, THREAD_STATUS_OK, THREAD_STATUS_YIELD, ThreadLifecycle, ThreadState,
11};
12use crate::value::nil_object;
13
14#[allow(
15    clippy::missing_safety_doc,
16    reason = "Thread's shared unsafe auxiliary API contract is documented on Thread"
17)]
18mod auxiliary;
19#[allow(
20    clippy::missing_safety_doc,
21    reason = "Thread's shared unsafe call API contract is documented on Thread"
22)]
23mod call;
24#[allow(
25    clippy::missing_safety_doc,
26    reason = "Thread's shared unsafe debug API contract is documented on Thread"
27)]
28mod debug;
29#[allow(
30    clippy::missing_safety_doc,
31    reason = "Thread's shared unsafe GC API contract is documented on Thread"
32)]
33mod gc;
34#[allow(
35    clippy::missing_safety_doc,
36    reason = "Thread's shared unsafe stack API contract is documented on Thread"
37)]
38pub(crate) mod stack;
39#[allow(
40    clippy::missing_safety_doc,
41    reason = "Thread's shared unsafe string builder contract is documented on Thread"
42)]
43mod string_builder;
44#[allow(
45    clippy::missing_safety_doc,
46    reason = "Thread's shared unsafe table API contract is documented on Thread"
47)]
48mod table;
49#[allow(
50    clippy::missing_safety_doc,
51    reason = "Thread's shared unsafe userdata API contract is documented on Thread"
52)]
53mod userdata;
54
55pub use crate::types::{
56    LUA_TBOOLEAN, LUA_TBUFFER, LUA_TCLASS, LUA_TFUNCTION, LUA_TINTEGER, LUA_TLIGHTUSERDATA,
57    LUA_TNIL, LUA_TNUMBER, LUA_TOBJECT, LUA_TSTRING, LUA_TTABLE, LUA_TTHREAD, LUA_TUSERDATA,
58    LUA_TVECTOR,
59};
60pub use string_builder::{LuaStringBuilder, LuaStringBuilderStorage};
61
62#[derive(PartialEq, Eq)]
63#[repr(transparent)]
64/// A non-owning Luau thread handle.
65///
66/// `Thread` does not keep the VM alive and does not encode exclusive access
67/// to the VM stack. It is deliberately `!Send` and `!Sync` through its raw
68/// state representation.
69///
70/// # Safety model for unsafe methods
71///
72/// Unless a method documents stricter requirements, every unsafe `Thread`
73/// operation requires a live thread belonging to a live VM. Stack indices,
74/// stack shapes, counts, tags, and pointer arguments must satisfy the
75/// corresponding Luau API operation's contract. The caller must provide
76/// sufficient stack capacity where the operation does not grow the stack,
77/// prevent reentrant or concurrent access to VM-owned records, and treat any
78/// operation that can allocate or execute code as a possible GC transition.
79/// Returned pointers and raw handles are non-owning and may be invalidated by
80/// stack relocation, collection, closure, reset, or VM destruction.
81pub struct Thread {
82    pub(crate) raw: NonNull<RawLuaState>,
83}
84
85impl crate::handle::sealed::Sealed for Thread {}
86
87impl RawHandle for Thread {
88    type Raw = RawLuaState;
89
90    fn as_ptr(&self) -> *mut RawLuaState {
91        self.raw.as_ptr()
92    }
93}
94
95impl AsRef<Thread> for Thread {
96    fn as_ref(&self) -> &Thread {
97        self
98    }
99}
100
101impl Thread {
102    /// Returns a non-owning handle to this thread's global VM state.
103    ///
104    /// # Safety
105    ///
106    /// This thread and its owning VM must still be live. The returned handle
107    /// does not extend the global state's lifetime or establish a borrow of
108    /// the underlying record.
109    pub unsafe fn global(&self) -> GlobalState {
110        unsafe {
111            GlobalState::from_raw(NonNull::new_unchecked(
112                self.as_ptr().as_ref().unwrap_unchecked().global,
113            ))
114        }
115    }
116}
117
118/// Restores a thread's stack height when the guard is dropped.
119///
120/// Values intentionally left on the stack can be accounted for with
121/// [`keep`](Self::keep), or restoration can be disabled with
122/// [`into_top`](Self::into_top) or [`dismiss`](Self::dismiss).
123#[must_use = "dropping the guard restores the captured stack height"]
124pub struct StackGuard<'thread> {
125    thread: &'thread Thread,
126    top: i32,
127    restore_on_drop: bool,
128}
129
130impl<'thread> StackGuard<'thread> {
131    /// Captures the thread's current stack height.
132    ///
133    /// # Safety
134    ///
135    /// `thread` must remain a live VM thread for the guard's lifetime. While
136    /// the guard is active, callers must not shrink the stack below the
137    /// captured height or otherwise invalidate that saved stack position.
138    pub unsafe fn new(thread: &'thread Thread) -> Self {
139        Self {
140            thread,
141            top: unsafe { thread.get_top() },
142            restore_on_drop: true,
143        }
144    }
145
146    pub const fn top(&self) -> i32 {
147        self.top
148    }
149
150    pub fn keep(&mut self, count: i32) {
151        debug_assert!(count >= 0);
152        self.top += count;
153    }
154
155    pub fn into_top(mut self) -> i32 {
156        self.restore_on_drop = false;
157        self.top
158    }
159
160    pub fn dismiss(mut self) {
161        self.restore_on_drop = false;
162    }
163}
164
165impl Drop for StackGuard<'_> {
166    fn drop(&mut self) {
167        if self.restore_on_drop {
168            unsafe { self.thread.restore_top(self.top) };
169        }
170    }
171}
172
173/// `LUA_MINSTACK`
174pub const LUA_MIN_STACK: usize = 20;
175
176/// `LUAI_MAXCSTACK`
177pub const LUAI_MAX_C_STACK: i32 = 8000;
178
179/// `LUAI_MAXCALLS`
180pub const LUAI_MAX_CALLS: usize = 20_000;
181
182/// `LUAI_MAXCCALLS`
183pub const LUAI_MAX_NATIVE_CALLS: u16 = 200;
184
185/// `LUA_BUFFERSIZE`
186pub const LUA_BUFFER_SIZE: usize = 512;
187
188pub const LUA_MULTRET: i32 = -1;
189pub const LUA_TNONE: i32 = -1;
190
191pub const LUA_REGISTRY_INDEX: i32 = -LUAI_MAX_C_STACK - 2000;
192pub const LUA_ENVIRON_INDEX: i32 = -LUAI_MAX_C_STACK - 2001;
193pub const LUA_GLOBALS_INDEX: i32 = -LUAI_MAX_C_STACK - 2002;
194
195pub const LUA_NOREF: i32 = -1;
196pub const LUA_REFNIL: i32 = 0;
197
198pub const LUA_CORUN: i32 = 0;
199pub const LUA_COSUS: i32 = 1;
200pub const LUA_CONOR: i32 = 2;
201pub const LUA_COFIN: i32 = 3;
202pub const LUA_COERR: i32 = 4;
203
204/// `lua_upvalueindex`
205pub const fn upvalue_index(index: i32) -> i32 {
206    LUA_GLOBALS_INDEX - index
207}
208
209/// `lua_ispseudo`
210pub const fn is_pseudo(index: i32) -> bool {
211    index <= LUA_REGISTRY_INDEX
212}
213
214// Thread lifecycle and host state
215#[allow(
216    clippy::missing_safety_doc,
217    reason = "Thread's shared unsafe lifecycle API contract is documented on Thread"
218)]
219impl Thread {
220    /// Returns whether both handles belong to the same VM.
221    ///
222    /// # Safety
223    ///
224    /// Both threads and their owning VMs must still be live.
225    pub unsafe fn same_vm(&self, other: impl AsRef<Thread>) -> bool {
226        unsafe { self.global() == other.as_ref().global() }
227    }
228
229    /// `lua_encodepointer`
230    pub unsafe fn encode_pointer(&self, pointer: usize) -> usize {
231        unsafe { self.global().encode_pointer(pointer) }
232    }
233
234    /// `lua_setpointerencodekey`
235    pub unsafe fn set_pointer_encode_key(&self, a: u64, b: u64, c: u64, d: u64) {
236        unsafe {
237            self.global()
238                .as_ptr()
239                .as_mut()
240                .unwrap_unchecked()
241                .ptr_enc_key = [a & !1, b | 1, c, d];
242        }
243    }
244
245    /// `lua_resetthread`
246    pub unsafe fn reset(&self) -> VmErrorResult {
247        unsafe {
248            debug_assert!(!self.as_ptr().as_ref().unwrap_unchecked().is_active);
249            debug_assert!(
250                self.as_ptr().as_ref().unwrap_unchecked().status != THREAD_STATUS_OK
251                    || self.as_ptr().as_ref().unwrap_unchecked().ci
252                        == self.as_ptr().as_ref().unwrap_unchecked().base_ci
253            );
254
255            self.close(self.base_call_info().function().value_unchecked());
256
257            let function = self.stack();
258            let ci = self.base_call_info();
259            ci.function().value_unchecked().set_nil();
260            ci.init_call(function, function.add(1 + LUA_MIN_STACK), 0, None);
261
262            let raw = self.as_ptr().as_mut().unwrap_unchecked();
263            raw.status = THREAD_STATUS_OK;
264            raw.native_call_depth = 0;
265            raw.base_native_call_depth = 0;
266            self.set_current_call_info(self.base_call_info_cursor());
267            self.set_stack_base(ci.base());
268            self.set_stack_top(ci.base());
269
270            if self.as_ptr().as_ref().unwrap_unchecked().size_ci as usize != BASIC_CI_SIZE {
271                self.realloc_ci(BASIC_CI_SIZE as i32)?;
272            }
273
274            let target_stack_size = INITIAL_STACK_SIZE as i32;
275            if self.as_ptr().as_ref().unwrap_unchecked().stack_size != target_stack_size {
276                self.realloc_stack(BASIC_STACK_SIZE as i32, false)?;
277            }
278
279            let stack = self.stack();
280            let stack_size = self.as_ptr().as_ref().unwrap_unchecked().stack_size as usize;
281            for index in 0..stack_size {
282                stack.add(index).value_unchecked().set_nil();
283            }
284        }
285        Ok(())
286    }
287
288    /// `lua_isthreadreset`
289    pub unsafe fn is_reset(&self) -> bool {
290        unsafe {
291            self.current_call_info() == self.base_call_info()
292                && self.as_ptr().as_ref().unwrap_unchecked().base
293                    == self.as_ptr().as_ref().unwrap_unchecked().top
294                && self.as_ptr().as_ref().unwrap_unchecked().status == THREAD_STATUS_OK
295        }
296    }
297
298    /// `lua_status`
299    pub unsafe fn status(&self) -> i32 {
300        unsafe { self.as_ptr().as_ref().unwrap_unchecked().status as i32 }
301    }
302
303    /// `lua_isyieldable`
304    pub unsafe fn is_yieldable(&self) -> i32 {
305        i32::from(unsafe {
306            self.as_ptr().as_ref().unwrap_unchecked().native_call_depth
307                <= self
308                    .as_ptr()
309                    .as_ref()
310                    .unwrap_unchecked()
311                    .base_native_call_depth
312        })
313    }
314
315    /// `lua_getthreaddata`
316    pub unsafe fn thread_data(&self) -> *mut () {
317        unsafe { self.as_ptr().as_ref().unwrap_unchecked().userdata }
318    }
319
320    /// `lua_setthreaddata`
321    pub unsafe fn set_thread_data(&self, data: *mut ()) {
322        unsafe {
323            self.as_ptr().as_mut().unwrap_unchecked().userdata = data;
324        }
325    }
326
327    /// `lua_callbacks`
328    ///
329    /// Returns the VM-owned callback record address. Mutating this record is
330    /// only valid while the VM is quiescent, and the update must not be
331    /// observable in a partially written state. The record is non-atomic and
332    /// the pointer is not a cross-thread interruption mechanism.
333    ///
334    /// # Safety
335    ///
336    /// The thread and its VM must remain live. The caller must not dereference
337    /// a stale pointer or read or write the record concurrently with VM
338    /// execution.
339    pub unsafe fn callbacks(&self) -> *mut LuaCallbacks {
340        unsafe { self.global().callbacks() }
341    }
342}
343
344// Thread values
345#[allow(
346    clippy::missing_safety_doc,
347    reason = "Thread's shared unsafe thread-value API contract is documented on Thread"
348)]
349impl Thread {
350    /// `lua_pushthread`
351    pub unsafe fn push_thread(&self) -> VmErrorResult<i32> {
352        unsafe {
353            self.thread_barrier();
354            self.ensure_stack(self, 1)?;
355
356            let top = self.stack_top();
357            top.value_unchecked().set_thread_value(self);
358            debug_assert!(top < self.current_call_info().top());
359            self.set_stack_top(top.add(1));
360        }
361
362        Ok(i32::from(unsafe { self.global().main_thread() == *self }))
363    }
364
365    /// `lua_tothread`
366    pub unsafe fn to_thread(&self, index: i32) -> Option<Thread> {
367        let object = unsafe { self.index_to_addr(index) };
368        if object == nil_object() || !object.is_thread() {
369            None
370        } else {
371            Some(object.thread_value())
372        }
373    }
374
375    /// `lua_newthread`
376    pub unsafe fn new_thread(&self) -> VmErrorResult<Thread> {
377        unsafe {
378            self.check_gc()?;
379            self.thread_barrier();
380            self.ensure_stack(self, 1)?;
381            let thread = self.new_thread_internal()?;
382
383            let top = self.stack_top();
384            top.value_unchecked().set_thread_value(&thread);
385            debug_assert!(top < self.current_call_info().top());
386            self.set_stack_top(top.add(1));
387
388            if let Some(user_thread) = self.global().user_thread_callback() {
389                user_thread(Some(self), &thread);
390            }
391
392            Ok(thread)
393        }
394    }
395
396    /// `lua_costatus`
397    pub unsafe fn co_status(&self, thread: &Thread) -> i32 {
398        unsafe {
399            debug_assert!(self.global() == thread.global());
400
401            if *thread == *self {
402                LUA_CORUN
403            } else if thread.as_ptr().as_ref().unwrap_unchecked().status == THREAD_STATUS_YIELD {
404                LUA_COSUS
405            } else if thread.as_ptr().as_ref().unwrap_unchecked().status == THREAD_STATUS_BREAK {
406                LUA_CONOR
407            } else if thread.as_ptr().as_ref().unwrap_unchecked().status != THREAD_STATUS_OK {
408                LUA_COERR
409            } else if thread.current_call_info() != thread.base_call_info() {
410                LUA_CONOR
411            } else if thread.stack_top() == thread.stack_base() {
412                LUA_COFIN
413            } else {
414                LUA_COSUS
415            }
416        }
417    }
418}