ZXC 0.15.0
High-performance asymmetric lossless compression library
Loading...
Searching...
No Matches
zxc_dict.c
Go to the documentation of this file.
1// SPDX-License-Identifier: BSD-3-Clause
2/*
3 * ZXC - High-performance lossless compression
4 *
5 * Copyright (c) Bertrand Lebonnois and contributors.
6 */
7
13#include "../../include/zxc_dict.h"
14
15#include "../../include/zxc_buffer.h"
16#include "zxc_internal.h"
17
18// -------------------------------------------------------------------------
19// Dictionary ID
20// -------------------------------------------------------------------------
21
29uint32_t zxc_dict_id(const void* RESTRICT dict, const size_t dict_size,
30 const void* RESTRICT huf_lengths) {
31 if (UNLIKELY(!dict || dict_size == 0)) return 0;
32 const uint32_t base = zxc_checksum(dict, dict_size, 0, ZXC_CHECKSUM_RAPIDHASH);
33 if (!huf_lengths) return base;
34 return zxc_checksum(huf_lengths, ZXC_HUF_TABLE_SIZE, base, ZXC_CHECKSUM_RAPIDHASH);
35}
36
37// -------------------------------------------------------------------------
38// .zxd format: save / load / bound
39//
40// Layout (ZXC_DICT_HEADER_SIZE = 16 bytes + content + Huffman table):
41// 0x00 4 Magic (0x9CB0D1C7 LE)
42// 0x04 1 Version (2)
43// 0x05 1 Flags (bits 0-3: checksum algo id, 0=RapidHash; bits 4-7 reserved)
44// 0x06 2 Content size (u16 LE)
45// 0x08 4 Dictionary ID (u32 LE; covers content AND the Huffman table)
46// 0x0C 2 Reserved (0)
47// 0x0E 2 Header Checksum (zxc_hash16, computed with 0x0C-0x0F zeroed)
48// 0x10 N Content bytes
49// +N 128 Packed Huffman code lengths (always present)
50// -------------------------------------------------------------------------
51
57uint32_t zxc_dict_get_id(const void* buf, const size_t buf_size) {
58 if (UNLIKELY(!buf || buf_size < ZXC_DICT_HEADER_SIZE)) return 0;
59 const uint8_t* p = (const uint8_t*)buf;
60 if (UNLIKELY(zxc_le32(p) != ZXC_DICT_MAGIC)) return 0;
61 return zxc_le32(p + 8);
62}
63
69size_t zxc_dict_save_bound(const size_t content_size) {
70 return ZXC_DICT_HEADER_SIZE + content_size + ZXC_HUF_TABLE_SIZE;
71}
72
79int64_t zxc_dict_save(const void* RESTRICT content, const size_t content_size,
80 const void* RESTRICT huf_lengths, void* RESTRICT buf,
81 const size_t buf_capacity) {
82 if (UNLIKELY(!content || content_size == 0 || !huf_lengths)) return ZXC_ERROR_NULL_INPUT;
83 if (UNLIKELY(content_size > ZXC_DICT_SIZE_MAX)) return ZXC_ERROR_DICT_TOO_LARGE;
84
85 const size_t total = ZXC_DICT_HEADER_SIZE + content_size + ZXC_HUF_TABLE_SIZE;
86 if (UNLIKELY(buf_capacity < total)) return ZXC_ERROR_DST_TOO_SMALL;
87
88 uint8_t* dst = (uint8_t*)buf;
89
91 dst[4] = ZXC_DICT_VERSION;
92 dst[5] = 0; /* flags: reserved */
93 zxc_store_le16(dst + 6, (uint16_t)content_size);
94 zxc_store_le32(dst + 8, zxc_dict_id(content, content_size, (const uint8_t*)huf_lengths));
95 zxc_store_le32(dst + 12, 0); /* reserved (0x0C) + checksum (0x0E), zeroed before hashing */
96 const uint16_t sum = zxc_hash16(dst);
97 zxc_store_le16(dst + 14, sum);
98
99 ZXC_MEMCPY(dst + ZXC_DICT_HEADER_SIZE, content, content_size);
100 ZXC_MEMCPY(dst + ZXC_DICT_HEADER_SIZE + content_size, huf_lengths, ZXC_HUF_TABLE_SIZE);
101
102 return (int64_t)total;
103}
104
112int zxc_dict_load(const void* RESTRICT buf, const size_t buf_size,
113 const void** RESTRICT content_out, size_t* RESTRICT content_size_out,
114 const void** RESTRICT huf_out, uint32_t* RESTRICT dict_id_out) {
115 if (UNLIKELY(!buf || !content_out || !content_size_out)) return ZXC_ERROR_NULL_INPUT;
116 if (UNLIKELY(buf_size < ZXC_DICT_HEADER_SIZE)) return ZXC_ERROR_SRC_TOO_SMALL;
117
118 const uint8_t* src = (const uint8_t*)buf;
119
120 if (UNLIKELY(zxc_le32(src) != ZXC_DICT_MAGIC)) return ZXC_ERROR_BAD_MAGIC;
121 if (UNLIKELY(src[4] != ZXC_DICT_VERSION)) return ZXC_ERROR_BAD_VERSION;
122
123 const size_t content_size = zxc_le16(src + 6);
124 if (UNLIKELY(content_size == 0)) return ZXC_ERROR_CORRUPT_DATA;
125 if (UNLIKELY(content_size > ZXC_DICT_SIZE_MAX)) return ZXC_ERROR_DICT_TOO_LARGE;
126 if (UNLIKELY(buf_size < ZXC_DICT_HEADER_SIZE + content_size + ZXC_HUF_TABLE_SIZE))
127 return ZXC_ERROR_SRC_TOO_SMALL; // LCOV_EXCL_LINE
128
129 uint8_t temp[ZXC_DICT_HEADER_SIZE];
130 ZXC_MEMCPY(temp, src, sizeof(temp));
131 zxc_store_le32(temp + 12, 0); /* reserved (0x0C) + checksum (0x0E), zeroed before hashing */
132 const uint16_t expected_sum = zxc_hash16(temp);
133 if (UNLIKELY(zxc_le16(src + 14) != expected_sum)) return ZXC_ERROR_BAD_HEADER;
134
135 const uint8_t* content = src + ZXC_DICT_HEADER_SIZE;
136 const uint8_t* huf = content + content_size;
137
138 // Verify dict_id matches the (content, table) pair.
139 const uint32_t id = zxc_dict_id(content, content_size, huf);
140 if (UNLIKELY(zxc_le32(src + 8) != id)) return ZXC_ERROR_BAD_CHECKSUM;
141
142 *content_out = content;
143 *content_size_out = content_size;
144 if (huf_out) *huf_out = huf;
145 if (dict_id_out) *dict_id_out = id;
146
147 return ZXC_OK;
148}
149
156const void* zxc_dict_huf(const void* buf, const size_t buf_size) {
157 if (UNLIKELY(!buf || buf_size < ZXC_DICT_HEADER_SIZE)) return NULL;
158 const uint8_t* src = (const uint8_t*)buf;
159 if (UNLIKELY(zxc_le32(src) != ZXC_DICT_MAGIC)) return NULL;
160 if (UNLIKELY(src[4] != ZXC_DICT_VERSION)) return NULL;
161 const size_t content_size = zxc_le16(src + 6);
162 if (UNLIKELY(content_size == 0 ||
163 buf_size < ZXC_DICT_HEADER_SIZE + content_size + ZXC_HUF_TABLE_SIZE))
164 return NULL; // LCOV_EXCL_LINE
165 return src + ZXC_DICT_HEADER_SIZE + content_size;
166}
167
168// -------------------------------------------------------------------------
169// Dictionary training: k-gram frequency selection
170//
171// Algorithm:
172// 1. Concatenate all samples into a corpus.
173// 2. For each position in the corpus, hash the k-gram (k = MIN_MATCH_LEN)
174// and count occurrences in a fixed-size hash map.
175// 3. Build candidate segments: each starts at a frequent k-gram and extends
176// while neighbours stay frequent. Its score is the summed frequency of its
177// k-grams - its coverage of the corpus.
178// 4. Fill the dictionary greedily in descending coverage, zeroing each pick's
179// k-grams: one copy serves all future matches, so segments that collapse
180// are skipped and capacity goes to NEW patterns.
181// -------------------------------------------------------------------------
182
192static uint32_t zxc_dict_hash(const uint8_t* p) {
193 uint32_t v = zxc_le32(p);
194 v ^= (uint32_t)p[4];
195 return (v * ZXC_HASH_MULT32) >> (32 - ZXC_DICT_HASH_BITS);
196}
197
201typedef struct {
202 uint32_t offset;
203 uint16_t length;
204 uint32_t score;
205} zxc_dict_seg_t;
206
217static void zxc_dict_sift_down(zxc_dict_seg_t* RESTRICT a, size_t root, const size_t n) {
218 for (;;) {
219 size_t child = 2 * root + 1;
220 if (child >= n) break;
221 if (child + 1 < n && a[child + 1].score < a[child].score) child++;
222 if (a[root].score <= a[child].score) break;
223 const zxc_dict_seg_t t = a[root];
224 a[root] = a[child];
225 a[child] = t;
226 root = child;
227 }
228}
229
244static void zxc_dict_sort_segs_desc(zxc_dict_seg_t* RESTRICT a, const size_t n) {
245 if (UNLIKELY(n < 2)) return;
246 for (size_t i = n / 2; i-- > 0;) zxc_dict_sift_down(a, i, n);
247 for (size_t end = n; end > 1;) {
248 end--;
249 const zxc_dict_seg_t t = a[0];
250 a[0] = a[end];
251 a[end] = t;
252 zxc_dict_sift_down(a, 0, end);
253 }
254}
255
263int64_t zxc_train_dict(const void* const* RESTRICT samples, const size_t* RESTRICT sample_sizes,
264 const size_t n_samples, void* RESTRICT dict_buf,
265 const size_t dict_capacity) {
266 if (UNLIKELY(!samples || !sample_sizes || n_samples == 0 || !dict_buf || dict_capacity == 0))
267 return ZXC_ERROR_NULL_INPUT; // LCOV_EXCL_LINE
268 if (UNLIKELY(dict_capacity > ZXC_DICT_SIZE_MAX)) return ZXC_ERROR_DICT_TOO_LARGE;
269
270 // Step 1: concatenate samples
271 size_t corpus_size = 0;
272 for (size_t i = 0; i < n_samples; i++) corpus_size += sample_sizes[i];
273 if (UNLIKELY(corpus_size < ZXC_DICT_KGRAM_LEN)) return ZXC_ERROR_SRC_TOO_SMALL;
274
275 int64_t rc;
276 uint16_t* freq = NULL;
277 zxc_dict_seg_t* segs = NULL;
278 uint8_t* corpus = (uint8_t*)ZXC_MALLOC(corpus_size);
279 if (UNLIKELY(!corpus)) return ZXC_ERROR_MEMORY;
280 {
281 size_t pos = 0;
282 for (size_t i = 0; i < n_samples; i++) {
283 if (sample_sizes[i] > 0) ZXC_MEMCPY(corpus + pos, samples[i], sample_sizes[i]);
284 pos += sample_sizes[i];
285 }
286 }
287
288 // Step 2: count k-gram frequencies
289 freq = (uint16_t*)ZXC_CALLOC(ZXC_DICT_HASH_SIZE, sizeof(uint16_t));
290 if (UNLIKELY(!freq)) {
291 rc = ZXC_ERROR_MEMORY; // LCOV_EXCL_LINE
292 goto done; // LCOV_EXCL_LINE
293 }
294
295 // Sample positions rather than count them all: a full count saturates the
296 // 16-bit counters, the extension test then never stops and segments balloon
297 // into filler.
298 const size_t kgram_limit = corpus_size - ZXC_DICT_KGRAM_LEN + 1;
299 size_t freq_stride = kgram_limit / ZXC_DICT_SAMPLE_TARGET;
300 if (freq_stride < 1) freq_stride = 1;
301 for (size_t i = 0; i < kgram_limit; i += freq_stride) {
302 const uint32_t h = zxc_dict_hash(corpus + i);
303 if (freq[h] < UINT16_MAX) freq[h]++;
304 }
305
306 // Step 3: build candidate segments, scored by coverage. Starts are spread
307 // across the whole corpus - a fixed k-gram stride would exhaust the segment
308 // budget in the prefix and never see a large input's tail.
309 const size_t max_segs = corpus_size / ZXC_DICT_KGRAM_LEN;
310 const size_t seg_alloc = (max_segs < ZXC_DICT_MAX_SEGMENTS) ? max_segs : ZXC_DICT_MAX_SEGMENTS;
311 size_t stride = ZXC_DICT_KGRAM_LEN;
312 if (seg_alloc > 0 && corpus_size / seg_alloc > stride) stride = corpus_size / seg_alloc;
313
314 segs = (zxc_dict_seg_t*)ZXC_MALLOC(seg_alloc * sizeof(zxc_dict_seg_t));
315 if (UNLIKELY(!segs)) {
316 rc = ZXC_ERROR_MEMORY; // LCOV_EXCL_LINE
317 goto done; // LCOV_EXCL_LINE
318 }
319
320 size_t n_segs = 0;
321 for (size_t i = 0; i + ZXC_DICT_KGRAM_LEN <= corpus_size && n_segs < seg_alloc; i += stride) {
322 const uint32_t h = zxc_dict_hash(corpus + i);
323 const uint16_t f = freq[h];
324 if (f < 2) continue;
325
326 // Extend the segment as long as the next k-gram is also frequent, and
327 // accumulate coverage (summed k-gram frequency) as the score.
328 uint32_t coverage = f;
329 size_t end = i + ZXC_DICT_KGRAM_LEN;
330 while (end + ZXC_DICT_KGRAM_LEN <= corpus_size && end - i < 4096) {
331 const uint16_t nf = freq[zxc_dict_hash(corpus + end)];
332 if (nf < 2) break;
333 coverage += nf;
334 end += ZXC_DICT_KGRAM_LEN;
335 }
336
337 segs[n_segs].offset = (uint32_t)i;
338 segs[n_segs].length = (uint16_t)(end - i);
339 segs[n_segs].score = coverage;
340 n_segs++;
341 }
342
343 if (UNLIKELY(n_segs == 0)) {
344 // No frequent patterns. Use tail of corpus as dict.
345 const size_t copy = (corpus_size < dict_capacity) ? corpus_size : dict_capacity;
346 ZXC_MEMCPY(dict_buf, corpus + corpus_size - copy, copy);
347 rc = (int64_t)copy;
348 goto done;
349 }
350
351 // Step 4: pick greedily in descending coverage, zeroing each pick's k-grams
352 // so overlapping patterns aren't copied twice. Picks are compacted in place
353 // into segs[0..n_sel); placement is step 5.
354 zxc_dict_sort_segs_desc(segs, n_segs);
355
356 uint8_t* out = (uint8_t*)dict_buf;
357 size_t n_sel = 0;
358 size_t total = 0;
359
360 for (size_t i = 0; i < n_segs && total < dict_capacity; i++) {
361 const size_t seg_off = segs[i].offset;
362 const size_t seg_end = seg_off + segs[i].length;
363
364 // Recompute coverage from the decrementing table: skip the segment if
365 // earlier picks have already covered more than half of its k-grams.
366 uint32_t cur = 0;
367 for (size_t p = seg_off; p + ZXC_DICT_KGRAM_LEN <= seg_end; p += ZXC_DICT_KGRAM_LEN)
368 cur += freq[zxc_dict_hash(corpus + p)];
369 if (cur * 2 < segs[i].score) continue;
370
371 size_t copy = segs[i].length;
372 if (copy > dict_capacity - total) copy = dict_capacity - total;
373
374 // One copy in the dictionary serves all future matches: mark this
375 // segment's k-grams as covered so later segments cover new ground.
376 for (size_t p = seg_off; p + ZXC_DICT_KGRAM_LEN <= seg_end; p += ZXC_DICT_KGRAM_LEN)
377 freq[zxc_dict_hash(corpus + p)] = 0;
378
379 // Record the pick (n_sel <= i, so this never clobbers an unread entry).
380 segs[n_sel].offset = (uint32_t)seg_off;
381 segs[n_sel].length = (uint16_t)copy;
382 n_sel++;
383 total += copy;
384 }
385
386 // Released early: the emit loop below only needs the picks.
387 ZXC_FREE(freq);
388 freq = NULL;
389
390 // Step 5: emit in reverse so the highest-coverage segment lands at the END
391 // of the dict. The dict sits just before the data, so bytes near its end
392 // have the smallest offsets: cheapest to encode, last to fall out of the
393 // 16-bit window. No padding - short picks just make a shorter dict, whereas
394 // padding would raise the offset of everything after it.
395 size_t filled = 0;
396 for (size_t i = n_sel; i-- > 0;) {
397 ZXC_MEMCPY(out + filled, corpus + segs[i].offset, segs[i].length);
398 filled += segs[i].length;
399 }
400
401 // Nothing selected (every segment subsumed by earlier picks): fall back to
402 // the corpus tail so the dict is never empty, like the n_segs == 0 path.
403 if (UNLIKELY(filled == 0)) {
404 const size_t tail = (corpus_size < dict_capacity) ? corpus_size : dict_capacity;
405 ZXC_MEMCPY(out, corpus + corpus_size - tail, tail);
406 filled = tail;
407 }
408
409 rc = (int64_t)filled;
410
411done:
412 ZXC_FREE(segs);
413 ZXC_FREE(freq);
414 ZXC_FREE(corpus);
415 return rc;
416}
417
418// -------------------------------------------------------------------------
419// Shared literal Huffman table training (Tier-2)
420//
421// Compresses the samples with the freshly trained dictionary at level
422// ZXC_LEVEL_DENSITY and histograms the REAL post-LZ literals (cctx
423// lit_freq_acc hook). Raw bytes are a poor proxy: matches against the dict
424// remove most repeated content, so the two distributions differ a lot.
425//
426// Slices are ZXC_DICT_HUF_TRAIN_BLOCK bytes - the small-block regime is where
427// a shared table pays (per-block headers are unaffordable there) and where
428// literal density is highest.
429// -------------------------------------------------------------------------
430
437int zxc_train_dict_huf(const void* const* RESTRICT samples, const size_t* RESTRICT sample_sizes,
438 const size_t n_samples, const void* RESTRICT dict, const size_t dict_size,
439 uint8_t* RESTRICT huf_lengths_out) {
440 if (UNLIKELY(!samples || !sample_sizes || n_samples == 0 || !dict || dict_size == 0 ||
441 !huf_lengths_out))
443 if (UNLIKELY(dict_size > ZXC_DICT_SIZE_MAX)) return ZXC_ERROR_DICT_TOO_LARGE;
444
445 const size_t eff_chunk = zxc_block_size_ceil(dict_size + ZXC_DICT_HUF_TRAIN_BLOCK);
446
447 zxc_cctx_t cctx;
448 if (UNLIKELY(zxc_cctx_init(&cctx, eff_chunk, 1, ZXC_LEVEL_DENSITY, 0, dict_size) != ZXC_OK))
449 return ZXC_ERROR_MEMORY; // LCOV_EXCL_LINE
450
451 uint32_t freq[ZXC_HUF_NUM_SYMBOLS] = {0};
452 cctx.lit_freq_acc = freq;
453
454 const size_t out_cap = (size_t)zxc_compress_bound(eff_chunk);
455 uint8_t* out_scratch = (uint8_t*)ZXC_MALLOC(out_cap);
456 if (UNLIKELY(!out_scratch)) {
457 // LCOV_EXCL_START
458 zxc_cctx_free(&cctx);
459 return ZXC_ERROR_MEMORY;
460 // LCOV_EXCL_STOP
461 }
462
463 // [dict | slice] concat scratch carved by zxc_cctx_init (mode 1).
464 uint8_t* const work = cctx.dict_buffer;
465 ZXC_MEMCPY(work, dict, dict_size);
466
467 // Slice stride: process every slice while the corpus fits the budget,
468 // else 1 slice out of `stride` spread evenly across all samples.
469 size_t corpus_total = 0;
470 for (size_t s = 0; s < n_samples; s++) corpus_total += sample_sizes[s];
471 const size_t stride =
472 (corpus_total > ZXC_DICT_HUF_SAMPLE_BUDGET)
474 : 1;
475
476 int rc = ZXC_OK;
477 size_t slice_idx = 0;
478 for (size_t s = 0; s < n_samples && rc == ZXC_OK; s++) {
479 const uint8_t* sample = (const uint8_t*)samples[s];
480 const size_t sample_size = sample_sizes[s];
481 if (!sample || sample_size == 0) continue;
482
483 for (size_t off = 0; off < sample_size; off += ZXC_DICT_HUF_TRAIN_BLOCK, slice_idx++) {
484 if (slice_idx % stride != 0) continue;
485 const size_t slice = (sample_size - off < ZXC_DICT_HUF_TRAIN_BLOCK)
486 ? (sample_size - off)
488 ZXC_MEMCPY(work + dict_size, sample + off, slice);
489 const int r =
490 zxc_compress_chunk_wrapper(&cctx, work, dict_size + slice, out_scratch, out_cap, 0);
491 if (UNLIKELY(r < 0)) {
492 rc = r;
493 break;
494 }
495 }
496 }
497
498 if (rc == ZXC_OK) {
499 // A low-entropy corpus leaves no post-LZ literals - an empty histogram,
500 // not corrupt input. Detected on the histogram itself, so an all-zero
501 // table is emitted and every block falls back to its own; inferring
502 // "empty" from a build error code could mask a real failure.
503 uint32_t any = 0;
504 for (int i = 0; i < ZXC_HUF_NUM_SYMBOLS; i++) any |= freq[i];
505 if (any == 0) {
506 ZXC_MEMSET(huf_lengths_out, 0, ZXC_HUF_TABLE_SIZE);
507 } else {
508 // No coverage smoothing: under an 8-bit cap a code over all 256
509 // symbols can only be the degenerate all-8-bit one (Kraft equality),
510 // which compresses nothing. Unseen symbols stay code-less and their
511 // blocks fall back to a per-block table.
512 uint8_t code_len[ZXC_HUF_NUM_SYMBOLS];
514 if (rc == ZXC_OK) {
515 // Dict tables serve the most literal-bound decode path, so the
516 // flat/length nudge pays off most here.
517 (void)zxc_huf_nudge_code_lengths(freq, code_len, NULL, 0,
519 zxc_huf_pack_lengths(code_len, huf_lengths_out);
520 }
521 }
522 }
523
524 ZXC_FREE(out_scratch);
525 zxc_cctx_free(&cctx);
526 return rc;
527}
528
529// -------------------------------------------------------------------------
530// All-in-one convenience: samples -> ready-to-write .zxd bytes
531// -------------------------------------------------------------------------
532
540int64_t zxc_dict_train(const void* const* RESTRICT samples, const size_t* RESTRICT sample_sizes,
541 const size_t n_samples, void* RESTRICT zxd_buf, const size_t zxd_capacity) {
542 if (UNLIKELY(!samples || !sample_sizes || n_samples == 0 || !zxd_buf || zxd_capacity == 0))
544
545 // Train the content into a temporary buffer (max-content sized), then the
546 // shared table from that content, then serialize both into zxd_buf. The
547 // two-phase training is a real data dependency (the table needs the trained
548 // content to histogram post-LZ literals); this hides it behind one call.
549 uint8_t* content = (uint8_t*)ZXC_MALLOC(ZXC_DICT_SIZE_MAX);
550 if (UNLIKELY(!content)) return ZXC_ERROR_MEMORY;
551
552 int64_t out;
553 const int64_t content_size =
554 zxc_train_dict(samples, sample_sizes, n_samples, content, ZXC_DICT_SIZE_MAX);
555 if (UNLIKELY(content_size <= 0)) {
556 // LCOV_EXCL_START
557 out = (content_size < 0) ? content_size : ZXC_ERROR_SRC_TOO_SMALL;
558 goto done;
559 // LCOV_EXCL_STOP
560 }
561
562 {
563 uint8_t huf[ZXC_HUF_TABLE_SIZE];
564 const int hrc = zxc_train_dict_huf(samples, sample_sizes, n_samples, content,
565 (size_t)content_size, huf);
566 if (UNLIKELY(hrc != ZXC_OK)) {
567 // LCOV_EXCL_START
568 out = hrc;
569 goto done;
570 // LCOV_EXCL_STOP
571 }
572 out = zxc_dict_save(content, (size_t)content_size, huf, zxd_buf, zxd_capacity);
573 }
574
575done:
576 ZXC_FREE(content);
577 return out;
578}
uint64_t zxc_compress_bound(const size_t input_size)
Maximum compressed size for an input of input_size bytes.
int64_t zxc_dict_train(const void *const *samples, const size_t *sample_sizes, const size_t n_samples, void *zxd_buf, const size_t zxd_capacity)
One-shot training: sample buffers in, ready-to-write .zxd bytes out.
Definition zxc_dict.c:540
int zxc_train_dict_huf(const void *const *samples, const size_t *sample_sizes, const size_t n_samples, const void *dict, const size_t dict_size, uint8_t *huf_lengths_out)
Trains the shared literal Huffman table for a dictionary (Tier-2).
Definition zxc_dict.c:437
int64_t zxc_train_dict(const void *const *samples, const size_t *sample_sizes, const size_t n_samples, void *dict_buf, const size_t dict_capacity)
Trains a raw dictionary from sample buffers by k-gram coverage.
Definition zxc_dict.c:263
size_t zxc_dict_save_bound(const size_t content_size)
Worst-case .zxd byte size for a dictionary of content_size bytes.
Definition zxc_dict.c:69
const void * zxc_dict_huf(const void *buf, const size_t buf_size)
Returns a pointer to the packed Huffman table inside a .zxd buffer.
Definition zxc_dict.c:156
int zxc_dict_load(const void *buf, const size_t buf_size, const void **content_out, size_t *content_size_out, const void **huf_out, uint32_t *dict_id_out)
Parses a .zxd buffer, returning in-buffer views of content and table.
Definition zxc_dict.c:112
int64_t zxc_dict_save(const void *content, const size_t content_size, const void *huf_lengths, void *buf, const size_t buf_capacity)
Serializes dictionary content + Huffman table into the .zxd format.
Definition zxc_dict.c:79
uint32_t zxc_dict_get_id(const void *buf, const size_t buf_size)
Extracts the stored dict_id from a serialized .zxd buffer.
Definition zxc_dict.c:57
uint32_t zxc_dict_id(const void *dict, const size_t dict_size, const void *huf_lengths)
Computes the dictionary identifier for dict (and optional table).
Definition zxc_dict.c:29
#define ZXC_DICT_SIZE_MAX
Maximum dictionary content size in bytes (64 KB - 1).
#define ZXC_DICT_MAGIC
Magic word opening a .zxd dictionary file, stored little-endian (C7 D1 B0 9C).
#define ZXC_DICT_HEADER_SIZE
Size of the .zxd dictionary file header in bytes.
#define ZXC_HUF_TABLE_SIZE
Size of a packed literal Huffman code-lengths table (256 symbols, 4 bits each): the shared table in a...
@ ZXC_ERROR_BAD_MAGIC
Invalid magic word in file header.
Definition zxc_error.h:48
@ ZXC_ERROR_BAD_VERSION
Unsupported file format version.
Definition zxc_error.h:49
@ ZXC_ERROR_CORRUPT_DATA
Corrupted compressed data.
Definition zxc_error.h:56
@ ZXC_ERROR_NULL_INPUT
Required input pointer is NULL.
Definition zxc_error.h:62
@ ZXC_ERROR_MEMORY
Memory allocation failure.
Definition zxc_error.h:41
@ ZXC_ERROR_BAD_HEADER
Corrupted or invalid header (checksum mismatch).
Definition zxc_error.h:50
@ ZXC_ERROR_DST_TOO_SMALL
Destination buffer too small.
Definition zxc_error.h:44
@ ZXC_ERROR_SRC_TOO_SMALL
Source buffer too small or truncated input.
Definition zxc_error.h:45
@ ZXC_ERROR_BAD_CHECKSUM
A block's decoded bytes fail its checksum, or the archive digest mismatches.
Definition zxc_error.h:51
@ ZXC_OK
Success (no error).
Definition zxc_error.h:38
@ ZXC_ERROR_DICT_TOO_LARGE
Dictionary exceeds maximum allowed size.
Definition zxc_error.h:71
#define ZXC_MALLOC(size)
Heap allocator.
Definition zxc_deps.h:68
#define ZXC_CHECKSUM_RAPIDHASH
Checksum algorithm id for RapidHash (default, sole implementation).
#define ZXC_DICT_MAX_SEGMENTS
Maximum number of candidate segments the dictionary trainer keeps.
#define ZXC_DICT_VERSION
Current dictionary file format version.
#define ZXC_CALLOC(nmemb, size)
Zero-initialised heap allocator.
Definition zxc_deps.h:74
#define ZXC_DICT_HASH_SIZE
Number of buckets in the dictionary trainer's frequency table.
#define ZXC_FREE(ptr)
Heap deallocator.
Definition zxc_deps.h:86
#define ZXC_HASH_MULT32
Marsaglia 32-bit multiplier: the 4-byte LZ and dictionary hash.
#define ZXC_HUF_MAX_CODE_LEN_DENSITY
Encoder code-length cap for levels up to ZXC_LEVEL_DENSITY (below ZXC_LEVEL_ULTRA): shallow 8-bit tre...
#define ZXC_DICT_HUF_TRAIN_BLOCK
Training block size for the shared-table literal statistics.
#define ZXC_DICT_SAMPLE_TARGET
Target number of sampled k-gram positions for the trainer's frequency estimate.
#define ZXC_DICT_HUF_SAMPLE_BUDGET
Cap on the corpus bytes compressed by the literal-table trainer: the histogram converges early,...
#define ZXC_DICT_KGRAM_LEN
K-gram length scanned by the dictionary trainer.
#define ZXC_DICT_HASH_BITS
Address bits for the dictionary trainer's k-gram frequency table.
#define ZXC_HUF_NUM_SYMBOLS
Alphabet size: one entry per possible byte value.
@ ZXC_LEVEL_DENSITY
Higher density: adds Huffman-coded literals on top of COMPACT.
Compression / decompression context.
uint32_t * lit_freq_acc
Trainer hook: when non-NULL, the GLO encoder accumulates post-LZ literal byte frequencies here (256 e...
uint8_t * dict_buffer
[dict | data] concat scratch carved from memory_block when dict_size > 0 (NULL otherwise).
void zxc_cctx_free(zxc_cctx_t *ctx)
Releases all resources owned by a compression context.
Definition zxc_common.c:400
int zxc_cctx_init(zxc_cctx_t *ctx, const size_t chunk_size, const int mode, const int level, const int checksum_enabled, const size_t dict_size)
Initialises a compression / decompression context, allocating the persistent buffer with ZXC_ALIGNED_...
Definition zxc_common.c:339
int zxc_compress_chunk_wrapper(zxc_cctx_t *ctx, const uint8_t *chunk, const size_t src_sz, uint8_t *dst, const size_t dst_cap, const uint64_t block_index)
Compresses one chunk into a single ZXC block (the compression hot path).
static void zxc_dict_sift_down(zxc_dict_seg_t *a, size_t root, const size_t n)
Restore the min-heap property at root over the range a[0..n).
Definition zxc_dict.c:217
static void zxc_dict_sort_segs_desc(zxc_dict_seg_t *a, const size_t n)
Sort a[0..n) by zxc_dict_seg_t::score in descending order.
Definition zxc_dict.c:244
static uint32_t zxc_dict_hash(const uint8_t *p)
Hashes the k-gram at p into a frequency-table bucket index.
Definition zxc_dict.c:192
int zxc_huf_build_code_lengths(const uint32_t *freq, uint8_t *code_len, void *scratch, const int max_code_len)
Build length-limited per-symbol Huffman code lengths from frequencies.
void zxc_huf_pack_lengths(const uint8_t *code_len, uint8_t *out)
Pack per-symbol code lengths into the 128-byte nibble header.
int zxc_huf_nudge_code_lengths(const uint32_t *freq, uint8_t *code_len, void *scratch, const size_t scratch_cap, const int max_code_len)
Optionally reshape freshly built code lengths for faster PivCo decode.
Internal definitions, constants, SIMD helpers, and utility functions.
static uint32_t zxc_le32(const void *p)
Reads a little-endian 32-bit value from a possibly unaligned address.
static size_t zxc_block_size_ceil(const size_t v)
Branchless bit_ceil: smallest power of two >= v, clamped to ZXC_BLOCK_SIZE_MIN.
static uint16_t zxc_le16(const void *p)
Reads a little-endian 16-bit value from a possibly unaligned address.
static uint16_t zxc_hash16(const uint8_t *p)
Computes the 2-byte checksum for file and dictionary headers.
static void zxc_store_le16(void *p, const uint16_t v)
Writes a 16-bit value little-endian to a possibly unaligned address.
static void zxc_store_le32(void *p, const uint32_t v)
Writes a 32-bit value little-endian to a possibly unaligned address.
static uint32_t zxc_checksum(const void *input, const size_t len, const uint64_t seed, const uint8_t hash_method)
Calculates a 32-bit hash for a given input buffer.