Path: blob/main/cranelift/codegen/src/ctxhash.rs
1693 views
//! A hashmap with "external hashing": nodes are hashed or compared for1//! equality only with some external context provided on lookup/insert.2//! This allows very memory-efficient data structures where3//! node-internal data references some other storage (e.g., offsets into4//! an array or pool of shared data).56use hashbrown::hash_table::HashTable;7use std::hash::{Hash, Hasher};89/// Trait that allows for equality comparison given some external10/// context.11///12/// Note that this trait is implemented by the *context*, rather than13/// the item type, for somewhat complex lifetime reasons (lack of GATs14/// to allow `for<'ctx> Ctx<'ctx>`-like associated types in traits on15/// the value type).16pub trait CtxEq<V1: ?Sized, V2: ?Sized> {17/// Determine whether `a` and `b` are equal, given the context in18/// `self` and the union-find data structure `uf`.19fn ctx_eq(&self, a: &V1, b: &V2) -> bool;20}2122/// Trait that allows for hashing given some external context.23pub trait CtxHash<Value: ?Sized>: CtxEq<Value, Value> {24/// Compute the hash of `value`, given the context in `self` and25/// the union-find data structure `uf`.26fn ctx_hash<H: Hasher>(&self, state: &mut H, value: &Value);27}2829/// A null-comparator context type for underlying value types that30/// already have `Eq` and `Hash`.31#[derive(Default)]32pub struct NullCtx;3334impl<V: Eq + Hash> CtxEq<V, V> for NullCtx {35fn ctx_eq(&self, a: &V, b: &V) -> bool {36a.eq(b)37}38}39impl<V: Eq + Hash> CtxHash<V> for NullCtx {40fn ctx_hash<H: Hasher>(&self, state: &mut H, value: &V) {41value.hash(state);42}43}4445/// A bucket in the hash table.46///47/// Some performance-related design notes: we cache the hashcode for48/// speed, as this often buys a few percent speed in49/// interning-table-heavy workloads. We only keep the low 32 bits of50/// the hashcode, for memory efficiency: in common use, `K` and `V`51/// are often 32 bits also, and a 12-byte bucket is measurably better52/// than a 16-byte bucket.53struct BucketData<K, V> {54hash: u32,55k: K,56v: V,57}5859/// A HashMap that takes external context for all operations.60pub struct CtxHashMap<K, V> {61raw: HashTable<BucketData<K, V>>,62}6364impl<K, V> CtxHashMap<K, V> {65/// Create an empty hashmap with pre-allocated space for the given66/// capacity.67pub fn with_capacity(capacity: usize) -> Self {68Self {69raw: HashTable::with_capacity(capacity),70}71}72}7374fn compute_hash<Ctx, K>(ctx: &Ctx, k: &K) -> u3275where76Ctx: CtxHash<K>,77{78let mut hasher = rustc_hash::FxHasher::default();79ctx.ctx_hash(&mut hasher, k);80hasher.finish() as u3281}8283impl<K, V> CtxHashMap<K, V> {84/// Insert a new key-value pair, returning the old value associated85/// with this key (if any).86pub fn insert<Ctx>(&mut self, k: K, v: V, ctx: &Ctx) -> Option<V>87where88Ctx: CtxEq<K, K> + CtxHash<K>,89{90let hash = compute_hash(ctx, &k);91match self.raw.find_mut(hash as u64, |bucket| {92hash == bucket.hash && ctx.ctx_eq(&bucket.k, &k)93}) {94Some(bucket) => Some(std::mem::replace(&mut bucket.v, v)),95None => {96let data = BucketData { hash, k, v };97self.raw98.insert_unique(hash as u64, data, |bucket| bucket.hash as u64);99None100}101}102}103104/// Look up a key, returning a borrow of the value if present.105pub fn get<'a, Q, Ctx>(&'a self, k: &Q, ctx: &Ctx) -> Option<&'a V>106where107Ctx: CtxEq<K, Q> + CtxHash<Q> + CtxHash<K>,108{109let hash = compute_hash(ctx, k);110self.raw111.find(hash as u64, |bucket| {112hash == bucket.hash && ctx.ctx_eq(&bucket.k, k)113})114.map(|bucket| &bucket.v)115}116117/// Look up a key, returning an `Entry` that refers to an existing118/// value or allows inserting a new one.119pub fn entry<'a, Ctx>(&'a mut self, k: K, ctx: &Ctx) -> Entry<'a, K, V>120where121Ctx: CtxEq<K, K> + CtxHash<K>,122{123let hash = compute_hash(ctx, &k);124let raw = self.raw.entry(125hash as u64,126|bucket| hash == bucket.hash && ctx.ctx_eq(&bucket.k, &k),127|bucket| compute_hash(ctx, &bucket.k) as u64,128);129match raw {130hashbrown::hash_table::Entry::Occupied(o) => Entry::Occupied(OccupiedEntry { raw: o }),131hashbrown::hash_table::Entry::Vacant(v) => Entry::Vacant(VacantEntry {132hash,133key: k,134raw: v,135}),136}137}138}139140/// A reference to an existing or vacant entry in the hash table.141pub enum Entry<'a, K, V> {142Occupied(OccupiedEntry<'a, K, V>),143Vacant(VacantEntry<'a, K, V>),144}145146/// A reference to an occupied entry in the hash table.147pub struct OccupiedEntry<'a, K, V> {148raw: hashbrown::hash_table::OccupiedEntry<'a, BucketData<K, V>>,149}150151/// A reference to a vacant entry in the hash table.152pub struct VacantEntry<'a, K, V> {153hash: u32,154key: K,155raw: hashbrown::hash_table::VacantEntry<'a, BucketData<K, V>>,156}157158impl<'a, K, V> OccupiedEntry<'a, K, V> {159/// Get the existing value.160pub fn get(&self) -> &V {161&self.raw.get().v162}163164/// Get the existing value, mutably.165pub fn get_mut(&mut self) -> &mut V {166&mut self.raw.get_mut().v167}168}169170impl<'a, K, V> VacantEntry<'a, K, V> {171/// Insert a new value.172pub fn insert(self, v: V) {173self.raw.insert(BucketData {174hash: self.hash,175k: self.key,176v,177});178}179}180181#[cfg(test)]182mod test {183use super::*;184185#[derive(Clone, Copy, Debug)]186struct Key {187index: u32,188}189struct Ctx {190vals: &'static [&'static str],191}192impl CtxEq<Key, Key> for Ctx {193fn ctx_eq(&self, a: &Key, b: &Key) -> bool {194self.vals[a.index as usize].eq(self.vals[b.index as usize])195}196}197impl CtxHash<Key> for Ctx {198fn ctx_hash<H: Hasher>(&self, state: &mut H, value: &Key) {199self.vals[value.index as usize].hash(state);200}201}202203#[test]204fn test_basic() {205let ctx = Ctx {206vals: &["a", "b", "a"],207};208209let k0 = Key { index: 0 };210let k1 = Key { index: 1 };211let k2 = Key { index: 2 };212213assert!(ctx.ctx_eq(&k0, &k2));214assert!(!ctx.ctx_eq(&k0, &k1));215assert!(!ctx.ctx_eq(&k2, &k1));216217let mut map: CtxHashMap<Key, u64> = CtxHashMap::with_capacity(4);218assert_eq!(map.insert(k0, 42, &ctx), None);219assert_eq!(map.insert(k2, 84, &ctx), Some(42));220assert_eq!(map.get(&k1, &ctx), None);221assert_eq!(*map.get(&k0, &ctx).unwrap(), 84);222}223}224225226