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#[allow(
28 clippy::missing_safety_doc,
29 reason = "all methods share the capability-level safety contract"
30)]
31pub trait VmCallFrame: Sealed {
32 unsafe fn try_func_tm(&self, function: TValueCursor) -> VmResult;
34
35 unsafe fn pre_call(&self, function: TValueCursor, n_results: i32) -> VmResult<PreCallResult>;
37
38 unsafe fn pos_call(&self, first: TValueCursor);
40
41 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 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 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 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 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}