use crate::Table;
use crate::call::ProtectedCall;
use crate::function::FunctionRuntime;
use crate::function::UpVal;
use crate::gc::GcObject;
use crate::handle::RawHandle;
use crate::metamethod::TmEvent;
use crate::table::LuaNode;
use crate::table::TableRuntime;
use crate::thread::Thread;
use crate::types::{LUA_TDEADKEY, LUA_TSTRING};
use crate::value::{RAW_TVALUE_NIL, TValue};
use crate::{VmError, VmExit, VmResult};
use luau_common::flags;
fn embedder_mark_ref(thread: &Thread, reference: i32) {
if reference <= crate::thread::LUA_REFNIL {
return;
}
unsafe {
let global = thread.global();
let registry = global.weak_registry().table_value();
let value = registry.get_num(reference);
if value.is_collectable() && value.gc_value().is_white() {
global.really_mark_object(value.gc_value());
}
}
}
impl LuaNode {
fn remove_entry(&self) {
debug_assert!(self.value_unchecked().is_nil());
if self.key().is_collectable() {
self.key().set_tt(LUA_TDEADKEY);
}
}
}
impl Table {
unsafe fn mode_contains(&self, thread: &Thread, byte: u8) -> bool {
unsafe {
let global = thread.global();
let event_name = global.tm_name(TmEvent::Mode as usize);
let Some(mode) = self.get_tm(TmEvent::Mode, event_name) else {
return false;
};
if mode.is_string() {
mode.string_value().as_bytes().contains(&byte)
} else {
false
}
}
}
unsafe fn resize_hash_protected(&self, thread: &Thread, hash_size: i32) {
#[repr(C)]
struct ResizeHashContext {
table: Table,
hash_size: i32,
}
unsafe fn resize_hash_trampoline(
thread: &Thread,
context: &mut ResizeHashContext,
) -> VmResult {
unsafe { thread.resize_hash(context.table, context.hash_size) }?;
Ok(())
}
let mut context = ResizeHashContext {
table: *self,
hash_size,
};
let result = unsafe { thread.raw_run_protected(resize_hash_trampoline, &mut context) };
debug_assert!(matches!(
result,
Ok(()) | Err(VmExit::Error(VmError::Memory))
));
}
}
impl TValue {
unsafe fn is_cleared(&self) -> bool {
if !self.is_collectable() {
return false;
}
let object_ref = self.gc_value();
unsafe {
if object_ref.as_ptr().as_ref().unwrap_unchecked().tt == LUA_TSTRING as u8 {
object_ref.to_string().mark_gc();
return false;
}
object_ref.is_white()
}
}
}
impl Thread {
unsafe fn remark_upvalues(&self) -> usize {
unsafe {
let global = self.global();
let uv_head = global.uv_head();
let mut work = 0;
let mut upvalue = uv_head.open_data().next();
while upvalue != uv_head {
work += UpVal::allocation_size();
let open = upvalue.open_data();
let object: GcObject = upvalue.into();
debug_assert!(upvalue.is_open());
debug_assert!(open.next().open_data().prev() == upvalue);
debug_assert!(open.prev().open_data().next() == upvalue);
debug_assert!(!object.is_black());
if object.is_gray() {
global.mark_value(upvalue.value());
}
upvalue = open.next();
}
work
}
}
unsafe fn clear_tables(&self, mut list: Option<GcObject>) -> usize {
let mut work = 0;
unsafe {
while let Some(object) = list {
let table = object.to_table();
let table_ref = table.as_ptr().as_ref().unwrap_unchecked();
let size_node = table.node_count();
work += table.gc_work_size(!flags::LuauGcTableStepFix.get());
for index in (0..table_ref.size_array as usize).rev() {
let value = table.array_slot(index);
if value.is_cleared() {
value.set_nil();
}
}
let mut active_values = 0usize;
for index in (0..size_node).rev() {
let node = table.node(index as i32);
if !node.value_unchecked().is_nil() {
let mut key = RAW_TVALUE_NIL;
let key = TValue::from_mut(&mut key);
node.write_key_to_value(key);
if key.is_cleared() || node.value_unchecked().is_cleared() {
node.value_unchecked().set_nil();
node.remove_entry();
} else {
active_values += 1;
}
}
}
if table.mode_contains(self, b's') && active_values < size_node * 3 / 8 {
table.resize_hash_protected(self, active_values as i32);
}
list = table.gc_list();
}
}
work
}
unsafe fn clear_upvalues(&self) -> usize {
unsafe {
let global = self.global();
let uv_head = global.uv_head();
let mut work = 0;
let mut upvalue = uv_head.open_data().next();
while upvalue != uv_head {
work += UpVal::allocation_size();
let current_upvalue = upvalue;
let open = current_upvalue.open_data();
let object: GcObject = current_upvalue.into();
debug_assert!(current_upvalue.is_open());
debug_assert!(open.next().open_data().prev() == current_upvalue);
debug_assert!(open.prev().open_data().next() == current_upvalue);
debug_assert!(!object.is_black());
let value = current_upvalue.value();
debug_assert!(
object.is_white() || !value.is_collectable() || !value.gc_value().is_white()
);
if current_upvalue
.as_ptr()
.as_ref()
.unwrap_unchecked()
.marked_open
!= 0
{
debug_assert!(object.is_gray());
current_upvalue
.as_ptr()
.as_mut()
.unwrap_unchecked()
.marked_open = 0;
upvalue = open.next();
} else {
let next = open.next();
self.close_upvalue(current_upvalue, object.is_white());
upvalue = next;
}
}
work
}
}
pub(super) unsafe fn atomic(&self) -> usize {
unsafe {
let global = self.global();
debug_assert_eq!(global.gc_state(), crate::gc::GCS_ATOMIC);
let mut work = 0;
work += self.remark_upvalues();
work += global.propagate_all();
global.set_gray(global.weak());
global.set_weak(None);
debug_assert!(!GcObject::from(global.main_thread()).is_white());
global.mark_object(self.into());
global.mark_mt();
global.mark_userdata_type_metatables();
if flags::LuauUdataMetatablePinned.get() {
global.mark_tagged_userdata_metatables();
}
if flags::LuauGcMarkUdataAccess.get() {
global.mark_userdata_direct_access();
}
if flags::LuauDirectFieldGet.get() {
global.mark_userdata_direct_fields();
}
work += global.propagate_all();
global.set_gray(global.gray_again());
global.set_gray_again(None);
work += global.propagate_all();
if flags::LuauGcTraceUdata.get()
&& let Some(embedder_gc) = global.embedder_gc()
{
let main_thread = global.main_thread();
embedder_gc(&main_thread, Some(embedder_mark_ref));
while global.gray().is_some() {
work += global.propagate_all();
embedder_gc(&main_thread, Some(embedder_mark_ref));
}
}
work += self.clear_tables(global.weak());
global.set_weak(None);
work += self.clear_upvalues();
(&mut *global.userdata_type_registry_ptr()).remove_unreachable_metatables();
let global_mut = global.as_ptr().as_mut().unwrap_unchecked();
global_mut.current_white ^= crate::gc::WHITE_BITS;
global.set_sweep_gco_page(global.all_gco_pages());
global.set_gc_state(crate::gc::GCS_SWEEP);
work
}
}
}