| ... | @@ -5,11 +5,7 @@ const expectEqual = std.testing.expectEqual; | ... | @@ -5,11 +5,7 @@ const expectEqual = std.testing.expectEqual; |
| 5 | const rotl = std.math.rotl; | 5 | const rotl = std.math.rotl; |
| 6 | | 6 | |
| 7 | pub const XxHash64 = struct { | 7 | pub const XxHash64 = struct { |
| 8 | acc1: u64, | 8 | accumulator: Accumulator, |
| 9 | acc2: u64, | | |
| 10 | acc3: u64, | | |
| 11 | acc4: u64, | | |
| 12 | | | |
| 13 | seed: u64, | 9 | seed: u64, |
| 14 | buf: [32]u8, | 10 | buf: [32]u8, |
| 15 | buf_len: usize, | 11 | buf_len: usize, |
| ... | @@ -21,20 +17,174 @@ pub const XxHash64 = struct { | ... | @@ -21,20 +17,174 @@ pub const XxHash64 = struct { |
| 21 | const prime_4 = 0x85EBCA77C2B2AE63; // 0b1000010111101011110010100111011111000010101100101010111001100011 | 17 | const prime_4 = 0x85EBCA77C2B2AE63; // 0b1000010111101011110010100111011111000010101100101010111001100011 |
| 22 | const prime_5 = 0x27D4EB2F165667C5; // 0b0010011111010100111010110010111100010110010101100110011111000101 | 18 | const prime_5 = 0x27D4EB2F165667C5; // 0b0010011111010100111010110010111100010110010101100110011111000101 |
| 23 | | 19 | |
| | 20 | const Accumulator = struct { |
| | 21 | acc1: u64, |
| | 22 | acc2: u64, |
| | 23 | acc3: u64, |
| | 24 | acc4: u64, |
| | 25 | |
| | 26 | fn init(seed: u64) Accumulator { |
| | 27 | return .{ |
| | 28 | .acc1 = seed +% prime_1 +% prime_2, |
| | 29 | .acc2 = seed +% prime_2, |
| | 30 | .acc3 = seed, |
| | 31 | .acc4 = seed -% prime_1, |
| | 32 | }; |
| | 33 | } |
| | 34 | |
| | 35 | fn updateEmpty(self: *Accumulator, input: anytype, comptime unroll_count: usize) usize { |
| | 36 | var i: usize = 0; |
| | 37 | |
| | 38 | if (unroll_count > 0) { |
| | 39 | const unrolled_bytes = unroll_count * 32; |
| | 40 | while (i + unrolled_bytes <= input.len) : (i += unrolled_bytes) { |
| | 41 | inline for (0..unroll_count) |j| { |
| | 42 | self.processStripe(input[i + j * 32 ..][0..32]); |
| | 43 | } |
| | 44 | } |
| | 45 | } |
| | 46 | |
| | 47 | while (i + 32 <= input.len) : (i += 32) { |
| | 48 | self.processStripe(input[i..][0..32]); |
| | 49 | } |
| | 50 | |
| | 51 | return i; |
| | 52 | } |
| | 53 | |
| | 54 | fn processStripe(self: *Accumulator, buf: *const [32]u8) void { |
| | 55 | self.acc1 = round(self.acc1, mem.readIntLittle(u64, buf[0..8])); |
| | 56 | self.acc2 = round(self.acc2, mem.readIntLittle(u64, buf[8..16])); |
| | 57 | self.acc3 = round(self.acc3, mem.readIntLittle(u64, buf[16..24])); |
| | 58 | self.acc4 = round(self.acc4, mem.readIntLittle(u64, buf[24..32])); |
| | 59 | } |
| | 60 | |
| | 61 | fn merge(self: Accumulator) u64 { |
| | 62 | var acc = rotl(u64, self.acc1, 1) +% rotl(u64, self.acc2, 7) +% |
| | 63 | rotl(u64, self.acc3, 12) +% rotl(u64, self.acc4, 18); |
| | 64 | acc = mergeAccumulator(acc, self.acc1); |
| | 65 | acc = mergeAccumulator(acc, self.acc2); |
| | 66 | acc = mergeAccumulator(acc, self.acc3); |
| | 67 | acc = mergeAccumulator(acc, self.acc4); |
| | 68 | return acc; |
| | 69 | } |
| | 70 | |
| | 71 | fn mergeAccumulator(acc: u64, other: u64) u64 { |
| | 72 | const a = acc ^ round(0, other); |
| | 73 | const b = a *% prime_1; |
| | 74 | return b +% prime_4; |
| | 75 | } |
| | 76 | }; |
| | 77 | |
| | 78 | fn finalize( |
| | 79 | unfinished: u64, |
| | 80 | byte_count: usize, |
| | 81 | partial: anytype, |
| | 82 | ) u64 { |
| | 83 | std.debug.assert(partial.len < 32); |
| | 84 | var acc = unfinished +% @as(u64, byte_count) +% @as(u64, partial.len); |
| | 85 | |
| | 86 | switch (partial.len) { |
| | 87 | inline 0, 1, 2, 3 => |count| { |
| | 88 | inline for (0..count) |i| acc = finalize1(acc, partial[i]); |
| | 89 | return avalanche(acc); |
| | 90 | }, |
| | 91 | inline 4, 5, 6, 7 => |count| { |
| | 92 | acc = finalize4(acc, partial[0..4]); |
| | 93 | inline for (4..count) |i| acc = finalize1(acc, partial[i]); |
| | 94 | return avalanche(acc); |
| | 95 | }, |
| | 96 | inline 8, 9, 10, 11 => |count| { |
| | 97 | acc = finalize8(acc, partial[0..8]); |
| | 98 | inline for (8..count) |i| acc = finalize1(acc, partial[i]); |
| | 99 | return avalanche(acc); |
| | 100 | }, |
| | 101 | inline 12, 13, 14, 15 => |count| { |
| | 102 | acc = finalize8(acc, partial[0..8]); |
| | 103 | acc = finalize4(acc, partial[8..12]); |
| | 104 | inline for (12..count) |i| acc = finalize1(acc, partial[i]); |
| | 105 | return avalanche(acc); |
| | 106 | }, |
| | 107 | inline 16, 17, 18, 19 => |count| { |
| | 108 | acc = finalize8(acc, partial[0..8]); |
| | 109 | acc = finalize8(acc, partial[8..16]); |
| | 110 | inline for (16..count) |i| acc = finalize1(acc, partial[i]); |
| | 111 | return avalanche(acc); |
| | 112 | }, |
| | 113 | inline 20, 21, 22, 23 => |count| { |
| | 114 | acc = finalize8(acc, partial[0..8]); |
| | 115 | acc = finalize8(acc, partial[8..16]); |
| | 116 | acc = finalize4(acc, partial[16..20]); |
| | 117 | inline for (20..count) |i| acc = finalize1(acc, partial[i]); |
| | 118 | return avalanche(acc); |
| | 119 | }, |
| | 120 | inline 24, 25, 26, 27 => |count| { |
| | 121 | acc = finalize8(acc, partial[0..8]); |
| | 122 | acc = finalize8(acc, partial[8..16]); |
| | 123 | acc = finalize8(acc, partial[16..24]); |
| | 124 | inline for (24..count) |i| acc = finalize1(acc, partial[i]); |
| | 125 | return avalanche(acc); |
| | 126 | }, |
| | 127 | inline 28, 29, 30, 31 => |count| { |
| | 128 | acc = finalize8(acc, partial[0..8]); |
| | 129 | acc = finalize8(acc, partial[8..16]); |
| | 130 | acc = finalize8(acc, partial[16..24]); |
| | 131 | acc = finalize4(acc, partial[24..28]); |
| | 132 | inline for (28..count) |i| acc = finalize1(acc, partial[i]); |
| | 133 | return avalanche(acc); |
| | 134 | }, |
| | 135 | else => unreachable, |
| | 136 | } |
| | 137 | } |
| | 138 | |
| | 139 | fn finalize8(v: u64, bytes: *const [8]u8) u64 { |
| | 140 | var acc = v; |
| | 141 | const lane = mem.readIntLittle(u64, bytes); |
| | 142 | acc ^= round(0, lane); |
| | 143 | acc = rotl(u64, acc, 27) *% prime_1; |
| | 144 | acc +%= prime_4; |
| | 145 | return acc; |
| | 146 | } |
| | 147 | |
| | 148 | fn finalize4(v: u64, bytes: *const [4]u8) u64 { |
| | 149 | var acc = v; |
| | 150 | const lane = @as(u64, mem.readIntLittle(u32, bytes)); |
| | 151 | acc ^= lane *% prime_1; |
| | 152 | acc = rotl(u64, acc, 23) *% prime_2; |
| | 153 | acc +%= prime_3; |
| | 154 | return acc; |
| | 155 | } |
| | 156 | |
| | 157 | fn finalize1(v: u64, byte: u8) u64 { |
| | 158 | var acc = v; |
| | 159 | const lane = @as(u64, byte); |
| | 160 | acc ^= lane *% prime_5; |
| | 161 | acc = rotl(u64, acc, 11) *% prime_1; |
| | 162 | return acc; |
| | 163 | } |
| | 164 | |
| | 165 | fn avalanche(value: u64) u64 { |
| | 166 | var result = value ^ (value >> 33); |
| | 167 | result *%= prime_2; |
| | 168 | result ^= result >> 29; |
| | 169 | result *%= prime_3; |
| | 170 | result ^= result >> 32; |
| | 171 | |
| | 172 | return result; |
| | 173 | } |
| | 174 | |
| 24 | pub fn init(seed: u64) XxHash64 { | 175 | pub fn init(seed: u64) XxHash64 { |
| 25 | return XxHash64{ | 176 | return XxHash64{ |
| | 177 | .accumulator = Accumulator.init(seed), |
| 26 | .seed = seed, | 178 | .seed = seed, |
| 27 | .acc1 = seed +% prime_1 +% prime_2, | | |
| 28 | .acc2 = seed +% prime_2, | | |
| 29 | .acc3 = seed, | | |
| 30 | .acc4 = seed -% prime_1, | | |
| 31 | .buf = undefined, | 179 | .buf = undefined, |
| 32 | .buf_len = 0, | 180 | .buf_len = 0, |
| 33 | .byte_count = 0, | 181 | .byte_count = 0, |
| 34 | }; | 182 | }; |
| 35 | } | 183 | } |
| 36 | | 184 | |
| 37 | pub fn update(self: *XxHash64, input: []const u8) void { | 185 | pub fn update(self: *XxHash64, input: anytype) void { |
| | 186 | validateType(@TypeOf(input)); |
| | 187 | |
| 38 | if (input.len < 32 - self.buf_len) { | 188 | if (input.len < 32 - self.buf_len) { |
| 39 | @memcpy(self.buf[self.buf_len..][0..input.len], input); | 189 | @memcpy(self.buf[self.buf_len..][0..input.len], input); |
| 40 | self.buf_len += input.len; | 190 | self.buf_len += input.len; |
| ... | @@ -46,99 +196,54 @@ pub const XxHash64 = struct { | ... | @@ -46,99 +196,54 @@ pub const XxHash64 = struct { |
| 46 | if (self.buf_len > 0) { | 196 | if (self.buf_len > 0) { |
| 47 | i = 32 - self.buf_len; | 197 | i = 32 - self.buf_len; |
| 48 | @memcpy(self.buf[self.buf_len..][0..i], input[0..i]); | 198 | @memcpy(self.buf[self.buf_len..][0..i], input[0..i]); |
| 49 | self.processStripe(&self.buf); | 199 | self.accumulator.processStripe(&self.buf); |
| 50 | self.buf_len = 0; | 200 | self.byte_count += self.buf_len; |
| 51 | } | 201 | } |
| 52 | | 202 | |
| 53 | while (i + 32 <= input.len) : (i += 32) { | 203 | i += self.accumulator.updateEmpty(input[i..], 32); |
| 54 | self.processStripe(input[i..][0..32]); | 204 | self.byte_count += i; |
| 55 | } | | |
| 56 | | 205 | |
| 57 | const remaining_bytes = input[i..]; | 206 | const remaining_bytes = input[i..]; |
| 58 | @memcpy(self.buf[0..remaining_bytes.len], remaining_bytes); | 207 | @memcpy(self.buf[0..remaining_bytes.len], remaining_bytes); |
| 59 | self.buf_len = remaining_bytes.len; | 208 | self.buf_len = remaining_bytes.len; |
| 60 | } | 209 | } |
| 61 | | 210 | |
| 62 | inline fn processStripe(self: *XxHash64, buf: *const [32]u8) void { | 211 | fn round(acc: u64, lane: u64) u64 { |
| 63 | self.acc1 = round(self.acc1, mem.readIntLittle(u64, buf[0..8])); | | |
| 64 | self.acc2 = round(self.acc2, mem.readIntLittle(u64, buf[8..16])); | | |
| 65 | self.acc3 = round(self.acc3, mem.readIntLittle(u64, buf[16..24])); | | |
| 66 | self.acc4 = round(self.acc4, mem.readIntLittle(u64, buf[24..32])); | | |
| 67 | self.byte_count += 32; | | |
| 68 | } | | |
| 69 | | | |
| 70 | inline fn round(acc: u64, lane: u64) u64 { | | |
| 71 | const a = acc +% (lane *% prime_2); | 212 | const a = acc +% (lane *% prime_2); |
| 72 | const b = rotl(u64, a, 31); | 213 | const b = rotl(u64, a, 31); |
| 73 | return b *% prime_1; | 214 | return b *% prime_1; |
| 74 | } | 215 | } |
| 75 | | 216 | |
| 76 | pub fn final(self: *XxHash64) u64 { | 217 | pub fn final(self: *XxHash64) u64 { |
| 77 | var acc: u64 = undefined; | 218 | const unfinished = if (self.byte_count < 32) |
| 78 | | 219 | self.seed +% prime_5 |
| 79 | if (self.byte_count < 32) { | 220 | else |
| 80 | acc = self.seed +% prime_5; | 221 | self.accumulator.merge(); |
| 81 | } else { | | |
| 82 | acc = rotl(u64, self.acc1, 1) +% rotl(u64, self.acc2, 7) +% | | |
| 83 | rotl(u64, self.acc3, 12) +% rotl(u64, self.acc4, 18); | | |
| 84 | acc = mergeAccumulator(acc, self.acc1); | | |
| 85 | acc = mergeAccumulator(acc, self.acc2); | | |
| 86 | acc = mergeAccumulator(acc, self.acc3); | | |
| 87 | acc = mergeAccumulator(acc, self.acc4); | | |
| 88 | } | | |
| 89 | | 222 | |
| 90 | acc = acc +% @as(u64, self.byte_count) +% @as(u64, self.buf_len); | 223 | return finalize(unfinished, self.byte_count, self.buf[0..self.buf_len]); |
| | 224 | } |
| 91 | | 225 | |
| 92 | var pos: usize = 0; | 226 | const Size = enum { |
| 93 | while (pos + 8 <= self.buf_len) : (pos += 8) { | 227 | small, |
| 94 | const lane = mem.readIntLittle(u64, self.buf[pos..][0..8]); | 228 | large, |
| 95 | acc ^= round(0, lane); | 229 | unknown, |
| 96 | acc = rotl(u64, acc, 27) *% prime_1; | 230 | }; |
| 97 | acc +%= prime_4; | | |
| 98 | } | | |
| 99 | | 231 | |
| 100 | if (pos + 4 <= self.buf_len) { | 232 | pub fn hash(seed: u64, input: anytype) u64 { |
| 101 | const lane = @as(u64, mem.readIntLittle(u32, self.buf[pos..][0..4])); | 233 | validateType(@TypeOf(input)); |
| 102 | acc ^= lane *% prime_1; | | |
| 103 | acc = rotl(u64, acc, 23) *% prime_2; | | |
| 104 | acc +%= prime_3; | | |
| 105 | pos += 4; | | |
| 106 | } | | |
| 107 | | 234 | |
| 108 | while (pos < self.buf_len) : (pos += 1) { | 235 | if (input.len < 32) { |
| 109 | const lane = @as(u64, self.buf[pos]); | 236 | return finalize(seed +% prime_5, 0, input); |
| 110 | acc ^= lane *% prime_5; | 237 | } else { |
| 111 | acc = rotl(u64, acc, 11) *% prime_1; | 238 | var hasher = Accumulator.init(seed); |
| | 239 | const i = hasher.updateEmpty(input, 0); |
| | 240 | return finalize(hasher.merge(), i, input[i..]); |
| 112 | } | 241 | } |
| 113 | | | |
| 114 | acc ^= acc >> 33; | | |
| 115 | acc *%= prime_2; | | |
| 116 | acc ^= acc >> 29; | | |
| 117 | acc *%= prime_3; | | |
| 118 | acc ^= acc >> 32; | | |
| 119 | | | |
| 120 | return acc; | | |
| 121 | } | | |
| 122 | | | |
| 123 | inline fn mergeAccumulator(acc: u64, other: u64) u64 { | | |
| 124 | const a = acc ^ round(0, other); | | |
| 125 | const b = a *% prime_1; | | |
| 126 | return b +% prime_4; | | |
| 127 | } | | |
| 128 | | | |
| 129 | pub fn hash(seed: u64, input: []const u8) u64 { | | |
| 130 | var hasher = XxHash64.init(seed); | | |
| 131 | hasher.update(input); | | |
| 132 | return hasher.final(); | | |
| 133 | } | 242 | } |
| 134 | }; | 243 | }; |
| 135 | | 244 | |
| 136 | pub const XxHash32 = struct { | 245 | pub const XxHash32 = struct { |
| 137 | acc1: u32, | 246 | accumulator: Accumulator, |
| 138 | acc2: u32, | | |
| 139 | acc3: u32, | | |
| 140 | acc4: u32, | | |
| 141 | | | |
| 142 | seed: u32, | 247 | seed: u32, |
| 143 | buf: [16]u8, | 248 | buf: [16]u8, |
| 144 | buf_len: usize, | 249 | buf_len: usize, |
| ... | @@ -150,13 +255,57 @@ pub const XxHash32 = struct { | ... | @@ -150,13 +255,57 @@ pub const XxHash32 = struct { |
| 150 | const prime_4 = 0x27D4EB2F; // 0b00100111110101001110101100101111 | 255 | const prime_4 = 0x27D4EB2F; // 0b00100111110101001110101100101111 |
| 151 | const prime_5 = 0x165667B1; // 0b00010110010101100110011110110001 | 256 | const prime_5 = 0x165667B1; // 0b00010110010101100110011110110001 |
| 152 | | 257 | |
| | 258 | const Accumulator = struct { |
| | 259 | acc1: u32, |
| | 260 | acc2: u32, |
| | 261 | acc3: u32, |
| | 262 | acc4: u32, |
| | 263 | |
| | 264 | fn init(seed: u32) Accumulator { |
| | 265 | return .{ |
| | 266 | .acc1 = seed +% prime_1 +% prime_2, |
| | 267 | .acc2 = seed +% prime_2, |
| | 268 | .acc3 = seed, |
| | 269 | .acc4 = seed -% prime_1, |
| | 270 | }; |
| | 271 | } |
| | 272 | |
| | 273 | fn updateEmpty(self: *Accumulator, input: anytype, comptime unroll_count: usize) usize { |
| | 274 | var i: usize = 0; |
| | 275 | |
| | 276 | if (unroll_count > 0) { |
| | 277 | const unrolled_bytes = unroll_count * 16; |
| | 278 | while (i + unrolled_bytes <= input.len) : (i += unrolled_bytes) { |
| | 279 | inline for (0..unroll_count) |j| { |
| | 280 | self.processStripe(input[i + j * 16 ..][0..16]); |
| | 281 | } |
| | 282 | } |
| | 283 | } |
| | 284 | |
| | 285 | while (i + 16 <= input.len) : (i += 16) { |
| | 286 | self.processStripe(input[i..][0..16]); |
| | 287 | } |
| | 288 | |
| | 289 | return i; |
| | 290 | } |
| | 291 | |
| | 292 | fn processStripe(self: *Accumulator, buf: *const [16]u8) void { |
| | 293 | self.acc1 = round(self.acc1, mem.readIntLittle(u32, buf[0..4])); |
| | 294 | self.acc2 = round(self.acc2, mem.readIntLittle(u32, buf[4..8])); |
| | 295 | self.acc3 = round(self.acc3, mem.readIntLittle(u32, buf[8..12])); |
| | 296 | self.acc4 = round(self.acc4, mem.readIntLittle(u32, buf[12..16])); |
| | 297 | } |
| | 298 | |
| | 299 | fn merge(self: Accumulator) u32 { |
| | 300 | return rotl(u32, self.acc1, 1) +% rotl(u32, self.acc2, 7) +% |
| | 301 | rotl(u32, self.acc3, 12) +% rotl(u32, self.acc4, 18); |
| | 302 | } |
| | 303 | }; |
| | 304 | |
| 153 | pub fn init(seed: u32) XxHash32 { | 305 | pub fn init(seed: u32) XxHash32 { |
| 154 | return XxHash32{ | 306 | return XxHash32{ |
| | 307 | .accumulator = Accumulator.init(seed), |
| 155 | .seed = seed, | 308 | .seed = seed, |
| 156 | .acc1 = seed +% prime_1 +% prime_2, | | |
| 157 | .acc2 = seed +% prime_2, | | |
| 158 | .acc3 = seed, | | |
| 159 | .acc4 = seed -% prime_1, | | |
| 160 | .buf = undefined, | 309 | .buf = undefined, |
| 161 | .buf_len = 0, | 310 | .buf_len = 0, |
| 162 | .byte_count = 0, | 311 | .byte_count = 0, |
| ... | @@ -164,6 +313,8 @@ pub const XxHash32 = struct { | ... | @@ -164,6 +313,8 @@ pub const XxHash32 = struct { |
| 164 | } | 313 | } |
| 165 | | 314 | |
| 166 | pub fn update(self: *XxHash32, input: []const u8) void { | 315 | pub fn update(self: *XxHash32, input: []const u8) void { |
| | 316 | validateType(@TypeOf(input)); |
| | 317 | |
| 167 | if (input.len < 16 - self.buf_len) { | 318 | if (input.len < 16 - self.buf_len) { |
| 168 | @memcpy(self.buf[self.buf_len..][0..input.len], input); | 319 | @memcpy(self.buf[self.buf_len..][0..input.len], input); |
| 169 | self.buf_len += input.len; | 320 | self.buf_len += input.len; |
| ... | @@ -175,59 +326,85 @@ pub const XxHash32 = struct { | ... | @@ -175,59 +326,85 @@ pub const XxHash32 = struct { |
| 175 | if (self.buf_len > 0) { | 326 | if (self.buf_len > 0) { |
| 176 | i = 16 - self.buf_len; | 327 | i = 16 - self.buf_len; |
| 177 | @memcpy(self.buf[self.buf_len..][0..i], input[0..i]); | 328 | @memcpy(self.buf[self.buf_len..][0..i], input[0..i]); |
| 178 | self.processStripe(&self.buf); | 329 | self.accumulator.processStripe(&self.buf); |
| | 330 | self.byte_count += self.buf_len; |
| 179 | self.buf_len = 0; | 331 | self.buf_len = 0; |
| 180 | } | 332 | } |
| 181 | | 333 | |
| 182 | while (i + 16 <= input.len) : (i += 16) { | 334 | i += self.accumulator.updateEmpty(input[i..], 16); |
| 183 | self.processStripe(input[i..][0..16]); | 335 | self.byte_count += i; |
| 184 | } | | |
| 185 | | 336 | |
| 186 | const remaining_bytes = input[i..]; | 337 | const remaining_bytes = input[i..]; |
| 187 | @memcpy(self.buf[0..remaining_bytes.len], remaining_bytes); | 338 | @memcpy(self.buf[0..remaining_bytes.len], remaining_bytes); |
| 188 | self.buf_len = remaining_bytes.len; | 339 | self.buf_len = remaining_bytes.len; |
| 189 | } | 340 | } |
| 190 | | 341 | |
| 191 | inline fn processStripe(self: *XxHash32, buf: *const [16]u8) void { | 342 | fn round(acc: u32, lane: u32) u32 { |
| 192 | self.acc1 = round(self.acc1, mem.readIntLittle(u32, buf[0..4])); | | |
| 193 | self.acc2 = round(self.acc2, mem.readIntLittle(u32, buf[4..8])); | | |
| 194 | self.acc3 = round(self.acc3, mem.readIntLittle(u32, buf[8..12])); | | |
| 195 | self.acc4 = round(self.acc4, mem.readIntLittle(u32, buf[12..16])); | | |
| 196 | self.byte_count += 16; | | |
| 197 | } | | |
| 198 | | | |
| 199 | inline fn round(acc: u32, lane: u32) u32 { | | |
| 200 | const a = acc +% (lane *% prime_2); | 343 | const a = acc +% (lane *% prime_2); |
| 201 | const b = rotl(u32, a, 13); | 344 | const b = rotl(u32, a, 13); |
| 202 | return b *% prime_1; | 345 | return b *% prime_1; |
| 203 | } | 346 | } |
| 204 | | 347 | |
| 205 | pub fn final(self: *XxHash32) u32 { | 348 | pub fn final(self: *XxHash32) u32 { |
| 206 | var acc: u32 = undefined; | 349 | const unfinished = if (self.byte_count < 16) |
| | 350 | self.seed +% prime_5 |
| | 351 | else |
| | 352 | self.accumulator.merge(); |
| 207 | | 353 | |
| 208 | if (self.byte_count < 16) { | 354 | return finalize(unfinished, self.byte_count, self.buf[0..self.buf_len]); |
| 209 | acc = self.seed +% prime_5; | 355 | } |
| 210 | } else { | 356 | |
| 211 | acc = rotl(u32, self.acc1, 1) +% rotl(u32, self.acc2, 7) +% | 357 | fn finalize(unfinished: u32, byte_count: usize, partial: anytype) u32 { |
| 212 | rotl(u32, self.acc3, 12) +% rotl(u32, self.acc4, 18); | 358 | std.debug.assert(partial.len < 16); |
| | 359 | var acc = unfinished +% @as(u32, @intCast(byte_count)) +% @as(u32, @intCast(partial.len)); |
| | 360 | |
| | 361 | switch (partial.len) { |
| | 362 | inline 0, 1, 2, 3 => |count| { |
| | 363 | inline for (0..count) |i| acc = finalize1(acc, partial[i]); |
| | 364 | return avalanche(acc); |
| | 365 | }, |
| | 366 | inline 4, 5, 6, 7 => |count| { |
| | 367 | acc = finalize4(acc, partial[0..4]); |
| | 368 | inline for (4..count) |i| acc = finalize1(acc, partial[i]); |
| | 369 | return avalanche(acc); |
| | 370 | }, |
| | 371 | inline 8, 9, 10, 11 => |count| { |
| | 372 | acc = finalize4(acc, partial[0..4]); |
| | 373 | acc = finalize4(acc, partial[4..8]); |
| | 374 | inline for (8..count) |i| acc = finalize1(acc, partial[i]); |
| | 375 | return avalanche(acc); |
| | 376 | }, |
| | 377 | inline 12, 13, 14, 15 => |count| { |
| | 378 | acc = finalize4(acc, partial[0..4]); |
| | 379 | acc = finalize4(acc, partial[4..8]); |
| | 380 | acc = finalize4(acc, partial[8..12]); |
| | 381 | inline for (12..count) |i| acc = finalize1(acc, partial[i]); |
| | 382 | return avalanche(acc); |
| | 383 | }, |
| | 384 | else => unreachable, |
| 213 | } | 385 | } |
| 214 | | 386 | |
| 215 | acc = acc +% @as(u32, @intCast(self.byte_count)) +% @as(u32, @intCast(self.buf_len)); | 387 | return avalanche(acc); |
| | 388 | } |
| 216 | | 389 | |
| 217 | var pos: usize = 0; | 390 | fn finalize4(v: u32, bytes: *const [4]u8) u32 { |
| 218 | while (pos + 4 <= self.buf_len) : (pos += 4) { | 391 | var acc = v; |
| 219 | const lane = mem.readIntLittle(u32, self.buf[pos..][0..4]); | 392 | const lane = mem.readIntLittle(u32, bytes); |
| 220 | acc +%= lane *% prime_3; | 393 | acc +%= lane *% prime_3; |
| 221 | acc = rotl(u32, acc, 17) *% prime_4; | 394 | acc = rotl(u32, acc, 17) *% prime_4; |
| 222 | } | 395 | return acc; |
| | 396 | } |
| 223 | | 397 | |
| 224 | while (pos < self.buf_len) : (pos += 1) { | 398 | fn finalize1(v: u32, byte: u8) u32 { |
| 225 | const lane = @as(u32, self.buf[pos]); | 399 | var acc = v; |
| 226 | acc +%= lane *% prime_5; | 400 | const lane = @as(u32, byte); |
| 227 | acc = rotl(u32, acc, 11) *% prime_1; | 401 | acc +%= lane *% prime_5; |
| 228 | } | 402 | acc = rotl(u32, acc, 11) *% prime_1; |
| | 403 | return acc; |
| | 404 | } |
| 229 | | 405 | |
| 230 | acc ^= acc >> 15; | 406 | fn avalanche(value: u32) u32 { |
| | 407 | var acc = value ^ value >> 15; |
| 231 | acc *%= prime_2; | 408 | acc *%= prime_2; |
| 232 | acc ^= acc >> 13; | 409 | acc ^= acc >> 13; |
| 233 | acc *%= prime_3; | 410 | acc *%= prime_3; |
| ... | @@ -236,33 +413,58 @@ pub const XxHash32 = struct { | ... | @@ -236,33 +413,58 @@ pub const XxHash32 = struct { |
| 236 | return acc; | 413 | return acc; |
| 237 | } | 414 | } |
| 238 | | 415 | |
| 239 | pub fn hash(seed: u32, input: []const u8) u32 { | 416 | pub fn hash(seed: u32, input: anytype) u32 { |
| 240 | var hasher = XxHash32.init(seed); | 417 | validateType(@TypeOf(input)); |
| 241 | hasher.update(input); | 418 | |
| 242 | return hasher.final(); | 419 | if (input.len < 16) { |
| | 420 | return finalize(seed +% prime_5, 0, input); |
| | 421 | } else { |
| | 422 | var hasher = Accumulator.init(seed); |
| | 423 | const i = hasher.updateEmpty(input, 0); |
| | 424 | return finalize(hasher.merge(), i, input[i..]); |
| | 425 | } |
| 243 | } | 426 | } |
| 244 | }; | 427 | }; |
| 245 | | 428 | |
| | 429 | fn validateType(comptime T: type) void { |
| | 430 | comptime { |
| | 431 | if (!((std.meta.trait.isSlice(T) or |
| | 432 | std.meta.trait.is(.Array)(T) or |
| | 433 | std.meta.trait.isPtrTo(.Array)(T)) and |
| | 434 | std.meta.Elem(T) == u8)) |
| | 435 | { |
| | 436 | @compileError("expect a slice, array or pointer to array of u8, got " ++ @typeName(T)); |
| | 437 | } |
| | 438 | } |
| | 439 | } |
| | 440 | |
| | 441 | fn testExpect(comptime H: type, seed: anytype, input: []const u8, expected: u64) !void { |
| | 442 | try expectEqual(expected, H.hash(0, input)); |
| | 443 | |
| | 444 | var hasher = H.init(seed); |
| | 445 | hasher.update(input); |
| | 446 | try expectEqual(expected, hasher.final()); |
| | 447 | } |
| | 448 | |
| 246 | test "xxhash64" { | 449 | test "xxhash64" { |
| 247 | const hash = XxHash64.hash; | 450 | const H = XxHash64; |
| 248 | | 451 | try testExpect(H, 0, "", 0xef46db3751d8e999); |
| 249 | try expectEqual(hash(0, ""), 0xef46db3751d8e999); | 452 | try testExpect(H, 0, "a", 0xd24ec4f1a98c6e5b); |
| 250 | try expectEqual(hash(0, "a"), 0xd24ec4f1a98c6e5b); | 453 | try testExpect(H, 0, "abc", 0x44bc2cf5ad770999); |
| 251 | try expectEqual(hash(0, "abc"), 0x44bc2cf5ad770999); | 454 | try testExpect(H, 0, "message digest", 0x066ed728fceeb3be); |
| 252 | try expectEqual(hash(0, "message digest"), 0x066ed728fceeb3be); | 455 | try testExpect(H, 0, "abcdefghijklmnopqrstuvwxyz", 0xcfe1f278fa89835c); |
| 253 | try expectEqual(hash(0, "abcdefghijklmnopqrstuvwxyz"), 0xcfe1f278fa89835c); | 456 | try testExpect(H, 0, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 0xaaa46907d3047814); |
| 254 | try expectEqual(hash(0, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"), 0xaaa46907d3047814); | 457 | try testExpect(H, 0, "12345678901234567890123456789012345678901234567890123456789012345678901234567890", 0xe04a477f19ee145d); |
| 255 | try expectEqual(hash(0, "12345678901234567890123456789012345678901234567890123456789012345678901234567890"), 0xe04a477f19ee145d); | | |
| 256 | } | 458 | } |
| 257 | | 459 | |
| 258 | test "xxhash32" { | 460 | test "xxhash32" { |
| 259 | const hash = XxHash32.hash; | 461 | const H = XxHash32; |
| 260 | | 462 | |
| 261 | try expectEqual(hash(0, ""), 0x02cc5d05); | 463 | try testExpect(H, 0, "", 0x02cc5d05); |
| 262 | try expectEqual(hash(0, "a"), 0x550d7456); | 464 | try testExpect(H, 0, "a", 0x550d7456); |
| 263 | try expectEqual(hash(0, "abc"), 0x32d153ff); | 465 | try testExpect(H, 0, "abc", 0x32d153ff); |
| 264 | try expectEqual(hash(0, "message digest"), 0x7c948494); | 466 | try testExpect(H, 0, "message digest", 0x7c948494); |
| 265 | try expectEqual(hash(0, "abcdefghijklmnopqrstuvwxyz"), 0x63a14d5f); | 467 | try testExpect(H, 0, "abcdefghijklmnopqrstuvwxyz", 0x63a14d5f); |
| 266 | try expectEqual(hash(0, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"), 0x9c285e64); | 468 | try testExpect(H, 0, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 0x9c285e64); |
| 267 | try expectEqual(hash(0, "12345678901234567890123456789012345678901234567890123456789012345678901234567890"), 0x9c05f475); | 469 | try testExpect(H, 0, "12345678901234567890123456789012345678901234567890123456789012345678901234567890", 0x9c05f475); |
| 268 | } | 470 | } |