use core::ptr::NonNull;
use luau_common::{BStr, BString};
use crate::Table;
use crate::VmErrorResult;
use crate::function::UpVal;
use crate::handle::RawHandle;
use crate::handle::sealed::Sealed;
use crate::state::GlobalState;
use crate::thread::Thread;
use crate::value::TValue;
mod atomic;
pub(crate) mod barrier;
mod debug;
mod mark;
pub(crate) mod object;
pub(crate) mod step;
mod sweep;
pub(crate) use object::GcHandle;
pub use object::{GcObject, RawGcObject};
pub const LUA_GC_STOP: i32 = 0;
pub const LUA_GC_RESTART: i32 = 1;
pub const LUA_GC_COLLECT: i32 = 2;
pub const LUA_GC_COUNT: i32 = 3;
pub const LUA_GC_COUNT_B: i32 = 4;
pub const LUA_GC_IS_RUNNING: i32 = 5;
pub const LUA_GC_STEP: i32 = 6;
pub const LUA_GC_SET_GOAL: i32 = 7;
pub const LUA_GC_SET_STEP_MUL: i32 = 8;
pub const LUA_GC_SET_STEP_SIZE: i32 = 9;
pub const LUA_GC_IS_PAUSED: i32 = 10;
#[derive(Default)]
#[repr(C)]
pub struct GcStats {
pub trigger_terms: [i32; 32],
pub trigger_term_pos: u32,
pub trigger_integral: i32,
pub atomic_start_total_size_bytes: usize,
pub end_total_size_bytes: usize,
pub heap_goal_size_bytes: usize,
pub start_timestamp: f64,
pub atomic_start_timestamp: f64,
pub end_timestamp: f64,
}
#[repr(C)]
pub struct GcCycleMetrics {
pub start_total_size_bytes: usize,
pub heap_trigger_size_bytes: usize,
pub pause_time: f64,
pub start_timestamp: f64,
pub end_timestamp: f64,
pub mark_time: f64,
pub mark_assist_time: f64,
pub mark_max_explicit_time: f64,
pub mark_explicit_steps: usize,
pub mark_work: usize,
pub atomic_start_timestamp: f64,
pub atomic_start_total_size_bytes: usize,
pub atomic_time: f64,
pub atomic_time_upval: f64,
pub atomic_time_weak: f64,
pub atomic_time_gray: f64,
pub atomic_time_clear: f64,
pub sweep_time: f64,
pub sweep_assist_time: f64,
pub sweep_max_explicit_time: f64,
pub sweep_explicit_steps: usize,
pub sweep_work: usize,
pub assist_work: usize,
pub explicit_work: usize,
pub propagate_work: usize,
pub propagate_again_work: usize,
pub end_total_size_bytes: usize,
}
#[repr(C)]
pub struct GcMetrics {
pub step_explicit_time_acc: f64,
pub step_assist_time_acc: f64,
pub completed_cycles: u64,
pub last_cycle: GcCycleMetrics,
pub curr_cycle: GcCycleMetrics,
}
pub const fn bit_mask(bit: u8) -> u8 {
1u8 << bit
}
pub const WHITE0_BIT: u8 = 0;
pub const WHITE1_BIT: u8 = 1;
pub const BLACK_BIT: u8 = 2;
pub const FIXED_BIT: u8 = 3;
pub const WHITE_BITS: u8 = bit_mask(WHITE0_BIT) | bit_mask(WHITE1_BIT);
pub const GCS_PAUSE: u8 = 0;
pub const GCS_PROPAGATE: u8 = 1;
pub const GCS_PROPAGATE_AGAIN: u8 = 2;
pub const GCS_ATOMIC: u8 = 3;
pub const GCS_SWEEP: u8 = 4;
#[allow(
clippy::missing_safety_doc,
reason = "GlobalState's shared raw-handle contract is documented on GlobalState"
)]
impl GlobalState {
pub fn gc_state(&self) -> u8 {
unsafe { (*self.as_ptr()).gc_state }
}
pub fn set_gc_state(&self, gc_state: u8) {
unsafe {
(*self.as_ptr()).gc_state = gc_state;
}
}
pub fn gray(&self) -> Option<GcObject> {
unsafe {
NonNull::new(self.as_ptr().as_ref().unwrap_unchecked().gray)
.map(|raw| GcObject::from_raw(raw))
}
}
pub fn set_gray(&self, gray: Option<GcObject>) {
unsafe {
self.as_ptr().as_mut().unwrap_unchecked().gray =
gray.map_or(core::ptr::null_mut(), |object| object.as_ptr());
}
}
pub fn gray_again(&self) -> Option<GcObject> {
unsafe {
NonNull::new(self.as_ptr().as_ref().unwrap_unchecked().gray_again)
.map(|raw| GcObject::from_raw(raw))
}
}
pub fn set_gray_again(&self, gray_again: Option<GcObject>) {
unsafe {
self.as_ptr().as_mut().unwrap_unchecked().gray_again =
gray_again.map_or(core::ptr::null_mut(), |object| object.as_ptr());
}
}
pub fn weak(&self) -> Option<GcObject> {
unsafe {
NonNull::new(self.as_ptr().as_ref().unwrap_unchecked().weak)
.map(|raw| GcObject::from_raw(raw))
}
}
pub fn set_weak(&self, weak: Option<GcObject>) {
unsafe {
self.as_ptr().as_mut().unwrap_unchecked().weak =
weak.map_or(core::ptr::null_mut(), |object| object.as_ptr());
}
}
}
pub type GcHeapNode = fn(*mut (), *mut (), u8, u8, usize, Option<&BStr>);
pub type GcHeapEdge = fn(*mut (), *mut (), *mut (), &BStr);
pub trait GcCategoryNamer {
fn category_name(&mut self, thread: &Thread, memcat: u8, out: &mut BString);
}
pub trait GcHeapVisitor {
fn node(&mut self, ptr: *mut (), tt: u8, memcat: u8, size: usize, name: Option<&BStr>);
fn edge(&mut self, from: *mut (), to: *mut (), name: &BStr);
}
#[allow(
clippy::missing_safety_doc,
reason = "all methods share the capability-level safety contract"
)]
pub trait GcRuntime: Sealed {
unsafe fn free_all(&self);
unsafe fn needs_gc(&self) -> bool;
unsafe fn check_gc(&self) -> VmErrorResult;
unsafe fn step(&self, assist: bool) -> VmErrorResult<usize>;
unsafe fn full_gc(&self);
unsafe fn validate(&self);
unsafe fn dump(&self, file: *mut (), category_name: Option<&mut dyn GcCategoryNamer>);
unsafe fn enum_heap(&self, context: *mut (), node: GcHeapNode, edge: GcHeapEdge);
unsafe fn allocation_rate(&self) -> i64;
}
#[allow(
clippy::missing_safety_doc,
reason = "all methods share the capability-level safety contract"
)]
pub trait GcBarrier: Sealed {
unsafe fn barrier_value(&self, object: GcObject, value: TValue);
unsafe fn object_barrier(&self, object: GcObject, child: GcObject);
unsafe fn thread_barrier(&self);
unsafe fn upvalue_closed(&self, upvalue: UpVal);
unsafe fn barrier_forward(&self, object: GcObject, value: GcObject);
unsafe fn barrier_table(&self, table: Table, value: GcObject);
unsafe fn barrier_back(&self, object: GcObject, gc_list: *mut *mut RawGcObject);
}
#[allow(
clippy::missing_safety_doc,
reason = "GlobalState's shared raw-handle contract is documented on GlobalState"
)]
impl GlobalState {
pub fn white(&self) -> u8 {
(unsafe { self.as_ptr().as_ref().unwrap_unchecked().current_white }) & WHITE_BITS
}
pub unsafe fn keep_invariant(&self) -> bool {
matches!(
self.gc_state(),
GCS_PROPAGATE | GCS_PROPAGATE_AGAIN | GCS_ATOMIC
)
}
pub unsafe fn is_dead(&self, object: GcObject) -> bool {
let other_white =
unsafe { self.as_ptr().as_ref().unwrap_unchecked().current_white ^ WHITE_BITS };
let marked = unsafe { object.as_ptr().as_ref().unwrap_unchecked().marked };
(marked & (WHITE_BITS | bit_mask(FIXED_BIT))) == (other_white & WHITE_BITS)
}
pub unsafe fn make_white(&self, object: GcObject) {
unsafe {
let mask_marks = !(bit_mask(BLACK_BIT) | WHITE_BITS);
let current_white = self.as_ptr().as_ref().unwrap_unchecked().current_white;
let raw = object.as_ptr().as_mut().unwrap_unchecked();
let new_marked = (raw.marked & mask_marks) | (current_white & WHITE_BITS);
raw.marked = new_marked;
}
}
}
#[allow(
clippy::missing_safety_doc,
reason = "GcObject's shared raw-handle contract is documented on GcObject"
)]
impl GcObject {
pub unsafe fn is_white(&self) -> bool {
(unsafe { self.as_ptr().as_ref().unwrap_unchecked().marked } & WHITE_BITS) != 0
}
pub unsafe fn is_black(&self) -> bool {
(unsafe { self.as_ptr().as_ref().unwrap_unchecked().marked } & bit_mask(BLACK_BIT)) != 0
}
pub unsafe fn is_gray(&self) -> bool {
(unsafe { self.as_ptr().as_ref().unwrap_unchecked().marked }
& (WHITE_BITS | bit_mask(BLACK_BIT)))
== 0
}
pub unsafe fn change_white(&mut self) {
unsafe { self.as_ptr().as_mut().unwrap_unchecked().marked ^= WHITE_BITS };
}
pub unsafe fn gray_to_black(&mut self) {
unsafe { self.as_ptr().as_mut().unwrap_unchecked().marked |= bit_mask(BLACK_BIT) };
}
pub unsafe fn white_to_gray(&mut self) {
unsafe { self.as_ptr().as_mut().unwrap_unchecked().marked &= !WHITE_BITS };
}
pub unsafe fn black_to_gray(&mut self) {
unsafe { self.as_ptr().as_mut().unwrap_unchecked().marked &= !bit_mask(BLACK_BIT) };
}
}