alloy_primitives/utils/
keccak_cache.rs

1//! A minimalistic one-way set associative cache for Keccak256 values.
2//!
3//! This cache has a fixed size to allow fast access and minimize per-call overhead.
4
5use super::{hint::unlikely, keccak256_impl as keccak256};
6use crate::{B256, KECCAK256_EMPTY};
7use std::mem::MaybeUninit;
8
9/// Maximum input length that can be cached.
10pub(super) const MAX_INPUT_LEN: usize =
11    128 - size_of::<B256>() - size_of::<u8>() - size_of::<usize>();
12
13const COUNT: usize = 1 << 17; // ~131k entries
14static CACHE: fixed_cache::Cache<Key, B256, BuildHasher> =
15    fixed_cache::static_cache!(Key, B256, COUNT, BuildHasher::new());
16
17pub(super) fn compute(input: &[u8]) -> B256 {
18    if unlikely(input.is_empty() | (input.len() > MAX_INPUT_LEN)) {
19        return if input.is_empty() { KECCAK256_EMPTY } else { keccak256(input) };
20    }
21
22    CACHE.get_or_insert_with_ref(input, keccak256, |input| {
23        let mut data = [MaybeUninit::uninit(); MAX_INPUT_LEN];
24        unsafe {
25            std::ptr::copy_nonoverlapping(input.as_ptr(), data.as_mut_ptr().cast(), input.len())
26        };
27        Key { len: input.len() as u8, data }
28    })
29}
30
31type BuildHasher = std::hash::BuildHasherDefault<Hasher>;
32#[derive(Default)]
33struct Hasher(u64);
34
35impl std::hash::Hasher for Hasher {
36    #[inline]
37    fn finish(&self) -> u64 {
38        self.0
39    }
40
41    #[inline]
42    fn write(&mut self, bytes: &[u8]) {
43        // This is tricky because our most common inputs are medium length: 16..=88
44        // `foldhash` and `rapidhash` have a fast-path for ..16 bytes and outline the rest,
45        // but really we want the opposite, or at least the 16.. path to be inlined.
46
47        // SAFETY: `bytes.len()` is checked to be within the bounds of `MAX_INPUT_LEN` by caller.
48        unsafe { core::hint::assert_unchecked(bytes.len() <= MAX_INPUT_LEN) };
49        if bytes.len() <= 16 {
50            super::hint::cold_path();
51        }
52        self.0 = rapidhash::v3::rapidhash_v3_micro_inline::<false, false>(
53            bytes,
54            const { &rapidhash::v3::RapidSecrets::seed(0) },
55        );
56    }
57}
58
59#[derive(Clone, Copy)]
60struct Key {
61    len: u8,
62    data: [MaybeUninit<u8>; MAX_INPUT_LEN],
63}
64
65impl PartialEq for Key {
66    #[inline]
67    fn eq(&self, other: &Self) -> bool {
68        self.len == other.len && self.get() == other.get()
69    }
70}
71impl Eq for Key {}
72
73impl std::borrow::Borrow<[u8]> for Key {
74    #[inline]
75    fn borrow(&self) -> &[u8] {
76        self.get()
77    }
78}
79
80impl std::hash::Hash for Key {
81    #[inline]
82    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
83        state.write(self.get());
84    }
85}
86
87impl Key {
88    #[inline]
89    const fn get(&self) -> &[u8] {
90        unsafe { std::slice::from_raw_parts(self.data.as_ptr().cast(), self.len as usize) }
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn test_sizes() {
100        assert_eq!(size_of::<Key>(), MAX_INPUT_LEN + 1);
101        assert_eq!(size_of::<fixed_cache::Bucket<(Key, B256)>>(), 128);
102    }
103}