#![no_std]
#[macro_export]
macro_rules! derive_hash_fast_bytemuck {
($T:ty) => {
impl core::hash::Hash for $T {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
let bytes = ::bytemuck::bytes_of(self);
$crate::write_to_optimal_hasher_function::<{core::mem::size_of::<$T>()}>(bytes, state);
}
fn hash_slice<H: core::hash::Hasher>(data: &[Self], state: &mut H)
where
Self: Sized,
{
state.write(::bytemuck::cast_slice(data));
}
}
};
}
#[macro_export]
macro_rules! derive_hash_fast_zerocopy {
($T:ty) => {
impl core::hash::Hash for $T {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
let bytes = ::zerocopy::IntoBytes::as_bytes(self);
$crate::write_to_optimal_hasher_function::<{core::mem::size_of::<$T>()}>(bytes, state);
}
fn hash_slice<H: core::hash::Hasher>(data: &[Self], state: &mut H)
where
Self: Sized,
{
state.write(::zerocopy::transmute_ref!(data));
}
}
};
}
use core::hash::Hasher;
#[doc(hidden)]
#[inline]
pub fn write_to_optimal_hasher_function<const B: usize>(bytes: &[u8], state: &mut impl Hasher) {
assert!(bytes.len() == B);
match B {
1 => state.write_u8(u8::from_ne_bytes(bytes.try_into().unwrap())),
2 => state.write_u16(u16::from_ne_bytes(bytes.try_into().unwrap())),
3 => state.write_u32(pad_to_u32::<3>(bytes.try_into().unwrap())),
4 => state.write_u32(u32::from_ne_bytes(bytes.try_into().unwrap())),
5..=7 => state.write_u64(pad_to_u64::<B>(bytes.try_into().unwrap())),
8 => state.write_u64(u64::from_ne_bytes(bytes.try_into().unwrap())),
9..=15 => state.write_u128(pad_to_u128::<B>(bytes.try_into().unwrap())),
16 => state.write_u128(u128::from_ne_bytes(bytes.try_into().unwrap())),
17..=64 => hash_padded_large::<B>(bytes.try_into().unwrap(), state),
_ => state.write(bytes),
}
}
#[inline]
fn pad_to_u32<const N: usize>(bytes: &[u8; N]) -> u32 {
let mut padded_bytes = [0u8; core::mem::size_of::<u32>()];
padded_bytes[..N].copy_from_slice(bytes);
u32::from_ne_bytes(padded_bytes.try_into().unwrap())
}
#[inline]
fn pad_to_u64<const N: usize>(bytes: &[u8; N]) -> u64 {
let mut padded_bytes = [0u8; core::mem::size_of::<u64>()];
padded_bytes[..N].copy_from_slice(bytes);
u64::from_ne_bytes(padded_bytes.try_into().unwrap())
}
#[inline]
fn pad_to_u128<const N: usize>(bytes: &[u8; N]) -> u128 {
let mut padded_bytes = [0u8; core::mem::size_of::<u128>()];
padded_bytes[..N].copy_from_slice(bytes);
u128::from_ne_bytes(padded_bytes.try_into().unwrap())
}
#[inline]
fn hash_padded_large<const N: usize>(bytes: &[u8; N], state: &mut impl Hasher) {
const SIZEOF_U128: usize = core::mem::size_of::<u128>();
let chunks_iter = bytes.chunks_exact(SIZEOF_U128);
let remainder = chunks_iter.remainder();
for chunk in chunks_iter {
state.write_u128(u128::from_ne_bytes(chunk.try_into().unwrap()))
}
match remainder.len() {
0 => (), 1..=7 => {
let mut padded_bytes = [0u8; core::mem::size_of::<u64>()];
padded_bytes[..remainder.len()].copy_from_slice(remainder);
state.write_u64(u64::from_ne_bytes(padded_bytes.try_into().unwrap()))
}
8 => state.write_u64(u64::from_ne_bytes(remainder.try_into().unwrap())),
9..=15 => {
let mut padded_bytes = [0u8; core::mem::size_of::<u128>()];
padded_bytes[..remainder.len()].copy_from_slice(remainder);
state.write_u128(u128::from_ne_bytes(padded_bytes.try_into().unwrap()))
},
SIZEOF_U128.. => unreachable!(),
}
}
#[cfg(test)]
mod tests {
extern crate std;
use super::*;
#[repr(C)]
#[derive(Clone, Copy, bytemuck::NoUninit)]
struct FooB {
a: u16,
b: u16,
c: char,
}
derive_hash_fast_bytemuck!(FooB);
fn hash_struct_bytemuck(val: FooB) -> u64 {
use std::hash::{DefaultHasher, Hash, Hasher};
let mut hasher = DefaultHasher::new();
val.hash(&mut hasher);
hasher.finish()
}
#[test]
fn test_bytemuck() {
let test_struct_1 = FooB {
a: 5,
b: 10,
c: 'a',
};
let test_struct_2 = FooB {
a: 5,
b: 500,
c: 'a',
};
let hash_1 = hash_struct_bytemuck(test_struct_1);
let hash_1_again = hash_struct_bytemuck(test_struct_1);
let hash_2 = hash_struct_bytemuck(test_struct_2);
assert_eq!(hash_1, hash_1_again);
assert_ne!(hash_1, hash_2);
}
#[derive(Clone, zerocopy::Immutable, zerocopy::IntoBytes)]
struct FooZ {
a: u16,
b: u16,
c: char,
}
derive_hash_fast_zerocopy!(FooZ);
fn hash_struct_zerocopy(val: FooZ) -> u64 {
use std::hash::{DefaultHasher, Hash, Hasher};
let mut hasher = DefaultHasher::new();
val.hash(&mut hasher);
hasher.finish()
}
#[test]
fn test_zerocopy() {
let test_struct_1 = FooZ {
a: 5,
b: 10,
c: 'a',
};
let test_struct_2 = FooZ {
a: 5,
b: 500,
c: 'a',
};
let hash_1 = hash_struct_zerocopy(test_struct_1.clone());
let hash_1_again = hash_struct_zerocopy(test_struct_1);
let hash_2 = hash_struct_zerocopy(test_struct_2);
assert_eq!(hash_1, hash_1_again);
assert_ne!(hash_1, hash_2);
}
}