const KEY_STATE: u64 = 0x9E37_79B9_7F4A_7C15;
const KEY_VALUE: u64 = 0xD1B5_4A32_D192_ED03;
#[inline]
#[must_use]
pub(crate) const fn mix(state: u64, value: u64) -> u64 {
let product = ((state ^ KEY_STATE) as u128) * ((value ^ KEY_VALUE) as u128);
((product >> 64) as u64) ^ (product as u64)
}
const fn seed(tag: u64) -> u64 {
let mut z = tag.wrapping_add(0x9E37_79B9_7F4A_7C15);
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
pub(crate) const NAME_ANONYMOUS: u64 = seed(1);
pub(crate) const NAME_STR: u64 = seed(2);
pub(crate) const NAME_NUM: u64 = seed(3);
pub(crate) const LEVEL_ZERO: u64 = seed(10);
pub(crate) const LEVEL_SUCC: u64 = seed(11);
pub(crate) const LEVEL_MAX: u64 = seed(12);
pub(crate) const LEVEL_IMAX: u64 = seed(13);
pub(crate) const LEVEL_PARAM: u64 = seed(14);
pub(crate) const LEVEL_MVAR: u64 = seed(15);
pub(crate) const EXPR_SORT: u64 = seed(20);
pub(crate) const EXPR_BVAR: u64 = seed(21);
pub(crate) const EXPR_FVAR: u64 = seed(22);
pub(crate) const EXPR_CONST: u64 = seed(23);
pub(crate) const EXPR_APP: u64 = seed(24);
pub(crate) const EXPR_LAM: u64 = seed(25);
pub(crate) const EXPR_PI: u64 = seed(26);
pub(crate) const EXPR_LET: u64 = seed(27);
pub(crate) const EXPR_LIT_NAT: u64 = seed(28);
pub(crate) const EXPR_LIT_STR: u64 = seed(29);
pub(crate) const EXPR_PROJ: u64 = seed(30);
#[must_use]
pub(crate) fn mix_bytes(state: u64, bytes: &[u8]) -> u64 {
let mut hash = state;
for group in bytes.chunks(8) {
let word = group
.iter()
.enumerate()
.fold(0u64, |acc, (i, &byte)| acc | (u64::from(byte) << (8 * i)));
hash = mix(hash, word);
}
mix(hash, bytes.len() as u64)
}
#[must_use]
pub(crate) fn mix_words(state: u64, words: &[u64]) -> u64 {
let hash = words.iter().fold(state, |acc, &word| mix(acc, word));
mix(hash, words.len() as u64)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mix_is_order_sensitive() {
for (a, b) in [(1u64, 2u64), (0, 1), (u64::MAX, 0), (7, 0x1234_5678)] {
assert_ne!(mix(a, b), mix(b, a), "mix({a}, {b}) is symmetric");
assert_ne!(
mix(mix(EXPR_APP, a), b),
mix(mix(EXPR_APP, b), a),
"absorbing {a} then {b} equals absorbing {b} then {a}"
);
}
}
#[test]
fn mix_separates_small_consecutive_values() {
let hashes: Vec<u64> = (0u64..4096).map(|n| mix(EXPR_BVAR, n)).collect();
let distinct: std::collections::HashSet<u64> = hashes.iter().copied().collect();
assert_eq!(distinct.len(), hashes.len());
let high: std::collections::HashSet<u64> = hashes.iter().map(|h| h >> 48).collect();
assert!(
high.len() > 3500,
"top 16 bits take only {} values over 4096 inputs",
high.len()
);
let low: std::collections::HashSet<u64> = hashes.iter().map(|h| h & 0xFFFF).collect();
assert!(
low.len() > 3500,
"bottom 16 bits take only {} values over 4096 inputs",
low.len()
);
}
#[test]
fn seeds_are_pairwise_distinct() {
let seeds = [
NAME_ANONYMOUS,
NAME_STR,
NAME_NUM,
LEVEL_ZERO,
LEVEL_SUCC,
LEVEL_MAX,
LEVEL_IMAX,
LEVEL_PARAM,
LEVEL_MVAR,
EXPR_SORT,
EXPR_BVAR,
EXPR_FVAR,
EXPR_CONST,
EXPR_APP,
EXPR_LAM,
EXPR_PI,
EXPR_LET,
EXPR_LIT_NAT,
EXPR_LIT_STR,
EXPR_PROJ,
];
let distinct: std::collections::HashSet<u64> = seeds.iter().copied().collect();
assert_eq!(distinct.len(), seeds.len());
}
#[test]
fn mix_bytes_distinguishes_padding_and_length() {
let h = |s: &str| mix_bytes(EXPR_LIT_STR, s.as_bytes());
assert_ne!(h(""), h("\0"));
assert_ne!(h("a"), h("a\0"));
assert_ne!(h("abcdefgh"), h("abcdefgh\0"));
assert_ne!(h("abcdefgh"), h("abcdefg"));
assert_ne!(h("ab"), h("ba"));
assert_eq!(h("Nat.add"), h("Nat.add"));
assert_ne!(h("abcdefghi"), h("abcdefgh"));
}
#[test]
fn mix_words_distinguishes_length() {
assert_ne!(mix_words(EXPR_LIT_NAT, &[]), mix_words(EXPR_LIT_NAT, &[0]));
assert_ne!(
mix_words(EXPR_LIT_NAT, &[1, 2]),
mix_words(EXPR_LIT_NAT, &[2, 1])
);
assert_eq!(
mix_words(EXPR_LIT_NAT, &[5, 6, 7]),
mix_words(EXPR_LIT_NAT, &[5, 6, 7])
);
}
}