mod closure;
mod proto;
mod upvalue;
pub use self::closure::{Closure, RawClosure, RawLuaClosure, RawNativeClosure};
pub use self::proto::{
FeedbackVectorSlot, FeedbackVectorSlotCallTarget, FeedbackVectorSlotData, LocVar, Proto,
RawLocVar, RawProto,
};
pub use self::upvalue::{RawUpVal, RawUpValData, RawUpValOpen, UpVal, UpValOpen};
use core::ptr;
use luau_common::flags;
use crate::Table;
use crate::VmErrorResult;
use crate::gc::GcBarrier;
use crate::gc::GcObject;
use crate::handle::RawHandle;
use crate::handle::sealed::Sealed;
use crate::memory::{LuaPage, MemoryRuntime};
use crate::state::ThreadState;
use crate::thread::LUA_MIN_STACK;
use crate::thread::Thread;
use crate::types::{LUA_TFUNCTION, LUA_TPROTO, LUA_TUPVALUE};
use crate::value::TValue;
#[allow(
clippy::missing_safety_doc,
reason = "all methods share the capability-level safety contract"
)]
pub trait FunctionRuntime: Sealed {
unsafe fn find_upvalue(&self, level: TValue) -> VmErrorResult<UpVal>;
unsafe fn close(&self, level: TValue);
unsafe fn close_upvalue(&self, upvalue: UpVal, dead: bool);
unsafe fn record_hit(&self, caller: Closure, target: Closure, slot_id: u32) -> bool;
unsafe fn new_proto(&self) -> VmErrorResult<Proto>;
unsafe fn new_lua_closure(
&self,
element_count: i32,
environment: Option<Table>,
proto: Proto,
) -> VmErrorResult<Closure>;
unsafe fn new_native_closure(
&self,
element_count: i32,
environment: Option<Table>,
) -> VmErrorResult<Closure>;
unsafe fn free_proto(&self, proto: Proto, page: LuaPage);
unsafe fn free_closure(&self, closure: Closure, page: LuaPage);
unsafe fn free_upvalue(&self, upvalue: UpVal, page: LuaPage);
}
impl FunctionRuntime for Thread {
unsafe fn find_upvalue(&self, level: TValue) -> VmErrorResult<UpVal> {
unsafe {
let global = self.global();
let mut previous_upvalue = None;
let mut current_upvalue = self.open_upvalue();
while let Some(open_upvalue) = current_upvalue {
debug_assert!(!global.is_dead(open_upvalue.into()));
debug_assert!(open_upvalue.is_open());
if open_upvalue.value_ptr() == level.as_ptr() {
return Ok(open_upvalue);
}
if open_upvalue.value_ptr() < level.as_ptr() {
break;
}
previous_upvalue = current_upvalue;
current_upvalue = open_upvalue.open_data().thread_next();
}
debug_assert!(self.as_ptr().as_ref().unwrap_unchecked().is_active);
let upvalue = self.new_gco::<UpVal>(
UpVal::allocation_size(),
self.as_ptr().as_ref().unwrap_unchecked().active_memcat,
)?;
GcObject::from(upvalue).init_header(self, LUA_TUPVALUE as u8);
let uv_head = global.uv_head();
let uv_head_next = uv_head.open_data().next();
upvalue.as_ptr().as_mut().unwrap_unchecked().marked_open = 0;
upvalue.set_value(level);
let open = upvalue.open_data();
open.set_thread_next(current_upvalue);
if let Some(previous_upvalue) = previous_upvalue {
previous_upvalue.open_data().set_thread_next(Some(upvalue));
} else {
self.set_open_upvalue(Some(upvalue));
}
open.set_prev(uv_head);
open.set_next(uv_head_next);
uv_head_next.open_data().set_prev(upvalue);
uv_head.open_data().set_next(upvalue);
debug_assert!(uv_head_next.open_data().prev() == upvalue);
debug_assert!(uv_head.open_data().next() == upvalue);
Ok(upvalue)
}
}
unsafe fn close(&self, level: TValue) {
unsafe {
let mut open_upvalue = self.open_upvalue();
while let Some(current_upvalue) = open_upvalue {
if current_upvalue.value_ptr() < level.as_ptr() {
break;
}
self.set_open_upvalue(current_upvalue.open_data().thread_next());
open_upvalue = self.open_upvalue();
self.close_upvalue(current_upvalue, false);
}
}
}
unsafe fn close_upvalue(&self, upvalue: UpVal, dead: bool) {
unsafe {
let open = upvalue.open_data();
let next = open.next();
let prev = open.prev();
debug_assert!(next.open_data().prev() == upvalue);
debug_assert!(prev.open_data().next() == upvalue);
next.open_data().set_prev(prev);
prev.open_data().set_next(next);
if dead {
return;
}
upvalue.close();
debug_assert!(core::ptr::eq(
upvalue.value_ptr().cast_const(),
upvalue.closed_value().as_ptr().cast_const(),
));
self.upvalue_closed(upvalue);
}
}
unsafe fn new_proto(&self) -> VmErrorResult<Proto> {
unsafe {
let proto = self.new_gco::<Proto>(
size_of::<RawProto>(),
self.as_ptr().as_ref().unwrap_unchecked().active_memcat,
)?;
GcObject::from(proto).init_header(self, LUA_TPROTO as u8);
let proto_ref = proto.as_ptr().as_mut().unwrap_unchecked();
proto_ref.n_ups = 0;
proto_ref.num_params = 0;
proto_ref.is_vararg = 0;
proto_ref.max_stack_size = 0;
proto_ref.flags = 0;
proto_ref.k = ptr::null_mut();
proto_ref.code = ptr::null_mut();
proto_ref.p = ptr::null_mut();
proto_ref.code_entry = ptr::null();
proto_ref.exec_data = ptr::null_mut();
proto_ref.exec_target = 0;
proto_ref.line_info = ptr::null_mut();
proto_ref.abs_line_info = ptr::null_mut();
proto_ref.loc_vars = ptr::null_mut();
proto_ref.upvalues = ptr::null_mut();
proto_ref.source = ptr::null_mut();
proto_ref.debug_name = ptr::null_mut();
proto_ref.debug_insn = ptr::null_mut();
proto_ref.type_info = ptr::null_mut();
proto_ref.userdata = ptr::null_mut();
proto_ref.gc_list = ptr::null_mut();
proto_ref.size_code = 0;
proto_ref.size_p = 0;
proto_ref.size_loc_vars = 0;
proto_ref.size_upvalues = 0;
proto_ref.size_k = 0;
proto_ref.size_line_info = 0;
proto_ref.line_gap_log2 = 0;
proto_ref.line_defined = 0;
proto_ref.bytecode_id = 0;
proto_ref.size_type_info = 0;
proto_ref.feedback_vec = ptr::null_mut();
proto_ref.feedback_vec_size = 0;
proto_ref.fun_id = 0;
proto_ref.cost = 0;
Ok(proto)
}
}
unsafe fn new_lua_closure(
&self,
element_count: i32,
environment: Option<Table>,
proto: Proto,
) -> VmErrorResult<Closure> {
unsafe {
let closure = self.new_gco::<Closure>(
Closure::size_lua_closure(element_count as usize),
self.as_ptr().as_ref().unwrap_unchecked().active_memcat,
)?;
GcObject::from(closure).init_header(self, LUA_TFUNCTION as u8);
let closure_ref = closure.as_ptr().as_mut().unwrap_unchecked();
closure_ref.is_native = 0;
closure_ref.n_upvalues = element_count as u8;
closure_ref.stack_size = proto.as_ptr().as_ref().unwrap_unchecked().max_stack_size;
closure_ref.preload = 0;
closure_ref.gc_list = ptr::null_mut();
closure_ref.env = environment.map_or(ptr::null_mut(), |table| table.as_ptr());
closure.set_lua_proto(proto);
for index in 0..element_count as usize {
closure.lua_upvalue_ref(index).set_nil();
}
Ok(closure)
}
}
unsafe fn new_native_closure(
&self,
element_count: i32,
environment: Option<Table>,
) -> VmErrorResult<Closure> {
unsafe {
let closure = self.new_gco::<Closure>(
Closure::size_native_closure(element_count as usize),
self.as_ptr().as_ref().unwrap_unchecked().active_memcat,
)?;
GcObject::from(closure).init_header(self, LUA_TFUNCTION as u8);
let closure_ref = closure.as_ptr().as_mut().unwrap_unchecked();
closure_ref.is_native = 1;
closure_ref.n_upvalues = element_count as u8;
closure_ref.stack_size = LUA_MIN_STACK as u8;
closure_ref.preload = 0;
closure_ref.gc_list = ptr::null_mut();
closure_ref.env = environment.map_or(ptr::null_mut(), |table| table.as_ptr());
closure.set_native_data(RawNativeClosure {
function: None,
continuation: None,
debug_name_deprecated: None,
debug_name: ptr::null_mut(),
});
for index in 0..element_count as usize {
closure.native_upvalue(index).set_nil();
}
Ok(closure)
}
}
unsafe fn free_proto(&self, proto: Proto, page: LuaPage) {
unsafe {
let proto_ref = proto.as_ptr().as_ref().unwrap_unchecked();
let memcat = proto_ref.memcat;
let code = proto_ref.code;
let size_code = proto_ref.size_code as usize;
let p = proto_ref.p;
let size_p = proto_ref.size_p as usize;
let k = proto_ref.k;
let size_k = proto_ref.size_k as usize;
let line_info = proto_ref.line_info;
let size_line_info = proto_ref.size_line_info as usize;
let loc_vars = proto_ref.loc_vars;
let size_loc_vars = proto_ref.size_loc_vars as usize;
let upvalues = proto_ref.upvalues;
let size_upvalues = proto_ref.size_upvalues as usize;
let debug_insn = proto_ref.debug_insn;
let exec_data = proto_ref.exec_data;
let type_info = proto_ref.type_info;
let size_type_info = proto_ref.size_type_info as usize;
let feedback_vec = proto_ref.feedback_vec;
let feedback_vec_size = proto_ref.feedback_vec_size as usize;
if !code.is_null() {
self.free_array(code, size_code, memcat);
}
if !p.is_null() {
self.free_array(p, size_p, memcat);
}
if !k.is_null() {
self.free_array(k, size_k, memcat);
}
if !line_info.is_null() {
self.free_array(line_info, size_line_info, memcat);
}
if !loc_vars.is_null() {
self.free_array(loc_vars, size_loc_vars, memcat);
}
if !upvalues.is_null() {
self.free_array(upvalues, size_upvalues, memcat);
}
if !debug_insn.is_null() {
self.free_array(debug_insn, size_code, memcat);
}
if !exec_data.is_null()
&& let Some(destroy) = self.global().execution_destroy()
{
destroy(self, proto);
}
if !type_info.is_null() {
self.free_array(type_info, size_type_info, memcat);
}
if !feedback_vec.is_null() {
self.free_array(feedback_vec, feedback_vec_size, memcat);
}
self.free_gco(proto.into(), size_of::<RawProto>(), memcat, page);
}
}
unsafe fn free_closure(&self, closure: Closure, page: LuaPage) {
unsafe {
self.free_gco(
closure.into(),
closure.size(),
closure.as_ptr().as_ref().unwrap_unchecked().memcat,
page,
);
}
}
unsafe fn free_upvalue(&self, upvalue: UpVal, page: LuaPage) {
unsafe {
self.free_gco(
upvalue.into(),
UpVal::allocation_size(),
upvalue.as_ptr().as_ref().unwrap_unchecked().memcat,
page,
)
};
}
unsafe fn record_hit(&self, caller: Closure, target: Closure, slot_id: u32) -> bool {
unsafe {
let Some(inline_function) = self.global().execution_inline_function() else {
return false;
};
debug_assert!(caller.is_lua());
if target.is_native() {
return false;
}
let caller_proto = caller.proto().unwrap_unchecked();
let target_proto = target.proto().unwrap_unchecked();
debug_assert!(
slot_id
< caller_proto
.as_ptr()
.as_ref()
.unwrap_unchecked()
.feedback_vec_size
);
let slot = &mut *caller_proto
.as_ptr()
.as_ref()
.unwrap_unchecked()
.feedback_vec
.add(slot_id as usize);
let call_target = &mut slot.data.call_target;
if call_target.proto == 0 {
call_target.proto = target_proto.as_ptr().as_ref().unwrap_unchecked().fun_id;
}
if call_target.proto != target_proto.as_ptr().as_ref().unwrap_unchecked().fun_id {
return false;
}
call_target.hits += 1;
let inline_hits_threshold = flags::LuauInlineHitsThreshold.get();
if call_target.hits as i32 >= inline_hits_threshold {
let _ = inline_function(self, caller, target, call_target.pc);
return false;
}
true
}
}
}