use std::collections::HashMap;
use std::hash::{BuildHasherDefault, Hasher};
use std::marker::PhantomData;
use crate::structural_hash::{mix, mix_bytes};
use crate::{Expr, Node};
#[derive(Clone, Copy, Default)]
pub(crate) struct WordHasher(u64);
impl Hasher for WordHasher {
#[inline]
fn finish(&self) -> u64 {
self.0
}
#[inline]
fn write(&mut self, bytes: &[u8]) {
self.0 = mix_bytes(self.0, bytes);
}
#[inline]
fn write_u32(&mut self, value: u32) {
self.0 = mix(self.0, u64::from(value));
}
#[inline]
fn write_u64(&mut self, value: u64) {
self.0 = mix(self.0, value);
}
#[inline]
fn write_usize(&mut self, value: usize) {
self.0 = mix(self.0, value as u64);
}
}
pub(crate) type WordBuildHasher = BuildHasherDefault<WordHasher>;
pub(crate) struct NodeMemo<'term, V> {
done: HashMap<(*const Expr, u32), V, WordBuildHasher>,
term: PhantomData<&'term Expr>,
}
impl<'term, V> NodeMemo<'term, V> {
#[inline]
pub(crate) fn new() -> Self {
Self {
done: HashMap::default(),
term: PhantomData,
}
}
#[inline]
pub(crate) fn get(&self, node: &'term Node, depth: u32) -> Option<&V> {
if self.done.is_empty() {
return None;
}
self.done.get(&(node.as_ptr(), depth))
}
#[inline]
pub(crate) fn insert(&mut self, node: &'term Node, depth: u32, value: V) {
self.done.insert((node.as_ptr(), depth), value);
}
#[cfg(test)]
pub(crate) fn len(&self) -> usize {
self.done.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Level, Name};
fn leaf(name: &str) -> Expr {
Expr::Const(Name::str(name), vec![])
}
#[test]
fn a_result_is_found_under_the_node_and_depth_it_was_stored_for() {
let shared = Node::new(leaf("c"));
let other = Node::new(leaf("c"));
let term = Expr::App(shared.clone(), other.clone());
let Expr::App(first, second) = &term else {
panic!("an application");
};
let mut memo: NodeMemo<'_, u32> = NodeMemo::new();
assert!(memo.get(first, 0).is_none(), "empty memo");
memo.insert(first, 0, 7);
assert_eq!(memo.get(first, 0), Some(&7));
assert!(memo.get(first, 1).is_none(), "another depth");
assert!(memo.get(second, 0).is_none(), "another allocation");
memo.insert(first, 1, 8);
memo.insert(second, 0, 9);
assert_eq!(memo.len(), 3);
assert_eq!(memo.get(first, 1), Some(&8));
assert_eq!(memo.get(second, 0), Some(&9));
}
#[test]
fn two_edges_to_one_allocation_are_one_key() {
let shared = Node::new(Expr::Sort(Level::zero()));
let term = Expr::App(shared.clone(), shared);
let Expr::App(first, second) = &term else {
panic!("an application");
};
let mut memo: NodeMemo<'_, &str> = NodeMemo::new();
memo.insert(first, 3, "done");
assert_eq!(memo.get(second, 3), Some(&"done"));
}
#[test]
fn the_word_hasher_separates_addresses_and_depths() {
use std::hash::BuildHasher;
let build = WordBuildHasher::default();
let hash = |key: (usize, u32)| build.hash_one(key);
let keys: Vec<(usize, u32)> = (0..2048usize)
.flat_map(|i| (0..4u32).map(move |depth| (0x1000_0000 + i * 16, depth)))
.collect();
let hashes: std::collections::HashSet<u64> = keys.iter().map(|&key| hash(key)).collect();
assert_eq!(hashes.len(), keys.len());
let low: std::collections::HashSet<u64> = hashes.iter().map(|h| h & 0xFFF).collect();
assert!(
low.len() > 3000,
"low 12 bits take only {} values",
low.len()
);
assert_eq!(hash((1, 2)), hash((1, 2)));
assert_ne!(hash((1, 2)), hash((2, 1)));
}
}