rainbow-dragon/Sources/ForgeOffload/XXHash64.swift

127 lines
3.7 KiB
Swift
Raw Permalink Normal View History

import Foundation
/// xxHash64 (seed 0) pure Swift, streaming. Spec: github.com/Cyan4973/xxHash.
public struct XXHash64 {
private static let p1: UInt64 = 0x9E3779B185EBCA87
private static let p2: UInt64 = 0xC2B2AE3D27D4EB4F
private static let p3: UInt64 = 0x165667B19E3779F9
private static let p4: UInt64 = 0x85EBCA77C2B2AE63
private static let p5: UInt64 = 0x27D4EB2F165667C5
private var v1: UInt64
private var v2: UInt64
private var v3: UInt64
private var v4: UInt64
private var buffer = [UInt8]()
private var totalLength: UInt64 = 0
private let seed: UInt64
public init(seed: UInt64 = 0) {
self.seed = seed
v1 = seed &+ Self.p1 &+ Self.p2
v2 = seed &+ Self.p2
v3 = seed
v4 = seed &- Self.p1
}
@inline(__always)
private static func rotl(_ x: UInt64, _ r: UInt64) -> UInt64 {
(x << r) | (x >> (64 - r))
}
@inline(__always)
private static func round(_ acc: UInt64, _ input: UInt64) -> UInt64 {
rotl(acc &+ input &* p2, 31) &* p1
}
@inline(__always)
private static func mergeRound(_ acc: UInt64, _ val: UInt64) -> UInt64 {
(acc ^ round(0, val)) &* p1 &+ p4
}
@inline(__always)
private static func readLE64(_ bytes: [UInt8], _ offset: Int) -> UInt64 {
var v: UInt64 = 0
for i in 0..<8 {
v |= UInt64(bytes[offset + i]) << (8 * UInt64(i))
}
return v
}
@inline(__always)
private static func readLE32(_ bytes: [UInt8], _ offset: Int) -> UInt64 {
var v: UInt64 = 0
for i in 0..<4 {
v |= UInt64(bytes[offset + i]) << (8 * UInt64(i))
}
return v
}
public mutating func update(_ data: Data) {
totalLength += UInt64(data.count)
buffer.append(contentsOf: data)
// Consume full 32-byte stripes, keep remainder buffered.
var offset = 0
while buffer.count - offset >= 32 {
v1 = Self.round(v1, Self.readLE64(buffer, offset))
v2 = Self.round(v2, Self.readLE64(buffer, offset + 8))
v3 = Self.round(v3, Self.readLE64(buffer, offset + 16))
v4 = Self.round(v4, Self.readLE64(buffer, offset + 24))
offset += 32
}
if offset > 0 {
buffer.removeFirst(offset)
}
}
public func finalize() -> UInt64 {
var h: UInt64
if totalLength >= 32 {
h = Self.rotl(v1, 1) &+ Self.rotl(v2, 7) &+ Self.rotl(v3, 12) &+ Self.rotl(v4, 18)
h = Self.mergeRound(h, v1)
h = Self.mergeRound(h, v2)
h = Self.mergeRound(h, v3)
h = Self.mergeRound(h, v4)
} else {
h = seed &+ Self.p5
}
h &+= totalLength
var offset = 0
let tail = buffer
while tail.count - offset >= 8 {
h ^= Self.round(0, Self.readLE64(tail, offset))
h = Self.rotl(h, 27) &* Self.p1 &+ Self.p4
offset += 8
}
if tail.count - offset >= 4 {
h ^= Self.readLE32(tail, offset) &* Self.p1
h = Self.rotl(h, 23) &* Self.p2 &+ Self.p3
offset += 4
}
while offset < tail.count {
h ^= UInt64(tail[offset]) &* Self.p5
h = Self.rotl(h, 11) &* Self.p1
offset += 1
}
h ^= h >> 33
h &*= Self.p2
h ^= h >> 29
h &*= Self.p3
h ^= h >> 32
return h
}
/// One-shot.
public static func hash(_ data: Data, seed: UInt64 = 0) -> UInt64 {
var h = XXHash64(seed: seed)
h.update(data)
return h.finalize()
}
public static func hexString(_ value: UInt64) -> String {
String(format: "%016lx", value)
}
}