| ... | ... | @@ -0,0 +1,125 @@ |
| 1 | const std = @import("std"); |
| 2 | |
| 3 | const readInt = std.mem.readInt; |
| 4 | const assert = std.debug.assert; |
| 5 | const expect = std.testing.expect; |
| 6 | const expectEqual = std.testing.expectEqual; |
| 7 | |
| 8 | const RAPID_SEED: u64 = 0xbdd89aa982704029; |
| 9 | const RAPID_SECRET: [3]u64 = .{ 0x2d358dccaa6c78a5, 0x8bb84b93962eacc9, 0x4b33a62ed433d4a3 }; |
| 10 | |
| 11 | pub fn hash(seed: u64, input: []const u8) u64 { |
| 12 | const sc = RAPID_SECRET; |
| 13 | const len = input.len; |
| 14 | var a: u64 = 0; |
| 15 | var b: u64 = 0; |
| 16 | var k = input; |
| 17 | var is: [3]u64 = .{ seed, 0, 0 }; |
| 18 | |
| 19 | is[0] ^= mix(seed ^ sc[0], sc[1]) ^ len; |
| 20 | |
| 21 | if (len <= 16) { |
| 22 | if (len >= 4) { |
| 23 | const d: u64 = ((len & 24) >> @intCast(len >> 3)); |
| 24 | const e = len - 4; |
| 25 | a = (r32(k) << 32) | r32(k[e..]); |
| 26 | b = ((r32(k[d..]) << 32) | r32(k[(e - d)..])); |
| 27 | } else if (len > 0) |
| 28 | a = (@as(u64, k[0]) << 56) | (@as(u64, k[len >> 1]) << 32) | @as(u64, k[len - 1]); |
| 29 | } else { |
| 30 | var remain = len; |
| 31 | if (len > 48) { |
| 32 | is[1] = is[0]; |
| 33 | is[2] = is[0]; |
| 34 | while (remain >= 96) { |
| 35 | inline for (0..6) |i| { |
| 36 | const m1 = r64(k[8 * i * 2 ..]); |
| 37 | const m2 = r64(k[8 * (i * 2 + 1) ..]); |
| 38 | is[i % 3] = mix(m1 ^ sc[i % 3], m2 ^ is[i % 3]); |
| 39 | } |
| 40 | k = k[96..]; |
| 41 | remain -= 96; |
| 42 | } |
| 43 | if (remain >= 48) { |
| 44 | inline for (0..3) |i| { |
| 45 | const m1 = r64(k[8 * i * 2 ..]); |
| 46 | const m2 = r64(k[8 * (i * 2 + 1) ..]); |
| 47 | is[i] = mix(m1 ^ sc[i], m2 ^ is[i]); |
| 48 | } |
| 49 | k = k[48..]; |
| 50 | remain -= 48; |
| 51 | } |
| 52 | |
| 53 | is[0] ^= is[1] ^ is[2]; |
| 54 | } |
| 55 | |
| 56 | if (remain > 16) { |
| 57 | is[0] = mix(r64(k) ^ sc[2], r64(k[8..]) ^ is[0] ^ sc[1]); |
| 58 | if (remain > 32) { |
| 59 | is[0] = mix(r64(k[16..]) ^ sc[2], r64(k[24..]) ^ is[0]); |
| 60 | } |
| 61 | } |
| 62 | |
| 63 | a = r64(input[len - 16 ..]); |
| 64 | b = r64(input[len - 8 ..]); |
| 65 | } |
| 66 | |
| 67 | a ^= sc[1]; |
| 68 | b ^= is[0]; |
| 69 | mum(&a, &b); |
| 70 | return mix(a ^ sc[0] ^ len, b ^ sc[1]); |
| 71 | } |
| 72 | |
| 73 | test "RapidHash.hash" { |
| 74 | const bytes: []const u8 = "abcdefgh" ** 128; |
| 75 | |
| 76 | const sizes: [13]u64 = .{ 0, 1, 2, 3, 4, 8, 16, 32, 64, 128, 256, 512, 1024 }; |
| 77 | |
| 78 | const outcomes: [13]u64 = .{ |
| 79 | 0x5a6ef77074ebc84b, |
| 80 | 0xc11328477bc0f5d1, |
| 81 | 0x5644ac035e40d569, |
| 82 | 0x347080fbf5fcd81, |
| 83 | 0x56b66b8dc802bcc, |
| 84 | 0xb6bf9055973aac7c, |
| 85 | 0xed56d62eead1e402, |
| 86 | 0xc19072d767da8ffb, |
| 87 | 0x89bb40a9928a4f0d, |
| 88 | 0xe0af7c5e7b6e29fd, |
| 89 | 0x9a3ed35fbedfa11a, |
| 90 | 0x4c684b2119ca19fb, |
| 91 | 0x4b575f5bf25600d6, |
| 92 | }; |
| 93 | |
| 94 | var success: bool = true; |
| 95 | for (sizes, outcomes) |s, e| { |
| 96 | const r = hash(RAPID_SEED, bytes[0..s]); |
| 97 | |
| 98 | expectEqual(e, r) catch |err| { |
| 99 | std.debug.print("Failed on {d}: {!}\n", .{ s, err }); |
| 100 | success = false; |
| 101 | }; |
| 102 | } |
| 103 | try expect(success); |
| 104 | } |
| 105 | |
| 106 | inline fn mum(a: *u64, b: *u64) void { |
| 107 | const r = @as(u128, a.*) * b.*; |
| 108 | a.* = @truncate(r); |
| 109 | b.* = @truncate(r >> 64); |
| 110 | } |
| 111 | |
| 112 | inline fn mix(a: u64, b: u64) u64 { |
| 113 | var copy_a = a; |
| 114 | var copy_b = b; |
| 115 | mum(&copy_a, &copy_b); |
| 116 | return copy_a ^ copy_b; |
| 117 | } |
| 118 | |
| 119 | inline fn r64(p: []const u8) u64 { |
| 120 | return readInt(u64, p[0..8], .little); |
| 121 | } |
| 122 | |
| 123 | inline fn r32(p: []const u8) u64 { |
| 124 | return readInt(u32, p[0..4], .little); |
| 125 | } |