Skip to main content

luau_vm/call/
mod.rs

1mod coroutine;
2mod protected;
3mod stack;
4
5pub(crate) use coroutine::{resume as resume_protected, resume_handle};
6pub use protected::{ErrorRuntime, LuaProtectedErrorFrame, Pfunc, ProtectedCall};
7pub use stack::ThreadStack;
8
9use crate::gc::{GcBarrier, GcRuntime};
10use crate::handle::RawHandle;
11use crate::handle::sealed::Sealed;
12use crate::state::ThreadState;
13use crate::state::{THREAD_STATUS_BREAK, THREAD_STATUS_SCHEDULED_REENTRY, THREAD_STATUS_YIELD};
14use crate::thread::{LUA_MULTRET, Thread};
15use crate::value::TValueCursor;
16use crate::vm::{PreCallResult, VmCallFrame, VmExecution};
17use crate::{VmControl, VmExit, VmResult};
18
19/// Unstable callable-invocation capability.
20///
21/// # Safety
22///
23/// Function cursors must be live positions in this thread's current stack,
24/// the argument/result stack shape must match the requested call, and callers
25/// must account for execution, yielding, stack relocation, and collection.
26#[allow(
27    clippy::missing_safety_doc,
28    reason = "all methods share the capability-level safety contract"
29)]
30pub trait CallRuntime: Sealed {
31    unsafe fn perform_cally(&self, function: TValueCursor, n_results: i32) -> VmResult;
32    unsafe fn call_int(
33        &self,
34        function: TValueCursor,
35        n_results: i32,
36        prepare_reentry: bool,
37    ) -> VmResult;
38    unsafe fn call_internal(&self, function: TValueCursor, n_results: i32) -> VmResult;
39    unsafe fn call_no_yield(&self, function: TValueCursor, n_results: i32) -> VmResult;
40}
41
42/// `performcall`
43unsafe fn perform_call(
44    thread: &Thread,
45    function: TValueCursor,
46    n_results: i32,
47    prepare_reentry: bool,
48) -> VmResult {
49    if unsafe { thread.pre_call(function, n_results)? } == PreCallResult::Lua {
50        unsafe {
51            let call_info = thread.current_call_info();
52            call_info.as_ptr().as_mut().unwrap_unchecked().flags |=
53                crate::state::LUA_CALLINFO_RETURN;
54
55            let old_active = thread.as_ptr().as_ref().unwrap_unchecked().is_active;
56            thread.as_ptr().as_mut().unwrap_unchecked().is_active = true;
57            thread.thread_barrier();
58
59            if prepare_reentry {
60                thread.as_ptr().as_mut().unwrap_unchecked().status =
61                    THREAD_STATUS_SCHEDULED_REENTRY;
62                if !old_active {
63                    thread.as_ptr().as_mut().unwrap_unchecked().is_active = false;
64                }
65                Err(VmExit::Control(VmControl::Yield))
66            } else {
67                let result = thread.execute();
68                if !old_active {
69                    thread.as_ptr().as_mut().unwrap_unchecked().is_active = false;
70                }
71                result
72            }
73        }
74    } else {
75        Ok(())
76    }
77}
78
79impl CallRuntime for Thread {
80    /// `luaD_performcally`
81    unsafe fn perform_cally(&self, function: TValueCursor, n_results: i32) -> VmResult {
82        unsafe {
83            self.increment_native_call_depth();
84            if self.as_ptr().as_ref().unwrap_unchecked().native_call_depth
85                >= crate::thread::LUAI_MAX_NATIVE_CALLS
86            {
87                self.check_c_stack()?;
88            }
89            self.increment_base_native_call_depth();
90            let ci_offset = self.save_ci(self.current_call_info_cursor());
91
92            match perform_call(self, function, n_results, false) {
93                Ok(()) => {}
94                Err(VmExit::Control(control)) => {
95                    let caller = self.restore_ci(ci_offset).call_info_unchecked();
96
97                    caller.as_ptr().as_mut().unwrap_unchecked().flags |=
98                        crate::state::LUA_CALLINFO_OP_YIELD;
99
100                    return Err(VmExit::Control(control));
101                }
102                Err(exit) => return Err(exit),
103            }
104
105            self.decrement_base_native_call_depth();
106            self.decrement_native_call_depth();
107            self.check_gc()?;
108            Ok(())
109        }
110    }
111
112    /// `luaD_callint`
113    unsafe fn call_int(
114        &self,
115        function: TValueCursor,
116        n_results: i32,
117        prepare_reentry: bool,
118    ) -> VmResult {
119        let (from_yieldable_native_call, function_offset, ci_offset) = unsafe {
120            self.increment_native_call_depth();
121            if self.as_ptr().as_ref().unwrap_unchecked().native_call_depth
122                >= crate::thread::LUAI_MAX_NATIVE_CALLS
123            {
124                self.check_c_stack()?;
125            }
126
127            let mut from_yieldable_native_call = false;
128            if self.current_call_info() != self.base_call_info() {
129                let current = self.current_call_info().function_closure();
130                if current.is_native() && current.native_data().continuation.is_some() {
131                    from_yieldable_native_call = true;
132                    self.increment_base_native_call_depth();
133                }
134            }
135
136            let function_offset = self.save_stack(function);
137            let ci_offset = self.save_ci(self.current_call_info_cursor());
138            (from_yieldable_native_call, function_offset, ci_offset)
139        };
140
141        unsafe {
142            let result = perform_call(self, function, n_results, prepare_reentry);
143            let control = match result {
144                Ok(()) => None,
145                Err(VmExit::Control(control)) => Some(control),
146                Err(exit) => return Err(exit),
147            };
148            let suspended = control.is_some();
149
150            if from_yieldable_native_call {
151                self.decrement_base_native_call_depth();
152
153                if suspended {
154                    let caller = self.restore_ci(ci_offset).call_info_unchecked();
155                    let caller_top =
156                        self.restore_stack(function_offset)
157                            .add(if n_results != LUA_MULTRET {
158                                n_results as usize
159                            } else {
160                                0
161                            });
162
163                    caller.set_top(caller_top);
164                }
165            }
166
167            if n_results != LUA_MULTRET && !suspended {
168                self.set_stack_top(self.restore_stack(function_offset).add(n_results as usize))
169            }
170
171            self.decrement_native_call_depth();
172            self.check_gc()?;
173
174            if let Some(control) = control {
175                return Err(VmExit::Control(control));
176            }
177        }
178        Ok(())
179    }
180
181    /// `luaD_call`
182    unsafe fn call_internal(&self, function: TValueCursor, n_results: i32) -> VmResult {
183        unsafe { self.call_int(function, n_results, false) }
184    }
185
186    /// `luaD_callny`
187    unsafe fn call_no_yield(&self, function: TValueCursor, n_results: i32) -> VmResult {
188        unsafe {
189            self.increment_native_call_depth();
190            if self.as_ptr().as_ref().unwrap_unchecked().native_call_depth
191                >= crate::thread::LUAI_MAX_NATIVE_CALLS
192            {
193                self.check_c_stack()?;
194            }
195            debug_assert!(
196                self.as_ptr().as_ref().unwrap_unchecked().native_call_depth
197                    > self
198                        .as_ptr()
199                        .as_ref()
200                        .unwrap_unchecked()
201                        .base_native_call_depth
202            );
203            let function_offset = self.save_stack(function);
204            perform_call(self, function, n_results, false)?;
205
206            debug_assert!(!matches!(
207                self.as_ptr().as_ref().unwrap_unchecked().status,
208                THREAD_STATUS_YIELD | THREAD_STATUS_BREAK | THREAD_STATUS_SCHEDULED_REENTRY
209            ));
210            if n_results != LUA_MULTRET {
211                self.set_stack_top(self.restore_stack(function_offset).add(n_results as usize))
212            }
213
214            self.decrement_native_call_depth();
215            self.check_gc()?;
216        }
217        Ok(())
218    }
219}