#![allow(dead_code)]
#![allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
use super::gc_codec::{le_u32, NULL_REF};
use crate::canon::{CanonicalTypeId, RefKind};
use crate::trap::Trap;
use crate::value::{AnyRef, Rooted, Val};
use crate::Result;
pub(crate) const ABORT_SAFETY_CAP: usize = 1 << 30;
const I31_TAG: u32 = 1 << 31;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum AnyRefHandle {
I31(i32),
Slot(u32),
}
pub(crate) fn anyref_handle_i31(value: i32) -> u32 {
I31_TAG | (value as u32 & !I31_TAG)
}
pub(crate) fn anyref_handle_slot(index: u32) -> u32 {
debug_assert!(
index & I31_TAG == 0,
"slot index overflows the 31-bit handle range"
);
index
}
pub(crate) fn decode_anyref_handle(handle: u32) -> AnyRefHandle {
if handle & I31_TAG == 0 {
AnyRefHandle::Slot(handle)
} else {
let payload = handle & !I31_TAG; AnyRefHandle::I31(((payload << 1) as i32) >> 1) }
}
pub(crate) fn anyref_value(handle: u32) -> Val {
Val::AnyRef(Some(Rooted::<AnyRef>::from_raw(handle, RefKind::Any)))
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum ObjKind {
Struct,
Array,
Extern,
}
#[derive(Debug)]
pub(crate) struct GcHeader {
pub type_id: CanonicalTypeId,
pub kind: ObjKind,
}
#[derive(Debug)]
pub(crate) struct GcObject {
pub header: GcHeader,
pub data: Box<[u8]>,
}
impl GcObject {
pub(crate) fn new_struct(type_id: CanonicalTypeId, data: Box<[u8]>) -> Self {
GcObject {
header: header(type_id, ObjKind::Struct),
data,
}
}
pub(crate) fn new_array(type_id: CanonicalTypeId, data: Box<[u8]>) -> Self {
GcObject {
header: header(type_id, ObjKind::Array),
data,
}
}
pub(crate) fn extern_wrapper(idx: u32) -> Self {
let header = header(CanonicalTypeId::new(u32::MAX), ObjKind::Extern);
GcObject {
header,
data: idx.to_le_bytes().to_vec().into_boxed_slice(),
}
}
pub(crate) fn extern_index(&self) -> Option<u32> {
match self.header.kind {
ObjKind::Extern => self.data.get(..4).map(le_u32),
ObjKind::Struct | ObjKind::Array => None,
}
}
pub(crate) fn array_len(&self, stride: usize) -> u32 {
(self.data.len() / stride.max(1)) as u32
}
pub(crate) fn byte_size(&self) -> usize {
OBJECT_OVERHEAD.saturating_add(self.data.len())
}
}
const OBJECT_OVERHEAD: usize =
core::mem::size_of::<Option<GcObject>>() + core::mem::size_of::<u32>() + 16;
pub(crate) fn object_charge(data_len: usize) -> usize {
OBJECT_OVERHEAD.saturating_add(data_len)
}
fn header(type_id: CanonicalTypeId, kind: ObjKind) -> GcHeader {
GcHeader { type_id, kind }
}
const RESERVE_BATCH: usize = 64 * 1024;
#[derive(Debug)]
pub(crate) struct GcHeap {
slots: Vec<Option<GcObject>>,
generations: Vec<u32>,
free: Vec<u32>,
used: usize,
reserved: usize,
reservation: usize,
next_grow: usize,
marks: Vec<u64>,
collecting: bool,
}
impl GcHeap {
pub(crate) fn byte_size(&self) -> usize {
self.used
}
pub(crate) fn new(collecting: bool, reservation: usize) -> Self {
GcHeap {
slots: Vec::new(),
generations: Vec::new(),
free: Vec::new(),
marks: Vec::new(),
used: 0,
reserved: 0,
reservation: reservation.min(ABORT_SAFETY_CAP),
next_grow: RESERVE_BATCH,
collecting,
}
}
pub(crate) fn is_collecting(&self) -> bool {
self.collecting
}
pub(crate) fn charge(&mut self, bytes: usize) {
self.used = self.used.saturating_add(bytes);
}
pub(crate) fn credit(&mut self, bytes: usize) {
self.used = self.used.saturating_sub(bytes);
}
pub(crate) fn reserved(&self) -> usize {
self.reserved
}
pub(crate) fn is_free_grant(&self, target: usize) -> bool {
target <= self.reservation
}
pub(crate) fn footprint_over_floor(&self) -> bool {
self.used >= RESERVE_BATCH
}
pub(crate) fn fits(&self, size: usize) -> bool {
self.used
.checked_add(size)
.is_some_and(|n| n <= self.reserved)
}
fn unreserved_ceiling(&self) -> usize {
self.reserved.max(self.reservation).max(ABORT_SAFETY_CAP)
}
pub(crate) fn can_fit_limit(&self, size: usize) -> bool {
self.used
.checked_add(size)
.is_some_and(|n| n <= self.unreserved_ceiling())
}
pub(crate) fn desired_reservation(&self, size: usize) -> usize {
let need = self.used.saturating_add(size);
self.reserved.saturating_add(self.next_grow).max(need)
}
pub(crate) fn grant(&mut self, target: usize) -> usize {
let old = self.reserved;
self.reserved = target;
let step_cap = self.reservation.max(RESERVE_BATCH);
self.next_grow = self.next_grow.saturating_mul(2).min(step_cap);
self.reserved.saturating_sub(old)
}
pub(crate) fn alloc(&mut self, object: GcObject) -> Result<u32> {
let size = object.byte_size();
if let Some(index) = self.free.pop() {
debug_assert!(
self.slots[index as usize].is_none(),
"reusing a live gc slot"
);
self.slots[index as usize] = Some(object);
self.used = self.used.saturating_add(size);
return Ok(index);
}
if self.slots.len() as u64 >= u64::from(NULL_REF) {
return Err(Trap::AllocationTooLarge.into());
}
let index = self.slots.len() as u32;
self.slots.push(Some(object));
self.generations.push(0); self.used = self.used.saturating_add(size);
Ok(index)
}
pub(crate) fn alloc_unreserved(&mut self, object: GcObject) -> Result<u32> {
if !self.can_fit_limit(object.byte_size()) {
return Err(Trap::AllocationTooLarge.into());
}
self.alloc(object)
}
pub(crate) fn check_capacity(&self, extra_bytes: usize) -> Result<()> {
if self.can_fit_limit(extra_bytes) {
Ok(())
} else {
Err(Trap::AllocationTooLarge.into())
}
}
pub(crate) fn get(&self, index: u32) -> Option<&GcObject> {
self.slots.get(index as usize)?.as_ref()
}
pub(crate) fn get_mut(&mut self, index: u32) -> Option<&mut GcObject> {
self.slots.get_mut(index as usize)?.as_mut()
}
pub(crate) fn generation(&self, index: u32) -> Option<u32> {
self.generations.get(index as usize).copied()
}
pub(crate) fn mark(&mut self, index: u32) -> bool {
let i = index as usize;
if self.slots.get(i).is_none_or(Option::is_none) {
return false;
}
let (word, bit) = (i / 64, 1u64 << (i % 64));
if word >= self.marks.len() {
self.marks.resize(word + 1, 0);
}
if self.marks[word] & bit != 0 {
false
} else {
self.marks[word] |= bit;
true
}
}
pub(crate) fn sweep(&mut self) {
for (i, slot) in self.slots.iter_mut().enumerate() {
let Some(obj) = slot.as_ref() else {
continue;
};
let marked = self
.marks
.get(i / 64)
.is_some_and(|word| word & (1 << (i % 64)) != 0);
if !marked {
self.used = self.used.saturating_sub(obj.byte_size());
*slot = None;
let next = self.generations[i].wrapping_add(1);
self.generations[i] = if next == u32::MAX { 0 } else { next };
self.free.push(i as u32);
}
}
self.marks.clear(); }
pub(crate) fn clear_marks(&mut self) {
self.marks.clear();
}
}
#[cfg(test)]
#[path = "gc_tests.rs"]
mod tests;