Skip to main content

luau_vm/function/
mod.rs

1mod closure;
2mod proto;
3mod upvalue;
4
5pub use self::closure::{Closure, RawClosure, RawLuaClosure, RawNativeClosure};
6pub use self::proto::{
7    FeedbackVectorSlot, FeedbackVectorSlotCallTarget, FeedbackVectorSlotData, LocVar, Proto,
8    RawLocVar, RawProto,
9};
10pub use self::upvalue::{RawUpVal, RawUpValData, RawUpValOpen, UpVal, UpValOpen};
11
12use core::ptr;
13
14use luau_common::flags;
15
16use crate::Table;
17use crate::VmErrorResult;
18use crate::gc::GcBarrier;
19use crate::gc::GcObject;
20use crate::handle::RawHandle;
21use crate::handle::sealed::Sealed;
22use crate::memory::{LuaPage, MemoryRuntime};
23use crate::state::ThreadState;
24use crate::thread::LUA_MIN_STACK;
25use crate::thread::Thread;
26use crate::types::{LUA_TFUNCTION, LUA_TPROTO, LUA_TUPVALUE};
27use crate::value::TValue;
28
29/// Unstable function-object allocation and lifetime capability.
30///
31/// # Safety
32///
33/// Function, upvalue, page, and stack handles must be live and belong to this
34/// thread's VM. Counts and sizes must match their layouts, and callers must
35/// preserve rooting, open-upvalue ordering, and GC barriers.
36#[allow(
37    clippy::missing_safety_doc,
38    reason = "all methods share the capability-level safety contract"
39)]
40pub trait FunctionRuntime: Sealed {
41    /// `luaF_findupval`
42    unsafe fn find_upvalue(&self, level: TValue) -> VmErrorResult<UpVal>;
43
44    /// `luaF_close`
45    unsafe fn close(&self, level: TValue);
46
47    /// `luaF_closeupval`
48    unsafe fn close_upvalue(&self, upvalue: UpVal, dead: bool);
49
50    /// `luaF_recordhit`
51    unsafe fn record_hit(&self, caller: Closure, target: Closure, slot_id: u32) -> bool;
52
53    /// `luaF_newproto`
54    unsafe fn new_proto(&self) -> VmErrorResult<Proto>;
55
56    /// `luaF_newLclosure`
57    unsafe fn new_lua_closure(
58        &self,
59        element_count: i32,
60        environment: Option<Table>,
61        proto: Proto,
62    ) -> VmErrorResult<Closure>;
63
64    /// `luaF_newCclosure`
65    unsafe fn new_native_closure(
66        &self,
67        element_count: i32,
68        environment: Option<Table>,
69    ) -> VmErrorResult<Closure>;
70
71    /// `luaF_freeproto`
72    unsafe fn free_proto(&self, proto: Proto, page: LuaPage);
73
74    /// `luaF_freeclosure`
75    unsafe fn free_closure(&self, closure: Closure, page: LuaPage);
76
77    /// `luaF_freeupval`
78    unsafe fn free_upvalue(&self, upvalue: UpVal, page: LuaPage);
79}
80
81impl FunctionRuntime for Thread {
82    /// `luaF_findupval`
83    unsafe fn find_upvalue(&self, level: TValue) -> VmErrorResult<UpVal> {
84        unsafe {
85            let global = self.global();
86            let mut previous_upvalue = None;
87            let mut current_upvalue = self.open_upvalue();
88
89            while let Some(open_upvalue) = current_upvalue {
90                debug_assert!(!global.is_dead(open_upvalue.into()));
91                debug_assert!(open_upvalue.is_open());
92
93                if open_upvalue.value_ptr() == level.as_ptr() {
94                    return Ok(open_upvalue);
95                }
96
97                if open_upvalue.value_ptr() < level.as_ptr() {
98                    break;
99                }
100
101                previous_upvalue = current_upvalue;
102                current_upvalue = open_upvalue.open_data().thread_next();
103            }
104
105            debug_assert!(self.as_ptr().as_ref().unwrap_unchecked().is_active);
106
107            let upvalue = self.new_gco::<UpVal>(
108                UpVal::allocation_size(),
109                self.as_ptr().as_ref().unwrap_unchecked().active_memcat,
110            )?;
111            GcObject::from(upvalue).init_header(self, LUA_TUPVALUE as u8);
112
113            let uv_head = global.uv_head();
114            let uv_head_next = uv_head.open_data().next();
115
116            upvalue.as_ptr().as_mut().unwrap_unchecked().marked_open = 0;
117            upvalue.set_value(level);
118
119            let open = upvalue.open_data();
120            open.set_thread_next(current_upvalue);
121            if let Some(previous_upvalue) = previous_upvalue {
122                previous_upvalue.open_data().set_thread_next(Some(upvalue));
123            } else {
124                self.set_open_upvalue(Some(upvalue));
125            }
126            open.set_prev(uv_head);
127            open.set_next(uv_head_next);
128
129            uv_head_next.open_data().set_prev(upvalue);
130            uv_head.open_data().set_next(upvalue);
131
132            debug_assert!(uv_head_next.open_data().prev() == upvalue);
133            debug_assert!(uv_head.open_data().next() == upvalue);
134
135            Ok(upvalue)
136        }
137    }
138
139    /// `luaF_close`
140    unsafe fn close(&self, level: TValue) {
141        unsafe {
142            let mut open_upvalue = self.open_upvalue();
143            while let Some(current_upvalue) = open_upvalue {
144                if current_upvalue.value_ptr() < level.as_ptr() {
145                    break;
146                }
147
148                self.set_open_upvalue(current_upvalue.open_data().thread_next());
149                open_upvalue = self.open_upvalue();
150
151                self.close_upvalue(current_upvalue, false);
152            }
153        }
154    }
155
156    /// `luaF_closeupval`
157    unsafe fn close_upvalue(&self, upvalue: UpVal, dead: bool) {
158        unsafe {
159            let open = upvalue.open_data();
160            let next = open.next();
161            let prev = open.prev();
162
163            debug_assert!(next.open_data().prev() == upvalue);
164            debug_assert!(prev.open_data().next() == upvalue);
165
166            next.open_data().set_prev(prev);
167            prev.open_data().set_next(next);
168
169            if dead {
170                return;
171            }
172
173            upvalue.close();
174            debug_assert!(core::ptr::eq(
175                upvalue.value_ptr().cast_const(),
176                upvalue.closed_value().as_ptr().cast_const(),
177            ));
178            self.upvalue_closed(upvalue);
179        }
180    }
181
182    /// `luaF_newproto`
183    unsafe fn new_proto(&self) -> VmErrorResult<Proto> {
184        unsafe {
185            let proto = self.new_gco::<Proto>(
186                size_of::<RawProto>(),
187                self.as_ptr().as_ref().unwrap_unchecked().active_memcat,
188            )?;
189            GcObject::from(proto).init_header(self, LUA_TPROTO as u8);
190            let proto_ref = proto.as_ptr().as_mut().unwrap_unchecked();
191            proto_ref.n_ups = 0;
192            proto_ref.num_params = 0;
193            proto_ref.is_vararg = 0;
194            proto_ref.max_stack_size = 0;
195            proto_ref.flags = 0;
196            proto_ref.k = ptr::null_mut();
197            proto_ref.code = ptr::null_mut();
198            proto_ref.p = ptr::null_mut();
199            proto_ref.code_entry = ptr::null();
200            proto_ref.exec_data = ptr::null_mut();
201            proto_ref.exec_target = 0;
202            proto_ref.line_info = ptr::null_mut();
203            proto_ref.abs_line_info = ptr::null_mut();
204            proto_ref.loc_vars = ptr::null_mut();
205            proto_ref.upvalues = ptr::null_mut();
206            proto_ref.source = ptr::null_mut();
207            proto_ref.debug_name = ptr::null_mut();
208            proto_ref.debug_insn = ptr::null_mut();
209            proto_ref.type_info = ptr::null_mut();
210            proto_ref.userdata = ptr::null_mut();
211            proto_ref.gc_list = ptr::null_mut();
212            proto_ref.size_code = 0;
213            proto_ref.size_p = 0;
214            proto_ref.size_loc_vars = 0;
215            proto_ref.size_upvalues = 0;
216            proto_ref.size_k = 0;
217            proto_ref.size_line_info = 0;
218            proto_ref.line_gap_log2 = 0;
219            proto_ref.line_defined = 0;
220            proto_ref.bytecode_id = 0;
221            proto_ref.size_type_info = 0;
222            proto_ref.feedback_vec = ptr::null_mut();
223            proto_ref.feedback_vec_size = 0;
224            proto_ref.fun_id = 0;
225            proto_ref.cost = 0;
226
227            Ok(proto)
228        }
229    }
230
231    /// `luaF_newLclosure`
232    unsafe fn new_lua_closure(
233        &self,
234        element_count: i32,
235        environment: Option<Table>,
236        proto: Proto,
237    ) -> VmErrorResult<Closure> {
238        unsafe {
239            let closure = self.new_gco::<Closure>(
240                Closure::size_lua_closure(element_count as usize),
241                self.as_ptr().as_ref().unwrap_unchecked().active_memcat,
242            )?;
243            GcObject::from(closure).init_header(self, LUA_TFUNCTION as u8);
244            let closure_ref = closure.as_ptr().as_mut().unwrap_unchecked();
245            closure_ref.is_native = 0;
246            closure_ref.n_upvalues = element_count as u8;
247            closure_ref.stack_size = proto.as_ptr().as_ref().unwrap_unchecked().max_stack_size;
248            closure_ref.preload = 0;
249            closure_ref.gc_list = ptr::null_mut();
250            closure_ref.env = environment.map_or(ptr::null_mut(), |table| table.as_ptr());
251
252            closure.set_lua_proto(proto);
253
254            for index in 0..element_count as usize {
255                closure.lua_upvalue_ref(index).set_nil();
256            }
257
258            Ok(closure)
259        }
260    }
261
262    /// `luaF_newCclosure`
263    unsafe fn new_native_closure(
264        &self,
265        element_count: i32,
266        environment: Option<Table>,
267    ) -> VmErrorResult<Closure> {
268        unsafe {
269            let closure = self.new_gco::<Closure>(
270                Closure::size_native_closure(element_count as usize),
271                self.as_ptr().as_ref().unwrap_unchecked().active_memcat,
272            )?;
273            GcObject::from(closure).init_header(self, LUA_TFUNCTION as u8);
274            let closure_ref = closure.as_ptr().as_mut().unwrap_unchecked();
275            closure_ref.is_native = 1;
276            closure_ref.n_upvalues = element_count as u8;
277            closure_ref.stack_size = LUA_MIN_STACK as u8;
278            closure_ref.preload = 0;
279            closure_ref.gc_list = ptr::null_mut();
280            closure_ref.env = environment.map_or(ptr::null_mut(), |table| table.as_ptr());
281
282            closure.set_native_data(RawNativeClosure {
283                function: None,
284                continuation: None,
285                debug_name_deprecated: None,
286                debug_name: ptr::null_mut(),
287            });
288
289            for index in 0..element_count as usize {
290                closure.native_upvalue(index).set_nil();
291            }
292
293            Ok(closure)
294        }
295    }
296
297    /// `luaF_freeproto`
298    unsafe fn free_proto(&self, proto: Proto, page: LuaPage) {
299        unsafe {
300            let proto_ref = proto.as_ptr().as_ref().unwrap_unchecked();
301            let memcat = proto_ref.memcat;
302            let code = proto_ref.code;
303            let size_code = proto_ref.size_code as usize;
304            let p = proto_ref.p;
305            let size_p = proto_ref.size_p as usize;
306            let k = proto_ref.k;
307            let size_k = proto_ref.size_k as usize;
308            let line_info = proto_ref.line_info;
309            let size_line_info = proto_ref.size_line_info as usize;
310            let loc_vars = proto_ref.loc_vars;
311            let size_loc_vars = proto_ref.size_loc_vars as usize;
312            let upvalues = proto_ref.upvalues;
313            let size_upvalues = proto_ref.size_upvalues as usize;
314            let debug_insn = proto_ref.debug_insn;
315            let exec_data = proto_ref.exec_data;
316            let type_info = proto_ref.type_info;
317            let size_type_info = proto_ref.size_type_info as usize;
318            let feedback_vec = proto_ref.feedback_vec;
319            let feedback_vec_size = proto_ref.feedback_vec_size as usize;
320
321            if !code.is_null() {
322                self.free_array(code, size_code, memcat);
323            }
324            if !p.is_null() {
325                self.free_array(p, size_p, memcat);
326            }
327            if !k.is_null() {
328                self.free_array(k, size_k, memcat);
329            }
330            if !line_info.is_null() {
331                self.free_array(line_info, size_line_info, memcat);
332            }
333            if !loc_vars.is_null() {
334                self.free_array(loc_vars, size_loc_vars, memcat);
335            }
336            if !upvalues.is_null() {
337                self.free_array(upvalues, size_upvalues, memcat);
338            }
339            if !debug_insn.is_null() {
340                self.free_array(debug_insn, size_code, memcat);
341            }
342
343            if !exec_data.is_null()
344                && let Some(destroy) = self.global().execution_destroy()
345            {
346                destroy(self, proto);
347            }
348
349            if !type_info.is_null() {
350                self.free_array(type_info, size_type_info, memcat);
351            }
352            if !feedback_vec.is_null() {
353                self.free_array(feedback_vec, feedback_vec_size, memcat);
354            }
355
356            self.free_gco(proto.into(), size_of::<RawProto>(), memcat, page);
357        }
358    }
359
360    /// `luaF_freeclosure`
361    unsafe fn free_closure(&self, closure: Closure, page: LuaPage) {
362        unsafe {
363            self.free_gco(
364                closure.into(),
365                closure.size(),
366                closure.as_ptr().as_ref().unwrap_unchecked().memcat,
367                page,
368            );
369        }
370    }
371
372    /// `luaF_freeupval`
373    unsafe fn free_upvalue(&self, upvalue: UpVal, page: LuaPage) {
374        unsafe {
375            self.free_gco(
376                upvalue.into(),
377                UpVal::allocation_size(),
378                upvalue.as_ptr().as_ref().unwrap_unchecked().memcat,
379                page,
380            )
381        };
382    }
383
384    /// `luaF_recordhit`
385    unsafe fn record_hit(&self, caller: Closure, target: Closure, slot_id: u32) -> bool {
386        unsafe {
387            let Some(inline_function) = self.global().execution_inline_function() else {
388                return false;
389            };
390
391            debug_assert!(caller.is_lua());
392            if target.is_native() {
393                return false;
394            }
395
396            let caller_proto = caller.proto().unwrap_unchecked();
397            let target_proto = target.proto().unwrap_unchecked();
398
399            debug_assert!(
400                slot_id
401                    < caller_proto
402                        .as_ptr()
403                        .as_ref()
404                        .unwrap_unchecked()
405                        .feedback_vec_size
406            );
407            let slot = &mut *caller_proto
408                .as_ptr()
409                .as_ref()
410                .unwrap_unchecked()
411                .feedback_vec
412                .add(slot_id as usize);
413            let call_target = &mut slot.data.call_target;
414
415            if call_target.proto == 0 {
416                call_target.proto = target_proto.as_ptr().as_ref().unwrap_unchecked().fun_id;
417            }
418
419            if call_target.proto != target_proto.as_ptr().as_ref().unwrap_unchecked().fun_id {
420                return false;
421            }
422
423            call_target.hits += 1;
424
425            let inline_hits_threshold = flags::LuauInlineHitsThreshold.get();
426            if call_target.hits as i32 >= inline_hits_threshold {
427                let _ = inline_function(self, caller, target, call_target.pc);
428                return false;
429            }
430
431            true
432        }
433    }
434}