use core::fmt;
use core::hash::{BuildHasher, Hash};
use hashbrown::hash_map::RawEntryMut;
use hashbrown::{DefaultHashBuilder, HashMap, TryReserveError};
use super::allocator::AccountAllocator;
use super::{Memory, MemoryError};
pub trait HashWork: Hash + Eq {
fn hash_work(&self) -> Result<u64, MemoryError>;
}
impl HashWork for usize {
fn hash_work(&self) -> Result<u64, MemoryError> {
Ok(1)
}
}
impl HashWork for (usize, usize, usize) {
fn hash_work(&self) -> Result<u64, MemoryError> {
Ok(3)
}
}
impl HashWork for &[u8] {
fn hash_work(&self) -> Result<u64, MemoryError> {
Ok(self.len() as u64 + 1)
}
}
impl HashWork for crate::Oid<'_> {
fn hash_work(&self) -> Result<u64, MemoryError> {
Ok(self.as_bytes().len() as u64 + 1)
}
}
impl HashWork for (crate::Oid<'_>, crate::Oid<'_>) {
fn hash_work(&self) -> Result<u64, MemoryError> {
self.0
.hash_work()?
.checked_add(self.1.hash_work()?)
.ok_or(MemoryError::WorkExceeded)
}
}
pub struct OwnedMap<K: HashWork, V> {
values: HashMap<K, V, DefaultHashBuilder, AccountAllocator>,
memory: Memory,
key_work: u64,
maximum_capacity: usize,
}
impl<K: HashWork + fmt::Debug, V: fmt::Debug> fmt::Debug for OwnedMap<K, V> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.values.fmt(f)
}
}
impl<K: HashWork, V> OwnedMap<K, V> {
pub(super) fn new(memory: Memory) -> Self {
Self {
values: HashMap::with_hasher_in(
DefaultHashBuilder::default(),
AccountAllocator::new(memory.clone()),
),
memory,
key_work: 0,
maximum_capacity: 0,
}
}
#[must_use]
pub fn memory(&self) -> &Memory {
&self.memory
}
#[must_use]
pub fn len(&self) -> usize {
self.values.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
pub fn try_iter<'a>(
&'a self,
execution: &Memory,
) -> Result<impl Iterator<Item = (&'a K, &'a V)> + use<'a, K, V>, MemoryError> {
execution.charge_work(self.buckets()?)?;
Ok(self.values.iter())
}
fn buckets(&self) -> Result<u64, MemoryError> {
match self.maximum_capacity {
0 => Ok(0),
capacity @ 1..=7 => Ok(capacity as u64 + 1),
capacity => (capacity as u64 / 7)
.checked_mul(8)
.ok_or(MemoryError::WorkExceeded),
}
}
fn control_work(&self) -> Result<u64, MemoryError> {
let buckets = self.buckets()?;
Ok(buckets / 4)
}
fn rehash_work(&self) -> Result<u64, MemoryError> {
let buckets = self.buckets()?;
let groups = buckets.checked_mul(2).ok_or(MemoryError::WorkExceeded)? / 4;
let moves = (core::mem::size_of::<(K, V)>() as u64)
.checked_mul(2)
.ok_or(MemoryError::WorkExceeded)?;
groups
.checked_add(moves)
.and_then(|per_entry| per_entry.checked_mul(self.values.len() as u64))
.and_then(|work| work.checked_add(self.key_work))
.and_then(|work| {
buckets
.checked_mul(2)
.and_then(|scan| work.checked_add(scan))
})
.ok_or(MemoryError::WorkExceeded)
}
fn hash_key(&self, key: &K, execution: &Memory) -> Result<u64, MemoryError> {
let work = key
.hash_work()?
.checked_add(self.control_work()?)
.ok_or(MemoryError::WorkExceeded)?;
execution.charge_work(work)?;
Ok(self.values.hasher().hash_one(key))
}
pub fn get(&self, key: &K, execution: &Memory) -> Result<Option<&V>, MemoryError> {
let hash = self.hash_key(key, execution)?;
let mut failure = None;
let entry = self.values.raw_entry().from_hash(hash, |candidate| {
equivalent(execution, key, candidate, &mut failure)
});
if let Some(reason) = failure {
return Err(reason);
}
Ok(entry.map(|(_, value)| value))
}
pub fn get_mut(&mut self, key: &K, execution: &Memory) -> Result<Option<&mut V>, MemoryError> {
let hash = self.hash_key(key, execution)?;
let mut failure = None;
let entry = self.values.raw_entry_mut().from_hash(hash, |candidate| {
equivalent(execution, key, candidate, &mut failure)
});
if let Some(reason) = failure {
return Err(reason);
}
Ok(match entry {
RawEntryMut::Occupied(entry) => Some(entry.into_mut()),
RawEntryMut::Vacant(_) => None,
})
}
pub fn try_insert(&mut self, key: K, value: V) -> Result<Option<V>, MemoryError> {
let hash = self.hash_key(&key, &self.memory)?;
let mut failure = None;
let entry = self.values.raw_entry_mut().from_hash(hash, |candidate| {
equivalent(&self.memory, &key, candidate, &mut failure)
});
if let Some(reason) = failure {
return Err(reason);
}
if let RawEntryMut::Occupied(entry) = entry {
return Ok(Some(core::mem::replace(entry.into_mut(), value)));
}
let key_work = self
.key_work
.checked_add(key.hash_work()?)
.ok_or(MemoryError::WorkExceeded)?;
if self.values.len() == self.values.capacity() {
self.memory().charge_work(self.rehash_work()?)?;
self.values.allocator().clear_failure();
self.values.try_reserve(1).map_err(|error| match error {
TryReserveError::CapacityOverflow => MemoryError::CapacityOverflow,
TryReserveError::AllocError { .. } => self.values.allocator().failure(),
})?;
self.maximum_capacity = self.maximum_capacity.max(self.values.capacity());
}
self.memory.charge_work(
self.control_work()?
.checked_mul(2)
.ok_or(MemoryError::WorkExceeded)?,
)?;
let RawEntryMut::Vacant(entry) = self.values.raw_entry_mut().from_hash(hash, |_| false)
else {
unreachable!("a predicate that always refuses cannot find an entry");
};
entry.insert_hashed_nocheck(hash, key, value);
self.key_work = key_work;
Ok(None)
}
pub fn remove(&mut self, key: &K, execution: &Memory) -> Result<Option<V>, MemoryError> {
let hash = self.hash_key(key, execution)?;
let mut failure = None;
let entry = self.values.raw_entry_mut().from_hash(hash, |candidate| {
equivalent(execution, key, candidate, &mut failure)
});
if let Some(reason) = failure {
return Err(reason);
}
let entry = match entry {
RawEntryMut::Occupied(entry) => {
let work = (core::mem::size_of::<(K, V)>() as u64)
.checked_mul(2)
.and_then(|work| work.checked_add(2))
.ok_or(MemoryError::WorkExceeded)?;
let removed_work = entry.key().hash_work()?;
execution.charge_work(work)?;
let (_, value) = entry.remove_entry();
self.key_work -= removed_work;
Some(value)
}
RawEntryMut::Vacant(_) => None,
};
Ok(entry)
}
}
fn equivalent<K: HashWork>(
memory: &Memory,
query: &K,
candidate: &K,
failure: &mut Option<MemoryError>,
) -> bool {
let cost = query.hash_work().and_then(|query| {
candidate.hash_work().and_then(|candidate| {
query
.checked_add(candidate)
.ok_or(MemoryError::WorkExceeded)
})
});
let admitted = cost.and_then(|cost| memory.charge_work(cost));
if let Err(reason) = admitted {
*failure = Some(reason);
return true;
}
query == candidate
}