| 1 | |
| 2 | |
| 3 | * Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 4 | * All rights reserved.
|
| 5 | *
|
| 6 | * This source code is licensed under both the BSD-style license (found in the
|
| 7 | * LICENSE file in the root directory of this source tree) and the GPLv2 (found
|
| 8 | * in the COPYING file in the root directory of this source tree).
|
| 9 | * You may select, at your option, one of the above-listed licenses.
|
| 10 |
|
| 11 |
|
| 12 | #include "zstd_ldm.h"
|
| 13 |
|
| 14 | #include "../common/debug.h"
|
| 15 | #include <linux/xxhash.h>
|
| 16 | #include "zstd_fast.h"
|
| 17 | #include "zstd_double_fast.h"
|
| 18 | #include "zstd_ldm_geartab.h"
|
| 19 |
|
| 20 | #define LDM_BUCKET_SIZE_LOG 4
|
| 21 | #define LDM_MIN_MATCH_LENGTH 64
|
| 22 | #define LDM_HASH_RLOG 7
|
| 23 |
|
| 24 | typedef struct {
|
| 25 | U64 rolling;
|
| 26 | U64 stopMask;
|
| 27 | } ldmRollingHashState_t;
|
| 28 |
|
| 29 | |
| 30 | *
|
| 31 | * Initializes the rolling hash state such that it will honor the
|
| 32 | * settings in params.
|
| 33 | static void ZSTD_ldm_gear_init(ldmRollingHashState_t* state, ldmParams_t const* params)
|
| 34 | {
|
| 35 | unsigned maxBitsInMask = MIN(params->minMatchLength, 64);
|
| 36 | unsigned hashRateLog = params->hashRateLog;
|
| 37 |
|
| 38 | state->rolling = ~(U32)0;
|
| 39 |
|
| 40 | |
| 41 | * 1. it has to trigger on average every 2^(hashRateLog) bytes;
|
| 42 | * 2. ideally, it has to depend on a window of minMatchLength bytes.
|
| 43 | *
|
| 44 | * In the gear hash algorithm, bit n depends on the last n bytes;
|
| 45 | * so in order to obtain a good quality splitting criterion it is
|
| 46 | * preferable to use bits with high weight.
|
| 47 | *
|
| 48 | * To match condition 1 we use a mask with hashRateLog bits set
|
| 49 | * and, because of the previous remark, we make sure these bits
|
| 50 | * have the highest possible weight while still respecting
|
| 51 | * condition 2.
|
| 52 |
|
| 53 | if (hashRateLog > 0 && hashRateLog <= maxBitsInMask) {
|
| 54 | state->stopMask = (((U64)1 << hashRateLog) - 1) << (maxBitsInMask - hashRateLog);
|
| 55 | } else {
|
| 56 |
|
| 57 | state->stopMask = ((U64)1 << hashRateLog) - 1;
|
| 58 | }
|
| 59 | }
|
| 60 |
|
| 61 | |
| 62 | * Feeds [data, data + minMatchLength) into the hash without registering any
|
| 63 | * splits. This effectively resets the hash state. This is used when skipping
|
| 64 | * over data, either at the beginning of a block, or skipping sections.
|
| 65 |
|
| 66 | static void ZSTD_ldm_gear_reset(ldmRollingHashState_t* state,
|
| 67 | BYTE const* data, size_t minMatchLength)
|
| 68 | {
|
| 69 | U64 hash = state->rolling;
|
| 70 | size_t n = 0;
|
| 71 |
|
| 72 | #define GEAR_ITER_ONCE() do { \
|
| 73 | hash = (hash << 1) + ZSTD_ldm_gearTab[data[n] & 0xff]; \
|
| 74 | n += 1; \
|
| 75 | } while (0)
|
| 76 | while (n + 3 < minMatchLength) {
|
| 77 | GEAR_ITER_ONCE();
|
| 78 | GEAR_ITER_ONCE();
|
| 79 | GEAR_ITER_ONCE();
|
| 80 | GEAR_ITER_ONCE();
|
| 81 | }
|
| 82 | while (n < minMatchLength) {
|
| 83 | GEAR_ITER_ONCE();
|
| 84 | }
|
| 85 | #undef GEAR_ITER_ONCE
|
| 86 | }
|
| 87 |
|
| 88 | |
| 89 | *
|
| 90 | * Registers in the splits array all the split points found in the first
|
| 91 | * size bytes following the data pointer. This function terminates when
|
| 92 | * either all the data has been processed or LDM_BATCH_SIZE splits are
|
| 93 | * present in the splits array.
|
| 94 | *
|
| 95 | * Precondition: The splits array must not be full.
|
| 96 | * Returns: The number of bytes processed.
|
| 97 | static size_t ZSTD_ldm_gear_feed(ldmRollingHashState_t* state,
|
| 98 | BYTE const* data, size_t size,
|
| 99 | size_t* splits, unsigned* numSplits)
|
| 100 | {
|
| 101 | size_t n;
|
| 102 | U64 hash, mask;
|
| 103 |
|
| 104 | hash = state->rolling;
|
| 105 | mask = state->stopMask;
|
| 106 | n = 0;
|
| 107 |
|
| 108 | #define GEAR_ITER_ONCE() do { \
|
| 109 | hash = (hash << 1) + ZSTD_ldm_gearTab[data[n] & 0xff]; \
|
| 110 | n += 1; \
|
| 111 | if (UNLIKELY((hash & mask) == 0)) { \
|
| 112 | splits[*numSplits] = n; \
|
| 113 | *numSplits += 1; \
|
| 114 | if (*numSplits == LDM_BATCH_SIZE) \
|
| 115 | goto done; \
|
| 116 | } \
|
| 117 | } while (0)
|
| 118 |
|
| 119 | while (n + 3 < size) {
|
| 120 | GEAR_ITER_ONCE();
|
| 121 | GEAR_ITER_ONCE();
|
| 122 | GEAR_ITER_ONCE();
|
| 123 | GEAR_ITER_ONCE();
|
| 124 | }
|
| 125 | while (n < size) {
|
| 126 | GEAR_ITER_ONCE();
|
| 127 | }
|
| 128 |
|
| 129 | #undef GEAR_ITER_ONCE
|
| 130 |
|
| 131 | done:
|
| 132 | state->rolling = hash;
|
| 133 | return n;
|
| 134 | }
|
| 135 |
|
| 136 | void ZSTD_ldm_adjustParameters(ldmParams_t* params,
|
| 137 | const ZSTD_compressionParameters* cParams)
|
| 138 | {
|
| 139 | params->windowLog = cParams->windowLog;
|
| 140 | ZSTD_STATIC_ASSERT(LDM_BUCKET_SIZE_LOG <= ZSTD_LDM_BUCKETSIZELOG_MAX);
|
| 141 | DEBUGLOG(4, "ZSTD_ldm_adjustParameters");
|
| 142 | if (params->hashRateLog == 0) {
|
| 143 | if (params->hashLog > 0) {
|
| 144 |
|
| 145 | assert(params->hashLog <= ZSTD_HASHLOG_MAX);
|
| 146 | if (params->windowLog > params->hashLog) {
|
| 147 | params->hashRateLog = params->windowLog - params->hashLog;
|
| 148 | }
|
| 149 | } else {
|
| 150 | assert(1 <= (int)cParams->strategy && (int)cParams->strategy <= 9);
|
| 151 |
|
| 152 | params->hashRateLog = 7 - (cParams->strategy/3);
|
| 153 | }
|
| 154 | }
|
| 155 | if (params->hashLog == 0) {
|
| 156 | params->hashLog = BOUNDED(ZSTD_HASHLOG_MIN, params->windowLog - params->hashRateLog, ZSTD_HASHLOG_MAX);
|
| 157 | }
|
| 158 | if (params->minMatchLength == 0) {
|
| 159 | params->minMatchLength = LDM_MIN_MATCH_LENGTH;
|
| 160 | if (cParams->strategy >= ZSTD_btultra)
|
| 161 | params->minMatchLength /= 2;
|
| 162 | }
|
| 163 | if (params->bucketSizeLog==0) {
|
| 164 | assert(1 <= (int)cParams->strategy && (int)cParams->strategy <= 9);
|
| 165 | params->bucketSizeLog = BOUNDED(LDM_BUCKET_SIZE_LOG, (U32)cParams->strategy, ZSTD_LDM_BUCKETSIZELOG_MAX);
|
| 166 | }
|
| 167 | params->bucketSizeLog = MIN(params->bucketSizeLog, params->hashLog);
|
| 168 | }
|
| 169 |
|
| 170 | size_t ZSTD_ldm_getTableSize(ldmParams_t params)
|
| 171 | {
|
| 172 | size_t const ldmHSize = ((size_t)1) << params.hashLog;
|
| 173 | size_t const ldmBucketSizeLog = MIN(params.bucketSizeLog, params.hashLog);
|
| 174 | size_t const ldmBucketSize = ((size_t)1) << (params.hashLog - ldmBucketSizeLog);
|
| 175 | size_t const totalSize = ZSTD_cwksp_alloc_size(ldmBucketSize)
|
| 176 | + ZSTD_cwksp_alloc_size(ldmHSize * sizeof(ldmEntry_t));
|
| 177 | return params.enableLdm == ZSTD_ps_enable ? totalSize : 0;
|
| 178 | }
|
| 179 |
|
| 180 | size_t ZSTD_ldm_getMaxNbSeq(ldmParams_t params, size_t maxChunkSize)
|
| 181 | {
|
| 182 | return params.enableLdm == ZSTD_ps_enable ? (maxChunkSize / params.minMatchLength) : 0;
|
| 183 | }
|
| 184 |
|
| 185 | |
| 186 | * Returns a pointer to the start of the bucket associated with hash.
|
| 187 | static ldmEntry_t* ZSTD_ldm_getBucket(
|
| 188 | const ldmState_t* ldmState, size_t hash, U32 const bucketSizeLog)
|
| 189 | {
|
| 190 | return ldmState->hashTable + (hash << bucketSizeLog);
|
| 191 | }
|
| 192 |
|
| 193 | |
| 194 | * Insert the entry with corresponding hash into the hash table
|
| 195 | static void ZSTD_ldm_insertEntry(ldmState_t* ldmState,
|
| 196 | size_t const hash, const ldmEntry_t entry,
|
| 197 | U32 const bucketSizeLog)
|
| 198 | {
|
| 199 | BYTE* const pOffset = ldmState->bucketOffsets + hash;
|
| 200 | unsigned const offset = *pOffset;
|
| 201 |
|
| 202 | *(ZSTD_ldm_getBucket(ldmState, hash, bucketSizeLog) + offset) = entry;
|
| 203 | *pOffset = (BYTE)((offset + 1) & ((1u << bucketSizeLog) - 1));
|
| 204 |
|
| 205 | }
|
| 206 |
|
| 207 | |
| 208 | * Returns the number of bytes that match backwards before pIn and pMatch.
|
| 209 | *
|
| 210 | * We count only bytes where pMatch >= pBase and pIn >= pAnchor.
|
| 211 | static size_t ZSTD_ldm_countBackwardsMatch(
|
| 212 | const BYTE* pIn, const BYTE* pAnchor,
|
| 213 | const BYTE* pMatch, const BYTE* pMatchBase)
|
| 214 | {
|
| 215 | size_t matchLength = 0;
|
| 216 | while (pIn > pAnchor && pMatch > pMatchBase && pIn[-1] == pMatch[-1]) {
|
| 217 | pIn--;
|
| 218 | pMatch--;
|
| 219 | matchLength++;
|
| 220 | }
|
| 221 | return matchLength;
|
| 222 | }
|
| 223 |
|
| 224 | |
| 225 | * Returns the number of bytes that match backwards from pMatch,
|
| 226 | * even with the backwards match spanning 2 different segments.
|
| 227 | *
|
| 228 | * On reaching `pMatchBase`, start counting from mEnd
|
| 229 | static size_t ZSTD_ldm_countBackwardsMatch_2segments(
|
| 230 | const BYTE* pIn, const BYTE* pAnchor,
|
| 231 | const BYTE* pMatch, const BYTE* pMatchBase,
|
| 232 | const BYTE* pExtDictStart, const BYTE* pExtDictEnd)
|
| 233 | {
|
| 234 | size_t matchLength = ZSTD_ldm_countBackwardsMatch(pIn, pAnchor, pMatch, pMatchBase);
|
| 235 | if (pMatch - matchLength != pMatchBase || pMatchBase == pExtDictStart) {
|
| 236 |
|
| 237 | return matchLength;
|
| 238 | }
|
| 239 | DEBUGLOG(7, "ZSTD_ldm_countBackwardsMatch_2segments: found 2-parts backwards match (length in prefix==%zu)", matchLength);
|
| 240 | matchLength += ZSTD_ldm_countBackwardsMatch(pIn - matchLength, pAnchor, pExtDictEnd, pExtDictStart);
|
| 241 | DEBUGLOG(7, "final backwards match length = %zu", matchLength);
|
| 242 | return matchLength;
|
| 243 | }
|
| 244 |
|
| 245 | |
| 246 | *
|
| 247 | * Fills the relevant tables for the ZSTD_fast and ZSTD_dfast strategies.
|
| 248 | * This is similar to ZSTD_loadDictionaryContent.
|
| 249 | *
|
| 250 | * The tables for the other strategies are filled within their
|
| 251 | * block compressors.
|
| 252 | static size_t ZSTD_ldm_fillFastTables(ZSTD_MatchState_t* ms,
|
| 253 | void const* end)
|
| 254 | {
|
| 255 | const BYTE* const iend = (const BYTE*)end;
|
| 256 |
|
| 257 | switch(ms->cParams.strategy)
|
| 258 | {
|
| 259 | case ZSTD_fast:
|
| 260 | ZSTD_fillHashTable(ms, iend, ZSTD_dtlm_fast, ZSTD_tfp_forCCtx);
|
| 261 | break;
|
| 262 |
|
| 263 | case ZSTD_dfast:
|
| 264 | #ifndef ZSTD_EXCLUDE_DFAST_BLOCK_COMPRESSOR
|
| 265 | ZSTD_fillDoubleHashTable(ms, iend, ZSTD_dtlm_fast, ZSTD_tfp_forCCtx);
|
| 266 | #else
|
| 267 | assert(0);
|
| 268 | #endif
|
| 269 | break;
|
| 270 |
|
| 271 | case ZSTD_greedy:
|
| 272 | case ZSTD_lazy:
|
| 273 | case ZSTD_lazy2:
|
| 274 | case ZSTD_btlazy2:
|
| 275 | case ZSTD_btopt:
|
| 276 | case ZSTD_btultra:
|
| 277 | case ZSTD_btultra2:
|
| 278 | break;
|
| 279 | default:
|
| 280 | assert(0);
|
| 281 | }
|
| 282 |
|
| 283 | return 0;
|
| 284 | }
|
| 285 |
|
| 286 | void ZSTD_ldm_fillHashTable(
|
| 287 | ldmState_t* ldmState, const BYTE* ip,
|
| 288 | const BYTE* iend, ldmParams_t const* params)
|
| 289 | {
|
| 290 | U32 const minMatchLength = params->minMatchLength;
|
| 291 | U32 const bucketSizeLog = params->bucketSizeLog;
|
| 292 | U32 const hBits = params->hashLog - bucketSizeLog;
|
| 293 | BYTE const* const base = ldmState->window.base;
|
| 294 | BYTE const* const istart = ip;
|
| 295 | ldmRollingHashState_t hashState;
|
| 296 | size_t* const splits = ldmState->splitIndices;
|
| 297 | unsigned numSplits;
|
| 298 |
|
| 299 | DEBUGLOG(5, "ZSTD_ldm_fillHashTable");
|
| 300 |
|
| 301 | ZSTD_ldm_gear_init(&hashState, params);
|
| 302 | while (ip < iend) {
|
| 303 | size_t hashed;
|
| 304 | unsigned n;
|
| 305 |
|
| 306 | numSplits = 0;
|
| 307 | hashed = ZSTD_ldm_gear_feed(&hashState, ip, (size_t)(iend - ip), splits, &numSplits);
|
| 308 |
|
| 309 | for (n = 0; n < numSplits; n++) {
|
| 310 | if (ip + splits[n] >= istart + minMatchLength) {
|
| 311 | BYTE const* const split = ip + splits[n] - minMatchLength;
|
| 312 | U64 const xxhash = xxh64(split, minMatchLength, 0);
|
| 313 | U32 const hash = (U32)(xxhash & (((U32)1 << hBits) - 1));
|
| 314 | ldmEntry_t entry;
|
| 315 |
|
| 316 | entry.offset = (U32)(split - base);
|
| 317 | entry.checksum = (U32)(xxhash >> 32);
|
| 318 | ZSTD_ldm_insertEntry(ldmState, hash, entry, params->bucketSizeLog);
|
| 319 | }
|
| 320 | }
|
| 321 |
|
| 322 | ip += hashed;
|
| 323 | }
|
| 324 | }
|
| 325 |
|
| 326 |
|
| 327 | |
| 328 | *
|
| 329 | * Sets cctx->nextToUpdate to a position corresponding closer to anchor
|
| 330 | * if it is far way
|
| 331 | * (after a long match, only update tables a limited amount).
|
| 332 | static void ZSTD_ldm_limitTableUpdate(ZSTD_MatchState_t* ms, const BYTE* anchor)
|
| 333 | {
|
| 334 | U32 const curr = (U32)(anchor - ms->window.base);
|
| 335 | if (curr > ms->nextToUpdate + 1024) {
|
| 336 | ms->nextToUpdate =
|
| 337 | curr - MIN(512, curr - ms->nextToUpdate - 1024);
|
| 338 | }
|
| 339 | }
|
| 340 |
|
| 341 | static
|
| 342 | ZSTD_ALLOW_POINTER_OVERFLOW_ATTR
|
| 343 | size_t ZSTD_ldm_generateSequences_internal(
|
| 344 | ldmState_t* ldmState, RawSeqStore_t* rawSeqStore,
|
| 345 | ldmParams_t const* params, void const* src, size_t srcSize)
|
| 346 | {
|
| 347 |
|
| 348 | int const extDict = ZSTD_window_hasExtDict(ldmState->window);
|
| 349 | U32 const minMatchLength = params->minMatchLength;
|
| 350 | U32 const entsPerBucket = 1U << params->bucketSizeLog;
|
| 351 | U32 const hBits = params->hashLog - params->bucketSizeLog;
|
| 352 |
|
| 353 | U32 const dictLimit = ldmState->window.dictLimit;
|
| 354 | U32 const lowestIndex = extDict ? ldmState->window.lowLimit : dictLimit;
|
| 355 | BYTE const* const base = ldmState->window.base;
|
| 356 | BYTE const* const dictBase = extDict ? ldmState->window.dictBase : NULL;
|
| 357 | BYTE const* const dictStart = extDict ? dictBase + lowestIndex : NULL;
|
| 358 | BYTE const* const dictEnd = extDict ? dictBase + dictLimit : NULL;
|
| 359 | BYTE const* const lowPrefixPtr = base + dictLimit;
|
| 360 |
|
| 361 | BYTE const* const istart = (BYTE const*)src;
|
| 362 | BYTE const* const iend = istart + srcSize;
|
| 363 | BYTE const* const ilimit = iend - HASH_READ_SIZE;
|
| 364 |
|
| 365 | BYTE const* anchor = istart;
|
| 366 | BYTE const* ip = istart;
|
| 367 |
|
| 368 | ldmRollingHashState_t hashState;
|
| 369 |
|
| 370 | size_t* const splits = ldmState->splitIndices;
|
| 371 | ldmMatchCandidate_t* const candidates = ldmState->matchCandidates;
|
| 372 | unsigned numSplits;
|
| 373 |
|
| 374 | if (srcSize < minMatchLength)
|
| 375 | return iend - anchor;
|
| 376 |
|
| 377 |
|
| 378 | ZSTD_ldm_gear_init(&hashState, params);
|
| 379 | ZSTD_ldm_gear_reset(&hashState, ip, minMatchLength);
|
| 380 | ip += minMatchLength;
|
| 381 |
|
| 382 | while (ip < ilimit) {
|
| 383 | size_t hashed;
|
| 384 | unsigned n;
|
| 385 |
|
| 386 | numSplits = 0;
|
| 387 | hashed = ZSTD_ldm_gear_feed(&hashState, ip, ilimit - ip,
|
| 388 | splits, &numSplits);
|
| 389 |
|
| 390 | for (n = 0; n < numSplits; n++) {
|
| 391 | BYTE const* const split = ip + splits[n] - minMatchLength;
|
| 392 | U64 const xxhash = xxh64(split, minMatchLength, 0);
|
| 393 | U32 const hash = (U32)(xxhash & (((U32)1 << hBits) - 1));
|
| 394 |
|
| 395 | candidates[n].split = split;
|
| 396 | candidates[n].hash = hash;
|
| 397 | candidates[n].checksum = (U32)(xxhash >> 32);
|
| 398 | candidates[n].bucket = ZSTD_ldm_getBucket(ldmState, hash, params->bucketSizeLog);
|
| 399 | PREFETCH_L1(candidates[n].bucket);
|
| 400 | }
|
| 401 |
|
| 402 | for (n = 0; n < numSplits; n++) {
|
| 403 | size_t forwardMatchLength = 0, backwardMatchLength = 0,
|
| 404 | bestMatchLength = 0, mLength;
|
| 405 | U32 offset;
|
| 406 | BYTE const* const split = candidates[n].split;
|
| 407 | U32 const checksum = candidates[n].checksum;
|
| 408 | U32 const hash = candidates[n].hash;
|
| 409 | ldmEntry_t* const bucket = candidates[n].bucket;
|
| 410 | ldmEntry_t const* cur;
|
| 411 | ldmEntry_t const* bestEntry = NULL;
|
| 412 | ldmEntry_t newEntry;
|
| 413 |
|
| 414 | newEntry.offset = (U32)(split - base);
|
| 415 | newEntry.checksum = checksum;
|
| 416 |
|
| 417 | |
| 418 | * the previous one, we merely register it in the hash table and
|
| 419 | * move on
|
| 420 | if (split < anchor) {
|
| 421 | ZSTD_ldm_insertEntry(ldmState, hash, newEntry, params->bucketSizeLog);
|
| 422 | continue;
|
| 423 | }
|
| 424 |
|
| 425 | for (cur = bucket; cur < bucket + entsPerBucket; cur++) {
|
| 426 | size_t curForwardMatchLength, curBackwardMatchLength,
|
| 427 | curTotalMatchLength;
|
| 428 | if (cur->checksum != checksum || cur->offset <= lowestIndex) {
|
| 429 | continue;
|
| 430 | }
|
| 431 | if (extDict) {
|
| 432 | BYTE const* const curMatchBase =
|
| 433 | cur->offset < dictLimit ? dictBase : base;
|
| 434 | BYTE const* const pMatch = curMatchBase + cur->offset;
|
| 435 | BYTE const* const matchEnd =
|
| 436 | cur->offset < dictLimit ? dictEnd : iend;
|
| 437 | BYTE const* const lowMatchPtr =
|
| 438 | cur->offset < dictLimit ? dictStart : lowPrefixPtr;
|
| 439 | curForwardMatchLength =
|
| 440 | ZSTD_count_2segments(split, pMatch, iend, matchEnd, lowPrefixPtr);
|
| 441 | if (curForwardMatchLength < minMatchLength) {
|
| 442 | continue;
|
| 443 | }
|
| 444 | curBackwardMatchLength = ZSTD_ldm_countBackwardsMatch_2segments(
|
| 445 | split, anchor, pMatch, lowMatchPtr, dictStart, dictEnd);
|
| 446 | } else {
|
| 447 | BYTE const* const pMatch = base + cur->offset;
|
| 448 | curForwardMatchLength = ZSTD_count(split, pMatch, iend);
|
| 449 | if (curForwardMatchLength < minMatchLength) {
|
| 450 | continue;
|
| 451 | }
|
| 452 | curBackwardMatchLength =
|
| 453 | ZSTD_ldm_countBackwardsMatch(split, anchor, pMatch, lowPrefixPtr);
|
| 454 | }
|
| 455 | curTotalMatchLength = curForwardMatchLength + curBackwardMatchLength;
|
| 456 |
|
| 457 | if (curTotalMatchLength > bestMatchLength) {
|
| 458 | bestMatchLength = curTotalMatchLength;
|
| 459 | forwardMatchLength = curForwardMatchLength;
|
| 460 | backwardMatchLength = curBackwardMatchLength;
|
| 461 | bestEntry = cur;
|
| 462 | }
|
| 463 | }
|
| 464 |
|
| 465 | |
| 466 | * and process the next candidate match
|
| 467 | if (bestEntry == NULL) {
|
| 468 | ZSTD_ldm_insertEntry(ldmState, hash, newEntry, params->bucketSizeLog);
|
| 469 | continue;
|
| 470 | }
|
| 471 |
|
| 472 |
|
| 473 | offset = (U32)(split - base) - bestEntry->offset;
|
| 474 | mLength = forwardMatchLength + backwardMatchLength;
|
| 475 | {
|
| 476 | rawSeq* const seq = rawSeqStore->seq + rawSeqStore->size;
|
| 477 |
|
| 478 |
|
| 479 | if (rawSeqStore->size == rawSeqStore->capacity)
|
| 480 | return ERROR(dstSize_tooSmall);
|
| 481 | seq->litLength = (U32)(split - backwardMatchLength - anchor);
|
| 482 | seq->matchLength = (U32)mLength;
|
| 483 | seq->offset = offset;
|
| 484 | rawSeqStore->size++;
|
| 485 | }
|
| 486 |
|
| 487 | |
| 488 | * done after the previous block to avoid clobbering bestEntry
|
| 489 | ZSTD_ldm_insertEntry(ldmState, hash, newEntry, params->bucketSizeLog);
|
| 490 |
|
| 491 | anchor = split + forwardMatchLength;
|
| 492 |
|
| 493 | |
| 494 | * then we have a repeating, overlapping, pattern. E.g. all zeros.
|
| 495 | * If one repetition of the pattern matches our `stopMask` then all
|
| 496 | * repetitions will. We don't need to insert them all into out table,
|
| 497 | * only the first one. So skip over overlapping matches.
|
| 498 | * This is a major speed boost (20x) for compressing a single byte
|
| 499 | * repeated, when that byte ends up in the table.
|
| 500 |
|
| 501 | if (anchor > ip + hashed) {
|
| 502 | ZSTD_ldm_gear_reset(&hashState, anchor - minMatchLength, minMatchLength);
|
| 503 |
|
| 504 | ip = anchor - hashed;
|
| 505 | break;
|
| 506 | }
|
| 507 | }
|
| 508 |
|
| 509 | ip += hashed;
|
| 510 | }
|
| 511 |
|
| 512 | return iend - anchor;
|
| 513 | }
|
| 514 |
|
| 515 | |
| 516 | * reduce table indexes by `reducerValue`
|
| 517 | static void ZSTD_ldm_reduceTable(ldmEntry_t* const table, U32 const size,
|
| 518 | U32 const reducerValue)
|
| 519 | {
|
| 520 | U32 u;
|
| 521 | for (u = 0; u < size; u++) {
|
| 522 | if (table[u].offset < reducerValue) table[u].offset = 0;
|
| 523 | else table[u].offset -= reducerValue;
|
| 524 | }
|
| 525 | }
|
| 526 |
|
| 527 | size_t ZSTD_ldm_generateSequences(
|
| 528 | ldmState_t* ldmState, RawSeqStore_t* sequences,
|
| 529 | ldmParams_t const* params, void const* src, size_t srcSize)
|
| 530 | {
|
| 531 | U32 const maxDist = 1U << params->windowLog;
|
| 532 | BYTE const* const istart = (BYTE const*)src;
|
| 533 | BYTE const* const iend = istart + srcSize;
|
| 534 | size_t const kMaxChunkSize = 1 << 20;
|
| 535 | size_t const nbChunks = (srcSize / kMaxChunkSize) + ((srcSize % kMaxChunkSize) != 0);
|
| 536 | size_t chunk;
|
| 537 | size_t leftoverSize = 0;
|
| 538 |
|
| 539 | assert(ZSTD_CHUNKSIZE_MAX >= kMaxChunkSize);
|
| 540 | |
| 541 | * to passing it to this function.
|
| 542 |
|
| 543 | assert(ldmState->window.nextSrc >= (BYTE const*)src + srcSize);
|
| 544 | |
| 545 | * chunks to enforce the maximum distance and handle overflow correction.
|
| 546 |
|
| 547 | assert(sequences->pos <= sequences->size);
|
| 548 | assert(sequences->size <= sequences->capacity);
|
| 549 | for (chunk = 0; chunk < nbChunks && sequences->size < sequences->capacity; ++chunk) {
|
| 550 | BYTE const* const chunkStart = istart + chunk * kMaxChunkSize;
|
| 551 | size_t const remaining = (size_t)(iend - chunkStart);
|
| 552 | BYTE const *const chunkEnd =
|
| 553 | (remaining < kMaxChunkSize) ? iend : chunkStart + kMaxChunkSize;
|
| 554 | size_t const chunkSize = chunkEnd - chunkStart;
|
| 555 | size_t newLeftoverSize;
|
| 556 | size_t const prevSize = sequences->size;
|
| 557 |
|
| 558 | assert(chunkStart < iend);
|
| 559 |
|
| 560 | if (ZSTD_window_needOverflowCorrection(ldmState->window, 0, maxDist, ldmState->loadedDictEnd, chunkStart, chunkEnd)) {
|
| 561 | U32 const ldmHSize = 1U << params->hashLog;
|
| 562 | U32 const correction = ZSTD_window_correctOverflow(
|
| 563 | &ldmState->window, 0, maxDist, chunkStart);
|
| 564 | ZSTD_ldm_reduceTable(ldmState->hashTable, ldmHSize, correction);
|
| 565 |
|
| 566 | ldmState->loadedDictEnd = 0;
|
| 567 | }
|
| 568 | |
| 569 | *
|
| 570 | * kMaxChunkSize should be small enough that we don't lose too much of
|
| 571 | * the window through early invalidation.
|
| 572 | * TODO: * Test the chunk size.
|
| 573 | * * Try invalidation after the sequence generation and test the
|
| 574 | * offset against maxDist directly.
|
| 575 | *
|
| 576 | * NOTE: Because of dictionaries + sequence splitting we MUST make sure
|
| 577 | * that any offset used is valid at the END of the sequence, since it may
|
| 578 | * be split into two sequences. This condition holds when using
|
| 579 | * ZSTD_window_enforceMaxDist(), but if we move to checking offsets
|
| 580 | * against maxDist directly, we'll have to carefully handle that case.
|
| 581 |
|
| 582 | ZSTD_window_enforceMaxDist(&ldmState->window, chunkEnd, maxDist, &ldmState->loadedDictEnd, NULL);
|
| 583 |
|
| 584 | newLeftoverSize = ZSTD_ldm_generateSequences_internal(
|
| 585 | ldmState, sequences, params, chunkStart, chunkSize);
|
| 586 | if (ZSTD_isError(newLeftoverSize))
|
| 587 | return newLeftoverSize;
|
| 588 | |
| 589 | * newly generated sequence, or add the `newLeftoverSize` if none are
|
| 590 | * generated.
|
| 591 |
|
| 592 |
|
| 593 | if (prevSize < sequences->size) {
|
| 594 | sequences->seq[prevSize].litLength += (U32)leftoverSize;
|
| 595 | leftoverSize = newLeftoverSize;
|
| 596 | } else {
|
| 597 | assert(newLeftoverSize == chunkSize);
|
| 598 | leftoverSize += chunkSize;
|
| 599 | }
|
| 600 | }
|
| 601 | return 0;
|
| 602 | }
|
| 603 |
|
| 604 | void
|
| 605 | ZSTD_ldm_skipSequences(RawSeqStore_t* rawSeqStore, size_t srcSize, U32 const minMatch)
|
| 606 | {
|
| 607 | while (srcSize > 0 && rawSeqStore->pos < rawSeqStore->size) {
|
| 608 | rawSeq* seq = rawSeqStore->seq + rawSeqStore->pos;
|
| 609 | if (srcSize <= seq->litLength) {
|
| 610 |
|
| 611 | seq->litLength -= (U32)srcSize;
|
| 612 | return;
|
| 613 | }
|
| 614 | srcSize -= seq->litLength;
|
| 615 | seq->litLength = 0;
|
| 616 | if (srcSize < seq->matchLength) {
|
| 617 |
|
| 618 | seq->matchLength -= (U32)srcSize;
|
| 619 | if (seq->matchLength < minMatch) {
|
| 620 |
|
| 621 | if (rawSeqStore->pos + 1 < rawSeqStore->size) {
|
| 622 | seq[1].litLength += seq[0].matchLength;
|
| 623 | }
|
| 624 | rawSeqStore->pos++;
|
| 625 | }
|
| 626 | return;
|
| 627 | }
|
| 628 | srcSize -= seq->matchLength;
|
| 629 | seq->matchLength = 0;
|
| 630 | rawSeqStore->pos++;
|
| 631 | }
|
| 632 | }
|
| 633 |
|
| 634 | |
| 635 | * If the sequence length is longer than remaining then the sequence is split
|
| 636 | * between this block and the next.
|
| 637 | *
|
| 638 | * Returns the current sequence to handle, or if the rest of the block should
|
| 639 | * be literals, it returns a sequence with offset == 0.
|
| 640 |
|
| 641 | static rawSeq maybeSplitSequence(RawSeqStore_t* rawSeqStore,
|
| 642 | U32 const remaining, U32 const minMatch)
|
| 643 | {
|
| 644 | rawSeq sequence = rawSeqStore->seq[rawSeqStore->pos];
|
| 645 | assert(sequence.offset > 0);
|
| 646 |
|
| 647 | if (remaining >= sequence.litLength + sequence.matchLength) {
|
| 648 | rawSeqStore->pos++;
|
| 649 | return sequence;
|
| 650 | }
|
| 651 |
|
| 652 | if (remaining <= sequence.litLength) {
|
| 653 | sequence.offset = 0;
|
| 654 | } else if (remaining < sequence.litLength + sequence.matchLength) {
|
| 655 | sequence.matchLength = remaining - sequence.litLength;
|
| 656 | if (sequence.matchLength < minMatch) {
|
| 657 | sequence.offset = 0;
|
| 658 | }
|
| 659 | }
|
| 660 |
|
| 661 | ZSTD_ldm_skipSequences(rawSeqStore, remaining, minMatch);
|
| 662 | return sequence;
|
| 663 | }
|
| 664 |
|
| 665 | void ZSTD_ldm_skipRawSeqStoreBytes(RawSeqStore_t* rawSeqStore, size_t nbBytes) {
|
| 666 | U32 currPos = (U32)(rawSeqStore->posInSequence + nbBytes);
|
| 667 | while (currPos && rawSeqStore->pos < rawSeqStore->size) {
|
| 668 | rawSeq currSeq = rawSeqStore->seq[rawSeqStore->pos];
|
| 669 | if (currPos >= currSeq.litLength + currSeq.matchLength) {
|
| 670 | currPos -= currSeq.litLength + currSeq.matchLength;
|
| 671 | rawSeqStore->pos++;
|
| 672 | } else {
|
| 673 | rawSeqStore->posInSequence = currPos;
|
| 674 | break;
|
| 675 | }
|
| 676 | }
|
| 677 | if (currPos == 0 || rawSeqStore->pos == rawSeqStore->size) {
|
| 678 | rawSeqStore->posInSequence = 0;
|
| 679 | }
|
| 680 | }
|
| 681 |
|
| 682 | size_t ZSTD_ldm_blockCompress(RawSeqStore_t* rawSeqStore,
|
| 683 | ZSTD_MatchState_t* ms, SeqStore_t* seqStore, U32 rep[ZSTD_REP_NUM],
|
| 684 | ZSTD_ParamSwitch_e useRowMatchFinder,
|
| 685 | void const* src, size_t srcSize)
|
| 686 | {
|
| 687 | const ZSTD_compressionParameters* const cParams = &ms->cParams;
|
| 688 | unsigned const minMatch = cParams->minMatch;
|
| 689 | ZSTD_BlockCompressor_f const blockCompressor =
|
| 690 | ZSTD_selectBlockCompressor(cParams->strategy, useRowMatchFinder, ZSTD_matchState_dictMode(ms));
|
| 691 |
|
| 692 | BYTE const* const istart = (BYTE const*)src;
|
| 693 | BYTE const* const iend = istart + srcSize;
|
| 694 |
|
| 695 | BYTE const* ip = istart;
|
| 696 |
|
| 697 | DEBUGLOG(5, "ZSTD_ldm_blockCompress: srcSize=%zu", srcSize);
|
| 698 |
|
| 699 | if (cParams->strategy >= ZSTD_btopt) {
|
| 700 | size_t lastLLSize;
|
| 701 | ms->ldmSeqStore = rawSeqStore;
|
| 702 | lastLLSize = blockCompressor(ms, seqStore, rep, src, srcSize);
|
| 703 | ZSTD_ldm_skipRawSeqStoreBytes(rawSeqStore, srcSize);
|
| 704 | return lastLLSize;
|
| 705 | }
|
| 706 |
|
| 707 | assert(rawSeqStore->pos <= rawSeqStore->size);
|
| 708 | assert(rawSeqStore->size <= rawSeqStore->capacity);
|
| 709 |
|
| 710 | while (rawSeqStore->pos < rawSeqStore->size && ip < iend) {
|
| 711 |
|
| 712 | rawSeq const sequence = maybeSplitSequence(rawSeqStore,
|
| 713 | (U32)(iend - ip), minMatch);
|
| 714 |
|
| 715 | if (sequence.offset == 0)
|
| 716 | break;
|
| 717 |
|
| 718 | assert(ip + sequence.litLength + sequence.matchLength <= iend);
|
| 719 |
|
| 720 |
|
| 721 | ZSTD_ldm_limitTableUpdate(ms, ip);
|
| 722 | ZSTD_ldm_fillFastTables(ms, ip);
|
| 723 |
|
| 724 | DEBUGLOG(5, "pos %u : calling block compressor on segment of size %u", (unsigned)(ip-istart), sequence.litLength);
|
| 725 | {
|
| 726 | int i;
|
| 727 | size_t const newLitLength =
|
| 728 | blockCompressor(ms, seqStore, rep, ip, sequence.litLength);
|
| 729 | ip += sequence.litLength;
|
| 730 |
|
| 731 | for (i = ZSTD_REP_NUM - 1; i > 0; i--)
|
| 732 | rep[i] = rep[i-1];
|
| 733 | rep[0] = sequence.offset;
|
| 734 |
|
| 735 | ZSTD_storeSeq(seqStore, newLitLength, ip - newLitLength, iend,
|
| 736 | OFFSET_TO_OFFBASE(sequence.offset),
|
| 737 | sequence.matchLength);
|
| 738 | ip += sequence.matchLength;
|
| 739 | }
|
| 740 | }
|
| 741 |
|
| 742 | ZSTD_ldm_limitTableUpdate(ms, ip);
|
| 743 | ZSTD_ldm_fillFastTables(ms, ip);
|
| 744 |
|
| 745 | return blockCompressor(ms, seqStore, rep, ip, iend - ip);
|
| 746 | }
|