Skip to main content

luau_vm/thread/
call.rs

1use luau_bytecode::opcodes::PROTO_FLAG_USES_EXPORT;
2use luau_common::{ByteSlice, flags};
3
4use super::stack::RawStackAccess;
5use super::{LUA_MULTRET, Thread};
6use crate::call::{CallRuntime, ProtectedCall, ThreadStack, resume_handle, resume_protected};
7use crate::debug::DebugRuntime;
8use crate::function::{FunctionRuntime, RawNativeClosure};
9use crate::gc::{GcBarrier, GcRuntime};
10use crate::handle::RawHandle;
11use crate::native::{
12    NativeCallContext, NativeCallResult, RawNativeContinuation, RawNativeFunction,
13};
14use crate::state::{
15    LUA_CALLINFO_HANDLE, LUA_OK, THREAD_STATUS_BREAK, THREAD_STATUS_OK, THREAD_STATUS_YIELD,
16    ThreadState,
17};
18use crate::string::StringRuntime;
19use crate::value::{TValueCursor, nil_object};
20use crate::{VmControl, VmError, VmErrorResult, VmExit, VmResult};
21
22// Callable values
23impl Thread {
24    /// `lua_pushcclosurek`
25    pub unsafe fn push_native_closure_k(
26        &self,
27        function: RawNativeFunction,
28        debug_name: Option<&'static str>,
29        upvalues: i32,
30        continuation: Option<RawNativeContinuation>,
31    ) -> VmErrorResult {
32        debug_assert!(upvalues >= 0);
33        debug_assert!(
34            upvalues <= unsafe { self.stack_top().offset_from(self.stack_base()) as i32 }
35        );
36        unsafe {
37            self.check_gc()?;
38            self.thread_barrier();
39            self.ensure_stack(self, 1)?;
40
41            let closure = self.new_native_closure(upvalues, Some(self.current_env()))?;
42            let managed_debug_name = if flags::LuauManagedDebugNames.get() {
43                debug_name
44                    .map(|name| self.intern_string(name.as_bytes().as_bstr()))
45                    .transpose()?
46            } else {
47                None
48            };
49            closure.set_native_data(RawNativeClosure {
50                function: Some(function),
51                continuation,
52                debug_name_deprecated: (!flags::LuauManagedDebugNames.get())
53                    .then_some(debug_name)
54                    .flatten(),
55                debug_name: managed_debug_name.map_or(core::ptr::null_mut(), |name| name.as_ptr()),
56            });
57            self.set_stack_top(self.stack_top().sub(upvalues as usize));
58
59            for index in (0..upvalues as usize).rev() {
60                closure
61                    .native_upvalue(index)
62                    .set_obj(self.stack_top().add(index).value_unchecked());
63            }
64
65            let top = self.stack_top();
66            top.value_unchecked().set_closure_value(closure);
67            debug_assert!(top < self.current_call_info().top());
68            self.set_stack_top(top.add(1));
69        }
70        Ok(())
71    }
72
73    /// `lua_pushcfunction`
74    pub unsafe fn push_native_function(
75        &self,
76        function: RawNativeFunction,
77        debug_name: Option<&'static str>,
78    ) -> VmErrorResult {
79        unsafe { self.push_native_closure_k(function, debug_name, 0, None) }
80    }
81
82    /// `lua_pushcclosure`
83    pub unsafe fn push_native_closure(
84        &self,
85        function: RawNativeFunction,
86        debug_name: Option<&'static str>,
87        upvalues: i32,
88    ) -> VmErrorResult {
89        unsafe { self.push_native_closure_k(function, debug_name, upvalues, None) }
90    }
91
92    /// `lua_tocfunction`
93    pub unsafe fn to_native_function(&self, index: i32) -> Option<RawNativeFunction> {
94        unsafe {
95            let object = self.index_to_addr(index);
96            if object == nil_object() || !object.is_function() {
97                return None;
98            }
99
100            let closure = object.closure_value();
101            if !closure.is_native() {
102                return None;
103            }
104
105            closure.native_data().function
106        }
107    }
108
109    /// `lua_clonefunction`
110    pub unsafe fn clone_function(&self, index: i32) -> VmErrorResult {
111        unsafe {
112            self.check_gc()?;
113            self.thread_barrier();
114            self.ensure_stack(self, 1)?;
115
116            let object = self.to_object(index).unwrap_unchecked();
117            debug_assert!(object.is_function());
118
119            let closure = object.closure_value();
120            let closure_ref = closure.as_ptr().as_ref().unwrap_unchecked();
121            debug_assert!(closure.is_lua());
122
123            let proto = closure.proto().unwrap_unchecked();
124            let environment = self.globals();
125            let new_closure =
126                self.new_lua_closure(i32::from(closure_ref.n_upvalues), Some(environment), proto)?;
127
128            for index in 0..closure_ref.n_upvalues as usize {
129                new_closure
130                    .lua_upvalue_ref(index)
131                    .set_obj(closure.lua_upvalue_ref(index));
132            }
133
134            let top = self.stack_top();
135            top.value_unchecked().set_closure_value(new_closure);
136            debug_assert!(top < self.current_call_info().top());
137            self.set_stack_top(top.add(1));
138        }
139        Ok(())
140    }
141
142    /// `lua_usesexport`
143    pub unsafe fn uses_export(&self, index: i32) -> i32 {
144        let object = unsafe { self.index_to_addr(index) };
145        if object == nil_object() || !object.is_function() {
146            return 0;
147        }
148
149        let closure = object.closure_value();
150        if unsafe { closure.is_native() } {
151            return 0;
152        }
153
154        let proto = unsafe { closure.proto().unwrap_unchecked() };
155        i32::from(
156            unsafe { proto.as_ptr().as_ref().unwrap_unchecked().flags } & PROTO_FLAG_USES_EXPORT
157                != 0,
158        )
159    }
160}
161
162// Calls and protected calls
163/// `adjustresults`
164fn adjust_results(thread: &Thread, n_results: i32) {
165    if n_results == LUA_MULTRET && unsafe { thread.stack_top() >= thread.current_call_info().top() }
166    {
167        unsafe {
168            thread.current_call_info().set_top(thread.stack_top());
169        }
170    }
171}
172
173/// `checkresults`
174fn check_results(thread: &Thread, n_args: i32, n_results: i32) {
175    debug_assert!(
176        n_results == LUA_MULTRET
177            || unsafe {
178                thread
179                    .current_call_info()
180                    .top()
181                    .offset_from(thread.stack_top()) as i32
182            } >= n_results - n_args
183    );
184}
185
186impl Thread {
187    /// `lua_call`
188    pub unsafe fn call(&self, n_args: i32, n_results: i32) -> VmResult {
189        debug_assert!(n_args >= 0);
190        debug_assert!(n_results >= LUA_MULTRET);
191        debug_assert!(n_args < unsafe { self.stack_top().offset_from(self.stack_base()) as i32 });
192        if n_results > n_args + 1 {
193            unsafe { self.ensure_stack(self, n_results - (n_args + 1))? };
194        }
195        check_results(self, n_args, n_results);
196
197        let result = unsafe {
198            debug_assert!(self.as_ptr().as_ref().unwrap_unchecked().status == THREAD_STATUS_OK);
199            let function = self.stack_top().sub((n_args + 1) as usize);
200            self.call_internal(function, n_results)
201        };
202
203        if matches!(result, Ok(()) | Err(VmExit::Control(_))) {
204            adjust_results(self, n_results);
205        }
206
207        result
208    }
209
210    /// `lua_pcall`
211    pub unsafe fn protected_call(&self, n_args: i32, n_results: i32, errfunc: i32) -> VmResult {
212        debug_assert!(n_args >= 0);
213        debug_assert!(n_results >= LUA_MULTRET);
214        #[repr(C)]
215        struct CallContext {
216            function: TValueCursor,
217            n_results: i32,
218        }
219
220        /// `f_call`
221        unsafe fn pcall_callback(thread: &Thread, context: &mut CallContext) -> VmResult {
222            unsafe { thread.call_internal(context.function, context.n_results)? };
223            Ok(())
224        }
225
226        if n_results > n_args + 1
227            && let Err(error) = unsafe { self.ensure_stack(self, n_results - (n_args + 1)) }
228        {
229            return Err(error.into());
230        }
231        check_results(self, n_args, n_results);
232
233        unsafe {
234            debug_assert!(n_args < self.stack_top().offset_from(self.stack_base()) as i32);
235            debug_assert!(self.as_ptr().as_ref().unwrap_unchecked().status == THREAD_STATUS_OK);
236
237            let error_function = if errfunc != 0 {
238                let error_function = self.stack_index_to_cursor(errfunc);
239                debug_assert!(error_function.value_unchecked() != nil_object());
240                self.save_stack(error_function)
241            } else {
242                0
243            };
244
245            let function = self.stack_top().sub((n_args + 1) as usize);
246            let mut context = CallContext {
247                function,
248                n_results,
249            };
250
251            let result = self.protected_call_internal(
252                pcall_callback,
253                &mut context,
254                self.save_stack(function),
255                error_function,
256            );
257            adjust_results(self, n_results);
258            result
259        }
260    }
261
262    /// `lua_cpcall`
263    pub unsafe fn protected_native_call(
264        &self,
265        function: RawNativeFunction,
266        userdata: *mut (),
267    ) -> VmResult {
268        #[repr(C)]
269        struct ProtectedCallContext {
270            function: RawNativeFunction,
271            userdata: *mut (),
272        }
273
274        /// `f_Ccall`
275        unsafe fn protected_call_callback(
276            thread: &Thread,
277            context: &mut ProtectedCallContext,
278        ) -> VmResult {
279            unsafe {
280                if thread.check_stack(2) == 0 {
281                    return crate::run_error!(thread, "stack limit").map_err(Into::into);
282                }
283
284                thread.push_native_closure_k(context.function, None, 0, None)?;
285                thread.push_light_userdata_tagged(context.userdata, 0)?;
286                let function = thread.stack_top().sub(2);
287                thread.call_internal(function, 0)?;
288            }
289            Ok(())
290        }
291
292        let mut context = ProtectedCallContext { function, userdata };
293        unsafe {
294            debug_assert!(self.as_ptr().as_ref().unwrap_unchecked().status == THREAD_STATUS_OK);
295            self.protected_call_internal(
296                protected_call_callback,
297                &mut context,
298                self.save_stack(self.stack_top()),
299                0,
300            )
301        }
302    }
303
304    /// `lua_error`
305    pub unsafe fn error<T>(&self) -> VmErrorResult<T> {
306        debug_assert!(unsafe { self.stack_top() > self.stack_base() });
307        Err(VmError::Runtime)
308    }
309
310    /// `lua_yield`
311    #[must_use = "yield_current returns VM control flow that must be propagated by the caller"]
312    pub unsafe fn yield_current(&self, results: i32) -> NativeCallResult {
313        unsafe {
314            debug_assert!(results >= 0);
315            debug_assert!(results <= self.stack_top().offset_from(self.stack_base()) as i32);
316
317            if self.as_ptr().as_ref().unwrap_unchecked().native_call_depth
318                > self
319                    .as_ptr()
320                    .as_ref()
321                    .unwrap_unchecked()
322                    .base_native_call_depth
323            {
324                return crate::run_error!(
325                    self,
326                    "attempt to yield across metamethod/C-call boundary"
327                )
328                .map_err(Into::into);
329            }
330
331            self.set_stack_base(self.stack_top().sub(results as usize));
332            self.as_ptr().as_mut().unwrap_unchecked().status = THREAD_STATUS_YIELD;
333        }
334
335        Err(VmExit::Control(VmControl::Yield))
336    }
337
338    /// `lua_break`
339    #[must_use = "break_current returns VM control flow that must be propagated by the caller"]
340    pub unsafe fn break_current(&self) -> NativeCallResult {
341        unsafe {
342            if self.as_ptr().as_ref().unwrap_unchecked().native_call_depth
343                > self
344                    .as_ptr()
345                    .as_ref()
346                    .unwrap_unchecked()
347                    .base_native_call_depth
348            {
349                return crate::run_error!(
350                    self,
351                    "attempt to break across metamethod/C-call boundary"
352                )
353                .map_err(Into::into);
354            }
355
356            self.as_ptr().as_mut().unwrap_unchecked().status = THREAD_STATUS_BREAK;
357        }
358
359        Err(VmExit::Control(VmControl::Break))
360    }
361}
362
363// Coroutine resumption
364impl Thread {
365    /// `lua_resume`
366    pub unsafe fn resume(&self, from: Option<&Thread>, nargs: i32) -> VmResult {
367        unsafe {
368            self.resume_start(from, nargs)?;
369
370            let old_native_call_depth = self.as_ptr().as_ref().unwrap_unchecked().native_call_depth;
371            let mut first_argument = self.stack_top().sub(nargs as usize);
372            let result = self.raw_run_protected(resume_protected, &mut first_argument);
373
374            self.resume_finish(result, old_native_call_depth)
375        }
376    }
377
378    /// `lua_resumeerror`
379    pub unsafe fn resume_error(&self, from: Option<&Thread>) -> VmResult {
380        unsafe {
381            self.resume_start(from, 1)?;
382
383            let old_native_call_depth = self.as_ptr().as_ref().unwrap_unchecked().native_call_depth;
384            let mut result = Err(VmError::Runtime.into());
385
386            if let Some(handler) = self.resume_find_handler() {
387                self.as_ptr().as_mut().unwrap_unchecked().status = VmError::Runtime.status() as u8;
388                let mut handler = handler;
389                result = self.raw_run_protected(resume_handle, &mut handler);
390            }
391
392            self.resume_finish(result, old_native_call_depth)
393        }
394    }
395}
396
397// Yieldable native calls
398impl Thread {
399    /// `luaL_callyieldable`
400    pub unsafe fn call_yieldable(&self, nargs: i32, nresults: i32) -> NativeCallResult {
401        unsafe {
402            let closure = self.current_function();
403            debug_assert!(closure.is_native());
404            debug_assert!(closure.native_data().continuation.is_some());
405
406            self.call(nargs, nresults)?;
407            closure.native_data().continuation.unwrap_unchecked()(
408                NativeCallContext::new(self),
409                LUA_OK,
410            )
411        }
412    }
413
414    /// `luaL_pcallyieldable`
415    pub unsafe fn protected_call_yieldable(
416        &self,
417        nargs: i32,
418        nresults: i32,
419        errfunc: i32,
420    ) -> NativeCallResult {
421        #[repr(C)]
422        struct CallContext {
423            function: TValueCursor,
424            n_results: i32,
425        }
426
427        unsafe fn protected_call_yieldable_run(
428            thread: &Thread,
429            context: &mut CallContext,
430        ) -> VmResult {
431            unsafe {
432                thread.call_int(
433                    context.function,
434                    context.n_results,
435                    thread.is_yieldable() != 0,
436                )?;
437            }
438            Ok(())
439        }
440
441        unsafe {
442            let closure = self.current_function();
443            debug_assert!(closure.is_native());
444            let continuation = closure.native_data().continuation.unwrap_unchecked();
445            debug_assert!(nargs < self.stack_top().offset_from(self.stack_base()) as i32);
446            debug_assert!(errfunc >= 0);
447            debug_assert!(errfunc <= self.stack_top().offset_from(self.stack_base()) as i32);
448
449            let call_info = self.current_call_info();
450            call_info.set_errfunc(errfunc);
451            call_info.as_ptr().as_mut().unwrap_unchecked().flags |= LUA_CALLINFO_HANDLE;
452
453            let function = self.stack_top().sub((nargs + 1) as usize);
454            let mut context = CallContext {
455                function,
456                n_results: nresults,
457            };
458            let saved_function = self.save_stack(function);
459            let saved_error_function = if errfunc != 0 {
460                self.save_stack(self.stack_base().add((errfunc - 1) as usize))
461            } else {
462                0
463            };
464
465            let result = self.protected_call_internal(
466                protected_call_yieldable_run,
467                &mut context,
468                saved_function,
469                saved_error_function,
470            );
471
472            self.expand_stack_limit(self.stack_top());
473
474            let status = match result {
475                Ok(()) => LUA_OK,
476                Err(VmExit::Error(error)) => error.status(),
477                Err(exit) => return Err(exit),
478            };
479
480            call_info.as_ptr().as_mut().unwrap_unchecked().flags &= !LUA_CALLINFO_HANDLE;
481
482            continuation(NativeCallContext::new(self), status)
483        }
484    }
485}