gbdk-2020 | GameBoy Development Kit |
| download: https://git.y1.nz/archives/gbdk.tar.gz | |
| README | Files | Log | Refs | LICENSE |
gbdk-support/gbcompress/zx0/zx0_matchfinder.c
1 /*
2 * matchfinder.c - LZ match finder implementation
3 *
4 * The following copying information applies to this specific source code file:
5 *
6 * Written in 2019-2021 by Emmanuel Marty <marty.emmanuel@gmail.com>
7 * Portions written in 2014-2015 by Eric Biggers <ebiggers3@gmail.com>
8 *
9 * To the extent possible under law, the author(s) have dedicated all copyright
10 * and related and neighboring rights to this software to the public domain
11 * worldwide via the Creative Commons Zero 1.0 Universal Public Domain
12 * Dedication (the "CC0").
13 *
14 * This software is distributed in the hope that it will be useful, but WITHOUT
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 * FOR A PARTICULAR PURPOSE. See the CC0 for more details.
17 *
18 * You should have received a copy of the CC0 along with this software; if not
19 * see <http://creativecommons.org/publicdomain/zero/1.0/>.
20 */
21
22 /*
23 * Uses the libdivsufsort library Copyright (c) 2003-2008 Yuta Mori
24 *
25 * Implements the ZX0 encoding designed by Einar Saukas. https://github.com/einar-saukas/ZX0
26 * Also inspired by Charles Bloom's compression blog. http://cbloomrants.blogspot.com/
27 *
28 */
29
30 #include <stdlib.h>
31 #include <string.h>
32 #include "zx0_matchfinder.h"
33 #include "zx0_format.h"
34 #include "zx0_libsalvador.h"
35
36 /**
37 * Hash index into TAG_BITS
38 *
39 * @param nIndex index value
40 *
41 * @return hash
42 */
43 static inline int salvador_get_index_tag(unsigned int nIndex) {
44 return (int)(((unsigned long long)nIndex * 11400714819323198485ULL) >> (64ULL - TAG_BITS));
45 }
46
47 /**
48 * Parse input data, build suffix array and overlaid data structures to speed up match finding
49 *
50 * @param pCompressor compression context
51 * @param pInWindow pointer to input data window (previously compressed bytes + bytes to compress)
52 * @param nInWindowSize total input size in bytes (previously compressed bytes + bytes to compress)
53 *
54 * @return 0 for success, non-zero for failure
55 */
56 int salvador_build_suffix_array(salvador_compressor *pCompressor, const unsigned char *pInWindow, const int nInWindowSize) {
57 unsigned long long *intervals = pCompressor->intervals;
58
59 /* Build suffix array from input data */
60 saidx_t *suffixArray = (saidx_t*)intervals;
61 if (divsufsort_build_array(&pCompressor->divsufsort_context, pInWindow, suffixArray, nInWindowSize) != 0) {
62 return 100;
63 }
64
65 int i, r;
66
67 for (i = nInWindowSize - 1; i >= 0; i--) {
68 intervals[i] = suffixArray[i];
69 }
70
71 int *PLCP = (int*)pCompressor->pos_data; /* Use temporarily */
72 int *Phi = PLCP;
73 int nCurLen = 0;
74
75 /* Compute the permuted LCP first (Kärkkäinen method) */
76 Phi[intervals[0]] = -1;
77 for (i = 1; i < nInWindowSize; i++)
78 Phi[intervals[i]] = (unsigned int)intervals[i - 1];
79 for (i = 0; i < nInWindowSize; i++) {
80 if (Phi[i] == -1) {
81 PLCP[i] = 0;
82 continue;
83 }
84 const int nMaxLen = (i > Phi[i]) ? (nInWindowSize - i) : (nInWindowSize - Phi[i]);
85 while (nCurLen < nMaxLen && pInWindow[i + nCurLen] == pInWindow[Phi[i] + nCurLen]) nCurLen++;
86 PLCP[i] = nCurLen;
87 if (nCurLen > 0)
88 nCurLen--;
89 }
90
91 /* Rotate permuted LCP into the LCP. This has better cache locality than the direct Kasai LCP method. This also
92 * saves us from having to build the inverse suffix array index, as the LCP is calculated without it using this method,
93 * and the interval builder below doesn't need it either. */
94 intervals[0] &= POS_MASK;
95
96 for (i = 1; i < nInWindowSize; i++) {
97 const int nIndex = (const int)(intervals[i] & POS_MASK);
98 int nLen = PLCP[nIndex];
99 if (nLen < MIN_MATCH_SIZE)
100 nLen = 0;
101 if (nLen > LCP_MAX)
102 nLen = LCP_MAX;
103 int nTaggedLen = 0;
104 if (nLen)
105 nTaggedLen = (nLen << TAG_BITS) | (salvador_get_index_tag((unsigned int)nIndex) & ((1 << TAG_BITS) - 1));
106 intervals[i] = ((unsigned long long)nIndex) | (((unsigned long long)nTaggedLen) << LCP_SHIFT);
107 }
108
109 /**
110 * Build intervals for finding matches
111 *
112 * Methodology and code fragment taken from wimlib (CC0 license):
113 * https://wimlib.net/git/?p=wimlib;a=blob_plain;f=src/lcpit_matchfinder.c;h=a2d6a1e0cd95200d1f3a5464d8359d5736b14cbe;hb=HEAD
114 */
115 unsigned long long * const SA_and_LCP = intervals;
116 unsigned long long *pos_data = pCompressor->pos_data;
117 unsigned long long next_interval_idx;
118 unsigned long long *top = pCompressor->open_intervals;
119 unsigned long long prev_pos = SA_and_LCP[0] & POS_MASK;
120
121 *top = 0;
122 intervals[0] = 0;
123 next_interval_idx = 1;
124
125 for (r = 1; r < nInWindowSize; r++) {
126 const unsigned long long next_pos = SA_and_LCP[r] & POS_MASK;
127 const unsigned long long next_lcp = SA_and_LCP[r] & LCP_MASK;
128 const unsigned long long top_lcp = *top & LCP_MASK;
129
130 if (next_lcp == top_lcp) {
131 /* Continuing the deepest open interval */
132 pos_data[prev_pos] = *top;
133 }
134 else if (next_lcp > top_lcp) {
135 /* Opening a new interval */
136 *++top = next_lcp | next_interval_idx++;
137 pos_data[prev_pos] = *top;
138 }
139 else {
140 /* Closing the deepest open interval */
141 pos_data[prev_pos] = *top;
142 for (;;) {
143 const unsigned long long closed_interval_idx = *top-- & POS_MASK;
144 const unsigned long long superinterval_lcp = *top & LCP_MASK;
145
146 if (next_lcp == superinterval_lcp) {
147 /* Continuing the superinterval */
148 intervals[closed_interval_idx] = *top;
149 break;
150 }
151 else if (next_lcp > superinterval_lcp) {
152 /* Creating a new interval that is a
153 * superinterval of the one being
154 * closed, but still a subinterval of
155 * its superinterval */
156 *++top = next_lcp | next_interval_idx++;
157 intervals[closed_interval_idx] = *top;
158 break;
159 }
160 else {
161 /* Also closing the superinterval */
162 intervals[closed_interval_idx] = *top;
163 }
164 }
165 }
166 prev_pos = next_pos;
167 }
168
169 /* Close any still-open intervals. */
170 pos_data[prev_pos] = *top;
171 for (; top > pCompressor->open_intervals; top--)
172 intervals[*top & POS_MASK] = *(top - 1);
173
174 /* Success */
175 return 0;
176 }
177
178 /**
179 * Find matches at the specified offset in the input window
180 *
181 * @param pCompressor compression context
182 * @param nOffset offset to find matches at, in the input window
183 * @param pMatches pointer to returned matches
184 * @param pMatchDepth pointer to returned match depths
185 * @param nMaxMatches maximum number of matches to return (0 for none)
186 *
187 * @return number of matches
188 */
189 static int salvador_find_matches_at(salvador_compressor *pCompressor, const int nOffset, salvador_match *pMatches, unsigned short *pMatchDepth, const int nMaxMatches) {
190 unsigned long long *intervals = pCompressor->intervals;
191 unsigned long long *pos_data = pCompressor->pos_data;
192 unsigned long long ref;
193 unsigned long long super_ref;
194 unsigned long long match_pos;
195 salvador_match *matchptr;
196 unsigned short *depthptr;
197 unsigned int nPrevOffset, nPrevLen, nCurDepth;
198 unsigned short* cur_depth;
199 const salvador_match* pMaxMatch = pMatches + nMaxMatches;
200 const unsigned int nMaxOffset = (const unsigned int)pCompressor->max_offset;
201
202 /**
203 * Find matches using intervals
204 *
205 * Taken from wimlib (CC0 license):
206 * https://wimlib.net/git/?p=wimlib;a=blob_plain;f=src/lcpit_matchfinder.c;h=a2d6a1e0cd95200d1f3a5464d8359d5736b14cbe;hb=HEAD
207 */
208
209 /* Get the deepest lcp-interval containing the current suffix. */
210 ref = pos_data[nOffset];
211
212 pos_data[nOffset] = 0;
213
214 /* Ascend until we reach a visited interval, the root, or a child of the
215 * root. Link unvisited intervals to the current suffix as we go. */
216 while ((super_ref = intervals[ref & POS_MASK]) & LCP_MASK) {
217 intervals[ref & POS_MASK] = nOffset | VISITED_FLAG;
218 ref = super_ref;
219 }
220
221 if (super_ref == 0) {
222 /* In this case, the current interval may be any of:
223 * (1) the root;
224 * (2) an unvisited child of the root */
225
226 if (ref != 0) /* Not the root? */
227 intervals[ref & POS_MASK] = nOffset | VISITED_FLAG;
228 return 0;
229 }
230
231 /* Ascend indirectly via pos_data[] links. */
232
233 match_pos = super_ref & EXCL_VISITED_MASK;
234 matchptr = pMatches;
235 depthptr = pMatchDepth;
236
237 nPrevOffset = 0;
238 nPrevLen = 0;
239 nCurDepth = 0;
240 cur_depth = NULL;
241
242 if (matchptr < pMaxMatch) {
243 const unsigned int nMatchOffset = (const unsigned int)(nOffset - match_pos);
244
245 if (nMatchOffset <= nMaxOffset) {
246 const unsigned int nMatchLen = (const unsigned int)(ref >> (LCP_SHIFT + TAG_BITS));
247
248 matchptr->length = nMatchLen;
249 matchptr->offset = nMatchOffset;
250 matchptr++;
251
252 *depthptr = nCurDepth = 0;
253 cur_depth = depthptr++;
254
255 nPrevLen = nMatchLen;
256 nPrevOffset = nMatchOffset;
257 }
258 }
259
260 for (;;) {
261 if ((super_ref = pos_data[match_pos]) > ref) {
262 match_pos = intervals[super_ref & POS_MASK] & EXCL_VISITED_MASK;
263
264 if (matchptr < pMaxMatch) {
265 const unsigned int nMatchOffset = (const unsigned int)(nOffset - match_pos);
266
267 if (nMatchOffset <= nMaxOffset) {
268 const unsigned int nMatchLen = (const unsigned int)(ref >> (LCP_SHIFT + TAG_BITS));
269
270 if (nPrevLen > 2 && nPrevOffset && nMatchOffset == (nPrevOffset - 1) && nMatchLen == (nPrevLen - 1) && cur_depth && nCurDepth < LCP_MAX) {
271 *cur_depth = ++nCurDepth;
272 }
273 else {
274 matchptr->length = nMatchLen;
275 matchptr->offset = nMatchOffset;
276 matchptr++;
277
278 *depthptr = nCurDepth = 0;
279 cur_depth = depthptr++;
280 }
281
282 nPrevLen = nMatchLen;
283 nPrevOffset = nMatchOffset;
284 }
285 }
286 }
287
288 while ((super_ref = pos_data[match_pos]) > ref)
289 match_pos = intervals[super_ref & POS_MASK] & EXCL_VISITED_MASK;
290
291 intervals[ref & POS_MASK] = nOffset | VISITED_FLAG;
292 pos_data[match_pos] = (unsigned long long)ref;
293
294 if (matchptr < pMaxMatch) {
295 const unsigned int nMatchOffset = (const unsigned int)(nOffset - match_pos);
296
297 if (nMatchOffset <= nMaxOffset && nMatchOffset != nPrevOffset) {
298 const unsigned int nMatchLen = (const unsigned int)(ref >> (LCP_SHIFT + TAG_BITS));
299
300 if (nPrevLen > 2 && nPrevOffset && nMatchOffset == (nPrevOffset - 1) && nMatchLen == (nPrevLen - 1) && cur_depth && nCurDepth < LCP_MAX) {
301 *cur_depth = ++nCurDepth;
302 }
303 else {
304 matchptr->length = nMatchLen;
305 matchptr->offset = nMatchOffset;
306 matchptr++;
307
308 *depthptr = nCurDepth = 0;
309 cur_depth = depthptr++;
310 }
311
312 nPrevLen = nMatchLen;
313 nPrevOffset = nMatchOffset;
314 }
315 }
316
317 if (super_ref == 0)
318 break;
319 ref = super_ref;
320 match_pos = intervals[ref & POS_MASK] & EXCL_VISITED_MASK;
321
322 if (matchptr < pMaxMatch) {
323 const unsigned int nMatchOffset = (const unsigned int)(nOffset - match_pos);
324
325 if (nMatchOffset <= nMaxOffset) {
326 const unsigned int nMatchLen = (const unsigned int)(ref >> (LCP_SHIFT + TAG_BITS));
327
328 if (nPrevLen > 2 && nPrevOffset && nMatchOffset == (nPrevOffset - 1) && nMatchLen == (nPrevLen - 1) && cur_depth && nCurDepth < LCP_MAX) {
329 *cur_depth = ++nCurDepth;
330 }
331 else {
332 matchptr->length = nMatchLen;
333 matchptr->offset = nMatchOffset;
334 matchptr++;
335
336 *depthptr = nCurDepth = 0;
337 cur_depth = depthptr++;
338 }
339
340 nPrevLen = nMatchLen;
341 nPrevOffset = nMatchOffset;
342 }
343 }
344 }
345
346 return (int)(matchptr - pMatches);
347 }
348
349 /**
350 * Skip previously compressed bytes
351 *
352 * @param pCompressor compression context
353 * @param nStartOffset current offset in input window (typically 0)
354 * @param nEndOffset offset to skip to in input window (typically the number of previously compressed bytes)
355 */
356 void salvador_skip_matches(salvador_compressor *pCompressor, const int nStartOffset, const int nEndOffset) {
357 salvador_match match;
358 unsigned short depth;
359 int i;
360
361 /* Skipping still requires scanning for matches, as this also performs a lazy update of the intervals. However,
362 * we don't store the matches. */
363 for (i = nStartOffset; i < nEndOffset; i++) {
364 salvador_find_matches_at(pCompressor, i, &match, &depth, 0);
365 }
366 }
367
368 /**
369 * Find all matches for the data to be compressed
370 *
371 * @param pCompressor compression context
372 * @param nMatchesPerOffset maximum number of matches to store for each offset
373 * @param nStartOffset current offset in input window (typically the number of previously compressed bytes)
374 * @param nEndOffset offset to end finding matches at (typically the size of the total input window in bytes
375 */
376 void salvador_find_all_matches(salvador_compressor *pCompressor, const int nMatchesPerOffset, const int nStartOffset, const int nEndOffset) {
377 salvador_match *pMatch = pCompressor->match;
378 unsigned short *pMatchDepth = pCompressor->match_depth;
379 int i;
380
381 for (i = nStartOffset; i < nEndOffset; i++) {
382 const int nMatches = salvador_find_matches_at(pCompressor, i, pMatch, pMatchDepth, nMatchesPerOffset);
383
384 if (nMatches < nMatchesPerOffset) {
385 memset(pMatch + nMatches, 0, (nMatchesPerOffset - nMatches) * sizeof(salvador_match));
386 memset(pMatchDepth + nMatches, 0, (nMatchesPerOffset - nMatches) * sizeof(unsigned short));
387 }
388
389 pMatch += nMatchesPerOffset;
390 pMatchDepth += nMatchesPerOffset;
391 }
392 }
This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.