use crate::header::{GcColor, GcHeader};
use crate::region::Region;
use std::collections::HashMap;
pub struct ForwardingTable {
table: HashMap<usize, *mut u8>,
}
unsafe impl Send for ForwardingTable {}
unsafe impl Sync for ForwardingTable {}
impl ForwardingTable {
pub fn new() -> Self {
Self {
table: HashMap::with_capacity(4096),
}
}
pub fn insert(&mut self, old: *mut u8, new: *mut u8) {
self.table.insert(old as usize, new);
}
pub fn lookup(&self, old: *const u8) -> Option<*mut u8> {
self.table.get(&(old as usize)).copied()
}
pub fn len(&self) -> usize {
self.table.len()
}
pub fn is_empty(&self) -> bool {
self.table.is_empty()
}
pub fn clear(&mut self) {
self.table.clear();
}
pub fn iter(&self) -> impl Iterator<Item = (*mut u8, *mut u8)> + '_ {
self.table.iter().map(|(&old, &new)| (old as *mut u8, new))
}
}
impl Default for ForwardingTable {
fn default() -> Self {
Self::new()
}
}
pub struct Relocator {
forwarding_table: ForwardingTable,
}
impl Relocator {
pub fn new() -> Self {
Self {
forwarding_table: ForwardingTable::new(),
}
}
pub fn install_forwarding(&mut self, old_addr: *mut u8, new_addr: *mut u8) {
self.forwarding_table.insert(old_addr, new_addr);
}
pub fn resolve(&self, ptr: *mut u8) -> *mut u8 {
self.forwarding_table
.lookup(ptr as *const u8)
.unwrap_or(ptr)
}
pub fn compact_region(&mut self, source: &Region, target: &mut Region) -> usize {
let header_size = std::mem::size_of::<GcHeader>();
let mut moved = 0;
source.for_each_object(|header, old_obj_ptr| {
if header.color() != GcColor::Black {
return;
}
let obj_size = header.size as usize;
let layout = std::alloc::Layout::from_size_align(obj_size, 8).unwrap();
if let Some(new_obj_ptr) = target.try_alloc(layout) {
unsafe {
std::ptr::copy_nonoverlapping(old_obj_ptr, new_obj_ptr, obj_size);
}
let new_header =
unsafe { &mut *((new_obj_ptr as *mut u8).sub(header_size) as *mut GcHeader) };
*new_header = *header;
new_header.set_color(GcColor::White);
let old_header =
unsafe { &mut *((old_obj_ptr as *mut u8).sub(header_size) as *mut GcHeader) };
old_header.set_forwarded(true);
self.forwarding_table.insert(old_obj_ptr, new_obj_ptr);
moved += 1;
}
});
moved
}
pub fn forwarding_table(&self) -> &ForwardingTable {
&self.forwarding_table
}
pub fn forwarding_table_mut(&mut self) -> &mut ForwardingTable {
&mut self.forwarding_table
}
pub fn reset(&mut self) {
self.forwarding_table.clear();
}
}
impl Default for Relocator {
fn default() -> Self {
Self::new()
}
}
pub fn relocate_region(source: &Region, target: &mut Region) -> ForwardingTable {
let mut relocator = Relocator::new();
relocator.compact_region(source, target);
let mut ft = ForwardingTable::new();
std::mem::swap(&mut ft, relocator.forwarding_table_mut());
ft
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_forwarding_table_insert_lookup() {
let mut ft = ForwardingTable::new();
let old = 0x1000 as *mut u8;
let new = 0x2000 as *mut u8;
ft.insert(old, new);
assert_eq!(ft.lookup(old), Some(new));
assert_eq!(ft.lookup(0x3000 as *const u8), None);
assert_eq!(ft.len(), 1);
}
#[test]
fn test_forwarding_table_overwrite() {
let mut ft = ForwardingTable::new();
let old = 0x1000 as *mut u8;
ft.insert(old, 0x2000 as *mut u8);
ft.insert(old, 0x3000 as *mut u8);
assert_eq!(ft.lookup(old), Some(0x3000 as *mut u8));
assert_eq!(ft.len(), 1);
}
#[test]
fn test_forwarding_table_clear() {
let mut ft = ForwardingTable::new();
ft.insert(0x1000 as *mut u8, 0x2000 as *mut u8);
ft.insert(0x3000 as *mut u8, 0x4000 as *mut u8);
assert_eq!(ft.len(), 2);
ft.clear();
assert!(ft.is_empty());
assert_eq!(ft.len(), 0);
assert_eq!(ft.lookup(0x1000 as *const u8), None);
}
#[test]
fn test_forwarding_table_iter() {
let mut ft = ForwardingTable::new();
ft.insert(0x1000 as *mut u8, 0x2000 as *mut u8);
ft.insert(0x3000 as *mut u8, 0x4000 as *mut u8);
let entries: Vec<_> = ft.iter().collect();
assert_eq!(entries.len(), 2);
}
#[test]
fn test_relocator_install_and_resolve() {
let mut relocator = Relocator::new();
let old = 0x1000 as *mut u8;
let new = 0x2000 as *mut u8;
assert_eq!(relocator.resolve(old), old);
relocator.install_forwarding(old, new);
assert_eq!(relocator.resolve(old), new);
let unknown = 0x9000 as *mut u8;
assert_eq!(relocator.resolve(unknown), unknown);
}
#[test]
fn test_relocator_reset() {
let mut relocator = Relocator::new();
relocator.install_forwarding(0x1000 as *mut u8, 0x2000 as *mut u8);
assert_eq!(relocator.forwarding_table().len(), 1);
relocator.reset();
assert!(relocator.forwarding_table().is_empty());
}
#[test]
fn test_relocator_compact_region() {
let mut source = Region::new();
let layout = std::alloc::Layout::from_size_align(64, 8).unwrap();
let obj1 = source.try_alloc(layout).unwrap();
let obj2 = source.try_alloc(layout).unwrap();
let obj3 = source.try_alloc(layout).unwrap();
unsafe {
std::ptr::write_bytes(obj1, 0xAA, 64);
std::ptr::write_bytes(obj2, 0xBB, 64);
std::ptr::write_bytes(obj3, 0xCC, 64);
}
let header_size = std::mem::size_of::<GcHeader>();
let h1 = unsafe { &mut *((obj1 as *mut u8).sub(header_size) as *mut GcHeader) };
let h3 = unsafe { &mut *((obj3 as *mut u8).sub(header_size) as *mut GcHeader) };
h1.set_color(GcColor::Black);
h3.set_color(GcColor::Black);
let mut target = Region::new();
let mut relocator = Relocator::new();
let moved = relocator.compact_region(&source, &mut target);
assert_eq!(moved, 2);
assert_eq!(relocator.forwarding_table().len(), 2);
let new_obj1 = relocator.resolve(obj1);
let new_obj3 = relocator.resolve(obj3);
assert_ne!(new_obj1, obj1); assert_ne!(new_obj3, obj3);
assert_eq!(relocator.resolve(obj2), obj2);
unsafe {
assert_eq!(*new_obj1, 0xAA);
assert_eq!(*new_obj3, 0xCC);
}
let new_h1 = unsafe { &*((new_obj1 as *mut u8).sub(header_size) as *const GcHeader) };
let new_h3 = unsafe { &*((new_obj3 as *mut u8).sub(header_size) as *const GcHeader) };
assert_eq!(new_h1.color(), GcColor::White);
assert_eq!(new_h3.color(), GcColor::White);
assert!(h1.is_forwarded());
assert!(h3.is_forwarded());
}
#[test]
fn test_relocator_compact_region_empty_source() {
let source = Region::new();
let mut target = Region::new();
let mut relocator = Relocator::new();
let moved = relocator.compact_region(&source, &mut target);
assert_eq!(moved, 0);
assert!(relocator.forwarding_table().is_empty());
}
#[test]
fn test_relocator_compact_region_all_dead() {
let mut source = Region::new();
let layout = std::alloc::Layout::from_size_align(32, 8).unwrap();
let _obj1 = source.try_alloc(layout).unwrap();
let _obj2 = source.try_alloc(layout).unwrap();
let mut target = Region::new();
let mut relocator = Relocator::new();
let moved = relocator.compact_region(&source, &mut target);
assert_eq!(moved, 0);
assert!(relocator.forwarding_table().is_empty());
}
#[test]
fn test_relocate_region_compatibility() {
let mut source = Region::new();
let layout = std::alloc::Layout::from_size_align(32, 8).unwrap();
let obj = source.try_alloc(layout).unwrap();
let header_size = std::mem::size_of::<GcHeader>();
let h = unsafe { &mut *((obj as *mut u8).sub(header_size) as *mut GcHeader) };
h.set_color(GcColor::Black);
let mut target = Region::new();
let ft = relocate_region(&source, &mut target);
assert_eq!(ft.len(), 1);
assert!(ft.lookup(obj).is_some());
}
}