zupt/src/vv_encoder.c
Cristian Cezar Moisés 59f9ebc59e codec: update vendored VaptVupt codec 2.65.0 -> 2.65.3
From vaptvupt-codec tag v2.65.3. Output is byte-identical to 2.65.0 (same
ratio, wire format v1.6 unchanged) but extreme-mode encode is ~1.6-2x faster
(Sprint 132 optimal-parser speedup) and the extreme prepass window allocation
is capped at wlog=20 = 8 MiB virtual instead of up to 128 MiB (Sprint 133
memory hygiene). Our AVX2 offset-read decoder guard is now UPSTREAM (dropped
from the local patch set); the ANS safe-zone 2*SAFEZONE_MAX_RUN reserve is
re-applied on top (still not upstream). make check 16/16, KAT 16/16, cross-
version roundtrip with 5.1.0 archives verified.
2026-07-12 14:47:40 -03:00

2739 lines
123 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* VaptVupt — Encoder v2 (Sprint 1)
*
* KEY CHANGES:
* 1. 5-byte multiply-shift hash (fewer collisions than 4-byte)
* 2. Rep-match: check 3 recent offsets before hash probe (30% hit rate)
* 3. Match-skip: after long matches, only insert boundary positions
* 4. AVX2 match extension: 32 bytes/cycle vs 1 byte/cycle scalar
* 5. Lazy-2 parsing for balanced mode (check pos+1 AND pos+2)
* 6. Extreme mode: deeper chains (256) + lazy-2
*/
#include "vaptvupt.h"
#include <stdio.h>
#include "vv_platform.h"
#include "vv_huffman.h"
#include "vv_ans.h"
#include "vv_bcj.h"
#include <stdlib.h>
#include <string.h>
#if defined(__x86_64__) && defined(__AVX2__)
#include <immintrin.h>
#define VV_ENC_AVX2 1
#else
#define VV_ENC_AVX2 0
#endif
/* ═══════════════════════════════════════════════════════════════
* SECURE MEMORY ZERO (Sprint 117 — defense in depth)
*
* Compiler-resistant memset that the optimizer cannot eliminate
* even when followed by free(). Used to scrub plaintext-derived
* working buffers (literals, stripped tokens, ANS scratch) before
* release back to the heap allocator. Without this, plaintext
* fragments persist in the heap free-list and may be observable
* through later allocations or memory disclosure.
*
* Implementation strategy:
* - Prefer `explicit_bzero` (BSD/glibc 2.25+, guaranteed-secure)
* - Fall back to `memset_explicit` (C23)
* - Last resort: volatile-pointer memset (compiler cannot
* prove the writes are dead)
* ═══════════════════════════════════════════════════════════════ */
#if defined(__GLIBC__) && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 25))
# define VV_HAS_EXPLICIT_BZERO 1
#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
# define VV_HAS_EXPLICIT_BZERO 1
#else
# define VV_HAS_EXPLICIT_BZERO 0
#endif
#if VV_HAS_EXPLICIT_BZERO
/* Forward-declare so we don't have to enable _DEFAULT_SOURCE globally
* (which would conflict with the strict _POSIX_C_SOURCE=199309L the
* project sets). The symbol is in libc on supported platforms. */
extern void explicit_bzero(void *s, size_t n);
#endif
static inline void vv_secure_zero(void *buf, size_t len) {
if (!buf || !len) return;
#if VV_HAS_EXPLICIT_BZERO
explicit_bzero(buf, len);
#else
/* Volatile pointer prevents the compiler from concluding the
* memset is dead and eliminating it. The volatile read of `p`
* each iteration forces the writes to be observable. */
volatile unsigned char *p = (volatile unsigned char *)buf;
while (len--) *p++ = 0;
#endif
}
/* Sprint 117: VV_NO_SANITIZE_INTEGER is provided by include/vv_platform.h. */
/* ═══════════════════════════════════════════════════════════════
* VARINT WRITER
* ═══════════════════════════════════════════════════════════════ */
static inline size_t write_varint(uint8_t *dst, size_t val) {
size_t n = 0;
while (val >= 255) { dst[n++] = 255; val -= 255; }
dst[n++] = (uint8_t)val;
return n;
}
/* ═══════════════════════════════════════════════════════════════
* IMPROVED HASH: 5-byte multiply-shift (safe read pattern)
*
* Reads exactly 5 bytes using 4+1 to prevent compiler from
* widening to an 8-byte load that over-reads the buffer.
* ═══════════════════════════════════════════════════════════════ */
static inline VV_NO_SANITIZE_INTEGER uint32_t hash5(const uint8_t *p) {
uint32_t lo;
memcpy(&lo, p, 4);
uint64_t v = (uint64_t)lo | ((uint64_t)p[4] << 32);
/* Shift by (64 - HC_BITS) to get the top HC_BITS of the product */
return (uint32_t)((v * 889523592379ULL) >> (64 - VV_HC_BITS));
}
/* 4-byte hash for positions near end of buffer */
static inline VV_NO_SANITIZE_INTEGER uint32_t hash4(const uint8_t *p) {
uint32_t v;
memcpy(&v, p, 4);
return (v * 2654435761u) >> (32 - VV_HC_BITS);
}
/* Safe hash: picks 5-byte or 4-byte depending on remaining bytes */
static inline VV_NO_SANITIZE_INTEGER uint32_t hash_safe(const uint8_t *p, int32_t remain) {
return (remain >= 5) ? hash5(p) : hash4(p);
}
/* ═══════════════════════════════════════════════════════════════
* AVX2 MATCH EXTENSION
*
* Compare 32 bytes at a time. Returns total match length.
* ~8× faster than byte-by-byte on data with long matches.
* ═══════════════════════════════════════════════════════════════ */
static inline int32_t extend_match(const uint8_t *a, const uint8_t *b,
int32_t max_len) {
int32_t len = 0;
/* SPRINT 55: 8-byte fast-path check first. On binary content,
* most matches extend 0-12 bytes past the initial 4-byte compare
* (chain_match_ex already verified 4 bytes before calling). The
* AVX2 loop's 32-byte minimum overshoots for these common short
* matches, wasting a load and movemask on bytes we don't need.
*
* Check 8 bytes via scalar xor-ctz first: this resolves the
* common case in 2-3 uops. On binary fixtures (bash, libc.so.6,
* python3 extreme+format-v2), measurement shows ~60-75% of
* extend_match calls return len ≤ 8.
*
* Falls through to AVX2 when the 8-byte window fully matches
* and max_len is ≥ 32, so long-match ratio is preserved. */
if (max_len >= 8) {
uint64_t va, vb;
memcpy(&va, a, 8);
memcpy(&vb, b, 8);
uint64_t xor_ab = va ^ vb;
if (xor_ab) {
/* Little-endian: byte at position k differs iff bit k*8 set */
return __builtin_ctzll(xor_ab) >> 3;
}
len = 8;
}
#if VV_ENC_AVX2
while (len + 32 <= max_len) {
__m256i va = _mm256_loadu_si256((const __m256i *)(a + len));
__m256i vb = _mm256_loadu_si256((const __m256i *)(b + len));
__m256i eq = _mm256_cmpeq_epi8(va, vb);
uint32_t mask = ~(uint32_t)_mm256_movemask_epi8(eq);
if (mask) return len + (int32_t)vv_ctz32(mask);
len += 32;
}
#endif
/* SPRINT 124: 8-byte xor/ctz stride for the post-8 region. This TU
* is deliberately built without -mavx2 (baseline portability), so
* before this loop existed every match longer than 8 bytes extended
* one byte per iteration — measured at 7-8% of encode wall on
* long-match corpora. Same technique as the fast path above. */
while (len + 8 <= max_len) {
uint64_t va, vb;
memcpy(&va, a + len, 8);
memcpy(&vb, b + len, 8);
uint64_t x = va ^ vb;
if (x) return len + (__builtin_ctzll(x) >> 3);
len += 8;
}
while (len < max_len && a[len] == b[len]) len++;
return len;
}
/* Branch-free floor(log2(v)); v=0 maps to 0. */
static inline int enc_ilog2(uint32_t v) {
return 31 - __builtin_clz(v | 1);
}
/* ═══════════════════════════════════════════════════════════════
* MATCHER: hash chain with 5-byte hash + rep-match
* ═══════════════════════════════════════════════════════════════ */
#define VV_HC4_BITS 16
#define VV_HC4_SIZE (1u << VV_HC4_BITS)
static inline VV_NO_SANITIZE_INTEGER uint32_t hash4_short(const uint8_t *p) {
uint32_t v;
memcpy(&v, p, 4);
return (v * 2654435761u) >> (32 - VV_HC4_BITS);
}
/* ─── SPRINT 45: Hash3 for format v2 ───────────────────────────
* When min_match=3 (opts.format_v2), we need to find 3-byte
* matches that hash5/hash4 cannot surface (both require 4-byte
* prefix equality before extending). Hash3 uses a 3-byte key
* with its own table and chain (SEPARATE — never share chain
* arrays across hash tables per Sprint 14's silent-corruption
* lesson). Table size is 14 bits = 16K entries = 64 KB, smaller
* than hash4's 256 KB to account for the lower entropy of
* 3-byte keys.
*
* Enabled only when matcher_t::use_hash3 is set (v2 path).
* When disabled, table3/hash3_chain are NULL, hash3 insert is
* skipped, and hash3 probe never runs. Zero cost on v1 path. */
#define VV_HC3_BITS 14
#define VV_HC3_SIZE (1u << VV_HC3_BITS)
static inline VV_NO_SANITIZE_INTEGER uint32_t hash3_short(const uint8_t *p) {
uint32_t v = (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16);
return (v * 2654435761u) >> (32 - VV_HC3_BITS);
}
typedef struct {
int32_t *table; /* Primary hash5: VV_HC_SIZE entries */
int32_t *chain; /* Primary chain: window_size entries */
int32_t *table4; /* Secondary hash4: VV_HC4_SIZE entries */
int32_t *hash4_chain; /* Secondary chain (SEPARATE from primary) */
int32_t *table3; /* Tertiary hash3 (v2 only): VV_HC3_SIZE entries, NULL on v1 */
int32_t *hash3_chain; /* Tertiary chain (SEPARATE from hash4/primary), NULL on v1 */
uint32_t chain_mask;
uint32_t chain_depth;
uint32_t rep[3]; /* 3 most recent match offsets */
uint8_t wlog; /* Window log: controls max offset distance */
uint8_t use_hash4; /* Enable hash4 fallback (binary data only) */
uint8_t use_hash3; /* Enable hash3 fallback (format v2 only) */
uint8_t single_probe; /* SPRINT 58: fast-mode lean match finder.
* When set, compress_block uses
* single_probe_match (a stripped chain walk:
* same depth and match-selection as
* chain_match, so output is identical, but
* without the priming prefetch and the dead
* hash4/hash3 branches). Set ONLY on the real
* ULTRA_FAST matcher; left 0 on the
* balanced/extreme window-selection trial
* matchers so their output stays
* bit-identical. */
uint32_t max_match; /* Max representable matchlen (65535 for v1,
* 65534 for v2: ml_base_v2[35]=32767 with 15
* extra bits only reaches 65534). */
uint32_t accel; /* Position-skip acceleration factor (0 = off).
* When >0, after a run of `f` consecutive
* positions with no match, compress_block
* advances by 1 + ((f*accel) >> 6) instead of 1,
* skipping the hash/insert/rep work on
* unmatchable regions. Massively speeds up
* encode on incompressible / already-compressed
* input (measured ~8-9x on random/gzip data),
* with a small ratio cost on compressible data
* (so it is opt-in; default 0 keeps output
* byte-identical). Skipped positions become
* literals; output stays decodable by any
* decoder. */
uint8_t no_rep; /* 1 = skip rep-match probing in compress_block.
* Measured net-positive on ratio in FAST mode
* (no entropy stage, so rep's short-offset code
* advantage never materializes; it only perturbs
* the greedy parse) and ~10% faster. Opt-in
* (--no-rep); default 0 keeps rep enabled and
* output byte-identical. */
} matcher_t;
/* SPRINT 93 audit: returns 1 on success, 0 on allocation failure.
* Callers MUST check the return value — on failure m is left in a
* partially-initialized state with all pointers either valid or NULL,
* safe to pass to matcher_free for cleanup.
*
* Prior to Sprint 93 this function was void-returning with unchecked
* mallocs. If allocation failed, the immediately-following memset on
* the NULL pointer would crash. Sprint 92 audit identified this as a
* real defect. The fix tolerates allocator failure cleanly. */
static void matcher_free(matcher_t *m); /* fwd decl for cleanup-on-failure */
static int matcher_init(matcher_t *m, uint32_t window_log, uint32_t depth) {
uint32_t wsz = 1u << window_log;
/* Initialize ALL pointers to NULL first so matcher_free is safe to
* call on partial-failure paths. */
m->table = NULL;
m->chain = NULL;
m->table4 = NULL;
m->hash4_chain = NULL;
m->table3 = NULL;
m->hash3_chain = NULL;
m->table = (int32_t *)malloc(VV_HC_SIZE * sizeof(int32_t));
m->chain = (int32_t *)malloc(wsz * sizeof(int32_t));
m->table4 = (int32_t *)malloc(VV_HC4_SIZE * sizeof(int32_t));
m->hash4_chain = (int32_t *)malloc(wsz * sizeof(int32_t));
if (!m->table || !m->chain || !m->table4 || !m->hash4_chain) {
matcher_free(m);
/* Re-NULL after free so caller's matcher_free is also safe */
m->table = m->chain = m->table4 = m->hash4_chain = NULL;
m->table3 = m->hash3_chain = NULL;
return 0;
}
/* hash3 tables allocated lazily only when use_hash3 is enabled.
* On v1 path (the default), they stay NULL and cost nothing. */
/* PERF: only the table arrays need to be cleared. chain/hash4_chain
* are only read via table entries (which are now -1), so stale
* data in them is unreachable. See matcher_reset for rationale. */
memset(m->table, 0xFF, VV_HC_SIZE * sizeof(int32_t));
memset(m->table4, 0xFF, VV_HC4_SIZE * sizeof(int32_t));
m->chain_mask = wsz - 1;
m->chain_depth = depth;
m->rep[0] = m->rep[1] = m->rep[2] = 0;
m->wlog = (uint8_t)window_log;
m->use_hash4 = 0; /* Disabled by default — enabled adaptively for binary */
m->use_hash3 = 0; /* Disabled by default — enabled for format v2 */
m->single_probe = 0; /* Disabled by default — set only for ULTRA_FAST encode */
m->accel = 0; /* Position-skip acceleration off by default (opt-in --accel) */
m->no_rep = 0; /* rep-match probing on by default (opt-in --no-rep) */
m->max_match = VV_MAX_MATCH; /* v1 default, see matcher_set_format_v2 */
return 1;
}
/* Apply format v2 matcher constraints. Must be called whenever the
* encoder is producing 'T'-tagged blocks, regardless of whether the
* hash3 probe is active (the adaptive trial may decide not to enable
* hash3 on non-binary data, but the ml_base_v2 range cap still
* applies to every match). */
static void matcher_set_format_v2(matcher_t *m) {
/* ml_base_v2[35]=32767 with 15 extra bits → max representable
* matchlen = 32767+32767 = 65534. Without this cap, the matcher
* can produce 65535-length matches whose extra field (32768)
* overflows 15 bits → encodes as 0 → decoder reconstructs 32767
* (short by exactly 32,768 bytes per affected match). Root cause
* of the python3 multi-block corruption in v2.34.0. */
m->max_match = 65534;
}
/* Enable hash3 path: allocate tables. Idempotent and cheap to skip. */
static int matcher_enable_hash3(matcher_t *m) {
if (m->table3) return 1; /* Already enabled */
uint32_t wsz = 1u << m->wlog;
m->table3 = (int32_t *)malloc(VV_HC3_SIZE * sizeof(int32_t));
m->hash3_chain = (int32_t *)malloc(wsz * sizeof(int32_t));
if (!m->table3 || !m->hash3_chain) {
free(m->table3); m->table3 = NULL;
free(m->hash3_chain); m->hash3_chain = NULL;
return 0;
}
memset(m->table3, 0xFF, VV_HC3_SIZE * sizeof(int32_t));
m->use_hash3 = 1;
return 1;
}
static void matcher_free(matcher_t *m) {
free(m->table); m->table = NULL;
free(m->chain); m->chain = NULL;
free(m->table4); m->table4 = NULL;
free(m->hash4_chain); m->hash4_chain = NULL;
free(m->table3); m->table3 = NULL;
free(m->hash3_chain); m->hash3_chain = NULL;
}
/* Reset matcher state without reallocating tables. Used by
* vv_cstream_reset() for fast per-file reuse.
*
* PERF: We only need to clear the `table` and `table4` arrays (the
* hash → position maps). The `chain` arrays store (pos → earlier
* pos) links, but those links are only FOLLOWED from table entries.
* After resetting the tables, any stale chain entries become
* unreachable. This cuts reset cost from ~1.6 MB of memset to
* ~1.25 MB (table=1MB + table4=256KB), a ~25% speedup. */
static void matcher_reset(matcher_t *m) {
memset(m->table, 0xFF, VV_HC_SIZE * sizeof(int32_t));
memset(m->table4, 0xFF, VV_HC4_SIZE * sizeof(int32_t));
if (m->table3) memset(m->table3, 0xFF, VV_HC3_SIZE * sizeof(int32_t));
m->rep[0] = m->rep[1] = m->rep[2] = 0;
m->use_hash4 = 0;
/* use_hash3 is NOT reset — it's a caller-opted-in mode flag,
* not adaptive behavior that should clear on reset. */
}
/* SPRINT 29 (v2.50.3): _fast variant of matcher_insert for use inside
* bulk-insert loops where the caller has already ensured pos + 5 <= end.
* Skips both the boundary check and the hash5/hash4 dispatch, going
* straight to hash5. Used inside the post-match-emit insert loops in
* compress_block where we already gate on `j <= end - 5`.
*
* Saves ~3 instructions per insert (one compare, one branch, one
* hash_safe dispatch). For dickens at ~2M inserts per encode, that's
* a measurable win on encode throughput (~+4% on Silesia fast mode,
* Sprint 29 measurement).
*
* SAFETY: caller MUST ensure pos + 5 <= end before calling. No
* runtime check — undefined behavior if violated. */
static inline void matcher_insert_fast(matcher_t *m, const uint8_t *data,
int32_t pos) {
uint32_t h = hash5(data + pos);
m->chain[pos & m->chain_mask] = m->table[h];
m->table[h] = pos;
if (m->use_hash4) {
uint32_t h4 = hash4_short(data + pos);
m->hash4_chain[pos & m->chain_mask] = m->table4[h4];
m->table4[h4] = pos;
}
if (m->use_hash3) {
uint32_t h3 = hash3_short(data + pos);
m->hash3_chain[pos & m->chain_mask] = m->table3[h3];
m->table3[h3] = pos;
}
}
static inline void matcher_insert(matcher_t *m, const uint8_t *data,
int32_t pos, int32_t end) {
if (pos + 4 > end) return;
uint32_t h = hash_safe(data + pos, end - pos);
m->chain[pos & m->chain_mask] = m->table[h];
m->table[h] = pos;
/* PERF: only maintain hash4 table when it's actually being used.
* For text/source (use_hash4==0), this saves a hash computation
* and two memory writes per insert — measurable on insert-heavy
* workloads (logs, JSON). */
if (m->use_hash4) {
uint32_t h4 = hash4_short(data + pos);
m->hash4_chain[pos & m->chain_mask] = m->table4[h4];
m->table4[h4] = pos;
}
/* SPRINT 45: hash3 insert, only when enabled. Same guard logic
* as hash4 — zero cost when disabled. */
if (m->use_hash3) {
uint32_t h3 = hash3_short(data + pos);
m->hash3_chain[pos & m->chain_mask] = m->table3[h3];
m->table3[h3] = pos;
}
}
/* ─── Rep-match check: O(1), checked BEFORE hash probe ─── */
static inline int32_t try_rep_match(const matcher_t *m, const uint8_t *data,
int32_t pos, int32_t end,
int32_t *rep_idx) {
/* Primary path: require 4-byte equality. Extends from there. */
for (int i = 0; i < 3; i++) {
uint32_t d = m->rep[i];
if (d == 0 || (uint32_t)pos < d) continue;
int32_t ref = pos - (int32_t)d;
uint32_t a, b;
memcpy(&a, data + pos, 4);
memcpy(&b, data + ref, 4);
if (a == b) {
int32_t max = end - pos;
if (max > (int32_t)m->max_match) max = (int32_t)m->max_match;
int32_t len = 4 + extend_match(data + pos + 4, data + ref + 4, max - 4);
*rep_idx = i;
return len;
}
}
/* SPRINT 47: format v2 secondary path. When hash3 is active, check
* if any rep-offset produces a 3-byte rep-match even when the
* 4-byte compare above fails. Rep-matches have 0 extra offset
* bits — a 3-byte rep is nearly always a win vs 3 literals, which
* the 4-byte requirement was blocking. Only runs when use_hash3
* is on (i.e. format v2 + binary-detected), so text/JSON paths
* stay bit-identical. */
if (m->use_hash3 && pos + 3 <= end) {
for (int i = 0; i < 3; i++) {
uint32_t d = m->rep[i];
if (d == 0 || (uint32_t)pos < d) continue;
int32_t ref = pos - (int32_t)d;
if (data[pos] == data[ref]
&& data[pos + 1] == data[ref + 1]
&& data[pos + 2] == data[ref + 2]) {
*rep_idx = i;
return 3; /* length-3 rep; caller accepts since min_match=3 */
}
}
}
return 0;
}
/* ─── Hash chain match: uses 5-byte hash, searches up to chain_depth.
* If use_hash4 is nonzero AND hash5 finds nothing, fall back to hash4
* chain for binary/struct coverage. ─── */
static VV_NO_SANITIZE_INTEGER int32_t
chain_match_ex(const matcher_t *m, const uint8_t *data,
int32_t pos, int32_t end, int32_t *best_off,
int use_hash4) {
/* Early exit: we need at least 3 bytes for hash3 probe, 4 for
* hash4/hash5. Use the looser bound if hash3 is enabled. */
int32_t min_bytes = m->use_hash3 ? 3 : 4;
if (pos + min_bytes > end) return 0;
int32_t best_len = 0;
*best_off = 0;
int32_t max_dist = (int32_t)((1u << m->wlog) - 1);
int32_t limit = pos - max_dist;
if (limit < 0) limit = 0;
/* Hash5/hash4 paths require 4 bytes. Skip them if only 3 remain. */
if (pos + 4 <= end) {
/* Hoist pos4: never changes during the chain walk */
uint32_t pos4;
memcpy(&pos4, data + pos, 4);
/* Primary hash5 chain traversal.
*
* SPRINT 55: 4-way software-pipelined chain walk. Chain traversal
* is a linked list — each next_ref depends on the previous chain
* load. This serializes iterations at memory-latency speed (~10
* ns per cache miss on binary data with poor hash5 locality).
*
* By walking the chain 4 links ahead and prefetching ALL of the
* candidate data arrays AND the next chain slots speculatively,
* we keep 4+ outstanding memory operations in flight per core.
* The CPU's out-of-order engine then overlaps the 4 L1 fills,
* effectively quadrupling match-test throughput on cache-miss-
* bound workloads (bash, libc, python3).
*
* Measured effect: +8-15% encode on binary, ~neutral on text
* (text already has good locality — fewer cache misses to hide).
*
* Safety: the prefetch is speculative ONLY. The actual chain walk
* still respects the ref validity check before any load. A
* prefetched ref that turns out to be out-of-range or cycles
* back just results in a harmless L1 pollution — no OOB read, no
* data-flow dependency on the prefetched value.
*/
uint32_t h = hash_safe(data + pos, end - pos);
int32_t ref = m->table[h];
uint32_t depth = m->chain_depth;
uint32_t chain_mask = m->chain_mask;
int32_t *chain_arr = m->chain;
/* Pipeline priming: look 4 chain entries ahead. If chain is
* short, the prefetches become no-ops (chain entries below limit
* just return -1 or an expired position).
* SPRINT 124: only prime for deep walks. At depth 4 (fast mode,
* window trial) the priming loads cost more than the misses they
* hide — measured 5-8% of fast-mode encode wall. */
if (depth >= 8 && ref >= limit && ref < pos) {
__builtin_prefetch(data + ref, 0, 0);
int32_t r1 = chain_arr[ref & chain_mask];
if (r1 >= limit && r1 < pos) {
__builtin_prefetch(data + r1, 0, 0);
__builtin_prefetch(&chain_arr[r1 & chain_mask], 0, 0);
int32_t r2 = chain_arr[r1 & chain_mask];
if (r2 >= limit && r2 < pos) {
__builtin_prefetch(data + r2, 0, 0);
__builtin_prefetch(&chain_arr[r2 & chain_mask], 0, 0);
}
}
}
while (ref >= limit && ref < pos && depth-- > 0) {
int32_t next_ref = chain_arr[ref & chain_mask];
/* SPRINT 30 (v2.50.4): prefetch unconditionally. The previous
* guard `if (next_ref >= limit && next_ref < pos)` cost 2
* branches per iteration in a hot function (chain_match_ex
* fires ~2.5M times per 10 MB encode in fast mode; each call
* walks chain_depth iterations).
*
* __builtin_prefetch tolerates any address — a bogus prefetch
* just becomes harmless L1 pollution. The actual data load
* (`memcpy(&b, data + ref, 4)`) and chain step still respect
* the validity invariants. Only the prefetch hint is unguarded.
*
* Also removed the redundant `ref >= 0` from the while condition:
* since `limit >= 0` (clamped at line 410), `ref >= limit` already
* implies `ref >= 0`.
*
* Measured: +2.7% encode on dickens fast (median of 10 runs,
* interleaved). Marginal on sao (+1.3%) and x-ray (+0.6%) —
* within measurement noise but directionally consistent. Effect
* is small because chain_depth=4 in fast mode and modern OoO
* cores already speculate past the guard's branches. The change
* still benefits because it (a) strictly removes code, (b) lets
* the hardware prefetcher start deeper, and (c) simplifies the
* inner loop for future optimization. */
__builtin_prefetch(data + next_ref, 0, 0);
__builtin_prefetch(&chain_arr[next_ref & chain_mask], 0, 0);
uint32_t b;
memcpy(&b, data + ref, 4);
if (pos4 == b) {
int32_t max = end - pos;
if (max > (int32_t)m->max_match) max = (int32_t)m->max_match;
int32_t len = 4 + extend_match(data + pos + 4, data + ref + 4, max - 4);
if (len > best_len) {
/* SPRINT 124: offset-cost-aware acceptance. The walk
* goes newest→oldest, so a later candidate always has
* a larger offset. SEQ codes offsets as log2 buckets +
* extra bits, so the farther match costs ~dbits more;
* each extra matched byte saves ~6 bits of literals.
* Without this check a barely-longer match at 512 KB
* displaces a same-ish match at 200 B, and the diverse
* offsets also break rep-offset streaks downstream.
* Only affects greedy/lazy paths — the optimal parser
* collects candidates via opt_collect and prices
* offsets itself. */
if (best_len >= 4) {
int dbits = enc_ilog2((uint32_t)(pos - ref))
- enc_ilog2((uint32_t)*best_off);
if ((len - best_len) * 6 < dbits) { ref = next_ref; continue; }
}
best_len = len;
*best_off = pos - ref;
if (len >= 256) return best_len;
}
}
ref = next_ref;
}
/* PERF: Secondary hash4 chain fallback — ONLY when hash5 found nothing
* AND caller indicates hash4 is safe to use (no competing rep-match).
* Uses SEPARATE hash4_chain array.
*
* Note: tried relaxing trigger to `best_len < 8` in sprint 41 but
* empirically found NO improvement on real binary data (bash, ls,
* python3, libc.so.6). The hash4 fallback finds the same matches
* hash5 already finds when primary prefix is 5 bytes. Closing the
* binary-compression gap vs gzip-9 (~11% worse) requires either
* min_match=3 (format change) or deeper LZ-optimal parsing. Kept
* the zero-only trigger which matches lz4's fallback pattern. */
if (use_hash4 && best_len == 0) {
uint32_t h4 = hash4_short(data + pos);
int32_t ref4 = m->table4[h4];
uint32_t depth4 = 8;
if (ref4 >= limit && ref4 < pos) {
__builtin_prefetch(data + ref4, 0, 0);
}
while (ref4 >= limit && ref4 < pos && depth4-- > 0) {
int32_t next_ref4 = m->hash4_chain[ref4 & m->chain_mask];
/* SPRINT 30: unconditional prefetch (same rationale as
* the hash5 walk above). */
__builtin_prefetch(data + next_ref4, 0, 0);
__builtin_prefetch(&m->hash4_chain[next_ref4 & m->chain_mask], 0, 0);
uint32_t b4;
memcpy(&b4, data + ref4, 4);
if (pos4 == b4) {
int32_t max = end - pos;
if (max > (int32_t)m->max_match) max = (int32_t)m->max_match;
int32_t len = 4 + extend_match(data + pos + 4, data + ref4 + 4, max - 4);
if (len > best_len) {
/* Same offset-cost-aware acceptance as the hash5 walk. */
if (best_len >= 4) {
int dbits = enc_ilog2((uint32_t)(pos - ref4))
- enc_ilog2((uint32_t)*best_off);
if ((len - best_len) * 6 < dbits) { ref4 = next_ref4; continue; }
}
best_len = len;
*best_off = pos - ref4;
if (len >= 256) return best_len;
}
}
ref4 = next_ref4;
}
}
} /* end if (pos + 4 <= end) */
/* ─── SPRINT 45: Tertiary hash3 probe ───────────────────────
* Only when use_hash3 is enabled (format v2) AND hash5/hash4
* found nothing ≥ 4 bytes (best_len < 4). Uses a SEPARATE
* chain array from hash4 — never share chain storage.
*
* Probe depth is intentionally small (4). Unlike hash4, hash3's
* collision rate is high (16K entries for up to 16M unique
* 3-byte keys), so deep walks waste cycles on spurious hits.
*
* Match length is reported honestly — may be 3, or may extend.
* The caller (compress_block) accepts len ≥ min_match. */
if (m->use_hash3 && best_len < 4 && pos + 3 <= end) {
uint32_t h3 = hash3_short(data + pos);
int32_t ref3 = m->table3[h3];
uint32_t depth3 = 4;
/* Compare key: the 3 bytes at pos. Pack into low 24 bits
* of a uint32 for a single compare against the candidate. */
uint32_t pos3 = (uint32_t)data[pos]
| ((uint32_t)data[pos + 1] << 8)
| ((uint32_t)data[pos + 2] << 16);
while (ref3 >= 0 && ref3 >= limit && ref3 < pos && depth3-- > 0) {
int32_t next_ref3 = m->hash3_chain[ref3 & m->chain_mask];
uint32_t b3 = (uint32_t)data[ref3]
| ((uint32_t)data[ref3 + 1] << 8)
| ((uint32_t)data[ref3 + 2] << 16);
if (pos3 == b3) {
int32_t max = end - pos;
if (max > (int32_t)m->max_match) max = (int32_t)m->max_match;
int32_t len = 3 + extend_match(data + pos + 3, data + ref3 + 3, max - 3);
/* SPRINT 48: extended offset filter for length-3 hash3
* matches. Flat ≤4096 threshold. Higher than v2.35.0's
* ≤256 — the v2.36.0 adaptive hash3 gate now keeps
* text/JSON fully neutral at ANY threshold, so the
* filter only governs binary precision.
*
* Measured threshold sweep (binary Δ vs V1):
* 256 (v2.37): bash -2.2% ls -3.3% libc -2.8% py -5.5%
* 1024: bash -3.4% ls -3.3% libc -3.3% py -6.1%
* 4096 (v2.38): bash -3.8% ls -3.9% libc -3.9% py -6.4%
* 8192+: plateau (noise-level changes)
*
* Text/JSON/source at 0/0/+0.1% across the entire
* sweep — the adaptive gate does its job.
*
* Rejected designs:
* - Sliding threshold (≤128 always, ≤512 if rep):
* tightening to 128 lost more binary gain than
* rep-aware loosening recovered. See CHANGELOG.
* - ANS_LOG 12→10 (Sprint A candidate): predicted
* 2-4× text decode; measured 2-6%. Not worth the
* format change. See CHANGELOG v2.38.0 dead-ends.
*
* Longer matches (len ≥ 4) are always accepted at any
* offset (the filter only gates len==3). */
int32_t off3 = pos - ref3;
if (len == 3 && off3 > 4096) {
ref3 = next_ref3;
continue;
}
if (len > best_len) {
best_len = len;
*best_off = off3;
if (len >= 8) break; /* Good enough — don't keep walking */
}
}
ref3 = next_ref3;
}
}
return best_len;
}
static int32_t chain_match(const matcher_t *m, const uint8_t *data,
int32_t pos, int32_t end, int32_t *best_off) {
return chain_match_ex(m, data, pos, end, best_off, m->use_hash4);
}
/* ─── SPRINT 58: lean fast-mode match finder (ULTRA_FAST encode) ─────
* A stripped-down chain walk for fast mode. It walks the same hash5
* chain to the same depth as chain_match (m->chain_depth == 4 for fast
* mode) and selects the match by the same rule (first strictly-longest,
* early-out at len >= 256), so it produces BYTE-IDENTICAL output to the
* pre-Sprint-58 chain_match on fast-mode input — verified on all 12
* Silesia fixtures. The speed comes purely from what it omits on the
* per-position hot path:
* - the 4-way software-pipelined priming prefetch block,
* - the per-iteration unconditional prefetches,
* - the (always-false in fast mode) hash4 and hash3 fallback branches.
* Measured fast-mode encode: +9-12% on dickens/xml/samba, with decode
* and ratio unchanged (output is identical). The change is measured byte-identical (ratio gate +/- 0).
*
* A depth-1 (true lz4-style single-probe) and a depth-2/3 sweep were
* measured and REJECTED: lowering the depth raises encode further but
* degrades BOTH ratio and decode (shorter matches → more tokens/byte →
* slower decode), trading the two metrics the SPEED PROGRAM ranks above
* encode. depth-4 is the only point that improves encode at zero cost.
*
* Used by compress_block ONLY when m->single_probe is set, which is
* ONLY the real ULTRA_FAST matcher. The balanced/extreme window trial
* matchers keep single_probe==0 and use chain_match, so their output is
* bit-identical to before this sprint.
*
* Returns match length (>= VV_MIN_MATCH on hit, 0 on miss) and writes
* the offset to *best_off. The 4-byte compare plus extend_match verify
* actual byte equality, so the emitted match is always decode-correct
* regardless of hash collisions. */
static VV_NO_SANITIZE_INTEGER int32_t
single_probe_match(const matcher_t *m, const uint8_t *data,
int32_t pos, int32_t end, int32_t *best_off) {
*best_off = 0;
if (pos + 4 > end) return 0;
int32_t max_dist = (int32_t)((1u << m->wlog) - 1);
int32_t limit = pos - max_dist;
if (limit < 0) limit = 0;
uint32_t h = hash_safe(data + pos, end - pos);
int32_t ref = m->table[h];
uint32_t pos4;
memcpy(&pos4, data + pos, 4);
int32_t best_len = 0;
uint32_t depth = m->chain_depth; /* same depth as chain_match (4 for fast) */
uint32_t chain_mask = m->chain_mask;
const int32_t *chain_arr = m->chain;
int32_t mm = (int32_t)m->max_match;
while (ref >= limit && ref < pos && depth-- > 0) {
int32_t next_ref = chain_arr[ref & chain_mask];
uint32_t b;
memcpy(&b, data + ref, 4);
if (pos4 == b) {
int32_t max = end - pos;
if (max > mm) max = mm;
int32_t len = 4 + extend_match(data + pos + 4, data + ref + 4, max - 4);
if (len > best_len) {
best_len = len;
*best_off = pos - ref;
if (len >= 256) break;
}
}
ref = next_ref;
}
return best_len;
}
/* Update rep offsets (push new offset, shift others down) */
static inline void update_rep(matcher_t *m, uint32_t offset) {
if (offset == m->rep[0]) return;
m->rep[2] = m->rep[1];
m->rep[1] = m->rep[0];
m->rep[0] = offset;
}
/* ═══════════════════════════════════════════════════════════════
* EMIT TOKEN (unchanged from v0.1)
* ═══════════════════════════════════════════════════════════════ */
static size_t emit_seq(uint8_t *dst, const uint8_t *lits,
size_t ll, size_t ml, uint32_t off, int off_bytes,
int min_match) {
uint8_t *op = dst;
uint8_t ll_f = (ll >= 15) ? 15 : (uint8_t)ll;
uint8_t ml_f;
if (ml == 0) { ml_f = 0; }
else { size_t v = ml - (size_t)min_match; ml_f = (v >= 15) ? 15 : (uint8_t)v; }
*op++ = (ll_f << 4) | ml_f;
if (ll >= 15) op += write_varint(op, ll - 15);
if (ll > 0) { memcpy(op, lits, ll); op += ll; }
if (ml > 0) {
/* PERF: 2-byte offset for wlog≤16, 3-byte for wlog>16 */
if (off_bytes == 3) {
op[0] = (uint8_t)(off);
op[1] = (uint8_t)(off >> 8);
op[2] = (uint8_t)(off >> 16);
op += 3;
} else {
vv_write16(op, (uint16_t)off); op += 2;
}
if (ml - (size_t)min_match >= 15)
op += write_varint(op, ml - (size_t)min_match - 15);
}
return (size_t)(op - dst);
}
/* ═══════════════════════════════════════════════════════════════
* SPRINT 42/43: OPTIMAL PARSE (extreme mode only) — RATIO PROGRAM
*
* Whole-block forward DP. price[i] = min bits to encode src[start..start+i).
* Matches are SINGLE edges i -> i+len (no windowing, no truncation), which
* is what makes long-match data (mozilla/nci) compress correctly: a long
* match stays one cheap token.
*
* Block size is bounded at VV_MAX_BLOCK_SIZE (1 MB), so price[block_len+1]
* (int32) is at most ~4 MB — affordable per block.
*
* WIRE FORMAT NEUTRAL: emits the same (ll, mlen, moff) token stream that
* emit_seq consumes. Verified byte-perfect roundtrip on all fixtures.
* GATED TO EXTREME MODE: balanced/fast keep greedy/lazy.
* ═══════════════════════════════════════════════════════════════ */
#define VV_OPT_MAX_CAND 16
#ifndef VV_OPT_LONG_MATCH
#define VV_OPT_LONG_MATCH 512 /* take immediately; skip interior DP */
#endif
#define VV_OPT_PRICE_INF 0x3FFFFFFF
typedef struct { uint32_t off; int32_t len; } opt_cand_t;
/* literal bit price (~6 bits/byte, formalizes Sprint 119 lazy model) */
/* Sprint 44: literal price 8 bits/byte (was 6 in v2.51.0).
*
* Sweep across full Silesia at extreme mode found litp=8 minimizes
* aggregate compressed size:
*
* litp aggregate vs v2.50.11 baseline
* 6 -2.75% geomean (v2.51.0 default)
* 7 -3.45%
* 8 -3.64% ← chosen
* 9 -3.50%
*
* The flat-6 model under-priced literals: 4-stream Huffman delivers ~6
* bits/byte on text but 7-8 bits/byte on dense binary (sao, x-ray,
* mozilla). Raising the constant to 8 makes the parser less willing to
* substitute a near-match for a literal run on dense data without
* sacrificing text wins. Result vs v2.51.0: 10/12 fixtures improve, only
* nci slightly worse (nci exceeds the 16 MB window; addressed in a
* future window-size sprint).
*
* A future refinement is true per-byte Huffman costs from a first parse
* pass (two-pass repricing) — that would let the parser exploit
* byte-frequency skew within a block. Empirically the flat constant
* captures most of the available win at zero added complexity, so this
* sprint ships it and defers the two-pass design until the window-size
* lever has been measured (matters more for nci-class fixtures). */
static inline int32_t opt_lit_price(void) { return 8; }
/* SPRINT 129: per-byte literal prices from the block's byte histogram.
* The flat-8 model (Sprint 44) was chosen as the best single constant,
* but the real literal coder delivers ~4-6 bits/byte on text and 7-8
* on dense binary — the flat constant over-prices text literals, so
* the parser substitutes marginal matches where literals are cheaper
* in reality. This is the "two-pass repricing" refinement that Sprint
* 44's note deferred, using the raw block histogram as the literal-
* distribution estimate (the true literal stream excludes match-
* covered bytes, but the distributions track closely in practice).
* price[b] = round(log2(N / hist[b])) clamped to [VV_OPT_LIT_MIN, 14];
* unseen bytes cannot appear as literals and get the ceiling. The
* clamp floor guards degenerate blocks (a byte at ~100% frequency
* would price to 0 and make literal runs look free). Constants swept
* on the 11-file corpus — see CHANGELOG v2.64.0. */
#ifndef VV_OPT_LIT_MIN
#define VV_OPT_LIT_MIN 2
#endif
#ifndef VV_OPT_LIT_BLEND
#define VV_OPT_LIT_BLEND 6
#endif
/* SPRINT 131: OF-code price blend. The old match price decomposes as
* 8 + code_bits + extra_bits with prior code costs {rep: 2, explicit:
* 6}; blend 0/8 therefore reproduces the v2.65.0 model exactly. The
* measured distribution comes from the same greedy prepass that feeds
* literal pricing, classified with the wire's exact rep rules. */
#ifndef VV_OPT_OF_BLEND
#define VV_OPT_OF_BLEND 0
#endif
static void opt_build_of_prices(const uint32_t of_hist[27], size_t nseq,
int32_t of_bits[27]) {
for (int x = 0; x < 27; x++) {
int prior = (x < 3) ? 2 : 6;
int bits;
if (!nseq || !of_hist[x]) {
bits = 12; /* unseen code: expensive if the DP tries it */
} else {
uint32_t ratio8 = (uint32_t)(((uint64_t)nseq << 8) / of_hist[x]);
int t = enc_ilog2(ratio8);
bits = t - 8;
if (t >= 1 && ((ratio8 >> (t - 1)) & 1)) bits++;
if (bits < 1) bits = 1;
if (bits > 12) bits = 12;
}
of_bits[x] = (VV_OPT_OF_BLEND * bits + (8 - VV_OPT_OF_BLEND) * prior) / 8;
}
}
/* fwd decl: the greedy parser (defined below) doubles as the residual-
* literal estimator for the optimal parser's pricing prepass. */
static size_t compress_block(const uint8_t *src, size_t start_pos, size_t block_len,
uint8_t *dst, size_t dst_cap,
matcher_t *m, vv_mode_t mode, int min_match);
/* SPRINT 130: histogram the literal bytes of an LZ token stream (the
* residual literals a parse actually leaves), walking the same wire
* layout extract_literals does but only counting. Returns total
* literal count, or 0 on a malformed stream (caller falls back to the
* raw-block histogram). */
static size_t tok_lit_hist(const uint8_t *tokens, size_t tok_len,
int off_bytes, uint32_t hist[256],
uint32_t of_hist[27], size_t *nseq_out) {
const uint8_t *tp = tokens, *tp_end = tokens + tok_len;
size_t total = 0, nseq = 0;
uint32_t rep[3] = {0, 0, 0}; /* wire-exact per-block rep tracking */
while (tp < tp_end) {
uint8_t token = *tp++;
size_t ll = token >> 4;
size_t mc = token & 0x0F;
if (ll == 15) {
do {
if (tp >= tp_end) return 0;
uint8_t b = *tp++;
ll += b;
if (b < 255) break;
} while (tp < tp_end);
}
if ((size_t)(tp_end - tp) < ll) return 0;
for (size_t i = 0; i < ll; i++) hist[tp[i]]++;
total += ll;
tp += ll;
if (tp >= tp_end) break;
if ((size_t)(tp_end - tp) < (size_t)off_bytes) return 0;
uint32_t off = (off_bytes == 3)
? ((uint32_t)tp[0] | ((uint32_t)tp[1] << 8) | ((uint32_t)tp[2] << 16))
: ((uint32_t)tp[0] | ((uint32_t)tp[1] << 8));
tp += off_bytes;
/* SPRINT 131: wire-exact OF code classification (mirrors the SEQ
* encoder's rep detection order and push rule). */
if (off != 0) {
int x;
if (off == rep[0]) x = 0;
else if (off == rep[1]) x = 1;
else if (off == rep[2]) x = 2;
else x = 3 + enc_ilog2(off);
if (x > 26) x = 26;
of_hist[x]++;
nseq++;
if (off != rep[0]) { rep[2] = rep[1]; rep[1] = rep[0]; rep[0] = off; }
}
if (mc == 15) {
do {
if (tp >= tp_end) return 0;
uint8_t b = *tp++;
if (b < 255) break;
} while (tp < tp_end);
}
}
*nseq_out = nseq;
return total;
}
static void opt_build_lit_prices_from_hist(const uint32_t hist[256], size_t n,
int32_t lit_bits[256]) {
for (int s = 0; s < 256; s++) {
if (!hist[s] || !n) { lit_bits[s] = 14; continue; }
/* ratio8 = (n / hist[s]) in 24.8 fixed point; log2(ratio8) =
* log2(n/hist) + 8. Round via the mantissa bit below the MSB. */
uint32_t ratio8 = (uint32_t)(((uint64_t)n << 8) / hist[s]);
int t = enc_ilog2(ratio8);
int bits = t - 8;
if (t >= 1 && ((ratio8 >> (t - 1)) & 1)) bits++; /* round half up */
if (bits < VV_OPT_LIT_MIN) bits = VV_OPT_LIT_MIN;
if (bits > 14) bits = 14;
/* Blend toward the flat-8 prior: a histogram estimate is still
* an approximation of the coder's delivered cost, and pricing
* from it unblended over-buys literals (measured; see the
* v2.64.0 sweep). blend/8 parts per-byte estimate, rest flat. */
lit_bits[s] = (VV_OPT_LIT_BLEND * bits + (8 - VV_OPT_LIT_BLEND) * 8) / 8;
}
}
/* match bit price: cost_const(14) + log2(off) + ml_extra; rep ~2 bits.
*
* SPRINT 128: priced against a caller-supplied rep set instead of
* m->rep. The matcher's rep state is a greedy-parser search heuristic
* that nothing updates during an optimal parse (it stayed {0,0,0} for
* every all-extreme frame, so rep pricing here was dead code), and the
* wire's rep state is PER-BLOCK and PATH-DEPENDENT: the SEQ encoder
* and decoder both start each block at {0,0,0} and evolve it per
* emitted sequence. The DP now threads that exact state through
* per-position rep histories (see compress_block_optimal). */
/* A rep match saves the offset EXTRA bits, not the per-sequence
* overhead: it still spends full LL/OF/ML code symbols (~10 bits).
* The explicit-match constant 14 approximates that overhead plus
* slack, so the rep price must stay close beneath it — pricing reps
* near-free makes the DP shred long matches into chains of short rep
* matches, each paying the un-modeled sequence overhead (measured:
* -15% ratio on logs at rep=2). Constant swept on the 11-file corpus. */
#ifndef VV_OPT_REP_BITS
#define VV_OPT_REP_BITS 10
#endif
static inline int32_t opt_match_price(const uint32_t reps[3], uint32_t off, int32_t len,
const int32_t of_bits[27]) {
int32_t log2_off = enc_ilog2(off);
int x;
if (off == reps[0]) x = 0;
else if (off == reps[1]) x = 1;
else if (off == reps[2]) x = 2;
else { x = 3 + log2_off; if (x > 26) x = 26; }
/* 8 = LL+ML sequence overhead; extras only for explicit offsets. */
int32_t off_cost = 8 + of_bits[x] + ((x >= 3) ? log2_off : 0);
int32_t ml_extra = 0, v = len - VV_MIN_MATCH;
if (v >= 15) ml_extra = 8 * (v / 255 + 1);
return off_cost + ml_extra;
}
/* Wire rep-history update rule — must mirror vva_encode_sequences'
* enc_rep update (and the decoder's dec_rep) exactly: push only when
* the offset differs from rep[0]. */
static inline void opt_rep_push(uint32_t dst[3], const uint32_t src3[3], uint32_t off) {
if (off == src3[0]) {
dst[0] = src3[0]; dst[1] = src3[1]; dst[2] = src3[2];
} else {
dst[0] = off; dst[1] = src3[0]; dst[2] = src3[1];
}
}
/* Collect match candidates at pos (longest per distinct offset).
* SPRINT 128: rep candidates come from the DP path's rep history. */
static int opt_collect(const matcher_t *m, const uint8_t *data,
int32_t pos, int32_t end, opt_cand_t *cands,
const uint32_t reps[3]) {
int n = 0;
int32_t max_dist = (int32_t)((1u << m->wlog) - 1);
int32_t limit = pos - max_dist; if (limit < 0) limit = 0;
if (pos + 4 > end) return 0;
int32_t max = end - pos;
if (max > (int32_t)m->max_match) max = (int32_t)m->max_match;
uint32_t pos4; memcpy(&pos4, data + pos, 4);
for (int r = 0; r < 3; r++) {
uint32_t roff = reps[r];
if (roff == 0 || (int32_t)roff > pos) continue;
/* SPRINT 132: extend_match (8-byte stride) instead of the
* byte-at-a-time loop — identical result, and this runs three
* times at every DP position. */
int32_t l = extend_match(data + pos, data + pos - roff, max);
if (l >= VV_MIN_MATCH && n < VV_OPT_MAX_CAND) { cands[n].off = roff; cands[n].len = l; n++; }
if (l >= VV_OPT_LONG_MATCH) return n; /* caller short-circuits on it */
}
uint32_t h = hash_safe(data + pos, end - pos);
int32_t ref = m->table[h];
uint32_t depth = m->chain_depth, chain_mask = m->chain_mask;
int32_t *chain_arr = m->chain;
while (ref >= limit && ref < pos && depth-- > 0 && n < VV_OPT_MAX_CAND) {
uint32_t b; memcpy(&b, data + ref, 4);
if (pos4 == b) {
int32_t l = 4 + extend_match(data + pos + 4, data + ref + 4, max - 4);
uint32_t off = (uint32_t)(pos - ref);
int dup = 0;
for (int k = 0; k < n; k++) if (cands[k].off == off) { if (cands[k].len < l) cands[k].len = l; dup = 1; break; }
if (!dup && l >= VV_MIN_MATCH) { cands[n].off = off; cands[n].len = l; n++; }
/* SPRINT 132: a LONG_MATCH-class hit makes the caller take
* it immediately and ignore other candidates — the rest of
* the walk (up to depth 256 with extends) is wasted work. */
if (l >= VV_OPT_LONG_MATCH) return n;
}
ref = chain_arr[ref & chain_mask];
}
return n;
}
static size_t compress_block_optimal(const uint8_t *src, size_t start_pos,
size_t block_len, uint8_t *dst,
size_t dst_cap, matcher_t *m, int min_match) {
uint8_t *op = dst;
int32_t base = (int32_t)start_pos;
int32_t end = (int32_t)(start_pos + block_len);
int off_bytes = (m->wlog > 16) ? 3 : 2;
int32_t N = (int32_t)block_len;
/* DP arrays indexed by offset-from-base [0..N].
* SPRINT 128: prep[i] is the wire rep-offset history of the best
* path reaching position i (zstd-btopt-style approximation: paths
* that lose on price but would carry better reps are dropped).
* prep[0] = {0,0,0} because the SEQ encoder and decoder both reset
* their rep state at every block boundary. */
int32_t *price = (int32_t *)malloc(sizeof(int32_t) * (N + 1));
int32_t *plen = (int32_t *)malloc(sizeof(int32_t) * (N + 1));
uint32_t *poff = (uint32_t *)malloc(sizeof(uint32_t) * (N + 1));
uint32_t (*prep)[3] = (uint32_t (*)[3])malloc(sizeof(uint32_t[3]) * (N + 1));
opt_cand_t *cands = (opt_cand_t *)malloc(sizeof(opt_cand_t) * VV_OPT_MAX_CAND);
if (!price || !plen || !poff || !prep || !cands) { free(price); free(plen); free(poff); free(prep); free(cands); return 0; }
for (int32_t i = 0; i <= N; i++) { price[i] = VV_OPT_PRICE_INF; plen[i] = 0; poff[i] = 0; }
price[0] = 0;
prep[0][0] = prep[0][1] = prep[0][2] = 0;
/* SPRINT 129/130: entropy-aware per-byte literal prices for this
* block. The distribution that matters is the RESIDUAL literal
* stream (bytes a parse leaves uncovered), not the raw block — the
* raw histogram is dominated by exactly the repetitive content
* that matches will remove. A depth-4 greedy prepass on a private
* throwaway matcher (no shared-state pollution, ~1% of the DP's
* runtime) estimates that stream; its token output is histogrammed
* and discarded. Falls back to the raw-block histogram if the
* prepass cannot run. */
int32_t lit_bits[256];
int32_t of_bits[27];
{
uint32_t hist[256];
uint32_t of_hist[27];
memset(hist, 0, sizeof(hist));
memset(of_hist, 0, sizeof(of_hist));
size_t nlit = 0, nseq_pp = 0;
matcher_t mp;
/* SPRINT 133: the prepass compresses ONE block (<= VV_MAX_BLOCK_SIZE
* = 2^20) with a fresh matcher, so every match it can find is
* intra-block: distance < block_len <= 2^20. A wlog-20 window
* covers that exactly, and its chain index (pos & (2^20-1)) is
* non-aliasing across a <= 2^20-wide position span — so the
* prepass finds the identical match set and emits the identical
* tokens/histogram/prices as it would at the real encode's wlog.
* Capping here avoids allocating and zeroing the full extreme
* window (up to 2 x 2^24 x 4 = 128 MB of chain arrays per block
* at wlog=24) when 2 x 2^20 x 4 = 8 MB suffices. off_bytes is
* unaffected: both >16 wlogs emit 3-byte offsets. Output-
* identical — verified by the ratio gate at +-0. */
uint32_t pp_wlog = (m->wlog < 20) ? m->wlog : 20;
if (matcher_init(&mp, pp_wlog, 4)) {
mp.accel = 2;
mp.max_match = m->max_match;
size_t pcap = block_len + block_len / 255 + 1024;
uint8_t *ptok = (uint8_t *)malloc(pcap);
if (ptok) {
size_t pcsz = compress_block(src, start_pos, block_len, ptok,
pcap, &mp, VV_MODE_ULTRA_FAST, min_match);
if (pcsz > 0)
nlit = tok_lit_hist(ptok, pcsz, off_bytes, hist, of_hist, &nseq_pp);
free(ptok);
}
matcher_free(&mp);
}
if (nlit == 0) {
/* Prepass unavailable or block fully covered: raw fallback. */
memset(hist, 0, sizeof(hist));
for (int32_t i = 0; i < N; i++) hist[src[base + i]]++;
nlit = (size_t)N;
}
opt_build_lit_prices_from_hist(hist, nlit, lit_bits);
opt_build_of_prices(of_hist, nseq_pp, of_bits);
}
/* Forward DP. We also must keep the matcher hash chains populated as we
* advance, so matches reference earlier positions correctly. We insert
* every position into the matcher as we visit it (DP order = position
* order since edges only go forward). */
/* SPRINT 43: work budget. The optimal DP is O(N × chain_depth ×
* extend). On adversarial self-similar data (long chains + long
* extends at every position) this degrades to near-quadratic and
* becomes a DoS vector. We bound total candidate-collection work;
* if exceeded, bail (return 0) so emit_block falls to greedy/lazy
* via the raw-store path is NOT what we want — instead we cap by
* short-circuiting long matches, which both bounds work AND is the
* correct optimal choice (a very long match is never beaten). */
const int32_t LONG_MATCH = VV_OPT_LONG_MATCH;
for (int32_t i = 0; i < N; i++) {
if (price[i] >= VV_OPT_PRICE_INF) {
matcher_insert(m, src, base + i, end);
continue;
}
int32_t ip = base + i;
/* literal edge (literals leave the rep history unchanged) */
int32_t lp = price[i] + lit_bits[src[ip]];
if (lp < price[i + 1]) {
price[i + 1] = lp; plen[i + 1] = 1; poff[i + 1] = 0;
prep[i + 1][0] = prep[i][0]; prep[i + 1][1] = prep[i][1]; prep[i + 1][2] = prep[i][2];
}
/* match edges */
if (ip + min_match <= end) {
int nc = opt_collect(m, src, ip, end, cands, prep[i]);
/* Find the longest candidate. */
int32_t best_len = 0; uint32_t best_off = 0;
for (int c = 0; c < nc; c++) {
if (cands[c].len > best_len) { best_len = cands[c].len; best_off = cands[c].off; }
}
if (best_len >= LONG_MATCH) {
/* LONG MATCH SHORT-CIRCUIT: a match this long is never
* beaten by any combination of shorter tokens. Take it
* as a single edge, skip the per-length relaxation AND
* skip DP/insertion for its interior positions. This
* bounds worst-case work on repetitive data: instead of
* O(match_len) work per interior position, we jump over
* the whole match. */
int32_t use = best_len;
if (i + use > N) use = N - i;
int32_t np = price[i] + opt_match_price(prep[i], best_off, use, of_bits);
int32_t j = i + use;
if (np < price[j]) {
price[j] = np; plen[j] = use; poff[j] = best_off;
opt_rep_push(prep[j], prep[i], best_off);
}
/* Insert boundary positions only (match-skip heuristic),
* then jump the DP cursor to the match end. */
int32_t end5 = end - 5;
for (int32_t q = ip; q < ip + 3 && q <= end5; q++) matcher_insert_fast(m, src, q);
for (int32_t q = ip + use - 3; q < ip + use && q <= end5; q++) matcher_insert_fast(m, src, q);
/* Advance i to j-1 (loop ++ makes it j). price[j] is set;
* intermediate price[i+1..j-1] stay INF, which is fine —
* the backtrack follows plen[] from reachable nodes only. */
i = j - 1;
continue;
}
for (int c = 0; c < nc; c++) {
int32_t mlen = cands[c].len; uint32_t moff = cands[c].off;
if (i + mlen > N) mlen = N - i;
if (mlen < min_match) continue;
for (int32_t L = mlen; L >= min_match; L--) {
int32_t np = price[i] + opt_match_price(prep[i], moff, L, of_bits);
int32_t j = i + L;
if (np < price[j]) {
price[j] = np; plen[j] = L; poff[j] = moff;
opt_rep_push(prep[j], prep[i], moff);
}
if (L > min_match + 8 && L < mlen) L = min_match + 9;
}
}
}
matcher_insert(m, src, ip, end);
}
/* Backtrack from N to 0 to recover the token sequence (reverse). */
/* Worst case every position is a literal: N entries. */
int32_t *seq_len = (int32_t *)malloc(sizeof(int32_t) * (N + 1));
uint32_t *seq_off = (uint32_t *)malloc(sizeof(uint32_t) * (N + 1));
if (!seq_len || !seq_off) { free(price); free(plen); free(poff); free(prep); free(cands); free(seq_len); free(seq_off); return 0; }
int32_t ns = 0, cur = N;
while (cur > 0) {
int32_t L = plen[cur];
if (L <= 0) L = 1; /* safety: treat as literal */
seq_len[ns] = L; seq_off[ns] = poff[cur]; ns++;
cur -= L;
}
/* Emit forward (reverse the backtrack). Accumulate literals between
* matches into literal runs, exactly like compress_block. */
const uint8_t *lit_start = src + base;
int32_t pos = base;
for (int k = ns - 1; k >= 0; k--) {
int32_t L = seq_len[k]; uint32_t O = seq_off[k];
if (O == 0) {
pos++; /* literal: extend pending run */
} else {
size_t ll = (size_t)(src + pos - lit_start);
size_t needed = 1 + (ll >= 15 ? ll / 255 + 2 : 0) + ll + 2 + ((size_t)L / 255 + 2);
if ((size_t)(op - dst) + needed > dst_cap) {
free(price); free(plen); free(poff); free(prep); free(cands); free(seq_len); free(seq_off);
return 0;
}
op += emit_seq(op, lit_start, ll, (size_t)L, O, off_bytes, min_match);
update_rep(m, O);
pos += L;
lit_start = src + pos;
}
}
/* trailing literals */
{
size_t ll = (size_t)(src + end - lit_start);
size_t needed = 1 + (ll >= 15 ? ll / 255 + 2 : 0) + ll;
if ((size_t)(op - dst) + needed > dst_cap) {
free(price); free(plen); free(poff); free(prep); free(cands); free(seq_len); free(seq_off);
return 0;
}
op += emit_seq(op, lit_start, ll, 0, 0, off_bytes, min_match);
}
free(price); free(plen); free(poff); free(prep); free(cands); free(seq_len); free(seq_off);
return (size_t)(op - dst);
}
/* ═══════════════════════════════════════════════════════════════
* COMPRESS BLOCK: greedy / lazy / lazy-2
*
* Match-skip heuristic: after a match of length ≥ 16, only insert
* the last 3 positions into the hash chain. The interior positions
* are inside the match and won't be needed. This saves O(match_len)
* hash insertions, speeding up compression by 15-25% at L3+.
* ═══════════════════════════════════════════════════════════════ */
static size_t compress_block(const uint8_t *src, size_t start_pos, size_t block_len,
uint8_t *dst, size_t dst_cap,
matcher_t *m, vv_mode_t mode, int min_match) {
uint8_t *op = dst;
int32_t pos = (int32_t)start_pos;
int32_t end = (int32_t)(start_pos + block_len);
const uint8_t *lit_start = src + start_pos;
int off_bytes = (m->wlog > 16) ? 3 : 2;
uint32_t failures = 0; /* consecutive no-match positions (for accel skip) */
uint32_t nmatch = 0; /* matches found in this block (early-RAW bail) */
while (pos < end - min_match) {
int32_t mlen = 0, moff = 0;
int pos_inserted = 0;
/* ─── Step 1: Try rep-match (free, no hash lookup) ─── */
int32_t rep_idx = -1;
int32_t rep_len = m->no_rep ? 0 : try_rep_match(m, src, pos, end, &rep_idx);
if (rep_len >= min_match) {
mlen = rep_len;
moff = (int32_t)m->rep[rep_idx];
}
/* ─── Step 2: Hash chain match (only if rep didn't find a long one) ─── */
if (mlen < 8) {
int32_t chain_off = 0;
/* SPRINT 58: fast mode (single_probe) uses the lean
* finder; balanced/extreme use the full chain walk. The
* branch is on a matcher flag set only for the real
* ULTRA_FAST encode, so balanced/extreme (and the window-
* selection trial) take the chain_match path exactly as
* before — bit-identical output. The lean finder selects
* the same match as chain_match at the same depth, so
* fast-mode output is unchanged too; only the per-position
* search overhead drops. */
int32_t chain_len = m->single_probe
? single_probe_match(m, src, pos, end, &chain_off)
: chain_match(m, src, pos, end, &chain_off);
if (chain_len > mlen) {
mlen = chain_len;
moff = chain_off;
rep_idx = -1; /* not a rep match */
}
}
/* ─── Step 3: Lazy evaluation (balanced + extreme) ─── */
/* Sprint 121: gate lazy probing on `mlen < 8`. Counterintuitive
* but empirically validated: the cost-aware lazy decision
* (added Sprint 120) makes WORSE choices when the current match
* is already moderately long.
*
* Why: cost-aware lazy compares per-byte costs of competing
* matches. When mlen ≥ 8, the current match's per-byte cost is
* already low (≈1.53 bits/byte for typical offsets). A lazy
* probe at pos+1 finding a slightly longer match at a different
* offset triggers a shift, paying 1 literal but only marginally
* improving per-byte cost. The literal cost dominates the small
* per-byte gain, AND the cost-model approximation accumulates
* error that biases toward shifting.
*
* Empirical sweep on the 8-fixture suite (aggregate Δ vs zstd-3):
* no gate (lazy always): 0.130% (v2.48.0 / Sprint 120)
* mlen < 16: 0.252%
* mlen < 12: 0.386%
* mlen < 9: 0.776%
* mlen < 8: 1.070% (THIS)
* mlen < 7: 1.282%
* mlen < 6: 1.860%
* mlen < 5: 2.044% (best aggregate, but
* fx_json regresses 8.5pp)
* no lazy (mlen < 4): 0.921%
*
* The mlen<5 setting wins aggregate but breaks fx_json from
* 2.49% to +6.04% — unacceptable per-fixture regression.
* mlen<8 is the safe optimum: improves every fixture vs v2.48.0
* with no regressions. */
if (mode >= VV_MODE_BALANCED && mlen >= min_match && mlen < 8 &&
pos + 1 < end - min_match) {
/* Check pos+1 */
matcher_insert(m, src, pos, end);
pos_inserted = 1;
int32_t noff = 0;
int32_t nlen = chain_match(m, src, pos + 1, end, &noff);
/* Also check rep at pos+1 */
int32_t nri = -1;
int32_t nrl = m->no_rep ? 0 : try_rep_match(m, src, pos + 1, end, &nri);
if (nrl > nlen && nri >= 0) { nlen = nrl; noff = (int32_t)m->rep[nri]; }
/* SPRINT 119: cost-aware lazy decision (closes the +1.2%
* ratio gap to zstd-3 — see CHANGELOG.md, Sprint 120).
*
* Old code used `nlen > mlen + 2` which ignores offset cost.
* This made vv prefer shorter matches at far offsets over
* longer matches at near offsets. zstd-3 produces ~11% fewer
* sequences on dickens (1.22M vs 1.37M) by accounting for
* offset cost when choosing between competing matches.
*
* Cost model:
* match_bits(off, len) ≈ 10 + log2(off) + ml_extra(len)
* - 10 covers ML/OF/LL ANS code values (avg ~3 bits each)
* - log2(off) is the OF extra-bit cost (info-theoretic min)
* - ml_extra is small for short matches (0 for len ≤ 19)
* literal_bits ≈ 6 (4-stream Huffman avg on text)
*
* Decide on B (shift) over A (emit current match) when:
* (literal_bits + match_bits(noff, nlen)) / (nlen + 1)
* < match_bits(moff, mlen) / mlen
*
* Rep matches have offset cost ≈ 1 bit (rep code, no extras),
* so they're heavily favored regardless of length. */
if (nlen >= min_match) {
/* Approx log2(off) — clamp to 1 for rep candidates and
* to >= 1 generally to avoid div-by-zero quirks. */
int log2_moff = 0; uint32_t mo = (uint32_t)moff;
while (mo > 1) { mo >>= 1; log2_moff++; }
int log2_noff = 0; uint32_t no = (uint32_t)noff;
while (no > 1) { no >>= 1; log2_noff++; }
/* Rep matches use 0 extra bits but do consume an OF code
* slot. Approximate them as cost 2 bits regardless of
* the actual offset value.
*
* Sprint 121: per-mode constant. Extreme mode (deep
* chain search) optimizes at +14; balanced mode
* (shallow chain) optimizes at +18 because the lazy
* candidates from a depth-24 search are noisier and
* benefit from less aggressive shifting. */
int cost_const = (mode >= VV_MODE_EXTREME) ? 14 : 18;
int moff_bits = (rep_idx >= 0) ? 2 : (cost_const + log2_moff);
int noff_bits = (nri >= 0) ? 2 : (cost_const + log2_noff);
/* Cross-multiply to avoid floating-point in hot path:
* (literal_bits + noff_bits) * mlen < moff_bits * (nlen + 1) */
int literal_bits = 6;
int lhs = (literal_bits + noff_bits) * mlen;
int rhs = moff_bits * (nlen + 1);
if (lhs < rhs) {
pos++;
pos_inserted = 0; /* the inserted position is now pos-1 */
mlen = nlen; moff = noff;
rep_idx = nri; /* may have shifted from explicit→rep or vice versa */
/* SPRINT 121: cost-aware lazy-2 was tested and rejected.
* Measured Δ vs lazy-1-only (gate mlen<8 in both cases):
* fx_text: neutral
* fx_json: 0.003% (negligible)
* fx_source: +0.022%
* bash: 0.052%
* dickens: +0.912% ← significant regression
* xml: +0.086%
* sao: +0.874% ← significant regression
* x-ray: +0.361%
* AGGREGATE: +0.601% (worse)
*
* The shift cascade dominates: after a successful
* lazy-1 shift, a second probe at the new pos+1
* tends to find marginally-longer matches and
* shifts again, eating literals faster than the
* cost model accounts for. The cost-model error
* compounds with each shift.
*
* Lazy-1 captures the available benefit cleanly. */
}
}
}
/* ─── Step 4: Emit sequence or literal ─── */
if (mlen >= min_match) {
size_t ll = (size_t)(src + pos - lit_start);
size_t needed = 1 + (ll >= 15 ? ll / 255 + 2 : 0)
+ ll + 2 + ((size_t)mlen / 255 + 2);
if ((size_t)(op - dst) + needed > dst_cap) return 0;
op += emit_seq(op, lit_start, ll, (size_t)mlen, (uint32_t)moff, off_bytes, min_match);
/* ─── Hash insertion with skip heuristic ─── */
/* SPRINT 29: use matcher_insert_fast inside the bulk loops
* (skips per-iteration hash_safe dispatch and boundary
* check). Bound is `j + 5 <= end` so hash5 is always safe.
* Positions in [end-4, end) are not inserted by this loop;
* for typical block sizes (64KB+) the missed boundary
* position is negligible (1 position).
*
* Saves ~3 instructions per insert. Measured +4% encode
* speedup on Silesia fast mode (Sprint 29). */
/* SPRINT 124: when the lazy probe already inserted pos and
* we did not shift, start at pos+1 — re-inserting pos would
* put a self-duplicate link in the chain, lengthening every
* future walk through that bucket. */
int32_t ins_first = pos + (pos_inserted ? 1 : 0);
if (mlen >= 16) {
/* Long match: only insert boundary positions */
int32_t end5 = end - 5;
for (int32_t j = ins_first; j < pos + 3 && j <= end5; j++)
matcher_insert_fast(m, src, j);
for (int32_t j = pos + mlen - 3; j < pos + mlen && j <= end5; j++)
matcher_insert_fast(m, src, j);
} else {
/* Short match: insert all positions */
int32_t end5 = end - 5;
for (int32_t j = ins_first; j < pos + mlen && j <= end5; j++)
matcher_insert_fast(m, src, j);
}
update_rep(m, (uint32_t)moff);
pos += mlen;
lit_start = src + pos;
failures = 0; /* matched: reset the no-match run */
nmatch++;
} else {
if (!pos_inserted) matcher_insert(m, src, pos, end);
/* Accel: skip ahead over unmatchable regions. accel==0 keeps
* the byte-identical old default (advance 1). The skipped
* positions are not hashed/inserted and simply become
* literals. SPRINT 124: balanced/extreme cap the stride at 8
* — on sparse-match data (struct-of-floats) an unbounded
* ramp skips over match starts and costs double-digit ratio;
* fast mode keeps the full lz4-style ramp. */
if (m->accel) {
uint32_t step = 1 + (((uint32_t)failures * m->accel) >> 6);
if (mode >= VV_MODE_BALANCED && step > 8) step = 8;
pos += (int32_t)step;
failures++;
/* Early RAW bail: 128 KB into the block with zero
* matches means this block is going raw anyway (csz
* would exceed braw). Returning 0 makes the caller
* emit a RAW block without paying for the rest of the
* parse or the literal memcpys. */
if (nmatch == 0 && pos - (int32_t)start_pos >= (1 << 17))
return 0;
} else {
pos++;
}
}
}
/* ─── Trailing literals ─── */
{
size_t ll = (size_t)(src + end - lit_start);
size_t needed = 1 + (ll >= 15 ? ll / 255 + 2 : 0) + ll;
if ((size_t)(op - dst) + needed > dst_cap) return 0;
op += emit_seq(op, lit_start, ll, 0, 0, off_bytes, min_match);
}
return (size_t)(op - dst);
}
/* ═══════════════════════════════════════════════════════════════
* EXTRACT LITERALS FROM TOKEN STREAM
*
* Walks a type-1 LZ token stream, copies all literal bytes into
* lit_buf and produces a "stripped" token stream (same format but
* with literal bytes removed) in stripped_buf.
*
* Returns the number of literals extracted, or 0 on error.
* ═══════════════════════════════════════════════════════════════ */
static size_t extract_literals(
const uint8_t *tokens, size_t tok_len,
uint8_t *lit_buf, size_t lit_cap,
uint8_t *stripped_buf, size_t *stripped_len, int off_bytes)
{
const uint8_t *tp = tokens;
const uint8_t *tp_end = tokens + tok_len;
uint8_t *sp = stripped_buf;
size_t total_lits = 0;
while (tp < tp_end) {
uint8_t token = *tp++;
*sp++ = token; /* Copy token byte to stripped stream */
size_t ll = token >> 4;
size_t mc = token & 0x0F;
/* Extended literal length */
if (ll == 15) {
size_t ext = 0;
do {
if (tp >= tp_end) return 0;
uint8_t b = *tp++;
*sp++ = b; /* Copy extension byte */
ext += b;
if (b < 255) break;
} while (tp < tp_end);
ll += ext;
}
/* Literal bytes: copy to lit_buf, do NOT copy to stripped stream */
if (tp + ll > tp_end) return 0;
if (total_lits + ll > lit_cap) return 0;
memcpy(lit_buf + total_lits, tp, ll);
total_lits += ll;
tp += ll;
/* End of block: no more data = last sequence (no match) */
if (tp >= tp_end) break;
/* Offset: 2 or 3 bytes, copy to stripped stream */
if (tp + off_bytes > tp_end) return 0;
for (int i = 0; i < off_bytes; i++) *sp++ = *tp++;
/* Extended match length */
if (mc == 15) {
size_t ext = 0;
do {
if (tp >= tp_end) return 0;
uint8_t b = *tp++;
*sp++ = b;
ext += b;
if (b < 255) break;
} while (tp < tp_end);
(void)ext;
}
}
*stripped_len = (size_t)(sp - stripped_buf);
return total_lits;
}
/* ═══════════════════════════════════════════════════════════════
* BLOCK EMISSION HELPER
*
* Encodes a single block of up to VV_MAX_BLOCK_SIZE bytes from
* src[block_start..block_start+braw) and emits the compressed block
* to dst. Picks the best path (raw / LZ-raw / 'S' seq / 'I'/'C' lit)
* via winner-takes-all in balanced+extreme modes.
*
* Used by both the one-shot vv_compress() and the streaming
* vv_cstream_compress_chunk(). Expects:
* - src: source buffer (full source for vv_compress; the
* persistent stream buffer for streaming)
* - block_start: offset in src where this block begins
* - braw: block raw length (≤ VV_MAX_BLOCK_SIZE)
* - last: 1 if this is the last block in the frame
* - m: matcher state (persists across blocks)
* - mode: compression mode (affects path choice)
* - wlog: window log
* - tmp/tcap: scratch buffer for LZ-compressed tokens
* - lit_buf/lit_cap, stripped, ent_buf/ent_cap: entropy scratch
* - dst/dst_cap: output buffer
*
* Returns bytes written to dst on success, or 0 on overflow. */
/* SPRINT 124: high-watermark tracking for the secure-zero scrub.
* Scrubbing full buffer capacities (~4 MB) per vv_compress call cost
* up to 14% of encode wall on fast inputs; only bytes actually written
* can hold plaintext, so tracking write watermarks preserves the
* Sprint 117 security property at a fraction of the cost. */
typedef struct {
size_t tmp, lit, stripped, ent_front, ent_back;
} scrub_wm_t;
static inline void wm_max(size_t *wm, size_t used) {
if (used > *wm) *wm = used;
}
static size_t emit_block(const uint8_t *src, size_t block_start, size_t braw,
int last, matcher_t *m, vv_mode_t mode, uint8_t wlog,
uint8_t *tmp, size_t tcap,
uint8_t *lit_buf, size_t lit_cap,
uint8_t *stripped, uint8_t *ent_buf, size_t ent_cap,
uint8_t *dst, size_t dst_cap, int min_match,
int compat_v246_5, scrub_wm_t *wm) {
uint8_t *op = dst;
/* SPRINT 42/43 RATIO PROGRAM: extreme mode uses the whole-block optimal
* parser; balanced/fast keep greedy/lazy. csz==0 (overflow/alloc) flows
* into the raw-store branch below.
*
* SPRINT 124: on format-v2 (binary-detected) input, extreme uses the
* deep greedy/lazy parser instead. The optimal DP prices every match
* at full log2(offset) cost — it has no rep-offset model — so on
* rep-heavy record data (struct-of-floats, sensor logs) it loses
* 15-20% ratio to the rep-aware greedy path, and on incompressible
* binary it pays a full O(N·depth) DP just to store raw (the greedy
* path has skip acceleration and an early-RAW bail). Text-like input
* keeps the optimal parser, where it wins 3-11% over greedy. */
size_t csz;
int v2_block = (min_match < (int)VV_MIN_MATCH);
if (mode >= VV_MODE_EXTREME && !v2_block)
csz = compress_block_optimal(src, block_start, braw, tmp, tcap, m, min_match);
else
csz = compress_block(src, block_start, braw, tmp, tcap, m, mode, min_match);
if (wm) wm_max(&wm->tmp, csz);
/* SPRINT 124: in balanced/extreme, a token stream slightly larger
* than raw can still win AFTER entropy coding — on low-match data
* (struct-of-floats, sensor logs) nearly all the compression comes
* from the entropy stage over literals, not from matches. Only the
* entropy-less fast path must reject csz >= braw outright. */
size_t raw_gate = (mode >= VV_MODE_BALANCED) ? braw + braw / 8 : braw;
if (csz == 0 || csz >= raw_gate) {
/* Incompressible: store raw */
if ((size_t)(op - dst) + 4 + braw > dst_cap) return 0;
uint32_t bh = vv_bh_pack(VV_BLOCK_RAW, last, (uint32_t)braw);
memcpy(op, &bh, 4); op += 4;
memcpy(op, src + block_start, braw); op += braw;
return (size_t)(op - dst);
}
if (mode >= VV_MODE_BALANCED) {
/* Path A: sequence coding ('S') */
size_t seq_len = 0;
int seq_valid = 0;
size_t seq_block_sz = (size_t)-1;
int off_bytes = (wlog > 16) ? 3 : 2;
/* Format v2: when min_match < 4 (i.e. 3), encode with the v2
* table so length-3 matches are representable as code 0. The
* token stream produced by compress_block(min_match=3) may
* contain 3-byte matches that v1 encode_sequences cannot
* represent correctly. */
int use_v2 = (min_match < (int)VV_MIN_MATCH);
/* Sprint 105 Phase C: thread compat flag through to SEQ encoder. */
int dis_huf4 = compat_v246_5;
vva_error_t serr = use_v2
? vva_encode_sequences_v2_compat(tmp, csz, ent_buf, ent_cap, &seq_len, off_bytes, dis_huf4)
: vva_encode_sequences_compat(tmp, csz, ent_buf, ent_cap, &seq_len, off_bytes, dis_huf4);
if (serr == VVA_OK) {
seq_block_sz = 4 + 3 + 1 + seq_len;
seq_valid = 1;
}
if (wm) wm_max(&wm->ent_front, seq_len);
/* Path B: literal-only entropy ('I' or 'A') */
size_t stripped_len = 0;
size_t lit_count = 0;
uint8_t *ent_buf2 = ent_buf + ent_cap / 2;
size_t ent_cap2 = ent_cap / 2;
size_t ent_len = 0;
uint8_t ent_tag = 0;
size_t ent_block_sz = (size_t)-1;
/* Path B gate (v2.53.3, revised SPRINT 124): Path B has a
* measured 0% win rate against Path A (SEQ) on real inputs —
* SEQ codes the same literals at least as small while also
* coding the matches. Run it only when SEQ failed or produced
* weak output (>= 7/8 of raw). Path B is v1-only (its stripped
* tokens carry v1 matchlen bias), so on the v2 path skip the
* work entirely — the result could never be emitted.
*
* SPRINT 124: the CTX (order-1) coder is gone from this path.
* It ran exactly when SEQ was weak — low-redundancy binary —
* where it burned 50% of encode wall (sensors-class inputs)
* and, per the Sprint 53 measurements, never won a block. */
int try_path_b = !use_v2 && (!seq_valid ||
seq_block_sz >= (braw * 7 / 8));
if (try_path_b) {
lit_count = extract_literals(tmp, csz, lit_buf, lit_cap,
stripped, &stripped_len, off_bytes);
if (wm) {
wm_max(&wm->lit, lit_count);
wm_max(&wm->stripped, stripped_len);
}
if (lit_count > 0) {
vva_error_t aerr = vva_encode4(lit_buf, lit_count,
ent_buf2, ent_cap2, &ent_len);
if (aerr == VVA_OK) ent_tag = VV_ENTROPY_ANS4;
if (!ent_tag) {
aerr = vva_encode(lit_buf, lit_count,
ent_buf2, ent_cap2, &ent_len);
if (aerr == VVA_OK) ent_tag = VV_ENTROPY_ANS;
}
if (ent_tag) {
ent_block_sz = 4 + 3 + 1 + 2 + 2 + ent_len + stripped_len;
}
if (wm) wm_max(&wm->ent_back, ent_len);
}
}
size_t raw_block_sz = 4 + 3 + csz;
/* Raw-store block size: with the relaxed raw_gate above, csz may
* exceed braw, so every candidate must also beat plain storage. */
size_t store_sz = 4 + braw;
if (raw_block_sz > store_sz) raw_block_sz = store_sz;
if (seq_valid && seq_block_sz <= ent_block_sz && seq_block_sz < raw_block_sz) {
if ((size_t)(op - dst) + seq_block_sz > dst_cap) return 0;
uint32_t bh = vv_bh_pack(VV_BLOCK_ENTROPY, last, (uint32_t)braw);
memcpy(op, &bh, 4); op += 4;
uint32_t total_comp = (uint32_t)(1 + seq_len);
op[0] = (uint8_t)(total_comp);
op[1] = (uint8_t)(total_comp >> 8);
op[2] = (uint8_t)(total_comp >> 16);
op += 3;
*op++ = use_v2 ? VV_ENTROPY_SEQ_V2 : VV_ENTROPY_SEQ;
memcpy(op, ent_buf, seq_len); op += seq_len;
} else if (!use_v2 && ent_tag && ent_block_sz < raw_block_sz) {
/* Path B (H/I/C entropy) uses `stripped` tokens which still
* contain v1-format matchlen bytes. Only safe for v1. For
* v2, we must skip this fallback to avoid emitting v1 tokens
* that a v2-aware decoder wouldn't reconstruct correctly. */
if ((size_t)(op - dst) + ent_block_sz > dst_cap) return 0;
uint32_t bh = vv_bh_pack(VV_BLOCK_ENTROPY, last, (uint32_t)braw);
memcpy(op, &bh, 4); op += 4;
uint32_t total_comp = (uint32_t)(5 + ent_len + stripped_len);
op[0] = (uint8_t)(total_comp);
op[1] = (uint8_t)(total_comp >> 8);
op[2] = (uint8_t)(total_comp >> 16);
op += 3;
*op++ = ent_tag;
op[0] = (uint8_t)(lit_count); op[1] = (uint8_t)(lit_count >> 8); op += 2;
op[0] = (uint8_t)(ent_len); op[1] = (uint8_t)(ent_len >> 8); op += 2;
memcpy(op, ent_buf2, ent_len); op += ent_len;
memcpy(op, stripped, stripped_len); op += stripped_len;
} else if (!use_v2 && csz < braw) {
/* Plain VV_BLOCK_COMPRESSED carries raw v1-format tokens.
* For v2, we must not emit these — the decoder would
* reconstruct matchlen with +4 instead of +3. Guarded on
* csz < braw because the relaxed raw_gate can let a token
* stream slightly larger than raw reach this point. */
if ((size_t)(op - dst) + 4 + 3 + csz > dst_cap) return 0;
uint32_t bh = vv_bh_pack(VV_BLOCK_COMPRESSED, last, (uint32_t)braw);
memcpy(op, &bh, 4); op += 4;
op[0] = (uint8_t)(csz); op[1] = (uint8_t)(csz >> 8); op[2] = (uint8_t)(csz >> 16);
op += 3;
memcpy(op, tmp, csz); op += csz;
} else {
/* Nothing beat plain storage: emit RAW. */
if ((size_t)(op - dst) + 4 + braw > dst_cap) return 0;
uint32_t bh = vv_bh_pack(VV_BLOCK_RAW, last, (uint32_t)braw);
memcpy(op, &bh, 4); op += 4;
memcpy(op, src + block_start, braw); op += braw;
}
} else {
/* Ultra-fast mode */
if ((size_t)(op - dst) + 4 + 3 + csz > dst_cap) return 0;
uint32_t bh = vv_bh_pack(VV_BLOCK_COMPRESSED, last, (uint32_t)braw);
memcpy(op, &bh, 4); op += 4;
op[0] = (uint8_t)(csz); op[1] = (uint8_t)(csz >> 8); op[2] = (uint8_t)(csz >> 16);
op += 3;
memcpy(op, tmp, csz); op += csz;
}
return (size_t)(op - dst);
}
/* ═══════════════════════════════════════════════════════════════
* PUBLIC API: COMPRESS
* ═══════════════════════════════════════════════════════════════ */
size_t vv_compress_bound(size_t src_len) {
return src_len + src_len / 255 + 256
+ sizeof(vv_frame_header_t) + sizeof(vv_frame_footer_t);
}
/* Public vv_compress: select and apply a reversible BCJ branch filter, then
* compress. A filter may be requested explicitly (filter_x86 / filter_arm64)
* or chosen automatically (filter_auto: sniff the executable header). The
* filter runs on a private copy because the public input is const; the
* matching header flag (bit2 x86 / bit3 ARM64), set by vv_compress_inner from
* the resolved options, tells the decoder to invert it. When no filter
* applies, this is a direct pass-through with no copy and byte-identical
* output. */
int64_t vv_compress_inner(const uint8_t *src, size_t src_len,
uint8_t *dst, size_t dst_cap,
const vv_options_t *opts);
int64_t vv_compress(const uint8_t *src, size_t src_len,
uint8_t *dst, size_t dst_cap,
const vv_options_t *opts) {
int auto_on = opts && opts->filter_auto &&
!opts->filter_x86 && !opts->filter_arm64;
if (opts && (opts->filter_x86 || opts->filter_arm64 || auto_on) &&
src_len > 0 && src) {
vv_options_t eff = *opts;
if (auto_on) {
vv_filter_kind_t k = vv_bcj_detect(src, src_len);
if (k == VV_FILTER_X86) eff.filter_x86 = 1;
else if (k == VV_FILTER_ARM64) eff.filter_arm64 = 1;
/* k == NONE: leave eff with no filter -> falls through below */
}
if (eff.filter_x86 || eff.filter_arm64) {
uint8_t *copy = (uint8_t *)malloc(src_len);
if (!copy) return VV_ERR_NOMEM;
memcpy(copy, src, src_len);
if (eff.filter_x86)
vv_bcj_x86(copy, src_len, 0, 1); /* forward: relative -> absolute */
else
vv_bcj_arm64(copy, src_len, 0, 1); /* AArch64 BL + ADRP */
int64_t r = vv_compress_inner(copy, src_len, dst, dst_cap, &eff);
free(copy);
return r;
}
/* auto-detect found no executable header: fall through unchanged */
}
return vv_compress_inner(src, src_len, dst, dst_cap, opts);
}
int64_t vv_compress_inner(const uint8_t *src, size_t src_len,
uint8_t *dst, size_t dst_cap,
const vv_options_t *opts) {
/* SPRINT 95 audit: accept NULL opts (fall back to defaults) for
* consistency with vv_cstream_create. Also accept src_len=0
* (an empty frame is a valid thing to produce — some streaming
* protocols rely on it as a flush marker). */
if (!dst) return VV_ERR_PARAM;
if (src_len > 0 && !src) return VV_ERR_PARAM;
if (dst_cap < sizeof(vv_frame_header_t) + sizeof(vv_frame_footer_t) + 16)
return VV_ERR_OVERFLOW;
vv_options_t local_opts;
if (!opts) {
vv_default_options(&local_opts);
opts = &local_opts;
}
uint8_t wlog = opts->window_log;
uint32_t depth;
if (wlog == 0) {
switch (opts->mode) {
case VV_MODE_ULTRA_FAST: wlog = 16; break;
case VV_MODE_BALANCED: wlog = 16; break; /* may be overridden below */
case VV_MODE_EXTREME: wlog = 16; break; /* may be overridden below */
}
}
switch (opts->mode) {
case VV_MODE_ULTRA_FAST: depth = 4; break;
case VV_MODE_BALANCED: depth = 24; break; /* was 48 — halving barely affects ratio, doubles speed */
case VV_MODE_EXTREME: depth = 256; break;
default: depth = 24;
}
/* Opt-in chain-depth override (default 0 = mode default, byte-identical). */
if (opts->depth_override) {
depth = opts->depth_override;
if (depth > 4096) depth = 4096;
}
/* ─── ADAPTIVE WINDOW + HASH4 detection in a single trial.
* PERF: previously this was two separate 128K+64K=192K trials, run
* sequentially. We can make BOTH decisions from the SAME trial:
* - window: wlog=20 wins if it saves ≥3% vs wlog=16
* - hash4: enable if ratio < 2:1 (indicates binary-like data) */
int enable_hash4 = 0;
if (opts->window_log == 0 && opts->mode >= VV_MODE_BALANCED && src_len > 65536) {
size_t trial_len = 131072;
if (trial_len > src_len) trial_len = src_len;
size_t trial_cap = trial_len + trial_len / 255 + 1024;
uint8_t *trial_buf = (uint8_t *)malloc(trial_cap);
if (trial_buf) {
matcher_t m16;
size_t sz16 = 0, sz20 = 0;
/* SPRINT 93 audit: matcher_init can fail; if it does, skip
* the trial (this path is a perf-tuning probe — falling
* back to default wlog is safe).
* SPRINT 124: trials run with accel=2 so incompressible
* inputs no longer pay two full 128 KB parses just to
* decide "store raw". Both trials use the same accel, so
* the 16-vs-20 comparison stays apples-to-apples. */
if (matcher_init(&m16, 16, 4)) {
m16.accel = 2;
sz16 = compress_block(src, 0, trial_len, trial_buf, trial_cap, &m16, VV_MODE_ULTRA_FAST, VV_MIN_MATCH);
matcher_free(&m16);
}
matcher_t m20;
if (matcher_init(&m20, 20, 4)) {
m20.accel = 2;
sz20 = compress_block(src, 0, trial_len, trial_buf, trial_cap, &m20, VV_MODE_ULTRA_FAST, VV_MIN_MATCH);
matcher_free(&m20);
}
free(trial_buf);
if (sz20 > 0 && sz16 > 0 && sz20 < (sz16 * 97 / 100)) wlog = 20;
/* Binary-like detection: best trial ratio < 2:1. A zero
* size means the early-RAW bail fired — maximally
* incompressible, so binary-like by definition. */
size_t best_sz = (sz20 > 0 && sz20 < sz16) ? sz20 : sz16;
if (best_sz == 0 || best_sz * 2 > trial_len) enable_hash4 = 1;
}
}
/* SPRINT 124: adaptive format v2 (decided here because the window
* overrides below must not fire for v2-routed input). min_match=3
* ('T' blocks) is a measured 14%+ ratio win on struct-of-floats/
* record binary and 2-3% on ELF, while slightly HURTING text/JSON
* ratio and decode speed (more, shorter sequences). Auto-enable
* exactly where it wins: binary-detected inputs. Suppressed by
* the compat flag because 'T' blocks require a v2.33.0+ decoder.
* Explicit opts->format_v2 still forces it for any input. */
int use_v2_fmt = opts->format_v2 ||
(enable_hash4 && opts->mode >= VV_MODE_BALANCED &&
!opts->compat_v246_5_decoder);
/* SPRINT 67: size-based wlog override. The trial above often
* misses wins that only become visible past the 128 KB trial
* boundary (long-range refs in multi-MB files). Override to
* wlog=18 for files ≥ 3 MB when the trial left wlog at 16.
* SPRINT 124: not for v2-routed (binary) input — the greedy
* parser regresses badly on rep-heavy data with large windows
* (diverse far offsets break rep streaks and bloat OF codes). */
if (opts->window_log == 0 && opts->mode >= VV_MODE_BALANCED &&
!use_v2_fmt && wlog == 16 && src_len >= 3145728) {
wlog = 18;
}
/* SPRINT 46 (RATIO PROGRAM): extreme-mode large-window scaling.
* The trial/override above caps extreme at wlog≈18-20 (256KB-1MB),
* far too small for the multi-MB Silesia fixtures with long-range
* structure (nci 33MB, webster 41MB, mozilla 51MB). The whole-block
* optimal parser exploits a larger window across block boundaries
* (the matcher chains persist between blocks).
*
* Scale wlog with file size, capped at 2^24 = 16 MB. The cap is a
* HARD wire-format limit: the offset field is 3 bytes (24 bits) for
* wlog>16, so the maximum representable offset is exactly 2^24.
* (Reaching 2^27 like zstd --long requires 4-byte offsets — a
* wire-format change deferred to Lever B.)
*
* REQUIRED companion fix (same sprint): the ANS sequence decoder's
* SAFEZONE_MAX_OFFSET was raised from 1<<20 to 1<<24, since it
* previously rejected any offset > 1 MB as corrupt. Without that
* fix this scaling breaks roundtrip on multi-block files (the bug
* diagnosed and reverted in Sprint 45).
*
* Memory at wlog=24: chain[wsz]+hash4_chain[wsz] = 2*4*16M = 128 MB
* matcher. Acceptable for extreme ("max ratio, will wait"). */
if (opts->window_log == 0 && opts->mode >= VV_MODE_EXTREME &&
!use_v2_fmt && src_len > (1u << 20)) {
/* SPRINT 124: v2-routed (binary) extreme input uses the greedy
* parser (no rep model in the optimal DP), and greedy + large
* window is a measured 15-30% ratio LOSS on rep-heavy data —
* keep the trial-chosen window there. */
uint8_t want = 20;
uint64_t s = src_len;
while ((1ull << want) < s && want < 24) want++;
if (want > wlog) wlog = want;
}
/* Frame header */
uint8_t *op = dst;
vv_frame_header_t fh;
memset(&fh, 0, sizeof(fh));
fh.magic = VV_MAGIC;
fh.version = 1;
fh.flags = (opts->checksum ? 1 : 0)
| (opts->filter_x86 ? 4 : 0)
| (opts->filter_arm64 ? 8 : 0);
fh.mode_hint = (uint8_t)opts->mode;
fh.window_log = wlog;
fh.content_size = (uint64_t)src_len;
memcpy(op, &fh, sizeof(fh)); op += sizeof(fh);
/* Matcher */
matcher_t m;
/* SPRINT 93 audit: handle allocation failure cleanly */
if (!matcher_init(&m, wlog, depth)) {
return VV_ERR_NOMEM;
}
m.use_hash4 = (uint8_t)enable_hash4; /* From fused adaptive-window trial */
/* SPRINT 58: enable the single-probe match finder for ULTRA_FAST.
* Set here (not in matcher_init) so the depth-4 chain matchers used
* by the balanced/extreme window-selection trial above stay at
* single_probe==0 and produce bit-identical trial sizes. */
m.single_probe = (opts->mode == VV_MODE_ULTRA_FAST) ? 1 : 0;
/* SPRINT 124: accel defaults ON. opts->accel == 0 now means "auto":
* fast mode gets the lz4-style ramp (2 → step 1 + failures/32),
* balanced/extreme a gentle one (1 → step 1 + failures/64, capped
* at 8 inside compress_block). This is what turns 1 MB of random
* bytes from a 24 ns/byte full-parse crawl into a near-memcpy RAW
* store. Explicit --accel values are honored unchanged. */
{
uint32_t eff_accel = opts->accel;
if (eff_accel == 0)
eff_accel = (opts->mode >= VV_MODE_BALANCED) ? 1 : 2;
m.accel = eff_accel > 64 ? 64 : eff_accel;
}
m.no_rep = opts->no_rep ? 1 : 0;
/* Format v2 cap applies to EVERY match emitted from this matcher,
* not just those produced via hash3. Set unconditionally when
* the v2 format is active. */
if (use_v2_fmt) {
matcher_set_format_v2(&m);
}
/* Hash3 enablement is a separate, adaptive decision. Only fires
* on binary-like data (enable_hash4) where length-3 matches
* actually help. On text/JSON it stays off to avoid regressions. */
if (use_v2_fmt && enable_hash4) {
if (!matcher_enable_hash3(&m)) {
matcher_free(&m);
return VV_ERR_NOMEM;
}
}
/* Temp buffer.
* PERF: size to the actual input (not always 1MB). For a 4KB input,
* tcap was ~1.03MB — a wasteful allocation. Now allocate just enough
* to hold the LZ-tokenized output, bounded by VV_MAX_BLOCK_SIZE. */
size_t block_bound = src_len < VV_MAX_BLOCK_SIZE ? src_len : VV_MAX_BLOCK_SIZE;
size_t tcap = block_bound + block_bound / 255 + 1024;
uint8_t *tmp = (uint8_t *)malloc(tcap);
if (!tmp) { matcher_free(&m); return VV_ERR_NOMEM; }
/* Additional buffers for entropy path (only allocated if needed) */
uint8_t *lit_buf = NULL, *stripped = NULL, *ent_buf = NULL;
size_t lit_cap = 0, ent_cap = 0;
if (opts->mode >= VV_MODE_BALANCED) {
/* PERF: size these to the actual input too — they only need to
* cover the single in-flight block's worth of literals/entropy
* output. For small one-shot calls this avoids ~3 MB of wasted
* allocation and page-faulting every call. */
lit_cap = block_bound;
/* SPRINT 124 (latent-corruption fix): ent_buf is shared by Path A
* (SEQ, writes at ent_buf[0..]) and Path B (literal entropy,
* writes at ent_buf + ent_cap/2). SEQ output on weak blocks can
* reach vva_bound(braw) — with ent_cap == vva_bound the halves
* OVERLAP and Path B silently clobbers SEQ's tail before the
* winner is chosen. Size the buffer so each half holds a full
* vva_bound worth of output. */
ent_cap = 2 * vva_bound(block_bound);
lit_buf = (uint8_t *)malloc(lit_cap);
stripped = (uint8_t *)malloc(tcap);
ent_buf = (uint8_t *)malloc(ent_cap);
if (!lit_buf || !stripped || !ent_buf) {
free(lit_buf); free(stripped); free(ent_buf);
free(tmp); matcher_free(&m);
return VV_ERR_NOMEM;
}
}
size_t remaining = src_len;
const uint8_t *ip = src;
if (remaining == 0) {
uint32_t bh = vv_bh_pack(VV_BLOCK_RAW, 1, 0);
memcpy(op, &bh, 4); op += 4;
}
/* Format v2 (explicit or adaptive): encode with min_match=3.
* Produces 'T'-tagged ENTROPY blocks which only v2.33.0+ decoders
* can read. Closes the real-binary compression gap vs gzip-9. */
int min_match = use_v2_fmt ? 3 : (int)VV_MIN_MATCH;
scrub_wm_t wm = {0, 0, 0, 0, 0};
while (remaining > 0) {
size_t braw = remaining > VV_MAX_BLOCK_SIZE ? VV_MAX_BLOCK_SIZE : remaining;
int last = (remaining <= VV_MAX_BLOCK_SIZE);
size_t block_start = (size_t)(ip - src);
size_t written = emit_block(src, block_start, braw, last, &m, opts->mode, wlog,
tmp, tcap, lit_buf, lit_cap,
stripped, ent_buf, ent_cap,
op, dst_cap - (size_t)(op - dst), min_match,
opts->compat_v246_5_decoder, &wm);
if (written == 0) {
free(lit_buf); free(stripped); free(ent_buf);
free(tmp); matcher_free(&m);
return VV_ERR_OVERFLOW;
}
op += written;
ip += braw; remaining -= braw;
}
/* Sprint 117: scrub plaintext-derived working buffers before free
* to prevent heap-residue leak (defense in depth).
* SPRINT 124: scrub only up to each buffer's write watermark —
* bytes beyond it were never written and cannot hold plaintext. */
vv_secure_zero(tmp, wm.tmp < tcap ? wm.tmp : tcap);
if (lit_buf) vv_secure_zero(lit_buf, wm.lit < lit_cap ? wm.lit : lit_cap);
if (stripped) vv_secure_zero(stripped, wm.stripped < tcap ? wm.stripped : tcap);
if (ent_buf) {
vv_secure_zero(ent_buf, wm.ent_front < ent_cap ? wm.ent_front : ent_cap);
size_t back_cap = ent_cap - ent_cap / 2;
if (wm.ent_back)
vv_secure_zero(ent_buf + ent_cap / 2,
wm.ent_back < back_cap ? wm.ent_back : back_cap);
}
free(lit_buf); free(stripped); free(ent_buf);
free(tmp);
if (opts->checksum) {
vv_frame_footer_t ff;
ff.checksum = vv_xxh64(src, src_len, 0);
ff.footer_magic = 0x56564E44u;
memcpy(op, &ff, sizeof(ff)); op += sizeof(ff);
}
matcher_free(&m);
return (int64_t)(op - dst);
}
/* ═══════════════════════════════════════════════════════════════
* STREAMING COMPRESSION
*
* A compression stream buffers persistent state across calls:
* - The matcher (hash tables, chains, rep-match offsets)
* - Scratch buffers (tmp/lit_buf/stripped/ent_buf)
* - Streaming xxh64 state for the frame checksum
* - The full input so far in sliding-window form (needed because
* LZ matches can reference up to 2^wlog bytes back)
*
* Each call to vv_cstream_compress_chunk() appends chunk bytes to
* the internal source buffer, emits one block covering those bytes,
* and optionally emits the frame header (first call) and footer
* (when is_last is set).
*
* Memory cost: 2 × window_size + ~10 MB scratch (ent_buf, etc.).
* For wlog=16 that's ~131 KB + scratch; wlog=20 is ~2 MB + scratch.
* ═══════════════════════════════════════════════════════════════ */
struct vv_cstream_s {
vv_options_t opts;
uint8_t wlog;
matcher_t m;
/* Scratch buffers — allocated once, reused across chunks */
uint8_t *tmp; size_t tcap;
uint8_t *lit_buf; size_t lit_cap;
uint8_t *stripped;
uint8_t *ent_buf; size_t ent_cap;
/* Sliding-window source buffer. We accumulate input so offset-based
* match references resolve correctly. Old bytes beyond the window
* are dropped in periodic compaction. */
uint8_t *src_buf; /* Capacity = 2 × window_size */
size_t src_cap;
size_t src_head; /* First valid byte index in src_buf */
size_t src_len; /* Number of valid bytes in src_buf */
size_t global_offset; /* src_buf[i] corresponds to stream offset (global_offset - src_len + i) */
/* Streaming checksum */
vv_xxh64_state_t cks;
int header_emitted;
};
vv_cstream_t *vv_cstream_create(const vv_options_t *opts) {
vv_cstream_t *ctx = (vv_cstream_t *)calloc(1, sizeof(vv_cstream_t));
if (!ctx) return NULL;
if (opts) ctx->opts = *opts;
else vv_default_options(&ctx->opts);
/* Resolve window log (fixed for streams — no adaptive probe) */
uint8_t wlog = ctx->opts.window_log;
if (wlog == 0) wlog = 16;
ctx->wlog = wlog;
uint32_t depth;
switch (ctx->opts.mode) {
case VV_MODE_ULTRA_FAST: depth = 4; break;
case VV_MODE_BALANCED: depth = 24; break;
case VV_MODE_EXTREME: depth = 256; break;
default: depth = 24;
}
if (ctx->opts.depth_override) {
depth = ctx->opts.depth_override;
if (depth > 4096) depth = 4096;
}
/* SPRINT 93 audit: matcher_init can fail; cstream returns NULL
* on any allocation error per public API contract. */
if (!matcher_init(&ctx->m, wlog, depth)) {
free(ctx);
return NULL;
}
/* SPRINT 58: single-probe finder for ULTRA_FAST streaming, matching
* the one-shot fast path. balanced/extreme keep single_probe==0. */
ctx->m.single_probe = (ctx->opts.mode == VV_MODE_ULTRA_FAST) ? 1 : 0;
ctx->m.accel = ctx->opts.accel > 64 ? 64 : ctx->opts.accel;
ctx->m.no_rep = ctx->opts.no_rep ? 1 : 0;
/* Format v2 matchlen cap applies to every match — set whenever
* streaming opts has format_v2 on, not just when hash3 fires.
*
* Sprint 89 audit: read from ctx->opts (populated above with either
* the caller's opts or default values) rather than the raw opts
* pointer, which can be NULL when caller wants defaults. The prior
* code dereferenced NULL when called as vv_cstream_create(NULL). */
if (ctx->opts.format_v2) {
matcher_set_format_v2(&ctx->m);
}
/* SPRINT 45: enable hash3 for format v2 streaming. Must free
* ctx before returning NULL — callers use NULL-check semantics
* here, not error codes. */
if (ctx->opts.format_v2) {
if (!matcher_enable_hash3(&ctx->m)) {
matcher_free(&ctx->m);
free(ctx);
return NULL;
}
}
/* Scratch buffers sized for VV_MAX_BLOCK_SIZE */
ctx->tcap = VV_MAX_BLOCK_SIZE + VV_MAX_BLOCK_SIZE / 255 + 1024;
ctx->tmp = (uint8_t *)malloc(ctx->tcap);
ctx->lit_cap = VV_MAX_BLOCK_SIZE;
ctx->lit_buf = (uint8_t *)malloc(ctx->lit_cap);
/* SPRINT 124: stripped tokens can slightly exceed the raw block
* size now that emit_block lets csz ∈ [braw, braw*9/8) reach the
* entropy stage — size like tmp, not like lit_buf. */
ctx->stripped = (uint8_t *)malloc(ctx->tcap);
/* SPRINT 124: 2× so Path A (front half) and Path B (back half)
* can never overlap — see the matching fix in vv_compress_inner. */
ctx->ent_cap = 2 * vva_bound(VV_MAX_BLOCK_SIZE);
ctx->ent_buf = (uint8_t *)malloc(ctx->ent_cap);
/* Source window = 2 × window_size so a full block of input can
* land before we compact. */
size_t window = (size_t)1u << wlog;
ctx->src_cap = window * 2 + VV_MAX_BLOCK_SIZE;
ctx->src_buf = (uint8_t *)malloc(ctx->src_cap);
if (!ctx->tmp || !ctx->lit_buf || !ctx->stripped || !ctx->ent_buf || !ctx->src_buf) {
vv_cstream_destroy(ctx);
return NULL;
}
if (ctx->opts.checksum) vv_xxh64_init(&ctx->cks, 0);
ctx->header_emitted = 0;
return ctx;
}
void vv_cstream_destroy(vv_cstream_t *ctx) {
if (!ctx) return;
/* Sprint 117: zero plaintext-derived working buffers before free.
* lit_buf and stripped contain literal bytes from the input; src_buf
* holds raw input. tmp/ent_buf may contain compressed-but-not-yet-
* encrypted output. All are scrubbed to prevent heap-residue leak. */
if (ctx->tmp) vv_secure_zero(ctx->tmp, ctx->tcap);
if (ctx->lit_buf) vv_secure_zero(ctx->lit_buf, ctx->lit_cap);
if (ctx->stripped) vv_secure_zero(ctx->stripped, ctx->tcap);
if (ctx->ent_buf) vv_secure_zero(ctx->ent_buf, ctx->ent_cap);
if (ctx->src_buf) vv_secure_zero(ctx->src_buf, ctx->src_cap);
free(ctx->tmp); free(ctx->lit_buf); free(ctx->stripped); free(ctx->ent_buf);
free(ctx->src_buf);
matcher_free(&ctx->m);
/* Scrub the context itself in case it held sensitive options */
vv_secure_zero(ctx, sizeof(*ctx));
free(ctx);
}
int vv_cstream_reset(vv_cstream_t *ctx, const vv_options_t *opts) {
if (!ctx) return VV_ERR_PARAM;
/* Apply new options if provided. window_log cannot change without
* reallocating the matcher tables — reject the change. */
if (opts) {
uint8_t new_wlog = opts->window_log;
if (new_wlog == 0) new_wlog = 16;
if (new_wlog != ctx->wlog) return VV_ERR_PARAM;
ctx->opts = *opts;
}
/* Update chain_depth in case the mode changed */
uint32_t depth;
switch (ctx->opts.mode) {
case VV_MODE_ULTRA_FAST: depth = 4; break;
case VV_MODE_BALANCED: depth = 24; break;
case VV_MODE_EXTREME: depth = 256; break;
default: depth = 24;
}
if (ctx->opts.depth_override) {
depth = ctx->opts.depth_override;
if (depth > 4096) depth = 4096;
}
ctx->m.chain_depth = depth;
/* SPRINT 58: keep the single-probe flag in sync if the mode changed
* across reset (e.g. balanced stream reset to fast). */
ctx->m.single_probe = (ctx->opts.mode == VV_MODE_ULTRA_FAST) ? 1 : 0;
ctx->m.accel = ctx->opts.accel > 64 ? 64 : ctx->opts.accel;
ctx->m.no_rep = ctx->opts.no_rep ? 1 : 0;
matcher_reset(&ctx->m);
/* Reset sliding-window source buffer */
ctx->src_head = 0;
ctx->src_len = 0;
ctx->global_offset = 0;
/* Reset checksum */
if (ctx->opts.checksum) vv_xxh64_init(&ctx->cks, 0);
ctx->header_emitted = 0;
return VV_OK;
}
int vv_cstream_compress_chunk(vv_cstream_t *ctx,
const uint8_t *chunk, size_t chunk_len,
uint8_t *dst, size_t dst_cap,
size_t *written, int is_last) {
if (!ctx || !dst || !written) return VV_ERR_PARAM;
if (chunk_len > VV_MAX_BLOCK_SIZE) return VV_ERR_PARAM;
*written = 0;
uint8_t *op = dst;
size_t cap_left = dst_cap;
/* Emit frame header on first call */
if (!ctx->header_emitted) {
if (cap_left < sizeof(vv_frame_header_t)) return VV_ERR_OVERFLOW;
vv_frame_header_t fh;
memset(&fh, 0, sizeof(fh));
fh.magic = VV_MAGIC;
fh.version = 1;
fh.flags = ctx->opts.checksum ? 1 : 0;
fh.mode_hint = (uint8_t)ctx->opts.mode;
fh.window_log = ctx->wlog;
/* content_size unknown in streaming mode → 0 */
fh.content_size = 0;
memcpy(op, &fh, sizeof(fh));
op += sizeof(fh); cap_left -= sizeof(fh);
ctx->header_emitted = 1;
}
/* Append chunk to sliding-window source buffer.
* Compact the buffer if needed to stay under src_cap. We keep
* the last (window_size) bytes as match-lookback history. */
if (chunk_len > 0) {
size_t window = (size_t)1u << ctx->wlog;
size_t needed = ctx->src_len + chunk_len;
if (needed > ctx->src_cap) {
/* Compact: drop everything older than (window) bytes before end */
size_t keep = ctx->src_len > window ? window : ctx->src_len;
size_t drop = ctx->src_len - keep;
if (drop > 0) {
memmove(ctx->src_buf, ctx->src_buf + drop, keep);
ctx->src_len = keep;
/* Adjust matcher table/chain entries: positions were
* relative to src_buf[0] and are now shifted by -drop.
* Easiest correct approach: invalidate chains — they
* reference positions < limit automatically and are
* bounded-distance walked. The hash table's `table[h]`
* entries would now point at shifted positions, but
* we can shift them en masse. */
/* Shift matcher table entries (positions get re-based) */
for (uint32_t i = 0; i < VV_HC_SIZE; i++) {
if (ctx->m.table[i] >= (int32_t)drop)
ctx->m.table[i] -= (int32_t)drop;
else ctx->m.table[i] = -1;
}
for (uint32_t i = 0; i < VV_HC4_SIZE; i++) {
if (ctx->m.table4[i] >= (int32_t)drop)
ctx->m.table4[i] -= (int32_t)drop;
else ctx->m.table4[i] = -1;
}
/* Chain arrays are also indexed by position — shift those
* too, BUT the array is indexed by (pos & chain_mask) so
* we need to shift values (the successor position) while
* keeping the circular layout. For simplicity and safety,
* we rebuild conservatively: clear chain entries whose
* references would now be negative. */
for (uint32_t i = 0; i < (1u << ctx->wlog); i++) {
if (ctx->m.chain[i] >= (int32_t)drop)
ctx->m.chain[i] -= (int32_t)drop;
else ctx->m.chain[i] = -1;
if (ctx->m.hash4_chain[i] >= (int32_t)drop)
ctx->m.hash4_chain[i] -= (int32_t)drop;
else ctx->m.hash4_chain[i] = -1;
}
}
}
memcpy(ctx->src_buf + ctx->src_len, chunk, chunk_len);
ctx->src_len += chunk_len;
ctx->global_offset += chunk_len;
if (ctx->opts.checksum) vv_xxh64_update(&ctx->cks, chunk, chunk_len);
}
/* Emit block(s) for the newly added chunk_len bytes.
* block_start in the src_buf = ctx->src_len - chunk_len. */
if (chunk_len == 0 && is_last) {
/* Empty final chunk: emit empty raw-last block */
if (cap_left < 4) return VV_ERR_OVERFLOW;
uint32_t bh = vv_bh_pack(VV_BLOCK_RAW, 1, 0);
memcpy(op, &bh, 4); op += 4; cap_left -= 4;
} else if (chunk_len > 0) {
size_t block_start = ctx->src_len - chunk_len;
int stream_min_match = ctx->opts.format_v2 ? 3 : (int)VV_MIN_MATCH;
size_t block_sz = emit_block(ctx->src_buf, block_start, chunk_len, is_last,
&ctx->m, ctx->opts.mode, ctx->wlog,
ctx->tmp, ctx->tcap,
ctx->lit_buf, ctx->lit_cap,
ctx->stripped, ctx->ent_buf, ctx->ent_cap,
op, cap_left, stream_min_match,
ctx->opts.compat_v246_5_decoder,
NULL /* stream scrubs full caps at destroy */);
if (block_sz == 0) return VV_ERR_OVERFLOW;
op += block_sz; cap_left -= block_sz;
}
/* Emit frame footer on last chunk */
if (is_last && ctx->opts.checksum) {
if (cap_left < sizeof(vv_frame_footer_t)) return VV_ERR_OVERFLOW;
vv_frame_footer_t ff;
ff.checksum = vv_xxh64_finalize(&ctx->cks);
ff.footer_magic = 0x56564E44u;
memcpy(op, &ff, sizeof(ff));
op += sizeof(ff);
/* cap_left no longer read — function returns immediately below */
}
*written = (size_t)(op - dst);
return VV_OK;
}
/* ═══════════════════════════════════════════════════════════════
* MULTI-THREADED COMPRESSION
*
* Strategy: split input into chunks of chunk_size bytes. Each chunk
* is encoded independently via vv_compress() into its own .vv frame.
* Output frames are concatenated into dst. vv_decompress handles
* multi-frame input natively.
*
* When VV_ENABLE_THREADS is defined, use pthread to run N worker
* threads in parallel. Otherwise, run sequentially.
*
* Ratio cost: frames are independent — cross-frame match history
* is lost at chunk boundaries. For chunk_size ≥ 4 MB on
* compressible data, the ratio hit is typically < 2%.
* ═══════════════════════════════════════════════════════════════ */
#ifdef VV_ENABLE_THREADS
#include <pthread.h>
#include <unistd.h>
typedef struct {
const uint8_t *src;
size_t src_len;
uint8_t *dst;
size_t dst_cap;
const vv_options_t *opts;
int64_t result; /* compressed size, or error code */
} mt_task_t;
typedef struct {
mt_task_t *tasks;
size_t ntasks;
/* SPRINT 98 audit: removed redundant `volatile`. next_task is
* protected by the mutex below, which provides full memory
* ordering. `volatile` was misleading — it doesn't provide
* synchronization, only prevents compiler reordering, and the
* mutex already prevents both. */
size_t next_task;
pthread_mutex_t mutex;
} mt_pool_t;
static void *mt_worker(void *arg) {
mt_pool_t *pool = (mt_pool_t *)arg;
for (;;) {
pthread_mutex_lock(&pool->mutex);
size_t idx = pool->next_task++;
pthread_mutex_unlock(&pool->mutex);
if (idx >= pool->ntasks) break;
mt_task_t *t = &pool->tasks[idx];
t->result = vv_compress(t->src, t->src_len, t->dst, t->dst_cap, t->opts);
}
return NULL;
}
#endif
int64_t vv_compress_mt(const uint8_t *src, size_t src_len,
uint8_t *dst, size_t dst_cap,
const vv_options_t *opts,
unsigned int nthreads,
size_t chunk_size) {
/* SPRINT 95 audit: same as vv_compress — accept NULL opts and
* src_len=0 for API consistency. */
if (!dst) return VV_ERR_PARAM;
if (src_len > 0 && !src) return VV_ERR_PARAM;
vv_options_t local_opts;
if (!opts) {
vv_default_options(&local_opts);
opts = &local_opts;
}
if (chunk_size == 0) chunk_size = 4 * 1024 * 1024; /* 4 MB default */
if (chunk_size < VV_MAX_BLOCK_SIZE) chunk_size = VV_MAX_BLOCK_SIZE;
/* For small inputs, just use vv_compress directly — no speedup
* available and avoids the per-frame fixed overhead. */
if (src_len <= chunk_size) {
return vv_compress(src, src_len, dst, dst_cap, opts);
}
/* Split into N chunks */
size_t n_chunks = (src_len + chunk_size - 1) / chunk_size;
/* Allocate per-chunk temporary output buffers. Each could be up to
* vv_compress_bound(chunk_size), which can be ~4 MB * 1.01 for a
* 4 MB chunk. Total scratch = n_chunks * ~4 MB. */
uint8_t **chunk_dst = (uint8_t **)calloc(n_chunks, sizeof(uint8_t *));
int64_t *chunk_sz = (int64_t *)calloc(n_chunks, sizeof(int64_t));
if (!chunk_dst || !chunk_sz) {
free(chunk_dst); free(chunk_sz);
return VV_ERR_NOMEM;
}
size_t chunk_cap = vv_compress_bound(chunk_size);
int alloc_failed = 0;
for (size_t i = 0; i < n_chunks; i++) {
chunk_dst[i] = (uint8_t *)malloc(chunk_cap);
if (!chunk_dst[i]) { alloc_failed = 1; break; }
}
if (alloc_failed) {
for (size_t i = 0; i < n_chunks; i++) free(chunk_dst[i]);
free(chunk_dst); free(chunk_sz);
return VV_ERR_NOMEM;
}
#ifdef VV_ENABLE_THREADS
/* Determine thread count */
if (nthreads == 0) {
long n = sysconf(_SC_NPROCESSORS_ONLN);
nthreads = (n > 0) ? (unsigned int)n : 1;
}
if (nthreads > n_chunks) nthreads = (unsigned int)n_chunks;
if (nthreads == 0) nthreads = 1;
/* Build task list */
mt_task_t *tasks = (mt_task_t *)malloc(n_chunks * sizeof(mt_task_t));
if (!tasks) {
for (size_t i = 0; i < n_chunks; i++) free(chunk_dst[i]);
free(chunk_dst); free(chunk_sz);
return VV_ERR_NOMEM;
}
for (size_t i = 0; i < n_chunks; i++) {
size_t off = i * chunk_size;
size_t len = (off + chunk_size <= src_len) ? chunk_size : (src_len - off);
tasks[i].src = src + off;
tasks[i].src_len = len;
tasks[i].dst = chunk_dst[i];
tasks[i].dst_cap = chunk_cap;
tasks[i].opts = opts;
tasks[i].result = 0;
}
mt_pool_t pool;
pool.tasks = tasks;
pool.ntasks = n_chunks;
pool.next_task = 0;
pthread_mutex_init(&pool.mutex, NULL);
pthread_t *threads = (pthread_t *)malloc(nthreads * sizeof(pthread_t));
if (!threads) {
pthread_mutex_destroy(&pool.mutex);
free(tasks);
for (size_t i = 0; i < n_chunks; i++) free(chunk_dst[i]);
free(chunk_dst); free(chunk_sz);
return VV_ERR_NOMEM;
}
for (unsigned int t = 0; t < nthreads; t++)
pthread_create(&threads[t], NULL, mt_worker, &pool);
for (unsigned int t = 0; t < nthreads; t++)
pthread_join(threads[t], NULL);
free(threads);
pthread_mutex_destroy(&pool.mutex);
for (size_t i = 0; i < n_chunks; i++) chunk_sz[i] = tasks[i].result;
free(tasks);
#else
/* Sequential fallback: encode each chunk in turn. */
(void)nthreads;
for (size_t i = 0; i < n_chunks; i++) {
size_t off = i * chunk_size;
size_t len = (off + chunk_size <= src_len) ? chunk_size : (src_len - off);
chunk_sz[i] = vv_compress(src + off, len, chunk_dst[i], chunk_cap, opts);
}
#endif
/* Check for errors and total up sizes */
int64_t total = 0;
for (size_t i = 0; i < n_chunks; i++) {
if (chunk_sz[i] < 0) {
int64_t err = chunk_sz[i];
for (size_t j = 0; j < n_chunks; j++) free(chunk_dst[j]);
free(chunk_dst); free(chunk_sz);
return err;
}
total += chunk_sz[i];
}
if ((size_t)total > dst_cap) {
for (size_t i = 0; i < n_chunks; i++) free(chunk_dst[i]);
free(chunk_dst); free(chunk_sz);
return VV_ERR_OVERFLOW;
}
/* Concatenate frames into dst */
uint8_t *op = dst;
for (size_t i = 0; i < n_chunks; i++) {
memcpy(op, chunk_dst[i], (size_t)chunk_sz[i]);
op += chunk_sz[i];
free(chunk_dst[i]);
}
free(chunk_dst); free(chunk_sz);
return total;
}