Skip to main content

luau_vm/vm/
entry.rs

1use crate::VmResult;
2use crate::call::ThreadStack;
3use crate::debug::DebugRuntime;
4use crate::debug::LuaDebug;
5use crate::handle::RawHandle;
6use crate::handle::sealed::Sealed;
7use crate::metamethod::{MetamethodRuntime, TmEvent};
8use crate::native::NativeCallContext;
9use crate::state::ThreadState;
10use crate::state::{THREAD_STATUS_BREAK, THREAD_STATUS_OK, THREAD_STATUS_YIELD};
11use crate::thread::{LUA_MULTRET, Thread};
12use crate::value::TValueCursor;
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub enum PreCallResult {
16    Lua,
17    Native,
18}
19
20/// Unstable VM call-frame entry and exit capability.
21///
22/// # Safety
23///
24/// Function cursors and call frames must be live positions in this thread,
25/// with the required argument/result stack shape. Execution may relocate the
26/// stack, collect objects, invoke callbacks, yield, or perform VM control flow.
27#[allow(
28    clippy::missing_safety_doc,
29    reason = "all methods share the capability-level safety contract"
30)]
31pub trait VmCallFrame: Sealed {
32    /// `luaV_tryfuncTM`
33    unsafe fn try_func_tm(&self, function: TValueCursor) -> VmResult;
34
35    /// `luau_precall`
36    unsafe fn pre_call(&self, function: TValueCursor, n_results: i32) -> VmResult<PreCallResult>;
37
38    /// `luau_poscall`
39    unsafe fn pos_call(&self, first: TValueCursor);
40
41    /// `luau_callhook`
42    unsafe fn call_hook<F>(&self, hook: F, userdata: *mut ()) -> VmResult
43    where
44        F: FnOnce(&Thread, &mut LuaDebug) -> VmResult;
45}
46
47impl VmCallFrame for Thread {
48    /// `luaV_tryfuncTM`
49    unsafe fn try_func_tm(&self, function: TValueCursor) -> VmResult {
50        unsafe {
51            let function_value = function.value_unchecked();
52            let Some(tm) = self.get_tm_by_obj(function_value, TmEvent::Call) else {
53                return self.type_error(function_value, "call").map_err(Into::into);
54            };
55
56            if !tm.is_function() {
57                return self.type_error(function_value, "call").map_err(Into::into);
58            }
59
60            let top = self.stack_top();
61            let mut slot = top;
62
63            while slot > function {
64                slot.value_unchecked()
65                    .set_obj(slot.sub(1).value_unchecked());
66                slot = slot.sub(1);
67            }
68
69            self.set_stack_top(top.add(1));
70            function.value_unchecked().set_obj(tm);
71        }
72        Ok(())
73    }
74
75    /// `luau_precall`
76    unsafe fn pre_call(&self, function: TValueCursor, n_results: i32) -> VmResult<PreCallResult> {
77        unsafe {
78            if !function.value_unchecked().is_function() {
79                self.try_func_tm(function)?;
80            }
81
82            let closure = function.value_unchecked().closure_value();
83            let call_info = self.incr_ci()?.call_info_unchecked();
84
85            call_info.init_call(
86                function,
87                self.stack_top()
88                    .add(closure.as_ptr().as_ref().unwrap_unchecked().stack_size as usize),
89                n_results,
90                closure.proto(),
91            );
92            self.set_stack_base(call_info.base());
93
94            self.check_stack_for_new_ci(
95                closure.as_ptr().as_ref().unwrap_unchecked().stack_size as i32,
96            )?;
97            debug_assert!(call_info.top() <= self.stack_last());
98
99            if closure.is_lua() {
100                let proto = closure.proto().unwrap_unchecked();
101                let mut arg = self.stack_top();
102                let arg_end = self
103                    .stack_base()
104                    .add(proto.as_ptr().as_ref().unwrap_unchecked().num_params as usize);
105                while arg < arg_end {
106                    arg.value_unchecked().set_nil();
107                    arg = arg.add(1);
108                }
109
110                self.set_stack_top(
111                    if proto.as_ptr().as_ref().unwrap_unchecked().is_vararg != 0 {
112                        arg
113                    } else {
114                        call_info.top()
115                    },
116                );
117
118                call_info.set_saved_pc(proto.as_ptr().as_ref().unwrap_unchecked().code);
119
120                if proto.as_ptr().as_ref().unwrap_unchecked().exec_target != 0
121                    && !proto
122                        .as_ptr()
123                        .as_ref()
124                        .unwrap_unchecked()
125                        .exec_data
126                        .is_null()
127                {
128                    call_info.as_ptr().as_mut().unwrap_unchecked().flags =
129                        crate::state::LUA_CALLINFO_NATIVE;
130                }
131
132                Ok(PreCallResult::Lua)
133            } else {
134                let native_function = closure.native_data().function.unwrap_unchecked();
135                let count = native_function(NativeCallContext::new(self))?;
136
137                let call_info = self.current_call_info();
138                let parent_cursor = self.current_call_info_cursor().sub(1);
139
140                let mut result = call_info.function();
141                let mut value = self.stack_top().sub(count);
142                let value_end = self.stack_top();
143                let mut remaining = n_results;
144
145                while remaining != 0 && value < value_end {
146                    result.value_unchecked().set_obj(value.value_unchecked());
147                    result = result.add(1);
148                    value = value.add(1);
149                    remaining -= 1;
150                }
151
152                while remaining > 0 {
153                    result.value_unchecked().set_nil();
154                    result = result.add(1);
155                    remaining -= 1;
156                }
157
158                self.restore_call_frame(parent_cursor, result);
159
160                Ok(PreCallResult::Native)
161            }
162        }
163    }
164
165    /// `luau_poscall`
166    unsafe fn pos_call(&self, first: TValueCursor) {
167        unsafe {
168            let call_info = self.current_call_info();
169            let parent_cursor = self.current_call_info_cursor().sub(1);
170            let parent = parent_cursor.call_info_unchecked();
171
172            let mut result = call_info.function();
173            let mut value = first;
174            let value_end = self.stack_top();
175            let mut remaining = call_info.as_ptr().as_ref().unwrap_unchecked().n_results;
176
177            while remaining != 0 && value < value_end {
178                result.value_unchecked().set_obj(value.value_unchecked());
179                result = result.add(1);
180                value = value.add(1);
181                remaining -= 1;
182            }
183
184            while remaining > 0 {
185                result.value_unchecked().set_nil();
186                result = result.add(1);
187                remaining -= 1;
188            }
189
190            self.restore_call_frame(
191                parent_cursor,
192                if call_info.as_ptr().as_ref().unwrap_unchecked().n_results == LUA_MULTRET {
193                    result
194                } else {
195                    parent.top()
196                },
197            );
198        }
199    }
200
201    /// `luau_callhook`
202    unsafe fn call_hook<F>(&self, hook: F, userdata: *mut ()) -> VmResult
203    where
204        F: FnOnce(&Thread, &mut LuaDebug) -> VmResult,
205    {
206        let (base_offset, top_offset, ci_top_offset, status, old_saved_pc, mut ar) = unsafe {
207            let base_offset = self.save_stack(self.stack_base());
208            let top_offset = self.save_stack(self.stack_top());
209            let ci_top_offset = self.save_stack(self.current_call_info().top());
210            let status = self.as_ptr().as_ref().unwrap_unchecked().status;
211
212            if status == THREAD_STATUS_YIELD || status == THREAD_STATUS_BREAK {
213                self.as_ptr().as_mut().unwrap_unchecked().status = THREAD_STATUS_OK;
214                self.set_stack_base(self.current_call_info().base());
215            }
216
217            let closure = self.current_call_info().function_closure();
218            let old_saved_pc = self.current_call_info().saved_pc();
219
220            if closure.is_lua() {
221                let proto = closure.proto().unwrap_unchecked();
222                let code_end = proto
223                    .as_ptr()
224                    .as_ref()
225                    .unwrap_unchecked()
226                    .code
227                    .add(proto.as_ptr().as_ref().unwrap_unchecked().size_code as usize);
228
229                if !old_saved_pc.is_null() && old_saved_pc != code_end {
230                    self.current_call_info().set_saved_pc(old_saved_pc.add(1));
231                }
232            }
233
234            self.check_stack_internal(crate::thread::LUA_MIN_STACK as i32)?;
235            self.current_call_info()
236                .set_top(self.stack_top().add(crate::thread::LUA_MIN_STACK));
237            debug_assert!(self.current_call_info().top() <= self.stack_last());
238
239            let ar = LuaDebug {
240                currentline: if closure.is_native() {
241                    -1
242                } else {
243                    let proto = closure.proto().unwrap_unchecked();
244                    proto.get_line(proto.pc_rel(old_saved_pc))
245                },
246                userdata,
247                ..LuaDebug::default()
248            };
249
250            (
251                base_offset,
252                top_offset,
253                ci_top_offset,
254                status,
255                old_saved_pc,
256                ar,
257            )
258        };
259
260        let result = hook(self, &mut ar);
261
262        unsafe {
263            self.current_call_info().set_saved_pc(old_saved_pc);
264            self.current_call_info()
265                .set_top(self.restore_stack(ci_top_offset));
266            self.set_stack_top(self.restore_stack(top_offset));
267
268            if status == THREAD_STATUS_YIELD
269                && self.as_ptr().as_ref().unwrap_unchecked().status != THREAD_STATUS_YIELD
270            {
271                self.as_ptr().as_mut().unwrap_unchecked().status = THREAD_STATUS_YIELD;
272                self.set_stack_base(self.restore_stack(base_offset));
273            } else if status == THREAD_STATUS_BREAK {
274                debug_assert_ne!(
275                    self.as_ptr().as_ref().unwrap_unchecked().status,
276                    THREAD_STATUS_BREAK
277                );
278
279                self.as_ptr().as_mut().unwrap_unchecked().status = THREAD_STATUS_BREAK;
280                self.set_stack_base(self.restore_stack(base_offset));
281            }
282        }
283        result
284    }
285}