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#[allow(
37 clippy::missing_safety_doc,
38 reason = "all methods share the capability-level safety contract"
39)]
40pub trait FunctionRuntime: Sealed {
41 unsafe fn find_upvalue(&self, level: TValue) -> VmErrorResult<UpVal>;
43
44 unsafe fn close(&self, level: TValue);
46
47 unsafe fn close_upvalue(&self, upvalue: UpVal, dead: bool);
49
50 unsafe fn record_hit(&self, caller: Closure, target: Closure, slot_id: u32) -> bool;
52
53 unsafe fn new_proto(&self) -> VmErrorResult<Proto>;
55
56 unsafe fn new_lua_closure(
58 &self,
59 element_count: i32,
60 environment: Option<Table>,
61 proto: Proto,
62 ) -> VmErrorResult<Closure>;
63
64 unsafe fn new_native_closure(
66 &self,
67 element_count: i32,
68 environment: Option<Table>,
69 ) -> VmErrorResult<Closure>;
70
71 unsafe fn free_proto(&self, proto: Proto, page: LuaPage);
73
74 unsafe fn free_closure(&self, closure: Closure, page: LuaPage);
76
77 unsafe fn free_upvalue(&self, upvalue: UpVal, page: LuaPage);
79}
80
81impl FunctionRuntime for Thread {
82 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 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 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 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 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 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 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 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 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 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}