Skip to main content

luau_vm/call/
stack.rs

1use crate::debug::DebugRuntime;
2use crate::handle::RawHandle;
3use crate::handle::sealed::Sealed;
4use crate::memory::MemoryRuntime;
5use crate::state::ThreadState;
6use crate::state::{CallInfoCursor, RawCallInfo};
7use crate::thread::{LUAI_MAX_CALLS, LUAI_MAX_NATIVE_CALLS, Thread};
8use crate::value::{RawTValue, TValueCursor};
9use crate::{VmError, VmErrorResult};
10
11pub(super) const MAX_STACK_SIZE: i32 =
12    ((1024 / core::mem::size_of::<RawTValue>()) * 1024 * 1024) as i32;
13pub(super) const MAX_CALLS_HARD: usize = LUAI_MAX_CALLS + (LUAI_MAX_CALLS >> 3);
14
15/// Unstable stack and call-frame relocation capability.
16///
17/// # Safety
18///
19/// All pointers and cursors must belong to this thread's current stack or
20/// call-info arrays. Saved offsets must originate from the matching array.
21/// Growth and reallocation invalidate outstanding cursors and record views.
22#[allow(
23    clippy::missing_safety_doc,
24    reason = "all methods share the capability-level safety contract"
25)]
26pub trait ThreadStack: Sealed {
27    unsafe fn check_stack_internal(&self, needed: i32) -> VmErrorResult;
28    unsafe fn check_stack_for_new_ci(&self, needed: i32) -> VmErrorResult;
29    unsafe fn get_grown_stack_size(&self, needed: i32) -> i32;
30    unsafe fn stack_limit_reached(&self, needed: i32) -> bool;
31    unsafe fn save_stack(&self, pointer: TValueCursor) -> isize;
32    unsafe fn restore_stack(&self, offset: isize) -> TValueCursor;
33    unsafe fn save_ci(&self, pointer: CallInfoCursor) -> isize;
34    unsafe fn restore_ci(&self, offset: isize) -> CallInfoCursor;
35    unsafe fn expand_stack_limit(&self, pointer: TValueCursor);
36    unsafe fn realloc_stack(&self, new_size: i32, for_new_ci: bool) -> VmErrorResult;
37    unsafe fn realloc_ci(&self, new_size: i32) -> VmErrorResult;
38    unsafe fn grow_stack(&self, needed: i32) -> VmErrorResult;
39    unsafe fn grow_ci(&self) -> VmErrorResult<CallInfoCursor>;
40    unsafe fn incr_ci(&self) -> VmErrorResult<CallInfoCursor>;
41    unsafe fn check_c_stack(&self) -> VmErrorResult;
42}
43
44impl Thread {
45    /// `correctstack`
46    unsafe fn correct_stack(&self, old_stack: TValueCursor) {
47        unsafe {
48            let new_stack_cursor = self.stack();
49            let top_offset = self.stack_top().addr_offset_from(old_stack) as usize;
50            self.set_stack_top(new_stack_cursor.add(top_offset));
51
52            let mut upvalue = self.open_upvalue();
53            while let Some(current_upvalue) = upvalue {
54                current_upvalue.rebase_value(old_stack, new_stack_cursor);
55                upvalue = current_upvalue.open_data().thread_next();
56            }
57
58            let mut call_info_cursor = self.base_call_info_cursor();
59            let current_call_info_cursor = self.current_call_info_cursor();
60            while call_info_cursor <= current_call_info_cursor {
61                call_info_cursor
62                    .call_info_unchecked()
63                    .rebase_stack(old_stack, new_stack_cursor);
64                call_info_cursor = call_info_cursor.add(1);
65            }
66
67            let base_offset = self.stack_base().addr_offset_from(old_stack) as usize;
68            self.set_stack_base(new_stack_cursor.add(base_offset));
69        }
70    }
71}
72
73impl ThreadStack for Thread {
74    /// `luaD_checkstack`
75    unsafe fn check_stack_internal(&self, needed: i32) -> VmErrorResult {
76        if unsafe { self.stack_limit_reached(needed) } {
77            unsafe { self.grow_stack(needed)? };
78        }
79        Ok(())
80    }
81
82    /// `luaD_checkstackfornewci`
83    unsafe fn check_stack_for_new_ci(&self, needed: i32) -> VmErrorResult {
84        if unsafe { self.stack_limit_reached(needed) } {
85            unsafe { self.realloc_stack(self.get_grown_stack_size(needed), true)? };
86        }
87        Ok(())
88    }
89
90    /// `getgrownstacksize`
91    unsafe fn get_grown_stack_size(&self, needed: i32) -> i32 {
92        let stack_size = unsafe { self.as_ptr().as_ref().unwrap_unchecked().stack_size };
93        if needed <= stack_size {
94            2 * stack_size
95        } else {
96            stack_size + needed
97        }
98    }
99
100    /// `stacklimitreached`
101    unsafe fn stack_limit_reached(&self, needed: i32) -> bool {
102        let thread = unsafe { self.as_ptr().as_ref().unwrap_unchecked() };
103        let available_bytes = (thread.stack_last as usize).wrapping_sub(thread.top as usize);
104        available_bytes <= needed as usize * core::mem::size_of::<RawTValue>()
105    }
106
107    /// `savestack`
108    unsafe fn save_stack(&self, pointer: TValueCursor) -> isize {
109        let stack = unsafe { self.stack() };
110        unsafe {
111            pointer
112                .as_ptr()
113                .cast::<u8>()
114                .offset_from(stack.as_ptr().cast::<u8>())
115        }
116    }
117
118    /// `restorestack`
119    unsafe fn restore_stack(&self, offset: isize) -> TValueCursor {
120        unsafe { TValueCursor::from_ptr(self.stack().as_ptr().cast::<u8>().offset(offset).cast()) }
121    }
122
123    /// `saveci`
124    unsafe fn save_ci(&self, pointer: CallInfoCursor) -> isize {
125        let base_ci = unsafe { self.as_ptr().as_ref().unwrap_unchecked().base_ci };
126        unsafe {
127            pointer
128                .as_ptr()
129                .cast::<u8>()
130                .offset_from(base_ci.cast::<u8>())
131        }
132    }
133
134    /// `restoreci`
135    unsafe fn restore_ci(&self, offset: isize) -> CallInfoCursor {
136        unsafe {
137            CallInfoCursor::from_ptr(
138                self.as_ptr()
139                    .as_ref()
140                    .unwrap_unchecked()
141                    .base_ci
142                    .cast::<u8>()
143                    .offset(offset)
144                    .cast::<RawCallInfo>(),
145            )
146        }
147    }
148
149    /// `expandstacklimit`
150    unsafe fn expand_stack_limit(&self, pointer: TValueCursor) {
151        debug_assert!(pointer <= unsafe { self.stack_last() });
152
153        unsafe {
154            let current_call_info = self.current_call_info();
155            if current_call_info.top() < pointer {
156                current_call_info.set_top(pointer);
157            }
158        }
159    }
160
161    /// `luaD_reallocstack`
162    unsafe fn realloc_stack(&self, new_size: i32, for_new_ci: bool) -> VmErrorResult {
163        unsafe {
164            if new_size > MAX_STACK_SIZE {
165                if for_new_ci {
166                    let parent_cursor = self.current_call_info_cursor().sub(1);
167                    self.restore_call_frame(
168                        parent_cursor,
169                        parent_cursor.call_info_unchecked().top(),
170                    );
171                }
172
173                return Err(VmError::Memory);
174            }
175
176            let old_stack = self.stack();
177            let real_size = new_size + crate::state::EXTRA_STACK as i32;
178            debug_assert!(
179                self.as_ptr()
180                    .as_ref()
181                    .unwrap_unchecked()
182                    .stack_last
183                    .offset_from(self.as_ptr().as_ref().unwrap_unchecked().stack)
184                    == self.as_ptr().as_ref().unwrap_unchecked().stack_size as isize
185                        - crate::state::EXTRA_STACK as isize
186            );
187
188            let old_stack_size = self.as_ptr().as_ref().unwrap_unchecked().stack_size as usize;
189            let new_stack_size = real_size as usize;
190            let memcat = self.as_ptr().as_ref().unwrap_unchecked().active_memcat;
191            let new_stack = TValueCursor::from_ptr(self.realloc_array(
192                old_stack.as_ptr(),
193                old_stack_size,
194                new_stack_size,
195                memcat,
196            )?);
197
198            self.set_stack(new_stack);
199
200            for index in old_stack_size..new_stack_size {
201                new_stack.add(index).value_unchecked().set_nil();
202            }
203
204            let raw = self.as_ptr().as_mut().unwrap_unchecked();
205            raw.stack_size = real_size;
206            raw.stack_last = new_stack.add(new_size as usize).as_ptr();
207            self.correct_stack(old_stack);
208        }
209        Ok(())
210    }
211
212    /// `luaD_reallocCI`
213    unsafe fn realloc_ci(&self, new_size: i32) -> VmErrorResult {
214        unsafe {
215            let old_ci = self.as_ptr().as_ref().unwrap_unchecked().base_ci;
216            let old_size = self.as_ptr().as_ref().unwrap_unchecked().size_ci as usize;
217            let new_size_usize = new_size as usize;
218            let memcat = self.as_ptr().as_ref().unwrap_unchecked().active_memcat;
219            let new_ci = self.realloc_array(old_ci, old_size, new_size_usize, memcat)?;
220            let current_offset =
221                self.current_call_info_cursor()
222                    .offset_from(self.base_call_info_cursor()) as usize;
223
224            let raw = self.as_ptr().as_mut().unwrap_unchecked();
225            raw.base_ci = new_ci;
226            raw.size_ci = new_size;
227            raw.end_ci = new_ci.add(new_size_usize - 1);
228            self.set_current_call_info(self.base_call_info_cursor().add(current_offset));
229        }
230        Ok(())
231    }
232
233    /// `luaD_growstack`
234    unsafe fn grow_stack(&self, needed: i32) -> VmErrorResult {
235        unsafe { self.realloc_stack(self.get_grown_stack_size(needed), false) }
236    }
237
238    /// `luaD_growCI`
239    unsafe fn grow_ci(&self) -> VmErrorResult<CallInfoCursor> {
240        unsafe {
241            if self.as_ptr().as_ref().unwrap_unchecked().size_ci as usize >= MAX_CALLS_HARD {
242                return Err(VmError::ErrorHandler);
243            }
244
245            let current_size = self.as_ptr().as_ref().unwrap_unchecked().size_ci;
246            let request = current_size * 2;
247            let new_size = if current_size as usize >= LUAI_MAX_CALLS {
248                MAX_CALLS_HARD as i32
249            } else if request as usize >= LUAI_MAX_CALLS {
250                LUAI_MAX_CALLS as i32
251            } else {
252                request
253            };
254
255            self.realloc_ci(new_size)?;
256
257            if self.as_ptr().as_ref().unwrap_unchecked().size_ci as usize > LUAI_MAX_CALLS {
258                return crate::run_error!(self, "stack overflow");
259            }
260
261            let call_info = self.current_call_info_cursor().add(1);
262            self.set_current_call_info(call_info);
263            Ok(call_info)
264        }
265    }
266
267    /// `incr_ci`
268    unsafe fn incr_ci(&self) -> VmErrorResult<CallInfoCursor> {
269        unsafe {
270            if self.current_call_info_cursor()
271                == self
272                    .base_call_info_cursor()
273                    .add(self.as_ptr().as_ref().unwrap_unchecked().size_ci as usize - 1)
274            {
275                self.grow_ci()
276            } else {
277                let call_info = self.current_call_info_cursor().add(1);
278                self.set_current_call_info(call_info);
279                Ok(call_info)
280            }
281        }
282    }
283
284    /// `luaD_checkCstack`
285    unsafe fn check_c_stack(&self) -> VmErrorResult {
286        unsafe {
287            let native_call_depth = self.as_ptr().as_ref().unwrap_unchecked().native_call_depth;
288            if native_call_depth == LUAI_MAX_NATIVE_CALLS {
289                return crate::run_error!(self, "C stack overflow");
290            } else if native_call_depth >= crate::state::MAX_NATIVE_CALLS_HARD {
291                return Err(VmError::ErrorHandler);
292            }
293        }
294        Ok(())
295    }
296}