use std::cell::Cell;
use std::rc::{Rc, Weak};
use super::object::InternalData;
use super::{Heap, HeapError};
use crate::value::{Kind, ObjectId, Value};
const VACANT: u32 = 0;
const DELETED: u32 = u32::MAX;
#[derive(Debug)]
pub struct Table {
width: usize,
index: Vec<u32>,
hashes: Vec<u32>,
live: usize,
cursors: Vec<Weak<Cell<u32>>>,
}
impl Table {
pub fn new(width: usize) -> Self {
Table {
width,
index: Vec::new(),
hashes: Vec::new(),
live: 0,
cursors: Vec::new(),
}
}
pub fn len(&self) -> usize {
self.live
}
pub fn is_empty(&self) -> bool {
self.live == 0
}
pub fn width(&self) -> usize {
self.width
}
pub fn is_zombie(&self, entry: usize) -> bool {
self.cursors.iter().any(|cursor| {
cursor
.upgrade()
.is_some_and(|cursor| cursor.get() as usize == entry + 1)
})
}
pub fn entries(&self) -> usize {
self.hashes.len()
}
pub fn owned_bytes(&self) -> usize {
self.index.capacity() * 4 + self.hashes.capacity() * 4 + self.cursors.capacity() * 8
}
fn insert_index(&mut self, hash: u32, entry: usize) {
let mask = self.index.len() - 1;
let mut bucket = hash as usize & mask;
while self.index[bucket] != VACANT && self.index[bucket] != DELETED {
bucket = (bucket + 1) & mask;
}
self.index[bucket] = entry as u32 + 1;
}
fn rebuild(&mut self, entries: usize, slots: &[Value]) {
let buckets = (entries * 2).next_power_of_two().max(8);
self.index.clear();
self.index.resize(buckets, VACANT);
for entry in 0..self.hashes.len() {
if !slots[entry * self.width].is_empty() {
self.insert_index(self.hashes[entry], entry);
}
}
}
}
pub(crate) fn tombstone(internal: &mut super::object::Internal, entry: usize) {
let InternalData::Table(table) = &mut internal.data else {
unreachable!("a collection");
};
let width = table.width;
let hash = table.hashes[entry];
let mask = table.index.len() - 1;
let mut bucket = hash as usize & mask;
while table.index[bucket] != entry as u32 + 1 {
bucket = (bucket + 1) & mask;
}
table.index[bucket] = DELETED;
table.live -= 1;
for slot in &mut internal.slots[entry * width..entry * width + width] {
*slot = Value::EMPTY;
}
}
impl Heap {
pub(crate) fn table_hash(&self, key: Value) -> u32 {
let bits = match key.kind() {
Kind::Int32 | Kind::Number => {
let number = key.as_f64().unwrap();
if number == 0.0 {
0
} else if number.is_nan() {
0x7FF8_0000_0000_0000
} else {
number.to_bits()
}
}
Kind::String => return self.string(key.as_string().unwrap()).hash(),
Kind::BigInt => {
let record = self.bigint(key.as_bigint().unwrap());
let mut hash = u64::from(record.negative);
for &limb in record.limbs.iter() {
hash = hash.rotate_left(5) ^ limb;
}
hash
}
_ => key.bits(),
};
let mixed = bits.wrapping_mul(0x9E37_79B9_7F4A_7C15);
(mixed >> 32) as u32 ^ mixed as u32
}
pub(crate) fn table(&self, object: ObjectId) -> &Table {
match &self.internal(object).data {
InternalData::Table(table) => table,
_ => unreachable!("a collection"),
}
}
fn table_mut(&mut self, object: ObjectId) -> &mut Table {
match &mut self.internal_mut(object).data {
InternalData::Table(table) => table,
_ => unreachable!("a collection"),
}
}
pub(crate) fn table_find(&self, object: ObjectId, key: Value) -> Option<usize> {
let table = self.table(object);
if table.live == 0 {
return None;
}
let hash = self.table_hash(key);
let slots = &self.internal(object).slots;
let mask = table.index.len() - 1;
let mut bucket = hash as usize & mask;
loop {
match table.index[bucket] {
VACANT => return None,
DELETED => {}
entry => {
let entry = entry as usize - 1;
if table.hashes[entry] == hash
&& crate::vm::ops::same_value_zero_heap(
self,
slots[entry * table.width],
key,
)
{
return Some(entry);
}
}
}
bucket = (bucket + 1) & mask;
}
}
pub(crate) fn table_get(&self, object: ObjectId, key: Value) -> Option<Value> {
let entry = self.table_find(object, key)?;
let width = self.table(object).width;
Some(self.internal(object).slots[entry * width + width - 1])
}
pub(crate) fn table_set(
&mut self,
object: ObjectId,
key: Value,
value: Value,
) -> Result<(), HeapError> {
let key = if key.as_f64() == Some(0.0) {
Value::int32(0)
} else {
key
};
let width = self.table(object).width;
if let Some(entry) = self.table_find(object, key) {
if width == 2 {
self.internal_mut(object).slots[entry * 2 + 1] = value;
}
return Ok(());
}
let hash = self.table_hash(key);
self.reserve_internal_slots(object, width, &[key, value])?;
let internal = self.internal_mut(object);
internal.slots.push(key);
if width == 2 {
internal.slots.push(value);
}
let InternalData::Table(table) = &mut internal.data else {
unreachable!("a collection");
};
let entry = table.hashes.len();
table.hashes.push(hash);
table.live += 1;
if (entry + 1) * 2 > table.index.len() {
let entries = table.live.max(entry + 1);
table.rebuild(entries, &internal.slots);
} else {
table.insert_index(hash, entry);
}
Ok(())
}
pub(crate) fn table_delete(&mut self, object: ObjectId, key: Value) -> bool {
let Some(entry) = self.table_find(object, key) else {
return false;
};
self.table_delete_entry(object, entry);
true
}
pub(crate) fn table_delete_entry(&mut self, object: ObjectId, entry: usize) {
let internal = self.internal_mut(object);
let InternalData::Table(table) = &mut internal.data else {
unreachable!("a collection");
};
let width = table.width;
let hash = table.hashes[entry];
let mask = table.index.len() - 1;
let mut bucket = hash as usize & mask;
while table.index[bucket] != entry as u32 + 1 {
bucket = (bucket + 1) & mask;
}
table.index[bucket] = DELETED;
table.live -= 1;
for slot in &mut internal.slots[entry * width..entry * width + width] {
*slot = Value::EMPTY;
}
if table.hashes.len() >= 16 && table.live * 2 < table.hashes.len() {
self.table_compact(object);
}
}
pub(crate) fn table_clear(&mut self, object: ObjectId) {
let internal = self.internal_mut(object);
internal.slots.fill(Value::EMPTY);
self.table_compact(object);
}
fn table_compact(&mut self, object: ObjectId) {
let internal = self.internal_mut(object);
let InternalData::Table(table) = &mut internal.data else {
unreachable!("a collection");
};
let width = table.width;
let entries = table.hashes.len();
let mut before = Vec::with_capacity(entries + 1);
let mut kept = 0u32;
let mut write = 0;
for entry in 0..entries {
before.push(kept);
if !internal.slots[entry * width].is_empty() {
for offset in 0..width {
internal.slots[write * width + offset] = internal.slots[entry * width + offset];
}
table.hashes[write] = table.hashes[entry];
write += 1;
kept += 1;
}
}
before.push(kept);
internal.slots.truncate(write * width);
table.hashes.truncate(write);
table.cursors.retain(|cursor| match cursor.upgrade() {
Some(cursor) => {
let at = (cursor.get() as usize).min(entries);
cursor.set(before[at]);
true
}
None => false,
});
table.live = write;
table.rebuild(write, &internal.slots);
}
pub(crate) fn table_cursor(&mut self, object: ObjectId) -> Rc<Cell<u32>> {
let cursor = Rc::new(Cell::new(0));
let table = self.table_mut(object);
table.cursors.retain(|cursor| cursor.strong_count() > 0);
table.cursors.push(Rc::downgrade(&cursor));
cursor
}
pub(crate) fn table_next(
&self,
object: ObjectId,
cursor: &Cell<u32>,
) -> Option<(Value, Value)> {
let table = self.table(object);
let slots = &self.internal(object).slots;
let width = table.width;
let mut entry = cursor.get() as usize;
while entry < table.hashes.len() {
let key = slots[entry * width];
entry += 1;
if !key.is_empty() {
cursor.set(entry as u32);
return Some((key, slots[entry * width - 1]));
}
}
cursor.set(entry as u32);
None
}
pub(crate) fn reserve_internal_slots(
&mut self,
object: ObjectId,
additional: usize,
keep: &[Value],
) -> Result<(), HeapError> {
let internal = self.internal(object);
if internal.slots.capacity() - internal.slots.len() >= additional {
return Ok(());
}
let extra = internal.slots.capacity().max(4) * 8;
let mut kept = Vec::with_capacity(keep.len() + 1);
kept.push(Value::object(object));
kept.extend_from_slice(keep);
self.safepoint(
&super::Need {
extra,
..Default::default()
},
&kept,
)?;
let internal = self.internal_mut(object);
let grow = internal.slots.capacity().max(4).max(additional);
internal.slots.reserve(grow);
Ok(())
}
}