use std::{cell::Cell, ptr::NonNull};
use crate::{GcHead, GcHeap, node::GcTriColor, node_link::GcNodeLink};
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct GcPartitionId(pub u16);
impl GcPartitionId {
pub const NONE: Self = Self(0);
#[inline(always)]
pub const fn is_null(&self) -> bool {
self.0 == 0
}
}
impl std::fmt::Debug for GcPartitionId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug)]
pub struct GcPartition {
pub(crate) nodes: GcNodeLink,
pub(crate) gray_list: Vec<NonNull<GcHead>>,
marking: bool,
pub(crate) memory_used: usize,
}
impl GcPartition {
fn new() -> Self {
Self {
memory_used: 0,
nodes: GcNodeLink::default(),
gray_list: Vec::new(),
marking: false,
}
}
#[inline(always)]
pub fn memory_used(&self) -> usize {
self.memory_used
}
#[inline(always)]
pub const fn is_marking(&self) -> bool {
self.marking
}
#[inline(always)]
pub(crate) const fn set_marking(&mut self, marking: bool) {
self.marking = marking;
}
pub(crate) fn add_gray_node(&mut self, mut node: NonNull<GcHead>) {
debug_assert!(self.is_marking());
match unsafe { node.as_ref().color() } {
GcTriColor::White => unsafe {
node.as_mut().set_color(GcTriColor::Gray);
},
GcTriColor::Gray => {}
GcTriColor::Black => {
return;
}
}
if !self.gray_list.contains(&node) {
self.gray_list.push(node);
}
}
}
impl GcHeap {
pub fn create_partition(&mut self) -> GcPartitionId {
thread_local! {
static NEXT_PARTITION_ID: Cell<u16> = const { Cell::new(1) };
}
let id = NEXT_PARTITION_ID.with(|next_id| {
let mut serial = next_id.get();
if serial == 0 {
serial = 1;
}
let start = serial;
loop {
let candidate = GcPartitionId(serial);
let conflict = self.partitions.contains_key(&candidate);
if !conflict {
let next = if serial == u16::MAX { 1 } else { serial + 1 };
next_id.set(next);
return candidate;
}
serial = if serial == u16::MAX { 1 } else { serial + 1 };
if serial == start {
panic!("too many active partitions");
}
}
});
let partition = GcPartition::new();
self.partitions.insert(id, partition);
log::trace!("[new_scope] {id:?}");
id
}
pub fn remove_partition(
&mut self,
partition_id: GcPartitionId,
on_dispose: impl Fn(&GcHeap, &GcHead),
) -> usize {
if partition_id.is_null() {
return 0;
}
#[cfg(debug_assertions)]
{
self.dbg_dropping_root_partition = Some(partition_id);
}
log::trace!("[close_scope] {partition_id:?}");
let mut freed_bytes = 0;
if let Some(mut par) = self.partitions.remove(&partition_id) {
let link = std::mem::take(&mut par.nodes);
freed_bytes += self.dispose_all_nodes(link, &on_dispose);
}
log::trace!("[close_scope_done] {partition_id:?}");
#[cfg(debug_assertions)]
{
self.dbg_dropping_root_partition = None;
}
freed_bytes
}
#[inline(always)]
pub fn partition(&self, partition_id: GcPartitionId) -> Option<&GcPartition> {
self.partitions.get(&partition_id)
}
#[inline(always)]
pub fn partition_mut(&mut self, partition_id: GcPartitionId) -> Option<&mut GcPartition> {
self.partitions.get_mut(&partition_id)
}
pub fn partition_ids(&self) -> Vec<GcPartitionId> {
self.partitions.keys().copied().collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
struct DummyType;
impl crate::trace::GcTrace for DummyType {
fn trace(&self, _: &mut crate::trace::GcTraceCtx) {}
}
crate::gc_type_register! {
DummyType, drop_pass = 0;
}
#[test]
fn test_partition_creation() {
let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
let id = heap.create_partition();
let partition = heap.partition(id).unwrap();
assert_eq!(partition.memory_used(), 0);
heap.remove_partition(id, |_, n| {
println!("dispose: {n:?}");
});
assert!(heap.partition(id).is_none());
}
#[test]
fn test_gc_threshold() {
let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
heap.set_memory_limit(1024);
assert_eq!(heap.gc_threshold(), 0);
heap.set_gc_threshold(512);
assert_eq!(heap.gc_threshold(), 512);
heap.set_gc_threshold(2048);
assert_eq!(heap.gc_threshold(), 819);
heap.set_gc_threshold(0);
assert_eq!(heap.gc_threshold(), 0);
}
#[test]
fn test_memory_limit() {
let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
let id = heap.create_partition();
assert_eq!(heap.memory_limit(), 0);
heap.update_mem_use(id, 100);
assert_eq!(heap.memory_limit(), 0);
let applied = heap.set_memory_limit(512);
assert_eq!(applied, 512);
assert_eq!(heap.memory_limit(), 512);
let applied_small = heap.set_memory_limit(80);
assert_eq!(applied_small, 100);
assert_eq!(heap.memory_limit(), 100);
let applied_zero = heap.set_memory_limit(0);
assert_eq!(applied_zero, 0);
assert_eq!(heap.memory_limit(), 0);
}
#[test]
fn test_is_ancestor_of() {
}
#[test]
fn test_common_parent() {
}
#[test]
fn test_update_mem_use() {
let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
let p1 = heap.create_partition();
let p2 = heap.create_partition();
heap.update_mem_use(p1, 100);
assert_eq!(heap.partition(p1).unwrap().memory_used(), 100);
assert_eq!(heap.partition(p2).unwrap().memory_used(), 0);
heap.update_mem_use(p2, 50);
assert_eq!(heap.partition(p1).unwrap().memory_used(), 100);
assert_eq!(heap.partition(p2).unwrap().memory_used(), 50);
heap.update_mem_use(p1, -20);
assert_eq!(heap.partition(p1).unwrap().memory_used(), 80);
assert_eq!(heap.partition(p2).unwrap().memory_used(), 50);
heap.remove_partition(p1, GcHeap::DUMMY_DISPOSE_CALLBACK);
}
#[test]
fn test_partition_id_serial_and_range() {
let id = GcPartitionId(10);
assert_eq!(id.0, 10);
assert!(!id.is_null());
assert!(GcPartitionId::NONE.is_null());
}
}