1 // Written in the D programming language.
2 
3 /**
4 The D port of rapidhash and adapted for SwissTabled
5 
6 Copyright: Copyright 2024- Masahiro Nakagawa
7 Authros:   Masahiro Nakagawa
8 License:   $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache License Version 2.0)
9 Credits:   Core algorithm is based on C's rapidhash. See $(HTTP github.com/Nicoshev/rapidhash, rapidhash - Very fast, high quality, platform-independent) by Nicolas De Carli
10  */
11 module swisstable.hash;
12 
13 /*
14  * This version affects rapidMul multiplication function.
15  *
16  * RAPIDHASH_FAST: Normal behavior, max speed.
17  * RAPIDHASH_PROTECTED: Extra protection against entropy loss.
18  */
19 version (RAPIDHASH_PROTECTED) {} else { version = RAPIDHASH_FAST; }
20 
21 // Drop RAPIDHASH_COMPACT support
22 
23 ///  Default seed
24 enum size_t RapidSeed = 0xbdd89aa982704029;
25 
26 /**
27  * rapidhash seeded hash for string, integer and pointer types in LDC.
28  * For other types and DMD, use D's built-in hashOf instead.
29  *
30  * Params:
31  *  key = buffer to be hashed
32  *  len = key length in bytes
33  *  seed = 64-bit seed used to alter the hash result predictably
34  *
35  * Returns: a 64-bit hash.
36  */
37 pragma(inline, true) ulong rapidhashOf(K)(const auto ref K key, size_t seed = RapidSeed) nothrow @trusted
38 {
39     version (LDC)
40     {
41         import std.traits : isSomeString, ForeachType, isIntegral, isPointer;
42 
43         static if (isSomeString!K)
44             return rapidhashInternal(cast(const(ubyte)*)key.ptr, key.length * (ForeachType!K).sizeof, seed);
45         else static if (isIntegral!K)
46             return rapidhashInternal(cast(const(ubyte)*)&key, K.sizeof, seed);
47         else static if (isPointer!K) {
48             size_t k = cast(size_t)key; // Use ptr address, not target value
49             return rapidhashInternal(cast(const(ubyte)*)&key, size_t.sizeof, seed);
50         } else
51             return .hashOf(key, seed); // Fallback to dlang's hashOf for other types
52     }
53     else
54     {
55         return .hashOf(key, seed); // DMD can't generate fast code for rapidhash
56     }
57 }
58 
59 unittest
60 {
61     version (LDC)
62     {
63         assert(rapidhashOf("test") == 945056766619593);
64         assert(rapidhashOf("0123456789abcdefghijklmnopqrstuvwxyz") == 17012243428125851551);
65         assert(rapidhashOf(1000U) == 10318066423548077317);
66         assert(rapidhashOf(uint.max) == 9968829716010863328);
67     }
68     else
69     {
70         assert(rapidhashOf("test") == .hashOf("test", RapidSeed));
71         assert(rapidhashOf(1000U) == .hashOf(1000U, RapidSeed));
72     }
73 }
74 
75 private nothrow @trusted @nogc pure:
76 
77 /**
78  * 64 * 64 -> 128bit multiply function. Calculates 128-bit C = *A * *B.
79  *
80  * When RAPIDHASH_FAST is defined:
81  * Overwrites A contents with C's low 64 bits.
82  * Overwrites B contents with C's high 64 bits.
83  *
84  * When RAPIDHASH_PROTECTED is defined:
85  * Xors and overwrites A contents with C's low 64 bits.
86  * Xors and overwrites B contents with C's high 64 bits.
87  *
88  * Params:
89  *  A = Address of 64-bit number
90  *  B = Address of 64-bit number
91  */
92 pragma(inline, true) void rapidMul(ulong* A, ulong* B)
93 {
94     version (RAPIDHASH_USE_CENT)
95     {
96         // Cent version is now too slow.
97         import core.int128;
98 
99         Cent r = {lo:*A};
100         r = mul(r, Cent(0, *B));
101         version (RAPIDHASH_PROTECTED) {
102             *A ^= cast(ulong)r.lo;
103             *B ^= cast(ulong)r.hi;
104         } else {
105             *A = cast(ulong)r.lo;
106             *B = cast(ulong)r.hi;
107         }
108         /*
109     } else version(LDC) {
110         // From https://forum.dlang.org/post/nydunscuwdcinamqails@forum.dlang.org
111         import ldc.intrinsics;
112         pragma(LDC_inline_ir)
113             R inlineIR(string s,  R,  P...)(P);
114 
115         ulong[2] result;
116         inlineIR!(`
117     %a = zext i64 %0 to i128
118     %b = zext i64 %1 to i128
119     %c = mul i128 %a, %b
120     %d = bitcast [2 x i64]* %2 to i128*
121     store i128 %c, i128* %d
122     ret void`, void)(*A, *B, &result);
123 
124         version (RAPIDHASH_PROTECTED) {
125             *A ^= result[0]; *B ^= result[1];
126         } else {
127             *A = result[0]; *B = result[1];
128         }
129         */
130     }
131     else
132     {
133         ulong ha = *A >> 32, hb = *B >> 32, la = cast(uint)*A, lb = cast(uint)*B, hi = void, lo = void;
134         ulong rh = ha * hb, rm0 = ha * lb, rm1 = hb * la, rl = la * lb, t = rl + (rm0 << 32), c = t < rl;
135         lo = t + (rm1 << 32);
136         c += lo < t;
137         hi = rh + (rm0 >> 32) + (rm1 >> 32) + c;
138         version (RAPIDHASH_PROTECTED) {
139             *A ^= lo;
140             *B ^= hi;
141         } else {
142             *A = lo;
143             *B = hi;
144         }
145     }
146 }
147 
148 /**
149  * Multiply and xor mix function.
150  *
151  * Params:
152  *  A = 64-bit number
153  *  B = 64-bit number
154  *
155  * Returns: calculates 128-bit C = A * B. 64-bit xor between high and low 64 bits of C.
156  */
157 pragma(inline, true) ulong rapidMix(ulong A, ulong B) { rapidMul(&A, &B); return A ^ B; }
158 
159 import core.stdc.string : memcpy;
160 import core.bitop : bswap;
161 
162 version (LittleEndian)
163 {
164     pragma(inline, true) ulong rapidRead64(const(ubyte)* p) { ulong v = void; memcpy(&v, p, 8); return v; }
165     pragma(inline, true) ulong rapidRead32(const(ubyte)* p) { uint  v = void; memcpy(&v, p, 4); return v; }
166 }
167 else
168 {
169     pragma(inline, true) ulong rapidRead64(const(ubyte)* p) { ulong v = void; memcpy(&v, p, 8); return bswap(v); }
170     pragma(inline, true) ulong rapidRead32(const(ubyte)* p) { uint  v = void; memcpy(&v, p, 4); return bswap(v); }
171 }
172 
173 /**
174  * Reads and combines 3 bytes of input. Reads and combines 3 bytes from given buffer.
175  * Guarantees to read each buffer position at least once.
176  *
177  * Params:
178  *  p = buffer to read from
179  *  k = length of p in bytes
180  *  
181  * Returns: a 64-bit value containing all three bytes read. 
182  */
183 pragma(inline, true) ulong rapidReadSmall(const(ubyte)* p, size_t k)
184 {
185     return (cast(ulong)p[0] << 56) | (cast(ulong)p[k >> 1] << 32) | p[k - 1];
186 }
187 
188 /**
189  * main routine
190  *
191  * Params:
192  *  key = buffer to be hashed
193  *  len = key length in bytes
194  *  seed = 64-bit seed used to alter the hash result predictably
195  *  secret = triplet of 64-bit secrets used to alter hash result predictably
196  *
197  * Returns: a 64-bit hash.
198  */
199 pragma(inline, true) ulong rapidhashInternal(const(void)* key, size_t len, ulong seed)
200 {
201     // Default secret parameters
202     static immutable ulong[3] secret = [0x2d358dccaa6c78a5UL, 0x8bb84b93962eacc9UL, 0x4b33a62ed433d4a3UL];
203 
204     const(ubyte)* p = cast(const(ubyte)*)key;
205     seed ^= rapidMix(seed ^ secret[0], secret[1]) ^ len;
206     ulong a = void, b = void;
207     if (len <= 16) {
208         if (len >= 4) {
209             const(ubyte)* plast = p + len - 4;
210             a = (rapidRead32(p) << 32) | rapidRead32(plast);
211             const(ulong) delta = (len & 24) >> (len >> 3);
212             b = (rapidRead32(p + delta) << 32) | rapidRead32(plast - delta);
213         } else if (len > 0) {
214             a = rapidReadSmall(p, len);
215             b = 0;
216         } else {
217             a = b = 0;
218         }
219     } else {
220         size_t i = len; 
221         if (i > 48) {
222             ulong see1 = seed, see2 = seed;
223             while (i >= 96) {
224                 seed = rapidMix(rapidRead64(p)      ^ secret[0], rapidRead64(p + 8)  ^ seed);
225                 see1 = rapidMix(rapidRead64(p + 16) ^ secret[1], rapidRead64(p + 24) ^ see1);
226                 see2 = rapidMix(rapidRead64(p + 32) ^ secret[2], rapidRead64(p + 40) ^ see2);
227                 seed = rapidMix(rapidRead64(p + 48) ^ secret[0], rapidRead64(p + 56) ^ seed);
228                 see1 = rapidMix(rapidRead64(p + 64) ^ secret[1], rapidRead64(p + 72) ^ see1);
229                 see2 = rapidMix(rapidRead64(p + 80) ^ secret[2], rapidRead64(p + 88) ^ see2);
230                 p += 96;
231                 i -= 96;
232             }
233             if (i >= 48) {
234                 seed = rapidMix(rapidRead64(p)      ^ secret[0], rapidRead64(p + 8)  ^ seed);
235                 see1 = rapidMix(rapidRead64(p + 16) ^ secret[1], rapidRead64(p + 24) ^ see1);
236                 see2 = rapidMix(rapidRead64(p + 32) ^ secret[2], rapidRead64(p + 40) ^ see2);
237                 p += 48;
238                 i -= 48;
239             }
240             seed ^= see1 ^ see2;
241         }
242         if (i > 16) {
243             seed = rapidMix(rapidRead64(p) ^ secret[2], rapidRead64(p + 8) ^ seed ^ secret[1]);
244             if (i > 32)
245                 seed = rapidMix(rapidRead64(p + 16) ^ secret[2], rapidRead64(p + 24) ^ seed);
246         }
247         a = rapidRead64(p + i - 16); b = rapidRead64(p + i - 8);
248     }
249     a ^= secret[1];
250     b ^= seed;
251     rapidMul(&a, &b);
252 
253     return rapidMix(a ^ secret[0] ^ len, b ^ secret[1]);
254 }