| ... | ... | @@ -6,6 +6,19 @@ |
| 6 | 6 | const std = @import("std"); |
| 7 | 7 | const builtin = @import("builtin"); |
| 8 | 8 | |
| 9 | inline fn offsetPtr(ptr: [*]const u8, offset: usize) [*]const u8 { |
| 10 | // ptr + offset doesn't work at comptime so we need this instead. |
| 11 | return @ptrCast([*]const u8, &ptr[offset]); |
| 12 | } |
| 13 | |
| 14 | fn fetch32(ptr: [*]const u8, offset: usize) u32 { |
| 15 | return std.mem.readIntLittle(u32, offsetPtr(ptr, offset)[0..4]); |
| 16 | } |
| 17 | |
| 18 | fn fetch64(ptr: [*]const u8, offset: usize) u64 { |
| 19 | return std.mem.readIntLittle(u64, offsetPtr(ptr, offset)[0..8]); |
| 20 | } |
| 21 | |
| 9 | 22 | pub const CityHash32 = struct { |
| 10 | 23 | const Self = @This(); |
| 11 | 24 | |
| ... | ... | @@ -13,14 +26,6 @@ pub const CityHash32 = struct { |
| 13 | 26 | const c1: u32 = 0xcc9e2d51; |
| 14 | 27 | const c2: u32 = 0x1b873593; |
| 15 | 28 | |
| 16 | | fn fetch32(ptr: [*]const u8) u32 { |
| 17 | | var v: u32 = undefined; |
| 18 | | @memcpy(@ptrCast([*]u8, &v), ptr, 4); |
| 19 | | if (builtin.endian == .Big) |
| 20 | | return @byteSwap(u32, v); |
| 21 | | return v; |
| 22 | | } |
| 23 | | |
| 24 | 29 | // A 32-bit to 32-bit integer hash copied from Murmur3. |
| 25 | 30 | fn fmix(h: u32) u32 { |
| 26 | 31 | var h1: u32 = h; |
| ... | ... | @@ -66,21 +71,21 @@ pub const CityHash32 = struct { |
| 66 | 71 | var c: u32 = 9; |
| 67 | 72 | const d: u32 = b; |
| 68 | 73 | |
| 69 | | a +%= fetch32(str.ptr); |
| 70 | | b +%= fetch32(str.ptr + str.len - 4); |
| 71 | | c +%= fetch32(str.ptr + ((str.len >> 1) & 4)); |
| 74 | a +%= fetch32(str.ptr, 0); |
| 75 | b +%= fetch32(str.ptr, str.len - 4); |
| 76 | c +%= fetch32(str.ptr, (str.len >> 1) & 4); |
| 72 | 77 | |
| 73 | 78 | return fmix(mur(c, mur(b, mur(a, d)))); |
| 74 | 79 | } |
| 75 | 80 | |
| 76 | 81 | fn hash32Len13To24(str: []const u8) u32 { |
| 77 | 82 | const len: u32 = @truncate(u32, str.len); |
| 78 | | const a: u32 = fetch32(str.ptr + (str.len >> 1) - 4); |
| 79 | | const b: u32 = fetch32(str.ptr + 4); |
| 80 | | const c: u32 = fetch32(str.ptr + str.len - 8); |
| 81 | | const d: u32 = fetch32(str.ptr + (str.len >> 1)); |
| 82 | | const e: u32 = fetch32(str.ptr); |
| 83 | | const f: u32 = fetch32(str.ptr + str.len - 4); |
| 83 | const a: u32 = fetch32(str.ptr, (str.len >> 1) - 4); |
| 84 | const b: u32 = fetch32(str.ptr, 4); |
| 85 | const c: u32 = fetch32(str.ptr, str.len - 8); |
| 86 | const d: u32 = fetch32(str.ptr, str.len >> 1); |
| 87 | const e: u32 = fetch32(str.ptr, 0); |
| 88 | const f: u32 = fetch32(str.ptr, str.len - 4); |
| 84 | 89 | |
| 85 | 90 | return fmix(mur(f, mur(e, mur(d, mur(c, mur(b, mur(a, len))))))); |
| 86 | 91 | } |
| ... | ... | @@ -101,11 +106,11 @@ pub const CityHash32 = struct { |
| 101 | 106 | var g: u32 = c1 *% len; |
| 102 | 107 | var f: u32 = g; |
| 103 | 108 | |
| 104 | | const a0: u32 = rotr32(fetch32(str.ptr + str.len - 4) *% c1, 17) *% c2; |
| 105 | | const a1: u32 = rotr32(fetch32(str.ptr + str.len - 8) *% c1, 17) *% c2; |
| 106 | | const a2: u32 = rotr32(fetch32(str.ptr + str.len - 16) *% c1, 17) *% c2; |
| 107 | | const a3: u32 = rotr32(fetch32(str.ptr + str.len - 12) *% c1, 17) *% c2; |
| 108 | | const a4: u32 = rotr32(fetch32(str.ptr + str.len - 20) *% c1, 17) *% c2; |
| 109 | const a0: u32 = rotr32(fetch32(str.ptr, str.len - 4) *% c1, 17) *% c2; |
| 110 | const a1: u32 = rotr32(fetch32(str.ptr, str.len - 8) *% c1, 17) *% c2; |
| 111 | const a2: u32 = rotr32(fetch32(str.ptr, str.len - 16) *% c1, 17) *% c2; |
| 112 | const a3: u32 = rotr32(fetch32(str.ptr, str.len - 12) *% c1, 17) *% c2; |
| 113 | const a4: u32 = rotr32(fetch32(str.ptr, str.len - 20) *% c1, 17) *% c2; |
| 109 | 114 | |
| 110 | 115 | h ^= a0; |
| 111 | 116 | h = rotr32(h, 19); |
| ... | ... | @@ -125,11 +130,11 @@ pub const CityHash32 = struct { |
| 125 | 130 | var iters = (str.len - 1) / 20; |
| 126 | 131 | var ptr = str.ptr; |
| 127 | 132 | while (iters != 0) : (iters -= 1) { |
| 128 | | const b0: u32 = rotr32(fetch32(ptr) *% c1, 17) *% c2; |
| 129 | | const b1: u32 = fetch32(ptr + 4); |
| 130 | | const b2: u32 = rotr32(fetch32(ptr + 8) *% c1, 17) *% c2; |
| 131 | | const b3: u32 = rotr32(fetch32(ptr + 12) *% c1, 17) *% c2; |
| 132 | | const b4: u32 = fetch32(ptr + 16); |
| 133 | const b0: u32 = rotr32(fetch32(ptr, 0) *% c1, 17) *% c2; |
| 134 | const b1: u32 = fetch32(ptr, 4); |
| 135 | const b2: u32 = rotr32(fetch32(ptr, 8) *% c1, 17) *% c2; |
| 136 | const b3: u32 = rotr32(fetch32(ptr, 12) *% c1, 17) *% c2; |
| 137 | const b4: u32 = fetch32(ptr, 16); |
| 133 | 138 | |
| 134 | 139 | h ^= b0; |
| 135 | 140 | h = rotr32(h, 18); |
| ... | ... | @@ -152,7 +157,7 @@ pub const CityHash32 = struct { |
| 152 | 157 | h = f; |
| 153 | 158 | f = g; |
| 154 | 159 | g = t; |
| 155 | | ptr += 20; |
| 160 | ptr = offsetPtr(ptr, 20); |
| 156 | 161 | } |
| 157 | 162 | g = rotr32(g, 11) *% c1; |
| 158 | 163 | g = rotr32(g, 17) *% c1; |
| ... | ... | @@ -176,22 +181,6 @@ pub const CityHash64 = struct { |
| 176 | 181 | const k1: u64 = 0xb492b66fbe98f273; |
| 177 | 182 | const k2: u64 = 0x9ae16a3b2f90404f; |
| 178 | 183 | |
| 179 | | fn fetch32(ptr: [*]const u8) u32 { |
| 180 | | var v: u32 = undefined; |
| 181 | | @memcpy(@ptrCast([*]u8, &v), ptr, 4); |
| 182 | | if (builtin.endian == .Big) |
| 183 | | return @byteSwap(u32, v); |
| 184 | | return v; |
| 185 | | } |
| 186 | | |
| 187 | | fn fetch64(ptr: [*]const u8) u64 { |
| 188 | | var v: u64 = undefined; |
| 189 | | @memcpy(@ptrCast([*]u8, &v), ptr, 8); |
| 190 | | if (builtin.endian == .Big) |
| 191 | | return @byteSwap(u64, v); |
| 192 | | return v; |
| 193 | | } |
| 194 | | |
| 195 | 184 | // Rotate right helper |
| 196 | 185 | fn rotr64(x: u64, comptime r: u64) u64 { |
| 197 | 186 | return (x >> r) | (x << (64 - r)); |
| ... | ... | @@ -222,16 +211,16 @@ pub const CityHash64 = struct { |
| 222 | 211 | const len: u64 = @as(u64, str.len); |
| 223 | 212 | if (len >= 8) { |
| 224 | 213 | const mul: u64 = k2 +% len *% 2; |
| 225 | | const a: u64 = fetch64(str.ptr) +% k2; |
| 226 | | const b: u64 = fetch64(str.ptr + str.len - 8); |
| 214 | const a: u64 = fetch64(str.ptr, 0) +% k2; |
| 215 | const b: u64 = fetch64(str.ptr, str.len - 8); |
| 227 | 216 | const c: u64 = rotr64(b, 37) *% mul +% a; |
| 228 | 217 | const d: u64 = (rotr64(a, 25) +% b) *% mul; |
| 229 | 218 | return hashLen16Mul(c, d, mul); |
| 230 | 219 | } |
| 231 | 220 | if (len >= 4) { |
| 232 | 221 | const mul: u64 = k2 +% len *% 2; |
| 233 | | const a: u64 = fetch32(str.ptr); |
| 234 | | return hashLen16Mul(len +% (a << 3), fetch32(str.ptr + str.len - 4), mul); |
| 222 | const a: u64 = fetch32(str.ptr, 0); |
| 223 | return hashLen16Mul(len +% (a << 3), fetch32(str.ptr, str.len - 4), mul); |
| 235 | 224 | } |
| 236 | 225 | if (len > 0) { |
| 237 | 226 | const a: u8 = str[0]; |
| ... | ... | @@ -247,10 +236,10 @@ pub const CityHash64 = struct { |
| 247 | 236 | fn hashLen17To32(str: []const u8) u64 { |
| 248 | 237 | const len: u64 = @as(u64, str.len); |
| 249 | 238 | const mul: u64 = k2 +% len *% 2; |
| 250 | | const a: u64 = fetch64(str.ptr) *% k1; |
| 251 | | const b: u64 = fetch64(str.ptr + 8); |
| 252 | | const c: u64 = fetch64(str.ptr + str.len - 8) *% mul; |
| 253 | | const d: u64 = fetch64(str.ptr + str.len - 16) *% k2; |
| 239 | const a: u64 = fetch64(str.ptr, 0) *% k1; |
| 240 | const b: u64 = fetch64(str.ptr, 8); |
| 241 | const c: u64 = fetch64(str.ptr, str.len - 8) *% mul; |
| 242 | const d: u64 = fetch64(str.ptr, str.len - 16) *% k2; |
| 254 | 243 | |
| 255 | 244 | return hashLen16Mul(rotr64(a +% b, 43) +% rotr64(c, 30) +% d, a +% rotr64(b +% k2, 18) +% c, mul); |
| 256 | 245 | } |
| ... | ... | @@ -258,14 +247,14 @@ pub const CityHash64 = struct { |
| 258 | 247 | fn hashLen33To64(str: []const u8) u64 { |
| 259 | 248 | const len: u64 = @as(u64, str.len); |
| 260 | 249 | const mul: u64 = k2 +% len *% 2; |
| 261 | | const a: u64 = fetch64(str.ptr) *% k2; |
| 262 | | const b: u64 = fetch64(str.ptr + 8); |
| 263 | | const c: u64 = fetch64(str.ptr + str.len - 24); |
| 264 | | const d: u64 = fetch64(str.ptr + str.len - 32); |
| 265 | | const e: u64 = fetch64(str.ptr + 16) *% k2; |
| 266 | | const f: u64 = fetch64(str.ptr + 24) *% 9; |
| 267 | | const g: u64 = fetch64(str.ptr + str.len - 8); |
| 268 | | const h: u64 = fetch64(str.ptr + str.len - 16) *% mul; |
| 250 | const a: u64 = fetch64(str.ptr, 0) *% k2; |
| 251 | const b: u64 = fetch64(str.ptr, 8); |
| 252 | const c: u64 = fetch64(str.ptr, str.len - 24); |
| 253 | const d: u64 = fetch64(str.ptr, str.len - 32); |
| 254 | const e: u64 = fetch64(str.ptr, 16) *% k2; |
| 255 | const f: u64 = fetch64(str.ptr, 24) *% 9; |
| 256 | const g: u64 = fetch64(str.ptr, str.len - 8); |
| 257 | const h: u64 = fetch64(str.ptr, str.len - 16) *% mul; |
| 269 | 258 | |
| 270 | 259 | const u: u64 = rotr64(a +% g, 43) +% (rotr64(b, 30) +% c) *% 9; |
| 271 | 260 | const v: u64 = ((a +% g) ^ d) +% f +% 1; |
| ... | ... | @@ -297,10 +286,10 @@ pub const CityHash64 = struct { |
| 297 | 286 | |
| 298 | 287 | fn weakHashLen32WithSeeds(ptr: [*]const u8, a: u64, b: u64) WeakPair { |
| 299 | 288 | return @call(.{ .modifier = .always_inline }, weakHashLen32WithSeedsHelper, .{ |
| 300 | | fetch64(ptr), |
| 301 | | fetch64(ptr + 8), |
| 302 | | fetch64(ptr + 16), |
| 303 | | fetch64(ptr + 24), |
| 289 | fetch64(ptr, 0), |
| 290 | fetch64(ptr, 8), |
| 291 | fetch64(ptr, 16), |
| 292 | fetch64(ptr, 24), |
| 304 | 293 | a, |
| 305 | 294 | b, |
| 306 | 295 | }); |
| ... | ... | @@ -319,29 +308,29 @@ pub const CityHash64 = struct { |
| 319 | 308 | |
| 320 | 309 | var len: u64 = @as(u64, str.len); |
| 321 | 310 | |
| 322 | | var x: u64 = fetch64(str.ptr + str.len - 40); |
| 323 | | var y: u64 = fetch64(str.ptr + str.len - 16) +% fetch64(str.ptr + str.len - 56); |
| 324 | | var z: u64 = hashLen16(fetch64(str.ptr + str.len - 48) +% len, fetch64(str.ptr + str.len - 24)); |
| 325 | | var v: WeakPair = weakHashLen32WithSeeds(str.ptr + str.len - 64, len, z); |
| 326 | | var w: WeakPair = weakHashLen32WithSeeds(str.ptr + str.len - 32, y +% k1, x); |
| 311 | var x: u64 = fetch64(str.ptr, str.len - 40); |
| 312 | var y: u64 = fetch64(str.ptr, str.len - 16) +% fetch64(str.ptr, str.len - 56); |
| 313 | var z: u64 = hashLen16(fetch64(str.ptr, str.len - 48) +% len, fetch64(str.ptr, str.len - 24)); |
| 314 | var v: WeakPair = weakHashLen32WithSeeds(offsetPtr(str.ptr, str.len - 64), len, z); |
| 315 | var w: WeakPair = weakHashLen32WithSeeds(offsetPtr(str.ptr, str.len - 32), y +% k1, x); |
| 327 | 316 | |
| 328 | | x = x *% k1 +% fetch64(str.ptr); |
| 317 | x = x *% k1 +% fetch64(str.ptr, 0); |
| 329 | 318 | len = (len - 1) & ~@intCast(u64, 63); |
| 330 | 319 | |
| 331 | 320 | var ptr: [*]const u8 = str.ptr; |
| 332 | 321 | while (true) { |
| 333 | | x = rotr64(x +% y +% v.first +% fetch64(ptr + 8), 37) *% k1; |
| 334 | | y = rotr64(y +% v.second +% fetch64(ptr + 48), 42) *% k1; |
| 322 | x = rotr64(x +% y +% v.first +% fetch64(ptr, 8), 37) *% k1; |
| 323 | y = rotr64(y +% v.second +% fetch64(ptr, 48), 42) *% k1; |
| 335 | 324 | x ^= w.second; |
| 336 | | y +%= v.first +% fetch64(ptr + 40); |
| 325 | y +%= v.first +% fetch64(ptr, 40); |
| 337 | 326 | z = rotr64(z +% w.first, 33) *% k1; |
| 338 | 327 | v = weakHashLen32WithSeeds(ptr, v.second *% k1, x +% w.first); |
| 339 | | w = weakHashLen32WithSeeds(ptr + 32, z +% w.second, y +% fetch64(ptr + 16)); |
| 328 | w = weakHashLen32WithSeeds(offsetPtr(ptr, 32), z +% w.second, y +% fetch64(ptr, 16)); |
| 340 | 329 | const t: u64 = z; |
| 341 | 330 | z = x; |
| 342 | 331 | x = t; |
| 343 | 332 | |
| 344 | | ptr += 64; |
| 333 | ptr = offsetPtr(ptr, 64); |
| 345 | 334 | len -= 64; |
| 346 | 335 | if (len == 0) |
| 347 | 336 | break; |
| ... | ... | @@ -359,27 +348,31 @@ pub const CityHash64 = struct { |
| 359 | 348 | } |
| 360 | 349 | }; |
| 361 | 350 | |
| 362 | | fn SMHasherTest(comptime hash_fn: anytype, comptime hashbits: u32) u32 { |
| 363 | | const hashbytes = hashbits / 8; |
| 351 | fn SMHasherTest(comptime hash_fn: anytype) u32 { |
| 352 | const HashResult = @typeInfo(@TypeOf(hash_fn)).Fn.return_type.?; |
| 353 | |
| 364 | 354 | var key: [256]u8 = undefined; |
| 365 | | var hashes: [hashbytes * 256]u8 = undefined; |
| 366 | | var final: [hashbytes]u8 = undefined; |
| 355 | var hashes_bytes: [256 * @sizeOf(HashResult)]u8 = undefined; |
| 356 | var final: HashResult = 0; |
| 367 | 357 | |
| 368 | | @memset(@ptrCast([*]u8, &key[0]), 0, @sizeOf(@TypeOf(key))); |
| 369 | | @memset(@ptrCast([*]u8, &hashes[0]), 0, @sizeOf(@TypeOf(hashes))); |
| 370 | | @memset(@ptrCast([*]u8, &final[0]), 0, @sizeOf(@TypeOf(final))); |
| 358 | std.mem.set(u8, &key, 0); |
| 359 | std.mem.set(u8, &hashes_bytes, 0); |
| 371 | 360 | |
| 372 | 361 | var i: u32 = 0; |
| 373 | 362 | while (i < 256) : (i += 1) { |
| 374 | 363 | key[i] = @intCast(u8, i); |
| 375 | 364 | |
| 376 | | var h = hash_fn(key[0..i], 256 - i); |
| 377 | | if (builtin.endian == .Big) |
| 378 | | h = @byteSwap(@TypeOf(h), h); |
| 379 | | @memcpy(@ptrCast([*]u8, &hashes[i * hashbytes]), @ptrCast([*]u8, &h), hashbytes); |
| 365 | var h: HashResult = hash_fn(key[0..i], 256 - i); |
| 366 | |
| 367 | // comptime can't really do reinterpret casting yet, |
| 368 | // so we need to write the bytes manually. |
| 369 | for (hashes_bytes[i*@sizeOf(HashResult)..][0..@sizeOf(HashResult)]) |*byte| { |
| 370 | byte.* = @truncate(u8, h); |
| 371 | h = h >> 8; |
| 372 | } |
| 380 | 373 | } |
| 381 | 374 | |
| 382 | | return @truncate(u32, hash_fn(&hashes, 0)); |
| 375 | return @truncate(u32, hash_fn(&hashes_bytes, 0)); |
| 383 | 376 | } |
| 384 | 377 | |
| 385 | 378 | fn CityHash32hashIgnoreSeed(str: []const u8, seed: u32) u32 { |
| ... | ... | @@ -387,13 +380,28 @@ fn CityHash32hashIgnoreSeed(str: []const u8, seed: u32) u32 { |
| 387 | 380 | } |
| 388 | 381 | |
| 389 | 382 | test "cityhash32" { |
| 390 | | // Note: SMHasher doesn't provide a 32bit version of the algorithm. |
| 391 | | // Note: The implementation was verified against the Google Abseil version. |
| 392 | | std.testing.expectEqual(SMHasherTest(CityHash32hashIgnoreSeed, 32), 0x68254F81); |
| 383 | const Test = struct { |
| 384 | fn doTest() void { |
| 385 | // Note: SMHasher doesn't provide a 32bit version of the algorithm. |
| 386 | // Note: The implementation was verified against the Google Abseil version. |
| 387 | std.testing.expectEqual(SMHasherTest(CityHash32hashIgnoreSeed), 0x68254F81); |
| 388 | std.testing.expectEqual(SMHasherTest(CityHash32hashIgnoreSeed), 0x68254F81); |
| 389 | } |
| 390 | }; |
| 391 | Test.doTest(); |
| 392 | @setEvalBranchQuota(50000); |
| 393 | comptime Test.doTest(); |
| 393 | 394 | } |
| 394 | 395 | |
| 395 | 396 | test "cityhash64" { |
| 396 | | // Note: This is not compliant with the SMHasher implementation of CityHash64! |
| 397 | | // Note: The implementation was verified against the Google Abseil version. |
| 398 | | std.testing.expectEqual(SMHasherTest(CityHash64.hashWithSeed, 64), 0x5FABC5C5); |
| 397 | const Test = struct { |
| 398 | fn doTest() void { |
| 399 | // Note: This is not compliant with the SMHasher implementation of CityHash64! |
| 400 | // Note: The implementation was verified against the Google Abseil version. |
| 401 | std.testing.expectEqual(SMHasherTest(CityHash64.hashWithSeed), 0x5FABC5C5); |
| 402 | } |
| 403 | }; |
| 404 | Test.doTest(); |
| 405 | @setEvalBranchQuota(50000); |
| 406 | comptime Test.doTest(); |
| 399 | 407 | } |