use core::hash::{BuildHasher, Hash, Hasher};
use malachite_bigint::BigInt;
use num_traits::ToPrimitive;
use siphasher::sip::SipHasher24;
pub type PyHash = i64;
pub type PyUHash = u64;
pub const SENTINEL: PyHash = -1;
pub const MULTIPLIER: PyHash = 1_000_003; pub const BITS: usize = 61;
pub const MODULUS: PyUHash = (1 << BITS) - 1;
pub const INF: PyHash = 314_159;
pub const NAN: PyHash = 0;
pub const IMAG: PyHash = MULTIPLIER;
pub const ALGO: &str = "siphash24";
pub const HASH_BITS: usize = core::mem::size_of::<PyHash>() * 8;
pub const SEED_BITS: usize = core::mem::size_of::<u64>() * 2 * 8;
#[derive(Clone, Copy)]
pub struct HashSecret {
k0: u64,
k1: u64,
}
impl BuildHasher for HashSecret {
type Hasher = SipHasher24;
fn build_hasher(&self) -> Self::Hasher {
SipHasher24::new_with_keys(self.k0, self.k1)
}
}
impl HashSecret {
#[must_use]
pub fn new(seed: u32) -> Self {
let mut buf = [0u8; 16];
lcg_urandom(seed, &mut buf);
let (left, right) = buf.split_at(8);
let k0 = u64::from_le_bytes(left.try_into().unwrap());
let k1 = u64::from_le_bytes(right.try_into().unwrap());
Self { k0, k1 }
}
#[must_use]
pub const fn from_keys(k0: u64, k1: u64) -> Self {
Self { k0, k1 }
}
pub fn hash_value<T: Hash + ?Sized>(&self, data: &T) -> PyHash {
fix_sentinel(mod_int(self.hash_one(data) as _))
}
pub fn hash_iter<'a, T: 'a, I, F, E>(&self, iter: I, hash_func: F) -> Result<PyHash, E>
where
I: IntoIterator<Item = &'a T>,
F: Fn(&'a T) -> Result<PyHash, E>,
{
let mut hasher = self.build_hasher();
for element in iter {
let item_hash = hash_func(element)?;
item_hash.hash(&mut hasher);
}
Ok(fix_sentinel(mod_int(hasher.finish() as PyHash)))
}
#[must_use]
pub fn hash_bytes(&self, value: &[u8]) -> PyHash {
if value.is_empty() {
0
} else {
self.hash_value(value)
}
}
#[must_use]
pub fn hash_str(&self, value: &str) -> PyHash {
self.hash_bytes(value.as_bytes())
}
}
#[inline]
#[must_use]
pub const fn hash_pointer(value: usize) -> PyHash {
let hash = (value >> 4) | value;
hash as _
}
#[inline]
#[must_use]
pub const fn hash_float(value: f64) -> Option<PyHash> {
if !value.is_finite() {
return if value.is_infinite() {
Some(if value > 0.0 { INF } else { -INF })
} else {
None
};
}
let frexp = super::float_ops::decompose_float(value);
let mut m = frexp.0;
let mut e = frexp.1;
let mut x: PyUHash = 0;
#[expect(clippy::while_float, reason = "keep this loop like CPython does it")]
while m != 0.0 {
x = ((x << 28) & MODULUS) | (x >> (BITS - 28));
m *= 268_435_456.0; e -= 28;
let y = m as PyUHash; m -= y as f64;
x += y;
if x >= MODULUS {
x -= MODULUS;
}
}
const BITS32: i32 = BITS as i32;
e = if e >= 0 {
e % BITS32
} else {
BITS32 - 1 - ((-1 - e) % BITS32)
};
x = ((x << e) & MODULUS) | (x >> (BITS32 - e));
Some(fix_sentinel(x as PyHash * value.signum() as PyHash))
}
#[must_use]
pub fn hash_bigint(value: &BigInt) -> PyHash {
let ret = if let Some(v) = value.to_i64() {
mod_int(v)
} else {
unsafe { (value % MODULUS).to_i64().unwrap_unchecked() }
};
fix_sentinel(ret)
}
#[inline]
#[must_use]
pub const fn hash_usize(data: usize) -> PyHash {
fix_sentinel(mod_int(data as i64))
}
#[inline(always)]
#[must_use]
pub const fn fix_sentinel(x: PyHash) -> PyHash {
if x == SENTINEL { -2 } else { x }
}
#[inline]
#[must_use]
pub const fn mod_int(value: i64) -> PyHash {
value % MODULUS as i64
}
pub fn lcg_urandom(mut x: u32, buf: &mut [u8]) {
for b in buf {
x = x.wrapping_mul(214013);
x = x.wrapping_add(2531011);
*b = ((x >> 16) & 0xff) as u8;
}
}
#[inline]
#[must_use]
pub const fn hash_object_id_raw(p: usize) -> PyHash {
p as PyHash
}
#[inline]
#[must_use]
pub const fn hash_object_id(p: usize) -> PyHash {
fix_sentinel(hash_object_id_raw(p))
}
#[must_use]
pub fn keyed_hash(key: u64, buf: &[u8]) -> u64 {
let mut hasher = SipHasher24::new_with_keys(key, 0);
buf.hash(&mut hasher);
hasher.finish()
}
pub fn hash_tuple<E>(
element_hashes: impl IntoIterator<Item = Result<PyHash, E>>,
) -> Result<PyHash, E> {
const PRIME1: PyUHash = cfg_select! {
target_pointer_width = "64" => 11400714785074694791,
target_pointer_width = "32" => 2654435761,
_ => unreachable!(),
};
const PRIME2: PyUHash = cfg_select! {
target_pointer_width = "64" => 14029467366897019727,
target_pointer_width = "32" => 2246822519,
_ => unreachable!(),
};
const PRIME5: PyUHash = cfg_select! {
target_pointer_width = "64" => 2870177450012600261,
target_pointer_width = "32" => 374761393,
_ => unreachable!(),
};
const ROTATE: u32 = cfg_select! {
target_pointer_width = "64" => 31,
target_pointer_width = "32" => 13,
_ => unreachable!(),
};
let mut acc = PRIME5;
let mut len: PyUHash = 0;
for element_hash in element_hashes {
let lane = element_hash? as PyUHash;
acc = acc.wrapping_add(lane.wrapping_mul(PRIME2));
acc = acc.rotate_left(ROTATE);
acc = acc.wrapping_mul(PRIME1);
len += 1;
}
acc = acc.wrapping_add(len ^ (PRIME5 ^ 3527539));
let acc_py_hash = acc as PyHash;
if acc_py_hash == -1 {
return Ok(1546275796);
}
Ok(acc_py_hash)
}
pub struct FrozenSetHash {
hash: u64,
}
impl FrozenSetHash {
#[must_use]
pub fn new(len: usize) -> Self {
Self {
hash: (len as u64 + 1).wrapping_mul(1927868237),
}
}
pub fn add(&mut self, element_hash: PyHash) {
const fn shuffle_bits(h: u64) -> u64 {
((h ^ 89869747) ^ (h.wrapping_shl(16))).wrapping_mul(3644798167)
}
self.hash ^= shuffle_bits(element_hash as u64);
}
#[must_use]
pub fn finish(self) -> PyHash {
let mut hash = self.hash;
hash ^= (hash >> 11) ^ (hash >> 25);
hash = hash.wrapping_mul(69069).wrapping_add(907133923);
if hash == u64::MAX {
hash = 590923713;
}
hash as PyHash
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_keys_is_stable_and_seed_independent() {
const K0: u64 = 0x0706_0504_0302_0100;
const K1: u64 = 0x0f0e_0d0c_0b0a_0908;
const LOCKED_DIGEST: PyHash = -1862661396243998188;
let a = HashSecret::from_keys(K0, K1);
let b = HashSecret::from_keys(K0, K1);
assert_eq!(a.hash_str("hello"), b.hash_str("hello"));
assert_eq!(
a.hash_bytes(b"a fixed message"),
b.hash_bytes(b"a fixed message")
);
let zero_k1 = HashSecret::from_keys(K0, 0);
assert_eq!(keyed_hash(K0, b"payload"), zero_k1.hash_one(b"payload"));
assert_eq!(a.hash_str("determinism"), LOCKED_DIGEST);
}
}