use std::alloc::Layout;
use std::collections::HashSet;
use std::mem::align_of;
use crate::gc::heap::{OldSpace, SemiSpace};
use crate::objects::heap_object::HeapObject;
pub const PROMOTION_AGE: u8 = 3;
pub struct RememberedSet {
slots: HashSet<*mut HeapObject>,
}
unsafe impl Send for RememberedSet {}
impl RememberedSet {
pub fn new() -> Self {
Self {
slots: HashSet::new(),
}
}
pub fn insert(&mut self, old_ptr: *mut HeapObject) {
self.slots.insert(old_ptr);
}
pub fn remove(&mut self, old_ptr: *mut HeapObject) {
self.slots.remove(&old_ptr);
}
pub fn iter(&self) -> impl Iterator<Item = *mut HeapObject> + '_ {
self.slots.iter().copied()
}
pub fn clear(&mut self) {
self.slots.clear();
}
pub fn len(&self) -> usize {
self.slots.len()
}
pub fn is_empty(&self) -> bool {
self.slots.is_empty()
}
}
impl Default for RememberedSet {
fn default() -> Self {
Self::new()
}
}
pub struct Scavenger<'heap> {
semi_space: &'heap mut SemiSpace,
old_space: &'heap mut OldSpace,
}
impl<'heap> Scavenger<'heap> {
pub fn new(semi_space: &'heap mut SemiSpace, old_space: &'heap mut OldSpace) -> Self {
Self {
semi_space,
old_space,
}
}
pub unsafe fn scavenge(
&mut self,
roots: &mut [*mut *mut HeapObject],
remembered_set: &mut RememberedSet,
) {
for slot in roots.iter_mut() {
let ptr = unsafe { **slot };
if !ptr.is_null() && self.semi_space.is_in_from_space(ptr) {
let new_ptr = unsafe { self.copy_object(ptr) };
unsafe { **slot = new_ptr };
}
}
let rem_ptrs: Vec<*mut HeapObject> = remembered_set.iter().collect();
for old_ptr in rem_ptrs {
if !old_ptr.is_null() && self.semi_space.is_in_from_space(old_ptr) {
unsafe { self.copy_object(old_ptr) };
}
}
remembered_set.clear();
let to_base = self.semi_space.to_space_base() as usize;
let mut scan = to_base;
loop {
let used = self.semi_space.to_space_used();
if scan >= to_base + used {
break;
}
let obj = scan as *mut HeapObject;
let size = unsafe { (*obj).alloc_size() as usize };
if size == 0 {
break;
}
scan += size;
}
self.semi_space.collect();
}
unsafe fn copy_object(&mut self, from_ptr: *mut HeapObject) -> *mut HeapObject {
if unsafe { (*from_ptr).is_forwarded() } {
return unsafe { (*from_ptr).forwarding_ptr() };
}
let alloc_size = unsafe { (*from_ptr).alloc_size() as usize };
let age = unsafe { (*from_ptr).age() };
let layout = Layout::from_size_align(alloc_size, align_of::<HeapObject>())
.expect("alloc_size and HeapObject alignment must produce a valid Layout");
let alloc_to_space = |ss: &mut SemiSpace| -> *mut HeapObject {
ss.bump_alloc_to_space(layout)
.map(|p| p as *mut HeapObject)
.unwrap_or(std::ptr::null_mut())
};
let dest: *mut HeapObject = if age >= PROMOTION_AGE {
match self.old_space.bump_alloc(layout) {
Some(ptr) => ptr as *mut HeapObject,
None => alloc_to_space(self.semi_space),
}
} else {
alloc_to_space(self.semi_space)
};
if dest.is_null() {
return from_ptr;
}
unsafe {
std::ptr::copy_nonoverlapping(from_ptr as *const u8, dest as *mut u8, alloc_size)
};
unsafe { (*dest).increment_age() };
unsafe { (*from_ptr).set_forwarding_ptr(dest) };
dest
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::gc::heap::{OldSpace, SemiSpace};
use crate::objects::heap_object::HeapObject;
use std::alloc::Layout;
fn alloc_in_from(semi: &mut SemiSpace) -> *mut HeapObject {
let layout = Layout::new::<HeapObject>();
let raw = semi.bump_alloc(layout).expect("from-space has space");
unsafe { std::ptr::write_bytes(raw, 0, layout.size()) };
let obj = raw as *mut HeapObject;
unsafe { (*obj).init_alloc_size(layout.size() as u32) };
obj
}
#[test]
fn test_remembered_set_insert_and_clear() {
let mut rs = RememberedSet::new();
let mut obj = HeapObject::new_null();
let ptr = &raw mut obj;
assert!(rs.is_empty());
rs.insert(ptr);
assert_eq!(rs.len(), 1);
rs.clear();
assert!(rs.is_empty());
}
#[test]
fn test_remembered_set_remove() {
let mut rs = RememberedSet::new();
let mut obj = HeapObject::new_null();
let ptr = &raw mut obj;
rs.insert(ptr);
rs.remove(ptr);
assert!(rs.is_empty());
}
#[test]
fn test_remembered_set_duplicate_insert_is_idempotent() {
let mut rs = RememberedSet::new();
let mut obj = HeapObject::new_null();
let ptr = &raw mut obj;
rs.insert(ptr);
rs.insert(ptr);
assert_eq!(rs.len(), 1, "duplicate inserts must not grow the set");
}
#[test]
fn test_scavenge_live_object_root_is_updated() {
let mut semi = SemiSpace::new(4096);
let mut old = OldSpace::new(65536);
let obj_ptr = alloc_in_from(&mut semi);
let original_addr = obj_ptr as usize;
let mut root: *mut HeapObject = obj_ptr;
let mut roots = [&raw mut root as *mut *mut HeapObject];
let mut rs = RememberedSet::new();
unsafe {
Scavenger::new(&mut semi, &mut old).scavenge(&mut roots, &mut rs);
}
let new_addr = root as usize;
assert_ne!(
new_addr, original_addr,
"root must point to the copy, not the original"
);
assert!(
semi.used() > 0,
"live object must appear in the new from-space"
);
}
#[test]
fn test_scavenge_dead_objects_do_not_extend_new_from_space() {
let mut semi = SemiSpace::new(4096);
let mut old = OldSpace::new(65536);
let live_ptr = alloc_in_from(&mut semi);
let _dead_ptr = alloc_in_from(&mut semi);
let mut root: *mut HeapObject = live_ptr;
let mut roots = [&raw mut root as *mut *mut HeapObject];
let mut rs = RememberedSet::new();
unsafe {
Scavenger::new(&mut semi, &mut old).scavenge(&mut roots, &mut rs);
}
let obj_size = std::mem::size_of::<HeapObject>();
assert_eq!(
semi.used(),
obj_size,
"only the live object must be in the new from-space"
);
}
#[test]
fn test_scavenge_two_roots_to_same_object_get_same_copy() {
let mut semi = SemiSpace::new(4096);
let mut old = OldSpace::new(65536);
let obj_ptr = alloc_in_from(&mut semi);
let mut root1: *mut HeapObject = obj_ptr;
let mut root2: *mut HeapObject = obj_ptr;
let mut roots = [
&raw mut root1 as *mut *mut HeapObject,
&raw mut root2 as *mut *mut HeapObject,
];
let mut rs = RememberedSet::new();
unsafe {
Scavenger::new(&mut semi, &mut old).scavenge(&mut roots, &mut rs);
}
assert_eq!(
root1, root2,
"two roots to the same object must both point to the single copy"
);
let obj_size = std::mem::size_of::<HeapObject>();
assert_eq!(
semi.used(),
obj_size,
"shared object must be copied exactly once"
);
}
#[test]
fn test_scavenge_promotes_old_enough_object_to_old_space() {
let mut semi = SemiSpace::new(4096);
let mut old = OldSpace::new(65536);
let obj_ptr = alloc_in_from(&mut semi);
for _ in 0..PROMOTION_AGE {
unsafe { (*obj_ptr).increment_age() };
}
let mut root: *mut HeapObject = obj_ptr;
let mut roots = [&raw mut root as *mut *mut HeapObject];
let mut rs = RememberedSet::new();
let old_used_before = old.used();
unsafe {
Scavenger::new(&mut semi, &mut old).scavenge(&mut roots, &mut rs);
}
assert!(
old.used() > old_used_before,
"promoted object must be in old-space"
);
assert_eq!(
semi.used(),
0,
"promoted object must not appear in the young from-space"
);
}
#[test]
fn test_scavenge_increments_age_on_copy() {
let mut semi = SemiSpace::new(4096);
let mut old = OldSpace::new(65536);
let obj_ptr = alloc_in_from(&mut semi);
assert_eq!(unsafe { (*obj_ptr).age() }, 0);
let mut root: *mut HeapObject = obj_ptr;
let mut roots = [&raw mut root as *mut *mut HeapObject];
let mut rs = RememberedSet::new();
unsafe {
Scavenger::new(&mut semi, &mut old).scavenge(&mut roots, &mut rs);
}
assert_eq!(
unsafe { (*root).age() },
1,
"copied object's age must be incremented by 1"
);
}
#[test]
fn test_scavenge_null_root_is_skipped() {
let mut semi = SemiSpace::new(4096);
let mut old = OldSpace::new(65536);
let mut root: *mut HeapObject = std::ptr::null_mut();
let mut roots = [&raw mut root as *mut *mut HeapObject];
let mut rs = RememberedSet::new();
unsafe {
Scavenger::new(&mut semi, &mut old).scavenge(&mut roots, &mut rs);
}
assert!(root.is_null(), "null root must remain null");
}
#[test]
fn test_scavenge_clears_remembered_set() {
let mut semi = SemiSpace::new(4096);
let mut old = OldSpace::new(65536);
let mut rs = RememberedSet::new();
let mut dummy = HeapObject::new_null();
rs.insert(&raw mut dummy);
assert!(!rs.is_empty());
let mut roots: [*mut *mut HeapObject; 0] = [];
unsafe {
Scavenger::new(&mut semi, &mut old).scavenge(&mut roots, &mut rs);
}
assert!(
rs.is_empty(),
"remembered set must be cleared after scavenge"
);
}
#[test]
fn test_multiple_scavenge_cycles_age_accumulates() {
let mut semi = SemiSpace::new(4096);
let mut old = OldSpace::new(65536);
let first_ptr = alloc_in_from(&mut semi);
let mut root: *mut HeapObject = first_ptr;
let mut rs = RememberedSet::new();
for expected_age in 1..PROMOTION_AGE {
let mut roots = [&raw mut root as *mut *mut HeapObject];
unsafe {
Scavenger::new(&mut semi, &mut old).scavenge(&mut roots, &mut rs);
}
assert_eq!(
unsafe { (*root).age() },
expected_age,
"age after {expected_age} cycle(s) must be {expected_age}"
);
}
}
}