MCRO
C++23 utilities for Unreal Engine.
Loading...
Searching...
No Matches
ConstexprXXH3.h
Go to the documentation of this file.
1/** @noop License Comment
2 * @file
3 * @copyright
4 * BSD 2-Clause License
5 * @copyright
6 * constexpr-xxh3 - C++20 constexpr implementation of the XXH3 64-bit variant of xxHash
7 * Copyright (c) 2021-2023, chys <admin@chys.info> <chys87@github>
8 * All rights reserved.
9 * @copyright
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions are met:
12 * 1. Redistributions of source code must retain the above copyright notice, this
13 * list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright notice,
15 * this list of conditions and the following disclaimer in the documentation
16 * and/or other materials provided with the distribution.
17 * @copyright
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 * @copyright
29 * This file uses code from Yann Collet's xxHash implementation.
30 * Original xxHash copyright notice:
31 * @copyright
32 * xxHash - Extremely Fast Hash algorithm
33 * Header File
34 * Copyright (C) 2012-2020 Yann Collet
35 *
36 * @author Chys <admin@chys.info> <chys87@github>; Yann Collet
37 * @date 2025
38 */
39
40#pragma once
41
42#include <cstddef>
43#include <cstdint>
44#include <iterator> // for std::data, std::size
45#include <type_traits>
46#include <utility>
47
48namespace constexpr_xxh3 {
49
50template <typename T>
51concept ByteType = (std::is_integral_v<T> && sizeof(T) == 1)
52#if defined __cpp_lib_byte && __cpp_lib_byte >= 201603
53 || std::is_same_v<T, std::byte>
54#endif
55 ;
56
57template <typename T>
58concept BytePtrType = requires (T ptr) {
59 requires std::is_pointer_v<T>;
60 requires ByteType<std::remove_cvref_t<decltype(*ptr)>>;
61};
62
63template <typename T>
64concept BytesType = requires (const T& bytes) {
65 { std::data(bytes) };
66 requires BytePtrType<decltype(std::data(bytes))>;
67 // -> std::convertible_to is not supported widely enough
68 { static_cast<size_t>(std::size(bytes)) };
69};
70
71inline constexpr uint32_t swap32(uint32_t x) noexcept {
72 return ((x << 24) & 0xff000000) | ((x << 8) & 0x00ff0000) |
73 ((x >> 8) & 0x0000ff00) | ((x >> 24) & 0x000000ff);
74}
75
76template <typename T>
77inline constexpr uint32_t readLE32(const T* ptr) noexcept {
78 return uint8_t(ptr[0]) | uint32_t(uint8_t(ptr[1])) << 8 |
79 uint32_t(uint8_t(ptr[2])) << 16 | uint32_t(uint8_t(ptr[3])) << 24;
80}
81
82inline constexpr uint64_t swap64(uint64_t x) noexcept {
83 return ((x << 56) & 0xff00000000000000ULL) |
84 ((x << 40) & 0x00ff000000000000ULL) |
85 ((x << 24) & 0x0000ff0000000000ULL) |
86 ((x << 8) & 0x000000ff00000000ULL) |
87 ((x >> 8) & 0x00000000ff000000ULL) |
88 ((x >> 24) & 0x0000000000ff0000ULL) |
89 ((x >> 40) & 0x000000000000ff00ULL) |
90 ((x >> 56) & 0x00000000000000ffULL);
91}
92
93template <typename T>
94inline constexpr uint64_t readLE64(const T* ptr) noexcept {
95 return readLE32(ptr) | uint64_t(readLE32(ptr + 4)) << 32;
96}
97
98inline constexpr void writeLE64(uint8_t* dst, uint64_t v) noexcept {
99 for (int i = 0; i < 8; ++i) dst[i] = uint8_t(v >> (i * 8));
100}
101
102inline constexpr uint32_t PRIME32_1 = 0x9E3779B1U;
103inline constexpr uint32_t PRIME32_2 = 0x85EBCA77U;
104inline constexpr uint32_t PRIME32_3 = 0xC2B2AE3DU;
105
106inline constexpr uint64_t PRIME64_1 = 0x9E3779B185EBCA87ULL;
107inline constexpr uint64_t PRIME64_2 = 0xC2B2AE3D27D4EB4FULL;
108inline constexpr uint64_t PRIME64_3 = 0x165667B19E3779F9ULL;
109inline constexpr uint64_t PRIME64_4 = 0x85EBCA77C2B2AE63ULL;
110inline constexpr uint64_t PRIME64_5 = 0x27D4EB2F165667C5ULL;
111
112inline constexpr size_t SECRET_DEFAULT_SIZE = 192;
113inline constexpr size_t SECRET_SIZE_MIN = 136;
114
115inline constexpr uint8_t kSecret[SECRET_DEFAULT_SIZE]{
116 0xb8, 0xfe, 0x6c, 0x39, 0x23, 0xa4, 0x4b, 0xbe, 0x7c, 0x01, 0x81, 0x2c,
117 0xf7, 0x21, 0xad, 0x1c, 0xde, 0xd4, 0x6d, 0xe9, 0x83, 0x90, 0x97, 0xdb,
118 0x72, 0x40, 0xa4, 0xa4, 0xb7, 0xb3, 0x67, 0x1f, 0xcb, 0x79, 0xe6, 0x4e,
119 0xcc, 0xc0, 0xe5, 0x78, 0x82, 0x5a, 0xd0, 0x7d, 0xcc, 0xff, 0x72, 0x21,
120 0xb8, 0x08, 0x46, 0x74, 0xf7, 0x43, 0x24, 0x8e, 0xe0, 0x35, 0x90, 0xe6,
121 0x81, 0x3a, 0x26, 0x4c, 0x3c, 0x28, 0x52, 0xbb, 0x91, 0xc3, 0x00, 0xcb,
122 0x88, 0xd0, 0x65, 0x8b, 0x1b, 0x53, 0x2e, 0xa3, 0x71, 0x64, 0x48, 0x97,
123 0xa2, 0x0d, 0xf9, 0x4e, 0x38, 0x19, 0xef, 0x46, 0xa9, 0xde, 0xac, 0xd8,
124 0xa8, 0xfa, 0x76, 0x3f, 0xe3, 0x9c, 0x34, 0x3f, 0xf9, 0xdc, 0xbb, 0xc7,
125 0xc7, 0x0b, 0x4f, 0x1d, 0x8a, 0x51, 0xe0, 0x4b, 0xcd, 0xb4, 0x59, 0x31,
126 0xc8, 0x9f, 0x7e, 0xc9, 0xd9, 0x78, 0x73, 0x64, 0xea, 0xc5, 0xac, 0x83,
127 0x34, 0xd3, 0xeb, 0xc3, 0xc5, 0x81, 0xa0, 0xff, 0xfa, 0x13, 0x63, 0xeb,
128 0x17, 0x0d, 0xdd, 0x51, 0xb7, 0xf0, 0xda, 0x49, 0xd3, 0x16, 0x55, 0x26,
129 0x29, 0xd4, 0x68, 0x9e, 0x2b, 0x16, 0xbe, 0x58, 0x7d, 0x47, 0xa1, 0xfc,
130 0x8f, 0xf8, 0xb8, 0xd1, 0x7a, 0xd0, 0x31, 0xce, 0x45, 0xcb, 0x3a, 0x8f,
131 0x95, 0x16, 0x04, 0x28, 0xaf, 0xd7, 0xfb, 0xca, 0xbb, 0x4b, 0x40, 0x7e,
132};
133
134inline constexpr std::pair<uint64_t, uint64_t> mult64to128(
135 uint64_t lhs, uint64_t rhs) noexcept {
136 uint64_t lo_lo = uint64_t(uint32_t(lhs)) * uint32_t(rhs);
137 uint64_t hi_lo = (lhs >> 32) * uint32_t(rhs);
138 uint64_t lo_hi = uint32_t(lhs) * (rhs >> 32);
139 uint64_t hi_hi = (lhs >> 32) * (rhs >> 32);
140 uint64_t cross = (lo_lo >> 32) + uint32_t(hi_lo) + lo_hi;
141 uint64_t upper = (hi_lo >> 32) + (cross >> 32) + hi_hi;
142 uint64_t lower = (cross << 32) | uint32_t(lo_lo);
143 return {lower, upper};
144}
145
146inline constexpr uint64_t mul128_fold64(uint64_t lhs, uint64_t rhs) noexcept {
147#if defined __GNUC__ && __WORDSIZE >= 64
148 // It appears both GCC and Clang support evaluating __int128 as constexpr
149 auto product = static_cast<unsigned __int128>(lhs) * rhs;
150 return uint64_t(product >> 64) ^ uint64_t(product);
151#else
152 auto product = mult64to128(lhs, rhs);
153 return product.first ^ product.second;
154#endif
155}
156
157inline constexpr uint64_t XXH64_avalanche(uint64_t h) noexcept {
158 h = (h ^ (h >> 33)) * PRIME64_2;
159 h = (h ^ (h >> 29)) * PRIME64_3;
160 return h ^ (h >> 32);
161}
162
163inline constexpr uint64_t XXH3_avalanche(uint64_t h) noexcept {
164 h = (h ^ (h >> 37)) * 0x165667919E3779F9ULL;
165 return h ^ (h >> 32);
166}
167
168inline constexpr uint64_t rrmxmx(uint64_t h, uint64_t len) noexcept {
169 h ^= ((h << 49) | (h >> 15)) ^ ((h << 24) | (h >> 40));
170 h *= 0x9FB21C651E98DF25ULL;
171 h ^= (h >> 35) + len;
172 h *= 0x9FB21C651E98DF25ULL;
173 return h ^ (h >> 28);
174}
175
176template <typename T, typename S>
177constexpr uint64_t mix16B(const T* input, const S* secret,
178 uint64_t seed) noexcept {
179 return mul128_fold64(readLE64(input) ^ (readLE64(secret) + seed),
180 readLE64(input + 8) ^ (readLE64(secret + 8) - seed));
181}
182
183inline constexpr size_t STRIPE_LEN = 64;
184inline constexpr size_t SECRET_CONSUME_RATE = 8;
185inline constexpr size_t ACC_NB = STRIPE_LEN / sizeof(uint64_t);
186
187template <typename T, typename S>
188constexpr void accumulate_512(uint64_t* acc, const T* input,
189 const S* secret) noexcept {
190 for (size_t i = 0; i < ACC_NB; i++) {
191 uint64_t data_val = readLE64(input + 8 * i);
192 uint64_t data_key = data_val ^ readLE64(secret + i * 8);
193 acc[i ^ 1] += data_val;
194 acc[i] += uint32_t(data_key) * (data_key >> 32);
195 }
196}
197
198template <typename T, typename S>
199constexpr uint64_t hashLong_64b_internal(const T* input, size_t len,
200 const S* secret,
201 size_t secretSize) noexcept {
204 size_t nbStripesPerBlock = (secretSize - STRIPE_LEN) / SECRET_CONSUME_RATE;
205 size_t block_len = STRIPE_LEN * nbStripesPerBlock;
206 size_t nb_blocks = (len - 1) / block_len;
207
208 for (size_t n = 0; n < nb_blocks; n++) {
209 for (size_t i = 0; i < nbStripesPerBlock; i++)
210 accumulate_512(acc, input + n * block_len + i * STRIPE_LEN,
211 secret + i * SECRET_CONSUME_RATE);
212 for (size_t i = 0; i < ACC_NB; i++)
213 acc[i] = (acc[i] ^ (acc[i] >> 47) ^
214 readLE64(secret + secretSize - STRIPE_LEN + 8 * i)) *
215 PRIME32_1;
216 }
217
218 size_t nbStripes = ((len - 1) - (block_len * nb_blocks)) / STRIPE_LEN;
219 for (size_t i = 0; i < nbStripes; i++)
220 accumulate_512(acc, input + nb_blocks * block_len + i * STRIPE_LEN,
221 secret + i * SECRET_CONSUME_RATE);
222 accumulate_512(acc, input + len - STRIPE_LEN,
223 secret + secretSize - STRIPE_LEN - 7);
224 uint64_t result = len * PRIME64_1;
225 for (size_t i = 0; i < 4; i++)
226 result +=
227 mul128_fold64(acc[2 * i] ^ readLE64(secret + 11 + 16 * i),
228 acc[2 * i + 1] ^ readLE64(secret + 11 + 16 * i + 8));
229 return XXH3_avalanche(result);
230}
231
232template <typename T, typename S, typename HashLong>
233constexpr uint64_t XXH3_64bits_internal(const T* input, size_t len,
234 uint64_t seed, const S* secret,
235 size_t secretLen,
236 HashLong f_hashLong) noexcept {
237 if (len == 0) {
238 return XXH64_avalanche(seed ^
239 (readLE64(secret + 56) ^ readLE64(secret + 64)));
240 } else if (len < 4) {
241 uint64_t keyed = ((uint32_t(uint8_t(input[0])) << 16) |
242 (uint32_t(uint8_t(input[len >> 1])) << 24) |
243 uint8_t(input[len - 1]) | (uint32_t(len) << 8)) ^
244 ((readLE32(secret) ^ readLE32(secret + 4)) + seed);
245 return XXH64_avalanche(keyed);
246 } else if (len <= 8) {
247 uint64_t keyed =
248 (readLE32(input + len - 4) + (uint64_t(readLE32(input)) << 32)) ^
249 ((readLE64(secret + 8) ^ readLE64(secret + 16)) -
250 (seed ^ (uint64_t(swap32(uint32_t(seed))) << 32)));
251 return rrmxmx(keyed, len);
252 } else if (len <= 16) {
253 uint64_t input_lo =
254 readLE64(input) ^
255 ((readLE64(secret + 24) ^ readLE64(secret + 32)) + seed);
256 uint64_t input_hi =
257 readLE64(input + len - 8) ^
258 ((readLE64(secret + 40) ^ readLE64(secret + 48)) - seed);
259 uint64_t acc =
260 len + swap64(input_lo) + input_hi + mul128_fold64(input_lo, input_hi);
261 return XXH3_avalanche(acc);
262 } else if (len <= 128) {
263 uint64_t acc = len * PRIME64_1;
264 size_t secret_off = 0;
265 for (size_t i = 0, j = len; j > i; i += 16, j -= 16) {
266 acc += mix16B(input + i, secret + secret_off, seed);
267 acc += mix16B(input + j - 16, secret + secret_off + 16, seed);
268 secret_off += 32;
269 }
270 return XXH3_avalanche(acc);
271 } else if (len <= 240) {
272 uint64_t acc = len * PRIME64_1;
273 for (size_t i = 0; i < 128; i += 16)
274 acc += mix16B(input + i, secret + i, seed);
275 acc = XXH3_avalanche(acc);
276 for (size_t i = 128; i < len / 16 * 16; i += 16)
277 acc += mix16B(input + i, secret + (i - 128) + 3, seed);
278 acc += mix16B(input + len - 16, secret + SECRET_SIZE_MIN - 17, seed);
279 return XXH3_avalanche(acc);
280 } else {
281 return f_hashLong(input, len, seed, secret, secretLen);
282 }
283}
284
285template <BytesType Bytes>
286constexpr size_t bytes_size(const Bytes& bytes) noexcept {
287 return std::size(bytes);
288}
289
290template <ByteType T, size_t N>
291constexpr size_t bytes_size(T (&)[N]) noexcept {
292 return (N ? N - 1 : 0);
293}
294
295/// Basic interfaces
296
297template <ByteType T>
298consteval uint64_t XXH3_64bits_const(const T* input, size_t len) noexcept {
300 input, len, 0, kSecret, sizeof(kSecret),
301 [](const T* input, size_t len, uint64_t, const void*,
302 size_t) constexpr noexcept {
303 return hashLong_64b_internal(input, len, kSecret, sizeof(kSecret));
304 });
305}
306
307template <ByteType T, ByteType S>
308consteval uint64_t XXH3_64bits_withSecret_const(const T* input, size_t len,
309 const S* secret,
310 size_t secretSize) noexcept {
312 input, len, 0, secret, secretSize,
313 [](const T* input, size_t len, uint64_t, const S* secret,
314 size_t secretLen) constexpr noexcept {
315 return hashLong_64b_internal(input, len, secret, secretLen);
316 });
317}
318
319template <ByteType T>
320consteval uint64_t XXH3_64bits_withSeed_const(const T* input, size_t len,
321 uint64_t seed) noexcept {
322 if (seed == 0) return XXH3_64bits_const(input, len);
324 input, len, seed, kSecret, sizeof(kSecret),
325 [](const T* input, size_t len, uint64_t seed, const void*,
326 size_t) constexpr noexcept {
327 uint8_t secret[SECRET_DEFAULT_SIZE];
328 for (size_t i = 0; i < SECRET_DEFAULT_SIZE; i += 16) {
329 writeLE64(secret + i, readLE64(kSecret + i) + seed);
330 writeLE64(secret + i + 8, readLE64(kSecret + i + 8) - seed);
331 }
332 return hashLong_64b_internal(input, len, secret, sizeof(secret));
333 });
334}
335
336/// Convenient interfaces
337
338template <BytesType Bytes>
339consteval uint64_t XXH3_64bits_const(const Bytes& input) noexcept {
340 return XXH3_64bits_const(std::data(input), bytes_size(input));
341}
342
343template <BytesType Bytes, BytesType Secret>
344consteval uint64_t XXH3_64bits_withSecret_const(const Bytes& input,
345 const Secret& secret) noexcept {
346 return XXH3_64bits_withSecret_const(std::data(input), bytes_size(input),
347 std::data(secret), bytes_size(secret));
348}
349
350template <BytesType Bytes>
351consteval uint64_t XXH3_64bits_withSeed_const(const Bytes& input,
352 uint64_t seed) noexcept {
353 return XXH3_64bits_withSeed_const(std::data(input), bytes_size(input), seed);
354}
355
356} // namespace constexpr_xxh3
constexpr uint64_t swap64(uint64_t x) noexcept
consteval uint64_t XXH3_64bits_withSeed_const(const T *input, size_t len, uint64_t seed) noexcept
constexpr uint8_t kSecret[SECRET_DEFAULT_SIZE]
constexpr uint64_t readLE64(const T *ptr) noexcept
constexpr uint64_t rrmxmx(uint64_t h, uint64_t len) noexcept
constexpr size_t ACC_NB
constexpr void writeLE64(uint8_t *dst, uint64_t v) noexcept
constexpr uint32_t readLE32(const T *ptr) noexcept
constexpr size_t SECRET_DEFAULT_SIZE
constexpr std::pair< uint64_t, uint64_t > mult64to128(uint64_t lhs, uint64_t rhs) noexcept
constexpr void accumulate_512(uint64_t *acc, const T *input, const S *secret) noexcept
constexpr size_t SECRET_CONSUME_RATE
constexpr uint32_t PRIME32_1
constexpr uint64_t hashLong_64b_internal(const T *input, size_t len, const S *secret, size_t secretSize) noexcept
constexpr uint32_t PRIME32_2
constexpr uint64_t XXH3_avalanche(uint64_t h) noexcept
constexpr size_t bytes_size(const Bytes &bytes) noexcept
constexpr uint64_t mul128_fold64(uint64_t lhs, uint64_t rhs) noexcept
constexpr uint64_t PRIME64_1
constexpr size_t SECRET_SIZE_MIN
constexpr uint64_t PRIME64_4
consteval uint64_t XXH3_64bits_withSecret_const(const T *input, size_t len, const S *secret, size_t secretSize) noexcept
constexpr uint32_t swap32(uint32_t x) noexcept
constexpr uint32_t PRIME32_3
constexpr uint64_t PRIME64_2
constexpr uint64_t XXH64_avalanche(uint64_t h) noexcept
constexpr uint64_t PRIME64_5
constexpr uint64_t mix16B(const T *input, const S *secret, uint64_t seed) noexcept
constexpr uint64_t PRIME64_3
consteval uint64_t XXH3_64bits_const(const T *input, size_t len) noexcept
Basic interfaces.
constexpr uint64_t XXH3_64bits_internal(const T *input, size_t len, uint64_t seed, const S *secret, size_t secretLen, HashLong f_hashLong) noexcept
constexpr size_t STRIPE_LEN