rain-lang 0.0.1

An implementation of an RVSDG in Rust with a concept of lifetimes
Documentation
/*!
Hash-consing and associated utilities
*/
use dashmap::{DashMap, mapref::entry::Entry};
use ahash::AHasher;
use std::hash::{Hash, Hasher, BuildHasherDefault};
use std::convert::Infallible;
use either::Either;
use super::node::{WeakNode, Node, Data};
use crate::util::PassThroughHasher;
use crate::value::{ValueDesc, ValueData, ValId};

/// A node cache type
pub trait CacheType<T> {
    /// The underlying map type for a given node cache type
    type UnderlyingMap;
}

/// A global node cache
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct GlobalCache;

impl<T> CacheType<T> for GlobalCache {
    type UnderlyingMap = DashMap<u64, WeakNode<T>, BuildHasherDefault<PassThroughHasher>>;
}

/// A node cache
#[derive(Debug, Clone)]
pub struct NodeCache<T, C: CacheType<T> = GlobalCache> {
    /// The underlying map of this
    pub map: C::UnderlyingMap,
    /// The keys of this node cache, for constructing `AHasher` instances
    pub keys: (u64, u64)
}

/// An entry in a node cache
pub type CacheEntry<'a, T> =
    Entry<'a, u64, WeakNode<T>, BuildHasherDefault<PassThroughHasher>>;

/// An object which can accept a cache entry pointer
pub trait CacheAcceptor<T> {
    /// Accept a cache entry pointer
    fn accept(self, weak: WeakNode<T>);
}

impl<'a, T> CacheAcceptor<T> for CacheEntry<'a, T> {
    fn accept(self, weak: WeakNode<T>) {
        *(self.or_default()) = weak;
    }
}

impl<T> CacheAcceptor<T> for () {
    fn accept(self, _weak: WeakNode<T>) {}
}

impl<T> NodeCache<T, GlobalCache> {
    /// Create a new, empty node cache with the given keys
    pub fn with_keys(keys: (u64, u64)) -> NodeCache<T> {
        NodeCache { map: DashMap::default(), keys }
    }
    /// Create a new, empty node cache
    pub fn new() -> NodeCache<T> { Self::with_keys((0, 0)) }
    /// Get either the cached value for a key (if it exists), or the entry for the key
    /// (if it does not). Note that this locks the bucket for the key up until the handle
    /// for the entry is destroyed, so be careful!
    pub fn cached_entry<'a, K>(&'a self, key: &K) -> Either<Node<T>, CacheEntry<'a, T>>
    where K: Hash {
        let hash = {
            let mut hasher = AHasher::new_with_keys(self.keys.0, self.keys.1);
            key.hash(&mut hasher);
            hasher.finish()
        };
        let entry = self.map.entry(hash);
        if let Entry::Occupied(occupied) = &entry {
            if let Some(node) = occupied.get().upgrade() {
                return Either::Left(node)
            }
        }
        Either::Right(entry)
    }
    /// Try to get the node cached for a given key, with a given node creation function.
    /// If the node has been collected or otherwise does not exist,
    /// try to create a new one and cache it. If this fails, return an error.
    pub fn cached_constructor<D, E, F>(&self, data: D, constructor: F) -> Result<Node<T>, E>
    where D: Hash, F: FnOnce(D) -> Result<Node<T>, E> {
        let entry = match self.cached_entry(&data) {
            Either::Left(node) => return Ok(node),
            Either::Right(entry) => entry
        };
        let node = constructor(data)?;
        *(entry.or_default()) = node.downgrade();
        Ok(node)
    }
    /// Remove a given cached value
    pub fn remove<K>(&self, key: &K) -> Option<(u64, WeakNode<T>)> where K: Hash {
        let mut hasher = AHasher::new_with_keys(self.keys.0, self.keys.1);
        key.hash(&mut hasher);
        let cache_key = hasher.finish();
        self.map.remove(&cache_key)
    }
    /// Try to get the node cached for a given descriptor.
    /// If the node has been collected or otherwise does not exist,
    /// try to create a new one and cache it. If this fails, return an error.
    #[inline(always)] pub fn cached_desc<D>(&self, desc: D) -> Result<Node<T>, T::CacheError>
    where D: Hash, T: CacheBy<D> {
        self.cached_constructor(desc, T::node_to_cache)
    }
}

/// A node data-type which can be cached by a given key type
pub trait CacheBy<K: Hash> {
    /// Potential error when trying to cache a key type
    type CacheError;
    /// Try to get the node to cache for a given key
    fn node_to_cache(key: K) -> Result<Node<Self>, Self::CacheError>;
}

impl<V> CacheBy<V> for ValueData where V: ValueDesc + Hash {
    type CacheError = V::Err;
    #[inline(always)] fn node_to_cache(key: V) -> Result<ValId, V::Err> { key.to_node() }
}

impl<T: Hash> CacheBy<Data<T>> for Data<T> {
    type CacheError = Infallible;
    #[inline(always)] fn node_to_cache(key: Data<T>) -> Result<Node<Data<T>>, Infallible> {
        Ok(Node::new(key))
    }
}

#[cfg(test)]
mod tests {
    use crate::util::AlwaysOk;
    use super::*;

    #[test]
    fn bools_cache_properly() {
        let cache = NodeCache::<Data<bool>>::new();
        let dt = Data(true);
        let df = Data(false);
        let nt = cache.cached_desc(dt).aok();
        let pass = cache.cached_constructor(Data(true), |_| Err(()))
            .expect("Valid key, should not generate");
        assert_eq!(nt, pass);
        cache.cached_constructor(Data(false), |_| Err(()))
            .expect_err("Invalid key, generator always errors");
        let nf = cache.cached_desc(df).aok();
        let pass = cache.cached_constructor(Data(false), |_| Err(()))
            .expect("Valid key, should not generate");
        assert_eq!(nf, pass);
        let nt2 = cache.cached_desc(dt).aok();
        let nf2 = cache.cached_desc(df).aok();
        assert_eq!(nt, nt2);
        assert_eq!(nf, nf2);
        assert_ne!(nt, nf);
        let mnt = Node::new(dt);
        let mnf = Node::new(df);
        assert_ne!(nt, mnt);
        assert_ne!(nf, mnt);
        assert_ne!(nt, mnf);
        assert_ne!(nf, mnf);
        let (_hash, ntr) = cache.remove(&dt).expect("Data(true) was previously inserted");
        assert_eq!(ntr.upgrade().expect("We hold a reference to Data(true)"), nt);
        let nt3 = cache.cached_desc(dt).expect("Valid key");
        let nf3 = cache.cached_desc(df).expect("Valid key");
        assert_ne!(nt, nt3);
        assert_eq!(nf, nf3);
    }
}