xref: /plan9/sys/lib/dist/cmd/bflz.c (revision 0a75e54a195f699ff0426de1f8ba80514066a83b)
19a747e4fSDavid du Colombier /*
29a747e4fSDavid du Colombier  * Extraordinarily brute force Lempel & Ziv-like
39a747e4fSDavid du Colombier  * compressor.  The input file must fit in memory
49a747e4fSDavid du Colombier  * during compression, and the output file will
59a747e4fSDavid du Colombier  * be reconstructed in memory during decompression.
69a747e4fSDavid du Colombier  * We search for large common sequences and use a
79a747e4fSDavid du Colombier  * greedy algorithm to choose which sequence gets
89a747e4fSDavid du Colombier  * compressed first.
99a747e4fSDavid du Colombier  *
109a747e4fSDavid du Colombier  * Files begin with "BLZ\n" and a 32-bit uncompressed file length.
119a747e4fSDavid du Colombier  *
129a747e4fSDavid du Colombier  * Output format is a series of blocks followed by
139a747e4fSDavid du Colombier  * a raw data section.  Each block begins with a 32-bit big-endian
149a747e4fSDavid du Colombier  * number.  The top bit is type and the next 31 bits
159a747e4fSDavid du Colombier  * are uncompressed size.  Type is one of
169a747e4fSDavid du Colombier  *	0 - use raw data for this length
179a747e4fSDavid du Colombier  *	1 - a 32-bit offset follows
189a747e4fSDavid du Colombier  * After the blocks come the raw data.  (The end of the blocks can be
199a747e4fSDavid du Colombier  * noted by summing block lengths until you reach the file length.)
209a747e4fSDavid du Colombier  */
219a747e4fSDavid du Colombier 
229a747e4fSDavid du Colombier #include <u.h>
239a747e4fSDavid du Colombier #include <libc.h>
249a747e4fSDavid du Colombier #include <bio.h>
259a747e4fSDavid du Colombier 
263ff48bf5SDavid du Colombier #define malloc sbrk
273ff48bf5SDavid du Colombier 
289a747e4fSDavid du Colombier int minrun = 16;
299a747e4fSDavid du Colombier int win = 16;
309a747e4fSDavid du Colombier ulong outn;
319a747e4fSDavid du Colombier int verbose;
329a747e4fSDavid du Colombier int mindist;
339a747e4fSDavid du Colombier 
349a747e4fSDavid du Colombier enum { Prime = 16777213 };	/* smallest prime < 2^24 (so p*256+256 < 2^32) */
353ff48bf5SDavid du Colombier enum { NOFF = 3 };
369a747e4fSDavid du Colombier 
379a747e4fSDavid du Colombier Biobuf bout;
389a747e4fSDavid du Colombier ulong length;
399a747e4fSDavid du Colombier uchar *data;
409a747e4fSDavid du Colombier ulong sum32(ulong, void*, long);
419a747e4fSDavid du Colombier uchar *odat;
429a747e4fSDavid du Colombier int nodat;
439a747e4fSDavid du Colombier int nraw;
449a747e4fSDavid du Colombier int rawstart;
459a747e4fSDavid du Colombier int acct;
469a747e4fSDavid du Colombier int zlength;
479a747e4fSDavid du Colombier int maxchain;
489a747e4fSDavid du Colombier int maxrle[256];
493ff48bf5SDavid du Colombier int nnew;
509a747e4fSDavid du Colombier 
519a747e4fSDavid du Colombier typedef struct Node Node;
529a747e4fSDavid du Colombier struct Node {
539a747e4fSDavid du Colombier 	Node *link;
543ff48bf5SDavid du Colombier 	ulong key;
553ff48bf5SDavid du Colombier 	ulong offset[NOFF];
569a747e4fSDavid du Colombier };
579a747e4fSDavid du Colombier 
589a747e4fSDavid du Colombier Node *nodepool;
599a747e4fSDavid du Colombier int nnodepool;
609a747e4fSDavid du Colombier 
613ff48bf5SDavid du Colombier Node **hash;
623ff48bf5SDavid du Colombier uint nhash;
633ff48bf5SDavid du Colombier 
643ff48bf5SDavid du Colombier uint maxlen;
653ff48bf5SDavid du Colombier uint maxsame;
663ff48bf5SDavid du Colombier uint replacesame = 8*1024*1024;
673ff48bf5SDavid du Colombier 
683ff48bf5SDavid du Colombier Node *freelist, **freeend;
693ff48bf5SDavid du Colombier uint nalloc;
709a747e4fSDavid du Colombier 
719a747e4fSDavid du Colombier Node*
allocnode(void)729a747e4fSDavid du Colombier allocnode(void)
739a747e4fSDavid du Colombier {
743ff48bf5SDavid du Colombier 	int i;
753ff48bf5SDavid du Colombier 	Node *n;
763ff48bf5SDavid du Colombier 
773ff48bf5SDavid du Colombier 	if(nnodepool == 0){
783ff48bf5SDavid du Colombier 		nnodepool = 256*1024;
793ff48bf5SDavid du Colombier 		nodepool = malloc(sizeof(Node)*nnodepool);
803ff48bf5SDavid du Colombier 	}
813ff48bf5SDavid du Colombier 	if(freelist){
823ff48bf5SDavid du Colombier 		n = freelist;
833ff48bf5SDavid du Colombier 		freelist = n->link;
843ff48bf5SDavid du Colombier 		return n;
859a747e4fSDavid du Colombier 	}
869a747e4fSDavid du Colombier 	assert(nnodepool > 0);
873ff48bf5SDavid du Colombier 	nalloc++;
883ff48bf5SDavid du Colombier 	n = &nodepool[--nnodepool];
893ff48bf5SDavid du Colombier 	for(i=0; i<NOFF; i++)
903ff48bf5SDavid du Colombier 		n->offset[i] = -1;
913ff48bf5SDavid du Colombier 
923ff48bf5SDavid du Colombier 	return n;
933ff48bf5SDavid du Colombier }
943ff48bf5SDavid du Colombier 
953ff48bf5SDavid du Colombier void
freenode(Node * n)963ff48bf5SDavid du Colombier freenode(Node *n)
973ff48bf5SDavid du Colombier {
983ff48bf5SDavid du Colombier 	if(freelist == nil)
993ff48bf5SDavid du Colombier 		freelist = n;
1003ff48bf5SDavid du Colombier 	else
1013ff48bf5SDavid du Colombier 		*freeend = n;
1023ff48bf5SDavid du Colombier 	freeend = &n->link;
1033ff48bf5SDavid du Colombier 	n->link = nil;
1049a747e4fSDavid du Colombier }
1059a747e4fSDavid du Colombier 
1069a747e4fSDavid du Colombier Node**
llookup(ulong key)1073ff48bf5SDavid du Colombier llookup(ulong key)
1089a747e4fSDavid du Colombier {
1093ff48bf5SDavid du Colombier 	uint c;
1103ff48bf5SDavid du Colombier 	Node **l, **top, *n;
1119a747e4fSDavid du Colombier 
1123ff48bf5SDavid du Colombier 	if(nhash == 0){
1133ff48bf5SDavid du Colombier 		uint x;
1143ff48bf5SDavid du Colombier 
1153ff48bf5SDavid du Colombier 		x = length/8;
1163ff48bf5SDavid du Colombier 		for(nhash=1; nhash<x; nhash<<=1)
1173ff48bf5SDavid du Colombier 			;
1183ff48bf5SDavid du Colombier 		hash = sbrk(sizeof(Node*)*nhash);
1199a747e4fSDavid du Colombier 	}
1203ff48bf5SDavid du Colombier 
1213ff48bf5SDavid du Colombier 	top = &hash[key&(nhash-1)];
1223ff48bf5SDavid du Colombier 	c = 0;
1233ff48bf5SDavid du Colombier 	for(l=top; *l; l=&(*l)->link){
1243ff48bf5SDavid du Colombier 		c++;
1253ff48bf5SDavid du Colombier 		if((*l)->key == key){
1263ff48bf5SDavid du Colombier 			/* move to front */
1273ff48bf5SDavid du Colombier 			n = *l;
1283ff48bf5SDavid du Colombier 			*l = n->link;
1293ff48bf5SDavid du Colombier 			n->link = *top;
1303ff48bf5SDavid du Colombier 			*top = n;
1313ff48bf5SDavid du Colombier 			return top;
1329a747e4fSDavid du Colombier 		}
1339a747e4fSDavid du Colombier 	}
1343ff48bf5SDavid du Colombier 	if(c > maxlen)
1353ff48bf5SDavid du Colombier 		maxlen = c;
1369a747e4fSDavid du Colombier 	return l;
1379a747e4fSDavid du Colombier }
1389a747e4fSDavid du Colombier 
1399a747e4fSDavid du Colombier Node*
lookup(ulong key)1409a747e4fSDavid du Colombier lookup(ulong key)
1419a747e4fSDavid du Colombier {
1423ff48bf5SDavid du Colombier 	return *llookup(key);
1433ff48bf5SDavid du Colombier }
1443ff48bf5SDavid du Colombier 
1453ff48bf5SDavid du Colombier void
insertnode(ulong key,ulong offset)1463ff48bf5SDavid du Colombier insertnode(ulong key, ulong offset)
1473ff48bf5SDavid du Colombier {
1483ff48bf5SDavid du Colombier 	int i;
1493ff48bf5SDavid du Colombier 	Node *n, **l;
1503ff48bf5SDavid du Colombier 
1513ff48bf5SDavid du Colombier 	l = llookup(key);
1523ff48bf5SDavid du Colombier 	if(*l == nil){
1533ff48bf5SDavid du Colombier 		if(l==&hash[key&(nhash-1)])
1543ff48bf5SDavid du Colombier 			nnew++;
1553ff48bf5SDavid du Colombier 		*l = allocnode();
1563ff48bf5SDavid du Colombier 		(*l)->key = key;
1573ff48bf5SDavid du Colombier 	}
1583ff48bf5SDavid du Colombier 	n = *l;
1593ff48bf5SDavid du Colombier 
1603ff48bf5SDavid du Colombier 	/* add or replace last */
1613ff48bf5SDavid du Colombier 	for(i=0; i<NOFF-1 && n->offset[i]!=-1; i++)
1623ff48bf5SDavid du Colombier 		;
1633ff48bf5SDavid du Colombier 	n->offset[i] = offset;
1649a747e4fSDavid du Colombier }
1659a747e4fSDavid du Colombier 
1669a747e4fSDavid du Colombier void
Bputint(Biobufhdr * b,int n)1679a747e4fSDavid du Colombier Bputint(Biobufhdr *b, int n)
1689a747e4fSDavid du Colombier {
1699a747e4fSDavid du Colombier 	uchar p[4];
1709a747e4fSDavid du Colombier 
1719a747e4fSDavid du Colombier 	p[0] = n>>24;
1729a747e4fSDavid du Colombier 	p[1] = n>>16;
1739a747e4fSDavid du Colombier 	p[2] = n>>8;
1749a747e4fSDavid du Colombier 	p[3] = n;
1759a747e4fSDavid du Colombier 	Bwrite(b, p, 4);
1769a747e4fSDavid du Colombier }
1779a747e4fSDavid du Colombier 
1789a747e4fSDavid du Colombier void
flushraw(void)1799a747e4fSDavid du Colombier flushraw(void)
1809a747e4fSDavid du Colombier {
1819a747e4fSDavid du Colombier 	if(nraw){
1829a747e4fSDavid du Colombier 		if(verbose)
1839a747e4fSDavid du Colombier 			fprint(2, "Raw %d+%d\n", rawstart, nraw);
1849a747e4fSDavid du Colombier 		zlength += 4+nraw;
1859a747e4fSDavid du Colombier 		Bputint(&bout, (1<<31)|nraw);
1869a747e4fSDavid du Colombier 		memmove(odat+nodat, data+rawstart, nraw);
1879a747e4fSDavid du Colombier 		nodat += nraw;
1889a747e4fSDavid du Colombier 		nraw = 0;
1899a747e4fSDavid du Colombier 	}
1909a747e4fSDavid du Colombier }
1919a747e4fSDavid du Colombier 
1929a747e4fSDavid du Colombier int
rawbyte(int i)1939a747e4fSDavid du Colombier rawbyte(int i)
1949a747e4fSDavid du Colombier {
1959a747e4fSDavid du Colombier 	assert(acct == i);
1969a747e4fSDavid du Colombier 	if(nraw == 0)
1979a747e4fSDavid du Colombier 		rawstart = i;
1989a747e4fSDavid du Colombier 	acct++;
1999a747e4fSDavid du Colombier 	nraw++;
2009a747e4fSDavid du Colombier 	return 1;
2019a747e4fSDavid du Colombier }
2029a747e4fSDavid du Colombier 
2039a747e4fSDavid du Colombier int
refblock(int i,int len,int off)2049a747e4fSDavid du Colombier refblock(int i, int len, int off)
2059a747e4fSDavid du Colombier {
2069a747e4fSDavid du Colombier 	assert(acct == i);
2079a747e4fSDavid du Colombier 	acct += len;
2089a747e4fSDavid du Colombier 	if(nraw)
2099a747e4fSDavid du Colombier 		flushraw();
2109a747e4fSDavid du Colombier 	if(verbose)
2119a747e4fSDavid du Colombier 		fprint(2, "Copy %d+%d from %d\n", i, len, off);
2129a747e4fSDavid du Colombier 	Bputint(&bout, len);
2139a747e4fSDavid du Colombier 	Bputint(&bout, off);
2149a747e4fSDavid du Colombier 	zlength += 4+4;
2159a747e4fSDavid du Colombier 	return len;
2169a747e4fSDavid du Colombier }
2179a747e4fSDavid du Colombier 
2189a747e4fSDavid du Colombier int
cmprun(uchar * a,uchar * b,int len)2199a747e4fSDavid du Colombier cmprun(uchar *a, uchar *b, int len)
2209a747e4fSDavid du Colombier {
2219a747e4fSDavid du Colombier 	int i;
2229a747e4fSDavid du Colombier 
2239a747e4fSDavid du Colombier 	if(a==b)
2249a747e4fSDavid du Colombier 		return 0;
2259a747e4fSDavid du Colombier 	for(i=0; i<len && a[i]==b[i]; i++)
2269a747e4fSDavid du Colombier 		;
2279a747e4fSDavid du Colombier 	return i;
2289a747e4fSDavid du Colombier }
2299a747e4fSDavid du Colombier 
2309a747e4fSDavid du Colombier int
countrle(uchar * a)2319a747e4fSDavid du Colombier countrle(uchar *a)
2329a747e4fSDavid du Colombier {
233*0a75e54aSDavid du Colombier 	uchar a0;
234*0a75e54aSDavid du Colombier 	uchar *p;
2359a747e4fSDavid du Colombier 
236*0a75e54aSDavid du Colombier 	p = a;
237*0a75e54aSDavid du Colombier 	a0 = *p;
238*0a75e54aSDavid du Colombier 	while(*++p == a0)
2399a747e4fSDavid du Colombier 		;
240*0a75e54aSDavid du Colombier 	return p - a;
2419a747e4fSDavid du Colombier }
2429a747e4fSDavid du Colombier 
2439a747e4fSDavid du Colombier void
compress(void)2449a747e4fSDavid du Colombier compress(void)
2459a747e4fSDavid du Colombier {
2463ff48bf5SDavid du Colombier 	int best, i, j, o, rle, run, maxrun, maxoff;
2479a747e4fSDavid du Colombier 	ulong sum;
2489a747e4fSDavid du Colombier 	Node *n;
2499a747e4fSDavid du Colombier 
2509a747e4fSDavid du Colombier 	sum = 0;
2519a747e4fSDavid du Colombier 	for(i=0; i<win && i<length; i++)
2529a747e4fSDavid du Colombier 		sum = (sum*256+data[i])%Prime;
2539a747e4fSDavid du Colombier 	for(i=0; i<length-win; ){
2549a747e4fSDavid du Colombier 		maxrun = 0;
2559a747e4fSDavid du Colombier 		maxoff = 0;
2569a747e4fSDavid du Colombier 		if(verbose)
2579a747e4fSDavid du Colombier 			fprint(2, "look %.6lux\n", sum);
2583ff48bf5SDavid du Colombier 		n = lookup(sum);
2593ff48bf5SDavid du Colombier 		if(n){
2603ff48bf5SDavid du Colombier 			best = -1;
2613ff48bf5SDavid du Colombier 			for(o=0; o<NOFF; o++){
2623ff48bf5SDavid du Colombier 				if(n->offset[o] == -1)
2633ff48bf5SDavid du Colombier 					break;
2643ff48bf5SDavid du Colombier 				run = cmprun(data+i, data+n->offset[o], length-i);
2653ff48bf5SDavid du Colombier 				if(run > maxrun && n->offset[o]+mindist < i){
2669a747e4fSDavid du Colombier 					maxrun = run;
2673ff48bf5SDavid du Colombier 					maxoff = n->offset[o];
2683ff48bf5SDavid du Colombier 					best = o;
2699a747e4fSDavid du Colombier 				}
2709a747e4fSDavid du Colombier 			}
2713ff48bf5SDavid du Colombier 			if(best > 0){
2723ff48bf5SDavid du Colombier 				o = n->offset[best];
2733ff48bf5SDavid du Colombier 				for(j=best; j>0; j--)
2743ff48bf5SDavid du Colombier 					n->offset[j] = n->offset[j-1];
2753ff48bf5SDavid du Colombier 				n->offset[0] = o;
2769a747e4fSDavid du Colombier 			}
2773ff48bf5SDavid du Colombier 		}
2783ff48bf5SDavid du Colombier 
2799a747e4fSDavid du Colombier 		if(maxrun >= minrun)
2809a747e4fSDavid du Colombier 			j = i+refblock(i, maxrun, maxoff);
2819a747e4fSDavid du Colombier 		else
2829a747e4fSDavid du Colombier 			j = i+rawbyte(i);
2839a747e4fSDavid du Colombier 		for(; i<j; i++){
2849a747e4fSDavid du Colombier 			/* avoid huge chains from large runs of same byte */
2859a747e4fSDavid du Colombier 			rle = countrle(data+i);
2869a747e4fSDavid du Colombier 			if(rle<4)
2879a747e4fSDavid du Colombier 				insertnode(sum, i);
2889a747e4fSDavid du Colombier 			else if(rle>maxrle[data[i]]){
2899a747e4fSDavid du Colombier 				maxrle[data[i]] = rle;
2909a747e4fSDavid du Colombier 				insertnode(sum, i);
2919a747e4fSDavid du Colombier 			}
2929a747e4fSDavid du Colombier 			sum = (sum*256+data[i+win]) % Prime;
2939a747e4fSDavid du Colombier 			sum = (sum + data[i]*outn) % Prime;
2949a747e4fSDavid du Colombier 		}
2959a747e4fSDavid du Colombier 	}
2969a747e4fSDavid du Colombier 	/* could do better here */
2979a747e4fSDavid du Colombier 	for(; i<length; i++)
2989a747e4fSDavid du Colombier 		rawbyte(i);
2999a747e4fSDavid du Colombier 	flushraw();
3009a747e4fSDavid du Colombier }
3019a747e4fSDavid du Colombier 
3029a747e4fSDavid du Colombier void
usage(void)3039a747e4fSDavid du Colombier usage(void)
3049a747e4fSDavid du Colombier {
3053ff48bf5SDavid du Colombier 	fprint(2, "usage: bflz [-n winsize] [file]\n");
3069a747e4fSDavid du Colombier 	exits("usage");
3079a747e4fSDavid du Colombier }
3089a747e4fSDavid du Colombier 
3099a747e4fSDavid du Colombier void
main(int argc,char ** argv)3109a747e4fSDavid du Colombier main(int argc, char **argv)
3119a747e4fSDavid du Colombier {
3129a747e4fSDavid du Colombier 	int fd, i, n;
3139a747e4fSDavid du Colombier 	char buf[10485760];
3149a747e4fSDavid du Colombier 
3159a747e4fSDavid du Colombier 	ARGBEGIN{
3169a747e4fSDavid du Colombier 	case 'd':
3179a747e4fSDavid du Colombier 		verbose = 1;
3189a747e4fSDavid du Colombier 		break;
3193ff48bf5SDavid du Colombier 	case 's':
3203ff48bf5SDavid du Colombier 		replacesame = atoi(EARGF(usage()));
3213ff48bf5SDavid du Colombier 		break;
3229a747e4fSDavid du Colombier 	case 'm':
3239a747e4fSDavid du Colombier 		mindist = atoi(EARGF(usage()));
3249a747e4fSDavid du Colombier 		break;
3259a747e4fSDavid du Colombier 	case 'n':
3269a747e4fSDavid du Colombier 		win = atoi(EARGF(usage()));
3279a747e4fSDavid du Colombier 		minrun = win;
3289a747e4fSDavid du Colombier 		break;
3299a747e4fSDavid du Colombier 	default:
3309a747e4fSDavid du Colombier 		usage();
3319a747e4fSDavid du Colombier 	}ARGEND
3329a747e4fSDavid du Colombier 
3339a747e4fSDavid du Colombier 	switch(argc){
3349a747e4fSDavid du Colombier 	default:
3359a747e4fSDavid du Colombier 		usage();
3369a747e4fSDavid du Colombier 	case 0:
3379a747e4fSDavid du Colombier 		fd = 0;
3389a747e4fSDavid du Colombier 		break;
3399a747e4fSDavid du Colombier 	case 1:
3409a747e4fSDavid du Colombier 		if((fd = open(argv[0], OREAD)) < 0)
3419a747e4fSDavid du Colombier 			sysfatal("open %s: %r", argv[0]);
3429a747e4fSDavid du Colombier 		break;
3439a747e4fSDavid du Colombier 	}
3449a747e4fSDavid du Colombier 
3459a747e4fSDavid du Colombier 	while((n = readn(fd, buf, sizeof buf)) > 0){
3469a747e4fSDavid du Colombier 		data = realloc(data, length+n);
3479a747e4fSDavid du Colombier 		if(data == nil)
3489a747e4fSDavid du Colombier 			sysfatal("realloc: %r");
3499a747e4fSDavid du Colombier 		memmove(data+length, buf, n);
3509a747e4fSDavid du Colombier 		length += n;
3519a747e4fSDavid du Colombier 		if(n < sizeof buf)
3529a747e4fSDavid du Colombier 			break;
3539a747e4fSDavid du Colombier 	}
3549a747e4fSDavid du Colombier 	odat = malloc(length);
3559a747e4fSDavid du Colombier 	if(odat == nil)
3569a747e4fSDavid du Colombier 		sysfatal("malloc: %r");
3579a747e4fSDavid du Colombier 
3589a747e4fSDavid du Colombier 	Binit(&bout, 1, OWRITE);
3599a747e4fSDavid du Colombier 	Bprint(&bout, "BLZ\n");
3609a747e4fSDavid du Colombier 	Bputint(&bout, length);
3619a747e4fSDavid du Colombier 	outn = 1;
3629a747e4fSDavid du Colombier 	for(i=0; i<win; i++)
3639a747e4fSDavid du Colombier 		outn = (outn * 256) % Prime;
3649a747e4fSDavid du Colombier 
3659a747e4fSDavid du Colombier 	if(verbose)
3669a747e4fSDavid du Colombier 		fprint(2, "256^%d = %.6lux\n", win, outn);
3679a747e4fSDavid du Colombier 	outn = Prime - outn;
3689a747e4fSDavid du Colombier 	if(verbose)
3699a747e4fSDavid du Colombier 		fprint(2, "outn = %.6lux\n", outn);
3709a747e4fSDavid du Colombier 
3719a747e4fSDavid du Colombier 	compress();
3729a747e4fSDavid du Colombier 	Bwrite(&bout, odat, nodat);
3739a747e4fSDavid du Colombier 	Bterm(&bout);
3743ff48bf5SDavid du Colombier 	fprint(2, "brk %p\n", sbrk(1));
3753ff48bf5SDavid du Colombier 	fprint(2, "%d nodes used; %d of %d hash slots used\n", nalloc, nnew, nhash);
3769a747e4fSDavid du Colombier 	exits(nil);
3779a747e4fSDavid du Colombier }
378