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 }