use alloc::collections::BTreeSet;
use alloc::vec::Vec;
use crate::value::{JSIDX_OFFSET, JSIDX_RESERVED};
pub(crate) trait SlabId: Ord + Copy + core::fmt::Display {
fn next(self) -> Option<Self>;
}
impl SlabId for u32 {
fn next(self) -> Option<Self> {
self.checked_add(1)
}
}
impl SlabId for u64 {
fn next(self) -> Option<Self> {
self.checked_add(1)
}
}
pub(crate) struct IdSlab<T: SlabId> {
live_ids: BTreeSet<T>,
free_ids: BTreeSet<T>,
next_id: T,
}
impl<T: SlabId> IdSlab<T> {
pub(crate) fn new(first_id: T) -> Self {
Self {
live_ids: BTreeSet::new(),
free_ids: BTreeSet::new(),
next_id: first_id,
}
}
pub(crate) fn alloc(&mut self) -> T {
if let Some(id) = self.free_ids.iter().next().copied() {
self.free_ids.remove(&id);
self.mark_live(id);
return id;
}
let id = self.next_id;
self.next_id = self.next_id.next().expect("ID space exhausted");
self.mark_live(id);
id
}
fn reserve_exact(&mut self, id: T) {
self.free_ids.remove(&id);
if id >= self.next_id {
self.next_id = id.next().expect("ID space exhausted");
}
self.mark_live(id);
}
fn release(&mut self, id: T) {
assert!(
self.live_ids.remove(&id),
"Attempted to release ID {id}, but it is not live"
);
}
fn recycle(&mut self, id: T) {
assert!(
!self.live_ids.contains(&id),
"Attempted to recycle ID {id}, but it is still live"
);
self.free_ids.insert(id);
}
fn recycle_if_released(&mut self, id: T) -> bool {
if self.live_ids.contains(&id) {
return false;
}
self.free_ids.insert(id);
true
}
pub(crate) fn free(&mut self, id: T) {
self.release(id);
self.recycle(id);
}
pub(crate) fn contains(&self, id: T) -> bool {
self.live_ids.contains(&id)
}
#[cfg(test)]
fn is_reusable(&self, id: T) -> bool {
self.free_ids.contains(&id)
}
fn mark_live(&mut self, id: T) {
assert!(self.live_ids.insert(id), "ID {id} is already live");
}
}
pub(crate) type InstallIdBatch = Vec<u64>;
pub(crate) struct HeapIds {
slab: IdSlab<u64>,
pending_install_ids: Vec<u64>,
reserved_placeholder_ids: Vec<u64>,
}
impl HeapIds {
pub(crate) fn new() -> Self {
Self {
slab: IdSlab::new(JSIDX_RESERVED),
pending_install_ids: Vec::new(),
reserved_placeholder_ids: Vec::new(),
}
}
fn next_heap_id(&mut self) -> u64 {
self.slab.alloc()
}
pub(crate) fn observe_js_heap_id(&mut self, id: u64) {
if id >= JSIDX_RESERVED {
self.slab.reserve_exact(id);
}
}
pub(crate) fn next_placeholder_id(&mut self) -> u64 {
let id = self.next_heap_id();
self.reserved_placeholder_ids.push(id);
id
}
pub(crate) fn next_inbound_js_heap_id(&mut self) -> u64 {
let id = self.slab.alloc();
self.pending_install_ids.push(id);
id
}
pub(crate) fn release_heap_slot(&mut self, id: u64) {
if id < JSIDX_RESERVED {
unreachable!("Attempted to release reserved JS heap ID {}", id);
}
self.slab.release(id);
}
pub(crate) fn recycle_heap_id(&mut self, id: u64) {
if id >= JSIDX_RESERVED {
self.slab.recycle(id);
}
}
pub(crate) fn recycle_heap_id_if_released(&mut self, id: u64) -> bool {
id >= JSIDX_RESERVED && self.slab.recycle_if_released(id)
}
pub(crate) fn take_pending_install_ids(&mut self) -> InstallIdBatch {
core::mem::take(&mut self.pending_install_ids)
}
pub(crate) fn take_reserved_placeholder_ids(&mut self) -> Vec<u64> {
core::mem::take(&mut self.reserved_placeholder_ids)
}
}
pub(crate) struct BorrowIds {
stack_pointer: u64,
frame_stack: Vec<u64>,
}
impl BorrowIds {
pub(crate) fn new() -> Self {
Self {
stack_pointer: JSIDX_OFFSET,
frame_stack: Vec::new(),
}
}
pub(crate) fn next_borrow_id(&mut self) -> u64 {
if self.stack_pointer <= 1 {
panic!("Borrow stack overflow: too many borrowed references in a single operation");
}
self.stack_pointer -= 1;
self.stack_pointer
}
pub(crate) fn push_frame(&mut self) {
self.frame_stack.push(self.stack_pointer);
}
pub(crate) fn pop_frame(&mut self) {
if let Some(saved_pointer) = self.frame_stack.pop() {
self.stack_pointer = saved_pointer;
} else {
panic!("pop_borrow_frame called with empty frame stack");
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn slab_reuses_recycled_ids_in_order() {
let mut slab = IdSlab::new(10_u64);
let first = slab.alloc();
let second = slab.alloc();
assert_eq!(first, 10);
assert_eq!(second, 11);
slab.release(second);
slab.recycle(second);
slab.release(first);
slab.recycle(first);
assert_eq!(slab.alloc(), 10);
assert_eq!(slab.alloc(), 11);
}
#[test]
fn reserve_exact_removes_id_from_free_list() {
let mut slab = IdSlab::new(10_u64);
let id = slab.alloc();
slab.release(id);
slab.recycle(id);
assert!(slab.is_reusable(id));
slab.reserve_exact(id);
assert!(slab.contains(id));
assert!(!slab.is_reusable(id));
assert_eq!(slab.alloc(), 11);
}
#[test]
fn recycle_if_released_leaves_reobserved_id_live() {
let mut slab = IdSlab::new(10_u64);
let id = slab.alloc();
slab.release(id);
slab.reserve_exact(id);
assert!(!slab.recycle_if_released(id));
assert!(slab.contains(id));
assert!(!slab.is_reusable(id));
}
#[test]
fn heap_ids_start_at_js_reserved_and_reuse_after_recycle() {
let mut heap = HeapIds::new();
let id = heap.next_placeholder_id();
assert_eq!(id, JSIDX_RESERVED);
heap.release_heap_slot(id);
heap.recycle_heap_id(id);
assert_eq!(heap.next_placeholder_id(), id);
}
#[test]
fn inbound_drop_uses_normal_release_path() {
let mut heap = HeapIds::new();
let id = heap.next_inbound_js_heap_id();
heap.release_heap_slot(id);
let next = heap.next_placeholder_id();
assert_ne!(next, id);
let batch = heap.take_pending_install_ids();
assert_eq!(batch, vec![id]);
heap.recycle_heap_id(id);
assert_eq!(heap.next_placeholder_id(), id);
}
#[test]
fn inbound_heap_ids_collapse_into_one_install_batch() {
let mut heap = HeapIds::new();
let ids = [
heap.next_inbound_js_heap_id(),
heap.next_inbound_js_heap_id(),
];
assert_eq!(ids, [JSIDX_RESERVED, JSIDX_RESERVED + 1]);
let batch = heap.take_pending_install_ids();
assert_eq!(batch, ids);
assert!(heap.take_pending_install_ids().is_empty());
}
#[test]
fn taking_with_no_inbound_ids_is_empty() {
let mut heap = HeapIds::new();
assert!(heap.take_pending_install_ids().is_empty());
}
#[test]
fn each_take_drains_only_ids_since_the_last_take() {
let mut heap = HeapIds::new();
let first = heap.next_inbound_js_heap_id();
assert_eq!(heap.take_pending_install_ids(), vec![first]);
let second = heap.next_inbound_js_heap_id();
assert_eq!(heap.take_pending_install_ids(), vec![second]);
}
#[test]
fn observe_js_heap_id_reserves_recycled_id() {
let mut heap = HeapIds::new();
let id = heap.next_placeholder_id();
heap.release_heap_slot(id);
heap.recycle_heap_id(id);
heap.observe_js_heap_id(id);
assert_ne!(heap.next_placeholder_id(), id);
}
#[test]
fn borrow_frames_restore_stack_pointer() {
let mut borrows = BorrowIds::new();
assert_eq!(borrows.next_borrow_id(), JSIDX_OFFSET - 1);
borrows.push_frame();
assert_eq!(borrows.next_borrow_id(), JSIDX_OFFSET - 2);
assert_eq!(borrows.next_borrow_id(), JSIDX_OFFSET - 3);
borrows.pop_frame();
assert_eq!(borrows.next_borrow_id(), JSIDX_OFFSET - 2);
}
#[test]
#[should_panic(expected = "Borrow stack overflow")]
fn borrow_stack_panics_on_overflow() {
let mut borrows = BorrowIds::new();
for _ in 0..JSIDX_OFFSET {
borrows.next_borrow_id();
}
}
#[test]
fn object_handles_reuse_released_handles() {
let mut handles = IdSlab::new(1_u32);
let first = handles.alloc();
let second = handles.alloc();
assert_eq!(first, 1);
handles.free(first);
assert_eq!(handles.alloc(), first);
assert_eq!(second, first + 1);
}
#[test]
#[should_panic(expected = "ID space exhausted")]
fn object_handles_panic_on_overflow() {
let mut handles = IdSlab::new(u32::MAX);
handles.alloc();
}
}