use std::rc::Rc;
use crate::bytecode::{CodeUnit, CodeUnitId, DYNAMIC};
#[derive(Default)]
pub struct DynamicUnits {
slots: Vec<Option<Rc<CodeUnit>>>,
free: Vec<u32>,
#[cfg(feature = "gc-stress")]
quarantine: Vec<u32>,
pinned: Vec<u32>,
marks: Vec<u64>,
live: usize,
live_bytes: usize,
}
#[inline]
fn bit(index: u32) -> (usize, u64) {
((index / 64) as usize, 1u64 << (index % 64))
}
impl DynamicUnits {
pub(crate) fn publish(&mut self, mut unit: CodeUnit) -> (CodeUnitId, usize) {
unit.atoms = unit.key_atoms();
let bytes = unit.bytes();
let unit = Some(Rc::new(unit));
let index = match self.free.pop() {
Some(index) => {
self.slots[index as usize] = unit;
index
}
None => {
let index = u32::try_from(self.slots.len())
.ok()
.filter(|&index| index < DYNAMIC)
.expect("dynamic code overflow");
self.slots.push(unit);
if index as usize / 64 == self.marks.len() {
self.marks.push(0);
}
index
}
};
self.pinned.push(index);
self.live += 1;
self.live_bytes += bytes;
(CodeUnitId(index | DYNAMIC), bytes)
}
#[inline]
#[track_caller]
pub fn get(&self, id: CodeUnitId) -> Rc<CodeUnit> {
Rc::clone(self.slot(id).expect("a live dynamic unit"))
}
pub fn lookup(&self, id: CodeUnitId) -> Option<&CodeUnit> {
self.slot(id).map(|unit| &**unit)
}
#[inline]
fn slot(&self, id: CodeUnitId) -> Option<&Rc<CodeUnit>> {
debug_assert!(id.is_dynamic());
self.slots.get((id.0 & !DYNAMIC) as usize)?.as_ref()
}
pub fn units(&self) -> impl Iterator<Item = (CodeUnitId, &CodeUnit)> {
self.slots.iter().enumerate().filter_map(|(index, unit)| {
let unit = unit.as_deref()?;
Some((CodeUnitId(index as u32 | DYNAMIC), unit))
})
}
pub fn len(&self) -> usize {
self.live
}
pub fn is_empty(&self) -> bool {
self.live == 0
}
pub fn live_bytes(&self) -> usize {
self.live_bytes
}
pub(crate) fn pin_mark(&self) -> usize {
self.pinned.len()
}
pub(crate) fn unpin(&mut self, mark: usize) {
self.pinned.truncate(mark);
}
pub(crate) fn pinned(&self) -> &[u32] {
&self.pinned
}
pub(crate) fn clear_marks(&mut self) {
self.marks.fill(0);
}
#[inline]
pub(crate) fn mark(&mut self, id: CodeUnitId) -> Option<&CodeUnit> {
let index = id.0 & !DYNAMIC;
let (word, mask) = bit(index);
let bits = &mut self.marks[word];
if *bits & mask != 0 {
return None;
}
*bits |= mask;
let unit = self.slots[index as usize].as_deref();
debug_assert!(unit.is_some(), "a record names freed dynamic unit {index}");
unit
}
pub(crate) fn is_marked(&self, index: u32) -> bool {
let (word, mask) = bit(index);
self.marks[word] & mask != 0
}
pub(crate) fn sweep(&mut self, mut dead: impl FnMut(&CodeUnit)) -> usize {
#[cfg(feature = "gc-stress")]
let held = {
let released = std::mem::take(&mut self.quarantine);
self.free.extend_from_slice(&released);
self.free.len()
};
let mut freed = 0;
for index in (0..self.slots.len() as u32).rev() {
if self.is_marked(index) {
continue;
}
let Some(unit) = self.slots[index as usize].take() else {
continue;
};
debug_assert_eq!(
Rc::strong_count(&unit),
1,
"a swept dynamic unit is still running"
);
dead(&unit);
freed += unit.bytes();
self.live -= 1;
self.free.push(index);
}
self.live_bytes -= freed;
#[cfg(feature = "gc-stress")]
self.quarantine.extend(self.free.drain(held..));
freed
}
pub(crate) fn verify(&self) {
for (index, unit) in self.slots.iter().enumerate() {
let index = index as u32;
assert_eq!(
unit.is_some(),
self.is_marked(index),
"gc verify: dynamic unit {index} is {}",
if unit.is_some() {
"live but unreachable"
} else {
"reachable but freed"
}
);
}
}
}