Skip to main content

shape_gc/
marker.rs

1//! Tri-color mark phase for the GC.
2//!
3//! Colors:
4//! - **White**: Unmarked, potentially reclaimable after marking completes.
5//! - **Gray**: Reachable, but children not yet scanned.
6//! - **Black**: Reachable, all children scanned.
7//!
8//! Algorithm:
9//! 1. All objects start white.
10//! 2. Root objects are colored gray and pushed to the worklist.
11//! 3. Process gray objects: for each, trace children (color white->gray), color self black.
12//! 4. After worklist is empty, all white objects are garbage.
13//!
14//! ## Incremental marking
15//!
16//! Instead of processing the entire gray worklist in one STW pause, the marker
17//! exposes `mark_step(budget)` which processes a bounded number of objects per
18//! mutator safepoint.  An SATB write barrier (see `barrier.rs`) records
19//! references overwritten by the mutator during marking.  Mark termination is a
20//! short STW pause that drains the SATB buffers and re-processes any new grays
21//! until convergence.
22
23use crate::barrier::SatbBuffer;
24use crate::header::{GcColor, GcHeader};
25use std::collections::HashSet;
26use std::sync::atomic::{AtomicBool, Ordering};
27
28/// Marker phase state.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum MarkPhase {
31    /// No marking cycle is active.
32    Idle,
33    /// Incremental marking is in progress (gray objects remain).
34    Marking,
35    /// Mark termination: draining SATB buffers and finalizing.
36    Terminating,
37    /// Marking complete; ready for sweep.
38    Complete,
39}
40
41/// The marker manages the gray worklist and tri-color state.
42pub struct Marker {
43    /// Gray set (objects reachable but children not scanned).
44    worklist: Vec<*mut u8>,
45    /// Set of all objects marked live (for sweep to check).
46    live_set: HashSet<usize>,
47    /// Whether a marking cycle is currently active.
48    ///
49    /// This is an `AtomicBool` so that the write barrier can check it without
50    /// requiring a mutable reference to the Marker.
51    is_marking: AtomicBool,
52    /// Current phase of the marking cycle.
53    phase: MarkPhase,
54}
55
56// Safety: Marker is only used during stop-the-world GC.
57unsafe impl Send for Marker {}
58
59impl Marker {
60    pub fn new() -> Self {
61        Self {
62            worklist: Vec::with_capacity(1024),
63            live_set: HashSet::with_capacity(4096),
64            is_marking: AtomicBool::new(false),
65            phase: MarkPhase::Idle,
66        }
67    }
68
69    /// Reset the marker for a new GC cycle.
70    pub fn reset(&mut self) {
71        self.worklist.clear();
72        self.live_set.clear();
73        self.is_marking.store(false, Ordering::Release);
74        self.phase = MarkPhase::Idle;
75    }
76
77    // ── Phase management ────────────────────────────────────────────────
78
79    /// Begin a new incremental marking cycle.
80    ///
81    /// After calling this, the mutator should activate its SATB write barrier
82    /// (checking `is_marking()`).
83    pub fn start_marking(&mut self) {
84        self.worklist.clear();
85        self.live_set.clear();
86        self.is_marking.store(true, Ordering::Release);
87        self.phase = MarkPhase::Marking;
88    }
89
90    /// Finish the marking cycle (called after termination + sweep).
91    pub fn finish_marking(&mut self) {
92        self.is_marking.store(false, Ordering::Release);
93        self.phase = MarkPhase::Idle;
94    }
95
96    /// Check whether a marking cycle is active.
97    ///
98    /// This is the fast-path check used by the SATB write barrier.
99    #[inline(always)]
100    pub fn is_marking(&self) -> bool {
101        self.is_marking.load(Ordering::Acquire)
102    }
103
104    /// Get the current mark phase.
105    pub fn phase(&self) -> MarkPhase {
106        self.phase
107    }
108
109    // ── Root and child marking ──────────────────────────────────────────
110
111    /// Mark a root pointer: color it gray and add to worklist.
112    pub fn mark_root(&mut self, ptr: *mut u8) {
113        if ptr.is_null() {
114            return;
115        }
116        let header = Self::header_of(ptr);
117        if header.color() == GcColor::White {
118            header.set_color(GcColor::Gray);
119            self.worklist.push(ptr);
120            self.live_set.insert(ptr as usize);
121        }
122    }
123
124    /// Mark a pointer as gray (add to worklist) if it is currently white.
125    ///
126    /// Used during SATB termination to re-gray old references.
127    pub fn mark_gray(&mut self, ptr: *mut u8) {
128        if ptr.is_null() {
129            return;
130        }
131        // Only add to live_set / worklist if not already known-live.
132        if self.live_set.insert(ptr as usize) {
133            let header = Self::header_of(ptr);
134            header.set_color(GcColor::Gray);
135            self.worklist.push(ptr);
136        } else {
137            // Already in live set — but if it was somehow reset to white
138            // (shouldn't normally happen), re-gray it.
139            let header = Self::header_of(ptr);
140            if header.color() == GcColor::White {
141                header.set_color(GcColor::Gray);
142                self.worklist.push(ptr);
143            }
144        }
145    }
146
147    /// Mark a child pointer discovered during tracing.
148    /// If it's white, color it gray and add to the worklist.
149    pub fn mark_child(&mut self, ptr: *mut u8) {
150        if ptr.is_null() {
151            return;
152        }
153        let header = Self::header_of(ptr);
154        if header.color() == GcColor::White {
155            header.set_color(GcColor::Gray);
156            self.worklist.push(ptr);
157            self.live_set.insert(ptr as usize);
158        }
159    }
160
161    // ── Incremental mark step ───────────────────────────────────────────
162
163    /// Process up to `budget` gray objects.
164    ///
165    /// Returns `true` if the worklist is empty (marking phase complete for
166    /// this step), `false` if more work remains.
167    ///
168    /// Each processed object is colored black.  In a full implementation the
169    /// `trace_object` callback would push the object's children as gray via
170    /// `mark_child`; for now objects are treated as opaque blobs and simply
171    /// colored black.
172    pub fn mark_step(&mut self, budget: usize) -> bool {
173        let mut processed = 0;
174        while processed < budget {
175            let Some(ptr) = self.worklist.pop() else {
176                return true;
177            };
178
179            let header = Self::header_of(ptr);
180
181            // Already fully scanned
182            if header.color() == GcColor::Black {
183                continue;
184            }
185
186            // Trace children — for now, objects are opaque blobs.
187            // The real tracing happens via the Trace trait implementations
188            // which call mark_child() for each inner pointer.
189            // Here we just color self black.
190            header.set_color(GcColor::Black);
191            processed += 1;
192        }
193        self.worklist.is_empty()
194    }
195
196    /// Process all remaining gray objects until the worklist is empty.
197    pub fn mark_all(&mut self) {
198        while !self.mark_step(256) {}
199    }
200
201    // ── Mark termination (short STW) ────────────────────────────────────
202
203    /// Terminate the marking cycle by draining the SATB buffer and processing
204    /// any remaining gray objects.
205    ///
206    /// This should be called in a short STW pause after incremental marking
207    /// reports the worklist as empty.
208    ///
209    /// Returns `true` if marking is truly complete (gray set empty AND SATB
210    /// buffer empty), `false` if new work was discovered (caller should loop).
211    pub fn terminate_marking(&mut self, satb: &mut SatbBuffer) -> bool {
212        self.phase = MarkPhase::Terminating;
213
214        // Drain SATB buffer — any old reference that was overwritten must be
215        // treated as potentially live.
216        for ptr in satb.drain() {
217            if !self.is_marked(ptr) {
218                self.mark_gray(ptr);
219            }
220        }
221
222        // Process all remaining grays (those from SATB + any stragglers).
223        while let Some(obj) = self.worklist.pop() {
224            let header = Self::header_of(obj);
225            if header.color() != GcColor::Black {
226                header.set_color(GcColor::Black);
227            }
228            // In a full implementation: trace_object(obj) pushes children.
229        }
230
231        let terminated = self.worklist.is_empty() && satb.is_empty();
232        if terminated {
233            self.phase = MarkPhase::Complete;
234        }
235        terminated
236    }
237
238    // ── Query helpers ───────────────────────────────────────────────────
239
240    /// Check if a pointer was marked live in this cycle.
241    pub fn is_marked(&self, ptr: *const u8) -> bool {
242        self.live_set.contains(&(ptr as usize))
243    }
244
245    /// Alias for `is_marked` — consistent naming with the public API.
246    pub fn is_live(&self, ptr: *const u8) -> bool {
247        self.is_marked(ptr)
248    }
249
250    /// Get the number of objects currently in the gray worklist.
251    pub fn gray_count(&self) -> usize {
252        self.worklist.len()
253    }
254
255    /// Get the number of objects marked live.
256    pub fn live_count(&self) -> usize {
257        self.live_set.len()
258    }
259
260    /// Clear the mark bit (reset to White) for a specific pointer.
261    ///
262    /// Used during sweep to prepare objects for the next cycle.
263    pub fn clear_mark(&self, ptr: *mut u8) {
264        let header = Self::header_of(ptr);
265        header.set_color(GcColor::White);
266    }
267
268    // ── Internal helpers ────────────────────────────────────────────────
269
270    /// Get a mutable reference to the GcHeader before an object pointer.
271    ///
272    /// # Safety
273    /// `ptr` must be a valid GC-managed object pointer (preceded by GcHeader).
274    fn header_of(ptr: *mut u8) -> &'static mut GcHeader {
275        unsafe {
276            let header_ptr = ptr.sub(std::mem::size_of::<GcHeader>()) as *mut GcHeader;
277            &mut *header_ptr
278        }
279    }
280}
281
282impl Default for Marker {
283    fn default() -> Self {
284        Self::new()
285    }
286}
287
288#[cfg(test)]
289mod tests {
290    use super::*;
291    use crate::header::GcHeader;
292
293    fn make_fake_object() -> Vec<u8> {
294        let mut buf = vec![0u8; 16]; // 8 header + 8 data
295        let header_ptr = buf.as_mut_ptr() as *mut GcHeader;
296        unsafe {
297            header_ptr.write(GcHeader::new(0, 8));
298        }
299        buf
300    }
301
302    #[test]
303    fn test_mark_root_colors_gray() {
304        let mut buf = make_fake_object();
305        let obj_ptr = unsafe { buf.as_mut_ptr().add(8) };
306
307        let mut marker = Marker::new();
308        marker.mark_root(obj_ptr);
309
310        let header = unsafe { &*(buf.as_ptr() as *const GcHeader) };
311        assert_eq!(header.color(), GcColor::Gray);
312        assert_eq!(marker.gray_count(), 1);
313    }
314
315    #[test]
316    fn test_mark_step_colors_black() {
317        let mut buf = make_fake_object();
318        let obj_ptr = unsafe { buf.as_mut_ptr().add(8) };
319
320        let mut marker = Marker::new();
321        marker.mark_root(obj_ptr);
322        let done = marker.mark_step(10);
323        assert!(done);
324
325        let header = unsafe { &*(buf.as_ptr() as *const GcHeader) };
326        assert_eq!(header.color(), GcColor::Black);
327        assert_eq!(marker.gray_count(), 0);
328    }
329
330    #[test]
331    fn test_null_root_ignored() {
332        let mut marker = Marker::new();
333        marker.mark_root(std::ptr::null_mut());
334        assert_eq!(marker.gray_count(), 0);
335    }
336
337    #[test]
338    fn test_is_marking_flag() {
339        let mut marker = Marker::new();
340        assert!(!marker.is_marking());
341
342        marker.start_marking();
343        assert!(marker.is_marking());
344        assert_eq!(marker.phase(), MarkPhase::Marking);
345
346        marker.finish_marking();
347        assert!(!marker.is_marking());
348        assert_eq!(marker.phase(), MarkPhase::Idle);
349    }
350
351    #[test]
352    fn test_mark_gray_adds_to_worklist() {
353        let mut buf = make_fake_object();
354        let obj_ptr = unsafe { buf.as_mut_ptr().add(8) };
355
356        let mut marker = Marker::new();
357        marker.start_marking();
358        marker.mark_gray(obj_ptr);
359
360        assert_eq!(marker.gray_count(), 1);
361        assert!(marker.is_marked(obj_ptr));
362    }
363
364    #[test]
365    fn test_mark_gray_idempotent() {
366        let mut buf = make_fake_object();
367        let obj_ptr = unsafe { buf.as_mut_ptr().add(8) };
368
369        let mut marker = Marker::new();
370        marker.start_marking();
371        marker.mark_gray(obj_ptr);
372        marker.mark_gray(obj_ptr); // second time — should not add again
373
374        assert_eq!(marker.live_count(), 1);
375    }
376
377    #[test]
378    fn test_incremental_mark_step_budget() {
379        // Create 5 fake objects
380        let mut bufs: Vec<Vec<u8>> = (0..5).map(|_| make_fake_object()).collect();
381        let ptrs: Vec<*mut u8> = bufs
382            .iter_mut()
383            .map(|b| unsafe { b.as_mut_ptr().add(8) })
384            .collect();
385
386        let mut marker = Marker::new();
387        marker.start_marking();
388        for &ptr in &ptrs {
389            marker.mark_root(ptr);
390        }
391        assert_eq!(marker.gray_count(), 5);
392
393        // Process 2 out of 5
394        let done = marker.mark_step(2);
395        assert!(!done);
396        // Should have 3 remaining
397        assert_eq!(marker.gray_count(), 3);
398
399        // Process remaining
400        let done = marker.mark_step(10);
401        assert!(done);
402        assert_eq!(marker.gray_count(), 0);
403    }
404
405    #[test]
406    fn test_terminate_marking_empty_satb() {
407        let mut buf = make_fake_object();
408        let obj_ptr = unsafe { buf.as_mut_ptr().add(8) };
409
410        let mut marker = Marker::new();
411        marker.start_marking();
412        marker.mark_root(obj_ptr);
413        marker.mark_all();
414
415        let mut satb = SatbBuffer::new(64);
416        let terminated = marker.terminate_marking(&mut satb);
417        assert!(terminated);
418        assert_eq!(marker.phase(), MarkPhase::Complete);
419    }
420
421    #[test]
422    fn test_terminate_marking_with_satb_entries() {
423        let mut buf1 = make_fake_object();
424        let mut buf2 = make_fake_object();
425        let ptr1 = unsafe { buf1.as_mut_ptr().add(8) };
426        let ptr2 = unsafe { buf2.as_mut_ptr().add(8) };
427
428        let mut marker = Marker::new();
429        marker.start_marking();
430        marker.mark_root(ptr1);
431        marker.mark_all();
432
433        // Simulate: mutator overwrote a reference to ptr2 during marking
434        let mut satb = SatbBuffer::new(64);
435        satb.enqueue(ptr2);
436
437        let terminated = marker.terminate_marking(&mut satb);
438        assert!(terminated);
439        // ptr2 should now be marked live (saved by SATB)
440        assert!(marker.is_marked(ptr2));
441    }
442
443    #[test]
444    fn test_clear_mark() {
445        let mut buf = make_fake_object();
446        let obj_ptr = unsafe { buf.as_mut_ptr().add(8) };
447
448        let mut marker = Marker::new();
449        marker.mark_root(obj_ptr);
450        marker.mark_all();
451
452        let header = unsafe { &*(buf.as_ptr() as *const GcHeader) };
453        assert_eq!(header.color(), GcColor::Black);
454
455        marker.clear_mark(obj_ptr);
456        assert_eq!(header.color(), GcColor::White);
457    }
458
459    #[test]
460    fn test_full_incremental_cycle() {
461        // Simulate: start marking -> incremental steps -> termination -> verify
462        let mut bufs: Vec<Vec<u8>> = (0..10).map(|_| make_fake_object()).collect();
463        let ptrs: Vec<*mut u8> = bufs
464            .iter_mut()
465            .map(|b| unsafe { b.as_mut_ptr().add(8) })
466            .collect();
467
468        let mut marker = Marker::new();
469        marker.start_marking();
470
471        // Root scanning
472        for &ptr in &ptrs {
473            marker.mark_root(ptr);
474        }
475
476        // Incremental steps with budget 3
477        let mut steps = 0;
478        while !marker.mark_step(3) {
479            steps += 1;
480        }
481        // Should have taken multiple steps
482        assert!(steps >= 1);
483
484        // Termination
485        let mut satb = SatbBuffer::new(64);
486        let terminated = marker.terminate_marking(&mut satb);
487        assert!(terminated);
488        assert_eq!(marker.phase(), MarkPhase::Complete);
489
490        // All objects should be marked live
491        for &ptr in &ptrs {
492            assert!(marker.is_marked(ptr));
493        }
494
495        marker.finish_marking();
496        assert!(!marker.is_marking());
497        assert_eq!(marker.phase(), MarkPhase::Idle);
498    }
499}