use std::{
marker::PhantomData,
ops::{Deref, DerefMut},
ptr::NonNull,
};
use crate::gctype::{GcTypeRegistry, payload_offset_of};
use crate::{GcHeap, GcPartitionId, GcTrace, GcWeak, weak::GcWeakRawId};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum GcTriColor {
White = 0b00,
Gray = 0b01,
Black = 0b10,
}
impl From<GcTriColor> for u32 {
fn from(color: GcTriColor) -> Self {
color as u32
}
}
impl TryFrom<u32> for GcTriColor {
type Error = &'static str;
fn try_from(value: u32) -> Result<Self, Self::Error> {
match value {
0b00 => Ok(GcTriColor::White),
0b01 => Ok(GcTriColor::Gray),
0b10 => Ok(GcTriColor::Black),
_ => Err("Invalid value for TriColor"),
}
}
}
const COLOR_MASK: u32 = 0b11;
bitflags::bitflags! {
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GcNodeFlag :u8 {
const ARENA_ALLOC = 1 << 2;
const GRAY_LISTED = 1 << 3;
const ROOT = 1 << 5;
const LOCAL = 1 << 6;
const TRAVERSE_VISITED = 1 << 7;
}
}
#[repr(C)]
pub struct GcHead {
pub(super) attrs: u32,
pub(super) partition: u32,
pub(super) weak_id: GcWeakRawId,
pub(super) next: Option<NonNull<GcHead>>,
#[cfg(debug_assertions)]
pub(crate) dbg_string: std::borrow::Cow<'static, str>,
}
impl std::fmt::Debug for GcHead {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut s = f.debug_struct("GcNode");
s.field("ptr", &(self as *const Self))
.field("partition", &self.partition_id())
.field("color", &self.color())
.field("local", &self.is_local());
if self.is_root() {
s.field("root", &true);
}
if !self.weak_id.is_null() {
let w = self.weak_id;
s.field("weakref", &format!("{}#{}", w.index(), w.version()));
}
#[cfg(debug_assertions)]
{
s.field("dbg_string", &self.dbg_string);
}
s.finish()
}
}
impl GcHead {
#[inline(always)]
pub(crate) fn dtype(&self) -> u8 {
((self.attrs & 0xFF00) >> 8) as u8
}
#[inline(always)]
pub(crate) fn color(&self) -> GcTriColor {
GcTriColor::try_from(self.attrs & COLOR_MASK).unwrap()
}
#[inline(always)]
pub(crate) fn set_color(&mut self, color: GcTriColor) {
self.attrs = (self.attrs & !COLOR_MASK) | (color as u32);
}
#[inline(always)]
pub(crate) fn flags(&self) -> GcNodeFlag {
GcNodeFlag::from_bits_truncate(self.attrs as u8)
}
#[inline(always)]
pub(crate) fn insert_flag(&mut self, flag: GcNodeFlag) {
self.attrs |= flag.bits() as u32;
}
#[inline(always)]
pub(crate) fn remove_flag(&mut self, flag: GcNodeFlag) {
self.attrs &= !(flag.bits() as u32);
}
#[inline(always)]
pub(crate) fn contains_flag(&self, flag: GcNodeFlag) -> bool {
(self.attrs & flag.bits() as u32) == flag.bits() as u32
}
#[inline(always)]
pub fn is_root(&self) -> bool {
self.contains_flag(GcNodeFlag::ROOT)
}
#[inline(always)]
pub fn is_local(&self) -> bool {
self.contains_flag(GcNodeFlag::LOCAL)
}
#[inline]
pub fn is_root_or_local(&self) -> bool {
let f = self.flags();
f.intersects(GcNodeFlag::ROOT | GcNodeFlag::LOCAL)
}
#[inline(always)]
pub(super) fn traverse_visited(&self) -> bool {
self.contains_flag(GcNodeFlag::TRAVERSE_VISITED)
}
#[inline(always)]
pub(super) fn set_traverse_visited(&mut self, visited: bool) {
if visited {
self.insert_flag(GcNodeFlag::TRAVERSE_VISITED);
} else {
self.remove_flag(GcNodeFlag::TRAVERSE_VISITED);
}
}
#[inline(always)]
pub(super) fn is_gray_listed(&self) -> bool {
(self.attrs & (GcNodeFlag::GRAY_LISTED.bits() as u32)) != 0
}
#[inline(always)]
pub(super) fn set_gray_listed(&mut self, b: bool) {
if b {
self.attrs |= GcNodeFlag::GRAY_LISTED.bits() as u32;
} else {
self.attrs &= !(GcNodeFlag::GRAY_LISTED.bits() as u32);
}
}
#[inline(always)]
pub fn partition_id(&self) -> GcPartitionId {
GcPartitionId((self.partition & 0x0000_FFFF) as u16)
}
#[inline(always)]
pub fn payload(&self, registry: &GcTypeRegistry) -> NonNull<u8> {
#[cfg(debug_assertions)]
self.debug_assert_node_valid_simple();
let info = ®istry.type_info_list[self.dtype() as usize];
info.payload_ptr(NonNull::from_ref(self))
}
#[inline(always)]
pub(crate) fn payload_for<T>(&self) -> NonNull<u8> {
#[cfg(debug_assertions)]
self.debug_assert_node_valid_simple();
unsafe {
NonNull::from_ref(self)
.cast::<u8>()
.add(payload_offset_of::<T>())
}
}
}
pub trait GcNode: GcTrace {
const GC_TYPE_ID: u8;
fn gc_ref(&self) -> GcRef<Self>
where
Self: std::marker::Sized;
#[inline(always)]
fn gc_head_ptr(&self) -> std::ptr::NonNull<GcHead>
where
Self: std::marker::Sized,
{
self.gc_ref().node_ptr()
}
#[inline(always)]
fn gc_head(&self) -> &GcHead
where
Self: std::marker::Sized,
{
unsafe { self.gc_head_ptr().as_ref() }
}
#[inline(always)]
fn gc_head_mut(&mut self) -> &mut GcHead
where
Self: std::marker::Sized,
{
unsafe { self.gc_head_ptr().as_mut() }
}
}
#[repr(transparent)]
pub struct GcRef<T: GcNode> {
pub(super) head_ptr: NonNull<GcHead>,
pub(super) _marker: PhantomData<T>,
}
impl<T: GcNode> Clone for GcRef<T> {
#[inline(always)]
fn clone(&self) -> Self {
*self
}
}
impl<T: GcNode> Copy for GcRef<T> {}
impl<T: GcNode> PartialEq for GcRef<T> {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
self.head_ptr == other.head_ptr
}
}
impl<T: GcNode> Eq for GcRef<T> {}
impl<T: GcNode> From<GcRef<T>> for NonNull<GcHead> {
#[inline(always)]
fn from(r: GcRef<T>) -> Self {
r.head_ptr
}
}
impl<T: GcNode> From<&GcRef<T>> for NonNull<GcHead> {
#[inline(always)]
fn from(r: &GcRef<T>) -> Self {
r.head_ptr
}
}
impl<T: GcNode> std::fmt::Debug for GcRef<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "GcRef<{:p}>", self.head_ptr)
}
}
impl<T: GcNode> GcRef<T> {
#[inline(always)]
pub unsafe fn as_ref(&self) -> &T {
unsafe {
self.head_ptr
.as_ref()
.payload_for::<T>()
.cast::<T>()
.as_ref()
}
}
#[inline(always)]
pub unsafe fn as_mut(&mut self) -> &mut T {
unsafe {
self.head_ptr
.as_mut()
.payload_for::<T>()
.cast::<T>()
.as_mut()
}
}
#[inline]
pub unsafe fn try_from_ref(heap: &GcHeap, data_ref: &T) -> Option<Self> {
let node = unsafe {
NonNull::from_ref(data_ref)
.cast::<u8>()
.sub(payload_offset_of::<T>())
.cast::<GcHead>()
};
if T::GC_TYPE_ID == unsafe { node.as_ref().dtype() } {
#[cfg(debug_assertions)]
unsafe {
node.as_ref().debug_assert_node_valid(heap);
}
Some(Self {
head_ptr: node,
_marker: PhantomData,
})
} else {
None
}
}
#[inline]
pub unsafe fn from_ref_unchecked(data_ref: &T) -> Self {
let node = unsafe {
NonNull::from_ref(data_ref)
.cast::<u8>()
.sub(payload_offset_of::<T>())
.cast::<GcHead>()
};
#[cfg(debug_assertions)]
unsafe {
node.as_ref().debug_assert_node_valid_simple();
}
Self {
head_ptr: node,
_marker: PhantomData,
}
}
#[inline]
pub unsafe fn with_write_barrier<F, R>(&mut self, heap: &mut GcHeap, mutator: F) -> R
where
F: FnOnce(&mut T) -> R,
{
if unsafe { heap.is_node_partition_marking(*self) } {
let node = unsafe { self.head_ptr.as_mut() };
if node.color() == GcTriColor::Black {
node.set_color(GcTriColor::Gray);
heap.add_gray_node(self.head_ptr);
}
}
let value = unsafe {
self.head_ptr
.as_mut()
.payload_for::<T>()
.cast::<T>()
.as_mut()
};
mutator(value)
}
#[inline]
pub unsafe fn as_ptr(&self) -> NonNull<T> {
unsafe { self.head_ptr.as_ref().payload_for::<T>().cast::<T>() }
}
#[inline(always)]
pub unsafe fn downgrade(&self, heap: &mut GcHeap) -> GcWeak<T> {
heap.downgrade(self)
}
#[inline(always)]
pub unsafe fn is_root(&self) -> bool {
unsafe { self.head_ptr.as_ref().is_root() }
}
#[inline(always)]
pub fn node_ptr(&self) -> NonNull<GcHead> {
self.head_ptr
}
#[inline(always)]
pub unsafe fn node_info(&self) -> &GcHead {
unsafe { self.head_ptr.as_ref() }
}
}
#[repr(transparent)]
pub struct Gc<'a, T: GcNode> {
inner: GcRef<T>,
_marker: PhantomData<&'a T>,
}
impl<'a, T: GcNode> Deref for Gc<'a, T> {
type Target = T;
#[inline(always)]
fn deref(&self) -> &Self::Target {
unsafe { self.inner.as_ref() }
}
}
impl<'a, T: GcNode> DerefMut for Gc<'a, T> {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { self.inner.as_mut() }
}
}
impl<'a, T: GcNode> Clone for Gc<'a, T> {
#[inline(always)]
fn clone(&self) -> Self {
*self
}
}
impl<'a, T: GcNode> Copy for Gc<'a, T> {}
impl<'a, T: GcNode> PartialEq for Gc<'a, T> {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner
}
}
impl<'a, T: GcNode> Eq for Gc<'a, T> {}
impl<'a, T: GcNode> std::fmt::Debug for Gc<'a, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Gc<{:p}>", self.inner.head_ptr)
}
}
impl<'a, T: GcNode> Gc<'a, T> {
#[inline(always)]
pub unsafe fn from_raw(inner: GcRef<T>) -> Self {
Self {
inner,
_marker: PhantomData,
}
}
#[inline(always)]
pub fn into_raw(self) -> GcRef<T> {
self.inner
}
#[inline(always)]
pub fn as_raw(&self) -> &GcRef<T> {
&self.inner
}
}
impl GcHeap {
pub fn bind(&mut self, master: NonNull<GcHead>, mut slave: NonNull<GcHead>) {
#[cfg(debug_assertions)]
unsafe {
master.as_ref().debug_assert_node_valid(self);
slave.as_ref().debug_assert_node_valid(self);
}
unsafe {
if matches!(
(master.as_ref().color(), slave.as_ref().color()),
(GcTriColor::Black, GcTriColor::White | GcTriColor::Gray)
) {
slave.as_mut().set_color(GcTriColor::Gray);
let slave_pid = slave.as_ref().partition_id();
if self.partition(slave_pid).is_some_and(|p| p.is_marking()) {
self.add_gray_node(slave);
}
}
}
unsafe {
let master_pid = master.as_ref().partition_id();
let slave_pid = slave.as_ref().partition_id();
if master_pid != slave_pid {
let slave_color = slave.as_ref().color();
if matches!(slave_color, GcTriColor::White | GcTriColor::Gray)
&& let Some(slave_par) = self.partition_mut(slave_pid)
&& slave_par.is_marking()
{
slave.as_mut().set_color(GcTriColor::Gray);
if !slave.as_ref().is_gray_listed() {
slave.as_mut().set_gray_listed(true);
slave_par.gray_list.push(slave);
}
}
}
}
}
}
#[cfg(debug_assertions)]
impl GcHead {
pub fn debug_set_dbg_string(&mut self, str: std::borrow::Cow<'static, str>) {
self.dbg_string = str;
}
pub fn debug_dbg_string(&self) -> &std::borrow::Cow<'static, str> {
&self.dbg_string
}
pub fn debug_assert_node_valid_simple(&self) {
if !std::thread::panicking() {
debug_assert!(
((self.attrs >> 24) & 0xFF) == 0xFF && self.next.is_none_or(|n| n.is_aligned()),
"bad node: {self:p}"
);
}
}
pub fn debug_assert_node_valid(&self, heap: &GcHeap) {
if !std::thread::panicking() {
debug_assert!(
heap.dbg_living_nodes.contains(&NonNull::from_ref(self)),
"[O.o] bad node: {self:p}"
);
self.debug_assert_node_valid_simple();
}
}
}