summaryrefslogtreecommitdiff
path: root/puff.c
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-07-29 00:49:47 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-07-29 00:49:47 +0200
commit302897c5e8d61db9107505ba929f9abfc197896d (patch)
tree55c1504dcb03727b22a9af6b0c420fc65c039298 /puff.c
parent46e482552c35456ddc836720df0a44ba25f60544 (diff)
downloadclectio-302897c5e8d61db9107505ba929f9abfc197896d.tar.gz
clectio-302897c5e8d61db9107505ba929f9abfc197896d.zip
perf: DEFLATE (puff) + -no-pie -> OF binary 879 KB -> 662 KB (-25%)
Two size wins: - Replace the home-grown LZSS (2.0x) with DEFLATE: mktext gzips the text and strips the gzip container to a raw DEFLATE stream; clectio decompresses it with puff, Mark Adler's public-domain reference inflate (~200 lines, verified byte-identical to gunzip on the OF/EF text, repetitive, random and empty inputs). Text blob 671 KB -> 492 KB (OF), 164 KB -> 132 KB (EF), 2.7x. - Build with -no-pie: clectio is a local CLI reading static embedded data with no attack surface, so it needs no PIE/ASLR. The linker then bakes the string- table pointers in at link time instead of emitting ~57 KB of runtime relocations. Net: OF 879 -> 662 KB, EF 266 -> 214 KB. Verified byte-identical to lectio. The default build still needs only a compiler + libc; a CORPUS= rebuild now also needs gzip. lz.h removed. Claude-Session: https://claude.ai/code/session_01S1CD1u3tgSS4xNu6WHfp5a
Diffstat (limited to 'puff.c')
-rw-r--r--puff.c282
1 files changed, 282 insertions, 0 deletions
diff --git a/puff.c b/puff.c
new file mode 100644
index 0000000..232438a
--- /dev/null
+++ b/puff.c
@@ -0,0 +1,282 @@
+/* puff.c -- a simple inflate (DEFLATE decompressor). Public domain,
+ * by Mark Adler (zlib/contrib/puff), trimmed to the decode path clectio needs.
+ */
+#include "puff.h"
+#include <setjmp.h>
+
+#define MAXBITS 15 /* maximum bits in a code */
+#define MAXLCODES 286 /* maximum number of literal/length codes */
+#define MAXDCODES 30 /* maximum number of distance codes */
+#define MAXCODES (MAXLCODES + MAXDCODES)
+#define FIXLCODES 288 /* number of fixed literal/length codes */
+#define NIL ((unsigned char *)0)
+
+struct state {
+ unsigned char *out; /* output buffer */
+ unsigned long outlen; /* available space at out */
+ unsigned long outcnt; /* bytes written to out so far */
+ const unsigned char *in; /* input buffer */
+ unsigned long inlen; /* available input at in */
+ unsigned long incnt; /* bytes read so far */
+ int bitbuf; /* bit buffer */
+ int bitcnt; /* number of bits in bit buffer */
+ jmp_buf env; /* for premature-EOF longjmp */
+};
+
+static int bits(struct state *s, int need) {
+ long val = s->bitbuf;
+ while (s->bitcnt < need) {
+ if (s->incnt == s->inlen)
+ longjmp(s->env, 1);
+ val |= (long)(s->in[s->incnt++]) << s->bitcnt;
+ s->bitcnt += 8;
+ }
+ s->bitbuf = (int)(val >> need);
+ s->bitcnt -= need;
+ return (int)(val & ((1L << need) - 1));
+}
+
+static int stored(struct state *s) {
+ unsigned len;
+ s->bitbuf = 0;
+ s->bitcnt = 0;
+ if (s->incnt + 4 > s->inlen)
+ return 2;
+ len = s->in[s->incnt++];
+ len |= s->in[s->incnt++] << 8;
+ if (s->in[s->incnt++] != (~len & 0xff) ||
+ s->in[s->incnt++] != ((~len >> 8) & 0xff))
+ return -2;
+ if (s->incnt + len > s->inlen)
+ return 2;
+ if (s->out != NIL) {
+ if (s->outcnt + len > s->outlen)
+ return 1;
+ while (len--)
+ s->out[s->outcnt++] = s->in[s->incnt++];
+ } else {
+ s->outcnt += len;
+ s->incnt += len;
+ }
+ return 0;
+}
+
+struct huffman {
+ short *count; /* number of symbols of each length */
+ short *symbol; /* canonically ordered symbols */
+};
+
+static int decode(struct state *s, const struct huffman *h) {
+ int len, code = 0, first = 0, count, index = 0;
+ for (len = 1; len <= MAXBITS; len++) {
+ code |= bits(s, 1);
+ count = h->count[len];
+ if (code - count < first)
+ return h->symbol[index + (code - first)];
+ index += count;
+ first += count;
+ first <<= 1;
+ code <<= 1;
+ }
+ return -10;
+}
+
+static int construct(struct huffman *h, const short *length, int n) {
+ int symbol, len, left;
+ short offs[MAXBITS + 1];
+ for (len = 0; len <= MAXBITS; len++)
+ h->count[len] = 0;
+ for (symbol = 0; symbol < n; symbol++)
+ (h->count[length[symbol]])++;
+ if (h->count[0] == n)
+ return 0;
+ left = 1;
+ for (len = 1; len <= MAXBITS; len++) {
+ left <<= 1;
+ left -= h->count[len];
+ if (left < 0)
+ return left;
+ }
+ offs[1] = 0;
+ for (len = 1; len < MAXBITS; len++)
+ offs[len + 1] = offs[len] + h->count[len];
+ for (symbol = 0; symbol < n; symbol++)
+ if (length[symbol] != 0)
+ h->symbol[offs[length[symbol]]++] = (short)symbol;
+ return left;
+}
+
+static int codes(struct state *s, const struct huffman *lencode,
+ const struct huffman *distcode) {
+ int symbol, len;
+ unsigned dist;
+ static const short lens[29] = {
+ 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
+ 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258};
+ static const short lext[29] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
+ 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0};
+ static const short dists[30] = {
+ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
+ 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
+ 8193, 12289, 16385, 24577};
+ static const short dext[30] = {
+ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
+ 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13};
+ do {
+ symbol = decode(s, lencode);
+ if (symbol < 0)
+ return symbol;
+ if (symbol < 256) {
+ if (s->out != NIL) {
+ if (s->outcnt == s->outlen)
+ return 1;
+ s->out[s->outcnt] = (unsigned char)symbol;
+ }
+ s->outcnt++;
+ } else if (symbol > 256) {
+ symbol -= 257;
+ if (symbol >= 29)
+ return -10;
+ len = lens[symbol] + bits(s, lext[symbol]);
+ symbol = decode(s, distcode);
+ if (symbol < 0)
+ return symbol;
+ dist = (unsigned)dists[symbol] + (unsigned)bits(s, dext[symbol]);
+ if (dist > s->outcnt)
+ return -11;
+ if (s->out != NIL) {
+ if (s->outcnt + len > s->outlen)
+ return 1;
+ while (len--) {
+ s->out[s->outcnt] = s->out[s->outcnt - dist];
+ s->outcnt++;
+ }
+ } else
+ s->outcnt += len;
+ }
+ } while (symbol != 256);
+ return 0;
+}
+
+static int fixed(struct state *s) {
+ static int virgin = 1;
+ static short lencnt[MAXBITS + 1], lensym[FIXLCODES];
+ static short distcnt[MAXBITS + 1], distsym[MAXDCODES];
+ static struct huffman lencode, distcode;
+ if (virgin) {
+ int symbol;
+ short lengths[FIXLCODES];
+ lencode.count = lencnt;
+ lencode.symbol = lensym;
+ distcode.count = distcnt;
+ distcode.symbol = distsym;
+ for (symbol = 0; symbol < 144; symbol++)
+ lengths[symbol] = 8;
+ for (; symbol < 256; symbol++)
+ lengths[symbol] = 9;
+ for (; symbol < 280; symbol++)
+ lengths[symbol] = 7;
+ for (; symbol < FIXLCODES; symbol++)
+ lengths[symbol] = 8;
+ construct(&lencode, lengths, FIXLCODES);
+ for (symbol = 0; symbol < MAXDCODES; symbol++)
+ lengths[symbol] = 5;
+ construct(&distcode, lengths, MAXDCODES);
+ virgin = 0;
+ }
+ return codes(s, &lencode, &distcode);
+}
+
+static int dynamic(struct state *s) {
+ int nlen, ndist, ncode, index, err;
+ short lengths[MAXCODES];
+ short lencnt[MAXBITS + 1], lensym[MAXLCODES];
+ short distcnt[MAXBITS + 1], distsym[MAXDCODES];
+ struct huffman lencode, distcode;
+ static const short order[19] = {
+ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
+ lencode.count = lencnt;
+ lencode.symbol = lensym;
+ distcode.count = distcnt;
+ distcode.symbol = distsym;
+ nlen = bits(s, 5) + 257;
+ ndist = bits(s, 5) + 1;
+ ncode = bits(s, 4) + 4;
+ if (nlen > MAXLCODES || ndist > MAXDCODES)
+ return -3;
+ for (index = 0; index < ncode; index++)
+ lengths[order[index]] = (short)bits(s, 3);
+ for (; index < 19; index++)
+ lengths[order[index]] = 0;
+ err = construct(&lencode, lengths, 19);
+ if (err != 0)
+ return -4;
+ index = 0;
+ while (index < nlen + ndist) {
+ int symbol, len;
+ symbol = decode(s, &lencode);
+ if (symbol < 0)
+ return symbol;
+ if (symbol < 16)
+ lengths[index++] = (short)symbol;
+ else {
+ len = 0;
+ if (symbol == 16) {
+ if (index == 0)
+ return -5;
+ len = lengths[index - 1];
+ symbol = 3 + bits(s, 2);
+ } else if (symbol == 17)
+ symbol = 3 + bits(s, 3);
+ else
+ symbol = 11 + bits(s, 7);
+ if (index + symbol > nlen + ndist)
+ return -6;
+ while (symbol--)
+ lengths[index++] = (short)len;
+ }
+ }
+ if (lengths[256] == 0)
+ return -9;
+ err = construct(&lencode, lengths, nlen);
+ if (err && (err < 0 || nlen != lencode.count[0] + lencode.count[1]))
+ return -7;
+ err = construct(&distcode, lengths + nlen, ndist);
+ if (err && (err < 0 || ndist != distcode.count[0] + distcode.count[1]))
+ return -8;
+ return codes(s, &lencode, &distcode);
+}
+
+int puff(unsigned char *dest, unsigned long *destlen,
+ const unsigned char *source, unsigned long *sourcelen) {
+ struct state s;
+ int last, type, err;
+ s.out = dest;
+ s.outlen = *destlen;
+ s.outcnt = 0;
+ s.in = source;
+ s.inlen = *sourcelen;
+ s.incnt = 0;
+ s.bitbuf = 0;
+ s.bitcnt = 0;
+ if (setjmp(s.env) != 0)
+ err = 2;
+ else {
+ do {
+ last = bits(&s, 1);
+ type = bits(&s, 2);
+ err = type == 0 ? stored(&s)
+ : type == 1 ? fixed(&s)
+ : type == 2 ? dynamic(&s)
+ : -1;
+ if (err != 0)
+ break;
+ } while (!last);
+ }
+ if (err <= 0) {
+ *destlen = s.outcnt;
+ *sourcelen = s.incnt;
+ }
+ return err;
+}