This commit is contained in:
Cristian Cezar Moisés 2026-05-02 00:49:49 -03:00
commit 7619c4c577
41 changed files with 2031 additions and 624 deletions

View file

@ -1,5 +1,5 @@
/* SPDX-License-Identifier: GPL-3.0-or-later
* Copyright (c) 2025-2026 Cristian Cezar Moisés
/*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* VaptVupt Canonical Huffman Codec Implementation
*
@ -461,6 +461,111 @@ vvh_error_t vvh_encode(const uint8_t *src, size_t src_len,
return VVH_OK;
}
/* ═══════════════════════════════════════════════════════════════
* 4-STREAM INTERLEAVED ENCODE (Sprint 103, Phase A)
*
* Splits input into 4 round-robin streams sharing a single Huffman
* table. The decoder runs 4 independent decoders in parallel,
* gaining instruction-level parallelism (1.8-2.2× decode throughput).
*
* Wire format (after the standard code-length header):
* [3B stream1_size] [3B stream2_size] [3B stream3_size]
* [stream0_bitstream] [stream1_bitstream] [stream2_bitstream] [stream3_bitstream]
*
* Stream0's size is implicit: total - 9 - hdr_sz - s1 - s2 - s3.
* Each stream is byte-aligned at its start (clean entry for decoder).
*
* Activation guard: src_len >= 1024. Below this, single-stream wins
* on overhead (9-byte stream-size header + per-stream alignment slop
* dominates).
*
* See CHANGELOG.md (Sprint 105) for the design rationale.
* */
#define VVH4_STREAM_HDR_SZ 9 /* 3 bytes × 3 stream sizes */
#define VVH4_MIN_LITERALS 1024
vvh_error_t vvh_encode4(const uint8_t *src, size_t src_len,
uint8_t *dst, size_t dst_cap, size_t *dst_len) {
/* Activation guard: 4-stream is only profitable above 1024 lits. */
if (src_len < VVH4_MIN_LITERALS) return VVH_ERR_OVERFLOW;
/* Need at least: code-length header (~129B) + 9B stream-hdr +
* 4 streams of nonzero size. Conservative lower bound. */
if (dst_cap < 200) return VVH_ERR_OVERFLOW;
/* ─── 1. Count frequencies (single shared table) ─── */
uint32_t freq[VVH_SYMBOLS];
memset(freq, 0, sizeof(freq));
for (size_t i = 0; i < src_len; i++)
freq[src[i]]++;
/* ─── 2. Build encode table (shared across all 4 streams) ─── */
vvh_enc_table_t enc;
build_enc_table(freq, &enc);
/* ─── 3. Write code-length header ─── */
size_t hdr_sz = write_header(enc.lengths, dst, dst_cap);
if (hdr_sz == 0) return VVH_ERR_OVERFLOW;
/* ─── 4. Reserve 9 bytes for stream-size header (backpatched) ─── */
if (hdr_sz + VVH4_STREAM_HDR_SZ >= dst_cap) return VVH_ERR_OVERFLOW;
uint8_t *stream_hdr = dst + hdr_sz;
size_t streams_offset = hdr_sz + VVH4_STREAM_HDR_SZ;
/* ─── 5. Encode each stream into the dst buffer ─── */
/* Per-stream symbol counts for round-robin distribution:
* stream0 gets indices 0, 4, 8, ..., (src_len + 3) / 4 symbols
* stream1 gets indices 1, 5, 9, ..., (src_len + 2) / 4 symbols
* stream2 gets indices 2, 6, 10, ..., (src_len + 1) / 4 symbols
* stream3 gets indices 3, 7, 11, ..., src_len / 4 symbols
*/
size_t cur_off = streams_offset;
size_t stream_sizes[4];
for (int s = 0; s < 4; s++) {
if (cur_off >= dst_cap) return VVH_ERR_OVERFLOW;
bw_t w;
bw_init(&w, dst + cur_off, dst_cap - cur_off);
/* Round-robin: encode symbols at indices s, s+4, s+8, ... */
for (size_t i = (size_t)s; i < src_len; i += 4) {
uint8_t sym = src[i];
/* enc.lengths[sym] could be 0 only if the symbol never
* appeared in input but we just counted and it did, so
* length > 0 for every symbol we encode. Defensive check
* for static analyzer happiness: */
if (enc.lengths[sym] == 0) return VVH_ERR_CORRUPT;
bw_add(&w, enc.codes[sym], enc.lengths[sym]);
}
size_t sz = bw_flush(&w);
stream_sizes[s] = sz;
cur_off += sz;
}
/* ─── 6. Backpatch stream-size header (3 bytes per stream, LE) ─── */
/* Stream 0 size is implicit; encode streams 1, 2, 3 here.
* Each size is stored as 24-bit little-endian (max 16 MB / stream
* far above any realistic literal-block size). */
for (int s = 1; s <= 3; s++) {
size_t sz = stream_sizes[s];
if (sz > 0xFFFFFF) return VVH_ERR_OVERFLOW; /* >16MB stream */
uint8_t *p = stream_hdr + (s - 1) * 3;
p[0] = (uint8_t)(sz & 0xFF);
p[1] = (uint8_t)((sz >> 8) & 0xFF);
p[2] = (uint8_t)((sz >> 16) & 0xFF);
}
size_t total = cur_off;
/* Incompressible guard: same convention as vvh_encode. */
if (total >= src_len) return VVH_ERR_OVERFLOW;
*dst_len = total;
return VVH_OK;
}
/* ═══════════════════════════════════════════════════════════════
* DECODE
*
@ -556,3 +661,142 @@ vvh_error_t vvh_decode(const uint8_t *src, size_t src_len,
free(dec);
return VVH_OK;
}
/* ═══════════════════════════════════════════════════════════════
* 4-STREAM INTERLEAVED DECODE (Sprint 104, Phase B)
*
* Production decoder for the wire format produced by vvh_encode4.
* Runs 4 independent decoders in parallel using a single shared
* decode table. Each iteration of the hot loop performs 4 lookups
* with no inter-decoder data dependencies, allowing the OoO engine
* to pipeline them.
*
* Wire format expected (see vvh_encode4):
* [code-length header] [3B s1] [3B s2] [3B s3]
* [stream0_data] [stream1_data] [stream2_data] [stream3_data]
*
* Stream0's size is implicit. All four streams share one Huffman
* table built from the code-length header.
* */
vvh_error_t vvh_decode4(const uint8_t *src, size_t src_len,
uint8_t *dst, size_t dst_cap,
size_t num_literals, size_t *src_consumed) {
if (num_literals == 0) {
*src_consumed = 0;
return VVH_OK;
}
if (num_literals > dst_cap) return VVH_ERR_OVERFLOW;
/* ─── 1. Read code-length header ─── */
uint8_t lengths[VVH_SYMBOLS];
size_t hdr_sz = read_header(src, src_len, lengths);
if (hdr_sz == 0) return VVH_ERR_CORRUPT;
/* Validate at least one nonzero length */
int has_sym = 0;
for (int i = 0; i < VVH_SYMBOLS; i++)
if (lengths[i] > 0) { has_sym = 1; break; }
if (!has_sym) return VVH_ERR_CORRUPT;
/* ─── 2. Read 9-byte stream-size header ─── */
if (hdr_sz + VVH4_STREAM_HDR_SZ > src_len) return VVH_ERR_CORRUPT;
const uint8_t *sh = src + hdr_sz;
size_t s1 = (size_t)sh[0] | ((size_t)sh[1] << 8) | ((size_t)sh[2] << 16);
size_t s2 = (size_t)sh[3] | ((size_t)sh[4] << 8) | ((size_t)sh[5] << 16);
size_t s3 = (size_t)sh[6] | ((size_t)sh[7] << 8) | ((size_t)sh[8] << 16);
/* ─── 3. Validate stream sizes (DoS-resistant bounds checks) ─── */
size_t streams_off = hdr_sz + VVH4_STREAM_HDR_SZ;
if (streams_off > src_len) return VVH_ERR_CORRUPT;
size_t streams_total = src_len - streams_off;
/* Overflow-safe check: s1 + s2 + s3 <= streams_total */
if (s1 > streams_total) return VVH_ERR_CORRUPT;
if (s2 > streams_total - s1) return VVH_ERR_CORRUPT;
if (s3 > streams_total - s1 - s2) return VVH_ERR_CORRUPT;
size_t s0 = streams_total - s1 - s2 - s3;
/* All streams must be non-zero unless num_literals < 4 (degenerate) */
if (num_literals >= 4) {
if (s0 == 0 || s1 == 0 || s2 == 0 || s3 == 0) return VVH_ERR_CORRUPT;
}
/* ─── 4. Build decode table (shared across all 4 streams) ─── */
vvh_dec_table_t *dec = (vvh_dec_table_t *)malloc(sizeof(vvh_dec_table_t));
if (!dec) return VVH_ERR_NOMEM;
build_dec_table(lengths, dec);
/* ─── 5. Initialize 4 independent bitstream readers ─── */
br_t r0, r1, r2, r3;
br_init(&r0, src + streams_off, s0);
br_init(&r1, src + streams_off + s0, s1);
br_init(&r2, src + streams_off + s0 + s1, s2);
br_init(&r3, src + streams_off + s0 + s1 + s2, s3);
br_refill(&r0); br_refill(&r1); br_refill(&r2); br_refill(&r3);
/* ─── 6. Per-stream symbol counts (round-robin) ─── */
/* num_literals = 4*Q + R where R in {0,1,2,3}.
* stream 0 decodes Q + (R >= 1) symbols
* stream 1 decodes Q + (R >= 2) symbols
* stream 2 decodes Q + (R >= 3) symbols
* stream 3 decodes Q symbols */
size_t Q = num_literals / 4;
/* Helper: decode one symbol. Inlined manually below for ILP. */
#define DEC_ONE(R, OUT) do { \
if ((R).nbits < VVH_MAX_CODE_LEN) br_refill(&(R)); \
uint32_t peek = br_peek(&(R), VVH_DECODE_BITS); \
uint32_t entry = dec->table[peek]; \
int sym = (int)(entry & 0xFF); \
int len = (int)((entry >> 8) & 0xF); \
if (VV_LIKELY(len > 0)) { \
br_consume(&(R), len); \
(OUT) = (uint8_t)sym; \
} else { \
int found = 0; \
for (int s = 0; s < dec->slow_count; s++) { \
int slen = dec->slow_len[s]; \
uint32_t mask = (1u << slen) - 1; \
if ((br_peek(&(R), slen) & mask) == dec->slow_code[s]) { \
br_consume(&(R), slen); \
(OUT) = dec->slow_sym[s]; \
found = 1; \
break; \
} \
} \
if (!found) { free(dec); return VVH_ERR_CORRUPT; } \
} \
} while (0)
/* ─── 7. Hot loop: decode 4 symbols per iteration ─── */
/* Each iteration's 4 decodes are fully independent — different
* readers, different table peeks, different output positions.
* Modern OoO engines can pipeline 4 independent decode chains
* achieving ~1.8-2.2× speedup over single-stream. */
size_t out_idx = 0;
for (size_t i = 0; i < Q; i++) {
uint8_t y0, y1, y2, y3;
DEC_ONE(r0, y0);
DEC_ONE(r1, y1);
DEC_ONE(r2, y2);
DEC_ONE(r3, y3);
dst[out_idx + 0] = y0;
dst[out_idx + 1] = y1;
dst[out_idx + 2] = y2;
dst[out_idx + 3] = y3;
out_idx += 4;
}
/* ─── 8. Tail (handle remaining 0-3 symbols) ─── */
size_t tail = num_literals - Q * 4;
if (tail >= 1) { uint8_t y; DEC_ONE(r0, y); dst[out_idx++] = y; }
if (tail >= 2) { uint8_t y; DEC_ONE(r1, y); dst[out_idx++] = y; }
if (tail >= 3) { uint8_t y; DEC_ONE(r2, y); dst[out_idx++] = y; }
#undef DEC_ONE
/* Total bytes consumed: header + stream-size header + all 4 streams */
*src_consumed = streams_off + s0 + s1 + s2 + s3;
free(dec);
return VVH_OK;
}