/src/zxc/src/lib/zxc_internal.h
Line | Count | Source |
1 | | /* |
2 | | * ZXC - High-performance lossless compression |
3 | | * |
4 | | * Copyright (c) 2025-2026 Bertrand Lebonnois and contributors. |
5 | | * SPDX-License-Identifier: BSD-3-Clause |
6 | | */ |
7 | | |
8 | | /** |
9 | | * @file zxc_internal.h |
10 | | * @brief Internal definitions, constants, SIMD helpers, and utility functions. |
11 | | * |
12 | | * This header is **not** part of the public API. It is shared across the |
13 | | * library's translation units and contains: |
14 | | * - Platform detection and SIMD intrinsic includes. |
15 | | * - Compiler-abstraction macros (LIKELY, PREFETCH, MEMCPY, ALIGN, ...). |
16 | | * - Endianness detection and byte-swap helpers. |
17 | | * - File-format constants (magic word, header sizes, block sizes, ...). |
18 | | * - Inline helpers for hashing, endian-safe loads/stores, bit manipulation, |
19 | | * aligned allocation, and bitstream reading. |
20 | | * - Internal function prototypes for chunk-level compression/decompression. |
21 | | * |
22 | | * @warning Do not include this header from user code; use the public headers |
23 | | * zxc_buffer.h or zxc_stream.h instead. |
24 | | */ |
25 | | |
26 | | #ifndef ZXC_INTERNAL_H |
27 | | #define ZXC_INTERNAL_H |
28 | | |
29 | | #include "zxc_deps.h" /* libc deps: <limits.h>, <stdint.h>, <stdlib.h>, <string.h>, |
30 | | and the ZXC_MALLOC / ZXC_ALIGNED_MALLOC macros. |
31 | | Vendor this file to retarget non-libc environments. */ |
32 | | |
33 | | #include "../../include/zxc_buffer.h" |
34 | | #include "../../include/zxc_constants.h" |
35 | | #include "../../include/zxc_error.h" |
36 | | #include "../../include/zxc_seekable.h" |
37 | | #include "rapidhash.h" |
38 | | |
39 | | #ifdef __cplusplus |
40 | | extern "C" { |
41 | | #endif |
42 | | |
43 | | /** |
44 | | * @defgroup internal Internal Helpers |
45 | | * @brief Platform abstractions, constants, and utility functions (private). |
46 | | * @{ |
47 | | */ |
48 | | |
49 | | /** |
50 | | * @name Atomic Qualifier |
51 | | * @brief Provides a portable atomic / volatile qualifier. |
52 | | * |
53 | | * If C11 atomics are available, @c ZXC_ATOMIC expands to @c _Atomic; |
54 | | * otherwise it falls back to @c volatile. |
55 | | * @{ |
56 | | */ |
57 | | #if !defined(__cplusplus) && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && \ |
58 | | !defined(__STDC_NO_ATOMICS__) |
59 | | #include <stdatomic.h> |
60 | | #define ZXC_ATOMIC _Atomic |
61 | | #define ZXC_USE_C11_ATOMICS 1 |
62 | | #else |
63 | | #define ZXC_ATOMIC volatile |
64 | | #define ZXC_USE_C11_ATOMICS 0 |
65 | | #endif |
66 | | /** @} */ /* end of Atomic Qualifier */ |
67 | | |
68 | | /** |
69 | | * @name SIMD Intrinsics & Compiler Macros |
70 | | * @brief Auto-detected SIMD feature macros for x86 (SSE/AVX) and ARM (NEON). |
71 | | * |
72 | | * Depending on the target architecture and compiler flags the following macros |
73 | | * may be defined: |
74 | | * - @c ZXC_USE_AVX512 - AVX-512F + AVX-512BW available. |
75 | | * - @c ZXC_USE_AVX2 - AVX2 available. |
76 | | * - @c ZXC_USE_SSE2 - SSE2 (x86-64 baseline) available. |
77 | | * - @c ZXC_USE_NEON64 - AArch64 NEON available. |
78 | | * - @c ZXC_USE_NEON32 - ARMv7 NEON available. |
79 | | * |
80 | | * Note: @c -mavx2 / @c -mavx512f imply @c __SSE2__, so @c ZXC_USE_SSE2 is |
81 | | * also defined in the AVX variants. The hand-written SIMD code paths therefore |
82 | | * order their preprocessor branches AVX512 -> AVX2 -> SSE2 so the widest |
83 | | * available path wins; the SSE2 branch is the active one in the @c _default |
84 | | * variant on x86-64 (no AVX2/AVX512 flags). SSE2 is the x86-64 baseline, so no |
85 | | * dedicated @c _sse2 variant exists: @c _default covers every 64-bit x86 CPU |
86 | | * (and i686 with @c -msse2). The handful of |
87 | | * operations that would otherwise require SSE4.1 (@c _mm_max_epu32, |
88 | | * @c _mm_blendv_epi8, @c _mm_packus_epi32) or SSSE3 (@c _mm_shuffle_epi8) are |
89 | | * emulated with pure SSE2 instruction sequences or fall back to scalar code. |
90 | | * |
91 | | * Define @c ZXC_DISABLE_SIMD to gate all hand-written SIMD paths (intrinsics, |
92 | | * inline assembly). Compiler auto-vectorisation is unaffected. |
93 | | * @{ |
94 | | */ |
95 | | #ifndef ZXC_DISABLE_SIMD |
96 | | #if defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86) |
97 | | #include <immintrin.h> |
98 | | #include <nmmintrin.h> |
99 | | #if defined(__AVX512F__) && defined(__AVX512BW__) |
100 | | #ifndef ZXC_USE_AVX512 |
101 | | #define ZXC_USE_AVX512 |
102 | | #endif |
103 | | #endif |
104 | | #if defined(__AVX2__) |
105 | | #ifndef ZXC_USE_AVX2 |
106 | | #define ZXC_USE_AVX2 |
107 | | #endif |
108 | | #endif |
109 | | #if defined(__SSE2__) || defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2) |
110 | | #ifndef ZXC_USE_SSE2 |
111 | | #define ZXC_USE_SSE2 |
112 | | #endif |
113 | | #endif |
114 | | #elif (defined(__ARM_NEON) || defined(__ARM_NEON__) || defined(_M_ARM64) || \ |
115 | | defined(ZXC_USE_NEON32) || defined(ZXC_USE_NEON64)) |
116 | | #if !defined(_MSC_VER) |
117 | | #include <arm_acle.h> |
118 | | #endif |
119 | | #include <arm_neon.h> |
120 | | #if defined(__aarch64__) || defined(_M_ARM64) |
121 | | #ifndef ZXC_USE_NEON64 |
122 | | #define ZXC_USE_NEON64 |
123 | | #endif |
124 | | #else |
125 | | #ifndef ZXC_USE_NEON32 |
126 | | #define ZXC_USE_NEON32 |
127 | | #endif |
128 | | #endif |
129 | | #endif |
130 | | #endif /* ZXC_DISABLE_SIMD */ |
131 | | /** @} */ /* end of SIMD Intrinsics */ |
132 | | |
133 | | /** |
134 | | * @name Compiler Abstractions |
135 | | * @brief Portable wrappers for branch hints, prefetch, memory ops, alignment, |
136 | | * and forced inlining. |
137 | | * @{ |
138 | | */ |
139 | | |
140 | | #if defined(__GNUC__) || defined(__clang__) |
141 | | /** @def LIKELY |
142 | | * @brief Branch prediction hint: expression is likely true. |
143 | | * @param x Expression to evaluate. |
144 | | */ |
145 | 268M | #define LIKELY(x) (__builtin_expect(!!(x), 1)) |
146 | | |
147 | | /** @def UNLIKELY |
148 | | * @brief Branch prediction hint: expression is unlikely to be true. |
149 | | * @param x Expression to evaluate. |
150 | | */ |
151 | 28.1G | #define UNLIKELY(x) (__builtin_expect(!!(x), 0)) |
152 | | |
153 | | /** @def RESTRICT |
154 | | * @brief Pointer aliasing hint (maps to __restrict__). |
155 | | */ |
156 | | #define RESTRICT __restrict__ |
157 | | |
158 | | /** @def ZXC_PREFETCH_READ |
159 | | * @brief Prefetch data for reading. |
160 | | * @param ptr Pointer to data to prefetch. |
161 | | */ |
162 | 10.7G | #define ZXC_PREFETCH_READ(ptr) __builtin_prefetch((const void*)(ptr), 0, 3) |
163 | | |
164 | | /** @def ZXC_MEMCPY |
165 | | * @brief Optimized memory copy using compiler built-in. |
166 | | */ |
167 | 42.3G | #define ZXC_MEMCPY(dst, src, n) __builtin_memcpy(dst, src, n) |
168 | | |
169 | | /** @def ZXC_MEMSET |
170 | | * @brief Optimized memory set using compiler built-in. |
171 | | */ |
172 | 3.00M | #define ZXC_MEMSET(dst, val, n) __builtin_memset(dst, val, n) |
173 | | |
174 | | /** @def ZXC_ALIGN |
175 | | * @brief Specifies memory alignment for a variable or structure. |
176 | | * @param x Alignment boundary in bytes (must be a power of 2). |
177 | | */ |
178 | | #define ZXC_ALIGN(x) __attribute__((aligned(x))) |
179 | | |
180 | | /** @def ZXC_ALWAYS_INLINE |
181 | | * @brief Forces a function to be inlined at all optimization levels. |
182 | | */ |
183 | | #define ZXC_ALWAYS_INLINE inline __attribute__((always_inline)) |
184 | | |
185 | | /** @def ZXC_NOINLINE |
186 | | * @brief Prevents a function from being inlined into its callers. |
187 | | */ |
188 | | #define ZXC_NOINLINE __attribute__((noinline)) |
189 | | |
190 | | /** @def ZXC_COLD |
191 | | * @brief Marks a function as rarely executed: optimized for size and placed |
192 | | * in a cold text section, away from the hot paths' i-cache footprint. |
193 | | * No MSVC equivalent -- expands to nothing there (noinline still keeps |
194 | | * the body out of the caller). |
195 | | */ |
196 | | #define ZXC_COLD __attribute__((cold)) |
197 | | |
198 | | #elif defined(_MSC_VER) |
199 | | #include <intrin.h> |
200 | | #if defined(_M_IX86) || defined(_M_X64) || defined(_M_AMD64) |
201 | | #include <xmmintrin.h> |
202 | | #define ZXC_PREFETCH_READ(ptr) _mm_prefetch((const char*)(ptr), _MM_HINT_T0) |
203 | | #else |
204 | | #define ZXC_PREFETCH_READ(ptr) __prefetch((const void*)(ptr)) |
205 | | #endif |
206 | | #define LIKELY(x) (x) |
207 | | #define UNLIKELY(x) (x) |
208 | | #define RESTRICT __restrict |
209 | | #pragma intrinsic(memcpy, memset) |
210 | | #define ZXC_MEMCPY(dst, src, n) memcpy(dst, src, n) |
211 | | #define ZXC_MEMSET(dst, val, n) memset(dst, val, n) |
212 | | |
213 | | /** @def ZXC_ALIGN |
214 | | * @brief Specifies memory alignment for a variable or structure (MSVC). |
215 | | * @param x Alignment boundary in bytes (must be a power of 2). |
216 | | */ |
217 | | #define ZXC_ALIGN(x) __declspec(align(x)) |
218 | | |
219 | | /** @def ZXC_ALWAYS_INLINE |
220 | | * @brief Forces a function to be inlined at all optimization levels (MSVC). |
221 | | */ |
222 | | #define ZXC_ALWAYS_INLINE __forceinline |
223 | | |
224 | | /** @def ZXC_NOINLINE |
225 | | * @brief Prevents a function from being inlined into its callers (MSVC). |
226 | | */ |
227 | | #define ZXC_NOINLINE __declspec(noinline) |
228 | | /** @copydoc ZXC_COLD */ |
229 | | #define ZXC_COLD |
230 | | #pragma intrinsic(_BitScanReverse) |
231 | | #else |
232 | | #define LIKELY(x) (x) |
233 | | #define UNLIKELY(x) (x) |
234 | | #define RESTRICT |
235 | | #define ZXC_PREFETCH_READ(ptr) |
236 | | #define ZXC_MEMCPY(dst, src, n) memcpy(dst, src, n) |
237 | | #define ZXC_MEMSET(dst, val, n) memset(dst, val, n) |
238 | | |
239 | | /** @def ZXC_ALWAYS_INLINE |
240 | | * @brief Forces a function to be inlined (fallback for non-GCC/Clang/MSVC compilers). |
241 | | */ |
242 | | #define ZXC_ALWAYS_INLINE inline |
243 | | |
244 | | /** @def ZXC_NOINLINE |
245 | | * @brief Prevents inlining (best-effort no-op fallback for unknown compilers). |
246 | | */ |
247 | | #define ZXC_NOINLINE |
248 | | #define ZXC_COLD |
249 | | |
250 | | #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L |
251 | | #include <stdalign.h> |
252 | | /** @def ZXC_ALIGN |
253 | | * @brief Specifies memory alignment using C11 _Alignas. |
254 | | * @param x Alignment boundary in bytes (must be a power of 2). |
255 | | */ |
256 | | #define ZXC_ALIGN(x) _Alignas(x) |
257 | | #else |
258 | | /** @def ZXC_ALIGN |
259 | | * @brief No-op alignment macro for compilers without alignment support. |
260 | | * @param x Ignored (alignment not supported). |
261 | | */ |
262 | | #define ZXC_ALIGN(x) |
263 | | #endif |
264 | | #endif |
265 | | /** @} */ /* end of Compiler Abstractions */ |
266 | | |
267 | | // Heap allocator and cache-line-aligned allocator macros are now defined |
268 | | // in @c zxc_deps.h (included at the top of this header), so non-libc |
269 | | // targets can override them by vendoring that single file. |
270 | | |
271 | | /** |
272 | | * @name Endianness Detection |
273 | | * @brief Compile-time detection of host byte order. |
274 | | * |
275 | | * Defines exactly one of @c ZXC_LITTLE_ENDIAN or @c ZXC_BIG_ENDIAN. |
276 | | * @{ |
277 | | */ |
278 | | #ifndef ZXC_LITTLE_ENDIAN |
279 | | #if defined(_WIN32) || defined(__LITTLE_ENDIAN__) || \ |
280 | | (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) |
281 | | #define ZXC_LITTLE_ENDIAN |
282 | | #elif defined(__BIG_ENDIAN__) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) |
283 | | #define ZXC_BIG_ENDIAN |
284 | | #else |
285 | | #warning "Endianness not detected, defaulting to little-endian" |
286 | | #define ZXC_LITTLE_ENDIAN |
287 | | #endif |
288 | | #endif |
289 | | /** @} */ /* end of Endianness Detection */ |
290 | | |
291 | | /** |
292 | | * @name Byte-Swap Helpers |
293 | | * @brief 16/32/64-bit byte-swap macros (only defined under @c ZXC_BIG_ENDIAN). |
294 | | * @{ |
295 | | */ |
296 | | #ifdef ZXC_BIG_ENDIAN |
297 | | #if defined(__GNUC__) || defined(__clang__) |
298 | | #define ZXC_BSWAP16(x) __builtin_bswap16(x) |
299 | | #define ZXC_BSWAP32(x) __builtin_bswap32(x) |
300 | | #define ZXC_BSWAP64(x) __builtin_bswap64(x) |
301 | | #elif defined(_MSC_VER) |
302 | | #define ZXC_BSWAP16(x) _byteswap_ushort(x) |
303 | | #define ZXC_BSWAP32(x) _byteswap_ulong(x) |
304 | | #define ZXC_BSWAP64(x) _byteswap_uint64(x) |
305 | | #else |
306 | | #define ZXC_BSWAP16(x) ((uint16_t)(((x) >> 8) | ((x) << 8))) |
307 | | #define ZXC_BSWAP32(x) \ |
308 | | ((uint32_t)(((x) >> 24) | (((x) >> 8) & 0xFF00) | (((x) << 8) & 0xFF0000) | ((x) << 24))) |
309 | | #define ZXC_BSWAP64(x) \ |
310 | | ((uint64_t)(((uint64_t)ZXC_BSWAP32((uint32_t)(x)) << 32) | ZXC_BSWAP32((uint32_t)((x) >> 32)))) |
311 | | #endif |
312 | | #endif |
313 | | /** @} */ /* end of Byte-Swap Helpers */ |
314 | | |
315 | | /** |
316 | | * @name File Format Constants |
317 | | * @brief Magic words, header sizes, block sizes, and related constants. |
318 | | * @{ |
319 | | */ |
320 | | |
321 | | /** @brief Magic word identifying ZXC files (little-endian 0x9CB02EF5). */ |
322 | 34.9k | #define ZXC_MAGIC_WORD 0x9CB02EF5U |
323 | | /** @brief Current on-disk file format version. The decoder accepts only this |
324 | | * version; Older versions are rejected with ZXC_ERROR_BAD_VERSION. */ |
325 | 34.9k | #define ZXC_FILE_FORMAT_VERSION 8 |
326 | | |
327 | | /** @brief Safety padding appended to buffers to tolerate overruns. */ |
328 | 649k | #define ZXC_PAD_SIZE 32 |
329 | | /** |
330 | | * @brief Readable bytes a GLO/GHI payload guarantees after its literal section. |
331 | | * |
332 | | * A wire guarantee, not a buffer allowance - hence separate from |
333 | | * @ref ZXC_PAD_SIZE even though the values match. RAW literals point into the |
334 | | * caller's buffer and @ref zxc_decode_copy_literals overshoots by up to 31 B, |
335 | | * so it needs readable bytes behind it. |
336 | | * |
337 | | * The following sections usually supply them for free (tokens + offsets clear |
338 | | * 32 B from 16 sequences on); the encoder pads only the shortfall, inside the |
339 | | * extras section. Extras are read on demand, so an over-long end pointer is |
340 | | * harmless - no wire field, nothing to validate about the padding. |
341 | | */ |
342 | 170k | #define ZXC_BLOCK_LIT_SLACK 32 |
343 | | /** |
344 | | * @brief Tail padding required on the decompression destination buffer. |
345 | | * |
346 | | * The decoder's fast path uses speculative wild-copy writes and gates |
347 | | * fast-loop entry on @c d_end - ZXC_DECOMPRESS_TAIL_PAD. Sizing |
348 | | * @c dst_capacity to @c uncompressed_size + ZXC_DECOMPRESS_TAIL_PAD |
349 | | * guarantees the fast path is reachable and that tail bounds checks |
350 | | * never spuriously reject the last literals of a valid block. |
351 | | * |
352 | | * @see zxc_decompress_block_bound() |
353 | | */ |
354 | 264k | #define ZXC_DECOMPRESS_TAIL_PAD (ZXC_PAD_SIZE * 66) |
355 | | /** @brief Assumed CPU cache line size for alignment. */ |
356 | 2.70M | #define ZXC_CACHE_LINE_SIZE 64 |
357 | | /** @brief Bitmask for cache-line alignment checks. */ |
358 | 2.70M | #define ZXC_ALIGNMENT_MASK (ZXC_CACHE_LINE_SIZE - 1) |
359 | | /** @brief Round @p x up to the next cache-line boundary. */ |
360 | 1.35M | #define ZXC_ALIGN_CL(x) (((x) + ZXC_ALIGNMENT_MASK) & ~(size_t)ZXC_ALIGNMENT_MASK) |
361 | | |
362 | | /** |
363 | | * @brief Number of @c uint64_t words needed to hold a bitmap of @p n_bits. |
364 | | * |
365 | | * Equivalent to @c ceil(n_bits / 64). |
366 | | */ |
367 | 83.9k | #define ZXC_BITMAP_WORDS(n_bits) (((n_bits) + 63) / 64) |
368 | | |
369 | | /** @brief Bit flag in the Flags byte indicating checksum presence (bit 7). */ |
370 | 63.5k | #define ZXC_FILE_FLAG_HAS_CHECKSUM 0x80U |
371 | | /** @brief Bit flag in the Flags byte indicating a dictionary is required (bit 6). */ |
372 | 48.3k | #define ZXC_FILE_FLAG_HAS_DICTIONARY 0x40U |
373 | | /** @brief Mask for the checksum algorithm id (bits 0-3). */ |
374 | | #define ZXC_FILE_CHECKSUM_ALGO_MASK 0x0FU |
375 | | |
376 | | /** @brief Magic word identifying ZXC dictionary files (.zxd). */ |
377 | 18.0k | #define ZXC_DICT_MAGIC 0x9CB0D1C7U |
378 | | /** @brief Current dictionary file format version. A 128-byte packed Huffman |
379 | | * code-lengths table (shared literal table) always follows the |
380 | | * dictionary content. */ |
381 | 18.0k | #define ZXC_DICT_VERSION 1 |
382 | | /** @brief K-gram length scanned by the dictionary trainer. Aligned on the LZ |
383 | | * minimum match length so trained patterns are matchable at encode time. */ |
384 | 47.3G | #define ZXC_DICT_KGRAM_LEN ZXC_LZ_MIN_MATCH_LEN |
385 | | /** @brief Address bits for the dictionary trainer's k-gram frequency table. */ |
386 | 23.8G | #define ZXC_DICT_HASH_BITS 16 |
387 | | /** @brief Maximum number of candidate segments the dictionary trainer keeps. */ |
388 | 9.00k | #define ZXC_DICT_MAX_SEGMENTS (1U << 16) |
389 | | /** @brief Target number of sampled k-gram positions for the trainer's frequency |
390 | | * estimate. Bounds the count so 16-bit counters stay unsaturated on large |
391 | | * corpora; the trainer strides the corpus to hit roughly this many positions. */ |
392 | 9.00k | #define ZXC_DICT_SAMPLE_TARGET (1U << 19) |
393 | | /** @brief Number of buckets in the dictionary trainer's frequency table. */ |
394 | | #define ZXC_DICT_HASH_SIZE (1U << ZXC_DICT_HASH_BITS) |
395 | | /** @brief Training block size for the shared-table literal statistics. */ |
396 | 207k | #define ZXC_DICT_HUF_TRAIN_BLOCK 4096U |
397 | | /** @brief Cap on the corpus bytes compressed by the literal-table trainer: the |
398 | | * histogram converges early, so past it slices are strided evenly instead. */ |
399 | 9.00k | #define ZXC_DICT_HUF_SAMPLE_BUDGET (8U << 20) |
400 | | |
401 | | /** @brief Block header size: Type(1)+Flags(1)+Reserved(1)+Checksum(1)+CompSize(4). */ |
402 | 1.24M | #define ZXC_BLOCK_HEADER_SIZE 8 |
403 | | /** @brief Size of the per-block checksum field in bytes. */ |
404 | 115k | #define ZXC_BLOCK_CHECKSUM_SIZE 4 |
405 | | /** @brief Binary size of a GLO block sub-header. */ |
406 | 377k | #define ZXC_GLO_HEADER_BINARY_SIZE 12 |
407 | | /** @brief Binary size of a GHI block sub-header. */ |
408 | 37.4k | #define ZXC_GHI_HEADER_BINARY_SIZE 12 |
409 | | |
410 | | /** @brief Worst-case format overhead inside a single block beyond the outer |
411 | | * 8-byte block header and the optional 4-byte checksum. |
412 | | * |
413 | | * Sub-header (12 B) + widest GLO section descriptors (8 B) + widest slack padding |
414 | | * (@ref ZXC_BLOCK_LIT_SLACK) = 52 B, plus the customary 16 B of margin for |
415 | | * future format evolution. Used by zxc_compress_block_bound() and |
416 | | * zxc_compress_bound(). |
417 | | */ |
418 | 53.4k | #define ZXC_BLOCK_FORMAT_OVERHEAD 68 |
419 | | |
420 | | /** @brief Checksum algorithm id for RapidHash (default, sole implementation). */ |
421 | 27.8k | #define ZXC_CHECKSUM_RAPIDHASH 0 |
422 | | |
423 | | /** @brief Size of the global checksum appended after EOF block (4 bytes). */ |
424 | 9.68k | #define ZXC_GLOBAL_CHECKSUM_SIZE 4 |
425 | | |
426 | | /** @name Seekable Format Constants |
427 | | * @brief Seek table block appended between EOF block and footer. |
428 | | * |
429 | | * The seek table is optional (opt-in at compression time) and allows |
430 | | * random-access decompression by recording per-block compressed and |
431 | | * decompressed sizes. It uses a standard ZXC block header with |
432 | | * @c block_type = @c ZXC_BLOCK_SEK. |
433 | | * |
434 | | * Detection from the end of the file: the reader derives @c num_blocks |
435 | | * from the file footer (total decompressed size) and file header (block size). |
436 | | * It then seeks backward to validate the SEK block header. |
437 | | * @{ */ |
438 | | /** @brief Per-block entry size: comp_size(4) only. decomp_size is derived |
439 | | * from the file header's block_size (all blocks except the last are full). */ |
440 | 72.3k | #define ZXC_SEEK_ENTRY_SIZE 4 |
441 | | /** @} */ /* end of Seekable Format Constants */ |
442 | | |
443 | | /** @name GLO Token Constants |
444 | | * @brief 4-bit literal length / 4-bit match length / 16-bit offset. |
445 | | * @{ */ |
446 | | /** @brief Bits for Literal Length in a GLO token. */ |
447 | 32.5M | #define ZXC_TOKEN_LIT_BITS 4 |
448 | | /** @brief Bits for Match Length in a GLO token. */ |
449 | 649M | #define ZXC_TOKEN_ML_BITS 4 |
450 | | /** @brief Mask to extract Literal Length from a GLO token. */ |
451 | 17.0M | #define ZXC_TOKEN_LL_MASK ((1U << ZXC_TOKEN_LIT_BITS) - 1) |
452 | | /** @brief Mask to extract Match Length from a GLO token. */ |
453 | 649M | #define ZXC_TOKEN_ML_MASK ((1U << ZXC_TOKEN_ML_BITS) - 1) |
454 | | /** @} */ |
455 | | |
456 | | /** @name GHI Sequence Constants |
457 | | * @brief 8-bit literal length / 8-bit match length / 16-bit offset. |
458 | | * @{ */ |
459 | | /** @brief Bits for Literal Length in a GHI sequence. */ |
460 | 7.30M | #define ZXC_SEQ_LL_BITS 8 |
461 | | /** @brief Bits for Match Length in a GHI sequence. */ |
462 | 14.5M | #define ZXC_SEQ_ML_BITS 8 |
463 | | /** @brief Bits for Offset in a GHI sequence. */ |
464 | 21.7M | #define ZXC_SEQ_OFF_BITS 16 |
465 | | /** @brief Mask to extract Literal Length from a GHI sequence. */ |
466 | 7.30M | #define ZXC_SEQ_LL_MASK ((1U << ZXC_SEQ_LL_BITS) - 1) |
467 | | /** @brief Mask to extract Match Length from a GHI sequence. */ |
468 | 7.26M | #define ZXC_SEQ_ML_MASK ((1U << ZXC_SEQ_ML_BITS) - 1) |
469 | | /** @brief Mask to extract Offset from a GHI sequence. */ |
470 | 7.26M | #define ZXC_SEQ_OFF_MASK ((1U << ZXC_SEQ_OFF_BITS) - 1) |
471 | | /** @} */ |
472 | | |
473 | | /** @name Literal Stream Encoding |
474 | | * @{ */ |
475 | | /** @brief Flag bit indicating an RLE run in the literal stream (0x80). */ |
476 | 2.40M | #define ZXC_LIT_RLE_FLAG 0x80U |
477 | | /** @brief Mask to extract the run/literal length (lower 7 bits). */ |
478 | 1.44M | #define ZXC_LIT_LEN_MASK (ZXC_LIT_RLE_FLAG - 1) |
479 | | /** @} */ |
480 | | |
481 | | /** @name LZ77 Constants |
482 | | * @brief Hash table geometry, sliding window, and match parameters. |
483 | | * |
484 | | * The hash table uses a split layout with 15-bit addressing (32 768 buckets): |
485 | | * - `hash_table[]`: uint32_t, stores `(epoch << offset_bits) | position` (128 KB). |
486 | | * - `hash_tags[]`: uint8_t, stores an 8-bit tag for fast rejection (32 KB). |
487 | | * Total: 160 KB. The tag table fits in L1 cache, enabling a |
488 | | * "filter-first" access pattern that avoids cold loads into hash_table |
489 | | * on the ~60-75% of lookups where the tag mismatches. |
490 | | * The 64 KB sliding window allows `chain_table` to use `uint16_t`. |
491 | | * @{ */ |
492 | | /** @brief Address bits for the LZ77 hash table (2^15 = 32 768 buckets). */ |
493 | 1.26G | #define ZXC_LZ_HASH_BITS 15 |
494 | | /** @brief Marsaglia multiplicative hash constant for 4-byte hashing. */ |
495 | 23.9G | #define ZXC_LZ_HASH_PRIME1 0x2D35182DU |
496 | | /** @brief Marsaglia/Vigna xorshift* multiplier for 5-byte hashing. */ |
497 | 1.19G | #define ZXC_LZ_HASH_PRIME2 0x2545F4914F6CDD1DULL |
498 | | /** @brief Maximum number of entries in the hash table. */ |
499 | 175k | #define ZXC_LZ_HASH_SIZE (1U << ZXC_LZ_HASH_BITS) |
500 | | /** @brief Sliding window size (64 KB). */ |
501 | 13.5G | #define ZXC_LZ_WINDOW_SIZE (1U << 16) |
502 | | /** @brief Mask for ring-buffer indexing into chain_table (power-of-two window). */ |
503 | 11.9G | #define ZXC_LZ_WINDOW_MASK (ZXC_LZ_WINDOW_SIZE - 1U) |
504 | | /** @brief Minimum match length for an LZ77 match. */ |
505 | 48.6G | #define ZXC_LZ_MIN_MATCH_LEN 5 |
506 | | /** @brief Maximum legitimate value a varint can decode to. |
507 | | * |
508 | | * A varint value represents (ll - MASK) or (ml - MASK) and is therefore always |
509 | | * strictly less than ZXC_BLOCK_SIZE_MAX (enforced by the Block API entry |
510 | | * points). The cap is set to (ZXC_BLOCK_SIZE_MAX - 1), which fits cleanly in a |
511 | | * 3-byte varint (21 bits): the decoder rejects any 4- or 5-byte encoding, and |
512 | | * the encoder refuses to emit values above this bound. Together they bound the |
513 | | * varint surface to exactly the format-defined block size limit. */ |
514 | | #define ZXC_MAX_VARINT_VALUE ((uint32_t)(ZXC_BLOCK_SIZE_MAX - 1U)) |
515 | | /** @brief Maximum decoded output of one sequence with inline ll/ml, used by the |
516 | | * 4x bounds checks to reserve the rest of a batch. |
517 | | * |
518 | | * Keep it small - the loop margins scale with it. Widening it to 543 |
519 | | * once cost 2 percent of decode on silesia. */ |
520 | | #define ZXC_GLO_MAX_INLINE_OUT_PER_SEQ ((ZXC_TOKEN_LL_MASK - 1U) + ZXC_GLO_MAX_INLINE_ML) /* 33 */ |
521 | | /** @brief Longest match a GLO sequence carries without a varint extension. |
522 | | * |
523 | | * Below @ref ZXC_PAD_SIZE, so the inline path needs no length ladder: one |
524 | | * 32-byte store covers it. The escape path always yields more, so comparing |
525 | | * against this recovers "was the ml nibble inline". */ |
526 | | #define ZXC_GLO_MAX_INLINE_ML ((ZXC_TOKEN_ML_MASK - 1U) + ZXC_LZ_MIN_MATCH_LEN) /* 19 */ |
527 | | #define ZXC_GHI_MAX_INLINE_OUT_PER_SEQ \ |
528 | | ((ZXC_SEQ_LL_MASK - 1U) + (ZXC_SEQ_ML_MASK - 1U) + ZXC_LZ_MIN_MATCH_LEN) /* 513 */ |
529 | | /** @brief Base bias added to encoded offsets (stored = actual - bias). */ |
530 | 198M | #define ZXC_LZ_OFFSET_BIAS 1 |
531 | | /** @brief Maximum allowed offset distance. */ |
532 | 779M | #define ZXC_LZ_MAX_DIST (ZXC_LZ_WINDOW_SIZE - 1) |
533 | | |
534 | | /** @brief Match distance floor the encoder holds to at levels 1 to 5, sized to |
535 | | * the decoder's widest match-copy arm. |
536 | | * |
537 | | * Applied per block, and only where @ref ZXC_LZ_MINDIST_MAX_SHORT_PCT clears |
538 | | * it; levels 6 and 7 keep every distance. Encoder policy: no format bit moves, |
539 | | * so any decoder of the same format version still reads the result. */ |
540 | 1.24M | #define ZXC_LZ_MINDIST 32 |
541 | | |
542 | | /** @brief Probe sampling: one position per KB, clamped. |
543 | | * |
544 | | * Proportional on purpose: a fixed count costs the same on a 4 KB block as on |
545 | | * a 512 KB one, which measured as a third of the compression time on small, |
546 | | * highly compressible inputs. */ |
547 | 25.3k | #define ZXC_LZ_MINDIST_PROBE_PER_KB 1024 |
548 | 42.4k | #define ZXC_LZ_MINDIST_PROBE_MIN 16 |
549 | 30.9k | #define ZXC_LZ_MINDIST_PROBE_MAX 64 |
550 | | |
551 | | /** @brief Short-distance hit rate, in percent, above which a block keeps its |
552 | | * short match distances. |
553 | | * |
554 | | * Measured: natural text 2-5 %, XML around 25 %, JSON 50-60 %, periodic data |
555 | | * 100 %. Decode gains follow the same order, so the cut sits just above text |
556 | | * and below everything that measured neutral or worse. */ |
557 | 25.3k | #define ZXC_LZ_MINDIST_MAX_SHORT_PCT 20 |
558 | | /** @brief Bytes at the block end where match search stops (left as literals). |
559 | | * Equals the 8-byte word the finder reads at each probe, so @c ip+8<=iend. */ |
560 | 199k | #define ZXC_LZ_SEARCH_MARGIN (sizeof(uint64_t)) |
561 | | /** @} */ |
562 | | |
563 | | /** @name Optimal Parser Tuning (level >= 6) |
564 | | * @brief Static prices and complexity guards used by the level-6 optimal |
565 | | * LZ77 parser DP. |
566 | | * @{ */ |
567 | | /** @brief Static price (bits) of a match token before varint extras: 1 byte |
568 | | * token + 2 byte offset. */ |
569 | 630M | #define ZXC_OPT_MATCH_COST_BASE ((uint32_t)(3U * CHAR_BIT)) |
570 | | /** @brief Threshold above which `find_best_match` is skipped at intra-match |
571 | | * positions, keeping the parser O(N) on highly repetitive data. */ |
572 | | #define ZXC_OPT_LONG_MATCH_SKIP ((size_t)256) |
573 | | /** @brief Minimum literal count for the sample-based Huffman cost estimator |
574 | | * used by the optimal parser. Below this, the strided sample is too |
575 | | * small for the resulting code-lengths to be statistically reliable, |
576 | | * so the estimator falls back to RAW cost (8 bits/byte). */ |
577 | | #define ZXC_OPT_LIT_SAMPLE_MIN 1024 |
578 | | |
579 | | /** @} */ |
580 | | |
581 | | /** @name Hash Prime Constants |
582 | | * @brief Mixing primes used by internal hash functions. |
583 | | * @{ */ |
584 | | /** @brief Hash prime 1. */ |
585 | 313k | #define ZXC_HASH_PRIME1 0x9E3779B97F4A7C15ULL |
586 | | /** @brief Hash prime 2. */ |
587 | 122k | #define ZXC_HASH_PRIME2 0xD2D84A61D2D84A61ULL |
588 | | /** @} */ |
589 | | |
590 | | /** @name Huffman Codec Constants |
591 | | * @brief Length-limited canonical Huffman codec for GLO literal sections |
592 | | * (level >= 6) and level-7 token sections, in the PivCo layout. |
593 | | * |
594 | | * On-disk section payload layout (FORMAT.md section 5.2.1): |
595 | | * - @c ZXC_HUF_TABLE_SIZE bytes: @c ZXC_HUF_NUM_SYMBOLS code lengths |
596 | | * packed two per byte (4 bits each). The same packed table is used as the |
597 | | * per-block lengths header (enc_lit=2) and as the shared table carried by |
598 | | * a .zxd dictionary (enc_lit=3, header omitted) -- hence the public |
599 | | * constant. |
600 | | * - Node runs: for every emitting node of the canonical code tree in BFS |
601 | | * order, its branch bits (or packed D-bit residuals for flat subtree |
602 | | * roots), LSB-first, each run padded to a byte boundary. All run sizes |
603 | | * are derived (root count + popcounts), never stored. |
604 | | * @{ */ |
605 | | /** @brief Maximum Huffman code length, in bits: the ceiling the decoder's |
606 | | * on-wire validation accepts (and the level-7 encoder cap). Bounds |
607 | | * the tree depth, hence the merge-level count and every stack array |
608 | | * sized off the code length. The encoder caps codes per level via |
609 | | * ::zxc_huf_enc_max_code_len. */ |
610 | 6.83M | #define ZXC_HUF_MAX_CODE_LEN_ULTRA 11 |
611 | | /** @brief Encoder code-length cap for levels up to ::ZXC_LEVEL_DENSITY (below |
612 | | * ::ZXC_LEVEL_ULTRA): shallow 8-bit trees decode fastest (fewer merge |
613 | | * levels, denser flat subtrees) -- this cap is the de-facto speed |
614 | | * governor of level 6. */ |
615 | 105k | #define ZXC_HUF_MAX_CODE_LEN_DENSITY 8 |
616 | | /** @brief Alphabet size: one entry per possible byte value. */ |
617 | 313M | #define ZXC_HUF_NUM_SYMBOLS 256 |
618 | | |
619 | | /** @brief Upper bound on PivCo tree nodes (full binary tree over the alphabet). */ |
620 | | #define ZXC_PIVCO_MAX_NODES (2 * ZXC_HUF_NUM_SYMBOLS - 1) |
621 | | |
622 | | /** @brief One PivCo Huffman tree node. */ |
623 | | typedef struct { |
624 | | int16_t child[2]; /* node index, -1 = absent */ |
625 | | int16_t sym; /* >= 0: leaf symbol; -1: internal */ |
626 | | } zxc_pivco_node_t; |
627 | | |
628 | | /** |
629 | | * @brief Canonical Huffman tree in PivCo (level-ordered) form. |
630 | | * |
631 | | * Derived deterministically from the 128-byte packed code lengths by |
632 | | * zxc_huf_dict_tree_build / the section decoders; pure value type (index-based, |
633 | | * no internal pointers), safe to copy or embed. Embedded in ::zxc_cctx_t so a |
634 | | * dictionary's shared table is built ONCE at attach instead of per block. |
635 | | */ |
636 | | typedef struct { |
637 | | zxc_pivco_node_t nd[ZXC_PIVCO_MAX_NODES]; |
638 | | int16_t bfs[ZXC_PIVCO_MAX_NODES]; /* node ids in BFS (== wire) order */ |
639 | | int16_t lvl_start[ZXC_HUF_MAX_CODE_LEN_ULTRA + 2]; |
640 | | int n_nodes; |
641 | | int max_depth; |
642 | | // Flat-subtree fast path: flat_d[nid] = D (>= 2) when nid roots a MAXIMAL |
643 | | // complete subtree with all leaves exactly D levels down. Its wire run is |
644 | | // the symbols' packed D-bit residuals instead of D partition bitmaps (same |
645 | | // bits, decode = unpack+lookup), and covered[nid] marks its strict |
646 | | // descendants, absent from the wire. Both sides derive this from the code |
647 | | // lengths, so nothing is signalled. |
648 | | uint8_t flat_d[ZXC_PIVCO_MAX_NODES]; |
649 | | uint8_t covered[ZXC_PIVCO_MAX_NODES]; |
650 | | } zxc_pivco_tree_t; |
651 | | |
652 | | /** |
653 | | * @brief Precomputed decode-side tables derived from a ::zxc_pivco_tree_t. |
654 | | * |
655 | | * Pure functions of the tree topology (no dependence on section data): |
656 | | * @c skip flags the children of leaf-pair parents (emitted directly by the |
657 | | * parent's XOR-blend, never materialised), and @c c2s_pool holds each flat |
658 | | * root's packed-code -> symbol table at @c c2s_off[nid]. Flat subtrees have |
659 | | * disjoint leaves, so the pool never exceeds ZXC_HUF_NUM_SYMBOLS entries; the |
660 | | * +16 slack covers zxc_pivco_unpack_flat's SIMD table loads, which round a |
661 | | * table up to 16 entries. Per-section trees rebuild these inline in |
662 | | * zxc_pivco_decode_core; dictionary trees build them ONCE at attach so the |
663 | | * small-block dict decode path stops repaying the DFS + fills per block. |
664 | | */ |
665 | | typedef struct { |
666 | | uint8_t skip[ZXC_PIVCO_MAX_NODES]; /**< 1 = child of a leaf-pair parent. */ |
667 | | uint16_t c2s_off[ZXC_PIVCO_MAX_NODES]; /**< Flat roots: offset into c2s_pool. */ |
668 | | uint8_t c2s_pool[ZXC_HUF_NUM_SYMBOLS + 16]; /**< Concatenated c2s tables. */ |
669 | | } zxc_pivco_decode_aux_t; |
670 | | |
671 | | /** |
672 | | * @brief Frame-constant dictionary Huffman state, prebuilt once at attach. |
673 | | * |
674 | | * Bundles everything the per-block dict paths reuse: the PivCo @c tree (decoder |
675 | | * + estimator), the canonical @c codes / @c code_len (encoder), and the |
676 | | * decode-side @c dec tables. Carved from the context workspace only when |
677 | | * @c dict_size > 0, so no-dict contexts pay nothing for it. Built by |
678 | | * @ref zxc_huf_dict_tree_build via @c zxc_cctx_attach_dict_huf. |
679 | | */ |
680 | | typedef struct { |
681 | | zxc_pivco_tree_t tree; /**< PivCo tree from the shared literal table. */ |
682 | | uint32_t codes[ZXC_HUF_NUM_SYMBOLS]; /**< Canonical codes (encoder side). */ |
683 | | uint8_t code_len[ZXC_HUF_NUM_SYMBOLS]; /**< Unpacked code lengths. */ |
684 | | zxc_pivco_decode_aux_t dec; /**< Precomputed decoder tables. */ |
685 | | } zxc_dict_huf_state_t; |
686 | | /** @brief RLE margin shift: source of the legacy below-ULTRA premium used by |
687 | | * ::zxc_ss_prem_rle_q8 (256 >> shift reproduces the historical |
688 | | * RLE-vs-RAW margin exactly). */ |
689 | 11.9k | #define ZXC_RLE_MARGIN_SHIFT 5 |
690 | | /** @brief Huffman margin shift: source of the legacy below-ULTRA premium used |
691 | | * by ::zxc_ss_prem_huf_q8 (the frozen ::ZXC_HUF_MIN_LITERALS floor was |
692 | | * also historically derived from it). */ |
693 | 0 | #define ZXC_HUF_MARGIN_SHIFT 5 |
694 | | |
695 | | /** @name Encoder-side joint flat/length nudge (PivCo decode-speed shaping) |
696 | | * |
697 | | * Package-merge minimizes section bits alone, but the PivCo decoder's cost |
698 | | * also depends on the SHAPE of the code-length histogram: the reconstruction |
699 | | * pass loop runs `max_depth + 1` times, and every maximal complete subtree |
700 | | * (all leaves exactly D levels below its root) collapses D merge levels into |
701 | | * a single unpack. ::zxc_huf_nudge_code_lengths reshapes freshly built |
702 | | * lengths toward power-of-two class counts and shallower caps, adopting a |
703 | | * candidate only when its modeled decode win clears the guard below at a |
704 | | * bounded ratio cost. Wire-compatible by construction: adjusted lengths stay |
705 | | * canonical, Kraft-exact and within the level cap, so any v7 decoder reads |
706 | | * the section unchanged (selection is encoder policy, FORMAT.md 5.2.1). |
707 | | * Idea from pivco-huffman issue #20 (dougallj). All knobs are |
708 | | * `#ifndef`-guarded so an A/B build can override them from CFLAGS; in |
709 | | * particular `-DZXC_HUF_NUDGE_MERGE_Q8=0` makes the guard reject every |
710 | | * candidate, restoring archives byte-identical to the unadjusted encoder. |
711 | | * @{ */ |
712 | | /** @brief Exchange rate (Q8 bits per modeled level-touch) in the candidate |
713 | | * cost `J = 256*bits + lambda*touches`; 26 ~= 0.10 bit per touch. */ |
714 | | #ifndef ZXC_HUF_NUDGE_LAMBDA_Q8 |
715 | 118M | #define ZXC_HUF_NUDGE_LAMBDA_Q8 26 |
716 | | #endif |
717 | | /** @brief Adoption guard, ratio side (permil): adopt only while |
718 | | * `bits' * 1000 <= bits0 * ZXC_HUF_NUDGE_BITS_PERMIL` (<= +1.5%). */ |
719 | | #ifndef ZXC_HUF_NUDGE_BITS_PERMIL |
720 | 157k | #define ZXC_HUF_NUDGE_BITS_PERMIL 1015 |
721 | | #endif |
722 | | /** @brief Adoption guard, speed side (Q8): adopt only while |
723 | | * `touches' * 256 <= touches0 * ZXC_HUF_NUDGE_MERGE_Q8` (<= ~0.90x). */ |
724 | | #ifndef ZXC_HUF_NUDGE_MERGE_Q8 |
725 | 122k | #define ZXC_HUF_NUDGE_MERGE_Q8 230 |
726 | | #endif |
727 | | /** @brief Deepest flat-subtree depth with a SIMD unpacker (see |
728 | | * zxc_pivco_unpack_flat); deeper flat roots fall back to the scalar |
729 | | * bit-reader and must NOT be priced as free. */ |
730 | 291M | #define ZXC_HUF_NUDGE_FLAT_SIMD_MAX 6 |
731 | | /** @brief Extra level-touches charged per occurrence under a flat root deeper |
732 | | * than ::ZXC_HUF_NUDGE_FLAT_SIMD_MAX (scalar bit-reader unpack path). |
733 | | * Measured on M2 silesia sections: the scalar unpack costs ~18 SIMD |
734 | | * touch-equivalents per occurrence even in its byte-aligned D = 8 best |
735 | | * case (a mispriced 2 let the walk collapse a 256-symbol section into |
736 | | * one all-8-bit flat root: modeled -30% touches, real -54% decode). |
737 | | * 24 keeps low-mass deep-flat tails adoptable while making |
738 | | * all-the-mass deep flats impossible to justify. */ |
739 | | #ifndef ZXC_HUF_NUDGE_DEEP_FLAT_PENALTY |
740 | 881k | #define ZXC_HUF_NUDGE_DEEP_FLAT_PENALTY 24 |
741 | | #endif |
742 | | /** @brief Fixed per-pass overhead (occurrence-equivalents) charged per merge |
743 | | * level, modeling the pass-loop and node-dispatch cost so shallower |
744 | | * trees also win on small sections. */ |
745 | | #ifndef ZXC_HUF_NUDGE_LEVEL_COST |
746 | 2.70M | #define ZXC_HUF_NUDGE_LEVEL_COST 64 |
747 | | #endif |
748 | | /** @} */ |
749 | | |
750 | | /** @name Space-speed section selection |
751 | | * |
752 | | * Section encodings are selected at EVERY level by pricing each candidate |
753 | | * with a Lagrangian cost `J = compressed_size + decode_tax` and taking the |
754 | | * minimum (Kraken-style `J = R + lambda*D` with a linear per-byte |
755 | | * decode-time model). The tax charges the DECODE time a candidate adds over |
756 | | * the RAW copy path, expressed in bytes: |
757 | | * `tax = (n_decoded_bytes * PREM) >> 8`. Only the premium is per-level: |
758 | | * |
759 | | * - Below DENSITY, the premiums reproduce the historical fixed margins |
760 | | * EXACTLY (8 = 3.125% = the old `>> 5` margins), keeping the sub-DENSITY |
761 | | * levels (1-5) output byte-stable. |
762 | | * - At DENSITY and above the physical premiums apply: `PREM_HUF = 4` (1.56% |
763 | | * -- the entropy decoder costs ~0.5 ns/B over a raw copy, so ~1 byte of |
764 | | * size buys ~30 ns of decode; validated on silesia: ~0.8 pt of ratio for |
765 | | * ~3% decode vs the legacy margin) and `PREM_RLE = 1` (0.39%: RLE decodes |
766 | | * at near copy speed, only a token tax to break ties toward RAW). |
767 | | * @{ */ |
768 | | /** @brief Decode tax of a candidate: `n` decoded bytes at premium `prem` (Q8). */ |
769 | 143k | #define ZXC_SS_TAX(n, prem_q8) (((size_t)(n) * (size_t)(prem_q8)) >> 8) |
770 | | /** |
771 | | * @brief Per-level RLE-vs-RAW decode premium (Q8) for the space-speed selector. |
772 | | * |
773 | | * Feeds @ref ZXC_SS_TAX, which charges an RLE candidate `n * premium >> 8` |
774 | | * decode-tax bytes over the RAW copy path. Below @ref ZXC_LEVEL_DENSITY the |
775 | | * premium is `256 >> ZXC_RLE_MARGIN_SHIFT` (8 = 3.125%), reproducing the |
776 | | * historical RLE-vs-RAW margin exactly so those levels keep byte-stable |
777 | | * selection. At @ref ZXC_LEVEL_DENSITY and above the physical premium 1 (0.39%) |
778 | | * applies: RLE decodes at near copy speed, so it needs only a token tax to break |
779 | | * ties toward RAW. |
780 | | * |
781 | | * @param[in] level Compression level. |
782 | | * @return Decode premium in Q8 (RLE decode-tax bytes per 256 decoded bytes). |
783 | | */ |
784 | 90.0k | static inline int zxc_ss_prem_rle_q8(const int level) { |
785 | 90.0k | return (level >= ZXC_LEVEL_DENSITY) ? 1 : (256 >> ZXC_RLE_MARGIN_SHIFT); |
786 | 90.0k | } Unexecuted instantiation: zxc_common.c:zxc_ss_prem_rle_q8 zxc_compress.c:zxc_ss_prem_rle_q8 Line | Count | Source | 784 | 90.0k | static inline int zxc_ss_prem_rle_q8(const int level) { | 785 | 90.0k | return (level >= ZXC_LEVEL_DENSITY) ? 1 : (256 >> ZXC_RLE_MARGIN_SHIFT); | 786 | 90.0k | } |
Unexecuted instantiation: zxc_decompress.c:zxc_ss_prem_rle_q8 Unexecuted instantiation: zxc_dict.c:zxc_ss_prem_rle_q8 Unexecuted instantiation: zxc_driver.c:zxc_ss_prem_rle_q8 Unexecuted instantiation: zxc_dispatch.c:zxc_ss_prem_rle_q8 Unexecuted instantiation: zxc_huffman.c:zxc_ss_prem_rle_q8 Unexecuted instantiation: zxc_pstream.c:zxc_ss_prem_rle_q8 Unexecuted instantiation: zxc_seekable.c:zxc_ss_prem_rle_q8 |
787 | | /** |
788 | | * @brief Per-level Huffman/PivCo-vs-RAW decode premium (Q8) for the space-speed |
789 | | * selector. |
790 | | * |
791 | | * Feeds @ref ZXC_SS_TAX, which charges a Huffman literal (or token) candidate |
792 | | * `n * premium >> 8` decode-tax bytes over the RAW copy path, the level's |
793 | | * lambda folded with the entropy decoder's cost. Below @ref ZXC_LEVEL_DENSITY |
794 | | * the premium is `256 >> ZXC_HUF_MARGIN_SHIFT` (8 = 3.125%), matching the |
795 | | * historical Huffman margin against a RAW baseline. At @ref ZXC_LEVEL_DENSITY |
796 | | * and above the physical premium 4 (1.56%) applies, trading more decode time for |
797 | | * ratio (the entropy decoder costs ~0.5 ns/B over a raw copy; validated on |
798 | | * silesia). |
799 | | * |
800 | | * @param[in] level Compression level. |
801 | | * @return Decode premium in Q8 (Huffman decode-tax bytes per 256 decoded bytes). |
802 | | */ |
803 | 53.7k | static inline int zxc_ss_prem_huf_q8(const int level) { |
804 | 53.7k | return (level >= ZXC_LEVEL_DENSITY) ? 4 : (256 >> ZXC_HUF_MARGIN_SHIFT); |
805 | 53.7k | } Unexecuted instantiation: zxc_common.c:zxc_ss_prem_huf_q8 zxc_compress.c:zxc_ss_prem_huf_q8 Line | Count | Source | 803 | 53.7k | static inline int zxc_ss_prem_huf_q8(const int level) { | 804 | 53.7k | return (level >= ZXC_LEVEL_DENSITY) ? 4 : (256 >> ZXC_HUF_MARGIN_SHIFT); | 805 | 53.7k | } |
Unexecuted instantiation: zxc_decompress.c:zxc_ss_prem_huf_q8 Unexecuted instantiation: zxc_dict.c:zxc_ss_prem_huf_q8 Unexecuted instantiation: zxc_driver.c:zxc_ss_prem_huf_q8 Unexecuted instantiation: zxc_dispatch.c:zxc_ss_prem_huf_q8 Unexecuted instantiation: zxc_huffman.c:zxc_ss_prem_huf_q8 Unexecuted instantiation: zxc_pstream.c:zxc_ss_prem_huf_q8 Unexecuted instantiation: zxc_seekable.c:zxc_ss_prem_huf_q8 |
806 | | /** @} */ |
807 | | /** @brief Absolute floor (in literals) below which a Huffman candidate is |
808 | | * never evaluated. |
809 | | * |
810 | | * Frozen byte-stability threshold. The value was originally derived |
811 | | * from v6 wire geometry (128-byte lengths table + 6-byte sub-stream |
812 | | * sizes) under the pre-Lagrangian `huf_total < baseline * (M-1)/M` |
813 | | * call-site rule; neither input exists in v7 (sub-stream sizes are |
814 | | * derived, selection uses J = size + lambda * tax), but raising or |
815 | | * lowering the floor changes which blocks pick Huffman and therefore |
816 | | * the emitted archive bytes, so it stays frozen at the historical |
817 | | * value rather than being re-derived. */ |
818 | 93.9k | #define ZXC_HUF_MIN_LITERALS 139 |
819 | | /** @} */ |
820 | | |
821 | | /** @brief Clamps a resolved compression level to the supported ceiling. |
822 | | * |
823 | | * Out-of-range levels above ::ZXC_LEVEL_ULTRA are silently clamped |
824 | | * (never rejected) at every compress entry point, so `level = 99` |
825 | | * behaves as ULTRA across the buffer, context, block and stream APIs |
826 | | * and every language binding inherits the same policy. Levels <= 0 |
827 | | * select the caller's default before this is applied. */ |
828 | 44.4k | static inline int zxc_level_clamp(const int level) { |
829 | 44.4k | return (level > ZXC_LEVEL_ULTRA) ? ZXC_LEVEL_ULTRA : level; |
830 | 44.4k | } Unexecuted instantiation: zxc_common.c:zxc_level_clamp Unexecuted instantiation: zxc_compress.c:zxc_level_clamp Unexecuted instantiation: zxc_decompress.c:zxc_level_clamp Unexecuted instantiation: zxc_dict.c:zxc_level_clamp Unexecuted instantiation: zxc_driver.c:zxc_level_clamp zxc_dispatch.c:zxc_level_clamp Line | Count | Source | 828 | 39.8k | static inline int zxc_level_clamp(const int level) { | 829 | 39.8k | return (level > ZXC_LEVEL_ULTRA) ? ZXC_LEVEL_ULTRA : level; | 830 | 39.8k | } |
Unexecuted instantiation: zxc_huffman.c:zxc_level_clamp zxc_pstream.c:zxc_level_clamp Line | Count | Source | 828 | 4.60k | static inline int zxc_level_clamp(const int level) { | 829 | 4.60k | return (level > ZXC_LEVEL_ULTRA) ? ZXC_LEVEL_ULTRA : level; | 830 | 4.60k | } |
Unexecuted instantiation: zxc_seekable.c:zxc_level_clamp |
831 | | |
832 | | /** @brief Dictionary length from a (possibly NULL) options struct. |
833 | | * |
834 | | * Gated on @c dict itself: no dictionary pointer means no dictionary, whatever |
835 | | * the sibling fields hold. Macros rather than functions because the compression |
836 | | * and decompression option structs carry these fields without sharing a type. */ |
837 | 65.2k | #define ZXC_OPTS_DICT_SIZE(o) (((o) && (o)->dict) ? (o)->dict_size : (size_t)0) |
838 | | /** @brief Shared literal Huffman table, gated on @c dict the same way. */ |
839 | 60.3k | #define ZXC_OPTS_DICT_HUF(o) (((o) && (o)->dict) ? (const uint8_t*)(o)->dict_huf : NULL) |
840 | | /** @brief Compression level, 0 meaning the default, clamped to the highest level |
841 | | * the encoder implements. */ |
842 | 44.4k | #define ZXC_OPTS_LEVEL(o, dflt) zxc_level_clamp(((o) && (o)->level > 0) ? (o)->level : (dflt)) |
843 | | /** @brief Block size, 0 meaning the default. */ |
844 | 44.4k | #define ZXC_OPTS_BLOCK_SIZE(o, dflt) (((o) && (o)->block_size > 0) ? (o)->block_size : (dflt)) |
845 | | |
846 | | /** @brief Encoder Huffman code-length cap for a compression @p level: levels below |
847 | | * ::ZXC_LEVEL_ULTRA use ::ZXC_HUF_MAX_CODE_LEN_DENSITY, ::ZXC_LEVEL_ULTRA uses |
848 | | * the full ::ZXC_HUF_MAX_CODE_LEN_ULTRA ceiling (denser codes, slower decode). |
849 | | * Applies to both literal and token Huffman; the decoder always supports the |
850 | | * ceiling, so lower-level streams decode unchanged. */ |
851 | 107k | static inline int zxc_huf_enc_max_code_len(const int level) { |
852 | 107k | return (level >= ZXC_LEVEL_ULTRA) ? ZXC_HUF_MAX_CODE_LEN_ULTRA : ZXC_HUF_MAX_CODE_LEN_DENSITY; |
853 | 107k | } Unexecuted instantiation: zxc_common.c:zxc_huf_enc_max_code_len zxc_compress.c:zxc_huf_enc_max_code_len Line | Count | Source | 851 | 107k | static inline int zxc_huf_enc_max_code_len(const int level) { | 852 | 107k | return (level >= ZXC_LEVEL_ULTRA) ? ZXC_HUF_MAX_CODE_LEN_ULTRA : ZXC_HUF_MAX_CODE_LEN_DENSITY; | 853 | 107k | } |
Unexecuted instantiation: zxc_decompress.c:zxc_huf_enc_max_code_len Unexecuted instantiation: zxc_dict.c:zxc_huf_enc_max_code_len Unexecuted instantiation: zxc_driver.c:zxc_huf_enc_max_code_len Unexecuted instantiation: zxc_dispatch.c:zxc_huf_enc_max_code_len Unexecuted instantiation: zxc_huffman.c:zxc_huf_enc_max_code_len Unexecuted instantiation: zxc_pstream.c:zxc_huf_enc_max_code_len Unexecuted instantiation: zxc_seekable.c:zxc_huf_enc_max_code_len |
854 | | |
855 | | /** |
856 | | * @brief Boundary package-merge work item. |
857 | | * |
858 | | * Each level holds at most `2 * ZXC_HUF_NUM_SYMBOLS` of these; exposed so |
859 | | * callers can size pre-allocated scratch via ::ZXC_HUF_BUILD_SCRATCH_SIZE. |
860 | | */ |
861 | | typedef struct { |
862 | | uint32_t weight; /**< Accumulated weight (summed frequency) of the package. */ |
863 | | int16_t left; /**< Left child index, or -1 for a leaf. */ |
864 | | int16_t right; /**< Right child index, or -1 for a leaf. */ |
865 | | int16_t sym; /**< Symbol index for a leaf, or -1 for an internal node. */ |
866 | | } zxc_huf_pm_item_t; |
867 | | |
868 | | /** @brief Trace-back stack frame for the package-merge code-length recovery. */ |
869 | | typedef struct { |
870 | | int8_t lvl; /**< Package-merge level being traced back. */ |
871 | | int16_t idx; /**< Item index within that level. */ |
872 | | } zxc_huf_pm_frame_t; |
873 | | |
874 | | /** @brief Per-level item bound: at most leaves + paired packages from the |
875 | | * previous level. */ |
876 | 238k | #define ZXC_HUF_PM_LEVEL_BOUND (2 * ZXC_HUF_NUM_SYMBOLS) |
877 | | |
878 | | /** @brief Worst-case scratch size (bytes) for ::zxc_huf_build_code_lengths. |
879 | | * Carved by the function into items / counts / stack regions; sized |
880 | | * for the worst-case alphabet (n = `ZXC_HUF_NUM_SYMBOLS`). Includes |
881 | | * a small alignment slack between regions. */ |
882 | | #define ZXC_HUF_BUILD_SCRATCH_SIZE \ |
883 | 84.8k | ((size_t)ZXC_HUF_MAX_CODE_LEN_ULTRA * (size_t)ZXC_HUF_PM_LEVEL_BOUND * \ |
884 | 84.8k | sizeof(zxc_huf_pm_item_t) + \ |
885 | 84.8k | 8U + (size_t)ZXC_HUF_MAX_CODE_LEN_ULTRA * sizeof(int) + 8U + \ |
886 | 84.8k | (size_t)ZXC_HUF_MAX_CODE_LEN_ULTRA * (size_t)ZXC_HUF_PM_LEVEL_BOUND * \ |
887 | 84.8k | sizeof(zxc_huf_pm_frame_t)) |
888 | | |
889 | | /** |
890 | | * @brief The four DP partitions the optimal parser carves out of opt_scratch. |
891 | | * |
892 | | * One definition shared by compute_cctx_layout(), which reserves the region, |
893 | | * and zxc_lz77_optimal_parse_glo(), which carves it: the two cannot drift. |
894 | | */ |
895 | | static ZXC_ALWAYS_INLINE void zxc_opt_dp_sizes(const size_t chunk_size, size_t* RESTRICT sz_dp, |
896 | | size_t* RESTRICT sz_pl, size_t* RESTRICT sz_po, |
897 | 119k | size_t* RESTRICT sz_bm) { |
898 | 119k | *sz_dp = ZXC_ALIGN_CL((chunk_size + 1) * sizeof(uint32_t)); |
899 | 119k | *sz_pl = ZXC_ALIGN_CL((chunk_size + 1) * sizeof(uint16_t)); |
900 | 119k | *sz_po = ZXC_ALIGN_CL((chunk_size + 1) * sizeof(uint16_t)); |
901 | 119k | *sz_bm = ZXC_ALIGN_CL(ZXC_BITMAP_WORDS(chunk_size + 1) * sizeof(uint64_t)); |
902 | 119k | } zxc_common.c:zxc_opt_dp_sizes Line | Count | Source | 897 | 35.4k | size_t* RESTRICT sz_bm) { | 898 | 35.4k | *sz_dp = ZXC_ALIGN_CL((chunk_size + 1) * sizeof(uint32_t)); | 899 | 35.4k | *sz_pl = ZXC_ALIGN_CL((chunk_size + 1) * sizeof(uint16_t)); | 900 | 35.4k | *sz_po = ZXC_ALIGN_CL((chunk_size + 1) * sizeof(uint16_t)); | 901 | 35.4k | *sz_bm = ZXC_ALIGN_CL(ZXC_BITMAP_WORDS(chunk_size + 1) * sizeof(uint64_t)); | 902 | 35.4k | } |
zxc_compress.c:zxc_opt_dp_sizes Line | Count | Source | 897 | 83.9k | size_t* RESTRICT sz_bm) { | 898 | 83.9k | *sz_dp = ZXC_ALIGN_CL((chunk_size + 1) * sizeof(uint32_t)); | 899 | 83.9k | *sz_pl = ZXC_ALIGN_CL((chunk_size + 1) * sizeof(uint16_t)); | 900 | 83.9k | *sz_po = ZXC_ALIGN_CL((chunk_size + 1) * sizeof(uint16_t)); | 901 | 83.9k | *sz_bm = ZXC_ALIGN_CL(ZXC_BITMAP_WORDS(chunk_size + 1) * sizeof(uint64_t)); | 902 | 83.9k | } |
Unexecuted instantiation: zxc_decompress.c:zxc_opt_dp_sizes Unexecuted instantiation: zxc_dict.c:zxc_opt_dp_sizes Unexecuted instantiation: zxc_driver.c:zxc_opt_dp_sizes Unexecuted instantiation: zxc_dispatch.c:zxc_opt_dp_sizes Unexecuted instantiation: zxc_huffman.c:zxc_opt_dp_sizes Unexecuted instantiation: zxc_pstream.c:zxc_opt_dp_sizes Unexecuted instantiation: zxc_seekable.c:zxc_opt_dp_sizes |
903 | | |
904 | | /** @name Block Size Helpers |
905 | | * @brief Runtime helpers for variable block sizes. |
906 | | * @{ */ |
907 | | |
908 | | /** |
909 | | * @brief Integer log-base-2 for a 32-bit value. |
910 | | * @param v Must be a power of two (returns 0 for zero). |
911 | | * @return Floor of log2(v). |
912 | | */ |
913 | 758M | static ZXC_ALWAYS_INLINE uint32_t zxc_log2_u32(const uint32_t v) { |
914 | | #ifdef _MSC_VER |
915 | | unsigned long index; |
916 | | return (v == 0) ? 0 : (_BitScanReverse(&index, v) ? index : 0); |
917 | | #else |
918 | 758M | return (v == 0) ? 0 : (uint32_t)(31 - __builtin_clz(v)); |
919 | 758M | #endif |
920 | 758M | } zxc_common.c:zxc_log2_u32 Line | Count | Source | 913 | 212k | static ZXC_ALWAYS_INLINE uint32_t zxc_log2_u32(const uint32_t v) { | 914 | | #ifdef _MSC_VER | 915 | | unsigned long index; | 916 | | return (v == 0) ? 0 : (_BitScanReverse(&index, v) ? index : 0); | 917 | | #else | 918 | 212k | return (v == 0) ? 0 : (uint32_t)(31 - __builtin_clz(v)); | 919 | 212k | #endif | 920 | 212k | } |
Unexecuted instantiation: zxc_compress.c:zxc_log2_u32 Unexecuted instantiation: zxc_decompress.c:zxc_log2_u32 Unexecuted instantiation: zxc_dict.c:zxc_log2_u32 Unexecuted instantiation: zxc_driver.c:zxc_log2_u32 Unexecuted instantiation: zxc_dispatch.c:zxc_log2_u32 zxc_huffman.c:zxc_log2_u32 Line | Count | Source | 913 | 758M | static ZXC_ALWAYS_INLINE uint32_t zxc_log2_u32(const uint32_t v) { | 914 | | #ifdef _MSC_VER | 915 | | unsigned long index; | 916 | | return (v == 0) ? 0 : (_BitScanReverse(&index, v) ? index : 0); | 917 | | #else | 918 | 758M | return (v == 0) ? 0 : (uint32_t)(31 - __builtin_clz(v)); | 919 | 758M | #endif | 920 | 758M | } |
Unexecuted instantiation: zxc_pstream.c:zxc_log2_u32 Unexecuted instantiation: zxc_seekable.c:zxc_log2_u32 |
921 | | |
922 | | /** |
923 | | * @brief Branchless bit_ceil: smallest power of two >= v, clamped to ZXC_BLOCK_SIZE_MIN. |
924 | | * @param[in] v Input size (must be > 0). |
925 | | * @return Smallest power of two >= @p v, clamped up to @ref ZXC_BLOCK_SIZE_MIN. |
926 | | */ |
927 | 22.9k | static ZXC_ALWAYS_INLINE size_t zxc_block_size_ceil(const size_t v) { |
928 | 22.9k | uint64_t x = (uint64_t)v - 1; |
929 | 22.9k | x |= x >> 1; |
930 | 22.9k | x |= x >> 2; |
931 | 22.9k | x |= x >> 4; |
932 | 22.9k | x |= x >> 8; |
933 | 22.9k | x |= x >> 16; |
934 | 22.9k | x |= x >> 32; |
935 | 22.9k | x++; |
936 | 22.9k | const size_t bs = (size_t)x; |
937 | 22.9k | return (bs < ZXC_BLOCK_SIZE_MIN) ? ZXC_BLOCK_SIZE_MIN : bs; |
938 | 22.9k | } Unexecuted instantiation: zxc_common.c:zxc_block_size_ceil Unexecuted instantiation: zxc_compress.c:zxc_block_size_ceil Unexecuted instantiation: zxc_decompress.c:zxc_block_size_ceil zxc_dict.c:zxc_block_size_ceil Line | Count | Source | 927 | 9.00k | static ZXC_ALWAYS_INLINE size_t zxc_block_size_ceil(const size_t v) { | 928 | 9.00k | uint64_t x = (uint64_t)v - 1; | 929 | 9.00k | x |= x >> 1; | 930 | 9.00k | x |= x >> 2; | 931 | 9.00k | x |= x >> 4; | 932 | 9.00k | x |= x >> 8; | 933 | 9.00k | x |= x >> 16; | 934 | 9.00k | x |= x >> 32; | 935 | 9.00k | x++; | 936 | 9.00k | const size_t bs = (size_t)x; | 937 | 9.00k | return (bs < ZXC_BLOCK_SIZE_MIN) ? ZXC_BLOCK_SIZE_MIN : bs; | 938 | 9.00k | } |
Unexecuted instantiation: zxc_driver.c:zxc_block_size_ceil zxc_dispatch.c:zxc_block_size_ceil Line | Count | Source | 927 | 13.9k | static ZXC_ALWAYS_INLINE size_t zxc_block_size_ceil(const size_t v) { | 928 | 13.9k | uint64_t x = (uint64_t)v - 1; | 929 | 13.9k | x |= x >> 1; | 930 | 13.9k | x |= x >> 2; | 931 | 13.9k | x |= x >> 4; | 932 | 13.9k | x |= x >> 8; | 933 | 13.9k | x |= x >> 16; | 934 | 13.9k | x |= x >> 32; | 935 | 13.9k | x++; | 936 | 13.9k | const size_t bs = (size_t)x; | 937 | 13.9k | return (bs < ZXC_BLOCK_SIZE_MIN) ? ZXC_BLOCK_SIZE_MIN : bs; | 938 | 13.9k | } |
Unexecuted instantiation: zxc_huffman.c:zxc_block_size_ceil Unexecuted instantiation: zxc_pstream.c:zxc_block_size_ceil Unexecuted instantiation: zxc_seekable.c:zxc_block_size_ceil |
939 | | |
940 | | /** |
941 | | * @brief Validates a block size. |
942 | | * Must be a power of two in [ZXC_BLOCK_SIZE_MIN, ZXC_BLOCK_SIZE_MAX]. |
943 | | * @param[in] bs Block size to validate. |
944 | | * @return 1 if valid, 0 otherwise. |
945 | | */ |
946 | 34.9k | static ZXC_ALWAYS_INLINE int zxc_validate_block_size(const size_t bs) { |
947 | 34.9k | return bs >= ZXC_BLOCK_SIZE_MIN && bs <= ZXC_BLOCK_SIZE_MAX && (bs & (bs - 1)) == 0; |
948 | 34.9k | } Unexecuted instantiation: zxc_common.c:zxc_validate_block_size Unexecuted instantiation: zxc_compress.c:zxc_validate_block_size Unexecuted instantiation: zxc_decompress.c:zxc_validate_block_size Unexecuted instantiation: zxc_dict.c:zxc_validate_block_size Unexecuted instantiation: zxc_driver.c:zxc_validate_block_size zxc_dispatch.c:zxc_validate_block_size Line | Count | Source | 946 | 34.9k | static ZXC_ALWAYS_INLINE int zxc_validate_block_size(const size_t bs) { | 947 | 34.9k | return bs >= ZXC_BLOCK_SIZE_MIN && bs <= ZXC_BLOCK_SIZE_MAX && (bs & (bs - 1)) == 0; | 948 | 34.9k | } |
Unexecuted instantiation: zxc_huffman.c:zxc_validate_block_size Unexecuted instantiation: zxc_pstream.c:zxc_validate_block_size Unexecuted instantiation: zxc_seekable.c:zxc_validate_block_size |
949 | | /** @} */ |
950 | | |
951 | | /** @} */ /* end of File Format Constants */ |
952 | | |
953 | | /** |
954 | | * @struct zxc_lz77_params_t |
955 | | * @brief Search parameters for LZ77 compression levels. |
956 | | * |
957 | | * Each compression level maps to a specific set of parameters that control the |
958 | | * trade-off between compression speed and ratio. Higher search depths and lazy |
959 | | * matching improve ratio at the expense of throughput; larger step values |
960 | | * accelerate literal scanning but may miss short matches. |
961 | | */ |
962 | | typedef struct { |
963 | | /** Maximum number of candidates explored in the hash chain per position. |
964 | | * Higher values find better matches but increase CPU cost linearly. */ |
965 | | int search_depth; |
966 | | |
967 | | /** "Good enough" match length: once a match reaches this threshold the |
968 | | * chain walk stops immediately, avoiding wasted effort on an already |
969 | | * excellent match. */ |
970 | | int sufficient_len; |
971 | | |
972 | | /** Enable lazy matching. When set, after finding a match at position |
973 | | * @c ip the compressor probes @c ip+1 (and @c ip+2 for level >= 4) to |
974 | | * see if a longer match exists. If so, a literal is emitted and the |
975 | | * better match is taken instead. Improves ratio but costs extra work. */ |
976 | | int use_lazy; |
977 | | |
978 | | /** Maximum number of candidates explored during lazy evaluation (same |
979 | | * semantics as @ref search_depth but applied to the ip+1 / ip+2 probes). |
980 | | * Only meaningful when @ref use_lazy is non-zero. */ |
981 | | int lazy_attempts; |
982 | | |
983 | | /** Skip lazy evaluation when the current match length already reaches |
984 | | * this threshold: a match this long is unlikely to be beaten at the |
985 | | * next byte. Set to 0 when @ref use_lazy is disabled. */ |
986 | | int lazy_len_threshold; |
987 | | |
988 | | /** Base step size when advancing through unmatched literals. |
989 | | * 1 = test every byte (best ratio), 4 = skip aggressively (fastest). */ |
990 | | uint32_t step_base; |
991 | | |
992 | | /** Acceleration factor for step size: @c step = step_base + (distance >> step_shift). |
993 | | * A larger value keeps the step conservative (grows slowly with distance); |
994 | | * a smaller value ramps up quickly, skipping more in long literal runs. */ |
995 | | uint32_t step_shift; |
996 | | |
997 | | /** Shortest match distance the parser may emit; 1 = unconstrained. Skipped |
998 | | * candidates do not end the chain walk, which continues to a legal one |
999 | | * further back. See @ref ZXC_LZ_MINDIST. */ |
1000 | | uint32_t min_offset; |
1001 | | } zxc_lz77_params_t; |
1002 | | |
1003 | | /** |
1004 | | * @brief Retrieves LZ77 compression parameters based on the specified compression level. |
1005 | | * |
1006 | | * This inline function returns the appropriate LZ77 parameters configuration |
1007 | | * for the given compression level. |
1008 | | * |
1009 | | * @param[in] level Compression level; out-of-range values clamp to level 1. |
1010 | | * @return The tuning tuple for that level. |
1011 | | */ |
1012 | 202k | static ZXC_ALWAYS_INLINE zxc_lz77_params_t zxc_get_lz77_params(const int level) { |
1013 | | // The distance floor stops at level 5: the slow levels keep every distance. |
1014 | | // search_depth, sufficient_len, use_lazy, lazy_attempts, lazy_len_threshold, step_base, |
1015 | | // step_shift, min_offset |
1016 | 202k | static const zxc_lz77_params_t table[7] = { |
1017 | 202k | {3, 16, 0, 0, 0, 4, 4, ZXC_LZ_MINDIST}, // fallback |
1018 | 202k | {3, 16, 0, 0, 0, 4, 4, ZXC_LZ_MINDIST}, // level 1 |
1019 | 202k | {3, 18, 0, 0, 0, 3, 6, ZXC_LZ_MINDIST}, // level 2 |
1020 | 202k | {3, 16, 1, 4, 128, 1, 4, ZXC_LZ_MINDIST}, // level 3 |
1021 | 202k | {3, 18, 1, 4, 128, 1, 5, ZXC_LZ_MINDIST}, // level 4 |
1022 | 202k | {64, 256, 1, 16, 128, 1, 8, ZXC_LZ_MINDIST}, // level 5 |
1023 | 202k | {64, 256, 0, 0, 0, 1, 8, 1} // level 6 |
1024 | 202k | }; |
1025 | 202k | return (level >= ZXC_LEVEL_ULTRA) |
1026 | 202k | ? (zxc_lz77_params_t){128, 256, 0, 0, 0, 1, 8, 1} |
1027 | 202k | : table[level < ZXC_LEVEL_FASTEST ? ZXC_LEVEL_FASTEST : level]; |
1028 | 202k | } Unexecuted instantiation: zxc_common.c:zxc_get_lz77_params zxc_compress.c:zxc_get_lz77_params Line | Count | Source | 1012 | 202k | static ZXC_ALWAYS_INLINE zxc_lz77_params_t zxc_get_lz77_params(const int level) { | 1013 | | // The distance floor stops at level 5: the slow levels keep every distance. | 1014 | | // search_depth, sufficient_len, use_lazy, lazy_attempts, lazy_len_threshold, step_base, | 1015 | | // step_shift, min_offset | 1016 | 202k | static const zxc_lz77_params_t table[7] = { | 1017 | 202k | {3, 16, 0, 0, 0, 4, 4, ZXC_LZ_MINDIST}, // fallback | 1018 | 202k | {3, 16, 0, 0, 0, 4, 4, ZXC_LZ_MINDIST}, // level 1 | 1019 | 202k | {3, 18, 0, 0, 0, 3, 6, ZXC_LZ_MINDIST}, // level 2 | 1020 | 202k | {3, 16, 1, 4, 128, 1, 4, ZXC_LZ_MINDIST}, // level 3 | 1021 | 202k | {3, 18, 1, 4, 128, 1, 5, ZXC_LZ_MINDIST}, // level 4 | 1022 | 202k | {64, 256, 1, 16, 128, 1, 8, ZXC_LZ_MINDIST}, // level 5 | 1023 | 202k | {64, 256, 0, 0, 0, 1, 8, 1} // level 6 | 1024 | 202k | }; | 1025 | 202k | return (level >= ZXC_LEVEL_ULTRA) | 1026 | 202k | ? (zxc_lz77_params_t){128, 256, 0, 0, 0, 1, 8, 1} | 1027 | 202k | : table[level < ZXC_LEVEL_FASTEST ? ZXC_LEVEL_FASTEST : level]; | 1028 | 202k | } |
Unexecuted instantiation: zxc_decompress.c:zxc_get_lz77_params Unexecuted instantiation: zxc_dict.c:zxc_get_lz77_params Unexecuted instantiation: zxc_driver.c:zxc_get_lz77_params Unexecuted instantiation: zxc_dispatch.c:zxc_get_lz77_params Unexecuted instantiation: zxc_huffman.c:zxc_get_lz77_params Unexecuted instantiation: zxc_pstream.c:zxc_get_lz77_params Unexecuted instantiation: zxc_seekable.c:zxc_get_lz77_params |
1029 | | |
1030 | | /** |
1031 | | * @enum zxc_block_type_t |
1032 | | * @brief Block types, i.e. which encoder produced a block's payload. |
1033 | | * |
1034 | | * - `ZXC_BLOCK_RAW` (0): stored as-is. Used when the data is incompressible or |
1035 | | * when compressing it would make the block bigger. |
1036 | | * - `ZXC_BLOCK_GLO` (1): the general path, LZ77 plus bitpacked sequences, |
1037 | | * levels 3 and up. Carries 0, 4 or 8 bytes of section descriptors depending |
1038 | | * on which streams need an explicit compressed size. |
1039 | | * - `ZXC_BLOCK_GHI` (2): the speed path, levels 1 and 2. Fixed 4-byte sequence |
1040 | | * records and always-RAW literals make every section size derivable from the |
1041 | | * header, so it carries no descriptor at all. |
1042 | | * - `ZXC_BLOCK_SEK` (254): seek table, holding per-block compressed and |
1043 | | * decompressed sizes. Sits between the EOF block and the file footer. |
1044 | | * - `ZXC_BLOCK_EOF` (255): end-of-file marker. |
1045 | | */ |
1046 | | typedef enum { |
1047 | | ZXC_BLOCK_RAW = 0, |
1048 | | ZXC_BLOCK_GLO = 1, |
1049 | | ZXC_BLOCK_GHI = 2, |
1050 | | ZXC_BLOCK_SEK = 254, |
1051 | | ZXC_BLOCK_EOF = 255 |
1052 | | } zxc_block_type_t; |
1053 | | |
1054 | | /** |
1055 | | * @enum zxc_section_encoding_t |
1056 | | * @brief Specifies the encoding methods used for internal data sections. |
1057 | | * |
1058 | | * These modes determine how specific components (like literals, match lengths, |
1059 | | * or offsets) are stored within a block. |
1060 | | * - `ZXC_SECTION_ENCODING_RAW`: Data is stored uncompressed. |
1061 | | * - `ZXC_SECTION_ENCODING_RLE`: Run-Length Encoding. |
1062 | | * - `ZXC_SECTION_ENCODING_HUFFMAN`: canonical Huffman in the PivCo layout |
1063 | | * (level-ordered branch runs, max 11-bit codes -- FORMAT.md section 5.2.1). |
1064 | | * Valid for the literal stream (`enc_lit`, level >= 6) and the token |
1065 | | * stream (`enc_tok`, level 7) of GLO blocks. |
1066 | | * - `ZXC_SECTION_ENCODING_HUFFMAN_DICT`: same payload as HUFFMAN but the |
1067 | | * 128-byte code-lengths header is omitted: codes come from the shared |
1068 | | * table carried by the dictionary (.zxd). Only valid for `enc_lit` of GLO |
1069 | | * blocks in dictionary-compressed archives; requires the same dictionary |
1070 | | * (content + table, bound by dict_id) at decode time. |
1071 | | */ |
1072 | | typedef enum { |
1073 | | ZXC_SECTION_ENCODING_RAW = 0, |
1074 | | ZXC_SECTION_ENCODING_RLE = 1, |
1075 | | ZXC_SECTION_ENCODING_HUFFMAN = 2, |
1076 | | ZXC_SECTION_ENCODING_HUFFMAN_DICT = 3 |
1077 | | } zxc_section_encoding_t; |
1078 | | |
1079 | | /** |
1080 | | * @struct zxc_gnr_header_t |
1081 | | * @brief Header specific to General (LZ-based) compression blocks. |
1082 | | * |
1083 | | * This header follows the main block header when the block type is GLO/GHI. It |
1084 | | * describes the layout of sequences and literals. |
1085 | | * |
1086 | | * @var zxc_gnr_header_t::n_sequences |
1087 | | * The total count of LZ sequences in the block. |
1088 | | * @var zxc_gnr_header_t::n_literals |
1089 | | * The total count of literal bytes. |
1090 | | * @var zxc_gnr_header_t::enc_lit |
1091 | | * Encoding method used for the literal stream. |
1092 | | * @var zxc_gnr_header_t::enc_tok |
1093 | | * GLO only: encoding of the token section, whose bytes each pack a literal |
1094 | | * length and a match length nibble. Only RAW and HUFFMAN (level 7) occur. |
1095 | | * @var zxc_gnr_header_t::enc_mlen |
1096 | | * Reserved, written 0 and ignored on decode. Match lengths have no stream of |
1097 | | * their own: they share the token byte and spill into the extras. |
1098 | | * @var zxc_gnr_header_t::enc_off |
1099 | | * GLO only: width of the offset stream (1 = 1-byte, 0 = 2-byte). GHI has no |
1100 | | * offset stream, so it writes 0 and ignores the field on decode. |
1101 | | */ |
1102 | | typedef struct { |
1103 | | uint32_t n_sequences; // Number of sequences |
1104 | | uint32_t n_literals; // Number of literals |
1105 | | uint8_t enc_lit; // Literal stream encoding |
1106 | | uint8_t enc_tok; // Token section encoding (GLO only) |
1107 | | uint8_t enc_mlen; // Reserved (see above) |
1108 | | uint8_t enc_off; // Offset stream width (GLO only; ignored on decode in GHI) |
1109 | | } zxc_gnr_header_t; |
1110 | | |
1111 | | /** |
1112 | | * ============================================================================ |
1113 | | * MEMORY & ENDIANNESS HELPERS |
1114 | | * ============================================================================ |
1115 | | * Functions to handle unaligned memory access and Little Endian conversion. |
1116 | | */ |
1117 | | |
1118 | | /** |
1119 | | * @brief Reads a little-endian 16-bit value from a possibly unaligned address. |
1120 | | * |
1121 | | * The memcpy is what makes the unaligned read defined; compilers fold it into |
1122 | | * a single load. Byte-swapped on big-endian hosts, so the wire stays LE. |
1123 | | * |
1124 | | * @param[in] p Address to read from. |
1125 | | * @return The value, in host order. |
1126 | | */ |
1127 | 388k | static ZXC_ALWAYS_INLINE uint16_t zxc_le16(const void* p) { |
1128 | 388k | uint16_t v; |
1129 | 388k | ZXC_MEMCPY(&v, p, sizeof(v)); |
1130 | | #ifdef ZXC_BIG_ENDIAN |
1131 | | return ZXC_BSWAP16(v); |
1132 | | #else |
1133 | 388k | return v; |
1134 | 388k | #endif |
1135 | 388k | } Line | Count | Source | 1127 | 53.0k | static ZXC_ALWAYS_INLINE uint16_t zxc_le16(const void* p) { | 1128 | 53.0k | uint16_t v; | 1129 | 53.0k | ZXC_MEMCPY(&v, p, sizeof(v)); | 1130 | | #ifdef ZXC_BIG_ENDIAN | 1131 | | return ZXC_BSWAP16(v); | 1132 | | #else | 1133 | 53.0k | return v; | 1134 | 53.0k | #endif | 1135 | 53.0k | } |
Unexecuted instantiation: zxc_compress.c:zxc_le16 zxc_decompress.c:zxc_le16 Line | Count | Source | 1127 | 302k | static ZXC_ALWAYS_INLINE uint16_t zxc_le16(const void* p) { | 1128 | 302k | uint16_t v; | 1129 | 302k | ZXC_MEMCPY(&v, p, sizeof(v)); | 1130 | | #ifdef ZXC_BIG_ENDIAN | 1131 | | return ZXC_BSWAP16(v); | 1132 | | #else | 1133 | 302k | return v; | 1134 | 302k | #endif | 1135 | 302k | } |
Line | Count | Source | 1127 | 32.9k | static ZXC_ALWAYS_INLINE uint16_t zxc_le16(const void* p) { | 1128 | 32.9k | uint16_t v; | 1129 | 32.9k | ZXC_MEMCPY(&v, p, sizeof(v)); | 1130 | | #ifdef ZXC_BIG_ENDIAN | 1131 | | return ZXC_BSWAP16(v); | 1132 | | #else | 1133 | 32.9k | return v; | 1134 | 32.9k | #endif | 1135 | 32.9k | } |
Unexecuted instantiation: zxc_driver.c:zxc_le16 Unexecuted instantiation: zxc_dispatch.c:zxc_le16 Unexecuted instantiation: zxc_huffman.c:zxc_le16 Unexecuted instantiation: zxc_pstream.c:zxc_le16 Unexecuted instantiation: zxc_seekable.c:zxc_le16 |
1136 | | |
1137 | | /** |
1138 | | * @brief Reads a little-endian 32-bit value from a possibly unaligned address. |
1139 | | * |
1140 | | * The memcpy is what makes the unaligned read defined; compilers fold it into |
1141 | | * a single load. Byte-swapped on big-endian hosts, so the wire stays LE. |
1142 | | * |
1143 | | * @param[in] p Address to read from. |
1144 | | * @return The value, in host order. |
1145 | | */ |
1146 | 34.8G | static ZXC_ALWAYS_INLINE uint32_t zxc_le32(const void* p) { |
1147 | 34.8G | uint32_t v; |
1148 | 34.8G | ZXC_MEMCPY(&v, p, sizeof(v)); |
1149 | | #ifdef ZXC_BIG_ENDIAN |
1150 | | return ZXC_BSWAP32(v); |
1151 | | #else |
1152 | 34.8G | return v; |
1153 | 34.8G | #endif |
1154 | 34.8G | } Line | Count | Source | 1146 | 511k | static ZXC_ALWAYS_INLINE uint32_t zxc_le32(const void* p) { | 1147 | 511k | uint32_t v; | 1148 | 511k | ZXC_MEMCPY(&v, p, sizeof(v)); | 1149 | | #ifdef ZXC_BIG_ENDIAN | 1150 | | return ZXC_BSWAP32(v); | 1151 | | #else | 1152 | 511k | return v; | 1153 | 511k | #endif | 1154 | 511k | } |
Line | Count | Source | 1146 | 10.9G | static ZXC_ALWAYS_INLINE uint32_t zxc_le32(const void* p) { | 1147 | 10.9G | uint32_t v; | 1148 | 10.9G | ZXC_MEMCPY(&v, p, sizeof(v)); | 1149 | | #ifdef ZXC_BIG_ENDIAN | 1150 | | return ZXC_BSWAP32(v); | 1151 | | #else | 1152 | 10.9G | return v; | 1153 | 10.9G | #endif | 1154 | 10.9G | } |
zxc_decompress.c:zxc_le32 Line | Count | Source | 1146 | 13.8M | static ZXC_ALWAYS_INLINE uint32_t zxc_le32(const void* p) { | 1147 | 13.8M | uint32_t v; | 1148 | 13.8M | ZXC_MEMCPY(&v, p, sizeof(v)); | 1149 | | #ifdef ZXC_BIG_ENDIAN | 1150 | | return ZXC_BSWAP32(v); | 1151 | | #else | 1152 | 13.8M | return v; | 1153 | 13.8M | #endif | 1154 | 13.8M | } |
Line | Count | Source | 1146 | 23.8G | static ZXC_ALWAYS_INLINE uint32_t zxc_le32(const void* p) { | 1147 | 23.8G | uint32_t v; | 1148 | 23.8G | ZXC_MEMCPY(&v, p, sizeof(v)); | 1149 | | #ifdef ZXC_BIG_ENDIAN | 1150 | | return ZXC_BSWAP32(v); | 1151 | | #else | 1152 | 23.8G | return v; | 1153 | 23.8G | #endif | 1154 | 23.8G | } |
Unexecuted instantiation: zxc_driver.c:zxc_le32 Line | Count | Source | 1146 | 18.2k | static ZXC_ALWAYS_INLINE uint32_t zxc_le32(const void* p) { | 1147 | 18.2k | uint32_t v; | 1148 | 18.2k | ZXC_MEMCPY(&v, p, sizeof(v)); | 1149 | | #ifdef ZXC_BIG_ENDIAN | 1150 | | return ZXC_BSWAP32(v); | 1151 | | #else | 1152 | 18.2k | return v; | 1153 | 18.2k | #endif | 1154 | 18.2k | } |
Unexecuted instantiation: zxc_huffman.c:zxc_le32 Line | Count | Source | 1146 | 4.29k | static ZXC_ALWAYS_INLINE uint32_t zxc_le32(const void* p) { | 1147 | 4.29k | uint32_t v; | 1148 | 4.29k | ZXC_MEMCPY(&v, p, sizeof(v)); | 1149 | | #ifdef ZXC_BIG_ENDIAN | 1150 | | return ZXC_BSWAP32(v); | 1151 | | #else | 1152 | 4.29k | return v; | 1153 | 4.29k | #endif | 1154 | 4.29k | } |
Line | Count | Source | 1146 | 10.6k | static ZXC_ALWAYS_INLINE uint32_t zxc_le32(const void* p) { | 1147 | 10.6k | uint32_t v; | 1148 | 10.6k | ZXC_MEMCPY(&v, p, sizeof(v)); | 1149 | | #ifdef ZXC_BIG_ENDIAN | 1150 | | return ZXC_BSWAP32(v); | 1151 | | #else | 1152 | 10.6k | return v; | 1153 | 10.6k | #endif | 1154 | 10.6k | } |
|
1155 | | |
1156 | | /** |
1157 | | * @brief Reports whether a block leans on back-references shorter than |
1158 | | * @ref ZXC_LZ_MINDIST. |
1159 | | * |
1160 | | * Asks one question per sampled position: is there a 4-byte repeat inside the |
1161 | | * distance the floor would forbid? Blocks that answer yes often -- periodic |
1162 | | * runs, JSON keys, XML tags -- lose size and decode time under it. |
1163 | | * |
1164 | | * @param[in] blk Block payload, past any dictionary prefix. |
1165 | | * @param[in] size Payload size in bytes. |
1166 | | * @return 1 when the block must keep its short distances, 0 when the floor is safe. |
1167 | | */ |
1168 | | static ZXC_ALWAYS_INLINE int zxc_block_is_short_dist_bound(const uint8_t* const blk, |
1169 | 28.0k | const size_t size) { |
1170 | 28.0k | const size_t d_max = ZXC_LZ_MINDIST; |
1171 | | // Too small to sample, and too small to gain: keep every distance. |
1172 | 28.0k | if (size < d_max + sizeof(uint32_t)) return 1; |
1173 | | |
1174 | 25.3k | size_t want = size / ZXC_LZ_MINDIST_PROBE_PER_KB; |
1175 | 25.3k | if (want < ZXC_LZ_MINDIST_PROBE_MIN) want = ZXC_LZ_MINDIST_PROBE_MIN; |
1176 | 25.3k | if (want > ZXC_LZ_MINDIST_PROBE_MAX) want = ZXC_LZ_MINDIST_PROBE_MAX; |
1177 | 25.3k | size_t stride = size / want; |
1178 | 25.3k | if (stride < d_max) stride = d_max; |
1179 | | |
1180 | 25.3k | const size_t planned = (size - sizeof(uint32_t) - d_max) / stride + 1; |
1181 | 25.3k | const size_t bar = (size_t)ZXC_LZ_MINDIST_MAX_SHORT_PCT * planned; |
1182 | | |
1183 | 25.3k | size_t samples = 0; |
1184 | 25.3k | size_t hits = 0; |
1185 | 360k | for (size_t i = d_max; i + sizeof(uint32_t) <= size; i += stride) { |
1186 | 360k | const uint32_t cur = zxc_le32(blk + i); |
1187 | 360k | samples++; |
1188 | 8.05M | for (size_t d = 1; d < d_max; d++) { |
1189 | 7.81M | if (zxc_le32(blk + i - d) == cur) { |
1190 | 121k | hits++; |
1191 | 121k | break; |
1192 | 121k | } |
1193 | 7.81M | } |
1194 | 360k | if (hits * 100U > bar) return 1; // can only rise |
1195 | 341k | if ((hits + (planned - samples)) * 100U <= bar) return 0; // out of reach |
1196 | 341k | } |
1197 | 0 | return hits * 100U > bar; |
1198 | 25.3k | } Unexecuted instantiation: zxc_common.c:zxc_block_is_short_dist_bound zxc_compress.c:zxc_block_is_short_dist_bound Line | Count | Source | 1169 | 28.0k | const size_t size) { | 1170 | 28.0k | const size_t d_max = ZXC_LZ_MINDIST; | 1171 | | // Too small to sample, and too small to gain: keep every distance. | 1172 | 28.0k | if (size < d_max + sizeof(uint32_t)) return 1; | 1173 | | | 1174 | 25.3k | size_t want = size / ZXC_LZ_MINDIST_PROBE_PER_KB; | 1175 | 25.3k | if (want < ZXC_LZ_MINDIST_PROBE_MIN) want = ZXC_LZ_MINDIST_PROBE_MIN; | 1176 | 25.3k | if (want > ZXC_LZ_MINDIST_PROBE_MAX) want = ZXC_LZ_MINDIST_PROBE_MAX; | 1177 | 25.3k | size_t stride = size / want; | 1178 | 25.3k | if (stride < d_max) stride = d_max; | 1179 | | | 1180 | 25.3k | const size_t planned = (size - sizeof(uint32_t) - d_max) / stride + 1; | 1181 | 25.3k | const size_t bar = (size_t)ZXC_LZ_MINDIST_MAX_SHORT_PCT * planned; | 1182 | | | 1183 | 25.3k | size_t samples = 0; | 1184 | 25.3k | size_t hits = 0; | 1185 | 360k | for (size_t i = d_max; i + sizeof(uint32_t) <= size; i += stride) { | 1186 | 360k | const uint32_t cur = zxc_le32(blk + i); | 1187 | 360k | samples++; | 1188 | 8.05M | for (size_t d = 1; d < d_max; d++) { | 1189 | 7.81M | if (zxc_le32(blk + i - d) == cur) { | 1190 | 121k | hits++; | 1191 | 121k | break; | 1192 | 121k | } | 1193 | 7.81M | } | 1194 | 360k | if (hits * 100U > bar) return 1; // can only rise | 1195 | 341k | if ((hits + (planned - samples)) * 100U <= bar) return 0; // out of reach | 1196 | 341k | } | 1197 | 0 | return hits * 100U > bar; | 1198 | 25.3k | } |
Unexecuted instantiation: zxc_decompress.c:zxc_block_is_short_dist_bound Unexecuted instantiation: zxc_dict.c:zxc_block_is_short_dist_bound Unexecuted instantiation: zxc_driver.c:zxc_block_is_short_dist_bound Unexecuted instantiation: zxc_dispatch.c:zxc_block_is_short_dist_bound Unexecuted instantiation: zxc_huffman.c:zxc_block_is_short_dist_bound Unexecuted instantiation: zxc_pstream.c:zxc_block_is_short_dist_bound Unexecuted instantiation: zxc_seekable.c:zxc_block_is_short_dist_bound |
1199 | | |
1200 | | /** |
1201 | | * @brief Reads a little-endian 64-bit value from a possibly unaligned address. |
1202 | | * |
1203 | | * The memcpy is what makes the unaligned read defined; compilers fold it into |
1204 | | * a single load. Byte-swapped on big-endian hosts, so the wire stays LE. |
1205 | | * |
1206 | | * @param[in] p Address to read from. |
1207 | | * @return The value, in host order. |
1208 | | */ |
1209 | 7.23G | static ZXC_ALWAYS_INLINE uint64_t zxc_le64(const void* p) { |
1210 | 7.23G | uint64_t v; |
1211 | 7.23G | ZXC_MEMCPY(&v, p, sizeof(v)); |
1212 | | #ifdef ZXC_BIG_ENDIAN |
1213 | | return ZXC_BSWAP64(v); |
1214 | | #else |
1215 | 7.23G | return v; |
1216 | 7.23G | #endif |
1217 | 7.23G | } Line | Count | Source | 1209 | 489k | static ZXC_ALWAYS_INLINE uint64_t zxc_le64(const void* p) { | 1210 | 489k | uint64_t v; | 1211 | 489k | ZXC_MEMCPY(&v, p, sizeof(v)); | 1212 | | #ifdef ZXC_BIG_ENDIAN | 1213 | | return ZXC_BSWAP64(v); | 1214 | | #else | 1215 | 489k | return v; | 1216 | 489k | #endif | 1217 | 489k | } |
Line | Count | Source | 1209 | 7.23G | static ZXC_ALWAYS_INLINE uint64_t zxc_le64(const void* p) { | 1210 | 7.23G | uint64_t v; | 1211 | 7.23G | ZXC_MEMCPY(&v, p, sizeof(v)); | 1212 | | #ifdef ZXC_BIG_ENDIAN | 1213 | | return ZXC_BSWAP64(v); | 1214 | | #else | 1215 | 7.23G | return v; | 1216 | 7.23G | #endif | 1217 | 7.23G | } |
zxc_decompress.c:zxc_le64 Line | Count | Source | 1209 | 3.67M | static ZXC_ALWAYS_INLINE uint64_t zxc_le64(const void* p) { | 1210 | 3.67M | uint64_t v; | 1211 | 3.67M | ZXC_MEMCPY(&v, p, sizeof(v)); | 1212 | | #ifdef ZXC_BIG_ENDIAN | 1213 | | return ZXC_BSWAP64(v); | 1214 | | #else | 1215 | 3.67M | return v; | 1216 | 3.67M | #endif | 1217 | 3.67M | } |
Line | Count | Source | 1209 | 68.7k | static ZXC_ALWAYS_INLINE uint64_t zxc_le64(const void* p) { | 1210 | 68.7k | uint64_t v; | 1211 | 68.7k | ZXC_MEMCPY(&v, p, sizeof(v)); | 1212 | | #ifdef ZXC_BIG_ENDIAN | 1213 | | return ZXC_BSWAP64(v); | 1214 | | #else | 1215 | 68.7k | return v; | 1216 | 68.7k | #endif | 1217 | 68.7k | } |
Unexecuted instantiation: zxc_driver.c:zxc_le64 Line | Count | Source | 1209 | 30.0k | static ZXC_ALWAYS_INLINE uint64_t zxc_le64(const void* p) { | 1210 | 30.0k | uint64_t v; | 1211 | 30.0k | ZXC_MEMCPY(&v, p, sizeof(v)); | 1212 | | #ifdef ZXC_BIG_ENDIAN | 1213 | | return ZXC_BSWAP64(v); | 1214 | | #else | 1215 | 30.0k | return v; | 1216 | 30.0k | #endif | 1217 | 30.0k | } |
Unexecuted instantiation: zxc_huffman.c:zxc_le64 Line | Count | Source | 1209 | 3.12k | static ZXC_ALWAYS_INLINE uint64_t zxc_le64(const void* p) { | 1210 | 3.12k | uint64_t v; | 1211 | 3.12k | ZXC_MEMCPY(&v, p, sizeof(v)); | 1212 | | #ifdef ZXC_BIG_ENDIAN | 1213 | | return ZXC_BSWAP64(v); | 1214 | | #else | 1215 | 3.12k | return v; | 1216 | 3.12k | #endif | 1217 | 3.12k | } |
Line | Count | Source | 1209 | 9.45k | static ZXC_ALWAYS_INLINE uint64_t zxc_le64(const void* p) { | 1210 | 9.45k | uint64_t v; | 1211 | 9.45k | ZXC_MEMCPY(&v, p, sizeof(v)); | 1212 | | #ifdef ZXC_BIG_ENDIAN | 1213 | | return ZXC_BSWAP64(v); | 1214 | | #else | 1215 | 9.45k | return v; | 1216 | 9.45k | #endif | 1217 | 9.45k | } |
|
1218 | | |
1219 | | /** |
1220 | | * @brief Writes a 16-bit value little-endian to a possibly unaligned address. |
1221 | | * |
1222 | | * Mirror of zxc_le16(): the memcpy makes the unaligned store defined, and the |
1223 | | * value is byte-swapped on big-endian hosts so the wire stays LE. |
1224 | | * |
1225 | | * @param[out] p Address to write to, at least 2 bytes. |
1226 | | * @param[in] v Value, in host order. |
1227 | | */ |
1228 | 105k | static ZXC_ALWAYS_INLINE void zxc_store_le16(void* p, const uint16_t v) { |
1229 | | #ifdef ZXC_BIG_ENDIAN |
1230 | | const uint16_t s = ZXC_BSWAP16(v); |
1231 | | ZXC_MEMCPY(p, &s, sizeof(s)); |
1232 | | #else |
1233 | 105k | ZXC_MEMCPY(p, &v, sizeof(v)); |
1234 | 105k | #endif |
1235 | 105k | } zxc_common.c:zxc_store_le16 Line | Count | Source | 1228 | 69.9k | static ZXC_ALWAYS_INLINE void zxc_store_le16(void* p, const uint16_t v) { | 1229 | | #ifdef ZXC_BIG_ENDIAN | 1230 | | const uint16_t s = ZXC_BSWAP16(v); | 1231 | | ZXC_MEMCPY(p, &s, sizeof(s)); | 1232 | | #else | 1233 | 69.9k | ZXC_MEMCPY(p, &v, sizeof(v)); | 1234 | 69.9k | #endif | 1235 | 69.9k | } |
Unexecuted instantiation: zxc_compress.c:zxc_store_le16 Unexecuted instantiation: zxc_decompress.c:zxc_store_le16 zxc_dict.c:zxc_store_le16 Line | Count | Source | 1228 | 36.0k | static ZXC_ALWAYS_INLINE void zxc_store_le16(void* p, const uint16_t v) { | 1229 | | #ifdef ZXC_BIG_ENDIAN | 1230 | | const uint16_t s = ZXC_BSWAP16(v); | 1231 | | ZXC_MEMCPY(p, &s, sizeof(s)); | 1232 | | #else | 1233 | 36.0k | ZXC_MEMCPY(p, &v, sizeof(v)); | 1234 | 36.0k | #endif | 1235 | 36.0k | } |
Unexecuted instantiation: zxc_driver.c:zxc_store_le16 Unexecuted instantiation: zxc_dispatch.c:zxc_store_le16 Unexecuted instantiation: zxc_huffman.c:zxc_store_le16 Unexecuted instantiation: zxc_pstream.c:zxc_store_le16 Unexecuted instantiation: zxc_seekable.c:zxc_store_le16 |
1236 | | |
1237 | | /** |
1238 | | * @brief Writes a 32-bit value little-endian to a possibly unaligned address. |
1239 | | * |
1240 | | * Mirror of zxc_le32(); see zxc_store_le16() for the memcpy and endianness |
1241 | | * rationale. |
1242 | | * |
1243 | | * @param[out] p Address to write to, at least 4 bytes. |
1244 | | * @param[in] v Value, in host order. |
1245 | | */ |
1246 | 595k | static ZXC_ALWAYS_INLINE void zxc_store_le32(void* p, const uint32_t v) { |
1247 | | #ifdef ZXC_BIG_ENDIAN |
1248 | | const uint32_t s = ZXC_BSWAP32(v); |
1249 | | ZXC_MEMCPY(p, &s, sizeof(s)); |
1250 | | #else |
1251 | 595k | ZXC_MEMCPY(p, &v, sizeof(v)); |
1252 | 595k | #endif |
1253 | 595k | } zxc_common.c:zxc_store_le32 Line | Count | Source | 1246 | 503k | static ZXC_ALWAYS_INLINE void zxc_store_le32(void* p, const uint32_t v) { | 1247 | | #ifdef ZXC_BIG_ENDIAN | 1248 | | const uint32_t s = ZXC_BSWAP32(v); | 1249 | | ZXC_MEMCPY(p, &s, sizeof(s)); | 1250 | | #else | 1251 | 503k | ZXC_MEMCPY(p, &v, sizeof(v)); | 1252 | 503k | #endif | 1253 | 503k | } |
zxc_compress.c:zxc_store_le32 Line | Count | Source | 1246 | 11.3k | static ZXC_ALWAYS_INLINE void zxc_store_le32(void* p, const uint32_t v) { | 1247 | | #ifdef ZXC_BIG_ENDIAN | 1248 | | const uint32_t s = ZXC_BSWAP32(v); | 1249 | | ZXC_MEMCPY(p, &s, sizeof(s)); | 1250 | | #else | 1251 | 11.3k | ZXC_MEMCPY(p, &v, sizeof(v)); | 1252 | 11.3k | #endif | 1253 | 11.3k | } |
Unexecuted instantiation: zxc_decompress.c:zxc_store_le32 zxc_dict.c:zxc_store_le32 Line | Count | Source | 1246 | 70.3k | static ZXC_ALWAYS_INLINE void zxc_store_le32(void* p, const uint32_t v) { | 1247 | | #ifdef ZXC_BIG_ENDIAN | 1248 | | const uint32_t s = ZXC_BSWAP32(v); | 1249 | | ZXC_MEMCPY(p, &s, sizeof(s)); | 1250 | | #else | 1251 | 70.3k | ZXC_MEMCPY(p, &v, sizeof(v)); | 1252 | 70.3k | #endif | 1253 | 70.3k | } |
Unexecuted instantiation: zxc_driver.c:zxc_store_le32 Unexecuted instantiation: zxc_dispatch.c:zxc_store_le32 Unexecuted instantiation: zxc_huffman.c:zxc_store_le32 Unexecuted instantiation: zxc_pstream.c:zxc_store_le32 zxc_seekable.c:zxc_store_le32 Line | Count | Source | 1246 | 10.6k | static ZXC_ALWAYS_INLINE void zxc_store_le32(void* p, const uint32_t v) { | 1247 | | #ifdef ZXC_BIG_ENDIAN | 1248 | | const uint32_t s = ZXC_BSWAP32(v); | 1249 | | ZXC_MEMCPY(p, &s, sizeof(s)); | 1250 | | #else | 1251 | 10.6k | ZXC_MEMCPY(p, &v, sizeof(v)); | 1252 | 10.6k | #endif | 1253 | 10.6k | } |
|
1254 | | |
1255 | | /** |
1256 | | * @brief Writes a 64-bit value little-endian to a possibly unaligned address. |
1257 | | * |
1258 | | * Mirror of zxc_le64(); see zxc_store_le16() for the memcpy and endianness |
1259 | | * rationale. |
1260 | | * |
1261 | | * @param[out] p Address to write to, at least 8 bytes. |
1262 | | * @param[in] v Value, in host order. |
1263 | | */ |
1264 | 34.9k | static ZXC_ALWAYS_INLINE void zxc_store_le64(void* p, const uint64_t v) { |
1265 | | #ifdef ZXC_BIG_ENDIAN |
1266 | | const uint64_t s = ZXC_BSWAP64(v); |
1267 | | ZXC_MEMCPY(p, &s, sizeof(s)); |
1268 | | #else |
1269 | 34.9k | ZXC_MEMCPY(p, &v, sizeof(v)); |
1270 | 34.9k | #endif |
1271 | 34.9k | } zxc_common.c:zxc_store_le64 Line | Count | Source | 1264 | 34.9k | static ZXC_ALWAYS_INLINE void zxc_store_le64(void* p, const uint64_t v) { | 1265 | | #ifdef ZXC_BIG_ENDIAN | 1266 | | const uint64_t s = ZXC_BSWAP64(v); | 1267 | | ZXC_MEMCPY(p, &s, sizeof(s)); | 1268 | | #else | 1269 | 34.9k | ZXC_MEMCPY(p, &v, sizeof(v)); | 1270 | 34.9k | #endif | 1271 | 34.9k | } |
Unexecuted instantiation: zxc_compress.c:zxc_store_le64 Unexecuted instantiation: zxc_decompress.c:zxc_store_le64 Unexecuted instantiation: zxc_dict.c:zxc_store_le64 Unexecuted instantiation: zxc_driver.c:zxc_store_le64 Unexecuted instantiation: zxc_dispatch.c:zxc_store_le64 Unexecuted instantiation: zxc_huffman.c:zxc_store_le64 Unexecuted instantiation: zxc_pstream.c:zxc_store_le64 Unexecuted instantiation: zxc_seekable.c:zxc_store_le64 |
1272 | | |
1273 | | /** |
1274 | | * @brief Computes the 1-byte checksum for block headers. |
1275 | | * |
1276 | | * Implementation based on Marsaglia's Xorshift (PRNG) principles. |
1277 | | * |
1278 | | * @param[in] p The 8 header bytes to hash. |
1279 | | * @return The checksum byte. |
1280 | | */ |
1281 | 313k | static ZXC_ALWAYS_INLINE uint8_t zxc_hash8(const uint8_t* p) { |
1282 | 313k | const uint64_t v = zxc_le64(p); |
1283 | 313k | uint64_t h = v ^ ZXC_HASH_PRIME1; |
1284 | 313k | h ^= h << 13; |
1285 | 313k | h ^= h >> 7; |
1286 | 313k | h ^= h << 17; |
1287 | 313k | return (uint8_t)((h >> 32) ^ h); |
1288 | 313k | } Line | Count | Source | 1281 | 313k | static ZXC_ALWAYS_INLINE uint8_t zxc_hash8(const uint8_t* p) { | 1282 | 313k | const uint64_t v = zxc_le64(p); | 1283 | 313k | uint64_t h = v ^ ZXC_HASH_PRIME1; | 1284 | 313k | h ^= h << 13; | 1285 | 313k | h ^= h >> 7; | 1286 | 313k | h ^= h << 17; | 1287 | 313k | return (uint8_t)((h >> 32) ^ h); | 1288 | 313k | } |
Unexecuted instantiation: zxc_compress.c:zxc_hash8 Unexecuted instantiation: zxc_decompress.c:zxc_hash8 Unexecuted instantiation: zxc_dict.c:zxc_hash8 Unexecuted instantiation: zxc_driver.c:zxc_hash8 Unexecuted instantiation: zxc_dispatch.c:zxc_hash8 Unexecuted instantiation: zxc_huffman.c:zxc_hash8 Unexecuted instantiation: zxc_pstream.c:zxc_hash8 Unexecuted instantiation: zxc_seekable.c:zxc_hash8 |
1289 | | |
1290 | | /** |
1291 | | * @brief Computes the 2-byte checksum for file headers. |
1292 | | * |
1293 | | * Implementation based on Marsaglia's Xorshift (PRNG) principles. |
1294 | | * |
1295 | | * @param[in] p The 16 header bytes to hash. |
1296 | | * @return The checksum halfword. |
1297 | | */ |
1298 | 122k | static ZXC_ALWAYS_INLINE uint16_t zxc_hash16(const uint8_t* p) { |
1299 | 122k | const uint64_t v1 = zxc_le64(p); |
1300 | 122k | const uint64_t v2 = zxc_le64(p + 8); |
1301 | 122k | uint64_t h = v1 ^ v2 ^ ZXC_HASH_PRIME2; |
1302 | 122k | h ^= h << 13; |
1303 | 122k | h ^= h >> 7; |
1304 | 122k | h ^= h << 17; |
1305 | 122k | const uint32_t res = (uint32_t)((h >> 32) ^ h); |
1306 | 122k | return (uint16_t)((res >> 16) ^ res); |
1307 | 122k | } Line | Count | Source | 1298 | 88.0k | static ZXC_ALWAYS_INLINE uint16_t zxc_hash16(const uint8_t* p) { | 1299 | 88.0k | const uint64_t v1 = zxc_le64(p); | 1300 | 88.0k | const uint64_t v2 = zxc_le64(p + 8); | 1301 | 88.0k | uint64_t h = v1 ^ v2 ^ ZXC_HASH_PRIME2; | 1302 | 88.0k | h ^= h << 13; | 1303 | 88.0k | h ^= h >> 7; | 1304 | 88.0k | h ^= h << 17; | 1305 | 88.0k | const uint32_t res = (uint32_t)((h >> 32) ^ h); | 1306 | 88.0k | return (uint16_t)((res >> 16) ^ res); | 1307 | 88.0k | } |
Unexecuted instantiation: zxc_compress.c:zxc_hash16 Unexecuted instantiation: zxc_decompress.c:zxc_hash16 Line | Count | Source | 1298 | 34.3k | static ZXC_ALWAYS_INLINE uint16_t zxc_hash16(const uint8_t* p) { | 1299 | 34.3k | const uint64_t v1 = zxc_le64(p); | 1300 | 34.3k | const uint64_t v2 = zxc_le64(p + 8); | 1301 | 34.3k | uint64_t h = v1 ^ v2 ^ ZXC_HASH_PRIME2; | 1302 | 34.3k | h ^= h << 13; | 1303 | 34.3k | h ^= h >> 7; | 1304 | 34.3k | h ^= h << 17; | 1305 | 34.3k | const uint32_t res = (uint32_t)((h >> 32) ^ h); | 1306 | 34.3k | return (uint16_t)((res >> 16) ^ res); | 1307 | 34.3k | } |
Unexecuted instantiation: zxc_driver.c:zxc_hash16 Unexecuted instantiation: zxc_dispatch.c:zxc_hash16 Unexecuted instantiation: zxc_huffman.c:zxc_hash16 Unexecuted instantiation: zxc_pstream.c:zxc_hash16 Unexecuted instantiation: zxc_seekable.c:zxc_hash16 |
1308 | | |
1309 | | /** |
1310 | | * @brief Copies exactly 16 bytes as one vector move where the ISA has one. |
1311 | | * |
1312 | | * SSE2 on x86, NEON on ARM, memcpy elsewhere. Fixed width: the caller must |
1313 | | * have 16 readable and 16 writable bytes. |
1314 | | * |
1315 | | * @param[out] dst Pointer to the destination memory block. |
1316 | | * @param[in] src Pointer to the source memory block. |
1317 | | */ |
1318 | 33.3M | static ZXC_ALWAYS_INLINE void zxc_copy16(void* dst, const void* src) { |
1319 | 33.3M | #if defined(ZXC_USE_AVX2) || defined(ZXC_USE_AVX512) || defined(ZXC_USE_SSE2) |
1320 | | // x86 SSE2/AVX2/AVX512: Single 128-bit unaligned load/store |
1321 | 33.3M | _mm_storeu_si128((__m128i*)dst, _mm_loadu_si128((const __m128i*)src)); |
1322 | | #elif defined(ZXC_USE_NEON64) || defined(ZXC_USE_NEON32) |
1323 | | vst1q_u8((uint8_t*)dst, vld1q_u8((const uint8_t*)src)); |
1324 | | #else |
1325 | | ZXC_MEMCPY(dst, src, 16); |
1326 | | #endif |
1327 | 33.3M | } Unexecuted instantiation: zxc_common.c:zxc_copy16 Unexecuted instantiation: zxc_compress.c:zxc_copy16 zxc_decompress.c:zxc_copy16 Line | Count | Source | 1318 | 33.3M | static ZXC_ALWAYS_INLINE void zxc_copy16(void* dst, const void* src) { | 1319 | 33.3M | #if defined(ZXC_USE_AVX2) || defined(ZXC_USE_AVX512) || defined(ZXC_USE_SSE2) | 1320 | | // x86 SSE2/AVX2/AVX512: Single 128-bit unaligned load/store | 1321 | 33.3M | _mm_storeu_si128((__m128i*)dst, _mm_loadu_si128((const __m128i*)src)); | 1322 | | #elif defined(ZXC_USE_NEON64) || defined(ZXC_USE_NEON32) | 1323 | | vst1q_u8((uint8_t*)dst, vld1q_u8((const uint8_t*)src)); | 1324 | | #else | 1325 | | ZXC_MEMCPY(dst, src, 16); | 1326 | | #endif | 1327 | 33.3M | } |
Unexecuted instantiation: zxc_dict.c:zxc_copy16 Unexecuted instantiation: zxc_driver.c:zxc_copy16 Unexecuted instantiation: zxc_dispatch.c:zxc_copy16 Unexecuted instantiation: zxc_huffman.c:zxc_copy16 Unexecuted instantiation: zxc_pstream.c:zxc_copy16 Unexecuted instantiation: zxc_seekable.c:zxc_copy16 |
1328 | | |
1329 | | /** |
1330 | | * @brief Copies 32 bytes from source to destination using SIMD when available. |
1331 | | * |
1332 | | * Uses AVX2 on x86, NEON on ARM64/ARM32, or two 16-byte copies as fallback. |
1333 | | * |
1334 | | * @param[out] dst Pointer to the destination memory block. |
1335 | | * @param[in] src Pointer to the source memory block. |
1336 | | */ |
1337 | 135M | static ZXC_ALWAYS_INLINE void zxc_copy32(void* dst, const void* src) { |
1338 | | #if defined(ZXC_USE_AVX2) || defined(ZXC_USE_AVX512) |
1339 | | // AVX2/AVX512: Single 256-bit (32 byte) unaligned load/store |
1340 | | _mm256_storeu_si256((__m256i*)dst, _mm256_loadu_si256((const __m256i*)src)); |
1341 | | #elif defined(ZXC_USE_SSE2) |
1342 | | // SSE2: Two 128-bit (16 byte) unaligned load/stores (no 256-bit regs) |
1343 | 135M | _mm_storeu_si128((__m128i*)dst, _mm_loadu_si128((const __m128i*)src)); |
1344 | 135M | _mm_storeu_si128((__m128i*)((uint8_t*)dst + 16), |
1345 | 135M | _mm_loadu_si128((const __m128i*)((const uint8_t*)src + 16))); |
1346 | | #elif defined(ZXC_USE_NEON64) || defined(ZXC_USE_NEON32) |
1347 | | // NEON: Two 128-bit (16 byte) unaligned load/stores |
1348 | | vst1q_u8((uint8_t*)dst, vld1q_u8((const uint8_t*)src)); |
1349 | | vst1q_u8((uint8_t*)dst + 16, vld1q_u8((const uint8_t*)src + 16)); |
1350 | | #else |
1351 | | ZXC_MEMCPY(dst, src, 32); |
1352 | | #endif |
1353 | 135M | } Unexecuted instantiation: zxc_common.c:zxc_copy32 zxc_compress.c:zxc_copy32 Line | Count | Source | 1337 | 5.58M | static ZXC_ALWAYS_INLINE void zxc_copy32(void* dst, const void* src) { | 1338 | | #if defined(ZXC_USE_AVX2) || defined(ZXC_USE_AVX512) | 1339 | | // AVX2/AVX512: Single 256-bit (32 byte) unaligned load/store | 1340 | | _mm256_storeu_si256((__m256i*)dst, _mm256_loadu_si256((const __m256i*)src)); | 1341 | | #elif defined(ZXC_USE_SSE2) | 1342 | | // SSE2: Two 128-bit (16 byte) unaligned load/stores (no 256-bit regs) | 1343 | 5.58M | _mm_storeu_si128((__m128i*)dst, _mm_loadu_si128((const __m128i*)src)); | 1344 | 5.58M | _mm_storeu_si128((__m128i*)((uint8_t*)dst + 16), | 1345 | 5.58M | _mm_loadu_si128((const __m128i*)((const uint8_t*)src + 16))); | 1346 | | #elif defined(ZXC_USE_NEON64) || defined(ZXC_USE_NEON32) | 1347 | | // NEON: Two 128-bit (16 byte) unaligned load/stores | 1348 | | vst1q_u8((uint8_t*)dst, vld1q_u8((const uint8_t*)src)); | 1349 | | vst1q_u8((uint8_t*)dst + 16, vld1q_u8((const uint8_t*)src + 16)); | 1350 | | #else | 1351 | | ZXC_MEMCPY(dst, src, 32); | 1352 | | #endif | 1353 | 5.58M | } |
zxc_decompress.c:zxc_copy32 Line | Count | Source | 1337 | 130M | static ZXC_ALWAYS_INLINE void zxc_copy32(void* dst, const void* src) { | 1338 | | #if defined(ZXC_USE_AVX2) || defined(ZXC_USE_AVX512) | 1339 | | // AVX2/AVX512: Single 256-bit (32 byte) unaligned load/store | 1340 | | _mm256_storeu_si256((__m256i*)dst, _mm256_loadu_si256((const __m256i*)src)); | 1341 | | #elif defined(ZXC_USE_SSE2) | 1342 | | // SSE2: Two 128-bit (16 byte) unaligned load/stores (no 256-bit regs) | 1343 | 130M | _mm_storeu_si128((__m128i*)dst, _mm_loadu_si128((const __m128i*)src)); | 1344 | 130M | _mm_storeu_si128((__m128i*)((uint8_t*)dst + 16), | 1345 | 130M | _mm_loadu_si128((const __m128i*)((const uint8_t*)src + 16))); | 1346 | | #elif defined(ZXC_USE_NEON64) || defined(ZXC_USE_NEON32) | 1347 | | // NEON: Two 128-bit (16 byte) unaligned load/stores | 1348 | | vst1q_u8((uint8_t*)dst, vld1q_u8((const uint8_t*)src)); | 1349 | | vst1q_u8((uint8_t*)dst + 16, vld1q_u8((const uint8_t*)src + 16)); | 1350 | | #else | 1351 | | ZXC_MEMCPY(dst, src, 32); | 1352 | | #endif | 1353 | 130M | } |
Unexecuted instantiation: zxc_dict.c:zxc_copy32 Unexecuted instantiation: zxc_driver.c:zxc_copy32 Unexecuted instantiation: zxc_dispatch.c:zxc_copy32 Unexecuted instantiation: zxc_huffman.c:zxc_copy32 Unexecuted instantiation: zxc_pstream.c:zxc_copy32 Unexecuted instantiation: zxc_seekable.c:zxc_copy32 |
1354 | | |
1355 | | /** |
1356 | | * @brief Counts trailing zeros, returning 32 for a zero input. |
1357 | | * |
1358 | | * The zero case is the reason for the guard: both `__builtin_ctz` and |
1359 | | * `_BitScanForward` leave the result undefined there. |
1360 | | * |
1361 | | * @param[in] x Value to scan. |
1362 | | * @return Trailing zero count, in [0, 32]. |
1363 | | */ |
1364 | 13.0M | static ZXC_ALWAYS_INLINE int zxc_ctz32(const uint32_t x) { |
1365 | 13.0M | if (x == 0) return 32; |
1366 | 13.0M | #if defined(__GNUC__) || defined(__clang__) |
1367 | 13.0M | return __builtin_ctz(x); |
1368 | | #elif defined(_MSC_VER) |
1369 | | unsigned long r; |
1370 | | _BitScanForward(&r, x); |
1371 | | return (int)r; |
1372 | | #else |
1373 | | // Fallback De Bruijn (32 bits) |
1374 | | static const int DeBruijn32[32] = {0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, |
1375 | | 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19, |
1376 | | 16, 7, 26, 12, 18, 6, 11, 5, 10, 9}; |
1377 | | return DeBruijn32[((uint32_t)((x & (0U - x)) * 0x077CB531U)) >> 27]; |
1378 | | #endif |
1379 | 13.0M | } Unexecuted instantiation: zxc_common.c:zxc_ctz32 Line | Count | Source | 1364 | 13.0M | static ZXC_ALWAYS_INLINE int zxc_ctz32(const uint32_t x) { | 1365 | 13.0M | if (x == 0) return 32; | 1366 | 13.0M | #if defined(__GNUC__) || defined(__clang__) | 1367 | 13.0M | return __builtin_ctz(x); | 1368 | | #elif defined(_MSC_VER) | 1369 | | unsigned long r; | 1370 | | _BitScanForward(&r, x); | 1371 | | return (int)r; | 1372 | | #else | 1373 | | // Fallback De Bruijn (32 bits) | 1374 | | static const int DeBruijn32[32] = {0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, | 1375 | | 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19, | 1376 | | 16, 7, 26, 12, 18, 6, 11, 5, 10, 9}; | 1377 | | return DeBruijn32[((uint32_t)((x & (0U - x)) * 0x077CB531U)) >> 27]; | 1378 | | #endif | 1379 | 13.0M | } |
Unexecuted instantiation: zxc_decompress.c:zxc_ctz32 Unexecuted instantiation: zxc_dict.c:zxc_ctz32 Unexecuted instantiation: zxc_driver.c:zxc_ctz32 Unexecuted instantiation: zxc_dispatch.c:zxc_ctz32 Unexecuted instantiation: zxc_huffman.c:zxc_ctz32 Unexecuted instantiation: zxc_pstream.c:zxc_ctz32 Unexecuted instantiation: zxc_seekable.c:zxc_ctz32 |
1380 | | |
1381 | | /** |
1382 | | * @brief Counts trailing zeros, returning 64 for a zero input. |
1383 | | * |
1384 | | * The zero case is the reason for the guard: both `__builtin_ctzll` and |
1385 | | * `_BitScanForward64` leave the result undefined there. |
1386 | | * |
1387 | | * @param[in] x Value to scan. |
1388 | | * @return Trailing zero count, in [0, 64]. |
1389 | | */ |
1390 | 1.62G | static ZXC_ALWAYS_INLINE int zxc_ctz64(const uint64_t x) { |
1391 | 1.62G | if (x == 0) return 64; |
1392 | 1.62G | #if defined(__GNUC__) || defined(__clang__) |
1393 | 1.62G | return __builtin_ctzll(x); |
1394 | | #elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)) |
1395 | | unsigned long r; |
1396 | | _BitScanForward64(&r, x); |
1397 | | return (int)r; |
1398 | | #elif defined(_MSC_VER) |
1399 | | // Use two 32-bit scans to avoid fragile 64-bit De Bruijn multiplication. |
1400 | | unsigned long r; |
1401 | | const uint32_t lo = (uint32_t)x; |
1402 | | if (_BitScanForward(&r, lo)) return (int)r; |
1403 | | _BitScanForward(&r, (uint32_t)(x >> 32)); |
1404 | | return 32 + (int)r; |
1405 | | #else |
1406 | | // Fallback De Bruijn for non-GCC/non-MSVC compilers |
1407 | | static const int Debruijn64[64] = { |
1408 | | 0, 1, 48, 2, 57, 49, 28, 3, 61, 58, 50, 42, 38, 29, 17, 4, 62, 55, 59, 36, 53, 51, |
1409 | | 43, 22, 45, 39, 33, 30, 24, 18, 12, 5, 63, 47, 56, 27, 60, 41, 37, 16, 54, 35, 52, 21, |
1410 | | 44, 32, 23, 11, 46, 26, 40, 15, 34, 20, 31, 10, 25, 14, 19, 9, 13, 8, 7, 6}; |
1411 | | return Debruijn64[((x & (0ULL - x)) * 0x03F79D71B4CA8B09ULL) >> 58]; |
1412 | | #endif |
1413 | 1.62G | } Unexecuted instantiation: zxc_common.c:zxc_ctz64 Line | Count | Source | 1390 | 1.62G | static ZXC_ALWAYS_INLINE int zxc_ctz64(const uint64_t x) { | 1391 | 1.62G | if (x == 0) return 64; | 1392 | 1.62G | #if defined(__GNUC__) || defined(__clang__) | 1393 | 1.62G | return __builtin_ctzll(x); | 1394 | | #elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)) | 1395 | | unsigned long r; | 1396 | | _BitScanForward64(&r, x); | 1397 | | return (int)r; | 1398 | | #elif defined(_MSC_VER) | 1399 | | // Use two 32-bit scans to avoid fragile 64-bit De Bruijn multiplication. | 1400 | | unsigned long r; | 1401 | | const uint32_t lo = (uint32_t)x; | 1402 | | if (_BitScanForward(&r, lo)) return (int)r; | 1403 | | _BitScanForward(&r, (uint32_t)(x >> 32)); | 1404 | | return 32 + (int)r; | 1405 | | #else | 1406 | | // Fallback De Bruijn for non-GCC/non-MSVC compilers | 1407 | | static const int Debruijn64[64] = { | 1408 | | 0, 1, 48, 2, 57, 49, 28, 3, 61, 58, 50, 42, 38, 29, 17, 4, 62, 55, 59, 36, 53, 51, | 1409 | | 43, 22, 45, 39, 33, 30, 24, 18, 12, 5, 63, 47, 56, 27, 60, 41, 37, 16, 54, 35, 52, 21, | 1410 | | 44, 32, 23, 11, 46, 26, 40, 15, 34, 20, 31, 10, 25, 14, 19, 9, 13, 8, 7, 6}; | 1411 | | return Debruijn64[((x & (0ULL - x)) * 0x03F79D71B4CA8B09ULL) >> 58]; | 1412 | | #endif | 1413 | 1.62G | } |
Unexecuted instantiation: zxc_decompress.c:zxc_ctz64 Unexecuted instantiation: zxc_dict.c:zxc_ctz64 Unexecuted instantiation: zxc_driver.c:zxc_ctz64 Unexecuted instantiation: zxc_dispatch.c:zxc_ctz64 Unexecuted instantiation: zxc_huffman.c:zxc_ctz64 Unexecuted instantiation: zxc_pstream.c:zxc_ctz64 Unexecuted instantiation: zxc_seekable.c:zxc_ctz64 |
1414 | | |
1415 | | /** |
1416 | | * @brief Allocates aligned memory (`_aligned_malloc` on Windows, else `posix_memalign`). |
1417 | | * |
1418 | | * @param[in] size Bytes to allocate. |
1419 | | * @param[in] alignment Power of two, and a multiple of `sizeof(void*)`. |
1420 | | * @return The block, or NULL on failure. Free it with zxc_aligned_free(), not |
1421 | | * `free()`: the Windows allocator is a separate one. |
1422 | | */ |
1423 | | void* zxc_aligned_malloc(const size_t size, const size_t alignment); |
1424 | | |
1425 | | /** |
1426 | | * @brief Frees a zxc_aligned_malloc() block (`_aligned_free` on Windows, else `free`). |
1427 | | * |
1428 | | * @param[in] ptr Block to free; NULL is a no-op. |
1429 | | */ |
1430 | | void zxc_aligned_free(void* ptr); |
1431 | | |
1432 | | // ============================================================================ |
1433 | | // COMPRESSION CONTEXT & STRUCTS |
1434 | | // ============================================================================ |
1435 | | |
1436 | | // INTERNAL API |
1437 | | // ------------ |
1438 | | |
1439 | | /** |
1440 | | * @brief Calculates a 32-bit hash for a given input buffer. |
1441 | | * @param[in] input Pointer to the data buffer. |
1442 | | * @param[in] len Length of the data in bytes. |
1443 | | * @param[in] hash_method Checksum algorithm identifier (e.g., ZXC_CHECKSUM_RAPIDHASH). |
1444 | | * @return The calculated 32-bit hash value. |
1445 | | */ |
1446 | | static ZXC_ALWAYS_INLINE uint32_t zxc_checksum(const void* RESTRICT input, const size_t len, |
1447 | 69.1k | const uint8_t hash_method) { |
1448 | 69.1k | (void)hash_method; /* single algorithm for now; extend when adding more */ |
1449 | 69.1k | const uint64_t hash = rapidhash(input, len); |
1450 | | |
1451 | 69.1k | return (uint32_t)(hash ^ (hash >> (sizeof(uint32_t) * CHAR_BIT))); |
1452 | 69.1k | } Unexecuted instantiation: zxc_common.c:zxc_checksum zxc_compress.c:zxc_checksum Line | Count | Source | 1447 | 11.3k | const uint8_t hash_method) { | 1448 | 11.3k | (void)hash_method; /* single algorithm for now; extend when adding more */ | 1449 | 11.3k | const uint64_t hash = rapidhash(input, len); | 1450 | | | 1451 | | return (uint32_t)(hash ^ (hash >> (sizeof(uint32_t) * CHAR_BIT))); | 1452 | 11.3k | } |
zxc_decompress.c:zxc_checksum Line | Count | Source | 1447 | 5.93k | const uint8_t hash_method) { | 1448 | 5.93k | (void)hash_method; /* single algorithm for now; extend when adding more */ | 1449 | 5.93k | const uint64_t hash = rapidhash(input, len); | 1450 | | | 1451 | | return (uint32_t)(hash ^ (hash >> (sizeof(uint32_t) * CHAR_BIT))); | 1452 | 5.93k | } |
Line | Count | Source | 1447 | 51.8k | const uint8_t hash_method) { | 1448 | 51.8k | (void)hash_method; /* single algorithm for now; extend when adding more */ | 1449 | 51.8k | const uint64_t hash = rapidhash(input, len); | 1450 | | | 1451 | | return (uint32_t)(hash ^ (hash >> (sizeof(uint32_t) * CHAR_BIT))); | 1452 | 51.8k | } |
Unexecuted instantiation: zxc_driver.c:zxc_checksum Unexecuted instantiation: zxc_dispatch.c:zxc_checksum Unexecuted instantiation: zxc_huffman.c:zxc_checksum Unexecuted instantiation: zxc_pstream.c:zxc_checksum Unexecuted instantiation: zxc_seekable.c:zxc_checksum |
1453 | | |
1454 | | /** |
1455 | | * @brief Seeded variant of @ref zxc_checksum, for chaining a hash over |
1456 | | * non-contiguous buffers: `zxc_checksum_seed(b, bn, zxc_checksum(a, an, m), m)` |
1457 | | * hashes each byte once without a concat copy. |
1458 | | * @param[in] input Pointer to the data buffer. |
1459 | | * @param[in] len Length of the data in bytes. |
1460 | | * @param[in] seed Previous 32-bit checksum to chain from. |
1461 | | * @param[in] hash_method Checksum algorithm identifier (e.g., ZXC_CHECKSUM_RAPIDHASH). |
1462 | | * @return The calculated 32-bit hash value. |
1463 | | */ |
1464 | | static ZXC_ALWAYS_INLINE uint32_t zxc_checksum_seed(const void* RESTRICT input, const size_t len, |
1465 | | const uint32_t seed, |
1466 | 33.8k | const uint8_t hash_method) { |
1467 | 33.8k | (void)hash_method; /* single algorithm for now; extend when adding more */ |
1468 | 33.8k | const uint64_t hash = rapidhash_withSeed(input, len, seed); |
1469 | | |
1470 | 33.8k | return (uint32_t)(hash ^ (hash >> (sizeof(uint32_t) * CHAR_BIT))); |
1471 | 33.8k | } Unexecuted instantiation: zxc_common.c:zxc_checksum_seed Unexecuted instantiation: zxc_compress.c:zxc_checksum_seed Unexecuted instantiation: zxc_decompress.c:zxc_checksum_seed zxc_dict.c:zxc_checksum_seed Line | Count | Source | 1466 | 33.8k | const uint8_t hash_method) { | 1467 | 33.8k | (void)hash_method; /* single algorithm for now; extend when adding more */ | 1468 | 33.8k | const uint64_t hash = rapidhash_withSeed(input, len, seed); | 1469 | | | 1470 | | return (uint32_t)(hash ^ (hash >> (sizeof(uint32_t) * CHAR_BIT))); | 1471 | 33.8k | } |
Unexecuted instantiation: zxc_driver.c:zxc_checksum_seed Unexecuted instantiation: zxc_dispatch.c:zxc_checksum_seed Unexecuted instantiation: zxc_huffman.c:zxc_checksum_seed Unexecuted instantiation: zxc_pstream.c:zxc_checksum_seed Unexecuted instantiation: zxc_seekable.c:zxc_checksum_seed |
1472 | | |
1473 | | /** |
1474 | | * @brief Folds a block hash into the running global checksum. |
1475 | | * |
1476 | | * `result = rotl32(hash, 1) ^ block_hash`. The rotate is what makes the result |
1477 | | * depend on block order, so a reordered archive fails the global check. |
1478 | | * |
1479 | | * @param[in] hash The current running hash value. |
1480 | | * @param[in] block_hash The hash of the new block to combine. |
1481 | | * @return The updated combined hash value. |
1482 | | */ |
1483 | | static ZXC_ALWAYS_INLINE uint32_t zxc_hash_combine_rotate(const uint32_t hash, |
1484 | 17.2k | const uint32_t block_hash) { |
1485 | 17.2k | return ((hash << 1) | (hash >> 31)) ^ block_hash; |
1486 | 17.2k | } Unexecuted instantiation: zxc_common.c:zxc_hash_combine_rotate Unexecuted instantiation: zxc_compress.c:zxc_hash_combine_rotate Unexecuted instantiation: zxc_decompress.c:zxc_hash_combine_rotate Unexecuted instantiation: zxc_dict.c:zxc_hash_combine_rotate Unexecuted instantiation: zxc_driver.c:zxc_hash_combine_rotate zxc_dispatch.c:zxc_hash_combine_rotate Line | Count | Source | 1484 | 13.9k | const uint32_t block_hash) { | 1485 | 13.9k | return ((hash << 1) | (hash >> 31)) ^ block_hash; | 1486 | 13.9k | } |
Unexecuted instantiation: zxc_huffman.c:zxc_hash_combine_rotate zxc_pstream.c:zxc_hash_combine_rotate Line | Count | Source | 1484 | 3.27k | const uint32_t block_hash) { | 1485 | 3.27k | return ((hash << 1) | (hash >> 31)) ^ block_hash; | 1486 | 3.27k | } |
Unexecuted instantiation: zxc_seekable.c:zxc_hash_combine_rotate |
1487 | | |
1488 | | /** |
1489 | | * @brief Writes a GLO sub-header followed by its section descriptors. |
1490 | | * |
1491 | | * They hold only the two sizes the header cannot imply, and are 0, 4 or 8 bytes |
1492 | | * wide accordingly. |
1493 | | * |
1494 | | * @param[out] dst Pointer to the destination buffer. |
1495 | | * @param[in] rem The remaining space in the destination buffer. |
1496 | | * @param[in] gh Pointer to the generic header structure to write. |
1497 | | * @param[in] lit_comp Compressed size of the literal section. |
1498 | | * @param[in] tok_comp Compressed size of the token section. |
1499 | | * @return The number of bytes written, or a negative error code if the buffer |
1500 | | * is too small. |
1501 | | */ |
1502 | | int zxc_write_glo_header_and_desc(uint8_t* RESTRICT dst, const size_t rem, |
1503 | | const zxc_gnr_header_t* RESTRICT gh, const uint32_t lit_comp, |
1504 | | const uint32_t tok_comp); |
1505 | | |
1506 | | /** |
1507 | | * @brief Reads a GLO sub-header and its section descriptors from a source buffer. |
1508 | | * |
1509 | | * Sizes absent from the descriptors are reconstructed from the header, so both |
1510 | | * outputs are always populated. |
1511 | | * |
1512 | | * @param[in] src Pointer to the source buffer. |
1513 | | * @param[in] len The length of the source buffer available for reading. |
1514 | | * @param[out] gh Pointer to the generic header structure to populate. |
1515 | | * @param[out] lit_comp Receives the literal section's compressed size. |
1516 | | * @param[out] tok_comp Receives the token section's compressed size. |
1517 | | * @return Bytes consumed (header + table), or a negative zxc_error_t code. |
1518 | | */ |
1519 | | int zxc_read_glo_header_and_desc(const uint8_t* RESTRICT src, const size_t len, |
1520 | | zxc_gnr_header_t* RESTRICT gh, uint32_t* RESTRICT lit_comp, |
1521 | | uint32_t* RESTRICT tok_comp); |
1522 | | |
1523 | | /** |
1524 | | * @brief Writes a GHI sub-header. GHI carries no section descriptors. |
1525 | | * |
1526 | | * @param[out] dst Pointer to the destination buffer. |
1527 | | * @param[in] rem Remaining size available in the destination buffer. |
1528 | | * @param[in] gh Pointer to the GNR header structure containing header information. |
1529 | | * @return The number of bytes written, or a negative error code on failure. |
1530 | | */ |
1531 | | int zxc_write_ghi_header(uint8_t* RESTRICT dst, const size_t rem, |
1532 | | const zxc_gnr_header_t* RESTRICT gh); |
1533 | | |
1534 | | /** |
1535 | | * @brief Reads a GHI sub-header from a buffer. |
1536 | | * |
1537 | | * @param[in] src Pointer to the source buffer containing the record data. |
1538 | | * @param[in] len Length of the source buffer in bytes. |
1539 | | * @param[out] gh Pointer to a zxc_gnr_header_t structure to store the parsed header. |
1540 | | * @return ZXC_OK on success, or a negative zxc_error_t code on failure. |
1541 | | */ |
1542 | | int zxc_read_ghi_header(const uint8_t* RESTRICT src, const size_t len, |
1543 | | zxc_gnr_header_t* RESTRICT gh); |
1544 | | |
1545 | | // ============================================================================ |
1546 | | // Huffman codec for the GLO literal stream (level >= 6). |
1547 | | // |
1548 | | // On-disk layout, decoder geometry and tunables: see |
1549 | | // @ref ZXC_HUF_MAX_CODE_LEN_ULTRA and the surrounding "Huffman Codec Constants" |
1550 | | // group above. |
1551 | | // ============================================================================ |
1552 | | |
1553 | | /** |
1554 | | * @brief Build length-limited canonical Huffman code lengths from a frequency table. |
1555 | | * |
1556 | | * Uses the boundary package-merge algorithm capped at `ZXC_HUF_MAX_CODE_LEN_ULTRA`. |
1557 | | * Symbols with `freq[i] == 0` get `code_len[i] == 0`; others receive a value |
1558 | | * in `[1, ZXC_HUF_MAX_CODE_LEN_ULTRA]`. |
1559 | | * |
1560 | | * @param[in] freq Frequency table of length `ZXC_HUF_NUM_SYMBOLS`. |
1561 | | * @param[out] code_len Output code-length array of length `ZXC_HUF_NUM_SYMBOLS`. |
1562 | | * @param[in] scratch Optional caller-owned scratch buffer of at least |
1563 | | * ::ZXC_HUF_BUILD_SCRATCH_SIZE bytes. If `NULL`, the |
1564 | | * function allocates its own working memory and frees |
1565 | | * it before returning. |
1566 | | * @param[in] max_code_len Code-length ceiling in `[1, ZXC_HUF_MAX_CODE_LEN_ULTRA]`; |
1567 | | * package-merge is run for this many levels (see |
1568 | | * ::zxc_huf_enc_max_code_len for the per-level value). |
1569 | | * @return `ZXC_OK` on success, negative `zxc_error_t` code on failure. |
1570 | | */ |
1571 | | int zxc_huf_build_code_lengths(const uint32_t* RESTRICT freq, uint8_t* RESTRICT code_len, |
1572 | | void* RESTRICT scratch, int max_code_len); |
1573 | | |
1574 | | /** |
1575 | | * @brief Optionally reshape freshly built code lengths for faster PivCo decode. |
1576 | | * |
1577 | | * Explores a small set of Kraft-exact alternatives to @p code_len (a greedy |
1578 | | * slot-ledger walk toward power-of-two class counts, package-merge rebuilds |
1579 | | * at reduced depth caps, and the slot-ledger dynamic program at a coarse |
1580 | | * granularity picked from the alphabet size) and prices each against the |
1581 | | * modeled decode cost of zxc_pivco_decode_core. The cheapest candidate that |
1582 | | * clears the adoption guard (<= +::ZXC_HUF_NUDGE_BITS_PERMIL ratio cost AND |
1583 | | * <= ::ZXC_HUF_NUDGE_MERGE_Q8 modeled level-touches) replaces @p code_len; |
1584 | | * otherwise the array is left byte-for-byte untouched, so a rejected nudge |
1585 | | * emits an archive identical to the unadjusted encoder. Coarse-DP candidates |
1586 | | * may pad the tree with zero-frequency "ghost" leaves on unused byte values; |
1587 | | * the wire carries them as empty runs. |
1588 | | * |
1589 | | * Encoder policy only: any adopted output is canonical, Kraft-exact and capped |
1590 | | * at @p max_code_len, so the wire format and every deployed decoder are |
1591 | | * unaffected. Compiled once in the primary variant (ISA-independent decision |
1592 | | * code), guaranteeing cross-ISA identical archives. Cost is a few hundred |
1593 | | * microseconds per table at most (DP plane sizes are capped by the coarse |
1594 | | * granularity), which the ULTRA-only and trainer call sites absorb. |
1595 | | * |
1596 | | * @param[in] freq Frequency table of length `ZXC_HUF_NUM_SYMBOLS`. |
1597 | | * @param[in,out] code_len Lengths from ::zxc_huf_build_code_lengths. |
1598 | | * @param[in] scratch Optional ::ZXC_HUF_BUILD_SCRATCH_SIZE scratch for |
1599 | | * the reduced-cap rebuilds (NULL = allocate). |
1600 | | * @param[in] max_code_len Cap the caller built with (level cap). |
1601 | | * @return 1 if @p code_len was adjusted, 0 if kept. |
1602 | | */ |
1603 | | int zxc_huf_nudge_code_lengths(const uint32_t* RESTRICT freq, uint8_t* RESTRICT code_len, |
1604 | | void* RESTRICT scratch, int max_code_len); |
1605 | | |
1606 | | /** |
1607 | | * @brief Modeled (bits, level-touches) decode cost of one code-length vector. |
1608 | | * |
1609 | | * Introspection hook for the nudge's cost model (exact, canonical-order |
1610 | | * frequency weighting); the unit tests cross-check it against the real |
1611 | | * tree built by the decoder. @p code_len must be structurally valid. |
1612 | | */ |
1613 | | void zxc_huf_nudge_cost(const uint8_t* RESTRICT code_len, const uint32_t* RESTRICT freq, |
1614 | | uint64_t* RESTRICT bits, uint64_t* RESTRICT touches); |
1615 | | |
1616 | | /** |
1617 | | * @brief Pack per-symbol code lengths into the 128-byte (4-bit nibble) header. |
1618 | | * |
1619 | | * Nibble order follows the byte: `code_len[2*i]` low, `code_len[2*i + 1]` high. |
1620 | | * Lengths above 15 are silently truncated, so the caller must have capped at |
1621 | | * `ZXC_HUF_MAX_CODE_LEN_ULTRA` (<= 15) first. |
1622 | | * |
1623 | | * @param[in] code_len Per-symbol lengths, `ZXC_HUF_NUM_SYMBOLS` entries. |
1624 | | * @param[out] out `ZXC_HUF_TABLE_SIZE` bytes of packed header. |
1625 | | */ |
1626 | | void zxc_huf_pack_lengths(const uint8_t* RESTRICT code_len, uint8_t* RESTRICT out); |
1627 | | |
1628 | | /** |
1629 | | * @brief Unpack and structurally validate a 128-byte packed lengths header. |
1630 | | * |
1631 | | * Inverts ::zxc_huf_pack_lengths and checks the two structural invariants: no |
1632 | | * length above `ZXC_HUF_MAX_CODE_LEN_ULTRA`, and at least one symbol present. |
1633 | | * Kraft consistency is not checked here; the tree build does that later. |
1634 | | * |
1635 | | * @param[in] in 128-byte packed header. |
1636 | | * @param[out] code_len Per-symbol lengths, `ZXC_HUF_NUM_SYMBOLS` entries. |
1637 | | * @return `ZXC_OK`, or `ZXC_ERROR_CORRUPT_DATA` if a length is too large or the |
1638 | | * table is empty. |
1639 | | */ |
1640 | | int zxc_huf_unpack_lengths(const uint8_t* RESTRICT in, uint8_t* RESTRICT code_len); |
1641 | | |
1642 | | // -------------------------------------------------------------------------- |
1643 | | // PivCo-Huffman section codec (enc 2/3) |
1644 | | // |
1645 | | // Layout from PivCo-Huffman by Marcin Zukowski |
1646 | | // (https://github.com/MarcinZukowski/pivco-huffman); implemented |
1647 | | // independently here. See zxc_huffman.c for the codec. |
1648 | | // |
1649 | | // Same code bits as canonical Huffman, reordered by tree LEVEL: for each |
1650 | | // internal node in BFS order, its branch bits (one per symbol routed through |
1651 | | // it, LSB-first, byte-aligned). No size fields - the decoder derives every run |
1652 | | // length from the root count and popcounts. Decoding is bottom-up level merges, |
1653 | | // shuffle-parallel and gather-free. |
1654 | | // -------------------------------------------------------------------------- |
1655 | | |
1656 | | /** @brief Extra scratch slack required past `n` by the PivCo decoder. */ |
1657 | 7.62k | #define ZXC_PIVCO_SCRATCH_PAD 32 |
1658 | | |
1659 | | /** @brief Exact encoded size (bytes) of a PivCo section for this histogram and |
1660 | | * code lengths; includes the 128-byte lengths header when @p with_header. */ |
1661 | | size_t zxc_huf_calc_size(const uint32_t* RESTRICT freq, const uint8_t* RESTRICT code_len, |
1662 | | int with_header); |
1663 | | |
1664 | | /** @brief Encode a PivCo literal section (128-byte lengths header + payload). */ |
1665 | | int zxc_huf_encode_section(const uint8_t* RESTRICT literals, size_t n_literals, |
1666 | | const uint32_t* RESTRICT freq, const uint8_t* RESTRICT code_len, |
1667 | | uint8_t* RESTRICT dst, size_t dst_cap); |
1668 | | |
1669 | | /** @brief Unpack a dict table's 128-byte packed lengths and prebuild its PivCo |
1670 | | * tree, canonical codes, code lengths and decoder tables (tree-at-attach). |
1671 | | * All outputs are frame-constant; per-block encode/estimate/decode then skip |
1672 | | * the rebuild. */ |
1673 | | int zxc_huf_dict_tree_build(const uint8_t* RESTRICT packed_lengths, zxc_pivco_tree_t* RESTRICT tree, |
1674 | | uint32_t* RESTRICT codes, uint8_t* RESTRICT code_len, |
1675 | | zxc_pivco_decode_aux_t* RESTRICT aux); |
1676 | | |
1677 | | /** @brief zxc_huf_calc_size for a dict section: prebuilt @p tree, no header. */ |
1678 | | size_t zxc_huf_calc_size_dict(const uint32_t* RESTRICT freq, const uint8_t* RESTRICT code_len, |
1679 | | const zxc_pivco_tree_t* RESTRICT tree); |
1680 | | |
1681 | | /** @brief Encode a PivCo section against a prebuilt dict tree/codes (no header). */ |
1682 | | int zxc_huf_encode_section_dict(const uint8_t* RESTRICT literals, size_t n_literals, |
1683 | | const uint32_t* RESTRICT freq, const uint8_t* RESTRICT code_len, |
1684 | | const zxc_pivco_tree_t* RESTRICT tree, |
1685 | | const uint32_t* RESTRICT codes, uint8_t* RESTRICT dst, |
1686 | | size_t dst_cap); |
1687 | | |
1688 | | /** @brief Decode a PivCo literal section into @p dst (exactly @p n bytes). |
1689 | | * @p dst needs ZXC_PAD_SIZE slack, @p scratch at least n + ZXC_PIVCO_SCRATCH_PAD. */ |
1690 | | int zxc_huf_decode_section(const uint8_t* RESTRICT payload, size_t payload_size, |
1691 | | uint8_t* RESTRICT dst, size_t n, uint8_t* RESTRICT scratch); |
1692 | | |
1693 | | /** @brief Decode a PivCo dict section against a prebuilt dict @p tree and its |
1694 | | * attach-time decoder tables @p aux. */ |
1695 | | int zxc_huf_decode_section_dict(const uint8_t* RESTRICT payload, size_t payload_size, |
1696 | | uint8_t* RESTRICT dst, size_t n, |
1697 | | const zxc_pivco_tree_t* RESTRICT tree, |
1698 | | const zxc_pivco_decode_aux_t* RESTRICT aux, |
1699 | | uint8_t* RESTRICT scratch); |
1700 | | |
1701 | | // --------------------------------------------------------------------------- |
1702 | | // Compression / decompression context. |
1703 | | // |
1704 | | // The context owns the working buffers (hash table, sequence buffers, scratch) |
1705 | | // that encoder and decoder reuse across blocks. It stays private - the public |
1706 | | // APIs already wrap it opaquely - so the layout can evolve (cache-line |
1707 | | // placement, extra scratch arenas) without breaking the ABI. |
1708 | | // --------------------------------------------------------------------------- |
1709 | | |
1710 | | /** |
1711 | | * @struct zxc_cctx_t |
1712 | | * @brief Compression / decompression context. |
1713 | | * |
1714 | | * Holds the buffers reused across blocks to avoid repeated allocations. |
1715 | | * |
1716 | | * **Key fields:** |
1717 | | * - @c hash_table: epoch-tagged positions (`ZXC_LZ_HASH_SIZE` * 4 bytes). |
1718 | | * - @c hash_tags: 8-bit tags for fast match rejection |
1719 | | * (`ZXC_LZ_HASH_SIZE` * 1 byte). |
1720 | | * - @c chain_table: collision chain storing the *previous* occurrence of a |
1721 | | * hash, forming a linked list per bucket and enabling history traversal. |
1722 | | * - @c epoch: drives "lazy hash table invalidation". Instead of memset-ing |
1723 | | * the hash table for every block, we store `(epoch << offset_bits) | offset`; an |
1724 | | * entry whose stored epoch differs from `ctx->epoch` is treated as empty. |
1725 | | */ |
1726 | | typedef struct { |
1727 | | // Hot zone: random access / high frequency. |
1728 | | // Kept at the start to ensure they reside in the first cache line (64 bytes). |
1729 | | uint32_t* hash_table; /**< Hash table for LZ77 match positions (epoch|pos). */ |
1730 | | uint8_t* hash_tags; /**< Split tag table for fast match rejection (8-bit tags). */ |
1731 | | uint16_t* chain_table; /**< Chain table for collision resolution. */ |
1732 | | void* memory_block; /**< Single allocation block owner. */ |
1733 | | uint32_t epoch; /**< Current epoch for lazy hash table invalidation. */ |
1734 | | |
1735 | | // Warm zone: sequential access per sequence. |
1736 | | uint32_t* buf_sequences; /**< Buffer for sequence records (packed: LL(8)|ML(8)|Offset(16)). */ |
1737 | | uint8_t* buf_tokens; /**< Buffer for token sequences. */ |
1738 | | uint16_t* buf_offsets; /**< Buffer for offsets. */ |
1739 | | uint8_t* buf_extras; /**< Buffer for extra lengths (vbytes for LL/ML). */ |
1740 | | uint8_t* literals; /**< Buffer for literal bytes. */ |
1741 | | |
1742 | | // Cold zone: configuration / scratch / resizeable. |
1743 | | uint8_t* lit_buffer; /**< Scratch buffer for literals (RLE / Huffman). */ |
1744 | | size_t lit_buffer_cap; /**< Current capacity of the scratch buffer. */ |
1745 | | uint8_t* work_buf; /**< Padded scratch buffer for buffer-API decompression. */ |
1746 | | size_t work_buf_cap; /**< Capacity of the work buffer. */ |
1747 | | uint8_t* tok_buffer; /**< Decode scratch for a Huffman-coded GLO token |
1748 | | section (enc_tok == HUFFMAN); NULL on compress. |
1749 | | Heap decode contexts defer it (with pivco_scratch) |
1750 | | to the first entropy section, see entropy_block. */ |
1751 | | size_t tok_buffer_cap; /**< Capacity of tok_buffer in bytes. */ |
1752 | | uint8_t* pivco_scratch; /**< Level ping-pong scratch for PivCo decode. */ |
1753 | | size_t pivco_scratch_cap; /**< Capacity of pivco_scratch in bytes. */ |
1754 | | void* entropy_block; /**< Lazy allocation backing tok_buffer + pivco_scratch |
1755 | | (heap decode contexts, first entropy block only). |
1756 | | NULL on compress contexts and static workspaces. |
1757 | | Freed by zxc_cctx_free. */ |
1758 | | uint8_t* opt_scratch; /**< Optimal-parser DP scratch (level >= 6 only, |
1759 | | lazy-allocated, packs dp/parent_len/parent_off/actions). |
1760 | | Also reused as transient scratch for the |
1761 | | length-limited Huffman code-length builder. */ |
1762 | | size_t opt_scratch_cap; /**< Current capacity of opt_scratch in bytes. */ |
1763 | | int checksum_enabled; /**< 1 if checksum calculation/verification is enabled. */ |
1764 | | int compression_level; /**< Compression level. */ |
1765 | | size_t dict_size; /**< Dictionary prefill size (0 = no dictionary). */ |
1766 | | uint8_t* dict_buffer; /**< [dict | data] concat scratch carved from memory_block |
1767 | | when dict_size > 0 (NULL otherwise). */ |
1768 | | size_t dict_buffer_cap; /**< Capacity of dict_buffer in bytes (0 = none). */ |
1769 | | zxc_dict_huf_state_t* dict_huf; /**< Tree-at-attach state (PivCo tree + codes + code |
1770 | | lengths), carved from the workspace only when |
1771 | | dict_size > 0 (NULL otherwise); built once by |
1772 | | zxc_cctx_attach_dict_huf. Valid iff dict_huf_tree_ok. */ |
1773 | | int dict_huf_tree_ok; /**< 1 when *dict_huf is built and valid. */ |
1774 | | uint32_t* lit_freq_acc; /**< Trainer hook: when non-NULL, the GLO encoder |
1775 | | accumulates post-LZ literal byte frequencies here |
1776 | | (256 entries). NULL outside dictionary training. */ |
1777 | | |
1778 | | // Block-size derived parameters (computed once at init). |
1779 | | size_t chunk_size; /**< Effective block size in bytes. */ |
1780 | | uint32_t offset_bits; /**< log2(chunk_size) - governs epoch_mark shift. */ |
1781 | | uint32_t offset_mask; /**< (1U << offset_bits) - 1 */ |
1782 | | uint32_t max_epoch; /**< 1U << (32 - offset_bits) */ |
1783 | | } zxc_cctx_t; |
1784 | | |
1785 | | /** |
1786 | | * @brief Initialises a ZXC compression / decompression context in place. |
1787 | | * |
1788 | | * Allocates the internal buffers (hash table, sequence buffers, scratch) sized |
1789 | | * for @p chunk_size and the requested @p mode. |
1790 | | * |
1791 | | * @param[out] ctx Context to initialise. |
1792 | | * @param[in] chunk_size Block size driving buffer sizing. |
1793 | | * @param[in] mode 1 for compression, 0 for decompression. |
1794 | | * @param[in] level Compression level (ignored when @p mode == 0). |
1795 | | * @param[in] checksum_enabled Non-zero to enable checksum computation. |
1796 | | * @param[in] dict_size Dictionary prefill size; when > 0 an extra |
1797 | | * [dict | data] concat buffer is carved into the |
1798 | | * workspace and @c ctx->dict_buffer is set. |
1799 | | * |
1800 | | * @return @c ZXC_OK on success, or a negative @ref zxc_error_t code (notably |
1801 | | * @c ZXC_ERROR_MEMORY on allocation failure). |
1802 | | */ |
1803 | | int zxc_cctx_init(zxc_cctx_t* ctx, const size_t chunk_size, const int mode, const int level, |
1804 | | const int checksum_enabled, const size_t dict_size); |
1805 | | |
1806 | | /** |
1807 | | * @brief Attach the shared dictionary literal table to an initialised context. |
1808 | | * |
1809 | | * Validates the 128-byte packed code-lengths header and builds the PivCo tree, |
1810 | | * canonical codes and decoder tables ONCE into the context (tree-at-attach); |
1811 | | * per-block encode/estimate/decode reuse them. @p lengths need only be valid |
1812 | | * during this call (everything is copied into the context workspace). A NULL |
1813 | | * @p lengths is a no-op. |
1814 | | * |
1815 | | * @return @ref ZXC_OK on success, @ref ZXC_ERROR_CORRUPT_DATA if the lengths |
1816 | | * header is structurally invalid (bad nibble, Kraft inequality). |
1817 | | */ |
1818 | | int zxc_cctx_attach_dict_huf(zxc_cctx_t* RESTRICT ctx, const uint8_t* RESTRICT lengths); |
1819 | | |
1820 | | /** |
1821 | | * @brief Returns the byte count that @ref zxc_cctx_init would allocate for |
1822 | | * the given parameters. |
1823 | | * |
1824 | | * Used by the static-cctx public API to size a caller-supplied workspace |
1825 | | * before calling @ref zxc_cctx_init_in_workspace. |
1826 | | * |
1827 | | * @param[in] chunk_size Block size in bytes (must satisfy |
1828 | | * @ref zxc_validate_block_size). |
1829 | | * @param[in] mode 1 = compression, 0 = decompression. |
1830 | | * @param[in] level Compression level (only consulted when @p mode == 1). |
1831 | | * @param[in] dict_size Dictionary prefill size; when > 0 the figure includes |
1832 | | * the [dict | data] concat buffer. |
1833 | | * @return Size in bytes, or 0 if the parameters are invalid. |
1834 | | */ |
1835 | | size_t zxc_cctx_compute_workspace_size(const size_t chunk_size, const int mode, const int level, |
1836 | | const size_t dict_size); |
1837 | | |
1838 | | /** |
1839 | | * @brief Initialises a compression / decompression context inside a |
1840 | | * caller-supplied workspace. |
1841 | | * |
1842 | | * Identical to @ref zxc_cctx_init except that the persistent buffer is |
1843 | | * carved out of @p workspace instead of being @c ZXC_ALIGNED_MALLOC'd |
1844 | | * internally. @p workspace must be cache-line aligned and at least as |
1845 | | * large as @ref zxc_cctx_compute_workspace_size for the same parameters. |
1846 | | * |
1847 | | * The caller owns @p workspace and must keep it alive for the lifetime of |
1848 | | * @p ctx. @ref zxc_cctx_free becomes a no-op for contexts initialised |
1849 | | * this way (the workspace is not freed by the library). |
1850 | | * |
1851 | | * @param[out] ctx Context to initialise (zeroed on entry). |
1852 | | * @param[in] workspace Caller-allocated, cache-line-aligned buffer. |
1853 | | * @param[in] workspace_size Capacity of @p workspace in bytes. |
1854 | | * @param[in] chunk_size Block size in bytes. |
1855 | | * @param[in] mode 1 = compression, 0 = decompression. |
1856 | | * @param[in] level Compression level (ignored when @p mode == 0). |
1857 | | * @param[in] checksum_enabled Non-zero to enable checksum computation. |
1858 | | * @param[in] dict_size Dictionary prefill size; when > 0 the workspace |
1859 | | * must include the [dict | data] concat buffer and |
1860 | | * @c ctx->dict_buffer is set into it. |
1861 | | * @param[in] defer_entropy_scratch Non-zero (heap decode contexts only) to |
1862 | | * leave the tok/PivCo decode scratch out of the |
1863 | | * partition; it is then lazily allocated by |
1864 | | * @ref zxc_cctx_alloc_entropy_scratch on the |
1865 | | * first entropy section. Static workspaces must |
1866 | | * pass 0 (no-allocation contract). |
1867 | | * @return @c ZXC_OK on success, @c ZXC_ERROR_DST_TOO_SMALL if the workspace |
1868 | | * is too small, or another negative @ref zxc_error_t. |
1869 | | */ |
1870 | | int zxc_cctx_init_in_workspace(zxc_cctx_t* RESTRICT ctx, void* RESTRICT workspace, |
1871 | | const size_t workspace_size, const size_t chunk_size, const int mode, |
1872 | | const int level, const int checksum_enabled, const size_t dict_size, |
1873 | | const int defer_entropy_scratch); |
1874 | | |
1875 | | /** |
1876 | | * @brief Lazily allocates the decode-side entropy scratch (tok_buffer + |
1877 | | * pivco_scratch) for a heap context initialised with deferral. |
1878 | | * |
1879 | | * No-op when the scratch is already present (static workspaces pre-carve it; |
1880 | | * subsequent entropy blocks reuse the first allocation). The block is owned |
1881 | | * by the context (@c entropy_block) and released by @ref zxc_cctx_free. |
1882 | | * |
1883 | | * @return @ref ZXC_OK, or @ref ZXC_ERROR_MEMORY on allocation failure. |
1884 | | */ |
1885 | | int zxc_cctx_alloc_entropy_scratch(zxc_cctx_t* ctx); |
1886 | | |
1887 | | /** |
1888 | | * @brief Releases the internal buffers owned by a context. |
1889 | | * |
1890 | | * Does NOT free @p ctx itself - the caller owns the struct storage. The |
1891 | | * context may safely be re-initialised with zxc_cctx_init() afterwards. |
1892 | | * |
1893 | | * @param[in,out] ctx Context whose buffers should be released. |
1894 | | */ |
1895 | | void zxc_cctx_free(zxc_cctx_t* ctx); |
1896 | | |
1897 | | /** |
1898 | | * @brief Decompresses one chunk through the runtime ISA dispatch. |
1899 | | * |
1900 | | * Un-suffixed entry point: it loads the resolved variant pointer (`_default`, |
1901 | | * `_avx2`, `_avx512`, ...), running the one-time CPU detection on the first |
1902 | | * call, and routes to the dict variant when the context carries a dictionary. |
1903 | | * |
1904 | | * @param[in] ctx Context holding the decode state and dictionary, if any. |
1905 | | * @param[in] src Compressed chunk. |
1906 | | * @param[in] src_sz Size of @p src in bytes. |
1907 | | * @param[out] dst Destination buffer. |
1908 | | * @param[in] dst_cap Capacity of @p dst. |
1909 | | * @return Bytes decoded (> 0), or a negative @ref zxc_error_t. |
1910 | | */ |
1911 | | int zxc_decompress_chunk_wrapper(const zxc_cctx_t* RESTRICT ctx, const uint8_t* RESTRICT src, |
1912 | | const size_t src_sz, uint8_t* RESTRICT dst, const size_t dst_cap); |
1913 | | int zxc_decompress_chunk_wrapper_dict(const zxc_cctx_t* RESTRICT ctx, const uint8_t* RESTRICT src, |
1914 | | const size_t src_sz, uint8_t* RESTRICT dst, |
1915 | | const size_t dst_cap); |
1916 | | |
1917 | | /** |
1918 | | * @brief Compresses one chunk through the runtime ISA dispatch. |
1919 | | * |
1920 | | * Counterpart of zxc_decompress_chunk_wrapper(): same lazily-resolved variant |
1921 | | * pointer, same one-time CPU detection on the first call. |
1922 | | * |
1923 | | * @param[in,out] ctx Compression context: configuration and working buffers. |
1924 | | * @param[in] src Raw data to compress. |
1925 | | * @param[in] src_sz Size of @p src in bytes. |
1926 | | * @param[out] dst Destination buffer. |
1927 | | * @param[in] dst_cap Capacity of @p dst. |
1928 | | * @return Bytes written (> 0), or a negative @ref zxc_error_t. |
1929 | | */ |
1930 | | int zxc_compress_chunk_wrapper(zxc_cctx_t* RESTRICT ctx, const uint8_t* RESTRICT src, |
1931 | | const size_t src_sz, uint8_t* RESTRICT dst, const size_t dst_cap); |
1932 | | |
1933 | | // --------------------------------------------------------------------------- |
1934 | | // Internal frame primitives. |
1935 | | // |
1936 | | // Read/write the ZXC file header, block header and footer. Kept internal: |
1937 | | // exposing them would freeze on-disk details (block_flags layout, footer |
1938 | | // composition) that stay free to evolve until the format is declared stable. |
1939 | | // --------------------------------------------------------------------------- |
1940 | | |
1941 | | /** |
1942 | | * @brief On-disk header structure for a ZXC block (8 bytes, little-endian). |
1943 | | * |
1944 | | * @c raw_size is not stored in the header; decoders derive it from Section |
1945 | | * Descriptors within the compressed payload. |
1946 | | */ |
1947 | | typedef struct { |
1948 | | uint8_t block_type; /**< Block type (see @ref zxc_block_type_t). */ |
1949 | | uint8_t block_flags; /**< Flags (e.g., checksum presence). */ |
1950 | | uint8_t reserved; /**< Reserved for future protocol extensions. */ |
1951 | | uint8_t header_crc; /**< Header integrity checksum (1 byte). */ |
1952 | | uint32_t comp_size; /**< Compressed size excluding this header. */ |
1953 | | } zxc_block_header_t; |
1954 | | |
1955 | | /** |
1956 | | * @brief Writes the standard ZXC file header into @p dst. |
1957 | | * |
1958 | | * Stores the magic word (little-endian) and the version number into the |
1959 | | * provided buffer, after checking that it has sufficient capacity. |
1960 | | * |
1961 | | * @param[out] dst Destination buffer. |
1962 | | * @param[in] dst_capacity Total capacity of @p dst in bytes. |
1963 | | * @param[in] chunk_size Block size to encode in the header. |
1964 | | * @param[in] has_checksum Non-zero if the checksum bit must be set. |
1965 | | * @param[in] dict_id Dictionary ID (0 = no dictionary). |
1966 | | * |
1967 | | * @return Number of bytes written (@c ZXC_FILE_HEADER_SIZE) on success, |
1968 | | * or @c ZXC_ERROR_DST_TOO_SMALL if @p dst_capacity is insufficient. |
1969 | | */ |
1970 | | int zxc_write_file_header(uint8_t* RESTRICT dst, const size_t dst_capacity, const size_t chunk_size, |
1971 | | const int has_checksum, const uint32_t dict_id); |
1972 | | |
1973 | | /** |
1974 | | * @brief Validates and reads the ZXC file header from @p src. |
1975 | | * |
1976 | | * Checks that the source buffer is large enough to contain a ZXC file header |
1977 | | * and that the magic word and version number match the expected format. |
1978 | | * |
1979 | | * @param[in] src Pointer to the source buffer. |
1980 | | * @param[in] src_size Size of the source buffer in bytes. |
1981 | | * @param[out] out_block_size Optional pointer that receives the recommended |
1982 | | * block size. May be @c NULL. |
1983 | | * @param[out] out_has_checksum Optional pointer that receives the checksum |
1984 | | * flag. May be @c NULL. |
1985 | | * @param[out] out_dict_id Optional pointer that receives the dictionary |
1986 | | * ID (0 if none). May be @c NULL. |
1987 | | * |
1988 | | * @return @c ZXC_OK on success, or a negative error code (e.g. |
1989 | | * @c ZXC_ERROR_SRC_TOO_SMALL, @c ZXC_ERROR_BAD_MAGIC, |
1990 | | * @c ZXC_ERROR_BAD_VERSION). |
1991 | | */ |
1992 | | int zxc_read_file_header(const uint8_t* RESTRICT src, const size_t src_size, size_t* out_block_size, |
1993 | | int* out_has_checksum, uint32_t* out_dict_id); |
1994 | | |
1995 | | /** |
1996 | | * @brief Encodes a block header into @p dst. |
1997 | | * |
1998 | | * Serialises the contents of a @ref zxc_block_header_t structure into a byte |
1999 | | * array in little-endian format, after checking that @p dst has sufficient |
2000 | | * capacity. |
2001 | | * |
2002 | | * @param[out] dst Destination buffer. |
2003 | | * @param[in] dst_capacity Total capacity of @p dst in bytes. |
2004 | | * @param[in] bh Source block header structure to serialise. |
2005 | | * |
2006 | | * @return Number of bytes written (@c ZXC_BLOCK_HEADER_SIZE) on success, |
2007 | | * or @c ZXC_ERROR_DST_TOO_SMALL if @p dst_capacity is insufficient. |
2008 | | */ |
2009 | | int zxc_write_block_header(uint8_t* RESTRICT dst, const size_t dst_capacity, |
2010 | | const zxc_block_header_t* bh); |
2011 | | |
2012 | | /** |
2013 | | * @brief Reads and parses a ZXC block header from @p src. |
2014 | | * |
2015 | | * Extracts the block type, flags, reserved fields, and compressed size from |
2016 | | * the first @c ZXC_BLOCK_HEADER_SIZE bytes of @p src. Multi-byte fields are |
2017 | | * decoded as little-endian. |
2018 | | * |
2019 | | * @param[in] src Source buffer holding the encoded block header. |
2020 | | * @param[in] src_size Size of @p src in bytes. |
2021 | | * @param[out] bh Block header structure populated with the parsed data. |
2022 | | * |
2023 | | * @return @c ZXC_OK on success, or @c ZXC_ERROR_SRC_TOO_SMALL if @p src is |
2024 | | * smaller than @c ZXC_BLOCK_HEADER_SIZE. |
2025 | | */ |
2026 | | int zxc_read_block_header(const uint8_t* RESTRICT src, const size_t src_size, |
2027 | | zxc_block_header_t* bh); |
2028 | | |
2029 | | /** |
2030 | | * @brief Writes the ZXC file footer into @p dst. |
2031 | | * |
2032 | | * The footer stores the original uncompressed size and an optional global |
2033 | | * checksum. It is always @c ZXC_FILE_FOOTER_SIZE (12) bytes long. |
2034 | | * |
2035 | | * @param[out] dst Destination buffer. |
2036 | | * @param[in] dst_capacity Total capacity of @p dst in bytes. |
2037 | | * @param[in] src_size Original uncompressed size of the data. |
2038 | | * @param[in] global_hash Global checksum hash (used only when |
2039 | | * @p checksum_enabled is non-zero). |
2040 | | * @param[in] checksum_enabled Non-zero if the checksum should be emitted. |
2041 | | * |
2042 | | * @return Number of bytes written (@c ZXC_FILE_FOOTER_SIZE) on success, |
2043 | | * or @c ZXC_ERROR_DST_TOO_SMALL on failure. |
2044 | | */ |
2045 | | int zxc_write_file_footer(uint8_t* RESTRICT dst, const size_t dst_capacity, const uint64_t src_size, |
2046 | | const uint32_t global_hash, const int checksum_enabled); |
2047 | | |
2048 | | // --------------------------------------------------------------------------- |
2049 | | // Seekable cross-TU hooks (defined in zxc_seekable.c, consumed by the |
2050 | | // FILE*-flavored open helper in zxc_driver.c). |
2051 | | // ------------------------------------------------------------------------- |
2052 | | |
2053 | | /** |
2054 | | * @brief Hands ownership of a heap-allocated reader context to a seekable |
2055 | | * handle. The context will be released via @c ZXC_FREE when |
2056 | | * @ref zxc_seekable_free is called on @p s. |
2057 | | * |
2058 | | * Safe to call exactly once per handle. Intended for thin wrappers that |
2059 | | * build a @ref zxc_reader_t over their own allocated state |
2060 | | * (@ref zxc_seekable_open_file) and need that state to outlive the call |
2061 | | * site. |
2062 | | * |
2063 | | * @param[in,out] s Seekable handle returned by @ref zxc_seekable_open_reader. |
2064 | | * @param[in] ctx Pointer previously returned by @c ZXC_MALLOC / @c ZXC_CALLOC. |
2065 | | */ |
2066 | | void zxc_seekable_attach_owned_ctx(zxc_seekable* s, void* ctx); |
2067 | | |
2068 | | /** @} */ /* end of internal */ |
2069 | | |
2070 | | #ifdef __cplusplus |
2071 | | } |
2072 | | #endif |
2073 | | |
2074 | | #endif // ZXC_INTERNAL_H |