use crate::barrier::SatbBuffer;
use crate::header::{GcColor, GcHeader};
use std::collections::HashSet;
use std::sync::atomic::{AtomicBool, Ordering};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MarkPhase {
Idle,
Marking,
Terminating,
Complete,
}
pub struct Marker {
worklist: Vec<*mut u8>,
live_set: HashSet<usize>,
is_marking: AtomicBool,
phase: MarkPhase,
}
unsafe impl Send for Marker {}
impl Marker {
pub fn new() -> Self {
Self {
worklist: Vec::with_capacity(1024),
live_set: HashSet::with_capacity(4096),
is_marking: AtomicBool::new(false),
phase: MarkPhase::Idle,
}
}
pub fn reset(&mut self) {
self.worklist.clear();
self.live_set.clear();
self.is_marking.store(false, Ordering::Release);
self.phase = MarkPhase::Idle;
}
pub fn start_marking(&mut self) {
self.worklist.clear();
self.live_set.clear();
self.is_marking.store(true, Ordering::Release);
self.phase = MarkPhase::Marking;
}
pub fn finish_marking(&mut self) {
self.is_marking.store(false, Ordering::Release);
self.phase = MarkPhase::Idle;
}
#[inline(always)]
pub fn is_marking(&self) -> bool {
self.is_marking.load(Ordering::Acquire)
}
pub fn phase(&self) -> MarkPhase {
self.phase
}
pub fn mark_root(&mut self, ptr: *mut u8) {
if ptr.is_null() {
return;
}
let header = Self::header_of(ptr);
if header.color() == GcColor::White {
header.set_color(GcColor::Gray);
self.worklist.push(ptr);
self.live_set.insert(ptr as usize);
}
}
pub fn mark_gray(&mut self, ptr: *mut u8) {
if ptr.is_null() {
return;
}
if self.live_set.insert(ptr as usize) {
let header = Self::header_of(ptr);
header.set_color(GcColor::Gray);
self.worklist.push(ptr);
} else {
let header = Self::header_of(ptr);
if header.color() == GcColor::White {
header.set_color(GcColor::Gray);
self.worklist.push(ptr);
}
}
}
pub fn mark_child(&mut self, ptr: *mut u8) {
if ptr.is_null() {
return;
}
let header = Self::header_of(ptr);
if header.color() == GcColor::White {
header.set_color(GcColor::Gray);
self.worklist.push(ptr);
self.live_set.insert(ptr as usize);
}
}
pub fn mark_step(&mut self, budget: usize) -> bool {
let mut processed = 0;
while processed < budget {
let Some(ptr) = self.worklist.pop() else {
return true;
};
let header = Self::header_of(ptr);
if header.color() == GcColor::Black {
continue;
}
header.set_color(GcColor::Black);
processed += 1;
}
self.worklist.is_empty()
}
pub fn mark_all(&mut self) {
while !self.mark_step(256) {}
}
pub fn terminate_marking(&mut self, satb: &mut SatbBuffer) -> bool {
self.phase = MarkPhase::Terminating;
for ptr in satb.drain() {
if !self.is_marked(ptr) {
self.mark_gray(ptr);
}
}
while let Some(obj) = self.worklist.pop() {
let header = Self::header_of(obj);
if header.color() != GcColor::Black {
header.set_color(GcColor::Black);
}
}
let terminated = self.worklist.is_empty() && satb.is_empty();
if terminated {
self.phase = MarkPhase::Complete;
}
terminated
}
pub fn is_marked(&self, ptr: *const u8) -> bool {
self.live_set.contains(&(ptr as usize))
}
pub fn is_live(&self, ptr: *const u8) -> bool {
self.is_marked(ptr)
}
pub fn gray_count(&self) -> usize {
self.worklist.len()
}
pub fn live_count(&self) -> usize {
self.live_set.len()
}
pub fn clear_mark(&self, ptr: *mut u8) {
let header = Self::header_of(ptr);
header.set_color(GcColor::White);
}
fn header_of(ptr: *mut u8) -> &'static mut GcHeader {
unsafe {
let header_ptr = ptr.sub(std::mem::size_of::<GcHeader>()) as *mut GcHeader;
&mut *header_ptr
}
}
}
impl Default for Marker {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::header::GcHeader;
fn make_fake_object() -> Vec<u8> {
let mut buf = vec![0u8; 16]; let header_ptr = buf.as_mut_ptr() as *mut GcHeader;
unsafe {
header_ptr.write(GcHeader::new(0, 8));
}
buf
}
#[test]
fn test_mark_root_colors_gray() {
let mut buf = make_fake_object();
let obj_ptr = unsafe { buf.as_mut_ptr().add(8) };
let mut marker = Marker::new();
marker.mark_root(obj_ptr);
let header = unsafe { &*(buf.as_ptr() as *const GcHeader) };
assert_eq!(header.color(), GcColor::Gray);
assert_eq!(marker.gray_count(), 1);
}
#[test]
fn test_mark_step_colors_black() {
let mut buf = make_fake_object();
let obj_ptr = unsafe { buf.as_mut_ptr().add(8) };
let mut marker = Marker::new();
marker.mark_root(obj_ptr);
let done = marker.mark_step(10);
assert!(done);
let header = unsafe { &*(buf.as_ptr() as *const GcHeader) };
assert_eq!(header.color(), GcColor::Black);
assert_eq!(marker.gray_count(), 0);
}
#[test]
fn test_null_root_ignored() {
let mut marker = Marker::new();
marker.mark_root(std::ptr::null_mut());
assert_eq!(marker.gray_count(), 0);
}
#[test]
fn test_is_marking_flag() {
let mut marker = Marker::new();
assert!(!marker.is_marking());
marker.start_marking();
assert!(marker.is_marking());
assert_eq!(marker.phase(), MarkPhase::Marking);
marker.finish_marking();
assert!(!marker.is_marking());
assert_eq!(marker.phase(), MarkPhase::Idle);
}
#[test]
fn test_mark_gray_adds_to_worklist() {
let mut buf = make_fake_object();
let obj_ptr = unsafe { buf.as_mut_ptr().add(8) };
let mut marker = Marker::new();
marker.start_marking();
marker.mark_gray(obj_ptr);
assert_eq!(marker.gray_count(), 1);
assert!(marker.is_marked(obj_ptr));
}
#[test]
fn test_mark_gray_idempotent() {
let mut buf = make_fake_object();
let obj_ptr = unsafe { buf.as_mut_ptr().add(8) };
let mut marker = Marker::new();
marker.start_marking();
marker.mark_gray(obj_ptr);
marker.mark_gray(obj_ptr);
assert_eq!(marker.live_count(), 1);
}
#[test]
fn test_incremental_mark_step_budget() {
let mut bufs: Vec<Vec<u8>> = (0..5).map(|_| make_fake_object()).collect();
let ptrs: Vec<*mut u8> = bufs
.iter_mut()
.map(|b| unsafe { b.as_mut_ptr().add(8) })
.collect();
let mut marker = Marker::new();
marker.start_marking();
for &ptr in &ptrs {
marker.mark_root(ptr);
}
assert_eq!(marker.gray_count(), 5);
let done = marker.mark_step(2);
assert!(!done);
assert_eq!(marker.gray_count(), 3);
let done = marker.mark_step(10);
assert!(done);
assert_eq!(marker.gray_count(), 0);
}
#[test]
fn test_terminate_marking_empty_satb() {
let mut buf = make_fake_object();
let obj_ptr = unsafe { buf.as_mut_ptr().add(8) };
let mut marker = Marker::new();
marker.start_marking();
marker.mark_root(obj_ptr);
marker.mark_all();
let mut satb = SatbBuffer::new(64);
let terminated = marker.terminate_marking(&mut satb);
assert!(terminated);
assert_eq!(marker.phase(), MarkPhase::Complete);
}
#[test]
fn test_terminate_marking_with_satb_entries() {
let mut buf1 = make_fake_object();
let mut buf2 = make_fake_object();
let ptr1 = unsafe { buf1.as_mut_ptr().add(8) };
let ptr2 = unsafe { buf2.as_mut_ptr().add(8) };
let mut marker = Marker::new();
marker.start_marking();
marker.mark_root(ptr1);
marker.mark_all();
let mut satb = SatbBuffer::new(64);
satb.enqueue(ptr2);
let terminated = marker.terminate_marking(&mut satb);
assert!(terminated);
assert!(marker.is_marked(ptr2));
}
#[test]
fn test_clear_mark() {
let mut buf = make_fake_object();
let obj_ptr = unsafe { buf.as_mut_ptr().add(8) };
let mut marker = Marker::new();
marker.mark_root(obj_ptr);
marker.mark_all();
let header = unsafe { &*(buf.as_ptr() as *const GcHeader) };
assert_eq!(header.color(), GcColor::Black);
marker.clear_mark(obj_ptr);
assert_eq!(header.color(), GcColor::White);
}
#[test]
fn test_full_incremental_cycle() {
let mut bufs: Vec<Vec<u8>> = (0..10).map(|_| make_fake_object()).collect();
let ptrs: Vec<*mut u8> = bufs
.iter_mut()
.map(|b| unsafe { b.as_mut_ptr().add(8) })
.collect();
let mut marker = Marker::new();
marker.start_marking();
for &ptr in &ptrs {
marker.mark_root(ptr);
}
let mut steps = 0;
while !marker.mark_step(3) {
steps += 1;
}
assert!(steps >= 1);
let mut satb = SatbBuffer::new(64);
let terminated = marker.terminate_marking(&mut satb);
assert!(terminated);
assert_eq!(marker.phase(), MarkPhase::Complete);
for &ptr in &ptrs {
assert!(marker.is_marked(ptr));
}
marker.finish_marking();
assert!(!marker.is_marking());
assert_eq!(marker.phase(), MarkPhase::Idle);
}
}