Signet Forge 0.1.0
C++20 Parquet library with AI-native extensions
DEMO
Loading...
Searching...
No Matches
xxhash.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: AGPL-3.0-or-later
2// Copyright 2026 Johnson Ogundeji
3#pragma once
4
20
21#include <cstdint>
22#include <cstring>
23#include <string>
24#include <type_traits>
25
26namespace signet::forge {
27
29namespace xxhash {
30
32namespace detail {
33
37static constexpr uint64_t PRIME1 = 0x9E3779B185EBCA87ULL;
38static constexpr uint64_t PRIME2 = 0xC2B2AE3D27D4EB4FULL;
39static constexpr uint64_t PRIME3 = 0x165667B19E3779F9ULL;
40static constexpr uint64_t PRIME4 = 0x85EBCA77C2B2AE63ULL;
41static constexpr uint64_t PRIME5 = 0x27D4EB2F165667C5ULL;
43
44// ---------------------------------------------------------------------------
45// Helpers
46// ---------------------------------------------------------------------------
47
52inline constexpr uint64_t rotl64(uint64_t v, int r) {
53 r &= 63; // guard against UB when r==0 or r>=64
54 return (v << r) | (v >> ((64 - r) & 63));
55}
56
61inline uint64_t read_u64_le(const uint8_t* p) {
62 uint64_t v;
63 std::memcpy(&v, p, 8);
64 // On big-endian platforms this would need a byte swap.
65 // x86, x86_64, and ARM (little-endian mode) are fine.
66#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
67 v = __builtin_bswap64(v);
68#elif defined(_MSC_VER)
69 // Portability: MSVC targets are always little-endian (x86/x64/ARM-LE),
70 // so no byte-swap is needed. If MSVC ever targets a big-endian arch,
71 // this branch must be revisited.
72#elif !defined(__BYTE_ORDER__)
73# error "Cannot determine endianness — define __BYTE_ORDER__"
74#endif
75 return v;
76}
77
82inline uint32_t read_u32_le(const uint8_t* p) {
83 uint32_t v;
84 std::memcpy(&v, p, 4);
85#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
86 v = __builtin_bswap32(v);
87#elif defined(_MSC_VER)
88 // Portability: MSVC targets are always little-endian (x86/x64/ARM-LE),
89 // so no byte-swap is needed.
90#elif !defined(__BYTE_ORDER__)
91# error "Cannot determine endianness — define __BYTE_ORDER__"
92#endif
93 return v;
94}
95
96// ---------------------------------------------------------------------------
97// Core primitives
98// ---------------------------------------------------------------------------
99
107inline constexpr uint64_t round(uint64_t acc, uint64_t lane) {
108 acc += lane * PRIME2;
109 acc = rotl64(acc, 31);
110 acc *= PRIME1;
111 return acc;
112}
113
121inline constexpr uint64_t merge_accumulator(uint64_t acc, uint64_t v) {
122 acc ^= round(0, v);
123 acc = acc * PRIME1 + PRIME4;
124 return acc;
125}
126
133inline constexpr uint64_t avalanche(uint64_t h) {
134 h ^= h >> 33;
135 h *= PRIME2;
136 h ^= h >> 29;
137 h *= PRIME3;
138 h ^= h >> 32;
139 return h;
140}
141
142} // namespace detail
143
144// ---------------------------------------------------------------------------
145// Public API
146// ---------------------------------------------------------------------------
147
159inline uint64_t hash64(const void* data, size_t length, uint64_t seed = 0) {
160 using namespace detail;
161
162 const auto* p = static_cast<const uint8_t*>(data);
163 const auto* end = p + length;
164
165 uint64_t h;
166
167 if (length >= 32) {
168 // -----------------------------------------------------------------
169 // Step 1: Initialize four accumulators
170 // -----------------------------------------------------------------
171 uint64_t v1 = seed + PRIME1 + PRIME2;
172 uint64_t v2 = seed + PRIME2;
173 uint64_t v3 = seed;
174 uint64_t v4 = seed - PRIME1;
175
176 // -----------------------------------------------------------------
177 // Step 2: Process 32-byte stripes
178 // -----------------------------------------------------------------
179 const auto* stripe_end = end - 31; // ensure at least 32 bytes remain
180 do {
181 v1 = round(v1, read_u64_le(p)); p += 8;
182 v2 = round(v2, read_u64_le(p)); p += 8;
183 v3 = round(v3, read_u64_le(p)); p += 8;
184 v4 = round(v4, read_u64_le(p)); p += 8;
185 } while (p < stripe_end);
186
187 // -----------------------------------------------------------------
188 // Step 3: Converge four accumulators into one
189 // -----------------------------------------------------------------
190 h = rotl64(v1, 1) +
191 rotl64(v2, 7) +
192 rotl64(v3, 12) +
193 rotl64(v4, 18);
194
195 h = merge_accumulator(h, v1);
196 h = merge_accumulator(h, v2);
197 h = merge_accumulator(h, v3);
198 h = merge_accumulator(h, v4);
199 } else {
200 // -----------------------------------------------------------------
201 // Small input (< 32 bytes): single accumulator
202 // -----------------------------------------------------------------
203 h = seed + PRIME5;
204 }
205
206 // -----------------------------------------------------------------
207 // Step 4: Add total input length
208 // -----------------------------------------------------------------
209 h += static_cast<uint64_t>(length);
210
211 // -----------------------------------------------------------------
212 // Step 5: Consume remaining bytes
213 // -----------------------------------------------------------------
214
215 // Process remaining 8-byte chunks
216 while (p + 8 <= end) {
217 uint64_t lane = read_u64_le(p);
218 h ^= round(0, lane);
219 h = rotl64(h, 27) * PRIME1 + PRIME4;
220 p += 8;
221 }
222
223 // Process a remaining 4-byte chunk
224 if (p + 4 <= end) {
225 uint64_t lane = static_cast<uint64_t>(read_u32_le(p));
226 h ^= lane * PRIME1;
227 h = rotl64(h, 23) * PRIME2 + PRIME3;
228 p += 4;
229 }
230
231 // Process remaining 1-byte chunks
232 while (p < end) {
233 uint64_t lane = static_cast<uint64_t>(*p);
234 h ^= lane * PRIME5;
235 h = rotl64(h, 11) * PRIME1;
236 p += 1;
237 }
238
239 // -----------------------------------------------------------------
240 // Step 6: Avalanche / finalization
241 // -----------------------------------------------------------------
242 return avalanche(h);
243}
244
249inline uint64_t hash64(const std::string& s, uint64_t seed = 0) {
250 return hash64(s.data(), s.size(), seed);
251}
252
262template <typename T>
263inline uint64_t hash64_value(const T& val, uint64_t seed = 0) {
264 static_assert(std::is_trivially_copyable_v<T>,
265 "hash64_value requires a trivially-copyable type");
266 return hash64(&val, sizeof(T), seed);
267}
268
269} // namespace xxhash
270} // namespace signet::forge
constexpr uint64_t round(uint64_t acc, uint64_t lane)
Round function: accumulate one 8-byte lane into an accumulator.
Definition xxhash.hpp:107
constexpr uint64_t avalanche(uint64_t h)
Avalanche / finalization mix to ensure all output bits are well-distributed.
Definition xxhash.hpp:133
uint64_t read_u64_le(const uint8_t *p)
Read a little-endian uint64_t from potentially unaligned memory.
Definition xxhash.hpp:61
uint32_t read_u32_le(const uint8_t *p)
Read a little-endian uint32_t from potentially unaligned memory.
Definition xxhash.hpp:82
constexpr uint64_t rotl64(uint64_t v, int r)
Rotate v left by r bits (circular shift).
Definition xxhash.hpp:52
constexpr uint64_t merge_accumulator(uint64_t acc, uint64_t v)
Merge an accumulator value into the converged accumulator.
Definition xxhash.hpp:121
uint64_t hash64(const void *data, size_t length, uint64_t seed=0)
Compute xxHash64 of an arbitrary byte buffer.
Definition xxhash.hpp:159
uint64_t hash64_value(const T &val, uint64_t seed=0)
Convenience overload: hash a trivially-copyable typed value.
Definition xxhash.hpp:263