use core::hash::Hasher;
use ahash::AHasher;
use hashbrown::{HashTable, hash_table::Entry};
use crate::{
ecmascript::{PropertyKey, Value, execution::Agent},
engine::{
Bindable, HeapRootCollection, NoGcScope, ScopableCollection, ScopedCollection,
bindable_handle,
},
heap::{CompactionLists, HeapMarkAndSweep, WorkQueues},
};
pub(crate) struct KeyedGroup<'a> {
keys: Vec<Value<'a>>,
values: Vec<Vec<Value<'a>>>,
hash_table: HashTable<u32>,
}
impl core::fmt::Debug for KeyedGroup<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "KeyedGroup")?;
f.debug_map()
.entries(self.keys.iter().zip(self.values.iter()))
.finish()
}
}
impl<'a> KeyedGroup<'a> {
pub(crate) fn new(_: NoGcScope<'a, '_>) -> Box<Self> {
Box::new(Self {
keys: Vec::new(),
values: Vec::new(),
hash_table: HashTable::new(),
})
}
pub(crate) fn len(&self) -> usize {
debug_assert!(
self.keys.len() == self.values.len() && self.keys.len() == self.hash_table.len()
);
self.keys.len()
}
pub(crate) fn add_collection_keyed_value(&mut self, agent: &Agent, key: Value, value: Value) {
let hasher = |value: Value| {
let mut hasher = AHasher::default();
value.hash(agent, &mut hasher);
hasher.finish()
};
let hash = hasher(key);
let entry = self.hash_table.entry(
hash,
|idx| self.keys[*idx as usize] == key,
|idx| {
let key = self.keys[*idx as usize];
hasher(key)
},
);
match entry {
Entry::Occupied(occupied) => {
let idx = *occupied.get() as usize;
self.values[idx].push(value.unbind());
}
Entry::Vacant(vacant) => {
let idx = u32::try_from(self.keys.len()).expect("Keyed group overflowed");
self.keys.push(key.unbind());
self.values.push(vec![value.unbind()]);
vacant.insert(idx);
}
}
}
pub(crate) fn add_property_keyed_value(
&mut self,
agent: &Agent,
key: PropertyKey,
value: Value,
) {
let hash = key.heap_hash(agent);
let key = unsafe { key.into_value_unchecked() };
let entry = self.hash_table.entry(
hash,
|idx| self.keys[*idx as usize] == key,
|idx| {
let key = self.keys[*idx as usize];
let key = unsafe { PropertyKey::from_value_unchecked(key) };
key.heap_hash(agent)
},
);
match entry {
Entry::Occupied(occupied) => {
let idx = *occupied.get() as usize;
self.values[idx].push(value.unbind());
}
Entry::Vacant(vacant) => {
let idx = u32::try_from(self.keys.len()).expect("Keyed group overflowed");
self.keys.push(key.unbind());
self.values.push(vec![value.unbind()]);
vacant.insert(idx);
}
}
}
fn insert_to_hash_table_unique(
hash_table: &mut HashTable<u32>,
keys: &[Value<'static>],
key: Value<'static>,
index: u32,
) {
let hasher = |value: Value| {
let mut hasher = AHasher::default();
value.try_hash(&mut hasher).unwrap();
hasher.finish()
};
let hash = hasher(key);
let entry = hash_table.entry(
hash,
|idx| {
let indexes_equal = *idx == index;
let values_equal = keys[*idx as usize] == key;
assert!(!indexes_equal || !values_equal);
false
},
|idx| {
let key = keys[*idx as usize];
hasher(key)
},
);
match entry {
Entry::Occupied(_) => {
unreachable!()
}
Entry::Vacant(vacant) => {
vacant.insert(index);
}
}
}
pub(crate) fn into_property_keyed_iter(
self,
) -> impl Iterator<Item = (PropertyKey<'a>, Vec<Value<'a>>)> {
debug_assert_eq!(self.keys.len(), self.values.len());
self.keys
.into_iter()
.map(|v| unsafe { PropertyKey::from_value_unchecked(v) })
.zip(self.values)
}
pub(crate) fn into_collection_keyed_iter(
self,
) -> impl Iterator<Item = (Value<'a>, Vec<Value<'a>>)> {
debug_assert_eq!(self.keys.len(), self.values.len());
self.keys.into_iter().zip(self.values)
}
}
impl ScopableCollection for Box<KeyedGroup<'_>> {
fn scope<'scope>(
self,
agent: &Agent,
gc: NoGcScope<'_, 'scope>,
) -> ScopedCollection<'scope, Self::Of<'static>> {
ScopedCollection::new(agent, self.unbind(), gc)
}
}
impl ScopedCollection<'_, Box<KeyedGroup<'static>>> {
pub(crate) fn add_collection_keyed_value(&mut self, agent: &Agent, key: Value, value: Value) {
let mut stack_ref_collections = agent.stack_ref_collections.borrow_mut();
let Some(stack_slot) = stack_ref_collections.get_mut(self.inner as usize) else {
unreachable!();
};
let HeapRootCollection::KeyedGroup(keyed_group) = stack_slot else {
unreachable!()
};
keyed_group.add_collection_keyed_value(agent, key, value);
}
pub(crate) fn add_property_keyed_value(
&mut self,
agent: &Agent,
key: PropertyKey,
value: Value,
) {
let mut stack_ref_collections = agent.stack_ref_collections.borrow_mut();
let Some(stack_slot) = stack_ref_collections.get_mut(self.inner as usize) else {
unreachable!();
};
let HeapRootCollection::KeyedGroup(keyed_group) = stack_slot else {
unreachable!()
};
keyed_group.add_property_keyed_value(agent, key, value);
}
}
bindable_handle!(KeyedGroup);
fn value_needs_rehash(v: &Value<'static>, compactions: &CompactionLists) -> bool {
if v.is_symbol() || v.is_object() {
let mut new_v = *v;
new_v.sweep_values(compactions);
if *v != new_v {
true
} else {
false
}
} else {
false
}
}
impl HeapMarkAndSweep for KeyedGroup<'static> {
fn mark_values(&self, queues: &mut WorkQueues) {
let Self {
keys,
values,
hash_table: _,
} = self;
keys.mark_values(queues);
for vec in values.iter() {
vec.mark_values(queues);
}
}
fn sweep_values(&mut self, compactions: &CompactionLists) {
let Self {
keys,
values,
hash_table,
} = self;
if keys.iter().any(|v| value_needs_rehash(v, compactions)) {
if keys
.iter()
.any(|v| matches!(v, Value::String(_) | Value::Number(_) | Value::BigInt(_)))
{
let rehash_indexes = keys
.iter()
.enumerate()
.filter(|(_, v)| value_needs_rehash(v, compactions))
.map(|(i, _)| i as u32)
.collect::<Vec<u32>>();
hash_table.retain(|idx| !rehash_indexes.contains(idx));
keys.sweep_values(compactions);
for vec in values.iter_mut() {
vec.sweep_values(compactions);
}
for idx in rehash_indexes.into_iter() {
let key = keys[idx as usize];
KeyedGroup::insert_to_hash_table_unique(hash_table, keys, key, idx);
}
} else {
hash_table.clear();
keys.sweep_values(compactions);
for vec in values.iter_mut() {
vec.sweep_values(compactions);
}
for (index, key) in keys.iter().enumerate() {
let index = index as u32;
let key = *key;
KeyedGroup::insert_to_hash_table_unique(hash_table, keys, key, index);
}
}
} else {
keys.sweep_values(compactions);
for vec in values.iter_mut() {
vec.sweep_values(compactions);
}
}
}
}