Coverage Report

Created: 2026-11-11 06:47

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