Skip to main content

molframe_core/hashing/
mix.rs

1//! A fixed multiply-shift hasher for keys that are already integers.
2//!
3//! `SipHash`, the standard-library default, is built to resist adversarial key
4//! choice. Nothing here hashes attacker-chosen keys: the keys are atom indices,
5//! symbol identifiers and index pairs the engine assigned itself. Paying a
6//! keyed pseudo-random function for them costs roughly a nanosecond per lookup,
7//! which at a billion contacts is a second of pure overhead.
8//!
9//! The multiplier and shifts are compile-time constants rather than process
10//! seeded, so iteration order over a map built from the same keys is identical
11//! across runs and machines — required by NFR-307.
12
13use std::hash::{BuildHasherDefault, Hasher};
14
15/// The golden-ratio odd multiplier, as used by Fibonacci hashing.
16///
17/// Multiplying by an odd constant is a bijection on `u64`, so no two distinct
18/// absorbed sequences collide through the multiply itself.
19const MULTIPLIER: u64 = 0x9E37_79B9_7F4A_7C15;
20
21/// The final xor-shift distance, chosen to fold high entropy into low bits.
22///
23/// The multiply concentrates entropy in the high bits, while a hash table
24/// selects its bucket from the low ones; without this fold, keys differing only
25/// in their low bits would land in the same bucket.
26const FOLD: u32 = 29;
27
28/// A `Hasher` for integer keys: one multiply and one xor-shift per word.
29#[derive(Debug, Clone, Copy, Default)]
30pub struct IdentityHasher {
31    /// The running hash, updated once per absorbed word.
32    state: u64,
33}
34
35impl IdentityHasher {
36    /// Folds one word into the state.
37    ///
38    /// Order dependent, so a two-field key such as `(u32, u32)` does not
39    /// collide with its own reverse.
40    fn absorb(&mut self, value: u64) {
41        self.state = (self.state ^ value).wrapping_mul(MULTIPLIER);
42        self.state ^= self.state >> FOLD;
43    }
44}
45
46impl Hasher for IdentityHasher {
47    fn finish(&self) -> u64 {
48        self.state
49    }
50
51    /// Absorbs arbitrary bytes eight at a time, then the remainder.
52    ///
53    /// Present so the type satisfies `Hasher` for any key; the integer paths
54    /// below are what the engine actually uses.
55    fn write(&mut self, bytes: &[u8]) {
56        let (chunks, remainder) = bytes.as_chunks::<8>();
57        for chunk in chunks {
58            self.absorb(u64::from_le_bytes(*chunk));
59        }
60
61        if !remainder.is_empty() {
62            let mut word = [0u8; 8];
63            match word.get_mut(..remainder.len()) {
64                Some(head) => head.copy_from_slice(remainder),
65                None => return,
66            }
67            self.absorb(u64::from_le_bytes(word));
68        }
69    }
70
71    fn write_u8(&mut self, value: u8) {
72        self.absorb(u64::from(value));
73    }
74
75    fn write_u16(&mut self, value: u16) {
76        self.absorb(u64::from(value));
77    }
78
79    fn write_u32(&mut self, value: u32) {
80        self.absorb(u64::from(value));
81    }
82
83    fn write_u64(&mut self, value: u64) {
84        self.absorb(value);
85    }
86
87    fn write_usize(&mut self, value: usize) {
88        self.absorb(value as u64);
89    }
90}
91
92/// The `BuildHasher` to give `HashMap`/`HashSet` over integer keys.
93pub type IdentityBuildHasher = BuildHasherDefault<IdentityHasher>;
94
95/// A `HashMap` keyed by integers, hashed by [`IdentityHasher`].
96pub type IdentityHashMap<K, V> = std::collections::HashMap<K, V, IdentityBuildHasher>;
97
98/// A `HashSet` of integers, hashed by [`IdentityHasher`].
99pub type IdentityHashSet<K> = std::collections::HashSet<K, IdentityBuildHasher>;
100
101#[cfg(test)]
102#[path = "mix_tests.rs"]
103mod tests;