const Accumulator = struct {
consumed: usize = 0,
seed: u64,
secret: [192]u8,
state: Block = .{
XxHash32.prime_3,
XxHash64.prime_1,
XxHash64.prime_2,
XxHash64.prime_3,
XxHash64.prime_4,
XxHash32.prime_2,
XxHash64.prime_5,
XxHash32.prime_1,
},
inline fn init(seed: u64) Accumulator {
const seed_block: Block = .{
seed, @as(u64, 0) -% seed,
seed, @as(u64, 0) -% seed,
seed, @as(u64, 0) -% seed,
seed, @as(u64, 0) -% seed,
};
var secret: [192]u8 = undefined;
const secret_blocks: []InputBlock = @ptrCast(&secret);
const default_secret_blocks: []const InputBlock = @ptrCast(&default_secret);
for (secret_blocks, default_secret_blocks) |*dst, *src| {
dst.store(src.load() +% seed_block);
}
return .{ .seed = seed, .secret = secret };
}
inline fn round(
noalias state: *Block,
noalias input_block: *const InputBlock,
noalias secret_block: *const InputBlock,
) void {
const data = input_block.load();
const mixed = data ^ secret_block.load();
state.* +%= (mixed & @as(Block, @splat(0xffffffff))) *% (mixed >> @splat(32));
state.* +%= @shuffle(u64, data, undefined, [_]i32{ 1, 0, 3, 2, 5, 4, 7, 6 });
}
fn accumulate(noalias self: *Accumulator, blocks: []const InputBlock) void {
const secret = std.mem.bytesAsSlice(u64, self.secret[self.consumed * 8 ..]);
for (blocks, secret[0..blocks.len]) |*input_block, *secret_block| {
@prefetch(@as([*]const u8, @ptrCast(input_block)) + 320, .{});
round(&self.state, input_block, @ptrCast(secret_block));
}
}
fn scramble(self: *Accumulator) void {
const secret_block: Block = @bitCast(self.secret[192 - block_bytes .. 192].*);
self.state ^= self.state >> @splat(47);
self.state ^= secret_block;
self.state *%= @as(Block, @splat(XxHash32.prime_1));
}
fn consume(noalias self: *Accumulator, input_blocks: []const InputBlock) void {
const blocks_per_scramble = 1024 / block_bytes;
std.debug.assert(self.consumed <= blocks_per_scramble);
var blocks = input_blocks;
var blocks_until_scramble = blocks_per_scramble - self.consumed;
while (blocks.len >= blocks_until_scramble) {
self.accumulate(blocks[0..blocks_until_scramble]);
self.scramble();
self.consumed = 0;
blocks = blocks[blocks_until_scramble..];
blocks_until_scramble = blocks_per_scramble;
}
self.accumulate(blocks);
self.consumed += blocks.len;
}
fn digest(noalias self: *Accumulator, total_len: u64, noalias last_block: *const InputBlock) u64 {
const secret_block = self.secret[192 - block_bytes - 7 ..][0..block_bytes];
round(&self.state, last_block, @ptrCast(secret_block));
const merge_block: Block = @bitCast(self.secret[11 .. 11 + block_bytes].*);
self.state ^= merge_block;
var result = XxHash64.prime_1 *% total_len;
inline for (0..4) |i| {
result +%= fold(self.state[i * 2], self.state[i * 2 + 1]);
}
return avalanche(.h3, result);
}
}