Skip to main content

rapidhash/v2/
rapid_const.rs

1use crate::util::mix::{rapid_mix, rapid_mum};
2use crate::util::read::{read_u32, read_u64};
3use super::{DEFAULT_RAPID_SECRETS, RapidSecrets};
4
5/// Rapidhash V2.2 a single byte stream, matching the C++ implementation, with the default seed.
6///
7/// See [rapidhash_v2_inline] to compute the hash value using V2.0 or V2.2.
8///
9/// Fixed length inputs will greatly benefit from inlining with [rapidhash_v2_inline] instead.
10#[inline]
11pub const fn rapidhash_v2_2(data: &[u8]) -> u64 {
12    rapidhash_v2_inline::<2, true, false, false>(data, &DEFAULT_RAPID_SECRETS)
13}
14
15/// Rapidhash V2.2 a single byte stream, matching the C++ implementation, with a custom seed.
16///
17/// See [rapidhash_v2_inline] to compute the hash value using V2.0 or V2.2.
18///
19/// Fixed length inputs will greatly benefit from inlining with [rapidhash_v2_inline] instead.
20#[inline]
21pub const fn rapidhash_v2_2_seeded(data: &[u8], secrets: &RapidSecrets) -> u64 {
22    rapidhash_v2_inline::<2, true, false, false>(data, secrets)
23}
24
25/// Rapidhash V2 a single byte stream, matching the C++ implementation.
26///
27/// Is marked with `#[inline(always)]` to force the compiler to inline and optimize the method.
28/// Can provide large performance uplifts for fixed-length inputs at compile time.
29///
30/// Compile time arguments:
31/// - `MINOR`: the minor version of the rapidhash algorithm:
32///   - 0: v2.0
33///   - 1: v2.1
34///   - 2: v2.2
35/// - `AVALANCHE`: Perform an extra mix step to avalanche the bits for higher hash quality. Enabled
36///   by default to match the C++ implementation.
37/// - `COMPACT`: Generates fewer instructions at compile time with less manual loop unrolling, but
38///   may be slower on some platforms. Disabled by default.
39/// - `PROTECTED`: Slightly stronger hash quality and DoS resistance by performing two extra XOR
40///   instructions on every mix step. Disabled by default.
41#[inline(always)]
42pub const fn rapidhash_v2_inline<const MINOR: u8, const AVALANCHE: bool, const COMPACT: bool, const PROTECTED: bool>(data: &[u8], secrets: &RapidSecrets) -> u64 {
43    rapidhash_core::<MINOR, AVALANCHE, COMPACT, PROTECTED>(secrets.seed, &secrets.secrets, data)
44}
45
46#[inline(always)]
47pub(super) const fn rapidhash_core<const MINOR: u8, const AVALANCHE: bool, const COMPACT: bool, const PROTECTED: bool>(mut seed: u64, secrets: &[u64; 7], data: &[u8]) -> u64 {
48    if MINOR > 2 {
49        panic!("rapidhash_core unsupported minor version. Supported versions are 0, 1, and 2.");
50    }
51
52    let mut a = 0;
53    let mut b = 0;
54    seed ^= data.len() as u64;
55
56    if data.len() <= 16 {
57        if data.len() >= 4 {
58            if data.len() >= 8 {
59                let plast = data.len() - 8;
60                a ^= read_u64(data, 0);
61                b ^= read_u64(data, plast);
62            } else {
63                let plast = data.len() - 4;
64                a ^= read_u32(data, 0) as u64;
65                b ^= read_u32(data, plast) as u64;
66            }
67        } else if !data.is_empty() {
68            if MINOR < 2 {
69                a ^= ((data[0] as u64) << 56) | ((data[data.len() >> 1] as u64) << 32) | data[data.len() - 1] as u64;
70            } else {
71                a ^= ((data[0] as u64) << 56) | data[data.len() - 1] as u64;
72                b ^= data[data.len() >> 1] as u64;
73            }
74        }
75    } else if (MINOR == 0 && data.len() <= 56) || (MINOR > 0 && data.len() <= 64) {
76        // len is 17..=64
77        return rapidhash_core_17_64::<MINOR, AVALANCHE, PROTECTED>(seed, secrets, data);
78    } else {
79        return rapidhash_core_cold::<AVALANCHE, COMPACT, PROTECTED>(seed, secrets, data);
80    }
81
82    a ^= secrets[1];
83    b ^= seed;
84
85    (a, b) = rapid_mum::<PROTECTED>(a, b);
86
87    if AVALANCHE {
88        rapidhash_finish::<PROTECTED>(a, b, data.len() as u64, secrets)
89    } else {
90        a ^ b
91    }
92}
93
94#[inline]  // intentionally not always
95const fn rapidhash_core_17_64<const MINOR: u8, const AVALANCHE: bool, const PROTECTED: bool>(mut seed: u64, secrets: &[u64; 7], data: &[u8]) -> u64 {
96    let mut a = 0;
97    let mut b = 0;
98
99    let slice = data;
100
101    seed = rapid_mix::<PROTECTED>(read_u64(slice, 0) ^ secrets[0], read_u64(slice, 8) ^ seed);
102    if slice.len() > 32 {
103        seed = rapid_mix::<PROTECTED>(read_u64(slice, 16) ^ secrets[1], read_u64(slice, 24) ^ seed);
104        if slice.len() > 48 {
105            let index: usize = if MINOR < 2 { 0 } else { 1 };
106            seed = rapid_mix::<PROTECTED>(read_u64(slice, 32) ^ secrets[index], read_u64(slice, 40) ^ seed);
107        }
108    }
109
110    a ^= read_u64(data, data.len() - 16);
111    b ^= read_u64(data, data.len() - 8);
112
113    a ^= secrets[1];
114    b ^= seed;
115
116    (a, b) = rapid_mum::<PROTECTED>(a, b);
117
118    if AVALANCHE {
119        rapidhash_finish::<PROTECTED>(a, b, data.len() as u64, secrets)
120    } else {
121        a ^ b
122    }
123}
124
125/// The long path, intentionally kept cold because at this length of data the function call is
126/// minor, but the complexity of this function — if it were inlined — could prevent x.hash() from
127/// being inlined which would have a much higher penalty and prevent other optimisations.
128#[cold]
129const fn rapidhash_core_cold<const AVALANCHE: bool, const COMPACT: bool, const PROTECTED: bool>(mut seed: u64, secrets: &[u64; 7], data: &[u8]) -> u64 {
130    let mut a = 0;
131    let mut b = 0;
132
133    let mut slice = data;
134
135    // most CPUs appear to benefit from this unrolled loop
136    let mut see1 = seed;
137    let mut see2 = seed;
138    let mut see3 = seed;
139    let mut see4 = seed;
140    let mut see5 = seed;
141    let mut see6 = seed;
142
143    if !COMPACT {
144        while slice.len() >= 224 {
145            seed = rapid_mix::<PROTECTED>(read_u64(slice, 0) ^ secrets[0], read_u64(slice, 8) ^ seed);
146            see1 = rapid_mix::<PROTECTED>(read_u64(slice, 16) ^ secrets[1], read_u64(slice, 24) ^ see1);
147            see2 = rapid_mix::<PROTECTED>(read_u64(slice, 32) ^ secrets[2], read_u64(slice, 40) ^ see2);
148            see3 = rapid_mix::<PROTECTED>(read_u64(slice, 48) ^ secrets[3], read_u64(slice, 56) ^ see3);
149            see4 = rapid_mix::<PROTECTED>(read_u64(slice, 64) ^ secrets[4], read_u64(slice, 72) ^ see4);
150            see5 = rapid_mix::<PROTECTED>(read_u64(slice, 80) ^ secrets[5], read_u64(slice, 88) ^ see5);
151            see6 = rapid_mix::<PROTECTED>(read_u64(slice, 96) ^ secrets[6], read_u64(slice, 104) ^ see6);
152
153            seed = rapid_mix::<PROTECTED>(read_u64(slice, 112) ^ secrets[0], read_u64(slice, 120) ^ seed);
154            see1 = rapid_mix::<PROTECTED>(read_u64(slice, 128) ^ secrets[1], read_u64(slice, 136) ^ see1);
155            see2 = rapid_mix::<PROTECTED>(read_u64(slice, 144) ^ secrets[2], read_u64(slice, 152) ^ see2);
156            see3 = rapid_mix::<PROTECTED>(read_u64(slice, 160) ^ secrets[3], read_u64(slice, 168) ^ see3);
157            see4 = rapid_mix::<PROTECTED>(read_u64(slice, 176) ^ secrets[4], read_u64(slice, 184) ^ see4);
158            see5 = rapid_mix::<PROTECTED>(read_u64(slice, 192) ^ secrets[5], read_u64(slice, 200) ^ see5);
159            see6 = rapid_mix::<PROTECTED>(read_u64(slice, 208) ^ secrets[6], read_u64(slice, 216) ^ see6);
160
161            let (_, split) = slice.split_at(224);
162            slice = split;
163        }
164
165        if slice.len() >= 112 {
166            seed = rapid_mix::<PROTECTED>(read_u64(slice, 0) ^ secrets[0], read_u64(slice, 8) ^ seed);
167            see1 = rapid_mix::<PROTECTED>(read_u64(slice, 16) ^ secrets[1], read_u64(slice, 24) ^ see1);
168            see2 = rapid_mix::<PROTECTED>(read_u64(slice, 32) ^ secrets[2], read_u64(slice, 40) ^ see2);
169            see3 = rapid_mix::<PROTECTED>(read_u64(slice, 48) ^ secrets[3], read_u64(slice, 56) ^ see3);
170            see4 = rapid_mix::<PROTECTED>(read_u64(slice, 64) ^ secrets[4], read_u64(slice, 72) ^ see4);
171            see5 = rapid_mix::<PROTECTED>(read_u64(slice, 80) ^ secrets[5], read_u64(slice, 88) ^ see5);
172            see6 = rapid_mix::<PROTECTED>(read_u64(slice, 96) ^ secrets[6], read_u64(slice, 104) ^ see6);
173            let (_, split) = slice.split_at(112);
174            slice = split;
175        }
176
177        if slice.len() >= 48 {
178            seed = rapid_mix::<PROTECTED>(read_u64(slice, 0) ^ secrets[0], read_u64(slice, 8) ^ seed);
179            see1 = rapid_mix::<PROTECTED>(read_u64(slice, 16) ^ secrets[1], read_u64(slice, 24) ^ see1);
180            see2 = rapid_mix::<PROTECTED>(read_u64(slice, 32) ^ secrets[2], read_u64(slice, 40) ^ see2);
181            let (_, split) = slice.split_at(48);
182            slice = split;
183
184            if slice.len() >= 48 {
185                seed = rapid_mix::<PROTECTED>(read_u64(slice, 0) ^ secrets[0], read_u64(slice, 8) ^ seed);
186                see1 = rapid_mix::<PROTECTED>(read_u64(slice, 16) ^ secrets[1], read_u64(slice, 24) ^ see1);
187                see2 = rapid_mix::<PROTECTED>(read_u64(slice, 32) ^ secrets[2], read_u64(slice, 40) ^ see2);
188                let (_, split) = slice.split_at(48);
189                slice = split;
190            }
191        }
192    } else {
193        while slice.len() >= 112 {
194            seed = rapid_mix::<PROTECTED>(read_u64(slice, 0) ^ secrets[0], read_u64(slice, 8) ^ seed);
195            see1 = rapid_mix::<PROTECTED>(read_u64(slice, 16) ^ secrets[1], read_u64(slice, 24) ^ see1);
196            see2 = rapid_mix::<PROTECTED>(read_u64(slice, 32) ^ secrets[2], read_u64(slice, 40) ^ see2);
197            see3 = rapid_mix::<PROTECTED>(read_u64(slice, 48) ^ secrets[3], read_u64(slice, 56) ^ see3);
198            see4 = rapid_mix::<PROTECTED>(read_u64(slice, 64) ^ secrets[4], read_u64(slice, 72) ^ see4);
199            see5 = rapid_mix::<PROTECTED>(read_u64(slice, 80) ^ secrets[5], read_u64(slice, 88) ^ see5);
200            see6 = rapid_mix::<PROTECTED>(read_u64(slice, 96) ^ secrets[6], read_u64(slice, 104) ^ see6);
201            let (_, split) = slice.split_at(112);
202            slice = split;
203        }
204
205        while slice.len() >= 48 {
206            seed = rapid_mix::<PROTECTED>(read_u64(slice, 0) ^ secrets[0], read_u64(slice, 8) ^ seed);
207            see1 = rapid_mix::<PROTECTED>(read_u64(slice, 16) ^ secrets[1], read_u64(slice, 24) ^ see1);
208            see2 = rapid_mix::<PROTECTED>(read_u64(slice, 32) ^ secrets[2], read_u64(slice, 40) ^ see2);
209            let (_, split) = slice.split_at(48);
210            slice = split;
211        }
212    }
213
214    see3 ^= see4;
215    see5 ^= see6;
216    seed ^= see1;
217    see3 ^= see2;
218    seed ^= see5;
219    seed ^= see3;
220
221    if slice.len() > 16 {
222        seed = rapid_mix::<PROTECTED>(read_u64(slice, 0) ^ secrets[2], read_u64(slice, 8) ^ seed);
223        if slice.len() > 32 {
224            seed = rapid_mix::<PROTECTED>(read_u64(slice, 16) ^ secrets[2], read_u64(slice, 24) ^ seed);
225        }
226    }
227
228    a ^= read_u64(data, data.len() - 16);
229    b ^= read_u64(data, data.len() - 8);
230
231    a ^= secrets[1];
232    b ^= seed;
233
234    (a, b) = rapid_mum::<PROTECTED>(a, b);
235
236    if AVALANCHE {
237        rapidhash_finish::<PROTECTED>(a, b, data.len() as u64, secrets)
238    } else {
239        a ^ b
240    }
241}
242
243#[inline(always)]
244pub(super) const fn rapidhash_finish<const PROTECTED: bool>(a: u64, b: u64, len: u64, secrets: &[u64; 7]) -> u64 {
245    rapid_mix::<PROTECTED>(a ^ 0xaaaaaaaaaaaaaaaa ^ len, b ^ secrets[1])
246}