blob: c8d047f2d9354b5315cafd18ef93fa82bdd5bbca [file] [log] [blame]
Abhay Kumar40252eb2025-10-13 13:25:53 +00001package flate
2
3import "fmt"
4
5// fastGen maintains the table for matches,
6// and the previous byte block for level 2.
7// This is the generic implementation.
8type fastEncL2 struct {
9 fastGen
10 table [bTableSize]tableEntry
11}
12
13// EncodeL2 uses a similar algorithm to level 1, but is capable
14// of matching across blocks giving better compression at a small slowdown.
15func (e *fastEncL2) Encode(dst *tokens, src []byte) {
16 const (
17 inputMargin = 12 - 1
18 minNonLiteralBlockSize = 1 + 1 + inputMargin
19 hashBytes = 5
20 )
21
22 if debugDeflate && e.cur < 0 {
23 panic(fmt.Sprint("e.cur < 0: ", e.cur))
24 }
25
26 // Protect against e.cur wraparound.
27 for e.cur >= bufferReset {
28 if len(e.hist) == 0 {
29 for i := range e.table[:] {
30 e.table[i] = tableEntry{}
31 }
32 e.cur = maxMatchOffset
33 break
34 }
35 // Shift down everything in the table that isn't already too far away.
36 minOff := e.cur + int32(len(e.hist)) - maxMatchOffset
37 for i := range e.table[:] {
38 v := e.table[i].offset
39 if v <= minOff {
40 v = 0
41 } else {
42 v = v - e.cur + maxMatchOffset
43 }
44 e.table[i].offset = v
45 }
46 e.cur = maxMatchOffset
47 }
48
49 s := e.addBlock(src)
50
51 // This check isn't in the Snappy implementation, but there, the caller
52 // instead of the callee handles this case.
53 if len(src) < minNonLiteralBlockSize {
54 // We do not fill the token table.
55 // This will be picked up by caller.
56 dst.n = uint16(len(src))
57 return
58 }
59
60 // Override src
61 src = e.hist
62 nextEmit := s
63
64 // sLimit is when to stop looking for offset/length copies. The inputMargin
65 // lets us use a fast path for emitLiteral in the main loop, while we are
66 // looking for copies.
67 sLimit := int32(len(src) - inputMargin)
68
69 // nextEmit is where in src the next emitLiteral should start from.
70 cv := load6432(src, s)
71 for {
72 // When should we start skipping if we haven't found matches in a long while.
73 const skipLog = 5
74 const doEvery = 2
75
76 nextS := s
77 var candidate tableEntry
78 for {
79 nextHash := hashLen(cv, bTableBits, hashBytes)
80 s = nextS
81 nextS = s + doEvery + (s-nextEmit)>>skipLog
82 if nextS > sLimit {
83 goto emitRemainder
84 }
85 candidate = e.table[nextHash]
86 now := load6432(src, nextS)
87 e.table[nextHash] = tableEntry{offset: s + e.cur}
88 nextHash = hashLen(now, bTableBits, hashBytes)
89
90 offset := s - (candidate.offset - e.cur)
91 if offset < maxMatchOffset && uint32(cv) == load3232(src, candidate.offset-e.cur) {
92 e.table[nextHash] = tableEntry{offset: nextS + e.cur}
93 break
94 }
95
96 // Do one right away...
97 cv = now
98 s = nextS
99 nextS++
100 candidate = e.table[nextHash]
101 now >>= 8
102 e.table[nextHash] = tableEntry{offset: s + e.cur}
103
104 offset = s - (candidate.offset - e.cur)
105 if offset < maxMatchOffset && uint32(cv) == load3232(src, candidate.offset-e.cur) {
106 break
107 }
108 cv = now
109 }
110
111 // A 4-byte match has been found. We'll later see if more than 4 bytes
112 // match. But, prior to the match, src[nextEmit:s] are unmatched. Emit
113 // them as literal bytes.
114
115 // Call emitCopy, and then see if another emitCopy could be our next
116 // move. Repeat until we find no match for the input immediately after
117 // what was consumed by the last emitCopy call.
118 //
119 // If we exit this loop normally then we need to call emitLiteral next,
120 // though we don't yet know how big the literal will be. We handle that
121 // by proceeding to the next iteration of the main loop. We also can
122 // exit this loop via goto if we get close to exhausting the input.
123 for {
124 // Invariant: we have a 4-byte match at s, and no need to emit any
125 // literal bytes prior to s.
126
127 // Extend the 4-byte match as long as possible.
128 t := candidate.offset - e.cur
129 l := e.matchlenLong(int(s+4), int(t+4), src) + 4
130
131 // Extend backwards
132 for t > 0 && s > nextEmit && src[t-1] == src[s-1] {
133 s--
134 t--
135 l++
136 }
137 if nextEmit < s {
138 if false {
139 emitLiteral(dst, src[nextEmit:s])
140 } else {
141 for _, v := range src[nextEmit:s] {
142 dst.tokens[dst.n] = token(v)
143 dst.litHist[v]++
144 dst.n++
145 }
146 }
147 }
148
149 dst.AddMatchLong(l, uint32(s-t-baseMatchOffset))
150 s += l
151 nextEmit = s
152 if nextS >= s {
153 s = nextS + 1
154 }
155
156 if s >= sLimit {
157 // Index first pair after match end.
158 if int(s+l+8) < len(src) {
159 cv := load6432(src, s)
160 e.table[hashLen(cv, bTableBits, hashBytes)] = tableEntry{offset: s + e.cur}
161 }
162 goto emitRemainder
163 }
164
165 // Store every second hash in-between, but offset by 1.
166 for i := s - l + 2; i < s-5; i += 7 {
167 x := load6432(src, i)
168 nextHash := hashLen(x, bTableBits, hashBytes)
169 e.table[nextHash] = tableEntry{offset: e.cur + i}
170 // Skip one
171 x >>= 16
172 nextHash = hashLen(x, bTableBits, hashBytes)
173 e.table[nextHash] = tableEntry{offset: e.cur + i + 2}
174 // Skip one
175 x >>= 16
176 nextHash = hashLen(x, bTableBits, hashBytes)
177 e.table[nextHash] = tableEntry{offset: e.cur + i + 4}
178 }
179
180 // We could immediately start working at s now, but to improve
181 // compression we first update the hash table at s-2 to s. If
182 // another emitCopy is not our next move, also calculate nextHash
183 // at s+1. At least on GOARCH=amd64, these three hash calculations
184 // are faster as one load64 call (with some shifts) instead of
185 // three load32 calls.
186 x := load6432(src, s-2)
187 o := e.cur + s - 2
188 prevHash := hashLen(x, bTableBits, hashBytes)
189 prevHash2 := hashLen(x>>8, bTableBits, hashBytes)
190 e.table[prevHash] = tableEntry{offset: o}
191 e.table[prevHash2] = tableEntry{offset: o + 1}
192 currHash := hashLen(x>>16, bTableBits, hashBytes)
193 candidate = e.table[currHash]
194 e.table[currHash] = tableEntry{offset: o + 2}
195
196 offset := s - (candidate.offset - e.cur)
197 if offset > maxMatchOffset || uint32(x>>16) != load3232(src, candidate.offset-e.cur) {
198 cv = x >> 24
199 s++
200 break
201 }
202 }
203 }
204
205emitRemainder:
206 if int(nextEmit) < len(src) {
207 // If nothing was added, don't encode literals.
208 if dst.n == 0 {
209 return
210 }
211
212 emitLiteral(dst, src[nextEmit:])
213 }
214}