xxhash/xxhash.rs
1#![no_std]
2#![allow(unexpected_cfgs)]
3
4//! xxHash — extremely fast non-cryptographic hash algorithm.
5//!
6//! Provides four variants:
7//!
8//! | Struct | Bits | Description |
9//! |--------|------|-------------|
10//! | [`Xxh3_64`] | 64 | Recommended — XXH3 algorithm, faster on modern CPUs |
11//! | [`Xxh3_128`] | 128 | Recommended — XXH3 algorithm, 128-bit output |
12//! | [`Xxh32`] | 32 | Classic xxHash (XXH32) |
13//! | [`Xxh64`] | 64 | Classic xxHash (XXH64) |
14//!
15//! **Prefer [`Xxh3_64`] or [`Xxh3_128`] for new code.** XXH3 is the modern
16//! variant and is around twice as fast as the classic XXH64 on large inputs,
17//! with better hash quality.
18//!
19//! All types implement the [`Checksum`] trait. Incremental hashing agrees with
20//! one-shot hashing for every input length and every split of the input across
21//! [`update`](Checksum::update) calls, and no input can make any of these
22//! functions panic.
23//!
24//! Results are identical on every platform and match the
25//! [reference implementation](https://github.com/Cyan4973/xxHash).
26//!
27//! # SIMD acceleration
28//!
29//! The long-input path of XXH3 is accelerated with NEON (aarch64), AVX2 /
30//! AVX-512 (x86) or SIMD128 (wasm32) kernels:
31//!
32//! - With the (default) `std` feature enabled, the kernel is chosen at runtime
33//! from the CPU features actually available, so a binary compiled for a
34//! baseline target still uses the widest instructions the machine supports.
35//! - With `default-features = false` (i.e. `no_std`), the kernel is chosen at
36//! compile time from the enabled `target-feature`s; pass e.g.
37//! `RUSTFLAGS="-C target-feature=+avx2"` to enable them.
38//!
39//! The [`const`](xxh3_64) one-shot functions are scalar-only, so prefer
40//! [`Checksum::checksum`] or [`Checksum::update`] for long inputs when runtime
41//! performance matters. Both produce the same value.
42//!
43//! # Security
44//!
45//! xxHash is **not** a cryptographic hash. It must not be used to authenticate
46//! messages, derive keys, or store passwords, and a seed or a custom secret is
47//! not a security boundary. Its collision resistance is not sufficient to
48//! protect a hash table from attacker-chosen keys (hash flooding): XXH3 in
49//! particular has known collision weaknesses for 17..=240 byte inputs. For
50//! those uses, prefer a keyed cryptographic hash.
51//!
52//! # Const one-shot hashing
53//!
54//! Use the free functions [`xxh32`], [`xxh64`], [`xxh3_64`], and
55//! [`xxh3_128`] for `const`-compatible one-shot hashing:
56//!
57//! ```rust
58//! use xxhash::xxh3_64;
59//!
60//! const HASH: u64 = xxh3_64(b"hello");
61//! assert_eq!(HASH, 0x9555E8555C62DCFD);
62//! ```
63//!
64//! # Examples
65//!
66//! ## One-shot hashing (via trait)
67//!
68//! ```rust
69//! use xxhash::{Xxh3_64, Checksum};
70//!
71//! let hash: u64 = Xxh3_64::checksum(b"hello world");
72//! ```
73//!
74//! ## Incremental hashing
75//!
76//! ```rust
77//! use xxhash::{Xxh3_128, Checksum};
78//!
79//! let mut hasher = Xxh3_128::new();
80//! hasher.update(b"hello ");
81//! hasher.update(b"world");
82//! let hash: u128 = hasher.sum();
83//! ```
84//!
85//! ## Seeded hashing
86//!
87//! ```rust
88//! use xxhash::{Xxh3_64, Checksum};
89//!
90//! let mut hasher = Xxh3_64::with_seed(42);
91//! hasher.update(b"data");
92//! let hash: u64 = hasher.sum();
93//! ```
94//!
95//! ## XXH3 with a custom secret
96//!
97//! ```rust
98//! use xxhash::{Xxh3_64, Checksum};
99//!
100//! let secret = [0xAB; 192];
101//! let mut hasher = Xxh3_64::with_secret(secret);
102//! hasher.update(b"data");
103//! let hash: u64 = hasher.sum();
104//! ```
105
106use core::fmt;
107
108// Runtime CPU detection requires the standard library. This crate is `no_std`
109// by default; enabling the (default) `std` feature makes the SIMD kernels
110// reachable on any CPU that supports them, without requiring `RUSTFLAGS`.
111#[cfg(feature = "std")]
112extern crate std;
113
114// SIMD kernels are compiled when the matching target feature is enabled at
115// compile time, and — for x86, when the `std` feature is on — also when it is
116// not, so that they can be selected at runtime by `x86_dispatch`.
117#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
118#[path = "xxh3_neon.rs"]
119mod xxh3_neon;
120
121#[cfg(all(target_arch = "x86_64", any(target_feature = "avx2", feature = "std")))]
122#[path = "xxh3_avx2.rs"]
123mod xxh3_avx2;
124
125#[cfg(all(target_arch = "x86_64", any(target_feature = "avx512f", feature = "std")))]
126#[path = "xxh3_avx512.rs"]
127mod xxh3_avx512;
128
129#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
130#[path = "xxh3_wasm_simd128.rs"]
131mod xxh3_wasm_simd128;
132
133/// Cached runtime CPU feature detection for x86 targets (requires `std`).
134///
135/// The detected level is computed once and cached in an atomic, so the cost per
136/// lookup is a single relaxed load plus a branch.
137#[cfg(all(target_arch = "x86_64", feature = "std"))]
138mod x86_dispatch {
139 use core::sync::atomic::{AtomicU8, Ordering};
140
141 #[derive(Clone, Copy, PartialEq, Eq)]
142 pub(crate) enum Level {
143 Scalar,
144 Avx2,
145 Avx512,
146 }
147
148 const UNKNOWN: u8 = 0;
149 const SCALAR: u8 = 1;
150 const AVX2: u8 = 2;
151 const AVX512: u8 = 3;
152
153 static LEVEL: AtomicU8 = AtomicU8::new(UNKNOWN);
154
155 #[inline]
156 pub(crate) fn level() -> Level {
157 let cached = LEVEL.load(Ordering::Relaxed);
158 if cached != UNKNOWN {
159 return decode(cached);
160 }
161 let detected = if std::arch::is_x86_feature_detected!("avx512f") {
162 AVX512
163 } else if std::arch::is_x86_feature_detected!("avx2") {
164 AVX2
165 } else {
166 SCALAR
167 };
168 LEVEL.store(detected, Ordering::Relaxed);
169 decode(detected)
170 }
171
172 #[inline]
173 const fn decode(level: u8) -> Level {
174 match level {
175 AVX512 => Level::Avx512,
176 AVX2 => Level::Avx2,
177 _ => Level::Scalar,
178 }
179 }
180}
181
182mod xxh3;
183mod xxh32;
184mod xxh64;
185
186mod sealed {
187 pub trait Sealed {}
188 impl Sealed for u32 {}
189 impl Sealed for u64 {}
190 impl Sealed for u128 {}
191}
192
193/// Trait for types that can be used as xxHash outputs.
194///
195/// This trait is sealed and only implemented for `u32`, `u64`, and `u128`.
196pub trait ChecksumOutput: sealed::Sealed + Copy + Clone + fmt::Debug + PartialEq + 'static {}
197
198impl ChecksumOutput for u32 {}
199impl ChecksumOutput for u64 {}
200impl ChecksumOutput for u128 {}
201
202/// A trait for computing checksums.
203///
204/// Types implementing this trait can compute a hash incrementally via
205/// [`update`](Checksum::update) and [`sum`](Checksum::sum), or in a single
206/// call via [`checksum`](Checksum::checksum).
207pub trait Checksum {
208 /// The type of the resulting checksum value.
209 type Output: ChecksumOutput;
210
211 /// Create a new checksum instance with default settings (seed = 0,
212 /// default secret for XXH3 variants).
213 fn new() -> Self;
214
215 /// Compute the checksum of `data` in a single call.
216 fn checksum(data: &[u8]) -> Self::Output
217 where
218 Self: Sized,
219 {
220 let mut hasher = Self::new();
221 hasher.update(data);
222 hasher.sum()
223 }
224
225 /// Feed additional data into the checksum.
226 fn update(&mut self, data: &[u8]);
227
228 /// Finalize and return the computed checksum value.
229 fn sum(self) -> Self::Output;
230}
231
232pub use xxh3::{Xxh3_64, Xxh3_128, xxh3_64, xxh3_128};
233pub use xxh32::{Xxh32, xxh32};
234pub use xxh64::{Xxh64, xxh64};
235
236#[cfg(test)]
237#[path = "test_vectors.rs"]
238mod test_vectors;
239
240#[cfg(test)]
241mod test_helpers {
242 extern crate alloc;
243
244 /// Fills a buffer with pseudorandom data, exactly matching the C reference's
245 /// `XSUM_fillTestBuffer` from `xsum_sanity_check.c`.
246 ///
247 /// The PRNG uses:
248 /// - `byteGen` initialized to `PRIME32` (0x9E3779B1)
249 /// - Each iteration: `buf[i] = byteGen >> 56; byteGen *= PRIME64` where
250 /// `PRIME64` is the C test file's constant 0x9E3779B185EBCA8D (not the
251 /// hash PRIME64_1 constant).
252 pub(crate) fn fill_test_buffer(len: usize) -> alloc::vec::Vec<u8> {
253 const C_PRIME32: u64 = 0x9E3779B1;
254 const C_PRIME64: u64 = 0x9E3779B185EBCA8D;
255
256 let mut buf = alloc::vec::Vec::with_capacity(len);
257 let mut byte_gen = C_PRIME32;
258 for _ in 0..len {
259 buf.push((byte_gen >> 56) as u8);
260 byte_gen = byte_gen.wrapping_mul(C_PRIME64);
261 }
262 buf
263 }
264}