pub mod major;
pub mod minor;
use std::collections::{HashMap, VecDeque};
use std::fmt;
use crate::process::{Process, heap::HeapFull};
use crate::term::{
Term,
boxed::{BoxedHeader, BoxedTag},
};
pub const MAJOR_SHRINK_THRESHOLD: f64 = 0.25;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct GcStats {
pub copied_objects: usize,
pub copied_words: usize,
pub young_before: usize,
pub old_before: usize,
pub young_after: usize,
pub old_after: usize,
}
impl GcStats {
fn new(process: &Process) -> Self {
Self {
copied_objects: 0,
copied_words: 0,
young_before: process.heap().young_used(),
old_before: process.heap().old_used(),
young_after: process.heap().young_used(),
old_after: process.heap().old_used(),
}
}
fn finish(&mut self, process: &Process) {
self.young_after = process.heap().young_used();
self.old_after = process.heap().old_used();
}
pub(crate) fn record_copy(&mut self, words: usize) {
self.copied_objects += 1;
self.copied_words += words;
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum GcError {
HeapFull(HeapFull),
InvalidObjectHeader(u64),
}
impl fmt::Display for GcError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::HeapFull(error) => write!(f, "{error}"),
Self::InvalidObjectHeader(header) => {
write!(f, "invalid boxed object header word {header:#x}")
}
}
}
}
impl std::error::Error for GcError {}
impl From<HeapFull> for GcError {
fn from(error: HeapFull) -> Self {
Self::HeapFull(error)
}
}
pub(crate) type ForwardingMap = HashMap<usize, Term>;
pub fn collect_minor(process: &mut Process) -> Result<GcStats, GcError> {
collect_minor_with_live(process, 256)
}
pub fn collect_minor_with_live(process: &mut Process, live_x: usize) -> Result<GcStats, GcError> {
minor::collect(process, live_x)
}
pub fn collect_major(process: &mut Process) -> Result<GcStats, GcError> {
major::collect(process)
}
pub fn alloc(process: &mut Process, words: usize) -> Result<*mut u64, GcError> {
match process.heap_mut().alloc(words) {
Ok(ptr) => return Ok(ptr),
Err(_heap_full) => {}
}
ensure_space(process, words, 256)?;
process.heap_mut().alloc(words).map_err(GcError::from)
}
pub fn ensure_space(process: &mut Process, words: usize, live_x: usize) -> Result<(), GcError> {
if process.heap().available() >= words {
return Ok(());
}
match collect_minor_with_live(process, live_x) {
Ok(_stats) => {}
Err(GcError::HeapFull(_)) => {
collect_major(process)?;
}
Err(error) => return Err(error),
}
if process.heap().available() >= words {
return Ok(());
}
while process.heap().available() < words {
process.heap_mut().grow_to_next_capacity_with_max()?;
}
Ok(())
}
pub(crate) fn new_stats(process: &Process) -> GcStats {
GcStats::new(process)
}
pub(crate) fn finish_stats(stats: &mut GcStats, process: &Process) {
stats.finish(process);
}
pub(crate) fn object_size(term: Term) -> Result<Option<usize>, GcError> {
if term.is_list() {
return Ok(Some(2));
}
if !term.is_boxed() {
return Ok(None);
}
let Some(ptr) = term.heap_ptr() else {
return Ok(None);
};
let header = unsafe { *ptr };
let Some(_tag) = BoxedHeader::tag(header) else {
return Err(GcError::InvalidObjectHeader(header));
};
Ok(Some(1 + BoxedHeader::size(header)))
}
pub(crate) fn term_from_ptr_like(original: Term, ptr: *const u64) -> Term {
if original.is_list() {
Term::list_ptr(ptr)
} else {
Term::boxed_ptr(ptr)
}
}
pub(crate) fn rewrite_copied_object(
term: Term,
work_queue: &mut VecDeque<Term>,
mut copy_term: impl FnMut(Term, &mut VecDeque<Term>) -> Result<Term, GcError>,
) -> Result<(), GcError> {
let Some(ptr) = term.heap_ptr() else {
return Ok(());
};
if term.is_list() {
rewrite_word(ptr, 0, work_queue, &mut copy_term)?;
rewrite_word(ptr, 1, work_queue, &mut copy_term)?;
return Ok(());
}
let header = read_raw_word(ptr, 0);
let Some(tag) = BoxedHeader::tag(header) else {
return Err(GcError::InvalidObjectHeader(header));
};
match tag {
BoxedTag::Tuple => {
for offset in 1..=BoxedHeader::size(header) {
rewrite_word(ptr, offset, work_queue, &mut copy_term)?;
}
}
BoxedTag::Closure => {
let num_free = read_raw_word(ptr, 4) as usize;
for index in 0..num_free {
rewrite_word(ptr, 7 + index, work_queue, &mut copy_term)?;
}
}
BoxedTag::Map => {
let len = read_raw_word(ptr, 1) as usize;
for offset in 2..(2 + len * 2) {
rewrite_word(ptr, offset, work_queue, &mut copy_term)?;
}
}
BoxedTag::MatchContext => rewrite_word(ptr, 3, work_queue, &mut copy_term)?,
BoxedTag::Float
| BoxedTag::BigInt
| BoxedTag::Reference
| BoxedTag::Binary
| BoxedTag::BinaryBuilder => {}
}
Ok(())
}
fn rewrite_word(
ptr: *const u64,
offset: usize,
work_queue: &mut VecDeque<Term>,
copy_term: &mut impl FnMut(Term, &mut VecDeque<Term>) -> Result<Term, GcError>,
) -> Result<(), GcError> {
let field = Term::from_raw(read_raw_word(ptr, offset));
let rewritten = copy_term(field, work_queue)?;
if rewritten.raw() != field.raw() {
write_raw_word(ptr, offset, rewritten.raw());
}
Ok(())
}
fn read_raw_word(ptr: *const u64, offset: usize) -> u64 {
unsafe { *ptr.add(offset) }
}
fn write_raw_word(ptr: *const u64, offset: usize, value: u64) {
unsafe { *(ptr as *mut u64).add(offset) = value }
}
#[cfg(test)]
pub(crate) mod tests;