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:
+5
-1118
@@ -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, ¶ms,
|
||||
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(¶ms, 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,
|
||||
¶ms);
|
||||
}
|
||||
|
||||
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(¶ms, 0, sizeof(params));
|
||||
return ZDICT_addEntropyTablesFromBuffer_advanced(dictBuffer, dictContentSize, dictBufferCapacity,
|
||||
samplesBuffer, samplesSizes, nbSamples,
|
||||
params);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
¶ms,
|
||||
&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
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user