Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,15 @@
|
||||
## Description
|
||||
|
||||
`hash` provides a way to hash binary data, i.e. produce a shorter value,
|
||||
that is highly content dependent, so even slightly different content will
|
||||
produce widely different hashes.
|
||||
|
||||
Hash functions are useful for implementing maps, caches etc.
|
||||
|
||||
## Available submodules
|
||||
|
||||
- `hash.adler32` - Adler-32 (RFC 1950 checksum used by zlib)
|
||||
- `hash.crc32` - CRC-32 variants (IEEE 802.3 `crc32`, Castagnoli `crc32c`, Koopman `crc32k`,
|
||||
CRC-32Q `crc32q`)
|
||||
- `hash.crc64` - CRC-64-ECMA-182
|
||||
- `hash.fnv1a` - Fowler-Noll-Vo hashes
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
// This is a pure V implementation of Adler-32 from RFC 1950.
|
||||
// Parameters: BASE=65521, init=1, output=(s2 << 16) | s1.
|
||||
module adler32
|
||||
|
||||
// base is the largest prime smaller than 2^16, mandated by RFC 1950.
|
||||
pub const base = u32(65521)
|
||||
// nmax is the largest chunk size that keeps intermediate sums inside u32.
|
||||
// See RFC 1950 Appendix and zlib's adler32 implementation.
|
||||
pub const nmax = 5552
|
||||
|
||||
// update_state updates an Adler-32 state with `data`.
|
||||
// For RFC-1950 compliant checksums, use state `1` for a new stream.
|
||||
@[direct_array_access]
|
||||
pub fn update_state(state u32, data []u8) u32 {
|
||||
mut s1 := state & u32(0xffff)
|
||||
mut s2 := state >> 16
|
||||
mut pos := 0
|
||||
for pos < data.len {
|
||||
block_len := if data.len - pos > nmax { nmax } else { data.len - pos }
|
||||
for _ in 0 .. block_len {
|
||||
s1 += data[pos]
|
||||
if s1 >= base {
|
||||
s1 -= base
|
||||
}
|
||||
s2 += s1
|
||||
pos++
|
||||
}
|
||||
s2 %= base
|
||||
}
|
||||
return (s2 << 16) | s1
|
||||
}
|
||||
|
||||
// checksum returns the RFC-1950 Adler-32 checksum for `data`.
|
||||
pub fn checksum(data []u8) u32 {
|
||||
return update_state(u32(1), data)
|
||||
}
|
||||
|
||||
// update extends an existing Adler-32 checksum `adler` with `data`.
|
||||
// Use `adler = 1` for a fresh checksum.
|
||||
pub fn update(adler u32, data []u8) u32 {
|
||||
return update_state(adler, data)
|
||||
}
|
||||
|
||||
// sum is an alias for checksum.
|
||||
pub fn sum(data []u8) u32 {
|
||||
return checksum(data)
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import hash.adler32
|
||||
|
||||
fn test_adler32_rfc_vectors() {
|
||||
assert adler32.sum([]u8{}) == u32(0x00000001)
|
||||
assert adler32.sum('123456789'.bytes()) == u32(0x091e01de)
|
||||
assert adler32.sum('Wikipedia'.bytes()) == u32(0x11e60398)
|
||||
}
|
||||
|
||||
fn test_adler32_basic_aliases() {
|
||||
data := 'Hello world!'.bytes()
|
||||
expected := u32(0x1d09045e)
|
||||
assert adler32.checksum(data) == expected
|
||||
assert adler32.sum(data) == expected
|
||||
}
|
||||
|
||||
fn test_adler32_update_matches_full_sum() {
|
||||
data := 'streaming adler32 data'.bytes()
|
||||
part1 := data[..8]
|
||||
part2 := data[8..14]
|
||||
part3 := data[14..]
|
||||
|
||||
mut state := u32(1)
|
||||
state = adler32.update(state, part1)
|
||||
state = adler32.update(state, part2)
|
||||
state = adler32.update(state, part3)
|
||||
|
||||
assert state == adler32.sum(data)
|
||||
}
|
||||
|
||||
fn test_adler32_update_state_matches_update() {
|
||||
data := ('chunked data '.repeat(80)).bytes()
|
||||
mut via_state := u32(1)
|
||||
mut via_update := u32(1)
|
||||
for chunk_size in [1, 2, 3, 5, 8, 16, 64, 257] {
|
||||
mut state_a := u32(1)
|
||||
mut state_b := u32(1)
|
||||
mut i := 0
|
||||
for i < data.len {
|
||||
end := if i + chunk_size < data.len { i + chunk_size } else { data.len }
|
||||
chunk := data[i..end]
|
||||
state_a = adler32.update_state(state_a, chunk)
|
||||
state_b = adler32.update(state_b, chunk)
|
||||
i = end
|
||||
}
|
||||
assert state_a == adler32.sum(data)
|
||||
assert state_b == adler32.sum(data)
|
||||
via_state = state_a
|
||||
via_update = state_b
|
||||
}
|
||||
assert via_state == via_update
|
||||
}
|
||||
|
||||
fn test_adler32_all_bytes() {
|
||||
mut all_bytes := []u8{len: 256}
|
||||
for i in 0 .. 256 {
|
||||
all_bytes[i] = u8(i)
|
||||
}
|
||||
assert adler32.sum(all_bytes) == u32(0xadf67f81)
|
||||
}
|
||||
|
||||
fn test_adler32_large_input() {
|
||||
data := 'a'.repeat(7000).bytes()
|
||||
assert adler32.sum(data) == u32(0x1a305cef)
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
// This is a fairly basic crc32 implementation, with 4 variants of the crc32 algorithm, and a way
|
||||
// to create custom crc32 tables from user-provided polynomials.
|
||||
module crc32
|
||||
|
||||
// polynomials
|
||||
pub const ieee = u32(0xedb88320)
|
||||
pub const castagnoli = u32(0x82f63b78)
|
||||
pub const koopman = u32(0xeb31d82e)
|
||||
// q is the standard CRC-32Q polynomial (MSB-first).
|
||||
pub const q = u32(0x814141ab)
|
||||
// q_reflected is the reflected (LSB-first) form of CRC-32Q polynomial.
|
||||
pub const q_reflected = u32(0xd5828281)
|
||||
|
||||
// Named aliases for common CRC-32 variants.
|
||||
pub const crc32c = castagnoli
|
||||
pub const crc32k = koopman
|
||||
pub const crc32q = q
|
||||
pub const crc32q_reflected = q_reflected
|
||||
|
||||
struct Crc32 {
|
||||
mut:
|
||||
table []u32
|
||||
}
|
||||
|
||||
// generate_table populates a 256-word table from the specified polynomial `poly`
|
||||
// to represent the polynomial for efficient processing.
|
||||
@[direct_array_access]
|
||||
fn (mut c Crc32) generate_table(poly u32) {
|
||||
c.table = []u32{len: 256}
|
||||
for i in 0 .. 256 {
|
||||
mut crc := u32(i)
|
||||
for _ in 0 .. 8 {
|
||||
if crc & u32(1) == u32(1) {
|
||||
crc = (crc >> 1) ^ poly
|
||||
} else {
|
||||
crc >>= u32(1)
|
||||
}
|
||||
}
|
||||
c.table[i] = crc
|
||||
}
|
||||
}
|
||||
|
||||
@[direct_array_access]
|
||||
fn (c &Crc32) update32(crc u32, b []u8) u32 {
|
||||
mut next := crc
|
||||
for i in 0 .. b.len {
|
||||
next = c.table[u8(next) ^ b[i]] ^ (next >> 8)
|
||||
}
|
||||
return next
|
||||
}
|
||||
|
||||
// update_state updates an internal CRC state with the bytes in `b`.
|
||||
// Start from `~u32(0)` and finalize with `~state`.
|
||||
pub fn (c &Crc32) update_state(state u32, b []u8) u32 {
|
||||
return c.update32(state, b)
|
||||
}
|
||||
|
||||
// checksum returns the CRC-32 checksum of data `b` by using the polynomial represented by `c`'s table.
|
||||
pub fn (c &Crc32) checksum(b []u8) u32 {
|
||||
return ~c.update_state(~u32(0), b)
|
||||
}
|
||||
|
||||
// update returns the updated CRC-32 checksum for `b`, starting from `crc`.
|
||||
// Use `crc = 0` for a fresh checksum, or pass a previous result to continue streaming.
|
||||
pub fn (c &Crc32) update(crc u32, b []u8) u32 {
|
||||
state := c.update_state(~crc, b)
|
||||
return ~state
|
||||
}
|
||||
|
||||
// new creates a `Crc32` polynomial.
|
||||
pub fn new(poly u32) &Crc32 {
|
||||
mut c := &Crc32{}
|
||||
c.generate_table(poly)
|
||||
return c
|
||||
}
|
||||
|
||||
// sum_with_poly calculates the CRC-32 checksum of `b` for the provided polynomial.
|
||||
// Built-in constants use their canonical parameter sets.
|
||||
pub fn sum_with_poly(poly u32, b []u8) u32 {
|
||||
return match poly {
|
||||
ieee { ieee_poly.checksum(b) }
|
||||
crc32c { crc32c_poly.checksum(b) }
|
||||
crc32k { crc32k_poly.checksum(b) }
|
||||
crc32q { crc32q_sum_internal(b) }
|
||||
crc32q_reflected { crc32q_reflected_poly.checksum(b) }
|
||||
else { new(poly).checksum(b) }
|
||||
}
|
||||
}
|
||||
|
||||
const ieee_poly = new(ieee)
|
||||
const crc32c_poly = new(crc32c)
|
||||
const crc32k_poly = new(crc32k)
|
||||
const crc32q_reflected_poly = new(crc32q_reflected)
|
||||
const crc32q_table = crc32q_generate_table(q)
|
||||
|
||||
@[direct_array_access]
|
||||
fn crc32q_generate_table(poly u32) []u32 {
|
||||
mut table := []u32{len: 256}
|
||||
for i in 0 .. 256 {
|
||||
mut crc := u32(i) << 24
|
||||
for _ in 0 .. 8 {
|
||||
if crc & u32(0x80000000) != 0 {
|
||||
crc = (crc << 1) ^ poly
|
||||
} else {
|
||||
crc <<= 1
|
||||
}
|
||||
}
|
||||
table[i] = crc
|
||||
}
|
||||
return table
|
||||
}
|
||||
|
||||
@[direct_array_access]
|
||||
fn crc32q_sum_internal(b []u8) u32 {
|
||||
mut crc := u32(0)
|
||||
for byte in b {
|
||||
idx := u8((crc >> 24) ^ byte)
|
||||
crc = crc32q_table[idx] ^ (crc << 8)
|
||||
}
|
||||
return crc
|
||||
}
|
||||
|
||||
// sum calculates the CRC-32 checksum of `b` by using the IEEE polynomial.
|
||||
pub fn sum(b []u8) u32 {
|
||||
return ieee_poly.checksum(b)
|
||||
}
|
||||
|
||||
// sum_crc32c calculates the CRC-32C checksum of `b`.
|
||||
pub fn sum_crc32c(b []u8) u32 {
|
||||
return crc32c_poly.checksum(b)
|
||||
}
|
||||
|
||||
// sum_crc32k calculates the CRC-32K checksum of `b`.
|
||||
pub fn sum_crc32k(b []u8) u32 {
|
||||
return crc32k_poly.checksum(b)
|
||||
}
|
||||
|
||||
// sum_crc32q calculates the CRC-32Q checksum of `b`.
|
||||
pub fn sum_crc32q(b []u8) u32 {
|
||||
return crc32q_sum_internal(b)
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
import hash.crc32
|
||||
|
||||
const reflected_test_polys = [crc32.ieee, crc32.crc32c, crc32.crc32k, crc32.crc32q_reflected]
|
||||
|
||||
fn sum_for_reflected_poly(poly u32, data []u8) u32 {
|
||||
return match poly {
|
||||
crc32.ieee { crc32.sum(data) }
|
||||
crc32.crc32c { crc32.sum_crc32c(data) }
|
||||
crc32.crc32k { crc32.sum_crc32k(data) }
|
||||
crc32.crc32q_reflected { crc32.sum_with_poly(crc32.crc32q_reflected, data) }
|
||||
else { panic('unexpected polynomial in test') }
|
||||
}
|
||||
}
|
||||
|
||||
fn expected_reflected_crc_123456789(poly u32) u32 {
|
||||
return match poly {
|
||||
crc32.ieee { u32(0xcbf43926) }
|
||||
crc32.crc32c { u32(0xe3069283) }
|
||||
crc32.crc32k { u32(0x2d3dd0ae) }
|
||||
crc32.crc32q_reflected { u32(0xa9cc8179) }
|
||||
else { panic('unexpected polynomial in test') }
|
||||
}
|
||||
}
|
||||
|
||||
fn expected_reflected_crc_a(poly u32) u32 {
|
||||
return match poly {
|
||||
crc32.ieee { u32(0xe8b7be43) }
|
||||
crc32.crc32c { u32(0xc1d04330) }
|
||||
crc32.crc32k { u32(0x0da2aa8a) }
|
||||
crc32.crc32q_reflected { u32(0x248ca0a3) }
|
||||
else { panic('unexpected polynomial in test') }
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_reflected_poly_paths_match(poly u32, data []u8) {
|
||||
c := crc32.new(poly)
|
||||
by_new := c.checksum(data)
|
||||
assert by_new == crc32.sum_with_poly(poly, data)
|
||||
assert by_new == sum_for_reflected_poly(poly, data)
|
||||
}
|
||||
|
||||
fn test_hash_crc32() {
|
||||
b1 := 'testing crc32'.bytes()
|
||||
sum1 := crc32.sum(b1)
|
||||
assert sum1 == u32(1212124400)
|
||||
assert sum1.hex() == '483f8cf0'
|
||||
|
||||
c := crc32.new(crc32.ieee)
|
||||
b2 := 'testing crc32 again'.bytes()
|
||||
sum2 := c.checksum(b2)
|
||||
assert sum2 == u32(1420327025)
|
||||
assert sum2.hex() == '54a87871'
|
||||
}
|
||||
|
||||
fn test_hash_crc32_variants() {
|
||||
data := '123456789'.bytes()
|
||||
for poly in reflected_test_polys {
|
||||
expected := expected_reflected_crc_123456789(poly)
|
||||
assert sum_for_reflected_poly(poly, data) == expected
|
||||
assert_reflected_poly_paths_match(poly, data)
|
||||
}
|
||||
}
|
||||
|
||||
fn test_hash_crc32q_standard() {
|
||||
data := '123456789'.bytes()
|
||||
assert crc32.sum_crc32q(data) == u32(0x3010bf7f)
|
||||
assert crc32.sum_with_poly(crc32.crc32q, data) == u32(0x3010bf7f)
|
||||
assert crc32.sum_crc32q('a'.bytes()) == u32(0xd1112b6b)
|
||||
}
|
||||
|
||||
fn test_hash_crc32_update() {
|
||||
data := '123456789'.bytes()
|
||||
part1 := data[..4]
|
||||
part2 := data[4..]
|
||||
|
||||
c := crc32.new(crc32.ieee)
|
||||
mut acc := u32(0)
|
||||
acc = c.update(acc, part1)
|
||||
acc = c.update(acc, part2)
|
||||
|
||||
assert acc == c.checksum(data)
|
||||
assert acc.hex() == 'cbf43926'
|
||||
}
|
||||
|
||||
fn test_hash_crc32_edge_cases() {
|
||||
empty := ''.bytes()
|
||||
one := 'a'.bytes()
|
||||
for poly in reflected_test_polys {
|
||||
assert sum_for_reflected_poly(poly, empty) == u32(0)
|
||||
assert sum_for_reflected_poly(poly, one) == expected_reflected_crc_a(poly)
|
||||
}
|
||||
assert crc32.sum_crc32q(empty) == u32(0)
|
||||
}
|
||||
|
||||
fn test_hash_crc32_sum_with_poly() {
|
||||
data := 'variant helper'.bytes()
|
||||
for poly in reflected_test_polys {
|
||||
assert_reflected_poly_paths_match(poly, data)
|
||||
}
|
||||
assert crc32.sum_with_poly(crc32.crc32q, data) == crc32.sum_crc32q(data)
|
||||
}
|
||||
|
||||
fn test_hash_crc32_sum_with_poly_custom() {
|
||||
data := 'custom poly checksum'.bytes()
|
||||
poly := u32(0xa833982b)
|
||||
|
||||
assert crc32.sum_with_poly(poly, data) == crc32.new(poly).checksum(data)
|
||||
}
|
||||
|
||||
fn test_hash_crc32_all_polys_consistent() {
|
||||
data := 'all polys consistent'.bytes()
|
||||
part1 := data[..7]
|
||||
part2 := data[7..]
|
||||
|
||||
for poly in reflected_test_polys {
|
||||
c := crc32.new(poly)
|
||||
full := c.checksum(data)
|
||||
|
||||
mut split := u32(0)
|
||||
split = c.update(split, part1)
|
||||
split = c.update(split, part2)
|
||||
|
||||
assert full == split
|
||||
assert full == sum_for_reflected_poly(poly, data)
|
||||
}
|
||||
}
|
||||
|
||||
fn test_hash_crc32_streaming_chunk_sizes() {
|
||||
data := ('streaming data block '.repeat(64)).bytes()
|
||||
for poly in reflected_test_polys {
|
||||
c := crc32.new(poly)
|
||||
expected := c.checksum(data)
|
||||
for chunk_size in [1, 2, 3, 5, 7, 16, 31, 64, 128] {
|
||||
mut state := ~u32(0)
|
||||
mut start := 0
|
||||
for start < data.len {
|
||||
end := if start + chunk_size < data.len { start + chunk_size } else { data.len }
|
||||
state = c.update_state(state, data[start..end])
|
||||
start = end
|
||||
}
|
||||
assert ~state == expected
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_hash_crc32_update_state() {
|
||||
data := 'stateful streaming'.bytes()
|
||||
part1 := data[..5]
|
||||
part2 := data[5..]
|
||||
c := crc32.new(crc32.crc32c)
|
||||
|
||||
mut state := ~u32(0)
|
||||
state = c.update_state(state, part1)
|
||||
state = c.update_state(state, part2)
|
||||
|
||||
assert ~state == c.checksum(data)
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
// This is a pure V implementation of CRC64, providing one standard variant:
|
||||
// CRC-64-ECMA-182 (poly 0x42F0E1EBA9EA3693, init 0, refin false, refout false, xorout 0).
|
||||
module crc64
|
||||
|
||||
// Polynomial constants for CRC-64
|
||||
pub const ecma = u64(0x42F0E1EBA9EA3693)
|
||||
|
||||
struct Crc64 {
|
||||
mut:
|
||||
table []u64
|
||||
}
|
||||
|
||||
// generate_table populates a 256-word MSB-first lookup table for `poly`.
|
||||
@[direct_array_access]
|
||||
fn (mut c Crc64) generate_table(poly u64) {
|
||||
c.table = []u64{len: 256}
|
||||
for i in 0 .. 256 {
|
||||
mut crc := u64(i) << 56
|
||||
for _ in 0 .. 8 {
|
||||
if crc & (u64(1) << 63) != 0 {
|
||||
crc = (crc << 1) ^ poly
|
||||
} else {
|
||||
crc <<= 1
|
||||
}
|
||||
}
|
||||
c.table[i] = crc
|
||||
}
|
||||
}
|
||||
|
||||
@[direct_array_access]
|
||||
fn (c &Crc64) update64(crc u64, b []u8) u64 {
|
||||
mut next := crc
|
||||
for i in 0 .. b.len {
|
||||
next = c.table[u8(next >> 56) ^ b[i]] ^ (next << 8)
|
||||
}
|
||||
return next
|
||||
}
|
||||
|
||||
// update_state updates an internal CRC state with the bytes in `b`.
|
||||
// For CRC-64-ECMA-182, use state 0 for a new stream.
|
||||
pub fn (c &Crc64) update_state(state u64, b []u8) u64 {
|
||||
return c.update64(state, b)
|
||||
}
|
||||
|
||||
// checksum returns the CRC-64 checksum of data `b` by using the polynomial represented by `c`'s table.
|
||||
pub fn (c &Crc64) checksum(b []u8) u64 {
|
||||
return c.update_state(u64(0), b)
|
||||
}
|
||||
|
||||
// update returns the updated CRC-64 checksum for `b`, starting from `crc`.
|
||||
// Use `crc = 0` for a fresh checksum, or pass a previous result to continue streaming.
|
||||
pub fn (c &Crc64) update(crc u64, b []u8) u64 {
|
||||
return c.update_state(crc, b)
|
||||
}
|
||||
|
||||
// new creates a `Crc64` polynomial.
|
||||
pub fn new(poly u64) &Crc64 {
|
||||
mut c := &Crc64{}
|
||||
c.generate_table(poly)
|
||||
return c
|
||||
}
|
||||
|
||||
// sum_with_poly calculates the CRC-64 checksum of `b` for the provided polynomial.
|
||||
pub fn sum_with_poly(poly u64, b []u8) u64 {
|
||||
return match poly {
|
||||
ecma { ecma_poly.checksum(b) }
|
||||
else { new(poly).checksum(b) }
|
||||
}
|
||||
}
|
||||
|
||||
const ecma_poly = new(ecma)
|
||||
|
||||
// sum calculates the CRC-64 checksum of `b` by using the ECMA polynomial.
|
||||
pub fn sum(b []u8) u64 {
|
||||
return ecma_poly.checksum(b)
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
import hash.crc64
|
||||
|
||||
fn test_crc64_basic() {
|
||||
b1 := 'testing crc64'.bytes()
|
||||
sum1 := crc64.sum(b1)
|
||||
// Verify it's not zero for non-empty input
|
||||
assert sum1 != u64(0)
|
||||
|
||||
c := crc64.new(crc64.ecma)
|
||||
b2 := 'testing crc64 again'.bytes()
|
||||
sum2 := c.checksum(b2)
|
||||
// Different input should yield different checksum (with high probability)
|
||||
assert sum2 != sum1
|
||||
}
|
||||
|
||||
fn test_crc64_empty() {
|
||||
empty := ''.bytes()
|
||||
assert crc64.sum(empty) == u64(0)
|
||||
|
||||
c := crc64.new(crc64.ecma)
|
||||
assert c.checksum(empty) == u64(0)
|
||||
}
|
||||
|
||||
fn test_crc64_single_byte() {
|
||||
a := 'a'.bytes()
|
||||
c := crc64.new(crc64.ecma)
|
||||
sum_a := c.checksum(a)
|
||||
assert sum_a != u64(0)
|
||||
|
||||
b := 'b'.bytes()
|
||||
sum_b := c.checksum(b)
|
||||
assert sum_b != sum_a
|
||||
}
|
||||
|
||||
fn test_crc64_roundtrip_known_vector() {
|
||||
// Standard test vector: "123456789"
|
||||
data := '123456789'.bytes()
|
||||
c := crc64.new(crc64.ecma)
|
||||
result := c.checksum(data)
|
||||
// CRC-64-ECMA-182 check value
|
||||
assert result == u64(0x6c40df5f0b497347)
|
||||
|
||||
// Verify consistency
|
||||
assert crc64.sum(data) == result
|
||||
assert crc64.sum_with_poly(crc64.ecma, data) == result
|
||||
}
|
||||
|
||||
fn test_crc64_binary_input() {
|
||||
// Test with all byte values
|
||||
data := [u8(0), 1, 2, 255, 128, 42, 0x00, 0xff]
|
||||
c := crc64.new(crc64.ecma)
|
||||
result := c.checksum(data)
|
||||
assert result != u64(0)
|
||||
}
|
||||
|
||||
fn test_crc64_update() {
|
||||
data := '123456789'.bytes()
|
||||
part1 := data[..4]
|
||||
part2 := data[4..]
|
||||
|
||||
c := crc64.new(crc64.ecma)
|
||||
mut acc := u64(0)
|
||||
acc = c.update(acc, part1)
|
||||
acc = c.update(acc, part2)
|
||||
|
||||
assert acc == c.checksum(data)
|
||||
}
|
||||
|
||||
fn test_crc64_streaming_chunk_sizes() {
|
||||
data := ('streaming data block '.repeat(64)).bytes()
|
||||
c := crc64.new(crc64.ecma)
|
||||
expected := c.checksum(data)
|
||||
|
||||
for chunk_size in [1, 2, 3, 5, 7, 16, 31, 64, 128] {
|
||||
mut state := u64(0)
|
||||
mut start := 0
|
||||
for start < data.len {
|
||||
end := if start + chunk_size < data.len { start + chunk_size } else { data.len }
|
||||
state = c.update_state(state, data[start..end])
|
||||
start = end
|
||||
}
|
||||
assert state == expected
|
||||
}
|
||||
}
|
||||
|
||||
fn test_crc64_update_state() {
|
||||
data := 'stateful streaming'.bytes()
|
||||
part1 := data[..5]
|
||||
part2 := data[5..]
|
||||
c := crc64.new(crc64.ecma)
|
||||
|
||||
mut state := u64(0)
|
||||
state = c.update_state(state, part1)
|
||||
state = c.update_state(state, part2)
|
||||
|
||||
assert state == c.checksum(data)
|
||||
}
|
||||
|
||||
fn test_crc64_sum_with_poly() {
|
||||
data := 'variant helper'.bytes()
|
||||
c := crc64.new(crc64.ecma)
|
||||
assert c.checksum(data) == crc64.sum_with_poly(crc64.ecma, data)
|
||||
assert crc64.sum(data) == crc64.sum_with_poly(crc64.ecma, data)
|
||||
}
|
||||
|
||||
fn test_crc64_sum_with_poly_custom() {
|
||||
data := 'custom poly checksum'.bytes()
|
||||
// Use a different polynomial to verify custom path
|
||||
poly := u64(0xa6fd4db2ef0b0da9)
|
||||
|
||||
assert crc64.sum_with_poly(poly, data) == crc64.new(poly).checksum(data)
|
||||
}
|
||||
|
||||
fn test_crc64_large_input() {
|
||||
// Test with large repetitive data
|
||||
large_data := ('large repetitive input '.repeat(1000)).bytes()
|
||||
c := crc64.new(crc64.ecma)
|
||||
result := c.checksum(large_data)
|
||||
assert result != u64(0)
|
||||
|
||||
// Verify consistency with streaming
|
||||
mut state := u64(0)
|
||||
for i := 0; i < large_data.len; i += 512 {
|
||||
end := if i + 512 < large_data.len { i + 512 } else { large_data.len }
|
||||
state = c.update_state(state, large_data[i..end])
|
||||
}
|
||||
assert state == result
|
||||
}
|
||||
|
||||
fn test_crc64_all_bytes() {
|
||||
// Create data with all possible byte values
|
||||
mut all_bytes := []u8{}
|
||||
for b in 0 .. 256 {
|
||||
all_bytes << u8(b)
|
||||
}
|
||||
c := crc64.new(crc64.ecma)
|
||||
result := c.checksum(all_bytes)
|
||||
assert result != u64(0)
|
||||
|
||||
// Verify it's consistent
|
||||
assert crc64.sum(all_bytes) == result
|
||||
}
|
||||
|
||||
fn test_crc64_deterministic() {
|
||||
data := 'deterministic test'.bytes()
|
||||
c := crc64.new(crc64.ecma)
|
||||
|
||||
result1 := c.checksum(data)
|
||||
result2 := c.checksum(data)
|
||||
result3 := crc64.sum(data)
|
||||
|
||||
assert result1 == result2
|
||||
assert result1 == result3
|
||||
}
|
||||
|
||||
fn test_crc64_prefix_sensitivity() {
|
||||
// Verify that different prefixes produce different results
|
||||
base := 'test'.bytes()
|
||||
c := crc64.new(crc64.ecma)
|
||||
|
||||
sum_base := c.checksum(base)
|
||||
sum_t := c.checksum('test_extended'.bytes())
|
||||
sum_prefix := c.checksum('prefix_test'.bytes())
|
||||
|
||||
assert sum_base != sum_t
|
||||
assert sum_base != sum_prefix
|
||||
assert sum_t != sum_prefix
|
||||
}
|
||||
|
||||
fn test_crc64_consistency_across_polys() {
|
||||
// If only one poly is defined, at least verify the path works
|
||||
data := 'poly path test'.bytes()
|
||||
direct := crc64.sum(data)
|
||||
via_poly := crc64.sum_with_poly(crc64.ecma, data)
|
||||
via_new := crc64.new(crc64.ecma).checksum(data)
|
||||
|
||||
assert direct == via_poly
|
||||
assert direct == via_new
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
# CRC64 Interoperability Fixture
|
||||
|
||||
This directory contains interoperability tests for the V `hash.crc64` module, cross-validating
|
||||
against reference implementations in C and Python.
|
||||
|
||||
## Overview
|
||||
|
||||
The fixture verifies that `hash.crc64` produces identical CRC-64-ECMA-182 checksums when:
|
||||
- Computing the same data in V, C, and Python
|
||||
- Operating on various input patterns (empty, binary, text, large payloads)
|
||||
|
||||
This helps catch:
|
||||
- Bit-ordering errors
|
||||
- Polynomial table generation bugs
|
||||
- Byte-order issues
|
||||
- Implementation divergences
|
||||
|
||||
## Files
|
||||
|
||||
- `crc64_interop.vsh` - Main V interop checker script
|
||||
- `crc64_ref.c` - C reference implementation (auto-compiled)
|
||||
- `crc64_ref.py` - Python reference implementation
|
||||
|
||||
## Running the fixture
|
||||
|
||||
From the V repository root:
|
||||
|
||||
```bash
|
||||
v run vlib/hash/crc64/interop/crc64_interop.vsh
|
||||
```
|
||||
|
||||
## Test vectors
|
||||
|
||||
The fixture tests 10 vector categories:
|
||||
|
||||
1. **empty** - Zero-length input
|
||||
2. **single_a** - Single character 'a'
|
||||
3. **single_null** - Single null byte
|
||||
4. **text_123456789** - Standard test vector
|
||||
5. **text_hello_world** - Common greeting
|
||||
6. **all_zeros_16** - 16 zero bytes
|
||||
7. **all_ones_16** - 16 0xFF bytes
|
||||
8. **repeating_pattern** - 150 bytes of "abc" repeated
|
||||
9. **all_bytes** - All 256 byte values in sequence
|
||||
10. **large_payload** - 10000 bytes of "test data " repeated
|
||||
|
||||
Each vector is checksummed by:
|
||||
- V: `hash.crc64.sum(data)`
|
||||
- C: compiled from `crc64_ref.c`
|
||||
- Python: `crc64_ref.py`
|
||||
|
||||
## Polynomial
|
||||
|
||||
All implementations use CRC-64-ECMA-182:
|
||||
- Polynomial: `0x42F0E1EBA9EA3693`
|
||||
- Initial value: `0x0000000000000000`
|
||||
- Final XOR: `0x0000000000000000`
|
||||
- Check value for `"123456789"`: `0x6c40df5f0b497347`
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- V compiler (tested with current repo version)
|
||||
- GCC (to compile C reference)
|
||||
- Python 3 (for Python reference)
|
||||
|
||||
## Expected output
|
||||
|
||||
```
|
||||
Compiling C reference helper...
|
||||
Running cross-validation tests...
|
||||
OK: empty => 0x0000000000000000
|
||||
OK: single_a => 0x926a79e87a919f5d
|
||||
OK: single_null => 0xbd4a6ec89c7eaafb
|
||||
...
|
||||
=== Results ===
|
||||
Passed: 10
|
||||
Failed: 0
|
||||
Total: 10
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If the fixture fails:
|
||||
1. Ensure `gcc` and `python3` are in PATH
|
||||
2. Check that `hash.crc64` module can be imported (`v -silent test vlib/hash/crc64/`)
|
||||
3. Manual check: `python3 crc64_ref.py checksum 313233343536373839` (should match C)
|
||||
@@ -0,0 +1,129 @@
|
||||
#!/usr/bin/env -S ./vnew run
|
||||
|
||||
import os
|
||||
import hash.crc64
|
||||
|
||||
struct TestVector {
|
||||
name string
|
||||
data []u8
|
||||
}
|
||||
|
||||
const ecma_check_123456789 = u64(0x6c40df5f0b497347)
|
||||
|
||||
fn compile_c_helper(dir string) string {
|
||||
ref_c := os.join_path(dir, 'crc64_ref.c')
|
||||
ref_bin := os.join_path(dir, 'crc64_ref')
|
||||
|
||||
// Compile: gcc -std=c99 crc64_ref.c -o crc64_ref
|
||||
cmd := 'gcc -std=c99 "${ref_c}" -o "${ref_bin}"'
|
||||
result := os.execute(cmd)
|
||||
if result.exit_code != 0 {
|
||||
eprintln('Failed to compile C helper:')
|
||||
eprintln(result.output)
|
||||
exit(1)
|
||||
}
|
||||
|
||||
return ref_bin
|
||||
}
|
||||
|
||||
fn run_c_checksum(ref_bin string, data []u8) !u64 {
|
||||
hex_str := data.hex()
|
||||
cmd := '${ref_bin} checksum ${hex_str}'
|
||||
result := os.execute(cmd)
|
||||
if result.exit_code != 0 {
|
||||
return error('C helper failed: ${result.output}')
|
||||
}
|
||||
|
||||
result_str := result.output.trim_space()
|
||||
return u64(result_str.parse_uint(16, 64)!)
|
||||
}
|
||||
|
||||
fn run_python_checksum(dir string, data []u8) !u64 {
|
||||
ref_py := os.join_path(dir, 'crc64_ref.py')
|
||||
hex_str := data.hex()
|
||||
cmd := 'python3 "${ref_py}" checksum ${hex_str}'
|
||||
result := os.execute(cmd)
|
||||
if result.exit_code != 0 {
|
||||
return error('Python helper failed: ${result.output}')
|
||||
}
|
||||
|
||||
result_str := result.output.trim_space()
|
||||
return u64(result_str.parse_uint(16, 64)!)
|
||||
}
|
||||
|
||||
fn test_vector(name string, data []u8, ref_bin string, dir string) ! {
|
||||
// V checksum
|
||||
v_sum := crc64.sum(data)
|
||||
|
||||
// C checksum
|
||||
c_sum := run_c_checksum(ref_bin, data)!
|
||||
|
||||
// Python checksum
|
||||
py_sum := run_python_checksum(dir, data)!
|
||||
|
||||
// All must match
|
||||
if v_sum != c_sum || v_sum != py_sum {
|
||||
eprintln('FAIL: ${name}')
|
||||
eprintln(' V: 0x${v_sum:016x}')
|
||||
eprintln(' C: 0x${c_sum:016x}')
|
||||
eprintln(' Python: 0x${py_sum:016x}')
|
||||
return error('checksum mismatch')
|
||||
}
|
||||
|
||||
if name == 'text_123456789' && v_sum != ecma_check_123456789 {
|
||||
eprintln('FAIL: ${name}')
|
||||
eprintln(' V: 0x${v_sum:016x}')
|
||||
eprintln(' Expected: 0x${ecma_check_123456789:016x}')
|
||||
return error('unexpected CRC-64-ECMA-182 check value')
|
||||
}
|
||||
|
||||
println('OK: ${name} => 0x${v_sum:016x}')
|
||||
}
|
||||
|
||||
fn main() {
|
||||
dir := os.dir(@FILE)
|
||||
|
||||
println('Compiling C reference helper...')
|
||||
ref_bin := compile_c_helper(dir)
|
||||
defer { os.rm(ref_bin) or {} }
|
||||
|
||||
println('Running cross-validation tests...')
|
||||
mut passed := 0
|
||||
mut failed := 0
|
||||
|
||||
mut vectors := []TestVector{}
|
||||
vectors << TestVector{'empty', []u8{}}
|
||||
vectors << TestVector{'single_a', 'a'.bytes()}
|
||||
vectors << TestVector{'single_null', [u8(0)]}
|
||||
vectors << TestVector{'text_123456789', '123456789'.bytes()}
|
||||
vectors << TestVector{'text_hello_world', 'Hello, World!'.bytes()}
|
||||
vectors << TestVector{'all_zeros_16', []u8{len: 16}}
|
||||
vectors << TestVector{'all_ones_16', []u8{len: 16, init: 0xFF}}
|
||||
vectors << TestVector{'repeating_pattern', ('abc'.repeat(50)).bytes()}
|
||||
|
||||
mut all_bytes := []u8{len: 256}
|
||||
for i in 0 .. 256 {
|
||||
all_bytes[i] = u8(i)
|
||||
}
|
||||
vectors << TestVector{'all_bytes', all_bytes}
|
||||
vectors << TestVector{'large_payload', ('test data '.repeat(1000)).bytes()}
|
||||
|
||||
for vec in vectors {
|
||||
test_vector(vec.name, vec.data, ref_bin, dir) or {
|
||||
eprintln(' Error: ${err}')
|
||||
failed++
|
||||
continue
|
||||
}
|
||||
passed++
|
||||
}
|
||||
|
||||
println('')
|
||||
println('=== Results ===')
|
||||
println('Passed: ${passed}')
|
||||
println('Failed: ${failed}')
|
||||
println('Total: ${passed + failed}')
|
||||
|
||||
if failed > 0 {
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
// Standard CRC64-ECMA reference implementation in C
|
||||
// Compiles with: gcc -std=c99 crc64_ref.c -o crc64_ref
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define CRC64_ECMA 0x42F0E1EBA9EA3693ULL
|
||||
|
||||
static uint64_t crc64_table[256];
|
||||
|
||||
void crc64_init_table(void) {
|
||||
for (int i = 0; i < 256; i++) {
|
||||
uint64_t crc = (uint64_t)i << 56;
|
||||
for (int j = 0; j < 8; j++) {
|
||||
if (crc & 0x8000000000000000ULL) {
|
||||
crc = (crc << 1) ^ CRC64_ECMA;
|
||||
} else {
|
||||
crc <<= 1;
|
||||
}
|
||||
}
|
||||
crc64_table[i] = crc;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t crc64_checksum(const uint8_t *data, size_t len) {
|
||||
uint64_t crc = 0ULL;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
uint8_t byte = data[i];
|
||||
crc = crc64_table[(uint8_t)((crc >> 56) ^ byte)] ^ (crc << 8);
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
crc64_init_table();
|
||||
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "Usage: %s <action> [data...]\n", argv[0]);
|
||||
fprintf(stderr, " checksum <hexstring> - compute CRC64 of hex data\n");
|
||||
fprintf(stderr, " table - print first 16 table entries\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char *action = argv[1];
|
||||
|
||||
if (strcmp(action, "table") == 0) {
|
||||
printf("CRC64_ECMA table (first 16):\n");
|
||||
for (int i = 0; i < 16; i++) {
|
||||
printf(" [%d] = 0x%016llx\n", i, (unsigned long long)crc64_table[i]);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strcmp(action, "checksum") == 0) {
|
||||
const char *hexstr = (argc > 2) ? argv[2] : "";
|
||||
size_t hexlen = strlen(hexstr);
|
||||
|
||||
if (hexlen == 0) {
|
||||
// Empty input
|
||||
uint64_t crc = crc64_checksum(NULL, 0);
|
||||
printf("%016llx\n", (unsigned long long)crc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (hexlen % 2 != 0) {
|
||||
fprintf(stderr, "Error: hex string must have even length\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t datalen = hexlen / 2;
|
||||
uint8_t *data = malloc(datalen);
|
||||
if (!data) {
|
||||
fprintf(stderr, "Error: memory allocation failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < datalen; i++) {
|
||||
unsigned int byte;
|
||||
if (sscanf(&hexstr[i * 2], "%2x", &byte) != 1) {
|
||||
fprintf(stderr, "Error: invalid hex character\n");
|
||||
free(data);
|
||||
return 1;
|
||||
}
|
||||
data[i] = (uint8_t)byte;
|
||||
}
|
||||
|
||||
uint64_t crc = crc64_checksum(data, datalen);
|
||||
printf("%016llx\n", (unsigned long long)crc);
|
||||
free(data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Error: unknown action '%s'\n", action);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env python3
|
||||
"""CRC64-ECMA reference implementation in Python."""
|
||||
|
||||
import sys
|
||||
import binascii
|
||||
|
||||
CRC64_ECMA = 0x42F0E1EBA9EA3693
|
||||
|
||||
class CRC64:
|
||||
def __init__(self):
|
||||
self.table = self._make_table(CRC64_ECMA)
|
||||
|
||||
@staticmethod
|
||||
def _make_table(poly):
|
||||
table = []
|
||||
for i in range(256):
|
||||
crc = i << 56
|
||||
for _ in range(8):
|
||||
if crc & 0x8000000000000000:
|
||||
crc = (crc << 1) ^ poly
|
||||
else:
|
||||
crc <<= 1
|
||||
table.append(crc & 0xFFFFFFFFFFFFFFFF)
|
||||
return table
|
||||
|
||||
def checksum(self, data):
|
||||
"""Compute CRC64 checksum of data."""
|
||||
crc = 0
|
||||
for byte in data:
|
||||
crc = self.table[((crc >> 56) ^ byte) & 0xFF] ^ ((crc << 8) & 0xFFFFFFFFFFFFFFFF)
|
||||
return crc & 0xFFFFFFFFFFFFFFFF
|
||||
|
||||
|
||||
def main():
|
||||
crc = CRC64()
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: crc64_ref.py <action> [data...]", file=sys.stderr)
|
||||
print(" checksum <hexstring> - compute CRC64 of hex data", file=sys.stderr)
|
||||
print(" table - print first 16 table entries", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
action = sys.argv[1]
|
||||
|
||||
if action == "table":
|
||||
print("CRC64_ECMA table (first 16):")
|
||||
for i in range(16):
|
||||
print(f" [{i:2d}] = 0x{crc.table[i]:016x}")
|
||||
return 0
|
||||
|
||||
if action == "checksum":
|
||||
hexstr = sys.argv[2] if len(sys.argv) > 2 else ""
|
||||
try:
|
||||
data = binascii.unhexlify(hexstr) if hexstr else b""
|
||||
result = crc.checksum(data)
|
||||
print(f"{result:016x}")
|
||||
return 0
|
||||
except (binascii.Error, ValueError) as e:
|
||||
print(f"Error: invalid hex input: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Error: unknown action '{action}'", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
module fnv1a
|
||||
|
||||
// This module implements a FNV-1a hash.
|
||||
// (see https://en.wikipedia.org/wiki/Fowler–Noll–Vo_hash_function)
|
||||
|
||||
const fnv64_prime = u64(1099511628211)
|
||||
const fnv64_offset_basis = u64(14695981039346656037)
|
||||
const fnv32_offset_basis = u32(2166136261)
|
||||
const fnv32_prime = u32(16777619)
|
||||
|
||||
// sum32 returns a fnv1a hash of the string, described by `data`
|
||||
@[direct_array_access; inline]
|
||||
pub fn sum32_string(data string) u32 {
|
||||
mut hash := fnv32_offset_basis
|
||||
for i in 0 .. data.len {
|
||||
hash = (hash ^ u32(data[i])) * fnv32_prime
|
||||
}
|
||||
return hash
|
||||
}
|
||||
|
||||
// sum32 returns a fnv1a hash of the memory block, described by the dynamic
|
||||
// byte array `data`.
|
||||
@[direct_array_access; inline]
|
||||
pub fn sum32(data []u8) u32 {
|
||||
mut hash := fnv32_offset_basis
|
||||
for i in 0 .. data.len {
|
||||
hash = (hash ^ u32(data[i])) * fnv32_prime
|
||||
}
|
||||
return hash
|
||||
}
|
||||
|
||||
// sum32_bytes returns a fnv1a hash of the struct `s`.
|
||||
@[direct_array_access; inline]
|
||||
pub fn sum32_struct[T](s &T) u32 {
|
||||
bp := unsafe { &u8(s) }
|
||||
sz := int(sizeof(T))
|
||||
mut hash := fnv32_offset_basis
|
||||
for i in 0 .. sz {
|
||||
hash = unsafe { (hash ^ u32(bp[i])) * fnv32_prime }
|
||||
}
|
||||
return hash
|
||||
}
|
||||
|
||||
// sum32_bytes returns a fnv1a hash of `data_len` bytes starting at
|
||||
// the address in the given &byte pointer `data`.
|
||||
@[direct_array_access; inline; unsafe]
|
||||
pub fn sum32_bytes(data &u8, data_len int) u32 {
|
||||
mut hash := fnv32_offset_basis
|
||||
for i in 0 .. data_len {
|
||||
hash = unsafe { (hash ^ u32(data[i])) * fnv32_prime }
|
||||
}
|
||||
return hash
|
||||
}
|
||||
|
||||
// sum64 returns a fnv1a hash of the string, described by `data`
|
||||
@[direct_array_access; inline]
|
||||
pub fn sum64_string(data string) u64 {
|
||||
mut hash := fnv64_offset_basis
|
||||
for i in 0 .. data.len {
|
||||
hash = (hash ^ u64(data[i])) * fnv64_prime
|
||||
}
|
||||
return hash
|
||||
}
|
||||
|
||||
// sum64 returns a fnv1a hash of the memory block, described by the dynamic
|
||||
// byte array `data`.
|
||||
@[direct_array_access; inline]
|
||||
pub fn sum64(data []u8) u64 {
|
||||
mut hash := fnv64_offset_basis
|
||||
for i in 0 .. data.len {
|
||||
hash = (hash ^ u64(data[i])) * fnv64_prime
|
||||
}
|
||||
return hash
|
||||
}
|
||||
|
||||
// sum64_bytes returns a fnv1a hash of `data_len` bytes starting at
|
||||
// the address in the given &byte pointer `data`.
|
||||
@[direct_array_access; inline; unsafe]
|
||||
pub fn sum64_bytes(data &u8, data_len int) u64 {
|
||||
mut hash := fnv64_offset_basis
|
||||
for i in 0 .. data_len {
|
||||
hash = unsafe { (hash ^ u64(data[i])) * fnv64_prime }
|
||||
}
|
||||
return hash
|
||||
}
|
||||
|
||||
// sum64_bytes returns a fnv1a hash of the struct `s`.
|
||||
@[direct_array_access; inline]
|
||||
pub fn sum64_struct[T](s &T) u64 {
|
||||
bp := unsafe { &u8(s) }
|
||||
sz := int(sizeof(T))
|
||||
mut hash := fnv64_offset_basis
|
||||
for i in 0 .. sz {
|
||||
hash = unsafe { (hash ^ u64(bp[i])) * fnv64_prime }
|
||||
}
|
||||
return hash
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import hash.fnv1a
|
||||
|
||||
struct MyStruct {
|
||||
mut:
|
||||
x int
|
||||
y int
|
||||
z int
|
||||
}
|
||||
|
||||
struct Abc {
|
||||
mut:
|
||||
a [5]u64
|
||||
}
|
||||
|
||||
fn test_fnv1a_sum32() {
|
||||
$if windows {
|
||||
return
|
||||
}
|
||||
ahash := '10bc2abf'
|
||||
a := 'apple'
|
||||
b := fnv1a.sum32_string(a)
|
||||
c := fnv1a.sum32(a.bytes())
|
||||
d := unsafe { fnv1a.sum32_bytes(a.str, a.len) }
|
||||
assert b.hex() == ahash
|
||||
assert c.hex() == ahash
|
||||
assert d.hex() == ahash
|
||||
|
||||
mut aa := Abc{}
|
||||
x := fnv1a.sum32_struct(aa)
|
||||
aa.a[3] = 5
|
||||
y := fnv1a.sum32_struct(aa)
|
||||
assert x != y
|
||||
mut ms := MyStruct{}
|
||||
xx := fnv1a.sum32_struct(ms)
|
||||
ms.x = 77
|
||||
yy := fnv1a.sum32_struct(ms)
|
||||
assert xx != yy
|
||||
assert x != xx
|
||||
assert y != yy
|
||||
}
|
||||
|
||||
fn test_fnv1a_sum64() {
|
||||
$if windows {
|
||||
return
|
||||
}
|
||||
a := 'apple'
|
||||
ahash := 'f74a62a458befdbf'
|
||||
b := fnv1a.sum64_string(a)
|
||||
c := fnv1a.sum64(a.bytes())
|
||||
d := unsafe { fnv1a.sum64_bytes(a.str, a.len) }
|
||||
assert b.hex() == ahash
|
||||
assert c.hex() == ahash
|
||||
assert d.hex() == ahash
|
||||
|
||||
mut aa := Abc{}
|
||||
x := fnv1a.sum64_struct(aa)
|
||||
aa.a[3] = 5
|
||||
y := fnv1a.sum64_struct(aa)
|
||||
assert x != y
|
||||
mut ms := MyStruct{}
|
||||
xx := fnv1a.sum64_struct(ms)
|
||||
ms.x = 77
|
||||
yy := fnv1a.sum64_struct(ms)
|
||||
assert xx != yy
|
||||
assert x != xx
|
||||
assert y != yy
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
module hash
|
||||
|
||||
pub interface Hash {
|
||||
mut:
|
||||
// Sum appends the current hash to b and returns the resulting array.
|
||||
// It does not change the underlying hash state.
|
||||
sum(b []u8) []u8
|
||||
size() int
|
||||
block_size() int
|
||||
free()
|
||||
reset()
|
||||
write(p []u8) !int
|
||||
}
|
||||
|
||||
interface Hash32er {
|
||||
sum32() u32
|
||||
}
|
||||
|
||||
interface Hash64er {
|
||||
sum64() u64
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
// Module huffman builds canonical Huffman codes from a per-symbol array of bit
|
||||
// lengths. This is the assignment step shared by RFC 1951 (DEFLATE, used by
|
||||
// compress.deflate / zlib / gzip) and RFC 7541 Appendix B (HPACK, used by the
|
||||
// HTTP/2 code in net.http): given only the lengths, both standards rebuild the
|
||||
// exact same codes via the `bl_count` / `next_code` algorithm (RFC 1951 §3.2.2).
|
||||
//
|
||||
// What the two callers do NOT share — and therefore what this module is
|
||||
// parameterized by — is the maximum code length (DEFLATE caps at 15 bits and
|
||||
// decodes via a flat 2^max_bits table; HPACK goes up to 30 bits, where a flat
|
||||
// table is infeasible) and the bit order (DEFLATE is LSB-first and bit-reverses
|
||||
// every code, HPACK is MSB-first). The bit I/O loops and codec-specific
|
||||
// semantics (EOS/padding, end-of-block, extra bits, distance alphabets) stay in
|
||||
// the callers.
|
||||
module huffman
|
||||
|
||||
// BitOrder selects how a code's bits are laid out in the returned `u32`.
|
||||
pub enum BitOrder {
|
||||
// msb_first keeps the canonical code as-is: the first transmitted bit is
|
||||
// the most-significant bit of the code (RFC 7541 / HPACK).
|
||||
msb_first
|
||||
// lsb_first reverses each code within its length, so the first transmitted
|
||||
// bit is the least-significant bit (RFC 1951 / DEFLATE). This is the form a
|
||||
// flat LSB-first decode table is indexed by.
|
||||
lsb_first
|
||||
}
|
||||
|
||||
// Config parameterizes build(). All fields are required and have no defaults:
|
||||
// the two callers have intentionally different requirements (15 vs 30 bits,
|
||||
// LSB vs MSB), so an implicit default would silently fit only one of them.
|
||||
@[params]
|
||||
pub struct Config {
|
||||
pub:
|
||||
lengths []int @[required] // per-symbol code length in bits; 0 marks an unused symbol
|
||||
max_bits int @[required] // maximum allowed code length; must be >= every nonzero length
|
||||
bit_order BitOrder @[required] // .msb_first (HPACK) or .lsb_first (DEFLATE)
|
||||
}
|
||||
|
||||
// Table is the result of build(): the canonical code for every symbol, plus the
|
||||
// metadata a caller needs to drive its own bit I/O and to build a decode
|
||||
// structure (flat_table() for small max_bits, decode_map() otherwise).
|
||||
@[noinit]
|
||||
pub struct Table {
|
||||
pub:
|
||||
codes []u32 // per-symbol code, right-aligned in a u32, in `bit_order`
|
||||
lengths []int // per-symbol bit length (a copy of the input)
|
||||
max_bits int
|
||||
bit_order BitOrder
|
||||
}
|
||||
|
||||
// max_flat_bits is the largest max_bits for which flat_table() will
|
||||
// allocate a table (2^max_bits entries). DEFLATE's 15 fits; HPACK's 30 does
|
||||
// not and must use decode_map() / a bit-at-a-time decoder instead.
|
||||
pub const max_flat_bits = 18
|
||||
|
||||
// flat_invalid_entry marks a flat_table() slot that no code maps to.
|
||||
pub const flat_invalid_entry = u32(0xffff_ffff)
|
||||
|
||||
// flat_length_bits is how many low bits of a flat_table() entry hold the
|
||||
// code length; the symbol is stored in the remaining high bits. 5 bits hold
|
||||
// lengths up to 31, covering every max_bits <= max_flat_bits.
|
||||
pub const flat_length_bits = 5
|
||||
|
||||
// next_codes validates `lengths` against `max_bits` and returns the canonical
|
||||
// starting code for each length (RFC 1951 §3.2.2): next_code[l] is the code the
|
||||
// first symbol of length l receives, and callers post-increment it per symbol.
|
||||
// It also reports whether the code is `complete` (uses the whole code space,
|
||||
// i.e. the Kraft inequality holds with equality), which lets flat_table() skip
|
||||
// pre-filling the table. It is the single source of truth for the code
|
||||
// assignment shared by build() and flat_table(). Errors on max_bits < 1 or
|
||||
// > 32, a negative length or one exceeding max_bits, or an over-subscribed code
|
||||
// (Kraft inequality); an incomplete (under-subscribed) code is allowed, as both
|
||||
// DEFLATE and HPACK use.
|
||||
fn next_codes(lengths []int, max_bits int) !([]u32, bool) {
|
||||
if max_bits < 1 {
|
||||
return error('huffman: max_bits must be >= 1, got ${max_bits}')
|
||||
}
|
||||
if max_bits > 32 {
|
||||
return error('huffman: max_bits ${max_bits} exceeds 32 (u32 code storage)')
|
||||
}
|
||||
mut bl_count := []int{len: max_bits + 1}
|
||||
for sym, l in lengths {
|
||||
if l < 0 {
|
||||
return error('huffman: negative length ${l} for symbol ${sym}')
|
||||
}
|
||||
if l > max_bits {
|
||||
return error('huffman: length ${l} for symbol ${sym} exceeds max_bits ${max_bits}')
|
||||
}
|
||||
if l > 0 {
|
||||
bl_count[l]++
|
||||
}
|
||||
}
|
||||
// Kraft inequality: sum over used symbols of 2^(max_bits - len) must not
|
||||
// exceed 2^max_bits, i.e. the code must not be over-subscribed. left == 0 at
|
||||
// the end means the code is complete (covers every index of a flat table).
|
||||
mut left := u64(1) << max_bits
|
||||
for bits in 1 .. max_bits + 1 {
|
||||
used := u64(bl_count[bits]) << (max_bits - bits)
|
||||
if used > left {
|
||||
return error('huffman: over-subscribed code (lengths exceed the code space)')
|
||||
}
|
||||
left -= used
|
||||
}
|
||||
mut next_code := []u32{len: max_bits + 1}
|
||||
mut c := u32(0)
|
||||
for bits in 1 .. max_bits + 1 {
|
||||
c = (c + u32(bl_count[bits - 1])) << 1
|
||||
next_code[bits] = c
|
||||
}
|
||||
return next_code, left == 0
|
||||
}
|
||||
|
||||
// build assigns a canonical Huffman code to every symbol from its bit length.
|
||||
// Symbols with length 0 are unused and get code 0. It returns the codes plus
|
||||
// the metadata for a decode structure; use it when you need the per-symbol
|
||||
// codes (e.g. HPACK encoding) and/or decode_map(). Callers that only want a
|
||||
// flat decode table should use flat_table(), which avoids materializing the
|
||||
// codes array. See next_codes() for the validation rules.
|
||||
pub fn build(cfg Config) !Table {
|
||||
mut next_code, _ := next_codes(cfg.lengths, cfg.max_bits)!
|
||||
mut codes := []u32{len: cfg.lengths.len}
|
||||
for sym, l in cfg.lengths {
|
||||
if l == 0 {
|
||||
continue
|
||||
}
|
||||
code := next_code[l]
|
||||
next_code[l]++
|
||||
codes[sym] = if cfg.bit_order == .lsb_first { bit_reverse(code, l) } else { code }
|
||||
}
|
||||
return Table{
|
||||
codes: codes
|
||||
lengths: cfg.lengths.clone()
|
||||
max_bits: cfg.max_bits
|
||||
bit_order: cfg.bit_order
|
||||
}
|
||||
}
|
||||
|
||||
// flat_table builds a 2^max_bits lookup table for fast decode of short codes
|
||||
// (the DEFLATE strategy), directly from the lengths. Each entry is
|
||||
// `(symbol << flat_length_bits) | length`, or flat_invalid_entry for indices no
|
||||
// code reaches. The table is indexed by the next max_bits bits read from the
|
||||
// stream in `bit_order`. It returns an error if max_bits > max_flat_bits, since
|
||||
// the table would be prohibitively large (use build() + decode_map() instead).
|
||||
//
|
||||
// Unlike build(), this allocates no per-symbol codes array and does not copy
|
||||
// the lengths: it assigns each code inline while filling the table, so a hot
|
||||
// caller rebuilding a tree per block (compress.deflate) pays no extra
|
||||
// allocation over hand-rolling the loop.
|
||||
pub fn flat_table(cfg Config) ![]u32 {
|
||||
if cfg.max_bits > max_flat_bits {
|
||||
return error('huffman: max_bits ${cfg.max_bits} > max_flat_bits ${max_flat_bits}; use build() + decode_map()')
|
||||
}
|
||||
mut next_code, complete := next_codes(cfg.lengths, cfg.max_bits)!
|
||||
table_size := 1 << cfg.max_bits
|
||||
// A complete code writes every table slot, so the invalid pre-fill is dead
|
||||
// work; allocate zeroed (vcalloc) and skip it. An incomplete code leaves
|
||||
// gaps that must read back as flat_invalid_entry, so it pays the fill.
|
||||
mut table := if complete {
|
||||
[]u32{len: table_size}
|
||||
} else {
|
||||
[]u32{len: table_size, init: flat_invalid_entry}
|
||||
}
|
||||
for sym, l in cfg.lengths {
|
||||
if l == 0 {
|
||||
continue
|
||||
}
|
||||
raw := next_code[l]
|
||||
next_code[l]++
|
||||
entry := (u32(sym) << flat_length_bits) | u32(l)
|
||||
// The (max_bits - l) bits beyond the code are don't-cares, so a code of
|
||||
// length l fills 2^(max_bits - l) table slots. Where those slots sit
|
||||
// depends on bit_order: LSB-first codes occupy every index whose low l
|
||||
// bits match the code (stride by 2^l); MSB-first codes occupy a
|
||||
// contiguous block whose high l bits match the code.
|
||||
if cfg.bit_order == .lsb_first {
|
||||
step := 1 << l
|
||||
mut idx := int(bit_reverse(raw, l))
|
||||
for idx < table_size {
|
||||
table[idx] = entry
|
||||
idx += step
|
||||
}
|
||||
} else {
|
||||
block := 1 << (cfg.max_bits - l)
|
||||
base := int(raw) * block
|
||||
for k in 0 .. block {
|
||||
table[base + k] = entry
|
||||
}
|
||||
}
|
||||
}
|
||||
return table
|
||||
}
|
||||
|
||||
// decode_map builds a map from a packed (length, code) key to its symbol, for
|
||||
// codecs whose max_bits is too large for a flat table (HPACK's 30 bits). The
|
||||
// key is `(u64(length) << 32) | code`; a decoder accumulates bits MSB-first and
|
||||
// looks up after each bit. Only defined for .msb_first tables, where the
|
||||
// accumulated value matches the stored code; it returns an error otherwise.
|
||||
pub fn (t Table) decode_map() !map[u64]int {
|
||||
if t.bit_order != .msb_first {
|
||||
return error('huffman: decode_map requires .msb_first bit order')
|
||||
}
|
||||
mut m := map[u64]int{}
|
||||
for sym, l in t.lengths {
|
||||
if l == 0 {
|
||||
continue
|
||||
}
|
||||
m[(u64(l) << 32) | u64(t.codes[sym])] = sym
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// bit_reverse reverses the low `n` bits of `v` (used to convert a canonical
|
||||
// MSB-first code into the LSB-first form a DEFLATE bit reader consumes).
|
||||
fn bit_reverse(v u32, n int) u32 {
|
||||
mut r := u32(0)
|
||||
mut val := v
|
||||
for _ in 0 .. n {
|
||||
r = (r << 1) | (val & 1)
|
||||
val >>= 1
|
||||
}
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
module huffman
|
||||
|
||||
// The worked example from RFC 1951 §3.2.2: symbols A..H with these lengths
|
||||
// produce these exact canonical (MSB-first) codes.
|
||||
fn test_rfc1951_canonical_example() {
|
||||
lengths := [3, 3, 3, 3, 3, 2, 4, 4] // A B C D E F G H
|
||||
t := build(lengths: lengths, max_bits: 4, bit_order: .msb_first)!
|
||||
expected := [u32(0b010), 0b011, 0b100, 0b101, 0b110, 0b00, 0b1110, 0b1111]
|
||||
assert t.codes == expected
|
||||
assert t.lengths == lengths
|
||||
assert t.max_bits == 4
|
||||
}
|
||||
|
||||
fn test_lsb_first_reverses_each_code() {
|
||||
lengths := [3, 3, 3, 3, 3, 2, 4, 4]
|
||||
msb := build(lengths: lengths, max_bits: 4, bit_order: .msb_first)!
|
||||
lsb := build(lengths: lengths, max_bits: 4, bit_order: .lsb_first)!
|
||||
// Each LSB code is the MSB code bit-reversed within its length.
|
||||
for sym, l in lengths {
|
||||
assert lsb.codes[sym] == bit_reverse(msb.codes[sym], l)
|
||||
}
|
||||
// e.g. F (len 2, code 00) is unchanged; A (len 3, 010) -> 010 reversed.
|
||||
assert lsb.codes[5] == 0b00
|
||||
assert lsb.codes[0] == 0b010 // 010 reversed is still 010
|
||||
assert lsb.codes[6] == bit_reverse(u32(0b1110), 4) // 1110 -> 0111
|
||||
}
|
||||
|
||||
fn test_flat_table_round_trips_lsb() {
|
||||
lengths := [3, 3, 3, 3, 3, 2, 4, 4]
|
||||
t := build(lengths: lengths, max_bits: 4, bit_order: .lsb_first)!
|
||||
table := flat_table(lengths: lengths, max_bits: 4, bit_order: .lsb_first)!
|
||||
assert table.len == 1 << 4
|
||||
// Every symbol must decode back from its code in every don't-care variant.
|
||||
for sym, l in lengths {
|
||||
step := 1 << l
|
||||
mut idx := int(t.codes[sym])
|
||||
for idx < table.len {
|
||||
entry := table[idx]
|
||||
assert entry != flat_invalid_entry
|
||||
assert int(entry & ((u32(1) << flat_length_bits) - 1)) == l
|
||||
assert int(entry >> flat_length_bits) == sym
|
||||
idx += step
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_flat_table_round_trips_msb() {
|
||||
// MSB-first flat table: a code of length l fills the contiguous block whose
|
||||
// high l bits equal the code (the low max_bits-l bits are don't-cares).
|
||||
lengths := [3, 3, 3, 3, 3, 2, 4, 4]
|
||||
t := build(lengths: lengths, max_bits: 4, bit_order: .msb_first)!
|
||||
table := flat_table(lengths: lengths, max_bits: 4, bit_order: .msb_first)!
|
||||
assert table.len == 1 << 4
|
||||
for sym, l in lengths {
|
||||
block := 1 << (t.max_bits - l)
|
||||
base := int(t.codes[sym]) * block
|
||||
for k in 0 .. block {
|
||||
entry := table[base + k]
|
||||
assert entry != flat_invalid_entry
|
||||
assert int(entry & ((u32(1) << flat_length_bits) - 1)) == l
|
||||
assert int(entry >> flat_length_bits) == sym
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_flat_table_incomplete_marks_gaps() {
|
||||
// A single length-1 code under-subscribes a 2-bit table: half the indices
|
||||
// belong to no code and must read back as flat_invalid_entry. This is the
|
||||
// path the complete-code fast path must NOT take.
|
||||
table := flat_table(lengths: [1], max_bits: 2, bit_order: .lsb_first)!
|
||||
assert table.len == 4
|
||||
// code 0, len 1, lsb stride 2 -> indices 0 and 2 are the symbol; 1 and 3 gaps.
|
||||
assert int(table[0] >> flat_length_bits) == 0
|
||||
assert int(table[0] & ((u32(1) << flat_length_bits) - 1)) == 1
|
||||
assert table[2] == table[0]
|
||||
assert table[1] == flat_invalid_entry
|
||||
assert table[3] == flat_invalid_entry
|
||||
}
|
||||
|
||||
fn test_decode_map_msb() {
|
||||
lengths := [3, 3, 3, 3, 3, 2, 4, 4]
|
||||
t := build(lengths: lengths, max_bits: 4, bit_order: .msb_first)!
|
||||
m := t.decode_map()!
|
||||
for sym, l in lengths {
|
||||
key := (u64(l) << 32) | u64(t.codes[sym])
|
||||
assert m[key] == sym
|
||||
}
|
||||
}
|
||||
|
||||
fn test_decode_map_rejects_lsb() {
|
||||
t := build(lengths: [1, 1], max_bits: 1, bit_order: .lsb_first)!
|
||||
if _ := t.decode_map() {
|
||||
assert false, 'decode_map should reject lsb_first tables'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_unused_symbols_get_zero_code() {
|
||||
// A length-0 symbol is unused; it must not consume a code.
|
||||
t := build(lengths: [1, 0, 1], max_bits: 1, bit_order: .msb_first)!
|
||||
assert t.codes[1] == 0
|
||||
assert t.codes[0] == 0
|
||||
assert t.codes[2] == 1
|
||||
}
|
||||
|
||||
fn test_error_length_exceeds_max_bits() {
|
||||
if _ := build(lengths: [5], max_bits: 4, bit_order: .msb_first) {
|
||||
assert false, 'length > max_bits must error'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_error_negative_length() {
|
||||
if _ := build(lengths: [-1], max_bits: 4, bit_order: .msb_first) {
|
||||
assert false, 'negative length must error'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_error_max_bits_too_small() {
|
||||
if _ := build(lengths: [1], max_bits: 0, bit_order: .msb_first) {
|
||||
assert false, 'max_bits < 1 must error'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_error_over_subscribed() {
|
||||
// Three length-1 codes cannot coexist (only two 1-bit codes exist).
|
||||
if _ := build(lengths: [1, 1, 1], max_bits: 1, bit_order: .msb_first) {
|
||||
assert false, 'over-subscribed code must error'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_incomplete_code_is_allowed() {
|
||||
// A single length-2 code under-subscribes the space; that is permitted.
|
||||
t := build(lengths: [2], max_bits: 2, bit_order: .msb_first)!
|
||||
assert t.codes[0] == 0
|
||||
}
|
||||
|
||||
fn test_flat_table_rejects_wide_codes() {
|
||||
if _ := flat_table(
|
||||
lengths: [max_flat_bits + 1]
|
||||
max_bits: max_flat_bits + 1
|
||||
bit_order: .lsb_first
|
||||
)
|
||||
{
|
||||
assert false, 'flat table must reject max_bits > max_flat_bits'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
module hash
|
||||
|
||||
//#flag -I @VEXEROOT/thirdparty/wyhash
|
||||
//#include "wyhash.h"
|
||||
fn C.wyhash(&u8, u64, u64, &u64) u64
|
||||
|
||||
fn C.wyhash64(u64, u64) u64
|
||||
|
||||
// wyhash_c returns a hash given a byte string `key`, its `len`, and a `seed`.
|
||||
@[inline]
|
||||
pub fn wyhash_c(key &u8, len u64, seed u64) u64 {
|
||||
return C.wyhash(key, len, seed, &u64(voidptr(C._wyp)))
|
||||
}
|
||||
|
||||
// wyhash64_c returns a hash given two u64 values `a` and `b`.
|
||||
@[inline]
|
||||
pub fn wyhash64_c(a u64, b u64) u64 {
|
||||
return C.wyhash64(a, b)
|
||||
}
|
||||
|
||||
// sum64_string returns a hash given a V string `key` and a `seed`.
|
||||
@[inline]
|
||||
pub fn sum64_string(key string, seed u64) u64 {
|
||||
return wyhash_c(key.str, u64(key.len), seed)
|
||||
}
|
||||
|
||||
// sum64 returns a hash given a byte array `key` and a `seed`.
|
||||
@[inline]
|
||||
pub fn sum64(key []u8, seed u64) u64 {
|
||||
return wyhash_c(key.data, u64(key.len), seed)
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
module hash
|
||||
|
||||
pub fn sum64_string(s string, key u64) u64 {
|
||||
panic('not yet implemented')
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
//
|
||||
// this is an implementation of wyhash v4
|
||||
// from https://github.com/wangyi-fudan/wyhash
|
||||
//
|
||||
// TODO: use u128 once implemented
|
||||
// currently the C version performs slightly better
|
||||
// because it uses 128 bit int when available and
|
||||
// branch prediction hints. the C version will be
|
||||
// removed once the performance is matched.
|
||||
// you can test performance by running:
|
||||
// `v run cmd/tools/bench/wyhash.v`
|
||||
// try running with and without the `-prod` flag
|
||||
module hash
|
||||
|
||||
const wyp0 = u64(0x2d358dccaa6c78a5)
|
||||
const wyp1 = u64(0x8bb84b93962eacc9)
|
||||
const wyp2 = u64(0x4b33a62ed433d4a3)
|
||||
const wyp3 = u64(0x4d5a2da51de1aa47)
|
||||
|
||||
// wymum returns a hash by performing multiply and mix on `a` and `b`.
|
||||
@[ignore_overflow; inline]
|
||||
pub fn wymum(a u64, b u64) u64 {
|
||||
/*
|
||||
mut r := u128(a)
|
||||
r = r*b
|
||||
return (r>>64)^r
|
||||
*/
|
||||
mask32 := u32(4294967295)
|
||||
x0 := a & mask32
|
||||
x1 := a >> 32
|
||||
y0 := b & mask32
|
||||
y1 := b >> 32
|
||||
w0 := x0 * y0
|
||||
t := x1 * y0 + (w0 >> 32)
|
||||
mut w1 := t & mask32
|
||||
w2 := t >> 32
|
||||
w1 += x0 * y1
|
||||
hi := x1 * y1 + w2 + (w1 >> 32)
|
||||
lo := a * b
|
||||
return hi ^ lo
|
||||
}
|
||||
Reference in new issue
Block a user