xxhash_rust/
const_xxh3.rs

1//!Xxh3 `const fn` implementation
2//!
3//!This module is efficient only when hashes are guaranteed to be executed at compile time.
4//!At runtime algorithm is written in fairly inefficient code, although still fast enough.
5
6use core::mem;
7
8use crate::xxh32_common as xxh32;
9use crate::xxh64_common as xxh64;
10use crate::xxh3_common::*;
11
12const INITIAL_ACC: [u64; ACC_NB] = [
13    xxh32::PRIME_3 as u64, xxh64::PRIME_1, xxh64::PRIME_2, xxh64::PRIME_3,
14    xxh64::PRIME_4, xxh32::PRIME_2 as u64, xxh64::PRIME_5, xxh32::PRIME_1 as u64
15];
16
17#[inline(always)]
18const fn read_u32(input: &[u8], cursor: usize) -> u32 {
19    input[cursor] as u32 | (input[cursor + 1] as u32) << 8 | (input[cursor + 2] as u32) << 16 | (input[cursor + 3] as u32) << 24
20}
21
22
23#[inline(always)]
24const fn read_u64(input: &[u8], cursor: usize) -> u64 {
25    input[cursor] as u64
26        | (input[cursor + 1] as u64) << 8
27        | (input[cursor + 2] as u64) << 16
28        | (input[cursor + 3] as u64) << 24
29        | (input[cursor + 4] as u64) << 32
30        | (input[cursor + 5] as u64) << 40
31        | (input[cursor + 6] as u64) << 48
32        | (input[cursor + 7] as u64) << 56
33}
34
35#[inline(always)]
36const fn mult32_to64(left: u32, right: u32) -> u64 {
37    (left as u64).wrapping_mul(right as u64)
38}
39
40#[inline]
41const fn mix16_b(input: &[u8], input_offset: usize, secret: &[u8], secret_offset: usize, seed: u64) -> u64 {
42    let mut input_lo = read_u64(input, input_offset);
43    let mut input_hi = read_u64(input, input_offset + 8);
44
45    input_lo ^= read_u64(secret, secret_offset).wrapping_add(seed);
46    input_hi ^= read_u64(secret, secret_offset + 8).wrapping_sub(seed);
47
48    mul128_fold64(input_lo, input_hi)
49}
50
51#[allow(clippy::too_many_arguments)]
52#[inline]
53const fn mix32_b(mut acc: (u64, u64), input_1: &[u8], input_1_off: usize, input_2: &[u8], input_2_off: usize, secret: &[u8], secret_offset: usize, seed: u64) -> (u64, u64) {
54    acc.0 = acc.0.wrapping_add(mix16_b(input_1, input_1_off, secret, secret_offset, seed));
55    acc.0 ^= read_u64(input_2, input_2_off).wrapping_add(read_u64(input_2, input_2_off + 8));
56
57    acc.1 = acc.1.wrapping_add(mix16_b(input_2, input_2_off, secret, secret_offset + 16, seed));
58    acc.1 ^= read_u64(input_1, input_1_off).wrapping_add(read_u64(input_1, input_1_off + 8));
59
60    acc
61}
62
63#[inline(always)]
64const fn xxh3_64_9to16(input: &[u8], seed: u64, secret: &[u8]) -> u64 {
65    let flip1 = (read_u64(secret, 24) ^ read_u64(secret, 32)).wrapping_add(seed);
66    let flip2 = (read_u64(secret, 40) ^ read_u64(secret, 48)).wrapping_sub(seed);
67
68    let input_lo = read_u64(input, 0) ^ flip1;
69    let input_hi = read_u64(input, input.len() - 8) ^ flip2;
70
71    let acc = (input.len() as u64).wrapping_add(input_lo.swap_bytes())
72                                  .wrapping_add(input_hi)
73                                  .wrapping_add(mul128_fold64(input_lo, input_hi));
74
75    avalanche(acc)
76}
77
78#[inline(always)]
79const fn xxh3_64_4to8(input: &[u8], mut seed: u64, secret: &[u8]) -> u64 {
80    seed ^= ((seed as u32).swap_bytes() as u64) << 32;
81
82    let input1 = read_u32(input, 0);
83    let input2 = read_u32(input, input.len() - 4);
84
85    let flip = (read_u64(secret, 8) ^ read_u64(secret, 16)).wrapping_sub(seed);
86    let input64 = (input2 as u64).wrapping_add((input1 as u64) << 32);
87    let keyed = input64 ^ flip;
88
89    strong_avalanche(keyed, input.len() as u64)
90}
91
92#[inline(always)]
93const fn xxh3_64_1to3(input: &[u8], seed: u64, secret: &[u8]) -> u64 {
94    let combo = ((input[0] as u32) << 16)
95                | ((input[input.len() >> 1] as u32) << 24)
96                | (input[input.len() - 1] as u32)
97                | ((input.len() as u32) << 8);
98
99
100    let flip = ((read_u32(secret, 0) ^ read_u32(secret, 4)) as u64).wrapping_add(seed);
101    xxh64::avalanche((combo as u64) ^ flip)
102}
103
104#[inline(always)]
105const fn xxh3_64_0to16(input: &[u8], seed: u64, secret: &[u8]) -> u64 {
106    if input.len() > 8 {
107        xxh3_64_9to16(input, seed, secret)
108    } else if input.len() >= 4 {
109        xxh3_64_4to8(input, seed, secret)
110    } else if input.len() > 0 {
111        xxh3_64_1to3(input, seed, secret)
112    } else {
113        xxh64::avalanche(seed ^ read_u64(secret, 56) ^ read_u64(secret, 64))
114    }
115}
116
117#[inline(always)]
118const fn xxh3_64_7to128(input: &[u8], seed: u64, secret: &[u8]) -> u64 {
119    let mut acc = (input.len() as u64).wrapping_mul(xxh64::PRIME_1);
120
121    if input.len() > 32 {
122        if input.len() > 64 {
123            if input.len() > 96 {
124                acc = acc.wrapping_add(mix16_b(input, 48, secret, 96, seed));
125                acc = acc.wrapping_add(mix16_b(input, input.len()-64, secret, 112, seed));
126            }
127
128            acc = acc.wrapping_add(mix16_b(input, 32, secret, 64, seed));
129            acc = acc.wrapping_add(mix16_b(input, input.len()-48, secret, 80, seed));
130        }
131
132        acc = acc.wrapping_add(mix16_b(input, 16, secret, 32, seed));
133        acc = acc.wrapping_add(mix16_b(input, input.len()-32, secret, 48, seed));
134    }
135
136    acc = acc.wrapping_add(mix16_b(input, 0, secret, 0, seed));
137    acc = acc.wrapping_add(mix16_b(input, input.len()-16, secret, 16, seed));
138
139    avalanche(acc)
140}
141
142const fn xxh3_64_129to240(input: &[u8], seed: u64, secret: &[u8]) -> u64 {
143    const START_OFFSET: usize = 3;
144    const LAST_OFFSET: usize = 17;
145
146    let mut acc = (input.len() as u64).wrapping_mul(xxh64::PRIME_1);
147    let nb_rounds = input.len() / 16;
148
149    let mut idx = 0;
150    while idx < 8 {
151        acc = acc.wrapping_add(mix16_b(input, 16*idx, secret, 16*idx, seed));
152        idx += 1;
153    }
154    acc = avalanche(acc);
155
156    while idx < nb_rounds {
157        acc = acc.wrapping_add(mix16_b(input, 16*idx, secret, 16*(idx-8)+START_OFFSET, seed));
158        idx += 1;
159    }
160
161    acc = acc.wrapping_add(mix16_b(input, input.len()-16, secret, SECRET_SIZE_MIN-LAST_OFFSET, seed));
162
163    avalanche(acc)
164}
165
166#[inline(always)]
167const fn mix_two_accs(acc: &[u64], acc_offset: usize, secret: &[u8], secret_offset: usize) -> u64 {
168    mul128_fold64(acc[acc_offset] ^ read_u64(secret, secret_offset),
169                  acc[acc_offset + 1] ^ read_u64(secret, secret_offset + 8))
170}
171
172#[inline]
173const fn merge_accs(acc: &[u64], secret: &[u8], secret_offset: usize, mut result: u64) -> u64 {
174    let mut idx = 0;
175    while idx < 4 {
176        result = result.wrapping_add(mix_two_accs(acc, idx * 2, secret, secret_offset + idx * 16));
177        idx += 1;
178    }
179
180    avalanche(result)
181}
182
183const fn scramble_acc(mut acc: [u64; ACC_NB], secret: &[u8], secret_offset: usize) -> [u64; ACC_NB] {
184    let mut idx = 0;
185
186    while idx < ACC_NB {
187        let key = read_u64(secret, secret_offset + 8 * idx);
188        let mut acc_val = xorshift64(acc[idx], 47);
189        acc_val ^= key;
190        acc[idx] = acc_val.wrapping_mul(xxh32::PRIME_1 as u64);
191
192        idx += 1;
193    }
194
195    acc
196}
197
198const fn accumulate_512(mut acc: [u64; ACC_NB], input: &[u8], input_offset: usize, secret: &[u8], secret_offset: usize) -> [u64; ACC_NB] {
199    let mut idx = 0;
200    while idx < ACC_NB {
201        let data_val = read_u64(input, input_offset + 8 * idx);
202        let data_key = data_val ^ read_u64(secret, secret_offset + 8 * idx);
203
204        acc[idx ^ 1] = acc[idx ^ 1].wrapping_add(data_val);
205        acc[idx] = acc[idx].wrapping_add(mult32_to64((data_key & 0xFFFFFFFF) as u32, (data_key >> 32) as u32));
206
207        idx += 1;
208    }
209
210    acc
211}
212
213#[inline(always)]
214const fn accumulate_loop(mut acc: [u64; ACC_NB], input: &[u8], input_offset: usize, secret: &[u8], secret_offset: usize, nb_stripes: usize) -> [u64; ACC_NB] {
215    let mut idx = 0;
216    while idx < nb_stripes {
217        acc = accumulate_512(acc, input, input_offset + idx * STRIPE_LEN, secret, secret_offset + idx * SECRET_CONSUME_RATE);
218
219        idx += 1;
220    }
221
222    acc
223}
224
225#[inline]
226const fn hash_long_internal_loop(input: &[u8], secret: &[u8]) -> [u64; ACC_NB] {
227    let mut acc = INITIAL_ACC;
228
229    let nb_stripes = (secret.len() - STRIPE_LEN) / SECRET_CONSUME_RATE;
230    let block_len = STRIPE_LEN * nb_stripes;
231    let nb_blocks = (input.len() - 1) / block_len;
232
233    let mut idx = 0;
234    while idx < nb_blocks {
235        acc = accumulate_loop(acc, input, idx * block_len, secret, 0, nb_stripes);
236        acc = scramble_acc(acc, secret, secret.len() - STRIPE_LEN);
237
238        idx += 1;
239    }
240
241    let nb_stripes = ((input.len() - 1) - (block_len * nb_blocks)) / STRIPE_LEN;
242
243    acc = accumulate_loop(acc, input, nb_blocks * block_len, secret, 0, nb_stripes);
244    accumulate_512(acc, input, input.len() - STRIPE_LEN, secret, secret.len() - STRIPE_LEN - SECRET_LASTACC_START)
245}
246
247const fn xxh3_64_long_impl(input: &[u8], secret: &[u8]) -> u64 {
248    let acc = hash_long_internal_loop(input, secret);
249
250    merge_accs(&acc, secret, SECRET_MERGEACCS_START, (input.len() as u64).wrapping_mul(xxh64::PRIME_1))
251}
252
253#[inline(always)]
254///Returns 64bit hash for provided input.
255pub const fn xxh3_64(input: &[u8]) -> u64 {
256    xxh3_64_with_seed(input, 0)
257}
258
259///Returns 64bit hash for provided input using seed.
260pub const fn xxh3_64_with_seed(input: &[u8], seed: u64) -> u64 {
261    if input.len() <= 16 {
262        xxh3_64_0to16(input, seed, &DEFAULT_SECRET)
263    } else if input.len() <= 128 {
264        xxh3_64_7to128(input, seed, &DEFAULT_SECRET)
265    } else if input.len() <= MID_SIZE_MAX {
266        xxh3_64_129to240(input, seed, &DEFAULT_SECRET)
267    } else {
268        xxh3_64_long_impl(input, &const_custom_default_secret(seed))
269    }
270}
271
272///Returns 64bit hash for provided input using custom secret.
273pub const fn xxh3_64_with_secret(input: &[u8], secret: &[u8; DEFAULT_SECRET_SIZE]) -> u64 {
274    if input.len() <= 16 {
275        xxh3_64_0to16(input, 0, secret)
276    } else if input.len() <= 128 {
277        xxh3_64_7to128(input, 0, secret)
278    } else if input.len() <= MID_SIZE_MAX {
279        xxh3_64_129to240(input, 0, secret)
280    } else {
281        xxh3_64_long_impl(input, secret)
282    }
283}
284
285//
286//128bit
287//
288
289#[inline(always)]
290const fn xxh3_128_1to3(input: &[u8], seed: u64, secret: &[u8]) -> u128 {
291    let c1 = input[0];
292    let c2 = input[input.len() >> 1];
293    let c3 = input[input.len() - 1];
294    let input_lo = (c1 as u32) << 16 | (c2 as u32) << 24 | c3 as u32 | (input.len() as u32) << 8;
295    let input_hi = input_lo.swap_bytes().rotate_left(13);
296
297    let flip_lo = (read_u32(secret, 0) as u64 ^ read_u32(secret, 4) as u64).wrapping_add(seed);
298    let flip_hi = (read_u32(secret, 8) as u64 ^ read_u32(secret, 12) as u64).wrapping_sub(seed);
299    let keyed_lo = input_lo as u64 ^ flip_lo;
300    let keyed_hi = input_hi as u64 ^ flip_hi;
301
302    xxh64::avalanche(keyed_lo) as u128 | (xxh64::avalanche(keyed_hi) as u128) << 64
303}
304
305#[inline(always)]
306const fn xxh3_128_4to8(input: &[u8], mut seed: u64, secret: &[u8]) -> u128 {
307    seed ^= ((seed as u32).swap_bytes() as u64) << 32;
308
309    let lo = read_u32(input, 0);
310    let hi = read_u32(input, input.len() - 4);
311    let input_64 = (lo as u64).wrapping_add((hi as u64) << 32);
312
313    let flip = (read_u64(secret, 16) ^ read_u64(secret, 24)).wrapping_add(seed);
314    let keyed = input_64 ^ flip;
315
316    let (mut lo, mut hi) = mul64_to128(keyed, xxh64::PRIME_1.wrapping_add((input.len() as u64) << 2));
317
318    hi = hi.wrapping_add(lo << 1);
319    lo ^= hi >> 3;
320
321    lo = xorshift64(lo, 35).wrapping_mul(0x9FB21C651E98DF25);
322    lo = xorshift64(lo, 28);
323    hi = avalanche(hi);
324
325    lo as u128 | (hi as u128) << 64
326}
327
328#[inline(always)]
329const fn xxh3_128_9to16(input: &[u8], seed: u64, secret: &[u8]) -> u128 {
330    let flip_lo = (read_u64(secret, 32) ^ read_u64(secret, 40)).wrapping_sub(seed);
331    let flip_hi = (read_u64(secret, 48) ^ read_u64(secret, 56)).wrapping_add(seed);
332    let input_lo = read_u64(input, 0);
333    let mut input_hi = read_u64(input, input.len() - 8);
334
335    let (mut mul_low, mut mul_high) = mul64_to128(input_lo ^ input_hi ^ flip_lo, xxh64::PRIME_1);
336
337    mul_low = mul_low.wrapping_add((input.len() as u64 - 1) << 54);
338    input_hi ^= flip_hi;
339    mul_high = mul_high.wrapping_add(
340        input_hi.wrapping_add(mult32_to64(input_hi as u32, xxh32::PRIME_2 - 1))
341    );
342
343    mul_low ^= mul_high.swap_bytes();
344
345    let (result_low, mut result_hi) = mul64_to128(mul_low, xxh64::PRIME_2);
346    result_hi = result_hi.wrapping_add(
347        mul_high.wrapping_mul(xxh64::PRIME_2)
348    );
349
350    avalanche(result_low) as u128 | (avalanche(result_hi) as u128) << 64
351}
352
353#[inline(always)]
354const fn xxh3_128_0to16(input: &[u8], seed: u64, secret: &[u8]) -> u128 {
355    if input.len() > 8 {
356        xxh3_128_9to16(input, seed, secret)
357    } else if input.len() >= 4 {
358        xxh3_128_4to8(input, seed, secret)
359    } else if input.len() > 0 {
360        xxh3_128_1to3(input, seed, secret)
361    } else {
362        let flip_lo = read_u64(secret, 64) ^ read_u64(secret, 72);
363        let flip_hi = read_u64(secret, 80) ^ read_u64(secret, 88);
364        xxh64::avalanche(seed ^ flip_lo) as u128 | (xxh64::avalanche(seed ^ flip_hi) as u128) << 64
365    }
366}
367
368#[inline(always)]
369const fn xxh3_128_7to128(input: &[u8], seed: u64, secret: &[u8]) -> u128 {
370    let mut acc = ((input.len() as u64).wrapping_mul(xxh64::PRIME_1), 0u64);
371
372    if input.len() > 32 {
373        if input.len() > 64 {
374            if input.len() > 96 {
375                acc = mix32_b(acc, input, 48, input, input.len() - 64, secret, 96, seed);
376            }
377
378            acc = mix32_b(acc, input, 32, input, input.len() - 48, secret, 64, seed);
379        }
380
381        acc = mix32_b(acc, input, 16, input, input.len() - 32, secret, 32, seed);
382    }
383
384    acc = mix32_b(acc, input, 0, input, input.len() - 16, secret, 0, seed);
385
386    let result_lo = acc.0.wrapping_add(acc.1);
387    let result_hi = acc.0.wrapping_mul(xxh64::PRIME_1)
388                         .wrapping_add(acc.1.wrapping_mul(xxh64::PRIME_4))
389                         .wrapping_add((input.len() as u64).wrapping_sub(seed).wrapping_mul(xxh64::PRIME_2));
390
391    avalanche(result_lo) as u128 | (0u64.wrapping_sub(avalanche(result_hi)) as u128) << 64
392}
393
394#[inline(never)]
395const fn xxh3_128_129to240(input: &[u8], seed: u64, secret: &[u8]) -> u128 {
396    const START_OFFSET: usize = 3;
397    const LAST_OFFSET: usize = 17;
398    let nb_rounds = input.len() / 32;
399
400    let mut acc = ((input.len() as u64).wrapping_mul(xxh64::PRIME_1), 0u64);
401
402    let mut idx = 0;
403    while idx < 4 {
404        acc = mix32_b(acc, input, 32 * idx, input, (32 * idx) + 16, secret, 32 * idx, seed);
405        idx += 1;
406    }
407
408    acc.0 = avalanche(acc.0);
409    acc.1 = avalanche(acc.1);
410
411    while idx < nb_rounds {
412        acc = mix32_b(acc, input, 32 * idx, input, (32 * idx) + 16, secret, START_OFFSET.wrapping_add(32 * (idx - 4)), seed);
413        idx += 1;
414    }
415
416    acc = mix32_b(acc, input, input.len() - 16, input, input.len() - 32, secret, SECRET_SIZE_MIN - LAST_OFFSET - 16, 0u64.wrapping_sub(seed));
417    let result_lo = acc.0.wrapping_add(acc.1);
418    let result_hi = acc.0.wrapping_mul(xxh64::PRIME_1)
419                         .wrapping_add(acc.1.wrapping_mul(xxh64::PRIME_4))
420                         .wrapping_add((input.len() as u64).wrapping_sub(seed).wrapping_mul(xxh64::PRIME_2));
421
422    avalanche(result_lo) as u128 | 0u128.wrapping_sub(avalanche(result_hi) as u128) << 64
423}
424
425const fn xxh3_128_long_impl(input: &[u8], secret: &[u8]) -> u128 {
426    let acc = hash_long_internal_loop(input, secret);
427
428    let lo = merge_accs(&acc, secret, SECRET_MERGEACCS_START, (input.len() as u64).wrapping_mul(xxh64::PRIME_1));
429    let hi = merge_accs(&acc,
430                        secret, secret.len() - ACC_NB * mem::size_of::<u64>() - SECRET_MERGEACCS_START,
431                        !(input.len() as u64).wrapping_mul(xxh64::PRIME_2));
432
433    lo as u128 | (hi as u128) << 64
434}
435
436#[inline(always)]
437///Returns 128 hash for provided input.
438pub const fn xxh3_128(input: &[u8]) -> u128 {
439    xxh3_128_with_seed(input, 0)
440}
441
442///Returns 128 hash for provided input using seed.
443pub const fn xxh3_128_with_seed(input: &[u8], seed: u64) -> u128 {
444    if input.len() <= 16 {
445        xxh3_128_0to16(input, seed, &DEFAULT_SECRET)
446    } else if input.len() <= 128 {
447        xxh3_128_7to128(input, seed, &DEFAULT_SECRET)
448    } else if input.len() <= MID_SIZE_MAX {
449        xxh3_128_129to240(input, seed, &DEFAULT_SECRET)
450    } else {
451        xxh3_128_long_impl(input, &const_custom_default_secret(seed))
452    }
453}
454
455///Returns 128 hash for provided input using custom secret.
456pub const fn xxh3_128_with_secret(input: &[u8], secret: &[u8; DEFAULT_SECRET_SIZE]) -> u128 {
457    if input.len() <= 16 {
458        xxh3_128_0to16(input, 0, secret)
459    } else if input.len() <= 128 {
460        xxh3_128_7to128(input, 0, secret)
461    } else if input.len() <= MID_SIZE_MAX {
462        xxh3_128_129to240(input, 0, secret)
463    } else {
464        xxh3_128_long_impl(input, secret)
465    }
466}
467
468//Const version is only efficient when it is actually executed at compile time
469#[inline(always)]
470///Generates secret derived from provided seed and default secret.
471///
472///Efficient when executed at compile time as alternative to using version of algorithm with custom `seed`
473pub const fn const_custom_default_secret(seed: u64) -> [u8; DEFAULT_SECRET_SIZE] {
474    if seed == 0 {
475        return DEFAULT_SECRET;
476    }
477
478    #[inline(always)]
479    const fn read_u64(input: &[u8], cursor: usize) -> u64 {
480        input[cursor] as u64
481            | (input[cursor + 1] as u64) << 8
482            | (input[cursor + 2] as u64) << 16
483            | (input[cursor + 3] as u64) << 24
484            | (input[cursor + 4] as u64) << 32
485            | (input[cursor + 5] as u64) << 40
486            | (input[cursor + 6] as u64) << 48
487            | (input[cursor + 7] as u64) << 56
488    }
489
490    let mut idx = 0;
491    let mut result = [0; DEFAULT_SECRET_SIZE];
492    const NB_ROUNDS: usize = DEFAULT_SECRET_SIZE / 16;
493
494    while idx < NB_ROUNDS {
495        let lo = read_u64(&DEFAULT_SECRET, idx * 16).wrapping_add(seed).to_le_bytes();
496        let hi = read_u64(&DEFAULT_SECRET, idx * 16 + 8).wrapping_sub(seed).to_le_bytes();
497
498        result[idx * 16] = lo[0];
499        result[idx * 16 + 1] = lo[1];
500        result[idx * 16 + 2] = lo[2];
501        result[idx * 16 + 3] = lo[3];
502        result[idx * 16 + 4] = lo[4];
503        result[idx * 16 + 5] = lo[5];
504        result[idx * 16 + 6] = lo[6];
505        result[idx * 16 + 7] = lo[7];
506
507        result[idx * 16 + 8] = hi[0];
508        result[idx * 16 + 8 + 1] = hi[1];
509        result[idx * 16 + 8 + 2] = hi[2];
510        result[idx * 16 + 8 + 3] = hi[3];
511        result[idx * 16 + 8 + 4] = hi[4];
512        result[idx * 16 + 8 + 5] = hi[5];
513        result[idx * 16 + 8 + 6] = hi[6];
514        result[idx * 16 + 8 + 7] = hi[7];
515
516        idx += 1;
517    }
518
519    result
520}