1use core::ptr::NonNull;
2
3use luau_common::{BStr, BString};
4
5use crate::Table;
6use crate::VmErrorResult;
7use crate::function::UpVal;
8use crate::handle::RawHandle;
9use crate::handle::sealed::Sealed;
10use crate::state::GlobalState;
11use crate::thread::Thread;
12use crate::value::TValue;
13
14mod atomic;
15pub(crate) mod barrier;
16mod debug;
17mod mark;
18pub(crate) mod object;
19pub(crate) mod step;
20mod sweep;
21
22pub(crate) use object::GcHandle;
23pub use object::{GcObject, RawGcObject};
24
25pub const LUA_GC_STOP: i32 = 0;
26pub const LUA_GC_RESTART: i32 = 1;
27pub const LUA_GC_COLLECT: i32 = 2;
28pub const LUA_GC_COUNT: i32 = 3;
29pub const LUA_GC_COUNT_B: i32 = 4;
30pub const LUA_GC_IS_RUNNING: i32 = 5;
31pub const LUA_GC_STEP: i32 = 6;
32pub const LUA_GC_SET_GOAL: i32 = 7;
33pub const LUA_GC_SET_STEP_MUL: i32 = 8;
34pub const LUA_GC_SET_STEP_SIZE: i32 = 9;
35pub const LUA_GC_IS_PAUSED: i32 = 10;
36
37#[derive(Default)]
38#[repr(C)]
39pub struct GcStats {
40 pub trigger_terms: [i32; 32],
41 pub trigger_term_pos: u32,
42 pub trigger_integral: i32,
43 pub atomic_start_total_size_bytes: usize,
44 pub end_total_size_bytes: usize,
45 pub heap_goal_size_bytes: usize,
46 pub start_timestamp: f64,
47 pub atomic_start_timestamp: f64,
48 pub end_timestamp: f64,
49}
50
51#[repr(C)]
52pub struct GcCycleMetrics {
53 pub start_total_size_bytes: usize,
54 pub heap_trigger_size_bytes: usize,
55 pub pause_time: f64,
56 pub start_timestamp: f64,
57 pub end_timestamp: f64,
58 pub mark_time: f64,
59 pub mark_assist_time: f64,
60 pub mark_max_explicit_time: f64,
61 pub mark_explicit_steps: usize,
62 pub mark_work: usize,
63 pub atomic_start_timestamp: f64,
64 pub atomic_start_total_size_bytes: usize,
65 pub atomic_time: f64,
66 pub atomic_time_upval: f64,
67 pub atomic_time_weak: f64,
68 pub atomic_time_gray: f64,
69 pub atomic_time_clear: f64,
70 pub sweep_time: f64,
71 pub sweep_assist_time: f64,
72 pub sweep_max_explicit_time: f64,
73 pub sweep_explicit_steps: usize,
74 pub sweep_work: usize,
75 pub assist_work: usize,
76 pub explicit_work: usize,
77 pub propagate_work: usize,
78 pub propagate_again_work: usize,
79 pub end_total_size_bytes: usize,
80}
81
82#[repr(C)]
83pub struct GcMetrics {
84 pub step_explicit_time_acc: f64,
85 pub step_assist_time_acc: f64,
86 pub completed_cycles: u64,
87 pub last_cycle: GcCycleMetrics,
88 pub curr_cycle: GcCycleMetrics,
89}
90
91pub const fn bit_mask(bit: u8) -> u8 {
92 1u8 << bit
93}
94
95pub const WHITE0_BIT: u8 = 0;
96pub const WHITE1_BIT: u8 = 1;
97pub const BLACK_BIT: u8 = 2;
98pub const FIXED_BIT: u8 = 3;
99pub const WHITE_BITS: u8 = bit_mask(WHITE0_BIT) | bit_mask(WHITE1_BIT);
100
101pub const GCS_PAUSE: u8 = 0;
102pub const GCS_PROPAGATE: u8 = 1;
103pub const GCS_PROPAGATE_AGAIN: u8 = 2;
104pub const GCS_ATOMIC: u8 = 3;
105pub const GCS_SWEEP: u8 = 4;
106
107#[allow(
108 clippy::missing_safety_doc,
109 reason = "GlobalState's shared raw-handle contract is documented on GlobalState"
110)]
111impl GlobalState {
112 pub fn gc_state(&self) -> u8 {
113 unsafe { (*self.as_ptr()).gc_state }
114 }
115
116 pub fn set_gc_state(&self, gc_state: u8) {
117 unsafe {
118 (*self.as_ptr()).gc_state = gc_state;
119 }
120 }
121
122 pub fn gray(&self) -> Option<GcObject> {
123 unsafe {
124 NonNull::new(self.as_ptr().as_ref().unwrap_unchecked().gray)
125 .map(|raw| GcObject::from_raw(raw))
126 }
127 }
128
129 pub fn set_gray(&self, gray: Option<GcObject>) {
130 unsafe {
131 self.as_ptr().as_mut().unwrap_unchecked().gray =
132 gray.map_or(core::ptr::null_mut(), |object| object.as_ptr());
133 }
134 }
135
136 pub fn gray_again(&self) -> Option<GcObject> {
137 unsafe {
138 NonNull::new(self.as_ptr().as_ref().unwrap_unchecked().gray_again)
139 .map(|raw| GcObject::from_raw(raw))
140 }
141 }
142
143 pub fn set_gray_again(&self, gray_again: Option<GcObject>) {
144 unsafe {
145 self.as_ptr().as_mut().unwrap_unchecked().gray_again =
146 gray_again.map_or(core::ptr::null_mut(), |object| object.as_ptr());
147 }
148 }
149
150 pub fn weak(&self) -> Option<GcObject> {
151 unsafe {
152 NonNull::new(self.as_ptr().as_ref().unwrap_unchecked().weak)
153 .map(|raw| GcObject::from_raw(raw))
154 }
155 }
156
157 pub fn set_weak(&self, weak: Option<GcObject>) {
158 unsafe {
159 self.as_ptr().as_mut().unwrap_unchecked().weak =
160 weak.map_or(core::ptr::null_mut(), |object| object.as_ptr());
161 }
162 }
163}
164
165pub type GcHeapNode = fn(*mut (), *mut (), u8, u8, usize, Option<&BStr>);
166pub type GcHeapEdge = fn(*mut (), *mut (), *mut (), &BStr);
167
168pub trait GcCategoryNamer {
169 fn category_name(&mut self, thread: &Thread, memcat: u8, out: &mut BString);
171}
172
173pub trait GcHeapVisitor {
174 fn node(&mut self, ptr: *mut (), tt: u8, memcat: u8, size: usize, name: Option<&BStr>);
176
177 fn edge(&mut self, from: *mut (), to: *mut (), name: &BStr);
179}
180
181#[allow(
189 clippy::missing_safety_doc,
190 reason = "all methods share the capability-level safety contract"
191)]
192pub trait GcRuntime: Sealed {
193 unsafe fn free_all(&self);
195
196 unsafe fn needs_gc(&self) -> bool;
198
199 unsafe fn check_gc(&self) -> VmErrorResult;
201
202 unsafe fn step(&self, assist: bool) -> VmErrorResult<usize>;
204
205 unsafe fn full_gc(&self);
207
208 unsafe fn validate(&self);
210
211 unsafe fn dump(&self, file: *mut (), category_name: Option<&mut dyn GcCategoryNamer>);
213
214 unsafe fn enum_heap(&self, context: *mut (), node: GcHeapNode, edge: GcHeapEdge);
216
217 unsafe fn allocation_rate(&self) -> i64;
219}
220
221#[allow(
229 clippy::missing_safety_doc,
230 reason = "all methods share the capability-level safety contract"
231)]
232pub trait GcBarrier: Sealed {
233 unsafe fn barrier_value(&self, object: GcObject, value: TValue);
235
236 unsafe fn object_barrier(&self, object: GcObject, child: GcObject);
238
239 unsafe fn thread_barrier(&self);
241
242 unsafe fn upvalue_closed(&self, upvalue: UpVal);
244
245 unsafe fn barrier_forward(&self, object: GcObject, value: GcObject);
247
248 unsafe fn barrier_table(&self, table: Table, value: GcObject);
250
251 unsafe fn barrier_back(&self, object: GcObject, gc_list: *mut *mut RawGcObject);
253}
254
255#[allow(
256 clippy::missing_safety_doc,
257 reason = "GlobalState's shared raw-handle contract is documented on GlobalState"
258)]
259impl GlobalState {
260 pub fn white(&self) -> u8 {
262 (unsafe { self.as_ptr().as_ref().unwrap_unchecked().current_white }) & WHITE_BITS
263 }
264
265 pub unsafe fn keep_invariant(&self) -> bool {
267 matches!(
268 self.gc_state(),
269 GCS_PROPAGATE | GCS_PROPAGATE_AGAIN | GCS_ATOMIC
270 )
271 }
272
273 pub unsafe fn is_dead(&self, object: GcObject) -> bool {
275 let other_white =
276 unsafe { self.as_ptr().as_ref().unwrap_unchecked().current_white ^ WHITE_BITS };
277 let marked = unsafe { object.as_ptr().as_ref().unwrap_unchecked().marked };
278 (marked & (WHITE_BITS | bit_mask(FIXED_BIT))) == (other_white & WHITE_BITS)
279 }
280
281 pub unsafe fn make_white(&self, object: GcObject) {
283 unsafe {
284 let mask_marks = !(bit_mask(BLACK_BIT) | WHITE_BITS);
285 let current_white = self.as_ptr().as_ref().unwrap_unchecked().current_white;
286 let raw = object.as_ptr().as_mut().unwrap_unchecked();
287 let new_marked = (raw.marked & mask_marks) | (current_white & WHITE_BITS);
288 raw.marked = new_marked;
289 }
290 }
291}
292
293#[allow(
294 clippy::missing_safety_doc,
295 reason = "GcObject's shared raw-handle contract is documented on GcObject"
296)]
297impl GcObject {
298 pub unsafe fn is_white(&self) -> bool {
300 (unsafe { self.as_ptr().as_ref().unwrap_unchecked().marked } & WHITE_BITS) != 0
301 }
302
303 pub unsafe fn is_black(&self) -> bool {
305 (unsafe { self.as_ptr().as_ref().unwrap_unchecked().marked } & bit_mask(BLACK_BIT)) != 0
306 }
307
308 pub unsafe fn is_gray(&self) -> bool {
310 (unsafe { self.as_ptr().as_ref().unwrap_unchecked().marked }
311 & (WHITE_BITS | bit_mask(BLACK_BIT)))
312 == 0
313 }
314
315 pub unsafe fn change_white(&mut self) {
317 unsafe { self.as_ptr().as_mut().unwrap_unchecked().marked ^= WHITE_BITS };
318 }
319
320 pub unsafe fn gray_to_black(&mut self) {
322 unsafe { self.as_ptr().as_mut().unwrap_unchecked().marked |= bit_mask(BLACK_BIT) };
323 }
324
325 pub unsafe fn white_to_gray(&mut self) {
327 unsafe { self.as_ptr().as_mut().unwrap_unchecked().marked &= !WHITE_BITS };
328 }
329
330 pub unsafe fn black_to_gray(&mut self) {
332 unsafe { self.as_ptr().as_mut().unwrap_unchecked().marked &= !bit_mask(BLACK_BIT) };
333 }
334}