rustpython_common/
hash.rs1use core::hash::{BuildHasher, Hash, Hasher};
2use malachite_bigint::BigInt;
3use num_traits::ToPrimitive;
4use siphasher::sip::SipHasher24;
5
6pub type PyHash = i64;
7pub type PyUHash = u64;
8
9pub const SENTINEL: PyHash = -1;
12
13pub const MULTIPLIER: PyHash = 1_000_003; pub const BITS: usize = 61;
17pub const MODULUS: PyUHash = (1 << BITS) - 1;
18pub const INF: PyHash = 314_159;
19pub const NAN: PyHash = 0;
20pub const IMAG: PyHash = MULTIPLIER;
21pub const ALGO: &str = "siphash24";
22pub const HASH_BITS: usize = core::mem::size_of::<PyHash>() * 8;
23pub const SEED_BITS: usize = core::mem::size_of::<u64>() * 2 * 8;
25
26#[derive(Clone, Copy)]
29pub struct HashSecret {
30 k0: u64,
31 k1: u64,
32}
33
34impl BuildHasher for HashSecret {
35 type Hasher = SipHasher24;
36
37 fn build_hasher(&self) -> Self::Hasher {
38 SipHasher24::new_with_keys(self.k0, self.k1)
39 }
40}
41
42impl HashSecret {
43 #[must_use]
44 pub fn new(seed: u32) -> Self {
45 let mut buf = [0u8; 16];
46 lcg_urandom(seed, &mut buf);
47 let (left, right) = buf.split_at(8);
48 let k0 = u64::from_le_bytes(left.try_into().unwrap());
49 let k1 = u64::from_le_bytes(right.try_into().unwrap());
50 Self { k0, k1 }
51 }
52
53 #[must_use]
57 pub const fn from_keys(k0: u64, k1: u64) -> Self {
58 Self { k0, k1 }
59 }
60
61 pub fn hash_value<T: Hash + ?Sized>(&self, data: &T) -> PyHash {
62 fix_sentinel(mod_int(self.hash_one(data) as _))
63 }
64
65 pub fn hash_iter<'a, T: 'a, I, F, E>(&self, iter: I, hash_func: F) -> Result<PyHash, E>
66 where
67 I: IntoIterator<Item = &'a T>,
68 F: Fn(&'a T) -> Result<PyHash, E>,
69 {
70 let mut hasher = self.build_hasher();
71 for element in iter {
72 let item_hash = hash_func(element)?;
73 item_hash.hash(&mut hasher);
74 }
75 Ok(fix_sentinel(mod_int(hasher.finish() as PyHash)))
76 }
77
78 #[must_use]
79 pub fn hash_bytes(&self, value: &[u8]) -> PyHash {
80 if value.is_empty() {
81 0
82 } else {
83 self.hash_value(value)
84 }
85 }
86
87 #[must_use]
88 pub fn hash_str(&self, value: &str) -> PyHash {
89 self.hash_bytes(value.as_bytes())
90 }
91}
92
93#[inline]
94#[must_use]
95pub const fn hash_pointer(value: usize) -> PyHash {
96 let hash = (value >> 4) | value;
98 hash as _
99}
100
101#[inline]
102#[must_use]
103pub const fn hash_float(value: f64) -> Option<PyHash> {
104 if !value.is_finite() {
106 return if value.is_infinite() {
107 Some(if value > 0.0 { INF } else { -INF })
108 } else {
109 None
110 };
111 }
112
113 let frexp = super::float_ops::decompose_float(value);
114
115 let mut m = frexp.0;
118 let mut e = frexp.1;
119 let mut x: PyUHash = 0;
120
121 #[expect(clippy::while_float, reason = "keep this loop like CPython does it")]
122 while m != 0.0 {
123 x = ((x << 28) & MODULUS) | (x >> (BITS - 28));
124 m *= 268_435_456.0; e -= 28;
126 let y = m as PyUHash; m -= y as f64;
128 x += y;
129 if x >= MODULUS {
130 x -= MODULUS;
131 }
132 }
133
134 const BITS32: i32 = BITS as i32;
136 e = if e >= 0 {
137 e % BITS32
138 } else {
139 BITS32 - 1 - ((-1 - e) % BITS32)
140 };
141 x = ((x << e) & MODULUS) | (x >> (BITS32 - e));
142
143 Some(fix_sentinel(x as PyHash * value.signum() as PyHash))
144}
145
146#[must_use]
147pub fn hash_bigint(value: &BigInt) -> PyHash {
148 let ret = if let Some(v) = value.to_i64() {
149 mod_int(v)
150 } else {
151 unsafe { (value % MODULUS).to_i64().unwrap_unchecked() }
154 };
155
156 fix_sentinel(ret)
157}
158
159#[inline]
160#[must_use]
161pub const fn hash_usize(data: usize) -> PyHash {
162 fix_sentinel(mod_int(data as i64))
163}
164
165#[inline(always)]
166#[must_use]
167pub const fn fix_sentinel(x: PyHash) -> PyHash {
168 if x == SENTINEL { -2 } else { x }
169}
170
171#[inline]
172#[must_use]
173pub const fn mod_int(value: i64) -> PyHash {
174 value % MODULUS as i64
175}
176
177pub fn lcg_urandom(mut x: u32, buf: &mut [u8]) {
178 for b in buf {
179 x = x.wrapping_mul(214013);
180 x = x.wrapping_add(2531011);
181 *b = ((x >> 16) & 0xff) as u8;
182 }
183}
184
185#[inline]
186#[must_use]
187pub const fn hash_object_id_raw(p: usize) -> PyHash {
188 p as PyHash
195}
196
197#[inline]
198#[must_use]
199pub const fn hash_object_id(p: usize) -> PyHash {
200 fix_sentinel(hash_object_id_raw(p))
201}
202
203#[must_use]
204pub fn keyed_hash(key: u64, buf: &[u8]) -> u64 {
205 let mut hasher = SipHasher24::new_with_keys(key, 0);
206 buf.hash(&mut hasher);
207 hasher.finish()
208}
209
210pub fn hash_tuple<E>(
215 element_hashes: impl IntoIterator<Item = Result<PyHash, E>>,
216) -> Result<PyHash, E> {
217 const PRIME1: PyUHash = cfg_select! {
218 target_pointer_width = "64" => 11400714785074694791,
219 target_pointer_width = "32" => 2654435761,
220 _ => unreachable!(),
221 };
222
223 const PRIME2: PyUHash = cfg_select! {
224 target_pointer_width = "64" => 14029467366897019727,
225 target_pointer_width = "32" => 2246822519,
226 _ => unreachable!(),
227 };
228
229 const PRIME5: PyUHash = cfg_select! {
230 target_pointer_width = "64" => 2870177450012600261,
231 target_pointer_width = "32" => 374761393,
232 _ => unreachable!(),
233 };
234
235 const ROTATE: u32 = cfg_select! {
236 target_pointer_width = "64" => 31,
237 target_pointer_width = "32" => 13,
238 _ => unreachable!(),
239 };
240
241 let mut acc = PRIME5;
242 let mut len: PyUHash = 0;
243
244 for element_hash in element_hashes {
245 let lane = element_hash? as PyUHash;
246 acc = acc.wrapping_add(lane.wrapping_mul(PRIME2));
247 acc = acc.rotate_left(ROTATE);
248 acc = acc.wrapping_mul(PRIME1);
249 len += 1;
250 }
251
252 acc = acc.wrapping_add(len ^ (PRIME5 ^ 3527539));
253
254 let acc_py_hash = acc as PyHash;
255 if acc_py_hash == -1 {
256 return Ok(1546275796);
257 }
258
259 Ok(acc_py_hash)
260}
261
262pub struct FrozenSetHash {
269 hash: u64,
270}
271
272impl FrozenSetHash {
273 #[must_use]
274 pub fn new(len: usize) -> Self {
275 Self {
277 hash: (len as u64 + 1).wrapping_mul(1927868237),
278 }
279 }
280
281 pub fn add(&mut self, element_hash: PyHash) {
282 const fn shuffle_bits(h: u64) -> u64 {
287 ((h ^ 89869747) ^ (h.wrapping_shl(16))).wrapping_mul(3644798167)
288 }
289 self.hash ^= shuffle_bits(element_hash as u64);
292 }
293
294 #[must_use]
295 pub fn finish(self) -> PyHash {
296 let mut hash = self.hash;
297 hash ^= (hash >> 11) ^ (hash >> 25);
299 hash = hash.wrapping_mul(69069).wrapping_add(907133923);
300 if hash == u64::MAX {
302 hash = 590923713;
303 }
304 hash as PyHash
305 }
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311
312 #[test]
313 fn from_keys_is_stable_and_seed_independent() {
314 const K0: u64 = 0x0706_0504_0302_0100;
315 const K1: u64 = 0x0f0e_0d0c_0b0a_0908;
316 const LOCKED_DIGEST: PyHash = -1862661396243998188;
317
318 let a = HashSecret::from_keys(K0, K1);
321 let b = HashSecret::from_keys(K0, K1);
322 assert_eq!(a.hash_str("hello"), b.hash_str("hello"));
323 assert_eq!(
324 a.hash_bytes(b"a fixed message"),
325 b.hash_bytes(b"a fixed message")
326 );
327
328 let zero_k1 = HashSecret::from_keys(K0, 0);
332 assert_eq!(keyed_hash(K0, b"payload"), zero_k1.hash_one(b"payload"));
333
334 assert_eq!(a.hash_str("determinism"), LOCKED_DIGEST);
336 }
337}