1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
//! kevy-hash — fast, well-distributed hashing for kevy's single-trust-domain
//! keyspace. Zero dependencies.
//!
//! std's `HashMap` is a hashbrown Swiss table (excellent — kevy keeps it) keyed
//! by `SipHash-1-3` (DoS-resistant, but a tax a single-threaded-per-shard
//! keyspace facing no adversarial cross-trust key collisions does not need).
//! This crate supplies the hasher that table should use instead: an FxHash-style
//! word-at-a-time absorb plus a murmur3 [`fmix64`] avalanche finalizer.
//!
//! Measured (via `kevy-store/examples/bench_keyspace.rs`): ~4× faster
//! hashing, ~1.2–2.8× faster GET-hit, ~1.1–1.7× faster GET-miss than
//! SipHash, with no clustering.
//! The finalizer is **essential** — the bare Fx absorb (no `fmix64`) clusters
//! 30–50× on low-entropy sequential keys like `"key:0".."key:99999"`.
//!
//! **Not DoS-resistant.** There is no random seed, so an attacker who can choose
//! keys *and* observe timing could force collisions. kevy's keyspace lives
//! inside one trust domain per shard, so this is the right trade; do not reuse
//! this hasher for maps fed untrusted, adversarially-chosen keys across a trust
//! boundary.
//!
//! ```
//! use kevy_hash::FxHashMap;
//!
//! let mut m: FxHashMap<Vec<u8>, u64> = FxHashMap::default();
//! m.insert(b"key".to_vec(), 1);
//! assert_eq!(m.get(b"key".as_slice()), Some(&1));
//! ```
extern crate alloc;
use Vec;
use ;
use ;
pub use ;
/// FxHash mixing constant (rustc's `rustc-hash` seed).
const SEED: u64 = 0x517c_c1b7_2722_0a95;
const ROTATE: u32 = 5;
/// murmur3 `fmix64` avalanche — spreads every input bit across all 64 output
/// bits. ~6 ALU ops, applied once on [`Hasher::finish`]. This is what the bare
/// Fx absorb lacks, and why it clusters without it.
/// # Examples
///
/// It is a bijection on `u64` — no two inputs collide — and it moves every
/// bit, which is exactly what the bare Fx absorb does not do:
///
/// ```
/// use kevy_hash::fmix64;
/// assert_ne!(fmix64(0), fmix64(1));
///
/// // One input bit flipped moves about half the output bits.
/// let moved = (fmix64(0) ^ fmix64(1)).count_ones();
/// assert_eq!(moved, 33);
/// assert!((16..48).contains(&moved), "avalanche moved {moved} bits");
/// ```
///
/// Zero is a fixed point — every step is a shift-xor or a multiply, and
/// each leaves zero alone. That is a property of murmur3's finalizer, not
/// a defect, and it is why the absorb seeds the state rather than starting
/// it empty:
///
/// ```
/// assert_eq!(kevy_hash::fmix64(0), 0);
/// ```
/// Two-stream pipelined hash_bytes inspired by rustc-hash 2.x's design
/// (`rustc-hash/src/lib.rs#hash_bytes`). The key trick is keeping two
/// independent state words `s0` / `s1` updated via 64×64→128 widening
/// multiplication (one `mul`+`mulhi` on aarch64, one `mul` on x86_64),
/// XORing the two halves of the product to mix top with bottom. The two
/// streams are independent of each other in the bulk loop, so LLVM can
/// schedule them on two ALU ports per cycle.
///
/// Lengths ≤ 16: XOR-only absorb of two reads (start + end), then a
/// single `multiply_mix` of the two streams. The XOR-only absorb is fast
/// because there's no ALU dependency between the two reads.
///
/// Lengths > 16: per-16-byte iteration, `s1 <- multiply_mix(s0 ^ x,
/// CONST ^ y); s0 <- s1`. The `CONST` (digits of pi) prevents the
/// all-zeros input from collapsing.
///
/// Final mix: `multiply_mix(s0, s1) ^ len` — folds length in so that
/// `"abc"` and `"ab\0c"` hash differently (the XOR-only short path
/// doesn't distinguish length-by-position without this).
///
/// Then `fmix64` to give us the anti-clustering avalanche we need (the
/// rustc-hash design assumes its consumer mixes again; we don't, so we
/// avalanche ourselves — same property as the legacy [`FxHasher`] path).
// A byte-faithful transcription of rustc-hash 2.x's `hash_bytes`
// (verified identical for every length 0..200) with `fmix64` appended.
// Splitting it would diverge from the upstream it is checked against,
// which is the whole reason it can be checked at all. The reason given
// here used to be codegen, which is not one of the two classes the rule
// allows; this is the second one.
// LOC-WAIVER: vendored engine core — see the note above.
/// 64×64→128 widening multiply, XOR'ing the two halves of the product.
/// Single `mul` on x86_64, one `mul`+one `mulhi` on aarch64. Mixes top and
/// bottom of the product so the entire output fluctuates with small
/// changes in the input.
/// Fast, well-distributed [`Hasher`] for kevy's keyspace. Word-at-a-time absorb
/// (FxHash-style) finished with [`fmix64`]. See the crate docs for the security
/// trade-off.
/// # Examples
///
/// ```
/// use core::hash::Hasher;
/// use kevy_hash::FxHasher;
///
/// let mut h = FxHasher::default();
/// h.write(b"user:1000");
/// let a = h.finish();
///
/// let mut h = FxHasher::default();
/// h.write(b"user:1001");
/// assert_ne!(a, h.finish(), "one byte apart must not share a hash");
/// ```
///
/// **The result depends on how the bytes were split across `write` calls.**
/// The absorb consumes each call word-at-a-time and handles that call's own
/// tail, so the same bytes delivered in different chunks land on different
/// values. This is inherited from FxHash and is not a defect, but it means
/// a hash is only comparable with one built the same way — feed a whole
/// key in one `write`:
///
/// ```
/// use core::hash::Hasher;
/// use kevy_hash::FxHasher;
///
/// let mut whole = FxHasher::default();
/// whole.write(b"abcdefgh");
///
/// let mut split = FxHasher::default();
/// split.write(b"abcd");
/// split.write(b"efgh");
/// assert_ne!(whole.finish(), split.finish());
/// ```
/// # Not injective over byte strings
///
/// The absorb consumes whole words and zero-extends a short tail, and
/// nothing folds in the total length — so distinct inputs share a value
/// in three families, all with the same cause:
///
/// * a zero-extended 1–3 byte tail is indistinguishable from a 4-byte
/// one: `"aaa"` and `"a\0\0\0aa"`;
/// * `mix(0, 0) == 0`, so leading zero words are absorbed with no
/// effect — `""`, `"\0\0\0\0"` and `"\0\0\0\0\0\0\0\0"` all hash to
/// **0**, and prefixing any key with four NULs leaves its hash
/// unchanged;
/// * a 4-byte tail and an 8-byte word with four trailing zeros agree.
///
/// Measured: over the 1,093 strings of length ≤ 6 from `{a, b, NUL}`,
/// **27 collision classes**. A uniform 64-bit hash on that many inputs
/// would be expected to produce none.
///
/// Whether it reaches you depends on how `Hash` feeds the bytes.
/// `Hash for [u8]` writes a length prefix, so `FxHashMap<Vec<u8>, _>`
/// and `FxHashMap<SmallBytes, _>` — everything kevy itself uses — are
/// unaffected, and the measurement above gives 0 classes for them.
/// `Hash for str` writes only a `0xff` terminator, so **`&str` and
/// `String` keys do reach it**, which is what the 27 are.
///
/// The fix is to fold the total length into [`Hasher::finish`], six
/// lines, and it changes every value this type produces — which the note
/// on [`KevyHash for [u8]`](KevyHash) says this path deliberately does
/// not do. That is an owner decision, still open. Until it is taken, this
/// section is here so the behaviour is chosen rather than discovered.
///
/// (One tempting non-fix, for the record: seeding the initial state to a
/// non-zero constant. Measured, it makes all three families *worse* —
/// 39 classes instead of 27 — because it removes the only case the
/// zero-absorption collapsed.)
;
/// [`BuildHasher`](std::hash::BuildHasher) for [`FxHasher`]. Seedless, so equal
/// keys hash equally across instances and process runs.
///
/// ```
/// use std::hash::BuildHasher;
/// use kevy_hash::FxBuildHasher;
/// let (a, b) = (FxBuildHasher::default(), FxBuildHasher::default());
/// assert_eq!(a.hash_one(b"key"), b.hash_one(b"key"), "no per-instance seed");
/// ```
pub type FxBuildHasher = ;
/// Single-call hashing for kevy's per-command hot path.
///
/// `std::hash::Hasher` is a state-machine API — every hash is `Hasher::default()`
/// → `write_*` → `finish`, with `BuildHasher` indirection on top. For
/// `kevy-map`'s open-addressing table the keyspace is a small handful of
/// well-known leaf types (`[u8]`, `u32`, `u64`, `i32`); we get a faster, inline-
/// friendly hash by exposing one method on each that produces the final mixed
/// 64-bit value in one go.
///
/// **The integer impls** agree with feeding the value through
/// [`FxHasher`] and calling `finish`, so for those the trait is a
/// dispatch shortcut and nothing more. **The `[u8]` impl does not** — it
/// takes the two-stream pipelined path, and its own documentation says
/// so.
///
/// That distinction used to be stated as "all impls must agree", forty
/// lines above the note admitting one of them does not. This is not a
/// typo to tidy: the sentence declared exactly the property that makes
/// mixing the two safe, so a caller who used `FxHashMap` in one place
/// and `kevy_hash()` in another and compared across them would have been
/// silently wrong, on the strength of a guarantee written here.
///
/// `kevy-map` consumes both the full hash (for bucket index) and its top
/// 7 bits (for the metadata byte).
/// # Examples
///
/// The point of the trait is that a leaf type hashes in one call, with no
/// `Hasher` to build and no dispatch to pay:
///
/// ```
/// use kevy_hash::KevyHash;
/// assert_ne!(1u64.kevy_hash(), 2u64.kevy_hash());
/// assert_ne!(b"a"[..].kevy_hash(), b"b"[..].kevy_hash());
/// ```
///
/// The integer impls agree with routing the same value through
/// [`FxHasher`], which is what lets the dispatch be cut without changing
/// the hash:
///
/// ```
/// use core::hash::Hasher;
/// use kevy_hash::{FxHasher, KevyHash};
///
/// let mut h = FxHasher::default();
/// h.write_u64(0x0123_4567_89ab_cdef);
/// assert_eq!(0x0123_4567_89ab_cdefu64.kevy_hash(), h.finish());
/// ```
///
/// # Implementing it
///
/// The trait is open: `kevy-map` keys a map by any `K: KevyHash + Eq`, so
/// a key type of your own implements it. An implementation must uphold:
///
/// * **Equal values hash equally.** `a == b` implies
/// `a.kevy_hash() == b.kevy_hash()`, as with [`core::hash::Hash`].
/// * **Borrowed forms agree.** If `K: Borrow<Q>` and a map of `K` is
/// looked up by `&Q`, then `k.kevy_hash() == k.borrow().kevy_hash()` —
/// the rule `Vec<u8>` and `[u8]` follow here.
/// * **Every bit moves.** `kevy-map` takes the bucket from the low bits
/// and a tag from the top seven, so a hash whose high bits barely vary
/// degrades lookups to a scan. Finishing with [`fmix64`] gives this.
///
/// A hash that breaks the first two loses entries; one that breaks the
/// third is slow but correct.
///
/// ```
/// use kevy_hash::{KevyHash, fmix64};
///
/// #[derive(PartialEq, Eq)]
/// struct Port(u16);
///
/// impl KevyHash for Port {
/// fn kevy_hash(&self) -> u64 {
/// fmix64(u64::from(self.0) | 1 << 32)
/// }
/// }
/// assert_ne!(Port(1).kevy_hash(), Port(2).kevy_hash());
/// ```
/// A [`HashMap`] using [`FxHasher`] instead of SipHash.
///
/// ```
/// use kevy_hash::FxHashMap;
/// let mut m: FxHashMap<&str, u32> = FxHashMap::default();
/// m.insert("a", 1);
/// *m.entry("a").or_insert(0) += 1;
/// assert_eq!(m["a"], 2);
/// ```
pub type FxHashMap<K, V> = ;
/// A [`HashSet`] using [`FxHasher`] instead of SipHash.
///
/// ```
/// use kevy_hash::FxHashSet;
/// let s: FxHashSet<u64> = [1, 2, 2, 3].into_iter().collect();
/// assert_eq!(s.len(), 3);
/// assert!(s.contains(&2));
/// ```
pub type FxHashSet<T> = ;
// Send and Sync are part of the public contract: a change that loses
// either fails to compile here rather than in a caller.
const _: = ;