use std::{cell::Cell, fmt};
const MARK_MASK: u32 = 1 << (u32::BITS - 1);
const NON_ROOTS_MASK: u32 = !MARK_MASK;
const NON_ROOTS_MAX: u32 = NON_ROOTS_MASK;
pub(crate) struct GcHeader {
ref_count: Cell<u32>,
non_root_count: Cell<u32>,
}
impl GcHeader {
pub(crate) fn new() -> Self {
Self {
ref_count: Cell::new(1),
non_root_count: Cell::new(0),
}
}
pub(crate) fn ref_count(&self) -> u32 {
self.ref_count.get()
}
pub(crate) fn non_root_count(&self) -> u32 {
self.non_root_count.get() & NON_ROOTS_MASK
}
pub(crate) fn inc_non_root_count(&self) {
let non_root_count = self.non_root_count.get() & NON_ROOTS_MASK;
if non_root_count < self.ref_count.get() {
self.non_root_count
.set(self.non_root_count.get().wrapping_add(1));
} else {
debug_assert_eq!(
non_root_count,
self.ref_count.get(),
"non_root_count exceeded ref_count: state corruption detected \
(only reachable via direct field writes that bypass the saturation cap)"
);
}
}
pub(crate) fn reset_non_root_count(&self) {
self.non_root_count
.set(self.non_root_count.get() & !NON_ROOTS_MASK);
}
pub(crate) fn is_marked(&self) -> bool {
self.non_root_count.get() & MARK_MASK != 0
}
pub(crate) fn inc_ref_count(&self) {
#[cold]
#[inline(never)]
fn overflow_panic() {
panic!("too many references to a gc allocation");
}
let count = self.ref_count.get().wrapping_add(1);
if count == 0 || count > NON_ROOTS_MAX {
overflow_panic();
}
self.ref_count.set(count);
}
pub(crate) fn dec_ref_count(&self) {
self.ref_count.set(self.ref_count.get() - 1);
}
pub(crate) fn is_rooted(&self) -> bool {
self.non_root_count() < self.ref_count()
}
pub(crate) fn mark(&self) {
self.non_root_count
.set(self.non_root_count.get() | MARK_MASK);
}
pub(crate) fn unmark(&self) {
self.non_root_count
.set(self.non_root_count.get() & !MARK_MASK);
}
}
impl fmt::Debug for GcHeader {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("GcHeader")
.field("marked", &self.is_marked())
.field("ref_count", &self.ref_count.get())
.field("non_root_count", &self.non_root_count())
.finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mark_bit_preserved() {
let header = GcHeader::new();
header.mark();
assert!(header.is_marked());
header.inc_non_root_count();
assert!(header.is_marked());
assert_eq!(header.non_root_count(), 1);
header.inc_non_root_count();
assert!(header.is_marked());
assert_eq!(header.non_root_count(), 1);
}
#[test]
fn reset_preserves_mark() {
let header = GcHeader::new();
header.inc_non_root_count();
header.mark();
header.reset_non_root_count();
assert_eq!(header.non_root_count(), 0);
assert!(header.is_marked());
}
#[test]
#[should_panic(expected = "too many references to a gc allocation")]
fn inc_ref_panics() {
let header = GcHeader::new();
header.ref_count.set(NON_ROOTS_MAX);
header.inc_ref_count();
}
#[test]
fn is_rooted_before_saturation() {
let header = GcHeader::new();
header.inc_ref_count();
header.inc_non_root_count();
assert!(header.is_rooted());
header.inc_non_root_count();
assert!(!header.is_rooted());
}
#[test]
fn saturation_at_higher_ref_count() {
let header = GcHeader::new();
header.inc_ref_count();
header.inc_ref_count();
header.inc_non_root_count();
header.inc_non_root_count();
header.inc_non_root_count(); header.inc_non_root_count(); assert_eq!(header.non_root_count(), 3);
assert!(!header.is_rooted());
}
#[test]
fn unmark_preserves_non_root_count() {
let header = GcHeader::new();
header.inc_ref_count();
header.inc_non_root_count();
header.mark();
header.unmark();
assert_eq!(header.non_root_count(), 1);
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "non_root_count exceeded ref_count: state corruption detected")]
fn debug_assert_fires_when_non_root_exceeds_ref_count() {
let header = GcHeader::new();
header.non_root_count.set(2);
header.inc_non_root_count(); }
}