/* Copyright (C) 1994, 1995, 1998, 1999 artofcode LLC. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA, 02111-1307. */ /*$Id: sbwbs.c,v 1.2.6.1.2.1 2003/01/17 00:49:05 giles Exp $ */ /* Burrows/Wheeler block sorting compression filters */ #include "stdio_.h" #include "memory_.h" #include /* for qsort */ #include "gdebug.h" #include "strimpl.h" #include "sfilter.h" #include "sbwbs.h" /* ------ Common code for streams that buffer a block ------ */ private_st_buffered_state(); /* Initialize */ private void s_buffered_set_defaults(stream_state * st) { stream_buffered_state *const ss = (stream_buffered_state *) st; /* Clear pointers */ ss->buffer = 0; } private int s_buffered_no_block_init(stream_state * st) { stream_buffered_state *const ss = (stream_buffered_state *) st; ss->buffer = 0; ss->filling = true; ss->bpos = 0; return 0; } private int s_buffered_block_init(stream_state * st) { stream_buffered_state *const ss = (stream_buffered_state *) st; s_buffered_no_block_init(st); ss->buffer = gs_alloc_bytes(st->memory, ss->BlockSize, "buffer"); if (ss->buffer == 0) return ERRC; /****** WRONG ******/ return 0; } /* Continue filling the buffer if needed. */ /* Return 0 if the buffer isn't full yet, 1 if it is full or if */ /* we reached the end of input data. */ /* In the latter case, also set filling = false. */ /* Note that this procedure doesn't take pw as an argument. */ private int s_buffered_process(stream_state * st, stream_cursor_read * pr, bool last) { stream_buffered_state *const ss = (stream_buffered_state *) st; register const byte *p = pr->ptr; const byte *rlimit = pr->limit; uint count = rlimit - p; uint left = ss->bsize - ss->bpos; if (!ss->filling) return 1; if (left < count) count = left; if_debug3('w', "[w]buffering %d bytes to position %d, last = %s\n", count, ss->bpos, (last ? "true" : "false")); memcpy(ss->buffer + ss->bpos, p + 1, count); pr->ptr = p += count; ss->bpos += count; if (ss->bpos == ss->bsize || (p == rlimit && last)) { ss->filling = false; return 1; } return 0; } /* Release */ private void s_buffered_release(stream_state * st) { stream_buffered_state *const ss = (stream_buffered_state *) st; gs_free_object(st->memory, ss->buffer, "buffer"); } /* ------ Common code for Burrows/Wheeler block sorting filters ------ */ private_st_BWBS_state(); private void s_BWBS_release(P1(stream_state *)); /* Set default parameter values (actually, just clear pointers). */ private void s_BWBS_set_defaults(stream_state * st) { stream_BWBS_state *const ss = (stream_BWBS_state *) st; s_buffered_set_defaults(st); ss->offsets = 0; } /* Initialize */ private int bwbs_init(stream_state * st, uint osize) { stream_BWBS_state *const ss = (stream_BWBS_state *) st; int code; ss->bsize = ss->BlockSize; code = s_buffered_block_init(st); if (code != 0) return code; ss->offsets = (void *)gs_alloc_bytes(st->memory, osize, "BWBlockSort offsets"); if (ss->offsets == 0) { s_BWBS_release(st); return ERRC; /****** WRONG ******/ } ss->I = -1; /* haven't read I yet */ return 0; } /* Release the filter. */ private void s_BWBS_release(stream_state * st) { stream_BWBS_state *const ss = (stream_BWBS_state *) st; gs_free_object(st->memory, ss->offsets, "BWBlockSort offsets"); s_buffered_release(st); } /* ------ BWBlockSortEncode ------ */ /* Initialize */ private int s_BWBSE_init(stream_state * st) { stream_BWBS_state *const ss = (stream_BWBS_state *) st; return bwbs_init(st, ss->BlockSize * sizeof(int)); } /* Compare two rotated strings for sorting. */ private stream_BWBS_state *bwbs_compare_ss; private int bwbs_compare_rotations(const void *p1, const void *p2) { const byte *buffer = bwbs_compare_ss->buffer; const byte *s1 = buffer + *(const int *)p1; const byte *s2 = buffer + *(const int *)p2; const byte *start1; const byte *end; int swap; if (*s1 != *s2) return (*s1 < *s2 ? -1 : 1); if (s1 < s2) swap = 1; else { const byte *t = s1; s1 = s2; s2 = t; swap = -1; } start1 = s1; end = buffer + bwbs_compare_ss->N; for (s1++, s2++; s2 < end; s1++, s2++) if (*s1 != *s2) return (*s1 < *s2 ? -swap : swap); s2 = buffer; for (; s1 < end; s1++, s2++) if (*s1 != *s2) return (*s1 < *s2 ? -swap : swap); s1 = buffer; for (; s1 < start1; s1++, s2++) if (*s1 != *s2) return (*s1 < *s2 ? -swap : swap); return 0; } /* Sort the strings. */ private void bwbse_sort(const byte * buffer, uint * indices, int N) { offsets_full Cs; #define C Cs.v /* Sort the strings. We start with a radix sort. */ uint sum = 0, j, ch; memset(C, 0, sizeof(Cs)); for (j = 0; j < N; j++) C[buffer[j]]++; for (ch = 0; ch <= 255; ch++) { sum += C[ch]; C[ch] = sum - C[ch]; } for (j = 0; j < N; j++) indices[C[buffer[j]]++] = j; /* Now C[ch] = the number of strings that start */ /* with a character less than or equal to ch. */ sum = 0; /* qsort each bucket produced by the radix sort. */ for (ch = 0; ch <= 255; sum = C[ch], ch++) qsort(indices + sum, C[ch] - sum, sizeof(*indices), bwbs_compare_rotations); #undef C } /* Encode a buffer */ private int s_BWBSE_process(stream_state * st, stream_cursor_read * pr, stream_cursor_write * pw, bool last) { stream_BWBS_state *const ss = (stream_BWBS_state *) st; register byte *q = pw->ptr; byte *wlimit = pw->limit; uint wcount = wlimit - q; uint *indices = ss->offsets; if (ss->filling) { int status, j, N; byte *buffer = ss->buffer; if (wcount < sizeof(int) * 2) return 1; /* Continue filling the buffer. */ status = s_buffered_process(st, pr, last); if (!status) return 0; ss->N = N = ss->bpos; /* We reverse the string before encoding it, */ /* so it will come out of the decoder correctly. */ for (j = N / 2 - 1; j >= 0; j--) { byte *p0 = &buffer[j]; byte *p1 = &buffer[N - 1 - j]; byte b = *p0; *p0 = *p1; *p1 = b; } /* Save st in a static, because that's the only way */ /* we can pass it to the comparison procedure (ugh). */ bwbs_compare_ss = ss; /* Sort the strings. */ bwbse_sort(buffer, indices, N); /* Find the unrotated string. */ for (j = 0; j < N; j++) if (indices[j] == 0) { ss->I = j; break; } for (j = sizeof(int); --j >= 0;) *++q = (byte) (N >> (j * 8)); for (j = sizeof(int); --j >= 0;) *++q = (byte) (ss->I >> (j * 8)); ss->bpos = 0; } /* We're reading out of the buffer, writing the permuted string. */ while (q < wlimit && ss->bpos < ss->N) { int i = indices[ss->bpos++]; *++q = ss->buffer[(i == 0 ? ss->N - 1 : i - 1)]; } if (ss->bpos == ss->N) { ss->filling = true; ss->bpos = 0; } pw->ptr = q; if (q == wlimit) return 1; return 0; } /* Stream template */ const stream_template s_BWBSE_template = { &st_BWBS_state, s_BWBSE_init, s_BWBSE_process, sizeof(int) * 2, 1, s_BWBS_release, s_BWBS_set_defaults }; /* ------ BWBlockSortDecode ------ */ #define SHORT_OFFSETS #ifdef SHORT_OFFSETS /* * Letting S[0..N-1] be the data block before depermutation, we need * a table P[0..N-1] that maps the index i to O(S[i],i), where O(c,i) is * the number of occurrences of c in S before position i. * We observe that for a fixed c, O(c,i) is monotonic with i, * and falls in the range 0 .. i; consequently, if 0 <= i <= j, * 0 <= O(c,j) - O(c,i) <= j - i. Proceeding from this observation, * rather than allocate an entire int to each entry of P, * we construct three tables as follows: * P2[k,c] = O(c,k*65536) for k = 0 .. (N-1)/65536; * each entry requires an int. * P1[k,c] = O(c,k*4096) - P2[k/16,c] for k = 0 .. (N-1)/4096; * each entry falls in the range 0 .. 15*4096 and hence * requires 16 bits. * P0[i] = O(S[i],i) - P1[i/4096,S[i]] for i = 0 .. N-1; * each entry falls in the range 0 .. 4095 and hence * requires 12 bits. * Since the value we need in the decompression loop is actually * P[i] + C[S[i]], where C[c] is the sum of O(0,N) ... O(c-1,N), * we add C[c] into P2[k,c] for all k. */ /*typedef struct { uint v[256]; } offsets_full; *//* in sbwbs.h */ typedef struct { bits16 v[256]; } offsets_4k; #if arch_sizeof_int > 2 # define ceil_64k(n) (((n) + 0xffff) >> 16) #else # define ceil_64k(n) 1 #endif #define ceil_4k(n) (((n) + 0xfff) >> 12) #define offset_space(bsize)\ (ceil_64k(bsize) * sizeof(offsets_full) +\ ceil_4k(bsize) * sizeof(offsets_4k) +\ ((bsize + 1) >> 1) * 3) #else /* !SHORT_OFFSETS */ #define offset_space(bsize)\ (bsize * sizeof(int)) #endif /* (!)SHORT_OFFSETS */ /* Initialize */ private int s_BWBSD_init(stream_state * st) { stream_BWBS_state *const ss = (stream_BWBS_state *) st; uint bsize = ss->BlockSize; return bwbs_init(st, offset_space(bsize)); } /* Construct the decoding tables. */ #ifdef SHORT_OFFSETS private void bwbsd_construct_offsets(stream_BWBS_state * sst, offsets_full * po64k, offsets_4k * po4k, byte * po1, int N) { offsets_full Cs; #define C Cs.v uint i1; byte *b = sst->buffer; offsets_full *p2 = po64k - 1; offsets_4k *p1 = po4k; byte *p0 = po1; memset(C, 0, sizeof(Cs)); for (i1 = 0; i1 < ceil_4k(N); i1++, p1++) { int j; if (!(i1 & 15)) *++p2 = Cs; for (j = 0; j < 256; j++) p1->v[j] = C[j] - p2->v[j]; j = (N + 1 - (i1 << 12)) >> 1; if (j > 4096 / 2) j = 4096 / 2; for (; j > 0; j--, b += 2, p0 += 3) { byte b0 = b[0]; uint d0 = C[b0]++ - (p1->v[b0] + p2->v[b0]); byte b1 = b[1]; uint d1 = C[b1]++ - (p1->v[b1] + p2->v[b1]); p0[0] = d0 >> 4; p0[1] = (byte) ((d0 << 4) + (d1 >> 8)); p0[2] = (byte) d1; } } /* If the block length is odd, discount the extra byte. */ if (N & 1) C[sst->buffer[N]]--; /* Compute the cumulative totals in C. */ { int sum = 0, ch; for (ch = 0; ch <= 255; ch++) { sum += C[ch]; C[ch] = sum - C[ch]; } } /* Add the C values back into the 64K table, */ /* which saves an addition of C[b] in the decoding loop. */ { int i2, ch; for (p2 = po64k, i2 = ceil_64k(N); i2 > 0; p2++, i2--) for (ch = 0; ch < 256; ch++) p2->v[ch] += C[ch]; } #undef C } #else /* !SHORT_OFFSETS */ private void bwbsd_construct_offsets(stream_BWBS_state * sst, int *po, int N) { offsets_full Cs; #define C Cs.v uint i; byte *b = sst->buffer; int *p = po; memset(C, 0, sizeof(Cs)); for (i = 0; i < N; i++, p++, b++) *p = C[*b]++; /* Compute the cumulative totals in C. */ { int sum = 0, ch; for (ch = 0; ch <= 255; ch++) { sum += C[ch]; C[ch] = sum - C[ch]; } } /* Add the C values back into the offsets. */ for (i = 0, b = sst->buffer, p = po; i < N; i++, b++, p++) *p += C[*b]; #undef C } #endif /* (!)SHORT_OFFSETS */ /* Decode a buffer */ private int s_BWBSD_process(stream_state * st, stream_cursor_read * pr, stream_cursor_write * pw, bool last) { stream_BWBS_state *const ss = (stream_BWBS_state *) st; register const byte *p = pr->ptr; const byte *rlimit = pr->limit; uint count = rlimit - p; register byte *q = pw->ptr; byte *wlimit = pw->limit; #ifdef SHORT_OFFSETS uint BlockSize = ss->BlockSize; offsets_full *po64k = ss->offsets; offsets_4k *po4k = (offsets_4k *) (po64k + ceil_64k(BlockSize)); byte *po1 = (byte *) (po4k + ceil_4k(BlockSize)); #else /* !SHORT_OFFSETS */ int *po = ss->offsets; #endif /* (!)SHORT_OFFSETS */ if (ss->I < 0) { /* Read block parameters */ int I, N, j; if (count < sizeof(int) * 2) return 0; for (N = 0, j = 0; j < sizeof(int); j++) N = (N << 8) + *++p; for (I = 0, j = 0; j < sizeof(int); j++) I = (I << 8) + *++p; ss->N = N; ss->I = I; if_debug2('w', "[w]N=%d I=%d\n", N, I); pr->ptr = p; if (N < 0 || N > ss->BlockSize || I < 0 || I >= N) return ERRC; if (N == 0) return EOFC; count -= sizeof(int) * 2; ss->bpos = 0; ss->bsize = N; } if (ss->filling) { /* Continue filling the buffer. */ if (!s_buffered_process(st, pr, last)) return 0; /* Construct the inverse sort order. */ #ifdef SHORT_OFFSETS bwbsd_construct_offsets(ss, po64k, po4k, po1, ss->bsize); #else /* !SHORT_OFFSETS */ bwbsd_construct_offsets(ss, po, ss->bsize); #endif /* (!)SHORT_OFFSETS */ ss->bpos = 0; ss->i = ss->I; } /* We're reading out of the buffer. */ while (q < wlimit && ss->bpos < ss->bsize) { int i = ss->i; byte b = ss->buffer[i]; #ifdef SHORT_OFFSETS uint d; const byte *pd = &po1[(i >> 1) + i]; *++q = b; if (!(i & 1)) d = ((uint) pd[0] << 4) + (pd[1] >> 4); else d = ((pd[0] & 0xf) << 8) + pd[1]; ss->i = po64k[i >> 16].v[b] + po4k[i >> 12].v[b] + d; #else /* !SHORT_OFFSETS */ *++q = b; ss->i = po[i]; #endif /* (!)SHORT_OFFSETS */ ss->bpos++; } if (ss->bpos == ss->bsize) { ss->I = -1; ss->filling = true; } pw->ptr = q; if (q == wlimit) return 1; return 0; } /* Stream template */ const stream_template s_BWBSD_template = { &st_BWBS_state, s_BWBSD_init, s_BWBSD_process, 1, sizeof(int) * 2, s_BWBS_release, s_BWBS_set_defaults };