Linux-EROFS Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/4] erofs-utils: add DEFLATE compression support
@ 2023-07-09 18:25 Gao Xiang
  2023-07-09 18:25 ` [PATCH 1/4] erofs-utils: switch to effective unaligned access Gao Xiang
                   ` (4 more replies)
  0 siblings, 5 replies; 12+ messages in thread
From: Gao Xiang @ 2023-07-09 18:25 UTC (permalink / raw)
  To: linux-erofs; +Cc: Gao Xiang

Hi folks,

This is the EROFS DEFLATE support which I've been working on these
months, mainly implementing DEFLATE fixed-sized output approach.

DEFLATE is a popular generic-purpose compression algorithm for a quite
long time (many advanced formats like zlib, gzip, zip, png are all
based on that), which is made of LZ77 as wells as Huffman code, fully
documented as RFC1951 and quite easy to understand.  DEFLATE encoder
and decoder are also easy to be implemented and they generally have
fairly small code size and runtime memory consumption.

In addition, there are several hardware on-market DEFLATE accelerators
as well, such as (s390) DFLTCC, (Intel) IAA/QAT, (HiSilicon) ZIP
accelerator, etc.  Therefore, it's useful to support DEFLATE in order
to use these for async I/Os and get benefits from these later.

As usual, some numbers of enwik8 and silesia.tar are available below:

Commandline option: -zdeflate,9 (to match mksquashfs default level)
Sliding window size: 32768

enwik8:
 ____________________________
|  file system  |    size    |
|_______________|____________|
|___ enwik8 ____|_100000000__|
|_ squashfs_4k _|__46325760__|
|_ squashfs_8k _|__43618304__|
|__ erofs_4k ___|__43073536__| (erofs default)
| squashfs_16k _|__41406464__|
|__ erofs_8k ___|__40873984__|
|__ erofs_16k __|__38998016__|
| squashfs_64k _|__38109184__|
| squashfs_128k |__37306368__| (squashfs default)
|__ erofs_64k __|__37142528__|

silesia.tar (much similar to enwik8 so I omit some):
 ____________________________
|  file system  |    size    |
|_______________|____________|
|_ silesia.tar _|_211957760__|
|_ squashfs_4k _|__86884352__|
|_ squashfs_8k _|__81125376__|
|__ erofs_4k ___|__78970880__| (erofs default)
|__ erofs_8k ___|__74768384__|
| squashfs_64k _|__70447104__|
| squashfs_128k |__69079040__| (squashfs default)
|__ erofs_64k __|__68640768__|

Thanks,
Gao Xiang

Gao Xiang (4):
  erofs-utils: switch to effective unaligned access
  erofs-utils: add a built-in DEFLATE compressor
  erofs-utils: fuse: add DEFLATE algorithm support
  erofs-utils: mkfs: add DEFLATE algorithm support

 configure.ac             |   45 ++
 dump/Makefile.am         |    3 +-
 fsck/Makefile.am         |    6 +-
 fuse/Makefile.am         |    2 +-
 include/erofs/defs.h     |   24 +-
 include/erofs/internal.h |    2 -
 include/erofs_fs.h       |    7 +
 lib/Makefile.am          |    2 +
 lib/compress.c           |   24 +
 lib/compressor.c         |    1 +
 lib/compressor.h         |    1 +
 lib/compressor_deflate.c |   77 +++
 lib/decompress.c         |  147 +++++
 lib/dedupe.c             |   23 +-
 lib/kite_deflate.c       | 1267 ++++++++++++++++++++++++++++++++++++++
 15 files changed, 1618 insertions(+), 13 deletions(-)
 create mode 100644 lib/compressor_deflate.c
 create mode 100644 lib/kite_deflate.c

-- 
2.24.4

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH 1/4] erofs-utils: switch to effective unaligned access
  2023-07-09 18:25 [PATCH 0/4] erofs-utils: add DEFLATE compression support Gao Xiang
@ 2023-07-09 18:25 ` Gao Xiang
  2023-07-09 18:25 ` [PATCH 2/4] erofs-utils: add a built-in DEFLATE compressor Gao Xiang
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 12+ messages in thread
From: Gao Xiang @ 2023-07-09 18:25 UTC (permalink / raw)
  To: linux-erofs; +Cc: Gao Xiang

In order to prepare for LZ77 matchfinder.  Note that erofs_memcmp2()
is still not quite effective.

Signed-off-by: Gao Xiang <hsiangkao@linux.alibaba.com>
---
 include/erofs/defs.h     | 24 ++++++++++++++++++++++--
 include/erofs/internal.h |  2 --
 lib/dedupe.c             | 23 ++++++++++++++++++-----
 3 files changed, 40 insertions(+), 9 deletions(-)

diff --git a/include/erofs/defs.h b/include/erofs/defs.h
index e5aa23c..44af557 100644
--- a/include/erofs/defs.h
+++ b/include/erofs/defs.h
@@ -179,9 +179,29 @@ typedef int64_t         s64;
 #define __maybe_unused      __attribute__((__unused__))
 #endif
 
-static inline u32 get_unaligned_le32(const u8 *p)
+#define __packed __attribute__((__packed__))
+
+#define __get_unaligned_t(type, ptr) ({						\
+	const struct { type x; } __packed *__pptr = (typeof(__pptr))(ptr);	\
+	__pptr->x;								\
+})
+
+#define __put_unaligned_t(type, val, ptr) do {					\
+	struct { type x; } __packed *__pptr = (typeof(__pptr))(ptr);		\
+	__pptr->x = (val);							\
+} while (0)
+
+#define get_unaligned(ptr)	__get_unaligned_t(typeof(*(ptr)), (ptr))
+#define put_unaligned(val, ptr) __put_unaligned_t(typeof(*(ptr)), (val), (ptr))
+
+static inline u32 get_unaligned_le32(const void *p)
+{
+	return le32_to_cpu(__get_unaligned_t(__le32, p));
+}
+
+static inline void put_unaligned_le32(u32 val, void *p)
 {
-	return p[0] | p[1] << 8 | p[2] << 16 | p[3] << 24;
+	__put_unaligned_t(__le32, cpu_to_le32(val), p);
 }
 
 /**
diff --git a/include/erofs/internal.h b/include/erofs/internal.h
index ab964d4..aad2115 100644
--- a/include/erofs/internal.h
+++ b/include/erofs/internal.h
@@ -17,8 +17,6 @@ extern "C"
 
 typedef unsigned short umode_t;
 
-#define __packed __attribute__((__packed__))
-
 #include "erofs_fs.h"
 #include <fcntl.h>
 #include <sys/types.h> /* for off_t definition */
diff --git a/lib/dedupe.c b/lib/dedupe.c
index 0a69b8f..17da452 100644
--- a/lib/dedupe.c
+++ b/lib/dedupe.c
@@ -11,12 +11,14 @@
 unsigned long erofs_memcmp2(const u8 *s1, const u8 *s2,
 			    unsigned long sz)
 {
+	const unsigned long *a1, *a2;
 	unsigned long n = sz;
 
-	if (sz >= sizeof(long) && ((long)s1 & (sizeof(long) - 1)) ==
-			((long)s2 & (sizeof(long) - 1))) {
-		const unsigned long *a1, *a2;
+	if (sz < sizeof(long))
+		goto out_bytes;
 
+	if (((long)s1 & (sizeof(long) - 1)) ==
+			((long)s2 & (sizeof(long) - 1))) {
 		while ((long)s1 & (sizeof(long) - 1)) {
 			if (*s1 != *s2)
 				break;
@@ -34,9 +36,20 @@ unsigned long erofs_memcmp2(const u8 *s1, const u8 *s2,
 			++a2;
 			sz -= sizeof(long);
 		}
-		s1 = (const u8 *)a1;
-		s2 = (const u8 *)a2;
+	} else {
+		a1 = (const unsigned long *)s1;
+		a2 = (const unsigned long *)s2;
+		do {
+			if (get_unaligned(a1) != get_unaligned(a2))
+				break;
+			++a1;
+			++a2;
+			sz -= sizeof(long);
+		} while (sz >= sizeof(long));
 	}
+	s1 = (const u8 *)a1;
+	s2 = (const u8 *)a2;
+out_bytes:
 	while (sz) {
 		if (*s1 != *s2)
 			break;
-- 
2.24.4


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH 2/4] erofs-utils: add a built-in DEFLATE compressor
  2023-07-09 18:25 [PATCH 0/4] erofs-utils: add DEFLATE compression support Gao Xiang
  2023-07-09 18:25 ` [PATCH 1/4] erofs-utils: switch to effective unaligned access Gao Xiang
@ 2023-07-09 18:25 ` Gao Xiang
  2023-07-10  2:33   ` Eric Biggers
  2023-07-09 18:25 ` [PATCH 3/4] erofs-utils: fuse: add DEFLATE algorithm support Gao Xiang
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 12+ messages in thread
From: Gao Xiang @ 2023-07-09 18:25 UTC (permalink / raw)
  To: linux-erofs; +Cc: Gao Xiang

As Apple documentation written "If you require interoperability with
non-Apple devices, use COMPRESSION_ZLIB. [1]", DEFLATE is a popular
generic-purpose compression algorithm for a quite long time (many
advanced formats like zlib, gzip, zip, png are all based on that),
which is made of LZ77 as wells as Huffman code, fully documented as
RFC1951 [2] and quite easy to understand, implement.

There are several hardware on-market DEFLATE accelerators as well,
such as (s390) DFLTCC, (Intel) IAA/QAT, (HiSilicon) ZIP accelerator,
etc.  Therefore, it's useful to support DEFLATE compression in order
to use these for async I/Os and get benefits from these.

Since there is _no fixed-sized output DEFLATE compression appoach_
available in public (fitblk is somewhat ineffective) and the original
zlib is quite slowly developping, let's work out one for our use cases.
Fortunately, it's only less than 1.5kLOC with lazy matching to match
the basic zlib ability.  Besides, near-optimal block splitting (based
on price function) doesn't support for now since it's not rush for us.

In the future, there may be more built-in optimizers landed to fulfill
our needs even further.  In addition, I'd be quite happy to see more
popular encoders to support fixed-sized output compression too.

[1] https://developer.apple.com/documentation/compression/compression_algorithm
[2] https://datatracker.ietf.org/doc/html/rfc1951
Signed-off-by: Gao Xiang <hsiangkao@linux.alibaba.com>
---
 lib/Makefile.am    |    2 +
 lib/kite_deflate.c | 1267 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 1269 insertions(+)
 create mode 100644 lib/kite_deflate.c

diff --git a/lib/Makefile.am b/lib/Makefile.am
index faa7311..fa3b804 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -43,3 +43,5 @@ if ENABLE_LIBLZMA
 liberofs_la_CFLAGS += ${liblzma_CFLAGS}
 liberofs_la_SOURCES += compressor_liblzma.c
 endif
+
+liberofs_la_SOURCES += kite_deflate.c
diff --git a/lib/kite_deflate.c b/lib/kite_deflate.c
new file mode 100644
index 0000000..569f1b0
--- /dev/null
+++ b/lib/kite_deflate.c
@@ -0,0 +1,1267 @@
+// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0
+/*
+ * erofs-utils/lib/kite_deflate.c
+ *
+ * Copyright (C) 2023, Alibaba Cloud
+ * Copyright (C) 2023, Gao Xiang <xiang@kernel.org>
+ */
+#include "erofs/defs.h"
+#include "erofs/print.h"
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdio.h>
+
+unsigned long erofs_memcmp2(const u8 *s1, const u8 *s2,
+			    unsigned long sz);
+
+#ifdef TEST
+#define kite_dbg(x, ...)	fprintf(stderr, x "\n", ##__VA_ARGS__)
+#else
+#define kite_dbg(x, ...)
+#endif
+
+#define kHistorySize32		(1U << 15)
+
+#define kNumLenSymbols32	256
+#define kNumLenSymbolsMax	kNumLenSymbols32
+
+#define kSymbolEndOfBlock	256
+#define kSymbolMatch		(kSymbolEndOfBlock + 1)
+#define kNumLenSlots		29
+#define kMainTableSize		(kSymbolMatch + kNumLenSlots)
+
+#define kFixedLenTableSize	(kSymbolMatch + 31)
+#define FixedDistTableSize	32
+
+#define kMainTableSize		(kSymbolMatch + kNumLenSlots)
+#define kDistTableSize32	30
+
+#define kNumLitLenCodesMin	257
+#define kNumDistCodesMin	1
+
+#define kNumLensCodesMin	4
+#define kLensTableSize		19
+
+#define kMatchMinLen		3
+#define kMatchMaxLen32		kNumLenSymbols32 + kMatchMinLen - 1
+
+static u32 kstaticHuff_mainCodes[kFixedLenTableSize];
+static const u8 kstaticHuff_litLenLevels[kFixedLenTableSize] = {
+	[0   ... 143] = 8, [144 ... 255] = 9,
+	[256 ... 279] = 7, [280 ... 287] = 8,
+};
+static u32 kstaticHuff_distCodes[kFixedLenTableSize];
+
+const u8 kLenStart32[kNumLenSlots] =
+	{0,1,2,3,4,5,6,7,8,10,12,14,16,20,24,28,32,40,48,56,64,80,96,112,128,160,192,224, 255};
+
+const u8 kLenExtraBits32[kNumLenSlots] =
+	{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};
+
+/* First normalized distance for each code (0 = distance of 1) */
+const u32 kDistStart[kDistTableSize32] =
+	{0,1,2,3,4,6,8,12,16,24,32,48,64,96,128,192,256,384,512,768,
+	 1024,1536,2048,3072,4096,6144,8192,12288,16384,24576};
+
+/* extra bits for each distance code */
+const u8 kDistExtraBits[kDistTableSize32] =
+	{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};
+
+const u8 kCodeLengthAlphabetOrder[kLensTableSize] =
+	{16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
+
+const u8 kLevelExtraBits[3] = {2, 3, 7};
+
+const unsigned int kTableDirectLevels = 16;
+const unsigned int kBitLensRepNumber_3_6 = kTableDirectLevels;
+const unsigned int kBitLens0Number_3_10 = kBitLensRepNumber_3_6 + 1;
+const unsigned int kBitLens0Number_11_138 = kBitLens0Number_3_10 + 1;
+
+#define kStored			0
+#define kFixedHuffman		1
+#define kDynamicHuffman		2
+
+struct kite_deflate_symbol {
+	u16 len, dist;
+};
+
+struct kite_deflate_table {
+	u32  mainCodes[kMainTableSize];
+	u8   litLenLevels[kMainTableSize];
+	u32  distCodes[kDistTableSize32];
+	u8   distLevels[kDistTableSize32];
+	u32  levelCodes[kLensTableSize];
+	u8   levelLens[kLensTableSize];
+
+	u8   numdistlens, numblcodes;
+	u16  numlitlens;
+};
+
+struct kite_deflate {
+	struct kite_deflate_table  *tab;
+	const u8   *in;
+	u8         *out;
+
+	u32  inlen, outlen;
+	u32  pos_in, pos_out;
+	u32  inflightbits;
+	u8   bitpos;
+	u8   numHuffBits;
+	u32  symbols;
+
+	u32  costbits, startpos;
+	u8   encode_mode;
+	bool freq_changed, lastblock;
+
+	/* Previous match for lazy matching */
+	bool prev_valid;
+	u16 prev_longest;
+
+	u32  mainFreqs[kMainTableSize];
+	u32  distFreqs[kDistTableSize32];
+	struct kite_deflate_table tables[2];
+
+	/* don't reset the following fields */
+	struct kite_matchfinder *mf;
+	struct kite_deflate_symbol *sym;
+	u32 max_symbols;
+	bool lazy_search;
+};
+
+#define ZLIB_DISTANCE_TOO_FAR	4096
+
+static u8 g_LenSlots[kNumLenSymbolsMax];
+
+#define kNumLogBits 9		// do not change it
+static u8 g_FastPos[1 << kNumLogBits];
+
+static void writebits(struct kite_deflate *s, unsigned int v, u8 bits)
+{
+	unsigned int rem = sizeof(s->inflightbits) * 8 - s->bitpos;
+
+	s->inflightbits |= (v << s->bitpos) & (!rem - 1);
+	if (bits > rem) {
+		u8 *out = s->out + s->pos_out;
+
+		out[0] = s->inflightbits & 0xff;
+		out[1] = (s->inflightbits >> 8) & 0xff;
+		out[2] = (s->inflightbits >> 16) & 0xff;
+		out[3] = (s->inflightbits >> 24) & 0xff;
+		s->pos_out += 4;
+		DBG_BUGON(s->pos_out > s->outlen);
+		s->inflightbits = v >> rem;
+		s->bitpos = bits - rem;
+		return;
+	}
+	s->bitpos += bits;
+}
+
+static void flushbits(struct kite_deflate *s)
+{
+	u8 *out = s->out + s->pos_out;
+
+	if (!s->bitpos)
+		return;
+	out[0] = s->inflightbits & 0xff;
+	if (s->bitpos >= 8) {
+		out[1] = (s->inflightbits >> 8) & 0xff;
+		if (s->bitpos >= 16) {
+			out[2] = (s->inflightbits >> 16) & 0xff;
+			if (s->bitpos >= 24)
+				out[3] = (s->inflightbits >> 24) & 0xff;
+		}
+	}
+	s->pos_out += round_up(s->bitpos, 8) >> 3;
+	DBG_BUGON(s->pos_out > s->outlen);
+	s->bitpos = 0;
+	s->inflightbits = 0;
+}
+
+#define kMaxLen 16
+
+static void deflate_genhuffcodes(const u8 *lens, u32 *p, unsigned int nr_codes,
+				 const u32 *bl_count)
+{
+	u32 nextCodes[kMaxLen + 1];	/* next code value for each bit length */
+	unsigned int code = 0;		/* running code value */
+	unsigned int bits, k;
+
+	for (bits = 1; bits <= kMaxLen; ++bits) {
+		code = (code + bl_count[bits - 1]) << 1;
+		nextCodes[bits] = code;
+	}
+
+	DBG_BUGON(code + bl_count[kMaxLen] != 1 << kMaxLen);
+
+	for (k = 0; k < nr_codes; ++k)
+		p[k] = nextCodes[lens[k]]++;
+}
+
+static u32 deflate_reversebits_one(u32 code, u8 bits)
+{
+	unsigned int x = code;
+
+	x = ((x & 0x5555) << 1) | ((x & 0xAAAA) >> 1);
+	x = ((x & 0x3333) << 2) | ((x & 0xCCCC) >> 2);
+	x = ((x & 0x0F0F) << 4) | ((x & 0xF0F0) >> 4);
+
+	return (((x & 0x00FF) << 8) | ((x & 0xFF00) >> 8)) >> (16 - bits);
+}
+
+static void Huffman_ReverseBits(u32 *codes, const u8 *lens, unsigned int n)
+{
+	while (n) {
+		u32 code = *codes;
+
+		*codes++ = deflate_reversebits_one(code, *lens++);
+		--n;
+	}
+}
+
+static void kite_deflate_init_once(void)
+{
+	static const u32 static_bl_count[kMaxLen + 1] = {
+		[7] = 279 - 256 + 1,
+		[8] = (143 + 1) + (287 - 280 + 1),
+		[9] = 255 - 144 + 1,
+	};
+	unsigned int i, c, j, k;
+
+	if (kstaticHuff_distCodes[31])
+		return;
+	deflate_genhuffcodes(kstaticHuff_litLenLevels, kstaticHuff_mainCodes,
+			     kFixedLenTableSize, static_bl_count);
+	Huffman_ReverseBits(kstaticHuff_mainCodes, kstaticHuff_litLenLevels,
+			    kFixedLenTableSize);
+
+	for (i = 0; i < ARRAY_SIZE(kstaticHuff_distCodes); ++i)
+		kstaticHuff_distCodes[i] = deflate_reversebits_one(i, 5);
+
+	for (i = 0; i < kNumLenSlots; i++) {
+		c = kLenStart32[i];
+		j = 1 << kLenExtraBits32[i];
+
+		for (k = 0; k < j; k++, c++)
+			g_LenSlots[c] = (u8)i;
+	}
+
+	c = 0;
+	for (i = 0; i < /*kFastSlots*/ kNumLogBits * 2; i++) {
+		k = 1 << kDistExtraBits[i];
+		for (j = 0; j < k; j++)
+			g_FastPos[c++] = i;
+	}
+}
+
+static void kite_deflate_scanlens(unsigned int numlens, u8 *lens, u32 *freqs)
+{
+	int n;				/* iterates over all tree elements */
+	int prevlen = -1;		/* last emitted length */
+	int curlen;			/* length of current code */
+	int nextlen = lens[0];		/* length of next code */
+	int count = 0;			/* repeat count of the current code */
+	int max_count = 7;		/* max repeat count */
+	int min_count = 4;		/* min repeat count */
+
+	if (!nextlen)
+		max_count = 138, min_count = 3;
+
+	for (n = 0; n < numlens; n++) {
+		curlen = nextlen;
+		nextlen = n + 1 < numlens ? lens[n + 1] : -1;
+		++count;
+
+		if (count < max_count && curlen == nextlen)
+			continue;
+		if (count < min_count) {
+			freqs[curlen] += count;
+		} else if (curlen != 0) {
+			if (curlen != prevlen)
+				freqs[curlen]++;
+			freqs[kBitLensRepNumber_3_6]++;
+		} else if (count <= 10) {
+			freqs[kBitLens0Number_3_10]++;
+		} else {
+			freqs[kBitLens0Number_11_138]++;
+		}
+
+		count = 0;
+		prevlen = curlen;
+		if (!nextlen)
+			max_count = 138, min_count = 3;
+		else if (curlen == nextlen)
+			max_count = 6, min_count = 3;
+		else
+			max_count = 7, min_count = 4;
+	}
+}
+
+static void kite_deflate_sendtree(struct kite_deflate *s, const u8 *lens,
+				  unsigned int numlens)
+{
+	int n;				/* iterates over all tree elements */
+	int prevlen = -1;		/* last emitted length */
+	int curlen;			/* length of current code */
+	int nextlen = lens[0];		/* length of next code */
+	int count = 0;			/* repeat count of the current code */
+	int max_count = 7;		/* max repeat count */
+	int min_count = 4;		/* min repeat count */
+	const u8 *bl_lens = s->tab->levelLens;
+	const u32 *bl_codes = s->tab->levelCodes;
+
+	if (!nextlen)
+		max_count = 138, min_count = 3;
+
+	for (n = 0; n < numlens; n++) {
+		curlen = nextlen;
+		nextlen = n + 1 < numlens ? lens[n + 1] : -1;
+		++count;
+
+		if (count < max_count && curlen == nextlen)
+			continue;
+		if (count < min_count) {
+			do {
+				writebits(s, bl_codes[curlen], bl_lens[curlen]);
+			} while (--count);
+		} else if (curlen) {
+			if (curlen != prevlen) {
+				writebits(s, bl_codes[curlen], bl_lens[curlen]);
+				count--;
+			}
+			writebits(s, bl_codes[kBitLensRepNumber_3_6],
+				  bl_lens[kBitLensRepNumber_3_6]);
+			writebits(s, count - 3, 2);
+		} else if (count <= 10) {
+			writebits(s, bl_codes[kBitLens0Number_3_10],
+				  bl_lens[kBitLens0Number_3_10]);
+			writebits(s, count - 3, 3);
+		} else {
+			writebits(s, bl_codes[kBitLens0Number_11_138],
+				  bl_lens[kBitLens0Number_11_138]);
+			writebits(s, count - 11, 7);
+		}
+
+		count = 0;
+		prevlen = curlen;
+		if (!nextlen)
+			max_count = 138, min_count = 3;
+		else if (curlen == nextlen)
+			max_count = 6, min_count = 3;
+		else
+			max_count = 7, min_count = 4;
+	}
+}
+
+static void kite_deflate_setfixedtrees(struct kite_deflate *s)
+{
+	writebits(s, (kFixedHuffman << 1) + s->lastblock, 3);
+}
+
+static void kite_deflate_sendtrees(struct kite_deflate *s)
+{
+	struct kite_deflate_table *t = s->tab;
+	unsigned int i;
+
+	writebits(s, (kDynamicHuffman << 1) + s->lastblock, 3);
+	writebits(s, t->numlitlens - kNumLitLenCodesMin, 5);
+	writebits(s, t->numdistlens - kNumDistCodesMin,  5);
+	writebits(s, t->numblcodes - kNumLensCodesMin,   4);
+
+	for (i = 0; i < t->numblcodes; i++)
+		writebits(s, t->levelLens[kCodeLengthAlphabetOrder[i]], 3);
+
+	Huffman_ReverseBits(t->levelCodes, t->levelLens, kLensTableSize);
+	kite_deflate_sendtree(s, t->litLenLevels, t->numlitlens);
+	kite_deflate_sendtree(s, t->distLevels, t->numdistlens);
+}
+
+static inline unsigned int deflateDistSlot(unsigned int pos)
+{
+	const unsigned int zz = (kNumLogBits - 1) &
+		((((1U << kNumLogBits) - 1) - pos) >> (31 - 3));
+
+	return g_FastPos[pos >> zz] + (zz * 2);
+}
+
+static void kite_deflate_writeblock(struct kite_deflate *s, bool fixed)
+{
+	int i;
+	u32 *mainCodes, *distCodes;
+	const u8 *litLenLevels, *distLevels;
+
+	if (!fixed) {
+		struct kite_deflate_table *t = s->tab;
+
+		mainCodes = t->mainCodes; distCodes = t->distCodes;
+		litLenLevels = t->litLenLevels;	distLevels = t->distLevels;
+
+		Huffman_ReverseBits(mainCodes, litLenLevels, kMainTableSize);
+		Huffman_ReverseBits(distCodes, distLevels, kDistTableSize32);
+	} else {
+		mainCodes = kstaticHuff_mainCodes;
+		distCodes = kstaticHuff_distCodes;
+
+		litLenLevels = kstaticHuff_litLenLevels;
+	}
+
+	for (i = 0; i < s->symbols; ++i) {
+		struct kite_deflate_symbol *sym = &s->sym[i];
+
+		if (sym->len < kMatchMinLen) {		/* literal */
+			writebits(s, mainCodes[sym->dist],
+				  litLenLevels[sym->dist]);
+		} else {
+			unsigned int lenSlot, distSlot;
+			unsigned int lc = sym->len - kMatchMinLen;
+
+			lenSlot = g_LenSlots[lc];
+			writebits(s, mainCodes[kSymbolMatch + lenSlot],
+				  litLenLevels[kSymbolMatch + lenSlot]);
+			writebits(s, lc - kLenStart32[lenSlot],
+				  kLenExtraBits32[lenSlot]);
+
+			distSlot = deflateDistSlot(sym->dist - 1);
+			writebits(s, distCodes[distSlot],
+				  fixed ? 5 : distLevels[distSlot]);
+			writebits(s, sym->dist - 1 - kDistStart[distSlot],
+				  kDistExtraBits[distSlot]);
+		}
+	}
+	writebits(s, mainCodes[kSymbolEndOfBlock],
+		  litLenLevels[kSymbolEndOfBlock]);
+}
+
+static u32 Huffman_GetPrice(const u32 *freqs, const u8 *lens, u32 num)
+{
+	u32 price = 0;
+
+	while (num) {
+		price += (*lens++) * (*freqs++);
+		--num;
+	}
+	return price;
+}
+
+static u32 Huffman_GetPriceEx(const u32 *freqs, const u8 *lens, u32 num,
+			      const u8 *extraBits, u32 extraBase)
+{
+	return Huffman_GetPrice(freqs, lens, num) +
+		Huffman_GetPrice(freqs + extraBase, extraBits, num - extraBase);
+}
+
+/* Adapted from C/HuffEnc.c (7zip) for now */
+#define HeapSortDown(p, k, size, temp) \
+  { for (;;) { \
+    size_t s = (k << 1); \
+    if (s > size) break; \
+    if (s < size && p[s + 1] > p[s]) s++; \
+    if (temp >= p[s]) break; \
+    p[k] = p[s]; k = s; \
+  } p[k] = temp; }
+
+static void HeapSort(u32 *p, size_t size)
+{
+  if (size <= 1)
+    return;
+  p--;
+  {
+    size_t i = size / 2;
+    do
+    {
+      u32 temp = p[i];
+      size_t k = i;
+      HeapSortDown(p, k, size, temp)
+    }
+    while (--i != 0);
+  }
+  /*
+  do
+  {
+    size_t k = 1;
+    UInt32 temp = p[size];
+    p[size--] = p[1];
+    HeapSortDown(p, k, size, temp)
+  }
+  while (size > 1);
+  */
+  while (size > 3)
+  {
+    u32 temp = p[size];
+    size_t k = (p[3] > p[2]) ? 3 : 2;
+    p[size--] = p[1];
+    p[1] = p[k];
+    HeapSortDown(p, k, size, temp)
+  }
+  {
+    u32 temp = p[size];
+    p[size] = p[1];
+    if (size > 2 && p[2] < temp)
+    {
+      p[1] = p[2];
+      p[2] = temp;
+    }
+    else
+      p[1] = temp;
+  }
+}
+
+#define NUM_BITS 10
+#define MASK (((unsigned)1 << NUM_BITS) - 1)
+
+static void Huffman_Generate(const u32 *freqs, u32 *p, u8 *lens,
+			     unsigned int numSymbols, unsigned int maxLen)
+{
+	u32 num, i;
+
+	num = 0;
+	/* if (maxLen > 10) maxLen = 10; */
+
+	for (i = 0; i < numSymbols; i++) {
+		u32 freq = freqs[i];
+
+		if (!freq)
+			lens[i] = 0;
+		else
+			p[num++] = i | (freq << NUM_BITS);
+	}
+	HeapSort(p, num);
+
+	if (num < 2) {
+		unsigned int minCode = 0, maxCode = 1;
+
+		if (num == 1) {
+			maxCode = (unsigned int)p[0] & MASK;
+			if (!maxCode)
+				maxCode++;
+		}
+		p[minCode] = 0;
+		p[maxCode] = 1;
+		lens[minCode] = lens[maxCode] = 1;
+		return;
+	}
+
+	{
+		u32 b, e, i;
+
+		i = b = e = 0;
+		do {
+			u32 n, m, freq;
+
+			n = (i != num && (b == e || (p[i] >> NUM_BITS) <= (p[b] >> NUM_BITS))) ? i++ : b++;
+			freq = (p[n] & ~MASK);
+			p[n] = (p[n] & MASK) | (e << NUM_BITS);
+			m = (i != num && (b == e || (p[i] >> NUM_BITS) <= (p[b] >> NUM_BITS))) ? i++ : b++;
+			freq += (p[m] & ~MASK);
+			p[m] = (p[m] & MASK) | (e << NUM_BITS);
+			p[e] = (p[e] & MASK) | freq;
+			e++;
+		} while (num - e > 1);
+
+		{
+			u32 lenCounters[kMaxLen + 1];
+
+			for (i = 0; i <= kMaxLen; i++)
+				lenCounters[i] = 0;
+
+			p[--e] &= MASK;
+			lenCounters[1] = 2;
+			while (e > 0) {
+				u32 len = (p[p[--e] >> NUM_BITS] >> NUM_BITS) + 1;
+
+				p[e] = (p[e] & MASK) | (len << NUM_BITS);
+				if (len >= maxLen)
+					for (len = maxLen - 1; lenCounters[len] == 0; len--);
+				lenCounters[len]--;
+				lenCounters[(size_t)len + 1] += 2;
+			}
+
+			{
+				u32 len;
+
+				i = 0;
+				for (len = maxLen; len != 0; len--) {
+					u32 k;
+					for (k = lenCounters[len]; k != 0; k--)
+						lens[p[i++] & MASK] = (u8)len;
+				}
+			}
+			deflate_genhuffcodes(lens, p, numSymbols, lenCounters);
+		}
+	}
+}
+
+static void kite_deflate_fixdynblock(struct kite_deflate *s)
+{
+	struct kite_deflate_table *t = s->tab;
+	unsigned int numlitlens, numdistlens, numblcodes;
+	u32 levelFreqs[kLensTableSize] = {0};
+	u32 opt_mainlen;
+
+	if (!s->freq_changed)
+		return;
+
+	/* in order to match zlib */
+	s->numHuffBits = kMaxLen;
+//	s->numHuffBits = (s->symbols > 18000 ? 12 :
+//		(s->symbols > 7000 ? 11 : (s->symbols > 2000 ? 10 : 9)));
+
+	Huffman_Generate(s->mainFreqs, t->mainCodes, t->litLenLevels,
+			 kMainTableSize, s->numHuffBits);
+	Huffman_Generate(s->distFreqs, t->distCodes, t->distLevels,
+			 kDistTableSize32, s->numHuffBits);
+
+	/* code lengths for the literal/length alphabet */
+	numlitlens = kMainTableSize;
+	while (numlitlens > kNumLitLenCodesMin &&
+	       !t->litLenLevels[numlitlens - 1])
+		--numlitlens;
+
+	/* code lengths for the distance alphabet */
+	numdistlens = kDistTableSize32;
+	while (numdistlens > kNumDistCodesMin &&
+	       !t->distLevels[numdistlens - 1])
+		--numdistlens;
+
+	kite_deflate_scanlens(numlitlens, t->litLenLevels, levelFreqs);
+	kite_deflate_scanlens(numdistlens, t->distLevels, levelFreqs);
+	Huffman_Generate(levelFreqs, t->levelCodes, t->levelLens,
+			 kLensTableSize, 7);
+	numblcodes = kLensTableSize;
+	while (numblcodes > kNumLensCodesMin &&
+	       !t->levelLens[kCodeLengthAlphabetOrder[numblcodes - 1]])
+		--numblcodes;
+
+	t->numlitlens = numlitlens;
+	t->numdistlens = numdistlens;
+	t->numblcodes = numblcodes;
+
+	opt_mainlen = Huffman_GetPriceEx(s->mainFreqs, t->litLenLevels,
+			kMainTableSize, kLenExtraBits32, kSymbolMatch) +
+		Huffman_GetPriceEx(s->distFreqs, t->distLevels,
+			kDistTableSize32, kDistExtraBits, 0);
+	s->costbits = 3 + 5 + 5 + 4 + 3 * numblcodes +
+		Huffman_GetPriceEx(levelFreqs, t->levelLens,
+			kLensTableSize, kLevelExtraBits, kTableDirectLevels) +
+		opt_mainlen;
+	s->freq_changed = false;
+}
+
+
+/*
+ * an array used used by the LZ-based encoder to hold the length-distance pairs
+ * found by LZ matchfinder.
+ */
+struct kite_match {
+	unsigned int len;
+	unsigned int dist;
+};
+
+struct kite_matchfinder {
+	/* pointer to buffer with data to be compressed */
+	const u8 *buffer;
+
+	/* indicate the first byte that doesn't contain valid input data */
+	const u8 *end;
+
+	/* LZ matchfinder hash chain representation */
+	u32 *hash, *chain;
+
+	u32 base;
+
+	/* indicate the next byte to run through the match finder */
+	u32 offset;
+
+	u32 cyclic_pos;
+
+	/* maximum length of a match that the matchfinder will try to find. */
+	u16 nice_len;
+
+	/* the total sliding window size */
+	u16 wsiz;
+
+	/* how many rounds a matchfinder searches on a hash chain for */
+	u16 depth;
+
+	/* do not perform lazy search no less than this match length */
+	u16 max_lazy;
+
+	/* reduce lazy search no less than this match length */
+	u8  good_len;
+
+	/* current match for lazy matching */
+	struct kite_match *matches;
+	struct kite_match matches_matrix[2][4];
+};
+
+/*
+ * This mysterious table is just the CRC of each possible byte. It can be
+ * computed using the standard bit-at-a-time methods. The polynomial can
+ * be seen in entry 128, 0x8408. This corresponds to x^0 + x^5 + x^12.
+ * Add the implicit x^16, and you have the standard CRC-CCITT.
+ */
+u16 const crc_ccitt_table[256] __attribute__((__aligned__(128))) = {
+	0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
+	0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
+	0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
+	0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
+	0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
+	0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
+	0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
+	0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
+	0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
+	0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
+	0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
+	0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
+	0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
+	0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
+	0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
+	0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
+	0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
+	0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
+	0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
+	0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
+	0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
+	0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
+	0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
+	0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
+	0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
+	0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
+	0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
+	0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
+	0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
+	0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
+	0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
+	0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78
+};
+
+int kite_mf_getmatches_hc3(struct kite_matchfinder *mf, u16 depth, u16 bestlen)
+{
+	const u8 *cur = mf->buffer + mf->offset;
+	const u8 *qbase = mf->buffer - mf->base;
+	u32 curMatch;
+	unsigned int v, hv, i, k, p, wsiz;
+
+	if (mf->end - cur < bestlen + 1)
+		return 0;
+
+	v = get_unaligned((u16 *)cur);
+	hv = v ^ crc_ccitt_table[cur[2]];
+	curMatch = mf->hash[hv];
+	p = mf->base + mf->offset;
+	mf->hash[hv] = p;
+	mf->chain[mf->cyclic_pos] = curMatch;
+	wsiz = mf->wsiz;
+	k = 1;
+
+	if (depth) {
+		unsigned int wpos = wsiz + mf->cyclic_pos;
+
+		hv = min_t(unsigned int, mf->nice_len, mf->end - cur);
+		DBG_BUGON(hv > kMatchMaxLen32);
+		do {
+			unsigned int diff = p - curMatch;
+			const u8 *q;
+
+			if (diff >= wsiz)
+				break;
+
+			q = qbase + curMatch;
+			curMatch = mf->chain[(wpos - diff) & (wsiz - 1)];
+			if (v == get_unaligned((u16 *)q) && (bestlen < 3 || (
+			    get_unaligned((u16 *)(cur + bestlen - 1)) ==
+			    get_unaligned((u16 *)(q + bestlen - 1)) &&
+			    !memcmp(cur + 3, q + 3, bestlen - 3)))) {
+				DBG_BUGON(cur[2] != q[2]);
+				i = erofs_memcmp2(cur + bestlen + 1,
+					q + bestlen + 1, hv - bestlen - 1);
+				bestlen += 1 + i;
+
+				k -= (k >= ARRAY_SIZE(mf->matches_matrix[0]));
+				mf->matches[k++] = (struct kite_match) {
+					.len = bestlen,
+					.dist = diff,
+				};
+				if (bestlen >= hv)
+					break;
+			}
+		} while (--depth);
+	}
+	mf->offset++;
+	mf->cyclic_pos = (mf->cyclic_pos + 1) & (wsiz - 1);
+	return k - 1;
+}
+
+/* let's align with zlib */
+static const struct kite_matchfinder_cfg {
+	u16  good_length;	/* reduce lazy search above this match length */
+	u16  max_lazy;	/* do not perform lazy search above this match length */
+	u16  nice_length;	/* quit search above this match length */
+	u16  depth;
+	bool lazy_search;
+} kite_mfcfg[10] = {
+/*      good lazy nice depth */
+/* 0 */ {0,    0,  0,    0, false},	/* store only [unsupported] */
+/* 1 */ {4,    4,  8,    4, false},	/* maximum speed, no lazy matches */
+/* 2 */ {4,    5, 16,    8, false},
+/* 3 */ {4,    6, 32,   32, false},
+
+/* 4 */ {4,    4,  16,   16, true},	/* lazy matches */
+/* 5 */ {8,   16,  32,   32, true},
+/* 6 */ {8,   16, 128,  128, true},
+/* 7 */ {8,   32, 128,  256, true},
+/* 8 */ {32, 128, 258, 1024, true},
+/* 9 */ {32, 258, 258, 4096, true},	/* maximum compression */
+};
+
+static int kite_mf_init(struct kite_matchfinder *mf, int wsiz, int level)
+{
+	const struct kite_matchfinder_cfg *cfg;
+
+	if (!level || level >= ARRAY_SIZE(kite_mfcfg))
+		return -EINVAL;
+	cfg = &kite_mfcfg[level];
+
+	if (wsiz > kHistorySize32 || (1 << ilog2(wsiz)) != wsiz)
+		return -EINVAL;
+
+	mf->hash = calloc(0x10000, sizeof(mf->hash[0]));
+	if (!mf->hash)
+		return -ENOMEM;
+
+	mf->chain = malloc(sizeof(mf->chain[0]) * wsiz);
+	if (!mf->chain) {
+		free(mf->hash);
+		mf->hash = NULL;
+		return -ENOMEM;
+	}
+	mf->wsiz = wsiz;
+
+	mf->good_len = cfg->good_length;
+	mf->nice_len = cfg->nice_length;
+	mf->depth = cfg->depth;
+	mf->max_lazy = cfg->max_lazy;
+	return cfg->lazy_search;
+}
+
+static void kite_mf_reset(struct kite_matchfinder *mf,
+			  const void *buffer, const void *end)
+{
+	mf->buffer = buffer;
+	mf->end = end;
+
+	/*
+	 * Set the initial value as max_distance + 1.  This would avoid hash
+	 * zero initialization.
+	 */
+	mf->base += mf->offset + kHistorySize32 + 1;
+
+	mf->offset = 0;
+	mf->cyclic_pos = 0;
+
+	mf->matches = mf->matches_matrix[0];
+	mf->matches_matrix[0][0].len =
+		mf->matches_matrix[1][0].len = kMatchMinLen - 1;
+}
+
+static bool deflate_count_code(struct kite_deflate *s, bool literal,
+			       unsigned int lenSlot, unsigned int distSlot)
+{
+	struct kite_deflate_table *t = s->tab;
+	unsigned int lenbase = (literal ? 0 : kSymbolMatch);
+	u64 rem = (s->outlen - s->pos_out) * 8 - s->bitpos;
+	bool recalc = false;
+	unsigned int bits;
+
+	s->freq_changed = true;
+	++s->mainFreqs[lenbase + lenSlot];
+	if (!literal)
+		++s->distFreqs[distSlot];
+
+	if (s->encode_mode == 1) {
+		if (literal) {
+			bits = kstaticHuff_litLenLevels[lenSlot];
+			goto out;
+		}
+		bits = kstaticHuff_litLenLevels[kSymbolMatch + lenSlot] +
+			kLenExtraBits32[lenSlot] + 5 + kDistExtraBits[distSlot];
+		goto out;
+	}
+
+	/* XXX: more ideas to be done later */
+	recalc |= (!literal && !t->distLevels[distSlot]);
+	recalc |= !t->litLenLevels[lenbase + lenSlot];
+	if (recalc) {
+		kite_dbg("recalc %c lS %u dS %u", literal ? 'l' : 'm',
+			 lenSlot, distSlot);
+		s->tab = s->tables + (s->tab == s->tables);
+		kite_deflate_fixdynblock(s);
+		bits = 0;
+		goto out;
+	}
+
+	if (literal) {
+		bits = t->litLenLevels[lenSlot];
+		goto out;
+	}
+
+	bits = t->distLevels[distSlot] + kDistExtraBits[distSlot] +
+		t->litLenLevels[kSymbolMatch + lenSlot] +
+		kLenExtraBits32[lenSlot];
+out:
+	if (rem < s->costbits + bits) {
+		--s->mainFreqs[lenbase + lenSlot];
+		if (!literal)
+			--s->distFreqs[distSlot];
+		if (recalc)
+			s->tab = s->tables + (s->tab == s->tables);
+		return false;
+	}
+	s->costbits += bits;
+	return true;
+}
+
+static bool kite_deflate_tally(struct kite_deflate *s,
+			       struct kite_match *match)
+{
+	struct kite_deflate_symbol *sym = s->sym + s->symbols;
+	u32 fixedcost = ~0;
+	bool hassp;
+
+	*sym = (struct kite_deflate_symbol) {
+		.len = match->len,
+		.dist = match->dist,
+	};
+
+retry:
+	if (sym->len < kMatchMinLen) {
+		hassp = deflate_count_code(s, true, sym->dist, 0);
+	} else {
+		unsigned int lc = sym->len - kMatchMinLen;
+		unsigned int lenSlot = g_LenSlots[lc];
+		unsigned int distSlot = deflateDistSlot(sym->dist - 1);
+
+		hassp = deflate_count_code(s, false, lenSlot, distSlot);
+	}
+
+	if (!hassp) {
+		if (s->encode_mode == 1) {
+			fixedcost = s->costbits;
+			s->encode_mode = 2;
+			goto retry;
+		}
+		s->lastblock = true;
+		if (fixedcost <= s->costbits)
+			s->encode_mode = 1;
+		return true;
+	}
+	++s->symbols;
+	return false;
+}
+
+static void kite_deflate_writestore(struct kite_deflate *s)
+{
+	bool fb = !s->startpos && !s->bitpos;
+	unsigned int totalsiz = s->pos_in - s->prev_valid - s->startpos;
+
+	do {
+		unsigned int len = min_t(unsigned int, totalsiz, 65535);
+
+		totalsiz -= len;
+		writebits(s, (fb << 3) | (kStored << 1) |
+			  (s->lastblock && !totalsiz), 3 + fb);
+		flushbits(s);
+		writebits(s, len, 16);
+		writebits(s, len ^ 0xffff, 16);
+		flushbits(s);
+		memcpy(s->out + s->pos_out, s->in + s->startpos, len);
+		s->pos_out += len;
+		s->startpos += len;
+	} while (totalsiz);
+}
+
+static void kite_deflate_endblock(struct kite_deflate *s)
+{
+	if (s->encode_mode == 1) {
+		u32 fixedcost = s->costbits;
+		unsigned int storelen, storeblocks, storecost;
+
+		kite_deflate_fixdynblock(s);
+		if (fixedcost > s->costbits)
+			s->encode_mode = 2;
+		else
+			s->costbits = fixedcost;
+
+		storelen = s->pos_in - s->prev_valid - s->startpos;
+		storeblocks = max(DIV_ROUND_UP(storelen, 65535), 1U);
+		storecost = (8 - s->bitpos) + storeblocks - 1 +
+			storeblocks * 32 + storelen * 8;
+		if (s->costbits > storecost) {
+			s->costbits = storecost;
+			s->encode_mode = 0;
+		}
+	}
+
+	s->lastblock |= (s->costbits + s->bitpos >=
+			(s->outlen - s->pos_out) * 8);
+}
+
+static void kite_deflate_startblock(struct kite_deflate *s)
+{
+	memset(s->mainFreqs, 0, sizeof(s->mainFreqs));
+	memset(s->distFreqs, 0, sizeof(s->distFreqs));
+	memset(s->tables, 0, sizeof(s->tables[0]));
+	s->symbols = 0;
+	s->mainFreqs[kSymbolEndOfBlock]++;
+	s->encode_mode = 1;
+	s->tab = s->tables;
+	s->costbits = 3 + kstaticHuff_litLenLevels[kSymbolEndOfBlock];
+}
+
+static bool kite_deflate_commitblock(struct kite_deflate *s)
+{
+	if (s->encode_mode == 1) {
+		kite_deflate_setfixedtrees(s);
+		kite_deflate_writeblock(s, true);
+	} else if (s->encode_mode == 2) {
+		kite_deflate_sendtrees(s);
+		kite_deflate_writeblock(s, false);
+	} else {
+		kite_deflate_writestore(s);
+	}
+	s->startpos = s->pos_in - s->prev_valid;
+	return s->lastblock;
+}
+
+static bool kite_deflate_fast(struct kite_deflate *s)
+{
+	struct kite_matchfinder *mf = s->mf;
+
+	kite_deflate_startblock(s);
+	while (1) {
+		int matches = kite_mf_getmatches_hc3(mf, mf->depth,
+				kMatchMinLen - 1);
+
+		if (matches) {
+			unsigned int len = mf->matches[matches].len;
+			unsigned int dist = mf->matches[matches].dist;
+
+			if (len == kMatchMinLen && dist > ZLIB_DISTANCE_TOO_FAR)
+				goto nomatch;
+
+			kite_dbg("%u matches found: longest [%u,%u] of distance %u",
+				 matches, s->pos_in, s->pos_in + len - 1, dist);
+
+			if (kite_deflate_tally(s, mf->matches + matches))
+				break;
+			s->pos_in += len;
+			/* skip the rest bytes */
+			while (--len)
+				(void)kite_mf_getmatches_hc3(mf, 0, 0);
+		} else {
+nomatch:
+			mf->matches[0].dist = s->in[s->pos_in];
+			if (isprint(s->in[s->pos_in]))
+				kite_dbg("literal %c pos_in %u", s->in[s->pos_in], s->pos_in);
+			else
+				kite_dbg("literal %x pos_in %u", s->in[s->pos_in], s->pos_in);
+
+			if (kite_deflate_tally(s, mf->matches))
+				break;
+			++s->pos_in;
+		}
+
+		s->lastblock |= (s->pos_in >= s->inlen);
+		if (s->pos_in >= s->inlen || s->symbols >= s->max_symbols) {
+			kite_deflate_endblock(s);
+			break;
+		}
+	}
+	return kite_deflate_commitblock(s);
+}
+
+static bool kite_deflate_slow(struct kite_deflate *s)
+{
+	struct kite_matchfinder *mf = s->mf;
+	bool flush = false;
+
+	kite_deflate_startblock(s);
+	while (1) {
+		struct kite_match *prev_matches = mf->matches;
+		unsigned int len = kMatchMinLen - 1;
+		int matches;
+		unsigned int len0;
+
+		mf->matches = mf->matches_matrix[
+				mf->matches == mf->matches_matrix[0]];
+		mf->matches[0].dist = s->in[s->pos_in];
+
+		len0 = prev_matches[s->prev_longest].len;
+		if (len0 < mf->max_lazy) {
+			matches = kite_mf_getmatches_hc3(mf, mf->depth >>
+				(len0 >= mf->good_len), len0);
+			if (matches) {
+				len = mf->matches[matches].len;
+				if (len == kMatchMinLen &&
+				    mf->matches[matches].dist > ZLIB_DISTANCE_TOO_FAR) {
+					matches = 0;
+					len = kMatchMinLen - 1;
+				}
+			}
+		} else {
+			matches = 0;
+			(void)kite_mf_getmatches_hc3(mf, 0, 0);
+		}
+
+		if (len < len0) {
+			if (kite_deflate_tally(s,
+					prev_matches + s->prev_longest))
+				break;
+
+			s->pos_in += --len0;
+			/* skip the rest bytes */
+			while (--len0)
+				(void)kite_mf_getmatches_hc3(mf, 0, 0);
+			s->prev_valid = false;
+			s->prev_longest = 0;
+		} else {
+			if (!s->prev_valid)
+				s->prev_valid = true;
+			else if (kite_deflate_tally(s, prev_matches))
+				break;
+			++s->pos_in;
+			s->prev_longest = matches;
+		}
+
+		s->lastblock |= (s->pos_in >= s->inlen);
+		if (s->pos_in >= s->inlen) {
+			flush = true;
+			break;
+		}
+		if (s->symbols >= s->max_symbols) {
+			kite_deflate_endblock(s);
+			break;
+		}
+	}
+
+	if (flush && s->prev_valid) {
+		(void)kite_deflate_tally(s, mf->matches + s->prev_longest);
+		s->prev_valid = false;
+	}
+	return kite_deflate_commitblock(s);
+}
+
+void kite_deflate_end(struct kite_deflate *s)
+{
+	if (s->mf) {
+		if (s->mf->hash)
+			free(s->mf->hash);
+		if (s->mf->chain)
+			free(s->mf->chain);
+		free(s->mf);
+	}
+	if (s->sym)
+		free(s->sym);
+	free(s);
+}
+
+struct kite_deflate *kite_deflate_init(int level, unsigned int dict_size)
+{
+	struct kite_deflate *s;
+	int err;
+
+	kite_deflate_init_once();
+	s = calloc(1, sizeof(*s));
+	if (!s)
+		return ERR_PTR(-ENOMEM);
+
+	s->max_symbols = 16384;
+	s->sym = malloc(sizeof(s->sym[0]) * s->max_symbols);
+	if (!s->sym) {
+		err = -ENOMEM;
+		goto err_out;
+	}
+
+	s->mf = malloc(sizeof(*s->mf));
+	if (!s->mf) {
+		err = -ENOMEM;
+		goto err_out;
+	}
+
+	if (!dict_size)
+		dict_size = kHistorySize32;
+
+	err = kite_mf_init(s->mf, dict_size, level);
+	if (err < 0)
+		goto err_out;
+
+	s->lazy_search = err;
+	return s;
+err_out:
+	if (s->mf)
+		free(s->mf);
+	if (s->sym)
+		free(s->sym);
+	free(s);
+	return ERR_PTR(err);
+}
+
+int kite_deflate_destsize(struct kite_deflate *s, const u8 *in, u8 *out,
+			   unsigned int *srcsize, unsigned int target_dstsize)
+{
+	memset(s, 0, offsetof(struct kite_deflate, mainFreqs));
+	s->in = in;
+	s->inlen = *srcsize;
+	s->out = out;
+	s->outlen = target_dstsize;
+	kite_mf_reset(s->mf, in, in + s->inlen);
+
+	if (s->lazy_search)
+		while (!kite_deflate_slow(s));
+	else
+		while (!kite_deflate_fast(s));
+	flushbits(s);
+
+	*srcsize = s->startpos;
+	return s->pos_out;
+}
+
+#if TEST
+#include <unistd.h>
+#include <fcntl.h>
+
+int main(int argc, char *argv[])
+{
+	int fd, nread;
+	u8 buf[1048576];
+	u8 out[1048576];
+	int dstsize = 4096;
+	unsigned int srcsize, outsize;
+	struct kite_deflate *s;
+
+	fd = open(argv[1], O_RDONLY);
+	if (fd < 0)
+		return -errno;
+	if (argc > 2)
+		dstsize = atoi(argv[2]);
+
+	s = kite_deflate_init(9, 0);
+	if (IS_ERR(s))
+		return PTR_ERR(s);
+
+	nread = read(fd, buf, sizeof(buf));
+	if (nread <= 0)
+		return -errno;
+	close(fd);
+
+	srcsize = nread;
+	outsize = kite_deflate_destsize(s, buf, out, &srcsize, dstsize);
+	fd = open("out.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
+	write(fd, out, outsize);
+	close(fd);
+	kite_deflate_end(s);
+	return 0;
+}
+#endif
-- 
2.24.4


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH 3/4] erofs-utils: fuse: add DEFLATE algorithm support
  2023-07-09 18:25 [PATCH 0/4] erofs-utils: add DEFLATE compression support Gao Xiang
  2023-07-09 18:25 ` [PATCH 1/4] erofs-utils: switch to effective unaligned access Gao Xiang
  2023-07-09 18:25 ` [PATCH 2/4] erofs-utils: add a built-in DEFLATE compressor Gao Xiang
@ 2023-07-09 18:25 ` Gao Xiang
  2023-07-09 18:25 ` [PATCH 4/4] erofs-utils: mkfs: " Gao Xiang
  2023-07-09 18:45 ` [PATCH v2 3/4] erofs-utils: fuse: " Gao Xiang
  4 siblings, 0 replies; 12+ messages in thread
From: Gao Xiang @ 2023-07-09 18:25 UTC (permalink / raw)
  To: linux-erofs; +Cc: Gao Xiang

This patch adds DEFLATE compression algorithm support to erofsfuse
by using zlib (by default) and libdeflate.  libdeflate will be used
instead of zlib if libdeflate is enabled.

Signed-off-by: Gao Xiang <hsiangkao@linux.alibaba.com>
---
 configure.ac       |  45 ++++++++++++++
 dump/Makefile.am   |   3 +-
 fsck/Makefile.am   |   6 +-
 fuse/Makefile.am   |   2 +-
 include/erofs_fs.h |   7 +++
 lib/decompress.c   | 145 +++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 204 insertions(+), 4 deletions(-)

diff --git a/configure.ac b/configure.ac
index cd6be4a..d41f4a3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -122,6 +122,15 @@ AC_ARG_ENABLE(lzma,
    [AS_HELP_STRING([--enable-lzma], [enable LZMA compression support @<:@default=no@:>@])],
    [enable_lzma="$enableval"], [enable_lzma="no"])
 
+AC_ARG_WITH(zlib,
+   [AS_HELP_STRING([--without-zlib],
+      [Ignore presence of zlib inflate support @<:@default=enabled@:>@])])
+
+AC_ARG_WITH(libdeflate,
+   [AS_HELP_STRING([--with-libdeflate],
+      [Enable and build with libdeflate inflate support @<:@default=disabled@:>@])], [],
+      [with_libdeflate="no"])
+
 AC_ARG_ENABLE(fuse,
    [AS_HELP_STRING([--enable-fuse], [enable erofsfuse @<:@default=no@:>@])],
    [enable_fuse="$enableval"], [enable_fuse="no"])
@@ -394,6 +403,34 @@ if test "x$enable_lzma" = "xyes"; then
   CPPFLAGS="${saved_CPPFLAGS}"
 fi
 
+# Configure zlib
+AS_IF([test "x$with_zlib" != "xno"], [
+  PKG_CHECK_MODULES([zlib], [zlib])
+  # Paranoia: don't trust the result reported by pkgconfig before trying out
+  saved_LIBS="$LIBS"
+  saved_CPPFLAGS=${CPPFLAGS}
+  CPPFLAGS="${zlib_CFLAGS} ${CPPFLAGS}"
+  LIBS="${zlib_LIBS} $LIBS"
+  AC_CHECK_LIB(z, inflate, [
+    have_zlib="yes" ], [
+    AC_MSG_ERROR([zlib doesn't work properly])])
+  LIBS="${saved_LIBS}"
+  CPPFLAGS="${saved_CPPFLAGS}"], [have_zlib="no"])
+
+# Configure libdeflate
+AS_IF([test "x$with_libdeflate" != "xno"], [
+  PKG_CHECK_MODULES([libdeflate], [libdeflate])
+  # Paranoia: don't trust the result reported by pkgconfig before trying out
+  saved_LIBS="$LIBS"
+  saved_CPPFLAGS=${CPPFLAGS}
+  CPPFLAGS="${libdeflate_CFLAGS} ${CPPFLAGS}"
+  LIBS="${libdeflate_LIBS} $LIBS"
+  AC_CHECK_LIB(deflate, libdeflate_deflate_decompress, [
+    have_libdeflate="yes" ], [
+    AC_MSG_ERROR([libdeflate doesn't work properly])])
+  LIBS="${saved_LIBS}"
+  CPPFLAGS="${saved_CPPFLAGS}"], [have_libdeflate="no"])
+
 # Enable 64-bit off_t
 CFLAGS+=" -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64"
 
@@ -449,6 +486,14 @@ if test "x${have_liblzma}" = "xyes"; then
   AC_SUBST([liblzma_CFLAGS])
 fi
 
+if test "x$have_zlib" = "xyes"; then
+  AC_DEFINE([HAVE_ZLIB], 1, [Define to 1 if zlib is found])
+fi
+
+if test "x$have_libdeflate" = "xyes"; then
+  AC_DEFINE([HAVE_LIBDEFLATE], 1, [Define to 1 if libdeflate is found])
+fi
+
 # Dump maximum block size
 AS_IF([test "x$erofs_cv_max_block_size" = "x"],
       [$erofs_cv_max_block_size = 4096], [])
diff --git a/dump/Makefile.am b/dump/Makefile.am
index c2bef6d..63f301e 100644
--- a/dump/Makefile.am
+++ b/dump/Makefile.am
@@ -7,4 +7,5 @@ AM_CPPFLAGS = ${libuuid_CFLAGS}
 dump_erofs_SOURCES = main.c
 dump_erofs_CFLAGS = -Wall -I$(top_srcdir)/include
 dump_erofs_LDADD = $(top_builddir)/lib/liberofs.la ${libselinux_LIBS} \
-	${libuuid_LIBS} ${liblz4_LIBS} ${liblzma_LIBS}
+	${libuuid_LIBS} ${liblz4_LIBS} ${liblzma_LIBS} ${zlib_LIBS} \
+	${libdeflate_LIBS}
diff --git a/fsck/Makefile.am b/fsck/Makefile.am
index 9366a9d..ffdd0be 100644
--- a/fsck/Makefile.am
+++ b/fsck/Makefile.am
@@ -7,7 +7,8 @@ AM_CPPFLAGS = ${libuuid_CFLAGS}
 fsck_erofs_SOURCES = main.c
 fsck_erofs_CFLAGS = -Wall -I$(top_srcdir)/include
 fsck_erofs_LDADD = $(top_builddir)/lib/liberofs.la ${libselinux_LIBS} \
-	${libuuid_LIBS} ${liblz4_LIBS} ${liblzma_LIBS}
+	${libuuid_LIBS} ${liblz4_LIBS} ${liblzma_LIBS} ${zlib_LIBS} \
+	${libdeflate_LIBS}
 
 if ENABLE_FUZZING
 noinst_PROGRAMS   = fuzz_erofsfsck
@@ -15,5 +16,6 @@ fuzz_erofsfsck_SOURCES = main.c
 fuzz_erofsfsck_CFLAGS = -Wall -I$(top_srcdir)/include -DFUZZING
 fuzz_erofsfsck_LDFLAGS = -fsanitize=address,fuzzer
 fuzz_erofsfsck_LDADD = $(top_builddir)/lib/liberofs.la ${libselinux_LIBS} \
-	${libuuid_LIBS} ${liblz4_LIBS} ${liblzma_LIBS}
+	${libuuid_LIBS} ${liblz4_LIBS} ${liblzma_LIBS} ${zlib_LIBS} \
+	${libdeflate_LIBS}
 endif
diff --git a/fuse/Makefile.am b/fuse/Makefile.am
index 3179a2b..50be783 100644
--- a/fuse/Makefile.am
+++ b/fuse/Makefile.am
@@ -7,4 +7,4 @@ erofsfuse_SOURCES = main.c
 erofsfuse_CFLAGS = -Wall -I$(top_srcdir)/include
 erofsfuse_CFLAGS += -DFUSE_USE_VERSION=26 ${libfuse_CFLAGS} ${libselinux_CFLAGS}
 erofsfuse_LDADD = $(top_builddir)/lib/liberofs.la ${libfuse_LIBS} ${liblz4_LIBS} \
-	${libselinux_LIBS} ${liblzma_LIBS}
+	${libselinux_LIBS} ${liblzma_LIBS} ${zlib_LIBS} ${libdeflate_LIBS}
diff --git a/include/erofs_fs.h b/include/erofs_fs.h
index 3697882..6b7b34f 100644
--- a/include/erofs_fs.h
+++ b/include/erofs_fs.h
@@ -297,6 +297,7 @@ enum {
 enum {
 	Z_EROFS_COMPRESSION_LZ4		= 0,
 	Z_EROFS_COMPRESSION_LZMA	= 1,
+	Z_EROFS_COMPRESSION_DEFLATE	= 1,
 	Z_EROFS_COMPRESSION_MAX
 };
 #define Z_EROFS_ALL_COMPR_ALGS		((1 << Z_EROFS_COMPRESSION_MAX) - 1)
@@ -317,6 +318,12 @@ struct z_erofs_lzma_cfgs {
 
 #define Z_EROFS_LZMA_MAX_DICT_SIZE	(8 * Z_EROFS_PCLUSTER_MAX_SIZE)
 
+/* 6 bytes (+ length field = 8 bytes) */
+struct z_erofs_deflate_cfgs {
+	u8 windowbits;			/* 8..15 for DEFLATE */
+	u8 reserved[5];
+} __packed;
+
 /*
  * bit 0 : COMPACTED_2B indexes (0 - off; 1 - on)
  *  e.g. for 4k logical cluster size,      4B        if compacted 2B is off;
diff --git a/lib/decompress.c b/lib/decompress.c
index 59a9ca0..5bc55c5 100644
--- a/lib/decompress.c
+++ b/lib/decompress.c
@@ -9,6 +9,147 @@
 #include "erofs/err.h"
 #include "erofs/print.h"
 
+#ifdef HAVE_LIBDEFLATE
+/* if libdeflate is available, use libdeflate instead. */
+#include <libdeflate.h>
+
+static int z_erofs_decompress_deflate(struct z_erofs_decompress_req *rq)
+{
+	u8 *dest = (u8 *)rq->out;
+	u8 *src = (u8 *)rq->in;
+	u8 *buff = NULL;
+	size_t actual_out;
+	unsigned int inputmargin = 0;
+	struct libdeflate_decompressor *inf;
+	enum libdeflate_result ret;
+
+	while (!src[inputmargin & (erofs_blksiz() - 1)])
+		if (!(++inputmargin & (erofs_blksiz() - 1)))
+			break;
+
+	if (inputmargin >= rq->inputsize)
+		return -EFSCORRUPTED;
+
+	if (rq->decodedskip) {
+		buff = malloc(rq->decodedlength);
+		if (!buff)
+			return -ENOMEM;
+		dest = buff;
+	}
+
+	inf = libdeflate_alloc_decompressor();
+	if (!inf)
+		return -ENOMEM;
+
+	if (rq->partial_decoding) {
+		ret = libdeflate_deflate_decompress(inf, src + inputmargin,
+				rq->inputsize - inputmargin, dest,
+				rq->decodedlength, &actual_out);
+		if (ret && ret != LIBDEFLATE_INSUFFICIENT_SPACE) {
+			ret = -EIO;
+			goto out_inflate_end;
+		}
+
+		if (actual_out != rq->decodedlength) {
+			ret = -EIO;
+			goto out_inflate_end;
+		}
+	} else {
+		ret = libdeflate_deflate_decompress(inf, src + inputmargin,
+				rq->inputsize - inputmargin, dest,
+				rq->decodedlength, NULL);
+		if (ret) {
+			ret = -EIO;
+			goto out_inflate_end;
+		}
+	}
+
+	if (rq->decodedskip)
+		memcpy(rq->out, dest + rq->decodedskip,
+		       rq->decodedlength - rq->decodedskip);
+
+out_inflate_end:
+	libdeflate_free_decompressor(inf);
+	if (buff)
+		free(buff);
+	return ret;
+}
+#elif defined(HAVE_ZLIB)
+#include <zlib.h>
+
+/* report a zlib or i/o error */
+static int zerr(int ret)
+{
+	switch (ret) {
+	case Z_STREAM_ERROR:
+		return -EINVAL;
+	case Z_DATA_ERROR:
+		return -EIO;
+	case Z_MEM_ERROR:
+		return -ENOMEM;
+	case Z_ERRNO:
+	case Z_VERSION_ERROR:
+	default:
+		return -EFAULT;
+	}
+}
+
+static int z_erofs_decompress_deflate(struct z_erofs_decompress_req *rq)
+{
+	int ret = 0;
+	u8 *dest = (u8 *)rq->out;
+	u8 *src = (u8 *)rq->in;
+	u8 *buff = NULL;
+	unsigned int inputmargin = 0;
+	z_stream strm;
+
+	while (!src[inputmargin & (erofs_blksiz() - 1)])
+		if (!(++inputmargin & (erofs_blksiz() - 1)))
+			break;
+
+	if (inputmargin >= rq->inputsize)
+		return -EFSCORRUPTED;
+
+	if (rq->decodedskip) {
+		buff = malloc(rq->decodedlength);
+		if (!buff)
+			return -ENOMEM;
+		dest = buff;
+	}
+
+	/* allocate inflate state */
+	strm.zalloc = Z_NULL;
+	strm.zfree = Z_NULL;
+	strm.opaque = Z_NULL;
+	strm.avail_in = 0;
+	strm.next_in = Z_NULL;
+	ret = inflateInit2(&strm, -15);
+	if (ret != Z_OK)
+		return zerr(ret);
+
+	strm.next_in = src + inputmargin;
+	strm.avail_in = rq->inputsize - inputmargin;
+	strm.next_out = dest;
+	strm.avail_out = rq->decodedlength;
+
+	ret = inflate(&strm, rq->partial_decoding ? Z_SYNC_FLUSH : Z_FINISH);
+	if (ret != Z_STREAM_END) {
+		ret = zerr(ret);
+		goto out_inflate_end;
+	}
+
+	if (rq->decodedskip)
+		memcpy(rq->out, dest + rq->decodedskip,
+		       rq->decodedlength - rq->decodedskip);
+
+out_inflate_end:
+	inflateEnd(&strm);
+	if (buff)
+		free(buff);
+	return ret;
+}
+#endif
+
 #ifdef HAVE_LIBLZMA
 #include <lzma.h>
 
@@ -167,6 +308,10 @@ int z_erofs_decompress(struct z_erofs_decompress_req *rq)
 #ifdef HAVE_LIBLZMA
 	if (rq->alg == Z_EROFS_COMPRESSION_LZMA)
 		return z_erofs_decompress_lzma(rq);
+#endif
+#ifdef HAVE_ZLIB
+	if (rq->alg == Z_EROFS_COMPRESSION_DEFLATE)
+		return z_erofs_decompress_deflate(rq);
 #endif
 	return -EOPNOTSUPP;
 }
-- 
2.24.4


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH 4/4] erofs-utils: mkfs: add DEFLATE algorithm support
  2023-07-09 18:25 [PATCH 0/4] erofs-utils: add DEFLATE compression support Gao Xiang
                   ` (2 preceding siblings ...)
  2023-07-09 18:25 ` [PATCH 3/4] erofs-utils: fuse: add DEFLATE algorithm support Gao Xiang
@ 2023-07-09 18:25 ` Gao Xiang
  2023-07-09 18:45 ` [PATCH v2 3/4] erofs-utils: fuse: " Gao Xiang
  4 siblings, 0 replies; 12+ messages in thread
From: Gao Xiang @ 2023-07-09 18:25 UTC (permalink / raw)
  To: linux-erofs; +Cc: Gao Xiang

This patch adds DEFLATE compression algorithm support to erofs-utils
compression framework.

Note that windowbits (which indicates dictionary size) is recorded in
the on-disk compression configuration.  Since some accelerators (e.g.
Intel IAA) don't have enough on-chip memory, compressed data generated
with large windowbits (e.g. > 12 for the IAA accelerator) doesn't seem
to be worked properly on those.

Signed-off-by: Gao Xiang <hsiangkao@linux.alibaba.com>
---
 lib/Makefile.am          |  2 +-
 lib/compress.c           | 24 +++++++++++++
 lib/compressor.c         |  1 +
 lib/compressor.h         |  1 +
 lib/compressor_deflate.c | 77 ++++++++++++++++++++++++++++++++++++++++
 lib/decompress.c         |  8 +++--
 6 files changed, 109 insertions(+), 4 deletions(-)
 create mode 100644 lib/compressor_deflate.c

diff --git a/lib/Makefile.am b/lib/Makefile.am
index fa3b804..553b387 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -44,4 +44,4 @@ liberofs_la_CFLAGS += ${liblzma_CFLAGS}
 liberofs_la_SOURCES += compressor_liblzma.c
 endif
 
-liberofs_la_SOURCES += kite_deflate.c
+liberofs_la_SOURCES += kite_deflate.c compressor_deflate.c
diff --git a/lib/compress.c b/lib/compress.c
index 14d228f..318b8de 100644
--- a/lib/compress.c
+++ b/lib/compress.c
@@ -1026,6 +1026,8 @@ static int erofs_get_compress_algorithm_id(const char *name)
 		return Z_EROFS_COMPRESSION_LZ4;
 	if (!strcmp(name, "lzma"))
 		return Z_EROFS_COMPRESSION_LZMA;
+	if (!strcmp(name, "deflate"))
+		return Z_EROFS_COMPRESSION_DEFLATE;
 	return -ENOTSUP;
 }
 
@@ -1080,6 +1082,28 @@ int z_erofs_build_compr_cfgs(struct erofs_buffer_head *sb_bh)
 		bh->op = &erofs_drop_directly_bhops;
 	}
 #endif
+	if (sbi.available_compr_algs & (1 << Z_EROFS_COMPRESSION_DEFLATE)) {
+		struct {
+			__le16 size;
+			struct z_erofs_deflate_cfgs z;
+		} __packed zalg = {
+			.size = cpu_to_le16(sizeof(struct z_erofs_deflate_cfgs)),
+			.z = {
+				.windowbits =
+					cpu_to_le32(ilog2(cfg.c_dict_size)),
+			}
+		};
+
+		bh = erofs_battach(bh, META, sizeof(zalg));
+		if (IS_ERR(bh)) {
+			DBG_BUGON(1);
+			return PTR_ERR(bh);
+		}
+		erofs_mapbh(bh->block);
+		ret = dev_write(&zalg, erofs_btell(bh, false),
+				sizeof(zalg));
+		bh->op = &erofs_drop_directly_bhops;
+	}
 	return ret;
 }
 
diff --git a/lib/compressor.c b/lib/compressor.c
index 52eb761..ca4d364 100644
--- a/lib/compressor.c
+++ b/lib/compressor.c
@@ -20,6 +20,7 @@ static const struct erofs_compressor *compressors[] = {
 #if HAVE_LIBLZMA
 		&erofs_compressor_lzma,
 #endif
+		&erofs_compressor_deflate,
 };
 
 int erofs_compress_destsize(const struct erofs_compress *c,
diff --git a/lib/compressor.h b/lib/compressor.h
index cf063f1..c1eee20 100644
--- a/lib/compressor.h
+++ b/lib/compressor.h
@@ -44,6 +44,7 @@ struct erofs_compress {
 extern const struct erofs_compressor erofs_compressor_lz4;
 extern const struct erofs_compressor erofs_compressor_lz4hc;
 extern const struct erofs_compressor erofs_compressor_lzma;
+extern const struct erofs_compressor erofs_compressor_deflate;
 
 int erofs_compress_destsize(const struct erofs_compress *c,
 			    const void *src, unsigned int *srcsize,
diff --git a/lib/compressor_deflate.c b/lib/compressor_deflate.c
new file mode 100644
index 0000000..ad76b7c
--- /dev/null
+++ b/lib/compressor_deflate.c
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0
+/*
+ * Copyright (C) 2023, Alibaba Cloud
+ * Copyright (C) 2023, Gao Xiang <xiang@kernel.org>
+ */
+#include "erofs/internal.h"
+#include "erofs/print.h"
+#include "erofs/config.h"
+#include "compressor.h"
+
+void *kite_deflate_init(int level, unsigned int dict_size);
+void kite_deflate_end(void *s);
+int kite_deflate_destsize(void *s, const u8 *in, u8 *out,
+			  unsigned int *srcsize, unsigned int target_dstsize);
+
+static int deflate_compress_destsize(const struct erofs_compress *c,
+				     const void *src, unsigned int *srcsize,
+				     void *dst, unsigned int dstsize)
+{
+	int rc = kite_deflate_destsize(c->private_data, src, dst,
+				       srcsize, dstsize);
+
+	if (rc <= 0)
+		return -EFAULT;
+	return rc;
+}
+
+static int compressor_deflate_exit(struct erofs_compress *c)
+{
+	if (!c->private_data)
+		return -EINVAL;
+
+	kite_deflate_end(c->private_data);
+	return 0;
+}
+
+static int compressor_deflate_init(struct erofs_compress *c)
+{
+	c->alg = &erofs_compressor_deflate;
+	c->private_data = NULL;
+
+	erofs_warn("EXPERIMENTAL DEFLATE feature in use. Use at your own risk!");
+	erofs_warn("Please *carefully* check filesystem data correctness to avoid corruption!");
+	return 0;
+}
+
+static int erofs_compressor_deflate_setlevel(struct erofs_compress *c,
+					     int compression_level)
+{
+	void *s;
+
+	if (c->private_data) {
+		kite_deflate_end(c->private_data);
+		c->private_data = NULL;
+	}
+
+	if (compression_level < 0)
+		compression_level = erofs_compressor_deflate.default_level;
+
+	s = kite_deflate_init(compression_level, cfg.c_dict_size);
+	if (IS_ERR(s))
+		return PTR_ERR(s);
+
+	c->private_data = s;
+	c->compression_level = compression_level;
+	return 0;
+}
+
+const struct erofs_compressor erofs_compressor_deflate = {
+	.name = "deflate",
+	.default_level = 1,
+	.best_level = 9,
+	.init = compressor_deflate_init,
+	.exit = compressor_deflate_exit,
+	.setlevel = erofs_compressor_deflate_setlevel,
+	.compress_destsize = deflate_compress_destsize,
+};
diff --git a/lib/decompress.c b/lib/decompress.c
index 5bc55c5..a3e8ed7 100644
--- a/lib/decompress.c
+++ b/lib/decompress.c
@@ -133,9 +133,11 @@ static int z_erofs_decompress_deflate(struct z_erofs_decompress_req *rq)
 	strm.avail_out = rq->decodedlength;
 
 	ret = inflate(&strm, rq->partial_decoding ? Z_SYNC_FLUSH : Z_FINISH);
-	if (ret != Z_STREAM_END) {
-		ret = zerr(ret);
-		goto out_inflate_end;
+	if (ret != Z_STREAM_END || strm.total_out != rq->decodedlength) {
+		if (rq->partial_decoding && ret != Z_OK) {
+			ret = zerr(ret);
+			goto out_inflate_end;
+		}
 	}
 
 	if (rq->decodedskip)
-- 
2.24.4


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v2 3/4] erofs-utils: fuse: add DEFLATE algorithm support
  2023-07-09 18:25 [PATCH 0/4] erofs-utils: add DEFLATE compression support Gao Xiang
                   ` (3 preceding siblings ...)
  2023-07-09 18:25 ` [PATCH 4/4] erofs-utils: mkfs: " Gao Xiang
@ 2023-07-09 18:45 ` Gao Xiang
  2023-07-09 18:45   ` [PATCH v2 4/4] erofs-utils: mkfs: " Gao Xiang
  4 siblings, 1 reply; 12+ messages in thread
From: Gao Xiang @ 2023-07-09 18:45 UTC (permalink / raw)
  To: linux-erofs; +Cc: Gao Xiang

This patch adds DEFLATE compression algorithm support to erofsfuse
by using zlib (by default) and libdeflate.  libdeflate will be used
instead of zlib if libdeflate is enabled.

Signed-off-by: Gao Xiang <hsiangkao@linux.alibaba.com>
---
v2:
 move a misplaced logic from [4/4] to [3/4].

 configure.ac       |  45 ++++++++++++++
 dump/Makefile.am   |   3 +-
 fsck/Makefile.am   |   6 +-
 fuse/Makefile.am   |   2 +-
 include/erofs_fs.h |   7 +++
 lib/decompress.c   | 147 +++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 206 insertions(+), 4 deletions(-)

diff --git a/configure.ac b/configure.ac
index cd6be4a..b393064 100644
--- a/configure.ac
+++ b/configure.ac
@@ -122,6 +122,15 @@ AC_ARG_ENABLE(lzma,
    [AS_HELP_STRING([--enable-lzma], [enable LZMA compression support @<:@default=no@:>@])],
    [enable_lzma="$enableval"], [enable_lzma="no"])
 
+AC_ARG_WITH(zlib,
+   [AS_HELP_STRING([--without-zlib],
+      [Ignore presence of zlib inflate support @<:@default=enabled@:>@])])
+
+AC_ARG_WITH(libdeflate,
+   [AS_HELP_STRING([--with-libdeflate],
+      [Enable and build with libdeflate inflate support @<:@default=disabled@:>@])], [],
+      [with_libdeflate="no"])
+
 AC_ARG_ENABLE(fuse,
    [AS_HELP_STRING([--enable-fuse], [enable erofsfuse @<:@default=no@:>@])],
    [enable_fuse="$enableval"], [enable_fuse="no"])
@@ -394,6 +403,34 @@ if test "x$enable_lzma" = "xyes"; then
   CPPFLAGS="${saved_CPPFLAGS}"
 fi
 
+# Configure zlib
+AS_IF([test "x$with_zlib" != "xno"], [
+  PKG_CHECK_MODULES([zlib], [zlib])
+  # Paranoia: don't trust the result reported by pkgconfig before trying out
+  saved_LIBS="$LIBS"
+  saved_CPPFLAGS=${CPPFLAGS}
+  CPPFLAGS="${zlib_CFLAGS} ${CPPFLAGS}"
+  LIBS="${zlib_LIBS} $LIBS"
+  AC_CHECK_LIB(z, inflate, [
+    have_zlib="yes" ], [
+    AC_MSG_ERROR([zlib doesn't work properly])])
+  LIBS="${saved_LIBS}"
+  CPPFLAGS="${saved_CPPFLAGS}"], [have_zlib="no"])
+
+# Configure libdeflate
+AS_IF([test "x$with_libdeflate" != "xno"], [
+  PKG_CHECK_MODULES([libdeflate], [libdeflate])
+  # Paranoia: don't trust the result reported by pkgconfig before trying out
+  saved_LIBS="$LIBS"
+  saved_CPPFLAGS=${CPPFLAGS}
+  CPPFLAGS="${libdeflate_CFLAGS} ${CPPFLAGS}"
+  LIBS="${libdeflate_LIBS} $LIBS"
+  AC_CHECK_LIB(deflate, libdeflate_deflate_decompress, [
+    have_libdeflate="yes" ], [
+    AC_MSG_ERROR([libdeflate doesn't work properly])])
+  LIBS="${saved_LIBS}"
+  CPPFLAGS="${saved_CPPFLAGS}"], [have_libdeflate="no"])
+
 # Enable 64-bit off_t
 CFLAGS+=" -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64"
 
@@ -449,6 +486,14 @@ if test "x${have_liblzma}" = "xyes"; then
   AC_SUBST([liblzma_CFLAGS])
 fi
 
+if test "x$have_zlib" = "xyes"; then
+  AC_DEFINE([HAVE_ZLIB], 1, [Define to 1 if zlib is found])
+fi
+
+if test "x$have_libdeflate" = "xyes"; then
+  AC_DEFINE([HAVE_LIBDEFLATE], 1, [Define to 1 if libdeflate is found])
+fi
+
 # Dump maximum block size
 AS_IF([test "x$erofs_cv_max_block_size" = "x"],
       [$erofs_cv_max_block_size = 4096], [])
diff --git a/dump/Makefile.am b/dump/Makefile.am
index c2bef6d..63f301e 100644
--- a/dump/Makefile.am
+++ b/dump/Makefile.am
@@ -7,4 +7,5 @@ AM_CPPFLAGS = ${libuuid_CFLAGS}
 dump_erofs_SOURCES = main.c
 dump_erofs_CFLAGS = -Wall -I$(top_srcdir)/include
 dump_erofs_LDADD = $(top_builddir)/lib/liberofs.la ${libselinux_LIBS} \
-	${libuuid_LIBS} ${liblz4_LIBS} ${liblzma_LIBS}
+	${libuuid_LIBS} ${liblz4_LIBS} ${liblzma_LIBS} ${zlib_LIBS} \
+	${libdeflate_LIBS}
diff --git a/fsck/Makefile.am b/fsck/Makefile.am
index 9366a9d..ffdd0be 100644
--- a/fsck/Makefile.am
+++ b/fsck/Makefile.am
@@ -7,7 +7,8 @@ AM_CPPFLAGS = ${libuuid_CFLAGS}
 fsck_erofs_SOURCES = main.c
 fsck_erofs_CFLAGS = -Wall -I$(top_srcdir)/include
 fsck_erofs_LDADD = $(top_builddir)/lib/liberofs.la ${libselinux_LIBS} \
-	${libuuid_LIBS} ${liblz4_LIBS} ${liblzma_LIBS}
+	${libuuid_LIBS} ${liblz4_LIBS} ${liblzma_LIBS} ${zlib_LIBS} \
+	${libdeflate_LIBS}
 
 if ENABLE_FUZZING
 noinst_PROGRAMS   = fuzz_erofsfsck
@@ -15,5 +16,6 @@ fuzz_erofsfsck_SOURCES = main.c
 fuzz_erofsfsck_CFLAGS = -Wall -I$(top_srcdir)/include -DFUZZING
 fuzz_erofsfsck_LDFLAGS = -fsanitize=address,fuzzer
 fuzz_erofsfsck_LDADD = $(top_builddir)/lib/liberofs.la ${libselinux_LIBS} \
-	${libuuid_LIBS} ${liblz4_LIBS} ${liblzma_LIBS}
+	${libuuid_LIBS} ${liblz4_LIBS} ${liblzma_LIBS} ${zlib_LIBS} \
+	${libdeflate_LIBS}
 endif
diff --git a/fuse/Makefile.am b/fuse/Makefile.am
index 3179a2b..50be783 100644
--- a/fuse/Makefile.am
+++ b/fuse/Makefile.am
@@ -7,4 +7,4 @@ erofsfuse_SOURCES = main.c
 erofsfuse_CFLAGS = -Wall -I$(top_srcdir)/include
 erofsfuse_CFLAGS += -DFUSE_USE_VERSION=26 ${libfuse_CFLAGS} ${libselinux_CFLAGS}
 erofsfuse_LDADD = $(top_builddir)/lib/liberofs.la ${libfuse_LIBS} ${liblz4_LIBS} \
-	${libselinux_LIBS} ${liblzma_LIBS}
+	${libselinux_LIBS} ${liblzma_LIBS} ${zlib_LIBS} ${libdeflate_LIBS}
diff --git a/include/erofs_fs.h b/include/erofs_fs.h
index 3697882..6b7b34f 100644
--- a/include/erofs_fs.h
+++ b/include/erofs_fs.h
@@ -297,6 +297,7 @@ enum {
 enum {
 	Z_EROFS_COMPRESSION_LZ4		= 0,
 	Z_EROFS_COMPRESSION_LZMA	= 1,
+	Z_EROFS_COMPRESSION_DEFLATE	= 1,
 	Z_EROFS_COMPRESSION_MAX
 };
 #define Z_EROFS_ALL_COMPR_ALGS		((1 << Z_EROFS_COMPRESSION_MAX) - 1)
@@ -317,6 +318,12 @@ struct z_erofs_lzma_cfgs {
 
 #define Z_EROFS_LZMA_MAX_DICT_SIZE	(8 * Z_EROFS_PCLUSTER_MAX_SIZE)
 
+/* 6 bytes (+ length field = 8 bytes) */
+struct z_erofs_deflate_cfgs {
+	u8 windowbits;			/* 8..15 for DEFLATE */
+	u8 reserved[5];
+} __packed;
+
 /*
  * bit 0 : COMPACTED_2B indexes (0 - off; 1 - on)
  *  e.g. for 4k logical cluster size,      4B        if compacted 2B is off;
diff --git a/lib/decompress.c b/lib/decompress.c
index 59a9ca0..04bd24e 100644
--- a/lib/decompress.c
+++ b/lib/decompress.c
@@ -9,6 +9,149 @@
 #include "erofs/err.h"
 #include "erofs/print.h"
 
+#ifdef HAVE_LIBDEFLATE
+/* if libdeflate is available, use libdeflate instead. */
+#include <libdeflate.h>
+
+static int z_erofs_decompress_deflate(struct z_erofs_decompress_req *rq)
+{
+	u8 *dest = (u8 *)rq->out;
+	u8 *src = (u8 *)rq->in;
+	u8 *buff = NULL;
+	size_t actual_out;
+	unsigned int inputmargin = 0;
+	struct libdeflate_decompressor *inf;
+	enum libdeflate_result ret;
+
+	while (!src[inputmargin & (erofs_blksiz() - 1)])
+		if (!(++inputmargin & (erofs_blksiz() - 1)))
+			break;
+
+	if (inputmargin >= rq->inputsize)
+		return -EFSCORRUPTED;
+
+	if (rq->decodedskip) {
+		buff = malloc(rq->decodedlength);
+		if (!buff)
+			return -ENOMEM;
+		dest = buff;
+	}
+
+	inf = libdeflate_alloc_decompressor();
+	if (!inf)
+		return -ENOMEM;
+
+	if (rq->partial_decoding) {
+		ret = libdeflate_deflate_decompress(inf, src + inputmargin,
+				rq->inputsize - inputmargin, dest,
+				rq->decodedlength, &actual_out);
+		if (ret && ret != LIBDEFLATE_INSUFFICIENT_SPACE) {
+			ret = -EIO;
+			goto out_inflate_end;
+		}
+
+		if (actual_out != rq->decodedlength) {
+			ret = -EIO;
+			goto out_inflate_end;
+		}
+	} else {
+		ret = libdeflate_deflate_decompress(inf, src + inputmargin,
+				rq->inputsize - inputmargin, dest,
+				rq->decodedlength, NULL);
+		if (ret) {
+			ret = -EIO;
+			goto out_inflate_end;
+		}
+	}
+
+	if (rq->decodedskip)
+		memcpy(rq->out, dest + rq->decodedskip,
+		       rq->decodedlength - rq->decodedskip);
+
+out_inflate_end:
+	libdeflate_free_decompressor(inf);
+	if (buff)
+		free(buff);
+	return ret;
+}
+#elif defined(HAVE_ZLIB)
+#include <zlib.h>
+
+/* report a zlib or i/o error */
+static int zerr(int ret)
+{
+	switch (ret) {
+	case Z_STREAM_ERROR:
+		return -EINVAL;
+	case Z_DATA_ERROR:
+		return -EIO;
+	case Z_MEM_ERROR:
+		return -ENOMEM;
+	case Z_ERRNO:
+	case Z_VERSION_ERROR:
+	default:
+		return -EFAULT;
+	}
+}
+
+static int z_erofs_decompress_deflate(struct z_erofs_decompress_req *rq)
+{
+	int ret = 0;
+	u8 *dest = (u8 *)rq->out;
+	u8 *src = (u8 *)rq->in;
+	u8 *buff = NULL;
+	unsigned int inputmargin = 0;
+	z_stream strm;
+
+	while (!src[inputmargin & (erofs_blksiz() - 1)])
+		if (!(++inputmargin & (erofs_blksiz() - 1)))
+			break;
+
+	if (inputmargin >= rq->inputsize)
+		return -EFSCORRUPTED;
+
+	if (rq->decodedskip) {
+		buff = malloc(rq->decodedlength);
+		if (!buff)
+			return -ENOMEM;
+		dest = buff;
+	}
+
+	/* allocate inflate state */
+	strm.zalloc = Z_NULL;
+	strm.zfree = Z_NULL;
+	strm.opaque = Z_NULL;
+	strm.avail_in = 0;
+	strm.next_in = Z_NULL;
+	ret = inflateInit2(&strm, -15);
+	if (ret != Z_OK)
+		return zerr(ret);
+
+	strm.next_in = src + inputmargin;
+	strm.avail_in = rq->inputsize - inputmargin;
+	strm.next_out = dest;
+	strm.avail_out = rq->decodedlength;
+
+	ret = inflate(&strm, rq->partial_decoding ? Z_SYNC_FLUSH : Z_FINISH);
+	if (ret != Z_STREAM_END || strm.total_out != rq->decodedlength) {
+		if (ret != Z_OK || !rq->partial_decoding) {
+			ret = zerr(ret);
+			goto out_inflate_end;
+		}
+	}
+
+	if (rq->decodedskip)
+		memcpy(rq->out, dest + rq->decodedskip,
+		       rq->decodedlength - rq->decodedskip);
+
+out_inflate_end:
+	inflateEnd(&strm);
+	if (buff)
+		free(buff);
+	return ret;
+}
+#endif
+
 #ifdef HAVE_LIBLZMA
 #include <lzma.h>
 
@@ -167,6 +310,10 @@ int z_erofs_decompress(struct z_erofs_decompress_req *rq)
 #ifdef HAVE_LIBLZMA
 	if (rq->alg == Z_EROFS_COMPRESSION_LZMA)
 		return z_erofs_decompress_lzma(rq);
+#endif
+#ifdef HAVE_ZLIB
+	if (rq->alg == Z_EROFS_COMPRESSION_DEFLATE)
+		return z_erofs_decompress_deflate(rq);
 #endif
 	return -EOPNOTSUPP;
 }
-- 
2.24.4


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v2 4/4] erofs-utils: mkfs: add DEFLATE algorithm support
  2023-07-09 18:45 ` [PATCH v2 3/4] erofs-utils: fuse: " Gao Xiang
@ 2023-07-09 18:45   ` Gao Xiang
  0 siblings, 0 replies; 12+ messages in thread
From: Gao Xiang @ 2023-07-09 18:45 UTC (permalink / raw)
  To: linux-erofs; +Cc: Gao Xiang

This patch adds DEFLATE compression algorithm support to erofs-utils
compression framework.

Note that windowbits (which indicates dictionary size) is recorded in
the on-disk compression configuration.  Since some accelerators (e.g.
Intel IAA) don't have enough on-chip memory, compressed data generated
with large windowbits (e.g. > 12 for the IAA accelerator) doesn't seem
to be worked properly on those.

Signed-off-by: Gao Xiang <hsiangkao@linux.alibaba.com>
---
v2:
  improve print message.

 lib/Makefile.am          |  2 +-
 lib/compress.c           | 24 +++++++++++++
 lib/compressor.c         |  1 +
 lib/compressor.h         |  1 +
 lib/compressor_deflate.c | 78 ++++++++++++++++++++++++++++++++++++++++
 5 files changed, 105 insertions(+), 1 deletion(-)
 create mode 100644 lib/compressor_deflate.c

diff --git a/lib/Makefile.am b/lib/Makefile.am
index fa3b804..553b387 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -44,4 +44,4 @@ liberofs_la_CFLAGS += ${liblzma_CFLAGS}
 liberofs_la_SOURCES += compressor_liblzma.c
 endif
 
-liberofs_la_SOURCES += kite_deflate.c
+liberofs_la_SOURCES += kite_deflate.c compressor_deflate.c
diff --git a/lib/compress.c b/lib/compress.c
index 14d228f..318b8de 100644
--- a/lib/compress.c
+++ b/lib/compress.c
@@ -1026,6 +1026,8 @@ static int erofs_get_compress_algorithm_id(const char *name)
 		return Z_EROFS_COMPRESSION_LZ4;
 	if (!strcmp(name, "lzma"))
 		return Z_EROFS_COMPRESSION_LZMA;
+	if (!strcmp(name, "deflate"))
+		return Z_EROFS_COMPRESSION_DEFLATE;
 	return -ENOTSUP;
 }
 
@@ -1080,6 +1082,28 @@ int z_erofs_build_compr_cfgs(struct erofs_buffer_head *sb_bh)
 		bh->op = &erofs_drop_directly_bhops;
 	}
 #endif
+	if (sbi.available_compr_algs & (1 << Z_EROFS_COMPRESSION_DEFLATE)) {
+		struct {
+			__le16 size;
+			struct z_erofs_deflate_cfgs z;
+		} __packed zalg = {
+			.size = cpu_to_le16(sizeof(struct z_erofs_deflate_cfgs)),
+			.z = {
+				.windowbits =
+					cpu_to_le32(ilog2(cfg.c_dict_size)),
+			}
+		};
+
+		bh = erofs_battach(bh, META, sizeof(zalg));
+		if (IS_ERR(bh)) {
+			DBG_BUGON(1);
+			return PTR_ERR(bh);
+		}
+		erofs_mapbh(bh->block);
+		ret = dev_write(&zalg, erofs_btell(bh, false),
+				sizeof(zalg));
+		bh->op = &erofs_drop_directly_bhops;
+	}
 	return ret;
 }
 
diff --git a/lib/compressor.c b/lib/compressor.c
index 52eb761..ca4d364 100644
--- a/lib/compressor.c
+++ b/lib/compressor.c
@@ -20,6 +20,7 @@ static const struct erofs_compressor *compressors[] = {
 #if HAVE_LIBLZMA
 		&erofs_compressor_lzma,
 #endif
+		&erofs_compressor_deflate,
 };
 
 int erofs_compress_destsize(const struct erofs_compress *c,
diff --git a/lib/compressor.h b/lib/compressor.h
index cf063f1..c1eee20 100644
--- a/lib/compressor.h
+++ b/lib/compressor.h
@@ -44,6 +44,7 @@ struct erofs_compress {
 extern const struct erofs_compressor erofs_compressor_lz4;
 extern const struct erofs_compressor erofs_compressor_lz4hc;
 extern const struct erofs_compressor erofs_compressor_lzma;
+extern const struct erofs_compressor erofs_compressor_deflate;
 
 int erofs_compress_destsize(const struct erofs_compress *c,
 			    const void *src, unsigned int *srcsize,
diff --git a/lib/compressor_deflate.c b/lib/compressor_deflate.c
new file mode 100644
index 0000000..5a7a657
--- /dev/null
+++ b/lib/compressor_deflate.c
@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0
+/*
+ * Copyright (C) 2023, Alibaba Cloud
+ * Copyright (C) 2023, Gao Xiang <xiang@kernel.org>
+ */
+#include "erofs/internal.h"
+#include "erofs/print.h"
+#include "erofs/config.h"
+#include "compressor.h"
+
+void *kite_deflate_init(int level, unsigned int dict_size);
+void kite_deflate_end(void *s);
+int kite_deflate_destsize(void *s, const u8 *in, u8 *out,
+			  unsigned int *srcsize, unsigned int target_dstsize);
+
+static int deflate_compress_destsize(const struct erofs_compress *c,
+				     const void *src, unsigned int *srcsize,
+				     void *dst, unsigned int dstsize)
+{
+	int rc = kite_deflate_destsize(c->private_data, src, dst,
+				       srcsize, dstsize);
+
+	if (rc <= 0)
+		return -EFAULT;
+	return rc;
+}
+
+static int compressor_deflate_exit(struct erofs_compress *c)
+{
+	if (!c->private_data)
+		return -EINVAL;
+
+	kite_deflate_end(c->private_data);
+	return 0;
+}
+
+static int compressor_deflate_init(struct erofs_compress *c)
+{
+	c->alg = &erofs_compressor_deflate;
+	c->private_data = NULL;
+
+	erofs_warn("EXPERIMENTAL DEFLATE algorithm in use. Use at your own risk!");
+	erofs_warn("*Carefully* check filesystem data correctness to avoid corruption!");
+	erofs_warn("Please send a report to <linux-erofs@lists.ozlabs.org> if something is wrong.");
+	return 0;
+}
+
+static int erofs_compressor_deflate_setlevel(struct erofs_compress *c,
+					     int compression_level)
+{
+	void *s;
+
+	if (c->private_data) {
+		kite_deflate_end(c->private_data);
+		c->private_data = NULL;
+	}
+
+	if (compression_level < 0)
+		compression_level = erofs_compressor_deflate.default_level;
+
+	s = kite_deflate_init(compression_level, cfg.c_dict_size);
+	if (IS_ERR(s))
+		return PTR_ERR(s);
+
+	c->private_data = s;
+	c->compression_level = compression_level;
+	return 0;
+}
+
+const struct erofs_compressor erofs_compressor_deflate = {
+	.name = "deflate",
+	.default_level = 1,
+	.best_level = 9,
+	.init = compressor_deflate_init,
+	.exit = compressor_deflate_exit,
+	.setlevel = erofs_compressor_deflate_setlevel,
+	.compress_destsize = deflate_compress_destsize,
+};
-- 
2.24.4


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* Re: [PATCH 2/4] erofs-utils: add a built-in DEFLATE compressor
  2023-07-09 18:25 ` [PATCH 2/4] erofs-utils: add a built-in DEFLATE compressor Gao Xiang
@ 2023-07-10  2:33   ` Eric Biggers
  2023-07-10  3:01     ` Gao Xiang
  0 siblings, 1 reply; 12+ messages in thread
From: Eric Biggers @ 2023-07-10  2:33 UTC (permalink / raw)
  To: Gao Xiang; +Cc: linux-erofs

Hi Gao,

On Mon, Jul 10, 2023 at 02:25:09AM +0800, Gao Xiang wrote:
> 
> Since there is _no fixed-sized output DEFLATE compression appoach_
> available in public (fitblk is somewhat ineffective) and the original
> zlib is quite slowly developping, let's work out one for our use cases.
> Fortunately, it's only less than 1.5kLOC with lazy matching to match
> the basic zlib ability.  Besides, near-optimal block splitting (based
> on price function) doesn't support for now since it's not rush for us.
> 
> In the future, there may be more built-in optimizers landed to fulfill
> our needs even further.  In addition, I'd be quite happy to see more
> popular encoders to support fixed-sized output compression too.
> 
> [1] https://developer.apple.com/documentation/compression/compression_algorithm
> [2] https://datatracker.ietf.org/doc/html/rfc1951
> Signed-off-by: Gao Xiang <hsiangkao@linux.alibaba.com>
> ---
>  lib/Makefile.am    |    2 +
>  lib/kite_deflate.c | 1267 ++++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 1269 insertions(+)
>  create mode 100644 lib/kite_deflate.c

Have you considered simply running any standard compressor multiple times to
find the maximum input size that compresses to less than or equal to the desired
output size?  It may sound too slow, but it can be made to do the search in a
smart way (binary search + heuristics).  Also, it would easily work with every
compressor, and it would always produce the optimal input size.

I wrote a proof-of-concept patch that implements this idea in the benchmark
program in libdeflate.  It finds the optimal input size in about 8 attempts on
average for a target output size of 4096, or about 13 for a target output size
of 65536.  Note, if an optimal solution is not needed, it could be sped up
slightly by stopping when the output size is at, or just under (if desired), the
target output size.  Also keep in mind that any compression level can be used.

The full code I tested is currently at
https://github.com/ebiggers/libdeflate/tree/fixed-output-size, but below is the
actual algorithm:

/*
 * Compresses the largest prefix of 'in', with maximum size 'in_nbytes', whose
 * compressed output is at most 'target_output_size' bytes.  The compressed size
 * is returned as the return value, and the uncompressed size is returned in
 * *actual_in_nbytes_ret.  The output buffer 'out' has size 'out_nbytes_avail',
 * which should be at least 'target_output_size + 9'.  'tmpbuf' must be a buffer
 * the same size as 'out'.
 */
static size_t
do_compress_withfixedoutputsize(struct compressor *c,
				const u8 *in, size_t in_nbytes,
				u8 *out, size_t out_nbytes_avail,
				size_t target_output_size,
				size_t last_uncompressed_size,
				u8 *tmpbuf,
				size_t *actual_in_nbytes_ret)
{
	size_t l = 0; /* largest input that fits so far */
	size_t l_csize = 0;
	size_t r = in_nbytes + 1; /* smallest input that doesn't fit so far */
	size_t m;

	if (last_uncompressed_size)
		m = last_uncompressed_size * 15 / 16;
	else
		m = target_output_size * 4;
	for (;;) {
		size_t csize;

		m = MAX(m, l + 1);
		m = MIN(m, r - 1);

		csize = do_compress(c, in, m, tmpbuf, out_nbytes_avail);
		if (csize > 0 && csize <= target_output_size) {
			/* Fits */
			memcpy(out, tmpbuf, csize);
			l = m;
			l_csize = csize;
			if (r <= l + 1)
				break;
			/*
			 * Estimate needed input prefix size based on current
			 * compression ratio.
			 */
			m = (target_output_size * m) / csize;
		} else {
			/* Doesn't fit */
			r = m;
			if (r <= l + 1)
				break;
			m = (l + r) / 2;
		}
	}
	*actual_in_nbytes_ret = l;
	return l_csize;
}

What do you think?

- Eric

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 2/4] erofs-utils: add a built-in DEFLATE compressor
  2023-07-10  2:33   ` Eric Biggers
@ 2023-07-10  3:01     ` Gao Xiang
  2023-07-10  4:16       ` Eric Biggers
  0 siblings, 1 reply; 12+ messages in thread
From: Gao Xiang @ 2023-07-10  3:01 UTC (permalink / raw)
  To: Eric Biggers; +Cc: linux-erofs

Hi Eric,

On 2023/7/10 10:33, Eric Biggers wrote:
> Hi Gao,
> 
> On Mon, Jul 10, 2023 at 02:25:09AM +0800, Gao Xiang wrote:
>>
>> Since there is _no fixed-sized output DEFLATE compression appoach_
>> available in public (fitblk is somewhat ineffective) and the original
>> zlib is quite slowly developping, let's work out one for our use cases.
>> Fortunately, it's only less than 1.5kLOC with lazy matching to match
>> the basic zlib ability.  Besides, near-optimal block splitting (based
>> on price function) doesn't support for now since it's not rush for us.
>>
>> In the future, there may be more built-in optimizers landed to fulfill
>> our needs even further.  In addition, I'd be quite happy to see more
>> popular encoders to support fixed-sized output compression too.
>>
>> [1] https://developer.apple.com/documentation/compression/compression_algorithm
>> [2] https://datatracker.ietf.org/doc/html/rfc1951
>> Signed-off-by: Gao Xiang <hsiangkao@linux.alibaba.com>
>> ---
>>   lib/Makefile.am    |    2 +
>>   lib/kite_deflate.c | 1267 ++++++++++++++++++++++++++++++++++++++++++++
>>   2 files changed, 1269 insertions(+)
>>   create mode 100644 lib/kite_deflate.c
> 
> Have you considered simply running any standard compressor multiple times to
> find the maximum input size that compresses to less than or equal to the desired
> output size?  It may sound too slow, but it can be made to do the search in a

Thanks for your interest and reply!

Actually I've tried several ways before (just like fitblk.c or binary search),
but the compression ratios is sliently impacted and compression speed is really
impacted (maybe we need to develop segment-splitted multi-threadded compression
as well, and it's developping by a college student now but I'm not sure the
progress.)

I think we could use this way first for the new Zstd support (if they don't have
bandwidth to add a offical fixed-sized output approach), but considering deflate
algorithm is quite easy for me to understand so at least I could have a simple
built-in one to avoid external dependency (mainly considering zlib since it's
quite outdated).  Since EROFS is actually offline compression so the compressed
data correctness can always be checked in advance before image release.

Also I have more ideas to optimize the last symbol from matchfinders (even lazy
matching) for fixed-sized output approaches to get even better compression
ratios (especially for smaller filesystem cluster like 4kb.)

> smart way (binary search + heuristics).  Also, it would easily work with every
> compressor, and it would always produce the optimal input size.
> 
> I wrote a proof-of-concept patch that implements this idea in the benchmark
> program in libdeflate.  It finds the optimal input size in about 8 attempts on
> average for a target output size of 4096, or about 13 for a target output size
> of 65536.  Note, if an optimal solution is not needed, it could be sped up
> slightly by stopping when the output size is at, or just under (if desired), the
> target output size.  Also keep in mind that any compression level can be used.
> 
> The full code I tested is currently at
> https://github.com/ebiggers/libdeflate/tree/fixed-output-size, but below is the
> actual algorithm:

If libdeflate officically supports this mode, I'm very happy to integrate
libdeflate in this way as an alternative encoder (if you could merge this), and
if the compression speed is improved, I could switch it to the default encoder
then. In the other words, I really hope there could be an official deflate
encoder to support this mode as well.  Also I think the in-kernel zlib
decompression is somewhat slow just due to some non-technical reasons, I might
need to improve this eveutually but it's no rush for us compared to support
DEFLATE format first as well.

> 
> /*
>   * Compresses the largest prefix of 'in', with maximum size 'in_nbytes', whose
>   * compressed output is at most 'target_output_size' bytes.  The compressed size
>   * is returned as the return value, and the uncompressed size is returned in
>   * *actual_in_nbytes_ret.  The output buffer 'out' has size 'out_nbytes_avail',
>   * which should be at least 'target_output_size + 9'.  'tmpbuf' must be a buffer
>   * the same size as 'out'.
>   */
> static size_t
> do_compress_withfixedoutputsize(struct compressor *c,
> 				const u8 *in, size_t in_nbytes,
> 				u8 *out, size_t out_nbytes_avail,
> 				size_t target_output_size,
> 				size_t last_uncompressed_size,
> 				u8 *tmpbuf,
> 				size_t *actual_in_nbytes_ret)
> {
> 	size_t l = 0; /* largest input that fits so far */
> 	size_t l_csize = 0;
> 	size_t r = in_nbytes + 1; /* smallest input that doesn't fit so far */
> 	size_t m;
> 
> 	if (last_uncompressed_size)
> 		m = last_uncompressed_size * 15 / 16;
> 	else
> 		m = target_output_size * 4;
> 	for (;;) {
> 		size_t csize;
> 
> 		m = MAX(m, l + 1);
> 		m = MIN(m, r - 1);
> 
> 		csize = do_compress(c, in, m, tmpbuf, out_nbytes_avail);
> 		if (csize > 0 && csize <= target_output_size) {
> 			/* Fits */
> 			memcpy(out, tmpbuf, csize);
> 			l = m;
> 			l_csize = csize;
> 			if (r <= l + 1)
> 				break;
> 			/*
> 			 * Estimate needed input prefix size based on current
> 			 * compression ratio.
> 			 */
> 			m = (target_output_size * m) / csize;
> 		} else {
> 			/* Doesn't fit */
> 			r = m;
> 			if (r <= l + 1)
> 				break;
> 			m = (l + r) / 2;
> 		}
> 	}
> 	*actual_in_nbytes_ret = l;
> 	return l_csize;
> }
> 
> What do you think?

Thank you for the time and your efforts, I much appreciate if it could be
supported so I could add a alternative encoder for this.

Thanks,
Gao Xiang

> 
> - Eric

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 2/4] erofs-utils: add a built-in DEFLATE compressor
  2023-07-10  3:01     ` Gao Xiang
@ 2023-07-10  4:16       ` Eric Biggers
  2023-07-10  4:51         ` Gao Xiang
  0 siblings, 1 reply; 12+ messages in thread
From: Eric Biggers @ 2023-07-10  4:16 UTC (permalink / raw)
  To: Gao Xiang; +Cc: linux-erofs

On Mon, Jul 10, 2023 at 11:01:16AM +0800, Gao Xiang wrote:
> Hi Eric,
> 
> On 2023/7/10 10:33, Eric Biggers wrote:
> > Hi Gao,
> > 
> > On Mon, Jul 10, 2023 at 02:25:09AM +0800, Gao Xiang wrote:
> > > 
> > > Since there is _no fixed-sized output DEFLATE compression appoach_
> > > available in public (fitblk is somewhat ineffective) and the original
> > > zlib is quite slowly developping, let's work out one for our use cases.
> > > Fortunately, it's only less than 1.5kLOC with lazy matching to match
> > > the basic zlib ability.  Besides, near-optimal block splitting (based
> > > on price function) doesn't support for now since it's not rush for us.
> > > 
> > > In the future, there may be more built-in optimizers landed to fulfill
> > > our needs even further.  In addition, I'd be quite happy to see more
> > > popular encoders to support fixed-sized output compression too.
> > > 
> > > [1] https://developer.apple.com/documentation/compression/compression_algorithm
> > > [2] https://datatracker.ietf.org/doc/html/rfc1951
> > > Signed-off-by: Gao Xiang <hsiangkao@linux.alibaba.com>
> > > ---
> > >   lib/Makefile.am    |    2 +
> > >   lib/kite_deflate.c | 1267 ++++++++++++++++++++++++++++++++++++++++++++
> > >   2 files changed, 1269 insertions(+)
> > >   create mode 100644 lib/kite_deflate.c
> > 
> > Have you considered simply running any standard compressor multiple times to
> > find the maximum input size that compresses to less than or equal to the desired
> > output size?  It may sound too slow, but it can be made to do the search in a
> 
> Thanks for your interest and reply!
> 
> Actually I've tried several ways before (just like fitblk.c or binary search),
> but the compression ratios is sliently impacted

That's strange, since the method I suggested should improve the compression
ratio.

> and compression speed is really
> impacted (maybe we need to develop segment-splitted multi-threadded compression
> as well, and it's developping by a college student now but I'm not sure the
> progress.)

I'm surprised that multi-threaded compression isn't something you have already!

> 
> I think we could use this way first for the new Zstd support (if they don't have
> bandwidth to add a offical fixed-sized output approach), but considering deflate
> algorithm is quite easy for me to understand so at least I could have a simple
> built-in one to avoid external dependency (mainly considering zlib since it's
> quite outdated).  Since EROFS is actually offline compression so the compressed
> data correctness can always be checked in advance before image release.

Writing an optimized compressor, especially one that implements high compression
levels, is very hard.  Are you sure it's something you want to implement and
maintain in erofs-utils?

> Also I have more ideas to optimize the last symbol from matchfinders (even lazy
> matching) for fixed-sized output approaches to get even better compression
> ratios (especially for smaller filesystem cluster like 4kb.)

Yes, there are some tricks that compressors that natively support
fixed-size-output mode could use in order to avoid redundant work.

> > smart way (binary search + heuristics).  Also, it would easily work with every
> > compressor, and it would always produce the optimal input size.
> > 
> > I wrote a proof-of-concept patch that implements this idea in the benchmark
> > program in libdeflate.  It finds the optimal input size in about 8 attempts on
> > average for a target output size of 4096, or about 13 for a target output size
> > of 65536.  Note, if an optimal solution is not needed, it could be sped up
> > slightly by stopping when the output size is at, or just under (if desired), the
> > target output size.  Also keep in mind that any compression level can be used.
> > 
> > The full code I tested is currently at
> > https://github.com/ebiggers/libdeflate/tree/fixed-output-size, but below is the
> > actual algorithm:
> 
> If libdeflate officically supports this mode, I'm very happy to integrate
> libdeflate in this way as an alternative encoder (if you could merge this), and
> if the compression speed is improved, I could switch it to the default encoder
> then. In the other words, I really hope there could be an official deflate
> encoder to support this mode as well.  Also I think the in-kernel zlib
> decompression is somewhat slow just due to some non-technical reasons, I might
> need to improve this eveutually but it's no rush for us compared to support
> DEFLATE format first as well.
> 
> > 
> > /*
> >   * Compresses the largest prefix of 'in', with maximum size 'in_nbytes', whose
> >   * compressed output is at most 'target_output_size' bytes.  The compressed size
> >   * is returned as the return value, and the uncompressed size is returned in
> >   * *actual_in_nbytes_ret.  The output buffer 'out' has size 'out_nbytes_avail',
> >   * which should be at least 'target_output_size + 9'.  'tmpbuf' must be a buffer
> >   * the same size as 'out'.
> >   */
> > static size_t
> > do_compress_withfixedoutputsize(struct compressor *c,
> > 				const u8 *in, size_t in_nbytes,
> > 				u8 *out, size_t out_nbytes_avail,
> > 				size_t target_output_size,
> > 				size_t last_uncompressed_size,
> > 				u8 *tmpbuf,
> > 				size_t *actual_in_nbytes_ret)
> > {
> > 	size_t l = 0; /* largest input that fits so far */
> > 	size_t l_csize = 0;
> > 	size_t r = in_nbytes + 1; /* smallest input that doesn't fit so far */
> > 	size_t m;
> > 
> > 	if (last_uncompressed_size)
> > 		m = last_uncompressed_size * 15 / 16;
> > 	else
> > 		m = target_output_size * 4;
> > 	for (;;) {
> > 		size_t csize;
> > 
> > 		m = MAX(m, l + 1);
> > 		m = MIN(m, r - 1);
> > 
> > 		csize = do_compress(c, in, m, tmpbuf, out_nbytes_avail);
> > 		if (csize > 0 && csize <= target_output_size) {
> > 			/* Fits */
> > 			memcpy(out, tmpbuf, csize);
> > 			l = m;
> > 			l_csize = csize;
> > 			if (r <= l + 1)
> > 				break;
> > 			/*
> > 			 * Estimate needed input prefix size based on current
> > 			 * compression ratio.
> > 			 */
> > 			m = (target_output_size * m) / csize;
> > 		} else {
> > 			/* Doesn't fit */
> > 			r = m;
> > 			if (r <= l + 1)
> > 				break;
> > 			m = (l + r) / 2;
> > 		}
> > 	}
> > 	*actual_in_nbytes_ret = l;
> > 	return l_csize;
> > }
> > 
> > What do you think?
> 
> Thank you for the time and your efforts, I much appreciate if it could be
> supported so I could add a alternative encoder for this.
> 

Native fixed-size-output size support in libdeflate is something I'll think
about.  However, the point I'm making is that it's unclear how valuable native
fixed-size-output size support in compressors is, given that the search
algorithm I described above actually seems to work fairly well, and it can
produce the optimal solution.

Here's a quick proof of concept patch for mkfs.erofs:

diff --git a/lib/Makefile.am b/lib/Makefile.am
index 553b387..f58a684 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -45,3 +45,4 @@ liberofs_la_SOURCES += compressor_liblzma.c
 endif
 
 liberofs_la_SOURCES += kite_deflate.c compressor_deflate.c
+liberofs_la_LDFLAGS = -ldeflate
diff --git a/lib/compressor_deflate.c b/lib/compressor_deflate.c
index ad76b7c..d7a5e90 100644
--- a/lib/compressor_deflate.c
+++ b/lib/compressor_deflate.c
@@ -6,6 +6,7 @@
 #include "erofs/internal.h"
 #include "erofs/print.h"
 #include "erofs/config.h"
+#include "libdeflate.h"
 #include "compressor.h"
 
 void *kite_deflate_init(int level, unsigned int dict_size);
@@ -13,16 +14,64 @@ void kite_deflate_end(void *s);
 int kite_deflate_destsize(void *s, const u8 *in, u8 *out,
 			  unsigned int *srcsize, unsigned int target_dstsize);
 
+#define USE_LIBDEFLATE
+
 static int deflate_compress_destsize(const struct erofs_compress *c,
 				     const void *src, unsigned int *srcsize,
 				     void *dst, unsigned int dstsize)
 {
+#ifdef USE_LIBDEFLATE
+	static size_t last_uncompressed_size = 0;
+	size_t l = 0; /* largest input that fits so far */
+	size_t l_csize = 0;
+	size_t r = *srcsize + 1; /* smallest input that doesn't fit so far */
+	size_t m;
+	u8 tmpbuf[dstsize + 9];
+
+	if (last_uncompressed_size)
+		m = last_uncompressed_size * 15 / 16;
+	else
+		m = dstsize * 4;
+	for (;;) {
+		size_t csize;
+
+		m = max(m, l + 1);
+		m = min(m, r - 1);
+
+		csize = libdeflate_deflate_compress(c->private_data, src, m, tmpbuf, dstsize + 9);
+		/*printf("Tried %zu => %zu\n", m, csize);*/
+		if (csize > 0 && csize <= dstsize) {
+			/* Fits */
+			memcpy(dst, tmpbuf, csize);
+			l = m;
+			l_csize = csize;
+			if (r <= l + 1 || csize + (22 - 2*(int)c->compression_level) >= dstsize)
+				break;
+			/*
+			 * Estimate needed input prefix size based on current
+			 * compression ratio.
+			 */
+			m = (dstsize * m) / csize;
+		} else {
+			/* Doesn't fit */
+			r = m;
+			if (r <= l + 1)
+				break;
+			m = (l + r) / 2;
+		}
+	}
+	/*printf("Choosing %zu => %zu\n", l, l_csize);*/
+	*srcsize = l;
+	last_uncompressed_size = l;
+	return l_csize;
+#else
 	int rc = kite_deflate_destsize(c->private_data, src, dst,
 				       srcsize, dstsize);
 
 	if (rc <= 0)
 		return -EFAULT;
 	return rc;
+#endif
 }
 
 static int compressor_deflate_exit(struct erofs_compress *c)
@@ -30,7 +79,11 @@ static int compressor_deflate_exit(struct erofs_compress *c)
 	if (!c->private_data)
 		return -EINVAL;
 
+#ifdef USE_LIBDEFLATE
+	libdeflate_free_compressor(c->private_data);
+#else
 	kite_deflate_end(c->private_data);
+#endif
 	return 0;
 }
 
@@ -47,8 +100,13 @@ static int compressor_deflate_init(struct erofs_compress *c)
 static int erofs_compressor_deflate_setlevel(struct erofs_compress *c,
 					     int compression_level)
 {
-	void *s;
+	if (compression_level < 0)
+		compression_level = erofs_compressor_deflate.default_level;
 
+#ifdef USE_LIBDEFLATE
+	libdeflate_free_compressor(c->private_data);
+	c->private_data = libdeflate_alloc_compressor(compression_level);
+#else
 	if (c->private_data) {
 		kite_deflate_end(c->private_data);
 		c->private_data = NULL;
@@ -57,11 +115,13 @@ static int erofs_compressor_deflate_setlevel(struct erofs_compress *c,
 	if (compression_level < 0)
 		compression_level = erofs_compressor_deflate.default_level;
 
-	s = kite_deflate_init(compression_level, cfg.c_dict_size);
+	void *s = kite_deflate_init(compression_level, cfg.c_dict_size);
 	if (IS_ERR(s))
 		return PTR_ERR(s);
 
 	c->private_data = s;
+#endif
+
 	c->compression_level = compression_level;
 	return 0;
 }
@@ -69,7 +129,11 @@ static int erofs_compressor_deflate_setlevel(struct erofs_compress *c,
 const struct erofs_compressor erofs_compressor_deflate = {
 	.name = "deflate",
 	.default_level = 1,
+#ifdef USE_LIBDEFLATE
+	.best_level = 12,
+#else
 	.best_level = 9,
+#endif
 	.init = compressor_deflate_init,
 	.exit = compressor_deflate_exit,
 	.setlevel = erofs_compressor_deflate_setlevel,

Here are some quick benchmarks I did of mkfs.erofs showing the size of the
resulting filesystem and the time it took for mkfs.erofs to run:

-zdeflate,1
    kite_deflate: 10772480 bytes in 0.474s
    libdeflate: 10366976 bytes in 0.468s
-zdeflate,3
    kite_deflate: 10203136 bytes in 0.511s
    libdeflate: 10092544 bytes in 0.702s
-zdeflate,5
    kite_deflate: 10080256 bytes in 0.596s
    libdeflate: 9973760 bytes in 0.896s
-zdeflate,6
    kite_deflate: 9977856 bytes in 0.650s
    libdeflate: 9936896 bytes in 0.981s
-zdeflate,7
    kite_deflate: 9969664 bytes in 0.690s
    libdeflate: 9920512 bytes in 1.187s
-zdeflate,9
    kite_deflate: 9945088 bytes in 0.986s
    libdeflate: 9871360 bytes in 2.041s
-zdeflate,12
    kite_deflate: N/A
    libdeflate: 9740288 bytes in 28.080s

So it seems my "naive" search algorithm, when combined with libdeflate, actually
does about as well as kite_deflate...  Plus it automatically brings in support
for higher compression levels.

- Eric

^ permalink raw reply related	[flat|nested] 12+ messages in thread

* Re: [PATCH 2/4] erofs-utils: add a built-in DEFLATE compressor
  2023-07-10  4:16       ` Eric Biggers
@ 2023-07-10  4:51         ` Gao Xiang
  2023-07-10  4:55           ` Gao Xiang
  0 siblings, 1 reply; 12+ messages in thread
From: Gao Xiang @ 2023-07-10  4:51 UTC (permalink / raw)
  To: Eric Biggers; +Cc: linux-erofs



On 2023/7/10 12:16, Eric Biggers wrote:
> On Mon, Jul 10, 2023 at 11:01:16AM +0800, Gao Xiang wrote:
>> Hi Eric,
>>
>> On 2023/7/10 10:33, Eric Biggers wrote:
>>> Hi Gao,
>>>
>>> On Mon, Jul 10, 2023 at 02:25:09AM +0800, Gao Xiang wrote:
>>>>
>>>> Since there is _no fixed-sized output DEFLATE compression appoach_
>>>> available in public (fitblk is somewhat ineffective) and the original
>>>> zlib is quite slowly developping, let's work out one for our use cases.
>>>> Fortunately, it's only less than 1.5kLOC with lazy matching to match
>>>> the basic zlib ability.  Besides, near-optimal block splitting (based
>>>> on price function) doesn't support for now since it's not rush for us.
>>>>
>>>> In the future, there may be more built-in optimizers landed to fulfill
>>>> our needs even further.  In addition, I'd be quite happy to see more
>>>> popular encoders to support fixed-sized output compression too.
>>>>
>>>> [1] https://developer.apple.com/documentation/compression/compression_algorithm
>>>> [2] https://datatracker.ietf.org/doc/html/rfc1951
>>>> Signed-off-by: Gao Xiang <hsiangkao@linux.alibaba.com>
>>>> ---
>>>>    lib/Makefile.am    |    2 +
>>>>    lib/kite_deflate.c | 1267 ++++++++++++++++++++++++++++++++++++++++++++
>>>>    2 files changed, 1269 insertions(+)
>>>>    create mode 100644 lib/kite_deflate.c
>>>
>>> Have you considered simply running any standard compressor multiple times to
>>> find the maximum input size that compresses to less than or equal to the desired
>>> output size?  It may sound too slow, but it can be made to do the search in a
>>
>> Thanks for your interest and reply!
>>
>> Actually I've tried several ways before (just like fitblk.c or binary search),
>> but the compression ratios is sliently impacted
> 
> That's strange, since the method I suggested should improve the compression
> ratio.

It depends, the current kite_deflate approach is already better than zlib (no
matter fitblk.c or binary search.)  Compared to libdeflate, yes, it doesn't
support optimal deflate block splitting and some advanced matchfinder other
than hash chains so I'm afraid not.

This encoder is only used to replace zlib and I think we could integrate
`libdeflate` like what you suggested first.

> 
>> and compression speed is really
>> impacted (maybe we need to develop segment-splitted multi-threadded compression
>> as well, and it's developping by a college student now but I'm not sure the
>> progress.)
> 
> I'm surprised that multi-threaded compression isn't something you have already!

Because my own time is limited and no more volunteer for this, rolling-hash
compressed data deduplication is more useful and natual to EROFS since
EROFS has byte-granularity uncompressed offset/length indexes (kernel
improvements always have higher priority than userspace stuffs since
userspace stuffs can easily be bumpped with new released versions.)

> 
>>
>> I think we could use this way first for the new Zstd support (if they don't have
>> bandwidth to add a offical fixed-sized output approach), but considering deflate
>> algorithm is quite easy for me to understand so at least I could have a simple
>> built-in one to avoid external dependency (mainly considering zlib since it's
>> quite outdated).  Since EROFS is actually offline compression so the compressed
>> data correctness can always be checked in advance before image release.
> 
> Writing an optimized compressor, especially one that implements high compression
> levels, is very hard.  Are you sure it's something you want to implement and
> maintain in erofs-utils?
> 
>> Also I have more ideas to optimize the last symbol from matchfinders (even lazy
>> matching) for fixed-sized output approaches to get even better compression
>> ratios (especially for smaller filesystem cluster like 4kb.)
> 
> Yes, there are some tricks that compressors that natively support
> fixed-size-output mode could use in order to avoid redundant work.
> 
>>> smart way (binary search + heuristics).  Also, it would easily work with every
>>> compressor, and it would always produce the optimal input size.
>>>
>>> I wrote a proof-of-concept patch that implements this idea in the benchmark
>>> program in libdeflate.  It finds the optimal input size in about 8 attempts on
>>> average for a target output size of 4096, or about 13 for a target output size
>>> of 65536.  Note, if an optimal solution is not needed, it could be sped up
>>> slightly by stopping when the output size is at, or just under (if desired), the
>>> target output size.  Also keep in mind that any compression level can be used.
>>>
>>> The full code I tested is currently at
>>> https://github.com/ebiggers/libdeflate/tree/fixed-output-size, but below is the
>>> actual algorithm:
>>
>> If libdeflate officically supports this mode, I'm very happy to integrate
>> libdeflate in this way as an alternative encoder (if you could merge this), and
>> if the compression speed is improved, I could switch it to the default encoder
>> then. In the other words, I really hope there could be an official deflate
>> encoder to support this mode as well.  Also I think the in-kernel zlib
>> decompression is somewhat slow just due to some non-technical reasons, I might
>> need to improve this eveutually but it's no rush for us compared to support
>> DEFLATE format first as well.
>>
>>>
>>> /*
>>>    * Compresses the largest prefix of 'in', with maximum size 'in_nbytes', whose
>>>    * compressed output is at most 'target_output_size' bytes.  The compressed size
>>>    * is returned as the return value, and the uncompressed size is returned in
>>>    * *actual_in_nbytes_ret.  The output buffer 'out' has size 'out_nbytes_avail',
>>>    * which should be at least 'target_output_size + 9'.  'tmpbuf' must be a buffer
>>>    * the same size as 'out'.
>>>    */
>>> static size_t
>>> do_compress_withfixedoutputsize(struct compressor *c,
>>> 				const u8 *in, size_t in_nbytes,
>>> 				u8 *out, size_t out_nbytes_avail,
>>> 				size_t target_output_size,
>>> 				size_t last_uncompressed_size,
>>> 				u8 *tmpbuf,
>>> 				size_t *actual_in_nbytes_ret)
>>> {
>>> 	size_t l = 0; /* largest input that fits so far */
>>> 	size_t l_csize = 0;
>>> 	size_t r = in_nbytes + 1; /* smallest input that doesn't fit so far */
>>> 	size_t m;
>>>
>>> 	if (last_uncompressed_size)
>>> 		m = last_uncompressed_size * 15 / 16;
>>> 	else
>>> 		m = target_output_size * 4;
>>> 	for (;;) {
>>> 		size_t csize;
>>>
>>> 		m = MAX(m, l + 1);
>>> 		m = MIN(m, r - 1);
>>>
>>> 		csize = do_compress(c, in, m, tmpbuf, out_nbytes_avail);
>>> 		if (csize > 0 && csize <= target_output_size) {
>>> 			/* Fits */
>>> 			memcpy(out, tmpbuf, csize);
>>> 			l = m;
>>> 			l_csize = csize;
>>> 			if (r <= l + 1)
>>> 				break;
>>> 			/*
>>> 			 * Estimate needed input prefix size based on current
>>> 			 * compression ratio.
>>> 			 */
>>> 			m = (target_output_size * m) / csize;
>>> 		} else {
>>> 			/* Doesn't fit */
>>> 			r = m;
>>> 			if (r <= l + 1)
>>> 				break;
>>> 			m = (l + r) / 2;
>>> 		}
>>> 	}
>>> 	*actual_in_nbytes_ret = l;
>>> 	return l_csize;
>>> }
>>>
>>> What do you think?
>>
>> Thank you for the time and your efforts, I much appreciate if it could be
>> supported so I could add a alternative encoder for this.
>>
> 
> Native fixed-size-output size support in libdeflate is something I'll think
> about.  However, the point I'm making is that it's unclear how valuable native
> fixed-size-output size support in compressors is, given that the search
> algorithm I described above actually seems to work fairly well, and it can
> produce the optimal solution.
> 
> Here's a quick proof of concept patch for mkfs.erofs:
> 
> diff --git a/lib/Makefile.am b/lib/Makefile.am
> index 553b387..f58a684 100644
> --- a/lib/Makefile.am
> +++ b/lib/Makefile.am
> @@ -45,3 +45,4 @@ liberofs_la_SOURCES += compressor_liblzma.c
>   endif
>   
>   liberofs_la_SOURCES += kite_deflate.c compressor_deflate.c
> +liberofs_la_LDFLAGS = -ldeflate
> diff --git a/lib/compressor_deflate.c b/lib/compressor_deflate.c
> index ad76b7c..d7a5e90 100644
> --- a/lib/compressor_deflate.c
> +++ b/lib/compressor_deflate.c
> @@ -6,6 +6,7 @@
>   #include "erofs/internal.h"
>   #include "erofs/print.h"
>   #include "erofs/config.h"
> +#include "libdeflate.h"
>   #include "compressor.h"
>   
>   void *kite_deflate_init(int level, unsigned int dict_size);
> @@ -13,16 +14,64 @@ void kite_deflate_end(void *s);
>   int kite_deflate_destsize(void *s, const u8 *in, u8 *out,
>   			  unsigned int *srcsize, unsigned int target_dstsize);
>   
> +#define USE_LIBDEFLATE
> +
>   static int deflate_compress_destsize(const struct erofs_compress *c,
>   				     const void *src, unsigned int *srcsize,
>   				     void *dst, unsigned int dstsize)
>   {
> +#ifdef USE_LIBDEFLATE
> +	static size_t last_uncompressed_size = 0;
> +	size_t l = 0; /* largest input that fits so far */
> +	size_t l_csize = 0;
> +	size_t r = *srcsize + 1; /* smallest input that doesn't fit so far */
> +	size_t m;
> +	u8 tmpbuf[dstsize + 9];
> +
> +	if (last_uncompressed_size)
> +		m = last_uncompressed_size * 15 / 16;
> +	else
> +		m = dstsize * 4;
> +	for (;;) {
> +		size_t csize;
> +
> +		m = max(m, l + 1);
> +		m = min(m, r - 1);
> +
> +		csize = libdeflate_deflate_compress(c->private_data, src, m, tmpbuf, dstsize + 9);
> +		/*printf("Tried %zu => %zu\n", m, csize);*/
> +		if (csize > 0 && csize <= dstsize) {
> +			/* Fits */
> +			memcpy(dst, tmpbuf, csize);
> +			l = m;
> +			l_csize = csize;
> +			if (r <= l + 1 || csize + (22 - 2*(int)c->compression_level) >= dstsize)
> +				break;
> +			/*
> +			 * Estimate needed input prefix size based on current
> +			 * compression ratio.
> +			 */
> +			m = (dstsize * m) / csize;
> +		} else {
> +			/* Doesn't fit */
> +			r = m;
> +			if (r <= l + 1)
> +				break;
> +			m = (l + r) / 2;
> +		}
> +	}
> +	/*printf("Choosing %zu => %zu\n", l, l_csize);*/
> +	*srcsize = l;
> +	last_uncompressed_size = l;
> +	return l_csize;
> +#else
>   	int rc = kite_deflate_destsize(c->private_data, src, dst,
>   				       srcsize, dstsize);
>   
>   	if (rc <= 0)
>   		return -EFAULT;
>   	return rc;
> +#endif
>   }
>   
>   static int compressor_deflate_exit(struct erofs_compress *c)
> @@ -30,7 +79,11 @@ static int compressor_deflate_exit(struct erofs_compress *c)
>   	if (!c->private_data)
>   		return -EINVAL;
>   
> +#ifdef USE_LIBDEFLATE
> +	libdeflate_free_compressor(c->private_data);
> +#else
>   	kite_deflate_end(c->private_data);
> +#endif
>   	return 0;
>   }
>   
> @@ -47,8 +100,13 @@ static int compressor_deflate_init(struct erofs_compress *c)
>   static int erofs_compressor_deflate_setlevel(struct erofs_compress *c,
>   					     int compression_level)
>   {
> -	void *s;
> +	if (compression_level < 0)
> +		compression_level = erofs_compressor_deflate.default_level;
>   
> +#ifdef USE_LIBDEFLATE
> +	libdeflate_free_compressor(c->private_data);
> +	c->private_data = libdeflate_alloc_compressor(compression_level);
> +#else
>   	if (c->private_data) {
>   		kite_deflate_end(c->private_data);
>   		c->private_data = NULL;
> @@ -57,11 +115,13 @@ static int erofs_compressor_deflate_setlevel(struct erofs_compress *c,
>   	if (compression_level < 0)
>   		compression_level = erofs_compressor_deflate.default_level;
>   
> -	s = kite_deflate_init(compression_level, cfg.c_dict_size);
> +	void *s = kite_deflate_init(compression_level, cfg.c_dict_size);
>   	if (IS_ERR(s))
>   		return PTR_ERR(s);
>   
>   	c->private_data = s;
> +#endif
> +
>   	c->compression_level = compression_level;
>   	return 0;
>   }
> @@ -69,7 +129,11 @@ static int erofs_compressor_deflate_setlevel(struct erofs_compress *c,
>   const struct erofs_compressor erofs_compressor_deflate = {
>   	.name = "deflate",
>   	.default_level = 1,
> +#ifdef USE_LIBDEFLATE
> +	.best_level = 12,
> +#else
>   	.best_level = 9,
> +#endif
>   	.init = compressor_deflate_init,
>   	.exit = compressor_deflate_exit,
>   	.setlevel = erofs_compressor_deflate_setlevel,
> 
> Here are some quick benchmarks I did of mkfs.erofs showing the size of the
> resulting filesystem and the time it took for mkfs.erofs to run:
> 
> -zdeflate,1
>      kite_deflate: 10772480 bytes in 0.474s
>      libdeflate: 10366976 bytes in 0.468s
> -zdeflate,3
>      kite_deflate: 10203136 bytes in 0.511s
>      libdeflate: 10092544 bytes in 0.702s
> -zdeflate,5
>      kite_deflate: 10080256 bytes in 0.596s
>      libdeflate: 9973760 bytes in 0.896s
> -zdeflate,6
>      kite_deflate: 9977856 bytes in 0.650s
>      libdeflate: 9936896 bytes in 0.981s
> -zdeflate,7
>      kite_deflate: 9969664 bytes in 0.690s
>      libdeflate: 9920512 bytes in 1.187s
> -zdeflate,9
>      kite_deflate: 9945088 bytes in 0.986s
>      libdeflate: 9871360 bytes in 2.041s
> -zdeflate,12
>      kite_deflate: N/A
>      libdeflate: 9740288 bytes in 28.080s
> 
> So it seems my "naive" search algorithm, when combined with libdeflate, actually
> does about as well as kite_deflate...  Plus it automatically brings in support
> for higher compression levels.

kite_deflate still has some room to improve since I just
make it work to replace the original zlib, just TBH.

Yeah, I could integrate libdeflate like this first,
thanks Eric!

Thanks,
Gao Xiang


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 2/4] erofs-utils: add a built-in DEFLATE compressor
  2023-07-10  4:51         ` Gao Xiang
@ 2023-07-10  4:55           ` Gao Xiang
  0 siblings, 0 replies; 12+ messages in thread
From: Gao Xiang @ 2023-07-10  4:55 UTC (permalink / raw)
  To: Eric Biggers; +Cc: linux-erofs


(forgot to respond something...)

On 2023/7/10 12:51, Gao Xiang wrote:
> 
> 

...

>>
>>>
>>> I think we could use this way first for the new Zstd support (if they don't have
>>> bandwidth to add a offical fixed-sized output approach), but considering deflate
>>> algorithm is quite easy for me to understand so at least I could have a simple
>>> built-in one to avoid external dependency (mainly considering zlib since it's
>>> quite outdated).  Since EROFS is actually offline compression so the compressed
>>> data correctness can always be checked in advance before image release.
>>
>> Writing an optimized compressor, especially one that implements high compression
>> levels, is very hard.  Are you sure it's something you want to implement and
>> maintain in erofs-utils?

Nope, as I said earlier, I'd like to write a simple one to replace zlib
functionality (kite_deflate already does), and I hope more popular encoders
could have offical modes for EROFS in the long term.  That is something that
I wish to the opensource communities...

Thanks,
Gao Xiang

^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2023-07-10  4:55 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2023-07-09 18:25 [PATCH 0/4] erofs-utils: add DEFLATE compression support Gao Xiang
2023-07-09 18:25 ` [PATCH 1/4] erofs-utils: switch to effective unaligned access Gao Xiang
2023-07-09 18:25 ` [PATCH 2/4] erofs-utils: add a built-in DEFLATE compressor Gao Xiang
2023-07-10  2:33   ` Eric Biggers
2023-07-10  3:01     ` Gao Xiang
2023-07-10  4:16       ` Eric Biggers
2023-07-10  4:51         ` Gao Xiang
2023-07-10  4:55           ` Gao Xiang
2023-07-09 18:25 ` [PATCH 3/4] erofs-utils: fuse: add DEFLATE algorithm support Gao Xiang
2023-07-09 18:25 ` [PATCH 4/4] erofs-utils: mkfs: " Gao Xiang
2023-07-09 18:45 ` [PATCH v2 3/4] erofs-utils: fuse: " Gao Xiang
2023-07-09 18:45   ` [PATCH v2 4/4] erofs-utils: mkfs: " Gao Xiang

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox