feat(dict): port the public zdict builder to Rust

Move the public ZDICT helpers, legacy trainer, dictionary finalization,
entropy-table construction, and supporting dictionary logic into Rust. Keep
the C translation unit as an ABI anchor and register the Rust module only
when compression and dictionary-builder features are enabled.

Test Plan:
- RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= cargo test --manifest-path rust/Cargo.toml dict_builder_zdict
- RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings
- rustfmt +nightly --check --edition 2021 rust/src/dict_builder_zdict.rs rust/src/lib.rs
- git diff --cached --check
This commit is contained in:
2026-07-12 10:38:41 +02:00
parent 63fdbc3fc2
commit 6a9b2d30b5
3 changed files with 1319 additions and 1120 deletions
+5 -1118
View File
@@ -8,1126 +8,13 @@
* You may select, at your option, one of the above-listed licenses.
*/
/*-**************************************
* Tuning parameters
****************************************/
#define MINRATIO 4 /* minimum nb of apparition to be selected in dictionary */
#define ZDICT_MAX_SAMPLES_SIZE (2000U << 20)
#define ZDICT_MIN_SAMPLES_SIZE (ZDICT_CONTENTSIZE_MIN * MINRATIO)
/*-**************************************
* Compiler Options
****************************************/
/* Unix Large Files support (>4GB) */
#define _FILE_OFFSET_BITS 64
#if (defined(__sun__) && (!defined(__LP64__))) /* Sun Solaris 32-bits requires specific definitions */
# ifndef _LARGEFILE_SOURCE
# define _LARGEFILE_SOURCE
# endif
#elif ! defined(__LP64__) /* No point defining Large file for 64 bit */
# ifndef _LARGEFILE64_SOURCE
# define _LARGEFILE64_SOURCE
# endif
#endif
/*-*************************************
* Dependencies
***************************************/
#include <stdlib.h> /* malloc, free */
#include <string.h> /* memset */
#include <stdio.h> /* fprintf, fopen, ftello64 */
#include <time.h> /* clock */
/*
* The public ZDICT implementation lives in rust/src/dict_builder_zdict.rs.
* Keep this translation unit as the C ABI anchor used by the existing build
* descriptions and by users which include the static-only declarations.
*/
#ifndef ZDICT_STATIC_LINKING_ONLY
# define ZDICT_STATIC_LINKING_ONLY
#endif
#include "../common/mem.h" /* read */
#include "../common/fse.h" /* FSE_normalizeCount, FSE_writeNCount */
#include "../common/huf.h" /* HUF_buildCTable, HUF_writeCTable */
#include "../common/zstd_internal.h" /* includes zstd.h */
#include "../common/xxhash.h" /* XXH64 */
#include "../compress/zstd_compress_internal.h" /* ZSTD_loadCEntropy() */
#include "../zdict.h"
#include "divsufsort.h"
#include "../common/bits.h" /* ZSTD_NbCommonBytes */
/*-*************************************
* Constants
***************************************/
#define KB *(1 <<10)
#define MB *(1 <<20)
#define GB *(1U<<30)
#define DICTLISTSIZE_DEFAULT 10000
#define NOISELENGTH 32
static const U32 g_selectivity_default = 9;
/*-*************************************
* Console display
***************************************/
#undef DISPLAY
#define DISPLAY(...) do { fprintf(stderr, __VA_ARGS__); fflush( stderr ); } while (0)
#undef DISPLAYLEVEL
#define DISPLAYLEVEL(l, ...) do { if (notificationLevel>=l) { DISPLAY(__VA_ARGS__); } } while (0) /* 0 : no display; 1: errors; 2: default; 3: details; 4: debug */
static clock_t ZDICT_clockSpan(clock_t nPrevious) { return clock() - nPrevious; }
static void ZDICT_printHex(const void* ptr, size_t length)
{
const BYTE* const b = (const BYTE*)ptr;
size_t u;
for (u=0; u<length; u++) {
BYTE c = b[u];
if (c<32 || c>126) c = '.'; /* non-printable char */
DISPLAY("%c", c);
}
}
/*-********************************************************
* Helper functions
**********************************************************/
unsigned ZDICT_isError(size_t errorCode) { return ERR_isError(errorCode); }
const char* ZDICT_getErrorName(size_t errorCode) { return ERR_getErrorName(errorCode); }
unsigned ZDICT_getDictID(const void* dictBuffer, size_t dictSize)
{
if (dictSize < 8) return 0;
if (MEM_readLE32(dictBuffer) != ZSTD_MAGIC_DICTIONARY) return 0;
return MEM_readLE32((const char*)dictBuffer + 4);
}
size_t ZDICT_getDictHeaderSize(const void* dictBuffer, size_t dictSize)
{
size_t headerSize;
if (dictSize <= 8 || MEM_readLE32(dictBuffer) != ZSTD_MAGIC_DICTIONARY) return ERROR(dictionary_corrupted);
{ ZSTD_compressedBlockState_t* bs = (ZSTD_compressedBlockState_t*)malloc(sizeof(ZSTD_compressedBlockState_t));
U32* wksp = (U32*)malloc(HUF_WORKSPACE_SIZE);
if (!bs || !wksp) {
headerSize = ERROR(memory_allocation);
} else {
ZSTD_reset_compressedBlockState(bs);
headerSize = ZSTD_loadCEntropy(bs, wksp, dictBuffer, dictSize);
}
free(bs);
free(wksp);
}
return headerSize;
}
/*-********************************************************
* Dictionary training functions
**********************************************************/
/*! ZDICT_count() :
Count the nb of common bytes between 2 pointers.
Note : this function presumes end of buffer followed by noisy guard band.
*/
static size_t ZDICT_count(const void* pIn, const void* pMatch)
{
const char* const pStart = (const char*)pIn;
for (;;) {
size_t const diff = MEM_readST(pMatch) ^ MEM_readST(pIn);
if (!diff) {
pIn = (const char*)pIn+sizeof(size_t);
pMatch = (const char*)pMatch+sizeof(size_t);
continue;
}
pIn = (const char*)pIn+ZSTD_NbCommonBytes(diff);
return (size_t)((const char*)pIn - pStart);
}
}
typedef struct {
U32 pos;
U32 length;
U32 savings;
} dictItem;
static void ZDICT_initDictItem(dictItem* d)
{
d->pos = 1;
d->length = 0;
d->savings = (U32)(-1);
}
#define LLIMIT 64 /* heuristic determined experimentally */
#define MINMATCHLENGTH 7 /* heuristic determined experimentally */
static dictItem ZDICT_analyzePos(
BYTE* doneMarks,
const int* suffix, U32 start,
const void* buffer, U32 minRatio, U32 notificationLevel)
{
U32 lengthList[LLIMIT] = {0};
U32 cumulLength[LLIMIT] = {0};
U32 savings[LLIMIT] = {0};
const BYTE* b = (const BYTE*)buffer;
size_t maxLength = LLIMIT;
size_t pos = (size_t)suffix[start];
U32 end = start;
dictItem solution;
/* init */
memset(&solution, 0, sizeof(solution));
doneMarks[pos] = 1;
/* trivial repetition cases */
if ( (MEM_read16(b+pos+0) == MEM_read16(b+pos+2))
||(MEM_read16(b+pos+1) == MEM_read16(b+pos+3))
||(MEM_read16(b+pos+2) == MEM_read16(b+pos+4)) ) {
/* skip and mark segment */
U16 const pattern16 = MEM_read16(b+pos+4);
U32 u, patternEnd = 6;
while (MEM_read16(b+pos+patternEnd) == pattern16) patternEnd+=2 ;
if (b[pos+patternEnd] == b[pos+patternEnd-1]) patternEnd++;
for (u=1; u<patternEnd; u++)
doneMarks[pos+u] = 1;
return solution;
}
/* look forward */
{ size_t length;
do {
end++;
length = ZDICT_count(b + pos, b + suffix[end]);
} while (length >= MINMATCHLENGTH);
}
/* look backward */
{ size_t length;
do {
length = ZDICT_count(b + pos, b + *(suffix+start-1));
if (length >=MINMATCHLENGTH) start--;
} while(length >= MINMATCHLENGTH);
}
/* exit if not found a minimum nb of repetitions */
if (end-start < minRatio) {
U32 idx;
for(idx=start; idx<end; idx++)
doneMarks[suffix[idx]] = 1;
return solution;
}
{ int i;
U32 mml;
U32 refinedStart = start;
U32 refinedEnd = end;
DISPLAYLEVEL(4, "\n");
DISPLAYLEVEL(4, "found %3u matches of length >= %i at pos %7u ", (unsigned)(end-start), MINMATCHLENGTH, (unsigned)pos);
DISPLAYLEVEL(4, "\n");
for (mml = MINMATCHLENGTH ; ; mml++) {
BYTE currentChar = 0;
U32 currentCount = 0;
U32 currentID = refinedStart;
U32 id;
U32 selectedCount = 0;
U32 selectedID = currentID;
for (id =refinedStart; id < refinedEnd; id++) {
if (b[suffix[id] + mml] != currentChar) {
if (currentCount > selectedCount) {
selectedCount = currentCount;
selectedID = currentID;
}
currentID = id;
currentChar = b[ suffix[id] + mml];
currentCount = 0;
}
currentCount ++;
}
if (currentCount > selectedCount) { /* for last */
selectedCount = currentCount;
selectedID = currentID;
}
if (selectedCount < minRatio)
break;
refinedStart = selectedID;
refinedEnd = refinedStart + selectedCount;
}
/* evaluate gain based on new dict */
start = refinedStart;
pos = suffix[refinedStart];
end = start;
memset(lengthList, 0, sizeof(lengthList));
/* look forward */
{ size_t length;
do {
end++;
length = ZDICT_count(b + pos, b + suffix[end]);
if (length >= LLIMIT) length = LLIMIT-1;
lengthList[length]++;
} while (length >=MINMATCHLENGTH);
}
/* look backward */
{ size_t length = MINMATCHLENGTH;
while ((length >= MINMATCHLENGTH) & (start > 0)) {
length = ZDICT_count(b + pos, b + suffix[start - 1]);
if (length >= LLIMIT) length = LLIMIT - 1;
lengthList[length]++;
if (length >= MINMATCHLENGTH) start--;
}
}
/* largest useful length */
memset(cumulLength, 0, sizeof(cumulLength));
cumulLength[maxLength-1] = lengthList[maxLength-1];
for (i=(int)(maxLength-2); i>=0; i--)
cumulLength[i] = cumulLength[i+1] + lengthList[i];
for (i=LLIMIT-1; i>=MINMATCHLENGTH; i--) if (cumulLength[i]>=minRatio) break;
maxLength = i;
/* reduce maxLength in case of final into repetitive data */
{ U32 l = (U32)maxLength;
BYTE const c = b[pos + maxLength-1];
while (b[pos+l-2]==c) l--;
maxLength = l;
}
if (maxLength < MINMATCHLENGTH) return solution; /* skip : no long-enough solution */
/* calculate savings */
savings[5] = 0;
for (i=MINMATCHLENGTH; i<=(int)maxLength; i++)
savings[i] = savings[i-1] + (lengthList[i] * (i-3));
DISPLAYLEVEL(4, "Selected dict at position %u, of length %u : saves %u (ratio: %.2f) \n",
(unsigned)pos, (unsigned)maxLength, (unsigned)savings[maxLength], (double)savings[maxLength] / (double)maxLength);
solution.pos = (U32)pos;
solution.length = (U32)maxLength;
solution.savings = savings[maxLength];
/* mark positions done */
{ U32 id;
for (id=start; id<end; id++) {
U32 p, pEnd, length;
U32 const testedPos = (U32)suffix[id];
if (testedPos == pos)
length = solution.length;
else {
length = (U32)ZDICT_count(b+pos, b+testedPos);
if (length > solution.length) length = solution.length;
}
pEnd = (U32)(testedPos + length);
for (p=testedPos; p<pEnd; p++)
doneMarks[p] = 1;
} } }
return solution;
}
static int isIncluded(const void* in, const void* container, size_t length)
{
const char* const ip = (const char*) in;
const char* const into = (const char*) container;
size_t u;
for (u=0; u<length; u++) { /* works because end of buffer is a noisy guard band */
if (ip[u] != into[u]) break;
}
return u==length;
}
/*! ZDICT_tryMerge() :
check if dictItem can be merged, do it if possible
@return : id of destination elt, 0 if not merged
*/
static U32 ZDICT_tryMerge(dictItem* table, dictItem elt, U32 eltNbToSkip, const void* buffer)
{
const U32 tableSize = table->pos;
const U32 eltEnd = elt.pos + elt.length;
const char* const buf = (const char*) buffer;
/* tail overlap */
U32 u; for (u=1; u<tableSize; u++) {
if (u==eltNbToSkip) continue;
if ((table[u].pos > elt.pos) && (table[u].pos <= eltEnd)) { /* overlap, existing > new */
/* append */
U32 const addedLength = table[u].pos - elt.pos;
table[u].length += addedLength;
table[u].pos = elt.pos;
table[u].savings += elt.savings * addedLength / elt.length; /* rough approx */
table[u].savings += elt.length / 8; /* rough approx bonus */
elt = table[u];
/* sort : improve rank */
while ((u>1) && (table[u-1].savings < elt.savings))
table[u] = table[u-1], u--;
table[u] = elt;
return u;
} }
/* front overlap */
for (u=1; u<tableSize; u++) {
if (u==eltNbToSkip) continue;
if ((table[u].pos + table[u].length >= elt.pos) && (table[u].pos < elt.pos)) { /* overlap, existing < new */
/* append */
int const addedLength = (int)eltEnd - (int)(table[u].pos + table[u].length);
table[u].savings += elt.length / 8; /* rough approx bonus */
if (addedLength > 0) { /* otherwise, elt fully included into existing */
table[u].length += addedLength;
table[u].savings += elt.savings * addedLength / elt.length; /* rough approx */
}
/* sort : improve rank */
elt = table[u];
while ((u>1) && (table[u-1].savings < elt.savings))
table[u] = table[u-1], u--;
table[u] = elt;
return u;
}
if (MEM_read64(buf + table[u].pos) == MEM_read64(buf + elt.pos + 1)) {
if (isIncluded(buf + table[u].pos, buf + elt.pos + 1, table[u].length)) {
size_t const addedLength = MAX( (int)elt.length - (int)table[u].length , 1 );
table[u].pos = elt.pos;
table[u].savings += (U32)(elt.savings * addedLength / elt.length);
table[u].length = MIN(elt.length, table[u].length + 1);
return u;
}
}
}
return 0;
}
static void ZDICT_removeDictItem(dictItem* table, U32 id)
{
/* convention : table[0].pos stores nb of elts */
U32 const max = table[0].pos;
U32 u;
if (!id) return; /* protection, should never happen */
for (u=id; u<max-1; u++)
table[u] = table[u+1];
table->pos--;
}
static void ZDICT_insertDictItem(dictItem* table, U32 maxSize, dictItem elt, const void* buffer)
{
/* merge if possible */
U32 mergeId = ZDICT_tryMerge(table, elt, 0, buffer);
if (mergeId) {
U32 newMerge = 1;
while (newMerge) {
newMerge = ZDICT_tryMerge(table, table[mergeId], mergeId, buffer);
if (newMerge) ZDICT_removeDictItem(table, mergeId);
mergeId = newMerge;
}
return;
}
/* insert */
{ U32 current;
U32 nextElt = table->pos;
if (nextElt >= maxSize) nextElt = maxSize-1;
current = nextElt-1;
while (table[current].savings < elt.savings) {
table[current+1] = table[current];
current--;
}
table[current+1] = elt;
table->pos = nextElt+1;
}
}
static U32 ZDICT_dictSize(const dictItem* dictList)
{
U32 u, dictSize = 0;
for (u=1; u<dictList[0].pos; u++)
dictSize += dictList[u].length;
return dictSize;
}
static size_t ZDICT_trainBuffer_legacy(dictItem* dictList, U32 dictListSize,
const void* const buffer, size_t bufferSize, /* buffer must end with noisy guard band */
const size_t* fileSizes, unsigned nbFiles,
unsigned minRatio, U32 notificationLevel)
{
int* const suffix0 = (int*)malloc((bufferSize+2)*sizeof(*suffix0));
int* const suffix = suffix0+1;
U32* reverseSuffix = (U32*)malloc((bufferSize)*sizeof(*reverseSuffix));
BYTE* doneMarks = (BYTE*)malloc((bufferSize+16)*sizeof(*doneMarks)); /* +16 for overflow security */
U32* filePos = (U32*)malloc(nbFiles * sizeof(*filePos));
size_t result = 0;
clock_t displayClock = 0;
clock_t const refreshRate = CLOCKS_PER_SEC * 3 / 10;
# undef DISPLAYUPDATE
# define DISPLAYUPDATE(l, ...) \
do { \
if (notificationLevel>=l) { \
if (ZDICT_clockSpan(displayClock) > refreshRate) { \
displayClock = clock(); \
DISPLAY(__VA_ARGS__); \
} \
if (notificationLevel>=4) fflush(stderr); \
} \
} while (0)
/* init */
DISPLAYLEVEL(2, "\r%70s\r", ""); /* clean display line */
if (!suffix0 || !reverseSuffix || !doneMarks || !filePos) {
result = ERROR(memory_allocation);
goto _cleanup;
}
if (minRatio < MINRATIO) minRatio = MINRATIO;
memset(doneMarks, 0, bufferSize+16);
/* limit sample set size (divsufsort limitation)*/
if (bufferSize > ZDICT_MAX_SAMPLES_SIZE) DISPLAYLEVEL(3, "sample set too large : reduced to %u MB ...\n", (unsigned)(ZDICT_MAX_SAMPLES_SIZE>>20));
while (bufferSize > ZDICT_MAX_SAMPLES_SIZE) bufferSize -= fileSizes[--nbFiles];
/* sort */
DISPLAYLEVEL(2, "sorting %u files of total size %u MB ...\n", nbFiles, (unsigned)(bufferSize>>20));
{ int const divSuftSortResult = divsufsort((const unsigned char*)buffer, suffix, (int)bufferSize, 0);
if (divSuftSortResult != 0) { result = ERROR(GENERIC); goto _cleanup; }
}
suffix[bufferSize] = (int)bufferSize; /* leads into noise */
suffix0[0] = (int)bufferSize; /* leads into noise */
/* build reverse suffix sort */
{ size_t pos;
for (pos=0; pos < bufferSize; pos++)
reverseSuffix[suffix[pos]] = (U32)pos;
/* note filePos tracks borders between samples.
It's not used at this stage, but planned to become useful in a later update */
filePos[0] = 0;
for (pos=1; pos<nbFiles; pos++)
filePos[pos] = (U32)(filePos[pos-1] + fileSizes[pos-1]);
}
DISPLAYLEVEL(2, "finding patterns ... \n");
DISPLAYLEVEL(3, "minimum ratio : %u \n", minRatio);
{ U32 cursor; for (cursor=0; cursor < bufferSize; ) {
dictItem solution;
if (doneMarks[cursor]) { cursor++; continue; }
solution = ZDICT_analyzePos(doneMarks, suffix, reverseSuffix[cursor], buffer, minRatio, notificationLevel);
if (solution.length==0) { cursor++; continue; }
ZDICT_insertDictItem(dictList, dictListSize, solution, buffer);
cursor += solution.length;
DISPLAYUPDATE(2, "\r%4.2f %% \r", (double)cursor / (double)bufferSize * 100.0);
} }
_cleanup:
free(suffix0);
free(reverseSuffix);
free(doneMarks);
free(filePos);
return result;
}
static void ZDICT_fillNoise(void* buffer, size_t length)
{
unsigned const prime1 = 2654435761U;
unsigned const prime2 = 2246822519U;
unsigned acc = prime1;
size_t p=0;
for (p=0; p<length; p++) {
acc *= prime2;
((unsigned char*)buffer)[p] = (unsigned char)(acc >> 21);
}
}
typedef struct
{
ZSTD_CDict* dict; /* dictionary */
ZSTD_CCtx* zc; /* working context */
void* workPlace; /* must be ZSTD_BLOCKSIZE_MAX allocated */
} EStats_ress_t;
#define MAXREPOFFSET 1024
static void ZDICT_countEStats(EStats_ress_t esr, const ZSTD_parameters* params,
unsigned* countLit, unsigned* offsetcodeCount, unsigned* matchlengthCount, unsigned* litlengthCount, U32* repOffsets,
const void* src, size_t srcSize,
U32 notificationLevel)
{
size_t const blockSizeMax = MIN (ZSTD_BLOCKSIZE_MAX, 1 << params->cParams.windowLog);
size_t cSize;
if (srcSize > blockSizeMax) srcSize = blockSizeMax; /* protection vs large samples */
{ size_t const errorCode = ZSTD_compressBegin_usingCDict_deprecated(esr.zc, esr.dict);
if (ZSTD_isError(errorCode)) { DISPLAYLEVEL(1, "warning : ZSTD_compressBegin_usingCDict failed \n"); return; }
}
cSize = ZSTD_compressBlock_deprecated(esr.zc, esr.workPlace, ZSTD_BLOCKSIZE_MAX, src, srcSize);
if (ZSTD_isError(cSize)) { DISPLAYLEVEL(3, "warning : could not compress sample size %u \n", (unsigned)srcSize); return; }
if (cSize) { /* if == 0; block is not compressible */
const SeqStore_t* const seqStorePtr = ZSTD_getSeqStore(esr.zc);
/* literals stats */
{ const BYTE* bytePtr;
for(bytePtr = seqStorePtr->litStart; bytePtr < seqStorePtr->lit; bytePtr++)
countLit[*bytePtr]++;
}
/* seqStats */
{ U32 const nbSeq = (U32)(seqStorePtr->sequences - seqStorePtr->sequencesStart);
ZSTD_seqToCodes(seqStorePtr);
{ const BYTE* codePtr = seqStorePtr->ofCode;
U32 u;
for (u=0; u<nbSeq; u++) offsetcodeCount[codePtr[u]]++;
}
{ const BYTE* codePtr = seqStorePtr->mlCode;
U32 u;
for (u=0; u<nbSeq; u++) matchlengthCount[codePtr[u]]++;
}
{ const BYTE* codePtr = seqStorePtr->llCode;
U32 u;
for (u=0; u<nbSeq; u++) litlengthCount[codePtr[u]]++;
}
if (nbSeq >= 2) { /* rep offsets */
const SeqDef* const seq = seqStorePtr->sequencesStart;
U32 offset1 = seq[0].offBase - ZSTD_REP_NUM;
U32 offset2 = seq[1].offBase - ZSTD_REP_NUM;
if (offset1 >= MAXREPOFFSET) offset1 = 0;
if (offset2 >= MAXREPOFFSET) offset2 = 0;
repOffsets[offset1] += 3;
repOffsets[offset2] += 1;
} } }
}
static size_t ZDICT_totalSampleSize(const size_t* fileSizes, unsigned nbFiles)
{
size_t total=0;
unsigned u;
for (u=0; u<nbFiles; u++) total += fileSizes[u];
return total;
}
typedef struct { U32 offset; U32 count; } offsetCount_t;
static void ZDICT_insertSortCount(offsetCount_t table[ZSTD_REP_NUM+1], U32 val, U32 count)
{
U32 u;
table[ZSTD_REP_NUM].offset = val;
table[ZSTD_REP_NUM].count = count;
for (u=ZSTD_REP_NUM; u>0; u--) {
offsetCount_t tmp;
if (table[u-1].count >= table[u].count) break;
tmp = table[u-1];
table[u-1] = table[u];
table[u] = tmp;
}
}
/* ZDICT_flatLit() :
* rewrite `countLit` to contain a mostly flat but still compressible distribution of literals.
* necessary to avoid generating a non-compressible distribution that HUF_writeCTable() cannot encode.
*/
static void ZDICT_flatLit(unsigned* countLit)
{
int u;
for (u=1; u<256; u++) countLit[u] = 2;
countLit[0] = 4;
countLit[253] = 1;
countLit[254] = 1;
}
#define OFFCODE_MAX 30 /* only applicable to first block */
static size_t ZDICT_analyzeEntropy(void* dstBuffer, size_t maxDstSize,
int compressionLevel,
const void* srcBuffer, const size_t* fileSizes, unsigned nbFiles,
const void* dictBuffer, size_t dictBufferSize,
unsigned notificationLevel)
{
unsigned countLit[256];
HUF_CREATE_STATIC_CTABLE(hufTable, 255);
unsigned offcodeCount[OFFCODE_MAX+1];
short offcodeNCount[OFFCODE_MAX+1];
U32 offcodeMax = ZSTD_highbit32((U32)(dictBufferSize + 128 KB));
unsigned matchLengthCount[MaxML+1];
short matchLengthNCount[MaxML+1];
unsigned litLengthCount[MaxLL+1];
short litLengthNCount[MaxLL+1];
U32 repOffset[MAXREPOFFSET];
offsetCount_t bestRepOffset[ZSTD_REP_NUM+1];
EStats_ress_t esr = { NULL, NULL, NULL };
ZSTD_parameters params;
U32 u, huffLog = 11, Offlog = OffFSELog, mlLog = MLFSELog, llLog = LLFSELog, total;
size_t pos = 0, errorCode;
size_t eSize = 0;
size_t const totalSrcSize = ZDICT_totalSampleSize(fileSizes, nbFiles);
size_t const averageSampleSize = totalSrcSize / (nbFiles + !nbFiles);
BYTE* dstPtr = (BYTE*)dstBuffer;
U32 wksp[HUF_CTABLE_WORKSPACE_SIZE_U32];
/* init */
DEBUGLOG(4, "ZDICT_analyzeEntropy");
if (offcodeMax>OFFCODE_MAX) { eSize = ERROR(dictionaryCreation_failed); goto _cleanup; } /* too large dictionary */
for (u=0; u<256; u++) countLit[u] = 1; /* any character must be described */
for (u=0; u<=offcodeMax; u++) offcodeCount[u] = 1;
for (u=0; u<=MaxML; u++) matchLengthCount[u] = 1;
for (u=0; u<=MaxLL; u++) litLengthCount[u] = 1;
memset(repOffset, 0, sizeof(repOffset));
repOffset[1] = repOffset[4] = repOffset[8] = 1;
memset(bestRepOffset, 0, sizeof(bestRepOffset));
if (compressionLevel==0) compressionLevel = ZSTD_CLEVEL_DEFAULT;
params = ZSTD_getParams(compressionLevel, averageSampleSize, dictBufferSize);
esr.dict = ZSTD_createCDict_advanced(dictBuffer, dictBufferSize, ZSTD_dlm_byRef, ZSTD_dct_rawContent, params.cParams, ZSTD_defaultCMem);
esr.zc = ZSTD_createCCtx();
esr.workPlace = malloc(ZSTD_BLOCKSIZE_MAX);
if (!esr.dict || !esr.zc || !esr.workPlace) {
eSize = ERROR(memory_allocation);
DISPLAYLEVEL(1, "Not enough memory \n");
goto _cleanup;
}
/* collect stats on all samples */
for (u=0; u<nbFiles; u++) {
ZDICT_countEStats(esr, &params,
countLit, offcodeCount, matchLengthCount, litLengthCount, repOffset,
(const char*)srcBuffer + pos, fileSizes[u],
notificationLevel);
pos += fileSizes[u];
}
if (notificationLevel >= 4) {
/* writeStats */
DISPLAYLEVEL(4, "Offset Code Frequencies : \n");
for (u=0; u<=offcodeMax; u++) {
DISPLAYLEVEL(4, "%2u :%7u \n", u, offcodeCount[u]);
} }
/* analyze, build stats, starting with literals */
{ size_t maxNbBits = HUF_buildCTable_wksp(hufTable, countLit, 255, huffLog, wksp, sizeof(wksp));
if (HUF_isError(maxNbBits)) {
eSize = maxNbBits;
DISPLAYLEVEL(1, " HUF_buildCTable error \n");
goto _cleanup;
}
if (maxNbBits==8) { /* not compressible : will fail on HUF_writeCTable() */
DISPLAYLEVEL(2, "warning : pathological dataset : literals are not compressible : samples are noisy or too regular \n");
ZDICT_flatLit(countLit); /* replace distribution by a fake "mostly flat but still compressible" distribution, that HUF_writeCTable() can encode */
maxNbBits = HUF_buildCTable_wksp(hufTable, countLit, 255, huffLog, wksp, sizeof(wksp));
assert(maxNbBits==9);
}
huffLog = (U32)maxNbBits;
}
/* looking for most common first offsets */
{ U32 offset;
for (offset=1; offset<MAXREPOFFSET; offset++)
ZDICT_insertSortCount(bestRepOffset, offset, repOffset[offset]);
}
/* note : the result of this phase should be used to better appreciate the impact on statistics */
total=0; for (u=0; u<=offcodeMax; u++) total+=offcodeCount[u];
errorCode = FSE_normalizeCount(offcodeNCount, Offlog, offcodeCount, total, offcodeMax, /* useLowProbCount */ 1);
if (FSE_isError(errorCode)) {
eSize = errorCode;
DISPLAYLEVEL(1, "FSE_normalizeCount error with offcodeCount \n");
goto _cleanup;
}
Offlog = (U32)errorCode;
total=0; for (u=0; u<=MaxML; u++) total+=matchLengthCount[u];
errorCode = FSE_normalizeCount(matchLengthNCount, mlLog, matchLengthCount, total, MaxML, /* useLowProbCount */ 1);
if (FSE_isError(errorCode)) {
eSize = errorCode;
DISPLAYLEVEL(1, "FSE_normalizeCount error with matchLengthCount \n");
goto _cleanup;
}
mlLog = (U32)errorCode;
total=0; for (u=0; u<=MaxLL; u++) total+=litLengthCount[u];
errorCode = FSE_normalizeCount(litLengthNCount, llLog, litLengthCount, total, MaxLL, /* useLowProbCount */ 1);
if (FSE_isError(errorCode)) {
eSize = errorCode;
DISPLAYLEVEL(1, "FSE_normalizeCount error with litLengthCount \n");
goto _cleanup;
}
llLog = (U32)errorCode;
/* write result to buffer */
{ size_t const hhSize = HUF_writeCTable_wksp(dstPtr, maxDstSize, hufTable, 255, huffLog, wksp, sizeof(wksp));
if (HUF_isError(hhSize)) {
eSize = hhSize;
DISPLAYLEVEL(1, "HUF_writeCTable error \n");
goto _cleanup;
}
dstPtr += hhSize;
maxDstSize -= hhSize;
eSize += hhSize;
}
{ size_t const ohSize = FSE_writeNCount(dstPtr, maxDstSize, offcodeNCount, OFFCODE_MAX, Offlog);
if (FSE_isError(ohSize)) {
eSize = ohSize;
DISPLAYLEVEL(1, "FSE_writeNCount error with offcodeNCount \n");
goto _cleanup;
}
dstPtr += ohSize;
maxDstSize -= ohSize;
eSize += ohSize;
}
{ size_t const mhSize = FSE_writeNCount(dstPtr, maxDstSize, matchLengthNCount, MaxML, mlLog);
if (FSE_isError(mhSize)) {
eSize = mhSize;
DISPLAYLEVEL(1, "FSE_writeNCount error with matchLengthNCount \n");
goto _cleanup;
}
dstPtr += mhSize;
maxDstSize -= mhSize;
eSize += mhSize;
}
{ size_t const lhSize = FSE_writeNCount(dstPtr, maxDstSize, litLengthNCount, MaxLL, llLog);
if (FSE_isError(lhSize)) {
eSize = lhSize;
DISPLAYLEVEL(1, "FSE_writeNCount error with litlengthNCount \n");
goto _cleanup;
}
dstPtr += lhSize;
maxDstSize -= lhSize;
eSize += lhSize;
}
if (maxDstSize<12) {
eSize = ERROR(dstSize_tooSmall);
DISPLAYLEVEL(1, "not enough space to write RepOffsets \n");
goto _cleanup;
}
# if 0
MEM_writeLE32(dstPtr+0, bestRepOffset[0].offset);
MEM_writeLE32(dstPtr+4, bestRepOffset[1].offset);
MEM_writeLE32(dstPtr+8, bestRepOffset[2].offset);
#else
/* at this stage, we don't use the result of "most common first offset",
* as the impact of statistics is not properly evaluated */
MEM_writeLE32(dstPtr+0, repStartValue[0]);
MEM_writeLE32(dstPtr+4, repStartValue[1]);
MEM_writeLE32(dstPtr+8, repStartValue[2]);
#endif
eSize += 12;
_cleanup:
ZSTD_freeCDict(esr.dict);
ZSTD_freeCCtx(esr.zc);
free(esr.workPlace);
return eSize;
}
/**
* @returns the maximum repcode value
*/
static U32 ZDICT_maxRep(U32 const reps[ZSTD_REP_NUM])
{
U32 maxRep = reps[0];
int r;
for (r = 1; r < ZSTD_REP_NUM; ++r)
maxRep = MAX(maxRep, reps[r]);
return maxRep;
}
size_t ZDICT_finalizeDictionary(void* dictBuffer, size_t dictBufferCapacity,
const void* customDictContent, size_t dictContentSize,
const void* samplesBuffer, const size_t* samplesSizes,
unsigned nbSamples, ZDICT_params_t params)
{
size_t hSize;
#define HBUFFSIZE 256 /* should prove large enough for all entropy headers */
BYTE header[HBUFFSIZE];
int const compressionLevel = (params.compressionLevel == 0) ? ZSTD_CLEVEL_DEFAULT : params.compressionLevel;
U32 const notificationLevel = params.notificationLevel;
/* The final dictionary content must be at least as large as the largest repcode */
size_t const minContentSize = (size_t)ZDICT_maxRep(repStartValue);
size_t paddingSize;
/* check conditions */
DEBUGLOG(4, "ZDICT_finalizeDictionary");
if (dictBufferCapacity < dictContentSize) return ERROR(dstSize_tooSmall);
if (dictBufferCapacity < ZDICT_DICTSIZE_MIN) return ERROR(dstSize_tooSmall);
/* dictionary header */
MEM_writeLE32(header, ZSTD_MAGIC_DICTIONARY);
{ U64 const randomID = XXH64(customDictContent, dictContentSize, 0);
U32 const compliantID = (randomID % ((1U<<31)-32768)) + 32768;
U32 const dictID = params.dictID ? params.dictID : compliantID;
MEM_writeLE32(header+4, dictID);
}
hSize = 8;
/* entropy tables */
DISPLAYLEVEL(2, "\r%70s\r", ""); /* clean display line */
DISPLAYLEVEL(2, "statistics ... \n");
{ size_t const eSize = ZDICT_analyzeEntropy(header+hSize, HBUFFSIZE-hSize,
compressionLevel,
samplesBuffer, samplesSizes, nbSamples,
customDictContent, dictContentSize,
notificationLevel);
if (ZDICT_isError(eSize)) return eSize;
hSize += eSize;
}
/* Shrink the content size if it doesn't fit in the buffer */
if (hSize + dictContentSize > dictBufferCapacity) {
dictContentSize = dictBufferCapacity - hSize;
}
/* Pad the dictionary content with zeros if it is too small */
if (dictContentSize < minContentSize) {
RETURN_ERROR_IF(hSize + minContentSize > dictBufferCapacity, dstSize_tooSmall,
"dictBufferCapacity too small to fit max repcode");
paddingSize = minContentSize - dictContentSize;
} else {
paddingSize = 0;
}
{
size_t const dictSize = hSize + paddingSize + dictContentSize;
/* The dictionary consists of the header, optional padding, and the content.
* The padding comes before the content because the "best" position in the
* dictionary is the last byte.
*/
BYTE* const outDictHeader = (BYTE*)dictBuffer;
BYTE* const outDictPadding = outDictHeader + hSize;
BYTE* const outDictContent = outDictPadding + paddingSize;
assert(dictSize <= dictBufferCapacity);
assert(outDictContent + dictContentSize == (BYTE*)dictBuffer + dictSize);
/* First copy the customDictContent into its final location.
* `customDictContent` and `dictBuffer` may overlap, so we must
* do this before any other writes into the output buffer.
* Then copy the header & padding into the output buffer.
*/
memmove(outDictContent, customDictContent, dictContentSize);
memcpy(outDictHeader, header, hSize);
memset(outDictPadding, 0, paddingSize);
return dictSize;
}
}
static size_t ZDICT_addEntropyTablesFromBuffer_advanced(
void* dictBuffer, size_t dictContentSize, size_t dictBufferCapacity,
const void* samplesBuffer, const size_t* samplesSizes, unsigned nbSamples,
ZDICT_params_t params)
{
int const compressionLevel = (params.compressionLevel == 0) ? ZSTD_CLEVEL_DEFAULT : params.compressionLevel;
U32 const notificationLevel = params.notificationLevel;
size_t hSize = 8;
/* calculate entropy tables */
DISPLAYLEVEL(2, "\r%70s\r", ""); /* clean display line */
DISPLAYLEVEL(2, "statistics ... \n");
{ size_t const eSize = ZDICT_analyzeEntropy((char*)dictBuffer+hSize, dictBufferCapacity-hSize,
compressionLevel,
samplesBuffer, samplesSizes, nbSamples,
(char*)dictBuffer + dictBufferCapacity - dictContentSize, dictContentSize,
notificationLevel);
if (ZDICT_isError(eSize)) return eSize;
hSize += eSize;
}
/* add dictionary header (after entropy tables) */
MEM_writeLE32(dictBuffer, ZSTD_MAGIC_DICTIONARY);
{ U64 const randomID = XXH64((char*)dictBuffer + dictBufferCapacity - dictContentSize, dictContentSize, 0);
U32 const compliantID = (randomID % ((1U<<31)-32768)) + 32768;
U32 const dictID = params.dictID ? params.dictID : compliantID;
MEM_writeLE32((char*)dictBuffer+4, dictID);
}
if (hSize + dictContentSize < dictBufferCapacity)
memmove((char*)dictBuffer + hSize, (char*)dictBuffer + dictBufferCapacity - dictContentSize, dictContentSize);
return MIN(dictBufferCapacity, hSize+dictContentSize);
}
/*! ZDICT_trainFromBuffer_unsafe_legacy() :
* Warning : `samplesBuffer` must be followed by noisy guard band !!!
* @return : size of dictionary, or an error code which can be tested with ZDICT_isError()
*/
static size_t ZDICT_trainFromBuffer_unsafe_legacy(
void* dictBuffer, size_t maxDictSize,
const void* samplesBuffer, const size_t* samplesSizes, unsigned nbSamples,
ZDICT_legacy_params_t params)
{
U32 const dictListSize = MAX(MAX(DICTLISTSIZE_DEFAULT, nbSamples), (U32)(maxDictSize/16));
dictItem* const dictList = (dictItem*)malloc(dictListSize * sizeof(*dictList));
unsigned const selectivity = params.selectivityLevel == 0 ? g_selectivity_default : params.selectivityLevel;
unsigned const minRep = (selectivity > 30) ? MINRATIO : nbSamples >> selectivity;
size_t const targetDictSize = maxDictSize;
size_t const samplesBuffSize = ZDICT_totalSampleSize(samplesSizes, nbSamples);
size_t dictSize = 0;
U32 const notificationLevel = params.zParams.notificationLevel;
/* checks */
if (!dictList) return ERROR(memory_allocation);
if (maxDictSize < ZDICT_DICTSIZE_MIN) { free(dictList); return ERROR(dstSize_tooSmall); } /* requested dictionary size is too small */
if (samplesBuffSize < ZDICT_MIN_SAMPLES_SIZE) { free(dictList); return ERROR(dictionaryCreation_failed); } /* not enough source to create dictionary */
/* init */
ZDICT_initDictItem(dictList);
/* build dictionary */
ZDICT_trainBuffer_legacy(dictList, dictListSize,
samplesBuffer, samplesBuffSize,
samplesSizes, nbSamples,
minRep, notificationLevel);
/* display best matches */
if (params.zParams.notificationLevel>= 3) {
unsigned const nb = MIN(25, dictList[0].pos);
unsigned const dictContentSize = ZDICT_dictSize(dictList);
unsigned u;
DISPLAYLEVEL(3, "\n %u segments found, of total size %u \n", (unsigned)dictList[0].pos-1, dictContentSize);
DISPLAYLEVEL(3, "list %u best segments \n", nb-1);
for (u=1; u<nb; u++) {
unsigned const pos = dictList[u].pos;
unsigned const length = dictList[u].length;
U32 const printedLength = MIN(40, length);
if ((pos > samplesBuffSize) || ((pos + length) > samplesBuffSize)) {
free(dictList);
return ERROR(GENERIC); /* should never happen */
}
DISPLAYLEVEL(3, "%3u:%3u bytes at pos %8u, savings %7u bytes |",
u, length, pos, (unsigned)dictList[u].savings);
ZDICT_printHex((const char*)samplesBuffer+pos, printedLength);
DISPLAYLEVEL(3, "| \n");
} }
/* create dictionary */
{ unsigned dictContentSize = ZDICT_dictSize(dictList);
if (dictContentSize < ZDICT_CONTENTSIZE_MIN) { free(dictList); return ERROR(dictionaryCreation_failed); } /* dictionary content too small */
if (dictContentSize < targetDictSize/4) {
DISPLAYLEVEL(2, "! warning : selected content significantly smaller than requested (%u < %u) \n", dictContentSize, (unsigned)maxDictSize);
if (samplesBuffSize < 10 * targetDictSize)
DISPLAYLEVEL(2, "! consider increasing the number of samples (total size : %u MB)\n", (unsigned)(samplesBuffSize>>20));
if (minRep > MINRATIO) {
DISPLAYLEVEL(2, "! consider increasing selectivity to produce larger dictionary (-s%u) \n", selectivity+1);
DISPLAYLEVEL(2, "! note : larger dictionaries are not necessarily better, test its efficiency on samples \n");
}
}
if ((dictContentSize > targetDictSize*3) && (nbSamples > 2*MINRATIO) && (selectivity>1)) {
unsigned proposedSelectivity = selectivity-1;
while ((nbSamples >> proposedSelectivity) <= MINRATIO) { proposedSelectivity--; }
DISPLAYLEVEL(2, "! note : calculated dictionary significantly larger than requested (%u > %u) \n", dictContentSize, (unsigned)maxDictSize);
DISPLAYLEVEL(2, "! consider increasing dictionary size, or produce denser dictionary (-s%u) \n", proposedSelectivity);
DISPLAYLEVEL(2, "! always test dictionary efficiency on real samples \n");
}
/* limit dictionary size */
{ U32 const max = dictList->pos; /* convention : nb of useful elts within dictList */
U32 currentSize = 0;
U32 n; for (n=1; n<max; n++) {
currentSize += dictList[n].length;
if (currentSize > targetDictSize) { currentSize -= dictList[n].length; break; }
}
dictList->pos = n;
dictContentSize = currentSize;
}
/* build dict content */
{ U32 u;
BYTE* ptr = (BYTE*)dictBuffer + maxDictSize;
for (u=1; u<dictList->pos; u++) {
U32 l = dictList[u].length;
ptr -= l;
if (ptr<(BYTE*)dictBuffer) { free(dictList); return ERROR(GENERIC); } /* should not happen */
memcpy(ptr, (const char*)samplesBuffer+dictList[u].pos, l);
} }
dictSize = ZDICT_addEntropyTablesFromBuffer_advanced(dictBuffer, dictContentSize, maxDictSize,
samplesBuffer, samplesSizes, nbSamples,
params.zParams);
}
/* clean up */
free(dictList);
return dictSize;
}
/* ZDICT_trainFromBuffer_legacy() :
* issue : samplesBuffer need to be followed by a noisy guard band.
* work around : duplicate the buffer, and add the noise */
size_t ZDICT_trainFromBuffer_legacy(void* dictBuffer, size_t dictBufferCapacity,
const void* samplesBuffer, const size_t* samplesSizes, unsigned nbSamples,
ZDICT_legacy_params_t params)
{
size_t result;
void* newBuff;
size_t const sBuffSize = ZDICT_totalSampleSize(samplesSizes, nbSamples);
if (sBuffSize < ZDICT_MIN_SAMPLES_SIZE) return 0; /* not enough content => no dictionary */
newBuff = malloc(sBuffSize + NOISELENGTH);
if (!newBuff) return ERROR(memory_allocation);
memcpy(newBuff, samplesBuffer, sBuffSize);
ZDICT_fillNoise((char*)newBuff + sBuffSize, NOISELENGTH); /* guard band, for end of buffer condition */
result =
ZDICT_trainFromBuffer_unsafe_legacy(dictBuffer, dictBufferCapacity, newBuff,
samplesSizes, nbSamples, params);
free(newBuff);
return result;
}
size_t ZDICT_trainFromBuffer(void* dictBuffer, size_t dictBufferCapacity,
const void* samplesBuffer, const size_t* samplesSizes, unsigned nbSamples)
{
ZDICT_fastCover_params_t params;
DEBUGLOG(3, "ZDICT_trainFromBuffer");
memset(&params, 0, sizeof(params));
params.d = 8;
params.steps = 4;
/* Use default level since no compression level information is available */
params.zParams.compressionLevel = ZSTD_CLEVEL_DEFAULT;
#if defined(DEBUGLEVEL) && (DEBUGLEVEL>=1)
params.zParams.notificationLevel = DEBUGLEVEL;
#endif
return ZDICT_optimizeTrainFromBuffer_fastCover(dictBuffer, dictBufferCapacity,
samplesBuffer, samplesSizes, nbSamples,
&params);
}
size_t ZDICT_addEntropyTablesFromBuffer(void* dictBuffer, size_t dictContentSize, size_t dictBufferCapacity,
const void* samplesBuffer, const size_t* samplesSizes, unsigned nbSamples)
{
ZDICT_params_t params;
memset(&params, 0, sizeof(params));
return ZDICT_addEntropyTablesFromBuffer_advanced(dictBuffer, dictContentSize, dictBufferCapacity,
samplesBuffer, samplesSizes, nbSamples,
params);
}
+1310
View File
@@ -0,0 +1,1310 @@
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(clippy::missing_safety_doc)]
#![allow(clippy::too_many_arguments)]
//! Public dictionary-builder wrappers.
//!
//! This is the Rust translation of `lib/dictBuilder/zdict.c`. The COVER and
//! fastCOVER implementations remain separate translation units, but their
//! finalization calls, the legacy trainer, entropy-header construction, and
//! public helper functions all live here. Compression contexts remain opaque:
//! the statistics pass uses the existing C context API and only projects the
//! stable `SeqStore_t` leaf into Rust.
use crate::bits::ZSTD_highbit32;
use crate::common::{LL_FSE_LOG, MAX_LL, MAX_ML, ML_FSE_LOG, OFF_FSE_LOG, ZSTD_REP_NUM};
use crate::divsufsort::divsufsort;
use crate::errors::{ERR_getErrorName, ERR_isError, ZstdErrorCode, ERROR};
use crate::fse_compress::{FSE_normalizeCount, FSE_writeNCount};
use crate::huf_compress::{HUF_buildCTable_wksp, HUF_writeCTable_wksp};
use crate::mem::{MEM_readLE32, MEM_writeLE32};
use crate::xxhash::XXH64;
use crate::zstd_compress_params::{ZSTD_compressionParameters, ZSTD_parameters};
use crate::zstd_compress_sequences::SeqDef;
use crate::zstd_compress_stats::{SeqStore_t, ZSTD_compressedBlockState_t, ZSTD_seqToCodes};
use std::ffi::{c_char, c_void};
use std::mem::{size_of, MaybeUninit};
use std::os::raw::{c_int, c_uint};
use std::ptr;
const ZSTD_MAGIC_DICTIONARY: u32 = 0xEC30_A437;
const ZSTD_CLEVEL_DEFAULT: c_int = 3;
const ZSTD_BLOCKSIZE_MAX: usize = 1 << 17;
const HUF_WORKSPACE_SIZE: usize = (8 << 10) + 512;
const HUF_CTABLE_WORKSPACE_SIZE_U32: usize = 4 * (255 + 1) + 192;
const ZDICT_DICTSIZE_MIN: usize = 256;
const ZDICT_CONTENTSIZE_MIN: usize = 128;
const ZDICT_MAX_SAMPLES_SIZE: usize = 2000 << 20;
const ZDICT_MIN_SAMPLES_SIZE: usize = ZDICT_CONTENTSIZE_MIN * 4;
const DICTLISTSIZE_DEFAULT: usize = 10_000;
const NOISELENGTH: usize = 32;
const MINRATIO: usize = 4;
const LLIMIT: usize = 64;
const MINMATCHLENGTH: usize = 7;
const MAXREPOFFSET: usize = 1024;
const OFFCODE_MAX: usize = 30;
const ZSTD_DLM_BY_REF: c_int = 1;
const ZSTD_DCT_RAW_CONTENT: c_int = 1;
type ZstdAllocFunction = unsafe extern "C" fn(*mut c_void, usize) -> *mut c_void;
type ZstdFreeFunction = unsafe extern "C" fn(*mut c_void, *mut c_void);
/// ABI-compatible `ZDICT_params_t` from `zdict.h`.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ZDICT_params_t {
pub compressionLevel: c_int,
pub notificationLevel: c_uint,
pub dictID: c_uint,
}
/// ABI-compatible `ZDICT_legacy_params_t` from `zdict.h`.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ZDICT_legacy_params_t {
pub selectivityLevel: c_uint,
pub zParams: ZDICT_params_t,
}
/// ABI-compatible fastCOVER parameters used by `ZDICT_trainFromBuffer()`.
///
/// The type is local to this module because the public fastCOVER declaration
/// is still provided by the C header and implementation.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
struct ZDICT_fastCover_params_t {
k: c_uint,
d: c_uint,
f: c_uint,
steps: c_uint,
nbThreads: c_uint,
splitPoint: f64,
accel: c_uint,
shrinkDict: c_uint,
shrinkDictMaxRegression: c_uint,
zParams: ZDICT_params_t,
}
type ZSTD_CCtx = c_void;
type ZSTD_CDict = c_void;
#[repr(C)]
#[derive(Clone, Copy)]
struct ZSTD_customMem {
customAlloc: Option<ZstdAllocFunction>,
customFree: Option<ZstdFreeFunction>,
opaque: *mut c_void,
}
const ZSTD_DEFAULT_CMEM: ZSTD_customMem = ZSTD_customMem {
customAlloc: None,
customFree: None,
opaque: ptr::null_mut(),
};
unsafe extern "C" {
fn ZSTD_loadCEntropy(
bs: *mut ZSTD_compressedBlockState_t,
workspace: *mut c_void,
dict: *const c_void,
dict_size: usize,
) -> usize;
fn ZSTD_reset_compressedBlockState(bs: *mut ZSTD_compressedBlockState_t);
fn ZSTD_getParams(
compression_level: c_int,
estimated_src_size: u64,
dict_size: usize,
) -> ZSTD_parameters;
fn ZSTD_createCDict_advanced(
dict: *const c_void,
dict_size: usize,
dict_load_method: c_int,
dict_content_type: c_int,
c_params: ZSTD_compressionParameters,
custom_mem: ZSTD_customMem,
) -> *mut ZSTD_CDict;
fn ZSTD_freeCDict(cdict: *mut ZSTD_CDict) -> usize;
fn ZSTD_createCCtx() -> *mut ZSTD_CCtx;
fn ZSTD_freeCCtx(cctx: *mut ZSTD_CCtx) -> usize;
fn ZSTD_compressBegin_usingCDict_deprecated(
cctx: *mut ZSTD_CCtx,
cdict: *const ZSTD_CDict,
) -> usize;
fn ZSTD_compressBlock_deprecated(
cctx: *mut ZSTD_CCtx,
dst: *mut c_void,
dst_capacity: usize,
src: *const c_void,
src_size: usize,
) -> usize;
fn ZSTD_getSeqStore(cctx: *const ZSTD_CCtx) -> *const SeqStore_t;
fn ZDICT_optimizeTrainFromBuffer_fastCover(
dict_buffer: *mut c_void,
dict_buffer_capacity: usize,
samples_buffer: *const c_void,
samples_sizes: *const usize,
nb_samples: c_uint,
parameters: *mut ZDICT_fastCover_params_t,
) -> usize;
}
#[inline]
fn dictionary_error(code: ZstdErrorCode) -> usize {
ERROR(code)
}
#[inline]
fn read_u16(data: &[u8], offset: usize) -> u16 {
u16::from_le_bytes([data[offset], data[offset + 1]])
}
#[inline]
fn count_common(data: &[u8], input: usize, matched: usize) -> usize {
let start = input;
let mut input = input;
let mut matched = matched;
while input < data.len() && matched < data.len() && data[input] == data[matched] {
input += 1;
matched += 1;
}
input - start
}
#[inline]
fn suffix_at(suffix0: &[i32], c_index: isize) -> usize {
suffix0[(c_index + 1) as usize] as usize
}
#[derive(Clone, Copy, Debug, Default)]
struct DictItem {
pos: u32,
length: u32,
savings: u32,
}
#[inline]
fn init_dict_item(item: &mut DictItem) {
item.pos = 1;
item.length = 0;
item.savings = u32::MAX;
}
fn analyze_pos(
done_marks: &mut [u8],
suffix0: &[i32],
start: usize,
data: &[u8],
min_ratio: usize,
_notification_level: c_uint,
) -> DictItem {
let mut length_list = [0u32; LLIMIT];
let mut cumulative_length = [0u32; LLIMIT];
let mut savings = [0u32; LLIMIT];
let mut pos = suffix_at(suffix0, start as isize);
let mut end = start;
let mut solution = DictItem::default();
done_marks[pos] = 1;
if read_u16(data, pos) == read_u16(data, pos + 2)
|| read_u16(data, pos + 1) == read_u16(data, pos + 3)
|| read_u16(data, pos + 2) == read_u16(data, pos + 4)
{
let pattern = read_u16(data, pos + 4);
let mut pattern_end = 6;
while read_u16(data, pos + pattern_end) == pattern {
pattern_end += 2;
}
if data[pos + pattern_end] == data[pos + pattern_end - 1] {
pattern_end += 1;
}
for offset in 1..pattern_end {
done_marks[pos + offset] = 1;
}
return solution;
}
loop {
end += 1;
let length = count_common(data, pos, suffix_at(suffix0, end as isize));
if length < MINMATCHLENGTH {
break;
}
}
let mut start = start;
loop {
let length = count_common(data, pos, suffix_at(suffix0, start as isize - 1));
if length < MINMATCHLENGTH {
break;
}
start -= 1;
}
if end - start < min_ratio {
for index in start..end {
done_marks[suffix_at(suffix0, index as isize)] = 1;
}
return solution;
}
let mut refined_start = start;
let mut refined_end = end;
let mut mml = MINMATCHLENGTH;
loop {
let mut current_char = 0u8;
let mut current_count = 0usize;
let mut current_id = refined_start;
let mut selected_count = 0usize;
let mut selected_id = current_id;
for id in refined_start..refined_end {
let byte = data[suffix_at(suffix0, id as isize) + mml];
if byte != current_char {
if current_count > selected_count {
selected_count = current_count;
selected_id = current_id;
}
current_id = id;
current_char = byte;
current_count = 0;
}
current_count += 1;
}
if current_count > selected_count {
selected_count = current_count;
selected_id = current_id;
}
if selected_count < min_ratio {
break;
}
refined_start = selected_id;
refined_end = refined_start + selected_count;
mml += 1;
}
start = refined_start;
pos = suffix_at(suffix0, refined_start as isize);
end = start;
loop {
end += 1;
let original_length = count_common(data, pos, suffix_at(suffix0, end as isize));
let length = original_length.min(LLIMIT - 1);
length_list[length] += 1;
if original_length < MINMATCHLENGTH {
break;
}
}
let mut length = MINMATCHLENGTH;
while length >= MINMATCHLENGTH && start > 0 {
let original_length = count_common(data, pos, suffix_at(suffix0, start as isize - 1));
length = original_length.min(LLIMIT - 1);
length_list[length] += 1;
if original_length >= MINMATCHLENGTH {
start -= 1;
}
}
cumulative_length[LLIMIT - 1] = length_list[LLIMIT - 1];
for index in (0..LLIMIT - 1).rev() {
cumulative_length[index] = cumulative_length[index + 1] + length_list[index];
}
let mut useful_length = MINMATCHLENGTH - 1;
for index in (MINMATCHLENGTH..LLIMIT).rev() {
if cumulative_length[index] >= min_ratio as u32 {
useful_length = index;
break;
}
}
let mut max_length = useful_length;
let repeated = data[pos + max_length - 1];
let mut reduced = max_length as u32;
while data[pos + reduced as usize - 2] == repeated {
reduced -= 1;
}
max_length = reduced as usize;
if max_length < MINMATCHLENGTH {
return solution;
}
savings[5] = 0;
for index in MINMATCHLENGTH..=max_length {
savings[index] =
savings[index - 1].wrapping_add(length_list[index].wrapping_mul((index - 3) as u32));
}
solution.pos = pos as u32;
solution.length = max_length as u32;
solution.savings = savings[max_length];
for index in start..end {
let tested_pos = suffix_at(suffix0, index as isize);
let length = if tested_pos == pos {
solution.length as usize
} else {
count_common(data, pos, tested_pos).min(solution.length as usize)
};
for mark in done_marks.iter_mut().skip(tested_pos).take(length) {
*mark = 1;
}
}
solution
}
#[inline]
fn is_included(data: &[u8], input: usize, container: usize, length: usize) -> bool {
data[input..input + length] == data[container..container + length]
}
fn resort_item(table: &mut [DictItem], mut index: usize) {
let item = table[index];
while index > 1 && table[index - 1].savings < item.savings {
table[index] = table[index - 1];
index -= 1;
}
table[index] = item;
}
fn try_merge(table: &mut [DictItem], elt: DictItem, skip: usize, data: &[u8]) -> usize {
let table_size = table[0].pos as usize;
let elt_end = elt.pos as usize + elt.length as usize;
for index in 1..table_size {
if index == skip {
continue;
}
let item_pos = table[index].pos as usize;
if item_pos > elt.pos as usize && item_pos <= elt_end {
let added = item_pos - elt.pos as usize;
table[index].length = table[index].length.wrapping_add(added as u32);
table[index].pos = elt.pos;
table[index].savings = table[index]
.savings
.wrapping_add(elt.savings.wrapping_mul(added as u32) / elt.length)
.wrapping_add(elt.length / 8);
resort_item(table, index);
return index;
}
}
for index in 1..table_size {
if index == skip {
continue;
}
let item_pos = table[index].pos as usize;
let item_end = item_pos + table[index].length as usize;
if item_end >= elt.pos as usize && item_pos < elt.pos as usize {
let added = elt_end as isize - item_end as isize;
table[index].savings = table[index].savings.wrapping_add(elt.length / 8);
if added > 0 {
table[index].length = table[index].length.wrapping_add(added as u32);
table[index].savings = table[index]
.savings
.wrapping_add(elt.savings.wrapping_mul(added as u32) / elt.length);
}
resort_item(table, index);
return index;
}
let left = item_pos;
let right = elt.pos as usize + 1;
if left + 8 <= data.len()
&& right + 8 <= data.len()
&& data[left..left + 8] == data[right..right + 8]
&& is_included(data, left, right, table[index].length as usize)
{
let added = ((elt.length as isize - table[index].length as isize).max(1)) as usize;
table[index].pos = elt.pos;
table[index].savings = table[index]
.savings
.wrapping_add(elt.savings.wrapping_mul(added as u32) / elt.length);
table[index].length =
(elt.length as usize).min(table[index].length as usize + 1) as u32;
return index;
}
}
0
}
fn remove_dict_item(table: &mut [DictItem], id: usize) {
if id == 0 {
return;
}
let max = table[0].pos as usize;
for index in id..max - 1 {
table[index] = table[index + 1];
}
table[0].pos -= 1;
}
fn insert_dict_item(table: &mut [DictItem], max_size: usize, elt: DictItem, data: &[u8]) {
let merge_id = try_merge(table, elt, 0, data);
if merge_id != 0 {
let mut merge = merge_id;
while merge != 0 {
let next = try_merge(table, table[merge], merge, data);
if next != 0 {
remove_dict_item(table, merge);
}
merge = next;
}
return;
}
let mut next_elt = table[0].pos as usize;
if next_elt >= max_size {
next_elt = max_size - 1;
}
let mut current = next_elt - 1;
while table[current].savings < elt.savings {
table[current + 1] = table[current];
current -= 1;
}
table[current + 1] = elt;
table[0].pos = (next_elt + 1) as u32;
}
fn dict_size(table: &[DictItem]) -> usize {
(1..table[0].pos as usize)
.map(|index| table[index].length as usize)
.sum()
}
fn fill_noise(buffer: &mut [u8]) {
let prime2 = 2_246_822_519u32;
let mut accumulator = 2_654_435_761u32;
for byte in buffer {
accumulator = accumulator.wrapping_mul(prime2);
*byte = (accumulator >> 21) as u8;
}
}
fn total_sample_size(file_sizes: &[usize]) -> usize {
file_sizes
.iter()
.fold(0usize, |total, size| total.wrapping_add(*size))
}
fn train_buffer_legacy(
dict_list: &mut [DictItem],
buffer: &[u8],
file_sizes: &[usize],
min_ratio: usize,
notification_level: c_uint,
) -> usize {
let buffer_size = buffer.len() - NOISELENGTH;
let mut suffix0 = vec![0i32; buffer_size.saturating_add(2)];
let mut reverse_suffix = vec![0u32; buffer_size];
let mut done_marks = vec![0u8; buffer_size.saturating_add(16)];
let mut file_pos = vec![0u32; file_sizes.len()];
let mut effective_buffer_size = buffer_size;
let mut effective_files = file_sizes.len();
while effective_buffer_size > ZDICT_MAX_SAMPLES_SIZE {
if effective_files == 0 {
break;
}
effective_files -= 1;
effective_buffer_size -= file_sizes[effective_files];
}
if effective_buffer_size > ZDICT_MAX_SAMPLES_SIZE {
eprintln!(
"sample set too large : reduced to {} MB ...",
ZDICT_MAX_SAMPLES_SIZE >> 20
);
}
if effective_files > 0 {
for index in 1..effective_files {
file_pos[index] = file_pos[index - 1].wrapping_add(file_sizes[index - 1] as u32);
}
}
let result = unsafe {
divsufsort(
buffer.as_ptr(),
suffix0.as_mut_ptr().add(1),
effective_buffer_size as c_int,
0,
)
};
if result != 0 {
return dictionary_error(ZstdErrorCode::Generic);
}
suffix0[0] = effective_buffer_size as i32;
suffix0[effective_buffer_size + 1] = effective_buffer_size as i32;
for position in 0..effective_buffer_size {
reverse_suffix[suffix_at(&suffix0, position as isize)] = position as u32;
}
done_marks.fill(0);
let mut cursor = 0usize;
while cursor < effective_buffer_size {
if done_marks[cursor] != 0 {
cursor += 1;
continue;
}
let solution = analyze_pos(
&mut done_marks,
&suffix0,
reverse_suffix[cursor] as usize,
buffer,
min_ratio.max(MINRATIO),
notification_level,
);
if solution.length == 0 {
cursor += 1;
continue;
}
insert_dict_item(dict_list, dict_list.len(), solution, buffer);
cursor += solution.length as usize;
}
0
}
fn count_entropy_stats(
cdict: *mut ZSTD_CDict,
cctx: *mut ZSTD_CCtx,
workplace: &mut [u8],
params: &ZSTD_parameters,
count_lit: &mut [u32; 256],
offset_counts: &mut [u32; OFFCODE_MAX + 1],
match_counts: &mut [u32; MAX_ML + 1],
lit_counts: &mut [u32; MAX_LL + 1],
rep_offsets: &mut [u32; MAXREPOFFSET],
src: *const c_void,
mut src_size: usize,
) {
let block_size_max = ZSTD_BLOCKSIZE_MAX.min(1usize << params.cParams.windowLog);
src_size = src_size.min(block_size_max);
let begin = unsafe { ZSTD_compressBegin_usingCDict_deprecated(cctx, cdict) };
if ERR_isError(begin) {
return;
}
let compressed = unsafe {
ZSTD_compressBlock_deprecated(
cctx,
workplace.as_mut_ptr().cast(),
workplace.len(),
src,
src_size,
)
};
if ERR_isError(compressed) || compressed == 0 {
return;
}
let store = unsafe { &*ZSTD_getSeqStore(cctx) };
let literal_count = unsafe { store.lit.offset_from(store.litStart) as usize };
for byte in unsafe { std::slice::from_raw_parts(store.litStart, literal_count) } {
count_lit[*byte as usize] += 1;
}
let nb_sequences = unsafe { store.sequences.offset_from(store.sequencesStart) as usize };
unsafe { ZSTD_seqToCodes(store) };
for index in 0..nb_sequences {
let of_code = unsafe { *store.ofCode.add(index) as usize };
let ml_code = unsafe { *store.mlCode.add(index) as usize };
let ll_code = unsafe { *store.llCode.add(index) as usize };
if of_code <= OFFCODE_MAX {
offset_counts[of_code] += 1;
}
if ml_code <= MAX_ML {
match_counts[ml_code] += 1;
}
if ll_code <= MAX_LL {
lit_counts[ll_code] += 1;
}
}
if nb_sequences >= 2 {
let first = unsafe { &*store.sequencesStart.cast::<SeqDef>() };
let second = unsafe { &*store.sequencesStart.add(1).cast::<SeqDef>() };
let offset1 = first.offBase.wrapping_sub(ZSTD_REP_NUM as u32);
let offset2 = second.offBase.wrapping_sub(ZSTD_REP_NUM as u32);
rep_offsets[if offset1 < MAXREPOFFSET as u32 {
offset1 as usize
} else {
0
}] += 3;
rep_offsets[if offset2 < MAXREPOFFSET as u32 {
offset2 as usize
} else {
0
}] += 1;
}
}
unsafe fn analyze_entropy(
dst_buffer: *mut u8,
max_dst_size: usize,
compression_level: c_int,
src_buffer: *const u8,
file_sizes: *const usize,
nb_files: c_uint,
dict_buffer: *const u8,
dict_buffer_size: usize,
_notification_level: c_uint,
) -> usize {
let offcode_max = ZSTD_highbit32((dict_buffer_size + (128 << 10)) as u32) as usize;
if offcode_max > OFFCODE_MAX {
return dictionary_error(ZstdErrorCode::DictionaryCreationFailed);
}
let file_sizes = if nb_files == 0 {
&[][..]
} else {
unsafe { std::slice::from_raw_parts(file_sizes, nb_files as usize) }
};
let total_src_size = total_sample_size(file_sizes);
let average_sample_size = total_src_size / (nb_files as usize + usize::from(nb_files == 0));
let mut count_lit = [1u32; 256];
let mut offset_counts = [0u32; OFFCODE_MAX + 1];
offset_counts[..=offcode_max].fill(1);
let mut match_counts = [1u32; MAX_ML + 1];
let mut lit_counts = [1u32; MAX_LL + 1];
let mut rep_offsets = [0u32; MAXREPOFFSET];
rep_offsets[1] = 1;
rep_offsets[4] = 1;
rep_offsets[8] = 1;
let level = if compression_level == 0 {
ZSTD_CLEVEL_DEFAULT
} else {
compression_level
};
let params = unsafe { ZSTD_getParams(level, average_sample_size as u64, dict_buffer_size) };
let cdict = unsafe {
ZSTD_createCDict_advanced(
dict_buffer.cast(),
dict_buffer_size,
ZSTD_DLM_BY_REF,
ZSTD_DCT_RAW_CONTENT,
params.cParams,
ZSTD_DEFAULT_CMEM,
)
};
let cctx = unsafe { ZSTD_createCCtx() };
let mut workplace = vec![0u8; ZSTD_BLOCKSIZE_MAX];
if cdict.is_null() || cctx.is_null() {
unsafe {
ZSTD_freeCDict(cdict);
ZSTD_freeCCtx(cctx);
}
return dictionary_error(ZstdErrorCode::MemoryAllocation);
}
let mut source_offset = 0usize;
for &sample_size in file_sizes {
count_entropy_stats(
cdict,
cctx,
&mut workplace,
&params,
&mut count_lit,
&mut offset_counts,
&mut match_counts,
&mut lit_counts,
&mut rep_offsets,
unsafe { src_buffer.add(source_offset).cast() },
sample_size,
);
source_offset = source_offset.wrapping_add(sample_size);
}
let mut huf_table = [0usize; 257];
let mut huf_workspace = [0u32; HUF_CTABLE_WORKSPACE_SIZE_U32];
let mut huff_log = 11u32;
let mut written = unsafe {
HUF_buildCTable_wksp(
huf_table.as_mut_ptr(),
count_lit.as_ptr(),
255,
huff_log,
huf_workspace.as_mut_ptr().cast(),
size_of_val(&huf_workspace),
)
};
if ERR_isError(written) {
unsafe {
ZSTD_freeCDict(cdict);
ZSTD_freeCCtx(cctx);
}
return written;
}
if written == 8 {
for count in count_lit.iter_mut().skip(1) {
*count = 2;
}
count_lit[0] = 4;
count_lit[253] = 1;
count_lit[254] = 1;
written = unsafe {
HUF_buildCTable_wksp(
huf_table.as_mut_ptr(),
count_lit.as_ptr(),
255,
huff_log,
huf_workspace.as_mut_ptr().cast(),
size_of_val(&huf_workspace),
)
};
if ERR_isError(written) {
unsafe {
ZSTD_freeCDict(cdict);
ZSTD_freeCCtx(cctx);
}
return written;
}
}
huff_log = written as u32;
let mut offcode_ncount = [0i16; OFFCODE_MAX + 1];
let mut match_ncount = [0i16; MAX_ML + 1];
let mut lit_ncount = [0i16; MAX_LL + 1];
let total = offset_counts[..=offcode_max]
.iter()
.fold(0usize, |sum, count| sum + *count as usize);
let mut off_log = OFF_FSE_LOG as u32;
let mut match_log = ML_FSE_LOG as u32;
let mut lit_log = LL_FSE_LOG as u32;
let normalized = unsafe {
FSE_normalizeCount(
offcode_ncount.as_mut_ptr(),
off_log,
offset_counts.as_ptr(),
total,
offcode_max as u32,
1,
)
};
if ERR_isError(normalized) {
unsafe {
ZSTD_freeCDict(cdict);
ZSTD_freeCCtx(cctx);
}
return normalized;
}
off_log = normalized as u32;
let total = match_counts
.iter()
.fold(0usize, |sum, count| sum + *count as usize);
let normalized = unsafe {
FSE_normalizeCount(
match_ncount.as_mut_ptr(),
match_log,
match_counts.as_ptr(),
total,
MAX_ML as u32,
1,
)
};
if ERR_isError(normalized) {
unsafe {
ZSTD_freeCDict(cdict);
ZSTD_freeCCtx(cctx);
}
return normalized;
}
match_log = normalized as u32;
let total = lit_counts
.iter()
.fold(0usize, |sum, count| sum + *count as usize);
let normalized = unsafe {
FSE_normalizeCount(
lit_ncount.as_mut_ptr(),
lit_log,
lit_counts.as_ptr(),
total,
MAX_LL as u32,
1,
)
};
if ERR_isError(normalized) {
unsafe {
ZSTD_freeCDict(cdict);
ZSTD_freeCCtx(cctx);
}
return normalized;
}
lit_log = normalized as u32;
let mut dst = dst_buffer;
let mut remaining = max_dst_size;
let mut entropy_size = unsafe {
HUF_writeCTable_wksp(
dst.cast(),
remaining,
huf_table.as_ptr(),
255,
huff_log,
huf_workspace.as_mut_ptr().cast(),
size_of_val(&huf_workspace),
)
};
if ERR_isError(entropy_size) {
unsafe {
ZSTD_freeCDict(cdict);
ZSTD_freeCCtx(cctx);
}
return entropy_size;
}
dst = unsafe { dst.add(entropy_size) };
remaining -= entropy_size;
let header_size = unsafe {
FSE_writeNCount(
dst.cast(),
remaining,
offcode_ncount.as_ptr(),
OFFCODE_MAX as u32,
off_log,
)
};
if ERR_isError(header_size) {
unsafe {
ZSTD_freeCDict(cdict);
ZSTD_freeCCtx(cctx);
}
return header_size;
}
entropy_size += header_size;
dst = unsafe { dst.add(header_size) };
remaining -= header_size;
let header_size = unsafe {
FSE_writeNCount(
dst.cast(),
remaining,
match_ncount.as_ptr(),
MAX_ML as u32,
match_log,
)
};
if ERR_isError(header_size) {
unsafe {
ZSTD_freeCDict(cdict);
ZSTD_freeCCtx(cctx);
}
return header_size;
}
entropy_size += header_size;
dst = unsafe { dst.add(header_size) };
remaining -= header_size;
let header_size = unsafe {
FSE_writeNCount(
dst.cast(),
remaining,
lit_ncount.as_ptr(),
MAX_LL as u32,
lit_log,
)
};
if ERR_isError(header_size) {
unsafe {
ZSTD_freeCDict(cdict);
ZSTD_freeCCtx(cctx);
}
return header_size;
}
entropy_size += header_size;
dst = unsafe { dst.add(header_size) };
remaining -= header_size;
if remaining < 12 {
unsafe {
ZSTD_freeCDict(cdict);
ZSTD_freeCCtx(cctx);
}
return dictionary_error(ZstdErrorCode::DstSizeTooSmall);
}
unsafe {
MEM_writeLE32(dst.cast(), 1);
MEM_writeLE32(dst.add(4).cast(), 4);
MEM_writeLE32(dst.add(8).cast(), 8);
ZSTD_freeCDict(cdict);
ZSTD_freeCCtx(cctx);
}
entropy_size + 12
}
#[no_mangle]
pub unsafe extern "C" fn ZDICT_isError(error_code: usize) -> c_uint {
c_uint::from(ERR_isError(error_code))
}
#[no_mangle]
pub unsafe extern "C" fn ZDICT_getErrorName(error_code: usize) -> *const c_char {
ERR_getErrorName(error_code)
}
#[no_mangle]
pub unsafe extern "C" fn ZDICT_getDictID(dict_buffer: *const c_void, dict_size: usize) -> c_uint {
if dict_size < 8 {
return 0;
}
if unsafe { MEM_readLE32(dict_buffer) } != ZSTD_MAGIC_DICTIONARY {
return 0;
}
unsafe { MEM_readLE32(dict_buffer.cast::<u8>().add(4).cast()) }
}
#[no_mangle]
pub unsafe extern "C" fn ZDICT_getDictHeaderSize(
dict_buffer: *const c_void,
dict_size: usize,
) -> usize {
if dict_size <= 8 || unsafe { MEM_readLE32(dict_buffer) } != ZSTD_MAGIC_DICTIONARY {
return dictionary_error(ZstdErrorCode::DictionaryCorrupted);
}
let mut state = unsafe { MaybeUninit::<ZSTD_compressedBlockState_t>::zeroed().assume_init() };
let mut workspace = vec![0u32; HUF_WORKSPACE_SIZE / size_of::<u32>()];
unsafe {
ZSTD_reset_compressedBlockState(&mut state);
ZSTD_loadCEntropy(
&mut state,
workspace.as_mut_ptr().cast(),
dict_buffer,
dict_size,
)
}
}
#[no_mangle]
pub unsafe extern "C" fn ZDICT_finalizeDictionary(
dict_buffer: *mut c_void,
dict_buffer_capacity: usize,
custom_dict_content: *const c_void,
mut dict_content_size: usize,
samples_buffer: *const c_void,
samples_sizes: *const usize,
nb_samples: c_uint,
params: ZDICT_params_t,
) -> usize {
if dict_buffer_capacity < dict_content_size || dict_buffer_capacity < ZDICT_DICTSIZE_MIN {
return dictionary_error(ZstdErrorCode::DstSizeTooSmall);
}
let mut header = [0u8; 256];
unsafe {
MEM_writeLE32(header.as_mut_ptr().cast(), ZSTD_MAGIC_DICTIONARY);
let hash = XXH64(custom_dict_content, dict_content_size, 0);
let compliant_id = (hash % ((1u64 << 31) - 32_768)) as u32 + 32_768;
let dict_id = if params.dictID != 0 {
params.dictID
} else {
compliant_id
};
MEM_writeLE32(header.as_mut_ptr().add(4).cast(), dict_id);
}
let entropy_size = analyze_entropy(
header.as_mut_ptr().add(8),
header.len() - 8,
if params.compressionLevel == 0 {
ZSTD_CLEVEL_DEFAULT
} else {
params.compressionLevel
},
samples_buffer.cast(),
samples_sizes,
nb_samples,
custom_dict_content.cast(),
dict_content_size,
params.notificationLevel,
);
if ERR_isError(entropy_size) {
return entropy_size;
}
let header_size = 8 + entropy_size;
if header_size + dict_content_size > dict_buffer_capacity {
dict_content_size = dict_buffer_capacity - header_size;
}
let min_content_size = 8usize;
let padding_size = if dict_content_size < min_content_size {
if header_size + min_content_size > dict_buffer_capacity {
return dictionary_error(ZstdErrorCode::DstSizeTooSmall);
}
min_content_size - dict_content_size
} else {
0
};
let dictionary_size = header_size + padding_size + dict_content_size;
unsafe {
let output = dict_buffer.cast::<u8>();
let content = output.add(header_size + padding_size);
ptr::copy(custom_dict_content.cast::<u8>(), content, dict_content_size);
ptr::copy_nonoverlapping(header.as_ptr(), output, header_size);
ptr::write_bytes(output.add(header_size), 0, padding_size);
}
dictionary_size
}
unsafe fn add_entropy_tables_advanced(
dict_buffer: *mut c_void,
dict_content_size: usize,
dict_buffer_capacity: usize,
samples_buffer: *const c_void,
samples_sizes: *const usize,
nb_samples: c_uint,
params: ZDICT_params_t,
) -> usize {
if dict_buffer_capacity < 8 || dict_content_size > dict_buffer_capacity {
return dictionary_error(ZstdErrorCode::DstSizeTooSmall);
}
let content = dict_buffer
.cast::<u8>()
.add(dict_buffer_capacity - dict_content_size);
let entropy_size = analyze_entropy(
dict_buffer.cast::<u8>().add(8),
dict_buffer_capacity - 8,
if params.compressionLevel == 0 {
ZSTD_CLEVEL_DEFAULT
} else {
params.compressionLevel
},
samples_buffer.cast(),
samples_sizes,
nb_samples,
content,
dict_content_size,
params.notificationLevel,
);
if ERR_isError(entropy_size) {
return entropy_size;
}
let header_size = 8 + entropy_size;
unsafe {
MEM_writeLE32(dict_buffer, ZSTD_MAGIC_DICTIONARY);
let hash = XXH64(content.cast(), dict_content_size, 0);
let compliant_id = (hash % ((1u64 << 31) - 32_768)) as u32 + 32_768;
MEM_writeLE32(
dict_buffer.cast::<u8>().add(4).cast(),
if params.dictID != 0 {
params.dictID
} else {
compliant_id
},
);
if header_size + dict_content_size < dict_buffer_capacity {
ptr::copy(
content,
dict_buffer.cast::<u8>().add(header_size),
dict_content_size,
);
}
}
dict_buffer_capacity.min(header_size + dict_content_size)
}
unsafe fn train_from_buffer_unsafe_legacy(
dict_buffer: *mut c_void,
max_dict_size: usize,
samples_buffer: &[u8],
samples_sizes: &[usize],
params: ZDICT_legacy_params_t,
) -> usize {
let dict_list_size = DICTLISTSIZE_DEFAULT
.max(samples_sizes.len())
.max(max_dict_size / 16);
let mut dict_list = Vec::<DictItem>::new();
if dict_list.try_reserve_exact(dict_list_size).is_err() {
return dictionary_error(ZstdErrorCode::MemoryAllocation);
}
dict_list.resize(dict_list_size, DictItem::default());
init_dict_item(&mut dict_list[0]);
let selectivity = if params.selectivityLevel == 0 {
9usize
} else {
params.selectivityLevel as usize
};
let min_rep = if selectivity > 30 {
MINRATIO
} else {
samples_sizes.len() >> selectivity
};
let sample_size = total_sample_size(samples_sizes);
if max_dict_size < ZDICT_DICTSIZE_MIN {
return dictionary_error(ZstdErrorCode::DstSizeTooSmall);
}
if sample_size < ZDICT_MIN_SAMPLES_SIZE {
return dictionary_error(ZstdErrorCode::DictionaryCreationFailed);
}
let _ = train_buffer_legacy(
&mut dict_list,
samples_buffer,
samples_sizes,
min_rep,
params.zParams.notificationLevel,
);
let mut content_size = dict_size(&dict_list);
if content_size < ZDICT_CONTENTSIZE_MIN {
return dictionary_error(ZstdErrorCode::DictionaryCreationFailed);
}
let output = unsafe { std::slice::from_raw_parts_mut(dict_buffer.cast::<u8>(), max_dict_size) };
let mut write_at = max_dict_size;
for item in dict_list.iter().take(dict_list[0].pos as usize).skip(1) {
let length = item.length as usize;
if length > write_at {
return dictionary_error(ZstdErrorCode::Generic);
}
write_at -= length;
if write_at + length > output.len()
|| item.pos as usize + length > samples_buffer.len().saturating_sub(NOISELENGTH)
{
return dictionary_error(ZstdErrorCode::Generic);
}
output[write_at..write_at + length]
.copy_from_slice(&samples_buffer[item.pos as usize..item.pos as usize + length]);
}
let max = dict_list[0].pos as usize;
let mut current_size = 0usize;
let mut count = 1usize;
while count < max {
current_size += dict_list[count].length as usize;
if current_size > max_dict_size {
current_size -= dict_list[count].length as usize;
break;
}
count += 1;
}
dict_list[0].pos = count as u32;
content_size = current_size;
add_entropy_tables_advanced(
dict_buffer,
content_size,
max_dict_size,
samples_buffer.as_ptr().cast(),
samples_sizes.as_ptr(),
samples_sizes.len() as c_uint,
params.zParams,
)
}
#[no_mangle]
pub unsafe extern "C" fn ZDICT_trainFromBuffer_legacy(
dict_buffer: *mut c_void,
dict_buffer_capacity: usize,
samples_buffer: *const c_void,
samples_sizes: *const usize,
nb_samples: c_uint,
params: ZDICT_legacy_params_t,
) -> usize {
let sizes = if nb_samples == 0 {
&[][..]
} else {
unsafe { std::slice::from_raw_parts(samples_sizes, nb_samples as usize) }
};
let sample_size = total_sample_size(sizes);
if sample_size < ZDICT_MIN_SAMPLES_SIZE {
return 0;
}
let mut guarded = Vec::<u8>::new();
if guarded
.try_reserve_exact(sample_size.saturating_add(NOISELENGTH))
.is_err()
{
return dictionary_error(ZstdErrorCode::MemoryAllocation);
}
guarded.resize(sample_size + NOISELENGTH, 0);
if sample_size != 0 {
unsafe {
ptr::copy_nonoverlapping(
samples_buffer.cast::<u8>(),
guarded.as_mut_ptr(),
sample_size,
);
}
}
fill_noise(&mut guarded[sample_size..]);
unsafe {
train_from_buffer_unsafe_legacy(dict_buffer, dict_buffer_capacity, &guarded, sizes, params)
}
}
#[no_mangle]
pub unsafe extern "C" fn ZDICT_trainFromBuffer(
dict_buffer: *mut c_void,
dict_buffer_capacity: usize,
samples_buffer: *const c_void,
samples_sizes: *const usize,
nb_samples: c_uint,
) -> usize {
let mut params = ZDICT_fastCover_params_t {
d: 8,
steps: 4,
zParams: ZDICT_params_t {
compressionLevel: ZSTD_CLEVEL_DEFAULT,
..ZDICT_params_t::default()
},
..ZDICT_fastCover_params_t::default()
};
unsafe {
ZDICT_optimizeTrainFromBuffer_fastCover(
dict_buffer,
dict_buffer_capacity,
samples_buffer,
samples_sizes,
nb_samples,
&mut params,
)
}
}
#[no_mangle]
pub unsafe extern "C" fn ZDICT_addEntropyTablesFromBuffer(
dict_buffer: *mut c_void,
dict_content_size: usize,
dict_buffer_capacity: usize,
samples_buffer: *const c_void,
samples_sizes: *const usize,
nb_samples: c_uint,
) -> usize {
unsafe {
add_entropy_tables_advanced(
dict_buffer,
dict_content_size,
dict_buffer_capacity,
samples_buffer,
samples_sizes,
nb_samples,
ZDICT_params_t::default(),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::CStr;
#[test]
fn helper_abi_and_error_names_match() {
assert_eq!(unsafe { ZDICT_getDictID(ptr::null(), 0) }, 0);
let error = dictionary_error(ZstdErrorCode::DstSizeTooSmall);
assert_eq!(unsafe { ZDICT_isError(error) }, 1);
let name = unsafe { CStr::from_ptr(ZDICT_getErrorName(error)) };
assert!(name
.to_bytes()
.windows(11)
.any(|part| part == b"Destination"));
}
#[test]
fn dictionary_id_reads_only_valid_headers() {
let mut dict = [0u8; 8];
unsafe {
MEM_writeLE32(dict.as_mut_ptr().cast(), ZSTD_MAGIC_DICTIONARY);
MEM_writeLE32(dict.as_mut_ptr().add(4).cast(), 1234);
}
assert_eq!(
unsafe { ZDICT_getDictID(dict.as_ptr().cast(), dict.len()) },
1234
);
dict[0] ^= 1;
assert_eq!(
unsafe { ZDICT_getDictID(dict.as_ptr().cast(), dict.len()) },
0
);
}
}
+4 -2
View File
@@ -6,6 +6,10 @@ pub mod common;
pub mod cpu;
pub mod debug;
#[cfg(feature = "dict-builder")]
pub mod dict_builder_cover;
#[cfg(all(feature = "compression", feature = "dict-builder"))]
pub mod dict_builder_zdict;
#[cfg(feature = "dict-builder")]
pub mod divsufsort;
pub mod entropy_common;
pub mod errors;
@@ -18,8 +22,6 @@ pub mod hist;
pub mod huf_compress;
#[cfg(feature = "decompression")]
pub mod huf_decompress;
#[cfg(feature = "dict-builder")]
pub mod dict_builder_cover;
pub mod legacy;
pub mod mem;
pub mod pool;