git.y1.nz

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.