1
// Copyright 2019+ Klaus Post. All rights reserved.
2
// License information can be found in the LICENSE file.
3
// Based on work by Yann Collet, released under BSD License.
12
tableBits = 15 // Bits used in the table
13
tableSize = 1 << tableBits // Size of the table
14
tableShardCnt = 1 << (tableBits - dictShardBits) // Number of shards in the table
15
tableShardSize = tableSize / tableShardCnt // Size of an individual shard
17
tableMask = tableSize - 1 // Mask for table indices. Redundant, but can eliminate bounds checks.
18
maxMatchLength = 131074
21
type tableEntry struct {
26
type fastEncoder struct {
28
table [tableSize]tableEntry
31
type fastEncoderDict struct {
33
dictTable []tableEntry
34
tableShardDirty [tableShardCnt]bool
38
// Encode mimmics functionality in zstd_fast.c
39
func (e *fastEncoder) Encode(blk *blockEnc, src []byte) {
42
minNonLiteralBlockSize = 1 + 1 + inputMargin
45
// Protect against e.cur wraparound.
46
for e.cur >= bufferReset {
48
for i := range e.table[:] {
49
e.table[i] = tableEntry{}
54
// Shift down everything in the table that isn't already too far away.
55
minOff := e.cur + int32(len(e.hist)) - e.maxMatchOff
56
for i := range e.table[:] {
57
v := e.table[i].offset
61
v = v - e.cur + e.maxMatchOff
71
if len(src) < minNonLiteralBlockSize {
72
blk.extraLits = len(src)
73
blk.literals = blk.literals[:len(src)]
74
copy(blk.literals, src)
80
sLimit := int32(len(src)) - inputMargin
81
// stepSize is the number of bytes to skip on every main loop iteration.
86
const hashLog = tableBits
87
// seems global, but would be nice to tweak.
88
const kSearchStrength = 6
90
// nextEmit is where in src the next emitLiteral should start from.
92
cv := load6432(src, s)
95
offset1 := int32(blk.recentOffsets[0])
96
offset2 := int32(blk.recentOffsets[1])
98
addLiterals := func(s *seq, until int32) {
99
if until == nextEmit {
102
blk.literals = append(blk.literals, src[nextEmit:until]...)
103
s.litLen = uint32(until - nextEmit)
106
println("recent offsets:", blk.recentOffsets)
111
// t will contain the match offset when we find one.
112
// When existing the search loop, we have already checked 4 bytes.
115
// We will not use repeat offsets across blocks.
116
// By not using them for the first 3 matches
117
canRepeat := len(blk.sequences) > 2
120
if debugAsserts && canRepeat && offset1 == 0 {
121
panic("offset0 was 0")
124
nextHash := hashLen(cv, hashLog, tableFastHashLen)
125
nextHash2 := hashLen(cv>>8, hashLog, tableFastHashLen)
126
candidate := e.table[nextHash]
127
candidate2 := e.table[nextHash2]
128
repIndex := s - offset1 + 2
130
e.table[nextHash] = tableEntry{offset: s + e.cur, val: uint32(cv)}
131
e.table[nextHash2] = tableEntry{offset: s + e.cur + 1, val: uint32(cv >> 8)}
133
if canRepeat && repIndex >= 0 && load3232(src, repIndex) == uint32(cv>>16) {
134
// Consider history as well.
137
length = 4 + e.matchlen(s+6, repIndex+4, src)
138
seq.matchLen = uint32(length - zstdMinMatch)
140
// We might be able to match backwards.
141
// Extend as long as we can.
143
// We end the search early, so we don't risk 0 literals
144
// and have to do special offset treatment.
145
startLimit := nextEmit + 1
147
sMin := s - e.maxMatchOff
151
for repIndex > sMin && start > startLimit && src[repIndex-1] == src[start-1] && seq.matchLen < maxMatchLength-zstdMinMatch {
156
addLiterals(&seq, start)
161
println("repeat sequence", seq, "next s:", s)
163
blk.sequences = append(blk.sequences, seq)
168
println("repeat ended", s, length)
173
cv = load6432(src, s)
176
coffset0 := s - (candidate.offset - e.cur)
177
coffset1 := s - (candidate2.offset - e.cur) + 1
178
if coffset0 < e.maxMatchOff && uint32(cv) == candidate.val {
179
// found a regular match
180
t = candidate.offset - e.cur
181
if debugAsserts && s <= t {
182
panic(fmt.Sprintf("s (%d) <= t (%d)", s, t))
184
if debugAsserts && s-t > e.maxMatchOff {
185
panic("s - t >e.maxMatchOff")
190
if coffset1 < e.maxMatchOff && uint32(cv>>8) == candidate2.val {
191
// found a regular match
192
t = candidate2.offset - e.cur
194
if debugAsserts && s <= t {
195
panic(fmt.Sprintf("s (%d) <= t (%d)", s, t))
197
if debugAsserts && s-t > e.maxMatchOff {
198
panic("s - t >e.maxMatchOff")
200
if debugAsserts && t < 0 {
205
s += stepSize + ((s - nextEmit) >> (kSearchStrength - 1))
209
cv = load6432(src, s)
211
// A 4-byte match has been found. We'll later see if more than 4 bytes.
215
if debugAsserts && s <= t {
216
panic(fmt.Sprintf("s (%d) <= t (%d)", s, t))
219
if debugAsserts && canRepeat && int(offset1) > len(src) {
220
panic("invalid offset")
223
// Extend the 4-byte match as long as possible.
224
l := e.matchlen(s+4, t+4, src) + 4
227
tMin := s - e.maxMatchOff
231
for t > tMin && s > nextEmit && src[t-1] == src[s-1] && l < maxMatchLength {
237
// Write our sequence.
239
seq.litLen = uint32(s - nextEmit)
240
seq.matchLen = uint32(l - zstdMinMatch)
242
blk.literals = append(blk.literals, src[nextEmit:s]...)
244
// Don't use repeat offsets
245
seq.offset = uint32(s-t) + 3
248
println("sequence", seq, "next s:", s)
250
blk.sequences = append(blk.sequences, seq)
255
cv = load6432(src, s)
258
if o2 := s - offset2; canRepeat && load3232(src, o2) == uint32(cv) {
259
// We have at least 4 byte match.
260
// No need to check backwards. We come straight from a match
261
l := 4 + e.matchlen(s+4, o2+4, src)
263
// Store this, since we have it.
264
nextHash := hashLen(cv, hashLog, tableFastHashLen)
265
e.table[nextHash] = tableEntry{offset: s + e.cur, val: uint32(cv)}
266
seq.matchLen = uint32(l) - zstdMinMatch
268
// Since litlen is always 0, this is offset 1.
273
println("sequence", seq, "next s:", s)
275
blk.sequences = append(blk.sequences, seq)
277
// Swap offset 1 and 2.
278
offset1, offset2 = offset2, offset1
282
// Prepare next loop.
283
cv = load6432(src, s)
287
if int(nextEmit) < len(src) {
288
blk.literals = append(blk.literals, src[nextEmit:]...)
289
blk.extraLits = len(src) - int(nextEmit)
291
blk.recentOffsets[0] = uint32(offset1)
292
blk.recentOffsets[1] = uint32(offset2)
294
println("returning, recent offsets:", blk.recentOffsets, "extra literals:", blk.extraLits)
298
// EncodeNoHist will encode a block with no history and no following blocks.
299
// Most notable difference is that src will not be copied for history and
300
// we do not need to check for max match length.
301
func (e *fastEncoder) EncodeNoHist(blk *blockEnc, src []byte) {
304
minNonLiteralBlockSize = 1 + 1 + inputMargin
307
if len(src) > maxBlockSize {
312
// Protect against e.cur wraparound.
313
if e.cur >= bufferReset {
314
for i := range e.table[:] {
315
e.table[i] = tableEntry{}
317
e.cur = e.maxMatchOff
322
if len(src) < minNonLiteralBlockSize {
323
blk.extraLits = len(src)
324
blk.literals = blk.literals[:len(src)]
325
copy(blk.literals, src)
329
sLimit := int32(len(src)) - inputMargin
330
// stepSize is the number of bytes to skip on every main loop iteration.
331
// It should be >= 2.
335
const hashLog = tableBits
336
// seems global, but would be nice to tweak.
337
const kSearchStrength = 6
339
// nextEmit is where in src the next emitLiteral should start from.
341
cv := load6432(src, s)
344
offset1 := int32(blk.recentOffsets[0])
345
offset2 := int32(blk.recentOffsets[1])
347
addLiterals := func(s *seq, until int32) {
348
if until == nextEmit {
351
blk.literals = append(blk.literals, src[nextEmit:until]...)
352
s.litLen = uint32(until - nextEmit)
355
println("recent offsets:", blk.recentOffsets)
360
// t will contain the match offset when we find one.
361
// When existing the search loop, we have already checked 4 bytes.
364
// We will not use repeat offsets across blocks.
365
// By not using them for the first 3 matches
368
nextHash := hashLen(cv, hashLog, tableFastHashLen)
369
nextHash2 := hashLen(cv>>8, hashLog, tableFastHashLen)
370
candidate := e.table[nextHash]
371
candidate2 := e.table[nextHash2]
372
repIndex := s - offset1 + 2
374
e.table[nextHash] = tableEntry{offset: s + e.cur, val: uint32(cv)}
375
e.table[nextHash2] = tableEntry{offset: s + e.cur + 1, val: uint32(cv >> 8)}
377
if len(blk.sequences) > 2 && load3232(src, repIndex) == uint32(cv>>16) {
378
// Consider history as well.
380
length := 4 + e.matchlen(s+6, repIndex+4, src)
382
seq.matchLen = uint32(length - zstdMinMatch)
384
// We might be able to match backwards.
385
// Extend as long as we can.
387
// We end the search early, so we don't risk 0 literals
388
// and have to do special offset treatment.
389
startLimit := nextEmit + 1
391
sMin := s - e.maxMatchOff
395
for repIndex > sMin && start > startLimit && src[repIndex-1] == src[start-1] {
400
addLiterals(&seq, start)
405
println("repeat sequence", seq, "next s:", s)
407
blk.sequences = append(blk.sequences, seq)
412
println("repeat ended", s, length)
417
cv = load6432(src, s)
420
coffset0 := s - (candidate.offset - e.cur)
421
coffset1 := s - (candidate2.offset - e.cur) + 1
422
if coffset0 < e.maxMatchOff && uint32(cv) == candidate.val {
423
// found a regular match
424
t = candidate.offset - e.cur
425
if debugAsserts && s <= t {
426
panic(fmt.Sprintf("s (%d) <= t (%d)", s, t))
428
if debugAsserts && s-t > e.maxMatchOff {
429
panic("s - t >e.maxMatchOff")
431
if debugAsserts && t < 0 {
432
panic(fmt.Sprintf("t (%d) < 0, candidate.offset: %d, e.cur: %d, coffset0: %d, e.maxMatchOff: %d", t, candidate.offset, e.cur, coffset0, e.maxMatchOff))
437
if coffset1 < e.maxMatchOff && uint32(cv>>8) == candidate2.val {
438
// found a regular match
439
t = candidate2.offset - e.cur
441
if debugAsserts && s <= t {
442
panic(fmt.Sprintf("s (%d) <= t (%d)", s, t))
444
if debugAsserts && s-t > e.maxMatchOff {
445
panic("s - t >e.maxMatchOff")
447
if debugAsserts && t < 0 {
452
s += stepSize + ((s - nextEmit) >> (kSearchStrength - 1))
456
cv = load6432(src, s)
458
// A 4-byte match has been found. We'll later see if more than 4 bytes.
462
if debugAsserts && s <= t {
463
panic(fmt.Sprintf("s (%d) <= t (%d)", s, t))
466
if debugAsserts && t < 0 {
467
panic(fmt.Sprintf("t (%d) < 0 ", t))
469
// Extend the 4-byte match as long as possible.
470
l := e.matchlen(s+4, t+4, src) + 4
473
tMin := s - e.maxMatchOff
477
for t > tMin && s > nextEmit && src[t-1] == src[s-1] {
483
// Write our sequence.
485
seq.litLen = uint32(s - nextEmit)
486
seq.matchLen = uint32(l - zstdMinMatch)
488
blk.literals = append(blk.literals, src[nextEmit:s]...)
490
// Don't use repeat offsets
491
seq.offset = uint32(s-t) + 3
494
println("sequence", seq, "next s:", s)
496
blk.sequences = append(blk.sequences, seq)
501
cv = load6432(src, s)
504
if o2 := s - offset2; len(blk.sequences) > 2 && load3232(src, o2) == uint32(cv) {
505
// We have at least 4 byte match.
506
// No need to check backwards. We come straight from a match
507
l := 4 + e.matchlen(s+4, o2+4, src)
509
// Store this, since we have it.
510
nextHash := hashLen(cv, hashLog, tableFastHashLen)
511
e.table[nextHash] = tableEntry{offset: s + e.cur, val: uint32(cv)}
512
seq.matchLen = uint32(l) - zstdMinMatch
514
// Since litlen is always 0, this is offset 1.
519
println("sequence", seq, "next s:", s)
521
blk.sequences = append(blk.sequences, seq)
523
// Swap offset 1 and 2.
524
offset1, offset2 = offset2, offset1
528
// Prepare next loop.
529
cv = load6432(src, s)
533
if int(nextEmit) < len(src) {
534
blk.literals = append(blk.literals, src[nextEmit:]...)
535
blk.extraLits = len(src) - int(nextEmit)
538
println("returning, recent offsets:", blk.recentOffsets, "extra literals:", blk.extraLits)
540
// We do not store history, so we must offset e.cur to avoid false matches for next user.
541
if e.cur < bufferReset {
542
e.cur += int32(len(src))
546
// Encode will encode the content, with a dictionary if initialized for it.
547
func (e *fastEncoderDict) Encode(blk *blockEnc, src []byte) {
550
minNonLiteralBlockSize = 1 + 1 + inputMargin
552
if e.allDirty || len(src) > 32<<10 {
553
e.fastEncoder.Encode(blk, src)
557
// Protect against e.cur wraparound.
558
for e.cur >= bufferReset {
559
if len(e.hist) == 0 {
560
for i := range e.table[:] {
561
e.table[i] = tableEntry{}
563
e.cur = e.maxMatchOff
566
// Shift down everything in the table that isn't already too far away.
567
minOff := e.cur + int32(len(e.hist)) - e.maxMatchOff
568
for i := range e.table[:] {
569
v := e.table[i].offset
573
v = v - e.cur + e.maxMatchOff
575
e.table[i].offset = v
577
e.cur = e.maxMatchOff
583
if len(src) < minNonLiteralBlockSize {
584
blk.extraLits = len(src)
585
blk.literals = blk.literals[:len(src)]
586
copy(blk.literals, src)
592
sLimit := int32(len(src)) - inputMargin
593
// stepSize is the number of bytes to skip on every main loop iteration.
594
// It should be >= 2.
598
const hashLog = tableBits
599
// seems global, but would be nice to tweak.
600
const kSearchStrength = 7
602
// nextEmit is where in src the next emitLiteral should start from.
604
cv := load6432(src, s)
607
offset1 := int32(blk.recentOffsets[0])
608
offset2 := int32(blk.recentOffsets[1])
610
addLiterals := func(s *seq, until int32) {
611
if until == nextEmit {
614
blk.literals = append(blk.literals, src[nextEmit:until]...)
615
s.litLen = uint32(until - nextEmit)
618
println("recent offsets:", blk.recentOffsets)
623
// t will contain the match offset when we find one.
624
// When existing the search loop, we have already checked 4 bytes.
627
// We will not use repeat offsets across blocks.
628
// By not using them for the first 3 matches
629
canRepeat := len(blk.sequences) > 2
632
if debugAsserts && canRepeat && offset1 == 0 {
633
panic("offset0 was 0")
636
nextHash := hashLen(cv, hashLog, tableFastHashLen)
637
nextHash2 := hashLen(cv>>8, hashLog, tableFastHashLen)
638
candidate := e.table[nextHash]
639
candidate2 := e.table[nextHash2]
640
repIndex := s - offset1 + 2
642
e.table[nextHash] = tableEntry{offset: s + e.cur, val: uint32(cv)}
643
e.markShardDirty(nextHash)
644
e.table[nextHash2] = tableEntry{offset: s + e.cur + 1, val: uint32(cv >> 8)}
645
e.markShardDirty(nextHash2)
647
if canRepeat && repIndex >= 0 && load3232(src, repIndex) == uint32(cv>>16) {
648
// Consider history as well.
651
length = 4 + e.matchlen(s+6, repIndex+4, src)
653
seq.matchLen = uint32(length - zstdMinMatch)
655
// We might be able to match backwards.
656
// Extend as long as we can.
658
// We end the search early, so we don't risk 0 literals
659
// and have to do special offset treatment.
660
startLimit := nextEmit + 1
662
sMin := s - e.maxMatchOff
666
for repIndex > sMin && start > startLimit && src[repIndex-1] == src[start-1] && seq.matchLen < maxMatchLength-zstdMinMatch {
671
addLiterals(&seq, start)
676
println("repeat sequence", seq, "next s:", s)
678
blk.sequences = append(blk.sequences, seq)
683
println("repeat ended", s, length)
688
cv = load6432(src, s)
691
coffset0 := s - (candidate.offset - e.cur)
692
coffset1 := s - (candidate2.offset - e.cur) + 1
693
if coffset0 < e.maxMatchOff && uint32(cv) == candidate.val {
694
// found a regular match
695
t = candidate.offset - e.cur
696
if debugAsserts && s <= t {
697
panic(fmt.Sprintf("s (%d) <= t (%d)", s, t))
699
if debugAsserts && s-t > e.maxMatchOff {
700
panic("s - t >e.maxMatchOff")
705
if coffset1 < e.maxMatchOff && uint32(cv>>8) == candidate2.val {
706
// found a regular match
707
t = candidate2.offset - e.cur
709
if debugAsserts && s <= t {
710
panic(fmt.Sprintf("s (%d) <= t (%d)", s, t))
712
if debugAsserts && s-t > e.maxMatchOff {
713
panic("s - t >e.maxMatchOff")
715
if debugAsserts && t < 0 {
720
s += stepSize + ((s - nextEmit) >> (kSearchStrength - 1))
724
cv = load6432(src, s)
726
// A 4-byte match has been found. We'll later see if more than 4 bytes.
730
if debugAsserts && s <= t {
731
panic(fmt.Sprintf("s (%d) <= t (%d)", s, t))
734
if debugAsserts && canRepeat && int(offset1) > len(src) {
735
panic("invalid offset")
738
// Extend the 4-byte match as long as possible.
739
l := e.matchlen(s+4, t+4, src) + 4
742
tMin := s - e.maxMatchOff
746
for t > tMin && s > nextEmit && src[t-1] == src[s-1] && l < maxMatchLength {
752
// Write our sequence.
754
seq.litLen = uint32(s - nextEmit)
755
seq.matchLen = uint32(l - zstdMinMatch)
757
blk.literals = append(blk.literals, src[nextEmit:s]...)
759
// Don't use repeat offsets
760
seq.offset = uint32(s-t) + 3
763
println("sequence", seq, "next s:", s)
765
blk.sequences = append(blk.sequences, seq)
770
cv = load6432(src, s)
773
if o2 := s - offset2; canRepeat && load3232(src, o2) == uint32(cv) {
774
// We have at least 4 byte match.
775
// No need to check backwards. We come straight from a match
776
l := 4 + e.matchlen(s+4, o2+4, src)
778
// Store this, since we have it.
779
nextHash := hashLen(cv, hashLog, tableFastHashLen)
780
e.table[nextHash] = tableEntry{offset: s + e.cur, val: uint32(cv)}
781
e.markShardDirty(nextHash)
782
seq.matchLen = uint32(l) - zstdMinMatch
784
// Since litlen is always 0, this is offset 1.
789
println("sequence", seq, "next s:", s)
791
blk.sequences = append(blk.sequences, seq)
793
// Swap offset 1 and 2.
794
offset1, offset2 = offset2, offset1
798
// Prepare next loop.
799
cv = load6432(src, s)
803
if int(nextEmit) < len(src) {
804
blk.literals = append(blk.literals, src[nextEmit:]...)
805
blk.extraLits = len(src) - int(nextEmit)
807
blk.recentOffsets[0] = uint32(offset1)
808
blk.recentOffsets[1] = uint32(offset2)
810
println("returning, recent offsets:", blk.recentOffsets, "extra literals:", blk.extraLits)
814
// ResetDict will reset and set a dictionary if not nil
815
func (e *fastEncoder) Reset(d *dict, singleBlock bool) {
816
e.resetBase(d, singleBlock)
818
panic("fastEncoder: Reset with dict")
822
// ResetDict will reset and set a dictionary if not nil
823
func (e *fastEncoderDict) Reset(d *dict, singleBlock bool) {
824
e.resetBase(d, singleBlock)
829
// Init or copy dict table
830
if len(e.dictTable) != len(e.table) || d.id != e.lastDictID {
831
if len(e.dictTable) != len(e.table) {
832
e.dictTable = make([]tableEntry, len(e.table))
835
end := e.maxMatchOff + int32(len(d.content)) - 8
836
for i := e.maxMatchOff; i < end; i += 3 {
837
const hashLog = tableBits
839
cv := load6432(d.content, i-e.maxMatchOff)
840
nextHash := hashLen(cv, hashLog, tableFastHashLen) // 0 -> 5
841
nextHash1 := hashLen(cv>>8, hashLog, tableFastHashLen) // 1 -> 6
842
nextHash2 := hashLen(cv>>16, hashLog, tableFastHashLen) // 2 -> 7
843
e.dictTable[nextHash] = tableEntry{
847
e.dictTable[nextHash1] = tableEntry{
848
val: uint32(cv >> 8),
851
e.dictTable[nextHash2] = tableEntry{
852
val: uint32(cv >> 16),
861
e.cur = e.maxMatchOff
864
for i := range e.tableShardDirty {
865
if e.tableShardDirty[i] {
871
const shardCnt = tableShardCnt
872
const shardSize = tableShardSize
873
if e.allDirty || dirtyShardCnt > shardCnt*4/6 {
874
copy(e.table[:], e.dictTable)
875
for i := range e.tableShardDirty {
876
e.tableShardDirty[i] = false
881
for i := range e.tableShardDirty {
882
if !e.tableShardDirty[i] {
886
copy(e.table[i*shardSize:(i+1)*shardSize], e.dictTable[i*shardSize:(i+1)*shardSize])
887
e.tableShardDirty[i] = false
892
func (e *fastEncoderDict) markAllShardsDirty() {
896
func (e *fastEncoderDict) markShardDirty(entryNum uint32) {
897
e.tableShardDirty[entryNum/tableShardSize] = true