v2.2.3
This commit is contained in:
parent
e5f5d32aab
commit
7619c4c577
41 changed files with 2031 additions and 624 deletions
248
src/vv_huffman.c
248
src/vv_huffman.c
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue