Initial project import

This commit is contained in:
Agila Forge Importer committed 2026-07-13 15:20:32 +00:00
commit 23f2d29e32
11957 files changed
+1754577

No files matched your search

+15
View File
@@ -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
+51
View File
@@ -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)
}
+64
View File
@@ -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)
}
+145
View File
@@ -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)
}
+157
View File
@@ -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)
}
+80
View File
@@ -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)
}
+179
View File
@@ -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
}
+86
View File
@@ -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()
+97
View File
@@ -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
}
+67
View File
@@ -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
}
+24
View File
@@ -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
}
+225
View File
@@ -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
}
+145
View File
@@ -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'
}
}
+31
View File
@@ -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)
}
+6
View File
@@ -0,0 +1,6 @@
module hash
pub fn sum64_string(s string, key u64) u64 {
panic('not yet implemented')
return 0
}
+44
View File
@@ -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
}